/*vim ts=4*/
/* oneac.c - Driver for Oneac UPS using the Advanced Interface.
 *
 * Supported Oneac UPS families in this driver:
 * EG (late 80s, early 90s, plug-in serial interface card)
 * ON (early and mid-90s, plug-in serial interface card)
 * OZ (mid-90s on, DB-25 std., interface slot)
 * OB (early 2000's on, big cabinet, DB-25 std., interface slot)
 *
 * Copyright (C)
 *     2003 by Eric Lawson <elawson@inficad.com>
 *     2012 by Bill Elliot <bill@wreassoc.com>
 *
 * This program was sponsored by MGE UPS SYSTEMS, and now Eaton
 *
 * 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
 *
 * History:
 * - 7 February 2012.  Bill Elliot
 * Enhancing the driver for additional capabilities and later units.
 *
 * - 28 November 2003.  Eric Lawson
 * More or less complete re-write for NUT 1.5.9 
 * This was somewhat easier than trying to beat the old driver code 
 * into submission
 * 
 */

#include "main.h"
#include "serial.h"
#include "oneac.h"

/* Prototypes to allow setting pointer before function is defined */
int setcmd(const char* varname, const char* setvalue);
int instcmd(const char *cmdname, const char *extra);

#define DRIVER_NAME	"Oneac EG/ON/OZ/OB UPS driver"
#define DRIVER_VERSION	"0.80"

/* driver description structure */
upsdrv_info_t upsdrv_info = {
	DRIVER_NAME,
	DRIVER_VERSION,
	"Bill Elliot <bill@wreassoc.com>\n"
	"Eric Lawson <elawson@inficad.com>",
	DRV_STABLE,
	{ NULL }
};

#define SECS 0			/* Serial function wait time*/
#define USEC 500000		/* Rest of serial function wait time*/

#define COMM_TRIES	3	/* Serial retries before "stale" */

static char UpsFamily [3];

/****************************************************************
 * Below are functions used only in this oneac driver			*
 ***************************************************************/

/* Since an installed network card may delay responses from the UPS
 *  allow for a repeat of the get request.  Also confirm that
 *  the correct number of characters are returned.
 */
 
int OneacGetResponse (char* chBuff, const int BuffSize, int ExpectedCount)
{
	int Retries = 10;		/* x/2 seconds max with 500000 USEC */
	int return_val;

	do
	{
		return_val = ser_get_line(upsfd, chBuff, BuffSize, ENDCHAR, IGNCHARS, 
																	SECS, USEC);

		if (return_val == ExpectedCount)
			break;

		upsdebugx (3,"!OneacGetResponse retry (%d, %d)...", return_val,Retries);
		
	} while (--Retries > 0);

	upsdebugx (4,"OneacGetResponse buffer: %s",chBuff);

	if (Retries == 0)
	{
		upsdebugx (2,"!!OneacGetResponse timeout...");
		return_val = 1;					/* Comms error */
	}
	else
	{
		if (Retries < 10)
			upsdebugx (2,"OneacGetResponse recovered (%d)...", Retries);
			
		return_val = 0;					/* Good comms */
	}
	
    return return_val;
}

void do_battery_test(void)
{
	char buffer[32];

	if (getval("testtime") == NULL)
		snprintf(buffer, 3, "%s", DEFAULT_BAT_TEST_TIME);
	else {
		snprintf(buffer, 3, "%s", getval("testtime"));

	/*the UPS wants this value to always be two characters long*/
	/*so put a zero in front of the string, if needed....      */
		if (strlen(buffer) < 2) {
			buffer[2] = '\0';
			buffer[1] = buffer[0];
			buffer[0] = '0';
		}
	}
	ser_send(upsfd,"%s%s%s",BAT_TEST_PREFIX,buffer,COMMAND_END);
}

int SetOutputAllow(const char* lowval, const char* highval)
{
	char buffer[32];

	snprintf(buffer, 4, "%.3s", lowval);

	/*the UPS wants this value to always be three characters long*/
	/*so put a zero in front of the string, if needed....      */

	if (strlen(buffer) < 3)
	{
		buffer[3] = '\0';
		buffer[2] = buffer[1];
		buffer[1] = buffer[0];
		buffer[0] = '0';
	}
	
	upsdebugx (2,"SetOutputAllow sending %s%.3s,%.3s...", 
											SETX_OUT_ALLOW, buffer, highval);
					
	ser_send(upsfd,"%s%.3s,%.3s%s", SETX_OUT_ALLOW, buffer, highval, 
																COMMAND_END);
	ser_get_line(upsfd,buffer,sizeof(buffer), ENDCHAR, IGNCHARS,SECS,USEC);
	
	if(buffer[0] == DONT_UNDERSTAND)
	{
		upsdebugx (2,"SetOutputAllow got asterisk back...");

		return 1;					/* Invalid command */
	}
	
	return 0;						/* Valid command */
}

