#!/bin/bash
#==============================================================================#
#                                                                              #
#  check status of sensor of HW Group Poseidon 3268. For more info refer to:   #
#  http://www.hw-group.com/products/poseidon/poseidon_3268_en.html             #
#  2012  by Florian Lechner                                                    #
#                                                                              #
#==============================================================================#
#                                                                              #
#   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, see <http://www.gnu.org/licenses/>.      #
#                                                                              #
#==============================================================================#

#  Munin autoconf and snmpautoconf magic
#%# family=auto snmpauto contrib
#%# capabilities=snmpconf

E_OK="0"			#  everything went allright
E_UNKNOWN="1"			#  "catch all" for otherwise unhandled errors

E_ARG="81"			#  invalid argument
E_USAGE="82"			#  wrong program name or arguments
E_SNMPGET="83"			#  error while executing the 'snmpget' utility

E_COMMANDNOTFOUND="127"		#  unable to locate binary

#==============================================================================#
#                        SNMP OIDs from the Poseidon MIB                       #
#==============================================================================#

SYS_LOC_OID=".1.3.6.1.2.1.1.6.0"		 #  SNMP-location
SYS_NAME_OID=".1.3.6.1.2.1.1.5.0"		 #  device name (string)
SYS_DESCR_OID=".1.3.6.1.2.1.1.1"		 #  text describing the device
SYS_UPTIME_OID=".1.3.6.1.2.1.1.3.0"		 #  time(in tens of milliseconds
						 #+ since the last reboot
IN_NAME_OID=".1.3.6.1.4.1.21796.3.3.1.1.3."	 #  binary input name (string)
IN_STATE_OID=".1.3.6.1.4.1.21796.3.3.1.1.2."	 #  binary input states(integer)
IN_ALARM_OID=".1.3.6.1.4.1.21796.3.3.1.1.4."	 #  alarm for the binary input,
						 #+ generated by the device
						 #+ under defined conditions
						 #+ 0=Invalid, 1=Normal,
						 #+ 2=AlarmState, 3=Alarm
SENS_NAME_OID=".1.3.6.1.4.1.21796.3.3.3.1.2."	 #  sensor name (string)
SENS_STATE_OID=".1.3.6.1.4.1.21796.3.3.3.1.4."	 #  binary input states(integer)
SENS_VALUE_OID=".1.3.6.1.4.1.21796.3.3.3.1.6."	 #  integer (decimal * 10)
SENS_ID_OID=".1.3.6.1.4.1.21796.3.3.99.1.2.1.4." #  unique sensor ID (integer)
						 #+ representation of the
						 #+ temperature (integer)
SENS_UNIT_OID=".1.3.6.1.4.1.21796.3.3.3.1.9."	 #  0=°C,1=°F,2=°K,3=%,4=V,5=mA,
RTS_OUTPUT_OID=".1.3.6.1.4.1.21796.3.3.2.1.2."	 #  binary input state (integer)
						 #+ 6=unknown, 7=pulse, 8=switch

#  define some Poseidon specific stuff:
STATE_OK="1"
STATE_WARN="2"
STATE_CRIT="3"
UNITS=("C" "F" "K" "%" "V" "mA" "unknown" "pulse" "switch")
TYPES=("Temp" "Temp" "Temp" "Hum" "Volt" "Curr" "Unkn" "Pulse" "Switch")

declare -A sensorsOfType
IFS=" 	
"

#==============================================================================#

