#!/usr/bin/perl -w

use Getopt::Std;
use Getopt::Long;
use LWP::UserAgent;
use HTTP::Request::Common qw(GET);
use HTML::TableExtract;
use Nagios::Plugin::Threshold;
use POSIX;
use Switch;
use strict;

# ChangeLog
# 20171022: Initial Version. I'll eventually implement functionality to monitor individual strings and cells.
# 20171105: Added --string and --cell options.
 
my $DEFAULT_TIMEOUT=5;

# Nagios MAX_PLUGIN_OUTPUT_LENGTH is typically 8192. Pushover accepts a message size of 1024. 
# Allow for some extra space for text like 'Events: ' and 'WARNING - ". 
# Not currently used, but may be implemented in future versions.
my $MAX_MESSAGE_SIZE=512;

# Version and script info.
my $VERSION="2.0";
my $DATE="20171104";
my $SCRIPT_NAME="check_cellwatch.pl";

my ($ok_output, $warn_output, $crit_output, @ok_data, @warn_data, @crit_data, @systems, @batteries, %strings, @threshold_array);
my ($hostname, $timeout, $system, $battery, $string, $cell, $warning, $critical, $verbose, $debug, $help, $usage, $version);
GetOptions ("H=s"	=>	\$hostname,
    "timeout=s"		=>	\$timeout,
    "system"		=>	\$system,
    "battery"		=>	\$battery,
    "string"		=>	\$string,
    "cell"		=>	\$cell,
    "warning=s"		=>	\$warning,
    "critical=s"	=>	\$critical,
    "verbose"		=>	\$verbose,
    "debug"		=>	\$debug,
    "help"		=>	\$help,
    "usage"		=>	\$help,
    "version"		=>	\$version
);

#### Main Program

if ($help) {
    usage();
    exit();
}

if ($version) {
    version();
    exit();
}

if (! $hostname) {
    print "UKNOWN - You must specify a Cellwatch Monitoring Station to query! Try --help";
    exit(3);
}

if (!($battery) && !($system) && !($string) && !($cell)) {
   print "UNKNOWN - You must specify either --battery, --system, --string, --cell or some combination. Try --help";
   exit(3);
}

# The timeout was not specified on the command line.
# We set it now, the 3 minute default is too long.
if (!$timeout) {
    $timeout = $DEFAULT_TIMEOUT;
}

# We got some garbage for the timeout. Fix that now.
elsif (!isdigit($timeout)) {
    $timeout = $DEFAULT_TIMEOUT;
}

if (defined($verbose)) {
   print "UNKNOWN - verbose option is not currently supported";
   exit(3);
}

if (defined($debug)) {
   print "UNKNOWN - debug option is not currently supported";
   exit(3);
}

# Some code I stole to check IP address validity.
# Seems to work! I don't want to bother with DNS.
if( $hostname =~ m/^(\d\d?\d?)\.(\d\d?\d?)\.(\d\d?\d?)\.(\d\d?\d?)$/ ) {

    if(! ($1 <= 255 && $2 <= 255 && $3 <= 255 && $4 <= 255)) {
	print "UNKNOWN - IP address is invalid!\n";
	exit(3);
    }
}
else {
    print "UNKNOWN - IP address is invalid!\n";
    exit(3);
}

if ($battery && ($warning || $critical)) {
   &set_battery_thresholds;
}
elsif ($battery) {
   print "UNKNOWN - when querying battery stats you must supply either a warning and/or a critical threshold";
   exit(3);
}

&fetch_system_status;

if ($string || $cell) {
   &fetch_string_status;
}




# Build outputs
for (my $i=0 ; $i <=scalar(@systems) ; $i++) {
  
   if ($system && $i == 0) {
      if (defined($crit_data[$i])) { $crit_output .= $systems[$i] . $crit_data[$i] ; }
      if (defined($warn_data[$i])) { $warn_output .= $systems[$i] . $warn_data[$i] ;}
      if (defined($ok_data[$i])) { $ok_output .= $systems[$i] . $ok_data[$i] ;}
   }
   if ($battery && $i > 0) {
      if (defined($crit_data[$i])) { $crit_output .= $systems[$i] . $crit_data[$i] ; }
      if (defined($warn_data[$i])) { $warn_output .= $systems[$i] . $warn_data[$i] ;}
      if (defined($ok_data[$i])) { $ok_output .= $systems[$i] . $ok_data[$i] ;}
   }
}

