############################################################################## # # 98_dev_proxy.pm # Copyright by A. Schulz # e-mail: # # This file is part of fhem. # # Fhem 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. # # Fhem 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 fhem. If not, see . # ############################################################################## # $Id: $ package main; use strict; use warnings; #use List::Util qw[min max]; use Data::Dumper; ##################################### sub dev_proxy_setDefaultObservedReadings($); sub dev_proxy_setObservedReading($@); sub dev_proxy_addDevice($$); sub dev_proxy_updateReadings($$); sub dev_proxy_computeCombReading($$); sub dev_proxy_mapDeviceReadingValueDefultMap($$$$$); sub dev_proxy_mapDeviceReadingValue($$$$$); sub dev_proxy_mapValue($$$); sub dev_proxy_eval_map_readings($$); sub dev_proxy_remap_reading($$$); sub dev_proxy_cleanup_readings($); ##################################### sub dev_proxy_Initialize($) { my ($hash) = @_; $hash->{DefFn} = "dev_proxy_Define"; $hash->{UndefFn} = "dev_proxy_Undef"; $hash->{NotifyFn} = "dev_proxy_Notify"; $hash->{SetFn} = "dev_proxy_Set"; $hash->{AttrFn} = "dev_proxy_Attr"; $hash->{AttrList} = "observedReadings setList mapValues mapReadings ". "disable disabledForIntervals useSetExtensions ". $readingFnAttributes; } sub dev_proxy_Define($$) { my ($hash, $def) = @_; my @a = split("[ \t][ \t]*", $def); my $u = "wrong syntax: define dev_proxy [device ...]*"; return $u if(int(@a) < 3); my $devname = shift(@a); my $modname = shift(@a); $hash->{CHANGEDCNT} = 0; my $or = AttrVal($devname, "observedReadings", undef); if(defined($or)) { dev_proxy_setObservedReading($or); } else { dev_proxy_setDefaultObservedReadings($hash); } my %list; $hash->{CONTENT} = \%list; foreach my $a (@a) { foreach my $d (devspec2array($a)) { dev_proxy_addDevice($hash, $d); } } my $valuesMap = AttrVal($devname,'mapValues',undef); if (defined $valuesMap) { $hash->{DEV_READING_VALUE_MAP} = eval($valuesMap); } else { $hash->{DEV_READING_VALUE_MAP} = undef; } dev_proxy_eval_map_readings($hash, AttrVal($devname,'mapReadings',undef)); dev_proxy_updateReadings($hash, undef); return undef; } sub dev_proxy_setDefaultObservedReadings($) { my ($hash) = @_; #$hash->{OBSERVED_READINGS} = ["state","dim", "position"]; $hash->{OBSERVED_READINGS} = {}; $hash->{OBSERVED_READINGS} ->{"state"}=1; $hash->{OBSERVED_READINGS} ->{"dim"}=1; $hash->{OBSERVED_READINGS} ->{"position"}=1; } sub dev_proxy_setObservedReading($@) { my ($hash, @list) = @_; $hash->{OBSERVED_READINGS} = {}; foreach my $a (@list) { $hash->{OBSERVED_READINGS} -> {$a} = 1; } } sub dev_proxy_addDevice($$) { my ($hash, $d) = @_; if($defs{$d}) { $hash->{CONTENT}{$d} = 1; } } sub dev_proxy_Undef($$) { my ($hash, $def) = @_; return undef; } sub dev_proxy_Notify($$) { my ($hash, $dev) = @_; my $name = $hash->{NAME}; if( $dev->{NAME} eq "global" ) { my $max = int(@{$dev->{CHANGED}}); for (my $i = 0; $i < $max; $i++) { my $s = $dev->{CHANGED}[$i]; $s = "" if(!defined($s)); if($s =~ m/^RENAMED ([^ ]*) ([^ ]*)$/) { my ($old, $new) = ($1, $2); if( exists($hash->{CONTENT}{$old}) ) { $hash->{DEF} =~ s/(\s+)$old(\s*)/$1$new$2/; delete( $hash->{CONTENT}{$old} ); $hash->{CONTENT}{$new} = 1; } } elsif($s =~ m/^DELETED ([^ ]*)$/) { my ($name) = ($1); if( exists($hash->{CONTENT}{$name}) ) { $hash->{DEF} =~ s/(\s+)$name(\s*)/ /; $hash->{DEF} =~ s/^ //; $hash->{DEF} =~ s/ $//; delete $hash->{CONTENT}{$name}; delete $hash->{".cachedHelp"}; } } } } return "" if(IsDisabled($name)); # pruefen ob Devices welches das notify ausgeloest hat Mitglied dieser Gruppe ist return "" if (! exists $hash->{CONTENT}->{$dev->{NAME}}); dev_proxy_updateReadings($hash, $dev); readingsSingleUpdate($hash, "LastDevice", $dev->{NAME}, 0); return undef; } sub dev_proxy_updateReadings($$) { my ($hash,$dev) = @_; my $name = $hash->{NAME}; if($hash->{INNTFY}) { Log3 $name, 1, "ERROR: endless loop detected in composite_Notify $name"; return ""; } $hash->{INNTFY} = 1; # my $nrmap; # foreach my $or (keys %{ $hash->{OBSERVED_READINGS}} ) { # my $map; # foreach my $d (keys %{ $hash->{CONTENT}} ) { # next if(!$defs{$d}); # my $or_mapped = dev_proxy_remap_reading($hash, $d, $or); # my $devReadings = ReadingsVal($d,$or_mapped,undef); # if(defined($devReadings)) { # ($devReadings) = dev_proxy_mapDeviceReadingValueDefultMap($hash,$d,$or,$devReadings,1); # $map->{$d}=$devReadings; # } # } # my $newReading = dev_proxy_computeCombReading($or, $map); # if(defined($newReading)) { # $nrmap->{$or}=$newReading; # } # } my $map; foreach my $or (keys %{ $hash->{OBSERVED_READINGS}} ) { foreach my $d (keys %{ $hash->{CONTENT}} ) { next if(!$defs{$d}); my $or_mapped = dev_proxy_remap_reading($hash, $d, $or); my $devReadings = ReadingsVal($d,$or_mapped,undef); if(defined($devReadings)) { my $nReading; ($devReadings, $nReading) = dev_proxy_mapDeviceReadingValueDefultMap($hash,$d,$or,$devReadings,1); # Nur wenn nicht ueberschrieben wurde if(!defined($map->{$or}->{$d})) { $map->{$or}->{$d}=$devReadings; } # falls umgemappt werden soll, den neuen Wert auch aufnehmen (ueberschreibt den eigentlichen Wert für das andere Reading) if($or ne $nReading) { $map->{$nReading}->{$d}=$devReadings; } } } } # jetzt gesammelten Werte kombinieren / zusammenrechnen my $nrmap; foreach my $or (keys %{ $map } ) { my $newReading = dev_proxy_computeCombReading($or, $map->{$or}); if(defined($newReading)) { $nrmap->{$or}=$newReading; } } readingsBeginUpdate($hash); foreach my $d (sort keys %{ $nrmap }) { my $newState = $nrmap->{$d}; my $dd = defined($dev)?" because device $dev->{NAME} has changed":""; Log3 ($name, 5, "Update composite '$name' reading $d to $newState $dd"); readingsBulkUpdate($hash, $d, $newState); } readingsEndUpdate($hash, 1); $hash->{CHANGEDCNT}++; delete($hash->{INNTFY}); dev_proxy_cleanup_readings($hash); } sub dev_proxy_computeCombReading($$) { my ($rName, $map) = @_; my $size = keys %{$map}; if($size<1) { return undef; } my @values = values %{$map}; if($rName eq 'state') { my $tm; foreach my $d (@values) { $tm->{$d}=1; } return join(" ", keys %{ $tm }); } my $maxV = List::Util::max(@values); my $minV = List::Util::min(@values); #if($maxV-$minV<10) { return $minV+($maxV-$minV)/2; #} return $maxV; return undef; } sub dev_proxy_mapDeviceReadingValueDefultMap($$$$$) { my ($hash, $dev, $reading, $val, $incoming) = @_; return dev_proxy_mapDeviceReadingValue($hash->{DEV_READING_VALUE_MAP}, $dev, $reading, $val,$incoming); } # Definition: map {'dev:reading'=>{'val'=>'valnew',.},..} # Priority: zuerst richtungsspezifische (in/out): # in:dev:reading, in:dev:*, in:*:reading, in:*:* (or in:*), # dann Standard: dev:reading, dev:*, *:reading, *:* (or *) # Nur bei out-Richtung (also set) relevant: # Moeglichkeit, Zielreading umzudefinieren. # Dafuer soll der Zielwert in Form WERT:NEWREADINGNAME geliefert werden: # ...{'val'=>'valnew:newreading',..}... sub dev_proxy_mapDeviceReadingValue($$$$$) { my ($map, $dev, $reading, $val, $incoming) = @_; return ($val, $reading) unless defined $map; my $nval; my $selectedMap; # zuerst richtungsspeziefische Map (in/out) ausprobieren my $prefix = $incoming ? 'in:' : 'out:'; $selectedMap = $map->{$prefix.$dev.':'.$reading}; $selectedMap = $map->{$prefix.$dev.':*'} unless defined $selectedMap; $selectedMap = $map->{$prefix.'*:'.$reading} unless defined $selectedMap; $selectedMap = $map->{$prefix.'*:*'} unless defined $selectedMap; $selectedMap = $map->{$prefix.'*'} unless defined $selectedMap; # falls keine passende Map vorhanden ist, oder sie keine passende Regel # enthaelt, dann Standardmap verwenden if(defined $selectedMap) { $nval = dev_proxy_mapValue($selectedMap, $val, $incoming); if(defined $nval) { my ($nval, @areading) = split(/:/, $nval); my $nreading = @areading ? join(':',@areading) : $reading; return ($nval, $nreading); } } $selectedMap = $map->{$dev.':'.$reading}; $selectedMap = $map->{$dev.':*'} unless defined $selectedMap; $selectedMap = $map->{'*:'.$reading} unless defined $selectedMap; $selectedMap = $map->{'*:*'} unless defined $selectedMap; $selectedMap = $map->{'*'} unless defined $selectedMap; # Originalwert, falls kein passendes Map return ($val, $reading) unless defined $selectedMap; $nval = dev_proxy_mapValue($selectedMap, $val, $incoming); return ($nval, $reading) if defined $nval; # Originalwert, falls keine Entsprechung im Map return ($val, $reading); } sub dev_proxy_mapValue($$$) { my ($map, $val, $incoming) = @_; my $nv=$map->{$val}; if(!defined($nv)) { $nv=$map->{'*'}; } return undef unless(defined($nv)) ; if($nv=~/^{/) { $nv = eval($nv); } return $nv; } sub dev_proxy_Set($@){ my ($hash,$name,$command,@values) = @_; return "no set value specified" if(!defined($command)); if ($command eq '?') { my $setList = AttrVal($name, "setList", undef); if(!defined $setList) { $setList = ""; foreach my $n (sort keys %{ $hash->{READINGS} }) { next if($n eq 'LastDevice' || $n eq 'state'); $setList.=$n; if($n eq 'position' || $n eq 'dim' ) { $setList.=":slider,0,1,100"; } $setList.=" "; } } $setList =~ s/\n/ /g; if(AttrVal($name,"useSetExtensions",undef)) { return SetExtensions($hash, $setList, $name, $command, @values); } else { return "Unknown argument $command, choose one of $setList"; } } if (AttrVal($name,"useSetExtensions",undef)) { if ($command =~ m/^(blink|intervals|(off-|on-)(for-timer|till)|toggle)/) { Log3($hash->{NAME},5,"calling SetExtensions(...) for $command"); my $setList = AttrVal($name, "setList", undef); return SetExtensions($hash, $setList, $name, $command, @values); } } if(int(@values)>0 && !defined($hash->{READINGS}->{$command})) { if(AttrVal($name,"useSetExtensions",undef)) { my $setList = AttrVal($name, "setList", undef); return SetExtensions($hash, $setList, $name, $command, @values); } else { return "Unknown reading $command"; } } SetExtensionsCancel($hash); my $ret; my @devList = keys %{$hash->{CONTENT}}; foreach my $d (@devList) { my $val; if(int(@values)<1) { # state my $cmd = "state"; ($val, $cmd) = dev_proxy_mapDeviceReadingValueDefultMap($hash, $d, "state", $command,0); $cmd = dev_proxy_remap_reading($hash, $d, $cmd); my $cmdstr; if($cmd ne "state") { $cmdstr = join(" ", ($d, $cmd, $val)); } else { $cmdstr = join(" ", ($d, $val)); } #Log3 $hash, 1, "SET: >>> ".$cmdstr; $ret .= CommandSet(undef, $cmdstr); } else { # benannte readings my $cmd = $command; ($val, $cmd) = dev_proxy_mapDeviceReadingValueDefultMap($hash, $d, $command, join(" ", @values),0); $cmd = dev_proxy_remap_reading($hash, $d, $cmd); my $cmdstr; if($cmd ne "state") { $cmdstr = join(" ", ($d, $cmd, $val)); } else { $cmdstr = join(" ", ($d, $val)); } #Log3 $hash, 1, "SET: >>> ".$cmdstr; $ret .= CommandSet(undef, $cmdstr); } } Log3 $hash, 5, "SET: $ret" if($ret); return undef; } sub dev_proxy_Attr($@){ my ($type, $name, $attrName, $attrVal) = @_; my %ignore = ( alias=>1, devStateIcon=>1, disable=>1, disabledForIntervals=>1, group=>1, icon=>1, room=>1, stateFormat=>1, webCmd=>1, userattr=>1 ); return undef if($ignore{$attrName}); my $hash = $defs{$name}; if($attrName eq "observedReadings") { if($type eq "del") { dev_proxy_setDefaultObservedReadings($hash); } else { my @a=split("[ \t][ \t]*",$attrVal); dev_proxy_setObservedReading($hash, @a); } } elsif($attrName eq "mapValues") { if($type ne "del") { $hash->{DEV_READING_VALUE_MAP} = eval($attrVal); } else { $hash->{DEV_READING_VALUE_MAP} = undef; } } elsif($attrName eq "mapReadings") { if($type ne "del") { dev_proxy_eval_map_readings($hash, $attrVal); } else { $hash->{READING_NAME_MAP} = undef; } } dev_proxy_updateReadings($hash, undef); Log3 $name, 4, "dev_proxy attr $type"; return undef; } sub dev_proxy_eval_map_readings($$) { my ($hash, $attrVal) = @_; $hash->{READING_NAME_MAP} = undef unless defined $attrVal; my $map; if(defined $attrVal) { my @list = split("[ \t][ \t]*", $attrVal); foreach (@list) { my($devName, $devReading, $newReading) = split(/:/, $_); $map->{$devName} -> {$newReading} = $devReading; } } $hash->{READING_NAME_MAP} = $map; } # Readings remappen, die von hier in die Richtung anderen Devices gesendet werden sub dev_proxy_remap_reading($$$) { my ($hash, $devName, $readingName) = @_; my $map = $hash->{READING_NAME_MAP}; return $readingName unless defined $map; my $t = $map->{$devName}; $t = $map->{"*"} unless defined $t; my $newReadingName = $t->{$readingName} if defined $t; return $readingName unless defined $newReadingName; return $newReadingName; } sub dev_proxy_cleanup_readings($) { my ($hash) = @_; my $name = $hash->{NAME}; my $map = $hash->{OBSERVED_READINGS}; return unless defined $map; foreach my $aName (keys %{$defs{$name}{READINGS}}) { if(!defined $map->{$aName} && ($aName ne "LastDevice") && ($aName ne "state")) { delete $defs{$name}{READINGS}{$aName}; } } } 1; =pod =item helper =item summary organize devices and readings, remap / rename readings =item summary_DE mehrere Geräte zu einem zusammenfassen, Readings umbenennen / umrechnen =begin html

dev_proxy

=end html =begin html_DE

dev_proxy

=end html_DE =cut