#  printMuninConfig ()
#  print output for munin auto configuration
printMuninConfig () {
	if [ ! $hostAddr = 'localhost' ]; then
        	cat <<- EOT
host_name $hostAddr
EOT
	fi

	for ((_sensorType=0; _sensorType<=${#UNITS[*]};_sensorType++)); do
		#  skip graphs without sensors
		if [ -z "${sensorsOfType[$_sensorType]}" ]; then
			continue
		fi

                _firstSensor="1"
		#  print specific config for each sensor
		for _sensorNr in ${sensorsOfType[$_sensorType]}; do
			getSensorData $_sensorNr
			getSystemInfo

                        if [ $_firstSensor = "1" ]; then
                                #  graph headers
                                cat <<- EOT
multigraph $sensorType
graph_title $sensorLocation - $sensorType [$sensorUnit]
graph_args --base 1000 -l 0
graph_vlabel $sensorUnit
graph_category sensors
graph_info This graph shows $sensorType history.
EOT
                        fi
                        _firstSensor="0"
	        	cat <<- EOT
$sensorType$_sensorNr.label $sensorName
$sensorType$_sensorNr.info  This graph shows $sensorType$_sensorNr history.
$sensorType$_sensorNr.draw LINE2
EOT
		done
	done
}

#  printMuninSnmpConfig ()
#  print output for munin snmp auto configuration
printMuninSnmpConfig () {
        cat <<- EOT
require $SENS_VALUE_OID$sensorNumber [0-9]
require $SENS_STATE_OID$sensorNumber
EOT
        return 0;
}

#  printMuninStatus ()
#  print status output for munin
printMuninStatus () {
        for ((_sensorType=0; _sensorType<=${#UNITS[*]};_sensorType++)); do
                #  skip graphs without sensors
                if [ -z "${sensorsOfType[$_sensorType]}" ]; then
                        continue
                fi
		_firstSensor="1"

                #  print config section
                for _sensorNr in ${sensorsOfType[$_sensorType]}; do
                        getSensorData $_sensorNr
			if [ $_firstSensor = "1" ]; then
				#  graph headers
				cat <<- EOT
multigraph $sensorType
EOT
			fi
			_firstSensor="0"

			#  print graph details
			cat <<- EOT
$sensorType$_sensorNr.value $(( $sensorValue / 10 ))
EOT
		done
		echo ""
	done
}

#  getSensorData(sensorNr)
#  fetch state, value, name unit and sensor type for the sensor
#+ with the number "sensorNr"
getSensorData() {
	_sensorNr=$1
	sensorState=`snmpGet $hostAddr $SENS_STATE_OID$_sensorNr || err \
		"Fatal: snmpget failed with code \"$?\"! Exiting..." $E_SNMPGET`
	sensorValue=`snmpGet $hostAddr $SENS_VALUE_OID$_sensorNr || err \
		"Fatal: snmpget failed with code \"$?\"! Exiting..." $E_SNMPGET`
	sensorName=`snmpGet $hostAddr $SENS_NAME_OID$_sensorNr || err \
		"Fatal: snmpget failed with code \"$?\"! Exiting..." $E_SNMPGET`
	sensorUnit=`getSensorUnitString $_sensorNr`
	sensorType=`getSensorType $_sensorNr`
}

#  getSystemInfo()
#  fetch general information about the system
getSystemInfo() {
	sensorLocation="`snmpGet $hostAddr $SYS_LOC_OID`"
}

#  snmpGet (hostAddr, OID)
#  use snmpget to fetch a given OID, using above configuration
snmpGet () {
	_oid="$2"

	_host="$1"
	_exit="0"

	#  fetch the requested OID
	_longValue="`snmpget	-O v\
				-m $MIBS\
				-v $SNMPVERSION\
				-c $SNMPCOMMUNITY\
				$_host $_oid 2>/dev/null`"

	_exitStatus="$?"

	echo ${_longValue#*:}	#  remove the type from the answer
	return $_exitStatus
}

#  get unit (string)
#+ find out the unit of the output of a given sensor. possible units are:
#+ "C" "F" "K" "%" "V" "mA" "unknown" "pulse" "switch"
getSensorUnitString () {
	_sensorNr=$1
	_sensorUnit=`snmpGet $hostAddr $SENS_UNIT_OID$_sensorNr || err \
		"Fatal: snmpget failed with code \"$?\"! Exiting..." $E_SNMPGET`
	echo ${UNITS[$_sensorUnit]}
}

#  get type (string)
#+ find out what type of sensor we are dealing with. possible types are:
#+ "Temp" "Hum" "Volt" "Curr" "Unkn" "Pulse" "Switch"
getSensorType () {
	_sensorNr=$1
	_sensorUnit=`snmpGet $hostAddr $SENS_UNIT_OID$_sensorNr || err \
		"Fatal: snmpget failed with code \"$?\"! Exiting..." $E_SNMPGET`
	echo ${TYPES[$_sensorUnit]}
}

#  getAvailableSensorsByType ()
#  check what sensors are available and store them
#+ in the array for the respective unit
getAvailableSensorsByType () {
	_thisSensorNr="1"

	#  initial fetch
	_snmpget=`snmpGet $hostAddr $SENS_UNIT_OID$_thisSensorNr`
	_nextSensorExits=$?
	_unit=`echo "$_snmpget" | tr -d " "`

	#  add next sensor if it exists
	while [ true ]; do
		#  add sensors of the same type to a list
		sensorsOfType[$_unit]="${sensorsOfType[$_unit]} $_thisSensorNr"

		#  fetch next sensor
		_thisSensorNr=$(($_thisSensorNr+1))
		_snmpget=`snmpGet $hostAddr $SENS_UNIT_OID$_thisSensorNr`
		_nextSensorExits=$?

		#  are we done?
		if [ $_nextSensorExits -ne 0 ]; then
			break
		fi

		_unit=`echo "$_snmpget" |cut -d" " -f 4`
	done
}

#  sanitize (<string>)
#  use bash builtin substitutions to sanitize string
#+  allowed chars are: a-z,A-Z,0-9 and "."
#+ if chars have been removed return 1, else return 0
sanitize () {
	input="$1"
	sanitizedInput="${1//[^a-zA-Z0-9.]/}"
	echo "$sanitizedInput"
	if [ "$input" = "$sanitizedInput" ]; then
		return 0
	else
		return 1
	fi
}

#  usage ()
#  print usage
usage () {
	echo "usage: snmp_<host>_poseidon-sensors [config|snmpconf]" 1>&2
	exit $E_USAGE
}

#  err (ErrorMsg, Exitcode)
#  basic error handling
err () {
	if [ $# -eq 2 -a "$2" -lt 256 ]; then
		_errorMsg="$1"
		_exitCode="$2"
	else
		_errorMsg="Fatal: An unknown error occurred! Exiting..."
		_exitCode="$E_UNKNOWN"

	fi

	#  print error message to STDERR ...
	echo "$_errorMsg" >&2
	#  ... and exit with error code.
	exit $_exitCode
}

#==============================================================================#

#  SNMP Config
MIBS=":"			#  we don't use any configured MIBs so we don't
				#+ have to deal with errors in the MIBs
if [ -z $SNMPVERSION ]; then
	SNMPVERSION="1"		#  as of firmware 3.1.5 only SNMPv1 is supported
fi

if [ -z $SNMPCOMMUNITY ]; then
	SNMPCOMMUNITY="public"	#  SNMP community string to read from the device
fi

#  handle -h option
while getopts ":h" opt; do
	case $opt in
	h)	usage
		;;
	\?)	echo "Invalid option: -$OPTARG" >&2
		usage
		;;
	esac
done

#  check for snmpget binary
if [ ! -x `which snmpget` ]; then
	err "Fatal: unable to locate \'snmpget\'! Exiting..." $E_COMMANDNOTFOUND
fi

#  extract pluginname ...
myName="`basename "$0" | cut -d "_" -f 1,3`"

#  ...and hostname
hostAddr="`basename "$0" | cut -d "_" -f 2`"

if [ "$myName" = "snmp_poseidon-sensors" ]; then
	hostAddr=`sanitize $hostAddr || err \
		"Fatal: Invalid argument \"$hostAddr\"! Exiting..." $E_ARG`
	if [ -z "$hostAddr" ]; then
		usage munin
	fi
	getAvailableSensorsByType
	if [ "$1" = "config" ]; then
		printMuninConfig
		exit $E_OK
	elif [ "$1" = "snmpconfig" ]; then
		printMuninSnmpConfig
		exit $E_OK
	else
		printMuninStatus
		exit $E_OK
	fi
else
	usage
fi

