Skip to content

955 battery sensors #963

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 20 commits into from
Feb 1, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,9 @@ Sensors
shwtemp(label='Core 1', current=52.0, high=100.0, critical=100.0),
shwtemp(label='Core 2', current=45.0, high=100.0, critical=100.0),
shwtemp(label='Core 3', current=47.0, high=100.0, critical=100.0)]}
>>>
>>> psutil.sensors_battery()
sbattery(percent=93, secsleft=16628, power_plugged=False)

Other system info
=================
Expand Down
50 changes: 47 additions & 3 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -641,15 +641,49 @@ Sensors
See also `temperatures.py <https://github.com/giampaolo/psutil/blob/master/scripts/temperatures.py>`__
for an example application.

Availability: Linux

.. versionadded:: 5.1.0

.. warning::

This API is experimental. Backwards incompatible changes may occur if
This API is experimental. Backward incompatible changes may occur if
deemed necessary.

Availability: Linux
.. function:: sensors_battery()

.. versionadded:: 5.1.0
Return battery status information as a namedtuple including the following
values. If no battery is installed returns ``None``.

- **percent**: battery power left as a percentage.
- **secsleft**: a rough approximation of how many seconds are left before the
battery runs out of power.
If the AC power cable is connected this is set to
:data:`psutil.POWER_TIME_UNLIMITED <psutil.POWER_TIME_UNLIMITED>`.
If it can't be determined it is set to
:data:`psutil.POWER_TIME_UNKNOWN <psutil.POWER_TIME_UNKNOWN>`.
- **power_plugged**: ``True`` if the AC power cable is connected.

Example::

>>> import psutil
>>>
>>> def secs2hours(secs):
... mm, ss = divmod(secs, 60)
... hh, mm = divmod(mm, 60)
... return "%d:%02d:%02d" % (hh, mm, ss)
...
>>> battery = psutil.sensors_battery()
>>> battery
sbattery(percent=93, secsleft=16628, power_plugged=False)
>>> print("charge = %s%%, time left = %s" % (batt.percent, secs2hours(batt.secsleft)))
charge = 93%, time left = 4:37:08

See also `battery.py <https://github.com/giampaolo/psutil/blob/master/scripts/battery.py>`__

Availability: Linux, Windows, FreeBSD

.. versionadded:: 5.1.0


Other system info
Expand Down Expand Up @@ -2022,6 +2056,16 @@ Constants

.. versionadded:: 3.0.0

.. _const-power:
.. data:: POWER_TIME_UNKNOWN
.. data:: POWER_TIME_UNLIMITED

Whether the remaining time of the battery cannot be determined or is
unlimited.
May be assigned to :func:`psutil.sensors_battery()`'s *secsleft* field.

.. versionadded:: 5.1.0

.. _const-version-info:
.. data:: version_info

Expand Down
25 changes: 24 additions & 1 deletion psutil/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,8 @@

"NIC_DUPLEX_FULL", "NIC_DUPLEX_HALF", "NIC_DUPLEX_UNKNOWN",

"POWER_TIME_UNKNOWN", "POWER_TIME_UNLIMITED",

"BSD", "FREEBSD", "LINUX", "NETBSD", "OPENBSD", "OSX", "POSIX", "SUNOS",
"WINDOWS",

Expand All @@ -185,14 +187,16 @@
"net_io_counters", "net_connections", "net_if_addrs", # network
"net_if_stats",
"disk_io_counters", "disk_partitions", "disk_usage", # disk
# "sensors_temperatures", # sensors
# "sensors_temperatures", "sensors_battery", # sensors
"users", "boot_time", # others
]
__all__.extend(_psplatform.__extra__all__)
__author__ = "Giampaolo Rodola'"
__version__ = "5.1.0"
version_info = tuple([int(num) for num in __version__.split('.')])
AF_LINK = _psplatform.AF_LINK
POWER_TIME_UNLIMITED = _common.POWER_TIME_UNLIMITED
POWER_TIME_UNKNOWN = _common.POWER_TIME_UNKNOWN
_TOTAL_PHYMEM = None
_timer = getattr(time, 'monotonic', time.time)

Expand Down Expand Up @@ -2186,6 +2190,7 @@ def net_if_stats():
# =====================================================================


# Linux
if hasattr(_psplatform, "sensors_temperatures"):

def sensors_temperatures(fahrenheit=False):
Expand Down Expand Up @@ -2225,6 +2230,24 @@ def to_fahrenheit(n):
__all__.append("sensors_temperatures")


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

