nut/drivers/rhino.c

813 lines
20 KiB
C
Raw Permalink Normal View History

2010-03-25 23:20:59 +00:00
/* rhino.c - driver for Microsol Rhino UPS hardware
Copyright (C) 2004 Silvino B. Magalhães <sbm2yk@gmail.com>
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
2004/11/13 - Version 0.10 - Initial release
2005/07/07 - Version 0.20 - Initial rhino commands tests
2005/10/25 - Version 0.30 - Operational-1 release
2005/10/26 - Version 0.40 - Operational-2 release
2005/11/29 - Version 0.50 - rhino commands release
http://www.microsol.com.br
*/
#include <stdio.h>
#include <math.h>
#include "main.h"
#include "serial.h"
#include "timehead.h"
#define DRIVER_NAME "Microsol Rhino UPS driver"
#define DRIVER_VERSION "0.51"
/* driver description structure */
upsdrv_info_t upsdrv_info = {
DRIVER_NAME,
DRIVER_VERSION,
"Silvino B. Magalhaes <sbm2yk@gmail.com>",
DRV_STABLE,
{ NULL }
};
#define UPSDELAY 500 /* 0.5 ms delay */
typedef int bool_t;
#define false 0
#define true 1
/* rhino commands */
#define CMD_INON 0x0001
#define CMD_INOFF 0x0002
#define CMD_SHUT 0x0004
#define CMD_OUTON 0x0003
#define CMD_OUTOFF 0x0004
#define CMD_PASSON 0x0005
#define CMD_PASSOFF 0x0006
#define CMD_UPSCONT 0x0053
/* xoff - xon protocol
#define _SOH = 0x01; // start of header
#define _EOT = 0x04; // end of transmission
#define _ACK = 0x06; // acknoledge (positive)
#define _DLE = 0x10; // data link escape
#define _XOn = 0x11; // transmit on
#define _XOff = 0x13; // transmit off
#define _NAK = 0x15; // negative acknoledge
#define _SYN = 0x16; // synchronous idle
#define _CAN = 0x18; // cancel
*/
static int const pacsize = 37; /* size of receive data package */
/* autonomy calcule */
static double const AmpH = 40; // Amperes-hora da bateria
static double const VbatMin = 126; // Tensão mínina das baterias
static double const VbatNom = 144; // Tensão nominal das baterias
static double const FM = 0.32; // Fator multiplicativo de correção da autonomia
static double const FA = -2; // Fator aditivo de correção da autonomia
static double const ConstInt = 250; // Consumo interno sem o carregador
static double const Vin = 220; // Tensão de entrada
static int Day, Month, Year;
static int dian=0, mesn=0, anon=0, weekn=0;
static int ihour,imin, isec;
/* unsigned char DaysOnWeek; */
/* char seman[4]; */
/* int FExpansaoBateria; */
// internal variables
// package handshake ariables
/* int ContadorEstouro; */
static bool_t detected;
static bool_t SourceFail, Out110, RedeAnterior, OcorrenciaDeFalha;
static bool_t RetornoDaRede, SuperAquecimento, SuperAquecimentoAnterior;
static bool_t OverCharge, OldOverCharge, CriticBatt, OldCritBatt;
static bool_t Flag_inversor, BypassOn, InputOn, OutputOn;
static bool_t LowBatt, oldInversorOn;
/* data vetor from received and configuration data package - not used yet
unsigned char Dados[ 161 ]; */
/* identification group */
static int RhinoModel; /*, imodel; */
static int PotenciaNominal, PowerFactor;
/* input group */
static double AppPowerIn, UtilPowerIn, InFreq, InCurrent;
static double LimInfEntrada, LimSupEntrada, ValorNominalEntrada;
static int FatorPotEntrada;
/* output group */
static double OutVoltage, InVoltage, OutCurrent, AppPowerOut;
static double UtilPowerOut, OutFreq, LimInfSaida, LimSupSaida, ValorNominalSaida;
static int FatorPotSaida;
/* battery group */
static int Autonomy, Waiting;
static double BattVoltage, Temperature, LimInfBattSrc, LimSupBattSrc;
static double LimInfBattInv, LimSupBattInv, BattNonValue;
/* general group */
static int BoostVolt, Rendimento;
/* status group */
static unsigned char StatusEntrada, StatusSaida, StatusBateria;
/* events group */
static unsigned char EventosRede, EventosSaida, EventosBateria;
// Grupo de Programação
/* Methods */
static void ScanReceivePack();
static int AutonomyCalc( int );
static void CommReceive(const unsigned char*, int );
static void getbaseinfo();
static void getupdateinfo();
static unsigned char RecPack[37];
/* comment on english language */
/* #define PORTUGUESE */
/* The following Portuguese strings are in UTF-8. */
#ifdef PORTUGUESE
#define M_UNKN "Modêlo rhino desconhecido\n"
#define NO_RHINO "Rhino não detectado! abortando ...\n"
#define UPS_DATE "Data no UPS %4d/%02d/%02d\n"
#define SYS_DATE "Data do Sistema %4d/%02d/%02d dia da semana %s\n"
#define ERR_PACK "Pacote errado\n"
#define NO_EVENT "Não há eventos\n"
#define UPS_TIME "Hora interna UPS %0d:%02d:%02d\n"
#else
#define M_UNKN "Unknown rhino model\n"
#define NO_RHINO "Rhino not detected! aborting ...\n"
#define UPS_DATE "UPS Date %4d/%02d/%02d\n"
#define SYS_DATE "System Date %4d/%02d/%02d day of week %s\n"
#define ERR_PACK "Wrong package\n"
#define NO_EVENT "No events\n"
#define UPS_TIME "UPS internal Time %0d:%02d:%02d\n"
#endif
static int
AutonomyCalc( int ia ) /* all models */
{
int result = 0;
double auton, calc, currin;
if( ia )
{
if( ( BattVoltage == 0 ) )
result = 0;
else
{
calc = ( OutVoltage * OutCurrent )* 1.0 / ( 0.08 * BattVoltage );
auton = pow( calc, 1.18 );
if( ( auton == 0 ) )
result = 0;
else
{
auton = 1.0 / auton;
auton = auton * 11.07;
calc = ( BattVoltage * 1.0 / 10 ) - 168;
result = (int) ( auton * calc * 2.5 );
}
}
}
else
{
currin = ( UtilPowerOut + ConstInt ) *1.0 / Vin;
auton = ( ( ( AmpH *1.0 / currin ) * 60 * ( ( BattVoltage - VbatMin ) * 1.0 /( VbatNom - VbatMin ) ) * FM ) + FA );
if( ( BattVoltage > 129 ) || ( BattVoltage < 144 ) )
result = 133;
else
result = (int) auton;
}
return result;
}
/* Treat received package */
static void
ScanReceivePack( void )
{
/* model independent data */
Year = RecPack[31] + ( RecPack[32] * 100 );
Month = RecPack[30];
Day = RecPack[29];
/* UPS internal time */
ihour = RecPack[26];
imin = RecPack[27];
isec = RecPack[28];
/* Flag1 */
/* SobreTemp = ( ( 0x01 & RecPack[33]) = 0x01 ); */
/* OutputOn = ( ( 0x02 & RecPack[33]) = 0x02 ); OutputOn */
/* InputOn = ( ( 0x04 & RecPack[33]) = 0x04 ); InputOn */
/* ByPassOn = ( ( 0x08 & RecPack[33]) = 0x08 ); BypassOn */
/* Auto_HAB = ( ( 0x10 & RecPack[33]) = 0x10 ); */
/* Timer_HAB = ( ( 0x20 & RecPack[33]) = 0x20 ); */
/* Boost_Ligado = ( ( 0x40 & RecPack[33]) = 0x40 ); */
/* Bateria_Desc = ( ( 0x80 & RecPack[33]) = 0x80 ); */
/* Flag2 */
/* Quad_Ant_Ent = ( ( 0x01 & RecPack[34]) = 0x01 ); */
/* Quadratura = ( ( 0x02 & RecPack[34]) = 0x02 ); */
/* Termino_XMODEM = ( ( 0x04 & RecPack[34]) = 0x04 ); */
/* Em_Sincronismo = ( ( 0x08 & RecPack[34]) = 0x08 ); */
/* Out110 = ( ( 0x10 & RecPack[34]) = 0x10 ); Out110 */
/* Exec_Beep = ( ( 0x20 & RecPack[34]) = 0x20 ); */
/* LowBatt = ( ( 0x40 & RecPack[34]) = 0x40 ); LowBatt */
/* Boost_Sobre = ( ( 0x80 & RecPack[34]) = 0x80 ); */
/* Flag3 */
/* OverCharge = ( ( 0x01 & RecPack[35]) = 0x01 ); OverCharge */
/* SourceFail = ( ( 0x02 & RecPack[35]) = 0x02 ); SourceFail */
/* RedeAnterior = ( ( 0x04 & RecPack[35]) = 0x04 ); */
/* Cmd_Executado = ( ( 0x08 & RecPack[35]) = 0x08 ); */
/* Exec_Autoteste = ( ( 0x10 & RecPack[35]) = 0x10 ); */
/* Quad_Ant_Sai = ( ( 0x20 & RecPack[35]) = 0x20 ); */
/* ComandoSerial = ( ( 0x40 & RecPack[35]) = 0x40 ); */
/* SobreTensao = ( ( 0x80 & RecPack[35]) = 0x80 ); */
OutputOn = ( ( 0x02 & RecPack[33] ) == 0x02 );
InputOn = ( ( 0x04 & RecPack[33] ) == 0x04 );
BypassOn = ( ( 0x08 & RecPack[33] ) == 0x08 );
Out110 = ( ( 0x10 & RecPack[34] ) == 0x10 );
LowBatt = ( ( 0x40 & RecPack[34] ) == 0x40 );
OverCharge = ( ( 0x01 & RecPack[35] ) == 0x01 );
SourceFail = ( ( 0x02 & RecPack[35] ) == 0x02 );
/* model dependent data read */
PowerFactor = 800;
if( RecPack[0] ==0xC2 )
{
LimInfBattSrc = 174;
LimSupBattSrc = 192;//180????? carregador eh 180 (SCOPOS)
LimInfBattInv = 174;
LimSupBattInv = 192;//170????? (SCOPOS)
}
else
{
LimInfBattSrc = 138;
LimSupBattSrc = 162;//180????? carregador eh 180 (SCOPOS)
LimInfBattInv = 126;
LimSupBattInv = 156;//170????? (SCOPOS)
}
BattNonValue = 144;
/* VersaoInterna = "R10" + IntToStr( RecPack[1] ); */
InVoltage = RecPack[2];
InCurrent = RecPack[3];
UtilPowerIn = RecPack[4] + RecPack[5] * 256;
AppPowerIn = RecPack[6] + RecPack[7] * 256;
FatorPotEntrada = RecPack[8];
InFreq = ( RecPack[9] + RecPack[10] * 256 ) * 1.0 / 10;
OutVoltage = RecPack[11];
OutCurrent = RecPack[12];
UtilPowerOut = RecPack[13] + RecPack[14] * 256;
AppPowerOut = RecPack[15] + RecPack[16] * 256;
FatorPotSaida = RecPack[17];
OutFreq = ( RecPack[18] + RecPack[19] * 256 ) * 1.0 / 10;
BattVoltage = RecPack[20];
BoostVolt = RecPack[21] + RecPack[22] * 256;
Temperature = ( 0x7F & RecPack[23] );
Rendimento = RecPack[24];
/* model independent data */
if( ( BattVoltage < LimInfBattInv ) )
CriticBatt = true;
if( BypassOn )
OutVoltage = ( InVoltage * 1.0 / 2 ) + 5;
if( SourceFail && RedeAnterior ) // falha pela primeira vez
OcorrenciaDeFalha = true;
if( !( SourceFail ) && !( RedeAnterior ) ) // retorno da rede
RetornoDaRede = true;
if( !( SourceFail ) == RedeAnterior )
{
RetornoDaRede = false;
OcorrenciaDeFalha = false;
}
RedeAnterior = !( SourceFail );
LimInfSaida = 75;
LimSupSaida = 150;
ValorNominalSaida = 110;
LimInfEntrada = 190;
LimSupEntrada = 250;
ValorNominalEntrada = 220;
if( SourceFail )
{
StatusEntrada = 2;
RecPack[8] = 200; /* ?????????????????????????????????? */
}
else
{
StatusEntrada = 1;
RecPack[8] = 99; /* ??????????????????????????????????? */
}
if( OutputOn ) // Output Status
StatusSaida = 2;
else
StatusSaida = 1;
if( OverCharge )
StatusSaida = 3;
if( CriticBatt ) // Battery Status
StatusBateria = 4;
else
StatusBateria = 1;
EventosRede = 0;
if( OcorrenciaDeFalha )
EventosRede = 1;
if( RetornoDaRede )
EventosRede = 2;
/* verify InversorOn */
if( Flag_inversor )
{
oldInversorOn = InputOn;
Flag_inversor = false;
}
EventosSaida = 0;
if( InputOn && !( oldInversorOn ) )
EventosSaida = 26;
if( oldInversorOn && !( InputOn ) )
EventosSaida = 27;
oldInversorOn = InputOn;
if( SuperAquecimento && !( SuperAquecimentoAnterior ) )
EventosSaida = 12;
if( SuperAquecimentoAnterior && !( SuperAquecimento ) )
EventosSaida = 13;
SuperAquecimentoAnterior = SuperAquecimento;
EventosBateria = 0;
OldCritBatt = CriticBatt;
if( OverCharge && !( OldOverCharge ) )
EventosSaida = 10;
if( OldOverCharge && !( OverCharge ) )
EventosSaida = 11;
OldOverCharge = OverCharge;
/* autonomy calc. */
if( RecPack[ 0 ] ==0xC2 )
Autonomy = AutonomyCalc( 1 );
else
Autonomy = AutonomyCalc( 0 );
}
static void
CommReceive(const unsigned char *bufptr, int size)
{
int i, i_end, CheckSum, chk;
if( ( size==37 ) )
Waiting = 0;
printf("CommReceive size = %d waiting = %d\n", size, Waiting );
switch( Waiting )
{
/* normal package */
case 0:
{
if( size == 37 )
{
i_end = 37;
for( i = 0 ; i < i_end ; ++i )
{
RecPack[i] = *bufptr;
bufptr++;
}
/* CheckSum verify */
CheckSum = 0;
i_end = 36;
for( i = 0 ; i < i_end ; ++i )
{
chk = RecPack[ i ];
CheckSum = CheckSum + chk;
}
CheckSum = CheckSum % 256;
ser_flush_in(upsfd,"",0); /* clean port */
/* correct package */
if( ( RecPack[0] == 0xC0 || RecPack[0] == 0xC1 || RecPack[0] == 0xC2 || RecPack[0] == 0xC3 )
&& ( RecPack[ 36 ] == CheckSum ) )
{
if(!(detected))
{
RhinoModel = RecPack[0];
detected = true;
}
switch( RhinoModel )
{
case 0xC0:
case 0xC1:
case 0xC2:
case 0xC3:
{
ScanReceivePack();
break;
}
default:
{
printf( M_UNKN );
break;
}
}
}
}
break;
}
case 1:
{
/* dumping package, nothing to do yet */
Waiting = 0;
break;
}
}
Waiting =0;
}
static int
send_command( int cmd )
{
int i, chk, checksum=0, iend=18, sizes, ret, kount; /*, j, uc; */
unsigned char ch, psend[19];
sizes = 19;
checksum = 0;
/* mounting buffer to send */
for(i = 0; i < iend; i++ )
{
if ( i == 0 )
chk = 0x01;
else
{
if( i == 1)
chk = cmd;
else
chk = 0x00; // 0x20;
}
ch = chk;
psend[i] = ch; /* psend[0 - 17] */
if( i > 0 ) /* psend[0] not computed */
checksum = checksum + chk;
}
ch = checksum;
ch = (~( ch) ); /* not ch */
psend[iend] = ch;
/* send five times the command */
kount = 0;
while ( kount < 5 )
{
/* ret = ser_send_buf_pace(upsfd, UPSDELAY, psend, sizes );// optional delay */
for(i=0; i < 19; i++)
{
ret = ser_send_char( upsfd, psend[i] );
/* usleep ( UPSDELAY ); sending without delay */
}
usleep( UPSDELAY ); /* delay between sent command */
kount++;
}
return ret;
}
static void sendshut( void )
{
int i;
for(i=0; i < 30000; i++)
usleep( UPSDELAY ); /* 15 seconds delay */
send_command( CMD_SHUT );
upslogx(LOG_NOTICE, "Ups shutdown command sent");
printf("Ups shutdown command sent\n");
}
static void getbaseinfo(void)
{
unsigned char temp[256];
unsigned char Pacote[37];
int tam, i, j=0;
time_t *tmt;
struct tm *now;
char *Model;
tmt = ( time_t * ) malloc( sizeof( time_t ) );
time( tmt );
now = localtime( tmt );
dian = now->tm_mday;
mesn = now->tm_mon+1;
anon = now->tm_year+1900;
weekn = now->tm_wday;
/* trying detect rhino model */
while ( ( !detected ) && ( j < 10 ) )
{
temp[0] = 0; // flush temp buffer
tam = ser_get_buf_len(upsfd, temp, pacsize, 3, 0);
if( tam == 37 )
{
for( i = 0 ; i < tam ; i++ )
{
Pacote[i] = temp[i];
}
}
j++;
if( tam == 37)
CommReceive(Pacote, tam);
else
CommReceive(temp, tam);
}
if( (!detected) )
{
fatalx(EXIT_FAILURE, NO_RHINO );
}
switch( RhinoModel )
{
case 0xC0:
{
Model = "Rhino 20.0 kVA";
PotenciaNominal = 20000;
break;
}
case 0xC1:
{
Model = "Rhino 10.0 kVA";
PotenciaNominal = 10000;
break;
}
case 0xC2:
{
Model = "Rhino 6.0 kVA";
PotenciaNominal = 6000;
break;
}
case 0xC3:
{
Model = "Rhino 7.5 kVA";
PotenciaNominal = 7500;
break;
}
default:
{
Model = "Rhino unknown model";
PotenciaNominal = 0;
break;
}
}
/* manufacturer and model */
dstate_setinfo("ups.mfr", "%s", "Microsol");
dstate_setinfo("ups.model", "%s", Model);
/*
dstate_setinfo("input.transfer.low", "%03.1f", InDownLim); LimInfBattInv ?
dstate_setinfo("input.transfer.high", "%03.1f", InUpLim); LimSupBattInv ?
*/
dstate_addcmd("shutdown.stayoff"); // CMD_SHUT
/* there is no reserved words for CMD_INON and CMD_INOFF yet */
/* dstate_addcmd("input.on"); // CMD_INON = 1 */
/* dstate_addcmd("input.off"); // CMD_INOFF = 2 */
dstate_addcmd("load.on"); /* CMD_OUTON = 3 */
dstate_addcmd("load.off"); /* CMD_OUTOFF = 4 */
dstate_addcmd("bypass.start"); /* CMD_PASSON = 5 */
dstate_addcmd("bypass.stop"); /* CMD_PASSOFF = 6 */
printf("Detected %s on %s\n", dstate_getinfo("ups.model"), device_path);
}
static void getupdateinfo(void)
{
unsigned char temp[256];
int tam;
int hours, mins;
/* time update */
time_t *tmt;
struct tm *now;
tmt = ( time_t * ) malloc( sizeof( time_t ) );
time( tmt );
now = localtime( tmt );
hours = now->tm_hour;
mins = now->tm_min;
temp[0] = 0; /* flush temp buffer */
tam = ser_get_buf_len(upsfd, temp, pacsize, 3, 0);
CommReceive(temp, tam);
}
static int instcmd(const char *cmdname, const char *extra)
{
int ret = 0;
if (!strcasecmp(cmdname, "shutdown.stayoff"))
{
// shutdown now (one way)
/* send_command( CMD_SHUT ); */
sendshut();
return STAT_INSTCMD_HANDLED;
}
if (!strcasecmp(cmdname, "load.on"))
{
// liga Saida
ret = send_command( 3 );
if ( ret < 1 )
upslogx(LOG_ERR, "send_command 3 failed");
return STAT_INSTCMD_HANDLED;
}
if (!strcasecmp(cmdname, "load.off"))
{
// desliga Saida
ret = send_command( 4 );
if ( ret < 1 )
upslogx(LOG_ERR, "send_command 4 failed");
return STAT_INSTCMD_HANDLED;
}
if (!strcasecmp(cmdname, "bypass.start"))
{
// liga Bypass
ret = send_command( 5 );
if ( ret < 1 )
upslogx(LOG_ERR, "send_command 5 failed");
return STAT_INSTCMD_HANDLED;
}
if (!strcasecmp(cmdname, "bypass.stop"))
{
// desliga Bypass
ret = send_command( 6 );
if ( ret < 1 )
upslogx(LOG_ERR, "send_command 6 failed");
return STAT_INSTCMD_HANDLED;
}
upslogx(LOG_NOTICE, "instcmd: unknown command [%s]", cmdname);
return STAT_INSTCMD_UNKNOWN;
}
void upsdrv_initinfo(void)
{
getbaseinfo();
upsh.instcmd = instcmd;
}
void upsdrv_updateinfo(void)
{
getupdateinfo(); /* new package for updates */
dstate_setinfo("output.voltage", "%03.1f", OutVoltage);
dstate_setinfo("input.voltage", "%03.1f", InVoltage);
dstate_setinfo("battery.voltage", "%02.1f", BattVoltage);
/* output and bypass tests */
if( OutputOn )
dstate_setinfo("outlet.switchable", "%s", "yes");
else
dstate_setinfo("outlet.switchable", "%s", "no");
if( BypassOn )
dstate_setinfo("outlet.1.switchable", "%s", "yes");
else
dstate_setinfo("outlet.1.switchable", "%s", "no");
status_init();
if (!SourceFail )
status_set("OL"); /* on line */
else
status_set("OB"); /* on battery */
if (Autonomy < 5 )
status_set("LB"); /* low battery */
status_commit();
dstate_setinfo("ups.temperature", "%2.2f", Temperature);
dstate_setinfo("input.frequency", "%2.1f", InFreq);
dstate_dataok();
}
/* power down the attached load immediately */
void upsdrv_shutdown(void)
{
/* basic idea: find out line status and send appropriate command */
/* on line: send normal shutdown, ups will return by itself on utility */
/* on battery: send shutdown+return, ups will cycle and return soon */
if (!SourceFail) // on line
{
printf("On line, forcing shutdown command...\n");
send_command( CMD_SHUT );
}
else
{
printf("On battery, sending normal shutdown command...\n");
send_command( CMD_SHUT );
}
}
void upsdrv_help(void)
{
}
void upsdrv_makevartable(void)
{
addvar(VAR_VALUE, "battext", "Battery Extension (0-80)min");
}
void upsdrv_initups(void)
{
upsfd = ser_open(device_path);
ser_set_speed(upsfd, device_path, B19200);
/* dtr and rts setting */
ser_set_dtr(upsfd, 1);
ser_set_rts(upsfd, 0);
}
void upsdrv_cleanup(void)
{
ser_close(upsfd, device_path);
}