Logo Search packages:      
Sourcecode: nut version File versions

cyberpower.c

/* cyberpower.c - Network UPS Tools driver for Cyber Power Systems units

   Copyright (C) 2001  Russell Kroll <rkroll@exploits.org>
   Copyright (C) 2002  Len White     <lwhite@darkfires.net>

   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
*/

#include "main.h"
#include "serial.h"

#include <sys/ioctl.h>

#include "timehead.h"

#define DRV_VERSION "1.00"

/* window for repeating dangerous command (shutdown.stayoff) */
#define MINCMDTIME      3
#define MAXCMDTIME      15

#define UPSDELAY 50000

/* ups frequency */
static float frequency(unsigned char in)
{
      float freq[22] = { 63.0, 62.7, 62.4, 62.1, 61.8, 61.4, 61.1, 60.8, 
            60.5, 60.2, 60.0, 59.7, 59.4, 59.1, 58.8, 58.5, 58.3, 58.0,
            57.7, 57.4, 57.2, 57.0 };
      int i, j;

      for (i = 0, j = 168; i < 23; j++, i++)
            if (in == j)
                  return (float)freq[i];

      return (float)60;
}

/* adjust bizarre UPS data to observed voltage data */
static int voltconvert(unsigned char in)
{
      int v_end[43] = { 36, 51, 55, 60, 65, 70, 75, 80, 85, 91, 98, 103, 
            108, 113, 118, 123, 128, 133, 138, 143, 148, 153, 158, 163, 
            168, 173, 178, 183, 188, 193, 198, 203, 208, 213, 218, 223, 
            228, 233, 238, 243, 248, 253, 255 };
      int v_adj[43] = {  3,  4,  5,  4,  3,  2,  1,  0, -1, -2, -3,  -4,  
            -5,  -6,  -7,  -8,  -9, -10, -11, -12, -13, -14, -15, -16, 
            -17, -18, -19, -20, -21, -22, -23, -24, -25, -26, -27, -28, 
            -29, -30, -31, -32, -33, -34, -35 };
      int   i;

      if (in < 27)
            return 0;

      for (i = 0; i < 19; i++) { 
            if (in <= v_end[i]) {
                  return (in + v_adj[i]);
            }
      }

      return 0;
}

/* map UPS data to realistic percentages */
static int battconvert(unsigned char in)
{
      /* these may only be valid for a load of 0 */
      int b_val[26] = {0, 1, 1, 2, 3, 4, 6, 8, 10, 12, 15, 18, 22, 26, 30, 
            35, 40, 46, 52, 58, 66, 73, 81, 88, 99, 100 };

      if (in > 185)
            return 100;

      if (in < 160)
            return 0;

      return (b_val[in - 160]);
}

/* Model mapping */
struct {
      int   first;
      int   second;
      const char  *pcode;           /* product code - presently ignored */
      const char  *model;
}     modelmap[] = {

      { 51, 51, "OP850", "850AVR"   },    /* O33 */
      { 52, 53, "OP1500", "1500AVR" },    /* O45 */
      { 52, 51, "OP1250", "1250AVR" },    /* O43 */
      { 52, 49, "OP700", "700AVR"   },    /* O41 */
      { 51, 57, "OP650", "650AVR"   },    /* O39 */
      { 51, 55, "OP900", "900AVR"   },    /* O37 */
      { 51, 49, "OP800", "800AVR"   },    /* O31 */
      { 50, 57, "OP500", "500AVR"   },    /* O29 */
      { 50, 55, "OP320", "320AVR"   },    /* O27 */
      {  0,  0, (char*) NULL, (char *) NULL }
};

/* more wacky mapping - get the picture yet? */