if ($string) {
   my $string_output;
   my $warn_string_output;
   foreach my $string (sort keys %strings) {
      #print $string;
      #$ok_output .= "$string: ";
      #$string_output .= "$string: ";

      # Any problem ... 
      if ( (!($strings{$string}{Temperature} eq 'OK')) || (!($strings{$string}{Voltage} eq 'OK')) ||
           (!($strings{$string}{Current} eq 'OK')) || (!($strings{$string}{"Ohmic-value"} eq 'OK')) ) {
         $warn_string_output .= "$string: ";

         # String Temperature Problem
         if (!($strings{$string}{Temperature} eq 'OK')) {
            for (my $s=1 ; $s <= $strings{$string}{"Temperature Sensors"}; $s++) {
               my $sensor_name = "Temp_" . $s;
               $warn_string_output .= $sensor_name . ":" . $strings{$string}{$sensor_name}{"Temperature Reading"};
               if ($strings{$string}{$sensor_name}{"Temperature Reading"} > $strings{$string}{$sensor_name}{"High Limit"}) {
                  $warn_string_output .= "(HIGH) ";
               }
               elsif ($strings{$string}{$sensor_name}{"Temperature Reading"} < $strings{$string}{$sensor_name}{"Low Limit"}) {
                  $warn_string_output .= "(LOW) ";
               }
               else {
                  $warn_string_output .= "(OK) ";
               }
            }
         }

         # Voltage Problem 
         if (!($strings{$string}{Voltage} eq 'OK')) {
            # May need to look at String limits here. if High String V Limit != 'Not set'... 
             $warn_string_output .= "Voltage:" . $strings{$string}{Volts} . "(" . $strings{$string}{Voltage} . ") ";
         }

         # Current Problem 
         if (!($strings{$string}{Current} eq 'OK')) {
            $warn_string_output .= "Current:" . $strings{$string}{Amps} . "(" . $strings{$string}{Current} . ") ";
         }

         # Impedance Problem 
         if (!($strings{$string}{"Ohmic-value"} eq 'OK')) {
            $warn_string_output .= "Ohmic-value:" . "(" . $strings{$string}{"Ohmic-value"} . ") ";
         }

      }   

      else {
         $string_output .= "$string:OK ";
      }
   }

   if (defined($warn_string_output)) {
      $warn_output .= $warn_string_output;
   }
   else {
      $ok_output .= $string_output;
   }
}

if ($cell) {
   my $cell_output;
   my $warn_cell_output;
   foreach my $string (sort keys %strings) {
      for (my $c=1 ; $c <= $strings{$string}{"Battery Cells"}; $c++) {
         my $cell_name = "Cell_" . $c;
         if ( (!($strings{$string}{$cell_name}{"Voltage Status"} eq 'OK')) || (!($strings{$string}{$cell_name}{"Impedance Status"} eq 'OK')) ) {
            $warn_cell_output .= $cell_name . ":";

            # Voltage problem
            if ( (!($strings{$string}{$cell_name}{"Voltage Status"} eq 'OK')) ) {
               $warn_cell_output .= $strings{$string}{$cell_name}{Volts} . "v ";
               if ($strings{$string}{$cell_name}{Volts} > $strings{$string}{$cell_name}{"High V Limit"}) { $warn_cell_output .= "(HIGH) "; }
               elsif ($strings{$string}{$cell_name}{Volts} < $strings{$string}{$cell_name}{"Low V Limit"}) { $warn_cell_output .= "(LOW) "; }
               else { $warn_cell_output .= "(??) "; }
            }   

            # Impedance Problem
            if ( (!($strings{$string}{$cell_name}{"Impedance Status"} eq 'OK')) ) {
               $warn_cell_output .= $strings{$string}{$cell_name}{Impedance} . "ohms ";
               if ($strings{$string}{$cell_name}{Impedance} > $strings{$string}{$cell_name}{"High Impedance Limit"}) { $warn_cell_output .= "(HIGH) "; }
               elsif ($strings{$string}{$cell_name}{Impedance} < $strings{$string}{$cell_name}{"Low Impedance Limit"}) { $warn_cell_output .= "(LOW) "; }
               else { $warn_cell_output .= "(??) "; }
            }
         }
      }
      if (defined($warn_cell_output) && (!($warn_cell_output eq '')) ) {
         $warn_output .= "$string: " . $warn_cell_output;
      } 
      else {
         $ok_output .= "$string: " . $strings{$string}{"Battery Cells"} . " Cells OK ";
      }
      # Reset this.
      $warn_cell_output = '';
   }
}

