Logo Search packages:      
Sourcecode: nut version File versions

energizerups.c

/* energizerups.c - model specific routines for Energizer USB units

   Copyright (C) 2003  Viktor T. Toth <vttoth@vttoth.com>

   Based in part on the fentonups.c driver by Russell Kroll:

   Copyright (C) 1999  Russell Kroll <rkroll@exploits.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
*/

#include <asm/types.h>
#include <sys/ioctl.h>

#ifndef HID_MAX_USAGES
#define HID_MAX_USAGES 1024   /* horrible workaround hack */
#endif

#include <linux/hiddev.h>
#include <fcntl.h>
#include <stdio.h>
#include <signal.h>
#include <sys/select.h>

#include "main.h"

#define DRV_VERSION     "0.01"

#define NUM_EVTS 64

/* Response to I identification queries is in the following format:
 *
 * 012345678901234567890123456789012345678
 * #Energizer       ER-HMOF600 A0        ^M
 * #Energizer       ER-OF800   A0        ^M
 */
#define MANUFR    (buf+1)
#define MDLNUM    (buf+17)
#define HWVERS    (buf+28)

/* Response to Q1 queries is in the following format:
 *
 * 01234567890123456789012345678901234567890123456
 * (118.6 118.6 118.6 020 60.0 13.8 32.0 00001000^M
 */
#define INVOLT    (buf+1)
#define OUTVOLT   (buf+13)
#define LOADPCT   (buf+19)
#define ACFREQ    (buf+23)
#define BATVOLT   (buf+28)
#define UPSTEMP   (buf+33)
#define STATUS    (buf+38)

#define OBFLAG    (STATUS[0]=='1'||STATUS[5]=='1')
#define LBFLAG    (STATUS[1]=='1')
#define BYPASS    (STATUS[2]=='1')
#define BEEPER    (STATUS[7]=='1')

/* A calibration test of the ER-HMOF600 shows its maximum voltage at
 * 14.0V during charging, falling down to 13.6 if the UPS is turned off
 * for three minutes (but connected to line power) and to 12.8V
 * immediately after starting a test with a 60W load. The 'low battery'
 * warning came at 11.0V, and a discharge time to LB with a 60W light
 * bulb took 35 minutes. The UPS ran another 5 minutes on LB before
 * shutting down, at which time the battery voltage was 10.1V. When
 * charging resumed at the moment the LB warning came on, the battery
 * started at 12.0V.
 *
 * With my ER-OF800, the maximum voltage is 13.8V. The difference is
 * probably within circuit tolerance and not due to a substantial design
 * difference between the units.
 *
 * For now, these are the only two units I have. Both are 115VAC units
 * with a 12V internal battery and an identical software interface. The
 * characteristic AC voltage values here are reasonable guesses based on
 * the values of other similar UPSs.
 */

#define LOWVOLT   (OBFLAG?11.0:12.0)
#define VOLTRNG   1.8
#define LOXFER    84
#define LONORM    98
#define HINORM    126
#define HIXFER    142

#define hidsend(x) hidcmd(x,NULL,0)


/*
 * Encode and send a string to the UPS. A trailing <cr> (0x0D) is appended.
 *
 * Commands are encoded in 8-byte reports, each consisting of 64 settable
 * bits. The last report is padded with null bits, just to be safe and not
 * send the UPS garbage by accident.
 */

void sendstring(int fd, char *psz)
{
      struct hiddev_report_info rinfo;
      struct hiddev_usage_ref uref;
      int i, j;
      unsigned char c;

      uref.usage_index = 0;
      i = 0;

      do
      {
            c = (*psz) ? *psz :0x0D;
            for (j = 0; j < 8; j++)
            {
                  uref.report_type = HID_REPORT_TYPE_OUTPUT;
                  uref.report_id = 0;
                  uref.field_index = 0;
                  uref.usage_code = 0x90001 + uref.usage_index;
                  uref.value = (c & 1);
                  if (ioctl(fd, HIDIOCSUSAGE, &uref) < 0)
                        fatalx("Error (SUSAGE) while talking to the UPS");
                  uref.usage_index++;
                  c >>= 1;
            }
            if (*psz == 0 || ++i == 8)
            {
                  i = 0;
                  while (uref.usage_index++ < 63)
                  {
                        uref.report_type = HID_REPORT_TYPE_OUTPUT;
                        uref.report_id = 0;
                        uref.field_index = 0;
                        uref.usage_code = 0x90001 + uref.usage_index;
                        uref.value = 0;
                        if (ioctl(fd, HIDIOCSUSAGE, &uref) < 0)
                              fatalx("Error (SUSAGE) while talking to the UPS");
                  }
                  uref.usage_index = 0;

                  rinfo.report_type = HID_REPORT_TYPE_OUTPUT;
                  rinfo.report_id = 0;
                  rinfo.num_fields = 1;
                  if (ioctl(fd, HIDIOCSREPORT, &rinfo) < 0)
                        fatalx("Error (SREPORT) while talking to the UPS");
            }
      } while (*psz++);
}

