diff --git a/lib/logitech_receiver/base.py b/lib/logitech_receiver/base.py index 1de5345018..00ee8e372e 100644 --- a/lib/logitech_receiver/base.py +++ b/lib/logitech_receiver/base.py @@ -595,7 +595,7 @@ def request( devnumber, request_id, error, - hidpp20_constants.ERROR[error], + hidpp20_constants.ErrorCode(error), ) raise exceptions.FeatureCallError( number=devnumber, diff --git a/lib/logitech_receiver/hidpp20.py b/lib/logitech_receiver/hidpp20.py index 459408d153..a4a01c6554 100644 --- a/lib/logitech_receiver/hidpp20.py +++ b/lib/logitech_receiver/hidpp20.py @@ -42,12 +42,13 @@ from .common import BatteryStatus from .common import FirmwareKind from .common import NamedInt -from .hidpp20_constants import CHARGE_LEVEL from .hidpp20_constants import CHARGE_STATUS -from .hidpp20_constants import CHARGE_TYPE from .hidpp20_constants import DEVICE_KIND -from .hidpp20_constants import ERROR from .hidpp20_constants import GESTURE +from .hidpp20_constants import ChargeLevel +from .hidpp20_constants import ChargeType +from .hidpp20_constants import ErrorCode +from .hidpp20_constants import ParamId from .hidpp20_constants import SupportedFeature logger = logging.getLogger(__name__) @@ -604,16 +605,6 @@ def _query_key(self, index: int): logger.warning(f"Key with index {index} was expected to exist but device doesn't report it.") -# Param Ids for feature GESTURE_2 -PARAM = common.NamedInts( - ExtraCapabilities=1, # not suitable for use - PixelZone=2, # 4 2-byte integers, left, bottom, width, height; pixels - RatioZone=3, # 4 bytes, left, bottom, width, height; unit 1/240 pad size - ScaleFactor=4, # 2-byte integer, with 256 as normal scale -) -PARAM._fallback = lambda x: f"unknown:{x:04X}" - - class SubParam: __slots__ = ("id", "length", "minimum", "maximum", "widget") @@ -632,20 +623,20 @@ def __repr__(self): SUB_PARAM = { # (byte count, minimum, maximum) - PARAM["ExtraCapabilities"]: None, # ignore - PARAM["PixelZone"]: ( # TODO: replace min and max with the correct values + ParamId.EXTRA_CAPABILITIES: None, # ignore + ParamId.PIXEL_ZONE: ( # TODO: replace min and max with the correct values SubParam("left", 2, 0x0000, 0xFFFF, "SpinButton"), SubParam("bottom", 2, 0x0000, 0xFFFF, "SpinButton"), SubParam("width", 2, 0x0000, 0xFFFF, "SpinButton"), SubParam("height", 2, 0x0000, 0xFFFF, "SpinButton"), ), - PARAM["RatioZone"]: ( # TODO: replace min and max with the correct values + ParamId.RATIO_ZONE: ( # TODO: replace min and max with the correct values SubParam("left", 1, 0x00, 0xFF, "SpinButton"), SubParam("bottom", 1, 0x00, 0xFF, "SpinButton"), SubParam("width", 1, 0x00, 0xFF, "SpinButton"), SubParam("height", 1, 0x00, 0xFF, "SpinButton"), ), - PARAM["ScaleFactor"]: (SubParam("scale", 2, 0x002E, 0x01FF, "Scale"),), + ParamId.SCALE_FACTOR: (SubParam("scale", 2, 0x002E, 0x01FF, "Scale"),), } # Spec Ids for feature GESTURE_2 @@ -765,10 +756,10 @@ def __repr__(self): class Param: - def __init__(self, device, low, high, next_param_index): + def __init__(self, device, low: int, high, next_param_index): self._device = device self.id = low - self.param = PARAM[low] + self.param = ParamId(low) self.size = high & 0x0F self.show_in_ui = bool(high & 0x1F) self._value = None @@ -1820,27 +1811,27 @@ def decipher_battery_status(report: FixedBytes5) -> Tuple[Any, Battery]: return SupportedFeature.BATTERY_STATUS, Battery(battery_discharge_level, battery_discharge_next_level, status, None) -def decipher_battery_voltage(report): +def decipher_battery_voltage(report: bytes): voltage, flags = struct.unpack(">HB", report[:3]) status = BatteryStatus.DISCHARGING - charge_sts = ERROR.unknown - charge_lvl = CHARGE_LEVEL.average - charge_type = CHARGE_TYPE.standard + charge_sts = ErrorCode.UNKNOWN + charge_lvl = ChargeLevel.AVERAGE + charge_type = ChargeType.STANDARD if flags & (1 << 7): status = BatteryStatus.RECHARGING charge_sts = CHARGE_STATUS[flags & 0x03] if charge_sts is None: - charge_sts = ERROR.unknown + charge_sts = ErrorCode.UNKNOWN elif charge_sts == CHARGE_STATUS.full: - charge_lvl = CHARGE_LEVEL.full + charge_lvl = ChargeLevel.FULL status = BatteryStatus.FULL if flags & (1 << 3): - charge_type = CHARGE_TYPE.fast + charge_type = ChargeType.FAST elif flags & (1 << 4): - charge_type = CHARGE_TYPE.slow + charge_type = ChargeType.SLOW status = BatteryStatus.SLOW_RECHARGE elif flags & (1 << 5): - charge_lvl = CHARGE_LEVEL.critical + charge_lvl = ChargeLevel.CRITICAL for level in battery_voltage_remaining: if level[0] < voltage: charge_lvl = level[1] diff --git a/lib/logitech_receiver/hidpp20_constants.py b/lib/logitech_receiver/hidpp20_constants.py index abd04d3972..2e962bca80 100644 --- a/lib/logitech_receiver/hidpp20_constants.py +++ b/lib/logitech_receiver/hidpp20_constants.py @@ -173,25 +173,45 @@ class FeatureFlag(IntFlag): ) -ONBOARD_MODES = NamedInts(MODE_NO_CHANGE=0x00, MODE_ONBOARD=0x01, MODE_HOST=0x02) +class OnboardMode(IntEnum): + MODE_NO_CHANGE = 0x00 + MODE_ONBOARD = 0x01 + MODE_HOST = 0x02 + CHARGE_STATUS = NamedInts(charging=0x00, full=0x01, not_charging=0x02, error=0x07) -CHARGE_LEVEL = NamedInts(average=50, full=90, critical=5) -CHARGE_TYPE = NamedInts(standard=0x00, fast=0x01, slow=0x02) +class ChargeStatus(IntEnum): + CHARGING = 0x00 + FULL = 0x01 + NOT_CHARGING = 0x02 + ERROR = 0x07 + + +class ChargeLevel(IntEnum): + AVERAGE = 50 + FULL = 90 + CRITICAL = 5 + + +class ChargeType(IntEnum): + STANDARD = 0x00 + FAST = 0x01 + SLOW = 0x02 + + +class ErrorCode(IntEnum): + UNKNOWN = 0x01 + INVALID_ARGUMENT = 0x02 + OUT_OF_RANGE = 0x03 + HARDWARE_ERROR = 0x04 + LOGITECH_ERROR = 0x05 + INVALID_FEATURE_INDEX = 0x06 + INVALID_FUNCTION = 0x07 + BUSY = 0x08 + UNSUPPORTED = 0x09 -ERROR = NamedInts( - unknown=0x01, - invalid_argument=0x02, - out_of_range=0x03, - hardware_error=0x04, - logitech_internal=0x05, - invalid_feature_index=0x06, - invalid_function=0x07, - busy=0x08, - unsupported=0x09, -) # Gesture Ids for feature GESTURE_2 GESTURE = NamedInts( @@ -258,11 +278,11 @@ class FeatureFlag(IntFlag): ) GESTURE._fallback = lambda x: f"unknown:{x:04X}" -# Param Ids for feature GESTURE_2 -PARAM = NamedInts( - ExtraCapabilities=1, # not suitable for use - PixelZone=2, # 4 2-byte integers, left, bottom, width, height; pixels - RatioZone=3, # 4 bytes, left, bottom, width, height; unit 1/240 pad size - ScaleFactor=4, # 2-byte integer, with 256 as normal scale -) -PARAM._fallback = lambda x: f"unknown:{x:04X}" + +class ParamId(IntEnum): + """Param Ids for feature GESTURE_2""" + + EXTRA_CAPABILITIES = 1 # not suitable for use + PIXEL_ZONE = 2 # 4 2-byte integers, left, bottom, width, height; pixels + RATIO_ZONE = 3 # 4 bytes, left, bottom, width, height; unit 1/240 pad size + SCALE_FACTOR = 4 # 2-byte integer, with 256 as normal scale diff --git a/lib/logitech_receiver/settings_templates.py b/lib/logitech_receiver/settings_templates.py index 7828bba26d..803a8ccd72 100644 --- a/lib/logitech_receiver/settings_templates.py +++ b/lib/logitech_receiver/settings_templates.py @@ -38,6 +38,7 @@ from . import settings from . import special_keys from .hidpp10_constants import Registers +from .hidpp20_constants import ParamId logger = logging.getLogger(__name__) @@ -46,7 +47,6 @@ _F = hidpp20_constants.SupportedFeature _GG = hidpp20_constants.GESTURE -_GP = hidpp20_constants.PARAM class State(enum.Enum): @@ -1296,10 +1296,10 @@ def build(cls, setting_class, device): } _GESTURE2_PARAMS_LABELS = { - _GP["ExtraCapabilities"]: (None, None), # not supported - _GP["PixelZone"]: (_("Pixel zone"), None), # TO DO: replace None with a short description - _GP["RatioZone"]: (_("Ratio zone"), None), # TO DO: replace None with a short description - _GP["ScaleFactor"]: (_("Scale factor"), _("Sets the cursor speed.")), + ParamId.EXTRA_CAPABILITIES: (None, None), # not supported + ParamId.PIXEL_ZONE: (_("Pixel zone"), None), # TO DO: replace None with a short description + ParamId.RATIO_ZONE: (_("Ratio zone"), None), # TO DO: replace None with a short description + ParamId.SCALE_FACTOR: (_("Scale factor"), _("Sets the cursor speed.")), } _GESTURE2_PARAMS_LABELS_SUB = { @@ -1351,7 +1351,7 @@ class Gesture2Params(settings.LongSettings): description = _("Change numerical parameters of a mouse/touchpad.") feature = _F.GESTURE_2 rw_options = {"read_fnid": 0x70, "write_fnid": 0x80} - choices_universe = hidpp20.PARAM + choices_universe = hidpp20_constants.ParamId sub_items_universe = hidpp20.SUB_PARAM # item (NamedInt) -> list/tuple of objects that have the following attributes # .id (sub-item text), .length (in bytes), .minimum and .maximum diff --git a/lib/solaar/cli/show.py b/lib/solaar/cli/show.py index de0402f00d..53db2a2913 100644 --- a/lib/solaar/cli/show.py +++ b/lib/solaar/cli/show.py @@ -237,7 +237,7 @@ def _print_device(dev, num=None): elif feature == SupportedFeature.REMAINING_PAIRING: print(f" Remaining Pairings: {int(_hidpp20.get_remaining_pairing(dev))}") elif feature == SupportedFeature.ONBOARD_PROFILES: - if _hidpp20.get_onboard_mode(dev) == hidpp20_constants.ONBOARD_MODES.MODE_HOST: + if _hidpp20.get_onboard_mode(dev) == hidpp20_constants.OnboardMode.MODE_HOST: mode = "Host" else: mode = "On-Board" diff --git a/tests/logitech_receiver/test_hidpp20_complex.py b/tests/logitech_receiver/test_hidpp20_complex.py index e1d2dfd433..b52a1a1e57 100644 --- a/tests/logitech_receiver/test_hidpp20_complex.py +++ b/tests/logitech_receiver/test_hidpp20_complex.py @@ -534,10 +534,10 @@ def test_Gesture_set(responses, gest, enabled, diverted, set_result, unset_resul @pytest.mark.parametrize( "responses, prm, id, index, size, value, default_value, write1, write2", [ - (fake_hidpp.responses_gestures, 4, common.NamedInt(4, "ScaleFactor"), 0, 2, 256, 256, "0080", "0180"), + (fake_hidpp.responses_gestures, 4, hidpp20_constants.ParamId.SCALE_FACTOR, 0, 2, 256, 256, "0080", "0180"), ], ) -def test_Param(responses, prm, id, index, size, value, default_value, write1, write2): +def test_param(responses, prm, id, index, size, value, default_value, write1, write2): device = fake_hidpp.Device("GESTURE", responses=responses, feature=hidpp20_constants.SupportedFeature.GESTURE_2) gestures = _hidpp20.get_gestures(device) @@ -548,7 +548,7 @@ def test_Param(responses, prm, id, index, size, value, default_value, write1, wr assert param.size == size assert param.value == value assert param.default_value == default_value - assert str(param) == id + assert param.param == id assert int(param) == id assert param.write(bytes.fromhex(write1)).hex().upper() == f"{index:02X}" + write1 + "FF" assert param.write(bytes.fromhex(write2)).hex().upper() == f"{index:02X}" + write2 + "FF"