Hi,
  I just acquired a Ritmo AS-800VA.
Unfortunately, it doesn't work out of the box with nut - so I had to do a
bit of reverse engineering on the protocol to get it working. Am now
posting my findings here in the hope it will get this added to the next
release.

There are also 1000VA and 12000VA versions that this should work with.

The protocol is very similar to the megatec protocol - although simpler.
Like megatec - it responds to a 'Q1' request.
The response is slightly different - instead of the megatec 46 character
reply string - the ritmo unit replies with a 35 character string of the
form:

(AAA BBB CCC DD.D EE.E FFF GGGGGGGG

Where:
        AAA is input voltage
        BBB is output voltage
        CCC is load - not sure what the range is - when my server plugged in and
running - this is 4
        DD.D is input frequency
        EE.E is output frequency
        FFF is battery voltage - seems to be 195 when fully charged - about 160
when flat.
        GGGGGGGG is a bit mask of the form b7b6b5b4b3b2b1b0. So far I've only
established b7=1 when on battery power, b6=1 when battery level is
critical. Have not seen the remaining bits change from 0.

A sample response to the "Q1" request when my server is running normally is:
(233 233 004 50.0 50.0 195 00000000

The unit does not respond to any of the other megatec queries.

I have modified megatec.c with changes to support this - and attached it
to this email.
I don't think I broke anything megatec related - but it would be prudent
if someone tested it to make sure.
Have been running it on my server for the last week or so with no probs -
so the ritmo code is ok.

Other notes:
1) The unit requires rts to be set high, and is also slower than the
megatec units, so my corresponding ups.conf entry is:
[Ritmo800]
        driver = megatec
        port = /dev/ttyS0
        sendpace = 300
        dtr = 1
        rts = 1
        battvolts=160:195

2) The critically low battery flag turns on quite late - so there isn't
really enough time to switch off a pc when this turns on - however the
battery itself lasts as long as you would expect from any other 800VA UPS.

Let me know if you have questions.
Thanks,
-Gino

/* -*- mode: c; c-basic-offset: 8; indent-tabs-mode: t; -*-
 *
 * megatec.c: support for Megatec protocol based UPSes
 *
 * Copyright (C) Carlos Rodrigues <carlos.efr at mail.telepac.pt>
 *
 * megatec.c created on 4/10/2003
 *
 * 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
 */


/*
 * A document describing the protocol implemented by this driver can be
 * found online at "http://www.networkupstools.org/protocols/megatec.html";.
 */


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

#include <stdio.h>
#include <limits.h>
#include <string.h>
#include <stdlib.h>

#define DRIVER_NAME	"Megatec protocol driver"
#define DRIVER_VERSION	"1.6" /* FIXME: doesn't conform to "X.YZ" */

/* driver description structure */
upsdrv_info_t upsdrv_info = {
	DRIVER_NAME,
	DRIVER_VERSION,
	"Carlos Rodrigues <[email protected]>",
	DRV_STABLE,
#ifdef MEGATEC_SUBDRV
	{ &megatec_subdrv_info, NULL }
#else
	{ NULL }
#endif
};

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

#define RECV_BUFFER_LEN 128
#define FIELD_BUFFER_LEN 16

/* The expected reply lengths */
#define F_CMD_REPLY_LEN  21
#define Q1_CMD_REPLY_LEN 46
#define Q1_RITMO_CMD_REPLY_LEN 35
#define I_CMD_REPLY_LEN  38

#define IDENT_MAXTRIES   5
#define IDENT_MINSUCCESS 3

#define READ_TIMEOUT 2       /* timeout on read (seconds) */
#define READ_PACE    300000  /* interval to wait between sending a command and reading the response (usec) */

#define MAX_START_DELAY    9999
#define MAX_SHUTDOWN_DELAY 99

/* Maximum length of a string representing these values */
#define MAX_START_DELAY_LEN    4
#define MAX_SHUTDOWN_DELAY_LEN 2

#define MAX_POLL_FAILURES 3

#define N_FLAGS 8

/* The UPS status flags */
#define FL_ON_BATT    0
#define FL_LOW_BATT   1
#define FL_BOOST_TRIM 2
#define FL_FAILED     3
#define FL_UPS_TYPE   4
#define FL_BATT_TEST  5
#define FL_LOAD_OFF   6
#define FL_BEEPER_ON  7  /* bogus on some models */

/* Maximum lengths for the "I" command reply fields */
#define UPS_MFR_CHARS     15
#define UPS_MODEL_CHARS   10
#define UPS_VERSION_CHARS 10

/* Below this value we can safely consider a voltage to be zero */
#define RESIDUAL_VOLTAGE 10.0


/* The values returned by the UPS for an "I" query */
typedef struct {
	char mfr[UPS_MFR_CHARS + 1];
	char model[UPS_MODEL_CHARS + 1];
	char version[UPS_VERSION_CHARS + 1];
} UPSInfo_t;


