]> git.sur5r.net Git - i3/i3status/commitdiff
Implement aggregates for batteries. 140/head
authorTommie Gannert <tommie@gannert.se>
Mon, 1 Aug 2016 17:03:03 +0000 (18:03 +0100)
committerTommie Gannert <tommie@gannert.se>
Sun, 28 Aug 2016 13:29:08 +0000 (14:29 +0100)
Using title number all, this enables aggregates. Note that FreeBSD and
OpenBSD previously only reported aggregates, so this is bringing Linux
and NetBSD that functionality.

Changes the default battery reporting to the aggregate since most
users probably don't care about individual batteries. For single-battery
systems there should be no change.

Fixes one obvious memory leak in NetBSD.

i3status.c
i3status.conf
include/i3status.h
man/i3status.man
src/print_battery_info.c

index 892d4aaa52c8eff401912b92134d0a1ab296777a..5e177450ae7f6be5f5cf5a59eea96e0dbf588c9f 100644 (file)
@@ -664,7 +664,7 @@ int main(int argc, char *argv[]) {
 
             CASE_SEC_TITLE("battery") {
                 SEC_OPEN_MAP("battery");
-                print_battery_info(json_gen, buffer, atoi(title), cfg_getstr(sec, "path"), cfg_getstr(sec, "format"), cfg_getstr(sec, "format_down"), cfg_getstr(sec, "status_chr"), cfg_getstr(sec, "status_bat"), cfg_getstr(sec, "status_unk"), cfg_getstr(sec, "status_full"), cfg_getint(sec, "low_threshold"), cfg_getstr(sec, "threshold_type"), cfg_getbool(sec, "last_full_capacity"), cfg_getbool(sec, "integer_battery_capacity"), cfg_getbool(sec, "hide_seconds"));
+                print_battery_info(json_gen, buffer, (strcasecmp(title, "all") == 0 ? -1 : atoi(title)), cfg_getstr(sec, "path"), cfg_getstr(sec, "format"), cfg_getstr(sec, "format_down"), cfg_getstr(sec, "status_chr"), cfg_getstr(sec, "status_bat"), cfg_getstr(sec, "status_unk"), cfg_getstr(sec, "status_full"), cfg_getint(sec, "low_threshold"), cfg_getstr(sec, "threshold_type"), cfg_getbool(sec, "last_full_capacity"), cfg_getbool(sec, "integer_battery_capacity"), cfg_getbool(sec, "hide_seconds"));
                 SEC_CLOSE_MAP;
             }
 
index 2d71a54af555bd4174bae7a277b7872a2edfd5f7..7f37964b9c19a975bfc98df2badb894760932fd1 100644 (file)
@@ -15,7 +15,7 @@ order += "ipv6"
 order += "disk /"
 order += "wireless _first_"
 order += "ethernet _first_"
-order += "battery 0"
+order += "battery all"
 order += "load"
 order += "tztime local"
 
@@ -30,7 +30,7 @@ ethernet _first_ {
         format_down = "E: down"
 }
 
-battery 0 {
+battery all {
         format = "%status %percentage %remaining"
 }
 
index 4d2d0f10ee6ae8ff259a1a8c640686df534fddd3..3911e04ccba0f8744e7333883527db2d5d0d0ea5 100644 (file)
@@ -168,11 +168,6 @@ char *pct_mark;
         }                                                                                      \
     } while (0)
 
-typedef enum { CS_DISCHARGING,
-               CS_CHARGING,
-               CS_UNKNOWN,
-               CS_FULL } charging_status_t;
-
 /*
  * The "min_width" module option may either be defined as a string or a number.
  */
index d0ca2a0c2eb2a00eeba17a5640c844fdb58ec985..cdde2aa3906dcb63fdbe315f03033a23104db63e 100644 (file)
@@ -336,6 +336,10 @@ colored red. The low_threshold type can be of threshold_type "time" or
 "percentage". So, if you configure low_threshold to 10 and threshold_type to
 "time", and your battery lasts another 9 minutes, it will be colored red.
 
+To show an aggregate of all batteries in the system, use "all" as the number. In
+this case (for Linux), the /sys path must contain the "%d" sequence. Otherwise,
+the number indicates the battery index as reported in /sys.
+
 Optionally custom strings including any UTF-8 symbols can be used for different
 battery states. This makes it possible to display individual symbols
 for each state (charging, discharging, unknown, full)
@@ -343,7 +347,9 @@ Of course it will also work with special iconic fonts, such as FontAwesome.
 If any of these special status strings are omitted, the default (CHR, BAT, UNK,
 FULL) is used.
 
