/*
 bcmxcp.c - driver for powerware UPS

 Total rewrite of bcmxcp.c (nut ver-1.4.3)
 * Copyright (c) 2002, Martin Schroeder *
 * emes -at- geomer.de *
 * All rights reserved.*

 Copyright (C)
   2004 Kjell Claesson <kjell.claesson-at-epost.tidanet.se>
   2004 Tore Ørpetveit <tore-at-orpetveit.net>
   2011 - 2015 Arnaud Quette <ArnaudQuette@Eaton.com>

 Thanks to Tore Ørpetveit <tore-at-orpetveit.net> that sent me the
 manuals for bcm/xcp.

 And to Fabio Di Niro <fabio.diniro@email.it> and his metasys module.
 It influenced the layout of this driver.

 Modified for USB by Wolfgang Ocker <weo@weo1.de>

 ojw0000 2007Apr5 Oliver Wilcock - modified to control individual load segments (outlet.2.shutdown.return) on Powerware PW5125.

 Modified to support setvar for outlet.n.delay.start by Rich Wrenn (RFW) 9-3-11.
 Modified to support setvar for outlet.n.delay.shutdown by Arnaud Quette, 9-12-11

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

TODO List:

        Extend the parsing of the Standard ID Block, to read:

                Config Block Length: (High priority)
                Give information if config block is
                present, and how long it is, if it exist.
                If config block exist, read the config block and parse the
                'Length of the Extended Limits Configuration Block' for
                extended configuration commands

                Statistic map Size: (Low priority)
                May be used to se if there is a Statistic Map.
                It holds data on the utility power quality for
                the past month and since last reset. Number of
                times on battery and how long. Up time and utility
                frequency deviation. (Only larger ups'es)

                Size of Alarm History Log: (Low priority)
                See if it have any alarm history block and enable
                command to dump it.

                Maximum Supported Command Length: ( Med. to High priority)
                Give info about the ups receive buffer size.

                Size of Alarm Block: ( Med. to High priority)
                Make a smarter handling of the Active alarm's if we know the length
                of the Active Alarm Block. Don't need the long loop to parse the
                alarm's. Maybe use another way to set up the alarm struct in the
                'init_alarm_map'.

        Parse 'Communication Capabilities Block' ( Low priority)
                Get info of the connected ports ID, number of baud rates,
                command and respnse length.

        Parse 'Communication Port List Block': ( Low priority)
                This block gives info about the communication ports. Some ups'es
                have multiple comport's, and use one port for eatch load segment.
                In this block it is possible to get:
                Number of ports. (In this List)
                This Comport id (Which Comm Port is reporting this block.)
                Comport id (Id for eatch port listed. The first comport ID=1)
                Baudrate of the listed port.
                Serial config.
                Port usage:
                        What this Comm Port is being used for:
                        0 = Unknown usage, No communication occurring.
                        1 = Undefined / Unknown communication occurring
                        2 = Waiting to communicate with a UPS
                        3 = Communication established with a UPS
                        4 = Waiting to communicate with software or adapter
                        5 = Communication established software (e.g., LanSafe)
                                or adapter (e.g., ConnectUPS)
                        6 = Communicating with a Display Device
                        7 = Multi-drop Serial channel
                        8 = Communicating with an Outlet Controller
                Number of outlets. (Number of Outlets "assigned to" (controlled by) this Comm Port)
                Outlet number. (Each assigned Outlet is listed (1-64))

                'Set outlet parameter command (0x97)' to alter the delay
                settings or turn the outlet on or off with a delay (0 - 32767 seconds)


        Rewrite some parts of the driver, to minimise code duplication. (Like the instant commands)

        Implement support for Password Authorization (XCP spec, §4.3.2)

        Complete support for settable variables (upsh.setvar)
*/


#include "main.h"
#include "nut_float.h"	/* For ldexp(), FLT_MAX */
#include "nut_stdint.h"	/* for uint8_t, uint16_t, uint32_t, ... */
#include "bcmxcp_io.h"
#include "bcmxcp.h"

#define DRIVER_NAME	"BCMXCP UPS driver"
#define DRIVER_VERSION	"0.39"

#define MAX_NUT_NAME_LENGTH 128
#define NUT_OUTLET_POSITION   7

/* driver description structure */
upsdrv_info_t upsdrv_info = {
	DRIVER_NAME,
	DRIVER_VERSION,
	"Martin Schroeder <emes@geomer.de>\n" \
	"Kjell Claesson <kjell.claesson@epost.tidanet.se>\n" \
	"Tore Ørpetveit <tore@orpetveit.net>\n" \
	"Arnaud Quette <ArnaudQuette@Eaton.com>\n" \
	"Wolfgang Ocker <weo@weo1.de>\n" \
	"Oliver Wilcock\n" \
	"Prachi Gandhi <prachisgandhi@eaton.com>\n" \
	"Alf Høgemark <alf@i100>\n" \
	"Gavrilov Igor",
	DRV_STABLE,
	{ &comm_upsdrv_info, NULL }
};

static uint16_t get_word(const unsigned char*);
static uint32_t get_long(const unsigned char*);
static float get_float(const unsigned char *data);
static void init_command_map(void);
static void init_meter_map(void);
static void init_alarm_map(void);
static bool_t init_command(int size);
static void init_config(void);
static void init_limit(void);
static void init_ext_vars(void);
static void init_topology(void);
static void init_ups_meter_map(const unsigned char *map, unsigned char len);
static void init_ups_alarm_map(const unsigned char *map, unsigned char len);
static bool_t set_alarm_support_in_alarm_map(const unsigned char *map, const unsigned int mapIndex, const unsigned int bitmask, const unsigned int alarmMapIndex, const unsigned int alarmBlockIndex);
static void decode_meter_map_entry(const unsigned char *entry, const unsigned char format, char* value);
static unsigned char init_outlet(unsigned char len);
static void init_system_test_capabilities(void);
static int instcmd(const char *cmdname, const char *extra);
static int setvar(const char *varname, const char *val);
static int decode_instcmd_exec(const ssize_t res, const unsigned char exec_status, const char *cmdname, const char *success_msg);
static int decode_setvar_exec(const ssize_t res, const unsigned char exec_status, const char *cmdname, const char *success_msg);
static float calculate_ups_load(const unsigned char *data);

static const char *nut_find_infoval(info_lkp_t *xcp2info, const double value, const bool_t debug_output_nonexisting);

/* static const char *FreqTol[3] = {"+/-2%", "+/-5%", "+/-7"}; */
static const char *ABMStatus[4] = {
	"charging",
	"discharging",
	"floating",
	"resting"
	};
static const char *OutletStatus[9] = {
	"unknown",
	"on/closed",
	"off/open",
	"on with pending",
	"off with pending",
	"unknown",
	"unknown",
	"failed and closed",
	"failed and open"
	};

/* Standard Authorization Block */
static unsigned char AUTHOR[4] = {0xCF, 0x69, 0xE8, 0xD5};
static int nphases = 0;
static uint16_t outlet_block_len = 0;
static const char *cpu_name[5] = {
	"Cont:",
	"Inve:",
	"Rect:",
	"Netw:",
	"Disp:"
	};
static const char *horn_stat[3] = {
	"disabled",
	"enabled",
	"muted"
	};

/* Battery test results */
static info_lkp_t batt_test_info[] = {
	{ 0, "No test initiated", NULL, NULL },
	{ 1, "In progress", NULL, NULL },
	{ 2, "Done and passed", NULL, NULL },
	{ 3, "Aborted", NULL, NULL },
	{ 4, "Done and error", NULL, NULL },
	{ 5, "Test scheduled", NULL, NULL },
	/* Not sure about the meaning of the below ones! */
	{ 6, NULL, NULL, NULL }, /* The string was present but it has now been removed */
	{ 7, NULL, NULL, NULL }, /* The string was not installed at the last power up */
	{ 0, NULL, NULL, NULL }
};

/* Topology map results */
static info_lkp_t topology_info[] = {
	{ BCMXCP_TOPOLOGY_OFFLINE_SWITCHER_1P, "Off-line switcher, Single Phase", NULL, NULL },
	{ BCMXCP_TOPOLOGY_LINEINT_UPS_1P, "Line-Interactive UPS, Single Phase", NULL, NULL },
	{ BCMXCP_TOPOLOGY_LINEINT_UPS_2P, "Line-Interactive UPS, Two Phase", NULL, NULL },
	{ BCMXCP_TOPOLOGY_LINEINT_UPS_3P, "Line-Interactive UPS, Three Phase", NULL, NULL },
	{ BCMXCP_TOPOLOGY_DUAL_AC_ONLINE_UPS_1P, "Dual AC Input, On-Line UPS, Single Phase", NULL, NULL },
	{ BCMXCP_TOPOLOGY_DUAL_AC_ONLINE_UPS_2P, "Dual AC Input, On-Line UPS, Two Phase", NULL, NULL },
	{ BCMXCP_TOPOLOGY_DUAL_AC_ONLINE_UPS_3P, "Dual AC Input, On-Line UPS, Three Phase", NULL, NULL },
	{ BCMXCP_TOPOLOGY_ONLINE_UPS_1P, "On-Line UPS, Single Phase", NULL, NULL },
	{ BCMXCP_TOPOLOGY_ONLINE_UPS_2P, "On-Line UPS, Two Phase", NULL, NULL },
	{ BCMXCP_TOPOLOGY_ONLINE_UPS_3P, "On-Line UPS, Three Phase", NULL, NULL },
	{ BCMXCP_TOPOLOGY_PARA_REDUND_ONLINE_UPS_1P, "Parallel Redundant On-Line UPS, Single Phase", NULL, NULL },
	{ BCMXCP_TOPOLOGY_PARA_REDUND_ONLINE_UPS_2P, "Parallel Redundant On-Line UPS, Two Phase", NULL, NULL },
	{ BCMXCP_TOPOLOGY_PARA_REDUND_ONLINE_UPS_3P, "Parallel Redundant On-Line UPS, Three Phase", NULL, NULL },
	{ BCMXCP_TOPOLOGY_PARA_CAPACITY_ONLINE_UPS_1P, "Parallel for Capacity On-Line UPS, Single Phase", NULL, NULL },
	{ BCMXCP_TOPOLOGY_PARA_CAPACITY_ONLINE_UPS_2P, "Parallel for Capacity On-Line UPS, Two Phase", NULL, NULL },
	{ BCMXCP_TOPOLOGY_PARA_CAPACITY_ONLINE_UPS_3P, "Parallel for Capacity On-Line UPS, Three Phase", NULL, NULL },
	{ BCMXCP_TOPOLOGY_SYSTEM_BYPASS_MODULE_3P, "System Bypass Module, Three Phase", NULL, NULL },
	{ BCMXCP_TOPOLOGY_HOT_TIE_CABINET_3P, "Hot-Tie Cabinet, Three Phase", NULL, NULL },
	{ BCMXCP_TOPOLOGY_OUTLET_CONTROLLER_1P, "Outlet Controller, Single Phase", NULL, NULL },
	{ BCMXCP_TOPOLOGY_DUAL_AC_STATIC_SWITCH_3P, "Dual AC Input Static Switch Module, 3 Phase", NULL, NULL },
	{ 0, NULL, NULL, NULL }
};

/* Command map results */
static info_lkp_t command_map_info[] = {
	{ PW_INIT_BAT_TEST, "test.battery.start", NULL, NULL },
	{ PW_LOAD_OFF_RESTART, "shutdown.return", NULL, NULL },
	{ PW_UPS_OFF, "shutdown.stayoff", NULL, NULL },
	{ PW_UPS_ON, "load.on", NULL, NULL },
	{ PW_GO_TO_BYPASS, "bypass.start", NULL, NULL },
	{ 0, NULL, NULL, NULL }
};

/* System test capabilities results */
static info_lkp_t system_test_info[] = {
	{ PW_SYS_TEST_GENERAL, "test.system.start", NULL, NULL },
/*	{ PW_SYS_TEST_SCHEDULE_BATTERY_COMMISSION, "test.battery.start.delayed", NULL, NULL }, */
/*	{ PW_SYS_TEST_ALTERNATE_AC_INPUT, "test.alternate_acinput.start", NULL, NULL }, */
	{ PW_SYS_TEST_FLASH_LIGHTS, "test.panel.start", NULL, NULL },
	{ 0, NULL, NULL, NULL }
};

/* allocate storage for shared variables (extern in bcmxcp.h) */
BCMXCP_COMMAND_MAP_ENTRY_t
	bcmxcp_command_map[BCMXCP_COMMAND_MAP_MAX];
BCMXCP_METER_MAP_ENTRY_t
	bcmxcp_meter_map[BCMXCP_METER_MAP_MAX];
BCMXCP_ALARM_MAP_ENTRY_t
	bcmxcp_alarm_map[BCMXCP_ALARM_MAP_MAX];
BCMXCP_STATUS_t
	bcmxcp_status;


/* get_word function from nut driver metasys.c */
uint16_t get_word(const unsigned char *buffer) /* return a short integer reading a word in the supplied buffer */
{
	unsigned char a, b;
	uint16_t result;

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

	return result;
}

/* get_long function from nut driver metasys.c for meter readings*/
uint32_t get_long(const unsigned char *buffer) /* return a long integer reading 4 bytes in the supplied buffer.*/
{
	unsigned char a, b, c, d;
	uint32_t result;

	a = buffer[0];
	b = buffer[1];
	c = buffer[2];
	d = buffer[3];
	result = (256*256*256*d) + (256*256*c) + (256*b) + a;

	return result;
}

/* get_float funktion for convering IEEE-754 to float */
float get_float(const unsigned char *data)
{
	int s, e;
	unsigned long src;
	long f;

	src = ((unsigned long)data[3] << 24) |
	((unsigned long)data[2] << 16) |
	((unsigned long)data[1] << 8) |
	((unsigned long)data[0]);

	s = (src & 0x80000000UL) >> 31;
	e = (src & 0x7F800000UL) >> 23;
	f = (src & 0x007FFFFFUL);

	if (e == 255 && f != 0)
	{
		/* NaN (Not a Number) */
		return FLT_MAX;
	}

	if (e == 255 && f == 0 && s == 1)
	{
		/* Negative infinity */
		return -FLT_MAX;
	}

	if (e == 255 && f == 0 && s == 0)
	{
		/* Positive infinity */
		return FLT_MAX;
	}

	if (e > 0 && e < 255)
	{
		/* Normal number */
		f += 0x00800000UL;
		if (s) f = -f;
		return ldexp(f, e - 150);
	}

	if (e == 0 && f != 0)
	{
		/* Denormal number */
		if (s) f = -f;
		return ldexp(f, -149);
	}

	if (e == 0 && f == 0 && (s == 1 || s == 0))
	{
		/* Zero */
		return 0;
	}

	/* Never happens */
	upslogx(LOG_ERR, "s = %d, e = %d, f = %ld", s, e, f);
	return 0;
}

/* lightweight function to calculate the 8-bit
 * two's complement checksum of buf, using XCP data length (including header)
 * the result must be 0 for the sequence data to be valid */
int checksum_test(const unsigned char *buf)
{
	unsigned char checksum = 0;
	int i, length;

	/* buf[2] is the length of the XCP frame ; add 5 for the header */
	length = (int)(buf[2]) + 5;

	for (i = 0; i < length; i++) {
		checksum += buf[i];
	}
	/* Compute the 8-bit, Two's Complement checksum now and return it */
	checksum = ((0x100 - checksum) & 0xFF);
	return (checksum == 0);
}

unsigned char calc_checksum(const unsigned char *buf)
{
	unsigned char c;
	int i;

	c = 0;
	for (i = 0; i < 2 + buf[1]; i++)
		c -= buf[i];

	return c;
}

void init_command_map(void)
{
	int i = 0;

	/* Clean entire map */
	memset(&bcmxcp_command_map, 0, sizeof(BCMXCP_COMMAND_MAP_ENTRY_t) * BCMXCP_COMMAND_MAP_MAX);

	/* Set all command descriptions */
	bcmxcp_command_map[PW_ID_BLOCK_REQ].command_desc = "PW_ID_BLOCK_REQ";
	bcmxcp_command_map[PW_EVENT_HISTORY_LOG_REQ].command_desc = "PW_EVENT_HISTORY_LOG_REQ";
	bcmxcp_command_map[PW_STATUS_REQ].command_desc = "PW_STATUS_REQ";
	bcmxcp_command_map[PW_METER_BLOCK_REQ].command_desc = "PW_METER_BLOCK_REQ";
	bcmxcp_command_map[PW_CUR_ALARM_REQ].command_desc = "PW_CUR_ALARM_REQ";
	bcmxcp_command_map[PW_CONFIG_BLOCK_REQ].command_desc = "PW_CONFIG_BLOCK_REQ";
	bcmxcp_command_map[PW_UTILITY_STATISTICS_BLOCK_REQ].command_desc = "PW_UTILITY_STATISTICS_BLOCK_REQ";
	bcmxcp_command_map[PW_WAVEFORM_BLOCK_REQ].command_desc = "PW_WAVEFORM_BLOCK_REQ";
	bcmxcp_command_map[PW_BATTERY_REQ].command_desc = "PW_BATTERY_REQ";
	bcmxcp_command_map[PW_LIMIT_BLOCK_REQ].command_desc = "PW_LIMIT_BLOCK_REQ";
	bcmxcp_command_map[PW_TEST_RESULT_REQ].command_desc = "PW_TEST_RESULT_REQ";
	bcmxcp_command_map[PW_COMMAND_LIST_REQ].command_desc = "PW_COMMAND_LIST_REQ";
	bcmxcp_command_map[PW_OUT_MON_BLOCK_REQ].command_desc = "PW_OUT_MON_BLOCK_REQ";
	bcmxcp_command_map[PW_COM_CAP_REQ].command_desc = "PW_COM_CAP_REQ";
	bcmxcp_command_map[PW_UPS_TOP_DATA_REQ].command_desc = "PW_UPS_TOP_DATA_REQ";
	bcmxcp_command_map[PW_COM_PORT_LIST_BLOCK_REQ].command_desc = "PW_COM_PORT_LIST_BLOCK_REQ";
	bcmxcp_command_map[PW_REQUEST_SCRATCHPAD_DATA_REQ].command_desc = "PW_REQUEST_SCRATCHPAD_DATA_REQ";
	bcmxcp_command_map[PW_GO_TO_BYPASS].command_desc = "PW_GO_TO_BYPASS";
	bcmxcp_command_map[PW_UPS_ON].command_desc = "PW_UPS_ON";
	bcmxcp_command_map[PW_LOAD_OFF_RESTART].command_desc = "PW_LOAD_OFF_RESTART";
	bcmxcp_command_map[PW_UPS_OFF].command_desc = "PW_UPS_OFF";
	bcmxcp_command_map[PW_DECREMENT_OUTPUT_VOLTAGE].command_desc = "PW_DECREMENT_OUTPUT_VOLTAGE";
	bcmxcp_command_map[PW_INCREMENT_OUTPUT_VOLTAGE].command_desc = "PW_INCREMENT_OUTPUT_VOLTAGE";
	bcmxcp_command_map[PW_SET_TIME_AND_DATE].command_desc = "PW_SET_TIME_AND_DATE";
	bcmxcp_command_map[PW_UPS_ON_TIME].command_desc = "PW_UPS_ON_TIME";
	bcmxcp_command_map[PW_UPS_ON_AT_TIME].command_desc = "PW_UPS_ON_AT_TIME";
	bcmxcp_command_map[PW_UPS_OFF_TIME].command_desc = "PW_UPS_OFF_TIME";
	bcmxcp_command_map[PW_UPS_OFF_AT_TIME].command_desc = "PW_UPS_OFF_AT_TIME";
	bcmxcp_command_map[PW_SET_CONF_COMMAND].command_desc = "PW_SET_CONF_COMMAND";
	bcmxcp_command_map[PW_SET_OUTLET_COMMAND].command_desc = "PW_SET_OUTLET_COMMAND";
	bcmxcp_command_map[PW_SET_COM_COMMAND].command_desc = "PW_SET_COM_COMMAND";
	bcmxcp_command_map[PW_SET_SCRATHPAD_SECTOR].command_desc = "PW_SET_SCRATHPAD_SECTOR";
	bcmxcp_command_map[PW_SET_POWER_STRATEGY].command_desc = "PW_SET_POWER_STRATEGY";
	bcmxcp_command_map[PW_SET_REQ_ONLY_MODE].command_desc = "PW_SET_REQ_ONLY_MODE";
	bcmxcp_command_map[PW_SET_UNREQUESTED_MODE].command_desc = "PW_SET_UNREQUESTED_MODE";
	bcmxcp_command_map[PW_INIT_BAT_TEST].command_desc = "PW_INIT_BAT_TEST";
	bcmxcp_command_map[PW_INIT_SYS_TEST].command_desc = "PW_INIT_SYS_TEST";
	bcmxcp_command_map[PW_SELECT_SUBMODULE].command_desc = "PW_SELECT_SUBMODULE";
	bcmxcp_command_map[PW_AUTHORIZATION_CODE].command_desc = "PW_AUTHORIZATION_CODE";

	for (i = 0; i < BCMXCP_COMMAND_MAP_MAX; i++) {
		bcmxcp_command_map[i].command_byte = 0;
	}
}

