1 /* gamatronic.c
2 *
3 * SEC UPS Driver ported to the new NUT API for Gamatronic UPS Usage.
4 *
5 * TODO: Replace lots of printf() by upslogx() or upsdebugx() below!
6 *
7 * Copyright (C)
8 * 2001 John Marley <John.Marley@alcatel.com.au>
9 * 2002 Jules Taplin <jules@netsitepro.co.uk>
10 * 2002 Eric Lawson <elawson@inficad.com>
11 * 2005 Arnaud Quette <arnaud.quette@gmail.com>
12 * 2005 Nadav Moskovitch <blutz@walla.com / http://www.gamatronic.com>
13 *
14 * This program is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation; either version 2 of the License, or
17 * (at your option) any later version.
18 *
19 * This program is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * You should have received a copy of the GNU General Public License
25 * along with this program; if not, write to the Free Software
26 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
27 *
28 */
29
30 #include "main.h"
31 #include "serial.h"
32 #include "gamatronic.h"
33 #include "nut_stdint.h"
34
35 #define DRIVER_NAME "Gamatronic UPS driver"
36 #define DRIVER_VERSION "0.02"
37
38 /* driver description structure */
39 upsdrv_info_t upsdrv_info = {
40 DRIVER_NAME,
41 DRIVER_VERSION,
42 "John Marley <John.Marley@alcatel.com.au>\n" \
43 "Jules Taplin <jules@netsitepro.co.uk>\n" \
44 "Eric Lawson <elawson@inficad.com>\n" \
45 "Arnaud Quette <arnaud.quette@gmail.com>\n" \
46 "Nadav Moskovitch <blutz@walla.com / http://www.gamatronic.com>",
47 DRV_STABLE,
48 { NULL }
49 };
50
51 #define ENDCHAR '\r'
52 #define IGNCHARS ""
53 #define SER_WAIT_SEC 1 /* allow 3.0 sec for ser_get calls */
54 #define SER_WAIT_USEC 0
55
sec_upsrecv(char * buf)56 static int sec_upsrecv (char *buf)
57 {
58 char lenbuf[4];
59 int ret;
60
61 ser_get_line(upsfd, buf, 140, ENDCHAR, IGNCHARS,SER_WAIT_SEC, SER_WAIT_USEC);
62 if (buf[0] == SEC_MSG_STARTCHAR) {
63 switch (buf[1]) {
64 case SEC_NAK:
65 return(-1);
66 case SEC_ACK:
67 return(0);
68 case SEC_DATAMSG:
69 strncpy(lenbuf, buf+2, 3);
70 lenbuf[3] = '\0';
71 ret = atoi(lenbuf);
72 if (ret > 0) {
73 strcpy(buf,buf+5);
74 return(ret);
75 }
76 else return (-2);
77 default:
78 return(-2);
79 }
80 }
81 else
82 return (-2);
83 }
84
sec_cmd(const char mode,const char * command,char * msgbuf,ssize_t * buflen)85 static ssize_t sec_cmd(const char mode, const char *command, char *msgbuf, ssize_t *buflen)
86 {
87 char msg[140];
88 ssize_t ret;
89
90 memset(msg, 0, sizeof(msg));
91
92 /* create the message string */
93 if (*buflen > 0) {
94 snprintf(msg, sizeof(msg), "%c%c%03zd%s%s", SEC_MSG_STARTCHAR,
95 mode, (*buflen)+3, command, msgbuf);
96 }
97 else {
98 snprintf(msg, sizeof(msg), "%c%c003%s", SEC_MSG_STARTCHAR,
99 mode, command);
100 }
101 upsdebugx(1, "PC-->UPS: \"%s\"",msg);
102 ret = ser_send(upsfd, "%s", msg);
103
104 upsdebugx(1, " send returned: %zd",ret);
105
106 if (ret == -1) return -1;
107
108 ret = sec_upsrecv(msg);
109
110 if (ret < 0) return -1;
111
112 strncpy(msgbuf, msg, (size_t)ret);
113 upsdebugx(1, "UPS<--PC: \"%s\"",msg);
114
115 /*
116 *(msgbuf+ret) = '\0';
117 */
118
119 *buflen = ret;
120 return ret;
121 }
122
addquery(const char * cmd,int field,int varnum,int pollflag)123 static void addquery(const char *cmd, int field, int varnum, int pollflag)
124 {
125 int q;
126
127 for (q=0; q<SEC_QUERYLIST_LEN; q++) {
128 if (sec_querylist[q].command == NULL) {
129 /* command has not been recorded yet */
130 sec_querylist[q].command = cmd;
131 sec_querylist[q].pollflag = pollflag;
132 upsdebugx(1, " Query %d is %s",q,cmd);
133 }
134 if (sec_querylist[q].command == cmd) {
135 sec_querylist[q].varnum[field-1] = varnum;
136 upsdebugx(1, " Querying varnum %d",varnum);
137 break;
138 }
139 }
140 }
141
sec_setinfo(int varnum,char * value)142 static void sec_setinfo(int varnum, char *value)
143 {
144 if (*sec_varlist[varnum].setcmd)
145 { /*Not empty*/
146 if (sec_varlist[varnum].flags == FLAG_STRING) {
147 dstate_setinfo(sec_varlist[varnum].setcmd,"%s", value);
148 }
149 else if (sec_varlist[varnum].unit == 1) {
150 dstate_setinfo(sec_varlist[varnum].setcmd,"%s", value);
151 }
152 else if (sec_varlist[varnum].flags == FLAG_MULTI) {
153 if (atoi(value) < 0) {
154 dstate_setinfo(sec_varlist[varnum].setcmd,"0");
155 }
156 else {
157 dstate_setinfo(sec_varlist[varnum].setcmd,"%d", atoi(value) * sec_varlist[varnum].unit);
158 }
159 }
160 else {
161 dstate_setinfo(sec_varlist[varnum].setcmd,"%.1f", atof(value) / sec_varlist[varnum].unit);
162 }
163 }
164 }
165
update_pseudovars(void)166 static void update_pseudovars( void )
167 {
168 status_init();
169
170 if(strncmp(sec_varlist[9].value, "1", 1)== 0) {
171 status_set("OFF");
172 }
173 if(strncmp(sec_varlist[76].value, "0", 1)== 0) {
174 status_set("OL");
175 }
176 if(strncmp(sec_varlist[76].value, "1", 1)== 0) {
177 status_set("OB");
178 }
179 if(strncmp(sec_varlist[76].value, "2", 1)== 0) {
180 status_set("BYPASS");
181 }
182 if(strncmp(sec_varlist[76].value, "3", 1)== 0) {
183 status_set("TRIM");
184 }
185 if(strncmp(sec_varlist[76].value, "4", 1)== 0) {
186 status_set("BOOST");
187 }
188 if(strncmp(sec_varlist[10].value, "1", 1)== 0) {
189 status_set("OVER");
190 }
191 if(strncmp(sec_varlist[22].value, "1", 1)== 0) {
192 status_set("LB");
193 }
194 if(strncmp(sec_varlist[19].value, "2", 1)== 0) {
195 status_set("RB");
196 }
197
198 status_commit();
199 }
200
sec_poll(int pollflag)201 static void sec_poll ( int pollflag ) {
202 ssize_t msglen;
203 int f, q;
204 char retbuf[140], *n, *r;
205
206 for (q=0; q<SEC_QUERYLIST_LEN; q++) {
207 if (sec_querylist[q].command == NULL) break;
208 if (sec_querylist[q].pollflag != pollflag) continue;
209
210 msglen = 0;
211 sec_cmd(SEC_POLLCMD, sec_querylist[q].command, retbuf, &msglen);
212 r = retbuf;
213 *(r+msglen) = '\0';
214 for (f=0; f<SEC_MAXFIELDS; f++) {
215 n = strchr(r, ',');
216 if (n != NULL) *n = '\0';
217
218 if (sqv(q,f) > 0) {
219 if (strcmp(sec_varlist[sqv(q,f)].value, r) != 0) {
220 #ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_TRUNCATION
221 #pragma GCC diagnostic push
222 #endif
223 #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_TRUNCATION
224 #pragma GCC diagnostic ignored "-Wformat-truncation"
225 #endif
226 /* NOTE: We intentionally limit the amount
227 * of characters picked from "r" buffer
228 * into respectively sized "*.value"
229 */
230 int len = snprintf(sec_varlist[sqv(q,f)].value,
231 sizeof(sec_varlist[sqv(q,f)].value), "%s", r);
232
233 if (len < 0) {
234 upsdebugx(1, "%s: got an error while extracting value", __func__);
235 }
236
237 if ((intmax_t)len > (intmax_t)sizeof(sec_varlist[sqv(q,f)].value)
238 || (intmax_t)strnlen(r, sizeof(retbuf)) > (intmax_t)sizeof(sec_varlist[sqv(q,f)].value)
239 ) {
240 upsdebugx(1, "%s: value was truncated", __func__);
241 }
242 #ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_TRUNCATION
243 #pragma GCC diagnostic pop
244 #endif
245 sec_setinfo(sqv(q,f), r);
246 }
247
248 /* If SEC VAR is alarm and it's on, add it to the alarm property */
249 if (sec_varlist[sqv(q,f)].flags & FLAG_ALARM && strncmp(r, "1", 1)== 0) {
250 alarm_set(sec_varlist[sqv(q,f)].name);
251 }
252 }
253
254 if (n == NULL) break;
255 r = n+1;
256 }
257 }
258
259 }
260
upsdrv_initinfo(void)261 void upsdrv_initinfo(void)
262 {
263 ssize_t msglen;
264 int v;
265 char *a, *p, avail_list[300];
266
267 /* find out which variables/commands this UPS supports */
268 msglen = 0;
269 sec_cmd(SEC_POLLCMD, SEC_AVAILP1, avail_list, &msglen);
270 p = avail_list + msglen;
271 if (p != avail_list) *p++ = ',';
272 msglen = 0;
273 sec_cmd(SEC_POLLCMD, SEC_AVAILP2, p, &msglen);
274 *(p+msglen) = '\0';
275
276 if (strlen(avail_list) == 0) {
277 fatalx(EXIT_FAILURE, "No available variables found!");
278 }
279 a = avail_list;
280 while ((p = strtok(a, ",")) != NULL) {
281 a = NULL;
282 v = atoi(p);
283 /* don't bother adding a write-only variable */
284 if (sec_varlist[v].flags == FLAG_WONLY) continue;
285 addquery(sec_varlist[v].cmd, sec_varlist[v].field, v, sec_varlist[v].poll);
286 }
287
288 /* poll one time values */
289 sec_poll(FLAG_POLLONCE);
290
291 printf("UPS: %s %s\n", dstate_getinfo("ups.mfr"), dstate_getinfo("ups.model"));
292 }
293
upsdrv_updateinfo(void)294 void upsdrv_updateinfo(void)
295 {
296 alarm_init();
297 /* poll status values values */
298 sec_poll(FLAG_POLL);
299 alarm_commit();
300 update_pseudovars();
301 dstate_dataok();
302 }
303
upsdrv_shutdown(void)304 void upsdrv_shutdown(void)
305 {
306 ssize_t msglen;
307 char msgbuf[SMALLBUF];
308
309 msglen = snprintf(msgbuf, sizeof(msgbuf), "-1");
310 sec_cmd(SEC_SETCMD, SEC_SHUTDOWN, msgbuf, &msglen);
311
312 msglen = snprintf(msgbuf, sizeof(msgbuf), "1");
313 sec_cmd(SEC_SETCMD, SEC_AUTORESTART, msgbuf, &msglen);
314
315 msglen = snprintf(msgbuf, sizeof(msgbuf), "2");
316 sec_cmd(SEC_SETCMD, SEC_SHUTTYPE,msgbuf, &msglen);
317
318 msglen = snprintf(msgbuf, sizeof(msgbuf), "5");
319 sec_cmd(SEC_SETCMD, SEC_SHUTDOWN, msgbuf, &msglen);
320 }
321
322 /*
323 int instcmd(const char *cmdname, const char *extra)
324 {
325 if (!strcasecmp(cmdname, "test.battery.stop")) {
326 ser_send_buf(upsfd, ...);
327 return STAT_INSTCMD_HANDLED;
328 }
329
330 upslogx(LOG_NOTICE, "instcmd: unknown command [%s]", cmdname);
331 return STAT_INSTCMD_UNKNOWN;
332 }
333 */
334
upsdrv_help(void)335 void upsdrv_help(void)
336 {
337 }
338
339 /* list flags and values that you want to receive via -x */
upsdrv_makevartable(void)340 void upsdrv_makevartable(void)
341 {
342 /* allow '-x xyzzy' */
343 /* addvar(VAR_FLAG, "xyzzy", "Enable xyzzy mode"); */
344
345 /* allow '-x foo=<some value>' */
346 /* addvar(VAR_VALUE, "foo", "Override foo setting"); */
347 }
348
setup_serial(const char * port)349 static void setup_serial(const char *port)
350 {
351 char temp[140];
352 int i;
353 ssize_t ret;
354
355 /* Detect the ups baudrate */
356 for (i=0; i<5; i++) {
357 ser_set_speed(upsfd, device_path, baud_rates[i].rate);
358 ret = ser_send(upsfd, "^P003MAN");
359 ret = sec_upsrecv(temp);
360 if (ret >= -1) break;
361 }
362
363 if (i == 5) {
364 printf("Can't talk to UPS on port %s!\n",port);
365 printf("Check the cabling and portname and try again\n");
366 printf("Please note that this driver only support UPS Models with SEC Protocol\n");
367 ser_close(upsfd, device_path);
368 exit (1);
369 }
370 else
371 printf("Connected to UPS on %s baudrate: %zu\n",
372 port, baud_rates[i].name);
373 }
374
upsdrv_initups(void)375 void upsdrv_initups(void)
376 {
377 upsfd = ser_open(device_path);
378 setup_serial(device_path);
379 /* upsfd = ser_open(device_path); */
380 /* ser_set_speed(upsfd, device_path, B1200); */
381
382 /* probe ups type */
383
384 /* to get variables and flags from the command line, use this:
385 *
386 * first populate with upsdrv_buildvartable above, then...
387 *
388 * set flag foo : /bin/driver -x foo
389 * set variable 'cable' to '1234' : /bin/driver -x cable=1234
390 *
391 * to test flag foo in your code:
392 *
393 * if (testvar("foo"))
394 * do_something();
395 *
396 * to show the value of cable:
397 *
398 * if ((cable == getval("cable")))
399 * printf("cable is set to %s\n", cable);
400 * else
401 * printf("cable is not set!\n");
402 *
403 * don't use NULL pointers - test the return result first!
404 */
405
406 /* the upsh handlers can't be done here, as they get initialized
407 * shortly after upsdrv_initups returns to main.
408 */
409 }
410
upsdrv_cleanup(void)411 void upsdrv_cleanup(void)
412 {
413 /* free(dynamic_mem); */
414 ser_close(upsfd, device_path);
415 }
416