882 lines
24 KiB
C
882 lines
24 KiB
C
/* tripplitesu.c - model specific routines for
|
|
Tripp Lite SmartOnline (SU*) models
|
|
|
|
Copyright (C) 2003 Allan N. Hessenflow <allanh@kallisti.com>
|
|
|
|
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
|
|
*/
|
|
|
|
/* Notes:
|
|
|
|
The map for commands_available isn't clear. All of the information
|
|
I have on the Tripp Lite SmartOnline protocol comes from the files
|
|
TUN.tlp and TUNRT.tlp from their drivers, and some experimentation.
|
|
One of those files told me what one bit was for in the AVL response,
|
|
out of 18 that the SU1000RT2U sends or 21 that some other model
|
|
sends. Later I found a description of the Belkin protocol, which is
|
|
the same. Unfortunately it gives a definition of the AVL response
|
|
that conflicts with the one bit I found from TUNRT.tlp, and in fact
|
|
the response my SU1000RT2U gives, compared to the commands I have
|
|
found it supports, does not match the Belkin description. So I'm
|
|
treating the whole field as unknown. It would be nice to be able to
|
|
use it to determine what variables to make RW. As a workaround, I'm
|
|
assuming any value I query successfully which also can be set on at
|
|
least one model, can in fact be set on the one I'm talking to.
|
|
|
|
I didn't just add Tripp Lite support to the existing Belkin driver
|
|
because I didn't discover that they were the same protocol until
|
|
after I had put more features in this driver than the Belkin driver
|
|
has. I'm not calling this a unified Belkin/Tripp Lite driver for a
|
|
couple of reasons. One is that I don't have a Belkin to test with
|
|
(and so I explicitly check for Tripp Lite in this drivers
|
|
initialization - it *may* work fine with a Belkin by simply removing
|
|
that test). The other reason is that I'd have to come up with a
|
|
name - I don't know a name for the protocol, and I don't know which
|
|
company (if either) originated the protocol. Picking one of the
|
|
companies arbitrarily to name it after just seems wrong.
|
|
|
|
There are a few things still to add. Primarily there's control of
|
|
individual outlet banks, using (I presume) outlet.n.x variables.
|
|
I'll probably wait for an example of that to show up in some other
|
|
driver before adding that, to try to make sure I do it in the way
|
|
that Russell Kroll envisioned. It also might be nice to give the
|
|
user control over the delays before shutdown and restart, probably
|
|
with additional driver parameters. Letting the user turn the buzzer
|
|
off might be nice too; for that I'd have to investigate whether the
|
|
command to do that disables it entirely, or just turns it off during
|
|
the existing alarm condition, to determine whether it would be better
|
|
implemented through variables or instant commands, and then of course
|
|
request that those get added to the list of known names. Finally,
|
|
there are a number of other alarm conditions that can be reported
|
|
that would be nice to pass on to the user; these would require new
|
|
variables or new status values.
|
|
|
|
|
|
The following parameters (ups.conf) are supported:
|
|
lowbatt
|
|
|
|
The following variables are supported (RW = read/write):
|
|
ambient.humidity (1)
|
|
ambient.temperature (1)
|
|
battery.charge
|
|
battery.current (1)
|
|
battery.temperature
|
|
battery.voltage
|
|
battery.voltage.nominal
|
|
input.frequency
|
|
input.sensitivity (RW) (1)
|
|
input.transfer.high (RW)
|
|
input.transfer.low (RW)
|
|
input.voltage
|
|
input.voltage.nominal
|
|
output.current (1)
|
|
output.frequency
|
|
output.voltage
|
|
output.voltage.nominal
|
|
ups.firmware
|
|
ups.id (RW) (1)
|
|
ups.load
|
|
ups.mfr
|
|
ups.model
|
|
ups.status
|
|
ups.test.result
|
|
ups.contacts (1)
|
|
|
|
The following instant commands are supported:
|
|
load.off
|
|
load.on
|
|
shutdown.reboot
|
|
shutdown.reboot.graceful
|
|
shutdown.return
|
|
shutdown.stop
|
|
test.battery.start
|
|
test.battery.stop
|
|
|
|
The following ups.status values are supported:
|
|
BOOST (1)
|
|
BYPASS
|
|
LB
|
|
OB
|
|
OFF
|
|
OL
|
|
OVER (2)
|
|
RB (2)
|
|
TRIM (1)
|
|
|
|
(1) these items have not been tested because they are not supported
|
|
by my SU1000RT2U.
|
|
(2) these items have not been tested because I haven't tested them.
|
|
*/
|
|
|
|
|
|
#include "main.h"
|
|
#include "serial.h"
|
|
|
|
#define DRIVER_NAME "Tripp Lite SmartOnline driver"
|
|
#define DRIVER_VERSION "0.06"
|
|
|
|
/* driver description structure */
|
|
upsdrv_info_t upsdrv_info = {
|
|
DRIVER_NAME,
|
|
DRIVER_VERSION,
|
|
"Allan N. Hessenflow <allanh@kallisti.com>",
|
|
DRV_EXPERIMENTAL,
|
|
{ NULL }
|
|
};
|
|
|
|
#define MAX_RESPONSE_LENGTH 256
|
|
|
|
static const char *test_result_names[] = {
|
|
"No test performed",
|
|
"Passed",
|
|
"In progress",
|
|
"General test failed",
|
|
"Battery failed",
|
|
"Deep battery test failed",
|
|
"Aborted"
|
|
};
|
|
|
|
static struct {
|
|
int code;
|
|
const char *name;
|
|
} sensitivity[] = {
|
|
{0, "Normal"},
|
|
{1, "Reduced"},
|
|
{2, "Low"}
|
|
};
|
|
|
|
static struct {
|
|
int outlet_banks;
|
|
unsigned long commands_available;
|
|
} ups;
|
|
|
|
/* bits in commands_available */
|
|
#define WDG_AVAILABLE (1UL << 1)
|
|
|
|
/* message types */
|
|
#define POLL 'P'
|
|
#define SET 'S'
|
|
#define ACCEPT 'A'
|
|
#define REJECT 'R'
|
|
#define DATA 'D'
|
|
|
|
/* commands */
|
|
#define AUTO_REBOOT "ARB" /* poll/set */
|
|
#define AUTO_TEST "ATT" /* poll/set */
|
|
#define ATX_REBOOT "ATX" /* poll/set */
|
|
#define AVAILABLE "AVL" /* poll */
|
|
#define BATTERY_REPLACEMENT_DATE "BRD" /* poll/set */
|
|
#define BUZZER_TEST "BTT" /* set */
|
|
#define BATTERY_TEST "BTV" /* poll/set */
|
|
#define BUZZER "BUZ" /* set */
|
|
#define ECONOMIC_MODE "ECO" /* set */
|
|
#define ENABLE_BUZZER "EDB" /* set */
|
|
#define ENVIRONMENT_INFORMATION "ENV" /* poll */
|
|
#define OUTLET_RELAYS "LET" /* poll */
|
|
#define MANUFACTURER "MNU" /* poll */
|
|
#define MODEL "MOD" /* poll */
|
|
#define RATINGS "RAT" /* poll */
|
|
#define RELAY_CYCLE "RNF" /* set */
|
|
#define RELAY_OFF "ROF" /* set */
|
|
#define RELAY_ON "RON" /* set */
|
|
#define ATX_RESUME "RSM" /* set */
|
|
#define SHUTDOWN_ACTION "SDA" /* set */
|
|
#define SHUTDOWN_RESTART "SDR" /* set */
|
|
#define SHUTDOWN_TYPE "SDT" /* poll/set */
|
|
#define RELAY_STATUS "SOL" /* poll/set */
|
|
#define SELECT_OUTPUT_VOLTAGE "SOV" /* poll/set */
|
|
#define STATUS_ALARM "STA" /* poll */
|
|
#define STATUS_BATTERY "STB" /* poll */
|
|
#define STATUS_INPUT "STI" /* poll */
|
|
#define STATUS_OUTPUT "STO" /* poll */
|
|
#define STATUS_BYPASS "STP" /* poll */
|
|
#define TELEPHONE "TEL" /* poll/set */
|
|
#define TEST_RESULT "TSR" /* poll */
|
|
#define TEST "TST" /* set */
|
|
#define TRANSFER_FREQUENCY "TXF" /* poll/set */
|
|
#define TRANSFER_VOLTAGE "TXV" /* poll/set */
|
|
#define BOOT_DELAY "UBD" /* poll/set */
|
|
#define BAUD_RATE "UBR" /* poll/set */
|
|
#define IDENTIFICATION "UID" /* poll/set */
|
|
#define VERSION_CMD "VER" /* poll */
|
|
#define VOLTAGE_SENSITIVITY "VSN" /* poll/set */
|
|
#define WATCHDOG "WDG" /* poll/set */
|
|
|
|
|
|
static ssize_t do_command(char type, const char *command, const char *parameters, char *response)
|
|
{
|
|
char buffer[SMALLBUF];
|
|
size_t count;
|
|
ssize_t ret;
|
|
|
|
ser_flush_io(upsfd);
|
|
|
|
if (response) {
|
|
*response = '\0';
|
|
}
|
|
|
|
snprintf(buffer, sizeof(buffer), "~00%c%03d%s%s", type, (int)(strlen(command) + strlen(parameters)), command, parameters);
|
|
|
|
ret = ser_send_pace(upsfd, 10000, "%s", buffer);
|
|
if (ret <= 0) {
|
|
upsdebug_with_errno(3, "do_command: send [%s]", buffer);
|
|
return -1;
|
|
}
|
|
|
|
upsdebugx(3, "do_command: %zd bytes sent [%s] -> OK", ret, buffer);
|
|
|
|
ret = ser_get_buf_len(upsfd, (unsigned char *)buffer, 4, 3, 0);
|
|
if (ret < 0) {
|
|
upsdebug_with_errno(3, "do_command: read");
|
|
return -1;
|
|
}
|
|
if (ret == 0) {
|
|
upsdebugx(3, "do_command: read -> TIMEOUT");
|
|
return -1;
|
|
}
|
|
|
|
buffer[ret] = '\0';
|
|
upsdebugx(3, "do_command: %zd byted read [%s]", ret, buffer);
|
|
|
|
if (!strcmp(buffer, "~00D")) {
|
|
|
|
ret = ser_get_buf_len(upsfd, (unsigned char *)buffer, 3, 3, 0);
|
|
if (ret < 0) {
|
|
upsdebug_with_errno(3, "do_command: read");
|
|
return -1;
|
|
}
|
|
if (ret == 0) {
|
|
upsdebugx(3, "do_command: read -> TIMEOUT");
|
|
return -1;
|
|
}
|
|
|
|
buffer[ret] = '\0';
|
|
upsdebugx(3, "do_command: %zd bytes read [%s]", ret, buffer);
|
|
|
|
int c = atoi(buffer);
|
|
if (c < 0) {
|
|
upsdebugx(3, "do_command: response not expected to be a negative count!");
|
|
return -1;
|
|
}
|
|
|
|
count = (size_t)c;
|
|
if (count >= MAX_RESPONSE_LENGTH) {
|
|
upsdebugx(3, "do_command: response exceeds expected size!");
|
|
return -1;
|
|
}
|
|
|
|
if (count && !response) {
|
|
upsdebugx(3, "do_command: response not expected!");
|
|
return -1;
|
|
}
|
|
|
|
if (count == 0) {
|
|
return 0;
|
|
}
|
|
|
|
ret = ser_get_buf_len(upsfd, (unsigned char *)response, count, 3, 0);
|
|
if (ret < 0) {
|
|
upsdebug_with_errno(3, "do_command: read");
|
|
return -1;
|
|
}
|
|
if (ret == 0) {
|
|
upsdebugx(3, "do_command: read -> TIMEOUT");
|
|
return -1;
|
|
}
|
|
|
|
response[ret] = '\0';
|
|
upsdebugx(3, "do_command: %zd bytes read [%s]", ret, response);
|
|
|
|
/* Tripp Lite pads their string responses with spaces.
|
|
I don't like that, so I remove them. This is safe to
|
|
do with all responses for this protocol, so I just
|
|
do that here. */
|
|
str_rtrim(response, ' ');
|
|
|
|
return ret;
|
|
}
|
|
|
|
if (!strcmp(buffer, "~00A")) {
|
|
return 0;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
static char *field(char *str, int fieldnum)
|
|
{
|
|
|
|
while (str && fieldnum--) {
|
|
str = strchr(str, ';');
|
|
if (str)
|
|
str++;
|
|
}
|
|
if (str && *str == ';')
|
|
return NULL;
|
|
|
|
return str;
|
|
}
|
|
|
|
static int get_identification(void) {
|
|
char response[MAX_RESPONSE_LENGTH];
|
|
|
|
if (do_command(POLL, IDENTIFICATION, "", response) >= 0) {
|
|
dstate_setinfo("ups.id", "%s", response);
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void set_identification(const char *val) {
|
|
char response[MAX_RESPONSE_LENGTH];
|
|
|
|
if (do_command(POLL, IDENTIFICATION, "", response) < 0)
|
|
return;
|
|
if (strcmp(val, response)) {
|
|
strncpy(response, val, MAX_RESPONSE_LENGTH);
|
|
response[MAX_RESPONSE_LENGTH - 1] = '\0';
|
|
do_command(SET, IDENTIFICATION, response, NULL);
|
|
}
|
|
}
|
|
|
|
static int get_transfer_voltage_low(void) {
|
|
char response[MAX_RESPONSE_LENGTH];
|
|
char *ptr;
|
|
|
|
if (do_command(POLL, TRANSFER_VOLTAGE, "", response) > 0) {
|
|
ptr = field(response, 0);
|
|
if (ptr)
|
|
dstate_setinfo("input.transfer.low", "%d", atoi(ptr));
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void set_transfer_voltage_low(int val) {
|
|
char response[MAX_RESPONSE_LENGTH];
|
|
char *ptr;
|
|
int high;
|
|
|
|
if (do_command(POLL, TRANSFER_VOLTAGE, "", response) <= 0)
|
|
return;
|
|
ptr = field(response, 0);
|
|
if (!ptr || val == atoi(ptr))
|
|
return;
|
|
ptr = field(response, 1);
|
|
if (!ptr)
|
|
return;
|
|
high = atoi(ptr);
|
|
snprintf(response, sizeof(response), "%d;%d", val, high);
|
|
do_command(SET, TRANSFER_VOLTAGE, response, NULL);
|
|
}
|
|
|
|
static int get_transfer_voltage_high(void) {
|
|
char response[MAX_RESPONSE_LENGTH];
|
|
char *ptr;
|
|
|
|
if (do_command(POLL, TRANSFER_VOLTAGE, "", response) > 0) {
|
|
ptr = field(response, 1);
|
|
if (ptr)
|
|
dstate_setinfo("input.transfer.high", "%d", atoi(ptr));
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void set_transfer_voltage_high(int val) {
|
|
char response[MAX_RESPONSE_LENGTH];
|
|
char *ptr;
|
|
int low;
|
|
|
|
if (do_command(POLL, TRANSFER_VOLTAGE, "", response) <= 0)
|
|
return;
|
|
ptr = field(response, 0);
|
|
if (!ptr)
|
|
return;
|
|
low = atoi(ptr);
|
|
ptr = field(response, 1);
|
|
if (!ptr || val == atoi(ptr))
|
|
return;
|
|
snprintf(response, sizeof(response), "%d;%d", low, val);
|
|
do_command(SET, TRANSFER_VOLTAGE, response, NULL);
|
|
}
|
|
|
|
static int get_sensitivity(void) {
|
|
char response[MAX_RESPONSE_LENGTH];
|
|
unsigned int i;
|
|
|
|
if (do_command(POLL, VOLTAGE_SENSITIVITY, "", response) <= 0)
|
|
return 0;
|
|
for (i = 0; i < sizeof(sensitivity) / sizeof(sensitivity[0]); i++) {
|
|
if (sensitivity[i].code == atoi(response)) {
|
|
dstate_setinfo("input.sensitivity", "%s",
|
|
sensitivity[i].name);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void set_sensitivity(const char *val) {
|
|
char parm[20];
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < sizeof(sensitivity) / sizeof(sensitivity[0]); i++) {
|
|
if (!strcasecmp(val, sensitivity[i].name)) {
|
|
snprintf(parm, sizeof(parm), "%u", i);
|
|
do_command(SET, VOLTAGE_SENSITIVITY, parm, NULL);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void auto_reboot(int enable) {
|
|
char parm[20];
|
|
char response[MAX_RESPONSE_LENGTH];
|
|
char *ptr;
|
|
int mode;
|
|
|
|
if (enable)
|
|
mode = 1;
|
|
else
|
|
mode = 2;
|
|
if (do_command(POLL, AUTO_REBOOT, "", response) <= 0)
|
|
return;
|
|
ptr = field(response, 0);
|
|
if (!ptr || atoi(ptr) != mode) {
|
|
snprintf(parm, sizeof(parm), "%d", mode);
|
|
do_command(SET, AUTO_REBOOT, parm, NULL);
|
|
}
|
|
}
|
|
|
|
static int instcmd(const char *cmdname, const char *extra)
|
|
{
|
|
int i;
|
|
char parm[20];
|
|
|
|
if (!strcasecmp(cmdname, "load.off")) {
|
|
for (i = 0; i < ups.outlet_banks; i++) {
|
|
snprintf(parm, sizeof(parm), "%d;1", i + 1);
|
|
do_command(SET, RELAY_OFF, parm, NULL);
|
|
}
|
|
return STAT_INSTCMD_HANDLED;
|
|
}
|
|
if (!strcasecmp(cmdname, "load.on")) {
|
|
for (i = 0; i < ups.outlet_banks; i++) {
|
|
snprintf(parm, sizeof(parm), "%d;1", i + 1);
|
|
do_command(SET, RELAY_ON, parm, NULL);
|
|
}
|
|
return STAT_INSTCMD_HANDLED;
|
|
}
|
|
if (!strcasecmp(cmdname, "shutdown.reboot")) {
|
|
auto_reboot(1);
|
|
do_command(SET, SHUTDOWN_RESTART, "1", NULL);
|
|
do_command(SET, SHUTDOWN_ACTION, "10", NULL);
|
|
return STAT_INSTCMD_HANDLED;
|
|
}
|
|
if (!strcasecmp(cmdname, "shutdown.reboot.graceful")) {
|
|
auto_reboot(1);
|
|
do_command(SET, SHUTDOWN_RESTART, "1", NULL);
|
|
do_command(SET, SHUTDOWN_ACTION, "60", NULL);
|
|
return STAT_INSTCMD_HANDLED;
|
|
}
|
|
if (!strcasecmp(cmdname, "shutdown.return")) {
|
|
auto_reboot(1);
|
|
do_command(SET, SHUTDOWN_RESTART, "1", NULL);
|
|
do_command(SET, SHUTDOWN_ACTION, "10", NULL);
|
|
return STAT_INSTCMD_HANDLED;
|
|
}
|
|
#if 0 /* doesn't seem to work */
|
|
if (!strcasecmp(cmdname, "shutdown.stayoff")) {
|
|
auto_reboot(0);
|
|
do_command(SET, SHUTDOWN_ACTION, "10", NULL);
|
|
return STAT_INSTCMD_HANDLED;
|
|
}
|
|
#endif
|
|
if (!strcasecmp(cmdname, "shutdown.stop")) {
|
|
do_command(SET, SHUTDOWN_ACTION, "0", NULL);
|
|
return STAT_INSTCMD_HANDLED;
|
|
}
|
|
if (!strcasecmp(cmdname, "test.battery.start")) {
|
|
do_command(SET, TEST, "3", NULL);
|
|
return STAT_INSTCMD_HANDLED;
|
|
}
|
|
if (!strcasecmp(cmdname, "test.battery.stop")) {
|
|
do_command(SET, TEST, "0", NULL);
|
|
return STAT_INSTCMD_HANDLED;
|
|
}
|
|
upslogx(LOG_NOTICE, "instcmd: unknown command [%s] [%s]", cmdname, extra);
|
|
return STAT_INSTCMD_UNKNOWN;
|
|
}
|
|
|
|
static int setvar(const char *varname, const char *val)
|
|
{
|
|
|
|
if (!strcasecmp(varname, "ups.id")) {
|
|
set_identification(val);
|
|
get_identification();
|
|
return STAT_SET_HANDLED;
|
|
}
|
|
if (!strcasecmp(varname, "input.transfer.low")) {
|
|
set_transfer_voltage_low(atoi(val));
|
|
get_transfer_voltage_low();
|
|
return STAT_SET_HANDLED;
|
|
}
|
|
if (!strcasecmp(varname, "input.transfer.high")) {
|
|
set_transfer_voltage_high(atoi(val));
|
|
get_transfer_voltage_high();
|
|
return STAT_SET_HANDLED;
|
|
}
|
|
if (!strcasecmp(varname, "input.sensitivity")) {
|
|
set_sensitivity(val);
|
|
get_sensitivity();
|
|
return STAT_SET_HANDLED;
|
|
}
|
|
|
|
upslogx(LOG_NOTICE, "setvar: unknown var [%s]", varname);
|
|
return STAT_SET_UNKNOWN;
|
|
}
|
|
|
|
static int init_comm(void)
|
|
{
|
|
size_t i, bit;
|
|
char response[MAX_RESPONSE_LENGTH];
|
|
|
|
ups.commands_available = 0;
|
|
/* Repeat enumerate command 2x, firmware bug on some units garbles 1st response */
|
|
if (do_command(POLL, AVAILABLE, "", response) <= 0){
|
|
upslogx(LOG_NOTICE, "init_comm: Initial response malformed, retrying in 300ms");
|
|
usleep(3E5);
|
|
}
|
|
if (do_command(POLL, AVAILABLE, "", response) <= 0)
|
|
return 0;
|
|
i = strlen(response);
|
|
for (bit = 0; bit < i; bit++)
|
|
if (response[i - bit - 1] == '1')
|
|
ups.commands_available |= (1UL << bit);
|
|
|
|
if (do_command(POLL, MANUFACTURER, "", response) <= 0)
|
|
return 0;
|
|
if (strcmp(response, "Tripp Lite"))
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
void upsdrv_initinfo(void)
|
|
{
|
|
char response[MAX_RESPONSE_LENGTH];
|
|
unsigned int min_low_transfer, max_low_transfer;
|
|
unsigned int min_high_transfer, max_high_transfer;
|
|
unsigned int i;
|
|
char *ptr;
|
|
|
|
if (!init_comm())
|
|
fatalx(EXIT_FAILURE, "Unable to detect Tripp Lite SmartOnline UPS on port %s\n",
|
|
device_path);
|
|
min_low_transfer = max_low_transfer = 0;
|
|
min_high_transfer = max_high_transfer = 0;
|
|
|
|
/* get all the read-only fields here */
|
|
if (do_command(POLL, MANUFACTURER, "", response) > 0)
|
|
dstate_setinfo("ups.mfr", "%s", response);
|
|
if (do_command(POLL, MODEL, "", response) > 0)
|
|
dstate_setinfo("ups.model", "%s", response);
|
|
if (do_command(POLL, VERSION_CMD, "", response) > 0)
|
|
dstate_setinfo("ups.firmware", "%s", response);
|
|
if (do_command(POLL, RATINGS, "", response) > 0) {
|
|
ptr = field(response, 0);
|
|
if (ptr)
|
|
dstate_setinfo("input.voltage.nominal", "%d",
|
|
atoi(ptr));
|
|
ptr = field(response, 2);
|
|
if (ptr) {
|
|
dstate_setinfo("output.voltage.nominal", "%d",
|
|
atoi(ptr));
|
|
}
|
|
ptr = field(response, 14);
|
|
if (ptr)
|
|
dstate_setinfo("battery.voltage.nominal", "%d",
|
|
atoi(ptr));
|
|
ptr = field(response, 10);
|
|
if (ptr) {
|
|
int ipv = atoi(ptr);
|
|
if (ipv >= 0)
|
|
min_low_transfer = (unsigned int)ipv;
|
|
}
|
|
ptr = field(response, 9);
|
|
if (ptr) {
|
|
int ipv = atoi(ptr);
|
|
if (ipv >= 0)
|
|
max_low_transfer = (unsigned int)ipv;
|
|
}
|
|
ptr = field(response, 12);
|
|
if (ptr) {
|
|
int ipv = atoi(ptr);
|
|
if (ipv >= 0)
|
|
min_high_transfer = (unsigned int)ipv;
|
|
}
|
|
ptr = field(response, 11);
|
|
if (ptr) {
|
|
int ipv = atoi(ptr);
|
|
if (ipv >= 0)
|
|
max_high_transfer = (unsigned int)ipv;
|
|
}
|
|
}
|
|
if (do_command(POLL, OUTLET_RELAYS, "", response) > 0)
|
|
ups.outlet_banks = atoi(response);
|
|
/* define things that are settable */
|
|
if (get_identification()) {
|
|
dstate_setflags("ups.id", ST_FLAG_RW | ST_FLAG_STRING);
|
|
dstate_setaux("ups.id", 100);
|
|
}
|
|
if (get_transfer_voltage_low() && max_low_transfer) {
|
|
dstate_setflags("input.transfer.low", ST_FLAG_RW);
|
|
for (i = min_low_transfer; i <= max_low_transfer; i++)
|
|
dstate_addenum("input.transfer.low", "%d", i);
|
|
}
|
|
if (get_transfer_voltage_high() && max_low_transfer) {
|
|
dstate_setflags("input.transfer.high", ST_FLAG_RW);
|
|
for (i = min_high_transfer; i <= max_high_transfer; i++)
|
|
dstate_addenum("input.transfer.high", "%d", i);
|
|
}
|
|
if (get_sensitivity()) {
|
|
dstate_setflags("input.sensitivity", ST_FLAG_RW);
|
|
for (i = 0; i < sizeof(sensitivity) / sizeof(sensitivity[0]);
|
|
i++)
|
|
dstate_addenum("input.sensitivity", "%s",
|
|
sensitivity[i].name);
|
|
}
|
|
if (ups.outlet_banks) {
|
|
dstate_addcmd("load.off");
|
|
dstate_addcmd("load.on");
|
|
}
|
|
dstate_addcmd("shutdown.reboot");
|
|
dstate_addcmd("shutdown.reboot.graceful");
|
|
dstate_addcmd("shutdown.return");
|
|
#if 0 /* doesn't work */
|
|
dstate_addcmd("shutdown.stayoff");
|
|
#endif
|
|
dstate_addcmd("shutdown.stop");
|
|
dstate_addcmd("test.battery.start");
|
|
dstate_addcmd("test.battery.stop");
|
|
|
|
/* add all the variables that change regularly */
|
|
upsdrv_updateinfo();
|
|
|
|
upsh.instcmd = instcmd;
|
|
upsh.setvar = setvar;
|
|
|
|
printf("Detected %s %s on %s\n", dstate_getinfo("ups.mfr"),
|
|
dstate_getinfo("ups.model"), device_path);
|
|
}
|
|
|
|
void upsdrv_updateinfo(void)
|
|
{
|
|
char response[MAX_RESPONSE_LENGTH];
|
|
char *ptr, *ptr2;
|
|
int i;
|
|
int flags;
|
|
int contacts_set;
|
|
int low_battery;
|
|
|
|
status_init();
|
|
if (do_command(POLL, STATUS_OUTPUT, "", response) <= 0) {
|
|
dstate_datastale();
|
|
return;
|
|
}
|
|
ptr = field(response, 0);
|
|
/* require output status field to exist */
|
|
if (!ptr) {
|
|
dstate_datastale();
|
|
return;
|
|
}
|
|
switch (atoi(ptr)) {
|
|
case 0:
|
|
status_set("OL");
|
|
break;
|
|
case 1:
|
|
status_set("OB");
|
|
break;
|
|
case 2:
|
|
status_set("BYPASS");
|
|
break;
|
|
case 3:
|
|
status_set("OL");
|
|
status_set("TRIM");
|
|
break;
|
|
case 4:
|
|
status_set("OL");
|
|
status_set("BOOST");
|
|
break;
|
|
case 5:
|
|
status_set("BYPASS");
|
|
break;
|
|
case 6:
|
|
break;
|
|
case 7:
|
|
status_set("OFF");
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
ptr = field(response, 6);
|
|
if (ptr)
|
|
dstate_setinfo("ups.load", "%d", atoi(ptr));
|
|
ptr = field(response, 3);
|
|
if (ptr)
|
|
dstate_setinfo("output.voltage", "%03.1f",
|
|
(double) (atoi(ptr)) / 10.0);
|
|
ptr = field(response, 1);
|
|
if (ptr)
|
|
dstate_setinfo("output.frequency", "%03.1f",
|
|
(double) (atoi(ptr)) / 10.0);
|
|
ptr = field(response, 4);
|
|
if (ptr)
|
|
dstate_setinfo("output.current", "%03.1f",
|
|
(double) (atoi(ptr)) / 10.0);
|
|
|
|
low_battery = 0;
|
|
if (do_command(POLL, STATUS_BATTERY, "", response) <= 0) {
|
|
dstate_datastale();
|
|
return;
|
|
}
|
|
ptr = field(response, 0);
|
|
if (ptr && atoi(ptr) == 2)
|
|
status_set("RB");
|
|
ptr = field(response, 1);
|
|
if (ptr && atoi(ptr))
|
|
low_battery = 1;
|
|
ptr = field(response, 8);
|
|
if (ptr)
|
|
dstate_setinfo("battery.temperature", "%d", atoi(ptr));
|
|
ptr = field(response, 9);
|
|
if (ptr) {
|
|
dstate_setinfo("battery.charge", "%d", atoi(ptr));
|
|
ptr2 = getval("lowbatt");
|
|
if (ptr2 && atoi(ptr2) > 0 && atoi(ptr2) <= 99 &&
|
|
atoi(ptr) <= atoi(ptr2))
|
|
low_battery = 1;
|
|
}
|
|
ptr = field(response, 6);
|
|
if (ptr)
|
|
dstate_setinfo("battery.voltage", "%03.1f",
|
|
(double) (atoi(ptr)) / 10.0);
|
|
ptr = field(response, 7);
|
|
if (ptr)
|
|
dstate_setinfo("battery.current", "%03.1f",
|
|
(double) (atoi(ptr)) / 10.0);
|
|
if (low_battery)
|
|
status_set("LB");
|
|
|
|
if (do_command(POLL, STATUS_ALARM, "", response) <= 0) {
|
|
dstate_datastale();
|
|
return;
|
|
}
|
|
ptr = field(response, 3);
|
|
if (ptr && atoi(ptr))
|
|
status_set("OVER");
|
|
|
|
if (do_command(POLL, STATUS_INPUT, "", response) > 0) {
|
|
ptr = field(response, 2);
|
|
if (ptr)
|
|
dstate_setinfo("input.voltage", "%03.1f",
|
|
(double) (atoi(ptr)) / 10.0);
|
|
ptr = field(response, 1);
|
|
if (ptr)
|
|
dstate_setinfo("input.frequency", "%03.1f",
|
|
(double) (atoi(ptr)) / 10.0);
|
|
}
|
|
|
|
if (do_command(POLL, TEST_RESULT, "", response) > 0) {
|
|
int r;
|
|
size_t trsize;
|
|
|
|
r = atoi(response);
|
|
trsize = sizeof(test_result_names) /
|
|
sizeof(test_result_names[0]);
|
|
|
|
if ((r < 0) || (r >= (int) trsize))
|
|
r = 0;
|
|
|
|
dstate_setinfo("ups.test.result", "%s", test_result_names[r]);
|
|
}
|
|
|
|
if (do_command(POLL, ENVIRONMENT_INFORMATION, "", response) > 0) {
|
|
ptr = field(response, 0);
|
|
if (ptr)
|
|
dstate_setinfo("ambient.temperature", "%d", atoi(ptr));
|
|
ptr = field(response, 1);
|
|
if (ptr)
|
|
dstate_setinfo("ambient.humidity", "%d", atoi(ptr));
|
|
flags = 0;
|
|
contacts_set = 0;
|
|
for (i = 0; i < 4; i++) {
|
|
ptr = field(response, 2 + i);
|
|
if (ptr) {
|
|
contacts_set = 1;
|
|
if (*ptr == '1')
|
|
flags |= 1 << i;
|
|
}
|
|
}
|
|
if (contacts_set)
|
|
dstate_setinfo("ups.contacts", "%02X", flags);
|
|
}
|
|
|
|
/* if we are here, status is valid */
|
|
status_commit();
|
|
dstate_dataok();
|
|
}
|
|
|
|
void upsdrv_shutdown(void)
|
|
{
|
|
char parm[20];
|
|
|
|
if (!init_comm())
|
|
printf("Status failed. Assuming it's on battery and trying a shutdown anyway.\n");
|
|
auto_reboot(1);
|
|
/* in case the power is on, tell it to automatically reboot. if
|
|
it is off, this has no effect. */
|
|
snprintf(parm, sizeof(parm), "%d", 1); /* delay before reboot, in minutes */
|
|
do_command(SET, SHUTDOWN_RESTART, parm, NULL);
|
|
snprintf(parm, sizeof(parm), "%d", 5); /* delay before shutdown, in seconds */
|
|
do_command(SET, SHUTDOWN_ACTION, parm, NULL);
|
|
}
|
|
|
|
void upsdrv_help(void)
|
|
{
|
|
}
|
|
|
|
/* list flags and values that you want to receive via -x or ups.conf */
|
|
void upsdrv_makevartable(void)
|
|
{
|
|
addvar(VAR_VALUE, "lowbatt", "Set low battery level, in percent");
|
|
}
|
|
|
|
void upsdrv_initups(void)
|
|
{
|
|
upsfd = ser_open(device_path);
|
|
ser_set_speed(upsfd, device_path, B2400);
|
|
}
|
|
|
|
void upsdrv_cleanup(void)
|
|
{
|
|
ser_close(upsfd, device_path);
|
|
}
|