void init_meter_map(void)
{
	/* Clean entire map */
	memset(&bcmxcp_meter_map, 0, sizeof(BCMXCP_METER_MAP_ENTRY_t) * BCMXCP_METER_MAP_MAX);

	/* Set all corresponding mappings NUT <-> BCM/XCP */
	bcmxcp_meter_map[BCMXCP_METER_MAP_OUTPUT_VOLTS_AB].nut_entity = "output.L1-L2.voltage";
	bcmxcp_meter_map[BCMXCP_METER_MAP_OUTPUT_VOLTS_BC].nut_entity = "output.L2-L3.voltage";
	bcmxcp_meter_map[BCMXCP_METER_MAP_OUTPUT_VOLTS_CA].nut_entity = "output.L3-L1.voltage";
	bcmxcp_meter_map[BCMXCP_METER_MAP_INPUT_VOLTS_AB].nut_entity = "input.L1-L2.voltage";
	bcmxcp_meter_map[BCMXCP_METER_MAP_INPUT_VOLTS_BC].nut_entity = "input.L2-L3.voltage";
	bcmxcp_meter_map[BCMXCP_METER_MAP_INPUT_VOLTS_CA].nut_entity = "input.L3-L1.voltage";
	bcmxcp_meter_map[BCMXCP_METER_MAP_INPUT_CURRENT_PHASE_B].nut_entity = "input.L2.current";
	bcmxcp_meter_map[BCMXCP_METER_MAP_INPUT_CURRENT_PHASE_C].nut_entity = "input.L3.current";
	bcmxcp_meter_map[BCMXCP_METER_MAP_INPUT_WATTS].nut_entity = "input.realpower";
	bcmxcp_meter_map[BCMXCP_METER_MAP_OUTPUT_VA].nut_entity = "ups.power";
	bcmxcp_meter_map[BCMXCP_METER_MAP_INPUT_VA].nut_entity = "input.power";
	bcmxcp_meter_map[BCMXCP_METER_MAP_OUTPUT_POWER_FACTOR].nut_entity = "output.powerfactor";
	bcmxcp_meter_map[BCMXCP_METER_MAP_INPUT_POWER_FACTOR].nut_entity = "input.powerfactor";

	if (nphases == 1) {
		bcmxcp_meter_map[BCMXCP_METER_MAP_INPUT_CURRENT_PHASE_A].nut_entity = "input.current";
		bcmxcp_meter_map[BCMXCP_METER_MAP_PERCENT_LOAD_PHASE_A].nut_entity = "ups.load"; /* TODO: Decide on corresponding three-phase variable mapping. */
		bcmxcp_meter_map[BCMXCP_METER_MAP_BYPASS_VOLTS_PHASE_A].nut_entity = "input.bypass.voltage";
		bcmxcp_meter_map[BCMXCP_METER_MAP_INPUT_VOLTS_PHASE_A].nut_entity = "input.voltage";
		bcmxcp_meter_map[BCMXCP_METER_MAP_LOAD_CURRENT_PHASE_A].nut_entity = "output.current";
		bcmxcp_meter_map[BCMXCP_METER_MAP_LOAD_CURRENT_PHASE_A_BAR_CHART].nut_entity = "output.current.nominal";
		bcmxcp_meter_map[BCMXCP_METER_MAP_OUTPUT_VOLTS_A].nut_entity = "output.voltage";
		bcmxcp_meter_map[BCMXCP_METER_MAP_OUTPUT_WATTS].nut_entity = "ups.realpower";
	} else {
		bcmxcp_meter_map[BCMXCP_METER_MAP_INPUT_CURRENT_PHASE_A].nut_entity = "input.L1.current";
		bcmxcp_meter_map[BCMXCP_METER_MAP_PERCENT_LOAD_PHASE_A].nut_entity = "output.L1.power.percent";
		bcmxcp_meter_map[BCMXCP_METER_MAP_PERCENT_LOAD_PHASE_B].nut_entity = "output.L2.power.percent";
		bcmxcp_meter_map[BCMXCP_METER_MAP_PERCENT_LOAD_PHASE_C].nut_entity = "output.L3.power.percent";
		bcmxcp_meter_map[BCMXCP_METER_MAP_OUTPUT_VA_PHASE_A].nut_entity = "output.L1.power";
		bcmxcp_meter_map[BCMXCP_METER_MAP_OUTPUT_VA_PHASE_B].nut_entity = "output.L2.power";
		bcmxcp_meter_map[BCMXCP_METER_MAP_OUTPUT_VA_PHASE_C].nut_entity = "output.L3.power";
		bcmxcp_meter_map[BCMXCP_METER_MAP_BYPASS_VOLTS_PHASE_A].nut_entity = "input.bypass.L1-N.voltage";
		bcmxcp_meter_map[BCMXCP_METER_MAP_BYPASS_VOLTS_PHASE_B].nut_entity = "input.bypass.L2-N.voltage";
		bcmxcp_meter_map[BCMXCP_METER_MAP_BYPASS_VOLTS_PHASE_C].nut_entity = "input.bypass.L3-N.voltage";
		bcmxcp_meter_map[BCMXCP_METER_MAP_INPUT_VOLTS_PHASE_A].nut_entity = "input.L1-N.voltage";
		bcmxcp_meter_map[BCMXCP_METER_MAP_LOAD_CURRENT_PHASE_A].nut_entity = "output.L1.current";
		bcmxcp_meter_map[BCMXCP_METER_MAP_LOAD_CURRENT_PHASE_A_BAR_CHART].nut_entity = "output.L1.current.nominal";
		bcmxcp_meter_map[BCMXCP_METER_MAP_OUTPUT_VOLTS_A].nut_entity = "output.L1-N.voltage";
	}
	bcmxcp_meter_map[BCMXCP_METER_MAP_OUTPUT_FREQUENCY].nut_entity = "output.frequency";
	bcmxcp_meter_map[BCMXCP_METER_MAP_INPUT_FREQUENCY].nut_entity = "input.frequency";
	bcmxcp_meter_map[BCMXCP_METER_MAP_BYPASS_FREQUENCY].nut_entity = "input.bypass.frequency";
	bcmxcp_meter_map[BCMXCP_METER_MAP_BATTERY_CURRENT].nut_entity = "battery.current";
	bcmxcp_meter_map[BCMXCP_METER_MAP_BATTERY_VOLTAGE].nut_entity = "battery.voltage";
	bcmxcp_meter_map[BCMXCP_METER_MAP_PERCENT_BATTERY_LEFT].nut_entity = "battery.charge";
	bcmxcp_meter_map[BCMXCP_METER_MAP_BATTERY_TIME_REMAINING].nut_entity = "battery.runtime";
	bcmxcp_meter_map[BCMXCP_METER_MAP_BATTERY_DCUV_BAR_CHART].nut_entity = "battery.voltage.low";
	bcmxcp_meter_map[BCMXCP_METER_MAP_LOW_BATTERY_WARNING_V_BAR_CHART].nut_entity = "battery.charge.low";
	bcmxcp_meter_map[BCMXCP_METER_MAP_BATTERY_DISCHARGING_CURRENT_BAR_CHART].nut_entity = "battery.current.total";
	bcmxcp_meter_map[BCMXCP_METER_MAP_INPUT_VOLTS_PHASE_B].nut_entity = "input.L2-N.voltage";
	bcmxcp_meter_map[BCMXCP_METER_MAP_INPUT_VOLTS_PHASE_C].nut_entity = "input.L3-N.voltage";
	bcmxcp_meter_map[BCMXCP_METER_MAP_AMBIENT_TEMPERATURE].nut_entity = "ambient.temperature";
	bcmxcp_meter_map[BCMXCP_METER_MAP_HEATSINK_TEMPERATURE].nut_entity = "ups.temperature";
	bcmxcp_meter_map[BCMXCP_METER_MAP_POWER_SUPPLY_TEMPERATURE].nut_entity = "ambient.1.temperature";
	bcmxcp_meter_map[BCMXCP_METER_MAP_LOAD_CURRENT_PHASE_B].nut_entity = "output.L2.current";
	bcmxcp_meter_map[BCMXCP_METER_MAP_LOAD_CURRENT_PHASE_C].nut_entity = "output.L3.current";
	bcmxcp_meter_map[BCMXCP_METER_MAP_LOAD_CURRENT_PHASE_B_BAR_CHART].nut_entity = "output.L2.current.nominal";
	bcmxcp_meter_map[BCMXCP_METER_MAP_LOAD_CURRENT_PHASE_C_BAR_CHART].nut_entity = "output.L3.current.nominal";
	bcmxcp_meter_map[BCMXCP_METER_MAP_OUTPUT_VA_BAR_CHART].nut_entity = "ups.power.nominal";
	bcmxcp_meter_map[BCMXCP_METER_MAP_DATE].nut_entity = "ups.date";
	bcmxcp_meter_map[BCMXCP_METER_MAP_TIME].nut_entity = "ups.time";
	bcmxcp_meter_map[BCMXCP_METER_MAP_BATTERY_TEMPERATURE].nut_entity = "battery.temperature";
	bcmxcp_meter_map[BCMXCP_METER_MAP_OUTPUT_VOLTS_B].nut_entity = "output.L2-N.voltage";
	bcmxcp_meter_map[BCMXCP_METER_MAP_OUTPUT_VOLTS_C].nut_entity = "output.L3-N.voltage";
	bcmxcp_meter_map[BCMXCP_METER_MAP_OUTPUT_WATTS_PHASE_A].nut_entity = "ups.L1-N.realpower";
	bcmxcp_meter_map[BCMXCP_METER_MAP_OUTPUT_WATTS_PHASE_B].nut_entity = "ups.L2-N.realpower";
	bcmxcp_meter_map[BCMXCP_METER_MAP_OUTPUT_WATTS_PHASE_C].nut_entity = "ups.L3-N.realpower";
	bcmxcp_meter_map[BCMXCP_METER_MAP_OUTPUT_WATTS_PHASE_A_B_C_BAR_CHART].nut_entity = "ups.realpower.nominal";
	bcmxcp_meter_map[BCMXCP_METER_MAP_LINE_EVENT_COUNTER].nut_entity = "input.quality";
}