def sensors_battery():
"""Return battery information. If no battery is installed
returns None.

- percent: battery power left as a percentage.
- secsleft: a rough approximation of how many seconds are left
before the battery runs out of power.
May be POWER_TIME_UNLIMITED or POWER_TIME_UNLIMITED.
- power_plugged: True if the AC power cable is connected.
"""
return _psplatform.sensors_battery()

__all__.append("sensors_battery")


# =====================================================================
# --- other system related functions
# =====================================================================
Expand Down
13 changes: 13 additions & 0 deletions psutil/_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,17 @@ class NicDuplex(enum.IntEnum):

globals().update(NicDuplex.__members__)

# sensors_battery()
if enum is None:
POWER_TIME_UNKNOWN = -1
POWER_TIME_UNLIMITED = -2
else:
class BatteryTime(enum.IntEnum):
POWER_TIME_UNKNOWN = -1
POWER_TIME_UNLIMITED = -2

globals().update(BatteryTime.__members__)


# ===================================================================
# --- namedtuples
Expand Down Expand Up @@ -161,6 +172,8 @@ class NicDuplex(enum.IntEnum):
# psutil.sensors_temperatures()
shwtemp = namedtuple(
'shwtemp', ['label', 'current', 'high', 'critical'])
# psutil.sensors_battery()
sbattery = namedtuple('sbattery', ['percent', 'secsleft', 'power_plugged'])

# --- for Process methods

Expand Down
19 changes: 19 additions & 0 deletions psutil/_psbsd.py
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,25 @@ def net_connections(kind):
return list(ret)


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


if FREEBSD:

def sensors_battery():
percent, minsleft, power_plugged = cext.sensors_battery()
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)


# =====================================================================
# --- other system functions
# =====================================================================
Expand Down
23 changes: 23 additions & 0 deletions psutil/_pslinux.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
# --- constants
# =====================================================================

POWER_SUPPLY_PATH = "/sys/class/power_supply"

HAS_SMAPS = os.path.exists('/proc/%s/smaps' % os.getpid())
HAS_PRLIMIT = hasattr(cext, "linux_prlimit")
Expand Down Expand Up @@ -1102,6 +1103,28 @@ def sensors_temperatures():
return ret


def sensors_battery():
root = os.path.join(POWER_SUPPLY_PATH, "BAT0")
if not os.path.exists(root):
return None

power_plugged = \
cat("/sys/class/power_supply/AC0/online", fallback=b"0") == b"1"
energy_now = int(cat(root + "/energy_now"))
power_now = int(cat(root + "/power_now"))
percent = int(cat(root + "/capacity"))

if power_plugged:
secsleft = _common.POWER_TIME_UNLIMITED
else:
try:
secsleft = int(energy_now / power_now * 3600)
except ZeroDivisionError:
secsleft = _common.POWER_TIME_UNKNOWN

return _common.sbattery(percent, secsleft, power_plugged)


# =====================================================================
# --- other system functions
# =====================================================================
Expand Down
4 changes: 4 additions & 0 deletions psutil/_psutil_bsd.c
Original file line number Diff line number Diff line change
Expand Up @@ -933,6 +933,10 @@ PsutilMethods[] = {
#if defined(PSUTIL_FREEBSD) || defined(PSUTIL_NETBSD)
{"net_connections", psutil_net_connections, METH_VARARGS,
"Return system-wide open connections."},
#endif
#if defined(PSUTIL_FREEBSD)
{"sensors_battery", psutil_sensors_battery, METH_VARARGS,
"Return battery information."},
#endif
{NULL, NULL, 0, NULL}
};
Expand Down
27 changes: 27 additions & 0 deletions psutil/_psutil_windows.c
Original file line number Diff line number Diff line change
Expand Up @@ -3456,6 +3456,31 @@ psutil_cpu_freq(PyObject *self, PyObject *args) {
}


/*
* Return battery usage stats.
*/
static PyObject *
psutil_sensors_battery(PyObject *self, PyObject *args) {
SYSTEM_POWER_STATUS sps;

if (GetSystemPowerStatus(&sps) == 0) {
PyErr_SetFromWindowsErr(0);
return NULL;
}
return Py_BuildValue(
"iiiI",
sps.ACLineStatus, // whether AC is connected: 0=no, 1=yes, 255=unknown
// status flag:
// 1, 2, 4 = high, low, critical
// 8 = charging
// 128 = no battery
sps.BatteryFlag,
sps.BatteryLifePercent, // percent
sps.BatteryLifeTime // remaining secs
);
}


