nut-debian/drivers/mge-utalk.c

957 lines
26 KiB
C
Raw Permalink Normal View History

2010-03-26 01:20:59 +02:00
/* mge-utalk.c - monitor MGE UPS for NUT with UTalk protocol
*
* Copyright (C) 2002 - 2005
* Arnaud Quette <arnaud.quette@gmail.com>
* Hans Ekkehard Plesser <hans.plesser@itf.nlh.no>
2013-11-24 17:00:12 +02:00
* Martin Loyer <martin@degraaf.fr>
2010-03-26 01:20:59 +02:00
* Patrick Agrain <patrick.agrain@alcatel.fr>
* Nicholas Reilly <nreilly@magma.ca>
* Dave Abbott <d.abbott@dcs.shef.ac.uk>
* Marek Kralewski <marek@mercy49.de>
*
* 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
2022-07-10 10:23:45 +03:00
*
2010-03-26 01:20:59 +02:00
* 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.
2022-07-10 10:23:45 +03:00
*
2010-03-26 01:20:59 +02:00
* mge-utalk.h defines a struct array that tells the driver how to read
* variables from the UPS and publish them as NUT data.
2022-07-10 10:23:45 +03:00
*
2010-03-26 01:20:59 +02:00
* "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.
2022-07-10 10:23:45 +03:00
*
2010-03-26 01:20:59 +02:00
* 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
2022-07-10 10:23:45 +03:00
* "bit" Ss.1.0 (Load unprotected).
*
2010-03-26 01:20:59 +02:00
* enable_ups_comm() is called before each attempt to read/write data
* from/to the UPS to re synchronise the communication.
*/
2022-07-10 10:23:45 +03:00
#include "config.h" /* must be the first header */
2010-03-26 01:20:59 +02:00
#include <ctype.h>
#include <sys/ioctl.h>
#include "timehead.h"
#include "main.h"
#include "serial.h"
#include "mge-utalk.h"
2022-07-10 10:23:45 +03:00
#include "nut_stdint.h"
2010-03-26 01:20:59 +02:00
/* --------------------------------------------------------------- */
/* Define "technical" constants */
/* --------------------------------------------------------------- */
#define DRIVER_NAME "MGE UPS SYSTEMS/U-Talk driver"
2022-07-10 10:23:45 +03:00
#define DRIVER_VERSION "0.94"
2010-03-26 01:20:59 +02:00
/* driver description structure */
upsdrv_info_t upsdrv_info = {
DRIVER_NAME,
DRIVER_VERSION,
"Arnaud Quette <ArnaudQuette@gmail.com>\n" \
"Hans Ekkehard Plesser <hans.plesser@itf.nlh.no>\n" \
2013-11-24 17:00:12 +02:00
"Martin Loyer <martin@degraaf.fr>\n" \
2010-03-26 01:20:59 +02:00
"Patrick Agrain <patrick.agrain@alcatel.fr>\n" \
"Nicholas Reilly <nreilly@magma.ca>\n" \
"Dave Abbott <d.abbott@dcs.shef.ac.uk>\n" \
"Marek Kralewski <marek@mercy49.de>",
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 */
2022-07-10 10:23:45 +03:00
#define BUFFLEN 256
2010-03-26 01:20:59 +02:00
#define SD_RETURN 0
#define SD_STAYOFF 1
2022-07-10 10:23:45 +03:00
static int sdtype = SD_RETURN;
2010-03-26 01:20:59 +02:00
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);
2022-07-10 10:23:45 +03:00
static void extract_info(const char *buf, const mge_info_item_t *mge,
char *infostr, size_t infolen);
2010-03-26 01:20:59 +02:00
static const char *info_variable_cmd(const char *type);
static bool_t info_variable_ok(const char *type);
static int get_ups_status(void);
2022-07-10 10:23:45 +03:00
static ssize_t mge_command(char *reply, size_t replylen, const char *fmt, ...);
2010-03-26 01:20:59 +02:00
/* --------------------------------------------------------------- */
/* UPS Driver Functions */
/* --------------------------------------------------------------- */
void upsdrv_makevartable(void)
{
char temp[BUFFLEN];
2022-07-10 10:23:45 +03:00
snprintf(temp, sizeof(temp),
"Low battery level, in %% (default = %3d)",
2010-03-26 01:20:59 +02:00
DEFAULT_LOWBATT);
addvar (VAR_VALUE, "LowBatt", temp);
2022-07-10 10:23:45 +03:00
snprintf(temp, sizeof(temp),
2010-03-26 01:20:59 +02:00
"Delay before startup, in minutes (default = %3d)",
DEFAULT_ONDELAY);
addvar (VAR_VALUE, "OnDelay", temp);
2022-07-10 10:23:45 +03:00
snprintf(temp, sizeof(temp),
2010-03-26 01:20:59 +02:00
"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;
2022-07-10 10:23:45 +03:00
2010-03-26 01:20:59 +02:00
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;
2022-07-10 10:23:45 +03:00
2010-03-26 01:20:59 +02:00
/* 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];
2011-01-26 11:35:08 +02:00
const char *model = NULL;
2010-03-26 01:20:59 +02:00
char *firmware = NULL;
char *p;
char *v = NULL; /* for parsing Si output, get Version ID */
int table;
int tries;
2011-01-26 11:35:08 +02:00
int status_ok = 0;
2022-07-10 10:23:45 +03:00
ssize_t bytes_rcvd;
2010-03-26 01:20:59 +02:00
int si_data1 = 0;
int si_data2 = 0;
mge_info_item_t *item;
2011-01-26 11:35:08 +02:00
models_name_t *model_info;
2010-03-26 01:20:59 +02:00
mge_model_info_t *legacy_model;
char infostr[32];
2022-07-10 10:23:45 +03:00
ssize_t chars_rcvd;
2010-03-26 01:20:59 +02:00
/* manufacturer -------------------------------------------- */
dstate_setinfo("ups.mfr", "MGE UPS SYSTEMS");
2022-07-10 10:23:45 +03:00
2010-03-26 01:20:59 +02:00
/* loop until we have at status */
tries = 0;
do {
printf(".");
/* get model information in ASCII string form: <Family> <Model> <Firmware> */
bytes_rcvd = mge_command(buf, sizeof(buf), "Si 1");
if(bytes_rcvd > 0 && buf[0] != '?') {
dstate_setinfo("ups.id", "%s", buf); /* raw id */
2011-01-26 11:35:08 +02:00
2010-03-26 01:20:59 +02:00
model = buf;
2011-01-26 11:35:08 +02:00
p = strrchr(buf, ' ');
2010-03-26 01:20:59 +02:00
if ( p != NULL ) {
*p = '\0';
firmware = p+1;
}
if( firmware && strlen(firmware) < 1 )
firmware = NULL; /* no firmware information */
2011-01-26 11:35:08 +02:00
/* 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;
}
}
2010-03-26 01:20:59 +02:00
}
else
{
upsdebugx(1, "initinfo: 'Si 1' unavailable, switching to 'Si' command");
/* get model information, numbered form, : <Model ID> <Version ID> <Firmware> */
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;
2012-06-01 16:55:19 +03:00
p = strchr(v, ' ');
2010-03-26 01:20:59 +02:00
}
if ( p != NULL ) {
*p = '\0';
si_data2 = atoi(v);
}
2011-01-26 11:35:08 +02:00
/* 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;
2010-03-26 01:20:59 +02:00
}
}
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 ) {
2011-01-26 11:35:08 +02:00
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);
}
2010-03-26 01:20:59 +02:00
}
2022-07-10 10:23:45 +03:00
if (firmware && firmware[0] != '\0')
2010-03-26 01:20:59 +02:00
dstate_setinfo("ups.firmware", "%s", firmware);
else
dstate_setinfo("ups.firmware", "unknown");
2022-07-10 10:23:45 +03:00
2010-03-26 01:20:59 +02:00
/* multiplier table */
2022-07-10 10:23:45 +03:00
/* <protocol level> <multiplier table> */
2010-03-26 01:20:59 +02:00
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();
2022-07-10 10:23:45 +03:00
2011-01-26 11:35:08 +02:00
} while ( (!status_ok) && (tries++ < MAXTRIES) && (exit_flag != 1) );
2010-03-26 01:20:59 +02:00
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);
2022-07-10 10:23:45 +03:00
2010-03-26 01:20:59 +02:00
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;
2022-07-10 10:23:45 +03:00
ssize_t bytes_rcvd;
2010-03-26 01:20:59 +02:00
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 */
2011-01-26 11:35:08 +02:00
if ( !status_ok )
{
2010-03-26 01:20:59 +02:00
upslogx(LOG_NOTICE, "updateinfo: Cannot update system status");
/* try to re enable communication */
disable_ups_comm();
enable_ups_comm();
2011-01-26 11:35:08 +02:00
}
else
{
2010-03-26 01:20:59 +02:00
dstate_dataok();
}
/* Don't overload old units (at startup) */
2022-07-10 10:23:45 +03:00
if ( time(NULL) <= (lastpoll + poll_interval) )
2010-03-26 01:20:59 +02:00
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);
2022-07-10 10:23:45 +03:00
2010-03-26 01:20:59 +02:00
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();
2011-01-26 11:35:08 +02:00
} else
{
2010-03-26 01:20:59 +02:00
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; */
2011-09-29 21:14:46 +03:00
memset(buf, 0, sizeof(buf));
2010-03-26 01:20:59 +02:00
if (sdtype == SD_RETURN) {
/* enable automatic restart */
mge_command(buf, sizeof(buf), "Sx 5");
2022-07-10 10:23:45 +03:00
2010-03-26 01:20:59 +02:00
upslogx(LOG_INFO, "UPS response to Automatic Restart was %s", buf);
}
2022-07-10 10:23:45 +03:00
2010-03-26 01:20:59 +02:00
/* 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);
2022-07-10 10:23:45 +03:00
2010-03-26 01:20:59 +02:00
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);
2022-07-10 10:23:45 +03:00
2010-03-26 01:20:59 +02:00
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();
}
2022-07-10 10:23:45 +03:00
2010-03-26 01:20:59 +02:00
if (!strcasecmp(cmdname, "shutdown.return"))
{
sdtype = SD_RETURN;
upsdrv_shutdown();
}
2022-07-10 10:23:45 +03:00
2010-03-26 01:20:59 +02:00
/* 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");
2022-07-10 10:23:45 +03:00
upsdebugx(2, "UPS response to %s was %s", cmdname, temp);
2010-03-26 01:20:59 +02:00
if(strcmp(temp, "OK"))
return STAT_INSTCMD_UNKNOWN;
2022-07-10 10:23:45 +03:00
else
2010-03-26 01:20:59 +02:00
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");
2022-07-10 10:23:45 +03:00
upsdebugx(2, "UPS response to %s was %s", cmdname, temp);
2010-03-26 01:20:59 +02:00
if(strcmp(temp, "OK"))
return STAT_INSTCMD_UNKNOWN;
2022-07-10 10:23:45 +03:00
else
2010-03-26 01:20:59 +02:00
return STAT_INSTCMD_HANDLED;
}
}
/* Switch on/off Maintenance Bypass */
2022-07-10 10:23:45 +03:00
if ((!strcasecmp(cmdname, "bypass.start"))
2010-03-26 01:20:59 +02:00
|| (!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");
}
2022-07-10 10:23:45 +03:00
2010-03-26 01:20:59 +02:00
upsdebugx(2, "UPS response to %s was %s", cmdname, temp);
2022-07-10 10:23:45 +03:00
2010-03-26 01:20:59 +02:00
if(strcmp(temp, "OK"))
return STAT_INSTCMD_UNKNOWN;
else
return STAT_INSTCMD_HANDLED;
}
}
2022-07-10 10:23:45 +03:00
upslogx(LOG_NOTICE, "instcmd: unknown command [%s] [%s]", cmdname, extra);
2010-03-26 01:20:59 +02:00
return STAT_INSTCMD_UNKNOWN;
}
/* --------------------------------------------------------------- */
/* handler for settable variables in UPS*/
int setvar(const char *varname, const char *val)
{
char temp[BUFFLEN];
char cmd[15];
2022-07-10 10:23:45 +03:00
2010-03-26 01:20:59 +02:00
/* TODO : add some controls */
2022-07-10 10:23:45 +03:00
2010-03-26 01:20:59 +02:00
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);
2022-07-10 10:23:45 +03:00
2010-03-26 01:20:59 +02:00
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];
2022-07-10 10:23:45 +03:00
2013-11-24 17:00:12 +02:00
/* send Z twice --- speeds up re-connect */
mge_command(NULL, 0, "Z");
mge_command(NULL, 0, "Z");
2010-03-26 01:20:59 +02:00
/* only enable communication if needed! */
if ( mge_command(buf, 8, "Si") <= 0)
{
mge_command(NULL, 0, "Ax 1");
usleep(MGE_CONNECT_DELAY);
}
2022-07-10 10:23:45 +03:00
2010-03-26 01:20:59 +02:00
ser_flush_in(upsfd, "?\r\n", nut_debug_level);
}
/* --------------------------------------------------------------- */
/* extract information from buffer
in: buf : reply from UPS
item : INFO item queried
2022-07-10 10:23:45 +03:00
out: infostr: to be placed in INFO_ variable
2010-03-26 01:20:59 +02:00
NOTE: buf="?" must be handled before calling extract_info
buf is changed inspite of const !!!!!
*/
2022-07-10 10:23:45 +03:00
static void extract_info(const char *buf, const mge_info_item_t *item,
char *infostr, size_t infolen)
2010-03-26 01:20:59 +02:00
{
/* initialize info string */
infostr[0] = '\0';
2022-07-10 10:23:45 +03:00
#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
2010-03-26 01:20:59 +02:00
/* 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);
}
2022-07-10 10:23:45 +03:00
#ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL
#pragma GCC diagnostic pop
#endif
2010-03-26 01:20:59 +02:00
}
/* --------------------------------------------------------------- */
/* get system status, at least: OB, OL, LB
calls set_status appropriately
tries MAXTRIES times
2022-07-10 10:23:45 +03:00
returns non-nil if successful
2010-03-26 01:20:59 +02:00
2022-07-10 10:23:45 +03:00
NOTE: MGE counts bytes/chars the opposite way as C,
2010-03-26 01:20:59 +02:00
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)
{
2022-07-10 10:23:45 +03:00
char buf[BUFFLEN];
2010-03-26 01:20:59 +02:00
int rb_set= FALSE; /* has RB flag been set ? */
int over_set= FALSE; /* has OVER flag been set ? */
int tries = 0;
int ok = FALSE;
2022-07-10 10:23:45 +03:00
ssize_t bytes_rcvd = 0;
2010-03-26 01:20:59 +02:00
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;
2022-07-10 10:23:45 +03:00
status_set("RB");
2010-03-26 01:20:59 +02:00
}
/* 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");
2022-07-10 10:23:45 +03:00
2010-03-26 01:20:59 +02:00
if (buf[1] == '1')
status_set("CHRG");
2022-07-10 10:23:45 +03:00
2010-03-26 01:20:59 +02:00
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 <SP> */
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"); */
2022-07-10 10:23:45 +03:00
} /* if strlen */
2010-03-26 01:20:59 +02:00
/* 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 */
2022-07-10 10:23:45 +03:00
2010-03-26 01:20:59 +02:00
} while ( !ok && tries++ < MAXTRIES );
status_commit();
2022-07-10 10:23:45 +03:00
2010-03-26 01:20:59 +02:00
return ok;
}
/* --------------------------------------------------------------- */
/* return proper variable "ok" given INFO_ type */
2022-07-10 10:23:45 +03:00
static bool_t info_variable_ok(const char *type)
2010-03-26 01:20:59 +02:00
{
mge_info_item_t *item = mge_info ;
2022-07-10 10:23:45 +03:00
2010-03-26 01:20:59 +02:00
while ( strcasecmp(item->type, type ))
item++;
2022-07-10 10:23:45 +03:00
2010-03-26 01:20:59 +02:00
return item->ok;
}
/* --------------------------------------------------------------- */
/* return proper variable "cmd" given INFO_ type */
2022-07-10 10:23:45 +03:00
static const char *info_variable_cmd(const char *type)
2010-03-26 01:20:59 +02:00
{
mge_info_item_t *item = mge_info ;
2022-07-10 10:23:45 +03:00
2010-03-26 01:20:59 +02:00
while ( strcasecmp(item->type, type ))
item++;
2022-07-10 10:23:45 +03:00
2010-03-26 01:20:59 +02:00
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
*/
2022-07-10 10:23:45 +03:00
static ssize_t mge_command(char *reply, size_t replylen, const char *fmt, ...)
2010-03-26 01:20:59 +02:00
{
const char *p;
char command[BUFFLEN];
2022-07-10 10:23:45 +03:00
ssize_t bytes_sent = 0;
ssize_t bytes_rcvd = 0;
2010-03-26 01:20:59 +02:00
int ret;
va_list ap;
/* build command string */
va_start(ap, fmt);
2022-07-10 10:23:45 +03:00
#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
2010-03-26 01:20:59 +02:00
ret = vsnprintf(command, sizeof(command), fmt, ap);
2022-07-10 10:23:45 +03:00
#ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL
#pragma GCC diagnostic pop
#endif
2010-03-26 01:20:59 +02:00
if ((ret < 1) || (ret >= (int) sizeof(command)))
upsdebugx(4, "mge_command: command truncated");
2022-07-10 10:23:45 +03:00
2010-03-26 01:20:59 +02:00
va_end(ap);
2013-11-24 17:00:12 +02:00
/* 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);
2022-07-10 10:23:45 +03:00
2010-03-26 01:20:59 +02:00
/* flush received, unread data */
tcflush(upsfd, TCIFLUSH);
2022-07-10 10:23:45 +03:00
2010-03-26 01:20:59 +02:00
/* send command */
for (p = command; *p; p++) {
2022-07-10 10:23:45 +03:00
if ( isprint((unsigned char)*p & 0xFF) )
2010-03-26 01:20:59 +02:00
upsdebugx(4, "mge_command: sending [%c]", *p);
else
upsdebugx(4, "mge_command: sending [%02X]", *p);
if (write(upsfd, p, 1) != 1)
return -1;
2022-07-10 10:23:45 +03:00
2010-03-26 01:20:59 +02:00
bytes_sent++;
usleep(MGE_CHAR_DELAY);
}
/* send terminating string */
2022-07-10 10:23:45 +03:00
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);
2010-03-26 01:20:59 +02:00
2022-07-10 10:23:45 +03:00
if (write(upsfd, p, 1) != 1)
return -1;
2010-03-26 01:20:59 +02:00
2022-07-10 10:23:45 +03:00
bytes_sent++;
usleep(MGE_CHAR_DELAY);
2010-03-26 01:20:59 +02:00
}
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);
2022-07-10 10:23:45 +03:00
upsdebugx(4, "mge_command: received %zd byte(s)", bytes_rcvd);
2010-03-26 01:20:59 +02:00
return bytes_rcvd;
}
void upsdrv_cleanup(void)
{
upsdebugx(1, "cleaning up");
disable_ups_comm();
ser_close(upsfd, device_path);
}