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

upscode2.c

/* upscode2.c - model specific routines for UPSes using the UPScode II
            command set.  This includes PowerWare, Fiskars,
            Compaq (PowerWare OEM?), some IBM (PowerWare OEM?)

   Copyright (C) 2002 H?vard Lygre <hklygre@online.no>
   Copyright (C) 2004-2006 Niels Baggesen <niels@baggesen.net>
   Copyright (C) 2006 Niklas Edmundsson <nikke@acc.umu.se>

   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

   $Id: upscode2.c 2350 2010-02-16 08:28:21Z adkorte-guest $
*/

/*
 * Currently testing against
 * Fiskars PowerRite Max
 * Fiskars PowerServer 10
 * Fiskars PowerServer 30
 * Powerware Profile (9150)
 * Powerware 9305
 *
 * Also tested against
 * Compaq T1500h (Per J?nsson <per.jonsson@bth.se>)
 * Powerware 9120 (Gorm J. Siiger <gjs@sonnit.dk>)
 * Fiskars PowerServer 10 (Per Larsson <tucker@algonet.se>)
 */

#include "main.h"
#include "serial.h"
#include "timehead.h"
#include "nut_stdint.h"

#include <math.h>

#define DRIVER_NAME     "UPScode II UPS driver"
#define DRIVER_VERSION  "0.87"

/* driver description structure */
upsdrv_info_t     upsdrv_info = {
      DRIVER_NAME,
      DRIVER_VERSION,
      "H K Lygre, <hklygre@online.no>\n" \
      "Niels Baggesen <niels@baggesen.net>\n" \
      "Niklas Edmundsson <nikke@acc.umu.se>",
      DRV_EXPERIMENTAL,
      { NULL }
};

#define ENDCHAR         '\n'

/* default values */
#define OUT_PACE_USEC   200
#define INP_TIMO_SEC    2
#define FULL_UPDATE_TIMER     60

#define UPSC_BUFLEN     256  /* Size of response buffers from UPS */

/* Status messages from UPS */
#define UPSC_STAT_ONLINE       (1UL << 0)
#define UPSC_STAT_ONBATT       (1UL << 1)
#define UPSC_STAT_LOBATT       (1UL << 2)
#define UPSC_STAT_REPLACEBATT  (1UL << 3)
#define UPSC_STAT_BOOST        (1UL << 4)
#define UPSC_STAT_TRIM         (1UL << 5)
#define UPSC_STAT_OVERLOAD     (1UL << 6)
#define UPSC_STAT_CALIBRATION  (1UL << 7)
#define UPSC_STAT_OFF          (1UL << 8)
#define UPSC_STAT_BYPASS       (1UL << 9)
#define UPSC_STAT_NOTINIT      (1UL << 31)


typedef enum {
      t_ignore,   /* Ignore this response  */
      t_value,    /* Sets a NUT variable */
      t_final,        /* Marks the end of UPS data for this command */
      t_string,   /* Set a NUT string variable */
      t_finstr,   /* Set a NUT string variable, and marks end of data */
      t_setval,   /* Sets a variable in the driver and possibly in NUT */
      t_setrecip, /* Sets a driver variable to 1/value and possibly NUT */
      t_setpct,
      t_setrecpct,
      t_status,   /* Sets a status bit */
      t_alarm,    /* Sets an alarm message */
      t_list            /* The value must be matched in the list */
} type_t;


typedef struct simple_s {
      const char *code;
      type_t type;
      const char *desc;
      int status;
      float *aux;
      struct simple_s *stats;
} simple_t;

typedef struct {
      const char *cmd;
      const char *upsc;
      const char *upsp;
      int enabled;
} cmd_t;


static int
      can_upda = 0,
      can_upbs = 0,
      can_upid = 0,
      can_uppm = 0,
      can_updt = 0,
      can_uptm = 0,
      can_upsd = 0,
      can_uppc = 0;

static char has_uppm_p[100];

static int
      input_timeout_sec = INP_TIMO_SEC,
      output_pace_usec = OUT_PACE_USEC,
      full_update_timer = FULL_UPDATE_TIMER,
      use_crlf = 0,
      use_pre_lf = 0,
      buffer_empty = 0;

static uint32_t
      status = UPSC_STAT_NOTINIT;

static time_t
      last_full = 0;

static float
      batt_volt_low = 0,
      batt_volt_nom = 0,
      batt_volt_high = 0,
      batt_volt = 0,
      batt_cap_nom = -1,
      batt_charge = -1,
      batt_current = -1,
      batt_disch_curr_max = 0,
      batt_runtime = -1,
      batt_runtime_max = -1,
      kilo_to_unity = 1000.0,
      outpwr_factor = 1000.0,
      nom_out_current = -1,
      max_out_current = -1;

/* To get average battery current */
#define NUM_BATTHIST 60
      static float batthist[NUM_BATTHIST];
      static int numbatthist=0, lastbatthist=0;

static int
      inited_phaseinfo = 0,
      num_inphases = 1,
      num_outphases = 1;


