/* nutdrv_qx.c - Driver for USB and serial UPS units with Q* protocols
 *
 * Copyright (C)
 *   2013 Daniele Pezzini <hyouko@gmail.com>
 * Based on:
 *  usbhid-ups.c - Copyright (C)
 *    2003-2012 Arnaud Quette <arnaud.quette@gmail.com>
 *    2005      John Stamp <kinsayder@hotmail.com>
 *    2005-2006 Peter Selinger <selinger@users.sourceforge.net>
 *    2007-2009 Arjen de Korte <adkorte-guest@alioth.debian.org>
 *  blazer.c - Copyright (C)
 *    2008-2009 Arjen de Korte <adkorte-guest@alioth.debian.org>
 *    2012      Arnaud Quette <ArnaudQuette@Eaton.com>
 *  blazer_ser.c - Copyright (C)
 *    2008      Arjen de Korte <adkorte-guest@alioth.debian.org>
 *  blazer_usb.c - Copyright (C)
 *    2003-2009 Arjen de Korte <adkorte-guest@alioth.debian.org>
 *    2011-2012 Arnaud Quette <arnaud.quette@free.fr>
 *
 * 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
 *
 */

#define DRIVER_VERSION	"0.28"

#include "main.h"

#include <math.h>

/* note: QX_USB/QX_SERIAL set through Makefile */
#ifdef QX_USB
	#include "libusb.h"
	#include "usb-common.h"

	#ifdef QX_SERIAL
		#define DRIVER_NAME	"Generic Q* USB/Serial driver"
	#else
		#define	DRIVER_NAME	"Generic Q* USB driver"
	#endif	/* QX_SERIAL */
#else
	#define DRIVER_NAME	"Generic Q* Serial driver"
#endif	/* QX_USB */

#ifdef QX_SERIAL
	#include "serial.h"
	#define SER_WAIT_SEC	1	/* 3 seconds for Best UPS */
#endif	/* QX_SERIAL */

#include "nutdrv_qx.h"

/* == Subdrivers == */
/* Include all known subdrivers */
#include "nutdrv_qx_bestups.h"
#include "nutdrv_qx_mecer.h"
#include "nutdrv_qx_megatec.h"
#include "nutdrv_qx_megatec-old.h"
#include "nutdrv_qx_mustek.h"
#include "nutdrv_qx_q1.h"
#include "nutdrv_qx_voltronic.h"
#include "nutdrv_qx_voltronic-qs.h"
#include "nutdrv_qx_voltronic-qs-hex.h"
#include "nutdrv_qx_zinto.h"

/* Master list of available subdrivers */
static subdriver_t	*subdriver_list[] = {
	&voltronic_subdriver,
	&voltronic_qs_subdriver,
	&voltronic_qs_hex_subdriver,
	&mustek_subdriver,
	&megatec_old_subdriver,
	&bestups_subdriver,
	&mecer_subdriver,
	&megatec_subdriver,
	&zinto_subdriver,
	/* Fallback Q1 subdriver */
	&q1_subdriver,
	NULL
};


/* == Driver description structure == */
upsdrv_info_t	upsdrv_info = {
	DRIVER_NAME,
	DRIVER_VERSION,
	"Daniele Pezzini <hyouko@gmail.com>" \
	"Arnaud Quette <arnaud.quette@gmail.com>" \
	"John Stamp <kinsayder@hotmail.com>" \
	"Peter Selinger <selinger@users.sourceforge.net>" \
	"Arjen de Korte <adkorte-guest@alioth.debian.org>",
	DRV_BETA,
#ifdef QX_USB
	{ &comm_upsdrv_info, NULL }
#else
	{ NULL }
#endif	/* QX_USB */
};


/* == Data walk modes == */
typedef enum {
	QX_WALKMODE_INIT = 0,
	QX_WALKMODE_QUICK_UPDATE,
	QX_WALKMODE_FULL_UPDATE
} walkmode_t;


/* == Global vars == */
/* Pointer to the active subdriver object (changed in subdriver_matcher() function) */
static subdriver_t	*subdriver = NULL;

static int	pollfreq = DEFAULT_POLLFREQ;
static int	ups_status = 0;
static bool_t	data_has_changed = FALSE;	/* for SEMI_STATIC data polling */

static time_t	lastpoll;	/* Timestamp the last polling */

#if defined(QX_USB) && defined(QX_SERIAL)
static int	is_usb = 0;	/* Whether the device is connected through USB (1) or serial (0) */
#endif	/* QX_USB && QX_SERIAL */

static struct {
	char	command[SMALLBUF];	/* Command sent to the UPS to get answer/to execute an instant command */
	char	answer[SMALLBUF];	/* Answer from the UPS, filled at runtime */
} previous_item = { "", "" };	/* Hold the values of the item processed just before the actual one */


/* == Support functions == */
static int	subdriver_matcher(void);
static int	qx_command(const char *cmd, char *buf, size_t buflen);
static int	qx_process_answer(item_t *item, const int len);
static bool_t	qx_ups_walk(walkmode_t mode);
static void	ups_status_set(void);
static void	ups_alarm_set(void);
static void	qx_set_var(item_t *item);


/* == Struct & data for status processing == */
typedef struct {
	const char	*status_str;	/* UPS status string */
	const int	status_mask;	/* UPS status mask */
} status_lkp_t;

static status_lkp_t	status_info[] = {
	/* Map status strings to bit masks */
	{ "OL", STATUS(OL) },
	{ "LB", STATUS(LB) },
	{ "RB", STATUS(RB) },
	{ "CHRG", STATUS(CHRG) },
	{ "DISCHRG", STATUS(DISCHRG) },
	{ "BYPASS", STATUS(BYPASS) },
	{ "CAL", STATUS(CAL) },
	{ "OFF", STATUS(OFF) },
	{ "OVER", STATUS(OVER) },
	{ "TRIM", STATUS(TRIM) },
	{ "BOOST", STATUS(BOOST) },
	{ "FSD", STATUS(FSD) },
	{ NULL, 0 },
};


/* == battery.{charge,runtime} guesstimation == */
/* Support functions */
static int	qx_battery(void);
static int	qx_load(void);
static void	qx_initbattery(void);

/* Battery data */
static struct {
	double	packs;	/* Battery voltage multiplier */
	struct {
		double	act;	/* Actual runtime on battery */
		double	nom;	/* Nominal runtime on battery (full load) */
		double	est;	/* Estimated runtime remaining (full load) */
		double	exp;	/* Load exponent */
	} runt;
	struct {
		double	act;	/* Actual battery voltage */
		double	high;	/* Battery float voltage */
		double	nom;	/* Nominal battery voltage */
		double	low;	/* Battery low voltage */
	} volt;
	struct {
		double	act;	/* Actual battery charge */
		long	time;	/* Recharge time from empty to full */
	} chrg;
} batt = { 1, { -1, -1, 0, 0 }, { -1, -1, -1, -1 }, { -1, 43200 } };

/* Load data */
static struct {
	double	act;	/* Actual load (reported by the UPS) */
	double	low;	/* Idle load */
	double	eff;	/* Effective load */
} load = { 0, 0.1, 1 };

static time_t	battery_lastpoll = 0;

/* Fill batt.volt.act and guesstimate the battery charge if it isn't already available. */
static int	qx_battery(void)
{
	const char	*val = dstate_getinfo("battery.voltage");

	if (!val) {
		upsdebugx(2, "%s: unable to get battery.voltage", __func__);
		return -1;
	}

	batt.volt.act = batt.packs * strtod(val, NULL);

	if (batt.chrg.act == -1 && batt.volt.low > 0 && batt.volt.high > batt.volt.low) {

		batt.chrg.act = 100 * (batt.volt.act - batt.volt.low) / (batt.volt.high - batt.volt.low);

		if (batt.chrg.act < 0) {
			batt.chrg.act = 0;
		}

		if (batt.chrg.act > 100) {
			batt.chrg.act = 100;
		}

		dstate_setinfo("battery.charge", "%.0f", batt.chrg.act);

	}

	return 0;
}

/* Load for battery.{charge,runtime} from runtimecal */
static int	qx_load(void)
{
	const char	*val = dstate_getinfo("ups.load");

	if (!val) {
		upsdebugx(2, "%s: unable to get ups.load", __func__);
		return -1;
	}

	load.act = strtod(val, NULL);

	load.eff = pow(load.act / 100, batt.runt.exp);

	if (load.eff < load.low) {
		load.eff = load.low;
	}

	return 0;
}

/* Guesstimation: init */
static void	qx_initbattery(void)
{
	if (!dstate_getinfo("battery.charge") || !dstate_getinfo("battery.runtime")) {

		const char	*val;

		val = dstate_getinfo("battery.voltage.high");
		if (val) {
			batt.volt.high = strtod(val, NULL);
		}

		val = dstate_getinfo("battery.voltage.low");
		if (val) {
			batt.volt.low = strtod(val, NULL);
		}

		val = dstate_getinfo("battery.voltage.nominal");
		if (val) {
			batt.volt.nom = strtod(val, NULL);
		}

		/* If no values are available for both battery.voltage.{low,high} either from the UPS or provided by the user in ups.conf, try to guesstimate them, but announce it! */
		if (batt.volt.nom != -1 && (batt.volt.low == -1 || batt.volt.high == -1)) {

			upslogx(LOG_INFO, "No values for battery high/low voltages");

			/* Basic formula, which should cover most cases */
			batt.volt.low = 104 * batt.volt.nom / 120;
			batt.volt.high = 130 * batt.volt.nom / 120;

			/* Publish these data too */
			dstate_setinfo("battery.voltage.low", "%.2f", batt.volt.low);
			dstate_setinfo("battery.voltage.high", "%.2f", batt.volt.high);

			upslogx(LOG_INFO, "Using 'guesstimation' (low: %f, high: %f)!", batt.volt.low, batt.volt.high);

		}

		val = dstate_getinfo("battery.packs");
		if (val && (strspn(val, "0123456789 .") == strlen(val))) {
			batt.packs = strtod(val, NULL);
		} else {

			/* qx_battery -> batt.volt.act */
			if (!qx_battery() && batt.volt.nom != -1) {

				const double	packs[] = { 120, 100, 80, 60, 48, 36, 30, 24, 18, 12, 8, 6, 4, 3, 2, 1, 0.5, -1 };
				int		i;

				/* The battery voltage will quickly return to at least the nominal value after discharging them.
				 * For overlapping battery.voltage.low/high ranges therefor choose the one with the highest multiplier. */
				for (i = 0; packs[i] > 0; i++) {

					if (packs[i] * batt.volt.act > 1.2 * batt.volt.nom) {
						continue;
					}

					if (packs[i] * batt.volt.act < 0.8 * batt.volt.nom) {
						upslogx(LOG_INFO, "Can't autodetect number of battery packs [%.0f/%.2f]", batt.volt.nom, batt.volt.act);
						break;
					}

					batt.packs = packs[i];
					break;

				}

			} else {
				upslogx(LOG_INFO, "Can't autodetect number of battery packs [%.0f/%.2f]", batt.volt.nom, batt.volt.act);
			}

		}

		/* Update batt.{chrg,volt}.act */
		qx_battery();

		val = getval("runtimecal");
		if (val) {

			double	rh, lh, rl, ll;

			time(&battery_lastpoll);

			if (sscanf(val, "%lf,%lf,%lf,%lf", &rh, &lh, &rl, &ll) < 4) {
				fatalx(EXIT_FAILURE, "Insufficient parameters for runtimecal");
			}

			if ((rl < rh) || (rh <= 0)) {
				fatalx(EXIT_FAILURE, "Parameter out of range (runtime)");
			}

			if ((lh > 100) || (ll > lh) || (ll <= 0)) {
				fatalx(EXIT_FAILURE, "Parameter out of range (load)");
			}

			batt.runt.exp = log(rl / rh) / log(lh / ll);
			upsdebugx(2, "%s: battery runtime exponent: %.3f", __func__, batt.runt.exp);

			batt.runt.nom = rh * pow(lh / 100, batt.runt.exp);
			upsdebugx(2, "%s: battery runtime nominal: %.1f", __func__, batt.runt.nom);

		} else {

			upslogx(LOG_INFO, "Battery runtime will not be calculated (runtimecal not set)");
			return;

		}

		val = dstate_getinfo("battery.charge");
		if (!val && batt.volt.nom != -1) {
			batt.volt.low = batt.volt.nom;
			batt.volt.high = 1.15 * batt.volt.nom;

			if (qx_battery())
				fatalx(EXIT_FAILURE, "Initial battery charge undetermined");

			val = dstate_getinfo("battery.charge");
		}

		if (val) {
			batt.runt.est = batt.runt.nom * strtod(val, NULL) / 100;
			upsdebugx(2, "%s: battery runtime estimate: %.1f", __func__, batt.runt.est);
		} else {
			fatalx(EXIT_FAILURE, "Initial battery charge undetermined");
		}

		val = getval("chargetime");
		if (val) {
			batt.chrg.time = strtol(val, NULL, 10);

			if (batt.chrg.time <= 0) {
				fatalx(EXIT_FAILURE, "Charge time out of range [1..s]");
			}

			upsdebugx(2, "%s: battery charge time: %ld", __func__, batt.chrg.time);
		} else {
			upslogx(LOG_INFO, "No charge time specified, using built in default [%ld seconds]", batt.chrg.time);
		}

		val = getval("idleload");
		if (val) {
			load.low = strtod(val, NULL) / 100;

			if ((load.low <= 0) || (load.low > 1)) {
				fatalx(EXIT_FAILURE, "Idle load out of range [0..100]");
			}

			upsdebugx(2, "%s: minimum load used (idle): %.3f", __func__, load.low);
		} else {
			upslogx(LOG_INFO, "No idle load specified, using built in default [%.1f %%]", 100 * load.low);
		}
	}
}


