mirror of
https://github.com/fhem/fhem-mirror.git
synced 2025-05-04 22:19:38 +00:00
10_MYSENSORS_DEVICE: change alive logic to heartbeat, add OTA feature, add more MySensors 2.0 API features
git-svn-id: https://svn.fhem.de/fhem/trunk/fhem@18131 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
parent
fb1b0de2b2
commit
eb96fc307c
4
CHANGED
4
CHANGED
@ -1,5 +1,9 @@
|
|||||||
# 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: make OTA feature available,
|
||||||
|
change battery name convention,
|
||||||
|
support MySensors API 2.0 features like
|
||||||
|
heartbeat and smartSleep
|
||||||
- bugfix: 49_SSCam: fix problem with some older SMTP SSL module
|
- bugfix: 49_SSCam: fix problem with some older SMTP SSL module
|
||||||
- bugfix: 73_AutoShuttersControl: fix typo in commandref
|
- bugfix: 73_AutoShuttersControl: fix typo in commandref
|
||||||
- change: 49_SSCam: V8.3.1, change usage of older SMTP versions when Email
|
- change: 49_SSCam: V8.3.1, change usage of older SMTP versions when Email
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
# fhem bridge to MySensors (see http://mysensors.org)
|
# fhem bridge to MySensors (see http://mysensors.org)
|
||||||
#
|
#
|
||||||
# Copyright (C) 2014 Norbert Truchsess
|
# Copyright (C) 2014 Norbert Truchsess
|
||||||
# Copyright (C) 2018 Hauswart@forum.fhem.de
|
# Copyright (C) 2019 Hauswart@forum.fhem.de
|
||||||
#
|
#
|
||||||
# This file is part of fhem.
|
# This file is part of fhem.
|
||||||
#
|
#
|
||||||
@ -27,10 +27,6 @@
|
|||||||
use strict;
|
use strict;
|
||||||
use warnings;
|
use warnings;
|
||||||
|
|
||||||
my %gets = (
|
|
||||||
"version" => "",
|
|
||||||
);
|
|
||||||
|
|
||||||
sub MYSENSORS_DEVICE_Initialize($) {
|
sub MYSENSORS_DEVICE_Initialize($) {
|
||||||
|
|
||||||
my $hash = shift @_;
|
my $hash = shift @_;
|
||||||
@ -39,6 +35,7 @@ sub MYSENSORS_DEVICE_Initialize($) {
|
|||||||
$hash->{DefFn} = "MYSENSORS::DEVICE::Define";
|
$hash->{DefFn} = "MYSENSORS::DEVICE::Define";
|
||||||
$hash->{UndefFn} = "MYSENSORS::DEVICE::UnDefine";
|
$hash->{UndefFn} = "MYSENSORS::DEVICE::UnDefine";
|
||||||
$hash->{SetFn} = "MYSENSORS::DEVICE::Set";
|
$hash->{SetFn} = "MYSENSORS::DEVICE::Set";
|
||||||
|
$hash->{GetFn} = "MYSENSORS::DEVICE::Get";
|
||||||
$hash->{AttrFn} = "MYSENSORS::DEVICE::Attr";
|
$hash->{AttrFn} = "MYSENSORS::DEVICE::Attr";
|
||||||
|
|
||||||
$hash->{AttrList} =
|
$hash->{AttrList} =
|
||||||
@ -54,6 +51,9 @@ sub MYSENSORS_DEVICE_Initialize($) {
|
|||||||
"timeoutAlive " .
|
"timeoutAlive " .
|
||||||
"IODev " .
|
"IODev " .
|
||||||
"showtime:0,1 " .
|
"showtime:0,1 " .
|
||||||
|
"OTA_autoUpdate:0,1 " .
|
||||||
|
"OTA_BL_Type:Optiboot,MYSBootloader " .
|
||||||
|
"OTA_Chan76_IODev " .
|
||||||
$main::readingFnAttributes;
|
$main::readingFnAttributes;
|
||||||
|
|
||||||
main::LoadModule("MYSENSORS");
|
main::LoadModule("MYSENSORS");
|
||||||
@ -75,6 +75,9 @@ BEGIN {
|
|||||||
GP_Import(qw(
|
GP_Import(qw(
|
||||||
AttrVal
|
AttrVal
|
||||||
readingsSingleUpdate
|
readingsSingleUpdate
|
||||||
|
readingsBeginUpdate
|
||||||
|
readingsEndUpdate
|
||||||
|
readingsBulkUpdate
|
||||||
CommandAttr
|
CommandAttr
|
||||||
CommandDeleteAttr
|
CommandDeleteAttr
|
||||||
CommandDeleteReading
|
CommandDeleteReading
|
||||||
@ -82,11 +85,23 @@ BEGIN {
|
|||||||
Log3
|
Log3
|
||||||
SetExtensions
|
SetExtensions
|
||||||
ReadingsVal
|
ReadingsVal
|
||||||
|
ReadingsNum
|
||||||
|
FileRead
|
||||||
InternalTimer
|
InternalTimer
|
||||||
RemoveInternalTimer
|
RemoveInternalTimer
|
||||||
))
|
))
|
||||||
};
|
};
|
||||||
|
|
||||||
|
my %gets = (
|
||||||
|
"version" => "noArg",
|
||||||
|
"heartbeat" => "noArg",
|
||||||
|
"presentation" => "noArg",
|
||||||
|
"RSSI" => "noArg",
|
||||||
|
"Extended_DEBUG" => "noArg",
|
||||||
|
"ReadingsFromComment" => "noArg",
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
my %static_types = (
|
my %static_types = (
|
||||||
S_DOOR => { receives => [], sends => [V_TRIPPED,V_ARMED] }, # Door and window sensors
|
S_DOOR => { receives => [], sends => [V_TRIPPED,V_ARMED] }, # Door and window sensors
|
||||||
S_MOTION => { receives => [], sends => [V_TRIPPED,V_ARMED] }, # Motion sensors
|
S_MOTION => { receives => [], sends => [V_TRIPPED,V_ARMED] }, # Motion sensors
|
||||||
@ -202,14 +217,16 @@ sub Define($$) {
|
|||||||
return "requires 1 parameters" unless (defined $radioId and $radioId ne "");
|
return "requires 1 parameters" unless (defined $radioId and $radioId ne "");
|
||||||
$hash->{radioId} = $radioId;
|
$hash->{radioId} = $radioId;
|
||||||
$hash->{sets} = {
|
$hash->{sets} = {
|
||||||
'time' => "",
|
'time' => "noArg",
|
||||||
reboot => "",
|
'reboot' => "noArg",
|
||||||
# clear => "",
|
'clear' => "noArg",
|
||||||
|
'flash' => "noArg",
|
||||||
|
'fwType' => "",
|
||||||
};
|
};
|
||||||
|
|
||||||
$hash->{ack} = 0;
|
$hash->{ack} = 0;
|
||||||
$hash->{typeMappings} = {map {variableTypeToIdx($_) => $static_mappings{$_}} keys %static_mappings};
|
$hash->{typeMappings} = {map {variableTypeToIdx($_) => $static_mappings{$_}} keys %static_mappings};
|
||||||
$hash->{sensorMappings} = {map {sensorTypeToIdx($_) => $static_types{$_}} keys %static_types};
|
$hash->{sensorMappings} = {map {sensorTypeToIdx($_) => $static_types{$_}} keys %static_types};
|
||||||
|
|
||||||
$hash->{readingMappings} = {};
|
$hash->{readingMappings} = {};
|
||||||
AssignIoPort($hash);
|
AssignIoPort($hash);
|
||||||
};
|
};
|
||||||
@ -219,6 +236,7 @@ sub UnDefine($) {
|
|||||||
my $name = $hash->{NAME};
|
my $name = $hash->{NAME};
|
||||||
RemoveInternalTimer("timeoutAck:$name");
|
RemoveInternalTimer("timeoutAck:$name");
|
||||||
RemoveInternalTimer("timeoutAlive:$name");
|
RemoveInternalTimer("timeoutAlive:$name");
|
||||||
|
RemoveInternalTimer("timeoutAwake:$name");
|
||||||
return undef;
|
return undef;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -226,23 +244,56 @@ sub Set($@) {
|
|||||||
my ($hash,$name,$command,@values) = @_;
|
my ($hash,$name,$command,@values) = @_;
|
||||||
return "Need at least one parameters" unless defined $command;
|
return "Need at least one parameters" unless defined $command;
|
||||||
if(!defined($hash->{sets}->{$command})) {
|
if(!defined($hash->{sets}->{$command})) {
|
||||||
|
$hash->{sets}->{fwType} = join(",", getFirmwareTypes($hash->{IODev}));
|
||||||
my $list = join(" ", map {$hash->{sets}->{$_} ne "" ? "$_:$hash->{sets}->{$_}" : $_} sort keys %{$hash->{sets}});
|
my $list = join(" ", map {$hash->{sets}->{$_} ne "" ? "$_:$hash->{sets}->{$_}" : $_} sort keys %{$hash->{sets}});
|
||||||
|
$hash->{sets}->{fwType} = "";
|
||||||
return grep (/(^on$)|(^off$)/,keys %{$hash->{sets}}) == 2 ? SetExtensions($hash, $list, $name, $command, @values) : "Unknown argument $command, choose one of $list";
|
return grep (/(^on$)|(^off$)/,keys %{$hash->{sets}}) == 2 ? SetExtensions($hash, $list, $name, $command, @values) : "Unknown argument $command, choose one of $list";
|
||||||
}
|
}
|
||||||
|
|
||||||
COMMAND_HANDLER: {
|
COMMAND_HANDLER: {
|
||||||
# $command eq "clear" and do {
|
|
||||||
# # Test 102 anstatt 255 :) und Log
|
|
||||||
# sendClientMessage($hash, childId => 255, cmd => C_INTERNAL, subType => I_CHILDREN, payload => "C");
|
|
||||||
# Log3 ($name,3,"MYSENSORS_DEVICE $name: clear");
|
|
||||||
# # Test
|
|
||||||
# last;
|
|
||||||
# };
|
|
||||||
$command eq "time" and do {
|
$command eq "time" and do {
|
||||||
sendClientMessage($hash, childId => 255, cmd => C_INTERNAL, subType => I_TIME, payload => time);
|
sendClientMessage($hash, childId => 255, cmd => C_INTERNAL, ack => 0, subType => I_TIME, payload => time);
|
||||||
last;
|
last;
|
||||||
};
|
};
|
||||||
$command eq "reboot" and do {
|
$command eq "reboot" and do {
|
||||||
sendClientMessage($hash, childId => 255, cmd => C_INTERNAL, subType => I_REBOOT);
|
my $blVersion = ReadingsVal($name, "BL_VERSION", "");
|
||||||
|
sendClientMessage($hash, childId => 255, cmd => C_INTERNAL, ack => 0, subType => I_REBOOT) if (defined($hash->{OTA_BL_Type}) or $blVersion eq "3.0");
|
||||||
|
last;
|
||||||
|
};
|
||||||
|
$command eq "clear" and do {
|
||||||
|
sendClientMessage($hash, childId => 255, cmd => C_INTERNAL, ack => 0, subType => I_DEBUG, payload => "E");
|
||||||
|
Log3 ($name,3,"MYSENSORS_DEVICE $name: clear");
|
||||||
|
last;
|
||||||
|
};
|
||||||
|
$command eq "flash" and do {
|
||||||
|
my $blVersion = ReadingsVal($name, "BL_VERSION", "");
|
||||||
|
my $blType = AttrVal($name, "OTA_BL_Type", "");
|
||||||
|
my $fwType = ReadingsNum($name, "FW_TYPE", -1);
|
||||||
|
if ($fwType == -1) {
|
||||||
|
Log3 ($name,3,"Firmware type not defined (FW_TYPE) for $name, update not started");
|
||||||
|
return "$name: Firmware type not defined (FW_TYPE)";
|
||||||
|
} elsif ($blVersion eq "3.0" or $blType eq "Optiboot") {
|
||||||
|
Log3 ($name,4,"Startet flashing Firmware: Optiboot method");
|
||||||
|
return flashFirmware($hash, $fwType);
|
||||||
|
} elsif ($blType eq "MYSBootloader") {
|
||||||
|
$hash->{OTA_requested} = 1;
|
||||||
|
Log3 ($name,4,"Send reboot command to MYSBootloader node to start update");
|
||||||
|
sendClientMessage($hash, childId => 255, cmd => C_INTERNAL, ack => 1, subType => I_REBOOT);
|
||||||
|
} else {
|
||||||
|
return "$name: No valid OTA_BL_Type specified" if ($blVersion eq "");
|
||||||
|
return "$name: Expected bootloader version 3.0 but found: $blVersion or specify a valid OTA_BL_Type";
|
||||||
|
}
|
||||||
|
last;
|
||||||
|
};
|
||||||
|
$command eq "fwType" and do {
|
||||||
|
if (@values == 1) {
|
||||||
|
my ($type) = @values;
|
||||||
|
if ($type =~ /^\d*$/) {
|
||||||
|
readingsSingleUpdate($hash, 'FW_TYPE', $type, 1);
|
||||||
|
} else {
|
||||||
|
return "fwType must be numeric";
|
||||||
|
}
|
||||||
|
}
|
||||||
last;
|
last;
|
||||||
};
|
};
|
||||||
(defined ($hash->{setcommands}->{$command})) and do {
|
(defined ($hash->{setcommands}->{$command})) and do {
|
||||||
@ -270,14 +321,132 @@ sub Set($@) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub Get($@) {
|
||||||
|
my ($hash, @a) = @_;
|
||||||
|
my $type = $hash->{TYPE};
|
||||||
|
return "\"get $type\" needs at least one parameter" if(@a < 2);
|
||||||
|
if(!defined($hash->{gets}->{$a[1]})) {
|
||||||
|
if(!defined($gets{$a[1]})) {
|
||||||
|
my @cList = map { $_ =~ m/^(file|raw)$/ ? $_ : "$_:noArg" } sort keys %gets;
|
||||||
|
return "Unknown argument $a[1], choose one of " . join(" ", @cList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
my $command = $a[1];
|
||||||
|
|
||||||
|
COMMAND_HANDLER: {
|
||||||
|
$command eq "version" and do {
|
||||||
|
sendClientMessage($hash, cmd => C_INTERNAL, ack => 0, subType => I_VERSION);
|
||||||
|
last;
|
||||||
|
};
|
||||||
|
$command eq "heartbeat" and do {
|
||||||
|
sendClientMessage($hash, cmd => C_INTERNAL, ack => 0, subType => I_HEARTBEAT_REQUEST);
|
||||||
|
last;
|
||||||
|
};
|
||||||
|
$command eq "presentation" and do {
|
||||||
|
sendClientMessage($hash, cmd => C_INTERNAL, ack => 0, subType => I_PRESENTATION);
|
||||||
|
last;
|
||||||
|
};
|
||||||
|
$command eq "RSSI" and do {
|
||||||
|
$hash->{I_RSSI} = 1;
|
||||||
|
sendClientMessage($hash, cmd => C_INTERNAL, subType => I_SIGNAL_REPORT_REQUEST, ack => 0, payload => "R");
|
||||||
|
last;
|
||||||
|
};
|
||||||
|
$command eq "Extended_DEBUG" and do {
|
||||||
|
$hash->{I_DEBUG} = 1;
|
||||||
|
sendClientMessage($hash, cmd => C_INTERNAL, subType => I_DEBUG, ack => 0, payload => "F");
|
||||||
|
last;
|
||||||
|
};
|
||||||
|
$command eq "ReadingsFromComment" and do {
|
||||||
|
$hash->{getCommentReadings} = 1;
|
||||||
|
sendClientMessage($hash, cmd => C_INTERNAL, subType => I_PRESENTATION, ack => 0);
|
||||||
|
last;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return undef;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub onStreamMessage($$) {
|
||||||
|
my ($hash, $msg) = @_;
|
||||||
|
my $name = $hash->{NAME};
|
||||||
|
my $type = $msg->{subType};
|
||||||
|
#my $typeStr = datastreamTypeToStr($type);
|
||||||
|
my $blType = AttrVal($name, "OTA_BL_Type", "");
|
||||||
|
my $blVersion = hex(substr($msg->{payload}, 16, 2)) . "." . hex(substr($msg->{payload}, 18, 2));
|
||||||
|
my $fwType = hex2Short(substr($msg->{payload}, 0, 4));
|
||||||
|
|
||||||
|
TYPE_HANDLER: {
|
||||||
|
$type == ST_FIRMWARE_CONFIG_REQUEST and do {
|
||||||
|
if (length($msg->{payload}) == 20) {
|
||||||
|
readingsBeginUpdate($hash);
|
||||||
|
readingsBulkUpdate($hash, 'FW_TYPE', $fwType) if ($blType eq "Optiboot");
|
||||||
|
readingsBulkUpdate($hash, 'FW_VERSION', hex2Short(substr($msg->{payload}, 4, 4))) if ($blType eq "Optiboot");
|
||||||
|
readingsBulkUpdate($hash, 'FW_BLOCKS', hex2Short(substr($msg->{payload}, 8, 4)));
|
||||||
|
readingsBulkUpdate($hash, 'FW_CRC', hex2Short(substr($msg->{payload}, 12, 4)));
|
||||||
|
readingsBulkUpdate($hash, 'BL_VERSION', $blVersion);
|
||||||
|
readingsEndUpdate($hash, 1);
|
||||||
|
Log3($name, 4, "$name: received ST_FIRMWARE_CONFIG_REQUEST");
|
||||||
|
if ((AttrVal($name, "OTA_autoUpdate", 0) == 1) && ($blVersion eq "3.0" or $blType eq "Optiboot")) {
|
||||||
|
Log3($name, 4, "$name: Optiboot BL, Node set to OTA_autoUpdate => calling firmware update procedure");
|
||||||
|
flashFirmware($hash, $fwType);
|
||||||
|
} elsif ($blType eq "MYSBootloader" && $hash->{OTA_requested} == 1) {
|
||||||
|
Log3($name, 4, "$name: MYSBootloader asking for firmware update, calling firmware update procedure");
|
||||||
|
$fwType = ReadingsVal($name, "FW_TYPE", "unknown");
|
||||||
|
flashFirmware($hash, $fwType);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log3($name, 2, "$name: Failed to parse ST_FIRMWARE_CONFIG_REQUEST - expected payload length 32 but retrieved ".length($msg->{payload}));
|
||||||
|
}
|
||||||
|
last;
|
||||||
|
};
|
||||||
|
$type == ST_FIRMWARE_REQUEST and do {
|
||||||
|
last if ($msg->{ack});
|
||||||
|
if (length($msg->{payload}) == 12) {
|
||||||
|
my $version = hex2Short(substr($msg->{payload}, 4, 4));
|
||||||
|
my $block = hex2Short(substr($msg->{payload}, 8, 4));
|
||||||
|
my $fromIndex = $block * 16;
|
||||||
|
my $payload = $msg->{payload};
|
||||||
|
my @fwData = @{$hash->{FW_DATA}};
|
||||||
|
Log3($name, 5, "$name: Firmware block request $block (type $fwType, version $version)");
|
||||||
|
|
||||||
|
for (my $index = $fromIndex; $index < $fromIndex + 16; $index++) {
|
||||||
|
$payload = $payload . sprintf("%02X", $fwData[$index]);
|
||||||
|
}
|
||||||
|
if (defined $hash->{OTA_Chan76_IODev}) {
|
||||||
|
sendMessage($hash->{OTA_Chan76_IODev}, radioId => $hash->{radioId}, childId => 255, ack => 0, cmd => C_STREAM, subType => ST_FIRMWARE_RESPONSE, payload => $payload);
|
||||||
|
} else {
|
||||||
|
sendClientMessage($hash, childId => 255, cmd => C_STREAM, ack => 0, subType => ST_FIRMWARE_RESPONSE, payload => $payload);
|
||||||
|
}
|
||||||
|
readingsSingleUpdate($hash, "state", "updating", 1) unless ($hash->{STATE} eq "updating");
|
||||||
|
readingsSingleUpdate($hash, "state", "update done", 1) if ($block == 0);
|
||||||
|
if ($block == 0 && $blType ne "Optiboot") {
|
||||||
|
readingsSingleUpdate($hash, 'FW_VERSION', $version, 1);
|
||||||
|
delete $hash->{OTA_requested} if (defined $hash->{OTA_requested});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log3($name, 2, "$name: Failed to parse ST_FIRMWARE_REQUEST - expected payload length 12 but retrieved ".length($msg->{payload}));
|
||||||
|
}
|
||||||
|
last;
|
||||||
|
};
|
||||||
|
$type == ST_SOUND and do {
|
||||||
|
#Approach for transfer sound and other stream data:
|
||||||
|
#https://forum.mysensors.org/topic/1668/sending-image-data-over-the-mysensors-network/11
|
||||||
|
#https://youtu.be/soaE5X6_aRk
|
||||||
|
last;
|
||||||
|
};
|
||||||
|
$type == ST_IMAGE and do {
|
||||||
|
#see remark to ST_Sound
|
||||||
|
last;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sub Attr($$$$) {
|
sub Attr($$$$) {
|
||||||
my ($command,$name,$attribute,$value) = @_;
|
my ($command,$name,$attribute,$value) = @_;
|
||||||
|
|
||||||
my $hash = $main::defs{$name};
|
my $hash = $main::defs{$name};
|
||||||
ATTRIBUTE_HANDLER: {
|
ATTRIBUTE_HANDLER: {
|
||||||
$attribute eq "config" and do {
|
$attribute eq "config" and do {
|
||||||
if ($main::init_done) {
|
if ($main::init_done) {
|
||||||
sendClientMessage($hash, cmd => C_INTERNAL, childId => 255, subType => I_CONFIG, payload => $command eq 'set' ? $value : "M");
|
sendClientMessage($hash, cmd => C_INTERNAL, childId => 255, ack => 0, subType => I_CONFIG, payload => $command eq 'set' ? $value : "M");
|
||||||
}
|
}
|
||||||
last;
|
last;
|
||||||
};
|
};
|
||||||
@ -406,6 +575,9 @@ sub Attr($$$$) {
|
|||||||
}
|
}
|
||||||
last;
|
last;
|
||||||
};
|
};
|
||||||
|
$attribute eq "OTA_autoUpdate" and do {
|
||||||
|
last;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -430,7 +602,8 @@ sub onPresentationMessage($$) {
|
|||||||
last;
|
last;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
CommandAttr(undef, "$name version $msg->{payload}");
|
$hash->{version} = $msg->{payload};
|
||||||
|
#CommandAttr(undef, "$name version $msg->{payload}");
|
||||||
};
|
};
|
||||||
|
|
||||||
my $readingMappings = $hash->{readingMappings};
|
my $readingMappings = $hash->{readingMappings};
|
||||||
@ -439,9 +612,25 @@ sub onPresentationMessage($$) {
|
|||||||
my $idStr = ($id > 0 ? $id : "");
|
my $idStr = ($id > 0 ? $id : "");
|
||||||
my @ret = ();
|
my @ret = ();
|
||||||
foreach my $type (@{$sensorMappings->{sends}}) {
|
foreach my $type (@{$sensorMappings->{sends}}) {
|
||||||
next if (defined $readingMappings->{$id}->{$type});
|
if (defined $readingMappings->{$id}->{$type}) {
|
||||||
|
next unless defined $hash->{getCommentReadings};
|
||||||
|
next unless $hash->{getCommentReadings} eq "2";
|
||||||
|
}
|
||||||
my $typeStr = $typeMappings->{$type}->{type};
|
my $typeStr = $typeMappings->{$type}->{type};
|
||||||
if ($hash->{IODev}->{'inclusion-mode'}) {
|
if ($hash->{IODev}->{'inclusion-mode'}) {
|
||||||
|
if ($msg->{payload} ne "" and $hash->{getCommentReadings} eq "2") {
|
||||||
|
$idStr = "_".$msg->{payload};
|
||||||
|
$idStr =~ s/\:/\./g; #replace illegal characters
|
||||||
|
$idStr =~ s/[^A-Za-z\d_\.-]+/_/g;
|
||||||
|
}
|
||||||
|
if (defined (my $mapping = $hash->{readingMappings}->{$id}->{$type})) {
|
||||||
|
if ($mapping->{name} ne "$typeStr$idStr" and $hash->{getCommentReadings} eq "2"and $msg->{payload} ne "") {
|
||||||
|
my $oldMappingName = $mapping->{name};
|
||||||
|
CommandDeleteAttr(undef, "$hash->{NAME} mapReading_$oldMappingName");
|
||||||
|
CommandDeleteReading(undef,"$hash->{NAME} $oldMappingName");
|
||||||
|
Log3 ($hash->{NAME}, 3, "MYSENSORS_DEVICE $hash->{NAME}: Deleted Reading $oldMappingName");
|
||||||
|
}
|
||||||
|
}
|
||||||
if (my $ret = CommandAttr(undef,"$name mapReading_$typeStr$idStr $id $typeStr")) {
|
if (my $ret = CommandAttr(undef,"$name mapReading_$typeStr$idStr $id $typeStr")) {
|
||||||
push @ret,$ret;
|
push @ret,$ret;
|
||||||
}
|
}
|
||||||
@ -452,7 +641,14 @@ sub onPresentationMessage($$) {
|
|||||||
foreach my $type (@{$sensorMappings->{receives}}) {
|
foreach my $type (@{$sensorMappings->{receives}}) {
|
||||||
my $typeMapping = $typeMappings->{$type};
|
my $typeMapping = $typeMappings->{$type};
|
||||||
my $typeStr = $typeMapping->{type};
|
my $typeStr = $typeMapping->{type};
|
||||||
next if (defined $hash->{sets}->{"$typeStr$idStr"});
|
if ($msg->{payload} ne "" and $hash->{getCommentReadings} eq "2") {
|
||||||
|
$idStr = "_".$msg->{payload};
|
||||||
|
$idStr =~ s/\:/\./g; #replace illegal characters
|
||||||
|
$idStr =~ s/[^A-Za-z\d_\.-]+/_/g;
|
||||||
|
}
|
||||||
|
if (defined $hash->{sets}->{"$typeStr$idStr"}) {
|
||||||
|
next unless $hash->{getCommentReadings} eq "2";
|
||||||
|
}
|
||||||
if ($hash->{IODev}->{'inclusion-mode'}) {
|
if ($hash->{IODev}->{'inclusion-mode'}) {
|
||||||
my @values = ();
|
my @values = ();
|
||||||
if ($typeMapping->{range}) {
|
if ($typeMapping->{range}) {
|
||||||
@ -462,7 +658,6 @@ sub onPresentationMessage($$) {
|
|||||||
}
|
}
|
||||||
if (my $ret = CommandAttr(undef,"$name setReading_$typeStr$idStr".(@values ? " ".join (",",@values) : ""))) {
|
if (my $ret = CommandAttr(undef,"$name setReading_$typeStr$idStr".(@values ? " ".join (",",@values) : ""))) {
|
||||||
push @ret,$ret;
|
push @ret,$ret;
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
push @ret,"no setReading for $id, $typeStr";
|
push @ret,"no setReading for $id, $typeStr";
|
||||||
}
|
}
|
||||||
@ -470,6 +665,7 @@ sub onPresentationMessage($$) {
|
|||||||
Log3 ($hash->{NAME}, 4, "MYSENSORS_DEVICE $hash->{NAME}: errors on C_PRESENTATION-message for childId $id, subType ".sensorTypeToStr($nodeType)." ".join (", ",@ret)) if @ret;
|
Log3 ($hash->{NAME}, 4, "MYSENSORS_DEVICE $hash->{NAME}: errors on C_PRESENTATION-message for childId $id, subType ".sensorTypeToStr($nodeType)." ".join (", ",@ret)) if @ret;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sub onSetMessage($$) {
|
sub onSetMessage($$) {
|
||||||
my ($hash,$msg) = @_;
|
my ($hash,$msg) = @_;
|
||||||
@ -480,7 +676,7 @@ sub onSetMessage($$) {
|
|||||||
readingsSingleUpdate($hash, $reading, $value, 1);
|
readingsSingleUpdate($hash, $reading, $value, 1);
|
||||||
};
|
};
|
||||||
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};
|
#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");
|
||||||
};
|
};
|
||||||
@ -497,7 +693,7 @@ sub onRequestMessage($$) {
|
|||||||
payload => ReadingsVal($hash->{NAME},$readingname,$val)
|
payload => ReadingsVal($hash->{NAME},$readingname,$val)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
refreshInternalMySTimer($hash,"Alive") if $hash->{timeoutAlive};
|
#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 $@;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -508,8 +704,11 @@ 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);
|
# readingsSingleUpdate($hash, "batterylevel", $msg->{payload}, 1);
|
||||||
Log3 ($name, 4, "MYSENSORS_DEVICE $name: batterylevel $msg->{payload}");
|
# 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);
|
||||||
|
refreshInternalMySTimer($hash,"Alive") if $hash->{timeoutAlive};
|
||||||
|
Log3 ($name, 4, "MYSENSORS_DEVICE $name: batteryPercent $msg->{payload}");
|
||||||
last;
|
last;
|
||||||
};
|
};
|
||||||
$type == I_TIME and do {
|
$type == I_TIME and do {
|
||||||
@ -542,7 +741,7 @@ sub onInternalMessage($$) {
|
|||||||
Log3 ($name, 4, "MYSENSORS_DEVICE $name: response to config-request acknowledged");
|
Log3 ($name, 4, "MYSENSORS_DEVICE $name: response to config-request acknowledged");
|
||||||
} else {
|
} else {
|
||||||
readingsSingleUpdate($hash, "parentId", $msg->{payload}, 1);
|
readingsSingleUpdate($hash, "parentId", $msg->{payload}, 1);
|
||||||
sendClientMessage($hash,cmd => C_INTERNAL, childId => 255, subType => I_CONFIG, payload => AttrVal($name,"config","M"));
|
sendClientMessage($hash,cmd => C_INTERNAL, ack => 0, childId => 255, subType => I_CONFIG, payload => AttrVal($name,"config","M"));
|
||||||
Log3 ($name, 4, "MYSENSORS_DEVICE $name: respond to config-request, node parentId = " . $msg->{payload});
|
Log3 ($name, 4, "MYSENSORS_DEVICE $name: respond to config-request, node parentId = " . $msg->{payload});
|
||||||
}
|
}
|
||||||
last;
|
last;
|
||||||
@ -565,17 +764,27 @@ sub onInternalMessage($$) {
|
|||||||
last;
|
last;
|
||||||
};
|
};
|
||||||
$type == I_SKETCH_NAME and do {
|
$type == I_SKETCH_NAME and do {
|
||||||
$hash->{$typeStr} = $msg->{payload};
|
#$hash->{$typeStr} = $msg->{payload};
|
||||||
|
readingsSingleUpdate($hash, "state", "received presentation", 1) unless ($hash->{STATE} eq "received presentation");
|
||||||
readingsSingleUpdate($hash, "SKETCH_NAME", $msg->{payload}, 1);
|
readingsSingleUpdate($hash, "SKETCH_NAME", $msg->{payload}, 1);
|
||||||
|
#undef $hash->{FW_DATA}; # enable this to free memory?
|
||||||
|
delete $hash->{FW_DATA} if (defined $hash->{FW_DATA});
|
||||||
|
if (defined $hash->{getCommentReadings}){
|
||||||
|
if ($hash->{getCommentReadings} eq "1") {
|
||||||
|
$hash->{getCommentReadings} = 2 ;
|
||||||
|
}elsif ($hash->{getCommentReadings} eq "2") {
|
||||||
|
delete $hash->{getCommentReadings};
|
||||||
|
}
|
||||||
|
}
|
||||||
last;
|
last;
|
||||||
};
|
};
|
||||||
$type == I_SKETCH_VERSION and do {
|
$type == I_SKETCH_VERSION and do {
|
||||||
$hash->{$typeStr} = $msg->{payload};
|
#$hash->{$typeStr} = $msg->{payload};
|
||||||
readingsSingleUpdate($hash, "SKETCH_VERSION", $msg->{payload}, 1);
|
readingsSingleUpdate($hash, "SKETCH_VERSION", $msg->{payload}, 1);
|
||||||
last;
|
last;
|
||||||
};
|
};
|
||||||
$type == I_REBOOT and do {
|
$type == I_REBOOT and do {
|
||||||
$hash->{$typeStr} = $msg->{payload};
|
#$hash->{$typeStr} = $msg->{payload};
|
||||||
last;
|
last;
|
||||||
};
|
};
|
||||||
$type == I_GATEWAY_READY and do {
|
$type == I_GATEWAY_READY and do {
|
||||||
@ -583,35 +792,161 @@ sub onInternalMessage($$) {
|
|||||||
last;
|
last;
|
||||||
};
|
};
|
||||||
$type == I_REQUEST_SIGNING and do {
|
$type == I_REQUEST_SIGNING and do {
|
||||||
$hash->{$typeStr} = $msg->{payload};
|
#$hash->{$typeStr} = $msg->{payload};
|
||||||
last;
|
last;
|
||||||
};
|
};
|
||||||
$type == I_GET_NONCE and do {
|
$type == I_GET_NONCE and do {
|
||||||
$hash->{$typeStr} = $msg->{payload};
|
#$hash->{$typeStr} = $msg->{payload};
|
||||||
last;
|
last;
|
||||||
};
|
};
|
||||||
$type == I_GET_NONCE_RESPONSE and do {
|
$type == I_GET_NONCE_RESPONSE and do {
|
||||||
|
#$hash->{$typeStr} = $msg->{payload};
|
||||||
|
last;
|
||||||
|
};
|
||||||
|
$type == I_HEARTBEAT_REQUEST and do {
|
||||||
|
#$hash->{$typeStr} = $msg->{payload};
|
||||||
|
refreshInternalMySTimer($hash,"Alive") if $hash->{timeoutAlive};
|
||||||
|
last;
|
||||||
|
};
|
||||||
|
$type == I_PRESENTATION and do {
|
||||||
|
#$hash->{$typeStr} = $msg->{payload};
|
||||||
|
last;
|
||||||
|
};
|
||||||
|
$type == I_DISCOVER_REQUEST and do {
|
||||||
|
#$hash->{$typeStr} = $msg->{payload};
|
||||||
|
last;
|
||||||
|
};
|
||||||
|
$type == I_DISCOVER_RESPONSE and do {
|
||||||
|
#$hash->{$typeStr} = $msg->{payload};
|
||||||
|
last;
|
||||||
|
};
|
||||||
|
$type == I_HEARTBEAT_RESPONSE and do {
|
||||||
|
readingsSingleUpdate($hash, "heartbeat", "last", 0);
|
||||||
|
refreshInternalMySTimer($hash,"Alive") if $hash->{timeoutAlive};
|
||||||
|
#$hash->{$typeStr} = $msg->{payload};
|
||||||
|
last;
|
||||||
|
};
|
||||||
|
$type == I_LOCKED and do {
|
||||||
$hash->{$typeStr} = $msg->{payload};
|
$hash->{$typeStr} = $msg->{payload};
|
||||||
last;
|
last;
|
||||||
};
|
};
|
||||||
|
$type == I_REGISTRATION_REQUEST and do {
|
||||||
|
$hash->{$typeStr} = $msg->{payload};
|
||||||
|
last;
|
||||||
|
};
|
||||||
|
$type == I_REGISTRATION_RESPONSE and do {
|
||||||
|
$hash->{$typeStr} = $msg->{payload};
|
||||||
|
last;
|
||||||
|
};
|
||||||
|
$type == I_DEBUG and do {
|
||||||
|
last if ($msg->{ack});
|
||||||
|
if ($hash->{I_DEBUG} == 1) {
|
||||||
|
readingsSingleUpdate($hash, "XDBG_CPU_FREQUENCY", $msg->{payload}, 1);
|
||||||
|
$hash->{I_DEBUG} = 2;
|
||||||
|
sendClientMessage($hash, childID => 255, cmd => C_INTERNAL, ack => 0, subType => I_DEBUG,payload => "V");
|
||||||
|
} elsif ($hash->{I_DEBUG} == 2) {
|
||||||
|
readingsSingleUpdate($hash, "XDBG_CPU_VOLTAGE", $msg->{payload}, 1);
|
||||||
|
$hash->{I_DEBUG} = 3;
|
||||||
|
sendClientMessage($hash, childID => 255, cmd => C_INTERNAL, ack => 0, subType => I_DEBUG,payload => "M");
|
||||||
|
} elsif ($hash->{I_DEBUG} == 3) {
|
||||||
|
readingsSingleUpdate($hash, "XDBG_FREE_MEMORY", $msg->{payload}, 1);
|
||||||
|
delete $hash->{I_DEBUG}; # if defined $hash->{I_DEBUG};
|
||||||
|
}
|
||||||
|
last;
|
||||||
|
};
|
||||||
|
$type == I_SIGNAL_REPORT_REVERSE and do {
|
||||||
|
#$hash->{$typeStr} = $msg->{payload};
|
||||||
|
last;
|
||||||
|
};
|
||||||
|
$type == I_SIGNAL_REPORT_RESPONSE and do {
|
||||||
|
last if ($msg->{ack});
|
||||||
|
my $subSet = $hash->{I_RSSI};
|
||||||
|
if ($subSet == 1) {
|
||||||
|
readingsSingleUpdate($hash, "R_RSSI_to_Parent", $msg->{payload}, 1) if ($msg->{payload} ne "-256" );
|
||||||
|
$hash->{I_RSSI} = 2; #$subSet;
|
||||||
|
sendClientMessage($hash, cmd => C_INTERNAL, ack => 0, subType => I_SIGNAL_REPORT_REQUEST, payload => "R!");
|
||||||
|
} elsif ($subSet == 2) {
|
||||||
|
readingsSingleUpdate($hash, "R_RSSI_from_Parent", $msg->{payload}, 1) if ($msg->{payload} ne "-256" );
|
||||||
|
$hash->{I_RSSI} = 3;
|
||||||
|
sendClientMessage($hash, cmd => C_INTERNAL, ack => 0, subType => I_SIGNAL_REPORT_REQUEST, payload => "S");
|
||||||
|
} elsif ($subSet == 3) {
|
||||||
|
readingsSingleUpdate($hash, "R_SNR_to_Parent", $msg->{payload}, 1) if ($msg->{payload} ne "-256" );
|
||||||
|
$hash->{I_RSSI} = 4;
|
||||||
|
sendClientMessage($hash, cmd => C_INTERNAL, ack => 0, subType => I_SIGNAL_REPORT_REQUEST, payload => "S!");
|
||||||
|
} elsif ($subSet == 4) {
|
||||||
|
readingsSingleUpdate($hash, "R_SNR_from_Parent", $msg->{payload}, 1) if ($msg->{payload} ne "-256" );
|
||||||
|
$hash->{I_RSSI} = 5;
|
||||||
|
sendClientMessage($hash, cmd => C_INTERNAL, ack => 0, subType => I_SIGNAL_REPORT_REQUEST, payload => "P");
|
||||||
|
} elsif ($subSet == 5) {
|
||||||
|
readingsSingleUpdate($hash, "R_TX_Powerlevel_Pct", $msg->{payload}, 1) if ($msg->{payload} ne "-256" );
|
||||||
|
$hash->{I_RSSI} = 6;
|
||||||
|
sendClientMessage($hash, cmd => C_INTERNAL, ack => 0, subType => I_SIGNAL_REPORT_REQUEST, payload => "T");
|
||||||
|
} elsif ($subSet == 6) {
|
||||||
|
readingsSingleUpdate($hash, "R_TX_Powerlevel_dBm", $msg->{payload}, 1) if ($msg->{payload} ne "-256" );
|
||||||
|
$hash->{I_RSSI} = 7;
|
||||||
|
sendClientMessage($hash, cmd => C_INTERNAL, ack => 0, subType => I_SIGNAL_REPORT_REQUEST, payload => "U");
|
||||||
|
} elsif ($subSet == 7) {
|
||||||
|
readingsSingleUpdate($hash, "R_Uplink_Quality", $msg->{payload}, 1) if ($msg->{payload} ne "-256" );
|
||||||
|
delete $hash->{I_RSSI};
|
||||||
|
}
|
||||||
|
last;
|
||||||
|
};
|
||||||
|
$type == I_PRE_SLEEP_NOTIFICATION and do {
|
||||||
|
#$hash->{$typeStr} = $msg->{payload};
|
||||||
|
refreshInternalMySTimer($hash,"Asleep");
|
||||||
|
refreshInternalMySTimer($hash,"Alive") if $hash->{timeoutAlive};
|
||||||
|
#here we send out retained and outstanding messages
|
||||||
|
MYSENSORS::Timer($hash);
|
||||||
|
my $retainedMsg;
|
||||||
|
while (ref ($retainedMsg = shift @{$hash->{retainedMessagesForRadioId}->{messages}}) eq 'HASH') {
|
||||||
|
sendClientMessage($hash,%$retainedMsg);
|
||||||
|
};
|
||||||
|
last;
|
||||||
|
};
|
||||||
|
$type == I_POST_SLEEP_NOTIFICATION and do {
|
||||||
|
#$hash->{$typeStr} = $msg->{payload};
|
||||||
|
readingsSingleUpdate($hash,"state","awake",1) unless ($hash->{STATE} eq "NACK");
|
||||||
|
$hash->{nowSleeping} = 0;
|
||||||
|
refreshInternalMySTimer($hash,"Alive") if $hash->{timeoutAlive};
|
||||||
|
last;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub sendClientMessage($%) {
|
sub sendClientMessage($%) {
|
||||||
my ($hash,%msg) = @_;
|
my ($hash,%msg) = @_;
|
||||||
$msg{radioId} = $hash->{radioId};
|
$msg{radioId} = $hash->{radioId};
|
||||||
|
my $name = $hash->{NAME};
|
||||||
$msg{ack} = $hash->{ack} unless defined $msg{ack};
|
$msg{ack} = $hash->{ack} unless defined $msg{ack};
|
||||||
|
my $messages = $hash->{retainedMessagesForRadioId}->{messages};
|
||||||
|
unless ($hash->{nowSleeping}) {
|
||||||
sendMessage($hash->{IODev},%msg);
|
sendMessage($hash->{IODev},%msg);
|
||||||
refreshInternalMySTimer($hash,"Ack") if (($hash->{ack} or $hash->{IODev}->{ack}) and $hash->{timeoutAck}) ;
|
refreshInternalMySTimer($hash,"Ack") if (($msg{ack} or $hash->{IODev}->{ack}) and $hash->{timeoutAck});
|
||||||
|
Log3 ($name,5,"$name is not sleeping, sending message!");
|
||||||
|
$hash->{retainedMessages}=scalar(@$messages) if (defined $hash->{retainedMessages});
|
||||||
|
} else {
|
||||||
|
Log3 ($name,5,"$name is sleeping, enqueueing message! ");
|
||||||
|
#write to queue if node is asleep
|
||||||
|
unless (defined $hash->{retainedMessages}) {
|
||||||
|
$messages = {messages => [%msg]};
|
||||||
|
$hash->{retainedMessages}=1;
|
||||||
|
Log3 ($name,5,"$name: No array yet for enqueued messages, building it!");
|
||||||
|
} else {
|
||||||
|
#my $referencetype = ref $messages;
|
||||||
|
#Log3 ($name,4,"$name: Reference type is $referencetype.");
|
||||||
|
@$messages = grep {
|
||||||
|
$_->{childId} != $msg{childId}
|
||||||
|
or $_->{cmd} != $msg{cmd}
|
||||||
|
or $_->{subType} != $msg{subType}
|
||||||
|
} @$messages;
|
||||||
|
push @$messages,\%msg;
|
||||||
|
eval($hash->{retainedMessages}=scalar(@$messages));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub onStreamMessage($$) {
|
|
||||||
my ($hash, $msg) = @_;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sub rawToMappedReading($$$$) {
|
sub rawToMappedReading($$$$) {
|
||||||
my($hash, $type, $childId, $value) = @_;
|
my($hash, $type, $childId, $value) = @_;
|
||||||
|
|
||||||
my $name;
|
my $name;
|
||||||
if (defined (my $mapping = $hash->{readingMappings}->{$childId}->{$type})) {
|
if (defined (my $mapping = $hash->{readingMappings}->{$childId}->{$type})) {
|
||||||
my $val = $mapping->{val} // $hash->{typeMappings}->{$type}->{val};
|
my $val = $mapping->{val} // $hash->{typeMappings}->{$type}->{val};
|
||||||
@ -622,7 +957,6 @@ sub rawToMappedReading($$$$) {
|
|||||||
|
|
||||||
sub mappedReadingToRaw($$$) {
|
sub mappedReadingToRaw($$$) {
|
||||||
my ($hash,$reading,$value) = @_;
|
my ($hash,$reading,$value) = @_;
|
||||||
|
|
||||||
my $readingsMapping = $hash->{readingMappings};
|
my $readingsMapping = $hash->{readingMappings};
|
||||||
foreach my $id (keys %$readingsMapping) {
|
foreach my $id (keys %$readingsMapping) {
|
||||||
my $readingTypesForId = $readingsMapping->{$id};
|
my $readingTypesForId = $readingsMapping->{$id};
|
||||||
@ -640,6 +974,104 @@ sub mappedReadingToRaw($$$) {
|
|||||||
die "no mapping for reading $reading";
|
die "no mapping for reading $reading";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub short2Hex($) {
|
||||||
|
my ($val) = @_;
|
||||||
|
my $temp = sprintf("%04X", $val);
|
||||||
|
return substr($temp, 2, 2) . substr($temp, 0, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub hex2Short($) {
|
||||||
|
my ($val) = @_;
|
||||||
|
return hex(substr($val, 2, 2) . substr($val, 0, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
sub flashFirmware($$) {
|
||||||
|
my ($hash, $fwType) = @_;
|
||||||
|
my $name = $hash->{NAME};
|
||||||
|
my ($version, $filename, $firmwarename) = getLatestFirmware($hash->{IODev}, $fwType);
|
||||||
|
if (not defined $filename) {
|
||||||
|
Log3 ($name,3,"No firmware defined for type $fwType - not flashing!");
|
||||||
|
return "No firmware defined for type " . $fwType ;
|
||||||
|
}
|
||||||
|
my ($err, @lines) = FileRead({FileName => "./FHEM/firmware/" . $filename, ForceType => "file"});
|
||||||
|
if (defined($err) && $err) {
|
||||||
|
Log3 ($name,3,"Could not read firmware file - $err: not flashing!");
|
||||||
|
return "Could not read firmware file - $err";
|
||||||
|
} else {
|
||||||
|
my $start = 0;
|
||||||
|
my $end = 0;
|
||||||
|
my @fwdata = ();
|
||||||
|
readingsSingleUpdate($hash, "state", "updating", 1) unless ($hash->{STATE} eq "updating");
|
||||||
|
for (my $i = 0; $i < @lines ; $i++) {
|
||||||
|
chomp(my $row = $lines[$i]);
|
||||||
|
if (length($row) > 0) {
|
||||||
|
$row =~ s/^:+//;
|
||||||
|
my $reclen = hex(substr($row, 0, 2));
|
||||||
|
my $offset = hex(substr($row, 2, 4));
|
||||||
|
my $rectype = hex(substr($row, 6, 2));
|
||||||
|
my $data = substr($row, 8, 2 * $reclen);
|
||||||
|
if ($rectype == 0) {
|
||||||
|
if (($start == 0) && ($end == 0)) {
|
||||||
|
if ($offset % 128 > 0) {
|
||||||
|
Log3 ($name,3,"error loading hex file - offset can't be devided by 128");
|
||||||
|
return "error loading hex file - offset can't be devided by 128" ;
|
||||||
|
}
|
||||||
|
$start = $offset;
|
||||||
|
$end = $offset;
|
||||||
|
}
|
||||||
|
if ($offset < $end) {
|
||||||
|
Log3 ($name,3,"error loading hex file - offset can't be devided by 128");
|
||||||
|
return "error loading hex file - offset lower than end" ;
|
||||||
|
}
|
||||||
|
while ($offset > $end) {
|
||||||
|
push(@fwdata, 255);
|
||||||
|
$end++;
|
||||||
|
}
|
||||||
|
for (my $i = 0; $i < $reclen; $i++) {
|
||||||
|
push(@fwdata, hex(substr($data, $i * 2, 2)));
|
||||||
|
}
|
||||||
|
$end += $reclen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
my $pad = $end % 128; # ATMega328 has 64 words per page / 128 bytes per page
|
||||||
|
for (my $i = 0; $i < 128 - $pad; $i++) {
|
||||||
|
push(@fwdata, 255);
|
||||||
|
$end++;
|
||||||
|
}
|
||||||
|
my $blocks = ($end - $start) / 16;
|
||||||
|
my $crc = 0xFFFF;
|
||||||
|
for (my $index = 0; $index < @fwdata; ++$index) {
|
||||||
|
$crc ^= $fwdata[$index] & 0xFF;
|
||||||
|
for (my $bit = 0; $bit < 8; ++$bit) {
|
||||||
|
if (($crc & 0x01) == 0x01) {
|
||||||
|
$crc = (($crc >> 1) ^ 0xA001);
|
||||||
|
} else {
|
||||||
|
$crc = ($crc >> 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (($version != ReadingsNum($name, "FW_VERSION", -1)) || ($blocks != ReadingsNum($name, "FW_BLOCKS", -1)) || ($crc != ReadingsNum($name, "FW_CRC", -1))) {
|
||||||
|
Log3($name, 4, "$name: Flashing './FHEM/firmware/" . $filename . "'");
|
||||||
|
$hash->{FW_DATA} = \@fwdata;
|
||||||
|
my $payload = short2Hex($fwType) . short2Hex($version) . short2Hex($blocks) . short2Hex($crc);
|
||||||
|
if (defined $hash->{OTA_Chan76_IODev}) {
|
||||||
|
sendMessage($hash->{OTA_Chan76_IODev}, radioId => $hash->{radioId}, childId => 255, ack => 0, cmd => C_STREAM, subType => ST_FIRMWARE_CONFIG_RESPONSE, payload => $payload);
|
||||||
|
Log3 ($name,5,"Directly send firmware info to $name using OTA_Chan76_IODev");
|
||||||
|
} elsif (AttrVal($name, "OTA_BL_Type", "") eq "MYSBootloader") {
|
||||||
|
sendMessage($hash->{IODev}, radioId => $hash->{radioId}, childId => 255, ack => 0, cmd => C_STREAM, subType => ST_FIRMWARE_CONFIG_RESPONSE, payload => $payload);
|
||||||
|
Log3 ($name,5,"Directly send firmware info to $name using regular IODev");
|
||||||
|
} else {
|
||||||
|
sendClientMessage($hash, childId => 255, cmd => C_STREAM, ack => 0, subType => ST_FIRMWARE_CONFIG_RESPONSE, payload => $payload);
|
||||||
|
Log3 ($name,5,"Send firmware info to $name using sendClientMessage");
|
||||||
|
}
|
||||||
|
return undef;
|
||||||
|
} else {
|
||||||
|
return "Nothing todo - latest firmware already installed";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sub refreshInternalMySTimer($$) {
|
sub refreshInternalMySTimer($$) {
|
||||||
my ($hash,$calltype) = @_;
|
my ($hash,$calltype) = @_;
|
||||||
my $name = $hash->{NAME};
|
my $name = $hash->{NAME};
|
||||||
@ -657,6 +1089,12 @@ sub refreshInternalMySTimer($$) {
|
|||||||
my $nextTrigger = main::gettimeofday() + $hash->{timeoutAck};
|
my $nextTrigger = main::gettimeofday() + $hash->{timeoutAck};
|
||||||
InternalTimer($nextTrigger, "MYSENSORS::DEVICE::timeoutMySTimer", "timeoutAck:$name", 0);
|
InternalTimer($nextTrigger, "MYSENSORS::DEVICE::timeoutMySTimer", "timeoutAck:$name", 0);
|
||||||
Log3 $name, 4, "$name: Ack timeout timer set at $nextTrigger";
|
Log3 $name, 4, "$name: Ack timeout timer set at $nextTrigger";
|
||||||
|
} elsif ($calltype eq "Asleep") {
|
||||||
|
RemoveInternalTimer("timeoutAwake:$name");
|
||||||
|
#0.5 is default; could be dynamized by attribute if needed
|
||||||
|
my $nextTrigger = main::gettimeofday() + 0.3;
|
||||||
|
InternalTimer($nextTrigger, "MYSENSORS::DEVICE::timeoutMySTimer", "timeoutAwake:$name", 0);
|
||||||
|
Log3 $name, 5, "$name: Awake timeout timer set at $nextTrigger";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -678,6 +1116,9 @@ sub timeoutMySTimer($) {
|
|||||||
Log3 $name, 4, "$name: timeoutMySTimer called ($calltype), no outstanding Acks for Node";
|
Log3 $name, 4, "$name: timeoutMySTimer called ($calltype), no outstanding Acks for Node";
|
||||||
readingsSingleUpdate($hash,"state","alive",1) if ($hash->{STATE} eq "NACK");
|
readingsSingleUpdate($hash,"state","alive",1) if ($hash->{STATE} eq "NACK");
|
||||||
}
|
}
|
||||||
|
} elsif ($calltype eq "timeoutAwake") {
|
||||||
|
readingsSingleUpdate($hash,"state","asleep",1) unless ($hash->{STATE} eq "NACK");
|
||||||
|
$hash->{nowSleeping} = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -704,7 +1145,19 @@ sub timeoutMySTimer($) {
|
|||||||
<p><b>Set</b></p>
|
<p><b>Set</b></p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<p><code>set <name> clear</code><br/>clears routing-table of a repeater-node</p>
|
<p><code>set <name> clear</code><br/>clears MySensors EEPROM area and reboot (i.e. "factory" reset) - requires MY_SPECIAL_DEBUG</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p><code>set <name> flash</code><br/>
|
||||||
|
Checks whether a newer firmware version is available. If a newer firmware version is
|
||||||
|
available the flash procedure is started. The sensor node must support FOTA for
|
||||||
|
this.</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p><code>set <name> fwType <value></code><br/>
|
||||||
|
assigns a firmware type to this node (must be a numeric value in the range 0 .. 65536).
|
||||||
|
Should be contained in the <a href="#MYSENSORSattrOTA_firmwareConfig">FOTA configuration
|
||||||
|
file</a>.</p>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<p><code>set <name> time</code><br/>sets time for nodes (that support it)</p>
|
<p><code>set <name> time</code><br/>sets time for nodes (that support it)</p>
|
||||||
@ -712,6 +1165,31 @@ sub timeoutMySTimer($) {
|
|||||||
<li>
|
<li>
|
||||||
<p><code>set <name> reboot</code><br/>reboots a node (requires a bootloader that supports it).<br/>Attention: Nodes that run the standard arduino-bootloader will enter a bootloop!<br/>Dis- and reconnect the nodes power to restart in this case.</p>
|
<p><code>set <name> reboot</code><br/>reboots a node (requires a bootloader that supports it).<br/>Attention: Nodes that run the standard arduino-bootloader will enter a bootloop!<br/>Dis- and reconnect the nodes power to restart in this case.</p>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
<a name="MYSENSORS_DEVICEget"></a>
|
||||||
|
<p><b>Get</b></p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p><code>get <name> Extended_DEBUG</code><br/>
|
||||||
|
requires MY_SPECIAL_DEBUG</p>
|
||||||
|
retrieves the CPU frequency, CPU voltage and free memory of the sensor</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p><code>get <name> ReadingsFromComment</code><br/>
|
||||||
|
rebuild reding names from comments of presentation messages if available</p>
|
||||||
|
After issuing this get check the log for changes. </p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p><code>get <name> RSSI</code><br/>
|
||||||
|
requires MY_SIGNAL_REPORT_ENABLED, not supported by all transportation layers</p>
|
||||||
|
delievers a set of Signal Quality information.</p>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<a name="MYSENSORS_DEVICEattr"></a>
|
<a name="MYSENSORS_DEVICEattr"></a>
|
||||||
<p><b>Attributes</b></p>
|
<p><b>Attributes</b></p>
|
||||||
@ -719,6 +1197,11 @@ sub timeoutMySTimer($) {
|
|||||||
<li>
|
<li>
|
||||||
<p><code>attr <name> config [<M|I>]</code><br/>configures metric (M) or inch (I). Defaults to 'M'</p>
|
<p><code>attr <name> config [<M|I>]</code><br/>configures metric (M) or inch (I). Defaults to 'M'</p>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<p><code>attr <name> OTA_autoUpdate [<0|1>]</code><br/>
|
||||||
|
specifies whether an automatic update of the sensor node should be performed (1) during startup of the
|
||||||
|
node or not (0). Defaults to 0</p>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<p><code>attr <name> setCommands [<command:reading:value>]*</code><br/>configures one or more commands that can be executed by set.<br/>e.g.: <code>attr <name> setCommands on:switch_1:on off:switch_1:off</code><br/>if list of commands contains both 'on' and 'off' <a href="#setExtensions">set extensions</a> are supported</p>
|
<p><code>attr <name> setCommands [<command:reading:value>]*</code><br/>configures one or more commands that can be executed by set.<br/>e.g.: <code>attr <name> setCommands on:switch_1:on off:switch_1:off</code><br/>if list of commands contains both 'on' and 'off' <a href="#setExtensions">set extensions</a> are supported</p>
|
||||||
</li>
|
</li>
|
||||||
@ -726,14 +1209,21 @@ sub timeoutMySTimer($) {
|
|||||||
<p><code>attr <name> setReading_<reading> [<value>]*</code><br/>configures a reading that can be modified by set-command<br/>e.g.: <code>attr <name> setReading_switch_1 on,off</code></p>
|
<p><code>attr <name> setReading_<reading> [<value>]*</code><br/>configures a reading that can be modified by set-command<br/>e.g.: <code>attr <name> setReading_switch_1 on,off</code></p>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<p><code>attr <name> mapReading_<reading> <childId> <readingtype> [<value>:<mappedvalue>]*</code><br/>configures the reading-name for a given childId and sensortype<br/>e.g.: <code>attr xxx mapReading_aussentemperatur 123 temperature</code></p>
|
<p><code>attr <name> mapReading_<reading> <childId> <readingtype> [<value>:<mappedvalue>]*</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>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<p><code>att <name> requestAck</code><br/>request acknowledge from nodes.<br/>if set the Readings of nodes are updated not before requested acknowledge is received<br/>if not set the Readings of nodes are updated immediatly (not awaiting the acknowledge).<br/>May also be configured on the gateway for all nodes at once</p>
|
<p><code>attr <name> requestAck</code><br/>request acknowledge from nodes.<br/>if set the Readings of nodes are updated not before requested acknowledge is received<br/>if not set the Readings of nodes are updated immediatly (not awaiting the acknowledge).<br/>May also be configured on the gateway for all nodes at once</p>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<p><code>attr <name> mapReadingType_<reading> <new reading name> [<value>:<mappedvalue>]*</code><br/>configures reading type names that should be used instead of technical names<br/>e.g.: <code>attr xxx mapReadingType_LIGHT switch 0:on 1:off</code>to be used for mysensor Variabletypes that have no predefined defaults (yet)</p>
|
<p><code>attr <name> mapReadingType_<reading> <new reading name> [<value>:<mappedvalue>]*</code><br/>configures reading type names that should be used instead of technical names<br/>e.g.: <code>attr xxx mapReadingType_LIGHT switch 0:on 1:off</code>to be used for mysensor Variabletypes that have no predefined defaults (yet)</p>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<p><code>attr <name> OTA_BL_Type <either Optiboot or MYSBootloader>*</code><br/>For other bootloaders than Optiboot V3.0 OTA updates will only work if bootloader type is specified - MYSBootloader will reboot node if firmware update is started, so make sure, node will really recover</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p><code>attr <name> OTA_Chan76_IODev </code><br/>As MYSBootloader per default uses nRF24 channel 76, you may specify a different IODev for OTA data using channel 76</p>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<p><code>attr <name> timeoutAck <time in seconds>*</code><br/>configures timeout to set device state to NACK in case not all requested acks are received</p>
|
<p><code>attr <name> timeoutAck <time in seconds>*</code><br/>configures timeout to set device state to NACK in case not all requested acks are received</p>
|
||||||
</li>
|
</li>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user