/*
* 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:
* 20031015/Revision 0.1 - Arjen de Korte <arjen@de-korte.org>
* - initial release (entirely based on reverse engineering the
* serial data stream)
* 20031022/Revision 0.2 - Arjen de Korte <arjen@de-korte.org>
* - status polling command is now "random" (just like the
* SafeNet (Windows) driver
* - low battery status is "LB" (not "BL")
* 20031228/Revision 0.3 - Arjen de Korte <arjen@de-korte.org>
* - instant command 'shutdown.return' added
* - added 'manufacturer', 'modelname' and 'serialnumber'
* commandline parameters
* - removed experimental driver flag
* - documentation & code cleanup
* 20060108/Revision 0.4 - Arjen de Korte <arjen@de-korte.org>
* - minor changes to make sure state changes are not missed
* - log errors when instant commands can't be handled
* - crude hardware detection which looks for DSR=1
* 20060128/Revision 0.5 - Arjen de Korte <arjen@de-korte.org>
* - removed TRUE/FALSE defines, which were not really
* improving the code anyway
* 20060131/Revision 1.0 - Arjen de Korte <arjen@de-korte.org>
* - put all UPS commands in header file (for easy retrieval)
* - remove log errors when instant commands can't be handled
* (doesn't work reliably on at least one flavor)
* 20061229/Revision 1.1 - Arjen de Korte <arjen@de-korte.org>
* - flush the serial input buffer before sending commands
* (to get rid of unsollicited serial PnP replies)
* 20070211/Revision 1.2 - Arjen de Korte <arjen@de-korte.org>
* - wait for three consecutive failed polls before declaring
* data stale in order not to bother clients with temporary
* problems
* 20070304/Revision 1.3 - Arjen de Korte <arjen@de-korte.org>
* - in battery test mode (CAL state) stop the test when the
* the low battery state is reached
*
* Copyright (C) 2003-2006 Arjen de Korte <arjen@de-korte.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 <stdlib.h>
#include <ctype.h>
#include <sys/ioctl.h>
#include "main.h"
#include "serial.h"
#include "safenet.h"
#define DRV_VERSION "1.3"
/*
* Here we keep the last known status of the UPS
*/
static union {
char reply[10];
struct safenet status;
} ups;
static int safenet_command(const char *command)
{
char reply[32];
int i;
/*
* Get rid of whatever is in the in- and output buffers.
*/
tcflush(upsfd, TCIOFLUSH);
/*
* Send the command and read back the status line. When we just send
* a status polling command, it will return the actual status.
*/
ser_send_pace(upsfd, 10000, command);
upsdebugx(3, "UPS command %s", command);
/*
* Read the reply from the UPS. Allow twice the time needed to read 12
* bytes (120 bits (including start & stop bits) / 1200 baud = 100ms).
*/
ser_get_line(upsfd, reply, sizeof(reply), '\r', "", 0, 200000);
upsdebugx(3, "UPS answers %s", (strlen(reply)>0) ? reply : "[INVALID]");
/*
* We check if the reply looks like a valid status.
*/
if ((strlen(reply) != 11) || (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()
{
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");
}
/*
* This is not actually an indication of the UPS in the BYPASS state, it is
* an indication that the UPS failed the self diagnostics (which is NOT the
* same as a replace battery warning). Since the result is the same (no
* backup in case of mains failure) we give it this status. Introduce a new
* status here?
*/
if (ups.status.systemfail) {
status_set("BYPASS");
}
/*
* This is not actually an indication of the UPS in the CAL state, it is an
* indication that the UPS is in the system test state. The result of this
* test may indicate a battery failure, so this is the closest we can get.
* Introduce a new status here?
*/
if (ups.status.systemtest) {
status_set("CAL");
}
status_commit();
}
static int instcmd(const char *cmdname, const char *extra)
{
/*
* Start the UPS selftest
*/
if (!strcasecmp(cmdname, "test.battery.start")) {
safenet_command(COM_BATT_TEST);
return STAT_INSTCMD_HANDLED;
}
/*
* Stop the UPS selftest
*/
if (!strcasecmp(cmdname, "test.battery.stop")) {
safenet_command(COM_STOP_TEST);
return STAT_INSTCMD_HANDLED;
}
/*
* Start simulated mains failure
*/
if (!strcasecmp (cmdname, "test.failure.start")) {
safenet_command(COM_MAINS_TEST);
return STAT_INSTCMD_HANDLED;
}
/*
* Stop simulated mains failure
*/
if (!strcasecmp (cmdname, "test.failure.stop")) {
safenet_command(COM_STOP_TEST);
return STAT_INSTCMD_HANDLED;
}
/*
* If beeper is off, toggle beeper state (so it should be ON after this)
*/
if (!strcasecmp(cmdname, "beeper.on")) {
if (ups.status.silenced) {
safenet_command(COM_TOGGLE_BEEP);
}
return STAT_INSTCMD_HANDLED;
}
/*
* If beeper is not off, toggle beeper state (so it should be OFF after this)
*/
if (!strcasecmp(cmdname, "beeper.off")) {
if (!ups.status.silenced) {
safenet_command(COM_TOGGLE_BEEP);
}
return STAT_INSTCMD_HANDLED;
}
/*
* Shutdown immediately and wait for the power to return
*/
if (!strcasecmp(cmdname, "shutdown.return")) {
safenet_command(SHUTDOWN_RETURN);
return STAT_INSTCMD_HANDLED;
}
/*
* Shutdown immediately and reboot after 1 minute
*/
if (!strcasecmp(cmdname, "shutdown.reboot")) {
safenet_command(SHUTDOWN_REBOOT);
return STAT_INSTCMD_HANDLED;
}
/*
* Shutdown in 20 seconds and reboot after 1 minute
*/
if (!strcasecmp(cmdname, "shutdown.reboot.graceful")) {
safenet_command(GRACEFUL_REBOOT);
return STAT_INSTCMD_HANDLED;
}
upslogx(LOG_NOTICE, "instcmd: unknown command [%s]", cmdname);
return STAT_INSTCMD_UNKNOWN;
}
void upsdrv_initinfo(void)
{
int i, retry = 3;
char *v;
dstate_setinfo("driver.version.internal", "%s", DRV_VERSION);
usleep(100000);
/*
* Very crude hardware detection. If an UPS is attached, it will set DSR
* to 1. Bail out if it isn't.
*/
ioctl(upsfd, TIOCMGET, &i);
if ((i & TIOCM_DSR) == 0) {
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");
/*
* 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 ("shutdown.return");
dstate_addcmd ("shutdown.reboot");
dstate_addcmd ("shutdown.reboot.graceful");
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) {
safenet_command(COM_STOP_TEST);
}
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) {
safenet_command(SHUTDOWN_RETURN);
} else {
safenet_command(SHUTDOWN_REBOOT);
}
}
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]");
}
void upsdrv_banner(void)
{
printf("Network UPS Tools - Generic SafeNet UPS driver %s (%s)\n\n", DRV_VERSION, UPS_VERSION);
}
void upsdrv_initups(void)
{
int dtr_bit = TIOCM_DTR;
int rts_bit = TIOCM_RTS;
/*
* 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);
/*
* Set DTR and clear RTS to provide power for the serial interface.
*/
ioctl(upsfd, TIOCMBIS, &dtr_bit);
ioctl(upsfd, TIOCMBIC, &rts_bit);
}
void upsdrv_cleanup(void)
{
ser_close(upsfd, device_path);
}
syntax highlighted by Code2HTML, v. 0.9.1