From c30bbe360cf05f0b562685840361738f7ba8a7a8 Mon Sep 17 00:00:00 2001 From: Benny Baumann Date: Sun, 19 Apr 2026 19:04:38 +0200 Subject: [PATCH 01/11] Initial refactor for BatteryMeter API extensibility --- BatteryMeter.c | 16 +++++++----- BatteryMeter.h | 6 +++++ darwin/Platform.c | 14 +++++----- darwin/Platform.h | 2 +- dragonflybsd/Platform.c | 19 +++++++------- dragonflybsd/Platform.h | 2 +- freebsd/Platform.c | 19 +++++++------- freebsd/Platform.h | 2 +- linux/Platform.c | 58 ++++++++++++++++++++++------------------- linux/Platform.h | 2 +- netbsd/Platform.c | 15 ++++++----- netbsd/Platform.h | 2 +- openbsd/Platform.c | 18 +++++++------ openbsd/Platform.h | 2 +- pcp/Platform.c | 8 +++--- pcp/Platform.h | 2 +- solaris/Platform.c | 8 +++--- solaris/Platform.h | 2 +- unsupported/Platform.c | 8 +++--- unsupported/Platform.h | 2 +- 20 files changed, 117 insertions(+), 90 deletions(-) diff --git a/BatteryMeter.c b/BatteryMeter.c index 7ab1bac97..a147ad492 100644 --- a/BatteryMeter.c +++ b/BatteryMeter.c @@ -25,21 +25,23 @@ static const int BatteryMeter_attributes[] = { }; static void BatteryMeter_updateValues(Meter* this) { - ACPresence isOnAC; - double percent; + BatteryInfo info = { + .ac = AC_ERROR, + .percent = NAN, + }; - Platform_getBattery(&percent, &isOnAC); + Platform_getBattery(&info); - if (!isNonnegative(percent)) { + if (!isNonnegative(info.percent)) { this->values[0] = NAN; xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "N/A"); return; } - this->values[0] = percent; + this->values[0] = info.percent; const char* text; - switch (isOnAC) { + switch (info.ac) { case AC_PRESENT: text = this->mode == TEXT_METERMODE ? " (Running on A/C)" : "(A/C)"; break; @@ -52,7 +54,7 @@ static void BatteryMeter_updateValues(Meter* this) { break; } - xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "%.1f%%%s", percent, text); + xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "%.1f%%%s", info.percent, text); } const MeterClass BatteryMeter_class = { diff --git a/BatteryMeter.h b/BatteryMeter.h index d0818b701..5dc0ab39d 100644 --- a/BatteryMeter.h +++ b/BatteryMeter.h @@ -18,6 +18,12 @@ typedef enum ACPresence_ { AC_ERROR } ACPresence; +typedef struct BatteryInfo_ { + ACPresence ac; + + double percent; /* [0..100], NAN if unknown */ +} BatteryInfo; + extern const MeterClass BatteryMeter_class; #endif diff --git a/darwin/Platform.c b/darwin/Platform.c index 2e83f46fa..bcef3b04a 100644 --- a/darwin/Platform.c +++ b/darwin/Platform.c @@ -677,9 +677,11 @@ bool Platform_getNetworkIO(NetworkIOData* data) { return true; } -void Platform_getBattery(double* percent, ACPresence* isOnAC) { - *percent = NAN; - *isOnAC = AC_ERROR; +void Platform_getBattery(BatteryInfo* info) { + *info = (BatteryInfo) { + .ac = AC_ERROR, + .percent = NAN, + }; CFArrayRef list = NULL; @@ -710,8 +712,8 @@ void Platform_getBattery(double* percent, ACPresence* isOnAC) { /* Determine the AC state */ CFStringRef power_state = CFDictionaryGetValue(power_source, CFSTR(kIOPSPowerSourceStateKey)); - if (*isOnAC != AC_PRESENT) - *isOnAC = (kCFCompareEqualTo == CFStringCompare(power_state, CFSTR(kIOPSACPowerValue), 0)) ? AC_PRESENT : AC_ABSENT; + if (info->ac != AC_PRESENT) + info->ac = (kCFCompareEqualTo == CFStringCompare(power_state, CFSTR(kIOPSACPowerValue), 0)) ? AC_PRESENT : AC_ABSENT; /* Get the percentage remaining */ double tmp; @@ -722,7 +724,7 @@ void Platform_getBattery(double* percent, ACPresence* isOnAC) { } if (cap_max > 0.0) - *percent = 100.0 * cap_current / cap_max; + info->percent = 100.0 * cap_current / cap_max; cleanup: if (list) diff --git a/darwin/Platform.h b/darwin/Platform.h index 387c17f12..18b26a2fc 100644 --- a/darwin/Platform.h +++ b/darwin/Platform.h @@ -83,7 +83,7 @@ bool Platform_getDiskIO(DiskIOData* data); bool Platform_getNetworkIO(NetworkIOData* data); -void Platform_getBattery(double* percent, ACPresence* isOnAC); +void Platform_getBattery(BatteryInfo* info); static inline void Platform_getHostname(char* buffer, size_t size) { Generic_hostname(buffer, size); diff --git a/dragonflybsd/Platform.c b/dragonflybsd/Platform.c index 77f72d64c..37e25f7c2 100644 --- a/dragonflybsd/Platform.c +++ b/dragonflybsd/Platform.c @@ -363,18 +363,19 @@ bool Platform_getNetworkIO(NetworkIOData* data) { return true; } -void Platform_getBattery(double* percent, ACPresence* isOnAC) { +void Platform_getBattery(BatteryInfo* info) { + *info = (BatteryInfo) { + .ac = AC_ERROR, + .percent = NAN, + }; + int life; size_t life_len = sizeof(life); - if (sysctlbyname("hw.acpi.battery.life", &life, &life_len, NULL, 0) == -1) - *percent = NAN; - else - *percent = life; + if (sysctlbyname("hw.acpi.battery.life", &life, &life_len, NULL, 0) != -1) + info->percent = life; int acline; size_t acline_len = sizeof(acline); - if (sysctlbyname("hw.acpi.acline", &acline, &acline_len, NULL, 0) == -1) - *isOnAC = AC_ERROR; - else - *isOnAC = acline == 0 ? AC_ABSENT : AC_PRESENT; + if (sysctlbyname("hw.acpi.acline", &acline, &acline_len, NULL, 0) != -1) + info->ac = (acline == 0) ? AC_ABSENT : AC_PRESENT; } diff --git a/dragonflybsd/Platform.h b/dragonflybsd/Platform.h index 67b34c0c7..ae7cc7ebc 100644 --- a/dragonflybsd/Platform.h +++ b/dragonflybsd/Platform.h @@ -71,7 +71,7 @@ bool Platform_getDiskIO(DiskIOData* data); bool Platform_getNetworkIO(NetworkIOData* data); -void Platform_getBattery(double* percent, ACPresence* isOnAC); +void Platform_getBattery(BatteryInfo* info); static inline void Platform_getHostname(char* buffer, size_t size) { Generic_hostname(buffer, size); diff --git a/freebsd/Platform.c b/freebsd/Platform.c index cc7563193..d9cbcc1a0 100644 --- a/freebsd/Platform.c +++ b/freebsd/Platform.c @@ -396,18 +396,19 @@ bool Platform_getNetworkIO(NetworkIOData* data) { return true; } -void Platform_getBattery(double* percent, ACPresence* isOnAC) { +void Platform_getBattery(BatteryInfo* info) { + *info = (BatteryInfo) { + .ac = AC_ERROR, + .percent = NAN, + }; + int life; size_t life_len = sizeof(life); - if (sysctlbyname("hw.acpi.battery.life", &life, &life_len, NULL, 0) == -1) - *percent = NAN; - else - *percent = life; + if (sysctlbyname("hw.acpi.battery.life", &life, &life_len, NULL, 0) != -1) + info->percent = life; int acline; size_t acline_len = sizeof(acline); - if (sysctlbyname("hw.acpi.acline", &acline, &acline_len, NULL, 0) == -1) - *isOnAC = AC_ERROR; - else - *isOnAC = acline == 0 ? AC_ABSENT : AC_PRESENT; + if (sysctlbyname("hw.acpi.acline", &acline, &acline_len, NULL, 0) != -1) + info->ac = (acline == 0) ? AC_ABSENT : AC_PRESENT; } diff --git a/freebsd/Platform.h b/freebsd/Platform.h index 13adf7bb7..8932291c1 100644 --- a/freebsd/Platform.h +++ b/freebsd/Platform.h @@ -71,7 +71,7 @@ bool Platform_getDiskIO(DiskIOData* data); bool Platform_getNetworkIO(NetworkIOData* data); -void Platform_getBattery(double* percent, ACPresence* isOnAC); +void Platform_getBattery(BatteryInfo* info); static inline void Platform_getHostname(char* buffer, size_t size) { Generic_hostname(buffer, size); diff --git a/linux/Platform.c b/linux/Platform.c index cecaf3b5d..44b5e6a32 100644 --- a/linux/Platform.c +++ b/linux/Platform.c @@ -162,8 +162,10 @@ const unsigned int Platform_numberOfMemoryClasses = ARRAYSIZE(Platform_memoryCla static enum { BAT_PROC, BAT_SYS, BAT_ERR } Platform_Battery_method = BAT_PROC; static time_t Platform_Battery_cacheTime; -static double Platform_Battery_cachePercent = NAN; -static ACPresence Platform_Battery_cacheIsOnAC; +static BatteryInfo Platform_Battery_cache = { + .ac = AC_ERROR, + .percent = NAN, +}; #ifdef HAVE_LIBCAP static enum CapMode Platform_capabilitiesMode = CAP_MODE_BASIC; @@ -833,18 +835,18 @@ static ACPresence procAcpiCheck(void) { return String_eq(buffer, "on-line") ? AC_PRESENT : AC_ABSENT; } -static void Platform_Battery_getProcData(double* percent, ACPresence* isOnAC) { - *isOnAC = procAcpiCheck(); - *percent = AC_ERROR != *isOnAC ? Platform_Battery_getProcBatInfo() : NAN; +static void Platform_Battery_getProcData(BatteryInfo* info) { + info->ac = procAcpiCheck(); + info->percent = AC_ERROR != info->ac ? Platform_Battery_getProcBatInfo() : NAN; } // ---------------------------------------- // READ FROM /sys // ---------------------------------------- -static void Platform_Battery_getSysData(double* percent, ACPresence* isOnAC) { - *percent = NAN; - *isOnAC = AC_ERROR; +static void Platform_Battery_getSysData(BatteryInfo* info) { + info->percent = NAN; + info->ac = AC_ERROR; DIR* dir = opendir(SYS_POWERSUPPLY_DIR); if (!dir) @@ -936,20 +938,20 @@ static void Platform_Battery_getSysData(double* percent, ACPresence* isOnAC) { totalRemain += capacityLevel * fullCharge; } else if (type == AC) { - if (*isOnAC != AC_ERROR) + if (info->ac != AC_ERROR) goto next; char buffer[2]; ssize_t r = Compat_readfileat(entryFd, "online", buffer, sizeof(buffer)); if (r < 1) { - *isOnAC = AC_ERROR; + info->ac = AC_ERROR; goto next; } if (buffer[0] == '0') - *isOnAC = AC_ABSENT; + info->ac = AC_ABSENT; else if (buffer[0] == '1') - *isOnAC = AC_PRESENT; + info->ac = AC_PRESENT; } next: @@ -958,37 +960,39 @@ static void Platform_Battery_getSysData(double* percent, ACPresence* isOnAC) { closedir(dir); - *percent = totalFull > 0 ? ((double) totalRemain * 100.0) / (double) totalFull : NAN; + info->percent = totalFull > 0 ? ((double) totalRemain * 100.0) / (double) totalFull : NAN; } -void Platform_getBattery(double* percent, ACPresence* isOnAC) { +void Platform_getBattery(BatteryInfo* info) { time_t now = time(NULL); // update battery reading is slow. Update it each 10 seconds only. if (now < Platform_Battery_cacheTime + 10) { - *percent = Platform_Battery_cachePercent; - *isOnAC = Platform_Battery_cacheIsOnAC; + *info = Platform_Battery_cache; return; } + Platform_Battery_cache = (BatteryInfo) { + .ac = AC_ERROR, + .percent = NAN, + }; + if (Platform_Battery_method == BAT_PROC) { - Platform_Battery_getProcData(percent, isOnAC); - if (!isNonnegative(*percent)) + Platform_Battery_getProcData(&Platform_Battery_cache); + if (!isNonnegative(Platform_Battery_cache.percent)) Platform_Battery_method = BAT_SYS; } if (Platform_Battery_method == BAT_SYS) { - Platform_Battery_getSysData(percent, isOnAC); - if (!isNonnegative(*percent)) + Platform_Battery_getSysData(&Platform_Battery_cache); + if (!isNonnegative(Platform_Battery_cache.percent)) Platform_Battery_method = BAT_ERR; } - if (Platform_Battery_method == BAT_ERR) { - *percent = NAN; - *isOnAC = AC_ERROR; - } else { - *percent = CLAMP(*percent, 0.0, 100.0); + if (Platform_Battery_method != BAT_ERR) { + Platform_Battery_cache.percent = CLAMP(Platform_Battery_cache.percent, 0.0, 100.0); } - Platform_Battery_cachePercent = *percent; - Platform_Battery_cacheIsOnAC = *isOnAC; + Platform_Battery_cacheTime = now; + + *info = Platform_Battery_cache; } void Platform_longOptionsUsage(const char* name) diff --git a/linux/Platform.h b/linux/Platform.h index d91856342..b5da0c97d 100644 --- a/linux/Platform.h +++ b/linux/Platform.h @@ -92,7 +92,7 @@ bool Platform_getDiskIO(DiskIOData* data); bool Platform_getNetworkIO(NetworkIOData* data); -void Platform_getBattery(double* percent, ACPresence* isOnAC); +void Platform_getBattery(BatteryInfo* info); static inline void Platform_getHostname(char* buffer, size_t size) { Generic_hostname(buffer, size); diff --git a/netbsd/Platform.c b/netbsd/Platform.c index 5f2a716f8..7f8fdf096 100644 --- a/netbsd/Platform.c +++ b/netbsd/Platform.c @@ -446,15 +446,17 @@ bool Platform_getNetworkIO(NetworkIOData* data) { return true; } -void Platform_getBattery(double* percent, ACPresence* isOnAC) { +void Platform_getBattery(BatteryInfo* info) { prop_dictionary_t dict, fields, props; prop_object_t device, class; intmax_t totalCharge = 0; intmax_t totalCapacity = 0; - *percent = NAN; - *isOnAC = AC_ERROR; + *info = (BatteryInfo) { + .ac = AC_ERROR, + .percent = NAN, + }; int fd = open(_PATH_SYSMON, O_RDONLY); if (fd == -1) @@ -522,11 +524,12 @@ void Platform_getBattery(double* percent, ACPresence* isOnAC) { totalCapacity += maxCharge; } - if (isACAdapter && *isOnAC != AC_PRESENT) { - *isOnAC = isConnected ? AC_PRESENT : AC_ABSENT; + if (isACAdapter && info->ac != AC_PRESENT) { + info->ac = isConnected ? AC_PRESENT : AC_ABSENT; } } - *percent = totalCapacity > 0 ? ((double)totalCharge / (double)totalCapacity) * 100.0 : NAN; + + info->percent = totalCapacity > 0 ? ((double)totalCharge / (double)totalCapacity) * 100.0 : NAN; error: if (fd != -1) diff --git a/netbsd/Platform.h b/netbsd/Platform.h index 3c4d77079..3569b3986 100644 --- a/netbsd/Platform.h +++ b/netbsd/Platform.h @@ -77,7 +77,7 @@ bool Platform_getDiskIO(DiskIOData* data); bool Platform_getNetworkIO(NetworkIOData* data); -void Platform_getBattery(double* percent, ACPresence* isOnAC); +void Platform_getBattery(BatteryInfo* info); static inline void Platform_getHostname(char* buffer, size_t size) { Generic_hostname(buffer, size); diff --git a/openbsd/Platform.c b/openbsd/Platform.c index 066e0672b..a06b0e64c 100644 --- a/openbsd/Platform.c +++ b/openbsd/Platform.c @@ -373,16 +373,20 @@ static bool findDevice(const char* name, int* mib, struct sensordev* snsrdev, si } } -void Platform_getBattery(double* percent, ACPresence* isOnAC) { +void Platform_getBattery(BatteryInfo* info) { int mib[] = {CTL_HW, HW_SENSORS, 0, 0, 0}; struct sensor s; size_t slen = sizeof(struct sensor); struct sensordev snsrdev; size_t sdlen = sizeof(struct sensordev); + *info = (BatteryInfo) { + .ac = AC_ERROR, + .percent = NAN, + }; + bool found = findDevice("acpibat0", mib, &snsrdev, &sdlen); - *percent = NAN; if (found) { /* See "sys/dev/acpi/acpibat.c" of OpenBSD source code for the indices of the last field. */ @@ -396,23 +400,21 @@ void Platform_getBattery(double* percent, ACPresence* isOnAC) { mib[4] = 3; /* "remaining capacity" */ if (sysctl(mib, 5, &s, &slen, NULL, 0) != -1) { double charge = s.value; - *percent = 100 * (charge / last_full_capacity); - if (charge >= last_full_capacity) { - *percent = 100; - } + info->percent = 100 * (charge / last_full_capacity); + if (charge >= last_full_capacity) + info->percent = 100; } } } found = findDevice("acpiac0", mib, &snsrdev, &sdlen); - *isOnAC = AC_ERROR; if (found) { /* See "sys/dev/acpi/acpiac.c" of OpenBSD source code. There is only one "sensor" for this device. */ mib[3] = SENSOR_INDICATOR; mib[4] = 0; /* "power supply" (status indicator) */ if (sysctl(mib, 5, &s, &slen, NULL, 0) != -1) - *isOnAC = s.value != 0 ? AC_PRESENT : AC_ABSENT; + info->ac = s.value != 0 ? AC_PRESENT : AC_ABSENT; } } diff --git a/openbsd/Platform.h b/openbsd/Platform.h index ba6519577..7a9bf4d5b 100644 --- a/openbsd/Platform.h +++ b/openbsd/Platform.h @@ -69,7 +69,7 @@ bool Platform_getDiskIO(DiskIOData* data); bool Platform_getNetworkIO(NetworkIOData* data); -void Platform_getBattery(double* percent, ACPresence* isOnAC); +void Platform_getBattery(BatteryInfo* info); static inline void Platform_getHostname(char* buffer, size_t size) { Generic_hostname(buffer, size); diff --git a/pcp/Platform.c b/pcp/Platform.c index c335b379d..e6bf62f9c 100644 --- a/pcp/Platform.c +++ b/pcp/Platform.c @@ -863,9 +863,11 @@ void Platform_getFileDescriptors(double* used, double* max) { *max = value.l; } -void Platform_getBattery(double* level, ACPresence* isOnAC) { - *level = NAN; - *isOnAC = AC_ERROR; +void Platform_getBattery(BatteryInfo* info) { + *info = (BatteryInfo) { + .ac = AC_ERROR, + .percent = NAN, + }; } const char* Platform_getFailedState(void) { diff --git a/pcp/Platform.h b/pcp/Platform.h index 611828907..5ef457c1c 100644 --- a/pcp/Platform.h +++ b/pcp/Platform.h @@ -121,7 +121,7 @@ bool Platform_getDiskIO(DiskIOData* data); bool Platform_getNetworkIO(NetworkIOData* data); -void Platform_getBattery(double* percent, ACPresence* isOnAC); +void Platform_getBattery(BatteryInfo* info); void Platform_getHostname(char* buffer, size_t size); diff --git a/solaris/Platform.c b/solaris/Platform.c index b23528d3a..c27fa7740 100644 --- a/solaris/Platform.c +++ b/solaris/Platform.c @@ -342,7 +342,9 @@ bool Platform_getNetworkIO(NetworkIOData* data) { return false; } -void Platform_getBattery(double* percent, ACPresence* isOnAC) { - *percent = NAN; - *isOnAC = AC_ERROR; +void Platform_getBattery(BatteryInfo* info) { + *info = (BatteryInfo) { + .ac = AC_ERROR, + .percent = NAN, + }; } diff --git a/solaris/Platform.h b/solaris/Platform.h index 36e95a0ee..ea3d991c9 100644 --- a/solaris/Platform.h +++ b/solaris/Platform.h @@ -97,7 +97,7 @@ bool Platform_getDiskIO(DiskIOData* data); bool Platform_getNetworkIO(NetworkIOData* data); -void Platform_getBattery(double* percent, ACPresence* isOnAC); +void Platform_getBattery(BatteryInfo* info); static inline void Platform_getHostname(char* buffer, size_t size) { Generic_hostname(buffer, size); diff --git a/unsupported/Platform.c b/unsupported/Platform.c index a5f19cf0f..80ee8065a 100644 --- a/unsupported/Platform.c +++ b/unsupported/Platform.c @@ -166,9 +166,11 @@ bool Platform_getNetworkIO(NetworkIOData* data) { return false; } -void Platform_getBattery(double* percent, ACPresence* isOnAC) { - *percent = NAN; - *isOnAC = AC_ERROR; +void Platform_getBattery(BatteryInfo* info) { + *info = (BatteryInfo) { + .ac = AC_ERROR, + .percent = NAN, + }; } void Platform_getHostname(char* buffer, size_t size) { diff --git a/unsupported/Platform.h b/unsupported/Platform.h index ece032f3a..3ba3ea0c3 100644 --- a/unsupported/Platform.h +++ b/unsupported/Platform.h @@ -66,7 +66,7 @@ bool Platform_getDiskIO(DiskIOData* data); bool Platform_getNetworkIO(NetworkIOData* data); -void Platform_getBattery(double* percent, ACPresence* isOnAC); +void Platform_getBattery(BatteryInfo* info); void Platform_getHostname(char* buffer, size_t size); From fd4ea92ffb7d6086075b84ffc9c2bb0eba1abb14 Mon Sep 17 00:00:00 2001 From: Benny Baumann Date: Wed, 22 Apr 2026 21:17:37 +0200 Subject: [PATCH 02/11] Add battery charge data to BatteryMeter API --- BatteryMeter.c | 2 ++ BatteryMeter.h | 2 ++ darwin/Platform.c | 7 ++++++- dragonflybsd/Platform.c | 2 ++ freebsd/Platform.c | 2 ++ linux/Platform.c | 10 ++++++++++ netbsd/Platform.c | 8 +++++++- openbsd/Platform.c | 4 ++++ pcp/Platform.c | 2 ++ solaris/Platform.c | 2 ++ unsupported/Platform.c | 2 ++ 11 files changed, 41 insertions(+), 2 deletions(-) diff --git a/BatteryMeter.c b/BatteryMeter.c index a147ad492..2d60b8d57 100644 --- a/BatteryMeter.c +++ b/BatteryMeter.c @@ -28,6 +28,8 @@ static void BatteryMeter_updateValues(Meter* this) { BatteryInfo info = { .ac = AC_ERROR, .percent = NAN, + .energyCurr = NAN, + .energyFull = NAN, }; Platform_getBattery(&info); diff --git a/BatteryMeter.h b/BatteryMeter.h index 5dc0ab39d..3e2492273 100644 --- a/BatteryMeter.h +++ b/BatteryMeter.h @@ -22,6 +22,8 @@ typedef struct BatteryInfo_ { ACPresence ac; double percent; /* [0..100], NAN if unknown */ + double energyCurr; /* Wh, NAN if unknown */ + double energyFull; /* Wh, NAN if unknown */ } BatteryInfo; extern const MeterClass BatteryMeter_class; diff --git a/darwin/Platform.c b/darwin/Platform.c index bcef3b04a..90d9d9869 100644 --- a/darwin/Platform.c +++ b/darwin/Platform.c @@ -681,6 +681,8 @@ void Platform_getBattery(BatteryInfo* info) { *info = (BatteryInfo) { .ac = AC_ERROR, .percent = NAN, + .energyCurr = NAN, + .energyFull = NAN, }; CFArrayRef list = NULL; @@ -723,8 +725,11 @@ void Platform_getBattery(BatteryInfo* info) { cap_max += tmp; } - if (cap_max > 0.0) + if (cap_max > 0.0) { info->percent = 100.0 * cap_current / cap_max; + info->energyCurr = cap_current; + info->energyFull = cap_max; + } cleanup: if (list) diff --git a/dragonflybsd/Platform.c b/dragonflybsd/Platform.c index 37e25f7c2..0abef6fe1 100644 --- a/dragonflybsd/Platform.c +++ b/dragonflybsd/Platform.c @@ -367,6 +367,8 @@ void Platform_getBattery(BatteryInfo* info) { *info = (BatteryInfo) { .ac = AC_ERROR, .percent = NAN, + .energyCurr = NAN, + .energyFull = NAN, }; int life; diff --git a/freebsd/Platform.c b/freebsd/Platform.c index d9cbcc1a0..04756cbb8 100644 --- a/freebsd/Platform.c +++ b/freebsd/Platform.c @@ -400,6 +400,8 @@ void Platform_getBattery(BatteryInfo* info) { *info = (BatteryInfo) { .ac = AC_ERROR, .percent = NAN, + .energyCurr = NAN, + .energyFull = NAN, }; int life; diff --git a/linux/Platform.c b/linux/Platform.c index 44b5e6a32..4ff28a0bf 100644 --- a/linux/Platform.c +++ b/linux/Platform.c @@ -165,6 +165,8 @@ static time_t Platform_Battery_cacheTime; static BatteryInfo Platform_Battery_cache = { .ac = AC_ERROR, .percent = NAN, + .energyCurr = NAN, + .energyFull = NAN, }; #ifdef HAVE_LIBCAP @@ -847,6 +849,8 @@ static void Platform_Battery_getProcData(BatteryInfo* info) { static void Platform_Battery_getSysData(BatteryInfo* info) { info->percent = NAN; info->ac = AC_ERROR; + info->energyCurr = NAN; + info->energyFull = NAN; DIR* dir = opendir(SYS_POWERSUPPLY_DIR); if (!dir) @@ -961,6 +965,10 @@ static void Platform_Battery_getSysData(BatteryInfo* info) { closedir(dir); info->percent = totalFull > 0 ? ((double) totalRemain * 100.0) / (double) totalFull : NAN; + if (totalFull > 0) { + info->energyCurr = (double) totalRemain; + info->energyFull = (double) totalFull; + } } void Platform_getBattery(BatteryInfo* info) { @@ -974,6 +982,8 @@ void Platform_getBattery(BatteryInfo* info) { Platform_Battery_cache = (BatteryInfo) { .ac = AC_ERROR, .percent = NAN, + .energyCurr = NAN, + .energyFull = NAN, }; if (Platform_Battery_method == BAT_PROC) { diff --git a/netbsd/Platform.c b/netbsd/Platform.c index 7f8fdf096..53a632a37 100644 --- a/netbsd/Platform.c +++ b/netbsd/Platform.c @@ -456,6 +456,8 @@ void Platform_getBattery(BatteryInfo* info) { *info = (BatteryInfo) { .ac = AC_ERROR, .percent = NAN, + .energyCurr = NAN, + .energyFull = NAN, }; int fd = open(_PATH_SYSMON, O_RDONLY); @@ -529,7 +531,11 @@ void Platform_getBattery(BatteryInfo* info) { } } - info->percent = totalCapacity > 0 ? ((double)totalCharge / (double)totalCapacity) * 100.0 : NAN; + if (totalCapacity > 0) { + info->percent = ((double)totalCharge / (double)totalCapacity) * 100.0; + info->energyCurr = (double) totalCharge; + info->energyFull = (double) totalCapacity; + } error: if (fd != -1) diff --git a/openbsd/Platform.c b/openbsd/Platform.c index a06b0e64c..9abce1375 100644 --- a/openbsd/Platform.c +++ b/openbsd/Platform.c @@ -383,6 +383,8 @@ void Platform_getBattery(BatteryInfo* info) { *info = (BatteryInfo) { .ac = AC_ERROR, .percent = NAN, + .energyCurr = NAN, + .energyFull = NAN, }; bool found = findDevice("acpibat0", mib, &snsrdev, &sdlen); @@ -403,6 +405,8 @@ void Platform_getBattery(BatteryInfo* info) { info->percent = 100 * (charge / last_full_capacity); if (charge >= last_full_capacity) info->percent = 100; + info->energyCurr = charge; + info->energyFull = last_full_capacity; } } } diff --git a/pcp/Platform.c b/pcp/Platform.c index e6bf62f9c..4aefd4452 100644 --- a/pcp/Platform.c +++ b/pcp/Platform.c @@ -867,6 +867,8 @@ void Platform_getBattery(BatteryInfo* info) { *info = (BatteryInfo) { .ac = AC_ERROR, .percent = NAN, + .energyCurr = NAN, + .energyFull = NAN, }; } diff --git a/solaris/Platform.c b/solaris/Platform.c index c27fa7740..f55151acf 100644 --- a/solaris/Platform.c +++ b/solaris/Platform.c @@ -346,5 +346,7 @@ void Platform_getBattery(BatteryInfo* info) { *info = (BatteryInfo) { .ac = AC_ERROR, .percent = NAN, + .energyCurr = NAN, + .energyFull = NAN, }; } diff --git a/unsupported/Platform.c b/unsupported/Platform.c index 80ee8065a..aac0d221e 100644 --- a/unsupported/Platform.c +++ b/unsupported/Platform.c @@ -170,6 +170,8 @@ void Platform_getBattery(BatteryInfo* info) { *info = (BatteryInfo) { .ac = AC_ERROR, .percent = NAN, + .energyCurr = NAN, + .energyFull = NAN, }; } From 4b3c71f1319782d39922c63c1fa99d1629632e65 Mon Sep 17 00:00:00 2001 From: Benny Baumann Date: Wed, 29 Apr 2026 22:03:04 +0200 Subject: [PATCH 03/11] Retrieve battery charge information on Linux --- linux/Platform.c | 109 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 86 insertions(+), 23 deletions(-) diff --git a/linux/Platform.c b/linux/Platform.c index 4ff28a0bf..4f2d8588c 100644 --- a/linux/Platform.c +++ b/linux/Platform.c @@ -901,46 +901,109 @@ static void Platform_Battery_getSysData(BatteryInfo* info) { if (r < 0) goto next; - bool full = false; - bool now = false; + bool haveBatteryEnergyFull = false; + bool haveBatteryEnergyCurr = false; + + bool haveBatteryChargeFull = false; + bool haveBatteryChargeCurr = false; + + uint8_t haveBatteryVoltage = 0; // 0 = no, 1 = min_voltage, 2 = curr_voltage + bool haveBatteryLevel = false; + + uint64_t batteryEnergyFull = 0; + uint64_t batteryEnergyCurr = 0; + + uint64_t batteryChargeFull = 0; + uint64_t batteryChargeCurr = 0; + + uint64_t batteryVoltage = 0; + uint64_t batteryLevel = 0; - double fullCharge = 0; - double capacityLevel = NAN; const char* line; char* buf = buffer; while ((line = strsep(&buf, "\n")) != NULL) { char field[100] = {0}; - int val = 0; - if (2 != sscanf(line, "POWER_SUPPLY_%99[^=]=%d", field, &val)) + int64_t val = 0; + if (2 != sscanf(line, "POWER_SUPPLY_%99[^=]=%" SCNd64, field, &val)) { + char strField[100] = {0}; + char strVal[32] = {0}; + if (2 == sscanf(line, "POWER_SUPPLY_%99[^=]=%31s", strField, strVal) && + String_eq(strField, "STATUS")) + batteryIsDischarging = String_eq(strVal, "Discharging"); continue; + } if (String_eq(field, "CAPACITY")) { - capacityLevel = val / 100.0; + batteryLevel = val; + haveBatteryLevel = true; continue; } - if (String_eq(field, "ENERGY_FULL") || String_eq(field, "CHARGE_FULL")) { - fullCharge = val; - totalFull += fullCharge; - full = true; - if (now) - break; + if (String_eq(field, "ENERGY_FULL")) { + batteryEnergyFull = val; + haveBatteryEnergyFull = true; continue; } - if (String_eq(field, "ENERGY_NOW") || String_eq(field, "CHARGE_NOW")) { - totalRemain += val; - now = true; - if (full) - break; + if (String_eq(field, "CHARGE_FULL")) { + batteryChargeFull = val; + haveBatteryChargeFull = true; continue; } + + if (String_eq(field, "ENERGY_NOW")) { + batteryEnergyCurr = val; + haveBatteryEnergyCurr = true; + continue; + } + + if (String_eq(field, "CHARGE_NOW")) { + batteryChargeCurr = val; + haveBatteryChargeCurr = true; + continue; + } + + if (haveBatteryVoltage < 1 && String_eq(field, "VOLTAGE_MIN_DESIGN")) { + batteryVoltage = val; + haveBatteryVoltage = 1; + continue; + } + + if (haveBatteryVoltage < 2 && String_eq(field, "VOLTAGE_NOW")) { + batteryVoltage = val; + haveBatteryVoltage = 2; + continue; + } + } + + if (haveBatteryLevel) { + // If we have capacity level but not charge or energy, we infer approximate values + if (haveBatteryChargeFull && !haveBatteryChargeCurr) { + batteryChargeCurr = batteryChargeFull * batteryLevel / 100.0; + haveBatteryChargeCurr = true; + } + if (haveBatteryEnergyFull && !haveBatteryEnergyCurr) { + batteryEnergyCurr = batteryEnergyFull * batteryLevel / 100.0; + haveBatteryEnergyCurr = true; + } } - if (!now && full && isNonnegative(capacityLevel)) - totalRemain += capacityLevel * fullCharge; + if (haveBatteryEnergyFull && haveBatteryEnergyCurr) { + // No need for conversion needed + } else if (haveBatteryChargeFull && haveBatteryChargeCurr && haveBatteryVoltage) { + // Convert charge to energy using voltage + batteryEnergyFull = (batteryChargeFull * batteryVoltage) / 1000000; + haveBatteryEnergyFull = true; + batteryEnergyCurr = (batteryChargeCurr * batteryVoltage) / 1000000; + haveBatteryEnergyCurr = true; + } + + if (haveBatteryEnergyFull && haveBatteryEnergyCurr && batteryEnergyFull > 0) { + totalFull += batteryEnergyFull; + totalRemain += batteryEnergyCurr > batteryEnergyFull ? batteryEnergyFull : batteryEnergyCurr; + } } else if (type == AC) { if (info->ac != AC_ERROR) goto next; @@ -964,10 +1027,10 @@ static void Platform_Battery_getSysData(BatteryInfo* info) { closedir(dir); - info->percent = totalFull > 0 ? ((double) totalRemain * 100.0) / (double) totalFull : NAN; if (totalFull > 0) { - info->energyCurr = (double) totalRemain; - info->energyFull = (double) totalFull; + info->percent = ((double) totalRemain * 100.0) / (double) totalFull; + info->energyCurr = (double) totalRemain / 1000000.0; + info->energyFull = (double) totalFull / 1000000.0; } } From 7949ad81be0c49aeb92fca47cafb0587cc045a84 Mon Sep 17 00:00:00 2001 From: Benny Baumann Date: Fri, 1 May 2026 10:33:22 +0200 Subject: [PATCH 04/11] Retrieve battery charge information on PCP --- pcp/Metric.h | 2 ++ pcp/Platform.c | 39 +++++++++++++++++++++++++++++++++------ 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/pcp/Metric.h b/pcp/Metric.h index 14e1e144f..c78f28ea3 100644 --- a/pcp/Metric.h +++ b/pcp/Metric.h @@ -107,6 +107,8 @@ typedef enum Metric_ { PCP_MEM_ZSWAPPED, /* mem.util.zswapped */ PCP_VFS_FILES_COUNT, /* vfs.files.count */ PCP_VFS_FILES_MAX, /* vfs.files.max */ + PCP_DENKI_ENERGY_NOW, /* denki.bat.energy_now */ + PCP_DENKI_ENERGY_FULL, /* denki.bat.capacity */ PCP_PROC_PID, /* proc.psinfo.pid */ PCP_PROC_PPID, /* proc.psinfo.ppid */ diff --git a/pcp/Platform.c b/pcp/Platform.c index 4aefd4452..c4022510b 100644 --- a/pcp/Platform.c +++ b/pcp/Platform.c @@ -234,6 +234,8 @@ static const char* Platform_metricNames[] = { [PCP_MEM_ZSWAPPED] = "mem.util.zswapped", [PCP_VFS_FILES_COUNT] = "vfs.files.count", [PCP_VFS_FILES_MAX] = "vfs.files.max", + [PCP_DENKI_ENERGY_NOW] = "denki.bat.energy_now", + [PCP_DENKI_ENERGY_FULL] = "denki.bat.capacity", [PCP_PROC_PID] = "proc.psinfo.pid", [PCP_PROC_PPID] = "proc.psinfo.ppid", @@ -864,12 +866,37 @@ void Platform_getFileDescriptors(double* used, double* max) { } void Platform_getBattery(BatteryInfo* info) { - *info = (BatteryInfo) { - .ac = AC_ERROR, - .percent = NAN, - .energyCurr = NAN, - .energyFull = NAN, - }; + info->ac = AC_ERROR; + info->percent = NAN; + info->energyCurr = NAN; + info->energyFull = NAN; + + if (Metric_desc(PCP_DENKI_ENERGY_NOW) == NULL) + return; + + int i, count = Metric_instanceCount(PCP_DENKI_ENERGY_NOW); + if (count < 1) { + info->ac = AC_PRESENT; + return; + } + + pmAtomValue* batteryEnergyCurr = xCalloc(count, sizeof(pmAtomValue)); + pmAtomValue* batteryEnergyFull = xCalloc(count, sizeof(pmAtomValue)); + if (Metric_values(PCP_DENKI_ENERGY_NOW, batteryEnergyCurr, count, PM_TYPE_DOUBLE) && + Metric_values(PCP_DENKI_ENERGY_FULL, batteryEnergyFull, count, PM_TYPE_DOUBLE)) { + info->energyCurr = 0.0; + info->energyFull = 0.0; + for (i = 0; i < count; i++) { + info->energyCurr += CLAMP(batteryEnergyCurr[i].d, 0, batteryEnergyFull[i].d); + info->energyFull += isNonnegative(batteryEnergyFull[i].d) ? batteryEnergyFull[i].d : 0; + } + + if (info->energyFull > 0) { + info->percent = CLAMP((info->energyCurr / info->energyFull) * 100.0, 0.0, 100.0); + } + } + free(batteryEnergyCurr); + free(batteryEnergyFull); } const char* Platform_getFailedState(void) { From cc9d4570cb028d894d2866a7c6d02391ba41c685 Mon Sep 17 00:00:00 2001 From: Benny Baumann Date: Wed, 22 Apr 2026 21:18:34 +0200 Subject: [PATCH 05/11] Retrieve battery charge information on FreeBSD --- freebsd/Platform.c | 73 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/freebsd/Platform.c b/freebsd/Platform.c index 04756cbb8..3e1f2507f 100644 --- a/freebsd/Platform.c +++ b/freebsd/Platform.c @@ -10,14 +10,18 @@ in the source distribution for its full text. #include "freebsd/Platform.h" #include +#include #include #include #include #include +#include +#include #include #include #include #include +#include #include #include #include @@ -413,4 +417,73 @@ void Platform_getBattery(BatteryInfo* info) { size_t acline_len = sizeof(acline); if (sysctlbyname("hw.acpi.acline", &acline, &acline_len, NULL, 0) != -1) info->ac = (acline == 0) ? AC_ABSENT : AC_PRESENT; + + int units = 0; + size_t units_len = sizeof(units); + if (sysctlbyname("hw.acpi.battery.units", &units, &units_len, NULL, 0) == -1 || units <= 0) + return; + + int fd = open("/dev/acpi", O_RDONLY | O_CLOEXEC); + if (fd == -1) + return; + + bool haveTotalRemain = false; + bool haveTotalFull = false; + + int64_t totalRemain = 0; + int64_t totalFull = 0; + + for (int u = 0; u < units; u++) { + union acpi_battery_ioctl_arg bixArg = { .unit = u }; + if (ioctl(fd, ACPIIO_BATT_GET_BIX, &bixArg) == -1) + continue; + + union acpi_battery_ioctl_arg bstArg = { .unit = u }; + if (ioctl(fd, ACPIIO_BATT_GET_BST, &bstArg) == -1) + continue; + + const struct acpi_bix* bix = &bixArg.bix; + const struct acpi_bst* bst = &bstArg.bst; + + bool haveBatteryEnergyCurr = false; + bool haveBatteryEnergyFull = false; + + int64_t batteryEnergyCurr = 0; + int64_t batteryEnergyFull = 0; + + if (bix->lfcap != ACPI_BATT_UNKNOWN && bst->cap != ACPI_BATT_UNKNOWN) { + if (bix->units == ACPI_BIX_UNITS_MW) { + batteryEnergyCurr = (int64_t) bst->cap * 1000; + batteryEnergyFull = (int64_t) bix->lfcap * 1000; + haveBatteryEnergyCurr = true; + haveBatteryEnergyFull = true; + } else { + uint32_t batteryVoltage = (bst->volt != ACPI_BATT_UNKNOWN) ? bst->volt : bix->dvol; + if (batteryVoltage != ACPI_BATT_UNKNOWN && batteryVoltage != 0) { + batteryEnergyCurr = (int64_t) bst->cap * batteryVoltage; + batteryEnergyFull = (int64_t) bix->lfcap * batteryVoltage; + haveBatteryEnergyCurr = true; + haveBatteryEnergyFull = true; + } + } + } + + if (haveBatteryEnergyCurr && haveBatteryEnergyFull && batteryEnergyFull > 0) { + totalRemain += batteryEnergyCurr; + totalFull += batteryEnergyFull; + haveTotalRemain = true; + haveTotalFull = true; + } + } + + close(fd); + + if (haveTotalRemain && haveTotalFull && totalFull > 0) { + info->percent = ((double) totalRemain * 100.0) / (double) totalFull; + if (totalRemain >= totalFull) + info->percent = 100; + + info->energyCurr = (double) totalRemain / 1000000.0; + info->energyFull = (double) totalFull / 1000000.0; + } } From 8237fe512d5889bc0ad5edac6cab7f689f15f5e6 Mon Sep 17 00:00:00 2001 From: Benny Baumann Date: Thu, 30 Apr 2026 07:11:31 +0200 Subject: [PATCH 06/11] Retrieve battery charge information on NetBSD --- netbsd/Platform.c | 50 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 5 deletions(-) diff --git a/netbsd/Platform.c b/netbsd/Platform.c index 53a632a37..a417af5da 100644 --- a/netbsd/Platform.c +++ b/netbsd/Platform.c @@ -488,6 +488,11 @@ void Platform_getBattery(BatteryInfo* info) { intmax_t isConnected = 0; intmax_t curCharge = 0; intmax_t maxCharge = 0; + intmax_t voltage = 0; + intmax_t designVoltage = 0; + + bool haveCharge = false; + bool chargeIsAmpHours = false; while ((fields = prop_object_iterator_next(fieldsIter)) != NULL) { props = prop_dictionary_get(fields, "device-properties"); @@ -505,6 +510,7 @@ void Platform_getBattery(BatteryInfo* info) { prop_object_t curValue = prop_dictionary_get(fields, "cur-value"); prop_object_t maxValue = prop_dictionary_get(fields, "max-value"); prop_object_t descField = prop_dictionary_get(fields, "description"); + prop_object_t typeField = prop_dictionary_get(fields, "type"); if (descField == NULL || curValue == NULL) continue; @@ -513,17 +519,48 @@ void Platform_getBattery(BatteryInfo* info) { isConnected = prop_number_signed_value(curValue); } else if (prop_string_equals_string(descField, "present")) { isPresent = prop_number_signed_value(curValue); + } else if (prop_string_equals_string(descField, "voltage")) { + voltage = prop_number_signed_value(curValue); + } else if (prop_string_equals_string(descField, "design voltage")) { + designVoltage = prop_number_signed_value(curValue); } else if (prop_string_equals_string(descField, "charge")) { if (maxValue == NULL) continue; curCharge = prop_number_signed_value(curValue); maxCharge = prop_number_signed_value(maxValue); + haveCharge = true; + chargeIsAmpHours = typeField != NULL && + prop_string_equals_string(typeField, "Ampere hour"); } } if (isBattery && isPresent) { - totalCharge += curCharge; - totalCapacity += maxCharge; + intmax_t batteryVoltage = (voltage != 0) ? voltage : designVoltage; + + bool haveBatteryCharge = false; + + intmax_t batteryCharge = 0; + intmax_t batteryCapacity = 0; + + if (haveCharge) { + if (chargeIsAmpHours) { + if (batteryVoltage > 0) { + batteryCharge = curCharge * batteryVoltage / 1000000; + batteryCapacity = maxCharge * batteryVoltage / 1000000; + haveBatteryCharge = true; + } + } else { + batteryCharge = curCharge; + batteryCapacity = maxCharge; + haveBatteryCharge = true; + } + } + + if (haveBatteryCharge) { + totalCharge += batteryCharge; + totalCapacity += batteryCapacity; + } + } if (isACAdapter && info->ac != AC_PRESENT) { @@ -532,9 +569,12 @@ void Platform_getBattery(BatteryInfo* info) { } if (totalCapacity > 0) { - info->percent = ((double)totalCharge / (double)totalCapacity) * 100.0; - info->energyCurr = (double) totalCharge; - info->energyFull = (double) totalCapacity; + info->percent = ((double) totalCharge * 100.0) / (double) totalCapacity; + if (totalCharge >= totalCapacity) + info->percent = 100.0; + + info->energyCurr = (double) totalCharge / 1000000.0; + info->energyFull = (double) totalCapacity / 1000000.0; } error: From f99c78b1b0deeb8193fb2c83469b923dca825151 Mon Sep 17 00:00:00 2001 From: Benny Baumann Date: Thu, 30 Apr 2026 07:15:50 +0200 Subject: [PATCH 07/11] Retrieve battery charge information on OpenBSD --- openbsd/Platform.c | 39 ++++++++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/openbsd/Platform.c b/openbsd/Platform.c index 9abce1375..49d0725c6 100644 --- a/openbsd/Platform.c +++ b/openbsd/Platform.c @@ -390,25 +390,46 @@ void Platform_getBattery(BatteryInfo* info) { bool found = findDevice("acpibat0", mib, &snsrdev, &sdlen); if (found) { + bool haveTotalFull = false; + bool haveTotalRemain = false; + + int64_t totalFull = 0; + int64_t totalRemain = 0; + /* See "sys/dev/acpi/acpibat.c" of OpenBSD source code for the indices of the last field. */ mib[3] = SENSOR_WATTHOUR; mib[4] = 0; /* "last full capacity" */ - double last_full_capacity = 0; + bool haveBatteryFull = false; + int64_t batteryFull = 0; if (sysctl(mib, 5, &s, &slen, NULL, 0) != -1) - last_full_capacity = s.value; - if (last_full_capacity > 0) { + batteryFull = s.value; + + if (batteryFull > 0) + haveBatteryFull = true; + + if (haveBatteryFull) { mib[3] = SENSOR_WATTHOUR; mib[4] = 3; /* "remaining capacity" */ if (sysctl(mib, 5, &s, &slen, NULL, 0) != -1) { - double charge = s.value; - info->percent = 100 * (charge / last_full_capacity); - if (charge >= last_full_capacity) - info->percent = 100; - info->energyCurr = charge; - info->energyFull = last_full_capacity; + int64_t batteryRemain = s.value; + if (batteryRemain >= 0) { + totalRemain += batteryRemain; + totalFull += batteryFull; + haveTotalRemain = true; + haveTotalFull = true; + } } } + + if (haveTotalRemain && haveTotalFull && totalFull > 0) { + info->percent = ((double) totalRemain * 100.0) / (double) totalFull; + if (totalRemain >= totalFull) + info->percent = 100; + + info->energyCurr = (double) totalRemain / 1000000.0; + info->energyFull = (double) totalFull / 1000000.0; + } } found = findDevice("acpiac0", mib, &snsrdev, &sdlen); From 00a32baa7b72f3d4ab968e1b00df0c3837227175 Mon Sep 17 00:00:00 2001 From: Benny Baumann Date: Thu, 30 Apr 2026 07:23:31 +0200 Subject: [PATCH 08/11] Retrieve battery charge information on DragonflyBSD --- dragonflybsd/Platform.c | 71 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/dragonflybsd/Platform.c b/dragonflybsd/Platform.c index 0abef6fe1..6c623b7f6 100644 --- a/dragonflybsd/Platform.c +++ b/dragonflybsd/Platform.c @@ -380,4 +380,75 @@ void Platform_getBattery(BatteryInfo* info) { size_t acline_len = sizeof(acline); if (sysctlbyname("hw.acpi.acline", &acline, &acline_len, NULL, 0) != -1) info->ac = (acline == 0) ? AC_ABSENT : AC_PRESENT; + + int units = 0; + int fd = open("/dev/acpi", O_RDONLY | O_CLOEXEC); + if (fd == -1) + return; + + if (ioctl(fd, ACPIIO_BATT_GET_UNITS, &units) == -1 || units <= 0) { + close(fd); + return; + } + + bool haveTotalRemain = false; + bool haveTotalFull = false; + + int64_t totalRemain = 0; + int64_t totalFull = 0; + + for (int u = 0; u < units; u++) { + union acpi_battery_ioctl_arg bifArg = { .unit = u }; + if (ioctl(fd, ACPIIO_BATT_GET_BIF, &bifArg) == -1) + continue; + + union acpi_battery_ioctl_arg bstArg = { .unit = u }; + if (ioctl(fd, ACPIIO_BATT_GET_BST, &bstArg) == -1) + continue; + + const struct acpi_bif* bif = &bifArg.bif; + const struct acpi_bst* bst = &bstArg.bst; + + bool haveBatteryEnergyCurr = false; + bool haveBatteryEnergyFull = false; + + int64_t batteryEnergyCurr = 0; + int64_t batteryEnergyFull = 0; + + if (bif->lfcap != ACPI_BATT_UNKNOWN && bst->cap != ACPI_BATT_UNKNOWN) { + if (bif->units == ACPI_BIF_UNITS_MW) { + batteryEnergyCurr = (int64_t) bst->cap * 1000; + batteryEnergyFull = (int64_t) bif->lfcap * 1000; + haveBatteryEnergyCurr = true; + haveBatteryEnergyFull = true; + } else { + uint32_t batteryVoltage = (bst->volt != ACPI_BATT_UNKNOWN) ? bst->volt : bif->dvol; + if (batteryVoltage != ACPI_BATT_UNKNOWN && batteryVoltage != 0) { + batteryEnergyCurr = (int64_t) bst->cap * batteryVoltage; + batteryEnergyFull = (int64_t) bif->lfcap * batteryVoltage; + haveBatteryEnergyCurr = true; + haveBatteryEnergyFull = true; + } + } + } + + if (haveBatteryEnergyCurr && haveBatteryEnergyFull && batteryEnergyFull > 0) { + totalRemain += batteryEnergyCurr; + haveTotalRemain = true; + + totalFull += batteryEnergyFull; + haveTotalFull = true; + } + } + + close(fd); + + if (haveTotalRemain && haveTotalFull && totalFull > 0) { + info->percent = ((double) totalRemain * 100.0) / (double) totalFull; + if (totalRemain >= totalFull) + info->percent = 100; + + info->energyCurr = (double) totalRemain / 1000000.0; + info->energyFull = (double) totalFull / 1000000.0; + } } From 825c1a64ee5ce12adf682d6bd7ffa9c5a331fb20 Mon Sep 17 00:00:00 2001 From: Benny Baumann Date: Wed, 29 Apr 2026 22:08:23 +0200 Subject: [PATCH 09/11] Retrieve battery charge information on Darwin --- darwin/Platform.c | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/darwin/Platform.c b/darwin/Platform.c index 90d9d9869..ded805c84 100644 --- a/darwin/Platform.c +++ b/darwin/Platform.c @@ -49,6 +49,7 @@ in the source distribution for its full text. #include "SysArchMeter.h" #include "TasksMeter.h" #include "UptimeMeter.h" +#include "XUtils.h" #include "darwin/DarwinMachine.h" #include "darwin/PlatformHelpers.h" #include "generic/fdstat_sysctl.h" @@ -727,8 +728,39 @@ void Platform_getBattery(BatteryInfo* info) { if (cap_max > 0.0) { info->percent = 100.0 * cap_current / cap_max; - info->energyCurr = cap_current; - info->energyFull = cap_max; + } + + io_service_t batt = IOServiceGetMatchingService(iokit_port, IOServiceMatching("AppleSmartBattery")); + if (batt) { + CFNumberRef voltRef = IORegistryEntryCreateCFProperty(batt, CFSTR("Voltage"), kCFAllocatorDefault, 0); + CFNumberRef currCapRef = IORegistryEntryCreateCFProperty(batt, CFSTR("AppleRawCurrentCapacity"), kCFAllocatorDefault, 0); + CFNumberRef maxCapRef = IORegistryEntryCreateCFProperty(batt, CFSTR("AppleRawMaxCapacity"), kCFAllocatorDefault, 0); + + if (currCapRef && maxCapRef && voltRef) { + double currMAh = 0.0; + CFNumberGetValue(currCapRef, kCFNumberDoubleType, &currMAh); + + double maxMAh = 0.0; + CFNumberGetValue(maxCapRef, kCFNumberDoubleType, &maxMAh); + + double voltMV = 0.0; + CFNumberGetValue(voltRef, kCFNumberDoubleType, &voltMV); + + /* Approximate energy from charge and current battery voltage: mAh * mV / 1e6 = Wh. */ + if (maxMAh > 0.0 && voltMV > 0.0) { + info->energyCurr = CLAMP(currMAh, 0.0, maxMAh) * voltMV / 1e6; + info->energyFull = maxMAh * voltMV / 1e6; + } + } + + if (maxCapRef) + CFRelease(maxCapRef); + if (currCapRef) + CFRelease(currCapRef); + if (voltRef) + CFRelease(voltRef); + + IOObjectRelease(batt); } cleanup: From df550b50c2c55ed6e6b246019f3abcacedc94721 Mon Sep 17 00:00:00 2001 From: Benny Baumann Date: Thu, 23 Apr 2026 07:12:59 +0200 Subject: [PATCH 10/11] Fetch the current power consumption --- BatteryMeter.c | 1 + BatteryMeter.h | 1 + darwin/Platform.c | 15 +++++++++++ dragonflybsd/Platform.c | 37 ++++++++++++++++++++++++++ freebsd/Platform.c | 30 ++++++++++++++++++++++ linux/Platform.c | 43 +++++++++++++++++++++++++++++++ netbsd/Platform.c | 57 +++++++++++++++++++++++++++++++++++++++++ openbsd/Platform.c | 24 +++++++++++++++++ pcp/Metric.h | 1 + pcp/Platform.c | 15 +++++++++++ solaris/Platform.c | 1 + unsupported/Platform.c | 1 + 12 files changed, 226 insertions(+) diff --git a/BatteryMeter.c b/BatteryMeter.c index 2d60b8d57..963a0cb32 100644 --- a/BatteryMeter.c +++ b/BatteryMeter.c @@ -28,6 +28,7 @@ static void BatteryMeter_updateValues(Meter* this) { BatteryInfo info = { .ac = AC_ERROR, .percent = NAN, + .powerCurr = NAN, .energyCurr = NAN, .energyFull = NAN, }; diff --git a/BatteryMeter.h b/BatteryMeter.h index 3e2492273..b69fd5643 100644 --- a/BatteryMeter.h +++ b/BatteryMeter.h @@ -22,6 +22,7 @@ typedef struct BatteryInfo_ { ACPresence ac; double percent; /* [0..100], NAN if unknown */ + double powerCurr; /* instantaneous power in W, NAN if unknown */ double energyCurr; /* Wh, NAN if unknown */ double energyFull; /* Wh, NAN if unknown */ } BatteryInfo; diff --git a/darwin/Platform.c b/darwin/Platform.c index ded805c84..8df67bfa0 100644 --- a/darwin/Platform.c +++ b/darwin/Platform.c @@ -682,6 +682,7 @@ void Platform_getBattery(BatteryInfo* info) { *info = (BatteryInfo) { .ac = AC_ERROR, .percent = NAN, + .powerCurr = NAN, .energyCurr = NAN, .energyFull = NAN, }; @@ -732,10 +733,22 @@ void Platform_getBattery(BatteryInfo* info) { io_service_t batt = IOServiceGetMatchingService(iokit_port, IOServiceMatching("AppleSmartBattery")); if (batt) { + CFNumberRef ampRef = IORegistryEntryCreateCFProperty(batt, CFSTR("Amperage"), kCFAllocatorDefault, 0); CFNumberRef voltRef = IORegistryEntryCreateCFProperty(batt, CFSTR("Voltage"), kCFAllocatorDefault, 0); CFNumberRef currCapRef = IORegistryEntryCreateCFProperty(batt, CFSTR("AppleRawCurrentCapacity"), kCFAllocatorDefault, 0); CFNumberRef maxCapRef = IORegistryEntryCreateCFProperty(batt, CFSTR("AppleRawMaxCapacity"), kCFAllocatorDefault, 0); + if (ampRef && voltRef) { + double ampMA = 0.0; + CFNumberGetValue(ampRef, kCFNumberDoubleType, &MA); + + double voltMV = 0.0; + CFNumberGetValue(voltRef, kCFNumberDoubleType, &voltMV); + + // Follows the Smart Battery System (SBS) Standard + info->powerCurr = ampMA * voltMV / 1e6; + } + if (currCapRef && maxCapRef && voltRef) { double currMAh = 0.0; CFNumberGetValue(currCapRef, kCFNumberDoubleType, &currMAh); @@ -759,6 +772,8 @@ void Platform_getBattery(BatteryInfo* info) { CFRelease(currCapRef); if (voltRef) CFRelease(voltRef); + if (ampRef) + CFRelease(ampRef); IOObjectRelease(batt); } diff --git a/dragonflybsd/Platform.c b/dragonflybsd/Platform.c index 6c623b7f6..ba33ac6e4 100644 --- a/dragonflybsd/Platform.c +++ b/dragonflybsd/Platform.c @@ -12,9 +12,13 @@ in the source distribution for its full text. #include #include +#include #include #include #include +#include +#include +#include #include #include #include @@ -367,6 +371,7 @@ void Platform_getBattery(BatteryInfo* info) { *info = (BatteryInfo) { .ac = AC_ERROR, .percent = NAN, + .powerCurr = NAN, .energyCurr = NAN, .energyFull = NAN, }; @@ -393,9 +398,11 @@ void Platform_getBattery(BatteryInfo* info) { bool haveTotalRemain = false; bool haveTotalFull = false; + bool haveTotalPower = false; int64_t totalRemain = 0; int64_t totalFull = 0; + int64_t totalPower = 0; for (int u = 0; u < units; u++) { union acpi_battery_ioctl_arg bifArg = { .unit = u }; @@ -411,9 +418,11 @@ void Platform_getBattery(BatteryInfo* info) { bool haveBatteryEnergyCurr = false; bool haveBatteryEnergyFull = false; + bool haveBatteryPower = false; int64_t batteryEnergyCurr = 0; int64_t batteryEnergyFull = 0; + int64_t batteryPower = 0; if (bif->lfcap != ACPI_BATT_UNKNOWN && bst->cap != ACPI_BATT_UNKNOWN) { if (bif->units == ACPI_BIF_UNITS_MW) { @@ -439,6 +448,30 @@ void Platform_getBattery(BatteryInfo* info) { totalFull += batteryEnergyFull; haveTotalFull = true; } + + if (bst->rate == ACPI_BATT_UNKNOWN) + continue; + + if (bif->units == ACPI_BIF_UNITS_MW) { + batteryPower = (int64_t) bst->rate * 1000; + haveBatteryPower = true; + } else { + uint32_t batteryVoltage = (bst->volt != ACPI_BATT_UNKNOWN) ? bst->volt : bif->dvol; + + if (batteryVoltage != ACPI_BATT_UNKNOWN && batteryVoltage != 0) { + batteryPower = (int64_t) bst->rate * batteryVoltage; + haveBatteryPower = true; + } + } + + if (!haveBatteryPower) + continue; + + if (bst->state & ACPI_BATT_STAT_DISCHARG) + batteryPower = -batteryPower; + + totalPower += batteryPower; + haveTotalPower = true; } close(fd); @@ -451,4 +484,8 @@ void Platform_getBattery(BatteryInfo* info) { info->energyCurr = (double) totalRemain / 1000000.0; info->energyFull = (double) totalFull / 1000000.0; } + + if (haveTotalPower) { + info->powerCurr = (double) totalPower / 1000000.0; + } } diff --git a/freebsd/Platform.c b/freebsd/Platform.c index 3e1f2507f..d64e84c24 100644 --- a/freebsd/Platform.c +++ b/freebsd/Platform.c @@ -404,6 +404,7 @@ void Platform_getBattery(BatteryInfo* info) { *info = (BatteryInfo) { .ac = AC_ERROR, .percent = NAN, + .powerCurr = NAN, .energyCurr = NAN, .energyFull = NAN, }; @@ -429,9 +430,11 @@ void Platform_getBattery(BatteryInfo* info) { bool haveTotalRemain = false; bool haveTotalFull = false; + bool haveTotalPower = false; int64_t totalRemain = 0; int64_t totalFull = 0; + int64_t totalPower = 0; for (int u = 0; u < units; u++) { union acpi_battery_ioctl_arg bixArg = { .unit = u }; @@ -447,9 +450,11 @@ void Platform_getBattery(BatteryInfo* info) { bool haveBatteryEnergyCurr = false; bool haveBatteryEnergyFull = false; + bool haveBatteryPower = false; int64_t batteryEnergyCurr = 0; int64_t batteryEnergyFull = 0; + int64_t batteryPower = 0; if (bix->lfcap != ACPI_BATT_UNKNOWN && bst->cap != ACPI_BATT_UNKNOWN) { if (bix->units == ACPI_BIX_UNITS_MW) { @@ -474,6 +479,27 @@ void Platform_getBattery(BatteryInfo* info) { haveTotalRemain = true; haveTotalFull = true; } + + if (bst->rate != ACPI_BATT_UNKNOWN && bst->rate > 0) { + if (bix->units == ACPI_BIX_UNITS_MW) { + batteryPower = (int64_t) bst->rate * 1000; + haveBatteryPower = true; + } else { + uint32_t rateVoltage = (bst->volt != ACPI_BATT_UNKNOWN) ? bst->volt : bix->dvol; + + if (rateVoltage != ACPI_BATT_UNKNOWN && rateVoltage != 0) { + batteryPower = (int64_t) bst->rate * rateVoltage; + haveBatteryPower = true; + } + } + } + + if (haveBatteryPower) { + if (bst->state & ACPI_BATT_STAT_DISCHARG) + batteryPower = -batteryPower; + totalPower += batteryPower; + haveTotalPower = true; + } } close(fd); @@ -486,4 +512,8 @@ void Platform_getBattery(BatteryInfo* info) { info->energyCurr = (double) totalRemain / 1000000.0; info->energyFull = (double) totalFull / 1000000.0; } + + if (haveTotalPower) { + info->powerCurr = (double) totalPower / 1000000.0; + } } diff --git a/linux/Platform.c b/linux/Platform.c index 4f2d8588c..ba6a91014 100644 --- a/linux/Platform.c +++ b/linux/Platform.c @@ -165,6 +165,7 @@ static time_t Platform_Battery_cacheTime; static BatteryInfo Platform_Battery_cache = { .ac = AC_ERROR, .percent = NAN, + .powerCurr = NAN, .energyCurr = NAN, .energyFull = NAN, }; @@ -849,6 +850,7 @@ static void Platform_Battery_getProcData(BatteryInfo* info) { static void Platform_Battery_getSysData(BatteryInfo* info) { info->percent = NAN; info->ac = AC_ERROR; + info->powerCurr = NAN; info->energyCurr = NAN; info->energyFull = NAN; @@ -858,6 +860,9 @@ static void Platform_Battery_getSysData(BatteryInfo* info) { uint64_t totalFull = 0; uint64_t totalRemain = 0; + int64_t totalPower = 0; + + bool havePower = false; const struct dirent* dirEntry; while ((dirEntry = readdir(dir))) { @@ -910,6 +915,9 @@ static void Platform_Battery_getSysData(BatteryInfo* info) { uint8_t haveBatteryVoltage = 0; // 0 = no, 1 = min_voltage, 2 = curr_voltage bool haveBatteryLevel = false; + bool haveBatteryCurrent = false; + bool haveBatteryPower = false; + uint64_t batteryEnergyFull = 0; uint64_t batteryEnergyCurr = 0; @@ -919,6 +927,11 @@ static void Platform_Battery_getSysData(BatteryInfo* info) { uint64_t batteryVoltage = 0; uint64_t batteryLevel = 0; + int64_t batteryCurrent = 0; + int64_t batteryPower = 0; + + bool batteryIsDischarging = false; + const char* line; char* buf = buffer; @@ -975,6 +988,18 @@ static void Platform_Battery_getSysData(BatteryInfo* info) { haveBatteryVoltage = 2; continue; } + + if (String_eq(field, "CURRENT_NOW")) { + batteryCurrent = val; + haveBatteryCurrent = true; + continue; + } + + if (String_eq(field, "POWER_NOW")) { + batteryPower += val; + haveBatteryPower = true; + continue; + } } if (haveBatteryLevel) { @@ -1004,6 +1029,19 @@ static void Platform_Battery_getSysData(BatteryInfo* info) { totalFull += batteryEnergyFull; totalRemain += batteryEnergyCurr > batteryEnergyFull ? batteryEnergyFull : batteryEnergyCurr; } + + if (!haveBatteryPower && haveBatteryCurrent && haveBatteryVoltage) { + batteryPower = (batteryCurrent * batteryVoltage) / 1000000; + haveBatteryPower = true; + } + + if (haveBatteryPower) { + if (batteryIsDischarging) + batteryPower = -batteryPower; + + totalPower += batteryPower; + havePower = true; + } } else if (type == AC) { if (info->ac != AC_ERROR) goto next; @@ -1032,6 +1070,10 @@ static void Platform_Battery_getSysData(BatteryInfo* info) { info->energyCurr = (double) totalRemain / 1000000.0; info->energyFull = (double) totalFull / 1000000.0; } + + if (havePower) { + info->powerCurr = (double) totalPower / 1000000.0; + } } void Platform_getBattery(BatteryInfo* info) { @@ -1045,6 +1087,7 @@ void Platform_getBattery(BatteryInfo* info) { Platform_Battery_cache = (BatteryInfo) { .ac = AC_ERROR, .percent = NAN, + .powerCurr = NAN, .energyCurr = NAN, .energyFull = NAN, }; diff --git a/netbsd/Platform.c b/netbsd/Platform.c index a417af5da..aaf0c08b0 100644 --- a/netbsd/Platform.c +++ b/netbsd/Platform.c @@ -453,9 +453,13 @@ void Platform_getBattery(BatteryInfo* info) { intmax_t totalCharge = 0; intmax_t totalCapacity = 0; + bool havePower = false; + intmax_t totalPower = 0; + *info = (BatteryInfo) { .ac = AC_ERROR, .percent = NAN, + .powerCurr = NAN, .energyCurr = NAN, .energyFull = NAN, }; @@ -488,11 +492,17 @@ void Platform_getBattery(BatteryInfo* info) { intmax_t isConnected = 0; intmax_t curCharge = 0; intmax_t maxCharge = 0; + intmax_t chargeRate = 0; + intmax_t dischargeRate = 0; intmax_t voltage = 0; intmax_t designVoltage = 0; bool haveCharge = false; + bool haveChargeRate = false; + bool haveDischargeRate = false; bool chargeIsAmpHours = false; + bool chargeRateIsAmps = false; + bool dischargeRateIsAmps = false; while ((fields = prop_object_iterator_next(fieldsIter)) != NULL) { props = prop_dictionary_get(fields, "device-properties"); @@ -531,14 +541,29 @@ void Platform_getBattery(BatteryInfo* info) { haveCharge = true; chargeIsAmpHours = typeField != NULL && prop_string_equals_string(typeField, "Ampere hour"); + } else if (prop_string_equals_string(descField, "charge rate")) { + chargeRate = prop_number_signed_value(curValue); + chargeRateIsAmps = typeField != NULL && + prop_string_equals_string(typeField, "Ampere"); + haveChargeRate = true; + } else if (prop_string_equals_string(descField, "discharge rate")) { + dischargeRate = prop_number_signed_value(curValue); + dischargeRateIsAmps = typeField != NULL && + prop_string_equals_string(typeField, "Ampere"); + haveDischargeRate = true; } } if (isBattery && isPresent) { intmax_t batteryVoltage = (voltage != 0) ? voltage : designVoltage; + bool haveBatteryChargeRate = false; + bool haveBatteryDischargeRate = false; + bool haveBatteryCharge = false; + intmax_t batteryChargeRate = 0; + intmax_t batteryDischargeRate = 0; intmax_t batteryCharge = 0; intmax_t batteryCapacity = 0; @@ -556,11 +581,39 @@ void Platform_getBattery(BatteryInfo* info) { } } + if (haveChargeRate) { + if (chargeRateIsAmps) { + if (batteryVoltage > 0) { + batteryChargeRate = chargeRate * batteryVoltage / 1000000; + haveBatteryChargeRate = true; + } + } else { + batteryChargeRate = chargeRate; + haveBatteryChargeRate = true; + } + } + + if (haveDischargeRate) { + if (dischargeRateIsAmps) { + if (batteryVoltage > 0) { + batteryDischargeRate = dischargeRate * batteryVoltage / 1000000; + haveBatteryDischargeRate = true; + } + } else { + batteryDischargeRate = dischargeRate; + haveBatteryDischargeRate = true; + } + } + if (haveBatteryCharge) { totalCharge += batteryCharge; totalCapacity += batteryCapacity; } + if (haveBatteryChargeRate || haveBatteryDischargeRate) { + totalPower += batteryChargeRate - batteryDischargeRate; + havePower = true; + } } if (isACAdapter && info->ac != AC_PRESENT) { @@ -577,6 +630,10 @@ void Platform_getBattery(BatteryInfo* info) { info->energyFull = (double) totalCapacity / 1000000.0; } + if (havePower) { + info->powerCurr = (double) totalPower / 1000000.0; + } + error: if (fd != -1) close(fd); diff --git a/openbsd/Platform.c b/openbsd/Platform.c index 49d0725c6..8b9a216e0 100644 --- a/openbsd/Platform.c +++ b/openbsd/Platform.c @@ -383,6 +383,7 @@ void Platform_getBattery(BatteryInfo* info) { *info = (BatteryInfo) { .ac = AC_ERROR, .percent = NAN, + .powerCurr = NAN, .energyCurr = NAN, .energyFull = NAN, }; @@ -392,9 +393,11 @@ void Platform_getBattery(BatteryInfo* info) { if (found) { bool haveTotalFull = false; bool haveTotalRemain = false; + bool haveTotalPower = false; int64_t totalFull = 0; int64_t totalRemain = 0; + int64_t totalPower = 0; /* See "sys/dev/acpi/acpibat.c" of OpenBSD source code for the indices of the last field. */ @@ -430,6 +433,27 @@ void Platform_getBattery(BatteryInfo* info) { info->energyCurr = (double) totalRemain / 1000000.0; info->energyFull = (double) totalFull / 1000000.0; } + + mib[3] = SENSOR_INTEGER; + mib[4] = 0; /* "battery state" */ + int64_t batteryState = 0; + if (sysctl(mib, 5, &s, &slen, NULL, 0) != -1) + batteryState = s.value; + + mib[3] = SENSOR_WATTS; + mib[4] = 0; /* "rate" */ + if (sysctl(mib, 5, &s, &slen, NULL, 0) != -1) { + int64_t batteryPower = s.value; + if (batteryState & 0x01) + batteryPower = -batteryPower; + + totalPower += batteryPower; + haveTotalPower = true; + } + + if (haveTotalPower) { + info->powerCurr = (double) totalPower / 1000000.0; + } } found = findDevice("acpiac0", mib, &snsrdev, &sdlen); diff --git a/pcp/Metric.h b/pcp/Metric.h index c78f28ea3..5c3c7b86c 100644 --- a/pcp/Metric.h +++ b/pcp/Metric.h @@ -107,6 +107,7 @@ typedef enum Metric_ { PCP_MEM_ZSWAPPED, /* mem.util.zswapped */ PCP_VFS_FILES_COUNT, /* vfs.files.count */ PCP_VFS_FILES_MAX, /* vfs.files.max */ + PCP_DENKI_POWER_NOW, /* denki.bat.power_now */ PCP_DENKI_ENERGY_NOW, /* denki.bat.energy_now */ PCP_DENKI_ENERGY_FULL, /* denki.bat.capacity */ diff --git a/pcp/Platform.c b/pcp/Platform.c index c4022510b..ee2cabac0 100644 --- a/pcp/Platform.c +++ b/pcp/Platform.c @@ -234,6 +234,7 @@ static const char* Platform_metricNames[] = { [PCP_MEM_ZSWAPPED] = "mem.util.zswapped", [PCP_VFS_FILES_COUNT] = "vfs.files.count", [PCP_VFS_FILES_MAX] = "vfs.files.max", + [PCP_DENKI_POWER_NOW] = "denki.bat.power_now", [PCP_DENKI_ENERGY_NOW] = "denki.bat.energy_now", [PCP_DENKI_ENERGY_FULL] = "denki.bat.capacity", @@ -868,6 +869,7 @@ void Platform_getFileDescriptors(double* used, double* max) { void Platform_getBattery(BatteryInfo* info) { info->ac = AC_ERROR; info->percent = NAN; + info->powerCurr = NAN; info->energyCurr = NAN; info->energyFull = NAN; @@ -897,6 +899,19 @@ void Platform_getBattery(BatteryInfo* info) { } free(batteryEnergyCurr); free(batteryEnergyFull); + + pmAtomValue* batteryPowerCurr = xCalloc(count, sizeof(pmAtomValue)); + if (Metric_values(PCP_DENKI_POWER_NOW, batteryPowerCurr, count, PM_TYPE_DOUBLE)) { + info->powerCurr = 0.0; + for (i = 0; i < count; i++) { + info->powerCurr += batteryPowerCurr[i].d; + } + } + free(batteryPowerCurr); + + if (info->powerCurr < 0) { + info->ac = AC_ABSENT; + } } const char* Platform_getFailedState(void) { diff --git a/solaris/Platform.c b/solaris/Platform.c index f55151acf..3de703300 100644 --- a/solaris/Platform.c +++ b/solaris/Platform.c @@ -346,6 +346,7 @@ void Platform_getBattery(BatteryInfo* info) { *info = (BatteryInfo) { .ac = AC_ERROR, .percent = NAN, + .powerCurr = NAN, .energyCurr = NAN, .energyFull = NAN, }; diff --git a/unsupported/Platform.c b/unsupported/Platform.c index aac0d221e..f3bada9f4 100644 --- a/unsupported/Platform.c +++ b/unsupported/Platform.c @@ -170,6 +170,7 @@ void Platform_getBattery(BatteryInfo* info) { *info = (BatteryInfo) { .ac = AC_ERROR, .percent = NAN, + .powerCurr = NAN, .energyCurr = NAN, .energyFull = NAN, }; From 876564f49647b942a91bfbcd958b23266329aa58 Mon Sep 17 00:00:00 2001 From: Benny Baumann Date: Fri, 15 May 2026 20:04:14 +0200 Subject: [PATCH 11/11] Enhance display of BatteryMeter with extra information --- BatteryMeter.c | 129 ++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 116 insertions(+), 13 deletions(-) diff --git a/BatteryMeter.c b/BatteryMeter.c index 963a0cb32..adbe14085 100644 --- a/BatteryMeter.c +++ b/BatteryMeter.c @@ -43,21 +43,124 @@ static void BatteryMeter_updateValues(Meter* this) { this->values[0] = info.percent; - const char* text; - switch (info.ac) { - case AC_PRESENT: - text = this->mode == TEXT_METERMODE ? " (Running on A/C)" : "(A/C)"; - break; - case AC_ABSENT: - text = this->mode == TEXT_METERMODE ? " (Running on battery)" : "(bat)"; - break; - case AC_ERROR: - default: - text = ""; - break; + bool haveEnergy = isNonnegative(info.energyCurr) && isNonnegative(info.energyFull); + + /* Without energy data there is nothing useful to show beyond the percent. */ + if (!haveEnergy) { + xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "%.1f%%", info.percent); + return; + } + + bool havePower = isfinite(info.powerCurr); + + /* stable: power unknown or |power| < 5 W – time estimate would be unreliable */ + bool isDischarging = havePower && info.powerCurr <= -5.0; + bool isCharging = havePower && info.powerCurr >= 5.0; + + /* time estimate in whole minutes; -1 means not available */ + int timeMinutes = -1; + if (isDischarging && isPositive(info.energyCurr)) { + /* floor for discharge; powerCurr is negative, use negative scaling when dividing */ + timeMinutes = (int)floor(info.energyCurr / info.powerCurr * -60.0); + } else if (isCharging && 0.95 * info.energyFull > info.energyCurr) { + /* ceil for charge */ + timeMinutes = (int)ceil((0.95 * info.energyFull - info.energyCurr) / info.powerCurr * 60.0); + } + + char* buf = this->txtBuffer; + size_t len = sizeof(this->txtBuffer); + int ret = 0; + + if (this->mode == TEXT_METERMODE) { + if (info.ac == AC_PRESENT) { + ret = xSnprintf(buf, len, "Using %s", isDischarging ? "AC+bat" : "AC"); + buf += ret; len -= ret; + } else if (info.ac == AC_ABSENT) { + ret = xSnprintf(buf, len, "Using bat"); + buf += ret; len -= ret; + } + + if (ret && len > 2) { + *buf++ = ','; + *buf++ = ' '; + *buf = 0; + len -= 2; + } + + if (isDischarging) { + ret = xSnprintf( + buf, len, "discharging at %.1fW, %.1f/%.1fWh (%.1f%%)", + -info.powerCurr, info.energyCurr, info.energyFull, info.percent + ); + buf += ret; len -= ret; + if (timeMinutes >= 0) { + ret = xSnprintf(buf, len, ", time remaining: %dh%02dm", timeMinutes / 60, timeMinutes % 60); + buf += ret; len -= ret; + } + } else if (isCharging) { + ret = xSnprintf( + buf, len, "charging at %.1fW, %.1f/%.1fWh (%.1f%%)", + info.powerCurr, info.energyCurr, info.energyFull, info.percent + ); + buf += ret; len -= ret; + + if (timeMinutes >= 0) { + ret = xSnprintf( + buf, len, ", time to full: %dh%02dm", + timeMinutes / 60, timeMinutes % 60 + ); + buf += ret; len -= ret; + } + } else { + ret = xSnprintf( + buf, len, "stable at %.1f/%.1fWh (%.1f%%)", + info.energyCurr, info.energyFull, info.percent + ); + buf += ret; len -= ret; + } + } else { + /* compact label for bar / graph modes */ + if (info.ac == AC_PRESENT) { + ret = xSnprintf(buf, len, "%s", isDischarging ? "AC+bat" : "AC"); + buf += ret; len -= ret; + } else if (info.ac == AC_ABSENT) { + ret = xSnprintf(buf, len, "bat"); + buf += ret; len -= ret; + } + + if (ret && len > 1) { + *buf++ = ' '; + *buf = 0; + len--; + } + + if (isCharging || isDischarging) { + ret = xSnprintf( + buf, len, "%+.1fW @ %.1f/%.1fWh", + info.powerCurr, info.energyCurr, info.energyFull + ); + buf += ret; len -= ret; + + if (timeMinutes >= 0) { + ret = xSnprintf( + buf, len, ", %dh%02dm", + timeMinutes / 60, timeMinutes % 60 + ); + buf += ret; len -= ret; + } + } else { + ret = xSnprintf( + buf, len, "stable @ %.1f/%.1fWh", + info.energyCurr, info.energyFull + ); + buf += ret; len -= ret; + } } - xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), "%.1f%%%s", info.percent, text); + // Simplify the pattern to always use "buf += ret; len -= ret;" for all cases + (void)ret; + (void)buf; + (void)len; } const MeterClass BatteryMeter_class = {