2
bestfcom.c - model specific routines for Best Power F-Command ups models
4
This module is yet another rewritten mangle of the bestuferrups
5
driver. This driver was written in an attempt to consolidate
6
the various Best Fortress/FERRUPS modules that support the
7
'f'-command set and provide support for more of these models.
9
Models tested with this new version:
18
This module is a 40% rewritten mangle of the bestfort module by
19
Grant, which is a 75% rewritten mangle of the bestups module by
20
Russell. It has no test battery command since my ME3100 does this
21
by itself. (same as Grant's driver in this respect)
23
Copyright (C) 2002 Andreas Wrede <andreas@planix.com>
24
Copyright (C) 2000 John Stone <johns@megapixel.com>
25
Copyright (C) 2000 Grant Taylor <gtaylor@picante.com>
26
Copyright (C) 1999 Russell Kroll <rkroll@exploits.org>
28
This program is free software; you can redistribute it and/or modify
29
it under the terms of the GNU General Public License as published by
30
the Free Software Foundation; either version 2 of the License, or
31
(at your option) any later version.
33
This program is distributed in the hope that it will be useful,
34
but WITHOUT ANY WARRANTY; without even the implied warranty of
35
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
36
GNU General Public License for more details.
38
You should have received a copy of the GNU General Public License
39
along with this program; if not, write to the Free Software
40
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
44
#define DRV_VERSION "0.11"
50
#define IGNCHARS "\012"
53
/* BEST Factory UPS Model Codes */
60
/* Internal driver UPS Model Codes */
72
/* Blob of UPS configuration data from the formatconfig string */
74
int valid; /* set to 1 when this is filled in */
76
float idealbvolts; /* various interesting battery voltages */
81
int va; /* capacity of UPS in Volt-Amps */
82
int watts; /* capacity of UPS in watts */
83
int model; /* enumerated model type */
84
int type; /* enumerated ups type*/
86
char name[16]; /* ups type name*/
90
static int inverter_status;
94
/* Set up all the funky shared memory stuff used to communicate with upsd */
95
void upsdrv_initinfo (void)
97
/* now set up room for all future variables that are supported */
100
dstate_setinfo("driver.name", "%s", "bestfcom");
102
dstate_setinfo("ups.mfr", "%s", "Best Power");
105
dstate_setinfo("ups.model", "%s ME%d", fc.name, fc.va);
108
dstate_setinfo("ups.model", "%s MD%d", fc.name, fc.va);
111
dstate_setinfo("ups.model", "%s FD%d", fc.name, fc.va);
114
dstate_setinfo("ups.model", "%s FE%d", fc.name, fc.va);
117
dstate_setinfo("ups.model", "%s LI%d", fc.name, fc.va);
120
exit(EXIT_FAILURE); /* Will never get here, upsdrv_initups() will catch */
123
dstate_setinfo("battery.voltage.nominal", "%05.2f", (double)fc.idealbvolts);
125
/* Do we really need to waste time on this? */
127
if (fc.model != FDxxxx) {
128
if (execute("d 00\r", tmp, sizeof(tmp)) > 0)
129
sscanf(tmp, "00 Time %8s", time);
131
if (execute("d 10\r", tmp, sizeof(tmp)) > 0)
132
sscanf(tmp, "10 Date %8s", date);
134
dstate_setinfo("ups.time", "%s", time);
135
dstate_setinfo("ups.date", "%s", date);
139
upsdebugx(1, "Best Power %s detected", dstate_getinfo("ups.model"));
140
upsdebugx(1, "Battery voltages: %5.2f nominal, %5.2f full, %5.2f low, %5.2f empty",
148
/* atoi() without the freebie octal conversion */
149
int bcd2i (const char *bcdstring, const int bcdlen)
151
int i, digit, total = 0, factor = 1;
152
for (i = 1; i < bcdlen; i++)
154
for (i = 0; i < bcdlen; i++) {
155
digit = bcdstring[i] - '0';
159
total += digit * factor;
165
#define POLL_ALERT "{"
166
static void alert_handler(char ch)
170
/* Received an Inverter status alarm :
171
* "\r\n{Inverter: On}\r\n=>"
172
* Try to flush the message
174
ser_get_line(upsfd, buf, sizeof(buf), '\012', "", 0, 20);
177
/* Debugging display from kermit:
178
----------------------------------------------------
179
time^M^M^JFeb 20, 22:13:32^M^J^M^J=>id^M^JUnit ID "ME3.1K12345"^M^J^M^J=>
180
----------------------------------------------------
182
static int execute(const char *cmd, char *result, int resultsize)
187
/* Check for the Inverter status alarm if pending :
188
* "\r\n{Inverter: On}\r\n=>"
190
ser_get_line_alert(upsfd, buf, sizeof(buf), '\012', "",
191
POLL_ALERT, alert_handler, 0, 20);
193
ser_send(upsfd, cmd);
195
/* delete command echo up to \012 but no further */
196
for (ch = '\0'; ch != '\012'; ser_get_char(upsfd, &ch, 0, 10));
198
/* get command response */
199
ret = ser_get_line(upsfd, result, resultsize, '\015', "\012", 3, 0);
206
format command response -> 80 chars
209
Date Invtr |12| error
210
| ||Time| || |Vi||Vo| |Io|| VA | |Vb||Hz||rt| || |vr|CS
211
011314581801000000010000011601160000002300026600000265600000190000000000E00106E6\r
212
01161706430100010001000002040121000000980011890000057959980001002200000064080727\r
213
011800364801000100010000021301200000003100037100001343599803060024000000680807A0\r
214
0121022719010001000100000208011900190000000000000005676001082200350000000006101A\r
215
00000000000100000000000002370236000000220005190000026850000009002600000000030161\r
216
0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8
217
0 5 0 5 0 5 0 5 0 5 0 5 0 5 0 5 0
219
Above f-responses listed in this order:
224
Fortress ?????? (from Holger's old Best Fortress notes)
227
void upsdrv_updateinfo(void)
232
upsdebugx(1, "upsupdate run before ups_ident() read ups config");
236
if (execute("f\r", fstring, sizeof(fstring)) >= 80) {
237
int inverter=0, charger=0, vin=0, vout=0, btimeleft=0, linestat=0,
240
double ampsout=0.0, vbatt=0.0, battpercent=0.0, loadpercent=0.0,
241
upstemp=0.0, acfreq=0.0;
243
char date[9], time[9], tmp[32];
245
upsdebugx(3, "f response: %d %s", strlen(fstring), fstring);
250
/* Inverter status. 0=off 1=on */
251
inverter = bcd2i(&fstring[16], 2);
253
/* Charger status. 0=off 1=on */
254
charger = bcd2i(&fstring[18], 2);
256
/* Input Voltage. integer number */
257
vin = bcd2i(&fstring[24], 4);
259
/* Output Voltage. integer number */
260
vout = bcd2i(&fstring[28], 4);
262
/* Battery voltage. int times 10 */
263
vbatt = ((double)bcd2i(&fstring[50], 4) / 10.0);
265
/* Alarm status reg 1. Bitmask */
266
alstat = bcd2i(&fstring[20], 2);
268
/* Alarm status reg 2. Bitmask */
269
alstat = alstat | (bcd2i(&fstring[22], 2) << 8);
271
/* AC line frequency */
272
acfreq = ((double)bcd2i(&fstring[54], 4) / 100.0);
274
/* Runtime remaining */
275
btimeleft = bcd2i(&fstring[58], 4);
277
if (fc.model != FDxxxx) {
278
/* Iout. int times 10 */
279
ampsout = ((double)bcd2i(&fstring[36], 4) / 10.0);
281
/* Volt-amps out. int */
282
vaout = ((double)bcd2i(&fstring[40], 6) / 10);
284
/* Line status. Bitmask */
285
linestat = bcd2i(&fstring[72], 2);
289
if (fc.model != LIxxxx) {
290
upstemp = (double) bcd2i(&fstring[62], 4);
299
if (execute("d 16\r", tmp, sizeof(tmp)) > 0) {
301
sscanf(tmp, "16 FullLoad%% %d", &l);
302
loadpercent = (double) l;
306
if (execute("d 22\r", tmp, sizeof(tmp)) > 0) {
308
sscanf(tmp, "22 FullLoad%% %d", &l);
309
loadpercent = (double) l;
312
default: /* Will never happen, caught in upsdrv_initups() */
313
upsdebugx(1, "Uknown model in upsdrv_updateinfo()");
317
/* Compute battery percent left based on battery voltages. */
318
battpercent = ((vbatt - fc.emptyvolts)
319
/ (fc.fullvolts - fc.emptyvolts) * 100.0);
320
if (battpercent < 0.0)
322
else if (battpercent > 100.0)
325
/* Compute status string */
327
int lowbatt, lowvolts, overload, replacebatt, boosting, trimming;
329
lowbatt = alstat & (1<<1);
330
overload = alstat & (1<<6);
331
replacebatt = alstat & (1<<10);
333
boosting = inverter && (linestat & (1<<2)) && (vin < 115);
334
trimming = inverter && (linestat & (1<<2)) && (vin > 115);
336
/* status bits can be unreliable, so try to help it out */
337
lowvolts = (vbatt <= fc.lowvolts);
342
if (inverter_status < 1) {
343
upsdebugx(1, "Inverter On, charger: %d battery time left: %d",
349
if (inverter_status) {
350
upsdebugx(1, "Inverter Off, charger: %d battery time left: %d",
357
if (lowbatt | lowvolts)
376
"Poll: inverter %d charger %d vin %d vout %d vaout %d btimeleft %d",
377
inverter, charger, vin, vout, vaout, btimeleft);
379
" vbatt %5.1f batpcnt %5.1f loadpcnt %5.1f upstemp %5.1f ampsout %5.1f acfreq %5.2f",
380
vbatt, battpercent, loadpercent, upstemp, ampsout, acfreq);
382
/* Stuff information into info structures */
383
dstate_setinfo("input.voltage", "%05.1f", (double)vin);
384
dstate_setinfo("input.frequency", "%05.2f", acfreq);
385
dstate_setinfo("output.voltage", "%05.1f", (double)vout);
386
dstate_setinfo("output.current", "%04.1f", ampsout);
387
dstate_setinfo("battery.charge", "%02.1f", battpercent);
388
dstate_setinfo("battery.voltage", "%02.1f", vbatt);
389
dstate_setinfo("battery.runtime", "%d", btimeleft);
390
dstate_setinfo("ups.load", "%02.1f", loadpercent);
392
dstate_setinfo("ups.temperature", "%05.1f", (double)upstemp);
398
upsdebugx(1, "failed f response. strlen: %d", strlen(fstring));
401
} /* if (execute("f\r", fstring, sizeof(fstring)) >= 80) */
407
static void ups_sync(void)
411
/* A bit better sanity might be good here. As is, we expect the
412
human to observe the time being totally not a time. */
414
if (execute("time\r", buf, sizeof(buf)) > 0) {
415
upsdebugx(1, "UPS Time: %s", buf);
417
upsdebugx(1, "Error connecting to UPS.");
422
/* power down the attached load immediately */
423
void upsdrv_shutdown(void)
425
/* NB: hard-wired password */
426
ser_send(upsfd, "pw377\r");
427
ser_send(upsfd, "off 1 a\r"); /* power off in 1 second and restart when line power returns */
430
/* list flags and values that you want to receive via -x */
431
void upsdrv_makevartable(void)
435
void upsdrv_help(void)
439
void upsdrv_banner(void)
441
printf("Network UPS Tools - Best Ferrups/Fortress %s (%s)\n",
442
DRV_VERSION, UPS_VERSION);
445
static void sync_serial(void) {
448
ser_flush_in(upsfd, "", 1);
450
ser_send(upsfd, "\r");
452
ser_get_line(upsfd, buffer, sizeof(buffer), '\r', "\012", 3, 0);
453
ser_get_line(upsfd, buffer, sizeof(buffer), ENDCHAR, IGNCHARS, 3, 0);
455
while (ser_get_line(upsfd, buffer, sizeof(buffer), '>', "\012", 3, 0) <= 0) {
457
ser_send(upsfd, "\r");
462
/* Begin code stolen from bestups.c */
463
static void setup_serial(void)
467
if (tcgetattr(upsfd, &tio) == -1)
470
tio.c_iflag = IXON | IXOFF;
472
tio.c_cflag = (CS8 | CREAD | HUPCL | CLOCAL);
477
#ifdef HAVE_CFSETISPEED
478
cfsetispeed(&tio, B1200); /* baud change here */
479
cfsetospeed(&tio, B1200);
481
#error This system lacks cfsetispeed() and has no other means to set the speed
484
if (tcsetattr(upsfd, TCSANOW, &tio) == -1)
486
/* end code stolen from bestups.c */
492
These models don't support the formatconfig (fc) command so use
493
the identify command.
497
FERRUPS Uninterruptible Power System
498
By Best Power Technology, Inc.
499
Route 1 Highway 80 / P.O. Box 280
500
Necedah, WI 54646 USA
501
Sales: (800) 356-5794
502
Service: (800) 356-5737
505
Copyright (C) 1993, 1994, 1995 Best Power Technology, Inc.
509
Serial #: FE4.3K02376
514
void upsdrv_init_nofc()
516
char tmp[256], rstring[1024];
518
/* This is a Best UPS
519
* Set initial values for old Fortress???
522
/* Attempt the id command */
523
ser_send(upsfd, "id\r");
525
/* prevent upsrecv from timing out */
528
ser_get_line(upsfd, rstring, sizeof(rstring), '>', "", 3, 0);
530
rstring[sizeof(rstring)] = '\0';
531
upsdebugx(2, "id response: %s", rstring);
533
/* Better way to identify this unit is using "d 15\r", which results in
534
"15 M# MD1KVA", "id\r" yields "Unit ID "C1K03588"" */
535
if (strstr(rstring, "Unit ID \"C1K")){
537
snprintf(fc.name, sizeof(fc.name), "%s", "Micro Ferrups");
539
/* Determine load rating by Unit Id? */
540
if (strstr(rstring, "Unit ID \"C1K")) {
542
fc.watts = 770; /* Approximate, based on 0.7 power factor */
545
if (strstr(rstring, "Unit ID \"ME")){
547
snprintf(fc.name, sizeof(fc.name), "%s", "Micro Ferrups");
549
/* Determine load rating by Unit Id? */
550
if (strstr(rstring, "Unit ID \"ME3.1K")) {
555
if (strstr(rstring, "Unit ID \"FD")){
557
snprintf(fc.name, sizeof(fc.name), "%s", "Ferrups");
559
/* Determine load rating by Unit Id? */
560
if (strstr(rstring, "Unit ID \"FD4.3K")) {
565
if (strstr(rstring, "Model: FE")){
568
snprintf(fc.name, sizeof(fc.name), "%s", "Ferrups");
570
if (strlen(rstring) < 300 ) {
571
/* How does the old Fortress respond to this? */
572
upsdebugx(2, "Old Best Fortress???");
573
/* fc.model = FORTRESS; */
576
if (fc.model == UNKNOWN) {
577
upsdebugx(1, "Unknown model %s in upsdrv_init_nofc()", rstring);
585
/* determine shutdown battery voltage */
586
if (execute("d 27\r", tmp, sizeof(tmp)) > 0) {
587
sscanf(tmp, "27 LowBatt %f", &fc.emptyvolts);
589
/* determine near low battery voltage */
590
if (execute("d 30\r", tmp, sizeof(tmp)) > 0) {
591
sscanf(tmp, "30 NLBatt %f", &fc.lowvolts);
593
/* determine fully charged battery voltage */
594
if (execute("d 28\r", tmp, sizeof(tmp)) > 0) {
595
sscanf(tmp, "28 Hi Batt %f", &fc.fullvolts);
597
fc.fullvolts = 13.70;
598
/* determine "ideal" voltage by a guess */
599
fc.idealbvolts = ((fc.fullvolts - fc.emptyvolts) * 0.7) + fc.emptyvolts;
602
if (execute("d 45\r", tmp, sizeof(tmp)) > 0) {
603
sscanf(tmp, "45 RatedVA %d", &fc.va); /* 4300 */
605
if (execute("d 46\r", tmp, sizeof(tmp)) > 0) {
606
sscanf(tmp, "46 RatedW %d", &fc.watts); /* 3000 */
608
if (execute("d 65\r", tmp, sizeof(tmp)) > 0) {
609
sscanf(tmp, "65 LoBatV %f", &fc.emptyvolts); /* 41.00 */
611
if (execute("d 66\r", tmp, sizeof(tmp)) > 0) {
612
sscanf(tmp, "66 NLBatV %f", &fc.lowvolts); /* 44.00 */
614
if (execute("d 67\r", tmp, sizeof(tmp)) > 0) {
615
sscanf(tmp, "67 HiBatV %f", &fc.fullvolts); /* 59.60 */
617
fc.idealbvolts = ((fc.fullvolts - fc.emptyvolts) * 0.7) + fc.emptyvolts;
619
upsdebugx(1, "Error determining Ferrups UPS rating.");
624
upsdebugx(1, "Unknown model %s in upsdrv_init_nofc()", rstring);
632
These models support the formatconfig (fc) command
634
formatconfig (fc) response is a one-line packed string starting with $
635
Model Wt rat Vout VHi FrLo BatVN BatNLo LRuntime Model
636
| | | | | | | | | | | | | | | | | | E
637
rev VA rat Vin VLo FrN FrHi BatHi BatLo Opt O
638
|| | | | | | | | | | | | | | | | T
639
$010207010600720004701201200911446000570063000240028802150190003??????\LI????VA\\|
640
$010207010600720004701201200911446000570063000240028802150190003??????\LI720VU\LI720VU18112\|
641
0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8
642
0 5 0 5 0 5 0 5 0 5 0 5 0 5 0 5 0
644
Model: [0,1] => 00 = unk, 01 = Patriot/SPS, 02 = FortressII, 03 = Ferrups, 04 = Unity/1
645
[2,3] => 00 = LI520, 01 = LI720, 02 = LI1020, 03 = LI1420, 07 = ???
648
void upsdrv_init_fc(const char *fcstring)
652
upsdebugx(3, "fc response: %d %s", strlen(fcstring), fcstring);
655
if (memcmp(fcstring, "$", 1)) {
656
upsdebugx(1, "Bad response from formatconfig command in upsdrv_init_fc()");
659
if (memcmp(fcstring+3, "00", 2) == 0) {
660
upsdebugx(1, "ups type unknown in upsdrv_init_fc()");
664
if (memcmp(fcstring+3, "01", 2) == 0) {
665
upsdebugx(1, "Best Patriot ups not supported");
668
else if (memcmp(fcstring+3, "02", 2) == 0) {
669
snprintf(fc.name, sizeof(fc.name), "%s", "FortressII");
670
fc.type = FORTRESSII;
672
else if (memcmp(fcstring+3, "03", 2) == 0) {
673
snprintf(fc.name, sizeof(fc.name), "%s", "Ferrups");
676
else if (memcmp(fcstring+3, "04", 2) == 0) {
677
snprintf(fc.name, sizeof(fc.name), "%s", "Unity/1");
681
/* (fc.type == FORTRESSII || fc.type == FERRUPS || fc.type == UNITY1) */
682
if (memcmp(fcstring+5, "00", 2) == 0) {
683
/* fc.model = LI520; */
686
else if (memcmp(fcstring+5, "01", 2) == 0) {
687
/* fc.model = LI720; */
690
else if (memcmp(fcstring+5, "02", 2) == 0) {
691
/* fc.model = LI1020; */
694
else if (memcmp(fcstring+5, "03", 2) == 0) {
695
/* fc.model = LI1420; */
698
else if (memcmp(fcstring+71, "LI", 2) == 0) {
704
fc.va = bcd2i(&fcstring[11], 5);
705
fc.watts = bcd2i(&fcstring[16], 5);
707
/* determine shutdown battery voltage */
708
fc.emptyvolts= ((double)bcd2i(&fcstring[57], 4) / 10.0);
710
/* determine fully charged battery voltage */
711
fc.lowvolts= ((double)bcd2i(&fcstring[53], 4) / 10.0);
713
/* determine fully charged battery voltage */
714
fc.fullvolts= ((double)bcd2i(&fcstring[49], 4) / 10.0);
716
/* determine "ideal" voltage by a guess */
717
fc.idealbvolts = ((fc.fullvolts - fc.emptyvolts) * 0.7) + fc.emptyvolts;
720
upsdebugx(1, "Unknown model %s in upsdrv_init_fc()", tmp);
727
void upsdrv_initups ()
731
upsfd = ser_open(device_path);
732
ser_set_speed(upsfd, device_path, B1200);
736
experimental_driver = 1;
740
if (execute("f\r", rstring, sizeof(rstring)) < 0 ) {
741
upsdebugx(1, "Failed format request in upsdrc_initups()");
745
execute("fc\r", rstring, sizeof(rstring));
746
if (strlen(rstring) < 80 ) {
749
upsdrv_init_fc(rstring);
755
void upsdrv_cleanup(void)
757
ser_close(upsfd, device_path);