pax_global_header00006660000000000000000000000064124121343360014511gustar00rootroot0000000000000052 comment=1add64705141c193a277b2a9f291d9f5de31c58e power/000077500000000000000000000000001241213433600122115ustar00rootroot00000000000000power/.gitignore000066400000000000000000000004571241213433600142070ustar00rootroot00000000000000*.py[cod] # C extensions *.so # Packages *.egg *.egg-info dist build eggs parts bin var sdist develop-eggs .installed.cfg lib lib64 # Installer logs pip-log.txt # Unit test / coverage reports .coverage .tox nosetests.xml # Translations *.mo # Mr Developer .mr.developer.cfg .project .pydevproject power/README.md000066400000000000000000000032601241213433600134710ustar00rootroot00000000000000Power ===== Power is a module for python (currently supports only 2.7) that provides short, obvious and generic interface to power capabilities of the the system. It's crossplatform and supports Mac OS X, Windows and Linux. If your target system is missed, please creat new issue. Since accessing power information requires acccess to system functions, it's vital to be fault tolerant. If Power is unable to access data it needs, it returns some generic value and logs error via warnings module. Using warnings module makes it easier for you to turn generic values into exceptions to suite your needs. Current feature-set allows you to get the following information about power: - Power source type - Battery warning level - Time remaining estimate Since the system may have more than one battery, Power should take that into account and calculate some avergae. Current implementation just trusts system functions where possible (Mac OS X, Windows) or uses some naive approach (Linux). You're encouraged to review code and ask questions (by creating issues) if you feel it may affect behavior of your app. Power also includes generic interface to create observers for changes in power configuration. E.g. when you attach/detach battery, connect system to power wall or battery warning level is changed. This feature is only supported in Mac OS X. It's also possible to add support for Windows 8 on demand (create an issue). Research is needed to add this feature to other systems. If your application targets multiple systems, it probably would be easier to just use timer. Note to set timer to some reasonable long value like 5min, because Power should be efficient in terms of power (obviously). power/power/000077500000000000000000000000001241213433600133455ustar00rootroot00000000000000power/power/__init__.py000066400000000000000000000027261241213433600154650ustar00rootroot00000000000000# coding=utf-8 """ Provides crossplatform checking of current power source, battery warning level and battery time remaining estimate. Allows you to add observer for power notifications if platform supports it. Usage: from power import PowerManagement, PowerManagementObserver # Automatically imports platform-specific implementation class Observer(PowerManagementObserver): def on_power_sources_change(self, power_management): print "Power sources did change." def on_time_remaining_change(self, power_management): print "Time remaining did change." # class Observer(object): # ... # PowerManagementObserver.register(Observer) """ __version__ = '1.3' 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)) 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)) from power.common import PowerManagementNoop as PowerManagement power/power/common.py000066400000000000000000000133051241213433600152110ustar00rootroot00000000000000# coding=utf-8 """ Represents common constants and classes for all platforms. @group Power Source Type: POWER_TYPE_AC, POWER_TYPE_BATTERY, POWER_TYPE_UPS @var POWER_TYPE_AC: The system is connected to the external power source. @var POWER_TYPE_BATTERY: The system is connected to the battery. @var POWER_TYPE_UPS: The system is connected to UPS. @type POWER_TYPE_BATTERY: int @type POWER_TYPE_AC: int @type POWER_TYPE_UPS: int @group Low Battery Warning Levels: LOW_BATTERY_WARNING_NONE, LOW_BATTERY_WARNING_EARLY, LOW_BATTERY_WARNING_FINAL @var LOW_BATTERY_WARNING_NONE: The system is connected to the unlimited power source. @var LOW_BATTERY_WARNING_EARLY: The battery has dropped below 22% remaining power. @var LOW_BATTERY_WARNING_FINAL: The battery can provide no more than 10 minutes of runtime. @type LOW_BATTERY_WARNING_EARLY: int @type LOW_BATTERY_WARNING_NONE: int @type LOW_BATTERY_WARNING_FINAL: int @group Special Values For Time Remaining: TIME_REMAINING_UNKNOWN, TIME_REMAINING_UNLIMITED @var TIME_REMAINING_UNKNOWN: Indicates the system is connected to a limited power source, but system is still calculating a time remaining estimate. @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 """ __author__ = 'kulakov.ilya@gmail.com' from abc import ABCMeta, abstractmethod import weakref __all__ = [ 'POWER_TYPE_AC', 'POWER_TYPE_BATTERY', 'POWER_TYPE_UPS', 'LOW_BATTERY_WARNING_NONE', 'LOW_BATTERY_WARNING_EARLY', 'LOW_BATTERY_WARNING_FINAL', 'TIME_REMAINING_UNKNOWN', 'TIME_REMAINING_UNLIMITED', 'PowerManagementObserver' ] POWER_TYPE_AC = 0 POWER_TYPE_BATTERY = 1 POWER_TYPE_UPS = 2 LOW_BATTERY_WARNING_NONE = 1 LOW_BATTERY_WARNING_EARLY = 2 LOW_BATTERY_WARNING_FINAL = 3 TIME_REMAINING_UNKNOWN = -1.0 TIME_REMAINING_UNLIMITED = -2.0 class PowerManagementBase(object): """ Base class for platform dependent PowerManagement functions. @ivar _weak_observers: List of weak reference to added observers @note: Platform's implementation may provide additional parameters for initialization """ __metaclass__ = ABCMeta def __init__(self): self._weak_observers = [] @abstractmethod def get_providing_power_source_type(self): """ Returns type of the providing power source. @return: Possible values: - POWER_TYPE_AC - POWER_TYPE_BATTERY - POWER_TYPE_UPS @rtype: int """ pass @abstractmethod def get_low_battery_warning_level(self): """ Returns the system battery warning level. @return: Possible values: - LOW_BATTERY_WARNING_NONE - LOW_BATTERY_WARNING_EARLY - LOW_BATTERY_WARNING_FINAL @rtype: int """ pass @abstractmethod def get_time_remaining_estimate(self): """ Returns the estimated minutes remaining until all power sources (battery and/or UPS) are empty. @return: Special values: - TIME_REMAINING_UNKNOWN - TIME_REMAINING_UNLIMITED @rtype: float """ pass @abstractmethod def add_observer(self, observer): """ Adds weak ref to an observer. @param observer: Instance of class registered with PowerManagementObserver @raise TypeError: If observer is not registered with PowerManagementObserver abstract class """ if not isinstance(observer, PowerManagementObserver): raise TypeError("observer MUST conform to power.PowerManagementObserver") self._weak_observers.append(weakref.ref(observer)) @abstractmethod def remove_observer(self, observer): """ Removes an observer. @param observer: Previously added observer """ self._weak_observers.remove(weakref.ref(observer)) def remove_all_observers(self): """ Removes all registered observers. """ for weak_observer in self._weak_observers: observer = weak_observer() if observer: self.remove_observer(observer) class PowerManagementObserver: """ Base class for PowerManagement observers. Do not make assumptions in what thread or event loop these methods are called. """ __metaclass__ = ABCMeta @abstractmethod def on_power_sources_change(self, power_management): """ @param power_management: Instance of PowerManagement posted notification """ pass @abstractmethod def on_time_remaining_change(self, power_management): """ @param power_management: Instance of PowerManagement posted notification """ pass class PowerManagementNoop(PowerManagementBase): """ No-op subclass of PowerManagement. It operates like AC is always attached and power sources are never changed. """ def get_providing_power_source_type(self): """ @return: Always POWER_TYPE_AC """ return POWER_TYPE_AC def get_low_battery_warning_level(self): """ @return: Always LOW_BATTERY_WARNING_NONE """ return LOW_BATTERY_WARNING_NONE def get_time_remaining_estimate(self): """ @return: Always TIME_REMAINING_UNLIMITED """ return TIME_REMAINING_UNLIMITED def add_observer(self, observer): """ Does nothing. """ pass def remove_observer(self, observer): """ Does nothing. """ pass def remove_all_observers(self): """ Does nothing. """ pass power/power/darwin.py000066400000000000000000000374071241213433600152160ustar00rootroot00000000000000# 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 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 power/power/freebsd.py000066400000000000000000000142241241213433600153340ustar00rootroot00000000000000# 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 power/power/linux.py000066400000000000000000000177641241213433600150750ustar00rootroot00000000000000# coding=utf-8 """ Implements PowerManagement functions using /sys/class/power_supply/* See doc/linux for platform-specific details. """ __author__ = 'kulakov.ilya@gmail.com' import os import warnings from power import common POWER_SUPPLY_PATH = '/sys/class/power_supply' if not os.access(POWER_SUPPLY_PATH, os.R_OK): raise RuntimeError("Unable to read {path}.".format(path=POWER_SUPPLY_PATH)) class PowerManagement(common.PowerManagementBase): @staticmethod def power_source_type(supply_path): """ @param supply_path: Path to power supply @return: One of common.POWER_TYPE_* @raise: Runtime error if type of power source is not supported """ with open(os.path.join(supply_path, 'type'), 'r') as type_file: type = type_file.readline().strip() if type == 'Mains': return common.POWER_TYPE_AC elif type == 'UPS': return common.POWER_TYPE_UPS elif type == 'Battery': return common.POWER_TYPE_BATTERY else: raise RuntimeError("Type of {path} ({type}) is not supported".format(path=supply_path, type=type)) @staticmethod def is_ac_online(supply_path): """ @param supply_path: Path to power supply @return: True if ac is online. Otherwise False """ with open(os.path.join(supply_path, 'online'), 'r') as online_file: return online_file.readline().strip() == '1' @staticmethod def is_battery_present(supply_path): """ @param supply_path: Path to power supply @return: True if battery is present. Otherwise False """ with open(os.path.join(supply_path, 'present'), 'r') as present_file: return present_file.readline().strip() == '1' @staticmethod def is_battery_discharging(supply_path): """ @param supply_path: Path to power supply @return: True if ac is online. Otherwise False """ with open(os.path.join(supply_path, 'status'), 'r') as status_file: return status_file.readline().strip() == 'Discharging' @staticmethod def get_battery_state(supply_path): """ @param supply_path: Path to power supply @return: Tuple (energy_full, energy_now, power_now) """ 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 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. """ 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.POWER_TYPE_AC elif type == common.POWER_TYPE_BATTERY: if self.is_battery_present(supply_path) and self.is_battery_discharging(supply_path): return common.POWER_TYPE_BATTERY 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))) 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 = [] 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.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) 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 properties of {path}: {error}".format(path=supply_path, error=str(e))) 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 = [] 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 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) 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 properties of {path}: {error}".format(path=supply_path, error=str(e))) 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 power/power/tests.py000066400000000000000000000030331241213433600150600ustar00rootroot00000000000000# coding=utf-8 __author__ = 'kulakov.ilya@gmail.com' import unittest import power class TestPowerManagementCommon(unittest.TestCase): def testGetLowBatteryWarningLevel(self): level = power.PowerManagement().get_low_battery_warning_level() self.assertIsNotNone(level) self.assertIsInstance(level, int) self.assertIn(level, [power.LOW_BATTERY_WARNING_NONE, power.LOW_BATTERY_WARNING_EARLY, power.LOW_BATTERY_WARNING_FINAL]) def testGetRemainingEstimate(self): estimate = power.PowerManagement().get_time_remaining_estimate() self.assertIsNotNone(estimate) self.assertIsInstance(estimate, float) self.assertTrue(estimate == -1.0 or estimate == -2.0 or estimate >= 0.0) def testGetProvidingPowerSource(self): type = power.PowerManagement().get_providing_power_source_type() self.assertIsNotNone(type) self.assertIsInstance(type, int) self.assertIn(type, [power.POWER_TYPE_AC, power.POWER_TYPE_BATTERY, power.POWER_TYPE_UPS]) class TestObserver(power.PowerManagementObserver): def on_power_sources_change(self, power_management): print "on_power_sources_change" def on_time_remaining_change(self, power_management): print "on_time_remaining_change" if __name__ == "__main__": o = TestObserver() p = power.PowerManagement() p.add_observer(o) try: print "Power management observer is registered" import time while True: time.sleep(1) finally: p.remove_observer(o) power/power/win32.py000066400000000000000000000065371241213433600146740ustar00rootroot00000000000000# 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 power/setup.py000066400000000000000000000024471241213433600137320ustar00rootroot00000000000000#!/usr/bin/env python # coding=utf-8 __author__ = 'kulakov.ilya@gmail.com' from setuptools import setup from sys import platform REQUIREMENTS = [] if platform.startswith('darwin'): REQUIREMENTS.append('pyobjc >= 2.5') setup( name="power", version="1.3", 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"], packages=['power'], license="MIT License", classifiers=[ 'Intended Audience :: Developers', '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', 'Topic :: System :: Power (UPS)', ], install_requires=REQUIREMENTS )