/* == USB communication subdrivers == */
#if defined(QX_USB) && !defined(TESTING)
static usb_communication_subdriver_t	*usb = &usb_subdriver;
static usb_dev_handle			*udev = NULL;
static USBDevice_t			usbdevice;
static USBDeviceMatcher_t		*reopen_matcher = NULL;
static USBDeviceMatcher_t		*regex_matcher = NULL;
static int				langid_fix = -1;

static int	(*subdriver_command)(const char *cmd, char *buf, size_t buflen) = NULL;

/* Cypress communication subdriver */
static int	cypress_command(const char *cmd, char *buf, size_t buflen)
{
	char	tmp[SMALLBUF];
	int	ret;
	size_t	i;

	/* Send command */
	memset(tmp, 0, sizeof(tmp));
	snprintf(tmp, sizeof(tmp), "%s", cmd);

	for (i = 0; i < strlen(tmp); i += ret) {

		/* Write data in 8-byte chunks */
		/* ret = usb->set_report(udev, 0, (unsigned char *)&tmp[i], 8); */
		ret = usb_control_msg(udev, USB_ENDPOINT_OUT + USB_TYPE_CLASS + USB_RECIP_INTERFACE, 0x09, 0x200, 0, &tmp[i], 8, 5000);

		if (ret <= 0) {
			upsdebugx(3, "send: %s (%d)", ret ? usb_strerror() : "timeout", ret);
			return ret;
		}

	}

	upsdebugx(3, "send: %.*s", (int)strcspn(tmp, "\r"), tmp);

	/* Read reply */
	memset(buf, 0, buflen);

	for (i = 0; (i <= buflen-8) && (memchr(buf, '\r', buflen) == NULL); i += ret) {

		/* Read data in 8-byte chunks */
		/* ret = usb->get_interrupt(udev, (unsigned char *)&buf[i], 8, 1000); */
		ret = usb_interrupt_read(udev, 0x81, &buf[i], 8, 1000);

		/* Any errors here mean that we are unable to read a reply (which will happen after successfully writing a command to the UPS) */
		if (ret <= 0) {
			upsdebugx(3, "read: %s (%d)", ret ? usb_strerror() : "timeout", ret);
			return ret;
		}

		snprintf(tmp, sizeof(tmp), "read [% 3d]", (int)i);
		upsdebug_hex(5, tmp, &buf[i], ret);

	}

	upsdebugx(3, "read: %.*s", (int)strcspn(buf, "\r"), buf);
	return i;
}

/* SGS communication subdriver */
static int	sgs_command(const char *cmd, char *buf, size_t buflen)
{
	char	tmp[SMALLBUF];
	int	ret;
	size_t  cmdlen, i;

	/* Send command */
	cmdlen = strlen(cmd);

	for (i = 0; i < cmdlen; i += ret) {

		memset(tmp, 0, sizeof(tmp));

		ret = (cmdlen - i) < 7 ? (cmdlen - i) : 7;

		tmp[0] = ret;
		memcpy(&tmp[1], &cmd[i], ret);

		/* Write data in 8-byte chunks */
		ret = usb_control_msg(udev, USB_ENDPOINT_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, 0x09, 0x200, 0, tmp, 8, 5000);

		if (ret <= 0) {
			upsdebugx(3, "send: %s (%d)", ret ? usb_strerror() : "timeout", ret);
			return ret;
		}

		ret--;

	}

	upsdebugx(3, "send: %.*s", (int)strcspn(cmd, "\r"), cmd);

	/* Read reply */
	memset(buf, 0, buflen);

	for (i = 0; i <= buflen - 8; i += ret) {

		memset(tmp, 0, sizeof(tmp));

		/* Read data in 8-byte chunks */
		ret = usb_interrupt_read(udev, 0x81, tmp, 8, 1000);

		/* No error!!! */
		if (ret == -110)
			break;

		/* Any errors here mean that we are unable to read a reply (which will happen after successfully writing a command to the UPS) */
		if (ret <= 0) {
			upsdebugx(3, "read: %s (%d)", ret ? usb_strerror() : "timeout", ret);
			return ret;
		}

		/* Every call to read returns 8 bytes
		 * -> actually returned bytes: */
		ret = tmp[0] <= 7 ? tmp[0] : 7;

		if (ret > 0)
			memcpy(&buf[i], &tmp[1], ret);

		snprintf(tmp, sizeof(tmp), "read [% 3d]", (int)i);
		upsdebug_hex(5, tmp, &buf[i], ret);

	}

	upsdebugx(3, "read: %.*s", (int)strcspn(buf, "\r"), buf);
	return i;
}

/* Phoenix communication subdriver */
static int	phoenix_command(const char *cmd, char *buf, size_t buflen)
{
	char	tmp[SMALLBUF];
	int	ret;
	size_t	i;

	for (i = 0; i < 8; i++) {

		/* Read data in 8-byte chunks */
		/* ret = usb->get_interrupt(udev, (unsigned char *)tmp, 8, 1000); */
		ret = usb_interrupt_read(udev, 0x81, tmp, 8, 1000);

		/* This USB to serial implementation is crappy.
		 * In order to read correct replies we need to flush the output buffers of the converter until we get no more data (ie, it times out). */
		switch (ret)
		{
		case -EPIPE:		/* Broken pipe */
			usb_clear_halt(udev, 0x81);
		case -ETIMEDOUT:	/* Connection timed out */
			break;
		}

		if (ret < 0) {
			upsdebugx(3, "flush: %s (%d)", usb_strerror(), ret);
			break;
		}

		upsdebug_hex(4, "dump", tmp, ret);

	}

	/* Send command */
	memset(tmp, 0, sizeof(tmp));
	snprintf(tmp, sizeof(tmp), "%s", cmd);

	for (i = 0; i < strlen(tmp); i += ret) {

		/* Write data in 8-byte chunks */
		/* ret = usb->set_report(udev, 0, (unsigned char *)&tmp[i], 8); */
		ret = usb_control_msg(udev, USB_ENDPOINT_OUT + USB_TYPE_CLASS + USB_RECIP_INTERFACE, 0x09, 0x200, 0, &tmp[i], 8, 1000);

		if (ret <= 0) {
			upsdebugx(3, "send: %s (%d)", ret ? usb_strerror() : "timeout", ret);
			return ret;
		}

	}

	upsdebugx(3, "send: %.*s", (int)strcspn(tmp, "\r"), tmp);

	/* Read reply */
	memset(buf, 0, buflen);

	for (i = 0; (i <= buflen-8) && (memchr(buf, '\r', buflen) == NULL); i += ret) {

		/* Read data in 8-byte chunks */
		/* ret = usb->get_interrupt(udev, (unsigned char *)&buf[i], 8, 1000); */
		ret = usb_interrupt_read(udev, 0x81, &buf[i], 8, 1000);

		/* Any errors here mean that we are unable to read a reply (which will happen after successfully writing a command to the UPS) */
		if (ret <= 0) {
			upsdebugx(3, "read: %s (%d)", ret ? usb_strerror() : "timeout", ret);
			return ret;
		}

		snprintf(tmp, sizeof(tmp), "read [% 3d]", (int)i);
		upsdebug_hex(5, tmp, &buf[i], ret);

	}

	upsdebugx(3, "read: %.*s", (int)strcspn(buf, "\r"), buf);
	return i;
}

/* Ippon communication subdriver */
static int	ippon_command(const char *cmd, char *buf, size_t buflen)
{
	char	tmp[64];
	int	ret;
	size_t	i, len;

	/* Send command */
	snprintf(tmp, sizeof(tmp), "%s", cmd);

	for (i = 0; i < strlen(tmp); i += ret) {

		/* Write data in 8-byte chunks */
		ret = usb_control_msg(udev, USB_ENDPOINT_OUT + USB_TYPE_CLASS + USB_RECIP_INTERFACE, 0x09, 0x2, 0, &tmp[i], 8, 1000);

		if (ret <= 0) {
			upsdebugx(3, "send: %s (%d)", (ret != -ETIMEDOUT) ? usb_strerror() : "Connection timed out", ret);
			return ret;
		}

	}

	upsdebugx(3, "send: %.*s", (int)strcspn(tmp, "\r"), tmp);

	/* Read all 64 bytes of the reply in one large chunk */
	ret = usb_interrupt_read(udev, 0x81, tmp, sizeof(tmp), 1000);

	/* Any errors here mean that we are unable to read a reply (which will happen after successfully writing a command to the UPS) */
	if (ret <= 0) {
		upsdebugx(3, "read: %s (%d)", (ret != -ETIMEDOUT) ? usb_strerror() : "Connection timed out", ret);
		return ret;
	}

	/* As Ippon will always return 64 bytes in response, we have to calculate and return length of actual response data here.
	 * Empty response will look like 0x00 0x0D, otherwise it will be data string terminated by 0x0D. */

	for (i = 0, len = 0; i < (size_t)ret; i++) {

		if (tmp[i] != '\r')
			continue;

		len = ++i;
		break;

	}

	/* Just in case there wasn't any '\r', fallback to string length, if any */
	if (!len)
		len = strlen(tmp);

	upsdebug_hex(5, "read", tmp, (int)len);
	upsdebugx(3, "read: %.*s", (int)strcspn(tmp, "\r"), tmp);

	len = len < buflen ? len : buflen - 1;

	memset(buf, 0, buflen);
	memcpy(buf, tmp, len);

	return (int)len;
}

