Logo Search packages:      
Sourcecode: yacpi version File versions

libacpi.c

/* libacpi 
 * Copyright (C) 2005-2007 Simon Fowler <simon@dreamcraft.com.au>, Nico Golde <nico@ngolde.de>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */


/* I know the text indention is a bit terrible, I will fix it in the future ;-) */

#define _GNU_SOURCE

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include <sys/types.h>
#include <dirent.h>
#include <time.h>
#include <ncurses.h>
#include "libacpi.h"

extern char *state[];

/* extern global_t *globals; */

/* local proto */
int acpi_get_design_cap (int batt);

/* initialise the batteries */
int init_batteries (global_t * globals) {
      DIR *battdir;
      struct dirent *batt;
      char *name;
      char *names[MAXBATT];
      int i, j;

      /* now enumerate batteries */
      globals->battery_count = 0;

      if(!(battdir = opendir ("/proc/acpi/battery"))){
            endwin ();
            exit(EXIT_FAILURE);
      }
      while ((batt = readdir (battdir))) {
            name = batt->d_name;

            /* skip . and .. */
            if (!strncmp (".", name, 1) || !strncmp ("..", name, 2))
                  continue;

            if(!(names[globals->battery_count] = strdup (name))){
                  endwin();
                  perror("strdup");
                  exit(EXIT_FAILURE);
            }
            globals->battery_count++;
      }
      closedir (battdir);

      /* A nice quick insertion sort, ala CLR. */
      {
            char *tmp1, *tmp2;
            for (i = 1; i < globals->battery_count; i++) {
                  tmp1 = names[i];
                  j = i - 1;
                  while ((j >= 0) && ((strcmp (tmp1, names[j])) < 0)) {
                        tmp2 = names[j + 1];
                        names[j + 1] = names[j];
                        names[j] = tmp2;
                  }
            }
      }

      for (i = 0; i < globals->battery_count; i++) {
            snprintf (batteries[i].name, MAX_NAME, "%s", names[i]);
            snprintf (batteries[i].info_file, MAX_NAME,
                        "/proc/acpi/battery/%s/info", names[i]);
            snprintf (batteries[i].state_file, MAX_NAME,
                        "/proc/acpi/battery/%s/state", names[i]);
      }

      return 0;
}

/* the actual name of the subdirectory under ac_adapter may
 * be anything, so we need to read the directory and use the
 * name we find there. */

/* get temperature of cpu */
int get_temp (global_t * globals) {
      DIR *tempdir;
      FILE *file;
      struct dirent *dirptr;
      char *name=NULL;
      temperature_t *temperature = &globals->temperature;

      if(!(tempdir = opendir ("/proc/acpi/thermal_zone"))){
            endwin();
            printf ("Unable to open /proc/acpi/thermal_zone -"
                        " are you sure this system supports temperature info?\n");
            exit(EXIT_FAILURE);
      }

      while ((dirptr = readdir (tempdir)) != NULL) {
            name = dirptr->d_name;
            if (!strncmp (".", name, 1) || !strncmp ("..", name, 2)) {
                  continue;
            }
      }
      closedir (tempdir);
      snprintf (temperature->state_file, MAX_NAME, "/proc/acpi/thermal_zone/%s/temperature", name);

      if (!(file = fopen (temperature->state_file, "r"))) {
            endwin();
            printf ("error: can't get information in %s\n", temperature->state_file);
            exit(EXIT_FAILURE);
      }
      fscanf (file, "temperature: %d", &temperature->temp);
      fclose (file);
      return temperature->temp;
}


int init_ac_adapters (global_t * globals) {
      DIR *acdir;
      struct dirent *adapter;
      adapter_t *ap = &globals->adapter;
      char *name=NULL;

      if(!(acdir = opendir ("/proc/acpi/ac_adapter"))){
            printf("Unable to open /proc/acpi/ac_adapter - are you sure this system supports ACPI?\n");
            return 1;
      }

      while ((adapter = readdir (acdir))) {
            name = adapter->d_name;

            if (!strncmp (".", name, 1) || !strncmp ("..", name, 2))
                  continue;
      }
      closedir (acdir);
      ap->name = strdup (name);
      snprintf (ap->state_file, MAX_NAME, "/proc/acpi/ac_adapter/%s/state", ap->name);
      return 0;
}