/* Status codes for the STAT and STMF status responses */
static simple_t att[] = {
      { "00", t_ignore  },
      { "AC", t_alarm,  "Aux contact failure", 0 },
      { "BA", t_alarm,  "Batteries disconnected", 0 },
      { "BC", t_alarm,  "Backfeed contact failure", 0 },
      { "BD", t_alarm,  "Abnormal battery discharge", 0 },
      { "BF", t_alarm,  "Battery fuse  failure", 0 },
      { "BL", t_status, "Battery low limit", UPSC_STAT_LOBATT },
      { "BO", t_alarm,  "Battery over voltage", 0 },
      { "BP", t_alarm,  "Bypass fuse failure", 0 },
      { "BR", t_alarm,  "Abnormal battery recharge", 0 },
      { "BT", t_alarm,  "Battery over temperature", 0 },
      { "BX", t_alarm,  "Bypass unavailable", 0 },
      { "BY", t_alarm,  "Battery failure", 0 },
      { "CE", t_alarm,  "Configuration error", 0 },
      { "CM", t_alarm,  "Battery converter failure", 0 },
      { "CT", t_alarm,  "Cabinet over temperature", 0 },
      { "DO", t_alarm,  "DC over voltage", 0 },
      { "DU", t_alarm,  "DC under voltage", 0 },
      { "EP", t_alarm,  "Emergency power off", 0 },
      { "FF", t_alarm,  "Fan failure", 0 },
      { "FH", t_alarm,  "Line frequency high", 0 },
      { "FL", t_alarm,  "Line frequency low", 0 },
      { "FT", t_alarm,  "Filter over temperature", 0 },
      { "GF", t_alarm,  "Ground failure", 0 },
      { "HT", t_alarm,  "Charger over temperature", 0 },
      { "IB", t_alarm,  "Internal data bus failure", 0 },
      { "IF", t_alarm,  "Inverter fuse failure", 0 },
      { "IM", t_alarm,  "Inverter failure", 0 },
      { "IO", t_alarm,  "Inverter over voltage", 0 },
      { "IP", t_alarm,  "Internal power supply failure", 0 },
      { "IT", t_alarm,  "Inverter over temperature", 0 },
      { "IU", t_alarm,  "Inverter under voltage", 0 },
      { "IV", t_alarm,  "Inverter off", 0 },
      { "LR", t_alarm,  "Loss of redundancy", 0 },
      { "NF", t_alarm,  "Neutral fault", 0 },
      { "OD", t_status, "UPS not supplying load", UPSC_STAT_OFF },
      { "OF", t_alarm,  "Oscillator failure", 0 },
      { "OL", t_status, "Overload", UPSC_STAT_OVERLOAD },
      { "OR", t_alarm,  "Redundancy overload", 0 },
      { "OV", t_alarm,  "Abnormal output voltage", 0 },
      { "OW", t_alarm,  "Output failure", 0 },
      { "PB", t_alarm,  "Parallel bus failure", 0 },
      { "PE", t_alarm,  "Phase rotation error", 0 },
      { "RE", t_alarm,  "Rectifier off", 0 },
      { "RF", t_alarm,  "Rectifier fuse failure", 0 },
      { "RM", t_alarm,  "Rectifier failure", 0 },
      { "RT", t_alarm,  "Rectifier over temperature", 0 },
      { "SM", t_alarm,  "Static switch failure", 0 },
      { "ST", t_alarm,  "Static switch over temperature", 0 },
      { "TT", t_alarm,  "Trafo over temperature", 0 },
      { "UD", t_alarm,  "UPS disabled", 0 },
      { "UO", t_alarm,  "Utility over voltage", 0 },
      { "US", t_alarm,  "Unsynchronized", 0 },
      { "UU", t_alarm,  "Utility under voltage", 0 },
      { "VE", t_alarm,  "internal voltage error", 0 },
      { NULL }
};


/* Status code for the STLR response */
static simple_t stlr[] = {
      { "NO", t_ignore },
      { "SD", t_status, NULL, UPSC_STAT_TRIM },
      { "SU", t_status, NULL, UPSC_STAT_BOOST },
      { "DU", t_status, NULL, UPSC_STAT_BOOST },
      { NULL }
};


/* Status code for the STEA and STEM responses */
static simple_t env[] = {
      { "HH", t_ignore, "Humidity high", 0 },
      { "HL", t_ignore, "Humidity low", 0 },
      { "TH", t_ignore, "Temperature high", 0 },
      { "TL", t_ignore, "Temperature low", 0 },
      { "01", t_ignore, "Environment alarm 1", 0 },
      { "02", t_ignore, "Environment alarm 2", 0 },
      { "03", t_ignore, "Environment alarm 3", 0 },
      { "04", t_ignore, "Environment alarm 4", 0 },
      { "05", t_ignore, "Environment alarm 5", 0 },
      { "06", t_ignore, "Environment alarm 6", 0 },
      { "07", t_ignore, "Environment alarm 7", 0 },
      { "08", t_ignore, "Environment alarm 8", 0 },
      { "09", t_ignore, "Environment alarm 9", 0 },
      { NULL }
};


/* Responses for UPSS and UPDS */
static simple_t simple[] = {
      { "STAT", t_list,   NULL, 0, 0, att },
      { "STBO", t_status, NULL, UPSC_STAT_ONBATT },
      { "STBL", t_status, NULL, UPSC_STAT_LOBATT },
      { "STBM", t_ignore },
      { "STBP", t_status, NULL, UPSC_STAT_BYPASS },
      { "STEA", t_list,   NULL, 0, 0, env },
      { "STEM", t_list,   NULL, 0, 0, env },
      { "STLR", t_list,   NULL, 0, 0, stlr },
      { "STMF", t_list,   NULL, 0, 0, att },
      { "STOK", t_ignore },
      { "STUF", t_status, NULL, UPSC_STAT_ONBATT },

      { "BTIME", t_setval, NULL, 0, &batt_runtime },

      { "METE1", t_value, "ambient.temperature" },
      { "MERH1", t_value, "ambient.humidity" },

      { "MIFFF", t_value, "input.frequency" },
      { "MIIL1", t_value, "input.current" },
      { "MIIL2", t_value, "input.L2.current" },
      { "MIIL3", t_value, "input.L3.current" },
      { "MIPL1", t_value, "input.realpower" },
      { "MIPL2", t_value, "input.L2.realpower" },
      { "MIPL3", t_value, "input.L3.realpower" },
      { "MISL1", t_value, "input.power" },
      { "MISL2", t_value, "input.L2.power" },
      { "MISL3", t_value, "input.L3.power" },
      { "MIUL1", t_value, "input.voltage" },
      { "MIUL2", t_value, "input.L2-N.voltage" },
      { "MIUL3", t_value, "input.L3-N.voltage" },
      { "MIU12", t_value, "input.L1-L2.voltage" },
      { "MIU23", t_value, "input.L2-L3.voltage" },
      { "MIU31", t_value, "input.L3-L1.voltage" },

      { "MBCH1", t_setval, NULL, 0, &batt_charge }, /* battery.charge */
      { "MBIII", t_setval, "battery.current", 0, &batt_current },
      { "MBINE", t_ignore, /* "battery.current.negative" */ },
      { "MBIPO", t_ignore, /* "battery.current.positive" */ },
      { "MBUNE", t_ignore, /* "battery.voltage.negative" */ },
      { "MBUPO", t_ignore, /* "battery.voltage.positive" */},
      { "MBUUU", t_setval, "battery.voltage", 0, &batt_volt },

      { "MLUNE", t_ignore, /* "dc.voltage.negative" */ },
      { "MLUPO", t_ignore, /* "dc.voltage.positive" */ },
      { "MLUUU", t_ignore, /* "dc.voltage" */ },

      { "MOFFF", t_final, "output.frequency" },
      { "MOIL1", t_value, "output.current" },
      { "MOIL2", t_value, "output.L2.current" },
      { "MOIL3", t_value, "output.L3.current" },
      { "MOIP1", t_value, "output.peakcurrent" },
      { "MOIP2", t_value, "output.L2.peakcurrent" },
      { "MOIP3", t_value, "output.L3.peakcurrent" },
      { "MOPL1", t_value, "output.realpower", 0, &kilo_to_unity },
      { "MOPL2", t_value, "output.L2.realpower", 0, &kilo_to_unity },
      { "MOPL3", t_value, "output.L3.realpower", 0, &kilo_to_unity },
      { "MOSL1", t_value, "output.power" },
      { "MOSL2", t_value, "output.L2.power" },
      { "MOSL3", t_value, "output.L3.power" },
      { "MOUL1", t_value, "output.voltage" },
      { "MOUL2", t_value, "output.L2-N.voltage" },
      { "MOUL3", t_value, "output.L3-N.voltage" },
      { "MOU12", t_value, "output.L1-L2.voltage" },
      { "MOU23", t_value, "output.L2-L3.voltage" },
      { "MOU31", t_value, "output.L3-L1.voltage" },

      { "MPUL1", t_value, "input.bypass.L1-N.voltage" },
      { "MPUL2", t_value, "input.bypass.L2-N.voltage" },
      { "MPUL3", t_value, "input.bypass.L3-N.voltage" },

      { "MUTE1", t_value, "ups.temperature" },
      { NULL }
};


