#!/usr/bin/perl -w
#
# RETrieve_Machine_Temperature, retmt:
#  Retrieve the temperatures measured within the RPI3 machine and report
#  them to Xymon.
#
# Written by W.J.M. Nelis, wim.nelis@ziggo.nl, 2017.10
#
use strict ;
use Time::Piece ;			# Format time
#
# Installation constants.
# -----------------------
#
my $XyDisp= $ENV{XYMSRV} ;		# Name of monitor server
my $XySend= $ENV{XYMON} ;		# Monitor interface program
my $FmtDate= "%Y.%m.%d %H:%M:%S" ;	# Default date format
   $FmtDate= $ENV{XYMONDATEFORMAT} if exists $ENV{XYMONDATEFORMAT} ;
my $HostName= `hostname` ;		# 'Source' of this test
chomp $HostName ;
my $TestName= 'env' ;			# Test name
my $ThresholdYellow= 50 ;		# Warning threshold [C]
my $ThresholdRed   = 60 ;		# Error threshold [C]
my @ColourOf= ( 'red', 'yellow', 'clear', 'green' ) ;
my $CpuFil= '/sys/class/thermal/thermal_zone0/temp' ;
my $GpuCmd= '/usr/bin/vcgencmd measure_temp' ;
#
# Global variables.
# -----------------
#
my $Now= localtime ;			# Timestamp of tests
   $Now= $Now->strftime( $FmtDate ) ;
my $Colour=  3 ;			# Test status
my $Result= '' ;			# Message to sent to Xymon
my %Temp ;				# Temperature readings
my %ErrMsg ;				# Error messages
   $ErrMsg{$_}= []		foreach ( @ColourOf ) ;
