device: move battery information from status to Device

This commit is contained in:
Peter F. Patel-Schneider 2024-03-09 16:43:37 -05:00
parent 0805ecb511
commit 15d425c365
6 changed files with 63 additions and 75 deletions

View File

@ -605,3 +605,6 @@ class Battery:
return _("Battery: %(percent)d%% (%(status)s)") % {"percent": self.level, "status": _(self.status)} return _("Battery: %(percent)d%% (%(status)s)") % {"percent": self.level, "status": _(self.status)}
else: else:
return "" return ""
ALERT = NamedInts(NONE=0x00, NOTIFICATION=0x01, SHOW_WINDOW=0x02, ATTENTION=0x04, ALL=0xFF)

View File

@ -26,6 +26,7 @@ import hidapi as _hid
import solaar.configuration as _configuration import solaar.configuration as _configuration
from . import base, descriptors, exceptions, hidpp10, hidpp10_constants, hidpp20 from . import base, descriptors, exceptions, hidpp10, hidpp10_constants, hidpp20
from .common import ALERT, Battery
from .settings_templates import check_feature_settings as _check_feature_settings from .settings_templates import check_feature_settings as _check_feature_settings
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -92,6 +93,7 @@ class Device:
self._led_effects = self._firmware = self._keys = self._remap_keys = self._gestures = None self._led_effects = self._firmware = self._keys = self._remap_keys = self._gestures = None
self._profiles = self._backlight = self._registers = self._settings = None self._profiles = self._backlight = self._registers = self._settings = None
self.notification_flags = None self.notification_flags = None
self.battery_info = None
self._feature_settings_checked = False self._feature_settings_checked = False
self._gestures_lock = _threading.Lock() self._gestures_lock = _threading.Lock()
@ -351,6 +353,36 @@ class Device:
if self.persister and battery_feature is None: if self.persister and battery_feature is None:
self.persister["_battery"] = result self.persister["_battery"] = result
def set_battery_info(self, info):
"""Update battery information for device, calling changed callback if necessary"""
if logger.isEnabledFor(logging.DEBUG):
logger.debug("%s: battery %s, %s", self, info.level, info.status)
if info.level is None and self.battery_info: # use previous level if missing from new information
info.level = self.battery_info.level
changed = self.battery_info != info
print("SBI", changed, info, self.battery_info)
self.battery_info, old_info = info, self.battery_info
alert, reason = ALERT.NONE, None
if not info.ok():
logger.warning("%s: battery %d%%, ALERT %s", self, info.level, info.status)
if old_info.status != info.status:
alert = ALERT.NOTIFICATION | ALERT.ATTENTION
reason = info.to_str()
if changed or reason:
# update the leds on the device, if any
_hidpp10.set_3leds(self, info.level, charging=info.charging(), warning=bool(alert))
if hasattr(self, "status"):
self.status.changed(active=True, alert=alert, reason=reason)
# Retrieve and regularize battery status
def read_battery(self):
if self.online:
battery = self.battery()
self.set_battery_info(battery if battery is not None else Battery(None, None, None, None))
def enable_connection_notifications(self, enable=True): def enable_connection_notifications(self, enable=True):
"""Enable or disable device (dis)connection notifications on this """Enable or disable device (dis)connection notifications on this
receiver.""" receiver."""
@ -464,7 +496,7 @@ class Device:
__nonzero__ = __bool__ __nonzero__ = __bool__
def status_string(self): def status_string(self):
return self.status.battery.to_str() if hasattr(self, "status") and self.status.battery is not None else "" return self.battery_info.to_str() if hasattr(self, "status") and self.battery_info is not None else ""
def __str__(self): def __str__(self):
try: try:

View File

@ -222,7 +222,7 @@ def _process_hidpp10_custom_notification(device, status, n):
if n.sub_id in (_R.battery_status, _R.battery_charge): if n.sub_id in (_R.battery_status, _R.battery_charge):
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
status.set_battery_info(hidpp10.parse_battery_status(n.sub_id, data)) device.set_battery_info(hidpp10.parse_battery_status(n.sub_id, data))
return True return True
logger.warning("%s: unrecognized %s", device, n) logger.warning("%s: unrecognized %s", device, n)
@ -295,7 +295,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:
status.set_battery_info(hidpp20.decipher_battery_status(n.data)[1]) device.set_battery_info(hidpp20.decipher_battery_status(n.data)[1])
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)
@ -304,13 +304,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:
status.set_battery_info(hidpp20.decipher_battery_voltage(n.data)[1]) device.set_battery_info(hidpp20.decipher_battery_voltage(n.data)[1])
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:
status.set_battery_info(hidpp20.decipher_battery_unified(n.data)[1]) device.set_battery_info(hidpp20.decipher_battery_unified(n.data)[1])
else: else:
logger.warning("%s: unknown UNIFIED BATTERY %s", device, n) logger.warning("%s: unknown UNIFIED BATTERY %s", device, n)
@ -318,7 +318,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:
status.set_battery_info(result[1]) device.set_battery_info(result[1])
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:
@ -331,11 +331,11 @@ def _process_feature_notification(device, status, n, feature):
# 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.set_battery_info(_Battery(charge, None, status_text, None)) device.set_battery_info(_Battery(charge, None, status_text, None))
elif n.address == 0x10: elif n.address == 0x10:
if lux > 200: if lux > 200:
status_text = _Battery.STATUS.recharging status_text = _Battery.STATUS.recharging
status.set_battery_info(_Battery(charge, None, status_text, None, lux)) device.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)