-*Example order*: +battery 0+
+*Example order (for the first battery)*: +battery 0+
+
+*Example order (aggregate of all batteries)*: +battery all+
 
 *Example format*: +%status %remaining (%emptytime %consumption)+
 
@@ -361,7 +367,9 @@ FULL) is used.
 
 *Example threshold_type*: +time+
 
-*Example path*: +/sys/class/power_supply/CMB1/uevent+
+*Example path (%d replaced by title number)*: +/sys/class/power_supply/CMB%d/uevent+
+
+*Example path (ignoring the number)*: +/sys/class/power_supply/CMB1/uevent+
 
 === CPU-Temperature
 
index 27473a2db2ab549ff437907d2d4b38d46623192e..52d503149d4e2b6081d83517e62cd88daf190741 100644 (file)
@@ -9,6 +9,12 @@
 
 #include "i3status.h"
 
+#if defined(LINUX)
+#include <errno.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#endif
+
 #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__DragonFly__)
 #include <sys/types.h>
 #include <sys/sysctl.h>
 #include <sys/envsys.h>
 #endif
 
+typedef enum {
+    CS_UNKNOWN,
+    CS_DISCHARGING,
+    CS_CHARGING,
+    CS_FULL,
+} charging_status_t;
+
+/* A description of the state of one or more batteries. */
 struct battery_info {
-    int full_design;
-    int full_last;
-    int remaining;
+    /* measured properties */
+    int full_design;  /* in uAh */
+    int full_last;    /* in uAh */
+    int remaining;    /* in uAh */
+    int present_rate; /* in uA, always non-negative */
 
-    int present_rate;
+    /* derived properties */
     int seconds_remaining;
     float percentage_remaining;
     charging_status_t status;
 };
 
