1package Classes::UPNP::AVM::FritzBox7390::Component::SmartHomeSubsystem; 2our @ISA = qw(Monitoring::GLPlugin::SNMP::Item Classes::UPNP::AVM::FritzBox7390); 3use strict; 4use JSON; 5use File::Slurp qw(read_file); 6 7sub init { 8 my ($self) = @_; 9 if ($self->mode =~ /smarthome::device::list/) { 10 $self->update_device_cache(1); 11 foreach my $ain (keys %{$self->{device_cache}}) { 12 my $name = $self->{device_cache}->{$ain}->{name}; 13 printf "%s %s\n", $ain, $name; 14 } 15 } elsif ($self->mode =~ /smarthome::device/) { 16 $self->update_device_cache(0); 17 my @indices = $self->get_device_indices(); 18 foreach my $ain (map {$_->[0]} @indices) { 19 my %tmp_dev = ( 20 ain => $ain, 21 name => $self->{device_cache}->{$ain}->{name}, 22 functionbitmask => $self->{device_cache}->{$ain}->{functionbitmask}, 23 ); 24 push(@{$self->{smart_home_devices}}, 25 Classes::UPNP::AVM::FritzBox7390::Component::SmartHomeSubsystem::Device->new(%tmp_dev)); 26 } 27 } 28} 29 30sub check { 31 my ($self) = @_; 32 foreach (@{$self->{smart_home_devices}}) { 33 $_->check(); 34 } 35} 36 37sub create_device_cache_file { 38 my ($self) = @_; 39 my $extension = ""; 40 if ($self->opts->community) { 41 $extension .= Digest::MD5::md5_hex($self->opts->community); 42 } 43 $extension =~ s/\//_/g; 44 $extension =~ s/\(/_/g; 45 $extension =~ s/\)/_/g; 46 $extension =~ s/\*/_/g; 47 $extension =~ s/\s/_/g; 48 return sprintf "%s/%s_interface_cache_%s", $self->statefilesdir(), 49 $self->opts->hostname, lc $extension; 50} 51 52sub update_device_cache { 53 my ($self, $force) = @_; 54 my $statefile = $self->create_device_cache_file(); 55 my $update = time - 3600; 56 if ($force || ! -f $statefile || ((stat $statefile)[9]) < ($update)) { 57 $self->debug('force update of device cache'); 58 $self->{device_cache} = {}; 59 my $switchlist = $self->http_get('/webservices/homeautoswitch.lua?switchcmd=getdevicelistinfos'); 60 $switchlist = join(",", map { 61 /<device identifier="(.*?)"/; 62 my $ain = $1; $ain =~ s/\s//g; 63 /<name>(.*?)<\/name>/; $self->{device_cache}->{$ain}->{name} = $1; 64 /functionbitmask="(.*?)"/; $self->{device_cache}->{$ain}->{functionbitmask} = $1; 65 $ain; 66 } ($switchlist =~ /<device.*?<\/device>/g)); 67 $self->save_device_cache(); 68 } 69 $self->load_device_cache(); 70} 71 72sub save_device_cache { 73 my ($self) = @_; 74 $self->create_statefilesdir(); 75 my $statefile = $self->create_device_cache_file(); 76 my $tmpfile = $self->statefilesdir().'/check_nwc_health_tmp_'.$$; 77 my $fh = IO::File->new(); 78 if ($fh->open($tmpfile, "w")) { 79 my $coder = JSON::XS->new->ascii->pretty->allow_nonref; 80 my $jsonscalar = $coder->encode($self->{device_cache}); 81 $fh->print($jsonscalar); 82 $fh->flush(); 83 $fh->close(); 84 } 85 rename $tmpfile, $statefile; 86 $self->debug(sprintf "saved %s to %s", 87 Data::Dumper::Dumper($self->{device_cache}), $statefile); 88} 89 90sub load_device_cache { 91 my ($self) = @_; 92 my $statefile = $self->create_device_cache_file(); 93 if ( -f $statefile) { 94 my $jsonscalar = read_file($statefile); 95 our $VAR1; 96 eval { 97 my $coder = JSON::XS->new->ascii->pretty->allow_nonref; 98 $VAR1 = $coder->decode($jsonscalar); 99 }; 100 if($@) { 101 $self->debug(sprintf "json load from %s failed. fallback", $statefile); 102 delete $INC{$statefile} if exists $INC{$statefile}; # else unit tests fail 103 eval "$jsonscalar"; 104 if($@) { 105 printf "FATAL: Could not load cache in perl format!\n"; 106 $self->debug(sprintf "fallback perl load from %s failed", $statefile); 107 } 108 } 109 $self->debug(sprintf "load %s", Data::Dumper::Dumper($VAR1)); 110 $self->{device_cache} = $VAR1; 111 } 112} 113 114sub get_device_indices { 115 my ($self) = @_; 116 my @indices = (); 117 foreach my $id (keys %{$self->{device_cache}}) { 118 my $name = $self->{device_cache}->{$id}->{name}; 119 if ($self->opts->name) { 120 if ($self->opts->regexp) { 121 my $pattern = $self->opts->name; 122 if ($name =~ /$pattern/i) { 123 push(@indices, [$id]); 124 } 125 } else { 126 if ($self->opts->name =~ /^\d+$/) { 127 if ($id == 1 * $self->opts->name) { 128 push(@indices, [1 * $self->opts->name]); 129 } 130 } else { 131 if (lc $name eq lc $self->opts->name) { 132 push(@indices, [$id]); 133 } 134 } 135 } 136 } else { 137 push(@indices, [$id]); 138 } 139 } 140 return @indices; 141} 142 143 144package Classes::UPNP::AVM::FritzBox7390::Component::SmartHomeSubsystem::Device; 145our @ISA = qw(Monitoring::GLPlugin::SNMP::TableItem Classes::UPNP::AVM::FritzBox7390::Component::SmartHomeSubsystem); 146use strict; 147 148sub finish { 149 my ($self) = @_; 150 $self->{cometdect} = ($self->{functionbitmask} & 0b000001000000) ? 1 : 0; 151 $self->{energy} = ($self->{functionbitmask} & 0b000010000000) ? 1 : 0; 152 $self->{temperature} = ($self->{functionbitmask} & 0b000100000000) ? 1 : 0; 153 $self->{schaltsteck} = ($self->{functionbitmask} & 0b001000000000) ? 1 : 0; 154 $self->{dectrepeater} = ($self->{functionbitmask} & 0b010000000000) ? 1 : 0; 155 if ($self->mode =~ /smarthome::device::status/) { 156 $self->{connected} = $self->http_get('/webservices/homeautoswitch.lua?switchcmd=getswitchpresent&ain='.$self->{ain}); 157 $self->{switched} = $self->http_get('/webservices/homeautoswitch.lua?switchcmd=getswitchstate&ain='.$self->{ain}); 158 chomp $self->{connected}; 159 chomp $self->{switched}; 160 } elsif ($self->mode =~ /smarthome::device::energy/ && $self->{energy}) { 161 eval { 162 $self->{last_watt} = $self->http_get('/webservices/homeautoswitch.lua?switchcmd=getswitchpower&ain='.$self->{ain}); 163 $self->{last_watt} /= 1000; 164 }; 165 } elsif ($self->mode =~ /smarthome::device::consumption/ && $self->{energy}) { 166 eval { 167 $self->{kwh} = $self->http_get('/webservices/homeautoswitch.lua?switchcmd=getswitchenergy&ain='.$self->{ain}); 168 $self->{kwh} /= 1000; 169 }; 170 } elsif ($self->mode =~ /smarthome::device::temperature/ && $self->{temperature}) { 171 eval { 172 $self->{celsius} = $self->http_get('/webservices/homeautoswitch.lua?switchcmd=gettemperature&ain='.$self->{ain}); 173 $self->{celsius} /= 10; 174 }; 175 } 176} 177 178sub check { 179 my ($self) = @_; 180 my $label = $self->{name}; 181 if ($self->mode =~ /smarthome::device::status/) { 182 $self->add_info(sprintf "device %s is %sconnected and switched %s", 183 $self->{name}, $self->{connected} ? "" : "not ", $self->{switched} ? "on" : "off"); 184 if (! $self->{connected} || ! $self->{switched}) { 185 $self->add_critical(); 186 } else { 187 $self->add_ok(sprintf "device %s ok", $self->{name}); 188 } 189 } elsif ($self->mode =~ /smarthome::device::energy/ && $self->{energy}) { 190 $self->add_info(sprintf "device %s consumes %.4f watts", 191 $self->{name}, $self->{last_watt}); 192 $self->set_thresholds(metric => $label."_watt", 193 warning => 80 / 100 * 220 * 10, critical => 90 / 100 * 220 * 10); 194 $self->add_message($self->check_thresholds( 195 metric => $label."_watt", value => $self->{last_watt})); 196 $self->add_perfdata( 197 label => $label."_watt", 198 value => $self->{last_watt}, 199 ); 200 } elsif ($self->mode =~ /smarthome::device::consumption/ && $self->{energy}) { 201 $self->add_info(sprintf "device %s consumed %.4f kwh", 202 $self->{name}, $self->{kwh}); 203 $self->set_thresholds(metric => $label."_kwh", 204 warning => 1000, critical => 1000); 205 $self->add_message($self->check_thresholds( 206 metric => $label."_kwh", value => $self->{kwh})); 207 $self->add_perfdata( 208 label => $label."_kwh", 209 value => $self->{kwh}, 210 ); 211 } elsif ($self->mode =~ /smarthome::device::temperature/ && $self->{temperature}) { 212 $self->add_info(sprintf "device %s temperature is %.4f C", 213 $self->{name}, $self->{celsius}); 214 $self->set_thresholds(metric => $label."_temperature", 215 warning => 40, critical => 50); 216 $self->add_message($self->check_thresholds( 217 metric => $label."_temperature", value => $self->{celsius})); 218 $self->add_perfdata( 219 label => $label."_temperature", 220 value => $self->{celsius}, 221 ); 222 } 223} 224