Logo Search packages:      
Sourcecode: nut version File versions

powerp-txt.c

/*
 * powerp-txt.c - Model specific routines for CyberPower text
 *                protocol UPSes 
 *
 * Copyright (C)
 *    2007        Doug Reynolds <mav@wastegate.net>
 *    2007-2008   Arjen de Korte <adkorte-guest@alioth.debian.org>
 *
 * 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
 */

/*
 * Throughout this driver, READ and WRITE comments are shown. These are
 * the typical commands to and replies from the UPS that was used for
 * decoding the protocol (with a serial logger).
 */

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

#include "powerp-txt.h"

typedef struct {
      float       i_volt;
      float       o_volt;
      int         o_load;
      int         b_chrg;
      int         u_temp;
      float       i_freq;
      unsigned char     flags[2];
} status_t;

static int  ondelay = 1;      /* minutes */
static int  offdelay = 60;    /* seconds */

static char powpan_answer[SMALLBUF];

static const struct {
      char  *var;
      char  *get;
      char  *set;
} vartab[] = {
      { "input.transfer.high", "P6\r", "C2:%03d\r" },
      { "input.transfer.low", "P7\r", "C3:%03d\r" },
      { "battery.charge.low", "P8\r", "C4:%02d\r" },
      { NULL }
};

static const struct {
      char  *cmd;
      char  *command;
} cmdtab[] = {
      { "test.battery.start.quick", "T\r" },
      { "test.battery.stop", "CT\r" },
      { "beeper.enable", "C7:1\r" },
      { "beeper.disable", "C7:0\r" },
      { "beeper.on", NULL },
      { "beeper.off", NULL },
      { "shutdown.stop", "C\r" },
      { NULL }
};

static int powpan_command(const char *command)
{
      int   ret;

      ser_flush_io(upsfd);

      ret = ser_send_pace(upsfd, UPSDELAY, "%s", command);

      if (ret < 0) {
            upsdebug_with_errno(3, "send");
            return -1;
      }

      if (ret == 0) {
            upsdebug_with_errno(3, "send: timeout");
            return -1;
      }

      upsdebug_hex(3, "send", command, strlen(command));

      usleep(100000);

      ret = ser_get_line(upsfd, powpan_answer, sizeof(powpan_answer),
            ENDCHAR, IGNCHAR, SER_WAIT_SEC, SER_WAIT_USEC);

      if (ret < 0) {
            upsdebug_with_errno(3, "read");
            upsdebug_hex(4, "  \\_", powpan_answer, strlen(powpan_answer));
            return -1;
      }

      if (ret == 0) {
            upsdebugx(3, "read: timeout");
            upsdebug_hex(4, "  \\_", powpan_answer, strlen(powpan_answer));
            return -1;
      }

      upsdebug_hex(3, "read", powpan_answer, ret);
      return ret;
}

static int powpan_instcmd(const char *cmdname, const char *extra)
{
      int   i;
      char  command[SMALLBUF];

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

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

      for (i = 0; cmdtab[i].cmd != NULL; i++) {

            if (strcasecmp(cmdname, cmdtab[i].cmd)) {
                  continue;
            }

            if ((powpan_command(cmdtab[i].command) == 2) && (!strcasecmp(powpan_answer, "#0"))) { 
                  return STAT_INSTCMD_HANDLED;
            }

            upslogx(LOG_ERR, "%s: command [%s] failed", __func__, cmdname);
            return STAT_INSTCMD_FAILED;
      }

      if (!strcasecmp(cmdname, "shutdown.return")) {
            if (offdelay < 60) {
                  snprintf(command, sizeof(command), "Z.%d\r", offdelay / 6);
            } else {
                  snprintf(command, sizeof(command), "Z%02d\r", offdelay / 60);
            }
      } else if (!strcasecmp(cmdname, "shutdown.stayoff")) {
            if (offdelay < 60) {
                  snprintf(command, sizeof(command), "S.%d\r", offdelay / 6);
            } else {
                  snprintf(command, sizeof(command), "S%02d\r", offdelay / 60);
            }
      } else if (!strcasecmp(cmdname, "shutdown.reboot")) {
            if (offdelay < 60) {
                  snprintf(command, sizeof(command), "S.%dR%04d\r", offdelay / 6, ondelay);
            } else {
                  snprintf(command, sizeof(command), "S%02dR%04d\r", offdelay / 60, ondelay);
            }
      } else {
            upslogx(LOG_NOTICE, "%s: command [%s] unknown", __func__, cmdname);
            return STAT_INSTCMD_UNKNOWN;
      }

      if ((powpan_command(command) == 2) && (!strcasecmp(powpan_answer, "#0"))) {
            return STAT_INSTCMD_HANDLED;
      }

      upslogx(LOG_ERR, "%s: command [%s] failed", __func__, cmdname);
      return STAT_INSTCMD_FAILED;
}

