diff --git a/lib/logitech_receiver/device.py b/lib/logitech_receiver/device.py index 618e466e..fde1023b 100644 --- a/lib/logitech_receiver/device.py +++ b/lib/logitech_receiver/device.py @@ -30,6 +30,7 @@ from .settings_templates import check_feature_settings as _check_feature_setting logger = logging.getLogger(__name__) +_hidpp10 = hidpp10.Hidpp10() _R = hidpp10_constants.REGISTERS _IR = hidpp10_constants.INFO_SUBREGISTERS @@ -201,7 +202,7 @@ class Device: if self.protocol >= 2.0: self._firmware = hidpp20.get_firmware(self) else: - self._firmware = hidpp10.get_firmware(self) + self._firmware = _hidpp10.get_firmware(self) return self._firmware or () @property @@ -315,7 +316,7 @@ class Device: def battery(self): # None or level, next, status, voltage if self.protocol < 2.0: - return hidpp10.get_battery(self) + return _hidpp10.get_battery(self) else: battery_feature = self.persister.get("_battery", None) if self.persister else None if battery_feature != 0: @@ -344,11 +345,11 @@ class Device: ) else: set_flag_bits = 0 - ok = hidpp10.set_notification_flags(self, set_flag_bits) + ok = _hidpp10.set_notification_flags(self, set_flag_bits) if not ok: logger.warning("%s: failed to %s device notifications", self, "enable" if enable else "disable") - flag_bits = hidpp10.get_notification_flags(self) + flag_bits = _hidpp10.get_notification_flags(self) 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: device notifications %s %s", self, "enabled" if enable else "disabled", flag_names) diff --git a/lib/logitech_receiver/hidpp10.py b/lib/logitech_receiver/hidpp10.py index e83f8ad4..3009b2a4 100644 --- a/lib/logitech_receiver/hidpp10.py +++ b/lib/logitech_receiver/hidpp10.py @@ -46,35 +46,158 @@ def write_register(device, register_number, *value): return device.request(request_id, *value) -def get_battery(device): - assert device is not None - assert device.kind is not None - if not device.online: - return - """Reads a device's battery level, if provided by the HID++ 1.0 protocol.""" - if device.protocol and device.protocol >= 2.0: - # let's just assume HID++ 2.0 devices do not provide the battery info in a register - return - - for r in (REGISTERS.battery_status, REGISTERS.battery_charge): - if r in device.registers: - reply = read_register(device, r) - if reply: - return parse_battery_status(r, reply) +class Hidpp10: + def get_battery(self, device): + assert device is not None + assert device.kind is not None + if not device.online: + return + """Reads a device's battery level, if provided by the HID++ 1.0 protocol.""" + if device.protocol and device.protocol >= 2.0: + # let's just assume HID++ 2.0 devices do not provide the battery info in a register return - # the descriptor does not tell us which register this device has, try them both - reply = read_register(device, REGISTERS.battery_charge) - if reply: - # remember this for the next time - device.registers.append(REGISTERS.battery_charge) - return parse_battery_status(REGISTERS.battery_charge, reply) + for r in (REGISTERS.battery_status, REGISTERS.battery_charge): + if r in device.registers: + reply = read_register(device, r) + if reply: + return parse_battery_status(r, reply) + return - reply = read_register(device, REGISTERS.battery_status) - if reply: - # remember this for the next time - device.registers.append(REGISTERS.battery_status) - return parse_battery_status(REGISTERS.battery_status, reply) + # the descriptor does not tell us which register this device has, try them both + reply = read_register(device, REGISTERS.battery_charge) + if reply: + # remember this for the next time + device.registers.append(REGISTERS.battery_charge) + return parse_battery_status(REGISTERS.battery_charge, reply) + + reply = read_register(device, REGISTERS.battery_status) + if reply: + # remember this for the next time + device.registers.append(REGISTERS.battery_status) + return parse_battery_status(REGISTERS.battery_status, reply) + + def get_firmware(self, device): + assert device is not None + + firmware = [None, None, None] + + reply = read_register(device, REGISTERS.firmware, 0x01) + if not reply: + # won't be able to read any of it now... + return + + fw_version = _strhex(reply[1:3]) + fw_version = "%s.%s" % (fw_version[0:2], fw_version[2:4]) + reply = read_register(device, REGISTERS.firmware, 0x02) + if reply: + fw_version += ".B" + _strhex(reply[1:3]) + fw = _FirmwareInfo(FIRMWARE_KIND.Firmware, "", fw_version, None) + firmware[0] = fw + + reply = read_register(device, REGISTERS.firmware, 0x04) + if reply: + bl_version = _strhex(reply[1:3]) + bl_version = "%s.%s" % (bl_version[0:2], bl_version[2:4]) + bl = _FirmwareInfo(FIRMWARE_KIND.Bootloader, "", bl_version, None) + firmware[1] = bl + + reply = read_register(device, REGISTERS.firmware, 0x03) + if reply: + o_version = _strhex(reply[1:3]) + o_version = "%s.%s" % (o_version[0:2], o_version[2:4]) + o = _FirmwareInfo(FIRMWARE_KIND.Other, "", o_version, None) + firmware[2] = o + + if any(firmware): + return tuple(f for f in firmware if f) + + def set_3leds(self, device, battery_level=None, charging=None, warning=None): + assert device is not None + assert device.kind is not None + if not device.online: + return + + if REGISTERS.three_leds not in device.registers: + return + + if battery_level is not None: + if battery_level < _BATTERY_APPROX.critical: + # 1 orange, and force blink + v1, v2 = 0x22, 0x00 + warning = True + elif battery_level < _BATTERY_APPROX.low: + # 1 orange + v1, v2 = 0x22, 0x00 + elif battery_level < _BATTERY_APPROX.good: + # 1 green + v1, v2 = 0x20, 0x00 + elif battery_level < _BATTERY_APPROX.full: + # 2 greens + v1, v2 = 0x20, 0x02 + else: + # all 3 green + v1, v2 = 0x20, 0x22 + if warning: + # set the blinking flag for the leds already set + v1 |= v1 >> 1 + v2 |= v2 >> 1 + elif charging: + # blink all green + v1, v2 = 0x30, 0x33 + elif warning: + # 1 red + v1, v2 = 0x02, 0x00 + else: + # turn off all leds + v1, v2 = 0x11, 0x11 + + write_register(device, REGISTERS.three_leds, v1, v2) + + def get_notification_flags(self, device): + assert device is not None + + # Avoid a call if the device is not online, + # or the device does not support registers. + if device.kind is not None: + # peripherals with protocol >= 2.0 don't support registers + if device.protocol and device.protocol >= 2.0: + return + + flags = read_register(device, REGISTERS.notifications) + if flags is not None: + assert len(flags) == 3 + return _bytes2int(flags) + + def set_notification_flags(self, device, *flag_bits): + assert device is not None + + # Avoid a call if the device is not online, + # or the device does not support registers. + if device.kind is not None: + # peripherals with protocol >= 2.0 don't support registers + if device.protocol and device.protocol >= 2.0: + return + + flag_bits = sum(int(b) for b in flag_bits) + assert flag_bits & 0x00FFFFFF == flag_bits + result = write_register(device, REGISTERS.notifications, _int2bytes(flag_bits, 3)) + return result is not None + + def get_device_features(self, device): + assert device is not None + + # Avoid a call if the device is not online, + # or the device does not support registers. + if device.kind is not None: + # peripherals with protocol >= 2.0 don't support registers + if device.protocol and device.protocol >= 2.0: + return + + flags = read_register(device, REGISTERS.mouse_button_flags) + if flags is not None: + assert len(flags) == 3 + return _bytes2int(flags) def parse_battery_status(register, reply): @@ -124,130 +247,3 @@ def parse_battery_status(register, reply): # Return None for next charge level and voltage as these are not in HID++ 1.0 spec return charge, None, status_text, None - - -def get_firmware(device): - assert device is not None - - firmware = [None, None, None] - - reply = read_register(device, REGISTERS.firmware, 0x01) - if not reply: - # won't be able to read any of it now... - return - - fw_version = _strhex(reply[1:3]) - fw_version = "%s.%s" % (fw_version[0:2], fw_version[2:4]) - reply = read_register(device, REGISTERS.firmware, 0x02) - if reply: - fw_version += ".B" + _strhex(reply[1:3]) - fw = _FirmwareInfo(FIRMWARE_KIND.Firmware, "", fw_version, None) - firmware[0] = fw - - reply = read_register(device, REGISTERS.firmware, 0x04) - if reply: - bl_version = _strhex(reply[1:3]) - bl_version = "%s.%s" % (bl_version[0:2], bl_version[2:4]) - bl = _FirmwareInfo(FIRMWARE_KIND.Bootloader, "", bl_version, None) - firmware[1] = bl - - reply = read_register(device, REGISTERS.firmware, 0x03) - if reply: - o_version = _strhex(reply[1:3]) - o_version = "%s.%s" % (o_version[0:2], o_version[2:4]) - o = _FirmwareInfo(FIRMWARE_KIND.Other, "", o_version, None) - firmware[2] = o - - if any(firmware): - return tuple(f for f in firmware if f) - - -def set_3leds(device, battery_level=None, charging=None, warning=None): - assert device is not None - assert device.kind is not None - if not device.online: - return - - if REGISTERS.three_leds not in device.registers: - return - - if battery_level is not None: - if battery_level < _BATTERY_APPROX.critical: - # 1 orange, and force blink - v1, v2 = 0x22, 0x00 - warning = True - elif battery_level < _BATTERY_APPROX.low: - # 1 orange - v1, v2 = 0x22, 0x00 - elif battery_level < _BATTERY_APPROX.good: - # 1 green - v1, v2 = 0x20, 0x00 - elif battery_level < _BATTERY_APPROX.full: - # 2 greens - v1, v2 = 0x20, 0x02 - else: - # all 3 green - v1, v2 = 0x20, 0x22 - if warning: - # set the blinking flag for the leds already set - v1 |= v1 >> 1 - v2 |= v2 >> 1 - elif charging: - # blink all green - v1, v2 = 0x30, 0x33 - elif warning: - # 1 red - v1, v2 = 0x02, 0x00 - else: - # turn off all leds - v1, v2 = 0x11, 0x11 - - write_register(device, REGISTERS.three_leds, v1, v2) - - -def get_notification_flags(device): - assert device is not None - - # Avoid a call if the device is not online, - # or the device does not support registers. - if device.kind is not None: - # peripherals with protocol >= 2.0 don't support registers - if device.protocol and device.protocol >= 2.0: - return - - flags = read_register(device, REGISTERS.notifications) - if flags is not None: - assert len(flags) == 3 - return _bytes2int(flags) - - -def set_notification_flags(device, *flag_bits): - assert device is not None - - # Avoid a call if the device is not online, - # or the device does not support registers. - if device.kind is not None: - # peripherals with protocol >= 2.0 don't support registers - if device.protocol and device.protocol >= 2.0: - return - - flag_bits = sum(int(b) for b in flag_bits) - assert flag_bits & 0x00FFFFFF == flag_bits - result = write_register(device, REGISTERS.notifications, _int2bytes(flag_bits, 3)) - return result is not None - - -def get_device_features(device): - assert device is not None - - # Avoid a call if the device is not online, - # or the device does not support registers. - if device.kind is not None: - # peripherals with protocol >= 2.0 don't support registers - if device.protocol and device.protocol >= 2.0: - return - - flags = read_register(device, REGISTERS.mouse_button_flags) - if flags is not None: - assert len(flags) == 3 - return _bytes2int(flags) diff --git a/lib/logitech_receiver/notifications.py b/lib/logitech_receiver/notifications.py index 60f02bae..a77607ba 100644 --- a/lib/logitech_receiver/notifications.py +++ b/lib/logitech_receiver/notifications.py @@ -24,7 +24,7 @@ import threading as _threading from struct import unpack as _unpack from . import diversion as _diversion -from . import hidpp10 as _hidpp10 +from . import hidpp10 from . import hidpp10_constants as _hidpp10_constants from . import hidpp20 as _hidpp20 from . import hidpp20_constants as _hidpp20_constants @@ -38,6 +38,7 @@ from .status import KEYS as _K logger = logging.getLogger(__name__) +_hidpp10 = hidpp10.Hidpp10() _R = _hidpp10_constants.REGISTERS _F = _hidpp20_constants.FEATURE @@ -220,7 +221,7 @@ def _process_hidpp10_custom_notification(device, status, n): # message layout: 10 ix <00> assert n.data[-1:] == b"\x00" data = chr(n.address).encode() + n.data - charge, next_charge, status_text, voltage = _hidpp10.parse_battery_status(n.sub_id, data) + charge, next_charge, status_text, voltage = hidpp10.parse_battery_status(n.sub_id, data) status.set_battery_info(charge, next_charge, status_text, voltage) return True diff --git a/lib/logitech_receiver/status.py b/lib/logitech_receiver/status.py index c0712fe9..07475e30 100644 --- a/lib/logitech_receiver/status.py +++ b/lib/logitech_receiver/status.py @@ -16,7 +16,7 @@ import logging -from . import hidpp10 as _hidpp10 +from . import hidpp10 from . import hidpp10_constants as _hidpp10_constants from . import hidpp20_constants as _hidpp20_constants from . import settings as _settings @@ -31,6 +31,8 @@ logger = logging.getLogger(__name__) _R = _hidpp10_constants.REGISTERS +_hidpp10 = hidpp10.Hidpp10() + # # # diff --git a/lib/solaar/cli/pair.py b/lib/solaar/cli/pair.py index 3d00fc4c..0a4fb10e 100644 --- a/lib/solaar/cli/pair.py +++ b/lib/solaar/cli/pair.py @@ -17,11 +17,12 @@ from time import time as _timestamp from logitech_receiver import base as _base -from logitech_receiver import hidpp10 as _hidpp10 +from logitech_receiver import hidpp10 from logitech_receiver import hidpp10_constants as _hidpp10_constants from logitech_receiver import notifications as _notifications from logitech_receiver import status as _status +_hidpp10 = hidpp10.Hidpp10() _R = _hidpp10_constants.REGISTERS diff --git a/lib/solaar/cli/show.py b/lib/solaar/cli/show.py index ed859b0a..c35d2ec6 100644 --- a/lib/solaar/cli/show.py +++ b/lib/solaar/cli/show.py @@ -14,8 +14,7 @@ ## with this program; if not, write to the Free Software Foundation, Inc., ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -from logitech_receiver import exceptions -from logitech_receiver import hidpp10 as _hidpp10 +from logitech_receiver import exceptions, hidpp10 from logitech_receiver import hidpp10_constants as _hidpp10_constants from logitech_receiver import hidpp20 as _hidpp20 from logitech_receiver import hidpp20_constants as _hidpp20_constants @@ -26,7 +25,7 @@ from logitech_receiver.common import strhex as _strhex from solaar import NAME, __version__ -_F = _hidpp20_constants.FEATURE +_hidpp10 = hidpp10.Hidpp10() def _print_receiver(receiver):