/* see if we have ACPI support and check version */
int power_init (global_t * globals) {
      FILE *acpi;
      char buf[4096];
      int acpi_ver = 0;
      int retval;

      if (!(acpi = fopen ("/proc/acpi/info", "r"))) {
            endwin();
            fprintf (stderr, "This system does not support ACPI\n");
            exit(EXIT_FAILURE);
      }

      /* okay, now see if we got the right version */
      fread (buf, 4096, 1, acpi);
      acpi_ver = strtol (buf + 25, NULL, 10);
      if (acpi_ver < 20020214) {
            fprintf (stderr, "This version requires ACPI subsystem version 20020214\n");
            fclose (acpi);
            exit(EXIT_FAILURE);
      }

      fclose (acpi);

      if (!(retval = init_batteries (globals)))
            retval = init_ac_adapters (globals);
      return retval;
}

char *get_value (char *string) {
      char *retval;
      int i=0;

      if (!string)
            return NULL;

      for(;string[i] != ':'; i++);
      for(;!isalnum(string[i]); i++);

      retval = (string + i);
      return retval;
}

int check_error (char *buf) {
      if (strstr (buf, "ERROR") != NULL)
            return 1;
      return 0;
}

power_state_t get_power_status (global_t * globals) {
      FILE *file;
      char buf[1024];
      char *val;
      adapter_t *ap = &globals->adapter;

      if ((file = fopen (ap->state_file, "r")) == NULL) {
            snprintf (buf, 1024, "Could not open state file %s", ap->state_file);
            perror ("fopen");
            return PS_ERR;
      }

      fgets (buf, 1024, file);
      fclose (file);
      val = get_value(buf);
      if ((strncmp (val, "on-line", 7)) == 0)
            return AC;
      else
            return BATT;
}

int get_battery_info (int batt_no) {
      FILE *file;
      battery_t *info = &batteries[batt_no];
      char buf[1024];
      char *entry;
      int buflen;
      char *val;

      if (!(file = fopen (info->info_file, "r"))) {
            perror ("fopen");
            return 0;
      }

      /* grab the contents of the file */
      buflen = fread (buf, sizeof (buf), 1, file);
      fclose (file);

      /* check to see if battery is present */
      if(!(entry = strstr (buf, "present:"))) return 0;

      val = get_value (entry);
      if ((strncmp (val, "yes", 3)) == 0) info->present = 1;
      else {
            info->present = 0;
            return 0;
      }

      /* get design capacity
       * note that all these integer values can also contain the
       * string 'unknown', so we need to check for this. */
      if(!(entry = strstr (buf, "design capacity:"))) return 0;

      val = get_value (entry);
      if (val[0] == 'u')
            info->design_cap = -1;
      else
            info->design_cap = strtoul (val, NULL, 10);

      /* get last full capacity */
      if(!(entry = strstr (buf, "last full capacity:"))) return 0;
      val = get_value (entry);
      if (val[0] == 'u')
            info->last_full_cap = -1;
      else
            info->last_full_cap = strtoul (val, NULL, 10);

      /* get design voltage */
      if(!(entry = strstr (buf, "design voltage:"))) return 0;
      val = get_value (entry);
      if (val[0] == 'u')
            info->design_voltage = -1;
      else
            info->design_voltage = strtoul (val, NULL, 10);


      if (!(file = fopen (info->state_file, "r"))) {
            perror ("fopen");
            return 0;
      }

      /* grab the file contents */
      memset (buf, 0, sizeof (buf));
      buflen = fread (buf, sizeof (buf), 1, file);
      fclose (file);

      /* check to see if there were any errors reported in the file */
      if (check_error (buf)) return 0;

      /* check to see if battery is present */
      if(!(entry = strstr (buf, "present:"))) return 0;
      val = get_value (entry);
      if ((strncmp (val, "yes", 3)) == 0) {
            info->present = 1;
      }
      else {
            info->present = 0;
            return 0;
      }

      /* get charging state */
      if(!(entry = strstr (buf, "charging state:"))) return 0;
      val = get_value (entry);
      if (val[0] == 'u')
            info->charge_state = CH_ERR;
      else if ((strncmp (val, "discharging", 11)) == 0)
            info->charge_state = DISCHARGE;
      else if ((strncmp (val, "charged", 7)) == 0)
            info->charge_state = CHARGED;
      else if ((strncmp (val, "charging", 8)) == 0)
            info->charge_state = CHARGE;
      else
            info->charge_state = NOINFO;

      /* get current rate of burn 
       * note that if it's on AC, this will report 0 */
      if(!(entry = strstr (buf, "present rate:"))) return 0;
      val = get_value (entry);
      if (val[0] == 'u') {
            info->present_rate = -1;
      }
      else {
            int rate;

            rate = strtoul (val, NULL, 10);
            if (rate != 0)
                  info->present_rate = rate;
      }

      /* get remaining capacity */
      if(!(entry = strstr (buf, "remaining capacity:"))) return 0;
      val = get_value (entry);
      if (val[0] == 'u')
            info->remaining_cap = -1;
      else
            info->remaining_cap = strtoul (val, NULL, 10);

      /* get current voltage */
      if(!(entry = strstr (buf, "present voltage:"))) return 0;
      val = get_value (entry);
      if (val[0] == 'u')
            info->present_voltage = -1;
      else
            info->present_voltage = strtoul (val, NULL, 10);

      return 1;
}

