From 886df1daaf5b352420b30ce5e4dacf0d421c54ce Mon Sep 17 00:00:00 2001 From: "Peter F. Patel-Schneider" Date: Tue, 2 Nov 2021 09:45:52 -0400 Subject: [PATCH] receiver: add support for Bolt receiver (no pairing yet) --- lib/logitech_receiver/base_usb.py | 26 ++++++++++++++++++++++++-- lib/logitech_receiver/hidpp10.py | 1 + lib/logitech_receiver/receiver.py | 27 ++++++++++++++++++++++++--- lib/solaar/listener.py | 2 +- 4 files changed, 50 insertions(+), 6 deletions(-) diff --git a/lib/logitech_receiver/base_usb.py b/lib/logitech_receiver/base_usb.py index c258328a..72e1b454 100644 --- a/lib/logitech_receiver/base_usb.py +++ b/lib/logitech_receiver/base_usb.py @@ -39,12 +39,24 @@ from .i18n import _ _DRIVER = ('hid-generic', 'generic-usb', 'logitech-djreceiver') +_bolt_receiver = lambda product_id: { + 'vendor_id': 0x046d, + 'product_id': product_id, + 'usb_interface': 2, + 'hid_driver': _DRIVER, # noqa: F821 + 'name': _('Bolt Receiver'), + 'receiver_kind': 'bolt', + 'max_devices': 6, + 'may_unpair': True +} + _unifying_receiver = lambda product_id: { 'vendor_id': 0x046d, 'product_id': product_id, 'usb_interface': 2, 'hid_driver': _DRIVER, # noqa: F821 - 'name': _('Unifying Receiver') + 'name': _('Unifying Receiver'), + 'receiver_kind': 'unifying' } _nano_receiver = lambda product_id: { @@ -53,6 +65,7 @@ _nano_receiver = lambda product_id: { 'usb_interface': 1, 'hid_driver': _DRIVER, # noqa: F821 'name': _('Nano Receiver'), + 'receiver_kind': 'nano', 'may_unpair': False, 're_pairs': True } @@ -63,6 +76,7 @@ _nano_receiver_no_unpair = lambda product_id: { 'usb_interface': 1, 'hid_driver': _DRIVER, # noqa: F821 'name': _('Nano Receiver'), + 'receiver_kind': 'nano', 'may_unpair': False, 'unpair': False, 're_pairs': True @@ -74,6 +88,7 @@ _nano_receiver_max2 = lambda product_id: { 'usb_interface': 1, 'hid_driver': _DRIVER, # noqa: F821 'name': _('Nano Receiver'), + 'receiver_kind': 'nano', 'max_devices': 2, 'may_unpair': False, 're_pairs': True @@ -85,6 +100,7 @@ _nano_receiver_maxn = lambda product_id, max: { 'usb_interface': 1, 'hid_driver': _DRIVER, # noqa: F821 'name': _('Nano Receiver'), + 'receiver_kind': 'nano', 'max_devices': max, 'may_unpair': False, 're_pairs': True @@ -95,7 +111,8 @@ _lenovo_receiver = lambda product_id: { 'product_id': product_id, 'usb_interface': 1, 'hid_driver': _DRIVER, # noqa: F821 - 'name': _('Nano Receiver') + 'name': _('Nano Receiver'), + 'receiver_kind': 'nano' } _lightspeed_receiver = lambda product_id: { @@ -112,12 +129,16 @@ _ex100_receiver = lambda product_id: { 'usb_interface': 1, 'hid_driver': _DRIVER, # noqa: F821 'name': _('EX100 Receiver 27 Mhz'), + 'receiver_kind': '27Mhz', 'max_devices': 4, 'may_unpair': False, 're_pairs': True, 'ex100_27mhz_wpid_fix': True } +# Bolt receivers (marked with the yellow lightning bolt logo) +BOLT_RECEIVER_C548 = _bolt_receiver(0xc548) + # standard Unifying receivers (marked with the orange Unifying logo) UNIFYING_RECEIVER_C52B = _unifying_receiver(0xc52b) UNIFYING_RECEIVER_C532 = _unifying_receiver(0xc532) @@ -151,6 +172,7 @@ LIGHTSPEED_RECEIVER_C541 = _lightspeed_receiver(0xc541) LIGHTSPEED_RECEIVER_C547 = _lightspeed_receiver(0xc547) ALL = ( + BOLT_RECEIVER_C548, UNIFYING_RECEIVER_C52B, UNIFYING_RECEIVER_C532, NANO_RECEIVER_ADVANCED, diff --git a/lib/logitech_receiver/hidpp10.py b/lib/logitech_receiver/hidpp10.py index a08ec71d..06ef05f2 100644 --- a/lib/logitech_receiver/hidpp10.py +++ b/lib/logitech_receiver/hidpp10.py @@ -143,6 +143,7 @@ INFO_SUBREGISTERS = _NamedInts( pairing_information=0x20, # 0x2N, by connected device extended_pairing_information=0x30, # 0x3N, by connected device device_name=0x40, # 0x4N, by connected device + bolt_pairing_information=0x50, # 0x5N, by connected device ) # Flags taken from https://drive.google.com/file/d/0BxbRzx7vEV7eNDBheWY0UHM5dEU/view?usp=sharing diff --git a/lib/logitech_receiver/receiver.py b/lib/logitech_receiver/receiver.py index 720a9906..930f6130 100644 --- a/lib/logitech_receiver/receiver.py +++ b/lib/logitech_receiver/receiver.py @@ -59,9 +59,13 @@ class Receiver: product_info = _product_information(self.product_id) if not product_info: raise Exception('Unknown receiver type', self.product_id) + self.receiver_kind = product_info.get('receiver_kind', 'unknown') # read the serial immediately, so we can find out max_devices - serial_reply = self.read_register(_R.receiver_info, _IR.receiver_information) + if self.receiver_kind == 'bolt': + serial_reply = None + else: + serial_reply = self.read_register(_R.receiver_info, _IR.receiver_information) if serial_reply: self.serial = _strhex(serial_reply[1:5]) self.max_devices = ord(serial_reply[6:7]) @@ -73,7 +77,7 @@ class Receiver: self.may_unpair = product_info['unpair'] else: self.may_unpair = self.write_register(_R.receiver_pairing) is None - else: # handle receivers that don't have a serial number specially (i.e., c534) + else: # handle receivers that don't have a serial number specially (i.e., c534 and Bolt receivers) self.serial = None self.max_devices = product_info.get('max_devices', 1) self.may_unpair = product_info.get('may_unpair', False) @@ -138,6 +142,8 @@ class Receiver: return flag_bits def device_codename(self, n): + if self.receiver_kind == 'bolt': + return codename = self.read_register(_R.receiver_info, _IR.device_name + n - 1) if codename: codename_length = ord(codename[1:2]) @@ -145,6 +151,14 @@ class Receiver: return codename.decode('ascii') def device_pairing_information(self, n): + if self.receiver_kind == 'bolt': + pair_info = self.read_register(_R.receiver_info, _IR.bolt_pairing_information + n) + if pair_info: + wpid = _strhex(pair_info[3:4]) + _strhex(pair_info[2:3]) + kind = _hidpp10.DEVICE_KIND[ord(pair_info[1:2]) & 0x0F] + return wpid, kind, 0 + else: + return '0000', _hidpp10.DEVICE_KIND[0], 0 pair_info = self.read_register(_R.receiver_info, _IR.pairing_information + n - 1) polling_rate = 0 if pair_info: # may be either a Unifying receiver, or an Unifying-ready receiver @@ -168,8 +182,15 @@ class Receiver: return wpid, kind, polling_rate def device_extended_pairing_information(self, n): - pair_info = self.read_register(_R.receiver_info, _IR.extended_pairing_information + n - 1) power_switch = '(unknown)' + if self.receiver_kind == 'bolt': + pair_info = self.read_register(_R.receiver_info, _IR.bolt_pairing_information + n) + if pair_info: + serial = _strhex(pair_info[4:8]) + return serial, power_switch + else: + return '?', power_switch + pair_info = self.read_register(_R.receiver_info, _IR.extended_pairing_information + n - 1) if pair_info: power_switch = _hidpp10.POWER_SWITCH_LOCATION[ord(pair_info[9:10]) & 0x0F] else: # some Nano receivers? diff --git a/lib/solaar/listener.py b/lib/solaar/listener.py index 941ab3f8..d716f86b 100644 --- a/lib/solaar/listener.py +++ b/lib/solaar/listener.py @@ -224,7 +224,7 @@ class ReceiverListener(_listener.EventsListener): return elif n.sub_id == 0x41: if not already_known: - if n.address == 0x0A: + if n.address == 0x0A and not self.receiver.receiver_kind == 'bolt': # some Nanos send a notification even if no new pairing - check that there really is a device there if self.receiver.read_register(_R.receiver_info, _IR.pairing_information + n.devnumber - 1) is None: return