/* c-mos.c - Support for the C-MOS UPS, made in Rosario - Argentina.
 *
 *    Copyright (C) 2002 Juan Pablo Giménez <jpg@rcom.com.ar>
 *    Copyright (C) 2007 Maximiliano Curia <maxy@gnuservers.com.ar>
 *
 *       This program is free software; you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation; either version 2 of the License, or
 *     (at your option) any later version.
 *
 *       This program is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 *
 *       You should have received a copy of the GNU General Public License
 *     along with this program; if not, write to the Free Software
 *     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 */

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

#define CMOS_INIT "\x1A\x32"
#define CMOS_BTEST "\xCC\xEE"
#define CMOS_OFF "\x55\x3A"
#define CMOS_END "\x1A\x77"

#define CMOS_LINE (unsigned char) '\xAA'
#define CMOS_FUSE (unsigned char) '\x8F'
#define CMOS_BATT (unsigned char) '\xCC'
#define CMOS_LBATT (unsigned char) '\xF0'
#define CMOS_TEST (unsigned char) '\xB3'

#define CMOS_DELAY (unsigned long) 1000000

#define DRV_VERSION	"0.03"

static int instcmd (const char *cmdname, const char *extra)
{
	if (!strcasecmp(cmdname, "test.battery.stop") || !strcasecmp(cmdname, "test.battery.start")) {
		ser_send_pace(upsfd, CMOS_DELAY, CMOS_BTEST);
		return STAT_INSTCMD_HANDLED;
	}
	if (!strcasecmp(cmdname, "shutdown.reboot") || !strcasecmp(cmdname, "load.off")) {
		ser_send_pace(upsfd, CMOS_DELAY, CMOS_OFF);
		return STAT_INSTCMD_HANDLED;
	}
	upslogx(LOG_NOTICE, "instcmd: unknown command [%s]", cmdname);
	return STAT_INSTCMD_UNKNOWN;
}

void upsdrv_initinfo(void)
{
	int ret;
	int i,found;
	unsigned char value;
	/* We'll try to fetch at most 3 bytes from the UPS and we expect to read
	 * one "state" byte, after that, we'll need to read some non-state bytes
	 * to make sure the next read would fetch a state byte.
	 * */

	/* try to detect the UPS here - call fatal() if it fails */
	ret = ser_send_pace(upsfd, CMOS_DELAY, CMOS_INIT);
	if (ret < 2) {
		fatal("Could not initialize UPS.");
	}
	for (found=0,i=1;i<3;i++) {
		ret = ser_get_char(upsfd, &value, 5, 0);
		if (ret < 1) {
			fatal("Could not read from UPS");
		}
		switch (value) {
			case CMOS_FUSE:
			case CMOS_LINE:
			case CMOS_TEST:
			case CMOS_BATT:
			case CMOS_LBATT:
				found=i;
				break;
		}
		if (found != 0) {
			break;
		}
	}
	if (found == 0) {
		fatal("Could not get a valid state from the UPS");
	} else {
		if (value == CMOS_LINE) {
			ret = ser_get_char(upsfd, &value, 5, 0);
		}
		ret = ser_get_char(upsfd, &value, 5, 0);
	}

	dstate_setinfo("driver.version.internal", "%s", DRV_VERSION);
	dstate_setinfo("ups.mfr", "C-MOS");
	dstate_setinfo("ups.model", "UNKNOWN");
	dstate_setflags("ups.model", ST_FLAG_STRING | ST_FLAG_RW);
	dstate_setinfo("ups.power.nominal", "UNKNOWN");
	dstate_setflags("ups.power.nominal", ST_FLAG_STRING | ST_FLAG_RW);
	dstate_setinfo("ups.load", "N/D");
	dstate_setinfo("ups.temperature", "N/D");
	dstate_setinfo("input.voltage", "UNKNOWN");
	dstate_setinfo("input.voltage.nominal", "220");
	dstate_setinfo("battery.charge", "UNKNOWN");
	dstate_setinfo("battery.voltage", "UNKNOWN");
	dstate_setinfo("battery.voltage.nominal", "12");
	dstate_setinfo("battery.packs", "UNKNOWN");
	dstate_setflags("battery.packs", ST_FLAG_STRING | ST_FLAG_RW);
	dstate_setinfo("output.voltage.target.line", "220");
	dstate_setinfo("output.voltage.target.battery", "220");

	upsh.instcmd = instcmd;
}

