521 lines
14 KiB
C
521 lines
14 KiB
C
/*
|
|
bestuferrups.c - model specific routines for Best Power Micro-Ferrups
|
|
|
|
This module is a 40% rewritten mangle of the bestfort module by
|
|
Grant, which is a 75% rewritten mangle of the bestups module by
|
|
Russell. It has no test battery command since my ME3100 does this
|
|
by itself. (same as Grant's driver in this respect)
|
|
|
|
Support for model RE added by Tim Thompson (7/22/04)
|
|
|
|
Copyright (C) 2002 Andreas Wrede <andreas@planix.com>
|
|
Copyright (C) 2000 John Stone <johns@megapixel.com>
|
|
Copyright (C) 2000 Grant Taylor <gtaylor@picante.com>
|
|
Copyright (C) 1999 Russell Kroll <rkroll@exploits.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 Ferrups Series ME/RE/MD driver"
|
|
#define DRIVER_VERSION "0.04"
|
|
|
|
/* driver description structure */
|
|
upsdrv_info_t upsdrv_info = {
|
|
DRIVER_NAME,
|
|
DRIVER_VERSION,
|
|
"Andreas Wrede <andreas@planix.com>\n" \
|
|
"John Stone <johns@megapixel.com>\n" \
|
|
"Grant Taylor <gtaylor@picante.com>\n" \
|
|
"Russell Kroll <rkroll@exploits.org>\n" \
|
|
"Tim Thompson",
|
|
DRV_BETA, /* FIXME: STABLE? */
|
|
{ NULL }
|
|
};
|
|
|
|
#define ENDCHAR '\r'
|
|
#define IGNCHARS "\012"
|
|
|
|
/* UPS Model Codes */
|
|
#define UNKNOWN 100
|
|
#define ME3100 200
|
|
#define MD1KVA 300 /* Software version P5.05 dated 05/18/89 */
|
|
#define RE1800 400
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
static int debugging = 0;
|
|
|
|
/* Blob of UPS configuration data from the formatconfig string */
|
|
static struct {
|
|
int valid; /* set to 1 when this is filled in */
|
|
|
|
float idealbvolts; /* various interestin battery voltages */
|
|
float fullvolts;
|
|
float emptyvolts;
|
|
int va; /* capacity of UPS in Volt-Amps */
|
|
int watts; /* capacity of UPS in watts */
|
|
int model; /* enumerated model type */
|
|
} fc;
|
|
|
|
|
|
/* Forward decls */
|
|
|
|
/* Set up all the funky shared memory stuff used to communicate with upsd */
|
|
void upsdrv_initinfo (void)
|
|
{
|
|
/* now set up room for all future variables that are supported */
|
|
|
|
dstate_setinfo("ups.mfr", "%s", "Best Power");
|
|
switch(fc.model)
|
|
{
|
|
case ME3100:
|
|
dstate_setinfo("ups.model", "Micro Ferrups (ME) %d", fc.va);
|
|
break;
|
|
case MD1KVA:
|
|
dstate_setinfo("ups.model", "Micro Ferrups (MD) %d", fc.va);
|
|
break;
|
|
case RE1800:
|
|
dstate_setinfo("ups.model", "Micro Ferrups (RE) %d", fc.va);
|
|
break;
|
|
default:
|
|
fatalx(EXIT_FAILURE, "UPS model not matched!"); /* Will never get here, upsdrv_initups() will catch */
|
|
}
|
|
fprintf(stderr, "Best Power %s detected\n",
|
|
dstate_getinfo("ups.model"));
|
|
fprintf(stderr, "Battery voltages %5.1f nominal, %5.1f full, %5.1f empty\n",
|
|
fc.idealbvolts,
|
|
fc.fullvolts,
|
|
fc.emptyvolts);
|
|
}
|
|
|
|
|
|
/* Debugging display from kermit:
|
|
|
|
----------------------------------------------------
|
|
time^M^M^JFeb 20, 22:13:32^M^J^M^J=>id^M^JUnit ID "ME3.1K12345"^M^J^M^J=>
|
|
----------------------------------------------------
|
|
*/
|
|
|
|
static ssize_t execute(const char *cmd, char *result, size_t resultsize)
|
|
{
|
|
ssize_t ret;
|
|
char buf[256];
|
|
|
|
ser_send(upsfd, "%s", cmd);
|
|
ser_get_line(upsfd, buf, sizeof(buf), '\012', "", 3, 0);
|
|
ret = ser_get_line(upsfd, result, resultsize, '\015', "\012", 3, 0);
|
|
ser_get_line(upsfd, buf, sizeof(buf), '>', "", 3, 0);
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
void upsdrv_updateinfo(void)
|
|
{
|
|
char fstring[512];
|
|
|
|
if (! fc.valid) {
|
|
fprintf(stderr,
|
|
"upsupdate run before ups_ident() read ups config\n");
|
|
#ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE
|
|
#pragma GCC diagnostic push
|
|
#endif
|
|
#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE
|
|
#pragma GCC diagnostic ignored "-Wunreachable-code"
|
|
#endif
|
|
#ifdef __clang__
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wunreachable-code"
|
|
#endif
|
|
/* NOTE: This assert() always fails because of "0":
|
|
* error: will never be executed [-Werror,-Wunreachable-code]
|
|
* ((0) ? (void) (0) : __assert_fail ("0", "bestuferrups.c", 138, __PRETTY_FUNCTION__));
|
|
* ^
|
|
*/
|
|
assert(0);
|
|
#ifdef __clang__
|
|
#pragma clang diagnostic pop
|
|
#endif
|
|
#ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE
|
|
#pragma GCC diagnostic pop
|
|
#endif
|
|
}
|
|
|
|
if (execute("f\r", fstring, sizeof(fstring)) > 0) {
|
|
int inverter=0, charger=0, vin=0, vout=0, btimeleft=0, linestat=0,
|
|
alstat=0, vaout=0;
|
|
double ampsout=0.0, vbatt=0.0, battpercent=0.0, loadpercent=0.0,
|
|
hstemp=0.0, acfreq=0.0, ambtemp=0.0;
|
|
char tmp[16];
|
|
|
|
/* Inverter status: 0=off 1=on */
|
|
memcpy(tmp, fstring+16, 2);
|
|
tmp[2] = '\0';
|
|
inverter = atoi(tmp);
|
|
|
|
/* Charger status: 0=off 1=on */
|
|
memcpy(tmp, fstring+18, 2);
|
|
tmp[2] = '\0';
|
|
charger = atoi(tmp);
|
|
|
|
/* Input Voltage. integer number */
|
|
memcpy(tmp, fstring+24, 4);
|
|
tmp[4] = '\0';
|
|
vin = atoi(tmp);
|
|
|
|
/* Output Voltage. integer number */
|
|
memcpy(tmp, fstring+28, 4);
|
|
tmp[4] = '\0';
|
|
vout = atoi(tmp);
|
|
|
|
/* Iout: int times 10 */
|
|
memcpy(tmp, fstring+36, 4);
|
|
tmp[4] = '\0';
|
|
ampsout = ((double)(atoi(tmp)) / 10.0);
|
|
|
|
/* Battery voltage: int times 10 */
|
|
memcpy(tmp, fstring+50, 4);
|
|
tmp[4] = '\0';
|
|
vbatt = ((double)(atoi(tmp)) / 10.0);
|
|
|
|
/* Volt-amps out: int */
|
|
memcpy(tmp, fstring+40, 6);
|
|
tmp[6] = '\0';
|
|
vaout = atoi(tmp);
|
|
|
|
/* Line status. Bitmask */
|
|
memcpy(tmp, fstring+72, 2);
|
|
tmp[2] = '\0';
|
|
linestat = atoi(tmp);
|
|
|
|
/* Alarm status reg 1. Bitmask */
|
|
memcpy(tmp, fstring+20, 2);
|
|
tmp[2] = '\0';
|
|
alstat = atoi(tmp);
|
|
|
|
/* Alarm status reg 2. Bitmask */
|
|
memcpy(tmp, fstring+22, 2);
|
|
tmp[2] = '\0';
|
|
alstat = alstat | (atoi(tmp) << 8);
|
|
|
|
/* AC line frequency */
|
|
memcpy(tmp, fstring+54, 4);
|
|
tmp[4]= '\0';
|
|
acfreq = ((double)(atoi(tmp)) / 100.0);
|
|
|
|
/* Runtime remaining */
|
|
memcpy(tmp, fstring+58, 4);
|
|
tmp[4]= '\0';
|
|
btimeleft = atoi(tmp);
|
|
|
|
/* UPS Temperature */
|
|
memcpy(tmp, fstring+62, 4);
|
|
tmp[4]= '\0';
|
|
ambtemp = (double)(atoi(tmp));
|
|
|
|
/* Percent Load */
|
|
switch(fc.model)
|
|
{
|
|
case ME3100:
|
|
if (execute("d 16\r", fstring, sizeof(fstring)) > 0) {
|
|
int l;
|
|
sscanf(fstring, "16 FullLoad%% %d", &l);
|
|
loadpercent = (double) l;
|
|
}
|
|
break;
|
|
case RE1800:
|
|
if (execute("d 16\r", fstring, sizeof(fstring)) > 0) {
|
|
int l;
|
|
sscanf(fstring, "16 FullLoad%% %d", &l);
|
|
loadpercent = (double) l;
|
|
}
|
|
if (execute("d 12\r", fstring, sizeof(fstring)) > 0) {
|
|
int l;
|
|
sscanf(fstring, "12 HS Temp %dC", &l);
|
|
hstemp = (double) l;
|
|
}
|
|
break;
|
|
case MD1KVA:
|
|
if (execute("d 22\r", fstring, sizeof(fstring)) > 0) {
|
|
int l;
|
|
sscanf(fstring, "22 FullLoad%% %d", &l);
|
|
loadpercent = (double) l;
|
|
}
|
|
break;
|
|
default: /* Will never happen, caught in upsdrv_initups() */
|
|
fatalx(EXIT_FAILURE, "Unknown model in upsdrv_updateinfo()");
|
|
}
|
|
/* Compute battery percent left based on battery voltages. */
|
|
battpercent = ((vbatt - fc.emptyvolts)
|
|
/ (fc.fullvolts - fc.emptyvolts) * 100.0);
|
|
if (battpercent < 0.0)
|
|
battpercent = 0.0;
|
|
else if (battpercent > 100.0)
|
|
battpercent = 100.0;
|
|
|
|
/* Compute status string */
|
|
{
|
|
int lowbatt, overload, replacebatt, boosting, trimming;
|
|
|
|
lowbatt = alstat & (1<<1);
|
|
overload = alstat & (1<<6);
|
|
replacebatt = alstat & (1<<10);
|
|
boosting = inverter && (linestat & (1<<2)) && (vin < 115);
|
|
trimming = inverter && (linestat & (1<<2)) && (vin > 115);
|
|
|
|
status_init();
|
|
|
|
if (inverter)
|
|
status_set("OB");
|
|
else
|
|
status_set("OL");
|
|
|
|
if (lowbatt)
|
|
status_set("LB");
|
|
|
|
if (trimming)
|
|
status_set("TRIM");
|
|
|
|
if (boosting)
|
|
status_set("BOOST");
|
|
|
|
if (replacebatt)
|
|
status_set("RB");
|
|
|
|
if (overload)
|
|
status_set("OVER");
|
|
|
|
status_commit();
|
|
}
|
|
|
|
/* FIXME: change to upsdebugx() and friends */
|
|
if (debugging) {
|
|
fprintf(stderr,
|
|
"Poll: inverter %d charger %d vin %d vout %d vaout %d btimeleft %d\n",
|
|
inverter, charger, vin, vout, vaout, btimeleft);
|
|
fprintf(stderr,
|
|
" ampsout %5.1f vbatt %5.1f batpcnt %5.1f loadpcnt %5.1f upstemp %5.1f acfreq %5.2f ambtemp %5.1f\n",
|
|
ampsout, vbatt, battpercent, loadpercent, hstemp, acfreq, ambtemp);
|
|
|
|
}
|
|
|
|
/* Stuff information into info structures */
|
|
|
|
dstate_setinfo("input.voltage", "%05.1f", (double)vin);
|
|
dstate_setinfo("output.voltage", "%05.1f", (double)vout);
|
|
dstate_setinfo("battery.charge", "%02.1f", battpercent);
|
|
dstate_setinfo("ups.load", "%02.1f", loadpercent);
|
|
dstate_setinfo("battery.voltage", "%02.1f", vbatt);
|
|
dstate_setinfo("input.frequency", "%05.2f", (double)acfreq);
|
|
dstate_setinfo("ups.temperature", "%05.1f", (double)hstemp);
|
|
dstate_setinfo("battery.runtime", "%d", btimeleft);
|
|
dstate_setinfo("ambient.temperature", "%05.1f", (double)ambtemp);
|
|
|
|
dstate_dataok();
|
|
/* Tim: With out this return, it always falls over to the
|
|
datastate() at the end of the function */
|
|
return;
|
|
} else {
|
|
|
|
dstate_datastale();
|
|
|
|
} /* if (execute("f\r", fstring, sizeof(fstring)) > 0) */
|
|
|
|
dstate_datastale();
|
|
return;
|
|
}
|
|
|
|
|
|
static void ups_sync(void)
|
|
{
|
|
char buf[256];
|
|
|
|
printf ("Syncing: ");
|
|
fflush (stdout);
|
|
|
|
/* A bit better sanity might be good here. As is, we expect the
|
|
human to observe the time being totally not a time. */
|
|
|
|
if (execute("time\r", buf, sizeof(buf)) > 0) {
|
|
fprintf(stderr, "UPS Time: %s\n", buf);
|
|
} else {
|
|
fatalx(EXIT_FAILURE, "Error connecting to UPS");
|
|
}
|
|
}
|
|
|
|
/* power down the attached load immediately */
|
|
void upsdrv_shutdown(void)
|
|
{
|
|
/* NB: hard-wired password */
|
|
ser_send(upsfd, "pw377\r");
|
|
ser_send(upsfd, "off 1 a\r"); /* power off in 1 second and restart when line power returns */
|
|
}
|
|
|
|
/* list flags and values that you want to receive via -x */
|
|
void upsdrv_makevartable(void)
|
|
{
|
|
}
|
|
|
|
void upsdrv_help(void)
|
|
{
|
|
}
|
|
|
|
static void sync_serial(void) {
|
|
char buffer[10];
|
|
|
|
ser_send(upsfd, "\r");
|
|
ser_get_line(upsfd, buffer, sizeof(buffer), '\r', "\012", 3, 0);
|
|
ser_get_line(upsfd, buffer, sizeof(buffer), ENDCHAR, IGNCHARS, 3, 0);
|
|
|
|
while (ser_get_line(upsfd, buffer, sizeof(buffer), '>', "\012", 3, 0) <= 0) {
|
|
ser_send(upsfd, "\r");
|
|
}
|
|
}
|
|
|
|
/* Begin code stolen from bestups.c */
|
|
static void setup_serial(void)
|
|
{
|
|
struct termios tio;
|
|
|
|
if (tcgetattr(upsfd, &tio) == -1)
|
|
fatal_with_errno(EXIT_FAILURE, "tcgetattr");
|
|
|
|
tio.c_iflag = IXON | IXOFF;
|
|
tio.c_oflag = 0;
|
|
tio.c_cflag = (CS8 | CREAD | HUPCL | CLOCAL);
|
|
tio.c_lflag = 0;
|
|
tio.c_cc[VMIN] = 1;
|
|
tio.c_cc[VTIME] = 0;
|
|
|
|
#ifdef HAVE_CFSETISPEED
|
|
cfsetispeed(&tio, B1200); /* baud change here */
|
|
cfsetospeed(&tio, B1200);
|
|
#else
|
|
#error This system lacks cfsetispeed() and has no other means to set the speed
|
|
#endif
|
|
|
|
if (tcsetattr(upsfd, TCSANOW, &tio) == -1)
|
|
fatal_with_errno(EXIT_FAILURE, "tcsetattr");
|
|
/* end code stolen from bestups.c */
|
|
|
|
sync_serial();
|
|
}
|
|
|
|
|
|
void upsdrv_initups ()
|
|
{
|
|
char temp[256], fcstring[512];
|
|
|
|
upsfd = ser_open(device_path);
|
|
ser_set_speed(upsfd, device_path, B1200);
|
|
setup_serial();
|
|
ups_sync();
|
|
|
|
fc.model = UNKNOWN;
|
|
/* Obtain Model */
|
|
if (execute("id\r", fcstring, sizeof(fcstring)) < 1) {
|
|
fatalx(EXIT_FAILURE, "Failed execute in ups_ident()");
|
|
}
|
|
|
|
/* response is a one-line packed string starting with $ */
|
|
if (memcmp(fcstring, "Unit", 4)) {
|
|
fatalx(EXIT_FAILURE,
|
|
"Bad response from formatconfig command in ups_ident()\n"
|
|
"id: %s\n", fcstring
|
|
);
|
|
}
|
|
|
|
/* FIXME: upsdebugx() */
|
|
if (debugging)
|
|
fprintf(stderr, "id: %s\n", fcstring);
|
|
|
|
/* chars 4:2 are a two-digit ascii hex enumerated model code */
|
|
memcpy(temp, fcstring+9, 2);
|
|
temp[2] = '\0';
|
|
|
|
if (memcmp(temp, "ME", 2) == 0) {
|
|
fc.model = ME3100;
|
|
} else if ((memcmp(temp, "RE", 2) == 0)) {
|
|
fc.model = RE1800;
|
|
} else if (memcmp(temp, "C1", 2) == 0) {
|
|
/* Better way to identify unit is using "d 15\r", which results in
|
|
"15 M# MD1KVA", "id\r" yields "Unit ID "C1K03588"" */
|
|
fc.model = MD1KVA;
|
|
}
|
|
|
|
switch(fc.model) {
|
|
case ME3100:
|
|
fc.va = 3100;
|
|
fc.watts = 2200;
|
|
/* determine shutdown battery voltage */
|
|
if (execute("d 29\r", fcstring, sizeof(fcstring)) > 0) {
|
|
sscanf(fcstring, "29 LowBat %f", &fc.emptyvolts);
|
|
}
|
|
/* determine fully charged battery voltage */
|
|
if (execute("d 31\r", fcstring, sizeof(fcstring)) > 0) {
|
|
sscanf(fcstring, "31 HiBatt %f", &fc.fullvolts);
|
|
}
|
|
fc.fullvolts = 54.20;
|
|
/* determine "ideal" voltage by a guess */
|
|
fc.idealbvolts = ((fc.fullvolts - fc.emptyvolts) * 0.7) + fc.emptyvolts;
|
|
break;
|
|
case RE1800:
|
|
fc.va = 1800;
|
|
fc.watts = 1200;
|
|
/* determine shutdown battery voltage */
|
|
if (execute("d 29\r", fcstring, sizeof(fcstring)) > 0) {
|
|
sscanf(fcstring, "29 LowBat %f", &fc.emptyvolts);
|
|
}
|
|
/* determine fully charged battery voltage */
|
|
if (execute("d 31\r", fcstring, sizeof(fcstring)) > 0) {
|
|
sscanf(fcstring, "31 HiBatt %f", &fc.fullvolts);
|
|
}
|
|
fc.fullvolts = 54.20;
|
|
/* determine "ideal" voltage by a guess */
|
|
fc.idealbvolts = ((fc.fullvolts - fc.emptyvolts) * 0.7) + fc.emptyvolts;
|
|
break;
|
|
case MD1KVA:
|
|
fc.va = 1100;
|
|
fc.watts = 770; /* Approximate, based on 0.7 power factor */
|
|
/* determine shutdown battery voltage */
|
|
if (execute("d 27\r", fcstring, sizeof(fcstring)) > 0) {
|
|
sscanf(fcstring, "27 LowBatt %f", &fc.emptyvolts);
|
|
}
|
|
/* determine fully charged battery voltage */
|
|
if (execute("d 28\r", fcstring, sizeof(fcstring)) > 0) {
|
|
sscanf(fcstring, "28 Hi Batt %f", &fc.fullvolts);
|
|
}
|
|
fc.fullvolts = 13.70;
|
|
/* determine "ideal" voltage by a guess */
|
|
fc.idealbvolts = ((fc.fullvolts - fc.emptyvolts) * 0.7) + fc.emptyvolts;
|
|
break;
|
|
default:
|
|
fatalx(EXIT_FAILURE, "Unknown model %s in ups_ident()", temp);
|
|
}
|
|
|
|
fc.valid = 1;
|
|
return;
|
|
}
|
|
|
|
void upsdrv_cleanup(void)
|
|
{
|
|
ser_close(upsfd, device_path);
|
|
}
|