wmacpi: zero-initialize buffer in order to avoid reading garbage in strtol.
[dockapps.git] / wmacpi / libacpi.c
blobb2cf5a18fdb562afcb2529d0d042f947e8594ba7
1 #define _GNU_SOURCE
3 #include <stdio.h>
4 #include <string.h>
5 #include <ctype.h>
6 #include <stdlib.h>
7 #include <unistd.h>
8 #include <sys/types.h>
9 #include <dirent.h>
10 #include <time.h>
12 #include "libacpi.h"
14 extern char *state[];
16 battery_t batteries[MAXBATT];
17 int verbosity;
19 #define PROC_DATA_SOURCE 0
20 #define SYSFS_DATA_SOURCE 1
21 static int data_source;
23 /* local proto */
24 int acpi_get_design_cap(int batt);
26 static int
27 cmpstr (const void *va, const void *vb)
29 const char *a = *(const char **) va;
30 const char *b = *(const char **) vb;
32 return strcmp (a, b);
35 static int read_sysfs_file(char *node, char *prop, char *buf, size_t buflen)
37 char tmp[256];
38 FILE *fp;
39 int ret;
41 ret = snprintf(tmp, sizeof(tmp), "/sys/class/power_supply/%s/%s", node, prop);
42 if (ret >= (int)sizeof(tmp)) {
43 perr("Path too long for %s/%s\n", node, prop);
44 return -1;
47 fp = fopen(tmp, "r");
48 if (fp == NULL) {
49 perr("Could not open %s/%s\n", node, prop);
50 return -2;
53 ret = fread(buf, 1, buflen - 1, fp);
55 fclose(fp);
57 if (ret == 0) {
58 perr("Could not read %s/%s\n", node, prop);
59 return -3;
62 buf[ret] = '\0';
64 return 0;
67 /* initialise the batteries */
68 static int sysfs_init_batteries(global_t *globals)
70 DIR *battdir;
71 struct dirent *batt;
72 char *name;
73 char *names[MAXBATT];
74 char ps_type[16];
75 int i;
77 /* now enumerate batteries */
78 globals->battery_count = 0;
79 battdir = opendir("/sys/class/power_supply");
80 if (battdir == NULL) {
81 pfatal("No batteries or ACPI not supported\n");
82 return 1;
84 while ((batt = readdir(battdir))) {
85 /* there's a serious problem with this code when there's
86 * more than one battery: the readdir won't return the
87 * entries in sorted order, so battery one won't
88 * necessarily be the first one returned. So, we need
89 * to sort them ourselves before adding them to the
90 * batteries array. */
91 name = batt->d_name;
93 /* skip ., .. and dotfiles */
94 if (name[0] == '.')
95 continue;
97 if (read_sysfs_file(name, "type", ps_type, sizeof(ps_type)) < 0)
98 continue;
100 if (strncmp("Battery", ps_type, 7) != 0)
101 continue;
103 names[globals->battery_count] = strdup(name);
104 globals->battery_count++;
106 closedir(battdir);
108 qsort(names, globals->battery_count, sizeof *names, cmpstr);
110 for (i = 0; i < globals->battery_count; i++) {
111 snprintf(batteries[i].name, MAX_NAME, "%s", names[i]);
112 pdebug("battery detected at /sys/class/power_supply/%s\n", batteries[i].name);
113 pinfo("found battery %s\n", names[i]);
114 free(names[i]);
116 if (read_sysfs_file(batteries[i].name, "energy_now", ps_type, sizeof(ps_type)) == 0)
117 batteries[i].sysfs_capa_mode = SYSFS_CAPA_ENERGY;
118 else if (read_sysfs_file(batteries[i].name, "charge_now", ps_type, sizeof(ps_type)) == 0)
119 batteries[i].sysfs_capa_mode = SYSFS_CAPA_CHARGE;
120 else if (read_sysfs_file(batteries[i].name, "capacity", ps_type, sizeof(ps_type)) == 0) {
121 batteries[i].sysfs_capa_mode = SYSFS_CAPA_PERCENT;
122 batteries[i].design_cap = 100;
123 batteries[i].last_full_cap = 100;
124 } else
125 batteries[i].sysfs_capa_mode = SYSFS_CAPA_ERR;
128 /* tell user some info */
129 pdebug("%d batteries detected\n", globals->battery_count);
130 pinfo("libacpi: found %d batter%s\n", globals->battery_count,
131 (globals->battery_count == 1) ? "y" : "ies");
133 return 0;
136 /* initialise the batteries */
137 static int procfs_init_batteries(global_t *globals)
139 DIR *battdir;
140 struct dirent *batt;
141 char *name;
142 char *names[MAXBATT];
143 int i;
145 /* now enumerate batteries */
146 globals->battery_count = 0;
147 battdir = opendir("/proc/acpi/battery");
148 if (battdir == NULL) {
149 pfatal("No batteries or ACPI not supported\n");
150 return 1;
152 while ((batt = readdir(battdir))) {
153 /* there's a serious problem with this code when there's
154 * more than one battery: the readdir won't return the
155 * entries in sorted order, so battery one won't
156 * necessarily be the first one returned. So, we need
157 * to sort them ourselves before adding them to the
158 * batteries array. */
159 name = batt->d_name;
161 /* skip . and .. */
162 if (!strncmp(".", name, 1) || !strncmp("..", name, 2))
163 continue;
165 names[globals->battery_count] = strdup(name);
166 globals->battery_count++;
168 closedir(battdir);
170 qsort(names, globals->battery_count, sizeof *names, cmpstr);
172 for (i = 0; i < globals->battery_count; i++) {
173 snprintf(batteries[i].name, MAX_NAME, "%s", names[i]);
174 snprintf(batteries[i].info_file, MAX_NAME,
175 "/proc/acpi/battery/%s/info", names[i]);
176 snprintf(batteries[i].state_file, MAX_NAME,
177 "/proc/acpi/battery/%s/state", names[i]);
178 pdebug("battery detected at %s\n", batteries[i].info_file);
179 pinfo("found battery %s\n", names[i]);
180 free(names[i]);
183 /* tell user some info */
184 pdebug("%d batteries detected\n", globals->battery_count);
185 pinfo("libacpi: found %d batter%s\n", globals->battery_count,
186 (globals->battery_count == 1) ? "y" : "ies");
188 return 0;
191 int init_batteries(global_t *globals)
193 if (data_source == SYSFS_DATA_SOURCE)
194 return sysfs_init_batteries(globals);
195 else
196 return procfs_init_batteries(globals);
199 /* a stub that just calls the current function */
200 int reinit_batteries(global_t *globals)
202 pdebug("reinitialising batteries\n");
203 return init_batteries(globals);
206 /* the actual name of the subdirectory under power_supply may
207 * be anything, so we need to read the directory and use the
208 * name we find there. */
209 static int sysfs_init_ac_adapters(global_t *globals)
211 DIR *acdir;
212 struct dirent *adapter;
213 adapter_t *ap = &globals->adapter;
214 char *name;
215 char ps_type[16];
217 acdir = opendir("/sys/class/power_supply");
218 if (acdir == NULL) {
219 pfatal("Unable to open /sys/class/power_supply -"
220 " are you sure this system supports ACPI?\n");
221 return 1;
223 name = NULL;
224 while ((adapter = readdir(acdir)) != NULL) {
226 if (adapter->d_name[0] == '.') {
227 continue;
230 if (read_sysfs_file(adapter->d_name, "type", ps_type, sizeof(ps_type)) < 0) {
231 continue;
234 if (strncmp("Mains", ps_type, 5) == 0) {
235 pdebug("found adapter %s\n", adapter->d_name);
236 name = strdup(adapter->d_name);
237 break;
240 closedir(acdir);
242 if (name == NULL) {
243 perr("No AC adapter found !\n");
244 return 1;
247 /* we'll just use the first adapter we find ... */
248 ap->name = name;
249 pinfo("libacpi: found ac adapter %s\n", ap->name);
251 return 0;
254 /* the actual name of the subdirectory under ac_adapter may
255 * be anything, so we need to read the directory and use the
256 * name we find there. */
257 static int procfs_init_ac_adapters(global_t *globals)
259 DIR *acdir;
260 struct dirent *adapter;
261 adapter_t *ap = &globals->adapter;
262 char *name;
264 acdir = opendir("/proc/acpi/ac_adapter");
265 if (acdir == NULL) {
266 pfatal("Unable to open /proc/acpi/ac_adapter -"
267 " are you sure this system supports ACPI?\n");
268 return 1;
270 name = NULL;
271 while ((adapter = readdir(acdir)) != NULL) {
272 name = adapter->d_name;
274 if (!strncmp(".", name, 1) || !strncmp("..", name, 2))
275 continue;
276 pdebug("found adapter %s\n", name);
278 ap->name = strdup(name);
279 closedir(acdir);
280 /* we /should/ only see one filename other than . and .. so
281 * we'll just use the last value name acquires . . . */
282 snprintf(ap->state_file, MAX_NAME, "/proc/acpi/ac_adapter/%s/state",
283 ap->name);
284 pinfo("libacpi: found ac adapter %s\n", ap->name);
286 return 0;
289 int init_ac_adapters(global_t *globals)
291 if (data_source == SYSFS_DATA_SOURCE)
292 return sysfs_init_ac_adapters(globals);
293 else
294 return procfs_init_ac_adapters(globals);
297 /* stub that does nothing but call the normal init function */
298 int reinit_ac_adapters(global_t *globals)
300 pdebug("reinitialising ac adapters\n");
301 return init_ac_adapters(globals);
304 /* see if we have ACPI support and check version */
305 int power_init(global_t *globals)
307 FILE *acpi;
308 char buf[4096] = "";
309 int acpi_ver = 0;
310 int retval;
311 size_t buflen;
312 unsigned int version_offset = 0;
314 if (!(acpi = fopen("/sys/module/acpi/parameters/acpica_version", "r"))) {
315 if (!(acpi = fopen("/proc/acpi/info", "r"))) {
316 pfatal("This system does not support ACPI\n");
317 return 1;
318 } else {
319 version_offset = 25;
323 /* okay, now see if we got the right version */
324 buflen = fread(buf, 4096, 1, acpi);
325 if (buflen != 4096 && ferror(acpi)) {
326 pfatal("Could not read file\n");
327 return 1;
329 acpi_ver = strtol(buf + version_offset, NULL, 10);
330 pinfo("ACPI version detected: %d\n", acpi_ver);
331 if (acpi_ver < 20020214) {
332 pfatal("This version requires ACPI subsystem version 20020214\n");
333 fclose(acpi);
334 return 1;
336 /* yep, all good */
337 fclose(acpi);
339 /* determine data source */
340 if (access("/sys/class/power_supply", R_OK | X_OK) == 0) {
341 data_source = SYSFS_DATA_SOURCE;
342 pinfo("Selecting sysfs as the data source\n");
343 } else {
344 data_source = PROC_DATA_SOURCE;
345 pinfo("Selecting procfs as the data source\n");
348 if (!(retval = init_batteries(globals)))
349 retval = init_ac_adapters(globals);
351 return retval;
354 /* reinitialise everything, to deal with changing batteries or ac adapters */
355 int power_reinit(global_t *globals)
357 FILE *acpi;
358 int retval;
360 if (!(acpi = fopen("/sys/module/acpi/parameters/acpica_version", "r"))) {
361 if (!(acpi = fopen("/proc/acpi/info", "r"))) {
362 pfatal("Could not reopen ACPI info file - does this system support ACPI?\n");
363 return 1;
367 if (!(retval = reinit_batteries(globals)))
368 retval = reinit_ac_adapters(globals);
370 fclose (acpi);
371 return retval;
374 static char *get_value(char *string)
376 char *retval;
377 int i;
379 if (string == NULL)
380 return NULL;
382 i = 0;
383 while (string[i] != ':') i++;
384 while (!isalnum(string[i])) i++;
385 retval = (string + i);
387 return retval;
390 static int check_error(char *buf)
392 if(strstr(buf, "ERROR") != NULL)
393 return 1;
394 return 0;
397 static power_state_t sysfs_get_power_status(global_t *globals)
399 char online[2];
400 adapter_t *ap = &globals->adapter;
402 if (read_sysfs_file(ap->name, "online", online, sizeof(online)) < 0)
403 return PS_ERR;
405 if (*online == '1')
406 return AC;
407 else
408 return BATT;
411 static power_state_t procfs_get_power_status(global_t *globals)
413 FILE *file;
414 char buf[1024];
415 char *val;
416 adapter_t *ap = &globals->adapter;
418 if ((file = fopen(ap->state_file, "r")) == NULL) {
419 snprintf(buf, 1024, "Could not open state file %s", ap->state_file);
420 perror(buf);
421 return PS_ERR;
424 if (!fgets(buf, 1024, file)) {
425 pfatal("Could not read file\n");
426 return PS_ERR;
428 fclose(file);
429 val = get_value(buf);
430 if ((strncmp(val, "on-line", 7)) == 0)
431 return AC;
432 else
433 return BATT;
436 power_state_t get_power_status(global_t *globals)
438 if (data_source == SYSFS_DATA_SOURCE)
439 return sysfs_get_power_status(globals);
440 else
441 return procfs_get_power_status(globals);
444 static int sysfs_get_battery_info(global_t *globals, int batt_no)
446 battery_t *info = &batteries[batt_no];
447 char buf[32];
448 int ret;
450 /* check to see if battery is present */
451 ret = read_sysfs_file(info->name, "present", buf, sizeof(buf));
452 if (ret < 0) {
453 /* interestingly, when the battery is not present, the whole
454 * /sys/class/power_supply/BATn directory does not exist.
455 * Yes, this is broken.
457 if (ret == -2)
458 info->present = 0;
460 /* reinit batteries, this one went away and it's very
461 possible there just isn't any other one */
462 reinit_batteries(globals);
464 return 0;
467 info->present = (*buf == '1');
468 if (!info->present) {
469 pinfo("Battery %s not present\n", info->name);
470 return 0;
473 /* get design capacity
474 * note that all these integer values can also contain the
475 * string 'unknown', so we need to check for this. */
476 if (info->sysfs_capa_mode == SYSFS_CAPA_ENERGY) {
477 if (read_sysfs_file(info->name, "energy_full_design", buf, sizeof(buf)) < 0)
478 info->design_cap = -1;
479 else
480 info->design_cap = strtoul(buf, NULL, 10) / 1000;
482 /* get last full capacity */
483 if (read_sysfs_file(info->name, "energy_full", buf, sizeof(buf)) < 0)
484 info->last_full_cap = -1;
485 else
486 info->last_full_cap = strtoul(buf, NULL, 10) / 1000;
487 } else if (info->sysfs_capa_mode == SYSFS_CAPA_CHARGE) {
488 /* get design capacity */
489 if (read_sysfs_file(info->name, "charge_full_design", buf, sizeof(buf)) < 0)
490 info->design_cap = -1;
491 else
492 info->design_cap = strtoul(buf, NULL, 10) / 1000;
494 /* get last full capacity */
495 if (read_sysfs_file(info->name, "charge_full", buf, sizeof(buf)) < 0)
496 info->last_full_cap = -1;
497 else
498 info->last_full_cap = strtoul(buf, NULL, 10) / 1000;
499 } else if (info->sysfs_capa_mode != SYSFS_CAPA_PERCENT) {
500 info->design_cap = -1;
501 info->last_full_cap = -1;
505 /* get design voltage */
506 if (read_sysfs_file(info->name, "voltage_min_design", buf, sizeof(buf)) < 0)
507 info->design_voltage = -1;
508 else
509 info->design_voltage = strtoul(buf, NULL, 10) / 1000;
511 /* get charging state */
512 if (read_sysfs_file(info->name, "status", buf, sizeof(buf)) < 0) {
513 info->charge_state = CH_ERR;
514 } else {
515 if (strncmp(buf, "Unknown", 7) == 0)
516 info->charge_state = CH_ERR;
517 else if (strncmp(buf, "Discharging", 11) == 0)
518 info->charge_state = DISCHARGE;
519 else if (strncmp(buf, "Charging", 8) == 0)
520 info->charge_state = CHARGE;
521 else if (strncmp(buf, "Not charging", 12) == 0)
522 info->charge_state = NO_CHARGE;
523 else if (strncmp(buf, "Full", 4) == 0)
524 info->charge_state = FULL; /* DISCHARGE ? as per old comment ... */
527 /* get current rate of burn
528 * note that if it's on AC, this will report 0 */
529 if (read_sysfs_file(info->name, "current_now", buf, sizeof(buf)) < 0)
530 info->present_rate = -1;
531 else {
532 int rate;
533 rate = strtoul(buf, NULL, 10) / 1000;
534 info->present_rate = (rate != 0) ? rate : info->present_rate;
537 if (info->sysfs_capa_mode == SYSFS_CAPA_ENERGY) {
538 /* get remaining capacity */
539 if (read_sysfs_file(info->name, "energy_now", buf, sizeof(buf)) < 0)
540 info->remaining_cap = -1;
541 else
542 info->remaining_cap = strtoul(buf, NULL, 10) / 1000;
544 } else if (info->sysfs_capa_mode == SYSFS_CAPA_CHARGE) {
545 /* get remaining capacity */
546 if (read_sysfs_file(info->name, "charge_now", buf, sizeof(buf)) < 0)
547 info->remaining_cap = -1;
548 else
549 info->remaining_cap = strtoul(buf, NULL, 10) / 1000;
550 } else if (info->sysfs_capa_mode == SYSFS_CAPA_PERCENT) {
551 /* get remaining capacity */
552 if (read_sysfs_file(info->name, "capacity", buf, sizeof(buf)) < 0)
553 info->remaining_cap = -1;
554 else
555 info->remaining_cap = strtoul(buf, NULL, 10) / 1000;
556 } else {
557 info->remaining_cap = -1;
560 /* get current voltage */
561 if (read_sysfs_file(info->name, "voltage_now", buf, sizeof(buf)) < 0)
562 info->present_voltage = -1;
563 else
564 info->present_voltage = strtoul(buf, NULL, 10) / 1000;
566 return 1;
569 static int procfs_get_battery_info(global_t *globals, int batt_no)
571 FILE *file;
572 battery_t *info = &batteries[batt_no];
573 char buf[1024];
574 char *entry;
575 int buflen;
576 char *val;
578 globals = globals; /* silencing a warning */
580 if ((file = fopen(info->info_file, "r")) == NULL) {
581 /* this is cheating, but string concatenation should work . . . */
582 pfatal("Could not open %s:", info->info_file );
583 perror(NULL);
584 return 0;
587 /* grab the contents of the file */
588 buflen = fread(buf, sizeof(buf), 1, file);
589 fclose(file);
591 /* check to see if there were any errors reported in the file */
592 if(check_error(buf)) {
593 pinfo("Error reported in file %s - discarding data\n",
594 info->info_file);
595 return 0;
598 /* check to see if battery is present */
599 entry = strstr(buf, "present:");
600 val = get_value(entry);
601 if ((strncmp(val, "yes", 3)) == 0) {
602 info->present = 1;
603 } else {
604 pinfo("Battery %s not present\n", info->name);
605 info->present = 0;
606 return 0;
609 /* get design capacity
610 * note that all these integer values can also contain the
611 * string 'unknown', so we need to check for this. */
612 entry = strstr(buf, "design capacity:");
613 val = get_value(entry);
614 if (val[0] == 'u')
615 info->design_cap = -1;
616 else
617 info->design_cap = strtoul(val, NULL, 10);
619 /* get last full capacity */
620 entry = strstr(buf, "last full capacity:");
621 val = get_value(entry);
622 if (val[0] == 'u')
623 info->last_full_cap = -1;
624 else
625 info->last_full_cap = strtoul(val, NULL, 10);
627 /* get design voltage */
628 entry = strstr(buf, "design voltage:");
629 val = get_value(entry);
630 if (val[0] == 'u')
631 info->design_voltage = -1;
632 else
633 info->design_voltage = strtoul(val, NULL, 10);
636 if ((file = fopen(info->state_file, "r")) == NULL) {
637 perr("Could not open %s:", info->state_file );
638 perror(NULL);
639 return 0;
642 /* grab the file contents */
643 memset(buf, 0, sizeof(buf));
644 buflen = fread(buf, sizeof(buf), 1, file);
645 if (buflen != sizeof(buf) && ferror(file)) {
646 pfatal("Could not read file\n");
647 return 1;
649 fclose(file);
651 /* check to see if there were any errors reported in the file */
652 if(check_error(buf)) {
653 pinfo("Error reported in file %s - discarding data\n",
654 info->state_file);
655 return 0;
657 /* check to see if battery is present */
658 entry = strstr(buf, "present:");
659 val = get_value(entry);
660 if ((strncmp(val, "yes", 3)) == 0) {
661 info->present = 1;
662 } else {
663 info->present = 0;
664 perr("Battery %s no longer present\n", info->name);
665 return 0;
668 /* get charging state */
669 entry = strstr(buf, "charging state:");
670 val = get_value(entry);
671 if (val[0] == 'u')
672 info->charge_state = CH_ERR;
673 else if ((strncmp(val, "discharging", 10)) == 0)
674 info->charge_state = DISCHARGE;
675 else if ((strncmp(val, "charged", 7)) == 0)
676 /* this is a workaround for machines that report
677 * their charge state as 'charged', rather than
678 * what my laptop does, which is go straight to
679 * 'discharging'. dunno which matches the standard */
680 info->charge_state = DISCHARGE;
681 else
682 info->charge_state = CHARGE;
684 /* get current rate of burn
685 * note that if it's on AC, this will report 0 */
686 entry = strstr(buf, "present rate:");
687 val = get_value(entry);
688 if (val[0] == 'u') {
689 info->present_rate = -1;
690 } else {
691 int rate;
692 rate = strtoul(val, NULL, 10);
693 if (rate != 0)
694 info->present_rate = rate;
697 /* get remaining capacity */
698 entry = strstr(buf, "remaining capacity:");
699 val = get_value(entry);
700 if (val[0] == 'u')
701 info->remaining_cap = -1;
702 else
703 info->remaining_cap = strtoul(val, NULL, 10);
705 /* get current voltage */
706 entry = strstr(buf, "present voltage:");
707 val = get_value(entry);
708 if (val[0] == 'u')
709 info->present_voltage = -1;
710 else
711 info->present_voltage = strtoul(val, NULL, 10);
713 return 1;
716 int get_battery_info(global_t *globals, int batt_no)
718 if (data_source == SYSFS_DATA_SOURCE)
719 return sysfs_get_battery_info(globals, batt_no);
720 else
721 return procfs_get_battery_info(globals, batt_no);
725 * 2003-7-1.
726 * In order to make this code more convenient for things other than
727 * just plain old wmacpi-ng I'm breaking the basic functionality
728 * up into several chunks: collecting and collating info for a
729 * single battery, calculating the global info (such as rtime), and
730 * some stuff to provide a similar interface to now.
733 /* calculate the percentage remaining, using the values of
734 * remaining capacity and last full capacity, as outlined in
735 * the ACPI spec v2.0a, section 3.9.3. */
736 static int calc_remaining_percentage(int batt)
738 float rcap, lfcap;
739 battery_t *binfo;
740 int retval;
742 binfo = &batteries[batt];
744 rcap = (float)binfo->remaining_cap;
745 lfcap = (float)binfo->last_full_cap;
747 /* we use -1 to indicate that the value is unknown . . . */
748 if (rcap < 0) {
749 perr("unknown percentage value\n");
750 retval = -1;
751 } else {
752 if (lfcap <= 0)
753 lfcap = 1;
754 retval = (int)((rcap/lfcap) * 100.0);
755 pdebug("percent: %d\n", retval);
757 return retval;
760 /* check to see if we've been getting bad data from the batteries - if
761 * we get more than some limit we switch to using the remaining capacity
762 * for the calculations. */
763 static enum rtime_mode check_rt_mode(global_t *globals)
765 int i;
766 int bad_limit = 5;
767 battery_t *binfo;
769 /* if we were told what to do, we should keep doing it */
770 if(globals->rt_forced)
771 return globals->rt_mode;
773 for(i = 0; i < MAXBATT; i++) {
774 binfo = &batteries[i];
775 if(binfo->present && globals->adapter.power == BATT) {
776 if(binfo->present_rate <= 0) {
777 pdebug("Bad report from %s\n", binfo->name);
778 binfo->bad_count++;
782 for(i = 0; i < MAXBATT; i++) {
783 binfo = &batteries[i];
784 if(binfo->bad_count > bad_limit) {
785 if(globals->rt_mode != RT_CAP)
786 pinfo("More than %d bad reports from %s; "
787 "Switching to remaining capacity mode\n",
788 bad_limit, binfo->name);
789 return RT_CAP;
792 return RT_RATE;
795 /* calculate remaining time until the battery is charged.
796 * when charging, the battery state file reports the
797 * current being used to charge the battery. We can use
798 * this and the remaining capacity to work out how long
799 * until it reaches the last full capacity of the battery.
800 * XXX: make sure this is actually portable . . . */
801 static int calc_charge_time_rate(int batt)
803 float rcap, lfcap;
804 battery_t *binfo;
805 int charge_time = 0;
807 binfo = &batteries[batt];
809 if (binfo->charge_state == CHARGE) {
810 if (binfo->present_rate == -1) {
811 perr("unknown present rate\n");
812 charge_time = -1;
813 } else {
814 lfcap = (float)binfo->last_full_cap;
815 rcap = (float)binfo->remaining_cap;
817 charge_time = (int)(((lfcap - rcap)/binfo->present_rate) * 60.0);
819 } else
820 if (binfo->charge_time)
821 charge_time = 0;
822 return charge_time;
825 /* we need to calculate the present rate the same way we do in rt_cap
826 * mode, and then use that to estimate charge time. This will
827 * necessarily be even less accurate than it is for remaining time, but
828 * it's just as neessary . . . */
829 static int calc_charge_time_cap(int batt)
831 static float cap_samples[CAP_SAMPLES];
832 static int time_samples[CAP_SAMPLES];
833 static int sample_count = 0;
834 static int current = 0;
835 static int old = 1;
836 int rtime;
837 int tdiff;
838 float cdiff;
839 float current_rate;
840 battery_t *binfo = &batteries[batt];
842 cap_samples[current] = (float) binfo->remaining_cap;
843 time_samples[current] = time(NULL);
845 if (sample_count == 0) {
846 /* we can't do much if we don't have any data . . . */
847 current_rate = 0;
848 } else if (sample_count < CAP_SAMPLES) {
849 /* if we have less than SAMPLES samples so far, we use the first
850 * sample and the current one */
851 cdiff = cap_samples[current] - cap_samples[0];
852 tdiff = time_samples[current] - time_samples[0];
853 current_rate = cdiff/tdiff;
854 } else {
855 /* if we have more than SAMPLES samples, we use the oldest
856 * current one, which at this point is current + 1. This will
857 * wrap the same way that current will wrap, but one cycle
858 * ahead */
859 cdiff = cap_samples[current] - cap_samples[old];
860 tdiff = time_samples[current] - time_samples[old];
861 current_rate = cdiff/(float)tdiff;
863 if (current_rate == 0)
864 rtime = 0;
865 else {
866 float cap_left = (float)(binfo->last_full_cap - binfo->remaining_cap);
867 rtime = (int)(cap_left/(current_rate * 60.0));
869 sample_count++, current++, old++;
870 if (current >= CAP_SAMPLES)
871 current = 0;
872 if (old >= CAP_SAMPLES)
873 old = 0;
875 pdebug("cap charge time rem: %d\n", rtime);
876 return rtime;
879 static int calc_charge_time(global_t *globals, int batt)
881 int ctime = 0;
883 globals->rt_mode = check_rt_mode(globals);
885 switch(globals->rt_mode) {
886 case RT_RATE:
887 ctime = calc_charge_time_rate(batt);
888 break;
889 case RT_CAP:
890 ctime = calc_charge_time_cap(batt);
891 break;
893 return ctime;
896 void acquire_batt_info(global_t *globals, int batt)
898 battery_t *binfo;
899 adapter_t *ap = &globals->adapter;
901 get_battery_info(globals, batt);
903 binfo = &batteries[batt];
905 if (!binfo->present) {
906 binfo->percentage = 0;
907 binfo->valid = 0;
908 binfo->charge_time = 0;
909 globals->rtime = 0;
910 return;
913 binfo->percentage = calc_remaining_percentage(batt);
915 /* set the battery's capacity state, based (at present) on some
916 * guesstimated values: more than 75% == HIGH, 25% to 75% MED, and
917 * less than 25% is LOW. Less than globals->crit_level is CRIT. */
918 if (binfo->percentage == -1)
919 binfo->state = BS_ERR;
920 if (binfo->percentage < globals->crit_level)
921 binfo->state = CRIT;
922 else if (binfo->percentage > 75)
923 binfo->state = HIGH;
924 else if (binfo->percentage > 25)
925 binfo->state = MED;
926 else
927 binfo->state = LOW;
929 /* we need to /know/ that we've got a valid state for the
930 * globals->power value . . . .*/
931 ap->power = get_power_status(globals);
933 binfo->charge_time = calc_charge_time(globals, batt);
935 /* and finally, we tell anyone who wants to use this information
936 * that it's now valid . . .*/
937 binfo->valid = 1;
940 void acquire_all_batt_info(global_t *globals)
942 int i;
944 for(i = 0; i < globals->battery_count; i++)
945 acquire_batt_info(globals, i);
949 * One of the feature requests I've had is for some way to deal with
950 * batteries that are too dumb or too b0rken to report a present rate
951 * value. The way to do this, obviously, is to record the time that
952 * samples were taken and use that information to calculate the rate
953 * at which the battery is draining/charging. This still won't help
954 * systems where the battery doesn't even report the remaining
955 * capacity, but without the present rate or the remaining capacity, I
956 * don't think there's /anything/ we can do to work around it.
958 * So, what we need to do is provide a way to use a different method
959 * to calculate the time remaining. What seems most sensible is to
960 * split out the code to calculate it into a seperate function, and
961 * then provide multiple implementations . . .
965 * the default implementation - if present rate and remaining capacity
966 * are both reported correctly, we use them.
968 int calc_time_remaining_rate(global_t *globals)
970 int i;
971 int rtime;
972 float rcap = 0;
973 float rate = 0;
974 battery_t *binfo;
975 static float rate_samples[SAMPLES];
976 static int sample_count = 0;
977 static int j = 0;
978 static int n = 0;
980 /* calculate the time remaining, using the battery's remaining
981 * capacity and the reported burn rate (3.9.3).
982 * For added accuracy, we average the value over the last
983 * SAMPLES number of calls, or for anything less than this we
984 * simply report the raw number. */
985 /* XXX: this needs to correctly handle the case where
986 * any of the values used is unknown (which we flag using
987 * -1). */
988 for (i = 0; i < globals->battery_count; i++) {
989 binfo = &batteries[i];
990 if (binfo->present && binfo->valid) {
991 rcap += (float)binfo->remaining_cap;
992 rate += (float)binfo->present_rate;
995 rate_samples[j] = rate;
996 j++, sample_count++;
997 if (j >= SAMPLES)
998 j = 0;
1000 /* for the first SAMPLES number of calls we calculate the
1001 * average based on sample_count, then we use SAMPLES to
1002 * calculate the rolling average. */
1004 /* when this fails, n should be equal to SAMPLES. */
1005 if (sample_count < SAMPLES)
1006 n++;
1007 for (i = 0, rate = 0; i < n; i++) {
1008 /* if any of our samples are invalid, we drop
1009 * straight out, and flag our unknown values. */
1010 if (rate_samples[i] < 0) {
1011 rate = -1;
1012 rtime = -1;
1013 goto out;
1015 rate += rate_samples[i];
1017 rate = rate/(float)n;
1019 if ((rcap < 1) || (rate < 1)) {
1020 rtime = 0;
1021 goto out;
1023 if (rate <= 0)
1024 rate = 1;
1025 /* time remaining in minutes */
1026 rtime = (int)((rcap/rate) * 60.0);
1027 if(rtime <= 0)
1028 rtime = 0;
1029 out:
1030 pdebug("discharge time rem: %d\n", rtime);
1031 return rtime;
1035 * the alternative implementation - record the time at which each
1036 * sample was taken, and then use the difference between the latest
1037 * sample and the one SAMPLES ago to calculate the difference over
1038 * that time, and from there the rate of change of capacity.
1040 * XXX: this code sucks, but largely because batteries aren't exactly
1041 * precision instruments - mine only report with about 70mAH
1042 * resolution, so they don't report any changes until the difference
1043 * is 70mAH. This means that calculating the current rate from the
1044 * remaining capacity is very choppy . . .
1046 * To fix this, we should calculate an average over some number of
1047 * samples at the old end of the set - this would smooth out the
1048 * transitions.
1050 int calc_time_remaining_cap(global_t *globals)
1052 static float cap_samples[CAP_SAMPLES];
1053 static int time_samples[CAP_SAMPLES];
1054 static int sample_count = 0;
1055 static int current = 0;
1056 static int old = 1;
1057 battery_t *binfo;
1058 int i;
1059 int rtime;
1060 int tdiff;
1061 float cdiff;
1062 float cap = 0;
1063 float current_rate;
1065 for (i = 0; i < globals->battery_count; i++) {
1066 binfo = &batteries[i];
1067 if (binfo->present && binfo->valid)
1068 cap += binfo->remaining_cap;
1070 cap_samples[current] = cap;
1071 time_samples[current] = time(NULL);
1073 if (sample_count == 0) {
1074 /* we can't do much if we don't have any data . . . */
1075 current_rate = 0;
1076 } else if (sample_count < CAP_SAMPLES) {
1077 /* if we have less than SAMPLES samples so far, we use the first
1078 * sample and the current one */
1079 cdiff = cap_samples[0] - cap_samples[current];
1080 tdiff = time_samples[current] - time_samples[0];
1081 current_rate = cdiff/tdiff;
1082 } else {
1083 /* if we have more than SAMPLES samples, we use the oldest
1084 * current one, which at this point is current + 1. This will
1085 * wrap the same way that current will wrap, but one cycle
1086 * ahead */
1087 cdiff = cap_samples[old] - cap_samples[current];
1088 tdiff = time_samples[current] - time_samples[old];
1089 current_rate = cdiff/tdiff;
1091 if (current_rate == 0)
1092 rtime = 0;
1093 else
1094 rtime = (int)(cap_samples[current]/(current_rate * 60.0));
1096 sample_count++, current++, old++;
1097 if (current >= CAP_SAMPLES)
1098 current = 0;
1099 if (old >= CAP_SAMPLES)
1100 old = 0;
1102 pdebug("cap discharge time rem: %d\n", rtime);
1103 return rtime;
1106 void acquire_global_info(global_t *globals)
1108 adapter_t *ap = &globals->adapter;
1110 globals->rt_mode = check_rt_mode(globals);
1112 switch(globals->rt_mode) {
1113 case RT_RATE:
1114 globals->rtime = calc_time_remaining_rate(globals);
1115 break;
1116 case RT_CAP:
1117 globals->rtime = calc_time_remaining_cap(globals);
1118 break;
1121 /* get the power status.
1122 * note that this is actually reported seperately from the
1123 * battery info, under /proc/acpi/ac_adapter/AC/state */
1124 ap->power = get_power_status(globals);
1127 void acquire_all_info(global_t *globals)
1129 acquire_all_batt_info(globals);
1130 acquire_global_info(globals);