1130 lines
25 KiB
C
1130 lines
25 KiB
C
/* solis.c - driver for Microsol Solis 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/10/10 - Version 0.10 - Initial release
|
|
2004/10/20 - Version 0.20 - add Battery information in driver
|
|
2004/10/26 - Version 0.30 - add commands and test shutdown
|
|
2004/10/30 - Version 0.40 - add model data structs
|
|
2005/06/30 - Version 0.41 - patch for solaris compability
|
|
2005/07/01 - Version 0.50 - add internal e external shutdown programming
|
|
2005/08/18 - Version 0.60 - save external shutdown programming to ups,
|
|
and support new cables for solis 3
|
|
|
|
Microsol contributed with UPS Solis 1.5 HS 1.5 KVA for my tests.
|
|
|
|
http://www.microsol.com.br
|
|
|
|
*/
|
|
|
|
#include <ctype.h>
|
|
#include <stdio.h>
|
|
|
|
#include "main.h"
|
|
#include "serial.h"
|
|
#include "solis.h"
|
|
#include "timehead.h"
|
|
|
|
#define DRIVER_NAME "Microsol Solis UPS driver"
|
|
#define DRIVER_VERSION "0.61"
|
|
|
|
/* driver description structure */
|
|
upsdrv_info_t upsdrv_info = {
|
|
DRIVER_NAME,
|
|
DRIVER_VERSION,
|
|
"Silvino B. Magalhães <sbm2yk@gmail.com>",
|
|
DRV_STABLE,
|
|
{ NULL }
|
|
};
|
|
|
|
#define false 0
|
|
#define true 1
|
|
#define ENDCHAR 13 /* replies end with CR */
|
|
/* solis commands */
|
|
#define CMD_UPSCONT 0xCC
|
|
#define CMD_SHUT 0xDD
|
|
#define CMD_SHUTRET 0xDE
|
|
#define CMD_EVENT 0xCE
|
|
#define CMD_DUMP 0xCD
|
|
|
|
/* comment on english language */
|
|
/* #define PORTUGUESE */
|
|
|
|
/* The following Portuguese strings are in UTF-8. */
|
|
#ifdef PORTUGUESE
|
|
#define M_UNKN "Modêlo solis desconhecido\n"
|
|
#define NO_SOLIS "Solis 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"
|
|
#define PRG_DAYS "Shutdown Programavel Dom Seg Ter Qua Qui Sex Sab\n"
|
|
#define PRG_ONON "Programação shutdown ativa externa\n"
|
|
#define PRG_ONOU "Programação shutdown ativa interna\n"
|
|
#define TIME_OFF "UPS Hora desligar %02d:%02d\n"
|
|
#define TIME_ON "UPS Hora ligar %02d:%02d\n"
|
|
#define PRG_ONOF "Programação shutdown desativada\n"
|
|
#define TODAY_DD "Desligamento hoje as %02d:%02d\n"
|
|
#define SHUT_NOW "Shutdown iminente!\n"
|
|
#else
|
|
#define M_UNKN "Unknown solis model\n"
|
|
#define NO_SOLIS "Solis 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"
|
|
#define PRG_DAYS "Programming Shutdown Sun Mon Tue Wed Thu Fri Sat\n"
|
|
#define PRG_ONON "External shutdown programming ative\n"
|
|
#define PRG_ONOU "Internal shutdown programming ative\n"
|
|
#define TIME_OFF "UPS Time power off %02d:%02d\n"
|
|
#define TIME_ON "UPS Time power on %02d:%02d\n"
|
|
#define PRG_ONOF "Shutdown programming not atived\n"
|
|
#define TODAY_DD "Shutdown today at %02d:%02d\n"
|
|
#define SHUT_NOW "Shutdown now!\n"
|
|
#endif
|
|
|
|
#define FMT_DAYS " %d %d %d %d %d %d %d\n"
|
|
|
|
/* convert standard days string to firmware format */
|
|
static char* convdays( char *cop )
|
|
{
|
|
|
|
char *stra;
|
|
char alt[8];
|
|
int i, ish, fim, iw;
|
|
iw = weekn;
|
|
if ( iw == 6)
|
|
ish = 0;
|
|
else
|
|
ish = 1 + iw;
|
|
|
|
fim = 7 - ish;
|
|
/* rotate left only 7 bits */
|
|
|
|
for(i=0; i < fim; i++) {
|
|
alt[i] = cop[i+ish];
|
|
}
|
|
|
|
if ( ish > 0 ) {
|
|
|
|
for(i=0; i < ish; i++) {
|
|
alt[i+fim] = cop[i];
|
|
}
|
|
}
|
|
|
|
alt[7] = 0; /* string terminator */
|
|
|
|
stra = strdup( alt );
|
|
return stra;
|
|
}
|
|
|
|
static int IsBinary(char ch )
|
|
{
|
|
if( ch == '1' || ch == '0' )
|
|
return 1;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
/* convert string to binary */
|
|
static int Binary( char *nome )
|
|
{
|
|
|
|
char ch, cc;
|
|
int cont=0, nint = 1, tobin=0;
|
|
int ex, nbin;
|
|
|
|
while( *nome && ( cont < 7 ) ) {
|
|
ch = *nome;
|
|
if( !(IsBinary( ch ) ) )
|
|
nint = 0;
|
|
else
|
|
{
|
|
if( ch == '1') {
|
|
cc = 1;
|
|
ex = (6 - cont);
|
|
nbin = cc<<ex;
|
|
tobin = tobin + nbin;
|
|
}
|
|
}
|
|
nome++;
|
|
cont++;
|
|
}
|
|
|
|
if( nint == 0 )
|
|
return nint;
|
|
else
|
|
return tobin;
|
|
}
|
|
|
|
/* revert firmware format to standard string binary days */
|
|
static unsigned char revertdays( unsigned char dweek )
|
|
{
|
|
|
|
char alt[8];
|
|
unsigned char keewd;
|
|
int i, iw;
|
|
iw = weekn;
|
|
|
|
/* it uses only 7 bits */
|
|
|
|
switch ( iw )
|
|
{
|
|
case 0: /* sunday */
|
|
{
|
|
alt[0] = ( ( ( dweek & 0x20 ) == 0x20 ) );
|
|
alt[1] = ( ( ( dweek & 0x10 ) == 0x10 ) );
|
|
alt[2] = ( ( ( dweek & 0x08 ) == 0x08 ) );
|
|
alt[3] = ( ( ( dweek & 0x04 ) == 0x04 ) );
|
|
alt[4] = ( ( ( dweek & 0x02 ) == 0x02 ) );
|
|
alt[5] = ( ( ( dweek & 0x01 ) == 0x01 ) );
|
|
alt[6] = ( ( ( dweek & 0x40 ) == 0x40 ) );
|
|
break;
|
|
}
|
|
case 1:
|
|
{
|
|
alt[0] = ( ( ( dweek & 0x10 ) == 0x10 ) );
|
|
alt[1] = ( ( ( dweek & 0x08 ) == 0x08 ) );
|
|
alt[2] = ( ( ( dweek & 0x04 ) == 0x04 ) );
|
|
alt[3] = ( ( ( dweek & 0x02 ) == 0x02 ) );
|
|
alt[4] = ( ( ( dweek & 0x01 ) == 0x01 ) );
|
|
alt[5] = ( ( ( dweek & 0x40 ) == 0x40 ) );
|
|
alt[6] = ( ( ( dweek & 0x20 ) == 0x20 ) );
|
|
break;
|
|
}
|
|
case 2:
|
|
{
|
|
alt[0] = ( ( ( dweek & 0x08 ) == 0x08 ) );
|
|
alt[1] = ( ( ( dweek & 0x04 ) == 0x04 ) );
|
|
alt[2] = ( ( ( dweek & 0x02 ) == 0x02 ) );
|
|
alt[3] = ( ( ( dweek & 0x01 ) == 0x01 ) );
|
|
alt[4] = ( ( ( dweek & 0x40 ) == 0x40 ) );
|
|
alt[5] = ( ( ( dweek & 0x20 ) == 0x20 ) );
|
|
alt[6] = ( ( ( dweek & 0x10 ) == 0x10 ) );
|
|
break;
|
|
}
|
|
case 3:
|
|
{
|
|
alt[0] = ( ( ( dweek & 0x04 ) == 0x04 ) );
|
|
alt[1] = ( ( ( dweek & 0x02 ) == 0x02 ) );
|
|
alt[2] = ( ( ( dweek & 0x01 ) == 0x01 ) );
|
|
alt[3] = ( ( ( dweek & 0x40 ) == 0x40 ) );
|
|
alt[4] = ( ( ( dweek & 0x20 ) == 0x20 ) );
|
|
alt[5] = ( ( ( dweek & 0x10 ) == 0x10 ) );
|
|
alt[6] = ( ( ( dweek & 0x08 ) == 0x08 ) );
|
|
break;
|
|
}
|
|
case 4:
|
|
{
|
|
alt[0] = ( ( ( dweek & 0x02 ) == 0x02 ) );
|
|
alt[1] = ( ( ( dweek & 0x01 ) == 0x01 ) );
|
|
alt[2] = ( ( ( dweek & 0x40 ) == 0x40 ) );
|
|
alt[3] = ( ( ( dweek & 0x20 ) == 0x20 ) );
|
|
alt[4] = ( ( ( dweek & 0x10 ) == 0x10 ) );
|
|
alt[5] = ( ( ( dweek & 0x08 ) == 0x08 ) );
|
|
alt[6] = ( ( ( dweek & 0x04 ) == 0x04 ) );
|
|
break;
|
|
}
|
|
case 5:
|
|
{
|
|
alt[0] = ( ( ( dweek & 0x01 ) == 0x01 ) );
|
|
alt[1] = ( ( ( dweek & 0x40 ) == 0x40 ) );
|
|
alt[2] = ( ( ( dweek & 0x20 ) == 0x20 ) );
|
|
alt[3] = ( ( ( dweek & 0x10 ) == 0x10 ) );
|
|
alt[4] = ( ( ( dweek & 0x08 ) == 0x08 ) );
|
|
alt[5] = ( ( ( dweek & 0x04 ) == 0x04 ) );
|
|
alt[6] = ( ( ( dweek & 0x02 ) == 0x02 ) );
|
|
break;
|
|
}
|
|
case 6: /* saturday */
|
|
{
|
|
alt[0] = ( ( ( dweek & 0x40 ) == 0x40 ) );
|
|
alt[1] = ( ( ( dweek & 0x20 ) == 0x20 ) );
|
|
alt[2] = ( ( ( dweek & 0x10 ) == 0x10 ) );
|
|
alt[3] = ( ( ( dweek & 0x08 ) == 0x08 ) );
|
|
alt[4] = ( ( ( dweek & 0x04 ) == 0x04 ) );
|
|
alt[5] = ( ( ( dweek & 0x02 ) == 0x02 ) );
|
|
alt[6] = ( ( ( dweek & 0x01 ) == 0x01 ) );
|
|
}
|
|
|
|
}
|
|
|
|
for(i=0; i < 7; i++) {
|
|
if( alt[i] == 0 )
|
|
alt[i] = '0';
|
|
if( alt[i] == 1 )
|
|
alt[i] = '1';
|
|
}
|
|
|
|
alt[7] = 0; /* string terminator */
|
|
keewd = Binary ( alt );
|
|
|
|
return keewd;
|
|
|
|
}
|
|
|
|
static int IsHour( char *strx, int qual )
|
|
{
|
|
|
|
int hora=0, min = 0;
|
|
|
|
if ((strlen(strx) != 5) || (sscanf(strx, "%d:%d", &hora, &min) != 2)) {
|
|
return -1;
|
|
}
|
|
|
|
if( qual ) {
|
|
dhour = hora;
|
|
dmin = min;
|
|
}
|
|
else
|
|
{
|
|
lhour = hora;
|
|
lmin = min;
|
|
}
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
static void sendshut( void )
|
|
{
|
|
|
|
int i;
|
|
|
|
for(i=0; i < 10; i++)
|
|
ser_send_char(upsfd, CMD_SHUT );
|
|
|
|
upslogx(LOG_NOTICE, "Ups shutdown command sent");
|
|
printf("Ups shutdown command sent\n");
|
|
|
|
}
|
|
|
|
/* save config ups */
|
|
static void confups( void )
|
|
{
|
|
|
|
int i, chks = 0;
|
|
|
|
ConfigPack[0] = 0xCF;
|
|
ConfigPack[1] = ihour;
|
|
ConfigPack[2] = imin;
|
|
ConfigPack[3] = isec;
|
|
ConfigPack[4] = lhour;
|
|
ConfigPack[5] = lmin;
|
|
ConfigPack[6] = dhour;
|
|
ConfigPack[7] = dmin;
|
|
ConfigPack[8] = weekn << 5;
|
|
ConfigPack[8] = ConfigPack[8] | dian;
|
|
ConfigPack[9] = mesn << 4;
|
|
ConfigPack[9] = ConfigPack[9] | ( anon - BASE_YEAR );
|
|
ConfigPack[10] = DaysOffWeek;
|
|
|
|
/* MSB zero */
|
|
ConfigPack[10] = ConfigPack[10] & (~(0x80));
|
|
|
|
for(i=0; i < 11; i++)
|
|
chks = chks + ConfigPack[i];
|
|
|
|
ConfigPack[11] = chks % 256;
|
|
|
|
for(i=0; i < 12; i++)
|
|
ser_send_char(upsfd, ConfigPack[i] );
|
|
|
|
}
|
|
|
|
/* print UPS internal variables */
|
|
static void prnInfo( void )
|
|
{
|
|
|
|
int sunday=0, monday=0, tuesday=0, wednesday=0, thursday=0, friday=0, saturday=0;
|
|
unsigned char dweek;
|
|
|
|
printf( UPS_DATE, Year, Month, Day );
|
|
printf( SYS_DATE, anon, mesn, dian, seman );
|
|
|
|
printf( UPS_TIME, ihour, imin, isec);
|
|
|
|
dweek = DaysStd;
|
|
|
|
if( prgups > 0 ) {
|
|
|
|
/* this is the string to binary standard */
|
|
sunday = ( ( dweek & 0x40 ) == 0x40 );
|
|
monday = ( ( dweek & 0x20 ) == 0x20 );
|
|
tuesday = ( ( dweek & 0x10 ) == 0x10 );
|
|
wednesday = ( ( dweek & 0x08 ) == 0x08 );
|
|
thursday = ( ( dweek & 0x04 ) == 0x04 );
|
|
friday = ( ( dweek & 0x02 ) == 0x02 );
|
|
saturday = ( ( dweek & 0x01 ) == 0x01 );
|
|
|
|
if( prgups == 3)
|
|
printf( PRG_ONOU );
|
|
else
|
|
printf( PRG_ONON );
|
|
printf( TIME_ON, lhour, lmin);
|
|
printf( TIME_OFF, dhour, dmin);
|
|
printf( PRG_DAYS );
|
|
printf( FMT_DAYS, sunday, monday, tuesday, wednesday, thursday, friday, saturday);
|
|
}
|
|
else
|
|
printf( PRG_ONOF );
|
|
|
|
}
|
|
|
|
/* is today shutdown day ? */
|
|
static int IsToday( unsigned char dweek, int nweek)
|
|
{
|
|
|
|
switch ( nweek )
|
|
{
|
|
case 0: /* sunday */
|
|
return ( ( ( dweek & 0x40 ) == 0x40 ) );
|
|
case 1:
|
|
return ( ( ( dweek & 0x20 ) == 0x20 ) );
|
|
case 2:
|
|
return ( ( ( dweek & 0x10 ) == 0x10 ) );
|
|
case 3:
|
|
return ( ( ( dweek & 0x08 ) == 0x08 ) );
|
|
case 4:
|
|
return ( ( ( dweek & 0x04 ) == 0x04 ) );
|
|
case 5:
|
|
return ( ( ( dweek & 0x02 ) == 0x02 ) );
|
|
case 6: /* saturday */
|
|
return ( ( ( dweek & 0x01 ) == 0x01 ) );
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
static void AutonomyCalc( int iauto ) /* all models */
|
|
{
|
|
|
|
int indice, indd, lim, min, max, inf, sup, indc, bx, ipo =0;
|
|
|
|
bx = bext[iauto];
|
|
indice = RecPack[3];
|
|
indd = indice - 139;
|
|
if( UtilPower > 20 )
|
|
ipo = ( UtilPower - 51 ) / 100;
|
|
|
|
indc = auton[iauto].maxi;
|
|
|
|
if( ipo > indc )
|
|
return;
|
|
|
|
min = auton[iauto].minc[ipo];
|
|
inf = min - 1;
|
|
max = auton[iauto].maxc[ipo];
|
|
lim = max - 139;
|
|
sup = max + 1;
|
|
|
|
if( UtilPower <= 20 ) {
|
|
Autonomy = 170;
|
|
maxauto = 170;
|
|
}
|
|
else
|
|
{
|
|
maxauto = auton[iauto].mm[ipo][lim];
|
|
if( indice > inf && indice < sup ) {
|
|
Autonomy = auton[iauto].mm[ipo][indd];
|
|
}
|
|
else
|
|
{
|
|
if( indice > max ) Autonomy = maxauto;
|
|
if( indice < min ) Autonomy = 0;
|
|
}
|
|
}
|
|
|
|
if( BattExtension > 0 && iauto < 4 )
|
|
Autonomy = ( Autonomy * ( BattExtension + bx ) * 1.0 / bx );
|
|
|
|
}
|
|
|
|
static void ScanReceivePack( void )
|
|
{
|
|
|
|
int aux, im, ov = 0;
|
|
|
|
/* model independent data */
|
|
|
|
Year = ( RecPack[ 19 ] & 0x0F ) + BASE_YEAR;
|
|
Month = ( RecPack[ 19 ] & 0xF0 ) >> 4;
|
|
Day = ( RecPack[ 18 ] & 0x1F );
|
|
DaysOnWeek = RecPack[17];
|
|
|
|
/* Days of week if in UPS shutdown programming mode */
|
|
if( prgups == 3 ) {
|
|
DaysStd = revertdays( DaysOnWeek );
|
|
|
|
/* time for programming UPS off */
|
|
dhour = RecPack[15];
|
|
dmin = RecPack[16];
|
|
/* time for programming UPS on */
|
|
lhour = RecPack[13];
|
|
lmin = RecPack[14];
|
|
}
|
|
|
|
/* UPS internal time */
|
|
ihour = RecPack[11];
|
|
imin = RecPack[10];
|
|
isec = RecPack[9];
|
|
|
|
if( ( ( 0x01 & RecPack[ 20 ] ) == 0x01 ) )
|
|
Out220 = 1;
|
|
CriticBatt = ( ( 0x04 & RecPack[ 20 ] ) == 0x04 );
|
|
InversorOn = ( ( 0x08 & RecPack[ 20 ] ) == 0x08 );
|
|
SuperHeat = ( ( 0x10 & RecPack[ 20 ] ) == 0x10 );
|
|
SourceFail = ( ( 0x20 & RecPack[ 20 ] ) == 0x20 );
|
|
OverCharge = ( ( 0x80 & RecPack[ 20 ] ) == 0x80 );
|
|
|
|
if( ( ( 0x40 & RecPack[ 20 ] ) == 0x40 ) )
|
|
InputValue = 1;
|
|
else
|
|
InputValue = 0;
|
|
Temperature = ( 0x7F & RecPack[ 4 ]);
|
|
if( ( ( 0x80 & RecPack[ 4 ] ) == 0x80 ) )
|
|
Temperature = Temperature - 128;
|
|
|
|
/* model dependent data */
|
|
|
|
im = inds[imodel];
|
|
ov = Out220;
|
|
|
|
if( RecPack[ 6 ] >= 194 )
|
|
InVoltage = RecPack[ 6 ] * ctab[imodel].m_involt194[0] + ctab[imodel].m_involt194[1];
|
|
else
|
|
InVoltage = RecPack[ 6 ] * ctab[imodel].m_involt193[0] + ctab[imodel].m_involt193[1];
|
|
|
|
BattVoltage = RecPack[ 3 ] * ctab[imodel].m_battvolt[0] + ctab[imodel].m_battvolt[1];
|
|
|
|
NominalPower = nompow[im];
|
|
if( SourceFail ) {
|
|
OutVoltage = RecPack[ 1 ] * ctab[imodel].m_outvolt_i[ov][0] + ctab[imodel].m_outvolt_i[ov][1];
|
|
OutCurrent = RecPack[ 5 ] * ctab[imodel].m_outcurr_i[ov][0] + ctab[imodel].m_outcurr_i[ov][1];
|
|
AppPower = ( RecPack[ 5 ] * RecPack[ 1 ] ) * ctab[imodel].m_appp_i[ov][0] + ctab[imodel].m_appp_i[ov][1];
|
|
UtilPower = ( RecPack[ 7 ] + RecPack[ 8 ] * 256 ) * ctab[imodel].m_utilp_i[ov][0] + ctab[imodel].m_utilp_i[ov][1];
|
|
InCurrent = 0;
|
|
}
|
|
else
|
|
{
|
|
OutVoltage = RecPack[ 1 ] * ctab[imodel].m_outvolt_s[ov][0] + ctab[imodel].m_outvolt_s[ov][1];
|
|
OutCurrent = RecPack[ 5 ] * ctab[imodel].m_outcurr_s[ov][0] + ctab[imodel].m_outcurr_s[ov][1];
|
|
AppPower = ( RecPack[ 5 ] * RecPack[ 1 ] ) * ctab[imodel].m_appp_s[ov][0] + ctab[imodel].m_appp_s[ov][1];
|
|
UtilPower = ( RecPack[ 7 ] + RecPack[ 8 ] * 256 ) * ctab[imodel].m_utilp_s[ov][0] + ctab[imodel].m_utilp_s[ov][1];
|
|
InCurrent = ( ctab[imodel].m_incurr[0] * 1.0 / BattVoltage ) - ( AppPower * 1.0 / ctab[imodel].m_incurr[1] )
|
|
+ OutCurrent *( OutVoltage * 1.0 / InVoltage );
|
|
}
|
|
|
|
aux = ( RecPack[ 21 ] + RecPack[ 22 ] * 256 );
|
|
if( aux > 0 )
|
|
InFreq = ctab[imodel].m_infreq * 1.0 / aux;
|
|
else
|
|
InFreq = 0;
|
|
|
|
/* input voltage offset */
|
|
if( InVoltage < InVolt_offset ) { /* all is equal 30 */
|
|
InFreq = 0;
|
|
InVoltage = 0;
|
|
InCurrent = 0;
|
|
}
|
|
|
|
/* app power offset */
|
|
if( AppPower < ctab[imodel].m_appp_offset ) {
|
|
AppPower = 0;
|
|
UtilPower = 0;
|
|
ChargePowerFactor = 0;
|
|
OutCurrent = 0;
|
|
}
|
|
|
|
if( im < 3 )
|
|
AutonomyCalc( im );
|
|
else
|
|
{
|
|
if( BattExtension == 80 )
|
|
AutonomyCalc( im + 1 );
|
|
else
|
|
AutonomyCalc( im );
|
|
}
|
|
|
|
/* model independent data */
|
|
|
|
batcharge = ( Autonomy / maxauto ) * 100.0;
|
|
upscharge = ( AppPower / NominalPower ) * 100.0;
|
|
|
|
if (batcharge > 100.0)
|
|
batcharge = 100.0;
|
|
|
|
OutFreq = 60;
|
|
if( !( InversorOn ) ) {
|
|
OutVoltage = 0;
|
|
OutFreq = 0;
|
|
}
|
|
|
|
if( ( !( SourceFail ) && InversorOn ) )
|
|
OutFreq = InFreq;
|
|
|
|
if( AppPower <= 0 ) /* charge pf */
|
|
ChargePowerFactor = 0;
|
|
else
|
|
{
|
|
if( AppPower == 0 )
|
|
ChargePowerFactor = 100;
|
|
else
|
|
ChargePowerFactor = (( UtilPower / AppPower) * 100 );
|
|
if( ChargePowerFactor > 100 )
|
|
ChargePowerFactor = 100;
|
|
}
|
|
|
|
if( SourceFail && SourceLast ) /* first time failure */
|
|
FailureFlag = true;
|
|
|
|
/* source return */
|
|
if( !( SourceFail ) && !( SourceLast ) ) {
|
|
SourceReturn = true;
|
|
ser_flush_in(upsfd,"",0); /* clean port */
|
|
}
|
|
|
|
if( !( SourceFail ) == SourceLast ) {
|
|
SourceReturn = false;
|
|
FailureFlag = false;
|
|
}
|
|
|
|
SourceLast = !( SourceFail );
|
|
|
|
/* Autonomy */
|
|
|
|
if( ( Autonomy < 5 ) )
|
|
LowBatt = true;
|
|
else
|
|
LowBatt = false;
|
|
|
|
UpsPowerFactor = 700;
|
|
|
|
/* input 110V or 220v */
|
|
if( ( InputValue == 0 ) ) {
|
|
InDownLim = 75;
|
|
InUpLim = 150;
|
|
NomInVolt = 110;
|
|
}
|
|
else
|
|
{
|
|
InDownLim = 150;
|
|
InUpLim = 300;
|
|
NomInVolt = 220;
|
|
}
|
|
|
|
/* output volage 220V or 110V */
|
|
if( Out220 ) {
|
|
OutDownLim = 190;
|
|
OutUpLim = 250;
|
|
NomOutVolt = 220;
|
|
}
|
|
else
|
|
{
|
|
OutDownLim = 100;
|
|
OutUpLim = 140;
|
|
NomOutVolt = 110;
|
|
}
|
|
|
|
if( SourceFail ) /* source status */
|
|
InputStatus = 2;
|
|
else
|
|
InputStatus = 1;
|
|
|
|
if( InversorOn ) /* output status */
|
|
OutputStatus = 1;
|
|
else
|
|
OutputStatus = 2;
|
|
|
|
if( OverCharge )
|
|
OutputStatus = 3;
|
|
|
|
if( CriticBatt ) /* battery status */
|
|
BattStatus = 4;
|
|
else
|
|
BattStatus = 1;
|
|
|
|
SourceEvents = 0;
|
|
|
|
if( FailureFlag )
|
|
SourceEvents = 1;
|
|
if( SourceReturn )
|
|
SourceEvents = 2;
|
|
|
|
/* verify Inversor */
|
|
if( Flag_inversor ) {
|
|
InversorOnLast = InversorOn;
|
|
Flag_inversor = false;
|
|
}
|
|
|
|
OutputEvents = 0;
|
|
if( InversorOn && !( InversorOnLast ) )
|
|
OutputEvents = 26;
|
|
if( InversorOnLast && !( InversorOn ) )
|
|
OutputEvents = 27;
|
|
InversorOnLast = InversorOn;
|
|
if( SuperHeat && !( SuperHeatLast ) )
|
|
OutputEvents = 12;
|
|
if( SuperHeatLast && !( SuperHeat ) )
|
|
OutputEvents = 13;
|
|
SuperHeatLast = SuperHeat;
|
|
if( OverCharge && !( OverChargeLast ) )
|
|
OutputEvents = 10;
|
|
if( OverChargeLast && !( OverCharge ) )
|
|
OutputEvents = 11;
|
|
OverChargeLast = OverCharge;
|
|
|
|
BattEvents = 0;
|
|
CriticBattLast = CriticBatt;
|
|
|
|
}
|
|
|
|
static void
|
|
CommReceive(const char *bufptr, int size)
|
|
{
|
|
|
|
int i, CheckSum, i_end;
|
|
|
|
if( ( size==25 ) )
|
|
Waiting = 0;
|
|
|
|
switch( Waiting )
|
|
{
|
|
/* normal package */
|
|
case 0:
|
|
{
|
|
if( size == 25 ) {
|
|
i_end = 25;
|
|
for( i = 0 ; i < i_end ; ++i ) {
|
|
RecPack[i] = *bufptr;
|
|
bufptr++;
|
|
}
|
|
|
|
/* CheckSum verify */
|
|
CheckSum = 0;
|
|
i_end = 23;
|
|
for( i = 0 ; i < i_end ; ++i )
|
|
CheckSum = RecPack[ i ] + CheckSum;
|
|
CheckSum = CheckSum % 256;
|
|
|
|
ser_flush_in(upsfd,"",0); /* clean port */
|
|
|
|
/* correct package */
|
|
if( ( (RecPack[0] & 0xF0) == 0xA0 )
|
|
&& ( RecPack[ 24 ] == 254 )
|
|
&& ( RecPack[ 23 ] == CheckSum ) ) {
|
|
|
|
if(!(detected)) {
|
|
SolisModel = (int) (RecPack[0] & 0x0F);
|
|
if( SolisModel < 13 )
|
|
imodel = SolisModel - 10; /* 10 = 0, 11 = 1 */
|
|
else
|
|
imodel = SolisModel - 11; /* 13 = 2, 14 = 3, 15 = 4 */
|
|
detected = true;
|
|
}
|
|
|
|
switch( SolisModel )
|
|
{
|
|
case 10:
|
|
case 11:
|
|
case 12:
|
|
case 13:
|
|
case 14:
|
|
case 15:
|
|
{
|
|
ScanReceivePack();
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
printf( M_UNKN );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case 1:
|
|
{
|
|
/* dumping package nothing to do yet */
|
|
Waiting = 0;
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
Waiting =0;
|
|
|
|
}
|
|
|
|
static void getbaseinfo(void)
|
|
{
|
|
|
|
unsigned char temp[256];
|
|
#ifdef PORTUGUESE
|
|
char diassemana[7][4]={"Dom", "Seg", "Ter", "Qua", "Qui", "Sex", "Sab"};
|
|
#else
|
|
char DaysOfWeek[7][4]={"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
|
|
#endif
|
|
char mycmd[8];
|
|
char *str1, *str2, *str3, *str4, *strx;
|
|
unsigned char Pacote[25];
|
|
int i, i1=0, i2=0, j=0, tam, tpac=25;
|
|
|
|
time_t *tmt;
|
|
struct tm *now;
|
|
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;
|
|
ihour = now->tm_hour;
|
|
imin = now->tm_min;
|
|
isec = now->tm_sec;
|
|
weekn = now->tm_wday;
|
|
|
|
#ifdef PORTUGUESE
|
|
strcpy( seman, diassemana[weekn] );
|
|
#else
|
|
strcpy( seman, DaysOfWeek[weekn] );
|
|
#endif
|
|
|
|
if( testvar("battext"))
|
|
BattExtension = atoi(getval("battext"));
|
|
|
|
if( testvar("prgshut"))
|
|
prgups = atoi(getval("prgshut"));
|
|
|
|
if( prgups > 0 && prgups < 3 ) {
|
|
if( testvar("daysweek") ) {
|
|
strx = getval("daysweek");
|
|
str1 = convdays( strx );
|
|
DaysOnWeek = Binary( str1 );
|
|
}
|
|
|
|
if( testvar("daysoff") ) {
|
|
strx = getval("daysoff");
|
|
str2 = convdays( strx );
|
|
DaysStd = Binary ( strx );
|
|
DaysOffWeek = Binary( str2 );
|
|
}
|
|
|
|
if( testvar("houron") ) {
|
|
str3 = getval("houron");
|
|
i1 = IsHour( str3, 0 );
|
|
}
|
|
|
|
if( testvar("houroff") ) {
|
|
str4 = getval("houroff");
|
|
i2 = IsHour( str4, 1 );
|
|
}
|
|
|
|
if( i1 == 1 && i2 == 1 && ( DaysOnWeek > 0 ) ) {
|
|
isprogram = 1; /* prgups == 1 ou 2 */
|
|
|
|
if( prgups == 2 )
|
|
confups(); /* save ups config */
|
|
}
|
|
else
|
|
{
|
|
if( (i2 == 1) && ( DaysOffWeek > 0 ) ) {
|
|
isprogram = 1;
|
|
if( DaysOnWeek != DaysOffWeek )
|
|
DaysOnWeek = DaysOffWeek;
|
|
}
|
|
}
|
|
|
|
} /* end prgups 1 - 2 */
|
|
|
|
/* dummy read attempt to sync - throw it out */
|
|
snprintf(mycmd, sizeof(mycmd), "%c%c",CMD_UPSCONT, ENDCHAR);
|
|
ser_send(upsfd, "%s", mycmd);
|
|
|
|
/* trying detect solis model */
|
|
while ( ( !detected ) && ( j < 20 ) ) {
|
|
temp[0] = 0; /* flush temp buffer */
|
|
tam = ser_get_buf_len(upsfd, temp, tpac, 3, 0);
|
|
if( tam == 25 ) {
|
|
for( i = 0 ; i < tam ; i++ ) {
|
|
Pacote[i] = temp[i];
|
|
}
|
|
}
|
|
|
|
j++;
|
|
if( tam == 25)
|
|
CommReceive((char *)Pacote, tam);
|
|
else
|
|
CommReceive((char *)temp, tam);
|
|
} /* while end */
|
|
|
|
if( (!detected) ) {
|
|
fatalx(EXIT_FAILURE, NO_SOLIS );
|
|
}
|
|
|
|
switch( SolisModel )
|
|
{
|
|
case 10:
|
|
case 11:
|
|
case 12:
|
|
{
|
|
strcpy(Model, "Solis 1.0");
|
|
break;
|
|
}
|
|
case 13:
|
|
{
|
|
strcpy(Model, "Solis 1.5");
|
|
break;
|
|
}
|
|
case 14:
|
|
{
|
|
strcpy(Model, "Solis 2.0");
|
|
break;
|
|
}
|
|
case 15:
|
|
{
|
|
strcpy(Model, "Solis 3.0");
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* if( isprogram ) */
|
|
if( prgups == 1 ) {
|
|
hourshut = dhour;
|
|
minshut = dmin;
|
|
}
|
|
else
|
|
{
|
|
if( prgups == 2 || prgups == 3 ) { /* broadcast before firmware shutdown */
|
|
if( dmin < 5 ) {
|
|
if( dhour > 1 )
|
|
hourshut = dhour - 1;
|
|
else
|
|
hourshut = 23;
|
|
minshut = 60 - ( 5 - dmin );
|
|
}
|
|
else
|
|
{
|
|
hourshut = dhour;
|
|
minshut = dmin - 5;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* manufacturer */
|
|
dstate_setinfo("ups.mfr", "%s", "Microsol");
|
|
|
|
dstate_setinfo("ups.model", "%s", Model);
|
|
dstate_setinfo("input.transfer.low", "%03.1f", InDownLim);
|
|
dstate_setinfo("input.transfer.high", "%03.1f", InUpLim);
|
|
|
|
dstate_addcmd("shutdown.return"); /* CMD_SHUTRET */
|
|
dstate_addcmd("shutdown.stayoff"); /* CMD_SHUT */
|
|
|
|
printf("Detected %s on %s\n", dstate_getinfo("ups.model"), device_path);
|
|
|
|
prnInfo();
|
|
|
|
|
|
}
|
|
|
|
static void getupdateinfo(void)
|
|
{
|
|
unsigned char temp[256];
|
|
int tam, isday, hourn, minn;
|
|
|
|
/* time update and programable shutdown block */
|
|
time_t *tmt;
|
|
struct tm *now;
|
|
tmt = ( time_t * ) malloc( sizeof( time_t ) );
|
|
time( tmt );
|
|
now = localtime( tmt );
|
|
hourn = now->tm_hour;
|
|
minn = now->tm_min;
|
|
weekn = now->tm_wday;
|
|
|
|
if( isprogram || prgups == 3 ) {
|
|
if( isprogram )
|
|
isday = IsToday( DaysStd, weekn );
|
|
else
|
|
isday = IsToday( DaysStd, weekn );
|
|
|
|
if( isday )
|
|
printf( TODAY_DD, hourshut, minshut );
|
|
|
|
if( ( hourn == hourshut ) && ( minn >= minshut ) && isday ) {
|
|
printf( SHUT_NOW );
|
|
progshut = 1;
|
|
}
|
|
}
|
|
|
|
/* programable shutdown end block */
|
|
|
|
pacsize = 25;
|
|
|
|
/* get update package */
|
|
temp[0] = 0; /* flush temp buffer */
|
|
tam = ser_get_buf_len(upsfd, temp, pacsize, 3, 0);
|
|
|
|
CommReceive((char *)temp, tam);
|
|
|
|
}
|
|
|
|
static int instcmd(const char *cmdname, const char *extra)
|
|
{
|
|
|
|
if (!strcasecmp(cmdname, "shutdown.return")) {
|
|
/* shutdown and restart */
|
|
ser_send_char(upsfd, CMD_SHUTRET); /* 0xDE */
|
|
/* ser_send_char(upsfd, ENDCHAR); */
|
|
return STAT_INSTCMD_HANDLED;
|
|
}
|
|
|
|
if (!strcasecmp(cmdname, "shutdown.stayoff"))
|
|
{
|
|
/* shutdown now (one way) */
|
|
ser_send_char(upsfd, CMD_SHUT); /* 0xDD */
|
|
/* ser_send_char(upsfd, ENDCHAR); */
|
|
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);
|
|
dstate_setinfo("battery.charge", "%03.1f", batcharge);
|
|
|
|
status_init();
|
|
|
|
if (!SourceFail )
|
|
status_set("OL"); /* on line */
|
|
else
|
|
status_set("OB"); /* on battery */
|
|
|
|
if (Autonomy < 5 )
|
|
status_set("LB"); /* low battery */
|
|
|
|
if( progshut ) { /* software programable shutdown immediately */
|
|
if( prgups == 2 )
|
|
sendshut(); /* Ups shutdown in 4-5 minutes -- redundant Ups shutdown */
|
|
|
|
status_set("LB"); /* no low battery but is a force shutdown */
|
|
}
|
|
|
|
status_commit();
|
|
|
|
dstate_setinfo("ups.temperature", "%2.2f", Temperature);
|
|
dstate_setinfo("input.frequency", "%2.1f", InFreq);
|
|
dstate_setinfo("ups.load", "%03.1f", upscharge);
|
|
|
|
dstate_dataok();
|
|
|
|
}
|
|
|
|
/* power down the attached load immediately */
|
|
void upsdrv_shutdown(void)
|
|
{
|
|
|
|
/* basic idea: find out line status and send appropriate command */
|
|
/* on battery: send normal shutdown, ups will return by itself on utility */
|
|
/* on line: send shutdown+return, ups will cycle and return soon */
|
|
|
|
if (!SourceFail) { /* on line */
|
|
|
|
printf("On line, sending shutdown+return command...\n");
|
|
ser_send_char(upsfd, CMD_SHUTRET );
|
|
}
|
|
else
|
|
{
|
|
printf("On battery, sending normal shutdown command...\n");
|
|
ser_send_char(upsfd, CMD_SHUT);
|
|
}
|
|
|
|
}
|
|
|
|
void upsdrv_help(void)
|
|
{
|
|
|
|
printf("\nSolis options\n");
|
|
printf(" Battery Extension in AH\n");
|
|
printf(" battext = 80\n");
|
|
printf(" Programable UPS power on/off\n");
|
|
printf(" prgshut = 0 (default, no software programable shutdown)\n");
|
|
printf(" prgshut = 1 (software programable shutdown without UPS power off)\n");
|
|
printf(" prgshut = 2 (software programable shutdown with UPS power off)\n");
|
|
printf(" prgshut = 3 (activate UPS programable power on/off)\n");
|
|
printf(" Otherwise uses:\n");
|
|
printf(" daysweek = 1010101 ( power on days )\n");
|
|
printf(" daysoff = 1010101 ( power off days )\n");
|
|
printf(" where each digit is a day from sun...sat with 0 = off and 1 = on\n");
|
|
printf(" houron = hh:mm hh = hour 0-23 mm = minute 0-59 separated with :\n");
|
|
printf(" houroff = hh:mm hh = hour 0-23 mm = minute 0-59 separated with :\n");
|
|
printf(" where houron is power-on hour and houroff is shutdown and power-off hour\n");
|
|
printf(" Uses daysweek and houron to programing and save UPS power on/off\n");
|
|
printf(" These are valid only if prgshut = 2 or 3\n");
|
|
|
|
}
|
|
|
|
void upsdrv_makevartable(void)
|
|
{
|
|
|
|
addvar(VAR_VALUE, "battext", "Battery Extension (0-80)min");
|
|
addvar(VAR_VALUE, "prgshut", "Programable power off (0-3)");
|
|
addvar(VAR_VALUE, "daysweek", "Days of week UPS power of/off");
|
|
addvar(VAR_VALUE, "daysoff", "Days of week Driver shutdown");
|
|
addvar(VAR_VALUE, "houron", "Power on hour (hh:mm)");
|
|
addvar(VAR_VALUE, "houroff", "Power off hour (hh:mm)");
|
|
|
|
}
|
|
|
|
void upsdrv_initups(void)
|
|
{
|
|
upsfd = ser_open(device_path);
|
|
ser_set_speed(upsfd, device_path, B9600);
|
|
|
|
ser_set_dtr(upsfd, 1);
|
|
ser_set_rts(upsfd, 0);
|
|
}
|
|
|
|
void upsdrv_cleanup(void)
|
|
{
|
|
ser_close(upsfd, device_path);
|
|
}
|