wmshutdown: Destroy dialog window before shutting down. This is especially useful...
[dockapps.git] / wmacpi / libacpi.c
blob7590b1bfe7004538dbad90e0c7878b9891265562
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 #define PROC_DATA_SOURCE 0
17 #define SYSFS_DATA_SOURCE 1
18 static int data_source;
20 /* local proto */
21 int acpi_get_design_cap(int batt);
23 static int read_sysfs_file(char *node, char *prop, char *buf, size_t buflen)
25 char tmp[256];
26 FILE *fp;
27 int ret;
29 ret = snprintf(tmp, sizeof(tmp), "/sys/class/power_supply/%s/%s", node, prop);
30 if (ret >= (int)sizeof(tmp)) {
31 perr("Path too long for %s/%s\n", node, prop);
32 return -1;
35 fp = fopen(tmp, "r");
36 if (fp == NULL) {
37 perr("Could not open %s/%s\n", node, prop);
38 return -2;
41 ret = fread(buf, 1, buflen - 1, fp);
43 fclose(fp);
45 if (ret == 0) {
46 perr("Could not read %s/%s\n", node, prop);
47 return -3;
50 buf[ret] = '\0';
52 return 0;
55 /* initialise the batteries */
56 static int sysfs_init_batteries(global_t *globals)
58 DIR *battdir;
59 struct dirent *batt;
60 char *name;
61 char *names[MAXBATT];
62 char ps_type[16];
63 int i, j;
65 /* now enumerate batteries */
66 globals->battery_count = 0;
67 battdir = opendir("/sys/class/power_supply");
68 if (battdir == NULL) {
69 pfatal("No batteries or ACPI not supported\n");
70 return 1;
72 while ((batt = readdir(battdir))) {
73 /* there's a serious problem with this code when there's
74 * more than one battery: the readdir won't return the
75 * entries in sorted order, so battery one won't
76 * necessarily be the first one returned. So, we need
77 * to sort them ourselves before adding them to the
78 * batteries array. */
79 name = batt->d_name;
81 /* skip ., .. and dotfiles */
82 if (name[0] == '.')
83 continue;
85 if (read_sysfs_file(name, "type", ps_type, sizeof(ps_type)) < 0)
86 continue;
88 if (strncmp("Battery", ps_type, 7) != 0)
89 continue;
91 names[globals->battery_count] = strdup(name);
92 globals->battery_count++;
94 closedir(battdir);
96 /* A nice quick insertion sort, ala CLR. */
98 char *tmp1, *tmp2;
100 for (i = 1; i < globals->battery_count; i++) {
101 tmp1 = names[i];
102 j = i - 1;
103 while ((j >= 0) && ((strcmp(tmp1, names[j])) < 0)) {
104 tmp2 = names[j+1];
105 names[j+1] = names[j];
106 names[j] = tmp2;
111 for (i = 0; i < globals->battery_count; i++) {
112 snprintf(batteries[i].name, MAX_NAME, "%s", names[i]);
113 pdebug("battery detected at /sys/class/power_supply/%s\n", batteries[i].name);
114 pinfo("found battery %s\n", 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, j;
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 /* A nice quick insertion sort, ala CLR. */
172 char *tmp1, *tmp2;
174 for (i = 1; i < globals->battery_count; i++) {
175 tmp1 = names[i];
176 j = i - 1;
177 while ((j >= 0) && ((strcmp(tmp1, names[j])) < 0)) {
178 tmp2 = names[j+1];
179 names[j+1] = names[j];
180 names[j] = tmp2;
185 for (i = 0; i < globals->battery_count; i++) {
186 snprintf(batteries[i].name, MAX_NAME, "%s", names[i]);
187 snprintf(batteries[i].info_file, MAX_NAME,
188 "/proc/acpi/battery/%s/info", names[i]);
189 snprintf(batteries[i].state_file, MAX_NAME,
190 "/proc/acpi/battery/%s/state", names[i]);
191 pdebug("battery detected at %s\n", batteries[i].info_file);
192 pinfo("found battery %s\n", names[i]);
195 /* tell user some info */
196 pdebug("%d batteries detected\n", globals->battery_count);
197 pinfo("libacpi: found %d batter%s\n", globals->battery_count,
198 (globals->battery_count == 1) ? "y" : "ies");
200 return 0;
203 int init_batteries(global_t *globals)
205 if (data_source == SYSFS_DATA_SOURCE)
206 return sysfs_init_batteries(globals);
207 else
208 return procfs_init_batteries(globals);
211 /* a stub that just calls the current function */
212 int reinit_batteries(global_t *globals)
214 pdebug("reinitialising batteries\n");
215 return init_batteries(globals);
218 /* the actual name of the subdirectory under power_supply may
219 * be anything, so we need to read the directory and use the
220 * name we find there. */
221 static int sysfs_init_ac_adapters(global_t *globals)
223 DIR *acdir;
224 struct dirent *adapter;
225 adapter_t *ap = &globals->adapter;
226 char *name;
227 char ps_type[16];
229 acdir = opendir("/sys/class/power_supply");
230 if (acdir == NULL) {
231 pfatal("Unable to open /sys/class/power_supply -"
232 " are you sure this system supports ACPI?\n");
233 return 1;
235 name = NULL;
236 while ((adapter = readdir(acdir)) != NULL) {
237 name = adapter->d_name;
239 if (name[0] == '.') {
240 name = NULL;
241 continue;
244 if (read_sysfs_file(name, "type", ps_type, sizeof(ps_type)) < 0) {
245 name = NULL;
246 continue;
249 if (strncmp("Mains", ps_type, 5) == 0) {
250 pdebug("found adapter %s\n", name);
251 break;
252 } else {
253 name = NULL;
256 closedir(acdir);
258 if (name == NULL) {
259 perr("No AC adapter found !\n");
260 return 1;
263 /* we'll just use the first adapter we find ... */
264 ap->name = strdup(name);
265 pinfo("libacpi: found ac adapter %s\n", ap->name);
267 return 0;
270 /* the actual name of the subdirectory under ac_adapter may
271 * be anything, so we need to read the directory and use the
272 * name we find there. */
273 static int procfs_init_ac_adapters(global_t *globals)
275 DIR *acdir;
276 struct dirent *adapter;
277 adapter_t *ap = &globals->adapter;
278 char *name;
280 acdir = opendir("/proc/acpi/ac_adapter");
281 if (acdir == NULL) {
282 pfatal("Unable to open /proc/acpi/ac_adapter -"
283 " are you sure this system supports ACPI?\n");
284 return 1;
286 name = NULL;
287 while ((adapter = readdir(acdir)) != NULL) {
288 name = adapter->d_name;
290 if (!strncmp(".", name, 1) || !strncmp("..", name, 2))
291 continue;
292 pdebug("found adapter %s\n", name);
294 closedir(acdir);
295 /* we /should/ only see one filename other than . and .. so
296 * we'll just use the last value name acquires . . . */
297 ap->name = strdup(name);
298 snprintf(ap->state_file, MAX_NAME, "/proc/acpi/ac_adapter/%s/state",
299 ap->name);
300 pinfo("libacpi: found ac adapter %s\n", ap->name);
302 return 0;
305 int init_ac_adapters(global_t *globals)
307 if (data_source == SYSFS_DATA_SOURCE)
308 return sysfs_init_ac_adapters(globals);
309 else
310 return procfs_init_ac_adapters(globals);
313 /* stub that does nothing but call the normal init function */
314 int reinit_ac_adapters(global_t *globals)
316 pdebug("reinitialising ac adapters\n");
317 return init_ac_adapters(globals);
320 /* see if we have ACPI support and check version */
321 int power_init(global_t *globals)
323 FILE *acpi;
324 char buf[4096];
325 int acpi_ver = 0;
326 int retval;
327 size_t buflen;
328 unsigned int version_offset = 0;
330 if (!(acpi = fopen("/sys/module/acpi/parameters/acpica_version", "r"))) {
331 if (!(acpi = fopen("/proc/acpi/info", "r"))) {
332 pfatal("This system does not support ACPI\n");
333 return 1;
334 } else {
335 version_offset = 25;
339 /* okay, now see if we got the right version */
340 buflen = fread(buf, 4096, 1, acpi);
341 if (buflen != 4096 && ferror(acpi)) {
342 pfatal("Could not read file\n");
343 return 1;
345 acpi_ver = strtol(buf + version_offset, NULL, 10);
346 pinfo("ACPI version detected: %d\n", acpi_ver);
347 if (acpi_ver < 20020214) {
348 pfatal("This version requires ACPI subsystem version 20020214\n");
349 fclose(acpi);
350 return 1;
352 /* yep, all good */
353 fclose(acpi);
355 /* determine data source */
356 if (access("/sys/class/power_supply", R_OK | X_OK) == 0) {
357 data_source = SYSFS_DATA_SOURCE;
358 pinfo("Selecting sysfs as the data source\n");
359 } else {
360 data_source = PROC_DATA_SOURCE;
361 pinfo("Selecting procfs as the data source\n");
364 if (!(retval = init_batteries(globals)))
365 retval = init_ac_adapters(globals);
367 return retval;
370 /* reinitialise everything, to deal with changing batteries or ac adapters */
371 int power_reinit(global_t *globals)
373 FILE *acpi;
374 int retval;
376 if (!(acpi = fopen("/sys/module/acpi/parameters/acpica_version", "r"))) {
377 if (!(acpi = fopen("/proc/acpi/info", "r"))) {
378 pfatal("Could not reopen ACPI info file - does this system support ACPI?\n");
379 return 1;
383 if (!(retval = reinit_batteries(globals)))
384 retval = reinit_ac_adapters(globals);
386 return retval;
389 static char *get_value(char *string)
391 char *retval;
392 int i;
394 if (string == NULL)
395 return NULL;
397 i = 0;
398 while (string[i] != ':') i++;
399 while (!isalnum(string[i])) i++;
400 retval = (string + i);
402 return retval;
405 static int check_error(char *buf)
407 if(strstr(buf, "ERROR") != NULL)
408 return 1;
409 return 0;
412 static power_state_t sysfs_get_power_status(global_t *globals)
414 char online[2];
415 adapter_t *ap = &globals->adapter;
417 if (read_sysfs_file(ap->name, "online", online, sizeof(online)) < 0)
418 return PS_ERR;
420 if (*online == '1')
421 return AC;
422 else
423 return BATT;
426 static power_state_t procfs_get_power_status(global_t *globals)
428 FILE *file;
429 char buf[1024];
430 char *val;
431 adapter_t *ap = &globals->adapter;
433 if ((file = fopen(ap->state_file, "r")) == NULL) {
434 snprintf(buf, 1024, "Could not open state file %s", ap->state_file);
435 perror(buf);
436 return PS_ERR;
439 if (!fgets(buf, 1024, file)) {
440 pfatal("Could not read file\n");
441 return PS_ERR;
443 fclose(file);
444 val = get_value(buf);
445 if ((strncmp(val, "on-line", 7)) == 0)
446 return AC;
447 else
448 return BATT;
451 power_state_t get_power_status(global_t *globals)
453 if (data_source == SYSFS_DATA_SOURCE)
454 return sysfs_get_power_status(globals);
455 else
456 return procfs_get_power_status(globals);
459 static int sysfs_get_battery_info(global_t *globals, int batt_no)
461 battery_t *info = &batteries[batt_no];
462 char buf[32];
463 int ret;
465 /* check to see if battery is present */
466 ret = read_sysfs_file(info->name, "present", buf, sizeof(buf));
467 if (ret < 0) {
468 /* interestingly, when the battery is not present, the whole
469 * /sys/class/power_supply/BATn directory does not exist.
470 * Yes, this is broken.
472 if (ret == -2)
473 info->present = 0;
475 /* reinit batteries, this one went away and it's very
476 possible there just isn't any other one */
477 reinit_batteries(globals);
479 return 0;
482 info->present = (*buf == '1');
483 if (!info->present) {
484 pinfo("Battery %s not present\n", info->name);
485 return 0;
488 /* get design capacity
489 * note that all these integer values can also contain the
490 * string 'unknown', so we need to check for this. */
491 if (info->sysfs_capa_mode == SYSFS_CAPA_ENERGY) {
492 if (read_sysfs_file(info->name, "energy_full_design", buf, sizeof(buf)) < 0)
493 info->design_cap = -1;
494 else
495 info->design_cap = strtoul(buf, NULL, 10) / 1000;
497 /* get last full capacity */
498 if (read_sysfs_file(info->name, "energy_full", buf, sizeof(buf)) < 0)
499 info->last_full_cap = -1;
500 else
501 info->last_full_cap = strtoul(buf, NULL, 10) / 1000;
502 } else if (info->sysfs_capa_mode == SYSFS_CAPA_CHARGE) {
503 /* get design capacity */
504 if (read_sysfs_file(info->name, "charge_full_design", buf, sizeof(buf)) < 0)
505 info->design_cap = -1;
506 else
507 info->design_cap = strtoul(buf, NULL, 10) / 1000;
509 /* get last full capacity */
510 if (read_sysfs_file(info->name, "charge_full", buf, sizeof(buf)) < 0)
511 info->last_full_cap = -1;
512 else
513 info->last_full_cap = strtoul(buf, NULL, 10) / 1000;
514 } else if (info->sysfs_capa_mode != SYSFS_CAPA_PERCENT) {
515 info->design_cap = -1;
516 info->last_full_cap = -1;
520 /* get design voltage */
521 if (read_sysfs_file(info->name, "voltage_min_design", buf, sizeof(buf)) < 0)
522 info->design_voltage = -1;
523 else
524 info->design_voltage = strtoul(buf, NULL, 10) / 1000;
526 /* get charging state */
527 if (read_sysfs_file(info->name, "status", buf, sizeof(buf)) < 0) {
528 info->charge_state = CH_ERR;
529 } else {
530 if (strncmp(buf, "Unknown", 7) == 0)
531 info->charge_state = CH_ERR;
532 else if (strncmp(buf, "Discharging", 11) == 0)
533 info->charge_state = DISCHARGE;
534 else if (strncmp(buf, "Charging", 8) == 0)
535 info->charge_state = CHARGE;
536 else if (strncmp(buf, "Not charging", 12) == 0)
537 info->charge_state = NO_CHARGE;
538 else if (strncmp(buf, "Full", 4) == 0)
539 info->charge_state = FULL; /* DISCHARGE ? as per old comment ... */
542 /* get current rate of burn
543 * note that if it's on AC, this will report 0 */
544 if (read_sysfs_file(info->name, "current_now", buf, sizeof(buf)) < 0)
545 info->present_rate = -1;
546 else {
547 int rate;
548 rate = strtoul(buf, NULL, 10) / 1000;
549 info->present_rate = (rate != 0) ? rate : info->present_rate;
552 if (info->sysfs_capa_mode == SYSFS_CAPA_ENERGY) {
553 /* get remaining capacity */
554 if (read_sysfs_file(info->name, "energy_now", buf, sizeof(buf)) < 0)
555 info->remaining_cap = -1;
556 else
557 info->remaining_cap = strtoul(buf, NULL, 10) / 1000;
559 } else if (info->sysfs_capa_mode == SYSFS_CAPA_CHARGE) {
560 /* get remaining capacity */
561 if (read_sysfs_file(info->name, "charge_now", buf, sizeof(buf)) < 0)
562 info->remaining_cap = -1;
563 else
564 info->remaining_cap = strtoul(buf, NULL, 10) / 1000;
565 } else if (info->sysfs_capa_mode == SYSFS_CAPA_PERCENT) {
566 /* get remaining capacity */
567 if (read_sysfs_file(info->name, "capacity", buf, sizeof(buf)) < 0)
568 info->remaining_cap = -1;
569 else
570 info->remaining_cap = strtoul(buf, NULL, 10) / 1000;
571 } else {
572 info->remaining_cap = -1;
575 /* get current voltage */
576 if (read_sysfs_file(info->name, "voltage_now", buf, sizeof(buf)) < 0)
577 info->present_voltage = -1;
578 else
579 info->present_voltage = strtoul(buf, NULL, 10) / 1000;
581 return 1;
584 static int procfs_get_battery_info(global_t *globals, int batt_no)
586 FILE *file;
587 battery_t *info = &batteries[batt_no];
588 char buf[1024];
589 char *entry;
590 int buflen;
591 char *val;
593 globals = globals; /* silencing a warning */
595 if ((file = fopen(info->info_file, "r")) == NULL) {
596 /* this is cheating, but string concatenation should work . . . */
597 pfatal("Could not open %s:", info->info_file );
598 perror(NULL);
599 return 0;
602 /* grab the contents of the file */
603 buflen = fread(buf, sizeof(buf), 1, file);
604 fclose(file);
606 /* check to see if there were any errors reported in the file */
607 if(check_error(buf)) {
608 pinfo("Error reported in file %s - discarding data\n",
609 info->info_file);
610 return 0;
613 /* check to see if battery is present */
614 entry = strstr(buf, "present:");
615 val = get_value(entry);
616 if ((strncmp(val, "yes", 3)) == 0) {
617 info->present = 1;
618 } else {
619 pinfo("Battery %s not present\n", info->name);
620 info->present = 0;
621 return 0;
624 /* get design capacity
625 * note that all these integer values can also contain the
626 * string 'unknown', so we need to check for this. */
627 entry = strstr(buf, "design capacity:");
628 val = get_value(entry);
629 if (val[0] == 'u')
630 info->design_cap = -1;
631 else
632 info->design_cap = strtoul(val, NULL, 10);
634 /* get last full capacity */
635 entry = strstr(buf, "last full capacity:");
636 val = get_value(entry);
637 if (val[0] == 'u')
638 info->last_full_cap = -1;
639 else
640 info->last_full_cap = strtoul(val, NULL, 10);
642 /* get design voltage */
643 entry = strstr(buf, "design voltage:");
644 val = get_value(entry);
645 if (val[0] == 'u')
646 info->design_voltage = -1;
647 else
648 info->design_voltage = strtoul(val, NULL, 10);
651 if ((file = fopen(info->state_file, "r")) == NULL) {
652 perr("Could not open %s:", info->state_file );
653 perror(NULL);
654 return 0;
657 /* grab the file contents */
658 memset(buf, 0, sizeof(buf));
659 buflen = fread(buf, sizeof(buf), 1, file);
660 if (buflen != sizeof(buf) && ferror(file)) {
661 pfatal("Could not read file\n");
662 return 1;
664 fclose(file);
666 /* check to see if there were any errors reported in the file */
667 if(check_error(buf)) {
668 pinfo("Error reported in file %s - discarding data\n",
669 info->state_file);
670 return 0;
672 /* check to see if battery is present */
673 entry = strstr(buf, "present:");
674 val = get_value(entry);
675 if ((strncmp(val, "yes", 3)) == 0) {
676 info->present = 1;
677 } else {
678 info->present = 0;
679 perr("Battery %s no longer present\n", info->name);
680 return 0;
683 /* get charging state */
684 entry = strstr(buf, "charging state:");
685 val = get_value(entry);
686 if (val[0] == 'u')
687 info->charge_state = CH_ERR;
688 else if ((strncmp(val, "discharging", 10)) == 0)
689 info->charge_state = DISCHARGE;
690 else if ((strncmp(val, "charged", 7)) == 0)
691 /* this is a workaround for machines that report
692 * their charge state as 'charged', rather than
693 * what my laptop does, which is go straight to
694 * 'discharging'. dunno which matches the standard */
695 info->charge_state = DISCHARGE;
696 else
697 info->charge_state = CHARGE;
699 /* get current rate of burn
700 * note that if it's on AC, this will report 0 */
701 entry = strstr(buf, "present rate:");
702 val = get_value(entry);
703 if (val[0] == 'u') {
704 info->present_rate = -1;
705 } else {
706 int rate;
707 rate = strtoul(val, NULL, 10);
708 if (rate != 0)
709 info->present_rate = rate;
712 /* get remaining capacity */
713 entry = strstr(buf, "remaining capacity:");
714 val = get_value(entry);
715 if (val[0] == 'u')
716 info->remaining_cap = -1;
717 else
718 info->remaining_cap = strtoul(val, NULL, 10);
720 /* get current voltage */
721 entry = strstr(buf, "present voltage:");
722 val = get_value(entry);
723 if (val[0] == 'u')
724 info->present_voltage = -1;
725 else
726 info->present_voltage = strtoul(val, NULL, 10);
728 return 1;
731 int get_battery_info(global_t *globals, int batt_no)
733 if (data_source == SYSFS_DATA_SOURCE)
734 return sysfs_get_battery_info(globals, batt_no);
735 else
736 return procfs_get_battery_info(globals, batt_no);
740 * 2003-7-1.
741 * In order to make this code more convenient for things other than
742 * just plain old wmacpi-ng I'm breaking the basic functionality
743 * up into several chunks: collecting and collating info for a
744 * single battery, calculating the global info (such as rtime), and
745 * some stuff to provide a similar interface to now.
748 /* calculate the percentage remaining, using the values of
749 * remaining capacity and last full capacity, as outlined in
750 * the ACPI spec v2.0a, section 3.9.3. */
751 static int calc_remaining_percentage(int batt)
753 float rcap, lfcap;
754 battery_t *binfo;
755 int retval;
757 binfo = &batteries[batt];
759 rcap = (float)binfo->remaining_cap;
760 lfcap = (float)binfo->last_full_cap;
762 /* we use -1 to indicate that the value is unknown . . . */
763 if (rcap < 0) {
764 perr("unknown percentage value\n");
765 retval = -1;
766 } else {
767 if (lfcap <= 0)
768 lfcap = 1;
769 retval = (int)((rcap/lfcap) * 100.0);
770 pdebug("percent: %d\n", retval);
772 return retval;
775 /* check to see if we've been getting bad data from the batteries - if
776 * we get more than some limit we switch to using the remaining capacity
777 * for the calculations. */
778 static enum rtime_mode check_rt_mode(global_t *globals)
780 int i;
781 int bad_limit = 5;
782 battery_t *binfo;
784 /* if we were told what to do, we should keep doing it */
785 if(globals->rt_forced)
786 return globals->rt_mode;
788 for(i = 0; i < MAXBATT; i++) {
789 binfo = &batteries[i];
790 if(binfo->present && globals->adapter.power == BATT) {
791 if(binfo->present_rate <= 0) {
792 pdebug("Bad report from %s\n", binfo->name);
793 binfo->bad_count++;
797 for(i = 0; i < MAXBATT; i++) {
798 binfo = &batteries[i];
799 if(binfo->bad_count > bad_limit) {
800 if(globals->rt_mode != RT_CAP)
801 pinfo("More than %d bad reports from %s; "
802 "Switching to remaining capacity mode\n",
803 bad_limit, binfo->name);
804 return RT_CAP;
807 return RT_RATE;
810 /* calculate remaining time until the battery is charged.
811 * when charging, the battery state file reports the
812 * current being used to charge the battery. We can use
813 * this and the remaining capacity to work out how long
814 * until it reaches the last full capacity of the battery.
815 * XXX: make sure this is actually portable . . . */
816 static int calc_charge_time_rate(int batt)
818 float rcap, lfcap;
819 battery_t *binfo;
820 int charge_time = 0;
822 binfo = &batteries[batt];
824 if (binfo->charge_state == CHARGE) {
825 if (binfo->present_rate == -1) {
826 perr("unknown present rate\n");
827 charge_time = -1;
828 } else {
829 lfcap = (float)binfo->last_full_cap;
830 rcap = (float)binfo->remaining_cap;
832 charge_time = (int)(((lfcap - rcap)/binfo->present_rate) * 60.0);
834 } else
835 if (binfo->charge_time)
836 charge_time = 0;
837 return charge_time;
840 /* we need to calculate the present rate the same way we do in rt_cap
841 * mode, and then use that to estimate charge time. This will
842 * necessarily be even less accurate than it is for remaining time, but
843 * it's just as neessary . . . */
844 static int calc_charge_time_cap(int batt)
846 static float cap_samples[CAP_SAMPLES];
847 static int time_samples[CAP_SAMPLES];
848 static int sample_count = 0;
849 static int current = 0;
850 static int old = 1;
851 int rtime;
852 int tdiff;
853 float cdiff;
854 float current_rate;
855 battery_t *binfo = &batteries[batt];
857 cap_samples[current] = (float) binfo->remaining_cap;
858 time_samples[current] = time(NULL);
860 if (sample_count == 0) {
861 /* we can't do much if we don't have any data . . . */
862 current_rate = 0;
863 } else if (sample_count < CAP_SAMPLES) {
864 /* if we have less than SAMPLES samples so far, we use the first
865 * sample and the current one */
866 cdiff = cap_samples[current] - cap_samples[0];
867 tdiff = time_samples[current] - time_samples[0];
868 current_rate = cdiff/tdiff;
869 } else {
870 /* if we have more than SAMPLES samples, we use the oldest
871 * current one, which at this point is current + 1. This will
872 * wrap the same way that current will wrap, but one cycle
873 * ahead */
874 cdiff = cap_samples[current] - cap_samples[old];
875 tdiff = time_samples[current] - time_samples[old];
876 current_rate = cdiff/(float)tdiff;
878 if (current_rate == 0)
879 rtime = 0;
880 else {
881 float cap_left = (float)(binfo->last_full_cap - binfo->remaining_cap);
882 rtime = (int)(cap_left/(current_rate * 60.0));
884 sample_count++, current++, old++;
885 if (current >= CAP_SAMPLES)
886 current = 0;
887 if (old >= CAP_SAMPLES)
888 old = 0;
890 pdebug("cap charge time rem: %d\n", rtime);
891 return rtime;
894 static int calc_charge_time(global_t *globals, int batt)
896 int ctime = 0;
898 globals->rt_mode = check_rt_mode(globals);
900 switch(globals->rt_mode) {
901 case RT_RATE:
902 ctime = calc_charge_time_rate(batt);
903 break;
904 case RT_CAP:
905 ctime = calc_charge_time_cap(batt);
906 break;
908 return ctime;
911 void acquire_batt_info(global_t *globals, int batt)
913 battery_t *binfo;
914 adapter_t *ap = &globals->adapter;
916 get_battery_info(globals, batt);
918 binfo = &batteries[batt];
920 if (!binfo->present) {
921 binfo->percentage = 0;
922 binfo->valid = 0;
923 binfo->charge_time = 0;
924 globals->rtime = 0;
925 return;
928 binfo->percentage = calc_remaining_percentage(batt);
930 /* set the battery's capacity state, based (at present) on some
931 * guesstimated values: more than 75% == HIGH, 25% to 75% MED, and
932 * less than 25% is LOW. Less than globals->crit_level is CRIT. */
933 if (binfo->percentage == -1)
934 binfo->state = BS_ERR;
935 if (binfo->percentage < globals->crit_level)
936 binfo->state = CRIT;
937 else if (binfo->percentage > 75)
938 binfo->state = HIGH;
939 else if (binfo->percentage > 25)
940 binfo->state = MED;
941 else
942 binfo->state = LOW;
944 /* we need to /know/ that we've got a valid state for the
945 * globals->power value . . . .*/
946 ap->power = get_power_status(globals);
948 binfo->charge_time = calc_charge_time(globals, batt);
950 /* and finally, we tell anyone who wants to use this information
951 * that it's now valid . . .*/
952 binfo->valid = 1;
955 void acquire_all_batt_info(global_t *globals)
957 int i;
959 for(i = 0; i < globals->battery_count; i++)
960 acquire_batt_info(globals, i);
964 * One of the feature requests I've had is for some way to deal with
965 * batteries that are too dumb or too b0rken to report a present rate
966 * value. The way to do this, obviously, is to record the time that
967 * samples were taken and use that information to calculate the rate
968 * at which the battery is draining/charging. This still won't help
969 * systems where the battery doesn't even report the remaining
970 * capacity, but without the present rate or the remaining capacity, I
971 * don't think there's /anything/ we can do to work around it.
973 * So, what we need to do is provide a way to use a different method
974 * to calculate the time remaining. What seems most sensible is to
975 * split out the code to calculate it into a seperate function, and
976 * then provide multiple implementations . . .
980 * the default implementation - if present rate and remaining capacity
981 * are both reported correctly, we use them.
983 int calc_time_remaining_rate(global_t *globals)
985 int i;
986 int rtime;
987 float rcap = 0;
988 float rate = 0;
989 battery_t *binfo;
990 static float rate_samples[SAMPLES];
991 static int sample_count = 0;
992 static int j = 0;
993 static int n = 0;
995 /* calculate the time remaining, using the battery's remaining
996 * capacity and the reported burn rate (3.9.3).
997 * For added accuracy, we average the value over the last
998 * SAMPLES number of calls, or for anything less than this we
999 * simply report the raw number. */
1000 /* XXX: this needs to correctly handle the case where
1001 * any of the values used is unknown (which we flag using
1002 * -1). */
1003 for (i = 0; i < globals->battery_count; i++) {
1004 binfo = &batteries[i];
1005 if (binfo->present && binfo->valid) {
1006 rcap += (float)binfo->remaining_cap;
1007 rate += (float)binfo->present_rate;
1010 rate_samples[j] = rate;
1011 j++, sample_count++;
1012 if (j >= SAMPLES)
1013 j = 0;
1015 /* for the first SAMPLES number of calls we calculate the
1016 * average based on sample_count, then we use SAMPLES to
1017 * calculate the rolling average. */
1019 /* when this fails, n should be equal to SAMPLES. */
1020 if (sample_count < SAMPLES)
1021 n++;
1022 for (i = 0, rate = 0; i < n; i++) {
1023 /* if any of our samples are invalid, we drop
1024 * straight out, and flag our unknown values. */
1025 if (rate_samples[i] < 0) {
1026 rate = -1;
1027 rtime = -1;
1028 goto out;
1030 rate += rate_samples[i];
1032 rate = rate/(float)n;
1034 if ((rcap < 1) || (rate < 1)) {
1035 rtime = 0;
1036 goto out;
1038 if (rate <= 0)
1039 rate = 1;
1040 /* time remaining in minutes */
1041 rtime = (int)((rcap/rate) * 60.0);
1042 if(rtime <= 0)
1043 rtime = 0;
1044 out:
1045 pdebug("discharge time rem: %d\n", rtime);
1046 return rtime;
1050 * the alternative implementation - record the time at which each
1051 * sample was taken, and then use the difference between the latest
1052 * sample and the one SAMPLES ago to calculate the difference over
1053 * that time, and from there the rate of change of capacity.
1055 * XXX: this code sucks, but largely because batteries aren't exactly
1056 * precision instruments - mine only report with about 70mAH
1057 * resolution, so they don't report any changes until the difference
1058 * is 70mAH. This means that calculating the current rate from the
1059 * remaining capacity is very choppy . . .
1061 * To fix this, we should calculate an average over some number of
1062 * samples at the old end of the set - this would smooth out the
1063 * transitions.
1065 int calc_time_remaining_cap(global_t *globals)
1067 static float cap_samples[CAP_SAMPLES];
1068 static int time_samples[CAP_SAMPLES];
1069 static int sample_count = 0;
1070 static int current = 0;
1071 static int old = 1;
1072 battery_t *binfo;
1073 int i;
1074 int rtime;
1075 int tdiff;
1076 float cdiff;
1077 float cap = 0;
1078 float current_rate;
1080 for (i = 0; i < globals->battery_count; i++) {
1081 binfo = &batteries[i];
1082 if (binfo->present && binfo->valid)
1083 cap += binfo->remaining_cap;
1085 cap_samples[current] = cap;
1086 time_samples[current] = time(NULL);
1088 if (sample_count == 0) {
1089 /* we can't do much if we don't have any data . . . */
1090 current_rate = 0;
1091 } else if (sample_count < CAP_SAMPLES) {
1092 /* if we have less than SAMPLES samples so far, we use the first
1093 * sample and the current one */
1094 cdiff = cap_samples[0] - cap_samples[current];
1095 tdiff = time_samples[current] - time_samples[0];
1096 current_rate = cdiff/tdiff;
1097 } else {
1098 /* if we have more than SAMPLES samples, we use the oldest
1099 * current one, which at this point is current + 1. This will
1100 * wrap the same way that current will wrap, but one cycle
1101 * ahead */
1102 cdiff = cap_samples[old] - cap_samples[current];
1103 tdiff = time_samples[current] - time_samples[old];
1104 current_rate = cdiff/tdiff;
1106 if (current_rate == 0)
1107 rtime = 0;
1108 else
1109 rtime = (int)(cap_samples[current]/(current_rate * 60.0));
1111 sample_count++, current++, old++;
1112 if (current >= CAP_SAMPLES)
1113 current = 0;
1114 if (old >= CAP_SAMPLES)
1115 old = 0;
1117 pdebug("cap discharge time rem: %d\n", rtime);
1118 return rtime;
1121 void acquire_global_info(global_t *globals)
1123 adapter_t *ap = &globals->adapter;
1125 globals->rt_mode = check_rt_mode(globals);
1127 switch(globals->rt_mode) {
1128 case RT_RATE:
1129 globals->rtime = calc_time_remaining_rate(globals);
1130 break;
1131 case RT_CAP:
1132 globals->rtime = calc_time_remaining_cap(globals);
1133 break;
1136 /* get the power status.
1137 * note that this is actually reported seperately from the
1138 * battery info, under /proc/acpi/ac_adapter/AC/state */
1139 ap->power = get_power_status(globals);
1142 void acquire_all_info(global_t *globals)
1144 acquire_all_batt_info(globals);
1145 acquire_global_info(globals);