if (defined($crit_output)) {
   $crit_output =~ s/\s+$//;
   if (defined($warn_output)) {
      $warn_output =~ s/\s+$//;
      print "CRITICAL - $crit_output WARNING - $warn_output";
      exit(2);
   }
   else {
      print "CRITICAL - $crit_output";
      exit(2);
   }
}

if (defined($warn_output)) {
   $warn_output =~ s/\s+$//;
   print "WARNING - $warn_output";
   exit(1);
}

if (defined($ok_output)) {
   $ok_output =~ s/\s+$//;
   print "OK - $ok_output";
   exit(0);
}

print "UKNOWN - No data received internally. This is a bug and should never happen.";
exit(3);

### Functions

sub usage {
    print "$SCRIPT_NAME -H <ip address> [--timeout <seconds>] (--battery | --system) \n\n";
    print "Options:\n";
    print "\t-H\n";
    print "\t   IP Address for Cellwatch monitoring station. DNS is not supported\n\n";
    print "\t--timeout\n";
    print "\t   Specify timeout for LWP call. Defaults to 5 seconds\n\n";
    print "\t--system\n";
    print "\t   Evaluate the Overall system table\n\n";
    print "\t--battery\n";
    print "\t   Evaluate the battery table for all battery systems\n\n";
    print "\t--warning\n";
    print "\t   Enter a standard Nagios warning threshold to be used with --battery\n\n";
    print "\t--critical\n";
    print "\t   Enter a standard Nagios critical threshold to be used with --battery\n\n";
    print "\t--help\n";
    print "\t   This message.\n\n";
    print "\t--version\n";
    print "\t   Version information.\n\n";
    print "\t--verbose\n";
    print "\t   Not used at this time.\n\n";
    print "\t--debug\n";
    print "\t   Not used at this time\n\n";
}

sub version {
    print "\n$SCRIPT_NAME version $VERSION Written by Eric Schoeller, $DATE\n\n";
}

sub set_battery_thresholds {
   my (@warn, @crit);
   # Check to see if they are multi-part
   if (defined($warning) && (!($warning =~ /,/))) { print "UNKNOWN - A multi-part warning threshold is needed volts,amps"; exit(3); }
   if (defined($critical) && (!($critical =~ /,/))) { print "UNKNOWN - A multi-part critical threshold is needed volts,amps"; exit(3); }
   # split up the multi-part into an array
   if (defined($warning)) { @warn = split(',', $warning); } 
   if (defined($critical)) { @crit = split(',', $critical); }

   # Both WARNING and CRITICAL thresholds need to be set.
   if (defined($warning) && defined($critical)) {
      push(@threshold_array, Nagios::Plugin::Threshold->new(warning => $warn[0], critical => $crit[0]));
      push(@threshold_array, Nagios::Plugin::Threshold->new(warning => $warn[1], critical => $crit[2]));
   }

   # Just a CRITICAL is defined
   elsif (defined($critical)) {
      push(@threshold_array, Nagios::Plugin::Threshold->new(critical => $crit[0]));
      push(@threshold_array, Nagios::Plugin::Threshold->new(critical => $crit[1]));
   }

   # Just a WARNING is defined
   elsif (defined($warning)) {
      push(@threshold_array, Nagios::Plugin::Threshold->new(warning => $warn[0]));
      push(@threshold_array, Nagios::Plugin::Threshold->new(warning => $warn[1]));
   }
}
   
