/* solis.c - driver for Microsol Solis UPS hardware

   Copyright (C) 2004  Silvino B. Magalhães    <sbm2yk@gmail.com>
                 2019  Roberto Panerai Velloso <rvelloso@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
   2015/09/19 - Version 0.65 - patch for correct reading for Microsol Back-Ups BZ1200-BR
   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.
   (see the version control logs for more recent updates)

   Microsol contributed with UPS Solis 1.5 HS 1.5 KVA for my tests.

   http://www.microsol.com.br

*/

#include "main.h"	/* Includes "config.h", must be first */

#include <ctype.h>
#include <stdio.h>
#include "nut_stdint.h"
#include "serial.h"
#include "nut_float.h"
#include "solis.h"
#include "timehead.h"

#define DRIVER_NAME	"Microsol Solis UPS driver"
#define DRIVER_VERSION	"0.74"

/* driver description structure */
upsdrv_info_t upsdrv_info = {
	DRIVER_NAME,
	DRIVER_VERSION,
	"Silvino B. Magalhães <sbm2yk@gmail.com>" \
	"Roberto Panerai Velloso <rvelloso@gmail.com>",
	DRV_STABLE,
	{ NULL }
};

#define false 0
#define true 1
#define RESP_END    0xFE
#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 active\n"
#define PRG_ONOU   "Internal shutdown programming atcive\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 activated\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* convert_days(char *cop) {
	static char alt[8];

	int ish, fim;
	if (weekn >= 6 || weekn < 0)
		ish = 0;
	else
		ish = 1 + weekn;

	fim = 7 - ish;
	/* rotate left only 7 bits */

	memcpy(alt, &cop[ish], (size_t)fim);

	if (ish > 0)
		memcpy(&alt[fim], cop, (size_t)ish);

	alt[7] = 0; /* string terminator */

	return alt;
}

inline static int is_binary(char ch ) {
	return ( ch == '1' || ch == '0' );
}

/* convert string to binary */
static uint8_t str2bin( char *binStr ) {
	uint8_t result = 0;
	int i;

	for (i = 0; i < 7; ++i) {
		char ch = binStr[i];
		if (is_binary(ch))
			result += ( (ch - '0') << (6 - i) );
		else
			return 0;
	}

	return result;
}

/* revert firmware format to standard string binary days */
static uint8_t revert_days(unsigned char dweek) {
	char alt[8];
	int i;

	for (i = 0; i < (6 - weekn); ++i)
		alt[i] = (dweek >> (5 - weekn - i)) & 0x01;

	for (i = 0; i < weekn+1; ++i)
		alt[i+(6-weekn)] = (dweek >> (6 - i)) & 0x01;

	for (i=0; i < 7; i++)
		alt[i] += '0';

	alt[7] = 0; /* string terminator */

	return str2bin(alt);
}

static int is_hour(char *hour, int qual) {
	int hora, min;

	if ((strlen(hour) != 5) ||
		(sscanf(hour, "%d:%d", &hora, &min) != 2))
		return -1;

	if (qual) {
		dhour = hora;
		dmin = min;
	} else {
		lhour = hora;
		lmin = min;
	}
	return 1;
}

