/* nutdrv_qx_voltronic.c - Subdriver for Voltronic Power UPSes
 *
 * Copyright (C)
 *   2013 Daniele Pezzini <hyouko@gmail.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 */

#include "main.h"
#include "nut_float.h"
#include "nut_stdint.h"
#include "nutdrv_qx.h"
#include "nutdrv_qx_voltronic.h"

#define VOLTRONIC_VERSION "Voltronic 0.14"

/* Support functions */
static int	voltronic_claim(void);
static void	voltronic_makevartable(void);
static void	voltronic_massive_unskip(const long protocol);

/* Range/enum functions */
static int	voltronic_batt_low(char *value, const size_t len);
static int	voltronic_bypass_volt_max(char *value, const size_t len);
static int	voltronic_bypass_volt_min(char *value, const size_t len);
static int	voltronic_bypass_freq_max(char *value, const size_t len);
static int	voltronic_bypass_freq_min(char *value, const size_t len);
static int	voltronic_eco_freq_min(char *value, const size_t len);
static int	voltronic_eco_freq_max(char *value, const size_t len);

/* Preprocess functions */
static int	voltronic_process_setvar(item_t *item, char *value, const size_t valuelen);
static int	voltronic_process_command(item_t *item, char *value, const size_t valuelen);
static int	voltronic_capability(item_t *item, char *value, const size_t valuelen);
static int	voltronic_capability_set(item_t *item, char *value, const size_t valuelen);
static int	voltronic_capability_set_nonut(item_t *item, char *value, const size_t valuelen);
static int	voltronic_capability_reset(item_t *item, char *value, const size_t valuelen);
static int	voltronic_eco_volt(item_t *item, char *value, const size_t valuelen);
static int	voltronic_eco_volt_range(item_t *item, char *value, const size_t valuelen);
static int	voltronic_eco_freq(item_t *item, char *value, const size_t valuelen);
static int	voltronic_bypass(item_t *item, char *value, const size_t valuelen);
static int	voltronic_batt_numb(item_t *item, char *value, const size_t valuelen);
static int	voltronic_batt_runtime(item_t *item, char *value, const size_t valuelen);
static int	voltronic_protocol(item_t *item, char *value, const size_t valuelen);
static int	voltronic_fault(item_t *item, char *value, const size_t valuelen);
static int	voltronic_warning(item_t *item, char *value, const size_t valuelen);
static int	voltronic_mode(item_t *item, char *value, const size_t valuelen);
static int	voltronic_status(item_t *item, char *value, const size_t valuelen);
static int	voltronic_output_powerfactor(item_t *item, char *value, const size_t valuelen);
static int	voltronic_serial_numb(item_t *item, char *value, const size_t valuelen);
static int	voltronic_outlet(item_t *item, char *value, const size_t valuelen);
static int	voltronic_outlet_delay(item_t *item, char *value, const size_t valuelen);
static int	voltronic_outlet_delay_set(item_t *item, char *value, const size_t valuelen);
static int	voltronic_p31b(item_t *item, char *value, const size_t valuelen);
static int	voltronic_p31b_set(item_t *item, char *value, const size_t valuelen);
static int	voltronic_p31g(item_t *item, char *value, const size_t valuelen);
static int	voltronic_p31g_set(item_t *item, char *value, const size_t valuelen);
static int	voltronic_phase(item_t *item, char *value, const size_t valuelen);
static int	voltronic_phase_set(item_t *item, char *value, const size_t valuelen);
static int	voltronic_parallel(item_t *item, char *value, const size_t valuelen);

/* Capability vars */
static char	*bypass_alarm,
		*battery_alarm,
		*bypass_when_off,
		*alarm_control,
		*converter_mode,
		*eco_mode,
		*battery_open_status_check,
		*bypass_forbidding,
		*site_fault_detection,
		*advanced_eco_mode,
		*constant_phase_angle,
		*limited_runtime_on_battery;

/* ups.conf settings */
static long	max_bypass_volt,
		min_bypass_volt,
		battery_number,
		output_phase_angle,
		work_range_type;
static double	max_bypass_freq,
		min_bypass_freq;


/* == Ranges/enums == */

/* Range for ups.delay.start */
static info_rw_t	voltronic_r_ondelay[] = {
	{ "0", 0 },
	{ "599940", 0 },
	{ "", 0 }
};

/* Range for ups.delay.shutdown */
static info_rw_t	voltronic_r_offdelay[] = {
	{ "12", 0 },
	{ "5940", 0 },
	{ "", 0 }
};

/* Enumlist for output phase angle */
static info_rw_t	voltronic_e_phase[] = {
	{ "000", 0 },
	{ "120", 0 },
	{ "180", 0 },
	{ "240", 0 },
	{ "", 0 }
};

/* Range for battery low voltage */
static info_rw_t	voltronic_r_batt_low[] = {
	{ "20", 0 },
	{ "24", voltronic_batt_low },
	{ "28", voltronic_batt_low },
	{ "", 0 }
};

/* Preprocess range value for battery low voltage */
static int	voltronic_batt_low(char *value, const size_t len)
{
	long		val = strtol(value, NULL, 10);
	const char	*ovn = dstate_getinfo("output.voltage.nominal"),
			*ocn = dstate_getinfo("output.current.nominal");
	NUT_UNUSED_VARIABLE(len);

	if (!ovn || !ocn) {
		upsdebugx(2, "%s: unable to get the value of output voltage nominal/output current nominal", __func__);
		return -1;
	}

	if ((strtol(ovn, NULL, 10) * strtol(ocn, NULL, 10)) < 1000) {

		if (val == 24)
			return 0;
		else
			return -1;

	} else {

		if (val == 28)
			return 0;
		else
			return -1;
	}
}

/* Range for outlet.n.delay.shutdown */
static info_rw_t	voltronic_r_outlet_delay[] = {
	{ "0", 0 },
	{ "59940", 0 },
	{ "", 0 }
};

/* Enumlist for device grid working range type */
static info_rw_t	voltronic_e_work_range[] = {
	{ "Appliance", 0 },	/* 00 */
	{ "UPS", 0 },		/* 01 */
	{ "", 0 }
};

/* Enumlist for battery type */
static info_rw_t	voltronic_e_batt_type[] = {
	{ "Li", 0 },		/* 00 */
	{ "Flooded", 0 },	/* 01 */
	{ "AGM", 0 },		/* 02 */
	{ "", 0 }
};

/* Range for number of battery packs */
static info_rw_t	voltronic_r_batt_packs[] = {
	{ "1", 0 },
	{ "99", 0 },
	{ "", 0 }
};

/* Range for number of batteries */
static info_rw_t	voltronic_r_batt_numb[] = {
	{ "1", 0 },
	{ "9", 0 },
	{ "", 0 }
};

/* Range for Bypass Mode maximum voltage */
static info_rw_t	voltronic_r_bypass_volt_max[] = {
	{ "60", voltronic_bypass_volt_max },	/* P09 */
	{ "115", voltronic_bypass_volt_max },	/* P02/P03/P10/P13/P14/P99 ivn<200 */
	{ "120", voltronic_bypass_volt_max },	/* P01 ivn<200 */
	{ "132", voltronic_bypass_volt_max },	/* P99 ivn<200 */
	{ "138", voltronic_bypass_volt_max },	/* P02/P03/P10/P13/P14 ivn<200 */
	{ "140", voltronic_bypass_volt_max },	/* P01 ivn<200, P09 */
	{ "230", voltronic_bypass_volt_max },	/* P01 ivn>=200 */
	{ "231", voltronic_bypass_volt_max },	/* P02/P03/P10/P13/P14/P99 ivn>=200 */
	{ "261", voltronic_bypass_volt_max },	/* P99 ivn>=200 */
	{ "264", voltronic_bypass_volt_max },	/* P01 ivn>=200 */
	{ "276", voltronic_bypass_volt_max },	/* P02/P03/P10/P13/P14 ivn>=200 */
	{ "", 0 }
};

/* Preprocess range value for Bypass Mode maximum voltage */
static int	voltronic_bypass_volt_max(char *value, const size_t len)
{
	long	protocol = strtol(dstate_getinfo("ups.firmware.aux")+1, NULL, 10),
			val = strtol(value, NULL, 10),
			ivn;
	const char	*involtnom = dstate_getinfo("input.voltage.nominal");
	NUT_UNUSED_VARIABLE(len);

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

	ivn = strtol(involtnom, NULL, 10);

	switch (val)
	{
	case 60:	/* P09 */

		if (protocol == 9)
			return 0;

		break;

	case 115:	/* P02/P03/P10/P13/P14/P99 ivn<200 */

		if (ivn >= 200)
			return -1;

		if (protocol == 2 || protocol == 3 || protocol == 10 || protocol == 13 || protocol == 14 || protocol == 99)
			return 0;

		break;

	case 120:	/* P01 ivn<200 */

		if (ivn >= 200)
			return -1;

		if (protocol == 1)
			return 0;

		break;

	case 132:	/* P99 ivn<200 */

		if (ivn >= 200)
			return -1;

		if (protocol == 99)
			return 0;

		break;

	case 138:	/* P02/P03/P10/P13/P14 ivn<200 */

		if (ivn >= 200)
			return -1;

		if (protocol == 2 || protocol == 3 || protocol == 10 || protocol == 13 || protocol == 14)
			return 0;

		break;

	case 140:	/* P01 ivn<200, P09 */

		if (protocol == 9)
			return 0;

		if (ivn >= 200)
			return -1;

		if (protocol == 1)
			return 0;

		break;

	case 230:	/* P01 ivn>=200 */

		if (ivn < 200)
			return -1;

		if (protocol == 1)
			return 0;

		break;

	case 231:	/* P02/P03/P10/P13/P14/P99 ivn>=200 */

		if (ivn < 200)
			return -1;

		if (protocol == 2 || protocol == 3 || protocol == 10 || protocol == 13 || protocol == 14 || protocol == 99)
			return 0;

		break;

	case 261:	/* P99 ivn>=200 */

		if (ivn < 200)
			return -1;

		if (protocol == 99)
			return 0;

		break;

	case 264:	/* P01 ivn>=200 */

		if (ivn < 200)
			return -1;

		if (protocol == 1)
			return 0;

		break;

	case 276:	/* P02/P03/P10/P13/P14 ivn>=200 */

		if (ivn < 200)
			return -1;

		if (protocol == 2 || protocol == 3 || protocol == 10 || protocol == 13 || protocol == 14)
			return 0;

		break;

	default:

		upsdebugx(2, "%s: unknown value (%s)", __func__, value);
		break;

	}

	return -1;
}

/* Range for Bypass Mode minimum voltage */
static info_rw_t	voltronic_r_bypass_volt_min[] = {
	{ "50", voltronic_bypass_volt_min },	/* P99 ivn<200 */
	{ "55", voltronic_bypass_volt_min },	/* P02/P03/P10/P13/P14 ivn<200 */
	{ "60", voltronic_bypass_volt_min },	/* P09 */
	{ "85", voltronic_bypass_volt_min },	/* P01/P99 ivn<200 */
	{ "104", voltronic_bypass_volt_min },	/* P02/P03/P10/P13/P14 ivn<200 */
	{ "110", voltronic_bypass_volt_min },	/* P02/P03/P10/P13/P14 ivn>=200 */
	{ "115", voltronic_bypass_volt_min },	/* P01 ivn<200 */
	{ "140", voltronic_bypass_volt_min },	/* P09 */
	{ "149", voltronic_bypass_volt_min },	/* P99 ivn>=200 */
	{ "170", voltronic_bypass_volt_min },	/* P01 ivn>=200 */
	{ "209", voltronic_bypass_volt_min },	/* P02/P03/P10/P13/P14/P99 ivn>=200 */
	{ "220", voltronic_bypass_volt_min },	/* P01 ivn>=200 */
	{ "", 0 }
};

/* Preprocess range value for Bypass Mode minimum voltage */
static int	voltronic_bypass_volt_min(char *value, const size_t len)
{
	long	protocol = strtol(dstate_getinfo("ups.firmware.aux")+1, NULL, 10),
			val = strtol(value, NULL, 10),
			ivn;
	const char	*involtnom = dstate_getinfo("input.voltage.nominal");
	NUT_UNUSED_VARIABLE(len);

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

	ivn = strtol(involtnom, NULL, 10);

	switch (val)
	{
	case 50:	/* P99 ivn<200 */

		if (ivn >= 200)
			return -1;

		if (protocol == 99)
			return 0;

		break;

	case 55:	/* P02/P03/P10/P13/P14 ivn<200 */

		if (ivn >= 200)
			return -1;

		if (protocol == 2 || protocol == 3 || protocol == 10 || protocol == 13 || protocol == 14)
			return 0;

		break;

	case 60:	/* P09 */
	case 140:	/* P09 */

		if (protocol == 9)
			return 0;

		break;

	case 85:	/* P01/P99 ivn<200 */

		if (ivn >= 200)
			return -1;

		if (protocol == 1 || protocol == 99)
			return 0;

		break;

	case 104:	/* P02/P03/P10/P13/P14 ivn<200 */

		if (ivn >= 200)
			return -1;

		if (protocol == 2 || protocol == 3 || protocol == 10 || protocol == 13 || protocol == 14)
			return 0;

		break;

	case 110:	/* P02/P03/P10/P13/P14 ivn>=200 */

		if (ivn < 200)
			return -1;

		if (protocol == 2 || protocol == 3 || protocol == 10 || protocol == 13 || protocol == 14)
			return 0;

		break;

	case 115:	/* P01 ivn<200 */

		if (ivn >= 200)
			return -1;

		if (protocol == 1)
			return 0;

		break;

	case 149:	/* P99 ivn>=200 */

		if (ivn < 200)
			return -1;

		if (protocol == 99)
			return 0;

		break;

	case 170:	/* P01 ivn>=200 */

		if (ivn < 200)
			return -1;

		if (protocol == 1)
			return 0;

		break;

	case 209:	/* P02/P03/P10/P13/P14/P99 ivn>=200 */

		if (ivn < 200)
			return -1;

		if (protocol == 2 || protocol == 3 || protocol == 10 || protocol == 13 || protocol == 14 || protocol == 99)
			return 0;

		break;

	case 220:	/* P01 ivn>=200 */

		if (ivn < 200)
			return -1;

		if (protocol == 1)
			return 0;

		break;

	default:

		upsdebugx(2, "%s: unknown value (%s)", __func__, value);
		break;

	}

	return -1;
}

/* Range for Bypass Mode maximum frequency */
static info_rw_t	voltronic_r_bypass_freq_max[] = {
	{ "51.0", voltronic_bypass_freq_max },	/* P01/P09/P02/P03/P10/P13/P14/P99 ofn==50.0 */
	{ "54.0", voltronic_bypass_freq_max },	/* P02/P03/P10/P13/P14/P99 ofn==50.0 */
	{ "60.0", voltronic_bypass_freq_max },	/* P01/P09 ofn==50.0 */
	{ "61.0", voltronic_bypass_freq_max },	/* P01/P09/P02/P03/P10/P13/P14/P99 ofn==60.0 */
	{ "64.0", voltronic_bypass_freq_max },	/* P02/P03/P10/P13/P14/P99 ofn==60.0 */
	{ "70.0", voltronic_bypass_freq_max },	/* P01/P09 ofn==60.0 */
	{ "", 0 }
};

/* Preprocess range value for Bypass Mode maximum frequency */
static int	voltronic_bypass_freq_max(char *value, const size_t len)
{
	long	protocol = strtol(dstate_getinfo("ups.firmware.aux")+1, NULL, 10),
			val = strtol(value, NULL, 10);
	double		ofn;
	const char	*outfreqnom = dstate_getinfo("output.frequency.nominal");
	NUT_UNUSED_VARIABLE(len);

	if (!outfreqnom) {
		upsdebugx(2, "%s: unable to get output.frequency.nominal", __func__);
		return -1;
	}

	ofn = strtod(outfreqnom, NULL);

	switch (val)
	{
	case 51:	/* P01/P09/P02/P03/P10/P13/P14/P99 ofn==50.0 */

		if (ofn != 50.0)
			return -1;

		if (protocol == 1 || protocol == 2 || protocol == 3 || protocol == 9 || protocol == 10 || protocol == 13 || protocol == 14 || protocol == 99)
			return 0;

		break;

	case 54:	/* P02/P03/P10/P13/P14/P99 ofn==50.0 */

		if (ofn != 50.0)
			return -1;

		if (protocol == 2 || protocol == 3 || protocol == 10 || protocol == 13 || protocol == 14 || protocol == 99)
			return 0;

		break;

	case 60:	/* P01/P09 ofn==50.0 */

		if (ofn != 50.0)
			return -1;

		if (protocol == 1 || protocol == 9)
			return 0;

		break;

	case 61:	/* P01/P09/P02/P03/P10/P13/P14/P99 ofn==60.0 */

		if (ofn != 60.0)
			return -1;

		if (protocol == 1 || protocol == 2 || protocol == 3 || protocol == 9 || protocol == 10 || protocol == 13 || protocol == 14 || protocol == 99)
			return 0;

		break;

	case 64:	/* P02/P03/P10/P13/P14/P99 ofn==60.0 */

		if (ofn != 60.0)
			return -1;

		if (protocol == 2 || protocol == 3 || protocol == 10 || protocol == 13 || protocol == 14 || protocol == 99)
			return 0;

		break;

	case 70:	/* P01/P09 ofn==60.0 */

		if (ofn != 60.0)
			return -1;

		if (protocol == 1 || protocol == 9)
			return 0;

		break;

	default:

		upsdebugx(2, "%s: unknown value (%s)", __func__, value);
		break;

	}

	return -1;
}

/* Range for Bypass Mode minimum frequency */
static info_rw_t	voltronic_r_bypass_freq_min[] = {
	{ "40.0", voltronic_bypass_freq_min },	/* P01/P09 ofn==50.0 */
	{ "46.0", voltronic_bypass_freq_min },	/* P02/P03/P10/P13/P14/P99 ofn==50.0 */
	{ "49.0", voltronic_bypass_freq_min },	/* P01/P09/P02/P03/P10/P13/P14/P99 ofn==50.0 */
	{ "50.0", voltronic_bypass_freq_min },	/* P01/P09 ofn==60.0 */
	{ "56.0", voltronic_bypass_freq_min },	/* P02/P03/P10/P13/P14/P99 ofn==60.0 */
	{ "59.0", voltronic_bypass_freq_min },	/* P01/P09/P02/P03/P10/P13/P14/P99 ofn==60.0 */
	{ "", 0 }
};

/* Preprocess range value for Bypass Mode minimum frequency */
static int	voltronic_bypass_freq_min(char *value, const size_t len)
{
	long	protocol = strtol(dstate_getinfo("ups.firmware.aux")+1, NULL, 10),
			val = strtol(value, NULL, 10);
	double		ofn;
	const char	*outfreqnom = dstate_getinfo("output.frequency.nominal");
	NUT_UNUSED_VARIABLE(len);

	if (!outfreqnom) {
		upsdebugx(2, "%s: unable to get output.frequency.nominal", __func__);
		return -1;
	}

	ofn = strtod(outfreqnom, NULL);

	switch (val)
	{
	case 40:	/* P01/P09 ofn==50.0 */

		if (ofn != 50.0)
			return -1;

		if (protocol == 1 || protocol == 9)
			return 0;

		break;

	case 46:	/* P02/P03/P10/P13/P14/P99 ofn==50.0 */

		if (ofn != 50.0)
			return -1;

		if (protocol == 2 || protocol == 3 || protocol == 10 || protocol == 13 || protocol == 14 || protocol == 99)
			return 0;

		break;

	case 49:	/* P01/P09/P02/P03/P10/P13/P14/P99 ofn==50.0 */

		if (ofn != 50.0)
			return -1;

		if (protocol == 1 || protocol == 2 || protocol == 3 || protocol == 9 || protocol == 10 || protocol == 13 || protocol == 14 || protocol == 99)
			return 0;

		break;

	case 50:	/* P01/P09 ofn==60.0 */

		if (ofn != 60.0)
			return -1;

		if (protocol == 1 || protocol == 9)
			return 0;

		break;

	case 56:	/* P02/P03/P10/P13/P14/P99 ofn==60.0 */

		if (ofn != 60.0)
			return -1;

		if (protocol == 2 || protocol == 3 || protocol == 10 || protocol == 13 || protocol == 14 || protocol == 99)
			return 0;

		break;

	case 59:	/* P01/P09/P02/P03/P10/P13/P14/P99 ofn==60.0 */

		if (ofn != 60.0)
			return -1;

		if (protocol == 1 || protocol == 2 || protocol == 3 || protocol == 9 || protocol == 10 || protocol == 13 || protocol == 14 || protocol == 99)
			return 0;

		break;

	default:

		upsdebugx(2, "%s: unknown value (%s)", __func__, value);
		break;

	}

	return -1;
}

/* Range for Eco Mode maximum voltage: filled at runtime by voltronic_eco_volt */
static info_rw_t	voltronic_r_eco_volt_max[] = {
	{ "", 0 },
	{ "", 0 },
	{ "", 0 }
};

/* Range for ECO Mode minimum voltage: filled at runtime by voltronic_eco_volt */
static info_rw_t	voltronic_r_eco_volt_min[] = {
	{ "", 0 },
	{ "", 0 },
	{ "", 0 }
};

/* Range for ECO Mode minimum frequency */
static info_rw_t	voltronic_r_eco_freq_min[] = {
	{ "40.0", voltronic_eco_freq_min },	/* P01/P09 ofn==50.0 */
	{ "46.0", voltronic_eco_freq_min },	/* P02/P03/P10/P13/P14/P99 ofn==50.0 */
	{ "47.0", voltronic_eco_freq_min },	/* P01/P09 ofn==50.0 */
	{ "48.0", voltronic_eco_freq_min },	/* P02/P03/P10/P13/P14/P99 ofn==50.0 */
	{ "50.0", voltronic_eco_freq_min },	/* P01/P09 ofn==60.0 */
	{ "56.0", voltronic_eco_freq_min },	/* P02/P03/P10/P13/P14/P99 ofn==60.0 */
	{ "57.0", voltronic_eco_freq_min },	/* P01/P09 ofn==60.0 */
	{ "58.0", voltronic_eco_freq_min },	/* P02/P03/P10/P13/P14/P99 ofn==60.0 */
	{ "", 0 }
};

