/* nitram.c - Network UPS Tools driver for Nitram Systems units
   This driver support:
    - Nitram Elite 2005 (manufactured by Cyber Power)

   Copyrights:
   (C) 2005 Olivier Albiez <oalbiez@free.fr>
   (C) 2005 Nadine Albiez <oalbiez@free.fr>

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/


#include <string.h>
#include <sys/ioctl.h>

#include "main.h"
#include "serial.h"
#include "nitram.h"

#define ENDCHAR	'\r'
#define IGNCHARS ""
#define UPSDELAY 50000

#define VOLTAGE_RANGE 3


struct status_t
{
	char input_tag;
	char input[5];
	char output_tag;
	char output[5];
	char load_tag;
	char load[3];
	char battery_tag;
	char battery[3];
	char temperature_tag;
	char temperature[3];
	char frequency_tag;
	char frequency[5];
	char flags_tag;
	char flags[2];
};


struct fields_t
{
	char* field[10];
	int count;
};


struct buffer_t
{
	char frame[128];
	char* payload;
	size_t length;
};


static void send_command(const char* command)
{
	ser_send_pace(upsfd, UPSDELAY, "%s\r", command);
}


static void send_command_and_flush(const char* command)
{
	struct buffer_t reply;
	send_command(command);
	usleep(100000);
	ser_get_line(upsfd, reply.frame, sizeof(reply.frame), ENDCHAR, IGNCHARS, 3, 0);
}


static int execute_command(const char* command, struct buffer_t* reply)
{
	int status;
	send_command(command);
	usleep(100000);
	status = ser_get_line(upsfd, reply->frame, sizeof(reply->frame), ENDCHAR, IGNCHARS, 3, 0);

	if (status <= 1)
	{
		ser_comm_fail("Invalid frame size");
		memset(reply, 0, sizeof(struct buffer_t));
		return 0;
	}
	else if (reply->frame[0] != '#')
	{
		ser_comm_fail("Invalid frame marker 0x%02x", reply->frame[0]);
		memset(reply, 0, sizeof(struct buffer_t));
		return 0;
	}

	ser_comm_good();
	reply->payload = reply->frame+1;
	reply->length = status-1;
	return 1;
}


static struct fields_t extract_fields(struct buffer_t* reply)
{
	struct fields_t result;
	char* payload = reply->payload;
	char* position;
	result.count = 0;
	while ((position = strchr(payload, ',')) != NULL)
	{
		*position++ = 0;
		result.field[result.count] = payload;
		++result.count;
		payload = position;
	}
	result.field[result.count] = payload;
	++result.count;
	return result;
}


static int extract_status(struct buffer_t* reply, struct status_t** status)
{
	if (reply->length != sizeof(struct status_t))
	{
		return 0;
	}

	*status = (struct status_t*)(reply->payload);
	#define CHECK_AND_CLEAR_TAG(name, value) if ((*status)->name != (value)) return 0; else (*status)->name = 0
	CHECK_AND_CLEAR_TAG(input_tag, 'I');
	CHECK_AND_CLEAR_TAG(output_tag, 'O');
	CHECK_AND_CLEAR_TAG(load_tag, 'L');
	CHECK_AND_CLEAR_TAG(battery_tag, 'B');
	CHECK_AND_CLEAR_TAG(temperature_tag, 'T');
	CHECK_AND_CLEAR_TAG(frequency_tag, 'F');
	CHECK_AND_CLEAR_TAG(flags_tag, 'S');
	#undef CHECK_AND_CLEAR_TAG
	return 1;
}


static int get_identification(struct buffer_t* reply)
{
	int tries;
	ser_flush_in(upsfd, IGNCHARS, 0);
	for (tries = 0; tries < 3; tries++)
	{
		if (execute_command("P4", reply) == 1)
			return 1;
	}
	upslogx(LOG_INFO, "Giving up on hardware detection after 3 tries");
	return 0;
}


static int instcmd(const char *command, const char *extra)
{
	#define DEFINE_COMMAND(name, nitram_command) \
		if (strcasecmp(command, (name)) == 0) \
		{ \
			send_command_and_flush(nitram_command); \
			return STAT_INSTCMD_HANDLED; \
		}
	DEFINE_COMMAND("test.battery.start", "T.1");
	DEFINE_COMMAND("test.battery.stop", "CT");
	DEFINE_COMMAND("beeper.on", "C7:1");
	DEFINE_COMMAND("beeper.off", "C7:0");
	#undef DEFINE_COMMAND

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


static int setvar(const char *varname, const char *val)
{
	struct buffer_t reply;
	char command[50];

	if (strcasecmp(varname, "battery.charge.low") == 0)
	{
		snprintf(command, sizeof(command), "C4:%s", val);
		command[sizeof(command)] = 0;
		if (execute_command(command, &reply) == 1)
		{
			dstate_setinfo("battery.charge.low", val);
		}
		return STAT_INSTCMD_HANDLED;
	}

	upslogx(LOG_NOTICE, "setvar: unknown var [%s]", varname);
	return STAT_SET_UNKNOWN;
}


void upsdrv_initinfo(void)
{
	struct buffer_t reply;
	struct fields_t fields;

	if (get_identification(&reply) == 1)
	{
		fields = extract_fields(&reply);
		dstate_setinfo("ups.model", "%s", fields.field[0]);
		dstate_setinfo("ups.firmware", "%s", fields.field[1]);
		dstate_setinfo("ups.mfr", "CyberPower for Nitram");
		dstate_setinfo("driver.version.internal", "%s", DRV_VERSION);
	}
	else
	{
		fatalx(EXIT_FAILURE, "Unable to get initial hardware info string");
	}

	dstate_setinfo("battery.charge.low", "20");

	if (execute_command("P1", &reply) == 1)
	{
		fields = extract_fields(&reply);
		dstate_setinfo("output.voltage.nominal", "%s", fields.field[0]);
		dstate_setinfo("input.voltage.maximum", "%s", fields.field[1]);
		dstate_setinfo("input.voltage.minimum", "%s", fields.field[2]);
		dstate_setinfo("battery.charge.low", "%s", fields.field[3]);
	}

	if (execute_command("P3", &reply) == 1)
	{
		fields = extract_fields(&reply);
		dstate_setinfo("battery.voltage", "%s", fields.field[0]);
		dstate_setinfo("battery.packs", "%s", fields.field[1]);
		dstate_setinfo("battery.current", "%s", fields.field[2]);
	}

	dstate_setflags("battery.charge.low", ST_FLAG_STRING | ST_FLAG_RW);
	dstate_setaux("battery.charge.low", 20);
	dstate_addcmd("test.battery.start");
	dstate_addcmd("test.battery.stop");
	dstate_addcmd("beeper.on");
	dstate_addcmd("beeper.off");
	upsh.instcmd = instcmd;
	upsh.setvar = setvar;
}


void upsdrv_updateinfo(void)
{
	struct buffer_t reply;
	struct status_t* status;

	if (execute_command("D", &reply) == 0)
	{
		dstate_datastale();
		return;
	}

	if (extract_status(&reply, &status) == 0)
	{
		dstate_datastale();
		return;
	}

	dstate_setinfo("input.frequency", "%s", status->frequency);
	dstate_setinfo("ups.temperature", "%s", status->temperature);
	dstate_setinfo("battery.charge", "%s", status->battery);
	dstate_setinfo("ups.load", "%s", status->load);
	dstate_setinfo("input.voltage", "%s", status->input);
	dstate_setinfo("output.voltage", "%s", status->output);

	status_init();
	if (status->flags[0] & 0x40)
		status_set("OB");
	else
		status_set("OL");

	if (status->flags[0] & 0x20)
		status_set("LB");

	if (atoi(status->input) > atoi(status->output) + VOLTAGE_RANGE)
		status_set("TRIM");

	if (atoi(status->input) < atoi(status->output) - VOLTAGE_RANGE)
		status_set("BOOST");

	if ((atoi(status->battery) == 100) && !(status->flags[0] & 0x40))
		status_set("BYPASS");

	status_commit();
	dstate_dataok();

	upsdebugx(LOG_DEBUG, "status:%s; load:%s; battery:%s; input:%s; output:%s; frequency:%s;",
		dstate_getinfo("ups.status"),
		status->load,
		status->battery,
		status->input,
		status->output,
		status->frequency);
}


void upsdrv_shutdown(void)
{
	fatalx(EXIT_FAILURE, "shutdown not supported");
}


void upsdrv_help(void)
{
}


void upsdrv_makevartable(void)
{
}


void upsdrv_banner(void)
{
	printf("Network UPS Tools - Nitram UPS driver %s (%s)\n\n",
		DRV_VERSION, UPS_VERSION);
}


void upsdrv_initups(void)
{
	int dtr_bit = TIOCM_DTR;
	int rts_bit = TIOCM_RTS;

	upsfd = ser_open(device_path);
	ser_set_speed(upsfd, device_path, B2400);

	/* dtr high, rts high */
	ioctl(upsfd, TIOCMBIS, &rts_bit);
	ioctl(upsfd, TIOCMBIS, &dtr_bit);
}


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





syntax highlighted by Code2HTML, v. 0.9.1