From c302db32c2bad1458d1827bacaef6f399d2c6a60 Mon Sep 17 00:00:00 2001 From: Mattias Andrée Date: Wed, 8 Mar 2017 22:10:21 +0100 Subject: m + add lid and powersupply MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mattias Andrée --- src/plugins/alsa.py | 12 +- src/plugins/lid.py | 41 +++++++ src/plugins/powersupply.py | 266 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 313 insertions(+), 6 deletions(-) create mode 100644 src/plugins/lid.py create mode 100644 src/plugins/powersupply.py (limited to 'src') diff --git a/src/plugins/alsa.py b/src/plugins/alsa.py index 68d47bf..f624672 100644 --- a/src/plugins/alsa.py +++ b/src/plugins/alsa.py @@ -44,7 +44,7 @@ class ALSA: ''' - def __init__(self, card = -1, mixername = 'Master', *, **kwargs): + def __init__(self, card = -1, mixername = 'Master', *, cardindex = None): ''' Constructor @@ -52,8 +52,8 @@ class ALSA: `ALSA.DEFAULT_CARD` (-1) or 'default' for the default card @param mixername:str The name of the mixer ''' - if card == -1 and 'cardindex' in kwargs: # For backwards compatibility - card = kwargs['cardindex'] + if card == -1 and cardindex is not None: # For backwards compatibility + card = cardindex if isinstance(card, str): if card == 'default': card = -1 @@ -131,7 +131,7 @@ class ALSA: @staticmethod - def get_mixers(card = -1, *, **kwargs): + def get_mixers(card = -1, *, cardindex = None): ''' Get the names of all available mixers for an audio card @@ -139,8 +139,8 @@ class ALSA: `ALSA.DEFAULT_CARD` (-1) or 'default' for the default card @return :list The names of all available mixers for an audio card ''' - if card == -1 and 'cardindex' in kwargs: # For backwards compatibility - card = kwargs['cardindex'] + if card == -1 and cardindex is not None: # For backwards compatibility + card = cardindex if isinstance(card, str): if card == 'default': card = -1 diff --git a/src/plugins/lid.py b/src/plugins/lid.py new file mode 100644 index 0000000..c307abb --- /dev/null +++ b/src/plugins/lid.py @@ -0,0 +1,41 @@ +# -*- python -*- +''' +xpybar – xmobar replacement written in python +Copyright © 2014, 2015, 2016, 2017 Mattias Andrée (maandree@member.fsf.org) + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +''' + +import os + + +class Lid: + ''' + Laptop lid monitor + ''' + + @staticmethod + def is_open(): + ''' + Check whether the lid is open + + @param :bool? `True` if the lid is open, + `False` if the lid is closed, + `None` if there is no lid, or if the + computer does not report the lid's state + ''' + if not os.path.exists('/proc/acpi/button/lid/LID/state'): + return None + with open('/proc/acpi/button/lid/LID/state') as file: + return 'open' in file.read.decode('utf-8', 'strict') diff --git a/src/plugins/powersupply.py b/src/plugins/powersupply.py new file mode 100644 index 0000000..5408088 --- /dev/null +++ b/src/plugins/powersupply.py @@ -0,0 +1,266 @@ +# -*- python -*- +''' +xpybar – xmobar replacement written in python +Copyright © 2014, 2015, 2016, 2017 Mattias Andrée (maandree@member.fsf.org) + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +''' + +import os + + +class PowerSupply: + ''' + Power supply monitor + + To calculate the time (in hours) until discharged: + ``` + power_supply.get_charge() / power_supply.get_current() + ``` + + To calculate the time (in hours) until charged: + ``` + (power_supply.get_charge_full() - power_supply.get_charge()) / power_supply.get_current() + ``` + + @variable name:str The name of the power supply + @variable path:str The path to the power supply in /sys + @variable type:str? The power supply type, or `None` if not found + (this is most likely not the case), known possible values + 'Mains' and 'Battery' + + The following will probably be `None` for non-Battery power supplies + + @variable manufacturer:str? The manufacturer of the power supply, `None` if unknown + @variable model_name:str? The model name of the power supply, `None` if unknown + @variable serial_number:str? The serial number of the power supply, `None` if unknown + @variable technology:str? The technology the power supply uses, `None` if unknown + @variable charge_full_design:int? The charge (nAh) of the power supply at full capacity + when the power supply supply is 100 % healthy, + `None` if unknown of if not applicable + @variable voltage_min_design:int? The minimum voltage (nV) when the power supply supply is + 100 % healthy, `None` if unknown of if not applicable + ''' + + def __init__(self, name): + ''' + Constructor + + @param name:str The name of the power supply, you can find + the with `PowerSupplu.supplies` + ''' + self.name = name + self.path = '/sys/class/power_supply/' + name + + self.type = None + if os.path.exists(self.path + '/type'): + with open(self.path + '/type', 'rb') as file: + self.type = file.read().decode('utf-8', 'strict')[:-1] + + self.manufacturer = None + if os.path.exists(self.path + '/manufacturer'): + with open(self.path + '/manufacturer', 'rb') as file: + self.manufacturer = file.read().decode('utf-8', 'strict')[:-1] + + self.model_name = None + if os.path.exists(self.path + '/model_name'): + with open(self.path + '/model_name', 'rb') as file: + self.model_name = file.read().decode('utf-8', 'strict')[:-1] + + self.serial_number = None + if os.path.exists(self.path + '/serial_number'): + with open(self.path + '/serial_number', 'rb') as file: + self.serial_number = file.read().decode('utf-8', 'strict')[:-1] + + self.technology = None + if os.path.exists(self.path + '/technology'): + with open(self.path + '/technology', 'rb') as file: + self.technology = file.read().decode('utf-8', 'strict')[:-1] + + self.charge_full_design = None + if os.path.exists(self.path + '/charge_full_design'): + with open(self.path + '/charge_full_design', 'rb') as file: + self.charge_full_design = int(file.read().decode('utf-8', 'strict')[:-1]) + elif os.path.exists(self.path + '/energy_full_design'): + with open(self.path + '/energy_full_design', 'rb') as file: + self.charge_full_design = int(file.read().decode('utf-8', 'strict')[:-1]) + + self.voltage_min_design = None + if os.path.exists(self.path + '/voltage_min_design'): + with open(self.path + '/voltage_min_design', 'rb') as file: + self.voltage_min_design = int(file.read().decode('utf-8', 'strict')[:-1]) + + def get_alarm(): + ''' + Get the alarm level + + @return :int? The alarm level, `None` if unknown or if unapplicable + ''' + if os.path.exists(self.path + '/alarm'): + with open(self.path + '/alarm', 'rb') as file: + return int(file.read().decode('utf-8', 'strict')[:-1]) + return None + + def get_capacity(): + ''' + Get the current capacity + + This is a rounded down version of `.get_charge() / .get_charge_full()` + + @param :int? The current capacity, + `None` if unknown or if unapplicable + ''' + if os.path.exists(self.path + '/capacity'): + with open(self.path + '/capacity', 'rb') as file: + return int(file.read().decode('utf-8', 'strict')[:-1]) + return None + + def get_capacity_level(): + ''' + TODO some information about what this is would be nice + ''' + if os.path.exists(self.path + '/capacity_level'): + with open(self.path + '/capacity_level', 'rb') as file: + return file.read().decode('utf-8', 'strict')[:-1] + return None + + def get_charge_full(): + ''' + Get the charge when the power supply is fully charged + according to the last known charge as fully charged state + + @return :int? The charge in nAh when the power supply is fully charged, + `None` if unknown or if unapplicable + ''' + if os.path.exists(self.path + '/charge_full'): + with open(self.path + '/charge_full', 'rb') as file: + return int(file.read().decode('utf-8', 'strict')[:-1]) + if os.path.exists(self.path + '/energy_full'): + with open(self.path + '/energy_full', 'rb') as file: + return int(file.read().decode('utf-8', 'strict')[:-1]) + return None + + def get_charge(): + ''' + Get the current charge + + @return :int? The current charge in nAh, + `None` if unknown or if unapplicable + ''' + if os.path.exists(self.path + '/charge_now'): + with open(self.path + '/charge_now', 'rb') as file: + return int(file.read().decode('utf-8', 'strict')[:-1]) + if os.path.exists(self.path + '/energy_now'): + with open(self.path + '/energy_now', 'rb') as file: + return int(file.read().decode('utf-8', 'strict')[:-1]) + return None + + def get_current(): + ''' + Get the current current + + @return :int? The current current in nA, `None` if unknown + ''' + if os.path.exists(self.path + '/current_now'): + with open(self.path + '/current_now', 'rb') as file: + return int(file.read().decode('utf-8', 'strict')[:-1]) + elif os.path.exists(self.path + '/power_now') and os.path.exists(self.path + '/voltage_now'): + with open(self.path + '/power_now', 'rb') as file: + power = int(file.read().decode('utf-8', 'strict')[:-1]) + with open(self.path + '/voltage_now', 'rb') as file: + voltage = int(file.read().decode('utf-8', 'strict')[:-1]) + return 1000000 * power / voltage + return None + + def get_power(): + ''' + Get the current power + + @return :int? The current power in nW, `None` if unknown + ''' + if os.path.exists(self.path + '/power_now'): + with open(self.path + '/power_now', 'rb') as file: + return int(file.read().decode('utf-8', 'strict')[:-1]) + elif os.path.exists(self.path + '/current_now') and os.path.exists(self.path + '/voltage_now'): + with open(self.path + '/current_now', 'rb') as file: + current = int(file.read().decode('utf-8', 'strict')[:-1]) + with open(self.path + '/voltage_now', 'rb') as file: + voltage = int(file.read().decode('utf-8', 'strict')[:-1]) + return voltage * current / 1000000 + return None + + def get_cycle_count(): + ''' + Get the battery's cycle count, that is, the full charge + energy divided by the total used energy + + @param :int? The number of cycle, is often appear as zero, + `None` if unknown or if unapplicable + ''' + if os.path.exists(self.path + '/cycle_count'): + with open(self.path + '/cycle_count', 'rb') as file: + return int(file.read().decode('utf-8', 'strict')[:-1]) + return None + + def get_status(): + ''' + Get the current status + + @return :str? The current status, known values are + 'Charging', 'Discharging' and 'Unknown', + `None` if unknown or if unapplicable + ''' + if os.path.exists(self.path + '/status'): + with open(self.path + '/status', 'rb') as file: + return file.read().decode('utf-8', 'strict')[:-1] + return None + + def get_voltage(): + ''' + Get the current voltage + + @return :int? The current voltage in nV, `None` if unknown + ''' + if os.path.exists(self.path + '/voltage_now'): + with open(self.path + '/voltage_now', 'rb') as file: + return int(file.read().decode('utf-8', 'strict')[:-1]) + return None + + def is_online(self): + ''' + Check whether the power supply is online + + @return :bool? Whether the power supply is online, `None` if unknown + ''' + if os.path.exists(self.path + '/online'): + with open(self.path + '/online', 'rb') as file: + return file.read().decode('utf-8', 'strict') == '1\n' + if os.path.exists(self.path + '/present'): + with open(self.path + '/present', 'rb') as file: + return file.read().decode('utf-8', 'strict') == '1\n' + return False + + @staticmethod + def supplies(): + ''' + Lists all available power supplies + + If the returned list is empty, you are probably running on a + desktop (or more powerful machine such as a server) that only + runs on mains (AC). + + @return :list The name of all available power supplies + ''' + return os.listdir('/sys/class/power_supply') + -- cgit v1.2.3-70-g09d2