/* Responses for UPDV */
static simple_t nominal[] = {
      { "NIUHH", t_value, "input.voltage.maximum" },
      { "NIULL", t_value, "input.voltage.minimum" },
      { "NIUNN", t_value, "input.voltage.nominal" },

      { "NIIHH", t_value, "input.current.maximum" },
      { "NIILL", t_value, "input.current.minimum" },
      { "NIINN", t_value, "input.current.nominal" },

      { "NIPHH", t_value, "input.realpower.maximum" },
      { "NIPNN", t_value, "input.realpower.nominal" },

      { "NISHH", t_value, "input.power.maximum" },
      { "NISNN", t_value, "input.power.nominal" },

      { "NBAHN", t_setval, "battery.capacity.nominal", 0, &batt_cap_nom },
      { "NBIHH", t_ignore, "battery charge current maximum" },
      { "NBILL", t_setval, NULL, 0, &batt_disch_curr_max},
      { "NBINN", t_value, "battery.current.nominal" },
      { "NBTHH", t_setval, NULL, 0, &batt_runtime_max},
      { "NBUHH", t_setval, "battery.voltage.maximum", 0, &batt_volt_high },
      { "NBULL", t_setval, "battery.voltage.minimum", 0, &batt_volt_low },
      { "NBUNN", t_setval,  "battery.voltage.nominal", 0, &batt_volt_nom },

      { "NOFHH", t_value,  "output.frequency.maximum" },
      { "NOFLL", t_final,  "output.frequency.minimum" },
      { "NOIHH", t_setval,  "output.current.maximum", 0, &max_out_current },
      { "NOINN", t_setval, "output.current.nominal", 0, &nom_out_current },
      { "NOPNN", t_value, "output.realpower.nominal", 0, &outpwr_factor },
      { "NOSNN", t_value,  "ups.power.nominal", 0, &outpwr_factor },
      { "NOUHH", t_value,  "output.voltage.maximum" },
      { "NOULL", t_value,  "output.voltage.minimum" },
      { "NOUNN", t_value,  "output.voltage.nominal" },

      { "NUTEH", t_value,  "ups.temperature.maximum" },
      { NULL }
};


/* Status responses for UPBS command */
static simple_t battery[] = {
      { "MBTE1", t_value, "battery.1.temperature" },
      { "MBIN1", t_ignore, NULL /* aging index */ },
      { "BDAT1", t_string, "battery.1.date" },
      { "MBTE2", t_value, "battery.2.temperature.2" },
      { "MBIN2", t_ignore, NULL },
      { "BDAT2", t_string, "battery.2.date" },
      { "MBTE3", t_value, "battery.3.temperature" },
      { "MBIN3", t_ignore, NULL },
      { "BDAT3", t_string, "battery.3.date" },
      { "MBTE4", t_value, "battery.4.temperature" },
      { "MBIN4", t_ignore, NULL },
      { "BDAT4", t_string, "battery.4.date" },
      { "MBTE5", t_value, "battery.5.temperature" },
      { "MBIN5", t_ignore, NULL },
      { "BDAT5", t_string, "battery.5.date" },
      { "MBTE6", t_value, "battery.6.temperature" },
      { "MBIN6", t_ignore, NULL },
      { "BDAT6", t_string, "battery.6.date" },
      { "MBTE7", t_value, "battery.7.temperature" },
      { "MBIN7", t_ignore, NULL },
      { "BDAT7", t_string, "battery.7.date" },
      { "MBTE8", t_value, "battery.8.temperature" },
      { "MBIN8", t_ignore, NULL },
      { "BDAT8", t_finstr, "battery.8.date" },
      { NULL }
};


static cmd_t commands[] = {
      { "load.off",                 NULL, NULL },
      { "load.on",                  NULL, NULL },
      { "shutdown.return",          "UPPF", "IJHLDMGCIU" },
      { "shutdown.stayoff",         "UPPD", "LGGNLMDPGV" },
      { "shutdown.stop",            "UPPU", NULL },
      { "shutdown.reboot",          "UPPC", "IJHLDMGCIU" },
      { "shutdown.reboot.graceful", NULL, NULL },
      { "test.panel.start",         "UPIS", NULL },
      { "test.panel.stop",          NULL, NULL },
      { "test.failure.start",       NULL, NULL },
      { "test.failure.stop",        NULL, NULL },
      { "test.battery.start",       "UPBT", "1" },
      { "test.battery.stop",        NULL, NULL },
      { "calibrate.start",          NULL, NULL },
      { "calibrate.stop",           NULL, NULL },
      { "bypass.start",       NULL, NULL },
      { "bypass.stop",        NULL, NULL },
      { "reset.input.minmax",       NULL, NULL },
      { "reset.watchdog",           NULL, NULL },
      { "beeper.enable",            NULL, NULL },
      { "beeper.disable",           NULL, NULL },
      { "beeper.on",                NULL, NULL },
      { "beeper.off",               NULL, NULL },
      { NULL }
};