/* Preprocess range value for ECO Mode minimum frequency */
static int	voltronic_eco_freq_min(char *value, const size_t len)
{
	long	protocol = strtol(dstate_getinfo("ups.firmware.aux")+1, NULL, 10),
			val = strtol(value, NULL, 10);
	double		ofn;
	const char	*outfreqnom = dstate_getinfo("output.frequency.nominal");
	NUT_UNUSED_VARIABLE(len);

	if (!outfreqnom) {
		upsdebugx(2, "%s: unable to get output.frequency.nominal", __func__);
		return -1;
	}

	ofn = strtod(outfreqnom, NULL);

	switch (val)
	{
	case 40:	/* P01/P09 ofn==50.0 */

		if (ofn != 50.0)
			return -1;

		if (protocol == 1 || protocol == 9)
			return 0;

		break;

	case 46:	/* P02/P03/P10/P13/P14/P99 ofn==50.0 */

		if (ofn != 50.0)
			return -1;

		if (protocol == 2 || protocol == 3 || protocol == 10 || protocol == 13 || protocol == 14 || protocol == 99)
			return 0;

		break;

	case 47:	/* P01/P09 ofn==50.0 */

		if (ofn != 50.0)
			return -1;

		if (protocol == 1 || protocol == 9)
			return 0;

		break;

	case 48:	/* P02/P03/P10/P13/P14/P99 ofn==50.0 */

		if (ofn != 50.0)
			return -1;

		if (protocol == 2 || protocol == 3 || protocol == 10 || protocol == 13 || protocol == 14 || protocol == 99)
			return 0;

		break;

	case 50:	/* P01/P09 ofn==60.0 */

		if (ofn != 60.0)
			return -1;

		if (protocol == 1 || protocol == 9)
			return 0;

		break;

	case 56:	/* P02/P03/P10/P13/P14/P99 ofn==60.0 */

		if (ofn != 60.0)
			return -1;

		if (protocol == 2 || protocol == 3 || protocol == 10 || protocol == 13 || protocol == 14 || protocol == 99)
			return 0;

		break;

	case 57:	/* P01/P09 ofn==60.0 */

		if (ofn != 60.0)
			return -1;

		if (protocol == 1 || protocol == 9)
			return 0;

		break;

	case 58:	/* P02/P03/P10/P13/P14/P99 ofn==60.0 */

		if (ofn != 60.0)
			return -1;

		if (protocol == 2 || protocol == 3 || protocol == 10 || protocol == 13 || protocol == 14 || protocol == 99)
			return 0;

		break;

	default:

		upsdebugx(2, "%s: unknown value (%s)", __func__, value);
		break;

	}

	return -1;
}

/* Range for ECO Mode maximum frequency */
static info_rw_t	voltronic_r_eco_freq_max[] = {
	{ "52.0", voltronic_eco_freq_max },	/* P02/P03/P10/P13/P14/P99 ofn==50.0 */
	{ "53.0", voltronic_eco_freq_max },	/* P01/P09 ofn==50.0 */
	{ "54.0", voltronic_eco_freq_max },	/* P02/P03/P10/P13/P14/P99 ofn==50.0 */
	{ "60.0", voltronic_eco_freq_max },	/* P01/P09 ofn==50.0 */
	{ "62.0", voltronic_eco_freq_max },	/* P02/P03/P10/P13/P14/P99 ofn==60.0 */
	{ "63.0", voltronic_eco_freq_max },	/* P01/P09 ofn==60.0 */
	{ "64.0", voltronic_eco_freq_max },	/* P02/P03/P10/P13/P14/P99 ofn==60.0 */
	{ "70.0", voltronic_eco_freq_max },	/* P01/P09 ofn==60.0 */
	{ "", 0 }
};

/* Preprocess range value for ECO Mode maximum frequency */
static int	voltronic_eco_freq_max(char *value, const size_t len)
{
	long	protocol = strtol(dstate_getinfo("ups.firmware.aux")+1, NULL, 10),
			val = strtol(value, NULL, 10);
	double		ofn;
	const char	*outfreqnom = dstate_getinfo("output.frequency.nominal");
	NUT_UNUSED_VARIABLE(len);

	if (!outfreqnom) {
		upsdebugx(2, "%s: unable to get output.frequency.nominal", __func__);
		return -1;
	}

	ofn = strtod(outfreqnom, NULL);

	switch (val)
	{
	case 52:	/* P02/P03/P10/P13/P14/P99 ofn==50.0 */

		if (ofn != 50.0)
			return -1;

		if (protocol == 2 || protocol == 3 || protocol == 10 || protocol == 13 || protocol == 14 || protocol == 99)
			return 0;

		break;

	case 53:	/* P01/P09 ofn==50.0 */

		if (ofn != 50.0)
			return -1;

		if (protocol == 1 || protocol == 9)
			return 0;

		break;

	case 54:	/* P02/P03/P10/P13/P14/P99 ofn==50.0 */

		if (ofn != 50.0)
			return -1;

		if (protocol == 2 || protocol == 3 || protocol == 10 || protocol == 13 || protocol == 14 || protocol == 99)
			return 0;

		break;

	case 60:	/* P01/P09 ofn==50.0 */

		if (ofn != 50.0)
			return -1;

		if (protocol == 1 || protocol == 9)
			return 0;

		break;

	case 62:	/* P02/P03/P10/P13/P14/P99 ofn==60.0 */

		if (ofn != 60.0)
			return -1;

		if (protocol == 2 || protocol == 3 || protocol == 10 || protocol == 13 || protocol == 14 || protocol == 99)
			return 0;

		break;

	case 63:	/* P01/P09 ofn==60.0 */

		if (ofn != 60.0)
			return -1;

		if (protocol == 1 || protocol == 9)
			return 0;

		break;

	case 64:	/* P02/P03/P10/P13/P14/P99 ofn==60.0 */

		if (ofn != 60.0)
			return -1;

		if (protocol == 2 || protocol == 3 || protocol == 10 || protocol == 13 || protocol == 14 || protocol == 99)
			return 0;

		break;

	case 70:	/* P01/P09 ofn==60.0 */

		if (ofn != 60.0)
			return -1;

		if (protocol == 1 || protocol == 9)
			return 0;

		break;

	default:

		upsdebugx(2, "%s: unknown value (%s)", __func__, value);
		break;

	}

	return -1;
}

/* Enumlist for UPS capabilities that have a NUT var */
static info_rw_t	voltronic_e_cap[] = {
	{ "no", 0 },
	{ "yes", 0 },
	{ "", 0 }
};

/* Enumlist for NONUT capabilities */
static info_rw_t	voltronic_e_cap_nonut[] = {
	{ "enabled", 0 },
	{ "disabled", 0 },
	{ "", 0 }
};


