diff --git a/fhem/CHANGED b/fhem/CHANGED
index 01d847b63..add7992b2 100644
--- a/fhem/CHANGED
+++ b/fhem/CHANGED
@@ -1,5 +1,7 @@
# 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.
+ - added: MYSENSORS: connect to serial or Ethernet MySensors Gateway
+ - added: MYSENSORS_DEVICE: represent a MySensors sensor- or actor node
- feature: global ATTR/DELETEATTR/MODIFIED events
- feature: 55_GDS.pm - attr disable added
- bugfix: SYSMON: prevent endless loop at startup with 'disable' attribute
diff --git a/fhem/FHEM/00_MYSENSORS.pm b/fhem/FHEM/00_MYSENSORS.pm
new file mode 100644
index 000000000..bedf87885
--- /dev/null
+++ b/fhem/FHEM/00_MYSENSORS.pm
@@ -0,0 +1,451 @@
+##############################################
+#
+# fhem driver for MySensors serial or network gateway (see http://mysensors.org)
+#
+# Copyright (C) 2014 Norbert Truchsess
+#
+# This file is part of fhem.
+#
+# Fhem is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# Fhem is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with fhem. If not, see .
+#
+# $Id$
+#
+##############################################
+
+my %sets = (
+ "connect" => [],
+ "disconnect" => [],
+ "inclusion-mode" => [qw(on off)],
+);
+
+my %gets = (
+ "version" => ""
+);
+
+my @clients = qw(
+ MYSENSORS_DEVICE
+);
+
+sub MYSENSORS_Initialize($) {
+
+ my $hash = shift @_;
+
+ require "$main::attr{global}{modpath}/FHEM/DevIo.pm";
+
+ # Provider
+ $hash->{Clients} = join (':',@clients);
+ $hash->{ReadyFn} = "MYSENSORS::Ready";
+ $hash->{ReadFn} = "MYSENSORS::Read";
+
+ # Consumer
+ $hash->{DefFn} = "MYSENSORS::Define";
+ $hash->{UndefFn} = "MYSENSORS::Undef";
+ $hash->{SetFn} = "MYSENSORS::Set";
+ $hash->{AttrFn} = "MYSENSORS::Attr";
+ $hash->{NotifyFn} = "MYSENSORS::Notify";
+
+ $hash->{AttrList} =
+ "autocreate:1 ".
+ "requestAck:1 ".
+ "first-sensorid ".
+ "stateFormat";
+}
+
+package MYSENSORS;
+
+use Exporter ('import');
+@EXPORT = ();
+@EXPORT_OK = qw(sendMessage);
+%EXPORT_TAGS = (all => [@EXPORT_OK]);
+
+use strict;
+use warnings;
+
+use GPUtils qw(:all);
+
+use Device::MySensors::Constants qw(:all);
+use Device::MySensors::Message qw(:all);
+
+BEGIN {GP_Import(qw(
+ CommandDefine
+ CommandModify
+ CommandAttr
+ gettimeofday
+ readingsSingleUpdate
+ DevIo_OpenDev
+ DevIo_SimpleWrite
+ DevIo_SimpleRead
+ DevIo_CloseDev
+ RemoveInternalTimer
+ InternalTimer
+ AttrVal
+ Log3
+ ))};
+
+my %sensorAttr = (
+ LIGHT => ['setCommands on:V_LIGHT:1 off:V_LIGHT:0' ],
+ ARDUINO_NODE => [ 'config M' ],
+ ARDUINO_REPEATER_NODE => [ 'config M' ],
+);
+
+sub Define($$) {
+ my ( $hash, $def ) = @_;
+
+ $hash->{NOTIFYDEV} = "global";
+
+ if ($main::init_done) {
+ return Start($hash);
+ } else {
+ return undef;
+ }
+}
+
+sub Undef($) {
+ Stop(shift);
+}
+
+sub Set($@) {
+ my ($hash, @a) = @_;
+ return "Need at least one parameters" if(@a < 2);
+ return "Unknown argument $a[1], choose one of " . join(" ", map {@{$sets{$_}} ? $_.':'.join ',', @{$sets{$_}} : $_} sort keys %sets)
+ if(!defined($sets{$a[1]}));
+ my $command = $a[1];
+ my $value = $a[2];
+
+ COMMAND_HANDLER: {
+ $command eq "connect" and do {
+ Start($hash);
+ last;
+ };
+ $command eq "disconnect" and do {
+ Stop($hash);
+ last;
+ };
+ $command eq "inclusion-mode" and do {
+ sendMessage($hash,radioId => 0, childId => 0, cmd => C_INTERNAL, ack => 0, subType => I_INCLUSION_MODE, payload => $value eq 'on' ? 1 : 0);
+ $hash->{'inclusion-mode'} = $value eq 'on' ? 1 : 0;
+ last;
+ };
+ };
+}
+
+sub Attr($$$$) {
+ my ($command,$name,$attribute,$value) = @_;
+
+ my $hash = $main::defs{$name};
+ ATTRIBUTE_HANDLER: {
+ $attribute eq "autocreate" and do {
+ if ($main::init_done) {
+ my $mode = $command eq "set" ? 1 : 0;
+ sendMessage($hash,radioId => $hash->{radioId}, childId => $hash->{childId}, ack => 0, subType => I_INCLUSION_MODE, payload => $mode);
+ $hash->{'inclusion-mode'} = $mode;
+ }
+ last;
+ };
+ $attribute eq "requestAck" and do {
+ if ($command eq "set") {
+ $hash->{ack} = 1;
+ } else {
+ $hash->{ack} = 0;
+ $hash->{messages} = {};
+ $hash->{outstandingAck} = 0;
+ }
+ last;
+ };
+ }
+}
+
+sub Notify($$) {
+ my ($hash,$dev) = @_;
+ if( grep(m/^(INITIALIZED|REREADCFG)$/, @{$dev->{CHANGED}}) ) {
+ Start($hash);
+ } elsif( grep(m/^SAVE$/, @{$dev->{CHANGED}}) ) {
+ }
+}
+
+sub Start($) {
+ my $hash = shift;
+ my ($dev) = split("[ \t]+", $hash->{DEF});
+ $hash->{DeviceName} = $dev;
+ CommandAttr(undef, "$hash->{NAME} stateFormat connection") unless AttrVal($hash->{NAME},"stateFormat",undef);
+ DevIo_CloseDev($hash);
+ return DevIo_OpenDev($hash, 0, "MYSENSORS::Init");
+}
+
+sub Stop($) {
+ my $hash = shift;
+ DevIo_CloseDev($hash);
+ RemoveInternalTimer($hash);
+ readingsSingleUpdate($hash,"connection","disconnected",1);
+}
+
+sub Ready($) {
+ my $hash = shift;
+ return DevIo_OpenDev($hash, 1, "MYSENSORS::Init") if($hash->{STATE} eq "disconnected");
+}
+
+sub Init($) {
+ my $hash = shift;
+ my $name = $hash->{NAME};
+ $hash->{'inclusion-mode'} = AttrVal($name,"autocreate",0);
+ $hash->{ack} = AttrVal($name,"requestAck",0);
+ $hash->{messages} = {};
+ $hash->{outstandingAck} = 0;
+ readingsSingleUpdate($hash,"connection","connected",1);
+ Timer($hash);
+ return undef;
+}
+
+sub Timer($) {
+ my $hash = shift;
+ RemoveInternalTimer($hash);
+ my $now = time;
+ foreach my $msg (keys %{$hash->{messages}}) {
+ if ($now > $hash->{messages}->{$msg}) {
+ Log3 ($hash->{NAME},5,"MYSENSORS outstanding ack, re-send: ".$msg);
+ DevIo_SimpleWrite($hash,"$msg\n", undef);
+ }
+ }
+ InternalTimer(gettimeofday()+1, "MYSENSORS::Timer", $hash, 0);
+}
+
+sub Read {
+ my ($hash) = @_;
+ my $name = $hash->{NAME};
+
+ my $buf = DevIo_SimpleRead($hash);
+ return "" if(!defined($buf));
+
+ my $data = $hash->{PARTIAL};
+ Log3 ($name, 5, "MYSENSORS/RAW: $data/$buf");
+ $data .= $buf;
+
+ while ($data =~ m/\n/) {
+ my $txt;
+ ($txt,$data) = split("\n", $data, 2);
+ $txt =~ s/\r//;
+ my $msg = parseMsg($txt);
+ Log3 ($name,5,"MYSENSORS Read: ".dumpMsg($msg));
+
+ if ($msg->{ack}) {
+ delete $hash->{messages}->{$txt};
+ $hash->{outstandingAck} = keys %{$hash->{messages}};
+ }
+
+ my $type = $msg->{cmd};
+ MESSAGE_TYPE: {
+ $type == C_PRESENTATION and do {
+ onPresentationMsg($hash,$msg);
+ last;
+ };
+ $type == C_SET and do {
+ onSetMsg($hash,$msg);
+ last;
+ };
+ $type == C_REQ and do {
+ onRequestMsg($hash,$msg);
+ last;
+ };
+ $type == C_INTERNAL and do {
+ onInternalMsg($hash,$msg);
+ last;
+ };
+ $type == C_STREAM and do {
+ onStreamMsg($hash,$msg);
+ last;
+ };
+ }
+ }
+ $hash->{PARTIAL} = $data;
+ return undef;
+};
+
+sub onPresentationMsg($$) {
+ my ($hash,$msg) = @_;
+ my $client = matchClient($hash,$msg);
+ my $clientname;
+ my $sensorType = $msg->{subType};
+ unless ($client) {
+ if ($hash->{'inclusion-mode'}) {
+ $clientname = "MYSENSOR_$msg->{radioId}";
+ CommandDefine(undef,"$clientname MYSENSORS_DEVICE $msg->{radioId}");
+ $client = $main::defs{$clientname};
+ return unless ($client);
+ } else {
+ Log3($hash->{NAME},3,"MYSENSORS: ignoring presentation-msg from unknown radioId $msg->{radioId}, childId $msg->{childId}, sensorType $sensorType");
+ return;
+ }
+ }
+ MYSENSORS::DEVICE::onPresentationMessage($client,$msg);
+};
+
+sub onSetMsg($$) {
+ my ($hash,$msg) = @_;
+ if (my $client = matchClient($hash,$msg)) {
+ MYSENSORS::DEVICE::onSetMessage($client,$msg);
+ } else {
+ Log3($hash->{NAME},3,"MYSENSORS: ignoring set-msg from unknown radioId $msg->{radioId}, childId $msg->{childId} for ".variableTypeToStr($msg->{subType}));
+ }
+};
+
+sub onRequestMsg($$) {
+ my ($hash,$msg) = @_;
+ if (my $client = matchClient($hash,$msg)) {
+ MYSENSORS::DEVICE::onRequestMessage($client,$msg);
+ } else {
+ Log3($hash->{NAME},3,"MYSENSORS: ignoring req-msg from unknown radioId $msg->{radioId}, childId $msg->{childId} for ".variableTypeToStr($msg->{subType}));
+ }
+};
+
+sub onInternalMsg($$) {
+ my ($hash,$msg) = @_;
+ my $address = $msg->{radioId};
+ my $type = $msg->{subType};
+ if ($address == 0 or $address == 255) { #msg to or from gateway
+ TYPE: {
+ $type == I_INCLUSION_MODE and do {
+ if (AttrVal($hash->{NAME},"autocreate",0)) { #if autocreate is switched on, keep gateways inclusion-mode active
+ if ($msg->{payload} == 0) {
+ sendMessage($hash,radioId => $msg->{radioId}, childId => $msg->{childId}, ack => 0, subType => I_INCLUSION_MODE, payload => 1);
+ }
+ } else {
+ $hash->{'inclusion-mode'} = $msg->{payload};
+ }
+ last;
+ };
+ $type == I_STARTUP_COMPLETE and do {
+ readingsSingleUpdate($hash,'connection','startup complete',1);
+ GP_ForallClients($hash,sub {
+ my $client = shift;
+ MYSENSORS::DEVICE::onGatewayStarted($client);
+ });
+ last;
+ };
+ $type == I_LOG_MESSAGE and do {
+ Log3($hash->{NAME},5,"MYSENSORS gateway $hash->{NAME}: $msg->{payload}");
+ last;
+ };
+ $type == I_ID_REQUEST and do {
+ if ($hash->{'inclusion-mode'}) {
+ my %nodes = map {$_ => 1} (AttrVal($hash->{NAME},"first-sensorid",20) ... 254);
+ GP_ForallClients($hash,sub {
+ my $client = shift;
+ delete $nodes{$client->{radioId}};
+ });
+ if (keys %nodes) {
+ my $newid = (keys %nodes)[0];
+ sendMessage($hash,radioId => 255, childId => 255, cmd => C_INTERNAL, ack => 0, subType => I_ID_RESPONSE, payload => $newid);
+ Log3($hash->{NAME},4,"MYSENSORS $hash->{NAME} assigned new nodeid $newid");
+ } else {
+ Log3($hash->{NAME},4,"MYSENSORS $hash->{NAME} cannot assign new nodeid");
+ }
+ } else {
+ Log3($hash->{NAME},4,"MYSENSORS: ignoring id-request-msg from unknown radioId $msg->{radioId}");
+ }
+ last;
+ };
+ }
+ } elsif (my $client = matchClient($hash,$msg)) {
+ MYSENSORS::DEVICE::onInternalMessage($client,$msg);
+ } else {
+ Log3($hash->{NAME},3,"MYSENSORS: ignoring internal-msg from unknown radioId $msg->{radioId}, childId $msg->{childId} for ".internalMessageTypeToStr($msg->{subType}));
+ }
+};
+
+sub onStreamMsg($$) {
+ my ($hash,$msg) = @_;
+};
+
+sub sendMessage($%) {
+ my ($hash,%msg) = @_;
+ $msg{ack} = $hash->{ack} unless $msg{ack};
+ my $txt = createMsg(%msg);
+ Log3 ($hash->{NAME},5,"MYSENSORS send: ".dumpMsg(\%msg));
+ DevIo_SimpleWrite($hash,"$txt\n",undef);
+ if ($msg{ack}) {
+ $hash->{messages}->{$txt} = gettimeofday() + 1,
+ $hash->{outstandingAck} = keys %{$hash->{messages}};
+ }
+};
+
+sub matchClient($$) {
+ my ($hash,$msg) = @_;
+ my $radioId = $msg->{radioId};
+ my $found;
+ GP_ForallClients($hash,sub {
+ return if $found;
+ my $client = shift;
+ if ($client->{radioId} == $radioId) {
+ $found = $client;
+ }
+ });
+ return $found;
+}
+
+1;
+
+=pod
+=begin html
+
+
+
MYSENSORS
+
+ connects fhem to MYSENSORS.
+ A single MYSENSORS device can serve multiple MYSENSORS_DEVICE clients.
+ Each MYSENSORS_DEVICE represents a mysensors node.
+
+
Define
+
+ define <name> MYSENSORS <serial device>|<ip:port>
+ Specifies the MYSENSORS device.
+
+
+ Set
+
+ -
+
set <name> connect
+ (re-)connects the MYSENSORS-device to the MYSENSORS-gateway
+
+ -
+
set <name> disconnect
+ disconnects the MYSENSORS-device from the MYSENSORS-gateway
+
+ -
+
set <name> inclusion-mode on|off
+ turns the gateways inclusion-mode on or off
+
+
+
+ Attributes
+
+ -
+
att <name> autocreate
+ enables auto-creation of MYSENSOR_DEVICE-devices on receival of presentation-messages
+
+ -
+
att <name> requestAck
+ request acknowledge from nodes.
+ if set the Readings of nodes are updated not before requested acknowledge is received
+ if not set the Readings of nodes are updated immediatly (not awaiting the acknowledge).
+ May also be configured for individual nodes if not set for gateway.
+
+ -
+
att <name> first-sensorid <<number <h; 255>>
+ configures the lowest node-id assigned to a mysensor-node on request (defaults to 20)
+
+
+
+
+=end html
+=cut
diff --git a/fhem/FHEM/10_MYSENSORS_DEVICE.pm b/fhem/FHEM/10_MYSENSORS_DEVICE.pm
new file mode 100644
index 000000000..3fb520cce
--- /dev/null
+++ b/fhem/FHEM/10_MYSENSORS_DEVICE.pm
@@ -0,0 +1,410 @@
+##############################################
+#
+# fhem bridge to MySensors (see http://mysensors.org)
+#
+# Copyright (C) 2014 Norbert Truchsess
+#
+# This file is part of fhem.
+#
+# Fhem is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# Fhem is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with fhem. If not, see .
+#
+# $Id$
+#
+##############################################
+
+use strict;
+use warnings;
+
+my %gets = (
+ "version" => "",
+);
+
+sub MYSENSORS_DEVICE_Initialize($) {
+
+ my $hash = shift @_;
+
+ # Consumer
+ $hash->{DefFn} = "MYSENSORS::DEVICE::Define";
+ $hash->{UndefFn} = "MYSENSORS::DEVICE::UnDefine";
+ $hash->{SetFn} = "MYSENSORS::DEVICE::Set";
+ $hash->{AttrFn} = "MYSENSORS::DEVICE::Attr";
+
+ $hash->{AttrList} =
+ "config:M,I ".
+ "setCommands ".
+ "setReading_.+_\\d+ ".
+ "mapReadingType_.+ ".
+ "requestAck:1 ".
+ "IODev ".
+ $main::readingFnAttributes;
+
+ main::LoadModule("MYSENSORS");
+}
+
+package MYSENSORS::DEVICE;
+
+use strict;
+use warnings;
+use GPUtils qw(:all);
+
+use Device::MySensors::Constants qw(:all);
+use Device::MySensors::Message qw(:all);
+
+BEGIN {
+ MYSENSORS->import(qw(:all));
+
+ GP_Import(qw(
+ AttrVal
+ readingsSingleUpdate
+ CommandDeleteReading
+ AssignIoPort
+ Log3
+ ))
+};
+
+my %static_mappings = (
+ V_TEMP => { type => "temperature" },
+ V_HUM => { type => "humidity" },
+ V_PRESSURE => { type => "pressure" },
+ V_LIGHT_LEVEL => { type => "brightness" },
+ V_LIGHT => { type => "switch", val => { 0 => 'off', 1 => 'on' }},
+);
+
+sub Define($$) {
+ my ( $hash, $def ) = @_;
+ my ($name, $type, $radioId) = split("[ \t]+", $def);
+ return "requires 1 parameters" unless (defined $radioId and $radioId ne "");
+ $hash->{radioId} = $radioId;
+ $hash->{sets} = {
+ 'time' => "",
+ clear => "",
+ reboot => "",
+ };
+ $hash->{ack} = 0;
+ $hash->{typeMappings} = {map {variableTypeToIdx($_) => $static_mappings{$_}} keys %static_mappings};
+ $hash->{readingMappings} = {};
+ AssignIoPort($hash);
+};
+
+sub UnDefine($) {
+ my ($hash) = @_;
+
+ return undef;
+}
+
+sub Set($@) {
+ my ($hash,$name,$command,@values) = @_;
+ return "Need at least one parameters" unless defined $command;
+ return "Unknown argument $command, choose one of " . join(" ", map {$hash->{sets}->{$_} ne "" ? "$_:$hash->{sets}->{$_}" : $_} sort keys %{$hash->{sets}})
+ if(!defined($hash->{sets}->{$command}));
+ COMMAND_HANDLER: {
+ $command eq "clear" and do {
+ sendClientMessage($hash, childId => 255, cmd => C_INTERNAL, subType => I_CHILDREN, payload => "C");
+ last;
+ };
+ $command eq "time" and do {
+ sendClientMessage($hash, childId => 255, cmd => C_INTERNAL, subType => I_TIME, payload => time);
+ last;
+ };
+ $command eq "reboot" and do {
+ sendClientMessage($hash, childId => 255, cmd => C_INTERNAL, subType => I_REBOOT);
+ last;
+ };
+ $command =~ /^(.+_\d+)$/ and do {
+ my $value = @values ? join " ",@values : "";
+ my ($type,$childId,$mappedValue) = readingToType($hash,$1,$value);
+ sendClientMessage($hash, childId => $childId, cmd => C_SET, subType => $type, payload => $mappedValue);
+ readingsSingleUpdate($hash,$command,$value,1) unless ($hash->{ack} or $hash->{IODev}->{ack});
+ last;
+ };
+ (defined ($hash->{setcommands}->{$command})) and do {
+ my $setcommand = $hash->{setcommands}->{$command};
+ my ($type,$childId,$mappedValue) = readingToType($hash,$setcommand->{var},$setcommand->{val});
+ sendClientMessage($hash,
+ childId => $childId,
+ cmd => C_SET,
+ subType => $type,
+ payload => $mappedValue,
+ );
+ readingsSingleUpdate($hash,$setcommand->{var},$setcommand->{val},1) unless ($hash->{ack} or $hash->{IODev}->{ack});
+ last;
+ };
+ return "$command not defined by attr setCommands";
+ }
+}
+
+sub Attr($$$$) {
+ my ($command,$name,$attribute,$value) = @_;
+
+ my $hash = $main::defs{$name};
+ ATTRIBUTE_HANDLER: {
+ $attribute eq "config" and do {
+ if ($main::init_done) {
+ sendClientMessage($hash, cmd => C_INTERNAL, subType => I_CONFIG, payload => $command eq 'set' ? $value : "M");
+ }
+ last;
+ };
+ $attribute eq "setCommands" and do {
+ if ($command eq "set") {
+ foreach my $setCmd (split ("[, \t]+",$value)) {
+ $setCmd =~ /^(.+):(.+_\d+):(.+)$/;
+ $hash->{sets}->{$1}="";
+ $hash->{setcommands}->{$1} = {
+ var => $2,
+ val => $3,
+ };
+ }
+ } else {
+ foreach my $set (keys %{$hash->{setcommands}}) {
+ delete $hash->{sets}->{$set};
+ }
+ $hash->{setcommands} = {};
+ }
+ last;
+ };
+ $attribute =~ /^setReading_(.+_\d+)$/ and do {
+ if ($command eq "set") {
+ $hash->{sets}->{$1}=join(",",split ("[, \t]+",$value));
+ } else {
+ CommandDeleteReading(undef,"$hash->{NAME} $1");
+ delete $hash->{sets}->{$1};
+ }
+ last;
+ };
+ $attribute =~ /^mapReadingType_(.+)/ and do {
+ my $type = variableTypeToIdx("V_$1");
+ if ($command eq "set") {
+ my @values = split ("[, \t]",$value);
+ $hash->{typeMappings}->{$type}={
+ type => shift @values,
+ val => {map {$_ =~ /^(.+):(.+)$/; $1 => $2} @values},
+ }
+ } else {
+ if ($static_mappings{"V_$1"}) {
+ $hash->{typeMappings}->{$type}=$static_mappings{"V_$1"};
+ } else {
+ delete $hash->{typeMappings}->{$type};
+ }
+ CommandDeleteReading(undef,"$hash->{NAME} $1"); #TODO do propper remap of existing readings
+ }
+ last;
+ };
+ $attribute eq "requestAck" and do {
+ if ($command eq "set") {
+ $hash->{ack} = 1;
+ } else {
+ $hash->{ack} = 0;
+ }
+ last;
+ };
+ }
+}
+
+sub onGatewayStarted($) {
+ my ($hash) = @_;
+}
+
+sub onPresentationMessage($$) {
+ my ($hash,$msg) = @_;
+}
+
+sub onSetMessage($$) {
+ my ($hash,$msg) = @_;
+ if (defined $msg->{payload}) {
+ my ($reading,$value) = mapReading($hash,$msg->{subType},$msg->{childId},$msg->{payload});
+ readingsSingleUpdate($hash,$reading,$value,1);
+ } else {
+ Log3 ($hash->{NAME},5,"MYSENSORS_DEVICE $hash->{NAME}: ignoring C_SET-message without payload");
+ }
+}
+
+sub onRequestMessage($$) {
+ my ($hash,$msg) = @_;
+ variableTypeToStr($msg->{subType}) =~ /^V_(.+)$/;
+ sendClientMessage($hash,
+ childId => $msg->{childId},
+ cmd => C_SET,
+ subType => $msg->{subType},
+ payload => ReadingsVal($hash->{NAME},"$1\_$msg->{childId}","")
+ );
+}
+
+sub onInternalMessage($$) {
+ my ($hash,$msg) = @_;
+ my $name = $hash->{NAME};
+ my $type = $msg->{subType};
+ my $typeStr = internalMessageTypeToStr($type);
+ INTERNALMESSAGE: {
+ $type == I_BATTERY_LEVEL and do {
+ readingsSingleUpdate($hash,"batterylevel",$msg->{payload},1);
+ Log3 ($name,4,"MYSENSORS_DEVICE $name: batterylevel $msg->{payload}");
+ last;
+ };
+ $type == I_TIME and do {
+ sendClientMessage($hash,cmd => C_INTERNAL, subType => I_TIME, payload => time);
+ Log3 ($name,4,"MYSENSORS_DEVICE $name: update of time requested");
+ last;
+ };
+ $type == I_VERSION and do {
+ $hash->{$typeStr} = $msg->{payload};
+ last;
+ };
+ $type == I_ID_REQUEST and do {
+ $hash->{$typeStr} = $msg->{payload};
+ last;
+ };
+ $type == I_ID_RESPONSE and do {
+ $hash->{$typeStr} = $msg->{payload};
+ last;
+ };
+ $type == I_INCLUSION_MODE and do {
+ $hash->{$typeStr} = $msg->{payload};
+ last;
+ };
+ $type == I_CONFIG and do {
+ sendClientMessage($hash,cmd => C_INTERNAL, subType => I_CONFIG, payload => AttrVal($name,"config","M"));
+ Log3 ($name,4,"MYSENSORS_DEVICE $name: respond to config-request");
+ last;
+ };
+ $type == I_PING and do {
+ $hash->{$typeStr} = $msg->{payload};
+ last;
+ };
+ $type == I_PING_ACK and do {
+ $hash->{$typeStr} = $msg->{payload};
+ last;
+ };
+ $type == I_LOG_MESSAGE and do {
+ $hash->{$typeStr} = $msg->{payload};
+ last;
+ };
+ $type == I_CHILDREN and do {
+ readingsSingleUpdate($hash,"state","routingtable cleared",1);
+ Log3 ($name,4,"MYSENSORS_DEVICE $name: routingtable cleared");
+ last;
+ };
+ $type == I_SKETCH_NAME and do {
+ $hash->{$typeStr} = $msg->{payload};
+ last;
+ };
+ $type == I_SKETCH_VERSION and do {
+ $hash->{$typeStr} = $msg->{payload};
+ last;
+ };
+ $type == I_REBOOT and do {
+ $hash->{$typeStr} = $msg->{payload};
+ last;
+ };
+ }
+}
+
+sub sendClientMessage($%) {
+ my ($hash,%msg) = @_;
+ $msg{radioId} = $hash->{radioId};
+ $msg{ack} = 1 if $hash->{ack};
+ sendMessage($hash->{IODev},%msg);
+}
+
+sub mapReading($$) {
+ my($hash, $type, $childId, $value) = @_;
+
+ if(defined (my $mapping = $hash->{typeMappings}->{$type})) {
+ return ("$mapping->{type}_$childId",defined $mapping->{val}->{$value} ? $mapping->{val}->{$value} : $value);
+ } else {
+ return (variableTypeToStr($type)."_$childId",$value);
+ }
+}
+
+sub readingToType($$$) {
+ my ($hash,$reading,$value) = @_;
+ $reading =~ /^(.+)_(\d+)$/;
+ if (my @types = grep {$hash->{typeMappings}->{$_}->{type} eq $1} keys %{$hash->{typeMappings}}) {
+ my $type = shift @types;
+ my $valueMappings = $hash->{typeMappings}->{$type}->{val};
+ if (my @mappedValues = grep {$valueMappings->{$_} eq $value} keys %$valueMappings) {
+ return ($type,$2,shift @mappedValues);
+ }
+ return ($type,$2,$value);
+ }
+ return (variableTypeToIdx("V_$1"),$2,$value);
+}
+
+1;
+
+=pod
+=begin html
+
+
+MYSENSORS_DEVICE
+
+ represents a mysensors sensor attached to a mysensor-node
+ requires a MYSENSOR-device as IODev
+
+ Define
+
+
+ Set
+
+ -
+
set <name> clear
+ clears routing-table of a repeater-node
+
+ -
+
set <name> time
+ sets time for nodes (that support it)
+
+ -
+
set <name> reboot
+ reboots a node (requires a bootloader that supports it).
+ Attention: Nodes that run the standard arduino-bootloader will enter a bootloop!
+ Dis- and reconnect the nodes power to restart in this case.
+
+
+
+ Attributes
+
+ -
+
attr <name> config [<M|I>]
+ configures metric (M) or inch (I). Defaults to 'M'
+
+ -
+
attr <name> setCommands [<command:reading:value>]*
+ configures one or more commands that can be executed by set.
+ e.g.: attr <name> setCommands on:switch_1:on off:switch_1:off
+
+ -
+
attr <name> setReading_<reading> [<value>]*
+ configures a reading that can be modified by set-command
+ e.g.: attr <name> setReading_switch_1 on,off
+
+ -
+
attr <name> mapReadingType_<reading> <new reading name> [<value>:<mappedvalue>]*
+ configures reading user names that should be used instead of technical names
+ E.g.: attr xxx mapReadingType_LIGHT switch 0:on 1:off
+
+ -
+
att <name> requestAck
+ request acknowledge from nodes.
+ if set the Readings of nodes are updated not before requested acknowledge is received
+ if not set the Readings of nodes are updated immediatly (not awaiting the acknowledge).
+ May also be configured on the gateway for all nodes at once
+
+
+
+
+=end html
+=cut
diff --git a/fhem/FHEM/lib/Device/MySensors/Constants.pm b/fhem/FHEM/lib/Device/MySensors/Constants.pm
new file mode 100644
index 000000000..b5f81aac0
--- /dev/null
+++ b/fhem/FHEM/lib/Device/MySensors/Constants.pm
@@ -0,0 +1,236 @@
+package Device::MySensors::Constants;
+
+use List::Util qw(first);
+
+#-- Message types
+use constant {
+ C_PRESENTATION => 0,
+ C_SET => 1,
+ C_REQ => 2,
+ C_INTERNAL => 3,
+ C_STREAM => 4,
+};
+
+use constant commands => qw( C_PRESENTATION C_SET C_REQ C_INTERNAL C_STREAM );
+
+sub commandToStr($) {
+ (commands)[shift];
+}
+
+#-- Variable types
+use constant {
+ V_TEMP => 0,
+ V_HUM => 1,
+ V_LIGHT => 2,
+ V_DIMMER => 3,
+ V_PRESSURE => 4,
+ V_FORECAST => 5,
+ V_RAIN => 6,
+ V_RAINRATE => 7,
+ V_WIND => 8,
+ V_GUST => 9,
+ V_DIRECTION => 10,
+ V_UV => 11,
+ V_WEIGHT => 12,
+ V_DISTANCE => 13,
+ V_IMPEDANCE => 14,
+ V_ARMED => 15,
+ V_TRIPPED => 16,
+ V_WATT => 17,
+ V_KWH => 18,
+ V_SCENE_ON => 19,
+ V_SCENE_OFF => 20,
+ V_HEATER => 21,
+ V_HEATER_SW => 22,
+ V_LIGHT_LEVEL => 23,
+ V_VAR1 => 24,
+ V_VAR2 => 25,
+ V_VAR3 => 26,
+ V_VAR4 => 27,
+ V_VAR5 => 28,
+ V_UP => 29,
+ V_DOWN => 30,
+ V_STOP => 31,
+ V_IR_SEND => 32,
+ V_IR_RECEIVE => 33,
+ V_FLOW => 34,
+ V_VOLUME => 35,
+ V_LOCK_STATUS => 36,
+ V_DUST_LEVEL => 37,
+ V_VOLTAGE => 38,
+ V_CURRENT => 39,
+};
+
+use constant variableTypes => qw{ V_TEMP V_HUM V_LIGHT V_DIMMER V_PRESSURE V_FORECAST V_RAIN
+ V_RAINRATE V_WIND V_GUST V_DIRECTION V_UV V_WEIGHT V_DISTANCE
+ V_IMPEDANCE V_ARMED V_TRIPPED V_WATT V_KWH V_SCENE_ON V_SCENE_OFF
+ V_HEATER V_HEATER_SW V_LIGHT_LEVEL V_VAR1 V_VAR2 V_VAR3 V_VAR4 V_VAR5
+ V_UP V_DOWN V_STOP V_IR_SEND V_IR_RECEIVE V_FLOW V_VOLUME V_LOCK_STATUS
+ V_DUST_LEVEL V_VOLTAGE V_CURRENT};
+
+sub variableTypeToStr($) {
+ (variableTypes)[shift];
+}
+
+sub variableTypeToIdx($) {
+ my $var = shift;
+ return first { (variableTypes)[$_] eq $var } 0 .. scalar(variableTypes);
+}
+
+#-- Internal messages
+use constant {
+ I_BATTERY_LEVEL => 0,
+ I_TIME => 1,
+ I_VERSION => 2,
+ I_ID_REQUEST => 3,
+ I_ID_RESPONSE => 4,
+ I_INCLUSION_MODE => 5,
+ I_CONFIG => 6,
+ I_PING => 7,
+ I_PING_ACK => 8,
+ I_LOG_MESSAGE => 9,
+ I_CHILDREN => 10,
+ I_SKETCH_NAME => 11,
+ I_SKETCH_VERSION => 12,
+ I_REBOOT => 13,
+ I_STARTUP_COMPLETE => 14.
+};
+
+use constant internalMessageTypes => qw{ I_BATTERY_LEVEL I_TIME I_VERSION I_ID_REQUEST I_ID_RESPONSE
+ I_INCLUSION_MODE I_CONFIG I_PING I_PING_ACK
+ I_LOG_MESSAGE I_CHILDREN I_SKETCH_NAME I_SKETCH_VERSION
+ I_REBOOT I_STARTUP_COMPLETE };
+
+sub internalMessageTypeToStr($) {
+ (internalMessageTypes)[shift];
+}
+
+#-- Sensor types
+use constant {
+ S_DOOR => 0,
+ S_MOTION => 1,
+ S_SMOKE => 2,
+ S_LIGHT => 3,
+ S_DIMMER => 4,
+ S_COVER => 5,
+ S_TEMP => 6,
+ S_HUM => 7,
+ S_BARO => 8,
+ S_WIND => 9,
+ S_RAIN => 10,
+ S_UV => 11,
+ S_WEIGHT => 12,
+ S_POWER => 13,
+ S_HEATER => 14,
+ S_DISTANCE => 15,
+ S_LIGHT_LEVEL => 16,
+ S_ARDUINO_NODE => 17,
+ S_ARDUINO_REPEATER_NODE => 18,
+ S_LOCK => 19,
+ S_IR => 20,
+ S_WATER => 21,
+ S_AIR_QUALITY => 22,
+};
+
+use constant sensorTypes => qw{ S_DOOR S_MOTION S_SMOKE S_LIGHT S_DIMMER S_COVER S_TEMP S_HUM S_BARO S_WIND
+ S_RAIN S_UV S_WEIGHT S_POWER S_HEATER S_DISTANCE S_LIGHT_LEVEL S_ARDUINO_NODE
+ S_ARDUINO_REPEATER_NODE S_LOCK S_IR S_WATER S_AIR_QUALITY };
+
+sub sensorTypeToStr($) {
+ (sensorTypes)[shift];
+}
+
+sub sensorTypeToIdx($) {
+ my $var = shift;
+ return first { (sensorTypes)[$_] eq $var } 0 .. scalar(sensorTypes);
+}
+
+#-- Datastream types
+use constant {
+ ST_FIRMWARE_CONFIG_REQUEST => 0,
+ ST_FIRMWARE_CONFIG_RESPONSE => 1,
+ ST_FIRMWARE_REQUEST => 2,
+ ST_FIRMWARE_RESPONSE => 3,
+ ST_SOUND => 4,
+ ST_IMAGE => 5,
+};
+
+use constant datastreamTypes => qw{ ST_FIRMWARE_CONFIG_REQUEST ST_FIRMWARE_CONFIG_RESPONSE ST_FIRMWARE_REQUEST ST_FIRMWARE_RESPONSE
+ ST_SOUND ST_IMAGE };
+
+sub datastreamTypeToStr($) {
+ (datastreamTypes)[shift];
+}
+
+#-- Payload types
+use constant {
+ P_STRING => 0,
+ P_BYTE => 1,
+ P_INT16 => 2,
+ P_UINT16 => 3,
+ P_LONG32 => 4,
+ P_ULONG32 => 5,
+ P_CUSTOM => 6,
+};
+
+use constant payloadTypes => qw{ P_STRING P_BYTE P_INT16 P_UINT16 P_LONG32 P_ULONG32 P_CUSTOM };
+
+sub payloadTypeToStr($) {
+ (payloadTypes)[shift];
+}
+
+sub subTypeToStr($$) {
+ my $cmd = shift;
+ my $subType = shift;
+ # Convert subtype to string, depending on message type
+ TYPE: {
+ $cmd == C_PRESENTATION and do {
+ $subType = (sensorTypes)[$subType];
+ last;
+ };
+ $cmd == C_SET and do {
+ $subType = (variableTypes)[$subType];
+ last;
+ };
+ $cmd == C_REQ and do {
+ $subType = (variableTypes)[$subType];
+ last;
+ };
+ $cmd == C_INTERNAL and do {
+ $subType = (internalMessageTypes)[$subType];
+ last;
+ };
+ $subType = "";
+ }
+ return $subType;
+}
+
+use Exporter ('import');
+@EXPORT = ();
+@EXPORT_OK = (
+ commands,
+ variableTypes,
+ internalMessageTypes,
+ sensorTypes,
+ datastreamTypes,
+ payloadTypes,
+ qw(commandToStr
+ variableTypeToStr
+ variableTypeToIdx
+ internalMessageTypeToStr
+ sensorTypeToStr
+ sensorTypeToIdx
+ datastreamTypeToStr
+ payloadTypeToStr
+ subTypeToStr
+ commands
+ variableTypes
+ internalMessageTypes
+ sensorTypes
+ datastreamTypes
+ payloadTypes
+ ));
+
+%EXPORT_TAGS = (all => [@EXPORT_OK]);
+
+1;
\ No newline at end of file
diff --git a/fhem/FHEM/lib/Device/MySensors/Message.pm b/fhem/FHEM/lib/Device/MySensors/Message.pm
new file mode 100644
index 000000000..ca37e2e0e
--- /dev/null
+++ b/fhem/FHEM/lib/Device/MySensors/Message.pm
@@ -0,0 +1,50 @@
+package Device::MySensors::Message;
+
+use Device::MySensors::Constants qw(:all);
+
+use Exporter ('import');
+@EXPORT = ();
+@EXPORT_OK = qw(parseMsg createMsg dumpMsg);
+%EXPORT_TAGS = (all => [@EXPORT_OK]);
+
+use strict;
+use warnings;
+
+sub parseMsg($) {
+ my $txt = shift;
+ my @fields = split(/;/,$txt);
+ my $msgRef = { radioId => $fields[0],
+ childId => $fields[1],
+ cmd => $fields[2],
+ ack => $fields[3],
+ subType => $fields[4],
+ payload => $fields[5] };
+ return $msgRef;
+}
+
+sub createMsg(%) {
+ my %msgRef = @_;
+ my @fields = ( $msgRef{'radioId'},
+ $msgRef{'childId'},
+ $msgRef{'cmd'},
+ $msgRef{'ack'},
+ $msgRef{'subType'},
+ defined($msgRef{'payload'}) ? $msgRef{'payload'} : "" );
+ return join(';', @fields);
+}
+
+sub dumpMsg($) {
+ my $msgRef = shift;
+ my $cmd = commandToStr($msgRef->{'cmd'});
+ my $st = subTypeToStr( $msgRef->{'cmd'}, $msgRef->{'subType'} );
+ return sprintf("Rx: fr=%03d ci=%03d c=%03d(%-14s) st=%03d(%-16s) ack=%d %s\n", $msgRef->{'radioId'}, $msgRef->{'childId'}, $msgRef->{'cmd'}, $cmd, $msgRef->{'subType'}, $st, $msgRef->{'ack'}, defined($msgRef->{'payload'}) ? "'".$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);
+}
+
+1;
\ No newline at end of file
diff --git a/fhem/MAINTAINER.txt b/fhem/MAINTAINER.txt
index 8fea8ab8a..b0bab05b7 100644
--- a/fhem/MAINTAINER.txt
+++ b/fhem/MAINTAINER.txt
@@ -24,6 +24,7 @@ FHEM/00_KM271.pm physikus http://forum.fhem.de Sonstiges
FHEM/00_LIRC.pm rudolfkoenig http://forum.fhem.de Sonstiges
FHEM/00_MAXLAN.pm mgehre http://forum.fhem.de MAX
FHEM/00_MQTT.pm ntruchsess http://forum.fhem.de Sonstige Systeme
+FHEM/00_MYSENSORS.pm ntruchsess http://forum.fhem.de Sonstige Systeme
FHEM/00_NetzerI2C.pm klausw http://forum.fhem.de Sonstige Systeme
FHEM/00_OWX.pm pahenning/ntruchsess http://forum.fhem.de 1Wire
FHEM/00_OWX_ASYNC ntruchsess http://forum.fhem.de 1Wire
@@ -51,6 +52,7 @@ FHEM/10_Itach_IR ulimaass http://forum.fhem.de Sonstige
FHEM/10_MAX.pm mgehre http://forum.fhem.de MAX
FHEM/10_MQTT_BRIDGE ntruchsess http://forum.fhem.de Sonstige Systeme
FHEM/10_MQTT_DEVICE ntruchsess http://forum.fhem.de Sonstige Systeme
+FHEM/10_MYSENSORS_DEVICE ntruchsess http://forum.fhem.de Sonstige Systeme
FHEM/10_OWServer.pm borisneubert/mfr69bs http://forum.fhem.de 1Wire
FHEM/10_SOMFY.pm thdankert http://forum.fhem.de Sonstiges
FHEM/10_UNIRoll.pm c-herrmann http://forum.fhem.de SlowRF
@@ -276,6 +278,7 @@ FHEM/SHC_datafields.pm rr2000 http://forum.fhem.de Sonstige
FHEM/SHC_parser.pm rr2000 http://forum.fhem.de Sonstige Systeme
FHEM/TcpServerUtils.pm rudolfkoenig http://forum.fhem.de Automatisierung
FHEM/lib/Device/Firmata/* ntruchsess http://forum.fhem.de Sonstige Systeme
+FHEM/lib/Device/MySensors/* ntruchsess http://forum.fhem.de Sonstige Systeme
FHEM/lib/ProtoThreads.pm ntruchsess http://forum.fhem.de FHEM Development
FHEM/lib/SHC_packet_layout.xml rr2000 http://forum.fhem.de Sonstige Systeme
FHEM/lib/SWAP/* justme1968 http://forum.fhem.de Sonstige Systeme