1
/* solis.c - driver for Microsol Solis UPS hardware
3
Copyright (C) 2004 Silvino B. MagalhĆ£es <sbm2yk@gmail.com>
5
This program is free software; you can redistribute it and/or modify
6
it under the terms of the GNU General Public License as published by
7
the Free Software Foundation; either version 2 of the License, or
8
(at your option) any later version.
10
This program is distributed in the hope that it will be useful,
11
but WITHOUT ANY WARRANTY; without even the implied warranty of
12
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
GNU General Public License for more details.
15
You should have received a copy of the GNU General Public License
16
along with this program; if not, write to the Free Software
17
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19
2004/10/10 - Version 0.10 - Initial release
20
2004/10/20 - Version 0.20 - add Battery information in driver
21
2004/10/26 - Version 0.30 - add commands and test shutdown
22
2004/10/30 - Version 0.40 - add model data structs
24
Microsol contributed with UPS Solis 1.2 HS 1.2 KVA for my tests.
26
http://www.microsol.com.br
30
#define DRV_VERSION "0.40"
40
#define ENDCHAR 13 /* replies end with CR */
42
#define CMD_UPSCONT 0xCC
44
#define CMD_SHUTRET 0xDE
45
#define CMD_EVENT 0xCE
48
/* comment out on english language */
52
#define M_UNKN "ModĆŖlo solis desconhecido\n"
53
#define NO_SOLIS "Solis nĆ£o detectado! abortando ...\n"
54
#define UPS_DATE "Data no UPS %4d/%02d/%02d\n"
55
#define SYS_DATE "Data do Sistema %4d/%02d/%02d dia da semana %s\n"
56
#define ERR_PACK "Pacote errado\n"
57
#define NO_EVENT "NĆ£o hĆ” eventos\n"
58
#define UPS_TIME "Hora interna UPS %0d:%02d:%02d\n"
59
#define PRG_DAYS "Shutdown Programavel Dom Seg Ter Qua Qui Sex Sab\n"
60
#define PRG_ONON "ProgramaĆ§Ć£o shutdown ativa\n"
61
#define TIME_OFF "UPS Hora desligar %02d:%02d\n"
62
#define TIME_ON "UPS Hora ligar %02d:%02d\n"
63
#define PRG_ONOF "ProgramaĆ§Ć£o shutdown desativada\n"
65
#define M_UNKN "Unknown solis model\n"
66
#define NO_SOLIS "Solis not detected! aborting ...\n"
67
#define UPS_DATE "UPS Date %4d/%02d/%02d\n"
68
#define SYS_DATE "System Date %4d/%02d/%02d day of week %s\n"
69
#define ERR_PACK "Wrong package\n"
70
#define NO_EVENT "No events\n"
71
#define UPS_TIME "UPS internal Time %0d:%02d:%02d\n"
72
#define PRG_DAYS "Programming Shutdown Sun Mon Tue Wed Thu fri Sat\n"
73
#define PRG_ONON "Shutdown programming ative\n"
74
#define TIME_OFF "UPS Time power off %02d:%02d\n"
75
#define TIME_ON "UPS Time power on %02d:%02d\n"
76
#define PRG_ONOF "Shutdown programming not atived\n"
79
#define FMT_DAYS " %d %d %d %d %d %d %d\n"
81
/* print UPS internal variables */
82
static void prnInfo( void )
85
int sun=0, mon=0, tue=0, wed=0, thu=0, fri=0, sat=0;
87
printf( UPS_DATE, Year, Month, Day );
88
printf( SYS_DATE, anon, mesn, dian, seman );
90
printf( UPS_TIME, ihour, imin, isec);
92
tue = ( ( DaysOnWeek & 0x40 ) == 0x40 );
93
wed = ( ( DaysOnWeek & 0x20 ) == 0x20 );
94
thu = ( ( DaysOnWeek & 0x10 ) == 0x10 );
95
fri = ( ( DaysOnWeek & 0x08 ) == 0x08 );
96
sat = ( ( DaysOnWeek & 0x04 ) == 0x04 );
97
sun = ( ( DaysOnWeek & 0x02 ) == 0x02 );
98
mon = ( ( DaysOnWeek & 0x01 ) == 0x01 );
102
printf( TIME_ON, lhour, lmin);
103
printf( TIME_OFF, dhour, dmin);
105
printf( FMT_DAYS, sun, mon, tue, wed, thu, fri, sat);
112
/* is today shutdown day */
113
static int IsToday( unsigned char dweek, int nweek)
119
return ( ( ( dweek & 0x02 ) == 0x02 ) );
121
return ( ( ( dweek & 0x01 ) == 0x01 ) );
123
return ( ( ( dweek & 0x40 ) == 0x40 ) );
125
return ( ( ( dweek & 0x20 ) == 0x20 ) );
127
return ( ( ( dweek & 0x10 ) == 0x10 ) );
129
return ( ( ( dweek & 0x08 ) == 0x08 ) );
131
return ( ( ( dweek & 0x04 ) == 0x04 ) );
136
static void AutonomyCalc( int iauto ) /* all models */
138
int indice, indd, lim, min, max, inf, sup, indc, bx, ipo =0;
144
ipo = ( UtilPower - 51 ) / 100;
146
indc = auton[iauto].maxi;
151
min = auton[iauto].minc[ipo];
153
max = auton[iauto].maxc[ipo];
157
if( UtilPower <= 20 )
164
maxauto = auton[iauto].mm[ipo][lim];
165
if( indice > inf && indice < sup )
166
Autonomy = auton[iauto].mm[ipo][indd];
169
if( indice > max ) Autonomy = maxauto;
170
if( indice < min ) Autonomy = 0;
174
if( BattExtension > 0 && iauto < 4 )
175
Autonomy = ( Autonomy * ( BattExtension + bx ) * 1.0 / bx );
179
static void ScanReceivePack( void )
183
/* model independent data */
185
Year = ( RecPack[ 19 ] & 0x0F ) + BASE_YEAR;
186
Month = ( RecPack[ 19 ] & 0xF0 ) >> 4;
187
Day = ( RecPack[ 18 ] & 0x1F );
189
/* Days of week in UPS shutdown programming */
190
DaysOnWeek = RecPack[ 17 ];
192
/* time for programming UPS off */
195
/* time for programming UPS on */
198
/* UPS internal time */
203
if( ( ( 0x01 & RecPack[ 20 ] ) == 0x01 ) )
205
CriticBatt = ( ( 0x04 & RecPack[ 20 ] ) == 0x04 );
206
InversorOn = ( ( 0x08 & RecPack[ 20 ] ) == 0x08 );
207
SuperHeat = ( ( 0x10 & RecPack[ 20 ] ) == 0x10 );
208
SourceFail = ( ( 0x20 & RecPack[ 20 ] ) == 0x20 );
209
OverCharge = ( ( 0x80 & RecPack[ 20 ] ) == 0x80 );
211
if( ( ( 0x40 & RecPack[ 20 ] ) == 0x40 ) )
215
Temperature = ( 0x7F & RecPack[ 4 ]);
216
if( ( ( 0x80 & RecPack[ 4 ] ) == 0x80 ) )
217
Temperature = Temperature - 128;
219
/* model dependent data */
224
if( RecPack[ 6 ] >= 194 )
225
InVoltage = RecPack[ 6 ] * ctab[imodel].m_involt194[0] + ctab[imodel].m_involt194[1];
227
InVoltage = RecPack[ 6 ] * ctab[imodel].m_involt193[0] + ctab[imodel].m_involt193[1];
229
BattVoltage = RecPack[ 3 ] * ctab[imodel].m_battvolt[0] + ctab[imodel].m_battvolt[1];
231
NominalPower = nompow[im];
233
OutVoltage = RecPack[ 1 ] * ctab[imodel].m_outvolt_i[ov][0] + ctab[imodel].m_outvolt_i[ov][1];
234
OutCurrent = RecPack[ 5 ] * ctab[imodel].m_outcurr_i[ov][0] + ctab[imodel].m_outcurr_i[ov][1];
235
AppPower = ( RecPack[ 5 ] * RecPack[ 1 ] ) * ctab[imodel].m_appp_i[ov][0] + ctab[imodel].m_appp_i[ov][1];
236
UtilPower = ( RecPack[ 7 ] + RecPack[ 8 ] * 256 ) * ctab[imodel].m_utilp_i[ov][0] + ctab[imodel].m_utilp_i[ov][1];
240
OutVoltage = RecPack[ 1 ] * ctab[imodel].m_outvolt_s[ov][0] + ctab[imodel].m_outvolt_s[ov][1];
241
OutCurrent = RecPack[ 5 ] * ctab[imodel].m_outcurr_s[ov][0] + ctab[imodel].m_outcurr_s[ov][1];
242
AppPower = ( RecPack[ 5 ] * RecPack[ 1 ] ) * ctab[imodel].m_appp_s[ov][0] + ctab[imodel].m_appp_s[ov][1];
243
UtilPower = ( RecPack[ 7 ] + RecPack[ 8 ] * 256 ) * ctab[imodel].m_utilp_s[ov][0] + ctab[imodel].m_utilp_s[ov][1];
244
InCurrent = ( ctab[imodel].m_incurr[0] * 1.0 / BattVoltage ) - ( AppPower * 1.0 / ctab[imodel].m_incurr[1] )
245
+ OutCurrent *( OutVoltage * 1.0 / InVoltage );
248
aux = ( RecPack[ 21 ] + RecPack[ 22 ] * 256 );
250
InFreq = ctab[imodel].m_infreq * 1.0 / aux;
254
/* input voltage offset */
255
if( InVoltage < InVolt_offset ) { /* all is equal 30 */
261
/* app power offset */
262
if( AppPower < ctab[imodel].m_appp_offset ) {
265
ChargePowerFactor = 0;
272
if( BattExtension == 80 )
273
AutonomyCalc( im + 1 );
278
/* model independent data */
280
batcharge = ( Autonomy / maxauto ) * 100.0;
281
upscharge = ( AppPower / NominalPower ) * 100.0;
283
if (batcharge > 100.0)
287
if( !( InversorOn ) ) {
292
if( ( !( SourceFail ) && InversorOn ) )
295
if( AppPower <= 0 ) /* charge pf */
296
ChargePowerFactor = 0;
299
ChargePowerFactor = 100;
301
ChargePowerFactor = (( UtilPower / AppPower) * 100 );
302
if( ChargePowerFactor > 100 )
303
ChargePowerFactor = 100;
306
if( SourceFail && SourceLast ) /* first time failure */
310
if( !( SourceFail ) && !( SourceLast ) ) {
312
ser_flush_in(upsfd,"",0); /* clean port */
315
if( !( SourceFail ) == SourceLast ) {
316
SourceReturn = false;
320
SourceLast = !( SourceFail );
324
if( ( Autonomy < 5 ) )
329
UpsPowerFactor = 700;
331
/* input 110V or 220v */
332
if( ( InputValue == 0 ) ) {
343
/* output volage 220V or 110V */
355
if( SourceFail ) /* source status */
360
if( InversorOn ) /* output status */
368
if( CriticBatt ) /* battery status */
380
/* verify Inversor */
381
if( Flag_inversor ) {
382
InversorOnLast = InversorOn;
383
Flag_inversor = false;
388
if( InversorOn && !( InversorOnLast ) )
390
if( InversorOnLast && !( InversorOn ) )
392
InversorOnLast = InversorOn;
393
if( SuperHeat && !( SuperHeatLast ) )
395
if( SuperHeatLast && !( SuperHeat ) )
397
SuperHeatLast = SuperHeat;
398
if( OverCharge && !( OverChargeLast ) )
400
if( OverChargeLast && !( OverCharge ) )
402
OverChargeLast = OverCharge;
405
CriticBattLast = CriticBatt;
410
CommReceive(const char *bufptr, int size)
412
int i, CheckSum, i_end;
423
for( i = 0 ; i < i_end ; ++i ) {
424
RecPack[i] = *bufptr;
428
/* CheckSum verify */
431
for( i = 0 ; i < i_end ; ++i )
432
CheckSum = RecPack[ i ] + CheckSum;
433
CheckSum = CheckSum % 256;
435
ser_flush_in(upsfd,"",0); /* clean port */
437
/* correct package */
438
if( ( (RecPack[0] & 0xF0) == 0xA0 )
439
&& ( RecPack[ 24 ] == 254 )
440
&& ( RecPack[ 23 ] == CheckSum ) ) {
443
SolisModel = (int) (RecPack[0] & 0x0F);
444
if( SolisModel < 13 )
445
imodel = SolisModel - 10; /* 10 = 0, 11 = 1 */
447
imodel = SolisModel - 11; /* 13 = 2, 14 = 3, 15 = 4 */
478
/* dumping package nothing to do yet */
489
static void getbaseinfo(void)
491
unsigned char temp[256];
493
char diassemana[7][4]={"Dom", "Seg", "Ter", "Qua", "Qui", "Sex", "Sab"};
495
char DaysOfWeek[7][4]={"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
497
char mycmd[8]; // , ch;
498
unsigned char Pacote[25];
499
int i, j=0, tam, tpac=25;
503
tmt = ( time_t * ) malloc( sizeof( time_t ) );
505
now = localtime( tmt );
507
mesn = now->tm_mon+1;
508
anon = now->tm_year+1900;
509
weekn = now->tm_wday;
512
strcpy( seman, diassemana[weekn] );
514
strcpy( seman, DaysOfWeek[weekn] );
517
if( testvar("battext"))
518
BattExtension = atoi(getval("battext"));
520
if( testvar("prgshut"))
521
isprogram = atoi(getval("prgshut"));
523
/* dummy read attempt to sync - throw it out */
524
sprintf(mycmd,"%c%c",CMD_UPSCONT, ENDCHAR);
525
ser_send(upsfd, mycmd);
526
/* ser_send_char(upsfd, CMD_UPSCONT); // send the character */
528
/* trying detect solis model */
529
while ( ( !detected ) && ( j < 20 ) ) {
530
temp[0] = 0; // flush temp buffer
531
tam = ser_get_buf_len(upsfd, temp, tpac, 3, 0);
533
for( i = 0 ; i < tam ; i++ )
539
CommReceive(Pacote, tam);
541
CommReceive(temp, tam);
556
strcpy(Model, "Solis 1.0");
561
strcpy(Model, "Solis 1.5");
566
strcpy(Model, "Solis 2.0");
571
strcpy(Model, "Solis 3.0");
581
hourshut = dhour - 1;
584
minshut = 60 - ( 5 -dmin );
591
dstate_setinfo("ups.mfr", "%s", "Microsol");
593
dstate_setinfo("ups.model", "%s", Model);
594
dstate_setinfo("input.transfer.low", "%03.1f", InDownLim);
595
dstate_setinfo("input.transfer.high", "%03.1f", InUpLim);
597
dstate_addcmd("shutdown.return"); /* CMD_SHUTRET */
598
dstate_addcmd("shutdown.stayoff"); /* CMD_SHUT */
600
printf("Detected %s on %s\n", dstate_getinfo("ups.model"), device_path);
606
static void getupdateinfo(void)
608
unsigned char temp[256];
609
int tam, isday, hourn, minn;
611
/* time update and programable shutdown block */
614
tmt = ( time_t * ) malloc( sizeof( time_t ) );
616
now = localtime( tmt );
617
hourn = now->tm_hour;
619
weekn = now->tm_wday;
622
isday = IsToday( DaysOnWeek, weekn );
623
if( ( dhour == hourshut ) && ( minshut >= dmin ) && isday )
627
/* programable shutdown end block */
631
/* get update package */
632
temp[0] = 0; /* flush temp buffer */
633
tam = ser_get_buf_len(upsfd, temp, pacsize, 3, 0);
635
CommReceive(temp, tam);
639
static int instcmd(const char *cmdname, const char *extra)
642
if (!strcasecmp(cmdname, "shutdown.return")) {
643
// shutdown and restart
644
ser_send_char(upsfd, CMD_SHUTRET); // 0xDE
645
// ser_send_char(upsfd, ENDCHAR);
646
return STAT_INSTCMD_HANDLED;
649
if (!strcasecmp(cmdname, "shutdown.stayoff")) {
650
// shutdown now (one way)
651
ser_send_char(upsfd, CMD_SHUT); // 0xDD
652
// ser_send_char(upsfd, ENDCHAR);
653
return STAT_INSTCMD_HANDLED;
656
upslogx(LOG_NOTICE, "instcmd: unknown command [%s]", cmdname);
657
return STAT_INSTCMD_UNKNOWN;
661
void upsdrv_initinfo(void)
665
upsh.instcmd = instcmd;
666
dstate_setinfo("driver.version.internal", "%s", DRV_VERSION);
669
void upsdrv_updateinfo(void)
672
getupdateinfo(); /* new package for updates */
674
dstate_setinfo("output.voltage", "%03.1f", OutVoltage);
675
dstate_setinfo("input.voltage", "%03.1f", InVoltage);
676
dstate_setinfo("battery.voltage", "%02.1f", BattVoltage);
677
dstate_setinfo("battery.charge", "%03.1f", batcharge);
682
status_set("OL"); /* on line */
684
status_set("OB"); /* on battery */
687
status_set("LB"); /* low battery */
690
status_set("LB"); /* low battery but is a force shutdown */
694
dstate_setinfo("ups.temperature", "%2.2f", Temperature);
695
dstate_setinfo("input.frequency", "%2.1f", InFreq);
696
dstate_setinfo("ups.load", "%03.1f", upscharge);
702
/* power down the attached load immediately */
703
void upsdrv_shutdown(void)
706
/* basic idea: find out line status and send appropriate command */
707
/* on battery: send normal shutdown, ups will return by itself on utility */
708
/* on line: send shutdown+return, ups will cycle and return soon */
710
if (!SourceFail) { /* on line */
712
printf("On line, sending shutdown+return command...\n");
713
ser_send_char(upsfd, CMD_SHUTRET );
714
/* ser_send_char(upsfd, ENDCHAR); */
717
printf("On battery, sending normal shutdown command...\n");
718
ser_send_char(upsfd, CMD_SHUT);
719
/* ser_send_char(upsfd, ENDCHAR); */
724
void upsdrv_help(void)
727
printf("\nSolis options\n");
728
printf(" Battery Extension\n");
729
printf(" battext = 80\n");
730
printf(" Programable power off\n");
731
printf(" prgshut = 1 (activate programable power off)\n");
732
printf(" Uses Solis Monitor for setting date-time power off\n");
736
void upsdrv_makevartable(void)
739
addvar(VAR_VALUE, "battext", "Battery Extension (0-80)min");
740
addvar(VAR_VALUE, "prgshut", "Programable power off (0-1)");
744
void upsdrv_banner(void)
746
printf("Network UPS Tools - Microsol Solis UPS driver %s (%s)\n",
747
DRV_VERSION, UPS_VERSION);
748
printf("by Silvino Magalhaes for Microsol - sbm2yk@gmail.com\n\n");
751
void upsdrv_initups(void)
753
upsfd = ser_open(device_path);
754
ser_set_speed(upsfd, device_path, B9600);
757
void upsdrv_cleanup(void)
759
ser_close(upsfd, device_path);