/* == qx2nut lookup table == */
static item_t	voltronic_qx2nut[] = {

	/* Query UPS for protocol
	 * > [QPI\r]
	 * < [(PI00\r]
	 *    012345
	 *    0
	 */

	{ "ups.firmware.aux",		0,	NULL,	"QPI\r",	"",	6,	'(',	"",	1,	4,	"%s",	QX_FLAG_STATIC,	NULL,	NULL,	voltronic_protocol },

	/* Query UPS for ratings
	 * > [QRI\r]
	 * < [(230.0 004 024.0 50.0\r]
	 *    0123456789012345678901
	 *    0         1         2
	 */

	{ "output.voltage.nominal",	0,	NULL,	"QRI\r",	"",	22,	'(',	"",	1,	5,	"%.1f",	QX_FLAG_STATIC,		NULL,	NULL,	NULL },
	{ "output.current.nominal",	0,	NULL,	"QRI\r",	"",	22,	'(',	"",	7,	9,	"%.0f",	QX_FLAG_STATIC,		NULL,	NULL,	NULL },
	{ "battery.voltage.nominal",	0,	NULL,	"QRI\r",	"",	22,	'(',	"",	11,	15,	"%.1f",	QX_FLAG_SEMI_STATIC,	NULL,	NULL,	NULL },	/* as *per battery pack*: the value will change when the number of batteries is changed (battery_number through BATNn) */
	{ "output.frequency.nominal",	0,	NULL,	"QRI\r",	"",	22,	'(',	"",	17,	20,	"%.1f",	QX_FLAG_STATIC,		NULL,	NULL,	NULL },

	/* Query UPS for ratings
	 * > [QMD\r]
	 * < [(#######OLHVT1K0 ###1000 80 1/1 230 230 02 12.0\r]	<- Some UPS may reply with spaces instead of hashes
	 *    012345678901234567890123456789012345678901234567
	 *    0         1         2         3         4
	 */

	{ "device.model",		0,	NULL,	"QMD\r",	"",	48,	'(',	"",	1,	15,	"%s",	QX_FLAG_STATIC | QX_FLAG_TRIM,	NULL,	NULL,	NULL },
	{ "ups.power.nominal",		0,	NULL,	"QMD\r",	"",	48,	'(',	"",	17,	23,	"%s",	QX_FLAG_STATIC | QX_FLAG_TRIM,	NULL,	NULL,	NULL },
	{ "output.powerfactor",		0,	NULL,	"QMD\r",	"",	48,	'(',	"",	25,	26,	"%.1f",	QX_FLAG_STATIC,	NULL,	NULL,	voltronic_output_powerfactor },
	{ "input.phases",		0,	NULL,	"QMD\r",	"",	48,	'(',	"",	28,	28,	"%.0f",	QX_FLAG_STATIC,	NULL,	NULL,	NULL },
	{ "output.phases",		0,	NULL,	"QMD\r",	"",	48,	'(',	"",	30,	30,	"%.0f",	QX_FLAG_STATIC,	NULL,	NULL,	NULL },
	{ "input.voltage.nominal",	0,	NULL,	"QMD\r",	"",	48,	'(',	"",	32,	34,	"%.1f",	QX_FLAG_STATIC,	NULL,	NULL,	NULL },
	{ "output.voltage.nominal",	0,	NULL,	"QMD\r",	"",	48,	'(',	"",	36,	38,	"%.1f",	QX_FLAG_STATIC,	NULL,	NULL,	NULL },	/* redundant with value from QRI */
/*	{ "battery_number",		ST_FLAG_RW,	voltronic_r_batt_numb,	"QMD\r",	"",	48,	'(',	"",	40,	41,	"%d",	QX_FLAG_SEMI_STATIC | QX_FLAG_RANGE | QX_FLAG_NONUT,	NULL,	NULL,	voltronic_batt_numb },	*//* redundant with value from QBV */
/*	{ "battery.voltage.nominal",	0,	NULL,	"QMD\r",	"",	48,	'(',	"",	43,	46,	"%.1f",	QX_FLAG_STATIC,	NULL,	NULL,	NULL },	*//* as *per battery* vs. *per pack* reported by QRI */

	/* Query UPS for ratings
	 * > [F\r]
	 * < [#220.0 000 024.0 50.0\r]
	 *    0123456789012345678901
	 *    0         1         2
	 */

	{ "input.voltage.nominal",	0,	NULL,	"F\r",	"",	22,	'#',	"",	1,	5,	"%.1f",	QX_FLAG_STATIC,	NULL,	NULL,	NULL },
	{ "input.current.nominal",	0,	NULL,	"F\r",	"",	22,	'#',	"",	7,	9,	"%.1f",	QX_FLAG_STATIC,	NULL,	NULL,	NULL },
	{ "battery.voltage.nominal",	0,	NULL,	"F\r",	"",	22,	'#',	"",	11,	15,	"%.1f",	QX_FLAG_STATIC,	NULL,	NULL,	NULL },
	{ "input.frequency.nominal",	0,	NULL,	"F\r",	"",	22,	'#',	"",	17,	20,	"%.1f",	QX_FLAG_STATIC,	NULL,	NULL,	NULL },

	/* Query UPS for manufacturer
	 * > [QMF\r]
	 * < [(#######BOH\r]	<- I don't know if it has a fixed length (-> so min length = ( + \r = 2). Hashes may be replaced by spaces
	 *    012345678901
	 *    0         1
	 */

	{ "device.mfr",		0,	NULL,	"QMF\r",	"",	2,	'(',	"",	1,	0,	"%s",	QX_FLAG_STATIC | QX_FLAG_TRIM,	NULL,	NULL,	NULL },

	/* Query UPS for firmware version
	 * > [QVFW\r]
	 * < [(VERFW:00322.02\r]
	 *    0123456789012345
	 *    0         1
	 */

	{ "ups.firmware",	0,	NULL,	"QVFW\r",	"",	16,	'(',	"",	7,	14,	"%s",	QX_FLAG_STATIC,	NULL,	NULL,	NULL },

	/* Query UPS for serial number
	 * > [QID\r]
	 * < [(12345679012345\r]	<- As far as I know it hasn't a fixed length -> min length = ( + \r = 2
	 *    0123456789012345
	 *    0         1
	 */

	{ "device.serial",	0,	NULL,	"QID\r",	"",	2,	'(',	"",	1,	0,	"%s",	QX_FLAG_STATIC,	NULL,	NULL,	voltronic_serial_numb },

	/* Query UPS for vendor infos
	 * > [I\r]
	 * < [#-------------   ------     VT12046Q  \r]
	 *    012345678901234567890123456789012345678
	 *    0         1         2         3
	 */

	{ "device.mfr",		0,	NULL,	"I\r",	"",	39,	'#',	"",	1,	15,	"%s",	QX_FLAG_STATIC | QX_FLAG_TRIM,	NULL,	NULL,	NULL },
	{ "device.model",	0,	NULL,	"I\r",	"",	39,	'#',	"",	17,	26,	"%s",	QX_FLAG_STATIC | QX_FLAG_TRIM,	NULL,	NULL,	NULL },
	{ "ups.firmware",	0,	NULL,	"I\r",	"",	39,	'#',	"",	28,	37,	"%s",	QX_FLAG_STATIC | QX_FLAG_TRIM,	NULL,	NULL,	NULL },

	/* Query UPS for status
	 * > [QGS\r]
	 * < [(234.9 50.0 229.8 50.0 000.0 000 369.1 ---.- 026.5 ---.- 018.8 100000000001\r]
	 *    0123456789012345678901234567890123456789012345678901234567890123456789012345
	 *    0         1         2         3         4         5         6         7
	 */

	{ "input.voltage",	0,	NULL,	"QGS\r",	"",	76,	'(',	"",	1,	5,	"%.1f",	0,	NULL,	NULL,	NULL },
	{ "input.frequency",	0,	NULL,	"QGS\r",	"",	76,	'(',	"",	7,	10,	"%.1f",	0,	NULL,	NULL,	NULL },
	{ "output.voltage",	0,	NULL,	"QGS\r",	"",	76,	'(',	"",	12,	16,	"%.1f",	0,	NULL,	NULL,	NULL },
	{ "output.frequency",	0,	NULL,	"QGS\r",	"",	76,	'(',	"",	18,	21,	"%.1f",	0,	NULL,	NULL,	NULL },
	{ "output.current",	0,	NULL,	"QGS\r",	"",	76,	'(',	"",	23,	27,	"%.1f",	0,	NULL,	NULL,	NULL },
	{ "ups.load",		0,	NULL,	"QGS\r",	"",	76,	'(',	"",	29,	31,	"%.0f",	0,	NULL,	NULL,	NULL },
/*	{ "unknown.1",		0,	NULL,	"QGS\r",	"",	76,	'(',	"",	33,	37,	"%.1f",	0,	NULL,	NULL,	NULL },	*//* Unknown */
/*	{ "unknown.2",		0,	NULL,	"QGS\r",	"",	76,	'(',	"",	39,	43,	"%.1f",	0,	NULL,	NULL,	NULL },	*//* Unknown */
	{ "battery.voltage",	0,	NULL,	"QGS\r",	"",	76,	'(',	"",	45,	49,	"%.2f",	0,	NULL,	NULL,	qx_multiply_battvolt },
/*	{ "unknown.3",		0,	NULL,	"QGS\r",	"",	76,	'(',	"",	51,	55,	"%.1f",	0,	NULL,	NULL,	NULL },	*//* Unknown */
	{ "ups.temperature",	0,	NULL,	"QGS\r",	"",	76,	'(',	"",	57,	61,	"%.1f",	0,	NULL,	NULL,	NULL },
	{ "ups.type",		0,	NULL,	"QGS\r",	"",	76,	'(',	"",	63,	64,	"%s",	QX_FLAG_SEMI_STATIC,	NULL,	NULL,	voltronic_status },
	{ "ups.status",		0,	NULL,	"QGS\r",	"",	76,	'(',	"",	65,	65,	"%s",	QX_FLAG_QUICK_POLL,	NULL,	NULL,	voltronic_status },	/* Utility Fail (Immediate) */
	{ "ups.status",		0,	NULL,	"QGS\r",	"",	76,	'(',	"",	66,	66,	"%s",	QX_FLAG_QUICK_POLL,	NULL,	NULL,	voltronic_status },	/* Battery Low */
	{ "ups.status",		0,	NULL,	"QGS\r",	"",	76,	'(',	"",	67,	67,	"%s",	QX_FLAG_QUICK_POLL,	NULL,	NULL,	voltronic_status },	/* Bypass/Boost or Buck Active */
	{ "ups.alarm",		0,	NULL,	"QGS\r",	"",	76,	'(',	"",	67,	67,	"%s",	0,			NULL,	NULL,	voltronic_status },	/* Bypass/Boost or Buck Active */
	{ "ups.alarm",		0,	NULL,	"QGS\r",	"",	76,	'(',	"",	68,	68,	"%s",	0,			NULL,	NULL,	voltronic_status },	/* UPS Fault */
/*	{ "unknown.4",		0,	NULL,	"QGS\r",	"",	76,	'(',	"",	69,	69,	"%s",	0,			NULL,	NULL,	voltronic_status },	*//* Unknown */
	{ "ups.status",		0,	NULL,	"QGS\r",	"",	76,	'(',	"",	70,	70,	"%s",	QX_FLAG_QUICK_POLL,	NULL,	NULL,	voltronic_status },	/* Test in Progress */
	{ "ups.status",		0,	NULL,	"QGS\r",	"",	76,	'(',	"",	71,	71,	"%s",	QX_FLAG_QUICK_POLL,	NULL,	NULL,	voltronic_status },	/* Shutdown Active */
	{ "ups.beeper.status",	0,	NULL,	"QGS\r",	"",	76,	'(',	"",	72,	72,	"%s",	0,			NULL,	NULL,	voltronic_status },	/* Beeper status - ups.beeper.status */
/*	{ "unknown.5",		0,	NULL,	"QGS\r",	"",	76,	'(',	"",	73,	73,	"%s",	0,			NULL,	NULL,	voltronic_status },	*//* Unknown */
/*	{ "unknown.6",		0,	NULL,	"QGS\r",	"",	76,	'(',	"",	74,	74,	"%s",	0,			NULL,	NULL,	voltronic_status },	*//* Unknown */

	/* Query UPS for actual working mode
	 * > [QMOD\r]
	 * < [(S\r]
	 *    012
	 *    0
	 */

	{ "ups.alarm",		0,	NULL,	"QMOD\r",	"",	3,	'(',	"",	1,	1,	"%s",	0,	NULL,	NULL,	voltronic_mode },
	{ "ups.status",		0,	NULL,	"QMOD\r",	"",	3,	'(',	"",	1,	1,	"%s",	0,	NULL,	NULL,	voltronic_mode },

	/* Query UPS for faults and their type. Unskipped when a fault is found in 12bit flag of QGS, otherwise you'll get a fake reply.
	 * > [QFS\r]
	 * < [(OK\r] <- No fault
	 *    0123
	 *    0
	 * < [(14 212.1 50.0 005.6 49.9 006 010.6 343.8 ---.- 026.2 021.8 01101100\r] <- Fault type + Short status
	 *    012345678901234567890123456789012345678901234567890123456789012345678
	 *    0         1         2         3         4         5         6
	 */

	{ "ups.alarm",		0,	NULL,	"QFS\r",	"",	4,	'(',	"",	1,	2,	"%s",	QX_FLAG_SKIP,	NULL,	NULL,	voltronic_fault },

	/* Query UPS for warnings and their type
	 * > [QWS\r]
	 * < [(0000000100000000000000000000000000000000000000000000000000000000\r]
	 *    012345678901234567890123456789012345678901234567890123456789012345
	 *    0         1         2         3         4         5         6
	 */

	{ "ups.alarm",		0,	NULL,	"QWS\r",	"",	66,	'(',	"",	1,	64,	"%s",	0,	NULL,	NULL,	voltronic_warning },

	/* Query UPS for actual infos about battery
	 * > [QBV\r]
	 * < [(026.5 02 01 068 255\r]
	 *    012345678901234567890
	 *    0         1         2
	 *
	 * NOTE: PowerWalker VFI 1500 CG PF1 as of
	 * https://github.com/networkupstools/nut/issues/2765
	 * reported a longer last component, so we do not expect
	 * an exact ending location here, just "to end of reply":
	 * < [(041.0 03 01 100 00037\r]
	 *    01234567890123456789012
	 *    0         1         2
	 */

	{ "battery.voltage",	0,		NULL,			"QBV\r",	"",	21,	'(',	"",	1,	5,	"%.2f",	0,	NULL,	NULL,	qx_multiply_battvolt },
	{ "battery_number",	ST_FLAG_RW,	voltronic_r_batt_numb,	"QBV\r",	"",	21,	'(',	"",	7,	9,	"%d",	QX_FLAG_SEMI_STATIC | QX_FLAG_RANGE | QX_FLAG_NONUT,	NULL,	NULL,	voltronic_batt_numb },	/* Number of batteries that make a pack */
	{ "battery.packs",	ST_FLAG_RW,	voltronic_r_batt_packs,	"QBV\r",	"",	21,	'(',	"",	10,	11,	"%.0f",	QX_FLAG_SEMI_STATIC | QX_FLAG_RANGE,	NULL,	NULL,	NULL },	/* Number of battery packs in parallel */
	{ "battery.charge",	0,		NULL,			"QBV\r",	"",	21,	'(',	"",	13,	15,	"%.0f",	0,	NULL,	NULL,	NULL },
	{ "battery.runtime",	0,		NULL,			"QBV\r",	"",	21,	'(',	"",	17,	0,	"%.0f",	0,	NULL,	NULL,	voltronic_batt_runtime },

	/* Query UPS for last seen min/max load level
	 * > [QLDL\r]
	 * < [(021 023\r]	<- minimum load level - maximum load level
	 *    012345678
	 *    0
	 */

	{ "output.power.minimum.percent",	0,	NULL,	"QLDL\r",	"",	9,	'(',	"",	1,	3,	"%.0f",	0,	NULL,	NULL,	NULL },
	{ "output.power.maximum.percent",	0,	NULL,	"QLDL\r",	"",	9,	'(',	"",	5,	7,	"%.0f",	0,	NULL,	NULL,	NULL },

	/* Query UPS for multi-phase voltages/frequencies
	 * > [Q3**\r]
	 * < [(123.4 123.4 123.4 123.4 123.4 123.4\r]	<- Q3PV
	 * < [(123.4 123.4 123.4 123.4 123.4 123.4\r]	<- Q3OV
	 * < [(123 123 123\r]	<- Q3PC
	 * < [(123 123 123\r]	<- Q3OC
	 * < [(123 123 123\r]	<- Q3LD
	 * < [(123.4 123.4 123.4\r]	<- Q3YV - P09 protocol
	 * < [(123.4 123.4 123.4 123.4 123.4 123.4\r]	<- Q3YV - P10/P03 protocols
	 *    0123456789012345678901234567890123456
	 *    0         1         2         3
	 *
	 * P09 = 2-phase input/2-phase output
	 * Q3PV	(Input Voltage L1 | Input Voltage L2 | Input Voltage L3 | Input Voltage L1-L2 | Input Voltage L1-L3 | Input Voltage L2-L3
	 * Q3OV	(Output Voltage L1 | Output Voltage L2 | Output Voltage L3 | Output Voltage L1-L2 | Output Voltage L1-L3 | Output Voltage L2-L3
	 * Q3PC	(Input Current L1 | Input Current L2 | Input Current L3
	 * Q3OC	(Output Current L1 | Output Current L2 | Output Current L3
	 * Q3LD	(Output Load Level L1 | Output Load Level L2 | Output Load Level L3
	 * Q3YV	(Output Bypass Voltage L1 | Output Bypass Voltage L2 | Output Bypass Voltage L3
	 *
	 * P10 = 3-phase input/3-phase output / P03 = 3-phase input/ 1-phase output
	 * Q3PV	(Input Voltage L1 | Input Voltage L2 | Input Voltage L3 | Input Voltage L1-L2 | Input Voltage L2-L3 | Input Voltage L1-L3
	 * Q3OV	(Output Voltage L1 | Output Voltage L2 | Output Voltage L3 | Output Voltage L1-L2 | Output Voltage L2-L3 | Output Voltage L1-L3
	 * Q3PC	(Input Current L1 | Input Current L2 | Input Current L3
	 * Q3OC	(Output Current L1 | Output Current L2 | Output Current L3
	 * Q3LD	(Output Load Level L1 | Output Load Level L2 | Output Load Level L3
	 * Q3YV	(Output Bypass Voltage L1 | Output Bypass Voltage L2 | Output Bypass Voltage L3 | Output Bypass Voltage L1-L2 | Output Bypass Voltage L2-L3 | Output Bypass Voltage L1-L3
	 *
	 */

	/*	From Q3PV	*/
	{ "input.L1-N.voltage",			0,	NULL,	"Q3PV\r",	"",	37,	'(',	"",	1,	5,	"%.1f",	QX_FLAG_SKIP,	NULL,	NULL,	NULL },
	{ "input.L2-N.voltage",			0,	NULL,	"Q3PV\r",	"",	37,	'(',	"",	7,	11,	"%.1f",	QX_FLAG_SKIP,	NULL,	NULL,	NULL },
	{ "input.L3-N.voltage",			0,	NULL,	"Q3PV\r",	"",	37,	'(',	"",	13,	17,	"%.1f",	QX_FLAG_SKIP,	NULL,	NULL,	NULL },
	{ "input.L1-L2.voltage",		0,	NULL,	"Q3PV\r",	"",	37,	'(',	"",	19,	23,	"%.1f",	QX_FLAG_SKIP,	NULL,	NULL,	NULL },
	{ "input.L2-L3.voltage",		0,	NULL,	"Q3PV\r",	"",	37,	'(',	"",	25,	29,	"%.1f",	QX_FLAG_SKIP,	NULL,	NULL,	NULL },
	{ "input.L1-L3.voltage",		0,	NULL,	"Q3PV\r",	"",	37,	'(',	"",	31,	35,	"%.1f",	QX_FLAG_SKIP,	NULL,	NULL,	NULL },
/*	{ "input.L1-L3.voltage",		0,	NULL,	"Q3PV\r",	"",	37,	'(',	"",	25,	29,	"%.1f",	QX_FLAG_SKIP,	NULL,	NULL,	NULL },	*//* P09 *//* Commented out because P09 should be two-phase input/output UPSes */
/*	{ "input.L2-L3.voltage",		0,	NULL,	"Q3PV\r",	"",	37,	'(',	"",	31,	35,	"%.1f",	QX_FLAG_SKIP,	NULL,	NULL,	NULL },	*//* P09 *//* Commented out because P09 should be two-phase input/output UPSes */

	/*	From Q3PC	*/
	{ "input.L1.current",			0,	NULL,	"Q3PC\r",	"",	13,	'(',	"",	1,	3,	"%.0f",	QX_FLAG_SKIP,	NULL,	NULL,	NULL },
	{ "input.L2.current",			0,	NULL,	"Q3PC\r",	"",	13,	'(',	"",	5,	7,	"%.0f",	QX_FLAG_SKIP,	NULL,	NULL,	NULL },
	{ "input.L3.current",			0,	NULL,	"Q3PC\r",	"",	13,	'(',	"",	9,	11,	"%.0f",	QX_FLAG_SKIP,	NULL,	NULL,	NULL },

	/*	From Q3OV	*/
	{ "output.L1-N.voltage",		0,	NULL,	"Q3OV\r",	"",	37,	'(',	"",	1,	5,	"%.1f",	QX_FLAG_SKIP,	NULL,	NULL,	NULL },
	{ "output.L2-N.voltage",		0,	NULL,	"Q3OV\r",	"",	37,	'(',	"",	7,	11,	"%.1f",	QX_FLAG_SKIP,	NULL,	NULL,	NULL },
	{ "output.L3-N.voltage",		0,	NULL,	"Q3OV\r",	"",	37,	'(',	"",	13,	17,	"%.1f",	QX_FLAG_SKIP,	NULL,	NULL,	NULL },
	{ "output.L1-L2.voltage",		0,	NULL,	"Q3OV\r",	"",	37,	'(',	"",	19,	23,	"%.1f",	QX_FLAG_SKIP,	NULL,	NULL,	NULL },
	{ "output.L2-L3.voltage",		0,	NULL,	"Q3OV\r",	"",	37,	'(',	"",	25,	29,	"%.1f",	QX_FLAG_SKIP,	NULL,	NULL,	NULL },
	{ "output.L1-L3.voltage",		0,	NULL,	"Q3OV\r",	"",	37,	'(',	"",	31,	35,	"%.1f",	QX_FLAG_SKIP,	NULL,	NULL,	NULL },
/*	{ "output.L1-L3.voltage",		0,	NULL,	"Q3OV\r",	"",	37,	'(',	"",	25,	29,	"%.1f",	QX_FLAG_SKIP,	NULL,	NULL,	NULL },	*//* P09 *//* Commented out because P09 should be two-phase input/output UPSes */
/*	{ "output.L2-L3.voltage",		0,	NULL,	"Q3OV\r",	"",	37,	'(',	"",	31,	35,	"%.1f",	QX_FLAG_SKIP,	NULL,	NULL,	NULL },	*//* P09 *//* Commented out because P09 should be two-phase input/output UPSes */

	/*	From Q3OC	*/
	{ "output.L1.current",			0,	NULL,	"Q3OC\r",	"",	13,	'(',	"",	1,	3,	"%.0f",	QX_FLAG_SKIP,	NULL,	NULL,	NULL },
	{ "output.L2.current",			0,	NULL,	"Q3OC\r",	"",	13,	'(',	"",	5,	7,	"%.0f",	QX_FLAG_SKIP,	NULL,	NULL,	NULL },
	{ "output.L3.current",			0,	NULL,	"Q3OC\r",	"",	13,	'(',	"",	9,	11,	"%.0f",	QX_FLAG_SKIP,	NULL,	NULL,	NULL },

	/*	From Q3LD	*/
	{ "output.L1.power.percent",		0,	NULL,	"Q3LD\r",	"",	13,	'(',	"",	1,	3,	"%.0f",	QX_FLAG_SKIP,	NULL,	NULL,	NULL },
	{ "output.L2.power.percent",		0,	NULL,	"Q3LD\r",	"",	13,	'(',	"",	5,	7,	"%.0f",	QX_FLAG_SKIP,	NULL,	NULL,	NULL },
	{ "output.L3.power.percent",		0,	NULL,	"Q3LD\r",	"",	13,	'(',	"",	9,	11,	"%.0f",	QX_FLAG_SKIP,	NULL,	NULL,	NULL },

	/*	From Q3YV	*/
	{ "output.bypass.L1-N.voltage",		0,	NULL,	"Q3YV\r",	"",	37,	'(',	"",	1,	5,	"%.1f",	QX_FLAG_SKIP,	NULL,	NULL,	NULL },
	{ "output.bypass.L2-N.voltage",		0,	NULL,	"Q3YV\r",	"",	37,	'(',	"",	7,	11,	"%.1f",	QX_FLAG_SKIP,	NULL,	NULL,	NULL },
	{ "output.bypass.L3-N.voltage",		0,	NULL,	"Q3YV\r",	"",	37,	'(',	"",	13,	17,	"%.1f",	QX_FLAG_SKIP,	NULL,	NULL,	NULL },
	{ "output.bypass.L1-N.voltage",		0,	NULL,	"Q3YV\r",	"",	19,	'(',	"",	1,	5,	"%.1f",	QX_FLAG_SKIP,	NULL,	NULL,	NULL },	/* P09 */
	{ "output.bypass.L2-N.voltage",		0,	NULL,	"Q3YV\r",	"",	19,	'(',	"",	7,	11,	"%.1f",	QX_FLAG_SKIP,	NULL,	NULL,	NULL },	/* P09 */
/*	{ "output.bypass.L3-N.voltage",		0,	NULL,	"Q3YV\r",	"",	19,	'(',	"",	13,	17,	"%.1f",	QX_FLAG_SKIP,	NULL,	NULL,	NULL },	*//* P09 *//* Commented out because P09 should be two-phase input/output UPSes */
	{ "output.bypass.L1-L2.voltage",	0,	NULL,	"Q3YV\r",	"",	37,	'(',	"",	19,	23,	"%.1f",	QX_FLAG_SKIP,	NULL,	NULL,	NULL },
	{ "output.bypass.L2-L3.voltage",	0,	NULL,	"Q3YV\r",	"",	37,	'(',	"",	25,	29,	"%.1f",	QX_FLAG_SKIP,	NULL,	NULL,	NULL },
	{ "output.bypass.L1-L3.voltage",	0,	NULL,	"Q3YV\r",	"",	37,	'(',	"",	31,	35,	"%.1f",	QX_FLAG_SKIP,	NULL,	NULL,	NULL },

	/* Query UPS for capability - total options available: 23; only those whom the UPS is capable of are reported as Enabled or Disabled
	 * > [QFLAG\r]
	 * < [(EpashcDbroegfl\r]
	 *    0123456789012345
	 *    0         1	* min length = ( + E + D + \r = 4
	 */

	{ "ups.start.auto",		ST_FLAG_RW,	voltronic_e_cap,	"QFLAG\r",	"",	4,	'(',	"",	1,	0,	"%s",	QX_FLAG_SEMI_STATIC | QX_FLAG_ENUM,	NULL,	NULL,	voltronic_capability },
	{ "battery.protection",		ST_FLAG_RW,	voltronic_e_cap,	"QFLAG\r",	"",	4,	'(',	"",	1,	0,	"%s",	QX_FLAG_SEMI_STATIC | QX_FLAG_ENUM,	NULL,	NULL,	voltronic_capability },
	{ "battery.energysave",		ST_FLAG_RW,	voltronic_e_cap,	"QFLAG\r",	"",	4,	'(',	"",	1,	0,	"%s",	QX_FLAG_SEMI_STATIC | QX_FLAG_ENUM,	NULL,	NULL,	voltronic_capability },
	{ "ups.start.battery",		ST_FLAG_RW,	voltronic_e_cap,	"QFLAG\r",	"",	4,	'(',	"",	1,	0,	"%s",	QX_FLAG_SEMI_STATIC | QX_FLAG_ENUM,	NULL,	NULL,	voltronic_capability },
	{ "outlet.0.switchable",	ST_FLAG_RW,	voltronic_e_cap,	"QFLAG\r",	"",	4,	'(',	"",	1,	0,	"%s",	QX_FLAG_SEMI_STATIC | QX_FLAG_ENUM,	NULL,	NULL,	voltronic_capability },
	/* Not available in NUT */
	{ "bypass_alarm",		0,	NULL,	"QFLAG\r",	"",	4,	'(',	"",	1,	0,	"%s",	QX_FLAG_SEMI_STATIC | QX_FLAG_NONUT,	NULL,	NULL,	voltronic_capability },
	{ "battery_alarm",		0,	NULL,	"QFLAG\r",	"",	4,	'(',	"",	1,	0,	"%s",	QX_FLAG_SEMI_STATIC | QX_FLAG_NONUT,	NULL,	NULL,	voltronic_capability },
	{ "bypass_when_off",		0,	NULL,	"QFLAG\r",	"",	4,	'(',	"",	1,	0,	"%s",	QX_FLAG_SEMI_STATIC | QX_FLAG_NONUT,	NULL,	NULL,	voltronic_capability },
	{ "alarm_control",		0,	NULL,	"QFLAG\r",	"",	4,	'(',	"",	1,	0,	"%s",	QX_FLAG_SEMI_STATIC | QX_FLAG_NONUT,	NULL,	NULL,	voltronic_capability },
	{ "converter_mode",		0,	NULL,	"QFLAG\r",	"",	4,	'(',	"",	1,	0,	"%s",	QX_FLAG_SEMI_STATIC | QX_FLAG_NONUT,	NULL,	NULL,	voltronic_capability },
	{ "eco_mode",			0,	NULL,	"QFLAG\r",	"",	4,	'(',	"",	1,	0,	"%s",	QX_FLAG_SEMI_STATIC | QX_FLAG_NONUT,	NULL,	NULL,	voltronic_capability },
	{ "battery_open_status_check",	0,	NULL,	"QFLAG\r",	"",	4,	'(',	"",	1,	0,	"%s",	QX_FLAG_SEMI_STATIC | QX_FLAG_NONUT,	NULL,	NULL,	voltronic_capability },
	{ "bypass_forbidding",		0,	NULL,	"QFLAG\r",	"",	4,	'(',	"",	1,	0,	"%s",	QX_FLAG_SEMI_STATIC | QX_FLAG_NONUT,	NULL,	NULL,	voltronic_capability },
	{ "site_fault_detection",	0,	NULL,	"QFLAG\r",	"",	4,	'(',	"",	1,	0,	"%s",	QX_FLAG_SEMI_STATIC | QX_FLAG_NONUT,	NULL,	NULL,	voltronic_capability },
	{ "advanced_eco_mode",		0,	NULL,	"QFLAG\r",	"",	4,	'(',	"",	1,	0,	"%s",	QX_FLAG_SEMI_STATIC | QX_FLAG_NONUT,	NULL,	NULL,	voltronic_capability },
	{ "constant_phase_angle",	0,	NULL,	"QFLAG\r",	"",	4,	'(',	"",	1,	0,	"%s",	QX_FLAG_SEMI_STATIC | QX_FLAG_NONUT,	NULL,	NULL,	voltronic_capability },
	{ "limited_runtime_on_battery",	0,	NULL,	"QFLAG\r",	"",	4,	'(',	"",	1,	0,	"%s",	QX_FLAG_SEMI_STATIC | QX_FLAG_NONUT,	NULL,	NULL,	voltronic_capability },

	/*   Enable	or	  Disable	or	Reset to safe default values	capability options
	 * > [PEX\r]		> [PDX\r]		> [PF\r]
	 * < [(ACK\r]		< [(ACK\r]		< [(ACK\r]
	 *    01234		   01234		   01234
	 *    0			   0			   0
	 */

	{ "ups.start.auto",		0,	voltronic_e_cap,	"P%sR\r",	"",	5,	'(',	"",	1,	3,	NULL,	QX_FLAG_SETVAR | QX_FLAG_ENUM | QX_FLAG_SKIP,	NULL,	NULL,	voltronic_capability_set },
	{ "battery.protection",		0,	voltronic_e_cap,	"P%sS\r",	"",	5,	'(',	"",	1,	3,	NULL,	QX_FLAG_SETVAR | QX_FLAG_ENUM | QX_FLAG_SKIP,	NULL,	NULL,	voltronic_capability_set },
	{ "battery.energysave",		0,	voltronic_e_cap,	"P%sG\r",	"",	5,	'(',	"",	1,	3,	NULL,	QX_FLAG_SETVAR | QX_FLAG_ENUM | QX_FLAG_SKIP,	NULL,	NULL,	voltronic_capability_set },
	{ "ups.start.battery",		0,	voltronic_e_cap,	"P%sC\r",	"",	5,	'(',	"",	1,	3,	NULL,	QX_FLAG_SETVAR | QX_FLAG_ENUM | QX_FLAG_SKIP,	NULL,	NULL,	voltronic_capability_set },
	{ "outlet.0.switchable",	0,	voltronic_e_cap,	"P%sJ\r",	"",	5,	'(',	"",	1,	3,	NULL,	QX_FLAG_SETVAR | QX_FLAG_ENUM | QX_FLAG_SKIP,	NULL,	NULL,	voltronic_capability_set },
	/* Not available in NUT */
	{ "reset_to_default",		0,	NULL,			"PF\r",		"",	5,	'(',	"",	1,	3,	NULL,	QX_FLAG_SETVAR | QX_FLAG_NONUT | QX_FLAG_SKIP,	NULL,	NULL,	voltronic_capability_reset },
	{ "bypass_alarm",		0,	voltronic_e_cap_nonut,	"P%sP\r",	"",	5,	'(',	"",	1,	3,	NULL,	QX_FLAG_SETVAR | QX_FLAG_ENUM | QX_FLAG_NONUT | QX_FLAG_SKIP,	NULL,	NULL,	voltronic_capability_set_nonut },
	{ "battery_alarm",		0,	voltronic_e_cap_nonut,	"P%sB\r",	"",	5,	'(',	"",	1,	3,	NULL,	QX_FLAG_SETVAR | QX_FLAG_ENUM | QX_FLAG_NONUT | QX_FLAG_SKIP,	NULL,	NULL,	voltronic_capability_set_nonut },
	{ "bypass_when_off",		0,	voltronic_e_cap_nonut,	"P%sO\r",	"",	5,	'(',	"",	1,	3,	NULL,	QX_FLAG_SETVAR | QX_FLAG_ENUM | QX_FLAG_NONUT | QX_FLAG_SKIP,	NULL,	NULL,	voltronic_capability_set_nonut },
	{ "alarm_control",		0,	voltronic_e_cap_nonut,	"P%sA\r",	"",	5,	'(',	"",	1,	3,	NULL,	QX_FLAG_SETVAR | QX_FLAG_ENUM | QX_FLAG_NONUT | QX_FLAG_SKIP,	NULL,	NULL,	voltronic_capability_set_nonut },
	{ "converter_mode",		0,	voltronic_e_cap_nonut,	"P%sV\r",	"",	5,	'(',	"",	1,	3,	NULL,	QX_FLAG_SETVAR | QX_FLAG_ENUM | QX_FLAG_NONUT | QX_FLAG_SKIP,	NULL,	NULL,	voltronic_capability_set_nonut },
	{ "eco_mode",			0,	voltronic_e_cap_nonut,	"P%sE\r",	"",	5,	'(',	"",	1,	3,	NULL,	QX_FLAG_SETVAR | QX_FLAG_ENUM | QX_FLAG_NONUT | QX_FLAG_SKIP,	NULL,	NULL,	voltronic_capability_set_nonut },
	{ "battery_open_status_check",	0,	voltronic_e_cap_nonut,	"P%sD\r",	"",	5,	'(',	"",	1,	3,	NULL,	QX_FLAG_SETVAR | QX_FLAG_ENUM | QX_FLAG_NONUT | QX_FLAG_SKIP,	NULL,	NULL,	voltronic_capability_set_nonut },
	{ "bypass_forbidding",		0,	voltronic_e_cap_nonut,	"P%sF\r",	"",	5,	'(',	"",	1,	3,	NULL,	QX_FLAG_SETVAR | QX_FLAG_ENUM | QX_FLAG_NONUT | QX_FLAG_SKIP,	NULL,	NULL,	voltronic_capability_set_nonut },
	{ "site_fault_detection",	0,	voltronic_e_cap_nonut,	"P%sL\r",	"",	5,	'(',	"",	1,	3,	NULL,	QX_FLAG_SETVAR | QX_FLAG_ENUM | QX_FLAG_NONUT | QX_FLAG_SKIP,	NULL,	NULL,	voltronic_capability_set_nonut },
	{ "advanced_eco_mode",		0,	voltronic_e_cap_nonut,	"P%sN\r",	"",	5,	'(',	"",	1,	3,	NULL,	QX_FLAG_SETVAR | QX_FLAG_ENUM | QX_FLAG_NONUT | QX_FLAG_SKIP,	NULL,	NULL,	voltronic_capability_set_nonut },
	{ "constant_phase_angle",	0,	voltronic_e_cap_nonut,	"P%sQ\r",	"",	5,	'(',	"",	1,	3,	NULL,	QX_FLAG_SETVAR | QX_FLAG_ENUM | QX_FLAG_NONUT | QX_FLAG_SKIP,	NULL,	NULL,	voltronic_capability_set_nonut },
	{ "limited_runtime_on_battery",	0,	voltronic_e_cap_nonut,	"P%sW\r",	"",	5,	'(',	"",	1,	3,	NULL,	QX_FLAG_SETVAR | QX_FLAG_ENUM | QX_FLAG_NONUT | QX_FLAG_SKIP,	NULL,	NULL,	voltronic_capability_set_nonut },

	/* Query UPS for programmable outlet (1-4) status
	 * > [QSK1\r]
	 * < [(1\r]	<- if outlet is on -> (1 , if off -> (0 ; (NAK -> outlet is not programmable
	 *    012
	 *    0
	 */

	{ "outlet.1.switchable",	0,	NULL,	"QSK1\r",	"",	3,	'(',	"",	1,	1,	"%s",	QX_FLAG_SEMI_STATIC | QX_FLAG_SKIP,	NULL,	NULL,	voltronic_outlet },
	{ "outlet.1.status",		0,	NULL,	"QSK1\r",	"",	3,	'(',	"",	1,	1,	"%s",	QX_FLAG_SEMI_STATIC | QX_FLAG_SKIP,	NULL,	NULL,	voltronic_outlet },
	{ "outlet.2.switchable",	0,	NULL,	"QSK2\r",	"",	3,	'(',	"",	1,	1,	"%s",	QX_FLAG_SEMI_STATIC | QX_FLAG_SKIP,	NULL,	NULL,	voltronic_outlet },
	{ "outlet.2.status",		0,	NULL,	"QSK2\r",	"",	3,	'(',	"",	1,	1,	"%s",	QX_FLAG_SEMI_STATIC | QX_FLAG_SKIP,	NULL,	NULL,	voltronic_outlet },
	{ "outlet.3.switchable",	0,	NULL,	"QSK3\r",	"",	3,	'(',	"",	1,	1,	"%s",	QX_FLAG_SEMI_STATIC | QX_FLAG_SKIP,	NULL,	NULL,	voltronic_outlet },
	{ "outlet.3.status",		0,	NULL,	"QSK3\r",	"",	3,	'(',	"",	1,	1,	"%s",	QX_FLAG_SEMI_STATIC | QX_FLAG_SKIP,	NULL,	NULL,	voltronic_outlet },
	{ "outlet.4.switchable",	0,	NULL,	"QSK4\r",	"",	3,	'(',	"",	1,	1,	"%s",	QX_FLAG_SEMI_STATIC | QX_FLAG_SKIP,	NULL,	NULL,	voltronic_outlet },
	{ "outlet.4.status",		0,	NULL,	"QSK4\r",	"",	3,	'(',	"",	1,	1,	"%s",	QX_FLAG_SEMI_STATIC | QX_FLAG_SKIP,	NULL,	NULL,	voltronic_outlet },

	/* Query UPS for programmable outlet n (1-4) delay time before it shuts down the load when on battery mode
	 * > [QSKT1\r]
	 * < [(008\r]	<- if delay time is set (by PSK[1-4]n) it'll report n (minutes) otherwise it'll report (NAK (also if outlet is not programmable)
	 *    01234
	 *    0
	 */

	{ "outlet.1.delay.shutdown",	ST_FLAG_RW,	voltronic_r_outlet_delay,	"QSKT1\r",	"",	5,	'(',	"",	1,	3,	"%.0f",	QX_FLAG_SEMI_STATIC | QX_FLAG_RANGE | QX_FLAG_SKIP,	NULL,	NULL,	voltronic_outlet_delay },
	{ "outlet.2.delay.shutdown",	ST_FLAG_RW,	voltronic_r_outlet_delay,	"QSKT2\r",	"",	5,	'(',	"",	1,	3,	"%.0f",	QX_FLAG_SEMI_STATIC | QX_FLAG_RANGE | QX_FLAG_SKIP,	NULL,	NULL,	voltronic_outlet_delay },
	{ "outlet.3.delay.shutdown",	ST_FLAG_RW,	voltronic_r_outlet_delay,	"QSKT3\r",	"",	5,	'(',	"",	1,	3,	"%.0f",	QX_FLAG_SEMI_STATIC | QX_FLAG_RANGE | QX_FLAG_SKIP,	NULL,	NULL,	voltronic_outlet_delay },
	{ "outlet.4.delay.shutdown",	ST_FLAG_RW,	voltronic_r_outlet_delay,	"QSKT4\r",	"",	5,	'(',	"",	1,	3,	"%.0f",	QX_FLAG_SEMI_STATIC | QX_FLAG_RANGE | QX_FLAG_SKIP,	NULL,	NULL,	voltronic_outlet_delay },

	/* Set delay time for programmable outlets
	 * > [PSK1nnn\r]	n = 0..9
	 * < [(ACK\r]
	 *    01234
	 *    0
	 */

	{ "outlet.1.delay.shutdown",	0,	voltronic_r_outlet_delay,	"PSK1%03d\r",	"",	5,	'(',	"",	1,	3,	NULL,	QX_FLAG_SETVAR | QX_FLAG_RANGE | QX_FLAG_SKIP,	NULL,	NULL,	voltronic_outlet_delay_set },
	{ "outlet.2.delay.shutdown",	0,	voltronic_r_outlet_delay,	"PSK2%03d\r",	"",	5,	'(',	"",	1,	3,	NULL,	QX_FLAG_SETVAR | QX_FLAG_RANGE | QX_FLAG_SKIP,	NULL,	NULL,	voltronic_outlet_delay_set },
	{ "outlet.3.delay.shutdown",	0,	voltronic_r_outlet_delay,	"PSK3%03d\r",	"",	5,	'(',	"",	1,	3,	NULL,	QX_FLAG_SETVAR | QX_FLAG_RANGE | QX_FLAG_SKIP,	NULL,	NULL,	voltronic_outlet_delay_set },
	{ "outlet.4.delay.shutdown",	0,	voltronic_r_outlet_delay,	"PSK4%03d\r",	"",	5,	'(',	"",	1,	3,	NULL,	QX_FLAG_SETVAR | QX_FLAG_RANGE | QX_FLAG_SKIP,	NULL,	NULL,	voltronic_outlet_delay_set },

	/* Query UPS for ECO Mode voltage limits
	 * > [QHE\r]
	 * < [(242 218\r]
	 *    012345678
	 *    0
	 */

	{ "input.transfer.high",	ST_FLAG_RW,	voltronic_r_eco_volt_max,	"QHE\r",	"",	9,	'(',	"",	1,	3,	"%.0f",	QX_FLAG_SEMI_STATIC | QX_FLAG_RANGE | QX_FLAG_SKIP,	NULL,	NULL,	voltronic_eco_volt },
	{ "input.transfer.low",		ST_FLAG_RW,	voltronic_r_eco_volt_min,	"QHE\r",	"",	9,	'(',	"",	5,	7,	"%.0f",	QX_FLAG_SEMI_STATIC | QX_FLAG_RANGE | QX_FLAG_SKIP,	NULL,	NULL,	voltronic_eco_volt },
	{ "input.transfer.low.min",	0,		NULL,				"QHE\r",	"",	9,	'(',	"",	5,	7,	"%.0f",	QX_FLAG_SEMI_STATIC | QX_FLAG_SKIP,			NULL,	NULL,	voltronic_eco_volt_range },
	{ "input.transfer.low.max",	0,		NULL,				"QHE\r",	"",	9,	'(',	"",	5,	7,	"%.0f",	QX_FLAG_SEMI_STATIC | QX_FLAG_SKIP,			NULL,	NULL,	voltronic_eco_volt_range },
	{ "input.transfer.high.min",	0,		NULL,				"QHE\r",	"",	9,	'(',	"",	1,	3,	"%.0f",	QX_FLAG_SEMI_STATIC | QX_FLAG_SKIP,			NULL,	NULL,	voltronic_eco_volt_range },
	{ "input.transfer.high.max",	0,		NULL,				"QHE\r",	"",	9,	'(',	"",	1,	3,	"%.0f",	QX_FLAG_SEMI_STATIC | QX_FLAG_SKIP,			NULL,	NULL,	voltronic_eco_volt_range },

	/* Set ECO Mode voltage limits
	 * > [HEHnnn\r]		> [HELnnn\r]		n = 0..9
	 * < [(ACK\r]		< [(ACK\r]
	 *    01234		   01234
	 *    0			   0
	 */

	{ "input.transfer.high",	0,	voltronic_r_eco_volt_max,	"HEH%03.0f\r",	"",	5,	'(',	"",	1,	3,	NULL,	QX_FLAG_SETVAR | QX_FLAG_RANGE | QX_FLAG_SKIP,	NULL,	NULL,	voltronic_process_setvar },
	{ "input.transfer.low",		0,	voltronic_r_eco_volt_min,	"HEL%03.0f\r",	"",	5,	'(',	"",	1,	3,	NULL,	QX_FLAG_SETVAR | QX_FLAG_RANGE | QX_FLAG_SKIP,	NULL,	NULL,	voltronic_process_setvar },

	/* Query UPS for ECO Mode frequency limits
	 * > [QFRE\r]
	 * < [(53.0 47.0\r]
	 *    01234567890
	 *    0         1
	 */

	{ "input.frequency.high",	ST_FLAG_RW,	voltronic_r_eco_freq_max,	"QFRE\r",	"",	11,	'(',	"",	1,	4,	"%.1f",	QX_FLAG_SEMI_STATIC | QX_FLAG_RANGE | QX_FLAG_SKIP,	NULL,	NULL,	voltronic_eco_freq },
	{ "input.frequency.low",	ST_FLAG_RW,	voltronic_r_eco_freq_min,	"QFRE\r",	"",	11,	'(',	"",	6,	9,	"%.1f",	QX_FLAG_SEMI_STATIC | QX_FLAG_RANGE | QX_FLAG_SKIP,	NULL,	NULL,	voltronic_eco_freq },

	/* Set ECO Mode frequency limits
	 * > [FREHnn.n\r]	> [FRELnn.n\r]		n = 0..9
	 * < [(ACK\r]		< [(ACK\r]
	 *    01234		   01234
	 *    0			   0
	 */

	{ "input.frequency.high",	0,	voltronic_r_eco_freq_max,	"FREH%04.1f\r",	"",	5,	'(',	"",	1,	3,	NULL,	QX_FLAG_SETVAR | QX_FLAG_RANGE | QX_FLAG_SKIP,	NULL,	NULL,	voltronic_process_setvar },
	{ "input.frequency.low",	0,	voltronic_r_eco_freq_min,	"FREL%04.1f\r",	"",	5,	'(',	"",	1,	3,	NULL,	QX_FLAG_SETVAR | QX_FLAG_RANGE | QX_FLAG_SKIP,	NULL,	NULL,	voltronic_process_setvar },

	/* Query UPS for Bypass Mode voltage limits
	 * > [QBYV\r]
	 * < [(264 170\r]
	 *    012345678
	 *    0
	 */

	{ "max_bypass_volt",	ST_FLAG_RW,	voltronic_r_bypass_volt_max,	"QBYV\r",	"",	9,	'(',	"",	1,	3,	"%.0f",	QX_FLAG_SEMI_STATIC | QX_FLAG_RANGE | QX_FLAG_NONUT | QX_FLAG_SKIP,	NULL,	NULL,	voltronic_bypass },
	{ "min_bypass_volt",	ST_FLAG_RW,	voltronic_r_bypass_volt_min,	"QBYV\r",	"",	9,	'(',	"",	5,	7,	"%.0f",	QX_FLAG_SEMI_STATIC | QX_FLAG_RANGE | QX_FLAG_NONUT | QX_FLAG_SKIP,	NULL,	NULL,	voltronic_bypass },

	/* Set Bypass Mode voltage limits
	 * > [PHVnnn\r]		> [PLVnnn\r]		n = 0..9
	 * < [(ACK\r]		< [(ACK\r]
	 *    01234		   01234
	 *    0			   0
	 */

	{ "max_bypass_volt",	0,	voltronic_r_bypass_volt_max,	"PHV%03.0f\r",	"",	5,	'(',	"",	1,	3,	NULL,	QX_FLAG_SETVAR | QX_FLAG_RANGE | QX_FLAG_NONUT | QX_FLAG_SKIP,	NULL,	NULL,	voltronic_process_setvar },
	{ "min_bypass_volt",	0,	voltronic_r_bypass_volt_min,	"PLV%03.0f\r",	"",	5,	'(',	"",	1,	3,	NULL,	QX_FLAG_SETVAR | QX_FLAG_RANGE | QX_FLAG_NONUT | QX_FLAG_SKIP,	NULL,	NULL,	voltronic_process_setvar },

	/* Query UPS for Bypass Mode frequency limits
	 * > [QBYF\r]
	 * < [(53.0 47.0\r]
	 *    01234567890
	 *    0         1
	 */

	{ "max_bypass_freq",	ST_FLAG_RW,	voltronic_r_bypass_freq_max,	"QBYF\r",	"",	11,	'(',	"",	1,	4,	"%.1f",	QX_FLAG_SEMI_STATIC | QX_FLAG_RANGE | QX_FLAG_NONUT | QX_FLAG_SKIP,	NULL,	NULL,	voltronic_bypass },
	{ "min_bypass_freq",	ST_FLAG_RW,	voltronic_r_bypass_freq_min,	"QBYF\r",	"",	11,	'(',	"",	6,	9,	"%.1f",	QX_FLAG_SEMI_STATIC | QX_FLAG_RANGE | QX_FLAG_NONUT | QX_FLAG_SKIP,	NULL,	NULL,	voltronic_bypass },

	/* Set Bypass Mode frequency limits
	 * > [PGFnn.n\r]	> [PSFnn.n\r]		n = 0..9
	 * < [(ACK\r]		< [(ACK\r]
	 *    01234		   01234
	 *    0			   0
	 */

	{ "max_bypass_freq",	0,	voltronic_r_bypass_freq_max,	"PGF%04.1f\r",	"",	5,	'(',	"",	1,	3,	NULL,	QX_FLAG_SETVAR | QX_FLAG_RANGE | QX_FLAG_NONUT | QX_FLAG_SKIP,	NULL,	NULL,	voltronic_process_setvar },
	{ "min_bypass_freq",	0,	voltronic_r_bypass_freq_min,	"PSF%04.1f\r",	"",	5,	'(',	"",	1,	3,	NULL,	QX_FLAG_SETVAR | QX_FLAG_RANGE | QX_FLAG_NONUT | QX_FLAG_SKIP,	NULL,	NULL,	voltronic_process_setvar },

	/* Set number of batteries that make a pack to n (integer, 1-9). NOTE: changing the number of batteries will change the UPS's estimation on battery charge/runtime
	 * > [BATNn\r]
	 * < [(ACK\r]
	 *    01234
	 *    0
	 */

	{ "battery_number",	0,	voltronic_r_batt_numb,	"BATN%1.0f\r",	"",	5,	'(',	"",	1,	3,	NULL,	QX_FLAG_SETVAR | QX_FLAG_RANGE | QX_FLAG_NONUT,	NULL,	NULL,	voltronic_process_setvar },

	/* Set number of battery packs in parallel to n (integer, 01-99). NOTE: changing the number of battery packs will change the UPS's estimation on battery charge/runtime
	 * > [BATGNn\r]
	 * < [(ACK\r]
	 *    01234
	 *    0
	 */

	{ "battery.packs",	0,	voltronic_r_batt_packs,	"BATGN%02.0f\r",	"",	5,	'(',	"",	1,	3,	NULL,	QX_FLAG_SETVAR | QX_FLAG_RANGE,	NULL,	NULL,	voltronic_process_setvar },

	/* Query UPS for battery type (Only P31)
	 * > [QBT\r]
	 * < [(01\r]	<- 00="Li", 01="Flooded" or 02="AGM"
	 *    0123
	 *    0
	 */

	{ "battery.type",	ST_FLAG_RW,	voltronic_e_batt_type,	"QBT\r",	"",	4,	'(',	"",	1,	2,	"%s",	QX_FLAG_SEMI_STATIC | QX_FLAG_ENUM | QX_FLAG_SKIP,	NULL,	NULL,	voltronic_p31b },

	/* Set battery type (Only P31)
	 * > [PBTnn\r]		nn = 00/01/02
	 * < [(ACK\r]
	 *    01234
	 *    0
	 */

	{ "battery.type",	0,	voltronic_e_batt_type,	"PBT%02.0f\r",	"",	5,	'(',	"",	1,	3,	NULL,	QX_FLAG_SETVAR | QX_FLAG_ENUM | QX_FLAG_SKIP,	NULL,	NULL,	voltronic_p31b_set },

	/* Query UPS for device grid working range (Only P31)
	 * > [QGR\r]
	 * < [(01\r]	<- 00=Appliance, 01=UPS
	 *    0123
	 *    0
	 */

	{ "work_range_type",	ST_FLAG_RW,	voltronic_e_work_range,	"QGR\r",	"",	4,	'(',	"",	1,	2,	"%s",	QX_FLAG_SEMI_STATIC | QX_FLAG_ENUM | QX_FLAG_NONUT | QX_FLAG_SKIP,	NULL,	NULL,	voltronic_p31g },

	/* Set device grid working range type (Only P31)
	 * > [PBTnn\r]		nn = 00/01
	 * < [(ACK\r]
	 *    01234
	 *    0
	 */

	{ "work_range_type",	0,	voltronic_e_work_range,	"PGR%02.0f\r",	"",	5,	'(',	"",	1,	3,	NULL,	QX_FLAG_SETVAR | QX_FLAG_ENUM | QX_FLAG_NONUT | QX_FLAG_SKIP,	NULL,	NULL,	voltronic_p31g_set },

	/* Query UPS for battery low voltage
	 * > [RE0\r]
	 * < [#20\r]
	 *    012
	 *    0
	 */

	{ "battery.voltage.low",	ST_FLAG_RW,	voltronic_r_batt_low,	"RE0\r",	"",	3,	'#',	"",	1,	2,	"%.1f",	QX_FLAG_SEMI_STATIC | QX_FLAG_RANGE,	NULL,	NULL,	NULL },

	/* Set voltage for battery low to n (integer, 20..24/20..28). NOTE: changing the battery low voltage will change the UPS's estimation on battery charge/runtime
	 * > [W0En\r]
	 * < [(ACK\r]
	 *    01234
	 *    0
	 */

	{ "battery.voltage.low",	0,	voltronic_r_batt_low,	"W0E%02.0f\r",	"",	5,	'(',	"",	1,	3,	NULL,	QX_FLAG_SETVAR | QX_FLAG_RANGE,	NULL,	NULL,	voltronic_process_setvar },

	/* Query UPS for Phase Angle
	 * > [QPD\r]
	 * < [(000 120\r]	<- Input Phase Angle - Output Phase Angle
	 *    012345678
	 *    0
	 */

	{ "input_phase_angle",		0,		NULL,			"QPD\r",	"",	9,	'(',	"",	1,	3,	"%03d",	QX_FLAG_SEMI_STATIC | QX_FLAG_NONUT,			NULL,	NULL,	voltronic_phase },
	{ "output_phase_angle",		ST_FLAG_RW,	voltronic_e_phase,	"QPD\r",	"",	9,	'(',	"",	5,	7,	"%03d",	QX_FLAG_SEMI_STATIC | QX_FLAG_ENUM | QX_FLAG_NONUT,	NULL,	NULL,	voltronic_phase },

	/* Set output phase angle
	 * > [PPDn\r]		n = (000, 120, 180 or 240)
	 * < [(ACK\r]
	 *    01234
	 *    0
	 */

	{ "output_phase_angle",		0,	voltronic_e_phase,	"PPD%03.0f\r",	"",	5,	'(',	"",	1,	3,	NULL,	QX_FLAG_SETVAR | QX_FLAG_ENUM | QX_FLAG_NONUT | QX_FLAG_SKIP,	NULL,	NULL,	voltronic_phase_set },

	/* Query UPS for master/slave for a system of UPSes in parallel
	 * > [QPAR\r]
	 * < [(001\r]	<- 001 for master UPS, 002 and 003 for slave UPSes
	 *    01234
	 *    0
	 */

	{ "voltronic_parallel",		0,	NULL,	"QPAR\r",	"",	5,	'(',	"",	1,	3,	"%s",	QX_FLAG_STATIC | QX_FLAG_NONUT,	NULL,	NULL,	voltronic_parallel },

	/* Query UPS for ??
	 * > [QBDR\r]
	 * < [(1234\r]	<- unknown reply - My UPS (NAK at me
	 *    012345
	 *    0
	 */

	{ "unknown.7",		0,	NULL,	"QBDR\r",	"",	5,	'(',	"",	1,	0,	"%s",	QX_FLAG_STATIC | QX_FLAG_NONUT | QX_FLAG_SKIP,	NULL,	NULL,	NULL },

	/* Instant commands */
	{ "load.off",			0,	NULL,	"SOFF\r",	"",	5,	'(',	"",	1,	3,	NULL,	QX_FLAG_CMD,	NULL,	NULL,	NULL },
	{ "load.on",			0,	NULL,	"SON\r",	"",	5,	'(',	"",	1,	3,	NULL,	QX_FLAG_CMD,	NULL,	NULL,	NULL },

	{ "shutdown.return",		0,	NULL,	"S%s\r",	"",	5,	'(',	"",	1,	3,	NULL,	QX_FLAG_CMD,	NULL,	NULL,	voltronic_process_command },
	{ "shutdown.stayoff",		0,	NULL,	"S%sR0000\r",	"",	5,	'(',	"",	1,	3,	NULL,	QX_FLAG_CMD,	NULL,	NULL,	voltronic_process_command },
	{ "shutdown.stop",		0,	NULL,	"CS\r",		"",	5,	'(',	"",	1,	3,	NULL,	QX_FLAG_CMD,	NULL,	NULL,	NULL },

	{ "test.battery.start",		0,	NULL,	"T%s\r",	"",	5,	'(',	"",	1,	3,	NULL,	QX_FLAG_CMD,	NULL,	NULL,	voltronic_process_command },
	{ "test.battery.start.deep",	0,	NULL,	"TL\r",		"",	5,	'(',	"",	1,	3,	NULL,	QX_FLAG_CMD,	NULL,	NULL,	NULL },
	{ "test.battery.start.quick",	0,	NULL,	"T\r",		"",	5,	'(',	"",	1,	3,	NULL,	QX_FLAG_CMD,	NULL,	NULL,	NULL },
	{ "test.battery.stop",		0,	NULL,	"CT\r",		"",	5,	'(',	"",	1,	3,	NULL,	QX_FLAG_CMD,	NULL,	NULL,	NULL },

	{ "beeper.toggle",		0,	NULL,	"BZ%s\r",	"",	5,	'(',	"",	1,	3,	NULL,	QX_FLAG_CMD,	NULL,	NULL,	voltronic_process_command },
	/* Enable/disable beeper: unskipped if the UPS can control alarm (capability) */
	{ "beeper.enable",		0,	NULL,	"PEA\r",	"",	5,	'(',	"",	1,	3,	NULL,	QX_FLAG_CMD | QX_FLAG_SKIP,	NULL,	NULL,	NULL },
	{ "beeper.disable",		0,	NULL,	"PDA\r",	"",	5,	'(',	"",	1,	3,	NULL,	QX_FLAG_CMD | QX_FLAG_SKIP,	NULL,	NULL,	NULL },

	/* Outlet control: unskipped if the outlets are manageable */
	{ "outlet.1.load.off",		0,	NULL,	"SKOFF1\r",	"",	5,	'(',	"",	1,	3,	NULL,	QX_FLAG_CMD | QX_FLAG_SKIP,	NULL,	NULL,	NULL },
	{ "outlet.1.load.on",		0,	NULL,	"SKON1\r",	"",	5,	'(',	"",	1,	3,	NULL,	QX_FLAG_CMD | QX_FLAG_SKIP,	NULL,	NULL,	NULL },
	{ "outlet.2.load.off",		0,	NULL,	"SKOFF2\r",	"",	5,	'(',	"",	1,	3,	NULL,	QX_FLAG_CMD | QX_FLAG_SKIP,	NULL,	NULL,	NULL },
	{ "outlet.2.load.on",		0,	NULL,	"SKON2\r",	"",	5,	'(',	"",	1,	3,	NULL,	QX_FLAG_CMD | QX_FLAG_SKIP,	NULL,	NULL,	NULL },
	{ "outlet.3.load.off",		0,	NULL,	"SKOFF3\r",	"",	5,	'(',	"",	1,	3,	NULL,	QX_FLAG_CMD | QX_FLAG_SKIP,	NULL,	NULL,	NULL },
	{ "outlet.3.load.on",		0,	NULL,	"SKON3\r",	"",	5,	'(',	"",	1,	3,	NULL,	QX_FLAG_CMD | QX_FLAG_SKIP,	NULL,	NULL,	NULL },
	{ "outlet.4.load.off",		0,	NULL,	"SKOFF4\r",	"",	5,	'(',	"",	1,	3,	NULL,	QX_FLAG_CMD | QX_FLAG_SKIP,	NULL,	NULL,	NULL },
	{ "outlet.4.load.on",		0,	NULL,	"SKON4\r",	"",	5,	'(',	"",	1,	3,	NULL,	QX_FLAG_CMD | QX_FLAG_SKIP,	NULL,	NULL,	NULL },

	/* Bypass: unskipped if the UPS is capable of ECO Mode */
	{ "bypass.start",		0,	NULL,	"PEE\r",	"",	5,	'(',	"",	1,	3,	NULL,	QX_FLAG_CMD | QX_FLAG_SKIP,	NULL,	NULL,	NULL },
	{ "bypass.stop",		0,	NULL,	"PDE\r",	"",	5,	'(',	"",	1,	3,	NULL,	QX_FLAG_CMD | QX_FLAG_SKIP,	NULL,	NULL,	NULL },

	/* Server-side settable vars */
	{ "ups.delay.start",		ST_FLAG_RW,	voltronic_r_ondelay,	NULL,		"",	0,	0,	"",	0,	0,	DEFAULT_ONDELAY,	QX_FLAG_ABSENT | QX_FLAG_SETVAR | QX_FLAG_RANGE,	NULL,	NULL,	voltronic_process_setvar },
	{ "ups.delay.shutdown",		ST_FLAG_RW,	voltronic_r_offdelay,	NULL,		"",	0,	0,	"",	0,	0,	DEFAULT_OFFDELAY,	QX_FLAG_ABSENT | QX_FLAG_SETVAR | QX_FLAG_RANGE,	NULL,	NULL,	voltronic_process_setvar },

	/* End of structure. */
	{ NULL,		0,	NULL,	NULL,	"",	0,	0,	"",	0,	0,	NULL,	0,	NULL,	NULL,	NULL }
};