static cmd_t variables[] = {
      { "ups.delay.reboot",         "UPCD", "ACCD" },
      { "ups.delay.shutdown",       "UPSD", "ACSD" },
      { NULL }
};


static int instcmd (const char *auxcmd, const char *data);
static int setvar (const char *var, const char *data);
static void upsc_setstatus(unsigned int status);
static void upsc_flush_input(void);
static void upsc_getbaseinfo(void);
static int upsc_commandlist(void);
static int upsc_getparams(const char *cmd, const simple_t *table);
static int upsc_getvalue(const char *cmd, const char *param,
      const char *resp, const char *var, char *ret);
static int upscsend(const char *cmd);
static int upscrecv(char *buf);
static int upsc_simple(const simple_t *sp, const char *var, const char *val);
static void check_uppm(void);
static float batt_charge_pct(void);



void upsdrv_help(void)
{
}


void upsdrv_initups(void)
{
      struct termios tio;
      int baud = B1200;
      char *str;

      if ((str = getval("baudrate")) != NULL) {
            int temp = atoi(str);
            switch (temp) {
            case   300:
                  baud =   B300; break;
            case   600:
                  baud =   B600; break;
            case  1200:
                  baud =  B1200; break;
            case  2400:
                  baud =  B2400; break;
            case  4800:
                  baud =  B4800; break;
            case  9600:
                  baud =  B9600; break;
            case 19200:
                  baud = B19200; break;
            case 38400:
                  baud = B38400; break;
            default:
                  fatalx(EXIT_FAILURE, "Unrecognized baudrate: %s", str);
            }
            upsdebugx(1, "baud_rate = %d", temp);
      }
      upsfd = ser_open(device_path);
      ser_set_speed(upsfd, device_path, baud);

      if (tcgetattr(upsfd, &tio) != 0)
            fatal_with_errno(EXIT_FAILURE, "tcgetattr(%s)", device_path);
      tio.c_lflag = ICANON;
      tio.c_iflag |= IGNCR;   /* Ignore CR */
      tio.c_cc[VMIN] = 0;
      tio.c_cc[VTIME] = 0;
      tcsetattr(upsfd, TCSANOW, &tio);

      if ((str = getval("input_timeout")) != NULL) {
            int temp = atoi(str);
            if (temp <= 0)
                  fatalx(EXIT_FAILURE, "Bad input_timeout parameter: %s", str);
            input_timeout_sec = temp;
      }
      upsdebugx(1, "input_timeout = %d Sec", input_timeout_sec);

      if ((str = getval("output_pace")) != NULL) {
            int temp = atoi(str);
            if (temp <= 0)
                  fatalx(EXIT_FAILURE, "Bad output_pace parameter: %s", str);
            output_pace_usec = temp;
      }
      upsdebugx(1, "output_pace = %d uSec", output_pace_usec);

      if ((str = getval("full_update_timer")) != NULL) {
            int temp = atoi(str);
            if (temp <= 0)
                  fatalx(EXIT_FAILURE, "Bad full_update_timer parameter: %s", str);
            full_update_timer = temp;
      }
      upsdebugx(1, "full_update_timer = %d Sec", full_update_timer);

      use_crlf = testvar("use_crlf");
      upsdebugx(1, "use_crlf = %d", use_crlf);
      use_pre_lf = testvar("use_pre_lf");
      upsdebugx(1, "use_pre_lf = %d", use_pre_lf);
}


void upsdrv_initinfo(void)
{
      if (!upsc_commandlist()) {
            upslogx(LOG_ERR, "No contact with UPS, delaying init.");
            status = UPSC_STAT_NOTINIT;
            return;
      }
      else {
            status = 0;
      }

      upsc_getbaseinfo();
      if (can_upda) {
            upsc_flush_input();
            upscsend("UPDA");
      }
      if (can_upid)
            upsc_getvalue("UPID", NULL, "ACID", "ups.id", NULL);
      if (can_uppm)
            check_uppm();

      upsh.instcmd = instcmd;
      upsh.setvar = setvar;
}


/* Change a variable name in a table */
static void change_name(simple_t *sp,
            const char *oldname, const char *newname)
{
      while(sp->code) {
            if (sp->desc && !strcmp(sp->desc, oldname)) {
                  sp->desc = strdup(newname);
                  if (dstate_getinfo(oldname)) {
                        dstate_setinfo(newname, "%s",
                                    dstate_getinfo(oldname));
                  }
                  dstate_delinfo(oldname);
                  upsdebugx(1, "Changing name: %s => %s", oldname, newname);
                  break;
            }
            sp++;
      }
}

