#!/usr/bin/perl -w
#
# bb-temptrax.pl
#
# Copyright (C) 2002-2003 Michael J. MacDonald <mjmac@pobox.com>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of version 2 of the GNU General Public
# License as published by the Free Software Foundation.
#
# 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.
# 
# Authors
#   Michael MacDonald <mjmac@pobox.com>
#
# The current version of this script will always be found in the DeadCat
# Big Brother FTP Archive of EXT scripts.  Please check 
# http://www.deadcat.net/ to be sure that you've got the latest version
# before mailing me to ask for any new features or before hacking them in
# yourself.  Unless you want to hack them in yourself, of course.
#
# Installation:
#
# Place this script in $BBHOME/ext and make sure it's executable (chmod +x).
# Try running the script by hand to make sure the correct Perl modules
# are installed, and that you've configured the correct device.
# You'll have to read the Big Brother documentation to figure out how best
# to integrate this script into your setup.
#
# Notes:
#
# This Big Brother plugin should work for any UNIX-like environment with 
# minimal changes.  In theory, it should also work for win32, with the
# Win32::SerialPort module.  I haven't tested it as a BB win32 plugin,
# but I was able to get results when I ran it from the CLI in Win2k.
# 
# The only variables of note are:
#
# (Search for the string CONFIG to find these vars)
# $device    = the serial device to which the TempTrax is attached
#              OR the IP address of a TempTrax model E
# $testname  = Big Brother display name for this test
# %warntemps = the temperatures at which you want Big Brother to go yellow
# %pagetemps = the temperatures at which you want Big Brother to go red
# $hostname  = should work fine, unless your system's hostname doesn't
#              support the -f switch, or if your bb setup isn't using FQDNs
#
# Running the script outside of the Big Brother environment (i.e. no
# BBHOME var) used to result in an error.  Now the script will just print
# probe readings to STDOUT if it doesn't detect a valid BBHOME.
#
# The BigBrother package could probably be thrown into its own file, but
# it's easier to distribute this way.
#
# Changelog:
#
# 2002-07-18  Michael MacDonald <mjmac@pobox.com>
#   Should do everything it needs to.  Won't work properly if the TempTrax
#   output changes, as it's hardcoded to expect two temperature readings.
#
# 2002-07-19  Michael MacDonald <mjmac@pobox.com>
#   Hmm.  The logic for reporting probe readings was a little dumb.  Because
#   warntemp was checked first, temperatures which could be higher than
#   pagetemp were reported as yellow events.  Duh.  Now the code will go
#   through the %probes hash and stuff the readings from each probe into
#   the message, and any red or yellow events will get the correct precedence.
#
# 2002-09-16  Michael MacDonald <mjmac@pobox.com>
#   Jeremy Mates sent me a patch to fix two small problems.
#   Apparently he needed to add a second's sleep after writing settings 
#   to the serial port, and he added a check to skip over unattached probes
#   (temp == -99.9) when generating the report.  Thanks, Jeremy!
#
# 2003-02-19  Michael MacDonald <mjmac@pobox.com>
#   Decided to try and future-proof the script a little by being smarter
#   about grokking the temptrax output.  There's no reason other than
#   laziness to not check for output on more than two probes.  Also thought
#   it would be nice to make debugging outside of a Big Brother environment
#   a little easier.  Hell, while I'm at it, I'll make the script check
#   for low temperatures, too.  ...  Coffee in the afternoon makes one
#   productive.  I've also added win32 support, since it wasn't very
#   difficult.  I don't have a win32 test environment for BB, but I did
#   verify that the script can read from the TempTrax under Win2k.  Please
#   let me know if it doesn't work for you, and what you did to fix it.
#   Mark Redding's port from an earlier version of this script seems to use 
#   tempfiles for communication with Big Brother.  I don't really know how
#   BB works on win32, but would appreciate any input if the script doesn't
#   work as-is.
#  
# 2003-02-20  Michael MacDonald <mjmac@pobox.com>
#   The TempTrax Model E has a built in http daemon to serve temperature data
#   via ethernet.  Neat.  This plugin will grab temps from a Model E if the
#   $device variable contains a dotted-quad IP address.  Don't use a hostname!
#   

{

  package BigBrother;

  sub new
  {
    my $proto = shift;
    my $class = ref($proto) || $proto;
    my $self  = {};
    bless($self, $class);

    (my $prog = $0) =~ s/.*\/(.*)$/$1/;

    $ENV{ 'PROG' } = $prog;

    return $self;
  }

  sub check_bbhome
  {
    my $self = shift;

    if (defined($ENV{ 'BBHOME' })) {
      if (-d $ENV{ 'BBHOME' }) {
        return 1;
      }
    }
    
    return 0;
  }

  sub report
  {
    my ($self, $hostname, $testname, $color, $message) = @_;

    unless ($self->check_bbhome) {
      die "$ENV{ 'PROG' }: BBHOME is not set or is not a valid directory!";
    }

    my $date = localtime;

    my $line = "status $hostname.$testname $color $date\n$message";

    eval {
      system("$ENV{ 'BB' } $ENV{ 'BBDISP' } \"$line\"");
    };

    die "$ENV{ 'PROG' }: $@" if ($@);
  }

  1;
}