View File

@ -21,7 +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, NamedInts from .common import NamedInts
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -52,19 +52,6 @@ class ReceiverStatus:
assert changed_callback assert changed_callback
self._changed_callback = changed_callback self._changed_callback = changed_callback
# self.notification_flags = None
# self.error = None
# self.lock_open = False
# self.discovering = False
# self.counter = None
# self.device_address = None
# self.device_authentication = None
# self.device_kind = None
# self.device_name = None
# self.device_passkey = None
# self.new_device = None
def changed(self, alert=ALERT.NOTIFICATION, reason=None): def changed(self, alert=ALERT.NOTIFICATION, reason=None):
self._changed_callback(self._receiver, alert=alert, reason=reason) self._changed_callback(self._receiver, alert=alert, reason=reason)
@ -81,46 +68,13 @@ class DeviceStatus:
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
self.link_encrypted = None self.link_encrypted = None
# self.notification_flags = None
self.battery_error = None
def __bool__(self): def __bool__(self):
return bool(self._active) return bool(self._active)
__nonzero__ = __bool__ __nonzero__ = __bool__
def set_battery_info(self, info):
if logger.isEnabledFor(logging.DEBUG):
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
changed = self.battery != info
self.battery = info
alert, reason = ALERT.NONE, None
if info.ok():
self.battery_error = None
else:
logger.warning("%s: battery %d%%, ALERT %s", self._device, info.level, info.status)
if self.battery_error != info.status:
self.battery_error = info.status
alert = ALERT.NOTIFICATION | ALERT.ATTENTION
reason = info.to_str()
if changed or reason or not self._active: # a battery response means device is active
# update the leds on the device, if any
_hidpp10.set_3leds(self._device, info.level, charging=info.charging(), warning=bool(alert))
self.changed(active=True, alert=alert, reason=reason)
# Retrieve and regularize battery status
def read_battery(self):
if self._active:
battery = self._device.battery()
self.set_battery_info(battery if battery is not None else Battery(None, None, None, None))
def changed(self, active=None, alert=ALERT.NONE, reason=None, push=False): def changed(self, active=None, alert=ALERT.NONE, reason=None, push=False):
d = self._device d = self._device
@ -135,7 +89,7 @@ class DeviceStatus:
if d.protocol < 2.0: if d.protocol < 2.0:
self._device.notification_flags = d.enable_connection_notifications() self._device.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._device.read_battery()
# Push settings for new devices when devices request software reconfiguration # Push settings for new devices 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,

View File

@ -197,9 +197,8 @@ try:
def _update_tray_icon(): def _update_tray_icon():
if _picked_device and gtk.battery_icons_style != "solaar": if _picked_device and gtk.battery_icons_style != "solaar":
_ignore, _ignore, name, device = _picked_device _ignore, _ignore, name, device = _picked_device
device_status = device.status battery_level = device.battery_info.level if device.battery_info is not None else None
battery_level = device_status.battery.level if device_status.battery is not None else None battery_charging = device.battery_info.charging() if device.battery_info is not None else None
battery_charging = device_status.battery.charging() if device_status.battery is not None else None
tray_icon_name = _icons.battery(battery_level, battery_charging) tray_icon_name = _icons.battery(battery_level, battery_charging)
description = "%s: %s" % (name, device.status_string()) description = "%s: %s" % (name, device.status_string())
@ -252,9 +251,9 @@ except ImportError:
_icon.set_tooltip_markup(tooltip) _icon.set_tooltip_markup(tooltip)
if _picked_device and gtk.battery_icons_style != "solaar": if _picked_device and gtk.battery_icons_style != "solaar":
_ignore, _ignore, name, device_status = _picked_device _ignore, _ignore, name, device = _picked_device
battery_level = device_status.battery.level if device_status.battery is not None else None battery_level = device.battery_info.level if device.battery_info is not None else None
battery_charging = device_status.battery.charging() if device_status.battery is not None else None battery_charging = device.battery_info.charging() if device.battery_info is not None else None
tray_icon_name = _icons.battery(battery_level, battery_charging) tray_icon_name = _icons.battery(battery_level, battery_charging)
else: else:
# there may be a receiver, but no peripherals # there may be a receiver, but no peripherals
@ -331,7 +330,7 @@ def _pick_device_with_lowest_battery():
for info in _devices_info: for info in _devices_info:
if info[1] is None: # is receiver if info[1] is None: # is receiver
continue continue
level = info[-1].status.battery.level if hasattr(info[-1], "status") and info[-1].status.battery is not None else None level = info[-1].battery_info.level if hasattr(info[-1], "status") and info[-1].battery_info is not None else None
# print ("checking %s -> %s", info, level) # print ("checking %s -> %s", info, level)
if level is not None and picked_level > level: if level is not None and picked_level > level:
picked = info picked = info
@ -428,8 +427,8 @@ def _update_menu_item(index, device):
menu_items = _menu.get_children() menu_items = _menu.get_children()
menu_item = menu_items[index] menu_item = menu_items[index]
level = device.status.battery.level if device.status.battery is not None else None level = device.battery_info.level if device.battery_info is not None else None
charging = device.status.battery.charging() if device.status.battery is not None else None charging = device.battery_info.charging() if device.battery_info is not None else None
icon_name = _icons.battery(level, charging) icon_name = _icons.battery(level, charging)
menu_item.set_label((" " if 0 < device.number <= 6 else "") + device.name + ": " + device.status_string()) menu_item.set_label((" " if 0 < device.number <= 6 else "") + device.name + ": " + device.status_string())

