Skip to content

Commit

Permalink
OSX: implement sensors_battery
Browse files Browse the repository at this point in the history
  • Loading branch information
Arnon Yaari committed Nov 19, 2017
1 parent e0df5da commit 6d36e65
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 3 deletions.
2 changes: 1 addition & 1 deletion psutil/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2210,7 +2210,7 @@ def sensors_fans():
__all__.append("sensors_fans")


# Linux, Windows, FreeBSD
# Linux, Windows, FreeBSD, OSX
if hasattr(_psplatform, "sensors_battery"):

def sensors_battery():
Expand Down
23 changes: 23 additions & 0 deletions psutil/_psosx.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,29 @@ def disk_partitions(all=False):
return retlist


# =====================================================================
# --- sensors
# =====================================================================


def sensors_battery():
"""Return battery information.
"""
try:
percent, minsleft, power_plugged = cext.sensors_battery()
except NotImplementedError:
# no power source - return None according to interface
return None
power_plugged = power_plugged == 1
if power_plugged:
secsleft = _common.POWER_TIME_UNLIMITED
elif minsleft == -1:
secsleft = _common.POWER_TIME_UNKNOWN
else:
secsleft = minsleft * 60
return _common.sbattery(percent, secsleft, power_plugged)


# =====================================================================
# --- network
# =====================================================================
Expand Down
90 changes: 90 additions & 0 deletions psutil/_psutil_osx.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
#include <IOKit/storage/IOBlockStorageDriver.h>
#include <IOKit/storage/IOMedia.h>
#include <IOKit/IOBSD.h>
#include <IOKit/ps/IOPowerSources.h>
#include <IOKit/ps/IOPSKeys.h>

#include "_psutil_common.h"
#include "_psutil_posix.h"
Expand Down Expand Up @@ -1788,6 +1790,92 @@ psutil_cpu_stats(PyObject *self, PyObject *args) {
}


/*
* Return battery information.
*/
static PyObject *
psutil_sensors_battery(PyObject *self, PyObject *args) {
PyObject *py_retlist = NULL;
CFTypeRef power_info = NULL;
CFArrayRef power_sources_list = NULL;
CFDictionaryRef power_sources_information = NULL;
CFNumberRef capacity_ref = NULL;
CFNumberRef time_to_empty_ref = NULL;
CFStringRef ps_state_ref = NULL;
uint32_t capacity; /* units are percent */
int time_to_empty; /* units are minutes */
int is_power_plugged;

power_info = IOPSCopyPowerSourcesInfo();

if (!power_info) {
PyErr_SetString(PyExc_RuntimeError,
"Failed to get power sources info");
goto error;
}

power_sources_list = IOPSCopyPowerSourcesList(power_info);
if (!power_sources_list) {
PyErr_SetString(PyExc_RuntimeError,
"Failed to get power sources list");
goto error;
}

/* Should only get one source. But in practice, check for > 0 sources */
if (!CFArrayGetCount(power_sources_list)) {
PyErr_SetString(PyExc_NotImplementedError, "no battery");
goto error;
}

power_sources_information = IOPSGetPowerSourceDescription(
power_info, CFArrayGetValueAtIndex(power_sources_list, 0));

capacity_ref = (CFNumberRef) CFDictionaryGetValue(
power_sources_information, CFSTR(kIOPSCurrentCapacityKey));
if (!CFNumberGetValue(capacity_ref, kCFNumberSInt32Type, &capacity)) {
PyErr_SetString(PyExc_RuntimeError,
"No battery capacity infomration in power sources info");
goto error;
}

ps_state_ref = (CFStringRef) CFDictionaryGetValue(
power_sources_information, CFSTR(kIOPSPowerSourceStateKey));
is_power_plugged = CFStringCompare(
ps_state_ref, CFSTR(kIOPSACPowerValue), 0)
== kCFCompareEqualTo;

time_to_empty_ref = (CFNumberRef) CFDictionaryGetValue(
power_sources_information, CFSTR(kIOPSTimeToEmptyKey));
if (!CFNumberGetValue(time_to_empty_ref,
kCFNumberIntType, &time_to_empty)) {
/* This value is recommended for non-Apple power sources, so it's not
* an error if it doesn't exist. We'll return -1 for "unknown" */
/* A value of -1 indicates "Still Calculating the Time" also for
* apple power source */
time_to_empty = -1;
}

py_retlist = Py_BuildValue("Iii",
capacity, time_to_empty, is_power_plugged);
if (!py_retlist) {
goto error;
}

CFRelease(power_info);
CFRelease(power_sources_list);
/* Caller should NOT release power_sources_information */

return py_retlist;

error:
if (power_info)
CFRelease(power_info);
if (power_sources_list)
CFRelease(power_sources_list);
Py_XDECREF(py_retlist);
return NULL;
}


/*
* define the psutil C module methods and initialize the module.
Expand Down Expand Up @@ -1854,6 +1942,8 @@ PsutilMethods[] = {
"Return currently connected users as a list of tuples"},
{"cpu_stats", psutil_cpu_stats, METH_VARARGS,
"Return CPU statistics"},
{"sensors_battery", psutil_sensors_battery, METH_VARARGS,
"Return battery information."},

// --- others
{"set_testing", psutil_set_testing, METH_NOARGS,
Expand Down
2 changes: 1 addition & 1 deletion psutil/tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@
'VERBOSITY',
"HAS_CPU_AFFINITY", "HAS_CPU_FREQ", "HAS_ENVIRON", "HAS_PROC_IO_COUNTERS",
"HAS_IONICE", "HAS_MEMORY_MAPS", "HAS_PROC_CPU_NUM", "HAS_RLIMIT",
"HAS_SENSORS_BATTERY", "HAS_BATTERY""HAS_SENSORS_FANS",
"HAS_SENSORS_BATTERY", "HAS_BATTERY", "HAS_SENSORS_FANS",
"HAS_SENSORS_TEMPERATURES", "HAS_MEMORY_FULL_INFO",
# subprocesses
'pyrun', 'reap_children', 'get_test_subprocess', 'create_zombie_proc',
Expand Down
2 changes: 1 addition & 1 deletion psutil/tests/test_contracts.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ def test_sensors_fans(self):

def test_battery(self):
self.assertEqual(hasattr(psutil, "sensors_battery"),
LINUX or WINDOWS or FREEBSD)
LINUX or WINDOWS or FREEBSD or OSX)

def test_proc_environ(self):
self.assertEqual(hasattr(psutil.Process, "environ"),
Expand Down

0 comments on commit 6d36e65

Please sign in to comment.