diff --git a/plugins/ups/upsmonpro_ b/plugins/ups/upsmonpro_ new file mode 100644 index 00000000..0008e4e2 --- /dev/null +++ b/plugins/ups/upsmonpro_ @@ -0,0 +1,222 @@ +#!/usr/bin/perl + +=head1 NAME + +upsmonpro_ - Munin plugin to monitor Powercom UPS via UPSMON PRO program L + +=head1 INSTALLATION + + /etc/munin/plugins/upsmonpro_load -> /usr/share/munin/plugins/upsmonpro_ + /etc/munin/plugins/upsmonpro_status -> /usr/share/munin/plugins/upsmonpro_ + /etc/munin/plugins/upsmonpro_temp -> /usr/share/munin/plugins/upsmonpro_ + /etc/munin/plugins/upsmonpro_voltage -> /usr/share/munin/plugins/upsmonpro_ + +=head1 CONFIGURATION + +Environment variables: + + host - UPSMON PRO server host, default localhost + port - UPSMON PRO port, default 2601 + +Example configuration (optional): + + [upsmonpro_*] + env.host localhost + env.port 2601 + +=head1 MAGIC MARKERS + + #%# family=auto + #%# capabilities=autoconf + #%# capabilities=suggest + +=head1 AUTHOR + +Copyright (C) 2017 pru.mike@gmail.com + +=head1 LICENSE + +GPLv2. + +=cut + +use strict; +use warnings; +use feature qw/say/; +use Munin::Plugin; +use IO::Socket::INET; +use Time::HiRes qw/usleep/; +use English qw/-no-math-vars/; +our $VERSION = '0.0.1'; +$OUTPUT_AUTOFLUSH++; + +our $DEFAULT_HOST = 'localhost'; +our $DEFAULT_PORT = 2601; +our %TYPES = ( + voltage => [qw/input output/], + load => [qw/battery_load battery_capacity/], + temp => [q/temp/], + status => [qw/power_failure low_battery voltage_status ups_status battery_test/] +); +our @TYPES = sort keys %TYPES; +my %DISPATCH_TABLE = (); +my $pkg = __PACKAGE__; + +$DISPATCH_TABLE{"${pkg}::run_suggest"} = \&run_suggest; +$DISPATCH_TABLE{"${pkg}::run_autoconf"} = \&run_autoconf; +for my $t (@TYPES) { + $DISPATCH_TABLE{"${pkg}::run_config_$t"} = \&{"run_config_$t"}; + $DISPATCH_TABLE{"${pkg}::run_autoconf_$t"} = \&run_autoconf; + $DISPATCH_TABLE{"${pkg}::run_default_$t"} = sub { + run_default(@{ $TYPES{$t} }); + }; +} + +find_key($ARGV[0])->(); + +sub find_key { + my $argv = shift || ''; + my $type_re = join '|', @TYPES; + my $key; + if ($argv =~ /(suggest|autoconf)/i) { + $key = 'run_' . lc($1); + } elsif ($Munin::Plugin::me =~ /upsmonpro_{1,}($type_re)$/) { + my $graph = $1; + $key = 'run_' . ((grep { $argv eq $_ } qw/autoconf config/) ? $argv : 'default') . "_$graph"; + } else { + die "Could not determine script type [@TYPES] ? name=$Munin::Plugin::me\n"; + } + + return $DISPATCH_TABLE{"${pkg}::$key"}; +} + +sub run_config_voltage { + print <<'END'; +graph_title UPS Input/Output Voltage +graph_vlabel volt +graph_scale no +graph_category sensors +input.label input +input.info Input Voltage +input.type GAUGE +output.label output +output.info Output Voltage +output.type GAUGE +END + exit(0); +} + +sub run_config_temp { + print <<'END'; +graph_title UPS Temperature +graph_vlabel celsius +graph_scale no +graph_category sensors +temp.label temperature +temp.type GAUGE +END + exit(0); +} + +sub run_config_load { + print <<'END'; +graph_title UPS Battery Load/Capacity +graph_vlabel precent% +graph_scale no +graph_category sensors +battery_load.label battery_load +battery_load.type GAUGE +battery_capacity.label battery_capacity +battery_capacity.type GAUGE +END + exit(0); +} + +sub run_config_status { + print <<'END'; +graph_title UPS Statuses +graph_vlabel status +graph_scale no +graph_category sensors +power_failure.label power_failure +power_failure.type GAUGE +low_battery.label low_battery +low_battery.type GAUGE +voltage_status.label voltage_status +voltage_status.info 0 normal, 1 boost, 2 buck +voltage_status.type GAUGE +ups_status.label ups_status +ups_status.type GAUGE +battery_test.label battery_test +battery_test.type GAUGE +END + exit(0); +} + +sub run_default { + my $host = $ENV{host} || $DEFAULT_HOST; + my $port = $ENV{port} || $DEFAULT_PORT; + my @val_list = @_; + my $r = gather_data($host, $port); + for (@val_list) { + die "Wrong value: $_" if not exists $r->{$_}; + say "${_}.value $r->{$_}"; + } +} + +sub run_suggest { + local $LIST_SEPARATOR = "\n"; + say "@TYPES"; + exit(0); +} + +sub run_autoconf { + if (gather_data($DEFAULT_HOST, $DEFAULT_PORT)->{response} eq 'ok') { + say "yes"; + } else { + say "no ($DEFAULT_HOST:$DEFAULT_PORT not response)"; + } + exit(0); +} + +sub gather_data { + my $host = shift; + my $port = shift; + my %data = map { ($_ => 'U') } map { @{ $TYPES{$_} } } @TYPES; + $data{response} = 'failed'; + + my $sock = IO::Socket::INET->new( + PeerAddr => $host, + Proto => 'udp', + PeerPort => $port, + Blocking => 0 + ) or die "Cannot create socket: $@"; + + my $pattern = pack 'AAAACAAA', split //, 'PCMG1END'; + $sock->send($pattern) or die "send to $host: $!"; + usleep(200); + my $data; + my $buf; + while ($sock->read($buf, 32)) { + $data .= $buf; + } + if (defined $data and $data =~ /^PCMR.*END$/) { + my @data = unpack('C*', $data); + %data = ( + response => 'ok', + input => $data[6] + 256 * $data[5], + output => $data[8] + 256 * $data[7], + battery_load => $data[11], + battery_capacity => $data[12], + temp => $data[15], + power_failure => ($data[18] ? 1 : 0), + low_battery => ($data[19] ? 1 : 0), + + #voltage_status: 0 = normal, 3 = buck, 2 = boost + voltage_status => ($data[17] == 0 ? 0 : ($data[17] == 3 ? 2 : 1)), + ups_status => ($data[21] ? 1 : 0), + battery_test => ($data[23] ? 1 : 0), + ); + } + return \%data; +}