static int powpan_setvar(const char *varname, const char *val)
{
      char  command[SMALLBUF];
      int   i;

      for (i = 0;  vartab[i].var != NULL; i++) {

            if (strcasecmp(varname, vartab[i].var)) {
                  continue;
            }

            if (!strcasecmp(val, dstate_getinfo(varname))) {
                  upslogx(LOG_INFO, "%s: [%s] no change for variable [%s]", __func__, val, varname);
                  return STAT_SET_HANDLED;
            }

            snprintf(command, sizeof(command), vartab[i].set, atoi(val));

            if ((powpan_command(command) == 2) && (!strcasecmp(powpan_answer, "#0"))) {
                  dstate_setinfo(varname, "%s", val);
                  return STAT_SET_HANDLED;
            }

            upslogx(LOG_ERR, "%s: setting variable [%s] to [%s] failed", __func__, varname, val);
            return STAT_SET_UNKNOWN;
      }

      upslogx(LOG_ERR, "%s: variable [%s] not found", __func__, varname);
      return STAT_SET_UNKNOWN;
}

static void powpan_initinfo()
{
      int   i;
      char  *s;

      dstate_setinfo("ups.delay.start", "%d", 60 * ondelay);
      dstate_setinfo("ups.delay.shutdown", "%d", offdelay);

      /*
       * NOTE: The reply is already in the buffer, since the P4\r command
       * was used for autodetection of the UPS. No need to do it again.
       */
      if ((s = strtok(&powpan_answer[1], ",")) != NULL) {
            dstate_setinfo("ups.model", "%s", rtrim(s, ' '));
      }
      if ((s = strtok(NULL, ",")) != NULL) {
            dstate_setinfo("ups.firmware", "%s", s);
      }
      if ((s = strtok(NULL, ",")) != NULL) {
            dstate_setinfo("ups.serial", "%s", s);
      }
      if ((s = strtok(NULL, ",")) != NULL) {
            dstate_setinfo("ups.mfr", "%s", rtrim(s, ' '));
      }

      /*
       * WRITE P3\r
       * READ #12.0,002,008.0,00\r
       */
      if (powpan_command("P3\r") > 0) {

            if ((s = strtok(&powpan_answer[1], ",")) != NULL) {
                  dstate_setinfo("battery.voltage.nominal", "%g", strtod(s, NULL));
            }
            if ((s = strtok(NULL, ",")) != NULL) {
                  dstate_setinfo("battery.packs", "%li", strtol(s, NULL, 10));
            }
            if ((s = strtok(NULL, ",")) != NULL) {
                  dstate_setinfo("battery.capacity", "%g", strtod(s, NULL));
            }
      }

      /*
       * WRITE P2\r
       * READ #1200,0720,120,47,63\r
       */
      if (powpan_command("P2\r") > 0) {

            if ((s = strtok(&powpan_answer[1], ",")) != NULL) {
                  dstate_setinfo("ups.power.nominal", "%li", strtol(s, NULL, 10));
            }
            if ((s = strtok(NULL, ",")) != NULL) {
                  dstate_setinfo("ups.realpower.nominal", "%li", strtol(s, NULL, 10));
            }
            if ((s = strtok(NULL, ",")) != NULL) {
                  dstate_setinfo("input.voltage.nominal", "%li", strtol(s, NULL, 10));
            }
            if ((s = strtok(NULL, ",")) != NULL) {
                  dstate_setinfo("input.frequency.low", "%li", strtol(s, NULL, 10));
            }
            if ((s = strtok(NULL, ",")) != NULL) {
                  dstate_setinfo("input.frequency.high", "%li", strtol(s, NULL, 10));
            }
      }

      /*
       * WRITE P1\r
       * READ #120,138,088,20\r
       */
      if (powpan_command("P1\r") > 0) {

            if ((s = strtok(&powpan_answer[1], ",")) != NULL) {
                  dstate_setinfo("input.voltage.nominal", "%li", strtol(s, NULL, 10));
            }
            if ((s = strtok(NULL, ",")) != NULL) {
                  dstate_setinfo("input.transfer.high", "%li", strtol(s, NULL, 10));
            }
            if ((s = strtok(NULL, ",")) != NULL) {
                  dstate_setinfo("input.transfer.low", "%li", strtol(s, NULL, 10));
            }
            if ((s = strtok(NULL, ",")) != NULL) {
                  dstate_setinfo("battery.charge.low", "%li", strtol(s, NULL, 10));
            }
      }

      for (i = 0; cmdtab[i].cmd != NULL; i++) {
            dstate_addcmd(cmdtab[i].cmd);
      }

      for (i = 0; vartab[i].var != NULL; i++) {

            if (!dstate_getinfo(vartab[i].var)) {
                  continue;
            }

            if (powpan_command(vartab[i].get) < 1) {
                  continue;
            }

            if ((s = strtok(&powpan_answer[1], ",")) != NULL) {
                  dstate_setflags(vartab[i].var, ST_FLAG_RW);
                  dstate_addenum(vartab[i].var, "%li", strtol(s, NULL, 10));
            }

            while ((s = strtok(NULL, ",")) != NULL) {
                  dstate_addenum(vartab[i].var, "%li", strtol(s, NULL, 10));
            }
      }

      /*
       * WRITE P5\r
       * READ #<unknown>\r
       */
      if (powpan_command("P5\r") > 0) {
            /*
             * Looking at the format of the commands "P<n>\r" it seems likely
             * that this command exists also. Let's see if someone cares to
             * tell us if it does (should be visible when running with -DDDDD).
             */
      }

      /*
       * WRITE P9\r
       * READ #<unknown>\r
       */
      if (powpan_command("P9\r") > 0) {
            /*
             * Looking at the format of the commands "P<n>\r" it seems likely
             * that this command exists also. Let's see if someone cares to
             * tell us if it does (should be visible when running with -DDDDD).
             */
      }

      /*
       * Cancel pending shutdown.
       * WRITE C\r
       * READ #0\r
      */
      powpan_command("C\r");

      dstate_addcmd("shutdown.return");
      dstate_addcmd("shutdown.stayoff");
      dstate_addcmd("shutdown.reboot");
}

