/*
 * huawei-ups2000.c - Driver for Huawei UPS2000 (1kVA-3kVA)
 *
 * Note: Huawei UPS2000 (1kVA-3kVA) can be accessed via RS-232,
 * USB, or an optional RMS-MODBUS01B (RS-485) adapter. Only
 * RS-232 and USB are supported, RS-485 is not.
 *
 * The USB port on the UPS is implemented via a MaxLinear RX21V1410
 * USB-to-serial converter, and can be recongized as a standard
 * USB-CDC serial device. Unfortunately, the generic USB-CDC driver
 * is incompatible with the specific chip configuration and cannot
 * be used. A device-specific driver, "xr_serial", must be used.
 *
 * The driver has only been merged to Linux 5.12 or later, via the
 * "xr_serial" kernel module. When the UPS2000 is connected via USB
 * to a supported Linux system, you should see the following logs in
 * "dmesg".
 *
 *     xr_serial 1-1.2:1.1: xr_serial converter detected
 *     usb 1-1.2: xr_serial converter now attached to ttyUSB0
 *
 * The driver must be "xr_serial". If your system doesn't have the
 * necessary device driver, you will get this message instead:
 *
 *     cdc_acm 1-1.2:1.0: ttyACM0: USB ACM device
 *
 * On other operating systems, USB cannot be used due to the absence
 * of the driver. You must use connect UPS2000 to your computer via
 * RS-232, either directly or using an USB-to-RS-232 converter supported
 * by your Linux or BSD kernel.
 *
 * A document describing the protocol implemented by this driver can
 * be found online at:
 *
 *   https://support.huawei.com/enterprise/en/doc/EDOC1000110696
 *
 * Huawei UPS2000 driver implemented by
 *   Copyright (C) 2020, 2021 Yifeng Li <tomli@tomli.me>
 *   The author is not affiliated to Huawei or other manufacturers.
 *
 * 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
 */

#include "config.h"  /* must be the first header */

#include <stdbool.h>
#include <modbus.h>
#include "main.h"
#include "serial.h"

#define DRIVER_NAME	"NUT Huawei UPS2000 (1kVA-3kVA) RS-232 Modbus driver"
#define DRIVER_VERSION	"0.02"

#define CHECK_BIT(var,pos) ((var) & (1<<(pos)))
#define MODBUS_SLAVE_ID 1

/*
 * Known UPS models. We only attempt to load the driver if
 * the initial communication indicates the UPS is a known
 * model of the UPS2000 series.
 */
static const char *supported_model[] = {
	"UPS2000", "UPS2000A",
	NULL
};

/*
 * UPS2000 device identification. The information is obtained during
 * initial communication using Modbus command 0x2B (read device identi-
 * fication) to read the object 0x87 (device list). The object contains
 * a list of fields, each with a type, length, and value. The object is
 * parsed by ups2000_device_identification() and filled into the array
 * of struct ups2000_ident.
 *
 * Fields of interest are:
 *
 *     0x87, int32 (Device Count): Only one UPS unit is supported,
 *     the driver aborts if more than one device is detected.
 *
 *     0x88, string (Device Description of the 1st unit): This is a
 *     ASCII string that contains information about the 1st UPS unit.
 *     This string, again, contains a list of fields. They are parsed
 *     further into the array ups2000_desc.
 *
 */
#define UPS2000_IDENT_MAX_FIELDS 9
#define UPS2000_IDENT_MAX_LEN 128
#define UPS2000_IDENT_OFFSET
static struct {
	uint8_t type;
	uint8_t len;
	uint8_t val[UPS2000_IDENT_MAX_LEN];
} ups2000_ident[UPS2000_IDENT_MAX_FIELDS];

/*
 * UPS2000 device description. The information is initially obtained
 * as field 0x88 in the UPS2000 device identification. This field is
 * a semicolon seperated ASCII string that contains multiple fields.
 * It is parsed again by ups2000_device_identification() and filled
 * into the ups2000_desc[] 2D array. The first dimension is used as
 * a key to select the wanted field (defined in the following enmu,
 * the second dimension is a NULL-terminated ASCII string.
 *
 * Note that ups2000_desc[0] is deliberately unused, the array begins
 * at one, allowing mapping from UPS2000_DESC_* to ups2000_desc[]
 * directly without using offsets.
 */
#define UPS2000_DESC_MAX_FIELDS 9
#define UPS2000_DESC_MAX_LEN 128
enum {
	UPS2000_DESC_MODEL = 1,
	UPS2000_DESC_FIRMWARE_REV,
	UPS2000_DESC_PROTOCOL_REV,
	UPS2000_DESC_ESN,
	UPS2000_DESC_DEVICE_ID,    /* currently unused */
	UPS2000_DESC_PARALLEL_ID   /* currently unused */
};
static char ups2000_desc[UPS2000_DESC_MAX_FIELDS][UPS2000_DESC_MAX_LEN] = { { 0 } };

/* global variable for modbus communication */
static modbus_t *modbus_ctx = NULL;

/*
 * How many seconds to wait before switching off/on/reboot the UPS?
 *
 * This can be set at startup time via a command-line argument,
 * or at runtime by writing to RW variables "ups.delay.shutdown"
 * and "ups.delay.start". See ups2000_delay_get/set.
 */
#define UPS2000_DELAY_INVALID 0xFFFF
static uint16_t ups2000_offdelay = UPS2000_DELAY_INVALID;
static uint16_t ups2000_ondelay  = UPS2000_DELAY_INVALID;
static uint16_t ups2000_rebootdelay = UPS2000_DELAY_INVALID;

/*
 * Time when the current shutdown/reboot request is expected
 * to complete. This is used to calculate the ETA, See
 * ups2000_update_timers().
 */
static time_t shutdown_at = 0;
static time_t reboot_at = 0;
static time_t start_at = 0;

/*
 * Is it safe to enter bypass mode? It's checked by ups2000_update_alarm()
 * and used by ups2000_instcmd_bypass_start().
 */
static bool bypass_available = 0;

/* function prototypes */
static int ups2000_update_info(void);
static int ups2000_update_status(void);
static int ups2000_update_alarm(void);
static int ups2000_update_timers(void);
static void ups2000_device_identification(void);
static size_t ups2000_read_serial(uint8_t *buf, size_t buf_len);
static int ups2000_read_registers(modbus_t *ctx, int addr, int nb, uint16_t *dest);
static int ups2000_write_register(modbus_t *ctx, int addr, uint16_t val);
static int ups2000_write_registers(modbus_t *ctx, int addr, int nb, uint16_t *src);
static uint16_t crc16(uint8_t *buffer, uint16_t buffer_length);
static time_t time_seek(time_t t, int seconds);

/* rw variables function prototypes */
static int ups2000_update_rw_var(void);
static int setvar(const char *name, const char *val);
static int ups2000_autostart_set(const uint16_t reg, const char *string);
static int ups2000_autostart_get(const uint16_t reg);
static int ups2000_beeper_set(const uint16_t reg, const char *string);
static int ups2000_beeper_get(const uint16_t reg);
static void ups2000_delay_get(void);
static int ups2000_delay_set(const char *var, const char *string);

/* instant command function prototypes */
static void ups2000_init_instcmd(void);
static int instcmd(const char *cmd, const char *extra);
static int ups2000_instcmd_load_on(const uint16_t reg);
static int ups2000_instcmd_bypass_start(const uint16_t reg);
static int ups2000_instcmd_beeper_toggle(const uint16_t reg);
static int ups2000_instcmd_shutdown_stayoff(const uint16_t reg);
static int ups2000_instcmd_shutdown_return(const uint16_t reg);
static int ups2000_instcmd_shutdown_reboot(const uint16_t reg);
static int ups2000_instcmd_shutdown_reboot_graceful(const uint16_t reg);

/* driver description structure */
upsdrv_info_t upsdrv_info = {
	DRIVER_NAME,
	DRIVER_VERSION,
	"Yifeng Li <tomli@tomli.me>\n",
	DRV_EXPERIMENTAL,
	{ NULL }
};


void upsdrv_initups(void)
{
	int r;

	upsdebugx(2, "upsdrv_initups");

	/*
	 * This is an ugly workaround to a serious problem: libmodbus doesn't
	 * support device identification. Although there's a function called
	 * modbus_send_raw_request() for custom commands, but modbus_receive_
	 * confirmation() assumes a message length in the header, which is
	 * incompatible with device identification - It simply stops reading
	 * in the middle of the message and cannot receive our message. Worse,
	 * there's no public API to receive a raw response.
	 *
	 * See: https://github.com/stephane/libmodbus/issues/231
	 *
	 * Thus, the only thing we could do is opening it as a serial device
	 * for device identification, and reopen it via libmodbus for other
	 * commands as usual. We also have to copy the CRC-16 function from
	 * the libmodbus source code since there's no public API to use that...
	 */
	upsfd = ser_open(device_path);
	ser_set_speed(upsfd, device_path, B9600);
	ser_set_rts(upsfd, 0);
	ser_set_dtr(upsfd, 0);

	modbus_ctx = modbus_new_rtu(device_path, 9600, 'N', 8, 1);
	if (modbus_ctx == NULL)
		fatalx(EXIT_FAILURE, "Unable to create the libmodbus context");

#if LIBMODBUS_VERSION_CHECK(3, 1, 2)
	/* It can take as slow as 1 sec. for the UPS to respond. */
	modbus_set_response_timeout(modbus_ctx, 1, 0);
#else
	{
		struct timeval timeout;
		timeout.tv_sec = 1;
		timeout.tv_usec = 0;
		modbus_set_response_timeout(modbus_ctx, &timeout);
	}
#endif

	r = modbus_set_slave(modbus_ctx, MODBUS_SLAVE_ID);
	if (r < 0) {
		modbus_free(modbus_ctx);
		fatalx(EXIT_FAILURE, "Invalid slave ID %d", MODBUS_SLAVE_ID);
	}

	if (modbus_connect(modbus_ctx) == -1) {
		modbus_free(modbus_ctx);
		fatalx(EXIT_FAILURE, "modbus_connect: unable to connect: %s", modbus_strerror(errno));
	}
}


