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