From 227be01e02b2595220955e6aaaf98bc9f7c76827 Mon Sep 17 00:00:00 2001 From: ntruchsess <> Date: Sat, 25 Oct 2014 10:17:25 +0000 Subject: [PATCH] MYSENSORS_DEVICE: implement autocreate of map- and setReading based on presentation-messages git-svn-id: https://svn.fhem.de/fhem/trunk@6806 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/FHEM/10_MYSENSORS_DEVICE.pm | 290 ++++++++++++++++---- fhem/FHEM/lib/Device/MySensors/Constants.pm | 5 +- fhem/FHEM/lib/Device/MySensors/Message.pm | 12 +- 3 files changed, 250 insertions(+), 57 deletions(-) diff --git a/fhem/FHEM/10_MYSENSORS_DEVICE.pm b/fhem/FHEM/10_MYSENSORS_DEVICE.pm index 71e9541d7..600749c4f 100644 --- a/fhem/FHEM/10_MYSENSORS_DEVICE.pm +++ b/fhem/FHEM/10_MYSENSORS_DEVICE.pm @@ -42,9 +42,12 @@ sub MYSENSORS_DEVICE_Initialize($) { $hash->{AttrList} = "config:M,I ". + "mode:node,repeater ". + "version:1.4 ". "setCommands ". - "setReading_.+_\\d+ ". + "setReading_.+ ". "mapReadingType_.+ ". + "mapReading_.+ ". "requestAck:1 ". "IODev ". $main::readingFnAttributes; @@ -67,18 +70,90 @@ BEGIN { GP_Import(qw( AttrVal readingsSingleUpdate + CommandAttr + CommandDeleteAttr CommandDeleteReading AssignIoPort Log3 )) }; +my %static_types = ( + S_DOOR => { receives => [], sends => [V_TRIPPED] }, # BinarySwitchSensor + S_MOTION => { receives => [], sends => [V_TRIPPED] }, # MotionSensor + S_SMOKE => { receives => [], sends => [] }, # Not used so far + S_LIGHT => { receives => [V_LIGHT], sends => [V_LIGHT] }, # BinarySwitchSensor + S_DIMMER => { receives => [V_LIGHT,V_DIMMER], sends => [V_LIGHT,V_DIMMER] }, # DimmableLEDActuator + S_COVER => { receives => [V_DIMMER,V_UP,V_DOWN,V_STOP], sends => [V_DIMMER] }, # ServoActuator + S_TEMP => { receives => [], sends => [V_TEMP] }, # DallasTemperatureSensor + S_HUM => { receives => [], sends => [V_HUM] }, # HumiditySensor + S_BARO => { receives => [], sends => [V_PRESSURE,V_FORECAST] }, + S_WIND => { receives => [], sends => [] }, # Not used so far + S_RAIN => { receives => [], sends => [] }, # Not used so far + S_UV => { receives => [], sends => [V_UV] }, # UVSensor + S_WEIGHT => { receives => [], sends => [] }, # Not used so far + S_POWER => { receives => [V_VAR1], sends => [V_WATT,V_KWH,V_VAR1] }, # EnergyMeterPulseSensor + S_HEATER => { receives => [], sends => [] }, # Not used so far + S_DISTANCE => { receives => [], sends => [V_DISTANCE] }, # DistanceSensor + S_LIGHT_LEVEL => { receives => [], sends => [V_LIGHT_LEVEL] }, # LightSensor + S_ARDUINO_NODE => { receives => [], sends => [] }, # Not used so far + S_ARDUINO_REPEATER_NODE => { receives => [], sends => [] }, # Not used so far + S_LOCK => { receives => [V_LOCK_STATUS], sends => [V_LOCK_STATUS] }, #R FIDLockSensor + S_IR => { receives => [], sends => [] }, # Not used so far + S_WATER => { receives => [V_VAR1], sends => [V_FLOW,V_VOLUME] }, # WaterMeterPulseSensor + S_AIR_QUALITY => { receives => [], sends => [V_VAR1] }, # AirQualitySensor + S_CUSTOM => { receives => [], sends => [] }, # Not used so far + S_DUST => { receives => [], sends => [V_DUST_LEVEL] }, # Not used so far + S_SCENE_CONTROLLER => { receives => [], sends => [V_SCENE_ON,V_SCENE_OFF] }, # TouchDisplaySceneControllerDisplaySensor +); + my %static_mappings = ( V_TEMP => { type => "temperature" }, - V_HUM => { type => "humidity" }, + V_HUM => { type => "humidity", range => { min => 0, max => 100 }}, V_PRESSURE => { type => "pressure" }, V_LIGHT_LEVEL => { type => "brightness" }, V_LIGHT => { type => "switch", val => { 0 => 'off', 1 => 'on' }}, + V_DIMMER => { type => "dimmer", range => { min => 0, max => 100 }}, + V_FORECAST => { type => "forecast", val => { # PressureSensor, DP/Dt explanation + 0 => 'stable', # 0 = "Stable Weather Pattern" + 1 => 'slow rising', # 1 = "Slowly rising Good Weather", "Clear/Sunny " + 2 => 'slow falling', # 2 = "Slowly falling L-Pressure ", "Cloudy/Rain " + 3 => 'quick rising', # 3 = "Quickly rising H-Press", "Not Stable" + 4 => 'quick falling',# 4 = "Quickly falling L-Press", "Thunderstorm" + 5 => 'unknown' }}, # 5 = "Unknown (More Time needed) + V_RAIN => { type => "rain" }, + V_RAINRATE => { type => "rainrate" }, + V_WIND => { type => "wind" }, + V_GUST => { type => "gust" }, + V_DIRECTION => { type => "direction" }, + V_UV => { type => "uv" }, + V_WEIGHT => { type => "weight" }, + V_DISTANCE => { type => "distance" }, + V_IMPEDANCE => { type => "impedance" }, + V_ARMED => { type => "armed" }, + V_TRIPPED => { type => "tripped" }, + V_WATT => { type => "power" }, + V_KWH => { type => "energy" }, + V_SCENE_ON => { type => "button_on" }, + V_SCENE_OFF => { type => "button_off" }, + V_HEATER => { type => "heater" }, + V_HEATER_SW => { type => "heater_sw" }, + V_VAR1 => { type => "value1" }, + V_VAR2 => { type => "value2" }, + V_VAR3 => { type => "value3" }, + V_VAR4 => { type => "value4" }, + V_VAR5 => { type => "value5" }, + V_UP => { type => "up" }, + V_DOWN => { type => "down" }, + V_STOP => { type => "stop" }, + V_IR_SEND => { type => "ir_send" }, + V_IR_RECEIVE => { type => "ir_receive" }, + V_FLOW => { type => "flow" }, + V_VOLUME => { type => "volume" }, + V_LOCK_STATUS => { type => "lockstatus", val => { 0 => 'locked', 1 => 'unlocked' }}, + V_DUST_LEVEL => { type => "dustlevel" }, + V_VOLTAGE => { type => "voltage" }, + V_CURRENT => { type => "current" }, ); sub Define($$) { @@ -88,11 +163,12 @@ sub Define($$) { $hash->{radioId} = $radioId; $hash->{sets} = { 'time' => "", - clear => "", reboot => "", }; $hash->{ack} = 0; $hash->{typeMappings} = {map {variableTypeToIdx($_) => $static_mappings{$_}} keys %static_mappings}; + $hash->{sensorMappings} = {map {sensorTypeToIdx($_) => $static_types{$_}} keys %static_types}; + $hash->{readingMappings} = {}; AssignIoPort($hash); }; @@ -121,26 +197,28 @@ sub Set($@) { 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}); + eval { + my ($type,$childId,$mappedValue) = mappedReadingToRaw($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}); + }; + return "$command not defined: ".GP_Catch($@) if $@; last; }; - return "$command not defined by attr setCommands"; + my $value = @values ? join " ",@values : ""; + eval { + my ($type,$childId,$mappedValue) = mappedReadingToRaw($hash,$command,$value); + sendClientMessage($hash, childId => $childId, cmd => C_SET, subType => $type, payload => $mappedValue); + readingsSingleUpdate($hash,$command,$value,1) unless ($hash->{ack} or $hash->{IODev}->{ack}); + }; + return "$command not defined: ".GP_Catch($@) if $@; } } @@ -151,12 +229,34 @@ sub Attr($$$$) { 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"); + sendClientMessage($hash, cmd => C_INTERNAL, childId => 255, subType => I_CONFIG, payload => $command eq 'set' ? $value : "M"); + } + last; + }; + $attribute eq "mode" and do { + if ($command eq "set" and $value eq "repeater") { + $hash->{repeater} = 1; + $hash->{sets}->{clear} = ""; + } else { + $hash->{repeater} = 0; + delete $hash->{sets}->{clear}; + } + last; + }; + $attribute eq "version" and do { + if ($command eq "set") { + $hash->{protocol} = $value; + } else { + delete $hash->{protocol}; } last; }; $attribute eq "setCommands" and do { - if ($command eq "set") { + foreach my $set (keys %{$hash->{setcommands}}) { + delete $hash->{sets}->{$set}; + } + $hash->{setcommands} = {}; + if ($command eq "set" and defined $value) { foreach my $setCmd (split ("[, \t]+",$value)) { $setCmd =~ /^(.+):(.+_\d+):(.+)$/; $hash->{sets}->{$1}=""; @@ -165,17 +265,12 @@ sub Attr($$$$) { val => $3, }; } - } else { - foreach my $set (keys %{$hash->{setcommands}}) { - delete $hash->{sets}->{$set}; - } - $hash->{setcommands} = {}; } last; }; - $attribute =~ /^setReading_(.+_\d+)$/ and do { + $attribute =~ /^setReading_(.+)$/ and do { if ($command eq "set") { - $hash->{sets}->{$1}=join(",",split ("[, \t]+",$value)); + $hash->{sets}->{$1}= (defined $value) ? join(",",split ("[, \t]+",$value)) : ""; } else { CommandDeleteReading(undef,"$hash->{NAME} $1"); delete $hash->{sets}->{$1}; @@ -196,7 +291,38 @@ sub Attr($$$$) { } else { delete $hash->{typeMappings}->{$type}; } - CommandDeleteReading(undef,"$hash->{NAME} $1"); #TODO do propper remap of existing readings + my $readings = $hash->{READINGS}; + my $readingMappings = $hash->{readingMappings}; + foreach my $todelete (map {$readingMappings->{$_}->{name}} grep {$readingMappings->{$_}->{type} == $type} keys %$readingMappings) { + CommandDeleteReading(undef,"$hash->{NAME} $todelete"); #TODO do propper remap of existing readings + } + } + last; + }; + $attribute =~ /^mapReading_(.+)/ and do { + my $readingMappings = $hash->{readingMappings}; + FIND: foreach my $id (keys %$readingMappings) { + my $readingsForId = $readingMappings->{$id}; + foreach my $type (keys %$readingsForId) { + if ($readingsForId->{$type} eq $1) { + delete $readingsForId->{$type}; + unless (keys %$readingsForId) { + delete $readingMappings->{$id}; + } + last FIND; + } + } + } + if ($command eq "set") { + my ($id,$typeStr) = split ("[, \t]",$value); + my $typeMappings = $hash->{typeMappings}; + if (my @match = grep {$typeMappings->{$_}->{type} eq $typeStr} keys %$typeMappings) { + $hash->{readingMappings}->{$id}->{shift @match} = $1; + } else { + return "unknown reading type $typeStr"; + } + } else { + CommandDeleteReading(undef,"$hash->{NAME} $1"); } last; }; @@ -217,13 +343,62 @@ sub onGatewayStarted($) { sub onPresentationMessage($$) { my ($hash,$msg) = @_; + my $name = $hash->{NAME}; + my $nodeType = $msg->{subType}; + my $id = $msg->{childId}; + if ($id == 255) { #special id + NODETYPE: { + $nodeType == S_ARDUINO_NODE and do { + CommandAttr(undef, "$name mode node"); + last; + }; + $nodeType == S_ARDUINO_REPEATER_NODE and do { + CommandAttr(undef, "$name mode repeater"); + last; + }; + }; + CommandAttr(undef, "$name version $msg->{payload}"); + }; + + my $readingMappings = $hash->{readingMappings}; + my $typeMappings = $hash->{typeMappings}; + if (my $sensorMappings = $hash->{sensorMappings}->{$nodeType}) { + my @ret = (); + foreach my $type (@{$sensorMappings->{sends}}) { + next if (defined $readingMappings->{$id}->{$type}); + my $typeStr = $typeMappings->{$type}->{type}; + if ($hash->{IODev}->{'inclusion-mode'}) { + if (my $ret = CommandAttr(undef,"$name mapReading_$typeStr$id $id $typeStr")) { + push @ret,$ret; + } + } else { + push @ret,"no mapReading for $id, $typeStr"; + } + } + foreach my $type (@{$sensorMappings->{receives}}) { + my $typeMapping = $typeMappings->{$type}; + my $typeStr = $typeMapping->{type}; + next if (defined $hash->{sets}->{"$typeStr$id"}); + if ($hash->{IODev}->{'inclusion-mode'}) { + if (my $ret = CommandAttr(undef,"$name setReading_$typeStr$id".($typeMapping->{val} ? " ".join (",",values %{$typeMapping->{val}}) : ""))) { + push @ret,$ret; + } + } else { + push @ret,"no setReading for $id, $typeStr"; + } + } + 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($$) { my ($hash,$msg) = @_; if (defined $msg->{payload}) { - my ($reading,$value) = mapReading($hash,$msg->{subType},$msg->{childId},$msg->{payload}); - readingsSingleUpdate($hash,$reading,$value,1); + eval { + my ($reading,$value) = rawToMappedReading($hash,$msg->{subType},$msg->{childId},$msg->{payload}); + readingsSingleUpdate($hash,$reading,$value,1); + }; + Log3 ($hash->{NAME},4,"MYSENSORS_DEVICE $hash->{NAME}: ignoring C_SET-message ".GP_Catch($@)) if $@; } else { Log3 ($hash->{NAME},5,"MYSENSORS_DEVICE $hash->{NAME}: ignoring C_SET-message without payload"); } @@ -252,7 +427,7 @@ sub onInternalMessage($$) { last; }; $type == I_TIME and do { - sendClientMessage($hash,cmd => C_INTERNAL, subType => I_TIME, payload => time); + sendClientMessage($hash,cmd => C_INTERNAL, childId => 255, subType => I_TIME, payload => time); Log3 ($name,4,"MYSENSORS_DEVICE $name: update of time requested"); last; }; @@ -273,7 +448,7 @@ sub onInternalMessage($$) { last; }; $type == I_CONFIG and do { - sendClientMessage($hash,cmd => C_INTERNAL, subType => I_CONFIG, payload => AttrVal($name,"config","M")); + sendClientMessage($hash,cmd => C_INTERNAL, childId => 255, subType => I_CONFIG, payload => AttrVal($name,"config","M")); Log3 ($name,4,"MYSENSORS_DEVICE $name: respond to config-request"); last; }; @@ -316,28 +491,37 @@ sub sendClientMessage($%) { sendMessage($hash->{IODev},%msg); } -sub mapReading($$$$) { +sub rawToMappedReading($$$$) { 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); + my $name; + if (defined (my $name = $hash->{readingMappings}->{$childId}->{$type})) { + if(defined (my $mapping = $hash->{typeMappings}->{$type})) { + return ($name,defined $mapping->{val}->{$value} ? $mapping->{val}->{$value} : $value); + } + die "not type-mapping for type ".variableTypeToStr($type); } + die "no reading-mapping for childId $childId, type ".($hash->{typeMappings}->{$type}->{type} ? $hash->{typeMappings}->{$type}->{type} : variableTypeToStr($type)); } -sub readingToType($$$) { +sub mappedReadingToRaw($$$) { 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); + + my $readingsMapping = $hash->{readingMappings}; + foreach my $id (keys %$readingsMapping) { + my $readingTypesForId = $readingsMapping->{$id}; + foreach my $type (keys %$readingTypesForId) { + if ($readingTypesForId->{$type} eq $reading) { + if (my $valueMappings = $hash->{typeMappings}->{$type}->{val}) { + if (my @mappedValues = grep {$valueMappings->{$_} eq $value} keys %$valueMappings) { + return ($type,$id,shift @mappedValues); + } + } + return ($type,$id,$value); + } } - return ($type,$2,$value); } - return (variableTypeToIdx("V_$1"),$2,$value); + die "no mapping for reading $reading"; } 1; @@ -392,9 +576,9 @@ sub readingToType($$$) { 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

    +

    attr <name> mapReading_<reading> <childId> <readingtype>
    + configures the reading-name for a given childId and sensortype
    + E.g.: attr xxx mapReading_aussentemperatur 123 temperature

  • att <name> requestAck
    @@ -403,6 +587,12 @@ sub readingToType($$$) { 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

  • +
  • +

    attr <name> mapReadingType_<reading> <new reading name> [<value>:<mappedvalue>]*
    + configures reading type names that should be used instead of technical names
    + E.g.: attr xxx mapReadingType_LIGHT switch 0:on 1:off + to be used for mysensor Variabletypes that have no predefined defaults (yet)

    +
  • diff --git a/fhem/FHEM/lib/Device/MySensors/Constants.pm b/fhem/FHEM/lib/Device/MySensors/Constants.pm index b5f81aac0..9a9f4eb4a 100644 --- a/fhem/FHEM/lib/Device/MySensors/Constants.pm +++ b/fhem/FHEM/lib/Device/MySensors/Constants.pm @@ -130,11 +130,14 @@ use constant { S_IR => 20, S_WATER => 21, S_AIR_QUALITY => 22, + S_CUSTOM => 23, + S_DUST => 24, + S_SCENE_CONTROLLER => 25, }; 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 }; + S_ARDUINO_REPEATER_NODE S_LOCK S_IR S_WATER S_AIR_QUALITY S_CUSTOM S_DUST S_SCENE_CONTROLLER }; sub sensorTypeToStr($) { (sensorTypes)[shift]; diff --git a/fhem/FHEM/lib/Device/MySensors/Message.pm b/fhem/FHEM/lib/Device/MySensors/Message.pm index fe3168f19..0e069290b 100644 --- a/fhem/FHEM/lib/Device/MySensors/Message.pm +++ b/fhem/FHEM/lib/Device/MySensors/Message.pm @@ -26,11 +26,11 @@ sub parseMsg($) { sub createMsg(%) { my %msgRef = @_; - my @fields = ( $msgRef{'radioId'} // "", - $msgRef{'childId'} // "", - $msgRef{'cmd'} // "", - $msgRef{'ack'} // "", - $msgRef{'subType'} // "", + my @fields = ( $msgRef{'radioId'} // -1, + $msgRef{'childId'} // -1, + $msgRef{'cmd'} // -1, + $msgRef{'ack'} // -1, + $msgRef{'subType'} // -1, $msgRef{'payload'} // ""); return join(';', @fields); } @@ -39,7 +39,7 @@ sub dumpMsg($) { my $msgRef = shift; my $cmd = defined $msgRef->{'cmd'} ? commandToStr($msgRef->{'cmd'}) : "''"; my $st = (defined $msgRef->{'cmd'} and defined $msgRef->{'subType'}) ? subTypeToStr( $msgRef->{'cmd'}, $msgRef->{'subType'} ) : "''"; - return sprintf("Rx: fr=%03d ci=%03d c=%03d(%-14s) st=%03d(%-16s) ack=%d %s\n", $msgRef->{'radioId'} // "", $msgRef->{'childId'} // "", $msgRef->{'cmd'} // "", $cmd, $msgRef->{'subType'} // "", $st, $msgRef->{'ack'} // "", "'".$msgRef->{'payload'} // ""."'"); + return sprintf("Rx: fr=%03d ci=%03d c=%03d(%-14s) st=%03d(%-16s) ack=%d %s\n", $msgRef->{'radioId'} // -1, $msgRef->{'childId'} // -1, $msgRef->{'cmd'} // -1, $cmd, $msgRef->{'subType'} // -1, $st, $msgRef->{'ack'} // -1, "'".($msgRef->{'payload'} // "")."'"); } sub gettime {