// ------------------------ Python init ---------------------------

static PyMethodDef
Expand Down Expand Up @@ -3562,6 +3587,8 @@ PsutilMethods[] = {
"Return NICs stats."},
{"cpu_freq", psutil_cpu_freq, METH_VARARGS,
"Return CPU frequency."},
{"sensors_battery", psutil_sensors_battery, METH_VARARGS,
"Return battery metrics usage."},

// --- windows services
{"winservice_enumerate", psutil_winservice_enumerate, METH_VARARGS,
Expand Down
24 changes: 24 additions & 0 deletions psutil/_pswindows.py
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,30 @@ def net_if_addrs():
return ret


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


def sensors_battery():
# For constants meaning see:
# https://msdn.microsoft.com/en-us/library/windows/desktop/
# aa373232(v=vs.85).aspx
acline_status, flags, percent, secsleft = cext.sensors_battery()
power_plugged = acline_status == 1
no_battery = bool(flags & 128)
charging = bool(flags & 8)

if no_battery:
return None
if power_plugged or charging:
secsleft = _common.POWER_TIME_UNLIMITED
elif secsleft == -1:
secsleft = _common.POWER_TIME_UNKNOWN

return _common.sbattery(percent, secsleft, power_plugged)


# =====================================================================
# --- other system functions
# =====================================================================
Expand Down
24 changes: 24 additions & 0 deletions psutil/arch/bsd/freebsd.c
Original file line number Diff line number Diff line change
Expand Up @@ -994,3 +994,27 @@ psutil_cpu_stats(PyObject *self, PyObject *args) {
PyErr_SetFromErrno(PyExc_OSError);
return NULL;
}


/*
* Return battery information.
*/
PyObject *
psutil_sensors_battery(PyObject *self, PyObject *args) {
int percent;
int minsleft;
int power_plugged;
size_t size = sizeof(percent);

if (sysctlbyname("hw.acpi.battery.life", &percent, &size, NULL, 0))
goto error;
if (sysctlbyname("hw.acpi.battery.time", &minsleft, &size, NULL, 0))
goto error;
if (sysctlbyname("hw.acpi.acline", &power_plugged, &size, NULL, 0))
goto error;
return Py_BuildValue("iii", percent, minsleft, power_plugged);

error:
PyErr_SetFromErrno(PyExc_OSError);
return NULL;
}
3 changes: 3 additions & 0 deletions psutil/arch/bsd/freebsd.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,6 @@ PyObject* psutil_proc_threads(PyObject* self, PyObject* args);
PyObject* psutil_swap_mem(PyObject* self, PyObject* args);
PyObject* psutil_virtual_mem(PyObject* self, PyObject* args);
PyObject* psutil_cpu_stats(PyObject* self, PyObject* args);
#if defined(PSUTIL_FREEBSD)
PyObject* psutil_sensors_battery(PyObject* self, PyObject* args);
#endif
32 changes: 32 additions & 0 deletions psutil/tests/test_bsd.py
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,38 @@ def test_boot_time(self):
btime = int(s)
self.assertEqual(btime, psutil.boot_time())

# --- sensors_battery

@unittest.skipUnless(psutil.sensors_battery(), "no battery")
def test_sensors_battery(self):
def secs2hours(secs):
m, s = divmod(secs, 60)
h, m = divmod(m, 60)
return "%d:%02d" % (h, m)

out = sh("acpiconf -i 0")
fields = dict([(x.split('\t')[0], x.split('\t')[-1])
for x in out.split("\n")])
metrics = psutil.sensors_battery()
percent = int(fields['Remaining capacity:'].replace('%', ''))
remaining_time = fields['Remaining time:']
self.assertEqual(metrics.percent, percent)
if remaining_time == 'unknown':
self.assertEqual(metrics.secsleft, psutil.POWER_TIME_UNLIMITED)
else:
self.assertEqual(secs2hours(metrics.secsleft), remaining_time)

def test_sensors_battery_against_sysctl(self):
self.assertEqual(psutil.sensors_battery().percent,
sysctl("hw.acpi.battery.life"))
self.assertEqual(psutil.sensors_battery().power_plugged,
sysctl("hw.acpi.acline") == 1)
secsleft = psutil.sensors_battery().secsleft
if secsleft < 0:
self.assertEqual(sysctl("hw.acpi.battery.time"), -1)
else:
self.assertEqual(secsleft, sysctl("hw.acpi.battery.time") * 60)


# =====================================================================
# --- OpenBSD
Expand Down
Loading