Move platform-dependent parts of Linux battery meter.

This commit is contained in:
Hisham Muhammad 2014-11-27 21:02:52 -02:00
parent b4f6b11092
commit 430c7c9a9b
11 changed files with 471 additions and 270 deletions

View File

@ -9,14 +9,13 @@ This meter written by Ian P. Hands (iphands@gmail.com, ihands@redhat.com).
#include "BatteryMeter.h" #include "BatteryMeter.h"
#include "Platform.h"
#include "ProcessList.h" #include "ProcessList.h"
#include "CRT.h" #include "CRT.h"
#include "String.h" #include "String.h"
#include <string.h> #include <string.h>
#include <stdlib.h> #include <stdlib.h>
#include <dirent.h>
#include <unistd.h>
/*{ /*{
#include "Meter.h" #include "Meter.h"
@ -26,273 +25,22 @@ typedef enum ACPresence_ {
AC_PRESENT, AC_PRESENT,
AC_ERROR AC_ERROR
} ACPresence; } ACPresence;
}*/ }*/
int BatteryMeter_attributes[] = { int BatteryMeter_attributes[] = {
BATTERY BATTERY
}; };
static unsigned long int parseUevent(FILE * file, const char *key) {
char line[100];
unsigned long int dValue = 0;
char* saveptr;
while (fgets(line, sizeof line, file)) {
if (strncmp(line, key, strlen(key)) == 0) {
char *value;
strtok_r(line, "=", &saveptr);
value = strtok_r(NULL, "=", &saveptr);
dValue = atoi(value);
break;
}
}
return dValue;
}
static unsigned long int parseBatInfo(const char *fileName, const unsigned short int lineNum, const unsigned short int wordNum) {
const char batteryPath[] = PROCDIR "/acpi/battery/";
DIR* batteryDir = opendir(batteryPath);
if (!batteryDir)
return 0;
#define MAX_BATTERIES 64
char* batteries[MAX_BATTERIES];
unsigned int nBatteries = 0;
memset(batteries, 0, MAX_BATTERIES * sizeof(char*));
struct dirent result;
struct dirent* dirEntry;
while (nBatteries < MAX_BATTERIES) {
int err = readdir_r(batteryDir, &result, &dirEntry);
if (err || !dirEntry)
break;
char* entryName = dirEntry->d_name;
if (strncmp(entryName, "BAT", 3))
continue;
batteries[nBatteries] = strdup(entryName);
nBatteries++;
}
closedir(batteryDir);
unsigned long int total = 0;
for (unsigned int i = 0; i < nBatteries; i++) {
char infoPath[30];
snprintf(infoPath, sizeof infoPath, "%s%s/%s", batteryPath, batteries[i], fileName);
FILE* file = fopen(infoPath, "r");
if (!file) {
break;
}
char line[50] = "";
for (unsigned short int i = 0; i < lineNum; i++) {
char* ok = fgets(line, sizeof line, file);
if (!ok) break;
}
fclose(file);
char *foundNumStr = String_getToken(line, wordNum);
const unsigned long int foundNum = atoi(foundNumStr);
free(foundNumStr);
total += foundNum;
}
for (unsigned int i = 0; i < nBatteries; i++) {
free(batteries[i]);
}
return total;
}
static ACPresence procAcpiCheck() {
ACPresence isOn = AC_ERROR;
const char *power_supplyPath = PROCDIR "/acpi/ac_adapter";
DIR *power_supplyDir = opendir(power_supplyPath);
if (!power_supplyDir) {
return AC_ERROR;
}
struct dirent result;
struct dirent* dirEntry;
for (;;) {
int err = readdir_r((DIR *) power_supplyDir, &result, &dirEntry);
if (err || !dirEntry)
break;
char* entryName = (char *) dirEntry->d_name;
if (entryName[0] != 'A')
continue;
char statePath[50];
snprintf((char *) statePath, sizeof statePath, "%s/%s/state", power_supplyPath, entryName);
FILE* file = fopen(statePath, "r");
if (!file) {
isOn = AC_ERROR;
continue;
}
char line[100];
fgets(line, sizeof line, file);
line[sizeof(line) - 1] = '\0';
fclose(file);
const char *isOnline = String_getToken(line, 2);
if (strcmp(isOnline, "on-line") == 0) {
isOn = AC_PRESENT;
} else {
isOn = AC_ABSENT;
}
free((char *) isOnline);
if (isOn == AC_PRESENT) {
break;
}
}
if (power_supplyDir)
closedir(power_supplyDir);
return isOn;
}
static ACPresence sysCheck() {
ACPresence isOn = AC_ERROR;
const char *power_supplyPath = "/sys/class/power_supply";
DIR *power_supplyDir = opendir(power_supplyPath);
if (!power_supplyDir) {
return AC_ERROR;
}
struct dirent result;
struct dirent* dirEntry;
for (;;) {
int err = readdir_r((DIR *) power_supplyDir, &result, &dirEntry);
if (err || !dirEntry)
break;
char* entryName = (char *) dirEntry->d_name;
if (strncmp(entryName, "A", 1)) {
continue;
}
char onlinePath[50];
snprintf((char *) onlinePath, sizeof onlinePath, "%s/%s/online", power_supplyPath, entryName);
FILE* file = fopen(onlinePath, "r");
if (!file) {
isOn = AC_ERROR;
} else {
isOn = (fgetc(file) - '0');
fclose(file);
if (isOn == AC_PRESENT) {
// If any AC adapter is being used then stop
break;
}
}
}
if (power_supplyDir)
closedir(power_supplyDir);
return isOn;
}
static ACPresence chkIsOnline() {
if (access(PROCDIR "/acpi/ac_adapter", F_OK) == 0) {
return procAcpiCheck();
} else if (access("/sys/class/power_supply", F_OK) == 0) {
return sysCheck();
} else {
return AC_ERROR;
}
}
static double getProcBatData() {
const unsigned long int totalFull = parseBatInfo("info", 3, 4);
if (totalFull == 0)
return 0;
const unsigned long int totalRemain = parseBatInfo("state", 5, 3);
if (totalRemain == 0)
return 0;
return totalRemain * 100.0 / (double) totalFull;
}
static double getSysBatData() {
const char *power_supplyPath = "/sys/class/power_supply/";
DIR *power_supplyDir = opendir(power_supplyPath);
if (!power_supplyDir)
return 0;
unsigned long int totalFull = 0;
unsigned long int totalRemain = 0;
struct dirent result;
struct dirent* dirEntry;
for (;;) {
int err = readdir_r((DIR *) power_supplyDir, &result, &dirEntry);
if (err || !dirEntry)
break;
char* entryName = (char *) dirEntry->d_name;
if (strncmp(entryName, "BAT", 3)) {
continue;
}
const char ueventPath[50];
snprintf((char *) ueventPath, sizeof ueventPath, "%s%s/uevent", power_supplyPath, entryName);
FILE *file;
if ((file = fopen(ueventPath, "r")) == NULL) {
closedir(power_supplyDir);
return 0;
}
if ((totalFull += parseUevent(file, "POWER_SUPPLY_ENERGY_FULL="))) {
totalRemain += parseUevent(file, "POWER_SUPPLY_ENERGY_NOW=");
} else {
//reset file pointer
if (fseek(file, 0, SEEK_SET) < 0) {
closedir(power_supplyDir);
fclose(file);
return 0;
}
}
//Some systems have it as CHARGE instead of ENERGY.
if ((totalFull += parseUevent(file, "POWER_SUPPLY_CHARGE_FULL="))) {
totalRemain += parseUevent(file, "POWER_SUPPLY_CHARGE_NOW=");
} else {
//reset file pointer
if (fseek(file, 0, SEEK_SET) < 0) {
closedir(power_supplyDir);
fclose(file);
return 0;
}
}
fclose(file);
}
const double percent = totalFull > 0 ? ((double) totalRemain * 100) / (double) totalFull : 0;
closedir(power_supplyDir);
return percent;
}
static void BatteryMeter_setValues(Meter * this, char *buffer, int len) { static void BatteryMeter_setValues(Meter * this, char *buffer, int len) {
double percent = getProcBatData(); ACPresence isOnAC;
double percent;
if (percent == 0) { Platform_getBatteryLevel(&percent, &isOnAC);
percent = getSysBatData();
if (percent == 0) { if (percent == -1) {
snprintf(buffer, len, "n/a"); this->values[0] = 0;
return; snprintf(buffer, len, "n/a");
} return;
} }
this->values[0] = percent; this->values[0] = percent;
@ -308,11 +56,9 @@ static void BatteryMeter_setValues(Meter * this, char *buffer, int len) {
onBatteryText = "%.1f%%(bat)"; onBatteryText = "%.1f%%(bat)";
} }
ACPresence isOnLine = chkIsOnline(); if (isOnAC == AC_PRESENT) {
if (isOnLine == AC_PRESENT) {
snprintf(buffer, len, onAcText, percent); snprintf(buffer, len, onAcText, percent);
} else if (isOnLine == AC_ABSENT) { } else if (isOnAC == AC_ABSENT) {
snprintf(buffer, len, onBatteryText, percent); snprintf(buffer, len, onBatteryText, percent);
} else { } else {
snprintf(buffer, len, unknownText, percent); snprintf(buffer, len, unknownText, percent);

View File

@ -19,7 +19,6 @@ typedef enum ACPresence_ {
AC_ERROR AC_ERROR
} ACPresence; } ACPresence;
extern int BatteryMeter_attributes[]; extern int BatteryMeter_attributes[];
extern MeterClass BatteryMeter_class; extern MeterClass BatteryMeter_class;

View File

@ -35,10 +35,10 @@ HostnameMeter.h OpenFilesScreen.h Affinity.h IncSet.h Action.h
if HTOP_LINUX if HTOP_LINUX
myhtopplatsources = linux/Platform.c linux/IOPriorityPanel.c linux/IOPriority.c \ myhtopplatsources = linux/Platform.c linux/IOPriorityPanel.c linux/IOPriority.c \
linux/LinuxProcess.c linux/LinuxProcessList.c linux/LinuxCRT.c linux/LinuxProcess.c linux/LinuxProcessList.c linux/LinuxCRT.c linux/Battery.c
myhtopplatheaders = linux/Platform.h linux/IOPriorityPanel.h linux/IOPriority.h \ myhtopplatheaders = linux/Platform.h linux/IOPriorityPanel.h linux/IOPriority.h \
linux/LinuxProcess.h linux/LinuxProcessList.h linux/LinuxCRT.h linux/LinuxProcess.h linux/LinuxProcessList.h linux/LinuxCRT.h linux/Battery.h
endif endif
if HTOP_UNSUPPORTED if HTOP_UNSUPPORTED

269
linux/Battery.c Normal file
View File

@ -0,0 +1,269 @@
/*
htop - linux/Battery.c
(C) 2004-2014 Hisham H. Muhammad
Released under the GNU GPL, see the COPYING file
in the source distribution for its full text.
Linux battery readings written by Ian P. Hands (iphands@gmail.com, ihands@redhat.com).
*/
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <dirent.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include "BatteryMeter.h"
#include "String.h"
static unsigned long int parseUevent(FILE * file, const char *key) {
char line[100];
unsigned long int dValue = 0;
char* saveptr;
while (fgets(line, sizeof line, file)) {
if (strncmp(line, key, strlen(key)) == 0) {
char *value;
strtok_r(line, "=", &saveptr);
value = strtok_r(NULL, "=", &saveptr);
dValue = atoi(value);
break;
}
}
return dValue;
}
static unsigned long int parseBatInfo(const char *fileName, const unsigned short int lineNum, const unsigned short int wordNum) {
const char batteryPath[] = PROCDIR "/acpi/battery/";
DIR* batteryDir = opendir(batteryPath);
if (!batteryDir)
return 0;
#define MAX_BATTERIES 64
char* batteries[MAX_BATTERIES];
unsigned int nBatteries = 0;
memset(batteries, 0, MAX_BATTERIES * sizeof(char*));
struct dirent result;
struct dirent* dirEntry;
while (nBatteries < MAX_BATTERIES) {
int err = readdir_r(batteryDir, &result, &dirEntry);
if (err || !dirEntry)
break;
char* entryName = dirEntry->d_name;
if (strncmp(entryName, "BAT", 3))
continue;
batteries[nBatteries] = strdup(entryName);
nBatteries++;
}
closedir(batteryDir);
unsigned long int total = 0;
for (unsigned int i = 0; i < nBatteries; i++) {
char infoPath[30];
snprintf(infoPath, sizeof infoPath, "%s%s/%s", batteryPath, batteries[i], fileName);
FILE* file = fopen(infoPath, "r");
if (!file) {
break;
}
char line[50] = "";
for (unsigned short int i = 0; i < lineNum; i++) {
char* ok = fgets(line, sizeof line, file);
if (!ok) break;
}
fclose(file);
char *foundNumStr = String_getToken(line, wordNum);
const unsigned long int foundNum = atoi(foundNumStr);
free(foundNumStr);
total += foundNum;
}
for (unsigned int i = 0; i < nBatteries; i++) {
free(batteries[i]);
}
return total;
}
static ACPresence procAcpiCheck() {
ACPresence isOn = AC_ERROR;
const char *power_supplyPath = PROCDIR "/acpi/ac_adapter";
DIR *power_supplyDir = opendir(power_supplyPath);
if (!power_supplyDir) {
return AC_ERROR;
}
struct dirent result;
struct dirent* dirEntry;
for (;;) {
int err = readdir_r((DIR *) power_supplyDir, &result, &dirEntry);
if (err || !dirEntry)
break;
char* entryName = (char *) dirEntry->d_name;
if (entryName[0] != 'A')
continue;
char statePath[50];
snprintf((char *) statePath, sizeof statePath, "%s/%s/state", power_supplyPath, entryName);
FILE* file = fopen(statePath, "r");
if (!file) {
isOn = AC_ERROR;
continue;
}
char line[100];
fgets(line, sizeof line, file);
line[sizeof(line) - 1] = '\0';
fclose(file);
const char *isOnline = String_getToken(line, 2);
if (strcmp(isOnline, "on-line") == 0) {
isOn = AC_PRESENT;
} else {
isOn = AC_ABSENT;
}
free((char *) isOnline);
if (isOn == AC_PRESENT) {
break;
}
}
if (power_supplyDir)
closedir(power_supplyDir);
return isOn;
}
static ACPresence sysCheck() {
ACPresence isOn = AC_ERROR;
const char *power_supplyPath = "/sys/class/power_supply";
DIR *power_supplyDir = opendir(power_supplyPath);
if (!power_supplyDir) {
return AC_ERROR;
}
struct dirent result;
struct dirent* dirEntry;
for (;;) {
int err = readdir_r((DIR *) power_supplyDir, &result, &dirEntry);
if (err || !dirEntry)
break;
char* entryName = (char *) dirEntry->d_name;
if (strncmp(entryName, "A", 1)) {
continue;
}
char onlinePath[50];
snprintf((char *) onlinePath, sizeof onlinePath, "%s/%s/online", power_supplyPath, entryName);
FILE* file = fopen(onlinePath, "r");
if (!file) {
isOn = AC_ERROR;
} else {
isOn = (fgetc(file) - '0');
fclose(file);
if (isOn == AC_PRESENT) {
// If any AC adapter is being used then stop
break;
}
}
}
if (power_supplyDir)
closedir(power_supplyDir);
return isOn;
}
ACPresence Battery_isOnAC() {
if (access(PROCDIR "/acpi/ac_adapter", F_OK) == 0) {
return procAcpiCheck();
} else if (access("/sys/class/power_supply", F_OK) == 0) {
return sysCheck();
} else {
return AC_ERROR;
}
}
double Battery_getProcBatData() {
const unsigned long int totalFull = parseBatInfo("info", 3, 4);
if (totalFull == 0)
return 0;
const unsigned long int totalRemain = parseBatInfo("state", 5, 3);
if (totalRemain == 0)
return 0;
return totalRemain * 100.0 / (double) totalFull;
}
double Battery_getSysBatData() {
const char *power_supplyPath = "/sys/class/power_supply/";
DIR *power_supplyDir = opendir(power_supplyPath);
if (!power_supplyDir)
return 0;
unsigned long int totalFull = 0;
unsigned long int totalRemain = 0;
struct dirent result;
struct dirent* dirEntry;
for (;;) {
int err = readdir_r((DIR *) power_supplyDir, &result, &dirEntry);
if (err || !dirEntry)
break;
char* entryName = (char *) dirEntry->d_name;
if (strncmp(entryName, "BAT", 3)) {
continue;
}
const char ueventPath[50];
snprintf((char *) ueventPath, sizeof ueventPath, "%s%s/uevent", power_supplyPath, entryName);
FILE *file;
if ((file = fopen(ueventPath, "r")) == NULL) {
closedir(power_supplyDir);
return 0;
}
if ((totalFull += parseUevent(file, "POWER_SUPPLY_ENERGY_FULL="))) {
totalRemain += parseUevent(file, "POWER_SUPPLY_ENERGY_NOW=");
} else {
//reset file pointer
if (fseek(file, 0, SEEK_SET) < 0) {
closedir(power_supplyDir);
fclose(file);
return 0;
}
}
//Some systems have it as CHARGE instead of ENERGY.
if ((totalFull += parseUevent(file, "POWER_SUPPLY_CHARGE_FULL="))) {
totalRemain += parseUevent(file, "POWER_SUPPLY_CHARGE_NOW=");
} else {
//reset file pointer
if (fseek(file, 0, SEEK_SET) < 0) {
closedir(power_supplyDir);
fclose(file);
return 0;
}
}
fclose(file);
}
const double percent = totalFull > 0 ? ((double) totalRemain * 100) / (double) totalFull : 0;
closedir(power_supplyDir);
return percent;
}

24
linux/Battery.h Normal file
View File

@ -0,0 +1,24 @@
/* Do not edit this file. It was automatically generated. */
#ifndef HEADER_Battery
#define HEADER_Battery
/*
htop - linux/Battery.h
(C) 2004-2014 Hisham H. Muhammad
Released under the GNU GPL, see the COPYING file
in the source distribution for its full text.
Linux battery readings written by Ian P. Hands (iphands@gmail.com, ihands@redhat.com).
*/
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
ACPresence Battery_isOnAC();
double Battery_getProcBatData();
double Battery_getSysBatData();
#endif

17
linux/LinuxCRT.h Normal file
View File

@ -0,0 +1,17 @@
/* Do not edit this file. It was automatically generated. */
#ifndef HEADER_LinuxCRT
#define HEADER_LinuxCRT
/*
htop - LinuxCRT.h
(C) 2014 Hisham H. Muhammad
Released under the GNU GPL, see the COPYING file
in the source distribution for its full text.
*/
#ifdef HAVE_EXECINFO_H
#endif
void CRT_handleSIGSEGV(int sgn);
#endif

39
linux/LinuxProcess.h Normal file
View File

@ -0,0 +1,39 @@
/* Do not edit this file. It was automatically generated. */
#ifndef HEADER_LinuxProcess
#define HEADER_LinuxProcess
/*
htop - LinuxProcess.h
(C) 2014 Hisham H. Muhammad
Released under the GNU GPL, see the COPYING file
in the source distribution for its full text.
*/
#include "IOPriority.h"
typedef struct LinuxProcess_ {
Process super;
IOPriority ioPriority;
} LinuxProcess;
/*
[1] Note that before kernel 2.6.26 a process that has not asked for
an io priority formally uses "none" as scheduling class, but the
io scheduler will treat such processes as if it were in the best
effort class. The priority within the best effort class will be
dynamically derived from the cpu nice level of the process:
extern io_priority;
*/
#define LinuxProcess_effectiveIOPriority(p_) (IOPriority_class(p_->ioPriority) == IOPRIO_CLASS_NONE ? IOPriority_tuple(IOPRIO_CLASS_BE, (p_->super.nice + 20) / 5) : p_->ioPriority)
IOPriority LinuxProcess_updateIOPriority(LinuxProcess* this);
bool LinuxProcess_setIOPriority(LinuxProcess* this, IOPriority ioprio);
void LinuxProcess_writeField(LinuxProcess* this, RichString* str, ProcessField field);
long LinuxProcess_compare(const void* v1, const void* v2);
#endif

56
linux/LinuxProcessList.h Normal file
View File

@ -0,0 +1,56 @@
/* Do not edit this file. It was automatically generated. */
#ifndef HEADER_LinuxProcessList
#define HEADER_LinuxProcessList
/*
htop - LinuxProcessList.h
(C) 2014 Hisham H. Muhammad
Released under the GNU GPL, see the COPYING file
in the source distribution for its full text.
*/
#include "ProcessList.h"
#ifndef PROCDIR
#define PROCDIR "/proc"
#endif
#ifndef PROCSTATFILE
#define PROCSTATFILE PROCDIR "/stat"
#endif
#ifndef PROCMEMINFOFILE
#define PROCMEMINFOFILE PROCDIR "/meminfo"
#endif
ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* pidWhiteList);
void ProcessList_delete(ProcessList* this);
#ifdef HAVE_TASKSTATS
#endif
#ifdef HAVE_OPENVZ
#endif
#ifdef HAVE_CGROUP
#endif
#ifdef HAVE_VSERVER
#endif
#ifdef HAVE_OOM
#endif
void ProcessList_scan(ProcessList* this);
#endif

View File

@ -9,6 +9,8 @@ in the source distribution for its full text.
#include "IOPriority.h" #include "IOPriority.h"
#include "IOPriorityPanel.h" #include "IOPriorityPanel.h"
#include "LinuxProcess.h" #include "LinuxProcess.h"
#include "Battery.h"
#include "Meter.h" #include "Meter.h"
#include "CPUMeter.h" #include "CPUMeter.h"
#include "MemoryMeter.h" #include "MemoryMeter.h"
@ -16,7 +18,6 @@ in the source distribution for its full text.
#include "TasksMeter.h" #include "TasksMeter.h"
#include "LoadAverageMeter.h" #include "LoadAverageMeter.h"
#include "UptimeMeter.h" #include "UptimeMeter.h"
#include "BatteryMeter.h"
#include "ClockMeter.h" #include "ClockMeter.h"
#include "HostnameMeter.h" #include "HostnameMeter.h"
@ -25,6 +26,7 @@ in the source distribution for its full text.
/*{ /*{
#include "Action.h" #include "Action.h"
#include "BatteryMeter.h"
}*/ }*/
static Htop_Reaction Platform_actionSetIOPriority(Panel* panel, ProcessList* pl, Header* header) { static Htop_Reaction Platform_actionSetIOPriority(Panel* panel, ProcessList* pl, Header* header) {
@ -102,3 +104,19 @@ int Platform_getMaxPid() {
return maxPid; return maxPid;
} }
void Platform_getBatteryLevel(double* level, ACPresence* isOnAC) {
double percent = Battery_getProcBatData();
if (percent == 0) {
percent = Battery_getSysBatData();
if (percent == 0) {
*level = -1;
*isOnAC = AC_ERROR;
return;
}
}
*isOnAC = Battery_isOnAC();
*level = percent;
}

27
linux/Platform.h Normal file
View File

@ -0,0 +1,27 @@
/* Do not edit this file. It was automatically generated. */
#ifndef HEADER_Platform
#define HEADER_Platform
/*
htop - linux/Platform.h
(C) 2014 Hisham H. Muhammad
Released under the GNU GPL, see the COPYING file
in the source distribution for its full text.
*/
#include "Action.h"
#include "BatteryMeter.h"
void Platform_setBindings(Htop_Action* keys);
extern MeterClass* Platform_meterTypes[];
int Platform_getUptime();
void Platform_getLoadAverage(double* one, double* five, double* fifteen);
int Platform_getMaxPid();
void Platform_getBatteryLevel(double* level, ACPresence* isOnAC);
#endif

View File

@ -11,13 +11,13 @@ in the source distribution for its full text.
#include "SwapMeter.h" #include "SwapMeter.h"
#include "TasksMeter.h" #include "TasksMeter.h"
#include "LoadAverageMeter.h" #include "LoadAverageMeter.h"
#include "BatteryMeter.h"
#include "ClockMeter.h" #include "ClockMeter.h"
#include "HostnameMeter.h" #include "HostnameMeter.h"
#include "UptimeMeter.h" #include "UptimeMeter.h"
/*{ /*{
#include "Action.h" #include "Action.h"
#include "BatteryMeter.h"
}*/ }*/
void Platform_setBindings(Htop_Action* keys) { void Platform_setBindings(Htop_Action* keys) {
@ -58,3 +58,9 @@ void Platform_getLoadAverage(double* one, double* five, double* fifteen) {
int Platform_getMaxPid() { int Platform_getMaxPid() {
return -1; return -1;
} }
void Platform_getBatteryLevel(double* level, ACPresence* isOnAC) {
*level = -1;
*isOnAC = AC_ERROR;
}