void EliminateLeadingZeroes (const char* buff1, int StringSize, char* buff2, 
															const int buff2size)
{
	int i = 0;
	int j = 0;

	memset(buff2, '\0', buff2size);			/* Fill with nulls */
	
	/* Find first non-'0' */
	while ((buff1[i] == '0') && (i < (StringSize - 1)))
	{
		i++;
	}
	
	while (i < StringSize)					/* Move rest of string */
	{
		buff2[j++] = buff1[i++];
	}
}


/****************************************************************
 * Below are the commands that are called by main				*
 ***************************************************************/

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

	/*get the UPS in the right frame of mind*/
	ser_send_pace(upsfd, 100, "%s", COMMAND_END);
	ser_send_pace(upsfd, 100, "%s", COMMAND_END);
	sleep (1);
}

void upsdrv_initinfo(void)
{
	int i,j, k;
	int VRange=0;
	int timevalue;
	int RetValue;
	char buffer[256], buffer2[32];

	/* All families should reply to this request so we can confirm that it is
	 *  an ONEAC UPS
	 */

	ser_flush_in(upsfd,"",0);
	ser_send(upsfd,"%c%s",GET_FAMILY,COMMAND_END);

	if(OneacGetResponse (buffer, sizeof(buffer), 2))
	{
		fatalx(EXIT_FAILURE, "Serial timeout with ONEAC UPS on %s\n",
																device_path);
	}
	else
	{
		if (strncmp(buffer,FAMILY_ON,FAMILY_SIZE) != 0 && 
			strncmp(buffer,FAMILY_OZ,FAMILY_SIZE) != 0 &&
			strncmp(buffer,FAMILY_OB,FAMILY_SIZE) != 0 && 
			strncmp(buffer,FAMILY_EG,FAMILY_SIZE) != 0)
		{
			fatalx(EXIT_FAILURE, "Did not find an ONEAC UPS on %s\n",
																device_path);	
		}
	}

	/* UPS Model (either EG, ON, OZ or OB series of UPS) */
	strncpy(UpsFamily, buffer, FAMILY_SIZE);
	UpsFamily[2] = '\0';
	dstate_setinfo("device.model", "%s",UpsFamily);
	printf("Found %s family of Oneac UPS\n", UpsFamily);

	dstate_setinfo("ups.type", "%s", "Line Interactive");
	
	dstate_addcmd("test.battery.start.quick");
	dstate_addcmd("test.battery.stop");
	dstate_addcmd("test.failure.start");
	dstate_addcmd("shutdown.return");
	dstate_addcmd("shutdown.stop");
	dstate_addcmd("shutdown.reboot");

	upsh.setvar = setcmd;
	upsh.instcmd = instcmd;

	/* set some stuff that shouldn't change after initialization */
	/* this stuff is common to all families of UPS */

	ser_send(upsfd,"%c%s",GET_ALL,COMMAND_END);

	if (strncmp(UpsFamily, FAMILY_EG, FAMILY_SIZE) == 0)
	{
		RetValue = OneacGetResponse (buffer, sizeof(buffer), 
														GETALL_EG_RESP_SIZE);
	}
	else
	{
		RetValue = OneacGetResponse (buffer, sizeof(buffer), GETALL_RESP_SIZE);
	}

	if(RetValue)
	{
		fatalx(EXIT_FAILURE, "Serial timeout(2) with ONEAC UPS on %s\n",
																device_path);
	}

	/* Manufacturer */
	dstate_setinfo("device.mfr", "%.5s", buffer);

	/*firmware revision*/
	dstate_setinfo("ups.firmware", "%.3s",buffer+7);

	/*nominal AC frequency setting --either 50 or 60*/
	dstate_setinfo("input.frequency.nominal", "%.2s", buffer+20);
	dstate_setinfo("output.frequency.nominal", "%.2s", buffer+20);

	/* Shutdown delay in seconds...can be changed by user */
	if (getval("offdelay") == NULL)
		dstate_setinfo("ups.delay.shutdown", "0");
	else
		dstate_setinfo("ups.delay.shutdown", "%s", getval("offdelay"));

	dstate_setflags("ups.delay.shutdown", ST_FLAG_STRING | ST_FLAG_RW);
	dstate_setaux("ups.delay.shutdown", GET_SHUTDOWN_RESP_SIZE);
	
	/* Setup some ON/OZ/OB only stuff ... i.e. not EG */

	if (strncmp(UpsFamily, FAMILY_EG, FAMILY_SIZE) != 0)
	{
		dstate_addcmd("reset.input.minmax");

		/*nominal input voltage*/

		VRange = buffer[26];			/* Keep for later use also */
		
		switch (VRange)					/* Will be '1' or '2' */
		{
			case V120AC:
				dstate_setinfo("input.voltage.nominal", "120");
				dstate_setinfo("output.voltage.nominal", "120");
				break;

			case V230AC:
				dstate_setinfo("input.voltage.nominal", "230");
				dstate_setinfo("output.voltage.nominal", "230");
				break;

			default:
				upslogx(LOG_INFO,"Oneac: "
					"Invalid nom voltage parameter from UPS [%c]", VRange);
		}
	}
		  
	/* Setup some OZ/OB only stuff */
	
	if ((strncmp (UpsFamily, FAMILY_OZ, FAMILY_SIZE) == 0) ||
		(strncmp (UpsFamily, FAMILY_OB, FAMILY_SIZE) == 0))
	{
		dstate_addcmd("test.panel.start");
		dstate_addcmd("test.battery.start.deep");
		dstate_addcmd("beeper.enable");
		dstate_addcmd("beeper.disable");
		dstate_addcmd("beeper.mute");
		
		dstate_setaux("ups.delay.shutdown", GETX_SHUTDOWN_RESP_SIZE);

		ser_flush_in(upsfd,"",0);
		ser_send(upsfd,"%c%s",GETX_ALL_2,COMMAND_END);
		if(OneacGetResponse (buffer, sizeof(buffer), GETX_ALL2_RESP_SIZE))
		{
			fatalx(EXIT_FAILURE, "Serial timeout(3) with ONEAC UPS on %s\n",
																device_path);
		}

		/* Low and high output trip points */
		EliminateLeadingZeroes (buffer+73, 3, buffer2, sizeof(buffer2));
		dstate_setinfo("input.transfer.low", "%s", buffer2);
		dstate_setflags("input.transfer.low", ST_FLAG_STRING | ST_FLAG_RW );
		dstate_setaux("input.transfer.low", 3);

		EliminateLeadingZeroes (buffer+76, 3, buffer2, sizeof(buffer2));
		dstate_setinfo("input.transfer.high", "%s", buffer2);
		dstate_setflags("input.transfer.high", ST_FLAG_STRING | ST_FLAG_RW);
		dstate_setaux("input.transfer.high", 3);

		/* Restart delay */
		EliminateLeadingZeroes (buffer+84, 4, buffer2, sizeof(buffer2));
		dstate_setinfo("ups.delay.start", "%s", buffer2);
		dstate_setflags("ups.delay.start", ST_FLAG_STRING | ST_FLAG_RW);
		dstate_setaux("ups.delay.start", 4);
		
		/* Low Batt at time */
		strncpy(buffer2, buffer+82, 2);
		buffer2[2]='\0';
		timevalue = atoi(buffer2) * 60;		/* Change minutes to seconds */
		dstate_setinfo("battery.runtime.low", "%d",timevalue);
		dstate_setflags("battery.runtime.low", ST_FLAG_STRING | ST_FLAG_RW);
		dstate_setaux("battery.runtime.low", 2);
		
		/*Get the actual model string for ON UPS reported as OZ/OB family*/

		/*UPS Model (full string)*/
		memset(buffer2, '\0', 32);
		strncpy(buffer2, buffer+5, 10);
		for (i = 9; i >= 0 && buffer2[i] == ' '; --i) 
		{
			buffer2[i] = '\0';
		}

		dstate_setinfo("device.model", "%s", buffer2);
		
		/* Serial number */
		dstate_setinfo("device.serial", "%.4s-%.4s", buffer+18, buffer+22);
		printf("Found %.10s UPS with serial number %.4s-%.4s\n", 
												buffer2, buffer+18, buffer+22);
		
		/* Manufacture Date */
		dstate_setinfo("ups.mfr.date", "%.6s (yymmdd)", buffer+38);

		/* Battery Replace Date */
		dstate_setinfo("battery.date", "%.6s (yymmdd)", buffer+44);
		dstate_setflags("battery.date", ST_FLAG_STRING | ST_FLAG_RW);
		dstate_setaux("battery.date", 6);
		
		/* Real power nominal */
		EliminateLeadingZeroes (buffer+55, 5, buffer2, sizeof(buffer2));
		dstate_setinfo("ups.realpower.nominal", "%s", buffer2);

		/* Set up ups.start.auto to be writable */
		dstate_setinfo("ups.start.auto", "yes");
		dstate_setflags("ups.start.auto", ST_FLAG_STRING | ST_FLAG_RW);
		dstate_setaux("ups.start.auto", 3);

		/* Get output window min/max points from OB or OZ v1.9 or later */
		if ((strncmp (UpsFamily, FAMILY_OB, FAMILY_SIZE) == 0) ||
			(strcmp (dstate_getinfo("ups.firmware"), MIN_ALLOW_FW) >= 0 ))
		{
			upsdebugx (2,"Can get output window min/max! (%s)", 
												dstate_getinfo("ups.firmware"));

			ser_send(upsfd,"%s%s",GETX_ALLOW_RANGE,COMMAND_END);
			if(OneacGetResponse (buffer, sizeof(buffer), GETX_RANGE_RESP_SIZE))
			{
				fatalx(EXIT_FAILURE, 
						"Serial timeout(4) with ONEAC UPS on %s\n",device_path);
			}

			strncpy(buffer2, buffer, 3);
			buffer2[3]='\0';
			i = atoi(buffer2);		/* Minimum voltage */
			
			strncpy(buffer2, buffer+4, 3);
			j = atoi(buffer2);		/* Maximum voltage */

			strncpy(buffer2, buffer+8, 2);
			buffer2[2]='\0';
			k = atoi(buffer2);		/* Spread between */
			
			dstate_setinfo("input.transfer.low.min", "%3d", i);
			dstate_setinfo("input.transfer.low.max", "%3d", j-k);
			dstate_setinfo("input.transfer.high.min", "%3d", i+k);
			dstate_setinfo("input.transfer.high.max", "%3d", j);

		}
		else
		{
			/* Use default values from firmware */
			upsdebugx (2,"Using trip defaults (%s)...", 
												dstate_getinfo("ups.firmware"));

			switch (VRange)				/* Held from initial use */
			{
				case V120AC:
					dstate_setinfo("input.transfer.low.min", "90");
					dstate_setinfo("input.transfer.low.max", "120");
					dstate_setinfo("input.transfer.high.min", "110");
					dstate_setinfo("input.transfer.high.max", "140");
					break;

				case V230AC:
					dstate_setinfo("input.transfer.low.min", "172");
					dstate_setinfo("input.transfer.low.max", "228");
					dstate_setinfo("input.transfer.high.min", "212");
					dstate_setinfo("input.transfer.high.max", "268");
					break;

				default:
					;

			}
		}
	}
}