static float calc_upsload(void) {

      float       load=-1, nom_out_power=-1, nom_out_realpower=-1, maxcurr, tmp;
      const char  *s;

      /* Some UPSen (Fiskars 9000 for example) only reports current, and
       * only the max current */
      if (nom_out_current > 0) {
            maxcurr = nom_out_current;
      }
      else {
            maxcurr = max_out_current;
      }

      if (maxcurr > 0) {
            if ((s=dstate_getinfo("output.L1.current")) ||
                        (s=dstate_getinfo("output.current"))) {
                  if (sscanf(s, "%f", &tmp) == 1) {
                        load = tmp/maxcurr;
                  }
            }
            if ((s=dstate_getinfo("output.L2.current"))) {
                  if (sscanf(s, "%f", &tmp) == 1) {
                        tmp=tmp/maxcurr;
                        if (tmp>load) {
                              load = tmp;
                        }
                  }
            }
            if ((s=dstate_getinfo("output.L3.current"))) {
                  if (sscanf(s, "%f", &tmp) == 1) {
                        tmp=tmp/maxcurr;
                        if (tmp>load) {
                              load = tmp;
                        }
                  }
            }
      }

      /* This is aggregated (all phases) */
      if ((s=dstate_getinfo("ups.power.nominal"))) {
            if (sscanf(s, "%f", &nom_out_power) != 1) {
                  nom_out_power = -1;
            }
      }

      if (nom_out_power > 0) {
            if ((s=dstate_getinfo("output.L1.power"))) {
                  if (sscanf(s, "%f", &tmp) == 1) {
                        tmp /= (nom_out_power/num_outphases);
                        if (tmp>load) {
                              load = tmp;
                        }
                        dstate_setinfo("output.L1.power.percent",
                                    "%.1f", tmp*100);
                  }
            }
            if ((s=dstate_getinfo("output.L2.power"))) {
                  if (sscanf(s, "%f", &tmp) == 1) {
                        tmp /= (nom_out_power/num_outphases);
                        if (tmp>load) {
                              load = tmp;
                        }
                        dstate_setinfo("output.L2.power.percent",
                                    "%.1f", tmp*100);
                  }
            }
            if ((s=dstate_getinfo("output.L3.power"))) {
                  if (sscanf(s, "%f", &tmp) == 1) {
                        tmp /= (nom_out_power/num_outphases);
                        if (tmp>load) {
                              load = tmp;
                        }
                        dstate_setinfo("output.L3.power.percent",
                                    "%.1f", tmp*100);
                  }
            }
      }

      /* This is aggregated (all phases) */
      if ((s=dstate_getinfo("output.realpower.nominal"))) {
            if (sscanf(s, "%f", &nom_out_realpower) != 1) {
                  nom_out_realpower = -1;
            }
      }
      if (nom_out_realpower >= 0) {
            if ((s=dstate_getinfo("output.L1.realpower"))) {
                  if (sscanf(s, "%f", &tmp) == 1) {
                        tmp /= (nom_out_realpower/num_outphases);
                        if (tmp>load) {
                              load = tmp;
                        }
                        dstate_setinfo("output.L1.realpower.percent",
                                    "%.1f", tmp*100);
                  }
            }
            if ((s=dstate_getinfo("output.L2.realpower"))) {
                  if (sscanf(s, "%f", &tmp) == 1) {
                        tmp /= (nom_out_realpower/num_outphases);
                        if (tmp>load) {
                              load = tmp;
                        }
                        dstate_setinfo("output.L2.realpower.percent",
                                    "%.1f", tmp*100);
                  }
            }
            if ((s=dstate_getinfo("output.L3.realpower"))) {
                  if (sscanf(s, "%f", &tmp) == 1) {
                        tmp /= (nom_out_realpower/num_outphases);
                        if (tmp>load) {
                              load = tmp;
                        }
                        dstate_setinfo("output.L3.realpower.percent",
                                    "%.1f", tmp*100);
                  }
            }
      }

      return load;
}


void upsdrv_updateinfo(void)
{
      time_t now;
      int ok;
      float load;

      if (status & UPSC_STAT_NOTINIT) {
            upsdrv_initinfo();
      }

      if (status & UPSC_STAT_NOTINIT) {
            return;
      }

      status = 0;

      ok = upsc_getparams("UPDS", simple);

      time(&now);
      if (ok && now - last_full > full_update_timer) {
            last_full = now;
            ok = upsc_getparams("UPDV", nominal);
            if (ok && can_upbs)
                  ok = upsc_getparams("UPBS", battery);
      }

      if (!ok) {
            dstate_datastale();
            last_full = 0;
            return;
      }

      if (!inited_phaseinfo) {
            if (dstate_getinfo("input.L3-L1.voltage") ||
                        dstate_getinfo("input.L3-N.voltage")) {
                  num_inphases = 3;

                  change_name(simple,
                        "input.current", "input.L1.current");
                  change_name(simple,
                        "input.realpower", "input.L1.realpower");
                  change_name(simple,
                        "input.power", "input.L1.power");
                  change_name(simple,
                        "input.voltage", "input.L1-N.voltage");
            }
            if (dstate_getinfo("output.L3-L1.voltage") ||
                        dstate_getinfo("output.L3-N.voltage")) {
                  const char *s;

                  num_outphases = 3;

                  if ((s=dstate_getinfo("ups.model")) &&
                              (!strncmp(s, "UPS9075", 7) ||
                              !strncmp(s, "UPS9100", 7) ||
                              !strncmp(s, "UPS9150", 7) ||
                              !strncmp(s, "UPS9200", 7) ||
                              !strncmp(s, "UPS9250", 7) ||
                              !strncmp(s, "UPS9300", 7) ||
                              !strncmp(s, "UPS9400", 7) ||
                              !strncmp(s, "UPS9500", 7) ||
                              !strncmp(s, "UPS9600", 7)) ) {
                        /* Insert kludges for Fiskars UPS9000 here */
                        upslogx(LOG_INFO, "Fiskars UPS9000 detected, protocol kludges activated");
                        batt_volt_nom = 384;
                        dstate_setinfo("battery.voltage.nominal", "%.0f", batt_volt_nom);

                  }
                  else {
                        outpwr_factor *= 3;
                  }

                  change_name(simple,
                        "output.current", "output.L1.current");
                  change_name(simple,
                        "output.peakcurrent", "output.L1.peakcurrent");
                  change_name(simple,
                        "output.realpower", "output.L1.realpower");
                  change_name(simple,
                        "output.power", "output.L1.power");
                  change_name(simple,
                        "output.voltage", "output.L1-N.voltage");
            }

            dstate_setinfo("input.phases", "%d", num_inphases);
            dstate_setinfo("output.phases", "%d", num_outphases);

            inited_phaseinfo=1;
      }

      load = calc_upsload();

      if (load >= 0) {
            upsdebugx(2, "ups.load: %.1f", load*100);
            dstate_setinfo("ups.load", "%.1f", load*100);
      }
      else {
            upsdebugx(2, "ups.load: No value");
      }

      /* TODO/FIXME: Set UPS date/time on startup and daily if needed */
      if (can_updt) {
            char dtbuf[UPSC_BUFLEN];
            if (upsc_getvalue("UPDT", "0", "ACDT", NULL, dtbuf)) {
                  dstate_setinfo("ups.date", "%s", dtbuf);
            }
      }
      if (can_uptm) {
            char tmbuf[UPSC_BUFLEN];
            if (upsc_getvalue("UPTM", "0", "ACTM", NULL, tmbuf)) {
                  dstate_setinfo("ups.time", "%s", tmbuf);
            }
      }


      if (batt_charge < 0) {
            if (batt_current < 0) {
                  /* Reset battery current history if discharging */
                  numbatthist = lastbatthist = 0;
            }
            batt_charge = batt_charge_pct();
      }
      if (batt_charge >= 0) {
            dstate_setinfo("battery.charge", "%.1f", batt_charge);
      }
      else {
            dstate_delinfo("battery.charge");
      }

      /* 9999 == unknown value */
      if (batt_runtime >= 0 && batt_runtime < 9999) {
            dstate_setinfo("battery.runtime", "%.0f", batt_runtime*60);
      }
      else if (load > 0 && batt_disch_curr_max != 0) {
            float est_battcurr = load * abs(batt_disch_curr_max);
            /* Peukert equation */
            float runtime = (batt_cap_nom*3600)/pow(est_battcurr, 1.35);

            upsdebugx(2, "Calculated runtime: %.0f seconds", runtime);
            if (batt_runtime_max > 0 && runtime > batt_runtime_max*60) {
                  runtime = batt_runtime_max*60;
            }
            dstate_setinfo("battery.runtime", "%.0f", runtime);

      }
      else if (batt_runtime_max > 0) {
            /* Show max possible runtime as reported by UPS */
            dstate_setinfo("battery.runtime", "%.0f", batt_runtime_max*60);
      }
      else {
            dstate_delinfo("battery.runtime");
      }
      /* Some UPSen only provides this when on battery, so reset between
       * each iteration to make sure we use the right value */
      batt_charge = -1;
      batt_runtime = -1;


      if (!(status & UPSC_STAT_ONBATT))
            status |= UPSC_STAT_ONLINE;

      upsc_setstatus(status);

      dstate_dataok();
      ser_comm_good();
}


