/* LINUX DOCS: https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-class-power * (C) 2024 Tim Keller * MIT License */ #include #include #include #include #include #define PSUPATH "/sys/class/power_supply" #define PERIOD 3 // number of seconds before each check #define MAX_BATTERIES 8 #define MAX_PSU_NAME_LEN 8 #define MAX_MSG_LEN 32 #define MAX_PROP_PATH_LEN 64 // variables static char batteries[MAX_BATTERIES][MAX_PSU_NAME_LEN]; static unsigned int num_batteries; static FILE *fp; static char prop_path[MAX_PROP_PATH_LEN]; static enum StatusSymbols { UNKNOWN = '?', CHARGING = '+', DISCHARGING = '-', NOT_CHARGING = '=', FULL = '=' }; // functions void load_installed_batteries() { // search through "power supplies" for "status" property (only available on batteries) DIR *dir, *subdir; struct dirent *entry, *subentry; if ((dir = opendir(PSUPATH)) == NULL) { fprintf(stderr, "failed to open power supplies filesystem: %s\n", PSUPATH); exit(EXIT_FAILURE); } // power supplies TODO warn user if max batteries exceeded num_batteries = 0; while ((entry = readdir(dir)) != NULL) { if (entry->d_name[0] == '.') continue; // skip . and .. if (strlen(entry->d_name) > MAX_PSU_NAME_LEN) { fprintf(stderr, "the power supply's name was too long: %s\n", entry->d_name); exit(EXIT_FAILURE); } // power supplies will always be a directory, no need to check beforehand :) snprintf(prop_path, MAX_PROP_PATH_LEN, PSUPATH "/%.8s", entry->d_name); // 8 is equal to MAX_PSU_NAME_LEN, prevent compiler warning if ((subdir = opendir(prop_path)) == NULL) { fprintf(stderr, "failed to open power supply: %s\n", prop_path); exit(EXIT_FAILURE); } // power supply properties while ((subentry = readdir(subdir)) != NULL) { // add to list of batteries if it has the status property if (strcmp(subentry->d_name, "status") == 0) { if(num_batteries >= MAX_BATTERIES) { fprintf(stderr, "maximum number of batteries (8) exceeded\n"); exit(EXIT_FAILURE); } strncpy(batteries[num_batteries], entry->d_name, MAX_PSU_NAME_LEN); num_batteries++; break; } } closedir(subdir); } closedir(dir); } void notifysend(char *title, char *content, NotifyUrgency urgency) { NotifyNotification *notif; notify_init("lowbat"); notif = notify_notification_new(title, content, NULL); notify_notification_set_timeout(notif, NOTIFY_EXPIRES_NEVER); notify_notification_set_urgency(notif, urgency); notify_notification_show(notif, NULL); } void read_prop(char *psu, char *prop, char *fmt, void *out) { /* this function is only designed to read one var at a time because all bat * properties will only be one var */ snprintf(prop_path, MAX_PROP_PATH_LEN, PSUPATH "/%s/%s", psu, prop); if ((fp = fopen(prop_path, "r")) != NULL) { fscanf(fp, fmt, out); fclose(fp); } else { fprintf(stderr, "failed to read fmt '%s' from property %s\n", fmt, prop_path); exit(EXIT_FAILURE); } } int get_total_capacity() { unsigned long int energy_full = 0, energy_now = 0, x; int i; if (!num_batteries) return 0; for (i = 0; i < num_batteries; i++) { read_prop(batteries[i], "energy_full", "%ld", &x); energy_full += x; read_prop(batteries[i], "energy_now", "%ld", &x); energy_now += x; } return energy_now * 100 / energy_full; } char get_status() { char status[MAX_BATTERIES]; int i; if (!num_batteries) return UNKNOWN; /* store first letter of each battery's status * valid values: "Unknown", "Charging", "Discharging", "Not charging", "Full" */ // return discharging if any batteries are discharging for (i = 0; i < num_batteries; i++) { read_prop(batteries[i], "status", "%c", &status[i]); if (status[i] == 'D') return DISCHARGING; } // return charging if any batteries are charging if (strchr(status, 'C') != NULL) return CHARGING; // return not charging if any batteries are not charging if (strchr(status, 'F') != NULL || strchr(status, 'N') != NULL) return NOT_CHARGING; // return unknown otherwise return UNKNOWN; } int main() { char status, old_status = -1; int capacity, old_capacity = -1; int warning_level = 0; // 1 if warned already, 2 if warned with critical warning char msg[MAX_MSG_LEN]; while (1) { load_installed_batteries(); status = get_status(); capacity = get_total_capacity(); if (status == DISCHARGING) { if (capacity <= 5 && warning_level < 2) { snprintf(msg, MAX_MSG_LEN, "%d%% remains", capacity); notifysend("Critical Low Battery Warning", msg, NOTIFY_URGENCY_CRITICAL); warning_level = 2; } else if (capacity <= 20 && warning_level < 1) { snprintf(msg, MAX_MSG_LEN, "%d%% remains", capacity); notifysend("Low Battery Warning", msg, NOTIFY_URGENCY_NORMAL); warning_level = 1; } } else { warning_level = 0; // clear conditions } if (status != old_status || capacity != old_capacity) { printf("%c%d%%\n", status, capacity); fflush(stdout); } old_status = status; old_capacity = capacity; sleep(PERIOD); } return 0; }