1
/* cyberpower.c - Network UPS Tools driver for Cyber Power Systems units
3
Copyright (C) 2001 Russell Kroll <rkroll@exploits.org>
4
Copyright (C) 2002 Len White <lwhite@darkfires.net>
6
This program is free software; you can redistribute it and/or modify
7
it under the terms of the GNU General Public License as published by
8
the Free Software Foundation; either version 2 of the License, or
9
(at your option) any later version.
11
This program is distributed in the hope that it will be useful,
12
but WITHOUT ANY WARRANTY; without even the implied warranty of
13
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
GNU General Public License for more details.
16
You should have received a copy of the GNU General Public License
17
along with this program; if not, write to the Free Software
18
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23
#include <sys/ioctl.h>
24
#include <sys/termios.h>
28
#define DRV_VERSION "0.22"
30
/* window for repeating dangerous command (shutdown.stayoff) */
34
/* limit the amount of spew that goes in the syslog when we lose the UPS */
35
#define CP_ERR_LIMIT 10 /* start limiting after 10 in a row */
36
#define CP_ERR_RATE 100 /* then only print every 100th error */
40
unsigned int poll_failures = 0;
43
float frequency(unsigned char in)
45
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 };
48
for (i = 0, j = 168; i < 23; j++, i++)
50
return (float)freq[i];
55
/* adjust bizarre UPS data to observed voltage data */
56
int voltconvert(unsigned char in)
58
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 };
59
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 };
65
for (i = 0; i < 19; i++) {
67
return (in + v_adj[i]);
74
/* map UPS data to realistic percentages */
75
int battconvert(unsigned char in)
77
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 }; /* XXX - for load of 0 */
85
return (b_val[in - 160]);
96
{ 51, 51, "OP850", "850AVR" }, /* O33 */
97
{ 52, 53, "OP1500", "1500AVR" }, /* O45 */
98
{ 52, 51, "OP1250", "1250AVR" }, /* O43 */
99
{ 52, 49, "OP700", "700AVR" }, /* O41 */
100
{ 51, 57, "OP650", "650AVR" }, /* O39 */
101
{ 51, 55, "OP900", "900AVR" }, /* O37 */
102
{ 51, 49, "OP800", "800AVR" }, /* O31 */
103
{ 50, 57, "OP500", "500AVR" }, /* O29 */
104
{ 50, 55, "OP320", "320AVR" }, /* O27 */
105
{ 0, 0, (char*) NULL, (char *) NULL }
108
/* more wacky mapping - get the picture yet? */
155
{ 216, 255, 40, 70 },
159
float tempconvert(unsigned char in)
161
int i, j, found, count;
164
for (i = 0; temptab[i].sz != 0; i++)
165
if ((temptab[i].st <= in) && (temptab[i].end >= in))
169
upslogx(LOG_ERR, "tempconvert: unhandled value %d", in);
173
count = temptab[found].end - temptab[found].st + 1;
175
for (i = 0; i < count; i++) {
176
j = temptab[found].st + (i * temptab[found].sz);
178
if ((in - j) < temptab[found].sz) {
179
return ((float)((in - j) / temptab[found].sz) +
180
temptab[found].base + i);
184
upslogx(LOG_ERR, "tempconvert: fell through with %d", in);
188
void sendtoups(char ch)
190
write(upsfd, &ch, 1);
194
static void read_timeout(int sig)
200
static int confirm_write(const unsigned char *buf, int buflen)
205
for (i = 0; i < buflen; i++) {
206
ret = write(upsfd, &buf[i], 1);
215
write(upsfd, &ch, 1);
217
for (i = 0; i < buflen; i++) {
220
ret = read(upsfd, &ch, 1);
224
upsdebugx(1, "mismatch at position %d", i);
229
/* made it this far, so it must have heard us */
231
return 1; /* success */
234
/* provide a quick status check to select the right shutdown command */
235
static int ups_on_line(void)
238
char buf[SMALLBUF], ch;
245
memset(buf, '\0', sizeof(buf));
249
ret = read(upsfd, &ch, 1);
253
upslogx(LOG_ERR, "Status read failed: assuming on battery");
261
return 0; /* on battery */
263
return 1; /* on line */
266
static void setup_sigalrm(void)
268
sigemptyset(&cp_sigmask);
269
sa.sa_mask = cp_sigmask;
271
sa.sa_handler = read_timeout;
272
sigaction(SIGALRM, &sa, NULL);
275
/* power down the attached load immediately */
276
void upsdrv_shutdown(void)
280
unsigned char sdbuf[16];
284
/* get this thing's attention */
285
for (i = 0; i < 10; i++) {
286
printf("Trying to wake up the ups... ");
292
write(upsfd, &ch, 1);
294
ret = read(upsfd, buf, sizeof(buf));
307
if (ups_on_line() == 1) {
309
/* this does not come back when on battery! */
311
printf("Online: sending 7 byte command (back in about 45 sec)\n");
316
sdbuf[1] = 0x00; /* how long until shutdown */
317
sdbuf[2] = 0x00; /* 0 = nearly immediate */
320
sdbuf[4] = 0x00; /* how long to stay off */
321
sdbuf[5] = 0x01; /* 0 = about 13 seconds */
322
/* 1 = about 45 seconds */
328
/* this one is one-way when on-line, but is the only
329
* known way to come back online when on battery ...
332
printf("On battery: sending 4 byte command (back when line power returns)\n");
341
printf("Sending command...");
344
for (i = 0; i < 10; i++) {
346
if (confirm_write(sdbuf, sdlen)) {
347
printf(" confirmed\n");
351
printf("failed, retrying...");
356
static void pollfail(const char *why)
360
if ((poll_failures == CP_ERR_LIMIT) ||
361
((poll_failures % CP_ERR_RATE) == 0)) {
362
upslogx(LOG_WARNING, "Warning: excessive poll failures, "
363
"limiting error reporting");
366
if ((poll_failures < CP_ERR_LIMIT) ||
367
((poll_failures % CP_ERR_RATE) == 0)) {
368
upslogx(LOG_ERR, "UPS status unavailable: %s", why);
372
void upsdrv_updateinfo(void)
375
char ch, buf[SMALLBUF];
382
memset(buf, '\0', sizeof(buf));
386
ret = read(upsfd, &ch, 1);
390
pollfail("Short read from UPS");
399
upslogx(LOG_ERR, "Invalid start char 0x%02x", buf[0] & 0xff);
404
if ((buf[4] != 46) || (buf[8] != 46)) {
405
upslogx(LOG_ERR, "Invalid separator in response");
412
dstate_setinfo("input.frequency", "%2.1f", frequency(buf[7]));
413
dstate_setinfo("ups.temperature", "%2.1f", tempconvert(buf[6]));
414
dstate_setinfo("battery.charge", "%03d", battconvert(buf[5]));
415
dstate_setinfo("ups.load", "%03d", (buf[3] & 0xff) * 2);
416
dstate_setinfo("input.voltage", "%03d", voltconvert(buf[1]));
435
static int get_ident(char *buf, size_t bufsize)
437
int ret, tries, count;
440
for (tries = 0; tries < 3; tries++) {
444
memset(buf, '\0', bufsize);
447
ret = read(upsfd, &ch, 1);
454
if ((buf[0] == '.') && (ch == 13))
457
/* reading way too much */
458
if (count == bufsize) {
459
memset(buf, '\0', bufsize);
464
ret = read(upsfd, &ch, 1);
468
/* if we got here, then the read failed somehow */
471
upslogx(LOG_INFO, "Giving up on hardware detection after 3 tries");
476
static int detect_hardware(void)
478
int i, foundmodel = 0;
481
if (!get_ident(buf, sizeof(buf)))
482
fatalx("Unable to get initial hardware info string");
485
upslogx(LOG_ERR, "Invalid start model string 0x%02x", buf[0] & 0xff);
489
for (i = 0; modelmap[i].model != NULL; i++) {
490
if (buf[3] == modelmap[i].first &&
491
buf[4] == modelmap[i].second) {
493
dstate_setinfo("ups.model", "%s", modelmap[i].model);
500
dstate_setinfo("ups.model", "Unknown model - %c%c",
503
dstate_setinfo("ups.firmware", "%c.%c%c%c",
504
buf[16], buf[17], buf[18], buf[19]);
508
/* FUTURE: allow variable length tests (additional data from instcmd) */
509
static int instcmd_btest(void)
516
cbuf[1] = 0x80; /* try for a brief test: about 20 seconds */
518
for (i = 0; i < 3; i++)
519
if (confirm_write(cbuf, clen))
520
return STAT_INSTCMD_HANDLED; /* FUTURE: success */
522
upslogx(LOG_WARNING, "btest: UPS failed to confirm command");
524
return STAT_INSTCMD_HANDLED; /* FUTURE: failed */
527
static int instcmd_stayoff(void)
533
static time_t last = 0;
537
elapsed = difftime(now, last);
540
/* for safety this must be repeated in a small window of time */
541
if ((elapsed < MINCMDTIME) || (elapsed > MAXCMDTIME)) {
542
upsdebugx(1, "instcmd_shutdown: outside window (%2.0f)",
544
return STAT_INSTCMD_HANDLED; /* FUTURE: again */
547
/* this is a one-way trip unless you happen to be on battery */
555
for (i = 0; i < 3; i++)
556
if (confirm_write(cbuf, clen))
557
return STAT_INSTCMD_HANDLED; /* FUTURE: success */
559
upslogx(LOG_WARNING, "instcmd_shutdown: UPS failed to confirm command");
561
return STAT_INSTCMD_HANDLED; /* FUTURE: failed */
564
int instcmd(const char *cmdname, const char *extra)
566
if (!strcasecmp(cmdname, "test.battery.start"))
567
return instcmd_btest();
568
if (!strcasecmp(cmdname, "shutdown.stayoff"))
569
return instcmd_stayoff();
571
upslogx(LOG_NOTICE, "instcmd: unknown command [%s]", cmdname);
572
return STAT_INSTCMD_UNKNOWN;
575
/* install pointers to functions for msg handlers called from msgparse */
576
static void setuphandlers(void)
578
upsh.new_instcmd = instcmd;
581
void upsdrv_banner(void)
583
printf("Network UPS Tools - CyberPower driver %s (%s)\n",
584
DRV_VERSION, UPS_VERSION);
586
experimental_driver = 1;
589
void upsdrv_help(void)
593
void upsdrv_makevartable(void)
597
/* prep the serial port */
598
void upsdrv_initups(void)
600
int dtr_bit = TIOCM_DTR;
601
int rts_bit = TIOCM_RTS;
604
open_serial(device_path, B1200);
606
/* dtr high, rts high */
607
ioctl(upsfd, TIOCMBIS, &rts_bit);
608
ioctl(upsfd, TIOCMBIS, &dtr_bit);
610
tcgetattr(upsfd, &tio);
611
tio.c_cflag = 0 | CS8 | CLOCAL | ~CRTSCTS | CREAD;
613
tio.c_cflag &= ~(PARODD|CSTOPB|HUPCL|CRTSCTS);
615
cfsetispeed(&tio, B1200);
616
cfsetospeed(&tio, B1200);
617
tcsetattr(upsfd, TCSANOW, &tio);
619
/* avoid getting stuck in read() */
623
void upsdrv_initinfo(void)
625
if (detect_hardware() == -1) {
626
printf("Unable to detect a CyberPower UPS on port %s\n",
628
printf("Check the cabling, port name or model name and try again\n");
632
dstate_setinfo("ups.mfr", "CyberPower", 0, 0);
633
dstate_setinfo("driver.version.internal", "%s", DRV_VERSION);
635
/* poll once to put in some good data */
638
printf("Detected %s on %s\n", dstate_getinfo("ups.model"), device_path);
640
dstate_addcmd("test.battery.start");
641
dstate_addcmd("shutdown.stayoff");
646
void upsdrv_cleanup(void)