[ale] ups.c

Bob Toxen bob at cavu.com
Wed Dec 18 20:52:37 EST 1996


Here is the program I wrote that will shut down my Linux box when my
UPS is running low on power.  I hate busy-loops but the trick that I've
used before, of getting the open() to hang until the UPS signals it is
running low on power doesn't work.  I'm sure I could tweak the tty driver.

In any case it uses virtually no CPU time so it doesn't matter.

This program needs to be run as root.  I installed it in /usr/local/bin
and start it with the following at the end of /etc/rc.d/rc.local:

	/usr/local/bin/ups&
	cp /home/bob/sounds/ups_up.au /dev/audio

ups_up.au says "UPS watch is up".
ups_low.au says "UPS battery is low.  System is shutting down now."

Enjoy,

Bob Toxen
bob at cavu.com
http://www.mindspring.com/~cavu
http://www.mindspring.com/~cavu/sunset.html   [Sunset Computer]
Fly-By-Day Consulting, Inc.
N79879 C-172 based PDK, Atlanta, GA
"Venimus, Vidimus, Dolavimus" (We came, we saw, we hacked)

--------------- ups.c follows -------------------------------
/*
 * ups.c: program to shut down a Linux System on an Intel 486, etc. when
 * the UPS indicates that the battery is running low during a power failure.
 * My UPS is an APC Back-UPS 1250.
 *
 * This program can be tweaked to work with any UNIX system, including most
 * System V systems, where init catches the SIGPWR signal and shuts the system
 * down.
 *
 * On other systems, the kill(1, SIGPWR) call can be replaced with
 * system("/etc/init 0") or similar.  Adjustments to the other notifica-
 * tions, such as the wall or email notification may be desirable.
 *
 * Copyright 1994, 1995, 1996 Robert M. Toxen.  All rights reserved, except
 * as noted.  Unlimited use on Linux and adhering to the GNU Copyleft
 * agreement is hereby granted so long as this copyright notice remains in
 * the source.  (Other low volume usage frequently will be granted on
 * request.)
 *
 * You may want to enhance to shut off the UPS after the system has shut down.
 * 
 * Connections between UPS and Computer (plugs sold at computer stores)
 * UPS 9-pin pin number				Linux serial DB25
 * --------------------				--------------------
 * 4  Ground			connect to	7      Ground
 * 5  2/5 minute warning (N/O)	connect to	8 & 20 CD & DTR
 *
 * On Linux (standard serial DTE line running the monitor program):
 * 
 *	pin	signal	color of my cable wires
 *	---	------	-----------------------
 *	7	GND	Black	(one switch contact)
 *	8	CD	Green	(other switch contact)
 *	20	DTR	Blue	(other switch contact)
 * 
 * Notes:
 * If your computer can shut down in about 90 seconds then set the UPS
 * to a 2 minute warning.  Otherwise set it to a 5 minute warning.
 * I estimate that this UPS will power my system for about  6 minutes.
 *
 * The Linux FAQs have an alternate way of doing an automatic
 * shutdown that is much more complicated.
 * 
 * For further information contact:
 * Bob Toxen
 * Fly-By-Day Consulting, Inc.
 * 4642 Bentley Place
 * Duluth, GA 30136 USA
 * bob at cavu.com
 * +1 770-662-8321
 */

#include <stdio.h>
#include <signal.h>
#include <string.h>
#include <sys/ioctl.h>
#include <termios.h>
#include <syslog.h>

				/*
				 * System must be up at least 5 minutes
				 * in order for shutdown sequence to begin.
				 *
				 * This is to guard against bugs, bad cables,
				 * etc. by preventing the program from being
				 * killed within 5 minutes in case of problems.
				 * Adjust for your system.
				 */
#define	MINUP		(5*60)
				/* Email path, for notification. */
#define	MAILPATH	"/usr/spool/mail/bob"
				/* Email user, for notification. */
#define	MAILUSER	"bob"
#define	AUINPUT		"/home/bob/sounds/ups_low.au"
#define	AUDEV		"/dev/audio"
#define	AUSIZE		(8*1024)
				/* Serial device to use: change yours! */
/* #define DEVICE		"/dev/ttyS3" */
#define DEVICE	"/dev/cua3"

char	*dev	= DEVICE;
int	active;
extern	int	errno;
extern	long	time();
char	aubuf[AUSIZE];

