/* metasys.c - driver for Meta System UPS

   Copyright (C) 2004  Fabio Di Niro <fabio.diniro@email.it>

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

/* Uncomment if you want to read additional Meta System UPS data */
/*
#define EXTRADATA
*/

#include "main.h"
#include "serial.h"
#include "nut_float.h"
#include "nut_stdint.h"

#define DRIVER_NAME	"Metasystem UPS driver"
#define DRIVER_VERSION	"0.14"

/* driver description structure */
upsdrv_info_t upsdrv_info = {
	DRIVER_NAME,
	DRIVER_VERSION,
	"Fabio Di Niro <fabio.diniro@email.it>",
	DRV_STABLE,
	{ NULL }
};

/* Autorestart flag */
static int autorestart = 0;
static int nominal_power = 0;

/* ups commands */
#define UPS_INFO 			0x00
#define UPS_OUTPUT_DATA 		0x01
#define UPS_INPUT_DATA 			0x02
#define UPS_STATUS 			0x03
#define UPS_BATTERY_DATA 		0x04
#define UPS_HISTORY_DATA		0x05
#define UPS_GET_SCHEDULING		0x06
#define UPS_EVENT_LIST			0x07
#define UPS_GET_TIMES_ON_BATTERY	0x08
#define UPS_GET_NEUTRAL_SENSE		0x09
#define UPS_SET_SCHEDULING		0x0a
#define UPS_SET_NEUTRAL_SENSE		0x0b
#define UPS_SET_TIMES_ON_BATTERY	0x0c
#define UPS_SET_BUZZER_MUTE		0x0d
#define UPS_SET_BATTERY_TEST		0x0e

static int instcmd(const char *cmdname, const char *extra);
static void send_zeros(void);

/*
	Metasystem UPS data transfer are made with packet of the format:
	STX		DATA_LENGTH		DATA		CHECKSUM
	where:
	STX is 0x02 and is the start of transmission byte
	DATA_LENGTH is number of data bytes + the checksum byte
	DATA ......
	CHECKSUM is the sum modulus 256 of all DATA bytes + DATA_LENGTH

	The answer from the UPS have the same packet format and the first
	data byte is equal to the command that the ups is answering to
*/
static uint16_t get_word(unsigned char *buffer) {		/* return an integer reading a word in the supplied buffer */
	unsigned char a, b;
	uint16_t result;

	a = buffer[0];
	b = buffer[1];
	result = b*256 + a;
	return result;
}


static uint32_t get_long(unsigned char *buffer) {	/* return a long integer reading 4 bytes in the supplied buffer */
	unsigned char a, b, c, d;
	uint32_t result;
	a=buffer[0];
	b=buffer[1];
	c=buffer[2];
	d=buffer[3];
	result = (256*256*256*d) + (256*256*c) + (256*b) + a;
	return result;
}

static float get_word_float(unsigned char *buffer) {
	/* return a float converted after reading a word in the supplied buffer */
	/* NOTE: This started as a wrapper for legacy logic that directly assigned
	 *   float_num = get_word(...)
	 * in code below. No idea if the protocol really sends 16-bit floats.
	 * FWIW, bcmxcp.c copied get_word() and get_long() from metasys.c but did
	 * implement a get_float() for IEEE-754 32-bit values.
	 */
	return (float)(int16_t)get_word(buffer);
}


static void send_zeros(void) {				/* send 100 times the value 0x00.....it seems to be used for resetting */
	unsigned char buf[100];				/* the ups serial port */

	memset(buf, '\0', sizeof(buf));
	ser_send_buf(upsfd, buf, sizeof(buf));
}


/* was used just for the debug process */
static void dump_buffer(unsigned char *buffer, int buf_len) {
	int i;
	for (i = 0; i < buf_len; i++) {
		printf("byte %d: %x\n", i, buffer[i]);
	}
	return;
}

/* send a read command to the UPS, it retries 5 times before give up
 * it's a 4 byte request (STX, LENGTH, COMMAND and CHECKSUM) */
static void send_read_command(unsigned char command) {
	int retry;
	ssize_t sent;
	unsigned char buf[4];

	retry = 0;
	sent = 0;
	while ((sent != 4) && (retry < 5)) {
		buf[0]=0x02; 			/* STX Start of Transmission */
		buf[1]=0x02;			/* data length(data + checksum byte) */
		buf[2]=command;			/* command to send */
		buf[3]=buf[1] + buf[2];	/* checksum (sum modulus 256 of data bytes + length) */
		if (retry == 4) send_zeros();	/* last retry is preceded by a serial reset...*/
		sent = ser_send_buf(upsfd, buf, 4);
		retry += 1;
	}
}

/* send a write command to the UPS, the write command and the value to be written are passed
 * with a char* buffer
 * it retries 5 times before give up */