/*
 * Send a command to the UPS and (optionally) receive a reply. The parameter
 * pRsp can be zero, in which case no reply is expected.
 *
 * A new file descriptor is opened as experience has shown that a previously
 * opened descriptor cannot be reused reliably for commanding.
 *
 * Data is received in the form of an 8-byte buffer. After the initial report
 * has been read, HID events are received indicating bit changes in the 8-byte
 * buffer. When the bit counter rolls over, we know we have received a full
 * buffer. This doesn't seem to be a 100% reliable method, since it'd not tell
 * us, for instance, if the same 8 bytes are sent twice in a row, but in
 * practice, it does appear to reliably transmit all UPS response messages.
 */

int hidcmd(unsigned char *pCmd, unsigned char *pRsp, int l)
{
      unsigned int i;
      int fd, rd, j, k, hid;
      fd_set rdfs;
      struct timeval tv;
      struct hiddev_event ev[NUM_EVTS];
      struct hiddev_usage_ref uref;
      unsigned char data[8];

      if ((fd = open(device_path, O_RDWR)) < 0)
            fatalx("Cannot communicate with UPS at %s", device_path);

      FD_ZERO(&rdfs);
      FD_SET(fd, &rdfs);

      memset(data, 0, sizeof(data));
      for (i = 0; i < 64; i++)
      {
            uref.report_type = HID_REPORT_TYPE_INPUT;
            uref.report_id = 0;
            uref.field_index = 0;
            uref.usage_index = i;
            ioctl(fd, HIDIOCGUCODE, &uref);
            ioctl(fd, HIDIOCGUSAGE, &uref);

            if (uref.value) data[i >> 3] |= 1 << (i & 7);
      }

      sendstring(fd, pCmd);

      k = 0;

      if (pRsp != NULL)
      {
            hid = -1;
            while (l > 0)
            {
                  tv.tv_sec = 0;
                  tv.tv_usec = 500000;
                  if (select(fd+1, &rdfs, 0, 0, &tv) <= 0) break;
                  rd = read(fd, ev, sizeof(ev));
                  if (rd < (int) sizeof(ev[0]))
                        fatalx("Communication failure with UPS");

                  for (i = 0; i < rd / sizeof(ev[0]); i++)
                  {
                        if (hid >= 0 && (int) ev[i].hid <= hid)
                        {
                              for (j = 0; j < 8; j++)
                              {
                                    if (k < l - 1)
                                    {
                                          if (k > 0 || data[j] != 0xFF) pRsp[k++] = data[j];
                                    }
                                    else break;
                              }
                        }
                        j = (ev[i].hid - 1) & 0x3F;
                        if (ev[i].value) data[j >> 3] |= 1 << (j & 7);
                        else data[j >> 3] &= ~(1 << (j & 7));
                        hid = ev[i].hid;
                  }
            }
            if (hid >= 0) for (j = 0; j < ((hid - 1) & 0x3F) >> 3; j++)
            {
                  if (k < l - 1)
                  {
                        if (k > 0 || data[j] != 0xFF) pRsp[k++] = data[j];
                  }
                  else break;
            }
            pRsp[k] = '\0';
      }
      close(fd);
      return k;
}


