Updating Ubuntu Battery Status (upower)
A laptop computer running Ubuntu has a battery icon in the upper-right corner depicting its battery's status. Whether it is charging and if not, the state of charge. Fine for majority of normal use, but what if I want that information programmatically? Since it's Linux, I knew not only was it possible, but there would also be multiple ways to do it. A web search brought me to UPower. Its official website is quite sparse, and the official documentation written for people who are already knowledgeable about Linux hardware management. For a more beginner-friendly introduction I needed the Wikipedia overview.
There is a command-line utility for querying upower information, and we can get started with upower --help
.
Usage:
upower [OPTION…] UPower tool
Help Options:
-h, --help Show help options
Application Options:
-e, --enumerate Enumerate objects paths for devices
-d, --dump Dump all parameters for all objects
-w, --wakeups Get the wakeup data
-m, --monitor Monitor activity from the power daemon
--monitor-detail Monitor with detail
-i, --show-info Show information about object path
-v, --version Print version of client and daemon
Seeing "Enumerate" as the top of the non-alphabetized list told me that should be where I start. Running upower --enumerate
returned the following on my laptop. (Your hardware will differ.)
/org/freedesktop/UPower/devices/line_power_AC
/org/freedesktop/UPower/devices/battery_BAT0
/org/freedesktop/UPower/devices/DisplayDevice
One of these three items has "battery" in its name, so that's where I could query for information with upower -i /org/freedesktop/UPower/devices/battery_BAT0
.
native-path: BAT0
vendor: DP-SDI56
model: DELL YJNKK18
serial: 1
power supply: yes
updated: Mon 04 Sep 2023 11:28:38 AM PDT (119 seconds ago)
has history: yes
has statistics: yes
battery
present: yes
rechargeable: yes
state: pending-charge
warning-level: none
energy: 50.949 Wh
energy-empty: 0 Wh
energy-full: 53.9238 Wh
energy-full-design: 57.72 Wh
energy-rate: 0.0111 W
voltage: 9.871 V
charge-cycles: N/A
percentage: 94%
capacity: 93.4231%
technology: lithium-ion
icon-name: 'battery-full-charging-symbolic'
That should be all the information I need to inform many different project ideas, but there are two problems:
- I still want the information from my code rather than running the command line. Yes, I can probably write code to run the command line and parse its output, but there is a more elegant method.
- The information is updated once every few minutes. This should be frequent enough most of the time, but sometimes we need more up-to-date information. For example, if I want to write a piece of code to watch for the rapid and precipitous voltage drop that happens when a battery is nearly empty. We may only have a few seconds to react before the machine shuts down, so I would want to dynamically increase the polling frequency when the time is near.
I didn't see a upower
command line option to refresh information, so I went searching further and found the answer to both problems in this thread "Get battery status to update more often or on AC power/wake" on AskUbuntu. I learned there is a way to request status refresh via a Linux system mechanism called D-Bus. Communicating via D-Bus is much more elegant (and potentially less of a security risk) than executing command-line tools. The forum thread answer is in the form of "run this code" but I wanted to follow along step-by-step in Python interactive prompt.
>>> import dbus
>>> bus = dbus.SystemBus()
>>> enum_proxy = bus.get_object('org.freedesktop.UPower','/org/freedesktop/UPower')
>>> enum_method = enum_proxy.get_dbus_method('EnumerateDevices','org.freedesktop.UPower')
>>> enum_method()
dbus.Array([dbus.ObjectPath('/org/freedesktop/UPower/devices/line_power_AC'), dbus.ObjectPath('/org/freedesktop/UPower/devices/battery_BAT0')], signature=dbus.Signature('o'))
>>> devices = enum_method()
>>> devices[0]
dbus.ObjectPath('/org/freedesktop/UPower/devices/line_power_AC')
>>> str(devices[0])
'/org/freedesktop/UPower/devices/line_power_AC'
>>> str(devices[1])
'/org/freedesktop/UPower/devices/battery_BAT0'
>>> batt_path = str(devices[1])
>>> batt_proxy = bus.get_object('org.freedesktop.UPower',batt_path)
>>> batt_method = batt_proxy.get_dbus_method('Refresh','org.freedesktop.UPower.Device')
>>> batt_method()
I understood those lines to perform the following tasks:
- Gain access to D-Bus from my Python code
- Get the object representing UPower globally.
- Enumerate devices under UPower control. EnumerateDevices is one of the methods listed on the corresponding
UPower
documentation page. - One of the enumerated devices had a "battery" in its name.
- Convert that name to a string. I don't understand why this was necessary, I would have expected the UPower D-Bus API should understand the objects it sent out itself.
- Get an UPower object again, but this time with the battery path so we're retrieving an UPower object representing the battery specifically.
- From that object, get a handle to the "Refresh" method. Refresh is one of the methods listed on the corresponding
UPower.Device
documentation page. - Calling that handle will trigger a refresh. The call itself wouldn't return any data, but the next query for battery statistics (either via
upower
command line tool or via theGetStatistics
D-Bus method) will return updated data.