void upsdrv_shutdown(void)
{
      if (upsc_commandlist()) {
            if (!can_upsd || !can_uppc) {
                  fatalx(LOG_EMERG, "Shutdown called, but UPS does not support it");
            }
      } else {
            upslogx(LOG_EMERG, "Can't determine if shutdown is supported, attempting anyway");
      }

      upslogx(LOG_EMERG, "Emergency shutdown");
      upscsend("UPSD"); /* Set shutdown delay */
      upscsend("1");          /* 1 second (lowest possible. 0 returns current.*/

      upslogx(LOG_EMERG, "Shutting down...");
      upscsend("UPPC"); /* Powercycle UPS */
      upscsend("IJHLDMGCIU"); /* security code */
}


static int instcmd (const char *auxcmd, const char *data)
{
      cmd_t *cp = commands;

      if (!strcasecmp(auxcmd, "beeper.off")) {
            /* compatibility mode for old command */
            upslogx(LOG_WARNING,
                  "The 'beeper.off' command has been renamed to 'beeper.disable'");
            return instcmd("beeper.disable", NULL);
      }

      if (!strcasecmp(auxcmd, "beeper.on")) {
            /* compatibility mode for old command */
            upslogx(LOG_WARNING,
                  "The 'beeper.on' command has been renamed to 'beeper.enable'");
            return instcmd("beeper.enable", NULL);
      }

      upsdebugx(1, "Instcmd: %s %s", auxcmd, data ? data : "\"\"");
      while (cp->cmd) {
            if (strcmp(cp->cmd, auxcmd) == 0) {
                  upscsend(cp->upsc);
                  if (cp->upsp)
                        upscsend(cp->upsp);
                  else if (data)
                        upscsend(data);
                  return STAT_INSTCMD_HANDLED;
            }
            cp++;
      }
      upslogx(LOG_INFO, "instcmd: unknown command %s", auxcmd);
      return STAT_INSTCMD_UNKNOWN;
}


static int setvar (const char *var, const char *data)
{
      cmd_t *cp = variables;

      upsdebugx(1, "Setvar: %s %s", var, data);
      while (cp->cmd) {
            if (strcmp(cp->cmd, var) == 0) {
                  upsc_getvalue(cp->upsc, data, cp->upsp, cp->cmd, NULL);
                  return STAT_SET_HANDLED;
            }
            cp++;
      }
      upslogx(LOG_INFO, "Setvar: unsettable variable %s", var);
      return STAT_SET_UNKNOWN;
}


/* list flags and values that you want to receive via -x */
void upsdrv_makevartable(void)
{
      addvar(VAR_VALUE, "manufacturer", "manufacturer [unknown]");
      addvar(VAR_VALUE, "baudrate", "Serial interface baudrate [1200]");
      addvar(VAR_VALUE, "input_timeout", "Command response timeout [2]");
      addvar(VAR_VALUE, "output_pace", "Output character delay in usecs [200]");
      addvar(VAR_VALUE, "full_update", "Delay between full value downloads [60]");
      addvar(VAR_FLAG, "use_crlf", "Use CR-LF to terminate commands to UPS");
      addvar(VAR_FLAG, "use_pre_lf", "Use LF to introduce commands to UPS");
}


void upsdrv_cleanup(void)
{
      ser_close(upsfd, device_path);
}


/*
 * Generate status string from bitfield
 */
static void upsc_setstatus(unsigned int status)
{

      /*
       * I'll look for all available statuses, even though they might not be
       *  supported in the UPScode II protocol.
       */

      status_init();

      if (status & UPSC_STAT_ONLINE)
            status_set("OL");
      if (status & UPSC_STAT_ONBATT)
            status_set("OB");
      if (status & UPSC_STAT_LOBATT)
            status_set("LB");
      if (status & UPSC_STAT_REPLACEBATT)
            status_set("RB");
      if (status & UPSC_STAT_BOOST)
            status_set("BOOST");
      if (status & UPSC_STAT_TRIM)
            status_set("TRIM");
      if (status & UPSC_STAT_OVERLOAD)
            status_set("OVER");
      if (status & UPSC_STAT_CALIBRATION)
            status_set("CAL");
      if (status & UPSC_STAT_OFF)
            status_set("OFF");
      if (status & UPSC_STAT_BYPASS)
            status_set("BYPASS");

      status_commit();
}