/* calculate the percentage remaining, using the values of 
 * remaining capacity and last full capacity, as outlined in
 * the ACPI spec v2.0a, section 3.9.3. */
static int calc_remaining_percentage (int batt) {
      float rcap, lfcap;
      battery_t *binfo;
      int retval;

      binfo = &batteries[batt];

      rcap = (float) binfo->remaining_cap;
      lfcap = (float) binfo->last_full_cap;

      /* we use -1 to indicate that the value is unknown . . . */
      if (rcap < 0) retval = -1;
      else {
            if (lfcap <= 0)
                  lfcap = 1;
            retval = (int) ((rcap / lfcap) * 100.0);
      }
      return retval>100?100:retval;
}

/* check to see if we've been getting bad data from the batteries - if
 * we get more than some limit we switch to using the remaining capacity
 * for the calculations. */
static enum rtime_mode check_rt_mode (global_t * globals) {
      int i;
      int bad_limit = 5;
      battery_t *binfo;

      /* if we were told what to do, we should keep doing it */
      if (globals->rt_forced)
            return globals->rt_mode;

      for (i = 0; i < MAXBATT; i++) {
            binfo = &batteries[i];
            if (binfo->present && globals->adapter.power == BATT) {
                  if (binfo->present_rate <= 0) binfo->bad_count++;
            }
      }
      for (i = 0; i < MAXBATT; i++) {
            binfo = &batteries[i];
            if (binfo->bad_count > bad_limit) return RT_CAP;
      }
      return RT_RATE;
}

/* calculate remaining time until the battery is charged.
 * when charging, the battery state file reports the 
 * current being used to charge the battery. We can use 
 * this and the remaining capacity to work out how long
 * until it reaches the last full capacity of the battery.
 * XXX: make sure this is actually portable . . . */
static int calc_charge_time_rate (int batt) {
      float rcap, lfcap;
      battery_t *binfo;
      int charge_time = 0;

      binfo = &batteries[batt];

      if (binfo->charge_state == CHARGE) {
            if (binfo->present_rate == -1) charge_time = -1;
            else {
                  lfcap = (float) binfo->last_full_cap;
                  rcap = (float) binfo->remaining_cap;

                  charge_time = (int) (((lfcap - rcap) / binfo->present_rate) * 60.0);
            }
      }
      else if (binfo->charge_time)
            charge_time = 0;
      return charge_time;
}

/* we need to calculate the present rate the same way we do in rt_cap
 * mode, and then use that to estimate charge time. This will 
 * necessarily be even less accurate than it is for remaining time, but
 * it's just as neessary . . . */
static int calc_charge_time_cap (int batt) {
      static float cap_samples[CAP_SAMPLES];
      static int time_samples[CAP_SAMPLES];
      static int sample_count = 0;
      static int current = 0;
      static int old = 1;
      int rtime;
      int tdiff;
      float cdiff;
      float current_rate;
      battery_t *binfo = &batteries[batt];

      cap_samples[current] = (float) binfo->remaining_cap;
      time_samples[current] = time (NULL);

      if (sample_count == 0) {
            /* we can't do much if we don't have any data . . . */
            current_rate = 0;
      }
      else if (sample_count < CAP_SAMPLES) {
            /* if we have less than SAMPLES samples so far, we use the first
             * sample and the current one */
            cdiff = cap_samples[current] - cap_samples[0];
            tdiff = time_samples[current] - time_samples[0];
            current_rate = cdiff / tdiff;
      }
      else {
            /* if we have more than SAMPLES samples, we use the oldest
             * current one, which at this point is current + 1. This will
             * wrap the same way that current will wrap, but one cycle
             * ahead */
            cdiff = cap_samples[current] - cap_samples[old];
            tdiff = time_samples[current] - time_samples[old];
            current_rate = cdiff / (float) tdiff;
      }
      if (current_rate == 0)
            rtime = 0;
      else {
            float cap_left = (float) (binfo->last_full_cap - binfo->remaining_cap);

            rtime = (int) (cap_left / (current_rate * 60.0));
      }
      sample_count++, current++, old++;
      if (current >= CAP_SAMPLES)
            current = 0;
      if (old >= CAP_SAMPLES)
            old = 0;

      return rtime;
}