void upsdrv_updateinfo(void)
{
	static int CommTry = COMM_TRIES;		/* Comm loss counter */
	char buffer[256];	/* Main response buffer */
	char buffer2[32];	/* Conversion buffer */
	char s;
	int RetValue;
	int timevalue;

	/* Start with EG/ON information */
	ser_flush_in(upsfd,"",0);  /*just in case*/
	ser_send (upsfd,"%c%s", GET_ALL, COMMAND_END);

	if (strncmp(UpsFamily, FAMILY_EG, FAMILY_SIZE) == 0)
	{
		RetValue = OneacGetResponse (buffer,sizeof(buffer),GETALL_EG_RESP_SIZE);
	}
	else
	{
		RetValue = OneacGetResponse (buffer, sizeof(buffer), GETALL_RESP_SIZE);
	}

	if ((RetValue != 0) && (CommTry == 0))
	{
		ser_comm_fail("Oneac UPS Comm failure continues on port %s",
																device_path);
	}	
	else if (RetValue != 0)
	{
		if (--CommTry == 0)
		{
			ser_comm_fail("Oneac UPS Comm failure on port %s",device_path);
			dstate_datastale();
		}
		upsdebugx(2,"Oneac: Update serial comm retry value: %d", CommTry);

		return;
	}
	else
	{
		CommTry = COMM_TRIES;			/* Reset serial retries */

		s = buffer[12];

		status_init();
		alarm_init();

		/*take care of the UPS status information*/
		if (s == '@')
		{
			status_set("OL");
		}
		else
		{
			if (s & 0x01)			/* On Battery */
			{
				status_set("OB");
			}
			else
			{
				status_set("OL");
			}
			
			if (s & 0x02)			/* Low Battery */
				status_set("LB");
			
			if (s & 0x04)			/* General fault */
			{
				dstate_setinfo("ups.test.result","UPS Internal Failure");
			}
			else
			{
				dstate_setinfo("ups.test.result","Normal");
			}

			if (s & 0x08)			/* Replace Battery */
				status_set("RB");

/*			if (s & 0x10)	*/		/* High Line */

			if (s & 0x20)			/* Unit is hot */
				alarm_set("OVERHEAT");
		}

		/*take care of the reason why the UPS last transferred to battery*/
		switch (buffer[13]) {
			case XFER_BLACKOUT :
				dstate_setinfo("input.transfer.reason",	"Blackout");
				break;
			case XFER_LOW_VOLT :
				dstate_setinfo("input.transfer.reason",
					"Low Input Voltage");
				break;
			case XFER_HI_VOLT :
				dstate_setinfo("input.transfer.reason",
					"High Input Voltage");
				break;
			case NO_VALUE_YET :
				dstate_setinfo("input.transfer.reason", 
					"No transfer yet.");
				break;
			default :
				upslogx(LOG_INFO,"Oneac: Unknown reason for UPS battery"
										" transfer [%c]", buffer[13]);
		}

		/* now update info for only the non-EG families of UPS*/

		if (strncmp(UpsFamily, FAMILY_EG, FAMILY_SIZE) != 0)
		{
			dstate_setinfo("ups.load", "0%.2s",buffer+31);

			/* Output ON or OFF? */
			if(buffer[27] == NO_VALUE_YET)
				status_set("OFF");

			/*battery charge*/
			if(buffer[10] == YES)
				dstate_setinfo("battery.charge", "0%.2s",buffer+33);
			else 
				dstate_setinfo("battery.charge", "100");

			EliminateLeadingZeroes (buffer+35, 3, buffer2, sizeof(buffer2));
			dstate_setinfo("input.voltage", "%s",buffer2);

			EliminateLeadingZeroes (buffer+38, 3, buffer2, sizeof(buffer2));
			dstate_setinfo("input.voltage.minimum", "%s",buffer2);

			EliminateLeadingZeroes (buffer+41, 3, buffer2, sizeof(buffer2));
			dstate_setinfo("input.voltage.maximum", "%s",buffer2);

			EliminateLeadingZeroes (buffer+44, 3, buffer2, sizeof(buffer2));
			dstate_setinfo("output.voltage", "%s",buffer2);

			if (buffer[15] == NO_VALUE_YET)
			{
				dstate_delinfo("ups.timer.shutdown");
			}
			else
			{
				/* A shutdown is underway! */
				status_set("FSD");
				
				if(buffer[15] != HIGH_COUNT)
				{
					EliminateLeadingZeroes (buffer+15, 3, buffer2, 
															sizeof(buffer2));
					dstate_setinfo("ups.timer.shutdown", "%s", buffer2);
				}
				else
				{
					dstate_setinfo("ups.timer.shutdown", "999");
				}
			}

			if (buffer[47] == YES) 
				status_set("BOOST");
		}

		/* Now update info for only the OZ/OB families of UPS */

		if ((strncmp(UpsFamily, FAMILY_OZ, FAMILY_SIZE) == 0) ||
			(strncmp(UpsFamily, FAMILY_OB, FAMILY_SIZE) == 0)) 
		{
			ser_flush_in(upsfd,"",0);  /*just in case*/
			ser_send (upsfd,"%c%s",GETX_ALL_1,COMMAND_END);
			RetValue = OneacGetResponse (buffer, sizeof(buffer), 
														GETX_ALL1_RESP_SIZE);

			if(RetValue)
			{
				if (--CommTry == 0)
				{
					ser_comm_fail("Oneac (OZ) UPS Comm failure on port %s",
																device_path);
					dstate_datastale();
				}

				upsdebugx(2,"Oneac: "
					"Update (OZ) serial comm retry value: %d", CommTry);
			}
			else
			{
				CommTry = COMM_TRIES;		/* Reset count */

				EliminateLeadingZeroes (buffer+57, 5, buffer2, sizeof(buffer2));
				dstate_setinfo("ups.realpower", "%s",buffer2);

				dstate_setinfo("input.frequency", "%.2s.%c",
														buffer+42,buffer[44]);
				dstate_setinfo("output.frequency", "%.2s.%c",
														buffer+76, buffer[78]);

				EliminateLeadingZeroes (buffer+29, 3, buffer2, sizeof(buffer2));
				dstate_setinfo("battery.voltage", "%s.%c",buffer2, buffer[32]);

				dstate_setinfo("ups.temperature", "%.2s",buffer+13);
				dstate_setinfo("ups.load", "%.3s",buffer+73);

				strncpy(buffer2, buffer+19, 4);
				buffer2[4]='\0';
				timevalue = atoi(buffer2) * 60;		/* Change mins to secs */
				dstate_setinfo("battery.runtime", "%d",timevalue);

				/* Now some individual requests... */

				/* Battery replace date */
				ser_send (upsfd,"%c%s",GETX_BATT_REPLACED,COMMAND_END);
				if(!OneacGetResponse (buffer, sizeof(buffer), 
														GETX_DATE_RESP_SIZE))
					dstate_setinfo("battery.date", "%.6s (yymmdd)", buffer);

				/* Low and high output trip points */
				ser_send (upsfd,"%c%s",GETX_LOW_OUT_ALLOW,COMMAND_END);
				if(!OneacGetResponse (buffer, sizeof(buffer), 
														GETX_ALLOW_RESP_SIZE))
				{
					EliminateLeadingZeroes (buffer, 3, buffer2,sizeof(buffer2));
					dstate_setinfo("input.transfer.low", "%s", buffer2);
				}

				ser_send (upsfd,"%c%s",GETX_HI_OUT_ALLOW,COMMAND_END);
				if(!OneacGetResponse (buffer, sizeof(buffer), 
														GETX_ALLOW_RESP_SIZE))
					dstate_setinfo("input.transfer.high", "%s", buffer);

				/* Restart delay */
				ser_send (upsfd,"%c%s",GETX_RESTART_DLY,COMMAND_END);
				if(!OneacGetResponse (buffer, sizeof(buffer), 
														GETX_RSTRT_RESP_SIZE))
				{
					EliminateLeadingZeroes (buffer, 4, buffer2, 
															sizeof(buffer2));
					dstate_setinfo("ups.delay.start", "%s", buffer2);
				}

				/* Buzzer state */
				ser_send (upsfd,"%s%s",GETX_BUZZER_WHAT,COMMAND_END);
				if(!OneacGetResponse (buffer, sizeof(buffer), 1))
				{
					switch (buffer[0]) 
					{
						case BUZZER_ENABLED :
							dstate_setinfo("ups.beeper.status",	"enabled");
							break;
						case BUZZER_DISABLED :
							dstate_setinfo("ups.beeper.status",	"disabled");
							break;
						case BUZZER_MUTED :
							dstate_setinfo("ups.beeper.status",	"muted");
							break;
						default :
							dstate_setinfo("ups.beeper.status",	"enabled");
					}
				}

				/* Auto start setting */
				ser_send (upsfd,"%s%s",GETX_AUTO_START,COMMAND_END);
				if(!OneacGetResponse (buffer, sizeof(buffer), 1))
				{
					if (buffer[0] == '0')
						dstate_setinfo("ups.start.auto", "yes");
					else
						dstate_setinfo("ups.start.auto", "no");
				}

				/* Low Batt at time */
				ser_send (upsfd,"%c%s",GETX_LOW_BATT_TIME,COMMAND_END);
				if(!OneacGetResponse (buffer, sizeof(buffer), 2))
				{
					strncpy(buffer2, buffer, 2);
					buffer2[2]='\0';
					timevalue = atoi(buffer2) * 60;		/* Mins to secs */
					dstate_setinfo("battery.runtime.low", "%d",timevalue);
				}

				/* Shutdown timer */
				ser_send (upsfd,"%c%s",GETX_SHUTDOWN,COMMAND_END);
				if(!OneacGetResponse (buffer, sizeof(buffer), 
													GETX_SHUTDOWN_RESP_SIZE))
				{
					/* ON would have handled NO_VALUE_YET and setting FSD 
					 *  above so only deal with counter value here.
					 */
					if (buffer[0] != NO_VALUE_YET)
					{
						EliminateLeadingZeroes (buffer, 5, buffer2, 
															sizeof(buffer2));
						dstate_setinfo("ups.timer.shutdown", "%s", buffer2);
					}
				}

				/* Restart timer */
				ser_send (upsfd,"%s%s",GETX_RESTART_COUNT,COMMAND_END);
				if(!OneacGetResponse (buffer, sizeof(buffer), 
														GETX_RSTRT_RESP_SIZE))
				{
					if (atoi(buffer) == 0)
					{
						dstate_delinfo("ups.timer.start");
					}
					else
					{
						EliminateLeadingZeroes (buffer, 4, buffer2, 
															sizeof(buffer2));
						dstate_setinfo("ups.timer.start", "%s", buffer2);
					}
				}
			}
		}

		alarm_commit();
		status_commit();

		/* If the comm retry counter is zero then datastale has been set.
		 *  We don't want to set dataok or ser_comm_good if that is the case.
		 */

		if (CommTry != 0)
		{
			dstate_dataok();
			ser_comm_good();
		}
	}
}

