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

+ + +=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

+ + +=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