static void send_write_command(unsigned char *command, size_t command_length) {
	int retry, checksum;
	ssize_t sent;
	size_t i;
	unsigned char raw_buf[255];

	/* prepares the raw data */
	raw_buf[0] = 0x02;		/* STX byte */
	raw_buf[1] = (unsigned char)(command_length + 1);		/* data length + checksum */
	memcpy(raw_buf+2, command, command_length);
	command_length += 2;

	/* calculate checksum */
	checksum = 0;
	for (i = 1; i < command_length; i++) checksum += raw_buf[i];
	checksum = checksum % 256;
	raw_buf[command_length] = (unsigned char)checksum;
	command_length +=1;

	assert (command_length < INT_MAX);
	retry = 0;
	sent = 0;
	while ((sent != (int)(command_length)) && (retry < 5)) {
		if (retry == 4) send_zeros();	/* last retry is preceded by a serial reset... */
		sent = ser_send_buf(upsfd, raw_buf, (command_length));
		if (sent < 0) ser_comm_fail("Error sending command %d\n", raw_buf[2]);
		if (sent != (int)(command_length)) printf("Error sending command %d\n", raw_buf[2]);
		retry += 1;
	}
}

/* get the answer of a command from the ups */
static int get_answer(unsigned char *data) {
	unsigned char my_buf[255];	/* packet has a maximum length of 256 bytes */
	unsigned char packet_length, checksum, i;
	ssize_t res;

	/* Read STX byte */
	res = ser_get_char(upsfd, my_buf, 1, 0);
	if (res < 1) {
		ser_comm_fail("Receive error (STX): %" PRIiSIZE "!!!\n", res);
		return -1;
	}
	if (my_buf[0] != 0x02) {
		ser_comm_fail("Receive error (STX): packet not on start!!\n");
		return -1;
	}
	/* Read data length byte */
	res = ser_get_char(upsfd, my_buf, 1, 0);
	if (res < 1) {
		ser_comm_fail("Receive error (length): %" PRIiSIZE "!!!\n", res);
		return -1;
	}
	packet_length = my_buf[0];
	if (packet_length < 2) {
		ser_comm_fail("Receive error (length): packet length %d!!!\n", packet_length);
		return -1;
	}
	/* Try to read all the remainig bytes (packet_length) */
	res = ser_get_buf_len(upsfd, my_buf, packet_length, 1, 0);
	if (res != packet_length) {
		ser_comm_fail("Receive error (data): got %" PRIiSIZE " bytes instead of %d!!!\n", res, packet_length);
		return -1;
	}

	/* now we have the whole answer from the ups, we can checksum it
	 * checksum byte is equal to the sum modulus 256 of all the data bytes + packet_length
	 * (no STX no checksum byte itself) */
	checksum = packet_length;
	for (i = 0; i < (packet_length - 1); i++) checksum += my_buf[i];
	checksum = checksum % 256;
	if (my_buf[packet_length-1] != checksum) {
		ser_comm_fail("checksum error! got %x instead of %x, received %d bytes \n", my_buf[packet_length - 1], checksum, packet_length);
		dump_buffer(my_buf, packet_length);
		return -1;
	}
	packet_length-=1;		/* get rid of the checksum byte */
	memcpy(data, my_buf, packet_length);
	return packet_length;
}

/* send a read command and try get the answer, if something fails, it retries (5 times max)
 * if it is on the 4th or 5th retry, it will flush the serial before sending commands
 * it returns the length of the received answer or -1 in case of failure */
static int command_read_sequence(unsigned char command, unsigned char *data) {
	int bytes_read = 0;
	int retry = 0;

	while ((bytes_read < 1) && (retry < 5)) {
		send_read_command(command);
		bytes_read = get_answer(data);
		if (retry > 2) ser_flush_in(upsfd, "", 0);
		retry += 1;
	}
	if ((data[0] != command) || (retry == 5)) {
		ser_comm_fail("Error executing command %d\n", command);
		dstate_datastale();
		return -1;
	}
	ser_comm_good();
	return bytes_read;
}

/* send a write command and try get the answer, if something fails, it retries (5 times max)
 * if it is on the 4th or 5th retry, it will flush the serial before sending commands
 * it returns the length of the received answer or -1 in case of failure */
static int command_write_sequence(unsigned char *command, size_t command_length, unsigned char *answer) {
	int bytes_read = 0;
	int retry = 0;

	while ((bytes_read < 1) && (retry < 5)) {
		send_write_command(command, command_length);
		bytes_read = get_answer(answer);
		if (retry > 2) ser_flush_in(upsfd, "", 0);
		retry += 1;
	}
	if ((answer[0] != command[0]) || (retry == 5)) {
		ser_comm_fail("Error executing command N.%d\n", command[0]);
		dstate_datastale();
		return -1;
	}
	ser_comm_good();
	return bytes_read;
}

