2010-03-25 23:20:59 +00: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 15:00:12 +00:00
* Martin Loyer < martin @ degraaf . fr >
2010-03-25 23:20:59 +00: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-06-29 10:37:36 +00:00
*
2010-03-25 23:20:59 +00: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-06-29 10:37:36 +00:00
*
2010-03-25 23:20:59 +00: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-06-29 10:37:36 +00:00
*
2010-03-25 23:20:59 +00: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-06-29 10:37:36 +00:00
*
2010-03-25 23:20:59 +00: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-06-29 10:37:36 +00:00
* " bit " Ss .1 .0 ( Load unprotected ) .
*
2010-03-25 23:20:59 +00:00
* enable_ups_comm ( ) is called before each attempt to read / write data
* from / to the UPS to re synchronise the communication .
*/
2022-06-29 10:37:36 +00:00
# include "config.h" /* must be the first header */
2010-03-25 23:20:59 +00:00
# include <ctype.h>
# include <sys/ioctl.h>
# include "timehead.h"
# include "main.h"
# include "serial.h"
# include "mge-utalk.h"
2022-06-29 10:37:36 +00:00
# include "nut_stdint.h"
2010-03-25 23:20:59 +00:00
/* --------------------------------------------------------------- */
/* Define "technical" constants */
/* --------------------------------------------------------------- */
# define DRIVER_NAME "MGE UPS SYSTEMS / U-Talk driver"
2022-06-29 10:37:36 +00:00
# define DRIVER_VERSION "0.94"
2010-03-25 23:20:59 +00: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 15:00:12 +00:00
" Martin Loyer <martin@degraaf.fr> \n " \
2010-03-25 23:20:59 +00: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-06-29 10:37:36 +00:00
# define BUFFLEN 256
2010-03-25 23:20:59 +00:00
# define SD_RETURN 0
# define SD_STAYOFF 1
2022-06-29 10:37:36 +00:00
static int sdtype = SD_RETURN ;
2010-03-25 23:20:59 +00: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-06-29 10:37:36 +00:00
static void extract_info ( const char * buf , const mge_info_item_t * mge ,
char * infostr , size_t infolen ) ;
2010-03-25 23:20:59 +00: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-06-29 10:37:36 +00:00
static ssize_t mge_command ( char * reply , size_t replylen , const char * fmt , . . . ) ;
2010-03-25 23:20:59 +00:00
/* --------------------------------------------------------------- */
/* UPS Driver Functions */
/* --------------------------------------------------------------- */
void upsdrv_makevartable ( void )
{
char temp [ BUFFLEN ] ;
2022-06-29 10:37:36 +00:00
snprintf ( temp , sizeof ( temp ) ,
" Low battery level, in %% (default = %3d) " ,
2010-03-25 23:20:59 +00:00
DEFAULT_LOWBATT ) ;
addvar ( VAR_VALUE , " LowBatt " , temp ) ;
2022-06-29 10:37:36 +00:00
snprintf ( temp , sizeof ( temp ) ,
2010-03-25 23:20:59 +00:00
" Delay before startup, in minutes (default = %3d) " ,
DEFAULT_ONDELAY ) ;
addvar ( VAR_VALUE , " OnDelay " , temp ) ;
2022-06-29 10:37:36 +00:00
snprintf ( temp , sizeof ( temp ) ,
2010-03-25 23:20:59 +00: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-06-29 10:37:36 +00:00
2010-03-25 23:20:59 +00: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-06-29 10:37:36 +00:00
2010-03-25 23:20:59 +00: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 09:35:08 +00:00
const char * model = NULL ;
2010-03-25 23:20:59 +00:00
char * firmware = NULL ;
char * p ;
char * v = NULL ; /* for parsing Si output, get Version ID */
int table ;
int tries ;
2011-01-26 09:35:08 +00:00
int status_ok = 0 ;
2022-06-29 10:37:36 +00:00
ssize_t bytes_rcvd ;
2010-03-25 23:20:59 +00:00
int si_data1 = 0 ;
int si_data2 = 0 ;
mge_info_item_t * item ;
2011-01-26 09:35:08 +00:00
models_name_t * model_info ;
2010-03-25 23:20:59 +00:00
mge_model_info_t * legacy_model ;
char infostr [ 32 ] ;
2022-06-29 10:37:36 +00:00
ssize_t chars_rcvd ;
2010-03-25 23:20:59 +00:00
/* manufacturer -------------------------------------------- */
dstate_setinfo ( " ups.mfr " , " MGE UPS SYSTEMS " ) ;
2022-06-29 10:37:36 +00:00
2010-03-25 23:20:59 +00: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 09:35:08 +00:00
2010-03-25 23:20:59 +00:00
model = buf ;
2011-01-26 09:35:08 +00:00
p = strrchr ( buf , ' ' ) ;
2010-03-25 23:20:59 +00:00
if ( p ! = NULL ) {
* p = ' \0 ' ;
firmware = p + 1 ;
}
if ( firmware & & strlen ( firmware ) < 1 )
firmware = NULL ; /* no firmware information */
2011-01-26 09:35:08 +00: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-25 23:20:59 +00: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 ( " \n CAUTION : This is an older model. It may not support too much polling. \n Please 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 13:55:19 +00:00
p = strchr ( v , ' ' ) ;
2010-03-25 23:20:59 +00:00
}
if ( p ! = NULL ) {
* p = ' \0 ' ;
si_data2 = atoi ( v ) ;
}
2011-01-26 09:35:08 +00: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-25 23:20:59 +00:00
}
}
if ( model = = NULL )
printf ( " No model found by that model and version ID \n Please contact us with UPS model, name and reminder info \n Reminder info : Data1=%i , Data2=%i \n " , si_data1 , si_data2 ) ;
}
}
if ( model ) {
2011-01-26 09:35:08 +00: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-25 23:20:59 +00:00
}
2022-06-29 10:37:36 +00:00
if ( firmware & & firmware [ 0 ] ! = ' \0 ' )
2010-03-25 23:20:59 +00:00
dstate_setinfo ( " ups.firmware " , " %s " , firmware ) ;
else
dstate_setinfo ( " ups.firmware " , " unknown " ) ;
2022-06-29 10:37:36 +00:00
2010-03-25 23:20:59 +00:00
/* multiplier table */
2022-06-29 10:37:36 +00:00
/* <protocol level> <multiplier table> */
2010-03-25 23:20:59 +00: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-06-29 10:37:36 +00:00
2011-01-26 09:35:08 +00:00
} while ( ( ! status_ok ) & & ( tries + + < MAXTRIES ) & & ( exit_flag ! = 1 ) ) ;
2010-03-25 23:20:59 +00: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-06-29 10:37:36 +00:00
2010-03-25 23:20:59 +00: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-06-29 10:37:36 +00:00
ssize_t bytes_rcvd ;
2010-03-25 23:20:59 +00: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 09:35:08 +00:00
if ( ! status_ok )
{
2010-03-25 23:20:59 +00:00
upslogx ( LOG_NOTICE , " updateinfo: Cannot update system status " ) ;
/* try to re enable communication */
disable_ups_comm ( ) ;
enable_ups_comm ( ) ;
2011-01-26 09:35:08 +00:00
}
else
{
2010-03-25 23:20:59 +00:00
dstate_dataok ( ) ;
}
/* Don't overload old units (at startup) */
2022-06-29 10:37:36 +00:00
if ( time ( NULL ) < = ( lastpoll + poll_interval ) )
2010-03-25 23:20:59 +00: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-06-29 10:37:36 +00:00
2010-03-25 23:20:59 +00: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 09:35:08 +00:00
} else
{
2010-03-25 23:20:59 +00: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 18:14:46 +00:00
memset ( buf , 0 , sizeof ( buf ) ) ;
2010-03-25 23:20:59 +00:00
if ( sdtype = = SD_RETURN ) {
/* enable automatic restart */
mge_command ( buf , sizeof ( buf ) , " Sx 5 " ) ;
2022-06-29 10:37:36 +00:00
2010-03-25 23:20:59 +00:00
upslogx ( LOG_INFO , " UPS response to Automatic Restart was %s " , buf ) ;
}
2022-06-29 10:37:36 +00:00
2010-03-25 23:20:59 +00: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-06-29 10:37:36 +00:00
2010-03-25 23:20:59 +00: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-06-29 10:37:36 +00:00
2010-03-25 23:20:59 +00: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-06-29 10:37:36 +00:00
2010-03-25 23:20:59 +00:00
if ( ! strcasecmp ( cmdname , " shutdown.return " ) )
{
sdtype = SD_RETURN ;
upsdrv_shutdown ( ) ;
}
2022-06-29 10:37:36 +00:00
2010-03-25 23:20:59 +00: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-06-29 10:37:36 +00:00
upsdebugx ( 2 , " UPS response to %s was %s " , cmdname , temp ) ;
2010-03-25 23:20:59 +00:00
if ( strcmp ( temp , " OK " ) )
return STAT_INSTCMD_UNKNOWN ;
2022-06-29 10:37:36 +00:00
else
2010-03-25 23:20:59 +00: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-06-29 10:37:36 +00:00
upsdebugx ( 2 , " UPS response to %s was %s " , cmdname , temp ) ;
2010-03-25 23:20:59 +00:00
if ( strcmp ( temp , " OK " ) )
return STAT_INSTCMD_UNKNOWN ;
2022-06-29 10:37:36 +00:00
else
2010-03-25 23:20:59 +00:00
return STAT_INSTCMD_HANDLED ;
}
}
/* Switch on/off Maintenance Bypass */
2022-06-29 10:37:36 +00:00
if ( ( ! strcasecmp ( cmdname , " bypass.start " ) )
2010-03-25 23:20:59 +00: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-06-29 10:37:36 +00:00
2010-03-25 23:20:59 +00:00
upsdebugx ( 2 , " UPS response to %s was %s " , cmdname , temp ) ;
2022-06-29 10:37:36 +00:00
2010-03-25 23:20:59 +00:00
if ( strcmp ( temp , " OK " ) )
return STAT_INSTCMD_UNKNOWN ;
else
return STAT_INSTCMD_HANDLED ;
}
}
2022-06-29 10:37:36 +00:00
upslogx ( LOG_NOTICE , " instcmd: unknown command [%s] [%s] " , cmdname , extra ) ;
2010-03-25 23:20:59 +00: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-06-29 10:37:36 +00:00
2010-03-25 23:20:59 +00:00
/* TODO : add some controls */
2022-06-29 10:37:36 +00:00
2010-03-25 23:20:59 +00: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-06-29 10:37:36 +00:00
2010-03-25 23:20:59 +00: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-06-29 10:37:36 +00:00
2013-11-24 15:00:12 +00:00
/* send Z twice --- speeds up re-connect */
mge_command ( NULL , 0 , " Z " ) ;
mge_command ( NULL , 0 , " Z " ) ;
2010-03-25 23:20:59 +00:00
/* only enable communication if needed! */
if ( mge_command ( buf , 8 , " Si " ) < = 0 )
{
mge_command ( NULL , 0 , " Ax 1 " ) ;
usleep ( MGE_CONNECT_DELAY ) ;
}
2022-06-29 10:37:36 +00:00
2010-03-25 23:20:59 +00:00
ser_flush_in ( upsfd , " ? \r \n " , nut_debug_level ) ;
}
/* --------------------------------------------------------------- */
/* extract information from buffer
in : buf : reply from UPS
item : INFO item queried
2022-06-29 10:37:36 +00:00
out : infostr : to be placed in INFO_ variable
2010-03-25 23:20:59 +00:00
NOTE : buf = " ? " must be handled before calling extract_info
buf is changed inspite of const ! ! ! ! !
*/
2022-06-29 10:37:36 +00:00
static void extract_info ( const char * buf , const mge_info_item_t * item ,
char * infostr , size_t infolen )
2010-03-25 23:20:59 +00:00
{
/* initialize info string */
infostr [ 0 ] = ' \0 ' ;
2022-06-29 10:37:36 +00: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-25 23:20:59 +00: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-06-29 10:37:36 +00:00
# ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL
# pragma GCC diagnostic pop
# endif
2010-03-25 23:20:59 +00:00
}
/* --------------------------------------------------------------- */
/* get system status, at least: OB, OL, LB
calls set_status appropriately
tries MAXTRIES times
2022-06-29 10:37:36 +00:00
returns non - nil if successful
2010-03-25 23:20:59 +00:00
2022-06-29 10:37:36 +00:00
NOTE : MGE counts bytes / chars the opposite way as C ,
2010-03-25 23:20:59 +00: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-06-29 10:37:36 +00:00
char buf [ BUFFLEN ] ;
2010-03-25 23:20:59 +00: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-06-29 10:37:36 +00:00
ssize_t bytes_rcvd = 0 ;
2010-03-25 23:20:59 +00: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-06-29 10:37:36 +00:00
status_set ( " RB " ) ;
2010-03-25 23:20:59 +00: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-06-29 10:37:36 +00:00
2010-03-25 23:20:59 +00:00
if ( buf [ 1 ] = = ' 1 ' )
status_set ( " CHRG " ) ;
2022-06-29 10:37:36 +00:00
2010-03-25 23:20:59 +00: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-06-29 10:37:36 +00:00
} /* if strlen */
2010-03-25 23:20:59 +00: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-06-29 10:37:36 +00:00
2010-03-25 23:20:59 +00:00
} while ( ! ok & & tries + + < MAXTRIES ) ;
status_commit ( ) ;
2022-06-29 10:37:36 +00:00
2010-03-25 23:20:59 +00:00
return ok ;
}
/* --------------------------------------------------------------- */
/* return proper variable "ok" given INFO_ type */
2022-06-29 10:37:36 +00:00
static bool_t info_variable_ok ( const char * type )
2010-03-25 23:20:59 +00:00
{
mge_info_item_t * item = mge_info ;
2022-06-29 10:37:36 +00:00
2010-03-25 23:20:59 +00:00
while ( strcasecmp ( item - > type , type ) )
item + + ;
2022-06-29 10:37:36 +00:00
2010-03-25 23:20:59 +00:00
return item - > ok ;
}
/* --------------------------------------------------------------- */
/* return proper variable "cmd" given INFO_ type */
2022-06-29 10:37:36 +00:00
static const char * info_variable_cmd ( const char * type )
2010-03-25 23:20:59 +00:00
{
mge_info_item_t * item = mge_info ;
2022-06-29 10:37:36 +00:00
2010-03-25 23:20:59 +00:00
while ( strcasecmp ( item - > type , type ) )
item + + ;
2022-06-29 10:37:36 +00:00
2010-03-25 23:20:59 +00: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-06-29 10:37:36 +00:00
static ssize_t mge_command ( char * reply , size_t replylen , const char * fmt , . . . )
2010-03-25 23:20:59 +00:00
{
const char * p ;
char command [ BUFFLEN ] ;
2022-06-29 10:37:36 +00:00
ssize_t bytes_sent = 0 ;
ssize_t bytes_rcvd = 0 ;
2010-03-25 23:20:59 +00:00
int ret ;
va_list ap ;
/* build command string */
va_start ( ap , fmt ) ;
2022-06-29 10:37:36 +00: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-25 23:20:59 +00:00
ret = vsnprintf ( command , sizeof ( command ) , fmt , ap ) ;
2022-06-29 10:37:36 +00:00
# ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL
# pragma GCC diagnostic pop
# endif
2010-03-25 23:20:59 +00:00
if ( ( ret < 1 ) | | ( ret > = ( int ) sizeof ( command ) ) )
upsdebugx ( 4 , " mge_command: command truncated " ) ;
2022-06-29 10:37:36 +00:00
2010-03-25 23:20:59 +00:00
va_end ( ap ) ;
2013-11-24 15:00:12 +00: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-06-29 10:37:36 +00:00
2010-03-25 23:20:59 +00:00
/* flush received, unread data */
tcflush ( upsfd , TCIFLUSH ) ;
2022-06-29 10:37:36 +00:00
2010-03-25 23:20:59 +00:00
/* send command */
for ( p = command ; * p ; p + + ) {
2022-06-29 10:37:36 +00:00
if ( isprint ( ( unsigned char ) * p & 0xFF ) )
2010-03-25 23:20:59 +00: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-06-29 10:37:36 +00:00
2010-03-25 23:20:59 +00:00
bytes_sent + + ;
usleep ( MGE_CHAR_DELAY ) ;
}
/* send terminating string */
2022-06-29 10:37:36 +00: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-25 23:20:59 +00:00
2022-06-29 10:37:36 +00:00
if ( write ( upsfd , p , 1 ) ! = 1 )
return - 1 ;
2010-03-25 23:20:59 +00:00
2022-06-29 10:37:36 +00:00
bytes_sent + + ;
usleep ( MGE_CHAR_DELAY ) ;
2010-03-25 23:20:59 +00: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-06-29 10:37:36 +00:00
upsdebugx ( 4 , " mge_command: received %zd byte(s) " , bytes_rcvd ) ;
2010-03-25 23:20:59 +00:00
return bytes_rcvd ;
}
void upsdrv_cleanup ( void )
{
upsdebugx ( 1 , " cleaning up " ) ;
disable_ups_comm ( ) ;
ser_close ( upsfd , device_path ) ;
}