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