diff --git a/BatteryMeter.c b/BatteryMeter.c index 7ab1bac97..adbe14085 100644 --- a/BatteryMeter.c +++ b/BatteryMeter.c @@ -25,34 +25,142 @@ static const int BatteryMeter_attributes[] = { }; static void BatteryMeter_updateValues(Meter* this) { - ACPresence isOnAC; - double percent; + BatteryInfo info = { + .ac = AC_ERROR, + .percent = NAN, + .powerCurr = NAN, + .energyCurr = NAN, + .energyFull = 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; - - const char* text; - switch (isOnAC) { - 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; + this->values[0] = info.percent; + + 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", 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 = { diff --git a/BatteryMeter.h b/BatteryMeter.h index d0818b701..b69fd5643 100644 --- a/BatteryMeter.h +++ b/BatteryMeter.h @@ -18,6 +18,15 @@ typedef enum ACPresence_ { AC_ERROR } ACPresence; +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; + extern const MeterClass BatteryMeter_class; #endif diff --git a/darwin/Platform.c b/darwin/Platform.c index 2e83f46fa..8df67bfa0 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" @@ -677,9 +678,14 @@ 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, + .powerCurr = NAN, + .energyCurr = NAN, + .energyFull = NAN, + }; CFArrayRef list = NULL; @@ -710,8 +716,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; @@ -721,8 +727,56 @@ void Platform_getBattery(double* percent, ACPresence* isOnAC) { cap_max += tmp; } - if (cap_max > 0.0) - *percent = 100.0 * cap_current / cap_max; + if (cap_max > 0.0) { + info->percent = 100.0 * cap_current / cap_max; + } + + 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); + + 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); + if (ampRef) + CFRelease(ampRef); + + IOObjectRelease(batt); + } 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..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 @@ -363,18 +367,125 @@ 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, + .powerCurr = NAN, + .energyCurr = NAN, + .energyFull = 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; + + 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; + 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 }; + 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; + 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) { + 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; + } + + 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); + + 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; + } + + if (haveTotalPower) { + info->powerCurr = (double) totalPower / 1000000.0; + } } 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..d64e84c24 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 @@ -396,18 +400,120 @@ 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, + .powerCurr = NAN, + .energyCurr = NAN, + .energyFull = 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; + + 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; + 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 }; + 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; + 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) { + 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; + } + + 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); + + 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; + } + + if (haveTotalPower) { + info->powerCurr = (double) totalPower / 1000000.0; + } } 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..ba6a91014 100644 --- a/linux/Platform.c +++ b/linux/Platform.c @@ -162,8 +162,13 @@ 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, + .powerCurr = NAN, + .energyCurr = NAN, + .energyFull = NAN, +}; #ifdef HAVE_LIBCAP static enum CapMode Platform_capabilitiesMode = CAP_MODE_BASIC; @@ -833,18 +838,21 @@ 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; + info->powerCurr = NAN; + info->energyCurr = NAN; + info->energyFull = NAN; DIR* dir = opendir(SYS_POWERSUPPLY_DIR); if (!dir) @@ -852,6 +860,9 @@ static void Platform_Battery_getSysData(double* percent, ACPresence* isOnAC) { uint64_t totalFull = 0; uint64_t totalRemain = 0; + int64_t totalPower = 0; + + bool havePower = false; const struct dirent* dirEntry; while ((dirEntry = readdir(dir))) { @@ -895,61 +906,157 @@ static void Platform_Battery_getSysData(double* percent, ACPresence* isOnAC) { 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; + + bool haveBatteryCurrent = false; + bool haveBatteryPower = 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; + + int64_t batteryCurrent = 0; + int64_t batteryPower = 0; + + bool batteryIsDischarging = false; - 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 (String_eq(field, "CURRENT_NOW")) { + batteryCurrent = val; + haveBatteryCurrent = true; + continue; + } + + if (String_eq(field, "POWER_NOW")) { + batteryPower += val; + haveBatteryPower = true; + 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 (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; + } + + if (!haveBatteryPower && haveBatteryCurrent && haveBatteryVoltage) { + batteryPower = (batteryCurrent * batteryVoltage) / 1000000; + haveBatteryPower = true; } - if (!now && full && isNonnegative(capacityLevel)) - totalRemain += capacityLevel * fullCharge; + if (haveBatteryPower) { + if (batteryIsDischarging) + batteryPower = -batteryPower; + totalPower += batteryPower; + havePower = true; + } } 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 +1065,50 @@ static void Platform_Battery_getSysData(double* percent, ACPresence* isOnAC) { closedir(dir); - *percent = totalFull > 0 ? ((double) totalRemain * 100.0) / (double) totalFull : NAN; + if (totalFull > 0) { + info->percent = ((double) totalRemain * 100.0) / (double) totalFull; + info->energyCurr = (double) totalRemain / 1000000.0; + info->energyFull = (double) totalFull / 1000000.0; + } + + if (havePower) { + info->powerCurr = (double) totalPower / 1000000.0; + } } -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, + .powerCurr = NAN, + .energyCurr = NAN, + .energyFull = 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..aaf0c08b0 100644 --- a/netbsd/Platform.c +++ b/netbsd/Platform.c @@ -446,15 +446,23 @@ 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; + bool havePower = false; + intmax_t totalPower = 0; + + *info = (BatteryInfo) { + .ac = AC_ERROR, + .percent = NAN, + .powerCurr = NAN, + .energyCurr = NAN, + .energyFull = NAN, + }; int fd = open(_PATH_SYSMON, O_RDONLY); if (fd == -1) @@ -484,6 +492,17 @@ void Platform_getBattery(double* percent, ACPresence* isOnAC) { 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"); @@ -501,6 +520,7 @@ void Platform_getBattery(double* percent, ACPresence* isOnAC) { 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; @@ -509,24 +529,110 @@ void Platform_getBattery(double* percent, ACPresence* isOnAC) { 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"); + } 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) { - totalCharge += curCharge; - totalCapacity += maxCharge; + 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; + + 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 (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 && *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; + + if (totalCapacity > 0) { + 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; + } + + if (havePower) { + info->powerCurr = (double) totalPower / 1000000.0; + } 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..8b9a216e0 100644 --- a/openbsd/Platform.c +++ b/openbsd/Platform.c @@ -373,46 +373,97 @@ 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, + .powerCurr = NAN, + .energyCurr = NAN, + .energyFull = NAN, + }; + bool found = findDevice("acpibat0", mib, &snsrdev, &sdlen); - *percent = NAN; 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. */ 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; - *percent = 100 * (charge / last_full_capacity); - if (charge >= last_full_capacity) { - *percent = 100; + 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; + } + + 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); - *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/Metric.h b/pcp/Metric.h index 14e1e144f..5c3c7b86c 100644 --- a/pcp/Metric.h +++ b/pcp/Metric.h @@ -107,6 +107,9 @@ 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 */ PCP_PROC_PID, /* proc.psinfo.pid */ PCP_PROC_PPID, /* proc.psinfo.ppid */ diff --git a/pcp/Platform.c b/pcp/Platform.c index c335b379d..ee2cabac0 100644 --- a/pcp/Platform.c +++ b/pcp/Platform.c @@ -234,6 +234,9 @@ 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", [PCP_PROC_PID] = "proc.psinfo.pid", [PCP_PROC_PPID] = "proc.psinfo.ppid", @@ -863,9 +866,52 @@ 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->ac = AC_ERROR; + info->percent = NAN; + info->powerCurr = 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); + + 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/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..3de703300 100644 --- a/solaris/Platform.c +++ b/solaris/Platform.c @@ -342,7 +342,12 @@ 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, + .powerCurr = NAN, + .energyCurr = NAN, + .energyFull = 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..f3bada9f4 100644 --- a/unsupported/Platform.c +++ b/unsupported/Platform.c @@ -166,9 +166,14 @@ 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, + .powerCurr = NAN, + .energyCurr = NAN, + .energyFull = 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);