bumblebee-status/bumblebee_status/modules/contrib/battery.py
tobi-wan-kenobi caf2fea04b [modules/battery] gracefully handle "no batteries" case
if no batteries are found, assume the machine is running on AC.

fixes #636
2020-05-28 08:17:35 +02:00

212 lines
7.3 KiB
Python

# pylint: disable=C0111,R0903
"""Displays battery status, remaining percentage and charging information.
Parameters:
* battery.device : Comma-separated list of battery devices to read information from (defaults to auto for auto-detection)
* battery.warning : Warning threshold in % of remaining charge (defaults to 20)
* battery.critical : Critical threshold in % of remaining charge (defaults to 10)
* battery.showdevice : If set to 'true', add the device name to the widget (defaults to False)
* battery.decorate : If set to 'false', hides additional icons (charging, etc.) (defaults to True)
* battery.showpowerconsumption: If set to 'true', show current power consumption (defaults to False)
* battery.compact-devices : If set to 'true', compacts multiple batteries into a single entry (default to False)
(partially) contributed by `martindoublem <https://github.com/martindoublem>`_ - many thanks!
"""
import os
import glob
import logging
log = logging.getLogger(__name__)
try:
import power
except ImportError:
log.warning('unable to import module "power": Time estimates will not be available')
import core.module
import core.input
import util.format
class BatteryManager(object):
def remaining(self):
try:
estimate = power.PowerManagement().get_time_remaining_estimate()
# do not show remaining if on AC
if estimate == power.common.TIME_REMAINING_UNLIMITED:
return None
return estimate * 60 # return value in seconds
except Exception as e:
return -1
return -1
def read(self, battery, component, default=None):
path = "/sys/class/power_supply/{}".format(battery)
if not os.path.exists(path):
return default
try:
with open("{}/{}".format(path, component)) as f:
return f.read().strip()
except IOError:
return "n/a"
return default
def capacity(self, battery):
capacity = self.read(battery, "capacity", 100)
if capacity != "n/a":
capacity = int(capacity)
return capacity if capacity < 100 else 100
def capacity_all(self, batteries):
now = 0
full = 0
for battery in batteries:
try:
with open(
"/sys/class/power_supply/{}/energy_full".format(battery)
) as f:
full += int(f.read())
with open("/sys/class/power_supply/{}/energy_now".format(battery)) as f:
now += int(f.read())
except IOError:
return "n/a"
return int(float(now) / float(full) * 100.0)
def isac(self, battery):
path = "/sys/class/power_supply/{}".format(battery)
return not os.path.exists(path)
def isac_any(self, batteries):
for battery in batteries:
if self.isac(battery):
return True
return False
def consumption(self, battery):
consumption = self.read(battery, "power_now", "n/a")
if consumption == "n/a":
return "n/a"
return "{}W".format(int(consumption) / 1000000)
def charge(self, battery):
return self.read(battery, "status", "n/a")
def charge_any(self, batteries):
for battery in batteries:
if self.charge(battery) == "Discharging":
return "Discharging"
return "Charged"
class Module(core.module.Module):
def __init__(self, config, theme):
super().__init__(config, theme, [])
self.__manager = BatteryManager()
self._batteries = util.format.aslist(self.parameter("device", "auto"))
if self._batteries[0] == "auto":
self._batteries = [
os.path.basename(battery)
for battery in glob.glob("/sys/class/power_supply/BAT*")
]
core.input.register(
self, button=core.input.LEFT_MOUSE, cmd="gnome-power-statistics"
)
if len(self._batteries) == 0:
widget = self.add_widget(full_text=self.ac, name="ac")
widget.set("ac", True)
widget.set("capacity", 100)
else:
if util.format.asbool(self.parameter("compact-devices", False)):
widget = self.add_widget(full_text=self.capacity, name="all-batteries")
else:
for battery in self._batteries:
log.debug("adding new widget for {}".format(battery))
widget = self.add_widget(full_text=self.capacity, name=battery)
for w in self.widgets():
if util.format.asbool(self.parameter("decorate", True)) == False:
widget.set("theme.exclude", "suffix")
def ac(self, widget):
return "ac"
def capacity(self, widget):
if widget.name == "all-batteries":
capacity = self.__manager.capacity_all(self._batteries)
else:
capacity = self.__manager.capacity(widget.name)
widget.set("capacity", capacity)
widget.set("ac", self.__manager.isac_any(self._batteries))
widget.set("theme.minwidth", "100%")
# Read power conumption
if util.format.asbool(self.parameter("showpowerconsumption", False)):
output = "{}% ({})".format(
capacity, self.__manager.consumption(widget.name)
)
else:
output = "{}%".format(capacity)
if (
util.format.asbool(self.parameter("showremaining", True))
and self.__manager.charge(widget.name) == "Discharging"
):
remaining = self.__manager.remaining()
if remaining >= 0:
output = "{} {}".format(
output, util.format.duration(remaining, compact=True, unit=True)
)
if util.format.asbool(self.parameter("showdevice", False)):
output = "{} ({})".format(output, widget.name)
return output
def state(self, widget):
state = []
capacity = widget.get("capacity")
if capacity < 0:
log.debug("battery state: {}".format(state))
return ["critical", "unknown"]
if capacity < int(self.parameter("critical", 10)):
state.append("critical")
elif capacity < int(self.parameter("warning", 20)):
state.append("warning")
if widget.get("ac"):
state.append("AC")
else:
if widget.name == "all-batteries":
charge = self.__manager.charge_any(self._batteries)
else:
charge = self.__manager.charge(widget.name)
if charge == "Discharging":
state.append(
"discharging-{}".format(
min([10, 25, 50, 80, 100], key=lambda i: abs(i - capacity))
)
)
elif charge == "Unknown":
state.append(
"unknown-{}".format(
min([10, 25, 50, 80, 100], key=lambda i: abs(i - capacity))
)
)
else:
if capacity > 95:
state.append("charged")
else:
state.append("charging")
return state
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4