/* The values returned by the UPS for an "F" query */
typedef struct {
	float volt;
	float current;
	float battvolt;
	float freq;
} FirmwareValues_t;


/* The values returned by the UPS for an "Q1" query */
typedef struct {
	float ivolt;
	float fvolt;
	float ovolt;
	float load;
	float freq;
	float battvolt;
	float temp;
	float outfreq;
	char flags[N_FLAGS + 1];
} QueryValues_t;


/* Parameters for known battery types */
typedef struct {
	float nominal;  /* battery voltage (nominal) */
	float min;      /* lower bound for a single battery of "nominal" voltage (see "set_battery_params" below) */
	float max;      /* upper bound for a single battery of "nominal" voltage (see "set_battery_params" below) */
	float empty;    /* fully discharged battery */
	float full;     /* fully charged battery */
	float low;      /* low battery (unused) */
} BatteryVolts_t;


/* Known battery types must be in ascending order by "nominal" first, and then by "max". */
static BatteryVolts_t batteries[] = {{ 12.0,  9.0, 16.0,  9.7, 13.7,  0.0 },   /* Mustek PowerMust 600VA Plus (LB unknown) */
                                     { 12.0, 18.0, 30.0, 18.8, 26.8, 22.3 },   /* PowerWalker Line-Interactive VI 1000 */
                                     { 23.5, 18.0, 30.0, 21.3, 27.1, 22.2 },   /* UNITEK ALPHA2600 */
                                     { 24.0, 18.0, 30.0, 19.4, 27.4, 22.2 },   /* Mustek PowerMust 1000VA Plus */
                                     { 36.0,  1.5,  3.0, 1.64, 2.31, 1.88 },   /* Mustek PowerMust 1000VA On-Line */
                                     { 36.0, 30.0, 42.0, 32.5, 41.0,  0.0 },   /* Mecer ME-2000 (LB unknown) */
                                     { 48.0, 38.0, 58.0, 40.0, 54.6, 44.0 },   /* Sven Smart RM2000 */
                                     { 72.0,  1.5,  3.0, 1.74, 2.37, 1.82 },   /* Effekta RM2000MH */
                                     { 96.0,  1.5,  3.0, 1.63, 2.29,  1.8 },   /* Ablerex MS3000RT (LB at 25% charge) */
                                     {  0.0,  0.0,  0.0,  0.0,  0.0,  0.0 }};  /* END OF DATA */

/* Interval between chars on send (usec) */
static int send_pace = 100000;

/* Some models need this */
static char state_dtr = 1;
static char state_rts = 0;

/* Workaround for buggy models */
static char ignore_off = 0;  /* ignore FL_LOAD_OFF if it behaves strangely */

/* Defined in upsdrv_initinfo */
static float battvolt_empty = -1;  /* unknown */
static float battvolt_full = -1;   /* unknown */

/* Battery voltage multiplier (to match the nominal voltage on some models) */
static float battvolt_mult = 1;

/* Minimum and maximum voltage seen on input */
static float ivolt_min = INT_MAX;  /* unknown */
static float ivolt_max = -1;       /* unknown */

/* In minutes: */
static short start_delay = 2;     /* wait at least this amount of time before coming back online */
static short shutdown_delay = 0;  /* wait until going offline */

/* In percentage: */
static float lowbatt = -1;  /* disabled */

static char watchdog_enabled = 0;  /* disabled by default, of course */
static char watchdog_timeout = 1;  /* in minutes */


static char *copy_field(char* dest, char *src, int field_len);
static float get_battery_charge(float battvolt);
static int set_battery_params(float volt_nominal, float volt_now);
static int check_ups(QueryValues_t *status);
static int get_ups_info(UPSInfo_t *info);
static int get_firmware_values(FirmwareValues_t *values);
static int run_query(QueryValues_t *values);

int instcmd(const char *cmdname, const char *extra);
int setvar(const char *varname, const char *val);


/* I know, macros should evaluate their arguments only once */
#define CLAMP(x, min, max) (((x) < (min)) ? (min) : (((x) > (max)) ? (max) : (x)))


static char *copy_field(char* dest, char *src, int field_len)
{
	int i, j;

	/* First we skip the leading spaces... */
	for (i = 0; i < field_len; i++) {
		if (src[i] != ' ') {
			break;
		}
	}

	/* ... then we copy the rest of the field... */
	j = 0;
	while (i < field_len) {
		dest[j] = src[i];

		i++; j++;
	}

	dest[j] = '\0';

	/* ...and finally, remove the trailing spaces. */
	rtrim(dest, ' ');

	return &src[field_len];  /* return the rest of the source buffer */
}


static float get_battery_charge(float battvolt)
{
	float value;

	if (battvolt_empty < 0 || battvolt_full < 0) {
		return -1;
	}

	battvolt = CLAMP(battvolt, battvolt_empty, battvolt_full);
	value = (battvolt - battvolt_empty) / (battvolt_full - battvolt_empty);

	return value * 100;  /* percentage */
}


