Plan 9 from Bell Labs’s /usr/web/sources/contrib/quanstro/root/sys/src/fs/dev/apc.c

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


/*
 * 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");
}

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to webmaster@9p.io.