device: change status battery fields to Battery objects
This commit is contained in:
parent
3916c189be
commit
64d8cad81a
|
@ -1,4 +1,5 @@
|
||||||
## Copyright (C) 2012-2013 Daniel Pavel
|
## Copyright (C) 2012-2013 Daniel Pavel
|
||||||
|
## Copyright (C) 2014-2024 Solaar Contributors https://pwr-solaar.github.io/Solaar/
|
||||||
##
|
##
|
||||||
## This program is free software; you can redistribute it and/or modify
|
## This program is free software; you can redistribute it and/or modify
|
||||||
## it under the terms of the GNU General Public License as published by
|
## it under the terms of the GNU General Public License as published by
|
||||||
|
@ -18,9 +19,13 @@
|
||||||
|
|
||||||
from binascii import hexlify as _hexlify
|
from binascii import hexlify as _hexlify
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
import yaml as _yaml
|
import yaml as _yaml
|
||||||
|
|
||||||
|
from .i18n import _
|
||||||
|
|
||||||
|
|
||||||
def is_string(d):
|
def is_string(d):
|
||||||
return isinstance(d, str)
|
return isinstance(d, str)
|
||||||
|
@ -546,18 +551,57 @@ 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)
|
|
||||||
|
|
||||||
BATTERY_STATUS = NamedInts(
|
@dataclass
|
||||||
discharging=0x00,
|
class Battery:
|
||||||
recharging=0x01,
|
"""Information about the current state of a battery"""
|
||||||
almost_full=0x02,
|
|
||||||
full=0x03,
|
|
||||||
slow_recharge=0x04,
|
|
||||||
invalid_battery=0x05,
|
|
||||||
thermal_error=0x06,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
level: Optional[NamedInt | int]
|
||||||
|
next_level: Optional[NamedInt | int]
|
||||||
|
status: Optional[NamedInt]
|
||||||
|
voltage: Optional[int]
|
||||||
|
light_level: Optional[int] = None # light level for devices with solaar recharging
|
||||||
|
|
||||||
def BATTERY_OK(status):
|
def __post_init__(self):
|
||||||
return status not in (BATTERY_STATUS.invalid_battery, BATTERY_STATUS.thermal_error)
|
if self.level is None: # infer level from status if needed and possible
|
||||||
|
if self.status == Battery.STATUS.full:
|
||||||
|
self.level = Battery.APPROX.full
|
||||||
|
elif self.status in (Battery.STATUS.almost_full, Battery.STATUS.recharging):
|
||||||
|
self.level = Battery.APPROX.good
|
||||||
|
elif self.status == Battery.STATUS.slow_recharge:
|
||||||
|
self.level = Battery.APPROX.low
|
||||||
|
|
||||||
|
STATUS = NamedInts(
|
||||||
|
discharging=0x00,
|
||||||
|
recharging=0x01,
|
||||||
|
almost_full=0x02,
|
||||||
|
full=0x03,
|
||||||
|
slow_recharge=0x04,
|
||||||
|
invalid_battery=0x05,
|
||||||
|
thermal_error=0x06,
|
||||||
|
)
|
||||||
|
|
||||||
|
APPROX = NamedInts(empty=0, critical=5, low=20, good=50, full=90)
|
||||||
|
|
||||||
|
ATTENTION_LEVEL = 5
|
||||||
|
|
||||||
|
def ok(self):
|
||||||
|
return self.status not in (Battery.STATUS.invalid_battery, Battery.STATUS.thermal_error) and (
|
||||||
|
self.level is None or self.level > Battery.ATTENTION_LEVEL
|
||||||
|
)
|
||||||
|
|
||||||
|
def charging(self):
|
||||||
|
return self.status in (
|
||||||
|
Battery.STATUS.recharging,
|
||||||
|
Battery.STATUS.almost_full,
|
||||||
|
Battery.STATUS.full,
|
||||||
|
Battery.STATUS.slow_recharge,
|
||||||
|
)
|
||||||
|
|
||||||
|
def to_str(self):
|
||||||
|
if isinstance(self.level, NamedInt):
|
||||||
|
return _("Battery: %(level)s (%(status)s)") % {"level": _(self.level), "status": _(self.status)}
|
||||||
|
elif isinstance(self.level, int):
|
||||||
|
return _("Battery: %(percent)d%% (%(status)s)") % {"percent": self.level, "status": _(self.status)}
|
||||||
|
else:
|
||||||
|
return ""
|
||||||
|
|
|
@ -342,10 +342,10 @@ class Device:
|
||||||
if battery_feature != 0:
|
if battery_feature != 0:
|
||||||
result = _hidpp20.get_battery(self, battery_feature)
|
result = _hidpp20.get_battery(self, battery_feature)
|
||||||
try:
|
try:
|
||||||
feature, level, next, status, voltage = result
|
feature, battery = result
|
||||||
if self.persister and battery_feature is None:
|
if self.persister and battery_feature is None:
|
||||||
self.persister["_battery"] = feature
|
self.persister["_battery"] = feature
|
||||||
return level, next, status, voltage
|
return battery
|
||||||
except Exception:
|
except Exception:
|
||||||
if self.persister and battery_feature is None:
|
if self.persister and battery_feature is None:
|
||||||
self.persister["_battery"] = result
|
self.persister["_battery"] = result
|
||||||
|
|
|
@ -16,8 +16,7 @@
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from .common import BATTERY_APPROX as _BATTERY_APPROX
|
from .common import Battery as _Battery
|
||||||
from .common import BATTERY_STATUS as _BATTERY_STATUS
|
|
||||||
from .common import FirmwareInfo as _FirmwareInfo
|
from .common import FirmwareInfo as _FirmwareInfo
|
||||||
from .common import bytes2int as _bytes2int
|
from .common import bytes2int as _bytes2int
|
||||||
from .common import int2bytes as _int2bytes
|
from .common import int2bytes as _int2bytes
|
||||||
|
@ -122,17 +121,17 @@ class Hidpp10:
|
||||||
return
|
return
|
||||||
|
|
||||||
if battery_level is not None:
|
if battery_level is not None:
|
||||||
if battery_level < _BATTERY_APPROX.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_APPROX.low:
|
elif battery_level < _Battery.APPROX.low:
|
||||||
# 1 orange
|
# 1 orange
|
||||||
v1, v2 = 0x22, 0x00
|
v1, v2 = 0x22, 0x00
|
||||||
elif battery_level < _BATTERY_APPROX.good:
|
elif battery_level < _Battery.APPROX.good:
|
||||||
# 1 green
|
# 1 green
|
||||||
v1, v2 = 0x20, 0x00
|
v1, v2 = 0x20, 0x00
|
||||||
elif battery_level < _BATTERY_APPROX.full:
|
elif battery_level < _Battery.APPROX.full:
|
||||||
# 2 greens
|
# 2 greens
|
||||||
v1, v2 = 0x20, 0x02
|
v1, v2 = 0x20, 0x02
|
||||||
else:
|
else:
|
||||||
|
@ -196,38 +195,38 @@ def parse_battery_status(register, reply):
|
||||||
charge = ord(reply[:1])
|
charge = ord(reply[:1])
|
||||||
status_byte = ord(reply[2:3]) & 0xF0
|
status_byte = ord(reply[2:3]) & 0xF0
|
||||||
status_text = (
|
status_text = (
|
||||||
_BATTERY_STATUS.discharging
|
_Battery.STATUS.discharging
|
||||||
if status_byte == 0x30
|
if status_byte == 0x30
|
||||||
else _BATTERY_STATUS.recharging
|
else _Battery.STATUS.recharging
|
||||||
if status_byte == 0x50
|
if status_byte == 0x50
|
||||||
else _BATTERY_STATUS.full
|
else _Battery.STATUS.full
|
||||||
if status_byte == 0x90
|
if status_byte == 0x90
|
||||||
else None
|
else None
|
||||||
)
|
)
|
||||||
return charge, None, status_text, None
|
return _Battery(charge, None, status_text, None)
|
||||||
|
|
||||||
if register == REGISTERS.battery_status:
|
if register == REGISTERS.battery_status:
|
||||||
status_byte = ord(reply[:1])
|
status_byte = ord(reply[:1])
|
||||||
charge = (
|
charge = (
|
||||||
_BATTERY_APPROX.full
|
_Battery.APPROX.full
|
||||||
if status_byte == 7 # full
|
if status_byte == 7 # full
|
||||||
else _BATTERY_APPROX.good
|
else _Battery.APPROX.good
|
||||||
if status_byte == 5 # good
|
if status_byte == 5 # good
|
||||||
else _BATTERY_APPROX.low
|
else _Battery.APPROX.low
|
||||||
if status_byte == 3 # low
|
if status_byte == 3 # low
|
||||||
else _BATTERY_APPROX.critical
|
else _Battery.APPROX.critical
|
||||||
if status_byte == 1 # critical
|
if status_byte == 1 # critical
|
||||||
# pure 'charging' notifications may come without a status
|
# pure 'charging' notifications may come without a status
|
||||||
else _BATTERY_APPROX.empty
|
else _Battery.APPROX.empty
|
||||||
)
|
)
|
||||||
|
|
||||||
charging_byte = ord(reply[1:2])
|
charging_byte = ord(reply[1:2])
|
||||||
if charging_byte == 0x00:
|
if charging_byte == 0x00:
|
||||||
status_text = _BATTERY_STATUS.discharging
|
status_text = _Battery.STATUS.discharging
|
||||||
elif charging_byte & 0x21 == 0x21:
|
elif charging_byte & 0x21 == 0x21:
|
||||||
status_text = _BATTERY_STATUS.recharging
|
status_text = _Battery.STATUS.recharging
|
||||||
elif charging_byte & 0x22 == 0x22:
|
elif charging_byte & 0x22 == 0x22:
|
||||||
status_text = _BATTERY_STATUS.full
|
status_text = _Battery.STATUS.full
|
||||||
else:
|
else:
|
||||||
logger.warning("could not parse 0x07 battery status: %02X (level %02X)", charging_byte, status_byte)
|
logger.warning("could not parse 0x07 battery status: %02X (level %02X)", charging_byte, status_byte)
|
||||||
status_text = None
|
status_text = None
|
||||||
|
@ -237,4 +236,4 @@ def parse_battery_status(register, reply):
|
||||||
charge = None
|
charge = None
|
||||||
|
|
||||||
# Return None for next charge level and voltage as these are not in HID++ 1.0 spec
|
# Return None for next charge level and voltage as these are not in HID++ 1.0 spec
|
||||||
return charge, None, status_text, None
|
return _Battery(charge, None, status_text, None)
|
||||||
|
|
|
@ -27,8 +27,7 @@ import yaml as _yaml
|
||||||
|
|
||||||
from . import exceptions, special_keys
|
from . import exceptions, special_keys
|
||||||
from . import hidpp10_constants as _hidpp10_constants
|
from . import hidpp10_constants as _hidpp10_constants
|
||||||
from .common import BATTERY_APPROX as _BATTERY_APPROX
|
from .common import Battery
|
||||||
from .common import BATTERY_STATUS as _BATTERY_STATUS
|
|
||||||
from .common import FirmwareInfo as _FirmwareInfo
|
from .common import FirmwareInfo as _FirmwareInfo
|
||||||
from .common import NamedInt as _NamedInt
|
from .common import NamedInt as _NamedInt
|
||||||
from .common import NamedInts as _NamedInts
|
from .common import NamedInts as _NamedInts
|
||||||
|
@ -1730,31 +1729,31 @@ battery_functions = {
|
||||||
def decipher_battery_status(report):
|
def decipher_battery_status(report):
|
||||||
discharge, next, status = _unpack("!BBB", report[:3])
|
discharge, next, status = _unpack("!BBB", report[:3])
|
||||||
discharge = None if discharge == 0 else discharge
|
discharge = None if discharge == 0 else discharge
|
||||||
status = _BATTERY_STATUS[status]
|
status = Battery.STATUS[status]
|
||||||
if logger.isEnabledFor(logging.DEBUG):
|
if logger.isEnabledFor(logging.DEBUG):
|
||||||
logger.debug("battery status %s%% charged, next %s%%, status %s", discharge, next, status)
|
logger.debug("battery status %s%% charged, next %s%%, status %s", discharge, next, status)
|
||||||
return FEATURE.BATTERY_STATUS, discharge, next, status, None
|
return FEATURE.BATTERY_STATUS, Battery(discharge, next, status, None)
|
||||||
|
|
||||||
|
|
||||||
def decipher_battery_voltage(report):
|
def decipher_battery_voltage(report):
|
||||||
voltage, flags = _unpack(">HB", report[:3])
|
voltage, flags = _unpack(">HB", report[:3])
|
||||||
status = _BATTERY_STATUS.discharging
|
status = Battery.STATUS.discharging
|
||||||
charge_sts = ERROR.unknown
|
charge_sts = ERROR.unknown
|
||||||
charge_lvl = CHARGE_LEVEL.average
|
charge_lvl = CHARGE_LEVEL.average
|
||||||
charge_type = CHARGE_TYPE.standard
|
charge_type = CHARGE_TYPE.standard
|
||||||
if flags & (1 << 7):
|
if flags & (1 << 7):
|
||||||
status = _BATTERY_STATUS.recharging
|
status = Battery.STATUS.recharging
|
||||||
charge_sts = CHARGE_STATUS[flags & 0x03]
|
charge_sts = CHARGE_STATUS[flags & 0x03]
|
||||||
if charge_sts is None:
|
if charge_sts is None:
|
||||||
charge_sts = ERROR.unknown
|
charge_sts = ERROR.unknown
|
||||||
elif charge_sts == CHARGE_STATUS.full:
|
elif charge_sts == CHARGE_STATUS.full:
|
||||||
charge_lvl = CHARGE_LEVEL.full
|
charge_lvl = CHARGE_LEVEL.full
|
||||||
status = _BATTERY_STATUS.full
|
status = Battery.STATUS.full
|
||||||
if flags & (1 << 3):
|
if flags & (1 << 3):
|
||||||
charge_type = CHARGE_TYPE.fast
|
charge_type = CHARGE_TYPE.fast
|
||||||
elif flags & (1 << 4):
|
elif flags & (1 << 4):
|
||||||
charge_type = CHARGE_TYPE.slow
|
charge_type = CHARGE_TYPE.slow
|
||||||
status = _BATTERY_STATUS.slow_recharge
|
status = Battery.STATUS.slow_recharge
|
||||||
elif flags & (1 << 5):
|
elif flags & (1 << 5):
|
||||||
charge_lvl = CHARGE_LEVEL.critical
|
charge_lvl = CHARGE_LEVEL.critical
|
||||||
for level in battery_voltage_remaining:
|
for level in battery_voltage_remaining:
|
||||||
|
@ -1771,26 +1770,26 @@ def decipher_battery_voltage(report):
|
||||||
charge_lvl,
|
charge_lvl,
|
||||||
charge_type,
|
charge_type,
|
||||||
)
|
)
|
||||||
return FEATURE.BATTERY_VOLTAGE, charge_lvl, None, status, voltage
|
return FEATURE.BATTERY_VOLTAGE, Battery(charge_lvl, None, status, voltage)
|
||||||
|
|
||||||
|
|
||||||
def decipher_battery_unified(report):
|
def decipher_battery_unified(report):
|
||||||
discharge, level, status, _ignore = _unpack("!BBBB", report[:4])
|
discharge, level, status, _ignore = _unpack("!BBBB", report[:4])
|
||||||
status = _BATTERY_STATUS[status]
|
status = Battery.STATUS[status]
|
||||||
if logger.isEnabledFor(logging.DEBUG):
|
if logger.isEnabledFor(logging.DEBUG):
|
||||||
logger.debug("battery unified %s%% charged, level %s, charging %s", discharge, level, status)
|
logger.debug("battery unified %s%% charged, level %s, charging %s", discharge, level, status)
|
||||||
level = (
|
level = (
|
||||||
_BATTERY_APPROX.full
|
Battery.APPROX.full
|
||||||
if level == 8 # full
|
if level == 8 # full
|
||||||
else _BATTERY_APPROX.good
|
else Battery.APPROX.good
|
||||||
if level == 4 # good
|
if level == 4 # good
|
||||||
else _BATTERY_APPROX.low
|
else Battery.APPROX.low
|
||||||
if level == 2 # low
|
if level == 2 # low
|
||||||
else _BATTERY_APPROX.critical
|
else Battery.APPROX.critical
|
||||||
if level == 1 # critical
|
if level == 1 # critical
|
||||||
else _BATTERY_APPROX.empty
|
else Battery.APPROX.empty
|
||||||
)
|
)
|
||||||
return FEATURE.UNIFIED_BATTERY, discharge if discharge else level, None, status, None
|
return FEATURE.UNIFIED_BATTERY, Battery(discharge if discharge else level, None, status, None)
|
||||||
|
|
||||||
|
|
||||||
def decipher_adc_measurement(report):
|
def decipher_adc_measurement(report):
|
||||||
|
@ -1801,5 +1800,5 @@ def decipher_adc_measurement(report):
|
||||||
charge_level = level[1]
|
charge_level = level[1]
|
||||||
break
|
break
|
||||||
if flags & 0x01:
|
if flags & 0x01:
|
||||||
status = _BATTERY_STATUS.recharging if flags & 0x02 else _BATTERY_STATUS.discharging
|
status = Battery.STATUS.recharging if flags & 0x02 else Battery.STATUS.discharging
|
||||||
return FEATURE.ADC_MEASUREMENT, charge_level, None, status, adc
|
return FEATURE.ADC_MEASUREMENT, Battery(charge_level, None, status, adc)
|
||||||
|
|
|
@ -29,7 +29,7 @@ from . import hidpp10_constants as _hidpp10_constants
|
||||||
from . import hidpp20_constants as _hidpp20_constants
|
from . import hidpp20_constants as _hidpp20_constants
|
||||||
from . import settings_templates as _st
|
from . import settings_templates as _st
|
||||||
from .base import DJ_MESSAGE_ID as _DJ_MESSAGE_ID
|
from .base import DJ_MESSAGE_ID as _DJ_MESSAGE_ID
|
||||||
from .common import BATTERY_STATUS as _BATTERY_STATUS
|
from .common import Battery as _Battery
|
||||||
from .common import strhex as _strhex
|
from .common import strhex as _strhex
|
||||||
from .i18n import _
|
from .i18n import _
|
||||||
from .status import ALERT as _ALERT
|
from .status import ALERT as _ALERT
|
||||||
|
@ -218,11 +218,9 @@ def _process_hidpp10_custom_notification(device, status, n):
|
||||||
logger.debug("%s (%s) custom notification %s", device, device.protocol, n)
|
logger.debug("%s (%s) custom notification %s", device, device.protocol, n)
|
||||||
|
|
||||||
if n.sub_id in (_R.battery_status, _R.battery_charge):
|
if n.sub_id in (_R.battery_status, _R.battery_charge):
|
||||||
# message layout: 10 ix <register> <xx> <yy> <zz> <00>
|
|
||||||
assert n.data[-1:] == b"\x00"
|
assert n.data[-1:] == b"\x00"
|
||||||
data = chr(n.address).encode() + n.data
|
data = chr(n.address).encode() + n.data
|
||||||
charge, next_charge, status_text, voltage = hidpp10.parse_battery_status(n.sub_id, data)
|
status.set_battery_info(hidpp10.parse_battery_status(n.sub_id, data))
|
||||||
status.set_battery_info(charge, next_charge, status_text, voltage)
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
logger.warning("%s: unrecognized %s", device, n)
|
logger.warning("%s: unrecognized %s", device, n)
|
||||||
|
@ -296,8 +294,7 @@ def _process_feature_notification(device, status, n, feature):
|
||||||
|
|
||||||
if feature == _F.BATTERY_STATUS:
|
if feature == _F.BATTERY_STATUS:
|
||||||
if n.address == 0x00:
|
if n.address == 0x00:
|
||||||
_ignore, discharge_level, discharge_next_level, battery_status, voltage = hidpp20.decipher_battery_status(n.data)
|
status.set_battery_info(hidpp20.decipher_battery_status(n.data)[1])
|
||||||
status.set_battery_info(discharge_level, discharge_next_level, battery_status, voltage)
|
|
||||||
elif n.address == 0x10:
|
elif n.address == 0x10:
|
||||||
if logger.isEnabledFor(logging.INFO):
|
if logger.isEnabledFor(logging.INFO):
|
||||||
logger.info("%s: spurious BATTERY status %s", device, n)
|
logger.info("%s: spurious BATTERY status %s", device, n)
|
||||||
|
@ -306,15 +303,13 @@ def _process_feature_notification(device, status, n, feature):
|
||||||
|
|
||||||
elif feature == _F.BATTERY_VOLTAGE:
|
elif feature == _F.BATTERY_VOLTAGE:
|
||||||
if n.address == 0x00:
|
if n.address == 0x00:
|
||||||
_ignore, level, nextl, battery_status, voltage = hidpp20.decipher_battery_voltage(n.data)
|
status.set_battery_info(hidpp20.decipher_battery_voltage(n.data)[1])
|
||||||
status.set_battery_info(level, nextl, battery_status, voltage)
|
|
||||||
else:
|
else:
|
||||||
logger.warning("%s: unknown VOLTAGE %s", device, n)
|
logger.warning("%s: unknown VOLTAGE %s", device, n)
|
||||||
|
|
||||||
elif feature == _F.UNIFIED_BATTERY:
|
elif feature == _F.UNIFIED_BATTERY:
|
||||||
if n.address == 0x00:
|
if n.address == 0x00:
|
||||||
_ignore, level, nextl, battery_status, voltage = hidpp20.decipher_battery_unified(n.data)
|
status.set_battery_info(hidpp20.decipher_battery_unified(n.data)[1])
|
||||||
status.set_battery_info(level, nextl, battery_status, voltage)
|
|
||||||
else:
|
else:
|
||||||
logger.warning("%s: unknown UNIFIED BATTERY %s", device, n)
|
logger.warning("%s: unknown UNIFIED BATTERY %s", device, n)
|
||||||
|
|
||||||
|
@ -322,8 +317,7 @@ def _process_feature_notification(device, status, n, feature):
|
||||||
if n.address == 0x00:
|
if n.address == 0x00:
|
||||||
result = hidpp20.decipher_adc_measurement(n.data)
|
result = hidpp20.decipher_adc_measurement(n.data)
|
||||||
if result:
|
if result:
|
||||||
_ignore, level, nextl, battery_status, voltage = result
|
status.set_battery_info(result[1])
|
||||||
status.set_battery_info(level, nextl, battery_status, voltage)
|
|
||||||
else: # this feature is used to signal device becoming inactive
|
else: # this feature is used to signal device becoming inactive
|
||||||
status.changed(active=False)
|
status.changed(active=False)
|
||||||
else:
|
else:
|
||||||
|
@ -334,15 +328,13 @@ def _process_feature_notification(device, status, n, feature):
|
||||||
charge, lux, adc = _unpack("!BHH", n.data[:5])
|
charge, lux, adc = _unpack("!BHH", n.data[:5])
|
||||||
# guesstimate the battery voltage, emphasis on 'guess'
|
# guesstimate the battery voltage, emphasis on 'guess'
|
||||||
# status_text = '%1.2fV' % (adc * 2.67793237653 / 0x0672)
|
# status_text = '%1.2fV' % (adc * 2.67793237653 / 0x0672)
|
||||||
status_text = _BATTERY_STATUS.discharging
|
status_text = _Battery.STATUS.discharging
|
||||||
if n.address == 0x00:
|
if n.address == 0x00:
|
||||||
status[_K.LIGHT_LEVEL] = None
|
status.set_battery_info(_Battery(charge, None, status_text, None))
|
||||||
status.set_battery_info(charge, None, status_text, None)
|
|
||||||
elif n.address == 0x10:
|
elif n.address == 0x10:
|
||||||
status[_K.LIGHT_LEVEL] = lux
|
|
||||||
if lux > 200:
|
if lux > 200:
|
||||||
status_text = _BATTERY_STATUS.recharging
|
status_text = _Battery.STATUS.recharging
|
||||||
status.set_battery_info(charge, None, status_text, None)
|
status.set_battery_info(_Battery(charge, None, status_text, None, lux))
|
||||||
elif n.address == 0x20:
|
elif n.address == 0x20:
|
||||||
if logger.isEnabledFor(logging.DEBUG):
|
if logger.isEnabledFor(logging.DEBUG):
|
||||||
logger.debug("%s: Light Check button pressed", device)
|
logger.debug("%s: Light Check button pressed", device)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
## Copyright (C) 2012-2013 Daniel Pavel
|
## Copyright (C) 2012-2013 Daniel Pavel
|
||||||
|
## Copyright (C) 2014-2024 Solaar Contributors https://pwr-solaar.github.io/Solaar/
|
||||||
##
|
##
|
||||||
## This program is free software; you can redistribute it and/or modify
|
## This program is free software; you can redistribute it and/or modify
|
||||||
## it under the terms of the GNU General Public License as published by
|
## it under the terms of the GNU General Public License as published by
|
||||||
|
@ -20,11 +21,7 @@ from . import hidpp10
|
||||||
from . import hidpp10_constants as _hidpp10_constants
|
from . import hidpp10_constants as _hidpp10_constants
|
||||||
from . import hidpp20_constants as _hidpp20_constants
|
from . import hidpp20_constants as _hidpp20_constants
|
||||||
from . import settings as _settings
|
from . import settings as _settings
|
||||||
from .common import BATTERY_APPROX as _BATTERY_APPROX
|
from .common import Battery, NamedInts
|
||||||
from .common import BATTERY_OK as _BATTERY_OK
|
|
||||||
from .common import BATTERY_STATUS as _BATTERY_STATUS
|
|
||||||
from .common import NamedInt as _NamedInt
|
|
||||||
from .common import NamedInts as _NamedInts
|
|
||||||
from .i18n import _, ngettext
|
from .i18n import _, ngettext
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -33,27 +30,9 @@ _R = _hidpp10_constants.REGISTERS
|
||||||
|
|
||||||
_hidpp10 = hidpp10.Hidpp10()
|
_hidpp10 = hidpp10.Hidpp10()
|
||||||
|
|
||||||
#
|
ALERT = NamedInts(NONE=0x00, NOTIFICATION=0x01, SHOW_WINDOW=0x02, ATTENTION=0x04, ALL=0xFF)
|
||||||
#
|
|
||||||
#
|
|
||||||
|
|
||||||
ALERT = _NamedInts(NONE=0x00, NOTIFICATION=0x01, SHOW_WINDOW=0x02, ATTENTION=0x04, ALL=0xFF)
|
KEYS = NamedInts(LINK_ENCRYPTED=5, NOTIFICATION_FLAGS=6, ERROR=7)
|
||||||
|
|
||||||
KEYS = _NamedInts(
|
|
||||||
BATTERY_LEVEL=1,
|
|
||||||
BATTERY_CHARGING=2,
|
|
||||||
BATTERY_STATUS=3,
|
|
||||||
LIGHT_LEVEL=4,
|
|
||||||
LINK_ENCRYPTED=5,
|
|
||||||
NOTIFICATION_FLAGS=6,
|
|
||||||
ERROR=7,
|
|
||||||
BATTERY_NEXT_LEVEL=8,
|
|
||||||
BATTERY_VOLTAGE=9,
|
|
||||||
)
|
|
||||||
|
|
||||||
# If the battery charge is under this percentage, trigger an attention event
|
|
||||||
# (blink systray icon/notification/whatever).
|
|
||||||
_BATTERY_ATTENTION_LEVEL = 5
|
|
||||||
|
|
||||||
|
|
||||||
def attach_to(device, changed_callback):
|
def attach_to(device, changed_callback):
|
||||||
|
@ -67,11 +46,6 @@ def attach_to(device, changed_callback):
|
||||||
device.status = DeviceStatus(device, changed_callback)
|
device.status = DeviceStatus(device, changed_callback)
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
#
|
|
||||||
#
|
|
||||||
|
|
||||||
|
|
||||||
class ReceiverStatus(dict):
|
class ReceiverStatus(dict):
|
||||||
"""The 'runtime' status of a receiver, mostly about the pairing process --
|
"""The 'runtime' status of a receiver, mostly about the pairing process --
|
||||||
is the pairing lock open or closed, any pairing errors, etc.
|
is the pairing lock open or closed, any pairing errors, etc.
|
||||||
|
@ -111,15 +85,10 @@ class ReceiverStatus(dict):
|
||||||
self._changed_callback(self._receiver, alert=alert, reason=reason)
|
self._changed_callback(self._receiver, alert=alert, reason=reason)
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
#
|
|
||||||
#
|
|
||||||
|
|
||||||
|
|
||||||
class DeviceStatus(dict):
|
class DeviceStatus(dict):
|
||||||
"""Holds the 'runtime' status of a peripheral -- things like
|
"""Holds the 'runtime' status of a peripheral
|
||||||
active/inactive, battery charge, lux, etc. It updates them mostly by
|
Currently _active, battery -- dict entries are being moved to attributs
|
||||||
processing incoming notification events from the device itself.
|
Updates mostly come from incoming notification events from the device itself.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, device, changed_callback):
|
def __init__(self, device, changed_callback):
|
||||||
|
@ -128,19 +97,10 @@ class DeviceStatus(dict):
|
||||||
assert changed_callback
|
assert changed_callback
|
||||||
self._changed_callback = changed_callback
|
self._changed_callback = changed_callback
|
||||||
self._active = None # is the device active?
|
self._active = None # is the device active?
|
||||||
|
self.battery = None
|
||||||
|
|
||||||
def to_string(self):
|
def to_string(self):
|
||||||
status = ""
|
return self.battery.to_str() if self.battery is not None else ""
|
||||||
battery_level = self.get(KEYS.BATTERY_LEVEL)
|
|
||||||
if battery_level is not None:
|
|
||||||
if isinstance(battery_level, _NamedInt):
|
|
||||||
status = _("Battery: %(level)s") % {"level": _(str(battery_level))}
|
|
||||||
else:
|
|
||||||
status = _("Battery: %(percent)d%%") % {"percent": battery_level}
|
|
||||||
battery_status = self.get(KEYS.BATTERY_STATUS)
|
|
||||||
if battery_status is not None:
|
|
||||||
status += " (%s)" % _(str(battery_status))
|
|
||||||
return status
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "{" + ", ".join("'%s': %r" % (k, v) for k, v in self.items()) + "}"
|
return "{" + ", ".join("'%s': %r" % (k, v) for k, v in self.items()) + "}"
|
||||||
|
@ -150,81 +110,38 @@ class DeviceStatus(dict):
|
||||||
|
|
||||||
__nonzero__ = __bool__
|
__nonzero__ = __bool__
|
||||||
|
|
||||||
def set_battery_info(self, level, nextLevel, status, voltage):
|
def set_battery_info(self, info):
|
||||||
if logger.isEnabledFor(logging.DEBUG):
|
if logger.isEnabledFor(logging.DEBUG):
|
||||||
logger.debug("%s: battery %s, %s", self._device, level, status)
|
logger.debug("%s: battery %s, %s", self._device, info.level, info.status)
|
||||||
|
if info.level is None and self.battery: # use previous level if missing from new information
|
||||||
|
info.level = self.battery.level
|
||||||
|
|
||||||
if level is None:
|
changed = self.battery != info
|
||||||
# Some notifications may come with no battery level info, just
|
self.battery = info
|
||||||
# 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
|
|
||||||
if status == _BATTERY_STATUS.full:
|
|
||||||
level = _BATTERY_APPROX.full
|
|
||||||
elif status in (_BATTERY_STATUS.almost_full, _BATTERY_STATUS.recharging):
|
|
||||||
level = _BATTERY_APPROX.good
|
|
||||||
elif status == _BATTERY_STATUS.slow_recharge:
|
|
||||||
level = _BATTERY_APPROX.low
|
|
||||||
else:
|
|
||||||
level = self.get(KEYS.BATTERY_LEVEL)
|
|
||||||
else:
|
|
||||||
assert isinstance(level, int)
|
|
||||||
|
|
||||||
# TODO: this is also executed when pressing Fn+F7 on K800.
|
|
||||||
old_level, self[KEYS.BATTERY_LEVEL] = self.get(KEYS.BATTERY_LEVEL), level
|
|
||||||
old_status, self[KEYS.BATTERY_STATUS] = self.get(KEYS.BATTERY_STATUS), status
|
|
||||||
self[KEYS.BATTERY_NEXT_LEVEL] = nextLevel
|
|
||||||
old_voltage, self[KEYS.BATTERY_VOLTAGE] = self.get(KEYS.BATTERY_VOLTAGE), voltage
|
|
||||||
|
|
||||||
charging = status in (
|
|
||||||
_BATTERY_STATUS.recharging,
|
|
||||||
_BATTERY_STATUS.almost_full,
|
|
||||||
_BATTERY_STATUS.full,
|
|
||||||
_BATTERY_STATUS.slow_recharge,
|
|
||||||
)
|
|
||||||
old_charging, self[KEYS.BATTERY_CHARGING] = self.get(KEYS.BATTERY_CHARGING), charging
|
|
||||||
|
|
||||||
changed = old_level != level or old_status != status or old_charging != charging or old_voltage != voltage
|
|
||||||
alert, reason = ALERT.NONE, None
|
alert, reason = ALERT.NONE, None
|
||||||
|
if info.ok():
|
||||||
if _BATTERY_OK(status) and (level is None or level > _BATTERY_ATTENTION_LEVEL):
|
|
||||||
self[KEYS.ERROR] = None
|
self[KEYS.ERROR] = None
|
||||||
else:
|
else:
|
||||||
logger.warning("%s: battery %d%%, ALERT %s", self._device, level, status)
|
logger.warning("%s: battery %d%%, ALERT %s", self._device, info.level, info.status)
|
||||||
if self.get(KEYS.ERROR) != status:
|
if self.get(KEYS.ERROR) != info.status:
|
||||||
self[KEYS.ERROR] = status
|
self[KEYS.ERROR] = info.status
|
||||||
# only show the notification once
|
|
||||||
alert = ALERT.NOTIFICATION | ALERT.ATTENTION
|
alert = ALERT.NOTIFICATION | ALERT.ATTENTION
|
||||||
if isinstance(level, _NamedInt):
|
reason = info.to_str()
|
||||||
reason = _("Battery: %(level)s (%(status)s)") % {"level": _(level), "status": _(status)}
|
|
||||||
else:
|
|
||||||
reason = _("Battery: %(percent)d%% (%(status)s)") % {"percent": level, "status": status.name}
|
|
||||||
|
|
||||||
if changed or reason or not self._active: # a battery response means device is active
|
if changed or reason or not self._active: # a battery response means device is active
|
||||||
# update the leds on the device, if any
|
# update the leds on the device, if any
|
||||||
_hidpp10.set_3leds(self._device, level, charging=charging, warning=bool(alert))
|
_hidpp10.set_3leds(self._device, info.level, charging=info.charging(), warning=bool(alert))
|
||||||
self.changed(active=True, alert=alert, reason=reason)
|
self.changed(active=True, alert=alert, reason=reason)
|
||||||
|
|
||||||
# Retrieve and regularize battery status
|
# Retrieve and regularize battery status
|
||||||
def read_battery(self):
|
def read_battery(self):
|
||||||
if self._active:
|
if self._active:
|
||||||
assert self._device
|
|
||||||
battery = self._device.battery()
|
battery = self._device.battery()
|
||||||
self.set_battery_keys(battery)
|
self.set_battery_info(battery if battery is not None else Battery(None, None, None, None))
|
||||||
|
|
||||||
def set_battery_keys(self, battery):
|
|
||||||
if battery is not None:
|
|
||||||
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
|
|
||||||
self[KEYS.BATTERY_VOLTAGE] = None
|
|
||||||
self.changed()
|
|
||||||
|
|
||||||
def changed(self, active=None, alert=ALERT.NONE, reason=None, push=False):
|
def changed(self, active=None, alert=ALERT.NONE, reason=None, push=False):
|
||||||
assert self._changed_callback
|
|
||||||
d = self._device
|
d = self._device
|
||||||
# assert d # may be invalid when processing the 'unpaired' notification
|
|
||||||
|
|
||||||
if active is not None:
|
if active is not None:
|
||||||
d.online = active
|
d.online = active
|
||||||
|
@ -236,12 +153,10 @@ class DeviceStatus(dict):
|
||||||
# goes idle, and we can't tell the difference right now).
|
# goes idle, and we can't tell the difference right now).
|
||||||
if d.protocol < 2.0:
|
if d.protocol < 2.0:
|
||||||
self[KEYS.NOTIFICATION_FLAGS] = d.enable_connection_notifications()
|
self[KEYS.NOTIFICATION_FLAGS] = d.enable_connection_notifications()
|
||||||
|
|
||||||
# battery information may have changed so try to read it now
|
# battery information may have changed so try to read it now
|
||||||
self.read_battery()
|
self.read_battery()
|
||||||
|
|
||||||
# Push settings for new devices (was_active is None),
|
# Push settings for new devices when devices request software reconfiguration
|
||||||
# when devices request software reconfiguration
|
|
||||||
# and when devices become active if they don't have wireless device status feature,
|
# and when devices become active if they don't have wireless device status feature,
|
||||||
if (
|
if (
|
||||||
was_active is None
|
was_active is None
|
||||||
|
@ -253,10 +168,6 @@ class DeviceStatus(dict):
|
||||||
logger.info("%s pushing device settings %s", d, d.settings)
|
logger.info("%s pushing device settings %s", d, d.settings)
|
||||||
_settings.apply_all_settings(d)
|
_settings.apply_all_settings(d)
|
||||||
|
|
||||||
else:
|
if logger.isEnabledFor(logging.DEBUG):
|
||||||
if was_active: # don't clear status when devices go inactive
|
logger.debug("device %d changed: active=%s %s", d.number, self._active, self.battery)
|
||||||
pass
|
|
||||||
|
|
||||||
# if logger.isEnabledFor(logging.DEBUG):
|
|
||||||
# logger.debug("device %d changed: active=%s %s", d.number, self._active, dict(self))
|
|
||||||
self._changed_callback(d, alert, reason)
|
self._changed_callback(d, alert, reason)
|
||||||
|
|
Loading…
Reference in New Issue