/* Add \r to end of command and send to UPS */
/* returns < 0 on errors, 0 on timeout and > 0 on success. */
static int upscsend(const char *cmd)
{
      int   res;

      res = ser_send_pace(upsfd, output_pace_usec, "%s%s%s",
            use_pre_lf ? "\n" : "",
            cmd,
            use_crlf ? "\r\n" : "\r");

      if (res < 0) {
            upsdebug_with_errno(3, "upscsend");
      } else if (res == 0) {
            upsdebugx(3, "upscsend: Timeout");
      } else {
            upsdebugx(3, "upscsend: '%s'", cmd);
      }

      return res;
}


/* Return a string read from UPS */
/* returns < 0 on errors, 0 on timeout and > 0 on success. */
static int upscrecv(char *buf)
{
      int   res;

      /* NOTE: the serial port is set to use Canonical Mode Input Processing,
         which means ser_get_buf() either returns one line terminated with
         ENDCHAR, an error or times out. */

      while (1) {
            res = ser_get_buf(upsfd, buf, UPSC_BUFLEN, input_timeout_sec, 0);
            if (res != 1) {
                  break;
            }

            /* Only one character, must be ENDCHAR */
            upsdebugx(3, "upscrecv: Empty line");
      }

      if (res < 0) {
            upsdebug_with_errno(3, "upscrecv");
      } else if (res == 0) {
            upsdebugx(3, "upscrecv: Timeout");
      } else {
            upsdebugx(3, "upscrecv: %u bytes:\t'%s'", res-1, rtrim(buf, ENDCHAR));
      }

      return res;
}


static void upsc_flush_input(void)
{
/*
      char buf[UPSC_BUFLEN];

      do {
            upscrecv(buf);
            if (strlen(buf) > 0)
                  upsdebugx(1, "Skipping input: %s", buf);
      } while (strlen(buf) > 0);
*/
      ser_flush_in(upsfd, "", nut_debug_level);
}


/* check which commands this ups supports.
 * Returns TRUE if command list was recieved, FALSE otherwise */
static int upsc_commandlist(void)
{
      char buf[UPSC_BUFLEN];
      cmd_t *cp;

      upsc_flush_input();
      upscsend("UPCL");
      while (1) {
            upscrecv(buf);
            if (strlen(buf) == 0) {
                  upslogx(LOG_ERR, "Missing UPCL after UPCL");
                  return 0;
            }
            upsdebugx(2, "Supports command: %s", buf);

            if (strcmp(buf, "UPBS") == 0)
                  can_upbs = 1;
            else if (strcmp(buf, "UPPM") == 0)
                  can_uppm = 1;
            else if (strcmp(buf, "UPID") == 0)
                  can_upid = 1;
            else if (strcmp(buf, "UPDA") == 0)
                  can_upda = 1;
            else if (strcmp(buf, "UPDT") == 0)
                  can_updt = 1;
            else if (strcmp(buf, "UPTM") == 0)
                  can_uptm = 1;
            else if (strcmp(buf, "UPSD") == 0)
                  can_upsd = 1;
            else if (strcmp(buf, "UPPC") == 0)
                  can_uppc = 1;
            cp = commands;
            while (cp->cmd) {
                  if (cp->upsc && strcmp(cp->upsc, buf) == 0) {
                        upsdebugx(1, "instcmd: %s %s", cp->cmd, cp->upsc);
                        dstate_addcmd(cp->cmd);
                        cp->enabled = 1;
                        break;
                  }
                  cp++;
            }
            cp = variables;
            while (cp->cmd) {
                  if (cp->upsc && strcmp(cp->upsc, buf) == 0) {
                        upsdebugx(1, "setvar: %s %s", cp->cmd, cp->upsc);
                        cp->enabled = 1;
                        break;
                  }
                  cp++;
            }

            if (strcmp(buf, "UPCL") == 0)
                  break;
      }

      cp = variables;
      while (cp->cmd) {
            if (cp->enabled) {
                  upsc_getvalue(cp->upsc, "0", cp->upsp, cp->cmd, NULL);
                  dstate_setflags(cp->cmd, ST_FLAG_RW | ST_FLAG_STRING);
                  dstate_setaux(cp->cmd, 7);
            }
            cp++;
      }

      return 1;
}


/* get limits and parameters */
static int upsc_getparams(const char *cmd, const simple_t *table)
{
      char var[UPSC_BUFLEN];
      char val[UPSC_BUFLEN];
      int first = 1;

      upsc_flush_input();

      upscsend(cmd);
      buffer_empty = 0;
      while (!buffer_empty) {
            upscrecv(var);
            if (strlen(var) == 0) {
                  if (first)
                        upscrecv(var);
                  if (strlen(var) == 0) {
                        ser_comm_fail("Empty string from UPS for %s!",
                              cmd);
                        break;
                  }
            }
            first = 0;
            upscrecv(val);
            if (strlen(val) == 0) {
                  ser_comm_fail("Empty value from UPS for %s %s!", cmd, var);
                  break;
            }
            upsdebugx(2, "Parameter %s %s", var, val);
            if (!upsc_simple(table, var, val))
                  upslogx(LOG_ERR, "Unknown response to %s: %s %s",
                        cmd, var, val);
      }
      return buffer_empty;
}


static void check_uppm(void)
{
      int i, last = 0;
      char var[UPSC_BUFLEN];
      char val[UPSC_BUFLEN];

      upsc_flush_input();
      upscsend("UPPM");
      upscsend("0");
      upscrecv(var);
      if (strcmp(var, "ACPM"))
            upslogx(LOG_ERR, "Bad response to UPPM: %s", var);
      while (1) {
            int val, stat;
            upscrecv(var);
            if (strlen(var) == 0)
                  break;
            upsdebugx(2, "UPPM available: %s", var);
            stat = sscanf(var, "P%2d", &val);
            if (stat != 1) {
                  upslogx(LOG_ERR, "Bad response to UPPM: %s", var);
                  return;
            }
            has_uppm_p[val] = 1;
            if (val > last)
                  last = val;
      }

      for (i = 0; i <= last; i++) {
            if (!has_uppm_p[i])
                  continue;
            upscsend("UPPM");
            snprintf(var, sizeof(var), "P%.2d", i);
            upscsend(var);
            upscrecv(val);
            if (strcmp(val, "ACPM")) {
                  upslogx(LOG_ERR, "Bad response to UPPM %s: %s", var, val);
                  continue;
            }
            upscrecv(var);
            upsdebugx(1, "UPPM value: %s", var);
      }
}