void upsdrv_shutdown(void)
{
	ser_send(upsfd,"%s",SHUTDOWN);
}

void upsdrv_help(void)
{
	printf("\n---------\nNOTE:\n");
	printf("You must set the UPS interface card DIP switch to 9600 BPS\n");
}

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

void upsdrv_makevartable(void)
{
	addvar(VAR_VALUE,"testtime",
		"Change battery test time from the 2 minute default.");

	addvar(VAR_VALUE,"offdelay",
		"Change shutdown delay time from 0 second default.");
}

int instcmd(const char *cmdname, const char *extra)
{
	int i;

	upsdebugx(2, "In instcmd with %s and extra %s.", cmdname, extra);

	if (!strcasecmp(cmdname, "test.failure.start")) {
		ser_send(upsfd,"%s%s",SIM_PWR_FAIL,COMMAND_END);
		return STAT_INSTCMD_HANDLED;
	}

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

		i = atoi(dstate_getinfo("ups.delay.shutdown"));

		if ((strncmp (UpsFamily, FAMILY_OZ, FAMILY_SIZE) == 0) ||
			(strncmp (UpsFamily, FAMILY_OB, FAMILY_SIZE) == 0))
		{
			upsdebugx(3, "Shutdown using %c%d...", DELAYED_SHUTDOWN_PREFIX, i);
			ser_send(upsfd,"%c%d%s",DELAYED_SHUTDOWN_PREFIX, i, COMMAND_END);
		}
		else
		{
			upsdebugx(3, "Shutdown using %c%03d...",DELAYED_SHUTDOWN_PREFIX, i);
			ser_send(upsfd,"%c%03d%s",DELAYED_SHUTDOWN_PREFIX, i, COMMAND_END);
		}

		return STAT_INSTCMD_HANDLED;
	}
	
	if(!strcasecmp(cmdname, "shutdown.reboot")) {
		ser_send(upsfd, "%s", SHUTDOWN);
		return STAT_INSTCMD_HANDLED;
	}

	if (!strcasecmp(cmdname, "shutdown.stop")) {
		ser_send(upsfd,"%c%s",DELAYED_SHUTDOWN_PREFIX,COMMAND_END);
		return STAT_INSTCMD_HANDLED;
	}

	if (!strcasecmp(cmdname, "test.battery.start.quick")) {
		do_battery_test();
		return STAT_INSTCMD_HANDLED;
	}

	if (!strcasecmp(cmdname, "test.battery.start.deep")) {
		ser_send(upsfd, "%s%s", TEST_BATT_DEEP, COMMAND_END);
		return STAT_INSTCMD_HANDLED;
	}

	if (!strcasecmp(cmdname, "test.battery.stop")) 
	{
		if ((strncmp (UpsFamily, FAMILY_EG, FAMILY_SIZE) == 0) ||
			(strncmp (UpsFamily, FAMILY_ON, FAMILY_SIZE) == 0))
		{
			ser_send(upsfd,"%s00%s",BAT_TEST_PREFIX,COMMAND_END);
		}
		else
		{
			ser_send(upsfd,"%c%s",TEST_ABORT,COMMAND_END);
		}			
		return STAT_INSTCMD_HANDLED;
	}

	if (!strcasecmp(cmdname, "reset.input.minmax")) {
		ser_send(upsfd,"%c%s",RESET_MIN_MAX, COMMAND_END);
		return STAT_INSTCMD_HANDLED;
	}

	if (!strcasecmp(cmdname, "beeper.enable")) {
		ser_send(upsfd,"%c%c%s",SETX_BUZZER_PREFIX, BUZZER_ENABLED,COMMAND_END);
		return STAT_INSTCMD_HANDLED;
	}

	if (!strcasecmp(cmdname, "beeper.disable")) {
		ser_send(upsfd,"%c%c%s",SETX_BUZZER_PREFIX,BUZZER_DISABLED,COMMAND_END);
		return STAT_INSTCMD_HANDLED;
	}

	if (!strcasecmp(cmdname, "beeper.mute")) {
		ser_send(upsfd,"%c%c%s",SETX_BUZZER_PREFIX, BUZZER_MUTED, COMMAND_END);
		return STAT_INSTCMD_HANDLED;
	}

	if (!strcasecmp(cmdname, "test.panel.start")) {
		ser_send(upsfd,"%s%s",TEST_INDICATORS, COMMAND_END);
		return STAT_INSTCMD_HANDLED;
	}

	upslogx(LOG_NOTICE, "instcmd: unknown command [%s]", cmdname);
	return STAT_INSTCMD_UNKNOWN;
}