#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP_BESIDEFUNC) && (!defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP_INSIDEFUNC) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS_BESIDEFUNC) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE_BESIDEFUNC) )
# pragma GCC diagnostic push
#endif
#if (!defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP_INSIDEFUNC) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS_BESIDEFUNC)
# pragma GCC diagnostic ignored "-Wtype-limits"
#endif
#if (!defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP_INSIDEFUNC) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE_BESIDEFUNC)
# pragma GCC diagnostic ignored "-Wtautological-constant-out-of-range-compare"
#endif
void upsdrv_initinfo(void)
{
	unsigned char my_answer[255];
	char serial[13];
	int res, i;

	/* Reset comms */
	send_zeros();

	/* Initial setup of variables */
#ifdef EXTRADATA
	 dstate_setinfo("output.power", "%d", -1);
	dstate_setflags("output.power", ST_FLAG_RW);
#endif
	 dstate_setinfo("output.voltage", "%d", -1);
	dstate_setflags("output.voltage", ST_FLAG_RW);
	 dstate_setinfo("output.current", "%d", -1);
	dstate_setflags("output.current", ST_FLAG_RW);
#ifdef EXTRADATA
	 dstate_setinfo("output.current.peak", "%2.2f", -1);
	dstate_setflags("output.current.peak", ST_FLAG_RW);
	 dstate_setinfo("input.power", "%d", -1);
	dstate_setflags("input.power", ST_FLAG_RW);
#endif
	 dstate_setinfo("input.voltage", "%d", -1);
	dstate_setflags("input.voltage", ST_FLAG_RW);
#ifdef EXTRADATA
	 dstate_setinfo("input.current", "%2.2f", -1);
	dstate_setflags("input.current", ST_FLAG_RW);
	 dstate_setinfo("input.current.peak", "%2.2f", -1);
	dstate_setflags("input.current.peak", ST_FLAG_RW);
#endif
	 dstate_setinfo("battery.voltage", "%d", -1);
	dstate_setflags("battery.voltage", ST_FLAG_RW);
#ifdef EXTRADATA
	 dstate_setinfo("battery.voltage.low", "%2.2f", -1);
	dstate_setflags("battery.voltage.low", ST_FLAG_RW);
	 dstate_setinfo("battery.voltage.exhaust", "%2.2f", -1);
	dstate_setflags("battery.voltage.exhaust", ST_FLAG_RW);
	 dstate_setinfo("ups.total.runtime", "retrieving...");
	dstate_setflags("ups.total.runtime", ST_FLAG_STRING | ST_FLAG_RW);
	  dstate_setaux("ups.total.runtime", 20);
	 dstate_setinfo("ups.inverter.runtime", "retrieving...");
	dstate_setflags("ups.inverter.runtime", ST_FLAG_STRING | ST_FLAG_RW);
	  dstate_setaux("ups.inverter.runtime", 20);
	 dstate_setinfo("ups.inverter.interventions", "%d", -1);
	dstate_setflags("ups.inverter.interventions", ST_FLAG_RW);
	 dstate_setinfo("battery.full.discharges", "%d", -1);
	dstate_setflags("battery.full.discharges", ST_FLAG_RW);
	 dstate_setinfo("ups.bypass.interventions", "%d", -1);
	dstate_setflags("ups.bypass.interventions", ST_FLAG_RW);
	 dstate_setinfo("ups.overheatings", "%d", -1);
	dstate_setflags("ups.overheatings", ST_FLAG_RW);
#endif
	 dstate_setinfo("ups.load", "%d", -1);
	dstate_setflags("ups.load", ST_FLAG_RW);
	 dstate_setinfo("ups.delay.shutdown", "%d", -1);
	dstate_setflags("ups.delay.shutdown", ST_FLAG_RW);
	 dstate_setinfo("ups.delay.start", "%d", -1);
	dstate_setflags("ups.delay.start", ST_FLAG_RW);
	 dstate_setinfo("ups.temperature", "%d", -1);
	dstate_setflags("ups.temperature", ST_FLAG_RW);
	 dstate_setinfo("ups.test.result", "not yet done...");
	dstate_setflags("ups.test.result", ST_FLAG_STRING | ST_FLAG_RW);
	  dstate_setaux("ups.test.result", 20);

	/* UPS INFO READ */
	res = command_read_sequence(UPS_INFO, my_answer);
	if (res < 0) fatal_with_errno(EXIT_FAILURE, "Could not communicate with the ups");
	/* the manufacturer is hard coded into the driver, the model type is in the second
		byte of the answer, the third byte identifies the model version */
	dstate_setinfo("ups.mfr", "Meta System");
	i = my_answer[1] * 10 + my_answer[2];
	switch (i) {
		case 11:
			dstate_setinfo("ups.model", "%s", "HF Line (1 board)");
			nominal_power = 630;
			break;
		case 12:
			dstate_setinfo("ups.model", "%s", "HF Line (2 boards)");
			nominal_power = 1260;
			break;
		case 13:
			dstate_setinfo("ups.model", "%s", "HF Line (3 boards)");
			nominal_power = 1890;
			break;
		case 14:
			dstate_setinfo("ups.model", "%s", "HF Line (4 boards)");
			nominal_power = 2520;
			break;
		case 21:
			dstate_setinfo("ups.model", "%s", "ECO Network 750/1000");
			nominal_power = 500;
			break;
		case 22:
			dstate_setinfo("ups.model", "%s", "ECO Network 1050/1500");
			nominal_power = 700;
			break;
		case 23:
			dstate_setinfo("ups.model", "%s", "ECO Network 1500/2000");
			nominal_power = 1000;
			break;
		case 24:
			dstate_setinfo("ups.model", "%s", "ECO Network 1800/2500");
			nominal_power = 1200;
			break;
		case 25:
			dstate_setinfo("ups.model", "%s", "ECO Network 2100/3000");
			nominal_power = 1400;
			break;
		case 31:
			dstate_setinfo("ups.model", "%s", "ECO 308");
			nominal_power = 500;
			break;
		case 32:
			dstate_setinfo("ups.model", "%s", "ECO 311");
			nominal_power = 700;
			break;
		case 44:
			dstate_setinfo("ups.model", "%s", "HF Line (4 boards)/2");
			nominal_power = 2520;
			break;
		case 45:
			dstate_setinfo("ups.model", "%s", "HF Line (5 boards)/2");
			nominal_power = 3150;
			break;
		case 46:
			dstate_setinfo("ups.model", "%s", "HF Line (6 boards)/2");
			nominal_power = 3780;
			break;
		case 47:
			dstate_setinfo("ups.model", "%s", "HF Line (7 boards)/2");
			nominal_power = 4410;
			break;
		case 48:
			dstate_setinfo("ups.model", "%s", "HF Line (8 boards)/2");
			nominal_power = 5040;
			break;
		case 51:
			dstate_setinfo("ups.model", "%s", "HF Millennium 810");
			nominal_power = 700;
			break;
		case 52:
			dstate_setinfo("ups.model", "%s", "HF Millennium 820");
			nominal_power = 1400;
			break;
		case 61:
			dstate_setinfo("ups.model", "%s", "HF TOP Line 910");
			nominal_power = 700;
			break;
		case 62:
			dstate_setinfo("ups.model", "%s", "HF TOP Line 920");
			nominal_power = 1400;
			break;
		case 63:
			dstate_setinfo("ups.model", "%s", "HF TOP Line 930");
			nominal_power = 2100;
			break;
		case 64:
			dstate_setinfo("ups.model", "%s", "HF TOP Line 940");
			nominal_power = 2800;
			break;
		case 74:
			dstate_setinfo("ups.model", "%s", "HF TOP Line 940/2");
			nominal_power = 2800;
			break;
		case 75:
			dstate_setinfo("ups.model", "%s", "HF TOP Line 950/2");
			nominal_power = 3500;
			break;
		case 76:
			dstate_setinfo("ups.model", "%s", "HF TOP Line 960/2");
			nominal_power = 4200;
			break;
		case 77:
			dstate_setinfo("ups.model", "%s", "HF TOP Line 970/2");
			nominal_power = 4900;
			break;
		case 78:
			dstate_setinfo("ups.model", "%s", "HF TOP Line 980/2");
			nominal_power = 5600;
			break;
		case 81:
			dstate_setinfo("ups.model", "%s", "ECO 508");
			nominal_power = 500;
			break;
		case 82:
			dstate_setinfo("ups.model", "%s", "ECO 511");
			nominal_power = 700;
			break;
		case 83:
			dstate_setinfo("ups.model", "%s", "ECO 516");
			nominal_power = 1000;
			break;
		case 84:
			dstate_setinfo("ups.model", "%s", "ECO 519");
			nominal_power = 1200;
			break;
		case 85:
			dstate_setinfo("ups.model", "%s", "ECO 522");
			nominal_power = 1400;
			break;
		case 91:
			dstate_setinfo("ups.model", "%s", "ECO 305 / Harviot 530 SX");
			nominal_power = 330;
			break;
		case 92:
			dstate_setinfo("ups.model", "%s", "ORDINATORE 2");
			nominal_power = 330;
			break;
		case 93:
			dstate_setinfo("ups.model", "%s", "Harviot 730 SX");
			nominal_power = 430;
			break;
		case 101:
			dstate_setinfo("ups.model", "%s", "ECO 308 SX / SX Interactive / Ordinatore");
			nominal_power = 500;
			break;
		case 102:
			dstate_setinfo("ups.model", "%s", "ECO 311 SX / SX Interactive");
			nominal_power = 700;
			break;
		case 111:
			dstate_setinfo("ups.model", "%s", "ally HF 800 / BI-TWICE 800");
			nominal_power = 560;
			break;
		case 112:
			dstate_setinfo("ups.model", "%s", "ally HF 1600");
			nominal_power = 1120;
			break;
		case 121:
			dstate_setinfo("ups.model", "%s", "ally HF 1000 / BI-TWICE 1000");
			nominal_power = 700;
			break;
		case 122:
			dstate_setinfo("ups.model", "%s", "ally HF 2000");
			nominal_power = 1400;
			break;
		case 131:
			dstate_setinfo("ups.model", "%s", "ally HF 1250 / BI-TWICE 1250");
			nominal_power = 875;
			break;
		case 132:
			dstate_setinfo("ups.model", "%s", "ally HF 2500");
			nominal_power = 1750;
			break;
		case 141:
			dstate_setinfo("ups.model", "%s", "Megaline 1250");
			nominal_power = 875;
			break;
		case 142:
			dstate_setinfo("ups.model", "%s", "Megaline 2500");
			nominal_power = 1750;
			break;
		case 143:
			dstate_setinfo("ups.model", "%s", "Megaline 3750");
			nominal_power = 2625;
			break;
		case 144:
			dstate_setinfo("ups.model", "%s", "Megaline 5000");
			nominal_power = 3500;
			break;
		case 154:
			dstate_setinfo("ups.model", "%s", "Megaline 5000 / 2");
			nominal_power = 3500;
			break;
		case 155:
			dstate_setinfo("ups.model", "%s", "Megaline 6250 / 2");
			nominal_power = 4375;
			break;
		case 156:
			dstate_setinfo("ups.model", "%s", "Megaline 7500 / 2");
			nominal_power = 5250;
			break;
		case 157:
			dstate_setinfo("ups.model", "%s", "Megaline 8750 / 2");
			nominal_power = 6125;
			break;
		case 158:
			dstate_setinfo("ups.model", "%s", "Megaline 10000 / 2");
			nominal_power = 7000;
			break;

		default:
			fatal_with_errno(EXIT_FAILURE, "Unknown UPS");
	}

	/* Get the serial number; res >=0 per check above */
#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE) )
/* Note for gating macros above: unsuffixed HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP
 * means support of contexts both inside and outside function body, so the push
 * above and pop below (outside this finction) are not used.
 */
# pragma GCC diagnostic push
#endif
#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS
/* Note that the individual warning pragmas for use inside function bodies
 * are named without a _INSIDEFUNC suffix, for simplicity and legacy reasons
 */
# pragma GCC diagnostic ignored "-Wtype-limits"
#endif
#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE
# pragma GCC diagnostic ignored "-Wtautological-constant-out-of-range-compare"
#endif
	if (res < 7 || (unsigned long long int)res >= SIZE_MAX)
#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE) )
# pragma GCC diagnostic pop
#endif
		fatal_with_errno(EXIT_FAILURE, "Could not communicate with the ups");
	memcpy(serial, my_answer + 7, (size_t)(res - 7));
	/* serial number start from the 8th byte */
	serial[12]='\0';		/* terminate string */
	dstate_setinfo("ups.serial", "%s", serial);

	/* get the ups firmware. The major number is in the 5th byte, the minor is in the 6th */
	dstate_setinfo("ups.firmware", "%u.%u", my_answer[5], my_answer[6]);

	printf("Detected %s [%s] v.%s on %s\n",
		dstate_getinfo("ups.model"), dstate_getinfo("ups.serial"),
		dstate_getinfo("ups.firmware"), device_path);

	/* Add instant commands */
	dstate_addcmd("shutdown.return");
	dstate_addcmd("shutdown.stayoff");
	dstate_addcmd("shutdown.stop");
	dstate_addcmd("test.failure.start");
	dstate_addcmd("test.failure.stop");
	dstate_addcmd("test.battery.start");
	dstate_addcmd("beeper.enable");
	dstate_addcmd("beeper.mute");
	dstate_addcmd("beeper.on");
	dstate_addcmd("beeper.off");

	upsh.instcmd = instcmd;
}
#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP_BESIDEFUNC) && (!defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP_INSIDEFUNC) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS_BESIDEFUNC) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE_BESIDEFUNC) )
# pragma GCC diagnostic pop
#endif

