device: implement UNIFIED_BATTERY feature
device: implement UNIFIED_BATTERY feature
This commit is contained in:
parent
1162ccb897
commit
733bf913e6
|
@ -270,4 +270,6 @@ class KwException(Exception):
|
||||||
"""Firmware information."""
|
"""Firmware information."""
|
||||||
FirmwareInfo = namedtuple('FirmwareInfo', ['kind', 'name', 'version', 'extras'])
|
FirmwareInfo = namedtuple('FirmwareInfo', ['kind', 'name', 'version', 'extras'])
|
||||||
|
|
||||||
|
BATTERY_APPROX = NamedInts(empty=0, critical=5, low=20, good=50, full=90)
|
||||||
|
|
||||||
del namedtuple
|
del namedtuple
|
||||||
|
|
|
@ -21,6 +21,7 @@ from __future__ import absolute_import, division, print_function, unicode_litera
|
||||||
|
|
||||||
from logging import getLogger # , DEBUG as _DEBUG
|
from logging import getLogger # , DEBUG as _DEBUG
|
||||||
|
|
||||||
|
from .common import BATTERY_APPROX as _BATTERY_APPROX
|
||||||
from .common import FirmwareInfo as _FirmwareInfo
|
from .common import FirmwareInfo as _FirmwareInfo
|
||||||
from .common import NamedInts as _NamedInts
|
from .common import NamedInts as _NamedInts
|
||||||
from .common import bytes2int as _bytes2int
|
from .common import bytes2int as _bytes2int
|
||||||
|
@ -102,8 +103,6 @@ ERROR = _NamedInts(
|
||||||
)
|
)
|
||||||
|
|
||||||
PAIRING_ERRORS = _NamedInts(device_timeout=0x01, device_not_supported=0x02, too_many_devices=0x03, sequence_timeout=0x06)
|
PAIRING_ERRORS = _NamedInts(device_timeout=0x01, device_not_supported=0x02, too_many_devices=0x03, sequence_timeout=0x06)
|
||||||
|
|
||||||
BATTERY_APPOX = _NamedInts(empty=0, critical=5, low=20, good=50, full=90)
|
|
||||||
"""Known registers.
|
"""Known registers.
|
||||||
Devices usually have a (small) sub-set of these. Some registers are only
|
Devices usually have a (small) sub-set of these. Some registers are only
|
||||||
applicable to certain device kinds (e.g. smooth_scroll only applies to mice."""
|
applicable to certain device kinds (e.g. smooth_scroll only applies to mice."""
|
||||||
|
@ -211,12 +210,12 @@ def parse_battery_status(register, reply):
|
||||||
if register == REGISTERS.battery_status:
|
if register == REGISTERS.battery_status:
|
||||||
status_byte = ord(reply[:1])
|
status_byte = ord(reply[:1])
|
||||||
charge = (
|
charge = (
|
||||||
BATTERY_APPOX.full if status_byte == 7 # full
|
_BATTERY_APPROX.full if status_byte == 7 # full
|
||||||
else BATTERY_APPOX.good if status_byte == 5 # good
|
else _BATTERY_APPROX.good if status_byte == 5 # good
|
||||||
else BATTERY_APPOX.low if status_byte == 3 # low
|
else _BATTERY_APPROX.low if status_byte == 3 # low
|
||||||
else BATTERY_APPOX.critical if status_byte == 1 # critical
|
else _BATTERY_APPROX.critical if status_byte == 1 # critical
|
||||||
# pure 'charging' notifications may come without a status
|
# pure 'charging' notifications may come without a status
|
||||||
else BATTERY_APPOX.empty
|
else _BATTERY_APPROX.empty
|
||||||
)
|
)
|
||||||
|
|
||||||
charging_byte = ord(reply[1:2])
|
charging_byte = ord(reply[1:2])
|
||||||
|
@ -284,17 +283,17 @@ def set_3leds(device, battery_level=None, charging=None, warning=None):
|
||||||
return
|
return
|
||||||
|
|
||||||
if battery_level is not None:
|
if battery_level is not None:
|
||||||
if battery_level < BATTERY_APPOX.critical:
|
if battery_level < _BATTERY_APPROX.critical:
|
||||||
# 1 orange, and force blink
|
# 1 orange, and force blink
|
||||||
v1, v2 = 0x22, 0x00
|
v1, v2 = 0x22, 0x00
|
||||||
warning = True
|
warning = True
|
||||||
elif battery_level < BATTERY_APPOX.low:
|
elif battery_level < _BATTERY_APPROX.low:
|
||||||
# 1 orange
|
# 1 orange
|
||||||
v1, v2 = 0x22, 0x00
|
v1, v2 = 0x22, 0x00
|
||||||
elif battery_level < BATTERY_APPOX.good:
|
elif battery_level < _BATTERY_APPROX.good:
|
||||||
# 1 green
|
# 1 green
|
||||||
v1, v2 = 0x20, 0x00
|
v1, v2 = 0x20, 0x00
|
||||||
elif battery_level < BATTERY_APPOX.full:
|
elif battery_level < _BATTERY_APPROX.full:
|
||||||
# 2 greens
|
# 2 greens
|
||||||
v1, v2 = 0x20, 0x02
|
v1, v2 = 0x20, 0x02
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -28,6 +28,7 @@ from logging import getLogger
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from . import special_keys
|
from . import special_keys
|
||||||
|
from .common import BATTERY_APPROX as _BATTERY_APPROX
|
||||||
from .common import FirmwareInfo as _FirmwareInfo
|
from .common import FirmwareInfo as _FirmwareInfo
|
||||||
from .common import KwException as _KwException
|
from .common import KwException as _KwException
|
||||||
from .common import NamedInt as _NamedInt
|
from .common import NamedInt as _NamedInt
|
||||||
|
@ -72,6 +73,7 @@ FEATURE = _NamedInts(
|
||||||
DFU=0x00D0,
|
DFU=0x00D0,
|
||||||
BATTERY_STATUS=0x1000,
|
BATTERY_STATUS=0x1000,
|
||||||
BATTERY_VOLTAGE=0x1001,
|
BATTERY_VOLTAGE=0x1001,
|
||||||
|
UNIFIED_BATTERY=0x1004,
|
||||||
CHARGING_CONTROL=0x1010,
|
CHARGING_CONTROL=0x1010,
|
||||||
LED_CONTROL=0x1300,
|
LED_CONTROL=0x1300,
|
||||||
GENERIC_TEST=0x1800,
|
GENERIC_TEST=0x1800,
|
||||||
|
@ -1131,14 +1133,31 @@ def get_battery(device):
|
||||||
"""Reads a device's battery level."""
|
"""Reads a device's battery level."""
|
||||||
battery = feature_request(device, FEATURE.BATTERY_STATUS)
|
battery = feature_request(device, FEATURE.BATTERY_STATUS)
|
||||||
if battery:
|
if battery:
|
||||||
discharge, dischargeNext, status = _unpack('!BBB', battery[:3])
|
discharge, next, status = _unpack('!BBB', battery[:3])
|
||||||
discharge = None if discharge == 0 else discharge
|
discharge = None if discharge == 0 else discharge
|
||||||
|
status = BATTERY_STATUS[status]
|
||||||
if _log.isEnabledFor(_DEBUG):
|
if _log.isEnabledFor(_DEBUG):
|
||||||
_log.debug(
|
_log.debug('device %d battery %s%% charged, next %s%%, status %s', device.number, discharge, next, status)
|
||||||
'device %d battery %s%% charged, next level %s%% charge, status %s = %s', device.number, discharge,
|
return discharge, status, next
|
||||||
dischargeNext, status, BATTERY_STATUS[status]
|
else:
|
||||||
)
|
battery = feature_request(device, FEATURE.UNIFIED_BATTERY, 0x10)
|
||||||
return discharge, BATTERY_STATUS[status], dischargeNext
|
if battery:
|
||||||
|
return decipher_unified_battery(battery)
|
||||||
|
|
||||||
|
|
||||||
|
def decipher_unified_battery(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, status)
|
||||||
|
level = (
|
||||||
|
_BATTERY_APPROX.full if level == 8 # full
|
||||||
|
else _BATTERY_APPROX.good if level == 4 # good
|
||||||
|
else _BATTERY_APPROX.low if level == 2 # low
|
||||||
|
else _BATTERY_APPROX.critical if level == 1 # critical
|
||||||
|
else _BATTERY_APPROX.empty
|
||||||
|
)
|
||||||
|
return discharge if discharge else level, status, None
|
||||||
|
|
||||||
|
|
||||||
def get_voltage(device):
|
def get_voltage(device):
|
||||||
|
|
|
@ -275,6 +275,14 @@ def _process_feature_notification(device, status, n, feature):
|
||||||
_log.warn('%s: unknown VOLTAGE %s', device, n)
|
_log.warn('%s: unknown VOLTAGE %s', device, n)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
if 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)
|
||||||
|
else:
|
||||||
|
_log.warn('%s: unknown UNIFIED BATTERY %s', device, n)
|
||||||
|
return True
|
||||||
|
|
||||||
# TODO: what are REPROG_CONTROLS_V{2,3}?
|
# TODO: what are REPROG_CONTROLS_V{2,3}?
|
||||||
if feature == _F.REPROG_CONTROLS:
|
if feature == _F.REPROG_CONTROLS:
|
||||||
if n.address == 0x00:
|
if n.address == 0x00:
|
||||||
|
|
|
@ -25,6 +25,7 @@ from time import time as _timestamp
|
||||||
|
|
||||||
from . import hidpp10 as _hidpp10
|
from . import hidpp10 as _hidpp10
|
||||||
from . import hidpp20 as _hidpp20
|
from . import hidpp20 as _hidpp20
|
||||||
|
from .common import BATTERY_APPROX as _BATTERY_APPROX
|
||||||
from .common import NamedInt as _NamedInt
|
from .common import NamedInt as _NamedInt
|
||||||
from .common import NamedInts as _NamedInts
|
from .common import NamedInts as _NamedInts
|
||||||
from .i18n import _, ngettext
|
from .i18n import _, ngettext
|
||||||
|
@ -194,11 +195,11 @@ class DeviceStatus(dict):
|
||||||
# charging state info, so do our best to infer a level (even if it is just the last level)
|
# charging state info, so do our best to infer a level (even if it is just the last level)
|
||||||
# It is not always possible to do this well
|
# It is not always possible to do this well
|
||||||
if status == _hidpp20.BATTERY_STATUS.full:
|
if status == _hidpp20.BATTERY_STATUS.full:
|
||||||
level = _hidpp10.BATTERY_APPOX.full
|
level = _BATTERY_APPROX.full
|
||||||
elif status in (_hidpp20.BATTERY_STATUS.almost_full, _hidpp20.BATTERY_STATUS.recharging):
|
elif status in (_hidpp20.BATTERY_STATUS.almost_full, _hidpp20.BATTERY_STATUS.recharging):
|
||||||
level = _hidpp10.BATTERY_APPOX.good
|
level = _BATTERY_APPROX.good
|
||||||
elif status == _hidpp20.BATTERY_STATUS.slow_recharge:
|
elif status == _hidpp20.BATTERY_STATUS.slow_recharge:
|
||||||
level = _hidpp10.BATTERY_APPOX.low
|
level = _BATTERY_APPROX.low
|
||||||
else:
|
else:
|
||||||
level = self.get(KEYS.BATTERY_LEVEL)
|
level = self.get(KEYS.BATTERY_LEVEL)
|
||||||
else:
|
else:
|
||||||
|
|
Loading…
Reference in New Issue