/* rhino.c - driver for Microsol Rhino UPS hardware Copyright (C) 2004 Silvino B. Magalhães 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 #include #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 ", 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 variables */ /* 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(void); static int AutonomyCalc( int ); static void CommReceive(const unsigned char*, int ); static void getbaseinfo(void); static void getupdateinfo(void); 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; const 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); }