int instcmd(const char *cmdname, const char *extra)
{
      if (!strcasecmp(cmdname, "test.battery.start"))
      {
            hidsend("TL");
            upslogx(LOG_NOTICE, "UPS test start");
            return STAT_INSTCMD_HANDLED;
      }

      if (!strcasecmp(cmdname, "test.battery.stop"))
      {
            hidsend("CT");
            upslogx(LOG_NOTICE, "UPS test stop");
            return STAT_INSTCMD_HANDLED;
      }

      if (!strcasecmp(cmdname, "beeper.on") ||
            !strcasecmp(cmdname, "beeper.off"))
      {
            char buf[128];
            int r;

            /* Find out beeper status and send appropriate command. */

            r = hidcmd("Q1", buf, sizeof(buf));
            if (r < 46 || r > 47 || buf[0] != '(')
                  r = hidcmd("Q1", buf, sizeof(buf));
            if (r >= 46 && r <= 47 && buf[0] == '(')
            {
                  if (OBFLAG && !LBFLAG)  /* Otherwise there's not much we can do */
                  {
                        if ((BEEPER && !strcasecmp(cmdname, "beeper.off")) ||
                              (!BEEPER && !strcasecmp(cmdname, "beeper.on")))
                                    hidsend("Q");
                        return STAT_INSTCMD_HANDLED;
                  }
            }
            return STAT_INSTCMD_HANDLED;  /* FUTURE: failure */
      }

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

/*
 * Some trivial initialization. Let's detect the UPS.
 */

void upsdrv_initinfo(void)
{
      char buf[128];
      int i, r;

      /* Yes, sometimes it took as many as 6-7 tries after the UPS has just
       * been turned on or after a communications problem.
       */
      for (i = 0; i < 10; i++)
      {
            r = hidcmd("I", buf, sizeof(buf));
            if (r == 39 && buf[0] == '#') break;
            sleep(3);
      }
      if (r != 39 || buf[0] != '#') fatalx("No Energizer UPS detected");
      buf[16]=buf[27]=buf[38] = '\0';
      rtrim(MANUFR, ' ');
      rtrim(MDLNUM, ' ');
      rtrim(HWVERS, ' ');

      dstate_setinfo("driver.version.internal", "%s", DRV_VERSION);
      dstate_setinfo("ups.mfr", "%s", MANUFR);
      dstate_setinfo("ups.model", "%s", MDLNUM);

      dstate_setinfo("input.transfer.low", "%d", LOXFER);
      dstate_setinfo("input.transfer.high", "%d", HIXFER);

      hidsend("C");

      /* now add instant command support info */
      dstate_addcmd("test.battery.start");
      dstate_addcmd("test.battery.stop");
      dstate_addcmd("beeper.on");
      dstate_addcmd("beeper.off");
      upsh.instcmd = instcmd;
}

/*
 * Query the UPS and update values.
 */

void upsdrv_updateinfo(void)
{
      char buf[128];
      int r;
      static int f;     /* To prevent excessive logging */
      double v;


      r = hidcmd("Q1", buf, sizeof(buf));
      if (r < 46 || r > 47)
      {
            if (f++ < 3)
                  upslogx(LOG_ERR, "Invalid response length from UPS [%s]", buf);
            else
                  dstate_datastale();
            return;
      }
      if (buf[0] != '(')
      {
            if (f++ < 3)
                  upslogx(LOG_ERR, "Invalid response data from UPS [%s]", buf);
            else
                  dstate_datastale();
            return;
      }
      f = 0;

      buf[6]=buf[12]=buf[18]=buf[22]=buf[27]=buf[32]=buf[37]=buf[46] = '\0';

      dstate_setinfo("input.voltage", "%s", INVOLT);
      dstate_setinfo("output.voltage", "%s", OUTVOLT);
      dstate_setinfo("battery.voltage", "%s", BATVOLT);

      v = ((atof(BATVOLT) - LOWVOLT) / VOLTRNG) * 100.0;
      if (v > 100.0) v = 100.0;
      if (v < 0.0) v = 0.0;
      dstate_setinfo("battery.charge", "%02.1f", v);

      status_init();
      if (OBFLAG) status_set("OB");                   /* on battery */
      else
      {
            status_set("OL");                               /* on line */
            /* only allow these when OL since they're bogus when OB */
            if (BYPASS)                                           /* boost or trim in effect */
            {
                  if (atoi(INVOLT) < LONORM) status_set("BOOST");
                  else if (atoi(INVOLT) > HINORM) status_set("TRIM");
            }
      }
      if (LBFLAG) status_set("LB");       /* low battery */

      status_commit();

      dstate_setinfo("ups.temperature", "%s", UPSTEMP);
      dstate_setinfo("input.frequency", "%s", ACFREQ);
      dstate_setinfo("ups.load", "%s", LOADPCT);
      dstate_dataok();
}

void upsdrv_shutdown(void)
{
      char buf[128];
      int r;
      int b = 1;

      /* Basic idea: find out line status and send appropriate command.
       * If the status query fails, we do not retry more than once (we
       * may be short on time with a dying UPS) but assume that we're
       * on battery.
       */

      r = hidcmd("Q1", buf, sizeof(buf));
      if (r < 46 || r > 47 || buf[0] != '(') r = hidcmd("Q1", buf, sizeof(buf));

      if (r >= 46 && r <= 47 && buf[0] == '(')
      {
            if (OBFLAG)
                  upslogx(LOG_WARNING, "On battery, sending shutdown command...");
            else
            {
                  b = 0;
                  upslogx(LOG_WARNING, "On line, sending shutdown+return command...");
            }
      }
      else upslogx(LOG_WARNING, "Status undetermined, assuming battery power, "
                        "sending shutdown command...");

      hidsend(b ? "S01" : "S01R0003");
}


void upsdrv_help(void)
{
}

/* list flags and values that you want to receive via -x */
void upsdrv_makevartable(void)
{
}

void upsdrv_banner(void)
{
      printf("Network UPS Tools - Energizer USB UPS driver %s (%s)\n\n", 
            DRV_VERSION, UPS_VERSION);
      experimental_driver = 1;      /* Causes a warning to be printed */
}

void upsdrv_initups(void)
{
      /* No initialization needed */
}

void upsdrv_cleanup(void)
{
      /* No cleanup needed */
}

Generated by  Doxygen 1.6.0   Back to index