Last active
May 5, 2024 06:29
-
-
Save tomtzook/d9ab3327fe5e78d24569cc1bba284b68 to your computer and use it in GitHub Desktop.
Get battery status on a Linux laptop from a C program
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdio.h> | |
#include <string.h> | |
#include <errno.h> | |
#include <dirent.h> | |
#include <linux/limits.h> | |
#include <regex.h> | |
#include <stdbool.h> | |
#include <stdlib.h> | |
#define DATADIR "/sys/class/power_supply" | |
#define CHARGE_NOW "energy_now" | |
#define CHARGE_FULL "energy_full" | |
#define CHARGE_STATUS "status" | |
#define CHARGE_STATUS_CHARGING 0 | |
#define CHARGE_STATUS_DISCHARGING 1 | |
#define CHARGE_STATUS_FULL 2 | |
#define CHARGE_STATUS_CHARGING_STR "Charging" | |
#define CHARGE_STATUS_DISCHARGING_STR "Discharging" | |
#define CHARGE_STATUS_FULL_STR "Full" | |
#define ERROR_FOLDER_NOT_FOUND (-1) | |
#define ERROR_FILE_NOT_FOUND (-2) | |
#define ERROR_REGEX_COMPILE (-3) | |
#define ERROR_REGEX_FIND (-4) | |
#define ERROR_FILE_READING (-5) | |
#define ERROR_CHARGE_STATUS_UNKNOWN (-6) | |
static int read_charge_level(const char* data_dir, | |
const char* sub_dir, | |
char* buffer, | |
float* charge_level) { | |
FILE* file_current = NULL; | |
FILE* file_full = NULL; | |
long current; | |
long full; | |
int result = 0; | |
snprintf(buffer, PATH_MAX, "%s/%s/%s", data_dir, sub_dir, CHARGE_NOW); | |
file_current = fopen(buffer, "r"); | |
snprintf(buffer, PATH_MAX, "%s/%s/%s", data_dir, sub_dir, CHARGE_FULL); | |
file_full = fopen(buffer, "r"); | |
if (file_current == NULL || file_full == NULL) { | |
result = ERROR_FILE_NOT_FOUND; | |
goto clean; | |
} | |
if (fscanf(file_current, "%ld", ¤t) != 1 || | |
fscanf(file_full, "%ld", &full) != 1) { | |
result = ERROR_FILE_READING; | |
goto clean; | |
} | |
*charge_level = (current / (float)full); | |
clean: | |
if (file_current != NULL) fclose(file_current); | |
if (file_full != NULL) fclose(file_full); | |
return result; | |
} | |
static int read_charge_status(const char* data_dir, | |
const char* sub_dir, | |
char* buffer, | |
int* charge_status) { | |
FILE* file = NULL; | |
int result = 0; | |
snprintf(buffer, PATH_MAX, "%s/%s/%s", data_dir, sub_dir, CHARGE_STATUS); | |
file = fopen(buffer, "r"); | |
if (file == NULL) { | |
result = ERROR_FILE_NOT_FOUND; | |
goto clean; | |
} | |
if (fscanf(file, "%s", buffer) != 1) { | |
result = ERROR_FILE_READING; | |
goto clean; | |
} | |
printf("%s\n", buffer); | |
if (strcmp(buffer, CHARGE_STATUS_CHARGING_STR) == 0) { | |
*charge_status = CHARGE_STATUS_CHARGING; | |
} else if (strcmp(buffer, CHARGE_STATUS_DISCHARGING_STR) == 0) { | |
*charge_status = CHARGE_STATUS_DISCHARGING; | |
} else if (strcmp(buffer, CHARGE_STATUS_FULL_STR) == 0) { | |
*charge_status = CHARGE_STATUS_FULL; | |
} else { | |
result = ERROR_CHARGE_STATUS_UNKNOWN; | |
goto clean; | |
} | |
clean: | |
if (file != NULL) fclose(file); | |
return result; | |
} | |
int get_battery_status(float *level, int *charge_status) { | |
DIR* directory; | |
struct dirent* dirent; | |
char buffer[PATH_MAX]; | |
int result = 0; | |
regex_t regex; | |
bool data_found = false; | |
if ((directory = opendir(DATADIR)) == NULL) { | |
return ERROR_FOLDER_NOT_FOUND; | |
} | |
while ((dirent = readdir(directory)) != NULL) { | |
snprintf(buffer, PATH_MAX, "%s/%s", DATADIR, dirent->d_name); | |
if (regcomp(®ex, "BAT[0-9]+", REG_EXTENDED) != 0) { | |
result = ERROR_REGEX_COMPILE; | |
goto clean; | |
} | |
if (regexec(®ex, dirent->d_name, 0, NULL, 0) != 0) { | |
regfree(®ex); | |
continue; | |
} | |
regfree(®ex); | |
result = read_charge_level(DATADIR, dirent->d_name, | |
buffer, level); | |
if (result != 0) { | |
continue; | |
} | |
result = read_charge_status(DATADIR, dirent->d_name, | |
buffer, charge_status); | |
if (result == 0) { | |
data_found = true; | |
break; | |
} | |
} | |
if (!data_found) { | |
result = ERROR_REGEX_FIND; | |
goto clean; | |
} | |
clean: | |
if (directory != NULL) closedir(directory); | |
return result; | |
} | |
int main(int argc, char** argv) { | |
float level; | |
int charge_status; | |
int result = get_battery_status(&level, &charge_status); | |
if (result == 0) { | |
char charge_status_str[20] = {0}; | |
switch (charge_status) { | |
case CHARGE_STATUS_CHARGING: | |
strcpy(charge_status_str, CHARGE_STATUS_CHARGING_STR); | |
break; | |
case CHARGE_STATUS_DISCHARGING: | |
strcpy(charge_status_str, CHARGE_STATUS_DISCHARGING_STR); | |
break; | |
case CHARGE_STATUS_FULL: | |
strcpy(charge_status_str, CHARGE_STATUS_FULL_STR); | |
break; | |
} | |
printf("Level: %.2f, Charge: %s\n", level, charge_status_str); | |
} | |
return result; | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Not sure about consistency of the
CHARGE_NOW
andCHARGE_FULL
files across versions. So might need adaptation.