#
# Issue a message the the logfile. As this script is run periodically by Xymon,
# StdOut will be redirected to the logfile.
#
sub LogMessage {
  my $Msg= shift ;
  my @Time= (localtime())[0..5] ;
  $Time[4]++ ;  $Time[5]+= 1900 ;
  chomp $Msg ;
  printf "%4d%02d%02d %02d%02d%02d %s\n", reverse(@Time), $Msg ;
}  # of LogMessage
sub max($$) { return $_[0] > $_[1] ? $_[0] : $_[1] ; }
sub min($$) { return $_[0] < $_[1] ? $_[0] : $_[1] ; }
#
# Function InformXymon sends the message, in global variable $Result, to
# the Xymon server. Any error messages in %ErrMsg are prepended to the message
# and the status (colour) of the message is adapted accordingly.
#
sub InformXymon() {
  my $ErrMsg= '' ;
  my $Clr ;				# Colour of one sub-test
  for ( my $i= 0 ; $i < @ColourOf ; $i++ ) {
    $Clr= $ColourOf[$i] ;
    next			unless @{$ErrMsg{$Clr}} ;
    $Colour= min( $Colour, $i ) ;
    $ErrMsg.= "&$Clr $_\n"	foreach ( @{$ErrMsg{$Clr}} ) ;
  }  # of foreach
  $ErrMsg.= "\n"		if $ErrMsg ;
  $Colour= $ColourOf[$Colour] ;
  $Result= "\"status $HostName.$TestName $Colour $Now\n" .
	   "<b>Temperature sensor readings</b>\n\n" .
           "$ErrMsg$Result\"\n" ;
  `$XySend $XyDisp $Result` ;           # Inform Xymon
  $Result= '' ;                         # Reset message parameters
  $Colour=  3 ;
  $ErrMsg{$_}= []		foreach ( @ColourOf ) ;
}  # of InformXymon
#
# Function ReadSensors retrieves the temperatures and their thresholds. The
# results are stored in hash %Temp. In case of an error, the result area
# will be empty.
#
sub ReadSensors() {
  my @Lines ;				# Content of one 'file'
  %Temp= () ;				# Clear result area
  unless ( defined open(FH,'<',$CpuFil) ) {
    push @{$ErrMsg{clear}}, "Cannot read CPU temperature from $CpuFil:\n" .
			    "  $!" ;
  } else {
    @Lines= <FH> ;			# Read entire file
    unless ( @Lines == 1  and  $Lines[0] =~ m/^(\d+)/ ) {
      push @{$ErrMsg{clear}}, "Cannot read CPU temperature from $CpuFil\n:" .
			      "  Unexpected input" ;
    } else {
      $Temp{CPU}{label}= 'CPU' ;
      $Temp{CPU}{input}= sprintf( '%.1f', $1/1000 ) ;
      $Temp{CPU}{max}  = $ThresholdYellow ;
      $Temp{CPU}{crit} = $ThresholdRed ;
    }  # of else
  }  # of else
  @Lines= `$GpuCmd` ;			# Retrieve information
  if ( @Lines == 0 ) {
    push @{$ErrMsg{clear}}, "Cannot read GPU temperature from $GpuCmd:\n" .
			    "  no data returned" ;
  } else {
    chomp $Lines[0] ;
    unless ( $Lines[0] =~ m/^temp=([\d\.]+).+C$/ ) {
      push @{$ErrMsg{clear}}, "Cannot read GPU temperature from $GpuCmd:\n" .
			      "  unexpected input : $Lines[0]" ;
    } else {
      $Temp{GPU}{label}= 'GPU' ;
      $Temp{GPU}{input}= $1 ;
      $Temp{GPU}{max}  = $ThresholdYellow ;
      $Temp{GPU}{crit} = $ThresholdRed ;
    }  # of else
  }  # of else
}  # of ReadSensors
#
# Function BuildMessage formats the collected data into a nice table, performs
# the threshold checks and leaves the results in the status indicator. The
# statistics are added in the DEVMON format to be moved into an RRD.
#
sub BuildMessage() {
  my ($TempMin,$TempAvg,$TempMax)= (100,0,-100) ;	# Temperature statistics
  my $T ;				# Ref to data of one sensor
  my $Clr ;				# Status (colour) of one reading
  if ( scalar(keys %Temp) == 0 ) {
    $Result= "No data received\n" ;
    return ;
  }  # of if
 #
 # Build a table showing the various sensors, their readings and their
 # thresholds.
 #
  $Result = "<table border=1 cellpadding=5>\n" ;
  $Result.= " <tr> <th>Sensor</th> <th>Temp [C]</th> <th>Threshold [C]</th> </tr>\n" ;
  foreach ( sort keys %Temp ) {
    $T= $Temp{$_} ;			# Ref to sensor data
    $Result.= " <tr> " ;
    $Result.= "<td>$$T{label}</td> " ;	# Name of sensor
    $TempMin= min( $TempMin, $$T{input} ) ;
    $TempMax= max( $TempMax, $$T{input} ) ;
    $TempAvg+= $$T{input} ;
    $Clr= 'green' ;			# Assume temperature in range
    if      ( $$T{input} < 10 ) {	# Temperature is too low
      $Clr= 'yellow' ;
      push @{$ErrMsg{$Clr}}, "Temperature of $$T{label} is low" ;
    } elsif ( $$T{input} >= $$T{crit} ) {
      $Clr= 'red' ;
      push @{$ErrMsg{$Clr}}, "Temperature of $$T{label} is too high" ;
    } elsif ( $$T{input} >= $$T{max}  ) {
      $Clr= 'yellow' ;
      push @{$ErrMsg{$Clr}}, "Temperature of $$T{label} is high" ;
    }  # of elsif
    $Result.= "<td align='right'>$$T{input} &$Clr</td> " ;
    $Result.= "<td align='right'>$$T{max}</td> " ;
    $Result.= "</tr>\n" ;
  }  # of foreach
  $Result.= "</table>\n" ;
  $TempAvg/= scalar( keys %Temp ) ;	# Average temperature
  $TempAvg = sprintf( '%5.1f', $TempAvg ) ;
 #
 # Append the statistics, using the DEVMON format.
 #
  $Result.= "<!-- linecount=1 -->\n" ;
  $Result.= "<!--DEVMON RRD: env 0 0\n" ;
  $Result.= "DS:Temperature:GAUGE:600:-100:100 DS:MinTemp:GAUGE:600:-100:100 DS:MaxTemp:GAUGE:600:-100:100\n" ;
  $Result.= "temp $TempAvg:$TempMin:$TempMax\n" ;
  $Result.= "-->" ;
}  # of BuildMessage
#
# ----- MAIN PROGRAM -----
#
ReadSensors ;
BuildMessage ;
InformXymon ;