diff --git a/fhem/FHEM/10_MQTT_GENERIC_BRIDGE.pm b/fhem/FHEM/10_MQTT_GENERIC_BRIDGE.pm index ab0fc9523..53b965bec 100644 --- a/fhem/FHEM/10_MQTT_GENERIC_BRIDGE.pm +++ b/fhem/FHEM/10_MQTT_GENERIC_BRIDGE.pm @@ -30,6 +30,9 @@ # # CHANGE LOG # +# 04.03.2021 1.4.0 +# change : perl critic fixes by Beta-User +# # 16.02.2021 1.3.3 # fix: : fix cref by Beta-User # @@ -394,13 +397,25 @@ # [open] # +package MQTT::GENERIC_BRIDGE; + use strict; use warnings; use AttrTemplate; +use Carp qw(carp); +##no critic qw(constant Package) + +use GPUtils qw(:all); + +#if ($DEBUG) { + use Data::Dumper; +## $gets{"debugInfo"}="noArg"; +## $gets{"debugReinit"}="noArg"; +#} #my $DEBUG = 1; my $cvsid = '$Id$'; -my $VERSION = "version 1.3.3 by hexenmeister\n$cvsid"; +my $VERSION = "version 1.4.0 by hexenmeister\n$cvsid"; my %sets = ( ); @@ -425,69 +440,8 @@ use constant { CTRL_ATTR_NAME_GLOBAL_PREFIX => "global" }; -sub MQTT_GENERIC_BRIDGE_Initialize($) { - - my $hash = shift @_; - - # Consumer - $hash->{DefFn} = "MQTT::GENERIC_BRIDGE::Define"; - $hash->{UndefFn} = "MQTT::GENERIC_BRIDGE::Undefine"; - $hash->{SetFn} = "MQTT::GENERIC_BRIDGE::Set"; - $hash->{GetFn} = "MQTT::GENERIC_BRIDGE::Get"; - $hash->{NotifyFn} = "MQTT::GENERIC_BRIDGE::Notify"; - $hash->{AttrFn} = "MQTT::GENERIC_BRIDGE::Attr"; - $hash->{OnMessageFn} = "MQTT::GENERIC_BRIDGE::onmessage"; - #$hash->{RenameFn} = "MQTT::GENERIC_BRIDGE::Rename"; - - $hash->{Match} = ".*"; - $hash->{ParseFn} = "MQTT::GENERIC_BRIDGE::Parse"; - - $hash->{OnClientConnectFn} = "MQTT::GENERIC_BRIDGE::ioDevConnect"; - $hash->{OnClientDisconnectFn} = "MQTT::GENERIC_BRIDGE::ioDevDisconnect"; - $hash->{OnClientConnectionTimeoutFn} = "MQTT::GENERIC_BRIDGE::ioDevDisconnect"; - - $hash->{AttrList} = - "IODev ". - CTRL_ATTR_NAME_GLOBAL_PREFIX.CTRL_ATTR_NAME_DEFAULTS.":textField-long ". - CTRL_ATTR_NAME_GLOBAL_PREFIX.CTRL_ATTR_NAME_ALIAS.":textField-long ". - CTRL_ATTR_NAME_GLOBAL_PREFIX.CTRL_ATTR_NAME_PUBLISH.":textField-long ". - #CTRL_ATTR_NAME_GLOBAL_PREFIX.CTRL_ATTR_NAME_SUBSCRIBE.":textField-long ". - CTRL_ATTR_NAME_GLOBAL_TYPE_EXCLUDE.":textField-long ". - CTRL_ATTR_NAME_GLOBAL_DEV_EXCLUDE.":textField-long ". - "disable:1,0 ". - "debug:0,1 ". - "forceNEXT:0,1 ". - $main::readingFnAttributes; - - #main::LoadModule("MQTT"); - - # Beim ModulReload Deviceliste loeschen (eig. nur fuer bei der Entwicklung nuetzich) - #if($DEBUG) { - #if($hash->{'.debug'}) { - foreach my $d (keys %defs) { - if(defined($defs{$d}{TYPE})) { - if($defs{$d}{TYPE} eq "MQTT_GENERIC_BRIDGE") { - $defs{$d}{".initialized"} = 0; - } - } - } - #} - - $hash->{'.debug'} = '0'; -} - -package MQTT::GENERIC_BRIDGE; - -use strict; -use warnings; -use GPUtils qw(:all); #if ($DEBUG) { - use Data::Dumper; -## $gets{"debugInfo"}="noArg"; -## $gets{"debugReinit"}="noArg"; -#} - BEGIN { GP_Import(qw( @@ -500,6 +454,9 @@ BEGIN { DoSet fhem defs + attr + readingFnAttributes + init_done AttrVal ReadingsVal ReadingsTimestamp @@ -516,20 +473,13 @@ BEGIN { toJSON TimeNow IOWrite - CTRL_ATTR_NAME_DEFAULTS - CTRL_ATTR_NAME_ALIAS - CTRL_ATTR_NAME_PUBLISH - CTRL_ATTR_NAME_SUBSCRIBE - CTRL_ATTR_NAME_IGNORE - CTRL_ATTR_NAME_FORWARD - CTRL_ATTR_NAME_GLOBAL_TYPE_EXCLUDE - CTRL_ATTR_NAME_GLOBAL_DEV_EXCLUDE - CTRL_ATTR_NAME_GLOBAL_PREFIX AttrTemplate_Set )) }; +sub ::MQTT_GENERIC_BRIDGE_Initialize { goto &MQTT_GENERIC_BRIDGE_Initialize } + use constant { HELPER => ".helper", IO_DEV_TYPE => "IO_DEV_TYPE", @@ -561,61 +511,87 @@ use constant { DEFAULT_GLOBAL_DEV_EXCLUDES => "" }; -sub publishDeviceUpdate($$$$$); -sub UpdateSubscriptionsSingleDevice($$); -sub InitializeDevices($); -sub firstInit($); -sub removeOldUserAttr($;$$$); -sub IsObservedAttribute($$); -sub defineGlobalTypeExclude($;$); -sub defineGlobalDevExclude($;$); -sub defineDefaultGlobalExclude($); -sub isTypeDevReadingExcluded($$$$$); -sub getDevicePublishRecIntern($$$$$$$); -sub getDevicePublishRec($$$); -sub isConnected($); -sub ioDevConnect($); -sub ioDevDisconnect($); -sub updateDevCount($); -sub retrieveIODevName($); -sub retrieveIODevType($); -sub isIODevMQTT2($); -sub isIODevMQTT2_CLIENT($); -sub isIODevMQTT($); -sub initUserAttr($); -sub createRegexpForTopic($); -sub isDebug($); -sub checkPublishDeviceReadingsUpdates($$); -sub RefreshGlobalTableAll($); -sub _evalValue2($$;$$); + +sub MQTT_GENERIC_BRIDGE_Initialize { + my $hash = shift // return; + + # Consumer + $hash->{DefFn} = "MQTT::GENERIC_BRIDGE::Define"; + $hash->{UndefFn} = "MQTT::GENERIC_BRIDGE::Undefine"; + $hash->{SetFn} = "MQTT::GENERIC_BRIDGE::Set"; + $hash->{GetFn} = "MQTT::GENERIC_BRIDGE::Get"; + $hash->{NotifyFn} = "MQTT::GENERIC_BRIDGE::Notify"; + $hash->{AttrFn} = "MQTT::GENERIC_BRIDGE::Attr"; + $hash->{OnMessageFn} = "MQTT::GENERIC_BRIDGE::onmessage"; + #$hash->{RenameFn} = "MQTT::GENERIC_BRIDGE::Rename"; + + $hash->{Match} = ".*"; + $hash->{ParseFn} = "MQTT::GENERIC_BRIDGE::Parse"; + + $hash->{OnClientConnectFn} = "MQTT::GENERIC_BRIDGE::ioDevConnect"; + $hash->{OnClientDisconnectFn} = "MQTT::GENERIC_BRIDGE::ioDevDisconnect"; + $hash->{OnClientConnectionTimeoutFn} = "MQTT::GENERIC_BRIDGE::ioDevDisconnect"; + + $hash->{AttrList} = + "IODev ". + CTRL_ATTR_NAME_GLOBAL_PREFIX.CTRL_ATTR_NAME_DEFAULTS.":textField-long ". + CTRL_ATTR_NAME_GLOBAL_PREFIX.CTRL_ATTR_NAME_ALIAS.":textField-long ". + CTRL_ATTR_NAME_GLOBAL_PREFIX.CTRL_ATTR_NAME_PUBLISH.":textField-long ". + #CTRL_ATTR_NAME_GLOBAL_PREFIX.CTRL_ATTR_NAME_SUBSCRIBE.":textField-long ". + CTRL_ATTR_NAME_GLOBAL_TYPE_EXCLUDE.":textField-long ". + CTRL_ATTR_NAME_GLOBAL_DEV_EXCLUDE.":textField-long ". + "disable:1,0 ". + "debug:0,1 ". + "forceNEXT:0,1 ". + $readingFnAttributes; + + #main::LoadModule("MQTT"); + + # Beim ModulReload Deviceliste loeschen (eig. nur fuer bei der Entwicklung nuetzich) + #if($DEBUG) { + #if($hash->{'.debug'}) { + for my $d (keys %defs) { + if(defined($defs{$d}{TYPE})) { + if($defs{$d}{TYPE} eq "MQTT_GENERIC_BRIDGE") { + $defs{$d}{".initialized"} = 0; + } + } + } + #} + + $hash->{'.debug'} = '0'; + return; +} ############################################################################### # prueft, ob debug Attribute auf 1 gesetzt ist (Debugmode) -sub isDebug($) { - my ($hash) = @_; - return AttrVal($hash->{NAME},"debug",0); +sub isDebug { + my $hash = shift // return; + return AttrVal($hash->{NAME},'debug',0); } # Entfernt Leerzeichen vom string vorne und hinten -sub trim { my $s = shift; $s =~ s/^\s+|\s+$//g; return $s } +sub trim { my $s = shift; $s =~ s{\A\s+|\s+\z}{}gx; return $s } # prueft, ob der erste gegebene String mit dem zweiten anfaengt -sub startsWith($$) { - my($str, $subStr) = @_; +sub startsWith { + my $str = shift; + my $subStr = shift // return 0; return substr($str, 0, length($subStr)) eq $subStr; } ############################################################################### # Device define -sub Define() { - my ($hash, $def) = @_; +sub Define { + my $hash = shift; + my $def = shift // return; # Definition :=> defmod mqttGeneric MQTT_GENERIC_BRIDGE [prefix] [devspec,[devspec]] my($name, $type, $prefix, @devspeca) = split("[ \t][ \t]*", $def); # restlichen Parameter nach Leerzeichen trennen # aus dem Array einen kommagetrennten String erstellen my $devspec = join(",", @devspeca); # Doppelte Kommas entfernen. - $devspec =~s/,+/,/g; + $devspec =~s{,+}{,}gx; # damit ist jetzt Trennung der zu ueberwachenden Geraete mit Kommas, Leezeichen, Kommas mit Leerzeichen und Mischung davon moeglich my $oldprefix = $hash->{+HS_PROP_NAME_PREFIX}; my $olddevspec = $hash->{+HS_PROP_NAME_DEVSPEC}; @@ -639,10 +615,10 @@ sub Define() { $hash->{+HELPER}->{+HS_PROP_NAME_DEVICE_CNT} = 0; - $hash->{+HELPER}->{+HS_PROP_NAME_INCOMING_CNT} = 0 unless defined $hash->{+HELPER}->{+HS_PROP_NAME_INCOMING_CNT}; - $hash->{+HELPER}->{+HS_PROP_NAME_OUTGOING_CNT} = 0 unless defined $hash->{+HELPER}->{+HS_PROP_NAME_OUTGOING_CNT}; - $hash->{+HELPER}->{+HS_PROP_NAME_UPDATE_R_CNT} = 0 unless defined $hash->{+HELPER}->{+HS_PROP_NAME_UPDATE_R_CNT}; - $hash->{+HELPER}->{+HS_PROP_NAME_UPDATE_S_CNT} = 0 unless defined $hash->{+HELPER}->{+HS_PROP_NAME_UPDATE_S_CNT}; + $hash->{+HELPER}->{+HS_PROP_NAME_INCOMING_CNT} = 0 if !defined $hash->{+HELPER}->{+HS_PROP_NAME_INCOMING_CNT}; + $hash->{+HELPER}->{+HS_PROP_NAME_OUTGOING_CNT} = 0 if !defined $hash->{+HELPER}->{+HS_PROP_NAME_OUTGOING_CNT}; + $hash->{+HELPER}->{+HS_PROP_NAME_UPDATE_R_CNT} = 0 if !defined $hash->{+HELPER}->{+HS_PROP_NAME_UPDATE_R_CNT}; + $hash->{+HELPER}->{+HS_PROP_NAME_UPDATE_S_CNT} = 0 if !defined $hash->{+HELPER}->{+HS_PROP_NAME_UPDATE_S_CNT}; #TODO: aktivieren, wenn gebraucht wird $hash->{+HELPER}->{+HS_PROP_NAME_INTERVAL} = 60; # Sekunden @@ -667,40 +643,41 @@ sub Define() { # $hash->{subscribeExpr} = []; # } - ::AttrTemplate_Initialize() if $::init_done; + ::AttrTemplate_Initialize() if $init_done; # noetig hier beim Anlegen im laufendem Betrieb InternalTimer(1, \&firstInit, $hash); - return undef; + return; } # Device undefine -sub Undefine() { - my ($hash) = @_; +sub Undefine { + my $hash = shift // return; RemoveInternalTimer($hash); - MQTT::client_stop($hash) if isIODevMQTT($hash); #if defined($hash->{+HELPER}->{+IO_DEV_TYPE}) and $hash->{+HELPER}->{+IO_DEV_TYPE} eq 'MQTT'; - removeOldUserAttr($hash); + MQTT::client_stop($hash) if isIODevMQTT($hash); + return removeOldUserAttr($hash); } # erstellt / loescht die notwendigen userattr-Werte (die Bridge-Steuerattribute an den Geraeten laut devspec) -sub refreshUserAttr($) { - my ($hash) = @_; +sub refreshUserAttr { + my $hash = shift // return; my $oldprefix = $hash->{+HS_PROP_NAME_PREFIX}; my $olddevspec = $hash->{+HS_PROP_NAME_DEVSPEC}; my $newdevspec = initUserAttr($hash); removeOldUserAttr($hash,$oldprefix,$olddevspec,$newdevspec) if (defined ($olddevspec)); + return; } # liefert TYPE des IODev, wenn definiert (MQTT; MQTT2,..) -sub retrieveIODevName($) { - my ($hash) = @_; +sub retrieveIODevName { + my $hash = shift // return; my $iodn = AttrVal($hash->{NAME}, "IODev", undef); return $iodn; } # liefert TYPE des IODev, wenn definiert (MQTT; MQTT2,..) -sub retrieveIODevType($) { - my ($hash) = @_; +sub retrieveIODevType { + my $hash = shift // return; return $hash->{+HELPER}->{+IO_DEV_TYPE} if defined $hash->{+HELPER}->{+IO_DEV_TYPE}; @@ -710,52 +687,50 @@ sub retrieveIODevType($) { $iodt = $defs{$iodn}{TYPE}; } $hash->{+HELPER}->{+IO_DEV_TYPE} = $iodt; - #return ($iodt, $iodn); return $iodt; - #return $hash->{+HELPER}->{+IO_DEV_TYPE}; } # prueft, ob IODev MQTT-Instanz ist -sub isIODevMQTT($) { - my ($hash) = @_; +sub isIODevMQTT { + my $hash = shift // return 0; my $iodt = retrieveIODevType($hash); return 0 unless defined $iodt; return 0 unless $iodt eq 'MQTT'; return 1; } -sub checkIODevMQTT2($) { - my ($iodt) = @_; - return 0 unless defined $iodt; +sub checkIODevMQTT2 { + + my $iodt = shift // return 0; return 1 if $iodt eq 'MQTT2_SERVER'; return 1 if $iodt eq 'MQTT2_CLIENT'; return 0; } -sub checkIODevMQTT2_CLIENT($) { - my ($iodt) = @_; - return 0 unless defined $iodt; +sub checkIODevMQTT2_CLIENT { + + my $iodt = shift // return 0; return 1 if $iodt eq 'MQTT2_CLIENT'; return 0; } # prueft, ob IODev MQTT2-Instanz ist -sub isIODevMQTT2($) { - my ($hash) = @_; +sub isIODevMQTT2 { + my $hash = shift // return 0; my $iodt = retrieveIODevType($hash); return checkIODevMQTT2($iodt); } # prueft, ob IODev MQTT2_CLIENT-Instanz ist -sub isIODevMQTT2_CLIENT($) { - my ($hash) = @_; +sub isIODevMQTT2_CLIENT { + my $hash = shift // return 0; my $iodt = retrieveIODevType($hash); return checkIODevMQTT2_CLIENT($iodt); } # Fuegt notwendige UserAttr hinzu -sub initUserAttr($) { - my ($hash) = @_; +sub initUserAttr { + my $hash = shift // return; # wenn bereits ein prefix bestand, die userAttr entfernen : HS_PROP_NAME_PREFIX_OLD != HS_PROP_NAME_PREFIX my $prefix = $hash->{+HS_PROP_NAME_PREFIX}; my $devspec = $hash->{+HS_PROP_NAME_DEVSPEC}; @@ -769,7 +744,7 @@ sub initUserAttr($) { #Log3($hash->{NAME},5,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] initUserAttr: new list: ".Dumper(@devices)); #Log3($hash->{NAME},5,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] initUserAttr: addToDevAttrList: $prefix"); #Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] initUserAttr> devspec: '$devspec', array: ".Dumper(@devices)); - foreach my $dev (@devices) { + for my $dev (@devices) { addToDevAttrList($dev, $prefix.CTRL_ATTR_NAME_DEFAULTS.":textField-long"); addToDevAttrList($dev, $prefix.CTRL_ATTR_NAME_ALIAS.":textField-long"); addToDevAttrList($dev, $prefix.CTRL_ATTR_NAME_PUBLISH.":textField-long"); @@ -782,22 +757,22 @@ sub initUserAttr($) { # Erstinitialization. # Variablen werden im HASH abgelegt, userattr der betroffenen Geraete wird erweitert, MQTT-Initialisierungen. -sub firstInit($) { - my ($hash) = @_; +sub firstInit { + my $hash = shift // return; # IO AssignIoPort($hash); if(isIODevMQTT($hash)) { require Net::MQTT::Constants; - main::LoadModule("MQTT"); + ::LoadModule("MQTT"); MQTT->import(qw(:all)); } - if ($main::init_done) { + if ($init_done) { $hash->{+HELPER}->{+HS_FLAG_INITIALIZED} = 0; - return unless defined(AttrVal($hash->{NAME},"IODev",undef)); + return if !defined(AttrVal($hash->{NAME},'IODev',undef)); # Default-Excludes defineDefaultGlobalExclude($hash); @@ -831,13 +806,13 @@ sub firstInit($) { # im firstinit schleife ueber alle devices im map und bei mode 'A' senden # publishDeviceUpdate($hash, $defs{$sdev}, 'A', $attrName, $val); # ggf. vorkehrungen treffen, falls nicht connected - + return; } } # Vom Timer periodisch aufzurufende Methode -sub timerProc($) { - my ($hash, $refresh_all) = @_; +sub timerProc { + my $hash = shift // return; my $name = $hash->{NAME}; # TODO: Resend @@ -847,15 +822,16 @@ sub timerProc($) { if(defined($hash->{+HELPER}->{+HS_PROP_NAME_INTERVAL}) && ($hash->{+HELPER}->{+HS_PROP_NAME_INTERVAL} ne '0')) { InternalTimer(gettimeofday()+$hash->{+HELPER}->{+HS_PROP_NAME_INTERVAL}, "MQTT::GENERIC_BRIDGE::timerProc", $hash, 0); } + return; } # prueft, ob Verbindung zum MQTT-Broker besteht. # Parameter: Bridge-Hash -sub isConnected($) { - my $hash = shift; +sub isConnected { + my $hash = shift // return 0; return MQTT::isConnected($hash->{IODev}) if isIODevMQTT($hash); #if $hash->{+HELPER}->{+IO_DEV_TYPE} eq 'MQTT'; - return 1 if isIODevMQTT2($hash); + return 1 if isIODevMQTT2($hash); # TODO: check connected #Beta-User: might need review, see https://forum.fhem.de/index.php/topic,115279.msg1130603.html#msg1130603 # ich weiß nicht, ob das eine gute Idee ist, zu prüfen, evtl. wird FHEM-Standard-writeBuffef für das Senden nach dem Connect selbst sorgen # in diesem Fall koenne wir annehmen, dass immer connected ist und keine eigene Warteschlangen verwenden # my $iodt = retrieveIODevType($hash); @@ -871,17 +847,17 @@ sub isConnected($) { } # Berechnet Anzahl der ueberwachten Geraete neu -sub updateDevCount($) { - my $hash = shift; +sub updateDevCount { + my $hash = shift // return; # device count my $size = 0; - foreach my $dname (sort keys %{$hash->{+HS_TAB_NAME_DEVICES}}) { + for my $dname (sort keys %{$hash->{+HS_TAB_NAME_DEVICES}}) { if($dname ne ":global") { $size++; } } $hash->{+HELPER}->{+HS_PROP_NAME_DEVICE_CNT} = $size; - readingsSingleUpdate($hash,"device-count",$size,1); + return readingsSingleUpdate($hash,"device-count",$size,1); } # loescht angelegte userattr aus den jeweiligen Devices (oder aus dem global-Device) @@ -890,27 +866,30 @@ sub updateDevCount($) { # $prefix: Attribute (publish, subscribe, defaults und alis) mit diesem Prefix werden entfernt # $devspec: definiert Geraete, deren userattr bereinigt werden # Die letzten zwei Parameter sind optinal, fehlen sie, werden werte aus dem Hash genommen. -sub removeOldUserAttr($;$$$) { - my ($hash, $prefix, $devspec, $newDevices) = @_; +sub removeOldUserAttr { + #my ($hash, $prefix, $devspec, $newDevices) = @_; + my $hash = shift // return; + my $prefix = shift // $hash->{+HS_PROP_NAME_PREFIX}; + my $devspec = shift // $hash->{+HS_PROP_NAME_DEVSPEC}; + my $newDevices = shift; #Einleitung passt irgendwie nicht... + #Log3($hash->{NAME},5,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] newDevices: ".Dumper($newDevices)); - $prefix = $hash->{+HS_PROP_NAME_PREFIX} unless defined $prefix; # Pruefen, on ein weiteres Device (MQTT_GENERIC_BRIDGE) mit dem selben Prefix existiert (waere zwar Quatsch, aber dennoch) my @bridges = devspec2array("TYPE=MQTT_GENERIC_BRIDGE"); my $name = $hash->{NAME}; - foreach my $dev (@bridges) { + for my $dev (@bridges) { if($dev ne $name) { my $aPrefix = $defs{$dev}->{+HS_PROP_NAME_PREFIX}; return if ($aPrefix eq $prefix); } } - $devspec = $hash->{+HS_PROP_NAME_DEVSPEC} unless defined $devspec; $devspec = 'global' if ($devspec eq '.*'); # kann spaeter auch delFromDevAttrList Methode genutzt werden my @devices = devspec2array($devspec); #Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] removeOldUserAttr> devspec: $devspec, array: ".Dumper(@devices)); - foreach my $dev (@devices) { + for my $dev (@devices) { next if grep {$_ eq $dev} @{$newDevices}; #Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] removeOldUserAttr> delete: from $dev ".$prefix.CTRL_ATTR_NAME_DEFAULTS); # O> subs aus fhem.pl nicht nutzen, da diese auch die Steuerungsattribute mit loescht. Vor allem bei global ist das ein Problem @@ -921,7 +900,7 @@ sub removeOldUserAttr($;$$$) { # delFromDevAttrList($dev,$prefix.CTRL_ATTR_NAME_IGNORE.":both,incoming,outgoing"); # delFromDevAttrList($dev,$prefix.CTRL_ATTR_NAME_FORWARD.":all,none"); # => stattdessen selbst loeschen (nur die 'userattr') - my $ua = $main::attr{$dev}{userattr}; + my $ua = $attr{$dev}{userattr}; if (defined $ua) { my %h = map { ($_ => 1) } split(" ", "$ua"); #delete $h{$prefix.CTRL_ATTR_NAME_DEFAULTS}; @@ -936,19 +915,21 @@ sub removeOldUserAttr($;$$$) { delete $h{$prefix.CTRL_ATTR_NAME_IGNORE.":both,incoming,outgoing"}; #delete $h{$prefix.CTRL_ATTR_NAME_FORWARD}; delete $h{$prefix.CTRL_ATTR_NAME_FORWARD.":all,none"}; - if(!keys %h && defined($main::attr{$dev}{userattr})) { + if(!keys %h && defined($attr{$dev}{userattr})) { # ganz loeschen, wenn nichts mehr drin - delete $main::attr{$dev}{userattr}; + delete $attr{$dev}{userattr}; } else { - $main::attr{$dev}{userattr} = join(" ", sort keys %h); + $attr{$dev}{userattr} = join(" ", sort keys %h); } } } + return; } # Prueft, ob der gegebene Zeichenkette einem der zu ueberwachenden Device-Attributennamen entspricht. -sub IsObservedAttribute($$) { - my ($hash, $aname) = @_; +sub IsObservedAttribute { + my $hash = shift; + my $aname = shift // return; my $prefix = $hash->{+HS_PROP_NAME_PREFIX}; if($aname eq $prefix.CTRL_ATTR_NAME_DEFAULTS) { @@ -970,7 +951,7 @@ sub IsObservedAttribute($$) { return 1; } - return undef; + return; } # Internal. Legt Defaultwerte im Map ab. Je nach Schluessel werden die Werte fuer 'pub:', 'sub:' oder beides abgelegt. @@ -979,80 +960,90 @@ sub IsObservedAttribute($$) { # $dev: Devicename # $valMap: Map mit den Werten (Quelle) # $key: Schluessel. Unter Inhalt aus dem Quellmap unter diesem Schluessel wird in Zielmap kopiert. -sub _takeDefaults($$$$) { - my ($map, $dev, $valMap, $key) = @_; - my $pr = ''; +sub _takeDefaults { #($$$$) { + my $map = shift; + my $dev = shift; + my $valMap = shift; + my $key = shift // return; + my $pr = q{}; $pr = substr($key, 0, 4) if (length($key)>4); if(($pr eq 'sub:') or ($pr eq 'pub:')) { #if (defined($valMap->{$key})) { # ggf. nicht ueberschreiben (damit nicht undefiniertes VErhalten entsteht, # wenn mit und ohne Prefx gleichzeitig angegeben wird. So wird die Definition mit Prefix immer gewinnen) - $map->{$dev}->{':defaults'}->{$key}=$valMap->{$key} unless defined $map->{$dev}->{':defaults'}->{$key}; - $map->{$dev}->{':defaults'}->{$key}=$valMap->{$key} unless defined $map->{$dev}->{':defaults'}->{$key}; + $map->{$dev}->{':defaults'}->{$key}=$valMap->{$key} if !defined $map->{$dev}->{':defaults'}->{$key}; + $map->{$dev}->{':defaults'}->{$key}=$valMap->{$key} if !defined $map->{$dev}->{':defaults'}->{$key}; } else { $map->{$dev}->{':defaults'}->{'pub:'.$key}=$valMap->{$key}; $map->{$dev}->{':defaults'}->{'sub:'.$key}=$valMap->{$key}; } + return; } # Erstellt Strukturen fuer 'Defaults' fuer ein bestimmtes Geraet. # Params: Bridge-Hash, Dev-Name (im Map, ist auch = DevName), # Internes Map mit allen Definitionen fuer alle Gerate, # Attribute-Value zum Parsen -sub CreateSingleDeviceTableAttrDefaults($$$$) { - my($hash, $dev, $map, $attrVal) = @_; +sub CreateSingleDeviceTableAttrDefaults { #($$$$) { + #my($hash, $dev, $map, $attrVal) = @_; + my $hash = shift // return; + my $dev = shift // carp q[No device name provided!] && return; + my $map = shift // carp q[No devMapName provided!] && return; + my $attrVal = shift; + # collect defaults delete ($map->{$dev}->{':defaults'}); - if(defined $attrVal) { - # format: [pub:|sub:]base=ha/wz/ [pub:|sub:]qos=0 [pub:|sub:]retain=0 - my($unnamed, $named) = main::parseParams($attrVal,'\s',' ','='); #main::parseParams($attrVal); - foreach my $param (keys %{$named}) { - # my $pr = substr($param, 0, 4); - # if($pr eq 'sub:' or $pr eq 'pub:') { - # $param = substr($param, 4); - # } - _takeDefaults($map, $dev, $named, $param); - } - # _takeDefaults($map, $dev, $named, 'base'); - # _takeDefaults($map, $dev, $named, 'qos'); - # _takeDefaults($map, $dev, $named, 'retain'); - # _takeDefaults($map, $dev, $named, 'expression'); - return defined($map->{$dev}->{':defaults'}); - } else { - return undef; + return if !defined $attrVal; + # format: [pub:|sub:]base=ha/wz/ [pub:|sub:]qos=0 [pub:|sub:]retain=0 + my($unnamed, $named) = main::parseParams($attrVal,'\s',' ','='); + for my $param (keys %{$named}) { + # my $pr = substr($param, 0, 4); + # if($pr eq 'sub:' or $pr eq 'pub:') { + # $param = substr($param, 4); + # } + _takeDefaults($map, $dev, $named, $param); } + # _takeDefaults($map, $dev, $named, 'base'); + # _takeDefaults($map, $dev, $named, 'qos'); + # _takeDefaults($map, $dev, $named, 'retain'); + # _takeDefaults($map, $dev, $named, 'expression'); + return defined($map->{$dev}->{':defaults'}); } # Erstellt Strukturen fuer 'Alias' fuer ein bestimmtes Geraet. # Params: Bridge-Hash, Dev-Name (im Map, ist auch = DevName), # Internes Map mit allen Definitionen fuer alle Gerate, # Attribute-Value zum Parsen -sub CreateSingleDeviceTableAttrAlias($$$$) { - my($hash, $dev, $map, $attrVal) = @_; +sub CreateSingleDeviceTableAttrAlias { #($$$$) { + #my($hash, $dev, $map, $attrVal) = @_; + my $hash = shift // return; + my $dev = shift // carp q[No device name provided!] && return; + my $map = shift // carp q[No devMapName provided!] && return; + my $attrVal = shift; + delete ($map->{$dev}->{':alias'}); - if(defined $attrVal) { - # format [pub:|sub:][=] ... - my($unnamed, $named) = main::parseParams($attrVal,'\s',' ','='); #main::parseParams($attrVal); - if(defined($named)){ - foreach my $param (keys %{$named}) { - my $val = $named->{$param}; - my($pref,$name) = split(":",$param); - if(defined($name)) { - if($pref eq 'pub' or $pref eq 'sub') { - $map->{$dev}->{':alias'}->{$pref.":".$name}=$val; - } - } else { - $name = $pref; - # ggf. nicht ueberschreiben (damit nicht undefiniertes Verhalten entsteht, - # wenn mit und ohne Prefx gleichzeitig angegeben wird. So wird die Definition mit Prefix immer gewinnen) - $map->{$dev}->{':alias'}->{"pub:".$name}=$val unless defined $map->{$dev}->{':alias'}->{"pub:".$name}; - $map->{$dev}->{':alias'}->{"sub:".$name}=$val unless defined $map->{$dev}->{':alias'}->{"sub:".$name}; + return if !defined $attrVal; + # format [pub:|sub:][=] ... + my($unnamed, $named) = main::parseParams($attrVal,'\s',' ','='); #main::parseParams($attrVal); + if(defined($named)){ + for my $param (keys %{$named}) { + my $val = $named->{$param}; + my($pref,$name) = split(":",$param); + if(defined($name)) { + if($pref eq 'pub' or $pref eq 'sub') { + $map->{$dev}->{':alias'}->{$pref.":".$name}=$val; } + } else { + $name = $pref; + # ggf. nicht ueberschreiben (damit nicht undefiniertes Verhalten entsteht, + # wenn mit und ohne Prefx gleichzeitig angegeben wird. So wird die Definition mit Prefix immer gewinnen) + $map->{$dev}->{':alias'}->{"pub:".$name}=$val if !defined $map->{$dev}->{':alias'}->{"pub:".$name}; + $map->{$dev}->{':alias'}->{"sub:".$name}=$val if !defined $map->{$dev}->{':alias'}->{"sub:".$name}; } - return defined($map->{$dev}->{':alias'}); } + return defined($map->{$dev}->{':alias'}); } - return undef; + return; } # Erstellt Strukturen fuer 'Publish' fuer ein bestimmtes Geraet. @@ -1061,12 +1052,18 @@ sub CreateSingleDeviceTableAttrAlias($$$$) { # Attribute-Value zum Parsen # NB: stopic gibt es beim 'publish' nicht # ?: internal-topic? - keine Verwendung bis jetzt -sub CreateSingleDeviceTableAttrPublish($$$$) { - my($hash, $dev, $map, $attrVal) = @_; +sub CreateSingleDeviceTableAttrPublish { #($$$$) { + #my($hash, $dev, $map, $attrVal) = @_; + my $hash = shift // return; + my $dev = shift // carp q[No device name provided!] && return; + my $map = shift // carp q[No devMapName provided!] && return; + my $attrVal = shift; + #Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] CreateSingleDeviceTableAttrPublish: $dev, $attrVal, ".Dumper($map)); # collect publish topics delete ($map->{$dev}->{':publish'}); - if(defined $attrVal) { + + return if !defined $attrVal; # format: # :topic=<"static topic"|{evaluated (first time only) topic # (avialable vars: $base, $reading (oringinal name), $name ($reading oder alias))}> @@ -1077,28 +1074,24 @@ sub CreateSingleDeviceTableAttrPublish($$$$) { # *:topic=# same as *:topic={"$base/$reading"} my($unnamed, $named) = main::parseParams($attrVal,'\s',' ','='); #Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] CreateSingleDeviceTableAttrPublish: parseParams: ".Dumper($named)); - if(defined($named)){ + return if !defined($named); my $autoResend = {}; - foreach my $param (keys %{$named}) { + for my $param (keys %{$named}) { my $val = $named->{$param}; my($name,$ident) = split(":",$param); - if(!defined($ident) or !defined($name)) { next; } - if(($ident eq 'topic') or ($ident eq 'readings-topic') or - ($ident eq 'atopic') or ($ident eq 'attr-topic') or + if(!defined($ident) || !defined($name)) { next; } + if($ident =~ m{\Atopic|(readings-|a|attr-)topic|qos|retain|expression|resendOnConnect|autoResendInterval\z}x) { + #($ident eq 'stopic') or ($ident eq 'set-topic') or # stopic nur bei subscribe - ($ident eq 'qos') or ($ident eq 'retain') or - ($ident eq 'expression') or - ($ident eq 'resendOnConnect') or - ($ident eq 'autoResendInterval')) { - my @nameParts = split(/\|/, $name); + my @nameParts = split m{\|}xms, $name; while (@nameParts) { - my $namePart = shift(@nameParts); - next if($namePart eq ""); + my $namePart = shift @nameParts; + next if $namePart eq ''; $map->{$dev}->{':publish'}->{$namePart}->{$ident}=$val; - $map->{$dev}->{':publish'}->{$namePart}->{'mode'} = 'R' if (($ident eq 'topic') or ($ident eq 'readings-topic')); + $map->{$dev}->{':publish'}->{$namePart}->{'mode'} = 'R' if $ident eq 'topic' || $ident eq 'readings-topic'; #$map->{$dev}->{':publish'}->{$namePart}->{'mode'} = 'S' if (($ident eq 'stopic') or ($ident eq 'set-topic')); - $map->{$dev}->{':publish'}->{$namePart}->{'mode'} = 'A' if (($ident eq 'atopic') or ($ident eq 'attr-topic')); + $map->{$dev}->{':publish'}->{$namePart}->{'mode'} = 'A' if $ident eq 'atopic' || $ident eq 'attr-topic'; $autoResend->{$namePart} = $val if $ident eq 'autoResendInterval'; } @@ -1110,15 +1103,17 @@ sub CreateSingleDeviceTableAttrPublish($$$$) { } else { delete $map->{$dev}->{':autoResend'}; } - } - } - return undef; + return; } # Sucht nach device/reading in der Dev-Map und speichert aktuellen dort den Zeitstempel -sub updatePubTime($$$) { - my ($hash,$device,$reading) = @_; +sub updatePubTime { + #my ($hash,$device,$reading) = @_; + my $hash = shift // return; + my $device = shift // carp q[No device name provided!] && return; + my $reading = shift // carp q[No reading provided!] && return; + my $map = $hash->{+HS_TAB_NAME_DEVICES}; if(defined ($map)) { my $dmap = $map->{$device}; @@ -1132,14 +1127,18 @@ sub updatePubTime($$$) { } } } + return; } # sucht zu den gegebenen device und reading die publish-Eintraege (topic, qos, retain) # liefert Liste der passenden dev-hashes # verwendet device-record und beruecksichtigt defaults und globals # parameter: $hash, device-name, reading-name -sub getDevicePublishRec($$$) { - my($hash, $dev, $reading) = @_; +sub getDevicePublishRec { + #my($hash, $dev, $reading) = @_; + my $hash = shift // return; + my $dev = shift // carp q[No device name provided!] && return; + my $reading = shift // carp q[No reading provided!] && return; my $ret = []; my $map = $hash->{+HS_TAB_NAME_DEVICES}; return $ret unless defined $map; @@ -1149,7 +1148,7 @@ sub getDevicePublishRec($$$) { #Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] getDevicePublishRec> devmap: ".Dumper($devMap)); - foreach my $key (keys %{$devMap->{':publish'}} ) { + for my $key (keys %{$devMap->{':publish'}} ) { #Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] getDevicePublishRec> dev: $key"); my($keyRName,$keyPostfix) = split("!",$key); if($keyRName eq $reading) { @@ -1161,7 +1160,7 @@ sub getDevicePublishRec($$$) { # wenn keine explizite Readings gefunden wurden, dann noch einmal fragen, damit evtl. vorhandenen '*'-Definitionen zur Geltung kommen if(!@$ret) { #push(@$ret, getDevicePublishRecIntern($hash, $devMap, $globalMap, $dev, $reading, $reading, undef)); - foreach my $key (keys %{$devMap->{':publish'}} ) { + for my $key (keys %{$devMap->{':publish'}} ) { #Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] getDevicePublishRec> dev: $key"); my($keyRName,$keyPostfix) = split("!",$key); if($keyRName eq '*') { @@ -1186,9 +1185,14 @@ sub getDevicePublishRec($$$) { # in den uebergebenen Maps # verwendet device-record und beruecksichtigt defaults und globals # parameter: $hash, map, globalMap, device-name, reading-name -sub getDevicePublishRecIntern($$$$$$$) { - my($hash, $devMap, $globalMap, $dev, $readingKey, $reading, $postFix) = @_; - +sub getDevicePublishRecIntern { + my $hash = shift // return; + my $devMap = shift // carp q[No device map provided!] && return; + my $globalMap = shift // carp q[No globalMap provided!] && return; + my $dev = shift // carp q[No device name provided!] && return; + my $readingKey = shift; #seems to be optional + my $reading = shift // carp q[No reading provided!] && return; + my $postFix = shift; #mandatory? or assign a default? #Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] getDevicePublishRec> params> devmap: ".Dumper($devMap)); #Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] getDevicePublishRec> params> globalmap: ".Dumper($globalMap)); #Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] getDevicePublishRec> params> dev: ".Dumper($dev)); @@ -1217,49 +1221,36 @@ sub getDevicePublishRecIntern($$$$$$$) { #Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] getDevicePublishRec> global readingMap ".Dumper($globalReadingMap)); #Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] getDevicePublishRec> global wildcardReadingMap ".Dumper($globalWildcardReadingsMap)); # topic - my $topic = undef; - $topic = $readingMap->{'topic'} if defined $readingMap; - $topic = $wildcardReadingMap->{'topic'} if (defined($wildcardReadingMap) and !defined($topic)); - - # global topic - $topic = $globalReadingMap->{'topic'} if (defined($globalReadingMap) and !defined($topic)); - $topic = $globalWildcardReadingsMap->{'topic'} if (defined($globalWildcardReadingsMap) and !defined($topic)); + my $topic = $readingMap->{'topic'} // + $wildcardReadingMap->{'topic'} // + $globalReadingMap->{'topic'} // + $globalWildcardReadingsMap->{'topic'} // undef; # attr-topic - my $atopic = undef; - $atopic = $readingMap->{'atopic'} if defined $readingMap; - $atopic = $wildcardReadingMap->{'atopic'} if (defined($wildcardReadingMap) and !defined($atopic)); - - # global attr-topic - $atopic = $globalReadingMap->{'atopic'} if (defined($globalReadingMap) and !defined($atopic)); - $atopic = $globalWildcardReadingsMap->{'atopic'} if (defined($globalWildcardReadingsMap) and !defined($atopic)); + my $atopic = $readingMap->{'atopic'} // + $wildcardReadingMap->{'atopic'} // + $globalReadingMap->{'atopic'} // + $globalWildcardReadingsMap->{'atopic'} // undef; # qos & retain & expression #my($qos, $retain, $expression) = retrieveQosRetainExpression($globalWildcardReadingsMap, $globalReadingMap, $wildcardReadingMap, $readingMap); my($qos, $retain, $expression) = retrieveQosRetainExpression($globalMap->{':defaults'}, $globalReadingMap, $globalWildcardReadingsMap, $wildcardReadingMap, $devMap->{':defaults'}, $readingMap); # wenn kein topic und keine expression definiert sind, kann auch nicht gesendet werden, es muss nichts mehr ausgewertet werden - return unless (defined($topic) or defined($atopic) or defined( $expression)); + return if !defined($topic) && !defined($atopic) && !defined($expression); # resendOnConnect Option - my $resendOnConnect = undef; - $resendOnConnect = $readingMap->{'resendOnConnect'} if defined $readingMap; - $resendOnConnect = $wildcardReadingMap->{'resendOnConnect'} if (defined($wildcardReadingMap) and !defined($resendOnConnect)); - # global - $resendOnConnect = $globalReadingMap->{'resendOnConnect'} if (defined($globalReadingMap) and !defined($resendOnConnect)); - $resendOnConnect = $globalWildcardReadingsMap->{'resendOnConnect'} if (defined($globalWildcardReadingsMap) and !defined($resendOnConnect)); + my $resendOnConnect = $readingMap->{'resendOnConnect'} // + $wildcardReadingMap->{'resendOnConnect'} // + $globalReadingMap->{'resendOnConnect'} // + $globalWildcardReadingsMap->{'resendOnConnect'} // undef; # map name - my $name = undef; - if (defined($devMap) and defined($devMap->{':alias'})) { - $name = $devMap->{':alias'}->{'pub:'.$readingKey}; - $name = $devMap->{':alias'}->{'pub:'.$reading} unless defined $name; - } - if (defined($globalMap) and defined($globalMap->{':alias'}) and !defined($name)) { - $name = $globalMap->{':alias'}->{'pub:'.$readingKey}; - $name = $globalMap->{':alias'}->{'pub:'.$reading} unless defined $name; - } - $name = $reading unless defined $name; + my $name = $devMap->{':alias'}->{'pub:'.$readingKey} // + $devMap->{':alias'}->{'pub:'.$reading} // + $globalMap->{':alias'}->{'pub:'.$readingKey} // + $globalMap->{':alias'}->{'pub:'.$reading} // + $reading; # get mode my $mode = $readingMap->{'mode'}; @@ -1269,10 +1260,10 @@ sub getDevicePublishRecIntern($$$$$$$) { # $topic evaluieren (avialable vars: $device (device name), $reading (oringinal name), $name ($reading oder alias, if defined), defaults) $combined->{'base'} = '' unless defined $combined->{'base'}; # base leer anlegen wenn nicht definiert - if(defined($topic) and ($topic =~ m/^{.*}$/)) { + if(defined($topic) && $topic =~ m{\A\{.*\}\z}x) { $topic = _evalValue2($hash->{NAME},$topic,{'topic'=>$topic,'device'=>$dev,'reading'=>$reading,'name'=>$name,'postfix'=>$postFix,%$combined}) if defined $topic; } - if(defined($atopic) and ($atopic =~ m/^{.*}$/)) { + if(defined($atopic) && $atopic =~ m{\A\{.*\}\z}x) { $atopic = _evalValue2($hash->{NAME},$atopic,{'topic'=>$atopic,'device'=>$dev,'reading'=>$reading,'name'=>$name,'postfix'=>$postFix,%$combined}) if defined $atopic; } @@ -1282,94 +1273,57 @@ sub getDevicePublishRecIntern($$$$$$$) { } # sucht Qos, Retain, Expression Werte unter Beruecksichtigung von Defaults und Globals -sub retrieveQosRetainExpression($$$$$$) { - my($globalDefaultReadingMap, $globalReadingMap, $wildcardDefaultReadingMap, $wildcardReadingMap, $defaultReadingMap, $readingMap) = @_; - my $qos=undef; - my $retain = undef; - my $expression = undef; - - # Log3('GB',1,"MQTT_GENERIC_BRIDGE: retrieveQosRetainExpression: globalDefaultReadingMap: ".Dumper($globalDefaultReadingMap)); - # Log3('GB',1,"MQTT_GENERIC_BRIDGE: retrieveQosRetainExpression: globalReadingMap: ".Dumper($globalReadingMap)); - # Log3('GB',1,"MQTT_GENERIC_BRIDGE: retrieveQosRetainExpression: defaultReadingMap: ".Dumper($defaultReadingMap)); - # Log3('GB',1,"MQTT_GENERIC_BRIDGE: retrieveQosRetainExpression: readingMap: ".Dumper($readingMap)); - - if(defined $readingMap) { - $qos = $readingMap->{'qos'}; - $retain = $readingMap->{'retain'}; - $expression = $readingMap->{'expression'}; - # if(defined($readingMap->{':defaults'})) { - # $qos = $readingMap->{':defaults'}->{'pub:qos'} unless defined $qos; - # $retain = $readingMap->{':defaults'}->{'pub:retain'} unless defined $retain; - # $expression = $readingMap->{':defaults'}->{'expression'} unless defined $expression; - # } - } - - if(defined $wildcardReadingMap) { - $qos = $wildcardReadingMap->{'qos'} unless defined $qos; - $retain = $wildcardReadingMap->{'retain'} unless defined $retain; - $expression = $wildcardReadingMap->{'expression'} unless defined $expression; - } - - if(defined $defaultReadingMap) { - $qos = $defaultReadingMap->{'pub:qos'} unless defined $qos; - $retain = $defaultReadingMap->{'pub:retain'} unless defined $retain; - $expression = $defaultReadingMap->{'pub:expression'} unless defined $expression; - $qos = $defaultReadingMap->{'qos'} unless defined $qos; - $retain = $defaultReadingMap->{'retain'} unless defined $retain; - $expression = $defaultReadingMap->{'expression'} unless defined $expression; - # if(defined($defaultReadingMap->{':defaults'})) { - # $qos = $defaultReadingMap->{':defaults'}->{'pub:qos'} unless defined $qos; - # $retain = $defaultReadingMap->{':defaults'}->{'pub:retain'} unless defined $retain; - # $expression = $defaultReadingMap->{':defaults'}->{'expression'} unless defined $expression; - # } - } - - if(defined $globalReadingMap) { - $qos = $globalReadingMap->{'qos'} unless defined $qos; # warum stand hier nicht unless defined? - $retain = $globalReadingMap->{'retain'} unless defined $retain; # s.o.? - $expression = $globalReadingMap->{'expression'} unless defined $expression; - # if(defined($globalReadingMap->{':defaults'})) { - # $qos = $globalReadingMap->{':defaults'}->{'pub:qos'} unless defined $qos; - # $retain = $globalReadingMap->{':defaults'}->{'pub:retain'} unless defined $retain; - # $expression = $globalReadingMap->{':defaults'}->{'expression'} unless defined $expression; - # } - } +sub retrieveQosRetainExpression { + my $globalDefaultReadingMap = shift; + my $globalReadingMap = shift; + my $wildcardDefaultReadingMap = shift; + my $wildcardReadingMap = shift; + my $defaultReadingMap = shift; + my $readingMap = shift; - if(defined $wildcardDefaultReadingMap) { - $qos = $wildcardDefaultReadingMap->{'qos'} unless defined $qos; - $retain = $wildcardDefaultReadingMap->{'retain'} unless defined $retain; - $expression = $wildcardDefaultReadingMap->{'expression'} unless defined $expression; - } + my $qos = $readingMap->{'qos'} // + $wildcardReadingMap->{'qos'} // + $defaultReadingMap->{'pub:qos'} // + $defaultReadingMap->{'qos'} // + $globalReadingMap->{'qos'} // + $wildcardDefaultReadingMap->{'qos'} // + $globalDefaultReadingMap->{'pub:qos'} // + $globalDefaultReadingMap->{'qos'} // 0; - if(defined $globalDefaultReadingMap) { - $qos = $globalDefaultReadingMap->{'pub:qos'} unless defined $qos; - $retain = $globalDefaultReadingMap->{'pub:retain'} unless defined $retain; - $expression = $globalDefaultReadingMap->{'pub:expression'} unless defined $expression; - $qos = $globalDefaultReadingMap->{'qos'} unless defined $qos; - $retain = $globalDefaultReadingMap->{'retain'} unless defined $retain; - $expression = $globalDefaultReadingMap->{'expression'} unless defined $expression; - # if(defined($globalDefaultReadingMap->{':defaults'})) { - # $qos = $globalDefaultReadingMap->{':defaults'}->{'pub:qos'} unless defined $qos; - # $retain = $globalDefaultReadingMap->{':defaults'}->{'pub:retain'} unless defined $retain; - # $expression = $globalDefaultReadingMap->{':defaults'}->{'expression'} unless defined $expression; - # } - } + my $retain = $readingMap->{'retain'} // + $wildcardReadingMap->{'retain'} // + $defaultReadingMap->{'pub:retain'} // + $defaultReadingMap->{'retain'} // + $globalReadingMap->{'retain'} // + $wildcardDefaultReadingMap->{'retain'} // + $globalDefaultReadingMap->{'pub:retain'} // + $globalDefaultReadingMap->{'retain'} // 0; - $qos = 0 unless defined $qos; - $retain = 0 unless defined $retain; + my $expression = $readingMap->{'expression'} // + $wildcardReadingMap->{'expression'} // + $defaultReadingMap->{'pub:expression'} // + $defaultReadingMap->{'expression'} // + $globalReadingMap->{'expression'} // + $wildcardDefaultReadingMap->{'expression'} // + $globalDefaultReadingMap->{'pub:expression'} // + $globalDefaultReadingMap->{'expression'} // undef; return ($qos, $retain, $expression); } # Evaluiert Werte in Default, wenn diese Variable / Perl-Expressions enthalten -sub computeDefaults($$$$$) { - my($hash, $modifier, $globalMap, $devMap, $infoMap) = @_; +sub computeDefaults { #Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] computeDefaults> infoMap: ".Dumper($infoMap)); + my $hash = shift // return; + my $modifier = shift // carp q[No modifier provided!] && return;; + my $globalMap = shift; #seems not to be mandatory + my $devMap = shift; #seems not to be mandatory + my $infoMap = shift // {}; my $mdLng = length($modifier); my $defaultCombined={}; - $infoMap = {} unless defined $infoMap; + #$infoMap = {} unless defined $infoMap; if (defined($globalMap) and defined($globalMap->{':defaults'})) { - foreach my $param (keys %{$globalMap->{':defaults'}} ) { + for my $param (keys %{$globalMap->{':defaults'}} ) { if(startsWith($param,$modifier)) { my $key = substr($param,$mdLng); my $val = $globalMap->{':defaults'}->{$param}; @@ -1382,7 +1336,7 @@ sub computeDefaults($$$$$) { } my $devCombined={}; if (defined($devMap) and defined($devMap->{':defaults'})) { - foreach my $param (keys %{$devMap->{':defaults'}} ) { + for my $param (keys %{$devMap->{':defaults'}} ) { if(startsWith($param,$modifier)) { my $key = substr($param,$mdLng); my $val = $devMap->{':defaults'}->{$param}; @@ -1391,7 +1345,7 @@ sub computeDefaults($$$$$) { } } } - foreach my $param (keys %{$devCombined} ) { + for my $param (keys %{$devCombined} ) { my $val = $devCombined->{$param}; $devCombined->{$param} = _evalValue2($hash->{NAME},$val,{%$defaultCombined, %$infoMap}); } @@ -1402,31 +1356,34 @@ sub computeDefaults($$$$$) { # Ersetzt im $str alle Variable $xxx durch entsprechende Werte aus dem Map {xxx=>wert, xxy=>wert2} # Ersetzt wird jedoch nur dann, wenn $str mit '{' anfaengt und mit '}' endet. # Nach dem Ersetzen wird (je $noEval-Wert) Perl-eval durchgefuehrt -sub _evalValue2($$;$$) { - my($mod, $str, $map, $noEval) = @_; - $noEval = 0 unless defined $noEval; +sub _evalValue2 { + my $mod = shift // return; + my $str = shift // carp q[No string to analyze!] && return; + my $map = shift; + my $noEval = shift // 0; #Log3('xxx',1,"MQTT_GENERIC_BRIDGE:DEBUG:> eval2: str: $str; map: ".Dumper($map)); my$ret = $str; # TODO : umbauen $str =~ m/^(.*)({.*})(.*)$/;; $1.$2.$3 - ok # TODO : Maskierte Klammern unterstuetzen? $str =~ m/^(.*)(\\{.*\\})(.*)({.*})(.*)$/;; $1.$2.$3.$4.$5 - irgendwie so #if($str =~ m/^{.*}$/) { #if($str =~ m/^(.*)({.*})(.*)$/) { - if($str =~ m/^(.*)(\{.*\})(.*)$/) { # forum https://forum.fhem.de/index.php/topic,117659.msg1121004.html#msg1121004 - my $s1=$1; $s1='' unless defined $s1; - my $s2=$2; $s2='' unless defined $s2; - my $s3=$3; $s3='' unless defined $s3; + if($str =~ m{\A(.*)(\{.*\})(.*)\z}x) { # forum https://forum.fhem.de/index.php/topic,117659.msg1121004.html#msg1121004 + my $s1 = $1 // q{}; #$s1='' unless defined $s1; + my $s2 = $2 // q{}; #$s2='' unless defined $s2; + my $s3 = $3 // q{}; #$s3='' unless defined $s3; no strict "refs"; - local $@; - my $base = ''; - my $device = ''; - my $reading = ''; - my $name = ''; + local $@ = undef; + my $base = q{}; + my $device = q{}; + my $reading = q{}; + my $name = q{}; #my $room = ''; if(defined($map)) { - foreach my $param (keys %{$map}) { - my $val = $map->{$param}; + for my $param (keys %{$map}) { + my $pname = '$'.$param; - $val=$pname unless defined $val; + my $val = $map->{$param} // $pname; + #$val=$pname if !defined $val; # Sonderlocken fuer $base, $name, $reading, $device, damit auch xxx:topic={$base} geht (sonst koente nur {"$base"} verwendet werden) if($pname eq '$base') { $base = $val; @@ -1441,14 +1398,15 @@ sub _evalValue2($$;$$) { } else { #Log3('xxx',1,"MQTT_GENERIC_BRIDGE:DEBUG:> replace2: $ret : $pname => $val"); #$ret =~ s/\Q$pname\E/$val/g; - $s2 =~ s/\Q$pname\E/$val/g; + $s2 =~ s{\Q$pname\E}{$val}gx; #Log3('xxx',1,"MQTT_GENERIC_BRIDGE:DEBUG:> replace2 done: $s2"); } } } #Log3('xxx',1,"MQTT_GENERIC_BRIDGE:DEBUG:> eval2 expr: $s2"); #$ret = eval($ret) unless $noEval; - $s2 = eval($s2) unless $noEval; + $s2 = eval($s2) if !$noEval; ##no critic qw(eval) + #we expressively want user code to be executed! This is added after compile time... #Log3('xxx',1,"MQTT_GENERIC_BRIDGE:DEBUG:> eval2 done: $s2"); if ($@) { Log3($mod,2,"MQTT_GENERIC_BRIDGE: evalValue: user value ('".$str."'') eval error: ".$@); @@ -1456,44 +1414,26 @@ sub _evalValue2($$;$$) { } else { $ret = $s1.$s2.$s3; } - $ret = _evalValue2($mod, $ret, $map, $noEval) unless $noEval; + $ret = _evalValue2($mod, $ret, $map, $noEval) if !$noEval; } return $ret; } -# Alte Methode, verwendet noch fixe Variable (base, dev, reading, name), kein Map -# soll durch _evalValue2 ersetzt werden -# sub _evalValue($$;$$$$) { -# my($mod, $str, $base, $device, $reading, $name) = @_; -# #Log3('xxx',1,"MQTT_GENERIC_BRIDGE:DEBUG:> eval: (str, base, dev, reading, name) $str, $base, $device, $reading, $name"); -# my$ret = $str; -# #$base="" unless defined $base; -# if($str =~ m/^{.*}$/) { -# no strict "refs"; -# local $@; -# #Log3('xxx',1,"MQTT_GENERIC_BRIDGE:DEBUG:> eval !!!"); -# $ret = eval($str); -# #Log3('xxx',1,"MQTT_GENERIC_BRIDGE:DEBUG:> eval done: $ret"); -# if ($@) { -# Log3($mod,2,"MQTT_GENERIC_BRIDGE: evalValue: user value ('".$str."'') eval error: ".$@); -# } -# } -# return $ret; -# } + # sucht zu dem gegebenen (ankommenden) topic das entsprechende device und reading # Params: $hash, $topic (empfangene topic) # return: map (device1->{reading}=>reading1, device1->{expression}=>{...}, deviceN->{reading}=>readingM) -sub searchDeviceForTopic($$) { - my($hash, $topic) = @_; - +sub searchDeviceForTopic { + #my($hash, $topic) = @_; + my $hash = shift // return; + my $topic = shift // carp q[No topic provided!] && return; my $ret = {}; - my $map = $hash->{+HS_TAB_NAME_DEVICES}; + my $map = $hash->{+HS_TAB_NAME_DEVICES} // return; my $globalMap = $map->{':global'}; - if(defined ($map)) { - foreach my $dname (keys %{$map}) { + for my $dname (keys %{$map}) { my $dmap = $map->{$dname}->{':subscribe'}; - foreach my $rmap (@{$dmap}) { + for my $rmap (@{$dmap}) { my $topicExp = $rmap->{'topicExp'}; #Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] searchDeviceForTopic: $dname => expr: ".Dumper($topicExp)); if (defined($topicExp) and $topic =~ $topicExp) { @@ -1502,21 +1442,21 @@ sub searchDeviceForTopic($$) { # Check named groups: $+{reading},.. my $reading = undef; my $oReading = $rmap->{'reading'}; - my $nReading = undef; - #my $nReading = $+{name}; # TODO ummappen ueber 'alias' - # map name my $fname = $+{name}; + my $nReading; + if(defined($fname)) { if (defined($map->{$dname}->{':alias'})) { $nReading = $map->{$dname}->{':alias'}->{'sub:'.$fname}; } - if (!defined($nReading) and defined($globalMap) and defined($globalMap->{':alias'})) { + if (!defined($nReading) && defined($globalMap) && defined($globalMap->{':alias'})) { $nReading = $globalMap->{':alias'}->{'sub:'.$fname}; } - $nReading = $fname unless defined $nReading; + $nReading = $fname if !defined $nReading; } - $nReading = $+{reading} unless defined $nReading; - if((!defined($nReading)) or ($oReading eq $nReading)) { + $nReading = $+{reading} if !defined $nReading; + + if( !defined($nReading) || $oReading eq $nReading ) { $reading = $oReading; } if($rmap->{'wildcardTarget'}) { @@ -1526,38 +1466,37 @@ sub searchDeviceForTopic($$) { $reading = $nReading; } #$reading = $rmap->{'reading'} unless defined $reading; - next unless defined $reading; + next if !defined $reading; #Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] searchDeviceForTopic: match topic: $topic, reading: $reading, nREading: $nReading, oReading: $oReading"); my $tn = $dname.':'.$reading; $ret->{$tn}->{'mode'}=$rmap->{'mode'}; $ret->{$tn}->{'reading'}=$reading; - my $device = $+{device}; # TODO: Pruefen, ob Device zu verwenden ist => wie? - $device = $dname unless defined $device; + my $device = $+{device} // $dname; # TODO: Pruefen, ob Device zu verwenden ist => wie? + #$device = $dname unless defined $device; $ret->{$tn}->{'device'}=$device; $ret->{$tn}->{'expression'}=$rmap->{'expression'}; #Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] searchDeviceForTopic: deliver: ".Dumper($ret)); } } } - } return $ret; } # Erstellt RexExp-Definitionen zum Erkennen der ankommenden Topics # Platzhalter werden entsprechend verarbeitet -sub createRegexpForTopic($) { - my $t = shift; - $t =~ s|#$|.\*|; +sub createRegexpForTopic { + my $t = shift // return; + $t =~ s|#$|.\*|x; # Zugriff auf benannte captures: $+{reading} - $t =~ s|(\$reading)|(\?\+)|g; - $t =~ s|(\$name)|(\?\+)|g; - $t =~ s|(\$device)|(\?\+)|g; - $t =~ s|\$|\\\$|g; - $t =~ s|\/\.\*$|.\*|; - $t =~ s|\/|\\\/|g; + $t =~ s|(\$reading)|(\?\+)|gx; + $t =~ s|(\$name)|(\?\+)|gx; + $t =~ s|(\$device)|(\?\+)|gx; + $t =~ s|\$|\\\$|gx; + $t =~ s|\/\.\*$|.\*|x; + $t =~ s|\/|\\\/|gx; #$t =~ s|(\+)([^+]*$)|(+)$2|; - $t =~ s|\+|[^\/]+|g; + $t =~ s|\+|[^\/]+|gx; return "^$t\$"; } @@ -1565,15 +1504,20 @@ sub createRegexpForTopic($) { # Params: Bridge-Hash, Dev-Name (im Map, ist auch = DevName), # Internes Map mit allen Definitionen fuer alle Gerate, # Attribute-Value zum Parsen -sub CreateSingleDeviceTableAttrSubscribe($$$$) { - my($hash, $dev, $map, $attrVal) = @_; +sub CreateSingleDeviceTableAttrSubscribe { #($$$$) { + #my($hash, $dev, $map, $attrVal) = @_; + my $hash = shift // return; + my $dev = shift // carp q[No device name provided!] && return; + my $map = shift // carp q[No map arg provided!] && return; + my $attrVal = shift; + #Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] CreateSingleDeviceTableAttrSubscribe: $dev, $attrVal, ".Dumper($map)); #Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] CreateSingleDeviceTableAttrSubscribe: ".Dumper($map)); # collect subscribe topics my $devMap = $map->{$dev}; my $globalMap = $map->{':global'}; delete ($devMap->{':subscribe'}); - if(defined $attrVal) { + return if !defined $attrVal; # format: # :topic="asd/asd" # :stopic="asd/asd" @@ -1591,10 +1535,10 @@ sub CreateSingleDeviceTableAttrSubscribe($$$$) { if(defined($named)){ #Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] CreateSingleDeviceTableAttrSubscribe: ".Dumper($map)); my $dmap = {}; - foreach my $param (keys %{$named}) { + for my $param (keys %{$named}) { my $val = $named->{$param}; - my($name,$ident) = split(":",$param); - if(!defined($ident) or !defined($name)) { next; } + my($name,$ident) = split m{:}xms, $param; + if(!defined($ident) || !defined($name)) { next; } $ident = 'topic' if $ident eq 'readings-topic'; #$ident = 'sttopic' if $ident eq 'self-trigger-topic'; @@ -1606,20 +1550,18 @@ sub CreateSingleDeviceTableAttrSubscribe($$$$) { ($ident eq 'stopic') or ($ident eq 'atopic') or ($ident eq 'qos') or ($ident eq 'retain') or ($ident eq 'expression')) { - my @nameParts = split(/\|/, $name); - while (@nameParts) { - my $namePart = shift(@nameParts); - next if($namePart eq ""); - my $rmap = $dmap->{$namePart}; - $rmap = {} unless defined $rmap; + my @nameParts = split m{\|}xms, $name; + for my $namePart (@nameParts) { + next if($namePart eq ''); + my $rmap = $dmap->{$namePart} // {}; $rmap->{'reading'}=$namePart; - $rmap->{'wildcardTarget'} = $namePart =~ /^\*/; + $rmap->{'wildcardTarget'} = $namePart =~ m{\A\*}x; #$rmap->{'evalTarget'} = $namePart =~ /^{.+}.*$/; $rmap->{'dev'}=$dev; $rmap->{$ident}=$val; - if(($ident eq 'topic') or + if( $ident eq 'topic' || #($ident eq 'sttopic') or - ($ident eq 'stopic') or ($ident eq 'atopic')) { # -> topic + $ident eq 'stopic' || $ident eq 'atopic') { # -> topic $rmap->{'mode'} = 'R'; #$rmap->{'mode'} = 'T' if $ident eq 'sttopic'; @@ -1648,22 +1590,25 @@ sub CreateSingleDeviceTableAttrSubscribe($$$$) { $topic = _evalValue2($hash->{NAME},$val,{'device'=>$dev,'reading'=>'#reading','name'=>'#name',%$combined}) if defined $val; if(!defined($topic)) { Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE: [$hash->{NAME}] subscribe: error while interpret topic: $val"); + } else { my $old = '#reading'; my $new = '$reading'; - $topic =~ s/\Q$old\E/$new/g; + #$topic =~ s/\Q$old\E/$new/g; + $topic =~ s{\Q$old\E}{$new}gx; $old = '#name'; $new = '$name'; - $topic =~ s/\Q$old\E/$new/g; + #$topic =~ s/\Q$old\E/$new/g; + $topic =~ s{\Q$old\E}{$new}gx; #Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] sub: Topic old: $topic"); #Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] sub: Topic new: $topic"); $rmap->{'topicOrig'} = $val; $rmap->{'topicExp'}=createRegexpForTopic($topic); - $topic =~ s/\$reading/+/g; - $topic =~ s/\$name/+/g; - $topic =~ s/\$device/+/g; + $topic =~ s{\$reading}{+}gx; + $topic =~ s{\$name}{+}gx; + $topic =~ s{\$device}{+}gx; } $rmap->{'topic'} = $topic; } # <- topic @@ -1676,40 +1621,52 @@ sub CreateSingleDeviceTableAttrSubscribe($$$$) { $devMap->{':subscribe'}= \@vals; } $map->{$dev} = $devMap; - } - return undef; + return; } # Prueft, ob Geraete keine Definitionen mehr enthalten und entfernt diese ggf. aus der Tabelle -sub deleteEmptyDevices($$$) { - my ($hash, $map, $devMapName) = @_; - return unless defined $map; - return unless defined $devMapName; - return unless defined $map->{$devMapName}; +sub deleteEmptyDevices { #($$$) { + #my ($hash, $map, $devMapName) = @_; + my $hash = shift // return; + my $map = shift // carp q[No map arg provided!] && return; + my $devMapName = shift // carp q[No devMapName provided!] && return; + + return if !defined $map->{$devMapName}; # Wenn keine Eintraege => Device loeschen if(keys %{$map->{$devMapName}} == 0) { delete($map->{$devMapName}); } + return; } # Erstellt alle Strukturen fuer fuer ein bestimmtes Geraet (Default, Alias, Publish, Subscribe). # Params: Bridge-Hash, Dev-Name , Dev-Map-Name (meist = DevName, kann aber auch ein Pseudegeraet wie ':global' sein), # Attr-prefix (idR 'mqtt') # Internes Map mit allen Definitionen fuer alle Gerate, -sub CreateSingleDeviceTable($$$$$) { - my ($hash, $dev, $devMapName, $prefix, $map) = @_; +sub CreateSingleDeviceTable { #($$$$$) { + # my ($hash, $dev, $devMapName, $prefix, $map) = @_; + my $hash = shift // return; + my $dev = shift // carp q[No device name provided!] && return; + my $devMapName = shift // carp q[No devMapName provided!] && return; + my $prefix = shift // carp q[No prefix provided!] && return; + my $map = shift // carp q[No map arg provided!] && return; # Divece-Attribute fuer ein bestimmtes Device aus Device-Attributen auslesen CreateSingleDeviceTableAttrDefaults($hash, $devMapName, $map, AttrVal($dev, $prefix.CTRL_ATTR_NAME_DEFAULTS, undef)); CreateSingleDeviceTableAttrAlias($hash, $devMapName, $map, AttrVal($dev, $prefix.CTRL_ATTR_NAME_ALIAS, undef)); CreateSingleDeviceTableAttrPublish($hash, $devMapName, $map, AttrVal($dev, $prefix.CTRL_ATTR_NAME_PUBLISH, undef)); CreateSingleDeviceTableAttrSubscribe($hash, $devMapName, $map, AttrVal($dev, $prefix.CTRL_ATTR_NAME_SUBSCRIBE, undef)); - deleteEmptyDevices($hash, $map, $devMapName); + return deleteEmptyDevices($hash, $map, $devMapName); } # Geraet-Infos neu einlesen -sub _RefreshDeviceTable($$$$;$$) { - my ($hash, $dev, $devMapName, $prefix, $attrName, $attrVal) = @_; +sub _RefreshDeviceTable { + my $hash = shift // return; + my $dev = shift // carp q[No device name provided!] && return; + my $devMapName = shift // carp q[No devMapName provided!] && return; + my $prefix = shift // carp q[No prefix provided!] && return; + my $attrName = shift; + my $attrVal = shift; #Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] _RefreshDeviceTable: $dev, $devMapName, $prefix, $attrName, $attrVal"); # Attribute zu dem angegeben Geraet neu erfassen my $map = $hash->{+HS_TAB_NAME_DEVICES}; @@ -1725,58 +1682,69 @@ sub _RefreshDeviceTable($$$$;$$) { } deleteEmptyDevices($hash, $map, $devMapName) unless defined $attrVal; - UpdateSubscriptionsSingleDevice($hash, $dev); + return UpdateSubscriptionsSingleDevice($hash, $dev); } # Geraet-Infos neu einlesen -sub RefreshDeviceTable($$;$$) { - my ($hash, $dev, $attrName, $attrVal) = @_; +sub RefreshDeviceTable { + my $hash = shift // return; + my $dev = shift // carp q[No device name provided!] && return; + my $attrName = shift; + my $attrVal = shift; my $prefix = $hash->{+HS_PROP_NAME_PREFIX}; - _RefreshDeviceTable($hash, $dev, $dev, $prefix, $attrName, $attrVal); + return _RefreshDeviceTable($hash, $dev, $dev, $prefix, $attrName, $attrVal); } -sub RefreshGlobalTableAll($) { - my ($hash) = @_; +sub RefreshGlobalTableAll { + my $hash = shift // return; my $name = $hash->{NAME}; RefreshGlobalTable($hash, CTRL_ATTR_NAME_GLOBAL_PREFIX.CTRL_ATTR_NAME_DEFAULTS, AttrVal($name,CTRL_ATTR_NAME_GLOBAL_PREFIX.CTRL_ATTR_NAME_DEFAULTS, undef)); RefreshGlobalTable($hash, CTRL_ATTR_NAME_GLOBAL_PREFIX.CTRL_ATTR_NAME_ALIAS, AttrVal($name,CTRL_ATTR_NAME_GLOBAL_PREFIX.CTRL_ATTR_NAME_ALIAS, undef)); - RefreshGlobalTable($hash, CTRL_ATTR_NAME_GLOBAL_PREFIX.CTRL_ATTR_NAME_PUBLISH, AttrVal($name,CTRL_ATTR_NAME_GLOBAL_PREFIX.CTRL_ATTR_NAME_PUBLISH, undef)); + return RefreshGlobalTable($hash, CTRL_ATTR_NAME_GLOBAL_PREFIX.CTRL_ATTR_NAME_PUBLISH, AttrVal($name,CTRL_ATTR_NAME_GLOBAL_PREFIX.CTRL_ATTR_NAME_PUBLISH, undef)); #RefreshGlobalTable($hash, CTRL_ATTR_NAME_GLOBAL_PREFIX.CTRL_ATTR_NAME_SUBSCRIBE, AttrVal($name,CTRL_ATTR_NAME_GLOBAL_PREFIX.CTRL_ATTR_NAME_SUBSCRIBE, undef)); } # GlobalTable-Infos neu einlesen fuer einen bestimmten Attribut -sub RefreshGlobalTable($;$$) { - my ($hash, $attrName, $attrVal) = @_; +sub RefreshGlobalTable { + my $hash = shift // return; + my $attrName = shift // carp q[No attribute name]; + my $attrVal = shift // carp q[No attribute value] && return; + my $prefix = CTRL_ATTR_NAME_GLOBAL_PREFIX; - _RefreshDeviceTable($hash, $hash->{NAME}, ':global', $prefix, $attrName, $attrVal); + return _RefreshDeviceTable($hash, $hash->{NAME}, ':global', $prefix, $attrName, $attrVal); } # Geraet umbenennen, wird aufgerufen, wenn ein Geraet in FHEM umbenannt wird -sub RenameDeviceInTable($$$) { - my($hash, $dev, $devNew) = @_; +sub RenameDeviceInTable { + my $hash = shift // return; + my $dev = shift // carp q[No device name provided!] && return; + my $devNew = shift // carp q[No new device name provided!] && return; + my $map = $hash->{+HS_TAB_NAME_DEVICES}; - if(defined($map->{$dev})) { - #$map->{$devNew}=$map->{$dev}; + + return if !defined($map->{$dev}); + delete($map->{$dev}); my $prefix = $hash->{+HS_PROP_NAME_PREFIX}; CreateSingleDeviceTable($hash, $devNew, $devNew, $prefix, $map); - UpdateSubscriptionsSingleDevice($hash, $devNew); - } + return UpdateSubscriptionsSingleDevice($hash, $devNew); + } # Geraet loeschen (geloescht in FHEM) -sub DeleteDeviceInTable($$) { - my($hash, $dev) = @_; +sub DeleteDeviceInTable { + my $hash = shift // return; + my $dev = shift // carp q[No device name provided!] && return; my $map = $hash->{+HS_TAB_NAME_DEVICES}; - if(defined($map->{$dev})) { + + return if !defined($map->{$dev}); delete($map->{$dev}); - UpdateSubscriptions($hash); - } + return UpdateSubscriptions($hash); } # alle zu ueberwachende Geraete durchsuchen und relevanter Informationen einlesen -sub CreateDevicesTable($) { - my ($hash) = @_; +sub CreateDevicesTable { + my $hash = shift // return; # alle zu ueberwachende Geraete durchgehen und Attribute erfassen my $map={}; $hash->{+HS_TAB_NAME_DEVICES} = $map; @@ -1786,7 +1754,7 @@ sub CreateDevicesTable($) { my @devices = devspec2array($hash->{+HS_PROP_NAME_DEVSPEC}); #Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] CreateDevicesTable: ".Dumper(@devices)); my $prefix = $hash->{+HS_PROP_NAME_PREFIX}; - foreach my $dev (@devices) { + for my $dev (@devices) { if($dev ne $hash->{NAME}) { Log3($hash->{NAME},5,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] CreateDevicesTable for ".$dev); CreateSingleDeviceTable($hash, $dev, $dev, $prefix, $map); @@ -1797,22 +1765,22 @@ sub CreateDevicesTable($) { CreateSingleDeviceTable($hash, $hash->{NAME}, ":global", CTRL_ATTR_NAME_GLOBAL_PREFIX, $map); $hash->{+HS_TAB_NAME_DEVICES} = $map; - UpdateSubscriptions($hash); + return UpdateSubscriptions($hash); #$hash->{+HELPER}->{+HS_FLAG_INITIALIZED} = 1; } # Ueberbleibsel eines Optimierungsversuchs -sub UpdateSubscriptionsSingleDevice($$) { - my ($hash, $dev) = @_; +sub UpdateSubscriptionsSingleDevice { + my $hash = shift // return; # Liste der Geraete mit der Liste der Subscriptions abgleichen # neue Subscriptions bei Bedarf anlegen und/oder ueberfluessige loeschen # fuer Einzeldevices vermutlich eher schwer, erstmal komplet updaten - UpdateSubscriptions($hash); + return UpdateSubscriptions($hash); } # Alle MQTT-Subscriptions erneuern -sub UpdateSubscriptions($) { - my ($hash) = @_; +sub UpdateSubscriptions { + my $hash = shift // return; updateDevCount($hash); @@ -1822,11 +1790,11 @@ sub UpdateSubscriptions($) { my $topicMap = {}; my $gmap = $hash->{+HS_TAB_NAME_DEVICES}; if(defined($gmap)) { - foreach my $dname (keys %{$gmap}) { + for my $dname (keys %{$gmap}) { my $smap = $gmap->{$dname}->{':subscribe'}; #Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] UpdateSubscriptions: smap = ".Dumper($gmap->{$dname})); if(defined($smap)) { - foreach my $rmap (@{$smap}) { + for my $rmap (@{$smap}) { my $topic = $rmap->{'topic'}; $topicMap->{$topic}->{'qos'}=$rmap->{'qos'} if defined $topic; } @@ -1843,11 +1811,11 @@ sub UpdateSubscriptions($) { #Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] UpdateSubscriptions: topics = ".Dumper(@topics)); my @new=(); my @remove=(); - foreach my $topic (@topics) { + for my $topic (@topics) { next if ($topic eq ""); push @new,$topic unless grep {$_ eq $topic} @{$hash->{subscribe}}; } - foreach my $topic (@{$hash->{subscribe}}) { + for my $topic (@{$hash->{subscribe}}) { next if ($topic eq ""); push @remove,$topic unless grep {$_ eq $topic} @topics; } @@ -1856,11 +1824,11 @@ sub UpdateSubscriptions($) { #Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] UpdateSubscriptions: new = ".Dumper(@new)); if(isIODevMQTT($hash)) { - foreach my $topic (@remove) { + for my $topic (@remove) { #Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] UpdateSubscriptions: unsubscribe: topic = ".Dumper($topic)); client_unsubscribe_topic($hash,$topic); } - foreach my $topic (@new) { + for my $topic (@new) { my $qos = $topicMap->{$topic}->{'qos'}; # TODO: Default lesen $qos = 0 unless defined $qos; my $retain = 0; # not supported @@ -1874,11 +1842,12 @@ sub UpdateSubscriptions($) { # MQTT2 Subscriptions IOWrite($hash, "subscriptions", join(" ", @new)); } + return; } # Alle MQTT-Subscription erntfernen -sub RemoveAllSubscripton($) { - my ($hash) = @_; +sub RemoveAllSubscripton { + my $hash = shift // return; #if(isIODevMQTT($hash)) { if(isIODevMQTT2_CLIENT($hash)) { @@ -1898,23 +1867,24 @@ sub RemoveAllSubscripton($) { $hash->{subscribeExpr}=[]; $hash->{subscribeQos}={}; } + return; } -sub InitializeDevices($) { - my ($hash) = @_; +sub InitializeDevices { + my $hash = shift // return; # alles neu aufbauen # Deviceliste neu aufbauen, ggf., alte subscription kuendigen, neue abonieren #Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] ------------ InitializeDevices --------------"); - CreateDevicesTable($hash); + return CreateDevicesTable($hash); #UpdateSubscriptions($hash); } # Falls noetig, Geraete initialisieren -sub CheckInitialization($) { - my ($hash) = @_; +sub CheckInitialization { + my $hash = shift // return; # Pruefen, on interne Strukturen initialisiert sind return if $hash->{+HELPER}->{+HS_FLAG_INITIALIZED}; - InitializeDevices($hash); + return InitializeDevices($hash); } # Zusaetzliche Attribute im Debug-Modus @@ -1925,13 +1895,16 @@ my %getsDebug = ( ); # Routine fuer FHEM Get-Commando -sub Get($$$@) { - my ($hash, $name, $command, $args) = @_; +sub Get { + my $hash = shift // return; + my $name = shift; + my $command = shift // return "Need at least one parameters"; + my $args = shift; #Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] get CL: ".Dumper($hash->{CL})); - return "Need at least one parameters" unless (defined $command); + #return "Need at least one parameters" unless (defined $command); unless (defined($gets{$command}) or (isDebug($hash) && defined($getsDebug{$command}))) { my $rstr="Unknown argument $command, choose one of"; - foreach my $vname (keys %gets) { + for my $vname (keys %gets) { $rstr.=" $vname"; my $vval=$gets{$vname}; $rstr.=":$vval" if $vval; @@ -1939,8 +1912,8 @@ sub Get($$$@) { if (isDebug($hash)) { $rstr.=" debugInfo:noArg debugReinit:noArg"; $rstr.=" debugShowPubRec:"; - foreach my $dname (sort keys %{$hash->{+HS_TAB_NAME_DEVICES}}) { - foreach my $rname (sort keys %{$hash->{+HS_TAB_NAME_DEVICES}->{$dname}->{':publish'}}) { + for my $dname (sort keys %{$hash->{+HS_TAB_NAME_DEVICES}}) { + for my $rname (sort keys %{$hash->{+HS_TAB_NAME_DEVICES}->{$dname}->{':publish'}}) { $rstr.= $dname.'>'.$rname.','; } $rstr.= $dname.'>unknownReading,'; @@ -1956,8 +1929,8 @@ sub Get($$$@) { $clientIsWeb = 1 if (defined($clType) and ($clType eq 'FHEMWEB')); } - COMMAND_HANDLER: { - $command eq "debugInfo" and isDebug($hash) and do { + #COMMAND_HANDLER: { + if ($command eq "debugInfo" and isDebug($hash)) { my $debugInfo = "initialized: ".$hash->{+HELPER}->{+HS_FLAG_INITIALIZED}."\n\n"; $debugInfo.= "device data records: ".Dumper($hash->{+HS_TAB_NAME_DEVICES})."\n\n"; $debugInfo.= "subscriptionTab: ".Dumper($hash->{+HS_TAB_NAME_SUBSCRIBE})."\n\n"; @@ -1968,55 +1941,66 @@ sub Get($$$@) { $debugInfo.= "exclude reading map: ".Dumper($hash->{+HS_PROP_NAME_GLOBAL_EXCLUDES_READING})."\n\n"; $debugInfo.= "exclude device map: ".Dumper($hash->{+HS_PROP_NAME_GLOBAL_EXCLUDES_DEVICES})."\n\n"; - $debugInfo =~ s//>/g; + $debugInfo =~ s{<}{<}gx; + $debugInfo =~ s{>}{>}gx; return $debugInfo; - }; - $command eq "version" and do { + } + + if ($command eq "version") { return $VERSION; - }; - $command eq "debugReinit" and isDebug($hash) and do { + } + if ($command eq "debugReinit" and isDebug($hash)) { InitializeDevices($hash); - last; + return; }; - $command eq "debugShowPubRec" and do { - my($dev,$reading) = split(/>/,$args); + if ($command eq "debugShowPubRec") { + my($dev,$reading) = split m{>}xms, $args; return "PubRec: $dev:$reading = ".Dumper(getDevicePublishRec($hash, $dev, $reading)); - #last; - }; - $command eq "devlist" and do { - my $res=""; - foreach my $dname (sort keys %{$hash->{+HS_TAB_NAME_DEVICES}}) { + } + if ($command eq "devlist") { + my $res= q{}; + for my $dname (sort keys %{$hash->{+HS_TAB_NAME_DEVICES}}) { if($dname ne ":global") { if($args) { - next unless $dname =~ /^$args$/; + next if $dname !~ m{\A$args\z}x; } - $res.=$dname."\n"; + $res.= "${dname}\n"; } } - $res = "no devices found" unless ($res ne ""); + return "no devices found" if $res eq ''; return $res; - }; - $command eq "devinfo" and do { - my $res=""; - foreach my $dname (sort keys %{$hash->{+HS_TAB_NAME_DEVICES}}) { + } + if ($command eq "devinfo") { + return getDevInfo($hash,$args);; + } + if ($command eq "refreshUserAttr") { + return refreshUserAttr($hash); + } + + return; +} + +sub getDevInfo { + my $hash = shift // return; + my $args = shift; + my $res = q{}; + for my $dname (sort keys %{$hash->{+HS_TAB_NAME_DEVICES}}) { if($dname ne ":global") { if($args) { - next unless $dname =~ /^$args$/; + next if $dname !~ m{\A$args\z}x; } $res.=$dname."\n"; $res.=" publish:\n"; - foreach my $rname (sort keys %{$hash->{+HS_TAB_NAME_DEVICES}->{$dname}->{':publish'}}) { - #my $pubRec = getDevicePublishRec($hash, $dname, $rname); + for my $rname (sort keys %{$hash->{+HS_TAB_NAME_DEVICES}->{$dname}->{':publish'}}) { my $pubRecList = getDevicePublishRec($hash, $dname, $rname); - if(defined($pubRecList)) { - foreach my $pubRec (@$pubRecList) { - if(defined($pubRec)) { + next if !defined($pubRecList); + for my $pubRec (@$pubRecList) { + next if !defined($pubRec); my $expression = $pubRec->{'expression'}; my $mode = $pubRec->{'mode'}; - $mode='E' if(defined($expression) and !defined($mode)); - my $topic = undef; + $mode='E' if(defined($expression) && !defined($mode)); + my $topic = 'undefined'; if($mode eq 'R') { $topic = $pubRec->{'topic'}; } elsif($mode eq 'A') { @@ -2026,7 +2010,6 @@ sub Get($$$@) { } else { $topic = '!unexpected mode!'; } - $topic = 'undefined' unless defined $topic; my $qos = $pubRec->{'qos'}; my $retain = $pubRec->{'retain'}; my $postFix = $pubRec->{'postfix'}; @@ -2040,18 +2023,15 @@ sub Get($$$@) { $res.= ")\n"; $res.= " exp: $expression\n" if defined ($expression); } - } - } } $res.=" subscribe:\n"; my @resa; - foreach my $subRec (@{$hash->{+HS_TAB_NAME_DEVICES}->{$dname}->{':subscribe'}}) { + for my $subRec (@{$hash->{+HS_TAB_NAME_DEVICES}->{$dname}->{':subscribe'}}) { my $qos = $subRec->{'qos'}; my $mode = $subRec->{'mode'}; my $expression = $subRec->{'expression'}; - my $topic = $subRec->{'topic'}; - $topic = '---' unless defined $topic; - my $rest.= sprintf(' %-16s <= %s', $subRec->{'reading'}, $topic); + my $topic = $subRec->{'topic'} // '---'; + my $rest= sprintf(' %-16s <= %s', $subRec->{'reading'}, $topic); $rest.= " (mode: $mode"; $rest.= "; qos: $qos" if defined ($qos); $rest.= ")\n"; @@ -2062,20 +2042,8 @@ sub Get($$$@) { } $res.= "\n"; } - # TODO : Weitere Dev Infos? - $res = "no devices found" unless ($res ne ""); + $res = "no devices found" if $res eq ''; return $res; - #last; - }; - $command eq "refreshUserAttr" and do { - refreshUserAttr($hash); - } - - # $command eq "YYY" and do { - # # - # last; - # }; - }; } sub Set { @@ -2084,13 +2052,14 @@ sub Set { } # Routine fuer FHEM Notify -sub Notify() { - my ($hash,$dev) = @_; +sub Notify { + my $hash = shift // return; + my $dev = shift // carp q[No device hash provided!] && return; #Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] notify for ".$dev->{NAME}." ".Dumper(@{$dev->{CHANGED}})) if $dev->{TYPE} ne 'MQTT_GENERIC_BRIDGE'; if( $dev->{NAME} eq "global" ) { #Log3($hash->{NAME},5,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] notify for global ".Dumper(@{$dev->{CHANGED}})); - if( grep(m/^(INITIALIZED|REREADCFG)$/, @{$dev->{CHANGED}}) ) { + if( grep { m{\A(INITIALIZED|REREADCFG)\z}x } @{$dev->{CHANGED}} ) { # FHEM (re)Start firstInit($hash); } @@ -2099,23 +2068,29 @@ sub Notify() { my $max = int(@{$dev->{CHANGED}}); for (my $i = 0; $i < $max; $i++) { my $s = $dev->{CHANGED}[$i]; - $s = "" if(!defined($s)); + $s = q{} if(!defined($s)); # tab, CR, LF durch spaces ersetzen - $s =~ s/[\r\n\t]/ /g; + $s =~ s{[\r\n\t]}{ }gx; #$s =~ s/ [ ]+/ /g; - if($s =~ m/^RENAMED ([^ ]*) ([^ ]*)$/) { + if($s =~ m{\ARENAMED\s+([^ ]*)\s+([^ ]*)\z}x) { # Device renamed my ($old, $new) = ($1, $2); #Log3($hash->{NAME},5,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] Device renamed: $old => $new"); # wenn ein ueberwachtes device, tabelle korrigieren RenameDeviceInTable($hash, $old, $new); - } elsif($s =~ m/^DELETED ([^ ]*)$/) { + next; + } + if($s =~ m{\ADELETED\s+([^ ]*)\z}x) { + #elsif($s =~ m/^DELETED ([^ ]*)$/) { # Device deleted my ($name) = ($1); #Log3($hash->{NAME},5,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] Device deleted: $name"); # wenn ein ueberwachtes device, tabelle korrigieren DeleteDeviceInTable($hash, $name); - } elsif($s =~ m/^ATTR ([^ ]*) ([^ ]*) (.*)$/) { + next; + } + if($s =~ m{\AATTR\s+([^ ]*)\s+([^ ]*)\s+(.*)\z}x) { + #elsif($s =~ m/^ATTR ([^ ]*) ([^ ]*) (.*)$/) { # Attribut created or changed my ($sdev, $attrName, $val) = ($1, $2, $3); #Log3($hash->{NAME},5,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] attr created/changed: $sdev : $attrName = $val"); @@ -2128,7 +2103,10 @@ sub Notify() { # check/ publish atopic => val publishDeviceUpdate($hash, $defs{$sdev}, 'A', $attrName, $val); } - } elsif($s =~ m/^DELETEATTR ([^ ]*) ([^ ]*)$/) { + next; + } + if($s =~ m{\ADELETEATTR\s+([^ ]*)\s+([^ ]*)\z}x) { + #elsif($s =~ m/^DELETEATTR ([^ ]*) ([^ ]*)$/) { # Attribut deleted my ($sdev, $attrName) = ($1, $2); #Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] attr deleted: $sdev : $attrName"); @@ -2140,17 +2118,19 @@ sub Notify() { # check/ publish atopic => null publishDeviceUpdate($hash, $defs{$sdev}, 'A', $attrName, undef); } + next; } } - return undef; + return; } - checkPublishDeviceReadingsUpdates($hash, $dev); + return checkPublishDeviceReadingsUpdates($hash, $dev); } # Pruefen, ob in dem Device Readings-Aenderungen vorliegen, die gepublished werden sollen -sub checkPublishDeviceReadingsUpdates($$) { - my ($hash, $dev) = @_; +sub checkPublishDeviceReadingsUpdates { + my $hash = shift // return; + my $dev = shift // carp q[No monitored device hash provided!] && return; # # pruefen, ob die Aenderung von der Bridge selbst getriggert wurde # # es ist der Readingsname drin, die Pruefung wird jedoch derzeit nicht vorgenommen, da nur ein Reading in CHANGE drin sein kann @@ -2165,10 +2145,18 @@ sub checkPublishDeviceReadingsUpdates($$) { #Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] checkPublishDeviceReadingsUpdates: ".$dev->{NAME}." : ".Dumper(@{$dev->{CHANGED}})) if $dev->{TYPE} ne 'MQTT_GENERIC_BRIDGE'; # nicht waehrend FHEM startet - return if( !$main::init_done ); + return if !$init_done ; # nicht, wenn deaktivert - return "" if(main::IsDisabled($hash->{NAME})); + return '' if(::IsDisabled($hash->{NAME})); + + #are we at the end of a bulk update? + if ($dev->{'.mqttGenericBridge_triggeredBulk'}) { + delete $dev->{'.mqttGenericBridge_triggeredReading'}; + delete $dev->{'.mqttGenericBridge_triggeredReading_val'}; + delete $dev->{'.mqttGenericBridge_triggeredBulk'}; + return; + } #CheckInitialization($hash); #Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] checkPublishDeviceReadingsUpdates ------------------------ "); @@ -2177,34 +2165,39 @@ sub checkPublishDeviceReadingsUpdates($$) { # Pruefen, ob ein ueberwachtes Geraet vorliegt my $devName = $dev->{NAME}; my $devDataTab = $hash->{+HS_TAB_NAME_DEVICES}; # Geraetetabelle - return unless defined $devDataTab; # not initialized now or internal error + return if !defined $devDataTab; # not initialized now or internal error my $devDataRecord = $devDataTab->{$devName}; # - unless (defined($devDataRecord)) { + if (!defined($devDataRecord)) { # Pruefen, ob ggf. Default map existiert. my $globalDataRecord = $devDataTab->{':global'}; - return "" unless defined $globalDataRecord; + return '' if !defined $globalDataRecord; my $globalPublishMap = $globalDataRecord->{':publish'}; - return "" unless defined $globalPublishMap; - my $size = int(keys %{$globalPublishMap}); - return "" unless ($size>0); + #return '' if !defined $globalPublishMap; + #my $size = int(keys %{$globalPublishMap}); + #return '' unless ($size>0); + return '' if !defined $globalPublishMap || !(%{$globalPublishMap}); } - foreach my $event (@{deviceEvents($dev,1)}) { + for my $event (@{deviceEvents($dev,1)}) { #Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] Notify for $dev->{NAME} event: $event STATE: $dev->{STATE} ".Dumper($dev)); #Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] Notify for $dev->{NAME} event: $event STATE: $dev->{STATE}"); - $event =~ /^([^:]+)(:\s)?(.*)$/sm; # Schalter /sm ist wichtig! Sonst wir bei mehrzeiligen Texten Ende nicht korrekt erkannt. s. https://perldoc.perl.org/perlretut.html#Using-regular-expressions-in-Perl + #$event =~ /^([^:]+)(:\s)?(.*)$/sm; # Schalter /sm ... + $event =~ m{\A(?[^:]+)(?:\s)?(?.*)\z}smx; # Schalter /sm ist wichtig! Sonst wir bei mehrzeiligen Texten Ende nicht korrekt erkannt. s. https://perldoc.perl.org/perlretut.html#Using-regular-expressions-in-Perl #Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] event: $event, '".((defined $1) ? $1 : "-undef-")."', '".((defined $3) ? $3 : "-undef-")."'") if $dev->{TYPE} ne 'MQTT_GENERIC_BRIDGE'; - my $devreading = $1; - my $devval = $3; + #my $devreading = $1; + #my $devval = $3; + my $devreading = $+{dev}; + my $devval = $+{devrv}; + # Sonderlocke fuer 'state' in einigen Faellen: z.B. bei ReadingsProxy kommt in CHANGEDWITHSTATE nichts an, und in CHANGE, wie gehabt, z.B. 'off' - if(!$2) { + if(!$+{devr}) { #$devval = $devreading; $devval = $event; $devreading = 'state'; } - if(defined($devreading) and defined($devval)) { + if(defined($devreading) && defined($devval)) { #Log3($hash->{NAME},1,">MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] event: $event, '".((defined $devreading) ? $devreading : "-undef-")."', '".((defined $devval) ? $devval : "-undef-")."'"); # wenn ueberwachtes device and reading # pruefen, ob die Aenderung von der Bridge selbst getriggert wurde TODO TEST @@ -2214,8 +2207,8 @@ sub checkPublishDeviceReadingsUpdates($$) { #Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] Notify [mqttGenericBridge_triggeredReading]=>".$triggeredReading."=".$triggeredReadingVal." changed reading: ".$devreading); #} # Auch Wert vergleichen - if(!defined($triggeredReading) or ($devreading ne $triggeredReading) or ($devval ne $triggeredReadingVal)) { - if(defined($triggeredReading) and ($devreading eq $triggeredReading)) { + if(!defined($triggeredReading) || $devreading ne $triggeredReading || $devval ne $triggeredReadingVal) { + if(defined($triggeredReading) && $devreading eq $triggeredReading) { # Wenn Name passt, aber der Wert veraendert wurde, dann einmal senden und den gesendeten Wert merken # TODO: Besser in einer Tabelle (name=value) fuehren (fuer jedes einzelne Reading) und bei match enizeln entfernen # => damit verhindert, dass wert verloren geht, wenn eine endere REading dazwischenkommt @@ -2230,6 +2223,7 @@ sub checkPublishDeviceReadingsUpdates($$) { } } } + return; } # Definiert Liste der auszuschliessenden Type/Readings-Kombinationen. @@ -2243,11 +2237,16 @@ sub checkPublishDeviceReadingsUpdates($$) { # bzw. genannte Readings an jedem Geraetetyp ignoriert werden. # Zusaetzlich kann auch die Richtung optional angegeben werden (pub oder sub). Dann gilt die Ausnahme entsprechend nur fuers Senden oder nur fuer Empfang. # TEST: {Dumper(MQTT::GENERIC_BRIDGE::defineGlobalTypeExclude($defs{'mqttGenericBridge'},'sub:type:reading pub:*:reading2 sub:*:* test'))} -sub defineGlobalTypeExclude($;$) { - my ($hash, $valueType) = @_; +sub defineGlobalTypeExclude { + my $hash = shift // return; + my $valueType = shift // DEFAULT_GLOBAL_TYPE_EXCLUDES; + + $valueType.= ' '.DEFAULT_GLOBAL_TYPE_EXCLUDES if $valueType ne DEFAULT_GLOBAL_TYPE_EXCLUDES; + + #my ($hash, $valueType) = @_; #$valueType = AttrVal($hash->{NAME}, CTRL_ATTR_NAME_GLOBAL_TYPE_EXCLUDE, DEFAULT_GLOBAL_TYPE_EXCLUDES) unless defined $valueType; - $valueType = DEFAULT_GLOBAL_TYPE_EXCLUDES unless defined $valueType; - $valueType.= ' '.DEFAULT_GLOBAL_TYPE_EXCLUDES if defined $valueType; + #$valueType = DEFAULT_GLOBAL_TYPE_EXCLUDES unless defined $valueType; + #$valueType.= ' '.DEFAULT_GLOBAL_TYPE_EXCLUDES if defined $valueType; #$main::attr{$hash->{NAME}}{+CTRL_ATTR_NAME_GLOBAL_TYPE_EXCLUDE} = $valueType; # HS_PROP_NAME_GLOBAL_EXCLUDES_TYPE und HS_PROP_NAME_GLOBAL_EXCLUDES_READING @@ -2265,16 +2264,16 @@ sub defineGlobalTypeExclude($;$) { my($unnamed, $named) = main::parseParams($valueType,'\s',' ','='); - foreach my $val (@$unnamed) { + for my $val (@$unnamed) { next if($val eq ''); - my($dir, $type, $reading) = split(/:/, $val); - if ((!defined $reading) and ($dir ne 'pub') and ($dir ne 'sub')) { + my($dir, $type, $reading) = split m{:}xms, $val; + if (!defined $reading && $dir ne 'pub' && $dir ne 'sub') { $reading=$type; $type=$dir; $dir=undef; } next if($type eq ''); - $reading='*' unless defined $reading; + $reading = '*' if !defined $reading; $reading = '*' if $reading eq ''; #Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] defineGlobalTypeExclude: dir, type, reading: ".Dumper(($dir, $type, $reading))); if (!defined $dir) { @@ -2302,10 +2301,14 @@ sub defineGlobalTypeExclude($;$) { # Ein Stern anstatt des Geraetenamens ist nicht erlaubt (benutzen Sie in diesem Fall GlobalTypeExclude). # Zusaetzlich kann auch die Richtung optional angegeben werden (pub oder sub). Dann gilt die Ausnahme entsprechend nur fuers Senden oder nur fuer Empfang. # TEST {Dumper(MQTT::GENERIC_BRIDGE::defineGlobalDevExclude($defs{'mqttGenericBridge'},'sub:dev1:reading1 dev2:reading2 dev3 pub:a: *:* test'))} -sub defineGlobalDevExclude($;$) { - my ($hash, $valueName) = @_; - $valueName = DEFAULT_GLOBAL_DEV_EXCLUDES unless defined $valueName; - $valueName.= ' '.DEFAULT_GLOBAL_DEV_EXCLUDES if defined $valueName; +sub defineGlobalDevExclude { + my $hash = shift // return; + my $valueName = shift // DEFAULT_GLOBAL_DEV_EXCLUDES; + #$valueName = DEFAULT_GLOBAL_DEV_EXCLUDES unless defined $valueName; + #$valueName.= ' '.DEFAULT_GLOBAL_DEV_EXCLUDES if defined $valueName; + #Beta-User: Logikfehler? Wenn, dann müßte man die beiden vorangehenden Zeilen umdrehen, oder? Oder so: + $valueName.= ' '.DEFAULT_GLOBAL_DEV_EXCLUDES if $valueName ne DEFAULT_GLOBAL_DEV_EXCLUDES; + # HS_PROP_NAME_GLOBAL_EXCLUDES_DEVICES $hash->{+HS_PROP_NAME_GLOBAL_EXCLUDES_DEVICES}={}; @@ -2319,16 +2322,16 @@ sub defineGlobalDevExclude($;$) { # } my($unnamed, $named) = main::parseParams($valueName,'\s',' ','='); - foreach my $val (@$unnamed) { + for my $val (@$unnamed) { next if($val eq ''); - my($dir, $dev, $reading) = split(/:/, $val); - if ((!defined $reading) and ($dir ne 'pub') and ($dir ne 'sub')) { + my($dir, $dev, $reading) = split m{:}xms , $val; + if (!defined $reading && $dir ne 'pub' && $dir ne 'sub') { $reading=$dev; $dev=$dir; $dir=undef; } next if($dev eq ''); - $reading = '*' unless defined $reading; + $reading = '*' if !defined $reading; $reading = '*' if $reading eq ''; #Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] defineGlobalDevExclude: dir, dev, reading: ".Dumper(($dir, $dev, $reading))); if (!defined $dir) { @@ -2343,10 +2346,10 @@ sub defineGlobalDevExclude($;$) { } # Setzt Liste der auszuschliessenden Type/Readings-Kombinationenb auf Defaultwerte zurueck (also falls Attribut nicht definiert ist). -sub defineDefaultGlobalExclude($) { - my ($hash) = @_; +sub defineDefaultGlobalExclude { + my $hash = shift // return; defineGlobalTypeExclude($hash, AttrVal($hash->{NAME}, CTRL_ATTR_NAME_GLOBAL_TYPE_EXCLUDE, DEFAULT_GLOBAL_TYPE_EXCLUDES)); - defineGlobalDevExclude($hash, AttrVal($hash->{NAME}, CTRL_ATTR_NAME_GLOBAL_DEV_EXCLUDE, DEFAULT_GLOBAL_DEV_EXCLUDES)); + return defineGlobalDevExclude($hash, AttrVal($hash->{NAME}, CTRL_ATTR_NAME_GLOBAL_DEV_EXCLUDE, DEFAULT_GLOBAL_DEV_EXCLUDES)); } # Prueft, ob Type/Reading- oder Geraete/Reading-Kombination von der Uebertragung ausgeschlossen werden soll, @@ -2356,11 +2359,15 @@ sub defineDefaultGlobalExclude($) { # $type: Geraetetyp # $devName: Geraetename # $reading: Reading -sub isTypeDevReadingExcluded($$$$$) { - my ($hash, $direction, $type, $devName, $reading) = @_; +sub isTypeDevReadingExcluded { + my $hash = shift // return; + my $direction = shift // carp q[No direction provided!] && return; + my $type = shift // carp q[No device type provided!] && return; + my $devName = shift // carp q[No device name provided!] && return; + my $reading = shift // carp q[No reading provided!] && return; # pruefen, ob im Geraet ignore steht - my $devDisable = $main::attr{$devName}{$hash->{+HS_PROP_NAME_PREFIX}.CTRL_ATTR_NAME_IGNORE}; + my $devDisable = $attr{$devName}{$hash->{+HS_PROP_NAME_PREFIX}.CTRL_ATTR_NAME_IGNORE}; $devDisable = '0' unless defined $devDisable; return 1 if $devDisable eq 'both'; return 1 if (($direction eq 'pub') and ($devDisable eq 'outgoing')); @@ -2403,24 +2410,35 @@ sub isTypeDevReadingExcluded($$$$$) { # $hash: HASH # $devName: Geraetename # $reading: Reading (ggf. for future use) -sub isDoForward($$$) { - my ($hash, $devName, $reading) = @_; - my $doForward = $main::attr{$devName}{$hash->{+HS_PROP_NAME_PREFIX}.CTRL_ATTR_NAME_FORWARD}; +sub isDoForward { + my $hash = shift // return; + my $devName = shift // carp q[No device name provided!] && return; + #my $reading = shift // carp q[No reading provided!] && return; - $doForward = 'none' if (!defined($doForward) and ($defs{$devName}->{TYPE} eq 'dummy')); # Hack fuer Dummy-Devices + my $doForward = $attr{$devName}{$hash->{+HS_PROP_NAME_PREFIX}.CTRL_ATTR_NAME_FORWARD}; - $doForward = 'all' unless defined $doForward; + $doForward = 'none' if !defined($doForward) && $defs{$devName}->{TYPE} eq 'dummy'; # Hack fuer Dummy-Devices + + #$doForward = 'all' if !defined $doForward; #Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] isDoForward $devName => $doForward"); - return 1 if $doForward eq 'all'; + return 1 if !defined $doForward || $doForward eq 'all'; return 0; } # MQTT-Nachricht senden # Params: Bridge-Hash, Topic, Nachricht, QOS- und Retain-Flags -sub doPublish($$$$$$$$) { - my ($hash,$device,$reading,$topic,$message,$qos,$retain,$resendOnConnect) = @_; +sub doPublish { #($$$$$$$$) { + #my ($hash,$device,$reading,$topic,$message,$qos,$retain,$resendOnConnect) = @_; + my $hash = shift // return; + my $device = shift // carp q[No device provided!] && return; + my $reading = shift // carp q[No reading provided!] && return; + my $topic = shift // carp q[No topic provided!] && return; + my $message = shift // carp q[No message provided!] && return; + my $qos = shift // 0; + my $retain = shift // 0; + my $resendOnConnect = shift; #Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] doPublish: topic: $topic, msg: $message, resend mode: ".(defined($resendOnConnect)?$resendOnConnect:"no")); if(!isConnected($hash)) { @@ -2437,23 +2455,23 @@ sub doPublish($$$$$$$$) { my $entry = {'topic'=>$topic, 'message'=>$message, 'qos'=>$qos, 'retain'=>$retain, 'resendOnConnect'=>$resendOnConnect,'device'=>$device,'reading'=>$reading}; my $topicQueue = $queue->{$topic}; - unless (defined($topicQueue)) { + if (!defined($topicQueue)) { $topicQueue = [$entry]; } else { if ($resendOnConnect eq 'first') { - if (scalar @$topicQueue == 0) { - $topicQueue = [$entry]; - } + #if (scalar @$topicQueue == 0) { + $topicQueue = [$entry] if !(@$topicQueue); + #} } elsif($resendOnConnect eq 'last') { $topicQueue = [$entry]; } else { # all - push (@$topicQueue, $entry); + push @$topicQueue, $entry; } } # check max lng - my $max = $hash->{+HS_PROP_NAME_PUB_OFFLINE_QUEUE_MAX_CNT_PROTOPIC}; - $max = 10 unless defined $max; + my $max = $hash->{+HS_PROP_NAME_PUB_OFFLINE_QUEUE_MAX_CNT_PROTOPIC} // 10; + #$max = 10 unless defined $max; while (scalar @$topicQueue > $max) { shift @$topicQueue; } @@ -2467,34 +2485,45 @@ sub doPublish($$$$$$$$) { return 'stored'; } - Log3($hash->{NAME},5,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] publish: $topic => $message (qos: $qos, retain: ".(defined($retain)?$retain:'0').")"); + Log3($hash->{NAME},5,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] publish: $topic => $message (qos: $qos, retain: $retain"); - if (isIODevMQTT2($hash)){ #if ($hash->{+HELPER}->{+IO_DEV_TYPE} eq 'MQTT2_SERVER') { + if (isIODevMQTT2($hash)){ # TODO: publish MQTT2 # TODO qos / retain ? $topic.=':r' if $retain; IOWrite($hash, "publish", $topic.' '.$message); - readingsSingleUpdate($hash,"transmission-state","outgoing publish sent",1); + $hash->{+HELPER}->{+HS_PROP_NAME_OUTGOING_CNT}++; - readingsSingleUpdate($hash,"outgoing-count",$hash->{+HELPER}->{+HS_PROP_NAME_OUTGOING_CNT},1); - return undef; + readingsBeginUpdate($hash); + readingsBulkUpdate($hash,'transmission-state','outgoing publish sent'); + #readingsSingleUpdate($hash,"transmission-state","outgoing publish sent",1); + readingsBulkUpdate($hash,'outgoing-count',$hash->{+HELPER}->{+HS_PROP_NAME_OUTGOING_CNT}); + #readingsSingleUpdate($hash,"outgoing-count",$hash->{+HELPER}->{+HS_PROP_NAME_OUTGOING_CNT},1); + readingsEndUpdate($hash,1); + return; } elsif (isIODevMQTT($hash)) { #elsif ($hash->{+HELPER}->{+IO_DEV_TYPE} eq 'MQTT') { #Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] doPublish for $device, $reading, topic: $topic, message: $message"); my $msgid; if(defined($topic) and defined($message)) { #Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] send_publish: topic: $topic, message: $message"); $msgid = send_publish($hash->{IODev}, topic => $topic, message => $message, qos => $qos, retain => $retain); - readingsSingleUpdate($hash,"transmission-state","outgoing publish sent",1); + $hash->{+HELPER}->{+HS_PROP_NAME_OUTGOING_CNT}++; - readingsSingleUpdate($hash,"outgoing-count",$hash->{+HELPER}->{+HS_PROP_NAME_OUTGOING_CNT},1); + readingsBeginUpdate($hash); + readingsBulkUpdate($hash,'transmission-state','outgoing publish sent'); + readingsBulkUpdate($hash,'outgoing-count',$hash->{+HELPER}->{+HS_PROP_NAME_OUTGOING_CNT}); + readingsEndUpdate($hash,1); + + #readingsSingleUpdate($hash,"transmission-state","outgoing publish sent",1); + #readingsSingleUpdate($hash,"outgoing-count",$hash->{+HELPER}->{+HS_PROP_NAME_OUTGOING_CNT},1); #Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] publish: $topic => $message"); - return undef; + return; } $hash->{message_ids}->{$msgid}++ if defined $msgid; return 'empty topic or message'; } else { my $iodt = retrieveIODevType($hash); - $iodt = 'undef' unless defined $iodt; + $iodt = 'undef' if !defined $iodt; Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE: [$hash->{NAME}] unknown IODev: ".$iodt); return 'unknown IODev'; } @@ -2504,11 +2533,17 @@ sub doPublish($$$$$$$$) { # Params: Bridge-Hash, Device-Hash, # Modus (Topics entsprechend Readings- oder Attributen-Tabelleneintraegen suchen), # Name des Readings/Attributes, Wert -sub publishDeviceUpdate($$$$$) { - my ($hash, $devHash, $mode, $reading, $value) = @_; +sub publishDeviceUpdate { #($$$$$) { +# my ($hash, $devHash, $mode, $reading, $value) = @_; + my $hash = shift // return; + my $devHash = shift // carp q[No hash for target device provided!] && return; + my $mode = shift // q{R}; + my $reading = shift // carp q[No reading provided!] && return; + my $value = shift // q{\0} ; # TODO: pruefen: oder doch ""?; + my $devn = $devHash->{NAME}; my $type = $devHash->{TYPE}; - $mode = 'R' unless defined $mode; + #$mode = 'R' unless defined $mode; #Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] publishDeviceUpdate for $type, $mode, $devn, $reading, $value"); # bestimmte bekannte types und readings ausschliessen (vor allem 'transmission-state' in der eigenen Instanz, das fuert sonst zu einem Endlosloop) return if($type eq "MQTT_GENERIC_BRIDGE"); @@ -2529,8 +2564,10 @@ sub publishDeviceUpdate($$$$$) { #Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] publishDeviceUpdate for $devn, $reading, $value"); my $pubRecList = getDevicePublishRec($hash, $devn, $reading); #Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] publishDeviceUpdate pubRec: ".Dumper($pubRecList)); + + #Beta-User: direct return? if(defined($pubRecList)) { - foreach my $pubRec (@$pubRecList) { + for my $pubRec (@$pubRecList) { if(defined($pubRec)) { # my $msgid; #Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] publishDeviceUpdate pubRec: ".Dumper($pubRec)); @@ -2541,7 +2578,7 @@ sub publishDeviceUpdate($$$$$) { my $qos = $pubRec->{'qos'}; my $retain = $pubRec->{'retain'}; my $expression = $pubRec->{'expression'}; - my $base = $pubRec->{'base'}; + my $base = $pubRec->{'base'} // q{}; my $resendOnConnect = $pubRec->{'resendOnConnect'}; # # damit beim start die Attribute einmal uebertragen werden => geht wohl mangels event beim start nicht # if(!$main::init_done and !defined($resendOnConnect) and ($mode eq 'A')) { @@ -2549,9 +2586,9 @@ sub publishDeviceUpdate($$$$$) { # Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] queueing Attr-Chang at start"); # } - $base='' unless defined $base; + #$base='' unless defined $base; - $value="\0" unless defined $value; # TODO: pruefen: oder doch ""? + #$value="\0" unless defined $value; # TODO: pruefen: oder doch ""? my $redefMap=undef; my $message=$value; @@ -2562,21 +2599,22 @@ sub publishDeviceUpdate($$$$$) { # Rueckgabewert wird ignoriert, falls dieser ein Array ist. # Bei einem Hash werden Paare als Topic-Message Paare verwendet und mehrere Nachrichten gesendet no strict "refs"; - local $@; + local $@ = undef; # $device, $reading, $name (und fuer alle Faelle $topic) in $defMap packen, so zur Verfügung stellen (für eval)reicht wegen _evalValue2 wohl nicht my $name = $reading; # TODO: Name-Mapping my $device = $devn; #if(!defined($defMap->{'room'})) { # $defMap->{'room'} = AttrVal($devn,'room',''); #} - if(!defined($defMap->{'uid'}) and defined($defs{$devn})) { - $defMap->{'uid'} = $defs{$devn}->{'FUUID'}; - $defMap->{'uid'} = '' unless defined $defMap->{'uid'}; + if(!defined($defMap->{'uid'}) && defined($defs{$devn})) { + $defMap->{'uid'} = $defs{$devn}->{'FUUID'} // q{}; + #$defMap->{'uid'} = '' unless defined $defMap->{'uid'}; } #Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE:DEBUG:> DEBUG: >>> expression: $expression : ".Dumper($defMap)); my $ret = _evalValue2($hash->{NAME},$expression,{'topic'=>$topic,'device'=>$devn,'reading'=>$reading,'name'=>$name,'time'=>TimeNow(),%$defMap},1); #Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE:DEBUG:> DEBUG: <<< expression: ".Dumper($ret)); - $ret = eval($ret); + $ret = eval($ret); ##no critic qw(eval) + # we expressively want user code to be executed! This is added after compile time... #Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE:DEBUG:> DEBUG: <<< eval expression: ".Dumper($ret)); if(ref($ret) eq 'HASH') { $redefMap = $ret; @@ -2599,10 +2637,10 @@ sub publishDeviceUpdate($$$$$) { my $updated = 0; if(defined($redefMap)) { #Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE:DEBUG:> DEBUG: redefMap: ".Dumper($redefMap)); - foreach my $key (keys %{$redefMap}) { + for my $key (keys %{$redefMap}) { my $val = $redefMap->{$key}; my $r = doPublish($hash,$devn,$reading,$key,$val,$qos,$retain,$resendOnConnect); - $updated = 1 unless defined $r; + $updated = 1 if !defined $r; } } elsif (defined $topic and defined $message) { my $r = doPublish($hash,$devn,$reading,$topic,$message,$qos,$retain,$resendOnConnect); @@ -2615,107 +2653,85 @@ sub publishDeviceUpdate($$$$$) { } } } + return; } # Routine fuer FHEM Attr -sub Attr($$$$) { +sub Attr { my ($command,$name,$attribute,$value) = @_; - my $hash = $main::defs{$name}; - ATTRIBUTE_HANDLER: { + my $hash = $defs{$name} // return; + # Steuerattribute - $attribute eq CTRL_ATTR_NAME_GLOBAL_PREFIX.CTRL_ATTR_NAME_DEFAULTS and do { + if ( $attribute eq CTRL_ATTR_NAME_GLOBAL_PREFIX.CTRL_ATTR_NAME_DEFAULTS + || $attribute eq CTRL_ATTR_NAME_GLOBAL_PREFIX.CTRL_ATTR_NAME_ALIAS + || $attribute eq CTRL_ATTR_NAME_GLOBAL_PREFIX.CTRL_ATTR_NAME_PUBLISH) { if ($command eq "set") { RefreshGlobalTable($hash, $attribute, $value); } else { RefreshGlobalTable($hash, $attribute, undef); } - last; - }; - $attribute eq CTRL_ATTR_NAME_GLOBAL_PREFIX.CTRL_ATTR_NAME_ALIAS and do { - if ($command eq "set") { - RefreshGlobalTable($hash, $attribute, $value); - } else { - RefreshGlobalTable($hash, $attribute, undef); + return; } - last; - }; - $attribute eq CTRL_ATTR_NAME_GLOBAL_PREFIX.CTRL_ATTR_NAME_PUBLISH and do { - if ($command eq "set") { - RefreshGlobalTable($hash, $attribute, $value); - } else { - RefreshGlobalTable($hash, $attribute, undef); - } - last; - }; - $attribute eq CTRL_ATTR_NAME_GLOBAL_TYPE_EXCLUDE and do { + if ($attribute eq CTRL_ATTR_NAME_GLOBAL_TYPE_EXCLUDE) { if ($command eq "set") { defineGlobalTypeExclude($hash,$value); } else { defineGlobalTypeExclude($hash,undef); } - last; - }; - $attribute eq CTRL_ATTR_NAME_GLOBAL_DEV_EXCLUDE and do { + return; + } + if ($attribute eq CTRL_ATTR_NAME_GLOBAL_DEV_EXCLUDE) { if ($command eq "set") { defineGlobalDevExclude($hash,$value); } else { defineGlobalDevExclude($hash,undef); } - last; - }; - # $attribute eq "XXX" and do { - # if ($command eq "set") { - # #$hash->{publishState} = $value; - # } else { - # #delete $hash->{publishState}; - # } - # last; - # }; + return; + } + my $prefix = $hash->{+HS_PROP_NAME_PREFIX}; - (($attribute eq $prefix.CTRL_ATTR_NAME_DEFAULTS) or + if (($attribute eq $prefix.CTRL_ATTR_NAME_DEFAULTS) or ($attribute eq $prefix.CTRL_ATTR_NAME_ALIAS) or ($attribute eq $prefix.CTRL_ATTR_NAME_PUBLISH) or ($attribute eq $prefix.CTRL_ATTR_NAME_SUBSCRIBE) or ($attribute eq $prefix.CTRL_ATTR_NAME_IGNORE) or - ($attribute eq $prefix.CTRL_ATTR_NAME_FORWARD) - ) and do { + ($attribute eq $prefix.CTRL_ATTR_NAME_FORWARD)) { + if ($command eq "set") { return "this attribute is not allowed here"; } - last; - }; + return; + } # Gateway-Device - $attribute eq "IODev" and do { + if ($attribute eq "IODev") { my $ioDevType = undef; $ioDevType = $defs{$value}{TYPE} if defined ($value) and defined ($defs{$value}); $hash->{+HELPER}->{+IO_DEV_TYPE} = $ioDevType; if ($command eq "set") { - my $oldValue = $main::attr{$name}{IODev}; - if ($main::init_done) { + my $oldValue = $attr{$name}{IODev}; + if ($init_done) { unless (defined ($oldValue) and ($oldValue eq $value) ) { #Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] attr: change IODev"); - MQTT::client_stop($hash) if defined($main::attr{$name}{IODev}) and ($main::attr{$name}{IODev} eq 'MQTT'); - $main::attr{$name}{IODev} = $value; + MQTT::client_stop($hash) if defined($attr{$name}{IODev}) and ($attr{$name}{IODev} eq 'MQTT'); + $attr{$name}{IODev} = $value; firstInit($hash); } } } else { - if ($main::init_done) { + if ($init_done) { MQTT::client_stop($hash) if defined ($ioDevType) and ($ioDevType eq 'MQTT'); } } - - last; - }; - return undef; + return; } + return; } # CallBack-Handler fuer IODev beim Connect -sub ioDevConnect($) { +sub ioDevConnect { my $hash = shift; #return if isIODevMQTT2($hash); #if $hash->{+HELPER}->{+IO_DEV_TYPE} eq 'MQTT2_SERVER'; # TODO @@ -2726,8 +2742,10 @@ sub ioDevConnect($) { # resend stored msgs => doPublish (...., undef) my $queue = $hash->{+HELPER}->{+HS_PROP_NAME_PUB_OFFLINE_QUEUE}; - if (defined($queue)) { - foreach my $topic (keys %{$queue}) { + + return if !defined($queue); + #if (defined($queue)) { + for my $topic (keys %{$queue}) { my $topicQueue = $queue->{$topic}; my $topicRec = undef; while ($topicRec = shift(@$topicQueue)) { @@ -2741,71 +2759,80 @@ sub ioDevConnect($) { updatePubTime($hash,$devn,$reading) unless defined $r; } } - } - + #} + return; } # CallBack-Handler fuer IODev beim Disconnect -sub ioDevDisconnect($) { +sub ioDevDisconnect { my $hash = shift; #return if isIODevMQTT2($hash); #if $hash->{+HELPER}->{+IO_DEV_TYPE} eq 'MQTT2_SERVER'; #Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] ioDevDisconnect"); # TODO ? + return; } # Per MQTT-Empfangenen Aktualisierungen an die entsprechende Geraete anwenden # Params: Bridge-Hash, Modus (R=Readings, A=Attribute), Device, Reading/Attribute-Name, Nachricht -sub doSetUpdate($$$$$) { - my ($hash,$mode,$device,$reading,$message) = @_; +sub doSetUpdate { #($$$$$) { + #my ($hash,$mode,$device,$reading,$message) = @_; + my $hash = shift // return; + my $mode = shift // q{unexpected!}; + my $device = shift // carp q[No device provided!] && return; + my $reading = shift // carp q[No reading provided!] && return; + my $message = shift; # // carp q[No message content!] && return; + my $isBulk = shift // 0; - my $dhash = $defs{$device}; - return unless defined $dhash; + my $dhash = $defs{$device} // carp qq[No device hash for $device registered!] && return; + #return unless defined $dhash; #Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE: [$hash->{NAME}] setUpdate enter: update: $reading = $message"); - my $doForward = isDoForward($hash, $device,$reading); + #my $doForward = isDoForward($hash, $device,$reading); + my $doForward = isDoForward($hash, $device); #code seems only to support on device level! if($mode eq 'S') { my $err; my @args = split ("[ \t]+",$message); #Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] mqttGenericBridge_triggeredReading=".Dumper($dhash->{'.mqttGenericBridge_triggeredReading'})); if(($reading eq '') or ($reading eq 'state')) { - $dhash->{'.mqttGenericBridge_triggeredReading'}="state" unless $doForward; - $dhash->{'.mqttGenericBridge_triggeredReading_val'}=$message unless $doForward; + $dhash->{'.mqttGenericBridge_triggeredReading'}="state" if !$doForward; + $dhash->{'.mqttGenericBridge_triggeredReading_val'}=$message if !$doForward; #$err = DoSet($device,$message); $err = DoSet($device,@args); } else { - $dhash->{'.mqttGenericBridge_triggeredReading'}=$reading unless $doForward; - $dhash->{'.mqttGenericBridge_triggeredReading_val'}=$message unless $doForward; + $dhash->{'.mqttGenericBridge_triggeredReading'}=$reading if !$doForward; + $dhash->{'.mqttGenericBridge_triggeredReading_val'}=$message if !$doForward; #$err = DoSet($device,$reading,$message); $err = DoSet($device,$reading,@args); } - unless (defined($err)) { + if (!defined($err)) { $hash->{+HELPER}->{+HS_PROP_NAME_UPDATE_S_CNT}++; readingsSingleUpdate($hash,"updated-set-count",$hash->{+HELPER}->{+HS_PROP_NAME_UPDATE_S_CNT},1); - return undef; + return; } Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE: [$hash->{NAME}] setUpdate: error in set command: ".$err); return "error in set command: $err"; } elsif($mode eq 'R') { # or $mode eq 'T') { # R - Normale Topic (beim Empfang nicht weiter publishen) # T - Selt-Trigger-Topic (Sonderfall, auch wenn gerade empfangen, kann weiter getriggert/gepublisht werden. Vorsicht! Gefahr von 'Loops'!) - readingsBeginUpdate($dhash); - if ($mode eq 'R') { - $dhash->{'.mqttGenericBridge_triggeredReading'}=$reading unless $doForward; - $dhash->{'.mqttGenericBridge_triggeredReading_val'}=$message unless $doForward; + readingsBeginUpdate($dhash) if !$isBulk; + if ($mode eq 'R' && !$doForward) { + $dhash->{'.mqttGenericBridge_triggeredReading'} = $reading; + $dhash->{'.mqttGenericBridge_triggeredReading_val'} = $message; + $dhash->{'.mqttGenericBridge_triggeredBulk'} = 1 if $isBulk; } readingsBulkUpdate($dhash,$reading,$message); - readingsEndUpdate($dhash,1); + readingsEndUpdate($dhash,1) if !$isBulk; #Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE: [$hash->{NAME}] setUpdate: update: $reading = $message"); # wird in 'notify' entfernt # delete $dhash->{'.mqttGenericBridge_triggeredReading'}; $hash->{+HELPER}->{+HS_PROP_NAME_UPDATE_R_CNT}++; readingsSingleUpdate($hash,"updated-reading-count",$hash->{+HELPER}->{+HS_PROP_NAME_UPDATE_R_CNT},1); - return undef; + return; } elsif($mode eq 'A') { CommandAttr(undef, "$device $reading $message"); - return undef; + return; } else { Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE: [$hash->{NAME}] setUpdate: unexpected mode: ".$mode); return "unexpected mode: $mode"; @@ -2814,8 +2841,10 @@ sub doSetUpdate($$$$$) { } # Call von IODev-Dispatch (e.g.MQTT2) -sub Parse($$) { - my ($iodev, $msg) = @_; +sub Parse { + my $iodev = shift // carp q[No IODev provided!] && return;; + my $msg = shift // carp q[No message to analyze!] && return;; + my $ioname = $iodev->{NAME}; #my $iotype = $iodev->{TYPE}; #Log3($iodev->{NAME},1,"MQTT_GENERIC_BRIDGE: Parse: IODev: $ioname"); @@ -2823,25 +2852,25 @@ sub Parse($$) { # no support for autocreate #my $autocreate = "no"; - if($msg =~ m/^autocreate=([^\0]+)\0(.*)$/s) { - #$autocreate = $1; - $msg = $2; - } - + #if($msg =~ m{\Aautocreate=([^\0]+)\0(.*)\z}sx) { + ##$autocreate = $1; + #$msg = $2; + #} + $msg =~ s{\Aautocreate=([^\0]+)\0(.*)\z}{$2}sx; #my ($cid, $topic, $value) = split(":", $msg, 3); - my ($cid, $topic, $value) = split("\0", $msg, 3); + my ($cid, $topic, $value) = split m{\0}xms, $msg, 3; my @instances = devspec2array("TYPE=MQTT_GENERIC_BRIDGE"); my @ret=(); my $forceNext = 0; - foreach my $dev (@instances) { + for my $dev (@instances) { my $hash = $defs{$dev}; # Name mit IODev vegleichen my $iiodn = retrieveIODevName($hash); #Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE: [$hash->{NAME}] Parse: test IODev: $iiodn vs. $ioname"); - next unless $ioname eq $iiodn; + next if $ioname ne $iiodn; my $iiodt = retrieveIODevType($hash); - next unless checkIODevMQTT2($iiodt); + next if !checkIODevMQTT2($iiodt); #next unless isIODevMQTT2($hash); Log3($hash->{NAME},5,"MQTT_GENERIC_BRIDGE: [$hash->{NAME}] Parse ($iiodt : '$ioname'): Msg: $topic => $value"); @@ -2851,7 +2880,7 @@ sub Parse($$) { # unshift(@ret, "[NEXT]"); # damit weitere Geraetemodule ggf. aufgerufen werden # return @ret; my $fret = onmessage($hash, $topic, $value); - next unless defined $fret; + next if !defined $fret; if( ref($fret) eq 'ARRAY' ) { push (@ret, @{$fret}); $forceNext = 1 if AttrVal($hash->{NAME},'forceNEXT',0); @@ -2867,8 +2896,10 @@ sub Parse($$) { } # Routine MQTT-Message Callback -sub onmessage($$$) { - my ($hash,$topic,$message) = @_; +sub onmessage { + my $hash = shift // return; + my $topic = shift // carp q[No topic provided!] && return; + my $message = shift // q{}; #might be empty... // carp q[No message content!] && return; #CheckInitialization($hash); #Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] onmessage: $topic => $message"); @@ -2883,18 +2914,16 @@ sub onmessage($$$) { my $updated = 0; my @updatedList; - foreach my $deviceKey (keys %{$fMap}) { + for my $deviceKey (keys %{$fMap}) { my $device = $fMap->{$deviceKey}->{'device'}; my $reading = $fMap->{$deviceKey}->{'reading'}; my $mode = $fMap->{$deviceKey}->{'mode'}; my $expression = $fMap->{$deviceKey}->{'expression'}; - next unless defined $device; - next unless defined $reading; + next if !defined $device || !defined $reading; my $dhash = $defs{$device}; - next unless defined $dhash; - next if(isTypeDevReadingExcluded($hash, 'sub', $dhash->{TYPE}, $device, $reading)); + next if !defined $dhash || isTypeDevReadingExcluded($hash, 'sub', $dhash->{TYPE}, $device, $reading); my $redefMap=undef; @@ -2905,18 +2934,19 @@ sub onmessage($$$) { # Rueckgabewert wird ignoriert, falls dieser ein Array ist. # Bei einem Hash werden Paare als Reading-Wert Paare gesetzt (auch set (stopic), attr (atopic)) no strict "refs"; - local $@; + local $@ = undef; #Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE:DEBUG:> [$hash->{NAME}] eval ($expression) !!!"); my $value = $message; - my $ret = eval($expression); + my $ret = eval($expression); ##no critic qw(eval) + # we expressively want user code to be executed! This is added after compile time... if(ref($ret) eq 'HASH') { $redefMap = $ret; } elsif(ref($ret) eq 'ARRAY') { # ignore } elsif($value ne $message) { $message = $value; - } elsif(!defined($ret)) { - $message = undef; + #} elsif(!defined($ret)) { #Beta-User: same as next assignement..? + # $message = undef; } else { $message = $ret; } @@ -2924,22 +2954,24 @@ sub onmessage($$$) { if ($@) { Log3($hash->{NAME},2,"MQTT_GENERIC_BRIDGE: [$hash->{NAME}] onmessage: error while evaluating expression ('".$expression."'') eval error: ".$@); } - use strict "refs"; + #use strict "refs"; # this is automatically done in lexical scope } #next unless defined $device; #next unless defined $reading; - next unless defined $message; + next if !defined $message; if(defined($redefMap)) { - foreach my $key (keys %{$redefMap}) { + for my $key (keys %{$redefMap}) { my $val = $redefMap->{$key}; - my $r = doSetUpdate($hash,$mode,$device,$key,$val); + readingsBeginUpdate($defs{$device}); + my $r = doSetUpdate($hash,$mode,$device,$key,$val,1); unless (defined($r)) { - $updated = 1; + $updated = 1 if !$updated; push(@updatedList, $device); } + readingsEndUpdate($defs{$device},1); } } else { my $r = doSetUpdate($hash,$mode,$device,$reading,$message); @@ -2955,7 +2987,7 @@ sub onmessage($$$) { #} } return \@updatedList if($updated); - return undef; + return; } 1; __END__