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