/* solis.c - driver for Microsol Solis 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/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 #include #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 ", 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< 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); }