/* Krauler communication subdriver */
static int	krauler_command(const char *cmd, char *buf, size_t buflen)
{
	/* Still not implemented:
	 * 0x6	T<n>	(don't know how to pass the parameter)
	 * 0x68 and 0x69 both cause shutdown after an undefined interval */
	const struct {
		const char	*str;	/* Megatec command */
		const int	index;	/* Krauler string index for this command */
		const char	prefix;	/* Character to replace the first byte in reply */
	} command[] = {
		{ "Q1\r", 0x03, '(' },
		{ "F\r", 0x0d, '#' },
		{ "I\r", 0x0c, '#' },
		{ "T\r", 0x04, '\r' },
		{ "TL\r", 0x05, '\r' },
		{ "Q\r", 0x07, '\r' },
		{ "C\r", 0x0b, '\r' },
		{ "CT\r", 0x0b, '\r' },
		{ NULL }
	};

	int	i;

	upsdebugx(3, "send: %.*s", (int)strcspn(cmd, "\r"), cmd);

	for (i = 0; command[i].str; i++) {

		int	retry;

		if (strcmp(cmd, command[i].str)) {
			continue;
		}

		for (retry = 0; retry < 10; retry++) {

			int	ret;

			if (langid_fix != -1) {
				/* Apply langid_fix value */
				ret = usb_get_string(udev, command[i].index, langid_fix, buf, buflen);
			} else {
				ret = usb_get_string_simple(udev, command[i].index, buf, buflen);
			}

			if (ret <= 0) {
				upsdebugx(3, "read: %s (%d)", ret ? usb_strerror() : "timeout", ret);
				return ret;
			}

			/* This may serve in the future */
			upsdebugx(1, "received %d (%d)", ret, buf[0]);

			if (langid_fix != -1) {
				/* Limit this check, at least for now */
				/* Invalid receive size - message corrupted */
				if (ret != buf[0]) {
					upsdebugx(1, "size mismatch: %d / %d", ret, buf[0]);
					continue;
				}

				/* Simple unicode -> ASCII inplace conversion
				 * FIXME: this code is at least shared with mge-shut/libshut
				 * Create a common function? */
				unsigned int	di, si, size = buf[0];
				for (di = 0, si = 2; si < size; si += 2) {

					if (di >= (buflen - 1))
						break;

					if (buf[si + 1])	/* high byte */
						buf[di++] = '?';
					else
						buf[di++] = buf[si];

				}

				buf[di] = 0;
				ret = di;
			}

			/* "UPS No Ack" has a special meaning */
			if (
				strcspn(buf, "\r") == 10 &&
				!strncasecmp(buf, "UPS No Ack", 10)
			) {
				upsdebugx(3, "read: %.*s", (int)strcspn(buf, "\r"), buf);
				continue;
			}

			/* Replace the first byte of what we received with the correct one */
			buf[0] = command[i].prefix;

			upsdebug_hex(5, "read", buf, ret);
			upsdebugx(3, "read: %.*s", (int)strcspn(buf, "\r"), buf);

			return ret;

		}

		return 0;

	}

	/* Echo the unknown command back */
	upsdebugx(3, "read: %.*s", (int)strcspn(cmd, "\r"), cmd);
	return snprintf(buf, buflen, "%s", cmd);
}

/* Fabula communication subdriver */
static int	fabula_command(const char *cmd, char *buf, size_t buflen)
{
	const struct {
		const char	*str;	/* Megatec command */
		const int	index;	/* Fabula string index for this command */
	} commands[] = {
		{ "Q1\r",	0x03, },	/* Status */
		{ "F\r",	0x0d, },	/* Ratings */
		{ "I\r",	0x0c, },	/* Vendor infos */
		{ "Q\r",	0x07, },	/* Beeper toggle */
		{ "C\r",	0x0a, },	/* Cancel shutdown/Load on [0x(0..F)A]*/
		{ NULL }
	};
	int	i, ret, index = 0;

	upsdebugx(3, "send: %.*s", (int)strcspn(cmd, "\r"), cmd);

	for (i = 0; commands[i].str; i++) {

		if (strcmp(cmd, commands[i].str))
			continue;

		index = commands[i].index;
		break;

	}

	if (!index) {

		int	val2 = -1;
		double	val1 = -1;

		/* Shutdowns */
		if (
			sscanf(cmd, "S%lfR%d\r", &val1, &val2) == 2 ||
			sscanf(cmd, "S%lf\r", &val1) == 1
		) {

			double	delay;

			/* 0x(1+)0 -> shutdown.stayoff (SnR0000)
			 * 0x(1+)8 -> shutdown.return (Sn[Rm], m != 0) [delay before restart is always 10 seconds]
			 * +0x10 (16dec) = next megatec delay (min .5 = hex 0x1*; max 10 = hex 0xF*) -> n < 1 ? -> n += .1; n >= 1 ? -> n += 1 */

			/* delay: [.5..10] (-> seconds: [30..600]) */
			delay = val1 < .5 ? .5 : val1 > 10 ? 10 : val1;

			if (delay < 1)
				index = 16 + round((delay - .5) * 10) * 16;
			else
				index = 96 + (delay - 1) * 16;

			/* shutdown.return (Sn[Rm], m != 0) */
			if (val2)
				index += 8;

		/* Unknown commands */
		} else {

			/* Echo the unknown command back */
			upsdebugx(3, "read: %.*s", (int)strcspn(cmd, "\r"), cmd);
			return snprintf(buf, buflen, "%s", cmd);

		}

	}

	upsdebugx(4, "command index: 0x%02x", index);

	/* Send command/Read reply */
	ret = usb_get_string_simple(udev, index, buf, buflen);

	if (ret <= 0) {
		upsdebugx(3, "read: %s (%d)", ret ? usb_strerror() : "timeout", ret);
		return ret;
	}

	upsdebug_hex(5, "read", buf, ret);
	upsdebugx(3, "read: %.*s", (int)strcspn(buf, "\r"), buf);

	/* The UPS always replies "UPS No Ack" when a supported command is issued (either if it fails or if it succeeds).. */
	if (
		strcspn(buf, "\r") == 10 &&
		!strncasecmp(buf, "UPS No Ack", 10)
	) {
		/* ..because of that, always return 0 (with buf empty, as if it was a timeout): queries will see it as a failure, instant commands ('megatec' protocol) as a success */
		memset(buf, 0, buflen);
		return 0;
	}

	return ret;
}

/* Fuji communication subdriver */
static int	fuji_command(const char *cmd, char *buf, size_t buflen)
{
	unsigned char	tmp[8];
	char		command[SMALLBUF] = "",
			read[SMALLBUF] = "";
	int		ret, answer_len, val2;
	double		val1;
	size_t		i;
	const struct {
		const char	*command;	/* Megatec command */
		const int	answer_len;	/* Expected length of the answer to the ongoing query */
	} query[] = {
		{ "Q1",	47 },
		{ "F",	22 },
		{ "I",	39 },
		{ NULL }
	};

	/*
	 * Queries (b1..b8) sent (as a 8-bytes interrupt) to the UPS adopt the following scheme:
	 *
	 *	b1:		0x80
	 *	b2:		0x06
	 *	b3:		<LEN>
	 *	b4:		0x03
	 *	b5..bn:		<COMMAND>
	 *	bn+1..b7:	[<PADDING>]
	 *	b8:		<ANSWER_LEN>
	 *
	 * Where:
	 *	<LEN>		Length (in Hex) of the command (without the trailing CR) + 1
	 *	<COMMAND>	Command/query (without the trailing CR)
	 *	[<PADDING>]	0x00 padding to the 7th byte
	 *	<ANSWER_LEN>	Expected length (in Hex) of the answer to the ongoing query (0 when no reply is expected, i.e. commands)
	 *
	 * Replies to queries (commands are followed by action without any reply) are sent from the UPS (in 8-byte chunks) with 0x00 padding after the trailing CR to full 8 bytes.
	 *
	 */

	/* Send command */

	/* Remove the CR */
	snprintf(command, sizeof(command), "%.*s", (int)strcspn(cmd, "\r"), cmd);

	/* Length of the command that will be sent to the UPS can be at most: 8 - 5 (0x80, 0x06, <LEN>, 0x03, <ANSWER_LEN>) = 3.
	 * As a consequence also 'SnRm' commands (shutdown.{return,stayoff} and load.off) are not supported.
	 * So, map all the 'SnRm' shutdown.returns (m != 0) as the corresponding 'Sn' commands, meanwhile ignoring ups.delay.start and making the UPS turn on the load as soon as power is back. */
	if (sscanf(cmd, "S%lfR%d\r", &val1, &val2) == 2 && val2) {
		upsdebugx(4, "%s: trimming '%s' to '%.*s'", __func__, command, 3, command);
		command[3] = 0;
	}
	/* Too long command */
	if (strlen(command) > 3) {
		/* Be 'megatec-y': echo the unsupported command back */
		upsdebugx(3, "%s: unsupported command %s", __func__, command);
		return snprintf(buf, buflen, "%s", cmd);
	}

	/* Expected length of the answer to the ongoing query (0 when no reply is expected, i.e. commands) */
	answer_len = 0;
	for (i = 0; query[i].command; i++) {

		if (strcmp(command, query[i].command))
			continue;

		answer_len = query[i].answer_len;
		break;

	}

	memset(tmp, 0, sizeof(tmp));

	/* 0x80 */
	tmp[0] = 0x80;
	/* 0x06 */
	tmp[1] = 0x06;
	/* <LEN> */
	tmp[2] = strlen(command) + 1;
	/* 0x03 */
	tmp[3] = 0x03;
	/* <COMMAND> */
	memcpy(&tmp[4], command, strlen(command));
	/* <ANSWER_LEN> */
	tmp[7] = answer_len;

	upsdebug_hex(4, "command", (char *)tmp, 8);

	/* Write data */
	ret = usb_interrupt_write(udev, USB_ENDPOINT_OUT | 2, (char *)tmp, 8, USB_TIMEOUT);

	if (ret <= 0) {
		upsdebugx(3, "send: %s (%d)", ret ? usb_strerror() : "timeout", ret);
		return ret;
	}

	upsdebugx(3, "send: %s", command);

	/* Read reply */

	memset(buf, 0, buflen);

	for (i = 0; (i <= buflen - 8) && (memchr(buf, '\r', buflen) == NULL); i += ret) {

		/* Read data in 8-byte chunks */
		ret = usb_interrupt_read(udev, USB_ENDPOINT_IN | 1, &buf[i], 8, 1000);

		/* Any errors here mean that we are unable to read a reply (which will happen after successfully writing a command to the UPS) */
		if (ret <= 0) {
			upsdebugx(3, "read: %s (%d)", ret ? usb_strerror() : "timeout", ret);
			return ret;
		}

		snprintf(read, sizeof(read), "read [%3d]", (int)i);
		upsdebug_hex(5, read, &buf[i], ret);

	}

	upsdebugx(3, "read: %.*s", (int)strcspn(buf, "\r"), buf);

	/* As Fuji units return the reply in 8-byte chunks always padded to the 8th byte with 0x00, we need to calculate and return the length of the actual response here. */
	return (int)strlen(buf);
}

static void	*cypress_subdriver(USBDevice_t *device)
{
	subdriver_command = &cypress_command;
	return NULL;
}

static void	*sgs_subdriver(USBDevice_t *device)
{
	subdriver_command = &sgs_command;
	return NULL;
}