void init_alarm_map(void)
{
	/* Clean entire map */
	memset(&bcmxcp_alarm_map, 0, sizeof(BCMXCP_ALARM_MAP_ENTRY_t) * BCMXCP_ALARM_MAP_MAX);

	/* Set all alarm descriptions */
	bcmxcp_alarm_map[BCMXCP_ALARM_INVERTER_AC_OVER_VOLTAGE].alarm_desc = "INVERTER_AC_OVER_VOLTAGE";
	bcmxcp_alarm_map[BCMXCP_ALARM_INVERTER_AC_UNDER_VOLTAGE].alarm_desc = "INVERTER_AC_UNDER_VOLTAGE";
	bcmxcp_alarm_map[BCMXCP_ALARM_INVERTER_OVER_OR_UNDER_FREQ].alarm_desc = "INVERTER_OVER_OR_UNDER_FREQ";
	bcmxcp_alarm_map[BCMXCP_ALARM_BYPASS_AC_OVER_VOLTAGE].alarm_desc = "BYPASS_AC_OVER_VOLTAGE";
	bcmxcp_alarm_map[BCMXCP_ALARM_BYPASS_AC_UNDER_VOLTAGE].alarm_desc = "BYPASS_AC_UNDER_VOLTAGE";
	bcmxcp_alarm_map[BCMXCP_ALARM_BYPASS_OVER_OR_UNDER_FREQ].alarm_desc = "BYPASS_OVER_OR_UNDER_FREQ";
	bcmxcp_alarm_map[BCMXCP_ALARM_INPUT_AC_OVER_VOLTAGE].alarm_desc = "INPUT_AC_OVER_VOLTAGE";
	bcmxcp_alarm_map[BCMXCP_ALARM_INPUT_AC_UNDER_VOLTAGE].alarm_desc = "INPUT_AC_UNDER_VOLTAGE";
	bcmxcp_alarm_map[BCMXCP_ALARM_INPUT_UNDER_OR_OVER_FREQ].alarm_desc = "INPUT_UNDER_OR_OVER_FREQ";
	bcmxcp_alarm_map[BCMXCP_ALARM_OUTPUT_OVER_VOLTAGE].alarm_desc = "OUTPUT_OVER_VOLTAGE";
	bcmxcp_alarm_map[BCMXCP_ALARM_OUTPUT_UNDER_VOLTAGE].alarm_desc = "OUTPUT_UNDER_VOLTAGE";
	bcmxcp_alarm_map[BCMXCP_ALARM_OUTPUT_UNDER_OR_OVER_FREQ].alarm_desc = "OUTPUT_UNDER_OR_OVER_FREQ";
	bcmxcp_alarm_map[BCMXCP_ALARM_REMOTE_EMERGENCY_PWR_OFF].alarm_desc = "REMOTE_EMERGENCY_PWR_OFF";
	bcmxcp_alarm_map[BCMXCP_ALARM_REMOTE_GO_TO_BYPASS].alarm_desc = "REMOTE_GO_TO_BYPASS";
	bcmxcp_alarm_map[BCMXCP_ALARM_BUILDING_ALARM_6].alarm_desc = "BUILDING_ALARM_6";
	bcmxcp_alarm_map[BCMXCP_ALARM_BUILDING_ALARM_5].alarm_desc = "BUILDING_ALARM_5";
	bcmxcp_alarm_map[BCMXCP_ALARM_BUILDING_ALARM_4].alarm_desc = "BUILDING_ALARM_4";
	bcmxcp_alarm_map[BCMXCP_ALARM_BUILDING_ALARM_3].alarm_desc = "BUILDING_ALARM_3";
	bcmxcp_alarm_map[BCMXCP_ALARM_BUILDING_ALARM_2].alarm_desc = "BUILDING_ALARM_2";
	bcmxcp_alarm_map[BCMXCP_ALARM_BUILDING_ALARM_1].alarm_desc = "BUILDING_ALARM_1";
	bcmxcp_alarm_map[BCMXCP_ALARM_STATIC_SWITCH_OVER_TEMP].alarm_desc = "STATIC_SWITCH_OVER_TEMP";
	bcmxcp_alarm_map[BCMXCP_ALARM_CHARGER_OVER_TEMP].alarm_desc = "CHARGER_OVER_TEMP";
	bcmxcp_alarm_map[BCMXCP_ALARM_CHARGER_LOGIC_PWR_FAIL].alarm_desc = "CHARGER_LOGIC_PWR_FAIL";
	bcmxcp_alarm_map[BCMXCP_ALARM_CHARGER_OVER_VOLTAGE_OR_CURRENT].alarm_desc = "CHARGER_OVER_VOLTAGE_OR_CURRENT";
	bcmxcp_alarm_map[BCMXCP_ALARM_INVERTER_OVER_TEMP].alarm_desc = "INVERTER_OVER_TEMP";
	bcmxcp_alarm_map[BCMXCP_ALARM_OUTPUT_OVERLOAD].alarm_desc = "OUTPUT_OVERLOAD";
	bcmxcp_alarm_map[BCMXCP_ALARM_RECTIFIER_INPUT_OVER_CURRENT].alarm_desc = "RECTIFIER_INPUT_OVER_CURRENT";
	bcmxcp_alarm_map[BCMXCP_ALARM_INVERTER_OUTPUT_OVER_CURRENT].alarm_desc = "INVERTER_OUTPUT_OVER_CURRENT";
	bcmxcp_alarm_map[BCMXCP_ALARM_DC_LINK_OVER_VOLTAGE].alarm_desc = "DC_LINK_OVER_VOLTAGE";
	bcmxcp_alarm_map[BCMXCP_ALARM_DC_LINK_UNDER_VOLTAGE].alarm_desc = "DC_LINK_UNDER_VOLTAGE";
	bcmxcp_alarm_map[BCMXCP_ALARM_RECTIFIER_FAILED].alarm_desc = "RECTIFIER_FAILED";
	bcmxcp_alarm_map[BCMXCP_ALARM_INVERTER_FAULT].alarm_desc = "INVERTER_FAULT";
	bcmxcp_alarm_map[BCMXCP_ALARM_BATTERY_CONNECTOR_FAIL].alarm_desc = "BATTERY_CONNECTOR_FAIL";
	bcmxcp_alarm_map[BCMXCP_ALARM_BYPASS_BREAKER_FAIL].alarm_desc = "BYPASS_BREAKER_FAIL";
	bcmxcp_alarm_map[BCMXCP_ALARM_CHARGER_FAIL].alarm_desc = "CHARGER_FAIL";
	bcmxcp_alarm_map[BCMXCP_ALARM_RAMP_UP_FAILED].alarm_desc = "RAMP_UP_FAILED";
	bcmxcp_alarm_map[BCMXCP_ALARM_STATIC_SWITCH_FAILED].alarm_desc = "STATIC_SWITCH_FAILED";
	bcmxcp_alarm_map[BCMXCP_ALARM_ANALOG_AD_REF_FAIL].alarm_desc = "ANALOG_AD_REF_FAIL";
	bcmxcp_alarm_map[BCMXCP_ALARM_BYPASS_UNCALIBRATED].alarm_desc = "BYPASS_UNCALIBRATED";
	bcmxcp_alarm_map[BCMXCP_ALARM_RECTIFIER_UNCALIBRATED].alarm_desc = "RECTIFIER_UNCALIBRATED";
	bcmxcp_alarm_map[BCMXCP_ALARM_OUTPUT_UNCALIBRATED].alarm_desc = "OUTPUT_UNCALIBRATED";
	bcmxcp_alarm_map[BCMXCP_ALARM_INVERTER_UNCALIBRATED].alarm_desc = "INVERTER_UNCALIBRATED";
	bcmxcp_alarm_map[BCMXCP_ALARM_DC_VOLT_UNCALIBRATED].alarm_desc = "DC_VOLT_UNCALIBRATED";
	bcmxcp_alarm_map[BCMXCP_ALARM_OUTPUT_CURRENT_UNCALIBRATED].alarm_desc = "OUTPUT_CURRENT_UNCALIBRATED";
	bcmxcp_alarm_map[BCMXCP_ALARM_RECTIFIER_CURRENT_UNCALIBRATED].alarm_desc = "RECTIFIER_CURRENT_UNCALIBRATED";
	bcmxcp_alarm_map[BCMXCP_ALARM_BATTERY_CURRENT_UNCALIBRATED].alarm_desc = "BATTERY_CURRENT_UNCALIBRATED";
	bcmxcp_alarm_map[BCMXCP_ALARM_INVERTER_ON_OFF_STAT_FAIL].alarm_desc = "INVERTER_ON_OFF_STAT_FAIL";
	bcmxcp_alarm_map[BCMXCP_ALARM_BATTERY_CURRENT_LIMIT].alarm_desc = "BATTERY_CURRENT_LIMIT";
	bcmxcp_alarm_map[BCMXCP_ALARM_INVERTER_STARTUP_FAIL].alarm_desc = "INVERTER_STARTUP_FAIL";
	bcmxcp_alarm_map[BCMXCP_ALARM_ANALOG_BOARD_AD_STAT_FAIL].alarm_desc = "ANALOG_BOARD_AD_STAT_FAIL";
	bcmxcp_alarm_map[BCMXCP_ALARM_OUTPUT_CURRENT_OVER_100].alarm_desc = "OUTPUT_CURRENT_OVER_100";
	bcmxcp_alarm_map[BCMXCP_ALARM_BATTERY_GROUND_FAULT].alarm_desc = "BATTERY_GROUND_FAULT";
	bcmxcp_alarm_map[BCMXCP_ALARM_WAITING_FOR_CHARGER_SYNC].alarm_desc = "WAITING_FOR_CHARGER_SYNC";
	bcmxcp_alarm_map[BCMXCP_ALARM_NV_RAM_FAIL].alarm_desc = "NV_RAM_FAIL";
	bcmxcp_alarm_map[BCMXCP_ALARM_ANALOG_BOARD_AD_TIMEOUT].alarm_desc = "ANALOG_BOARD_AD_TIMEOUT";
	bcmxcp_alarm_map[BCMXCP_ALARM_SHUTDOWN_IMMINENT].alarm_desc = "SHUTDOWN_IMMINENT";
	bcmxcp_alarm_map[BCMXCP_ALARM_BATTERY_LOW].alarm_desc = "BATTERY_LOW";
	bcmxcp_alarm_map[BCMXCP_ALARM_UTILITY_FAIL].alarm_desc = "UTILITY_FAIL";
	bcmxcp_alarm_map[BCMXCP_ALARM_OUTPUT_SHORT_CIRCUIT].alarm_desc = "OUTPUT_SHORT_CIRCUIT";
	bcmxcp_alarm_map[BCMXCP_ALARM_UTILITY_NOT_PRESENT].alarm_desc = "UTILITY_NOT_PRESENT";
	bcmxcp_alarm_map[BCMXCP_ALARM_FULL_TIME_CHARGING].alarm_desc = "FULL_TIME_CHARGING";
	bcmxcp_alarm_map[BCMXCP_ALARM_FAST_BYPASS_COMMAND].alarm_desc = "FAST_BYPASS_COMMAND";
	bcmxcp_alarm_map[BCMXCP_ALARM_AD_ERROR].alarm_desc = "AD_ERROR";
	bcmxcp_alarm_map[BCMXCP_ALARM_INTERNAL_COM_FAIL].alarm_desc = "INTERNAL_COM_FAIL";
	bcmxcp_alarm_map[BCMXCP_ALARM_RECTIFIER_SELFTEST_FAIL].alarm_desc = "RECTIFIER_SELFTEST_FAIL";
	bcmxcp_alarm_map[BCMXCP_ALARM_RECTIFIER_EEPROM_FAIL].alarm_desc = "RECTIFIER_EEPROM_FAIL";
	bcmxcp_alarm_map[BCMXCP_ALARM_RECTIFIER_EPROM_FAIL].alarm_desc = "RECTIFIER_EPROM_FAIL";
	bcmxcp_alarm_map[BCMXCP_ALARM_INPUT_LINE_VOLTAGE_LOSS].alarm_desc = "INPUT_LINE_VOLTAGE_LOSS";
	bcmxcp_alarm_map[BCMXCP_ALARM_BATTERY_DC_OVER_VOLTAGE].alarm_desc = "BATTERY_DC_OVER_VOLTAGE";
	bcmxcp_alarm_map[BCMXCP_ALARM_POWER_SUPPLY_OVER_TEMP].alarm_desc = "POWER_SUPPLY_OVER_TEMP";
	bcmxcp_alarm_map[BCMXCP_ALARM_POWER_SUPPLY_FAIL].alarm_desc = "POWER_SUPPLY_FAIL";
	bcmxcp_alarm_map[BCMXCP_ALARM_POWER_SUPPLY_5V_FAIL].alarm_desc = "POWER_SUPPLY_5V_FAIL";
	bcmxcp_alarm_map[BCMXCP_ALARM_POWER_SUPPLY_12V_FAIL].alarm_desc = "POWER_SUPPLY_12V_FAIL";
	bcmxcp_alarm_map[BCMXCP_ALARM_HEATSINK_OVER_TEMP].alarm_desc = "HEATSINK_OVER_TEMP";
	bcmxcp_alarm_map[BCMXCP_ALARM_HEATSINK_TEMP_SENSOR_FAIL].alarm_desc = "HEATSINK_TEMP_SENSOR_FAIL";
	bcmxcp_alarm_map[BCMXCP_ALARM_RECTIFIER_CURRENT_OVER_125].alarm_desc = "RECTIFIER_CURRENT_OVER_125";
	bcmxcp_alarm_map[BCMXCP_ALARM_RECTIFIER_FAULT_INTERRUPT_FAIL].alarm_desc = "RECTIFIER_FAULT_INTERRUPT_FAIL";
	bcmxcp_alarm_map[BCMXCP_ALARM_RECTIFIER_POWER_CAPACITOR_FAIL].alarm_desc = "RECTIFIER_POWER_CAPACITOR_FAIL";
	bcmxcp_alarm_map[BCMXCP_ALARM_INVERTER_PROGRAM_STACK_ERROR].alarm_desc = "INVERTER_PROGRAM_STACK_ERROR";
	bcmxcp_alarm_map[BCMXCP_ALARM_INVERTER_BOARD_SELFTEST_FAIL].alarm_desc = "INVERTER_BOARD_SELFTEST_FAIL";
	bcmxcp_alarm_map[BCMXCP_ALARM_INVERTER_AD_SELFTEST_FAIL].alarm_desc = "INVERTER_AD_SELFTEST_FAIL";
	bcmxcp_alarm_map[BCMXCP_ALARM_INVERTER_RAM_SELFTEST_FAIL].alarm_desc = "INVERTER_RAM_SELFTEST_FAIL";
	bcmxcp_alarm_map[BCMXCP_ALARM_NV_MEMORY_CHECKSUM_FAIL].alarm_desc = "NV_MEMORY_CHECKSUM_FAIL";
	bcmxcp_alarm_map[BCMXCP_ALARM_PROGRAM_CHECKSUM_FAIL].alarm_desc = "PROGRAM_CHECKSUM_FAIL";
	bcmxcp_alarm_map[BCMXCP_ALARM_INVERTER_CPU_SELFTEST_FAIL].alarm_desc = "INVERTER_CPU_SELFTEST_FAIL";
	bcmxcp_alarm_map[BCMXCP_ALARM_NETWORK_NOT_RESPONDING].alarm_desc = "NETWORK_NOT_RESPONDING";
	bcmxcp_alarm_map[BCMXCP_ALARM_FRONT_PANEL_SELFTEST_FAIL].alarm_desc = "FRONT_PANEL_SELFTEST_FAIL";
	bcmxcp_alarm_map[BCMXCP_ALARM_NODE_EEPROM_VERIFICATION_ERROR].alarm_desc = "NODE_EEPROM_VERIFICATION_ERROR";
	bcmxcp_alarm_map[BCMXCP_ALARM_OUTPUT_AC_OVER_VOLT_TEST_FAIL].alarm_desc = "OUTPUT_AC_OVER_VOLT_TEST_FAIL";
	bcmxcp_alarm_map[BCMXCP_ALARM_OUTPUT_DC_OVER_VOLTAGE].alarm_desc = "OUTPUT_DC_OVER_VOLTAGE";
	bcmxcp_alarm_map[BCMXCP_ALARM_INPUT_PHASE_ROTATION_ERROR].alarm_desc = "INPUT_PHASE_ROTATION_ERROR";
	bcmxcp_alarm_map[BCMXCP_ALARM_INVERTER_RAMP_UP_TEST_FAILED].alarm_desc = "INVERTER_RAMP_UP_TEST_FAILED";
	bcmxcp_alarm_map[BCMXCP_ALARM_INVERTER_OFF_COMMAND].alarm_desc = "INVERTER_OFF_COMMAND";
	bcmxcp_alarm_map[BCMXCP_ALARM_INVERTER_ON_COMMAND].alarm_desc = "INVERTER_ON_COMMAND";
	bcmxcp_alarm_map[BCMXCP_ALARM_TO_BYPASS_COMMAND].alarm_desc = "TO_BYPASS_COMMAND";
	bcmxcp_alarm_map[BCMXCP_ALARM_FROM_BYPASS_COMMAND].alarm_desc = "FROM_BYPASS_COMMAND";
	bcmxcp_alarm_map[BCMXCP_ALARM_AUTO_MODE_COMMAND].alarm_desc = "AUTO_MODE_COMMAND";
	bcmxcp_alarm_map[BCMXCP_ALARM_EMERGENCY_SHUTDOWN_COMMAND].alarm_desc = "EMERGENCY_SHUTDOWN_COMMAND";
	bcmxcp_alarm_map[BCMXCP_ALARM_SETUP_SWITCH_OPEN].alarm_desc = "SETUP_SWITCH_OPEN";
	bcmxcp_alarm_map[BCMXCP_ALARM_INVERTER_OVER_VOLT_INT].alarm_desc = "INVERTER_OVER_VOLT_INT";
	bcmxcp_alarm_map[BCMXCP_ALARM_INVERTER_UNDER_VOLT_INT].alarm_desc = "INVERTER_UNDER_VOLT_INT";
	bcmxcp_alarm_map[BCMXCP_ALARM_ABSOLUTE_DCOV_ACOV].alarm_desc = "ABSOLUTE_DCOV_ACOV";
	bcmxcp_alarm_map[BCMXCP_ALARM_PHASE_A_CURRENT_LIMIT].alarm_desc = "PHASE_A_CURRENT_LIMIT";
	bcmxcp_alarm_map[BCMXCP_ALARM_PHASE_B_CURRENT_LIMIT].alarm_desc = "PHASE_B_CURRENT_LIMIT";
	bcmxcp_alarm_map[BCMXCP_ALARM_PHASE_C_CURRENT_LIMIT].alarm_desc = "PHASE_C_CURRENT_LIMIT";
	bcmxcp_alarm_map[BCMXCP_ALARM_BYPASS_NOT_AVAILABLE].alarm_desc = "BYPASS_NOT_AVAILABLE";
	bcmxcp_alarm_map[BCMXCP_ALARM_RECTIFIER_BREAKER_OPEN].alarm_desc = "RECTIFIER_BREAKER_OPEN";
	bcmxcp_alarm_map[BCMXCP_ALARM_BATTERY_CONTACTOR_OPEN].alarm_desc = "BATTERY_CONTACTOR_OPEN";
	bcmxcp_alarm_map[BCMXCP_ALARM_INVERTER_CONTACTOR_OPEN].alarm_desc = "INVERTER_CONTACTOR_OPEN";
	bcmxcp_alarm_map[BCMXCP_ALARM_BYPASS_BREAKER_OPEN].alarm_desc = "BYPASS_BREAKER_OPEN";
	bcmxcp_alarm_map[BCMXCP_ALARM_INV_BOARD_ACOV_INT_TEST_FAIL].alarm_desc = "INV_BOARD_ACOV_INT_TEST_FAIL";
	bcmxcp_alarm_map[BCMXCP_ALARM_INVERTER_OVER_TEMP_TRIP].alarm_desc = "INVERTER_OVER_TEMP_TRIP";
	bcmxcp_alarm_map[BCMXCP_ALARM_INV_BOARD_ACUV_INT_TEST_FAIL].alarm_desc = "INV_BOARD_ACUV_INT_TEST_FAIL";
	bcmxcp_alarm_map[BCMXCP_ALARM_INVERTER_VOLTAGE_FEEDBACK_ERROR].alarm_desc = "INVERTER_VOLTAGE_FEEDBACK_ERROR";
	bcmxcp_alarm_map[BCMXCP_ALARM_DC_UNDER_VOLTAGE_TIMEOUT].alarm_desc = "DC_UNDER_VOLTAGE_TIMEOUT";
	bcmxcp_alarm_map[BCMXCP_ALARM_AC_UNDER_VOLTAGE_TIMEOUT].alarm_desc = "AC_UNDER_VOLTAGE_TIMEOUT";
	bcmxcp_alarm_map[BCMXCP_ALARM_DC_UNDER_VOLTAGE_WHILE_CHARGE].alarm_desc = "DC_UNDER_VOLTAGE_WHILE_CHARGE";
	bcmxcp_alarm_map[BCMXCP_ALARM_INVERTER_VOLTAGE_BIAS_ERROR].alarm_desc = "INVERTER_VOLTAGE_BIAS_ERROR";
	bcmxcp_alarm_map[BCMXCP_ALARM_RECTIFIER_PHASE_ROTATION].alarm_desc = "RECTIFIER_PHASE_ROTATION";
	bcmxcp_alarm_map[BCMXCP_ALARM_BYPASS_PHASER_ROTATION].alarm_desc = "BYPASS_PHASER_ROTATION";
	bcmxcp_alarm_map[BCMXCP_ALARM_SYSTEM_INTERFACE_BOARD_FAIL].alarm_desc = "SYSTEM_INTERFACE_BOARD_FAIL";
	bcmxcp_alarm_map[BCMXCP_ALARM_PARALLEL_BOARD_FAIL].alarm_desc = "PARALLEL_BOARD_FAIL";
	bcmxcp_alarm_map[BCMXCP_ALARM_LOST_LOAD_SHARING_PHASE_A].alarm_desc = "LOST_LOAD_SHARING_PHASE_A";
	bcmxcp_alarm_map[BCMXCP_ALARM_LOST_LOAD_SHARING_PHASE_B].alarm_desc = "LOST_LOAD_SHARING_PHASE_B";
	bcmxcp_alarm_map[BCMXCP_ALARM_LOST_LOAD_SHARING_PHASE_C].alarm_desc = "LOST_LOAD_SHARING_PHASE_C";
	bcmxcp_alarm_map[BCMXCP_ALARM_DC_OVER_VOLTAGE_TIMEOUT].alarm_desc = "DC_OVER_VOLTAGE_TIMEOUT";
	bcmxcp_alarm_map[BCMXCP_ALARM_BATTERY_TOTALLY_DISCHARGED].alarm_desc = "BATTERY_TOTALLY_DISCHARGED";
	bcmxcp_alarm_map[BCMXCP_ALARM_INVERTER_PHASE_BIAS_ERROR].alarm_desc = "INVERTER_PHASE_BIAS_ERROR";
	bcmxcp_alarm_map[BCMXCP_ALARM_INVERTER_VOLTAGE_BIAS_ERROR_2].alarm_desc = "INVERTER_VOLTAGE_BIAS_ERROR_2";
	bcmxcp_alarm_map[BCMXCP_ALARM_DC_LINK_BLEED_COMPLETE].alarm_desc = "DC_LINK_BLEED_COMPLETE";
	bcmxcp_alarm_map[BCMXCP_ALARM_LARGE_CHARGER_INPUT_CURRENT].alarm_desc = "LARGE_CHARGER_INPUT_CURRENT";
	bcmxcp_alarm_map[BCMXCP_ALARM_INV_VOLT_TOO_LOW_FOR_RAMP_LEVEL].alarm_desc = "INV_VOLT_TOO_LOW_FOR_RAMP_LEVEL";
	bcmxcp_alarm_map[BCMXCP_ALARM_LOSS_OF_REDUNDANCY].alarm_desc = "LOSS_OF_REDUNDANCY";
	bcmxcp_alarm_map[BCMXCP_ALARM_LOSS_OF_SYNC_BUS].alarm_desc = "LOSS_OF_SYNC_BUS";
	bcmxcp_alarm_map[BCMXCP_ALARM_RECTIFIER_BREAKER_SHUNT_TRIP].alarm_desc = "RECTIFIER_BREAKER_SHUNT_TRIP";
	bcmxcp_alarm_map[BCMXCP_ALARM_LOSS_OF_CHARGER_SYNC].alarm_desc = "LOSS_OF_CHARGER_SYNC";
	bcmxcp_alarm_map[BCMXCP_ALARM_INVERTER_LOW_LEVEL_TEST_TIMEOUT].alarm_desc = "INVERTER_LOW_LEVEL_TEST_TIMEOUT";
	bcmxcp_alarm_map[BCMXCP_ALARM_OUTPUT_BREAKER_OPEN].alarm_desc = "OUTPUT_BREAKER_OPEN";
	bcmxcp_alarm_map[BCMXCP_ALARM_CONTROL_POWER_ON].alarm_desc = "CONTROL_POWER_ON";
	bcmxcp_alarm_map[BCMXCP_ALARM_INVERTER_ON].alarm_desc = "INVERTER_ON";
	bcmxcp_alarm_map[BCMXCP_ALARM_CHARGER_ON].alarm_desc = "CHARGER_ON";
	bcmxcp_alarm_map[BCMXCP_ALARM_BYPASS_ON].alarm_desc = "BYPASS_ON";
	bcmxcp_alarm_map[BCMXCP_ALARM_BYPASS_POWER_LOSS].alarm_desc = "BYPASS_POWER_LOSS";
	bcmxcp_alarm_map[BCMXCP_ALARM_ON_MANUAL_BYPASS].alarm_desc = "ON_MANUAL_BYPASS";
	bcmxcp_alarm_map[BCMXCP_ALARM_BYPASS_MANUAL_TURN_OFF].alarm_desc = "BYPASS_MANUAL_TURN_OFF";
	bcmxcp_alarm_map[BCMXCP_ALARM_INVERTER_BLEEDING_DC_LINK_VOLT].alarm_desc = "INVERTER_BLEEDING_DC_LINK_VOLT";
	bcmxcp_alarm_map[BCMXCP_ALARM_CPU_ISR_ERROR].alarm_desc = "CPU_ISR_ERROR";
	bcmxcp_alarm_map[BCMXCP_ALARM_SYSTEM_ISR_RESTART].alarm_desc = "SYSTEM_ISR_RESTART";
	bcmxcp_alarm_map[BCMXCP_ALARM_PARALLEL_DC].alarm_desc = "PARALLEL_DC";
	bcmxcp_alarm_map[BCMXCP_ALARM_BATTERY_NEEDS_SERVICE].alarm_desc = "BATTERY_NEEDS_SERVICE";
	bcmxcp_alarm_map[BCMXCP_ALARM_BATTERY_CHARGING].alarm_desc = "BATTERY_CHARGING";
	bcmxcp_alarm_map[BCMXCP_ALARM_BATTERY_NOT_CHARGED].alarm_desc = "BATTERY_NOT_CHARGED";
	bcmxcp_alarm_map[BCMXCP_ALARM_DISABLED_BATTERY_TIME].alarm_desc = "DISABLED_BATTERY_TIME";
	bcmxcp_alarm_map[BCMXCP_ALARM_SERIES_7000_ENABLE].alarm_desc = "SERIES_7000_ENABLE";
	bcmxcp_alarm_map[BCMXCP_ALARM_OTHER_UPS_ON].alarm_desc = "OTHER_UPS_ON";
	bcmxcp_alarm_map[BCMXCP_ALARM_PARALLEL_INVERTER].alarm_desc = "PARALLEL_INVERTER";
	bcmxcp_alarm_map[BCMXCP_ALARM_UPS_IN_PARALLEL].alarm_desc = "UPS_IN_PARALLEL";
	bcmxcp_alarm_map[BCMXCP_ALARM_OUTPUT_BREAKER_REALY_FAIL].alarm_desc = "OUTPUT_BREAKER_REALY_FAIL";
	bcmxcp_alarm_map[BCMXCP_ALARM_CONTROL_POWER_OFF].alarm_desc = "CONTROL_POWER_OFF";
	bcmxcp_alarm_map[BCMXCP_ALARM_LEVEL_2_OVERLOAD_PHASE_A].alarm_desc = "LEVEL_2_OVERLOAD_PHASE_A";
	bcmxcp_alarm_map[BCMXCP_ALARM_LEVEL_2_OVERLOAD_PHASE_B].alarm_desc = "LEVEL_2_OVERLOAD_PHASE_B";
	bcmxcp_alarm_map[BCMXCP_ALARM_LEVEL_2_OVERLOAD_PHASE_C].alarm_desc = "LEVEL_2_OVERLOAD_PHASE_C";
	bcmxcp_alarm_map[BCMXCP_ALARM_LEVEL_3_OVERLOAD_PHASE_A].alarm_desc = "LEVEL_3_OVERLOAD_PHASE_A";
	bcmxcp_alarm_map[BCMXCP_ALARM_LEVEL_3_OVERLOAD_PHASE_B].alarm_desc = "LEVEL_3_OVERLOAD_PHASE_B";
	bcmxcp_alarm_map[BCMXCP_ALARM_LEVEL_3_OVERLOAD_PHASE_C].alarm_desc = "LEVEL_3_OVERLOAD_PHASE_C";
	bcmxcp_alarm_map[BCMXCP_ALARM_LEVEL_4_OVERLOAD_PHASE_A].alarm_desc = "LEVEL_4_OVERLOAD_PHASE_A";
	bcmxcp_alarm_map[BCMXCP_ALARM_LEVEL_4_OVERLOAD_PHASE_B].alarm_desc = "LEVEL_4_OVERLOAD_PHASE_B";
	bcmxcp_alarm_map[BCMXCP_ALARM_LEVEL_4_OVERLOAD_PHASE_C].alarm_desc = "LEVEL_4_OVERLOAD_PHASE_C";
	bcmxcp_alarm_map[BCMXCP_ALARM_UPS_ON_BATTERY].alarm_desc = "UPS_ON_BATTERY";
	bcmxcp_alarm_map[BCMXCP_ALARM_UPS_ON_BYPASS].alarm_desc = "UPS_ON_BYPASS";
	bcmxcp_alarm_map[BCMXCP_ALARM_LOAD_DUMPED].alarm_desc = "LOAD_DUMPED";
	bcmxcp_alarm_map[BCMXCP_ALARM_LOAD_ON_INVERTER].alarm_desc = "LOAD_ON_INVERTER";
	bcmxcp_alarm_map[BCMXCP_ALARM_UPS_ON_COMMAND].alarm_desc = "UPS_ON_COMMAND";
	bcmxcp_alarm_map[BCMXCP_ALARM_UPS_OFF_COMMAND].alarm_desc = "UPS_OFF_COMMAND";
	bcmxcp_alarm_map[BCMXCP_ALARM_LOW_BATTERY_SHUTDOWN].alarm_desc = "LOW_BATTERY_SHUTDOWN";
	bcmxcp_alarm_map[BCMXCP_ALARM_AUTO_ON_ENABLED].alarm_desc = "AUTO_ON_ENABLED";
	bcmxcp_alarm_map[BCMXCP_ALARM_SOFTWARE_INCOMPABILITY_DETECTED].alarm_desc = "SOFTWARE_INCOMPABILITY_DETECTED";
	bcmxcp_alarm_map[BCMXCP_ALARM_INVERTER_TEMP_SENSOR_FAILED].alarm_desc = "INVERTER_TEMP_SENSOR_FAILED";
	bcmxcp_alarm_map[BCMXCP_ALARM_DC_START_OCCURED].alarm_desc = "DC_START_OCCURED";
	bcmxcp_alarm_map[BCMXCP_ALARM_IN_PARALLEL_OPERATION].alarm_desc = "IN_PARALLEL_OPERATION";
	bcmxcp_alarm_map[BCMXCP_ALARM_SYNCING_TO_BYPASS].alarm_desc = "SYNCING_TO_BYPASS";
	bcmxcp_alarm_map[BCMXCP_ALARM_RAMPING_UPS_UP].alarm_desc = "RAMPING_UPS_UP";
	bcmxcp_alarm_map[BCMXCP_ALARM_INVERTER_ON_DELAY].alarm_desc = "INVERTER_ON_DELAY";
	bcmxcp_alarm_map[BCMXCP_ALARM_CHARGER_ON_DELAY].alarm_desc = "CHARGER_ON_DELAY";
	bcmxcp_alarm_map[BCMXCP_ALARM_WAITING_FOR_UTIL_INPUT].alarm_desc = "WAITING_FOR_UTIL_INPUT";
	bcmxcp_alarm_map[BCMXCP_ALARM_CLOSE_BYPASS_BREAKER].alarm_desc = "CLOSE_BYPASS_BREAKER";
	bcmxcp_alarm_map[BCMXCP_ALARM_TEMPORARY_BYPASS_OPERATION].alarm_desc = "TEMPORARY_BYPASS_OPERATION";
	bcmxcp_alarm_map[BCMXCP_ALARM_SYNCING_TO_OUTPUT].alarm_desc = "SYNCING_TO_OUTPUT";
	bcmxcp_alarm_map[BCMXCP_ALARM_BYPASS_FAILURE].alarm_desc = "BYPASS_FAILURE";
	bcmxcp_alarm_map[BCMXCP_ALARM_AUTO_OFF_COMMAND_EXECUTED].alarm_desc = "AUTO_OFF_COMMAND_EXECUTED";
	bcmxcp_alarm_map[BCMXCP_ALARM_AUTO_ON_COMMAND_EXECUTED].alarm_desc = "AUTO_ON_COMMAND_EXECUTED";
	bcmxcp_alarm_map[BCMXCP_ALARM_BATTERY_TEST_FAILED].alarm_desc = "BATTERY_TEST_FAILED";
	bcmxcp_alarm_map[BCMXCP_ALARM_FUSE_FAIL].alarm_desc = "FUSE_FAIL";
	bcmxcp_alarm_map[BCMXCP_ALARM_FAN_FAIL].alarm_desc = "FAN_FAIL";
	bcmxcp_alarm_map[BCMXCP_ALARM_SITE_WIRING_FAULT].alarm_desc = "SITE_WIRING_FAULT";
	bcmxcp_alarm_map[BCMXCP_ALARM_BACKFEED_CONTACTOR_FAIL].alarm_desc = "BACKFEED_CONTACTOR_FAIL";
	bcmxcp_alarm_map[BCMXCP_ALARM_ON_BUCK].alarm_desc = "ON_BUCK";
	bcmxcp_alarm_map[BCMXCP_ALARM_ON_BOOST].alarm_desc = "ON_BOOST";
	bcmxcp_alarm_map[BCMXCP_ALARM_ON_DOUBLE_BOOST].alarm_desc = "ON_DOUBLE_BOOST";
	bcmxcp_alarm_map[BCMXCP_ALARM_BATTERIES_DISCONNECTED].alarm_desc = "BATTERIES_DISCONNECTED";
	bcmxcp_alarm_map[BCMXCP_ALARM_UPS_CABINET_OVER_TEMP].alarm_desc = "UPS_CABINET_OVER_TEMP";
	bcmxcp_alarm_map[BCMXCP_ALARM_TRANSFORMER_OVER_TEMP].alarm_desc = "TRANSFORMER_OVER_TEMP";
	bcmxcp_alarm_map[BCMXCP_ALARM_AMBIENT_UNDER_TEMP].alarm_desc = "AMBIENT_UNDER_TEMP";
	bcmxcp_alarm_map[BCMXCP_ALARM_AMBIENT_OVER_TEMP].alarm_desc = "AMBIENT_OVER_TEMP";
	bcmxcp_alarm_map[BCMXCP_ALARM_CABINET_DOOR_OPEN].alarm_desc = "CABINET_DOOR_OPEN";
	bcmxcp_alarm_map[BCMXCP_ALARM_CABINET_DOOR_OPEN_VOLT_PRESENT].alarm_desc = "CABINET_DOOR_OPEN_VOLT_PRESENT";
	bcmxcp_alarm_map[BCMXCP_ALARM_AUTO_SHUTDOWN_PENDING].alarm_desc = "AUTO_SHUTDOWN_PENDING";
	bcmxcp_alarm_map[BCMXCP_ALARM_TAP_SWITCHING_REALY_PENDING].alarm_desc = "TAP_SWITCHING_REALY_PENDING";
	bcmxcp_alarm_map[BCMXCP_ALARM_UNABLE_TO_CHARGE_BATTERIES].alarm_desc = "UNABLE_TO_CHARGE_BATTERIES";
	bcmxcp_alarm_map[BCMXCP_ALARM_STARTUP_FAILURE_CHECK_EPO].alarm_desc = "STARTUP_FAILURE_CHECK_EPO";
	bcmxcp_alarm_map[BCMXCP_ALARM_AUTOMATIC_STARTUP_PENDING].alarm_desc = "AUTOMATIC_STARTUP_PENDING";
	bcmxcp_alarm_map[BCMXCP_ALARM_MODEM_FAILED].alarm_desc = "MODEM_FAILED";
	bcmxcp_alarm_map[BCMXCP_ALARM_INCOMING_MODEM_CALL_STARTED].alarm_desc = "INCOMING_MODEM_CALL_STARTED";
	bcmxcp_alarm_map[BCMXCP_ALARM_OUTGOING_MODEM_CALL_STARTED].alarm_desc = "OUTGOING_MODEM_CALL_STARTED";
	bcmxcp_alarm_map[BCMXCP_ALARM_MODEM_CONNECTION_ESTABLISHED].alarm_desc = "MODEM_CONNECTION_ESTABLISHED";
	bcmxcp_alarm_map[BCMXCP_ALARM_MODEM_CALL_COMPLETED_SUCCESS].alarm_desc = "MODEM_CALL_COMPLETED_SUCCESS";
	bcmxcp_alarm_map[BCMXCP_ALARM_MODEM_CALL_COMPLETED_FAIL].alarm_desc = "MODEM_CALL_COMPLETED_FAIL";
	bcmxcp_alarm_map[BCMXCP_ALARM_INPUT_BREAKER_FAIL].alarm_desc = "INPUT_BREAKER_FAIL";
	bcmxcp_alarm_map[BCMXCP_ALARM_SYSINIT_IN_PROGRESS].alarm_desc = "SYSINIT_IN_PROGRESS";
	bcmxcp_alarm_map[BCMXCP_ALARM_AUTOCALIBRATION_FAIL].alarm_desc = "AUTOCALIBRATION_FAIL";
	bcmxcp_alarm_map[BCMXCP_ALARM_SELECTIVE_TRIP_OF_MODULE].alarm_desc = "SELECTIVE_TRIP_OF_MODULE";
	bcmxcp_alarm_map[BCMXCP_ALARM_INVERTER_OUTPUT_FAILURE].alarm_desc = "INVERTER_OUTPUT_FAILURE";
	bcmxcp_alarm_map[BCMXCP_ALARM_ABNORMAL_OUTPUT_VOLT_AT_STARTUP].alarm_desc = "ABNORMAL_OUTPUT_VOLT_AT_STARTUP";
	bcmxcp_alarm_map[BCMXCP_ALARM_RECTIFIER_OVER_TEMP].alarm_desc = "RECTIFIER_OVER_TEMP";
	bcmxcp_alarm_map[BCMXCP_ALARM_CONFIG_ERROR].alarm_desc = "CONFIG_ERROR";
	bcmxcp_alarm_map[BCMXCP_ALARM_REDUNDANCY_LOSS_DUE_TO_OVERLOAD].alarm_desc = "REDUNDANCY_LOSS_DUE_TO_OVERLOAD";
	bcmxcp_alarm_map[BCMXCP_ALARM_ON_ALTERNATE_AC_SOURCE].alarm_desc = "ON_ALTERNATE_AC_SOURCE";
	bcmxcp_alarm_map[BCMXCP_ALARM_IN_HIGH_EFFICIENCY_MODE].alarm_desc = "IN_HIGH_EFFICIENCY_MODE";
	bcmxcp_alarm_map[BCMXCP_ALARM_SYSTEM_NOTICE_ACTIVE].alarm_desc = "SYSTEM_NOTICE_ACTIVE";
	bcmxcp_alarm_map[BCMXCP_ALARM_SYSTEM_ALARM_ACTIVE].alarm_desc = "SYSTEM_ALARM_ACTIVE";
	bcmxcp_alarm_map[BCMXCP_ALARM_ALTERNATE_POWER_SOURCE_NOT_AVAILABLE].alarm_desc = "ALTERNATE_POWER_SOURCE_NOT_AVAILABLE";
	bcmxcp_alarm_map[BCMXCP_ALARM_CURRENT_BALANCE_FAILURE].alarm_desc = "CURRENT_BALANCE_FAILURE";
	bcmxcp_alarm_map[BCMXCP_ALARM_CHECK_AIR_FILTER].alarm_desc = "CHECK_AIR_FILTER";
	bcmxcp_alarm_map[BCMXCP_ALARM_SUBSYSTEM_NOTICE_ACTIVE].alarm_desc = "SUBSYSTEM_NOTICE_ACTIVE";
	bcmxcp_alarm_map[BCMXCP_ALARM_SUBSYSTEM_ALARM_ACTIVE].alarm_desc = "SUBSYSTEM_ALARM_ACTIVE";
	bcmxcp_alarm_map[BCMXCP_ALARM_CHARGER_ON_COMMAND].alarm_desc = "CHARGER_ON_COMMAND";
	bcmxcp_alarm_map[BCMXCP_ALARM_CHARGER_OFF_COMMAND].alarm_desc = "CHARGER_OFF_COMMAND";
	bcmxcp_alarm_map[BCMXCP_ALARM_UPS_NORMAL].alarm_desc = "UPS_NORMAL";
	bcmxcp_alarm_map[BCMXCP_ALARM_INVERTER_PHASE_ROTATION].alarm_desc = "INVERTER_PHASE_ROTATION";
	bcmxcp_alarm_map[BCMXCP_ALARM_UPS_OFF].alarm_desc = "UPS_OFF";
	bcmxcp_alarm_map[BCMXCP_ALARM_EXTERNAL_COMMUNICATION_FAILURE].alarm_desc = "EXTERNAL_COMMUNICATION_FAILURE";
	bcmxcp_alarm_map[BCMXCP_ALARM_BATTERY_TEST_INPROGRESS].alarm_desc = "BATTERY_TEST_INPROGRESS";
	bcmxcp_alarm_map[BCMXCP_ALARM_SYSTEM_TEST_INPROGRESS].alarm_desc = "SYSTEM_TEST_INPROGRESS";
	bcmxcp_alarm_map[BCMXCP_ALARM_BATTERY_TEST_ABORTED].alarm_desc = "BATTERY_TEST_ABORTED";
}