+#if defined(LINUX) || defined(__NetBSD__)
+/*
+ * Add batt_info data to acc.
+ */
+static void add_battery_info(struct battery_info *acc, const struct battery_info *batt_info) {
+    if (acc->remaining < 0) {
+        /* initialize accumulator so we can add to it */
+        acc->full_design = 0;
+        acc->full_last = 0;
+        acc->remaining = 0;
+        acc->present_rate = 0;
+    }
+
+    acc->full_design += batt_info->full_design;
+    acc->full_last += batt_info->full_last;
+    acc->remaining += batt_info->remaining;
+
+    /* make present_rate negative for discharging and positive for charging */
+    int present_rate = (acc->status == CS_DISCHARGING ? -1 : 1) * acc->present_rate;
+    present_rate += (batt_info->status == CS_DISCHARGING ? -1 : 1) * batt_info->present_rate;
+
+    /* merge status */
+    switch (acc->status) {
+        case CS_UNKNOWN:
+            acc->status = batt_info->status;
+            break;
+
+        case CS_DISCHARGING:
+            if (present_rate > 0)
+                acc->status = CS_CHARGING;
+            /* else if batt_info is DISCHARGING: no conflict
+             * else if batt_info is CHARGING: present_rate should indicate that
+             * else if batt_info is FULL: but something else is discharging */
+            break;
+
+        case CS_CHARGING:
+            if (present_rate < 0)
+                acc->status = CS_DISCHARGING;
+            /* else if batt_info is DISCHARGING: present_rate should indicate that
+             * else if batt_info is CHARGING: no conflict
+             * else if batt_info is FULL: but something else is charging */
+            break;
+
+        case CS_FULL:
+            if (batt_info->status != CS_UNKNOWN)
+                acc->status = batt_info->status;
+            /* else: retain FULL, since it is more specific than UNKNOWN */
+            break;
+    }
+
+    acc->present_rate = abs(present_rate);
+}
+#endif
+
 static bool slurp_battery_info(struct battery_info *batt_info, yajl_gen json_gen, char *buffer, int number, const char *path, const char *format_down) {
     char *outwalk = buffer;
 
@@ -185,12 +255,9 @@ static bool slurp_battery_info(struct battery_info *batt_info, yajl_gen json_gen
     /*
      * Using envsys(4) via sysmon(4).
      */
-    bool watt_as_unit = false;
-    int voltage = -1;
     int fd, rval;
     bool is_found = false;
-    char *sensor_desc;
-    bool is_full = false;
+    char sensor_desc[16];
 
     prop_dictionary_t dict;
     prop_array_t array;
@@ -198,7 +265,8 @@ static bool slurp_battery_info(struct battery_info *batt_info, yajl_gen json_gen
     prop_object_iterator_t iter2;
     prop_object_t obj, obj2, obj3, obj4, obj5;
 
-    asprintf(&sensor_desc, "acpibat%d", number);
+    if (number >= 0)
+        (void)snprintf(sensor_desc, sizeof(sensor_desc), "acpibat%d", number);
 
     fd = open("/dev/sysmon", O_RDONLY);
     if (fd < 0) {
@@ -227,9 +295,17 @@ static bool slurp_battery_info(struct battery_info *batt_info, yajl_gen json_gen
     /* iterate over the dictionary returned by the kernel */
     while ((obj = prop_object_iterator_next(iter)) != NULL) {
         /* skip this dict if it's not what we're looking for */
-        if (strcmp(sensor_desc,
-                   prop_dictionary_keysym_cstring_nocopy(obj)) != 0)
-            continue;
+        if (number < 0) {
+            /* we want all batteries */
+            if (!BEGINS_WITH(prop_dictionary_keysym_cstring_nocopy(obj),
+                             "acpibat"))
+                continue;
+        } else {
+            /* we want a specific battery */
+            if (strcmp(sensor_desc,
+                       prop_dictionary_keysym_cstring_nocopy(obj)) != 0)
+                continue;
+        }
 
         is_found = true;
 
@@ -249,6 +325,16 @@ static bool slurp_battery_info(struct battery_info *batt_info, yajl_gen json_gen
             return false;
         }
 
+        struct battery_info batt_buf = {
+            .full_design = 0,
+            .full_last = 0,
+            .remaining = 0,
+            .present_rate = 0,
+            .status = CS_UNKNOWN,
+        };
+        int voltage = -1;
+        bool watt_as_unit = false;
+
         /* iterate over array of dicts specific to target battery */
         while ((obj2 = prop_object_iterator_next(iter2)) != NULL) {
             obj3 = prop_dictionary_get(obj2, "description");
@@ -260,19 +346,16 @@ static bool slurp_battery_info(struct battery_info *batt_info, yajl_gen json_gen
                 obj3 = prop_dictionary_get(obj2, "cur-value");
 
                 if (prop_number_integer_value(obj3))
-                    batt_info->status = CS_CHARGING;
+                    batt_buf.status = CS_CHARGING;
                 else
-                    batt_info->status = CS_DISCHARGING;
+                    batt_buf.status = CS_DISCHARGING;
             } else if (strcmp("charge", prop_string_cstring_nocopy(obj3)) == 0) {
                 obj3 = prop_dictionary_get(obj2, "cur-value");
                 obj4 = prop_dictionary_get(obj2, "max-value");
                 obj5 = prop_dictionary_get(obj2, "type");
 
-                batt_info->remaining = prop_number_integer_value(obj3);
-                batt_info->full_design = prop_number_integer_value(obj4);
-
-                if (batt_info->remaining == batt_info->full_design)
-                    is_full = true;
+                batt_buf.remaining = prop_number_integer_value(obj3);
+                batt_buf.full_design = prop_number_integer_value(obj4);
 
                 if (strcmp("Ampere hour", prop_string_cstring_nocopy(obj5)) == 0)
                     watt_as_unit = false;
@@ -280,19 +363,31 @@ static bool slurp_battery_info(struct battery_info *batt_info, yajl_gen json_gen
                     watt_as_unit = true;
             } else if (strcmp("discharge rate", prop_string_cstring_nocopy(obj3)) == 0) {
                 obj3 = prop_dictionary_get(obj2, "cur-value");
-                batt_info->present_rate = prop_number_integer_value(obj3);
+                batt_buf.present_rate = prop_number_integer_value(obj3);
             } else if (strcmp("charge rate", prop_string_cstring_nocopy(obj3)) == 0) {
                 obj3 = prop_dictionary_get(obj2, "cur-value");
                 batt_info->present_rate = prop_number_integer_value(obj3);
             } else if (strcmp("last full cap", prop_string_cstring_nocopy(obj3)) == 0) {
                 obj3 = prop_dictionary_get(obj2, "cur-value");
-                batt_info->full_last = prop_number_integer_value(obj3);
+                batt_buf.full_last = prop_number_integer_value(obj3);
             } else if (strcmp("voltage", prop_string_cstring_nocopy(obj3)) == 0) {
                 obj3 = prop_dictionary_get(obj2, "cur-value");
                 voltage = prop_number_integer_value(obj3);
             }
         }
         prop_object_iterator_release(iter2);
+
+        if (!watt_as_unit && voltage != -1) {
+            batt_buf.present_rate = (((float)voltage / 1000.0) * ((float)batt_buf.present_rate / 1000.0));
+            batt_buf.remaining = (((float)voltage / 1000.0) * ((float)batt_buf.remaining / 1000.0));
+            batt_buf.full_design = (((float)voltage / 1000.0) * ((float)batt_buf.full_design / 1000.0));
+            batt_buf.full_last = (((float)voltage / 1000.0) * ((float)batt_buf.full_last / 1000.0));
+        }
+
+        if (batt_buf.remaining == batt_buf.full_design)
+            batt_buf.status = CS_FULL;
+
+        add_battery_info(batt_info, &batt_buf);
     }
 
     prop_object_iterator_release(iter);
@@ -304,15 +399,67 @@ static bool slurp_battery_info(struct battery_info *batt_info, yajl_gen json_gen
         return false;
     }
 
-    if (!watt_as_unit && voltage != -1) {
-        batt_info->present_rate = (((float)voltage / 1000.0) * ((float)batt_info->present_rate / 1000.0));
-        batt_info->remaining = (((float)voltage / 1000.0) * ((float)batt_info->remaining / 1000.0));
-        batt_info->full_design = (((float)voltage / 1000.0) * ((float)batt_info->full_design / 1000.0));
-        batt_info->full_last = (((float)voltage / 1000.0) * ((float)batt_info->full_last / 1000.0));
+    batt_info->present_rate = abs(batt_info->present_rate);
+#endif
+
+    return true;
+}
+
+/*
+ * Populate batt_info with aggregate information about all batteries.
+ * Returns false on error, and an error message will have been written.
+ */
+static bool slurp_all_batteries(struct battery_info *batt_info, yajl_gen json_gen, char *buffer, const char *path, const char *format_down) {
+#if defined(LINUX)
+    char *outwalk = buffer;
+    bool is_found = false;
+
+    /* 1,000 batteries should be enough for anyone */
+    for (int i = 0; i < 1000; i++) {
+        char batpath[1024];
+        (void)snprintf(batpath, sizeof(batpath), path, i);
+
+        if (!strcmp(batpath, path)) {
+            OUTPUT_FULL_TEXT("no '%d' in battery path");
+            return false;
+        }
+
+        /* Probe to see if there is such a battery. */
+        struct stat sb;
+        if (stat(batpath, &sb) != 0) {
+            /* No such file, then we are done, assuming sysfs files have sequential numbers. */
+            if (errno == ENOENT)
+                break;
+
+            OUTPUT_FULL_TEXT(format_down);
+            return false;
+        }
+
+        struct battery_info batt_buf = {
+            .full_design = 0,
+            .full_last = 0,
+            .remaining = 0,
+            .present_rate = 0,
+            .status = CS_UNKNOWN,
+        };
+        if (!slurp_battery_info(&batt_buf, json_gen, buffer, i, path, format_down))
+            return false;
+
+        is_found = true;
+        add_battery_info(batt_info, &batt_buf);
     }
 
-    if (is_full)
-        batt_info->status = CS_FULL;
+    if (!is_found) {
+        OUTPUT_FULL_TEXT(format_down);
+        return false;
+    }
+
+    batt_info->present_rate = abs(batt_info->present_rate);
+#else
+    /* FreeBSD and OpenBSD only report aggregates. NetBSD always
+     * iterates through all batteries, so it's more efficient to
+     * aggregate in slurp_battery_info. */
+    return slurp_battery_info(batt_info, json_gen, buffer, -1, path, format_down);
 #endif
 
     return true;
@@ -324,10 +471,11 @@ void print_battery_info(yajl_gen json_gen, char *buffer, int number, const char
     struct battery_info batt_info = {
         .full_design = -1,
         .full_last = -1,
+        .remaining = -1,
         .present_rate = -1,
         .seconds_remaining = -1,
         .percentage_remaining = -1,
-        .status = CS_DISCHARGING,
+        .status = CS_UNKNOWN,
     };
     bool colorful_output = false;
 
@@ -340,8 +488,13 @@ void print_battery_info(yajl_gen json_gen, char *buffer, int number, const char
     hide_seconds = true;
 #endif
 
-    if (!slurp_battery_info(&batt_info, json_gen, buffer, number, path, format_down))
-        return;
+    if (number < 0) {
+        if (!slurp_all_batteries(&batt_info, json_gen, buffer, path, format_down))
+            return;
+    } else {
+        if (!slurp_battery_info(&batt_info, json_gen, buffer, number, path, format_down))
+            return;
+    }
 
     int full = (last_full_capacity ? batt_info.full_last : batt_info.full_design);
     if (full < 0 && batt_info.percentage_remaining < 0) {