/*
 * Set the proper limits, depending on the battery voltage,
 * so that the "charge" calculations return meaningful values.
 *
 * This has to be done by looking at the present battery voltage and
 * the nominal voltage because, for example, some 24V models will
 * show a nominal voltage of 24, while others will show a nominal
 * voltage of 12. The present voltage helps telling them apart.
 */
static int set_battery_params(float volt_nominal, float volt_now)
{
	int i = 0;

	while (batteries[i].nominal > 0) {
		if (volt_nominal == batteries[i].nominal) {         /* battery voltage matches... */
			while (volt_nominal == batteries[i].nominal) {  /* ...find the most adequate parameters */
				if (volt_now > batteries[i].min && volt_now < batteries[i].max) {
					battvolt_empty = batteries[i].empty;
					battvolt_full = batteries[i].full;

					upsdebugx(1, "%.1fV battery, interval [%.1fV, %.1fV].", volt_nominal, battvolt_empty, battvolt_full);

					return i;
				}

				i++;

			}

			upsdebugx(1, "%.1fV battery, present voltage (%.1fV) outside of supported intervals.", volt_nominal, volt_now);

			return -1;
		}

		i++;
	}

	upsdebugx(1, "Unsupported battery voltage (%.1fV).", volt_nominal);

	return -1;
}


/*
 * The "status" parameter is left unchanged on failure.
 */
static int check_ups(QueryValues_t *status)
{
	QueryValues_t values;

	if (run_query(&values) < 0) {
		return -1;
	}

	memcpy(status, &values, sizeof(values));

	return 0;
}


static int get_ups_info(UPSInfo_t *info)
{
	char buffer[RECV_BUFFER_LEN];
	char *anchor;
	int ret;

	upsdebugx(2, "Asking for UPS information [I]...");
	ser_flush_io(upsfd);
	ser_send_pace(upsfd, send_pace, "I%c", ENDCHAR);
	usleep(READ_PACE);

	/*
	 * Expected reply: "#UPS_MFR........ UPS_MODEL. UPS_VER...<cr>"
	 */
	ret = ser_get_line(upsfd, buffer, RECV_BUFFER_LEN, ENDCHAR, IGNCHARS, READ_TIMEOUT, 0);

	if (ret < 0) {
		upsdebug_with_errno(2, "I => FAILED");

		return -1;
	}

	if (ret == 0) {
		upsdebugx(2, "I => FAILED [timeout]");

		return -1;
	}

	if (ret < I_CMD_REPLY_LEN) {
		upsdebugx(2, "I => FAILED [short read]");
		upsdebug_hex(3, "I detail", (unsigned char *)buffer, ret);

		return -1;
	}

	if (buffer[0] != '#') {
		upsdebugx(2, "I => FAILED [invalid start character]");
		upsdebug_hex(3, "I detail", (unsigned char *)buffer, ret);

		return -1;
	}

	upsdebugx(2, "I => OK [%s]", buffer);

	memset(info, 0, sizeof(UPSInfo_t));

	/*
	 * Get the manufacturer, model and version fields, skipping
	 * the separator character that sits between them, as well as
	 * the first character (the control character, always a '#').
	 */
	anchor = copy_field(info->mfr, &buffer[1], UPS_MFR_CHARS);
	anchor = copy_field(info->model, anchor + 1, UPS_MODEL_CHARS);
	copy_field(info->version, anchor + 1, UPS_VERSION_CHARS);

	upsdebugx(2, "I VALUES => [%s %s %s]", info->mfr, info->model, info->version);

	return 0;
}


static int get_firmware_values(FirmwareValues_t *values)
{
	char buffer[RECV_BUFFER_LEN];
	char field[FIELD_BUFFER_LEN];
	char *anchor;
	int ret;

	upsdebugx(2, "Asking for UPS power ratings [F]...");
	ser_flush_io(upsfd);
	ser_send_pace(upsfd, send_pace, "F%c", ENDCHAR);
	usleep(READ_PACE);

	/*
	 * Expected reply: "#MMM.M QQQ SS.SS RR.R<cr>"
	 */
	ret = ser_get_line(upsfd, buffer, RECV_BUFFER_LEN, ENDCHAR, IGNCHARS, READ_TIMEOUT, 0);

	if (ret < 0) {
		upsdebug_with_errno(2, "F => FAILED");

		return -1;
	}

	if (ret == 0) {
		upsdebugx(2, "F => FAILED [timeout]");

		return -1;
	}

	if (ret < F_CMD_REPLY_LEN) {
		upsdebugx(2, "F => FAILED [short read]");
		upsdebug_hex(3, "F detail", (unsigned char *)buffer, ret);

		return -1;
	}


	if (buffer[0] != '#') {
		upsdebugx(2, "F => FAILED [invalid start character]");
		upsdebug_hex(3, "F detail", (unsigned char *)buffer, ret);

		return -1;
	}

	upsdebugx(2, "F => OK [%s]", buffer);

	anchor = copy_field(field, &buffer[1], 5);
	values->volt = atof(field);

	anchor = copy_field(field, anchor + 1, 3);
	values->current = atof(field);

	anchor = copy_field(field, anchor + 1, 5);
	values->battvolt = atof(field);

	anchor = copy_field(field, anchor + 1, 4);
	values->freq = atof(field);

	upsdebugx(2, "F VALUES => [%.1f %.1f %.1f %.1f]", values->volt,
	          values->current, values->battvolt, values->freq);

	return 0;
}