/* Get information on UPS commands */
bool_t init_command(int size)
{
	unsigned char answer[PW_ANSWER_MAX_SIZE];
	unsigned char commandByte;
	const char* nutvalue;
	ssize_t res;
	int iIndex = 0, ncounter, NumComms = 0, i;

	upsdebugx(1, "entering init_command(%i)", size);

	res = command_read_sequence(PW_COMMAND_LIST_REQ, answer);
	if (res <= 0)
	{
		upsdebugx(2, "No command list block.");
		return FALSE;
	}
	else
	{
		upsdebugx(2, "Command list block supported.");

		res = answer[iIndex];
		NumComms = (int)res; /* Number of commands implemented in this UPS */
		upsdebugx(3, "Number of commands implemented in ups %" PRIiSIZE, res);
		iIndex++;
		res = answer[iIndex]; /* Entry length - bytes reported for each command */
		iIndex++;
		upsdebugx(5, "bytes per command %" PRIiSIZE, res);

		/* In case of debug - make explanation of values */
		upsdebugx(2, "Index\tCmd byte\tDescription");

		/* Get command bytes if size of command block matches with size from standard ID block */
		if (NumComms + 2 == size)
		{
			for (ncounter = 0; ncounter < NumComms; ncounter++)
			{
				commandByte = answer[iIndex];
				if (commandByte < BCMXCP_COMMAND_MAP_MAX) {
					upsdebugx(2, "%03d\t%02x\t%s", ncounter, commandByte, bcmxcp_command_map[commandByte].command_desc);
					bcmxcp_command_map[commandByte].command_byte = commandByte;
				}
				else {
					upsdebugx(2, "%03d\t%02x\t%s", ncounter, commandByte, "Unknown command, the commandByte is not mapped");
				}

				iIndex++;
			}

			/* Map supported commands to instcmd */
			for (i = 0; i < BCMXCP_COMMAND_MAP_MAX; i++) {
				if (bcmxcp_command_map[i].command_desc != NULL) {
					if (bcmxcp_command_map[i].command_byte > 0) {
						if ((nutvalue = nut_find_infoval(command_map_info, bcmxcp_command_map[i].command_byte, FALSE)) != NULL) {
							dstate_addcmd(nutvalue);
							upsdebugx(2, "Added support for instcmd %s", nutvalue);
						}
					}
				}
			}

			return TRUE;
		}
		else {
			upsdebugx(1, "Invalid response received from Command List block");
			return FALSE;
		}
	}
}