struct {
      int   st;
      int   end;
      int   sz;
      int   base;
}     temptab[] =
{
      {   0,  39, 5,  0 },
      {  40,  43, 4,  8 }, 
      {  44,  78, 5,  9 },
      {  79,  82, 4, 16 },
      {  83, 117, 5, 17 },
      { 118, 121, 4, 24 },
      { 122, 133, 3, 25 },
      { 134, 135, 2, 29 },
      { 136, 143, 4, 30 },
      { 144, 146, 3, 32 },
      { 147, 150, 4, 33 },
      { 151, 156, 3, 34 },
      { 157, 164, 2, 36 },
      { 165, 170, 3, 40 },
      { 171, 172, 2, 42 },
      { 173, 175, 3, 43 },
      { 176, 183, 2, 44 },
      { 184, 184, 1, 48 },
      { 185, 188, 2, 49 },
      { 189, 190, 2, 51 },
      { 191, 191, 1, 52 },
      { 192, 193, 2, 53 },
      { 194, 194, 1, 54 },
      { 195, 196, 2, 55 },
      { 197, 197, 1, 56 },
      { 198, 199, 2, 57 },
      { 200, 200, 1, 58 },
      { 201, 202, 2, 59 },
      { 203, 203, 1, 60 },
      { 204, 205, 2, 61 },
      { 206, 206, 1, 62 },
      { 207, 208, 2, 63 },
      { 209, 209, 1, 64 },
      { 210, 211, 2, 65 },
      { 212, 212, 1, 66 },
      { 213, 213, 1, 67 },
      { 214, 214, 1, 68 },
      { 215, 215, 1, 69 },
      { 216, 255, 40, 70 },
      {   0,   0, 0,  0 },
};

static float tempconvert(unsigned char in)
{
      int   i, j, found, count;

      found = -1;
      for (i = 0; temptab[i].sz != 0; i++)
            if ((temptab[i].st <= in) && (temptab[i].end >= in))
                  found = i;

      if (found == -1) {
            upslogx(LOG_ERR, "tempconvert: unhandled value %d", in);
            return 0;
      }

      count = temptab[found].end - temptab[found].st + 1;

      for (i = 0; i < count; i++) {
            j = temptab[found].st + (i * temptab[found].sz);

            if ((in - j) < temptab[found].sz) {
                  return ((float)((in - j) / temptab[found].sz) + 
                        temptab[found].base + i);
            }
      }

      upslogx(LOG_ERR, "tempconvert: fell through with %d", in);
      return 0;
}

static int confirm_write(const unsigned char *buf, size_t buflen)
{
      int   ret;
      char  verify[16];
      unsigned int      i;

      ret = ser_send_buf_pace(upsfd, UPSDELAY, buf, buflen);

      if (ret < 1) {
            upsdebugx(1, "confirm_write: ser_send_buf_pace failed");
            return 0;
      }

      /* don't try to read back the \r */
      ret = ser_get_buf_len(upsfd, verify, buflen - 1, 5, 0);

      if ((ret < 1) || (ret < ((int) buflen - 1))) {
            upsdebugx(1, "confirm_write: ret=%d, needed %d",
                  ret, buflen - 1);
            return 0;
      }

      for (i = 0; i < buflen - 1; i++) {
            if (buf[i] != verify[i]) {
                  upsdebugx(1, "mismatch at position %d",   i);
                  return 0;
            }
      }

      /* made it this far, so it must have heard us */

      return 1;   /* success */
}

/* provide a quick status check to select the right shutdown command */
static int ups_on_line(void)
{
      int   ret;
      char  buf[SMALLBUF];

      ser_send_pace(upsfd, UPSDELAY, "D\r");

      /* give it a chance to reply completely */
      usleep(100000);

      ret = ser_get_buf_len(upsfd, buf, 14, 3, 0);

      if (ret < 14) {
            upslogx(LOG_ERR, "Status read failed: assuming on battery");
            return 0;
      }

      if (buf[9] & 128)
            return 0;   /* on battery */
      
      return 1;   /* on line */
}