/* == Testing table == */
#ifdef TESTING
static testing_t	voltronic_testing[] = {
	{ "QGS\r",	"(234.9 50.0 229.8 50.0 000.0 00A 369.1 ---.- 026.5 ---.- 018.8 100000000001\r",	-1 },
	{ "QPI\r",	"(PI01\r",	-1 },
	{ "QRI\r",	"(230.0 004 024.0 50.0\r",	-1 },
	{ "QMF\r",	"(#####VOLTRONIC\r",	-1 },
	{ "I\r",	"#-------------   ------     VT12046Q  \r",	-1 },
	{ "F\r",	"#220.0 000 024.0 50.0\r",	-1 },
	{ "QMD\r",	"(#######OLHVT1K0 ###1000 80 2/2 230 230 02 12.0\r",	-1 },
	{ "QFS\r",	"(14 212.1 50.0 005.6 49.9 006 010.6 343.8 ---.- 026.2 021.8 01101100\r",	-1 },
	{ "QMOD\r",	"(S\r",	-1 },
	{ "QVFW\r",	"(VERFW:00322.02\r",	-1 },
	{ "QID\r",	"(685653211455\r",	-1 },
	{ "QBV\r",	"(026.5 02 01 068 255\r",	-1 },
	{ "QFLAG\r",	"(EpashcjDbroegfl\r",	-1 },
	{ "QWS\r",	"(0000000000000000000000000000000000000000000000000000000001000001\r",	-1 },
	{ "QHE\r",	"(242 218\r",	-1 },
	{ "QBYV\r",	"(264 170\r",	-1 },
	{ "QBYF\r",	"(53.0 47.0\r",	-1 },
	{ "QSK1\r",	"(1\r",	-1 },
	{ "QSK2\r",	"(0\r",	-1 },
	{ "QSK3\r",	"(1\r",	-1 },
	{ "QSK4\r",	"(NAK\r",	-1 },
	{ "QSKT1\r",	"(008\r",	-1 },
	{ "QSKT2\r",	"(012\r",	-1 },
	{ "QSKT3\r",	"(NAK\r",	-1 },
	{ "QSKT4\r",	"(007\r",	-1 },
	{ "RE0\r",	"#20\r",	-1 },
	{ "W0E24\r",	"(ACK\r",	-1 },
	{ "PF\r",	"(ACK\r",	-1 },
	{ "PEA\r",	"(ACK\r",	-1 },
	{ "PDR\r",	"(NAK\r",	-1 },
	{ "HEH250\r",	"(ACK\r",	-1 },
	{ "HEL210\r",	"(ACK\r",	-1 },
	{ "PHV260\r",	"(NAK\r",	-1 },
	{ "PLV190\r",	"(ACK\r",	-1 },
	{ "PGF51.0\r",	"(NAK\r",	-1 },
	{ "PSF47.5\r",	"(ACK\r",	-1 },
	{ "BATN2\r",	"(ACK\r",	-1 },
	{ "BATGN04\r",	"(ACK\r",	-1 },
	{ "QBT\r",	"(01\r",	-1 },
	{ "PBT02\r",	"(ACK\r",	-1 },
	{ "QGR\r",	"(00\r",	-1 },
	{ "PGR01\r",	"(ACK\r",	-1 },
	{ "PSK1008\r",	"(ACK\r",	-1 },
	{ "PSK3987\r",	"(ACK\r",	-1 },
	{ "PSK2009\r",	"(ACK\r",	-1 },
	{ "PSK4012\r",	"(ACK\r",	-1 },
	{ "Q3PV\r",	"(123.4 456.4 789.4 012.4 323.4 223.4\r",	-1 },
	{ "Q3OV\r",	"(253.4 163.4 023.4 143.4 103.4 523.4\r",	-1 },
	{ "Q3OC\r",	"(109 069 023\r",	-1 },
	{ "Q3LD\r",	"(005 033 089\r",	-1 },
	{ "Q3YV\r",	"(303.4 245.4 126.4 222.4 293.4 321.4\r",	-1 },
	{ "Q3PC\r",	"(002 023 051\r",	-1 },
	{ "SOFF\r",	"(NAK\r",	-1 },
	{ "SON\r",	"(ACK\r",	-1 },
	{ "T\r",	"(NAK\r",	-1 },
	{ "TL\r",	"(ACK\r",	-1 },
	{ "CS\r",	"(ACK\r",	-1 },
	{ "CT\r",	"(NAK\r",	-1 },
	{ "BZOFF\r",	"(ACK\r",	-1 },
	{ "BZON\r",	"(ACK\r",	-1 },
	{ "S.3R0002\r",	"(ACK\r",	-1 },
	{ "S02R0024\r",	"(NAK\r",	-1 },
	{ "S.5\r",	"(ACK\r",	-1 },
	{ "T.3\r",	"(ACK\r",	-1 },
	{ "T02\r",	"(NAK\r",	-1 },
	{ "SKON1\r",	"(ACK\r",	-1 },
	{ "SKOFF1\r",	"(NAK\r",	-1 },
	{ "SKON2\r",	"(ACK\r",	-1 },
	{ "SKOFF2\r",	"(ACK\r",	-1 },
	{ "SKON3\r",	"(NAK\r",	-1 },
	{ "SKOFF3\r",	"(ACK\r",	-1 },
	{ "SKON4\r",	"(NAK\r",	-1 },
	{ "SKOFF4\r",	"(NAK\r",	-1 },
	{ "QPAR\r",	"(003\r",	-1 },
	{ "QPD\r",	"(000 240\r",	-1 },
	{ "PPD120\r",	"(ACK\r",	-1 },
	{ "QLDL\r",	"(005 080\r",	-1 },
	{ "QBDR\r",	"(1234\r",	-1 },
	{ "QFRE\r",	"(50.0 00.0\r",	-1 },
	{ "FREH54.0\r",	"(ACK\r",	-1 },
	{ "FREL47.0\r",	"(ACK\r",	-1 },
	{ "PEP\r",	"(ACK\r",	-1 },
	{ "PDP\r",	"(ACK\r",	-1 },
	{ "PEB\r",	"(ACK\r",	-1 },
	{ "PDB\r",	"(ACK\r",	-1 },
	{ "PER\r",	"(NAK\r",	-1 },
	{ "PDR\r",	"(NAK\r",	-1 },
	{ "PEO\r",	"(ACK\r",	-1 },
	{ "PDO\r",	"(ACK\r",	-1 },
	{ "PEA\r",	"(ACK\r",	-1 },
	{ "PDA\r",	"(ACK\r",	-1 },
	{ "PES\r",	"(ACK\r",	-1 },
	{ "PDS\r",	"(ACK\r",	-1 },
	{ "PEV\r",	"(ACK\r",	-1 },
	{ "PDV\r",	"(ACK\r",	-1 },
	{ "PEE\r",	"(ACK\r",	-1 },
	{ "PDE\r",	"(ACK\r",	-1 },
	{ "PEG\r",	"(ACK\r",	-1 },
	{ "PDG\r",	"(NAK\r",	-1 },
	{ "PED\r",	"(ACK\r",	-1 },
	{ "PDD\r",	"(ACK\r",	-1 },
	{ "PEC\r",	"(ACK\r",	-1 },
	{ "PDC\r",	"(NAK\r",	-1 },
	{ "PEF\r",	"(NAK\r",	-1 },
	{ "PDF\r",	"(ACK\r",	-1 },
	{ "PEJ\r",	"(NAK\r",	-1 },
	{ "PDJ\r",	"(ACK\r",	-1 },
	{ "PEL\r",	"(ACK\r",	-1 },
	{ "PDL\r",	"(ACK\r",	-1 },
	{ "PEN\r",	"(ACK\r",	-1 },
	{ "PDN\r",	"(ACK\r",	-1 },
	{ "PEQ\r",	"(ACK\r",	-1 },
	{ "PDQ\r",	"(ACK\r",	-1 },
	{ "PEW\r",	"(NAK\r",	-1 },
	{ "PDW\r",	"(ACK\r",	-1 },
	{ NULL }
};
#endif	/* TESTING */