static int calc_charge_time (global_t * globals, int batt) {
      int ctime = 0;

      globals->rt_mode = check_rt_mode (globals);

      switch (globals->rt_mode) {
            case RT_RATE:
                  ctime = calc_charge_time_rate (batt);
                  break;
            case RT_CAP:
                  ctime = calc_charge_time_cap (batt);
                  break;
      }
      return ctime;
}

void acquire_batt_info (global_t * globals, int batt) {
      battery_t *binfo;
      adapter_t *ap = &globals->adapter;
      get_battery_info (batt);

      binfo = &batteries[batt];

      if (!binfo->present) {
            binfo->percentage = 0;
            binfo->valid = 0;
            binfo->charge_time = 0;
            globals->rtime = 0;
            return;
      }

      binfo->percentage = calc_remaining_percentage (batt);

      /* set the battery's capacity state, based (at present) on some 
       * guesstimated values: more than 75% == HIGH, 25% to 75% MED, and
       * less than 25% is LOW. Less than globals->crit_level is CRIT. */
      if (binfo->percentage == -1)
            binfo->state = BS_ERR;
      if (binfo->percentage < globals->crit_level)
            binfo->state = CRIT;
      else if (binfo->percentage > 75)
            binfo->state = HIGH;
      else if (binfo->percentage > 25)
            binfo->state = MED;
      else
            binfo->state = LOW;

      /* we need to /know/ that we've got a valid state for the 
       * globals->power value . . . .*/
      ap->power = get_power_status (globals);

      if ((ap->power != AC) && (binfo->charge_state == DISCHARGE)) {
            /* we're not on power, and not charging. So we might as well 
             * check if we're at a critical battery level, and calculate
             * other interesting stuff . . . */
            if (binfo->capacity_state == CRITICAL) ap->power = HARD_CRIT;
      }

      binfo->charge_time = calc_charge_time (globals, batt);

      /* and finally, we tell anyone who wants to use this information
       * that it's now valid . . .*/
      binfo->valid = 1;
}

void acquire_all_batt_info (global_t * globals) {
      int i;
      for (i = 0; i < globals->battery_count; i++)
            acquire_batt_info (globals, i);
}

/*
 * One of the feature requests I've had is for some way to deal with
 * batteries that are too dumb or too b0rken to report a present rate
 * value. The way to do this, obviously, is to record the time that
 * samples were taken and use that information to calculate the rate
 * at which the battery is draining/charging. This still won't help
 * systems where the battery doesn't even report the remaining
 * capacity, but without the present rate or the remaining capacity, I
 * don't think there's /anything/ we can do to work around it.
 *
 * So, what we need to do is provide a way to use a different method
 * to calculate the time remaining. What seems most sensible is to
 * split out the code to calculate it into a seperate function, and
 * then provide multiple implementations . . . 
 */

/*
 * the default implementation - if present rate and remaining capacity
 * are both reported correctly, we use them.
 */