#define IDENT_REQUEST_LEN 7
#define IDENT_RESPONSE_MAX_LEN 128
#define IDENT_RESPONSE_HEADER_LEN 8
#define IDENT_RESPONSE_CRC_LEN 2
#define IDENT_FIELD_HEADER_LEN 2
static void ups2000_device_identification(void)
{
	static const uint8_t ident_req[IDENT_REQUEST_LEN] = {
		MODBUS_SLAVE_ID,  /* addr */
		0x2B,             /* command: device identification */
		0x0E,             /* MEI type */
		0x03,             /* ReadDevID: extended identification */
		0x87,             /* Object ID: device list */
		0x31, 0x75        /* CRC-16 */
	};

	/*
	 * Response header:
	 * 	0x01, 0x2B, 0x0E, 0x03, 0x03, 0x00, 0x00, 0x02
	 *
	 * Response fields:
	 * 	header: 0x87, 0x04  // type (device counts), length
	 * 	data:   uint32_t
	 * 	(e.g. 0x00, 0x00, 0x00, 0x01)
	 *
	 * 	header: 0x88, 0x??  // type (1st dev desc), length
	 * 	data:   ASCII string
	 * 	(e.g. 1=UPS2000;2=V100R001C01SPC120;3=...)
	 *
	 * 	header: 0x89, 0x??  // type (2nd dev desc), length
	 * 	data:   ASCII string
	 *
	 * 	...
	 * 	header: 0xFF, 0x??  // type (120th dev desc), length
	 * 	data:   ASCII string
	 *
	 * CRC-16:
	 * 	0x??, 0x??
	 */
	static const uint8_t expected_header[IDENT_RESPONSE_HEADER_LEN] = {
		MODBUS_SLAVE_ID,
		0x2B, 0x0E, 0x03, 0x03, 0x00, 0x00, 0x02,
	};

	bool serial_fail = 0;  /* unable to read from serial */
	uint16_t crc16_recv, crc16_calc;  /* resp CRC */
	bool crc16_fail = 0;  /* resp CRC failure */
	uint32_t ups_count = 0;  /* number of UPS in the resp list */
	uint8_t ident_response[IDENT_RESPONSE_MAX_LEN];  /* resp buf */
	size_t ident_response_len;    /* buf len */
	uint8_t *ident_response_end = NULL;  /* buf end marker (excluding CRC) */
	uint8_t *ptr = NULL;  /* buf iteratior */

	/* a desc string copied from ups2000_ident[] */
	char *ups2000_ident_desc = NULL;
	int i;
	ssize_t r;

	/* attempt to obtain a response header with valid CRC. */
	for (i = 0; i < 3; i++) {
		/* step 1: record response length and initialize ptr */
		upsdebugx(2, "ser_send_buf");

		ser_flush_in(upsfd, "", nut_debug_level);
		r = ser_send_buf(upsfd, ident_req, IDENT_REQUEST_LEN);
		if (r != IDENT_REQUEST_LEN) {
			fatalx(EXIT_FAILURE, "unable to send request!\n");
		}

		ident_response_len = ups2000_read_serial(ident_response, IDENT_RESPONSE_MAX_LEN);
		ptr = ident_response;
		ident_response_end = ptr + ident_response_len - IDENT_RESPONSE_CRC_LEN;

		/* step 2: check response length */
		if (ident_response_len == 0) {
			upslogx(LOG_ERR, "unable to read from serial port %s, retry...", device_path);
			serial_fail = 1;
			continue;
		}
		else
			serial_fail = 0;

		upsdebug_hex(2, "ups2000_read_serial() received", ptr, ident_response_len);

		if (ptr + IDENT_RESPONSE_HEADER_LEN > ident_response_end) {
			fatalx(EXIT_FAILURE, "response header too short! "
					     "expected %d, received %zu.",
					     IDENT_RESPONSE_HEADER_LEN, ident_response_len);
		}

		/* step 3: check response CRC-16 */
		crc16_recv = (uint16_t)((uint16_t)(ident_response_end[0]) << 8) | (uint16_t)(ident_response_end[1]);
		if (ident_response_len < IDENT_RESPONSE_CRC_LEN
		|| (((uintmax_t)(ident_response_len) - IDENT_RESPONSE_CRC_LEN) > UINT16_MAX)
		) {
			fatalx(EXIT_FAILURE, "response header shorter than CRC "
					     "or longer than UINT16_MAX!");
		}

		crc16_calc = crc16(ident_response, (uint16_t)(ident_response_len - IDENT_RESPONSE_CRC_LEN));
		if (crc16_recv == crc16_calc) {
			crc16_fail = 0;
			break;
		}
		crc16_fail = 1;
	}

	/* step 4: check serial & CRC-16 verification status */
	if (serial_fail)
		fatalx(EXIT_FAILURE, "unable to read from serial port %s!", device_path);

	if (crc16_fail)
		fatalx(EXIT_FAILURE, "response CRC verification failed!");

	/* step 5: check response header */
	if (memcmp(expected_header, ident_response, IDENT_RESPONSE_HEADER_LEN))
		fatalx(EXIT_FAILURE, "unexpected response header!");

	ptr += IDENT_RESPONSE_HEADER_LEN;

	/* step 6: extract ident fields */
	memset(ups2000_ident, 0x00, sizeof(ups2000_ident));
	for (i = 0; i < UPS2000_IDENT_MAX_FIELDS; i++) {
		uint8_t type, len;

		if (ptr + 2 > ident_response_end)
			break;

		type = *ptr++;
		len = *ptr++;

		if (len + 1 > UPS2000_IDENT_MAX_LEN)
			fatalx(EXIT_FAILURE, "response field too long!");

		ups2000_ident[i].type = type;
		ups2000_ident[i].len = len;
		/*
		 * Always zero-terminate the bytes, in case the data
		 * is an ASCII string (i.e. device desc string), libc
		 * string functions can be used.
		 */
		ups2000_ident[i].val[len] = '\0';

		if (ptr + len > ident_response_end)
			fatalx(EXIT_FAILURE, "response field too short!");

		memcpy(ups2000_ident[i].val, ptr, len);
		ptr += len;
	}

	/* step 7: validate device identification field 0x87 and 0x88 */
	for (i = 0; i < UPS2000_IDENT_MAX_FIELDS; i++) {
		/* only one device is supported */
		if (ups2000_ident[i].type == 0x87) {
			/* so we assume 0x87 must be 1 */
			ups_count =
				(uint32_t)(ups2000_ident[i].val[0]) << 24 |
				(uint32_t)(ups2000_ident[i].val[1]) << 16 |
				(uint32_t)(ups2000_ident[i].val[2]) << 8  |
				(uint32_t)(ups2000_ident[i].val[3]);
		}
		if (ups2000_ident[i].type == 0x88) {
			/*
			 * And only check 0x88, not 0x89, etc. Also copy the
			 * string for later parsing via strtok().
			 */
			ups2000_ident_desc = strdup((char *) ups2000_ident[i].val);
			break;
		}
	}
	if (ups_count != 1)
		fatalx(EXIT_FAILURE, "only 1 UPS is supported, %u found", ups_count);

	if (!ups2000_ident_desc)
		fatalx(EXIT_FAILURE, "device desc string not found");

	/*
	 * step 8: extract fields from the desc string.
	 * (1=UPS2000;2=V100R001C01SPC120;3=...)
	 */
	for (i = 0; i < UPS2000_DESC_MAX_FIELDS; i++) {
		char *key;  /* "1", "2", "3", ... */
		char *val;  /* "UPS2000", "V100R001C01SPC120", ... */
		unsigned int idx = 0;

		if (i == 0)
			key = strtok(ups2000_ident_desc, "=");
		else
			key = strtok(NULL, "=");
		if (!key)
			break;

		val = strtok(NULL, ";");
		if (!val)
			break;

		r = str_to_uint_strict(key, &idx, 10);
		if (!r || idx + 1 > UPS2000_DESC_MAX_FIELDS || idx < 1)
			fatalx(EXIT_FAILURE, "desc index %d is invalid!", idx);

		if (strlen(val) + 1 > UPS2000_DESC_MAX_LEN)
			fatalx(EXIT_FAILURE, "desc field %d too long!", idx);

		memcpy(ups2000_desc[idx], val, strlen(val) + 1);
	}
	free(ups2000_ident_desc);

	/*
	 * step 9: Validate desc fields that we are going to use are valid.
	 *
	 * Note: UPS2000_DESC_DEVICE_ID and UPS2000_DESC_PARALLEL_ID are
	 * currently unused and unchecked.
	 */
	for (i = UPS2000_DESC_MODEL; i <= UPS2000_DESC_ESN; i++) {
		if (strlen(ups2000_desc[i]) == 0)
			fatalx(EXIT_FAILURE, "desc field %d is missing!", i);
	}
}


