nut-debian/drivers/solis.c

1008 lines
26 KiB
C
Raw Normal View History

2010-03-26 01:20:59 +02:00
/* solis.c - driver for Microsol Solis UPS hardware
2022-07-10 10:23:45 +03:00
Copyright (C) 2004 Silvino B. Magalhães <sbm2yk@gmail.com>
2019 Roberto Panerai Velloso <rvelloso@gmail.com>
2010-03-26 01:20:59 +02:00
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,
2022-07-10 10:23:45 +03:00
and support new cables for solis 3
2016-07-18 03:11:41 +03:00
2015/09/19 - Version 0.65 - patch for correct reading for Microsol Back-Ups BZ1200-BR
2022-07-10 10:23:45 +03:00
2017/12/21 - Version 0.66 - remove memory leaks (unfreed strdup()s);
remove ser_flush_in calls that were causing desync issues;
other minor improvements in source code.
2016-07-18 03:11:41 +03:00
(see the version control logs for more recent updates)
2010-03-26 01:20:59 +02:00
Microsol contributed with UPS Solis 1.5 HS 1.5 KVA for my tests.
http://www.microsol.com.br
*/
2022-07-10 10:23:45 +03:00
#include "main.h" /* Includes "config.h", must be first */
2010-03-26 01:20:59 +02:00
#include <ctype.h>
#include <stdio.h>
2022-07-10 10:23:45 +03:00
#include "nut_stdint.h"
2010-03-26 01:20:59 +02:00
#include "serial.h"
2022-07-10 10:23:45 +03:00
#include "nut_float.h"
2010-03-26 01:20:59 +02:00
#include "solis.h"
#include "timehead.h"
#define DRIVER_NAME "Microsol Solis UPS driver"
2022-07-10 10:23:45 +03:00
#define DRIVER_VERSION "0.68"
2010-03-26 01:20:59 +02:00
/* driver description structure */
upsdrv_info_t upsdrv_info = {
DRIVER_NAME,
DRIVER_VERSION,
2022-07-10 10:23:45 +03:00
"Silvino B. Magalhães <sbm2yk@gmail.com>" \
"Roberto Panerai Velloso <rvelloso@gmail.com>",
2010-03-26 01:20:59 +02:00
DRV_STABLE,
{ NULL }
};
#define false 0
#define true 1
2016-07-18 03:11:41 +03:00
#define RESP_END 0xFE
2022-07-10 10:23:45 +03:00
#define ENDCHAR 13 /* replies end with CR */
2010-03-26 01:20:59 +02:00
/* 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"
2016-07-18 03:11:41 +03:00
#define PRG_ONON "External shutdown programming active\n"
#define PRG_ONOU "Internal shutdown programming atcive\n"
2010-03-26 01:20:59 +02:00
#define TIME_OFF "UPS Time power off %02d:%02d\n"
#define TIME_ON "UPS Time power on %02d:%02d\n"
2016-07-18 03:11:41 +03:00
#define PRG_ONOF "Shutdown programming not activated\n"
2010-03-26 01:20:59 +02:00
#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 */
2022-07-10 10:23:45 +03:00
static char* convert_days(char *cop) {
static char alt[8];
2010-03-26 01:20:59 +02:00
2022-07-10 10:23:45 +03:00
int ish, fim;
if (weekn >= 6 || weekn < 0)
2010-03-26 01:20:59 +02:00
ish = 0;
else
2022-07-10 10:23:45 +03:00
ish = 1 + weekn;
2010-03-26 01:20:59 +02:00
fim = 7 - ish;
/* rotate left only 7 bits */
2022-07-10 10:23:45 +03:00
memcpy(alt, &cop[ish], (size_t)fim);
2010-03-26 01:20:59 +02:00
2022-07-10 10:23:45 +03:00
if (ish > 0)
memcpy(&alt[fim], cop, (size_t)ish);
2010-03-26 01:20:59 +02:00
alt[7] = 0; /* string terminator */
2016-07-18 03:11:41 +03:00
2022-07-10 10:23:45 +03:00
return alt;
2010-03-26 01:20:59 +02:00
}
2022-07-10 10:23:45 +03:00
inline static int is_binary(char ch ) {
return ( ch == '1' || ch == '0' );
2010-03-26 01:20:59 +02:00
}
/* convert string to binary */
2022-07-10 10:23:45 +03:00
static uint8_t str2bin( char *binStr ) {
uint8_t result = 0;
int i;
2016-07-18 03:11:41 +03:00
2022-07-10 10:23:45 +03:00
for (i = 0; i < 7; ++i) {
char ch = binStr[i];
if (is_binary(ch))
result += ( (ch - '0') << (6 - i) );
2010-03-26 01:20:59 +02:00
else
2022-07-10 10:23:45 +03:00
return 0;
2010-03-26 01:20:59 +02:00
}
2016-07-18 03:11:41 +03:00
2022-07-10 10:23:45 +03:00
return result;
2010-03-26 01:20:59 +02:00
}
/* revert firmware format to standard string binary days */
2022-07-10 10:23:45 +03:00
static uint8_t revert_days(unsigned char dweek) {
2010-03-26 01:20:59 +02:00
char alt[8];
2022-07-10 10:23:45 +03:00
int i;
2010-03-26 01:20:59 +02:00
2022-07-10 10:23:45 +03:00
for (i = 0; i < (6 - weekn); ++i)
alt[i] = (dweek >> (5 - weekn - i)) & 0x01;
2010-03-26 01:20:59 +02:00
2022-07-10 10:23:45 +03:00
for (i = 0; i < weekn+1; ++i)
alt[i+(6-weekn)] = (dweek >> (6 - i)) & 0x01;
2010-03-26 01:20:59 +02:00
2022-07-10 10:23:45 +03:00
for (i=0; i < 7; i++)
alt[i] += '0';
2010-03-26 01:20:59 +02:00
2022-07-10 10:23:45 +03:00
alt[7] = 0; /* string terminator */
2010-03-26 01:20:59 +02:00
2022-07-10 10:23:45 +03:00
return str2bin(alt);
2010-03-26 01:20:59 +02:00
}
2022-07-10 10:23:45 +03:00
static int is_hour(char *hour, int qual) {
int hora, min;
2010-03-26 01:20:59 +02:00
2022-07-10 10:23:45 +03:00
if ((strlen(hour) != 5) ||
(sscanf(hour, "%d:%d", &hora, &min) != 2))
2010-03-26 01:20:59 +02:00
return -1;
2022-07-10 10:23:45 +03:00
if (qual) {
2010-03-26 01:20:59 +02:00
dhour = hora;
dmin = min;
2022-07-10 10:23:45 +03:00
} else {
2010-03-26 01:20:59 +02:00
lhour = hora;
lmin = min;
}
return 1;
}
2022-07-10 10:23:45 +03:00
static void send_shutdown( void ) {
2010-03-26 01:20:59 +02:00
int i;
2022-07-10 10:23:45 +03:00
for (i = 0; i < 10; i++)
2010-03-26 01:20:59 +02:00
ser_send_char(upsfd, CMD_SHUT );
upslogx(LOG_NOTICE, "Ups shutdown command sent");
printf("Ups shutdown command sent\n");
}
/* save config ups */
2022-07-10 10:23:45 +03:00
static void save_ups_config( void ) {
2010-03-26 01:20:59 +02:00
int i, chks = 0;
2022-07-10 10:23:45 +03:00
/* FIXME? Check for overflows with int => char truncations?
* See also microsol-common.c for very similar code
*/
ConfigPack[0] = (unsigned char)0xCF;
ConfigPack[1] = (unsigned char)ihour;
ConfigPack[2] = (unsigned char)imin;
ConfigPack[3] = (unsigned char)isec;
ConfigPack[4] = (unsigned char)lhour;
ConfigPack[5] = (unsigned char)lmin;
ConfigPack[6] = (unsigned char)dhour;
ConfigPack[7] = (unsigned char)dmin;
ConfigPack[8] = (unsigned char)(weekn << 5);
ConfigPack[8] = (unsigned char)ConfigPack[8] | (unsigned char)dian;
ConfigPack[9] = (unsigned char)(mesn << 4);
ConfigPack[9] = (unsigned char)ConfigPack[9] | (unsigned char)( anon - BASE_YEAR );
ConfigPack[10] = (unsigned char)DaysOffWeek;
2010-03-26 01:20:59 +02:00
/* MSB zero */
ConfigPack[10] = ConfigPack[10] & (~(0x80));
2022-07-10 10:23:45 +03:00
for (i=0; i < 11; i++)
chks += ConfigPack[i];
2010-03-26 01:20:59 +02:00
2022-07-10 10:23:45 +03:00
/* FIXME? Does truncation to char have same effect as %256 ? */
ConfigPack[11] = (unsigned char)(chks % 256);
2016-07-18 03:11:41 +03:00
2022-07-10 10:23:45 +03:00
for (i=0; i < 12; i++)
ser_send_char(upsfd, ConfigPack[i]);
2010-03-26 01:20:59 +02:00
}
/* print UPS internal variables */
2022-07-10 10:23:45 +03:00
static void print_info( void ) {
printf(UPS_DATE, Year, Month, Day);
printf(SYS_DATE, anon, mesn, dian, seman);
printf(UPS_TIME, ihour, imin, isec);
2010-03-26 01:20:59 +02:00
2022-07-10 10:23:45 +03:00
if (prgups > 0) {
/*sunday, monday, tuesday, wednesday, thursday, friday, saturday*/
int week_days[7] = {0, 0, 0, 0, 0, 0, 0};
int i;
2016-07-18 03:11:41 +03:00
2010-03-26 01:20:59 +02:00
/* this is the string to binary standard */
2022-07-10 10:23:45 +03:00
for (i = 0; i < 7; ++i)
week_days[i] = (DaysStd >> (6 - i)) & 0x01;
2010-03-26 01:20:59 +02:00
2022-07-10 10:23:45 +03:00
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,
week_days[0], week_days[1], week_days[2],
week_days[3], week_days[4], week_days[5],
week_days[6]);
} else
printf(PRG_ONOF);
2010-03-26 01:20:59 +02:00
}
/* is today shutdown day ? */
2022-07-10 10:23:45 +03:00
inline static int is_today( unsigned char dweek, int nweek) {
return (dweek >> (6 - nweek)) & 0x01;
2010-03-26 01:20:59 +02:00
}
2022-07-10 10:23:45 +03:00
/* all models */
static void autonomy_calc( int iauto ) {
int indice, indd, lim, min, max, inf, sup, indc, bx, ipo = 0;
2010-03-26 01:20:59 +02:00
bx = bext[iauto];
indice = RecPack[3];
indd = indice - 139;
2022-07-10 10:23:45 +03:00
if (UtilPower > 20)
ipo = (UtilPower - 51) / 100;
2010-03-26 01:20:59 +02:00
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;
2022-07-10 10:23:45 +03:00
if (UtilPower <= 20) {
2010-03-26 01:20:59 +02:00
Autonomy = 170;
2022-07-10 10:23:45 +03:00
maxauto = 170;
} else {
2010-03-26 01:20:59 +02:00
maxauto = auton[iauto].mm[ipo][lim];
2022-07-10 10:23:45 +03:00
if( indice > inf && indice < sup )
2010-03-26 01:20:59 +02:00
Autonomy = auton[iauto].mm[ipo][indd];
2022-07-10 10:23:45 +03:00
else {
if (indice > max)
Autonomy = maxauto;
if (indice < min)
Autonomy = 0;
2010-03-26 01:20:59 +02:00
}
}
2016-07-18 03:11:41 +03:00
2022-07-10 10:23:45 +03:00
if (BattExtension > 0 && iauto < 4)
2010-03-26 01:20:59 +02:00
Autonomy = ( Autonomy * ( BattExtension + bx ) * 1.0 / bx );
}
2022-07-10 10:23:45 +03:00
static void scan_received_pack(void) {
int aux, im, ov;
2010-03-26 01:20:59 +02:00
/* model independent data */
2022-07-10 10:23:45 +03:00
Year = (RecPack[ 19 ] & 0x0F) + BASE_YEAR;
Month = (RecPack[ 19 ] & 0xF0) >> 4;
Day = (RecPack[ 18 ] & 0x1F);
2010-03-26 01:20:59 +02:00
DaysOnWeek = RecPack[17];
/* Days of week if in UPS shutdown programming mode */
2022-07-10 10:23:45 +03:00
if (prgups == 3) {
DaysStd = revert_days( DaysOnWeek );
2016-07-18 03:11:41 +03:00
2010-03-26 01:20:59 +02:00
/* time for programming UPS off */
dhour = RecPack[15];
dmin = RecPack[16];
/* time for programming UPS on */
lhour = RecPack[13];
lmin = RecPack[14];
}
2016-07-18 03:11:41 +03:00
2010-03-26 01:20:59 +02:00
/* UPS internal time */
ihour = RecPack[11];
imin = RecPack[10];
isec = RecPack[9];
2016-07-18 03:11:41 +03:00
2022-07-10 10:23:45 +03:00
if ((0x01 & RecPack[20]) == 0x01)
2010-03-26 01:20:59 +02:00
Out220 = 1;
2022-07-10 10:23:45 +03:00
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)
2010-03-26 01:20:59 +02:00
InputValue = 1;
else
InputValue = 0;
2022-07-10 10:23:45 +03:00
Temperature = 0x7F & RecPack[4];
if (0x80 & RecPack[4])
Temperature -= 128;
2010-03-26 01:20:59 +02:00
/* model dependent data */
im = inds[imodel];
ov = Out220;
2016-07-18 03:11:41 +03:00
if (SolisModel != 16) {
2022-07-10 10:23:45 +03:00
if (RecPack[6] >= 194)
InVoltage = RecPack[6] * ctab[imodel].m_involt194[0] + ctab[imodel].m_involt194[1];
2016-07-18 03:11:41 +03:00
else
2022-07-10 10:23:45 +03:00
InVoltage = RecPack[6] * ctab[imodel].m_involt193[0] + ctab[imodel].m_involt193[1];
2016-07-18 03:11:41 +03:00
} else {
/* Code InVoltage for STAY1200_USB */
2022-07-10 10:23:45 +03:00
if ((RecPack[20] & 0x1) == 0) /* IsOutVoltage 220 */
2016-07-18 03:11:41 +03:00
InVoltage = RecPack[2] * ctab[imodel].m_involt193[0] + ctab[imodel].m_involt193[1];
2022-07-10 10:23:45 +03:00
else
2016-07-18 03:11:41 +03:00
InVoltage = RecPack[2] * ctab[imodel].m_involt193[0] + ctab[imodel].m_involt193[1] - 3.0;
}
2010-03-26 01:20:59 +02:00
BattVoltage = RecPack[ 3 ] * ctab[imodel].m_battvolt[0] + ctab[imodel].m_battvolt[1];
2016-07-18 03:11:41 +03:00
2010-03-26 01:20:59 +02:00
NominalPower = nompow[im];
2016-07-18 03:11:41 +03:00
2022-07-10 10:23:45 +03:00
if (SourceFail) {
2010-03-26 01:20:59 +02:00
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;
2022-07-10 10:23:45 +03:00
} else {
2010-03-26 01:20:59 +02:00
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 );
}
2016-07-18 03:11:41 +03:00
2022-07-10 10:23:45 +03:00
if (SolisModel == 16) {
int configRelay = (RecPack[6] & 0x38) >> 3;
double TENSAO_SAIDA_F1_MR[8] = { 1.1549, 1.0925, 0.0, 0.0, 1.0929, 1.0885, 0.0, 0.8654262224145391 };
double TENSAO_SAIDA_F2_MR[8] = { -6.9157, 11.026, 10.43, 0.0, -0.6109, 12.18, 0.0, 13.677};
2016-07-18 03:11:41 +03:00
2022-07-10 10:23:45 +03:00
const double TENSAO_SAIDA_F2_MI[8] ={ 5.59, 9.47, 13.7, 0.0, 0.0, 0.0, 0.0, 0.0 };
const double TENSAO_SAIDA_F1_MI[8] = { 7.9, 9.1, 17.6, 0.0, 0.0, 0.0, 0.0, 0.0 };
2016-07-18 03:11:41 +03:00
2022-07-10 10:23:45 +03:00
const double corrente_saida_F1_MR = 0.12970000389100012;
const double corrente_saida_F2_MR = 0.5387060281204546;
/* double corrente_saida_F1_MI = 0.1372;
double corrente_saida_F2_MI = 0.3456; */
2016-07-18 03:11:41 +03:00
2022-07-10 10:23:45 +03:00
if (SourceFail) {
if (RecPack[20] == 0) {
double a = RecPack[1] * 2;
a /= 128.0;
/* a = double sqrt(a); */
OutVoltage = RecPack[1] * a * TENSAO_SAIDA_F1_MI[configRelay] + TENSAO_SAIDA_F2_MI[configRelay];
}
} else {
OutCurrent = (float)(corrente_saida_F1_MR * RecPack[5] + corrente_saida_F2_MR);
OutVoltage = RecPack[1] * TENSAO_SAIDA_F1_MR[configRelay] + TENSAO_SAIDA_F2_MR[configRelay];
AppPower = OutCurrent * OutVoltage;
double RealPower = (RecPack[7] + RecPack[8] * 256);
double potVA1 = 5.968 * AppPower - 284.36;
double potVA2 = 7.149 * AppPower - 567.18;
double potLin = 0.1664 * RealPower + 49.182;
double potRe = 0.1519 * RealPower + 32.644;
if (fabs(potVA1 - RealPower) < fabs(potVA2 - RealPower))
RealPower = potLin;
else
RealPower = potRe;
if (OutCurrent < 0.7)
RealPower = AppPower;
if (AppPower < RealPower) {
double f = AppPower;
AppPower = RealPower;
RealPower = f;
2016-07-18 03:11:41 +03:00
}
}
2022-07-10 10:23:45 +03:00
}
2010-03-26 01:20:59 +02:00
2022-07-10 10:23:45 +03:00
aux = (RecPack[ 21 ] + RecPack[ 22 ] * 256);
if (aux > 0)
2010-03-26 01:20:59 +02:00
InFreq = ctab[imodel].m_infreq * 1.0 / aux;
2016-07-18 03:11:41 +03:00
/* Specific for STAY1200_USB */
2022-07-10 10:23:45 +03:00
if (SolisModel == 16) {
InFreq = ((float)(0.37 * (257 - (aux >> 8))));
} else
2010-03-26 01:20:59 +02:00
InFreq = 0;
2016-07-18 03:11:41 +03:00
2010-03-26 01:20:59 +02:00
/* input voltage offset */
2022-07-10 10:23:45 +03:00
if (InVoltage < InVolt_offset) { /* all is equal 30 */
2010-03-26 01:20:59 +02:00
InFreq = 0;
InVoltage = 0;
InCurrent = 0;
}
/* app power offset */
2022-07-10 10:23:45 +03:00
if (AppPower < ctab[imodel].m_appp_offset) {
2010-03-26 01:20:59 +02:00
AppPower = 0;
UtilPower = 0;
ChargePowerFactor = 0;
OutCurrent = 0;
}
2016-07-18 03:11:41 +03:00
2022-07-10 10:23:45 +03:00
if (im < 3)
autonomy_calc(im);
else {
if (BattExtension == 80 && im == 3)
autonomy_calc(im + 1);
2010-03-26 01:20:59 +02:00
else
2022-07-10 10:23:45 +03:00
autonomy_calc(im);
2010-03-26 01:20:59 +02:00
}
/* model independent data */
batcharge = ( Autonomy / maxauto ) * 100.0;
upscharge = ( AppPower / NominalPower ) * 100.0;
if (batcharge > 100.0)
batcharge = 100.0;
OutFreq = 60;
2022-07-10 10:23:45 +03:00
if (!InversorOn) {
2010-03-26 01:20:59 +02:00
OutVoltage = 0;
OutFreq = 0;
}
2022-07-10 10:23:45 +03:00
if (!SourceFail && InversorOn)
2010-03-26 01:20:59 +02:00
OutFreq = InFreq;
2022-07-10 10:23:45 +03:00
if (AppPower < 0) /* charge pf */
2010-03-26 01:20:59 +02:00
ChargePowerFactor = 0;
2022-07-10 10:23:45 +03:00
else {
if( d_equal(AppPower, 0) )
2010-03-26 01:20:59 +02:00
ChargePowerFactor = 100;
else
2022-07-10 10:23:45 +03:00
ChargePowerFactor = (( UtilPower / AppPower) * 100);
if(ChargePowerFactor > 100)
2010-03-26 01:20:59 +02:00
ChargePowerFactor = 100;
}
2022-07-10 10:23:45 +03:00
if (SourceFail && SourceLast) /* first time failure */
2010-03-26 01:20:59 +02:00
FailureFlag = true;
/* source return */
2022-07-10 10:23:45 +03:00
if (!SourceFail && !SourceLast) {
2010-03-26 01:20:59 +02:00
SourceReturn = true;
2022-07-10 10:23:45 +03:00
/* clean port: */
/* ser_flush_in(upsfd,"",0); */
2010-03-26 01:20:59 +02:00
}
2016-07-18 03:11:41 +03:00
2022-07-10 10:23:45 +03:00
if((!SourceFail) == SourceLast) {
2010-03-26 01:20:59 +02:00
SourceReturn = false;
FailureFlag = false;
}
2022-07-10 10:23:45 +03:00
SourceLast = !SourceFail;
2010-03-26 01:20:59 +02:00
/* Autonomy */
2022-07-10 10:23:45 +03:00
if (Autonomy < 5)
2010-03-26 01:20:59 +02:00
LowBatt = true;
else
LowBatt = false;
UpsPowerFactor = 700;
/* input 110V or 220v */
2022-07-10 10:23:45 +03:00
if (InputValue == 0) {
2010-03-26 01:20:59 +02:00
InDownLim = 75;
InUpLim = 150;
NomInVolt = 110;
2022-07-10 10:23:45 +03:00
} else {
2010-03-26 01:20:59 +02:00
InDownLim = 150;
InUpLim = 300;
NomInVolt = 220;
}
/* output volage 220V or 110V */
2022-07-10 10:23:45 +03:00
if (Out220) {
2010-03-26 01:20:59 +02:00
OutDownLim = 190;
OutUpLim = 250;
NomOutVolt = 220;
2022-07-10 10:23:45 +03:00
} else {
2010-03-26 01:20:59 +02:00
OutDownLim = 100;
OutUpLim = 140;
NomOutVolt = 110;
}
2022-07-10 10:23:45 +03:00
if (SourceFail) /* source status */
2010-03-26 01:20:59 +02:00
InputStatus = 2;
else
InputStatus = 1;
2022-07-10 10:23:45 +03:00
if (InversorOn) /* output status */
2010-03-26 01:20:59 +02:00
OutputStatus = 1;
else
OutputStatus = 2;
2022-07-10 10:23:45 +03:00
if (OverCharge)
2010-03-26 01:20:59 +02:00
OutputStatus = 3;
2022-07-10 10:23:45 +03:00
if (CriticBatt) /* battery status */
2010-03-26 01:20:59 +02:00
BattStatus = 4;
else
BattStatus = 1;
SourceEvents = 0;
2022-07-10 10:23:45 +03:00
if (FailureFlag)
2010-03-26 01:20:59 +02:00
SourceEvents = 1;
2022-07-10 10:23:45 +03:00
if (SourceReturn)
2010-03-26 01:20:59 +02:00
SourceEvents = 2;
/* verify Inversor */
2022-07-10 10:23:45 +03:00
if (Flag_inversor) {
2010-03-26 01:20:59 +02:00
InversorOnLast = InversorOn;
Flag_inversor = false;
}
OutputEvents = 0;
2022-07-10 10:23:45 +03:00
if (InversorOn && !InversorOnLast)
2010-03-26 01:20:59 +02:00
OutputEvents = 26;
2022-07-10 10:23:45 +03:00
if (InversorOnLast && !InversorOn)
2010-03-26 01:20:59 +02:00
OutputEvents = 27;
InversorOnLast = InversorOn;
2022-07-10 10:23:45 +03:00
if (SuperHeat && !SuperHeatLast)
2010-03-26 01:20:59 +02:00
OutputEvents = 12;
2022-07-10 10:23:45 +03:00
if (SuperHeatLast && !SuperHeat)
2010-03-26 01:20:59 +02:00
OutputEvents = 13;
SuperHeatLast = SuperHeat;
2022-07-10 10:23:45 +03:00
if (OverCharge && !OverChargeLast)
2010-03-26 01:20:59 +02:00
OutputEvents = 10;
2022-07-10 10:23:45 +03:00
if (OverChargeLast && !OverCharge)
2010-03-26 01:20:59 +02:00
OutputEvents = 11;
OverChargeLast = OverCharge;
BattEvents = 0;
CriticBattLast = CriticBatt;
}
2022-07-10 10:23:45 +03:00
static void comm_receive(const unsigned char *bufptr, size_t size) {
if (size == packet_size) {
int CheckSum = 0;
size_t i;
2010-03-26 01:20:59 +02:00
2022-07-10 10:23:45 +03:00
memcpy(RecPack, bufptr, packet_size);
2010-03-26 01:20:59 +02:00
2022-07-10 10:23:45 +03:00
if (nut_debug_level >= 3)
upsdebug_hex(3, "comm_receive: RecPack", RecPack, size);
2016-07-18 03:11:41 +03:00
2010-03-26 01:20:59 +02:00
/* CheckSum verify */
2022-07-10 10:23:45 +03:00
for (i = 0 ; i < packet_size-2 ; ++i )
CheckSum += RecPack[i];
2010-03-26 01:20:59 +02:00
CheckSum = CheckSum % 256;
2016-07-18 03:11:41 +03:00
upsdebugx(4, "%s: calculated checksum = 0x%02x, RecPack[23] = 0x%02x", __func__, CheckSum, RecPack[23]);
2022-07-10 10:23:45 +03:00
/* clean port: */
/* ser_flush_in(upsfd,"",0); */
/* RecPack[0] == model number below:
* SOLIS = 1;
* RHINO = 2;
* STAY = 3;
* SOLIS_LI_700 = 169;
* SOLIS_M11 = 171;
* SOLIS_M15 = 175;
* SOLIS_M14 = 174;
* SOLIS_M13 = 173;
* SOLISDC_M14 = 201;
* SOLISDC_M13 = 206;
* SOLISDC_M15 = 207;
* CABECALHO_RHINO = 194;
* PS800 = 185;
* STAY1200_USB = 186;
* PS350_CII = 184;
* PS2200 = 187;
* PS2200_22 = 188;
* STAY700_USB = 189;
* BZ1500 = 190;
2016-07-18 03:11:41 +03:00
*/
2010-03-26 01:20:59 +02:00
2022-07-10 10:23:45 +03:00
if ((((RecPack[0] & 0xF0) == 0xA0 ) || (RecPack[0] & 0xF0) == 0xB0) &&
(RecPack[24] == 254) &&
(RecPack[23] == CheckSum)) {
2016-07-18 03:11:41 +03:00
2022-07-10 10:23:45 +03:00
if (!detected) {
if (RecPack[0] == 186)
2016-07-18 03:11:41 +03:00
SolisModel = 16;
2022-07-10 10:23:45 +03:00
else
2016-07-18 03:11:41 +03:00
SolisModel = (int) (RecPack[0] & 0x0F);
2022-07-10 10:23:45 +03:00
if (SolisModel < 13)
2010-03-26 01:20:59 +02:00
imodel = SolisModel - 10; /* 10 = 0, 11 = 1 */
else
imodel = SolisModel - 11; /* 13 = 2, 14 = 3, 15 = 4 */
2016-07-18 03:11:41 +03:00
2010-03-26 01:20:59 +02:00
detected = true;
}
2022-07-10 10:23:45 +03:00
switch (SolisModel) {
case 10:
case 11:
case 12:
case 13:
case 14:
case 15:
scan_received_pack();
break;
case 16: /* STAY1200_USB model */
scan_received_pack();
break;
default:
printf(M_UNKN);
scan_received_pack(); /* Scan anyway. */
break;
2010-03-26 01:20:59 +02:00
}
}
}
}
2022-07-10 10:23:45 +03:00
static void get_base_info(void) {
2010-03-26 01:20:59 +02:00
#ifdef PORTUGUESE
2022-07-10 10:23:45 +03:00
const char DaysOfWeek[7][4]={"Dom", "Seg", "Ter", "Qua", "Qui", "Sex", "Sab"};
2010-03-26 01:20:59 +02:00
#else
2016-07-18 03:11:41 +03:00
const char DaysOfWeek[7][4]={"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
2010-03-26 01:20:59 +02:00
#endif
2022-07-10 10:23:45 +03:00
unsigned char packet[PACKET_SIZE], syncEOR = '\0', syncEOR_was_read = 0;
int i1=0, i2=0;
size_t i;
ssize_t tam;
2010-03-26 01:20:59 +02:00
2012-06-01 16:55:19 +03:00
time_t tmt;
2010-03-26 01:20:59 +02:00
struct tm *now;
2022-07-10 10:23:45 +03:00
struct tm tmbuf;
time(&tmt);
now = localtime_r(&tmt, &tmbuf);
2010-03-26 01:20:59 +02:00
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;
2022-07-10 10:23:45 +03:00
weekn = now->tm_wday;
2010-03-26 01:20:59 +02:00
2022-07-10 10:23:45 +03:00
strcpy(seman, DaysOfWeek[weekn]);
2010-03-26 01:20:59 +02:00
2022-07-10 10:23:45 +03:00
if (testvar("battext"))
2010-03-26 01:20:59 +02:00
BattExtension = atoi(getval("battext"));
2022-07-10 10:23:45 +03:00
if (testvar("prgshut"))
2010-03-26 01:20:59 +02:00
prgups = atoi(getval("prgshut"));
2022-07-10 10:23:45 +03:00
if (prgups > 0 && prgups < 3) {
if (testvar("daysweek"))
DaysOnWeek = str2bin(convert_days(getval("daysweek")));
2010-03-26 01:20:59 +02:00
2022-07-10 10:23:45 +03:00
if (testvar("daysoff")) {
char *doff = getval("daysoff");
DaysStd = str2bin(doff);
DaysOffWeek = str2bin( convert_days(doff));
2010-03-26 01:20:59 +02:00
}
2022-07-10 10:23:45 +03:00
if (testvar("houron"))
i1 = is_hour(getval("houron"), 0);
2010-03-26 01:20:59 +02:00
2022-07-10 10:23:45 +03:00
if (testvar("houroff"))
i2 = is_hour(getval("houroff"), 1);
2010-03-26 01:20:59 +02:00
2022-07-10 10:23:45 +03:00
if (i1 == 1 && i2 == 1 && (DaysOnWeek > 0)) {
2010-03-26 01:20:59 +02:00
isprogram = 1; /* prgups == 1 ou 2 */
2022-07-10 10:23:45 +03:00
if (prgups == 2)
save_ups_config(); /* save ups config */
} else {
if (i2 == 1 && DaysOffWeek > 0) {
2010-03-26 01:20:59 +02:00
isprogram = 1;
DaysOnWeek = DaysOffWeek;
2022-07-10 10:23:45 +03:00
}
2010-03-26 01:20:59 +02:00
}
2011-06-01 23:31:49 +03:00
} /* end prgups 1 - 2 */
2010-03-26 01:20:59 +02:00
/* dummy read attempt to sync - throw it out */
2016-07-18 03:11:41 +03:00
upsdebugx(3, "%s: sending CMD_UPSCONT and ENDCHAR to sync", __func__);
ser_send(upsfd, "%c%c", CMD_UPSCONT, ENDCHAR);
2022-07-10 10:23:45 +03:00
/*
* - Read until end-of-response character (0xFE):
* read up to 3 packets in size before giving up
* synchronizing with the device.
*/
for (i = 0; i < packet_size*3; i++) {
ser_get_char(upsfd, &syncEOR, 3, 0);
syncEOR_was_read = 1;
if(syncEOR == RESP_END)
2016-07-18 03:11:41 +03:00
break;
}
2010-03-26 01:20:59 +02:00
2022-07-10 10:23:45 +03:00
if (!syncEOR_was_read || syncEOR != RESP_END) {
/* synchronization failed */
2016-07-18 03:11:41 +03:00
fatalx(EXIT_FAILURE, NO_SOLIS);
} else {
2022-07-10 10:23:45 +03:00
upsdebugx(4, "%s: requesting %zu bytes from ser_get_buf_len()", __func__, packet_size);
tam = ser_get_buf_len(upsfd, packet, packet_size, 3, 0);
if (tam < 0) {
upsdebugx(0, "%s: Error (%zd) reading from ser_get_buf_len()", __func__, tam);
fatalx(EXIT_FAILURE, NO_SOLIS);
2016-07-18 03:11:41 +03:00
}
2022-07-10 10:23:45 +03:00
upsdebugx(2, "%s: received %zd bytes from ser_get_buf_len()", __func__, tam);
if (tam > 0 && nut_debug_level >= 4) {
upsdebug_hex(4, "received from ser_get_buf_len()", packet, (size_t)tam);
}
comm_receive(packet, (size_t)tam);
2016-07-18 03:11:41 +03:00
}
2010-03-26 01:20:59 +02:00
2022-07-10 10:23:45 +03:00
if (!detected)
2010-03-26 01:20:59 +02:00
fatalx(EXIT_FAILURE, NO_SOLIS );
2022-07-10 10:23:45 +03:00
switch (SolisModel) {
2016-07-18 03:11:41 +03:00
case 10:
2010-03-26 01:20:59 +02:00
case 11:
case 12:
2015-04-30 16:53:36 +03:00
Model = "Solis 1.0";
2010-03-26 01:20:59 +02:00
break;
case 13:
2015-04-30 16:53:36 +03:00
Model = "Solis 1.5";
2010-03-26 01:20:59 +02:00
break;
case 14:
2015-04-30 16:53:36 +03:00
Model = "Solis 2.0";
2010-03-26 01:20:59 +02:00
break;
case 15:
2015-04-30 16:53:36 +03:00
Model = "Solis 3.0";
2010-03-26 01:20:59 +02:00
break;
2016-07-18 03:11:41 +03:00
case 16:
Model = "Microsol Back-Ups BZ1200-BR";
break;
2010-03-26 01:20:59 +02:00
}
/* if( isprogram ) */
2022-07-10 10:23:45 +03:00
if (prgups == 1) {
2010-03-26 01:20:59 +02:00
hourshut = dhour;
minshut = dmin;
2022-07-10 10:23:45 +03:00
} else {
if (prgups == 2 || prgups == 3) { /* broadcast before firmware shutdown */
if (dmin < 5) {
if (dhour > 1)
2010-03-26 01:20:59 +02:00
hourshut = dhour - 1;
else
hourshut = 23;
2022-07-10 10:23:45 +03:00
2010-03-26 01:20:59 +02:00
minshut = 60 - ( 5 - dmin );
2022-07-10 10:23:45 +03:00
} else {
hourshut = dhour;
minshut = dmin - 5;
2010-03-26 01:20:59 +02:00
}
}
}
/* 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);
2022-07-10 10:23:45 +03:00
print_info();
2010-03-26 01:20:59 +02:00
}
2022-07-10 10:23:45 +03:00
static void get_update_info(void) {
unsigned char temp[256];
int isday, hourn, minn;
ssize_t tam;
2010-03-26 01:20:59 +02:00
/* time update and programable shutdown block */
2012-06-01 16:55:19 +03:00
time_t tmt;
2010-03-26 01:20:59 +02:00
struct tm *now;
2022-07-10 10:23:45 +03:00
struct tm tmbuf;
time(&tmt);
now = localtime_r(&tmt, &tmbuf);
2010-03-26 01:20:59 +02:00
hourn = now->tm_hour;
minn = now->tm_min;
2022-07-10 10:23:45 +03:00
weekn = now->tm_wday;
2010-03-26 01:20:59 +02:00
2022-07-10 10:23:45 +03:00
if (isprogram || prgups == 3) {
if (isprogram)
isday = is_today(DaysStd, weekn);
2010-03-26 01:20:59 +02:00
else
2022-07-10 10:23:45 +03:00
isday = is_today( DaysStd, weekn);
2016-07-18 03:11:41 +03:00
2022-07-10 10:23:45 +03:00
if (isday)
printf(TODAY_DD, hourshut, minshut);
if (
(hourn == hourshut) &&
(minn >= minshut) &&
isday) {
2010-03-26 01:20:59 +02:00
printf( SHUT_NOW );
progshut = 1;
}
2016-07-18 03:11:41 +03:00
}
2010-03-26 01:20:59 +02:00
/* programable shutdown end block */
/* get update package */
temp[0] = 0; /* flush temp buffer */
2016-07-18 03:11:41 +03:00
2022-07-10 10:23:45 +03:00
upsdebugx(3, "%s: requesting %zu bytes from ser_get_buf_len()", __func__, packet_size);
tam = ser_get_buf_len(upsfd, temp, packet_size, 3, 0);
2010-03-26 01:20:59 +02:00
2022-07-10 10:23:45 +03:00
if (tam < 0) {
upsdebugx(0, "%s: Error (%zd) reading from ser_get_buf_len()", __func__, tam);
fatalx(EXIT_FAILURE, NO_SOLIS);
2016-07-18 03:11:41 +03:00
}
2022-07-10 10:23:45 +03:00
upsdebugx(2, "%s: received %zd bytes from ser_get_buf_len()", __func__, tam);
if(tam > 0 && nut_debug_level >= 4)
upsdebug_hex(4, "received from ser_get_buf_len()", temp, (size_t)tam);
2010-03-26 01:20:59 +02:00
2022-07-10 10:23:45 +03:00
comm_receive(temp, (size_t)tam);
2010-03-26 01:20:59 +02:00
}
2022-07-10 10:23:45 +03:00
static int instcmd(const char *cmdname, const char *extra) {
if (!strcasecmp(cmdname, "shutdown.return")) {
2011-06-01 23:31:49 +03:00
/* shutdown and restart */
ser_send_char(upsfd, CMD_SHUTRET); /* 0xDE */
/* ser_send_char(upsfd, ENDCHAR); */
2010-03-26 01:20:59 +02:00
return STAT_INSTCMD_HANDLED;
}
2022-07-10 10:23:45 +03:00
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;
}
2010-03-26 01:20:59 +02:00
2022-07-10 10:23:45 +03:00
upslogx(LOG_NOTICE, "instcmd: unknown command [%s] [%s]", cmdname, extra);
2010-03-26 01:20:59 +02:00
return STAT_INSTCMD_UNKNOWN;
}
2022-07-10 10:23:45 +03:00
void upsdrv_initinfo(void) {
get_base_info();
2010-03-26 01:20:59 +02:00
upsh.instcmd = instcmd;
}
2022-07-10 10:23:45 +03:00
void upsdrv_updateinfo(void) {
get_update_info(); /* new package for updates */
2010-03-26 01:20:59 +02:00
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);
2016-07-18 03:11:41 +03:00
dstate_setinfo("output.current", "%03.1f", OutCurrent);
2010-03-26 01:20:59 +02:00
status_init();
2022-07-10 10:23:45 +03:00
if (!SourceFail)
2010-03-26 01:20:59 +02:00
status_set("OL"); /* on line */
else
status_set("OB"); /* on battery */
2022-07-10 10:23:45 +03:00
if (Autonomy < 5)
2010-03-26 01:20:59 +02:00
status_set("LB"); /* low battery */
2022-07-10 10:23:45 +03:00
if (progshut) { /* software programable shutdown immediately */
2010-03-26 01:20:59 +02:00
if( prgups == 2 )
2022-07-10 10:23:45 +03:00
send_shutdown(); /* Ups shutdown in 4-5 minutes -- redundant Ups shutdown */
2010-03-26 01:20:59 +02:00
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();
}
2016-07-18 03:11:41 +03:00
/*! @brief Power down the attached load immediately.
* 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.
*/
2022-07-10 10:23:45 +03:00
void upsdrv_shutdown(void) {
2011-06-01 23:31:49 +03:00
if (!SourceFail) { /* on line */
2016-07-18 03:11:41 +03:00
upslogx(LOG_NOTICE, "On line, sending shutdown+return command...\n");
2010-03-26 01:20:59 +02:00
ser_send_char(upsfd, CMD_SHUTRET );
2022-07-10 10:23:45 +03:00
} else {
2016-07-18 03:11:41 +03:00
upslogx(LOG_NOTICE, "On battery, sending normal shutdown command...\n");
2010-03-26 01:20:59 +02:00
ser_send_char(upsfd, CMD_SHUT);
}
}
2022-07-10 10:23:45 +03:00
void upsdrv_help(void) {
2010-03-26 01:20:59 +02:00
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");
2022-07-10 10:23:45 +03:00
printf(" Uses daysweek and houron to programming and save UPS power on/off\n");
2010-03-26 01:20:59 +02:00
printf(" These are valid only if prgshut = 2 or 3\n");
}
2022-07-10 10:23:45 +03:00
void upsdrv_makevartable(void) {
2010-03-26 01:20:59 +02:00
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)");
}
2022-07-10 10:23:45 +03:00
void upsdrv_initups(void) {
2010-03-26 01:20:59 +02:00
upsfd = ser_open(device_path);
ser_set_speed(upsfd, device_path, B9600);
ser_set_dtr(upsfd, 1);
ser_set_rts(upsfd, 0);
}
2022-07-10 10:23:45 +03:00
void upsdrv_cleanup(void) {
2010-03-26 01:20:59 +02:00
ser_close(upsfd, device_path);
}