#!/usr/bin/perl # # UPS monitor for APC Back UPS Pro # (c) 1999 Paul Warren # License: GNU General Public License # # Edit the Configuration section of this file. # # When started this daemon will monitor the status of the attached UPS, # polling once every 5 seconds and syslogging its status once every # $LOG_DELAY seconds. # # If the mains power fails, users will be notified of this, along with # the uptime remaining, once every $REMIND_DELAY seconds. This info is # also syslogged. # # When the UPS reports low battery (a configurable number of minutes # before it runs out of power - see option "q" below), upsd will # initiate a UPS shutdown, and then shutdown the system. Note that # the UPS will turn off the power after the shutdown grace period has # elapsed, even if mains power has been restored. This is important # to ensure that the system gets a power cycle and reboots. # ###################### # # Configuration # # UPS Serial Port $PORT = "/dev/ttyS0"; # When on battery, log and wall once every $REMIND_DELAY seconds $REMIND_DELAY = 45; # Log UPS status every $LOG_DELAY seconds $LOG_DELAY = 7200; # 2 hours # UPS EEPROM settings # # It is important to use only the values as listed here %UPS_SETTINGS = ( # "k" Alarm Delay # 0 = 5sec delay # T = 30sec delay # L = low battery # N = no alarm "k" => "L", # "p" Shutdown grace seconds - should be longer than it takes to shutdown. # 020 = 20secs # 180 = 180secs # 300 = 300secs # 600 = 600secs "p" => "180", # "q" Low battery warning - minutes of remaining power # 02 = 2 minutes # 05 = 5 minutes # 07 = 7 minutes # 10 = 10 minutes "q" => "07", # "r" Wakeup time delay # 000 = immediately # 060 = 60 seconds # 180 = 180 seconds # 300 = 300 seconds "r" => "060" ); # # End of config # ##################### # # APC "Smart" protocol # # See: http://www.exploits.org/nut/library/apcsmart.html # $APC_SMART_MODE = "Y"; $APC_GET_MODEL = ""; $APC_GET_STATUS = "Q"; $APC_GET_TIME_REMAINING = "j"; $APC_GET_LOW_BATTERY_TIME = "q"; $APC_GET_BATTERY_LEVEL = "f"; $APC_GET_POWER_LOAD = "P"; $APC_SOFT_SHUTDOWN = "S"; use Sys::Syslog; # # Send a single character to the UPS # sub upsSend { my $sendbuf = shift; syswrite (UPSFD , $sendbuf , 1); } # # Read a line of reply from the UPS # sub upsRead { my $recvbuf = ""; my $ch =""; do { sysread(UPSFD, $ch, 1); if (! ($ch =~ m/[\|\!\$\%\n\r]/ )) # ignore status chars... { $recvbuf .= $ch ; } } while ( $ch ne "\n" ); return $recvbuf; } # # Sets an EEPROM property to the specified value by cycling the # UPS setting. Give up if the specified value is not available. # sub upsSetProperty { my $property = shift; my $value = shift; upsSend( $property ); my $oldvalue = upsRead(); my $curvalue = $oldvalue; while ($curvalue ne $value) { upsSend("-"); upsRead(); upsSend($property); $curvalue = upsRead(); if ($curvalue eq $oldvalue) { syslog('err',"Invalid value: [$value] for property [$property]"); return; } } } # # Reads the UPS's status bits and converts them to hex. # sub upsGetStatus { upsSend($APC_GET_STATUS); my $status = upsRead(); ($status =~ m/([0-9])([0-9])/) or die "Could not get status bits"; $status = $1 * 0x10 + $2; } # # Returns the battery level, as a percentage # sub upsGetBatteryLevel { upsSend($APC_GET_BATTERY_LEVEL); (my $batterylevel = upsRead()) =~ s/^0*(.*)/$1%/; return $batterylevel; } # # Returns the current power load on the UPS # sub upsGetPowerLoad { upsSend($APC_GET_POWER_LOAD); (my $load = upsRead()) =~ s/^0*(.*)/$1%/; return $load; } # # walls and syslogs the status, and time remaining until shutdown # # Called every $REMIND_DELAY seconds when on battery power. # sub doNotify { upsSend($APC_GET_TIME_REMAINING); $timeremaining = upsRead(); $timeremaining =~ s/^0*(.*):$/$1/; my $batterylevel = upsGetBatteryLevel(); my $load = upsGetPowerLoad(); $shutdowntime = $timeremaining - $lowbatterytime; $message = < /var/run/upsd.pid`; # # Get the model string # upsSend ( $APC_GET_MODEL ); syslog('info',"Found UPS ".upsRead()); # # Get status bits # upsSend ( $APC_GET_STATUS ); $status = upsGetStatus(); $stat = ""; ($status & 0x08) and do { $stat .= "ON-LINE " }; ($status & 0x10) and do { $stat .= "ON-BATTERY " }; (($status & 0x40) and do { $stat .= "LOW-BATTERY " }) or do { $stat .= "BATTERY-OK " }; ($status & 0x80) and do { $stat .= "REPLACE-BATTERY "}; syslog('info',"[ $stat ]"); $stat = ""; foreach $property (keys %UPS_SETTINGS) { upsSetProperty($property,$UPS_SETTINGS{$property}); $stat .= $property . "=" . $UPS_SETTINGS{$property}.", "; } syslog('info',"Settings: $stat"); upsSend($APC_GET_LOW_BATTERY_TIME); $lowbatterytime = upsRead(); $lastlogged = 0; $lastnotify = 0; while (1) { $oldstatus = $status; $status = upsGetStatus(); if (!($oldstatus & 0x08) && ($status & 0x08)) { syslog('warning',"Mains power restored"); `wall "Mains power restored"`; } elsif (($status & 0x10) && !($oldstatus & 0x10)) # We're on-battery { # # UPS on battery - force immediate notification. # syslog('crit',"UPS on battery"); $lastnotify = 0; } if (($status & 0x50) == 0x50) { # # On battery and low battery. Uncool. # syslog("emerg","UPS battery low - shutdown time!"); closelog(); upsSend($APC_SOFT_SHUTDOWN); `/sbin/shutdown -h now`; } # # Check how long ago we last bothered users. # if ($status & 0x10 && (time - $lastnotify > $REMIND_DELAY)) { $lastnotify = time; doNotify(); } if (time - $lastlogged > $LOG_DELAY) { syslog('info',"Battery: ".upsGetBatteryLevel()." Load: ".upsGetPowerLoad()); $lastlogged = time; } sleep(5); } close UPSFD;