1 /*
2  *
3  * microdowell.c: support for Microdowell Enterprise Nxx/Bxx serial protocol based UPSes
4  *
5  * Copyright (C) Elio Corbolante <eliocor at microdowell.com>
6  *
7  * microdowell.c created on 27/09/2007
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22  */
23 
24 /*
25 	anything commented is optional
26 	anything else is mandatory
27 */
28 
29 #include "main.h"
30 #include "serial.h"
31 #include <sys/ioctl.h>
32 #include "timehead.h"
33 #include "nut_stdint.h"
34 
35 #define	ENTERPRISE_PROTOCOL
36 #include "microdowell.h"
37 
38 #define MAX_START_DELAY    999999
39 #define MAX_SHUTDOWN_DELAY 32767
40 /* Maximum length of a string representing these values */
41 #define MAX_START_DELAY_LEN    6
42 #define MAX_SHUTDOWN_DELAY_LEN 5
43 
44 #define DRIVER_NAME	"MICRODOWELL UPS driver"
45 #define DRIVER_VERSION	"0.01"
46 
47 /* driver description structure */
48 upsdrv_info_t upsdrv_info = {
49 	DRIVER_NAME,
50 	DRIVER_VERSION,
51 	"Elio Corbolante <eliocor@microdowell.com>",
52 	DRV_STABLE,
53 	{ NULL }
54 };
55 
56 static ENT_STRUCT ups ;
57 
58 /* common driver routines */
59 int instcmd(const char *cmdname, const char *extra);
60 int setvar(const char *varname, const char *val);
61 
62 /* he knew... macros should evaluate their arguments only once */
63 #define CLAMP(x, min, max) (((x) < (min)) ? (min) : (((x) > (max)) ? (max) : (x)))
64 
CheckDataChecksum(unsigned char * Buff,size_t Len)65 static int CheckDataChecksum(unsigned char *Buff, size_t Len)
66 {
67 	size_t i, Idx ;
68 	unsigned char Xor ;
69 
70 	ups.FramePointer = Xor = 0 ;
71 	for (Idx=0 ; Idx < Len ; Idx++)
72 		if (Buff[Idx] == STX_CHAR)
73 			break ;
74 
75 	ups.FramePointer = Idx ; /* Memorise start point. */
76 
77 	/* Check that the message is not to short... */
78 	if ( (Idx > (Len-4)) || (Idx+Buff[Idx+1]+2 > Len) )
79 		return(ERR_MSG_TOO_SHORT) ;   /* Too short a message! */
80 
81 	/* Calculate checksum */
82 	for (i=Idx+1 ; i < Idx+Buff[Idx+1]+2 ; i++)
83 		Xor ^= Buff[i] ;
84 
85 	/* if Xor != then checksum error */
86 	if (Xor != Buff[i])
87 		return(ERR_MSG_CHECKSUM) ; /* error in checksum */
88 
89 	/* If checksum OK: return */
90 	return(0) ;
91 }
92 
93 
94 static const char *ErrMessages[] = {
95 /*  0 */   "errorcode NOT DEFINED",   /* default error message */
96 /*  1 */   "I2C bus busy (e2prom)",
97 /*  2 */   "Command received: checksum not valid",
98 /*  3 */   "Command received: unrecognized command",
99 /*  4 */   "WRITE: eeprom address not multiple of 8",
100 /*  5 */   "READ: eeprom address (added with size) out of bound ",
101 /*  6 */   "error writing e2prom address",
102 /*  7 */   "error writing e2prom subaddress",
103 /*  8 */   "error reading e2prom data",
104 /*  9 */   "error writing e2prom address",
105 /* 10 */   "error reading e2prom subaddress",
106 /* 11 */   "error writing e2prom data",
107 /* 12 */   "error writing e2prom address during data verification",
108 /* 13 */   "error verification e2prom data",
109 /* 14 */   "e2prom data are different from those in the write buffer",
110 /* 15 */   "e2prom checksum error",
111 
112 /* 16 */   "NO CHARS FROM PORT",
113 /* 17 */   "TOO FEW DATA RECEIVED: [STX] near end of message",
114 /* 18 */   "CHECKSUM ERROR IN MESSAGE",
115 /* 19 */   "OK",
116 /*    */   ""
117    } ;
118 
PrintErr(int ErrCode)119 static const char *PrintErr(int ErrCode)
120 {
121 	int msgIndex = 0 ;
122 
123 	/* The default 'msgIndex' is 0 (error code not defined) */
124 	switch (ErrCode) {
125 		case ERR_NO_ERROR       : msgIndex = 19 ; break ;
126 
127 		case ERR_I2C_BUSY       : msgIndex =  1 ; break ;
128 		case ERR_CMD_CHECKSUM   : msgIndex =  2 ; break ;
129 		case ERR_CMD_UNRECOG    : msgIndex =  3 ; break ;
130 		case ERR_EEP_NOBLOCK    : msgIndex =  4 ; break ;
131 		case ERR_EEP_OOBOUND    : msgIndex =  5 ; break ;
132 		case ERR_EEP_WADDR1     : msgIndex =  6 ; break ;
133 		case ERR_EEP_WSADDR1    : msgIndex =  7 ; break ;
134 		case ERR_EEP_RDATA      : msgIndex =  8 ; break ;
135 		case ERR_EEP_WADDR2     : msgIndex =  9 ; break ;
136 		case ERR_EEP_WSADDR2    : msgIndex = 10 ; break ;
137 		case ERR_EEP_WDATA      : msgIndex = 11 ; break ;
138 		case ERR_EEP_WADDRVER   : msgIndex = 12 ; break ;
139 		case ERR_EEP_WDATAVER   : msgIndex = 13 ; break ;
140 		case ERR_EEP_VERIFY     : msgIndex = 14 ; break ;
141 		case ERR_EEP_CHECKSUM   : msgIndex = 15 ; break ;
142 
143 		case ERR_COM_NO_CHARS   : msgIndex = 16 ; break ;
144 		case ERR_MSG_TOO_SHORT  : msgIndex = 17 ; break ;
145 		case ERR_MSG_CHECKSUM   : msgIndex = 18 ; break ;
146 
147 		default                 : msgIndex = 0  ; break ;
148 		}
149 	return(ErrMessages[msgIndex]) ;
150 }
151 
CheckErrCode(unsigned char * Buff)152 static int CheckErrCode(unsigned char * Buff)
153 {
154 	auto int Ret ;
155 
156 	switch (Buff[2]) {
157 		/* I have found an error */
158 		case CMD_NACK   :
159 			Ret = Buff[3] ;
160 			break ;
161 
162 		case CMD_ACK           :
163 		case CMD_GET_STATUS    :
164 		case CMD_GET_MEASURES  :
165 		case CMD_GET_CONFIG    :
166 		case CMD_GET_BATT_STAT :
167 		case CMD_GET_MASK      :
168 		case CMD_SET_TIMER     :
169 		case CMD_BATT_TEST     :
170 		case CMD_GET_BATT_TEST :
171 		case CMD_SD_ONESHOT    :
172 		case CMD_GET_SD_ONESHOT:
173 		case CMD_SET_SCHEDULE  :
174 		case CMD_GET_SCHEDULE  :
175 		case CMD_GET_EEP_BLOCK :
176 		case CMD_SET_EEP_BLOCK :
177 		case CMD_GET_EEP_SEED  :
178 		case CMD_INIT          :
179 			Ret = 0 ;
180 			break ;
181 
182 		/* command not recognized */
183 		default:
184 			Ret = ERR_CMD_UNRECOG ;
185 			break ;
186 		}
187 	return(Ret) ;
188 }
189 
190 
SendCmdToSerial(unsigned char * Buff,size_t Len)191 static void SendCmdToSerial(unsigned char *Buff, size_t Len)
192 {
193 	int i;
194 	unsigned char Tmp[20], Xor ;
195 
196 	Tmp[0] = STX_CHAR ;
197 	Xor = Tmp[1] = (unsigned char) (Len & 0x1f) ;
198 	for (i=0 ; i < Tmp[1] ; i++)
199 	{
200 		Tmp[i+2] = Buff[i] ;
201 		Xor ^= Buff[i] ;
202 	}
203 	Tmp[Len+2] = Xor ;
204 
205 	upsdebug_hex(4, "->UPS", Tmp, Len+3) ;
206 
207 	/* flush serial port */
208 	ser_flush_in(upsfd, "", 0) ; /* empty input buffer */
209 	ser_send_buf(upsfd, Tmp, Len+3) ; /* send data to the UPS */
210 }
211 
CmdSerial(unsigned char * OutBuffer,size_t Len,unsigned char * RetBuffer)212 static unsigned char * CmdSerial(unsigned char *OutBuffer, size_t Len, unsigned char *RetBuffer)
213 {
214 	#define TMP_BUFF_LEN	1024
215 	unsigned char InpBuff[TMP_BUFF_LEN+1] ;
216 	unsigned char TmpBuff[3] ;
217 	int i, ErrCode ;
218 	unsigned char *p ;
219 	size_t BuffLen ;
220 
221 	/* The default error code (no received character) */
222 	ErrCode = ERR_COM_NO_CHARS ;
223 
224 	SendCmdToSerial(OutBuffer, Len) ;
225 	usleep(10000) ; /* small delay (1/100 s) */
226 
227 	/* get chars until timeout */
228 	BuffLen = 0 ;
229 	while (ser_get_char(upsfd, TmpBuff, 0, 10000) == 1)
230 		{
231 		InpBuff[BuffLen++] = TmpBuff[0] ;
232 		if (BuffLen > TMP_BUFF_LEN)
233 			break ;
234 		}
235 
236 	upsdebug_hex(4, "UPS->", InpBuff, BuffLen) ;
237 
238 	if (BuffLen > 0)
239 		{
240 		ErrCode = CheckDataChecksum(InpBuff, BuffLen) ;
241 		/* upsdebugx(4, "ErrCode = %d / Len = %d", ErrCode, BuffLen); */
242 
243 		if (!ErrCode)
244 			{
245 			/* FramePointer to valid data! */
246 			p = InpBuff + ups.FramePointer ;
247 			/* p now point to valid data.
248 			 check if it is a error code. */
249 			ErrCode = CheckErrCode(p) ;
250 			if (!ErrCode)
251 				{
252 				/* I copy the data read in the buffer */
253 				for(i=0 ; i<(int) (p[1])+3  ; i++)
254 					RetBuffer[i] = p[i] ;
255 				ups.ErrCode = ups.ErrCount = ups.CommStatus = 0 ;
256 				return(RetBuffer) ;
257 				}
258 			}
259 		}
260 
261 	/* if they have arrived here, wants to say that I have found an error.... */
262 	ups.ErrCode = ErrCode ;
263 	ups.ErrCount++ ;
264 	if (ups.ErrCount > 3)
265 		{
266 		ups.CommStatus &= 0x80 ;
267 		ups.CommStatus |= (unsigned char) (ups.ErrCount & 0x7F)  ;
268 			if (ups.ErrCount > 100)
269 			ups.ErrCount = 100 ;
270 		}
271 	return(NULL) ;	/* There have been errors in the reading of the data */
272 }
273 
detect_hardware(void)274 static int detect_hardware(void)
275 {
276 	unsigned char OutBuff[20] ;
277 	unsigned char InpBuff[260] ;
278 	unsigned char *p ;
279 	int i, retries ;
280 	struct tm *Time ;
281 	struct tm tmbuf;
282 	time_t lTime ;
283 
284 	ups.ge_2kVA = 0 ;
285 
286 	for (retries=0 ; retries <= 4 ; retries++)
287 		{
288 		/* Identify UPS model */
289 		OutBuff[0] = CMD_GET_EEP_BLOCK ;		/* get EEPROM data */
290 		OutBuff[1] = EEP_UPS_MODEL ;			/* UPS model */
291 		OutBuff[2] = 8 ;							/* number of bytes */
292 		if ((p = CmdSerial(OutBuff, LEN_GET_EEP_BLOCK, InpBuff)) != NULL)
293 			{
294 			/* got UPS model */
295 			for (i=0 ; i<8 ; i++)
296 				ups.UpsModel[i] = (char)p[i+5] ;
297 			ups.UpsModel[8] = '\0' ;
298 			upsdebugx(2, "get 'UPS model': %s", PrintErr(ups.ErrCode));
299 			break ;	/* UPS identified: exit from ' for' LOOP */
300 			}
301 		else
302 			{
303 			upsdebugx(1, "[%d] get 'UPS model': %s", retries, PrintErr(ups.ErrCode));
304 			upslogx(LOG_ERR, "[%d] Unable to identify UPS model [%s]", retries, PrintErr(ups.ErrCode));
305 			usleep(100000) ; /* small delay (1/10 s) for next retry */
306 			}
307 		}
308 
309 	/* check if I was unable to find the UPS */
310 	if (retries == 4)	/* UPS not found! */
311 		return -1;
312 
313 	/* UPS serial number */
314 	OutBuff[0] = CMD_GET_EEP_BLOCK ;		/* get EEPROM data */
315 	OutBuff[1] = EEP_SERIAL_NUM ;			/* UPS serial # */
316 	OutBuff[2] = 8 ;							/* number of bytes */
317 	if ((p = CmdSerial(OutBuff, LEN_GET_EEP_BLOCK, InpBuff)) != NULL)
318 		{
319 		/* got UPS serial # */
320 		for (i=0 ; i<8 ; i++)
321 			ups.SerialNumber[i] = (char)p[i+5] ;
322 		ups.SerialNumber[8] = '\0' ;
323 		upsdebugx(2, "get 'UPS Serial #': %s", PrintErr(ups.ErrCode));
324 		}
325 	else
326 		{
327 		upsdebugx(1, "get 'UPS Serial #': %s", PrintErr(ups.ErrCode));
328 		upslogx(LOG_ERR, "Unable to identify UPS serial # [%s]", PrintErr(ups.ErrCode));
329 		return -1;
330 		}
331 
332 
333 	/* Get Production date & FW info */
334 	OutBuff[0] = CMD_GET_EEP_BLOCK ;		/* get EEPROM data */
335 	OutBuff[1] = EEP_PROD_DATE ;			/* Production date + HW version */
336 	OutBuff[2] = 8 ;							/* number of bytes */
337 	if ((p = CmdSerial(OutBuff, LEN_GET_EEP_BLOCK, InpBuff)) != NULL)
338 		{
339 		/* got Production date & FW info */
340 		p += 5 ;	/* 'p' points to eeprom data */
341 		ups.YearOfProd = 2000 + p[0] ;  /* Production year of the UPS */
342 		ups.MonthOfProd = p[1] ;        /* Production month of the UPS */
343 		ups.DayOfProd = p[2] ;          /* Production day of the UPS */
344 		ups.HW_MajorVersion = (p[3]>>4) & 0x0F ;  /* Hardware: Major version */
345 		ups.HW_MinorVersion = (p[3] & 0x0F)    ;  /* Hardware: Minor version */
346 		ups.BR_MajorVersion = (p[4]>>4) & 0x0F ;  /* BoardHardware: Major version */
347 		ups.BR_MinorVersion = (p[4] & 0x0F)    ;  /* BoardHardware: Minor version */
348 		ups.FW_MajorVersion = (p[5]>>4) & 0x0F ;  /* Firmware: Major version */
349 		ups.FW_MinorVersion = (p[5] & 0x0F)    ;  /* Firmware: Minor version */
350 		ups.FW_SubVersion = p[6] ;      /* Firmware: SUBVERSION (special releases */
351 		ups.BatteryNumber = p[7] ;      /* number of batteries in UPS */
352 		upsdebugx(2, "get 'Production date': %s", PrintErr(ups.ErrCode));
353 		}
354 	else
355 		{
356 		upsdebugx(1, "get 'Production date': %s", PrintErr(ups.ErrCode));
357 		upslogx(LOG_ERR, "Unable to read Production date [%s]", PrintErr(ups.ErrCode));
358 		return -1;
359 		}
360 
361 
362 	/* Get Battery substitution date */
363 	OutBuff[0] = CMD_GET_EEP_BLOCK ;		/* get EEPROM data */
364 	OutBuff[1] = EEP_BATT_SUBST ;			/* Battery substitution dates */
365 	OutBuff[2] = 8 ;							/* number of bytes */
366 	if ((p = CmdSerial(OutBuff, LEN_GET_EEP_BLOCK, InpBuff)) != NULL)
367 		{
368 		/* got Battery substitution date */
369 		p += 5 ;	/* 'p' points to eeprom data */
370 		upsdebugx(2, "get 'Battery Subst. Dates': %s", PrintErr(ups.ErrCode));
371 		}
372 	else
373 		{
374 		upsdebugx(1, "get 'Battery Subst. Dates': %s", PrintErr(ups.ErrCode));
375 		upslogx(LOG_ERR, "Unable to read Battery Subst. Dates [%s]", PrintErr(ups.ErrCode));
376 		return -1;
377 		}
378 
379 
380 	/* Get working time (battery+normal)) */
381 	OutBuff[0] = CMD_GET_EEP_BLOCK ;		/* get EEPROM data */
382 	OutBuff[1] = EEP_MIN_VBATT ;			/* working time */
383 	OutBuff[2] = 8 ;							/* number of bytes */
384 	if ((p = CmdSerial(OutBuff, LEN_GET_EEP_BLOCK, InpBuff)) != NULL)
385 		{
386 		/* got working time (battery+normal)) */
387 		p += 5 ;	/* 'p' points to eeprom data */
388 		upsdebugx(2, "get 'UPS life info': %s", PrintErr(ups.ErrCode));
389 		}
390 	else
391 		{
392 		upsdebugx(1, "get 'UPS life info': %s", PrintErr(ups.ErrCode));
393 		upslogx(LOG_ERR, "Unable to read UPS life info [%s]", PrintErr(ups.ErrCode));
394 		return -1;
395 		}
396 
397 
398 	/* Get the THRESHOLD table (0) */
399 	OutBuff[0] = CMD_GET_EEP_BLOCK ;		/* get EEPROM data */
400 	OutBuff[1] = EEP_THRESHOLD_0 ;		/* Thresholds table 0 */
401 	OutBuff[2] = 8 ;							/* number of bytes */
402 	if ((p = CmdSerial(OutBuff, LEN_GET_EEP_BLOCK, InpBuff)) != NULL)
403 		{
404 		/* got the THRESHOLD table (0) */
405 		p += 5 ;	/* 'p' points to eeprom data */
406 		upsdebugx(2, "get 'Thresholds table 0': %s", PrintErr(ups.ErrCode));
407 		}
408 	else
409 		{
410 		upsdebugx(1, "get 'Thresholds table 0': %s", PrintErr(ups.ErrCode));
411 		upslogx(LOG_ERR, "Unable to read Thresholds table 0 [%s]", PrintErr(ups.ErrCode));
412 		return -1;
413 		}
414 
415 
416 	/* Get the THRESHOLD table (1) */
417 	OutBuff[0] = CMD_GET_EEP_BLOCK ;		/* get EEPROM data */
418 	OutBuff[1] = EEP_THRESHOLD_1 ;		/* Thresholds table 0 */
419 	OutBuff[2] = 8 ;							/* number of bytes */
420 	if ((p = CmdSerial(OutBuff, LEN_GET_EEP_BLOCK, InpBuff)) != NULL)
421 		{
422 		/* got the THRESHOLD table (1) */
423 		p += 5 ;	/* 'p' points to eeprom data */
424 		upsdebugx(2, "get 'Thresholds table 1': %s", PrintErr(ups.ErrCode));
425 		}
426 	else
427 		{
428 		upsdebugx(1, "get 'Thresholds table 1': %s", PrintErr(ups.ErrCode));
429 		upslogx(LOG_ERR, "Unable to read Thresholds table 1 [%s]", PrintErr(ups.ErrCode));
430 		return -1;
431 		}
432 
433 
434 	/* Get the THRESHOLD table (2) */
435 	OutBuff[0] = CMD_GET_EEP_BLOCK ;		/* get EEPROM data */
436 	OutBuff[1] = EEP_THRESHOLD_2 ;		/* Thresholds table 0 */
437 	OutBuff[2] = 8 ;							/* number of bytes */
438 	if ((p = CmdSerial(OutBuff, LEN_GET_EEP_BLOCK, InpBuff)) != NULL)
439 		{
440 		/* got the THRESHOLD table (2) */
441 		p += 5 ;	/* 'p' points to eeprom data */
442 		upsdebugx(2, "get 'Thresholds table 2': %s", PrintErr(ups.ErrCode));
443 		}
444 	else
445 		{
446 		upsdebugx(1, "get 'Thresholds table 2': %s", PrintErr(ups.ErrCode));
447 		upslogx(LOG_ERR, "Unable to read Thresholds table 2 [%s]", PrintErr(ups.ErrCode));
448 		return -1;
449 		}
450 
451 
452 	/* Get Option Bytes */
453 	OutBuff[0] = CMD_GET_EEP_BLOCK ;			/* get EEPROM data */
454 	OutBuff[1] = EEP_OPT_BYTE_BLK ;		/* Option Bytes */
455 	OutBuff[2] = 8 ;					/* number of bytes */
456 	if ((p = CmdSerial(OutBuff, LEN_GET_EEP_BLOCK, InpBuff)) != NULL)
457 		{
458 		/* got Option Bytes */
459 		p += 5 ;	/* 'p' points to eeprom data */
460 		dstate_setinfo("input.voltage.nominal", "%s", (p[EEP_OPT_BYTE_1] & 0x02) ? "110": "230") ;
461 		dstate_setinfo("input.frequency", "%s", (p[EEP_OPT_BYTE_1] & 0x01) ? "60.0": "50.0") ;
462 		upsdebugx(2, "get 'Option Bytes': %s", PrintErr(ups.ErrCode));
463 		}
464 	else
465 		{
466 		upsdebugx(1, "get 'Option Bytes': %s", PrintErr(ups.ErrCode));
467 		upslogx(LOG_ERR, "Unable to read Option Bytes [%s]", PrintErr(ups.ErrCode));
468 		return -1;
469 		}
470 
471 
472 	/* Get UPS sensitivity (fault points) */
473 	OutBuff[0] = CMD_GET_EEP_BLOCK ;		/* get EEPROM data */
474 	OutBuff[1] = EEP_FAULT_POINTS ;		/* Number of fault points (sensitivity)) */
475 	OutBuff[2] = 8 ;							/* number of bytes */
476 	if ((p = CmdSerial(OutBuff, LEN_GET_EEP_BLOCK, InpBuff)) != NULL)
477 		{
478 		/* got UPS sensitivity (fault points) */
479 		p += 5 ;	/* 'p' points to eeprom data */
480 		switch (p[0]) {
481 			case 1 : dstate_setinfo("input.sensitivity", "H") ; break ;
482 			case 2 : dstate_setinfo("input.sensitivity", "M") ; break ;
483 			case 3 : dstate_setinfo("input.sensitivity", "L") ; break ;
484 			default : dstate_setinfo("input.sensitivity", "L") ; break ;
485 			}
486 		upsdebugx(2, "get 'Input Sensitivity': %s", PrintErr(ups.ErrCode));
487 		}
488 	else
489 		{
490 		upsdebugx(1, "get 'Input Sensitivity': %s", PrintErr(ups.ErrCode));
491 		upslogx(LOG_ERR, "Unable to read Input Sensitivity [%s]", PrintErr(ups.ErrCode));
492 		return -1;
493 		}
494 
495 
496 	/* Set internal UPS clock */
497 	time(&lTime) ;
498 	Time = localtime_r(&lTime, &tmbuf);
499 
500 	OutBuff[0] = CMD_SET_TIMER ;	/* set UPS internal timer */
501 	OutBuff[1] = (Time->tm_wday+6) % 7 ;	/* week day (0=monday) */
502 	OutBuff[2] = (unsigned char)Time->tm_hour ;	/* hours */
503 	OutBuff[3] = (unsigned char)Time->tm_min ;	/* minutes */
504 	OutBuff[4] = (unsigned char)Time->tm_sec ;	/* seconds */
505 	if ((p = CmdSerial(OutBuff, LEN_SET_TIMER, InpBuff)) != NULL)
506 		{
507 		upsdebugx(2, "set 'UPS internal clock': %s", PrintErr(ups.ErrCode));
508 		}
509 	else
510 		{
511 		upsdebugx(1, "set 'UPS internal clock': %s", PrintErr(ups.ErrCode));
512 		upslogx(LOG_ERR, "Unable to set UPS internal clock [%s]", PrintErr(ups.ErrCode));
513 		return -1;
514 		}
515 
516 	return 0;	/* everything was OK */
517 }
518 
519 
520 /* ========================= */
521 
522 
upsdrv_updateinfo(void)523 void upsdrv_updateinfo(void)
524 {
525 	unsigned char OutBuff[20] ;
526 	unsigned char InpBuff[260] ;
527 	unsigned char *p ;
528 	/* int i ; */
529 
530 	OutBuff[0] = CMD_GET_STATUS ;   /* get UPS status */
531 	if ((p = CmdSerial(OutBuff, LEN_GET_STATUS, InpBuff)) != NULL)
532 		{
533 		p += 3 ;	/* 'p' points to received data */
534 
535 		status_init();	/* reset status flags */
536 
537 		/* store last UPS status */
538 		ups.StatusUPS  =  (uint32_t)p[0] ;
539 		ups.StatusUPS |= ((uint32_t)p[1]<<8) ;
540 		ups.StatusUPS |= ((uint32_t)p[2]<<16) ;
541 		ups.StatusUPS |= ((uint32_t)p[3]<<24) ;
542 		ups.ShortStatus  =  (uint16_t)p[0];
543 		ups.ShortStatus |= ((uint16_t)p[1]<<8) ;
544 		upsdebugx(1, "ups.StatusUPS: %08" PRIX32, ups.StatusUPS);
545 		upsdebugx(1, "ups.ShortStatus: %04" PRIX16, ups.ShortStatus);
546 
547 		/* on battery? */
548 		if (p[0] & 0x01)
549 			status_set("OB");	/* YES */
550 
551 		/* LOW battery? */
552 		if (p[0] & 0x02)
553 			status_set("LB");	/* YES */
554 
555 		/* online? */
556 		if (p[0] & 0x08)
557 			status_set("OL");	/* YES */
558 
559 		/* Overload? */
560 		if (p[1] & 0xC0)
561 			status_set("OVER");	/* YES */
562 
563 		/* Offline/Init/Stanby/Waiting for mains? */
564 		if (p[0] & 0xE0)
565 			status_set("OFF");	/* YES */
566 
567 		/* AVR on (boost)? */
568 		if (p[4] & 0x04)
569 			status_set("BOOST");	/* YES */
570 
571 		/* AVR on (buck)? */
572 		if (p[4] & 0x08)
573 			status_set("TRIM");	/* YES */
574 
575 		dstate_setinfo("ups.time", "%02d:%02d:%02d", p[6], p[7], p[8]) ;
576 		upsdebugx(3, "get 'Get Status': %s", PrintErr(ups.ErrCode));
577 		}
578 	else
579 		{
580 		upsdebugx(1, "get 'Get Status': %s", PrintErr(ups.ErrCode));
581 		/* upslogx(LOG_ERR, "get 'Get Status': %s", PrintErr(ups.ErrCode)); */
582 		dstate_datastale();
583 		return;
584 		}
585 
586 	/* ========================= */
587 
588 	OutBuff[0] = CMD_GET_MEASURES ;   /* get UPS values */
589 	if ((p = CmdSerial(OutBuff, LEN_GET_MEASURES, InpBuff)) != NULL)
590 		{
591 		p += 3 ;	/* 'p' points to received data */
592 
593 		dstate_setinfo("input.voltage", "%d", (int)((float)(p[2]*256 + p[3]) / 36.4)) ;
594 		if (ups.ge_2kVA)
595 			{
596 			dstate_setinfo("output.voltage", "%d", (int)((float)(p[6]*256 + p[7]) / 63.8)) ;
597 			dstate_setinfo("output.current", "%1.f", ((float)(p[8]*256 + p[9]) / 635.0)) ;
598 			dstate_setinfo("battery.voltage", "%.1f", ((float) (p[4]*256 + p[5])) / 329.0) ;
599 			}
600 		else
601 			{
602 			dstate_setinfo("output.voltage", "%d", (int)((float)(p[6]*256 + p[7]) / 36.4)) ;
603 			dstate_setinfo("output.current", "%1.f", ((float)(p[8]*256 + p[9]) / 1350.0)) ;
604 			dstate_setinfo("battery.voltage", "%.1f", ((float) (p[4]*256 + p[5])) / 585.0) ;
605 			}
606 
607 		dstate_setinfo("ups.temperature", "%d", (int)(((float)(p[10]*256 + p[11])-202.97) / 1.424051)) ;
608 		upsdebugx(3, "get 'Get Measures': %s", PrintErr(ups.ErrCode));
609 		}
610 	else
611 		{
612 		/* upsdebugx(1, "get 'Get Measures': %s", PrintErr(ups.ErrCode)); */
613 		upslogx(LOG_ERR, "get 'Get Measures': %s", PrintErr(ups.ErrCode));
614 		dstate_datastale();
615 		return;
616 		}
617 
618 	/* ========================= */
619 
620 	OutBuff[0] = CMD_GET_BAT_LD ;   /* get UPS Battery and Load values */
621 	if ((p = CmdSerial(OutBuff, LEN_GET_BAT_LD, InpBuff)) != NULL)
622 		{
623 		p += 3 ;	/* 'p' points to received data */
624 
625 		dstate_setinfo("ups.power", "%d", (p[4]*256 + p[5])) ;
626 		/* dstate_setinfo("ups.realpower", "%d", (int)((float)(p[4]*256 + p[5]) * 0.6)) ; */
627 		dstate_setinfo("battery.charge", "%d", (int)p[0]) ;
628 		dstate_setinfo("ups.load", "%d", (int)p[6]) ;
629 		upsdebugx(3, "get 'Get Batt+Load Status': %s", PrintErr(ups.ErrCode));
630 		}
631 	else
632 		{
633 		/* upsdebugx(1, "get 'Get Batt+Load Status': %s", PrintErr(ups.ErrCode)); */
634 		upslogx(LOG_ERR, "get 'Get Batt+Load Status': %s", PrintErr(ups.ErrCode));
635 		dstate_datastale();
636 		return;
637 		}
638 
639 	status_commit();
640 	dstate_dataok();
641 
642 	poll_interval = 2;
643 }
644 
645 
646 /* ========================= */
647 
648 
instcmd(const char * cmdname,const char * extra)649 int instcmd(const char *cmdname, const char *extra)
650 {
651 	unsigned char OutBuff[20] ;
652 	unsigned char InpBuff[260] ;
653 	unsigned char *p ;
654 	/* int i ; */
655 
656 	upsdebugx(1, "instcmd(%s, %s)", cmdname, extra);
657 
658 
659 	if (strcasecmp(cmdname, "load.on") == 0)
660 		{
661 		OutBuff[0] = CMD_SD_ONESHOT ;   /* turn ON the outputs */
662 		OutBuff[1] = 0xFF ;		/* ALL outputs */
663 		OutBuff[2] = 0x08 ;		/* Enable outputs (immediately) */
664 		OutBuff[3] = 0 ;
665 		OutBuff[4] = 0 ;
666 		OutBuff[5] = 0 ;
667 		OutBuff[6] = 0 ;
668 		OutBuff[7] = 0 ;
669 		if ((p = CmdSerial(OutBuff, LEN_SD_ONESHOT, InpBuff)) != NULL)
670 			{
671 			p += 3 ;	/* 'p' points to received data */
672 			upslogx(LOG_INFO, "Turning load on.");
673 			upsdebugx(1, "'SdOneshot(turn_ON)': %s", PrintErr(ups.ErrCode));
674 			}
675 		else
676 			{
677 			upsdebugx(1, "'SdOneshot(turn_ON)': %s", PrintErr(ups.ErrCode));
678 			upslogx(LOG_ERR, "'SdOneshot(turn_ON)': %s", PrintErr(ups.ErrCode));
679 			}
680 		return STAT_INSTCMD_HANDLED;
681 		}
682 
683 	if (strcasecmp(cmdname, "load.off") == 0)
684 		{
685 		OutBuff[0] = CMD_SD_ONESHOT ;   /* turn ON the outputs */
686 		OutBuff[1] = 0xFF ;		/* ALL outputs */
687 		OutBuff[2] = 0x04 ;		/* Disable outputs (immediately) */
688 		OutBuff[3] = 0 ;
689 		OutBuff[4] = 0 ;
690 		OutBuff[5] = 0 ;
691 		OutBuff[6] = 0 ;
692 		OutBuff[7] = 0 ;
693 		if ((p = CmdSerial(OutBuff, LEN_SD_ONESHOT, InpBuff)) != NULL)
694 			{
695 			p += 3 ;	/* 'p' points to received data */
696 			upslogx(LOG_INFO, "Turning load on.");
697 			upsdebugx(1, "'SdOneshot(turn_OFF)': %s", PrintErr(ups.ErrCode));
698 			}
699 		else
700 			{
701 			upsdebugx(1, "'SdOneshot(turn_OFF)': %s", PrintErr(ups.ErrCode));
702 			upslogx(LOG_ERR, "'SdOneshot(turn_OFF)': %s", PrintErr(ups.ErrCode));
703 			}
704 		return STAT_INSTCMD_HANDLED;
705 		}
706 
707 
708 	if (strcasecmp(cmdname, "shutdown.return") == 0)
709 		{
710 		OutBuff[0] = CMD_SD_ONESHOT ;   /* turn ON the outputs */
711 		OutBuff[1] = 0xFF ;		/* ALL outputs */
712 		if (ups.StatusUPS & 0x01)
713 			OutBuff[2] = 0x02 ;		/* Battery shutdown */
714 		else
715 			OutBuff[2] = 0x01 ;		/* Online shutdown */
716 
717 		if (ups.ShutdownDelay < 6)
718 			ups.ShutdownDelay = 6 ;
719 
720 		OutBuff[3] = (ups.ShutdownDelay >> 8) & 0xFF ;	/* SDDELAY (MSB) 	Shutdown value (seconds) */
721 		OutBuff[4] = (ups.ShutdownDelay & 0xFF) ;			/* SDDELAY (LSB) */
722 		OutBuff[5] = (ups.WakeUpDelay >> 16) & 0xFF ;	/* WUDELAY (MSB)	Wakeup value (seconds) */
723 		OutBuff[6] = (ups.WakeUpDelay >> 8) & 0xFF ;		/* WUDELAY (...) */
724 		OutBuff[7] = (ups.WakeUpDelay & 0xFF ) ;			/* WUDELAY (LSB) */
725 
726 		if ((p = CmdSerial(OutBuff, LEN_SD_ONESHOT, InpBuff)) != NULL)
727 			{
728 			p += 3 ;	/* 'p' points to received data */
729 			upslogx(LOG_INFO, "Shutdown command(TYPE=%02x, SD=%u, WU=%u)", OutBuff[2], ups.ShutdownDelay, ups.WakeUpDelay) ;
730 			upsdebugx(3, "Shutdown command(TYPE=%02x, SD=%u, WU=%u): %s", OutBuff[2], ups.ShutdownDelay, ups.WakeUpDelay, PrintErr(ups.ErrCode));
731 			}
732 		else
733 			{
734 			upsdebugx(1, "Shutdown command(TYPE=%02x, SD=%u, WU=%u): %s", OutBuff[2], ups.ShutdownDelay, ups.WakeUpDelay, PrintErr(ups.ErrCode));
735 			upslogx(LOG_ERR, "Shutdown command(SD=%u, WU=%u): %s", ups.ShutdownDelay, ups.WakeUpDelay, PrintErr(ups.ErrCode));
736 			}
737 		return STAT_INSTCMD_HANDLED;
738 		}
739 
740 
741 	if (strcasecmp(cmdname, "shutdown.stayoff") == 0)
742 		{
743 		OutBuff[0] = CMD_SD_ONESHOT ;   /* turn ON the outputs */
744 		OutBuff[1] = 0xFF ;		/* ALL outputs */
745 		if (ups.StatusUPS & 0x01)
746 			OutBuff[2] = 0x02 ;		/* Battery shutdown */
747 		else
748 			OutBuff[2] = 0x01 ;		/* Online shutdown */
749 
750 		if (ups.ShutdownDelay < 6)
751 			ups.ShutdownDelay = 6 ;
752 
753 		OutBuff[3] = (ups.ShutdownDelay >> 8) & 0xFF ;	/* SDDELAY (MSB)	Shutdown value (seconds) */
754 		OutBuff[4] = (ups.ShutdownDelay & 0xFF) ;			/* SDDELAY (LSB) */
755 		OutBuff[5] = 0 ;	/* WUDELAY (MSB)	Wakeup value (seconds) */
756 		OutBuff[6] = 0 ;	/* WUDELAY (...) */
757 		OutBuff[7] = 0 ;	/* WUDELAY (LSB) */
758 
759 		if ((p = CmdSerial(OutBuff, LEN_SD_ONESHOT, InpBuff)) != NULL)
760 			{
761 			p += 3 ;	/* 'p' points to received data */
762 			upslogx(LOG_INFO, "shutdown.stayoff - (TYPE=%02x, SD=%u, WU=%u)", OutBuff[2], ups.ShutdownDelay, 0) ;
763 			upsdebugx(3, "shutdown.stayoff - (TYPE=%02x, SD=%u, WU=%u): %s", OutBuff[2], ups.ShutdownDelay, 0, PrintErr(ups.ErrCode));
764 			}
765 		else
766 			{
767 			upsdebugx(1, "shutdown.stayoff - (TYPE=%02x, SD=%u, WU=%u): %s", OutBuff[2], ups.ShutdownDelay, 0, PrintErr(ups.ErrCode));
768 			upslogx(LOG_ERR, "shutdown.stayoff - (TYPE=%02x, SD=%u, WU=%u)", OutBuff[2], ups.ShutdownDelay, 0) ;
769 			}
770 		return STAT_INSTCMD_HANDLED;
771 		}
772 
773 	return STAT_INSTCMD_UNKNOWN;
774 }
775 
776 #if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP_BESIDEFUNC) && (!defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP_INSIDEFUNC) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS_BESIDEFUNC) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE_BESIDEFUNC) || defined (HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_COMPARE_BESIDEFUNC) )
777 # pragma GCC diagnostic push
778 #endif
779 #if (!defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP_INSIDEFUNC) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS_BESIDEFUNC)
780 # pragma GCC diagnostic ignored "-Wtype-limits"
781 #endif
782 #if (!defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP_INSIDEFUNC) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE_BESIDEFUNC)
783 # pragma GCC diagnostic ignored "-Wtautological-constant-out-of-range-compare"
784 #endif
785 #if (!defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP_INSIDEFUNC) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_COMPARE_BESIDEFUNC)
786 # pragma GCC diagnostic ignored "-Wtautological-compare"
787 #endif
setvar(const char * varname,const char * val)788 int setvar(const char *varname, const char *val)
789 {
790 	unsigned int delay;
791 
792 	if (sscanf(val, "%u", &delay) != 1)
793 		{
794 		return STAT_SET_UNKNOWN;
795 		}
796 
797 	if (strcasecmp(varname, "ups.delay.start") == 0)
798 		{
799 #if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE) || defined (HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_COMPARE) )
800 # pragma GCC diagnostic push
801 #endif
802 #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS
803 # pragma GCC diagnostic ignored "-Wtype-limits"
804 #endif
805 #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE
806 # pragma GCC diagnostic ignored "-Wtautological-constant-out-of-range-compare"
807 #endif
808 #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_COMPARE
809 # pragma GCC diagnostic ignored "-Wtautological-compare"
810 #endif
811 /* Older CLANG (e.g. clang-3.4) seems to not support the GCC pragmas above */
812 #ifdef __clang__
813 #pragma clang diagnostic push
814 #pragma clang diagnostic ignored "-Wtautological-compare"
815 #pragma clang diagnostic ignored "-Wtautological-constant-out-of-range-compare"
816 #endif
817 		delay = CLAMP(delay, 0, MAX_START_DELAY);
818 #ifdef __clang__
819 #pragma clang diagnostic pop
820 #endif
821 #if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE) || defined (HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_COMPARE) )
822 # pragma GCC diagnostic pop
823 #endif
824 		upsdebugx(1, "set 'WUDELAY': %u/%u", delay, ups.WakeUpDelay);
825 		ups.WakeUpDelay = delay ;
826 		dstate_setinfo("ups.delay.start", "%u", ups.WakeUpDelay);
827 		dstate_dataok();
828 		return STAT_SET_HANDLED;
829 		}
830 
831 	if (strcasecmp(varname, "ups.delay.shutdown") == 0)
832 		{
833 #if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE) || defined (HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_COMPARE) )
834 # pragma GCC diagnostic push
835 #endif
836 #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS
837 # pragma GCC diagnostic ignored "-Wtype-limits"
838 #endif
839 #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE
840 # pragma GCC diagnostic ignored "-Wtautological-constant-out-of-range-compare"
841 #endif
842 #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_COMPARE
843 # pragma GCC diagnostic ignored "-Wtautological-compare"
844 #endif
845 /* Older CLANG (e.g. clang-3.4) seems to not support the GCC pragmas above */
846 #ifdef __clang__
847 #pragma clang diagnostic push
848 #pragma clang diagnostic ignored "-Wtautological-compare"
849 #pragma clang diagnostic ignored "-Wtautological-constant-out-of-range-compare"
850 #endif
851 		delay = CLAMP(delay, 0, MAX_SHUTDOWN_DELAY);
852 #ifdef __clang__
853 #pragma clang diagnostic pop
854 #endif
855 #if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE) || defined (HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_COMPARE) )
856 # pragma GCC diagnostic pop
857 #endif
858 		upsdebugx(1, "set 'SDDELAY': %u/%u", delay, ups.ShutdownDelay);
859 		ups.ShutdownDelay = delay;
860 		dstate_setinfo("ups.delay.shutdown", "%u", ups.ShutdownDelay);
861 		dstate_dataok();
862 		return STAT_SET_HANDLED;
863 		}
864 
865 	return STAT_SET_UNKNOWN;
866 }
867 #if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP_BESIDEFUNC) && (!defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP_INSIDEFUNC) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS_BESIDEFUNC) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE_BESIDEFUNC) || defined (HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_COMPARE_BESIDEFUNC) )
868 # pragma GCC diagnostic pop
869 #endif
870 
upsdrv_initinfo(void)871 void upsdrv_initinfo(void)
872 {
873 	/* Get vars from ups.conf */
874 	if (getval("ups.delay.shutdown")) {
875 		int ipv = atoi(getval("ups.delay.shutdown"));
876 		ups.ShutdownDelay = (unsigned int) CLAMP(ipv, 0, MAX_SHUTDOWN_DELAY);
877 	}
878 	else {
879 		ups.ShutdownDelay = 120;	/* Shutdown delay in seconds */
880 	}
881 
882 	if (getval("ups.delay.start")) {
883 		int ipv = atoi(getval("ups.delay.start"));
884 		ups.WakeUpDelay = (unsigned int) CLAMP(ipv, 0, MAX_START_DELAY);
885 	}
886 	else {
887 		ups.WakeUpDelay = 10;	/* WakeUp delay in seconds */
888 	}
889 
890 	if (detect_hardware() == -1)
891 		{
892 		fatalx(EXIT_FAILURE,
893 		       "Unable to detect a Microdowell's  Enterprise UPS on port %s\nCheck the cable, port name and try again", device_path);
894 		}
895 
896 	/* I set the correspondig UPS variables
897 	   They were read in 'detect_hardware()'
898 	   some other variables were set in 'detect_hardware()' */
899 	dstate_setinfo("ups.model", "Enterprise N%s", ups.UpsModel+3) ;
900 	dstate_setinfo("ups.power.nominal", "%d", atoi(ups.UpsModel+3) * 100) ;
901 	dstate_setinfo("ups.realpower.nominal", "%d", atoi(ups.UpsModel+3) * 60) ;
902 
903 	ups.ge_2kVA = 0 ;		/* differentiate between 2 type of UPSs */
904 	if (atoi(ups.UpsModel+3) >= 20)
905 		ups.ge_2kVA = 1 ;
906 
907 	dstate_setinfo("ups.type", "online-interactive") ;
908 	dstate_setinfo("ups.serial", "%s", ups.SerialNumber) ;
909 	dstate_setinfo("ups.firmware", "%d.%d (%d)", ups.FW_MajorVersion, ups.FW_MinorVersion, ups.FW_SubVersion) ;
910 	dstate_setinfo("ups.firmware.aux", "%d.%d %d.%d", ups.HW_MajorVersion, ups.HW_MinorVersion,
911 						ups.BR_MajorVersion, ups.BR_MinorVersion) ;
912 	dstate_setinfo("ups.mfr", "Microdowell") ;
913 	dstate_setinfo("ups.mfr.date", "%04d/%02d/%02d", ups.YearOfProd, ups.MonthOfProd, ups.DayOfProd) ;
914 	dstate_setinfo("battery.packs", "%d", ups.BatteryNumber) ;
915 
916 	/* Register the available variables. */
917 	dstate_setinfo("ups.delay.start", "%d", ups.WakeUpDelay);
918 	dstate_setflags("ups.delay.start", ST_FLAG_RW | ST_FLAG_STRING);
919 	dstate_setaux("ups.delay.start", MAX_START_DELAY_LEN);
920 
921 	dstate_setinfo("ups.delay.shutdown", "%d", ups.ShutdownDelay);
922 	dstate_setflags("ups.delay.shutdown", ST_FLAG_RW | ST_FLAG_STRING);
923 	dstate_setaux("ups.delay.shutdown", MAX_SHUTDOWN_DELAY_LEN);
924 
925 	dstate_addcmd("load.on");
926 	dstate_addcmd("load.off");
927 	dstate_addcmd("shutdown.return");
928 	dstate_addcmd("shutdown.stayoff");
929 
930 	/* Register the available instant commands. */
931 /*
932 	dstate_addcmd("test.battery.start");
933 	dstate_addcmd("test.battery.stop");
934 	dstate_addcmd("shutdown.stop");
935 	dstate_addcmd("beeper.toggle");
936 */
937 
938 	/* set handlers */
939 	upsh.instcmd = instcmd ;
940 	upsh.setvar = setvar;
941 }
942 
upsdrv_shutdown(void)943 void upsdrv_shutdown(void)
944 {
945 	unsigned char OutBuff[20] ;
946 	unsigned char InpBuff[260] ;
947 	unsigned char *p ;
948 	unsigned char BatteryFlag=0 ;
949 
950 	OutBuff[0] = CMD_GET_STATUS ;   /* get UPS status */
951 	if ((p = CmdSerial(OutBuff, LEN_GET_STATUS, InpBuff)) != NULL)
952 		{
953 		p += 3 ;	/* 'p' points to received data */
954 
955 		status_init();	/* reset status flags */
956 
957 		/* store last UPS status */
958 		ups.StatusUPS  =  (uint32_t)p[0] ;
959 		ups.StatusUPS |= ((uint32_t)p[1]<<8) ;
960 		ups.StatusUPS |= ((uint32_t)p[2]<<16) ;
961 		ups.StatusUPS |= ((uint32_t)p[3]<<24) ;
962 		ups.ShortStatus  =  (uint16_t)p[0] ;
963 		ups.ShortStatus |= ((uint16_t)p[1]<<8) ;
964 		upsdebugx(1, "ups.StatusUPS: %08" PRIX32, ups.StatusUPS);
965 		upsdebugx(1, "ups.ShortStatus: %04" PRIX16, ups.ShortStatus);
966 
967 		/* on battery? */
968 		if (p[0] & 0x01)
969 			BatteryFlag = 1 ;	/* YES */
970 		upsdebugx(3, "get 'Get Status': %s", PrintErr(ups.ErrCode));
971 		}
972 	else
973 		{
974 		upsdebugx(1, "get 'Get Status': %s", PrintErr(ups.ErrCode));
975 		/* upslogx(LOG_ERR, "get 'Get Status': %s", PrintErr(ups.ErrCode)); */
976 		}
977 
978 
979 	/* Send SHUTDOWN command */
980 	OutBuff[0] = CMD_SD_ONESHOT ;	/* Send SHUTDOWN command */
981 	OutBuff[1] = 0xFF ;				/* shutdown on ALL ports */
982 
983 	/* is the UPS on battery? */
984 	if (BatteryFlag)
985 		OutBuff[2] = 0x02 ;	/* Type of shutdown (BATTERY MODE) */
986 	else
987 		OutBuff[2] = 0x01 ;	/* Type of shutdown (ONLINE) */
988 
989 	if (ups.ShutdownDelay < 6)
990 		ups.ShutdownDelay = 6 ;
991 
992 	OutBuff[3] = (ups.ShutdownDelay >> 8) & 0xFF ;	/* SDDELAY (MSB)	Shutdown value (seconds) */
993 	OutBuff[4] = (ups.ShutdownDelay & 0xFF) ;			/* SDDELAY (LSB) */
994 	OutBuff[5] = (ups.WakeUpDelay >> 16) & 0xFF ;	/* WUDELAY (MSB)	Wakeup value (seconds) */
995 	OutBuff[6] = (ups.WakeUpDelay >> 8) & 0xFF ;		/* WUDELAY (...) */
996 	OutBuff[7] = (ups.WakeUpDelay & 0xFF ) ;			/* WUDELAY (LSB) */
997 
998 	if ((p = CmdSerial(OutBuff, LEN_SD_ONESHOT, InpBuff)) != NULL)
999 		{
1000 		upsdebugx(2, "Shutdown command(TYPE=%02x, SD=%u, WU=%u): %s", OutBuff[2], ups.ShutdownDelay, ups.WakeUpDelay, PrintErr(ups.ErrCode));
1001 		}
1002 	else
1003 		{
1004 		/* command not sent: print error code */
1005 		upsdebugx(1, "Shutdown command(TYPE=%02x, SD=%u, WU=%u): %s", OutBuff[2], ups.ShutdownDelay, ups.WakeUpDelay, PrintErr(ups.ErrCode));
1006 		upslogx(LOG_ERR, "Shutdown command(SD=%u, WU=%u): %s", ups.ShutdownDelay, ups.WakeUpDelay, PrintErr(ups.ErrCode));
1007 		}
1008 }
1009 
1010 
upsdrv_help(void)1011 void upsdrv_help(void)
1012 {
1013 }
1014 
1015 /* list flags and values that you want to receive via -x */
upsdrv_makevartable(void)1016 void upsdrv_makevartable(void)
1017 {
1018 	/* allow '-x xyzzy' */
1019 	/* addvar(VAR_FLAG, "xyzzy", "Enable xyzzy mode"); */
1020 
1021 	/* allow '-x foo=<some value>' */
1022 	addvar(VAR_VALUE, "ups.delay.shutdown", "Override shutdown delay (120s)");
1023 	addvar(VAR_VALUE, "ups.delay.start", "Override restart delay (10s)");
1024 }
1025 
upsdrv_initups(void)1026 void upsdrv_initups(void)
1027 {
1028 	upsfd = ser_open(device_path) ;
1029 
1030 	ser_set_speed(upsfd, device_path, B19200) ;
1031 
1032 	/* need to clear RTS and DTR: otherwise with default cable, communication will be problematic
1033 	   It is the same as removing pin7 from cable (pin 7 is needed for Plug&Play compatibility) */
1034 	ser_set_dtr(upsfd, 0);
1035 	ser_set_rts(upsfd, 0);
1036 
1037 	usleep(10000) ; /* small delay (1/100 s)) */
1038 }
1039 
upsdrv_cleanup(void)1040 void upsdrv_cleanup(void)
1041 {
1042 	/* free(dynamic_mem); */
1043 	ser_close(upsfd, device_path) ;
1044 }
1045