77
#define DRV_VERSION "0.02"
60
/* Known UPS Commands:
62
* :N%02X -- delay the UPS for provided time (hex seconds)
63
* :H%06X -- reboot the UPS. UPS will restart after provided time (hex s)
64
* :A -- begins a self-test
65
* :C -- fetches result of a self-test
66
* :K1 -- turns on power receptacles
67
* :K0 -- turns off power receptacles
68
* :G -- unconfirmed: shuts down UPS until power returns
69
* :Q1 -- enable "Remote Reboot"
70
* :Q0 -- disable "Remote Reboot"
71
* :W -- returns 'W' data
72
* :L -- returns 'L' data
73
* :V -- returns 'V' data (firmware revision)
74
* :X -- returns 'X' data (firmware revision)
75
* :D -- returns general status data
76
* :B -- returns battery voltage (hexadecimal decivolts)
77
* :I -- returns minimum input voltage (hexadecimal hertz)
78
* :M -- returns maximum input voltage (hexadecimal hertz)
79
* :P -- returns power rating
84
* :Y -- returns mains frequency (':D' is preferred)
85
* :T -- returns ups temperature (':D' is preferred)
86
* :R -- returns input voltage (':D' is preferred)
87
* :F -- returns load percentage (':D' is preferred)
88
* :S -- enables remote reboot/remote power on
91
/* Returned value from ':D' looks like:
93
* 0123456789abcdef01234
94
* ABCCDEFFGGGGHHIIIIJJJ
98
* D 0=normal 1=TRIM 2=BOOST 3="EXTRA BOOST"
99
* E 0=OFF 1=OB 2=OL 3=OB (1 and 3 are the same?)
101
* GG ? INFO_BATTPCT (00 when OB, values don't match table we use)
104
* JJJJ ? (e.g., 5B82 5B82 5982 037B 0082)
105
* KKK INFO_ACFREQ * 10
108
#define DRV_VERSION "0.8"
80
113
#include <ctype.h>
82
#define ENDCHAR '\n' /* replies end with CR LF -- use LF to end */
83
#define IGNCHARS "\r" /* ignore CR */
87
static char *w_value, *l_value, *v_value, *x_value;
88
static int poll_failures = 0;
90
static int hex2d(char *start, int len)
94
strncpy(buf, start, len);
96
return strtol(buf, NULL, 16);
99
int instcmd(const char *cmdname, const char *extra)
115
#define ENDCHAR '\n' /* replies end with CR LF -- use LF to end */
116
#define IGNCHAR '\r' /* ignore CR */
118
#define SER_WAIT_SEC 3 /* allow 3.0 sec for ser_get calls */
119
#define SER_WAIT_USEC 0
120
#define DEFAULT_OFFDELAY 64 /* seconds (max 0xFF) */
121
#define DEFAULT_STARTDELAY 60 /* seconds (max 0xFFFFFF) */
122
#define DEFAULT_BOOTDELAY 64 /* seconds (max 0xFF) */
123
#define MAX_VOLT 13.4 /* Max battery voltage (100%) */
124
#define MIN_VOLT 11.0 /* Min battery voltage (10%) */
126
/* We calculate battery charge (q) as a function of voltage (V).
127
* It seems that this function probably varies by firmware revision or
128
* UPS model - the Windows monitoring software gives different q for a
129
* given V than the old open source Tripp Lite monitoring software.
131
* The discharge curve should be the same for any given battery chemistry,
132
* so it should only be necessary to specify the minimum and maximum
133
* voltages likely to be seen in operation.
136
/* Interval notation for Q% = 10% <= [minV, maxV] <= 100% */
137
static float V_interval[2] = {MIN_VOLT, MAX_VOLT};
139
/* Time in seconds to delay before shutting down. */
140
static unsigned int offdelay = DEFAULT_OFFDELAY;
141
static unsigned int startdelay = DEFAULT_STARTDELAY;
142
static unsigned int bootdelay = DEFAULT_BOOTDELAY;
144
static int hex2d(char *start, unsigned int len)
149
strncpy(buf, start, (len < (sizeof buf) ? len : (sizeof buf - 1)));
150
return strtol(buf, NULL, 16);
153
/* The UPS that I'm using (SMART700SER) has the bizarre characteristic
154
* of innately echoing back commands. Therefore, we cannot use
155
* ser_get_line and must manually discard our echoed command.
157
* All UPS commands are challenge-response, so this function makes things
160
* return: # of chars in buf, excluding terminating \0 */
161
static int send_cmd(const char *str, char *buf, size_t len)
167
ser_send(upsfd, str);
173
ret = ser_get_char(upsfd, &c, SER_WAIT_SEC, SER_WAIT_USEC);
180
ret = ser_get_char(upsfd, &c, SER_WAIT_SEC, SER_WAIT_USEC);
184
if (c == IGNCHAR || c == ENDCHAR)
187
} while (c != ENDCHAR && i < len);
189
ser_flush_in(upsfd, NULL, 0);
193
static void ups_sync(void)
198
for (tries = 0; tries < MAXTRIES; ++tries) {
199
ret = send_cmd(":W\r", buf, sizeof buf);
200
if ((ret > 0) && isdigit((unsigned char)buf[0]))
203
fatalx("\nFailed to find UPS - giving up...");
206
static int do_reboot_now(void)
208
char buf[256], cmd[16];
210
snprintf(cmd, sizeof cmd, ":H%06X\r", startdelay);
211
return send_cmd(cmd, buf, sizeof buf);
214
static void do_reboot(void)
216
char buf[256], cmd[16];
218
snprintf(cmd, sizeof cmd, ":N%02X\r", bootdelay);
219
send_cmd(cmd, buf, sizeof buf);
223
static int soft_shutdown(void)
225
char buf[256], cmd[16];
227
snprintf(cmd, sizeof cmd, ":N%02X\r", offdelay);
228
send_cmd(cmd, buf, sizeof buf);
229
return send_cmd(":G\r", buf, sizeof buf);
232
static int hard_shutdown(void)
234
char buf[256], cmd[16];
236
snprintf(cmd, sizeof cmd, ":N%02X\r", offdelay);
237
send_cmd(cmd, buf, sizeof buf);
238
return send_cmd(":K0\r", buf, sizeof buf);
241
static int instcmd(const char *cmdname, const char *extra)
103
245
if (!strcasecmp(cmdname, "test.battery.start")) {
104
upssend("%s", ":A\r"); /* Start self test */
106
upsrecv(buf, sizeof(buf), ENDCHAR, IGNCHARS);
246
send_cmd(":A\r", buf, sizeof buf);
247
return STAT_INSTCMD_HANDLED;
249
if (!strcasecmp(cmdname, "load.off")) {
250
send_cmd(":K0\r", buf, sizeof buf);
251
return STAT_INSTCMD_HANDLED;
253
if (!strcasecmp(cmdname, "load.on")) {
254
send_cmd(":K1\r", buf, sizeof buf);
255
return STAT_INSTCMD_HANDLED;
257
if (!strcasecmp(cmdname, "shutdown.reboot")) {
259
return STAT_INSTCMD_HANDLED;
261
if (!strcasecmp(cmdname, "shutdown.reboot.graceful")) {
263
return STAT_INSTCMD_HANDLED;
265
if (!strcasecmp(cmdname, "shutdown.return")) {
267
return STAT_INSTCMD_HANDLED;
269
if (!strcasecmp(cmdname, "shutdown.stayoff")) {
108
271
return STAT_INSTCMD_HANDLED;
112
275
return STAT_INSTCMD_UNKNOWN;
278
static int setvar(const char *varname, const char *val)
280
if (!strcasecmp(varname, "ups.delay.shutdown")) {
281
offdelay = atoi(val);
282
dstate_setinfo("ups.delay.shutdown", val);
283
return STAT_SET_HANDLED;
285
if (!strcasecmp(varname, "ups.delay.start")) {
286
startdelay = atoi(val);
287
dstate_setinfo("ups.delay.start", val);
288
return STAT_SET_HANDLED;
290
if (!strcasecmp(varname, "ups.delay.reboot")) {
291
bootdelay = atoi(val);
292
dstate_setinfo("ups.delay.reboot", val);
293
return STAT_SET_HANDLED;
295
return STAT_SET_UNKNOWN;
115
298
void upsdrv_initinfo(void)
121
dstate_addcmd("test.battery.start"); /* Turns off automatically */
123
dstate_setinfo("ups.mfr", "%s", "Tripp Lite");
125
w = hex2d(w_value, 2);
126
l = hex2d(l_value, 2);
128
if (w & 0x40) model = "Unison %d";
129
else model = "Smart %d";
131
if (w & 0x80) { /* New VA rating formula */
132
va = ((w & 0x3f) * 32 + (l >> 3)) * 5;
133
} else { /* Old VA rating formula */
137
dstate_setinfo("ups.model", model, va);
138
dstate_setinfo("ups.firmware", "%c%c",
139
'A'+v_value[0]-'0', 'A'+v_value[1]-'0');
141
/* we need more details on what these really are */
143
setinfo(INFO_FIRMREV1, "%s", v_value);
144
setinfo(INFO_FIRMREV2, "%s", x_value);
145
setinfo(INFO_REG1, "%s", w_value);
146
setinfo(INFO_REG2, "%s", l_value);
149
upsh.new_instcmd = instcmd; /* Set here instead of upsdrv_initups */
151
printf("Detected %s %s on %s\n",
152
dstate_getinfo("ups.mfr"), dstate_getinfo("ups.model"), device_path);
154
dstate_setinfo("driver.version.internal", "%s", DRV_VERSION);
157
static void setup_serial(void)
161
if (tcgetattr(upsfd, &tio) == -1) fatal("tcgetattr");
163
tio.c_iflag = IXON | IXOFF;
165
tio.c_cflag = (CS8 | CREAD | HUPCL | CLOCAL);
170
#ifdef HAVE_CFSETISPEED
171
cfsetispeed(&tio, B2400);
172
cfsetospeed(&tio, B2400);
174
#error This system lacks cfsetispeed() and has no other means to set the speed
177
if (tcsetattr(upsfd, TCSANOW, &tio) == -1) fatal("tcsetattr");
188
if (tries > MAXTRIES) fatalx("\nFailed - giving up...");
190
upssend("%s", ":W\r");
192
ret = upsrecv(buf, sizeof(buf), ENDCHAR, IGNCHARS);
193
ret = upsrecv(buf, sizeof(buf), ENDCHAR, IGNCHARS);
195
if ((ret > 0) && isdigit((unsigned char) buf[0])) break;
199
void upsdrv_shutdown(void) /* FIXME: Not yet tested */
204
upssend("%s", ":N40\r"); /* Delay 64 seconds */
206
ret = upsrecv(buf, sizeof(buf), ENDCHAR, IGNCHARS);
208
upssend("%s", ":G\r"); /* Inverter off */
210
ret = upsrecv(buf, sizeof(buf), ENDCHAR, IGNCHARS);
213
/* FIXME: This may prevent UPS from powering up when utility comes
215
upssend("%s", ":K0\r"); /* Relay off */
217
ret = upsrecv(buf, sizeof(buf), ENDCHAR, IGNCHARS);
226
upssend("%s", ":W\r");
228
ret = upsrecv(buf, sizeof(buf), ENDCHAR, IGNCHARS);
229
ret = upsrecv(buf, sizeof(buf), ENDCHAR, IGNCHARS);
230
if (w_value) free(w_value);
231
w_value = xstrdup(buf);
233
upssend("%s", ":L\r");
235
ret = upsrecv(buf, sizeof(buf), ENDCHAR, IGNCHARS);
236
ret = upsrecv(buf, sizeof(buf), ENDCHAR, IGNCHARS);
237
if (l_value) free(l_value);
238
l_value = xstrdup(buf);
240
upssend("%s", ":V\r");
242
ret = upsrecv(buf, sizeof(buf), ENDCHAR, IGNCHARS);
243
ret = upsrecv(buf, sizeof(buf), ENDCHAR, IGNCHARS);
244
if (v_value) free(v_value);
245
v_value = xstrdup(buf);
247
upssend("%s", ":X\r");
249
ret = upsrecv(buf, sizeof(buf), ENDCHAR, IGNCHARS);
250
ret = upsrecv(buf, sizeof(buf), ENDCHAR, IGNCHARS);
251
if (x_value) free(x_value);
252
x_value = xstrdup(buf);
255
void pollfail(char *why)
259
/* ignore the first few since these UPSes tend to drop characters */
260
if (poll_failures == 3) upslogx(LOG_ERR, why);
301
char buf[16], w_value[16], l_value[16], v_value[16], x_value[16];
306
/* Detect the UPS or die. */
309
send_cmd(":W\r", w_value, sizeof w_value);
310
send_cmd(":L\r", l_value, sizeof l_value);
311
send_cmd(":V\r", v_value, sizeof v_value);
312
send_cmd(":X\r", x_value, sizeof x_value);
314
dstate_setinfo("ups.mfr", "%s", "Tripp Lite");
316
w = hex2d(w_value, 2);
317
l = hex2d(l_value, 2);
323
va = ((w & 0x3f) * 32 + (l >> 3)) * 5; /* New formula */
325
va = l / 2; /* Old formula */
327
dstate_setinfo("ups.model", model, va);
328
dstate_setinfo("ups.firmware", "%c%c",
329
'A'+v_value[0]-'0', 'A'+v_value[1]-'0');
331
snprintf(buf, sizeof buf, "%d", offdelay);
332
dstate_setinfo("ups.delay.shutdown", buf);
333
dstate_setflags("ups.delay.shutdown", ST_FLAG_RW | ST_FLAG_STRING);
334
dstate_setaux("ups.delay.shutdown", 3);
335
snprintf(buf, sizeof buf, "%d", startdelay);
336
dstate_setinfo("ups.delay.start", buf);
337
dstate_setflags("ups.delay.start", ST_FLAG_RW | ST_FLAG_STRING);
338
dstate_setaux("ups.delay.start", 8);
339
snprintf(buf, sizeof buf, "%d", bootdelay);
340
dstate_setinfo("ups.delay.reboot", buf);
341
dstate_setflags("ups.delay.reboot", ST_FLAG_RW | ST_FLAG_STRING);
342
dstate_setaux("ups.delay.reboot", 3);
344
dstate_addcmd("test.battery.start"); /* Turns off automatically */
345
dstate_addcmd("load.off");
346
dstate_addcmd("load.on");
347
dstate_addcmd("shutdown.reboot");
348
dstate_addcmd("shutdown.reboot.graceful");
349
dstate_addcmd("shutdown.return");
350
dstate_addcmd("shutdown.stayoff");
352
upsh.instcmd = instcmd;
353
upsh.setvar = setvar;
355
printf("Detected %s %s on %s\n",
356
dstate_getinfo("ups.mfr"), dstate_getinfo("ups.model"), device_path);
358
dstate_setinfo("driver.version.internal", "%s", DRV_VERSION);
361
void upsdrv_shutdown(void)
265
366
void upsdrv_updateinfo(void)
267
char buf[256], temp[32];
272
upssend("%s", ":D\r");
274
ret = upsrecv(buf, sizeof(buf), ENDCHAR, IGNCHARS);
275
ret = upsrecv(buf, sizeof(buf), ENDCHAR, IGNCHARS);
277
if (strlen(buf) < 21) {
278
pollfail("Poll failed: short read from UPS");
283
if (strlen(buf) > 21) {
284
pollfail("Poll failed: oversized read from UPS");
289
/* only say this if it got high enough to log a failure note */
290
if (poll_failures >= 3)
291
upslogx(LOG_NOTICE, "UPS poll succeeded");
295
/* this looks more like a candidate for upsdebugx, not a variable */
297
setinfo(INFO_REG3, "%s", buf);
298
/* 0123456789abcdef01234
299
ABCCDEFFGGGGHHIIIIJJJ
303
D 0=normal 1=TRIM 2=BOOST 3="EXTRA BOOST"
304
E 0=OFF 1=OB 2=OL 3=OB (1 and 3 are the same?)
306
GG ? INFO_BATTPCT (00 when OB, values don't match table we use)
309
JJJJ ? (e.g., 5B82 5B82 5982 037B 0082)
314
dstate_setinfo("input.voltage", "%03d", hex2d(buf + 2, 2));
315
dstate_setinfo("ups.temperature", "%03d", (int)(hex2d(buf + 6, 2)*0.3636 - 21.0));
316
dstate_setinfo("ups.load", "%03d", hex2d(buf + 12, 2));
317
dstate_setinfo("input.frequency", "%02.2f", hex2d(buf + 18, 3) / 10.0);
321
/* Battery Voltage Condition */
323
case '0': status_set("LB"); break; /* Low Battery */
324
case '1': break; /* Normal */
325
default: sprintf(temp, "BAT-%c?", buf[0]);
332
case '0': status_set("OVER"); break; /* Overload */
333
case '1': break; /* Normal */
334
default: sprintf(temp, "LOAD-%c?", buf[1]);
341
case '0': break; /* Normal */
342
case '1': status_set("TRIM"); break; /* Reducing */
343
case '2': status_set("BOOST"); break; /* Boost */
344
case '3': status_set("BOOST"); break; /* Extra Boost */
345
default: sprintf(temp, "TAP-%c?", buf[4]);
352
case '0': status_set("OFF"); break; /* Off */
353
case '1': status_set("OB"); break; /* On Battery */
354
case '2': status_set("OL"); break; /* On Line */
355
case '3': status_set("OB"); break; /* On Battery */
356
default: sprintf(temp, "MODE-%c?", buf[5]);
363
upssend("%s", ":B\r");
365
ret = upsrecv(buf, sizeof(buf), ENDCHAR, IGNCHARS);
366
ret = upsrecv(buf, sizeof(buf), ENDCHAR, IGNCHARS);
367
bv = hex2d(buf, 2) / 10;
368
if (bv <= 11.0) bp = 10;
369
else if (bv <= 11.2) bp = 20;
370
else if (bv <= 11.4) bp = 30;
371
else if (bv <= 11.6) bp = 40;
372
else if (bv <= 11.8) bp = 50;
373
else if (bv <= 12.0) bp = 60;
374
else if (bv <= 12.2) bp = 70;
375
else if (bv <= 12.5) bp = 80;
376
else if (bv <= 13.2) bp = 90;
379
dstate_setinfo("battery.voltage", "%.1f", bv);
380
dstate_setinfo("battery.charge", "%03d", bp);
382
upssend("%s", ":M\r");
384
ret = upsrecv(buf, sizeof(buf), ENDCHAR, IGNCHARS);
385
ret = upsrecv(buf, sizeof(buf), ENDCHAR, IGNCHARS);
386
dstate_setinfo("input.voltage.maximum", "%d", hex2d(buf, 2));
388
upssend("%s", ":I\r");
390
ret = upsrecv(buf, sizeof(buf), ENDCHAR, IGNCHARS);
391
ret = upsrecv(buf, sizeof(buf), ENDCHAR, IGNCHARS);
392
dstate_setinfo("input.voltage.minimum", "%d", hex2d(buf, 2));
394
upssend("%s", ":C\r");
396
ret = upsrecv(buf, sizeof(buf), ENDCHAR, IGNCHARS);
397
ret = upsrecv(buf, sizeof(buf), ENDCHAR, IGNCHARS);
399
case 0: dstate_setinfo("ups.test.result", "%s", "OK"); break;
400
case 1: dstate_setinfo("ups.test.result", "%s", "Battery Bad (Replace)"); break;
401
case 2: dstate_setinfo("ups.test.result", "%s", "In Progress"); break;
402
case 3: dstate_setinfo("ups.test.result", "%s", "Bad Inverter"); break;
403
default: dstate_setinfo("ups.test.result", "Unknown (%s)", buf); break;
372
send_cmd(":D\r", buf, sizeof buf);
374
if (strlen(buf) < 21) {
375
ser_comm_fail("Failed to get data: short read from UPS");
380
if (strlen(buf) > 21) {
381
ser_comm_fail("Failed to get data: oversized read from UPS");
386
dstate_setinfo("input.voltage", "%0d", hex2d(buf + 2, 2));
387
dstate_setinfo("ups.temperature", "%3d",
388
(int)(hex2d(buf + 6, 2)*0.3636 - 21.0));
389
dstate_setinfo("ups.load", "%3d", hex2d(buf + 12, 2));
390
dstate_setinfo("input.frequency", "%02.2f", hex2d(buf + 18, 3) / 10.0);
394
/* Battery Voltage Condition */
396
case '0': /* Low Battery */
399
case '1': /* Normal */
401
default: /* Unknown */
402
upslogx(LOG_ERR, "Unknown battery state: %c", buf[0]);
408
case '0': /* Overload */
411
case '1': /* Normal */
413
default: /* Unknown */
414
upslogx(LOG_ERR, "Unknown load state: %c", buf[1]);
420
case '0': /* Normal */
422
case '1': /* Reducing */
425
case '2': /* Boost */
426
case '3': /* Extra Boost */
429
default: /* Unknown */
430
upslogx(LOG_ERR, "Unknown tap state: %c", buf[4]);
439
case '1': /* On Battery */
442
case '2': /* On Line */
445
case '3': /* On Battery */
448
default: /* Unknown */
449
upslogx(LOG_ERR, "Unknown mode state: %c", buf[4]);
454
send_cmd(":B\r", buf, sizeof buf);
455
bv = (float)hex2d(buf, 2) / 10.0;
457
/* dq ~= sqrt(dV) is a reasonable approximation
458
* Results fit well against the discrete function used in the Tripp Lite
459
* source, but give a continuous result. */
460
if (bv >= V_interval[1])
462
else if (bv <= V_interval[0])
465
bp = (int)(100*sqrt((bv - V_interval[0])
466
/ (V_interval[1] - V_interval[0])));
468
dstate_setinfo("battery.voltage", "%.1f", bv);
469
dstate_setinfo("battery.charge", "%3d", bp);
471
send_cmd(":M\r", buf, sizeof buf);
472
dstate_setinfo("input.voltage.maximum", "%d", hex2d(buf, 2));
474
send_cmd(":I\r", buf, sizeof buf);
475
dstate_setinfo("input.voltage.minimum", "%d", hex2d(buf, 2));
477
send_cmd(":C\r", buf, sizeof buf);
480
dstate_setinfo("ups.test.result", "%s", "OK");
483
dstate_setinfo("ups.test.result", "%s", "Battery Bad (Replace)");
486
dstate_setinfo("ups.test.result", "%s", "In Progress");
489
dstate_setinfo("ups.test.result", "%s", "Bad Inverter");
492
dstate_setinfo("ups.test.result", "Unknown (%s)", buf);
409
500
void upsdrv_help(void)