diff --git a/lib/logitech/unifying_receiver/__init__.py b/lib/logitech/unifying_receiver/__init__.py index 4ca40a42..bbef95fb 100644 --- a/lib/logitech/unifying_receiver/__init__.py +++ b/lib/logitech/unifying_receiver/__init__.py @@ -27,7 +27,7 @@ del logging from .common import strhex from .base import NoReceiver, NoSuchDevice, DeviceUnreachable -from .receiver import Receiver, PairedDevice, MAX_PAIRED_DEVICES +from .receiver import Receiver, PairedDevice from .hidpp20 import FeatureNotSupported, FeatureCallError from . import listener diff --git a/lib/logitech/unifying_receiver/base.py b/lib/logitech/unifying_receiver/base.py index cba17ddf..f174f0cb 100644 --- a/lib/logitech/unifying_receiver/base.py +++ b/lib/logitech/unifying_receiver/base.py @@ -58,19 +58,22 @@ class DeviceUnreachable(_KwException): # # +# vendor_id, product_id, interface number, driver +DEVICE_UNIFYING_RECEIVER = (0x046d, 0xc52b, 2, 'logitech-djreceiver') +DEVICE_UNIFYING_RECEIVER_2 = (0x046d, 0xc532, 2, 'logitech-djreceiver') +#DEVICE_NANO_RECEIVER = (0x046d, 0xc526, 1, 'generic-usb') + + def receivers(): """List all the Linux devices exposed by the UR attached to the machine.""" - # (Vendor ID, Product ID) = ('Logitech', 'Unifying Receiver') - # interface 2 if the actual receiver interface + for d in _hid.enumerate(*DEVICE_UNIFYING_RECEIVER): + yield d + for d in _hid.enumerate(*DEVICE_UNIFYING_RECEIVER_2): + yield d + #for d in _hid.enumerate(*DEVICE_NANO_RECEIVER): + # yield d - for d in _hid.enumerate(0x046d, 0xc52b, 2): - if d.driver == 'logitech-djreceiver': - yield d - # apparently there are TWO product ids possible for the UR? - for d in _hid.enumerate(0x046d, 0xc532, 2): - if d.driver == 'logitech-djreceiver': - yield d def open_path(path): diff --git a/lib/logitech/unifying_receiver/hidpp10.py b/lib/logitech/unifying_receiver/hidpp10.py index e70e1fb9..9d7ab648 100644 --- a/lib/logitech/unifying_receiver/hidpp10.py +++ b/lib/logitech/unifying_receiver/hidpp10.py @@ -133,6 +133,13 @@ def get_firmware(device): fw = _FirmwareInfo(FIRMWARE_KIND.Firmware, '', fw_version, None) firmware.append(fw) + if device.kind is None and device.max_devices == 1: + # Nano receiver + return firmware + if device.kind is not None and not device._unifying: + # Nano device + return firmware + reply = device.request(0x81F1, 0x04) if reply: bl_version = _strhex(reply[1:3]) diff --git a/lib/logitech/unifying_receiver/receiver.py b/lib/logitech/unifying_receiver/receiver.py index 9147c801..200ff645 100644 --- a/lib/logitech/unifying_receiver/receiver.py +++ b/lib/logitech/unifying_receiver/receiver.py @@ -29,9 +29,10 @@ class PairedDevice(object): def __init__(self, receiver, number): assert receiver self.receiver = _proxy(receiver) - assert number > 0 and number <= MAX_PAIRED_DEVICES + assert number > 0 and number <= receiver.max_devices self.number = number + self._unifying = receiver.max_devices > 1 self._protocol = None self._wpid = None self._power_switch = None @@ -43,7 +44,7 @@ class PairedDevice(object): self._firmware = None self._keys = None - self.features = _hidpp20.FeaturesArray(self) + self.features = _hidpp20.FeaturesArray(self) if self._unifying else None self._registers = None self._settings = None @@ -57,25 +58,29 @@ class PairedDevice(object): @property def wpid(self): if self._wpid is None: - pair_info = self.receiver.request(0x83B5, 0x20 + self.number - 1) - if pair_info: - self._wpid = _strhex(pair_info[3:5]) - if self._kind is None: - kind = ord(pair_info[7:8]) & 0x0F - self._kind = _hidpp10.DEVICE_KIND[kind] - if self._polling_rate is None: - self._polling_rate = ord(pair_info[2:3]) + if self._unifying: + pair_info = self.receiver.request(0x83B5, 0x20 + self.number - 1) + if pair_info: + self._wpid = _strhex(pair_info[3:5]) + if self._kind is None: + kind = ord(pair_info[7:8]) & 0x0F + self._kind = _hidpp10.DEVICE_KIND[kind] + if self._polling_rate is None: + self._polling_rate = ord(pair_info[2:3]) + # else: + # device_info = self.receiver.request(0x83B5, 0x04) + # self.wpid = _strhex(device_info[3:5]) return self._wpid @property def polling_rate(self): - if self._polling_rate is None: + if self._polling_rate is None and self._unifying: self.wpid, 0 return self._polling_rate @property def power_switch_location(self): - if self._power_switch is None: + if self._power_switch is None and self._unifying: ps = self.receiver.request(0x83B5, 0x30 + self.number - 1) if ps: ps = ord(ps[9:10]) & 0x0F @@ -84,7 +89,7 @@ class PairedDevice(object): @property def codename(self): - if self._codename is None: + if self._codename is None and self._unifying: codename = self.receiver.request(0x83B5, 0x40 + self.number - 1) if codename: self._codename = codename[2:].rstrip(b'\x00').decode('utf-8') @@ -93,7 +98,7 @@ class PairedDevice(object): @property def name(self): - if self._name is None: + if self._name is None and self._unifying: if self.codename in _descriptors.DEVICES: self._name, self._kind = _descriptors.DEVICES[self._codename][:2] elif self.protocol >= 2.0: @@ -102,7 +107,7 @@ class PairedDevice(object): @property def kind(self): - if self._kind is None: + if self._kind is None and self._unifying: pair_info = self.receiver.request(0x83B5, 0x20 + self.number - 1) if pair_info: kind = ord(pair_info[7:8]) & 0x0F @@ -128,13 +133,13 @@ class PairedDevice(object): @property def serial(self): - if self._serial is None: + if self._serial is None and self._unifying: self._serial = _hidpp10.get_serial(self) return self._serial or '?' @property def keys(self): - if self._keys is None: + if self._keys is None and self._unifying: self._keys = _hidpp20.get_keys(self) or () return self._keys @@ -165,7 +170,8 @@ class PairedDevice(object): return _base.request(self.receiver.handle, self.number, request_id, *params) def feature_request(self, feature, function=0x00, *params): - return _hidpp20.feature_request(self, feature, function, *params) + if self._unifying: + return _hidpp20.feature_request(self, feature, function, *params) def ping(self): return _base.ping(self.receiver.handle, self.number) is not None @@ -197,9 +203,7 @@ class Receiver(object): The paired devices are available through the sequence interface. """ number = 0xFF - name = 'Unifying Receiver' kind = None - max_devices = MAX_PAIRED_DEVICES def __init__(self, handle, path=None): assert handle @@ -207,7 +211,19 @@ class Receiver(object): assert path self.path = path - self._serial = None + serial_reply = self.request(0x83B5, 0x03) + assert serial_reply + self._serial = _strhex(serial_reply[1:5]) + self.max_devices = ord(serial_reply[6:7][0]) + + if self.max_devices == 1: + self.name = 'Nano Receiver' + elif self.max_devices == 6: + self.name = 'Unifying Receiver' + else: + raise Exception("unknown receiver type") + self._str = '<%s(%s,%s%s)>' % (self.name.replace(' ', ''), self.path, '' if type(self.handle) == int else 'T', self.handle) + self._firmware = None self._devices = {} @@ -221,8 +237,9 @@ class Receiver(object): @property def serial(self): - if self._serial is None and self.handle: - self._serial = _hidpp10.get_serial(self) + assert self._serial + # if self._serial is None and self.handle: + # self._serial = _hidpp10.get_serial(self) return self._serial @property @@ -260,6 +277,15 @@ class Receiver(object): raise IndexError("device number %d already registered" % number) dev = PairedDevice(self, number) # create a device object, but only use it if the receiver knows about it + + # Nano receiver + #if self.max_devices == 1 and number == 1: + # # the Nano receiver does not provide the wpid + # _log.info("%s: found Nano device %d (%s)", self, number, dev.serial) + # # dev._wpid = self.serial + ':1' + # self._devices[number] = dev + # return dev + if dev.wpid: _log.info("found device %d (%s)", number, dev.wpid) self._devices[number] = dev @@ -283,7 +309,7 @@ class Receiver(object): return _base.request(self.handle, 0xFF, request_id, *params) def __iter__(self): - for number in range(1, 1 + MAX_PAIRED_DEVICES): + for number in range(1, 1 + self.max_devices): if number in self._devices: dev = self._devices[number] else: @@ -301,7 +327,7 @@ class Receiver(object): if type(key) != int: raise TypeError('key must be an integer') - if key < 1 or key > MAX_PAIRED_DEVICES: + if key < 1 or key > self.max_devices: raise IndexError(key) return self.register_new_device(key) @@ -329,7 +355,7 @@ class Receiver(object): return self.__contains__(dev.number) def __str__(self): - return '' % (self.path, '' if type(self.handle) == int else 'T', self.handle) + return self._str __unicode__ = __repr__ = __str__ __bool__ = __nonzero__ = lambda self: self.handle is not None diff --git a/lib/logitech/unifying_receiver/status.py b/lib/logitech/unifying_receiver/status.py index a98fe33d..7eb1f777 100644 --- a/lib/logitech/unifying_receiver/status.py +++ b/lib/logitech/unifying_receiver/status.py @@ -12,7 +12,7 @@ from logging import getLogger, DEBUG as _DEBUG _log = getLogger('LUR.status') del getLogger -from .common import NamedInts as _NamedInts +from .common import NamedInts as _NamedInts, strhex as _strhex from . import hidpp10 as _hidpp10 from . import hidpp20 as _hidpp20 @@ -223,8 +223,24 @@ class DeviceStatus(dict): self[ENCRYPTED] = link_encrypyed self._changed(link_established) - elif n.address == 0x03: - _log.warn("%s: connection notification with eQuad protocol, ignored: %s", self._device.number, n) + elif n.address == 0x03: # eQuad protocol + # Nano devices might not have been initialized fully + if self._device._kind is None: + kind = ord(n.data[:1]) & 0x0F + self._device._kind = _hidpp10.DEVICE_KIND[kind] + if self._device._wpid is None: + self._device._wpid = _strhex(n.data[2:3] + n.data[1:2]) + + flags = ord(n.data[:1]) & 0xF0 + link_encrypyed = bool(flags & 0x20) + link_established = not (flags & 0x40) + if _log.isEnabledFor(_DEBUG): + sw_present = bool(flags & 0x10) + has_payload = bool(flags & 0x80) + _log.debug("%s: eQuad connection notification: software=%s, encrypted=%s, link=%s, payload=%s", + self._device, sw_present, link_encrypyed, link_established, has_payload) + self[ENCRYPTED] = link_encrypyed + self._changed(link_established) else: _log.warn("%s: connection notification with unknown protocol %02X: %s", self._device.number, n.address, n) diff --git a/rules.d/99-logitech-unifying-receiver.rules b/rules.d/99-logitech-unifying-receiver.rules index d34343f9..d7b40bc1 100644 --- a/rules.d/99-logitech-unifying-receiver.rules +++ b/rules.d/99-logitech-unifying-receiver.rules @@ -6,7 +6,10 @@ # Make sure the plugdev group exists on your system and your user is a member # before applying these rules. -# HIDAPI/hidraw +# HIDAPI/hidraw for Logitech Unifying Receiver ACTION=="add", KERNEL=="hidraw*", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c52b", GROUP="plugdev", MODE="0660" +# HIDAPI/hidraw for Logitech Nano Receiver +#ACTION=="add", KERNEL=="hidraw*", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c526", GROUP="plugdev", MODE="0660" + # vim: ft=udevrules diff --git a/tools/scan-registers.sh b/tools/scan-registers.sh index 20aa8410..1fe21df7 100755 --- a/tools/scan-registers.sh +++ b/tools/scan-registers.sh @@ -1,7 +1,7 @@ #!/bin/sh if test -z "$1"; then - echo "Use: $0 " + echo "Use: $0 []" exit 2 fi @@ -14,10 +14,10 @@ for x in $z; do for y in $z; do echo "10 0${1} 81${x}${y} 000000" done -done | "$HC" --hidpp | grep -v ' 8F.. ..0[12]' | grep -B 1 '^>> ' +done | "$HC" --hidpp $2 | grep -v ' 8F.. ..0[12]' | grep -B 1 '^>> ' for x in $z; do for y in $z; do echo "10 0${1} 83${x}${y} 000000" done -done | "$HC" --hidpp | grep -v ' 8F.. ..0[12]' | grep -B 1 '^>> ' +done | "$HC" --hidpp $2 | grep -v ' 8F.. ..0[12]' | grep -B 1 '^>> '