static int run_query(QueryValues_t *values)
{
	char buffer[RECV_BUFFER_LEN];
	char field[FIELD_BUFFER_LEN];
	char *anchor;
	int ret;
	int protocolToUse;	//0 = megatec, 1 = ritmo

	upsdebugx(2, "Asking for UPS status [Q1]...");
	ser_flush_io(upsfd);
	ser_send_pace(upsfd, send_pace, "Q1%c", ENDCHAR);
	usleep(READ_PACE);

	/*
	 * Expected reply
	 * (protocol 0 megatec) : "(MMM.M NNN.N PPP.P QQQ RR.R S.SS TT.T b7b6b5b4b3b2b1b0<cr>"
	 * (protocol 1 ritmo)   : "(MMM NNN PPP QQ.Q RR.R SS.S TTT b7b6b5b4b3b2b1b0<cr>"
	 */
	ret = ser_get_line(upsfd, buffer, RECV_BUFFER_LEN, ENDCHAR, IGNCHARS, READ_TIMEOUT, 0);

	if (ret < 0) {
		upsdebug_with_errno(2, "Q1 => FAILED");

		return -1;
	}

	if (ret == 0) {
		upsdebugx(2, "Q1 => FAILED [timeout]");

		return -1;
	}

	if (ret >= Q1_CMD_REPLY_LEN) {
		protocolToUse = 0;
	} else if (ret == Q1_RITMO_CMD_REPLY_LEN) {
		protocolToUse = 1;
	} else {
		upsdebugx(2, "Q1 => FAILED [short read]");
		upsdebug_hex(3, "Q1 detail", (unsigned char *)buffer, ret);

		return -1;
	}

	if (buffer[0] != '(') {
		upsdebugx(2, "Q1 => FAILED [invalid start character]");
		upsdebug_hex(3, "Q1 detail", (unsigned char *)buffer, ret);

		return -1;
	}

	upsdebugx(2, "Q1 => OK [%s]", buffer);
	if (protocolToUse == 0) {
		anchor = copy_field(field, &buffer[1], 5);
		values->ivolt = atof(field);

		anchor = copy_field(field, anchor + 1, 5);
		values->fvolt = atof(field);

		anchor = copy_field(field, anchor + 1, 5);
		values->ovolt = atof(field);

		anchor = copy_field(field, anchor + 1, 3);
		values->load = atof(field);

		anchor = copy_field(field, anchor + 1, 4);
		values->freq = atof(field);

		anchor = copy_field(field, anchor + 1, 4);
		values->battvolt = atof(field);

		anchor = copy_field(field, anchor + 1, 4);
		values->temp = atof(field);

		values->outfreq = 0.0;

		anchor = copy_field(values->flags, anchor + 1, N_FLAGS);

		if (strlen(values->flags) < N_FLAGS) {
			upsdebugx(2, "Q1 => FAILED [flags error]");
			return -1;
		}
	} else if (protocolToUse == 1) {
		anchor = copy_field(field, &buffer[1], 3);
		values->ivolt = atof(field);

		anchor = copy_field(field, anchor + 1, 3);
		values->ovolt = atof(field);

		anchor = copy_field(field, anchor + 1, 3);
		values->load = atof(field);
		

		anchor = copy_field(field, anchor + 1, 4);
		values->outfreq = atof(field);


		anchor = copy_field(field, anchor + 1, 4);
		values->freq = atof(field);

		anchor = copy_field(field, anchor + 1, 3);
		values->battvolt = atof(field);

		values->fvolt = 0.0;
		values->temp = 0.0;

		anchor = copy_field(values->flags, anchor + 1, N_FLAGS);

		if (strlen(values->flags) < N_FLAGS) {
			upsdebugx(2, "Q1 => FAILED [flags error]");
			return -1;
		}		
	}
	upsdebugx(2, "Q1 VALUES => [%.1f %.1f %.1f %.1f %.1f %.1f %.1f %s]",
	          values->ivolt, values->fvolt, values->ovolt, values->load,
	          values->freq, values->battvolt, values->temp, values->flags);

	return 0;
}


