device: add settings for LED Zone control
This commit is contained in:
parent
15e14c2d48
commit
3328a6085f
|
@ -1218,14 +1218,15 @@ class LEDEffectSetting: # an effect plus its parameters
|
||||||
args['bytes'] = bytes
|
args['bytes'] = bytes
|
||||||
return cls(**args)
|
return cls(**args)
|
||||||
|
|
||||||
def to_bytes(self):
|
def to_bytes(self, ID=None):
|
||||||
if self.ID is None:
|
ID = self.ID if ID is None else ID
|
||||||
|
if ID is None:
|
||||||
return self.bytes if self.bytes else b'\xff' * 11
|
return self.bytes if self.bytes else b'\xff' * 11
|
||||||
else:
|
else:
|
||||||
bs = [0] * 10
|
bs = [0] * 10
|
||||||
for p, b in LEDEffectsParams[self.ID].items():
|
for p, b in LEDEffectsParams[self.ID].items():
|
||||||
bs[b:b + LEDParamSize[p]] = _int2bytes(getattr(self, str(p), 0), LEDParamSize[p])
|
bs[b:b + LEDParamSize[p]] = _int2bytes(getattr(self, str(p), 0), LEDParamSize[p])
|
||||||
return _int2bytes(self.ID, 1) + bytes(bs)
|
return _int2bytes(ID, 1) + bytes(bs)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_yaml(cls, loader, node):
|
def from_yaml(cls, loader, node):
|
||||||
|
@ -1236,11 +1237,52 @@ class LEDEffectSetting: # an effect plus its parameters
|
||||||
def to_yaml(cls, dumper, data):
|
def to_yaml(cls, dumper, data):
|
||||||
return dumper.represent_mapping('!LEDEffectSetting', data.__dict__, flow_style=True)
|
return dumper.represent_mapping('!LEDEffectSetting', data.__dict__, flow_style=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return _yaml.dump(self).rstrip('\n')
|
||||||
|
|
||||||
|
|
||||||
_yaml.SafeLoader.add_constructor('!LEDEffectSetting', LEDEffectSetting.from_yaml)
|
_yaml.SafeLoader.add_constructor('!LEDEffectSetting', LEDEffectSetting.from_yaml)
|
||||||
_yaml.add_representer(LEDEffectSetting, LEDEffectSetting.to_yaml)
|
_yaml.add_representer(LEDEffectSetting, LEDEffectSetting.to_yaml)
|
||||||
|
|
||||||
|
|
||||||
|
class LEDEffectIndexed(LEDEffectSetting): # an effect plus its parameters, using the effect indices from an effect zone
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_bytes(cls, bytes, options=None):
|
||||||
|
if options:
|
||||||
|
args = {'ID': next((ze.ID for ze in options if ze.index == bytes[0]), None)}
|
||||||
|
else:
|
||||||
|
args = {'ID': None}
|
||||||
|
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
|
||||||
|
args['options'] = options
|
||||||
|
return cls(**args)
|
||||||
|
|
||||||
|
def to_bytes(self): # needs zone information
|
||||||
|
ID = next((ze.index for ze in self.options if ze.ID == self.ID), None)
|
||||||
|
if ID is None:
|
||||||
|
return self.bytes if hasattr(self, 'bytes') else b'\xff' * 11
|
||||||
|
else:
|
||||||
|
return super().to_bytes(ID)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def to_yaml(cls, dumper, data):
|
||||||
|
options = getattr(data, 'options', None)
|
||||||
|
if hasattr(data, 'options'):
|
||||||
|
delattr(data, 'options')
|
||||||
|
result = dumper.represent_mapping('!LEDEffectIndexed', data.__dict__, flow_style=True)
|
||||||
|
if options is not None:
|
||||||
|
data.options = options
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
_yaml.SafeLoader.add_constructor('!LEDEffectIndexed', LEDEffectIndexed.from_yaml)
|
||||||
|
_yaml.add_representer(LEDEffectIndexed, LEDEffectIndexed.to_yaml)
|
||||||
|
|
||||||
|
|
||||||
class LEDEffectInfo: # an effect that a zone can do
|
class LEDEffectInfo: # an effect that a zone can do
|
||||||
|
|
||||||
def __init__(self, device, zindex, eindex):
|
def __init__(self, device, zindex, eindex):
|
||||||
|
|
|
@ -40,7 +40,14 @@ del getLogger
|
||||||
|
|
||||||
SENSITIVITY_IGNORE = 'ignore'
|
SENSITIVITY_IGNORE = 'ignore'
|
||||||
KIND = _NamedInts(
|
KIND = _NamedInts(
|
||||||
toggle=0x01, choice=0x02, range=0x04, map_choice=0x0A, multiple_toggle=0x10, packed_range=0x20, multiple_range=0x40
|
toggle=0x01,
|
||||||
|
choice=0x02,
|
||||||
|
range=0x04,
|
||||||
|
map_choice=0x0A,
|
||||||
|
multiple_toggle=0x10,
|
||||||
|
packed_range=0x20,
|
||||||
|
multiple_range=0x40,
|
||||||
|
hetero=0x80
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -344,9 +351,11 @@ class Setting:
|
||||||
if self.persist and value is not None: # If setting doesn't persist no need to write value just read
|
if self.persist and value is not None: # If setting doesn't persist no need to write value just read
|
||||||
try:
|
try:
|
||||||
self.write(value, save=False)
|
self.write(value, save=False)
|
||||||
except Exception:
|
except Exception as e:
|
||||||
if _log.isEnabledFor(_WARNING):
|
if _log.isEnabledFor(_WARNING):
|
||||||
_log.warn('%s: error applying value %s so ignore it (%s)', self.name, self._value, self._device)
|
_log.warn(
|
||||||
|
'%s: error applying value %s so ignore it (%s): %s', self.name, self._value, self._device, repr(e)
|
||||||
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
if hasattr(self, '_value'):
|
if hasattr(self, '_value'):
|
||||||
|
@ -1200,6 +1209,38 @@ class RangeValidator(Validator):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class HeteroValidator(Validator):
|
||||||
|
kind = KIND.hetero
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def build(cls, setting_class, device, **kwargs):
|
||||||
|
return cls(**kwargs)
|
||||||
|
|
||||||
|
def __init__(self, data_class=None, options=None):
|
||||||
|
assert data_class is not None and options is not None
|
||||||
|
self.data_class = data_class
|
||||||
|
self.options = options
|
||||||
|
self.needs_current_value = False
|
||||||
|
|
||||||
|
def validate_read(self, reply_bytes):
|
||||||
|
reply_value = self.data_class.from_bytes(reply_bytes, options=self.options)
|
||||||
|
return reply_value
|
||||||
|
|
||||||
|
def prepare_write(self, new_value, current_value=None):
|
||||||
|
new_value.options = self.options
|
||||||
|
to_write = new_value.to_bytes()
|
||||||
|
return to_write
|
||||||
|
|
||||||
|
def acceptable(self, args, current): # FIXME
|
||||||
|
if len(args) != 2:
|
||||||
|
return None
|
||||||
|
item = self.items[args[0]] if args[0] in self.items else None
|
||||||
|
if item.kind == KIND.range:
|
||||||
|
return None if args[1] < item.min_value or args[1] > item.max_value else args
|
||||||
|
elif item.kind == KIND.choice:
|
||||||
|
return args if args[1] in item.choices else None
|
||||||
|
|
||||||
|
|
||||||
class PackedRangeValidator(Validator):
|
class PackedRangeValidator(Validator):
|
||||||
kind = KIND.packed_range
|
kind = KIND.packed_range
|
||||||
"""Several range values, all the same size, all the same min and max"""
|
"""Several range values, all the same size, all the same min and max"""
|
||||||
|
|
|
@ -40,6 +40,7 @@ from .settings import BitFieldWithOffsetAndMaskValidator as _BitFieldOMV
|
||||||
from .settings import ChoicesMapValidator as _ChoicesMapV
|
from .settings import ChoicesMapValidator as _ChoicesMapV
|
||||||
from .settings import ChoicesValidator as _ChoicesV
|
from .settings import ChoicesValidator as _ChoicesV
|
||||||
from .settings import FeatureRW as _FeatureRW
|
from .settings import FeatureRW as _FeatureRW
|
||||||
|
from .settings import HeteroValidator as _HeteroV
|
||||||
from .settings import LongSettings as _LongSettings
|
from .settings import LongSettings as _LongSettings
|
||||||
from .settings import MultipleRangeValidator as _MultipleRangeV
|
from .settings import MultipleRangeValidator as _MultipleRangeV
|
||||||
from .settings import PackedRangeValidator as _PackedRangeV
|
from .settings import PackedRangeValidator as _PackedRangeV
|
||||||
|
@ -1429,6 +1430,36 @@ class LEDControl(_Setting):
|
||||||
validator_options = {'choices': choices_universe}
|
validator_options = {'choices': choices_universe}
|
||||||
|
|
||||||
|
|
||||||
|
# an LED Zone has an index, a set of possible LED effects, and an LED effect setting with parameters
|
||||||
|
# the parameters are different for each effect
|
||||||
|
# reading the current setting for a zone returns zeros on some devices
|
||||||
|
class LEDZoneSetting(_Setting):
|
||||||
|
name = 'led_zone_'
|
||||||
|
label = _('LED Zone Effects')
|
||||||
|
description = _('Set effect for LED Zone')
|
||||||
|
feature = _F.COLOR_LED_EFFECTS
|
||||||
|
|
||||||
|
class validator_class(_HeteroV):
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def build(cls, setting_class, device, effects):
|
||||||
|
return cls(data_class=_hidpp20.LEDEffectIndexed, options=effects)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def build(cls, device):
|
||||||
|
zone_infos = _hidpp20.LEDEffectsInfo(device).zones
|
||||||
|
settings = []
|
||||||
|
for zone in zone_infos:
|
||||||
|
prefix = zone.index.to_bytes(1)
|
||||||
|
rw = _FeatureRW(_F.COLOR_LED_EFFECTS, read_fnid=0xE0, write_fnid=0x30, prefix=prefix)
|
||||||
|
validator = cls.validator_class.build(cls, device, zone.effects)
|
||||||
|
setting = cls(device, rw, validator)
|
||||||
|
setting.name = cls.name + str(int(zone.location))
|
||||||
|
setting.label = _('LEDs: ') + str(_hidpp20.LEDZoneLocations[zone.location])
|
||||||
|
settings.append(setting)
|
||||||
|
return settings
|
||||||
|
|
||||||
|
|
||||||
SETTINGS = [
|
SETTINGS = [
|
||||||
RegisterHandDetection, # simple
|
RegisterHandDetection, # simple
|
||||||
RegisterSmoothScroll, # simple
|
RegisterSmoothScroll, # simple
|
||||||
|
@ -1459,6 +1490,7 @@ SETTINGS = [
|
||||||
Backlight2DurationPowered,
|
Backlight2DurationPowered,
|
||||||
Backlight3,
|
Backlight3,
|
||||||
LEDControl,
|
LEDControl,
|
||||||
|
LEDZoneSetting,
|
||||||
FnSwap, # simple
|
FnSwap, # simple
|
||||||
NewFnSwap, # simple
|
NewFnSwap, # simple
|
||||||
K375sFnSwap, # working
|
K375sFnSwap, # working
|
||||||
|
@ -1517,7 +1549,12 @@ def check_feature_settings(device, already_known):
|
||||||
known_present = device.persister and sclass.name in device.persister
|
known_present = device.persister and sclass.name in device.persister
|
||||||
if not any(s.name == sclass.name for s in already_known) and (known_present or sclass.name not in absent):
|
if not any(s.name == sclass.name for s in already_known) and (known_present or sclass.name not in absent):
|
||||||
setting = check_feature(device, sclass)
|
setting = check_feature(device, sclass)
|
||||||
if setting:
|
if isinstance(setting, list):
|
||||||
|
for s in setting:
|
||||||
|
already_known.append(s)
|
||||||
|
if sclass.name in newAbsent:
|
||||||
|
newAbsent.remove(sclass.name)
|
||||||
|
elif setting:
|
||||||
already_known.append(setting)
|
already_known.append(setting)
|
||||||
if sclass.name in newAbsent:
|
if sclass.name in newAbsent:
|
||||||
newAbsent.remove(sclass.name)
|
newAbsent.remove(sclass.name)
|
||||||
|
|
Loading…
Reference in New Issue