static int powpan_status(status_t *status)
{
      int   ret;

      ser_flush_io(upsfd);

      /*
       * WRITE D\r
       * READ #I119.0O119.0L000B100T027F060.0S..\r
       *      01234567890123456789012345678901234
       *      0         1         2         3
       */
      ret = ser_send_pace(upsfd, UPSDELAY, "D\r");

      if (ret < 0) {
            upsdebug_with_errno(3, "send");
            return -1;
      }

      if (ret == 0) {
            upsdebug_with_errno(3, "send: timeout");
            return -1;
      }

      upsdebug_hex(3, "send", "D\r", 2);

      usleep(200000);

      ret = ser_get_buf_len(upsfd, powpan_answer, 35, SER_WAIT_SEC, SER_WAIT_USEC);

      if (ret < 0) {
            upsdebug_with_errno(3, "read");
            upsdebug_hex(4, "  \\_", powpan_answer, 35);
            return -1;
      }

      if (ret == 0) {
            upsdebugx(3, "read: timeout");
            upsdebug_hex(4, "  \\_", powpan_answer, 35);
            return -1;
      }

      upsdebug_hex(3, "read", powpan_answer, ret);

      ret = sscanf(powpan_answer, "#I%fO%fL%dB%dT%dF%fS%2c\r",
            &status->i_volt, &status->o_volt, &status->o_load,
            &status->b_chrg, &status->u_temp, &status->i_freq,
            status->flags);

      if (ret < 7) {
            upsdebugx(4, "Parsing status string failed");
            return -1;
      }

      return 0;
}