void init_ups_meter_map(const unsigned char *map, unsigned char len)
{
	unsigned int iIndex, iOffset = 0;

	/* In case of debug - make explanation of values */
	upsdebugx(2, "Index\tOffset\tFormat\tNUT");

	/* Loop thru map */
	for (iIndex = 0; iIndex < len && iIndex < BCMXCP_METER_MAP_MAX; iIndex++)
	{
		bcmxcp_meter_map[iIndex].format = map[iIndex];
		if (map[iIndex] != 0)
		{
			/* Set meter map entry offset */
			bcmxcp_meter_map[iIndex].meter_block_index = iOffset;

			/* Debug info */
			upsdebugx(2, "%04u\t%04u\t%2x\t%s", iIndex, iOffset, bcmxcp_meter_map[iIndex].format,
					(bcmxcp_meter_map[iIndex].nut_entity == NULL ? "None" :bcmxcp_meter_map[iIndex].nut_entity));

			iOffset += 4;
		}
	}
	upsdebugx(2, "\n");
}

void decode_meter_map_entry(const unsigned char *entry, const unsigned char format, char* value)
{
	uint32_t lValue = 0;
	char sFormat[32];
	float fValue;
	unsigned char dd, mm, yy, cc, hh, ss;

	/* Paranoid input sanity checks */
	if (value == NULL)
		return;
	*value = '\0';
	if (entry == (unsigned char *)NULL || format == 0x00)
		return;

	/* Get data based on format */
	if (format == 0xf0) {
		/* Long integer */
		lValue = get_long(entry);
		snprintf(value, 127, "%d", (int)lValue);
	}
	else if ((format & 0xf0) == 0xf0) {
		/* Fixed point integer */
		fValue = get_long(entry) / ldexp(1, format & 0x0f);
		snprintf(value, 127, "%.2f", fValue);
	}
	else if (format <= 0x97) {
		/* Floating point */
		fValue = get_float(entry);
		/* Format is packed BCD */
		snprintf(sFormat, 31, "%%%d.%df", ((format & 0xf0) >> 4), (format & 0x0f));
		snprintf_dynamic(value, 127, sFormat, "%f", fValue);
	}
	else if (format == 0xe2) {
		/* Seconds */
		lValue = get_long(entry);
		snprintf(value, 127, "%d", (int)lValue);
	}
	else if (format == 0xe0) {
		/* Date */
		/* Format is packed BCD for each byte, and cc uses most signifcant bit to signal date format */
		dd = entry[0];
		mm = entry[1];
		yy = entry[2];
		cc = entry[3];

		/* Check format type */
		if (cc & 0x80) {
			/* Month:Day format */
			snprintf(value, 127, "%d%d/%d%d/%d%d%d%d", ((dd & 0xf0) >> 4), (dd & 0x0f), ((mm & 0xf0) >> 4), (mm & 0x0f), (((cc & 0x7f) & 0xf0) >> 4), ((cc & 0x7f) & 0x0f), ((yy & 0xf0) >> 4), (yy & 0x0f));
		}
		else {
			/* Julian format */
			/* TODO test this, unsure if the day part is correct, i.e. how we use the two bytes mm and dd to calculate the number of julian days */
			snprintf(value, 127, "%d%d%d%d:%d%d%d", (((cc & 0x7f) & 0xf0) >> 4), ((cc & 0x7f) & 0x0f), ((yy & 0xf0) >> 4), (yy & 0x0f), (mm & 0x0f), ((dd & 0xf0) >> 4), (dd & 0x0f));
		}
	}
	else if (format == 0xe1) {
		/* Time */
		/* Format is packed BCD for each byte */
		cc = entry[0];
		ss = entry[1];
		mm = entry[2];
		hh = entry[3];

		snprintf(value, 127, "%d%d:%d%d:%d%d.%d%d", ((hh & 0xf0) >> 4), (hh & 0x0f), ((mm & 0xf0) >> 4), (mm & 0x0f), ((ss & 0xf0) >> 4), (ss & 0x0f), ((cc & 0xf0) >> 4), (cc & 0x0f));
	}
	else {
		/* Unknown format */
		snprintf(value, 127, "???");
		return;
	}
	return;
}


void init_ups_alarm_map(const unsigned char *map, unsigned char len)
{
	unsigned int iIndex = 0;
	unsigned int alarm = 0;

	/* In case of debug - make explanation of values */
	upsdebugx(2, "Index\tAlarm\tSupported");

	/* Loop thru map */
	for (iIndex = 0; iIndex < len && iIndex < BCMXCP_ALARM_MAP_MAX / 8; iIndex++)
	{
		/* Bit 0 */
		if (set_alarm_support_in_alarm_map(map, iIndex, 0x01, iIndex * 8, alarm) == TRUE)
			alarm++;
		/* Bit 1 */
		if (set_alarm_support_in_alarm_map(map, iIndex, 0x02, iIndex * 8 + 1, alarm) == TRUE)
			alarm++;
		/* Bit 2 */
		if (set_alarm_support_in_alarm_map(map, iIndex, 0x04, iIndex * 8 + 2, alarm) == TRUE)
			alarm++;
		/* Bit 3 */
		if (set_alarm_support_in_alarm_map(map, iIndex, 0x08, iIndex * 8 + 3, alarm) == TRUE)
			alarm++;
		/* Bit 4 */
		if (set_alarm_support_in_alarm_map(map, iIndex, 0x10, iIndex * 8 + 4, alarm) == TRUE)
			alarm++;
		/* Bit 5 */
		if (set_alarm_support_in_alarm_map(map, iIndex, 0x20, iIndex * 8 + 5, alarm) == TRUE)
			alarm++;
		/* Bit 6 */
		if (set_alarm_support_in_alarm_map(map, iIndex, 0x40, iIndex * 8 + 6, alarm) == TRUE)
			alarm++;
		/* Bit 7 */
		if (set_alarm_support_in_alarm_map(map, iIndex, 0x80, iIndex * 8 + 7, alarm) == TRUE)
			alarm++;
	}
	upsdebugx(2, "\n");
}

bool_t set_alarm_support_in_alarm_map(
	const unsigned char *map,
	const unsigned int mapIndex,
	const unsigned int bitmask,
	const unsigned int alarmMapIndex,
	const unsigned int alarmBlockIndex
) {
	/* Check what the alarm block tells about the support for the alarm */
	if (map[mapIndex] & bitmask)
	{
		/* Set alarm active */
		assert (alarmBlockIndex < INT_MAX);
		bcmxcp_alarm_map[alarmMapIndex].alarm_block_index = (int)alarmBlockIndex;
	}
	else
	{
		/* Set alarm inactive */
		bcmxcp_alarm_map[alarmMapIndex].alarm_block_index = -1;
	}

	/* Return if the alarm was supported or not */
	if (bcmxcp_alarm_map[alarmMapIndex].alarm_block_index >= 0) {
		/* Debug info */
		upsdebugx(2, "%04d\t%s\tYes", bcmxcp_alarm_map[alarmMapIndex].alarm_block_index, bcmxcp_alarm_map[alarmMapIndex].alarm_desc);
		return TRUE;
		}
	else {
		/* Debug info */
		upsdebugx(3, "%04d\t%s\tNo", bcmxcp_alarm_map[alarmMapIndex].alarm_block_index, bcmxcp_alarm_map[alarmMapIndex].alarm_desc);
		return FALSE;
	}
}

unsigned char init_outlet(unsigned char len)
{
	/* Note: (bug?) the argument "len" is not practically used in code below
	 * Callers know it as "outlet_block_len" in their routines and it is greater than 8
	 */
	unsigned char answer[PW_ANSWER_MAX_SIZE];
	int iIndex = 0;
	ssize_t res;
	unsigned char num_outlet, size_outlet, num;
	unsigned char outlet_num, outlet_state;
	uint16_t auto_dly_off, auto_dly_on;
	char outlet_name[64];

	res = command_read_sequence(PW_OUT_MON_BLOCK_REQ, answer);
	if (res <= 0)
		fatal_with_errno(EXIT_FAILURE, "Could not communicate with the ups");
	else
		upsdebugx(1, "init_outlet(%i), res=%" PRIiSIZE, len, res);

	num_outlet = answer[iIndex++];
	upsdebugx(2, "Number of outlets: %u", num_outlet);

	size_outlet = answer[iIndex++];
	upsdebugx(2, "Number of bytes: %u", size_outlet);

	for (num = 1 ; num <= num_outlet ; num++) {
		outlet_num = answer[iIndex++];
		upsdebugx(2, "Outlet number: %u", outlet_num);
		snprintf(outlet_name, sizeof(outlet_name)-1, "outlet.%u.id", num);
		dstate_setinfo(outlet_name, "%u", outlet_num);

		outlet_state = answer[iIndex++];
		upsdebugx(2, "Outlet state: %u", outlet_state);
		snprintf(outlet_name, sizeof(outlet_name)-1, "outlet.%u.status", num);
		if (outlet_state>0 && outlet_state <9)
			dstate_setinfo(outlet_name, "%s", OutletStatus[outlet_state]);

		auto_dly_off = get_word(answer+iIndex);
		iIndex += 2;
		upsdebugx(2, "Auto delay off: %u", auto_dly_off);
		snprintf(outlet_name, sizeof(outlet_name)-1, "outlet.%u.delay.shutdown", num);
		dstate_setinfo(outlet_name, "%u", auto_dly_off);
		dstate_setflags(outlet_name, ST_FLAG_RW | ST_FLAG_STRING);
		dstate_setaux(outlet_name, 5);

		auto_dly_on = get_word(answer+iIndex);
		iIndex += 2;
		upsdebugx(2, "Auto delay on: %u", auto_dly_on);
		snprintf(outlet_name, sizeof(outlet_name)-1, "outlet.%u.delay.start", num);
		dstate_setinfo(outlet_name, "%u", auto_dly_on);
		dstate_setflags(outlet_name, ST_FLAG_RW | ST_FLAG_STRING);
		dstate_setaux(outlet_name, 5);
	}

	return num_outlet;
}

void init_ext_vars(void)
{
	unsigned char answer[PW_ANSWER_MAX_SIZE], cbuf[5];
	ssize_t length = 0;
	int index = 0;

	send_write_command(AUTHOR, 4);

	sleep(PW_SLEEP);	/* Need to. Have to wait at least 0,25 sec max 16 sec */

	cbuf[0] = PW_SET_CONF_COMMAND;
	cbuf[1] = PW_CONF_REQ;
	cbuf[2] = 0x0;
	cbuf[3] = 0x0;

	length = command_write_sequence(cbuf, 4, answer);
	if (length <= 0)
		fatal_with_errno(EXIT_FAILURE, "Could not communicate with the ups");
	if (length < 4)	/* UPS doesn't have configurable vars */
		return;

	for (index=3; index < length; index++) {
		switch(answer[index]) {
			case PW_CONF_LOW_DEV_LIMIT:
				dstate_setinfo("input.transfer.boost.high", "%d", 0);
				dstate_setflags("input.transfer.boost.high", ST_FLAG_RW | ST_FLAG_STRING);
				dstate_setaux("input.transfer.boost.high", 3);
				break;

			case PW_CONF_HIGH_DEV_LIMIT:
				dstate_setinfo("input.transfer.trim.low", "%d", 0);
				dstate_setflags("input.transfer.trim.low", ST_FLAG_RW | ST_FLAG_STRING);
				dstate_setaux("input.transfer.trim.low", 3);
				break;

			case PW_CONF_LOW_BATT:
				dstate_setinfo("battery.runtime.low", "%d", 0);
				dstate_setflags("battery.runtime.low", ST_FLAG_RW | ST_FLAG_STRING);
				dstate_setaux("battery.runtime.low", 2);
				break;

			case PW_CONF_BEEPER:
				dstate_addcmd("beeper.disable");
				dstate_addcmd("beeper.enable");
				dstate_addcmd("beeper.mute");
				break;

			case PW_CONF_RETURN_DELAY:
				dstate_setinfo("input.transfer.delay", "%d", 0);
				dstate_setflags("input.transfer.delay", ST_FLAG_RW | ST_FLAG_STRING);
				dstate_setaux("input.transfer.delay", 5);
				break;

			case PW_CONF_RETURN_CAP:
				dstate_setinfo("battery.charge.restart", "%d", 0);
				dstate_setflags("battery.charge.restart", ST_FLAG_RW | ST_FLAG_STRING);
				dstate_setaux("battery.charge.restart", 3);
				break;

			case PW_CONF_MAX_TEMP:
				dstate_setinfo("ambient.temperature.high", "%d", 0);
				dstate_setflags("ambient.temperature.high", ST_FLAG_RW | ST_FLAG_STRING);
				dstate_setaux("ambient.temperature.high", 3);
				break;

			case PW_CONF_NOMINAL_OUT_VOLTAGE:
				dstate_setinfo("output.voltage.nominal", "%d", 0);
				dstate_setflags("output.voltage.nominal", ST_FLAG_RW | ST_FLAG_STRING);
				dstate_setaux("output.voltage.nominal", 3);
				break;

			case PW_CONF_SLEEP_TH_LOAD:
				dstate_setinfo("battery.energysave.load", "%d", 0);
				dstate_setflags("battery.energysave.load", ST_FLAG_RW | ST_FLAG_STRING);
				dstate_setaux("battery.energysave.load", 3);
				break;

			case PW_CONF_SLEEP_DELAY:
				dstate_setinfo("battery.energysave.delay", "%d", 0);
				dstate_setflags("battery.energysave.delay", ST_FLAG_RW | ST_FLAG_STRING);
				dstate_setaux("battery.energysave.delay", 3);
				break;

			case PW_CONF_BATT_STRINGS:
				dstate_setinfo("battery.packs", "%d", 0);
				dstate_setflags("battery.packs", ST_FLAG_RW | ST_FLAG_STRING);
				dstate_setaux("battery.packs", 1);
				break;

			default:
				break;
		}
	}
}


void init_config(void)
{
	unsigned char answer[PW_ANSWER_MAX_SIZE];
	uint16_t voltage = 0, frequency = 0, tmp = 0;
	ssize_t res;
	char sValue[17];
	char sPartNumber[17];

	res = command_read_sequence(PW_CONFIG_BLOCK_REQ, answer);
	if (res <= 0)
		fatal_with_errno(EXIT_FAILURE, "Could not communicate with the ups");

	/* Get validation mask for status bitmap */
	bcmxcp_status.topology_mask = answer[BCMXCP_CONFIG_BLOCK_HW_MODULES_INSTALLED_BYTE3];

	/* Nominal output voltage of ups */
	voltage = get_word((answer + BCMXCP_CONFIG_BLOCK_NOMINAL_OUTPUT_VOLTAGE));
	if (voltage != 0)
		dstate_setinfo("output.voltage.nominal", "%u", voltage);

	/* Nominal Output Frequency */
	frequency = get_word((answer + BCMXCP_CONFIG_BLOCK_NOMINAL_OUTPUT_FREQ));
	if (frequency != 0)
		dstate_setinfo("output.frequency.nominal", "%u", frequency);

	/*Number of EBM*/
	tmp = (uint16_t) *(answer + BCMXCP_CONFIG_BLOCK_BATTERY_DATA_WORD3);
	if (tmp != 0)
		dstate_setinfo("battery.packs", "%u", tmp);

#ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_TRUNCATION
#pragma GCC diagnostic push
#endif
#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_TRUNCATION
#pragma GCC diagnostic ignored "-Wformat-truncation"
#endif
	/* NOTE: We intentionally limit the amount of characters picked from
	 * "answer" into "sValue" and "sPartNumber" buffers (16 byte + NUL).
	 */

	/* UPS serial number */
	snprintf(sValue, sizeof(sValue), "%s", answer + BCMXCP_CONFIG_BLOCK_SERIAL_NUMBER);
	if (sValue[0] != '\0')
		dstate_setinfo("ups.serial", "%s", sValue);

	/* UPS Part Number*/
	snprintf(sPartNumber, sizeof(sPartNumber), "%s", answer + BCMXCP_CONFIG_BLOCK_PART_NUMBER);
	if (sPartNumber[0] != '\0')
		dstate_setinfo("device.part", "%s", sPartNumber);
#ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_TRUNCATION
#pragma GCC diagnostic pop
#endif
}

