device: decipher LED control info in profiles

This commit is contained in:
Peter F. Patel-Schneider 2024-02-05 16:14:54 -05:00
parent 08fde28810
commit 1fcff028fe
2 changed files with 100 additions and 4 deletions

View File

@ -45,6 +45,13 @@ from .common import int2bytes as _int2bytes
_log = getLogger(__name__) _log = getLogger(__name__)
del getLogger del getLogger
def hexint_presenter(dumper, data):
return dumper.represent_int(hex(data))
_yaml.add_representer(int, hexint_presenter)
# #
# #
# #
@ -1144,6 +1151,94 @@ class Backlight:
self.device.feature_request(FEATURE.BACKLIGHT2, 0x10, data_bytes) self.device.feature_request(FEATURE.BACKLIGHT2, 0x10, data_bytes)
LEDParam = _NamedInts(color=0, speed=1, period=2, intensity=3, ramp=4, form=5)
LEDParamSize = {
LEDParam.color: 3,
LEDParam.speed: 1,
LEDParam.period: 2,
LEDParam.intensity: 1,
LEDParam.ramp: 1,
LEDParam.form: 1
}
LEDEffects = _NamedInts(
Disable=0x00,
Fixed=0x01,
Pulse=0x02,
Cycle=0x03,
# Wave=0x04, Stars=0x05, Press=0x06, Audio=0x07, # not implemented
Boot=0x08,
Demo=0x09,
Breathe=0x0A,
Ripple=0x0B
)
LEDEffectsParams = {
LEDEffects.Disable: {},
LEDEffects.Fixed: {
LEDParam.color: 0,
LEDParam.ramp: 3
},
LEDEffects.Pulse: {
LEDParam.color: 0,
LEDParam.speed: 3
},
LEDEffects.Cycle: {
LEDParam.period: 5,
LEDParam.intensity: 7
},
LEDEffects.Boot: {},
LEDEffects.Demo: {},
LEDEffects.Breathe: {
LEDParam.color: 0,
LEDParam.period: 3,
LEDParam.form: 5,
LEDParam.intensity: 6
},
LEDEffects.Ripple: {
LEDParam.color: 0,
LEDParam.period: 4
}
}
class LEDEffectSetting:
def __init__(self, **kwargs):
self.ID = None
for key, val in kwargs.items():
setattr(self, key, val)
@classmethod
def from_bytes(cls, bytes):
args = {'ID': LEDEffects[bytes[0]]}
if args['ID'] in LEDEffectsParams:
for p, b in LEDEffectsParams[args['ID']].items():
args[str(p)] = _bytes2int(bytes[1 + b:1 + b + LEDParamSize[p]])
else:
args['bytes'] = bytes
return cls(**args)
def to_bytes(self):
if self.ID is None:
return self.bytes if self.bytes else b'\xff' * 11
else:
bs = [0] * 10
for p, b in LEDEffectsParams[self.ID].items():
bs[b:b + LEDParamSize[p]] = _int2bytes(getattr(self, str(p)), LEDParamSize[p])
return _int2bytes(self.ID, 1) + bytes(bs)
@classmethod
def from_yaml(cls, loader, node):
args = loader.construct_mapping(node)
return cls(**args)
@classmethod
def to_yaml(cls, dumper, data):
return dumper.represent_mapping('!LEDEffectSetting', data.__dict__, flow_style=True)
_yaml.SafeLoader.add_constructor('!LEDEffectSetting', LEDEffectSetting.from_yaml)
_yaml.add_representer(LEDEffectSetting, LEDEffectSetting.to_yaml)
ButtonBehaviors = _NamedInts(MacroExecute=0x0, MacroStop=0x1, MacroStopAll=0x2, Send=0x8, Function=0x9) ButtonBehaviors = _NamedInts(MacroExecute=0x0, MacroStop=0x1, MacroStopAll=0x2, Send=0x8, Function=0x9)
ButtonMappingTypes = _NamedInts(No_Action=0x0, Button=0x1, Modifier_And_Key=0x2, Consumer_Key=0x3) ButtonMappingTypes = _NamedInts(No_Action=0x0, Button=0x1, Modifier_And_Key=0x2, Consumer_Key=0x3)
ButtonFunctions = _NamedInts( ButtonFunctions = _NamedInts(
@ -1197,7 +1292,7 @@ class Button:
value = ButtonButtons[(bytes[2] << 8) + bytes[3]] value = ButtonButtons[(bytes[2] << 8) + bytes[3]]
result = cls(behavior=behavior, type=mapping_type, value=value) result = cls(behavior=behavior, type=mapping_type, value=value)
elif mapping_type == ButtonMappingTypes.Modifier_And_Key: elif mapping_type == ButtonMappingTypes.Modifier_And_Key:
modifiers = ButtonModifiers[bytes[2]] modifiers = bytes[2]
value = ButtonKeys[bytes[3]] value = ButtonKeys[bytes[3]]
result = cls(behavior=behavior, type=mapping_type, modifiers=modifiers, value=value) result = cls(behavior=behavior, type=mapping_type, modifiers=modifiers, value=value)
elif mapping_type == ButtonMappingTypes.Consumer_Key: elif mapping_type == ButtonMappingTypes.Consumer_Key:
@ -1274,7 +1369,7 @@ class OnboardProfile:
buttons=[Button.from_bytes(bytes[32 + i * 4:32 + i * 4 + 4]) for i in range(0, buttons)], buttons=[Button.from_bytes(bytes[32 + i * 4:32 + i * 4 + 4]) for i in range(0, buttons)],
gbuttons=[Button.from_bytes(bytes[96 + i * 4:96 + i * 4 + 4]) for i in range(0, gbuttons)], gbuttons=[Button.from_bytes(bytes[96 + i * 4:96 + i * 4 + 4]) for i in range(0, gbuttons)],
name=bytes[160:208].decode('utf-16-be').rstrip('\x00').rstrip('\uFFFF'), name=bytes[160:208].decode('utf-16-be').rstrip('\x00').rstrip('\uFFFF'),
lighting=bytes[208:len(bytes) - 2] lighting=[LEDEffectSetting.from_bytes(bytes[208 + i * 11:219 + i * 11]) for i in range(0, 4)]
) )
@classmethod @classmethod
@ -1296,7 +1391,8 @@ class OnboardProfile:
bytes += self.name[0:24].ljust(24, '\x00').encode('utf-16be') bytes += self.name[0:24].ljust(24, '\x00').encode('utf-16be')
else: else:
bytes += b'\xff' * 48 bytes += b'\xff' * 48
bytes += self.lighting if getattr(self, 'lighting', None) else b'' for i in range(0, 4):
bytes += self.lighting[i].to_bytes()
while len(bytes) < length - 2: while len(bytes) < length - 2:
bytes += b'\xff' bytes += b'\xff'
bytes += _int2bytes(_crc16(bytes), 2) bytes += _int2bytes(_crc16(bytes), 2)

View File

@ -42,7 +42,7 @@ def run(receivers, args, find_receiver, find_device):
if not (dev.online and dev.profiles): if not (dev.online and dev.profiles):
print(f'Device {dev.name} is either offline or has no onboard profiles') print(f'Device {dev.name} is either offline or has no onboard profiles')
elif not profiles_file: elif not profiles_file:
print(f'Dumping profiles from {dev.name}') print(f'#Dumping profiles from {dev.name}')
print(_yaml.dump(dev.profiles)) print(_yaml.dump(dev.profiles))
else: else:
try: try: