438 lines
9.8 KiB
C
438 lines
9.8 KiB
C
/* bestups.c - model specific routines for Best-UPS Fortress models
|
|
|
|
Copyright (C) 1999 Russell Kroll <rkroll@exploits.org>
|
|
|
|
ID config option by Jason White <jdwhite@jdwhite.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"
|
|
|
|
#define DRIVER_NAME "Best UPS driver"
|
|
#define DRIVER_VERSION "1.06"
|
|
|
|
/* driver description structure */
|
|
upsdrv_info_t upsdrv_info = {
|
|
DRIVER_NAME,
|
|
DRIVER_VERSION,
|
|
"Russell Kroll <rkroll@exploits.org>\n" \
|
|
"Jason White <jdwhite@jdwhite.org>",
|
|
DRV_STABLE,
|
|
{ NULL }
|
|
};
|
|
|
|
#define ENDCHAR 13 /* replies end with CR */
|
|
#define MAXTRIES 5
|
|
#define UPSDELAY 50000 /* 50 ms delay required for reliable operation */
|
|
|
|
#define SER_WAIT_SEC 3 /* allow 3.0 sec for ser_get calls */
|
|
#define SER_WAIT_USEC 0
|
|
|
|
static float lowvolt = 0, highvolt = 0;
|
|
static int battvoltmult = 1;
|
|
static int inverted_bypass_bit = 0;
|
|
|
|
static void model_set(const char *abbr, const char *rating)
|
|
{
|
|
if (!strcmp(abbr, "FOR")) {
|
|
dstate_setinfo("ups.mfr", "%s", "Best Power");
|
|
dstate_setinfo("ups.model", "Fortress %s", rating);
|
|
return;
|
|
}
|
|
|
|
if (!strcmp(abbr, "FTC")) {
|
|
dstate_setinfo("ups.mfr", "%s", "Best Power");
|
|
dstate_setinfo("ups.model", "Fortress Telecom %s", rating);
|
|
return;
|
|
}
|
|
|
|
if (!strcmp(abbr, "PRO")) {
|
|
dstate_setinfo("ups.mfr", "%s", "Best Power");
|
|
dstate_setinfo("ups.model", "Patriot Pro %s", rating);
|
|
inverted_bypass_bit = 1;
|
|
return;
|
|
}
|
|
|
|
if (!strcmp(abbr, "PR2")) {
|
|
dstate_setinfo("ups.mfr", "%s", "Best Power");
|
|
dstate_setinfo("ups.model", "Patriot Pro II %s", rating);
|
|
inverted_bypass_bit = 1;
|
|
return;
|
|
}
|
|
|
|
if (!strcmp(abbr, "325")) {
|
|
dstate_setinfo("ups.mfr", "%s", "Sola Australia");
|
|
dstate_setinfo("ups.model", "Sola 325 %s", rating);
|
|
return;
|
|
}
|
|
|
|
if (!strcmp(abbr, "520")) {
|
|
dstate_setinfo("ups.mfr", "%s", "Sola Australia");
|
|
dstate_setinfo("ups.model", "Sola 520 %s", rating);
|
|
return;
|
|
}
|
|
|
|
if (!strcmp(abbr, "610")) {
|
|
dstate_setinfo("ups.mfr", "%s", "Best Power");
|
|
dstate_setinfo("ups.model", "610 %s", rating);
|
|
return;
|
|
}
|
|
|
|
if (!strcmp(abbr, "620")) {
|
|
dstate_setinfo("ups.mfr", "%s", "Sola Australia");
|
|
dstate_setinfo("ups.model", "Sola 620 %s", rating);
|
|
return;
|
|
}
|
|
|
|
if (!strcmp(abbr, "AX1")) {
|
|
dstate_setinfo("ups.mfr", "%s", "Best Power");
|
|
dstate_setinfo("ups.model", "Axxium Rackmount %s", rating);
|
|
return;
|
|
}
|
|
|
|
dstate_setinfo("ups.mfr", "%s", "Unknown");
|
|
dstate_setinfo("ups.model", "Unknown %s (%s)", abbr, rating);
|
|
|
|
printf("Unknown model detected - please report this ID: '%s'\n", abbr);
|
|
}
|
|
|
|
static int instcmd(const char *cmdname, const char *extra)
|
|
{
|
|
if (!strcasecmp(cmdname, "test.battery.stop")) {
|
|
ser_send_pace(upsfd, UPSDELAY, "CT\r");
|
|
return STAT_INSTCMD_HANDLED;
|
|
}
|
|
|
|
if (!strcasecmp(cmdname, "test.battery.start")) {
|
|
ser_send_pace(upsfd, UPSDELAY, "T\r");
|
|
return STAT_INSTCMD_HANDLED;
|
|
}
|
|
|
|
upslogx(LOG_NOTICE, "instcmd: unknown command [%s]", cmdname);
|
|
return STAT_INSTCMD_UNKNOWN;
|
|
}
|
|
|
|
static int get_ident(char *buf, size_t bufsize)
|
|
{
|
|
int i, ret;
|
|
char *ID;
|
|
|
|
ID = getval("ID"); /* user-supplied override from ups.conf */
|
|
|
|
if (ID) {
|
|
upsdebugx(2, "NOTE: using user-supplied ID response");
|
|
snprintf(buf, bufsize, "%s", ID);
|
|
return 1;
|
|
}
|
|
|
|
for (i = 0; i < MAXTRIES; i++) {
|
|
ser_send_pace(upsfd, UPSDELAY, "\rID\r");
|
|
|
|
ret = ser_get_line(upsfd, buf, bufsize, ENDCHAR, "",
|
|
SER_WAIT_SEC, SER_WAIT_USEC);
|
|
|
|
if (ret > 0)
|
|
upsdebugx(2, "get_ident: got [%s]", buf);
|
|
|
|
/* buf must start with ( and be in the range [25-27] */
|
|
if ((ret > 0) && (buf[0] != '(') && (strlen(buf) >= 25) &&
|
|
(strlen(buf) <= 27))
|
|
return 1;
|
|
|
|
sleep(1);
|
|
}
|
|
|
|
upslogx(LOG_INFO, "Giving up on hardware detection after %d tries",
|
|
MAXTRIES);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void ups_ident(void)
|
|
{
|
|
int i;
|
|
char buf[256], *ptr;
|
|
char *model = NULL, *rating = NULL;
|
|
|
|
if (!get_ident(buf, sizeof(buf))) {
|
|
fatalx(EXIT_FAILURE, "Unable to detect a Best/SOLA or Phoenix protocol UPS");
|
|
}
|
|
|
|
/* FOR,750,120,120,20.0,27.6 */
|
|
ptr = strtok(buf, ",");
|
|
|
|
for (i = 0; ptr; i++) {
|
|
|
|
switch (i)
|
|
{
|
|
case 0:
|
|
model = ptr;
|
|
break;
|
|
|
|
case 1:
|
|
rating = ptr;
|
|
break;
|
|
|
|
case 2:
|
|
dstate_setinfo("input.voltage.nominal", "%d", atoi(ptr));
|
|
break;
|
|
|
|
case 3:
|
|
dstate_setinfo("output.voltage.nominal", "%d", atoi(ptr));
|
|
break;
|
|
|
|
case 4:
|
|
lowvolt = atof(ptr);
|
|
break;
|
|
|
|
case 5:
|
|
highvolt = atof(ptr);
|
|
break;
|
|
}
|
|
|
|
ptr = strtok(NULL, ",");
|
|
}
|
|
|
|
if ((!model) || (!rating)) {
|
|
fatalx(EXIT_FAILURE, "Didn't get a valid ident string");
|
|
}
|
|
|
|
model_set(model, rating);
|
|
|
|
/* Battery voltage multiplier */
|
|
ptr = getval("battvoltmult");
|
|
|
|
if (ptr) {
|
|
battvoltmult = atoi(ptr);
|
|
}
|
|
|
|
/* Lookup the nominal battery voltage (should be between lowvolt and highvolt */
|
|
for (i = 0; i < 8; i++) {
|
|
const int nominal[] = { 2, 6, 12, 24, 36, 48, 72, 96 };
|
|
|
|
if ((lowvolt < nominal[i]) && (highvolt > nominal[i])) {
|
|
dstate_setinfo("battery.voltage.nominal", "%d", battvoltmult * nominal[i]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
ptr = getval("nombattvolt");
|
|
|
|
if (ptr) {
|
|
highvolt = atof(ptr);
|
|
}
|
|
}
|
|
|
|
static void ups_sync(void)
|
|
{
|
|
char buf[256];
|
|
int i, ret;
|
|
|
|
for (i = 0; i < MAXTRIES; i++) {
|
|
ser_send_pace(upsfd, UPSDELAY, "\rQ1\r");
|
|
|
|
ret = ser_get_line(upsfd, buf, sizeof(buf), ENDCHAR, "",
|
|
SER_WAIT_SEC, SER_WAIT_USEC);
|
|
|
|
/* return once we get something that looks usable */
|
|
if ((ret > 0) && (buf[0] == '('))
|
|
return;
|
|
|
|
usleep(250000);
|
|
}
|
|
|
|
fatalx(EXIT_FAILURE, "Unable to detect a Best/SOLA or Phoenix protocol UPS");
|
|
}
|
|
|
|
void upsdrv_initinfo(void)
|
|
{
|
|
ups_sync();
|
|
ups_ident();
|
|
|
|
printf("Detected %s %s on %s\n", dstate_getinfo("ups.mfr"),
|
|
dstate_getinfo("ups.model"), device_path);
|
|
|
|
/* paranoia - cancel any shutdown that might already be running */
|
|
ser_send_pace(upsfd, UPSDELAY, "C\r");
|
|
|
|
upsh.instcmd = instcmd;
|
|
|
|
dstate_addcmd("test.battery.start");
|
|
dstate_addcmd("test.battery.stop");
|
|
}
|
|
|
|
static int ups_on_line(void)
|
|
{
|
|
int i, ret;
|
|
char temp[256], pstat[32];
|
|
|
|
for (i = 0; i < MAXTRIES; i++) {
|
|
ser_send_pace(upsfd, UPSDELAY, "\rQ1\r");
|
|
|
|
ret = ser_get_line(upsfd, temp, sizeof(temp), ENDCHAR, "",
|
|
SER_WAIT_SEC, SER_WAIT_USEC);
|
|
|
|
/* Q1 must return 46 bytes starting with a ( */
|
|
if ((ret > 0) && (temp[0] == '(') && (strlen(temp) == 46)) {
|
|
|
|
sscanf(temp, "%*s %*s %*s %*s %*s %*s %*s %s", pstat);
|
|
|
|
if (pstat[0] == '0')
|
|
return 1; /* on line */
|
|
|
|
return 0; /* on battery */
|
|
}
|
|
|
|
sleep(1);
|
|
}
|
|
|
|
upslogx(LOG_ERR, "Status read failed: assuming on battery");
|
|
|
|
return 0; /* on battery */
|
|
}
|
|
|
|
void upsdrv_shutdown(void)
|
|
{
|
|
printf("The UPS will shut down in approximately one minute.\n");
|
|
|
|
if (ups_on_line())
|
|
printf("The UPS will restart in about one minute.\n");
|
|
else
|
|
printf("The UPS will restart when power returns.\n");
|
|
|
|
ser_send_pace(upsfd, UPSDELAY, "S01R0001\r");
|
|
}
|
|
|
|
void upsdrv_updateinfo(void)
|
|
{
|
|
char involt[16], outvolt[16], loadpct[16], acfreq[16],
|
|
battvolt[16], upstemp[16], pstat[16], buf[256];
|
|
float bvoltp;
|
|
int ret;
|
|
|
|
ret = ser_send_pace(upsfd, UPSDELAY, "\rQ1\r");
|
|
|
|
if (ret < 1) {
|
|
ser_comm_fail("ser_send_pace failed");
|
|
dstate_datastale();
|
|
return;
|
|
}
|
|
|
|
/* these things need a long time to respond completely */
|
|
usleep(200000);
|
|
|
|
ret = ser_get_line(upsfd, buf, sizeof(buf), ENDCHAR, "",
|
|
SER_WAIT_SEC, SER_WAIT_USEC);
|
|
|
|
if (ret < 1) {
|
|
ser_comm_fail("Poll failed: %s", ret ? strerror(errno) : "timeout");
|
|
dstate_datastale();
|
|
return;
|
|
}
|
|
|
|
if (ret < 46) {
|
|
ser_comm_fail("Poll failed: short read (got %d bytes)", ret);
|
|
dstate_datastale();
|
|
return;
|
|
}
|
|
|
|
if (ret > 46) {
|
|
ser_comm_fail("Poll failed: response too long (got %d bytes)",
|
|
ret);
|
|
dstate_datastale();
|
|
return;
|
|
}
|
|
|
|
if (buf[0] != '(') {
|
|
ser_comm_fail("Poll failed: invalid start character (got %02x)",
|
|
buf[0]);
|
|
dstate_datastale();
|
|
return;
|
|
}
|
|
|
|
ser_comm_good();
|
|
|
|
sscanf(buf, "%*c%s %*s %s %s %s %s %s %s", involt, outvolt,
|
|
loadpct, acfreq, battvolt, upstemp, pstat);
|
|
|
|
/* Guesstimation of battery charge left (inaccurate) */
|
|
bvoltp = 100 * (atof(battvolt) - lowvolt) / (highvolt - lowvolt);
|
|
|
|
if (bvoltp > 100) {
|
|
bvoltp = 100;
|
|
}
|
|
|
|
dstate_setinfo("battery.voltage", "%.1f", battvoltmult * atof(battvolt));
|
|
dstate_setinfo("input.voltage", "%s", involt);
|
|
dstate_setinfo("output.voltage", "%s", outvolt);
|
|
dstate_setinfo("ups.load", "%s", loadpct);
|
|
dstate_setinfo("input.frequency", "%s", acfreq);
|
|
|
|
if(upstemp[0] != 'X') {
|
|
dstate_setinfo("ups.temperature", "%s", upstemp);
|
|
}
|
|
|
|
dstate_setinfo("battery.charge", "%02.1f", bvoltp);
|
|
|
|
status_init();
|
|
|
|
if (pstat[0] == '0') {
|
|
status_set("OL"); /* on line */
|
|
|
|
/* only allow these when OL since they're bogus when OB */
|
|
|
|
if (pstat[2] == (inverted_bypass_bit ? '0' : '1')) {
|
|
/* boost or trim in effect */
|
|
if (atof(involt) < atof(outvolt))
|
|
status_set("BOOST");
|
|
|
|
if (atof(involt) > atof(outvolt))
|
|
status_set("TRIM");
|
|
}
|
|
|
|
} else {
|
|
status_set("OB"); /* on battery */
|
|
}
|
|
|
|
if (pstat[1] == '1')
|
|
status_set("LB"); /* low battery */
|
|
|
|
status_commit();
|
|
dstate_dataok();
|
|
}
|
|
|
|
void upsdrv_help(void)
|
|
{
|
|
}
|
|
|
|
void upsdrv_makevartable(void)
|
|
{
|
|
addvar(VAR_VALUE, "nombattvolt", "Override nominal battery voltage");
|
|
addvar(VAR_VALUE, "battvoltmult", "Battery voltage multiplier");
|
|
addvar(VAR_VALUE, "ID", "Force UPS ID response string");
|
|
}
|
|
|
|
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);
|
|
}
|