1 /* etapro.c - model specific routines for ETA UPS
2 
3    Copyright (C) 2002  Marek Michalkiewicz  <marekm@amelek.gda.pl>
4 
5    This program is free software; you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 2 of the License, or
8    (at your option) any later version.
9 
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14 
15    You should have received a copy of the GNU General Public License
16    along with this program; if not, write to the Free Software
17    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18  */
19 
20 /*
21    This driver is for the ETA UPS (http://www.eta.com.pl/) with the
22    "PRO" option (available at small added cost, highly recommended).
23    All units (even without that option) should also work in "dumb"
24    mode with the genericups driver (type 7 or 10), but in that mode
25    shutdown only works when running on battery.
26 
27    Tested with ETA mini+UPS 720 PRO.  Thanks to ETA for help with
28    protocol documentation, no free UPS though (but they still can
29    send me another one if they like me ;-).
30 
31    Shutdown should work even when on line, so this should help avoid
32    power races (system remaining in halted or "ATX standby" state,
33    requiring manual intervention).  Delay from power off to power on
34    can be set in software, currently hardcoded to 15 seconds.
35 
36    Instant commands CMD_OFF and CMD_ON should work (not tested yet).
37    Be careful with CMD_OFF - it turns off the load after one second.
38 
39    Known issues:
40     - larger units (>= 1000VA) have a 24V battery, so the battery
41       voltage reported should be multiplied by 2 if the model
42       string indicates such a larger unit.
43     - load percentage is only measured when running on battery, and
44       is reported as 0 when on line.  This seems to be a hardware
45       limitation of the UPS, so we can't do much about it...
46     - UPS does not provide any remaining battery charge (or time at
47       current load) information, but we should be able to estimate it
48       based on battery voltage, load percentage and UPS model.
49     - error handling not tested (we assume that the UPS is always
50       correctly connected to the serial port).
51  */
52 
53 #include "main.h"
54 #include "serial.h"
55 
56 #define DRIVER_NAME	"ETA PRO driver"
57 #define DRIVER_VERSION	"0.04"
58 
59 /* driver description structure */
60 upsdrv_info_t upsdrv_info = {
61 	DRIVER_NAME,
62 	DRIVER_VERSION,
63 	"Marek Michalkiewicz <marekm@amelek.gda.pl>",
64 	DRV_STABLE,
65 	{ NULL }
66 };
67 
68 static int
etapro_get_response(const char * resp_type)69 etapro_get_response(const char *resp_type)
70 {
71 	char tmp[256];
72 	char *cp;
73 	unsigned int n, val;
74 
75 	/* Read until a newline is found or there is no room in the buffer.
76 	   Unlike ser_get_line(), don't discard the following characters
77 	   because we have to handle multi-line responses.  */
78 	n = 0;
79 	while (ser_get_char(upsfd, (unsigned char *)&tmp[n], 1, 0) == 1) {
80 		if (n >= sizeof(tmp) - 1 || tmp[n] == '\n')
81 			break;
82 		n++;
83 	}
84 	tmp[n] = '\0';
85 	if (n == 0) {
86 		upslogx(LOG_ERR, "no response from UPS");
87 		return -1;
88 	}
89 	/* Search for start of response (skip any echoed back command).  */
90 	cp = strstr(tmp, resp_type);
91 	if (!cp || *cp == '\0' || cp[strlen(cp) - 1] != '\r') {
92 		upslogx(LOG_ERR, "bad response (%s)", tmp);
93 		return -1;
94 	}
95 	cp[strlen(cp) - 1] = '\0';  /* remove the CR */
96 
97 	switch (cp[1]) {
98 		/* Handle ASCII text responses directly here.  */
99 	case 'R':
100 		dstate_setinfo("ups.mfr", "%s", cp + 2);
101 		return 0;
102 	case 'S':
103 		dstate_setinfo("ups.model", "%s", cp + 2);
104 		return 0;
105 	case 'T':
106 		dstate_setinfo("ups.mfr.date", "%s", cp + 2);
107 		return 0;
108 	}
109 	/* Handle all other responses as hexadecimal numbers.  */
110 	val = 0;
111 	if (sscanf(cp + 2, "%x", &val) != 1) {
112 		upslogx(LOG_ERR, "bad response format (%s)", tmp);
113 		return -1;
114 	}
115 	return val;
116 }
117 
118 static void
etapro_set_on_timer(int seconds)119 etapro_set_on_timer(int seconds)
120 {
121 	int x;
122 
123 	if (seconds == 0) {  /* cancel the running timer */
124 		ser_send(upsfd, "RS\r");
125 		x = etapro_get_response("SV");
126 		if (x == 0x30)
127 			return;  /* OK */
128 	} else {
129 		if (seconds > 0x7fff) {  /* minutes */
130 			seconds = (seconds + 59) / 60;
131 			if (seconds > 0x7fff)
132 				seconds = 0x7fff;
133 			printf("UPS on in %d minutes\n", seconds);
134 			seconds |= 0x8000;
135 		} else {
136 			printf("UPS on in %d seconds\n", seconds);
137 		}
138 
139 		ser_send(upsfd, "RN%04X\r", seconds);
140 		x = etapro_get_response("SV");
141 		if (x == 0x20)
142 			return;  /* OK */
143 	}
144 	upslogx(LOG_ERR, "etapro_set_on_timer: error, status=0x%02x", x);
145 }
146 
147 static void
etapro_set_off_timer(int seconds)148 etapro_set_off_timer(int seconds)
149 {
150 	int x;
151 
152 	if (seconds == 0) {  /* cancel the running timer */
153 		ser_send(upsfd, "RR\r");
154 		x = etapro_get_response("SV");
155 		if (x == 0x10)
156 			return;  /* OK */
157 	} else {
158 		if (seconds > 0x7fff) {  /* minutes */
159 			seconds /= 60;
160 			if (seconds > 0x7fff)
161 				seconds = 0x7fff;
162 			printf("UPS off in %d minutes\n", seconds);
163 			seconds |= 0x8000;
164 		} else {
165 			printf("UPS off in %d seconds\n", seconds);
166 		}
167 
168 		ser_send(upsfd, "RO%04X\r", seconds);
169 		x = etapro_get_response("SV");
170 		if (x == 0)
171 			return;  /* OK */
172 	}
173 	upslogx(LOG_ERR, "etapro_set_off_timer: error, status=0x%02x", x);
174 }
175 
instcmd(const char * cmdname,const char * extra)176 static int instcmd(const char *cmdname, const char *extra)
177 {
178 	if (!strcasecmp(cmdname, "load.off")) {
179 		etapro_set_off_timer(1);
180 		return STAT_INSTCMD_HANDLED;
181 	}
182 
183 	if (!strcasecmp(cmdname, "load.on")) {
184 		etapro_set_on_timer(1);
185 		return STAT_INSTCMD_HANDLED;
186 	}
187 
188 	if (!strcasecmp(cmdname, "shutdown.return")) {
189 		upsdrv_shutdown();
190 		return STAT_INSTCMD_HANDLED;
191 	}
192 
193 	upslogx(LOG_NOTICE, "instcmd: unknown command [%s]", cmdname);
194 	return STAT_INSTCMD_UNKNOWN;
195 }
196 
197 void
upsdrv_initinfo(void)198 upsdrv_initinfo(void)
199 {
200 	dstate_addcmd("load.off");
201 	dstate_addcmd("load.on");
202 	dstate_addcmd("shutdown.return");
203 
204 	/* First command after power on returns junk - ignore it.  */
205 	ser_send(upsfd, "RI\r");
206 	sleep(1);
207 
208 	upsdrv_updateinfo();
209 
210 	upsh.instcmd = instcmd;
211 }
212 
213 void
upsdrv_updateinfo(void)214 upsdrv_updateinfo(void)
215 {
216 	int x, flags;
217 	double utility, outvolt, battvolt, loadpct;
218 
219 	ser_flush_in(upsfd, "", nut_debug_level);
220 	ser_send(upsfd, "RI\r");  /* identify */
221 
222 	x = etapro_get_response("SR");  /* manufacturer */
223 	if (x < 0) {
224 		dstate_datastale();
225 		return;
226 	}
227 	x = etapro_get_response("SS");  /* model */
228 	if (x < 0) {
229 		dstate_datastale();
230 		return;
231 	}
232 	x = etapro_get_response("ST");  /* mfr date */
233 	if (x < 0) {
234 		dstate_datastale();
235 		return;
236 	}
237 	x = etapro_get_response("SU");  /* UPS ident */
238 	if (x < 0) {
239 		dstate_datastale();
240 		return;
241 	}
242 
243 	ser_send(upsfd, "RP\r");  /* read measurements */
244 
245 	x = etapro_get_response("SO");  /* status flags */
246 	if (x < 0) {
247 		dstate_datastale();
248 		return;
249 	}
250 	flags = x;
251 
252 	x = etapro_get_response("SG");  /* input voltage, 0xFF = 270V */
253 	if (x < 0) {
254 		dstate_datastale();
255 		return;
256 	}
257 	utility = (270.0 / 255) * x;
258 
259 	x = etapro_get_response("SH");  /* output voltage, 0xFF = 270V */
260 	if (x < 0) {
261 		dstate_datastale();
262 		return;
263 	}
264 	outvolt = (270.0 / 255) * x;
265 
266 	x = etapro_get_response("SI");  /* battery voltage, 0xFF = 14V */
267 	if (x < 0) {
268 		dstate_datastale();
269 		return;
270 	}
271 
272 	/* TODO: >= 1000VA models have a 24V battery (max 28V) - check
273 	   the model string returned by the RI command.  */
274 	battvolt = (14.0 / 255) * x;
275 
276 	x = etapro_get_response("SL");  /* load (on battery), 0xFF = 150% */
277 	if (x < 0) {
278 		dstate_datastale();
279 		return;
280 	}
281 	loadpct = (150.0 / 255) * x;
282 
283 	x = etapro_get_response("SN");  /* time running on battery */
284 	if (x < 0) {
285 		dstate_datastale();
286 		return;
287 	}
288 	/* This is the time how long the UPS has been running on battery
289 	   (in seconds, reset to zero after power returns), but there
290 	   seems to be no variable defined for this yet...  */
291 
292 	status_init();
293 
294 	if (!(flags & 0x02))
295 		status_set("OFF");
296 	else if (flags & 0x01)
297 		status_set("OL");
298 	else
299 		status_set("OB");
300 
301 	if (!(flags & 0x04))
302 		status_set("LB");
303 
304 	/* TODO bit 3: 1 = ok, 0 = fault */
305 
306 	if (flags & 0x10)
307 		status_set("BOOST");
308 
309 	if (loadpct > 100.0)
310 		status_set("OVER");
311 
312 	/* Battery voltage out of range (lower than LB, or too high).  */
313 	if (flags & 0x20)
314 		status_set("RB");
315 
316 	/* TODO bit 6: 1 = charging, 0 = full */
317 
318 	status_commit();
319 
320 	dstate_setinfo("input.voltage", "%03.1f", utility);
321 	dstate_setinfo("output.voltage", "%03.1f", outvolt);
322 	dstate_setinfo("battery.voltage", "%02.2f", battvolt);
323 	dstate_setinfo("ups.load", "%03.1f", loadpct);
324 
325 	dstate_dataok();
326 }
327 
328 /* TODO: delays should be tunable, the UPS supports max 32767 minutes.  */
329 
330 /* Shutdown command to off delay in seconds.  */
331 #define SHUTDOWN_GRACE_TIME 10
332 
333 /* Shutdown to return delay in seconds.  */
334 #define SHUTDOWN_TO_RETURN_TIME 15
335 
336 void
upsdrv_shutdown(void)337 upsdrv_shutdown(void)
338 {
339 	etapro_set_on_timer(SHUTDOWN_GRACE_TIME + SHUTDOWN_TO_RETURN_TIME);
340 	etapro_set_off_timer(SHUTDOWN_GRACE_TIME);
341 }
342 
343 void
upsdrv_help(void)344 upsdrv_help(void)
345 {
346 }
347 
348 void
upsdrv_makevartable(void)349 upsdrv_makevartable(void)
350 {
351 }
352 
353 void
upsdrv_initups(void)354 upsdrv_initups(void)
355 {
356 	upsfd = ser_open(device_path);
357 	ser_set_speed(upsfd, device_path, B1200);
358 
359 	ser_set_dtr(upsfd, 0);
360 	ser_set_rts(upsfd, 1);
361 }
362 
upsdrv_cleanup(void)363 void upsdrv_cleanup(void)
364 {
365 	ser_close(upsfd, device_path);
366 }
367