diff --git a/lib/logitech_receiver/common.py b/lib/logitech_receiver/common.py index 5ed88b1c..0c3d0ccb 100644 --- a/lib/logitech_receiver/common.py +++ b/lib/logitech_receiver/common.py @@ -605,3 +605,6 @@ class Battery: return _("Battery: %(percent)d%% (%(status)s)") % {"percent": self.level, "status": _(self.status)} else: return "" + + +ALERT = NamedInts(NONE=0x00, NOTIFICATION=0x01, SHOW_WINDOW=0x02, ATTENTION=0x04, ALL=0xFF) diff --git a/lib/logitech_receiver/device.py b/lib/logitech_receiver/device.py index 5a6a08b3..2316b977 100644 --- a/lib/logitech_receiver/device.py +++ b/lib/logitech_receiver/device.py @@ -26,6 +26,7 @@ import hidapi as _hid import solaar.configuration as _configuration 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 logger = logging.getLogger(__name__) @@ -92,6 +93,7 @@ class Device: self._led_effects = self._firmware = self._keys = self._remap_keys = self._gestures = None self._profiles = self._backlight = self._registers = self._settings = None self.notification_flags = None + self.battery_info = None self._feature_settings_checked = False self._gestures_lock = _threading.Lock() @@ -351,6 +353,36 @@ class Device: if self.persister and battery_feature is None: 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): """Enable or disable device (dis)connection notifications on this receiver.""" @@ -464,7 +496,7 @@ class Device: __nonzero__ = __bool__ 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): try: diff --git a/lib/logitech_receiver/notifications.py b/lib/logitech_receiver/notifications.py index f43f8143..a909ffe8 100644 --- a/lib/logitech_receiver/notifications.py +++ b/lib/logitech_receiver/notifications.py @@ -222,7 +222,7 @@ def _process_hidpp10_custom_notification(device, status, n): if n.sub_id in (_R.battery_status, _R.battery_charge): assert n.data[-1:] == b"\x00" 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 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 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: if logger.isEnabledFor(logging.INFO): 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: 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: logger.warning("%s: unknown VOLTAGE %s", device, n) elif feature == _F.UNIFIED_BATTERY: 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: 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: result = hidpp20.decipher_adc_measurement(n.data) if result: - status.set_battery_info(result[1]) + device.set_battery_info(result[1]) else: # this feature is used to signal device becoming inactive status.changed(active=False) else: @@ -331,11 +331,11 @@ def _process_feature_notification(device, status, n, feature): # status_text = '%1.2fV' % (adc * 2.67793237653 / 0x0672) status_text = _Battery.STATUS.discharging 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: if lux > 200: 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: if logger.isEnabledFor(logging.DEBUG): logger.debug("%s: Light Check button pressed", device) diff --git a/lib/logitech_receiver/status.py b/lib/logitech_receiver/status.py index e1258910..59b03f84 100644 --- a/lib/logitech_receiver/status.py +++ b/lib/logitech_receiver/status.py @@ -21,7 +21,7 @@ from . import hidpp10 from . import hidpp10_constants as _hidpp10_constants from . import hidpp20_constants as _hidpp20_constants from . import settings as _settings -from .common import Battery, NamedInts +from .common import NamedInts logger = logging.getLogger(__name__) @@ -52,19 +52,6 @@ class ReceiverStatus: assert 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): self._changed_callback(self._receiver, alert=alert, reason=reason) @@ -81,46 +68,13 @@ class DeviceStatus: assert changed_callback self._changed_callback = changed_callback self._active = None # is the device active? - self.battery = None self.link_encrypted = None - # self.notification_flags = None - self.battery_error = None def __bool__(self): return bool(self._active) __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): d = self._device @@ -135,7 +89,7 @@ class DeviceStatus: if d.protocol < 2.0: self._device.notification_flags = d.enable_connection_notifications() # 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 # and when devices become active if they don't have wireless device status feature, diff --git a/lib/solaar/ui/tray.py b/lib/solaar/ui/tray.py index 59908047..afad8ee9 100644 --- a/lib/solaar/ui/tray.py +++ b/lib/solaar/ui/tray.py @@ -197,9 +197,8 @@ try: def _update_tray_icon(): if _picked_device and gtk.battery_icons_style != "solaar": _ignore, _ignore, name, device = _picked_device - device_status = device.status - battery_level = device_status.battery.level if device_status.battery is not None else None - battery_charging = device_status.battery.charging() 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.battery_info.charging() if device.battery_info is not None else None tray_icon_name = _icons.battery(battery_level, battery_charging) description = "%s: %s" % (name, device.status_string()) @@ -252,9 +251,9 @@ except ImportError: _icon.set_tooltip_markup(tooltip) if _picked_device and gtk.battery_icons_style != "solaar": - _ignore, _ignore, name, device_status = _picked_device - battery_level = device_status.battery.level if device_status.battery is not None else None - battery_charging = device_status.battery.charging() if device_status.battery is not None else None + _ignore, _ignore, name, device = _picked_device + battery_level = device.battery_info.level if device.battery_info 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) else: # there may be a receiver, but no peripherals @@ -331,7 +330,7 @@ def _pick_device_with_lowest_battery(): for info in _devices_info: if info[1] is None: # is receiver 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) if level is not None and picked_level > level: picked = info @@ -428,8 +427,8 @@ def _update_menu_item(index, device): menu_items = _menu.get_children() menu_item = menu_items[index] - level = device.status.battery.level if device.status.battery is not None else None - charging = device.status.battery.charging() 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.battery_info.charging() if device.battery_info is not None else None icon_name = _icons.battery(level, charging) menu_item.set_label((" " if 0 < device.number <= 6 else "") + device.name + ": " + device.status_string()) diff --git a/lib/solaar/ui/window.py b/lib/solaar/ui/window.py index 4ab3f3d8..6ae2e681 100644 --- a/lib/solaar/ui/window.py +++ b/lib/solaar/ui/window.py @@ -682,17 +682,17 @@ def _update_device_panel(device, panel, buttons, full=False): is_online = bool(device.online) panel.set_sensitive(is_online) - if device.status.battery is None or device.status.battery.level is None: - device.status.read_battery() + if device.battery_info is None or device.battery_info.level is None: + device.read_battery() - battery_level = device.status.battery.level if device.status.battery is not None else None - battery_voltage = device.status.battery.voltage 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.battery_info.voltage if device.battery_info is not None else None if battery_level is None and battery_voltage is None: panel._battery.set_visible(False) else: panel._battery.set_visible(True) - battery_next_level = device.status.battery.next_level - charging = device.status.battery.charging() if device.status.battery is not None else None + battery_next_level = device.battery_info.next_level + charging = device.battery_info.charging() if device.battery_info is not None else None icon_name = _icons.battery(battery_level, charging) panel._battery._icon.set_from_icon_name(icon_name, _INFO_ICON_SIZE) panel._battery._icon.set_sensitive(True) @@ -750,7 +750,7 @@ def _update_device_panel(device, panel, buttons, full=False): panel._secure.set_tooltip_text("") 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: panel._lux.set_visible(False) else: @@ -906,8 +906,8 @@ def update_device(device, item, selected_device_id, need_popup, full=False): is_online = bool(device.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_voltage = device.status.battery.voltage 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.battery_info.voltage if device.battery_info is not None else None if battery_level is None: _model.set_value(item, _COLUMN.STATUS_TEXT, _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} _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) _model.set_value(item, _COLUMN.STATUS_ICON, icon_name)