diff --git a/fhem/CHANGED b/fhem/CHANGED index 202b2bbcd..41ac7c75b 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +1,6 @@ # Add changes at the top of the list. Keep it in ASCII, and 80-char wide. # Do not insert empty lines here, update check depends on it. + - change: 10_MYSENSORS_DEVICE: implemented alive check thanks to Beta-User - bugfix: 93_DbRep: 7.15.2, Internal MODEL is set, minor fixes - change: 93_DbRep: 7.15.1, sqlCmd accept widget textField-long, Internal MODEL is set diff --git a/fhem/FHEM/10_MYSENSORS_DEVICE.pm b/fhem/FHEM/10_MYSENSORS_DEVICE.pm index 6118f622b..a568e301e 100755 --- a/fhem/FHEM/10_MYSENSORS_DEVICE.pm +++ b/fhem/FHEM/10_MYSENSORS_DEVICE.pm @@ -3,6 +3,7 @@ # fhem bridge to MySensors (see http://mysensors.org) # # Copyright (C) 2014 Norbert Truchsess +# Copyright (C) 2018 Hauswart@forum.fhem.de # # This file is part of fhem. # @@ -19,7 +20,7 @@ # You should have received a copy of the GNU General Public License # along with fhem. If not, see . # -# $Id$ +# $Id$id$ # ############################################## @@ -49,6 +50,8 @@ sub MYSENSORS_DEVICE_Initialize($) { "mapReadingType_.+ " . "mapReading_.+ " . "requestAck:1 " . + "timeoutAck " . + "timeoutAlive " . "IODev " . "showtime:0,1 " . $main::readingFnAttributes; @@ -79,6 +82,8 @@ BEGIN { Log3 SetExtensions ReadingsVal + InternalTimer + RemoveInternalTimer )) }; @@ -211,7 +216,9 @@ sub Define($$) { sub UnDefine($) { my ($hash) = @_; - + my $name = $hash->{NAME}; + RemoveInternalTimer("timeoutAck:$name"); + RemoveInternalTimer("timeoutAlive:$name"); return undef; } @@ -248,8 +255,8 @@ sub Set($@) { subType => $type, payload => $mappedValue, ); - readingsSingleUpdate($hash,$setcommand->{var},$setcommand->{val},1) unless ($hash->{ack} or $hash->{IODev}->{ack}); - }; + readingsSingleUpdate($hash,$setcommand->{var},$setcommand->{val},1) unless ($hash->{ack} or $hash->{IODev}->{ack}); + }; return "$command not defined: ".GP_Catch($@) if $@; last; }; @@ -382,11 +389,29 @@ sub Attr($$$$) { } last; }; + $attribute eq "timeoutAck" and do { + if ($command eq "set") { + $hash->{timeoutAck} = $value; + } else { + $hash->{timeoutAck} = 0; + } + last; + }; + $attribute eq "timeoutAlive" and do { + if ($command eq "set" and $value) { + $hash->{timeoutAlive} = $value; + refreshInternalMySTimer($hash,"Alive"); + } else { + $hash->{timeoutAlive} = 0; + } + last; + }; } } sub onGatewayStarted($) { - my ($hash) = @_; + my ($hash) = @_; + refreshInternalMySTimer($hash,"Alive") if ($hash->{timeoutAlive}); } sub onPresentationMessage($$) { @@ -447,31 +472,33 @@ sub onPresentationMessage($$) { } sub onSetMessage($$) { - my ($hash,$msg) = @_; - if (defined $msg->{payload}) { - eval { - my ($reading,$value) = rawToMappedReading($hash,$msg->{subType},$msg->{childId},$msg->{payload}); - readingsSingleUpdate($hash, $reading, $value, 1); - }; - Log3 ($hash->{NAME}, 4, "MYSENSORS_DEVICE $hash->{NAME}: ignoring C_SET-message ".GP_Catch($@)) if $@; - } else { - Log3 ($hash->{NAME}, 5, "MYSENSORS_DEVICE $hash->{NAME}: ignoring C_SET-message without payload"); - } + my ($hash,$msg) = @_; + my $name = $hash->{NAME}; + if (defined $msg->{payload}) { + eval { + my ($reading,$value) = rawToMappedReading($hash,$msg->{subType},$msg->{childId},$msg->{payload}); + readingsSingleUpdate($hash, $reading, $value, 1); + }; + Log3 ($hash->{NAME}, 4, "MYSENSORS_DEVICE $hash->{NAME}: ignoring C_SET-message ".GP_Catch($@)) if $@; + refreshInternalMySTimer($hash,"Alive") if $hash->{timeoutAlive}; + } else { + Log3 ($hash->{NAME}, 5, "MYSENSORS_DEVICE $hash->{NAME}: ignoring C_SET-message without payload"); + }; } sub onRequestMessage($$) { - my ($hash,$msg) = @_; - - eval { - my ($readingname,$val) = rawToMappedReading($hash, $msg->{subType}, $msg->{childId}, $msg->{payload}); - sendClientMessage($hash, - childId => $msg->{childId}, - cmd => C_SET, - subType => $msg->{subType}, - payload => ReadingsVal($hash->{NAME},$readingname,$val) - ); - }; - Log3 ($hash->{NAME}, 4, "MYSENSORS_DEVICE $hash->{NAME}: ignoring C_REQ-message ".GP_Catch($@)) if $@; + my ($hash,$msg) = @_; + eval { + my ($readingname,$val) = rawToMappedReading($hash, $msg->{subType}, $msg->{childId}, $msg->{payload}); + sendClientMessage($hash, + childId => $msg->{childId}, + cmd => C_SET, + subType => $msg->{subType}, + payload => ReadingsVal($hash->{NAME},$readingname,$val) + ); + }; + refreshInternalMySTimer($hash,"Alive") if $hash->{timeoutAlive}; + Log3 ($hash->{NAME}, 4, "MYSENSORS_DEVICE $hash->{NAME}: ignoring C_REQ-message ".GP_Catch($@)) if $@; } sub onInternalMessage($$) { @@ -534,7 +561,7 @@ sub onInternalMessage($$) { }; $type == I_CHILDREN and do { readingsSingleUpdate($hash, "state", "routingtable cleared", 1); - Log3 ($name, 3, "MYSENSORS_DEVICE $name: routingtable cleared"); + Log3 ($name, 3, "MYSENSORS_DEVICE $name: routingtable cleared"); last; }; $type == I_SKETCH_NAME and do { @@ -571,10 +598,11 @@ sub onInternalMessage($$) { } sub sendClientMessage($%) { - my ($hash,%msg) = @_; - $msg{radioId} = $hash->{radioId}; - $msg{ack} = $hash->{ack} unless defined $msg{ack}; - sendMessage($hash->{IODev},%msg); + my ($hash,%msg) = @_; + $msg{radioId} = $hash->{radioId}; + $msg{ack} = $hash->{ack} unless defined $msg{ack}; + sendMessage($hash->{IODev},%msg); + refreshInternalMySTimer($hash,"Ack") if (($hash->{ack} or $hash->{IODev}->{ack}) and $hash->{timeoutAck}) ; } sub rawToMappedReading($$$$) { @@ -608,6 +636,47 @@ sub mappedReadingToRaw($$$) { die "no mapping for reading $reading"; } +sub refreshInternalMySTimer($$) { + my ($hash,$calltype) = @_; + my $name = $hash->{NAME}; + Log3 $name, 5, "$name: refreshInternalMySTimer called ($calltype)"; + if ($calltype eq "Alive") { + RemoveInternalTimer("timeoutAlive:$name"); + my $nextTrigger = main::gettimeofday() + $hash->{timeoutAlive}; + InternalTimer($nextTrigger, "MYSENSORS::DEVICE::timeoutMySTimer", "timeoutAlive:$name", 0); + if ($hash->{STATE} ne "NACK" or $hash->{STATE} eq "NACK" and @{$hash->{IODev}->{messagesForRadioId}->{$hash->{radioId}}->{messages}} == 0) { + my $do_trigger = $hash->{STATE} ne "alive" ? 1 : 0; + readingsSingleUpdate($hash,"state","alive",$do_trigger); + } + } elsif ($calltype eq "Ack") { + RemoveInternalTimer("timeoutAck:$name"); + my $nextTrigger = main::gettimeofday() + $hash->{timeoutAck}; + InternalTimer($nextTrigger, "MYSENSORS::DEVICE::timeoutMySTimer", "timeoutAck:$name", 0); + Log3 $name, 4, "$name: Ack timeout timer set at $nextTrigger"; + } +} + +sub timeoutMySTimer($) { + my ($calltype, $name) = split(':', $_[0]); + my $hash = $main::defs{$name}; + Log3 $name, 5, "$name: timeoutMySTimer called ($calltype)"; + if ($calltype eq "timeoutAlive") { + readingsSingleUpdate($hash,"state","dead",1) unless ($hash->{STATE} eq "NACK"); + } elsif ($calltype eq "timeoutAck") { + #readingsSingleUpdate($hash,"state","timeoutAck passed",1);# if ($hash->{STATE} eq "NACK"); + if ($hash->{IODev}->{outstandingAck} == 0) { + Log3 $name, 4, "$name: timeoutMySTimer called ($calltype), no outstanding Acks at all"; + readingsSingleUpdate($hash,"state","alive",1) if ($hash->{STATE} eq "NACK"); + } elsif (@{$hash->{IODev}->{messagesForRadioId}->{$hash->{radioId}}->{messages}}) { + Log3 $name, 4, "$name: timeoutMySTimer called ($calltype), outstanding: $hash->{IODev}->{messagesForRadioId}->{$hash->{radioId}}->{messages}"; + readingsSingleUpdate($hash,"state","NACK",1) ; + } else { + Log3 $name, 4, "$name: timeoutMySTimer called ($calltype), no outstanding Acks for Node"; + readingsSingleUpdate($hash,"state","alive",1) if ($hash->{STATE} eq "NACK"); + } + } +} + 1; =pod @@ -620,71 +689,55 @@ sub mappedReadingToRaw($$$) {

