#! /usr/bin/perl
###############################################################################
#                                                                             #
#                 Check an RFC1628-compliant UPS via SNMP                     #
#                             (Battery tests)                                 #
#                                                                             #
###############################################################################
#
# Copyright (c) 2005 Simon Butcher <simon@butcher.name>
#  
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.    See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA    02111-1307    USA
#
##############################################################################
#
#
# Performance data:
#    +-------+-----------------------------------------------+
#    | Field | Description                                   |
#    +-------+-----------------------------------------------+
#    |   1   | Time remaining (seconds, 0 = on AC power)     |
#    |   2   | Estimated total battery time (seconds)        |
#    |   3   | Percentage of battery charge remaining        |
#    |   4   | Battery voltage (volts DC)                    |
#    |   5   | Battery current (amps DC)                     |
#    |   6   | Battery temperature (degrees celcius)         |
#    +-------+-----------------------------------------------+
#
##############################################################################


use Net::SNMP;
use Getopt::Std;

$script = "check_snmp_ups_battery";
$script_title =
  "Monitor the status of an RFC1628-compliant UPS via SNMP:\n" .
  "Battery Monitoring";
$script_version = "20051203";

# SNMP options
$version = "1";
$timeout = 2;

# Various SNMP OID prefixes to look at..
$oid_ups_batt_status    = ".1.3.6.1.2.1.33.1.2.1";
        # 1 = unknown, 2 = normal, 3 = low, 4 = dead
$oid_ups_batt_timeleft  = ".1.3.6.1.2.1.33.1.2.2"; # seconds, 0 = AC on-line
$oid_ups_batt_esttime   = ".1.3.6.1.2.1.33.1.2.3"; # minutes
$oid_ups_batt_charge    = ".1.3.6.1.2.1.33.1.2.4"; # percent
$oid_ups_batt_voltage   = ".1.3.6.1.2.1.33.1.2.5"; # / 0.1 volts DC
$oid_ups_batt_current   = ".1.3.6.1.2.1.33.1.2.6"; # / 0.1 amps DC
$oid_ups_batt_temp      = ".1.3.6.1.2.1.33.1.2.7"; # degrees celcius

# The information we returned
$ups_batt_status = 0;
$ups_batt_timeleft = 0;
$ups_batt_esttime = 0;
$ups_batt_charge = 0;
$ups_batt_voltage = 0;
$ups_batt_current = 0;
$ups_batt_temp = 0;

# Thresholds
$critical_charge = -1;
$warning_charge = -1;
$critical_timeleft = -1;
$warning_timeleft = -1;

# Our return status - we start with 0 (OK) and hope for the best
$status = 0;

# Our return string with lots of interesting stuff in it
$returnstr = "";

# The SNMP hostname and community to use for the query
$hostname = "";
$community = "public";


# Grab the command line options
getopts("h:H:C:w:c:");

# If we didn't get any options, show some help and quit
if ($opt_h){
    usage();
    exit(-1); # Unknown
}

# Get the hostname, if it was given..
if (defined($opt_H)){
    $hostname = $opt_H;
} else {
    # We really need a hostname
    usage();
    exit(-1); # Unknown
}

# Get the SNMP community
if (defined($opt_C)){
    $community = $opt_C;
}

# Grab the warning thresholds
if (defined($opt_w)) {
    my @warning = split(/\,/, $opt_w);

    if (!($warning[0] eq "")) {
	$warning_charge = $warning[0];

	# Make sure this is a percentage
	if (($warning_charge < 0) ||
	    ($warning_charge > 100)) {
	    print "Charge warning threshold must be a percentage\n";
	    exit(-1); # Unknown
	}
    }
    if (!($warning[1] eq "")) {
	$warning_timeleft = $warning[1];

	# Make sure this is a positive time value
	if ($warning_timeleft <= 0) {
	    print "The warning remaining time left is not a positive number\n";
	    exit(-1); # Unknown
	}
    }
}

# Grab the critical thresholds
if (defined($opt_c)) {
    my @critical = split(/\,/, $opt_c);

    if (!($critical[0] eq "")) {
	$critical_charge = $critical[0];

	# Make sure this is a percentage
	if (($critical_charge < 0) ||
	    ($critical_charge > 100)) {
	    print "Charge critical threshold must be a percentage\n";
	    exit(-1); # Unknown
	}
    }
    if (!($critical[1] eq "")) {
	$critical_timeleft = $critical[1];

	# Make sure this is a positive time value
	if ($critical_timeleft <= 0) {
	    print "The critical remaining time left is not a positive number\n";
	    exit(-1); # Unknown
	}
    }
}

# If we received both warning and critical times/charge values, make sure
# the warning values are higher than the critical values
if (($warning_charge != -1) &&
    ($critical_charge != -1) &&
    ($critical_charge >= $warning_charge)) {
    print "The critical charge threshold must be smaller than the warning\n";
    exit(-1); # Unknown
}
if (($warning_timeleft != -1) &&
    ($critical_timeleft != -1) &&
    ($critical_timeleft >= $warning_timeleft)) {
    print "The critical remaining time threshold must be smaller than the warning\n";
    exit(-1); # Unknown
}


# Initialise the SNMP session via the Net::SNMP perl module
my ($snmp_session, $snmp_error) = Net::SNMP->session(
    -community => $community,
    -hostname => $hostname,
    -version => $version,
    -timeout => $timeout,
);

