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