static void	*ippon_subdriver(USBDevice_t *device)
{
	subdriver_command = &ippon_command;
	return NULL;
}

static void	*krauler_subdriver(USBDevice_t *device)
{
	subdriver_command = &krauler_command;
	return NULL;
}

static void	*phoenix_subdriver(USBDevice_t *device)
{
	subdriver_command = &phoenix_command;
	return NULL;
}

static void	*fabula_subdriver(USBDevice_t *device)
{
	subdriver_command = &fabula_command;
	return NULL;
}

static void	*fuji_subdriver(USBDevice_t *device)
{
	subdriver_command = &fuji_command;
	return NULL;
}

/* USB device match structure */
typedef struct {
	const int	vendorID;		/* USB device's VendorID */
	const int	productID;		/* USB device's ProductID */
	const char	*vendor;		/* USB device's iManufacturer string */
	const char	*product;		/* USB device's iProduct string */
	void		*(*fun)(USBDevice_t *);	/* Handler for specific processing */
} qx_usb_device_id_t;

/* USB VendorID/ProductID/iManufacturer/iProduct match - note: rightmost comment is used for naming rules by tools/nut-usbinfo.pl */
static qx_usb_device_id_t	qx_usb_id[] = {
	{ USB_DEVICE(0x05b8, 0x0000),	NULL,		NULL,			&cypress_subdriver },	/* Agiler UPS */
	{ USB_DEVICE(0xffff, 0x0000),	NULL,		NULL,			&krauler_subdriver },	/* Ablerex 625L USB */
	{ USB_DEVICE(0x0665, 0x5161),	NULL,		NULL,			&cypress_subdriver },	/* Belkin F6C1200-UNV/Voltronic Power UPSes */
	{ USB_DEVICE(0x06da, 0x0002),	NULL,		NULL,			&cypress_subdriver },	/* Online Yunto YQ450 */
	{ USB_DEVICE(0x06da, 0x0003),	NULL,		NULL,			&ippon_subdriver },	/* Mustek Powermust */
	{ USB_DEVICE(0x06da, 0x0004),	NULL,		NULL,			&cypress_subdriver },	/* Phoenixtec Innova 3/1 T */
	{ USB_DEVICE(0x06da, 0x0005),	NULL,		NULL,			&cypress_subdriver },	/* Phoenixtec Innova RT */
	{ USB_DEVICE(0x06da, 0x0201),	NULL,		NULL,			&cypress_subdriver },	/* Phoenixtec Innova T */
	{ USB_DEVICE(0x06da, 0x0601),	NULL,		NULL,			&phoenix_subdriver },	/* Online Zinto A */
	{ USB_DEVICE(0x0f03, 0x0001),	NULL,		NULL,			&cypress_subdriver },	/* Unitek Alpha 1200Sx */
	{ USB_DEVICE(0x14f0, 0x00c9),	NULL,		NULL,			&phoenix_subdriver },	/* GE EP series */
	{ USB_DEVICE(0x0483, 0x0035),	NULL,		NULL,			&sgs_subdriver },	/* TS Shara UPSes */
	{ USB_DEVICE(0x0001, 0x0000),	"MEC",		"MEC0003",		&fabula_subdriver },	/* Fideltronik/MEC LUPUS 500 USB */
	{ USB_DEVICE(0x0001, 0x0000),	"ATCL FOR UPS",	"ATCL FOR UPS",		&fuji_subdriver },	/* Fuji UPSes */
	{ USB_DEVICE(0x0001, 0x0000),	NULL,		NULL,			&krauler_subdriver },	/* Krauler UP-M500VA */
	/* End of list */
	{ -1,	-1,	NULL,	NULL,	NULL }
};

static int qx_is_usb_device_supported(qx_usb_device_id_t *usb_device_id_list, USBDevice_t *device)
{
	int			retval = NOT_SUPPORTED;
	qx_usb_device_id_t	*usbdev;

	for (usbdev = usb_device_id_list; usbdev->vendorID != -1; usbdev++) {

		if (usbdev->vendorID != device->VendorID)
			continue;

		/* Flag as possibly supported if we see a known vendor */
		retval = POSSIBLY_SUPPORTED;

		if (usbdev->productID != device->ProductID)
			continue;

		if (usbdev->vendor && (!device->Vendor || strcasecmp(usbdev->vendor, device->Vendor)))
			continue;

		if (usbdev->product && (!device->Product || strcasecmp(usbdev->product, device->Product)))
			continue;

		/* Call the specific handler, if it exists */
		if (usbdev->fun != NULL)
			(*usbdev->fun)(device);

		return SUPPORTED;

	}

	return retval;
}

static int	device_match_func(USBDevice_t *hd, void *privdata)
{
	if (subdriver_command) {
		return 1;
	}

	switch (qx_is_usb_device_supported(qx_usb_id, hd))
	{
	case SUPPORTED:
		return 1;

	case POSSIBLY_SUPPORTED:
	case NOT_SUPPORTED:
	default:
		return 0;
	}
}

static USBDeviceMatcher_t	device_matcher = {
	&device_match_func,
	NULL,
	NULL
};
#endif	/* QX_USB && !TESTING */


/* == Driver functions implementations == */

/* See header file for details. */
int	instcmd(const char *cmdname, const char *extradata)
{
	item_t	*item;
	char	value[SMALLBUF];

	if (!strcasecmp(cmdname, "beeper.off")) {
		/* Compatibility mode for old command */
		upslogx(LOG_WARNING, "The 'beeper.off' command has been renamed to 'beeper.disable'");
		return instcmd("beeper.disable", 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);
	}

	upslogx(LOG_INFO, "%s(%s, %s)", __func__, cmdname, extradata ? extradata : "[NULL]");

	/* Retrieve item by command name */
	item = find_nut_info(cmdname, QX_FLAG_CMD, QX_FLAG_SKIP);

	/* Check for fallback if not found */
	if (item == NULL) {

		if (!strcasecmp(cmdname, "load.on")) {
			return instcmd("load.on.delay", "0");
		}

		if (!strcasecmp(cmdname, "load.off")) {
			return instcmd("load.off.delay", "0");
		}

		if (!strcasecmp(cmdname, "shutdown.return")) {

			int	ret;

			/* Ensure "ups.start.auto" is set to "yes", if supported */
			if (dstate_getinfo("ups.start.auto")) {
				if (setvar("ups.start.auto", "yes") != STAT_SET_HANDLED) {
					upslogx(LOG_ERR, "%s: FAILED", __func__);
					return STAT_INSTCMD_FAILED;
				}
			}

			ret = instcmd("load.on.delay", dstate_getinfo("ups.delay.start"));
			if (ret != STAT_INSTCMD_HANDLED) {
				return ret;
			}

			return instcmd("load.off.delay", dstate_getinfo("ups.delay.shutdown"));

		}

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

			int	ret;

			/* Ensure "ups.start.auto" is set to "no", if supported */
			if (dstate_getinfo("ups.start.auto")) {
				if (setvar("ups.start.auto", "no") != STAT_SET_HANDLED) {
					upslogx(LOG_ERR, "%s: FAILED", __func__);
					return STAT_INSTCMD_FAILED;
				}
			}

			ret = instcmd("load.on.delay", "-1");
			if (ret != STAT_INSTCMD_HANDLED) {
				return ret;
			}

			return instcmd("load.off.delay", dstate_getinfo("ups.delay.shutdown"));

		}

		upsdebugx(2, "%s: command %s unavailable", __func__, cmdname);
		return STAT_INSTCMD_INVALID;
	}

	/* If extradata is empty, use the default value from the QX to NUT table, if any */
	extradata = extradata ? extradata : item->dfl;
	snprintf(value, sizeof(value), "%s", extradata ? extradata : "");

	/* Preprocess command */
	if (item->preprocess != NULL && item->preprocess(item, value, sizeof(value))) {
		/* Something went wrong */
		upslogx(LOG_ERR, "%s: FAILED", __func__);
		return STAT_INSTCMD_FAILED;
	}

	/* No preprocess function -> nothing to do with extradata */
	if (item->preprocess == NULL)
		snprintf(value, sizeof(value), "%s", "");

	/* Send the command, get the reply */
	if (qx_process(item, strlen(value) > 0 ? value : NULL)) {
		/* Something went wrong */
		upslogx(LOG_ERR, "%s: FAILED", __func__);
		return STAT_INSTCMD_FAILED;
	}

	/* We got a reply from the UPS: either subdriver->accepted (-> command handled) or the command itself echoed back (-> command failed) */
	if (strlen(item->value) > 0) {

		if (subdriver->accepted != NULL && !strcasecmp(item->value, subdriver->accepted)) {
			upslogx(LOG_INFO, "%s: SUCCEED", __func__);
			/* Set the status so that SEMI_STATIC vars are polled */
			data_has_changed = TRUE;
			return STAT_INSTCMD_HANDLED;
		}

		upslogx(LOG_ERR, "%s: FAILED", __func__);
		return STAT_INSTCMD_FAILED;

	}

	/* No reply from the UPS -> command handled */
	upslogx(LOG_INFO, "%s: SUCCEED", __func__);
	/* Set the status so that SEMI_STATIC vars are polled */
	data_has_changed = TRUE;
	return STAT_INSTCMD_HANDLED;
}