sub fetch_string_status {

   for (my $i=0 ; $i < scalar(@batteries) ; $i++) {
      my $ua = LWP::UserAgent->new();
      $ua->timeout($timeout);
      my $req = GET "http://$hostname/Battery$i";
      my $response = $ua->request($req);
      my $num_strings = 0;
      if ($response->is_success) {
         my $data = $response->content;
         my @columns = ("Name", "Voltage", "Current", "Temperature", "Ohmic-value", "String current", "String voltage", "Low string V limit", "High string V limit");
         my $string_status = HTML::TableExtract->new( depth => 0, count => 1, headers => [@columns] );
         $string_status->parse($data);
         if (defined($string_status->first_table_found())) {
            my $string_status_table = $string_status->first_table_found();
            foreach my $row ($string_status_table->rows) {
               # Filter some text from the battery name.
               (my $battery_name = $batteries[$i]) =~ s/Battery: //g;
               # Strip leading and trailing spaces from battery name
               $battery_name =~ s/^\s+|\s+$//g;
               # Replace spaces in battery name with underscores
               $battery_name =~ s/ /_/g;
               # Chomp the battery name for good measure.
               chomp($battery_name);
               # All the same string processing for the string name ...
               (my $string_name = @$row[0]) =~ s/String: //g;
               $string_name =~ s/^\s+|\s+$//g;
               $string_name =~ s/ /_/g;
               chomp($string_name);
               # Combine the battery name and string name together. 
               my $battery_string = $battery_name . "/" . $string_name;

               # Build the hash with all the data.
               $strings{$battery_string}{Battery} = $battery_name; 
               $strings{$battery_string}{String} = $string_name; 
               $strings{$battery_string}{Voltage} = @$row[1]; 
               $strings{$battery_string}{Current} = @$row[2]; 
               $strings{$battery_string}{Temperature} = @$row[3]; 
               $strings{$battery_string}{"Ohmic-value"} = @$row[4]; 
               $strings{$battery_string}{"Amps"} = @$row[5]; 
               $strings{$battery_string}{"Volts"} = @$row[6]; 
               $strings{$battery_string}{"Low String V Limit"} = @$row[7]; 
               $strings{$battery_string}{"High String V Limit"} = @$row[8]; 

               # Fetch the string data
               &fetch_string_data($i,$num_strings,$ua,\%strings,$battery_string);
 
               $num_strings++;

            }
         }
         else {
            print "UNKNOWN - string status table not found";
            exit(3);
         }
      }
      else {
         print "UNKNOWN - " . $response->status_line ;
         exit(3);
      }
   }
   #print Dumper %strings;
}

sub fetch_string_data {

   # Handle the items passed.
   my $i = $_[0];
   my $num_strings = $_[1];
   my $ua = $_[2];
   my $strings = $_[3];
   my $battery_string = $_[4];

   # Get the string page
   my $req = GET "http://$hostname/Battery$i/String$num_strings";
   my $response = $ua->request($req);
   if ($response->is_success) {
      my $data = $response->content;

      # Process the temperature table
      my @temp_columns = ("Name", "Alarm Status", "Temperature reading", "Low limit", "High limit");
      my $string_temp_status = HTML::TableExtract->new( depth => 0, count => 2, headers => [@temp_columns] );
      $string_temp_status->parse($data);
      if (defined($string_temp_status->first_table_found())) {
         my $num_temp = 0;
         foreach my $row ($string_temp_status->rows) {
            my $temperature_name = "Temp_" . @$row[0];
            $strings->{$battery_string}{$temperature_name}{Name} = @$row[0];
            $strings->{$battery_string}{$temperature_name}{"Alarm Status"} = @$row[1];
            $strings->{$battery_string}{$temperature_name}{"Temperature Reading"} = @$row[2];
            $strings->{$battery_string}{$temperature_name}{"Low Limit"} = @$row[3];
            $strings->{$battery_string}{$temperature_name}{"High Limit"} = @$row[4];
            $num_temp++
         }
         $strings->{$battery_string}{"Temperature Sensors"} = $num_temp;
      }   
      else {
         print "UNKNOWN - string temperature status table not found";
         exit(3);
      }

      if (defined($cell)) {
         # Process the cell table
         my @cell_columns = ("Name", "V", "R", "Volt reading", "Low V limit", "High V limit", "Ri reading", "Low Ri limit", "High Ri limit");
         my $string_cell_status = HTML::TableExtract->new( depth => 0, count => 3, headers => [@cell_columns] );
         $string_cell_status->parse($data);
         if (defined($string_cell_status->first_table_found())) {
            my $num_cells = 0;
            foreach my $row ($string_cell_status->rows) {
               my $cell_name = "Cell_" . @$row[0];
               $strings->{$battery_string}{$cell_name}{Name} = @$row[0];
               $strings->{$battery_string}{$cell_name}{"Voltage Status"} = @$row[1];
               $strings->{$battery_string}{$cell_name}{"Impedance Status"} = @$row[2];
               $strings->{$battery_string}{$cell_name}{"Volts"} = @$row[3];
               $strings->{$battery_string}{$cell_name}{"Low V Limit"} = @$row[4];
               $strings->{$battery_string}{$cell_name}{"High V Limit"} = @$row[5];
               $strings->{$battery_string}{$cell_name}{"Impedance"} = @$row[6];
               $strings->{$battery_string}{$cell_name}{"Low Impedance Limit"} = @$row[7];
               $strings->{$battery_string}{$cell_name}{"High Impedance Limit"} = @$row[8];
               $num_cells++;
            }
            $strings->{$battery_string}{"Battery Cells"} = $num_cells;
         }   
         else {
            print "UNKNOWN - string cell status table not found";
            exit(3);
         }
      }
   }
   else {
      print "UNKNOWN - " . $response->status_line ;
      exit(3);
   }
}
               