static int powpan_updateinfo()
{
      status_t    status;

      if (powpan_status(&status)) {
            return -1;
      }

      dstate_setinfo("input.voltage", "%.1f", status.i_volt);
      dstate_setinfo("output.voltage", "%.1f", status.o_volt);
      dstate_setinfo("ups.load", "%d", status.o_load);
      dstate_setinfo("input.frequency", "%.1f", status.i_freq);
      dstate_setinfo("ups.temperature", "%d", status.u_temp);
      dstate_setinfo("battery.charge", "%d", status.b_chrg);

      status_init();

      if (status.flags[0] & 0x40) {
            status_set("OB");
      } else {
            status_set("OL");
      }

      if (status.flags[0] & 0x20) {
            status_set("LB");
      }

      /* !OB && !TEST */
      if (!(status.flags[0] & 0x48)) {

            if (status.o_volt < 0.5 * status.i_volt) {
                  upsdebugx(2, "%s: output voltage too low", __func__);
            } else if (status.o_volt < 0.95 * status.i_volt) {
                  status_set("TRIM");
            } else if (status.o_volt < 1.05 * status.i_volt) {
                  /* ignore */
            } else if (status.o_volt < 1.5 * status.i_volt) {
                  status_set("BOOST"); 
            } else {
                  upsdebugx(2, "%s: output voltage too high", __func__);
            }
      }

      if (status.flags[0] & 0x08) {
            status_set("TEST");
      }

      if (status.flags[0] == 0) {
            status_set("OFF");
      }

      status_commit();

      return (status.flags[0] & 0x40) ? 1 : 0;
}

static int powpan_initups()
{
      int   ret, i;

      upsdebugx(1, "Trying text protocol...");

      ser_set_speed(upsfd, device_path, B2400);

      /* This fails for many devices, so don't bother to complain */
      powpan_command("\r\r");

      for (i = 0; i < MAXTRIES; i++) {

            const char  *val;

            /*
             * WRITE P4\r
             * READ #BC1200     ,1.600,000000000000,CYBER POWER    
             *      01234567890123456789012345678901234567890123456
             *      0         1         2         3         4
             */
            ret = powpan_command("P4\r");

            if (ret < 1) {
                  continue;
            }
            
            if (ret < 46) {
                  upsdebugx(2, "Expected 46 bytes, but only got %d", ret);
                  continue;
            }

            if (powpan_answer[0] != '#') {
                  upsdebugx(2, "Expected start character '#', but got '%c'", powpan_answer[0]);
                  continue;
            }

            val = getval("ondelay");
            if (val) {
                  ondelay = strtol(val, NULL, 10);
            }

            if ((ondelay < 0) || (ondelay > 9999)) {
                  fatalx(EXIT_FAILURE, "Start delay '%d' out of range [0..9999]", ondelay);
            }

            val = getval("offdelay");
            if (val) {
                  offdelay = strtol(val, NULL, 10);
            }

            if ((offdelay < 6) || (offdelay > 600)) {
                  fatalx(EXIT_FAILURE, "Shutdown delay '%d' out of range [6..600]", offdelay);
            }

            /* Truncate to nearest setable value */
            if (offdelay < 60) {
                  offdelay -= (offdelay % 6);
            } else {
                  offdelay -= (offdelay % 60);
            }

            return ret;
      }

      return -1;
}

subdriver_t powpan_text = {
      "text",
      powpan_instcmd,
      powpan_setvar,
      powpan_initups,
      powpan_initinfo,
      powpan_updateinfo
};

Generated by  Doxygen 1.6.0   Back to index