MYSENSORS_DEVICE

=end html =cut - diff --git a/fhem/FHEM/lib/Device/MySensors/Message.pm b/fhem/FHEM/lib/Device/MySensors/Message.pm index 0e069290b..b1d920bc1 100644 --- a/fhem/FHEM/lib/Device/MySensors/Message.pm +++ b/fhem/FHEM/lib/Device/MySensors/Message.pm @@ -36,17 +36,17 @@ sub createMsg(%) { } sub dumpMsg($) { - my $msgRef = shift; - my $cmd = defined $msgRef->{'cmd'} ? commandToStr($msgRef->{'cmd'}) : "''"; - my $st = (defined $msgRef->{'cmd'} and defined $msgRef->{'subType'}) ? subTypeToStr( $msgRef->{'cmd'}, $msgRef->{'subType'} ) : "''"; - return sprintf("Rx: fr=%03d ci=%03d c=%03d(%-14s) st=%03d(%-16s) ack=%d %s\n", $msgRef->{'radioId'} // -1, $msgRef->{'childId'} // -1, $msgRef->{'cmd'} // -1, $cmd, $msgRef->{'subType'} // -1, $st, $msgRef->{'ack'} // -1, "'".($msgRef->{'payload'} // "")."'"); + my $msgRef = shift; + my $cmd = defined $msgRef->{'cmd'} ? commandToStr($msgRef->{'cmd'}) : "''"; + my $st = (defined $msgRef->{'cmd'} and defined $msgRef->{'subType'}) ? subTypeToStr( $msgRef->{'cmd'}, $msgRef->{'subType'} ) : "''"; + return sprintf("Rx: fr=%03d ci=%03d c=%03d(%-14s) st=%03d(%-16s) ack=%d %s\n", $msgRef->{'radioId'} // -1, $msgRef->{'childId'} // -1, $msgRef->{'cmd'} // -1, $cmd, $msgRef->{'subType'} // -1, $st, $msgRef->{'ack'} // -1, "'".($msgRef->{'payload'} // "")."'"); } sub gettime { - my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); - $year += 1900; - $mon++; - return sprintf("%04d%02d%02d-%02d:%02d:%02d", $year, $mon, $mday, $hour, $min, $sec); + my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); + $year += 1900; + $mon++; + return sprintf("%04d%02d%02d-%02d:%02d:%02d", $year, $mon, $mday, $hour, $min, $sec); } 1; \ No newline at end of file