int calc_time_remaining_rate (global_t * globals) {
      int i;
      int rtime;
      float rcap = 0;
      float rate = 0;
      battery_t *binfo;
      static float rate_samples[SAMPLES];
      static int sample_count = 0;
      static int j = 0;
      static int n = 0;

      /* calculate the time remaining, using the battery's remaining 
       * capacity and the reported burn rate (3.9.3). 
       * For added accuracy, we average the value over the last 
       * SAMPLES number of calls, or for anything less than this we
       * simply report the raw number. */
      /* XXX: this needs to correctly handle the case where 
       * any of the values used is unknown (which we flag using
       * -1). */
      for (i = 0; i < globals->battery_count; i++) {
            binfo = &batteries[i];
            if (binfo->present && binfo->valid) {
                  rcap = (float) binfo->remaining_cap;
                  rate = (float) binfo->present_rate;
            }
      }
      if(globals->samplef){
            rate_samples[j] = rate;
            j++, sample_count++;
            if (j >= SAMPLES)
                  j = 0;

            /* for the first SAMPLES number of calls we calculate the
             * average based on sample_count, then we use SAMPLES to
             * calculate the rolling average. */

            /* when this fails, n should be equal to SAMPLES. */
            if (sample_count < SAMPLES)
                  n++;
            for (i = 0, rate = 0; i < n; i++) {
                  /* if any of our samples are invalid, we drop 
                   * straight out, and flag our unknown values. */
                  if (rate_samples[i] < 0) {
                        rate = -1;
                        rtime = -1;
                        return rtime;
                  }
                  rate = rate_samples[i];
            }
            rate += rate / (float) n;
      }
      if ((rcap < 1) || (rate < 1)) {
            rtime = 0;
            return rtime;
      }
      if (rate <= 0) rate = 1;
      /* time remaining in minutes */
      rtime = (int) ((rcap / rate) * 60.0);
      if (rtime <= 0) rtime = 0;
      return rtime;
}

/*
 * the alternative implementation - record the time at which each
 * sample was taken, and then use the difference between the latest
 * sample and the one SAMPLES ago to calculate the difference over
 * that time, and from there the rate of change of capacity.
 *
 * XXX: this code sucks, but largely because batteries aren't exactly
 * precision instruments - mine only report with about 70mAH
 * resolution, so they don't report any changes until the difference
 * is 70mAH. This means that calculating the current rate from the
 * remaining capacity is very choppy . . . 
 *
 * To fix this, we should calculate an average over some number of
 * samples at the old end of the set - this would smooth out the
 * transitions. 
 */

int calc_time_remaining_cap (global_t * globals) {
      static float cap_samples[CAP_SAMPLES];
      static int time_samples[CAP_SAMPLES];
      static int sample_count = 0;
      static int current = 0;
      static int old = 1;
      battery_t *binfo;
      int i;
      int rtime;
      int tdiff;
      float cdiff;
      float cap = 0;
      float current_rate;

      for (i = 0; i < globals->battery_count; i++) {
            binfo = &batteries[i];
            if (binfo->present && binfo->valid)
                  cap += binfo->remaining_cap;
      }
      cap_samples[current] = cap;
      time_samples[current] = time (NULL);

      if (sample_count == 0) {
            /* we can't do much if we don't have any data . . . */
            current_rate = 0;
      }
      else if (sample_count < CAP_SAMPLES) {
            /* if we have less than SAMPLES samples so far, we use the first
             * sample and the current one */
            cdiff = cap_samples[0] - cap_samples[current];
            tdiff = time_samples[current] - time_samples[0];
            current_rate = cdiff / tdiff;
      }
      else {
            /* if we have more than SAMPLES samples, we use the oldest
             * current one, which at this point is current + 1. This will
             * wrap the same way that current will wrap, but one cycle
             * ahead */
            cdiff = cap_samples[old] - cap_samples[current];
            tdiff = time_samples[current] - time_samples[old];
            current_rate = cdiff / tdiff;
      }
      if (current_rate == 0)
            rtime = 0;
      else
            rtime = (int) (cap_samples[current] / (current_rate * 60.0));

      sample_count++, current++, old++;
      if (current >= CAP_SAMPLES)
            current = 0;
      if (old >= CAP_SAMPLES)
            old = 0;

      return rtime;
}

void acquire_global_info (global_t * globals) {
      adapter_t *ap = &globals->adapter;

      globals->rt_mode = check_rt_mode (globals);

      switch (globals->rt_mode) {
            case RT_RATE:
                  globals->rtime = calc_time_remaining_rate (globals);
                  break;
            case RT_CAP:
                  globals->rtime = calc_time_remaining_cap (globals);
                  break;
      }

      /* get the power status.
       * note that this is actually reported seperately from the
       * battery info, under /proc/acpi/ac_adapter/AC/state */
      ap->power = get_power_status (globals);
}

void acquire_all_info (global_t * globals)
{
      acquire_all_batt_info (globals);
      acquire_global_info (globals);
}

Generated by  Doxygen 1.6.0   Back to index