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
|
from logging import getLogger
|
||||||
|
|
||||||
import hidapi as _hid
|
import hidapi as _hid
|
||||||
|
import solaar.configuration as _configuration
|
||||||
|
|
||||||
from . import base as _base
|
from . import base as _base
|
||||||
from . import hidpp10 as _hidpp10
|
from . import hidpp10 as _hidpp10
|
||||||
|
|
@ -59,6 +60,14 @@ class Device(object):
|
||||||
self._protocol = None
|
self._protocol = None
|
||||||
# serial number (an 8-char hex string)
|
# serial number (an 8-char hex string)
|
||||||
self._serial = None
|
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._firmware = None
|
||||||
self._keys = None
|
self._keys = None
|
||||||
|
|
@ -196,6 +205,29 @@ class Device(object):
|
||||||
self._name = _hidpp20.get_name(self)
|
self._name = _hidpp20.get_name(self)
|
||||||
return self._name or self.codename or ('Unknown device %s' % (self.wpid or self.product_id))
|
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
|
@property
|
||||||
def kind(self):
|
def kind(self):
|
||||||
if not self._kind:
|
if not self._kind:
|
||||||
|
|
@ -285,7 +317,7 @@ class Device(object):
|
||||||
def settings(self):
|
def settings(self):
|
||||||
if self._settings is None:
|
if self._settings is None:
|
||||||
self._settings = []
|
self._settings = []
|
||||||
if self.descriptor and self.descriptor.settings:
|
if self.descriptor and self.descriptor.settings and self.persister:
|
||||||
self._settings = []
|
self._settings = []
|
||||||
for s in self.descriptor.settings:
|
for s in self.descriptor.settings:
|
||||||
try:
|
try:
|
||||||
|
|
@ -300,6 +332,12 @@ class Device(object):
|
||||||
self._feature_settings_checked = _check_feature_settings(self, self._settings)
|
self._feature_settings_checked = _check_feature_settings(self, self._settings)
|
||||||
return 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):
|
def get_kind_from_index(self, index, receiver):
|
||||||
"""Get device kind from 27Mhz device index"""
|
"""Get device kind from 27Mhz device index"""
|
||||||
# accordingly to drivers/hid/hid-logitech-dj.c
|
# accordingly to drivers/hid/hid-logitech-dj.c
|
||||||
|
|
|
||||||
|
|
@ -1050,6 +1050,21 @@ def get_firmware(device):
|
||||||
return tuple(fw)
|
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):
|
def get_kind(device):
|
||||||
"""Reads a device's type.
|
"""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(' %d: %s' % (num or dev.number, dev.name))
|
||||||
print(' Device path :', dev.path)
|
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(' Codename :', dev.codename)
|
||||||
print(' Kind :', dev.kind)
|
print(' Kind :', dev.kind)
|
||||||
if dev.protocol:
|
if dev.protocol:
|
||||||
|
|
@ -101,6 +104,10 @@ def _print_device(dev, num=None):
|
||||||
if dev.polling_rate:
|
if dev.polling_rate:
|
||||||
print(' Polling rate :', dev.polling_rate, 'ms (%dHz)' % (1000 // dev.polling_rate))
|
print(' Polling rate :', dev.polling_rate, 'ms (%dHz)' % (1000 // dev.polling_rate))
|
||||||
print(' Serial number:', dev.serial)
|
print(' Serial number:', dev.serial)
|
||||||
|
if dev.modelId:
|
||||||
|
print(' Model ID: ', dev.modelId)
|
||||||
|
if dev.unitId:
|
||||||
|
print(' Unit ID: ', dev.unitId)
|
||||||
if dev.firmware:
|
if dev.firmware:
|
||||||
for fw in dev.firmware:
|
for fw in dev.firmware:
|
||||||
print(' %11s:' % fw.kind, (fw.name + ' ' + fw.version).strip())
|
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:
|
if dev.online and dev.features:
|
||||||
print(' Supports %d HID++ 2.0 features:' % len(dev.features))
|
print(' Supports %d HID++ 2.0 features:' % len(dev.features))
|
||||||
dev.persister = None # Give the device a fake persister
|
|
||||||
dev_settings = []
|
dev_settings = []
|
||||||
_settings_templates.check_feature_settings(dev, dev_settings)
|
_settings_templates.check_feature_settings(dev, dev_settings)
|
||||||
for index, feature in enumerate(dev.features):
|
for index, feature in enumerate(dev.features):
|
||||||
|
|
@ -202,6 +208,8 @@ def _print_device(dev, num=None):
|
||||||
for fw in _hidpp20.get_firmware(dev):
|
for fw in _hidpp20.get_firmware(dev):
|
||||||
extras = _strhex(fw.extras) if fw.extras else ''
|
extras = _strhex(fw.extras) if fw.extras else ''
|
||||||
print(' Firmware: %s %s %s %s' % (fw.kind, fw.name, fw.version, extras))
|
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:
|
elif feature == _hidpp20.FEATURE.REPORT_RATE:
|
||||||
print(' Polling Rate (ms): %d' % _hidpp20.get_polling_rate(dev))
|
print(' Polling Rate (ms): %d' % _hidpp20.get_polling_rate(dev))
|
||||||
elif feature == _hidpp20.FEATURE.BATTERY_STATUS or feature == _hidpp20.FEATURE.BATTERY_VOLTAGE:
|
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_VERSION = '_version'
|
||||||
_KEY_NAME = '_name'
|
_KEY_NAME = '_name'
|
||||||
|
_KEY_MODEL_ID = '_modelId'
|
||||||
|
_KEY_UNIT_ID = '_unitId'
|
||||||
_configuration = {}
|
_configuration = {}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -82,8 +84,8 @@ def save():
|
||||||
if _log.isEnabledFor(_INFO):
|
if _log.isEnabledFor(_INFO):
|
||||||
_log.info('saved %s to %s', _configuration, _file_path)
|
_log.info('saved %s to %s', _configuration, _file_path)
|
||||||
return True
|
return True
|
||||||
except Exception:
|
except Exception as e:
|
||||||
_log.error('failed to save to %s', _file_path)
|
_log.error('failed to save to %s: %s', _file_path, e)
|
||||||
|
|
||||||
|
|
||||||
def _cleanup(d):
|
def _cleanup(d):
|
||||||
|
|
@ -96,38 +98,56 @@ def _cleanup(d):
|
||||||
_cleanup(value)
|
_cleanup(value)
|
||||||
|
|
||||||
|
|
||||||
def _device_key(device):
|
|
||||||
return '%s:%s' % (device.wpid, device.serial)
|
|
||||||
|
|
||||||
|
|
||||||
class _DeviceEntry(dict):
|
class _DeviceEntry(dict):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, device, **kwargs):
|
||||||
super(_DeviceEntry, self).__init__(*args, **kwargs)
|
super(_DeviceEntry, self).__init__(**kwargs)
|
||||||
|
self[_KEY_NAME] = device.name
|
||||||
|
self.update(device)
|
||||||
|
|
||||||
def __setitem__(self, key, value):
|
def __setitem__(self, key, value):
|
||||||
super(_DeviceEntry, self).__setitem__(key, value)
|
super(_DeviceEntry, self).__setitem__(key, value)
|
||||||
save()
|
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:
|
if not _configuration:
|
||||||
_load()
|
_load()
|
||||||
|
|
||||||
device_key = _device_key(device)
|
entry = {}
|
||||||
c = _configuration.get(device_key) or {}
|
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):
|
if key and not isinstance(entry, _DeviceEntry):
|
||||||
c[_KEY_NAME] = device.name
|
entry = _DeviceEntry(device, **entry)
|
||||||
c = _DeviceEntry(c)
|
_configuration[key] = entry
|
||||||
_configuration[device_key] = c
|
|
||||||
|
|
||||||
return c
|
return entry
|
||||||
|
|
||||||
|
|
||||||
def attach_to(device):
|
def attach_to(device):
|
||||||
"""Apply the last saved configuration to a device."""
|
pass
|
||||||
if not _configuration:
|
|
||||||
_load()
|
|
||||||
|
|
||||||
persister = _device_entry(device)
|
|
||||||
device.persister = persister
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue