1 /* upscode2.c - model specific routines for UPSes using the UPScode II
2 command set. This includes PowerWare, Fiskars,
3 Compaq (PowerWare OEM?), some IBM (PowerWare OEM?)
4
5 Copyright (C) 2002 H?vard Lygre <hklygre@online.no>
6 Copyright (C) 2004-2006 Niels Baggesen <niels@baggesen.net>
7 Copyright (C) 2006 Niklas Edmundsson <nikke@acc.umu.se>
8
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 2 of the License, or
12 (at your option) any later version.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22
23 */
24
25 /*
26 * Currently testing against
27 * Fiskars PowerRite Max
28 * Fiskars PowerServer 10
29 * Fiskars PowerServer 30
30 * Powerware Profile (9150)
31 * Powerware 9305
32 *
33 * Also tested against
34 * Compaq T1500h (Per J?nsson <per.jonsson@bth.se>)
35 * Powerware 9120 (Gorm J. Siiger <gjs@sonnit.dk>)
36 * Fiskars PowerServer 10 (Per Larsson <tucker@algonet.se>)
37 */
38
39 #include "main.h"
40 #include "serial.h"
41 #include "timehead.h"
42 #include "nut_stdint.h"
43
44 #include <math.h>
45
46 #define DRIVER_NAME "UPScode II UPS driver"
47 #define DRIVER_VERSION "0.89"
48
49 /* driver description structure */
50 upsdrv_info_t upsdrv_info = {
51 DRIVER_NAME,
52 DRIVER_VERSION,
53 "H K Lygre, <hklygre@online.no>\n" \
54 "Niels Baggesen <niels@baggesen.net>\n" \
55 "Niklas Edmundsson <nikke@acc.umu.se>",
56 DRV_EXPERIMENTAL,
57 { NULL }
58 };
59
60 #define ENDCHAR '\n'
61
62 /* default values */
63 #define OUT_PACE_USEC 200
64 #define INP_TIMO_SEC 2
65 #define FULL_UPDATE_TIMER 60
66
67 #define UPSC_BUFLEN 256 /* Size of response buffers from UPS */
68
69 /* Status messages from UPS */
70 #define UPSC_STAT_ONLINE (1UL << 0)
71 #define UPSC_STAT_ONBATT (1UL << 1)
72 #define UPSC_STAT_LOBATT (1UL << 2)
73 #define UPSC_STAT_REPLACEBATT (1UL << 3)
74 #define UPSC_STAT_BOOST (1UL << 4)
75 #define UPSC_STAT_TRIM (1UL << 5)
76 #define UPSC_STAT_OVERLOAD (1UL << 6)
77 #define UPSC_STAT_CALIBRATION (1UL << 7)
78 #define UPSC_STAT_OFF (1UL << 8)
79 #define UPSC_STAT_BYPASS (1UL << 9)
80 #define UPSC_STAT_NOTINIT (1UL << 31)
81
82
83 typedef enum {
84 t_ignore, /* Ignore this response */
85 t_value, /* Sets a NUT variable */
86 t_final, /* Marks the end of UPS data for this command */
87 t_string, /* Set a NUT string variable */
88 t_finstr, /* Set a NUT string variable, and marks end of data */
89 t_setval, /* Sets a variable in the driver and possibly in NUT */
90 t_setrecip, /* Sets a driver variable to 1/value and possibly NUT */
91 t_setpct,
92 t_setrecpct,
93 t_status, /* Sets a status bit */
94 t_alarm, /* Sets an alarm message */
95 t_list /* The value must be matched in the list */
96 } type_t;
97
98
99 typedef struct simple_s {
100 const char *code;
101 type_t type;
102 const char *desc;
103 int status;
104 float *aux;
105 struct simple_s *stats;
106 } simple_t;
107
108 typedef struct {
109 const char *cmd;
110 const char *upsc;
111 const char *upsp;
112 int enabled;
113 } cmd_t;
114
115
116 static int
117 can_upda = 0,
118 can_upbs = 0,
119 can_upid = 0,
120 can_uppm = 0,
121 can_updt = 0,
122 can_uptm = 0,
123 can_upsd = 0,
124 can_uppc = 0;
125
126 static char has_uppm_p[100];
127
128 static int
129 input_timeout_sec = INP_TIMO_SEC,
130 output_pace_usec = OUT_PACE_USEC,
131 full_update_timer = FULL_UPDATE_TIMER,
132 use_crlf = 0,
133 use_pre_lf = 0,
134 buffer_empty = 0;
135
136 static uint32_t
137 status = UPSC_STAT_NOTINIT;
138
139 static time_t
140 last_full = 0;
141
142 static float
143 batt_volt_low = 0,
144 batt_volt_nom = 0,
145 batt_volt_high = 0,
146 batt_volt = 0,
147 batt_cap_nom = -1,
148 batt_charge = -1,
149 batt_current = -1,
150 batt_disch_curr_max = 0,
151 batt_runtime = -1,
152 batt_runtime_max = -1,
153 kilo_to_unity = 1000.0,
154 outpwr_factor = 1000.0,
155 nom_out_current = -1,
156 max_out_current = -1;
157
158 /* To get average battery current */
159 #define NUM_BATTHIST 60
160 static float batthist[NUM_BATTHIST];
161 static int numbatthist=0, lastbatthist=0;
162
163 static int
164 inited_phaseinfo = 0,
165 num_inphases = 1,
166 num_outphases = 1;
167
168
169 /* Status codes for the STAT and STMF status responses */
170 static simple_t att[] = {
171 { "00", t_ignore },
172 { "AC", t_alarm, "Aux contact failure", 0 },
173 { "BA", t_alarm, "Batteries disconnected", 0 },
174 { "BC", t_alarm, "Backfeed contact failure", 0 },
175 { "BD", t_alarm, "Abnormal battery discharge", 0 },
176 { "BF", t_alarm, "Battery fuse failure", 0 },
177 { "BL", t_status, "Battery low limit", UPSC_STAT_LOBATT },
178 { "BO", t_alarm, "Battery over voltage", 0 },
179 { "BP", t_alarm, "Bypass fuse failure", 0 },
180 { "BR", t_alarm, "Abnormal battery recharge", 0 },
181 { "BT", t_alarm, "Battery over temperature", 0 },
182 { "BX", t_alarm, "Bypass unavailable", 0 },
183 { "BY", t_alarm, "Battery failure", 0 },
184 { "CE", t_alarm, "Configuration error", 0 },
185 { "CM", t_alarm, "Battery converter failure", 0 },
186 { "CT", t_alarm, "Cabinet over temperature", 0 },
187 { "DO", t_alarm, "DC over voltage", 0 },
188 { "DU", t_alarm, "DC under voltage", 0 },
189 { "EP", t_alarm, "Emergency power off", 0 },
190 { "FF", t_alarm, "Fan failure", 0 },
191 { "FH", t_alarm, "Line frequency high", 0 },
192 { "FL", t_alarm, "Line frequency low", 0 },
193 { "FT", t_alarm, "Filter over temperature", 0 },
194 { "GF", t_alarm, "Ground failure", 0 },
195 { "HT", t_alarm, "Charger over temperature", 0 },
196 { "IB", t_alarm, "Internal data bus failure", 0 },
197 { "IF", t_alarm, "Inverter fuse failure", 0 },
198 { "IM", t_alarm, "Inverter failure", 0 },
199 { "IO", t_alarm, "Inverter over voltage", 0 },
200 { "IP", t_alarm, "Internal power supply failure", 0 },
201 { "IT", t_alarm, "Inverter over temperature", 0 },
202 { "IU", t_alarm, "Inverter under voltage", 0 },
203 { "IV", t_alarm, "Inverter off", 0 },
204 { "LR", t_alarm, "Loss of redundancy", 0 },
205 { "NF", t_alarm, "Neutral fault", 0 },
206 { "OD", t_status, "UPS not supplying load", UPSC_STAT_OFF },
207 { "OF", t_alarm, "Oscillator failure", 0 },
208 { "OL", t_status, "Overload", UPSC_STAT_OVERLOAD },
209 { "OR", t_alarm, "Redundancy overload", 0 },
210 { "OV", t_alarm, "Abnormal output voltage", 0 },
211 { "OW", t_alarm, "Output failure", 0 },
212 { "PB", t_alarm, "Parallel bus failure", 0 },
213 { "PE", t_alarm, "Phase rotation error", 0 },
214 { "RE", t_alarm, "Rectifier off", 0 },
215 { "RF", t_alarm, "Rectifier fuse failure", 0 },
216 { "RM", t_alarm, "Rectifier failure", 0 },
217 { "RT", t_alarm, "Rectifier over temperature", 0 },
218 { "SM", t_alarm, "Static switch failure", 0 },
219 { "ST", t_alarm, "Static switch over temperature", 0 },
220 { "TT", t_alarm, "Trafo over temperature", 0 },
221 { "UD", t_alarm, "UPS disabled", 0 },
222 { "UO", t_alarm, "Utility over voltage", 0 },
223 { "US", t_alarm, "Unsynchronized", 0 },
224 { "UU", t_alarm, "Utility under voltage", 0 },
225 { "VE", t_alarm, "internal voltage error", 0 },
226 { NULL }
227 };
228
229
230 /* Status code for the STLR response */
231 static simple_t stlr[] = {
232 { "NO", t_ignore },
233 { "SD", t_status, NULL, UPSC_STAT_TRIM },
234 { "SU", t_status, NULL, UPSC_STAT_BOOST },
235 { "DU", t_status, NULL, UPSC_STAT_BOOST },
236 { NULL }
237 };
238
239
240 /* Status code for the STEA and STEM responses */
241 static simple_t env[] = {
242 { "HH", t_ignore, "Humidity high", 0 },
243 { "HL", t_ignore, "Humidity low", 0 },
244 { "TH", t_ignore, "Temperature high", 0 },
245 { "TL", t_ignore, "Temperature low", 0 },
246 { "01", t_ignore, "Environment alarm 1", 0 },
247 { "02", t_ignore, "Environment alarm 2", 0 },
248 { "03", t_ignore, "Environment alarm 3", 0 },
249 { "04", t_ignore, "Environment alarm 4", 0 },
250 { "05", t_ignore, "Environment alarm 5", 0 },
251 { "06", t_ignore, "Environment alarm 6", 0 },
252 { "07", t_ignore, "Environment alarm 7", 0 },
253 { "08", t_ignore, "Environment alarm 8", 0 },
254 { "09", t_ignore, "Environment alarm 9", 0 },
255 { NULL }
256 };
257
258
259 /* Responses for UPSS and UPDS */
260 static simple_t simple[] = {
261 { "STAT", t_list, NULL, 0, 0, att },
262 { "STBO", t_status, NULL, UPSC_STAT_ONBATT },
263 { "STBL", t_status, NULL, UPSC_STAT_LOBATT },
264 { "STBM", t_ignore },
265 { "STBP", t_status, NULL, UPSC_STAT_BYPASS },
266 { "STEA", t_list, NULL, 0, 0, env },
267 { "STEM", t_list, NULL, 0, 0, env },
268 { "STLR", t_list, NULL, 0, 0, stlr },
269 { "STMF", t_list, NULL, 0, 0, att },
270 { "STOK", t_ignore },
271 { "STUF", t_status, NULL, UPSC_STAT_ONBATT },
272
273 { "BTIME", t_setval, NULL, 0, &batt_runtime },
274
275 { "METE1", t_value, "ambient.temperature" },
276 { "MERH1", t_value, "ambient.humidity" },
277
278 { "MIFFF", t_value, "input.frequency" },
279 { "MIIL1", t_value, "input.current" },
280 { "MIIL2", t_value, "input.L2.current" },
281 { "MIIL3", t_value, "input.L3.current" },
282 { "MIPL1", t_value, "input.realpower" },
283 { "MIPL2", t_value, "input.L2.realpower" },
284 { "MIPL3", t_value, "input.L3.realpower" },
285 { "MISL1", t_value, "input.power" },
286 { "MISL2", t_value, "input.L2.power" },
287 { "MISL3", t_value, "input.L3.power" },
288 { "MIUL1", t_value, "input.voltage" },
289 { "MIUL2", t_value, "input.L2-N.voltage" },
290 { "MIUL3", t_value, "input.L3-N.voltage" },
291 { "MIU12", t_value, "input.L1-L2.voltage" },
292 { "MIU23", t_value, "input.L2-L3.voltage" },
293 { "MIU31", t_value, "input.L3-L1.voltage" },
294
295 { "MBCH1", t_setval, NULL, 0, &batt_charge }, /* battery.charge */
296 { "MBIII", t_setval, "battery.current", 0, &batt_current },
297 { "MBINE", t_ignore, /* "battery.current.negative" */ },
298 { "MBIPO", t_ignore, /* "battery.current.positive" */ },
299 { "MBUNE", t_ignore, /* "battery.voltage.negative" */ },
300 { "MBUPO", t_ignore, /* "battery.voltage.positive" */},
301 { "MBUUU", t_setval, "battery.voltage", 0, &batt_volt },
302
303 { "MLUNE", t_ignore, /* "dc.voltage.negative" */ },
304 { "MLUPO", t_ignore, /* "dc.voltage.positive" */ },
305 { "MLUUU", t_ignore, /* "dc.voltage" */ },
306
307 { "MOFFF", t_final, "output.frequency" },
308 { "MOIL1", t_value, "output.current" },
309 { "MOIL2", t_value, "output.L2.current" },
310 { "MOIL3", t_value, "output.L3.current" },
311 { "MOIP1", t_value, "output.current.peak" },
312 { "MOIP2", t_value, "output.L2.current.peak" },
313 { "MOIP3", t_value, "output.L3.current.peak" },
314 { "MOPL1", t_value, "output.realpower", 0, &kilo_to_unity },
315 { "MOPL2", t_value, "output.L2.realpower", 0, &kilo_to_unity },
316 { "MOPL3", t_value, "output.L3.realpower", 0, &kilo_to_unity },
317 { "MOSL1", t_value, "output.power" },
318 { "MOSL2", t_value, "output.L2.power" },
319 { "MOSL3", t_value, "output.L3.power" },
320 { "MOUL1", t_value, "output.voltage" },
321 { "MOUL2", t_value, "output.L2-N.voltage" },
322 { "MOUL3", t_value, "output.L3-N.voltage" },
323 { "MOU12", t_value, "output.L1-L2.voltage" },
324 { "MOU23", t_value, "output.L2-L3.voltage" },
325 { "MOU31", t_value, "output.L3-L1.voltage" },
326
327 { "MPUL1", t_value, "input.bypass.L1-N.voltage" },
328 { "MPUL2", t_value, "input.bypass.L2-N.voltage" },
329 { "MPUL3", t_value, "input.bypass.L3-N.voltage" },
330
331 { "MUTE1", t_value, "ups.temperature" },
332 { NULL }
333 };
334
335
336 /* Responses for UPDV */
337 static simple_t nominal[] = {
338 { "NIUHH", t_value, "input.voltage.maximum" },
339 { "NIULL", t_value, "input.voltage.minimum" },
340 { "NIUNN", t_value, "input.voltage.nominal" },
341
342 { "NIIHH", t_value, "input.current.maximum" },
343 { "NIILL", t_value, "input.current.minimum" },
344 { "NIINN", t_value, "input.current.nominal" },
345
346 { "NIPHH", t_value, "input.realpower.maximum" },
347 { "NIPNN", t_value, "input.realpower.nominal" },
348
349 { "NISHH", t_value, "input.power.maximum" },
350 { "NISNN", t_value, "input.power.nominal" },
351
352 { "NBAHN", t_setval, "battery.capacity.nominal", 0, &batt_cap_nom },
353 { "NBIHH", t_ignore, "battery charge current maximum" },
354 { "NBILL", t_setval, NULL, 0, &batt_disch_curr_max},
355 { "NBINN", t_value, "battery.current.nominal" },
356 { "NBTHH", t_setval, NULL, 0, &batt_runtime_max},
357 { "NBUHH", t_setval, "battery.voltage.maximum", 0, &batt_volt_high },
358 { "NBULL", t_setval, "battery.voltage.minimum", 0, &batt_volt_low },
359 { "NBUNN", t_setval, "battery.voltage.nominal", 0, &batt_volt_nom },
360
361 { "NOFHH", t_value, "output.frequency.maximum" },
362 { "NOFLL", t_final, "output.frequency.minimum" },
363 { "NOIHH", t_setval, "output.current.maximum", 0, &max_out_current },
364 { "NOINN", t_setval, "output.current.nominal", 0, &nom_out_current },
365 { "NOPNN", t_value, "output.realpower.nominal", 0, &outpwr_factor },
366 { "NOSNN", t_value, "ups.power.nominal", 0, &outpwr_factor },
367 { "NOUHH", t_value, "output.voltage.maximum" },
368 { "NOULL", t_value, "output.voltage.minimum" },
369 { "NOUNN", t_value, "output.voltage.nominal" },
370
371 { "NUTEH", t_value, "ups.temperature.maximum" },
372 { NULL }
373 };
374
375
376 /* Status responses for UPBS command */
377 static simple_t battery[] = {
378 { "MBTE1", t_value, "battery.1.temperature" },
379 { "MBIN1", t_ignore, NULL /* aging index */ },
380 { "BDAT1", t_string, "battery.1.date" },
381 { "MBTE2", t_value, "battery.2.temperature.2" },
382 { "MBIN2", t_ignore, NULL },
383 { "BDAT2", t_string, "battery.2.date" },
384 { "MBTE3", t_value, "battery.3.temperature" },
385 { "MBIN3", t_ignore, NULL },
386 { "BDAT3", t_string, "battery.3.date" },
387 { "MBTE4", t_value, "battery.4.temperature" },
388 { "MBIN4", t_ignore, NULL },
389 { "BDAT4", t_string, "battery.4.date" },
390 { "MBTE5", t_value, "battery.5.temperature" },
391 { "MBIN5", t_ignore, NULL },
392 { "BDAT5", t_string, "battery.5.date" },
393 { "MBTE6", t_value, "battery.6.temperature" },
394 { "MBIN6", t_ignore, NULL },
395 { "BDAT6", t_string, "battery.6.date" },
396 { "MBTE7", t_value, "battery.7.temperature" },
397 { "MBIN7", t_ignore, NULL },
398 { "BDAT7", t_string, "battery.7.date" },
399 { "MBTE8", t_value, "battery.8.temperature" },
400 { "MBIN8", t_ignore, NULL },
401 { "BDAT8", t_finstr, "battery.8.date" },
402 { NULL }
403 };
404
405
406 static cmd_t commands[] = {
407 { "load.off", NULL, NULL },
408 { "load.on", NULL, NULL },
409 { "shutdown.return", "UPPF", "IJHLDMGCIU" },
410 { "shutdown.stayoff", "UPPD", "LGGNLMDPGV" },
411 { "shutdown.stop", "UPPU", NULL },
412 { "shutdown.reboot", "UPPC", "IJHLDMGCIU" },
413 { "shutdown.reboot.graceful", NULL, NULL },
414 { "test.panel.start", "UPIS", NULL },
415 { "test.panel.stop", NULL, NULL },
416 { "test.failure.start", NULL, NULL },
417 { "test.failure.stop", NULL, NULL },
418 { "test.battery.start", "UPBT", "1" },
419 { "test.battery.stop", NULL, NULL },
420 { "calibrate.start", NULL, NULL },
421 { "calibrate.stop", NULL, NULL },
422 { "bypass.start", NULL, NULL },
423 { "bypass.stop", NULL, NULL },
424 { "reset.input.minmax", NULL, NULL },
425 { "reset.watchdog", NULL, NULL },
426 { "beeper.enable", NULL, NULL },
427 { "beeper.disable", NULL, NULL },
428 { "beeper.on", NULL, NULL },
429 { "beeper.off", NULL, NULL },
430 { NULL }
431 };
432
433
434 static cmd_t variables[] = {
435 { "ups.delay.reboot", "UPCD", "ACCD" },
436 { "ups.delay.shutdown", "UPSD", "ACSD" },
437 { NULL }
438 };
439
440
441 static int instcmd (const char *auxcmd, const char *data);
442 static int setvar (const char *var, const char *data);
443 static void upsc_setstatus(unsigned int status);
444 static void upsc_flush_input(void);
445 static void upsc_getbaseinfo(void);
446 static int upsc_commandlist(void);
447 static int upsc_getparams(const char *cmd, const simple_t *table);
448 static int upsc_getvalue(const char *cmd, const char *param,
449 const char *resp, const char *var, char *ret);
450 static int upscsend(const char *cmd);
451 static int upscrecv(char *buf);
452 static int upsc_simple(const simple_t *sp, const char *var, const char *val);
453 static void check_uppm(void);
454 static float batt_charge_pct(void);
455
456
457
upsdrv_help(void)458 void upsdrv_help(void)
459 {
460 }
461
462
upsdrv_initups(void)463 void upsdrv_initups(void)
464 {
465 struct termios tio;
466 int baud = B1200;
467 char *str;
468
469 if ((str = getval("baudrate")) != NULL) {
470 int temp = atoi(str);
471 switch (temp) {
472 case 300:
473 baud = B300; break;
474 case 600:
475 baud = B600; break;
476 case 1200:
477 baud = B1200; break;
478 case 2400:
479 baud = B2400; break;
480 case 4800:
481 baud = B4800; break;
482 case 9600:
483 baud = B9600; break;
484 case 19200:
485 baud = B19200; break;
486 case 38400:
487 baud = B38400; break;
488 default:
489 fatalx(EXIT_FAILURE, "Unrecognized baudrate: %s", str);
490 }
491 upsdebugx(1, "baud_rate = %d", temp);
492 }
493 upsfd = ser_open(device_path);
494 ser_set_speed(upsfd, device_path, baud);
495
496 if (tcgetattr(upsfd, &tio) != 0)
497 fatal_with_errno(EXIT_FAILURE, "tcgetattr(%s)", device_path);
498 tio.c_lflag = ICANON;
499 tio.c_iflag |= IGNCR; /* Ignore CR */
500 tio.c_cc[VMIN] = 0;
501 tio.c_cc[VTIME] = 0;
502 tcsetattr(upsfd, TCSANOW, &tio);
503
504 if ((str = getval("input_timeout")) != NULL) {
505 int temp = atoi(str);
506 if (temp <= 0)
507 fatalx(EXIT_FAILURE, "Bad input_timeout parameter: %s", str);
508 input_timeout_sec = temp;
509 }
510 upsdebugx(1, "input_timeout = %d Sec", input_timeout_sec);
511
512 if ((str = getval("output_pace")) != NULL) {
513 int temp = atoi(str);
514 if (temp <= 0)
515 fatalx(EXIT_FAILURE, "Bad output_pace parameter: %s", str);
516 output_pace_usec = temp;
517 }
518 upsdebugx(1, "output_pace = %d uSec", output_pace_usec);
519
520 if ((str = getval("full_update_timer")) != NULL) {
521 int temp = atoi(str);
522 if (temp <= 0)
523 fatalx(EXIT_FAILURE, "Bad full_update_timer parameter: %s", str);
524 full_update_timer = temp;
525 }
526 upsdebugx(1, "full_update_timer = %d Sec", full_update_timer);
527
528 use_crlf = testvar("use_crlf");
529 upsdebugx(1, "use_crlf = %d", use_crlf);
530 use_pre_lf = testvar("use_pre_lf");
531 upsdebugx(1, "use_pre_lf = %d", use_pre_lf);
532 }
533
534
upsdrv_initinfo(void)535 void upsdrv_initinfo(void)
536 {
537 if (!upsc_commandlist()) {
538 upslogx(LOG_ERR, "No contact with UPS, delaying init.");
539 status = UPSC_STAT_NOTINIT;
540 return;
541 } else {
542 status = 0;
543 }
544
545 upsc_getbaseinfo();
546 if (can_upda) {
547 upsc_flush_input();
548 upscsend("UPDA");
549 }
550 if (can_upid) {
551 upsc_getvalue("UPID", NULL, "ACID", "ups.id", NULL);
552 }
553 if (can_uppm) {
554 check_uppm();
555 }
556
557 /* make sure we have some sensible defaults */
558 setvar("ups.delay.shutdown", "10");
559 setvar("ups.delay.reboot", "60");
560
561 upsh.instcmd = instcmd;
562 upsh.setvar = setvar;
563 }
564
565
566 /* Change a variable name in a table */
change_name(simple_t * sp,const char * oldname,const char * newname)567 static void change_name(simple_t *sp,
568 const char *oldname, const char *newname)
569 {
570 while(sp->code) {
571 if (sp->desc && !strcmp(sp->desc, oldname)) {
572 sp->desc = strdup(newname);
573 if (dstate_getinfo(oldname)) {
574 dstate_setinfo(newname, "%s",
575 dstate_getinfo(oldname));
576 }
577 dstate_delinfo(oldname);
578 upsdebugx(1, "Changing name: %s => %s", oldname, newname);
579 break;
580 }
581 sp++;
582 }
583 }
584
calc_upsload(void)585 static float calc_upsload(void) {
586
587 float load=-1, nom_out_power=-1, nom_out_realpower=-1, maxcurr, tmp;
588 const char *s;
589
590 /* Some UPSen (Fiskars 9000 for example) only reports current, and
591 * only the max current */
592 if (nom_out_current > 0) {
593 maxcurr = nom_out_current;
594 }
595 else {
596 maxcurr = max_out_current;
597 }
598
599 if (maxcurr > 0) {
600 if ((s=dstate_getinfo("output.L1.current")) ||
601 (s=dstate_getinfo("output.current"))) {
602 if (sscanf(s, "%f", &tmp) == 1) {
603 load = tmp/maxcurr;
604 }
605 }
606 if ((s=dstate_getinfo("output.L2.current"))) {
607 if (sscanf(s, "%f", &tmp) == 1) {
608 tmp=tmp/maxcurr;
609 if (tmp>load) {
610 load = tmp;
611 }
612 }
613 }
614 if ((s=dstate_getinfo("output.L3.current"))) {
615 if (sscanf(s, "%f", &tmp) == 1) {
616 tmp=tmp/maxcurr;
617 if (tmp>load) {
618 load = tmp;
619 }
620 }
621 }
622 }
623
624 /* This is aggregated (all phases) */
625 if ((s=dstate_getinfo("ups.power.nominal"))) {
626 if (sscanf(s, "%f", &nom_out_power) != 1) {
627 nom_out_power = -1;
628 }
629 }
630
631 if (nom_out_power > 0) {
632 if ((s=dstate_getinfo("output.L1.power"))) {
633 if (sscanf(s, "%f", &tmp) == 1) {
634 tmp /= (nom_out_power/num_outphases);
635 if (tmp>load) {
636 load = tmp;
637 }
638 dstate_setinfo("output.L1.power.percent",
639 "%.1f", tmp*100);
640 }
641 }
642 if ((s=dstate_getinfo("output.L2.power"))) {
643 if (sscanf(s, "%f", &tmp) == 1) {
644 tmp /= (nom_out_power/num_outphases);
645 if (tmp>load) {
646 load = tmp;
647 }
648 dstate_setinfo("output.L2.power.percent",
649 "%.1f", tmp*100);
650 }
651 }
652 if ((s=dstate_getinfo("output.L3.power"))) {
653 if (sscanf(s, "%f", &tmp) == 1) {
654 tmp /= (nom_out_power/num_outphases);
655 if (tmp>load) {
656 load = tmp;
657 }
658 dstate_setinfo("output.L3.power.percent",
659 "%.1f", tmp*100);
660 }
661 }
662 }
663
664 /* This is aggregated (all phases) */
665 if ((s=dstate_getinfo("output.realpower.nominal"))) {
666 if (sscanf(s, "%f", &nom_out_realpower) != 1) {
667 nom_out_realpower = -1;
668 }
669 }
670 if (nom_out_realpower >= 0) {
671 if ((s=dstate_getinfo("output.L1.realpower"))) {
672 if (sscanf(s, "%f", &tmp) == 1) {
673 tmp /= (nom_out_realpower/num_outphases);
674 if (tmp>load) {
675 load = tmp;
676 }
677 dstate_setinfo("output.L1.realpower.percent",
678 "%.1f", tmp*100);
679 }
680 }
681 if ((s=dstate_getinfo("output.L2.realpower"))) {
682 if (sscanf(s, "%f", &tmp) == 1) {
683 tmp /= (nom_out_realpower/num_outphases);
684 if (tmp>load) {
685 load = tmp;
686 }
687 dstate_setinfo("output.L2.realpower.percent",
688 "%.1f", tmp*100);
689 }
690 }
691 if ((s=dstate_getinfo("output.L3.realpower"))) {
692 if (sscanf(s, "%f", &tmp) == 1) {
693 tmp /= (nom_out_realpower/num_outphases);
694 if (tmp>load) {
695 load = tmp;
696 }
697 dstate_setinfo("output.L3.realpower.percent",
698 "%.1f", tmp*100);
699 }
700 }
701 }
702
703 return load;
704 }
705
706
upsdrv_updateinfo(void)707 void upsdrv_updateinfo(void)
708 {
709 time_t now;
710 int ok;
711 float load;
712
713 if (status & UPSC_STAT_NOTINIT) {
714 upsdrv_initinfo();
715 }
716
717 if (status & UPSC_STAT_NOTINIT) {
718 return;
719 }
720
721 status = 0;
722
723 ok = upsc_getparams("UPDS", simple);
724
725 time(&now);
726 if (ok && now - last_full > full_update_timer) {
727 last_full = now;
728 ok = upsc_getparams("UPDV", nominal);
729 if (ok && can_upbs)
730 ok = upsc_getparams("UPBS", battery);
731 }
732
733 if (!ok) {
734 dstate_datastale();
735 last_full = 0;
736 return;
737 }
738
739 if (!inited_phaseinfo) {
740 if (dstate_getinfo("input.L3-L1.voltage") ||
741 dstate_getinfo("input.L3-N.voltage")) {
742 num_inphases = 3;
743
744 change_name(simple,
745 "input.current", "input.L1.current");
746 change_name(simple,
747 "input.realpower", "input.L1.realpower");
748 change_name(simple,
749 "input.power", "input.L1.power");
750 change_name(simple,
751 "input.voltage", "input.L1-N.voltage");
752 }
753 if (dstate_getinfo("output.L3-L1.voltage") ||
754 dstate_getinfo("output.L3-N.voltage")) {
755 const char *s;
756
757 num_outphases = 3;
758
759 if ((s=dstate_getinfo("ups.model")) &&
760 (!strncmp(s, "UPS9075", 7) ||
761 !strncmp(s, "UPS9100", 7) ||
762 !strncmp(s, "UPS9150", 7) ||
763 !strncmp(s, "UPS9200", 7) ||
764 !strncmp(s, "UPS9250", 7) ||
765 !strncmp(s, "UPS9300", 7) ||
766 !strncmp(s, "UPS9400", 7) ||
767 !strncmp(s, "UPS9500", 7) ||
768 !strncmp(s, "UPS9600", 7)) ) {
769 /* Insert kludges for Fiskars UPS9000 here */
770 upslogx(LOG_INFO, "Fiskars UPS9000 detected, protocol kludges activated");
771 batt_volt_nom = 384;
772 dstate_setinfo("battery.voltage.nominal", "%.0f", batt_volt_nom);
773
774 }
775 else {
776 outpwr_factor *= 3;
777 }
778
779 change_name(simple,
780 "output.current", "output.L1.current");
781 change_name(simple,
782 "output.current.peak", "output.L1.current.peak");
783 change_name(simple,
784 "output.realpower", "output.L1.realpower");
785 change_name(simple,
786 "output.power", "output.L1.power");
787 change_name(simple,
788 "output.voltage", "output.L1-N.voltage");
789 }
790
791 dstate_setinfo("input.phases", "%d", num_inphases);
792 dstate_setinfo("output.phases", "%d", num_outphases);
793
794 inited_phaseinfo=1;
795 }
796
797 load = calc_upsload();
798
799 if (load >= 0) {
800 upsdebugx(2, "ups.load: %.1f", load*100);
801 dstate_setinfo("ups.load", "%.1f", load*100);
802 }
803 else {
804 upsdebugx(2, "ups.load: No value");
805 }
806
807 /* TODO/FIXME: Set UPS date/time on startup and daily if needed */
808 if (can_updt) {
809 char dtbuf[UPSC_BUFLEN];
810 if (upsc_getvalue("UPDT", "0", "ACDT", NULL, dtbuf)) {
811 dstate_setinfo("ups.date", "%s", dtbuf);
812 }
813 }
814 if (can_uptm) {
815 char tmbuf[UPSC_BUFLEN];
816 if (upsc_getvalue("UPTM", "0", "ACTM", NULL, tmbuf)) {
817 dstate_setinfo("ups.time", "%s", tmbuf);
818 }
819 }
820
821
822 if (batt_charge < 0) {
823 if (batt_current < 0) {
824 /* Reset battery current history if discharging */
825 numbatthist = lastbatthist = 0;
826 }
827 batt_charge = batt_charge_pct();
828 }
829 if (batt_charge >= 0) {
830 dstate_setinfo("battery.charge", "%.1f", batt_charge);
831 }
832 else {
833 dstate_delinfo("battery.charge");
834 }
835
836 /* 9999 == unknown value */
837 if (batt_runtime >= 0 && batt_runtime < 9999) {
838 dstate_setinfo("battery.runtime", "%.0f", batt_runtime*60);
839 }
840 else if (load > 0 && batt_disch_curr_max != 0) {
841 float est_battcurr = load * abs(batt_disch_curr_max);
842 /* Peukert equation */
843 float runtime = (batt_cap_nom*3600)/pow(est_battcurr, 1.35);
844
845 upsdebugx(2, "Calculated runtime: %.0f seconds", runtime);
846 if (batt_runtime_max > 0 && runtime > batt_runtime_max*60) {
847 runtime = batt_runtime_max*60;
848 }
849 dstate_setinfo("battery.runtime", "%.0f", runtime);
850
851 }
852 else if (batt_runtime_max > 0) {
853 /* Show max possible runtime as reported by UPS */
854 dstate_setinfo("battery.runtime", "%.0f", batt_runtime_max*60);
855 }
856 else {
857 dstate_delinfo("battery.runtime");
858 }
859 /* Some UPSen only provides this when on battery, so reset between
860 * each iteration to make sure we use the right value */
861 batt_charge = -1;
862 batt_runtime = -1;
863
864
865 if (!(status & UPSC_STAT_ONBATT))
866 status |= UPSC_STAT_ONLINE;
867
868 upsc_setstatus(status);
869
870 dstate_dataok();
871 ser_comm_good();
872 }
873
874
upsdrv_shutdown(void)875 void upsdrv_shutdown(void)
876 {
877 upslogx(LOG_EMERG, "Shutting down...");
878
879 /* send shutdown command twice, just to be sure */
880 instcmd("shutdown.reboot", NULL);
881 sleep(1);
882 instcmd("shutdown.reboot", NULL);
883 sleep(1);
884 }
885
886
instcmd(const char * auxcmd,const char * data)887 static int instcmd (const char *auxcmd, const char *data)
888 {
889 cmd_t *cp;
890
891 if (!strcasecmp(auxcmd, "beeper.off")) {
892 /* compatibility mode for old command */
893 upslogx(LOG_WARNING,
894 "The 'beeper.off' command has been renamed to 'beeper.disable'");
895 return instcmd("beeper.disable", NULL);
896 }
897
898 if (!strcasecmp(auxcmd, "beeper.on")) {
899 /* compatibility mode for old command */
900 upslogx(LOG_WARNING,
901 "The 'beeper.on' command has been renamed to 'beeper.enable'");
902 return instcmd("beeper.enable", NULL);
903 }
904
905 upsdebugx(1, "Instcmd: %s %s", auxcmd, data ? data : "\"\"");
906
907 for (cp = commands; cp->cmd; cp++) {
908 if (strcasecmp(cp->cmd, auxcmd)) {
909 continue;
910 }
911 upscsend(cp->upsc);
912 if (cp->upsp) {
913 upscsend(cp->upsp);
914 } else if (data) {
915 upscsend(data);
916 }
917 return STAT_INSTCMD_HANDLED;
918 }
919
920 upslogx(LOG_INFO, "instcmd: unknown command %s", auxcmd);
921 return STAT_INSTCMD_UNKNOWN;
922 }
923
924
setvar(const char * var,const char * data)925 static int setvar (const char *var, const char *data)
926 {
927 cmd_t *cp;
928
929 upsdebugx(1, "Setvar: %s %s", var, data);
930
931 for (cp = variables; cp->cmd; cp++) {
932 if (strcasecmp(cp->cmd, var)) {
933 continue;
934 }
935 upsc_getvalue(cp->upsc, data, cp->upsp, cp->cmd, NULL);
936 return STAT_SET_HANDLED;
937 }
938
939 upslogx(LOG_INFO, "Setvar: unsettable variable %s", var);
940 return STAT_SET_UNKNOWN;
941 }
942
943
944 /* list flags and values that you want to receive via -x */
upsdrv_makevartable(void)945 void upsdrv_makevartable(void)
946 {
947 addvar(VAR_VALUE, "manufacturer", "manufacturer [unknown]");
948 addvar(VAR_VALUE, "baudrate", "Serial interface baudrate [1200]");
949 addvar(VAR_VALUE, "input_timeout", "Command response timeout [2]");
950 addvar(VAR_VALUE, "output_pace", "Output character delay in usecs [200]");
951 addvar(VAR_VALUE, "full_update", "Delay between full value downloads [60]");
952 addvar(VAR_FLAG, "use_crlf", "Use CR-LF to terminate commands to UPS");
953 addvar(VAR_FLAG, "use_pre_lf", "Use LF to introduce commands to UPS");
954 }
955
956
upsdrv_cleanup(void)957 void upsdrv_cleanup(void)
958 {
959 ser_close(upsfd, device_path);
960 }
961
962
963 /*
964 * Generate status string from bitfield
965 */
upsc_setstatus(unsigned int status)966 static void upsc_setstatus(unsigned int status)
967 {
968
969 /*
970 * I'll look for all available statuses, even though they might not be
971 * supported in the UPScode II protocol.
972 */
973
974 status_init();
975
976 if (status & UPSC_STAT_ONLINE)
977 status_set("OL");
978 if (status & UPSC_STAT_ONBATT)
979 status_set("OB");
980 if (status & UPSC_STAT_LOBATT)
981 status_set("LB");
982 if (status & UPSC_STAT_REPLACEBATT)
983 status_set("RB");
984 if (status & UPSC_STAT_BOOST)
985 status_set("BOOST");
986 if (status & UPSC_STAT_TRIM)
987 status_set("TRIM");
988 if (status & UPSC_STAT_OVERLOAD)
989 status_set("OVER");
990 if (status & UPSC_STAT_CALIBRATION)
991 status_set("CAL");
992 if (status & UPSC_STAT_OFF)
993 status_set("OFF");
994 if (status & UPSC_STAT_BYPASS)
995 status_set("BYPASS");
996
997 status_commit();
998 }
999
1000
1001 /* Add \r to end of command and send to UPS */
1002 /* returns < 0 on errors, 0 on timeout and > 0 on success. */
upscsend(const char * cmd)1003 static int upscsend(const char *cmd)
1004 {
1005 int res;
1006
1007 res = ser_send_pace(upsfd, output_pace_usec, "%s%s%s",
1008 use_pre_lf ? "\n" : "",
1009 cmd,
1010 use_crlf ? "\r\n" : "\r");
1011
1012 if (res < 0) {
1013 upsdebug_with_errno(3, "upscsend");
1014 } else if (res == 0) {
1015 upsdebugx(3, "upscsend: Timeout");
1016 } else {
1017 upsdebugx(3, "upscsend: '%s'", cmd);
1018 }
1019
1020 return res;
1021 }
1022
1023
1024 /* Return a string read from UPS */
1025 /* returns < 0 on errors, 0 on timeout and > 0 on success. */
upscrecv(char * buf)1026 static int upscrecv(char *buf)
1027 {
1028 int res;
1029
1030 /* NOTE: the serial port is set to use Canonical Mode Input Processing,
1031 which means ser_get_buf() either returns one line terminated with
1032 ENDCHAR, an error or times out. */
1033
1034 while (1) {
1035 res = ser_get_buf(upsfd, buf, UPSC_BUFLEN, input_timeout_sec, 0);
1036 if (res != 1) {
1037 break;
1038 }
1039
1040 /* Only one character, must be ENDCHAR */
1041 upsdebugx(3, "upscrecv: Empty line");
1042 }
1043
1044 if (res < 0) {
1045 upsdebug_with_errno(3, "upscrecv");
1046 } else if (res == 0) {
1047 upsdebugx(3, "upscrecv: Timeout");
1048 } else {
1049 upsdebugx(3, "upscrecv: %u bytes:\t'%s'", res-1, str_rtrim(buf, ENDCHAR));
1050 }
1051
1052 return res;
1053 }
1054
1055
upsc_flush_input(void)1056 static void upsc_flush_input(void)
1057 {
1058 /*
1059 char buf[UPSC_BUFLEN];
1060
1061 do {
1062 upscrecv(buf);
1063 if (strlen(buf) > 0)
1064 upsdebugx(1, "Skipping input: %s", buf);
1065 } while (strlen(buf) > 0);
1066 */
1067 ser_flush_in(upsfd, "", nut_debug_level);
1068 }
1069
1070
1071 /* check which commands this ups supports.
1072 * Returns TRUE if command list was recieved, FALSE otherwise */
upsc_commandlist(void)1073 static int upsc_commandlist(void)
1074 {
1075 char buf[UPSC_BUFLEN];
1076 cmd_t *cp;
1077
1078 upsc_flush_input();
1079 upscsend("UPCL");
1080 while (1) {
1081 upscrecv(buf);
1082 if (strlen(buf) == 0) {
1083 upslogx(LOG_ERR, "Missing UPCL after UPCL");
1084 return 0;
1085 }
1086 upsdebugx(2, "Supports command: %s", buf);
1087
1088 if (strcmp(buf, "UPBS") == 0)
1089 can_upbs = 1;
1090 else if (strcmp(buf, "UPPM") == 0)
1091 can_uppm = 1;
1092 else if (strcmp(buf, "UPID") == 0)
1093 can_upid = 1;
1094 else if (strcmp(buf, "UPDA") == 0)
1095 can_upda = 1;
1096 else if (strcmp(buf, "UPDT") == 0)
1097 can_updt = 1;
1098 else if (strcmp(buf, "UPTM") == 0)
1099 can_uptm = 1;
1100 else if (strcmp(buf, "UPSD") == 0)
1101 can_upsd = 1;
1102 else if (strcmp(buf, "UPPC") == 0)
1103 can_uppc = 1;
1104
1105 for (cp = commands; cp->cmd; cp++) {
1106 if (cp->upsc && strcmp(cp->upsc, buf) == 0) {
1107 upsdebugx(1, "instcmd: %s %s", cp->cmd, cp->upsc);
1108 dstate_addcmd(cp->cmd);
1109 cp->enabled = 1;
1110 break;
1111 }
1112 }
1113
1114 for (cp = variables; cp->cmd; cp++) {
1115 if (cp->upsc && strcmp(cp->upsc, buf) == 0) {
1116 upsdebugx(1, "setvar: %s %s", cp->cmd, cp->upsc);
1117 cp->enabled = 1;
1118 break;
1119 }
1120 }
1121
1122 if (strcmp(buf, "UPCL") == 0)
1123 break;
1124 }
1125
1126 for (cp = variables; cp->cmd; cp++) {
1127 if (cp->enabled) {
1128 upsc_getvalue(cp->upsc, "0000", cp->upsp, cp->cmd, NULL);
1129 dstate_setflags(cp->cmd, ST_FLAG_RW | ST_FLAG_STRING);
1130 dstate_setaux(cp->cmd, 7);
1131 }
1132 }
1133
1134 return 1;
1135 }
1136
1137
1138 /* get limits and parameters */
upsc_getparams(const char * cmd,const simple_t * table)1139 static int upsc_getparams(const char *cmd, const simple_t *table)
1140 {
1141 char var[UPSC_BUFLEN];
1142 char val[UPSC_BUFLEN];
1143 int first = 1;
1144
1145 upsc_flush_input();
1146
1147 upscsend(cmd);
1148 buffer_empty = 0;
1149 while (!buffer_empty) {
1150 upscrecv(var);
1151 if (strlen(var) == 0) {
1152 if (first)
1153 upscrecv(var);
1154 if (strlen(var) == 0) {
1155 ser_comm_fail("Empty string from UPS for %s!",
1156 cmd);
1157 break;
1158 }
1159 }
1160 first = 0;
1161 upscrecv(val);
1162 if (strlen(val) == 0) {
1163 ser_comm_fail("Empty value from UPS for %s %s!", cmd, var);
1164 break;
1165 }
1166 upsdebugx(2, "Parameter %s %s", var, val);
1167 if (!upsc_simple(table, var, val))
1168 upslogx(LOG_ERR, "Unknown response to %s: %s %s",
1169 cmd, var, val);
1170 }
1171 return buffer_empty;
1172 }
1173
1174
check_uppm(void)1175 static void check_uppm(void)
1176 {
1177 int i, last = 0;
1178 char var[UPSC_BUFLEN];
1179 char val[UPSC_BUFLEN];
1180
1181 upsc_flush_input();
1182 upscsend("UPPM");
1183 upscsend("0");
1184 upscrecv(var);
1185 if (strcmp(var, "ACPM"))
1186 upslogx(LOG_ERR, "Bad response to UPPM: %s", var);
1187 while (1) {
1188 int val, stat;
1189 upscrecv(var);
1190 if (strlen(var) == 0)
1191 break;
1192 upsdebugx(2, "UPPM available: %s", var);
1193 stat = sscanf(var, "P%2d", &val);
1194 if (stat != 1) {
1195 upslogx(LOG_ERR, "Bad response to UPPM: %s", var);
1196 return;
1197 }
1198 has_uppm_p[val] = 1;
1199 if (val > last)
1200 last = val;
1201 }
1202
1203 for (i = 0; i <= last; i++) {
1204 if (!has_uppm_p[i])
1205 continue;
1206 upscsend("UPPM");
1207 snprintf(var, sizeof(var), "P%.2d", i);
1208 upscsend(var);
1209 upscrecv(val);
1210 if (strcmp(val, "ACPM")) {
1211 upslogx(LOG_ERR, "Bad response to UPPM %s: %s", var, val);
1212 continue;
1213 }
1214 upscrecv(var);
1215 upsdebugx(1, "UPPM value: %s", var);
1216 }
1217 }
1218
1219
upsc_getvalue(const char * cmd,const char * param,const char * resp,const char * nutvar,char * ret)1220 static int upsc_getvalue(const char *cmd, const char *param,
1221 const char *resp, const char *nutvar, char *ret)
1222 {
1223 char var[UPSC_BUFLEN];
1224 char val[UPSC_BUFLEN];
1225
1226 upsdebugx(2, "Request value: %s %s", cmd, param ? param : "\"\"");
1227 upscsend(cmd);
1228 if (param)
1229 upscsend(param);
1230 upscrecv(var);
1231 upscrecv(val);
1232 upsdebugx(2, "Got value: %s %s", var, val);
1233 if (strcmp(var, resp)) {
1234 upslogx(LOG_ERR, "Bad response to %s %s: %s %s",
1235 cmd, param ? param : "\"\"", var, val);
1236 return 0;
1237 }
1238 else {
1239 if (nutvar)
1240 dstate_setinfo(nutvar, "%s", val);
1241 if (ret)
1242 strcpy(ret, val);
1243 }
1244 return 1;
1245 }
1246
1247
upsc_getbaseinfo(void)1248 static void upsc_getbaseinfo(void)
1249 {
1250 char *buf;
1251
1252 dstate_setinfo("ups.mfr", "%s",
1253 ((buf = getval("manufacturer")) != NULL) ? buf : "unknown");
1254
1255 if (!upsc_getvalue("UPTP", NULL, "NNAME", "ups.model", NULL))
1256 upsc_getvalue("UPTP", NULL, "NNAME", "ups.model", NULL);
1257 upsc_getvalue("UPSN", "0", "ACSN", "ups.serial", NULL);
1258 }
1259
1260
upsc_simple(const simple_t * sp,const char * var,const char * val)1261 static int upsc_simple(const simple_t *sp, const char *var, const char *val)
1262 {
1263 int stat;
1264 float fval;
1265
1266 while (sp->code) {
1267 if (strcmp(sp->code, var) == 0) {
1268 switch (sp->type) {
1269 case t_setval:
1270 stat = sscanf(val, "%f", &fval);
1271 if (stat != 1)
1272 upslogx(LOG_ERR, "Bad float: %s %s", var, val);
1273 if (sp->desc)
1274 dstate_setinfo(sp->desc, "%.2f", fval);
1275 *sp->aux = fval;
1276 break;
1277 case t_setrecip:
1278 stat = sscanf(val, "%f", &fval);
1279 if (stat != 1)
1280 upslogx(LOG_ERR, "Bad float: %s %s", var, val);
1281 if (sp->desc)
1282 dstate_setinfo(sp->desc, "%s", val);
1283 *sp->aux = 1/fval;
1284 break;
1285 case t_setpct:
1286 stat = sscanf(val, "%f", &fval);
1287 if (stat != 1)
1288 upslogx(LOG_ERR, "Bad float: %s %s", var, val);
1289 *sp->aux = fval*100;
1290 if (sp->desc)
1291 dstate_setinfo(sp->desc, "%s", val);
1292 break;
1293 case t_setrecpct:
1294 stat = sscanf(val, "%f", &fval);
1295 if (stat != 1)
1296 upslogx(LOG_ERR, "Bad float: %s %s", var, val);
1297 *sp->aux = 1/fval*100;
1298 if (sp->desc)
1299 dstate_setinfo(sp->desc, "%s", val);
1300 break;
1301 case t_final:
1302 buffer_empty = 1;
1303 case t_value:
1304 if (!sp->desc) {
1305 break;
1306 }
1307 if (sscanf(val, "%f", &fval) == 1) {
1308 if (sp->aux != NULL) {
1309 fval *= *(sp->aux);
1310 }
1311 dstate_setinfo(sp->desc, "%.2f", fval);
1312 }
1313 else {
1314 upslogx(LOG_ERR, "Bad float in %s: %s", var, val);
1315 dstate_setinfo(sp->desc, "%s", val);
1316 }
1317 break;
1318 case t_finstr:
1319 buffer_empty = 1;
1320 case t_string:
1321 if (!sp->desc) {
1322 break;
1323 }
1324 dstate_setinfo(sp->desc, "%s", val);
1325 break;
1326 case t_status:
1327 if (strcmp(val, "00") == 0)
1328 ;
1329 else if (strcmp(val, "11") == 0)
1330 status |= sp->status;
1331 else
1332 upslogx(LOG_ERR, "Unknown status value: '%s' '%s'", var, val);
1333 break;
1334 case t_alarm:
1335 if (strcmp(val, "00") == 0)
1336 ;
1337 else if (strcmp(val, "11") == 0)
1338 status |= sp->status;
1339 else
1340 upslogx(LOG_ERR, "Unknown alarm value: '%s' '%s'", var, val);
1341 break;
1342 case t_ignore:
1343 upsdebugx(3, "Ignored value: %s %s", var, val);
1344 break;
1345 case t_list:
1346 if (!upsc_simple(sp->stats, val, "11"))
1347 upslogx(LOG_ERR, "Unknown value: %s %s",
1348 var, val);
1349 break;
1350 default:
1351 upslogx(LOG_ERR, "Unknown type for %s", var);
1352 break;
1353 }
1354 return 1;
1355 }
1356 sp++;
1357 }
1358 return 0;
1359 }
1360
1361
batt_charge_pct(void)1362 static float batt_charge_pct(void)
1363 {
1364 float chg=-1;
1365
1366 /* This is only a rough estimate of charge status while charging.
1367 * When on battery something like Peukert's equation should be used */
1368 if (batt_current >= 0) {
1369 float maxcurr = 10; /* Assume 10A max as default */
1370 float avgcurr = 0;
1371 int i;
1372
1373 batthist[lastbatthist] = batt_current;
1374 lastbatthist = (lastbatthist+1) % NUM_BATTHIST;
1375 if (numbatthist < NUM_BATTHIST) {
1376 numbatthist++;
1377 }
1378 for(i=0; i<numbatthist; i++) {
1379 avgcurr += batthist[i];
1380 }
1381 avgcurr /= numbatthist;
1382
1383 if (batt_cap_nom > 0) {
1384 /* Estimate max charge current based on battery size */
1385 maxcurr = batt_cap_nom * 0.3;
1386 }
1387 chg = maxcurr - avgcurr;
1388 chg *= (100/maxcurr);
1389 }
1390 /* Old method, assumes battery high/low-voltages provided by UPS are
1391 * applicable to battery charge, but they usually aren't */
1392 else if (batt_volt_low > 0 && batt_volt_high > 0 && batt_volt > 0) {
1393 if (batt_volt > batt_volt_high) {
1394 chg=100;
1395 }
1396 else if (batt_volt < batt_volt_low) {
1397 chg=0;
1398 }
1399 else {
1400 chg = (batt_volt - batt_volt_low) /
1401 (batt_volt_high - batt_volt_low);
1402 chg*=100;
1403 }
1404 }
1405 else {
1406 return -1;
1407 }
1408
1409 if (chg < 0) {
1410 chg = 0;
1411 }
1412 else if (chg > 100) {
1413 chg = 100;
1414 }
1415
1416 return chg;
1417 }
1418
1419 /*
1420 vim:noet:ts=8:sw=8:cindent
1421 */
1422