device: regularize and improve battery status gathering and reporting
This commit is contained in:
parent
d362a24f17
commit
83eb836177
|
@ -341,6 +341,19 @@ class Device:
|
|||
self._persister = _configuration.persister(self)
|
||||
return self._persister
|
||||
|
||||
def battery(self): # None or level, next, status, voltage
|
||||
if self.protocol < 2.0:
|
||||
return _hidpp10.get_battery(self)
|
||||
else:
|
||||
battery_feature = self.persister.get('_battery', None) if self.persister else None
|
||||
if battery_feature != 0:
|
||||
result = _hidpp20.get_battery(self, battery_feature)
|
||||
if result:
|
||||
feature, level, next, status, voltage = result
|
||||
if self.persister and battery_feature is None:
|
||||
self.persister['_battery'] = feature
|
||||
return level, next, status, voltage
|
||||
|
||||
def enable_connection_notifications(self, enable=True):
|
||||
"""Enable or disable device (dis)connection notifications on this
|
||||
receiver."""
|
||||
|
|
|
@ -265,8 +265,8 @@ def parse_battery_status(register, reply):
|
|||
# some 'charging' notifications may come with no battery level information
|
||||
charge = None
|
||||
|
||||
# Return None for next charge level as this is not in HID++ 1.0 spec
|
||||
return charge, status_text, None
|
||||
# Return None for next charge level and voltage as these are not in HID++ 1.0 spec
|
||||
return charge, None, status_text, None
|
||||
|
||||
|
||||
def get_firmware(device):
|
||||
|
|
|
@ -1216,27 +1216,32 @@ def get_friendly_name(device):
|
|||
return name.decode('utf-8')
|
||||
|
||||
|
||||
def get_battery(device):
|
||||
"""Reads a device's battery level."""
|
||||
battery = feature_request(device, FEATURE.BATTERY_STATUS)
|
||||
if battery:
|
||||
discharge, next, status = _unpack('!BBB', battery[:3])
|
||||
def get_battery_status(device):
|
||||
report = feature_request(device, FEATURE.BATTERY_STATUS)
|
||||
if report:
|
||||
return decipher_battery_status(report)
|
||||
|
||||
|
||||
def decipher_battery_status(report):
|
||||
discharge, next, status = _unpack('!BBB', report[:3])
|
||||
discharge = None if discharge == 0 else discharge
|
||||
status = BATTERY_STATUS[status]
|
||||
if _log.isEnabledFor(_DEBUG):
|
||||
_log.debug('device %d battery %s%% charged, next %s%%, status %s', device.number, discharge, next, status)
|
||||
return discharge, status, next
|
||||
else:
|
||||
battery = feature_request(device, FEATURE.UNIFIED_BATTERY, 0x10)
|
||||
if battery:
|
||||
return decipher_unified_battery(battery)
|
||||
_log.debug('battery status %s%% charged, next %s%%, status %s', discharge, next, status)
|
||||
return FEATURE.BATTERY_STATUS, discharge, next, status, None
|
||||
|
||||
|
||||
def decipher_unified_battery(report):
|
||||
def get_battery_unified(device):
|
||||
report = feature_request(device, FEATURE.UNIFIED_BATTERY, 0x10)
|
||||
if report is not None:
|
||||
return decipher_battery_unified(report)
|
||||
|
||||
|
||||
def decipher_battery_unified(report):
|
||||
discharge, level, status, _ignore = _unpack('!BBBB', report[:4])
|
||||
status = BATTERY_STATUS[status]
|
||||
if _log.isEnabledFor(_DEBUG):
|
||||
_log.debug('battery %s%% charged, level %s, charging %s', discharge, level, status)
|
||||
_log.debug('battery unified %s%% charged, level %s, charging %s', discharge, level, status)
|
||||
level = (
|
||||
_BATTERY_APPROX.full if level == 8 # full
|
||||
else _BATTERY_APPROX.good if level == 4 # good
|
||||
|
@ -1244,13 +1249,7 @@ def decipher_unified_battery(report):
|
|||
else _BATTERY_APPROX.critical if level == 1 # critical
|
||||
else _BATTERY_APPROX.empty
|
||||
)
|
||||
return discharge if discharge else level, status, None
|
||||
|
||||
|
||||
def get_voltage(device):
|
||||
battery_voltage = feature_request(device, FEATURE.BATTERY_VOLTAGE)
|
||||
if battery_voltage:
|
||||
return decipher_voltage(battery_voltage)
|
||||
return FEATURE.UNIFIED_BATTERY, discharge if discharge else level, None, status, None
|
||||
|
||||
|
||||
# voltage to remaining charge from Logitech
|
||||
|
@ -1272,14 +1271,18 @@ battery_voltage_remaining = (
|
|||
)
|
||||
|
||||
|
||||
# modified to be much closer to battery reports
|
||||
def decipher_voltage(voltage_report):
|
||||
voltage, flags = _unpack('>HB', voltage_report[:3])
|
||||
def get_battery_voltage(device):
|
||||
report = feature_request(device, FEATURE.BATTERY_VOLTAGE)
|
||||
if report is not None:
|
||||
return decipher_battery_voltage(report)
|
||||
|
||||
|
||||
def decipher_battery_voltage(report):
|
||||
voltage, flags = _unpack('>HB', report[:3])
|
||||
status = BATTERY_STATUS.discharging
|
||||
charge_sts = ERROR.unknown
|
||||
charge_lvl = CHARGE_LEVEL.average
|
||||
charge_type = CHARGE_TYPE.standard
|
||||
|
||||
if flags & (1 << 7):
|
||||
status = BATTERY_STATUS.recharging
|
||||
charge_sts = CHARGE_STATUS[flags & 0x03]
|
||||
|
@ -1295,19 +1298,39 @@ def decipher_voltage(voltage_report):
|
|||
status = BATTERY_STATUS.slow_recharge
|
||||
elif (flags & (1 << 5)):
|
||||
charge_lvl = CHARGE_LEVEL.critical
|
||||
|
||||
for level in battery_voltage_remaining:
|
||||
if level[0] < voltage:
|
||||
charge_lvl = level[1]
|
||||
break
|
||||
|
||||
if _log.isEnabledFor(_DEBUG):
|
||||
_log.debug(
|
||||
'device ???, battery voltage %d mV, charging = %s, charge status %d = %s, charge level %s, charge type %s',
|
||||
voltage, status, (flags & 0x03), charge_sts, charge_lvl, charge_type
|
||||
'battery voltage %d mV, charging %s, status %d = %s, level %s, type %s', voltage, status, (flags & 0x03),
|
||||
charge_sts, charge_lvl, charge_type
|
||||
)
|
||||
return FEATURE.BATTERY_VOLTAGE, charge_lvl, None, status, voltage
|
||||
|
||||
return charge_lvl, status, voltage, charge_sts, charge_type
|
||||
|
||||
battery_functions = {
|
||||
FEATURE.BATTERY_STATUS: get_battery_status,
|
||||
FEATURE.BATTERY_VOLTAGE: get_battery_unified,
|
||||
FEATURE.UNIFIED_BATTERY: get_battery_voltage,
|
||||
}
|
||||
|
||||
|
||||
def get_battery(device, feature):
|
||||
"""Return battery information - feature, approximate level, next, charging, voltage"""
|
||||
if feature is not None:
|
||||
battery_function = battery_functions.get(feature, None)
|
||||
if battery_function:
|
||||
result = battery_function(device)
|
||||
if result:
|
||||
return result
|
||||
else:
|
||||
for battery_function in battery_functions.values():
|
||||
result = battery_function(device)
|
||||
if result:
|
||||
return result
|
||||
return 0, None, None, None, None
|
||||
|
||||
|
||||
def get_keys(device):
|
||||
|
|
|
@ -235,7 +235,7 @@ def _process_hidpp10_custom_notification(device, status, n):
|
|||
assert n.data[-1:] == b'\x00'
|
||||
data = chr(n.address).encode() + n.data
|
||||
charge, status_text, next_charge = _hidpp10.parse_battery_status(n.sub_id, data)
|
||||
status.set_battery_info(charge, status_text, next_charge)
|
||||
status.set_battery_info(charge, next_charge, status_text, None)
|
||||
return True
|
||||
|
||||
if n.sub_id == _R.keyboard_illumination:
|
||||
|
@ -315,11 +315,8 @@ def _process_feature_notification(device, status, n, feature):
|
|||
|
||||
if feature == _F.BATTERY_STATUS:
|
||||
if n.address == 0x00:
|
||||
discharge_level = ord(n.data[:1])
|
||||
discharge_level = None if discharge_level == 0 else discharge_level
|
||||
discharge_next_level = ord(n.data[1:2])
|
||||
battery_status = ord(n.data[2:3])
|
||||
status.set_battery_info(discharge_level, _hidpp20.BATTERY_STATUS[battery_status], discharge_next_level)
|
||||
_ignore, discharge_level, discharge_next_level, battery_status, voltage = _hidpp20.decipher_battery_status(n.data)
|
||||
status.set_battery_info(discharge_level, discharge_next_level, battery_status, voltage)
|
||||
elif n.address == 0x10:
|
||||
if _log.isEnabledFor(_INFO):
|
||||
_log.info('%s: spurious BATTERY status %s', device, n)
|
||||
|
@ -328,15 +325,15 @@ def _process_feature_notification(device, status, n, feature):
|
|||
|
||||
elif feature == _F.BATTERY_VOLTAGE:
|
||||
if n.address == 0x00:
|
||||
battery_level, battery_status, battery_voltage, _ignore, _ignore = _hidpp20.decipher_voltage(n.data)
|
||||
status.set_battery_info(battery_level, battery_status, None, battery_voltage)
|
||||
_ignore, level, next, status, voltage = _hidpp20.decipher_battery_voltage(n.data)
|
||||
status.set_battery_info(level, next, status, voltage)
|
||||
else:
|
||||
_log.warn('%s: unknown VOLTAGE %s', device, n)
|
||||
|
||||
elif feature == _F.UNIFIED_BATTERY:
|
||||
if n.address == 0x00:
|
||||
battery_level, battery_status, battery_voltage = _hidpp20.decipher_unified_battery(n.data)
|
||||
status.set_battery_info(battery_level, battery_status, None, battery_voltage)
|
||||
_ignore, level, next, status, voltage = _hidpp20.decipher_battery_unified(n.data)
|
||||
status.set_battery_info(level, next, status, voltage)
|
||||
else:
|
||||
_log.warn('%s: unknown UNIFIED BATTERY %s', device, n)
|
||||
|
||||
|
@ -348,12 +345,12 @@ def _process_feature_notification(device, status, n, feature):
|
|||
status_text = _hidpp20.BATTERY_STATUS.discharging
|
||||
if n.address == 0x00:
|
||||
status[_K.LIGHT_LEVEL] = None
|
||||
status.set_battery_info(charge, status_text, None)
|
||||
status.set_battery_info(charge, None, status_text, None)
|
||||
elif n.address == 0x10:
|
||||
status[_K.LIGHT_LEVEL] = lux
|
||||
if lux > 200:
|
||||
status_text = _hidpp20.BATTERY_STATUS.recharging
|
||||
status.set_battery_info(charge, status_text, None)
|
||||
status.set_battery_info(charge, None, status_text, None)
|
||||
elif n.address == 0x20:
|
||||
if _log.isEnabledFor(_DEBUG):
|
||||
_log.debug('%s: Light Check button pressed', device)
|
||||
|
|
|
@ -190,7 +190,7 @@ class DeviceStatus(dict):
|
|||
|
||||
__nonzero__ = __bool__
|
||||
|
||||
def set_battery_info(self, level, status, nextLevel=None, voltage=None, timestamp=None):
|
||||
def set_battery_info(self, level, nextLevel, status, voltage, timestamp=None):
|
||||
if _log.isEnabledFor(_DEBUG):
|
||||
_log.debug('%s: battery %s, %s', self._device, level, status)
|
||||
|
||||
|
@ -245,35 +245,14 @@ class DeviceStatus(dict):
|
|||
# Retrieve and regularize battery status
|
||||
def read_battery(self, timestamp=None):
|
||||
if self._active:
|
||||
d = self._device
|
||||
assert d
|
||||
|
||||
if d.protocol < 2.0:
|
||||
battery = _hidpp10.get_battery(d)
|
||||
self.set_battery_keys(battery)
|
||||
return
|
||||
|
||||
battery = _hidpp20.get_battery(d)
|
||||
if battery is None:
|
||||
v = _hidpp20.get_voltage(d)
|
||||
if v is not None:
|
||||
level, status, voltage, _ignore, _ignore = v
|
||||
self.set_battery_keys((level, status, None), voltage)
|
||||
return
|
||||
|
||||
# Really unnecessary, if the device has SOLAR_DASHBOARD it should be
|
||||
# broadcasting it's battery status anyway, it will just take a little while.
|
||||
# However, when the device has just been detected, it will not show
|
||||
# any battery status for a while (broadcasts happen every 90 seconds).
|
||||
if battery is None and d.features and _hidpp20.FEATURE.SOLAR_DASHBOARD in d.features:
|
||||
d.feature_request(_hidpp20.FEATURE.SOLAR_DASHBOARD, 0x00, 1, 1)
|
||||
return
|
||||
assert self._device
|
||||
battery = self._device.battery()
|
||||
self.set_battery_keys(battery)
|
||||
|
||||
def set_battery_keys(self, battery, voltage=None):
|
||||
def set_battery_keys(self, battery):
|
||||
if battery is not None:
|
||||
level, status, nextLevel = battery
|
||||
self.set_battery_info(level, status, nextLevel, voltage)
|
||||
level, nextLevel, status, voltage = battery
|
||||
self.set_battery_info(level, nextLevel, status, voltage)
|
||||
elif self.get(KEYS.BATTERY_STATUS, None) is not None:
|
||||
self[KEYS.BATTERY_STATUS] = None
|
||||
self[KEYS.BATTERY_CHARGING] = None
|
||||
|
|
|
@ -67,19 +67,14 @@ def _battery_text(level):
|
|||
|
||||
|
||||
def _battery_line(dev):
|
||||
battery = _hidpp20.get_battery(dev)
|
||||
if battery is None:
|
||||
battery = _hidpp10.get_battery(dev)
|
||||
battery = dev.battery()
|
||||
if battery is not None:
|
||||
level, status, nextLevel = battery
|
||||
level, nextLevel, status, voltage = battery
|
||||
text = _battery_text(level)
|
||||
if voltage is not None:
|
||||
text = text + (' %smV ' % voltage)
|
||||
nextText = '' if nextLevel is None else ', next level ' + _battery_text(nextLevel)
|
||||
print(' Battery: %s, %s%s.' % (text, status, nextText))
|
||||
else:
|
||||
battery_voltage = _hidpp20.get_voltage(dev)
|
||||
if battery_voltage:
|
||||
(level, status, voltage, charge_sts, charge_type) = battery_voltage
|
||||
print(' Battery: %smV, %s, %s.' % (voltage, status, level))
|
||||
else:
|
||||
print(' Battery status unavailable.')
|
||||
|
||||
|
@ -225,7 +220,7 @@ def _print_device(dev, num=None):
|
|||
else:
|
||||
mode = 'On-Board'
|
||||
print(' Device Mode: %s' % mode)
|
||||
elif feature in (_F.BATTERY_STATUS, _F.BATTERY_VOLTAGE, _F.BATTERY_VOLTAGE):
|
||||
elif _hidpp20.battery_functions.get(feature, None):
|
||||
print('', end=' ')
|
||||
_battery_line(dev)
|
||||
for setting in dev_settings:
|
||||
|
|
Loading…
Reference in New Issue