Logo Search packages:      
Sourcecode: yacpi version File versions  Download package

libacpi.c

/* libacpi 
 * Copyright (C) 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;
  battdir = opendir ("/proc/acpi/battery");
  if (battdir == NULL) {
    endwin ();
    pfatal ("No batteries or ACPI not supported\n");
    exit (1);
  }
  while ((batt = readdir (battdir))) {
    name = batt->d_name;

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

    names[globals->battery_count] = strdup (name);
    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]);
    pdebug ("battery detected at %s\n", batteries[i].info_file);
    pinfo ("found battery %s\n", names[i]);
  }

  /* tell user some info */
  pdebug ("%d batteries detected\n", globals->battery_count);
  pinfo ("libacpi: found %d batter%s\n", globals->battery_count,
         (globals->battery_count == 1) ? "y" : "ies");

  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;
  temperature_t *temperature = &globals->temperature;

  tempdir = opendir ("/proc/acpi/thermal_zone");
  if (tempdir == NULL) {
    printf ("Unable to open /proc/acpi/thermal_zone -"
            " are you sure this system supports temperature info?\n");
    endwin();
    exit(1);
  }
  name = NULL;
  while ((dirptr = readdir (tempdir)) != NULL) {
    name = dirptr->d_name;
    if (!strncmp (".", name, 1) || !strncmp ("..", name, 2)) {
      continue;
    }
  }
  closedir (tempdir);
  snprintf (temperature->state_file, 45,
            "/proc/acpi/thermal_zone/%s/temperature", name);
  if ((file = fopen (temperature->state_file, "r")) == NULL) {
    endwin();
    printf ("error: can't get information in %s\n", temperature->state_file);
    exit(1);
  }
  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;

  acdir = opendir ("/proc/acpi/ac_adapter");
  if (acdir == NULL) {
    pfatal ("Unable to open /proc/acpi/ac_adapter -"
            " are you sure this system supports ACPI?\n");
    return 1;
  }
  name = NULL;
  while ((adapter = readdir (acdir)) != NULL) {
    name = adapter->d_name;

    if (!strncmp (".", name, 1) || !strncmp ("..", name, 2))
      continue;
    pdebug ("found adapter %s\n", name);
  }
  closedir (acdir);
  ap->name = strdup (name);
  snprintf (ap->state_file, MAX_NAME, "/proc/acpi/ac_adapter/%s/state",
            ap->name);
  pinfo ("libacpi: found ac adapter %s\n", 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();
    pfatal ("This system does not support ACPI\n");
    exit(1);
  }

  /* okay, now see if we got the right version */
  fread (buf, 4096, 1, acpi);
  acpi_ver = strtol (buf + 25, NULL, 10);
  pinfo ("ACPI version detected: %d\n", acpi_ver);
  if (acpi_ver < 20020214) {
    pfatal ("This version requires ACPI subsystem version 20020214\n");
    fclose (acpi);
    exit(1);
  }
  /* yep, all good */
  fclose (acpi);

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

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

  if (string == NULL)
    return NULL;

  i = 0;
  while (string[i] != ':')
    i++;
  while (!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 (buf);
    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")) == NULL) {
    /* this is cheating, but string concatenation should work . . . */
    pfatal ("Could not open %s:", info->info_file);
    perror (NULL);
    return 0;
  }

  /* grab the contents of the file */
  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)) {
    pinfo ("Error reported in file %s - discarding data\n", info->info_file);
    return 0;
  }

  /* check to see if battery is present */
  entry = strstr (buf, "present:");
  val = get_value (entry);
  if ((strncmp (val, "yes", 3)) == 0) {
    info->present = 1;
  }
  else {
    pinfo ("Battery %s not present\n", info->name);
    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. */
  entry = strstr (buf, "design capacity:");
  val = get_value (entry);
  if (val[0] == 'u')
    info->design_cap = -1;
  else
    info->design_cap = strtoul (val, NULL, 10);

  /* get last full capacity */
  entry = strstr (buf, "last full capacity:");
  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 */
  entry = strstr (buf, "design voltage:");
  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")) == NULL) {
    perr ("Could not open %s:", info->state_file);
    perror (NULL);
    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)) {
    pinfo ("Error reported in file %s - discarding data\n", info->state_file);
    return 0;
  }
  /* check to see if battery is present */
  entry = strstr (buf, "present:");
  val = get_value (entry);
  if ((strncmp (val, "yes", 3)) == 0) {
    info->present = 1;
  }
  else {
    info->present = 0;
    perr ("Battery %s no longer present\n", info->name);
    return 0;
  }

  /* get charging state */
  entry = strstr (buf, "charging state:");
  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 */
  entry = strstr (buf, "present rate:");
  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 */
  entry = strstr (buf, "remaining capacity:");
  val = get_value (entry);
  if (val[0] == 'u')
    info->remaining_cap = -1;
  else
    info->remaining_cap = strtoul (val, NULL, 10);

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

  return 1;
}

/*
 * 2003-7-1.
 * In order to make this code more convenient for things other than
 * just plain old wmacpi-ng I'm breaking the basic functionality
 * up into several chunks: collecting and collating info for a 
 * single battery, calculating the global info (such as rtime), and 
 * some stuff to provide a similar interface to now.
 */

/* 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) {
    perr ("unknown percentage value\n");
    retval = -1;
  }
  else {
    if (lfcap <= 0)
      lfcap = 1;
    retval = (int) ((rcap / lfcap) * 100.0);
    pdebug ("percent: %d\n", retval);
  }
  if (retval > 100)
    retval = 100;
  return 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) {
        pdebug ("Bad report from %s\n", binfo->name);
        binfo->bad_count++;
      }
    }
  }
  for (i = 0; i < MAXBATT; i++) {
    binfo = &batteries[i];
    if (binfo->bad_count > bad_limit) {
      if (globals->rt_mode != RT_CAP)
        pinfo ("More than %d bad reports from %s; "
               "Switching to remaining capacity mode\n",
               bad_limit, binfo->name);
      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) {
      perr ("unknown present rate\n");
      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;

  pdebug ("cap charge time rem: %d\n", rtime);
  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) {
      pinfo ("Received critical battery status");
      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;
    }
  }
  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;
      goto out;
    }
    rate = rate_samples[i];
  }
  rate += rate / (float) n;

  if ((rcap < 1) || (rate < 1)) {
    rtime = 0;
    goto out;
  }
  if (rate <= 0)
    rate = 1;
  /* time remaining in minutes */
  rtime = (int) ((rcap / rate) * 60.0);
  if (rtime <= 0)
    rtime = 0;
out:
  pdebug ("discharge time rem: %d\n", rtime);
  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;

  pdebug ("cap discharge time rem: %d\n", rtime);
  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