receiver: add model and node ID and use in configurations
This commit is contained in:
parent
58823763ea
commit
b1d4b2f3cd
|
@ -6,6 +6,7 @@ from logging import INFO as _INFO
|
|||
from logging import getLogger
|
||||
|
||||
import hidapi as _hid
|
||||
import solaar.configuration as _configuration
|
||||
|
||||
from . import base as _base
|
||||
from . import hidpp10 as _hidpp10
|
||||
|
@ -59,6 +60,14 @@ class Device(object):
|
|||
self._protocol = None
|
||||
# serial number (an 8-char hex string)
|
||||
self._serial = None
|
||||
# unit id (distinguishes within a model - the same as serial)
|
||||
self._unitId = None
|
||||
# model id (contains identifiers for the transports of the device)
|
||||
self._modelId = None
|
||||
# map from transports to product identifiers
|
||||
self._tid_map = None
|
||||
# persister holds settings
|
||||
self._persister = None
|
||||
|
||||
self._firmware = None
|
||||
self._keys = None
|
||||
|
@ -196,6 +205,29 @@ class Device(object):
|
|||
self._name = _hidpp20.get_name(self)
|
||||
return self._name or self.codename or ('Unknown device %s' % (self.wpid or self.product_id))
|
||||
|
||||
@property
|
||||
def unitId(self):
|
||||
if not self._unitId:
|
||||
if self.online and self.protocol >= 2.0:
|
||||
self._unitId, self._modelId, self._tid_map = _hidpp20.get_ids(self)
|
||||
if _log.isEnabledFor(_INFO) and self._serial and self._serial != self._unitId:
|
||||
_log.info('%s: unitId %s does not match serial %s', self, self._unitId, self._serial)
|
||||
return self._unitId
|
||||
|
||||
@property
|
||||
def modelId(self):
|
||||
if not self._modelId:
|
||||
if self.online and self.protocol >= 2.0:
|
||||
self._unitId, self._modelId, self._tid_map = _hidpp20.get_ids(self)
|
||||
return self._modelId
|
||||
|
||||
@property
|
||||
def tid_map(self):
|
||||
if not self._tid_map:
|
||||
if self.online and self.protocol >= 2.0:
|
||||
self._unitId, self._modelId, self._tid_map = _hidpp20.get_ids(self)
|
||||
return self._tid_map
|
||||
|
||||
@property
|
||||
def kind(self):
|
||||
if not self._kind:
|
||||
|
@ -285,7 +317,7 @@ class Device(object):
|
|||
def settings(self):
|
||||
if self._settings is None:
|
||||
self._settings = []
|
||||
if self.descriptor and self.descriptor.settings:
|
||||
if self.descriptor and self.descriptor.settings and self.persister:
|
||||
self._settings = []
|
||||
for s in self.descriptor.settings:
|
||||
try:
|
||||
|
@ -300,6 +332,12 @@ class Device(object):
|
|||
self._feature_settings_checked = _check_feature_settings(self, self._settings)
|
||||
return self._settings
|
||||
|
||||
@property
|
||||
def persister(self):
|
||||
if not self._persister:
|
||||
self._persister = _configuration.persister(self)
|
||||
return self._persister
|
||||
|
||||
def get_kind_from_index(self, index, receiver):
|
||||
"""Get device kind from 27Mhz device index"""
|
||||
# accordingly to drivers/hid/hid-logitech-dj.c
|
||||
|
|
|
@ -1050,6 +1050,21 @@ def get_firmware(device):
|
|||
return tuple(fw)
|
||||
|
||||
|
||||
def get_ids(device):
|
||||
"""Reads a device's ids (unit and model numbers)"""
|
||||
ids = feature_request(device, FEATURE.DEVICE_FW_VERSION)
|
||||
unitId = ids[1:5]
|
||||
modelId = ids[7:13]
|
||||
transport_bits = ord(ids[6:7])
|
||||
offset = 0
|
||||
tid_map = {}
|
||||
for transport, flag in [('btid', 0x1), ('btleid', 0x02), ('wpid', 0x04), ('usbid', 0x08)]:
|
||||
if transport_bits & flag:
|
||||
tid_map[transport] = modelId[offset:offset + 2].hex().upper()
|
||||
offset = offset + 2
|
||||
return (unitId.hex().upper(), modelId.hex().upper(), tid_map)
|
||||
|
||||
|
||||
def get_kind(device):
|
||||
"""Reads a device's type.
|
||||
|
||||
|
|
|
@ -91,7 +91,10 @@ def _print_device(dev, num=None):
|
|||
|
||||
print(' %d: %s' % (num or dev.number, dev.name))
|
||||
print(' Device path :', dev.path)
|
||||
print(' USB id : 046d:%s' % (dev.wpid or dev.product_id))
|
||||
if dev.wpid:
|
||||
print(' WPID : %s' % dev.wpid)
|
||||
if dev.product_id:
|
||||
print(' USB id : 046d:%s' % dev.product_id)
|
||||
print(' Codename :', dev.codename)
|
||||
print(' Kind :', dev.kind)
|
||||
if dev.protocol:
|
||||
|
@ -101,6 +104,10 @@ def _print_device(dev, num=None):
|
|||
if dev.polling_rate:
|
||||
print(' Polling rate :', dev.polling_rate, 'ms (%dHz)' % (1000 // dev.polling_rate))
|
||||
print(' Serial number:', dev.serial)
|
||||
if dev.modelId:
|
||||
print(' Model ID: ', dev.modelId)
|
||||
if dev.unitId:
|
||||
print(' Unit ID: ', dev.unitId)
|
||||
if dev.firmware:
|
||||
for fw in dev.firmware:
|
||||
print(' %11s:' % fw.kind, (fw.name + ' ' + fw.version).strip())
|
||||
|
@ -126,7 +133,6 @@ def _print_device(dev, num=None):
|
|||
|
||||
if dev.online and dev.features:
|
||||
print(' Supports %d HID++ 2.0 features:' % len(dev.features))
|
||||
dev.persister = None # Give the device a fake persister
|
||||
dev_settings = []
|
||||
_settings_templates.check_feature_settings(dev, dev_settings)
|
||||
for index, feature in enumerate(dev.features):
|
||||
|
@ -202,6 +208,8 @@ def _print_device(dev, num=None):
|
|||
for fw in _hidpp20.get_firmware(dev):
|
||||
extras = _strhex(fw.extras) if fw.extras else ''
|
||||
print(' Firmware: %s %s %s %s' % (fw.kind, fw.name, fw.version, extras))
|
||||
unitId, modelId, tid_map = _hidpp20.get_ids(dev)
|
||||
print(' Unit ID: %s Model ID: %s Transport IDs: %s' % (unitId, modelId, tid_map))
|
||||
elif feature == _hidpp20.FEATURE.REPORT_RATE:
|
||||
print(' Polling Rate (ms): %d' % _hidpp20.get_polling_rate(dev))
|
||||
elif feature == _hidpp20.FEATURE.BATTERY_STATUS or feature == _hidpp20.FEATURE.BATTERY_VOLTAGE:
|
||||
|
|
|
@ -36,6 +36,8 @@ _file_path = _path.join(_XDG_CONFIG_HOME, 'solaar', 'config.json')
|
|||
|
||||
_KEY_VERSION = '_version'
|
||||
_KEY_NAME = '_name'
|
||||
_KEY_MODEL_ID = '_modelId'
|
||||
_KEY_UNIT_ID = '_unitId'
|
||||
_configuration = {}
|
||||
|
||||
|
||||
|
@ -82,8 +84,8 @@ def save():
|
|||
if _log.isEnabledFor(_INFO):
|
||||
_log.info('saved %s to %s', _configuration, _file_path)
|
||||
return True
|
||||
except Exception:
|
||||
_log.error('failed to save to %s', _file_path)
|
||||
except Exception as e:
|
||||
_log.error('failed to save to %s: %s', _file_path, e)
|
||||
|
||||
|
||||
def _cleanup(d):
|
||||
|
@ -96,38 +98,56 @@ def _cleanup(d):
|
|||
_cleanup(value)
|
||||
|
||||
|
||||
def _device_key(device):
|
||||
return '%s:%s' % (device.wpid, device.serial)
|
||||
|
||||
|
||||
class _DeviceEntry(dict):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(_DeviceEntry, self).__init__(*args, **kwargs)
|
||||
def __init__(self, device, **kwargs):
|
||||
super(_DeviceEntry, self).__init__(**kwargs)
|
||||
self[_KEY_NAME] = device.name
|
||||
self.update(device)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
super(_DeviceEntry, self).__setitem__(key, value)
|
||||
save()
|
||||
|
||||
def update(self, device):
|
||||
if device.modelId:
|
||||
self[_KEY_MODEL_ID] = device.modelId
|
||||
if device.unitId:
|
||||
self[_KEY_UNIT_ID] = device.unitId
|
||||
|
||||
def _device_entry(device):
|
||||
|
||||
def persister(device):
|
||||
if not _configuration:
|
||||
_load()
|
||||
|
||||
device_key = _device_key(device)
|
||||
c = _configuration.get(device_key) or {}
|
||||
entry = {}
|
||||
key = None
|
||||
if device.wpid: # connected via receiver
|
||||
entry = _configuration.get('%s:%s' % (device.wpid, device.serial), {})
|
||||
if entry or device.protocol == 1.0: # found entry or create entry for old-style devices
|
||||
key = '%s:%s' % (device.wpid, device.serial)
|
||||
elif not entry and device.modelId: # online new-style device so look for modelId and unitId
|
||||
for k, c in _configuration.items():
|
||||
if isinstance(c, dict) and c.get(_KEY_MODEL_ID) == device.modelId and c.get(_KEY_UNIT_ID) == device.unitId:
|
||||
entry = c # use the entry that matches modelId and unitId
|
||||
key = k
|
||||
break
|
||||
if device.wpid and entry: # move entry to wpid:serial
|
||||
del _configuration[key]
|
||||
key = '%s:%s' % (device.wpid, device.serial)
|
||||
_configuration[key] = entry
|
||||
elif device.wpid and not entry: # create now with wpid:serial
|
||||
key = '%s:%s' % (device.wpid, device.serial)
|
||||
else: # create now with modelId:unitId
|
||||
key = '%s:%s' % (device.modelId, device.unitId)
|
||||
else: # defer until more is known (i.e., device comes on line)
|
||||
return
|
||||
|
||||
if not isinstance(c, _DeviceEntry):
|
||||
c[_KEY_NAME] = device.name
|
||||
c = _DeviceEntry(c)
|
||||
_configuration[device_key] = c
|
||||
if key and not isinstance(entry, _DeviceEntry):
|
||||
entry = _DeviceEntry(device, **entry)
|
||||
_configuration[key] = entry
|
||||
|
||||
return c
|
||||
return entry
|
||||
|
||||
|
||||
def attach_to(device):
|
||||
"""Apply the last saved configuration to a device."""
|
||||
if not _configuration:
|
||||
_load()
|
||||
|
||||
persister = _device_entry(device)
|
||||
device.persister = persister
|
||||
pass
|
||||
|
|
Loading…
Reference in New Issue