2
bestuferrups.c - model specific routines for Best Power Micro-Ferrups
4
This module is a 40% rewritten mangle of the bestfort module by
5
Grant, which is a 75% rewritten mangle of the bestups module by
6
Russell. It has no test battery command since my ME700 does this
7
by itself. (same as Grant's driver in this respect)
9
Warning: I have not tested the -k function.
11
Copyright (C) 2000 John Stone <johns@megapixel.com>
12
Copyright (C) 2000 Grant Taylor <gtaylor@picante.com>
13
Copyright (C) 1999 Russell Kroll <rkroll@exploits.org>
15
This program is free software; you can redistribute it and/or modify
16
it under the terms of the GNU General Public License as published by
17
the Free Software Foundation; either version 2 of the License, or
18
(at your option) any later version.
20
This program is distributed in the hope that it will be useful,
21
but WITHOUT ANY WARRANTY; without even the implied warranty of
22
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23
GNU General Public License for more details.
25
You should have received a copy of the GNU General Public License
26
along with this program; if not, write to the Free Software
27
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
40
#include <sys/ioctl.h>
41
#include <sys/types.h>
42
#include <sys/termios.h>
49
#include "upscommon.h"
59
/* Blob of UPS configuration data from the formatconfig string */
61
int valid; /* set to 1 when this is filled in */
63
float idealbvolts; /* various interestin battery voltages */
66
int va; /* capacity of UPS in Volt-Amps */
67
int watts; /* capacity of UPS in watts */
68
int model; /* enumerated model type */
74
int readupsline (char *buf);
75
int execute_int(char *cmd, char *response);
77
/* Set up all the funky shared memory stuff used to communicate with upsd */
80
create_info (INFOMAX, shmok);
82
/* now set up room for all future variables that are supported */
83
addinfo (INFO_MFR, "", 0, 0);
84
addinfo (INFO_MODEL, "", 0, 0);
85
addinfo (INFO_UTILITY, "", 0, 0);
86
addinfo (INFO_OUTVOLT, "", 0, 0);
87
addinfo (INFO_ACFREQ, "", 0, 0);
88
addinfo (INFO_LOADPCT, "", 0, 0);
89
addinfo (INFO_BATTPCT, "", 0, 0);
90
addinfo (INFO_UPSTEMP, "", 0, 0);
91
addinfo (INFO_STATUS, "", 0, 0);
94
/* TODO: adapt to the standard upscommon open/send/recv calls */
96
/* This seems unlikely, as the current ones don't handle the
97
unterminated line containing the prompt that you get back after
98
each command. Perhaps the thing is to make our own or something;
99
the standard calls are nice in that they use alarms to do timeouts
100
and such. OTOH, the Fortress *always* babbles back at you giving
101
at least a prompt... */
103
int open_ups(char *filename)
107
upsfd = open(filename, O_RDWR | O_NDELAY);
109
fatal("open %s", filename);
120
/* Zero out the structure to prevent confusion. It took me forever
121
to figure out why my serial reading routines read everything from
122
the UPS perfectly, except for the infuriating detail that all the
123
`0's were missing: since it didn't wipe the c_cc part clean, the
124
VSTART character got set to `0', so that got eaten. The (horrid)
125
example code from Best Power was immune to this problem since
126
everything is a (n implicitly initialized) global. */
128
memset(&tio, 0, sizeof(tio));
130
tio.c_iflag = IXON | IXOFF;
132
tio.c_cflag = (B9600 | CS8 | CREAD | HUPCL | CLOCAL);
136
tio.c_cc[VSTART] = 17; /* CTRL('q'); is not portable */
138
if (tcsetattr (upsfd, TCSANOW, &tio) == -1)
148
/* It's not obvious that O_NDELAY was useful. We should probably do
149
selects with timeouts or set a timer against blocking i/o or
150
something. Luckily UPS's aren't prone to goign missing. */
153
ret = read (upsfd, &c, 1);
158
if (ret == 0 || errno == EAGAIN) {
163
perror("read error in readchar()");
168
/* Run a command. Put the response, or empty string if none, into
169
buffer response. God help you if the buffer is too short. It
170
would be best to not execute a continuous statement like
173
int execute(char *cmd, char *response)
177
/* First synchronize with a control-c and backspace. Then we'll be
178
at a "clean" prompt and ready to go. */
180
return execute_int("\x08\x03", junkbuf) || execute_int(cmd,response);
183
int execute_int(char *cmd, char *response)
186
char *ptr, linebuf[256];
188
/* First send command string plus the \r */
192
ret = write(upsfd, ptr++, 1);
197
perror("execute(): write() failed");
203
ret = write(upsfd, "\r", 1); /* carriage return */
208
perror("execute(): write() failed");
212
/* OK, now we read. We expect to see what we just wrote, followed
213
by some or no output, followed by a prompt ending in "=> " */
215
if (strlen(cmd) && cmd[0] != '\x08') {
217
do { /* read until we see our command */
218
prompt = readupsline(linebuf);
219
lineoffset = strlen(cmd) - strlen(linebuf);
220
} while (0 != strcmp(cmd,linebuf+lineoffset) && prompt != -1);
225
prompt = readupsline(linebuf);
228
/* the next prompt */
229
} else if (strlen(linebuf) > 1 && linebuf[0] != '\r') {
230
/* interesting output */
231
strcat(response, linebuf);
232
strcat(response, "\r");
234
} while (prompt == 0);
236
return ((prompt == -1) ? -1 : 0);
240
read a line from the UPS, terminated by either a '\r' or by a =>
244
return 0 for normal lines
245
return 1 for prompt lines
247
int readupsline (char *buf)
256
while ((ch = readchar ())) {
258
continue; /* absorb duplicate nl/crs */
262
/* done on carriage return/newline, don't include cr */
263
if (ch == '\n' || ch == '\r') {
268
/* include this character */
271
/* done if last two chars are '=>' */
274
if (0 == memcmp(prompt, "=>", 2)) {
276
return 1; /* one for prompt line */
289
printf ("Syncing: ");
292
/* A bit better sanity might be good here. As is, we expect the
293
human to observe the time being totally not a time. */
295
if (0 == execute("time", buf)) {
296
fprintf(stderr, "UPS Time: %s\n", buf);
298
fprintf(stderr, "Error connecting to UPS.\n");
303
/* power down the attached load immediately */
304
void forceshutdown(char *port)
308
upslogx(LOG_INFO, "Initiating UPS shutdown\n");
309
printf ("Initiating forced UPS shutdown!\n");
313
/* shutdown in 5 seconds, autostart later */
314
execute("shutdown autostart 5", temp);
316
printf ("Waiting for poweroff...\n");
318
printf ("Hmm, did the shutdown fail? Oh well...\n");
322
void usage (char *prog)
324
printf ("usage: %s [-h] [-k] [-D] <device>\n", prog);
325
printf ("Example: %s /dev/ttyS0\n", prog);
329
void help (char *prog)
331
printf ("usage: %s [-h] [-k] <device>\n", prog);
333
printf ("-h - display this help\n");
334
printf ("-k - force shutdown\n");
335
printf ("-D - run in foreground for debugging/testing\n");
336
printf ("<device> - /dev entry corresponding to UPS port\n");
343
char temp[256], fcstring[512];
346
if (0 != execute("id", fcstring)) {
347
fprintf(stderr, "Failed execute in ups_ident()\n");
351
/* response is a one-line packed string starting with $ */
352
if (memcmp(fcstring, "Unit", 4)) {
353
fprintf(stderr, "Bad response from formatconfig command in ups_ident()\n");
354
fprintf(stderr, "id: %s\n", fcstring);
359
fprintf(stderr, "id: %s\n", fcstring);
361
/* chars 4:2 are a two-digit ascii hex enumerated model code */
362
memcpy(temp, fcstring+9, 2);
365
if (memcmp(temp, "ME", 2)) {
366
fprintf(stderr, "Unknown model %s in ups_ident()\n", temp);
370
/* hard code this to be an ME700 for now */
374
/* determine shutdown battery voltage */
375
if (0 == execute("d 29", fcstring)) {
376
sscanf(fcstring, "29 LowBat %f", &fc.emptyvolts);
379
/* determine fully charged battery voltage */
380
if (0 == execute("d 31", fcstring)) {
381
sscanf(fcstring, "31 HiBatt %f", &fc.fullvolts);
384
/* determine "ideal" voltage by a guess */
385
fc.idealbvolts = ((fc.fullvolts - fc.emptyvolts) * 0.7) + fc.emptyvolts;
387
setinfo(INFO_MFR, "%s", "Best Power");
388
setinfo(INFO_MODEL, "%s %d", "Micro Ferrups (ME)", fc.va);
392
printf("Best Power %s detected\n", getdata(INFO_MODEL));
393
printf("Battery voltages %5.1f nominal, %5.1f full, %5.1f empty\n",
407
"upsupdate run before ups_ident() read ups config\n");
411
if (0==execute("f",fstring)) {
412
int inverter=0, charger=0, vin=0, vout=0, btimeleft=0, linestat=0,
414
double ampsout=0.0, vbatt=0.0, battpercent=0.0, loadpercent=0.0,
415
upstemp=0.0, acfreq=0.0;
416
char tmp[16], statstr[128];
418
/* Inverter status. 0=off 1=on */
419
memcpy(tmp, fstring+16, 2);
421
inverter = atoi(tmp);
423
/* Charger status. 0=off 1=on */
424
memcpy(tmp, fstring+18, 2);
428
/* Input Voltage. integer number */
429
memcpy(tmp, fstring+24, 4);
433
/* Output Voltage. integer number */
434
memcpy(tmp, fstring+28, 4);
438
/* Iout. int times 10 */
439
memcpy(tmp, fstring+36, 4);
441
ampsout = ((double)(atoi(tmp)) / 10.0);
443
/* Battery voltage. int times 10 */
444
memcpy(tmp, fstring+50, 4);
446
vbatt = ((double)(atoi(tmp)) / 10.0);
448
/* Volt-amps out. int */
449
memcpy(tmp, fstring+40, 6);
453
/* Line status. Bitmask */
454
memcpy(tmp, fstring+72, 2);
456
linestat = atoi(tmp);
458
/* Alarm status reg 1. Bitmask */
459
memcpy(tmp, fstring+20, 2);
463
/* Alarm status reg 2. Bitmask */
464
memcpy(tmp, fstring+22, 2);
466
alstat = alstat | (atoi(tmp) << 8);
468
/* AC line frequency */
469
memcpy(tmp, fstring+54, 4);
471
acfreq = ((double)(atoi(tmp)) / 100.0);
473
/* Runtime remaining */
474
memcpy(tmp, fstring+58, 4);
476
btimeleft = atoi(tmp);
478
/* UPS Temperature */
479
memcpy(tmp, fstring+62, 4);
481
upstemp = (double)(atoi(tmp));
484
if (0 == execute("d 16", fstring)) {
486
sscanf(fstring, "16 FullLoad%% %d", &l);
487
loadpercent = (double) l;
490
/* Compute battery percent left based on battery voltages. */
491
battpercent = ((vbatt - fc.emptyvolts)
492
/ (fc.fullvolts - fc.emptyvolts) * 100.0);
493
if (battpercent < 0.0)
495
else if (battpercent > 100.0)
498
/* Compute status string */
500
int lowbatt, overload, replacebatt, boosting, trimming;
502
lowbatt = alstat & (1<<1);
503
overload = alstat & (1<<6);
504
replacebatt = alstat & (1<<10);
505
boosting = inverter && (linestat & (1<<2)) && (vin < 115);
506
trimming = inverter && (linestat & (1<<2)) && (vin > 115);
508
strcpy(tmp, inverter ? "OB" : "OL");
509
if (lowbatt) strcat (tmp, " LB");
510
if (trimming) strcat (tmp, " TRIM");
511
if (boosting) strcat (tmp, " BOOST");
512
if (replacebatt) strcat (tmp, " RB");
513
if (overload) strcat (tmp, " OVER");
515
strlcpy(statstr, tmp, sizeof(statstr));
521
"Poll: inverter %d charger %d vin %d vout %d vaout %d btimeleft %d\n",
522
inverter, charger, vin, vout, vaout, btimeleft);
524
" ampsout %5.1f vbatt %5.1f batpcnt %5.1f loadpcnt %5.1f upstemp %5.1f acfreq %5.2f\n",
525
ampsout, vbatt, battpercent, loadpercent, upstemp, acfreq);
527
" STATUS '%s'\n", statstr);
531
/* Stuff information into info structures */
533
setinfo(INFO_STATUS, "%s", statstr);
535
setinfo(INFO_UTILITY, "%05.1f", (double)vin);
537
setinfo(INFO_OUTVOLT, "%05.1f", (double)vout);
539
setinfo(INFO_BATTPCT, "%02.1f", battpercent);
541
setinfo(INFO_LOADPCT, "%02.1f", loadpercent);
543
setinfo(INFO_ACFREQ, "%05.2f", (double)acfreq);
545
setinfo(INFO_UPSTEMP, "%05.1f", (double)upstemp);
553
int main (int argc, char **argv)
555
char *prog, *portname;
558
printf ("Network UPS Tools - Best Micro-Ferrups driver 0.1 (%s)\n",
560
openlog ("bestuferrups", LOG_PID, LOG_FACILITY);
563
while ((i = getopt(argc, argv, "+hk:D")) != EOF) {
566
forceshutdown(optarg);
591
for (i = strlen(argv[0]); i >= 0; i--)
592
if (argv[0][i] == '/') {
593
portname = &argv[0][i+1];
597
if (portname == NULL) {
598
printf ("Unable to abbreviate %s\n", argv[0]);
602
snprintf (statefn, sizeof(statefn), "%s/bestuferrups-%s", STATEPATH,
607
ups_sync(); /* get a prompt */
613
setuphandlers(); /* seems uneeded? */
616
ups_ident(); /* run formatconfig command and
617
extract fixed information into
621
/* Fortress does automagical testing monthly */
622
addinfo (INFO_INSTCMD, "", 0, CMD_BTEST0);
623
addinfo (INFO_INSTCMD, "", 0, CMD_BTEST1);
626
printf ("Detected %s %s on %s\n",
637
if (getupsmsg(2)) /* TODO: remove debug scaffolding */
638
upslogx(LOG_INFO, "Received a message from upsd\n");
647
void instcmd (int auxcmd, int dlen, char *data)
651
/* TODO: reply to upsd? */
654
case CMD_BTEST0: /* stop battery test */
656
case CMD_BTEST1: /* start battery test */
659
upslogx(LOG_INFO, "instcmd: unknown type 0x%04x\n",
667
/* upsh.instcmd = instcmd; */