void upsdrv_initinfo(void)
{
	bool in_list = 0;
	int i = 0;

	upsdebugx(2, "upsdrv_initinfo");

	ups2000_device_identification();

	/* check whether the UPS is a known model */
	for (i = 0; supported_model[i] != NULL; i++) {
		if (!strcmp(supported_model[i],
			    ups2000_desc[UPS2000_DESC_MODEL])) {
			in_list = 1;
		}
	}
	if (!in_list) {
		fatalx(EXIT_FAILURE, "Unknown UPS model %s",
				     ups2000_desc[UPS2000_DESC_MODEL]);
	}

	dstate_setinfo("device.mfr", "Huawei");
	dstate_setinfo("device.type", "ups");
	dstate_setinfo("device.model", "%s",
		       ups2000_desc[UPS2000_DESC_MODEL]);
	dstate_setinfo("device.serial", "%s",
		       ups2000_desc[UPS2000_DESC_ESN]);

	dstate_setinfo("ups.mfr", "Huawei");
	dstate_setinfo("ups.model", "%s",
		       ups2000_desc[UPS2000_DESC_MODEL]);
	dstate_setinfo("ups.firmware", "%s",
		       ups2000_desc[UPS2000_DESC_FIRMWARE_REV]);
	dstate_setinfo("ups.firmware.aux", "%s",
		       ups2000_desc[UPS2000_DESC_PROTOCOL_REV]);
	dstate_setinfo("ups.serial", "%s",
		       ups2000_desc[UPS2000_DESC_ESN]);
	dstate_setinfo("ups.type", "online");

	/* RW variables */
	upsh.setvar = setvar;

	/* instant commands */
	ups2000_init_instcmd();
	upsh.instcmd = instcmd;
}


/*
 * All registers are uint16_t. But the data they represent can
 * be either an integer or a float. This information is used for
 * error checking (int and float have different invalid values).
 */
enum {
	REG_UINT16,
	REG_UINT32, /* occupies two registers */
	REG_FLOAT,  /* actually a misnomer, it should really be called
		       fixed-point number, but we follow the datasheet */
};
#define REG_UINT16_INVALID 0xFFFFU
#define REG_UINT32_INVALID 0xFFFFFFFFU
#define REG_FLOAT_INVALID  0x7FFFU


/*
 * Declare UPS attribute variables, format strings, registers,
 * and their scaling factors in a lookup table to avoid spaghetti
 * code.
 */
static struct {
	const char *name;
	const char *fmt;
	const uint16_t reg;
	const int datatype;    /* only UINT32 occupies 2 regs */
	const float scaling;   /* scale it down to get the original */
} ups2000_var[] =
{
	{ "input.voltage",          "%03.1f", 1000, REG_FLOAT,  10.0  },
	{ "input.frequency",        "%02.1f", 1003, REG_FLOAT,  10.0  },
	{ "input.bypass.voltage",   "%03.1f", 1004, REG_FLOAT,  10.0  },
	{ "input.bypass.frequency", "%03.1f", 1007, REG_FLOAT,  10.0  },
	{ "output.voltage",         "%03.1f", 1008, REG_FLOAT,  10.0  },
	{ "output.current",         "%03.1f", 1011, REG_FLOAT,  10.0  },
	{ "output.frequency",       "%03.1f", 1014, REG_FLOAT,  10.0  },
	{ "output.realpower",       "%02.1f", 1015, REG_FLOAT,   0.01 }, /* 10 / 1 kW */
	{ "output.power",           "%03.1f", 1018, REG_FLOAT,   0.01 }, /* 10 / 1 kVA */
	{ "ups.load",               "%02.1f", 1021, REG_FLOAT,  10.0  },
	{ "ups.temperature",        "%02.1f", 1027, REG_FLOAT,  10.0  },
	{ "battery.voltage",        "%02.1f", 2000, REG_FLOAT,  10.0  },
	{ "battery.charge",         "%02.1f", 2003, REG_UINT16,  1.0  },
	{ "battery.runtime",        "%.0f",   2004, REG_UINT32,  1.0  },
	{ "battery.packs",          "%.0f",   2007, REG_UINT16,  1.0  },
	{ "battery.capacity",       "%.0f",   2033, REG_UINT16,  1.0  },
	{ "ups.power.nominal",      "%.0f",   9009, REG_FLOAT,   0.01 }, /* 10 / 1 kVA */
	{ NULL, NULL, 0, 0, 0 },
};


static int ups2000_update_info(void)
{
	uint16_t reg[3][34];
	int i;
	int r;

	upsdebugx(2, "ups2000_update_info");

	/*
	 * All status registers have an offset of 10000 * ups_number.
	 * We only support 1 UPS, thus it's always 10000. Register
	 * 1000 becomes 11000.
	 */
	r = ups2000_read_registers(modbus_ctx, 11000, 28, reg[0]);
	if (r != 28)
		return 1;

	r = ups2000_read_registers(modbus_ctx, 12000, 34, reg[1]);
	if (r != 34)
		return 1;

	r = ups2000_read_registers(modbus_ctx, 19009, 1, &reg[2][9]);
	if (r != 1)
		return 1;

	for (i = 0; ups2000_var[i].name != NULL; i++) {
		uint16_t reg_id = ups2000_var[i].reg;
		uint8_t page = (uint8_t)(reg_id / 1000 - 1);
		uint8_t idx =  (uint8_t)(reg_id % 1000);
		uint32_t val;
		bool invalid = 0;

		if (page == 8)  /* hack for the lonely register 9009 */
			page = 2;

		if (page > 2 || idx > 33)  /* also suppress compiler warn */
			fatalx(EXIT_FAILURE, "register calculation overflow!\n");

		switch (ups2000_var[i].datatype) {
		case REG_FLOAT:
			val = reg[page][idx];
			if (val == REG_FLOAT_INVALID)
				invalid = 1;
			break;
		case REG_UINT16:
			val = reg[page][idx];
			if (val == REG_UINT16_INVALID)
				invalid = 1;
			break;
		case REG_UINT32:
			val  = (uint32_t)(reg[page][idx]) << 16;
			val |= (uint32_t)(reg[page][idx + 1]);
			if (val == REG_UINT32_INVALID)
				invalid = 1;
			break;
		default:
			fatalx(EXIT_FAILURE, "invalid data type in register table!\n");
		}

		if (invalid) {
			upslogx(LOG_ERR, "register %04d has invalid value %04x,", reg_id, val);
			return 1;
		}

#ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL
#pragma GCC diagnostic push
#endif
#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
#endif
#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_SECURITY
#pragma GCC diagnostic ignored "-Wformat-security"
#endif
		dstate_setinfo(ups2000_var[i].name, ups2000_var[i].fmt,
			       (float) val / ups2000_var[i].scaling);
#ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL
#pragma GCC diagnostic pop
#endif
	}
	return 0;
}


/*
 * A lookup table of all the status registers and the list of
 * corresponding flags they represent. A register may set multiple
 * status flags, represented by an array of flags_t.
 *
 * There are two types of flags. If the flag is a "status flag"
 * for status_set(), for example, "OL" or "OB", the field
 * "status_name" is used. If the flag is a "data variable" for
 * dstate_setinfo(), the variable name and value is written in
 * "var_name" and "var_val" fields.
 *
 * For each flag, if it's indicated by a specific value in a
 * register, the "val" field is used. If a flag is indicated by
 * a bit, the "bit" field should be used. Fields "val" and "bit"
 * cannot be used at the same time, at least one must be "-1".
 *
 * Also, some important registers indicate basic system status
 * (e.g. whether the UPS is on line power or battery), this info
 * must always be available, and they are always expected to set
 * at least one flag. If the important register does not set any
 * flag, it means we've received an invalid or unknown value,
 * and we must report an error. The "must_set_flag" field is used
 * for this purpose.
 */
static struct {
	const uint16_t reg;
	bool must_set_flag;
	struct flags_t {
		const char *status_name;
		const int16_t val;
		const int bit;
		const char *var_name, *var_val;
	} flags[10];
} ups2000_status_reg[] =
{
	{ 1024, 1, {
		{ "OFF",      0, -1, NULL, NULL },
		{ "BYPASS",   1, -1, NULL, NULL },
		{ "OL",       2, -1, NULL, NULL },
		{ "OB",       3, -1, NULL, NULL },
		{ "OL ECO",   5, -1, NULL, NULL },
		{ NULL,      -1, -1, NULL, NULL },
	}},
	{ 1043, 0, {
		{ "CAL",     -1,  2, NULL, NULL },  /* battery self-test */
		{ "LB",      -1,  6, NULL, NULL },
		{ NULL,      -1, -1, NULL, NULL },
	}},
	/*
	 * Note: 3 = float charging, 4 = equalization charging, but
	 * both of them are reported as "charging", not "floating".
	 * The definition of "floating" in NUT is: "battery has
	 * completed its charge cycle, and waiting to go to resting
	 * mode", which is not true for UPS2000.
	 */
	{ 2002, 1, {
		{ "",         2, -1, "battery.charger.status", "resting"     },
		{ "CHRG",     3, -1, "battery.charger.status", "charging"    },
		{ "CHRG",     4, -1, "battery.charger.status", "charging"    },
		{ "DISCHRG",  5, -1, "battery.charger.status", "discharging" },
		{ NULL,      -1, -1, NULL, NULL },
	}},
	{ 0, 0, { { NULL, -1, -1, NULL, NULL } } }
};