/* power down the attached load immediately */
void upsdrv_shutdown(void)
{
      int   i, ret, sdlen;
      char  buf[256];
      unsigned char     sdbuf[16];

      /* get this thing's attention */
      for (i = 0; i < 10; i++) {
            printf("Trying to wake up the ups... ");
            fflush(stdout);

            ser_send_char(upsfd, 13);
            ret = ser_get_buf_len(upsfd, buf, 1, 5, 0);

            if (ret > 0) {
                  printf("OK\n");
                  break;
            }

            printf("failed\n");
      }

      usleep(250000);

      memset(sdbuf, '\0', sizeof(sdbuf));

      if (ups_on_line() == 1) {

            /* this does not come back when on battery! */

            printf("Online: sending 7 byte command (back in about 45 sec)\n");

            sdlen = 8;
            sdbuf[0] = 'S';

            sdbuf[1] = 0x00;        /* how long until shutdown */
            sdbuf[2] = 0x00;        /* 0 = nearly immediate */

            sdbuf[3] = 'R';
            sdbuf[4] = 0x00;        /* how long to stay off */
            sdbuf[5] = 0x01;        /* 0 = about 13 seconds */
                                    /* 1 = about 45 seconds */

            sdbuf[6] = 'W';
            sdbuf[7] = '\r';
      
      } else {

            /* this one is one-way when on-line, but is the only
             * known way to come back online when on battery ... 
             */

            printf("On battery: sending 4 byte command (back when line power returns)\n");

            sdlen = 5;
            sdbuf[0] = 'S';
            sdbuf[1] = 0x00;
            sdbuf[2] = 0x00;
            sdbuf[3] = 'W';
            sdbuf[4] = '\r';
      }

      for (i = 0; i < 10; i++) {
            printf("Sending command...");
            fflush(stdout);

            if (confirm_write(sdbuf, sdlen)) {
                  printf(" confirmed\n");
                  break;
            }

            printf("failed, retrying...\n");
            fflush(stdout);
      }
}

void upsdrv_updateinfo(void)
{
      int   ret;
      char  buf[SMALLBUF];

      ser_send_pace(upsfd, UPSDELAY, "D\r");

      /* give it a chance to reply completely */
      usleep(100000);

      ret = ser_get_buf_len(upsfd, buf, 14, 3, 0);

      if (ret < 14) {
            ser_comm_fail("Short read from UPS");
            dstate_datastale();
            return;
      }

      if (buf[0] != '#') {
            ser_comm_fail("Invalid start char 0x%02x", buf[0] & 0xff);
            dstate_datastale();
            return;
      }

      if ((buf[4] != 46) || (buf[8] != 46)) {
            ser_comm_fail("Invalid separator in response (0x%02x, 0x%02x)", 
                  buf[4], buf[8]);
            dstate_datastale();
            return;
      }

      ser_comm_good();

      dstate_setinfo("input.frequency", "%2.1f", frequency(buf[7]));
      dstate_setinfo("ups.temperature", "%2.1f", tempconvert(buf[6]));
      dstate_setinfo("battery.charge", "%03d", battconvert(buf[5]));
      dstate_setinfo("ups.load", "%03d", (buf[3] & 0xff) * 2);
      dstate_setinfo("input.voltage", "%03d", voltconvert(buf[1]));

      status_init();

      if (buf[9] & 2)
            status_set("OFF");

      if (buf[9] & 64)
            status_set("LB");

      if (buf[9] & 128)
            status_set("OB");
      else
            status_set("OL");

      status_commit();
      dstate_dataok();
}

static int get_ident(char *buf, size_t bufsize)
{
      int   ret, tries;

      for (tries = 0; tries < 3; tries++) {
            ret = ser_send_pace(upsfd, UPSDELAY, "F\r");

            if (ret != 2)
                  continue;

            /* give it a chance to reply completely */
            usleep(400000);

            ret = ser_get_line(upsfd, buf, bufsize, '\r', "", 3, 0);

            if (ret < 1)
                  continue;

            if (buf[0] == '.')
                  return 1;

            /* if we got here, then the read failed somehow */
      }

      upslogx(LOG_INFO, "Giving up on hardware detection after 3 tries");

      return 0;
}

static int detect_hardware(void)
{
      int   i, foundmodel = 0;
      char  buf[SMALLBUF];

      if (!get_ident(buf, sizeof(buf)))
            fatalx("Unable to get initial hardware info string");

      if (buf[0] != '.') {
            upslogx(LOG_ERR, "Invalid start model string 0x%02x", 
                  buf[0] & 0xff);
            return -1;
      }

      for (i = 0; modelmap[i].model != NULL; i++) {
            if (buf[3] == modelmap[i].first &&
                  buf[4] == modelmap[i].second) {

                  dstate_setinfo("ups.model", "%s", modelmap[i].model);
                  foundmodel = 1;
                  break;
            }
      }

      if (!foundmodel)
            dstate_setinfo("ups.model", "Unknown model - %c%c", 
                  buf[3], buf[4]);

      dstate_setinfo("ups.firmware", "%c.%c%c%c", 
            buf[16], buf[17], buf[18], buf[19]);
      return 0;
}

