Add CenturionCoreFeature enum for colliding feature IDs
Centurion transport reuses HID++ 2.0 feature IDs 0x0000, 0x0001, 0x0003, 0x0005, 0x0007 with different meanings. Since SupportedFeature (IntEnum) requires unique values, create a separate CenturionCoreFeature enum and resolve_feature() helper for transport-aware lookup. Also replace the +0x100 offset hack in FeaturesArray.inverse with a dedicated sub_inverse dict for sub-device feature indexing.
This commit is contained in:
parent
8e074524ce
commit
fc874710cd
|
|
@ -39,6 +39,7 @@ from .common import Alert
|
|||
from .common import Battery
|
||||
from .common import BatteryStatus
|
||||
from .common import FirmwareKind
|
||||
from .centurion_constants import CenturionCoreFeature
|
||||
from .common import _read_usb_product_string
|
||||
from .hidpp20_constants import SupportedFeature
|
||||
|
||||
|
|
@ -395,7 +396,7 @@ class CenturionReceiver:
|
|||
dev._centurion_usb_name = self._usb_name
|
||||
# Pre-set bridge index from dongle features so ping can probe the headset
|
||||
for _feat, feat_id, idx in self._dongle_features or []:
|
||||
if feat_id == 0x0003: # CentPPBridge
|
||||
if feat_id == CenturionCoreFeature.CENT_PP_BRIDGE:
|
||||
dev._centurion_bridge_index = idx
|
||||
break
|
||||
|
||||
|
|
@ -493,7 +494,7 @@ def create_centurion_receiver(low_level, device_info, setting_callback=None):
|
|||
base._centurion_handles.add(int(handle))
|
||||
cr = CenturionReceiver(low_level, handle, device_info, setting_callback)
|
||||
# Check if any discovered feature is CentPPBridge (0x0003)
|
||||
has_bridge = any(feat_id == 0x0003 for _, feat_id, _ in (cr.dongle_features or []))
|
||||
has_bridge = any(feat_id == CenturionCoreFeature.CENT_PP_BRIDGE for _, feat_id, _ in (cr.dongle_features or []))
|
||||
if not has_bridge:
|
||||
logger.info("Centurion device %s has no bridge, treating as direct device", device_info.path)
|
||||
base._centurion_handles.discard(int(handle))
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
"""Centurion transport-specific constants.
|
||||
|
||||
Feature IDs that collide with HID++ 2.0 core features live here
|
||||
so they can coexist with SupportedFeature (which requires unique values).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import IntEnum
|
||||
|
||||
from .hidpp20_constants import SupportedFeature
|
||||
|
||||
|
||||
class CenturionCoreFeature(IntEnum):
|
||||
"""Centurion transport-specific features that collide with HID++ 2.0 core IDs."""
|
||||
|
||||
CENTURION_ROOT = 0x0000
|
||||
CENTURION_FEATURE_SET = 0x0001
|
||||
CENT_PP_BRIDGE = 0x0003
|
||||
MULTI_HOST_CONTROL = 0x0005
|
||||
KEEP_ALIVE = 0x0007
|
||||
|
||||
def __str__(self):
|
||||
return self.name.replace("_", " ")
|
||||
|
||||
|
||||
def resolve_feature(feat_id: int, centurion: bool = False):
|
||||
"""Resolve a feature ID to the appropriate enum, checking centurion-specific
|
||||
features first when on the centurion transport."""
|
||||
if centurion:
|
||||
try:
|
||||
return CenturionCoreFeature(feat_id)
|
||||
except ValueError:
|
||||
pass
|
||||
try:
|
||||
return SupportedFeature(feat_id)
|
||||
except ValueError:
|
||||
return None
|
||||
|
|
@ -52,6 +52,8 @@ from .hidpp20_constants import ErrorCode
|
|||
from .hidpp20_constants import FeatureFlag
|
||||
from .hidpp20_constants import GestureId
|
||||
from .hidpp20_constants import ParamId
|
||||
from .centurion_constants import CenturionCoreFeature
|
||||
from .centurion_constants import resolve_feature
|
||||
from .hidpp20_constants import SupportedFeature
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -140,6 +142,7 @@ class FeaturesArray(dict):
|
|||
self.supported = True # Actually don't know whether it is supported yet
|
||||
self.device = device
|
||||
self.inverse = {}
|
||||
self.sub_inverse = {}
|
||||
self.version = {}
|
||||
self.flags = {}
|
||||
self.count = 0
|
||||
|
|
@ -195,14 +198,12 @@ class FeaturesArray(dict):
|
|||
continue
|
||||
# Centurion FeatureSet response: [remaining_count, feat_hi, feat_lo, type, flags]
|
||||
feat_id = struct.unpack("!H", response[1:3])[0]
|
||||
try:
|
||||
feature = SupportedFeature(feat_id)
|
||||
except ValueError:
|
||||
feature = resolve_feature(feat_id, centurion=True)
|
||||
if feature is None:
|
||||
feature = f"unknown:{feat_id:04X}"
|
||||
self[feature] = index
|
||||
self.inverse[index] = feature
|
||||
# Feature 0x0003 on Centurion = CentPPBridge (not FirmwareInfo)
|
||||
if feat_id == 0x0003:
|
||||
if feature is CenturionCoreFeature.CENT_PP_BRIDGE:
|
||||
bridge_index = index
|
||||
|
||||
if bridge_index is not None:
|
||||
|
|
@ -257,8 +258,8 @@ class FeaturesArray(dict):
|
|||
if dict.get(self, feature) is None:
|
||||
dict.__setitem__(self, feature, sub_feat_idx)
|
||||
self.device._centurion_sub_features.add(feature)
|
||||
# Always store in offset inverse for sub-device enumerate/display
|
||||
self.inverse[sub_feat_idx + 0x100] = feature
|
||||
# Always store in sub_inverse for sub-device enumerate/display
|
||||
self.sub_inverse[sub_feat_idx] = feature
|
||||
if logger.isEnabledFor(logging.DEBUG):
|
||||
logger.debug("Centurion sub-device feature: %s at sub-index %d", feature, sub_feat_idx)
|
||||
sub_feat_idx += 1
|
||||
|
|
@ -300,7 +301,7 @@ class FeaturesArray(dict):
|
|||
# Also yield sub-device features for Centurion devices
|
||||
sub_count = getattr(self, "_sub_feature_count", 0)
|
||||
for sub_idx in range(sub_count):
|
||||
feature = self.inverse.get(sub_idx + 0x100)
|
||||
feature = self.sub_inverse.get(sub_idx)
|
||||
if feature is not None:
|
||||
yield feature, sub_idx
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ from solaar.i18n import _
|
|||
from . import common
|
||||
from . import hidpp20_constants
|
||||
from . import settings_validator
|
||||
from .centurion_constants import CenturionCoreFeature
|
||||
from .common import NamedInt
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -628,7 +629,7 @@ class FeatureRW:
|
|||
read_prefix=b"",
|
||||
no_reply=False,
|
||||
):
|
||||
assert isinstance(feature, hidpp20_constants.SupportedFeature)
|
||||
assert isinstance(feature, (hidpp20_constants.SupportedFeature, CenturionCoreFeature))
|
||||
self.feature = feature
|
||||
self.read_fnid = read_fnid
|
||||
self.write_fnid = write_fnid
|
||||
|
|
@ -665,7 +666,7 @@ class FeatureRWMap(FeatureRW):
|
|||
key_byte_count=default_key_byte_count,
|
||||
no_reply=False,
|
||||
):
|
||||
assert isinstance(feature, hidpp20_constants.SupportedFeature)
|
||||
assert isinstance(feature, (hidpp20_constants.SupportedFeature, CenturionCoreFeature))
|
||||
self.feature = feature
|
||||
self.read_fnid = read_fnid
|
||||
self.write_fnid = write_fnid
|
||||
|
|
|
|||
|
|
@ -232,8 +232,7 @@ def _print_device(dev, num=None):
|
|||
else:
|
||||
feature_bytes = feature.to_bytes(2, byteorder="little")
|
||||
feature_int = int.from_bytes(feature_bytes, byteorder="little")
|
||||
# On Centurion, parent feature 0x0003 is CentPPBridge, not DEVICE_FW_VERSION
|
||||
display_name = "CENTPP BRIDGE" if is_centurion and not in_sub_device and feature_int == 0x0003 else feature
|
||||
display_name = feature
|
||||
if is_centurion_child and in_sub_device:
|
||||
# Use cached version — skip slow bridge ROOT queries
|
||||
version = dev.features.get_feature_version(feature_int) or 0
|
||||
|
|
@ -339,7 +338,7 @@ def _print_device(dev, num=None):
|
|||
if hw_info:
|
||||
model_id, hw_rev, product_id = hw_info
|
||||
print(f" Hardware: model {model_id}" f" rev {hw_rev} product {product_id:04X}")
|
||||
elif feature == SupportedFeature.DEVICE_FW_VERSION and not (is_centurion and not in_sub_device):
|
||||
elif isinstance(feature, SupportedFeature) and feature == SupportedFeature.DEVICE_FW_VERSION:
|
||||
for fw in _hidpp20.get_firmware(dev):
|
||||
extras = strhex(fw.extras) if fw.extras else ""
|
||||
print(f" Firmware: {fw.kind} {fw.name} {fw.version} {extras}")
|
||||
|
|
|
|||
Loading…
Reference in New Issue