nut/drivers/nutdrv_qx_masterguard.c
2022-06-29 12:37:36 +02:00

1090 lines
40 KiB
C

/* nutdrv_qx_masterguard.c - Subdriver for Masterguard A/E Series
*
* Copyright (C)
* 2020-2021 Edgar Fuß <ef@math.uni-bonn.de>, Mathematisches Institut der Universität Bonn
*
* 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, or a 2-clause BSD License.
*
* 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 "nutdrv_qx.h"
#include "nutdrv_qx_masterguard.h"
#include <stddef.h>
#define MASTERGUARD_VERSION "Masterguard 0.02"
/* series (for un-SKIP) */
static char masterguard_my_series = '?';
/* slave address for commands that require it */
static char masterguard_my_slaveaddr[3] = "??"; /* null-terminated for strtol() in claim() */
/* next slaveaddr to use after the SS command (which needs the old one) has been run */
static long masterguard_next_slaveaddr;
/* output current/power computation */
static long masterguard_my_power = 0;
/* battery voltage computation */
static long masterguard_my_numcells = 0;
/* ranges */
static info_rw_t masterguard_r_slaveaddr[] = {
{ "0", NULL },
{ "99", NULL },
{ "" , NULL }
};
static info_rw_t masterguard_r_batpacks[] = {
{ "0", NULL }, /* actually 1 for most models, see masterguard_model() */
{ "9", NULL }, /* varies across models, see masterguard_model() */
{ "" , NULL }
};
static info_rw_t masterguard_r_offdelay[] = {
{ "0", NULL },
{ "5940", NULL }, /* 99*60 */
{ "" , NULL }
};
static info_rw_t masterguard_r_ondelay[] = {
{ "0", NULL },
{ "599940", NULL }, /* 9999*60 */
{ "" , NULL }
};
/* enums */
static info_rw_t *masterguard_e_outvolts = NULL; /* set in masterguard_output_voltages() */
/* preprocess functions */
/* set masterguard_my_slaveaddr (for masterguard_add_slaveaddr) */
static int masterguard_slaveaddr(item_t *item, char *value, const size_t valuelen) {
if (strlen(item->value) != 2) {
upsdebugx(2, "slaveaddr length not 2");
return -1;
}
memcpy(masterguard_my_slaveaddr, item->value, 2);
if (valuelen >= 3) memcpy(value, item->value, 3);
return 0;
}
/* set masterguard_my_series (for activating supported commands in masterguard_claim() */
static int masterguard_series(item_t *item, char *value, const size_t valuelen) {
NUT_UNUSED_VARIABLE(valuelen);
switch (item->value[0]) {
case 'A':
break;
case 'E':
break;
default:
upsdebugx(2, "unknown series %s", item->value);
return -1;
}
masterguard_my_series = item->value[0];
memcpy(value, item->value, 2);
return 0;
}
/* Convert strangely formatted model name in WH output
* (spaces, -19 only after battery packs) to something readable
* Also set min/max battery packs according to model */
static int masterguard_model(item_t *item, char *value, const size_t valuelen) {
char *model;
int rack;
char min_bp, max_bp;
rack = (strstr(item->value, "-19") != NULL);
if (strncmp(item->value, "A 700", 6) == 0) {
model = "A700";
min_bp = 0; max_bp = 0;
} else if (strncmp(item->value, "A 1000", 6) == 0) {
model = "A1000";
min_bp = 0; max_bp = 2;
} else if (strncmp(item->value, "A 2000", 6) == 0) {
model = "A2000";
min_bp = rack ? 1 : 0; max_bp = rack ? 5 : 2;
} else if (strncmp(item->value, "A 3000", 6) == 0) {
model = "A3000";
min_bp = rack ? 1 : 0; max_bp = rack ? 5 : 2;
} else if (strncmp(item->value, "E 60", 4) == 0) {
model = "E60";
min_bp = 1; max_bp = 1; /* ??? */
} else if (strncmp(item->value, "E100", 4) == 0) {
model = "E100";
min_bp = 1; max_bp = 1; /* ??? */
} else if (strncmp(item->value, "E200", 4) == 0) {
model = "E200";
min_bp = 1; max_bp = 1; /* ??? */
} else {
upsdebugx(2, "unknown T %s", item->value);
return -1;
}
masterguard_r_batpacks[0].value[0] = '0' + min_bp;
masterguard_r_batpacks[1].value[0] = '0' + max_bp;
snprintf(value, valuelen, "%s%s", model, rack ? "-19" : "");
return 0;
}
/* set masterguard_my_power (for power/current calculations) according to model */
static int masterguard_power(item_t *item, char *value, const size_t valuelen) {
int p;
if (strncmp(item->value, "A 700", 6) == 0) {
p = 700;
} else if (strncmp(item->value, "A 1000", 6) == 0) {
p = 1000;
} else if (strncmp(item->value, "A 2000", 6) == 0) {
p = 2000;
} else if (strncmp(item->value, "A 3000", 6) == 0) {
p = 3000;
} else if (strncmp(item->value, "E 60", 4) == 0) {
p = 6000;
} else if (strncmp(item->value, "E100", 4) == 0) {
p = 10000;
} else if (strncmp(item->value, "E200", 4) == 0) {
p = 20000;
} else {
upsdebugx(2, "unknown T %s", item->value);
return -1;
}
masterguard_my_power = p;
snprintf(value, valuelen, "%d", p);
return 0;
}
/* convert mmm.ss to seconds */
static int masterguard_mmm_ss(item_t *item, char *value, const size_t valuelen) {
int m, s;
if (sscanf(item->value, "%d.%d", &m, &s) != 2) {
upsdebugx(2, "unparsable mmm.ss %s", item->value);
return -1;
}
snprintf(value, valuelen, "%d", 60*m + s);
return 0;
}
/* convert hhh to seconds */
static int masterguard_hhh(item_t *item, char *value, const size_t valuelen) {
int h;
if (sscanf(item->value, "%d", &h) != 1) {
upsdebugx(2, "unparsable hhh %s", item->value);
return -1;
}
snprintf(value, valuelen, "%d", 60*60*h);
return 0;
}
/* convert TTTT:hh:mm:dd to seconds */
static int masterguard_tttt_hh_mm_ss(item_t *item, char *value, const size_t valuelen) {
int t, h, m, s;
if (sscanf(item->value, "%d:%d:%d:%d", &t, &h, &m, &s) != 4) {
upsdebugx(2, "unparsable TTTT:hh:mm:ss %s", item->value);
return -1;
}
snprintf(value, valuelen, "%d", 86400*t + 3600*h + 60*m + s);
return 0;
}
/* set masterguard_my_numcells (for nominal battery voltage computation) */
static int masterguard_numcells(item_t *item, char *value, const size_t valuelen) {
int v;
if (sscanf(item->value, "%d", &v) != 1) {
upsdebugx(2, "unparsable vvv %s", item->value);
return -1;
}
masterguard_my_numcells = v;
snprintf(value, valuelen, "%d", v);
return 0;
}
/* compute nominal battery voltage */
static int masterguard_battvolt(item_t *item, char *value, const size_t valuelen) {
float s;
if (sscanf(item->value, "%f", &s) != 1) {
upsdebugx(2, "unparsable ss.ss %s", item->value);
return -1;
}
snprintf(value, valuelen, "%.2f", masterguard_my_numcells * s);
return 0;
}
/* compute output power from load percentage */
static int masterguard_ups_power(item_t *item, char *value, const size_t valuelen) {
int q;
if (sscanf(item->value, "%d", &q) != 1) {
upsdebugx(2, "unparsable qqq %s", item->value);
return -1;
}
snprintf(value, valuelen, "%.0f", q / 100.0 * masterguard_my_power + 0.5);
return 0;
}
/* helper routine, not to be called from table */
static int masterguard_output_current_fraction(item_t *item, char *value, const size_t valuelen, double fraction) {
NUT_UNUSED_VARIABLE(item);
snprintf(value, valuelen, "%.2f",
fraction * masterguard_my_power / strtod(dstate_getinfo("output.voltage") , NULL) + 0.005);
return 0;
}
/* compute output current from load percentage and output voltage */
static int masterguard_output_current(item_t *item, char *value, const size_t valuelen) {
int q;
if (sscanf(item->value, "%d", &q) != 1) {
upsdebugx(2, "unparsable qqq %s", item->value);
return -1;
}
return masterguard_output_current_fraction(item, value, valuelen, q/100.0);
}
/* compute nominal output current from output voltage */
static int masterguard_output_current_nominal(item_t *item, char *value, const size_t valuelen) {
return masterguard_output_current_fraction(item, value, valuelen, 1.0);
}
/* digest status bits */
static int masterguard_status(item_t *item, char *value, const size_t valuelen) {
int neg;
char *s;
switch (item->value[0]) {
case '0': neg = 1; break;
case '1': neg = 0; break;
default:
upsdebugx(2, "unknown flag value %c", item->value[0]);
return -1;
}
switch (item->from) {
case 53: /* B7 */ s = "OL"; neg = !neg; break;
case 54: /* B6 */ s = "LB"; break;
case 55: /* B5 */ s = "BYPASS"; break;
case 56: /* B4 */ s = neg ? "" : "UPS Failed"; neg = 0; break;
case 57: /* B3 */ s = neg ? "online" : "offline"; neg = 0; break;
case 58: /* B2 */ s = "CAL"; break;
case 59: /* B1 */ s = "FSD"; break;
/* 60: blank */
/* 61: B0 reserved */
/* 62: T7 reserved */
case 63: /* T6 */ s = neg ? "" : "problems in parallel operation mode"; neg = 0; break;
/* 64: T5 part of a parallel set */
case 65: /* T4 */ s = "RB"; break;
case 66: /* T3 */ s = neg ? "" : "no battery connected"; neg = 0; break;
case 67: /* T210 */
neg = 0;
if (strncmp(item->value, "000", 3) == 0) {
s = "no test in progress";
} else if (strncmp(item->value, "001", 3) == 0) {
s = "in progress";
} else if (strncmp(item->value, "010", 3) == 0) {
s = "OK";
} else if (strncmp(item->value, "011", 3) == 0) {
s = "failed";
} else if (strncmp(item->value, "100", 3) == 0) {
s = "not possible";
} else if (strncmp(item->value, "101", 3) == 0) {
s = "aborted";
} else if (strncmp(item->value, "110", 3) == 0) {
s = "autonomy time calibration in progress";
} else if (strncmp(item->value, "111", 3) == 0) {
s = "unknown";
} else {
upsdebugx(2, "unknown test result %s", item->value);
return -1;
}
break;
default:
upsdebugx(2, "unknown flag position %d", item->from);
return -1;
}
snprintf(value, valuelen, "%s%s", neg ? "!" : "", s);
return 0;
}
/* convert beeper status bit to string required by NUT */
static int masterguard_beeper_status(item_t *item, char *value, const size_t valuelen) {
switch (item->value[0]) {
case '0':
if (valuelen >= 9)
strcpy(value, "disabled");
else
*value = '\0';
break;
case '1':
if (valuelen >= 8)
strcpy(value, "enabled");
else
*value = '\0';
break;
default:
upsdebugx(2, "unknown beeper status %c", item->value[0]);
return -1;
}
return 0;
}
/* parse list of available (nominal) output voltages into masterguard_w_outvolts enum */
static int masterguard_output_voltages(item_t *item, char *value, const size_t valuelen) {
char sep[] = " ";
char *w;
size_t n = 0;
strncpy(value, item->value, valuelen); /* save before strtok mangles it */
for (w = strtok(item->value, sep); w; w = strtok(NULL, sep)) {
n++;
upsdebugx(4, "output voltage #%zu: %s", n, w);
if ((masterguard_e_outvolts = realloc(masterguard_e_outvolts, n * sizeof(info_rw_t))) == NULL) {
upsdebugx(1, "output voltages: allocating #%zu failed", n);
return -1;
}
strncpy(masterguard_e_outvolts[n - 1].value, w, SMALLBUF - 1);
masterguard_e_outvolts[n - 1].preprocess = NULL;
}
/* need to do this seperately in case the loop is run zero times */
if ((masterguard_e_outvolts = realloc(masterguard_e_outvolts, (n + 1) * sizeof(info_rw_t))) == NULL) {
upsdebugx(1, "output voltages: allocating terminator after #%zu failed", n);
return -1;
}
masterguard_e_outvolts[n].value[0] = '\0';
masterguard_e_outvolts[n].preprocess = NULL;
return 0;
}
/* parse fault record string into readable form */
static int masterguard_fault(item_t *item, char *value, const size_t valuelen) {
char c;
float f;
int t, h, m, s;
long l;
if (sscanf(item->value, "%c %f %d:%d:%d:%d", &c, &f, &t, &h, &m, &s) != 6) {
upsdebugx(1, "unparsable fault record %s", item->value);
return -1;
}
l = 86400*t + 3600*h + 60*m + s;
snprintf(value, valuelen, "%ld: ", l);
switch (c) {
case '0':
snprintfcat(value, valuelen, "none");
break;
case '1':
snprintfcat(value, valuelen, "bus fault (%.0fV)", f);
break;
case '2':
snprintfcat(value, valuelen, "inverter fault (%.0fV)", f);
break;
case '3':
snprintfcat(value, valuelen, "overheat fault (%.0fC)", f);
break;
case '4':
snprintfcat(value, valuelen, "battery overvoltage fault (%.2fV)", f);
break;
case '5':
snprintfcat(value, valuelen, "battery mode overload fault (%.0f%%)", f);
break;
case '6':
snprintfcat(value, valuelen, "bypass mode overload fault (%.0f%%)", f);
break;
case '7':
snprintfcat(value, valuelen, "inverter mode outpt short-circuit fault (%.0fV)", f);
break;
case '8':
snprintfcat(value, valuelen, "fan lock fault");
break;
case '9':
snprintfcat(value, valuelen, "battery fault (%.0fV)", f);
break;
case 'A':
snprintfcat(value, valuelen, "charger fault");
break;
case 'B':
snprintfcat(value, valuelen, "EPO activated");
break;
case 'C':
snprintfcat(value, valuelen, "parallel error");
break;
case 'D':
snprintfcat(value, valuelen, "MCU communication error");
break;
case 'E':
case 'F':
upsdebugx(1, "reserved fault id %c", c);
return -1;
default:
upsdebugx(1, "unknown fault id %c", c);
return -1;
}
return 0;
}
/* pre-command preprocessing functions */
/* add slave address (from masterguard_my_slaveaddr) to commands that require it */
static int masterguard_add_slaveaddr(item_t *item, char *command, const size_t commandlen) {
NUT_UNUSED_VARIABLE(item);
NUT_UNUSED_VARIABLE(commandlen);
size_t l;
l = strlen(command);
if (strncmp(command + l - 4, ",XX\r", 4) != 0) {
upsdebugx(1, "add slaveaddr: no ,XX\\r at end of command %s", command);
return -1;
}
upsdebugx(4, "add slaveaddr %s to command %s", masterguard_my_slaveaddr, command);
memcpy(command + l - 3, masterguard_my_slaveaddr, 2);
return 0;
}
/* instant command preprocessing functions */
/* helper, not to be called directly from table */
/*!! use parameter from the value field instead of ups.delay.{shutdown,return}?? */
static int masterguard_shutdown(item_t *item, char *value, const size_t valuelen, const int stayoff) {
NUT_UNUSED_VARIABLE(item);
long offdelay;
char *p;
const char *val, *name;
char offstr[3];
offdelay = strtol((val = dstate_getinfo(name = "ups.delay.shutdown")), &p, 10);
if (*p != '\0') goto ill;
if (offdelay < 0) {
goto ill;
} else if (offdelay < 60) {
offstr[0] = '.';
offstr[1] = '0' + (char)offdelay / 6;
} else if (offdelay <= 99*60) {
int m = (int)(offdelay / 60);
offstr[0] = '0' + (char)(m / 10);
offstr[1] = '0' + (char)(m % 10);
} else goto ill;
offstr[2] = '\0';
if (stayoff) {
snprintf(value, valuelen, "S%s\r", offstr);
} else {
long ondelay;
ondelay = strtol((val = dstate_getinfo(name = "ups.delay.start")), &p, 10);
if (*p != '\0') goto ill;
if (ondelay < 0 || ondelay > 9999*60) goto ill;
snprintf(value, valuelen, "S%sR%04ld\r", offstr, ondelay);
}
return 0;
ill:
upsdebugx(2, "shutdown: illegal %s %s", name, val);
return -1;
}
static int masterguard_shutdown_return(item_t *item, char *value, const size_t valuelen) {
return masterguard_shutdown(item, value, valuelen, 0);
}
static int masterguard_shutdown_stayoff(item_t *item, char *value, const size_t valuelen) {
return masterguard_shutdown(item, value, valuelen, 1);
}
static int masterguard_test_battery(item_t *item, char *value, const size_t valuelen) {
NUT_UNUSED_VARIABLE(item);
long duration;
char *p;
if (value[0] == '\0') {
upsdebugx(2, "battery test: no duration");
return -1;
}
duration = strtol(value, &p, 10);
if (*p != '\0') goto ill;
if (duration == 10) {
strncpy(value, "T\r", valuelen);
return 0;
}
if (duration < 60 || duration > 99*60) goto ill;
snprintf(value, valuelen, "T%02ld\r", duration / 60);
return 0;
ill: upsdebugx(2, "battery test: illegal duration %s", value);
return -1;
}
/* variable setting preprocessing functions */
/* set variable, input format specifier (d/f/s, thms) in item->dfl */
static int masterguard_setvar(item_t *item, char *value, const size_t valuelen) {
char *p;
char t = 's';
long i = 0;
double f = 0.0;
char s[80];
if (value[0] == '\0') {
upsdebugx(2, "setvar: no value");
return -1;
}
if (!item->dfl || item->dfl[0] == '\0') {
upsdebugx(2, "setvar: no dfl");
return -1;
}
if (item->dfl[1] == '\0') {
t = item->dfl[0];
switch (t) {
case 'd':
i = strtol(value, &p, 10);
if (*p != '\0') goto ill;
break;
case 'f':
f = strtod(value, &p);
if (*p != '\0') {
goto ill;
} else if (errno) {
upsdebug_with_errno(2, "setvar: f value %s", value);
return -1;
}
break;
case 's':
/* copy to s to avoid snprintf()ing value to itself */
if (strlen(value) >= sizeof s) goto ill;
strcpy(s, value);
break;
default:
upsdebugx(2, "setvar: unknown dfl %c", item->dfl[0]);
return -1;
}
} else if (strncmp(item->dfl, "thms", 4) == 0) {
int tt, h, m, sec;
if (sscanf(item->value, "%d:%d:%d:%d", &tt, &h, &m, &sec) == 4) {
if (tt < 0 || tt > 9999 || h < 0 || h > 23 || m < 0 || m > 59 || sec < 0 || sec > 59) goto ill;
} else {
long l;
char *pl;
l = strtol(value, &pl, 10);
if (*pl != '\0') goto ill;
sec = l % 60; l /= 60;
m = l % 60; l /= 60;
h = l % 24; l /= 24;
if (l > 9999) goto ill;
tt = (int)l;
}
snprintf(s, sizeof s, "%04d:%02d:%02d:%02d", tt, h, m, sec);
} else {
upsdebugx(2, "setvar: unknown dfl %s", item->dfl);
return -1;
}
#ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL
#pragma GCC diagnostic push
#endif
#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
#endif
#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_SECURITY
#pragma GCC diagnostic ignored "-Wformat-security"
#endif
switch (t) {
case 'd':
snprintf(value, valuelen, item->command, i);
break;
case 'f':
snprintf(value, valuelen, item->command, f);
break;
case 's':
snprintf(value, valuelen, item->command, s);
break;
}
#ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL
#pragma GCC diagnostic pop
#endif
return 0;
ill:
upsdebugx(2, "setvar: illegal %s value %s", item->dfl, value);
return -1;
}
/* record new slave address in masterguard_next_slaveaddr; moved to masterguard_my_slaveaddr in masterguard_new_slaveaddr() after the slaveaddr-changing command finished */
static int masterguard_set_slaveaddr(item_t *item, char *value, const size_t valuelen) {
char *p;
masterguard_next_slaveaddr = strtol(value, &p, 10);
if (*p != '\0') {
upsdebugx(2, "set_slaveaddr: illegal value %s", value);
return -1;
}
upsdebugx(3, "next slaveaddr %ld", masterguard_next_slaveaddr);
return masterguard_setvar(item, value, valuelen);
}
/* variable setting answer preprocessing functions */
/* set my_slaveaddr to next_slaveaddr /after/ issuing the SS command (which, itself, needs the /old/ slaveaddr) */
static int masterguard_new_slaveaddr(item_t *item, const int len) {
NUT_UNUSED_VARIABLE(item);
upsdebugx(3, "saved slaveaddr %ld", masterguard_next_slaveaddr);
if (masterguard_next_slaveaddr < 0 || masterguard_next_slaveaddr > 99) {
upsdebugx(2, "%s: illegal value %ld", __func__, masterguard_next_slaveaddr);
return -1;
}
masterguard_my_slaveaddr[0] = '0' + (char)(masterguard_next_slaveaddr / 10);
masterguard_my_slaveaddr[1] = '0' + (char)(masterguard_next_slaveaddr % 10);
upsdebugx(3, "new slaveaddr %s", masterguard_my_slaveaddr);
return len;
}
/* qx2nut lookup table */
static item_t masterguard_qx2nut[] = {
/* static values */
/* type flags rw command answer len leading value from to dfl qxflags precmd preans preproc */
{ "device.mfr", 0, NULL, "", "", 0, '\0', "", 0, 0, "Masterguard", QX_FLAG_STATIC | QX_FLAG_ABSENT,NULL, NULL, NULL },
{ "load.high", 0, NULL, "", "", 0, '\0', "", 0, 0, "140", QX_FLAG_STATIC | QX_FLAG_ABSENT,NULL, NULL, NULL },
/* battery.charge.low */
/* battery.charge.warning */
{ "battery.type", 0, NULL, "", "", 0, '\0', "", 0, 0, "PbAc", QX_FLAG_STATIC | QX_FLAG_ABSENT,NULL, NULL, NULL },
/* variables */
/*
* > [WH\r]
* < [(XX VV.VV PP.PP TTTTTTTTTTTTTTTTTTTTTTTTTTTTTT B MMM FF.FF VVV SS.SS HHH.hh GGG.gg RRR mm nn MMM NNN FF.FF FF.FF\r]
* 01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012
* 0 1 2 3 4 5 6 7 8 9 0 1
* (00 10.06 03.09 A 700 + 0 Bat Pack-19 0 230 50.00 012 02.30 006.00 012.00 018 10 40 160 276 47.00 53.00
*/
/* type flags rw command answer len leading value from to dfl qxflags precmd preans preproc */
{ "ups.id", ST_FLAG_RW, masterguard_r_slaveaddr,"WH\r", "", 113, '(', "", 1, 2, "%.0f", QX_FLAG_SEMI_STATIC | QX_FLAG_RANGE, NULL, NULL, masterguard_slaveaddr },
{ "ups.firmware", 0, NULL, "WH\r", "", 113, '(', "", 4, 8, "%s", QX_FLAG_STATIC, NULL, NULL, NULL },
{ "ups.firmware.aux", 0, NULL, "WH\r", "", 113, '(', "", 10, 14, "%s", QX_FLAG_STATIC, NULL, NULL, NULL },
/* several values are deduced from the T field */
{ "experimental.series", 0, NULL, "WH\r", "", 113, '(', "", 16, 16, "%s", QX_FLAG_STATIC | QX_FLAG_NONUT, NULL, NULL, masterguard_series },
{ "device.model", 0, NULL, "WH\r", "", 113, '(', "", 16, 45, "%s", QX_FLAG_STATIC, NULL, NULL, masterguard_model },
{ "ups.power.nominal", 0, NULL, "WH\r", "", 113, '(', "", 16, 45, "%s", QX_FLAG_STATIC, NULL, NULL, masterguard_power },
/* not used, use GS instead because the value is settable
{ "battery.packs", 0, NULL, "WH\r", "", 113, '(', "", 47, 47, "%.0f", QX_FLAG_STATIC, NULL, NULL, NULL },
*/
{ "input.voltage.nominal", 0, NULL, "WH\r", "", 113, '(', "", 49, 51, "%.0f", QX_FLAG_STATIC, NULL, NULL, NULL },
{ "input.frequency.nominal", 0, NULL, "WH\r", "", 113, '(', "", 53, 57, "%.2f", QX_FLAG_STATIC, NULL, NULL, NULL },
{ "experimental.number_of_battery_cells", 0, NULL, "WH\r", "", 113, '(', "", 59, 61, "%.0f", QX_FLAG_STATIC | QX_FLAG_NONUT, NULL, NULL, masterguard_numcells },
{ "experimental.nominal_cell_voltage", 0, NULL, "WH\r", "", 113, '(', "", 63, 67, "%.2f", QX_FLAG_STATIC | QX_FLAG_NONUT, NULL, NULL, NULL },
{ "battery.voltage.nominal", 0, NULL, "WH\r", "", 113, '(', "", 63, 67, "%.2f", QX_FLAG_STATIC, NULL, NULL, masterguard_battvolt},
{ "experimental.runtime_half", 0, NULL, "WH\r", "", 113, '(', "", 69, 74, "%.0f", QX_FLAG_STATIC | QX_FLAG_NONUT, NULL, NULL, masterguard_mmm_ss },
{ "experimental.runtime_full", 0, NULL, "WH\r", "", 113, '(', "", 76, 81, "%.0f", QX_FLAG_STATIC | QX_FLAG_NONUT, NULL, NULL, masterguard_mmm_ss },
{ "experimental.recharge_time", 0, NULL, "WH\r", "", 113, '(', "", 83, 85, "%.0f", QX_FLAG_STATIC | QX_FLAG_NONUT, NULL, NULL, masterguard_hhh },
/*!! what's the difference between low/high and low.critical/high.critical?? */
{ "ambient.0.temperature.low", 0, NULL, "WH\r", "", 113, '(', "", 87, 88, "%.0f", QX_FLAG_STATIC, NULL, NULL, NULL },
{ "ambient.0.temperature.high", 0, NULL, "WH\r", "", 113, '(', "", 90, 91, "%.0f", QX_FLAG_STATIC, NULL, NULL, NULL },
{ "input.voltage.low.critical", 0, NULL, "WH\r", "", 113, '(', "", 93, 95, "%.0f", QX_FLAG_STATIC, NULL, NULL, NULL },
{ "input.voltage.high.critical",0, NULL, "WH\r", "", 113, '(', "", 97, 99, "%.0f", QX_FLAG_STATIC, NULL, NULL, NULL },
{ "input.frequency.low", 0, NULL, "WH\r", "", 113, '(', "", 101, 105, "%.2f", QX_FLAG_STATIC, NULL, NULL, NULL },
{ "input.frequency.high", 0, NULL, "WH\r", "", 113, '(', "", 107, 111, "%.2f", QX_FLAG_STATIC, NULL, NULL, NULL },
/*
* > [Q3\r]
* 76543210 76543210
* < [(XX MMM.M NNN.N PPP.P QQQ RR.R SS.SS TT.T ttt.tt CCC BBBBBBBB TTTTTTTT\r]
* 01234567890123456789012345678901234567890123456789012345678901234567890
* 0 1 2 3 4 5 6 7
* (00 225.9 225.9 229.3 043 50.0 02.27 23.4 017.03 100 00000000 00000000
* (01 226.9 226.9 226.9 039 50.0 02.30 21.8 000.00 000 01100000 00011000
*/
/* type flags rw command answer len leading value from to dfl qxflags precmd preans preproc */
{ "input.voltage", 0, NULL, "Q3\r", "", 71, '(', "", 4, 8, "%.1f", 0, NULL, NULL, NULL },
{ "experimental.input_fault_voltage", 0, NULL, "Q3\r", "", 71, '(', "", 10, 14, "%.1f", QX_FLAG_NONUT, NULL, NULL, NULL },
{ "output.voltage", 0, NULL, "Q3\r", "", 71, '(', "", 16, 20, "%.1f", 0, NULL, NULL, NULL },
{ "ups.load", 0, NULL, "Q3\r", "", 71, '(', "", 22, 24, "%.0f", 0, NULL, NULL, NULL },
{ "ups.power", 0, NULL, "Q3\r", "", 71, '(', "", 22, 24, "%.0f", 0, NULL, NULL, masterguard_ups_power },
{ "output.current", 0, NULL, "Q3\r", "", 71, '(', "", 22, 24, "%f", 0, NULL, NULL, masterguard_output_current },
{ "input.frequency", 0, NULL, "Q3\r", "", 71, '(', "", 26, 29, "%.1f", 0, NULL, NULL, NULL },
{ "battery.voltage", 0, NULL, "Q3\r", "", 71, '(', "", 31, 35, "%.1f", 0, NULL, NULL, masterguard_battvolt },
{ "ups.temperature", 0, NULL, "Q3\r", "", 71, '(', "", 37, 40, "%.1f", 0, NULL, NULL, NULL },
/*!! report both ups.temperature and ambient.0.temperature?? */
{ "ambient.0.temperature", 0, NULL, "Q3\r", "", 71, '(', "", 37, 40, "%.1f", 0, NULL, NULL, NULL },
{ "battery.runtime", 0, NULL, "Q3\r", "", 71, '(', "", 42, 47, "%.0f", 0, NULL, NULL, masterguard_mmm_ss },
{ "battery.charge", 0, NULL, "Q3\r", "", 71, '(', "", 49, 51, "%.0f", 0, NULL, NULL, NULL },
/* Status bits, first half (B) */
{ "ups.status", 0, NULL, "Q3\r", "", 71, '(', "", 53, 53, NULL, QX_FLAG_QUICK_POLL, NULL, NULL, masterguard_status }, /* B7: Utility Fail */
{ "ups.status", 0, NULL, "Q3\r", "", 71, '(', "", 54, 54, NULL, QX_FLAG_QUICK_POLL, NULL, NULL, masterguard_status }, /* B6: Battery Low */
{ "ups.status", 0, NULL, "Q3\r", "", 71, '(', "", 55, 55, NULL, QX_FLAG_QUICK_POLL, NULL, NULL, masterguard_status }, /* B5: Bypass/Boost Active */
{ "ups.alarm", 0, NULL, "Q3\r", "", 71, '(', "", 56, 56, NULL, QX_FLAG_QUICK_POLL, NULL, NULL, masterguard_status }, /* B4: UPS Failed */
{ "ups.type", 0, NULL, "Q3\r", "", 71, '(', "", 57, 57, NULL, QX_FLAG_STATIC, NULL, NULL, masterguard_status }, /* B3: UPS Type */
{ "ups.status", 0, NULL, "Q3\r", "", 71, '(', "", 58, 58, NULL, QX_FLAG_QUICK_POLL, NULL, NULL, masterguard_status }, /* B2: Test in Progress */
{ "ups.status", 0, NULL, "Q3\r", "", 71, '(', "", 59, 59, NULL, QX_FLAG_QUICK_POLL, NULL, NULL, masterguard_status }, /* B1: Shutdown Active */
/* unused */ /* B0: unused */
/* Status bits, second half (T) */
/* unused */ /* T7: unused */
{ "ups.alarm", 0, NULL, "Q3\r", "", 69, '(', "", 63, 63, NULL, QX_FLAG_QUICK_POLL, NULL, NULL, masterguard_status }, /* T6: problems in parallel operation mode */
/* part of a parallel set */ /* T5: is part of a parallel set */
{ "ups.status", 0, NULL, "Q3\r", "", 71, '(', "", 65, 65, NULL, QX_FLAG_QUICK_POLL, NULL, NULL, masterguard_status }, /* T4: Battery: end of service life */
{ "ups.alarm", 0, NULL, "Q3\r", "", 71, '(', "", 66, 66, NULL, QX_FLAG_QUICK_POLL, NULL, NULL, masterguard_status }, /* T3: battery connected */
{ "ups.test.result", 0, NULL, "Q3\r", "", 71, '(', "", 67, 69, NULL, QX_FLAG_QUICK_POLL, NULL, NULL, masterguard_status }, /* T210: Test Status */
/*
* > [GS,XX\r]
* < [(XX,ii p a\r]
* 01234567890
* 0 1
* (00,00 0 1
*/
/* type flags rw command answer len leading value from to dfl qxflags precmd preans preproc */
/* ups.id obtained via WH */
{ "ups.id", 0, NULL, "SS%02d--,XX\r","", 0, '\0', "", 0, 0, "d", QX_FLAG_SETVAR | QX_FLAG_RANGE, masterguard_add_slaveaddr, masterguard_new_slaveaddr, masterguard_set_slaveaddr },
{ "battery.packs", ST_FLAG_RW, masterguard_r_batpacks, "GS,XX\r", "", 0, '(', "", 7, 7, "%.0f", QX_FLAG_SEMI_STATIC | QX_FLAG_RANGE, masterguard_add_slaveaddr, NULL, NULL },
{ "battery.packs", 0, NULL, "SS--%1d-,XX\r","", 0, '\0', "", 0, 0, "d", QX_FLAG_SETVAR | QX_FLAG_RANGE, masterguard_add_slaveaddr, NULL, masterguard_setvar },
/*!! which QX_FLAGs to use?? (changed by instcmd) */
{ "ups.beeper.status", 0, NULL, "GS,XX\r", "", 11, '(', "", 9, 9, NULL, QX_FLAG_SEMI_STATIC, masterguard_add_slaveaddr, NULL, masterguard_beeper_status },
/* set with beeper.{en,dis}able */
/*
* > [GBS,XX\r]
* < [(XX,CCC hhhh HHHH AAAA BBBB DDDD EEE SS.SS\r]
* 0123456789012345678901234567890123456789012
* 0 1 2 3 4
* (00,100 0017 0000 0708 0712 0994 115 02.28
* (01,000 0000 0360 0708 0712 0994 076 02.30
*/
/* type flags rw command answer len leading value from to dfl qxflags precmd preans preproc */
{ "battery.charge", 0, NULL, "GBS,XX\r", "", 43, '(', "", 4, 6, "%.0f", 0, masterguard_add_slaveaddr, NULL, NULL },
/*
* hhhh: hold time (minutes)
* HHHH: recharge time to 90% (minutes)
* AAAA: Ageing factor (promilles)
* BBBB: Ageing factor time dependant (promilles)
* DDDD: Ageing factor cyclic use (promilles)
* EEE: Calibration factor (percent)
* SS.SS: Actual battery cell voltage
*/
/*
* > [GSN,XX\r]
* < [(XX,SSN=SSSSSSSSSSSnnnnn\r]
* 0123456789012345678901234
* 0 1 2
* (00,SSN=6A1212 2782
*/
/* type flags rw command answer len leading value from to dfl qxflags precmd preans preproc */
{ "device.part", 0, NULL, "GSN,XX\r", "", 25, '(', "", 8, 18, "%s", QX_FLAG_STATIC | QX_FLAG_TRIM, masterguard_add_slaveaddr, NULL, NULL },
{ "device.serial", 0, NULL, "GSN,XX\r", "", 25, '(', "", 20, 23, "%s", QX_FLAG_STATIC, masterguard_add_slaveaddr, NULL, NULL },
/*
* > [DRC,XX\r]
* < [(XX,TTTT:hh:mm:ss\r]
* 012345678901234567
* 0 1
* (00,1869:19:06:37
*/
/* type flags rw command answer len leading value from to dfl qxflags precmd preans preproc */
/* this is not really the uptime, but the running time since last maintenance */
{ "device.uptime", ST_FLAG_RW, NULL, "DRC,XX\r", "", 17, '(', "", 4, 16, "%.0f", QX_FLAG_SEMI_STATIC, masterguard_add_slaveaddr, NULL, masterguard_tttt_hh_mm_ss },
{ "device.uptime", 0, NULL, "SRC%s,XX\r", "", 0, '\0', "", 0, 0, "thms", QX_FLAG_SETVAR, masterguard_add_slaveaddr, NULL, masterguard_setvar },
/*
* > [MSO\r]
* < [(220 230 240\r]
* 0123456789012
* 0 1
* (220 230 240
*/
/* type flags rw command answer len leading value from to dfl qxflags precmd preans preproc */
{ "experimental.output_voltages", 0, NULL, "MSO\r", "", 5, '(', "", 1, 0, "%s", QX_FLAG_STATIC | QX_FLAG_NONUT, NULL, NULL, masterguard_output_voltages },
/*
* > [PNV\r]
* < [(PNV=nnn\r]
* 012345678
* 0
* (PNV=230
*/
/* type flags rw command answer len leading value from to dfl qxflags precmd preans preproc */
{ "output.voltage.nominal", ST_FLAG_RW, NULL /* see claim */, "PNV\r", "", 8, '(', "", 5, 7, "%.0f", QX_FLAG_SEMI_STATIC | QX_FLAG_ENUM, NULL, NULL, NULL },
{ "output.voltage.nominal", 0, NULL, "PNV=%03d\r", "", 0, '\0', "", 0, 0, "d", QX_FLAG_SETVAR, NULL, NULL, masterguard_setvar },
{ "output.current.nominal", 0, NULL, "PNV\r", "", 8, '(', "", 5, 7, "%.0f", QX_FLAG_SEMI_STATIC, NULL, NULL, masterguard_output_current_nominal },
/*
* > [FLT,XX\r]
* < [(XX,A aaaa TTTT:hh:mm:ss B bbbb TTTT:hh:mm:ss C cccc TTTT:hh:mm:ss D dddd TTTT:hh:mm:ss E eeee TTTT:hh:mm:ss\r
* 0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678
* 0 1 2 3 4 5 6 7 8 9 0
* (00,7 0043 0000:16:48:06 0 0000 0000:00:00:00 0 0000 0000:00:00:00 0 0000 0000:00:00:00 0 0000 0000:00:00:00
* (01,9 0010 1780:14:57:19 7 0046 0000:21:14:41 0 0000 0000:00:00:00 0 0000 0000:00:00:00 0 0000 0000:00:00:00
*/
/* type flags rw command answer len leading value from to dfl qxflags precmd preans preproc */
{ "experimental.fault_1", 0, NULL, "FLT,XX\r", "", 108, '(', "", 4, 23, "%s", QX_FLAG_NONUT, masterguard_add_slaveaddr, NULL, masterguard_fault },
{ "experimental.fault_2", 0, NULL, "FLT,XX\r", "", 108, '(', "", 25, 44, "%s", QX_FLAG_NONUT, masterguard_add_slaveaddr, NULL, masterguard_fault },
{ "experimental.fault_3", 0, NULL, "FLT,XX\r", "", 108, '(', "", 46, 65, "%s", QX_FLAG_NONUT, masterguard_add_slaveaddr, NULL, masterguard_fault },
{ "experimental.fault_4", 0, NULL, "FLT,XX\r", "", 108, '(', "", 67, 86, "%s", QX_FLAG_NONUT, masterguard_add_slaveaddr, NULL, masterguard_fault },
{ "experimental.fault_5", 0, NULL, "FLT,XX\r", "", 108, '(', "", 88, 107, "%s", QX_FLAG_NONUT, masterguard_add_slaveaddr, NULL, masterguard_fault },
/* instant commands */
/* type flags rw command answer len leading value from to dfl qxflags precmd preans preproc */
/*!! what's the difference between load.off.delay and shutdown.stayoff?? */
#if 0
{ "load.off", 0, NULL, "S.0\r", "", 0, '\0', "", 0, 0, NULL, QX_FLAG_CMD, NULL, NULL, NULL },
{ "load.on", 0, NULL, "C\r", "", 0, '\0', "", 0, 0, NULL, QX_FLAG_CMD, NULL, NULL, NULL },
/* load.off.delay */
/* load.on.delay */
#endif
{ "shutdown.return", 0, NULL, NULL, "", 0, '\0', "", 0, 0, NULL, QX_FLAG_CMD, NULL, NULL, masterguard_shutdown_return },
{ "shutdown.stayoff", 0, NULL, NULL, "", 0, '\0', "", 0, 0, NULL, QX_FLAG_CMD, NULL, NULL, masterguard_shutdown_stayoff },
{ "shutdown.stop", 0, NULL, "C\r", "", 0, '\0', "", 0, 0, NULL, QX_FLAG_CMD, NULL, NULL, NULL },
/* shutdown.reboot */
/* shutdown.reboot.graceful */
/* test.panel.start */
/* test.panel.stop */
/* test.failure.start */
/* test.failure.stop */
{ "test.battery.start", 0, NULL, NULL, "", 0, '\0', "", 0, 0, NULL, QX_FLAG_CMD, NULL, NULL, masterguard_test_battery },
{ "test.battery.start.quick", 0, NULL, "T\r", "", 0, '\0', "", 0, 0, NULL, QX_FLAG_CMD, NULL, NULL, NULL },
{ "test.battery.start.deep", 0, NULL, "TUD\r", "", 0, '\0', "", 0, 0, NULL, QX_FLAG_CMD, NULL, NULL, NULL },
{ "test.battery.stop", 0, NULL, "CT\r", "", 0, '\0', "", 0, 0, NULL, QX_FLAG_CMD, NULL, NULL, NULL },
/* test.system.start */
/* calibrate.start */
/* calibrate.stop */
{ "bypass.start", 0, NULL, "FOFF\r", "", 0, '\0', "", 0, 0, NULL, QX_FLAG_CMD, NULL, NULL, NULL },
{ "bypass.stop", 0, NULL, "FON\r", "", 0, '\0', "", 0, 0, NULL, QX_FLAG_CMD, NULL, NULL, NULL },
/* reset.input.minmax */
/* reset.watchdog */
{ "beeper.enable", 0, NULL, "SS---1,XX\r", "", 0, '\0', "", 0, 0, NULL, QX_FLAG_CMD, masterguard_add_slaveaddr, NULL, NULL },
{ "beeper.disable", 0, NULL, "SS---0,XX\r", "", 0, '\0', "", 0, 0, NULL, QX_FLAG_CMD, masterguard_add_slaveaddr, NULL, NULL },
/* beeper.mute */
/* beeper.toggle */
/* outlet.* */
/* server variables */
/* type flags rw command answer len leading value from to dfl qxflags precmd preans preproc */
{ "ups.delay.shutdown", ST_FLAG_RW, masterguard_r_offdelay, NULL, "", 0, '\0', "", 0, 0, DEFAULT_OFFDELAY, QX_FLAG_ABSENT | QX_FLAG_SETVAR | QX_FLAG_RANGE, NULL, NULL, NULL },
{ "ups.delay.start", ST_FLAG_RW, masterguard_r_ondelay, NULL, "", 0, '\0', "", 0, 0, DEFAULT_ONDELAY, QX_FLAG_ABSENT | QX_FLAG_SETVAR | QX_FLAG_RANGE, NULL, NULL, NULL },
/* end marker */
{ NULL, 0, NULL, NULL, "", 0, 0, "", 0, 0, NULL, 0, NULL, NULL, NULL }
};
/*!! todo
untested:
Sxx (.n/nn)
C after S.0
unused:
>G01,00
(00,00000
additional E series commands:
PSR
BUS*
V
INVDC
use ups.{delay,timer}.{start,reboot,shutdown}?
report ups.contacts?
how to report battery.charger.status?
set battery.packs.bad?
how to report battery aeging/run time?
how to report nominal hold time at half/full load?
*/
/* commands supported by A series */
static char *masterguard_commands_a[] = {
"Q", "Q1", "Q3", "T", "TL", "S", "C", "CT", "WH", "M", "N", "O", "DECO", "DRC", "SRC", "FLT", "FCLR", "G", "SS", "GS", "MSO", "PNV", "FOFF", "FON", "TUD", "GBS", "SSN", "GSN", NULL
};
/* commands supported by E series */
static char *masterguard_commands_e[] = {
"Q", "Q1", "Q3", "PSR", "T", "TL", "S", "C", "CT", "WH", "DRC", "SRC", "FLT", "FCLR", "SS", "GS", "MSO", "PNV", "FOFF", "FON", "TUD", "GBS", "SSN", "GSN", "BUS", "V", "INVDC", "BUSP", "BUSN", NULL
};
/* claim function. fetch some mandatory values,
* disable unsupported commands,
* set enum for supported output voltages */
static int masterguard_claim(void) {
item_t *item;
/* mandatory values */
char *mandatory[] = {
"series", /* SKIP */
"device.model", /* minimal number of battery packs */
"ups.power.nominal", /* load computation */
"ups.id", /* slave address */
"output_voltages", /* output voltages enum */
#if 0
"battery.packs", /* battery voltage computation */
#endif
NULL
};
char **sp;
long config_slaveaddr;
char *sa;
char **commands;
if ((sa = getval("slave_address")) != NULL) {
char *p;
if (*sa == '\0') {
upsdebugx(2, "claim: empty slave_address");
return 0;
}
config_slaveaddr = strtol(sa, &p, 10);
if (*p != '\0' || config_slaveaddr < 0 || config_slaveaddr > 99) {
upsdebugx(2, "claim: illegal slave_address %s", sa);
return 0;
}
} else {
config_slaveaddr = -1;
}
for (sp = mandatory; *sp != NULL; sp++) {
char value[SMALLBUF] = "";
if ((item = find_nut_info(*sp, 0, QX_FLAG_SETVAR)) == NULL) {
upsdebugx(2, "claim: cannot find %s", *sp);
return 0;
}
/* since qx_process_answer() is not exported, there's no way
* to avoid sending the same command to the UPS again */
if (qx_process(item, NULL) < 0) {
upsdebugx(2, "claim: cannot process %s", *sp);
return 0;
}
/* only call the preprocess function; don't call ups_infoval_set()
* because that does a dstate_setinfo() before dstate_setflags()
* is called (via qx_set_var() in qx_ups_walk() with QX_WALKMODE_INIT);
* that leads to r/w vars ending up r/o. */
if (item->preprocess == NULL ) {
upsdebugx(2, "claim: no preprocess function for %s", *sp);
return 0;
}
if (item->preprocess(item, value, sizeof value)) {
upsdebugx(2, "claim: failed to preprocess %s", *sp);
return 0;
}
}
if (config_slaveaddr >= 0 && config_slaveaddr != strtol(masterguard_my_slaveaddr, NULL, 10)) {
upsdebugx(2, "claim: slave address mismatch: want %02ld, have %s", config_slaveaddr, masterguard_my_slaveaddr);
return 0;
}
switch (masterguard_my_series) {
case 'A':
commands = masterguard_commands_a;
break;
case 'E':
commands = masterguard_commands_e;
break;
default:
return 0;
}
/* set SKIP flag for unimplemented commands */
for (item = masterguard_qx2nut; item->info_type != NULL; item++) {
int match = 0;
if (item->command == NULL || item->command[0] == '\0') continue;
for (sp = commands; sp != NULL; sp++) {
const char *p = *sp, *q = item->command;
while (1) {
if (*p == '\0' && (*q < 'A' || *q > 'Z')) {
match = 1; break;
} else if (*p == '\0' || *q < 'A' || *q > 'Z' || *p != *q) {
match = 0; break;
}
p++; q++;
}
if (match) break;
}
if (nut_debug_level >= 3) {
char cmd[10];
char *p = cmd; const char *q = item->command;
while (*q >= 'A' && *q <= 'Z') {
*p++ = *q++;
if (p - cmd >= (ptrdiff_t)sizeof cmd - 1) break;
}
*p++ = '\0';
upsdebugx(3, "command %s %simplemented", cmd, match ? "" : "NOT ");
}
if (!match)
item->qxflags |= QX_FLAG_SKIP;
}
/* set enum for output.voltage.nominal */
if ((item = find_nut_info("output.voltage.nominal", QX_FLAG_ENUM, QX_FLAG_SETVAR)) == NULL) {
upsdebugx(2, "claim: cannot find output.voltage.nominal");
return 0;
}
item->info_rw = masterguard_e_outvolts;
return 1;
}
static void masterguard_makevartable(void) {
addvar(VAR_VALUE, "series", "Series (A/E)");
addvar(VAR_VALUE, "slave_address", "Slave address (UPS id) to match");
addvar(VAR_VALUE, "input_fault_voltage", "Input fault voltage (whatever that means)");
addvar(VAR_VALUE, "number_of_battery_cells", "Number of battery cells in series");
addvar(VAR_VALUE, "nominal_cell_voltage", "Nominal battery cell voltage");
addvar(VAR_VALUE, "runtime_half", "Nominal battery run time at 50% load (seconds)");
addvar(VAR_VALUE, "runtime_full", "Nominal battery run time at 100% load (seconds)");
addvar(VAR_VALUE, "recharge_time", "Nominal battery recharge time to 95% capacity (seconds)");
addvar(VAR_VALUE, "output_voltages", "Possible output voltages (volts)");
addvar(VAR_VALUE, "fault_1", "Fault record 1 (newest)");
addvar(VAR_VALUE, "fault_2", "Fault record 2");
addvar(VAR_VALUE, "fault_3", "Fault record 3");
addvar(VAR_VALUE, "fault_4", "Fault record 4");
addvar(VAR_VALUE, "fault_5", "Fault record 5 (oldest)");
}
#ifdef TESTING
static testing_t masterguard_testing[] = {
{ NULL }
};
#endif /* TESTING */
subdriver_t masterguard_subdriver = {
MASTERGUARD_VERSION,
masterguard_claim,
masterguard_qx2nut,
NULL, /* initups */
NULL, /* intinfo */
masterguard_makevartable,
NULL, /* accepted */
NULL, /* rejected */
#ifdef TESTING
masterguard_testing,
#endif /* TESTING */
};