static int ups2000_update_status(void)
{
	int i, j;
	int r;

	upsdebugx(2, "ups2000_update_status");

	for (i = 0; ups2000_status_reg[i].reg != 0; i++) {
		uint16_t reg, val;
		struct flags_t *flag;
		int flag_count = 0;

		reg = ups2000_status_reg[i].reg;
		r = ups2000_read_registers(modbus_ctx, reg + 10000, 1, &val);
		if (r != 1)
			return 1;

		if (val == REG_UINT16_INVALID) {
			upslogx(LOG_ERR, "register %04d has invalid value %04x,", reg, val);
			return 1;
		}

		flag = ups2000_status_reg[i].flags;
		for (j = 0; flag[j].status_name != NULL; j++) {
			/*
			 * if the register is equal to the "val" we are looking
			 * for, or if register has its n-th "bit" set...
			 */
			if ((flag[j].val != -1 && flag[j].val == val) ||
			    (flag[j].bit != -1 && CHECK_BIT(val, flag[j].bit))) {
				/* if it has a corresponding status flag */
				if (strlen(flag[j].status_name) != 0)
					status_set(flag[j].status_name);
				/* or if it has a corresponding dstate variable (or both) */
				if (flag[j].var_name && flag[j].var_val)
					dstate_setinfo(flag[j].var_name, "%s", flag[j].var_val);
				flag_count++;
			}
		}
		if (ups2000_status_reg[i].must_set_flag && flag_count == 0) {
			upslogx(LOG_ERR, "register %04d has invalid value %04x,", reg, val);
			return 1;
		}
	}

	return 0;
}


/*
 * A lookup table of all the alarm registers and the list of
 * corresponding alarms they represent. Each alarm condition is
 * listed by its register base address "reg" and its "bit"
 * position.
 *
 * Each alarm condition has an "alarm_id", "alarm_cause_id",
 * and "alarm_name". In addition, a few alarms conditions also
 * indicates conditions related to batteries that is needed to
 * be set via status_set(), those are listed in "status_name".
 * Unused "status_name" is set to NULL.
 *
 * After an alarm is reported/cleared by the UPS, the "active"
 * flag is changed to reflect its status. The error logging
 * code uses this variable to issue warnings only when needed
 * (i.e. only after a change, avoid issuing the same warning
 * repeatedly).
 */
#define ALARM_CLEAR_AUTO      1
#define ALARM_CLEAR_MANUAL    2
#define ALARM_CLEAR_DEPENDING 3

static struct {
	bool active;             /* runtime: is this alarm currently active? */
	const uint16_t reg;      /* alarm register to check */
	const int bit;           /* alarm bit to check */
	const int alarm_clear;   /* auto or manual clear */
	const int loglevel;      /* warning or error */
	const int alarm_id, alarm_cause_id;
	const char *status_name; /* corresponding NUT status word */
	const char *alarm_name;  /* alarm string */
	const char *alarm_desc;  /* brief explanation */
} ups2000_alarm[] =
{
	{
		false, 40156, 3, ALARM_CLEAR_AUTO, LOG_ALERT,
		30, 1, NULL, "UPS internal overtemperature",
		"The ambient temperature is over 50-degree C. "
		"Startup from standby mode is prohibited.",
	},
	{
		false, 40161, 1, ALARM_CLEAR_AUTO, LOG_WARNING,
		10, 1, NULL, "Abnormal bypass voltage",
		"Bypass input is unavailable or out-of-range. Wait for "
		"bypass input to recover, or change acceptable bypass "
		"range via front panel.",
	},
	{
		false, 40161, 2, ALARM_CLEAR_AUTO, LOG_WARNING,
		10, 2, NULL, "Abnormal bypass frequency",
		"Bypass input is unavailable or out-of-range. Wait for "
		"bypass input to recover, or change acceptable bypass "
		"range via front panel.",
	},
	{
		false, 40163, 3, ALARM_CLEAR_DEPENDING, LOG_WARNING,
		25, 1, NULL, "Battery overvoltage",
		"When the UPS is started, voltage of each battery exceeds 15 V. "
		"Or: current battery voltage exceeds 14.7 V.",
	},
	{
		false, 40164, 1, ALARM_CLEAR_AUTO, LOG_WARNING,
		29, 1, "RB", "Battery needs maintenance",
		"During the last battery self-check, the battery voltage "
		"was lower than the replacement threshold (11 V).",
	},
	{
		false, 40164, 3, ALARM_CLEAR_AUTO, LOG_WARNING,
		26, 1, NULL, "Battery undervoltage",
		NULL,
	},
	{
		false, 40170, 4, ALARM_CLEAR_AUTO, LOG_ALERT,
		22, 1, NULL, "Battery disconnected",
		"Battery is not connected, has loose connection, or faulty.",
	},
	{
		false, 40173, 5, ALARM_CLEAR_AUTO, LOG_ALERT,
		66, 1, "OVER", "Output overload (105%-110%)",
		"UPS will shut down or transfer to bypass mode in 5-10 minutes.",
	},
	{
		false, 40173, 3, ALARM_CLEAR_AUTO, LOG_ALERT,
		66, 2, "OVER", "Output overload (110%-130%)",
		"UPS will shut down or transfer to bypass mode in 30-60 seconds.",
	},
	{
		false, 40174, 0, ALARM_CLEAR_DEPENDING, LOG_ALERT,
		14, 1, NULL, "UPS startup timeout",
		"The inverter output voltage is not within +/- 2 V of the "
		"rated output. Or: battery is overdischarged.",
	},
	{
		false, 40179, 14, ALARM_CLEAR_MANUAL, LOG_ALERT,
		42, 15, NULL, "Rectifier fault (internal fault)",
		"Bus voltage is lower than 320 V.",
	},
	{
		false, 40179, 15, ALARM_CLEAR_MANUAL, LOG_ALERT,
		42, 17, NULL, "Rectifier fault (internal fault)",
		"Bus voltage is higher than 450 V.",
	},
	{
		false, 40180, 1, ALARM_CLEAR_MANUAL, LOG_ALERT,
		42, 18, NULL, "Rectifier fault (internal fault)",
		"Bus voltage is lower than 260 V.",
	},
	{
		false, 40180, 5, ALARM_CLEAR_AUTO, LOG_ALERT,
		42, 24, NULL, "EEPROM fault (internal fault)",
		"Faulty EEPROM. All settings are restored to "
		"factory default and cannot be saved.",
	},
	{
		false, 40180, 6, ALARM_CLEAR_MANUAL, LOG_ALERT,
		42, 27, NULL, "Inverter fault (internal fault)",
		"Inverter output overvoltage, undervoltage or "
		"undercurrent.",
	},
	{
		false, 40180, 7, ALARM_CLEAR_DEPENDING, LOG_ALERT,
		42, 28, NULL, "Inverter fault (internal fault)",
		"The inverter output voltage is lower than 100 V.",
	},
	{
		false, 40180, 10, ALARM_CLEAR_MANUAL, LOG_ALERT,
		42, 31, NULL, "Inverter fault (internal fault)",
		"The difference between the absolute value of the positive bus "
		"voltage and that of the negative bus voltage is 100 V.",
	},
	{
		false, 40180, 11, ALARM_CLEAR_DEPENDING, LOG_ALERT,
		42, 32, NULL, "UPS internal overtemperature",
		"The ambient temperature is over 50 degree C, "
		"switching to bypass mode.",

	},
	{
		false, 40180, 13, ALARM_CLEAR_MANUAL, LOG_ALERT,
		42, 36, NULL, "Charger fault (internal fault)",
		"The charger has no output. Faulty internal connections.",

	},
	{
		false, 40182, 4, ALARM_CLEAR_MANUAL, LOG_ALERT,
		42, 42, NULL, "Charger fault (internal fault)",
		"The charger has no output while the inverter is on, "
		"battery undervoltage. Faulty switching transistor.",
	},
	{
		false, 40182, 13, ALARM_CLEAR_MANUAL, LOG_ALERT,
		66, 3, "OVER", "Output overload shutdown",
		"UPS has shutdown or transferred to bypass mode.",
	},
	{
		false, 40182, 14, ALARM_CLEAR_MANUAL, LOG_ALERT,
		66, 4, "OVER", "Bypass output overload shutdown",
		"UPS has shutdown, bypass output was overload and exceeded "
		"time limit.",
	},
	{ false, 0, -1, -1, -1, -1, -1, NULL, NULL, NULL }
};


/* don't spam the syslog */
static time_t alarm_logged_since = 0;
#define UPS2000_LOG_INTERVAL 600  /* 10 minutes */


