2010-03-25 23:20:59 +00:00
|
|
|
|
/*
|
|
|
|
|
*
|
|
|
|
|
* microdowell.c: support for Microdowell Enterprise Nxx/Bxx serial protocol based UPSes
|
|
|
|
|
*
|
|
|
|
|
* Copyright (C) Elio Corbolante <eliocor at microdowell.com>
|
|
|
|
|
*
|
|
|
|
|
* microdowell.c created on 27/09/2007
|
|
|
|
|
*
|
|
|
|
|
* 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
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
anything commented is optional
|
|
|
|
|
anything else is mandatory
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#define ENTERPRISE_PROTOCOL
|
|
|
|
|
|
|
|
|
|
#include "microdowell.h"
|
|
|
|
|
|
|
|
|
|
#include "main.h"
|
|
|
|
|
#include "serial.h"
|
|
|
|
|
#include <sys/ioctl.h>
|
|
|
|
|
#include "timehead.h"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#define MAX_START_DELAY 999999
|
|
|
|
|
#define MAX_SHUTDOWN_DELAY 32767
|
|
|
|
|
/* Maximum length of a string representing these values */
|
|
|
|
|
#define MAX_START_DELAY_LEN 6
|
|
|
|
|
#define MAX_SHUTDOWN_DELAY_LEN 5
|
|
|
|
|
|
|
|
|
|
#define DRIVER_NAME "MICRODOWELL UPS driver"
|
|
|
|
|
#define DRIVER_VERSION "0.01"
|
|
|
|
|
|
|
|
|
|
/* driver description structure */
|
|
|
|
|
upsdrv_info_t upsdrv_info = {
|
|
|
|
|
DRIVER_NAME,
|
|
|
|
|
DRIVER_VERSION,
|
|
|
|
|
"Elio Corbolante <eliocor@microdowell.com>",
|
|
|
|
|
DRV_STABLE,
|
|
|
|
|
{ NULL }
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
ENT_STRUCT ups ;
|
|
|
|
|
int instcmd(const char *cmdname, const char *extra);
|
|
|
|
|
int setvar(const char *varname, const char *val);
|
|
|
|
|
|
|
|
|
|
/* he knew... macros should evaluate their arguments only once */
|
|
|
|
|
#define CLAMP(x, min, max) (((x) < (min)) ? (min) : (((x) > (max)) ? (max) : (x)))
|
|
|
|
|
|
|
|
|
|
static int CheckDataChecksum(unsigned char *Buff, int Len)
|
|
|
|
|
{
|
|
|
|
|
int i, Idx ;
|
|
|
|
|
unsigned char Xor ;
|
|
|
|
|
|
|
|
|
|
ups.FramePointer = Xor = 0 ;
|
|
|
|
|
for (Idx=0 ; Idx < Len ; Idx++)
|
|
|
|
|
if (Buff[Idx] == STX_CHAR)
|
|
|
|
|
break ;
|
|
|
|
|
|
|
|
|
|
ups.FramePointer = Idx ; /* Memorise start point. */
|
|
|
|
|
|
|
|
|
|
/* Check that the message is not to short... */
|
|
|
|
|
if ( (Idx > (Len-4)) || (Idx+Buff[Idx+1]+2 > Len) )
|
|
|
|
|
return(ERR_MSG_TOO_SHORT) ; /* To short message! */
|
|
|
|
|
|
|
|
|
|
/* Calculate checksum */
|
|
|
|
|
for (i=Idx+1 ; i < Idx+Buff[Idx+1]+2 ; i++)
|
|
|
|
|
Xor ^= Buff[i] ;
|
|
|
|
|
|
|
|
|
|
/* if Xor != then checksum error */
|
|
|
|
|
if (Xor != Buff[i])
|
|
|
|
|
return(ERR_MSG_CHECKSUM) ; /* error in checksum */
|
|
|
|
|
|
|
|
|
|
/* If checksum OK: return */
|
|
|
|
|
return(0) ;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2011-01-26 09:35:08 +00:00
|
|
|
|
static const char *ErrMessages[] = {
|
2010-03-25 23:20:59 +00:00
|
|
|
|
/* 0 */ "errorcode NOT DEFINED", /* default error message */
|
|
|
|
|
/* 1 */ "I2C bus busy (e2prom)",
|
|
|
|
|
/* 2 */ "Command received: checksum not valid",
|
|
|
|
|
/* 3 */ "Command received: unrecognized command",
|
|
|
|
|
/* 4 */ "WRITE: eeprom address not multiple of 8",
|
|
|
|
|
/* 5 */ "READ: eeprom address (added with size) out of bound ",
|
|
|
|
|
/* 6 */ "error writing e2prom address",
|
|
|
|
|
/* 7 */ "error writing e2prom subaddress",
|
|
|
|
|
/* 8 */ "error reading e2prom data",
|
|
|
|
|
/* 9 */ "error writing e2prom address",
|
|
|
|
|
/* 10 */ "error reading e2prom subaddress",
|
|
|
|
|
/* 11 */ "error writing e2prom data",
|
|
|
|
|
/* 12 */ "error writing e2prom address during data verification",
|
|
|
|
|
/* 13 */ "error verification e2prom data",
|
|
|
|
|
/* 14 */ "e2prom data are different from those in the write buffer",
|
|
|
|
|
/* 15 */ "e2prom checksum error",
|
|
|
|
|
|
|
|
|
|
/* 16 */ "NO CHARS FROM PORT",
|
|
|
|
|
/* 17 */ "TOO FEW DATA RECEIVED: [STX] near end of message",
|
|
|
|
|
/* 18 */ "CHECKSUM ERROR IN MESSAGE",
|
|
|
|
|
/* 19 */ "OK",
|
|
|
|
|
/* */ ""
|
|
|
|
|
} ;
|
|
|
|
|
|
|
|
|
|
const char *PrintErr(int ErrCode)
|
|
|
|
|
{
|
|
|
|
|
int msgIndex = 0 ;
|
|
|
|
|
|
|
|
|
|
/* The default 'msgIndex' is 0 (error code not defined) */
|
|
|
|
|
switch (ErrCode) {
|
|
|
|
|
case ERR_NO_ERROR : msgIndex = 19 ; break ;
|
|
|
|
|
|
|
|
|
|
case ERR_I2C_BUSY : msgIndex = 1 ; break ;
|
|
|
|
|
case ERR_CMD_CHECKSUM : msgIndex = 2 ; break ;
|
|
|
|
|
case ERR_CMD_UNRECOG : msgIndex = 3 ; break ;
|
|
|
|
|
case ERR_EEP_NOBLOCK : msgIndex = 4 ; break ;
|
|
|
|
|
case ERR_EEP_OOBOUND : msgIndex = 5 ; break ;
|
|
|
|
|
case ERR_EEP_WADDR1 : msgIndex = 6 ; break ;
|
|
|
|
|
case ERR_EEP_WSADDR1 : msgIndex = 7 ; break ;
|
|
|
|
|
case ERR_EEP_RDATA : msgIndex = 8 ; break ;
|
|
|
|
|
case ERR_EEP_WADDR2 : msgIndex = 9 ; break ;
|
|
|
|
|
case ERR_EEP_WSADDR2 : msgIndex = 10 ; break ;
|
|
|
|
|
case ERR_EEP_WDATA : msgIndex = 11 ; break ;
|
|
|
|
|
case ERR_EEP_WADDRVER : msgIndex = 12 ; break ;
|
|
|
|
|
case ERR_EEP_WDATAVER : msgIndex = 13 ; break ;
|
|
|
|
|
case ERR_EEP_VERIFY : msgIndex = 14 ; break ;
|
|
|
|
|
case ERR_EEP_CHECKSUM : msgIndex = 15 ; break ;
|
|
|
|
|
|
|
|
|
|
case ERR_COM_NO_CHARS : msgIndex = 16 ; break ;
|
|
|
|
|
case ERR_MSG_TOO_SHORT : msgIndex = 17 ; break ;
|
|
|
|
|
case ERR_MSG_CHECKSUM : msgIndex = 18 ; break ;
|
|
|
|
|
default: msgIndex = 0 ; break ;
|
|
|
|
|
}
|
|
|
|
|
return(ErrMessages[msgIndex]) ;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int CheckErrCode(unsigned char * Buff)
|
|
|
|
|
{
|
|
|
|
|
auto int Ret ;
|
|
|
|
|
|
|
|
|
|
switch (Buff[2]) {
|
|
|
|
|
/* I have found an error */
|
|
|
|
|
case CMD_NACK :
|
|
|
|
|
Ret = Buff[3] ;
|
|
|
|
|
break ;
|
|
|
|
|
|
|
|
|
|
case CMD_ACK :
|
|
|
|
|
case CMD_GET_STATUS :
|
|
|
|
|
case CMD_GET_MEASURES :
|
|
|
|
|
case CMD_GET_CONFIG :
|
|
|
|
|
case CMD_GET_BATT_STAT :
|
|
|
|
|
case CMD_GET_MASK :
|
|
|
|
|
case CMD_SET_TIMER :
|
|
|
|
|
case CMD_BATT_TEST :
|
|
|
|
|
case CMD_GET_BATT_TEST :
|
|
|
|
|
case CMD_SD_ONESHOT :
|
|
|
|
|
case CMD_GET_SD_ONESHOT:
|
|
|
|
|
case CMD_SET_SCHEDULE :
|
|
|
|
|
case CMD_GET_SCHEDULE :
|
|
|
|
|
case CMD_GET_EEP_BLOCK :
|
|
|
|
|
case CMD_SET_EEP_BLOCK :
|
|
|
|
|
case CMD_GET_EEP_SEED :
|
|
|
|
|
case CMD_INIT :
|
|
|
|
|
Ret = 0 ;
|
|
|
|
|
break ;
|
|
|
|
|
|
|
|
|
|
/* command not recognized */
|
|
|
|
|
default:
|
|
|
|
|
Ret = ERR_CMD_UNRECOG ;
|
|
|
|
|
break ;
|
|
|
|
|
}
|
|
|
|
|
return(Ret) ;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void SendCmdToSerial(unsigned char *Buff, int Len)
|
|
|
|
|
{
|
|
|
|
|
int i, ret ;
|
|
|
|
|
unsigned char Tmp[20], Xor ;
|
|
|
|
|
|
|
|
|
|
Tmp[0] = STX_CHAR ;
|
|
|
|
|
Xor = Tmp[1] = (unsigned char) (Len & 0x1f) ;
|
|
|
|
|
for (i=0 ; i < Tmp[1] ; i++)
|
|
|
|
|
{
|
|
|
|
|
Tmp[i+2] = Buff[i] ;
|
|
|
|
|
Xor ^= Buff[i] ;
|
|
|
|
|
}
|
|
|
|
|
Tmp[Len+2] = Xor ;
|
|
|
|
|
|
|
|
|
|
upsdebug_hex(4, "->UPS", Tmp, Len+3) ;
|
|
|
|
|
|
|
|
|
|
/* flush serial port */
|
|
|
|
|
ret = ser_flush_in(upsfd, "", 0) ; /* empty input buffer */
|
|
|
|
|
|
|
|
|
|
ret = ser_send_buf(upsfd, Tmp, Len+3) ; /* send data to the UPS */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
unsigned char * CmdSerial(unsigned char *OutBuffer, int Len, unsigned char *RetBuffer)
|
|
|
|
|
{
|
|
|
|
|
#define TMP_BUFF_LEN 1024
|
|
|
|
|
unsigned char InpBuff[TMP_BUFF_LEN+1] ;
|
|
|
|
|
unsigned char TmpBuff[3] ;
|
|
|
|
|
int i, ErrCode ;
|
|
|
|
|
unsigned char *p ;
|
|
|
|
|
int BuffLen ;
|
|
|
|
|
|
2011-06-01 20:31:49 +00:00
|
|
|
|
/* The default error code (no received character) */
|
2010-03-25 23:20:59 +00:00
|
|
|
|
ErrCode = ERR_COM_NO_CHARS ;
|
|
|
|
|
|
|
|
|
|
SendCmdToSerial(OutBuffer, Len) ;
|
2011-06-01 20:31:49 +00:00
|
|
|
|
usleep(10000) ; /* small delay (1/100 s) */
|
2010-03-25 23:20:59 +00:00
|
|
|
|
|
2011-06-01 20:31:49 +00:00
|
|
|
|
/* get chars until timeout */
|
2010-03-25 23:20:59 +00:00
|
|
|
|
BuffLen = 0 ;
|
|
|
|
|
while (ser_get_char(upsfd, TmpBuff, 0, 10000) == 1)
|
|
|
|
|
{
|
|
|
|
|
InpBuff[BuffLen++] = TmpBuff[0] ;
|
|
|
|
|
if (BuffLen > TMP_BUFF_LEN)
|
|
|
|
|
break ;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
upsdebug_hex(4, "UPS->", InpBuff, BuffLen) ;
|
|
|
|
|
|
|
|
|
|
if (BuffLen > 0)
|
|
|
|
|
{
|
|
|
|
|
ErrCode = CheckDataChecksum(InpBuff, BuffLen) ;
|
|
|
|
|
/* upsdebugx(4, "ErrCode = %d / Len = %d", ErrCode, BuffLen); */
|
|
|
|
|
|
|
|
|
|
if (!ErrCode)
|
|
|
|
|
{
|
|
|
|
|
/* FramePointer to valid data! */
|
|
|
|
|
p = InpBuff + ups.FramePointer ;
|
|
|
|
|
/* p now point to valid data.
|
|
|
|
|
check if it is a error code. */
|
|
|
|
|
ErrCode = CheckErrCode(p) ;
|
|
|
|
|
if (!ErrCode)
|
|
|
|
|
{
|
|
|
|
|
/* I copy the data read in the buffer */
|
|
|
|
|
for(i=0 ; i<(int) (p[1])+3 ; i++)
|
|
|
|
|
RetBuffer[i] = p[i] ;
|
|
|
|
|
ups.ErrCode = ups.ErrCount = ups.CommStatus = 0 ;
|
|
|
|
|
return(RetBuffer) ;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* if they have arrived here, wants to say that I have found an error.... */
|
|
|
|
|
ups.ErrCode = ErrCode ;
|
|
|
|
|
ups.ErrCount++ ;
|
|
|
|
|
if (ups.ErrCount > 3)
|
|
|
|
|
{
|
|
|
|
|
ups.CommStatus &= 0x80 ;
|
|
|
|
|
ups.CommStatus |= (unsigned char) (ups.ErrCount & 0x7F) ;
|
|
|
|
|
if (ups.ErrCount > 100)
|
|
|
|
|
ups.ErrCount = 100 ;
|
|
|
|
|
}
|
|
|
|
|
return(NULL) ; /* There have been errors in the reading of the data */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static int detect_hardware(void)
|
|
|
|
|
{
|
|
|
|
|
unsigned char OutBuff[20] ;
|
|
|
|
|
unsigned char InpBuff[260] ;
|
|
|
|
|
unsigned char *p ;
|
|
|
|
|
int i, retries ;
|
|
|
|
|
struct tm *Time ;
|
|
|
|
|
time_t lTime ;
|
|
|
|
|
|
|
|
|
|
ups.ge_2kVA = 0 ;
|
|
|
|
|
|
|
|
|
|
for (retries=0 ; retries <= 4 ; retries++)
|
|
|
|
|
{
|
|
|
|
|
/* Identify UPS model */
|
|
|
|
|
OutBuff[0] = CMD_GET_EEP_BLOCK ; /* get EEPROM data */
|
|
|
|
|
OutBuff[1] = EEP_UPS_MODEL ; /* UPS model */
|
|
|
|
|
OutBuff[2] = 8 ; /* number of bytes */
|
|
|
|
|
if ((p = CmdSerial(OutBuff, LEN_GET_EEP_BLOCK, InpBuff)) != NULL)
|
|
|
|
|
{
|
|
|
|
|
/* got UPS model */
|
|
|
|
|
for (i=0 ; i<8 ; i++)
|
|
|
|
|
ups.UpsModel[i] = p[i+5] ;
|
|
|
|
|
ups.UpsModel[8] = '\0' ;
|
|
|
|
|
upsdebugx(2, "get 'UPS model': %s", PrintErr(ups.ErrCode));
|
|
|
|
|
break ; /* UPS identified: exit from ' for' LOOP */
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
upsdebugx(1, "[%d] get 'UPS model': %s", retries, PrintErr(ups.ErrCode));
|
|
|
|
|
upslogx(LOG_ERR, "[%d] Unable to identify UPS model [%s]", retries, PrintErr(ups.ErrCode));
|
|
|
|
|
usleep(100000) ; /* small delay (1/10 s) for next retry */
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* check if I was unable to find the UPS */
|
|
|
|
|
if (retries == 4) /* UPS not found! */
|
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
|
|
/* UPS serial number */
|
|
|
|
|
OutBuff[0] = CMD_GET_EEP_BLOCK ; /* get EEPROM data */
|
|
|
|
|
OutBuff[1] = EEP_SERIAL_NUM ; /* UPS serial # */
|
|
|
|
|
OutBuff[2] = 8 ; /* number of bytes */
|
|
|
|
|
if ((p = CmdSerial(OutBuff, LEN_GET_EEP_BLOCK, InpBuff)) != NULL)
|
|
|
|
|
{
|
|
|
|
|
/* got UPS serial # */
|
|
|
|
|
for (i=0 ; i<8 ; i++)
|
|
|
|
|
ups.SerialNumber[i] = p[i+5] ;
|
|
|
|
|
ups.SerialNumber[8] = '\0' ;
|
|
|
|
|
upsdebugx(2, "get 'UPS Serial #': %s", PrintErr(ups.ErrCode));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
upsdebugx(1, "get 'UPS Serial #': %s", PrintErr(ups.ErrCode));
|
|
|
|
|
upslogx(LOG_ERR, "Unable to identify UPS serial # [%s]", PrintErr(ups.ErrCode));
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Get Production date & FW info */
|
|
|
|
|
OutBuff[0] = CMD_GET_EEP_BLOCK ; /* get EEPROM data */
|
|
|
|
|
OutBuff[1] = EEP_PROD_DATE ; /* Production date + HW version */
|
|
|
|
|
OutBuff[2] = 8 ; /* number of bytes */
|
|
|
|
|
if ((p = CmdSerial(OutBuff, LEN_GET_EEP_BLOCK, InpBuff)) != NULL)
|
|
|
|
|
{
|
|
|
|
|
/* got Production date & FW info */
|
|
|
|
|
p += 5 ; /* 'p' points to eeprom data */
|
|
|
|
|
ups.YearOfProd = 2000 + p[0] ; /* Production year of the UPS */
|
|
|
|
|
ups.MonthOfProd = p[1] ; /* Production month of the UPS */
|
|
|
|
|
ups.DayOfProd = p[2] ; /* Production day of the UPS */
|
|
|
|
|
ups.HW_MajorVersion = (p[3]>>4) & 0x0F ; /* Hardware: Major version */
|
|
|
|
|
ups.HW_MinorVersion = (p[3] & 0x0F) ; /* Hardware: Minor version */
|
|
|
|
|
ups.BR_MajorVersion = (p[4]>>4) & 0x0F ; /* BoardHardware: Major version */
|
|
|
|
|
ups.BR_MinorVersion = (p[4] & 0x0F) ; /* BoardHardware: Minor version */
|
|
|
|
|
ups.FW_MajorVersion = (p[5]>>4) & 0x0F ; /* Firmware: Major version */
|
|
|
|
|
ups.FW_MinorVersion = (p[5] & 0x0F) ; /* Firmware: Minor version */
|
|
|
|
|
ups.FW_SubVersion = p[6] ; /* Firmware: SUBVERSION (special releases */
|
|
|
|
|
ups.BatteryNumber = p[7] ; /* number of batteries in UPS */
|
|
|
|
|
upsdebugx(2, "get 'Production date': %s", PrintErr(ups.ErrCode));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
upsdebugx(1, "get 'Production date': %s", PrintErr(ups.ErrCode));
|
|
|
|
|
upslogx(LOG_ERR, "Unable to read Production date [%s]", PrintErr(ups.ErrCode));
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Get Battery substitution date */
|
|
|
|
|
OutBuff[0] = CMD_GET_EEP_BLOCK ; /* get EEPROM data */
|
|
|
|
|
OutBuff[1] = EEP_BATT_SUBST ; /* Battery substitution dates */
|
|
|
|
|
OutBuff[2] = 8 ; /* number of bytes */
|
|
|
|
|
if ((p = CmdSerial(OutBuff, LEN_GET_EEP_BLOCK, InpBuff)) != NULL)
|
|
|
|
|
{
|
|
|
|
|
/* got Battery substitution date */
|
|
|
|
|
p += 5 ; /* 'p' points to eeprom data */
|
|
|
|
|
upsdebugx(2, "get 'Battery Subst. Dates': %s", PrintErr(ups.ErrCode));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
upsdebugx(1, "get 'Battery Subst. Dates': %s", PrintErr(ups.ErrCode));
|
|
|
|
|
upslogx(LOG_ERR, "Unable to read Battery Subst. Dates [%s]", PrintErr(ups.ErrCode));
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Get working time (battery+normal)) */
|
|
|
|
|
OutBuff[0] = CMD_GET_EEP_BLOCK ; /* get EEPROM data */
|
|
|
|
|
OutBuff[1] = EEP_MIN_VBATT ; /* working time */
|
|
|
|
|
OutBuff[2] = 8 ; /* number of bytes */
|
|
|
|
|
if ((p = CmdSerial(OutBuff, LEN_GET_EEP_BLOCK, InpBuff)) != NULL)
|
|
|
|
|
{
|
|
|
|
|
/* got working time (battery+normal)) */
|
|
|
|
|
p += 5 ; /* 'p' points to eeprom data */
|
|
|
|
|
upsdebugx(2, "get 'UPS life info': %s", PrintErr(ups.ErrCode));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
upsdebugx(1, "get 'UPS life info': %s", PrintErr(ups.ErrCode));
|
|
|
|
|
upslogx(LOG_ERR, "Unable to read UPS life info [%s]", PrintErr(ups.ErrCode));
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Get the THRESHOLD table (0) */
|
|
|
|
|
OutBuff[0] = CMD_GET_EEP_BLOCK ; /* get EEPROM data */
|
|
|
|
|
OutBuff[1] = EEP_THRESHOLD_0 ; /* Thresholds table 0 */
|
|
|
|
|
OutBuff[2] = 8 ; /* number of bytes */
|
|
|
|
|
if ((p = CmdSerial(OutBuff, LEN_GET_EEP_BLOCK, InpBuff)) != NULL)
|
|
|
|
|
{
|
|
|
|
|
/* got the THRESHOLD table (0) */
|
|
|
|
|
p += 5 ; /* 'p' points to eeprom data */
|
|
|
|
|
upsdebugx(2, "get 'Thresholds table 0': %s", PrintErr(ups.ErrCode));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
upsdebugx(1, "get 'Thresholds table 0': %s", PrintErr(ups.ErrCode));
|
|
|
|
|
upslogx(LOG_ERR, "Unable to read Thresholds table 0 [%s]", PrintErr(ups.ErrCode));
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Get the THRESHOLD table (1) */
|
|
|
|
|
OutBuff[0] = CMD_GET_EEP_BLOCK ; /* get EEPROM data */
|
|
|
|
|
OutBuff[1] = EEP_THRESHOLD_1 ; /* Thresholds table 0 */
|
|
|
|
|
OutBuff[2] = 8 ; /* number of bytes */
|
|
|
|
|
if ((p = CmdSerial(OutBuff, LEN_GET_EEP_BLOCK, InpBuff)) != NULL)
|
|
|
|
|
{
|
|
|
|
|
/* got the THRESHOLD table (1) */
|
|
|
|
|
p += 5 ; /* 'p' points to eeprom data */
|
|
|
|
|
upsdebugx(2, "get 'Thresholds table 1': %s", PrintErr(ups.ErrCode));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
upsdebugx(1, "get 'Thresholds table 1': %s", PrintErr(ups.ErrCode));
|
|
|
|
|
upslogx(LOG_ERR, "Unable to read Thresholds table 1 [%s]", PrintErr(ups.ErrCode));
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Get the THRESHOLD table (2) */
|
|
|
|
|
OutBuff[0] = CMD_GET_EEP_BLOCK ; /* get EEPROM data */
|
|
|
|
|
OutBuff[1] = EEP_THRESHOLD_2 ; /* Thresholds table 0 */
|
|
|
|
|
OutBuff[2] = 8 ; /* number of bytes */
|
|
|
|
|
if ((p = CmdSerial(OutBuff, LEN_GET_EEP_BLOCK, InpBuff)) != NULL)
|
|
|
|
|
{
|
|
|
|
|
/* got the THRESHOLD table (2) */
|
|
|
|
|
p += 5 ; /* 'p' points to eeprom data */
|
|
|
|
|
upsdebugx(2, "get 'Thresholds table 2': %s", PrintErr(ups.ErrCode));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
upsdebugx(1, "get 'Thresholds table 2': %s", PrintErr(ups.ErrCode));
|
|
|
|
|
upslogx(LOG_ERR, "Unable to read Thresholds table 2 [%s]", PrintErr(ups.ErrCode));
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Get Option Bytes */
|
|
|
|
|
OutBuff[0] = CMD_GET_EEP_BLOCK ; /* get EEPROM data */
|
|
|
|
|
OutBuff[1] = EEP_OPT_BYTE_BLK ; /* Option Bytes */
|
|
|
|
|
OutBuff[2] = 8 ; /* number of bytes */
|
|
|
|
|
if ((p = CmdSerial(OutBuff, LEN_GET_EEP_BLOCK, InpBuff)) != NULL)
|
|
|
|
|
{
|
|
|
|
|
/* got Option Bytes */
|
|
|
|
|
p += 5 ; /* 'p' points to eeprom data */
|
|
|
|
|
dstate_setinfo("input.voltage.nominal", "%s", (p[EEP_OPT_BYTE_1] & 0x02) ? "110": "230") ;
|
|
|
|
|
dstate_setinfo("input.frequency", "%s", (p[EEP_OPT_BYTE_1] & 0x01) ? "60.0": "50.0") ;
|
|
|
|
|
upsdebugx(2, "get 'Option Bytes': %s", PrintErr(ups.ErrCode));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
upsdebugx(1, "get 'Option Bytes': %s", PrintErr(ups.ErrCode));
|
|
|
|
|
upslogx(LOG_ERR, "Unable to read Option Bytes [%s]", PrintErr(ups.ErrCode));
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Get UPS sensitivity (fault points) */
|
|
|
|
|
OutBuff[0] = CMD_GET_EEP_BLOCK ; /* get EEPROM data */
|
|
|
|
|
OutBuff[1] = EEP_FAULT_POINTS ; /* Number of fault points (sensitivity)) */
|
|
|
|
|
OutBuff[2] = 8 ; /* number of bytes */
|
|
|
|
|
if ((p = CmdSerial(OutBuff, LEN_GET_EEP_BLOCK, InpBuff)) != NULL)
|
|
|
|
|
{
|
|
|
|
|
/* got UPS sensitivity (fault points) */
|
|
|
|
|
p += 5 ; /* 'p' points to eeprom data */
|
|
|
|
|
switch (p[0]) {
|
|
|
|
|
case 1 : dstate_setinfo("input.sensitivity", "H") ; break ;
|
|
|
|
|
case 2 : dstate_setinfo("input.sensitivity", "M") ; break ;
|
|
|
|
|
case 3 : dstate_setinfo("input.sensitivity", "L") ; break ;
|
|
|
|
|
default : dstate_setinfo("input.sensitivity", "L") ; break ;
|
|
|
|
|
}
|
|
|
|
|
upsdebugx(2, "get 'Input Sensitivity': %s", PrintErr(ups.ErrCode));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
upsdebugx(1, "get 'Input Sensitivity': %s", PrintErr(ups.ErrCode));
|
|
|
|
|
upslogx(LOG_ERR, "Unable to read Input Sensitivity [%s]", PrintErr(ups.ErrCode));
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Set internal UPS clock */
|
|
|
|
|
time(&lTime) ;
|
|
|
|
|
Time = localtime(&lTime) ;
|
|
|
|
|
|
|
|
|
|
OutBuff[0] = CMD_SET_TIMER ; /* set UPS internal timer */
|
|
|
|
|
OutBuff[1] = (Time->tm_wday+6) % 7 ; /* week day (0=monday) */
|
|
|
|
|
OutBuff[2] = Time->tm_hour ; /* hours */
|
|
|
|
|
OutBuff[3] = Time->tm_min ; /* minutes */
|
|
|
|
|
OutBuff[4] = Time->tm_sec; /* seconds */
|
|
|
|
|
if ((p = CmdSerial(OutBuff, LEN_SET_TIMER, InpBuff)) != NULL)
|
|
|
|
|
{
|
|
|
|
|
upsdebugx(2, "set 'UPS internal clock': %s", PrintErr(ups.ErrCode));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
upsdebugx(1, "set 'UPS internal clock': %s", PrintErr(ups.ErrCode));
|
|
|
|
|
upslogx(LOG_ERR, "Unable to set UPS internal clock [%s]", PrintErr(ups.ErrCode));
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0; /* everything was OK */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* ========================= */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void upsdrv_updateinfo(void)
|
|
|
|
|
{
|
|
|
|
|
unsigned char OutBuff[20] ;
|
|
|
|
|
unsigned char InpBuff[260] ;
|
|
|
|
|
unsigned char *p ;
|
|
|
|
|
/* int i ; */
|
|
|
|
|
|
|
|
|
|
OutBuff[0] = CMD_GET_STATUS ; /* get UPS status */
|
|
|
|
|
if ((p = CmdSerial(OutBuff, LEN_GET_STATUS, InpBuff)) != NULL)
|
|
|
|
|
{
|
|
|
|
|
p += 3 ; /* 'p' points to received data */
|
|
|
|
|
|
|
|
|
|
status_init(); /* reset status flags */
|
|
|
|
|
|
|
|
|
|
/* store last UPS status */
|
|
|
|
|
ups.StatusUPS = (int)p[0] | ((int)p[1]<<8) | ((int)p[2]<<16) | ((int)p[3]<<24) ;
|
|
|
|
|
ups.ShortStatus = (int)p[0] | ((int)p[1]<<8) ;
|
|
|
|
|
upsdebugx(1, "ups.StatusUPS: %08lX", ups.StatusUPS);
|
|
|
|
|
upsdebugx(1, "ups.ShortStatus: %04X", ups.ShortStatus);
|
|
|
|
|
|
|
|
|
|
/* on battery? */
|
|
|
|
|
if (p[0] & 0x01)
|
|
|
|
|
status_set("OB"); /* YES */
|
|
|
|
|
|
|
|
|
|
/* LOW battery? */
|
|
|
|
|
if (p[0] & 0x02)
|
|
|
|
|
status_set("LB"); /* YES */
|
|
|
|
|
|
|
|
|
|
/* online? */
|
|
|
|
|
if (p[0] & 0x08)
|
|
|
|
|
status_set("OL"); /* YES */
|
|
|
|
|
|
|
|
|
|
/* Overload? */
|
|
|
|
|
if (p[1] & 0xC0)
|
|
|
|
|
status_set("OVER"); /* YES */
|
|
|
|
|
|
|
|
|
|
/* Offline/Init/Stanby/Waiting for mains? */
|
|
|
|
|
if (p[0] & 0xE0)
|
|
|
|
|
status_set("OFF"); /* YES */
|
|
|
|
|
|
|
|
|
|
/* AVR on (boost)? */
|
|
|
|
|
if (p[4] & 0x04)
|
|
|
|
|
status_set("BOOST"); /* YES */
|
|
|
|
|
|
|
|
|
|
/* AVR on (buck)? */
|
|
|
|
|
if (p[4] & 0x08)
|
|
|
|
|
status_set("TRIM"); /* YES */
|
|
|
|
|
|
|
|
|
|
dstate_setinfo("ups.time", "%02d:%02d:%02d", p[6], p[7], p[8]) ;
|
|
|
|
|
upsdebugx(3, "get 'Get Status': %s", PrintErr(ups.ErrCode));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
upsdebugx(1, "get 'Get Status': %s", PrintErr(ups.ErrCode));
|
|
|
|
|
/* upslogx(LOG_ERR, "get 'Get Status': %s", PrintErr(ups.ErrCode)); */
|
|
|
|
|
dstate_datastale();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ========================= */
|
|
|
|
|
|
|
|
|
|
OutBuff[0] = CMD_GET_MEASURES ; /* get UPS values */
|
|
|
|
|
if ((p = CmdSerial(OutBuff, LEN_GET_MEASURES, InpBuff)) != NULL)
|
|
|
|
|
{
|
|
|
|
|
p += 3 ; /* 'p' points to received data */
|
|
|
|
|
|
|
|
|
|
dstate_setinfo("input.voltage", "%d", (int)((float)(p[2]*256 + p[3]) / 36.4)) ;
|
|
|
|
|
if (ups.ge_2kVA)
|
|
|
|
|
{
|
|
|
|
|
dstate_setinfo("output.voltage", "%d", (int)((float)(p[6]*256 + p[7]) / 63.8)) ;
|
|
|
|
|
dstate_setinfo("output.current", "%1.f", ((float)(p[8]*256 + p[9]) / 635.0)) ;
|
|
|
|
|
dstate_setinfo("battery.voltage", "%.1f", ((float) (p[4]*256 + p[5])) / 329.0) ;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
dstate_setinfo("output.voltage", "%d", (int)((float)(p[6]*256 + p[7]) / 36.4)) ;
|
|
|
|
|
dstate_setinfo("output.current", "%1.f", ((float)(p[8]*256 + p[9]) / 1350.0)) ;
|
|
|
|
|
dstate_setinfo("battery.voltage", "%.1f", ((float) (p[4]*256 + p[5])) / 585.0) ;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dstate_setinfo("ups.temperature", "%d", (int)(((float)(p[10]*256 + p[11])-202.97) / 1.424051)) ;
|
|
|
|
|
upsdebugx(3, "get 'Get Measures': %s", PrintErr(ups.ErrCode));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
/* upsdebugx(1, "get 'Get Measures': %s", PrintErr(ups.ErrCode)); */
|
|
|
|
|
upslogx(LOG_ERR, "get 'Get Measures': %s", PrintErr(ups.ErrCode));
|
|
|
|
|
dstate_datastale();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ========================= */
|
|
|
|
|
|
|
|
|
|
OutBuff[0] = CMD_GET_BAT_LD ; /* get UPS Battery and Load values */
|
|
|
|
|
if ((p = CmdSerial(OutBuff, LEN_GET_BAT_LD, InpBuff)) != NULL)
|
|
|
|
|
{
|
|
|
|
|
p += 3 ; /* 'p' points to received data */
|
|
|
|
|
|
|
|
|
|
dstate_setinfo("ups.power", "%d", (p[4]*256 + p[5])) ;
|
|
|
|
|
/* dstate_setinfo("ups.realpower", "%d", (int)((float)(p[4]*256 + p[5]) * 0.6)) ; */
|
|
|
|
|
dstate_setinfo("battery.charge", "%d", (int)p[0]) ;
|
|
|
|
|
dstate_setinfo("ups.load", "%d", (int)p[6]) ;
|
|
|
|
|
upsdebugx(3, "get 'Get Batt+Load Status': %s", PrintErr(ups.ErrCode));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
/* upsdebugx(1, "get 'Get Batt+Load Status': %s", PrintErr(ups.ErrCode)); */
|
|
|
|
|
upslogx(LOG_ERR, "get 'Get Batt+Load Status': %s", PrintErr(ups.ErrCode));
|
|
|
|
|
dstate_datastale();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
status_commit();
|
|
|
|
|
dstate_dataok();
|
|
|
|
|
|
|
|
|
|
poll_interval = 2;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* ========================= */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int instcmd(const char *cmdname, const char *extra)
|
|
|
|
|
{
|
|
|
|
|
unsigned char OutBuff[20] ;
|
|
|
|
|
unsigned char InpBuff[260] ;
|
|
|
|
|
unsigned char *p ;
|
|
|
|
|
/* int i ; */
|
|
|
|
|
|
|
|
|
|
upsdebugx(1, "instcmd(%s, %s)", cmdname, extra);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (strcasecmp(cmdname, "load.on") == 0)
|
|
|
|
|
{
|
|
|
|
|
OutBuff[0] = CMD_SD_ONESHOT ; /* turn ON the outputs */
|
|
|
|
|
OutBuff[1] = 0xFF ; /* ALL outputs */
|
|
|
|
|
OutBuff[2] = 0x08 ; /* Enable outputs (immediately) */
|
|
|
|
|
OutBuff[3] = 0 ;
|
|
|
|
|
OutBuff[4] = 0 ;
|
|
|
|
|
OutBuff[5] = 0 ;
|
|
|
|
|
OutBuff[6] = 0 ;
|
|
|
|
|
OutBuff[7] = 0 ;
|
|
|
|
|
if ((p = CmdSerial(OutBuff, LEN_SD_ONESHOT, InpBuff)) != NULL)
|
|
|
|
|
{
|
|
|
|
|
p += 3 ; /* 'p' points to received data */
|
|
|
|
|
upslogx(LOG_INFO, "Turning load on.");
|
|
|
|
|
upsdebugx(1, "'SdOneshot(turn_ON)': %s", PrintErr(ups.ErrCode));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
upsdebugx(1, "'SdOneshot(turn_ON)': %s", PrintErr(ups.ErrCode));
|
|
|
|
|
upslogx(LOG_ERR, "'SdOneshot(turn_ON)': %s", PrintErr(ups.ErrCode));
|
|
|
|
|
}
|
|
|
|
|
return STAT_INSTCMD_HANDLED;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (strcasecmp(cmdname, "load.off") == 0)
|
|
|
|
|
{
|
|
|
|
|
OutBuff[0] = CMD_SD_ONESHOT ; /* turn ON the outputs */
|
|
|
|
|
OutBuff[1] = 0xFF ; /* ALL outputs */
|
|
|
|
|
OutBuff[2] = 0x04 ; /* Disable outputs (immediately) */
|
|
|
|
|
OutBuff[3] = 0 ;
|
|
|
|
|
OutBuff[4] = 0 ;
|
|
|
|
|
OutBuff[5] = 0 ;
|
|
|
|
|
OutBuff[6] = 0 ;
|
|
|
|
|
OutBuff[7] = 0 ;
|
|
|
|
|
if ((p = CmdSerial(OutBuff, LEN_SD_ONESHOT, InpBuff)) != NULL)
|
|
|
|
|
{
|
|
|
|
|
p += 3 ; /* 'p' points to received data */
|
|
|
|
|
upslogx(LOG_INFO, "Turning load on.");
|
|
|
|
|
upsdebugx(1, "'SdOneshot(turn_OFF)': %s", PrintErr(ups.ErrCode));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
upsdebugx(1, "'SdOneshot(turn_OFF)': %s", PrintErr(ups.ErrCode));
|
|
|
|
|
upslogx(LOG_ERR, "'SdOneshot(turn_OFF)': %s", PrintErr(ups.ErrCode));
|
|
|
|
|
}
|
|
|
|
|
return STAT_INSTCMD_HANDLED;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (strcasecmp(cmdname, "shutdown.return") == 0)
|
|
|
|
|
{
|
|
|
|
|
OutBuff[0] = CMD_SD_ONESHOT ; /* turn ON the outputs */
|
|
|
|
|
OutBuff[1] = 0xFF ; /* ALL outputs */
|
|
|
|
|
if (ups.StatusUPS & 0x01)
|
|
|
|
|
OutBuff[2] = 0x02 ; /* Battery shutdown */
|
|
|
|
|
else
|
|
|
|
|
OutBuff[2] = 0x01 ; /* Online shutdown */
|
|
|
|
|
|
|
|
|
|
if (ups.ShutdownDelay < 6)
|
|
|
|
|
ups.ShutdownDelay = 6 ;
|
|
|
|
|
|
|
|
|
|
OutBuff[3] = (ups.ShutdownDelay >> 8) & 0xFF ; /* SDDELAY (MSB) Shutdown value (seconds) */
|
|
|
|
|
OutBuff[4] = (ups.ShutdownDelay & 0xFF) ; /* SDDELAY (LSB) */
|
|
|
|
|
OutBuff[5] = (ups.WakeUpDelay >> 16) & 0xFF ; /* WUDELAY (MSB) Wakeup value (seconds) */
|
|
|
|
|
OutBuff[6] = (ups.WakeUpDelay >> 8) & 0xFF ; /* WUDELAY (...) */
|
|
|
|
|
OutBuff[7] = (ups.WakeUpDelay & 0xFF ) ; /* WUDELAY (LSB) */
|
|
|
|
|
|
|
|
|
|
if ((p = CmdSerial(OutBuff, LEN_SD_ONESHOT, InpBuff)) != NULL)
|
|
|
|
|
{
|
|
|
|
|
p += 3 ; /* 'p' points to received data */
|
|
|
|
|
upslogx(LOG_INFO, "Shutdown command(TYPE=%02x, SD=%u, WU=%u)", OutBuff[2], ups.ShutdownDelay, ups.WakeUpDelay) ;
|
|
|
|
|
upsdebugx(3, "Shutdown command(TYPE=%02x, SD=%u, WU=%u): %s", OutBuff[2], ups.ShutdownDelay, ups.WakeUpDelay, PrintErr(ups.ErrCode));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
upsdebugx(1, "Shutdown command(TYPE=%02x, SD=%u, WU=%u): %s", OutBuff[2], ups.ShutdownDelay, ups.WakeUpDelay, PrintErr(ups.ErrCode));
|
|
|
|
|
upslogx(LOG_ERR, "Shutdown command(SD=%u, WU=%u): %s", ups.ShutdownDelay, ups.WakeUpDelay, PrintErr(ups.ErrCode));
|
|
|
|
|
}
|
|
|
|
|
return STAT_INSTCMD_HANDLED;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (strcasecmp(cmdname, "shutdown.stayoff") == 0)
|
|
|
|
|
{
|
|
|
|
|
OutBuff[0] = CMD_SD_ONESHOT ; /* turn ON the outputs */
|
|
|
|
|
OutBuff[1] = 0xFF ; /* ALL outputs */
|
|
|
|
|
if (ups.StatusUPS & 0x01)
|
|
|
|
|
OutBuff[2] = 0x02 ; /* Battery shutdown */
|
|
|
|
|
else
|
|
|
|
|
OutBuff[2] = 0x01 ; /* Online shutdown */
|
|
|
|
|
|
|
|
|
|
if (ups.ShutdownDelay < 6)
|
|
|
|
|
ups.ShutdownDelay = 6 ;
|
|
|
|
|
|
|
|
|
|
OutBuff[3] = (ups.ShutdownDelay >> 8) & 0xFF ; /* SDDELAY (MSB) Shutdown value (seconds) */
|
|
|
|
|
OutBuff[4] = (ups.ShutdownDelay & 0xFF) ; /* SDDELAY (LSB) */
|
|
|
|
|
OutBuff[5] = 0 ; /* WUDELAY (MSB) Wakeup value (seconds) */
|
|
|
|
|
OutBuff[6] = 0 ; /* WUDELAY (...) */
|
|
|
|
|
OutBuff[7] = 0 ; /* WUDELAY (LSB) */
|
|
|
|
|
|
|
|
|
|
if ((p = CmdSerial(OutBuff, LEN_SD_ONESHOT, InpBuff)) != NULL)
|
|
|
|
|
{
|
|
|
|
|
p += 3 ; /* 'p' points to received data */
|
|
|
|
|
upslogx(LOG_INFO, "shutdown.stayoff - (TYPE=%02x, SD=%u, WU=%u)", OutBuff[2], ups.ShutdownDelay, 0) ;
|
|
|
|
|
upsdebugx(3, "shutdown.stayoff - (TYPE=%02x, SD=%u, WU=%u): %s", OutBuff[2], ups.ShutdownDelay, 0, PrintErr(ups.ErrCode));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
upsdebugx(1, "shutdown.stayoff - (TYPE=%02x, SD=%u, WU=%u): %s", OutBuff[2], ups.ShutdownDelay, 0, PrintErr(ups.ErrCode));
|
|
|
|
|
upslogx(LOG_ERR, "shutdown.stayoff - (TYPE=%02x, SD=%u, WU=%u)", OutBuff[2], ups.ShutdownDelay, 0) ;
|
|
|
|
|
}
|
|
|
|
|
return STAT_INSTCMD_HANDLED;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return STAT_INSTCMD_UNKNOWN;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int setvar(const char *varname, const char *val)
|
|
|
|
|
{
|
|
|
|
|
int delay;
|
|
|
|
|
|
|
|
|
|
if (sscanf(val, "%d", &delay) != 1)
|
|
|
|
|
{
|
|
|
|
|
return STAT_SET_UNKNOWN;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (strcasecmp(varname, "ups.delay.start") == 0)
|
|
|
|
|
{
|
|
|
|
|
delay = CLAMP(delay, 0, MAX_START_DELAY);
|
|
|
|
|
upsdebugx(1, "set 'WUDELAY': %d/%d", delay, ups.WakeUpDelay);
|
|
|
|
|
ups.WakeUpDelay = delay ;
|
|
|
|
|
dstate_setinfo("ups.delay.start", "%d", ups.WakeUpDelay);
|
|
|
|
|
dstate_dataok();
|
|
|
|
|
return STAT_SET_HANDLED;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (strcasecmp(varname, "ups.delay.shutdown") == 0)
|
|
|
|
|
{
|
|
|
|
|
delay = CLAMP(delay, 0, MAX_SHUTDOWN_DELAY);
|
|
|
|
|
upsdebugx(1, "set 'SDDELAY': %d/%d", delay, ups.ShutdownDelay);
|
|
|
|
|
ups.ShutdownDelay = delay;
|
|
|
|
|
dstate_setinfo("ups.delay.shutdown", "%d", ups.ShutdownDelay);
|
|
|
|
|
dstate_dataok();
|
|
|
|
|
return STAT_SET_HANDLED;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return STAT_SET_UNKNOWN;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void upsdrv_initinfo(void)
|
|
|
|
|
{
|
|
|
|
|
/* Get vars from ups.conf */
|
|
|
|
|
if (getval("ups.delay.shutdown")) {
|
|
|
|
|
ups.ShutdownDelay = CLAMP(atoi(getval("ups.delay.shutdown")), 0, MAX_SHUTDOWN_DELAY);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
ups.ShutdownDelay = 120; /* Shutdown delay in seconds */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (getval("ups.delay.start")) {
|
|
|
|
|
ups.WakeUpDelay = CLAMP(atoi(getval("ups.delay.start")), 0, MAX_START_DELAY);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
ups.WakeUpDelay = 10; /* WakeUp delay in seconds */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (detect_hardware() == -1)
|
|
|
|
|
{
|
|
|
|
|
fatalx(EXIT_FAILURE,
|
|
|
|
|
"Unable to detect a Microdowell's Enterprise UPS on port %s\nCheck the cable, port name and try again", device_path);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* I set the correspondig UPS variables
|
|
|
|
|
They were read in 'detect_hardware()'
|
|
|
|
|
some other variables were set in 'detect_hardware()' */
|
|
|
|
|
dstate_setinfo("ups.model", "Enterprise N%s", ups.UpsModel+3) ;
|
|
|
|
|
dstate_setinfo("ups.power.nominal", "%d", atoi(ups.UpsModel+3) * 100) ;
|
|
|
|
|
dstate_setinfo("ups.realpower.nominal", "%d", atoi(ups.UpsModel+3) * 60) ;
|
|
|
|
|
|
|
|
|
|
ups.ge_2kVA = 0 ; /* differentiate between 2 type of UPSs */
|
|
|
|
|
if (atoi(ups.UpsModel+3) >= 20)
|
|
|
|
|
ups.ge_2kVA = 1 ;
|
|
|
|
|
|
|
|
|
|
dstate_setinfo("ups.type", "online-interactive") ;
|
|
|
|
|
dstate_setinfo("ups.serial", "%s", ups.SerialNumber) ;
|
|
|
|
|
dstate_setinfo("ups.firmware", "%d.%d (%d)", ups.FW_MajorVersion, ups.FW_MinorVersion, ups.FW_SubVersion) ;
|
|
|
|
|
dstate_setinfo("ups.firmware.aux", "%d.%d %d.%d", ups.HW_MajorVersion, ups.HW_MinorVersion,
|
|
|
|
|
ups.BR_MajorVersion, ups.BR_MinorVersion) ;
|
|
|
|
|
dstate_setinfo("ups.mfr", "Microdowell") ;
|
|
|
|
|
dstate_setinfo("ups.mfr.date", "%04d/%02d/%02d", ups.YearOfProd, ups.MonthOfProd, ups.DayOfProd) ;
|
|
|
|
|
dstate_setinfo("battery.packs", "%d", ups.BatteryNumber) ;
|
|
|
|
|
|
|
|
|
|
dstate_setinfo("driver.version.internal", "%s", DRIVER_VERSION) ;
|
|
|
|
|
|
|
|
|
|
/* Register the available variables. */
|
|
|
|
|
dstate_setinfo("ups.delay.start", "%d", ups.WakeUpDelay);
|
|
|
|
|
dstate_setflags("ups.delay.start", ST_FLAG_RW | ST_FLAG_STRING);
|
|
|
|
|
dstate_setaux("ups.delay.start", MAX_START_DELAY_LEN);
|
|
|
|
|
|
|
|
|
|
dstate_setinfo("ups.delay.shutdown", "%d", ups.ShutdownDelay);
|
|
|
|
|
dstate_setflags("ups.delay.shutdown", ST_FLAG_RW | ST_FLAG_STRING);
|
|
|
|
|
dstate_setaux("ups.delay.shutdown", MAX_SHUTDOWN_DELAY_LEN);
|
|
|
|
|
|
|
|
|
|
dstate_addcmd("load.on");
|
|
|
|
|
dstate_addcmd("load.off");
|
|
|
|
|
dstate_addcmd("shutdown.return");
|
|
|
|
|
dstate_addcmd("shutdown.stayoff");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Register the available instant commands. */
|
|
|
|
|
/* dstate_addcmd("test.battery.start");
|
|
|
|
|
dstate_addcmd("test.battery.stop");
|
|
|
|
|
dstate_addcmd("shutdown.stop");
|
|
|
|
|
dstate_addcmd("beeper.toggle");
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/* set handlers */
|
|
|
|
|
upsh.instcmd = instcmd ;
|
|
|
|
|
upsh.setvar = setvar;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void upsdrv_shutdown(void)
|
|
|
|
|
{
|
|
|
|
|
unsigned char OutBuff[20] ;
|
|
|
|
|
unsigned char InpBuff[260] ;
|
|
|
|
|
unsigned char *p ;
|
|
|
|
|
unsigned char BatteryFlag=0 ;
|
|
|
|
|
|
|
|
|
|
OutBuff[0] = CMD_GET_STATUS ; /* get UPS status */
|
|
|
|
|
if ((p = CmdSerial(OutBuff, LEN_GET_STATUS, InpBuff)) != NULL)
|
|
|
|
|
{
|
|
|
|
|
p += 3 ; /* 'p' points to received data */
|
|
|
|
|
|
|
|
|
|
status_init(); /* reset status flags */
|
|
|
|
|
|
|
|
|
|
/* store last UPS status */
|
|
|
|
|
ups.StatusUPS = (int)p[0] | ((int)p[1]<<8) | ((int)p[2]<<16) | ((int)p[3]<<24) ;
|
|
|
|
|
ups.ShortStatus = (int)p[0] | ((int)p[1]<<8) ;
|
|
|
|
|
upsdebugx(1, "ups.StatusUPS: %08lX", ups.StatusUPS);
|
|
|
|
|
upsdebugx(1, "ups.ShortStatus: %04X", ups.ShortStatus);
|
|
|
|
|
|
|
|
|
|
/* on battery? */
|
|
|
|
|
if (p[0] & 0x01)
|
|
|
|
|
BatteryFlag = 1 ; /* YES */
|
|
|
|
|
upsdebugx(3, "get 'Get Status': %s", PrintErr(ups.ErrCode));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
upsdebugx(1, "get 'Get Status': %s", PrintErr(ups.ErrCode));
|
|
|
|
|
/* upslogx(LOG_ERR, "get 'Get Status': %s", PrintErr(ups.ErrCode)); */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Send SHUTDOWN command */
|
|
|
|
|
OutBuff[0] = CMD_SD_ONESHOT ; /* Send SHUTDOWN command */
|
|
|
|
|
OutBuff[1] = 0xFF ; /* shutdown on ALL ports */
|
|
|
|
|
|
|
|
|
|
/* is the UPS on battery? */
|
|
|
|
|
if (BatteryFlag)
|
|
|
|
|
OutBuff[2] = 0x02 ; /* Type of shutdown (BATTERY MODE) */
|
|
|
|
|
else
|
|
|
|
|
OutBuff[2] = 0x01 ; /* Type of shutdown (ONLINE) */
|
|
|
|
|
|
|
|
|
|
if (ups.ShutdownDelay < 6)
|
|
|
|
|
ups.ShutdownDelay = 6 ;
|
|
|
|
|
|
|
|
|
|
OutBuff[3] = (ups.ShutdownDelay >> 8) & 0xFF ; /* SDDELAY (MSB) Shutdown value (seconds) */
|
|
|
|
|
OutBuff[4] = (ups.ShutdownDelay & 0xFF) ; /* SDDELAY (LSB) */
|
|
|
|
|
OutBuff[5] = (ups.WakeUpDelay >> 16) & 0xFF ; /* WUDELAY (MSB) Wakeup value (seconds) */
|
|
|
|
|
OutBuff[6] = (ups.WakeUpDelay >> 8) & 0xFF ; /* WUDELAY (...) */
|
|
|
|
|
OutBuff[7] = (ups.WakeUpDelay & 0xFF ) ; /* WUDELAY (LSB) */
|
|
|
|
|
|
|
|
|
|
if ((p = CmdSerial(OutBuff, LEN_SD_ONESHOT, InpBuff)) != NULL)
|
|
|
|
|
{
|
|
|
|
|
upsdebugx(2, "Shutdown command(TYPE=%02x, SD=%u, WU=%u): %s", OutBuff[2], ups.ShutdownDelay, ups.WakeUpDelay, PrintErr(ups.ErrCode));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
/* command not sent: print error code */
|
|
|
|
|
upsdebugx(1, "Shutdown command(TYPE=%02x, SD=%u, WU=%u): %s", OutBuff[2], ups.ShutdownDelay, ups.WakeUpDelay, PrintErr(ups.ErrCode));
|
|
|
|
|
upslogx(LOG_ERR, "Shutdown command(SD=%u, WU=%u): %s", ups.ShutdownDelay, ups.WakeUpDelay, PrintErr(ups.ErrCode));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void upsdrv_help(void)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* list flags and values that you want to receive via -x */
|
|
|
|
|
void upsdrv_makevartable(void)
|
|
|
|
|
{
|
|
|
|
|
/* allow '-x xyzzy' */
|
|
|
|
|
/* addvar(VAR_FLAG, "xyzzy", "Enable xyzzy mode"); */
|
|
|
|
|
|
|
|
|
|
/* allow '-x foo=<some value>' */
|
|
|
|
|
addvar(VAR_VALUE, "ups.delay.shutdown", "Override shutdown delay (120s)");
|
|
|
|
|
addvar(VAR_VALUE, "ups.delay.start", "Override restart delay (10s)");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void upsdrv_initups(void)
|
|
|
|
|
{
|
|
|
|
|
upsfd = ser_open(device_path) ;
|
|
|
|
|
|
|
|
|
|
ser_set_speed(upsfd, device_path, B19200) ;
|
|
|
|
|
|
|
|
|
|
/* need to clear RTS and DTR: otherwise with default cable, communication will be problematic
|
|
|
|
|
It is the same as removing pin7 from cable (pin 7 is needed for Plug&Play compatibility) */
|
|
|
|
|
ser_set_dtr(upsfd, 0);
|
|
|
|
|
ser_set_rts(upsfd, 0);
|
|
|
|
|
|
|
|
|
|
usleep(10000) ; /* small delay (1/100 s)) */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void upsdrv_cleanup(void)
|
|
|
|
|
{
|
|
|
|
|
/* free(dynamic_mem); */
|
|
|
|
|
ser_close(upsfd, device_path) ;
|
|
|
|
|
}
|