static int upsc_getvalue(const char *cmd, const char *param,
                  const char *resp, const char *nutvar, char *ret)
{
      char var[UPSC_BUFLEN];
      char val[UPSC_BUFLEN];

      upsdebugx(2, "Request value: %s %s", cmd, param ? param : "\"\"");
      upscsend(cmd);
      if (param)
            upscsend(param);
      upscrecv(var);
      upscrecv(val);
      upsdebugx(2, "Got value: %s %s", var, val);
      if (strcmp(var, resp)) {
            upslogx(LOG_ERR, "Bad response to %s %s: %s %s",
                  cmd, param ? param : "\"\"", var, val);
            return 0;
      }
      else {
            if (nutvar)
                  dstate_setinfo(nutvar, "%s", val);
            if (ret)
                  strcpy(ret, val);
      }
      return 1;
}


static void upsc_getbaseinfo(void)
{
      char *buf;

      dstate_setinfo("ups.mfr", "%s",
            ((buf = getval("manufacturer")) != NULL) ? buf : "unknown");

      if (!upsc_getvalue("UPTP", NULL, "NNAME", "ups.model", NULL))
            upsc_getvalue("UPTP", NULL, "NNAME", "ups.model", NULL);
      upsc_getvalue("UPSN", "0", "ACSN", "ups.serial", NULL);
}


static int upsc_simple(const simple_t *sp, const char *var, const char *val)
{
      int stat;
      float fval;

      while (sp->code) {
            if (strcmp(sp->code, var) == 0) {
                  switch (sp->type) {
                  case t_setval:
                        stat = sscanf(val, "%f", &fval);
                        if (stat != 1)
                              upslogx(LOG_ERR, "Bad float: %s %s", var, val);
                        if (sp->desc)
                              dstate_setinfo(sp->desc, "%.2f", fval);
                        *sp->aux = fval;
                        break;
                  case t_setrecip:
                        stat = sscanf(val, "%f", &fval);
                        if (stat != 1)
                              upslogx(LOG_ERR, "Bad float: %s %s", var, val);
                        if (sp->desc)
                              dstate_setinfo(sp->desc, "%s", val);
                        *sp->aux = 1/fval;
                        break;
                  case t_setpct:
                        stat = sscanf(val, "%f", &fval);
                        if (stat != 1)
                              upslogx(LOG_ERR, "Bad float: %s %s", var, val);
                        *sp->aux = fval*100;
                        if (sp->desc)
                              dstate_setinfo(sp->desc, "%s", val);
                        break;
                  case t_setrecpct:
                        stat = sscanf(val, "%f", &fval);
                        if (stat != 1)
                              upslogx(LOG_ERR, "Bad float: %s %s", var, val);
                        *sp->aux = 1/fval*100;
                        if (sp->desc)
                              dstate_setinfo(sp->desc, "%s", val);
                        break;
                  case t_final:
                        buffer_empty = 1;
                  case t_value:
                        if (!sp->desc) {
                              break;
                        }
                        if (sscanf(val, "%f", &fval) == 1) {
                              if (sp->aux != NULL) {
                                    fval *= *(sp->aux);
                              }
                              dstate_setinfo(sp->desc, "%.2f", fval);
                        }
                        else {
                              upslogx(LOG_ERR, "Bad float in %s: %s", var, val);
                              dstate_setinfo(sp->desc, "%s", val);
                        }
                        break;
                  case t_finstr:
                        buffer_empty = 1;
                  case t_string:
                        if (!sp->desc) {
                              break;
                        }
                        dstate_setinfo(sp->desc, "%s", val);
                        break;
                  case t_status:
                        if (strcmp(val, "00") == 0)
                              ;
                        else if (strcmp(val, "11") == 0)
                              status |= sp->status;
                        else
                              upslogx(LOG_ERR, "Unknown status value: '%s' '%s'", var, val);
                        break;
                  case t_alarm:
                        if (strcmp(val, "00") == 0)
                              ;
                        else if (strcmp(val, "11") == 0)
                              status |= sp->status;
                        else
                              upslogx(LOG_ERR, "Unknown alarm value: '%s' '%s'", var, val);
                        break;
                  case t_ignore:
                        upsdebugx(3, "Ignored value: %s %s", var, val);
                        break;
                  case t_list:
                        if (!upsc_simple(sp->stats, val, "11"))
                              upslogx(LOG_ERR, "Unknown value: %s %s",
                                    var, val);
                        break;
                  default:
                        upslogx(LOG_ERR, "Unknown type for %s", var);
                        break;
                  }
                  return 1;
            }
            sp++;
      }
      return 0;
}


static float batt_charge_pct(void)
{
      float chg=-1;

      /* This is only a rough estimate of charge status while charging.
       * When on battery something like Peukert's equation should be used */
      if (batt_current >= 0) {
            float maxcurr = 10;     /* Assume 10A max as default */
            float avgcurr = 0;
            int i;

            batthist[lastbatthist] = batt_current;
            lastbatthist = (lastbatthist+1) % NUM_BATTHIST;
            if (numbatthist < NUM_BATTHIST) {
                  numbatthist++;
            }
            for(i=0; i<numbatthist; i++) {
                  avgcurr += batthist[i];
            }
            avgcurr /= numbatthist;

            if (batt_cap_nom > 0) {
                  /* Estimate max charge current based on battery size */
                  maxcurr = batt_cap_nom * 0.3;
            }
            chg = maxcurr - avgcurr;
            chg *= (100/maxcurr);
      }
      /* Old method, assumes battery high/low-voltages provided by UPS are
       * applicable to battery charge, but they usually aren't */
      else if (batt_volt_low > 0 && batt_volt_high > 0 && batt_volt > 0) {
            if (batt_volt > batt_volt_high) {
                  chg=100;
            }
            else if (batt_volt < batt_volt_low) {
                  chg=0;
            }
            else {
                  chg = (batt_volt - batt_volt_low) /
                        (batt_volt_high - batt_volt_low);
                  chg*=100;
            }
      }
      else {
            return -1;
      }

      if (chg < 0) {
            chg = 0;
      }
      else if (chg > 100) {
            chg = 100;
      }

      return chg;
}

/*
vim:noet:ts=8:sw=8:cindent
*/

Generated by  Doxygen 1.6.0   Back to index