/* == Support functions == */

/* This function allows the subdriver to "claim" a device: return 1 if the device is supported by this subdriver, else 0. */
static int	voltronic_claim(void)
{
	int	query_result;

	/* We need at least QGS and QPI to run this subdriver */

	item_t	*item = find_nut_info("input.voltage", 0, 0);

	/* Don't know what happened - should have looked up in the mapping table here! */
	if (!item) {
		upsdebug_with_errno(4, "%s: did not find 'input.voltage' in mapping table", __func__);
		return 0;
	}

	/* No reply/Unable to get value */
	if ((query_result = qx_process(item, NULL))) {
		upsdebug_with_errno(4, "%s: failed (%d) to get 'input.voltage'", __func__, query_result);

		if (errno == ETIMEDOUT) {
			upsdebugx(2, "%s: Sometimes the device is laggy, and we could have posted many queries and the buffer is full of replies to them (or it is still producing the answers); try to sleep, flush it and ask again", __func__);
			usleep(5000000);	/* arbitrary 5s delay for the device to maybe produce answers to earlier voltage requests */
			upsdebugx(2, "%s: Retry the query now, buffers will be flushed then", __func__);
			query_result = qx_process(item, NULL);
		}

		if (query_result) {
			/* Not a known timeout/zero-read initially, or still a bad response */
			return 0;
		}
	}

	/* Unable to process value */
	if ((query_result = ups_infoval_set(item)) != 1) {
		upsdebug_with_errno(4, "%s: failed (%d) to set infoval for 'input.voltage'", __func__, query_result);
		return 0;
	}

	/* UPS Protocol */
	item = find_nut_info("ups.firmware.aux", 0, 0);

	/* Don't know what happened */
	if (!item) {
		upsdebug_with_errno(4, "%s: did not find 'ups.firmware.aux' in mapping table", __func__);
		dstate_delinfo("input.voltage");
		return 0;
	}

	/* No reply/Unable to get value */
	if ((query_result = qx_process(item, NULL))) {
		upsdebug_with_errno(4, "%s: failed (%d) to get 'ups.firmware.aux'", __func__, query_result);
		dstate_delinfo("input.voltage");
		return 0;
	}

	/* Unable to process value/Protocol out of range */
	if ((query_result = ups_infoval_set(item)) != 1) {
		upsdebug_with_errno(4, "%s: failed (%d) to set infoval for 'ups.firmware.aux'", __func__, query_result);
		dstate_delinfo("input.voltage");
		return 0;
	}

	return 1;

}

/* Subdriver-specific flags/vars */
static void	voltronic_makevartable(void)
{
	/* Capability vars */
	addvar(VAR_FLAG, "reset_to_default", "Reset capability options and their limits to safe default values");
	addvar(VAR_VALUE, "bypass_alarm", "Alarm (BEEP!) at Bypass Mode [enabled/disabled]");
	addvar(VAR_VALUE, "battery_alarm", "Alarm (BEEP!) at Battery Mode [enabled/disabled]");
	addvar(VAR_VALUE, "bypass_when_off", "Bypass when the UPS is Off [enabled/disabled]");
	addvar(VAR_VALUE, "alarm_control", "Alarm (BEEP!) Control [enabled/disabled]");
	addvar(VAR_VALUE, "converter_mode", "Converter Mode [enabled/disabled]");
	addvar(VAR_VALUE, "eco_mode", "ECO Mode [enabled/disabled]");
	addvar(VAR_VALUE, "battery_open_status_check", "Battery Open Status Check [enabled/disabled]");
	addvar(VAR_VALUE, "bypass_forbidding", "Bypass not allowed (Bypass Forbidding) [enabled/disabled]");
	addvar(VAR_VALUE, "site_fault_detection", "Site fault detection [enabled/disabled]");
	addvar(VAR_VALUE, "advanced_eco_mode", "Advanced ECO Mode [enabled/disabled]");
	addvar(VAR_VALUE, "constant_phase_angle", "Constant Phase Angle Function (Output and input phase angles are not equal) [enabled/disabled]");
	addvar(VAR_VALUE, "limited_runtime_on_battery", "Limited runtime on battery mode [enabled/disabled]");

	/* Bypass Mode frequency/voltage limits */
	addvar(VAR_VALUE, "max_bypass_volt", "Maximum voltage for Bypass Mode");
	addvar(VAR_VALUE, "min_bypass_volt", "Minimum voltage for Bypass Mode");
	addvar(VAR_VALUE, "max_bypass_freq", "Maximum frequency for Bypass Mode");
	addvar(VAR_VALUE, "min_bypass_freq", "Minimum frequency for Bypass Mode");

	/* Device grid working range type for P31 UPSes */
	addvar(VAR_VALUE, "work_range_type", "Device grid working range for P31 UPSes [Appliance/UPS]");

	/* Output phase angle */
	addvar(VAR_VALUE, "output_phase_angle", "Change output phase angle to the provided value [000, 120, 180, 240]°");

	/* Number of batteries */
	addvar(VAR_VALUE, "battery_number", "Set number of batteries that make a pack to n (integer, 1-9)");

	/* For testing purposes */
	addvar(VAR_FLAG, "testing", "If invoked the driver will exec also commands that still need testing");
}

/* Unskip vars according to protocol used by the UPS */
static void	voltronic_massive_unskip(const long protocol)
{
	item_t	*item;

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

		if (!item->command)
			continue;

		if (	/* == Multiphase UPSes == */
			/* P09 */
			(protocol == 9 && (
			/*	(!strcasecmp(item->info_type, "input.L1-L3.voltage") && item->from == 25) ||	*//* Not unskipped because P09 should be 2-phase input/output */
			/*	(!strcasecmp(item->info_type, "input.L2-L3.voltage") && item->from == 31) ||	*//* Not unskipped because P09 should be 2-phase input/output */
			/*	(!strcasecmp(item->info_type, "output.L1-L3.voltage") && item->from == 25) ||	*//* Not unskipped because P09 should be 2-phase input/output */
			/*	(!strcasecmp(item->info_type, "output.L2-L3.voltage") && item->from == 31) ||	*//* Not unskipped because P09 should be 2-phase input/output */
				(!strcasecmp(item->info_type, "output.bypass.L1-N.voltage") && item->answer_len == 19) ||
				(!strcasecmp(item->info_type, "output.bypass.L2-N.voltage") && item->answer_len == 19)/* ||
				(!strcasecmp(item->info_type, "output.bypass.L3-N.voltage") && item->answer_len == 19)	*//* Not unskipped because P09 should be 2-phase input/output */
			)) ||
			/* P10 */
			(protocol == 10 && (
				!strcasecmp(item->info_type, "output.L3-N.voltage") ||
				(!strcasecmp(item->info_type, "output.L2-L3.voltage") && item->from == 25) ||
				(!strcasecmp(item->info_type, "output.L1-L3.voltage") && item->from == 31) ||
				(!strcasecmp(item->info_type, "output.bypass.L1-N.voltage") && item->answer_len == 37) ||
				(!strcasecmp(item->info_type, "output.bypass.L2-N.voltage") && item->answer_len == 37) ||
				(!strcasecmp(item->info_type, "output.bypass.L3-N.voltage") && item->answer_len == 37) ||
				!strcasecmp(item->info_type, "output.bypass.L1-L2.voltage") ||
				!strcasecmp(item->info_type, "output.bypass.L2-L3.voltage") ||
				!strcasecmp(item->info_type, "output.bypass.L1-L3.voltage") ||
				!strcasecmp(item->info_type, "output.L3.current") ||
				!strcasecmp(item->info_type, "output.L3.power.percent")
			)) ||
			/* P09-P10 */
			((protocol == 9 || protocol == 10) && (
				!strcasecmp(item->info_type, "output.L1-N.voltage") ||
				!strcasecmp(item->info_type, "output.L2-N.voltage") ||/*
				!strcasecmp(item->info_type, "output.L3-N.voltage") ||	*//* Not unskipped because P09 should be 2-phase input/output */
				!strcasecmp(item->info_type, "output.L1-L2.voltage") ||
				!strcasecmp(item->info_type, "output.L1.current") ||
				!strcasecmp(item->info_type, "output.L2.current") ||/*
				!strcasecmp(item->info_type, "output.L3.current") ||	*//* Not unskipped because P09 should be 2-phase input/output */
				!strcasecmp(item->info_type, "output.L1.power.percent") ||
				!strcasecmp(item->info_type, "output.L2.power.percent")/* ||
				!strcasecmp(item->info_type, "output.L3.power.percent")	*//* Not unskipped because P09 should be 2-phase input/output */
			)) ||
			/* P03-P09-P10 */
			((protocol == 3 || protocol == 9 || protocol == 10) && (
				!strcasecmp(item->info_type, "input.L1-N.voltage") ||
				!strcasecmp(item->info_type, "input.L2-N.voltage") ||/*
				!strcasecmp(item->info_type, "input.L3-N.voltage") ||*//* Not unskipped because P09 should be 2-phase input/output */
				!strcasecmp(item->info_type, "input.L1-L2.voltage") ||
				!strcasecmp(item->info_type, "input.L1.current") ||
				!strcasecmp(item->info_type, "input.L2.current")/* ||
				!strcasecmp(item->info_type, "input.L3.current")*//* Not unskipped because P09 should be 2-phase input/output */
			)) ||
			/* P03-P10 */
			((protocol == 3 || protocol == 10) && (
				!strcasecmp(item->info_type, "input.L3-N.voltage") ||
				(!strcasecmp(item->info_type, "input.L2-L3.voltage") && item->from == 25) ||
				(!strcasecmp(item->info_type, "input.L1-L3.voltage") && item->from == 31) ||
				!strcasecmp(item->info_type, "input.L3.current")
			)) ||
			/* == P31 battery type/device grid working range == */
			(protocol == 31 && (
				!strcasecmp(item->info_type, "battery.type") ||
				(!strcasecmp(item->info_type, "work_range_type") && !(item->qxflags & QX_FLAG_SETVAR)) ||
				(!strcasecmp(item->info_type, "work_range_type") && (item->qxflags & QX_FLAG_SETVAR) && getval(item->info_type))
			)) ||
			/* == ByPass limits: all but P00/P08/P31 == */
			(protocol != 0 && protocol != 8 && protocol != 31 && (
				(!strcasecmp(item->info_type, "max_bypass_volt") && !(item->qxflags & QX_FLAG_SETVAR)) ||
				(!strcasecmp(item->info_type, "min_bypass_volt") && !(item->qxflags & QX_FLAG_SETVAR)) ||
				(!strcasecmp(item->info_type, "max_bypass_freq") && !(item->qxflags & QX_FLAG_SETVAR)) ||
				(!strcasecmp(item->info_type, "min_bypass_freq") && !(item->qxflags & QX_FLAG_SETVAR))
			)) ||
			/* == Reset capabilities options to safe default values == */
			(!strcasecmp(item->info_type, "reset_to_default") && testvar("reset_to_default")) ||
			/* == QBDR (unknown) == */
			(!strcasecmp(item->info_type, "unknown.7") && testvar("testing"))
		) {

				item->qxflags &= ~QX_FLAG_SKIP;

		}

	}
}