void upsdrv_initinfo(void)
{
	int i;
	int success = 0;
	FirmwareValues_t values;
	QueryValues_t status;
	UPSInfo_t info;

	/*
	 * Some models apparently time-out with the default send pace, so we must
	 * allow the user to override it if needed be. The configuration parameter
	 * is specified in milliseconds for the user's benefit.
	 */
	if (getval("sendpace")) {
		upsdebugx(2, "Default command send pace is %d usec.", send_pace);
		upsdebugx(2, "Parameter [sendpace]: [%s]", getval("sendpace"));

		/* Having 1 second as the upper-bound is an arbitrary choice... */
		send_pace = CLAMP(atoi(getval("sendpace")), 1, 1000) * 1000;

		upslogx(LOG_NOTICE, "Command send pace changed to %d usec.", send_pace);
	}

	/*
	 * UPS detection sequence.
	 */
	upsdebugx(1, "Starting UPS detection process...");
	
	/* Some models seem to need this. We'll just discard the ouput for now... */
	get_ups_info(&info);

	/* Check for a compatible UPS and for a reliable connection... */
	for (i = 0; i < IDENT_MAXTRIES; i++) {
		if (check_ups(&status) == 0) {
			success++;
		}
	}

	upsdebugx(1, "%d out of %d detection attempts failed (minimum failures: %d).",
	          IDENT_MAXTRIES - success, IDENT_MAXTRIES, IDENT_MAXTRIES - IDENT_MINSUCCESS);

	if (success < IDENT_MINSUCCESS) {
		if (success > 0) {
			fatalx(EXIT_FAILURE, "The UPS is supported, but the connection is too unreliable. Try checking the cable for defects.");
		} else {
			fatalx(EXIT_FAILURE, "Megatec protocol UPS not detected.");
		}
	}

	dstate_setinfo("ups.type", status.flags[FL_UPS_TYPE] == '1' ? "standby" : "online");

	upsdebugx(1, "Cancelling any pending shutdown or battery test.");
	ser_send_pace(upsfd, send_pace, "C%c", ENDCHAR);

	/*
	 * Try to identify the UPS.
	 */
	if (get_ups_info(&info) >= 0) {
		char model[UPS_MODEL_CHARS + UPS_VERSION_CHARS + 2];
		snprintf(model, sizeof(model), "%s %s", info.model, info.version);

		dstate_setinfo("ups.mfr", "%s", getval("mfr") ? getval("mfr") : info.mfr);
		dstate_setinfo("ups.model", "%s", getval("model") ? getval("model") : model);

		upslogx(LOG_INFO, "Megatec protocol UPS detected [%s %s %s].", info.mfr, info.model, info.version);
	} else {
		dstate_setinfo("ups.mfr", "%s", getval("mfr") ? getval("mfr") : "unknown");
		dstate_setinfo("ups.model", "%s", getval("model") ? getval("model") : "unknown");

		upslogx(LOG_INFO, "Megatec protocol UPS detected.");
	}

	dstate_setinfo("ups.serial", "%s", getval("serial") ? getval("serial") : "unknown");

	/*
	 * Workaround for buggy models.
	 */
	ignore_off = testvar("ignoreoff");

	if (status.flags[FL_LOAD_OFF] == '1' && status.load > 0.01 && !ignore_off) {
		ignore_off = 1;
		upslogx(LOG_INFO, "The UPS reports OFF status but appears to be ON. Parameter \"ignoreoff\" set automatically.");
	}
	upsdebugx(2, "Parameter [ignoreoff]: [%s]", (ignore_off ? "true" : "false"));

	/*
	 * Set battery-related values.
	 */
	if (get_firmware_values(&values) >= 0) {
		dstate_setinfo("battery.voltage.nominal", "%.1f", values.battvolt);
		dstate_setinfo("input.voltage.nominal", "%.1f", values.volt);
		dstate_setinfo("input.frequency.nominal", "%.1f", values.freq);

		if (set_battery_params(values.battvolt, status.battvolt) < 0) {
			upslogx(LOG_NOTICE, "This UPS has an unsupported combination of battery voltage/number of batteries.");
		}
	}

	if (getval("battvoltmult")) {
		upsdebugx(2, "Parameter [battvoltmult]: [%s]", getval("battvoltmult"));

		/* Having SHRT_MAX as the upper-bound is an arbitrary choice... */
		battvolt_mult = CLAMP(atof(getval("battvoltmult")), 1, SHRT_MAX);

		upslogx(LOG_NOTICE, "The battery voltage reported by the UPS will be multiplied by %.1f.", battvolt_mult);
	}

	if (getval("battvolts")) {
		upsdebugx(2, "Parameter [battvolts]: [%s]", getval("battvolts"));

		if (sscanf(getval("battvolts"), "%f:%f", &battvolt_empty, &battvolt_full) != 2) {
			fatalx(EXIT_FAILURE, "Error in \"battvolts\" parameter.");
		}

		upslogx(LOG_NOTICE, "Overriding battery voltage interval [%.1fV, %.1fV].", battvolt_empty, battvolt_full);
	}

	if (battvolt_empty < 0 || battvolt_full < 0) {
		upslogx(LOG_NOTICE, "Cannot calculate charge percentage for this UPS.");
	}

	if (getval("lowbatt")) {
		if (battvolt_empty < 0 || battvolt_full < 0) {
			upslogx(LOG_NOTICE, "Ignoring \"lowbatt\" parameter.");
		} else {
			lowbatt = CLAMP(atof(getval("lowbatt")), 0, 100);
		}
	}

	/*
	 * Set the restart and shutdown delays.
	 */
	if (getval("ondelay")) {
		start_delay = CLAMP(atoi(getval("ondelay")), 0, MAX_START_DELAY);
	}

	if (getval("offdelay")) {
		shutdown_delay = CLAMP(atoi(getval("offdelay")), 0, MAX_SHUTDOWN_DELAY);
	}

	dstate_setinfo("ups.delay.start", "%d", start_delay);
	dstate_setinfo("ups.delay.shutdown", "%d", shutdown_delay);

	/*
	 * Register the available instant commands.
	 */
	dstate_addcmd("test.battery.start.deep");
	dstate_addcmd("test.battery.start");
	dstate_addcmd("test.battery.stop");
	dstate_addcmd("shutdown.return");
	dstate_addcmd("shutdown.stayoff");
	dstate_addcmd("shutdown.stop");
	dstate_addcmd("load.on");
	dstate_addcmd("load.off");
	dstate_addcmd("reset.input.minmax");
	dstate_addcmd("reset.watchdog");
	dstate_addcmd("beeper.toggle");

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

	upsdebugx(1, "Done setting up the UPS.");
}


