955 battery sensors #963

Merged
merged 20 commits into from Feb 1, 2017
Jump to file or symbol
Failed to load files and symbols.
+447 −7
Diff settings

Always

Just for now

View
@@ -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
=================
View
@@ -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
@@ -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
View
@@ -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",
@@ -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)
@@ -2186,6 +2190,7 @@ def net_if_stats():
# =====================================================================
+# Linux
if hasattr(_psplatform, "sensors_temperatures"):
def sensors_temperatures(fahrenheit=False):
@@ -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
# =====================================================================
View
@@ -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
@@ -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
View
@@ -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
# =====================================================================
View
@@ -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")
@@ -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
# =====================================================================
View
@@ -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}
};
View
@@ -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
@@ -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,
View
@@ -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
# =====================================================================
View
@@ -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;
+}
@@ -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
View
@@ -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
Oops, something went wrong.