View File

@ -682,17 +682,17 @@ def _update_device_panel(device, panel, buttons, full=False):
is_online = bool(device.online) is_online = bool(device.online)
panel.set_sensitive(is_online) panel.set_sensitive(is_online)
if device.status.battery is None or device.status.battery.level is None: if device.battery_info is None or device.battery_info.level is None:
device.status.read_battery() device.read_battery()
battery_level = device.status.battery.level if device.status.battery is not None else None battery_level = device.battery_info.level if device.battery_info is not None else None
battery_voltage = device.status.battery.voltage if device.status.battery is not None else None battery_voltage = device.battery_info.voltage if device.battery_info is not None else None
if battery_level is None and battery_voltage is None: if battery_level is None and battery_voltage is None:
panel._battery.set_visible(False) panel._battery.set_visible(False)
else: else:
panel._battery.set_visible(True) panel._battery.set_visible(True)
battery_next_level = device.status.battery.next_level battery_next_level = device.battery_info.next_level
charging = device.status.battery.charging() if device.status.battery is not None else None charging = device.battery_info.charging() if device.battery_info is not None else None
icon_name = _icons.battery(battery_level, charging) icon_name = _icons.battery(battery_level, charging)
panel._battery._icon.set_from_icon_name(icon_name, _INFO_ICON_SIZE) panel._battery._icon.set_from_icon_name(icon_name, _INFO_ICON_SIZE)
panel._battery._icon.set_sensitive(True) panel._battery._icon.set_sensitive(True)
@ -750,7 +750,7 @@ def _update_device_panel(device, panel, buttons, full=False):
panel._secure.set_tooltip_text("") panel._secure.set_tooltip_text("")
if is_online: if is_online:
light_level = device.status.battery.light_level if device.status.battery is not None else None light_level = device.battery_info.light_level if device.battery_info is not None else None
if light_level is None: if light_level is None:
panel._lux.set_visible(False) panel._lux.set_visible(False)
else: else:
@ -906,8 +906,8 @@ def update_device(device, item, selected_device_id, need_popup, full=False):
is_online = bool(device.online) is_online = bool(device.online)
_model.set_value(item, _COLUMN.ACTIVE, is_online) _model.set_value(item, _COLUMN.ACTIVE, is_online)
battery_level = device.status.battery.level if device.status.battery is not None else None battery_level = device.battery_info.level if device.battery_info is not None else None
battery_voltage = device.status.battery.voltage if device.status.battery is not None else None battery_voltage = device.battery_info.voltage if device.battery_info is not None else None
if battery_level is None: if battery_level is None:
_model.set_value(item, _COLUMN.STATUS_TEXT, _CAN_SET_ROW_NONE) _model.set_value(item, _COLUMN.STATUS_TEXT, _CAN_SET_ROW_NONE)
_model.set_value(item, _COLUMN.STATUS_ICON, _CAN_SET_ROW_NONE) _model.set_value(item, _COLUMN.STATUS_ICON, _CAN_SET_ROW_NONE)
@ -920,7 +920,7 @@ def update_device(device, item, selected_device_id, need_popup, full=False):
status_text = "%(battery_percent)d%%" % {"battery_percent": battery_level} status_text = "%(battery_percent)d%%" % {"battery_percent": battery_level}
_model.set_value(item, _COLUMN.STATUS_TEXT, status_text) _model.set_value(item, _COLUMN.STATUS_TEXT, status_text)
charging = device.status.battery.charging() if device.status.battery is not None else None charging = device.battery_info.charging() if device.battery_info is not None else None
icon_name = _icons.battery(battery_level, charging) icon_name = _icons.battery(battery_level, charging)
_model.set_value(item, _COLUMN.STATUS_ICON, icon_name) _model.set_value(item, _COLUMN.STATUS_ICON, icon_name)