/* See header file for details. */
int	setvar(const char *varname, const char *val)
{
	item_t		*item;
	char		value[SMALLBUF];
	st_tree_t	*root = (st_tree_t *)dstate_getroot();
	int		ok = 0;

	/* Retrieve variable */
	item = find_nut_info(varname, QX_FLAG_SETVAR, QX_FLAG_SKIP);

	if (item == NULL) {
		upsdebugx(2, "%s: element %s unavailable", __func__, varname);
		return STAT_SET_UNKNOWN;
	}

	/* No NUT variable is available for this item, so we're handling a one-time setvar from ups.conf */
	if (item->qxflags & QX_FLAG_NONUT) {

		const char	*userval;

		/* Nothing to do */
		if (!testvar(item->info_type)) {
			upsdebugx(2, "%s: nothing to do.. [%s]", __func__, item->info_type);
			return STAT_SET_HANDLED;
		}

		userval = getval(item->info_type);

		upslogx(LOG_INFO, "%s(%s, %s)", __func__, varname, userval ? userval : "[NULL]");

		snprintf(value, sizeof(value), "%s", userval ? userval : "");

	/* This item is available in NUT */
	} else {

		upslogx(LOG_INFO, "%s(%s, %s)", __func__, varname, strlen(val) ? val : "[NULL]");

		if (!strlen(val)) {
			upslogx(LOG_ERR, "%s: value not given for %s", __func__, item->info_type);
			return STAT_SET_UNKNOWN;	/* TODO: HANDLED but FAILED, not UNKNOWN! */
		}

		snprintf(value, sizeof(value), "%s", val);

		/* Nothing to do */
		if (!strcasecmp(dstate_getinfo(item->info_type), value)) {
			upslogx(LOG_INFO, "%s: nothing to do.. [%s]", __func__, item->info_type);
			return STAT_SET_HANDLED;
		}

	}

	/* Check if given value is in the range of accepted values (range) */
	if (item->qxflags & QX_FLAG_RANGE) {

		int	valuetoset, min, max;

		if (strspn(value, "0123456789 .") != strlen(value)) {
			upslogx(LOG_ERR, "%s: non numerical value [%s: %s]", __func__, item->info_type, value);
			return STAT_SET_UNKNOWN;	/* TODO: HANDLED but FAILED, not UNKNOWN! */
		}

		valuetoset = strtol(value, NULL, 10);

		/* No NUT var is available for this item, so take its range from qx2nut table */
		if (item->qxflags & QX_FLAG_NONUT) {

			info_rw_t	*rvalue;

			if (!strlen(value)) {
				upslogx(LOG_ERR, "%s: value not given for %s", __func__, item->info_type);
				return STAT_SET_UNKNOWN;	/* TODO: HANDLED but FAILED, not UNKNOWN! */
			}

			min = max = -1;

			/* Loop on all existing values */
			for (rvalue = item->info_rw; rvalue != NULL && strlen(rvalue->value) > 0; rvalue++) {

				if (rvalue->preprocess && rvalue->preprocess(rvalue->value, sizeof(rvalue->value)))
					continue;

				if (min < 0) {
					min = strtol(rvalue->value, NULL, 10);
					continue;
				}

				max = strtol(rvalue->value, NULL, 10);

				/* valuetoset is in the range */
				if (min <= valuetoset && valuetoset <= max) {
					ok = 1;
					break;
				}

				min = -1;
				max = -1;

			}

		/* We have a NUT var for this item, so check given value against the already set range */
		} else {

			const range_t	*range = state_getrangelist(root, item->info_type);

			/* Unable to find tree node for var */
			if (!range) {
				upsdebugx(2, "%s: unable to find tree node for %s", __func__, item->info_type);
				return STAT_SET_UNKNOWN;
			}

			while (range) {

				min = range->min;
				max = range->max;

				/* valuetoset is in the range */
				if (min <= valuetoset && valuetoset <= max) {
					ok = 1;
					break;
				}
				range = range->next;
			}

		}

		if (!ok) {
			upslogx(LOG_ERR, "%s: value out of range [%s: %s]", __func__, item->info_type, value);
			return STAT_SET_UNKNOWN;	/* TODO: HANDLED but FAILED, not UNKNOWN! */
		}

	/* Check if given value is in the range of accepted values (enum) */
	} else if (item->qxflags & QX_FLAG_ENUM) {

		/* No NUT var is available for this item, so take its range from qx2nut table */
		if (item->qxflags & QX_FLAG_NONUT) {

			info_rw_t	*envalue;

			if (!strlen(value)) {
				upslogx(LOG_ERR, "%s: value not given for %s", __func__, item->info_type);
				return STAT_SET_UNKNOWN;	/* TODO: HANDLED but FAILED, not UNKNOWN! */
			}

			/* Loop on all existing values */
			for (envalue = item->info_rw; envalue != NULL && strlen(envalue->value) > 0; envalue++) {

				if (envalue->preprocess && envalue->preprocess(envalue->value, sizeof(envalue->value)))
					continue;

				if (strcasecmp(envalue->value, value))
					continue;

				/* value found */
				ok = 1;
				break;

			}

		/* We have a NUT var for this item, so check given value against the already set range */
		} else {

			const enum_t	*enumlist = state_getenumlist(root, item->info_type);

			/* Unable to find tree node for var */
			if (!enumlist) {
				upsdebugx(2, "%s: unable to find tree node for %s", __func__, item->info_type);
				return STAT_SET_UNKNOWN;
			}

			while (enumlist) {

				/* If this is not the right value, go on to the next */
				if (strcasecmp(enumlist->val, value)) {
					enumlist = enumlist->next;
					continue;
				}

				/* value found in enumlist */
				ok = 1;
				break;
			}

		}

		if (!ok) {
			upslogx(LOG_ERR, "%s: value out of range [%s: %s]", __func__, item->info_type, value);
			return STAT_SET_UNKNOWN;	/* TODO: HANDLED but FAILED, not UNKNOWN! */
		}

	/* Check if given value is not too long (string) */
	} else if (item->info_flags & ST_FLAG_STRING) {

		const int	aux = state_getaux(root, item->info_type);

		/* Unable to find tree node for var */
		if (aux < 0) {
			upsdebugx(2, "%s: unable to find tree node for %s", __func__, item->info_type);
			return STAT_SET_UNKNOWN;
		}

		if (aux < (int)strlen(value)) {
			upslogx(LOG_ERR, "%s: value is too long [%s: %s]", __func__, item->info_type, value);
			return STAT_SET_UNKNOWN;	/* TODO: HANDLED but FAILED, not UNKNOWN! */
		}

	}

	/* Preprocess value: from NUT-compliant to UPS-compliant */
	if (item->preprocess != NULL && item->preprocess(item, value, sizeof(value))) {
		/* Something went wrong */
		upslogx(LOG_ERR, "%s: FAILED", __func__);
		return STAT_SET_UNKNOWN;	/* TODO: HANDLED but FAILED, not UNKNOWN! */
	}

	/* Handle server side variable */
	if (item->qxflags & QX_FLAG_ABSENT) {
		upsdebugx(2, "%s: setting server side variable %s", __func__, item->info_type);
		dstate_setinfo(item->info_type, "%s", value);
		upslogx(LOG_INFO, "%s: SUCCEED", __func__);
		return STAT_SET_HANDLED;
	}

	/* No preprocess function -> nothing to do with val */
	if (item->preprocess == NULL)
		snprintf(value, sizeof(value), "%s", "");

	/* Actual variable setting */
	if (qx_process(item, strlen(value) > 0 ? value : NULL)) {
		/* Something went wrong */
		upslogx(LOG_ERR, "%s: FAILED", __func__);
		return STAT_SET_UNKNOWN;	/* TODO: HANDLED but FAILED, not UNKNOWN! */
	}

	/* We got a reply from the UPS: either subdriver->accepted (-> command handled) or the command itself echoed back (-> command failed) */
	if (strlen(item->value) > 0) {

		if (subdriver->accepted != NULL && !strcasecmp(item->value, subdriver->accepted)) {
			upslogx(LOG_INFO, "%s: SUCCEED", __func__);
			/* Set the status so that SEMI_STATIC vars are polled */
			data_has_changed = TRUE;
			return STAT_SET_HANDLED;
		}

		upslogx(LOG_ERR, "%s: FAILED", __func__);
		return STAT_SET_UNKNOWN;	/* TODO: HANDLED but FAILED, not UNKNOWN! */

	}

	/* No reply from the UPS -> command handled */
	upslogx(LOG_INFO, "%s: SUCCEED", __func__);
	/* Set the status so that SEMI_STATIC vars are polled */
	data_has_changed = TRUE;
	return STAT_SET_HANDLED;
}

/* Try to shutdown the UPS */
void	upsdrv_shutdown(void)
{
	int		retry;
	item_t		*item;
	const char	*val;

	upsdebugx(1, "%s...", __func__);

	/* Get user-defined delays */

	/* Start delay */
	item = find_nut_info("ups.delay.start", 0, QX_FLAG_SKIP);

	/* Don't know what happened */
	if (!item)
		fatalx(EXIT_FAILURE, "Unable to set start delay");

	/* Set the default value */
	dstate_setinfo(item->info_type, "%s", item->dfl);

	/* Set var flags/range/enum */
	qx_set_var(item);

	/* Retrieve user defined delay settings */
	val = getval(QX_VAR_ONDELAY);

	if (val && setvar(item->info_type, val) != STAT_SET_HANDLED) {
		fatalx(EXIT_FAILURE, "Start delay '%s' out of range", val);
	}

	/* Shutdown delay */
	item = find_nut_info("ups.delay.shutdown", 0, QX_FLAG_SKIP);

	/* Don't know what happened */
	if (!item)
		fatalx(EXIT_FAILURE, "Unable to set shutdown delay");

	/* Set the default value */
	dstate_setinfo(item->info_type, "%s", item->dfl);

	/* Set var flags/range/enum */
	qx_set_var(item);

	/* Retrieve user defined delay settings */
	val = getval(QX_VAR_OFFDELAY);

	if (val && setvar(item->info_type, val) != STAT_SET_HANDLED) {
		fatalx(EXIT_FAILURE, "Shutdown delay '%s' out of range", val);
	}

	/* Stop pending shutdowns */
	if (find_nut_info("shutdown.stop", QX_FLAG_CMD, QX_FLAG_SKIP)) {

		for (retry = 1; retry <= MAXTRIES; retry++) {

			if (instcmd("shutdown.stop", NULL) != STAT_INSTCMD_HANDLED) {
				continue;
			}

			break;

		}

		if (retry > MAXTRIES) {
			upslogx(LOG_NOTICE, "No shutdown pending");
		}

	}

	/* Shutdown */
	for (retry = 1; retry <= MAXTRIES; retry++) {

		if (testvar("stayoff")) {

			if (instcmd("shutdown.stayoff", NULL) != STAT_INSTCMD_HANDLED) {
				continue;
			}

		} else {

			if (instcmd("shutdown.return", NULL) != STAT_INSTCMD_HANDLED) {
				continue;
			}

		}

		fatalx(EXIT_SUCCESS, "Shutting down in %s seconds", dstate_getinfo("ups.delay.shutdown"));
	}

	fatalx(EXIT_FAILURE, "Shutdown failed!");
}

void	upsdrv_help(void)
{
	printf("Read The Fine Manual ('man 8 nutdrv_qx')\n");
}

/* Adding flags/vars */
void	upsdrv_makevartable(void)
{
	char	temp[SMALLBUF];
	int	i;

	upsdebugx(1, "%s...", __func__);

	snprintf(temp, sizeof(temp), "Set shutdown delay, in seconds (default=%s)", DEFAULT_OFFDELAY);
	addvar(VAR_VALUE, QX_VAR_OFFDELAY, temp);

	snprintf(temp, sizeof(temp), "Set startup delay, in seconds (default=%s)", DEFAULT_ONDELAY);
	addvar(VAR_VALUE, QX_VAR_ONDELAY, temp);

	addvar(VAR_FLAG, "stayoff", "If invoked the UPS won't return after a shutdown when FSD arises");

	snprintf(temp, sizeof(temp), "Set polling frequency, in seconds, to reduce data flow (default=%d)", DEFAULT_POLLFREQ);
	addvar(VAR_VALUE, QX_VAR_POLLFREQ, temp);

	addvar(VAR_VALUE, "protocol", "Preselect communication protocol (skip autodetection)");

	/* battery.{charge,runtime} guesstimation */
	addvar(VAR_VALUE, "runtimecal", "Parameters used for runtime calculation");
	addvar(VAR_VALUE, "chargetime", "Nominal charge time for UPS battery");
	addvar(VAR_VALUE, "idleload", "Minimum load to be used for runtime calculation");

#ifdef QX_USB
	addvar(VAR_VALUE, "subdriver", "Serial-over-USB subdriver selection");
	/* allow -x vendor=X, vendorid=X, product=X, productid=X, serial=X */
	nut_usb_addvars();

	addvar(VAR_VALUE, "langid_fix", "Apply the language ID workaround to the krauler subdriver (0x409 or 0x4095)");
#endif	/* QX_USB */

#ifdef QX_SERIAL
	addvar(VAR_VALUE, "cablepower", "Set cable power for serial interface");
#endif	/* QX_SERIAL */

	/* Subdrivers flags/vars */
	for (i = 0; subdriver_list[i] != NULL; i++) {

		if (subdriver_list[i]->makevartable != NULL)
			subdriver_list[i]->makevartable();

	}
}

