From 624479d976bf5cff54e66e557d6cacb4f4700753 Mon Sep 17 00:00:00 2001 From: Oskari Rauta Date: Mon, 9 Nov 2015 10:59:30 +0200 Subject: [PATCH 1/9] Update to forked version 1.5 - Update version - Remove platforms from the list which I did not upgrade --- setup.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index 88f005d..734295b 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,7 @@ #!/usr/bin/env python # coding=utf-8 __author__ = 'kulakov.ilya@gmail.com' +__updatedby__ = 'oskari.rauta@gmail.com' from setuptools import setup from sys import platform @@ -15,13 +16,13 @@ setup( name="power", - version="1.4", + version="1.5", description="Cross-platform system power status information.", long_description="Library that allows you get current power source type (AC, Battery or UPS), warning level (none, <22%, <10min) and remaining minutes. You can also observe changes of power source and remaining time.", author="Ilya Kulakov", author_email="kulakov.ilya@gmail.com", url="https://github.com/Kentzo/Power", - platforms=["Mac OS X 10.6+", "Windows XP+", "Linux 2.6+", "FreeBSD"], + platforms=["Linux 2.6+"], packages=['power'], license="MIT License", classifiers=[ @@ -29,8 +30,6 @@ 'Intended Audience :: System Administrators', 'License :: OSI Approved :: MIT License', 'Natural Language :: English', - 'Operating System :: MacOS :: MacOS X', - 'Operating System :: Microsoft :: Windows', 'Operating System :: POSIX :: Linux', 'Programming Language :: Python', 'Topic :: System :: Monitoring', From 839b27b20a652d42a3e0ddcf58dba47d0f091c12 Mon Sep 17 00:00:00 2001 From: Oskari Rauta Date: Mon, 9 Nov 2015 11:01:43 +0200 Subject: [PATCH 2/9] Version changes - Update version string - Remove support for darwin, freebsd and win32 platforms --- power/__init__.py | 28 ++-- power/darwin.py | 341 ---------------------------------------------- power/freebsd.py | 174 ----------------------- power/win32.py | 99 -------------- 4 files changed, 16 insertions(+), 626 deletions(-) delete mode 100644 power/darwin.py delete mode 100644 power/freebsd.py delete mode 100644 power/win32.py diff --git a/power/__init__.py b/power/__init__.py index 0b1a59b..214e98d 100644 --- a/power/__init__.py +++ b/power/__init__.py @@ -1,6 +1,7 @@ # coding=utf-8 """ -Provides crossplatform checking of current power source, battery warning level and battery time remaining estimate. +Provides checking of current power source, battery warning level, ac status, battery +time remaining estimate and battery capacity. Allows you to add observer for power notifications if platform supports it. Usage: @@ -16,24 +17,27 @@ def on_time_remaining_change(self, power_management): # class Observer(object): # ... # PowerManagementObserver.register(Observer) + +Another example: + import power + + p = power.PowerManagement() + + ac_status, time_remaining, bat_capacity = p.get_ac_status() + + print('AC system status :', ac_status) + print('Time remaining :', time_remaining, 'minutes') + print('Capacity of batteries :', bat_capacity, '\n%') + """ -__version__ = '1.4' +__version__ = '1.5' from sys import platform from power.common import * try: - if platform.startswith('darwin'): - from power.darwin import PowerManagement - elif platform.startswith('freebsd'): - from power.freebsd import PowerManagement - elif platform.startswith('win32'): - from power.win32 import PowerManagement - elif platform.startswith('linux'): - from power.linux import PowerManagement - else: - raise RuntimeError("{platform} is not supported.".format(platform=platform)) + from power.linux import PowerManagement except RuntimeError as e: import warnings warnings.warn("Unable to load PowerManagement for {platform}. No-op PowerManagement class is used: {error}".format(error=str(e), platform=platform)) diff --git a/power/darwin.py b/power/darwin.py deleted file mode 100644 index 9678357..0000000 --- a/power/darwin.py +++ /dev/null @@ -1,341 +0,0 @@ -# coding=utf-8 -""" -Implements PowerManagement functions using IOPowerSources. -Requires Mac OS X 10.6+ -See doc/darwin for platform-specific details. -""" -__author__ = 'kulakov.ilya@gmail.com' - -import weakref -import warnings -import objc -from objc import super -from Foundation import * -from power import common - - -# Generated in Mac OS X 10.8.2 using the following command: -# gen_bridge_metadata -c '-l/System/Library/Frameworks/IOKit.framework/IOKit -I/System/Library/Frameworks/IOKit.framework/Headers/ps/' /System/Library/Frameworks/IOKit.framework/Headers/ps/IOPowerSources.h /System/Library/Frameworks/IOKit.framework/Headers/ps/IOPSKeys.h -# -# Following keas are added manually, because public headers misses their definitions: -# http://opensource.apple.com/source/IOKitUser/IOKitUser-514.16.50/pwr_mgt.subproj/IOPMLibPrivate.h -# - kIOPMUPSPowerKey -# - kIOPMBatteryPowerKey -# - kIOPMACPowerKey -# - kIOPSProvidesTimeRemaining -IO_POWER_SOURCES_BRIDGESUPPORT = """ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -""" - -objc.parseBridgeSupport( - IO_POWER_SOURCES_BRIDGESUPPORT, - globals(), - objc.pathForFramework("/System/Library/Frameworks/IOKit.framework") -) - - -POWER_TYPE_MAP = { - kIOPMACPowerKey: common.POWER_TYPE_AC, - kIOPMBatteryPowerKey: common.POWER_TYPE_BATTERY, - kIOPMUPSPowerKey: common.POWER_TYPE_UPS -} - - -WARNING_LEVEL_MAP = { - kIOPSLowBatteryWarningNone: common.LOW_BATTERY_WARNING_NONE, - kIOPSLowBatteryWarningEarly: common.LOW_BATTERY_WARNING_EARLY, - kIOPSLowBatteryWarningFinal: common.LOW_BATTERY_WARNING_FINAL -} - - -class PowerSourcesNotificationsObserver(NSObject): - """ - Manages NSThread instance which is used to run NSRunLoop with only source - IOPSNotificationCreateRunLoopSource. - Thread is automatically spawned when first observer is added and stopped when last observer is removed. - Does not keep strong references to observers. - - @note: Method names break PEP8 convention to conform PyObjC naming conventions - """ - def init(self): - self = super(PowerSourcesNotificationsObserver, self).init() - if self is not None: - self._weak_observers = [] - self._thread = None - self._lock = objc.object_lock(self) - return self - - def startThread(self): - """Spawns new NSThread to handle notifications.""" - if self._thread is not None: - return - self._thread = NSThread.alloc().initWithTarget_selector_object_(self, 'runPowerNotificationsThread', None) - self._thread.start() - - def stopThread(self): - """Stops spawned NSThread.""" - if self._thread is not None: - self.performSelector_onThread_withObject_waitUntilDone_('stopPowerNotificationsThread', self._thread, None, objc.YES) - self._thread = None - - def runPowerNotificationsThread(self): - """Main method of the spawned NSThread. Registers run loop source and runs current NSRunLoop.""" - pool = NSAutoreleasePool.alloc().init() - - @objc.callbackFor(IOPSNotificationCreateRunLoopSource) - def on_power_source_notification(context): - with self._lock: - for weak_observer in self._weak_observers: - observer = weak_observer() - if observer: - observer.on_power_source_notification() - - self._source = IOPSNotificationCreateRunLoopSource(on_power_source_notification, None) - CFRunLoopAddSource(NSRunLoop.currentRunLoop().getCFRunLoop(), self._source, kCFRunLoopDefaultMode) - while not NSThread.currentThread().isCancelled(): - NSRunLoop.currentRunLoop().runMode_beforeDate_(NSDefaultRunLoopMode, NSDate.distantFuture()) - del pool - - - def stopPowerNotificationsThread(self): - """Removes the only source from NSRunLoop and cancels thread.""" - assert NSThread.currentThread() == self._thread - - CFRunLoopSourceInvalidate(self._source) - self._source = None - NSThread.currentThread().cancel() - - def addObserver(self, observer): - """ - Adds weak ref to an observer. - - @param observer: Instance of class that implements on_power_source_notification() - """ - with self._lock: - self._weak_observers.append(weakref.ref(observer)) - if len(self._weak_observers) == 1: - self.startThread() - - def removeObserver(self, observer): - """ - Removes an observer. - - @param observer: Previously added observer - """ - with self._lock: - self._weak_observers.remove(weakref.ref(observer)) - if len(self._weak_observers) == 0: - self.stopThread() - - -class PowerManagement(common.PowerManagementBase): - notifications_observer = PowerSourcesNotificationsObserver.alloc().init() - - def __init__(self, cf_run_loop=None): - """ - @param cf_run_loop: If provided, all notifications are posted within this loop - """ - super(PowerManagement, self).__init__() - self._cf_run_loop = cf_run_loop - - def on_power_source_notification(self): - """ - Called in response to IOPSNotificationCreateRunLoopSource() event. - """ - for weak_observer in self._weak_observers: - observer = weak_observer() - if observer: - observer.on_power_sources_change(self) - observer.on_time_remaining_change(self) - - - def get_providing_power_source_type(self): - """ - Uses IOPSCopyPowerSourcesInfo and IOPSGetProvidingPowerSourceType to get providing power source type. - """ - blob = IOPSCopyPowerSourcesInfo() - type = IOPSGetProvidingPowerSourceType(blob) - return POWER_TYPE_MAP[type] - - def get_low_battery_warning_level(self): - """ - Uses IOPSGetBatteryWarningLevel to get battery warning level. - """ - warning_level = IOPSGetBatteryWarningLevel() - return WARNING_LEVEL_MAP[warning_level] - - def get_time_remaining_estimate(self): - """ - In Mac OS X 10.7+ - Uses IOPSGetTimeRemainingEstimate to get time remaining estimate. - - In Mac OS X 10.6 - IOPSGetTimeRemainingEstimate is not available. - If providing power source type is AC, returns TIME_REMAINING_UNLIMITED. - Otherwise looks through all power sources returned by IOPSGetProvidingPowerSourceType - and returns total estimate. - """ - if IOPSGetTimeRemainingEstimate is not None: # Mac OS X 10.7+ - estimate = float(IOPSGetTimeRemainingEstimate()) - if estimate == -1.0: - return common.TIME_REMAINING_UNKNOWN - elif estimate == -2.0: - return common.TIME_REMAINING_UNLIMITED - else: - return estimate / 60.0 - else: # Mac OS X 10.6 - warnings.warn("IOPSGetTimeRemainingEstimate is not preset", RuntimeWarning) - blob = IOPSCopyPowerSourcesInfo() - type = IOPSGetProvidingPowerSourceType(blob) - if type == common.POWER_TYPE_AC: - return common.TIME_REMAINING_UNLIMITED - else: - estimate = 0.0 - for source in IOPSCopyPowerSourcesList(blob): - description = IOPSGetPowerSourceDescription(blob, source) - if kIOPSIsPresentKey in description and description[kIOPSIsPresentKey] and kIOPSTimeToEmptyKey in description and description[kIOPSTimeToEmptyKey] > 0.0: - estimate += float(description[kIOPSTimeToEmptyKey]) - if estimate > 0.0: - return float(estimate) - else: - return common.TIME_REMAINING_UNKNOWN - - def add_observer(self, observer): - """ - Spawns thread or adds IOPSNotificationCreateRunLoopSource directly to provided cf_run_loop - @see: __init__ - """ - super(PowerManagement, self).add_observer(observer) - if len(self._weak_observers) == 1: - if not self._cf_run_loop: - PowerManagement.notifications_observer.addObserver(self) - else: - @objc.callbackFor(IOPSNotificationCreateRunLoopSource) - def on_power_sources_change(context): - self.on_power_source_notification() - - self._source = IOPSNotificationCreateRunLoopSource(on_power_sources_change, None) - CFRunLoopAddSource(self._cf_run_loop, self._source, kCFRunLoopDefaultMode) - - def remove_observer(self, observer): - """ - Stops thread and invalidates source. - """ - super(PowerManagement, self).remove_observer(observer) - if len(self._weak_observers) == 0: - if not self._cf_run_loop: - PowerManagement.notifications_observer.removeObserver(self) - else: - CFRunLoopSourceInvalidate(self._source) - self._source = None diff --git a/power/freebsd.py b/power/freebsd.py deleted file mode 100644 index 82af23d..0000000 --- a/power/freebsd.py +++ /dev/null @@ -1,174 +0,0 @@ -# coding=utf-8 -""" -Implements PowerManagement functions using FreeBSD SYSCTL mechanism. -FreeBSD portion written by Tomasz CEDRO (http://www.tomek.cedro.info) -""" -__author__ = 'cederom@tlen.pl' - -import os -import warnings -from power import common -import subprocess - -class PowerManagement(common.PowerManagementBase): - @staticmethod - def power_source_type(): - """ - FreeBSD use sysctl hw.acpi.acline to tell if Mains (1) is used or Battery (0). - Beware, that on a Desktop machines this hw.acpi.acline oid may not exist. - @return: One of common.POWER_TYPE_* - @raise: Runtime error if type of power source is not supported - """ - try: - supply=int(subprocess.check_output(["sysctl","-n","hw.acpi.acline"])) - except: - return common.POWER_TYPE_AC - - if supply == 1: - return common.POWER_TYPE_AC - elif supply == 0: - return common.POWER_TYPE_BATTERY - else: - raise RuntimeError("Unknown power source type!") - - - @staticmethod - def is_ac_online(): - """ - @return: True if ac is online. Otherwise False - """ - try: - supply=int(subprocess.check_output(["sysctl","-n","hw.acpi.acline"])) - except: - return True - return supply == 1 - - - @staticmethod - def is_battery_present(): - """ - TODO - @return: True if battery is present. Otherwise False - """ - return False - - - @staticmethod - def is_battery_discharging(): - """ - TODO - @return: True if ac is online. Otherwise False - """ - return False - - - @staticmethod - def get_battery_state(): - """ - TODO - @return: Tuple (energy_full, energy_now, power_now) - """ - energy_now = float(100.0) - power_now = float(100.0) - energy_full = float(100.0) - return energy_full, energy_now, power_now - - - def get_providing_power_source_type(self): - """ - Looks through all power supplies in POWER_SUPPLY_PATH. - If there is an AC adapter online returns POWER_TYPE_AC. - If there is a discharging battery, returns POWER_TYPE_BATTERY. - Since the order of supplies is arbitrary, whatever found first is returned. - """ - type = self.power_source_type() - if type == common.POWER_TYPE_AC: - if self.is_ac_online(): - return common.POWER_TYPE_AC - elif type == common.POWER_TYPE_BATTERY: - if self.is_battery_present() and self.is_battery_discharging(): - return common.POWER_TYPE_BATTERY - else: - warnings.warn("UPS is not supported.") - return common.POWER_TYPE_AC - - - def get_low_battery_warning_level(self): - """ - Looks through all power supplies in POWER_SUPPLY_PATH. - If there is an AC adapter online returns POWER_TYPE_AC returns LOW_BATTERY_WARNING_NONE. - Otherwise determines total percentage and time remaining across all attached batteries. - """ - all_energy_full = [] - all_energy_now = [] - all_power_now = [] - try: - type = self.power_source_type() - if type == common.POWER_TYPE_AC: - if self.is_ac_online(): - return common.LOW_BATTERY_WARNING_NONE - elif type == common.POWER_TYPE_BATTERY: - if self.is_battery_present() and self.is_battery_discharging(): - energy_full, energy_now, power_now = self.get_battery_state() - all_energy_full.append(energy_full) - all_energy_now.append(energy_now) - all_power_now.append(power_now) - else: - warnings.warn("UPS is not supported.") - except (RuntimeError, IOError) as e: - warnings.warn("Unable to read system power information!") - - try: - total_percentage = sum(all_energy_full) / sum(all_energy_now) - total_time = sum([energy_now / power_now * 60.0 for energy_now, power_now in zip(all_energy_now, all_power_now)]) - if total_time <= 10.0: - return common.LOW_BATTERY_WARNING_FINAL - elif total_percentage <= 22.0: - return common.LOW_BATTERY_WARNING_EARLY - else: - return common.LOW_BATTERY_WARNING_NONE - except ZeroDivisionError as e: - warnings.warn("Unable to calculate low battery level: {error}".format(error=str(e))) - return common.LOW_BATTERY_WARNING_NONE - - - def get_time_remaining_estimate(self): - """ - Looks through all power sources and returns total time remaining estimate - or TIME_REMAINING_UNLIMITED if ac power supply is online. - """ - all_energy_now = [] - all_power_now = [] - try: - type = self.power_source_type() - if type == common.POWER_TYPE_AC: - if self.is_ac_online(supply_path): - return common.TIME_REMAINING_UNLIMITED - elif type == common.POWER_TYPE_BATTERY: - if self.is_battery_present() and self.is_battery_discharging(): - energy_full, energy_now, power_now = self.get_battery_state() - all_energy_now.append(energy_now) - all_power_now.append(power_now) - else: - warnings.warn("UPS is not supported.") - except (RuntimeError, IOError) as e: - warnings.warn("Unable to read system power information!") - - if len(all_energy_now) > 0: - try: - return sum([energy_now / power_now * 60.0 for energy_now, power_now in zip(all_energy_now, all_power_now)]) - except ZeroDivisionError as e: - warnings.warn("Unable to calculate time remaining estimate: {error}".format(error=str(e))) - return common.TIME_REMAINING_UNKNOWN - else: - return common.TIME_REMAINING_UNKNOWN - - - def add_observer(self, observer): - warnings.warn("Current system does not support observing.") - pass - - - def remove_observer(self, observer): - warnings.warn("Current system does not support observing.") - pass diff --git a/power/win32.py b/power/win32.py deleted file mode 100644 index 0641753..0000000 --- a/power/win32.py +++ /dev/null @@ -1,99 +0,0 @@ -# coding=utf-8 -""" -Implements PowerManagement functions using GetSystemPowerStatus. -Requires Windows XP+. -Observing is not supported -""" -__author__ = 'kulakov.ilya@gmail.com' - -from ctypes import Structure, wintypes, POINTER, windll, WinError, pointer, WINFUNCTYPE -import warnings -from power import common - - -# GetSystemPowerStatus -# Returns brief description of current system power status. -# Windows XP+ -# REQUIRED. -GetSystemPowerStatus = None -try: - GetSystemPowerStatus = windll.kernel32.GetSystemPowerStatus - - class SYSTEM_POWER_STATUS(Structure): - _fields_ = [ - ('ACLineStatus', wintypes.c_ubyte), - ('BatteryFlag', wintypes.c_ubyte), - ('BatteryLifePercent', wintypes.c_ubyte), - ('Reserved1', wintypes.c_ubyte), - ('BatteryLifeTime', wintypes.DWORD), - ('BatteryFullLifeTime', wintypes.DWORD) - ] - - GetSystemPowerStatus.argtypes = [POINTER(SYSTEM_POWER_STATUS)] - GetSystemPowerStatus.restype = wintypes.BOOL -except AttributeError as e: - raise RuntimeError("Unable to load GetSystemPowerStatus." - "The system does not provide it (Win XP is required) or kernel32.dll is damaged.") - - -POWER_TYPE_MAP = { - 0: common.POWER_TYPE_BATTERY, - 1: common.POWER_TYPE_AC, - 255: common.POWER_TYPE_AC -} - - -class PowerManagement(common.PowerManagementBase): - def get_providing_power_source_type(self): - """ - Returns GetSystemPowerStatus().ACLineStatus - - @raise: WindowsError if any underlying error occures. - """ - power_status = SYSTEM_POWER_STATUS() - if not GetSystemPowerStatus(pointer(power_status)): - raise WinError() - return POWER_TYPE_MAP[power_status.ACLineStatus] - - def get_low_battery_warning_level(self): - """ - Returns warning according to GetSystemPowerStatus().BatteryLifeTime/BatteryLifePercent - - @raise WindowsError if any underlying error occures. - """ - power_status = SYSTEM_POWER_STATUS() - if not GetSystemPowerStatus(pointer(power_status)): - raise WinError() - - if POWER_TYPE_MAP[power_status.ACLineStatus] == common.POWER_TYPE_AC: - return common.LOW_BATTERY_WARNING_NONE - else: - if power_status.BatteryLifeTime != -1 and power_status.BatteryLifeTime <= 600: - return common.LOW_BATTERY_WARNING_FINAL - elif power_status.BatteryLifePercent <= 22: - return common.LOW_BATTERY_WARNING_EARLY - else: - return common.LOW_BATTERY_WARNING_NONE - - def get_time_remaining_estimate(self): - """ - Returns time remaining estimate according to GetSystemPowerStatus().BatteryLifeTime - """ - power_status = SYSTEM_POWER_STATUS() - if not GetSystemPowerStatus(pointer(power_status)): - raise WinError() - - if POWER_TYPE_MAP[power_status.ACLineStatus] == common.POWER_TYPE_AC: - return common.TIME_REMAINING_UNLIMITED - elif power_status.BatteryLifeTime == -1: - return common.TIME_REMAINING_UNKNOWN - else: - return float(power_status.BatteryLifeTime) / 60.0 - - def add_observer(self, observer): - warnings.warn("Current system does not support observing.") - pass - - def remove_observer(self, observer): - warnings.warn("Current system does not support observing.") - pass From c737a3b6bd13308b626c34838dbb5d61b755cbeb Mon Sep 17 00:00:00 2001 From: Oskari Rauta Date: Mon, 9 Nov 2015 11:02:39 +0200 Subject: [PATCH 3/9] Add definitions - Add some new definitions - Rename function - Insert documentation --- power/common.py | 61 ++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 53 insertions(+), 8 deletions(-) diff --git a/power/common.py b/power/common.py index 4874987..f43746a 100644 --- a/power/common.py +++ b/power/common.py @@ -24,6 +24,15 @@ @var TIME_REMAINING_UNLIMITED: Indicates that the system is connected to an external power source, without time limit. @type TIME_REMAINING_UNKNOWN: float @type TIME_REMAINING_UNLIMITED: float + +@type STATUS_UNKNOWN: int +@type STATUS_AC: int +@type STATUS_CHARGING: int +@type STATUS_DISCHARGING: int +@type STATUS_FULL: int +@type STATUS_FULL_ONAC: int + +Updated by oskari.rauta@gmail.com """ __author__ = 'kulakov.ilya@gmail.com' @@ -39,6 +48,12 @@ 'LOW_BATTERY_WARNING_FINAL', 'TIME_REMAINING_UNKNOWN', 'TIME_REMAINING_UNLIMITED', + 'STATUS_UNKNOWN', + 'STATUS_AC', + 'STATUS_CHARGING', + 'STATUS_DISCHARGING', + 'STATUS_FULL', + 'STATUS_FULL_ONAC', 'PowerManagementObserver' ] @@ -61,6 +76,17 @@ TIME_REMAINING_UNLIMITED = -2.0 +STATUS_UNKNOWN = 0 + +STATUS_AC = 1 + +STATUS_CHARGING = 2 + +STATUS_DISCHARGING = 3 + +STATUS_FULL = 4 + +STATUS_FULL_ONAC = 5 class PowerManagementBase(object): """ @@ -101,13 +127,32 @@ def get_low_battery_warning_level(self): pass @abstractmethod - def get_time_remaining_estimate(self): + def get_ac_status(self): """ - Returns the estimated minutes remaining until all power sources (battery and/or UPS) are empty. + Looks through all power sources. + + Returns AC's current status, time remaining in minutes for all power sources either to + empty or to full, whether system is charging or not and average of capacity on all + batteries (always 100 on AC only system). + + @return: Special values for status + - STATUS_UNKNOWN (0 - on error) + - STATUS_AC (1 - AC only system) + - STATUS_CHARGING (2) + - STATUS_DISCHARGING (3) + - STATUS_FULL (4 - batteries are full, system not on AC) + - STATUS_FULL_ONAC (5 - batteries are full, system is on AC) + + @rtype: int + + @return: Special values for time remaining + - TIME_REMAINING_UNKNOWN (-1.0) + - TIME_REMAINING_UNLIMITED (-2.0) + + @rtype: float + + @return: capacity - @return: Special values: - - TIME_REMAINING_UNKNOWN - - TIME_REMAINING_UNLIMITED @rtype: float """ pass @@ -182,11 +227,11 @@ def get_low_battery_warning_level(self): """ return LOW_BATTERY_WARNING_NONE - def get_time_remaining_estimate(self): + def get_ac_status(self): """ - @return: Always TIME_REMAINING_UNLIMITED + @return: Always STATUS_AC, TIME_REMAINING_UNLIMITED, 100 """ - return TIME_REMAINING_UNLIMITED + return STATUS_AC, TIME_REMAINING_UNLIMITED, 100 def add_observer(self, observer): """ From 3e3a15a35ea6ef435378d2dc326167112660b06e Mon Sep 17 00:00:00 2001 From: Oskari Rauta Date: Mon, 9 Nov 2015 11:03:27 +0200 Subject: [PATCH 4/9] code for v1.5 Update code to v1.5 of this fork --- power/linux.py | 125 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 104 insertions(+), 21 deletions(-) diff --git a/power/linux.py b/power/linux.py index e58abc2..3d0559b 100644 --- a/power/linux.py +++ b/power/linux.py @@ -2,6 +2,8 @@ """ Implements PowerManagement functions using /sys/class/power_supply/* See doc/linux for platform-specific details. + +Updated by oskari.rauta@gmail.com """ __author__ = 'kulakov.ilya@gmail.com' @@ -54,28 +56,64 @@ def is_battery_present(supply_path): with open(os.path.join(supply_path, 'present'), 'r') as present_file: return present_file.readline().strip() == '1' + @staticmethod + def is_battery_full(supply_path): + """ + @param supply_path: Path to power supply + @return: True if battery is full. Otherwise False + """ + with open(os.path.join(supply_path, 'status'), 'r') as status_file: + return status_file.readline().strip() == 'Full' + @staticmethod def is_battery_discharging(supply_path): """ @param supply_path: Path to power supply - @return: True if ac is online. Otherwise False + @return: True if battery is discharging. Otherwise False """ with open(os.path.join(supply_path, 'status'), 'r') as status_file: return status_file.readline().strip() == 'Discharging' + @staticmethod + def is_battery_charging(supply_path): + """ + @param supply_path: Path to power supply + @return: True if battery is charging. Otherwise False + """ + with open(os.path.join(supply_path, 'status'), 'r') as status_file: + return status_file.readline().strip() == 'Charging' + @staticmethod def get_battery_state(supply_path): """ @param supply_path: Path to power supply - @return: Tuple (energy_full, energy_now, power_now) + @return: Tuple (energy_full, energy_now, power_now, capacity) """ - with open(os.path.join(supply_path, 'energy_now'), 'r') as energy_now_file: - with open(os.path.join(supply_path, 'power_now'), 'r') as power_now_file: - with open(os.path.join(supply_path, 'energy_full'), 'r') as energy_full_file: - energy_now = float(energy_now_file.readline().strip()) - power_now = float(power_now_file.readline().strip()) - energy_full = float(energy_full_file.readline().strip()) - return energy_full, energy_now, power_now + + energy_now_filename = 'charge_now' + power_now_filename = 'current_now' + energy_full_filename = 'charge_full' + + if not os.path.isfile(os.path.join(supply_path, energy_now_filename)) or not os.path.isfile(os.path.join(supply_path, power_now_filename)) or not os.path.isfile(os.path.join(supply_path, energy_full_filename)): + + if os.path.isfile(os.path.join(supply_path, 'energy_now')) and os.path.isfile(os.path.join(supply_path, 'power_now')) and os.path.isfile(os.path.join(supply_path, 'energy_full')): + energy_now_filename = 'energy_now' + power_now_filename = 'power_now' + energy_full_filename = 'energy_full' + elif os.path.isfile(os.path.join(supply_path, 'energy_now')) and os.path.isfile(os.path.join(supply_path, 'current_now')) and os.path.isfile(os.path.join(supply_path, 'energy_full')): + energy_now_filename = 'energy_now' + power_now_filename = 'power_now' + energy_full_filename = 'energy_full' + + with open(os.path.join(supply_path, energy_now_filename), 'r') as energy_now_file: + with open(os.path.join(supply_path, power_now_filename), 'r') as power_now_file: + with open(os.path.join(supply_path, energy_full_filename), 'r') as energy_full_file: + with open(os.path.join(supply_path, 'capacity'), 'r') as capacity_file: + energy_now = float(energy_now_file.readline().strip()) + power_now = float(power_now_file.readline().strip()) + energy_full = float(energy_full_file.readline().strip()) + capacity = float(capacity_file.readline().strip()) + return energy_full, energy_now, power_now, capacity def get_providing_power_source_type(self): """ @@ -104,7 +142,7 @@ def get_providing_power_source_type(self): def get_low_battery_warning_level(self): """ Looks through all power supplies in POWER_SUPPLY_PATH. - If there is an AC adapter online returns POWER_TYPE_AC returns LOW_BATTERY_WARNING_NONE. + If there is an AC adapter online returns LOW_BATTERY_WARNING_NONE. Otherwise determines total percentage and time remaining across all attached batteries. """ all_energy_full = [] @@ -119,7 +157,7 @@ def get_low_battery_warning_level(self): return common.LOW_BATTERY_WARNING_NONE elif type == common.POWER_TYPE_BATTERY: if self.is_battery_present(supply_path) and self.is_battery_discharging(supply_path): - energy_full, energy_now, power_now = self.get_battery_state(supply_path) + energy_full, energy_now, power_now, capacity = self.get_battery_state(supply_path) all_energy_full.append(energy_full) all_energy_now.append(energy_now) all_power_now.append(power_now) @@ -141,38 +179,83 @@ def get_low_battery_warning_level(self): warnings.warn("Unable to calculate low battery level: {error}".format(error=str(e))) return common.LOW_BATTERY_WARNING_NONE - def get_time_remaining_estimate(self): + def get_ac_status(self): """ - Looks through all power sources and returns total time remaining estimate - or TIME_REMAINING_UNLIMITED if ac power supply is online. + Looks through all power sources. + @return: Tuple (status, time_remaining, capacity) + status in: STATUS_UNKNOWN(0, on error), STATUS_AC(AC only, 1), STATUS_CHARGING(2), + STATUS_DISCHARGING(3), STATUS_FULL(4), STATUS_FULL_ONAC(5) + time_remaining: amount of minutes left until full or empty, whether battery is being charged or is + discharging. Returns TIME_REMAINING_UNLIMITED(-2.0) when battery is full and ac is + online. TIME_REMAINING_UNKNOWN is returned on error. + capacity: battery's capacity in %'s between 0-100. If multiple batteries are found, this + will be average. 0 is returned on error. + + On AC only systems returns STATUS_AC, TIME_REMAINING_UNLIMITED, capacity 100. + On error will return STATUS_UNKNOWN, TIME_REMAINING_UNKNOWN, capacity 0. """ all_energy_now = [] all_power_now = [] + all_energy_full = [] + all_capacity = [] + battery_full = True + battery_discharging = True + ac_is_online = False for supply in os.listdir(POWER_SUPPLY_PATH): supply_path = os.path.join(POWER_SUPPLY_PATH, supply) try: type = self.power_source_type(supply_path) if type == common.POWER_TYPE_AC: if self.is_ac_online(supply_path): - return common.TIME_REMAINING_UNLIMITED + ac_is_online = True elif type == common.POWER_TYPE_BATTERY: - if self.is_battery_present(supply_path) and self.is_battery_discharging(supply_path): - energy_full, energy_now, power_now = self.get_battery_state(supply_path) + if self.is_battery_present(supply_path): + energy_full, energy_now, power_now, capacity = self.get_battery_state(supply_path) all_energy_now.append(energy_now) all_power_now.append(power_now) + all_energy_full.append(energy_full) + all_capacity.append(capacity) + if self.is_battery_charging(supply_path): + battery_discharging = False + if not self.is_battery_full(supply_path): + battery_full = False else: warnings.warn("UPS is not supported.") except (RuntimeError, IOError) as e: warnings.warn("Unable to read properties of {path}: {error}".format(path=supply_path, error=str(e))) - if len(all_energy_now) > 0: + if len(all_energy_now) == 0: + if ac_is_online: + return common.STATUS_AC, common.TIME_REMAINING_UNLIMITED, 100 + else: + return common.STATUS_UNKNOWN, common.TIME_REMAINING_UNKNOWN, 0 + + capacity = sum(all_capacity) / len(all_capacity) + + if battery_full and ac_is_online: + return common.STATUS_FULL_ONAC, common.TIME_REMAINING_UNLIMITED, capacity + elif battery_full and not ac_is_online: + try: + return common.STATUS_FULL, sum([energy_now / power_now * 60.0 for energy_now, power_now in zip(all_energy_now, all_power_now)]), capacity + except ZeroDivisionError as e: + warnings.warn("Unable to calculate time remaining estimate: {error}".format(error=str(e))) + return common.STATUS_UNKNOWN, common.TIME_REMAINING_UNKNOWN, 0 + elif battery_discharging: + try: + return common.STATUS_DISCHARGING, sum([energy_now / power_now * 60.0 for energy_now, power_now in zip(all_energy_now, all_power_now)]), capacity + except ZeroDivisionError as e: + warnings.warn("Unable to calculate time remaining estimate: {error}".format(error=str(e))) + return common.STATUS_UNKNOWN, common.TIME_REMAINING_UNKNOWN, 0 + elif not battery_discharging: + if not ac_is_online: + warnings.warn("AC is not online but battery is charging?") try: - return sum([energy_now / power_now * 60.0 for energy_now, power_now in zip(all_energy_now, all_power_now)]) + return common.STATUS_CHARGING, sum([((energy_full - energy_now) / power_now) * 60.0 for energy_full, energy_now, power_now in zip(all_energy_full, all_energy_now, all_power_now)]), capacity except ZeroDivisionError as e: warnings.warn("Unable to calculate time remaining estimate: {error}".format(error=str(e))) - return common.TIME_REMAINING_UNKNOWN + return common.STATUS_UNKNOWN, common.TIME_REMAINING_UNKNOWN, 0 else: - return common.TIME_REMAINING_UNKNOWN + return common.STATUS_UNKNOWN, common.TIME_REMAINING_UNKNOWN, 0 def add_observer(self, observer): warnings.warn("Current system does not support observing.") From 72ed014e3845517a5a78841c8855e54a9e1cead0 Mon Sep 17 00:00:00 2001 From: Oskari Rauta Date: Mon, 9 Nov 2015 11:09:09 +0200 Subject: [PATCH 5/9] Update README.md --- README.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index d2ab823..b09a380 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,11 @@ Power ===== -Crossplatform (Windows, Linux, Mac OS X, FreeBSD) python module to access power capabilities of the system. +Linux python module to access power capabilities of the system. - Power source type: AC, Battery or UPS - Battery warning level: no warning (None), less than 22% of battery (Early), less than 10min (Final) -- Time remaining estimate +- AC system status report: status code, time remaining to full/empty whether system is charging or discharging, battery capacity. - Fault tolerant: if for some reason power capabilities cannot be extracted, falls back to AC -- Support for multiple battries -- Power changes can be observed (Mac OS X only for now) -- Very easy to extand to support new features or new systems +- Support for multiple battries ( stats reported capacity is average of batteries in system with more than one battery) +- Very easy to extend to support new features or new systems From ff7a5285cf86492ba3952488fe93660a52487644 Mon Sep 17 00:00:00 2001 From: Oskari Rauta Date: Mon, 9 Nov 2015 21:48:15 +0200 Subject: [PATCH 6/9] Update setup.py --- setup.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/setup.py b/setup.py index 734295b..73a9900 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # coding=utf-8 __author__ = 'kulakov.ilya@gmail.com' -__updatedby__ = 'oskari.rauta@gmail.com' +__maintainer__ = 'oskari.rauta@gmail.com' from setuptools import setup from sys import platform @@ -9,11 +9,6 @@ REQUIREMENTS = [] - -if platform.startswith('darwin'): - REQUIREMENTS.append('pyobjc >= 2.5') - - setup( name="power", version="1.5", @@ -21,6 +16,8 @@ long_description="Library that allows you get current power source type (AC, Battery or UPS), warning level (none, <22%, <10min) and remaining minutes. You can also observe changes of power source and remaining time.", author="Ilya Kulakov", author_email="kulakov.ilya@gmail.com", + maintainer="Oskari Rauta", + maintainer_email="oskari.rauta@gmail.com", url="https://github.com/Kentzo/Power", platforms=["Linux 2.6+"], packages=['power'], From 03065c5a42e77b07f86fadbd3d728e605a67130a Mon Sep 17 00:00:00 2001 From: Oskari Rauta Date: Mon, 9 Nov 2015 22:41:44 +0200 Subject: [PATCH 7/9] Update linux.py fixed a mistype, thank you Justin Buchanan. --- power/linux.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/power/linux.py b/power/linux.py index 3d0559b..f862b3c 100644 --- a/power/linux.py +++ b/power/linux.py @@ -102,7 +102,7 @@ def get_battery_state(supply_path): energy_full_filename = 'energy_full' elif os.path.isfile(os.path.join(supply_path, 'energy_now')) and os.path.isfile(os.path.join(supply_path, 'current_now')) and os.path.isfile(os.path.join(supply_path, 'energy_full')): energy_now_filename = 'energy_now' - power_now_filename = 'power_now' + power_now_filename = 'current_now' energy_full_filename = 'energy_full' with open(os.path.join(supply_path, energy_now_filename), 'r') as energy_now_file: From b925ba19b9442c7fd593b7409d5ee25cf0d519c5 Mon Sep 17 00:00:00 2001 From: Oskari Rauta Date: Mon, 9 Nov 2015 22:43:13 +0200 Subject: [PATCH 8/9] Update common.py Added maintainer. --- power/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/power/common.py b/power/common.py index f43746a..a51f95a 100644 --- a/power/common.py +++ b/power/common.py @@ -32,9 +32,9 @@ @type STATUS_FULL: int @type STATUS_FULL_ONAC: int -Updated by oskari.rauta@gmail.com """ __author__ = 'kulakov.ilya@gmail.com' +__maintainer__ = 'oskari.rauta@gmail.com' from abc import ABCMeta, abstractmethod import weakref From a8dc97aa7b0694df6ab9dac5d7afee5a8f2936be Mon Sep 17 00:00:00 2001 From: Oskari Rauta Date: Mon, 9 Nov 2015 22:43:40 +0200 Subject: [PATCH 9/9] Update linux.py Added maintainer --- power/linux.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/power/linux.py b/power/linux.py index f862b3c..6a5b48b 100644 --- a/power/linux.py +++ b/power/linux.py @@ -3,9 +3,9 @@ Implements PowerManagement functions using /sys/class/power_supply/* See doc/linux for platform-specific details. -Updated by oskari.rauta@gmail.com """ __author__ = 'kulakov.ilya@gmail.com' +__maintainer__ = 'oskari.rauta@gmail.com' import os import warnings