void upsdrv_updateinfo(void)
{
	QueryValues_t query;
	float charge;
	static int poll_fail = 0;

	if (run_query(&query) < 0) {
		/*
		 * Query wasn't successful (we got some weird
		 * response), however we won't fatalx(EXIT_FAILURE, ) as this
		 * happens sometimes when the ups is offline.
		 *
		 * Some fault tolerance is good, we just assume
		 * that the UPS is just taking a nap. ;)
		 */
		poll_fail++;
		upsdebugx(2, "Poll failure [%d].", poll_fail);		
		ser_comm_fail("No status from UPS.");

		if (poll_fail >= MAX_POLL_FAILURES) {
			upsdebugx(2, "Too many poll failures, data is stale.");
			dstate_datastale();
		}

		return;
	}

	poll_fail = 0;
	ser_comm_good();

	dstate_setinfo("input.voltage", "%.1f", query.ivolt);
	dstate_setinfo("input.voltage.fault", "%.1f", query.fvolt);
	dstate_setinfo("output.voltage", "%.1f", query.ovolt);
	dstate_setinfo("ups.load", "%.1f", query.load);
	dstate_setinfo("input.frequency", "%.1f", query.freq);
	dstate_setinfo("output.frequency", "%.1f", query.outfreq);

	/*
	 * The battery voltage multiplier should only be applied to battery.voltage
	 * in order not to break the charge calculation (that uses the 'raw' value
	 * that is reported by the UPS).
	 */
	dstate_setinfo("battery.voltage", "%.2f", battvolt_mult * query.battvolt);

	if (query.temp > 0.01) {
		dstate_setinfo("ups.temperature", "%.1f", query.temp);
	}

	charge = get_battery_charge(query.battvolt);
	if (charge >= 0) {
		dstate_setinfo("battery.charge", "%.1f", charge);

		upsdebugx(2, "Calculated battery charge: %.1f%%", charge);
	}

	dstate_setinfo("ups.beeper.status", query.flags[FL_BEEPER_ON] == '1' ? "enabled" : "disabled");

	status_init();

	/*
	 * Some models, when OFF, never change to on-battery status when
	 * line power is unavailable. To get around this, we also look at
	 * the input voltage level here.
	 */
	if (query.flags[FL_ON_BATT] == '1' || query.ivolt < RESIDUAL_VOLTAGE) {
		status_set("OB");
	} else {
		status_set("OL");

		if (query.flags[FL_BOOST_TRIM] == '1') {
			if (query.ivolt < query.ovolt) {
				status_set("BOOST");
			} else if (query.ivolt > query.ovolt) {
				status_set("TRIM");
			} else {
				status_set("BYPASS");
			}
		}

		/* Update minimum and maximum input voltage levels too */
		if (query.ivolt < ivolt_min) {
			ivolt_min = query.ivolt;
		}

		if (query.ivolt > ivolt_max) {
			ivolt_max = query.ivolt;
		}

		dstate_setinfo("input.voltage.minimum", "%.1f", ivolt_min);
		dstate_setinfo("input.voltage.maximum", "%.1f", ivolt_max);
	}

	/*
	 * If "lowbatt > 0", it becomes a "soft" low battery level
	 * and the hardware flag "FL_LOW_BATT" is always ignored.
	 */
	if ((lowbatt <= 0 && query.flags[FL_LOW_BATT] == '1') ||
	    (lowbatt > 0 && charge < lowbatt)) {
		status_set("LB");
	}

	if (query.flags[FL_BATT_TEST] == '1') {
		status_set("CAL");
	}

	if (query.flags[FL_LOAD_OFF] == '1' && !ignore_off) {
		status_set("OFF");
	}

	alarm_init();

	if (query.flags[FL_FAILED] == '1') {
		alarm_set("Internal UPS fault!");
	}

	alarm_commit();

	status_commit();

	dstate_dataok();
}