use strict;

sub get_serial_temptrax_readings
{
  my $device = shift;
  my $port;

  if ($^O =~ /win32/i) {
    eval { require Win32::SerialPort; };
    die "$ENV{ 'PROG' }: No Win32::SerialPort detected!" if ($@);
  } else {
    eval { require Device::SerialPort; };
    die "$ENV{ 'PROG' }: No Device::SerialPort detected!" if ($@);
  }

  if ($^O =~ /win32/i) {
    $port = Win32::SerialPort->new($device);
  } else {
    $port = Device::SerialPort->new($device);
  }

  die "$ENV{ 'PROG' }: Unable to open serial device!" unless ($port);

  $port->baudrate(9600);
  $port->parity('none');
  $port->databits(8);
  $port->stopbits(1);
  $port->handshake('none');
  $port->dtr_active(1);

  $port->write_settings()
    or die "$ENV{'PROG'}: Unable to set serial device: $!";

  sleep 1;

  # Send any char to the port and the TempTrax should spit back
  # probe readings and a battery status.
  $port->write("\n");

  sleep 1;

  (my $result = $port->input()) =~ s/(\r| )//g;

  if ($result eq '') {
    die "$ENV{ 'PROG' }: Did not get reading from TempTrax";
  } else {
    return split(/\n/, $result);
  }
}

sub get_network_temptrax_readings
{
  my $device = shift;
  my @temps;

  eval { require LWP::UserAgent; };
  die "$ENV{ 'PROG' }: No LWP::UserAgent detected!" if ($@);
  eval { require HTML::TreeBuilder; };
  die "$ENV{ 'PROG' }: No HTML::TreeBuilder detected!" if ($@);

  my $url = "http://$device/";
  my $ua  = new LWP::UserAgent;

  $ua->agent('BB-TempTrax/2.0');

  my $req = HTTP::Request->new(GET => $url);
  my $res = $ua->request($req);

  die "$ENV{ 'PROG' }: Connection to $url failed!" unless ($res->is_success);

  my $tree = new HTML::TreeBuilder;
  $tree->parse($res->content);
  $tree->eof();

  # The temps are in the second table
  my $table = ( $tree->look_down('_tag', 'table') )[1];

  foreach my $row ($table->look_down('_tag', 'tr')) {
    # We want the text from the second td element in each row
    my $temp = ( $row->look_down('_tag', 'td') )[1];
    push(@temps, $temp->as_text);
  }

  $tree->delete();

  return @temps;
}

# CONFIG VARS 
# Change these to suit your needs.
my $device    = '/dev/ttyS0';
my $testname  = 'ttrax';
my %warntemps = ( low => 37.0, high => 90.0 );
my %pagetemps = ( low => 32.0, high => 95.0 );

# Shouldn't need to fiddle with anything below here.
my $bb        = new BigBrother;
my $hostname;
my $message;
my $color;
my @readings;
my %probes;

if ($device =~ /^\d+\.\d+\.\d+\.\d+$/) {
  @readings = get_network_temptrax_readings($device);
} else {
  @readings = get_serial_temptrax_readings($device);
}

if ($^O =~ /win32/i) {
  # Definitely works on 2k, supposedly works on NT, too
  $hostname = `hostname`;
} else {
  $hostname = `hostname -f`;
}

# Massage hostname a little for BB
chomp($hostname);
$hostname =~ s/\./,/g;

for (my $ctr = 0; $ctr <= $#readings; $ctr++) {
  # This assumes that the output we're interested in is only digits,
  # a decimal point, or a hyphen.
  if ($readings[$ctr] =~ /[\d\.\-]+/) {
    # Apparently the TempTrax Model F, at least, returns -99.9 for
    # unattached probes.  Ugly hack.
    $probes{ $ctr } = $readings[$ctr] unless ($readings[$ctr] == -99.9);
  }
}

foreach my $probe (sort(keys(%probes))) {
  my $temp = $probes{ $probe };

  if ($temp >= $pagetemps{ 'high' } || $temp <= $pagetemps{ 'low' }) {
    $color = 'red';
    $message .= sprintf("PANIC: Probe %d reads %.1fF\n", $probe, $temp);
  } elsif ($temp >= $warntemps{ 'high' } || $temp <= $warntemps{ 'low' }) {
    if ($color ne 'red') {
      $color = 'yellow'
    }
    $message .= sprintf("WARNING: Probe %d reads %.1fF\n", $probe, $temp);
  } else {
    unless ($color) {
      $color = 'green';
    }
    $message .= sprintf("OK: Probe %d reads %.1fF\n", $probe, $temp);
  }
}

unless ($message) {
  $color   = 'red';  
  $message = "PANIC: No probe(s) attached??\n";
}

if ($bb->check_bbhome) {
  $bb->report($hostname, $testname, $color, $message);
} else {
  print "BBHOME is unset or isn't a valid directory...  Printing to STDOUT.\n";
  print $message;
}
