Skip to content

Commit

Permalink
hid: Convert definition of HID registers to enum
Browse files Browse the repository at this point in the history
* Refactor HID Register definitions

Use enums for distinct type hints, easy discovery of registers.
Make constants uppercase and benefit from enum auto-completion.

Related pwr-Solaar#2273

* Improve type hints: Registers
  • Loading branch information
MattHag authored Jun 2, 2024
1 parent c23ebcd commit be83dac
Show file tree
Hide file tree
Showing 13 changed files with 213 additions and 209 deletions.
11 changes: 7 additions & 4 deletions lib/logitech_receiver/base_usb.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,13 @@

from solaar.i18n import _

# max_devices is only used for receivers that do not support reading from _R.receiver_info offset 0x03, default to 1
# may_unpair is only used for receivers that do not support reading from _R.receiver_info offset 0x03, default to False
# unpair is for receivers that do support reading from _R.receiver_info offset 0x03, no default
## should this last be changed so that may_unpair is used for all receivers? writing to _R.receiver_pairing doesn't seem right
# max_devices is only used for receivers that do not support reading from Registers.RECEIVER_INFO offset 0x03, default
# to 1.
# may_unpair is only used for receivers that do not support reading from Registers.RECEIVER_INFO offset 0x03,
# default to False.
# unpair is for receivers that do support reading from Registers.RECEIVER_INFO offset 0x03, no default.
## should this last be changed so that may_unpair is used for all receivers? writing to Registers.RECEIVER_PAIRING
## doesn't seem right
# re_pairs determines whether a receiver pairs by replacing existing pairings, default to False
## currently only one receiver is so marked - should there be more?