static void send_shutdown( 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 save_ups_config( void ) {
	int i, chks = 0;

	/* 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;

	/* MSB zero */
	ConfigPack[10] = ConfigPack[10] & (~(0x80));

	for (i=0; i < 11; i++)
		chks += ConfigPack[i];

	/* FIXME? Does truncation to char have same effect as %256 ? */
	ConfigPack[11] = (unsigned char)(chks % 256);

	for (i=0; i < 12; i++)
		ser_send_char(upsfd, ConfigPack[i]);
}

/* print UPS internal variables */
static void print_info( void ) {
	printf(UPS_DATE, Year, Month, Day);
	printf(SYS_DATE, anon, mesn, dian, seman);
	printf(UPS_TIME, ihour, imin, isec);

	if (prgups > 0) {
		/*sunday, monday, tuesday, wednesday, thursday, friday, saturday*/
		int week_days[7] = {0, 0, 0, 0, 0, 0, 0};
		int i;

		/* this is the string to binary standard */
		for (i = 0; i < 7; ++i)
			week_days[i] = (DaysStd >> (6 - i)) & 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,
			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);
}

/* is today shutdown day ? */
inline static int is_today( unsigned char dweek, int nweek) {
	return (dweek >> (6 - nweek)) & 0x01;
}

/* all models */
static void autonomy_calc( int iauto ) {
	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 scan_received_pack(void) {
	int aux, im, ov;

	/* 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 = revert_days( 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])
		Temperature -= 128;

	/* model dependent data */

	im = inds[imodel];
	ov = Out220;

	if (SolisModel != 16) {
		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];
	} else {
		/* Code InVoltage for STAY1200_USB */
		if ((RecPack[20] & 0x1) == 0) /* IsOutVoltage 220 */
			InVoltage = RecPack[2] * ctab[imodel].m_involt193[0] + ctab[imodel].m_involt193[1];
		else
			InVoltage = RecPack[2] * ctab[imodel].m_involt193[0] + ctab[imodel].m_involt193[1] - 3.0;
	}

	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 );
	}

	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};

		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 };

		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; */

		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 {
			double	RealPower, potVA1, potVA2, potLin, potRe;

			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;

			RealPower = (RecPack[7] + RecPack[8] * 256);

			potVA1 = 5.968 * AppPower - 284.36;
			potVA2 = 7.149 * AppPower - 567.18;
			potLin = 0.1664 * RealPower + 49.182;
			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;
			}
		}
	}

	aux = (RecPack[ 21 ] + RecPack[ 22 ] * 256);
	if (aux > 0)
		InFreq = ctab[imodel].m_infreq * 1.0 / aux;

	/* Specific for STAY1200_USB */
	if (SolisModel == 16) {
		InFreq = ((float)(0.37 * (257 - (aux >> 8))));
	} 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)
		autonomy_calc(im);
	else {
		if (BattExtension == 80 && im == 3)
			autonomy_calc(im + 1);
		else
			autonomy_calc(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( d_equal(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;
		/* clean port: */
		/* ser_flush_in(upsfd,"",0); */
	}

	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 comm_receive(const unsigned char *bufptr, size_t size) {
	if (size == packet_size) {
		int CheckSum = 0;
		size_t i;

		memcpy(RecPack, bufptr, packet_size);

		if (nut_debug_level >= 3)
			upsdebug_hex(3, "comm_receive: RecPack", RecPack, size);

		/* CheckSum verify */
		for (i = 0 ; i < packet_size-2 ; ++i )
			CheckSum += RecPack[i];
		CheckSum = CheckSum % 256;
		upsdebugx(4, "%s: calculated checksum = 0x%02x, RecPack[23] = 0x%02x",
			__func__, (unsigned int)CheckSum, RecPack[23]);

		/* 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;
		 */

		if ((((RecPack[0] & 0xF0) == 0xA0 ) || (RecPack[0] & 0xF0) == 0xB0) &&
			(RecPack[24] == 254) &&
			(RecPack[23] == CheckSum)) {

			if (!detected) {
				if (RecPack[0] == 186)
					SolisModel = 16;
				else
					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:
				scan_received_pack();
				break;
			case 16:	/* STAY1200_USB model */
				scan_received_pack();
				break;
			default:
				printf(M_UNKN);
				scan_received_pack(); /* Scan anyway. */
				break;
			}
		}
	}
}

static void get_base_info(void) {
#ifdef PORTUGUESE
	const char DaysOfWeek[7][4]={"Dom", "Seg", "Ter", "Qua", "Qui", "Sex", "Sab"};
#else
	const char DaysOfWeek[7][4]={"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
#endif
	unsigned char packet[PACKET_SIZE], syncEOR = '\0', syncEOR_was_read = 0;
	int i1=0, i2=0;
	size_t i;
	ssize_t tam;

	time_t tmt;
	struct tm *now;
	struct tm tmbuf;

	time(&tmt);
	now = localtime_r(&tmt, &tmbuf);
	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;

	snprintf(seman, sizeof(seman), "%s", DaysOfWeek[weekn]);

	if (testvar("battext"))
		BattExtension = atoi(getval("battext"));

	if (testvar("prgshut"))
		prgups = atoi(getval("prgshut"));

	if (prgups > 0 && prgups < 3) {
		if (testvar("daysweek"))
			DaysOnWeek = str2bin(convert_days(getval("daysweek")));

		if (testvar("daysoff")) {
			char *doff = getval("daysoff");
			DaysStd = str2bin(doff);
			DaysOffWeek = str2bin( convert_days(doff));
		}

		if (testvar("houron"))
			i1 = is_hour(getval("houron"), 0);

		if (testvar("houroff"))
			i2 =  is_hour(getval("houroff"), 1);

		if (i1 == 1 && i2 == 1 && (DaysOnWeek > 0)) {
			isprogram = 1; /* prgups == 1 ou 2  */

			if (prgups == 2)
				save_ups_config();  /* save ups config */
		} else {
			if (i2 == 1 && DaysOffWeek > 0) {
				isprogram = 1;
				DaysOnWeek = DaysOffWeek;
			}
		}
	} /* end prgups 1 - 2 */

	/* dummy read attempt to sync - throw it out */
	upsdebugx(3, "%s: sending CMD_UPSCONT and ENDCHAR to sync", __func__);
	ser_send(upsfd, "%c%c", CMD_UPSCONT, ENDCHAR);

	/*
	 * - 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)
			break;
	}

	if (!syncEOR_was_read || syncEOR != RESP_END) {
		/* synchronization failed */
		fatalx(EXIT_FAILURE, NO_SOLIS);
	} else {
		upsdebugx(4, "%s: requesting %" PRIuSIZE " 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 (%" PRIiSIZE ") reading from ser_get_buf_len()", __func__, tam);
			fatalx(EXIT_FAILURE, NO_SOLIS);
		}
		upsdebugx(2, "%s: received %" PRIiSIZE " 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);
	}

	if (!detected)
		fatalx(EXIT_FAILURE,  NO_SOLIS );

	switch (SolisModel) {
	case 10:
	case 11:
	case 12:
		Model = "Solis 1.0";
		break;
	case 13:
		Model = "Solis 1.5";
		break;
	case 14:
		Model = "Solis 2.0";
		break;
	case 15:
		Model = "Solis 3.0";
		break;
	case 16:
		Model = "Microsol Back-Ups BZ1200-BR";
		break;
	default:
		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);

	print_info();
}

static void get_update_info(void) {
	unsigned char temp[256];
	int isday, hourn, minn;
	ssize_t tam;

	/* time update and programable shutdown block */
	time_t tmt;
	struct tm *now;
	struct tm tmbuf;

	time(&tmt);
	now = localtime_r(&tmt, &tmbuf);
	hourn = now->tm_hour;
	minn = now->tm_min;
	weekn = now->tm_wday;

	if (isprogram || prgups == 3) {
		if (isprogram)
			isday = is_today(DaysStd, weekn);
		else
			isday = is_today( DaysStd, weekn);

		if (isday)
			printf(TODAY_DD, hourshut, minshut);

		if (
			(hourn == hourshut) &&
			(minn >= minshut) &&
			isday) {

			printf( SHUT_NOW );
			progshut = 1;
		}
	}
	/* programable shutdown end block */

	/* get update package */
	temp[0] = 0; /* flush temp buffer */

	upsdebugx(3, "%s: requesting %" PRIuSIZE " bytes from ser_get_buf_len()", __func__, packet_size);
	tam = ser_get_buf_len(upsfd, temp, packet_size, 3, 0);

	if (tam < 0) {
		upsdebugx(0, "%s: Error (%" PRIiSIZE ") reading from ser_get_buf_len()", __func__, tam);
		fatalx(EXIT_FAILURE, NO_SOLIS);
	}

	upsdebugx(2, "%s: received %" PRIiSIZE " 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);

	comm_receive(temp, (size_t)tam);
}

static int instcmd(const char *cmdname, const char *extra) {
	/* May be used in logging below, but not as a command argument */
	NUT_UNUSED_VARIABLE(extra);
	upsdebug_INSTCMD_STARTING(cmdname, extra);

	if (!strcasecmp(cmdname, "shutdown.return")) {
		/* shutdown and restart */
		/* FIXME: check with HW if this is not
		 *  a "shutdown.reboot" instead (or also)? */
		upslog_INSTCMD_POWERSTATE_CHANGE(cmdname, extra);
		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) */
		upslog_INSTCMD_POWERSTATE_CHANGE(cmdname, extra);
		ser_send_char(upsfd, CMD_SHUT); /* 0xDD */
		/* ser_send_char(upsfd, ENDCHAR); */
		return STAT_INSTCMD_HANDLED;
	}

	upslog_INSTCMD_UNKNOWN(cmdname, extra);
	return STAT_INSTCMD_UNKNOWN;

}

void upsdrv_initinfo(void) {
	get_base_info();

	upsh.instcmd = instcmd;
}

void upsdrv_updateinfo(void) {
	get_update_info(); /* 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);
	dstate_setinfo("output.current", "%03.1f", OutCurrent);
	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 )
			send_shutdown();       /* 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();
}

/*! @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.
 */
void upsdrv_shutdown(void) {
	/* Only implement "shutdown.default"; do not invoke
	 * general handling of other `sdcommands` here */

	if (!SourceFail) {     /* on line */
		upslog_INSTCMD_POWERSTATE_CHANGE("shutdown.return", (char *)NULL);
		upslogx(LOG_NOTICE, "On line, sending shutdown+return command...\n");
		ser_send_char(upsfd, CMD_SHUTRET );
		/* Seems AKA: instcmd("shutdown.return", NULL); */
	} else {
		upslog_INSTCMD_POWERSTATE_CHANGE("shutdown.stayoff", (char *)NULL);
		upslogx(LOG_NOTICE, "On battery, sending normal shutdown command...\n");
		ser_send_char(upsfd, CMD_SHUT);
		/* Seems AKA: instcmd("shutdown.stayoff", NULL); */
	}
}

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 programming and save UPS power on/off\n");
	printf(" These are valid only if prgshut = 2 or 3\n");
}

/* optionally tweak prognames[] entries */
void upsdrv_tweak_prognames(void)
{
}

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);
}