/* FUTURE: allow variable length tests (additional data from instcmd) */
static int instcmd_btest(void)
{
      int   i, clen;
      unsigned char     cbuf[8];

      clen = 2;
      cbuf[0] = 'T';
      cbuf[1] = 0x80;         /* try for a brief test: about 20 seconds */

      for (i = 0; i < 3; i++)
            if (confirm_write(cbuf, clen))
                  return STAT_INSTCMD_HANDLED;  /* FUTURE: success */

      upslogx(LOG_WARNING, "btest: UPS failed to confirm command");

      return STAT_INSTCMD_HANDLED;        /* FUTURE: failed */
}

static int instcmd_stayoff(void)
{
      int   i, clen;
      unsigned char     cbuf[8];
      double      elapsed;
      time_t      now;
      static      time_t      last = 0;

      time(&now);

      elapsed = difftime(now, last);
      last = now;

      /* for safety this must be repeated in a small window of time */
      if ((elapsed < MINCMDTIME) || (elapsed > MAXCMDTIME)) {
            upsdebugx(1, "instcmd_shutdown: outside window (%2.0f)",
                  elapsed);
            return STAT_INSTCMD_HANDLED;        /* FUTURE: again */
      }

      /* this is a one-way trip unless you happen to be on battery */

      clen = 5;
      cbuf[0] = 'S';
      cbuf[1] = 0x00;
      cbuf[2] = 0x00;         
      cbuf[3] = 'W';
      cbuf[4] = '\r';

      for (i = 0; i < 3; i++)
            if (confirm_write(cbuf, clen))
                  return STAT_INSTCMD_HANDLED;  /* FUTURE: success */

      upslogx(LOG_WARNING, "instcmd_shutdown: UPS failed to confirm command");

      return STAT_INSTCMD_HANDLED;        /* FUTURE: failed */
}     

static int instcmd(const char *cmdname, const char *extra)
{
      if (!strcasecmp(cmdname, "test.battery.start"))
            return instcmd_btest();
      if (!strcasecmp(cmdname, "shutdown.stayoff"))
            return instcmd_stayoff();

      upslogx(LOG_NOTICE, "instcmd: unknown command [%s]", cmdname);
      return STAT_INSTCMD_UNKNOWN;
}

/* install pointers to functions for msg handlers called from msgparse */
static void setuphandlers(void)
{
      upsh.instcmd = instcmd;
}

void upsdrv_banner(void)
{
      printf("Network UPS Tools - CyberPower driver %s (%s)\n", 
            DRV_VERSION, UPS_VERSION);
}

void upsdrv_help(void)
{
}

void upsdrv_makevartable(void)
{
}

/* prep the serial port */
void upsdrv_initups(void)
{
      int dtr_bit = TIOCM_DTR;
      int rts_bit = TIOCM_RTS;

      upsfd = ser_open(device_path);
      ser_set_speed(upsfd, device_path, B1200);

      /* dtr high, rts high */
      ioctl(upsfd, TIOCMBIS, &rts_bit);
      ioctl(upsfd, TIOCMBIS, &dtr_bit);
}

void upsdrv_initinfo(void)
{
      if (detect_hardware() == -1) {
            printf("Unable to detect a CyberPower UPS on port %s\n", 
                  device_path);
            printf("Check the cabling, port name or model name and try again\n");
            exit(EXIT_FAILURE);
      }

      dstate_setinfo("ups.mfr", "CyberPower");
      dstate_setinfo("driver.version.internal", "%s", DRV_VERSION);

      /* poll once to put in some good data */
      upsdrv_updateinfo();

      printf("Detected %s on %s\n", dstate_getinfo("ups.model"), device_path); 

      dstate_addcmd("test.battery.start");
      dstate_addcmd("shutdown.stayoff");

      setuphandlers(); 
}

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

Generated by  Doxygen 1.6.0   Back to index