/* == Preprocess functions == */

/* *SETVAR(/NONUT)* Preprocess setvars */
static int	voltronic_process_setvar(item_t *item, char *value, const size_t valuelen)
{
	double	val;

	/* upsdebug_SET_STARTING(item->info_type, value); */

	if (!strlen(value)) {
		upsdebugx(2, "%s: value not given for %s", __func__, item->info_type);
		return -1;
	}

	val = strtod(value, NULL);

	if (!strcasecmp(item->info_type, "ups.delay.start")) {

		/* Truncate to minute */
		val -= ((int)val % 60);

		snprintf(value, valuelen, "%.0f", val);

		return 0;

	} else if (!strcasecmp(item->info_type, "ups.delay.shutdown")) {

		/* Truncate to nearest setable value */
		if (val < 60) {
			val -= ((int)val % 6);
		} else {
			val -= ((int)val % 60);
		}

		snprintf(value, valuelen, "%.0f", val);

		return 0;

	} else if (!strcasecmp(item->info_type, "max_bypass_freq")) {

		if (d_equal(val, max_bypass_freq)) {
			upslogx(LOG_INFO, "%s is already set to %.1f", item->info_type, val);
			return -1;
		}

	} else if (!strcasecmp(item->info_type, "min_bypass_freq")) {

		if (d_equal(val, min_bypass_freq)) {
			upslogx(LOG_INFO, "%s is already set to %.1f", item->info_type, val);
			return -1;
		}

	} else if (!strcasecmp(item->info_type, "max_bypass_volt")) {

		if (d_equal(val, max_bypass_volt)) {
			upslogx(LOG_INFO, "%s is already set to %.0f", item->info_type, val);
			return -1;
		}

	} else if (!strcasecmp(item->info_type, "min_bypass_volt")) {

		if (d_equal(val, min_bypass_volt)) {
			upslogx(LOG_INFO, "%s is already set to %.0f", item->info_type, val);
			return -1;
		}

	} else if (!strcasecmp(item->info_type, "battery_number")) {

		if (d_equal(val, battery_number)) {
			upslogx(LOG_INFO, "%s is already set to %.0f", item->info_type, val);
			return -1;
		}

	}

	snprintf_dynamic(value, valuelen, item->command, "%f", val);

	return 0;
}

/* *CMD* Preprocess instant commands */
static int	voltronic_process_command(item_t *item, char *value, const size_t valuelen)
{
	char	buf[SMALLBUF] = "";

	/* upsdebug_INSTCMD_STARTING(item->info_type, value); */

	if (!strcasecmp(item->info_type, "shutdown.return")) {

		/* Sn: Shutdown after n minutes and then turn on when mains is back
		 * SnRm: Shutdown after n minutes and then turn on after m minutes
		 * Accepted values for n: .2 -> .9 , 01 -> 99
		 * Accepted values for m: 0001 -> 9999 */

		long	offdelay = strtol(dstate_getinfo("ups.delay.shutdown"), NULL, 10),
				ondelay = strtol(dstate_getinfo("ups.delay.start"), NULL, 10) / 60;

		if (ondelay == 0) {

			if (offdelay < 60) {
				snprintf(buf, sizeof(buf), ".%ld", offdelay / 6);
			} else {
				snprintf(buf, sizeof(buf), "%02ld", offdelay / 60);
			}

		} else if (offdelay < 60) {

			snprintf(buf, sizeof(buf), ".%ldR%04ld", offdelay / 6, ondelay);

		} else {

			snprintf(buf, sizeof(buf), "%02ldR%04ld", offdelay / 60, ondelay);

		}

	} else if (!strcasecmp(item->info_type, "shutdown.stayoff")) {

		/* SnR0000
		 * Shutdown after n minutes and stay off
		 * Accepted values for n: .2 -> .9 , 01 -> 99 */

		long	offdelay = strtol(dstate_getinfo("ups.delay.shutdown"), NULL, 10);

		if (offdelay < 60) {
			snprintf(buf, sizeof(buf), ".%ld", offdelay / 6);
		} else {
			snprintf(buf, sizeof(buf), "%02ld", offdelay / 60);
		}

	} else if (!strcasecmp(item->info_type, "test.battery.start")) {

		/* Accepted values for test time: .2 -> .9 (.2=12sec ..), 01 -> 99 (minutes)
		 * -> you have to invoke test.battery.start + number of seconds [12..5940] */

		long	delay;

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

		delay = (strlen(value) > 0) ? strtol(value, NULL, 10) : 600;

		if ((delay < 12) || (delay > 5940)) {
			upslogx(LOG_ERR,
				"%s: battery test time '%ld' out of range [12..5940] seconds",
				item->info_type, delay);
			return -1;
		}

		/* test time < 1 minute */
		if (delay < 60) {

			delay = delay / 6;
			snprintf(buf, sizeof(buf), ".%ld", delay);

		/* test time > 1 minute */
		} else {

			delay = delay / 60;
			snprintf(buf, sizeof(buf), "%02ld", delay);

		}

	} else if (!strcasecmp(item->info_type, "beeper.toggle")) {

		const char	*beeper_status = dstate_getinfo("ups.beeper.status");

		/* If the UPS is beeping then we can call BZOFF; if we previously set BZOFF we can call BZON, provided that the beeper is not disabled */

		/* The UPS can disable/enable alarm (from UPS capability) */
		if (alarm_control) {

			if (!strcmp(beeper_status, "enabled")) {

				snprintf(buf, sizeof(buf), "OFF");

			} else if (!strcmp(beeper_status, "muted")) {

				snprintf(buf, sizeof(buf), "ON");

			/* Beeper disabled */
			} else {

				upslogx(LOG_INFO, "The beeper is already disabled");
				return -1;

			}

		/* The UPS can't disable/enable alarm (from UPS capability) */
		} else {

			if (!strcmp(beeper_status, "enabled")) {

				snprintf(buf, sizeof(buf), "OFF");

			} else if (!strcmp(beeper_status, "disabled")) {

				snprintf(buf, sizeof(buf), "ON");

			}

		}

	} else {

		/* Don't know what happened: unknown entry for pre-processing? */
		/* upslog_INSTCMD_UNKNOWN(item->info_type, value); */
		return -1;

	}

	snprintf_dynamic(value, valuelen, item->command, "%s", buf);

	return 0;
}

/* UPS capabilities */
static int	voltronic_capability(item_t *item, char *value, const size_t valuelen)
{
	char	rawval[SMALLBUF], *enabled, *disabled, *val = NULL, *saveptr = NULL;
	item_t	*unskip;

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

	enabled = strtok_r(rawval+1, "D", &saveptr);
	disabled = strtok_r(NULL, "\0", &saveptr);

	if (!enabled && !disabled) {
		upsdebugx(2, "%s: parsing failed", __func__);
		return -1;
	}

	enabled = enabled ? enabled : "";
	disabled = disabled ? disabled : "";

	/* NONUT items */
	if (!strcasecmp(item->info_type, "bypass_alarm")) {

		if (strchr(enabled, 'p')) {
			val = bypass_alarm = "enabled";
		} else if (strchr(disabled, 'p')) {
			val = bypass_alarm = "disabled";
		}

	} else if (!strcasecmp(item->info_type, "battery_alarm")) {

		if (strchr(enabled, 'b')) {
			val = battery_alarm = "enabled";
		} else if (strchr(disabled, 'b')) {
			val = battery_alarm = "disabled";
		}

	} else if (!strcasecmp(item->info_type, "bypass_when_off")) {

		if (strchr(enabled, 'o')) {
			val = bypass_when_off = "enabled";
		} else if (strchr(disabled, 'o')) {
			val = bypass_when_off = "disabled";
		}

	} else if (!strcasecmp(item->info_type, "alarm_control")) {

		if (strchr(item->value, 'a')) {

			if (strchr(enabled, 'a')) {

				const char	*beeper = dstate_getinfo("ups.beeper.status");

				val = alarm_control = "enabled";

				if (!beeper || strcmp(beeper, "muted")) {
					dstate_setinfo("ups.beeper.status", "enabled");
				}

			} else if (strchr(disabled, 'a')) {

				val = alarm_control = "disabled";
				dstate_setinfo("ups.beeper.status", "disabled");

			}

			/* Unskip beeper.{enable,disable} instcmds */
			unskip = find_nut_info("beeper.enable", QX_FLAG_CMD, 0);

			/* Don't know what happened */
			if (!unskip)
				return -1;

			unskip->qxflags &= ~QX_FLAG_SKIP;

			unskip = find_nut_info("beeper.disable", QX_FLAG_CMD, 0);

			/* Don't know what happened */
			if (!unskip)
				return -1;

			unskip->qxflags &= ~QX_FLAG_SKIP;

		}

	} else if (!strcasecmp(item->info_type, "converter_mode")) {

		if (strchr(enabled, 'v')) {
			val = converter_mode = "enabled";
		} else if (strchr(disabled, 'v')) {
			val = converter_mode = "disabled";
		}

	} else if (!strcasecmp(item->info_type, "eco_mode")) {

		if (strchr(item->value, 'e')) {

			if (strchr(enabled, 'e')) {
				val = eco_mode = "enabled";
			} else if (strchr(disabled, 'e')) {
				val = eco_mode = "disabled";
			}

			/* Unskip bypass.{start,stop} instcmds */
			unskip = find_nut_info("bypass.start", QX_FLAG_CMD, 0);

			/* Don't know what happened */
			if (!unskip)
				return -1;

			unskip->qxflags &= ~QX_FLAG_SKIP;

			unskip = find_nut_info("bypass.stop", QX_FLAG_CMD, 0);

			/* Don't know what happened */
			if (!unskip)
				return -1;

			unskip->qxflags &= ~QX_FLAG_SKIP;

			/* Unskip input.transfer.{high,low} */
			unskip = find_nut_info("input.transfer.high", QX_FLAG_SEMI_STATIC, 0);

			/* Don't know what happened */
			if (!unskip)
				return -1;

			unskip->qxflags &= ~QX_FLAG_SKIP;

			unskip = find_nut_info("input.transfer.low", QX_FLAG_SEMI_STATIC, 0);

			/* Don't know what happened */
			if (!unskip)
				return -1;

			unskip->qxflags &= ~QX_FLAG_SKIP;

			/* Unskip input.frequency.{high,low} */
			unskip = find_nut_info("input.frequency.high", QX_FLAG_SEMI_STATIC, 0);

			/* Don't know what happened */
			if (!unskip)
				return -1;

			unskip->qxflags &= ~QX_FLAG_SKIP;

			unskip = find_nut_info("input.frequency.low", QX_FLAG_SEMI_STATIC, 0);

			/* Don't know what happened */
			if (!unskip)
				return -1;

			unskip->qxflags &= ~QX_FLAG_SKIP;

		}

	} else if (!strcasecmp(item->info_type, "battery_open_status_check")) {

		if (strchr(enabled, 'd')) {
			val = battery_open_status_check = "enabled";
		} else if (strchr(disabled, 'd')) {
			val = battery_open_status_check = "disabled";
		}

	} else if (!strcasecmp(item->info_type, "bypass_forbidding")) {

		if (strchr(enabled, 'f')) {
			val = bypass_forbidding = "enabled";
		} else if (strchr(disabled, 'f')) {
			val = bypass_forbidding = "disabled";
		}

	} else if (!strcasecmp(item->info_type, "site_fault_detection")) {

		if (strchr(enabled, 'l')) {
			val = site_fault_detection = "enabled";
		} else if (strchr(disabled, 'l')) {
			val = site_fault_detection = "disabled";
		}

	} else if (!strcasecmp(item->info_type, "advanced_eco_mode")) {

		if (strchr(enabled, 'n')) {
			val = advanced_eco_mode = "enabled";
		} else if (strchr(disabled, 'n')) {
			val = advanced_eco_mode = "disabled";
		}

	} else if (!strcasecmp(item->info_type, "constant_phase_angle")) {

		if (strchr(enabled, 'q')) {
			val = constant_phase_angle = "enabled";
		} else if (strchr(disabled, 'q')) {
			val = constant_phase_angle = "disabled";
		}

	} else if (!strcasecmp(item->info_type, "limited_runtime_on_battery")) {

		if (strchr(enabled, 'w')) {
			val = limited_runtime_on_battery = "enabled";
		} else if (strchr(disabled, 'w')) {
			val = limited_runtime_on_battery = "disabled";
		}

/*	} else if (!strcasecmp(item->info_type, "")) {

		if (strchr(enabled, 'h')) {	unknown/unused
		} else if (strchr(disabled, 'h')) { }

	} else if (!strcasecmp(item->info_type, "")) {

		if (strchr(enabled, 't')) {	unknown/unused
		} else if (strchr(disabled, 't')) { }

	} else if (!strcasecmp(item->info_type, "")) {

		if (strchr(enabled, 'k')) {	unknown/unused
		} else if (strchr(disabled, 'k')) { }

	} else if (!strcasecmp(item->info_type, "")) {

		if (strchr(enabled, 'i')) {	unknown/unused
		} else if (strchr(disabled, 'i')) { }

	} else if (!strcasecmp(item->info_type, "")) {

		if (strchr(enabled, 'm')) {	unknown/unused
		} else if (strchr(disabled, 'm')) { }

	} else if (!strcasecmp(item->info_type, "")) {

		if (strchr(enabled, 'z')) {	unknown/unused
		} else if (strchr(disabled, 'z')) { }
*/
	/* Items with a NUT variable */
	} else if (!strcasecmp(item->info_type, "ups.start.auto")) {

		if (strchr(enabled, 'r')) {
			val = "yes";
		} else if (strchr(disabled, 'r')) {
			val = "no";
		}

	} else if (!strcasecmp(item->info_type, "battery.protection")) {

		if (strchr(enabled, 's')) {
			val = "yes";
		} else if (strchr(disabled, 's')) {
			val = "no";
		}

	} else if (!strcasecmp(item->info_type, "battery.energysave")) {

		if (strchr(enabled, 'g')) {
			val = "yes";
		} else if (strchr(disabled, 'g')) {
			val = "no";
		}

	} else if (!strcasecmp(item->info_type, "ups.start.battery")) {

		if (strchr(enabled, 'c')) {
			val = "yes";
		} else if (strchr(disabled, 'c')) {
			val = "no";
		}

	} else if (!strcasecmp(item->info_type, "outlet.0.switchable")) {

		if (strchr(enabled, 'j')) {

			int	i;
			char	buf[SMALLBUF];

			val = "yes";

			/* Unskip outlet.n.{switchable,status} */
			for (i = 1; i <= 4; i++) {

				snprintf(buf, sizeof(buf), "outlet.%d.switchable", i);

				unskip = find_nut_info(buf, 0, 0);

				/* Don't know what happened */
				if (!unskip)
					return -1;

				unskip->qxflags &= ~QX_FLAG_SKIP;

				snprintf(buf, sizeof(buf), "outlet.%d.status", i);

				unskip = find_nut_info(buf, 0, 0);

				/* Don't know what happened */
				if (!unskip)
					return -1;

				unskip->qxflags &= ~QX_FLAG_SKIP;

			}

		} else if (strchr(disabled, 'j')) {
			val = "no";
		}

	}

	/* UPS doesn't have that capability */
	if (!val)
		return -1;

	snprintf_dynamic(value, valuelen, item->dfl, "%s", val);

	/* This item doesn't have a NUT var and we were not asked by the user to change its value */
	if ((item->qxflags & QX_FLAG_NONUT) && !getval(item->info_type))
		return 0;

	/* Unskip setvar */
	unskip = find_nut_info(item->info_type, QX_FLAG_SETVAR, 0);

	/* Don't know what happened */
	if (!unskip)
		return -1;

	unskip->qxflags &= ~QX_FLAG_SKIP;

	return 0;
}

/* *SETVAR* Set UPS capability options */
static int	voltronic_capability_set(item_t *item, char *value, const size_t valuelen)
{
	if (!strcasecmp(value, "yes")) {
		snprintf_dynamic(value, valuelen, item->command, "%s", "E");
		return 0;
	}

	if (!strcasecmp(value, "no")) {
		snprintf_dynamic(value, valuelen, item->command, "%s", "D");
		return 0;
	}

	/* At this point value should have been already checked against enum so this shouldn't happen.. however.. */
	upslogx(LOG_ERR, "%s: given value [%s] is not acceptable. Enter either 'yes' or 'no'.", item->info_type, value);

	return -1;
}

/* *SETVAR/NONUT* Change UPS capability according to user configuration in ups.conf */
static int	voltronic_capability_set_nonut(item_t *item, char *value, const size_t valuelen)
{
	const char	*match = NULL;
	int		i;
	const struct {
		const char	*type;	/* Name of the option */
		const char	*match;	/* Value reported by the UPS */
	} capability[] = {
		{ "bypass_alarm",		bypass_alarm },
		{ "battery_alarm",		battery_alarm },
		{ "bypass_when_off",		bypass_when_off },
		{ "alarm_control",		alarm_control },
		{ "converter_mode",		converter_mode },
		{ "eco_mode",			eco_mode },
		{ "battery_open_status_check",	battery_open_status_check },
		{ "bypass_forbidding",		bypass_forbidding },
		{ "site_fault_detection",	site_fault_detection },
		{ "advanced_eco_mode",		advanced_eco_mode },
		{ "constant_phase_angle",	constant_phase_angle },
		{ "limited_runtime_on_battery",	limited_runtime_on_battery },
		{ NULL, NULL }
	};

	for (i = 0; capability[i].type; i++) {

		if (strcasecmp(item->info_type, capability[i].type))
			continue;

		match = capability[i].match;

		break;

	}

	/* UPS doesn't have that capability */
	if (!match)
		return -1;

	/* At this point value should have been already checked by nutdrv_qx's own setvar so this shouldn't happen.. however.. */
	if (!strcasecmp(value, match)) {
		upslogx(LOG_INFO, "%s is already %s", item->info_type, match);
		return -1;
	}

	if (!strcasecmp(value, "disabled")) {
		snprintf_dynamic(value, valuelen, item->command, "%s", "D");
	} else if (!strcasecmp(value, "enabled")) {
		snprintf_dynamic(value, valuelen, item->command, "%s", "E");
	} else {
		/* At this point value should have been already checked against enum so this shouldn't happen.. however.. */
		upslogx(LOG_ERR, "%s: [%s] is not within acceptable values [enabled/disabled]", item->info_type, value);
		return -1;
	}

	return 0;
}

/* *SETVAR/NONUT* Reset capability options and their limits to safe default values */
static int	voltronic_capability_reset(item_t *item, char *value, const size_t valuelen)
{
	/* Nothing to do */
	if (!testvar("reset_to_default"))
		return -1;

	/* UPS capability options can be reset only when the UPS is in 'Standby Mode' (=OFF) (from QMOD) */
	if (!((unsigned int)(qx_status()) & STATUS(OFF))) {
		upslogx(LOG_ERR,
			"%s: UPS capability options can be reset only when the "
			"UPS is in Standby Mode (i.e. ups.status = 'OFF').",
			item->info_type);
		return -1;
	}

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

	return 0;
}