static int ups2000_update_alarm(void)
{
	uint16_t val[27];
	int i;
	int r;

	char alarm_buf[128];
	size_t all_alarms_len = 0;

	int alarm_count = 0;
	bool alarm_logged = 0;
	bool alarm_rtfm = 0;
	time_t now = time(NULL);

	upsdebugx(2, "ups2000_update_alarm");

	/*
	 * All alarm registers have an offset of 1024 * ups_number.
	 * We only support 1 UPS, it's always 1024.
	 */
	r = ups2000_read_registers(modbus_ctx, ups2000_alarm[0].reg + 1024, 27, val);
	if (r != 27)
		return 1;

	bypass_available = 1;  /* register 40161 hack, see comments below */

	for (i = 0; ups2000_alarm[i].alarm_id != -1; i++) {
		int idx = ups2000_alarm[i].reg - ups2000_alarm[0].reg;
		if (idx > 26 || idx < 0)
			fatalx(EXIT_FAILURE, "register calculation overflow!\n");

		if (CHECK_BIT(val[idx], ups2000_alarm[i].bit)) {
			int gotlen;
			if (ups2000_alarm[i].reg == 40161)
				/*
				 * HACK: special treatment for register 40161. If this
				 * register indicates an alarm, we need to lock the
				 * "bypass.on" command as a software foolproof mechanism.
				 * It's written to the global "bypass_available" flag.
				 */
				bypass_available = 0;

			alarm_count++;

			gotlen = snprintf(alarm_buf, 128, "(ID %02d/%02d): %s!",
						   ups2000_alarm[i].alarm_id,
						   ups2000_alarm[i].alarm_cause_id,
						   ups2000_alarm[i].alarm_name);

			if (gotlen < 0 || (uintmax_t)gotlen > SIZE_MAX) {
				fatalx(EXIT_FAILURE, "alarm_buf preparation over/under-flow!\n");
			}

			all_alarms_len += (size_t)gotlen;
			alarm_set(alarm_buf);

			if (ups2000_alarm[i].status_name)
				status_set(ups2000_alarm[i].status_name);

			/*
			 * Log the warning only if it's a new alarm, or if a long time
			 * has paseed since we first warned it.
			 */
			if (!ups2000_alarm[i].active ||
			    difftime(now, alarm_logged_since) >= UPS2000_LOG_INTERVAL) {
				int loglevel;
				const char *alarm_word;

				/*
				 * Most text editors have syntax highlighting, adding an
				 * alarm word makes the log more readable
				 */
				loglevel = ups2000_alarm[i].loglevel;
				if (loglevel <= LOG_ERR) {
					alarm_word = "ERROR";
					/*
					 * If at least one error is serious, suggest reading
					 * manual.
					 */
					alarm_rtfm = 1;
				}
				else {
					alarm_word = "WARNING";
				}

				upslogx(loglevel, "%s: alarm %02d, Cause %02d: %s!",
						  alarm_word,
						  ups2000_alarm[i].alarm_id,
						  ups2000_alarm[i].alarm_cause_id,
						  ups2000_alarm[i].alarm_name);

				if (ups2000_alarm[i].alarm_desc)
					upslogx(loglevel, "%s", ups2000_alarm[i].alarm_desc);

				switch (ups2000_alarm[i].alarm_clear) {
				case ALARM_CLEAR_AUTO:
					upslogx(loglevel, "This alarm can be auto cleared.");
					break;
				case ALARM_CLEAR_MANUAL:
					upslogx(loglevel, "This alarm can only be manual cleared "
							  "via front panel.");
					break;
				case ALARM_CLEAR_DEPENDING:
					upslogx(loglevel, "This alarm is auto or manual cleared "
							  "depending on the specific problem.");
				}

				ups2000_alarm[i].active = 1;
				alarm_logged = 1;
			}

		}
		else {
			if (ups2000_alarm[i].active) {
				upslogx(LOG_WARNING, "Cleared alarm %02d, Cause %02d: %s",
						     ups2000_alarm[i].alarm_id,
						     ups2000_alarm[i].alarm_cause_id,
						     ups2000_alarm[i].alarm_name);
				ups2000_alarm[i].active = 0;
				alarm_logged = 1;
			}
		}

	}

	if (alarm_count > 0) {
		/* append this to the alarm string as a friendly reminder */
		int gotlen = snprintf(alarm_buf, 128, "Check log for details!");

		if (gotlen < 0 || (uintmax_t)gotlen > SIZE_MAX) {
			fatalx(EXIT_FAILURE, "alarm_buf preparation over/under-flow!\n");
		}

		all_alarms_len += (size_t)gotlen;
		alarm_set(alarm_buf);

		/* if the alarm string is too long, replace it with this */
		if (all_alarms_len + 1 > ST_MAX_VALUE_LEN) {
			alarm_init();  /* discard all original alarms */
			snprintf(alarm_buf, 128, "UPS has %d alarms in effect, "
						 "check log for details!", alarm_count);
			alarm_set(alarm_buf);
		}

		/*
		 * If we are doing a syslog, write the final message and refresh the
		 * do-not-spam-the-log timer "alarm_logged_since".
		 */
		if (alarm_logged) {
			upslogx(LOG_WARNING, "UPS has %d alarms in effect.", alarm_count);
			if (alarm_rtfm)
				upslogx(LOG_WARNING, "Read Huawei User Manual for "
						     "troubleshooting information.");
			alarm_logged_since = time(NULL);
		}
	}
	else {
		upsdebugx(2, "UPS has 0 alarms in effect.");

		if (alarm_logged) {
			upslogx(LOG_WARNING, "UPS has cleared all alarms.");
			alarm_logged_since = time(NULL);
		}
	}
	return 0;
}


void upsdrv_updateinfo(void)
{
	int err = 0;

	upsdebugx(2, "upsdrv_updateinfo");
	status_init();
	alarm_init();

	err += ups2000_update_timers();
	err += ups2000_update_alarm();
	err += ups2000_update_info();
	err += ups2000_update_status();
	err += ups2000_update_rw_var();

	if (err > 0) {
		upsdebugx(2, "upsdrv_updateinfo failed, data stale.");
		dstate_datastale();
		return;
	}

	alarm_commit();
	status_commit();
	dstate_dataok();
	upsdebugx(2, "upsdrv_updateinfo done");
}


/*
 * A lookup table of simple RW (configurable) variable "name", and their
 * "getter" and "setter". A "getter" function reads the variable from
 * the UPS, and a "setter" overwrites it.
 *
 * This struct only handles simple variables, delays are handled in another
 * table.
 */
static struct {
	const char *name;
	const uint16_t reg;
	int (*const getter)(const uint16_t);
	int (*const setter)(const uint16_t, const char *);
} ups2000_rw_var[] =
{
	{ "ups.start.auto",     1044, ups2000_autostart_get, ups2000_autostart_set },
	{ "ups.beeper.status",  1046, ups2000_beeper_get,    ups2000_beeper_set    },
	{ NULL, 0, NULL, NULL },
};


/*
 * A specialized lookup table of startup, reboot and shutdown delays,
 * represented by RW variables.
 */
static struct ups2000_delay_t {
	const char *name;             /* RW variable name */
	uint16_t *const global_var;   /* its corresponding global variable */
	const char *varname_cmdline;  /* cmdline argument passed to us */
	const uint16_t min;           /* minimum value allowed (seconds) */
	const uint16_t max;           /* maximum value allowed (seconds) */
	const uint8_t step;           /* can only be set in discrete steps */
	const uint16_t dfault;        /* default value */
} ups2000_rw_delay[] =
{
	/* 5940 = 99 min. */
	{ "ups.delay.shutdown", &ups2000_offdelay,    "offdelay",     6, 5940,  6, 60 },
	{ "ups.delay.reboot",   &ups2000_rebootdelay, "rebootdelay",  6, 5940,  6, 60 },
	{ "ups.delay.start",    &ups2000_ondelay,     "ondelay",     60, 5940, 60, 60 },
	{ NULL, NULL, NULL, 0, 0, 0, 0 },
};
enum {
	SHUTDOWN,
	REBOOT,
	START
};


static int ups2000_update_rw_var(void)
{
	int i;
	int r;

	upsdebugx(2, "ups2000_update_rw_var");

	for (i = 0; ups2000_rw_var[i].name != NULL; i++) {
		r = ups2000_rw_var[i].getter(ups2000_rw_var[i].reg);
		if (r != 0)
			return 1;
	}

	ups2000_delay_get();

	return 0;
}


static int setvar(const char *name, const char *val)
{
	int i;
	int r;

	for (i = 0; ups2000_rw_var[i].name != NULL; i++) {
		if (!strcasecmp(ups2000_rw_var[i].name, name)) {
			r = ups2000_rw_var[i].setter(ups2000_rw_var[i].reg, val);
			goto found;
		}
	}

	for (i = 0; ups2000_rw_delay[i].name != NULL; i++) {
		if (!strcasecmp(ups2000_rw_delay[i].name, name)) {
			r = ups2000_rw_var[i].setter(ups2000_rw_var[i].reg, val);
			goto found;
		}
	}

	return STAT_SET_UNKNOWN;

found:
	if (r == STAT_SET_FAILED)
		upslogx(LOG_ERR, "setvar: setting variable [%s] to [%s] failed", name, val);
	else if (r == STAT_SET_INVALID)
		upslogx(LOG_WARNING, "setvar: [%s] is not valid for variable [%s]", val, name);
	return r;
}