void init_limit(void)
{
	unsigned char answer[PW_ANSWER_MAX_SIZE];
	uint16_t value;
	ssize_t res;

	res = command_read_sequence(PW_LIMIT_BLOCK_REQ, answer);
	if (res <= 0) {
		fatal_with_errno(EXIT_FAILURE, "Could not communicate with the ups");
	}

	/* Nominal input voltage */
	value = get_word((answer + BCMXCP_EXT_LIMITS_BLOCK_NOMINAL_INPUT_VOLTAGE));
	if (value != 0) {
		dstate_setinfo("input.voltage.nominal", "%u", value);
	}

	/* Nominal input frequency */
	value = get_word((answer + BCMXCP_EXT_LIMITS_BLOCK_NOMINAL_INPUT_FREQ));
	if (value != 0) {
		uint16_t fnom = value;
		dstate_setinfo("input.frequency.nominal", "%u", value);

		/* Input frequency deviation */
		value = get_word((answer + BCMXCP_EXT_LIMITS_BLOCK_FREQ_DEV_LIMIT));

		if (value != 0) {
			value /= 100;
			dstate_setinfo("input.frequency.low",  "%d", fnom - value);
			dstate_setinfo("input.frequency.high", "%d", fnom + value);
		}
	}

	/* Bypass Voltage Low Deviation Limit / Transfer to Boost Voltage */
	value = get_word((answer + BCMXCP_EXT_LIMITS_BLOCK_VOLTAGE_LOW_DEV_LIMIT));
	if (value != 0) {
		dstate_setinfo("input.transfer.boost.high", "%u", value);
	}

	/* Bypass Voltage High Deviation Limit / Transfer to Buck Voltage */
	value = get_word((answer + BCMXCP_EXT_LIMITS_BLOCK_VOLTAGE_HIGE_DEV_LIMIT));
	if (value != 0) {
		dstate_setinfo("input.transfer.trim.low", "%u", value);
	}

	/* Low battery warning */
	bcmxcp_status.lowbatt = answer[BCMXCP_EXT_LIMITS_BLOCK_LOW_BATT_WARNING] * 60;

	/* Check if we should warn the user that her shutdown delay is too long? */
	if (bcmxcp_status.shutdowndelay > bcmxcp_status.lowbatt)
		upslogx(LOG_WARNING,
			"Shutdown delay longer than battery capacity when Low Battery "
			"warning is given. (max %u seconds)", bcmxcp_status.lowbatt);

	/* Horn Status: */
	value = answer[BCMXCP_EXT_LIMITS_BLOCK_HORN_STATUS];
	if (value <= 2) {
		dstate_setinfo("ups.beeper.status", "%s", horn_stat[value]);
	}

	/* Minimum Supported Input Voltage */
	value = get_word((answer + BCMXCP_EXT_LIMITS_BLOCK_MIN_INPUT_VOLTAGE));
	if (value != 0) {
		dstate_setinfo("input.transfer.low", "%u", value);
	}

	/* Maximum Supported Input Voltage */
	value = get_word((answer + BCMXCP_EXT_LIMITS_BLOCK_MAX_INPUT_VOLTAGE));
	if (value != 0) {
		dstate_setinfo("input.transfer.high", "%u", value);
	}

	/* Ambient Temperature Lower Alarm Limit  */
	value = answer[BCMXCP_EXT_LIMITS_BLOCK_AMBIENT_TEMP_LOW];
	if (value != 0) {
		dstate_setinfo("ambient.temperature.low", "%u", value);
	}

	/* Ambient Temperature Upper Alarm Limit  */
	value = answer[BCMXCP_EXT_LIMITS_BLOCK_AMBIENT_TEMP_HIGE];
	if (value != 0) {
		dstate_setinfo("ambient.temperature.high", "%u", value);
	}

	/*Sleep minimum load*/
	value = answer[BCMXCP_EXT_LIMITS_BLOCK_SLEEP_TH_LOAD];
	if (value != 0) {
		dstate_setinfo("battery.energysave.load", "%u", value);
	}

	/* Sleep delay*/
	value = answer[BCMXCP_EXT_LIMITS_BLOCK_SLEEP_DELAY];
	if (value != 0) {
		dstate_setinfo("battery.energysave.delay", "%u", value);
	}

	/* Low batt minutes warning*/
	value = answer[BCMXCP_EXT_LIMITS_BLOCK_LOW_BATT_WARNING];
	if (value != 0) {
		dstate_setinfo("battery.runtime.low", "%u", value);
	}

	/* Return to mains delay */
	value = get_word(answer + BCMXCP_EXT_LIMITS_BLOCK_RETURN_STAB_DELAY);
	if (value != 0) {
		dstate_setinfo("input.transfer.delay", "%u", value);
	}

	/* Minimum return capacity*/
	value = answer[BCMXCP_EXT_LIMITS_BLOCK_BATT_CAPACITY_RETURN];
	if (value != 0) {
		dstate_setinfo("battery.charge.restart", "%u", value);
	}

}

void init_topology(void)
{
	unsigned char answer[PW_ANSWER_MAX_SIZE];
	const char* nutvalue;
	uint16_t value;
	ssize_t res;

	res = command_read_sequence(PW_UPS_TOP_DATA_REQ, answer);
	if (res <= 0)
		fatal_with_errno(EXIT_FAILURE, "Could not communicate with the ups");

	value = get_word(answer);

	if ((nutvalue = nut_find_infoval(topology_info, value, TRUE)) != NULL) {
		dstate_setinfo("ups.description", "%s", nutvalue);
	}
}

void init_system_test_capabilities(void)
{
	unsigned char answer[PW_ANSWER_MAX_SIZE], cbuf[5];
	const char* nutvalue;
	ssize_t res;
	int value, i;

	/* Query what system test capabilities are supported */
	send_write_command(AUTHOR, 4);

	sleep(PW_SLEEP); /* Need to. Have to wait at least 0,25 sec max 16 sec */

	cbuf[0] = PW_INIT_SYS_TEST;
	cbuf[1] = PW_SYS_TEST_REPORT_CAPABILITIES;
	res = command_write_sequence(cbuf, 2, answer);
	if (res <= 0) {
		upslogx(LOG_ERR, "Short read from UPS");
		return;
	}

	if ((unsigned char)answer[0] != BCMXCP_RETURN_ACCEPTED) {
		upsdebugx(2, "System test capabilities list not supported");
		return;
	}

	/* Add instcmd for system test capabilities */
	for (i = 3; i < res; i++) {
		value = answer[i];
		if ((nutvalue = nut_find_infoval(system_test_info, value, TRUE)) != NULL) {
			upsdebugx(2, "Added support for instcmd %s", nutvalue);
			dstate_addcmd(nutvalue);
		}
	}
}

void upsdrv_initinfo(void)
{
	unsigned char answer[PW_ANSWER_MAX_SIZE];
	char *pTmp;
	char outlet_name[64];
	char power_rating[10];
	ssize_t res;
	unsigned int ncpu = 0;
	size_t buf;
	uint16_t iRating = 0, iIndex = 0, len;
	uint16_t conf_block_len = 0, alarm_block_len = 0, cmd_list_len = 0, topology_block_len = 0;
	bool_t got_cmd_list = FALSE;

	/* Init BCM/XCP command descriptions */
	init_command_map();

	/* Init BCM/XCP alarm descriptions */
	init_alarm_map();

	/* Get vars from ups.conf */
	if (getval("shutdown_delay") != NULL) {
		int tmp = atoi(getval("shutdown_delay"));
		if (tmp >= 0) {
			bcmxcp_status.shutdowndelay = (unsigned int)tmp;
		} else {
			fatal_with_errno(EXIT_FAILURE,
				"Invalid setting for shutdown_delay: %s",
				getval("shutdown_delay"));
		}
	} else {
		bcmxcp_status.shutdowndelay = 120;
	}

	/* Get information on UPS from UPS ID block */
	res = command_read_sequence(PW_ID_BLOCK_REQ, answer);
	if (res <= 0)
		fatal_with_errno(EXIT_FAILURE, "Could not communicate with the ups");

	/* Get number of CPU's in ID block */
	len = answer[iIndex++];

	/* No overflow checks, len value is byte-sized here */
	buf = len * 11;
	pTmp = (char *)xmalloc(buf+1);

	pTmp[0] = 0;
	/* If there is one or more CPU number, get it */
	if (len > 0) {
		do {
			if ((answer[iIndex] != 0x00) || (answer[iIndex+1] != 0x00)) {
				/* Get the ups firmware. The major number is in the last byte, the minor is in the first */
				snprintfcat(pTmp, buf+1, "%s%02x.%02x ", cpu_name[ncpu], answer[iIndex+1], answer[iIndex]);
			}
			iIndex += 2;
			len--;
			ncpu++;

		} while ((len > 0) && (ncpu <= 5));

		dstate_setinfo("ups.firmware", "%s", pTmp);

		/* Increment index to point at end of CPU bytes. */
		iIndex += len * 2;
	}

	free(pTmp);

	/* Get rating in kVA, if present */
	if ((iRating = answer[iIndex++]) > 0)
		iRating *= 1000;
	else
	{
		/* The rating is given as 2 byte VA */
		iRating = get_word(answer+iIndex) * 50;
		iIndex += 2;
	}
	dstate_setinfo("ups.power.nominal", "%u", iRating);

	/* Get information on Phases from UPS */
	nphases = (answer[iIndex++]);
	dstate_setinfo("output.phases", "%d", nphases);

	/* Init BCM/XCP <-> NUT meter map */
	init_meter_map();

	/* Skip UPS' phase angle, as NUT do not care */
	iIndex += 1;

	/* Set manufacturer name */
	dstate_setinfo("ups.mfr", "Eaton");

	/* Get length of UPS description */
	len = answer[iIndex++];

	/* Extract and reformat the model string */
	pTmp = (char *)xmalloc(len+15);
	snprintf(pTmp, len + 1, "%s", answer + iIndex);
	pTmp[len+1] = 0;
	iIndex += len;
	/* power rating in the model name is in the form "<rating>i"
	 * ie "1500i", "500i", ...
	 * some models already includes it, so check to avoid duplication */
	snprintf(power_rating, sizeof(power_rating), "%ii", iRating);
	if (strstr(pTmp, power_rating) == NULL) {
		snprintfcat(pTmp, len+10, " %s", power_rating);
	}
	dstate_setinfo("ups.model", "%s", str_rtrim(pTmp, ' '));
	free(pTmp);

	/* Get meter map info from ups, and init our map */
	len = answer[iIndex++];
	upsdebugx(2, "Length of meter map: %u\n", len);
	/* Here and below, no range check needed - just initialized from unsigned char array */
	init_ups_meter_map(answer+iIndex, (unsigned char)len);
	iIndex += len;

	/* Next is alarm map */
	len = answer[iIndex++];
	upsdebugx(2, "Length of alarm map: %u\n", len);
	init_ups_alarm_map(answer+iIndex, (unsigned char)len);
	iIndex += len;

	/* Then the Config_block_length */
	conf_block_len = get_word(answer+iIndex);
	upsdebugx(2, "Length of Config_block: %u\n", conf_block_len);
	iIndex += 2;

	/* Next is statistics map */
	len = answer[iIndex++];
	upsdebugx(2, "Length of statistics map: %u\n", len);
	/* init_statistics_map(answer+iIndex, (unsigned char)len); */
	iIndex += len;

	/* Size of the alarm history log */
	len = get_word(answer+iIndex);
	upsdebugx(2, "Length of alarm history log: %u\n", len);
	iIndex += 2;

	/* Size of custom event log, always 0 according to spec */
	iIndex += 2;
	/* Size of topology block */
	topology_block_len = get_word(answer+iIndex);
	upsdebugx(2, "Length of topology block: %u\n", topology_block_len);
	iIndex += 2;

	/* Maximum supported command length */
	len = answer[iIndex++];
	upsdebugx(2, "Length of max supported command length: %u\n", len);

	/* Size of command list block */
	if (iIndex < (unsigned int)res)
		cmd_list_len = get_word(answer+iIndex);
	upsdebugx(2, "Length of command list: %u\n", cmd_list_len);
	iIndex += 2;

	/* Size of outlet monitoring block */
	if (iIndex < (unsigned int)res)
		outlet_block_len = get_word(answer+iIndex);
	upsdebugx(2, "Length of outlet_block: %u\n", outlet_block_len);
	iIndex += 2;

	/* Size of the alarm block */
	if (iIndex < (unsigned int)res)
		alarm_block_len = get_word(answer+iIndex);
	upsdebugx(2, "Length of alarm_block: %u\n", alarm_block_len);
	/* End of UPS ID block request */

	/* Due to a bug in PW5115 firmware, we need to use blocklength > 8.
	The protocol state that outlet block is only implemented if there is
	at least 2 outlet block. 5115 has only one outlet, but has outlet block! */
	if (outlet_block_len > 8) {
		if (outlet_block_len > 255)
			fatal_with_errno(EXIT_FAILURE, "outlet_block_len overflow: %u", outlet_block_len);
		len = init_outlet((unsigned char)outlet_block_len /* arg ignored */);

		for (res = 1 ; (unsigned int)res <= (unsigned int)len ; res++) {
			snprintf(outlet_name, sizeof(outlet_name) - 1, "outlet.%" PRIiSIZE ".shutdown.return", res);
			dstate_addcmd(outlet_name);
			snprintf(outlet_name, sizeof(outlet_name) - 1, "outlet.%" PRIiSIZE ".load.on", res);
			dstate_addcmd(outlet_name);
			snprintf(outlet_name, sizeof(outlet_name) - 1, "outlet.%" PRIiSIZE ".load.off", res);
			dstate_addcmd(outlet_name);
		}
	}

	/* Get information on UPS configuration */
	init_config();

	/* Get information on UPS extended limits */
	init_limit();

	/* Get information on UPS commands */
	if (cmd_list_len)
		got_cmd_list = init_command(cmd_list_len);
	/* Add default commands if we were not able to query UPS for support */
	if (got_cmd_list == FALSE) {
		dstate_addcmd("shutdown.return");
		dstate_addcmd("shutdown.stayoff");
		dstate_addcmd("test.battery.start");
	}
	/* Get information on UPS topology */
	if (topology_block_len)
		init_topology();
	/* Get information on system test capabilities */
	if (bcmxcp_command_map[PW_INIT_SYS_TEST].command_byte > 0) {
		init_system_test_capabilities();
	}

	/* Get information about configurable external variables*/
	init_ext_vars();

	upsh.instcmd = instcmd;
	upsh.setvar = setvar;
}