sub fetch_system_status {

   # Fire up a new user agent
   my $ua = LWP::UserAgent->new();

   # Set the timeout. The default of 3 minutes is too long!
   $ua->timeout($timeout);

   # Build the HTTP GET request
   my $req = GET "http://$hostname/System";

   # Set the response
   my $response = $ua->request($req);

   # If we recieved data from the meter, continue to process it.
   if ($response->is_success) {
      my $data = $response->content;
      $systems[0]="System ";

      # This is the first table from the webpage. Using both depth/count and headers is probably too specific. 
      my $system_status = HTML::TableExtract->new( depth => 0, count => 1, headers => [qw(Voltage Current Temperature Ohmic-value)] );
      $system_status->parse($data);

      # There should be only one table returned from the query, so that's the one we look at. 
      if (defined($system_status->first_table_found())) {
         my $system_status_table = $system_status->first_table_found();

         # I didn't bother to determine what the other possible values there are other than OK. Everything other than OK is a warning.
         # You can replace $warn_data with $crit_data if you need to.
         if ($system_status_table->cell(0,0) eq "OK") { $ok_data[0] .= "Voltage:" . $system_status_table->cell(0,0) . " "; } else { $warn_data[0] .= "Voltage:" . $system_status_table->cell(0,0) . " "; }
         if ($system_status_table->cell(0,1) eq "OK") { $ok_data[0] .= "Current:" . $system_status_table->cell(0,1) . " "; } else { $warn_data[0] .= "Current:" . $system_status_table->cell(0,1) . " "; }
         if ($system_status_table->cell(0,2) eq "OK") { $ok_data[0] .= "Temperature:" . $system_status_table->cell(0,2) . " "; } else { $warn_data[0] .= "Temperature:" . $system_status_table->cell(0,2) . " "; }
         if ($system_status_table->cell(0,3) eq "OK") { $ok_data[0] .= "Ohmic-Value:" . $system_status_table->cell(0,3) . " "; } else { $warn_data[0] .= "Ohmic-Value:" . $system_status_table->cell(0,3) . " "; }
      }
      else {
         print "UNKNOWN - Overall system status table not found!";
         exit(3);
      }

      # Again, this is very specific.
      my $battery_status = HTML::TableExtract->new( depth => 0, count => 2, headers => [qw(Battery Voltage Current Temperature Ohmic-value Volts Amps)] );
      $battery_status->parse($data);
      # Again, should only be one table anyway.
      if (defined($battery_status->first_table_found())) {
         my $battery_status_table = $battery_status->first_table_found();
         my $i=1;
         foreach my $row ($battery_status_table->rows) {

            # Filter some text from the battery name.
            (my $battery_name = @$row[0]) =~ s/Battery: //g;
            # Strip leading and trailing spaces from battery name
            $battery_name =~ s/^\s+|\s+$//g;
            # Replace spaces in battery name with underscores
            $battery_name =~ s/ /_/g;
            # Chomp the battery name for good measure.
            chomp($battery_name);

            push(@systems, $battery_name . " ");
            push(@batteries, $battery_name . " ");

            # Populate the OKs or whatever other text as WARNING, same as in --system
            if (@$row[1] eq "OK") { $ok_data[$i] .= "Voltage:" . @$row[1] . " "; } else { $warn_data[$i] .= "Voltage:" . @$row[1] . " "; }
            if (@$row[2] eq "OK") { $ok_data[$i] .= "Current:" . @$row[2] . " "; } else { $warn_data[$i] .= "Current:" . @$row[2] . " "; }
            if (@$row[3] eq "OK") { $ok_data[$i] .= "Temperature:" . @$row[3] . " "; } else { $warn_data[$i] .= "Temperature:" . @$row[3] . " "; }
            if (@$row[4] eq "OK") { $ok_data[$i] .= "Ohmic-Value:" . @$row[4] . " "; } else { $warn_data[$i] .= "Ohmic-Value:" . @$row[4] . " "; }
            
            if ($battery) { 
               # Apply some thresholds to the Volts and Amps
               switch ($threshold_array[0]->get_status(@$row[5])) {
                  case 0 { $ok_data[$i] .= "Volts:" . @$row[5] . " "}
                  case 1 { $warn_data[$i] .= "Volts:" . @$row[5] . " "; }
                  case 2 { $crit_data[$i] .= "Volts:" . @$row[5] . " "; }
               }

               switch ($threshold_array[1]->get_status(@$row[6])) {
                  case 0 { $ok_data[$i] .= "Amps:" . @$row[6] . " "}
                  case 1 { $warn_data[$i] .= "Amps:" . @$row[6] . " "; }
                  case 2 { $crit_data[$i] .= "Amps:" . @$row[6] . " "; }
               }
            }
 
            $i++;
         } 
      }
      else {
         print "UNKNOWN - Battery status table not found!";
         exit(3);
      }
   }
      
   # Our LWP call did not return a response. Exit with UNKNWON status and indicate the LWP error.
   else {
      print "UNKNOWN - " . $response->status_line ;
      exit(3);
   }
}
      