# Grab interesting details
check_device();
    
# Shut down the SNMP session
$snmp_session->close();

# Return the return string and return status
print "$returnstr\n";
exit($status);


##############################################################################
##############################################################################
#
# Change the status level
#
sub status {
    my $newstatus = $_[0];

    # If the new status is greater than the old status, change the old status
    if ($newstatus > $status) {
	$status = $newstatus;
    }
}


##############################################################################
##############################################################################
#
# Grab a value from snmp
#
sub grab_snmp_value {
    my $this_oid = $_[0];
    my $this_value = "";

    # Try to grab the OID's value, if it exists
    if (defined($snmp_session->get_request($this_oid))) {
        foreach ($snmp_session->var_bind_names()) {
            $this_value = $snmp_session->var_bind_list()->{$_};
        }
    }

    # Return the value we got..
    return $this_value;
}


##############################################################################
##############################################################################
#
# Grab lots of interesting info about the device
#
sub check_device {
    # Grab some values
    $ups_batt_status = grab_snmp_value($oid_ups_batt_status);
    $ups_batt_timeleft = grab_snmp_value($oid_ups_batt_timeleft);
    $ups_batt_esttime = grab_snmp_value($oid_ups_batt_esttime) * 60;
    $ups_batt_charge = grab_snmp_value($oid_ups_batt_charge);
    $ups_batt_voltage = grab_snmp_value($oid_ups_batt_voltage) * 0.1;
    $ups_batt_current = grab_snmp_value($oid_ups_batt_current) * 0.1;
    $ups_batt_temp = grab_snmp_value($oid_ups_batt_temp);

    # Check the battery status..
    if ($ups_batt_status == 1) {        # Unknown
	status(-1); # Unknown
	$returnstr = "Battery Status Unknown";
    } elsif ($ups_batt_status == 2) {   # Normal
	status(0); # OK
	$returnstr = "Battery OK";
    } elsif ($ups_batt_status == 3) {  # Low
        status(2); # Critical
	$returnstr = "Battery Low";
    } elsif ($ups_batt_status == 4) {  # Empty
	status(2); # Critical
	$returnstr = "Battery Empty";
    } else {
	status(2); # Critical
	$returnstr = "Battery Status Unavailable";
    }

    # Do we know if we are on battery or not?
    if (!($ups_batt_timeleft eq "") &&
	($ups_batt_timeleft != 0)) {
	# We're on battery power.. If we have no warning/critical, warn anyway
	if (($warning_timeleft == -1) &&
	    ($critical_timeleft == -1)) {
	    status(1); # Warning
	}
	   
        # Mention how much time we have left..
	$returnstr =
	    "$returnstr, " .
	    "Inverter Active: " .
	    "~$ups_batt_timeleft seconds remaining";

	# Have we broken a threshold here?
	if (($critical_timeleft != -1) &&
	    ($ups_batt_timeleft <= $critical_timeleft)) {
	    status(2); # Critical
	    $returnstr = "$returnstr (Critical)";
	} elsif (($warning_timeleft != -1) &&
		 ($ups_batt_timeleft <= $warning_timeleft)) {
	    status(1); # Warning
	    $returnstr = "$returnstr (Warning)";
	}
    }

    # If we have the battery charge, add that
    if (!($ups_batt_charge eq "")) {
	$returnstr =
	    "$returnstr, " .
	    "$ups_batt_charge\% charge";

	# Have we broken a threshold?
	if (($critical_charge != -1) &&
	    ($ups_batt_charge <= $critical_charge)) {
	    status(2); # Critical
	    $returnstr = "$returnstr (Critical)";
	} elsif (($warning_charge != -1) &&
		 ($ups_batt_charge <= $warning_charge)) {
	    status(1); # Warning
	    $returnstr = "$returnstr (Warning)";
	}
    }

    # If we have the battery voltage, add that
    if (!($ups_batt_voltage eq "") &&
	($ups_batt_voltage != 0)) {
	$returnstr =
	    "$returnstr, " .
	    "$ups_batt_voltage VDC";
    }

    # If we have the battery current, add that
    if (!($ups_batt_current eq "") &&
	($ups_batt_current != 0)) {
	$returnstr =
	    "$returnstr, " .
	    "$ups_batt_current A";
    }

    # If we have the battery temperature, also add that
    if (!($ups_batt_temp eq "")) {
	$returnstr =
	    "$returnstr, " .
	    "$ups_batt_temp\&#x2103";
    }

    # Add on some performance monitoring stuff to the return string :)
    $returnstr =
	"$returnstr" .
	"|$ups_batt_timeleft;$ups_batt_esttime;$ups_batt_charge" .
	";$ups_batt_voltage;$ups_batt_current;$ups_batt_temp" .
	";";
}


##############################################################################
##############################################################################
#
# Usage information
#
sub usage {
    print << "USAGE";
$script ($script_version)
Copyright (c) 2005 Simon Butcher <simon@butcher.name>

$script_title

Usage: $script -H <hostname> [-C <community>]
       [-w <%>,<s>] [-c <%>,<s>]

Options:
    -H 	Hostname or IP address of the UPS
    -C 	SNMP read community (default is $community)
    -w  Warning level charge percentage and remaining battery seconds
    -c  Critical level charge percentage and remaining battery seconds
        For example: 75% battery and 5 minutes left: 75,300

USAGE
     exit(-1); # Unknown
}