static int ups2000_autostart_get(const uint16_t reg)
{
	/*
	 * "ups.start.auto" is not supported because it overcomplicates
	 * the logic. The driver changes "ups.start.auto" internally to
	 * allow shutdown and reboot commands to do their jobs. If we make
	 * "ups.start.auto" an user configuration, it means we must (1)
	 * watch for UPS front panel updates and apply the user setting to
	 * the driver, and (2) save the restart setting temporally before
	 * restarting, track the UPS restart process, and program the value
	 * back later.
	 *
	 * Not supporting it greatly simplifies the logic - upsdrv_shutdown
	 * always put the UPS in a restartable mode, following the standard
	 * NUT behavior. Worse is better. (To prevent user confusion, we
	 * don't even report this variable, otherwise the user may attempt
	 * to change it using the front panel.
	 */
	NUT_UNUSED_VARIABLE(reg);
	return 0;
}


/*
 * Currently for internal use only, see comments above.
 */
static int ups2000_autostart_set(const uint16_t reg, const char *string)
{
	uint16_t val;
	int r;

	if (!strcasecmp(string, "yes"))
		val = 1;
	else if (!strcasecmp(string, "no"))
		val = 0;
	else
		return STAT_SET_INVALID;

	r = ups2000_write_register(modbus_ctx, reg + 10000, val);
	if (r != 1)
		return STAT_SET_FAILED;

	return STAT_SET_HANDLED;
}


static int ups2000_beeper_get(const uint16_t reg)
{
	uint16_t val;
	int r;

	r = ups2000_read_registers(modbus_ctx, reg + 10000, 1, &val);
	if (r != 1)
		return -1;

	if (val != 0 && val != 1)
		return -1;

	/*
	 * The register is "beeper disable", but we need to report whether it's
	 * enabled, thus we invert the boolean.
	 */
	if (val == 0)
		dstate_setinfo("ups.beeper.status", "enabled");
	else
		dstate_setinfo("ups.beeper.status", "disabled");

	dstate_setflags("ups.beeper.status", ST_FLAG_RW);
	dstate_addenum("ups.beeper.status", "enabled");
	dstate_addenum("ups.beeper.status", "disabled");

	return 0;
}


static int ups2000_beeper_set(const uint16_t reg, const char *string)
{
	uint16_t val;
	int r;

	if (!strcasecmp(string, "disabled") || !strcasecmp(string, "muted")) {
		/*
		 * Temporary "muted" is not supported. Only permanent "disabled"
		 * is. This is why we only support "beeper.disable" as an instant
		 * command, not "beeper.muted". But when setting it as a variable,
		 * we try to be robust here and treat both as synonyms.
		 */
		val = 1;
	}
	else if (!strcasecmp(string, "enabled"))
		val = 0;
	else
		return STAT_SET_INVALID;

	r = ups2000_write_register(modbus_ctx, reg + 10000, val);
	if (r != 1)
		return STAT_SET_FAILED;

	return STAT_SET_HANDLED;
}


/*
 * Note: variables "ups.delay.{shutdown,start,reboot}" are software-
 * only variables. We only get the user settings, validate its value
 * and store them as global variables. The actual hardware register
 * are only programmed when a shutdown/reboot is issued.
 */
static void ups2000_delay_get(void)
{
	char *cmdline;
	int i;
	int r;

	for (i = 0; ups2000_rw_delay[i].name != NULL; i++) {
		struct ups2000_delay_t *delay;

		delay = &ups2000_rw_delay[i];
		if (*delay->global_var == UPS2000_DELAY_INVALID) {
			cmdline = getval(delay->varname_cmdline);
			if (cmdline) {
				r = ups2000_delay_set(delay->name, cmdline);
				if (r != STAT_SET_HANDLED) {
					upslogx(LOG_ERR, "servar: %s is invalid. "
							 "Reverting to default %s %d seconds",
							 delay->varname_cmdline,
							 delay->varname_cmdline,
							 delay->dfault);
					*delay->global_var = delay->dfault;
				}
			}
			else {
				*delay->global_var = delay->dfault;
				upslogx(LOG_INFO, "setvar: use default %s %d seconds",
					delay->varname_cmdline, delay->dfault);
			}
		}

		dstate_setinfo(delay->name, "%d", *delay->global_var);
		dstate_setflags(delay->name, ST_FLAG_RW);
		dstate_addrange(delay->name, delay->min, delay->max);
	}
}


static int ups2000_delay_set(const char *var, const char *string)
{
	struct ups2000_delay_t *delay_schema = NULL;
	uint16_t delay, delay_rounded;
	int i;
	int r;

	r = str_to_ushort_strict(string, &delay, 10);
	if (!r)
		return STAT_SET_INVALID;

	for (i = 0; ups2000_rw_delay[i].name != NULL; i++) {
		if (!strcmp(ups2000_rw_delay[i].name, var)) {
			delay_schema = &ups2000_rw_delay[i];
			break;
		}
	}

	if (!delay_schema)
		return STAT_SET_UNKNOWN;

	if (delay > delay_schema->max)
		return STAT_SET_INVALID;
	if (delay < delay_schema->min) {
		upslogx(LOG_NOTICE, "setvar: %s [%u] is too low, "
				    "it has been set to %u seconds\n",
				    delay_schema->varname_cmdline, delay,
				    delay_schema->min);
		delay = delay_schema->min;
	}

	if (delay % delay_schema->step != 0) {
		delay_rounded = delay + delay_schema->step - delay % delay_schema->step;
		upslogx(LOG_NOTICE, "setvar: %s [%u] is not a multiple of %d, "
				    "it has been rounded up to %u seconds\n",
				    delay_schema->varname_cmdline, delay,
				    delay_schema->step, delay_rounded);
		delay = delay_rounded;
	}

	*delay_schema->global_var = delay;
	return STAT_SET_HANDLED;
}


/*
 * A lookup table of all instant commands "cmd" and their
 * corresponding registers "reg". For each instant command,
 * it's handled by...
 *
 * 1. One register write, by writing "val1" to "reg1", the
 * simplest case.
 *
 * 2. Two register writes, by writing "val1" to "reg1", and
 * writing "val2" to "reg2". One after another.
 *
 * 3. Calling "*handler_func" and passing "reg1". This is
 * used to handle commands that needs additional processing.
 * If "reg1" is not necessary or unsuitable, "-1" is used.
 */
#define REG_NONE -1, -1

static struct ups2000_cmd_t {
	const char *cmd;
	const int16_t reg1, val1, reg2, val2;
	int (*const handler_func)(const uint16_t);
} ups2000_cmd[] =
{
	{ "test.battery.start.quick", 2028,  1, REG_NONE, NULL },
	{ "test.battery.start.deep",  2021,  1, REG_NONE, NULL },
	{ "test.battery.stop",        2023,  1, REG_NONE, NULL },
	{ "beeper.enable",            1046,  0, REG_NONE, NULL },
	{ "beeper.disable",           1046,  1, REG_NONE, NULL },
	{ "load.off",                 1045,  0, 1030, 1,  NULL },
	{ "bypass.stop",              1029,  1, 1045, 0,  NULL },
	{ "load.on",                  1029, -1, REG_NONE, ups2000_instcmd_load_on                  },
	{ "bypass.start",             REG_NONE, REG_NONE, ups2000_instcmd_bypass_start             },
	{ "beeper.toggle",            1046, -1, REG_NONE, ups2000_instcmd_beeper_toggle            },
	{ "shutdown.stayoff",         1049, -1, REG_NONE, ups2000_instcmd_shutdown_stayoff         },
	{ "shutdown.return",          REG_NONE, REG_NONE, ups2000_instcmd_shutdown_return          },
	{ "shutdown.reboot",          REG_NONE, REG_NONE, ups2000_instcmd_shutdown_reboot          },
	{ "shutdown.reboot.graceful", REG_NONE, REG_NONE, ups2000_instcmd_shutdown_reboot_graceful },
	{ NULL, -1, -1, -1, -1, NULL },
};


static void ups2000_init_instcmd(void)
{
	int i;

	for (i = 0; ups2000_cmd[i].cmd != NULL; i++) {
		dstate_addcmd(ups2000_cmd[i].cmd);
	}
}