main(argc, argv)
int	argc;
char	**argv;
{
	int	fd;
	FILE	*fp;
	int	fdi;
	int	fdo;
	char	chr;
	int	i;
	char	*s;
	int	was;
	long	start;
	long	lowbat;
	long	delta;
	char	*when;
	char	*ctime();

	argv[0][0] = 'B';
	argc--;
	argv++;

	close(0);
	close(1);
	close(2);
	open("/dev/null", 2);
	dup(0);
	dup(0);

	signal(SIGCLD, SIG_IGN);
	signal(SIGHUP, SIG_IGN);
	signal(SIGINT, SIG_IGN);
	signal(SIGQUIT, SIG_IGN);
	signal(SIGTERM, SIG_IGN);

	openlog("ups", LOG_PID, LOG_DAEMON);
	syslog(LOG_INFO, "Starting");

	fd = open(dev, 0);
	if (fd < 0) {
		syslog(LOG_ALERT, "OPEN FAILED on %s: errno=%d", dev, errno);
		syslog(LOG_ALERT, "UPS exiting");
		exit(1);
	}
	errno = 0;
	if (ioctl(fd, TIOCMGET, &was) < 0) {
		syslog(LOG_ALERT, "initial TIOCMGET FAILED on %s: errno=%d",
		  dev, errno);
		syslog(LOG_ALERT, "UPS exiting");
		exit(2);
	}
	if (!(was & TIOCM_CAR)) {
		syslog(LOG_ALERT, "CD (Carrier Detect) is not initially on");
		syslog(LOG_ALERT, "UPS exiting");
		exit(3);
	}

	syslog(LOG_INFO, "Starting initial %d minute warmup", MINUP/60);

	start = time(0);
	for (;;) {
		if (!active && time(0) > start + MINUP) {
			syslog(LOG_INFO, "Ready for power failure");
			active = 1;
		}
		if (ioctl(fd, TIOCMGET, &i) < 0) {
			syslog(LOG_INFO, "ioctl failed: errno=%d", errno);
			break;
		}
#ifdef	notdef
		if (i != was)
			printf(
			  "RTS=%s DTR=%s CD=%s RI=%s DSR=%s CTS=%s\7\n",
			  (i & TIOCM_RTS) ? "YES" : "no ",
			  (i & TIOCM_DTR) ? "YES" : "no ",
			  (i & TIOCM_CAR) ? "YES" : "no ",
			  (i & TIOCM_RNG) ? "YES" : "no ",
			  (i & TIOCM_DSR) ? "YES" : "no ",
			  (i & TIOCM_CTS) ? "YES" : "no ");
#endif	/* notdef */
		if (!(i & TIOCM_CAR))
			break;
		was  = i;
		sleep(5);
	}

	lowbat = time(0);
	delta = lowbat - start;
	when = ctime(&lowbat);

	if (delta > 0 && delta < MINUP) {
		syslog(LOG_INFO, "UPS on battery and running low");
		syslog(LOG_INFO,  "The system has only been up %ld seconds", delta);
		syslog(LOG_INFO, 
		  "It must be up at least %d seconds to guard against bugs",
		  MINUP);
		syslog(LOG_INFO,
		   "We will assume a bug or cable problem and exit");

		syslog(LOG_ALERT, "UPS on battery and running low");
		syslog(LOG_ALERT, "The system has only been up %ld seconds", delta);
		syslog(LOG_ALERT, 
		  "It must be up at least %d seconds to guard against bugs",
		  MINUP);
		syslog(LOG_ALERT,
		  "We will assume a bug or cable problem and exit");
		exit(4);
	}

	syslog(LOG_INFO,  "UPS on battery and running low");
	syslog(LOG_ALERT, "UPS on battery and running low");
/*	kill(1, SIGPWR);		/* Possibly change for your system. */

	fp = fopen(MAILPATH, "a");
	if (fp) {
		fprintf(fp,
"From UPS %sFrom: UPS\nDate: %sTo: %s\nSubject: Power Failure\n\nLow battery during Power Failure @ %s\n",
		  when, when, MAILUSER, when);
		fclose(fp);
	}
	fdi = open(AUINPUT, 0);
	if (fdi < 0)
		goto didaudio;
	fdo = open(AUDEV, 1);
	if (fdo < 0) {
		close(fdi);
		goto didaudio;
	}
	while ((i = read(fdi, aubuf, AUSIZE)) > 0)
		write(fdo, aubuf, i);
	close(fdi);
	close(fdo);
didaudio:;
	sync();
	sleep(1);
	sync();

	system("echo 'POWER FAILURE: exit IMMEDIATELY' | /etc/wall&");
	sleep(10);
	s = strchr(when, '\n');
	if (s)
		*s = '\0';
	syslog(LOG_INFO, "Low battery during power failure at %s", when);
	syslog(LOG_INFO,  
	  "The system has been up for %ld seconds (%ld minutes) min=%ld",
	  delta, delta / 60L, MINUP);
	syslog(LOG_INFO,  "Shutting down gracefully");
	close(fd);

/*
 * Alternate shutdown methods if kill(1, SIGPWR) does not work on your system.
 */
	syslog(LOG_ALERT, "Starting shutdown sequence now");
	chdir("/");
/*	execl("/etc/init", "/etc/init", "0", 0); */
	execl("/sbin/shutdown", "shutdown", "-t", "10", "-r", "now",
	  "Power Failure And UPS Battery Is Now Low", 0);
	syslog(LOG_ALERT, "Cannot \"/sbin/shutdown -t 10 -r now reason\": exiting");

	exit(5);
}






More information about the Ale mailing list