############################################## # # fhem bridge to MySensors (see http://mysensors.org) # # Copyright (C) 2014 Norbert Truchsess # Copyright (C) 2018 Hauswart@forum.fhem.de # # 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 " . "mode:node,repeater " . "version:1.4 " . "setCommands " . "setReading_.+ " . "mapReadingType_.+ " . "mapReading_.+ " . "requestAck:1 " . "timeoutAck " . "timeoutAlive " . "IODev " . "showtime:0,1 " . $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); use SetExtensions qw/ :all /; BEGIN { MYSENSORS->import(qw(:all)); GP_Import(qw( AttrVal readingsSingleUpdate CommandAttr CommandDeleteAttr CommandDeleteReading AssignIoPort Log3 SetExtensions ReadingsVal InternalTimer RemoveInternalTimer )) }; my %static_types = ( S_DOOR => { receives => [], sends => [V_TRIPPED,V_ARMED] }, # Door and window sensors S_MOTION => { receives => [], sends => [V_TRIPPED,V_ARMED] }, # Motion sensors S_SMOKE => { receives => [], sends => [V_TRIPPED,V_ARMED] }, # Smoke sensor S_BINARY => { receives => [V_STATUS,V_WATT], sends => [V_STATUS,V_WATT] }, # Binary device (on/off) S_DIMMER => { receives => [V_STATUS,V_PERCENTAGE,V_WATT], sends => [V_STATUS,V_PERCENTAGE,V_WATT] }, # Dimmable device of some kind S_COVER => { receives => [V_UP,V_DOWN,V_STOP,V_PERCENTAGE], sends => [V_UP,V_DOWN,V_STOP,V_PERCENTAGE] }, # Window covers or shades S_TEMP => { receives => [], sends => [V_TEMP,V_ID] }, # Temperature sensor S_HUM => { receives => [], sends => [V_HUM] }, # Humidity sensor S_BARO => { receives => [], sends => [V_PRESSURE,V_FORECAST] }, # Barometer sensor (Pressure) S_WIND => { receives => [], sends => [V_WIND,V_GUST,V_DIRECTION] }, # Wind sensor S_RAIN => { receives => [], sends => [V_RAIN,V_RAINRATE] }, # Rain sensor S_UV => { receives => [], sends => [V_UV] }, # UV sensor S_WEIGHT => { receives => [], sends => [V_WEIGHT,V_IMPEDANCE] }, # Weight sensor for scales etc. S_POWER => { receives => [V_VAR1], sends => [V_WATT,V_KWH,V_VAR,V_VA,V_POWER_FACTOR,V_VAR1] }, # Power measuring device, like power meters S_HEATER => { receives => [], sends => [V_HVAC_SETPOINT_HEAT,V_HVAC_FLOW_STATE,V_TEMP,V_STATUS] }, # Heater device S_DISTANCE => { receives => [], sends => [V_DISTANCE,V_UNIT_PREFIX] }, # Distance sensor S_LIGHT_LEVEL => { receives => [], sends => [V_LIGHT_LEVEL,V_LEVEL] }, # Light sensor S_ARDUINO_NODE => { receives => [], sends => [] }, # Arduino node device S_ARDUINO_REPEATER_NODE => { receives => [], sends => [] }, # Arduino repeating node device S_LOCK => { receives => [V_LOCK_STATUS], sends => [V_LOCK_STATUS] }, # Lock device S_IR => { receives => [V_IR_SEND], sends => [V_IR_RECEIVE,V_IR_RECORD,V_IR_SEND] }, # Ir sender/receiver device S_WATER => { receives => [V_VAR1], sends => [V_FLOW,V_VOLUME,V_VAR1] }, # Water meter S_AIR_QUALITY => { receives => [], sends => [V_LEVEL,V_UNIT_PREFIX] }, # Air quality sensor e.g. MQ-2 S_CUSTOM => { receives => [V_VAR1,V_VAR2,V_VAR3,V_VAR4,V_VAR5], sends => [V_VAR1,V_VAR2,V_VAR3,V_VAR4,V_VAR5] }, # Use this for custom sensors where no other fits. S_DUST => { receives => [], sends => [V_LEVEL,V_UNIT_PREFIX] }, # Dust level sensor S_SCENE_CONTROLLER => { receives => [], sends => [V_SCENE_ON,V_SCENE_OFF] }, # Scene controller device S_RGB_LIGHT => { receives => [V_RGB,V_WATT,V_PERCENTAGE], sends => [V_RGB,V_WATT,V_PERCENTAGE] }, # RGB light S_RGBW_LIGHT => { receives => [V_RGBW,V_WATT,V_PERCENTAGE], sends => [V_RGBW,V_WATT,V_PERCENTAGE] }, # RGBW light (with separate white component) S_COLOR_SENSOR => { receives => [V_RGB], sends => [V_RGB] }, # Color sensor S_HVAC => { receives => [], sends => [V_STATUS,V_TEMP,V_HVAC_SETPOINT_HEAT,V_HVAC_SETPOINT_COOL,V_HVAC_FLOW_STATE,V_HVAC_FLOW_MODE,V_HVAC_SPEED] }, # Thermostat/HVAC device S_MULTIMETER => { receives => [], sends => [V_VOLTAGE,V_CURRENT,V_IMPEDANCE] }, # Multimeter device S_SPRINKLER => { receives => [], sends => [V_STATUS,V_TRIPPED] }, # Sprinkler device S_WATER_LEAK => { receives => [], sends => [V_TRIPPED,V_ARMED] }, # Water leak sensor S_SOUND => { receives => [], sends => [V_LEVEL,V_TRIPPED,V_ARMED] }, # Sound sensor S_VIBRATION => { receives => [], sends => [V_LEVEL,V_TRIPPED,V_ARMED] }, # Vibration sensor S_MOISTURE => { receives => [], sends => [V_LEVEL,V_TRIPPED,V_ARMED] }, # Moisture sensor S_INFO => { receives => [V_TEXT], sends => [V_TEXT] }, # LCD text device S_GAS => { receives => [], sends => [V_FLOW,V_VOLUME] }, # Gas meter S_GPS => { receives => [], sends => [V_POSITION] }, # GPS Sensor S_WATER_QUALITY => { receives => [], sends => [V_TEMP,V_PH,V_ORP,V_EC,V_STATUS] }, # Water quality sensor ); my %static_mappings = ( V_TEMP => { type => "temperature" }, V_HUM => { type => "humidity" }, V_STATUS => { type => "status", val => { 0 => 'off', 1 => 'on' }}, V_PERCENTAGE => { type => "percentage", range => { min => 0, step => 1, max => 100 }}, V_PRESSURE => { type => "pressure" }, V_FORECAST => { type => "forecast", val => { # PressureSensor, DP/Dt explanation 0 => 'stable', # 0 = "Stable Weather Pattern" 1 => 'sunny', # 1 = "Slowly rising Good Weather", "Clear/Sunny" 2 => 'cloudy', # 2 = "Slowly falling L-Pressure ", "Cloudy/Rain" 3 => 'unstable', # 3 = "Quickly rising H-Press", "Not Stable" 4 => 'thunderstorm',# 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", val => { 0 => 'off', 1 => 'on' }}, V_TRIPPED => { type => "tripped", val => { 0 => 'off', 1 => 'on' }}, V_WATT => { type => "power" }, V_KWH => { type => "energy" }, V_SCENE_ON => { type => "button_on" }, V_SCENE_OFF => { type => "button_off" }, V_HVAC_FLOW_STATE => { type => "hvacflowstate" }, V_HVAC_SPEED => { type => "hvacspeed" }, V_LIGHT_LEVEL => { type => "brightness", range => { min => 0, step => 1, max => 100 }}, 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 => 'off', 1 => 'on' }}, V_LEVEL => { type => "level" }, V_VOLTAGE => { type => "voltage" }, V_CURRENT => { type => "current" }, V_RGB => { type => "rgb" }, V_RGBW => { type => "rgbw" }, V_ID => { type => "id" }, V_UNIT_PREFIX => { type => "unitprefix" }, V_HVAC_SETPOINT_COOL => { type => "hvacsetpointcool" }, V_HVAC_SETPOINT_HEAT => { type => "hvacsetpointheat" }, V_HVAC_FLOW_MODE => { type => "hvacflowmode" }, V_TEXT => { type => "text" }, V_CUSTOM => { type => "custom" }, V_POSITION => { type => "position" }, V_IR_RECORD => { type => "ir_record" }, V_PH => { type => "ph" }, V_ORP => { type => "orp" }, V_EC => { type => "ec" }, V_VAR => { type => "value" }, V_VA => { type => "va" }, V_POWER_FACTOR => { type => "power_factor" }, ); 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' => "", reboot => "", # clear => "", }; $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); }; sub UnDefine($) { my ($hash) = @_; my $name = $hash->{NAME}; RemoveInternalTimer("timeoutAck:$name"); RemoveInternalTimer("timeoutAlive:$name"); return undef; } sub Set($@) { my ($hash,$name,$command,@values) = @_; return "Need at least one parameters" unless defined $command; if(!defined($hash->{sets}->{$command})) { my $list = join(" ", map {$hash->{sets}->{$_} ne "" ? "$_:$hash->{sets}->{$_}" : $_} sort keys %{$hash->{sets}}); return grep (/(^on$)|(^off$)/,keys %{$hash->{sets}}) == 2 ? SetExtensions($hash, $list, $name, $command, @values) : "Unknown argument $command, choose one of $list"; } 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 { 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; }; (defined ($hash->{setcommands}->{$command})) and do { my $setcommand = $hash->{setcommands}->{$command}; 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; }; 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 $@; } } 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, 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 { foreach my $set (keys %{$hash->{setcommands}}) { delete $hash->{sets}->{$set}; } $hash->{setcommands} = {}; if ($command eq "set" and $value) { foreach my $setCmd (split ("[, \t]+",$value)) { if ($setCmd =~ /^(.+):(.+):(.+)$/) { $hash->{sets}->{$1}=""; $hash->{setcommands}->{$1} = { var => $2, val => $3, }; } else { return "unparsable value in setCommands for $name: $setCmd"; } } } last; }; $attribute =~ /^setReading_(.+)$/ and do { if ($command eq "set") { $hash->{sets}->{$1}= (defined $value) ? 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}; } 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}->{name} // "") eq $1) { delete $readingsForId->{$type}; unless (keys %$readingsForId) { delete $readingMappings->{$id}; } last FIND; } } } if ($command eq "set") { my ($id,$typeStr,@values) = split ("[, \t]",$value); my $typeMappings = $hash->{typeMappings}; if (my @match = grep {$typeMappings->{$_}->{type} eq $typeStr} keys %$typeMappings) { my $type = shift @match; $readingMappings->{$id}->{$type}->{name} = $1; if (@values) { $readingMappings->{$id}->{$type}->{val} = {map {$_ =~ /^(.+):(.+)$/; $1 => $2} @values}; #TODO range? } } else { return "unknown reading type $typeStr"; } } else { CommandDeleteReading(undef,"$hash->{NAME} $1"); } last; }; $attribute eq "requestAck" and do { if ($command eq "set") { $hash->{ack} = 1; } else { $hash->{ack} = 0; } last; }; $attribute eq "timeoutAck" and do { if ($command eq "set") { $hash->{timeoutAck} = $value; } else { $hash->{timeoutAck} = 0; } last; }; $attribute eq "timeoutAlive" and do { if ($command eq "set" and $value) { $hash->{timeoutAlive} = $value; refreshInternalMySTimer($hash,"Alive"); } else { $hash->{timeoutAlive} = 0; } last; }; } } sub onGatewayStarted($) { my ($hash) = @_; refreshInternalMySTimer($hash,"Alive") if ($hash->{timeoutAlive}); } 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 $idStr = ($id > 0 ? $id : ""); 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$idStr $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$idStr"}); if ($hash->{IODev}->{'inclusion-mode'}) { my @values = (); if ($typeMapping->{range}) { @values = ('slider',$typeMapping->{range}->{min},$typeMapping->{range}->{step},$typeMapping->{range}->{max}); } elsif ($typeMapping->{val}) { @values = values %{$typeMapping->{val}}; } if (my $ret = CommandAttr(undef,"$name setReading_$typeStr$idStr".(@values ? " ".join (",",@values) : ""))) { 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) = @_; my $name = $hash->{NAME}; if (defined $msg->{payload}) { 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 $@; refreshInternalMySTimer($hash,"Alive") if $hash->{timeoutAlive}; } else { Log3 ($hash->{NAME}, 5, "MYSENSORS_DEVICE $hash->{NAME}: ignoring C_SET-message without payload"); }; } sub onRequestMessage($$) { my ($hash,$msg) = @_; eval { my ($readingname,$val) = rawToMappedReading($hash, $msg->{subType}, $msg->{childId}, $msg->{payload}); sendClientMessage($hash, childId => $msg->{childId}, cmd => C_SET, subType => $msg->{subType}, payload => ReadingsVal($hash->{NAME},$readingname,$val) ); }; refreshInternalMySTimer($hash,"Alive") if $hash->{timeoutAlive}; Log3 ($hash->{NAME}, 4, "MYSENSORS_DEVICE $hash->{NAME}: ignoring C_REQ-message ".GP_Catch($@)) if $@; } 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 { if ($msg->{ack}) { Log3 ($name, 4, "MYSENSORS_DEVICE $name: response to time-request acknowledged"); } else { sendClientMessage($hash,cmd => C_INTERNAL, childId => 255, 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 { if ($msg->{ack}) { Log3 ($name, 4, "MYSENSORS_DEVICE $name: response to config-request acknowledged"); } else { readingsSingleUpdate($hash, "parentId", $msg->{payload}, 1); 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, node parentId = " . $msg->{payload}); } last; }; $type == I_FIND_PARENT and do { $hash->{$typeStr} = $msg->{payload}; last; }; $type == I_FIND_PARENT_RESPONSE 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, 3, "MYSENSORS_DEVICE $name: routingtable cleared"); last; }; $type == I_SKETCH_NAME and do { $hash->{$typeStr} = $msg->{payload}; readingsSingleUpdate($hash, "SKETCH_NAME", $msg->{payload}, 1); last; }; $type == I_SKETCH_VERSION and do { $hash->{$typeStr} = $msg->{payload}; readingsSingleUpdate($hash, "SKETCH_VERSION", $msg->{payload}, 1); last; }; $type == I_REBOOT and do { $hash->{$typeStr} = $msg->{payload}; last; }; $type == I_GATEWAY_READY and do { $hash->{$typeStr} = $msg->{payload}; last; }; $type == I_REQUEST_SIGNING and do { $hash->{$typeStr} = $msg->{payload}; last; }; $type == I_GET_NONCE and do { $hash->{$typeStr} = $msg->{payload}; last; }; $type == I_GET_NONCE_RESPONSE and do { $hash->{$typeStr} = $msg->{payload}; last; }; } } sub sendClientMessage($%) { my ($hash,%msg) = @_; $msg{radioId} = $hash->{radioId}; $msg{ack} = $hash->{ack} unless defined $msg{ack}; sendMessage($hash->{IODev},%msg); refreshInternalMySTimer($hash,"Ack") if (($hash->{ack} or $hash->{IODev}->{ack}) and $hash->{timeoutAck}) ; } sub rawToMappedReading($$$$) { my($hash, $type, $childId, $value) = @_; my $name; if (defined (my $mapping = $hash->{readingMappings}->{$childId}->{$type})) { my $val = $mapping->{val} // $hash->{typeMappings}->{$type}->{val}; return ($mapping->{name},defined $val ? ($val->{$value} // $value) : $value); } die "no reading-mapping for childId $childId, type ".($hash->{typeMappings}->{$type}->{type} ? $hash->{typeMappings}->{$type}->{type} : variableTypeToStr($type)); } sub mappedReadingToRaw($$$) { my ($hash,$reading,$value) = @_; my $readingsMapping = $hash->{readingMappings}; foreach my $id (keys %$readingsMapping) { my $readingTypesForId = $readingsMapping->{$id}; foreach my $type (keys %$readingTypesForId) { if (($readingTypesForId->{$type}->{name} // "") eq $reading) { if (my $valueMappings = $readingTypesForId->{$type}->{val} // $hash->{typeMappings}->{$type}->{val}) { if (my @mappedValues = grep {$valueMappings->{$_} eq $value} keys %$valueMappings) { return ($type,$id,shift @mappedValues); } } return ($type,$id,$value); } } } die "no mapping for reading $reading"; } sub refreshInternalMySTimer($$) { my ($hash,$calltype) = @_; my $name = $hash->{NAME}; Log3 $name, 5, "$name: refreshInternalMySTimer called ($calltype)"; if ($calltype eq "Alive") { RemoveInternalTimer("timeoutAlive:$name"); my $nextTrigger = main::gettimeofday() + $hash->{timeoutAlive}; InternalTimer($nextTrigger, "MYSENSORS::DEVICE::timeoutMySTimer", "timeoutAlive:$name", 0); if ($hash->{STATE} ne "NACK" or $hash->{STATE} eq "NACK" and @{$hash->{IODev}->{messagesForRadioId}->{$hash->{radioId}}->{messages}} == 0) { my $do_trigger = $hash->{STATE} ne "alive" ? 1 : 0; readingsSingleUpdate($hash,"state","alive",$do_trigger); } } elsif ($calltype eq "Ack") { RemoveInternalTimer("timeoutAck:$name"); my $nextTrigger = main::gettimeofday() + $hash->{timeoutAck}; InternalTimer($nextTrigger, "MYSENSORS::DEVICE::timeoutMySTimer", "timeoutAck:$name", 0); Log3 $name, 4, "$name: Ack timeout timer set at $nextTrigger"; } } sub timeoutMySTimer($) { my ($calltype, $name) = split(':', $_[0]); my $hash = $main::defs{$name}; Log3 $name, 5, "$name: timeoutMySTimer called ($calltype)"; if ($calltype eq "timeoutAlive") { readingsSingleUpdate($hash,"state","dead",1) unless ($hash->{STATE} eq "NACK"); } elsif ($calltype eq "timeoutAck") { #readingsSingleUpdate($hash,"state","timeoutAck passed",1);# if ($hash->{STATE} eq "NACK"); if ($hash->{IODev}->{outstandingAck} == 0) { Log3 $name, 4, "$name: timeoutMySTimer called ($calltype), no outstanding Acks at all"; readingsSingleUpdate($hash,"state","alive",1) if ($hash->{STATE} eq "NACK"); } elsif (@{$hash->{IODev}->{messagesForRadioId}->{$hash->{radioId}}->{messages}}) { Log3 $name, 4, "$name: timeoutMySTimer called ($calltype), outstanding: $hash->{IODev}->{messagesForRadioId}->{$hash->{radioId}}->{messages}"; readingsSingleUpdate($hash,"state","NACK",1) ; } else { Log3 $name, 4, "$name: timeoutMySTimer called ($calltype), no outstanding Acks for Node"; readingsSingleUpdate($hash,"state","alive",1) if ($hash->{STATE} eq "NACK"); } } } 1; =pod =item device =item summary includes MYSENSOR clients =item summary_DE integriert MYSENSOR Sensoren =begin html

MYSENSORS_DEVICE

=end html =cut