void upsdrv_updateinfo(void)
{
	int res;
	uint16_t int_num;
#ifdef EXTRADATA
	int day, hour, minute;
#endif
	float float_num;
	uint32_t long_num;
	unsigned char my_answer[255];

	/* GET Output data */
	res = command_read_sequence(UPS_OUTPUT_DATA, my_answer);
	if (res < 0) {
		printf("Could not communicate with the ups");
		dstate_datastale();
	} else {
		/* Active power */
		int_num = get_word(&my_answer[1]);
		if (nominal_power != 0) {
			float_num = (float)((int_num * 100)/nominal_power);
			dstate_setinfo("ups.load", "%2.1f", float_num);
		} else {
			dstate_setinfo("ups.load", "%s", "not available");
		}
#ifdef EXTRADATA
		dstate_setinfo("output.power", "%u", int_num);
#endif
		/* voltage */
		int_num = get_word(&my_answer[3]);
		if ((int16_t)int_num > 0) dstate_setinfo("output.voltage", "%u", int_num);
		if ((int16_t)int_num == -1) dstate_setinfo("output.voltage", "%s", "overrange");
		if ((int16_t)int_num == -2) dstate_setinfo("output.voltage", "%s", "not available");
		/* current */
		float_num = get_word_float(&my_answer[5]);
		if (f_equal(float_num, -1.0)) dstate_setinfo("output.current", "%s", "overrange");
		if (f_equal(float_num, -2.0)) dstate_setinfo("output.current", "%s", "not available");
		if (float_num > 0) {
			float_num = (float)(float_num/10);
			dstate_setinfo("output.current", "%2.2f", float_num);
		}
#ifdef EXTRADATA
		/* peak current */
		float_num = get_word_float(&my_answer[7]);
		if (f_equal(float_num, -1.0)) dstate_setinfo("output.current.peak", "%s", "overrange");
		if (f_equal(float_num, -2.0)) dstate_setinfo("output.current.peak", "%s", "not available");
		if (float_num > 0) {
			float_num = (float)(float_num/10);
			dstate_setinfo("output.current.peak", "%2.2f", float_num);
		}

#endif
	}

	/* GET Input data */
	res = command_read_sequence(UPS_INPUT_DATA, my_answer);
	if (res < 0){
		printf("Could not communicate with the ups");
		dstate_datastale();
	} else {
#ifdef EXTRADATA
		/* Active power */
		int_num = get_word(&my_answer[1]);
		if ((int16_t)int_num > 0) dstate_setinfo("input.power", "%u", int_num);
		if ((int16_t)int_num == -1) dstate_setinfo("input.power", "%s", "overrange");
		if ((int16_t)int_num == -2) dstate_setinfo("input.power", "%s", "not available");
#endif
		/* voltage */
		int_num = get_word(&my_answer[3]);
		if ((int16_t)int_num > 0) dstate_setinfo("input.voltage", "%u", int_num);
		if ((int16_t)int_num == -1) dstate_setinfo("input.voltage", "%s", "overrange");
		if ((int16_t)int_num == -2) dstate_setinfo("input.voltage", "%s", "not available");
#ifdef EXTRADATA
		/* current */
		float_num = get_word_float(&my_answer[5]);
		if (float_num == -1) dstate_setinfo("input.current", "%s", "overrange");
		if (float_num == -2) dstate_setinfo("input.current", "%s", "not available");
		if (float_num > 0) {
			float_num = (float)(float_num/10);
			dstate_setinfo("input.current", "%2.2f", float_num);
		}
		/* peak current */
		float_num = get_word_float(&my_answer[7]);
		if (float_num == -1) dstate_setinfo("input.current.peak", "%s", "overrange");
		if (float_num == -2) dstate_setinfo("input.current.peak", "%s", "not available");
		if (float_num > 0) {
			float_num = (float)(float_num/10);
			dstate_setinfo("input.current.peak", "%2.2f", float_num);
		}
#endif
	}


	/* GET Battery data */
	res = command_read_sequence(UPS_BATTERY_DATA, my_answer);
	if (res < 0) {
		printf("Could not communicate with the ups");
		dstate_datastale();
	} else {
		/* Actual value */
		float_num = get_word_float(&my_answer[1]);
		float_num = (float)(float_num/10);
		dstate_setinfo("battery.voltage", "%2.2f", float_num);
#ifdef EXTRADATA
		/* reserve threshold */
		float_num = get_word_float(&my_answer[3]);
		float_num = (float)(float_num/10);
		dstate_setinfo("battery.voltage.low", "%2.2f", float_num);
		/* exhaust threshold */
		float_num = get_word_float(&my_answer[5]);
		float_num = (float)(float_num/10);
		dstate_setinfo("battery.voltage.exhaust", "%2.2f", float_num);
#endif
	}

#ifdef EXTRADATA
	/* GET history data */
	res = command_read_sequence(UPS_HISTORY_DATA, my_answer);
	if (res < 0) {
		printf("Could not communicate with the ups");
		dstate_datastale();
	} else {
		/* ups total runtime */
		long_num = get_long(&my_answer[1]);
		day = (int)(long_num / 86400);
		long_num -= (long)(day*86400);
		hour = (int)(long_num / 3600);
		long_num -= (long)(hour*3600);
		minute = (int)(long_num / 60);
		long_num -= (minute*60);
		dstate_setinfo("ups.total.runtime", "%d days %dh %dm %lus", day, hour, minute, long_num);

		/* ups inverter runtime */
		long_num = get_long(&my_answer[5]);
		day = (int)(long_num / 86400);
		long_num -= (long)(day*86400);
		hour = (int)(long_num / 3600);
		long_num -= (long)(hour*3600);
		minute = (int)(long_num / 60);
		long_num -= (minute*60);
		dstate_setinfo("ups.inverter.runtime", "%d days %dh %dm %lus", day, hour, minute, long_num);
		/* ups inverter interventions */
		dstate_setinfo("ups.inverter.interventions", "%u", get_word(&my_answer[9]));
		/* battery full discharges */
		dstate_setinfo("battery.full.discharges", "%u", get_word(&my_answer[11]));
		/* ups bypass / stabilizer interventions */
		int_num = get_word(&my_answer[13]);
		if ((int16_t)int_num == -2) dstate_setinfo("ups.bypass.interventions", "%s", "not avaliable");
		if ((int16_t)int_num >= 0) dstate_setinfo("ups.bypass.interventions", "%u", int_num);
		/* ups overheatings */
		int_num = get_word(&my_answer[15]);
		if ((int16_t)int_num == -2) dstate_setinfo("ups.overheatings", "%s", "not avalilable");
		if ((int16_t)int_num >= 0) dstate_setinfo("ups.overheatings", "%u", int_num);
	}
#endif

	/* GET times on battery */
	res = command_read_sequence(UPS_GET_TIMES_ON_BATTERY, my_answer);
	if (res < 0) {
		printf("Could not communicate with the ups");
		dstate_datastale();
	} else {
		autorestart = my_answer[5];
	}


	/* GET schedule */
	res = command_read_sequence(UPS_GET_SCHEDULING, my_answer);
	if (res < 0) {
		printf("Could not communicate with the ups");
		dstate_datastale();
	} else {
		/* time remaining to shutdown */
		long_num = get_long(&my_answer[1]);
		if ((int32_t)long_num == -1) {
			dstate_setinfo("ups.delay.shutdown", "%d", 120);
		} else {
			dstate_setinfo("ups.delay.shutdown", "%lu", (unsigned long)long_num);
		}
		/* time remaining to restart  */
		long_num = get_long(&my_answer[5]);
		if ((int32_t)long_num == -1) {
			dstate_setinfo("ups.delay.start", "%d", 0);
		} else {
			dstate_setinfo("ups.delay.start", "%lu", (unsigned long)long_num);
		}
	}


	/* GET ups status */
	res = command_read_sequence(UPS_STATUS, my_answer);
	if (res < 0) {
		printf("Could not communicate with the ups");
		dstate_datastale();
	} else {
		/* ups temperature */
		my_answer[3] -=128;
		if (my_answer[3] > 0) {
			dstate_setinfo("ups.temperature", "%d", my_answer[3]);
		} else {
			dstate_setinfo("ups.temperature", "%s", "not available");
		}
		/* Status */
		status_init();
		switch (my_answer[1]) {	/* byte 1 = STATUS */
			case 0x00:
				status_set("OL"); /* running on mains power */
				break;
			case 0x01:
				status_set("OB"); /* running on battery power */
				break;
			case 0x02:
				status_set("LB"); /* battery reserve */
				break;
			case 0x03:			/* bypass engaged */
			case 0x04:			/* manual bypass engaged */
				status_set("BY");
				break;
			default:
				printf("status unknown \n");
				break;
		}
		switch (my_answer[2]) {		/* byte 2 = FAULTS */
			case 0x00:				/* all right */
				break;
			case 0x01:				/* overload */
				status_set("OVER");
				break;
			case 0x02:				/* overheat */
				break;
			case 0x03:				/* hardware fault */
				break;
			case 0x04:				/* battery charger failure (overcharging) */
				break;
			case 0x05:				/* replace batteries */
				status_set("RB");
				break;
			default:
				printf("status unknown \n");
				break;
		}
		status_commit();
		dstate_dataok();
	}
	return;
}