__END__

=head1 NAME

check_cellwatch - Pull and evaluate data from two tables displayed on the main Cellwatch
                  Monitoring system webpage at http://IP/System
=head1 VERSION

This documentation refers to check_cellwatch version 2.0

=head1 APPLICATION REQUIREMENTS

 Several standard Perl libraries are required for this program to function.
 LWP::UserAgent, HTTP::Request::Common, use HTML::TableExtract, 
 Nagios::Plugin::Threshold, and POSIX 

=head1 USAGE

 check_cellwatch.pl -H <ip address> --timeout <seconds> (--battery | --system)

=head1 REQUIRED ARGUMENTS

 --battery or --system. --battery requires --warning and/or --critical. 

=head1 EXAMPLES

 $ check_cellwatch.pl -H X.X.X.X --system --battery --warning 500:560,-10:5 --critical 400:580,-15:10
 
 Check both the Overall system table and the Battery table. If all values in the system table
 are "OK", the plugin will return those elements as OK. If any are something other than OK,
 the plugin will return just those elements as a WARNING. 
 The same goes for the --battery option, with the exception of the "Volts" and "Amps" columns.
 Thresholds are applied to the integer values returned from those cells. In the above example
 if the voltage is outside the range 500-560 a WARNING is returned, and if the voltage is 
 outside the range 400-580 a CRITICAL condition is returned. Similar logic is applied to the 
 amps, having a WARNING range of -10 to 5 and a CRITICAL range -15 to 10. 

 In my case, this would return:

 OK - System Voltage:OK Current:OK Temperature:OK Ohmic-Value:OK Battery: XYZ UPS-1 Voltage:OK \
 Current:OK Temperature:OK Ohmic-Value:OK Volts:515.6 Amps:-3 Battery: EH093BAA09 UPS-2 \
 Voltage:OK Current:OK Temperature:OK Ohmic-Value:OK Volts:514.4 Amps:-5

 For testing I would do something like this:
 $ check_cellwatch.pl -H X.X.X.X --system --battery --warning 500:560,-10:5 --critical 515:580,-15:10

 Which would return:

 CRITICAL - Battery: XYZ UPS-2 Volts:514.4

 $ check_cellwatch.pl -H X.X.X.X --cell

 WARNING - XXX_UPS-2:/STRING-2: Cell_35:14.81v (HIGH)

 In this example the script is looking at every cell in every string on every battery. In total 
 I have 240 cells and it doesn't take very long. I have a cell here which is at 14.81 volts, which
 is .01 above the defined high threshold in cellwatch. That's where the (HIGH) comes from. If it 
 were below the low threshold it would say (LOW). 

 $ check_cellwatch.pl -H X.X.X.X --string

 WARNING - XXX_UPS-2:/STRING-2: Voltage:553.9(Alarmed)

 I think in this case the overally string voltage is not a problem, but the fact that there is
 a cell in alarm causes the overall string to report an Alarm as well. More testing and
 development needed. 

 See: https://nagios-plugins.org/doc/guidelines.html#THRESHOLDFORMAT
 For Nagios threshold examples

=head1 BUGS, LIMITATIONS, INCOMPATIBILITES

 None.
 If you experience any problems please contact me. (eric.schoeller <at> coloradoDOTedu)

=head1 AUTHOR

Eric Schoeller (eric.schoeller <at> coloradoDOTedu)

=head1 LICENCE AND COPYRIGHT

 Copyright (c) 2017 Eric Schoeller (eric.schoeller <at> coloradoDOTedu).
 All rights reserved.

 This module is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License.
 See L<http://www.fsf.org/licensing/licenses/gpl.html>.

 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.