void upsdrv_updateinfo(void)
{
	unsigned char answer[PW_ANSWER_MAX_SIZE];
	unsigned char status, topology;
	char sValue[128];
	int iIndex;
	ssize_t res;
	uint16_t value;
	bool_t has_ups_load = FALSE;
	int batt_status = 0;
	const char *nutvalue;
	float calculated_load;

	/* Get info from UPS */
	res = command_read_sequence(PW_METER_BLOCK_REQ, answer);

	if (res <= 0) {
		upslogx(LOG_ERR, "Short read from UPS");
		dstate_datastale();
		return;
	}

	/* Loop thru meter map, get all data UPS is willing to offer */
	for (iIndex = 0; iIndex < BCMXCP_METER_MAP_MAX; iIndex++) {
		if (bcmxcp_meter_map[iIndex].format != 0 && bcmxcp_meter_map[iIndex].nut_entity != NULL) {
			decode_meter_map_entry(
				answer + bcmxcp_meter_map[iIndex].meter_block_index,
				bcmxcp_meter_map[iIndex].format,
				sValue);

			/* Set result */
			dstate_setinfo(bcmxcp_meter_map[iIndex].nut_entity, "%s", sValue);

			/* Check if we read ups.load */
			if (has_ups_load == FALSE && !strcasecmp(bcmxcp_meter_map[iIndex].nut_entity, "ups.load")) {
				has_ups_load = TRUE;
			}
		}
	}

	/* Calculate ups.load if UPS does not report it directly */
	if (has_ups_load == FALSE) {
		calculated_load = calculate_ups_load(answer);
		if (calculated_load >= 0.0f) {
			dstate_setinfo("ups.load", "%5.1f", calculated_load);
		}
	}

	/* Due to a bug in PW5115 firmware, we need to use blocklength > 8.
	The protocol state that outlet block is only implemented if there is
	at least 2 outlet block. 5115 has only one outlet, but has outlet block. */
	if (outlet_block_len > 8) {
		if (outlet_block_len > 255)
			fatal_with_errno(EXIT_FAILURE, "outlet_block_len overflow: %u", outlet_block_len);
		init_outlet((unsigned char)outlet_block_len /* arg ignored */);
	}

	/* Get alarm info from UPS */
	res = command_read_sequence(PW_CUR_ALARM_REQ, answer);
	if (res <= 0) {
		upslogx(LOG_ERR, "Short read from UPS");
		dstate_datastale();
		return;
	}
	else
	{
		bcmxcp_status.alarm_on_battery = 0;
		bcmxcp_status.alarm_low_battery = 0;
		bcmxcp_status.alarm_replace_battery = 0;

		/* Set alarms */
		alarm_init();

		/* Loop thru alarm map, get all alarms UPS is willing to offer */
		for (iIndex = 0; iIndex < BCMXCP_ALARM_MAP_MAX; iIndex++) {
			if (bcmxcp_alarm_map[iIndex].alarm_block_index >= 0 && bcmxcp_alarm_map[iIndex].alarm_desc != NULL) {
				if (answer[bcmxcp_alarm_map[iIndex].alarm_block_index] > 0) {
					alarm_set(bcmxcp_alarm_map[iIndex].alarm_desc);

					if (iIndex == BCMXCP_ALARM_UPS_ON_BATTERY) {
						bcmxcp_status.alarm_on_battery = 1;
					}
					else if (iIndex == BCMXCP_ALARM_BATTERY_LOW) {
						bcmxcp_status.alarm_low_battery = 1;
					}
					else if (iIndex == BCMXCP_ALARM_BATTERY_TEST_FAILED) {
						bcmxcp_status.alarm_replace_battery = 1;
					}
					else if (iIndex == BCMXCP_ALARM_BATTERY_NEEDS_SERVICE) {
						bcmxcp_status.alarm_replace_battery = 1;
					}
				}
			}
		}

		/* Confirm alarms */
		alarm_commit();
	}

	/* Get status info from UPS */
	res = command_read_sequence(PW_STATUS_REQ, answer);
	if (res <= 0)
	{
		upslogx(LOG_ERR, "Short read from UPS");
		dstate_datastale();
		return;
	}
	else
	{

		/* Get overall status */
		memcpy(&status, answer, sizeof(status));

		/* Get topology status bitmap, validate */
		memcpy(&topology, answer+1, sizeof(topology));
		topology &= bcmxcp_status.topology_mask;

		/* Set status */
		status_init();

		switch (status) {
			case BCMXCP_STATUS_ONLINE: /* On line, everything is fine */
				status_set("OL");
				break;
			case BCMXCP_STATUS_ONBATTERY: /* Off line */
				if (bcmxcp_status.alarm_on_battery == 0)
					status_set("OB");
				break;
			case BCMXCP_STATUS_OVERLOAD: /* Overload */
				status_set("OL");
				status_set("OVER");
				break;
			case BCMXCP_STATUS_TRIM: /* Trim */
				status_set("OL");
				status_set("TRIM");
				break;
			case BCMXCP_STATUS_BOOST1:
			case BCMXCP_STATUS_BOOST2: /* Boost */
				status_set("OL");
				status_set("BOOST");
				break;
			case BCMXCP_STATUS_BYPASS: /* Bypass */
				status_set("OL");
				status_set("BYPASS");
				break;
			case BCMXCP_STATUS_OFF: /* Mostly off */
				status_set("OFF");
				break;
			default: /* Unknown, assume it is OK... */
				status_set("OL");
				break;
		}

		/* We might have to modify status based on topology status */
		if ((topology & 0x20) && bcmxcp_status.alarm_low_battery == 0)
			status_set("LB");

		/* And finally, we might need to modify status based on alarms - the most correct way */
		if (bcmxcp_status.alarm_on_battery)
			status_set("OB");
		if (bcmxcp_status.alarm_low_battery)
			status_set("LB");
		if (bcmxcp_status.alarm_replace_battery)
			status_set("RB");

		status_commit();
	}

	/* Get battery info from UPS, if exist */
	res = command_read_sequence(PW_BATTERY_REQ, answer);
	if (res <= 0)
	{
		upsdebugx(1, "Failed to read Battery Status from UPS");
	}
	else
	{
		/* Only parse the status (first byte)
		 *  Powerware 5115 RM output:
		 *   02 00 78 1d 42 00 e0 17 42 1e 00 00 00 00 00 00 00 00 00 01 03
		 *  Powerware 9130 output:
		 *   03 0a d7 25 42 0a d7 25 42 00 9a 19 6d 43 cd cc 4c 3e 01 00 01 03
		 */
		upsdebug_hex(2, "Battery Status", answer, (size_t)res);
		batt_status = answer[BCMXCP_BATTDATA_BLOCK_BATT_TEST_STATUS];

		if ((nutvalue = nut_find_infoval(batt_test_info, batt_status, TRUE)) != NULL) {
			dstate_setinfo("ups.test.result", "%s", nutvalue);
			upsdebugx(2, "Battery Status = %s (%i)", nutvalue, batt_status);
		}
		else {
			upsdebugx(1, "Failed to extract Battery Status from answer");
		}

		/*Extracting internal batteries ABM status*/
		/*Placed first in ABM statuses list. For examples above - on position BCMXCP_BATTDATA_BLOCK_NUMBER_OF_STRINGS (18):
		PW5115RM - 0 - no external strings, no status bytes,
		so next byte (19) - number of ABM statuses, next (20) - first ABM Status for internal batteries.

		PW9130 - 1 - one external string, so one additional status byte (#19 - 00 - no test run), next(20) - number of ABM statuses,
		next (21) - ABM Status for internal batteries.
		*/
		value =
			*(answer + BCMXCP_BATTDATA_BLOCK_NUMBER_OF_STRINGS +
			  *(answer + BCMXCP_BATTDATA_BLOCK_NUMBER_OF_STRINGS) * 1 + 2);
		upsdebugx(2, "ABM Status = %u ", value);
		if (value < 5)
			dstate_setinfo("battery.charger.status", "%s", ABMStatus[value-1]);
	}


	res = command_read_sequence(PW_LIMIT_BLOCK_REQ, answer);
	if (res <= 0) {
		upsdebugx(1, "Failed to read EXT LIMITs from UPS");
	} else
	{
		/* Nominal input voltage */
		value = get_word((answer + BCMXCP_EXT_LIMITS_BLOCK_NOMINAL_INPUT_VOLTAGE));

		if (value != 0) {
			dstate_setinfo("input.voltage.nominal", "%u", value);
		}

		/* Bypass Voltage Low Deviation Limit / Transfer to Boost Voltage */
		value = get_word((answer + BCMXCP_EXT_LIMITS_BLOCK_VOLTAGE_LOW_DEV_LIMIT));

		if (value != 0) {
			dstate_setinfo("input.transfer.boost.high", "%u", value);
		}

		/* Bypass Voltage High Deviation Limit / Transfer to Buck Voltage */
		value = get_word((answer + BCMXCP_EXT_LIMITS_BLOCK_VOLTAGE_HIGE_DEV_LIMIT));

		if (value != 0) {
			dstate_setinfo("input.transfer.trim.low", "%u", value);
		}

		/* Minimum Supported Input Voltage */
		value = get_word((answer + BCMXCP_EXT_LIMITS_BLOCK_MIN_INPUT_VOLTAGE));

		if (value != 0) {
			dstate_setinfo("input.transfer.low", "%u", value);
		}

		/* Maximum Supported Input Voltage */
		value = get_word((answer + BCMXCP_EXT_LIMITS_BLOCK_MAX_INPUT_VOLTAGE));

		if (value != 0) {
			dstate_setinfo("input.transfer.high", "%u", value);
		}

		/* Horn Status: */
		value = answer[BCMXCP_EXT_LIMITS_BLOCK_HORN_STATUS];

		if (value <= 2) {
			dstate_setinfo("ups.beeper.status", "%s", horn_stat[value]);
		}
		/* AAmbient Temperature Upper Alarm Limit  */
		value = answer[BCMXCP_EXT_LIMITS_BLOCK_AMBIENT_TEMP_HIGE];

		if (value != 0) {
			dstate_setinfo("ambient.temperature.high", "%u", value);
		}

		/*Sleep minimum load*/
		value = answer[BCMXCP_EXT_LIMITS_BLOCK_SLEEP_TH_LOAD];
		if (value != 0) {
			dstate_setinfo("battery.energysave.load", "%u", value);
		}

		/* Sleep delay*/
		value = answer[BCMXCP_EXT_LIMITS_BLOCK_SLEEP_DELAY];
		if (value != 0) {
			dstate_setinfo("battery.energysave.delay", "%u", value);
		}

		/* Low batt minutes warning*/
		value = answer[BCMXCP_EXT_LIMITS_BLOCK_LOW_BATT_WARNING];
		if (value != 0) {
			dstate_setinfo("battery.runtime.low", "%u", value);
		}

		/* Return to mains delay */
		value = get_word(answer + BCMXCP_EXT_LIMITS_BLOCK_RETURN_STAB_DELAY);
		if (value != 0) {
			dstate_setinfo("input.transfer.delay", "%u", value);
		}

		/* Minimum return capacity*/
		value = answer[BCMXCP_EXT_LIMITS_BLOCK_BATT_CAPACITY_RETURN];
		if (value != 0) {
			dstate_setinfo("battery.charge.restart", "%u", value);
		}
	}

	res = command_read_sequence(PW_CONFIG_BLOCK_REQ, answer);
	if (res <= 0) {
		upsdebugx(1, "Failed to read CONF BLOCK from UPS");
	}
	else
	{
		/*Nominal output voltage*/
		value = get_word((answer + BCMXCP_CONFIG_BLOCK_NOMINAL_OUTPUT_VOLTAGE));

		if (value != 0)
			dstate_setinfo("output.voltage.nominal", "%u", value);
		/*Number of EBM*/
		value = (uint16_t) *(answer + BCMXCP_CONFIG_BLOCK_BATTERY_DATA_WORD3);
		if (value != 0)
			dstate_setinfo("battery.packs", "%u", value);

	}


	dstate_dataok();
}

float calculate_ups_load(const unsigned char *answer)
{
	char sValue[128];
	float output = 0, max_output = -FLT_MAX, fValue = -FLT_MAX;

	if (bcmxcp_meter_map[BCMXCP_METER_MAP_OUTPUT_VA].format != 0 &&             /* Output VA */
			bcmxcp_meter_map[BCMXCP_METER_MAP_OUTPUT_VA_BAR_CHART].format != 0) /* Max output VA */
	{
		decode_meter_map_entry(answer + bcmxcp_meter_map[BCMXCP_METER_MAP_OUTPUT_VA].meter_block_index,
					bcmxcp_meter_map[BCMXCP_METER_MAP_OUTPUT_VA].format, sValue);
		output = atof(sValue);
		decode_meter_map_entry(answer + bcmxcp_meter_map[BCMXCP_METER_MAP_OUTPUT_VA_BAR_CHART].meter_block_index,
					bcmxcp_meter_map[BCMXCP_METER_MAP_OUTPUT_VA_BAR_CHART].format, sValue);
		max_output = atof(sValue);
	}
	else if (bcmxcp_meter_map[BCMXCP_METER_MAP_LOAD_CURRENT_PHASE_A].format != 0 &&                 /* Output A */
					bcmxcp_meter_map[BCMXCP_METER_MAP_LOAD_CURRENT_PHASE_A_BAR_CHART].format != 0) /* Max output A */
	{
		decode_meter_map_entry(answer + bcmxcp_meter_map[BCMXCP_METER_MAP_LOAD_CURRENT_PHASE_A].meter_block_index,
					bcmxcp_meter_map[BCMXCP_METER_MAP_LOAD_CURRENT_PHASE_A].format, sValue);
		output = atof(sValue);
		decode_meter_map_entry(answer + bcmxcp_meter_map[BCMXCP_METER_MAP_LOAD_CURRENT_PHASE_A_BAR_CHART].meter_block_index,
					bcmxcp_meter_map[BCMXCP_METER_MAP_LOAD_CURRENT_PHASE_A_BAR_CHART].format, sValue);
		max_output = atof(sValue);
	}
	if (max_output > 0.0)
		fValue = 100 * (output / max_output);

	return fValue;
}

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

	upsdebugx(1, "upsdrv_shutdown...");

	/* First try to shutdown with delay;
	 * if the above doesn't work, try shutdown.stayoff */
	if (do_loop_shutdown_commands("shutdown.return,shutdown.stayoff", NULL) == STAT_INSTCMD_HANDLED) {
		/* Shutdown successful */
		if (handling_upsdrv_shutdown > 0)
			set_exit_flag(EF_EXIT_SUCCESS);
		return;
	}

	upslogx(LOG_ERR, "Shutdown failed!");
	if (handling_upsdrv_shutdown > 0)
		set_exit_flag(EF_EXIT_FAILURE);
}


static int instcmd(const char *cmdname, const char *extra)
{
	unsigned char answer[128], cbuf[6];
	char success_msg[40];
	char namebuf[MAX_NUT_NAME_LENGTH];
	char varname[32];
	const char *varvalue = NULL;
	ssize_t res;
	int sec, outlet_num;
	int sddelay = 0x03; /* outlet off in 3 seconds, by default */

	/* May be used in logging below, but not as a command argument */
	NUT_UNUSED_VARIABLE(extra);
	upsdebug_INSTCMD_STARTING(cmdname, extra);

	if (!strcasecmp(cmdname, "shutdown.return")) {
		send_write_command(AUTHOR, 4);

		sleep(PW_SLEEP); /* Need to. Have to wait at least 0,25 sec max 16 sec */

		cbuf[0] = PW_LOAD_OFF_RESTART;
		cbuf[1] = (unsigned char)(bcmxcp_status.shutdowndelay & 0x00ff); /* "delay" sec delay for shutdown, */
		cbuf[2] = (unsigned char)(bcmxcp_status.shutdowndelay >> 8);     /* high byte sec. From ups.conf. */

		upslog_INSTCMD_POWERSTATE_CHANGE(cmdname, extra);
		res = command_write_sequence(cbuf, 3, answer);

		sec = (256 * (unsigned char)answer[3]) + (unsigned char)answer[2];
		snprintf(success_msg, sizeof(success_msg)-1, "Going down in %d sec", sec);

		return decode_instcmd_exec(res, (unsigned char)answer[0], cmdname, success_msg);
	}

	if (!strcasecmp(cmdname, "shutdown.stayoff")) {
		send_write_command(AUTHOR, 4);

		sleep(PW_SLEEP); /* Need to. Have to wait at least 0,25 sec max 16 sec */

		upslog_INSTCMD_POWERSTATE_CHANGE(cmdname, extra);
		res = command_read_sequence(PW_UPS_OFF, answer);

		return decode_instcmd_exec(res, (unsigned char)answer[0], cmdname, "Going down NOW");
	}

	if (!strcasecmp(cmdname, "load.on")) {
		send_write_command(AUTHOR, 4);

		sleep(PW_SLEEP); /* Need to. Have to wait at least 0,25 sec max 16 sec */

		upslog_INSTCMD_POWERSTATE_MAYBE(cmdname, extra);
		res = command_read_sequence(PW_UPS_ON, answer);

		return decode_instcmd_exec(res, (unsigned char)answer[0], cmdname, "Enabling");
	}

	if (!strcasecmp(cmdname, "bypass.start")) {
		send_write_command(AUTHOR, 4);

		sleep(PW_SLEEP); /* Need to. Have to wait at least 0,25 sec max 16 sec */

		upslog_INSTCMD_POWERSTATE_MAYBE(cmdname, extra);
		res = command_read_sequence(PW_GO_TO_BYPASS, answer);

		return decode_instcmd_exec(res, (unsigned char)answer[0], cmdname, "Bypass enabled");
	}

	/* Note: test result will be parsed from Battery status block,
	 * part of the update loop, and published into ups.test.result
	 */
	if (!strcasecmp(cmdname, "test.battery.start")) {
		send_write_command(AUTHOR, 4);

		sleep(PW_SLEEP); /* Need to. Have to wait at least 0,25 sec max 16 sec */

		cbuf[0] = PW_INIT_BAT_TEST;
		cbuf[1] = 0x0A; /* 10 sec start delay for test.*/
		cbuf[2] = 0x1E; /* 30 sec test duration.*/

		upslog_INSTCMD_POWERSTATE_MAYBE(cmdname, extra);
		res = command_write_sequence(cbuf, 3, answer);

		return decode_instcmd_exec(res, (unsigned char)answer[0], cmdname, "Testing battery now");
		/* Get test info from UPS ?
		 * Should we wait for 50 sec and get the
		 * answer from the test.
		 * Or return, as we may lose line power
		 * and need to do a shutdown.*/
	}

	if (!strcasecmp(cmdname, "test.system.start")) {
		send_write_command(AUTHOR, 4);

		sleep(PW_SLEEP); /* Need to. Have to wait at least 0,25 sec max 16 sec */

		cbuf[0] = PW_INIT_SYS_TEST;
		cbuf[1] = PW_SYS_TEST_GENERAL;

		upslog_INSTCMD_POWERSTATE_MAYBE(cmdname, extra);
		res = command_write_sequence(cbuf, 2, answer);

		return decode_instcmd_exec(res, (unsigned char)answer[0], cmdname, "Testing system now");
	}

	if (!strcasecmp(cmdname, "test.panel.start")) {
		send_write_command(AUTHOR, 4);

		sleep(PW_SLEEP); /* Need to. Have to wait at least 0,25 sec max 16 sec */

		cbuf[0] = PW_INIT_SYS_TEST;
		cbuf[1] = PW_SYS_TEST_FLASH_LIGHTS;
		cbuf[2] = 0x0A; /* Flash and beep 10 times */
		res = command_write_sequence(cbuf, 3, answer);

		return decode_instcmd_exec(res, (unsigned char)answer[0], cmdname, "Testing panel now");
	}

	if (!strcasecmp(cmdname, "beeper.disable") || !strcasecmp(cmdname, "beeper.enable") || !strcasecmp(cmdname, "beeper.mute")) {
		send_write_command(AUTHOR, 4);

		sleep(PW_SLEEP);        /* Need to. Have to wait at least 0,25 sec max 16 sec */

		cbuf[0] = PW_SET_CONF_COMMAND;
		cbuf[1] = PW_CONF_BEEPER;
		switch (cmdname[7]) {

			case 'd':
			case 'D': {
				cbuf[2] = 0x0;          /*disable beeper*/
				break;
				}
			case 'e':
			case 'E': {
				cbuf[2] = 0x1;          /*enable beeper*/
				break;
				}
			case 'm':
			case 'M': {
				cbuf[2] = 0x2;
				break;                  /*mute beeper*/
				}
			default:
				break;
		}
		cbuf[3] = 0x0;          /*padding*/

		res = command_write_sequence(cbuf, 4, answer);
		return decode_instcmd_exec(res, (unsigned char)answer[0], cmdname, "Beeper status changed");
	}

	strncpy(namebuf, cmdname, sizeof(namebuf));
	namebuf[NUT_OUTLET_POSITION] = 'n'; /* Assumes a maximum of 9 outlets */

	if (!strcasecmp(namebuf, "outlet.n.shutdown.return")) {
		send_write_command(AUTHOR, 4);

		sleep(PW_SLEEP); /* Need to. Have to wait at least 0,25 sec max 16 sec */

		/* Get the shutdown delay, if any */
		snprintf(varname, sizeof(varname)-1, "outlet.%c.delay.shutdown", cmdname[NUT_OUTLET_POSITION]);
		if ((varvalue = dstate_getinfo(varname)) != NULL) {
			sddelay = atoi(varvalue);
		}

		/*if -1 then use global shutdown_delay from ups.conf*/
		if (sddelay == -1) sddelay = (int)bcmxcp_status.shutdowndelay;

		outlet_num = cmdname[NUT_OUTLET_POSITION] - '0';
		if (outlet_num < 1 || outlet_num > 9)
			return STAT_INSTCMD_FAILED;

		cbuf[0] = PW_LOAD_OFF_RESTART;
		cbuf[1] = sddelay & 0xff;
		cbuf[2] = (unsigned char)(sddelay >> 8);     /* high byte of the 2 byte time argument */
		cbuf[3] = (unsigned char)outlet_num; /* which outlet load segment? Assumes outlet number at position 8 of the command string. */

		upslog_INSTCMD_POWERSTATE_CHANGE(cmdname, extra);
		res = command_write_sequence(cbuf, 4, answer);

		sec = (256 * (unsigned char)answer[3]) + (unsigned char)answer[2];
		snprintf(success_msg, sizeof(success_msg)-1, "Going down in %d sec", sec);

		return decode_instcmd_exec(res, (unsigned char)answer[0], cmdname, success_msg);
	}

	if (!strcasecmp(namebuf, "outlet.n.load.on") || !strcasecmp(namebuf, "outlet.n.load.off")) {
		send_write_command(AUTHOR, 4);

		sleep(PW_SLEEP); /* Need to. Have to wait at least 0,25 sec max 16 sec */

		outlet_num = cmdname[NUT_OUTLET_POSITION] - '0';
		if (outlet_num < 1 || outlet_num > 9)
			return STAT_INSTCMD_FAILED;

		/* Cmd oN or not? */
		if (cmdname[NUT_OUTLET_POSITION+8] == 'n') {
			upslog_INSTCMD_POWERSTATE_MAYBE(cmdname, extra);
			cbuf[0] = PW_UPS_ON;
		} else {
			upslog_INSTCMD_POWERSTATE_CHANGE(cmdname, extra);
			cbuf[0] = PW_UPS_OFF;
		}
		cbuf[1] = (unsigned char)outlet_num;                           /* Outlet number */

		res = command_write_sequence(cbuf, 2, answer);
		snprintf(success_msg, sizeof(success_msg)-1,
			"Outlet %d is  %s",
			outlet_num,
			((cmdname[NUT_OUTLET_POSITION+8] == 'n') ? "On" : "Off"));

		return decode_instcmd_exec(res, (unsigned char)answer[0], cmdname, success_msg);
	}

	upslog_INSTCMD_UNKNOWN(cmdname, extra);
	return STAT_INSTCMD_UNKNOWN;
}