void upsdrv_shutdown(void)
{
	int s_wait = getval("offdelay") ? CLAMP(atoi(getval("offdelay")), 0, MAX_SHUTDOWN_DELAY) : shutdown_delay;
	int r_wait = getval("ondelay") ? CLAMP(atoi(getval("ondelay")), 0, MAX_START_DELAY) : start_delay;

	upslogx(LOG_INFO, "Shutting down UPS.");

	ser_send_pace(upsfd, send_pace, "C%c", ENDCHAR);
	ser_send_pace(upsfd, send_pace, "S%02dR%04d%c", s_wait, r_wait, ENDCHAR);
}


int instcmd(const char *cmdname, const char *extra)
{
	char buffer[RECV_BUFFER_LEN];

	/*
	 * Some commands are always supported by every UPS implementing
	 * the megatec protocol, but others may or may not be supported.
	 * Unsupported commands are echoed back without ENDCHAR.
	 */

	if (strcasecmp(cmdname, "test.battery.start.deep") == 0) {
		ser_flush_io(upsfd);
		ser_send_pace(upsfd, send_pace, "TL%c", ENDCHAR);
		usleep(READ_PACE);

		if (ser_get_line(upsfd, buffer, 2 + 1, '\0', IGNCHARS, READ_TIMEOUT, 0) > 0) {
			upslogx(LOG_NOTICE, "test.battery.start.deep not supported by UPS hardware.");
		} else {
			upslogx(LOG_INFO, "Deep battery test started.");
		}

		return STAT_INSTCMD_HANDLED;
	}

	if (strcasecmp(cmdname, "test.battery.start") == 0) {
		ser_flush_io(upsfd);
		ser_send_pace(upsfd, send_pace, "T%c", ENDCHAR);
		usleep(READ_PACE);

		if (ser_get_line(upsfd, buffer, 1 + 1, '\0', IGNCHARS, READ_TIMEOUT, 0) > 0) {
			upslogx(LOG_NOTICE, "test.battery.start not supported by UPS hardware.");
		} else {
			upslogx(LOG_INFO, "Battery test started.");
		}

		return STAT_INSTCMD_HANDLED;
	}

	if (strcasecmp(cmdname, "test.battery.stop") == 0) {
		ser_flush_io(upsfd);
		ser_send_pace(upsfd, send_pace, "CT%c", ENDCHAR);
		usleep(READ_PACE);

		if (ser_get_line(upsfd, buffer, 2 + 1, '\0', IGNCHARS, READ_TIMEOUT, 0) > 0) {
			upslogx(LOG_NOTICE, "test.battery.stop not supported by UPS hardware.");
		} else {
			upslogx(LOG_INFO, "Battery test stopped.");
		}

		return STAT_INSTCMD_HANDLED;
	}

	if (strcasecmp(cmdname, "shutdown.return") == 0) {
		ser_send_pace(upsfd, send_pace, "C%c", ENDCHAR);
		watchdog_enabled = 0;

		ser_send_pace(upsfd, send_pace, "S%02dR%04d%c", shutdown_delay, start_delay, ENDCHAR);

		upslogx(LOG_INFO, "Shutdown (return) initiated.");

		return STAT_INSTCMD_HANDLED;
	}

	if (strcasecmp(cmdname, "shutdown.stayoff") == 0) {
		ser_send_pace(upsfd, send_pace, "C%c", ENDCHAR);
		watchdog_enabled = 0;

		ser_flush_io(upsfd);
		ser_send_pace(upsfd, send_pace, "S%02d%c", shutdown_delay, ENDCHAR);
		usleep(READ_PACE);

		if (ser_get_line(upsfd, buffer, 3 + 1, '\0', IGNCHARS, READ_TIMEOUT, 0) > 0) {
			ser_send_pace(upsfd, send_pace, "S%02dR9999%c", shutdown_delay, ENDCHAR);
			upslogx(LOG_NOTICE, "UPS refuses to turn the load off indefinitely. Will turn off for 9999 minutes instead.");
		}

		upslogx(LOG_INFO, "Shutdown (stayoff) initiated.");

		return STAT_INSTCMD_HANDLED;
	}

	if (strcasecmp(cmdname, "shutdown.stop") == 0) {
		ser_send_pace(upsfd, send_pace, "C%c", ENDCHAR);
		watchdog_enabled = 0;

		upslogx(LOG_INFO, "Shutdown canceled.");

		return STAT_INSTCMD_HANDLED;
	}

	if (strcasecmp(cmdname, "load.on") == 0) {
		ser_send_pace(upsfd, send_pace, "C%c", ENDCHAR);
		watchdog_enabled = 0;

		upslogx(LOG_INFO, "Turning the load on.");

		return STAT_INSTCMD_HANDLED;
	}

	if (strcasecmp(cmdname, "load.off") == 0) {
		ser_send_pace(upsfd, send_pace, "C%c", ENDCHAR);
		watchdog_enabled = 0;

		ser_flush_io(upsfd);
		ser_send_pace(upsfd, send_pace, "S00%c", ENDCHAR);
		usleep(READ_PACE);

		if (ser_get_line(upsfd, buffer, 3 + 1, '\0', IGNCHARS, READ_TIMEOUT, 0) > 0) {
			ser_send_pace(upsfd, send_pace, "S00R9999%c", ENDCHAR);
			upslogx(LOG_NOTICE, "UPS refuses to turn the load off indefinitely. Will turn off for 9999 minutes instead.");
		}

		upslogx(LOG_INFO, "Turning the load off.");

		return STAT_INSTCMD_HANDLED;
	}

	if (strcasecmp(cmdname, "reset.input.minmax") == 0) {
		ivolt_min = INT_MAX;
		ivolt_max = -1;

		dstate_setinfo("input.voltage.minimum", "%.1f", ivolt_min);
		dstate_setinfo("input.voltage.maximum", "%.1f", ivolt_max);

		upslogx(LOG_INFO, "Resetting minimum and maximum input voltage values.");

		return STAT_INSTCMD_HANDLED;
	}

	if (strcasecmp(cmdname, "reset.watchdog") == 0) {
		ser_send_pace(upsfd, send_pace, "C%c", ENDCHAR);
		ser_send_pace(upsfd, send_pace, "S%02dR0001%c", watchdog_timeout, ENDCHAR);

		if (watchdog_enabled) {
			upsdebugx(2, "Resetting the UPS watchdog.");
		} else {
			watchdog_enabled = 1;
			upslogx(LOG_INFO, "UPS watchdog started.");
		}

		return STAT_INSTCMD_HANDLED;
	}

	if (strcasecmp(cmdname, "beeper.toggle") == 0) {
		ser_flush_io(upsfd);
		ser_send_pace(upsfd, send_pace, "Q%c", ENDCHAR);
		usleep(READ_PACE);

		if (ser_get_line(upsfd, buffer, 1 + 1, '\0', IGNCHARS, READ_TIMEOUT, 0) > 0) {
			upslogx(LOG_NOTICE, "beeper.toggle not supported by UPS hardware.");
		} else {
			upslogx(LOG_INFO, "Toggling UPS beeper.");
		}

		return STAT_INSTCMD_HANDLED;
	}

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

	return STAT_INSTCMD_UNKNOWN;
}


