/* mge-utalk.c - monitor MGE UPS for NUT with UTalk protocol * * Copyright (C) 2002 - 2005 * Arnaud Quette * Hans Ekkehard Plesser * Martin Loyer * Patrick Agrain * Nicholas Reilly * Dave Abbott * Marek Kralewski * * This driver is a collaborative effort by the above people, * Sponsored by MGE UPS SYSTEMS * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ /* * IMPLEMENTATION DETAILS * * Not all UTalk models provide all possible information, settings and commands. * mge-utalk checks on startup which variables and commands are available from * the UPS, and re-reads these regularly. Thus, startup is a bit slow, but this * should not matter much. * * mge-utalk.h defines a struct array that tells the driver how to read * variables from the UPS and publish them as NUT data. * * "ups.status" variable is not included in this array, since it contains * information that requires several calls to the UPS and more advanced analysis * of the reponses. The function get_ups_status does this job. * * Note that MGE enumerates the status "bits" from right to left, * i.e., if buf[] contains the reponse to command "Ss" (read system status), * then buf[0] contains "bit" Ss.1.7 (General alarm), while buf[7] contains * "bit" Ss.1.0 (Load unprotected). * * enable_ups_comm() is called before each attempt to read/write data * from/to the UPS to re synchronise the communication. */ #include "config.h" /* must be the first header */ #include #include #include "timehead.h" #include "main.h" #include "serial.h" #include "mge-utalk.h" #include "nut_stdint.h" /* --------------------------------------------------------------- */ /* Define "technical" constants */ /* --------------------------------------------------------------- */ #define DRIVER_NAME "MGE UPS SYSTEMS/U-Talk driver" #define DRIVER_VERSION "0.94" /* driver description structure */ upsdrv_info_t upsdrv_info = { DRIVER_NAME, DRIVER_VERSION, "Arnaud Quette \n" \ "Hans Ekkehard Plesser \n" \ "Martin Loyer \n" \ "Patrick Agrain \n" \ "Nicholas Reilly \n" \ "Dave Abbott \n" \ "Marek Kralewski ", DRV_STABLE, { NULL } }; /* delay after sending each char to UPS (in MICROSECONDS) */ #define MGE_CHAR_DELAY 0 /* delay after command, before reading UPS reply (in MICROSECONDS) */ #define MGE_REPLY_DELAY 1000 /* delay after enable_ups_comm */ #define MGE_CONNECT_DELAY 500000 #define MGE_COMMAND_ENDCHAR "\r\n" /* some UPS need \r and \n */ #define MGE_REPLY_ENDCHAR '\r' #define MGE_REPLY_IGNCHAR "\r\n" #define MAXTRIES 10 /* max number of connect tries */ #define BUFFLEN 256 #define SD_RETURN 0 #define SD_STAYOFF 1 static int sdtype = SD_RETURN; static time_t lastpoll; /* Timestamp the last polling */ /* --------------------------------------------------------------- */ /* Structure with information about UPS */ /* --------------------------------------------------------------- */ static struct { int MultTab; int LowBatt; /* percent */ int OnDelay; /* minutes */ int OffDelay; /* seconds */ } mge_ups = { 0, DEFAULT_LOWBATT, DEFAULT_ONDELAY, DEFAULT_OFFDELAY }; /* --------------------------------------------------------------- */ /* Declaration of internal functions */ /* --------------------------------------------------------------- */ static int instcmd(const char *cmdname, const char *extra); static int setvar(const char *varname, const char *val); static void enable_ups_comm(void); static void disable_ups_comm(void); static void extract_info(const char *buf, const mge_info_item_t *mge, char *infostr, size_t infolen); static const char *info_variable_cmd(const char *type); static bool_t info_variable_ok(const char *type); static int get_ups_status(void); static ssize_t mge_command(char *reply, size_t replylen, const char *fmt, ...); /* --------------------------------------------------------------- */ /* UPS Driver Functions */ /* --------------------------------------------------------------- */ void upsdrv_makevartable(void) { char temp[BUFFLEN]; snprintf(temp, sizeof(temp), "Low battery level, in %% (default = %3d)", DEFAULT_LOWBATT); addvar (VAR_VALUE, "LowBatt", temp); snprintf(temp, sizeof(temp), "Delay before startup, in minutes (default = %3d)", DEFAULT_ONDELAY); addvar (VAR_VALUE, "OnDelay", temp); snprintf(temp, sizeof(temp), "Delay before shutdown, in seconds (default = %3d)", DEFAULT_OFFDELAY); addvar (VAR_VALUE, "OffDelay", temp); addvar(VAR_FLAG, "oldmac", "Enable Oldworld Apple Macintosh support"); } /* --------------------------------------------------------------- */ void upsdrv_initups(void) { char buf[BUFFLEN]; int RTS = TIOCM_RTS; upsfd = ser_open(device_path); ser_set_speed(upsfd, device_path, B2400); /* read command line/conf variable that affect comm. */ if (testvar ("oldmac")) RTS = ~TIOCM_RTS; /* Init serial line */ ioctl(upsfd, TIOCMBIC, &RTS); enable_ups_comm(); /* Try to set "Low Battery Level" (if supported and given) */ if (getval ("lowbatt")) { mge_ups.LowBatt = atoi (getval ("lowbatt")); /* Set the value in the UPS */ mge_command(buf, sizeof(buf), "Bl %d", mge_ups.LowBatt); if(!strcmp(buf, "OK")) upsdebugx(1, "Low Battery Level set to %d%%", mge_ups.LowBatt); else upsdebugx(1, "initups: Low Battery Level cannot be set"); } /* Try to set "ON delay" (if supported and given) */ if (getval ("ondelay")) { mge_ups.OnDelay = atoi (getval ("ondelay")); /* Set the value in the UPS */ mge_command(buf, sizeof(buf), "Sm %d", mge_ups.OnDelay); if(!strcmp(buf, "OK")) upsdebugx(1, "ON delay set to %d min", mge_ups.OnDelay); else upsdebugx(1, "initups: OnDelay unavailable"); } /* Try to set "OFF delay" (if supported and given) */ if (getval ("offdelay")) { mge_ups.OffDelay = atoi (getval ("offdelay")); /* Set the value in the UPS */ mge_command(buf, sizeof(buf), "Sn %d", mge_ups.OffDelay); if(!strcmp(buf, "OK")) upsdebugx(1, "OFF delay set to %d sec", mge_ups.OffDelay); else upsdebugx(1, "initups: OffDelay unavailable"); } } /* --------------------------------------------------------------- */ void upsdrv_initinfo(void) { char buf[BUFFLEN]; const char *model = NULL; char *firmware = NULL; char *p; char *v = NULL; /* for parsing Si output, get Version ID */ int table; int tries; int status_ok = 0; ssize_t bytes_rcvd; int si_data1 = 0; int si_data2 = 0; mge_info_item_t *item; models_name_t *model_info; mge_model_info_t *legacy_model; char infostr[32]; ssize_t chars_rcvd; /* manufacturer -------------------------------------------- */ dstate_setinfo("ups.mfr", "MGE UPS SYSTEMS"); /* loop until we have at status */ tries = 0; do { printf("."); /* get model information in ASCII string form: */ bytes_rcvd = mge_command(buf, sizeof(buf), "Si 1"); if(bytes_rcvd > 0 && buf[0] != '?') { dstate_setinfo("ups.id", "%s", buf); /* raw id */ model = buf; p = strrchr(buf, ' '); if ( p != NULL ) { *p = '\0'; firmware = p+1; } if( firmware && strlen(firmware) < 1 ) firmware = NULL; /* no firmware information */ /* Parsing model names table */ for ( model_info = Si1_models_names ; model_info->basename != NULL ; model_info++ ) { if(!strcasecmp(model_info->basename, model)) { model = model_info->finalname; upsdebugx(1, "initinfo: UPS model == >%s<", model); break; } } } else { upsdebugx(1, "initinfo: 'Si 1' unavailable, switching to 'Si' command"); /* get model information, numbered form, : */ bytes_rcvd = mge_command(buf, sizeof(buf), "Si"); if(bytes_rcvd > 0 && buf[0] != '?') { upsdebugx(1, "initinfo: Si == >%s<", buf); printf("\nCAUTION : This is an older model. It may not support too much polling.\nPlease read man mge-utalk and use pollinterval\n"); p = strchr(buf, ' '); if ( p != NULL ) { *p = '\0'; si_data1 = atoi(buf); v = p+1; p = strchr(v, ' '); } if ( p != NULL ) { *p = '\0'; si_data2 = atoi(v); } /* Parsing legacy model table in order to find it */ for ( legacy_model = mge_model ; legacy_model->name != NULL ; legacy_model++ ) { if(legacy_model->Data1 == si_data1 && legacy_model->Data2 == si_data2){ model = legacy_model->name; upsdebugx(1, "initinfo: UPS model == >%s<", model); break; } } if( model == NULL ) 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); } } if ( model ) { upsdebugx(2, "Got model name: %s", model); /* deal with truncated model names */ if (!strncmp(model, "Evolutio", 8)) { dstate_setinfo("ups.model", "Evolution %i", atoi(strchr(model, ' '))); } else { dstate_setinfo("ups.model", "%s", model); } } if (firmware && firmware[0] != '\0') dstate_setinfo("ups.firmware", "%s", firmware); else dstate_setinfo("ups.firmware", "unknown"); /* multiplier table */ /* */ bytes_rcvd = mge_command(buf, sizeof(buf), "Ai"); if (bytes_rcvd > 0 && buf[0] != '?') { p = strchr(buf, ' '); if ( p != NULL ) { table = atoi(p + 1); if ( 0 < table && table < 4 ) mge_ups.MultTab = table; } } /* status --- try only system status, to get the really important * information (OL, OB, LB); all else is added later by updateinfo */ status_ok = get_ups_status(); } while ( (!status_ok) && (tries++ < MAXTRIES) && (exit_flag != 1) ); if ( tries == MAXTRIES && !status_ok ) fatalx(EXIT_FAILURE, "Could not get status from UPS."); if ( mge_ups.MultTab == 0 ) upslogx(LOG_WARNING, "Could not get multiplier table: using raw readings."); /* all other variables ------------------------------------ */ for ( item = mge_info ; item->type != NULL ; item++ ) { /* Check if we are asked to stop (reactivity++) */ if (exit_flag != 0) return; /* send request, read answer */ chars_rcvd = mge_command(buf, sizeof(buf), item->cmd); if ( chars_rcvd < 1 || buf[0] == '?' ) { item->ok = FALSE; upsdebugx(1, "initinfo: %s unavailable", item->type); } else { item->ok = TRUE; extract_info(buf, item, infostr, sizeof(infostr)); dstate_setinfo(item->type, "%s", infostr); dstate_setflags(item->type, item->flags); upsdebugx(1, "initinfo: %s == >%s<", item->type, infostr); /* Set max length for strings */ if (item->flags & ST_FLAG_STRING) dstate_setaux(item->type, item->length); } } /* for item */ /* store timestamp */ lastpoll = time(NULL); /* commands ----------------------------------------------- */ /* FIXME: check if available before adding! */ dstate_addcmd("load.off"); dstate_addcmd("load.on"); dstate_addcmd("shutdown.return"); dstate_addcmd("shutdown.stayoff"); dstate_addcmd("test.panel.start"); dstate_addcmd("test.battery.start"); dstate_addcmd("bypass.start"); dstate_addcmd("bypass.stop"); /* install handlers */ upsh.setvar = setvar; upsh.instcmd = instcmd; printf("Detected %s on %s\n", dstate_getinfo("ups.model"), device_path); } /* --------------------------------------------------------------- */ void upsdrv_updateinfo(void) { char buf[BUFFLEN]; char infostr[32]; int status_ok; ssize_t bytes_rcvd; mge_info_item_t *item; /* make sure that communication is enabled */ enable_ups_comm(); /* update status */ status_ok = get_ups_status(); /* only sys status is critical */ if ( !status_ok ) { upslogx(LOG_NOTICE, "updateinfo: Cannot update system status"); /* try to re enable communication */ disable_ups_comm(); enable_ups_comm(); } else { dstate_dataok(); } /* Don't overload old units (at startup) */ if ( time(NULL) <= (lastpoll + poll_interval) ) return; /* update all other ok variables */ for ( item = mge_info ; item->type != NULL ; item++ ) { /* Check if we are asked to stop (reactivity++) */ if (exit_flag != 0) return; if ( item->ok ) { /* send request, read answer */ bytes_rcvd = mge_command(buf, sizeof(buf), item->cmd); if ( bytes_rcvd > 0 && buf[0] != '?' ) { extract_info(buf, item, infostr, sizeof(infostr)); dstate_setinfo(item->type, "%s", infostr); upsdebugx(2, "updateinfo: %s == >%s<", item->type, infostr); dstate_dataok(); } else { upslogx(LOG_NOTICE, "updateinfo: Cannot update %s", item->type); /* try to re enable communication */ disable_ups_comm(); enable_ups_comm(); } } /* if item->ok */ } /* store timestamp */ lastpoll = time(NULL); } /* --------------------------------------------------------------- */ void upsdrv_shutdown(void) { char buf[BUFFLEN]; /* static time_t lastcmd = 0; */ memset(buf, 0, sizeof(buf)); if (sdtype == SD_RETURN) { /* enable automatic restart */ mge_command(buf, sizeof(buf), "Sx 5"); upslogx(LOG_INFO, "UPS response to Automatic Restart was %s", buf); } /* Only call the effective shutoff if restart is ok */ /* or if we need only a stayoff... */ if (!strcmp(buf, "OK") || (sdtype == SD_STAYOFF)) { /* shutdown UPS */ mge_command(buf, sizeof(buf), "Sx 0"); upslogx(LOG_INFO, "UPS response to Shutdown was %s", buf); } /* if(strcmp(buf, "OK")) */ /* call the cleanup to disable/close the comm link */ upsdrv_cleanup(); } /* --------------------------------------------------------------- */ void upsdrv_help(void) { } /* --------------------------------------------------------------- */ /* Internal Functions */ /* --------------------------------------------------------------- */ /* handler for commands to be sent to UPS */ int instcmd(const char *cmdname, const char *extra) { char temp[BUFFLEN]; /* Start battery test */ if (!strcasecmp(cmdname, "test.battery.start")) { mge_command(temp, sizeof(temp), "Bx 1"); upsdebugx(2, "UPS response to %s was %s", cmdname, temp); if(strcmp(temp, "OK")) return STAT_INSTCMD_UNKNOWN; else return STAT_INSTCMD_HANDLED; } /* Start front panel test */ if (!strcasecmp(cmdname, "test.panel.start")) { mge_command(temp, sizeof(temp), "Sx 129"); upsdebugx(2, "UPS response to %s was %s", cmdname, temp); if(strcmp(temp, "OK")) return STAT_INSTCMD_UNKNOWN; else return STAT_INSTCMD_HANDLED; } /* Shutdown UPS */ if (!strcasecmp(cmdname, "shutdown.stayoff")) { sdtype = SD_STAYOFF; upsdrv_shutdown(); } if (!strcasecmp(cmdname, "shutdown.return")) { sdtype = SD_RETURN; upsdrv_shutdown(); } /* Power Off [all] plugs */ if (!strcasecmp(cmdname, "load.off")) { /* TODO: Powershare (per plug) control */ mge_command(temp, sizeof(temp), "Wy 65535"); upsdebugx(2, "UPS response to Select All Plugs was %s", temp); if(strcmp(temp, "OK")) return STAT_INSTCMD_UNKNOWN; else { mge_command(temp, sizeof(temp), "Wx 0"); upsdebugx(2, "UPS response to %s was %s", cmdname, temp); if(strcmp(temp, "OK")) return STAT_INSTCMD_UNKNOWN; else return STAT_INSTCMD_HANDLED; } } /* Power On all plugs */ if (!strcasecmp(cmdname, "load.on")) { /* TODO: add per plug control */ mge_command(temp, sizeof(temp), "Wy 65535"); upsdebugx(2, "UPS response to Select All Plugs was %s", temp); if(strcmp(temp, "OK")) return STAT_INSTCMD_UNKNOWN; else { mge_command(temp, sizeof(temp), "Wx 1"); upsdebugx(2, "UPS response to %s was %s", cmdname, temp); if(strcmp(temp, "OK")) return STAT_INSTCMD_UNKNOWN; else return STAT_INSTCMD_HANDLED; } } /* Switch on/off Maintenance Bypass */ if ((!strcasecmp(cmdname, "bypass.start")) || (!strcasecmp(cmdname, "bypass.stop"))) { /* TODO: add control on bypass value */ /* read maintenance bypass status */ if(mge_command(temp, sizeof(temp), "Ps") > 0) { if (temp[0] == '1') { /* Disable Maintenance Bypass */ mge_command(temp, sizeof(temp), "Px 2"); upsdebugx(2, "UPS response to Select All Plugs was %s", temp); } else { /* Enable Maintenance Bypass */ mge_command(temp, sizeof(temp), "Px 3"); } upsdebugx(2, "UPS response to %s was %s", cmdname, temp); if(strcmp(temp, "OK")) return STAT_INSTCMD_UNKNOWN; else return STAT_INSTCMD_HANDLED; } } upslogx(LOG_NOTICE, "instcmd: unknown command [%s] [%s]", cmdname, extra); return STAT_INSTCMD_UNKNOWN; } /* --------------------------------------------------------------- */ /* handler for settable variables in UPS*/ int setvar(const char *varname, const char *val) { char temp[BUFFLEN]; char cmd[15]; /* TODO : add some controls */ if(info_variable_ok(varname)) { /* format command */ snprintf(cmd, sizeof(cmd), "%s", info_variable_cmd(varname)); sprintf(strchr(cmd, '?'), "%s", val); /* Execute command */ mge_command(temp, sizeof(temp), cmd); upslogx(LOG_INFO, "setvar: UPS response to Set %s to %s was %s", varname, val, temp); } else upsdebugx(1, "setvar: Variable %s not supported by UPS", varname); return STAT_SET_UNKNOWN; } /* --------------------------------------------------------------- */ /* disable communication with UPS to avoid interference with * kernel serial init at boot time (ie with V24 init) */ static void disable_ups_comm(void) { upsdebugx(1, "disable_ups_comm()"); ser_flush_in(upsfd, "?\r\n", 0); usleep(MGE_CONNECT_DELAY); mge_command(NULL, 0, "Ax 0"); } /* enable communication with UPS */ static void enable_ups_comm(void) { char buf[8]; /* send Z twice --- speeds up re-connect */ mge_command(NULL, 0, "Z"); mge_command(NULL, 0, "Z"); /* only enable communication if needed! */ if ( mge_command(buf, 8, "Si") <= 0) { mge_command(NULL, 0, "Ax 1"); usleep(MGE_CONNECT_DELAY); } ser_flush_in(upsfd, "?\r\n", nut_debug_level); } /* --------------------------------------------------------------- */ /* extract information from buffer in: buf : reply from UPS item : INFO item queried out: infostr: to be placed in INFO_ variable NOTE: buf="?" must be handled before calling extract_info buf is changed inspite of const !!!!! */ static void extract_info(const char *buf, const mge_info_item_t *item, char *infostr, size_t infolen) { /* initialize info string */ infostr[0] = '\0'; #ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL #pragma GCC diagnostic push #endif #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL #pragma GCC diagnostic ignored "-Wformat-nonliteral" #endif #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_SECURITY #pragma GCC diagnostic ignored "-Wformat-security" #endif /* write into infostr with proper formatting */ if ( strpbrk(item->fmt, "feEgG") ) { /* float */ snprintf(infostr, infolen, item->fmt, multiplier[mge_ups.MultTab][item->unit] * atof(buf)); } else if ( strpbrk(item->fmt, "dioxXuc") ) { /* int */ snprintf(infostr, infolen, item->fmt, (int) (multiplier[mge_ups.MultTab][item->unit] * atof(buf))); } else { snprintf(infostr, infolen, item->fmt, buf); } #ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL #pragma GCC diagnostic pop #endif } /* --------------------------------------------------------------- */ /* get system status, at least: OB, OL, LB calls set_status appropriately tries MAXTRIES times returns non-nil if successful NOTE: MGE counts bytes/chars the opposite way as C, see mge-utalk manpage. If status commands send two data items, these are separated by a space, so the elements of the second item are in buf[16..9]. */ static int get_ups_status(void) { char buf[BUFFLEN]; int rb_set= FALSE; /* has RB flag been set ? */ int over_set= FALSE; /* has OVER flag been set ? */ int tries = 0; int ok = FALSE; ssize_t bytes_rcvd = 0; do { /* Check if we are asked to stop (reactivity++) */ if (exit_flag != 0) return FALSE; /* must clear status buffer before each round */ status_init(); /* system status */ /* FIXME: some old units sometimes return "Syst Stat >1<" resulting in an temporary OB status */ bytes_rcvd = mge_command(buf, sizeof(buf), "Ss"); upsdebugx(1, "Syst Stat >%s<", buf); if ( bytes_rcvd > 0 && strlen(buf) > 7 ) { ok = TRUE; if (buf[6] == '1') { over_set = TRUE; status_set("OVER"); } if (buf[5] == '1') status_set("OB"); else status_set("OL"); if (buf[4] == '1') status_set("LB"); if (buf[3] == '1') { rb_set = TRUE; status_set("RB"); } /* buf[2] not used */ if (buf[1] == '1') status_set("COMMFAULT"); /* self-invented */ /* FIXME: better to call datastale()?! */ if (buf[0] == '1') status_set("ALARM"); /* self-invented */ /* FIXME: better to use ups.alarm */ } /* if strlen */ /* battery status */ mge_command(buf, sizeof(buf), "Bs"); upsdebugx(1, "Batt Stat >%s<", buf); if ( strlen(buf) > 7 ) { if ( !rb_set && ( buf[7] == '1' || buf[3] == '1' ) ) status_set("RB"); if (buf[1] == '1') status_set("CHRG"); if (buf[0] == '1') status_set("DISCHRG"); } /* if strlen */ /* load status */ mge_command(buf, sizeof(buf), "Ls"); upsdebugx(1, "Load Stat >%s<", buf); if ( strlen(buf) > 7 ) { if (buf[4] == '1') status_set("BOOST"); if ( !over_set && ( buf[3] == '1' ) ) status_set("OVER"); if (buf[2] == '1') status_set("TRIM"); } /* if strlen */ if ( strlen(buf) > 15 ) { /* second "byte", skip */ if (buf[16] == '1') { status_set("OB"); status_set("LB"); } /* FIXME: to be checked (MUST be buf[8]) !! */ /* if ( !(buf[9] == '1') ) */ /* This is not the OFF status! if ( !(buf[8] == '1') ) status_set("OFF"); */ } /* if strlen */ /* Bypass status */ mge_command(buf, sizeof(buf), "Ps"); upsdebugx(1, "Bypass Stat >%s<", buf); if ( strlen(buf) > 7 ) { /* FIXME: extend ups.status for BYPASS: */ /* Manual Bypass */ if (buf[7] == '1') status_set("BYPASS"); /* Automatic Bypass */ if (buf[6] == '1') status_set("BYPASS"); } /* if strlen */ } while ( !ok && tries++ < MAXTRIES ); status_commit(); return ok; } /* --------------------------------------------------------------- */ /* return proper variable "ok" given INFO_ type */ static bool_t info_variable_ok(const char *type) { mge_info_item_t *item = mge_info ; while ( strcasecmp(item->type, type )) item++; return item->ok; } /* --------------------------------------------------------------- */ /* return proper variable "cmd" given INFO_ type */ static const char *info_variable_cmd(const char *type) { mge_info_item_t *item = mge_info ; while ( strcasecmp(item->type, type )) item++; return item->cmd; } /* --------------------------------------------------------------- */ /* send command to UPS and read reply if requested reply : buffer for reply, NULL if no reply expected replylen: length of buffer reply fmt : format string, followed by optional data for command returns : no of chars received, -1 if error */ static ssize_t mge_command(char *reply, size_t replylen, const char *fmt, ...) { const char *p; char command[BUFFLEN]; ssize_t bytes_sent = 0; ssize_t bytes_rcvd = 0; int ret; va_list ap; /* build command string */ va_start(ap, fmt); #ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL #pragma GCC diagnostic push #endif #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL #pragma GCC diagnostic ignored "-Wformat-nonliteral" #endif #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_SECURITY #pragma GCC diagnostic ignored "-Wformat-security" #endif ret = vsnprintf(command, sizeof(command), fmt, ap); #ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL #pragma GCC diagnostic pop #endif if ((ret < 1) || (ret >= (int) sizeof(command))) upsdebugx(4, "mge_command: command truncated"); va_end(ap); /* Delay a bit to avoid overlap of a previous answer (500 ms), as per * http://old.networkupstools.org/protocols/mge/9261zwfa.pdf ยง 6.1. Timings */ usleep(500000); /* flush received, unread data */ tcflush(upsfd, TCIFLUSH); /* send command */ for (p = command; *p; p++) { if ( isprint((unsigned char)*p & 0xFF) ) upsdebugx(4, "mge_command: sending [%c]", *p); else upsdebugx(4, "mge_command: sending [%02X]", *p); if (write(upsfd, p, 1) != 1) return -1; bytes_sent++; usleep(MGE_CHAR_DELAY); } /* send terminating string */ for (p = MGE_COMMAND_ENDCHAR; *p; p++) { if ( isprint((unsigned char)*p & 0xFF) ) upsdebugx(4, "mge_command: sending [%c]", *p); else upsdebugx(4, "mge_command: sending [%02X]", *p); if (write(upsfd, p, 1) != 1) return -1; bytes_sent++; usleep(MGE_CHAR_DELAY); } if ( !reply ) return bytes_rcvd; else usleep(MGE_REPLY_DELAY); bytes_rcvd = ser_get_line(upsfd, reply, replylen, MGE_REPLY_ENDCHAR, MGE_REPLY_IGNCHAR, 3, 0); upsdebugx(4, "mge_command: received %zd byte(s)", bytes_rcvd); return bytes_rcvd; } void upsdrv_cleanup(void) { upsdebugx(1, "cleaning up"); disable_ups_comm(); ser_close(upsfd, device_path); }