/* Update UPS status/infos */
void	upsdrv_updateinfo(void)
{
	time_t		now;
	static int	retry = 0;

	upsdebugx(1, "%s...", __func__);

	time(&now);

	/* Clear status buffer before beginning */
	status_init();

	/* Do a full update (polling) every pollfreq or upon data change (i.e. setvar/instcmd) */
	if ((now > (lastpoll + pollfreq)) || (data_has_changed == TRUE)) {

		upsdebugx(1, "Full update...");

		/* Clear ups_status */
		ups_status = 0;

		alarm_init();

		if (qx_ups_walk(QX_WALKMODE_FULL_UPDATE) == FALSE) {

			if (retry < MAXTRIES || retry == MAXTRIES) {
				upsdebugx(1, "Communications with the UPS lost: status read failed!");
				retry++;
			} else {
				dstate_datastale();
			}

			return;
		}

		lastpoll = now;
		data_has_changed = FALSE;

		ups_alarm_set();
		alarm_commit();

	} else {

		upsdebugx(1, "Quick update...");

		/* Quick poll data only to see if the UPS is still connected */
		if (qx_ups_walk(QX_WALKMODE_QUICK_UPDATE) == FALSE) {

			if (retry < MAXTRIES || retry == MAXTRIES) {
				upsdebugx(1, "Communications with the UPS lost: status read failed!");
				retry++;
			} else {
				dstate_datastale();
			}

			return;
		}

	}

	ups_status_set();
	status_commit();

	if (retry > MAXTRIES) {
		upslogx(LOG_NOTICE, "Communications with the UPS re-established");
	}

	retry = 0;

	dstate_dataok();
}

/* Initialise data from UPS */
void	upsdrv_initinfo(void)
{
	char	*val;

	upsdebugx(1, "%s...", __func__);

	dstate_setinfo("driver.version.data", "%s", subdriver->name);

	/* Initialise data */
	if (qx_ups_walk(QX_WALKMODE_INIT) == FALSE) {
		fatalx(EXIT_FAILURE, "Can't initialise data from the UPS");
	}

	/* Init battery guesstimation */
	qx_initbattery();

	if (dstate_getinfo("ups.delay.start")) {

		/* Retrieve user defined delay settings */
		val = getval(QX_VAR_ONDELAY);

		if (val && setvar("ups.delay.start", val) != STAT_SET_HANDLED) {
			fatalx(EXIT_FAILURE, "Start delay '%s' out of range", val);
		}

	}

	if (dstate_getinfo("ups.delay.shutdown")) {

		/* Retrieve user defined delay settings */
		val = getval(QX_VAR_OFFDELAY);

		if (val && setvar("ups.delay.shutdown", val) != STAT_SET_HANDLED) {
			fatalx(EXIT_FAILURE, "Shutdown delay '%s' out of range", val);
		}

	}

	if (!find_nut_info("load.off", QX_FLAG_CMD, QX_FLAG_SKIP) && find_nut_info("load.off.delay", QX_FLAG_CMD, QX_FLAG_SKIP)) {
		/* Adds default with a delay value of '0' (= immediate) */
		dstate_addcmd("load.off");
	}

	if (!find_nut_info("load.on", QX_FLAG_CMD, QX_FLAG_SKIP) && find_nut_info("load.on.delay", QX_FLAG_CMD, QX_FLAG_SKIP)) {
		/* Adds default with a delay value of '0' (= immediate) */
		dstate_addcmd("load.on");
	}

	/* Init polling frequency */
	val = getval(QX_VAR_POLLFREQ);
	if (val)
		pollfreq = strtol(val, NULL, 10);

	dstate_setinfo("driver.parameter.pollfreq", "%d", pollfreq);

	time(&lastpoll);

	/* Install handlers */
	upsh.setvar = setvar;
	upsh.instcmd = instcmd;

	/* Subdriver initinfo */
	if (subdriver->initinfo != NULL)
		subdriver->initinfo();
}

/* Open the port and the like and choose the subdriver */
void	upsdrv_initups(void)
{
	upsdebugx(1, "%s...", __func__);

#if defined(QX_SERIAL) && defined(QX_USB)

	/* Whether the device is connected through USB or serial */
	if (
		!strcasecmp(dstate_getinfo("driver.parameter.port"), "auto") ||
		getval("subdriver") ||
		getval("vendorid") ||
		getval("productid") ||
		getval("vendor") ||
		getval("product") ||
		getval("serial") ||
		getval("bus") ||
		getval("langid_fix")
	) {
		/* USB */
		is_usb = 1;
	} else {
		/* Serial */
		is_usb = 0;
	}

#endif	/* QX_SERIAL && QX_USB */

/* Serial */
#ifdef QX_SERIAL

	#ifdef QX_USB
	if (!is_usb) {
	#endif	/* QX_USB */

	#ifndef TESTING

		const struct {
			const char	*val;
			const int	dtr;
			const int	rts;
		} cablepower[] = {
			{ "normal",	1, 0 },	/* Default */
			{ "reverse",	0, 1 },
			{ "both",	1, 1 },
			{ "none",	0, 0 },
			{ NULL }
		};

		int		i;
		const char	*val;
		struct termios	tio;

		/* Open and lock the serial port and set the speed to 2400 baud. */
		upsfd = ser_open(device_path);
		ser_set_speed(upsfd, device_path, B2400);

		if (tcgetattr(upsfd, &tio)) {
			fatal_with_errno(EXIT_FAILURE, "tcgetattr");
		}

		/* Use canonical mode input processing (to read reply line) */
		tio.c_lflag |= ICANON;	/* Canonical input (erase and kill processing) */

		tio.c_cc[VEOF] = _POSIX_VDISABLE;
		tio.c_cc[VEOL] = '\r';
		tio.c_cc[VERASE] = _POSIX_VDISABLE;
		tio.c_cc[VINTR] = _POSIX_VDISABLE;
		tio.c_cc[VKILL] = _POSIX_VDISABLE;
		tio.c_cc[VQUIT] = _POSIX_VDISABLE;
		tio.c_cc[VSUSP] = _POSIX_VDISABLE;
		tio.c_cc[VSTART] = _POSIX_VDISABLE;
		tio.c_cc[VSTOP] = _POSIX_VDISABLE;

		if (tcsetattr(upsfd, TCSANOW, &tio)) {
			fatal_with_errno(EXIT_FAILURE, "tcsetattr");
		}

		val = getval("cablepower");
		for (i = 0; val && cablepower[i].val; i++) {

			if (!strcasecmp(val, cablepower[i].val)) {
				break;
			}
		}

		if (!cablepower[i].val) {
			fatalx(EXIT_FAILURE, "Value '%s' not valid for 'cablepower'", val);
		}

		ser_set_dtr(upsfd, cablepower[i].dtr);
		ser_set_rts(upsfd, cablepower[i].rts);

		/* Allow some time to settle for the cablepower */
		usleep(100000);

	#endif	/* TESTING */

	#ifdef QX_USB
	} else {	/* is_usb */
	#endif	/* QX_USB */

#endif	/* QX_SERIAL */

/* USB */
#ifdef QX_USB

	#ifndef TESTING

		const struct {
			const char	*name;
			int		(*command)(const char *cmd, char *buf, size_t buflen);
		} usbsubdriver[] = {
			{ "cypress", &cypress_command },
			{ "phoenix", &phoenix_command },
			{ "ippon", &ippon_command },
			{ "krauler", &krauler_command },
			{ "fabula", &fabula_command },
			{ "fuji", &fuji_command },
			{ "sgs", &sgs_command },
			{ NULL }
		};

		int	ret, langid;
		char	tbuf[255];	/* Some devices choke on size > 255 */
		char	*regex_array[6];

		char	*subdrv = getval("subdriver");

		regex_array[0] = getval("vendorid");
		regex_array[1] = getval("productid");
		regex_array[2] = getval("vendor");
		regex_array[3] = getval("product");
		regex_array[4] = getval("serial");
		regex_array[5] = getval("bus");

		/* Check for language ID workaround (#1) */
		if (getval("langid_fix")) {
			/* Skip "0x" prefix and set back to hexadecimal */
			if (sscanf(getval("langid_fix") + 2, "%x", &langid_fix) != 1) {
				upslogx(LOG_NOTICE, "Error enabling language ID workaround");
			} else {
				upsdebugx(2, "Language ID workaround enabled (using '0x%x')", langid_fix);
			}
		}

		/* Pick up the subdriver name if set explicitly */
		if (subdrv) {

			int	i;

			if (!regex_array[0] || !regex_array[1]) {
				fatalx(EXIT_FAILURE, "When specifying a subdriver, 'vendorid' and 'productid' are mandatory.");
			}

			for (i = 0; usbsubdriver[i].name; i++) {

				if (strcasecmp(subdrv, usbsubdriver[i].name)) {
					continue;
				}

				subdriver_command = usbsubdriver[i].command;
				break;
			}

			if (!subdriver_command) {
				fatalx(EXIT_FAILURE, "Subdriver '%s' not found!", subdrv);
			}

		}

		ret = USBNewRegexMatcher(&regex_matcher, regex_array, REG_ICASE | REG_EXTENDED);
		switch (ret)
		{
		case -1:
			fatal_with_errno(EXIT_FAILURE, "USBNewRegexMatcher");
		case 0:
			break;	/* All is well */
		default:
			fatalx(EXIT_FAILURE, "Invalid regular expression: %s", regex_array[ret]);
		}

		/* Link the matchers */
		regex_matcher->next = &device_matcher;

		ret = usb->open(&udev, &usbdevice, regex_matcher, NULL);
		if (ret < 0) {
			fatalx(EXIT_FAILURE,
				"No supported devices found. Please check your device availability with 'lsusb'\n"
				"and make sure you have an up-to-date version of NUT. If this does not help,\n"
				"try running the driver with at least 'subdriver', 'vendorid' and 'productid'\n"
				"options specified. Please refer to the man page for details about these options\n"
				"(man 8 nutdrv_qx).\n");
		}

		if (!subdriver_command) {
			fatalx(EXIT_FAILURE, "No subdriver selected");
		}

		/* Create a new matcher for later reopening */
		ret = USBNewExactMatcher(&reopen_matcher, &usbdevice);
		if (ret) {
			fatal_with_errno(EXIT_FAILURE, "USBNewExactMatcher");
		}

		/* Link the matchers */
		reopen_matcher->next = regex_matcher;

		dstate_setinfo("ups.vendorid", "%04x", usbdevice.VendorID);
		dstate_setinfo("ups.productid", "%04x", usbdevice.ProductID);

		/* Check for language ID workaround (#2) */
		if (langid_fix != -1) {
			/* Future improvement:
			 *   Asking for the zero'th index is special - it returns a string descriptor that contains all the language IDs supported by the device.
			 *   Typically there aren't many - often only one.
			 *   The language IDs are 16 bit numbers, and they start at the third byte in the descriptor.
			 *   See USB 2.0 specification, section 9.6.7, for more information on this.
			 * This should allow automatic application of the workaround */
			ret = usb_get_string(udev, 0, 0, tbuf, sizeof(tbuf));
			if (ret >= 4) {
				langid = tbuf[2] | (tbuf[3] << 8);
				upsdebugx(1, "First supported language ID: 0x%x (please report to the NUT maintainer!)", langid);
			}
		}

	#endif	/* TESTING */

	#ifdef QX_SERIAL
	}	/* is_usb */
	#endif	/* QX_SERIAL */

#endif	/* QX_USB */

	/* Choose subdriver */
	if (!subdriver_matcher())
		fatalx(EXIT_FAILURE, "Device not supported!");

	/* Subdriver initups */
	if (subdriver->initups != NULL)
		subdriver->initups();
}

