nut/drivers/safenet.c
2011-01-26 10:35:08 +01:00

530 lines
12 KiB
C

/*
* safenet.c - model specific routines for following units:
*
* - Fairstone L525/-625/-750
* - Fenton P400/-600/-800
* - Gemini UPS625/-1000
* - Powerwell PM525A/-625A/-800A/-1000A/-1250A
* - Repotec RPF525/-625/-800/-1000
* - Soltec Winmate 525/625/800/1000
* - Sweex 500/1000
* - others using SafeNet software and serial interface
*
* Status:
* 20081102/Revision 1.41 - Arjen de Korte <adkorte-guest@alioth.debian.org>
* - allow more time for reading reply to command
* 20081106/Revision 1.5 - Arjen de Korte <adkorte-guest@alioth.debian.org>
* - changed communication with UPS
* - improved handling of battery & system test
* 20081228/Revision 1.6 - Arjen de Korte <adkorte-guest@alioth.debian.org>
* - add ondelay and offdelay
*
* Copyright (C) 2003-2008 Arjen de Korte <adkorte-guest@alioth.debian.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "main.h"
#include "serial.h"
#include "safenet.h"
#define DRIVER_NAME "Generic SafeNet UPS driver"
#define DRIVER_VERSION "1.6"
/* driver description structure */
upsdrv_info_t upsdrv_info = {
DRIVER_NAME,
DRIVER_VERSION,
"Arjen de Korte <adkorte-guest@alioth.debian.org>",
DRV_STABLE,
{ NULL }
};
/*
* Here we keep the last known status of the UPS
*/
static union {
char reply[10];
struct safenet status;
} ups;
static int ondelay = 1; /* minutes */
static int offdelay = 30; /* seconds */
static int safenet_command(const char *command)
{
char reply[32];
int i, ret;
/*
* Get rid of whatever is in the in- and output buffers.
*/
ser_flush_io(upsfd);
/*
* Send the command and read back the status line. When we just send
* a status polling command, it will return the actual status.
*/
ret = ser_send(upsfd, "%s", command);
if (ret < 0) {
upsdebug_with_errno(3, "send");
return -1;
}
if (ret == 0) {
upsdebug_with_errno(3, "send: timeout");
return -1;
}
upsdebugx(3, "send: %.*s", (int)strcspn(command, "\r"), command);
/*
* Read the reply from the UPS.
*/
ret = ser_get_buf(upsfd, reply, sizeof(reply), 1, 0);
if (ret < 0) {
upsdebug_with_errno(3, "read");
return -1;
}
if (ret == 0) {
upsdebugx(3, "read: timeout");
return -1;
}
upsdebugx(3, "read: %.*s", (int)strcspn(reply, "\r"), reply);
/*
* We check if the reply looks like a valid status.
*/
if ((ret != 12) || (reply[0] != '$') || (strspn(reply+1, "AB") != 10)) {
return -1;
}
for (i = 0; i < 10; i++) {
ups.reply[i] = ((reply[i+1] == 'B') ? 1 : 0);
}
return 0;
}
static void safenet_update(void)
{
status_init();
if (ups.status.onbattery) {
status_set("OB");
} else {
status_set("OL");
}
if (ups.status.batterylow) {
status_set("LB");
}
if (ups.status.overload) {
status_set("OVER");
}
if (ups.status.batteryfail) {
status_set("RB");
}
if (ups.status.systemtest) {
status_set("CAL");
}
alarm_init();
if (ups.status.systemfail) {
alarm_set("System selftest fail!");
}
alarm_commit();
status_commit();
}
static int instcmd(const char *cmdname, const char *extra)
{
if (!strcasecmp(cmdname, "beeper.off")) {
/* compatibility mode for old command */
upslogx(LOG_WARNING,
"The 'beeper.off' command has been renamed to 'beeper.mute' for this driver");
return instcmd("beeper.mute", NULL);
}
if (!strcasecmp(cmdname, "beeper.on")) {
/* compatibility mode for old command */
upslogx(LOG_WARNING,
"The 'beeper.on' command has been renamed to 'beeper.enable'");
return instcmd("beeper.enable", NULL);
}
/*
* Start the UPS selftest
*/
if (!strcasecmp(cmdname, "test.battery.start")) {
if (safenet_command(COM_BATT_TEST)) {
return STAT_INSTCMD_FAILED;
} else {
return STAT_INSTCMD_HANDLED;
}
}
/*
* Stop the UPS selftest
*/
if (!strcasecmp(cmdname, "test.battery.stop")) {
if (safenet_command(COM_STOP_TEST)) {
return STAT_INSTCMD_FAILED;
} else {
return STAT_INSTCMD_HANDLED;
}
}
/*
* Start simulated mains failure
*/
if (!strcasecmp (cmdname, "test.failure.start")) {
if (safenet_command(COM_MAINS_TEST)) {
return STAT_INSTCMD_FAILED;
} else {
return STAT_INSTCMD_HANDLED;
}
}
/*
* Stop simulated mains failure
*/
if (!strcasecmp (cmdname, "test.failure.stop")) {
if (safenet_command(COM_STOP_TEST)) {
return STAT_INSTCMD_FAILED;
} else {
return STAT_INSTCMD_HANDLED;
}
}
/*
* If beeper is off, toggle beeper state (so it should be ON after this)
*/
if (!strcasecmp(cmdname, "beeper.enable")) {
if (ups.status.silenced && safenet_command(COM_TOGGLE_BEEP)) {
return STAT_INSTCMD_FAILED;
} else {
return STAT_INSTCMD_HANDLED;
}
}
/*
* If beeper is not off, toggle beeper state (so it should be OFF after this)
* Unfortunately, this only mutes the beeper, it turns back on for the next
* event automatically (no way to stop this, besides side cutters)
*/
if (!strcasecmp(cmdname, "beeper.mute")) {
if (!ups.status.silenced && safenet_command(COM_TOGGLE_BEEP)) {
return STAT_INSTCMD_FAILED;
} else {
return STAT_INSTCMD_HANDLED;
}
}
/*
* Toggle beeper state unconditionally
*/
if (!strcasecmp(cmdname, "beeper.toggle")) {
if (safenet_command(COM_TOGGLE_BEEP)) {
return STAT_INSTCMD_FAILED;
} else {
return STAT_INSTCMD_HANDLED;
}
}
/*
* Shutdown and wait for the power to return
*/
if (!strcasecmp(cmdname, "shutdown.return")) {
char command[] = SHUTDOWN_RETURN;
command[4] += ((offdelay % 1000) / 100);
command[5] += ((offdelay % 100) / 10);
command[6] += (offdelay % 10);
safenet_command(command);
return STAT_INSTCMD_HANDLED;
}
/*
* Shutdown and reboot
*/
if (!strcasecmp(cmdname, "shutdown.reboot")) {
char command[] = SHUTDOWN_REBOOT;
command[3] += ((offdelay % 1000) / 100);
command[4] += ((offdelay % 100) / 10);
command[5] += (offdelay % 10);
command[7] += ((ondelay % 10000) / 1000);
command[8] += ((ondelay % 1000) / 100);
command[9] += ((ondelay % 100) / 10);
command[10] += (ondelay % 10);
safenet_command(command);
return STAT_INSTCMD_HANDLED;
}
upslogx(LOG_NOTICE, "instcmd: unknown command [%s]", cmdname);
return STAT_INSTCMD_UNKNOWN;
}
void upsdrv_initinfo(void)
{
int retry = 3;
char *v;
dstate_setinfo("driver.version.internal", "%s", DRIVER_VERSION);
usleep(100000);
/*
* Very crude hardware detection. If an UPS is attached, it will set DSR
* to 1. Bail out if it isn't.
*/
if (!ser_get_dsr(upsfd)) {
fatalx(EXIT_FAILURE, "Serial cable problem or nothing attached to %s", device_path);
}
/*
* Initialize the serial interface of the UPS by sending the magic
* string. If it does not respond with a valid status reply,
* display an error message and give up.
*/
while (safenet_command(COM_INITIALIZE)) {
if (--retry) {
continue;
}
fatalx(EXIT_FAILURE, "SafeNet protocol compatible UPS not found on %s", device_path);
}
/*
* Read the commandline settings for the following parameters, since we can't
* autodetect them.
*/
dstate_setinfo("ups.mfr", "%s", ((v = getval("manufacturer")) != NULL) ? v : "unknown");
dstate_setinfo("ups.model", "%s", ((v = getval("modelname")) != NULL) ? v : "unknown");
dstate_setinfo("ups.serial", "%s", ((v = getval("serialnumber")) != NULL) ? v : "unknown");
dstate_setinfo("ups.delay.start", "%d", 60 * ondelay);
dstate_setinfo("ups.delay.shutdown", "%d", offdelay);
/*
* These are the instant commands we support.
*/
dstate_addcmd ("test.battery.start");
dstate_addcmd ("test.battery.stop");
dstate_addcmd ("test.failure.start");
dstate_addcmd ("test.failure.stop");
dstate_addcmd ("beeper.on");
dstate_addcmd ("beeper.off");
dstate_addcmd ("beeper.enable");
dstate_addcmd ("beeper.mute");
dstate_addcmd ("beeper.toggle");
dstate_addcmd ("shutdown.return");
dstate_addcmd ("shutdown.reboot");
upsh.instcmd = instcmd;
}
/*
* The status polling commands are *almost* random. Whatever the reason
* is, there is a certain pattern in them. The first character after the
* start character 'Z' determines how many positions there are between
* that character and the single 'L' character that's in each command (A=0,
* B=1,...,J=9). The rest is filled with random (?) data [A...J]. But why?
* No idea. The UPS *does* check if the polling commands match this format.
* And as the SafeNet software uses "random" polling commands, so do we.
*
* Note: if you don't use ASCII, the characters will be different!
*/
void upsdrv_updateinfo(void)
{
char command[] = COM_POLL_STAT;
int i;
static int retry = 0;
/*
* Fill the command portion with random characters from the range
* [A...J].
*/
for (i = 1; i < 12; i++) {
command[i] = (random() % 10) + 'A';
}
/*
* Find which character must be an 'L' and put it there.
*/
command[command[1]-'A'+2] = 'L';
/*
* Do a status poll.
*/
if (safenet_command(command)) {
ser_comm_fail("Status read failed");
if (retry < 2) {
retry++;
} else {
dstate_datastale();
}
return;
}
ser_comm_good();
retry = 0;
if (ups.status.systemtest && ups.status.batterylow) {
/*
* Don't update status after stopping battery test, to
* allow UPS to update the status flags (OB+LB glitch)
*/
if (safenet_command(COM_STOP_TEST)) {
upslogx(LOG_WARNING, "Can't terminate battery test!");
} else {
upslogx(LOG_INFO, "Battery test finished");
return;
}
}
safenet_update();
dstate_dataok();
}
void upsdrv_shutdown(void)
{
int retry = 3;
/*
* Since we may have arrived here before the hardware is initialized,
* try to initialize it here.
*
* Initialize the serial interface of the UPS by sending the magic
* string. If it does not respond with a valid status reply,
* display an error message and give up.
*/
while (safenet_command(COM_INITIALIZE)) {
if (--retry) {
continue;
}
fatalx(EXIT_FAILURE, "SafeNet protocol compatible UPS not found on %s", device_path);
}
/*
* Since the UPS will happily restart on battery, we must use a
* different shutdown command depending on the line status, so
* we need to check the status of the UPS here.
*/
if (ups.status.onbattery) {
instcmd("shutdown.return", NULL);
} else {
instcmd("shutdown.reboot", NULL);
}
}
void upsdrv_help(void)
{
}
void upsdrv_makevartable(void)
{
addvar(VAR_VALUE, "manufacturer", "manufacturer [unknown]");
addvar(VAR_VALUE, "modelname", "modelname [unknown]");
addvar(VAR_VALUE, "serialnumber", "serialnumber [unknown]");
addvar(VAR_VALUE, "ondelay", "Delay before UPS startup (minutes)");
addvar(VAR_VALUE, "offdelay", "Delay before UPS shutdown (seconds)");
}
void upsdrv_initups(void)
{
struct termios tio;
const char *val;
/*
* Open and lock the serial port and set the speed to 1200 baud.
*/
upsfd = ser_open(device_path);
ser_set_speed(upsfd, device_path, B1200);
if (tcgetattr(upsfd, &tio)) {
fatal_with_errno(EXIT_FAILURE, "tcgetattr");
}
/*
* Use canonical mode input processing (to read reply line)
*/
tio.c_lflag |= ICANON; /* Canonical input (erase and kill processing) */
tio.c_cc[VEOF] = _POSIX_VDISABLE;
tio.c_cc[VEOL] = '\r';
tio.c_cc[VERASE] = _POSIX_VDISABLE;
tio.c_cc[VINTR] = _POSIX_VDISABLE;
tio.c_cc[VKILL] = _POSIX_VDISABLE;
tio.c_cc[VQUIT] = _POSIX_VDISABLE;
tio.c_cc[VSUSP] = _POSIX_VDISABLE;
tio.c_cc[VSTART] = _POSIX_VDISABLE;
tio.c_cc[VSTOP] = _POSIX_VDISABLE;
if (tcsetattr(upsfd, TCSANOW, &tio)) {
fatal_with_errno(EXIT_FAILURE, "tcsetattr");
}
/*
* Set DTR and clear RTS to provide power for the serial interface.
*/
ser_set_dtr(upsfd, 1);
ser_set_rts(upsfd, 0);
val = getval("ondelay");
if (val) {
ondelay = strtol(val, NULL, 10);
}
if ((ondelay < 0) || (ondelay > 9999)) {
fatalx(EXIT_FAILURE, "Start delay '%d' out of range [0..9999]", ondelay);
}
val = getval("offdelay");
if (val) {
offdelay = strtol(val, NULL, 10);
}
if ((offdelay < 0) || (offdelay > 999)) {
fatalx(EXIT_FAILURE, "Shutdown delay '%d' out of range [0..999]", offdelay);
}
}
void upsdrv_cleanup(void)
{
ser_set_dtr(upsfd, 0);
ser_close(upsfd, device_path);
}