void upsdrv_shutdown(void)
{
	/* Only implement "shutdown.default"; do not invoke
	 * general handling of other `sdcommands` here */

	unsigned char	command[10], answer[10];

	/* Ensure that the ups is configured for automatically
	 * restart after a complete battery discharge
	 * and when the power comes back after a shutdown.
	 * Similar code to "shutdown.restart" but different timeouts.
	 */
	if (! autorestart) {
		command[0]=UPS_SET_TIMES_ON_BATTERY;
		command[1]=0x00;					/* max time on  */
		command[2]=0x00;					/* battery */

		command[3]=0x00;					/* max time after */
		command[4]=0x00;					/* battery reserve */

		command[5]=0x01;					/* autorestart after battery depleted enabled */
		command_write_sequence(command, 6, answer);
	}

	/* schedule a shutdown in 120 seconds */
	command[0]=UPS_SET_SCHEDULING;
	command[1]=0x96;					/* remaining  */
	command[2]=0x00;					/* time		 */
	command[3]=0x00;					/* to */
	command[4]=0x00;					/* shutdown 150 secs */

	/* restart time has been set to 1 instead of 0 for avoiding
		a bug in some ups firmware */
	command[5]=0x01;					/* programmed */
	command[6]=0x00;					/* time		 */
	command[7]=0x00;					/* to */
	command[8]=0x00;					/* restart 1 sec */
	command_write_sequence(command, 9, answer);

	/* you may have to check the line status since the commands
	 * for toggling power are frequently different for OL vs. OB */

	/* OL: this must power cycle the load if possible */

	/* OB: the load must remain off until the power returns */
}


