/*
* manage APC (American Power Corporation) UPS, notably shutting down
* gracefully when power gets too low. enabled by "ups0=type=apc" in plan9.ini.
* N.B.: connection to UPS is assumed to be on eia1 (2nd serial port)
* at 2400 bps! due to hardcoded use of uartputc1, etc., probably can't
* override port with "port=0" in plan9.ini entry.
*/
#include "all.h"
#include "mem.h"
#include "io.h"
//#define DEBUG if(cons.flags&apc.flag)print
#define DEBUG print
enum {
OnBattery = 1,
LowBattery = 2,
Abnormal = 4,
Uartspeed = 2400,
Trustlowbatt = 0, // trust the low-battery bit? i don't.
};
enum { First, Reinit };
enum { Timedout = -1, Kicked = -2, Event = -3 }; /* apcgetc return */
typedef enum { General, Cycle } CmdType;
typedef enum { UpsOff, JustOff } Action; /* unimplemented to date */
typedef struct Capability {
char cmd;
int n;
struct Capability *next;
char *val[1];
} Capability;
static struct{
Lock;
int flag;
int gotreply;
int kicked;
int detected;
Rendez r;
Rendez doze;
struct {
Lock;
Rendez;
int count;
uchar buf[100];
uchar *in;
uchar *out;
} rxq;
struct {
Rendez;
int done;
CmdType cmdtype;
char cmd;
void *arg1, *arg2;
char resp[250]; /* response */
} user;
/* hardware info */
char model[50];
char fwrev[10]; /* firmware revision */
Capability *cap;
struct {
ulong bits;
ulong change;
} status;
/* battery info */
Timet battonticks; /* ticks with battery on */
Timet lastrepticks; /* ticks at last report */
ulong battpct; /* battery percent */
ulong battpctthen;
ulong trigger;
Action action;
int port;
} apc = {
.trigger = 1000,
.action = UpsOff,
};
extern void uartspecial1(int port, void (*rx)(int), int (*tx)(void), int baud);
extern void uartputc1(int c);
void
statuschange(int bit, int on)
{
if (((apc.status.bits&(bit)) != 0) != on) {
apc.status.change |= bit;
apc.status.bits = (apc.status.bits&~bit) | (on? bit: 0);
}
}
static int
kicked(void*)
{
return apc.kicked != 0;
}
static int
apctxint(void)
{
return -1;
}
static void
apcputc(char c)
{
uartputc1(c);
}
static void
apcputs(char *s)
{
int c;
while(c = *s++){
delay(10);
apcputc(c);
}
}
void
apcrxint(int c)
{
uchar *p;
ilock(&apc.rxq);
if (apc.rxq.count < sizeof(apc.rxq.buf)) {
p = apc.rxq.in;
*p++ = c;
if(p >= apc.rxq.buf + sizeof(apc.rxq.buf))
p = apc.rxq.buf;
apc.rxq.in = p;
apc.rxq.count++;
wakeup(&apc.rxq);
}
iunlock(&apc.rxq);
}
static int
done(void *p)
{
return *(int *)p != 0;
}
static int
eitherdone(void *)
{
return apc.rxq.count != 0 || apc.kicked;
}
enum{
Freport = 1<<0,
Floop = 1<<1,
Fbreak = 1<<2,
};
static struct{
uchar c;
uchar type;
uchar on;
uchar action;
char *msg;
} atab[] = {
'!', OnBattery, 1, Freport, 0,
'$', OnBattery, 0, Freport, 0,
'%', LowBattery, 1, Freport, 0,
'+', LowBattery, 0, Freport, 0,
'?', Abnormal, 1, Freport, 0,
'=', Abnormal, 0, Freport, 0,
'*', 0, 0, Floop, "apc: turning off\n",
'#', 0, 0, Floop, "apc: replace battery\n",
'&', 0, 0, Floop, "apc: check alarm register\n",
0x7c, 0, 0, Floop, "apc: eeprom modified\n",
};
int
apcgetc(int timo, int noevents)
{
int i, c;
loop:
if (timo < 0)
sleep(&apc.rxq, eitherdone, 0);
else
tsleep(&apc.rxq, eitherdone, 0, timo);
if (apc.kicked)
return Kicked;
ilock(&apc.rxq);
if (apc.rxq.count == 0) {
iunlock(&apc.rxq);
if (timo >= 0)
return Timedout;
goto loop;
}
c = *apc.rxq.out++;
if (apc.rxq.out >= apc.rxq.buf + sizeof(apc.rxq.buf))
apc.rxq.out = apc.rxq.buf;
apc.rxq.count--;
iunlock(&apc.rxq);
for(i = 0; i < nelem(atab); i++){
if(c != atab[i].c)
continue;
if(atab[i].action == Floop){
print(atab[i].msg);
goto loop;
};
if(atab[i].action == Freport){
statuschange(atab[i].action, atab[i].on);
print("apc: event %c\n", c);
if (noevents)
goto loop;
return Event;
}
}
return c;
}
char *
apcgets(char *buf, int len, int timo)
{
char *q;
int c;
q = buf;
while ((c = apcgetc(timo, 1)) >= 0 && c != '\r')
if (q < buf + len - 1)
*q++ = c;
if (c < 0)
return nil;
c = apcgetc(timo, 1);
if (c < 0 || c != '\n')
return nil;
*q = 0;
return buf;
}
int
apcexpect(char *s, int skiprubbish, int timo)
{
int first = 1, c;
while (*s) {
c = apcgetc(timo, 1);
if (c < 0)
return 0;
if (*s == c) {
s++;
first = 0;
continue;
}
if (!first)
return 0;
if (!skiprubbish)
return 0;
first = 0;
}
return 1;
}
int
apcattention(void)
{
apcputc('Y');
if (!apcexpect("SM\r\n", 1, 1000))
return 0;
apc.detected = 1;
return 1;
}
char *
apccmdstrresponse(char *cmd, char *buf, int len)
{
char *s;
apcputs(cmd);
if(s = apcgets(buf, len, 1000))
return s;
print("APC asleep...\n");
if (!apcattention())
return nil;
apcputs(cmd);
return apcgets(buf, len, 1000);
}
char *
apccmdresponse(char cmd, char *buf, int len)
{
char cmdstr[2];
cmdstr[0] = cmd;
cmdstr[1] = 0;
return apccmdstrresponse(cmdstr, buf, len);
}
static void
parsecap(char *capstr, char locale)
{
char cmd, lc, c;
int n, el, i, j, p;
while (*capstr) {
char *s;
Capability *cap;
cmd = *capstr++;
lc = *capstr++;
n = *capstr++ - '0';
el = *capstr++ - '0';
p = lc == '4' || lc == locale;
if (p) {
cap = ialloc(sizeof *cap + sizeof s*(n - 1), 0);
cap->cmd = cmd;
cap->n = n;
s = ialloc(n*(el + 1), 0);
for (i = 0; i < n; i++) {
cap->val[i] = s + i*(el + 1);
cap->val[i][el] = 0;
}
} else
cap = nil;
for (i = 0; i < n; i++)
for (j = 0; j < el; j++) {
c = *capstr++;
if (p)
cap->val[i][j] = c;
}
if (p) {
cap->next = apc.cap;
apc.cap = cap;
}
}
}
static char *
cyclecmd(Capability *cap, int i)
{
char *s, resp[10];
for (;;) {
s = apccmdresponse(cap->cmd, resp, sizeof resp);
if (s == nil || strcmp(resp, cap->val[i]) == 0)
break;
s = apccmdresponse('-', resp, sizeof resp);
if (s == nil)
break;
}
return s;
}
static ulong
getfloat(char *p, int dp)
{
ulong total;
int afterdp = -1;
total = 0;
for (; *p; p++)
if (*p == '.')
afterdp = 0;
else {
total = total*10 + *p - '0';
if (afterdp >= 0)
afterdp++;
}
if (afterdp < 0)
afterdp = 0;
while (afterdp > dp + 1) {
afterdp--;
total /= 10;
}
if (afterdp > dp) {
afterdp--;
total = (total + 5) / 10;
}
while (dp > afterdp) {
afterdp++;
total *= 10;
}
return total;
}
static int
apcgetstatus(void)
{
char resp[10];
ulong status, change;
do {
change = apc.status.change;
if (apccmdresponse('Q', resp, sizeof(resp)) == nil)
return 0;
} while (apc.status.change != change);
status = strtoul(resp, 0, 16);
if (status&(1 << 3) && apc.status.bits&OnBattery) { /* online? */
apc.status.bits &= ~OnBattery;
apc.status.change |= OnBattery;
}
if (status&(1 << 4) && apc.status.bits&OnBattery) { /* on battery */
// apc.status.bits |= OnBattery;
apc.status.change |= OnBattery;
}
if (((status&(1 << 6)) != 0) != ((apc.status.bits&LowBattery) != 0)) {
/* low battery */
apc.status.bits ^= LowBattery;
apc.status.change |= LowBattery;
}
if (apccmdresponse('f', resp, sizeof(resp)) == nil)
return 0;
apc.battpct = getfloat(resp, 1);
return 1;
}
/*
* shutdown the file server gracefully.
*/
static void
apcshuffle(char *why)
{
char resp[10];
print("Shutting down due to %s\n", why);
wlock(&mainlock); /* don't process incoming requests from net */
sync("powerfail");
apccmdstrresponse("@000", resp, sizeof(resp));
print("APC responded: '%s'\n", resp);
print("File server is now idling.\n");
delay(2000);
splhi();
for (;;)
idle();
}
static void
apckick(void)
{
if (apc.detected) { /* don't blather once per minute */
print("No APC ups detected\n");
apc.detected = 0;
}
apc.kicked = 0;
tsleep(&apc.doze, kicked, 0, 1 * 60 * 1000);
}
static void
apcsetup(int reinit)
{
Capability *cap;
int i;
if (reinit)
apckick();
for(;;){
while (!apcattention())
apckick();
apcputc(1);
apcgets(apc.model, sizeof apc.model, -1);
print("APC UPS model: %s\n", apc.model);
apcputc('b');
apcgets(apc.fwrev, sizeof apc.fwrev, -1);
print("Firmware revision: %s\n", apc.fwrev);
apcputc('');
apcgets(apc.user.resp, sizeof apc.user.resp, -1);
parsecap(apc.user.resp, apc.fwrev[strlen(apc.fwrev) - 1]);
for (cap = apc.cap; cap; cap = cap->next) {
print("%c %d", cap->cmd, cap->n);
for (i = 0; i < cap->n; i++)
print(" %s", cap->val[i]);
print("\n");
}
apc.status.change = 0;
if (!apcgetstatus()) {
apckick();
continue;
}
}
}
static void
apcbatton(void)
{
Timet now, nextreport, remaining;
now = MACHP(0)->ticks;
if (apc.status.change & OnBattery) {
apc.lastrepticks = apc.battonticks = nextreport = now;
apc.battpctthen = apc.battpct;
} else
nextreport = apc.lastrepticks + MS2TK(30 * 1000);
if (now - nextreport >= 0) {
print("apc: on battery %lud seconds (%lud.%lud%%)",
TK2SEC(now - apc.battonticks),
apc.battpct / 10, apc.battpct % 10);
if (apc.battpct < apc.battpctthen - 10) {
remaining = ((apc.battpct-apc.trigger)*TK2SEC(now - apc.battonticks))/(apc.battpctthen-apc.battpct);
print(" - estimated %lud seconds left", remaining);
}
print("\n");
apc.lastrepticks = now;
}
if (apc.battpct <= apc.trigger)
apcshuffle("battery percent too low");
if (Trustlowbatt && apc.status.bits & LowBattery)
apcshuffle("low battery indicator");
}
void
apctask(void)
{
tsleep(&apc.doze, kicked, 0, 10 * 1000);
/* set up the serial port to the UPS */
DEBUG("apc: running: port %d trigger below %lud%% action %s\n",
apc.port, apc.trigger / 10,
(apc.action == UpsOff? "ups off": "just off"));
apc.rxq.in = apc.rxq.out = apc.rxq.buf;
uartspecial1(apc.port, apcrxint, apctxint, Uartspeed);
/*
* pretend we've been talking to it so we'll get an
* error message if it's not there.
*/
apc.detected = 1;
apcsetup(First);
for (;;) {
char *s;
int c;
if ((apc.status.bits & OnBattery))
apcbatton();
apc.kicked = 0;
apc.status.change = 0;
c = apcgetc(10 * 1000, 0);
if (c == Timedout || c == Event) {
if (!apcgetstatus())
apcsetup(Reinit);
} else if (c == Kicked) {
apc.kicked = 0;
switch (apc.user.cmdtype) {
case General:
s = apccmdresponse(apc.user.cmd,
apc.user.resp, sizeof apc.user.resp);
break;
case Cycle:
s = cyclecmd((Capability *)apc.user.arg1,
(int)apc.user.arg2);
break;
default:
s = nil;
break;
}
apc.user.done = 1;
wakeup(&apc.user);
if (s == nil)
apcsetup(Reinit);
} else
print("apc: unexpected character '%c' (0x%.2ux)\n",
c, c);
}
}
static void
enquiry(CmdType t, char c, void *arg1, void *arg2)
{
apc.user.cmdtype = t;
apc.user.cmd = c;
apc.user.arg1 = arg1;
apc.user.arg2 = arg2;
apc.user.done = 0;
apc.kicked = 1;
apc.user.resp[0] = 0;
wakeup(&apc.rxq);
tsleep(&apc.user, done, &apc.user.done, 15*1000);
print("'%s'\n", apc.user.resp);
apc.user.resp[0] = 0;
}
static struct {
char ch;
char *cmd;
} generalenquiries[] = {
{ '', "capabilities" },
{ 'B', "batteryvolts" },
{ 'C', "temperature" },
// { 'E', "selftestinterval" },
// { 'F', "frequency" },
{ 'L', "lineinvolts" },
// { 'M', "maxlineinvolts" },
// { 'N', "minlineinvolts" },
// { 'O', "lineoutvolts" },
{ 'P', "powerload" },
{ 'Q', "status" },
{ 'V', "firmware" },
{ 'X', "selftestresults" },
// { 'a', "protocol" },
// { 'b', "localid" },
{ 'e', "returnthresh" },
{ 'g', "nominalbatteryvolts" },
{ 'f', "battpct" },
// { 'h', "humidity" },
{ 'i', "contacts" },
{ 'j', "runtime" },
{ 'k', "alarmdelay" },
{ 'l', "lowtransfervolts" },
// { 'm', "manufactured" },
{ 'n', "serial" },
{ 'o', "onbatteryvolts" },
{ 'p', "grace" },
{ 'q', "lowbatterywarntime" },
{ 'r', "wakeupdelay" },
{ 's', "sensitivity" },
{ 'u', "uppertransfervolts" },
{ 'x', "lastbatterychange" },
// { 'y', "copyright" },
// { '~', "register1" },
// { ''', "register2" },
// { '7', "switches" },
// { '8', "register3" },
{ '9', "linequality" },
{ '>', "batterypacks" },
{ '-', "cycle" },
};
int
vaguelyequal(char *a, char *b)
{
return strcmp(a, b) == 0;
}
static void
cycle(char *name, char *val)
{
int g, i;
Capability *cap;
if (strcmp(name, "trigger") == 0) {
apc.trigger = getfloat(val, 1);
return;
}
/* convert name to enquiry */
for (g = 0; g < nelem(generalenquiries); g++)
if (strcmp(name, generalenquiries[g].cmd) == 0)
goto f1;
print("no such parameter '%s'\n", name);
return;
f1:
/* match enquiry to capability */
for (cap = apc.cap; cap; cap = cap->next)
if (cap->cmd == generalenquiries[g].ch)
goto f2;
print("parameter %s cannot be set\n", name);
return;
f2:
/* search capability's legal values */
for (i = 0; i < cap->n; i++)
if (vaguelyequal(cap->val[i], val))
goto f3;
print("%s: illegal value %s; try one of [", name, val);
for (i = 0; i < cap->n; i++) {
if (i > 0)
print(" ");
print("%s", cap->val[i]);
}
print("]\n");
return;
f3:
enquiry(Cycle, cap->cmd, cap, (void *)i);
}
void
cmd_apc(int argc, char *argv[])
{
int i, x;
if(argc <= 1) {
print("apc kick -- play now\n");
print("apc set var val -- set var to val\n");
print("apc enquiry... -- query the ups\n");
return;
}
for (i = 1; i < argc; i++) {
if(strcmp(argv[i], "kick") == 0) {
apc.kicked = 1;
wakeup(&apc.doze);
continue;
}
if(strcmp(argv[i], "set") == 0) {
if (argc - i >= 3)
cycle(argv[i + 1], argv[i + 2]);
i += 2;
continue;
}
for (x = 0; x < nelem(generalenquiries); x++)
if (strcmp(argv[i], generalenquiries[x].cmd) == 0)
break;
if (x < nelem(generalenquiries))
enquiry(General, generalenquiries[x].ch, nil, nil);
else {
print("no such parameter '%s'\n", argv[i]);
return;
}
}
}
void
apcinit(void)
{
ISAConf isa;
int o;
memset(&isa, 0, sizeof isa);
isa.port = 1;
if (!isaconfig("ups", 0, &isa) || strcmp(isa.type, "apc") != 0)
return;
cmd_install("apc", "subcommand -- apc ups driver", cmd_apc);
apc.flag = flag_install("apc", "-- verbose");
for (o = 0; o < isa.nopt; o++)
if (cistrncmp(isa.opt[o], "trigger=", 8) == 0)
apc.trigger = strtoul(isa.opt[o] + 8, 0, 0) * 10;
else if (cistrncmp(isa.opt[o], "action=", 7) == 0) {
if (strcmp(isa.opt[o] + 8, "off") == 0)
apc.action = JustOff;
}
apc.port = isa.port;
/*
* it's a little early to be starting this, before config mode is
* even started.
*/
print("apc...\n");
userinit(apctask, 0, "apc");
}
|