int setcmd(const char* varname, const char* setvalue)
{
	upsdebugx(2, "In setcmd for %s with %s...", varname, setvalue);

	if (!strcasecmp(varname, "ups.delay.shutdown"))
	{
		if ((strncmp (UpsFamily, FAMILY_OZ, FAMILY_SIZE) == 0) ||
			(strncmp (UpsFamily, FAMILY_OB, FAMILY_SIZE) == 0))
		{
			if (atoi(setvalue) > 65535)
			{
				upsdebugx(2, "Too big for OZ/OB (>65535)...(%s)", setvalue);
				return STAT_SET_UNKNOWN;
			}
		}
		else
		{
			if (atoi(setvalue) > 999)
			{
				upsdebugx(2, "Too big for EG/ON (>999)...(%s)", setvalue);
				return STAT_SET_UNKNOWN;
			}
		}

		dstate_setinfo("ups.delay.shutdown", "%s", setvalue);
		return STAT_SET_HANDLED;
	}

	if (!strcasecmp(varname, "input.transfer.low"))
	{
		if (SetOutputAllow(setvalue, dstate_getinfo("input.transfer.high")))
		{
			return STAT_SET_UNKNOWN;
		}
		else
		{
			dstate_setinfo("input.transfer.low" , "%s", setvalue);
			return STAT_SET_HANDLED;
		}
	}

	if (!strcasecmp(varname, "input.transfer.high"))
	{
		if (SetOutputAllow(dstate_getinfo("input.transfer.low"), setvalue))
		{
			return STAT_SET_UNKNOWN;
		}
		else
		{
			dstate_setinfo("input.transfer.high" , "%s", setvalue);
			return STAT_SET_HANDLED;
		}
	}

	if (!strcasecmp(varname, "battery.date"))
	{
		if(strlen(setvalue) == GETX_DATE_RESP_SIZE)		/* yymmdd (6 chars) */
		{
			ser_send(upsfd, "%s%s%s", SETX_BATTERY_DATE, setvalue, COMMAND_END);
			dstate_setinfo("battery.date", "%s (yymmdd)", setvalue);
			return STAT_SET_HANDLED;
		}
		else
		{
			return STAT_SET_UNKNOWN;
		}
	}

	if (!strcasecmp(varname, "ups.delay.start"))
	{
		if (atoi(setvalue) <= 9999)
		{
			ser_send(upsfd,"%s%s%s",SETX_RESTART_DELAY, setvalue, COMMAND_END);

			dstate_setinfo("ups.delay.start", "%s", setvalue);
			return STAT_SET_HANDLED;
		}
		else
		{
			return STAT_SET_UNKNOWN;
		}
	}

	if (!strcasecmp(varname, "battery.runtime.low"))
	{
		if (atoi(setvalue) <= 99)
		{
			ser_send(upsfd,"%s%s%s",SETX_LOWBATT_AT, setvalue, COMMAND_END);
	
			dstate_setinfo("battery.runtime.low", "%s", setvalue);
			return STAT_SET_HANDLED;
		}
		else
		{
			return STAT_SET_UNKNOWN;
		}
	}

	if (!strcasecmp(varname, "ups.start.auto"))
	{
		if (!strcasecmp(setvalue, "yes"))
		{
			ser_send(upsfd,"%c0%s",SETX_AUTO_START, COMMAND_END);
			dstate_setinfo("ups.start.auto", "yes");
			return STAT_SET_HANDLED;
		}
		else if (!strcasecmp(setvalue, "no"))
		{
			ser_send(upsfd,"%c1%s",SETX_AUTO_START, COMMAND_END);
			dstate_setinfo("ups.start.auto", "no");
			return STAT_SET_HANDLED;
		}
		
		return STAT_SET_UNKNOWN;
	}

	upslogx(LOG_NOTICE, "setcmd: unknown command [%s]", varname);

	return STAT_SET_UNKNOWN;
}
