wmacpi: Fix -Wunused-but-set-variable compiler warning.
[dockapps.git] / wmacpi / libacpi.c
blob14d98d4335b87fd50114430f184b85062481fb6e
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 unsigned int version_offset = 0;
329 if (!(acpi = fopen("/sys/module/acpi/parameters/acpica_version", "r"))) {
330 if (!(acpi = fopen("/proc/acpi/info", "r"))) {
331 pfatal("This system does not support ACPI\n");
332 return 1;
333 } else {
334 version_offset = 25;
338 /* okay, now see if we got the right version */
339 fread(buf, 4096, 1, acpi);
340 acpi_ver = strtol(buf + version_offset, NULL, 10);
341 pinfo("ACPI version detected: %d\n", acpi_ver);
342 if (acpi_ver < 20020214) {
343 pfatal("This version requires ACPI subsystem version 20020214\n");
344 fclose(acpi);
345 return 1;
347 /* yep, all good */
348 fclose(acpi);
350 /* determine data source */
351 if (access("/sys/class/power_supply", R_OK | X_OK) == 0) {
352 data_source = SYSFS_DATA_SOURCE;
353 pinfo("Selecting sysfs as the data source\n");
354 } else {
355 data_source = PROC_DATA_SOURCE;
356 pinfo("Selecting procfs as the data source\n");
359 if (!(retval = init_batteries(globals)))
360 retval = init_ac_adapters(globals);
362 return retval;
365 /* reinitialise everything, to deal with changing batteries or ac adapters */
366 int power_reinit(global_t *globals)
368 FILE *acpi;
369 int retval;
371 if (!(acpi = fopen("/sys/module/acpi/parameters/acpica_version", "r"))) {
372 if (!(acpi = fopen("/proc/acpi/info", "r"))) {
373 pfatal("Could not reopen ACPI info file - does this system support ACPI?\n");
374 return 1;
378 if (!(retval = reinit_batteries(globals)))
379 retval = reinit_ac_adapters(globals);
381 return retval;
384 static char *get_value(char *string)
386 char *retval;
387 int i;
389 if (string == NULL)
390 return NULL;
392 i = 0;
393 while (string[i] != ':') i++;
394 while (!isalnum(string[i])) i++;
395 retval = (string + i);
397 return retval;
400 static int check_error(char *buf)
402 if(strstr(buf, "ERROR") != NULL)
403 return 1;
404 return 0;
407 static power_state_t sysfs_get_power_status(global_t *globals)
409 char online[2];
410 adapter_t *ap = &globals->adapter;
412 if (read_sysfs_file(ap->name, "online", online, sizeof(online)) < 0)
413 return PS_ERR;
415 if (*online == '1')
416 return AC;
417 else
418 return BATT;
421 static power_state_t procfs_get_power_status(global_t *globals)
423 FILE *file;
424 char buf[1024];
425 char *val;
426 adapter_t *ap = &globals->adapter;
428 if ((file = fopen(ap->state_file, "r")) == NULL) {
429 snprintf(buf, 1024, "Could not open state file %s", ap->state_file);
430 perror(buf);
431 return PS_ERR;
434 fgets(buf, 1024, file);
435 fclose(file);
436 val = get_value(buf);
437 if ((strncmp(val, "on-line", 7)) == 0)
438 return AC;
439 else
440 return BATT;
443 power_state_t get_power_status(global_t *globals)
445 if (data_source == SYSFS_DATA_SOURCE)
446 return sysfs_get_power_status(globals);
447 else
448 return procfs_get_power_status(globals);
451 static int sysfs_get_battery_info(global_t *globals, int batt_no)
453 battery_t *info = &batteries[batt_no];
454 char buf[32];
455 int ret;
457 /* check to see if battery is present */
458 ret = read_sysfs_file(info->name, "present", buf, sizeof(buf));
459 if (ret < 0) {
460 /* interestingly, when the battery is not present, the whole
461 * /sys/class/power_supply/BATn directory does not exist.
462 * Yes, this is broken.
464 if (ret == -2)
465 info->present = 0;
467 /* reinit batteries, this one went away and it's very
468 possible there just isn't any other one */
469 reinit_batteries(globals);
471 return 0;
474 info->present = (*buf == '1');
475 if (!info->present) {
476 pinfo("Battery %s not present\n", info->name);
477 return 0;
480 /* get design capacity
481 * note that all these integer values can also contain the
482 * string 'unknown', so we need to check for this. */
483 if (info->sysfs_capa_mode == SYSFS_CAPA_ENERGY) {
484 if (read_sysfs_file(info->name, "energy_full_design", buf, sizeof(buf)) < 0)
485 info->design_cap = -1;
486 else
487 info->design_cap = strtoul(buf, NULL, 10) / 1000;
489 /* get last full capacity */
490 if (read_sysfs_file(info->name, "energy_full", buf, sizeof(buf)) < 0)
491 info->last_full_cap = -1;
492 else
493 info->last_full_cap = strtoul(buf, NULL, 10) / 1000;
494 } else if (info->sysfs_capa_mode == SYSFS_CAPA_CHARGE) {
495 /* get design capacity */
496 if (read_sysfs_file(info->name, "charge_full_design", buf, sizeof(buf)) < 0)
497 info->design_cap = -1;
498 else
499 info->design_cap = strtoul(buf, NULL, 10) / 1000;
501 /* get last full capacity */
502 if (read_sysfs_file(info->name, "charge_full", buf, sizeof(buf)) < 0)
503 info->last_full_cap = -1;
504 else
505 info->last_full_cap = strtoul(buf, NULL, 10) / 1000;
506 } else if (info->sysfs_capa_mode != SYSFS_CAPA_PERCENT) {
507 info->design_cap = -1;
508 info->last_full_cap = -1;
512 /* get design voltage */
513 if (read_sysfs_file(info->name, "voltage_min_design", buf, sizeof(buf)) < 0)
514 info->design_voltage = -1;
515 else
516 info->design_voltage = strtoul(buf, NULL, 10) / 1000;
518 /* get charging state */
519 if (read_sysfs_file(info->name, "status", buf, sizeof(buf)) < 0) {
520 info->charge_state = CH_ERR;
521 } else {
522 if (strncmp(buf, "Unknown", 7) == 0)
523 info->charge_state = CH_ERR;
524 else if (strncmp(buf, "Discharging", 11) == 0)
525 info->charge_state = DISCHARGE;
526 else if (strncmp(buf, "Charging", 8) == 0)
527 info->charge_state = CHARGE;
528 else if (strncmp(buf, "Not charging", 12) == 0)
529 info->charge_state = NO_CHARGE;
530 else if (strncmp(buf, "Full", 4) == 0)
531 info->charge_state = FULL; /* DISCHARGE ? as per old comment ... */
534 /* get current rate of burn
535 * note that if it's on AC, this will report 0 */
536 if (read_sysfs_file(info->name, "current_now", buf, sizeof(buf)) < 0)
537 info->present_rate = -1;
538 else {
539 int rate;
540 rate = strtoul(buf, NULL, 10) / 1000;
541 info->present_rate = (rate != 0) ? rate : info->present_rate;
544 if (info->sysfs_capa_mode == SYSFS_CAPA_ENERGY) {
545 /* get remaining capacity */
546 if (read_sysfs_file(info->name, "energy_now", buf, sizeof(buf)) < 0)
547 info->remaining_cap = -1;
548 else
549 info->remaining_cap = strtoul(buf, NULL, 10) / 1000;
551 } else if (info->sysfs_capa_mode == SYSFS_CAPA_CHARGE) {
552 /* get remaining capacity */
553 if (read_sysfs_file(info->name, "charge_now", buf, sizeof(buf)) < 0)
554 info->remaining_cap = -1;
555 else
556 info->remaining_cap = strtoul(buf, NULL, 10) / 1000;
557 } else if (info->sysfs_capa_mode == SYSFS_CAPA_PERCENT) {
558 /* get remaining capacity */
559 if (read_sysfs_file(info->name, "capacity", buf, sizeof(buf)) < 0)
560 info->remaining_cap = -1;
561 else
562 info->remaining_cap = strtoul(buf, NULL, 10) / 1000;
563 } else {
564 info->remaining_cap = -1;
567 /* get current voltage */
568 if (read_sysfs_file(info->name, "voltage_now", buf, sizeof(buf)) < 0)
569 info->present_voltage = -1;
570 else
571 info->present_voltage = strtoul(buf, NULL, 10) / 1000;
573 return 1;
576 static int procfs_get_battery_info(global_t *globals, int batt_no)
578 FILE *file;
579 battery_t *info = &batteries[batt_no];
580 char buf[1024];
581 char *entry;
582 int buflen;
583 char *val;
585 globals = globals; /* silencing a warning */
587 if ((file = fopen(info->info_file, "r")) == NULL) {
588 /* this is cheating, but string concatenation should work . . . */
589 pfatal("Could not open %s:", info->info_file );
590 perror(NULL);
591 return 0;
594 /* grab the contents of the file */
595 buflen = fread(buf, sizeof(buf), 1, file);
596 fclose(file);
598 /* check to see if there were any errors reported in the file */
599 if(check_error(buf)) {
600 pinfo("Error reported in file %s - discarding data\n",
601 info->info_file);
602 return 0;
605 /* check to see if battery is present */
606 entry = strstr(buf, "present:");
607 val = get_value(entry);
608 if ((strncmp(val, "yes", 3)) == 0) {
609 info->present = 1;
610 } else {
611 pinfo("Battery %s not present\n", info->name);
612 info->present = 0;
613 return 0;
616 /* get design capacity
617 * note that all these integer values can also contain the
618 * string 'unknown', so we need to check for this. */
619 entry = strstr(buf, "design capacity:");
620 val = get_value(entry);
621 if (val[0] == 'u')
622 info->design_cap = -1;
623 else
624 info->design_cap = strtoul(val, NULL, 10);
626 /* get last full capacity */
627 entry = strstr(buf, "last full capacity:");
628 val = get_value(entry);
629 if (val[0] == 'u')
630 info->last_full_cap = -1;
631 else
632 info->last_full_cap = strtoul(val, NULL, 10);
634 /* get design voltage */
635 entry = strstr(buf, "design voltage:");
636 val = get_value(entry);
637 if (val[0] == 'u')
638 info->design_voltage = -1;
639 else
640 info->design_voltage = strtoul(val, NULL, 10);
643 if ((file = fopen(info->state_file, "r")) == NULL) {
644 perr("Could not open %s:", info->state_file );
645 perror(NULL);
646 return 0;
649 /* grab the file contents */
650 memset(buf, 0, sizeof(buf));
651 buflen = fread(buf, sizeof(buf), 1, file);
652 if (buflen != sizeof(buf) && ferror(file)) {
653 pfatal("Could not read file\n");
654 return 1;
656 fclose(file);
658 /* check to see if there were any errors reported in the file */
659 if(check_error(buf)) {
660 pinfo("Error reported in file %s - discarding data\n",
661 info->state_file);
662 return 0;
664 /* check to see if battery is present */
665 entry = strstr(buf, "present:");
666 val = get_value(entry);
667 if ((strncmp(val, "yes", 3)) == 0) {
668 info->present = 1;
669 } else {
670 info->present = 0;
671 perr("Battery %s no longer present\n", info->name);
672 return 0;
675 /* get charging state */
676 entry = strstr(buf, "charging state:");
677 val = get_value(entry);
678 if (val[0] == 'u')
679 info->charge_state = CH_ERR;
680 else if ((strncmp(val, "discharging", 10)) == 0)
681 info->charge_state = DISCHARGE;
682 else if ((strncmp(val, "charged", 7)) == 0)
683 /* this is a workaround for machines that report
684 * their charge state as 'charged', rather than
685 * what my laptop does, which is go straight to
686 * 'discharging'. dunno which matches the standard */
687 info->charge_state = DISCHARGE;
688 else
689 info->charge_state = CHARGE;
691 /* get current rate of burn
692 * note that if it's on AC, this will report 0 */
693 entry = strstr(buf, "present rate:");
694 val = get_value(entry);
695 if (val[0] == 'u') {
696 info->present_rate = -1;
697 } else {
698 int rate;
699 rate = strtoul(val, NULL, 10);
700 if (rate != 0)
701 info->present_rate = rate;
704 /* get remaining capacity */
705 entry = strstr(buf, "remaining capacity:");
706 val = get_value(entry);
707 if (val[0] == 'u')
708 info->remaining_cap = -1;
709 else
710 info->remaining_cap = strtoul(val, NULL, 10);
712 /* get current voltage */
713 entry = strstr(buf, "present voltage:");
714 val = get_value(entry);
715 if (val[0] == 'u')
716 info->present_voltage = -1;
717 else
718 info->present_voltage = strtoul(val, NULL, 10);
720 return 1;
723 int get_battery_info(global_t *globals, int batt_no)
725 if (data_source == SYSFS_DATA_SOURCE)
726 return sysfs_get_battery_info(globals, batt_no);
727 else
728 return procfs_get_battery_info(globals, batt_no);
732 * 2003-7-1.
733 * In order to make this code more convenient for things other than
734 * just plain old wmacpi-ng I'm breaking the basic functionality
735 * up into several chunks: collecting and collating info for a
736 * single battery, calculating the global info (such as rtime), and
737 * some stuff to provide a similar interface to now.
740 /* calculate the percentage remaining, using the values of
741 * remaining capacity and last full capacity, as outlined in
742 * the ACPI spec v2.0a, section 3.9.3. */
743 static int calc_remaining_percentage(int batt)
745 float rcap, lfcap;
746 battery_t *binfo;
747 int retval;
749 binfo = &batteries[batt];
751 rcap = (float)binfo->remaining_cap;
752 lfcap = (float)binfo->last_full_cap;
754 /* we use -1 to indicate that the value is unknown . . . */
755 if (rcap < 0) {
756 perr("unknown percentage value\n");
757 retval = -1;
758 } else {
759 if (lfcap <= 0)
760 lfcap = 1;
761 retval = (int)((rcap/lfcap) * 100.0);
762 pdebug("percent: %d\n", retval);
764 return retval;
767 /* check to see if we've been getting bad data from the batteries - if
768 * we get more than some limit we switch to using the remaining capacity
769 * for the calculations. */
770 static enum rtime_mode check_rt_mode(global_t *globals)
772 int i;
773 int bad_limit = 5;
774 battery_t *binfo;
776 /* if we were told what to do, we should keep doing it */
777 if(globals->rt_forced)
778 return globals->rt_mode;
780 for(i = 0; i < MAXBATT; i++) {
781 binfo = &batteries[i];
782 if(binfo->present && globals->adapter.power == BATT) {
783 if(binfo->present_rate <= 0) {
784 pdebug("Bad report from %s\n", binfo->name);
785 binfo->bad_count++;
789 for(i = 0; i < MAXBATT; i++) {
790 binfo = &batteries[i];
791 if(binfo->bad_count > bad_limit) {
792 if(globals->rt_mode != RT_CAP)
793 pinfo("More than %d bad reports from %s; "
794 "Switching to remaining capacity mode\n",
795 bad_limit, binfo->name);
796 return RT_CAP;
799 return RT_RATE;
802 /* calculate remaining time until the battery is charged.
803 * when charging, the battery state file reports the
804 * current being used to charge the battery. We can use
805 * this and the remaining capacity to work out how long
806 * until it reaches the last full capacity of the battery.
807 * XXX: make sure this is actually portable . . . */
808 static int calc_charge_time_rate(int batt)
810 float rcap, lfcap;
811 battery_t *binfo;
812 int charge_time = 0;
814 binfo = &batteries[batt];
816 if (binfo->charge_state == CHARGE) {
817 if (binfo->present_rate == -1) {
818 perr("unknown present rate\n");
819 charge_time = -1;
820 } else {
821 lfcap = (float)binfo->last_full_cap;
822 rcap = (float)binfo->remaining_cap;
824 charge_time = (int)(((lfcap - rcap)/binfo->present_rate) * 60.0);
826 } else
827 if (binfo->charge_time)
828 charge_time = 0;
829 return charge_time;
832 /* we need to calculate the present rate the same way we do in rt_cap
833 * mode, and then use that to estimate charge time. This will
834 * necessarily be even less accurate than it is for remaining time, but
835 * it's just as neessary . . . */
836 static int calc_charge_time_cap(int batt)
838 static float cap_samples[CAP_SAMPLES];
839 static int time_samples[CAP_SAMPLES];
840 static int sample_count = 0;
841 static int current = 0;
842 static int old = 1;
843 int rtime;
844 int tdiff;
845 float cdiff;
846 float current_rate;
847 battery_t *binfo = &batteries[batt];
849 cap_samples[current] = (float) binfo->remaining_cap;
850 time_samples[current] = time(NULL);
852 if (sample_count == 0) {
853 /* we can't do much if we don't have any data . . . */
854 current_rate = 0;
855 } else if (sample_count < CAP_SAMPLES) {
856 /* if we have less than SAMPLES samples so far, we use the first
857 * sample and the current one */
858 cdiff = cap_samples[current] - cap_samples[0];
859 tdiff = time_samples[current] - time_samples[0];
860 current_rate = cdiff/tdiff;
861 } else {
862 /* if we have more than SAMPLES samples, we use the oldest
863 * current one, which at this point is current + 1. This will
864 * wrap the same way that current will wrap, but one cycle
865 * ahead */
866 cdiff = cap_samples[current] - cap_samples[old];
867 tdiff = time_samples[current] - time_samples[old];
868 current_rate = cdiff/(float)tdiff;
870 if (current_rate == 0)
871 rtime = 0;
872 else {
873 float cap_left = (float)(binfo->last_full_cap - binfo->remaining_cap);
874 rtime = (int)(cap_left/(current_rate * 60.0));
876 sample_count++, current++, old++;
877 if (current >= CAP_SAMPLES)
878 current = 0;
879 if (old >= CAP_SAMPLES)
880 old = 0;
882 pdebug("cap charge time rem: %d\n", rtime);
883 return rtime;
886 static int calc_charge_time(global_t *globals, int batt)
888 int ctime = 0;
890 globals->rt_mode = check_rt_mode(globals);
892 switch(globals->rt_mode) {
893 case RT_RATE:
894 ctime = calc_charge_time_rate(batt);
895 break;
896 case RT_CAP:
897 ctime = calc_charge_time_cap(batt);
898 break;
900 return ctime;
903 void acquire_batt_info(global_t *globals, int batt)
905 battery_t *binfo;
906 adapter_t *ap = &globals->adapter;
908 get_battery_info(globals, batt);
910 binfo = &batteries[batt];
912 if (!binfo->present) {
913 binfo->percentage = 0;
914 binfo->valid = 0;
915 binfo->charge_time = 0;
916 globals->rtime = 0;
917 return;
920 binfo->percentage = calc_remaining_percentage(batt);
922 /* set the battery's capacity state, based (at present) on some
923 * guesstimated values: more than 75% == HIGH, 25% to 75% MED, and
924 * less than 25% is LOW. Less than globals->crit_level is CRIT. */
925 if (binfo->percentage == -1)
926 binfo->state = BS_ERR;
927 if (binfo->percentage < globals->crit_level)
928 binfo->state = CRIT;
929 else if (binfo->percentage > 75)
930 binfo->state = HIGH;
931 else if (binfo->percentage > 25)
932 binfo->state = MED;
933 else
934 binfo->state = LOW;
936 /* we need to /know/ that we've got a valid state for the
937 * globals->power value . . . .*/
938 ap->power = get_power_status(globals);
940 binfo->charge_time = calc_charge_time(globals, batt);
942 /* and finally, we tell anyone who wants to use this information
943 * that it's now valid . . .*/
944 binfo->valid = 1;
947 void acquire_all_batt_info(global_t *globals)
949 int i;
951 for(i = 0; i < globals->battery_count; i++)
952 acquire_batt_info(globals, i);
956 * One of the feature requests I've had is for some way to deal with
957 * batteries that are too dumb or too b0rken to report a present rate
958 * value. The way to do this, obviously, is to record the time that
959 * samples were taken and use that information to calculate the rate
960 * at which the battery is draining/charging. This still won't help
961 * systems where the battery doesn't even report the remaining
962 * capacity, but without the present rate or the remaining capacity, I
963 * don't think there's /anything/ we can do to work around it.
965 * So, what we need to do is provide a way to use a different method
966 * to calculate the time remaining. What seems most sensible is to
967 * split out the code to calculate it into a seperate function, and
968 * then provide multiple implementations . . .
972 * the default implementation - if present rate and remaining capacity
973 * are both reported correctly, we use them.
975 int calc_time_remaining_rate(global_t *globals)
977 int i;
978 int rtime;
979 float rcap = 0;
980 float rate = 0;
981 battery_t *binfo;
982 static float rate_samples[SAMPLES];
983 static int sample_count = 0;
984 static int j = 0;
985 static int n = 0;
987 /* calculate the time remaining, using the battery's remaining
988 * capacity and the reported burn rate (3.9.3).
989 * For added accuracy, we average the value over the last
990 * SAMPLES number of calls, or for anything less than this we
991 * simply report the raw number. */
992 /* XXX: this needs to correctly handle the case where
993 * any of the values used is unknown (which we flag using
994 * -1). */
995 for (i = 0; i < globals->battery_count; i++) {
996 binfo = &batteries[i];
997 if (binfo->present && binfo->valid) {
998 rcap += (float)binfo->remaining_cap;
999 rate += (float)binfo->present_rate;
1002 rate_samples[j] = rate;
1003 j++, sample_count++;
1004 if (j >= SAMPLES)
1005 j = 0;
1007 /* for the first SAMPLES number of calls we calculate the
1008 * average based on sample_count, then we use SAMPLES to
1009 * calculate the rolling average. */
1011 /* when this fails, n should be equal to SAMPLES. */
1012 if (sample_count < SAMPLES)
1013 n++;
1014 for (i = 0, rate = 0; i < n; i++) {
1015 /* if any of our samples are invalid, we drop
1016 * straight out, and flag our unknown values. */
1017 if (rate_samples[i] < 0) {
1018 rate = -1;
1019 rtime = -1;
1020 goto out;
1022 rate += rate_samples[i];
1024 rate = rate/(float)n;
1026 if ((rcap < 1) || (rate < 1)) {
1027 rtime = 0;
1028 goto out;
1030 if (rate <= 0)
1031 rate = 1;
1032 /* time remaining in minutes */
1033 rtime = (int)((rcap/rate) * 60.0);
1034 if(rtime <= 0)
1035 rtime = 0;
1036 out:
1037 pdebug("discharge time rem: %d\n", rtime);
1038 return rtime;
1042 * the alternative implementation - record the time at which each
1043 * sample was taken, and then use the difference between the latest
1044 * sample and the one SAMPLES ago to calculate the difference over
1045 * that time, and from there the rate of change of capacity.
1047 * XXX: this code sucks, but largely because batteries aren't exactly
1048 * precision instruments - mine only report with about 70mAH
1049 * resolution, so they don't report any changes until the difference
1050 * is 70mAH. This means that calculating the current rate from the
1051 * remaining capacity is very choppy . . .
1053 * To fix this, we should calculate an average over some number of
1054 * samples at the old end of the set - this would smooth out the
1055 * transitions.
1057 int calc_time_remaining_cap(global_t *globals)
1059 static float cap_samples[CAP_SAMPLES];
1060 static int time_samples[CAP_SAMPLES];
1061 static int sample_count = 0;
1062 static int current = 0;
1063 static int old = 1;
1064 battery_t *binfo;
1065 int i;
1066 int rtime;
1067 int tdiff;
1068 float cdiff;
1069 float cap = 0;
1070 float current_rate;
1072 for (i = 0; i < globals->battery_count; i++) {
1073 binfo = &batteries[i];
1074 if (binfo->present && binfo->valid)
1075 cap += binfo->remaining_cap;
1077 cap_samples[current] = cap;
1078 time_samples[current] = time(NULL);
1080 if (sample_count == 0) {
1081 /* we can't do much if we don't have any data . . . */
1082 current_rate = 0;
1083 } else if (sample_count < CAP_SAMPLES) {
1084 /* if we have less than SAMPLES samples so far, we use the first
1085 * sample and the current one */
1086 cdiff = cap_samples[0] - cap_samples[current];
1087 tdiff = time_samples[current] - time_samples[0];
1088 current_rate = cdiff/tdiff;
1089 } else {
1090 /* if we have more than SAMPLES samples, we use the oldest
1091 * current one, which at this point is current + 1. This will
1092 * wrap the same way that current will wrap, but one cycle
1093 * ahead */
1094 cdiff = cap_samples[old] - cap_samples[current];
1095 tdiff = time_samples[current] - time_samples[old];
1096 current_rate = cdiff/tdiff;
1098 if (current_rate == 0)
1099 rtime = 0;
1100 else
1101 rtime = (int)(cap_samples[current]/(current_rate * 60.0));
1103 sample_count++, current++, old++;
1104 if (current >= CAP_SAMPLES)
1105 current = 0;
1106 if (old >= CAP_SAMPLES)
1107 old = 0;
1109 pdebug("cap discharge time rem: %d\n", rtime);
1110 return rtime;
1113 void acquire_global_info(global_t *globals)
1115 adapter_t *ap = &globals->adapter;
1117 globals->rt_mode = check_rt_mode(globals);
1119 switch(globals->rt_mode) {
1120 case RT_RATE:
1121 globals->rtime = calc_time_remaining_rate(globals);
1122 break;
1123 case RT_CAP:
1124 globals->rtime = calc_time_remaining_cap(globals);
1125 break;
1128 /* get the power status.
1129 * note that this is actually reported seperately from the
1130 * battery info, under /proc/acpi/ac_adapter/AC/state */
1131 ap->power = get_power_status(globals);
1134 void acquire_all_info(global_t *globals)
1136 acquire_all_batt_info(globals);
1137 acquire_global_info(globals);