2
0
mirror of https://github.com/fhem/fhem-mirror.git synced 2025-04-22 08:11:44 +00:00

change: global defaults, resend on connect, div. fixes

git-svn-id: https://svn.fhem.de/fhem/trunk@17387 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
hexenmeister 2018-09-22 21:24:52 +00:00
parent 12a9ca4d1d
commit 0732623b54

View File

@ -31,11 +31,38 @@
# CHANGE LOG # CHANGE LOG
# #
# #
# 22.09.2018 0.9.8
# improved : Topics-Ausgabe (devinfo, subscribe) alphabetisch sortiert
# improved : Trennung der zu ueberwachenden Geraete ist jetzt mit Kommas,
# Leezeichen, Kommas mit Leerzeichen
# oder einer Mischung davon moeglich
# feature : Unterstuetzung (fast) beliebiger variablen
# in defaults / global defaults (subscribe).
# Verwendung wie bei $base.
# z.B. hugo wird zum $hugo in Topics/Expression
# Variablen in Dev koennen Variablen aus global verwenden
# feature : Neue Einstellung: resendOnConnect fuer mgttPublish
# Bewirkt, dass wenn keine mqtt-Connection besteht,
# die zu sendende Nachrichten in einer Warteschlange gespeichet
# werden. Wird die Verbindung aufgebaut,
# werden die Nachrichten verschickt.
# Moegliche Optionen: none - alle verwerfen,
# last - immer nur die letzte Nachricht speichern,
# first - immer nur die erste Nachricht speichern,
# danach folgende verwerfen, all - alle speichern,
# allerdings existiert eine Obergrenze von 100, wird es mehr,
# werden aelteste ueberzaelige Nachrichten verworfen.
# (Beispiel: *:resendOnConnect=last)
#
# 21.09.2019 0.9.7
# fix : Anlegen / Loeschen von userAttr
# change : Vorbereitungen fuer resendOnConnect
#
# 16.09.2018 0.9.7 # 16.09.2018 0.9.7
# fix : defaults in globalPublish nicht verfuegbar # fix : defaults in globalPublish nicht verfuegbar
# fix : atopic in devinfo korrekt anzeigen # fix : atopic in devinfo korrekt anzeigen
# improved : Anzeige devinfo # improved : Anzeige devinfo
# change : Umstellung auf delFromDevAttrList (zum ENtfernen von UserArrt) # change : Umstellung auf delFromDevAttrList (zum Entfernen von UserArrt)
# change : Methoden-Doku # change : Methoden-Doku
# #
# 15.09.2018 0.9.7 # 15.09.2018 0.9.7
@ -46,10 +73,11 @@
# muss nicht mehr zwingend topic definiert werden # muss nicht mehr zwingend topic definiert werden
# (dafuer muss dann ggf. Expression sorgen) # (dafuer muss dann ggf. Expression sorgen)
# improved : Formatierung Ausgabe devinfo # improved : Formatierung Ausgabe devinfo
# feature : Unterstuetzung beliegiger (fast) variable # feature : Unterstuetzung (fast) beliebiger variablen
# in defaults / global defaults. # in defaults / global defaults (publish).
# Verwendung wie auch bei $base. # Verwendung wie bei $base.
# z.B. hugo wird zum $hugo in Topics/Expression # z.B. hugo wird zum $hugo in Topics/Expression
# Variablen in Dev koennen Variablen aus global verwenden
# minor fixes # minor fixes
# #
# xx.08.2018 0.9.6 # xx.08.2018 0.9.6
@ -70,10 +98,10 @@
# Ideen: # Ideen:
# #
# [i like it] # [i like it]
# - global base (sollte in $base verwendet werden koennen) # done - global base (sollte in $base verwendet werden koennen)
# - resend / interval, Warteschlange # - resend / interval, Warteschlange
# - resendOnConnect (no, first, last, all) # done - resendOnConnect (no, first, last, all)
# - resendInterval (no/0, x min) # -- autoResend Interval (no/0, x min)
# #
# [maybe] # [maybe]
# - QOS for subscribe (fertig?), defaults(qos, fertig?), alias mapping # - QOS for subscribe (fertig?), defaults(qos, fertig?), alias mapping
@ -81,10 +109,12 @@
# - global excludes # - global excludes
# - Support for MQTT2_SERVER # - Support for MQTT2_SERVER
# - commands per mqtt fuer die Bridge: Liste der Geraete, Infos dazu etc. # - commands per mqtt fuer die Bridge: Liste der Geraete, Infos dazu etc.
# - mqttOptions (zuschaltbare optionen im Form eines Perl-Routine) (json, ttl)
# #
# [I don't like it] # [I don't like it]
# - templates (e.g. 'define template' in der Bridge, 'mqttUseTemplate' in Devices) # - templates (e.g. 'define template' in der Bridge, 'mqttUseTemplate' in Devices)
# - autocreate # - autocreate
# - a reading for last send / receive mesages
# #
# [just no] # [just no]
# #
@ -113,7 +143,7 @@ use warnings;
#my $DEBUG = 1; #my $DEBUG = 1;
my $cvsid = '$Id$'; my $cvsid = '$Id$';
my $VERSION = "version 0.9.7 by hexenmeister\n$cvsid"; my $VERSION = "version 0.9.8 by hexenmeister\n$cvsid";
my %sets = ( my %sets = (
); );
@ -199,7 +229,6 @@ BEGIN {
MQTT->import(qw(:all)); MQTT->import(qw(:all));
GP_Import(qw( GP_Import(qw(
AttrVal
CommandAttr CommandAttr
readingsSingleUpdate readingsSingleUpdate
readingsBeginUpdate readingsBeginUpdate
@ -234,7 +263,7 @@ BEGIN {
}; };
use constant { use constant {
HELPER => ".helper", HELPER => ".helper",
IO_DEV_TYPE => "IO_DEV_TYPE", IO_DEV_TYPE => "IO_DEV_TYPE",
HS_TAB_NAME_DEVICES => "devices", HS_TAB_NAME_DEVICES => "devices",
@ -252,6 +281,9 @@ use constant {
HS_PROP_NAME_PREFIX => "prefix", HS_PROP_NAME_PREFIX => "prefix",
HS_PROP_NAME_DEVSPEC => "devspec", HS_PROP_NAME_DEVSPEC => "devspec",
HS_PROP_NAME_PUB_OFFLINE_QUEUE => ".pub_queue",
HS_PROP_NAME_PUB_OFFLINE_QUEUE_MAX_CNT_PROTOPIC => ".pub_queue_max_cnt_pro_topic",
HS_PROP_NAME_GLOBAL_EXCLUDES_TYPE => "globalTypeExcludes", HS_PROP_NAME_GLOBAL_EXCLUDES_TYPE => "globalTypeExcludes",
HS_PROP_NAME_GLOBAL_EXCLUDES_READING => "globalReadingExcludes", HS_PROP_NAME_GLOBAL_EXCLUDES_READING => "globalReadingExcludes",
HS_PROP_NAME_GLOBAL_EXCLUDES_DEVICES => "globalDeviceExcludes", HS_PROP_NAME_GLOBAL_EXCLUDES_DEVICES => "globalDeviceExcludes",
@ -306,8 +338,15 @@ sub startsWith($$) {
# Device define # Device define
sub Define() { sub Define() {
my ($hash, $def) = @_; my ($hash, $def) = @_;
# Definition :=> defmod mqttGeneric MQTT_GENERIC_BRIDGE [prefix] [devspec] # Definition :=> defmod mqttGeneric MQTT_GENERIC_BRIDGE [prefix] [devspec,[devspec]]
my($name, $type, $prefix, $devspec) = split("[ \t][ \t]*", $def); 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;
# damit ist jetzt Trennung der zu ueberwachenden Geraete mit Kommas, Leezeichen, Kommas mit Leerzeichen und Mischung davon moeglich
removeOldUserAttr($hash) if defined $hash->{+HS_PROP_NAME_PREFIX};
$prefix="mqtt" unless defined $prefix; # default prefix is 'mqtt' $prefix="mqtt" unless defined $prefix; # default prefix is 'mqtt'
@ -315,6 +354,8 @@ sub Define() {
$hash->{+HELPER}->{+HS_PROP_NAME_PREFIX_OLD}=$hash->{+HS_PROP_NAME_PREFIX}; $hash->{+HELPER}->{+HS_PROP_NAME_PREFIX_OLD}=$hash->{+HS_PROP_NAME_PREFIX};
$hash->{+HS_PROP_NAME_PREFIX}=$prefix; # store in device hash $hash->{+HS_PROP_NAME_PREFIX}=$prefix; # store in device hash
# wenn Leer oder nicht definiert => devspec auf '.*' setzen
$devspec = undef if $devspec eq '';
$hash->{+HS_PROP_NAME_DEVSPEC} = defined($devspec)?$devspec:".*"; $hash->{+HS_PROP_NAME_DEVSPEC} = defined($devspec)?$devspec:".*";
Log3($hash->{NAME},5,"MQTT-GB:DEBUG:> [$hash->{NAME}] Define: params: $name, $type, $hash->{+HS_PROP_NAME_PREFIX}, $hash->{+HS_PROP_NAME_DEVSPEC}"); Log3($hash->{NAME},5,"MQTT-GB:DEBUG:> [$hash->{NAME}] Define: params: $name, $type, $hash->{+HS_PROP_NAME_PREFIX}, $hash->{+HS_PROP_NAME_DEVSPEC}");
@ -333,6 +374,9 @@ sub Define() {
#TODO: aktivieren, wenn gebraucht wird #TODO: aktivieren, wenn gebraucht wird
$hash->{+HELPER}->{+HS_PROP_NAME_INTERVAL} = 60; # Sekunden $hash->{+HELPER}->{+HS_PROP_NAME_INTERVAL} = 60; # Sekunden
# Max messages count pro topic for offline queue
$hash->{+HS_PROP_NAME_PUB_OFFLINE_QUEUE_MAX_CNT_PROTOPIC} = 100;
readingsBeginUpdate($hash); readingsBeginUpdate($hash);
readingsBulkUpdate($hash,"incoming-count",$hash->{+HELPER}->{+HS_PROP_NAME_INCOMING_CNT}); readingsBulkUpdate($hash,"incoming-count",$hash->{+HELPER}->{+HS_PROP_NAME_INCOMING_CNT});
readingsBulkUpdate($hash,"outgoing-count",$hash->{+HELPER}->{+HS_PROP_NAME_OUTGOING_CNT}); readingsBulkUpdate($hash,"outgoing-count",$hash->{+HELPER}->{+HS_PROP_NAME_OUTGOING_CNT});
@ -405,6 +449,7 @@ sub initUserAttr($) {
} }
my @devices = devspec2array($devspec); my @devices = devspec2array($devspec);
Log3($hash->{NAME},5,"MQTT-GB:DEBUG:> [$hash->{NAME}] initUserAttr: addToDevAttrList: $prefix"); Log3($hash->{NAME},5,"MQTT-GB:DEBUG:> [$hash->{NAME}] initUserAttr: addToDevAttrList: $prefix");
#Log3('xxx',1,"MQTT-GB:DEBUG:> initUserAttr> devspec: '$devspec', array: ".Dumper(@devices));
foreach my $dev (@devices) { foreach my $dev (@devices) {
addToDevAttrList($dev, $prefix.CTRL_ATTR_NAME_DEFAULTS.":textField-long"); addToDevAttrList($dev, $prefix.CTRL_ATTR_NAME_DEFAULTS.":textField-long");
addToDevAttrList($dev, $prefix.CTRL_ATTR_NAME_ALIAS.":textField-long"); addToDevAttrList($dev, $prefix.CTRL_ATTR_NAME_ALIAS.":textField-long");
@ -492,7 +537,7 @@ sub updateDevCount($) {
sub removeOldUserAttr($;$$) { sub removeOldUserAttr($;$$) {
my ($hash, $prefix, $devspec) = @_; my ($hash, $prefix, $devspec) = @_;
$prefix = $hash->{+HS_PROP_NAME_PREFIX} unless defined $prefix; $prefix = $hash->{+HS_PROP_NAME_PREFIX} unless defined $prefix;
# Pruefen, on ein weiteres Device (MQTT_GENERIC_BRIDGE) mit dem selben Prefic existiert (waere zwar Quatsch, aber dennoch) # 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 @bridges = devspec2array("TYPE=MQTT_GENERIC_BRIDGE");
my $name = $hash->{NAME}; my $name = $hash->{NAME};
foreach my $dev (@bridges) { foreach my $dev (@bridges) {
@ -505,13 +550,16 @@ sub removeOldUserAttr($;$$) {
$devspec = 'global' if ($devspec eq '.*'); $devspec = 'global' if ($devspec eq '.*');
# kann spaeter auch delFromDevAttrList Methode genutzt werden # kann spaeter auch delFromDevAttrList Methode genutzt werden
my @devices = devspec2array($devspec); my @devices = devspec2array($devspec);
#Log3('xxx',1,"MQTT-GB:DEBUG:> removeOldUserAttr> devspec: $devspec, array: ".Dumper(@devices));
foreach my $dev (@devices) { foreach my $dev (@devices) {
# neue Methode im fhem.pl # neue Methode im fhem.pl
delFromDevAttrList($dev,$prefix.CTRL_ATTR_NAME_DEFAULTS); #Log3('xxx',1,"MQTT-GB:DEBUG:> removeOldUserAttr> delete: from $dev ".$prefix.CTRL_ATTR_NAME_DEFAULTS);
delFromDevAttrList($dev,$prefix.CTRL_ATTR_NAME_ALIAS); delFromDevAttrList($dev,$prefix.CTRL_ATTR_NAME_DEFAULTS.":textField-long");
delFromDevAttrList($dev,$prefix.CTRL_ATTR_NAME_PUBLISH); delFromDevAttrList($dev,$prefix.CTRL_ATTR_NAME_ALIAS.":textField-long");
delFromDevAttrList($dev,$prefix.CTRL_ATTR_NAME_SUBSCRIBE); delFromDevAttrList($dev,$prefix.CTRL_ATTR_NAME_PUBLISH.":textField-long");
delFromDevAttrList($dev,$prefix.CTRL_ATTR_NAME_IGNORE); delFromDevAttrList($dev,$prefix.CTRL_ATTR_NAME_SUBSCRIBE.":textField-long");
delFromDevAttrList($dev,$prefix.CTRL_ATTR_NAME_IGNORE.":both,incoming,outgoing");
# my $ua = $main::attr{$dev}{userattr}; # my $ua = $main::attr{$dev}{userattr};
# if (defined $ua) { # if (defined $ua) {
# my %h = map { ($_ => 1) } split(" ", "$ua"); # my %h = map { ($_ => 1) } split(" ", "$ua");
@ -656,7 +704,8 @@ sub CreateSingleDeviceTableAttrPublish($$$$) {
if(($ident eq 'topic') or ($ident eq 'readings-topic') or if(($ident eq 'topic') or ($ident eq 'readings-topic') or
($ident eq 'atopic') or ($ident eq 'attr-topic') or ($ident eq 'atopic') or ($ident eq 'attr-topic') or
($ident eq 'qos') or ($ident eq 'retain') or ($ident eq 'qos') or ($ident eq 'retain') or
($ident eq 'expression')) { ($ident eq 'expression') or
($ident eq 'resendOnConnect')) {
my @nameParts = split(/\|/, $name); my @nameParts = split(/\|/, $name);
while (@nameParts) { while (@nameParts) {
my $namePart = shift(@nameParts); my $namePart = shift(@nameParts);
@ -737,6 +786,15 @@ sub getDevicePublishRecIntern($$$$$) {
# wenn kein topic und keine expression definiert sind, kann auch nicht gesendet werden, es muss nichts mehr ausgewertet werden # 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 unless (defined($topic) or defined($atopic) or defined( $expression));
# resendOnConnect Option
my $resendOnConnect = undef;
$resendOnConnect = $readingMap->{'resendOnConnect'} if defined $readingMap;
$resendOnConnect = $defaultReadingMap->{'resendOnConnect'} if (defined($defaultReadingMap) and !defined($resendOnConnect));
# global
$resendOnConnect = $globalReadingMap->{'resendOnConnect'} if (defined($globalReadingMap) and !defined($resendOnConnect));
$resendOnConnect = $globalDefaultReadingMap->{'resendOnConnect'} if (defined($globalDefaultReadingMap) and !defined($resendOnConnect));
# map name
my $name = undef; my $name = undef;
if (defined($devMap) and defined($devMap->{':alias'})) { if (defined($devMap) and defined($devMap->{':alias'})) {
$name = $devMap->{':alias'}->{'pub:'.$reading}; $name = $devMap->{':alias'}->{'pub:'.$reading};
@ -746,8 +804,10 @@ sub getDevicePublishRecIntern($$$$$) {
} }
$name = $reading unless defined $name; $name = $reading unless defined $name;
# get mode
my $mode = $readingMap->{'mode'}; my $mode = $readingMap->{'mode'};
# compute defaults
my $combined = computeDefaults($hash, 'pub:', $globalMap, $devMap, {'device'=>$dev,'reading'=>$reading,'name'=>$name,'mode'=>$mode}); my $combined = computeDefaults($hash, 'pub:', $globalMap, $devMap, {'device'=>$dev,'reading'=>$reading,'name'=>$name,'mode'=>$mode});
# $topic evaluieren (avialable vars: $device (device name), $reading (oringinal name), $name ($reading oder alias, if defined), defaults) # $topic evaluieren (avialable vars: $device (device name), $reading (oringinal name), $name ($reading oder alias, if defined), defaults)
if(defined($topic) and ($topic =~ m/^{.*}$/)) { if(defined($topic) and ($topic =~ m/^{.*}$/)) {
@ -757,7 +817,9 @@ sub getDevicePublishRecIntern($$$$$) {
$atopic = _evalValue2($hash->{NAME},$atopic,{'topic'=>$atopic,'device'=>$dev,'reading'=>$reading,'name'=>$name,%$combined}) if defined $atopic; $atopic = _evalValue2($hash->{NAME},$atopic,{'topic'=>$atopic,'device'=>$dev,'reading'=>$reading,'name'=>$name,%$combined}) if defined $atopic;
} }
return {'topic'=>$topic,'atopic'=>$atopic,'qos'=>$qos,'retain'=>$retain,'expression'=>$expression,'name'=>$name,'mode'=>$mode,'.defaultMap'=>$combined}; return {'topic'=>$topic,'atopic'=>$atopic,'qos'=>$qos,'retain'=>$retain,
'expression'=>$expression,'name'=>$name,'mode'=>$mode,
'resendOnConnect'=>$resendOnConnect,'.defaultMap'=>$combined};
} }
# sucht Qos, Retain, Expression Werte unter Beruecksichtigung von Defaults und Globals # sucht Qos, Retain, Expression Werte unter Beruecksichtigung von Defaults und Globals
@ -888,23 +950,23 @@ sub _evalValue2($$;$$) {
# Alte Methode, verwendet noch fixe Variable (base, dev, reading, name), kein Map # Alte Methode, verwendet noch fixe Variable (base, dev, reading, name), kein Map
# soll durch _evalValue2 ersetzt werden # soll durch _evalValue2 ersetzt werden
sub _evalValue($$;$$$$) { # sub _evalValue($$;$$$$) {
my($mod, $str, $base, $device, $reading, $name) = @_; # my($mod, $str, $base, $device, $reading, $name) = @_;
#Log3('xxx',1,"MQTT-GB:DEBUG:> eval: (str, base, dev, reading, name) $str, $base, $device, $reading, $name"); # #Log3('xxx',1,"MQTT-GB:DEBUG:> eval: (str, base, dev, reading, name) $str, $base, $device, $reading, $name");
my$ret = $str; # my$ret = $str;
#$base="" unless defined $base; # #$base="" unless defined $base;
if($str =~ m/^{.*}$/) { # if($str =~ m/^{.*}$/) {
no strict "refs"; # no strict "refs";
local $@; # local $@;
#Log3('xxx',1,"MQTT-GB:DEBUG:> eval !!!"); # #Log3('xxx',1,"MQTT-GB:DEBUG:> eval !!!");
$ret = eval($str); # $ret = eval($str);
#Log3('xxx',1,"MQTT-GB:DEBUG:> eval done: $ret"); # #Log3('xxx',1,"MQTT-GB:DEBUG:> eval done: $ret");
if ($@) { # if ($@) {
Log3($mod,2,"user value ('".$str."'') eval error: ".$@); # Log3($mod,2,"user value ('".$str."'') eval error: ".$@);
} # }
} # }
return $ret; # return $ret;
} # }
# sucht zu dem gegebenen (ankommenden) topic das entsprechende device und reading # sucht zu dem gegebenen (ankommenden) topic das entsprechende device und reading
# Params: $hash, $topic (empfangene topic) # Params: $hash, $topic (empfangene topic)
@ -1016,25 +1078,40 @@ sub CreateSingleDeviceTableAttrSubscribe($$$$) {
if(($ident eq 'topic') or ($ident eq 'readings-topic') or if(($ident eq 'topic') or ($ident eq 'readings-topic') or
($ident eq 'stopic') or ($ident eq 'set-topic') or ($ident eq 'stopic') or ($ident eq 'set-topic') or
($ident eq 'atopic') or ($ident eq 'attr-topic')) { # -> topic ($ident eq 'atopic') or ($ident eq 'attr-topic')) { # -> topic
my $base=undef;
if (defined($devMap->{':defaults'})) {
$base = $devMap->{':defaults'}->{'sub:base'};
}
if (defined($globalMap) and defined($globalMap->{':defaults'}) and !defined($base)) {
$base = $globalMap->{':defaults'}->{'sub:base'};
}
$base='' unless defined $base;
$base = _evalValue($hash->{NAME},$base,$base,$dev,'$reading','$name');
$rmap->{'mode'} = 'R'; $rmap->{'mode'} = 'R';
$rmap->{'mode'} = 'S' if (($ident eq 'stopic') or ($ident eq 'set-topic')); $rmap->{'mode'} = 'S' if (($ident eq 'stopic') or ($ident eq 'set-topic'));
$rmap->{'mode'} = 'A' if (($ident eq 'atopic') or ($ident eq 'attr-topic')); $rmap->{'mode'} = 'A' if (($ident eq 'atopic') or ($ident eq 'attr-topic'));
# $base verwenden => eval # my $base=undef;
my $topic = _evalValue($hash->{NAME},$val,$base,$dev,'$reading','$name'); # if (defined($devMap->{':defaults'})) {
$rmap->{'topicOrig'} = $val; # $base = $devMap->{':defaults'}->{'sub:base'};
# }
# if (defined($globalMap) and defined($globalMap->{':defaults'}) and !defined($base)) {
# $base = $globalMap->{':defaults'}->{'sub:base'};
# }
# $base='' unless defined $base;
# $base = _evalValue($hash->{NAME},$base,$base,$dev,'$reading','$name');
#Log3($hash->{NAME},1,"MQTT-GB:DEBUG:> sub: old base: $base");
# $base verwenden => eval
#my $topic = _evalValue($hash->{NAME},$val,$base,$dev,'$reading','$name');
my $combined = computeDefaults($hash, 'sub:', $globalMap, $devMap, {'device'=>$dev,'reading'=>'#reading','name'=>'#name','mode'=>$rmap->{'mode'}});
#Log3($hash->{NAME},1,"MQTT-GB:DEBUG:> sub: Defaults: ".Dumper($combined));
my $topic = _evalValue2($hash->{NAME},$val,{'device'=>$dev,'reading'=>'#reading','name'=>'#name',%$combined}) if defined $val;
my $old = '#reading';
my $new = '$reading';
$topic =~ s/\Q$old\E/$new/g;
$old = '#name';
$new = '$name';
$topic =~ s/\Q$old\E/$new/g;
#Log3($hash->{NAME},1,"MQTT-GB:DEBUG:> sub: Topic old: $topic");
#Log3($hash->{NAME},1,"MQTT-GB:DEBUG:> sub: Topic new: $topic");
$rmap->{'topicOrig'} = $val;
$rmap->{'topicExp'}=createRegexpForTopic($topic); $rmap->{'topicExp'}=createRegexpForTopic($topic);
$topic =~ s/\$reading/+/g; $topic =~ s/\$reading/+/g;
@ -1389,42 +1466,25 @@ sub Get($$$@) {
} }
} }
$res.=" subscribe:\n"; $res.=" subscribe:\n";
my @resa;
foreach my $subRec (@{$hash->{+HS_TAB_NAME_DEVICES}->{$dname}->{':subscribe'}}) { foreach my $subRec (@{$hash->{+HS_TAB_NAME_DEVICES}->{$dname}->{':subscribe'}}) {
my $qos = $subRec->{'qos'}; my $qos = $subRec->{'qos'};
my $mode = $subRec->{'mode'}; my $mode = $subRec->{'mode'};
my $expression = $subRec->{'expression'}; my $expression = $subRec->{'expression'};
my $topic = $subRec->{'topic'}; my $topic = $subRec->{'topic'};
$topic = '---' unless defined $topic; $topic = '---' unless defined $topic;
$res.= sprintf(' %-16s <= %s', $subRec->{'reading'}, $topic); my $rest.= sprintf(' %-16s <= %s', $subRec->{'reading'}, $topic);
$res.= " (mode: $mode"; $rest.= " (mode: $mode";
$res.= "; qos: $qos" if defined ($qos); $rest.= "; qos: $qos" if defined ($qos);
$res.= ")\n"; $rest.= ")\n";
$res.= " exp: $expression\n" if defined ($expression); $rest.= " exp: $expression\n" if defined ($expression);
# TODO push (@resa, $rest);
# my $qos = $subRec->{'qos'};
# my $mode = $subRec->{'mode'};
# my $expression = $subRec->{'expression'};
# my $topic = "---";
# if($mode eq 'R') {
# $topic = $subRec->{'topic'};
# } elsif($mode eq 'S') {
# $topic = $subRec->{'stopic'};
# } elsif($mode eq 'A') {
# $topic = $subRec->{'atopic'};
# } else {
# $topic = '!unexpected mode!';
# }
# $topic = '---' unless defined $topic;
# $res.= sprintf(' %-16s <= %s', $subRec->{'reading'}, $topic);
# $res.= " (mode: $mode";
# $res.= "; qos: $qos" if defined ($qos);
# $res.= ")\n";
# $res.= " exp: $expression\n" if defined ($expression);
# # TODO
} }
$res.=join('', sort @resa);
} }
$res.= "\n"; $res.= "\n";
} }
# TODO : Weitere Dev Infos?
$res = "no devices found" unless ($res ne ""); $res = "no devices found" unless ($res ne "");
return $res; return $res;
#last; #last;
@ -1472,7 +1532,7 @@ sub Notify() {
RefreshDeviceTable($hash, $sdev, $attrName, $val); RefreshDeviceTable($hash, $sdev, $attrName, $val);
} else { } else {
#Log3($hash->{NAME},1,"MQTT-GB:DEBUG:> attr created/changed: non observed attr"); #Log3($hash->{NAME},1,"MQTT-GB:DEBUG:> attr created/changed: non observed attr");
# TODO: check/ publish atopic => val # check/ publish atopic => val
publishDeviceUpdate($hash, $defs{$sdev}, 'A', $attrName, $val); publishDeviceUpdate($hash, $defs{$sdev}, 'A', $attrName, $val);
} }
} elsif($s =~ m/^DELETEATTR ([^ ]*) ([^ ]*)$/) { } elsif($s =~ m/^DELETEATTR ([^ ]*) ([^ ]*)$/) {
@ -1483,7 +1543,7 @@ sub Notify() {
if(IsObservedAttribute($hash,$attrName)) { if(IsObservedAttribute($hash,$attrName)) {
RefreshDeviceTable($hash, $sdev, $attrName, undef); RefreshDeviceTable($hash, $sdev, $attrName, undef);
} else { } else {
# TODO: check/ publish atopic => null # check/ publish atopic => null
publishDeviceUpdate($hash, $defs{$sdev}, 'A', $attrName, undef); publishDeviceUpdate($hash, $defs{$sdev}, 'A', $attrName, undef);
} }
} }
@ -1638,8 +1698,54 @@ sub isTypeDevReadingExcluded($$$$) {
# MQTT-Nachricht senden # MQTT-Nachricht senden
# Params: Bridge-Hash, Topic, Nachricht, QOS- und Retain-Flags # Params: Bridge-Hash, Topic, Nachricht, QOS- und Retain-Flags
sub doPublish($$$$$) { sub doPublish($$$$$$) {
my ($hash,$topic,$message,$qos,$retain) = @_; my ($hash,$topic,$message,$qos,$retain,$resendOnConnect) = @_;
#Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE doPublish: topic: $topic, msg: $message, resend mode: ".(defined($resendOnConnect)?$resendOnConnect:"no"));
if(!isConnected($hash)) {
# store message?
if(defined($resendOnConnect)) {
$resendOnConnect = lc($resendOnConnect);
Log3($hash->{NAME},5,"MQTT_GENERIC_BRIDGE offline publish: store: topic: $topic, msg: $message, mode: $resendOnConnect");
if($resendOnConnect eq 'first' or $resendOnConnect eq 'last' or $resendOnConnect eq 'all') {
# store msg data
my $queue = $hash->{+HELPER}->{+HS_PROP_NAME_PUB_OFFLINE_QUEUE};
#my $queue = $hash->{+HS_PROP_NAME_PUB_OFFLINE_QUEUE};
$queue = {} unless defined $queue;
my $entry = {'topic'=>$topic, 'message'=>$message, 'qos'=>$qos, 'retain'=>$retain, 'resendOnConnect'=>$resendOnConnect};
my $topicQueue = $queue->{$topic};
unless (defined($topicQueue)) {
$topicQueue = [$entry];
}
else {
if ($resendOnConnect eq 'first') {
if (scalar @$topicQueue == 0) {
$topicQueue = [$entry];
}
} elsif($resendOnConnect eq 'last') {
$topicQueue = [$entry];
} else { # all
push (@$topicQueue, $entry);
}
}
# check max lng
my $max = $hash->{+HS_PROP_NAME_PUB_OFFLINE_QUEUE_MAX_CNT_PROTOPIC};
$max = 10 unless defined $max;
while (scalar @$topicQueue > $max) {
shift @$topicQueue;
}
$queue->{$topic} = $topicQueue;
#Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE offline publish: stored: ".Dumper($queue));
$hash->{+HELPER}->{+HS_PROP_NAME_PUB_OFFLINE_QUEUE} = $queue;
}
}
return;
}
Log3($hash->{NAME},5,"MQTT_GENERIC_BRIDGE publish: $topic => $message (qos: $qos, retain: ".(defined($retain)?$retain:'0').")");
if (isIODevMQTT2($hash)){ #if ($hash->{+HELPER}->{+IO_DEV_TYPE} eq 'MQTT2_SERVER') { if (isIODevMQTT2($hash)){ #if ($hash->{+HELPER}->{+IO_DEV_TYPE} eq 'MQTT2_SERVER') {
# TODO: publish MQTT2 # TODO: publish MQTT2
@ -1695,6 +1801,7 @@ sub publishDeviceUpdate($$$$$) {
my $retain = $pubRec->{'retain'}; my $retain = $pubRec->{'retain'};
my $expression = $pubRec->{'expression'}; my $expression = $pubRec->{'expression'};
my $base = $pubRec->{'base'}; my $base = $pubRec->{'base'};
my $resendOnConnect = $pubRec->{'resendOnConnect'};
$base='' unless defined $base; $base='' unless defined $base;
$value="\0" unless defined $value; $value="\0" unless defined $value;
@ -1732,10 +1839,10 @@ sub publishDeviceUpdate($$$$$) {
if(defined($redefMap)) { if(defined($redefMap)) {
foreach my $key (keys %{$redefMap}) { foreach my $key (keys %{$redefMap}) {
my $val = $redefMap->{$key}; my $val = $redefMap->{$key};
doPublish($hash,$key,$val,$qos,$retain); doPublish($hash,$key,$val,$qos,$retain,$resendOnConnect);
} }
} else { } else {
doPublish($hash,$topic,$message,$qos,$retain) if defined $topic; doPublish($hash,$topic,$message,$qos,$retain,$resendOnConnect) if defined $topic;
} }
} }
@ -1846,7 +1953,22 @@ sub ioDevConnect($) {
#Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE DEBUG: ioDevConnect"); #Log3($hash->{NAME},1,"MQTT_GENERIC_BRIDGE DEBUG: ioDevConnect");
# TODO # resend stored msgs => doPublish (...., undef)
my $queue = $hash->{+HELPER}->{+HS_PROP_NAME_PUB_OFFLINE_QUEUE};
if (defined($queue)) {
foreach my $topic (keys %{$queue}) {
my $topicQueue = $queue->{$topic};
my $topicRec = undef;
while ($topicRec = shift(@$topicQueue)) {
my $message = $topicRec->{'message'};
my $qos = $topicRec->{'qos'};
my $retain = $topicRec->{'retain'};
my $resendOnConnect = undef; #$topicRec->{'resendOnConnect'};
doPublish($hash,$topic,$message,$qos,$retain,$resendOnConnect);
}
}
}
} }
# CallBack-Handler fuer IODev beim Disconnect # CallBack-Handler fuer IODev beim Disconnect
@ -1964,7 +2086,7 @@ sub onmessage($$$) {
<h3>MQTT_GENERIC_BRIDGE</h3> <h3>MQTT_GENERIC_BRIDGE</h3>
<ul> <ul>
<p> <p>
This module is an MQTT bridge, which simultaneously collects data from several FHEM devices This module is a MQTT bridge, which simultaneously collects data from several FHEM devices
and passes their readings via MQTT or set readings from the incoming MQTT messages or executes them and passes their readings via MQTT or set readings from the incoming MQTT messages or executes them
as a 'set' command on the configured FHEM device. as a 'set' command on the configured FHEM device.
<br/>An <a href="#MQTT">MQTT</a> device is needed as IODev. <br/>An <a href="#MQTT">MQTT</a> device is needed as IODev.
@ -1974,7 +2096,7 @@ sub onmessage($$$) {
<p><b>Definition:</b></p> <p><b>Definition:</b></p>
<ul> <ul>
<p>In the simplest case, two lines are enough:</p> <p>In the simplest case, two lines are enough:</p>
<p><code>defmod mqttGeneric MQTT_GENERIC_BRIDGE [prefix] [devspec]</br> <p><code>defmod mqttGeneric MQTT_GENERIC_BRIDGE [prefix] [devspec,[devspec]</br>
attr mqttGeneric IODev <MQTT-Device></code></p> attr mqttGeneric IODev <MQTT-Device></code></p>
<p>All parameters in the define are optional.</p> <p>All parameters in the define are optional.</p>
<p>The first parameter is a prefix for the control attributes on which the devices to be <p>The first parameter is a prefix for the control attributes on which the devices to be
@ -1983,6 +2105,7 @@ sub onmessage($$$) {
</p> </p>
<p>The second parameter ('devspec') allows to minimize the number of devices to be monitored <p>The second parameter ('devspec') allows to minimize the number of devices to be monitored
(otherwise all devices will be monitored, which may cost performance). (otherwise all devices will be monitored, which may cost performance).
Example for devspec: 'TYPE=dummy' or 'dummy1,dummy2'. With comma separated list no spaces must be used!
see <a href="https://fhem.de/commandref_DE.html#devspec">devspec</a></p> see <a href="https://fhem.de/commandref_DE.html#devspec">devspec</a></p>
</ul> </ul>
@ -2159,13 +2282,27 @@ sub onmessage($$$) {
The return value is used as a new message value, the changed variables have priority.<br/> The return value is used as a new message value, the changed variables have priority.<br/>
If the return value is undef, setting / execution is suppressed. <br/> If the return value is undef, setting / execution is suppressed. <br/>
If the return is a hash (topic only), its key values are used as the topic, and the contents of the messages are the values from the hash.</p> If the return is a hash (topic only), its key values are used as the topic, and the contents of the messages are the values from the hash.</p>
<p>Option 'resendOnConnect' allows to save the messages,
if the bridge is not connected to the MQTT server.
The messages to be sent are stored in a queue.
When the connection is established, the messages are sent in the original order.
<ul>Possible values:
<li> none <br/> discard all </li>
<li> last <br/> save only the last message </li>
<li> first <br/> save only the first message
then discard the following</li>
<li>all<br/>save all, but if there is an upper limit of 100, if it is more, the most supernatural messages are discarded. </li>
</ul>
<p>Examples:<br/> <p>Examples:<br/>
<code> attr &lt;dev&gt; mqttPublish temperature:topic={"$base/$name"} temperature:qos=1 temperature:retain=0 *:topic={"$base/$name"} humidity:topic=/TEST/Feuchte<br/> <code> attr &lt;dev&gt; mqttPublish temperature:topic={"$base/$name"} temperature:qos=1 temperature:retain=0 *:topic={"$base/$name"} humidity:topic=/TEST/Feuchte<br/>
attr &lt;dev&gt; mqttPublish temperature|humidity:topic={"$base/$name"} temperature|humidity:qos=1 temperature|humidity:retain=0<br/> attr &lt;dev&gt; mqttPublish temperature|humidity:topic={"$base/$name"} temperature|humidity:qos=1 temperature|humidity:retain=0<br/>
attr &lt;dev&gt; mqttPublish *:topic={"$base/$name"} *:qos=2 *:retain=0<br/> attr &lt;dev&gt; mqttPublish *:topic={"$base/$name"} *:qos=2 *:retain=0<br/>
attr &lt;dev&gt; mqttPublish *:topic={"$base/$name"} reading:expression={"message: $value"}<br/> attr &lt;dev&gt; mqttPublish *:topic={"$base/$name"} reading:expression={"message: $value"}<br/>
attr &lt;dev&gt; mqttPublish *:topic={"$base/$name"} reading:expression={$value="message: $value"}<br/> attr &lt;dev&gt; mqttPublish *:topic={"$base/$name"} reading:expression={$value="message: $value"}<br/>
attr &lt;dev&gt; mqttPublish *:topic={"$base/$name"} reading:expression={"/TEST/Topic1"=>"$message", "/TEST/Topic2"=>"message: $message"}</code></p> attr &lt;dev&gt; mqttPublish *:topic={"$base/$name"} reading:expression={"/TEST/Topic1"=>"$message", "/TEST/Topic2"=>"message: $message"}<br/>
attr &lt;dev&gt; mqttPublish *:resendOnConnect=last
</code></p>
</p> </p>
</li> </li>
@ -2328,7 +2465,7 @@ sub onmessage($$$) {
<p><b>Definition:</b></p> <p><b>Definition:</b></p>
<ul> <ul>
<p>Im einfachsten Fall reichen schon zwei Zeilen:</p> <p>Im einfachsten Fall reichen schon zwei Zeilen:</p>
<p><code>defmod mqttGeneric MQTT_GENERIC_BRIDGE [prefix] [devspec]</br> <p><code>defmod mqttGeneric MQTT_GENERIC_BRIDGE [prefix] [devspec,[devspec]]</br>
attr mqttGeneric IODev <MQTT-Device></code></p> attr mqttGeneric IODev <MQTT-Device></code></p>
<p>Alle Parameter im Define sind optional.</p> <p>Alle Parameter im Define sind optional.</p>
<p>Der erste ist ein Prefix fuer die Steuerattribute, worueber die zu ueberwachende Geraete (s.u.) <p>Der erste ist ein Prefix fuer die Steuerattribute, worueber die zu ueberwachende Geraete (s.u.)
@ -2336,6 +2473,7 @@ sub onmessage($$$) {
Wird dieser z.B. als 'hugo' redefiniert, heissen die Steuerungsattribute entsprechend hugoPublish etc.</p> Wird dieser z.B. als 'hugo' redefiniert, heissen die Steuerungsattribute entsprechend hugoPublish etc.</p>
<p>Der zweite Parameter ('devspec') erlaubt die Menge der zu ueberwachenden Geraeten <p>Der zweite Parameter ('devspec') erlaubt die Menge der zu ueberwachenden Geraeten
zu begrenzen (sonst werden einfach alle ueberwacht, was jedoch Performance kosten kann). zu begrenzen (sonst werden einfach alle ueberwacht, was jedoch Performance kosten kann).
Beispiel fuer devspec: 'TYPE=dummy' oder 'dummy1,dummy2'. Bei kommaseparierten Liste duerfen keine Leerzeichen verwendet werden!
s.a. <a href="https://fhem.de/commandref_DE.html#devspec">devspec</a></p> s.a. <a href="https://fhem.de/commandref_DE.html#devspec">devspec</a></p>
</ul> </ul>
@ -2519,13 +2657,27 @@ sub onmessage($$$) {
Ist der Rueckgabewert undef, dann wird das Setzen/Ausfuehren unterbunden. <br/> Ist der Rueckgabewert undef, dann wird das Setzen/Ausfuehren unterbunden. <br/>
Ist die Rueckgabe ein Hash (nur 'topic'), werden seine Schluesselwerte als Topic verwendet, Ist die Rueckgabe ein Hash (nur 'topic'), werden seine Schluesselwerte als Topic verwendet,
die Inhalte der Nachrichten sind entsprechend die Werte aus dem Hash.</p> die Inhalte der Nachrichten sind entsprechend die Werte aus dem Hash.</p>
<p>Option 'resendOnConnect' erlaubt eine Speicherung der Nachrichten,
wenn keine Verbindung zu dem MQTT-Server besteht.
Die zu sendende Nachrichten in einer Warteschlange gespeichet.
Wird die Verbindung aufgebaut, werden die Nachrichten in der ursprungichen Reihenfolge verschickt.
<ul>Moegliche Werte:
<li>none<br/>alle verwerfen</li>
<li>last<br/>immer nur die letzte Nachricht speichern</li>
<li>first<br/>immer nur die erste Nachricht speichern danach folgende verwerfen</li>
<li>all<br/>alle speichern, allerdings existiert eine Obergrenze von 100,
wird es mehr, werden aelteste ueberzaelige Nachrichten verworfen.</li>
</ul>
</p>
<p>Beispiele:<br/> <p>Beispiele:<br/>
<code> attr &lt;dev&gt; mqttPublish temperature:topic={"$base/$name"} temperature:qos=1 temperature:retain=0 *:topic={"$base/$name"} humidity:topic=/TEST/Feuchte<br/> <code> attr &lt;dev&gt; mqttPublish temperature:topic={"$base/$name"} temperature:qos=1 temperature:retain=0 *:topic={"$base/$name"} humidity:topic=/TEST/Feuchte<br/>
attr &lt;dev&gt; mqttPublish temperature|humidity:topic={"$base/$name"} temperature|humidity:qos=1 temperature|humidity:retain=0<br/> attr &lt;dev&gt; mqttPublish temperature|humidity:topic={"$base/$name"} temperature|humidity:qos=1 temperature|humidity:retain=0<br/>
attr &lt;dev&gt; mqttPublish *:topic={"$base/$name"} *:qos=2 *:retain=0<br/> attr &lt;dev&gt; mqttPublish *:topic={"$base/$name"} *:qos=2 *:retain=0<br/>
attr &lt;dev&gt; mqttPublish *:topic={"$base/$name"} reading:expression={"message: $value"}<br/> attr &lt;dev&gt; mqttPublish *:topic={"$base/$name"} reading:expression={"message: $value"}<br/>
attr &lt;dev&gt; mqttPublish *:topic={"$base/$name"} reading:expression={$value="message: $value"}<br/> attr &lt;dev&gt; mqttPublish *:topic={"$base/$name"} reading:expression={$value="message: $value"}<br/>
attr &lt;dev&gt; mqttPublish *:topic={"$base/$name"} reading:expression={"/TEST/Topic1"=>"$message", "/TEST/Topic2"=>"message: $message"}</code></p> attr &lt;dev&gt; mqttPublish *:topic={"$base/$name"} reading:expression={"/TEST/Topic1"=>"$message", "/TEST/Topic2"=>"message: $message"}</br>
attr &lt;dev&gt; mqttPublish [...] *:resendOnConnect=last
</code></p>
</p> </p>
</li> </li>