2
bestfort.c - model specific routines for Best Power Fortress (some
3
don't speak to bestups, which does some entirely different protocol
4
than this one that my Fortress LI 1020 speaks). I think I've got a
5
"traditional" Fortress as opposed to one of the other dozen-odd
6
lines Best Power has made or acquired over the years. The protocol
7
here is documented in Best's document FTS502b. Just ask and
8
they'll send it to you as a PDF.
10
This module is a 75% rewritten mangle of the bestups module by
11
Russell. It lost the "test battery" command since my fortress does
12
this automagically each week at 2am. It gained a -D aka debugging
13
option to not background and summarize stuff from each poll.
15
Warning: I have not tested the -k function.
17
Copyright (C) 2000 Grant Taylor <gtaylor@picante.com>
18
Copyright (C) 1999 Russell Kroll <rkroll@exploits.org>
20
This program is free software; you can redistribute it and/or modify
21
it under the terms of the GNU General Public License as published by
22
the Free Software Foundation; either version 2 of the License, or
23
(at your option) any later version.
25
This program is distributed in the hope that it will be useful,
26
but WITHOUT ANY WARRANTY; without even the implied warranty of
27
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28
GNU General Public License for more details.
30
You should have received a copy of the GNU General Public License
31
along with this program; if not, write to the Free Software
32
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
45
#include <sys/ioctl.h>
46
#include <sys/types.h>
47
#include <sys/termios.h>
54
#include "upscommon.h"
63
/* Model ID strings. The Fortress returns an enumerated integer value
65
static const int num_models = 5;
66
static char *models[] = {"Unknown",
70
"UNITY/I single-phase" };
72
/* Blob of UPS configuration data from the formatconfig string */
74
int valid; /* set to 1 when this is filled in */
76
double idealbvolts; /* various interestin battery voltages */
79
int va; /* capacity of UPS in Volt-Amps */
80
int watts; /* capacity of UPS in watts */
81
int model; /* enumerated model type */
86
void setup_serial(void);
87
int readupsline (char *buf);
88
int execute_int(char *cmd, char *response);
90
/* Set up all the funky shared memory stuff used to communicate with upsd */
93
create_info (INFOMAX, shmok);
95
/* now set up room for all future variables that are supported */
96
addinfo (INFO_MFR, "", 0, 0);
97
addinfo (INFO_MODEL, "", 0, 0);
98
addinfo (INFO_UTILITY, "", 0, 0);
99
addinfo (INFO_BATTPCT, "", 0, 0);
100
addinfo (INFO_STATUS, "", 0, 0);
101
addinfo (INFO_LOADPCT, "", 0, 0);
104
/* TODO: adapt to the standard upscommon open/send/recv calls */
106
/* This seems unlikely, as the current ones don't handle the
107
unterminated line containing the prompt that you get back after
108
each command. Perhaps the thing is to make our own or something;
109
the standard calls are nice in that they use alarms to do timeouts
110
and such. OTOH, the Fortress *always* babbles back at you giving
111
at least a prompt... */
113
int open_ups(char *filename)
117
upsfd = open(filename, O_RDWR | O_NDELAY);
126
void setup_serial(void)
130
/* Zero out the structure to prevent confusion. It took me forever
131
to figure out why my serial reading routines read everything from
132
the UPS perfectly, except for the infuriating detail that all the
133
`0's were missing: since it didn't wipe the c_cc part clean, the
134
VSTART character got set to `0', so that got eaten. The (horrid)
135
example code from Best Power was immune to this problem since
136
everything is a (n implicitly initialized) global. */
138
memset(&tio, 0, sizeof(tio));
140
tio.c_iflag = IXON | IXOFF;
142
tio.c_cflag = (B1200 | CS8 | CREAD | HUPCL | CLOCAL);
146
tio.c_cc[VSTART] = 17; /* CTRL('q'); is not portable */
148
if (tcsetattr (upsfd, TCSANOW, &tio) == -1)
158
/* It's not obvious that O_NDELAY was useful. We should probably do
159
selects with timeouts or set a timer against blocking i/o or
160
something. Luckily UPS's aren't prone to goign missing. */
163
ret = read (upsfd, &c, 1);
168
if (errno == EAGAIN) {
173
perror("read error in readchar()");
178
/* Run a command. Put the response, or empty string if none, into
179
buffer response. God help you if the buffer is too short. It
180
would be best to not execute a continuous statement like
183
int execute(char *cmd, char *response)
187
/* First synchronize with a control-c and backspace. Then we'll be
188
at a "clean" prompt and ready to go. */
190
return execute_int("\x08\x03", junkbuf) || execute_int(cmd,response);
193
int execute_int(char *cmd, char *response)
196
char *ptr, linebuf[256];
198
/* First send command string plus the \r */
202
ret = write(upsfd, ptr++, 1);
207
perror("execute(): write() failed");
213
ret = write(upsfd, "\r", 1); /* carriage return */
218
perror("execute(): write() failed");
222
/* OK, now we read. We expect to see what we just wrote, followed
223
by some or no output, followed by a prompt ending in "=> " */
225
if (strlen(cmd) && cmd[0] != '\x08') {
227
do { /* read until we see our command */
228
prompt = readupsline(linebuf);
229
lineoffset = strlen(cmd) - strlen(linebuf);
230
} while (0 != strcmp(cmd,linebuf+lineoffset) && prompt != -1);
235
prompt = readupsline(linebuf);
238
/* the next prompt */
239
} else if (strlen(linebuf) > 1 && linebuf[0] != '\r') {
240
/* interesting output */
241
strcat(response, linebuf);
242
strcat(response, "\r");
244
} while (prompt == 0);
246
return ((prompt == -1) ? -1 : 0);
250
read a line from the UPS, terminated by either a '\r' or by a =>
254
return 0 for normal lines
255
return 1 for prompt lines
257
int readupsline (char *buf)
266
while ((ch = readchar ())) {
268
continue; /* absorb duplicate nl/crs */
272
/* done on carriage return/newline, don't include cr */
273
if (ch == '\n' || ch == '\r') {
278
/* include this character */
281
/* done if last two chars are '=>' */
284
if (0 == memcmp(prompt, "=>", 2)) {
286
return 1; /* one for prompt line */
299
printf ("Syncing: ");
302
/* A bit better sanity might be good here. As is, we expect the
303
human to observe the time being totally not a time. */
305
if (0 == execute("time", buf)) {
306
fprintf(stderr, "UPS Time: %s\n", buf);
308
fprintf(stderr, "Error connecting to UPS.\n");
313
/* power down the attached load immediately */
314
void forceshutdown(char *port)
318
upslogx(LOG_INFO, "Initiating UPS shutdown\n");
319
printf ("Initiating forced UPS shutdown!\n");
323
/* shutdown in 5 seconds, autostart later */
324
execute("shutdown autostart 5", temp);
326
printf ("Waiting for poweroff...\n");
328
printf ("Hmm, did the shutdown fail? Oh well...\n");
332
void usage (char *prog)
334
printf ("usage: %s [-h] [-k] [-D] <device>\n", prog);
335
printf ("Example: %s /dev/ttyS0\n", prog);
339
void help (char *prog)
341
printf ("usage: %s [-h] [-k] <device>\n", prog);
343
printf ("-h - display this help\n");
344
printf ("-k - force shutdown\n");
345
printf ("-D - run in foreground for debugging/testing\n");
346
printf ("<device> - /dev entry corresponding to UPS port\n");
351
void ups_ident (void)
353
char temp[256], fcstring[512];
356
if (0 != execute("fc", fcstring)) {
357
fprintf(stderr, "Failed execute in ups_ident()\n");
361
/* response is a one-line packed string starting with $ */
362
if (fcstring[0] != '$') {
364
"Bad response from formatconfig command in ups_ident()\n");
369
fprintf(stderr, "fc: %s\n", fcstring);
371
/* chars 4:2 are a two-digit ascii hex enumerated model code */
372
memcpy(temp, fcstring+3, 2);
374
sscanf(temp, "%x", &fc.model);
376
if (fc.model >= num_models) {
378
"Unknown model 0x%x in ups_ident()\n",
383
/* chars 12:5 are five-digit ascii decimal VA rating */
384
memcpy(temp, fcstring+11, 5);
388
/* chars 17:5 are 5-dig ascii deciaml watts rating */
389
memcpy(temp, fcstring+16, 5);
391
fc.watts = atoi(temp);
393
/* chars 46:4 are ascii decimal xxx.x nominal battery volt */
394
memcpy(temp, fcstring+45, 4);
396
fc.idealbvolts = atof(temp) / 10.0;
398
/* chars 50:4 are full charge battery voltage */
399
memcpy(temp, fcstring+49, 4);
401
fc.fullvolts = atof(temp) / 10.0;
403
/* chars 58:4 are empty battery voltage */
404
memcpy(temp, fcstring+57, 4);
406
fc.emptyvolts = atof(temp) / 10.0;
408
setinfo(INFO_MFR, "%s", "Best Power");
409
setinfo(INFO_MODEL, "%s %d", models[fc.model], fc.va);
413
printf("Best Power %s detected\n", getdata(INFO_MODEL));
414
printf("Battery voltages %5.1f nominal, %5.1f full, %5.1f empty\n",
422
void ups_update (void)
428
"upsupdate run before ups_ident() read ups config\n");
432
if (0==execute("format",fstring)) {
433
int inverter=0, charger=0,
435
linestat=0, alstat=0, vaout=0;
436
double ampsout=0.0, vbatt=0.0, battpercent=0.0, loadpercent=0.0;
437
char tmp[16], statstr[128];
439
/* Inverter status. 0=off 1=on */
440
memcpy(tmp, fstring+16, 2);
442
inverter = atoi(tmp);
444
/* Charger status. 0=off 1=on */
445
memcpy(tmp, fstring+18, 2);
449
/* Input Voltage. integer number */
450
memcpy(tmp, fstring+24, 4);
454
/* Iout. int times 10 */
455
memcpy(tmp, fstring+36, 4);
457
ampsout = ((double)(atoi(tmp)) / 10.0);
459
/* Battery voltage. int times 10 */
460
memcpy(tmp, fstring+50, 4);
462
vbatt = ((double)(atoi(tmp)) / 10.0);
464
/* Volt-amps out. int */
465
memcpy(tmp, fstring+40, 6);
469
/* Line status. Bitmask */
470
memcpy(tmp, fstring+72, 2);
472
linestat = atoi(tmp);
474
/* Alarm status reg 1. Bitmask */
475
memcpy(tmp, fstring+20, 2);
479
/* Alarm status reg 2. Bitmask */
480
memcpy(tmp, fstring+22, 2);
482
alstat = alstat | (atoi(tmp) << 8);
485
/* Get various assorted parameters */
488
if (0 == execute("p 9", fstring)) {
489
sscanf(fstring, "09 Runtime %dm", &btimeleft);
493
if (0 == execute("p 16", fstring)) {
495
sscanf(fstring, "16 FullLoad%% %d", &l);
496
loadpercent = (double) l;
499
/* Compute battery percent left based on battery voltages. */
500
battpercent = ((vbatt - fc.emptyvolts)
501
/ (fc.fullvolts - fc.emptyvolts) * 100.0);
502
if (battpercent < 0.0)
504
else if (battpercent > 100.0)
507
/* Compute status string */
509
int lowbatt, overload, replacebatt, boosting, trimming;
511
lowbatt = alstat & (1<<1);
512
overload = alstat & (1<<6);
513
replacebatt = alstat & (1<<10);
514
boosting = inverter && (linestat & (1<<2)) && (vin < 115);
515
trimming = inverter && (linestat & (1<<2)) && (vin > 115);
517
strcpy(tmp, inverter ? "OB" : "OL");
518
if (lowbatt) strcat (tmp, " LB");
519
if (trimming) strcat (tmp, " TRIM");
520
if (boosting) strcat (tmp, " BOOST");
521
if (replacebatt) strcat (tmp, " RB");
522
if (overload) strcat (tmp, " OVER");
524
strlcpy(statstr, tmp, sizeof(statstr));
531
"Poll: inverter %d charger %d vin %d vaout %d btimeleft %d\n",
532
inverter, charger, vin, vaout, btimeleft);
534
" ampsout %5.1f vbatt %5.1f batpcnt %5.1f loadpcnt %5.1f\n",
535
ampsout, vbatt, battpercent, loadpercent);
537
" STATUS '%s'\n", statstr);
543
/* Stuff information into info structures */
545
setinfo(INFO_STATUS, "%s", statstr);
547
setinfo(INFO_BATTPCT, "%02.1f", battpercent);
549
setinfo(INFO_LOADPCT, "%03.1f", loadpercent);
551
setinfo(INFO_UTILITY, "%05.1f", (double)vin);
561
int main (int argc, char **argv)
563
char *prog, *portname;
566
printf ("Network UPS Tools - Best Fortress driver 0.1 (%s)\n",
568
openlog ("bestfort", LOG_PID, LOG_FACILITY);
571
while ((i = getopt(argc, argv, "+hk:D")) != EOF) {
574
forceshutdown(optarg);
599
for (i = strlen(argv[0]); i >= 0; i--)
600
if (argv[0][i] == '/') {
601
portname = &argv[0][i+1];
605
if (portname == NULL) {
606
printf ("Unable to abbreviate %s\n", argv[0]);
610
snprintf (statefn, sizeof(statefn), "%s/bestfort-%s", STATEPATH,
615
ups_sync(); /* get a prompt */
621
setuphandlers(); /* seems uneeded? */
624
ups_ident(); /* run formatconfig command and
625
extract fixed information into
629
/* Fortress does automagical testing monthly */
630
addinfo (INFO_INSTCMD, "", 0, CMD_BTEST0);
631
addinfo (INFO_INSTCMD, "", 0, CMD_BTEST1);
634
printf ("Detected %s %s on %s\n",
645
if (getupsmsg(2)) /* TODO: remove debug scaffolding */
646
upslogx(LOG_INFO, "Received a message from upsd\n");
655
void instcmd (int auxcmd, int dlen, char *data)
659
/* TODO: reply to upsd? */
662
case CMD_BTEST0: /* stop battery test */
664
case CMD_BTEST1: /* start battery test */
667
upslogx(LOG_INFO, "instcmd: unknown type 0x%04x\n",
675
/* upsh.instcmd = instcmd; */