static int instcmd(const char *cmdname, const char *extra)
{
	unsigned char command[10], answer[10];
	int res;

	if (!strcasecmp(cmdname, "beeper.off")) {
		/* compatibility mode for old command */
		upslogx(LOG_WARNING,
			"The 'beeper.off' command has been renamed to 'beeper.mute' for this driver");
		return instcmd("beeper.mute", NULL);
	}

	if (!strcasecmp(cmdname, "beeper.on")) {
		/* compatibility mode for old command */
		upslogx(LOG_WARNING,
			"The 'beeper.on' command has been renamed to 'beeper.enable'");
		return instcmd("beeper.enable", NULL);
	}

	/* 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")) {
		upslog_INSTCMD_POWERSTATE_CHANGE(cmdname, extra);

		/* Same stuff as upsdrv_shutdown() */
		if (! autorestart) {
			command[0]=UPS_SET_TIMES_ON_BATTERY;
			command[1]=0x00;					/* max time on  */
			command[2]=0x00;					/* battery */
			command[3]=0x00;					/* max time after */
			command[4]=0x00;					/* battery reserve */
			command[5]=0x01;					/* autorestart after battery depleted enabled */
			command_write_sequence(command, 6, answer);
		}
		/* schedule a shutdown in 30 seconds */
		command[0]=UPS_SET_SCHEDULING;
		command[1]=0x1e;					/* remaining  */
		command[2]=0x00;					/* time		 */
		command[3]=0x00;					/* to */
		command[4]=0x00;					/* shutdown 30 secs */

		command[5]=0x01;					/* programmed */
		command[6]=0x00;					/* time		 */
		command[7]=0x00;					/* to */
		command[8]=0x00;					/* restart 1 sec */
		command_write_sequence(command, 9, answer);
		return STAT_INSTCMD_HANDLED;
	}

	if (!strcasecmp(cmdname, "shutdown.stayoff")) {
		upslog_INSTCMD_POWERSTATE_CHANGE(cmdname, extra);

		/* schedule a shutdown in 30 seconds with no restart (-1) */
		command[0]=UPS_SET_SCHEDULING;
		command[1]=0x1e;					/* remaining  */
		command[2]=0x00;					/* time		 */
		command[3]=0x00;					/* to */
		command[4]=0x00;					/* shutdown 150 secs */

		command[5]=0xff;					/* programmed */
		command[6]=0xff;					/* time		 */
		command[7]=0xff;					/* to */
		command[8]=0xff;					/* restart -1 no restart*/
		command_write_sequence(command, 9, answer);
		return STAT_INSTCMD_HANDLED;
	}

	if (!strcasecmp(cmdname, "shutdown.stop")) {
		upslog_INSTCMD_POWERSTATE_MAYBE(cmdname, extra);

		/* set shutdown and restart time to -1 (no shutdown, no restart) */
		command[0]=UPS_SET_SCHEDULING;
		command[1]=0xff;					/* remaining  */
		command[2]=0xff;					/* time		 */
		command[3]=0xff;					/* to */
		command[4]=0xff;					/* shutdown -1 (no shutdown) */

		command[5]=0xff;					/* programmed */
		command[6]=0xff;					/* time		 */
		command[7]=0xff;					/* to */
		command[8]=0xff;					/* restart -1 no restart */
		command_write_sequence(command, 9, answer);
		return STAT_INSTCMD_HANDLED;
	}

	if (!strcasecmp(cmdname, "test.failure.start")) {
		upslog_INSTCMD_POWERSTATE_MAYBE(cmdname, extra);

		/* force ups on battery power */
		command[0]=UPS_SET_BATTERY_TEST;
		command[1]=0x01;
		/* 0 = perform battery test
		 * 1 = force UPS on battery power
		 * 2 = restore standard mode (mains power)
		 */
		command_write_sequence(command, 2, answer);
		return STAT_INSTCMD_HANDLED;
	}

	if (!strcasecmp(cmdname, "test.failure.stop")) {
		upslog_INSTCMD_POWERSTATE_MAYBE(cmdname, extra);

		/* restore standard mode (mains power) */
		command[0]=UPS_SET_BATTERY_TEST;
		command[1]=0x02;
		/* 0 = perform battery test
		 * 1 = force UPS on battery power
		 * 2 = restore standard mode (mains power)
		 */
		command_write_sequence(command, 2, answer);
		return STAT_INSTCMD_HANDLED;
	}

	if (!strcasecmp(cmdname, "test.battery.start")) {
		upslog_INSTCMD_POWERSTATE_MAYBE(cmdname, extra);

		/* launch battery test */
		command[0]=UPS_SET_BATTERY_TEST;
		command[1]=0x00;
		/* 0 = perform battery test
		 * 1 = force UPS on battery power
		 * 2 = restore standard mode (mains power)
		 */
		send_write_command(command, 2);
		sleep(15);
		res = get_answer(answer);
		switch (answer[1]) {		/* byte 1 = Test result */
			case 0x00:				/* all right */
				dstate_setinfo("ups.test.result", "OK");
				break;
			case 0x01:
				dstate_setinfo("ups.test.result", "Battery charge: 20%%");
				break;
			case 0x02:
				dstate_setinfo("ups.test.result", "Battery charge: 40%%");
				break;
			case 0x03:
				dstate_setinfo("ups.test.result", "Battery charge: 60%%");
				break;
			case 0x04:
				dstate_setinfo("ups.test.result", "Battery charge: 80%%");
				break;
			case 0x05:
				dstate_setinfo("ups.test.result", "Battery charge: 100%%");
				break;
			case 0xfe:
				dstate_setinfo("ups.test.result", "Bad battery pack: replace");
				break;
			default:
				dstate_setinfo("ups.test.result", "Impossible to test");
				break;
		}
		dstate_dataok();
		upslogx(LOG_NOTICE, "instcmd: test battery returned with %d bytes", res);
		upslogx(LOG_NOTICE, "test battery byte 1 = %x", answer[1]);
		return STAT_INSTCMD_HANDLED;
	}

	if (!strcasecmp(cmdname, "beeper.enable")) {
		/* set buzzer to not muted */
		command[0]=UPS_SET_BUZZER_MUTE;
		command[1]=0x00;
		/* 0 = not muted
		 * 1 = muted
		 * 2 = read current status
		 */
		command_write_sequence(command, 2, answer);
		return STAT_INSTCMD_HANDLED;
	}

	if (!strcasecmp(cmdname, "beeper.mute")) {
		/* set buzzer to muted */
		command[0]=UPS_SET_BUZZER_MUTE;
		command[1]=0x01;
		/* 0 = not muted
		 * 1 = muted
		 * 2 = read current status
		 */
		command_write_sequence(command, 2, answer);
		return STAT_INSTCMD_HANDLED;
	}

	upslog_INSTCMD_UNKNOWN(cmdname, extra);
	return STAT_INSTCMD_UNKNOWN;
}


void upsdrv_help(void)
{
}

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

/* list flags and values that you want to receive via -x */
void upsdrv_makevartable(void)
{
	/* allow '-x xyzzy' */
	/* addvar(VAR_FLAG, "xyzzy", "Enable xyzzy mode"); */

	/* allow '-x foo=<some value>' */
	/* addvar(VAR_VALUE, "foo", "Override foo setting"); */

}

void upsdrv_initups(void)
{
	upsfd = ser_open(device_path);
	ser_set_speed(upsfd, device_path, B2400);
}

void upsdrv_cleanup(void)
{
	ser_close(upsfd, device_path);
}
