MYSENSORS_DEVICE: modify SetExtensions handling, separate readings for heartbeat, smartSleep & NACK (Forum #99653)

git-svn-id: https://svn.fhem.de/fhem/trunk/fhem@19314 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
Beta-User 2019-05-03 05:02:58 +00:00
parent 5f72d81082
commit 00ff993653
3 changed files with 85 additions and 62 deletions

View File

@ -1,5 +1,7 @@
# Add changes at the top of the list. Keep it in ASCII, and 80-char wide. # 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. # Do not insert empty lines here, update check depends on it.
- change: 10_MYSENSORS_DEVICE: enhance support for SetExtensions;
separate readings for heatrbeat, smartSleep & NACK
- bugfix: 73_GardenaSmartBridge: fix undefined_value Error - bugfix: 73_GardenaSmartBridge: fix undefined_value Error
- feature: 98_Text2Speech: add Amazon Polly as new suggested TTS-Engine - feature: 98_Text2Speech: add Amazon Polly as new suggested TTS-Engine
due best quality due best quality

View File

@ -4,6 +4,7 @@
# #
# Copyright (C) 2014 Norbert Truchsess # Copyright (C) 2014 Norbert Truchsess
# Copyright (C) 2019 Hauswart@forum.fhem.de # Copyright (C) 2019 Hauswart@forum.fhem.de
# Copyright (C) 2019 Beta-User@forum.fhem.de
# #
# This file is part of fhem. # This file is part of fhem.
# #

View File

@ -4,6 +4,7 @@
# #
# Copyright (C) 2014 Norbert Truchsess # Copyright (C) 2014 Norbert Truchsess
# Copyright (C) 2019 Hauswart@forum.fhem.de # Copyright (C) 2019 Hauswart@forum.fhem.de
# Copyright (C) 2019 Beta-User@forum.fhem.de
# #
# This file is part of fhem. # This file is part of fhem.
# #
@ -42,8 +43,9 @@ sub MYSENSORS_DEVICE_Initialize($) {
my @attrList = qw( my @attrList = qw(
config:M,I config:M,I
mode:node,repeater mode:node,repeater
version:1.4 # version:1.4
setCommands setCommands
setExtensionsEvent:1,0
setReading_.+ setReading_.+
mapReadingType_.+ mapReadingType_.+
mapReading_.+ mapReading_.+
@ -66,11 +68,11 @@ package MYSENSORS::DEVICE;
use strict; use strict;
use warnings; use warnings;
use Time::Local 'timegm_nocheck';
use GPUtils qw(:all); use GPUtils qw(:all);
use Device::MySensors::Constants qw(:all); use Device::MySensors::Constants qw(:all);
use Device::MySensors::Message qw(:all); use Device::MySensors::Message qw(:all);
#use SetExtensions qw/ :all /;
BEGIN { BEGIN {
main::LoadModule("MYSENSORS"); main::LoadModule("MYSENSORS");
@ -88,8 +90,10 @@ BEGIN {
AssignIoPort AssignIoPort
Log3 Log3
SetExtensions SetExtensions
SetExtensionsCancel
ReadingsVal ReadingsVal
ReadingsNum ReadingsNum
InternalVal
FileRead FileRead
InternalTimer InternalTimer
RemoveInternalTimer RemoveInternalTimer
@ -237,10 +241,7 @@ sub Define($$) {
sub UnDefine($) { sub UnDefine($) {
my ($hash) = @_; my ($hash) = @_;
my $name = $hash->{NAME}; RemoveInternalTimer($hash);
RemoveInternalTimer("timeoutAck:$name");
RemoveInternalTimer("timeoutAlive:$name");
RemoveInternalTimer("timeoutAwake:$name");
return undef; return undef;
} }
@ -256,7 +257,8 @@ sub Set($@) {
COMMAND_HANDLER: { COMMAND_HANDLER: {
$command eq "time" and do { $command eq "time" and do {
sendClientMessage($hash, childId => 255, cmd => C_INTERNAL, ack => 0, subType => I_TIME, payload => time); my $t = timegm_nocheck(localtime(time));
sendClientMessage($hash, childId => 255, cmd => C_INTERNAL, ack => 0, subType => I_TIME, payload => $t);
last; last;
}; };
$command eq "reboot" and do { $command eq "reboot" and do {
@ -309,9 +311,19 @@ sub Set($@) {
cmd => C_SET, cmd => C_SET,
subType => $type, subType => $type,
payload => $mappedValue, payload => $mappedValue,
); );
readingsSingleUpdate($hash,$setcommand->{var},$setcommand->{val},1) unless ($hash->{ack} or $hash->{IODev}->{ack}); unless ($hash->{ack} or $hash->{IODev}->{ack}) {
}; readingsSingleUpdate($hash,$setcommand->{var},$setcommand->{val},1) ;
SetExtensionsCancel($hash) if ($command eq "on" || $command eq "off");
if ($hash->{SetExtensionsCommand} && AttrVal($name, "setExtensionsEvent", undef)) {
readingsSingleUpdate($hash,"state",$hash->{SetExtensionsCommand},1) ;
} else {
readingsSingleUpdate($hash,"state","$command",1) ;
}
} else {
readingsSingleUpdate($hash,"state","set $command",1) ;
}
};
return "$command not defined: ".GP_Catch($@) if $@; return "$command not defined: ".GP_Catch($@) if $@;
last; last;
}; };
@ -576,8 +588,8 @@ sub Attr($$$$) {
$hash->{timeoutAlive} = $value; $hash->{timeoutAlive} = $value;
refreshInternalMySTimer($hash,"Alive"); refreshInternalMySTimer($hash,"Alive");
} else { } else {
$hash->{timeoutAlive} = 0; $hash->{timeoutAlive} = 0;
} }
last; last;
}; };
$attribute eq "OTA_autoUpdate" and do { $attribute eq "OTA_autoUpdate" and do {
@ -608,7 +620,6 @@ sub onPresentationMessage($$) {
}; };
}; };
$hash->{version} = $msg->{payload}; $hash->{version} = $msg->{payload};
#CommandAttr(undef, "$name version $msg->{payload}");
}; };
my $readingMappings = $hash->{readingMappings}; my $readingMappings = $hash->{readingMappings};
@ -679,9 +690,16 @@ sub onSetMessage($$) {
eval { eval {
my ($reading,$value) = rawToMappedReading($hash,$msg->{subType},$msg->{childId},$msg->{payload}); my ($reading,$value) = rawToMappedReading($hash,$msg->{subType},$msg->{childId},$msg->{payload});
readingsSingleUpdate($hash, $reading, $value, 1); readingsSingleUpdate($hash, $reading, $value, 1);
if ((defined ($hash->{setcommands}->{$value}) && $hash->{setcommands}->{$value}->{var} eq $reading)) { #$msg->{childId}
if ($hash->{SetExtensionsCommand} && AttrVal($name, "setExtensionsEvent", undef)) {
readingsSingleUpdate($hash,"state",$hash->{SetExtensionsCommand},1) ;
} else {
readingsSingleUpdate($hash,"state","$value",1);
SetExtensionsCancel($hash) unless $msg->{ack};
}
}
}; };
Log3 ($hash->{NAME}, 4, "MYSENSORS_DEVICE $hash->{NAME}: ignoring C_SET-message ".GP_Catch($@)) if $@; Log3 ($hash->{NAME}, 4, "MYSENSORS_DEVICE $hash->{NAME}: ignoring C_SET-message ".GP_Catch($@)) if $@;
#refreshInternalMySTimer($hash,"Alive") if $hash->{timeoutAlive}; #deactivate in case of wanted reduction of alive to internal (heartbeat/battery/smartsleep) messages
} else { } else {
Log3 ($hash->{NAME}, 5, "MYSENSORS_DEVICE $hash->{NAME}: ignoring C_SET-message without payload"); Log3 ($hash->{NAME}, 5, "MYSENSORS_DEVICE $hash->{NAME}: ignoring C_SET-message without payload");
}; };
@ -699,7 +717,6 @@ sub onRequestMessage($$) {
payload => ReadingsVal($hash->{NAME},$readingname,$val) 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 $@; Log3 ($hash->{NAME}, 4, "MYSENSORS_DEVICE $hash->{NAME}: ignoring C_REQ-message ".GP_Catch($@)) if $@;
} }
@ -710,8 +727,6 @@ sub onInternalMessage($$) {
my $typeStr = internalMessageTypeToStr($type); my $typeStr = internalMessageTypeToStr($type);
INTERNALMESSAGE: { INTERNALMESSAGE: {
$type == I_BATTERY_LEVEL and do { $type == I_BATTERY_LEVEL and do {
# readingsSingleUpdate($hash, "batterylevel", $msg->{payload}, 1);
# Log3 ($name, 3, "MYSENSORS_DEVICE $name: batterylevel is deprecated and will be removed soon, use batteryPercent instead (Forum #87575)");
readingsSingleUpdate($hash, "batteryPercent", $msg->{payload}, 1); readingsSingleUpdate($hash, "batteryPercent", $msg->{payload}, 1);
refreshInternalMySTimer($hash,"Alive") if $hash->{timeoutAlive}; refreshInternalMySTimer($hash,"Alive") if $hash->{timeoutAlive};
Log3 ($name, 4, "MYSENSORS_DEVICE $name: batteryPercent $msg->{payload}"); Log3 ($name, 4, "MYSENSORS_DEVICE $name: batteryPercent $msg->{payload}");
@ -722,7 +737,8 @@ sub onInternalMessage($$) {
Log3 ($name, 4, "MYSENSORS_DEVICE $name: response to time-request acknowledged"); Log3 ($name, 4, "MYSENSORS_DEVICE $name: response to time-request acknowledged");
} else { } else {
$hash->{nowSleeping} = 0 if $hash->{nowSleeping}; $hash->{nowSleeping} = 0 if $hash->{nowSleeping};
sendClientMessage($hash,cmd => C_INTERNAL, childId => 255, subType => I_TIME, payload => time); my $t = timegm_nocheck(localtime(time));
sendClientMessage($hash,cmd => C_INTERNAL, childId => 255, subType => I_TIME, payload => $t);
Log3 ($name, 4, "MYSENSORS_DEVICE $name: update of time requested"); Log3 ($name, 4, "MYSENSORS_DEVICE $name: update of time requested");
} }
last; last;
@ -829,7 +845,7 @@ sub onInternalMessage($$) {
last; last;
}; };
$type == I_HEARTBEAT_RESPONSE and do { $type == I_HEARTBEAT_RESPONSE and do {
readingsSingleUpdate($hash, "heartbeat", "last", 0); readingsSingleUpdate($hash, "heartbeat", "alive",1);
refreshInternalMySTimer($hash,"Alive") if $hash->{timeoutAlive}; refreshInternalMySTimer($hash,"Alive") if $hash->{timeoutAlive};
if ($hash->{nowSleeping}) { if ($hash->{nowSleeping}) {
$hash->{nowSleeping} = 0 ; $hash->{nowSleeping} = 0 ;
@ -913,7 +929,7 @@ sub onInternalMessage($$) {
}; };
$type == I_POST_SLEEP_NOTIFICATION and do { $type == I_POST_SLEEP_NOTIFICATION and do {
#$hash->{$typeStr} = $msg->{payload}; #$hash->{$typeStr} = $msg->{payload};
readingsSingleUpdate($hash,"state","awake",1) unless ($hash->{STATE} eq "NACK"); readingsSingleUpdate($hash,"sleepState","awake",1);
$hash->{nowSleeping} = 0; $hash->{nowSleeping} = 0;
refreshInternalMySTimer($hash,"Alive") if $hash->{timeoutAlive}; refreshInternalMySTimer($hash,"Alive") if $hash->{timeoutAlive};
last; last;
@ -944,15 +960,13 @@ sub sendClientMessage($%) {
$hash->{retainedMessages}=1; $hash->{retainedMessages}=1;
Log3 ($name,5,"$name: No array yet for enqueued messages, building it!"); Log3 ($name,5,"$name: No array yet for enqueued messages, building it!");
} else { } else {
#my $referencetype = ref $messages; @$messages = grep {
#Log3 ($name,4,"$name: Reference type is $referencetype."); $_->{childId} != $msg{childId}
@$messages = grep { or $_->{cmd} != $msg{cmd}
$_->{childId} != $msg{childId} or $_->{subType} != $msg{subType}
or $_->{cmd} != $msg{cmd} } @$messages;
or $_->{subType} != $msg{subType} push @$messages,\%msg;
} @$messages; eval($hash->{retainedMessages}=scalar(@$messages));
push @$messages,\%msg;
eval($hash->{retainedMessages}=scalar(@$messages));
} }
} }
} }
@ -1087,56 +1101,58 @@ sub flashFirmware($$) {
sub refreshInternalMySTimer($$) { sub refreshInternalMySTimer($$) {
my ($hash,$calltype) = @_; my ($hash,$calltype) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
my $heart = ReadingsVal($hash,"heartbeat","dead");
Log3 $name, 5, "$name: refreshInternalMySTimer called ($calltype)"; Log3 $name, 5, "$name: refreshInternalMySTimer called ($calltype)";
if ($calltype eq "Alive") { if ($calltype eq "Alive") {
RemoveInternalTimer("timeoutAlive:$name"); RemoveInternalTimer($hash,"MYSENSORS::DEVICE::timeoutAlive");
my $nextTrigger = main::gettimeofday() + $hash->{timeoutAlive}; my $nextTrigger = main::gettimeofday() + $hash->{timeoutAlive};
InternalTimer($nextTrigger, "MYSENSORS::DEVICE::timeoutMySTimer", "timeoutAlive:$name", 0); InternalTimer($nextTrigger, "MYSENSORS::DEVICE::timeoutAlive",$hash);
if ($hash->{STATE} ne "NACK" or $hash->{STATE} eq "NACK" and @{$hash->{IODev}->{messagesForRadioId}->{$hash->{radioId}}->{messages}} == 0) { if ($heart ne "NACK" or $heart eq "NACK" and @{$hash->{IODev}->{messagesForRadioId}->{$hash->{radioId}}->{messages}} == 0) {
my $do_trigger = $hash->{STATE} ne "alive" ? 1 : 0; readingsSingleUpdate($hash,"heartbeat","alive",1);
readingsSingleUpdate($hash,"state","alive",$do_trigger);
} }
} elsif ($calltype eq "Ack") { } elsif ($calltype eq "Ack") {
RemoveInternalTimer("timeoutAck:$name"); RemoveInternalTimer($hash,"MYSENSORS::DEVICE::timeoutAck");
my $nextTrigger = main::gettimeofday() + $hash->{timeoutAck}; my $nextTrigger = main::gettimeofday() + $hash->{timeoutAck};
InternalTimer($nextTrigger, "MYSENSORS::DEVICE::timeoutMySTimer", "timeoutAck:$name", 0); InternalTimer($nextTrigger, "MYSENSORS::DEVICE::timeoutAck",$hash);
Log3 $name, 5, "$name: Ack timeout timer set at $nextTrigger"; Log3 $name, 5, "$name: Ack timeout timer set at $nextTrigger";
} elsif ($calltype eq "Asleep") { } elsif ($calltype eq "Asleep") {
RemoveInternalTimer("timeoutAwake:$name"); RemoveInternalTimer($hash,"MYSENSORS::DEVICE::timeoutAwake");
#0.5 is default; could be dynamized by attribute if needed my $nextTrigger = main::gettimeofday() + 0.3;
my $nextTrigger = main::gettimeofday() + 0.3; InternalTimer($nextTrigger, "MYSENSORS::DEVICE::timeoutAwake",$hash);
InternalTimer($nextTrigger, "MYSENSORS::DEVICE::timeoutMySTimer", "timeoutAwake:$name", 0);
Log3 $name, 5, "$name: Awake timeout timer set at $nextTrigger"; Log3 $name, 5, "$name: Awake timeout timer set at $nextTrigger";
} }
} }
sub timeoutMySTimer($) { sub timeoutAlive($) {
my ($calltype, $name) = split(':', $_[0]); my $hash = shift;
my $hash = $main::defs{$name}; Log3 $hash->{NAME}, 5, "$hash->{NAME}: timeoutAlive called";
Log3 $name, 5, "$name: timeoutMySTimer called ($calltype)"; readingsSingleUpdate($hash,"heartbeat","dead",1) unless (ReadingsVal($hash,"heartbeat","dead") eq "NACK");
if ($calltype eq "timeoutAlive") { }
readingsSingleUpdate($hash,"state","dead",1) unless ($hash->{STATE} eq "NACK");
} elsif ($calltype eq "timeoutAck") { sub timeoutAck($) {
#readingsSingleUpdate($hash,"state","timeoutAck passed",1);# if ($hash->{STATE} eq "NACK"); my $hash = shift;
if ($hash->{IODev}->{outstandingAck} == 0) { Log3 $hash->{NAME}, 5, "$hash->{NAME}: timeoutAck called";
Log3 $name, 4, "$name: timeoutMySTimer called ($calltype), no outstanding Acks at all"; if ($hash->{IODev}->{outstandingAck} == 0) {
readingsSingleUpdate($hash,"state","alive",1) if ($hash->{STATE} eq "NACK"); Log3 $hash->{NAME}, 4, "$hash->{NAME}: timeoutAck called, no outstanding Acks at all";
} elsif (@{$hash->{IODev}->{messagesForRadioId}->{$hash->{radioId}}->{messages}}) { readingsSingleUpdate($hash,"heartbeat","alive",1) if (ReadingsVal($hash,"heartbeat","dead") eq "NACK");
Log3 $name, 4, "$name: timeoutMySTimer called ($calltype), outstanding: $hash->{IODev}->{messagesForRadioId}->{$hash->{radioId}}->{messages}"; } elsif (@{$hash->{IODev}->{messagesForRadioId}->{$hash->{radioId}}->{messages}}) {
readingsSingleUpdate($hash,"state","NACK",1) ; Log3 $hash->{NAME}, 4, "$hash->{NAME}: timeoutAck called, outstanding: @$hash->{IODev}->{messagesForRadioId}->{$hash->{radioId}}->{messages}";
} else { readingsSingleUpdate($hash,"heartbeat","NACK",1) ;
Log3 $name, 4, "$name: timeoutMySTimer called ($calltype), no outstanding Acks for Node"; } else {
readingsSingleUpdate($hash,"state","alive",1) if ($hash->{STATE} eq "NACK"); Log3 $hash->{NAME}, 4, "$hash->{NAME}: timeoutAck called, no outstanding Acks for Node";
} readingsSingleUpdate($hash,"heartbeat","alive",1) if (ReadingsVal($hash,"heartbeat","dead") eq "NACK");
} elsif ($calltype eq "timeoutAwake") {
readingsSingleUpdate($hash,"state","asleep",1) unless ($hash->{STATE} eq "NACK");
$hash->{nowSleeping} = 1;
} }
} }
sub timeoutAwake($) {
my $hash = shift;
Log3 $hash->{NAME}, 5, "$hash->{NAME}: timeoutAwake called";
readingsSingleUpdate($hash,"sleepState","asleep",1);
$hash->{nowSleeping} = 1;
}
sub sendRetainedMessages($) { sub sendRetainedMessages($) {
my ($hash) = @_; my ($hash) = @_;
my $name = $hash->{NAME};
my $retainedMsg; my $retainedMsg;
while (ref ($retainedMsg = shift @{$hash->{retainedMessagesForRadioId}->{messages}}) eq 'HASH') { while (ref ($retainedMsg = shift @{$hash->{retainedMessagesForRadioId}->{messages}}) eq 'HASH') {
sendClientMessage($hash,%$retainedMsg); sendClientMessage($hash,%$retainedMsg);
@ -1243,6 +1259,10 @@ sub sendRetainedMessages($) {
<li> <li>
<p><code>attr &lt;name&gt; setReading_&lt;reading&gt; [&lt;value&gt;]*</code><br/>configures a reading that can be modified by set-command<br/>e.g.: <code>attr &lt;name&gt; setReading_switch_1 on,off</code></p> <p><code>attr &lt;name&gt; setReading_&lt;reading&gt; [&lt;value&gt;]*</code><br/>configures a reading that can be modified by set-command<br/>e.g.: <code>attr &lt;name&gt; setReading_switch_1 on,off</code></p>
</li> </li>
<li>
<p><code>attr &lt;name&gt; setExtensionsEvent</code><br/>If set, the event will contain the command implemented by SetExtensions
(e.g. on-for-timer 10), else the executed command (e.g. on).
</li>
<li> <li>
<p><code>attr &lt;name&gt; mapReading_&lt;reading&gt; &lt;childId&gt; &lt;readingtype&gt; [&lt;value&gt;:&lt;mappedvalue&gt;]*</code><br/>configures the reading-name for a given childId and sensortype<br/>e.g.: <code>attr xxx mapReading_aussentemperatur 123 temperature</code> <p><code>attr &lt;name&gt; mapReading_&lt;reading&gt; &lt;childId&gt; &lt;readingtype&gt; [&lt;value&gt;:&lt;mappedvalue&gt;]*</code><br/>configures the reading-name for a given childId and sensortype<br/>e.g.: <code>attr xxx mapReading_aussentemperatur 123 temperature</code>
or <code>attr xxx mapReading_leftwindow 10 status 1:closed 0:open</code>. See also mapReadingType for setting defaults for types without predefined defaults</p> or <code>attr xxx mapReading_leftwindow 10 status 1:closed 0:open</code>. See also mapReadingType for setting defaults for types without predefined defaults</p>