int setvar(const char *varname, const char *val)
{
	return STAT_SET_UNKNOWN;
}


void upsdrv_help(void)
{
}


void upsdrv_makevartable(void)
{
	addvar(VAR_VALUE, "mfr", "Manufacturer name");
	addvar(VAR_VALUE, "model", "Model name");
	addvar(VAR_VALUE, "serial", "UPS serial number");
	addvar(VAR_VALUE, "lowbatt", "Low battery level (%)");
	addvar(VAR_VALUE, "ondelay", "Min. delay before UPS startup (minutes)");
	addvar(VAR_VALUE, "offdelay", "Delay before UPS shutdown (minutes)");
	addvar(VAR_VALUE, "battvolts", "Battery voltages (empty:full)");
	addvar(VAR_VALUE, "battvoltmult", "Battery voltage multiplier");
	addvar(VAR_FLAG , "ignoreoff", "Ignore the OFF status from the UPS");
	addvar(VAR_VALUE, "sendpace", "Interval between command chars (msec)");
	addvar(VAR_VALUE, "dtr", "Serial DTR line state (0/1)");
	addvar(VAR_VALUE, "rts", "Serial RTS line state (0/1)");

	megatec_subdrv_makevartable();
}


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

	if (getval("dtr")) {
		upsdebugx(2, "Parameter [dtr]: [%s]", getval("dtr"));

		if (strcmp(getval("dtr"), "0") != 0 && strcmp(getval("dtr"), "1") != 0) {
			fatalx(EXIT_FAILURE, "Error in \"dtr\" parameter.");
		}

		state_dtr = atoi(getval("dtr"));
	}

	if (getval("rts")) {
		upsdebugx(2, "Parameter [rts]: [%s]", getval("rts"));

		if (strcmp(getval("rts"), "0") != 0 && strcmp(getval("rts"), "1") != 0) {
			fatalx(EXIT_FAILURE, "Error in \"rts\" parameter.");
		}

		state_rts = atoi(getval("rts"));
	}

	upsdebugx(2, "DTR=%d, RTS=%d", state_dtr, state_rts);

	ser_set_dtr(upsfd, state_dtr);
	ser_set_rts(upsfd, state_rts);
}


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


/* EOF - megatec.c */
_______________________________________________
Nut-upsdev mailing list
[email protected]
http://lists.alioth.debian.org/mailman/listinfo/nut-upsdev

Reply via email to