Files
upsd/upsd.c
T
2010-09-30 17:26:20 -07:00

325 lines
6.4 KiB
C

/*
* Copyright (c) 2010, Stephen Waits <steve@waits.net>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <sys/types.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
#include <syslog.h>
#define POLL_INTERVAL (1) // poll the ups this often (seconds)
#define KILL_TIME (10) // ups needs this much time with shutdown signal
#define SHUTDOWN_TIME (300) // halt system after this much time
#define DEBOUNCE_TIME (5) // fluctuations less than this time don't count
#define SHUTDOWN_CMD "/sbin/halt -p"
#define INIT_BITS TIOCM_RTS
#define KILL_BITS TIOCM_DTR
#define BATT_GOOD_BITS TIOCM_CD
#define POWER_GOOD_BITS TIOCM_CTS
int fd = -1;
enum
{
U_OK,
U_LOW,
U_FAIL
};
void warn(int secondsleft)
{
//printf("warn\n");
syslog(LOG_ALERT,"power failure. shutting down in %d seconds.",secondsleft);
}
void lowmessage()
{
//printf("low\n");
syslog(LOG_ALERT,"ups battery low.");
}
void setline(int kill)
{
int flags;
// Get the current line bits
ioctl(fd, TIOCMGET, &flags);
// and set ours
flags |= INIT_BITS;
if ( kill )
{
syslog(LOG_ALERT,"ups powerdown enabled.");
flags |= KILL_BITS;
}
else
{
//syslog(LOG_ALERT,"reset ups. ups powerdown disabled.");
flags &= ~KILL_BITS;
}
ioctl(fd, TIOCMSET, &flags);
}
void init()
{
//printf("init\n");
setline(0);
}
void killups()
{
//printf("killups\n");
setline(1);
}
void shutdown()
{
//printf("shutdown\n");
syslog(LOG_ALERT,"shutting down.");
system(SHUTDOWN_CMD);
}
void cancel()
{
//printf("cancel\n");
syslog(LOG_ALERT,"power returned. cancelling shutdown.");
init();
}
void flagdump(int flags)
{
int low = !((flags & BATT_GOOD_BITS) == BATT_GOOD_BITS);
int fail = !((flags & POWER_GOOD_BITS) == POWER_GOOD_BITS);
int init = ((flags & INIT_BITS) == INIT_BITS);
int kill = ((flags & KILL_BITS) == KILL_BITS);
//printf("low=%d fail=%d init=%d kill=%d\n",low,fail,init,kill);
}
int readline()
{
static int count = 0;
static int state = U_OK;
static int lastnextstate = U_OK;
int nextstate;
int flags;
// Get the current line bits
ioctl(fd, TIOCMGET, &flags);
flagdump(flags);
// nextstate = current line conditions
nextstate = U_OK;
if ( (flags & POWER_GOOD_BITS) != POWER_GOOD_BITS )
{
if ( (flags & BATT_GOOD_BITS) != BATT_GOOD_BITS )
{
nextstate = U_LOW;
}
else
{
nextstate = U_FAIL;
}
}
// count the time we've been in this new state
if ( nextstate != lastnextstate )
{
count = 0;
}
else
{
// keep this from overflowing
if ( count <= DEBOUNCE_TIME )
{
count += POLL_INTERVAL;
}
}
lastnextstate = nextstate;
// and if we exceed the threshold take the state as our current
if ( count > DEBOUNCE_TIME )
{
state = nextstate;
}
return state;
}
void term()
{
syslog(LOG_NOTICE,"caught signal.");
init();
if ( fd != -1 )
{
close(fd);
}
syslog(LOG_NOTICE,"terminated.");
exit(EXIT_SUCCESS);
}
int main(int argc,char** argv)
{
// check parameters
if ( argc < 2 )
{
fprintf(stderr, "Usage: upsd <device>\n");
exit(1);
}
// open serial port
if ( (fd = open(argv[1], O_RDONLY | O_NDELAY)) < 0 )
{
fprintf(stderr, "upsd: %s: %s\n", argv[1], sys_errlist[errno]);
exit(1);
}
// become a daemon
switch ( fork() )
{
case 0: /* I am the child. */
setsid();
break;
case -1: /* Failed to become daemon. */
fprintf(stderr, "%s: can't create daemon.\n", argv[0]);
exit(EXIT_FAILURE);
default: /* I am the parent. */
exit(EXIT_SUCCESS);
}
// open syslog
openlog("upsd",LOG_PID,LOG_DAEMON);
syslog(LOG_NOTICE, "started on %s.",argv[1]);
// setup termination routine
signal(SIGHUP,SIG_IGN);
signal(SIGINT,term);
signal(SIGKILL,term);
signal(SIGTERM,term);
// init line
init();
while ( 1 )
{
static int count = 0;
static int killtime = 0;
static int laststate = U_OK;
static int nextwarn = 0;
int state;
// get current ups state
state = readline();
//printf("state = %s\n",state==U_OK?"OK":state==U_FAIL?"U_FAIL":"U_LOW");
// count the time we've been in this state
if ( state == laststate )
{
count += POLL_INTERVAL;
}
// don't count uptime (avoid overflow)
if ( state == U_OK )
{
count = 0;
killtime = 0;
}
// if we just went back up let's reset
if ( state == U_OK && state != laststate )
{
cancel();
}
// if the battery is low let's handle it immediately
if ( state == U_LOW )
{
// warn if we just got the signal!
if ( laststate != U_LOW )
{
lowmessage();
}
// set kill signal
if ( killtime == 0 )
{
killups();
killtime += POLL_INTERVAL;
}
else if ( killtime >= KILL_TIME )
{
shutdown();
}
else
{
killtime += POLL_INTERVAL;
}
}
// if the power is off handle
if ( state == U_FAIL )
{
// if this is the first time let's setup
if ( state != laststate )
{
nextwarn = 0;
}
// kill within KILL_TIME of SHUTDOWN_TIME
if ( count >= (SHUTDOWN_TIME - KILL_TIME) && killtime == 0 )
{
killups();
killtime += POLL_INTERVAL;
}
// and shutdown at appropriate time
if ( count >= SHUTDOWN_TIME )
{
shutdown();
}
else if ( count >= nextwarn )
{
int secondsleft = (SHUTDOWN_TIME-count);
if ( secondsleft >= 10 )
{
warn(SHUTDOWN_TIME-count);
nextwarn += secondsleft/2;
}
}
}
laststate = state;
sleep(POLL_INTERVAL);
}
}