diff --git a/README.md b/README.md index f7f2071..e719716 100644 --- a/README.md +++ b/README.md @@ -155,6 +155,26 @@ org.freedesktop.tuhi1.Device The physical dimensions (width, height) in µm Read-only + Property: BatteryPercent (u) + The last known battery charge level in percent. This charge level is + only valid when the BatteryState is other than Unknown. + Read-only + + Property: BatteryState (u) + An enum describing the battery state. Permitted enum values are + + 0: Unknown + 1: Charging + 2: Discharging + + 'Unknown' may refer to a state that could not be read, a state + that has not yet been updated, or a state that has not updated within + a daemon-internal time period. Thus, a device that is connected but + does not regularly send battery updates may eventually switch to + 'Unknown'. + + Read-only + Property: DrawingsAvailable (at) An array of timestamps of the available drawings. The timestamp of each drawing can be used as argument to GetJSONData(). Timestamps are diff --git a/tools/tuhi-kete.py b/tools/tuhi-kete.py index eeb35cb..2489c7b 100755 --- a/tools/tuhi-kete.py +++ b/tools/tuhi-kete.py @@ -163,6 +163,14 @@ class TuhiKeteDevice(_DBusObject): def drawings_available(self): return self.property('DrawingsAvailable') + @GObject.Property + def battery_percent(self): + return self.property('BatteryPercent') + + @GObject.Property + def battery_state(self): + return self.property('BatteryState') + def pair(self): logger.debug(f'{self}: Pairing') # FIXME: Pair() doesn't return anything useful yet, so we wait until @@ -199,6 +207,10 @@ class TuhiKeteDevice(_DBusObject): self.notify('drawings-available') elif 'Listening' in changed_props: self.notify('listening') + elif 'BatteryPercent' in changed_props: + self.notify('battery-percent') + elif 'BatteryState' in changed_props: + self.notify('battery-state') def __repr__(self): return f'{self.address} - {self.name}' @@ -923,6 +935,16 @@ class TuhiKeteShell(cmd.Cmd): for device in self._manager.devices: if parsed_args.address is None or parsed_args.address == device.address: print(device) + charge_strs = { + 0: 'unknown', + 1: 'charging', + 2: 'discharging' + } + try: + charge_str = charge_strs[device.battery_state] + except KeyError: + charge_str = 'invalid' + print(f'\tBattery level: {device.battery_percent}%, {charge_str}') print('\tAvailable drawings:') for d in device.drawings_available: t = time.localtime(d) diff --git a/tuhi/base.py b/tuhi/base.py index bd9afef..29600e7 100644 --- a/tuhi/base.py +++ b/tuhi/base.py @@ -12,6 +12,7 @@ # import argparse +import enum import logging import sys from gi.repository import GObject @@ -34,6 +35,12 @@ class TuhiDevice(GObject.Object): real device) with the frontend DBusServer object that exports the device over Tuhi's DBus interface ''' + + class BatteryState(enum.Enum): + UNKNOWN = 0 + CHARGING = 1 + DISCHARGING = 2 + __gsignals__ = { # Signal sent when an error occurs on the device itself. # Argument is a Wacom*Exception @@ -49,6 +56,8 @@ class TuhiDevice(GObject.Object): assert uuid is not None or paired is False self.paired = paired self._uuid = uuid + self._battery_state = TuhiDevice.BatteryState.UNKNOWN + self._battery_percent = 0 bluez_device.connect('connected', self._on_bluez_device_connected) bluez_device.connect('disconnected', self._on_bluez_device_disconnected) @@ -93,6 +102,23 @@ class TuhiDevice(GObject.Object): def listening(self): return self._tuhi_dbus_device.listening + @GObject.Property + def battery_percent(self): + return self._battery_percent + + @battery_percent.setter + def battery_percent(self, value): + self._battery_percent = value + + @GObject.Property + def battery_state(self): + return self._battery_state + + @battery_state.setter + def battery_state(self, value): + print('setting battery state on tuhidevice') + self._battery_state = value + def connect_device(self): self._bluez_device.connect_device() @@ -104,6 +130,7 @@ class TuhiDevice(GObject.Object): self._wacom_device.connect('done', self._on_fetching_finished, bluez_device) self._wacom_device.connect('button-press-required', self._on_button_press_required) self._wacom_device.connect('notify::uuid', self._on_uuid_updated, bluez_device) + self._wacom_device.connect('battery-status', self._on_battery_status, bluez_device) self._wacom_device.start(not self.paired) self.pairing_mode = False @@ -138,6 +165,13 @@ class TuhiDevice(GObject.Object): def _on_listening_updated(self, dbus_device, pspec): self.notify('listening') + def _on_battery_status(self, wacom_device, percent, is_charging, bluez_device): + if is_charging: + self.battery_state = TuhiDevice.BatteryState.CHARGING + else: + self.battery_state = TuhiDevice.BatteryState.DISCHARGING + self.battery_percent = percent + class Tuhi(GObject.Object): __gsignals__ = { diff --git a/tuhi/dbusserver.py b/tuhi/dbusserver.py index 7b4038f..7ae2df6 100755 --- a/tuhi/dbusserver.py +++ b/tuhi/dbusserver.py @@ -53,6 +53,12 @@ INTROSPECTION_XML = ''' + + + + + + @@ -146,7 +152,11 @@ class TuhiDBusDevice(_TuhiDBus): self._listening = False self._listening_client = None self._dbusid = self._register_object(connection) + self._battery_percent = 0 + self._battery_state = device.battery_state device.connect('notify::paired', self._on_device_paired) + device.connect('notify::battery-percent', self._on_battery_percent) + device.connect('notify::battery-state', self._on_battery_state) device.connect('device-error', self._on_device_error) @GObject.Property @@ -169,6 +179,30 @@ class TuhiDBusDevice(_TuhiDBus): def paired(self, paired): self._paired = paired + @GObject.Property + def battery_percent(self): + return self._battery_percent + + @battery_percent.setter + def battery_percent(self, value): + if self._battery_percent == value: + return + + self._battery_percent = value + self.properties_changed({'BatteryPercent': GLib.Variant.new_uint32(value)}) + + @GObject.Property + def battery_state(self): + return self._battery_state + + @battery_state.setter + def battery_state(self, value): + if self._battery_state == value: + return + + self._battery_state = value + self.properties_changed({'BatteryState': GLib.Variant.new_uint32(value.value)}) + def remove(self): self.connection.unregister_object(self._dbusid) self._dbusid = None @@ -221,6 +255,10 @@ class TuhiDBusDevice(_TuhiDBus): return ts elif propname == 'Listening': return GLib.Variant.new_boolean(self.listening) + elif propname == 'BatteryPercent': + return GLib.Variant.new_uint32(self.battery_percent) + elif propname == 'BatteryState': + return GLib.Variant.new_uint32(self.battery_state.value) return None @@ -235,6 +273,12 @@ class TuhiDBusDevice(_TuhiDBus): return self.paired = device.paired + def _on_battery_percent(self, device, pspec): + self.battery_percent = device.battery_percent + + def _on_battery_state(self, device, pspec): + self.battery_state = device.battery_state + def _on_device_error(self, device, exception): logger.info('An error occured while synching the device') if self.listening: diff --git a/tuhi/wacom.py b/tuhi/wacom.py index 1e45870..68107bc 100644 --- a/tuhi/wacom.py +++ b/tuhi/wacom.py @@ -106,6 +106,9 @@ class WacomDevice(GObject.Object): (GObject.SIGNAL_RUN_FIRST, None, (GObject.TYPE_PYOBJECT, )), 'button-press-required': (GObject.SIGNAL_RUN_FIRST, None, ()), + # battery level in %, boolean for is-charging + "battery-status": + (GObject.SIGNAL_RUN_FIRST, None, (GObject.TYPE_INT, GObject.TYPE_BOOLEAN)), } def __init__(self, device, uuid=None): @@ -544,6 +547,7 @@ class WacomDevice(GObject.Object): logger.debug(f'device is plugged in and charged at {battery}%') else: logger.debug(f'device is discharging: {battery}%') + self.emit('battery-status', battery, charging) if self.is_slate(): self.width = w = self.get_dimensions('width') self.height = h = self.get_dimensions('height')