From 2685d25762713324fe8f2b5c5232a10c3efb6476 Mon Sep 17 00:00:00 2001 From: Matthias Hagmann <16444067+MattHag@users.noreply.github.com> Date: Wed, 14 Feb 2024 20:36:42 +0100 Subject: [PATCH] logitech_receiver: Move hidpp10 constants into new module Related #1097 --- lib/logitech_receiver/base.py | 12 +- lib/logitech_receiver/descriptors.py | 4 +- lib/logitech_receiver/device.py | 8 +- lib/logitech_receiver/hidpp10.py | 152 +------------------- lib/logitech_receiver/hidpp10_constants.py | 151 +++++++++++++++++++ lib/logitech_receiver/receiver.py | 23 +-- lib/logitech_receiver/settings_templates.py | 6 +- lib/solaar/listener.py | 8 +- 8 files changed, 183 insertions(+), 181 deletions(-) create mode 100644 lib/logitech_receiver/hidpp10_constants.py diff --git a/lib/logitech_receiver/base.py b/lib/logitech_receiver/base.py index 0b48e1aab5..625d1edbd2 100644 --- a/lib/logitech_receiver/base.py +++ b/lib/logitech_receiver/base.py @@ -30,7 +30,7 @@ import hidapi as _hid -from . import hidpp10 as _hidpp10 +from . import hidpp10_constants as _hidpp10_constants from . import hidpp20 as _hidpp20 from .base_usb import ALL as _RECEIVER_USB_IDS from .base_usb import DEVICES as _DEVICE_IDS @@ -422,9 +422,9 @@ def request(handle, devnumber, request_id, *params, no_reply=False, return_error if logger.isEnabledFor(logging.DEBUG): logger.debug( '(%s) device 0x%02X error on request {%04X}: %d = %s', handle, devnumber, request_id, error, - _hidpp10.ERROR[error] + _hidpp10_constants.ERROR[error] ) - return _hidpp10.ERROR[error] if return_error else None + return _hidpp10_constants.ERROR[error] if return_error else None if reply_data[:1] == b'\xFF' and reply_data[1:3] == request_data[:2]: # a HID++ 2.0 feature call returned with an error error = ord(reply_data[3:4]) @@ -507,11 +507,11 @@ def ping(handle, devnumber, long_message=False): if report_id == HIDPP_SHORT_MESSAGE_ID and reply_data[:1] == b'\x8F' and \ reply_data[1:3] == request_data[:2]: # error response error = ord(reply_data[3:4]) - if error == _hidpp10.ERROR.invalid_SubID__command: # a valid reply from a HID++ 1.0 device + if error == _hidpp10_constants.ERROR.invalid_SubID__command: # a valid reply from a HID++ 1.0 device return 1.0 - if error == _hidpp10.ERROR.resource_error or error == _hidpp10.ERROR.connection_request_failed: + if error == _hidpp10_constants.ERROR.resource_error or error == _hidpp10_constants.ERROR.connection_request_failed: return # device unreachable - if error == _hidpp10.ERROR.unknown_device: # no paired device with that number + if error == _hidpp10_constants.ERROR.unknown_device: # no paired device with that number logger.error('(%s) device %d error on ping request: unknown device', handle, devnumber) raise exceptions.NoSuchDevice(number=devnumber, request=request_id) diff --git a/lib/logitech_receiver/descriptors.py b/lib/logitech_receiver/descriptors.py index 6c709ad987..bae4e7bfc5 100644 --- a/lib/logitech_receiver/descriptors.py +++ b/lib/logitech_receiver/descriptors.py @@ -28,8 +28,8 @@ from . import settings_templates as _ST from .common import NamedInts as _NamedInts -from .hidpp10 import DEVICE_KIND as _DK -from .hidpp10 import REGISTERS as _R +from .hidpp10_constants import DEVICE_KIND as _DK +from .hidpp10_constants import REGISTERS as _R # # diff --git a/lib/logitech_receiver/device.py b/lib/logitech_receiver/device.py index 18799f8959..56bcf0f3fa 100644 --- a/lib/logitech_receiver/device.py +++ b/lib/logitech_receiver/device.py @@ -27,17 +27,17 @@ from . import base as _base from . import descriptors as _descriptors -from . import hidpp10 as _hidpp10 from . import hidpp20 as _hidpp20 +from . import hidpp10_constants as _hidpp10_constants from .common import strhex as _strhex from .settings_templates import check_feature_settings as _check_feature_settings logger = logging.getLogger(__name__) -_R = _hidpp10.REGISTERS -_IR = _hidpp10.INFO_SUBREGISTERS +_R = _hidpp10_constants.REGISTERS +_IR = _hidpp10_constants.INFO_SUBREGISTERS -KIND_MAP = {kind: _hidpp10.DEVICE_KIND[str(kind)] for kind in _hidpp20.DEVICE_KIND} +KIND_MAP = {kind: _hidpp10_constants.DEVICE_KIND[str(kind)] for kind in _hidpp20.DEVICE_KIND} # # diff --git a/lib/logitech_receiver/hidpp10.py b/lib/logitech_receiver/hidpp10.py index 51de5c2870..2a6873d1e3 100644 --- a/lib/logitech_receiver/hidpp10.py +++ b/lib/logitech_receiver/hidpp10.py @@ -20,164 +20,14 @@ from .common import BATTERY_APPROX as _BATTERY_APPROX from .common import FirmwareInfo as _FirmwareInfo -from .common import NamedInts as _NamedInts from .common import bytes2int as _bytes2int from .common import int2bytes as _int2bytes from .common import strhex as _strhex from .hidpp20 import BATTERY_STATUS, FIRMWARE_KIND +from .hidpp10_constants import REGISTERS logger = logging.getLogger(__name__) -# -# Constants - most of them as defined by the official Logitech HID++ 1.0 -# documentation, some of them guessed. -# - -DEVICE_KIND = _NamedInts( - unknown=0x00, - keyboard=0x01, - mouse=0x02, - numpad=0x03, - presenter=0x04, - remote=0x07, - trackball=0x08, - touchpad=0x09, - headset=0x0D, # not from Logitech documentation - remote_control=0x0E, # for compatibility with HID++ 2.0 - receiver=0x0F # for compatibility with HID++ 2.0 -) - -POWER_SWITCH_LOCATION = _NamedInts( - base=0x01, - top_case=0x02, - edge_of_top_right_corner=0x03, - top_left_corner=0x05, - bottom_left_corner=0x06, - top_right_corner=0x07, - bottom_right_corner=0x08, - top_edge=0x09, - right_edge=0x0A, - left_edge=0x0B, - bottom_edge=0x0C -) - -# Some flags are used both by devices and receivers. The Logitech documentation -# mentions that the first and last (third) byte are used for devices while the -# second is used for the receiver. In practise, the second byte is also used for -# some device-specific notifications (keyboard illumination level). Do not -# simply set all notification bits if the software does not support it. For -# example, enabling keyboard_sleep_raw makes the Sleep key a no-operation unless -# the software is updated to handle that event. -# Observations: -# - wireless and software present were seen on receivers, reserved_r1b4 as well -# - the rest work only on devices as far as we can tell right now -# In the future would be useful to have separate enums for receiver and device notification flags, -# but right now we don't know enough. -# additional flags taken from https://drive.google.com/file/d/0BxbRzx7vEV7eNDBheWY0UHM5dEU/view?usp=sharing -NOTIFICATION_FLAG = _NamedInts( - numpad_numerical_keys=0x800000, - f_lock_status=0x400000, - roller_H=0x200000, - battery_status=0x100000, # send battery charge notifications (0x07 or 0x0D) - mouse_extra_buttons=0x080000, - roller_V=0x040000, - keyboard_sleep_raw=0x020000, # system control keys such as Sleep - keyboard_multimedia_raw=0x010000, # consumer controls such as Mute and Calculator - # reserved_r1b4= 0x001000, # unknown, seen on a unifying receiver - reserved5=0x008000, - reserved4=0x004000, - reserved3=0x002000, - reserved2=0x001000, - software_present=0x000800, # .. no idea - reserved1=0x000400, - keyboard_illumination=0x000200, # illumination brightness level changes (by pressing keys) - wireless=0x000100, # notify when the device wireless goes on/off-line - mx_air_3d_gesture=0x000001, -) - -ERROR = _NamedInts( - invalid_SubID__command=0x01, - invalid_address=0x02, - invalid_value=0x03, - connection_request_failed=0x04, - too_many_devices=0x05, - already_exists=0x06, - busy=0x07, - unknown_device=0x08, - resource_error=0x09, - request_unavailable=0x0A, - unsupported_parameter_value=0x0B, - wrong_pin_code=0x0C -) - -PAIRING_ERRORS = _NamedInts(device_timeout=0x01, device_not_supported=0x02, too_many_devices=0x03, sequence_timeout=0x06) -BOLT_PAIRING_ERRORS = _NamedInts(device_timeout=0x01, failed=0x02) -"""Known registers. -Devices usually have a (small) sub-set of these. Some registers are only -applicable to certain device kinds (e.g. smooth_scroll only applies to mice.""" -REGISTERS = _NamedInts( - # only apply to receivers - receiver_connection=0x02, - receiver_pairing=0xB2, - devices_activity=0x2B3, - receiver_info=0x2B5, - bolt_device_discovery=0xC0, - bolt_pairing=0x2C1, - bolt_uniqueId=0x02FB, - - # only apply to devices - mouse_button_flags=0x01, - keyboard_hand_detection=0x01, - battery_status=0x07, - keyboard_fn_swap=0x09, - battery_charge=0x0D, - keyboard_illumination=0x17, - three_leds=0x51, - mouse_dpi=0x63, - - # apply to both - notifications=0x00, - firmware=0xF1, - - # notifications - passkey_request_notification=0x4D, - passkey_pressed_notification=0x4E, - device_discovery_notification=0x4F, - discovery_status_notification=0x53, - pairing_status_notification=0x54, -) -# Subregisters for receiver_info register -INFO_SUBREGISTERS = _NamedInts( - serial_number=0x01, # not found on many receivers - fw_version=0x02, - receiver_information=0x03, - 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 - bolt_device_name=0x60, # 0x6N01, by connected device, -) - -# Flags taken from https://drive.google.com/file/d/0BxbRzx7vEV7eNDBheWY0UHM5dEU/view?usp=sharing -DEVICE_FEATURES = _NamedInts( - reserved1=0x010000, - special_buttons=0x020000, - enhanced_key_usage=0x040000, - fast_fw_rev=0x080000, - reserved2=0x100000, - reserved3=0x200000, - scroll_accel=0x400000, - buttons_control_resolution=0x800000, - inhibit_lock_key_sound=0x000001, - reserved4=0x000002, - mx_air_3d_engine=0x000004, - host_control_leds=0x000008, - reserved5=0x000010, - reserved6=0x000020, - reserved7=0x000040, - reserved8=0x000080, -) - # # functions # diff --git a/lib/logitech_receiver/hidpp10_constants.py b/lib/logitech_receiver/hidpp10_constants.py new file mode 100644 index 0000000000..eb1c3ad807 --- /dev/null +++ b/lib/logitech_receiver/hidpp10_constants.py @@ -0,0 +1,151 @@ +from .common import NamedInts + +# +# Constants - most of them as defined by the official Logitech HID++ 1.0 +# documentation, some of them guessed. +# + +DEVICE_KIND = NamedInts( + unknown=0x00, + keyboard=0x01, + mouse=0x02, + numpad=0x03, + presenter=0x04, + remote=0x07, + trackball=0x08, + touchpad=0x09, + headset=0x0D, # not from Logitech documentation + remote_control=0x0E, # for compatibility with HID++ 2.0 + receiver=0x0F # for compatibility with HID++ 2.0 +) + +POWER_SWITCH_LOCATION = NamedInts( + base=0x01, + top_case=0x02, + edge_of_top_right_corner=0x03, + top_left_corner=0x05, + bottom_left_corner=0x06, + top_right_corner=0x07, + bottom_right_corner=0x08, + top_edge=0x09, + right_edge=0x0A, + left_edge=0x0B, + bottom_edge=0x0C +) + +# Some flags are used both by devices and receivers. The Logitech documentation +# mentions that the first and last (third) byte are used for devices while the +# second is used for the receiver. In practise, the second byte is also used for +# some device-specific notifications (keyboard illumination level). Do not +# simply set all notification bits if the software does not support it. For +# example, enabling keyboard_sleep_raw makes the Sleep key a no-operation unless +# the software is updated to handle that event. +# Observations: +# - wireless and software present were seen on receivers, reserved_r1b4 as well +# - the rest work only on devices as far as we can tell right now +# In the future would be useful to have separate enums for receiver and device notification flags, +# but right now we don't know enough. +# additional flags taken from https://drive.google.com/file/d/0BxbRzx7vEV7eNDBheWY0UHM5dEU/view?usp=sharing +NOTIFICATION_FLAG = NamedInts( + numpad_numerical_keys=0x800000, + f_lock_status=0x400000, + roller_H=0x200000, + battery_status=0x100000, # send battery charge notifications (0x07 or 0x0D) + mouse_extra_buttons=0x080000, + roller_V=0x040000, + keyboard_sleep_raw=0x020000, # system control keys such as Sleep + keyboard_multimedia_raw=0x010000, # consumer controls such as Mute and Calculator + # reserved_r1b4= 0x001000, # unknown, seen on a unifying receiver + reserved5=0x008000, + reserved4=0x004000, + reserved3=0x002000, + reserved2=0x001000, + software_present=0x000800, # .. no idea + reserved1=0x000400, + keyboard_illumination=0x000200, # illumination brightness level changes (by pressing keys) + wireless=0x000100, # notify when the device wireless goes on/off-line + mx_air_3d_gesture=0x000001, +) + +ERROR = NamedInts( + invalid_SubID__command=0x01, + invalid_address=0x02, + invalid_value=0x03, + connection_request_failed=0x04, + too_many_devices=0x05, + already_exists=0x06, + busy=0x07, + unknown_device=0x08, + resource_error=0x09, + request_unavailable=0x0A, + unsupported_parameter_value=0x0B, + wrong_pin_code=0x0C +) + +PAIRING_ERRORS = NamedInts(device_timeout=0x01, device_not_supported=0x02, too_many_devices=0x03, sequence_timeout=0x06) +BOLT_PAIRING_ERRORS = NamedInts(device_timeout=0x01, failed=0x02) +"""Known registers. +Devices usually have a (small) sub-set of these. Some registers are only +applicable to certain device kinds (e.g. smooth_scroll only applies to mice.""" +REGISTERS = NamedInts( + # only apply to receivers + receiver_connection=0x02, + receiver_pairing=0xB2, + devices_activity=0x2B3, + receiver_info=0x2B5, + bolt_device_discovery=0xC0, + bolt_pairing=0x2C1, + bolt_uniqueId=0x02FB, + + # only apply to devices + mouse_button_flags=0x01, + keyboard_hand_detection=0x01, + battery_status=0x07, + keyboard_fn_swap=0x09, + battery_charge=0x0D, + keyboard_illumination=0x17, + three_leds=0x51, + mouse_dpi=0x63, + + # apply to both + notifications=0x00, + firmware=0xF1, + + # notifications + passkey_request_notification=0x4D, + passkey_pressed_notification=0x4E, + device_discovery_notification=0x4F, + discovery_status_notification=0x53, + pairing_status_notification=0x54, +) +# Subregisters for receiver_info register +INFO_SUBREGISTERS = NamedInts( + serial_number=0x01, # not found on many receivers + fw_version=0x02, + receiver_information=0x03, + 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 + bolt_device_name=0x60, # 0x6N01, by connected device, +) + +# Flags taken from https://drive.google.com/file/d/0BxbRzx7vEV7eNDBheWY0UHM5dEU/view?usp=sharing +DEVICE_FEATURES = NamedInts( + reserved1=0x010000, + special_buttons=0x020000, + enhanced_key_usage=0x040000, + fast_fw_rev=0x080000, + reserved2=0x100000, + reserved3=0x200000, + scroll_accel=0x400000, + buttons_control_resolution=0x800000, + inhibit_lock_key_sound=0x000001, + reserved4=0x000002, + mx_air_3d_engine=0x000004, + host_control_leds=0x000008, + reserved5=0x000010, + reserved6=0x000020, + reserved7=0x000040, + reserved8=0x000080, +) diff --git a/lib/logitech_receiver/receiver.py b/lib/logitech_receiver/receiver.py index 55786b0e98..f7952e5922 100644 --- a/lib/logitech_receiver/receiver.py +++ b/lib/logitech_receiver/receiver.py @@ -23,14 +23,15 @@ from . import base as _base from . import hidpp10 as _hidpp10 +from . import hidpp10_constants as _hidpp10_constants from .base_usb import product_information as _product_information from .common import strhex as _strhex from .device import Device logger = logging.getLogger(__name__) -_R = _hidpp10.REGISTERS -_IR = _hidpp10.INFO_SUBREGISTERS +_R = _hidpp10_constants.REGISTERS +_IR = _hidpp10_constants.INFO_SUBREGISTERS # # @@ -120,9 +121,9 @@ def enable_connection_notifications(self, enable=True): if enable: set_flag_bits = ( - _hidpp10.NOTIFICATION_FLAG.battery_status - | _hidpp10.NOTIFICATION_FLAG.wireless - | _hidpp10.NOTIFICATION_FLAG.software_present + _hidpp10_constants.NOTIFICATION_FLAG.battery_status + | _hidpp10_constants.NOTIFICATION_FLAG.wireless + | _hidpp10_constants.NOTIFICATION_FLAG.software_present ) else: set_flag_bits = 0 @@ -132,7 +133,7 @@ def enable_connection_notifications(self, enable=True): return None flag_bits = _hidpp10.get_notification_flags(self) - flag_names = None if flag_bits is None else tuple(_hidpp10.NOTIFICATION_FLAG.flag_names(flag_bits)) + flag_names = None if flag_bits is None else tuple(_hidpp10_constants.NOTIFICATION_FLAG.flag_names(flag_bits)) if logger.isEnabledFor(logging.INFO): logger.info('%s: receiver notifications %s => %s', self, 'enabled' if enable else 'disabled', flag_names) return flag_bits @@ -154,7 +155,7 @@ def device_pairing_information(self, n): 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] + kind = _hidpp10_constants.DEVICE_KIND[ord(pair_info[1:2]) & 0x0F] return wpid, kind, 0 else: raise _base.NoSuchDevice(number=n, receiver=self, error='read Bolt wpid') @@ -164,19 +165,19 @@ def device_pairing_information(self, n): pair_info = self.read_register(_R.receiver_info, _IR.pairing_information + n - 1) if pair_info: # may be either a Unifying receiver, or an Unifying-ready receiver wpid = _strhex(pair_info[3:5]) - kind = _hidpp10.DEVICE_KIND[ord(pair_info[7:8]) & 0x0F] + kind = _hidpp10_constants.DEVICE_KIND[ord(pair_info[7:8]) & 0x0F] polling_rate = str(ord(pair_info[2:3])) + 'ms' elif self.receiver_kind == '27Mz': # 27Mhz receiver, fill extracting WPID from udev path wpid = _hid.find_paired_node_wpid(self.path, n) if not wpid: logger.error('Unable to get wpid from udev for device %d of %s', n, self) raise _base.NoSuchDevice(number=n, receiver=self, error='Not present 27Mhz device') - kind = _hidpp10.DEVICE_KIND[self.get_kind_from_index(n)] + kind = _hidpp10_constants.DEVICE_KIND[self.get_kind_from_index(n)] elif not self.receiver_kind == 'unifying': # unifying protocol not supported, may be an old Nano receiver device_info = self.read_register(_R.receiver_info, 0x04) if device_info: wpid = _strhex(device_info[3:5]) - kind = _hidpp10.DEVICE_KIND[0x00] # unknown kind + kind = _hidpp10_constants.DEVICE_KIND[0x00] # unknown kind else: raise _base.NoSuchDevice(number=n, receiver=self, error='read pairing information - non-unifying') else: @@ -195,7 +196,7 @@ def device_extended_pairing_information(self, n): 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] + power_switch = _hidpp10_constants.POWER_SWITCH_LOCATION[ord(pair_info[9:10]) & 0x0F] else: # some Nano receivers? pair_info = self.read_register(0x2D5) if pair_info: diff --git a/lib/logitech_receiver/settings_templates.py b/lib/logitech_receiver/settings_templates.py index 15b65c5d62..2dd600604b 100644 --- a/lib/logitech_receiver/settings_templates.py +++ b/lib/logitech_receiver/settings_templates.py @@ -23,7 +23,7 @@ from struct import unpack as _unpack from time import time as _time -from . import hidpp10 as _hidpp10 +from . import hidpp10_constants as _hidpp10_constants from . import hidpp20 as _hidpp20 from . import special_keys as _special_keys from .common import NamedInt as _NamedInt @@ -53,8 +53,8 @@ logger = logging.getLogger(__name__) -_DK = _hidpp10.DEVICE_KIND -_R = _hidpp10.REGISTERS +_DK = _hidpp10_constants.DEVICE_KIND +_R = _hidpp10_constants.REGISTERS _F = _hidpp20.FEATURE _GG = _hidpp20.GESTURE diff --git a/lib/solaar/listener.py b/lib/solaar/listener.py index 3ddba6d5be..7468913a21 100644 --- a/lib/solaar/listener.py +++ b/lib/solaar/listener.py @@ -26,7 +26,7 @@ from logitech_receiver import Device, Receiver from logitech_receiver import base as _base -from logitech_receiver import hidpp10 as _hidpp10 +from logitech_receiver import hidpp10_constants as _hidpp10_constants from logitech_receiver import listener as _listener from logitech_receiver import notifications as _notifications from logitech_receiver import status as _status @@ -40,8 +40,8 @@ logger = logging.getLogger(__name__) -_R = _hidpp10.REGISTERS -_IR = _hidpp10.INFO_SUBREGISTERS +_R = _hidpp10_constants.REGISTERS +_IR = _hidpp10_constants.INFO_SUBREGISTERS # # @@ -87,7 +87,7 @@ def has_started(self): logger.info('%s: notifications listener has started (%s)', self.receiver, self.receiver.handle) nfs = self.receiver.enable_connection_notifications() if logger.isEnabledFor(logging.WARNING): - if not self.receiver.isDevice and not ((nfs if nfs else 0) & _hidpp10.NOTIFICATION_FLAG.wireless): + if not self.receiver.isDevice and not ((nfs if nfs else 0) & _hidpp10_constants.NOTIFICATION_FLAG.wireless): logger.warning( 'Receiver on %s might not support connection notifications, GUI might not show its devices', self.receiver.path