1 /* bestups.c - model specific routines for Best-UPS Fortress models
2
3 OBSOLETION WARNING: Please to not base new development on this
4 codebase, instead create a new subdriver for nutdrv_qx which
5 generally covers all Megatec/Qx protocol family and aggregates
6 device support from such legacy drivers over time.
7
8 Copyright (C) 1999 Russell Kroll <rkroll@exploits.org>
9
10 ID config option by Jason White <jdwhite@jdwhite.org>
11
12 This program is free software; you can redistribute it and/or modify
13 it under the terms of the GNU General Public License as published by
14 the Free Software Foundation; either version 2 of the License, or
15 (at your option) any later version.
16
17 This program is distributed in the hope that it will be useful,
18 but WITHOUT ANY WARRANTY; without even the implied warranty of
19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 GNU General Public License for more details.
21
22 You should have received a copy of the GNU General Public License
23 along with this program; if not, write to the Free Software
24 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25 */
26
27 #include "main.h"
28 #include "serial.h"
29
30 #define DRIVER_NAME "Best UPS driver"
31 #define DRIVER_VERSION "1.06"
32
33 /* driver description structure */
34 upsdrv_info_t upsdrv_info = {
35 DRIVER_NAME,
36 DRIVER_VERSION,
37 "Russell Kroll <rkroll@exploits.org>\n" \
38 "Jason White <jdwhite@jdwhite.org>",
39 DRV_STABLE,
40 { NULL }
41 };
42
43 #define ENDCHAR 13 /* replies end with CR */
44 #define MAXTRIES 5
45 #define UPSDELAY 50000 /* 50 ms delay required for reliable operation */
46
47 #define SER_WAIT_SEC 3 /* allow 3.0 sec for ser_get calls */
48 #define SER_WAIT_USEC 0
49
50 static float lowvolt = 0, highvolt = 0;
51 static int battvoltmult = 1;
52 static int inverted_bypass_bit = 0;
53
model_set(const char * abbr,const char * rating)54 static void model_set(const char *abbr, const char *rating)
55 {
56 if (!strncmp(abbr, "FOR", 3)) {
57 dstate_setinfo("ups.mfr", "%s", "Best Power");
58 dstate_setinfo("ups.model", "Fortress %s", rating);
59 return;
60 }
61
62 if (!strncmp(abbr, "FTC", 3)) {
63 dstate_setinfo("ups.mfr", "%s", "Best Power");
64 dstate_setinfo("ups.model", "Fortress Telecom %s", rating);
65 return;
66 }
67
68 if (!strncmp(abbr, "PRO", 3)) {
69 dstate_setinfo("ups.mfr", "%s", "Best Power");
70 dstate_setinfo("ups.model", "Patriot Pro %s", rating);
71 inverted_bypass_bit = 1;
72 return;
73 }
74
75 if (!strncmp(abbr, "PR2", 3)) {
76 dstate_setinfo("ups.mfr", "%s", "Best Power");
77 dstate_setinfo("ups.model", "Patriot Pro II %s", rating);
78 inverted_bypass_bit = 1;
79 return;
80 }
81
82 if (!strncmp(abbr, "325", 3)) {
83 dstate_setinfo("ups.mfr", "%s", "Sola Australia");
84 dstate_setinfo("ups.model", "Sola 325 %s", rating);
85 return;
86 }
87
88 if (!strncmp(abbr, "520", 3)) {
89 dstate_setinfo("ups.mfr", "%s", "Sola Australia");
90 dstate_setinfo("ups.model", "Sola 520 %s", rating);
91 return;
92 }
93
94 if (!strncmp(abbr, "610", 3)) {
95 dstate_setinfo("ups.mfr", "%s", "Best Power");
96 dstate_setinfo("ups.model", "610 %s", rating);
97 return;
98 }
99
100 if (!strncmp(abbr, "620", 3)) {
101 dstate_setinfo("ups.mfr", "%s", "Sola Australia");
102 dstate_setinfo("ups.model", "Sola 620 %s", rating);
103 return;
104 }
105
106 if (!strncmp(abbr, "AX1", 3)) {
107 dstate_setinfo("ups.mfr", "%s", "Best Power");
108 dstate_setinfo("ups.model", "Axxium Rackmount %s", rating);
109 return;
110 }
111
112 dstate_setinfo("ups.mfr", "%s", "Unknown");
113 dstate_setinfo("ups.model", "Unknown %s (%s)", abbr, rating);
114
115 printf("Unknown model detected - please report this ID: '%s'\n", abbr);
116 }
117
instcmd(const char * cmdname,const char * extra)118 static int instcmd(const char *cmdname, const char *extra)
119 {
120 if (!strcasecmp(cmdname, "test.battery.stop")) {
121 ser_send_pace(upsfd, UPSDELAY, "CT\r");
122 return STAT_INSTCMD_HANDLED;
123 }
124
125 if (!strcasecmp(cmdname, "test.battery.start")) {
126 ser_send_pace(upsfd, UPSDELAY, "T\r");
127 return STAT_INSTCMD_HANDLED;
128 }
129
130 upslogx(LOG_NOTICE, "instcmd: unknown command [%s] [%s]", cmdname, extra);
131 return STAT_INSTCMD_UNKNOWN;
132 }
133
get_ident(char * buf,size_t bufsize)134 static int get_ident(char *buf, size_t bufsize)
135 {
136 int i;
137 ssize_t ret;
138 char *ID;
139
140 ID = getval("ID"); /* user-supplied override from ups.conf */
141
142 if (ID) {
143 upsdebugx(2, "NOTE: using user-supplied ID response");
144 snprintf(buf, bufsize, "%s", ID);
145 return 1;
146 }
147
148 for (i = 0; i < MAXTRIES; i++) {
149 ser_send_pace(upsfd, UPSDELAY, "\rID\r");
150
151 ret = ser_get_line(upsfd, buf, bufsize, ENDCHAR, "",
152 SER_WAIT_SEC, SER_WAIT_USEC);
153
154 if (ret > 0)
155 upsdebugx(2, "get_ident: got [%s]", buf);
156
157 /* buf must start with ( and be in the range [25-27] */
158 if ((ret > 0) && (buf[0] != '(') && (strlen(buf) >= 25) &&
159 (strlen(buf) <= 27))
160 return 1;
161
162 sleep(1);
163 }
164
165 upslogx(LOG_INFO, "Giving up on hardware detection after %d tries",
166 MAXTRIES);
167
168 return 0;
169 }
170
ups_ident(void)171 static void ups_ident(void)
172 {
173 int i;
174 char buf[256], *ptr;
175 char *model = NULL, *rating = NULL;
176
177 if (!get_ident(buf, sizeof(buf))) {
178 fatalx(EXIT_FAILURE, "Unable to detect a Best/SOLA or Phoenix protocol UPS");
179 }
180
181 /* FOR,750,120,120,20.0,27.6 */
182 ptr = strtok(buf, ",");
183
184 for (i = 0; ptr; i++) {
185
186 switch (i)
187 {
188 case 0:
189 model = ptr;
190 break;
191
192 case 1:
193 rating = ptr;
194 break;
195
196 case 2:
197 dstate_setinfo("input.voltage.nominal", "%d", atoi(ptr));
198 break;
199
200 case 3:
201 dstate_setinfo("output.voltage.nominal", "%d", atoi(ptr));
202 break;
203
204 case 4:
205 lowvolt = atof(ptr);
206 break;
207
208 case 5:
209 highvolt = atof(ptr);
210 break;
211 }
212
213 ptr = strtok(NULL, ",");
214 }
215
216 if ((!model) || (!rating)) {
217 fatalx(EXIT_FAILURE, "Didn't get a valid ident string");
218 }
219
220 model_set(model, rating);
221
222 /* Battery voltage multiplier */
223 ptr = getval("battvoltmult");
224
225 if (ptr) {
226 battvoltmult = atoi(ptr);
227 }
228
229 /* Lookup the nominal battery voltage (should be between lowvolt and highvolt */
230 for (i = 0; i < 8; i++) {
231 const int nominal[] = { 2, 6, 12, 24, 36, 48, 72, 96 };
232
233 if ((lowvolt < nominal[i]) && (highvolt > nominal[i])) {
234 dstate_setinfo("battery.voltage.nominal", "%d", battvoltmult * nominal[i]);
235 break;
236 }
237 }
238
239 ptr = getval("nombattvolt");
240
241 if (ptr) {
242 highvolt = atof(ptr);
243 }
244 }
245
ups_sync(void)246 static void ups_sync(void)
247 {
248 char buf[256];
249 int i;
250 ssize_t ret;
251
252 for (i = 0; i < MAXTRIES; i++) {
253 ser_send_pace(upsfd, UPSDELAY, "\rQ1\r");
254
255 ret = ser_get_line(upsfd, buf, sizeof(buf), ENDCHAR, "",
256 SER_WAIT_SEC, SER_WAIT_USEC);
257
258 /* return once we get something that looks usable */
259 if ((ret > 0) && (buf[0] == '('))
260 return;
261
262 usleep(250000);
263 }
264
265 fatalx(EXIT_FAILURE, "Unable to detect a Best/SOLA or Phoenix protocol UPS");
266 }
267
upsdrv_initinfo(void)268 void upsdrv_initinfo(void)
269 {
270 ups_sync();
271 ups_ident();
272
273 printf("Detected %s %s on %s\n", dstate_getinfo("ups.mfr"),
274 dstate_getinfo("ups.model"), device_path);
275
276 /* paranoia - cancel any shutdown that might already be running */
277 ser_send_pace(upsfd, UPSDELAY, "C\r");
278
279 upsh.instcmd = instcmd;
280
281 dstate_addcmd("test.battery.start");
282 dstate_addcmd("test.battery.stop");
283 }
284
ups_on_line(void)285 static int ups_on_line(void)
286 {
287 int i;
288 ssize_t ret;
289 char temp[256], pstat[32];
290
291 for (i = 0; i < MAXTRIES; i++) {
292 ser_send_pace(upsfd, UPSDELAY, "\rQ1\r");
293
294 ret = ser_get_line(upsfd, temp, sizeof(temp), ENDCHAR, "",
295 SER_WAIT_SEC, SER_WAIT_USEC);
296
297 /* Q1 must return 46 bytes starting with a ( */
298 if ((ret > 0) && (temp[0] == '(') && (strlen(temp) == 46)) {
299
300 sscanf(temp, "%*s %*s %*s %*s %*s %*s %*s %s", pstat);
301
302 if (pstat[0] == '0')
303 return 1; /* on line */
304
305 return 0; /* on battery */
306 }
307
308 sleep(1);
309 }
310
311 upslogx(LOG_ERR, "Status read failed: assuming on battery");
312
313 return 0; /* on battery */
314 }
315
upsdrv_shutdown(void)316 void upsdrv_shutdown(void)
317 {
318 printf("The UPS will shut down in approximately one minute.\n");
319
320 if (ups_on_line())
321 printf("The UPS will restart in about one minute.\n");
322 else
323 printf("The UPS will restart when power returns.\n");
324
325 ser_send_pace(upsfd, UPSDELAY, "S01R0001\r");
326 }
327
upsdrv_updateinfo(void)328 void upsdrv_updateinfo(void)
329 {
330 char involt[16], outvolt[16], loadpct[16], acfreq[16],
331 battvolt[16], upstemp[16], pstat[16], buf[256];
332 float bvoltp;
333 ssize_t ret;
334
335 ret = ser_send_pace(upsfd, UPSDELAY, "\rQ1\r");
336
337 if (ret < 1) {
338 ser_comm_fail("ser_send_pace failed");
339 dstate_datastale();
340 return;
341 }
342
343 /* these things need a long time to respond completely */
344 usleep(200000);
345
346 ret = ser_get_line(upsfd, buf, sizeof(buf), ENDCHAR, "",
347 SER_WAIT_SEC, SER_WAIT_USEC);
348
349 if (ret < 1) {
350 ser_comm_fail("Poll failed: %s", ret ? strerror(errno) : "timeout");
351 dstate_datastale();
352 return;
353 }
354
355 if (ret < 46) {
356 ser_comm_fail("Poll failed: short read (got %zd bytes)", ret);
357 dstate_datastale();
358 return;
359 }
360
361 if (ret > 46) {
362 ser_comm_fail("Poll failed: response too long (got %zd bytes)",
363 ret);
364 dstate_datastale();
365 return;
366 }
367
368 if (buf[0] != '(') {
369 ser_comm_fail("Poll failed: invalid start character (got %02x)",
370 buf[0]);
371 dstate_datastale();
372 return;
373 }
374
375 ser_comm_good();
376
377 sscanf(buf, "%*c%s %*s %s %s %s %s %s %s", involt, outvolt,
378 loadpct, acfreq, battvolt, upstemp, pstat);
379
380 /* Guesstimation of battery charge left (inaccurate) */
381 bvoltp = 100 * (atof(battvolt) - lowvolt) / (highvolt - lowvolt);
382
383 if (bvoltp > 100) {
384 bvoltp = 100;
385 }
386
387 dstate_setinfo("battery.voltage", "%.1f", battvoltmult * atof(battvolt));
388 dstate_setinfo("input.voltage", "%s", involt);
389 dstate_setinfo("output.voltage", "%s", outvolt);
390 dstate_setinfo("ups.load", "%s", loadpct);
391 dstate_setinfo("input.frequency", "%s", acfreq);
392
393 if(upstemp[0] != 'X') {
394 dstate_setinfo("ups.temperature", "%s", upstemp);
395 }
396
397 dstate_setinfo("battery.charge", "%02.1f", bvoltp);
398
399 status_init();
400
401 if (pstat[0] == '0') {
402 status_set("OL"); /* on line */
403
404 /* only allow these when OL since they're bogus when OB */
405
406 if (pstat[2] == (inverted_bypass_bit ? '0' : '1')) {
407 /* boost or trim in effect */
408 if (atof(involt) < atof(outvolt))
409 status_set("BOOST");
410
411 if (atof(involt) > atof(outvolt))
412 status_set("TRIM");
413 }
414
415 } else {
416 status_set("OB"); /* on battery */
417 }
418
419 if (pstat[1] == '1')
420 status_set("LB"); /* low battery */
421
422 status_commit();
423 dstate_dataok();
424 }
425
upsdrv_help(void)426 void upsdrv_help(void)
427 {
428 }
429
upsdrv_makevartable(void)430 void upsdrv_makevartable(void)
431 {
432 addvar(VAR_VALUE, "nombattvolt", "Override nominal battery voltage");
433 addvar(VAR_VALUE, "battvoltmult", "Battery voltage multiplier");
434 addvar(VAR_VALUE, "ID", "Force UPS ID response string");
435 }
436
upsdrv_initups(void)437 void upsdrv_initups(void)
438 {
439 upsfd = ser_open(device_path);
440 ser_set_speed(upsfd, device_path, B2400);
441 }
442
upsdrv_cleanup(void)443 void upsdrv_cleanup(void)
444 {
445 ser_close(upsfd, device_path);
446 }
447