/* Close the ports and the like */
void	upsdrv_cleanup(void)
{
	upsdebugx(1, "%s...", __func__);

#ifndef TESTING

#ifdef QX_SERIAL

	#ifdef QX_USB
	if (!is_usb) {
	#endif	/* QX_USB */

		ser_set_dtr(upsfd, 0);
		ser_close(upsfd, device_path);

	#ifdef QX_USB
	} else {	/* is_usb */
	#endif	/* QX_USB */

#endif	/* QX_SERIAL */

#ifdef QX_USB

		usb->close(udev);
		USBFreeExactMatcher(reopen_matcher);
		USBFreeRegexMatcher(regex_matcher);
		free(usbdevice.Vendor);
		free(usbdevice.Product);
		free(usbdevice.Serial);
		free(usbdevice.Bus);

	#ifdef QX_SERIAL
	}	/* is_usb */
	#endif	/* QX_SERIAL */

#endif	/* QX_USB */

#endif	/* TESTING */

}


/* == Support functions == */

/* Generic command processing function: send a command and read a reply.
 * Returns < 0 on error, 0 on timeout and the number of bytes read on success. */
static int	qx_command(const char *cmd, char *buf, size_t buflen)
{
#ifndef TESTING

	int	ret = -1;

#ifdef QX_USB

	#ifdef QX_SERIAL
	/* Communication: USB */
	if (is_usb) {
	#endif	/* QX_SERIAL */

		if (udev == NULL) {
			ret = usb->open(&udev, &usbdevice, reopen_matcher, NULL);

			if (ret < 1) {
				return ret;
			}
		}

		ret = (*subdriver_command)(cmd, buf, buflen);

		if (ret >= 0) {
			return ret;
		}

		switch (ret)
		{
		case -EBUSY:		/* Device or resource busy */
			fatal_with_errno(EXIT_FAILURE, "Got disconnected by another driver");

		case -EPERM:		/* Operation not permitted */
			fatal_with_errno(EXIT_FAILURE, "Permissions problem");

		case -EPIPE:		/* Broken pipe */
			if (usb_clear_halt(udev, 0x81) == 0) {
				upsdebugx(1, "Stall condition cleared");
				break;
			}
	#ifdef ETIME
		case -ETIME:		/* Timer expired */
	#endif	/* ETIME */
			if (usb_reset(udev) == 0) {
				upsdebugx(1, "Device reset handled");
			}
		case -ENODEV:		/* No such device */
		case -EACCES:		/* Permission denied */
		case -EIO:		/* I/O error */
		case -ENXIO:		/* No such device or address */
		case -ENOENT:		/* No such file or directory */
			/* Uh oh, got to reconnect! */
			usb->close(udev);
			udev = NULL;
			break;

		case -ETIMEDOUT:	/* Connection timed out */
		case -EOVERFLOW:	/* Value too large for defined data type */
#ifdef EPROTO
		case -EPROTO:		/* Protocol error */
#endif
		default:
			break;
		}

	#ifdef QX_SERIAL
	/* Communication: serial */
	} else {	/* !is_usb */
	#endif	/* QX_SERIAL */

#endif	/* QX_USB */

#ifdef QX_SERIAL

		ser_flush_io(upsfd);

		ret = ser_send(upsfd, "%s", cmd);

		if (ret <= 0) {
			upsdebugx(3, "send: %s (%d)", ret ? strerror(errno) : "timeout", ret);
			return ret;
		}

		upsdebugx(3, "send: '%.*s'", (int)strcspn(cmd, "\r"), cmd);

		ret = ser_get_buf(upsfd, buf, buflen, SER_WAIT_SEC, 0);

		if (ret <= 0) {
			upsdebugx(3, "read: %s (%d)", ret ? strerror(errno) : "timeout", ret);
			return ret;
		}

		upsdebug_hex(5, "read", buf, ret);
		upsdebugx(3, "read: '%.*s'", (int)strcspn(buf, "\r"), buf);

	#ifdef QX_USB
	}	/* !is_usb */
	#endif	/* QX_USB */

#endif	/* QX_SERIAL */

	return ret;

#else	/* TESTING */

	testing_t	*testing = subdriver->testing;
	int		i;

	memset(buf, 0, buflen);

	upsdebugx(3, "send: '%.*s'", (int)strcspn(cmd, "\r"), cmd);

	for (i = 0; cmd && testing[i].cmd; i++) {

		if (strcasecmp(cmd, testing[i].cmd)) {
			continue;
		}

		upsdebugx(3, "read: '%.*s'", (int)strcspn(testing[i].answer, "\r"), testing[i].answer);

		/* If requested to do so and this is the case, try to preserve inner '\0's (treat answer as a sequence of bytes) */
		if (testing[i].answer_len > 0 && strlen(testing[i].answer) < (size_t)testing[i].answer_len) {

			size_t	len;

			len = buflen <= (size_t)testing[i].answer_len ? buflen - 1 : (size_t)testing[i].answer_len;
			len = len <= sizeof(testing[i].answer) ? len : sizeof(testing[i].answer);

			memcpy(buf, testing[i].answer, len);
			upsdebug_hex(4, "read", buf, (int)len);

			return len;

		}

		return snprintf(buf, buflen, "%s", testing[i].answer);

	}

	/* If the driver expects some kind of reply in case of error.. */
	if (subdriver->rejected != NULL) {

		/* ..fulfill its expectations.. */
		upsdebugx(3, "read: '%.*s'", (int)strcspn(subdriver->rejected, "\r"), subdriver->rejected);
		return snprintf(buf, buflen, "%s", subdriver->rejected);

	/* ..otherwise.. */
	} else {

		/* ..echo back the command */
		upsdebugx(3, "read: '%.*s'", (int)strcspn(cmd, "\r"), cmd);
		return snprintf(buf, buflen, "%s", cmd);

	}

#endif	/* TESTING */
}

/* See header file for details.
 * Interpretation is done in ups_status_set(). */
void	update_status(const char *value)
{
	status_lkp_t	*status_item;
	int		clear = 0;

	upsdebugx(5, "%s: %s", __func__, value);

	if (*value == '!') {
		value++;
		clear = 1;
	}

	for (status_item = status_info; status_item->status_str != NULL ; status_item++) {

		if (strcasecmp(status_item->status_str, value))
			continue;

		if (clear) {
			ups_status &= ~status_item->status_mask;
		} else {
			ups_status |= status_item->status_mask;
		}

		return;
	}

	upsdebugx(5, "%s: Warning! %s not in list of known values", __func__, value);
}

/* Choose subdriver */
static int	subdriver_matcher(void)
{
	const char	*protocol = getval("protocol");
	int		i;

	/* Select the subdriver for this device */
	for (i = 0; subdriver_list[i] != NULL; i++) {

		int	j;

		/* If protocol is set in ups.conf, use it */
		if (protocol) {

			char	subdrv_name[SMALLBUF];

			/* Get rid of subdriver version */
			snprintf(subdrv_name, sizeof(subdrv_name), "%.*s", (int)strcspn(subdriver_list[i]->name, " "), subdriver_list[i]->name);

			if (strcasecmp(subdrv_name, protocol)) {
				upsdebugx(2, "Skipping protocol %s", subdriver_list[i]->name);
				continue;
			}

		}

		/* Give every subdriver some tries */
		for (j = 0; j < MAXTRIES; j++) {

			subdriver = subdriver_list[i];

			if (subdriver->claim()) {
				break;
			}

			subdriver = NULL;

		}

		if (subdriver != NULL)
			break;

	}

	if (!subdriver) {
		upslogx(LOG_ERR, "Device not supported!");
		return 0;
	}

	upslogx(LOG_INFO, "Using protocol: %s", subdriver->name);

	return 1;
}

/* Set vars boundaries */
static void	qx_set_var(item_t *item)
{
	if (!(item->qxflags & QX_FLAG_NONUT))
		dstate_setflags(item->info_type, item->info_flags);

	/* Set max length for strings, if needed */
	if (item->info_flags & ST_FLAG_STRING && !(item->qxflags & QX_FLAG_NONUT))
		dstate_setaux(item->info_type, strtol(item->info_rw[0].value, NULL, 10));

	/* Set enum list */
	if (item->qxflags & QX_FLAG_ENUM) {

		info_rw_t	*envalue;
		char		buf[LARGEBUF] = "";

		/* Loop on all existing values */
		for (envalue = item->info_rw; envalue != NULL && strlen(envalue->value) > 0; envalue++) {

			if (envalue->preprocess && envalue->preprocess(envalue->value, sizeof(envalue->value)))
				continue;

			/* This item is not available yet in NUT, so publish these data in the logs */
			if (item->qxflags & QX_FLAG_NONUT) {

				snprintfcat(buf, sizeof(buf), " %s", envalue->value);

			/* This item is available in NUT, add its enum to the variable */
			} else {

				dstate_addenum(item->info_type, "%s", envalue->value);

			}

		}

		if (item->qxflags & QX_FLAG_NONUT)
			upslogx(LOG_INFO, "%s, settable values:%s", item->info_type, strlen(buf) > 0 ? buf : " none");

	}

	/* Set range */
	if (item->qxflags & QX_FLAG_RANGE) {

		info_rw_t	*rvalue, *from = NULL, *to = NULL;
		int		ok = 0;

		/* Loop on all existing values */
		for (rvalue = item->info_rw; rvalue != NULL && strlen(rvalue->value) > 0; rvalue++) {

			if (rvalue->preprocess && rvalue->preprocess(rvalue->value, sizeof(rvalue->value)))
				continue;

			if (!from) {
				from = rvalue;
				continue;
			}

			to = rvalue;

			/* This item is not available yet in NUT, so publish these data in the logs */
			if (item->qxflags & QX_FLAG_NONUT) {

				upslogx(LOG_INFO, "%s, settable range: %s..%s", item->info_type, from->value, to->value);
				ok++;

			/* This item is available in NUT, add its range to the variable */
			} else {

				dstate_addrange(item->info_type, strtol(from->value, NULL, 10), strtol(to->value, NULL, 10));

			}

			from = NULL;
			to = NULL;

		}

		/* This item is not available yet in NUT and we weren't able to get its range; let people know it */
		if ((item->qxflags & QX_FLAG_NONUT) && !ok)
			upslogx(LOG_INFO, "%s, settable range: none", item->info_type);

	}
}