/* Voltage limits for ECO Mode */
static int	voltronic_eco_volt(item_t *item, char *value, const size_t valuelen)
{
	const long	protocol = strtol(dstate_getinfo("ups.firmware.aux")+1, NULL, 10);
	int			ovn;
	const char	*outvoltnom;
	char		buf[SMALLBUF];
	item_t		*unskip;
	/* Range of accepted values for maximum voltage for ECO Mode */
	struct {
		int	lower;	/* Lower limit */
		int	upper;	/* Upper limit */
	} max;
	/* Range of accepted values for minimum voltage for ECO Mode */
	struct {
		int	lower;	/* Lower limit */
		int	upper;	/* Upper limit */
	} min;

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

	snprintf_dynamic(value, valuelen, item->dfl, "%f", strtod(item->value, NULL));

	outvoltnom = dstate_getinfo("output.voltage.nominal");

	/* Since query for ratings (QRI) is not mandatory to run this driver, skip next steps if we can't get the value of output voltage nominal */
	if (!outvoltnom) {
		upsdebugx(2, "%s: unable to get output voltage nominal", __func__);
		/* We return 0 since we have the value and all's ok: simply we can't set its range so we won't unskip SETVAR item and .{min,max} */
		return 0;
	}

	{ /* scoping */
		long l = strtol(outvoltnom, NULL, 10);
		if (l > INT_MAX || l < 0) {
			/* See comments above */
			upsdebugx(2, "%s: unable to get output voltage nominal: %ld", __func__, l);
			return 0;
		}
		ovn = (int)l;
	}

	/* For P01/P09 */
	if (protocol == 1 || protocol == 9) {

		if (ovn >= 200) {
			min.lower = ovn - 24;
			min.upper = ovn - 7;
			max.lower = ovn + 7;
			max.upper = ovn + 24;
		} else {
			min.lower = ovn - 12;
			min.upper = ovn - 3;
			max.lower = ovn + 3;
			max.upper = ovn + 12;

		}

	/* For P02/P03/P10/P13/P14/P99 */
	} else if (protocol == 2 || protocol == 3 || protocol == 10 || protocol == 13 || protocol == 14 || protocol == 99) {

		if (ovn >= 200) {
			min.lower = ovn - 24;
			min.upper = ovn - 11;
			max.lower = ovn + 11;
			max.upper = ovn + 24;
		} else {
			min.lower = ovn - 12;
			min.upper = ovn - 5;
			max.lower = ovn + 5;
			max.upper = ovn + 12;
		}

	/* ECO mode not supported */
	} else {
		upsdebugx(2, "%s: the UPS doesn't seem to support ECO Mode", __func__);
		/* We return 0 since we have the value and all's ok: simply we can't set its range so we won't unskip SETVAR item and .{min,max} */
		return 0;
	}

	if (!strcasecmp(item->info_type, "input.transfer.high")) {

		/* Fill voltronic_r_eco_volt_max */
		snprintf(item->info_rw[0].value, sizeof(item->info_rw[0].value), "%d", max.lower);
		snprintf(item->info_rw[1].value, sizeof(item->info_rw[1].value), "%d", max.upper);


	} else if (!strcasecmp(item->info_type, "input.transfer.low")) {

		/* Fill voltronic_r_eco_volt_min */
		snprintf(item->info_rw[0].value, sizeof(item->info_rw[0].value), "%d", min.lower);
		snprintf(item->info_rw[1].value, sizeof(item->info_rw[1].value), "%d", min.upper);

	}

	/* Unskip input.transfer.{high,low}.{min,max} */
	snprintf(buf, sizeof(buf), "%s.min", item->info_type);

	unskip = find_nut_info(buf, QX_FLAG_SEMI_STATIC, 0);

	/* Don't know what happened */
	if (!unskip)
		return -1;

	unskip->qxflags &= ~QX_FLAG_SKIP;

	snprintf(buf, sizeof(buf), "%s.max", item->info_type);

	unskip = find_nut_info(buf, QX_FLAG_SEMI_STATIC, 0);

	/* Don't know what happened */
	if (!unskip)
		return -1;

	unskip->qxflags &= ~QX_FLAG_SKIP;

	/* Unskip input.transfer.{high,low} setvar */
	unskip = find_nut_info(item->info_type, QX_FLAG_SETVAR, 0);

	/* Don't know what happened */
	if (!unskip)
		return -1;

	unskip->qxflags &= ~QX_FLAG_SKIP;

	return 0;
}

/* Voltage limits for ECO Mode (max, min) */
static int	voltronic_eco_volt_range(item_t *item, char *value, const size_t valuelen)
{
	char	*buf;
	int	i;
	item_t	*from;

	if (!strcasecmp(item->info_type, "input.transfer.low.min")) {

		buf = "input.transfer.low";
		i = 0;

	} else if (!strcasecmp(item->info_type, "input.transfer.low.max")) {

		buf = "input.transfer.low";
		i = 1;

	} else if (!strcasecmp(item->info_type, "input.transfer.high.min")) {

		buf = "input.transfer.high";
		i = 0;

	} else if (!strcasecmp(item->info_type, "input.transfer.high.max")) {

		buf = "input.transfer.high";
		i = 1;

	} else {

		/* Don't know what happened */
		return -1;

	}

	from = find_nut_info(buf, QX_FLAG_SEMI_STATIC, 0);

	/* Don't know what happened */
	if (!from)
		return -1;

	/* Value is set at runtime by voltronic_eco_volt, so if it's still unset something went wrong */
	if (!strlen(from->info_rw[i].value))
		return -1;

	snprintf(value, valuelen, "%s", from->info_rw[i].value);

	return 0;
}

/* Frequency limits for ECO Mode */
static int	voltronic_eco_freq(item_t *item, char *value, const size_t valuelen)
{
	item_t	*unskip;

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

	snprintf_dynamic(value, valuelen, item->dfl, "%f", strtod(item->value, NULL));

	/* Unskip input.transfer.{high,low} setvar */
	unskip = find_nut_info(item->info_type, QX_FLAG_SETVAR, 0);

	/* Don't know what happened */
	if (!unskip)
		return -1;

	unskip->qxflags &= ~QX_FLAG_SKIP;

	return 0;
}

/* *NONUT* Voltage/frequency limits for Bypass Mode */
static int	voltronic_bypass(item_t *item, char *value, const size_t valuelen)
{
	item_t	*unskip;
	double	val;

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

	if (!strcasecmp(item->info_type, "max_bypass_volt")) {

		val = max_bypass_volt = strtol(item->value, NULL, 10);

	} else if (!strcasecmp(item->info_type, "min_bypass_volt")) {

		val = min_bypass_volt = strtol(item->value, NULL, 10);

	} else if (!strcasecmp(item->info_type, "max_bypass_freq")) {

		val = max_bypass_freq = strtod(item->value, NULL);

	} else if (!strcasecmp(item->info_type, "min_bypass_freq")) {

		val = min_bypass_freq = strtod(item->value, NULL);

	} else {

		/* Don't know what happened */
		return -1;

	}

	snprintf_dynamic(value, valuelen, item->dfl, "%f", val);

	/* No user-provided value to change.. */
	if (!getval(item->info_type))
		return 0;

	/* Unskip {min,max}_bypass_volt setvar */
	unskip = find_nut_info(item->info_type, QX_FLAG_SETVAR, 0);

	/* Don't know what happened */
	if (!unskip)
		return -1;

	unskip->qxflags &= ~QX_FLAG_SKIP;

	return 0;
}

/* *NONUT* Number of batteries */
static int	voltronic_batt_numb(item_t *item, char *value, const size_t valuelen)
{
	item_t	*unskip;

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

	battery_number = strtol(item->value, NULL, 10);

	if (battery_number > INT_MAX) {
		upsdebugx(2, "%s: battery number out of range [%s: %s]",
			__func__, item->info_type, item->value);
		return -1;
	}

	snprintf_dynamic(value, valuelen, item->dfl, "%d", (int)battery_number);

	/* No user-provided value to change.. */
	if (!getval(item->info_type))
		return 0;

	/* Unskip battery_number setvar */
	unskip = find_nut_info("battery_number", QX_FLAG_SETVAR, 0);

	/* Don't know what happened */
	if (!unskip)
		return -1;

	unskip->qxflags &= ~QX_FLAG_SKIP;

	return 0;
}

/* Battery runtime */
static int	voltronic_batt_runtime(item_t *item, char *value, const size_t valuelen)
{
	double	runtime;

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

	/* Battery runtime is reported by the UPS in minutes, NUT expects seconds */
	runtime = strtod(item->value, NULL) * 60;

	snprintf_dynamic(value, valuelen, item->dfl, "%f", runtime);

	return 0;
}

/* Protocol used by the UPS */
static int	voltronic_protocol(item_t *item, char *value, const size_t valuelen)
{
	long	protocol;

	if (strncasecmp(item->value, "PI", 2)) {
		upsdebugx(2, "%s: invalid start characters [%.2s]", __func__, item->value);
		return -1;
	}

	/* Here we exclude non numerical value and other non accepted protocols (hence the restricted comparison target) */
	if (strspn(item->value+2, "0123489") != strlen(item->value+2)) {
		upslogx(LOG_ERR, "Protocol [%s] is not supported by this driver", item->value);
		return -1;
	}

	protocol = strtol(item->value+2, NULL, 10);

	switch (protocol)
	{
	case 0:
	case 1:
	case 2:
	case 3:
	case 8:
	case 9:
	case 10:
	case 13:
	case 14:
	case 31:
	case 99:

		break;

	default:

		upslogx(LOG_ERR, "Protocol [PI%02ld] is not supported by this driver", protocol);
		return -1;

	}

	snprintf(value, valuelen, "P%02ld", protocol);

	/* Unskip vars according to protocol */
	voltronic_massive_unskip(protocol);

	return 0;
}

/* Fault reported by the UPS:
 * When the UPS is queried for status (QGS), if it reports a fault (6th bit of 12bit flag of the reply to QGS set to 1), the driver unskips the QFS item in qx2nut array: this function processes the reply to QFS query */
static int	voltronic_fault(item_t *item, char *value, const size_t valuelen)
{
	long	protocol = strtol(dstate_getinfo("ups.firmware.aux")+1, NULL, 10);

	char	alarm[LARGEBUF]; /* can snprintf()/strncpy() SMALLBUF plus markup into here */

	upslogx(LOG_INFO, "Checking for faults..");

	if (!strcasecmp(item->value, "OK")) {
		snprintf_dynamic(value, valuelen, item->dfl, "%s", "No fault found");
		upslogx(LOG_INFO, "%s", value);
		item->qxflags |= QX_FLAG_SKIP;
		return 0;
	}

	if ((strspn(item->value, "0123456789ABC") != 2) || ((item->value[0] != '1') && (strspn(item->value+1, "0123456789") != 1))) {

		snprintf(alarm, sizeof(alarm), "Unknown fault [%s]", item->value);

	/* P31 UPSes */
	} else if (protocol == 31) {

		if (strpbrk(item->value+1, "ABC")) {

			snprintf(alarm, sizeof(alarm), "Unknown fault [%s]", item->value);

		} else {

			switch (strtol(item->value, NULL, 10))
			{
			case 1:

				strncpy(alarm, "Fan failure.", sizeof(alarm));
				break;

			case 2:

				strncpy(alarm, "Over temperature fault.", sizeof(alarm));
				break;

			case 3:

				strncpy(alarm, "Battery voltage is too high.", sizeof(alarm));
				break;

			case 4:

				strncpy(alarm, "Battery voltage too low.", sizeof(alarm));
				break;

			case 5:

				strncpy(alarm, "Inverter relay short-circuited.", sizeof(alarm));
				break;

			case 6:

				strncpy(alarm, "Inverter voltage over maximum value.", sizeof(alarm));
				break;

			case 7:

				strncpy(alarm, "Overload fault.", sizeof(alarm));
				update_status("OVER");
				break;

			case 8:

				strncpy(alarm, "Bus voltage exceeds its upper limit.", sizeof(alarm));
				break;

			case 9:

				strncpy(alarm, "Bus soft start fail.", sizeof(alarm));
				break;

			case 10:

				strncpy(alarm, "Unknown fault [Fault code: 10]", sizeof(alarm));
				break;

			case 51:

				strncpy(alarm, "Over current fault.", sizeof(alarm));
				break;

			case 52:

				strncpy(alarm, "Bus voltage below its under limit.", sizeof(alarm));
				break;

			case 53:

				strncpy(alarm, "Inverter soft start fail.", sizeof(alarm));
				break;

			case 54:

				strncpy(alarm, "Self test fail.", sizeof(alarm));
				break;

			case 55:

				strncpy(alarm, "Output DC voltage exceeds its upper limit.", sizeof(alarm));
				break;

			case 56:

				strncpy(alarm, "Battery open fault.", sizeof(alarm));
				break;

			case 57:

				strncpy(alarm, "Current sensor fault.", sizeof(alarm));
				break;

			case 58:

				strncpy(alarm, "Battery short.", sizeof(alarm));
				break;

			case 59:

				strncpy(alarm, "Inverter voltage below its lower limit.", sizeof(alarm));
				break;

			default:

				snprintf(alarm, sizeof(alarm), "Unknown fault [%s]", item->value);
				break;

			}

		}

	/* All other UPSes */
	} else {

		switch (strtol(item->value, NULL, 10))
		{
		case 1:

			switch (item->value[1])
			{
			case 'A':

				strncpy(alarm, "L1 inverter negative power out of acceptable range.", sizeof(alarm));
				break;

			case 'B':

				strncpy(alarm, "L2 inverter negative power out of acceptable range.", sizeof(alarm));
				break;

			case 'C':

				strncpy(alarm, "L3 inverter negative power out of acceptable range.", sizeof(alarm));
				break;

			default:

				strncpy(alarm, "Bus voltage not within default setting.", sizeof(alarm));
				break;

			}

			break;

		case 2:

			strncpy(alarm, "Bus voltage over maximum value.", sizeof(alarm));
			break;

		case 3:

			strncpy(alarm, "Bus voltage below minimum value.", sizeof(alarm));
			break;

		case 4:

			strncpy(alarm, "Bus voltage differences out of acceptable range.", sizeof(alarm));
			break;

		case 5:

			strncpy(alarm, "Bus voltage of slope rate drops too fast.", sizeof(alarm));
			break;

		case 6:

			strncpy(alarm, "Over current in PFC input inductor.", sizeof(alarm));
			break;

		case 11:

			strncpy(alarm, "Inverter voltage not within default setting.", sizeof(alarm));
			break;

		case 12:

			strncpy(alarm, "Inverter voltage over maximum value.", sizeof(alarm));
			break;

		case 13:

			strncpy(alarm, "Inverter voltage below minimum value.", sizeof(alarm));
			break;

		case 14:

			strncpy(alarm, "Inverter short-circuited.", sizeof(alarm));
			break;

		case 15:

			strncpy(alarm, "L2 phase inverter short-circuited.", sizeof(alarm));
			break;

		case 16:

			strncpy(alarm, "L3 phase inverter short-circuited.", sizeof(alarm));
			break;

		case 17:

			strncpy(alarm, "L1L2 inverter short-circuited.", sizeof(alarm));
			break;

		case 18:

			strncpy(alarm, "L2L3 inverter short-circuited.", sizeof(alarm));
			break;

		case 19:

			strncpy(alarm, "L3L1 inverter short-circuited.", sizeof(alarm));
			break;

		case 21:

			strncpy(alarm, "Battery SCR short-circuited.", sizeof(alarm));
			break;

		case 22:

			strncpy(alarm, "Line SCR short-circuited.", sizeof(alarm));
			break;

		case 23:

			strncpy(alarm, "Inverter relay open fault.", sizeof(alarm));
			break;

		case 24:

			strncpy(alarm, "Inverter relay short-circuited.", sizeof(alarm));
			break;

		case 25:

			strncpy(alarm, "Input and output wires oppositely connected.", sizeof(alarm));
			break;

		case 26:

			strncpy(alarm, "Battery oppositely connected.", sizeof(alarm));
			break;

		case 27:

			strncpy(alarm, "Battery voltage is too high.", sizeof(alarm));
			break;

		case 28:

			strncpy(alarm, "Battery voltage too low.", sizeof(alarm));
			break;

		case 29:

			strncpy(alarm, "Failure for battery fuse being open-circuited.", sizeof(alarm));
			break;

		case 31:

			strncpy(alarm, "CAN-bus communication fault.", sizeof(alarm));
			break;

		case 32:

			strncpy(alarm, "Host signal circuit fault.", sizeof(alarm));
			break;

		case 33:

			strncpy(alarm, "Synchronous signal circuit fault.", sizeof(alarm));
			break;

		case 34:

			strncpy(alarm, "Synchronous pulse signal circuit fault.", sizeof(alarm));
			break;

		case 35:

			strncpy(alarm, "Parallel cable disconnected.", sizeof(alarm));
			break;

		case 36:

			strncpy(alarm, "Load unbalanced.", sizeof(alarm));
			break;

		case 41:

			strncpy(alarm, "Over temperature fault.", sizeof(alarm));
			break;

		case 42:

			strncpy(alarm, "Communication failure between CPUs in control board.", sizeof(alarm));
			break;

		case 43:

			strncpy(alarm, "Overload fault.", sizeof(alarm));
			update_status("OVER");
			break;

		case 44:

			strncpy(alarm, "Fan failure.", sizeof(alarm));
			break;

		case 45:

			strncpy(alarm, "Charger failure.", sizeof(alarm));
			break;

		case 46:

			strncpy(alarm, "Model fault.", sizeof(alarm));
			break;

		case 47:

			strncpy(alarm, "MCU communication fault.", sizeof(alarm));
			break;

		default:

			snprintf(alarm, sizeof(alarm), "Unknown fault [%s]", item->value);
			break;

		}

	}

	snprintf_dynamic(value, valuelen, item->dfl, "%s", alarm);
	upslogx(LOG_INFO, "Fault found: %s", alarm);

	item->qxflags |= QX_FLAG_SKIP;

	return 0;
}