static int instcmd(const char *cmd, const char *extra)
{
	int i;
	int status;
	struct ups2000_cmd_t *cmd_action = NULL;
	NUT_UNUSED_VARIABLE(extra);

	for (i = 0; ups2000_cmd[i].cmd != NULL; i++) {
		if (!strcasecmp(cmd, ups2000_cmd[i].cmd)) {
			cmd_action = &ups2000_cmd[i];
		}
	}

	if (!cmd_action) {
		upslogx(LOG_WARNING, "instcmd: command [%s] unknown", cmd);
		return STAT_INSTCMD_UNKNOWN;
	}

	if (cmd_action->handler_func) {
		/* handled by a function */
		if (cmd_action->reg1 < 0) {
			upslogx(LOG_WARNING, "instcmd: command [%s] reg1 is negative", cmd);
			return STAT_INSTCMD_UNKNOWN;
		} else {
			status = cmd_action->handler_func((uint16_t)cmd_action->reg1);
		}
	}
	else if (cmd_action->reg1 >= 0 && cmd_action->val1 >= 0) {
		/* handled by a register write */
		int r = ups2000_write_register(modbus_ctx,
					       10000 + cmd_action->reg1,
					       (uint16_t)cmd_action->val1);
		if (r == 1)
			status = STAT_INSTCMD_HANDLED;
		else
			status = STAT_INSTCMD_FAILED;

		/*
		 * if the previous write succeeds and there is an additional
		 * register to write.
		 */
		if (r == 1 && cmd_action->reg2 >= 0 && cmd_action->val2 >= 0) {
			r = ups2000_write_register(modbus_ctx,
						   10000 + cmd_action->reg2,
						   (uint16_t)cmd_action->val2);
			if (r == 1)
				status = STAT_INSTCMD_HANDLED;
			else
				status = STAT_INSTCMD_FAILED;
		}
	}
	else {
		fatalx(EXIT_FAILURE, "invalid ups2000_cmd table!");
	}

	if (status == STAT_INSTCMD_FAILED)
		upslogx(LOG_ERR, "instcmd: command [%s] failed", cmd);
	else if (status == STAT_INSTCMD_HANDLED)
		upslogx(LOG_INFO, "instcmd: command [%s] handled", cmd);
	return status;
}


static int ups2000_instcmd_load_on(const uint16_t reg)
{
	int r;
	const char *status;

	/* force refresh UPS status */
	status_init();
	r = ups2000_update_status();
	if (r != 0) {
		/*
		 * When the UPS status is updated, the code must set either OL, OB, OL ECO,
		 * BYPASS, or OFF. These five options are mutually exclusive. If the register
		 * value is invalid and set none of these flags, failure code 1 is returned.
		 */
		dstate_datastale();
		return STAT_INSTCMD_FAILED;
	}
	status_commit();

	status = dstate_getinfo("ups.status");
	if (strstr(status, "OFF")) {
		/* no warning needed, continue at ups2000_write_register() below */
	}
	else if (strstr(status, "OL") || strstr(status, "OB")) {
		/*
		 * "Turning it on" has no effect if it's already on. Log a warning
		 * while still accepting and executing the command.
		 */
		upslogx(LOG_WARNING, "load.on: UPS is already on.");
		upslogx(LOG_WARNING, "load.on: still executing command anyway.");
	}
	else if (strstr(status, "BYPASS")) {
		/*
		 * If it's in bypass mode, reject this command. The UPS would otherwise
		 * enter normal mode, but "load.on" is not supposed to affect the
		 * normal/bypass status. Also log an error and suggest "bypass.stop".
		 */
		upslogx(LOG_ERR, "load.on error: UPS is already on, and is in bypass mode. "
				 "To enter normal mode, use bypass.stop");
		return STAT_INSTCMD_FAILED;
	}
	else {
		/* unreachable, see comments for r != 0 at the beginning */
		upslogx(LOG_ERR, "load.on error: invalid ups.status (%s) detected. "
				 "Please file a bug report!", status);
		return STAT_INSTCMD_FAILED;
	}

	r = ups2000_write_register(modbus_ctx, 10000 + reg, 1);
	if (r != 1)
		return STAT_INSTCMD_FAILED;
	return STAT_INSTCMD_HANDLED;
}


static int ups2000_instcmd_bypass_start(const uint16_t reg)
{
	int r;
	NUT_UNUSED_VARIABLE(reg);

	/* force update alarms */
	alarm_init();
	r = ups2000_update_alarm();
	if (r != 0)
		return STAT_INSTCMD_FAILED;
	alarm_commit();

	/* bypass input has a power failure, refuse to bypass */
	if (!bypass_available) {
		upslogx(LOG_ERR, "bypass input is abnormal, refuse to enter bypass mode.");
		return STAT_INSTCMD_FAILED;
	}

	/* enable "bypass on shutdown" */
	r = ups2000_write_register(modbus_ctx, 10000 + 1045, 1);
	if (r != 1)
		return STAT_INSTCMD_FAILED;

	/* shutdown */
	r = ups2000_write_register(modbus_ctx, 10000 + 1030, 1);
	if (r != 1)
		return STAT_INSTCMD_FAILED;

	return STAT_INSTCMD_HANDLED;
}


static int ups2000_instcmd_beeper_toggle(const uint16_t reg)
{
	int r;
	const char *string;

	r = ups2000_beeper_get(reg);
	if (r != 0)
		return STAT_INSTCMD_FAILED;

	string = dstate_getinfo("ups.beeper.status");
	if (!strcasecmp(string, "enabled"))
		r = ups2000_beeper_set(reg, "disabled");
	else if (!strcasecmp(string, "disabled"))
		r = ups2000_beeper_set(reg, "enabled");
	else
		return STAT_INSTCMD_FAILED;

	if (r != STAT_SET_HANDLED)
		return STAT_INSTCMD_FAILED;

	return STAT_INSTCMD_HANDLED;
}


/*
 * "ups.shutdown.stayoff": wait an optional offdelay and shutdown.
 * When the grid power returns, stay off.
 */
static int ups2000_instcmd_shutdown_stayoff(const uint16_t reg)
{
	uint16_t val;
	int r;

	r = setvar("ups.start.auto", "no");
	if (r != STAT_SET_HANDLED)
		return STAT_INSTCMD_FAILED;

	val = ups2000_offdelay * 10;  /* scaling factor */
	val /= 60;                    /* convert to minutes */

	r = ups2000_write_register(modbus_ctx, 10000 + reg, val);
	if (r != 1)
		return STAT_INSTCMD_FAILED;

	return STAT_INSTCMD_HANDLED;
}


/*
 * Wait for "offdelay" second, turn off the load. Then, wait
 * for "ondelay" seconds. If the grid power still exists or
 * has returned after the timer, turn on the load. Otherwise,
 * shutdown the UPS. When combined with "ups.start.auto", it
 * guarantees the server can always be restarted even if there
 * is a power race.
 *
 * "shutdown.return", "shutdown.reboot" and "shutdown.reboot.
 * graceful" all rely on this function.
 */
static int ups2000_shutdown_guaranteed_return(uint16_t offdelay, uint16_t ondelay)
{
	int r;
	uint16_t val[2];

	r = setvar("ups.start.auto", "yes");
	if (r != STAT_SET_HANDLED)
		return STAT_INSTCMD_FAILED;

	val[0] = (offdelay * 10) / 60;
	val[1] = ondelay / 60;

	r = ups2000_write_registers(modbus_ctx, 1047 + 10000, 2, val);
	if (r != 2)
		return STAT_INSTCMD_FAILED;

	return STAT_INSTCMD_HANDLED;
}


/*
 * "ups.shutdown.return": wait an optional "offdelay" and shutdown.
 * When the grid power returns, power on the load.
 */
static int ups2000_instcmd_shutdown_return(const uint16_t reg)
{
	int r;
	NUT_UNUSED_VARIABLE(reg);

	r = ups2000_shutdown_guaranteed_return(ups2000_offdelay,
					       ups2000_ondelay);
	if (r == STAT_INSTCMD_HANDLED) {
		shutdown_at = time_seek(time(NULL), ups2000_offdelay);
	}
	return r;
}


/*
 * "ups.shutdown.reboot": shutdown as soon as possible using the
 * smallest "rebootdelay" (inside the UPS, it's the same "ondelay"
 * timer), restart after an "ondelay".
 *
 * In our implementation, it's like "ups.shutdown.return", just
 * with a minimal "ondelay".
 */
static int ups2000_instcmd_shutdown_reboot(const uint16_t reg)
{
	int r;
	NUT_UNUSED_VARIABLE(reg);

	r = ups2000_shutdown_guaranteed_return(ups2000_rw_delay[REBOOT].min,
					       ups2000_ondelay);
	if (r == STAT_INSTCMD_HANDLED) {
		reboot_at = time_seek(time(NULL), ups2000_rw_delay[REBOOT].min);
		start_at = time_seek(reboot_at, ups2000_ondelay);
	}
	return r;
}


/*
 * "ups.shutdown.reboot.graceful": shutdown after a "rebootdelay"
 * (inside the UPS, it's the same "ondelay" timer), restart after
 * an "ondelay".
 *
 * In our implementation, it's like "ups.shutdown.return", just
 * with a "rebootdelay" instead of an "ondelay".
 */
static int ups2000_instcmd_shutdown_reboot_graceful(const uint16_t reg)
{
	int r;
	NUT_UNUSED_VARIABLE(reg);

	r = ups2000_shutdown_guaranteed_return(ups2000_rebootdelay,
					       ups2000_ondelay);
	if (r == STAT_INSTCMD_HANDLED) {
		reboot_at = time_seek(time(NULL), ups2000_rebootdelay);
		start_at = time_seek(reboot_at, ups2000_ondelay);
	}
	return r;
}


/*
 * List of countdown timers and pointers to their corresponding
 * global variables "at_time". They record estimated timestamps
 * when the actions are supposed to be performed.
 */
static struct {
	const char *name;
	time_t *const at_time;
} ups2000_timers[] = {
	{ "ups.timer.reboot",   &reboot_at   },
	{ "ups.timer.shutdown", &shutdown_at },
	{ "ups.timer.start",    &start_at    },
	{ NULL, NULL },
};


