1 /* mge-utalk.c - monitor MGE UPS for NUT with UTalk protocol
3 * Copyright (C) 2002 - 2005
4 * Arnaud Quette <arnaud.quette@gmail.com>
5 * Hans Ekkehard Plesser <hans.plesser@itf.nlh.no>
6 * Martin Loyer <martin@degraaf.fr>
7 * Patrick Agrain <patrick.agrain@alcatel.fr>
8 * Nicholas Reilly <nreilly@magma.ca>
9 * Dave Abbott <d.abbott@dcs.shef.ac.uk>
10 * Marek Kralewski <marek@mercy49.de>
12 * This driver is a collaborative effort by the above people,
13 * Sponsored by MGE UPS SYSTEMS
15 * This program is free software; you can redistribute it and/or modify
16 * it under the terms of the GNU General Public License as published by
17 * the Free Software Foundation; either version 2 of the License, or
18 * (at your option) any later version.
20 * This program is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
25 * You should have received a copy of the GNU General Public License
26 * along with this program; if not, write to the Free Software
27 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
32 * IMPLEMENTATION DETAILS
34 * Not all UTalk models provide all possible information, settings and commands.
35 * mge-utalk checks on startup which variables and commands are available from
36 * the UPS, and re-reads these regularly. Thus, startup is a bit slow, but this
37 * should not matter much.
39 * mge-utalk.h defines a struct array that tells the driver how to read
40 * variables from the UPS and publish them as NUT data.
42 * "ups.status" variable is not included in this array, since it contains
43 * information that requires several calls to the UPS and more advanced analysis
44 * of the reponses. The function get_ups_status does this job.
46 * Note that MGE enumerates the status "bits" from right to left,
47 * i.e., if buf[] contains the reponse to command "Ss" (read system status),
48 * then buf[0] contains "bit" Ss.1.7 (General alarm), while buf[7] contains
49 * "bit" Ss.1.0 (Load unprotected).
51 * enable_ups_comm() is called before each attempt to read/write data
52 * from/to the UPS to re synchronise the communication.
56 #include <sys/ioctl.h>
60 #include "mge-utalk.h"
62 /* --------------------------------------------------------------- */
63 /* Define "technical" constants */
64 /* --------------------------------------------------------------- */
66 #define DRIVER_NAME "MGE UPS SYSTEMS/U-Talk driver"
67 #define DRIVER_VERSION "0.93"
70 /* driver description structure */
71 upsdrv_info_t upsdrv_info
= {
74 "Arnaud Quette <ArnaudQuette@gmail.com>\n" \
75 "Hans Ekkehard Plesser <hans.plesser@itf.nlh.no>\n" \
76 "Martin Loyer <martin@degraaf.fr>\n" \
77 "Patrick Agrain <patrick.agrain@alcatel.fr>\n" \
78 "Nicholas Reilly <nreilly@magma.ca>\n" \
79 "Dave Abbott <d.abbott@dcs.shef.ac.uk>\n" \
80 "Marek Kralewski <marek@mercy49.de>",
85 /* delay after sending each char to UPS (in MICROSECONDS) */
86 #define MGE_CHAR_DELAY 0
88 /* delay after command, before reading UPS reply (in MICROSECONDS) */
89 #define MGE_REPLY_DELAY 1000
91 /* delay after enable_ups_comm */
92 #define MGE_CONNECT_DELAY 500000
94 #define MGE_COMMAND_ENDCHAR "\r\n" /* some UPS need \r and \n */
95 #define MGE_REPLY_ENDCHAR '\r'
96 #define MGE_REPLY_IGNCHAR "\r\n"
98 #define MAXTRIES 10 /* max number of connect tries */
104 int sdtype
= SD_RETURN
;
105 static time_t lastpoll
; /* Timestamp the last polling */
107 /* --------------------------------------------------------------- */
108 /* Structure with information about UPS */
109 /* --------------------------------------------------------------- */
113 int LowBatt
; /* percent */
114 int OnDelay
; /* minutes */
115 int OffDelay
; /* seconds */
116 } mge_ups
= { 0, DEFAULT_LOWBATT
, DEFAULT_ONDELAY
, DEFAULT_OFFDELAY
};
119 /* --------------------------------------------------------------- */
120 /* Declaration of internal functions */
121 /* --------------------------------------------------------------- */
123 static int instcmd(const char *cmdname
, const char *extra
);
124 static int setvar(const char *varname
, const char *val
);
125 static void enable_ups_comm(void);
126 static void disable_ups_comm(void);
127 static void extract_info(const char *buf
, const mge_info_item_t
*mge
,
128 char *infostr
, int infolen
);
129 static const char *info_variable_cmd(const char *type
);
130 static bool_t
info_variable_ok(const char *type
);
131 static int get_ups_status(void);
132 static int mge_command(char *reply
, int replylen
, const char *fmt
, ...);
134 /* --------------------------------------------------------------- */
135 /* UPS Driver Functions */
136 /* --------------------------------------------------------------- */
138 void upsdrv_makevartable(void)
142 snprintf(temp
, sizeof(temp
),
143 "Low battery level, in %% (default = %3d)",
145 addvar (VAR_VALUE
, "LowBatt", temp
);
147 snprintf(temp
, sizeof(temp
),
148 "Delay before startup, in minutes (default = %3d)",
150 addvar (VAR_VALUE
, "OnDelay", temp
);
152 snprintf(temp
, sizeof(temp
),
153 "Delay before shutdown, in seconds (default = %3d)",
155 addvar (VAR_VALUE
, "OffDelay", temp
);
157 addvar(VAR_FLAG
, "oldmac", "Enable Oldworld Apple Macintosh support");
160 /* --------------------------------------------------------------- */
162 void upsdrv_initups(void)
167 upsfd
= ser_open(device_path
);
168 ser_set_speed(upsfd
, device_path
, B2400
);
170 /* read command line/conf variable that affect comm. */
171 if (testvar ("oldmac"))
174 /* Init serial line */
175 ioctl(upsfd
, TIOCMBIC
, &RTS
);
178 /* Try to set "Low Battery Level" (if supported and given) */
179 if (getval ("lowbatt"))
181 mge_ups
.LowBatt
= atoi (getval ("lowbatt"));
182 /* Set the value in the UPS */
183 mge_command(buf
, sizeof(buf
), "Bl %d", mge_ups
.LowBatt
);
184 if(!strcmp(buf
, "OK"))
185 upsdebugx(1, "Low Battery Level set to %d%%", mge_ups
.LowBatt
);
187 upsdebugx(1, "initups: Low Battery Level cannot be set");
190 /* Try to set "ON delay" (if supported and given) */
191 if (getval ("ondelay"))
193 mge_ups
.OnDelay
= atoi (getval ("ondelay"));
194 /* Set the value in the UPS */
195 mge_command(buf
, sizeof(buf
), "Sm %d", mge_ups
.OnDelay
);
196 if(!strcmp(buf
, "OK"))
197 upsdebugx(1, "ON delay set to %d min", mge_ups
.OnDelay
);
199 upsdebugx(1, "initups: OnDelay unavailable");
202 /* Try to set "OFF delay" (if supported and given) */
203 if (getval ("offdelay"))
205 mge_ups
.OffDelay
= atoi (getval ("offdelay"));
206 /* Set the value in the UPS */
207 mge_command(buf
, sizeof(buf
), "Sn %d", mge_ups
.OffDelay
);
208 if(!strcmp(buf
, "OK"))
209 upsdebugx(1, "OFF delay set to %d sec", mge_ups
.OffDelay
);
211 upsdebugx(1, "initups: OffDelay unavailable");
215 /* --------------------------------------------------------------- */
217 void upsdrv_initinfo(void)
220 const char *model
= NULL
;
221 char *firmware
= NULL
;
223 char *v
= NULL
; /* for parsing Si output, get Version ID */
230 mge_info_item_t
*item
;
231 models_name_t
*model_info
;
232 mge_model_info_t
*legacy_model
;
236 /* manufacturer -------------------------------------------- */
237 dstate_setinfo("ups.mfr", "MGE UPS SYSTEMS");
239 /* loop until we have at status */
244 /* get model information in ASCII string form: <Family> <Model> <Firmware> */
245 bytes_rcvd
= mge_command(buf
, sizeof(buf
), "Si 1");
247 if(bytes_rcvd
> 0 && buf
[0] != '?') {
248 dstate_setinfo("ups.id", "%s", buf
); /* raw id */
252 p
= strrchr(buf
, ' ');
258 if( firmware
&& strlen(firmware
) < 1 )
259 firmware
= NULL
; /* no firmware information */
261 /* Parsing model names table */
262 for ( model_info
= Si1_models_names
; model_info
->basename
!= NULL
; model_info
++ ) {
263 if(!strcasecmp(model_info
->basename
, model
))
265 model
= model_info
->finalname
;
266 upsdebugx(1, "initinfo: UPS model == >%s<", model
);
273 upsdebugx(1, "initinfo: 'Si 1' unavailable, switching to 'Si' command");
275 /* get model information, numbered form, : <Model ID> <Version ID> <Firmware> */
276 bytes_rcvd
= mge_command(buf
, sizeof(buf
), "Si");
278 if(bytes_rcvd
> 0 && buf
[0] != '?') {
279 upsdebugx(1, "initinfo: Si == >%s<", buf
);
281 printf("\nCAUTION : This is an older model. It may not support too much polling.\nPlease read man mge-utalk and use pollinterval\n");
283 p
= strchr(buf
, ' ');
287 si_data1
= atoi(buf
);
297 /* Parsing legacy model table in order to find it */
298 for ( legacy_model
= mge_model
; legacy_model
->name
!= NULL
; legacy_model
++ ) {
299 if(legacy_model
->Data1
== si_data1
&& legacy_model
->Data2
== si_data2
){
300 model
= legacy_model
->name
;
301 upsdebugx(1, "initinfo: UPS model == >%s<", model
);
307 printf("No model found by that model and version ID\nPlease contact us with UPS model, name and reminder info\nReminder info : Data1=%i , Data2=%i\n", si_data1
, si_data2
);
313 upsdebugx(2, "Got model name: %s", model
);
315 /* deal with truncated model names */
316 if (!strncmp(model
, "Evolutio", 8)) {
317 dstate_setinfo("ups.model", "Evolution %i", atoi(strchr(model
, ' ')));
319 dstate_setinfo("ups.model", "%s", model
);
323 if ( firmware
&& strcmp(firmware
, ""))
324 dstate_setinfo("ups.firmware", "%s", firmware
);
326 dstate_setinfo("ups.firmware", "unknown");
328 /* multiplier table */
329 /* <protocol level> <multiplier table> */
330 bytes_rcvd
= mge_command(buf
, sizeof(buf
), "Ai");
332 if (bytes_rcvd
> 0 && buf
[0] != '?') {
333 p
= strchr(buf
, ' ');
336 if ( 0 < table
&& table
< 4 )
337 mge_ups
.MultTab
= table
;
341 /* status --- try only system status, to get the really important
342 * information (OL, OB, LB); all else is added later by updateinfo */
343 status_ok
= get_ups_status();
345 } while ( (!status_ok
) && (tries
++ < MAXTRIES
) && (exit_flag
!= 1) );
347 if ( tries
== MAXTRIES
&& !status_ok
)
348 fatalx(EXIT_FAILURE
, "Could not get status from UPS.");
350 if ( mge_ups
.MultTab
== 0 )
351 upslogx(LOG_WARNING
, "Could not get multiplier table: using raw readings.");
353 /* all other variables ------------------------------------ */
354 for ( item
= mge_info
; item
->type
!= NULL
; item
++ ) {
356 /* Check if we are asked to stop (reactivity++) */
360 /* send request, read answer */
361 chars_rcvd
= mge_command(buf
, sizeof(buf
), item
->cmd
);
363 if ( chars_rcvd
< 1 || buf
[0] == '?' ) {
365 upsdebugx(1, "initinfo: %s unavailable", item
->type
);
368 extract_info(buf
, item
, infostr
, sizeof(infostr
));
369 dstate_setinfo(item
->type
, "%s", infostr
);
370 dstate_setflags(item
->type
, item
->flags
);
371 upsdebugx(1, "initinfo: %s == >%s<", item
->type
, infostr
);
373 /* Set max length for strings */
374 if (item
->flags
& ST_FLAG_STRING
)
375 dstate_setaux(item
->type
, item
->length
);
379 /* store timestamp */
380 lastpoll
= time(NULL
);
382 /* commands ----------------------------------------------- */
383 /* FIXME: check if available before adding! */
384 dstate_addcmd("load.off");
385 dstate_addcmd("load.on");
386 dstate_addcmd("shutdown.return");
387 dstate_addcmd("shutdown.stayoff");
388 dstate_addcmd("test.panel.start");
389 dstate_addcmd("test.battery.start");
390 dstate_addcmd("bypass.start");
391 dstate_addcmd("bypass.stop");
393 /* install handlers */
394 upsh
.setvar
= setvar
;
395 upsh
.instcmd
= instcmd
;
397 printf("Detected %s on %s\n", dstate_getinfo("ups.model"), device_path
);
400 /* --------------------------------------------------------------- */
402 void upsdrv_updateinfo(void)
408 mge_info_item_t
*item
;
410 /* make sure that communication is enabled */
414 status_ok
= get_ups_status(); /* only sys status is critical */
417 upslogx(LOG_NOTICE
, "updateinfo: Cannot update system status");
418 /* try to re enable communication */
427 /* Don't overload old units (at startup) */
428 if ( (unsigned int)time(NULL
) <= (unsigned int)(lastpoll
+ poll_interval
) )
431 /* update all other ok variables */
432 for ( item
= mge_info
; item
->type
!= NULL
; item
++ ) {
433 /* Check if we are asked to stop (reactivity++) */
438 /* send request, read answer */
439 bytes_rcvd
= mge_command(buf
, sizeof(buf
), item
->cmd
);
441 if ( bytes_rcvd
> 0 && buf
[0] != '?' ) {
442 extract_info(buf
, item
, infostr
, sizeof(infostr
));
443 dstate_setinfo(item
->type
, "%s", infostr
);
444 upsdebugx(2, "updateinfo: %s == >%s<", item
->type
, infostr
);
448 upslogx(LOG_NOTICE
, "updateinfo: Cannot update %s", item
->type
);
449 /* try to re enable communication */
456 /* store timestamp */
457 lastpoll
= time(NULL
);
460 /* --------------------------------------------------------------- */
462 void upsdrv_shutdown(void)
465 /* static time_t lastcmd = 0; */
466 memset(buf
, 0, sizeof(buf
));
468 if (sdtype
== SD_RETURN
) {
469 /* enable automatic restart */
470 mge_command(buf
, sizeof(buf
), "Sx 5");
472 upslogx(LOG_INFO
, "UPS response to Automatic Restart was %s", buf
);
475 /* Only call the effective shutoff if restart is ok */
476 /* or if we need only a stayoff... */
477 if (!strcmp(buf
, "OK") || (sdtype
== SD_STAYOFF
)) {
479 mge_command(buf
, sizeof(buf
), "Sx 0");
481 upslogx(LOG_INFO
, "UPS response to Shutdown was %s", buf
);
483 /* if(strcmp(buf, "OK")) */
485 /* call the cleanup to disable/close the comm link */
489 /* --------------------------------------------------------------- */
491 void upsdrv_help(void)
495 /* --------------------------------------------------------------- */
496 /* Internal Functions */
497 /* --------------------------------------------------------------- */
499 /* handler for commands to be sent to UPS */
500 int instcmd(const char *cmdname
, const char *extra
)
504 /* Start battery test */
505 if (!strcasecmp(cmdname
, "test.battery.start"))
507 mge_command(temp
, sizeof(temp
), "Bx 1");
508 upsdebugx(2, "UPS response to %s was %s", cmdname
, temp
);
510 if(strcmp(temp
, "OK"))
511 return STAT_INSTCMD_UNKNOWN
;
513 return STAT_INSTCMD_HANDLED
;
516 /* Start front panel test */
517 if (!strcasecmp(cmdname
, "test.panel.start"))
519 mge_command(temp
, sizeof(temp
), "Sx 129");
520 upsdebugx(2, "UPS response to %s was %s", cmdname
, temp
);
522 if(strcmp(temp
, "OK"))
523 return STAT_INSTCMD_UNKNOWN
;
525 return STAT_INSTCMD_HANDLED
;
529 if (!strcasecmp(cmdname
, "shutdown.stayoff"))
535 if (!strcasecmp(cmdname
, "shutdown.return"))
541 /* Power Off [all] plugs */
542 if (!strcasecmp(cmdname
, "load.off"))
544 /* TODO: Powershare (per plug) control */
545 mge_command(temp
, sizeof(temp
), "Wy 65535");
546 upsdebugx(2, "UPS response to Select All Plugs was %s", temp
);
548 if(strcmp(temp
, "OK"))
549 return STAT_INSTCMD_UNKNOWN
;
552 mge_command(temp
, sizeof(temp
), "Wx 0");
553 upsdebugx(2, "UPS response to %s was %s", cmdname
, temp
);
554 if(strcmp(temp
, "OK"))
555 return STAT_INSTCMD_UNKNOWN
;
557 return STAT_INSTCMD_HANDLED
;
561 /* Power On all plugs */
562 if (!strcasecmp(cmdname
, "load.on"))
564 /* TODO: add per plug control */
565 mge_command(temp
, sizeof(temp
), "Wy 65535");
566 upsdebugx(2, "UPS response to Select All Plugs was %s", temp
);
568 if(strcmp(temp
, "OK"))
569 return STAT_INSTCMD_UNKNOWN
;
572 mge_command(temp
, sizeof(temp
), "Wx 1");
573 upsdebugx(2, "UPS response to %s was %s", cmdname
, temp
);
574 if(strcmp(temp
, "OK"))
575 return STAT_INSTCMD_UNKNOWN
;
577 return STAT_INSTCMD_HANDLED
;
581 /* Switch on/off Maintenance Bypass */
582 if ((!strcasecmp(cmdname
, "bypass.start"))
583 || (!strcasecmp(cmdname
, "bypass.stop")))
585 /* TODO: add control on bypass value */
586 /* read maintenance bypass status */
587 if(mge_command(temp
, sizeof(temp
), "Ps") > 0)
591 /* Disable Maintenance Bypass */
592 mge_command(temp
, sizeof(temp
), "Px 2");
593 upsdebugx(2, "UPS response to Select All Plugs was %s", temp
);
596 /* Enable Maintenance Bypass */
597 mge_command(temp
, sizeof(temp
), "Px 3");
600 upsdebugx(2, "UPS response to %s was %s", cmdname
, temp
);
602 if(strcmp(temp
, "OK"))
603 return STAT_INSTCMD_UNKNOWN
;
605 return STAT_INSTCMD_HANDLED
;
609 upslogx(LOG_NOTICE
, "instcmd: unknown command [%s]", cmdname
);
610 return STAT_INSTCMD_UNKNOWN
;
613 /* --------------------------------------------------------------- */
615 /* handler for settable variables in UPS*/
616 int setvar(const char *varname
, const char *val
)
621 /* TODO : add some controls */
623 if(info_variable_ok(varname
))
626 snprintf(cmd
, sizeof(cmd
), "%s", info_variable_cmd(varname
));
627 sprintf(strchr(cmd
, '?'), "%s", val
);
629 /* Execute command */
630 mge_command(temp
, sizeof(temp
), cmd
);
631 upslogx(LOG_INFO
, "setvar: UPS response to Set %s to %s was %s", varname
, val
, temp
);
633 upsdebugx(1, "setvar: Variable %s not supported by UPS", varname
);
635 return STAT_SET_UNKNOWN
;
638 /* --------------------------------------------------------------- */
640 /* disable communication with UPS to avoid interference with
641 * kernel serial init at boot time (ie with V24 init) */
642 static void disable_ups_comm(void)
644 upsdebugx(1, "disable_ups_comm()");
645 ser_flush_in(upsfd
, "?\r\n", 0);
646 usleep(MGE_CONNECT_DELAY
);
647 mge_command(NULL
, 0, "Ax 0");
650 /* enable communication with UPS */
651 static void enable_ups_comm(void)
655 /* send Z twice --- speeds up re-connect */
656 mge_command(NULL
, 0, "Z");
657 mge_command(NULL
, 0, "Z");
658 /* only enable communication if needed! */
659 if ( mge_command(buf
, 8, "Si") <= 0)
661 mge_command(NULL
, 0, "Ax 1");
662 usleep(MGE_CONNECT_DELAY
);
665 ser_flush_in(upsfd
, "?\r\n", nut_debug_level
);
668 /* --------------------------------------------------------------- */
670 /* extract information from buffer
671 in: buf : reply from UPS
672 item : INFO item queried
673 out: infostr: to be placed in INFO_ variable
674 NOTE: buf="?" must be handled before calling extract_info
675 buf is changed inspite of const !!!!!
677 static void extract_info(const char *buf
, const mge_info_item_t
*item
,
678 char *infostr
, int infolen
)
680 /* initialize info string */
683 /* write into infostr with proper formatting */
684 if ( strpbrk(item
->fmt
, "feEgG") ) { /* float */
685 snprintf(infostr
, infolen
, item
->fmt
,
686 multiplier
[mge_ups
.MultTab
][item
->unit
] * atof(buf
));
687 } else if ( strpbrk(item
->fmt
, "dioxXuc") ) { /* int */
688 snprintf(infostr
, infolen
, item
->fmt
,
689 (int) (multiplier
[mge_ups
.MultTab
][item
->unit
] * atof(buf
)));
691 snprintf(infostr
, infolen
, item
->fmt
, buf
);
697 /* --------------------------------------------------------------- */
699 /* get system status, at least: OB, OL, LB
700 calls set_status appropriately
702 returns non-nil if successful
704 NOTE: MGE counts bytes/chars the opposite way as C,
705 see mge-utalk manpage. If status commands send two
706 data items, these are separated by a space, so
707 the elements of the second item are in buf[16..9].
710 static int get_ups_status(void)
713 int rb_set
= FALSE
; /* has RB flag been set ? */
714 int over_set
= FALSE
; /* has OVER flag been set ? */
720 /* Check if we are asked to stop (reactivity++) */
724 /* must clear status buffer before each round */
728 /* FIXME: some old units sometimes return "Syst Stat >1<"
729 resulting in an temporary OB status */
730 bytes_rcvd
= mge_command(buf
, sizeof(buf
), "Ss");
731 upsdebugx(1, "Syst Stat >%s<", buf
);
732 if ( bytes_rcvd
> 0 && strlen(buf
) > 7 ) {
750 /* buf[2] not used */
752 status_set("COMMFAULT"); /* self-invented */
753 /* FIXME: better to call datastale()?! */
755 status_set("ALARM"); /* self-invented */
756 /* FIXME: better to use ups.alarm */
760 mge_command(buf
, sizeof(buf
), "Bs");
761 upsdebugx(1, "Batt Stat >%s<", buf
);
762 if ( strlen(buf
) > 7 ) {
763 if ( !rb_set
&& ( buf
[7] == '1' || buf
[3] == '1' ) )
770 status_set("DISCHRG");
774 mge_command(buf
, sizeof(buf
), "Ls");
775 upsdebugx(1, "Load Stat >%s<", buf
);
776 if ( strlen(buf
) > 7 ) {
780 if ( !over_set
&& ( buf
[3] == '1' ) )
787 if ( strlen(buf
) > 15 ) { /* second "byte", skip <SP> */
788 if (buf
[16] == '1') {
793 /* FIXME: to be checked (MUST be buf[8]) !! */
794 /* if ( !(buf[9] == '1') ) */
795 /* This is not the OFF status!
796 if ( !(buf[8] == '1') )
797 status_set("OFF"); */
801 mge_command(buf
, sizeof(buf
), "Ps");
802 upsdebugx(1, "Bypass Stat >%s<", buf
);
803 if ( strlen(buf
) > 7 ) {
804 /* FIXME: extend ups.status for BYPASS: */
807 status_set("BYPASS");
808 /* Automatic Bypass */
810 status_set("BYPASS");
813 } while ( !ok
&& tries
++ < MAXTRIES
);
820 /* --------------------------------------------------------------- */
822 /* return proper variable "ok" given INFO_ type */
824 static bool_t
info_variable_ok(const char *type
)
826 mge_info_item_t
*item
= mge_info
;
828 while ( strcasecmp(item
->type
, type
))
834 /* --------------------------------------------------------------- */
836 /* return proper variable "cmd" given INFO_ type */
838 static const char *info_variable_cmd(const char *type
)
840 mge_info_item_t
*item
= mge_info
;
842 while ( strcasecmp(item
->type
, type
))
848 /* --------------------------------------------------------------- */
850 /* send command to UPS and read reply if requested
852 reply : buffer for reply, NULL if no reply expected
853 replylen: length of buffer reply
854 fmt : format string, followed by optional data for command
856 returns : no of chars received, -1 if error
858 static int mge_command(char *reply
, int replylen
, const char *fmt
, ...)
861 char command
[BUFFLEN
];
867 /* build command string */
870 ret
= vsnprintf(command
, sizeof(command
), fmt
, ap
);
872 if ((ret
< 1) || (ret
>= (int) sizeof(command
)))
873 upsdebugx(4, "mge_command: command truncated");
877 /* Delay a bit to avoid overlap of a previous answer (500 ms), as per
878 * http://old.networkupstools.org/protocols/mge/9261zwfa.pdf ยง 6.1. Timings */
881 /* flush received, unread data */
882 tcflush(upsfd
, TCIFLUSH
);
885 for (p
= command
; *p
; p
++) {
886 if ( isprint(*p
& 0xFF) )
887 upsdebugx(4, "mge_command: sending [%c]", *p
);
889 upsdebugx(4, "mge_command: sending [%02X]", *p
);
891 if (write(upsfd
, p
, 1) != 1)
895 usleep(MGE_CHAR_DELAY
);
898 /* send terminating string */
899 if (MGE_COMMAND_ENDCHAR
) {
900 for (p
= MGE_COMMAND_ENDCHAR
; *p
; p
++) {
901 if ( isprint(*p
& 0xFF) )
902 upsdebugx(4, "mge_command: sending [%c]", *p
);
904 upsdebugx(4, "mge_command: sending [%02X]", *p
);
906 if (write(upsfd
, p
, 1) != 1)
910 usleep(MGE_CHAR_DELAY
);
917 usleep(MGE_REPLY_DELAY
);
919 bytes_rcvd
= ser_get_line(upsfd
, reply
, replylen
,
920 MGE_REPLY_ENDCHAR
, MGE_REPLY_IGNCHAR
, 3, 0);
922 upsdebugx(4, "mge_command: received %d byte(s)", bytes_rcvd
);
927 void upsdrv_cleanup(void)
929 upsdebugx(1, "cleaning up");
931 ser_close(upsfd
, device_path
);