/* Walk UPS variables and set elements of the qx2nut array. */
static bool_t	qx_ups_walk(walkmode_t mode)
{
	item_t	*item;
	int	retcode;

	/* Clear batt.{chrg,runt}.act for guesstimation */
	if (mode == QX_WALKMODE_FULL_UPDATE) {
		batt.runt.act = -1;
		batt.chrg.act = -1;
	}

	/* Clear data from previous_item */
	memset(previous_item.command, 0, sizeof(previous_item.command));
	memset(previous_item.answer, 0, sizeof(previous_item.answer));

	/* 3 modes: QX_WALKMODE_INIT, QX_WALKMODE_QUICK_UPDATE and QX_WALKMODE_FULL_UPDATE */

	/* Device data walk */
	for (item = subdriver->qx2nut; item->info_type != NULL; item++) {

		/* Skip this item */
		if (item->qxflags & QX_FLAG_SKIP)
			continue;

		upsdebugx(10, "%s: processing: %s", __func__, item->info_type);

		/* Filter data according to mode */
		switch (mode)
		{
		/* Device capabilities enumeration */
		case QX_WALKMODE_INIT:

			/* Special case for handling server side variables */
			if (item->qxflags & QX_FLAG_ABSENT) {

				/* Already set */
				if (dstate_getinfo(item->info_type))
					continue;

				dstate_setinfo(item->info_type, "%s", item->dfl);

				/* Set var flags/range/enum */
				qx_set_var(item);

				continue;
			}

			/* Allow duplicates for these NUT variables */
			if (!strncmp(item->info_type, "ups.alarm", 9) || !strncmp(item->info_type, "ups.status", 10))
				break;

			/* This one doesn't exist yet */
			if (dstate_getinfo(item->info_type) == NULL)
				break;

			continue;

		case QX_WALKMODE_QUICK_UPDATE:

			/* Quick update only deals with status and alarms! */
			if (!(item->qxflags & QX_FLAG_QUICK_POLL))
				continue;

			break;

		case QX_WALKMODE_FULL_UPDATE:

			/* These don't need polling after initinfo() */
			if (item->qxflags & (QX_FLAG_ABSENT | QX_FLAG_CMD | QX_FLAG_SETVAR | QX_FLAG_STATIC))
				continue;

			/* These need to be polled after user changes (setvar / instcmd) */
			if ((item->qxflags & QX_FLAG_SEMI_STATIC) && (data_has_changed == FALSE))
				continue;

			break;

		default:

			fatalx(EXIT_FAILURE, "%s: unknown update mode!", __func__);

		}

		/* Instant commands */
		if (item->qxflags & QX_FLAG_CMD) {
			dstate_addcmd(item->info_type);
			continue;
		}

		/* Setvars */
		if (item->qxflags & QX_FLAG_SETVAR) {

			if (item->qxflags & QX_FLAG_NONUT) {
				setvar(item->info_type, NULL);
				item->qxflags |= QX_FLAG_SKIP;
			}

			continue;

		}

		/* Check whether the previous item uses the same command and then use its answer, if available.. */
		if (strlen(previous_item.command) > 0 && strlen(previous_item.answer) > 0 && !strcasecmp(previous_item.command, item->command)) {

			snprintf(item->answer, sizeof(item->answer), "%s", previous_item.answer);

			/* Process the answer */
			retcode = qx_process_answer(item, strlen(item->answer));

		/* ..otherwise: execute command to get answer from the UPS */
		} else {

			retcode = qx_process(item, NULL);

		}

		/* Record item as previous_item */
		snprintf(previous_item.command, sizeof(previous_item.command), "%s", item->command);
		snprintf(previous_item.answer, sizeof(previous_item.answer), "%s", item->answer);

		if (retcode) {

			/* Clear data from the item */
			memset(item->answer, 0, sizeof(item->answer));
			memset(item->value, 0, sizeof(item->value));

			if (item->qxflags & QX_FLAG_QUICK_POLL)
				return FALSE;

			if (mode == QX_WALKMODE_INIT)
				/* Skip this item from now on */
				item->qxflags |= QX_FLAG_SKIP;

			/* Don't know what happened, try again later... */
			continue;

		}

		/* Process the value we got back (set status bits and set the value of other parameters) */
		retcode = ups_infoval_set(item);

		/* Clear data from the item */
		memset(item->answer, 0, sizeof(item->answer));
		memset(item->value, 0, sizeof(item->value));

		/* Uh-oh! Some error! */
		if (retcode == -1) {

			if (item->qxflags & QX_FLAG_QUICK_POLL)
				return FALSE;

			continue;

		}

		/* Set var flags/range/enum (not for ups.{alarm.status}, hence the retcode check) */
		if (retcode && mode == QX_WALKMODE_INIT) {
			qx_set_var(item);
		}

	}

	/* Update battery guesstimation */
	if (mode == QX_WALKMODE_FULL_UPDATE && (batt.runt.act == -1 || batt.chrg.act == -1)) {

		if (getval("runtimecal")) {

			time_t	battery_now;

			time(&battery_now);

			/* OL */
			if (ups_status & STATUS(OL)) {

				batt.runt.est += batt.runt.nom * difftime(battery_now, battery_lastpoll) / batt.chrg.time;
				if (batt.runt.est > batt.runt.nom) {
					batt.runt.est = batt.runt.nom;
				}

			/* OB */
			} else {

				batt.runt.est -= load.eff * difftime(battery_now, battery_lastpoll);
				if (batt.runt.est < 0) {
					batt.runt.est = 0;
				}

			}

			if (batt.chrg.act == -1)
				dstate_setinfo("battery.charge", "%.0f", 100 * batt.runt.est / batt.runt.nom);

			if (batt.runt.act == -1 && !qx_load())
				dstate_setinfo("battery.runtime", "%.0f", batt.runt.est / load.eff);

			battery_lastpoll = battery_now;

		} else {

			qx_battery();

		}
	}

	return TRUE;
}

/* Convert the local status information to NUT format and set NUT alarms. */
static void	ups_alarm_set(void)
{
	if (ups_status & STATUS(RB)) {
		alarm_set("Replace battery!");
	}
	if (ups_status & STATUS(FSD)) {
		alarm_set("Shutdown imminent!");
	}
}

/* Convert the local status information to NUT format and set NUT status. */
static void	ups_status_set(void)
{
	if (ups_status & STATUS(OL)) {
		status_set("OL");		/* On line */
	} else {
		status_set("OB");		/* On battery */
	}
	if (ups_status & STATUS(DISCHRG)) {
		status_set("DISCHRG");		/* Discharging */
	}
	if (ups_status & STATUS(CHRG)) {
		status_set("CHRG");		/* Charging */
	}
	if (ups_status & STATUS(LB)) {
		status_set("LB");		/* Low battery */
	}
	if (ups_status & STATUS(OVER)) {
		status_set("OVER");		/* Overload */
	}
	if (ups_status & STATUS(RB)) {
		status_set("RB");		/* Replace battery */
	}
	if (ups_status & STATUS(TRIM)) {
		status_set("TRIM");		/* SmartTrim */
	}
	if (ups_status & STATUS(BOOST)) {
		status_set("BOOST");		/* SmartBoost */
	}
	if (ups_status & STATUS(BYPASS)) {
		status_set("BYPASS");		/* On bypass */
	}
	if (ups_status & STATUS(OFF)) {
		status_set("OFF");		/* UPS is off */
	}
	if (ups_status & STATUS(CAL)) {
		status_set("CAL");		/* Calibration */
	}
	if (ups_status & STATUS(FSD)) {
		status_set("FSD");		/* Forced shutdown */
	}
}

/* See header file for details. */
item_t	*find_nut_info(const char *varname, const unsigned long flag, const unsigned long noflag)
{
	item_t	*item;

	for (item = subdriver->qx2nut; item->info_type != NULL; item++) {

		if (strcasecmp(item->info_type, varname))
			continue;

		if (flag && ((item->qxflags & flag) != flag))
			continue;

		if (noflag && (item->qxflags & noflag))
			continue;

		return item;
	}

	upsdebugx(2, "%s: info type %s not found", __func__, varname);
	return NULL;
}

/* Process the answer we got back from the UPS
 * Return -1 on errors, 0 on success */
static int	qx_process_answer(item_t *item, const int len)
{
	/* Query rejected by the UPS */
	if (subdriver->rejected && !strcasecmp(item->answer, subdriver->rejected)) {
		upsdebugx(2, "%s: query rejected by the UPS (%s)", __func__, item->info_type);
		return -1;
	}

	/* Short reply */
	if (item->answer_len && len < item->answer_len) {
		upsdebugx(2, "%s: short reply (%s)", __func__, item->info_type);
		return -1;
	}

	/* Wrong leading character */
	if (item->leading && item->answer[0] != item->leading) {
		upsdebugx(2, "%s: %s - invalid start character [%02x], expected [%02x]", __func__, item->info_type, item->answer[0], item->leading);
		return -1;
	}

	/* Check boundaries */
	if (item->to && item->to < item->from) {
		upsdebugx(1, "%s: in %s, starting char's position (%d) follows ending char's one (%d)", __func__, item->info_type, item->from, item->to);
		return -1;
	}

	/* Get value */
	if (strlen(item->answer)) {
		snprintf(item->value, sizeof(item->value), "%.*s", item->to ? 1 + item->to - item->from : (int)strcspn(item->answer, "\r") - item->from, item->answer + item->from);
	} else {
		snprintf(item->value, sizeof(item->value), "%s", "");
	}

	return 0;
}

/* See header file for details. */
int	qx_process(item_t *item, const char *command)
{
	char	buf[sizeof(item->answer) - 1] = "",
		cmd[command ? (strlen(command) >= SMALLBUF ? strlen(command) + 1 : SMALLBUF) : (item->command && strlen(item->command) >= SMALLBUF ? strlen(item->command) + 1 : SMALLBUF)];
	int	len;

	/* Prepare the command to be used */
	memset(cmd, 0, sizeof(cmd));
	snprintf(cmd, sizeof(cmd), "%s", command ? command : item->command);

	/* Preprocess the command */
	if (
		item->preprocess_command != NULL &&
		item->preprocess_command(item, cmd, sizeof(cmd)) == -1
	) {
		upsdebugx(4, "%s: failed to preprocess command [%s]", __func__, item->info_type);
		return -1;
	}

	/* Send the command */
	len = qx_command(cmd, buf, sizeof(buf));

	memset(item->answer, 0, sizeof(item->answer));
	memcpy(item->answer, buf, sizeof(buf));

	/* Preprocess the answer */
	if (item->preprocess_answer != NULL) {
		len = item->preprocess_answer(item, len);
		if (len == -1) {
			upsdebugx(4, "%s: failed to preprocess answer [%s]", __func__, item->info_type);
			/* Clear answer, preventing it from being reused by next items with same command */
			memset(item->answer, 0, sizeof(item->answer));
			return -1;
		}
	}

	/* Process the answer to get the value */
	return qx_process_answer(item, len);
}

/* See header file for details. */
int	ups_infoval_set(item_t *item)
{
	char	value[SMALLBUF] = "";

	/* Item need to be preprocessed? */
	if (item->preprocess != NULL){

		/* Process the value returned by the UPS to NUT standards */
		if (item->preprocess(item, value, sizeof(value))) {
			upsdebugx(4, "%s: failed to preprocess value [%s: %s]", __func__, item->info_type, item->value);
			return -1;
		}

		/* Deal with status items */
		if (!strncmp(item->info_type, "ups.status", 10)) {
			if (strlen(value) > 0)
				update_status(value);
			return 0;
		}

		/* Deal with alarm items */
		if (!strncmp(item->info_type, "ups.alarm", 9)) {
			if (strlen(value) > 0)
				alarm_set(value);
			return 0;
		}

	} else {

		snprintf(value, sizeof(value), "%s", item->value);

		/* Cover most of the cases: either left/right filled with hashes, spaces or a mix of both */
		if (item->qxflags & QX_FLAG_TRIM)
			str_trim_m(value, "# ");

		if (strcasecmp(item->dfl, "%s")) {

			if (strspn(value, "0123456789 .") != strlen(value)) {
				upsdebugx(2, "%s: non numerical value [%s: %s]", __func__, item->info_type, value);
				return -1;
			}

			snprintf(value, sizeof(value), item->dfl, strtod(value, NULL));
		}

	}

	if (item->qxflags & QX_FLAG_NONUT) {
		upslogx(LOG_INFO, "%s: %s", item->info_type, value);
		return 1;
	}

	if (!strlen(value)) {
		upsdebugx(1, "%s: non significant value [%s]", __func__, item->info_type);
		return -1;
	}

	dstate_setinfo(item->info_type, "%s", value);

	/* Fill batt.{chrg,runt}.act for guesstimation */
	if (!strcasecmp(item->info_type, "battery.charge"))
		batt.chrg.act = strtol(value, NULL, 10);
	else if (!strcasecmp(item->info_type, "battery.runtime"))
		batt.runt.act = strtol(value, NULL, 10);

	return 1;
}

/* See header file for details. */
int	qx_status(void)
{
	return ups_status;
}