static int ups2000_update_timers(void)
{
	time_t now;
	int eta;
	int i;

	now = time(NULL);

	for (i = 0; ups2000_timers[i].name != NULL; i++) {
		if (*ups2000_timers[i].at_time) {
			eta = difftime(*ups2000_timers[i].at_time, now);
			if (eta < 0)
				eta = 0;
			dstate_setinfo(ups2000_timers[i].name, "%d", eta);
		}
		else {
			dstate_setinfo(ups2000_timers[i].name, "%d", -1);
		}
	}
	return 0;
}


void upsdrv_shutdown(void)
{
	int r;

	r = instcmd("shutdown.reboot", "");
	if (r != STAT_INSTCMD_HANDLED)
		fatalx(EXIT_FAILURE, "upsdrv_shutdown failed!");
}


void upsdrv_help(void)
{
}


/* list flags and values that you want to receive via -x */
void upsdrv_makevartable(void)
{
	char msg[64];

	snprintf(msg, 64, "Set shutdown delay, in seconds, 6-second step"
			  " (default=%d)", ups2000_rw_delay[SHUTDOWN].dfault);
	addvar(VAR_VALUE, "offdelay", msg);

	snprintf(msg, 64, "Set reboot delay, in seconds, 6-second step"
			  " (default=%d).", ups2000_rw_delay[REBOOT].dfault);
	addvar(VAR_VALUE, "rebootdelay", msg);

	snprintf(msg, 64, "Set start delay, in seconds, 60-second step"
			  " (default=%d).", ups2000_rw_delay[START].dfault);
	addvar(VAR_VALUE, "ondelay", msg);
}


void upsdrv_cleanup(void)
{
	if (modbus_ctx != NULL) {
		modbus_close(modbus_ctx);
		modbus_free(modbus_ctx);
	}
	ser_close(upsfd, device_path);
}


/*
 * Seek time "t" forward or backward by n "seconds" without assuming
 * the underlying type and format of "time_t". This ensures maximum
 * portability. Although on POSIX and many other systems, "time_t"
 * is guaranteed to be in seconds.
 *
 * On error, abort the program.
 */
static time_t time_seek(time_t t, int seconds)
{
	struct tm time_tm;
	time_t time_output;

	if (!t)
		fatalx(EXIT_FAILURE, "time_seek() failed!");

	if (!gmtime_r(&t, &time_tm))
		fatalx(EXIT_FAILURE, "time_seek() failed!");

	time_tm.tm_sec += seconds;
	time_output = mktime(&time_tm);

	if (time_output == (time_t) -1)
		fatalx(EXIT_FAILURE, "time_seek() failed!");

	return time_output;
}


/*
 * Read bytes from the UPS2000 serial port, until the buffer has
 * nothing left to read (after ser_get_buf() times out). The buffer
 * size is limited to buf_len (inclusive).
 *
 * On error, return 0.
 *
 * In the serial library, ser_get_buf() can be a short read, and
 * ser_get_buf_let() requires a precalculated length, necessiates
 * our own read function.
 */
static size_t ups2000_read_serial(uint8_t *buf, size_t buf_len)
{
	ssize_t bytes = 0;
	size_t total = 0;

	/* wait 400 ms for the device to process our command */
	usleep(400 * 1000);

	while (buf_len > 0) {
		bytes = ser_get_buf(upsfd, buf, buf_len, 1, 0);
		if (bytes < 0)
			return 0;      /* read failure */
		else if (bytes == 0)
			return total;  /* nothing to read */

		total += (size_t)bytes;        /* increment byte counter */
		buf += bytes;                  /* advance buffer position */
		if ((size_t)bytes > buf_len) {
			fatalx(EXIT_FAILURE, "ups2000_read_serial() read too much!");
		}
		buf_len -= (size_t)bytes;      /* decrement limiter */
	}
	return 0;  /* buffer exhaustion */
}


/*
 * Retry control. By default, we are in RETRY_ENABLE mode. For each
 * register read, we retry three times (1 sec. between each attempt),
 * before raising a fatal error and giving up. So far so good, but,
 * if the link went down, all operation would fail and the program
 * would become unresponsive due to excessive retrys.
 *
 * To prevent this problem, after the first fatal error, we stop all
 * retry attempts by entering RETRY_DISABLE_TEMPORARY mode, allowing
 * subsequent operation to fail without retry. Later, after the first
 * success is encountered, we move back to RETRY_ENABLE mode.
 */
enum {
	RETRY_ENABLE,
	RETRY_DISABLE_TEMPORARY
};
static int retry_status = RETRY_ENABLE;


/*
 * Read one or more registers using libmodbus.
 *
 * This is simply a wrapper for libmodbus's modbus_read_registers() with
 * retry and workaround logic. When an error has occured, we retry 3 times
 * before giving up, allowing us to recover from non-fatal failures without
 * triggering a data stale.
 */
static int ups2000_read_registers(modbus_t *ctx, int addr, int nb, uint16_t *dest)
{
	int i;
	int r = -1;

	if (addr < 10000)
		upslogx(LOG_ERR, "Invalid register read from %04d detected. "
				 "Please file a bug report!", addr);

	for (i = 0; i < 3; i++) {
		r = modbus_read_registers(ctx, addr, nb, dest);

		/* generic retry for modbus read failures. */
		if (retry_status == RETRY_ENABLE && r != nb) {
			upslogx(LOG_WARNING, "Register %04d has a read failure. Retrying...", addr);
			sleep(1);
			continue;
		}
		else if (r == nb)
			retry_status = RETRY_ENABLE;

		/*
		 * Workaround for buggy register 2002 (battery status). Sometimes
		 * this register returns invalid values. This is a known problem
		 * and it's not fatal, so we use LOG_INFO.
		 */
		if (retry_status == RETRY_ENABLE &&
		    addr == 12002 && (dest[0] < 2 || dest[0] > 5)) {
			upslogx(LOG_INFO, "Battery status has a non-fatal read failure, it's usually harmless. Retrying... ");
			sleep(1);
			continue;
		}
		else if (addr == 12002 && dest[0] >= 2 && dest[0] <= 5)
			retry_status = RETRY_ENABLE;

		return r;
	}

	/* Give up */
	upslogx(LOG_WARNING, "Register %04d has a fatal read failure.", addr);
	retry_status = RETRY_DISABLE_TEMPORARY;
	return r;
}


static int ups2000_write_registers(modbus_t *ctx, int addr, int nb, uint16_t *src)
{
	int i;
	int r = -1;

	if (addr < 10000)
		upslogx(LOG_ERR, "Invalid register write to %04d detected. "
				 "Please file a bug report!", addr);

	for (i = 0; i < 3; i++) {
		r = modbus_write_registers(ctx, addr, nb, src);

		/* generic retry for modbus write failures. */
		if (retry_status == RETRY_ENABLE && r != nb) {
			upslogx(LOG_WARNING, "Register %04d has a write failure. Retrying...", addr);
			sleep(1);
			continue;
		}
		else if (r == nb)
			retry_status = RETRY_ENABLE;

		return r;
	}

	/* Give up */
	upslogx(LOG_WARNING, "Register %04d has a fatal write failure.", addr);
	retry_status = RETRY_DISABLE_TEMPORARY;
	return r;
}


static int ups2000_write_register(modbus_t *ctx, int addr, uint16_t val)
{
	return ups2000_write_registers(ctx, addr, 1, &val);
}


/*
 * The following CRC-16 code was copied from libmodbus.
 *
 * Copyright (C) 2001-2011 Stéphane Raimbault <stephane.raimbault@gmail.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 */

/* Table of CRC values for high-order byte */
static const uint8_t table_crc_hi[] = {
	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
	0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
	0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
	0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
	0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
	0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
	0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
	0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
	0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
	0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
	0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
	0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
	0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
	0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
	0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
	0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
	0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
	0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
	0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
	0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
	0x80, 0x41, 0x00, 0xC1, 0x81, 0x40
};

/* Table of CRC values for low-order byte */
static const uint8_t table_crc_lo[] = {
	0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06,
	0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD,
	0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09,
	0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A,
	0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4,
	0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
	0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3,
	0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4,
	0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A,
	0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29,
	0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED,
	0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
	0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60,
	0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67,
	0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F,
	0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68,
	0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E,
	0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
	0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71,
	0x70, 0xB0, 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92,
	0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C,
	0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B,
	0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B,
	0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
	0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42,
	0x43, 0x83, 0x41, 0x81, 0x80, 0x40
};


static uint16_t crc16(uint8_t * buffer, uint16_t buffer_length)
{
	uint8_t crc_hi = 0xFF;	/* high CRC byte initialized */
	uint8_t crc_lo = 0xFF;	/* low CRC byte initialized */
	unsigned int i;		/* will index into CRC lookup */

	/* pass through message buffer */
	while (buffer_length--) {
		i = crc_hi ^ *buffer++;	/* calculate the CRC  */
		crc_hi = crc_lo ^ table_crc_hi[i];
		crc_lo = table_crc_lo[i];
	}

	return ((uint16_t)((uint16_t)(crc_hi) << 8) | (uint16_t)crc_lo);
}