/* Warnings reported by the UPS */
static int	voltronic_warning(item_t *item, char *value, const size_t valuelen)
{
	char	warn[SMALLBUF] = "", unk[SMALLBUF] = "", bitwarns[4096] = "", warns[4096] = "";
	int	i;

	if (strspn(item->value, "01") != strlen(item->value)) {
		upsdebugx(2, "%s: invalid reply from the UPS [%s]", __func__, item->value);
		return -1;
	}

	/* No warnings */
	if (strspn(item->value, "0") == strlen(item->value)) {
		return 0;
	}

	snprintf(value, valuelen, "UPS warnings:");

	for (i = 0; i < (int)strlen(item->value); i++) {

		int	u = 0;

		if (item->value[i] == '1') {

			switch (i)
			{
			case 0:

				strncpy(warn, "Battery disconnected.", sizeof(warn));
				break;

			case 1:

				strncpy(warn, "Neutral not connected.", sizeof(warn));
				break;

			case 2:

				strncpy(warn, "Site fault.", sizeof(warn));
				break;

			case 3:

				strncpy(warn, "Phase sequence incorrect.", sizeof(warn));
				break;

			case 4:

				strncpy(warn, "Phase sequence incorrect in bypass.", sizeof(warn));
				break;

			case 5:

				strncpy(warn, "Input frequency unstable in bypass.", sizeof(warn));
				break;

			case 6:

				strncpy(warn, "Battery overcharged.", sizeof(warn));
				break;

			case 7:

				strncpy(warn, "Low battery.", sizeof(warn));
				update_status("LB");
				break;

			case 8:

				strncpy(warn, "Overload alarm.", sizeof(warn));
				update_status("OVER");
				break;

			case 9:

				strncpy(warn, "Fan alarm.", sizeof(warn));
				break;

			case 10:

				strncpy(warn, "EPO enabled.", sizeof(warn));
				break;

			case 11:

				strncpy(warn, "Unable to turn on UPS.", sizeof(warn));
				break;

			case 12:

				strncpy(warn, "Over temperature alarm.", sizeof(warn));
				break;

			case 13:

				strncpy(warn, "Charger alarm.", sizeof(warn));
				break;

			case 14:

				strncpy(warn, "Remote auto shutdown.", sizeof(warn));
				break;

			case 15:

				strncpy(warn, "L1 input fuse not working.", sizeof(warn));
				break;

			case 16:

				strncpy(warn, "L2 input fuse not working.", sizeof(warn));
				break;

			case 17:

				strncpy(warn, "L3 input fuse not working.", sizeof(warn));
				break;

			case 18:

				strncpy(warn, "Positive PFC abnormal in L1.", sizeof(warn));
				break;

			case 19:

				strncpy(warn, "Negative PFC abnormal in L1.", sizeof(warn));
				break;

			case 20:

				strncpy(warn, "Positive PFC abnormal in L2.", sizeof(warn));
				break;

			case 21:

				strncpy(warn, "Negative PFC abnormal in L2.", sizeof(warn));
				break;

			case 22:

				strncpy(warn, "Positive PFC abnormal in L3.", sizeof(warn));
				break;

			case 23:

				strncpy(warn, "Negative PFC abnormal in L3.", sizeof(warn));
				break;

			case 24:

				strncpy(warn, "Abnormal in CAN-bus communication.", sizeof(warn));
				break;

			case 25:

				strncpy(warn, "Abnormal in synchronous signal circuit.", sizeof(warn));
				break;

			case 26:

				strncpy(warn, "Abnormal in synchronous pulse signal circuit.", sizeof(warn));
				break;

			case 27:

				strncpy(warn, "Abnormal in host signal circuit.", sizeof(warn));
				break;

			case 28:

				strncpy(warn, "Male connector of parallel cable not connected well.", sizeof(warn));
				break;

			case 29:

				strncpy(warn, "Female connector of parallel cable not connected well.", sizeof(warn));
				break;

			case 30:

				strncpy(warn, "Parallel cable not connected well.", sizeof(warn));
				break;

			case 31:

				strncpy(warn, "Battery connection not consistent in parallel systems.", sizeof(warn));
				break;

			case 32:

				strncpy(warn, "AC connection not consistent in parallel systems.", sizeof(warn));
				break;

			case 33:

				strncpy(warn, "Bypass connection not consistent in parallel systems.", sizeof(warn));
				break;

			case 34:

				strncpy(warn, "UPS model types not consistent in parallel systems.", sizeof(warn));
				break;

			case 35:

				strncpy(warn, "Capacity of UPSes not consistent in parallel systems.", sizeof(warn));
				break;

			case 36:

				strncpy(warn, "Auto restart setting not consistent in parallel systems.", sizeof(warn));
				break;

			case 37:

				strncpy(warn, "Battery cell over charge.", sizeof(warn));
				break;

			case 38:

				strncpy(warn, "Battery protection setting not consistent in parallel systems.", sizeof(warn));
				break;

			case 39:

				strncpy(warn, "Battery detection setting not consistent in parallel systems.", sizeof(warn));
				break;

			case 40:

				strncpy(warn, "Bypass not allowed setting not consistent in parallel systems.", sizeof(warn));
				break;

			case 41:

				strncpy(warn, "Converter setting not consistent in parallel systems.", sizeof(warn));
				break;

			case 42:

				strncpy(warn, "High loss point for frequency in bypass mode not consistent in parallel systems.", sizeof(warn));
				break;

			case 43:

				strncpy(warn, "Low loss point for frequency in bypass mode not consistent in parallel systems.", sizeof(warn));
				break;

			case 44:

				strncpy(warn, "High loss point for voltage in bypass mode not consistent in parallel systems.", sizeof(warn));
				break;

			case 45:

				strncpy(warn, "Low loss point for voltage in bypass mode not consistent in parallel systems.", sizeof(warn));
				break;

			case 46:

				strncpy(warn, "High loss point for frequency in AC mode not consistent in parallel systems.", sizeof(warn));
				break;

			case 47:

				strncpy(warn, "Low loss point for frequency in AC mode not consistent in parallel systems.", sizeof(warn));
				break;

			case 48:

				strncpy(warn, "High loss point for voltage in AC mode not consistent in parallel systems.", sizeof(warn));
				break;

			case 49:

				strncpy(warn, "Low loss point for voltage in AC mode not consistent in parallel systems.", sizeof(warn));
				break;

			case 50:

				strncpy(warn, "Warning for locking in bypass mode after 3 consecutive overloads within 30 min.", sizeof(warn));
				break;

			case 51:

				strncpy(warn, "Warning for three-phase AC input current unbalance.", sizeof(warn));
				break;

			case 52:

				strncpy(warn, "Warning for a three-phase input current unbalance detected in battery mode.", sizeof(warn));
				break;

			case 53:

				strncpy(warn, "Warning for Inverter inter-current unbalance.", sizeof(warn));
				break;

			case 54:

				strncpy(warn, "Programmable outlets cut off pre-alarm.", sizeof(warn));
				break;

			case 55:

				strncpy(warn, "Warning for Battery replace.", sizeof(warn));
				update_status("RB");
				break;

			case 56:

				strncpy(warn, "Abnormal warning on input phase angle.", sizeof(warn));
				break;

			case 57:

				strncpy(warn, "Warning!! Cover of maintain switch is open.", sizeof(warn));
				break;

			case 61:

				strncpy(warn, "EEPROM operation error.", sizeof(warn));
				break;

			default:

				snprintf(warn, sizeof(warn), "Unknown warning from UPS [bit: #%02d]", i + 1);
				u++;
				break;

			}

			upslogx(LOG_INFO, "Warning from UPS: %s", warn);

			if (u) {	/* Unknown warnings */

				snprintfcat(unk, sizeof(unk), ", #%02d", i + 1);

			} else {	/* Known warnings */

				if (strlen(warns) > 0) {

					/* For too long warnings (total) */
					snprintfcat(bitwarns, sizeof(bitwarns), ", #%02d", i + 1);

					/* For warnings (total) not too long */
					snprintfcat(warns, sizeof(warns), " %s", warn);

				} else {

					snprintf(bitwarns, sizeof(bitwarns), "Known (see log or manual) [bit: #%02d", i + 1);
					snprintf(warns, sizeof(warns), "%s", warn);

				}

			}
		}
	}

	/* There's some known warning, at least */
	if (strlen(warns) > 0) {

		/* We have both known and unknown warnings */
		if (strlen(unk) > 0) {

			/* Appending unknown ones to known ones; removing leading comma from unk - 'explicit' */
			snprintfcat(warns, sizeof(warns), " Unknown warnings [bit:%s]", unk+1);

			/* Appending unknown ones to known ones; removing leading comma from unk - 'cryptic' */
			snprintfcat(bitwarns, sizeof(bitwarns), "]; Unknown warnings [bit:%s]", unk+1);

		/* We have only known warnings */
		} else {

			snprintfcat(bitwarns, sizeof(bitwarns), "]");

		}

	/* We have only unknown warnings */
	} else if (strlen(unk) > 0) {

		/* Removing leading comma from unk */
		snprintf(warns, sizeof(warns), "Unknown warnings [bit:%s]", unk+1);
		strncpy(bitwarns, warns, sizeof(bitwarns));

	} else {

		/* Don't know what happened */
		upsdebugx(2, "%s: failed to process warnings", __func__);
		return -1;

	}

	/* If grand total of warnings doesn't exceed value of alarm (=ST_MAX_VALUE_LEN) minus some space (32) for other alarms.. */
	if ((ST_MAX_VALUE_LEN - 32) > strlen(warns)) {
		/* ..then be explicit.. */
		snprintfcat(value, valuelen, " %s", warns);
	/* ..otherwise.. */
	} else {
		/* ..be cryptic */
		snprintfcat(value, valuelen, " %s", bitwarns);
	}

	return 0;
}

/* Working mode reported by the UPS */
static int	voltronic_mode(item_t *item, char *value, const size_t valuelen)
{
	char	*status = NULL, *alarm = NULL, *buzzword = NULL;

	if (nut_debug_level > 3) {
		char	buf[SMALLBUF];
		size_t	buflen;

		/* Just in case item->value is somehow not NUL-terminated */
		memset(buf, 0, sizeof(buf));
		buflen = snprintf(buf, sizeof(buf), "%s", item->value);
		if (buflen > 0 && buf[buflen - 1] == '\n')
			buf[buflen - 1] = '\0';
		upsdebugx(4, "%s: entering, item->info_type='%s', full item->value='%s'%s",
			__func__, NUT_STRARG(item->info_type), buf,
			(buflen + 3 > sizeof(buf) ? " (truncated)" : "")
			);
	}

	switch (item->value[0])
	{
	case 'P':

		alarm = "UPS is going ON.";
		break;

	case 'S':

		status = "OFF";
		break;

	case 'Y':

		status = "BYPASS";
		break;

	case 'L':

		status = "OL";
		break;

	case 'B':

		status = "!OL";
		break;

	case 'T':

		status = "CAL";
		break;

	case 'F':

		alarm = "Fault reported by UPS.";
		break;

	case 'E':

		/* From man page: When input voltage/frequency are within acceptable
		 * range, the UPS will bypass voltage to output for energy saving.
		 * PFC and INVERTER are still active at this mode. */

		/* FIXME: Any char for advanced ECO mode? (PFC and INVERTER are off, the UPS will bypass voltage to output for energy saving)*/
		buzzword = "vendor:voltronic:ECO-inverter-on";
		upsdebugx(2, "%s: UPS is in ECO Mode", __func__);
		break;

	case 'C':

		/* From man page: When input frequency is within 40 Hz to 70 Hz,
		 * the UPS can be set at a constant output frequency, 50 Hz or 60 Hz.
		 * The UPS will still charge battery under this mode. */

		buzzword = "vendor:voltronic:freq-converter-on";
		upsdebugx(2, "%s: UPS is in Converter Mode", __func__);
		break;

	case 'D':

		alarm = "UPS is shutting down!";
		status = "FSD";
		break;

	default:

		upsdebugx(2, "%s: invalid reply from the UPS [%s]", __func__, item->value);
		return -1;

	}

	if (alarm && !strcasecmp(item->info_type, "ups.alarm")) {
		snprintf_dynamic(value, valuelen, item->dfl, "%s", alarm);
	} else if (status && !strcasecmp(item->info_type, "ups.status")) {
		snprintf_dynamic(value, valuelen, item->dfl, "%s", status);
	}

	if (buzzword)
		buzzmode_set(buzzword);

	upsdebugx(4, "%s: done, item->value[0]='%c' determined status='%s' alarm='%s' buzzword='%s'",
		__func__, item->value[0], NUT_STRARG(status), NUT_STRARG(alarm), NUT_STRARG(buzzword));

	return 0;
}

/* Process status bits */
static int	voltronic_status(item_t *item, char *value, const size_t valuelen)
{
	char	*val = "";

	if (strspn(item->value, "01") != strlen(item->value)) {
		upsdebugx(3, "%s: unexpected value %s@%d->%s",
			__func__, item->value, item->from, item->value);
		return -1;
	}

	switch (item->from)
	{
	case 63:	/* UPS Type - ups.type */

		{
			long	type = strtol(item->value, NULL, 10);

			if (!type)		/* 00 -> Offline */
				val = "offline";
			else if (type == 1)	/* 01 -> Line-interactive */
				val = "line-interactive";
			else if (type == 10)	/* 10 -> Online */
				val = "online";
			else {
				upsdebugx(2, "%s: invalid type [%s: %s]",
					__func__, item->info_type, item->value);
				return -1;
			}
		}

		break;

	case 65:	/* Utility Fail (Immediate) - ups.status */

		if (item->value[0] == '1')
			val = "!OL";
		else
			val = "OL";
		break;

	case 66:	/* Battery Low - ups.status */

		if (item->value[0] == '1')
			val = "LB";
		else
			val = "!LB";
		break;

	case 67:	/* Bypass/Boost or Buck Active - ups.{status,alarm} */

		if (item->value[0] == '1') {

			double	vi, vo;

			vi = strtod(dstate_getinfo("input.voltage"), NULL);
			vo = strtod(dstate_getinfo("output.voltage"), NULL);

			if (vo < 0.5 * vi) {
				upsdebugx(2, "%s: output voltage too low", __func__);
				return -1;
			}

			if (vo < 0.95 * vi) {
				val = "TRIM";
			} else if (vo < 1.05 * vi) {

				long	prot = strtol(dstate_getinfo("ups.firmware.aux")+1, NULL, 10);

				if (!prot || prot == 8) {	/* ups.alarm */

					if (!strcasecmp(item->info_type, "ups.alarm"))
						val = "UPS is in AVR Mode.";

				} else {	/* ups.status */

					if (!strcasecmp(item->info_type, "ups.status"))
						val = "BYPASS";

				}

			} else if (vo < 1.5 * vi) {
				val = "BOOST";
			} else {
				upsdebugx(2, "%s: output voltage too high", __func__);
				return -1;
			}

		}

		break;

	case 68:	/* UPS Fault - ups.alarm */

		if (item->value[0] == 1) {

			item_t	*faultitem;

			for (faultitem = voltronic_qx2nut; faultitem->info_type != NULL; faultitem++) {

				if (!faultitem->command)
					continue;

				if (!strcasecmp(faultitem->command, "QFS\r")) {
					faultitem->qxflags &= ~QX_FLAG_SKIP;
					break;
				}

			}

			val = "UPS Fault!";

		}

		break;

/*	case 69:	*//* unknown */
/*		break;*/

	case 70:	/* Test in Progress - ups.status */

		if (item->value[0] == '1')
			val = "CAL";
		else
			val = "!CAL";
		break;

	case 71:	/* Shutdown Active - ups.status */

		if (item->value[0] == '1')
			val = "FSD";
		else
			val = "!FSD";
		break;

	case 72:	/* Beeper status - ups.beeper.status */

		/* The UPS has the ability to enable/disable the alarm (from UPS capability) */
		if (alarm_control) {

			const char	*beeper = dstate_getinfo("ups.beeper.status");

			if (!beeper || strcasecmp(beeper, "disabled")) {

				if (item->value[0] == '0')	/* Beeper On */
					val = "enabled";
				else
					val = "muted";

			}

		/* The UPS lacks the ability to enable/disable the alarm (from UPS capability) */
		} else {

			if (item->value[0] == '0')	/* Beeper On */
				val = "enabled";
			else
				val = "disabled";

		}

		break;

/*	case 73:	*//* unknown */
/*		break;*/
/*	case 74:	*//* unknown */
/*		break;*/

	default:
		/* Don't know what happened */
		return -1;
	}

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

	return 0;
}

/* Output power factor */
static int	voltronic_output_powerfactor(item_t *item, char *value, const size_t valuelen)
{
	double	opf;

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

	/* UPS report a value expressed in % so -> output.powerfactor*100 e.g. opf = 0,8 -> ups = 80 */
	opf = strtod(item->value, NULL) * 0.01;

	snprintf_dynamic(value, valuelen, item->dfl, "%f", opf);

	return 0;
}

/* UPS serial number */
static int	voltronic_serial_numb(item_t *item, char *value, const size_t valuelen)
{
	/* If the UPS report a 00..0 serial we'll log it but we won't store it in device.serial */
	if (strspn(item->value, "0") == strlen(item->value)) {
		upslogx(LOG_INFO, "%s: UPS reported a non-significant serial [%s]", item->info_type, item->value);
		return -1;
	}

	snprintf_dynamic(value, valuelen, item->dfl, "%s", item->value);

	return 0;
}

/* Outlet status */
static int	voltronic_outlet(item_t *item, char *value, const size_t valuelen)
{
	const char	*status, *switchable;
	char		number = item->info_type[7],
			buf[SMALLBUF];
	item_t		*outlet_item;

	switch (item->value[0])
	{
	case '1':

		switchable = "yes";
		status = "on";
		break;

	case '0':

		switchable = "yes";
		status = "off";
		break;

	default:

		upsdebugx(2, "%s: invalid reply from the UPS [%s: %s]", __func__, item->info_type, item->value);
		return -1;

	}

	if (strstr(item->info_type, "switchable")) {
		snprintf_dynamic(value, valuelen, item->dfl, "%s", switchable);
	} else if (strstr(item->info_type, "status")) {
		snprintf_dynamic(value, valuelen, item->dfl, "%s", status);
	} else {
		/* Don't know what happened */
		return -1;
	}

	/* Unskip outlet.n.delay.shutdown */
	snprintf(buf, sizeof(buf), "outlet.%c.delay.shutdown", number);

	outlet_item = find_nut_info(buf, QX_FLAG_SEMI_STATIC, 0);

	/* Don't know what happened*/
	if (!outlet_item)
		return -1;

	outlet_item->qxflags &= ~QX_FLAG_SKIP;

	/* Unskip outlet.n.load.on */
	snprintf(buf, sizeof(buf), "outlet.%c.load.on", number);

	outlet_item = find_nut_info(buf, QX_FLAG_CMD, 0);

	/* Don't know what happened*/
	if (!outlet_item)
		return -1;

	outlet_item->qxflags &= ~QX_FLAG_SKIP;

	/* Unskip outlet.n.load.off */
	snprintf(buf, sizeof(buf), "outlet.%c.load.off", number);

	outlet_item = find_nut_info(buf, QX_FLAG_CMD, 0);

	/* Don't know what happened*/
	if (!outlet_item)
		return -1;

	outlet_item->qxflags &= ~QX_FLAG_SKIP;

	return 0;
}

/* Outlet delay time */
static int	voltronic_outlet_delay(item_t *item, char *value, const size_t valuelen)
{
	char	number = item->info_type[7],
		buf[SMALLBUF];
	double	val;
	item_t	*setvar_item;

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

	/* UPS reports minutes, NUT expects seconds */
	val = strtod(item->value, NULL) * 60;

	snprintf_dynamic(value, valuelen, item->dfl, "%f", val);

	/* Unskip outlet.n.delay.shutdown setvar */
	snprintf(buf, sizeof(buf), "outlet.%c.delay.shutdown", number);

	setvar_item = find_nut_info(buf, QX_FLAG_SETVAR, 0);

	/* Don't know what happened*/
	if (!setvar_item)
		return -1;

	setvar_item->qxflags &= ~QX_FLAG_SKIP;

	return 0;
}

/* *SETVAR* Outlet delay time */
static int	voltronic_outlet_delay_set(item_t *item, char *value, const size_t valuelen)
{
	long	delay = strtol(value, NULL, 10);

	if ((delay/60) > INT_MAX) {
		upsdebugx(2, "%s: invalid delay %ld sec set for UPS [%s]",
			__func__, delay, item->value);
		return -1;
	}

	/* From seconds to minute */
	delay = delay / 60;

	snprintf_dynamic(value, valuelen, item->command, "%d", (int)delay);

	return 0;
}

/* Type of battery */
static int	voltronic_p31b(item_t *item, char *value, const size_t valuelen)
{
	long	val;

	if ((item->value[0] != '0') || (strspn(item->value+1, "012") != 1)) {
		upsdebugx(2, "%s: invalid battery type reported by the UPS [%s]",
			__func__, item->value);
		return -1;
	}

	val = strtol(item->value, NULL, 10);

	if (val < 0 || (uintmax_t)val > SIZE_MAX) {
		upsdebugx(2, "%s: invalid battery type reported by the UPS [%s]",
			__func__, item->value);
		return -1;
	}

	snprintf_dynamic(value, valuelen, item->dfl, "%s", item->info_rw[(size_t)val].value);

	return 0;
}

/* *SETVAR* Type of battery */
static int	voltronic_p31b_set(item_t *item, char *value, const size_t valuelen)
{
	int	i;

	if (!item->info_rw)
		return -1;

	for (i = 0; strlen(item->info_rw[i].value) > 0; i++) {

		if (!strcasecmp(item->info_rw[i].value, value))
			break;

	}

	/* At this point value should already be checked against enum so this shouldn't happen.. however.. */
	if (!strlen(item->info_rw[i].value)) {
		upslogx(LOG_ERR, "%s: value [%s] out of range", item->info_type, value);
		return -1;
	}

	snprintf(value, valuelen, "%d", i);

	return voltronic_process_setvar(item, value, valuelen);
}

/* *NONUT* Actual device grid working range type for P31 UPSes */
static int	voltronic_p31g(item_t *item, char *value, const size_t valuelen)
{
	long	val;

	if ((item->value[0] != '0') || (strspn(item->value+1, "01") != 1)) {
		upsdebugx(2, "%s: invalid device grid working range reported by the UPS [%s]",
			__func__, item->value);
		return -1;
	}

	val = strtol(item->value, NULL, 10);

	if (val < 0 || (uintmax_t)val > SIZE_MAX) {
		upsdebugx(2, "%s: invalid device grid working range reported by the UPS [%s]",
			__func__, item->value);
		return -1;
	}

	snprintf_dynamic(value, valuelen, item->dfl, "%s", item->info_rw[(size_t)val].value);

	work_range_type = val;

	return 0;
}

/* *SETVAR/NONUT* Device grid working range type for P31 UPSes */
static int	voltronic_p31g_set(item_t *item, char *value, const size_t valuelen)
{
	int	i;

	if (!item->info_rw)
		return -1;

	for (i = 0; strlen(item->info_rw[i].value) > 0; i++) {

		if (!strcasecmp(item->info_rw[i].value, value))
			break;

	}

	/* At this point value should have been already checked against enum so this shouldn't happen.. however.. */
	if (!strlen(item->info_rw[i].value)) {
		upslogx(LOG_ERR, "%s: value [%s] out of range", item->info_type, value);
		return -1;
	}

	if (i == work_range_type) {
		upslogx(LOG_INFO, "%s is already set to %s", item->info_type, item->info_rw[i].value);
		return -1;
	}

	snprintf(value, valuelen, "%d", i);

	return voltronic_process_setvar(item, value, valuelen);
}

/* *NONUT* UPS actual input/output phase angles */
static int	voltronic_phase(item_t *item, char *value, const size_t valuelen)
{
	long	angle;

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

	angle = strtol(item->value, NULL, 10);

	if (!strcasecmp(item->info_type, "output_phase_angle")) {

		output_phase_angle = angle;

		/* User-provided value to change.. */
		if (getval(item->info_type)) {

			item_t	*unskip;

			/* Unskip output_phase_angle setvar */
			unskip = find_nut_info(item->info_type, QX_FLAG_SETVAR, 0);

			/* Don't know what happened */
			if (!unskip)
				return -1;

			unskip->qxflags &= ~QX_FLAG_SKIP;

		}

	}

	if (angle < 0 || angle > INT_MAX) {
		upsdebugx(2, "%s: phase angle out of range [%s: %ld]",
			__func__, item->value, angle);
		return -1;
	}

	snprintf_dynamic(value, valuelen, item->dfl, "%d", (int)angle);

	return 0;
}

/* *SETVAR/NONUT* Output phase angle */
static int	voltronic_phase_set(item_t *item, char *value, const size_t valuelen)
{
	int	i;

	if (!item->info_rw)
		return -1;

	for (i = 0; strlen(item->info_rw[i].value) > 0; i++) {

		if (!strcasecmp(item->info_rw[i].value, value))
			break;

	}

	/* At this point value should have been already checked against enum so this shouldn't happen.. however.. */
	if (!strlen(item->info_rw[i].value)) {
		upslogx(LOG_ERR, "%s: value [%s] out of range", item->info_type, value);
		return -1;
	}

	if (strtol(item->info_rw[i].value, NULL, 10) == output_phase_angle) {
		upslogx(LOG_INFO, "%s is already set to %s", item->info_type, item->info_rw[i].value);
		return -1;
	}

	snprintf(value, valuelen, "%d", i);

	return voltronic_process_setvar(item, value, valuelen);
}

/* *NONUT* UPS is master/slave in a system of UPSes in parallel */
static int	voltronic_parallel(item_t *item, char *value, const size_t valuelen)
{
	char	*type;

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

	/* 001 for master UPS, 002 and 003 for slave UPSes */
	switch (strtol(item->value, NULL, 10))
	{
	case 1:

		type = "master";
		break;

	case 2:
	case 3:

		type = "slave";
		break;

	default:

		upsdebugx(2, "%s: invalid reply from the UPS [%s]", __func__, item->value);
		return -1;

	}

	snprintf(value, valuelen, "This UPS is *%s* in a system of UPSes in parallel", type);

	return 0;
}


/* == Subdriver interface == */
subdriver_t	voltronic_subdriver = {
	VOLTRONIC_VERSION,
	voltronic_claim,
	voltronic_qx2nut,
	NULL,
	NULL,
	voltronic_makevartable,
	"ACK",
	"(NAK\r",
#ifdef TESTING
	voltronic_testing,
#endif	/* TESTING */
};