Expand Down
86 changes: 43 additions & 43 deletions lib/logitech_receiver/descriptors.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"""

from .hidpp10_constants import DEVICE_KIND
from .hidpp10_constants import REGISTERS as REG
from .hidpp10_constants import Registers as Reg


class _DeviceDescriptor:
Expand Down Expand Up @@ -193,24 +193,24 @@ def get_btid(btid):

# Keyboards

_D("Wireless Keyboard EX110", codename="EX110", protocol=1.0, wpid="0055", registers=(REG.battery_status,))
_D("Wireless Keyboard S510", codename="S510", protocol=1.0, wpid="0056", registers=(REG.battery_status,))
_D("Wireless Wave Keyboard K550", codename="K550", protocol=1.0, wpid="0060", registers=(REG.battery_status,))
_D("Wireless Keyboard EX100", codename="EX100", protocol=1.0, wpid="0065", registers=(REG.battery_status,))
_D("Wireless Keyboard MK300", codename="MK300", protocol=1.0, wpid="0068", registers=(REG.battery_status,))
_D("Number Pad N545", codename="N545", protocol=1.0, wpid="2006", registers=(REG.battery_status,))
_D("Wireless Compact Keyboard K340", codename="K340", protocol=1.0, wpid="2007", registers=(REG.battery_status,))
_D("Wireless Keyboard MK700", codename="MK700", protocol=1.0, wpid="2008", registers=(REG.battery_status,))
_D("Wireless Wave Keyboard K350", codename="K350", protocol=1.0, wpid="200A", registers=(REG.battery_status,))
_D("Wireless Keyboard MK320", codename="MK320", protocol=1.0, wpid="200F", registers=(REG.battery_status,))
_D("Wireless Keyboard EX110", codename="EX110", protocol=1.0, wpid="0055", registers=(Reg.BATTERY_STATUS,))
_D("Wireless Keyboard S510", codename="S510", protocol=1.0, wpid="0056", registers=(Reg.BATTERY_STATUS,))
_D("Wireless Wave Keyboard K550", codename="K550", protocol=1.0, wpid="0060", registers=(Reg.BATTERY_STATUS,))
_D("Wireless Keyboard EX100", codename="EX100", protocol=1.0, wpid="0065", registers=(Reg.BATTERY_STATUS,))
_D("Wireless Keyboard MK300", codename="MK300", protocol=1.0, wpid="0068", registers=(Reg.BATTERY_STATUS,))
_D("Number Pad N545", codename="N545", protocol=1.0, wpid="2006", registers=(Reg.BATTERY_STATUS,))
_D("Wireless Compact Keyboard K340", codename="K340", protocol=1.0, wpid="2007", registers=(Reg.BATTERY_STATUS,))
_D("Wireless Keyboard MK700", codename="MK700", protocol=1.0, wpid="2008", registers=(Reg.BATTERY_STATUS,))
_D("Wireless Wave Keyboard K350", codename="K350", protocol=1.0, wpid="200A", registers=(Reg.BATTERY_STATUS,))
_D("Wireless Keyboard MK320", codename="MK320", protocol=1.0, wpid="200F", registers=(Reg.BATTERY_STATUS,))
_D(
"Wireless Illuminated Keyboard K800",
codename="K800",
protocol=1.0,
wpid="2010",
registers=(REG.battery_status, REG.three_leds),
registers=(Reg.BATTERY_STATUS, Reg.THREE_LEDS),
)
_D("Wireless Keyboard K520", codename="K520", protocol=1.0, wpid="2011", registers=(REG.battery_status,))
_D("Wireless Keyboard K520", codename="K520", protocol=1.0, wpid="2011", registers=(Reg.BATTERY_STATUS,))
_D("Wireless Solar Keyboard K750", codename="K750", protocol=2.0, wpid="4002")
_D("Wireless Keyboard K270 (unifying)", codename="K270", protocol=2.0, wpid="4003")
_D("Wireless Keyboard K360", codename="K360", protocol=2.0, wpid="4004")
Expand All @@ -235,57 +235,57 @@ def get_btid(btid):

# Mice

_D("LX5 Cordless Mouse", codename="LX5", protocol=1.0, wpid="0036", registers=(REG.battery_status,))
_D("LX7 Cordless Laser Mouse", codename="LX7", protocol=1.0, wpid="0039", registers=(REG.battery_status,))
_D("Wireless Wave Mouse M550", codename="M550", protocol=1.0, wpid="003C", registers=(REG.battery_status,))
_D("Wireless Mouse EX100", codename="EX100m", protocol=1.0, wpid="003F", registers=(REG.battery_status,))
_D("Wireless Mouse M30", codename="M30", protocol=1.0, wpid="0085", registers=(REG.battery_status,))
_D("MX610 Laser Cordless Mouse", codename="MX610", protocol=1.0, wpid="1001", registers=(REG.battery_status,))
_D("G7 Cordless Laser Mouse", codename="G7", protocol=1.0, wpid="1002", registers=(REG.battery_status,))
_D("V400 Laser Cordless Mouse", codename="V400", protocol=1.0, wpid="1003", registers=(REG.battery_status,))
_D("MX610 Left-Handled Mouse", codename="MX610L", protocol=1.0, wpid="1004", registers=(REG.battery_status,))
_D("V450 Laser Cordless Mouse", codename="V450", protocol=1.0, wpid="1005", registers=(REG.battery_status,))
_D("LX5 Cordless Mouse", codename="LX5", protocol=1.0, wpid="0036", registers=(Reg.BATTERY_STATUS,))
_D("LX7 Cordless Laser Mouse", codename="LX7", protocol=1.0, wpid="0039", registers=(Reg.BATTERY_STATUS,))
_D("Wireless Wave Mouse M550", codename="M550", protocol=1.0, wpid="003C", registers=(Reg.BATTERY_STATUS,))
_D("Wireless Mouse EX100", codename="EX100m", protocol=1.0, wpid="003F", registers=(Reg.BATTERY_STATUS,))
_D("Wireless Mouse M30", codename="M30", protocol=1.0, wpid="0085", registers=(Reg.BATTERY_STATUS,))
_D("MX610 Laser Cordless Mouse", codename="MX610", protocol=1.0, wpid="1001", registers=(Reg.BATTERY_STATUS,))
_D("G7 Cordless Laser Mouse", codename="G7", protocol=1.0, wpid="1002", registers=(Reg.BATTERY_STATUS,))
_D("V400 Laser Cordless Mouse", codename="V400", protocol=1.0, wpid="1003", registers=(Reg.BATTERY_STATUS,))
_D("MX610 Left-Handled Mouse", codename="MX610L", protocol=1.0, wpid="1004", registers=(Reg.BATTERY_STATUS,))
_D("V450 Laser Cordless Mouse", codename="V450", protocol=1.0, wpid="1005", registers=(Reg.BATTERY_STATUS,))
_D(
"VX Revolution",
codename="VX Revolution",
kind=DEVICE_KIND.mouse,
protocol=1.0,
wpid=("1006", "100D", "0612"),
registers=(REG.battery_charge,),
registers=(Reg.BATTERY_CHARGE,),
)
_D("MX Air", codename="MX Air", protocol=1.0, kind=DEVICE_KIND.mouse, wpid=("1007", "100E"), registers=(REG.battery_charge,))
_D("MX Air", codename="MX Air", protocol=1.0, kind=DEVICE_KIND.mouse, wpid=("1007", "100E"), registers=(Reg.BATTERY_CHARGE))
_D(
"MX Revolution",
codename="MX Revolution",
protocol=1.0,
kind=DEVICE_KIND.mouse,
wpid=("1008", "100C"),
registers=(REG.battery_charge,),
registers=(Reg.BATTERY_CHARGE,),
)
_D("MX620 Laser Cordless Mouse", codename="MX620", protocol=1.0, wpid=("100A", "1016"), registers=(REG.battery_charge,))
_D("VX Nano Cordless Laser Mouse", codename="VX Nano", protocol=1.0, wpid=("100B", "100F"), registers=(REG.battery_charge,))
_D("V450 Nano Cordless Laser Mouse", codename="V450 Nano", protocol=1.0, wpid="1011", registers=(REG.battery_charge,))
_D("V550 Nano Cordless Laser Mouse", codename="V550 Nano", protocol=1.0, wpid="1013", registers=(REG.battery_charge,))
_D("MX620 Laser Cordless Mouse", codename="MX620", protocol=1.0, wpid=("100A", "1016"), registers=(Reg.BATTERY_CHARGE,))
_D("VX Nano Cordless Laser Mouse", codename="VX Nano", protocol=1.0, wpid=("100B", "100F"), registers=(Reg.BATTERY_CHARGE,))
_D("V450 Nano Cordless Laser Mouse", codename="V450 Nano", protocol=1.0, wpid="1011", registers=(Reg.BATTERY_CHARGE,))
_D("V550 Nano Cordless Laser Mouse", codename="V550 Nano", protocol=1.0, wpid="1013", registers=(Reg.BATTERY_CHARGE,))
_D(
"MX 1100 Cordless Laser Mouse",
codename="MX 1100",
protocol=1.0,
kind=DEVICE_KIND.mouse,
wpid="1014",
registers=(REG.battery_charge,),
registers=(Reg.BATTERY_CHARGE,),
)
_D("Anywhere Mouse MX", codename="Anywhere MX", protocol=1.0, wpid="1017", registers=(REG.battery_charge,))
_D("Anywhere Mouse MX", codename="Anywhere MX", protocol=1.0, wpid="1017", registers=(Reg.BATTERY_CHARGE,))
_D(
"Performance Mouse MX",
codename="Performance MX",
protocol=1.0,
wpid="101A",
registers=(REG.battery_status, REG.three_leds),
registers=(Reg.BATTERY_STATUS, Reg.THREE_LEDS),
)
_D("Marathon Mouse M705 (M-R0009)", codename="M705 (M-R0009)", protocol=1.0, wpid="101B", registers=(REG.battery_charge,))
_D("Wireless Mouse M350", codename="M350", protocol=1.0, wpid="101C", registers=(REG.battery_charge,))
_D("Wireless Mouse M505", codename="M505/B605", protocol=1.0, wpid="101D", registers=(REG.battery_charge,))
_D("Wireless Mouse M305", codename="M305", protocol=1.0, wpid="101F", registers=(REG.battery_status,))
_D("Marathon Mouse M705 (M-R0009)", codename="M705 (M-R0009)", protocol=1.0, wpid="101B", registers=(Reg.BATTERY_CHARGE,))
_D("Wireless Mouse M350", codename="M350", protocol=1.0, wpid="101C", registers=(Reg.BATTERY_CHARGE,))
_D("Wireless Mouse M505", codename="M505/B605", protocol=1.0, wpid="101D", registers=(Reg.BATTERY_CHARGE,))
_D("Wireless Mouse M305", codename="M305", protocol=1.0, wpid="101F", registers=(Reg.BATTERY_STATUS,))
_D("Wireless Mouse M215", codename="M215", protocol=1.0, wpid="1020")
_D(
"G700 Gaming Mouse",
Expand All @@ -295,12 +295,12 @@ def get_btid(btid):
usbid=0xC06B,
interface=1,
registers=(
REG.battery_status,
REG.three_leds,
Reg.BATTERY_STATUS,
Reg.THREE_LEDS,
),
)
_D("Wireless Mouse M310", codename="M310", protocol=1.0, wpid="1024", registers=(REG.battery_status,))
_D("Wireless Mouse M510", codename="M510", protocol=1.0, wpid="1025", registers=(REG.battery_status,))
_D("Wireless Mouse M310", codename="M310", protocol=1.0, wpid="1024", registers=(Reg.BATTERY_STATUS,))
_D("Wireless Mouse M510", codename="M510", protocol=1.0, wpid="1025", registers=(Reg.BATTERY_STATUS,))
_D("Fujitsu Sonic Mouse", codename="Sonic", protocol=1.0, wpid="1029")
_D(
"G700s Gaming Mouse",
Expand All @@ -310,8 +310,8 @@ def get_btid(btid):
usbid=0xC07C,
interface=1,
registers=(
REG.battery_status,
REG.three_leds,
Reg.BATTERY_STATUS,
Reg.THREE_LEDS,
),
)
_D("Couch Mouse M515", codename="M515", protocol=2.0, wpid="4007")
Expand Down
2 changes: 0 additions & 2 deletions lib/logitech_receiver/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,6 @@

_hidpp10 = hidpp10.Hidpp10()
_hidpp20 = hidpp20.Hidpp20()
_R = hidpp10_constants.REGISTERS
_IR = hidpp10_constants.INFO_SUBREGISTERS


class LowLevelInterface(Protocol):
Expand Down
58 changes: 29 additions & 29 deletions lib/logitech_receiver/hidpp10.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from .common import Battery
from .common import BatteryLevelApproximation
from .common import BatteryStatus
from .hidpp10_constants import REGISTERS
from .hidpp10_constants import Registers
from .hidpp20_constants import FIRMWARE_KIND

logger = logging.getLogger(__name__)
Expand All @@ -52,30 +52,30 @@ def registers(self) -> list:
...


def read_register(device: Device, register_number, *params):
assert device is not None, f"tried to read register {register_number:02X} from invalid device {device}"
def read_register(device: Device, register: Registers | int, *params) -> Any:
assert device is not None, f"tried to read register {register:02X} from invalid device {device}"
# support long registers by adding a 2 in front of the register number
request_id = 0x8100 | (int(register_number) & 0x2FF)
request_id = 0x8100 | (int(register) & 0x2FF)
return device.request(request_id, *params)


def write_register(device: Device, register_number, *value):
assert device is not None, f"tried to write register {register_number:02X} to invalid device {device}"
def write_register(device: Device, register: Registers | int, *value) -> Any:
assert device is not None, f"tried to write register {register:02X} to invalid device {device}"
# support long registers by adding a 2 in front of the register number
request_id = 0x8000 | (int(register_number) & 0x2FF)
request_id = 0x8000 | (int(register) & 0x2FF)
return device.request(request_id, *value)


def get_configuration_pending_flags(receiver):
assert not receiver.isDevice
result = read_register(receiver, REGISTERS.devices_configuration)
result = read_register(receiver, Registers.DEVICES_CONFIGURATION)
if result is not None:
return ord(result[:1])


def set_configuration_pending_flags(receiver, devices):
assert not receiver.isDevice
result = write_register(receiver, REGISTERS.devices_configuration, devices)
result = write_register(receiver, Registers.DEVICES_CONFIGURATION, devices)
return result is not None


Expand All @@ -90,52 +90,52 @@ def get_battery(self, device: Device):
# 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):
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

# the descriptor does not tell us which register this device has, try them both
reply = read_register(device, REGISTERS.battery_charge)
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)
device.registers.append(Registers.BATTERY_CHARGE)
return parse_battery_status(Registers.BATTERY_CHARGE, reply)

reply = read_register(device, REGISTERS.battery_status)
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)
device.registers.append(Registers.BATTERY_STATUS)
return parse_battery_status(Registers.BATTERY_STATUS, reply)

def get_firmware(self, device: Device):
assert device is not None

firmware = [None, None, None]

reply = read_register(device, REGISTERS.firmware, 0x01)
reply = read_register(device, Registers.FIRMWARE, 0x01)
if not reply:
# won't be able to read any of it now...
return

fw_version = common.strhex(reply[1:3])
fw_version = f"{fw_version[0:2]}.{fw_version[2:4]}"
reply = read_register(device, REGISTERS.firmware, 0x02)
reply = read_register(device, Registers.FIRMWARE, 0x02)
if reply:
fw_version += ".B" + common.strhex(reply[1:3])
fw = common.FirmwareInfo(FIRMWARE_KIND.Firmware, "", fw_version, None)
firmware[0] = fw

reply = read_register(device, REGISTERS.firmware, 0x04)
reply = read_register(device, Registers.FIRMWARE, 0x04)
if reply:
bl_version = common.strhex(reply[1:3])
bl_version = f"{bl_version[0:2]}.{bl_version[2:4]}"
bl = common.FirmwareInfo(FIRMWARE_KIND.Bootloader, "", bl_version, None)
firmware[1] = bl

reply = read_register(device, REGISTERS.firmware, 0x03)
reply = read_register(device, Registers.FIRMWARE, 0x03)
if reply:
o_version = common.strhex(reply[1:3])
o_version = f"{o_version[0:2]}.{o_version[2:4]}"
Expand All @@ -151,7 +151,7 @@ def set_3leds(self, device: Device, battery_level=None, charging=None, warning=N
if not device.online:
return

if REGISTERS.three_leds not in device.registers:
if Registers.THREE_LEDS not in device.registers:
return

if battery_level is not None:
Expand Down Expand Up @@ -185,10 +185,10 @@ def set_3leds(self, device: Device, battery_level=None, charging=None, warning=N
# turn off all leds
v1, v2 = 0x11, 0x11

write_register(device, REGISTERS.three_leds, v1, v2)
write_register(device, Registers.THREE_LEDS, v1, v2)

def get_notification_flags(self, device: Device):
return self._get_register(device, REGISTERS.notifications)
return self._get_register(device, Registers.NOTIFICATIONS)

def set_notification_flags(self, device: Device, *flag_bits):
assert device is not None
Expand All @@ -202,13 +202,13 @@ def set_notification_flags(self, device: Device, *flag_bits):

flag_bits = sum(int(b) for b in flag_bits)
assert flag_bits & 0x00FFFFFF == flag_bits
result = write_register(device, REGISTERS.notifications, common.int2bytes(flag_bits, 3))
result = write_register(device, Registers.NOTIFICATIONS, common.int2bytes(flag_bits, 3))
return result is not None

def get_device_features(self, device: Device):
return self._get_register(device, REGISTERS.mouse_button_flags)
return self._get_register(device, Registers.MOUSE_BUTTON_FLAGS)

def _get_register(self, device: Device, register):
def _get_register(self, device: Device, register: Registers | int):
assert device is not None

# Avoid a call if the device is not online,
Expand All @@ -224,7 +224,7 @@ def _get_register(self, device: Device, register):
return common.bytes2int(flags)


def parse_battery_status(register, reply) -> Battery | None:
def parse_battery_status(register: Registers | int, reply) -> Battery | None:
def status_byte_to_charge(status_byte_: int) -> BatteryLevelApproximation:
if status_byte_ == 7:
charge_ = BatteryLevelApproximation.FULL
Expand Down Expand Up @@ -262,14 +262,14 @@ def charging_byte_to_status_text(charging_byte_: int) -> BatteryStatus:
status_text_ = None
return status_text_

if register == REGISTERS.battery_charge:
if register == Registers.BATTERY_CHARGE:
charge = ord(reply[:1])
status_byte = ord(reply[2:3]) & 0xF0

battery_status = status_byte_to_battery_status(status_byte)
return Battery(charge, None, battery_status, None)

if register == REGISTERS.battery_status:
if register == Registers.BATTERY_STATUS:
status_byte = ord(reply[:1])
charging_byte = ord(reply[1:2])

Expand Down
Loading

0 comments on commit be83dac

Please sign in to comment.