static int decode_instcmd_exec(const ssize_t res, const unsigned char exec_status, const char *cmdname, const char *success_msg)
{
	if (res <= 0) {
		upslogx(LOG_INSTCMD_FAILED, "[%s] Short read from UPS", cmdname);
		dstate_datastale();
		return STAT_INSTCMD_FAILED;
	}

	/* Decode the status code from command execution */
	switch (exec_status) {
		case BCMXCP_RETURN_ACCEPTED: {
			upslogx(LOG_NOTICE, "[%s] %s", cmdname, success_msg);
			upsdrv_comm_good();
			return STAT_INSTCMD_HANDLED;
			}
		case BCMXCP_RETURN_ACCEPTED_PARAMETER_ADJUST: {
			upslogx(LOG_NOTICE, "[%s] Parameter adjusted", cmdname);
			upslogx(LOG_NOTICE, "[%s] %s", cmdname, success_msg);
			upsdrv_comm_good();
			return STAT_INSTCMD_HANDLED;
			}
		case BCMXCP_RETURN_BUSY: {
			upslogx(LOG_INSTCMD_FAILED, "[%s] Busy or disabled by front panel", cmdname);
			return STAT_INSTCMD_FAILED;
			}
		case BCMXCP_RETURN_UNRECOGNISED: {
			upslogx(LOG_INSTCMD_FAILED, "[%s] Unrecognized command byte or corrupt checksum", cmdname);
			return STAT_INSTCMD_FAILED;
			}
		case BCMXCP_RETURN_INVALID_PARAMETER: {
			upslogx(LOG_INSTCMD_INVALID, "[%s] Invalid parameter", cmdname);
			return STAT_INSTCMD_INVALID;
			}
		case BCMXCP_RETURN_PARAMETER_OUT_OF_RANGE: {
			upslogx(LOG_INSTCMD_INVALID, "[%s] Parameter out of range", cmdname);
			return STAT_INSTCMD_INVALID;
			}
		default: {
			upslogx(LOG_INSTCMD_INVALID, "[%s] Not supported", cmdname);
			return STAT_INSTCMD_INVALID;
			}
	}
}

void upsdrv_help(void)
{
}

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

/* list flags and values that you want to receive via -x */
void upsdrv_makevartable(void)
{
	/* NOTE: The USB variant of this driver currently does not
	 * involve nut_usb_addvars() method like others do. When
	 * fixing, see also tools/nut-scanner/scan_usb.c "exceptions".
	 */
	addvar(VAR_VALUE, "shutdown_delay", "Specify shutdown delay (seconds)");
	addvar(VAR_VALUE, "baud_rate", "Specify communication speed (ex: 9600)");
}

int setvar (const char *varname, const char *val)
{
	unsigned char answer[128], cbuf[5];
	char namebuf[MAX_NUT_NAME_LENGTH];
	char success_msg[SMALLBUF];
	ssize_t res;
	int sec, outlet_num, tmp;
	int onOff_setting = PW_AUTO_OFF_DELAY;

	upsdebug_SET_STARTING(varname, val);

	if (!strcasecmp(varname, "input.transfer.boost.high")) {

		send_write_command(AUTHOR, 4);
		sleep(PW_SLEEP);        /* Need to. Have to wait at least 0,25 sec max 16 sec */

		tmp=atoi(val);
		if (tmp < 0 || tmp > 460) {
			return STAT_SET_INVALID;
		}

		cbuf[0]=PW_SET_CONF_COMMAND;
		cbuf[1]=PW_CONF_LOW_DEV_LIMIT;
		cbuf[2]=tmp&0xff;
		cbuf[3]=(unsigned char)(tmp>>8);

		res = command_write_sequence(cbuf, 4, answer);
		snprintf(success_msg, sizeof(success_msg)-1,
			" BOOST threshold volage set to %d V", tmp);

		return decode_setvar_exec(res, (unsigned char)answer[0], varname, success_msg);

	}

	if (!strcasecmp(varname, "input.transfer.trim.low")) {

		send_write_command(AUTHOR, 4);
		sleep(PW_SLEEP);        /* Need to. Have to wait at least 0,25 sec max 16 sec */

		tmp=atoi(val);
		if (tmp < 110 || tmp > 540) {
			return STAT_SET_INVALID;
		}

		cbuf[0]=PW_SET_CONF_COMMAND;
		cbuf[1]=PW_CONF_HIGH_DEV_LIMIT;
		cbuf[2]=tmp&0xff;
		cbuf[3]=(unsigned char)(tmp>>8);

		res = command_write_sequence(cbuf, 4, answer);
		snprintf(success_msg, sizeof(success_msg)-1,
			" TRIM threshold volage set to %d V", tmp);

		return decode_setvar_exec(res, (unsigned char)answer[0], varname, success_msg);

	}

	if (!strcasecmp(varname, "battery.runtime.low")) {

		send_write_command(AUTHOR, 4);
		sleep(PW_SLEEP);        /* Need to. Have to wait at least 0,25 sec max 16 sec */

		tmp=atoi(val);
		if (tmp < 0 || tmp > 30) {
			return STAT_SET_INVALID;
		}

		cbuf[0]=PW_SET_CONF_COMMAND;
		cbuf[1]=PW_CONF_LOW_BATT;
		cbuf[2]=tmp&0xff;
		cbuf[3]=0x0;

		res = command_write_sequence(cbuf, 4, answer);
		snprintf(success_msg, sizeof(success_msg)-1,
			" Low battery warning time set to %d min", tmp);

		return decode_setvar_exec(res, (unsigned char)answer[0], varname, success_msg);

	}

	if (!strcasecmp(varname, "input.transfer.delay")) {

		send_write_command(AUTHOR, 4);
		sleep(PW_SLEEP);        /* Need to. Have to wait at least 0,25 sec max 16 sec */

		tmp=atoi(val);
		if (tmp < 1 || tmp > 18000) {
			return STAT_SET_INVALID;
		}

		cbuf[0]=PW_SET_CONF_COMMAND;
		cbuf[1]=PW_CONF_RETURN_DELAY;
		cbuf[2]=tmp&0xff;
		cbuf[3]=(unsigned char)(tmp>>8);

		res = command_write_sequence(cbuf, 4, answer);
		snprintf(success_msg, sizeof(success_msg)-1,
			" Mains return delay set to %d sec", tmp);

		return decode_setvar_exec(res, (unsigned char)answer[0], varname, success_msg);

	}

	if (!strcasecmp(varname, "battery.charge.restart")) {

		send_write_command(AUTHOR, 4);
		sleep(PW_SLEEP);        /* Need to. Have to wait at least 0,25 sec max 16 sec */

		tmp=atoi(val);
		if (tmp < 0 || tmp > 100) {
			return STAT_SET_INVALID;
		}

		cbuf[0]=PW_SET_CONF_COMMAND;
		cbuf[1]=PW_CONF_RETURN_CAP;
		cbuf[2]=tmp&0xff;
		cbuf[3]=0x0;

		res = command_write_sequence(cbuf, 4, answer);
		snprintf(success_msg, sizeof(success_msg)-1,
			" Mains return minimum battery capacity set to %d %%", tmp);

		return decode_setvar_exec(res, (unsigned char)answer[0], varname, success_msg);

	}


	if (!strcasecmp(varname, "ambient.temperature.high")) {

		send_write_command(AUTHOR, 4);
		sleep(PW_SLEEP);        /* Need to. Have to wait at least 0,25 sec max 16 sec */

		tmp=atoi(val);
		if (tmp < 0 || tmp > 100) {
			return STAT_SET_INVALID;
		}

		cbuf[0]=PW_SET_CONF_COMMAND;
		cbuf[1]=PW_CONF_MAX_TEMP;
		cbuf[2]=tmp&0xff;
		cbuf[3]=0x0;

		res = command_write_sequence(cbuf, 4, answer);
		snprintf(success_msg, sizeof(success_msg)-1,
			" Maximum temperature set to %d C", tmp);

		return decode_setvar_exec(res, (unsigned char)answer[0], varname, success_msg);

	}

	if (!strcasecmp(varname, "output.voltage.nominal")) {

		send_write_command(AUTHOR, 4);
		sleep(PW_SLEEP);        /* Need to. Have to wait at least 0,25 sec max 16 sec */

		tmp=atoi(val);
		if (tmp < 0 || tmp > 460) {
			return STAT_SET_INVALID;
		}

		cbuf[0]=PW_SET_CONF_COMMAND;
		cbuf[1]=PW_CONF_NOMINAL_OUT_VOLTAGE;
		cbuf[2]=tmp&0xff;
		cbuf[3]=(unsigned char)(tmp>>8);

		res = command_write_sequence(cbuf, 4, answer);
		snprintf(success_msg, sizeof(success_msg)-1,
			" Nominal output voltage set to %d V", tmp);

		return decode_setvar_exec(res, (unsigned char)answer[0], varname, success_msg);

	}

	if (!strcasecmp(varname, "battery.energysave.load")) {

		send_write_command(AUTHOR, 4);
		sleep(PW_SLEEP);        /* Need to. Have to wait at least 0,25 sec max 16 sec */

		tmp=atoi(val);
		if (tmp < 0 || tmp > 100) {
			return STAT_SET_INVALID;
		}

		cbuf[0]=PW_SET_CONF_COMMAND;
		cbuf[1]=PW_CONF_SLEEP_TH_LOAD;
		cbuf[2]=tmp&0xff;
		cbuf[3]=0x0;

		res = command_write_sequence(cbuf, 4, answer);
		snprintf(success_msg, sizeof(success_msg)-1, " Minimum load before sleep countdown set to %d %%", tmp);

		return decode_setvar_exec(res, (unsigned char)answer[0], varname, success_msg);

	}

	if (!strcasecmp(varname, "battery.energysave.delay")) {

		send_write_command(AUTHOR, 4);
		sleep(PW_SLEEP);        /* Need to. Have to wait at least 0,25 sec max 16 sec */

		tmp=atoi(val);
		if (tmp < 0 || tmp > 255) {
			return STAT_SET_INVALID;
		}

		cbuf[0]=PW_SET_CONF_COMMAND;
		cbuf[1]=PW_CONF_SLEEP_DELAY;
		cbuf[2]=tmp&0xff;
		cbuf[3]=0x0;

		res = command_write_sequence(cbuf, 4, answer);
		snprintf(success_msg, sizeof(success_msg)-1,
			" Delay before sleep shutdown set to %d min", tmp);

		return decode_setvar_exec(res, (unsigned char)answer[0], varname, success_msg);


	}

	if (!strcasecmp(varname, "battery.packs")) {

		send_write_command(AUTHOR, 4);
		sleep(PW_SLEEP);        /* Need to. Have to wait at least 0,25 sec max 16 sec */

		tmp=atoi(val);
		if (tmp < 0 || tmp > 5) {
			return STAT_SET_INVALID;
		}

		cbuf[0]=PW_SET_CONF_COMMAND;
		cbuf[1]=PW_CONF_BATT_STRINGS;
		cbuf[2]=tmp&0xff;
		cbuf[3]=0x0;

		res = command_write_sequence(cbuf, 4, answer);
		snprintf(success_msg, sizeof(success_msg)-1, "EBM Count set to %d ", tmp);

		return decode_setvar_exec(res, (unsigned char)answer[0], varname, success_msg);
	}

	strncpy(namebuf, varname, sizeof(namebuf));
	namebuf[NUT_OUTLET_POSITION] = 'n'; /* Assumes a maximum of 9 outlets */

	if ( (!strcasecmp(namebuf, "outlet.n.delay.start")) ||
		(!strcasecmp(namebuf, "outlet.n.delay.shutdown"))) {


		if (outlet_block_len <= 8) {
			return STAT_SET_INVALID;
		}

		if (!strcasecmp(namebuf, "outlet.n.delay.start")) {
			onOff_setting = PW_AUTO_ON_DELAY;
		}

		send_write_command(AUTHOR, 4);
		sleep(PW_SLEEP);	/* Need to. Have to wait at least 0,25 sec max 16 sec */

		outlet_num = varname[NUT_OUTLET_POSITION] - '0';
		if (outlet_num < 1 || outlet_num > 9) {
			return STAT_SET_INVALID;
		}

		sec = atoi(val);
		/* Check value:
		 *	0-32767 are valid values
		 *	-1 means no Automatic off or restart
		 * for Auto Off Delay:
		 *	0-30 are valid but ill-advised */
		if (sec < -1 || sec > 0x7FFF) {
			return STAT_SET_INVALID;
		}

		cbuf[0] = PW_SET_OUTLET_COMMAND;		/* Cmd */
		cbuf[1] = (unsigned char)onOff_setting;	/* Set Auto Off (1) or On (2) Delay */
		cbuf[2] = (unsigned char)outlet_num;	/* Outlet number */
		cbuf[3] = sec&0xff;						/* Delay in seconds LSB */
		cbuf[4] = (unsigned char)(sec>>8);		/* Delay in seconds MSB */

		res = command_write_sequence(cbuf, 5, answer);
		snprintf(success_msg, sizeof(success_msg)-1,
			"Outlet %d %s delay set to %d sec",
			outlet_num,
			(onOff_setting == PW_AUTO_ON_DELAY) ? "start" : "shutdown",
			sec);

		return decode_setvar_exec(res, (unsigned char)answer[0], varname, success_msg);

	}

	return STAT_SET_INVALID;
}

static int decode_setvar_exec(const ssize_t res, const unsigned char exec_status, const char *cmdname, const char *success_msg)
{
	if (res <= 0) {
		upslogx(LOG_SET_FAILED, "[%s] Short read from UPS", cmdname);
		dstate_datastale();
		return STAT_SET_FAILED;
	}

	/* Decode the status code from command execution */
	switch (exec_status) {
		case BCMXCP_RETURN_ACCEPTED: {
			upslogx(LOG_NOTICE, "[%s] %s", cmdname, success_msg);
			upsdrv_comm_good();
			return STAT_SET_HANDLED;
			}
		case BCMXCP_RETURN_ACCEPTED_PARAMETER_ADJUST: {
			upslogx(LOG_NOTICE, "[%s] Parameter adjusted", cmdname);
			upslogx(LOG_NOTICE, "[%s] %s", cmdname, success_msg);
			upsdrv_comm_good();
			return STAT_SET_HANDLED;
			}
		case BCMXCP_RETURN_BUSY: {
			upslogx(LOG_SET_FAILED, "[%s] Busy or disabled by front panel", cmdname);
			return STAT_SET_FAILED;
			}
		case BCMXCP_RETURN_UNRECOGNISED: {
			upslogx(LOG_SET_FAILED, "[%s] Unrecognized command byte or corrupt checksum", cmdname);
			return STAT_SET_FAILED;
			}
		case BCMXCP_RETURN_INVALID_PARAMETER: {
			upslogx(LOG_SET_INVALID, "[%s] Invalid parameter", cmdname);
			return STAT_SET_INVALID;
			}
		case BCMXCP_RETURN_PARAMETER_OUT_OF_RANGE: {
			upslogx(LOG_SET_INVALID, "[%s] Parameter out of range", cmdname);
			return STAT_SET_INVALID;
			}
		default: {
			upslogx(LOG_SET_INVALID, "[%s] Not supported", cmdname);
			return STAT_SET_INVALID;
			}
	}
}

/*******************************
 * Extracted from usbhid-ups.c *
 *******************************/

/* find the NUT value matching that XCP Item value */
static const char *nut_find_infoval(info_lkp_t *xcp2info, const double value, const bool_t debug_output_nonexisting)
{
	info_lkp_t *info_lkp;

	/* if a conversion function is defined, use 'value' as argument for it */
	if (xcp2info->fun != NULL) {
		return xcp2info->fun(value);
	}

	/* use 'value' as an index for a lookup in an array */
	for (info_lkp = xcp2info; info_lkp->nut_value != NULL; info_lkp++) {
		if (info_lkp->xcp_value == (long)value) {
			upsdebugx(5, "nut_find_infoval: found %s (value: %ld)", info_lkp->nut_value, (long)value);
			return info_lkp->nut_value;
		}
	}
	if (debug_output_nonexisting == TRUE) {
		upsdebugx(3, "nut_find_infoval: no matching INFO_* value for this XCP value (%g)", value);
	}
	return NULL;
}
