627 lines
16 KiB
C
627 lines
16 KiB
C
/* masterguard.c - support for Masterguard models
|
|
|
|
Copyright (C) 2001 Michael Spanier <mail@michael-spanier.de>
|
|
|
|
masterguard.c created on 15.8.2001
|
|
|
|
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
|
|
*/
|
|
|
|
|
|
/********************************************************************
|
|
*
|
|
* Please if you edit this code convert tabs to spaces and use
|
|
* four characters indent.
|
|
* If you don't know what this means use the vim editor.
|
|
*
|
|
* Have fun
|
|
* Michael
|
|
*
|
|
********************************************************************/
|
|
#include "main.h"
|
|
#include "serial.h"
|
|
|
|
#define DRIVER_NAME "MASTERGUARD UPS driver"
|
|
#define DRIVER_VERSION "0.24"
|
|
|
|
/* driver description structure */
|
|
upsdrv_info_t upsdrv_info = {
|
|
DRIVER_NAME,
|
|
DRIVER_VERSION,
|
|
"Michael Spanier <mail@michael-spanier.de>",
|
|
DRV_STABLE,
|
|
{ NULL }
|
|
};
|
|
|
|
#define UPSDELAY 3
|
|
#define MAXTRIES 10
|
|
#define UPS_PACE 100 /* 100 us between chars on write */
|
|
|
|
#define Q1 1
|
|
#define Q3 2
|
|
|
|
#define DEBUG 1
|
|
|
|
int type;
|
|
char name[31];
|
|
char firmware[6];
|
|
|
|
/********************************************************************
|
|
*
|
|
* Helper function to split a sting into words by splitting at the
|
|
* SPACE character.
|
|
*
|
|
* Adds up to maxlen characters to the char word.
|
|
* Returns NULL on reaching the end of the string.
|
|
*
|
|
********************************************************************/
|
|
static char *StringSplit( char *source, char *word, int maxlen )
|
|
{
|
|
int i;
|
|
int len;
|
|
int wc=0;
|
|
|
|
word[0] = '\0';
|
|
len = strlen( source );
|
|
for( i = 0; i < len; i++ )
|
|
{
|
|
if( source[i] == ' ' )
|
|
{
|
|
word[wc] = '\0';
|
|
return source + i + 1;
|
|
}
|
|
word[wc] = source[i];
|
|
wc++;
|
|
}
|
|
word[wc] = '\0';
|
|
return NULL;
|
|
}
|
|
|
|
/********************************************************************
|
|
*
|
|
* Helper function to drop all whitespaces within a string.
|
|
*
|
|
* "word" must be large enought to hold "source", for the worst case
|
|
* "word" has to be exacly the size of "source".
|
|
*
|
|
********************************************************************/
|
|
static void StringStrip( char *source, char *word )
|
|
{
|
|
int wc=0;
|
|
int i;
|
|
int len;
|
|
|
|
word[0] = '\0';
|
|
len = strlen( source );
|
|
for( i = 0; i < len; i++ )
|
|
{
|
|
if( source[i] == ' ' )
|
|
continue;
|
|
if( source[i] == '\n' )
|
|
continue;
|
|
if( source[i] == '\t' )
|
|
continue;
|
|
word[wc] = source[i];
|
|
wc++;
|
|
}
|
|
word[wc] = '\0';
|
|
}
|
|
|
|
/********************************************************************
|
|
*
|
|
* Function parses the status flags which occure in the Q1 and Q3
|
|
* command. Sets the INFO_STATUS value ( OL, OB, ... )
|
|
*
|
|
********************************************************************/
|
|
static void parseFlags( char *flags )
|
|
{
|
|
status_init();
|
|
|
|
if( flags[0] == '1' )
|
|
status_set("OB");
|
|
else
|
|
status_set("OL");
|
|
|
|
if( flags[1] == '1' )
|
|
status_set("LB");
|
|
|
|
if( flags[2] == '1' )
|
|
status_set("BOOST");
|
|
|
|
/* this has no mapping */
|
|
#if 0
|
|
if( flags[3] == '1' )
|
|
setinfo( INFO_ALRM_GENERAL, "1" );
|
|
#endif
|
|
|
|
#if 0
|
|
/* and these are... ? */
|
|
|
|
if( flags[5] == '1' )
|
|
status_set("TIP");
|
|
if( flags[6] == '1' )
|
|
status_set("SD");
|
|
#endif
|
|
|
|
status_commit();
|
|
|
|
if( DEBUG )
|
|
printf( "Status is %s\n", dstate_getinfo("ups.status"));
|
|
}
|
|
|
|
/********************************************************************
|
|
*
|
|
* Function parses the response of the query1 ( "Q1" ) command.
|
|
* Also sets various values (IPFreq ... )
|
|
*
|
|
********************************************************************/
|
|
static void query1( char *buf )
|
|
{
|
|
#define WORDMAXLEN 255
|
|
char value[WORDMAXLEN];
|
|
char word[WORDMAXLEN];
|
|
char *newPOS;
|
|
char *oldPOS;
|
|
int count = 0;
|
|
|
|
if( DEBUG )
|
|
printf( "Q1 Buffer is : %s\n" , buf + 1 );
|
|
oldPOS = buf + 1;
|
|
newPOS = oldPOS;
|
|
|
|
do
|
|
{
|
|
newPOS = StringSplit( oldPOS, word, WORDMAXLEN );
|
|
StringStrip( word, value);
|
|
oldPOS = newPOS;
|
|
|
|
if( DEBUG )
|
|
{
|
|
printf( "value=%s\n", value );
|
|
fflush( stdout );
|
|
}
|
|
switch( count )
|
|
{
|
|
case 0:
|
|
/* IP Voltage */
|
|
dstate_setinfo("input.voltage", "%s", value );
|
|
break;
|
|
case 1:
|
|
/* IP Fault Voltage */
|
|
break;
|
|
case 2:
|
|
/* OP Voltage */
|
|
dstate_setinfo("output.voltage", "%s", value);
|
|
break;
|
|
case 3:
|
|
/* OP Load*/
|
|
dstate_setinfo("ups.load", "%s", value );
|
|
break;
|
|
case 4:
|
|
/* IP Frequency */
|
|
dstate_setinfo("input.frequency", "%s", value);
|
|
break;
|
|
case 5:
|
|
/* Battery Cell Voltage */
|
|
dstate_setinfo("battery.voltage", "%s", value);
|
|
break;
|
|
case 6:
|
|
/* UPS Temperature */
|
|
dstate_setinfo("ups.temperature", "%s", value );
|
|
break;
|
|
case 7:
|
|
/* Flags */
|
|
parseFlags( value );
|
|
break;
|
|
default:
|
|
/* Should never be reached */
|
|
break;
|
|
}
|
|
count ++;
|
|
oldPOS = newPOS;
|
|
}
|
|
while( newPOS != NULL );
|
|
}
|
|
|
|
/********************************************************************
|
|
*
|
|
* Function parses the response of the query3 ( "Q3" ) command.
|
|
* Also sets various values (IPFreq ... )
|
|
*
|
|
********************************************************************/
|
|
static void query3( char *buf )
|
|
{
|
|
#define WORDMAXLEN 255
|
|
char value[WORDMAXLEN];
|
|
char word[WORDMAXLEN];
|
|
char *newPOS;
|
|
char *oldPOS;
|
|
int count = 0;
|
|
|
|
if( DEBUG )
|
|
printf( "Q3 Buffer is : %s\n" , buf+1 );
|
|
oldPOS = buf + 1;
|
|
newPOS = oldPOS;
|
|
|
|
do
|
|
{
|
|
newPOS = StringSplit( oldPOS, word, WORDMAXLEN );
|
|
StringStrip( word, value);
|
|
oldPOS = newPOS;
|
|
|
|
/* Shortcut */
|
|
if( newPOS == NULL )
|
|
break;
|
|
|
|
if( DEBUG )
|
|
{
|
|
printf( "value=%s\n", value );
|
|
fflush( stdout );
|
|
}
|
|
switch( count )
|
|
{
|
|
case 0:
|
|
/* UPS ID */
|
|
break;
|
|
case 1:
|
|
/* Input Voltage */
|
|
dstate_setinfo("input.voltage", "%s", value );
|
|
break;
|
|
case 2:
|
|
/* Input Fault Voltage */
|
|
break;
|
|
case 3:
|
|
/* Output Voltage */
|
|
dstate_setinfo("output.voltage", "%s", value);
|
|
break;
|
|
case 4:
|
|
/* Output Current */
|
|
dstate_setinfo("output.current", "%s", value );
|
|
break;
|
|
case 5:
|
|
/* Input Frequency */
|
|
dstate_setinfo("input.frequency", "%s", value);
|
|
break;
|
|
case 6:
|
|
/* Battery Cell Voltage */
|
|
dstate_setinfo("battery.voltage", "%s", value);
|
|
break;
|
|
case 7:
|
|
/* Temperature */
|
|
dstate_setinfo("ups.temperature", "%s", value );
|
|
break;
|
|
case 8:
|
|
/* Estimated Runtime */
|
|
dstate_setinfo("battery.runtime", "%s", value);
|
|
break;
|
|
case 9:
|
|
/* Charge Status */
|
|
dstate_setinfo("battery.charge", "%s", value);
|
|
break;
|
|
case 10:
|
|
/* Flags */
|
|
parseFlags( value );
|
|
break;
|
|
case 11:
|
|
/* Flags2 */
|
|
break;
|
|
default:
|
|
/* This should never be reached */
|
|
/* printf( "DEFAULT\n" ); */
|
|
break;
|
|
}
|
|
count ++;
|
|
oldPOS = newPOS;
|
|
}
|
|
while( newPOS != NULL );
|
|
}
|
|
|
|
/********************************************************************
|
|
*
|
|
* Function to parse the WhoAmI response of the UPS. Also sets the
|
|
* values of the firmware version and the UPS identification.
|
|
*
|
|
********************************************************************/
|
|
static void parseWH( char *buf )
|
|
{
|
|
strncpy( name, buf + 16, 30 );
|
|
name[30] = '\0';
|
|
strncpy( firmware, buf + 4, 5 );
|
|
firmware[5] = '\0';
|
|
if( DEBUG )
|
|
printf( "Name = %s, Firmware Version = %s\n", name, firmware );
|
|
}
|
|
|
|
/********************************************************************
|
|
*
|
|
* Function to parse the old and possible broken WhoAmI response
|
|
* and set the values for the firmware Version and the identification
|
|
* of the UPS.
|
|
*
|
|
********************************************************************/
|
|
static void parseOldWH( char *buf )
|
|
{
|
|
strncpy( name, buf + 4, 12 );
|
|
name[12] = '\0';
|
|
strncpy( firmware, buf, 4 );
|
|
firmware[4] = '\0';
|
|
if( DEBUG )
|
|
printf( "Name = %s, Firmware Version = %s\n", name, firmware );
|
|
}
|
|
|
|
/********************************************************************
|
|
*
|
|
* Function to fake a WhoAmI response of a UPS that returns NAK.
|
|
*
|
|
********************************************************************/
|
|
static void fakeWH(void)
|
|
{
|
|
strcpy( name, "GenericUPS" );
|
|
strcpy( firmware, "unkn" );
|
|
if( DEBUG )
|
|
printf( "Name = %s, Firmware Version = %s\n", name, firmware );
|
|
}
|
|
|
|
static int ups_ident( void )
|
|
{
|
|
char buf[255];
|
|
int ret;
|
|
|
|
/* Check presence of Q1 */
|
|
ret = ser_send_pace(upsfd, UPS_PACE, "%s", "Q1\x0D" );
|
|
ret = ser_get_line(upsfd, buf, sizeof(buf), '\r', "", 3, 0);
|
|
ret = strlen( buf );
|
|
if( ret != 46 )
|
|
{
|
|
/* No Q1 response found */
|
|
type = 0;
|
|
return -1;
|
|
}
|
|
else
|
|
{
|
|
if( DEBUG )
|
|
printf( "Found Q1\n" );
|
|
type = Q1;
|
|
}
|
|
|
|
/* Check presence of Q3 */
|
|
ret = ser_send_pace(upsfd, UPS_PACE, "%s", "Q3\x0D" );
|
|
ret = ser_get_line(upsfd, buf, sizeof(buf), '\r', "", 3, 0);
|
|
ret = strlen( buf );
|
|
if( ret == 70 )
|
|
{
|
|
if( DEBUG )
|
|
printf( "Found Q3\n" );
|
|
type = Q1 | Q3;
|
|
}
|
|
|
|
/* Check presence of WH ( Who am I ) */
|
|
ret = ser_send_pace(upsfd, UPS_PACE, "%s", "WH\x0D" );
|
|
ret = ser_get_line(upsfd, buf, sizeof(buf), '\r', "", 3, 0);
|
|
ret = strlen( buf );
|
|
if( ret == 112 )
|
|
{
|
|
if( DEBUG )
|
|
printf( "WH found\n" );
|
|
parseWH( buf );
|
|
}
|
|
else if( ret == 53 )
|
|
{
|
|
if( DEBUG )
|
|
printf( "Old (broken) WH found\n" );
|
|
parseOldWH( buf );
|
|
}
|
|
else if( ret == 3 && strcmp(buf, "NAK") == 0 )
|
|
{
|
|
if( DEBUG )
|
|
printf( "WH was NAKed\n" );
|
|
fakeWH( );
|
|
}
|
|
else if( ret > 0 )
|
|
{
|
|
if( DEBUG )
|
|
printf( "WH says <%s> with length %i\n", buf, ret );
|
|
upslog_with_errno( LOG_INFO,
|
|
"New WH String found. Please report to maintainer\n" );
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/********************************************************************
|
|
*
|
|
*
|
|
*
|
|
*
|
|
********************************************************************/
|
|
void upsdrv_help( void )
|
|
{
|
|
|
|
}
|
|
|
|
/********************************************************************
|
|
*
|
|
* Function to initialize the fields of the ups driver.
|
|
*
|
|
********************************************************************/
|
|
void upsdrv_initinfo(void)
|
|
{
|
|
dstate_setinfo("ups.mfr", "MASTERGUARD");
|
|
dstate_setinfo("ups.model", "unknown");
|
|
|
|
/*
|
|
dstate_addcmd("test.battery.stop");
|
|
dstate_addcmd("test.battery.start");
|
|
*/
|
|
|
|
if( strlen( name ) > 0 )
|
|
dstate_setinfo("ups.model", "%s", name);
|
|
if( strlen( firmware ) > 0 )
|
|
dstate_setinfo("ups.firmware", "%s", firmware);
|
|
}
|
|
|
|
/********************************************************************
|
|
*
|
|
* This is the main function. It gets called if the driver wants
|
|
* to update the ups status and the informations.
|
|
*
|
|
********************************************************************/
|
|
void upsdrv_updateinfo(void)
|
|
{
|
|
char buf[255];
|
|
int ret;
|
|
int lenRSP=0;
|
|
|
|
if( DEBUG )
|
|
printf( "update info\n" );
|
|
|
|
/* Q3 found ? */
|
|
if( type & Q3 )
|
|
{
|
|
ser_send_pace(upsfd, UPS_PACE, "%s", "Q3\x0D" );
|
|
lenRSP = 70;
|
|
}
|
|
/* Q1 found ? */
|
|
else if( type & Q1 )
|
|
{
|
|
ser_send_pace(upsfd, UPS_PACE, "%s", "Q1\x0D" );
|
|
lenRSP = 46;
|
|
}
|
|
/* Should never be reached */
|
|
else
|
|
{
|
|
fatalx(EXIT_FAILURE, "Error, no Query mode defined. Please file bug against driver.");
|
|
}
|
|
|
|
sleep( UPSDELAY );
|
|
|
|
buf[0] = '\0';
|
|
ret = ser_get_line(upsfd, buf, sizeof(buf), '\r', "", 3, 0);
|
|
ret = strlen( buf );
|
|
|
|
if( ret != lenRSP )
|
|
{
|
|
if( DEBUG )
|
|
printf( "buf = %s len = %i\n", buf, ret );
|
|
upslog_with_errno( LOG_ERR, "Error in UPS response " );
|
|
dstate_datastale();
|
|
return;
|
|
}
|
|
|
|
/* Parse the response from the UPS */
|
|
if( type & Q3 )
|
|
{
|
|
query3( buf );
|
|
dstate_dataok();
|
|
return;
|
|
}
|
|
if( type & Q1 )
|
|
{
|
|
query1( buf );
|
|
dstate_dataok();
|
|
return;
|
|
}
|
|
}
|
|
|
|
/********************************************************************
|
|
*
|
|
* Called if the driver wants to shutdown the UPS.
|
|
* ( also used by the "-k" command line switch )
|
|
*
|
|
* This cuts the utility from the UPS after 20 seconds and restores
|
|
* the utility one minute _after_ the utility to the UPS has restored
|
|
*
|
|
********************************************************************/
|
|
void upsdrv_shutdown(void)
|
|
{
|
|
/* ups will come up within a minute if utility is restored */
|
|
ser_send_pace(upsfd, UPS_PACE, "%s", "S.2R0001\x0D" );
|
|
}
|
|
|
|
/********************************************************************
|
|
*
|
|
* Populate the command line switches.
|
|
*
|
|
* CS: Cancel the shutdown process
|
|
*
|
|
********************************************************************/
|
|
void upsdrv_makevartable(void)
|
|
{
|
|
addvar( VAR_FLAG, "CS", "Cancel Shutdown" );
|
|
}
|
|
|
|
/********************************************************************
|
|
*
|
|
* This is the first function called by the UPS driver.
|
|
* Detects the UPS and handles the command line args.
|
|
*
|
|
********************************************************************/
|
|
void upsdrv_initups(void)
|
|
{
|
|
int count = 0;
|
|
int fail = 0;
|
|
int good = 0;
|
|
|
|
/* setup serial port */
|
|
upsfd = ser_open(device_path);
|
|
ser_set_speed(upsfd, device_path, B2400);
|
|
|
|
name[0] = '\0';
|
|
firmware[0] = '\0';
|
|
|
|
/* probe ups type */
|
|
do
|
|
{
|
|
count++;
|
|
|
|
if( ups_ident( ) != 1 )
|
|
fail++;
|
|
/* at least two good identifications */
|
|
if( (count - fail) == 2 )
|
|
{
|
|
good = 1;
|
|
break;
|
|
}
|
|
} while( (count<MAXTRIES) | (good) );
|
|
|
|
if( ! good )
|
|
{
|
|
fatalx(EXIT_FAILURE, "No MASTERGUARD UPS found" );
|
|
}
|
|
|
|
upslogx(LOG_INFO, "MASTERGUARD UPS found\n" );
|
|
|
|
/* Cancel Shutdown */
|
|
if( testvar("CS") )
|
|
{
|
|
ser_send_pace(upsfd, UPS_PACE, "%s", "C\x0D" );
|
|
fatalx(EXIT_FAILURE, "Shutdown cancelled");
|
|
}
|
|
}
|
|
|
|
/********************************************************************
|
|
*
|
|
* VIM Preferences.
|
|
* As you probably know vim is the best editor ever ;-)
|
|
* http://www.vim.org
|
|
*
|
|
* vim:ts=4:sw=4:tw=78:et
|
|
*
|
|
********************************************************************/
|
|
|
|
void upsdrv_cleanup(void)
|
|
{
|
|
ser_close(upsfd, device_path);
|
|
}
|