2015-03-04 15:58:48 +01:00
|
|
|
#!/usr/bin/perl
|
|
|
|
|
|
|
|
# This script is used to feed sensor information from Allnet-Devices like e.g.
|
|
|
|
# Allnet 4000 or 4500 into munin. It uses the XML-Interface of the devices to
|
|
|
|
# fetch data.
|
|
|
|
#
|
|
|
|
# written by Michael Meier <michael.meier@fau.de>, License GPLv2
|
|
|
|
#
|
|
|
|
# Usage:
|
|
|
|
# Put this script onto some server, either on your munin-server or onto some
|
|
|
|
# other server that can reach the actual device. Then symlink to it from the
|
|
|
|
# munin-node plugin dir (usually /etc/munin/plugins). The naming of the symlink
|
|
|
|
# is a critical part of the configuration, it needs to be:
|
|
|
|
# SOMETHING_HOSTNAME_TYPE, e.g. allnet_monitordevice42.yourdomain.com_temp
|
|
|
|
# SOMETHING really is just a random name containing no special chars, use
|
|
|
|
# whatever you like.
|
|
|
|
# HOSTNAME is the hostname of the Allnet-Device. This is also the hostname
|
|
|
|
# under which the sensors will show up in munin.
|
|
|
|
# TYPE is the type of sensors you're interested in, valid values are:
|
|
|
|
# temp, hum, amps
|
|
|
|
# If you want to monitor more than one type of sensor, create more than one
|
|
|
|
# symlink.
|
|
|
|
# The plugin will automatically feed all sensors of the selected type to munin,
|
|
|
|
# under the names that you set in the allnets config. Take care with special
|
|
|
|
# characters, in particular Umlauts and the such - munin will probably not like
|
|
|
|
# those in names.
|
|
|
|
#
|
|
|
|
# Note that the format of the XML output these devices deliver varies vastly
|
|
|
|
# between different firmware versions. This script currently understands 3
|
|
|
|
# completely different outputs, but there is no guarantee that's all the
|
|
|
|
# variations that exist. If the script doesn't understand the output of your
|
|
|
|
# device, set the beextratolerant parameter. You might also want to try a
|
|
|
|
# different firmware version.
|
|
|
|
#
|
|
|
|
# The behaviour of the script can be influenced by environment variables. The
|
|
|
|
# variables are (note this is case sensitive!):
|
|
|
|
# username Username for basic HTTP auth (if your device is password protected)
|
|
|
|
# password Password for basic HTTP auth (if your device is password protected)
|
|
|
|
# beextratolerant This needs to be set to 1 for some extremely old/stripped
|
|
|
|
# down devices. Note that this relaxes sanity checks and
|
|
|
|
# will lead to nonsense-sensors showing up on devices that
|
|
|
|
# do not require this.
|
|
|
|
# DUMP set to 1 to enable some debug output (only outside of munin!)
|
|
|
|
# internalsensorname.warning The warning-threshold for the sensor reported
|
|
|
|
# to munin. You need to replace
|
|
|
|
# "internalsensorname" with the munin-internal
|
|
|
|
# name of the sensor, usually sensor+somenumber.
|
|
|
|
# Can be seen in the webinterface.
|
|
|
|
# internalsensorname.critical Same as above, but for the critical-threshold.
|
|
|
|
#
|
|
|
|
# As an example, you could put the following file into /etc/munin/plugin-conf.d:
|
|
|
|
# [allnet_monitordevice42.yourdomain.com_*]
|
|
|
|
# env.username horst
|
|
|
|
# env.password topsecret
|
|
|
|
# env.sensor14.warning 10.0
|
|
|
|
# env.sensor14.critical 15.0
|
|
|
|
|
|
|
|
# One tuneable:
|
|
|
|
# Timeout for requests.
|
|
|
|
my $timeout = 3; # the LWP default of 180 secs would be way too long
|
|
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
# No need to change those, can be overriden from commandline.
|
|
|
|
my $hostname = 'localhost';
|
|
|
|
my $valtype = 'temp'; # or humidity or amps
|
|
|
|
# These are tried one after another.
|
|
|
|
my @xmlpaths = ('/xml/sensordata.xml', '/xml');
|
|
|
|
|
|
|
|
use LWP::UserAgent;
|
|
|
|
use XML::Simple;
|
|
|
|
|
|
|
|
# AuthAgent is simply LWP::UserAgent with a reimplemented version of the
|
|
|
|
# method basic auth. Theoretically/according to the documentation, the base
|
|
|
|
# implementation should be able to do everything that is needed as well, but
|
|
|
|
# it seems to broken and "the internet" suggests it has been for a long time.
|
|
|
|
{
|
|
|
|
package AuthAgent;
|
|
|
|
use base 'LWP::UserAgent';
|
|
|
|
|
|
|
|
sub get_basic_credentials {
|
|
|
|
if (defined($ENV{'username'}) && defined($ENV{'password'})) {
|
|
|
|
return $ENV{'username'}, $ENV{'password'};
|
|
|
|
} else {
|
|
|
|
return 'admin', 'password';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
# Par. 0: URL
|
|
|
|
# Returns: The contents of the website
|
|
|
|
sub geturl($) {
|
|
|
|
my $url = shift();
|
|
|
|
|
|
|
|
my $ua = AuthAgent->new();
|
|
|
|
$ua->agent($0);
|
|
|
|
$ua->timeout($timeout);
|
|
|
|
# Create a request
|
|
|
|
my $req = HTTP::Request->new(GET => $url);
|
|
|
|
# Pass request to the user agent and get a response back
|
|
|
|
my $res = $ua->request($req);
|
|
|
|
# Check the outcome of the response
|
|
|
|
unless ($res->is_success()) {
|
|
|
|
#print("ERROR getting data from $url\n");
|
|
|
|
return undef;
|
|
|
|
}
|
|
|
|
return $res->content();
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((@ARGV > 0) && ($ARGV[0] eq "autoconf")) {
|
|
|
|
print("No\n");
|
|
|
|
exit(0);
|
|
|
|
}
|
|
|
|
my $progname = $0;
|
|
|
|
if ($progname =~ m/[-_]temp$/) {
|
|
|
|
$valtype = 'temp';
|
|
|
|
} elsif ($progname =~ m/[-_]temperature$/) {
|
|
|
|
$valtype = 'temp';
|
|
|
|
} elsif ($progname =~ m/[-_]hum$/) {
|
|
|
|
$valtype = 'humidity';
|
|
|
|
} elsif ($progname =~ m/[-_]humidity$/) {
|
|
|
|
$valtype = 'humidity';
|
|
|
|
} elsif ($progname =~ m/[-_]amps$/) {
|
|
|
|
$valtype = 'amps';
|
|
|
|
} elsif ($progname =~ m/[-_]ampere$/) {
|
|
|
|
$valtype = 'amps';
|
|
|
|
}
|
|
|
|
if ($progname =~ m/.+_(.+?)_.+/) {
|
|
|
|
$hostname = $1;
|
|
|
|
}
|
|
|
|
my $sensorxml;
|
|
|
|
foreach $xp (@xmlpaths) {
|
|
|
|
$sensorxml = geturl('http://' . $hostname . $xp);
|
|
|
|
if (defined($sensorxml)) { last; }
|
|
|
|
}
|
|
|
|
unless (defined($sensorxml)) {
|
|
|
|
print("# Sorry, Failed to fetch data.");
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
# VERY old firmware versions have HTML crap around the actual XML.
|
|
|
|
$sensorxml =~ s!.*<xml>(.*)</xml>.*!$1!sg;
|
|
|
|
if (defined($ENV{'DUMP'}) && ($ENV{'DUMP'} eq '1')) {
|
|
|
|
print($sensorxml . "\n");
|
|
|
|
}
|
|
|
|
if ((@ARGV > 0) && ($ARGV[0] eq "config")) {
|
|
|
|
if ($valtype eq 'humidity') {
|
|
|
|
print("graph_title Humidity sensors\n");
|
|
|
|
print("graph_args --lower-limit 0 --upper-limit 100.0\n");
|
|
|
|
print("graph_vlabel percent\n");
|
|
|
|
} elsif ($valtype eq 'temp') {
|
|
|
|
print("graph_title Temperature sensors\n");
|
|
|
|
print("graph_vlabel degC\n");
|
|
|
|
} elsif ($valtype eq 'amps') {
|
|
|
|
print("graph_title Electric current sensors\n");
|
|
|
|
print("graph_args --lower-limit 0\n");
|
|
|
|
print("graph_vlabel Ampere\n");
|
|
|
|
}
|
2017-02-20 22:14:23 +01:00
|
|
|
print("graph_category sensors\n");
|
2015-03-04 15:58:48 +01:00
|
|
|
print("host_name $hostname\n");
|
|
|
|
}
|
|
|
|
my $sensordata = XMLin($sensorxml, KeyAttr => { }, ForceArray => [ 'data' ] );
|
|
|
|
my $beextratolerant = 0;
|
|
|
|
if (defined($ENV{'beextratolerant'})) {
|
|
|
|
if (($ENV{'beextratolerant'} =~ m/^y/) || ($ENV{'beextratolerant'} eq '1')) {
|
|
|
|
$beextratolerant = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#print($sensordata->[0]);
|
|
|
|
foreach $k (keys($sensordata)) {
|
|
|
|
if ($k =~ m/^n(\d+)$/) { # Special handling: Could be output from the OLD XML interface.
|
|
|
|
my $nr = $1;
|
|
|
|
if (defined($sensordata->{'s'.$nr})
|
|
|
|
&& defined($sensordata->{'t'.$nr})
|
|
|
|
&& defined($sensordata->{'min'.$nr})
|
|
|
|
&& defined($sensordata->{'max'.$nr})) {
|
|
|
|
# OK, all values available, really is from the old XML interface.
|
|
|
|
if ($sensordata->{'s'.$nr} eq '0') { next; } # 0 means no sensor.
|
|
|
|
# OK, lets map the sensortype.
|
|
|
|
my $st = 'temp';
|
|
|
|
if ($sensordata->{'s'.$nr} eq '65') { $st = 'humidity'; }
|
|
|
|
if ($sensordata->{'s'.$nr} eq '101') {
|
|
|
|
# potential FIXME: 101 is actually "water/contact sensor", but since it
|
|
|
|
# returns 0.0 and 100.0 as values, we can treat it just like humidity
|
|
|
|
# for simplicity.
|
|
|
|
$st = 'humidity';
|
|
|
|
}
|
|
|
|
if ($valtype ne $st) { next; } # these aren't the droids you're looking for
|
|
|
|
if ((@ARGV > 0) && ($ARGV[0] eq "config")) {
|
|
|
|
print("sensor${nr}.label " . $sensordata->{'n'.$nr} . "\n");
|
|
|
|
print("sensor${nr}.type GAUGE\n");
|
|
|
|
if (defined($ENV{"sensor${nr}.warning"})) { printf("sensor%s.warning %s\n", $nr, $ENV{"sensor${nr}.warning"}); }
|
|
|
|
if (defined($ENV{"sensor${nr}.critical"})) { printf("sensor%s.critical %s\n", $nr, $ENV{"sensor${nr}.critical"}); }
|
|
|
|
} else {
|
|
|
|
print("sensor${nr}.value " . $sensordata->{'t'.$nr}. "\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
# Fall through - probably wasn't the old XML anyways.
|
|
|
|
}
|
|
|
|
my $onesens = $sensordata->{$k};
|
|
|
|
unless (defined($onesens->{'value_float'})
|
|
|
|
&& defined($onesens->{'name'})
|
|
|
|
&& defined($onesens->{'min_abs_float'})
|
|
|
|
&& defined($onesens->{'max_abs_float'})
|
|
|
|
&& defined($onesens->{'unit'})) {
|
|
|
|
# Not all values available -> no sane sensor data (or the "system" block in the XML)
|
|
|
|
unless ($beextratolerant) { next; }
|
|
|
|
# Maybe yet another firmware version that does not deliver all of these values,
|
|
|
|
# so lets check for the absolute MINIMUM set.
|
|
|
|
unless (defined($onesens->{'value_float'}) && defined($onesens->{'name'})) {
|
|
|
|
next; # Minimum set not available either.
|
|
|
|
}
|
|
|
|
if ($onesens->{'value_float'} < -20000.0) { next; } # Invalid value
|
|
|
|
# OK, so fill in the blanks
|
|
|
|
unless (defined($onesens->{'unit'})) { $onesens->{'unit'} = 'C'; }
|
|
|
|
unless (defined($onesens->{'min_abs_float'})) {
|
|
|
|
$onesens->{'min_abs_float'} = $onesens->{'value_float'} - 0.01;
|
|
|
|
}
|
|
|
|
unless (defined($onesens->{'max_abs_float'})) {
|
|
|
|
$onesens->{'max_abs_float'} = $onesens->{'value_float'} + 0.01;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (($onesens->{'min_abs_float'} == 0.0)
|
|
|
|
&& ($onesens->{'max_abs_float'} == 0.0)
|
|
|
|
&& ($onesens->{'value_float'} == 0.0)) {
|
|
|
|
next; # If the sensor never showed anything but 0.0, we do not care about it.
|
|
|
|
}
|
|
|
|
if ($onesens->{'unit'} eq 'A AC') { $onesens->{'unit'} = 'A'; }
|
|
|
|
if ($onesens->{'unit'} =~ m/C$/) {
|
|
|
|
$onesens->{'unit'} = 'C'; # Remove WTF8-Fuckup
|
|
|
|
}
|
|
|
|
if (($valtype eq 'temp') && ($onesens->{'unit'} ne 'C')) { next; }
|
|
|
|
if (($valtype eq 'humidity') && ($onesens->{'unit'} ne '%')) { next; }
|
|
|
|
if (($valtype eq 'amps') && ($onesens->{'unit'} ne 'A')) { next; }
|
|
|
|
if ((@ARGV > 0) && ($ARGV[0] eq "config")) {
|
|
|
|
print("${k}.label " . $onesens->{'name'} . "\n");
|
|
|
|
#print("${k}.info " . $onesens->{'name'} . "\n");
|
|
|
|
print("${k}.type GAUGE\n");
|
|
|
|
if (defined($ENV{"${k}.warning"})) { printf("%s.warning %s\n", $k, $ENV{"${k}.warning"}); }
|
|
|
|
if (defined($ENV{"${k}.critical"})) { printf("%s.critical %s\n", $k, $ENV{"${k}.critical"}); }
|
|
|
|
} else {
|
|
|
|
if ($onesens->{'value_float'} < -20000.0) { # Invalid readings return -20480.0
|
|
|
|
print("${k}.value U\n");
|
|
|
|
} else {
|
|
|
|
print("${k}.value " . $onesens->{'value_float'} . "\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|