void upsdrv_updateinfo(void)
{
	unsigned char state, linev, batteryv;
	char buf[16];
	int ret;

	ret = ser_get_char(upsfd, &state, 5, 0);
	if (ret < 1) {
		upslogx(LOG_ERR, "Could not read state");
		dstate_datastale();
		return;
	}

	status_init();

	/* upslog(LOG_INFO, "got %X, expected %X", state, CMOS_LINE); */
	switch(state) {
		case CMOS_FUSE:
		case CMOS_LINE:
			status_set("OL");
			break;

		case CMOS_TEST:
		case CMOS_BATT:
			status_set("OB");
			break;

		case CMOS_LBATT:
			status_set("OB");
			status_set("LB");
			break;

		default:
			upslog(LOG_ERR, "Unhandled state");
			upslog(LOG_INFO, "got %X", state);
			dstate_datastale();
			return;
			break;
	}

	status_commit();
	
	switch (state) {
		case CMOS_FUSE:
		case CMOS_LINE:
			ret = ser_get_char(upsfd, &linev, 5, 0);
			if (ret < 1) {
				upslogx(LOG_ERR, "Could not read line voltage");
				dstate_datastale();
				return;
			}
			/* upslog(LOG_INFO, "got %X, expected %X", linev, 110); */
			sprintf(buf, "%d", linev * 2);
			dstate_setinfo("input.voltage", buf);
			break;
	}

	switch (state) {
		case CMOS_BATT:
		case CMOS_LBATT:
		case CMOS_TEST:
		case CMOS_LINE:
			ret = ser_get_char(upsfd, &batteryv, 5, 0);
			if (ret < 1) {
				upslogx(LOG_ERR, "Could not read battery voltage");
				dstate_datastale();
				return;
			}
			/* upslog(LOG_INFO, "got %X, expected %X", linev, 110); */
			sprintf(buf, "%.2f", (double) batteryv / (double) 8);
			dstate_setinfo("battery.voltage", buf);

			sprintf(buf, "%.2f", ((double) batteryv / (double) 8 ) * ( (double) 100 / (double) 13.8 ) );
			dstate_setinfo("battery.charge", buf);
			break;
	}

	dstate_dataok();
}


void upsdrv_shutdown(void)
{
	/* replace with a proper shutdown function */
	ser_send_pace(upsfd, CMOS_DELAY, CMOS_BTEST);
	ser_send_pace(upsfd, CMOS_DELAY, CMOS_OFF);
	ser_send_pace(upsfd, CMOS_DELAY, CMOS_END);
}

int setvar(const char *varname, const char *val)
{
	if (strcasecmp(varname, "ups.model") == 0) {
		dstate_setinfo( "ups.delay.start", "%s", val);
		return STAT_SET_HANDLED;
	}

	if (strcasecmp(varname, "ups.power.nominal") == 0) {
		dstate_setinfo( "ups.power.nominal", "%s", val);
		return STAT_SET_HANDLED;
	}

	if (strcasecmp(varname, "battery.packs") == 0) {
		dstate_setinfo( "battery.packs", "%s", val);
		return STAT_SET_HANDLED;
	}

	return STAT_SET_UNKNOWN;
}

void upsdrv_help(void)
{
}

/* list flags and values that you want to receive via -x */
void upsdrv_makevartable(void)
{
	addvar(VAR_VALUE, "ups.model", "Set UPS Model");
	addvar(VAR_VALUE, "ups.power.nominal", "Set UPS Volt-Amp");
	addvar(VAR_VALUE, "battery.packs", "Set number of battery packs");
}

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

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

	/*
	 * upssend_delay = 100;
	 * upssend(CMOS_INIT);
	 */
}

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


