From 7cb44814a451ab07c525fdc921caafbb2642ab08 Mon Sep 17 00:00:00 2001 From: klaus-schauer <> Date: Mon, 15 Sep 2014 19:13:32 +0000 Subject: [PATCH] 10_EnOcean: # Added new EEP 2.6.1 profiles: A5-04-02 (tempHumiSensor.02), A5-04-03 (tempHumiSensor.03), A5-05-01 (baroSensor.01), D2-10-xx (roomCtrlPanel.00), F6-3F-7F (switch.7F) # minor changes and corrections # commandref: further explanations added git-svn-id: https://svn.fhem.de/fhem/trunk@6560 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/FHEM/10_EnOcean.pm | 2183 +++++++++++++++++++++++++++++++++------ 1 file changed, 1862 insertions(+), 321 deletions(-) diff --git a/fhem/FHEM/10_EnOcean.pm b/fhem/FHEM/10_EnOcean.pm index 28cc1d1c2..40ab6336e 100755 --- a/fhem/FHEM/10_EnOcean.pm +++ b/fhem/FHEM/10_EnOcean.pm @@ -23,10 +23,10 @@ sub EnOcean_Initialize($); sub EnOcean_Parse($$); sub EnOcean_Get($@); sub EnOcean_Set($@); -sub EnOcean_hvac_01Cmd($$); -sub EnOcean_roomCtrlPanel_00Cmd($$$$$$$); +sub EnOcean_hvac_01Cmd($$$); +sub EnOcean_roomCtrlPanel_00Snd($$$$$$$$); sub EnOcean_CheckSenderID($$$); -sub EnOcean_SndRadio($$$$$$$); +sub EnOcean_SndRadio($$$$$$$$); sub EnOcean_ReadingScaled($$$$); sub EnOcean_TimerSet($); sub EnOcean_Undef($$); @@ -34,6 +34,8 @@ sub EnOcean_Undef($$); my %EnO_rorgname = ("F6" => "switch", # RPS, org 05 "D5" => "contact", # 1BS, org 06 "A5" => "sensor", # 4BS, org 07 + "A6" => "ADT", # adressing destination telegram + "C5" => "SYSEX", # remote management "D1" => "MSC", # MSC "D2" => "VLD", # VLD "D4" => "UTE", # UTE @@ -151,6 +153,8 @@ my %EnO_subType = ( "A5.02.30" => "tempSensor.30", "A5.04.01" => "roomSensorControl.01", "A5.04.02" => "tempHumiSensor.02", + "A5.04.03" => "tempHumiSensor.03", + "A5.05.01" => "baroSensor.01", "A5.06.01" => "lightSensor.01", "A5.06.02" => "lightSensor.02", "A5.06.03" => "lightSensor.03", @@ -255,12 +259,28 @@ my %EnO_subType = ( #"F6.04.02" => "keycard.02", "F6.10.00" => "windowHandle", #"F6.10.01" => "windowHandle.01", + "F6.3F.7F" => "switch.7F", + 1 => "sensor", 2 => "FRW", 3 => "PM101", 4 => "raw", ); +my %EnO_getRemoteFunctionCode = ( + 4 => "id", + 6 => "ping", + 7 => "functionCommands", + 8 => "status" +); + +my %EnO_setRemoteFunctionCode = ( + 1 => "unlock", + 2 => "lock", + 3 => "setCode", + 5 => "action", +); + my @EnO_models = qw ( other FAE14 FHK14 FHK61 @@ -270,7 +290,7 @@ my @EnO_models = qw ( FTS12 ); -my @EnO_defaultChannel = ("all","input",0..29); +my @EnO_defaultChannel = ("all", "input", 0..29); # Initialize sub @@ -278,7 +298,9 @@ EnOcean_Initialize($) { my ($hash) = @_; my %subTypeList; - my %subTypeSetList; + my $subTypeList = join(",", sort grep { !$subTypeList{$_}++ } values %EnO_subType); + #my %subTypeSetList; + $hash->{Match} = "^EnOcean:"; $hash->{DefFn} = "EnOcean_Define"; @@ -291,26 +313,31 @@ EnOcean_Initialize($) $hash->{AttrList} = "IODev do_not_notify:1,0 ignore:0,1 dummy:0,1 " . "showtime:1,0 " . "actualTemp angleMax:slider,-180,20,180 angleMin:slider,-180,20,180 " . - "angleTime:0,1,2,3,4,5,6 comMode:biDir,uniDir dataEnc " . + "angleTime:0,1,2,3,4,5,6 blockUnknownMSC:no,yes blockMotion:no,yes " . + "blockTemp:no,yes blockDisplay:no,yes blockDateTime:no,yes " . + "blockTimeProgram:no,yes blockOccupancy:no,yes blockSetpointTemp:no,yes " . + "blockFanSpeed:no,yes blockKey:no,yes comMode:biDir,uniDir " . + "daylightSavingTime:supported,not_supported dataEnc " . "defaultChannel:" . join(",", @EnO_defaultChannel) . " " . "destinationID " . "devChannel devUpdate:off,auto,demand,polling,interrupt dimValueOn " . "disable:0,1 disabledForIntervals " . - "gwCmd:" . join(",", sort @EnO_gwCmd) . " humidityRefDev " . + "displayContent:humidity,off,setPointTemp,tempertureExtern,temperatureIntern,time,default,no_change " . + "eep gwCmd:" . join(",", sort @EnO_gwCmd) . " humidityRefDev " . "key macAlgo " . "manufID:" . join(",", sort keys %EnO_manuf) . " " . "model:" . join(",", @EnO_models) . " " . "pollInterval rampTime repeatingAllowed:yes,no " . - "rlc rlcAlgo rlcTX " . + "remoteManagement:off,on rlc rlcAlgo rlcTX " . "scaleDecimals:0,1,2,3,4,5,6,7,8,9 scaleMax scaleMin " . "securityLevel:unencrypted sensorMode:switch,pushbutton " . "shutTime shutTimeCloses subDef " . "subDef0 subDefI " . - "subType:" . join(",", sort grep { !$subTypeList{$_}++ } values %EnO_subType) . " " . - "subTypeReading:" . join(",", sort grep { !$subTypeSetList{$_}++ } values %EnO_subType) . " " . - "subTypeSet:" . join(",", sort grep { !$subTypeSetList{$_}++ } values %EnO_subType) . " " . + "subType:$subTypeList subTypeSet:$subTypeList subTypeReading:$subTypeList " . "summerMode:off,on switchMode:switch,pushbutton " . "switchType:direction,universal,central temperatureRefDev " . + "temperatureScale:C,F,default,no_change timeNotation:12,24,default,no_change " . + "timeProgram1 timeProgram2 timeProgram3 timeProgram4 " . $readingFnAttributes; for (my $i = 0; $i < @EnO_ptm200btn; $i++) { @@ -354,6 +381,8 @@ EnOcean_Get ($@) my ($hash, @a) = @_; return "no get value specified" if (@a < 2); my $name = $hash->{NAME}; + my $cmdID; + my $cmdList = ""; my $data; my $destinationID = AttrVal($name, "destinationID", undef); if (AttrVal($name, "comMode", "uniDir") eq "biDir") { @@ -366,8 +395,13 @@ EnOcean_Get ($@) return "DestinationID $destinationID wrong, choose <8-digit-hex-code>."; } $destinationID = uc($destinationID); + my $eep = uc(AttrVal($name, "eep", "00-00-00")); + $eep =~ m/^(..)(.)(..)(.)(..)$/; + $eep = (((hex($1) << 6) | hex($3)) << 7) | hex($5); my $manufID = uc(AttrVal($name, "manufID", "")); my $model = AttrVal($name, "model", ""); + my $packetType = 1; + $packetType = 0x0A if (ReadingsVal($hash->{IODev}, "mode", "00") eq "01"); my $rorg; my $status = "00"; my $st = AttrVal($name, "subType", ""); @@ -376,17 +410,77 @@ EnOcean_Get ($@) my $subDef = uc(AttrVal($name, "subDef", $hash->{DEF})); if ($subDef !~ m/^[\dA-F]{8}$/) {return "SenderID $subDef wrong, choose <8-digit-hex-code>.";} my $tn = TimeNow(); + if (AttrVal($name, "remoteManagement", "off") eq "on") { + # Remote Management + $cmdList = "remoteCommands:noArg remoteID:noArg remotePing:noArg remoteStatus:noArg "; + } + # control set actions + # $updateState = -1: no set commands available e. g. sensors + # 0: execute set commands + # 1: execute set commands and and update reading state + # 2: execute set commands delayed + my $updateState = 1; shift @a; for (my $i = 0; $i < @a; $i++) { my $cmd = $a[$i]; - if ($st eq "actuator.01") { + if ($cmd eq "remoteID") { + $cmdID = 4; + $manufID = 0x7FF; + #$packetType = 7; + $rorg = "C5"; + my $seq = int(rand(2) + 1) << 6; + shift(@a); + Log3 $name, 3, "EnOcean get $name $cmd"; + my $cntr = (((3 << 11) | $manufID) << 12) | $cmdID; + $data = sprintf "%02X%08X%06X00", $seq, $cntr, ($eep << 3) | 1; + #$data = sprintf "0004%04X", $manufID; + $destinationID = "FFFFFFFF"; + + } elsif ($cmd eq "remotePing") { + $cmdID = 6; + $manufID = 0x7FF; + $packetType = 7; + $rorg = "C5"; + my $seq = int(rand(2) + 1) << 6; + shift(@a); + Log3 $name, 3, "EnOcean get $name $cmd"; + my $cntr = ($manufID << 12) | $cmdID; + #$data = sprintf "%02X%08X00000000", $seq, $cntr; + $data = sprintf "0006%04X", $manufID; + $destinationID = $hash->{DEF}; + #($rorg, $data) = EnOcean_encapsulation($packetType, $rorg, $data, $destinationID); + + } elsif ($cmd eq "remoteCommands") { + $cmdID = 7; + $manufID = 0x7FF; + $rorg = "C5"; + my $seq = int(rand(2) + 1) << 6; + shift(@a); + Log3 $name, 3, "EnOcean get $name $cmd"; + my $cntr = ($manufID << 12) | $cmdID; + $data = sprintf "%02X%08X00000000", $seq, $cntr; + $destinationID = $hash->{DEF}; + ($rorg, $data) = EnOcean_encapsulation($packetType, $rorg, $data, $destinationID); + + } elsif ($cmd eq "remoteStatus") { + $cmdID = 8; + $manufID = 0x7FF; + $rorg = "C5"; + my $seq = int(rand(2) + 1) << 6; + shift(@a); + Log3 $name, 3, "EnOcean get $name $cmd"; + my $cntr = ($manufID << 12) | $cmdID; + $data = sprintf "%02X%08X00000000", $seq, $cntr; + $destinationID = $hash->{DEF}; + ($rorg, $data) = EnOcean_encapsulation($packetType, $rorg, $data, $destinationID); + + } elsif ($st eq "actuator.01") { # Electronic switches and dimmers with Energy Measurement and Local Control # (D2-01-00 - D2-01-11) $rorg = "D2"; shift(@a); - my $cmdID; my $channel = shift(@a); $channel = AttrVal($name, "defaultChannel", AttrVal($name, "devChannel", undef)) if (!defined $channel); if (!defined $channel || $channel eq "all") { @@ -437,20 +531,54 @@ EnOcean_Get ($@) } else { if ($manufID eq "033") { - return "Unknown argument $cmd, choose one of state measurement special"; + return "Unknown argument $cmd, choose one of $cmdList state measurement special"; } else { - return "Unknown argument $cmd, choose one of state measurement"; + return "Unknown argument $cmd, choose one of $cmdList state measurement"; } } + } elsif ($st eq "roomCtrlPanel.00") { + # Room Control Panel + # (D2-10-00 - D2-10-02) + $rorg = "D2"; + $updateState = 2; + if ($cmd eq "data") { + # data request + readingsSingleUpdate($hash, "waitingCmds", ReadingsVal($name, "waitingCmds", 0) | 1, 0); + Log3 $name, 3, "EnOcean get $name $cmd"; + + } elsif ($cmd eq "config") { + # configuration request + readingsSingleUpdate($hash, "waitingCmds", ReadingsVal($name, "waitingCmds", 0) | 32, 0); + Log3 $name, 3, "EnOcean get $name $cmd"; + + } elsif ($cmd eq "roomCtrl") { + # room control setup request + readingsSingleUpdate($hash, "waitingCmds", ReadingsVal($name, "waitingCmds", 0) | 128, 0); + Log3 $name, 3, "EnOcean get $name $cmd"; + + } elsif ($cmd eq "timeProgram") { + # time program request + readingsSingleUpdate($hash, "waitingCmds", ReadingsVal($name, "waitingCmds", 0) | 256, 0); + Log3 $name, 3, "EnOcean get $name $cmd"; + + } else { + $cmdList .= "data:noArg config:noArg roomCtrl:noArg timeProgram:noArg"; + return "Unknown argument $cmd, choose one of $cmdList"; + } + } else { # subtype does not support get commands - return; - + if (AttrVal($name, "remoteManagement", "off") eq "on") { + return "Unknown argument $cmd, choose one of $cmdList"; + } else { + return; + } + } + + if($updateState != 2) { + EnOcean_SndRadio(undef, $hash, $packetType, $rorg, $data, $subDef, $status, $destinationID); } - EnOcean_SndRadio(undef, $hash, $rorg, $data, $subDef, $status, $destinationID); - # next commands will be sent with a delay, previously 0.2 sec - ###select(undef, undef, undef, 0.1); } } @@ -479,6 +607,8 @@ EnOcean_Set($@) $destinationID = uc($destinationID); my $manufID = uc(AttrVal($name, "manufID", "")); my $model = AttrVal($name, "model", ""); + my $packetType = 1; + $packetType = 0x0A if (ReadingsVal($hash->{IODev}, "mode", "00") eq "01"); my $rorg; my $sendCmd = "yes"; my $status = "00"; @@ -499,6 +629,7 @@ EnOcean_Set($@) for (my $i = 0; $i < @a; $i++) { my $cmd = $a[$i]; + my ($cmd1, $cmd2); if ($st eq "roomSensorControl.05") { # Room Sensor and Control Unit (EEP A5-10-01 ... A5-10-0D) @@ -563,8 +694,8 @@ EnOcean_Set($@) } else { $nightReduction = 0; } - $actualTemp = (40 - $actualTemp) / 40 * 255; - $setpointTemp = $setpointTemp * 255 / 40; + $actualTemp = (40 - $actualTemp) / 40 * 254; + $setpointTemp = $setpointTemp * 254 / 40; # control of the aditional Room Sensor and Control Unit if ($block eq "lock") { # temperature setting is locked @@ -612,8 +743,8 @@ EnOcean_Set($@) } else { $nightReduction = 0; } - $actualTemp = (40 - $actualTemp) / 40 * 255; - $setpointTemp = $setpointTemp * 255 / 40; + $actualTemp = (40 - $actualTemp) / 40 * 254; + $setpointTemp = $setpointTemp * 254 / 40; # control of the aditional Room Sensor and Control Unit if ($block eq "lock") { # temperature setting is locked @@ -665,7 +796,7 @@ EnOcean_Set($@) } else { return "Usage: $a[1] is not numeric, out of range or unknown"; } - $actualTemp = (40 - $actualTemp) / 40 * 255; + $actualTemp = (40 - $actualTemp) / 40 * 254; $updateState = 0; $data = sprintf "%02X%02X%02X%02X", $fanStage, $setpoint, $actualTemp, $setCmd; @@ -1300,7 +1431,9 @@ EnOcean_Set($@) my $angleTime = AttrVal($name, "angleTime", 0); my $position = ReadingsVal($name, "position", undef); my $positionStart; - if ($cmd ne "?") { + if ($cmd eq "?" || $cmd eq "stop") { + + } else { # check actual shutter position my $actualState = ReadingsVal($name, "state", undef); if (defined $actualState) { @@ -1320,7 +1453,7 @@ EnOcean_Set($@) $rorg = "A5"; my $shutTime = AttrVal($name, "shutTime", 255); my $shutTimeCloses = AttrVal($name, "shutTimeCloses", $shutTime); - $shutTimeCloses = $shutTime if ($shutTimeCloses < $shutTimeCloses); + $shutTimeCloses = $shutTime if ($shutTimeCloses < $shutTime); my $shutCmd = 0; $angleMax = 90 if ($angleMax !~ m/^[+-]?\d+$/); $angleMax = 180 if ($angleMax > 180); @@ -1986,6 +2119,154 @@ EnOcean_Set($@) } Log3 $name, 3, "EnOcean set $name $cmd $data"; + } elsif ($st eq "roomCtrlPanel.00") { + # Room Control Panel + # (D2-10-00 - D2-10-02) + $rorg = "D2"; + $updateState = 2; + if ($cmd eq "desired-temp"|| $cmd eq "setpointTemp") { + if (defined $a[1]) { + if (($a[1] =~ m/^[+-]?\d+(\.\d+)?$/) && ($a[1] >= 0) && ($a[1] <= 40)) { + readingsSingleUpdate($hash, "setpointTemp", $a[1], 1); + readingsSingleUpdate($hash, "setpointTempSet", $a[1], 0); + Log3 $name, 3, "EnOcean set $name setpointTemp $a[1]"; + shift(@a); + readingsSingleUpdate($hash, "waitingCmds", ReadingsVal($name, "waitingCmds", 0) | 2, 0); + } else { + return "Usage: $a[1] is not numeric or out of range"; + } + } + + } elsif ($cmd eq "economyTemp" || $cmd eq "preComfortTemp" || $cmd eq "buildingProtectionTemp" || $cmd eq "comfortTemp") { + if (defined $a[1]) { + if (($a[1] =~ m/^[+-]?\d+(\.\d+)?$/) && ($a[1] >= 0) && ($a[1] <= 40)) { + Log3 $name, 3, "EnOcean set $name $cmd $a[1]"; + $cmd =~ s/(\b)([a-z])/$1\u$2/g; + readingsSingleUpdate($hash, "setpoint" . $cmd, $a[1], 1); + readingsSingleUpdate($hash, "setpoint" . $cmd . "Set", $a[1], 0); + shift(@a); + readingsSingleUpdate($hash, "waitingCmds", ReadingsVal($name, "waitingCmds", 0) | 8, 0); + } else { + return "Usage: $a[1] is not numeric or out of range"; + } + } + + } elsif ($cmd eq "fanSpeed") { + if (defined $a[1]) { + if ($a[1] >= 0 && $a[1] <= 100) { + readingsSingleUpdate($hash, "fanSpeed", $a[1], 1); + readingsSingleUpdate($hash, "fanSpeedSet", $a[1], 0); + Log3 $name, 3, "EnOcean set $name fanSpeed $a[1]"; + shift(@a); + readingsSingleUpdate($hash, "waitingCmds", ReadingsVal($name, "waitingCmds", 0) | 2, 0); + } else { + return "Usage: $a[1] is wrong."; + } + } + + } elsif ($cmd eq "fanSpeedMode") { + if (defined $a[1]) { + if ($a[1] =~ m/^(central|local)$/) { + readingsSingleUpdate($hash, "fanSpeedMode", $a[1], 1); + readingsSingleUpdate($hash, "fanSpeedModeSet", $a[1], 0); + Log3 $name, 3, "EnOcean set $name fanSpeedMode $a[1]"; + shift(@a); + readingsSingleUpdate($hash, "waitingCmds", ReadingsVal($name, "waitingCmds", 0) | 2, 0); + } else { + return "Usage: $a[1] is wrong."; + } + } + + } elsif ($cmd eq "cooling") { + if (defined $a[1]) { + if ($a[1] =~ m/^(on|off|auto|no_change)$/) { + readingsSingleUpdate($hash, "cooling", $a[1], 1); + readingsSingleUpdate($hash, "coolingSet", $a[1], 0); + Log3 $name, 3, "EnOcean set $name cooling $a[1]"; + shift(@a); + readingsSingleUpdate($hash, "waitingCmds", ReadingsVal($name, "waitingCmds", 0) | 2, 0); + } else { + return "Usage: $a[1] is wrong."; + } + } + + } elsif ($cmd eq "heating") { + if (defined $a[1]) { + if ($a[1] =~ m/^(on|off|auto|no_change)$/) { + readingsSingleUpdate($hash, "heating", $a[1], 1); + readingsSingleUpdate($hash, "heatingSet", $a[1], 0); + Log3 $name, 3, "EnOcean set $name heating $a[1]"; + shift(@a); + readingsSingleUpdate($hash, "waitingCmds", ReadingsVal($name, "waitingCmds", 0) | 2, 0); + } else { + return "Usage: $a[1] is wrong."; + } + } + + } elsif ($cmd eq "roomCtrlMode") { + if (defined $a[1]) { + if ($a[1] =~ m/^(comfort|preComfort|economy|buildingProtection)$/) { + readingsSingleUpdate($hash, "roomCtrlMode", $a[1], 1); + readingsSingleUpdate($hash, "roomCtrlModeSet", $a[1], 0); + Log3 $name, 3, "EnOcean set $name roomCtrlMode $a[1]"; + shift(@a); + readingsSingleUpdate($hash, "waitingCmds", ReadingsVal($name, "waitingCmds", 0) | 2, 0); + } else { + return "Usage: $a[1] is wrong."; + } + } + + } elsif ($cmd eq "config") { + readingsSingleUpdate($hash, "waitingCmds", ReadingsVal($name, "waitingCmds", 0) | 64, 0); + Log3 $name, 3, "EnOcean set $name $cmd"; + + } elsif ($cmd eq "timeProgram") { + # delete remote and send new time program + delete $hash->{helper}{4}{telegramWait}; + for (my $messagePartCntr = 1; $messagePartCntr <= 4; $messagePartCntr ++) { + if (defined AttrVal($name, "timeProgram" . $messagePartCntr, undef)) { + $hash->{helper}{4}{telegramWait}{$messagePartCntr} = 1; + Log3 $name, 2, "EnOcean $name EnOcean_Set timeProgram" . $messagePartCntr . " set"; + } + } + readingsSingleUpdate($hash, "waitingCmds", ReadingsVal($name, "waitingCmds", 0) | 528, 0); + Log3 $name, 3, "EnOcean set $name $cmd"; + + } elsif ($cmd eq "deleteTimeProgram") { + readingsSingleUpdate($hash, "waitingCmds", ReadingsVal($name, "waitingCmds", 0) | 512, 0); + Log3 $name, 3, "EnOcean set $name $cmd"; + + } elsif ($cmd eq "time") { + readingsSingleUpdate($hash, "waitingCmds", ReadingsVal($name, "waitingCmds", 0) | 4, 0); + Log3 $name, 3, "EnOcean set $name $cmd"; + + } elsif ($cmd eq "window") { + if (defined $a[1]) { + if ($a[1] =~ m/^closed|open$/) { + readingsSingleUpdate($hash, "window", $a[1], 1); + readingsSingleUpdate($hash, "windowSet", $a[1], 0); + Log3 $name, 3, "EnOcean set $name window $a[1]"; + shift(@a); + readingsSingleUpdate($hash, "waitingCmds", ReadingsVal($name, "waitingCmds", 0) | 2, 0); + } else { + return "Usage: $a[1] is wrong."; + } + } + + } elsif ($cmd eq "clearCmds") { + CommandDeleteReading(undef, "$name waitingCmds"); + Log3 $name, 3, "EnOcean set $name $cmd"; + + } else { + my $cmdList = "cooling:auto,off,on,no_change desired-temp setpointTemp:slider,0,1,40 " . + "comfortTemp deleteTimeProgram:noArg " . + "economyTemp preComfortTemp buildingProtectionTemp config:noArg " . + "clearCmds:noArg fanSpeed:slider,0,1,100 heating:auto,off,on,no_change " . + "fanSpeedMode:central,local time:noArg " . + "roomCtrlMode:comfort,economy,preComfort,buildingProtection timeProgram:noArg window:closed,open"; + return "Unknown argument $cmd, choose one of $cmdList"; + } + } elsif ($st eq "contact") { # 1BS Telegram # Single Input Contact (EEP D5-00-01) @@ -2075,9 +2356,9 @@ EnOcean_Set($@) } elsif ($st eq "switch") { # Rocker Switch, simulate a PTM200 switch module # separate first and second action - my ($c1,$c2) = split(",", $cmd, 2); + ($cmd1, $cmd2) = split(",", $cmd, 2); # check values - if (!defined($EnO_ptm200btn{$c1}) || ($c2 && !defined($EnO_ptm200btn{$c2}))) { + if (!defined($EnO_ptm200btn{$cmd1}) || ($cmd2 && !defined($EnO_ptm200btn{$cmd2}))) { my $list = join(" ", sort keys %EnO_ptm200btn); return SetExtensions($hash, $list, $name, @a); } @@ -2090,55 +2371,55 @@ EnOcean_Set($@) my $switchType = AttrVal($name, "switchType", "direction"); # first action if ($switchType eq "central") { - if ($c1 =~ m/.0/ || $c1 eq "released") { + if ($cmd1 =~ m/.0/ || $cmd1 eq "released") { $subDef = $subDef0; } else { $subDef = $subDefI; } } if ($switchType eq "universal") { - if ($c1 =~ m/A0|AI/ && (!$channelA || ($c1 ne $channelA))) { - $c1 = "A0"; - } elsif ($c1 =~ m/B0|BI/ && (!$channelB || $c1 ne $channelB)) { - $c1 = "B0"; - } elsif ($c1 =~ m/C0|CI/ && (!$channelC || ($c1 ne $channelC))) { - $c1 = "C0"; - } elsif ($c1 =~ m/D0|DI/ && (!$channelD || ($c1 ne $channelD))) { - $c1 = "D0"; - } elsif ($c1 eq "released") { + if ($cmd1 =~ m/A0|AI/ && (!$channelA || ($cmd1 ne $channelA))) { + $cmd1 = "A0"; + } elsif ($cmd1 =~ m/B0|BI/ && (!$channelB || $cmd1 ne $channelB)) { + $cmd1 = "B0"; + } elsif ($cmd1 =~ m/C0|CI/ && (!$channelC || ($cmd1 ne $channelC))) { + $cmd1 = "C0"; + } elsif ($cmd1 =~ m/D0|DI/ && (!$channelD || ($cmd1 ne $channelD))) { + $cmd1 = "D0"; + } elsif ($cmd1 eq "released") { } else { $sendCmd = "no"; } } # second action - if ($c2 && $switchType eq "universal") { - if ($c2 =~ m/A0|AI/ && (!$channelA || ($c2 ne $channelA))) { - $c2 = "A0"; - } elsif ($c2 =~ m/B0|BI/ && (!$channelB || $c2 ne $channelB)) { - $c2 = "B0"; - } elsif ($c2 =~ m/C0|CI/ && (!$channelC || ($c2 ne $channelC))) { - $c2 = "C0"; - } elsif ($c2 =~ m/D0|DI/ && (!$channelD || ($c2 ne $channelD))) { - $c2 = "D0"; + if ($cmd2 && $switchType eq "universal") { + if ($cmd2 =~ m/A0|AI/ && (!$channelA || ($cmd2 ne $channelA))) { + $cmd2 = "A0"; + } elsif ($cmd2 =~ m/B0|BI/ && (!$channelB || $cmd2 ne $channelB)) { + $cmd2 = "B0"; + } elsif ($cmd2 =~ m/C0|CI/ && (!$channelC || ($cmd2 ne $channelC))) { + $cmd2 = "C0"; + } elsif ($cmd2 =~ m/D0|DI/ && (!$channelD || ($cmd2 ne $channelD))) { + $cmd2 = "D0"; } else { - $c2 = undef; + $cmd2 = undef; } - if ($c2 && $sendCmd eq "no") { + if ($cmd2 && $sendCmd eq "no") { # only second action has changed, send as first action - $c1 = $c2; - $c2 = undef; + $cmd1 = $cmd2; + $cmd2 = undef; $sendCmd = "yes"; } } # convert and send first and second command my $switchCmd; - ($switchCmd, $status) = split(":", $EnO_ptm200btn{$c1}, 2); + ($switchCmd, $status) = split(":", $EnO_ptm200btn{$cmd1}, 2); $switchCmd <<= 5; - $switchCmd |= 0x10 if($c1 ne "released"); # set the pressed flag - if($c2 && $switchType ne "central") { - my ($d2, undef) = split(":", $EnO_ptm200btn{$c2}, 2); - $switchCmd |= ($d2<<1) | 0x01; + $switchCmd |= 0x10 if($cmd1 ne "released"); # set the pressed flag + if($cmd2 && $switchType ne "central") { + my ($d2, undef) = split(":", $EnO_ptm200btn{$cmd2}, 2); + $switchCmd |= ($d2 << 1) | 0x01; } if ($sendCmd ne "no") { $data = sprintf "%02X", $switchCmd; @@ -2146,6 +2427,8 @@ EnOcean_Set($@) Log3 $name, 2, "EnOcean set $name $cmd"; } + } elsif (AttrVal($name, "remoteManagement", "off") eq "on") { + } else { # subtype does not support set commands $updateState = -1; @@ -2154,19 +2437,20 @@ EnOcean_Set($@) # send commands if($updateState != 2) { - EnOcean_SndRadio(undef, $hash, $rorg, $data, $subDef, $status, $destinationID); - if ($switchMode eq "pushbutton") { - $data = "00"; - $rorg = "F6"; - $status = "20"; - # next commands will be sent with a delay, previously 0.2 sec - ###select(undef, undef, undef, 0.1); - Log3 $name, 2, "EnOcean set $name released"; - EnOcean_SndRadio(undef, $hash, $rorg, $data, $subDef, $status, $destinationID); + EnOcean_SndRadio(undef, $hash, $packetType, $rorg, $data, $subDef, $status, $destinationID); + if ($switchMode eq "pushbutton" && $cmd1 ne "released") { + #$data = "00"; + #$rorg = "F6"; + #$status = "20"; + #Log3 $name, 2, "EnOcean set $name released"; + ### + #EnOcean_SndRadio(undef, $hash, $packetType, $rorg, $data, $subDef, $status, $destinationID); + my @timerCmd = ($name, "released"); + my %par = (hash => $hash, timerCmd => \@timerCmd); + InternalTimer(gettimeofday() + 0.1, "EnOcean_TimerSet", \%par, 0); + } } - # next commands will be sent with a delay, previously 0.2 sec - ###select(undef, undef, undef, 0.1); } # set reading state if acknowledge is not expected @@ -2182,44 +2466,77 @@ sub EnOcean_Parse($$) { my ($iohash, $msg) = @_; - my $err; - my (undef, $packetType, $rorg, $data, $id, $status, $odata) = split(":", $msg); - $odata =~ m/^(..)(........)(..)(..)$/; - my ($subTelNum, $destinationID, $RSSI, $securityLevel) = (hex($1), $2, hex($3), hex($4)); - my $rorgname = $EnO_rorgname{$rorg}; - if (!$rorgname) { - Log3 undef, 1, "EnOcean RORG ($rorg) received from $id unknown."; - return ""; - } - my $hash = $modules{EnOcean}{defptr}{$id}; - if (!$hash) { - # SenderID unknown, created new device - my $learningMode = AttrVal($iohash->{NAME}, "learningMode", "demand"); - if ($learningMode eq "demand" && $iohash->{Teach}) { - Log3 undef, 1, "EnOcean Unknown device with ID $id and RORG $rorgname, please define it."; - return "UNDEFINED EnO_${rorgname}_$id EnOcean $id $msg"; - } elsif ($learningMode eq "nearfield" && $iohash->{Teach} && $RSSI <= 60) { - Log3 undef, 1, "EnOcean Unknown device with ID $id and RORG $rorgname, please define it."; - return "UNDEFINED EnO_${rorgname}_$id EnOcean $id $msg"; - } elsif ($learningMode eq "always") { - if ($rorgname eq "UTE") { - if ($iohash->{Teach}) { - Log3 undef, 1, "EnOcean Unknown device with ID $id and RORG $rorgname, please define it."; - return "UNDEFINED EnO_${rorgname}_$id EnOcean $id $msg"; - } else { - Log3 undef, 1, "EnOcean Unknown device with ID $id and RORG $rorgname, activate learning mode."; - return ""; - } - } else { - Log3 undef, 1, "EnOcean Unknown device with ID $id and RORG $rorgname, please define it."; - return "UNDEFINED EnO_${rorgname}_$id EnOcean $id $msg"; - } - } else { - Log3 undef, 4, "EnOcean Unknown device with ID $id and RORG $rorgname, activate learning mode."; + my ($hash, $name, $rorgname); + my ($err, $response); + Log3 undef, 5, "EnOcean received $msg"; + my @msg = split(":", $msg); + my ($packetType, $rorg, $data, $id, $status, $odata, $destinationID, $fnNumber, $manufID, $RSSI, $delay, $subTelNum); + $packetType = hex($msg[1]); + + if ($packetType == 1) { + # packet type RADIO + (undef, undef, $rorg, $data, $id, $status, $odata) = @msg; + $hash = $modules{EnOcean}{defptr}{$id}; + $odata =~ m/^(..)(........)(..)(..)$/; + ($subTelNum, $destinationID, $RSSI) = (hex($1), $2, hex($3)); + $rorgname = $EnO_rorgname{$rorg}; + if (!$rorgname) { + Log3 undef, 1, "EnOcean RORG $rorg received from $id unknown."; return ""; } + if($hash) { + $name = $hash->{NAME}; + Log3 $name, 5, "EnOcean $name received PacketType: $packetType RORG: $rorg DATA: $data SenderID: $id STATUS: $status"; + } else { + Log3 undef, 5, "EnOcean received PacketType: $packetType RORG: $rorg DATA: $data SenderID: $id STATUS: $status"; + # SenderID unknown, created new device + my $learningMode = AttrVal($iohash->{NAME}, "learningMode", "demand"); + if ($learningMode eq "demand" && $iohash->{Teach}) { + Log3 undef, 1, "EnOcean Unknown device with SenderID $id and RORG $rorg, please define it."; + return "UNDEFINED EnO_${rorgname}_$id EnOcean $id $msg"; + } elsif ($learningMode eq "nearfield" && $iohash->{Teach} && $RSSI <= 60) { + Log3 undef, 1, "EnOcean Unknown device with SenderID $id and RORG $rorg, please define it."; + return "UNDEFINED EnO_${rorgname}_$id EnOcean $id $msg"; + } elsif ($learningMode eq "always") { + if ($rorgname eq "UTE") { + if ($iohash->{Teach}) { + Log3 undef, 1, "EnOcean Unknown device with SenderID $id and RORG $rorg, please define it."; + return "UNDEFINED EnO_${rorgname}_$id EnOcean $id $msg"; + } else { + Log3 undef, 1, "EnOcean Unknown device with SenderID $id and RORG $rorg, activate learning mode."; + return ""; + } + } else { + Log3 undef, 1, "EnOcean Unknown device with SenderID $id and RORG $rorg, please define it."; + return "UNDEFINED EnO_${rorgname}_$id EnOcean $id $msg"; + } + } else { + Log3 undef, 4, "EnOcean Unknown device with SenderID $id and RORG $rorg, activate learning mode."; + return ""; + } + + } + + } elsif ($packetType == 7) { + # packet type REMOTE_MAN_COMMAND + #EnOcean:PacketType:RORG:MessageData:SourceID:DestinationID:FunctionNumber:ManufacturerID:RSSI:Delay + (undef, undef, $rorg, $data, $id, $destinationID, $fnNumber, $manufID, $RSSI, $delay) = @msg; + $hash = $modules{EnOcean}{defptr}{$id}; + $rorgname = $EnO_rorgname{$rorg}; + if($hash) { + $name = $hash->{NAME}; + Log3 $name, 2, "EnOcean $name received PacketType: $packetType RORG: $rorg DATA: $data SenderID: $id + DestinationID $destinationID FunctionNumber: $fnNumber ManufacturerID: $manufID"; + } else { + Log3 undef, 2, "EnOcean received PacketType: $packetType RORG: $rorg DATA: $data SenderID: $id + DestinationID $destinationID FunctionNumber: $fnNumber ManufacturerID: $manufID"; + return ""; + } + $fnNumber = hex($fnNumber); + $RSSI = hex($RSSI); + $delay = hex($delay); } - my $name = $hash->{NAME}; + my $teach = $defs{$name}{IODev}{Teach}; my $teachOut; @@ -2237,6 +2554,23 @@ EnOcean_Parse($$) } } + if ($rorg eq "A6") { + # addressing destination telegram (ADT) + # reconstruct RORG, DATA + $data =~ m/^(..)(.*)(........)$/; + ($rorg, $data) = ($1, $2); + $rorgname = $EnO_rorgname{$rorg}; + if (!$rorgname) { + Log3 undef, 1, "EnOcean RORG $rorg received from $id unknown."; + return ""; + } + if ($destinationID ne $3) { + Log3 $name, 1, "EnOcean $name ADT DestinationID wrong."; + return ""; + } + Log3 $name, 1, "EnOcean $name ADT decapsulation RORG: $rorg DATA: $data DestinationID: $3"; + } + # extract data bytes $db[x] ... $db[0] my @db; my $dbCntr = 0; @@ -2244,13 +2578,13 @@ EnOcean_Parse($$) $db[$dbCntr] = hex substr($data, $strCntr * 2, 2); $dbCntr++; } + my @event; my $model = AttrVal($name, "model", ""); - my $manufID = uc(AttrVal($name, "manufID", "")); + $manufID = uc(AttrVal($name, "manufID", "")); my $st = AttrVal($name, "subType", ""); my $subtypeReading = AttrVal($name, "subTypeReading", undef); - - Log3 $name, 5, "EnOcean $name received PacketType: $packetType RORG: $rorg DATA: $data ID: $id STATUS: $status"; + $st = $subtypeReading if (defined $subtypeReading); if ($rorg eq "F6") { # RPS Telegram (PTM200) @@ -2333,6 +2667,20 @@ EnOcean_Parse($$) $msg = "down"; } + } elsif ($st eq "switch.7F" && $manufID eq "00D") { + $msg = $EnO_ptm200btn[($db[0] & 0xE0) >> 5]; + $msg .= " " . $EnO_ptm200btn[($db[0] & 0x0E) >> 1] if ($db[0] & 1); + $msg .= " released" if (!($db[0] & 0x10)); + push @event, "3:buttons:" . ($db[0] & 0x10 ? "pressed" : "released"); + if ($msg =~ m/A0/) {push @event, "3:channelA:A0";} + if ($msg =~ m/AI/) {push @event, "3:channelA:AI";} + if ($msg =~ m/B0/) {push @event, "3:channelB:B0";} + if ($msg =~ m/BI/) {push @event, "3:channelB:BI";} + if ($msg =~ m/C0/) {push @event, "3:channelC:C0";} + if ($msg =~ m/CI/) {push @event, "3:channelC:CI";} + if ($msg =~ m/D0/) {push @event, "3:channelD:D0";} + if ($msg =~ m/DI/) {push @event, "3:channelD:DI";} + } else { if ($nu) { # Theoretically there can be a released event with some of the A0, BI @@ -2387,6 +2735,7 @@ EnOcean_Parse($$) # [Eltako FTK, STM-250] push @event, "3:state:" . ($db[0] & 1 ? "closed" : "open"); if (!($db[0] & 8)) { + $attr{$name}{eep} = "D5-00-01"; push @event, "3:teach-in:EEP D5-00-01 Manufacturer: no ID"; Log3 $name, 2, "EnOcean $name teach-in EEP D5-00-01 Manufacturer: no ID"; } @@ -2404,6 +2753,7 @@ EnOcean_Parse($$) $attr{$name}{manufID} = $mf; $mf = $EnO_manuf{$mf} if($EnO_manuf{$mf}); my $st = "A5.$fn.$tp"; + $attr{$name}{eep} = "A5-$fn-$tp"; if($EnO_subType{$st}) { $st = $EnO_subType{$st}; push @event, "3:teach-in:EEP A5-$fn-$tp Manufacturer: $mf"; @@ -2425,13 +2775,9 @@ EnOcean_Parse($$) my $subDef = "00000000"; $subDef = EnOcean_CheckSenderID("getNextID", $defs{$name}{IODev}{NAME}, $subDef); $attr{$name}{subDef} = $subDef; - # next commands will be sent with a delay, max 10 s - ###select(undef, undef, undef, 0.1); # teach-in response - EnOcean_SndRadio(undef, $hash, $rorg, "800FFFF0", $subDef, "00", $hash->{DEF}); - # next commands will be sent with a delay, previously 0.5 sec - ###select(undef, undef, undef, 0.1); - EnOcean_hvac_01Cmd($hash, 128); # 128 == 20 degree C + EnOcean_SndRadio(undef, $hash, $packetType, $rorg, "800FFFF0", $subDef, "00", $hash->{DEF}); + EnOcean_hvac_01Cmd($hash, $packetType, 128); # 128 == 20 degree C } else { Log3 $name, 1, "EnOcean Unknown device $name and subType $st, set transceiver in teach mode."; return ""; @@ -2486,7 +2832,7 @@ EnOcean_Parse($$) push @event, "3:actuatorStatus:".(($db[2] & 0x01) ? "obstructed" : "ok"); push @event, "3:measured-temp:". sprintf "%0.1f", ($db[1]*40/255); push @event, "3:selfCtl:" . (($db[0] & 0x04) ? "on" : "off"); - EnOcean_hvac_01Cmd($hash, $db[1]); + EnOcean_hvac_01Cmd($hash, $packetType, $db[1]); } elsif ($st eq "PM101") { # Light and Presence Sensor [Omnio Ratio eagle-PM101] @@ -3035,6 +3381,28 @@ EnOcean_Parse($$) push @event, "3:humidity:$humi"; push @event, "3:temperature:$temp"; + } elsif ($st eq "tempHumiSensor.03") { + # Temperatur and Humidity Sensor(EEP A5-04-03) + # [untested] + # $db[3] is the humidity where 0x00 = 0%rH ... 0xFF = 100%rH + # $db[2] .. $db[1] is the temperature where 0x00 = -20°C ... 0x3FF = +60°C + my $humi = sprintf "%d", $db[3] / 2.55; + my $temp = sprintf "%0.1f", -20 + ($db[2] << 8 | $db[1]) * 80 / 1023; + push @event, "3:state:T: $temp H: $humi"; + push @event, "3:humidity:$humi"; + push @event, "3:temperature:$temp"; + push @event, "3:telegramType:" . ($db[0] & 1 ? "event" : "heartbeat"); + + } elsif ($st eq "baroSensor.01") { + # Barometric Sensor(EEP A5-04-03) + # [untested] + # $db[3] is the humidity where 0x00 = 0%rH ... 0xFF = 100%rH + # $db[3] .. $db[2] is the barometric where 0x00 = 500 hPa ... 0x3FF = 1150 hPa + my $baro = sprintf "%d", 500 + ($db[2] << 8 | $db[1]) * 650 / 1023; + push @event, "3:state:$baro"; + push @event, "3:airPressure:$baro"; + push @event, "3:telegramType:" . ($db[0] & 1 ? "event" : "heartbeat"); + } elsif ($st eq "lightSensor.01") { # Light Sensor (EEP A5-06-01) # [Eltako FAH60, FAH63, FIH63, Thermokon SR65 LI, untested] @@ -3283,11 +3651,11 @@ EnOcean_Parse($$) if ($alarm == 0) { push @event, "3:alarm:off"; } elsif ($alarm == 1){ - push @event, "3:alarm:no endpoints defined"; + push @event, "3:alarm:no_endpoints_defined"; } elsif ($alarm == 2){ push @event, "3:alarm:on"; } elsif ($alarm == 3){ - push @event, "3:alarm:not used"; + push @event, "3:alarm:not_used"; } my $endPosition = ($db[1] & 0x0C) >> 2; if ($endPosition == 0) { @@ -3313,8 +3681,8 @@ EnOcean_Parse($$) } elsif (($db[1] & 3) == 3){ push @event, "3:shutterState:closes"; } - push @event, "3:serviceOn:" . ($db[2] & 0x80 ? "yes" : "no"); - push @event, "3:positionMode:" . ($db[2] & 0x40 ? "inverse" : "normal"); + push @event, "3:serviceOn:" . ($db[0] & 0x80 ? "yes" : "no"); + push @event, "3:positionMode:" . ($db[0] & 0x40 ? "inverse" : "normal"); } elsif ($st eq "lightCtrlState.02") { # Extended Lighting Status (EEP A5-11-04) @@ -3774,7 +4142,7 @@ EnOcean_Parse($$) } elsif ($getParam == 0x81) { $hash->{READINGS}{serialNumber}{VAL} = substr($data, 4, 4); $hash->{READINGS}{getParam}{VAL} = 0x82; - EnOcean_SndRadio(undef, $hash, "D1", "033182", AttrVal($name, "subDef", "00000000"), "00", $hash->{DEF}); + EnOcean_SndRadio(undef, $hash, $packetType, "D1", "033182", AttrVal($name, "subDef", "00000000"), "00", $hash->{DEF}); } elsif ($getParam == 0x82) { push @event, "3:serialNumber:" . $hash->{READINGS}{serialNumber}{VAL} . substr($data, 4, 4); CommandDeleteReading(undef, "$name getParam"); @@ -3791,8 +4159,9 @@ EnOcean_Parse($$) } } else { # unknown devices - push @event, "3:manufID:" . substr($data, 0, 3); - push @event, "3:state:$data"; + if(AttrVal($name, "blockUnknownMSC", "no") eq "yes") { + push @event, "3:MSC:$data"; + } } } elsif ($rorg eq "D2") { @@ -3944,168 +4313,311 @@ EnOcean_Parse($$) } } elsif ($st eq "roomCtrlPanel.00") { - ### Test - #push @event, "3:state:RORG: $rorg DATA: $data STATUS: $status ODATA: $odata"; + # EEP D2-10-01 - D2-10-03 + my ($key, $val); # message identifier my $mid = hex(substr($data, 0, 2)) >> 5; # message continuation flag my $mcf = hex(substr($data, 0, 2)) & 3; + my ($irc, $fbc, $gmt); + if ($mcf == 0) { # message complete - push @event, "3:message:complete"; + # read stored telegrams + if (!defined($hash->{helper}{$mid}{messagePart})) { + $hash->{helper}{$mid}{messagePart} = 1; + $hash->{helper}{$mid}{data}{1} = $data; + } else { + $hash->{helper}{$mid}{messagePart} += 1; + $hash->{helper}{$mid}{data}{$hash->{helper}{$mid}{messagePart}} = $data; + } + if ($mid == 4) { + CommandDeleteAttr(undef, "$name timeProgram1"); + CommandDeleteAttr(undef, "$name timeProgram2"); + CommandDeleteAttr(undef, "$name timeProgram3"); + CommandDeleteAttr(undef, "$name timeProgram4"); + } + + for (my $partCntr = $hash->{helper}{$mid}{messagePart}; $partCntr > 0; $partCntr --) { + $data = $hash->{helper}{$mid}{data}{$partCntr}; + delete $hash->{helper}{$mid}{data}{$partCntr}; + if ($partCntr == 1) { + delete $hash->{helper}{$mid}{messagePart}; + } else { + $hash->{helper}{$mid}{messagePart} --; + } + $dbCntr = 0; + for (my $strCntr = length($data) / 2 - 1; $strCntr >= 0; $strCntr --) { + $db[$dbCntr] = hex substr($data, $strCntr * 2, 2); + $dbCntr++; + } + + #Log3 $name, 2, "EnOcean $name EnOcean_Parse write MID $mid DATA $data to part $partCntr"; + #Log3 $name, 2, "EnOcean $name EnOcean_Parse write 1 MID $mid DATA $data to " . sprintf "%02X%02X%02X%02X%02X%02X", $db[5], $db[4], $db[3], $db[2], $db[1], $db[0]; + + if ($mid == 0) { + # general message + $irc = ($db[0] & 56) >> 3; + $fbc = ($db[0] & 6) >> 1; + $gmt = $db[0] & 1; + #push @event, "3:general:$data"; + + } elsif ($mid == 1) { + # data message + my $temperature = "-"; + $temperature = sprintf "%.1f", $db[0] / 254 * 40 if ($db[2] & 1); + push @event, "3:temperature:$temperature"; + my $setpointTemp = "-"; + $setpointTemp = sprintf "%.1f", $db[1] / 254 * 40 if ($db[2] & 2); + push @event, "3:setpointTemp:$setpointTemp"; + my $roomCtrlMode = ($db[2] & 12) >> 2; + if ($roomCtrlMode == 3) { + $roomCtrlMode = "buildingProtection"; + } elsif ($roomCtrlMode == 2) { + $roomCtrlMode = "preComfort"; + } elsif ($roomCtrlMode == 1) { + $roomCtrlMode = "economy"; + } else{ + $roomCtrlMode = "comfort"; + } + push @event, "3:roomCtrlMode:$roomCtrlMode"; + my $heating = ($db[2] & 48) >> 4; + if ($heating == 3) { + $heating = "auto"; + } elsif ($heating == 2) { + $heating = "off"; + } elsif ($heating == 1) { + $heating = "on"; + } else{ + $heating = "-"; + } + if ($heating ne "-") { + push @event, "3:heating:$heating"; + } + my $cooling = ($db[2] & 192) >> 6; + if ($cooling == 3) { + $cooling = "auto"; + } elsif ($cooling == 2) { + $cooling = "off"; + } elsif ($cooling == 1) { + $cooling = "on"; + } else{ + $cooling = "-"; + } + if ($cooling ne "-") { + push @event, "3:cooling:$cooling"; + } + my $occupancy = $db[3] & 3; + if ($occupancy == 3) { + $occupancy = "reserved"; + } elsif ($occupancy == 2) { + $occupancy = "absent"; + } elsif ($occupancy == 1) { + $occupancy = "present"; + } else{ + $occupancy = "-"; + } + if ($occupancy eq "-") { + $occupancy = ReadingsVal($name, "occupancy", "-") + } else { + push @event, "3:occupancy:$occupancy"; + } + my $motion = ($db[3] & 12) >> 2; + if ($motion == 3) { + $motion = "reserved"; + } elsif ($motion == 2) { + $motion = "on"; + } elsif ($motion == 1) { + $motion = "off"; + } else{ + $motion = "-"; + } + if ($motion eq "-") { + $motion = ReadingsVal($name, "motion", "-") + } else { + push @event, "3:motion:$motion"; + } + push @event, "3:solarPowered:" . ($db[3] & 16 ? "no" : "yes"); + my $battery = ($db[3] & 96) >> 5; + if ($battery == 3) { + $battery = "empty"; + } elsif ($battery == 2) { + $battery = "low"; + } elsif ($battery == 1) { + $battery = "ok"; + } else{ + $battery = "-"; + } + if ($battery ne "-") { + push @event, "3:battery:$battery"; + } + my $window = $db[4] & 3; + if ($window == 3) { + $window = "reserved"; + } elsif ($window == 2) { + $window = "open"; + } elsif ($window == 1) { + $window = "closed"; + } else{ + $window = "-"; + } + if ($window ne "-") { + push @event, "3:window:$window"; + } + push @event, "3:moldWarning:" . ($db[4] & 4 ? "on" : "off"); + push @event, "3:customWarning1:" . ($db[4] & 8 ? "on" : "off"); + push @event, "3:customWarning2:" . ($db[4] & 16 ? "on" : "off"); + push @event, "3:fanSpeedMode:" . ($db[4] & 64 ? "local" : "central"); + my $fanSpeed = 0; + $fanSpeed = sprintf "%d", $db[5] & 127 if ($db[4] & 128); + push @event, "3:fanSpeed:$fanSpeed"; + my $humi = "-"; + $humi = sprintf "%d", $db[6] / 2.55 if ($db[5] & 128); + push @event, "3:humidity:$humi"; + push @event, "3:state:T: $temperature H: $humi F: $fanSpeed SPT: $setpointTemp O: $occupancy M: $motion"; + + } elsif ($mid == 2) { + # configuration message + $attr{$name}{blockFanSpeed} = $db[6] & 1 ? "no" : "yes"; + $attr{$name}{blockSetpointTemp} = $db[6] & 2 ? "no" : "yes"; + $attr{$name}{blockOccupancy} = $db[6] & 4 ? "no" : "yes"; + $attr{$name}{blockTimeProgram} = $db[6] & 8 ? "no" : "yes"; + $attr{$name}{blockDateTime} = $db[6] & 16 ? "no" : "yes"; + $attr{$name}{blockDisplay} = $db[6] & 32 ? "no" : "yes"; + $attr{$name}{blockTemp} = $db[6] & 64 ? "no" : "yes"; + $attr{$name}{blockMotion} = $db[6] & 128 ? "no" : "yes"; + my $pollInterval = $db[5] >> 2; + if ($pollInterval == 63) { + $attr{$name}{pollInterval} = 1440; + } elsif ($pollInterval == 62) { + $attr{$name}{pollInterval} = 720; + } elsif ($pollInterval == 61) { + $attr{$name}{pollInterval} = 180; + } else { + $attr{$name}{pollInterval} = $pollInterval; + } + $attr{$name}{blockKey} = $db[5] & 2 ? "no" : "yes"; + my $displayContent = $db[4] >> 5; + my %displayContent = (7 => "humidity", + 6 => "off", + 5 => "setPointTemp", + 4 => "tempertureExtern", + 3 => "temperatureIntern", + 2 => "time", + 1 => "default", + 0 => "no_change" + ); + while (($key, $val) = each(%displayContent)) { + $attr{$name}{displayContent} = $val if ($key == $displayContent); + } + my $temperatureScale = ($db[4] & 24) >> 3; + my %temperatureScale = (3 => "F", + 2 => "C", + 1 => "default", + 0 => "no_change" + ); + while (($key, $val) = each(%temperatureScale)) { + $attr{$name}{temperatureScale} = $val if ($key == $temperatureScale); + } + $attr{$name}{daylightSavingTime} = $db[4] & 4 ? "not_supported" : "supported"; + my $timeNotation = $db[4] & 3; + if ($timeNotation == 0) { + $attr{$name}{timeNotation} = "no_change"; + } elsif ($timeNotation == 1) { + $attr{$name}{timeNotation} = "default"; + } elsif ($timeNotation == 2) { + $attr{$name}{timeNotation} = 24; + } elsif ($timeNotation == 3) { + $attr{$name}{timeNotation} = 12; + } + CommandSave(undef, undef); + + } elsif ($mid == 3) { + # room control setup + my $setpointComfort = "-"; + $setpointComfort = sprintf "%.1f", $db[1] / 254 * 40 if ($db[0] & 1); + push @event, "3:setpointComfortTemp:$setpointComfort"; + my $setpointEconomy = "-"; + $setpointEconomy = sprintf "%.1f", $db[2] / 254 * 40 if ($db[0] & 2); + push @event, "3:setpointEconomyTemp:$setpointEconomy"; + my $setpointPreComfort = "-"; + $setpointPreComfort = sprintf "%.1f", $db[3] / 254 * 40 if ($db[0] & 4); + push @event, "3:setpointPreComfortTemp:$setpointPreComfort"; + my $setpointBuildingProtection = "-"; + $setpointBuildingProtection = sprintf "%.1f", $db[3] / 254 * 40 if ($db[0] & 8); + push @event, "3:setpointBuildingProtectionTemp:$setpointBuildingProtection"; + + } elsif ($mid == 4) { + # time program setup + my $timeProgram = "timeProgram" . $partCntr; + my $period = $db[0] >> 4; + my $periodVal = ""; + my %period = (15 => "FrMo", + 14 => "FrSu", + 13 => "ThFr", + 12 => "WeFr", + 11 => "TuTh", + 10 => "MoWe", + 9 => "Su", + 8 => "Sa", + 7 => "Fr", + 6 => "Th", + 5 => "We", + 4 => "Tu", + 3 => "Mo", + 2 => "SaSu", + 1 => "MoFr", + 0 => "MoSu" + ); + while (($key, $val) = each(%period)) { + $periodVal = $val if ($key == $period); + } + my $roomCtrlMode = ($db[0] & 12) >> 2; + if ($roomCtrlMode == 3) { + $roomCtrlMode = "buildingProtection"; + } elsif ($roomCtrlMode == 2) { + $roomCtrlMode = "preComfort"; + } elsif ($roomCtrlMode == 1) { + $roomCtrlMode = "economy"; + } else{ + $roomCtrlMode = "comfort"; + } + my ($startHour, $startMinute, $endHour, $endMinute) = ($db[1], $db[2], $db[3], $db[4]); + $startHour = $startHour < 10 ? $startHour = "0" . $startHour : $startHour; + $startMinute = $startMinute < 10 ? $startMinute = "0" . $startMinute : $startMinute; + $endHour = $endHour < 10 ? $endHour = "0" . $endHour : $endHour; + $endMinute = $endMinute < 10 ? $endMinute = "0" . $endMinute : $endMinute; + + #Log3 $name, 2, "EnOcean $name EnOcean_Parse write 2 MID $mid DATA $data to " . sprintf "%02X%02X%02X%02X%02X%02X", $db[5], $db[4], $db[3], $db[2], $db[1], $db[0]; + #Log3 $name, 2, "EnOcean $name EnOcean_Parse write 3 MID $mid DATA $data to $timeProgram VAL: $periodVal $startHour:$startMinute $endHour:$endMinute $roomCtrlMode"; + + $attr{$name}{$timeProgram} = "$periodVal $startHour:$startMinute $endHour:$endMinute $roomCtrlMode"; + + #Log3 $name, 2, "EnOcean $name EnOcean_Parse write 4 MID $mid DATA $data to $timeProgram VAL: $attr{$name}{$timeProgram}"; + + CommandSave(undef, undef); + } + } + ($err, $response) = EnOcean_roomCtrlPanel_00Snd(undef, $hash, $packetType, $mid, $mcf, $irc, $fbc, $gmt); + } elsif ($mcf == 1) { # message incomplete - push @event, "3:message:incomplete"; - - } elsif ($mcf == 2) { - # automatic message control - push @event, "3:message:autoCtrl"; - - } elsif ($mcf == 3) { - # reserved - + if (!defined($hash->{helper}{$mid}{messagePart})) { + $hash->{helper}{$mid}{messagePart} = 1; + } elsif ($hash->{helper}{$mid}{messagePart} >= 4) { + # max 4 message parts stored + for (my $partCntr = 1; $partCntr < $hash->{helper}{$mid}{messagePart}; $partCntr ++) { + $hash->{helper}{$mid}{data}{$partCntr} = $hash->{helper}{$mid}{data}{$partCntr + 1}; + } + } else { + $hash->{helper}{$mid}{messagePart} += 1; + } + $hash->{helper}{$mid}{data}{$hash->{helper}{$mid}{messagePart}} = $data; + #Log3 $name, 2, "EnOcean $name EnOcean_Parse store MID $mid DATA $data to messagePart $hash->{helper}{$mid}{messagePart}"; + ($err, $response) = EnOcean_roomCtrlPanel_00Snd(undef, $hash, $packetType, $mid, $mcf, undef, undef, undef); } - if ($mid == 0) { - # general message - my $irc = ($db[0] & 56) >> 3; - my $fbc = ($db[0] & 6) >> 1; - my $gmt = $db[0] & 1; - push @event, "3:general:$data"; - EnOcean_roomCtrlPanel_00Cmd(undef, $hash, $mid, $mcf, $irc, $fbc, $gmt); - - } elsif ($mid == 1) { - # data message - my $temperature = "-"; - $temperature = sprintf "%.1f", $db[0] / 255 * 40 if ($db[2] & 1); - push @event, "3:temperature:$temperature"; - my $setpointTemp = "-"; - $setpointTemp = sprintf "%.1f", $db[1] / 255 * 40 if ($db[2] & 2); - push @event, "3:setpointTemp:$setpointTemp"; - my $roomCtrlMode = ($db[2] & 12) >> 2; - if ($roomCtrlMode == 3) { - $roomCtrlMode = "buildingProtection"; - } elsif ($roomCtrlMode == 2) { - $roomCtrlMode = "preComfort"; - } elsif ($roomCtrlMode == 1) { - $roomCtrlMode = "economy"; - } else{ - $roomCtrlMode = "comfort"; - } - push @event, "3:roomCtrlMode:$roomCtrlMode"; - my $heating = ($db[2] & 48) >> 4; - if ($heating == 3) { - $heating = "auto"; - } elsif ($heating == 2) { - $heating = "off"; - } elsif ($heating == 1) { - $heating = "on"; - } else{ - $heating = "-"; - } - push @event, "3:heating:$heating"; - my $cooling = ($db[2] & 192) >> 6; - if ($cooling == 3) { - $cooling = "auto"; - } elsif ($cooling == 2) { - $cooling = "off"; - } elsif ($cooling == 1) { - $cooling = "on"; - } else{ - $cooling = "-"; - } - push @event, "3:cooling:$cooling"; - my $occupancy = $db[3] & 3; - if ($occupancy == 3) { - $occupancy = "reserved"; - } elsif ($occupancy == 2) { - $occupancy = "absent"; - } elsif ($occupancy == 1) { - $occupancy = "present"; - } else{ - $occupancy = "-"; - } - push @event, "3:occupancy:$occupancy"; - my $motion = ($db[3] & 12) >> 2; - if ($motion == 3) { - $motion = "reserved"; - } elsif ($motion == 2) { - $motion = "on"; - } elsif ($motion == 1) { - $motion = "off"; - } else{ - $motion = "-"; - } - push @event, "3:motion:$motion"; - push @event, "3:solarPowered:" . ($db[3] & 16 ? "no" : "yes"); - my $battery = ($db[3] & 96) >> 5; - if ($battery == 3) { - $battery = "empty"; - } elsif ($battery == 2) { - $battery = "low"; - } elsif ($battery == 1) { - $battery = "ok"; - } else{ - $battery = "-"; - } - push @event, "3:battery:$battery"; - my $window = $db[4] & 3; - if ($window == 3) { - $window = "reserved"; - } elsif ($window == 2) { - $window = "open"; - } elsif ($window == 1) { - $window = "closed"; - } else{ - $window = "-"; - } - push @event, "3:window:$window"; - push @event, "3:moldWarning:" . ($db[4] & 4 ? "on" : "off"); - push @event, "3:customWarning1:" . ($db[4] & 8 ? "on" : "off"); - push @event, "3:customWarning2:" . ($db[4] & 16 ? "on" : "off"); - my $fanSpeed = "-"; - $fanSpeed = sprintf "%d", $db[5] & 127 if ($db[4] & 128); - push @event, "3:fan:$fanSpeed"; - my $humi = "-"; - $humi = sprintf "%d", $db[6] / 2.55 if ($db[5] & 128); - push @event, "3:humidity:$humi"; - push @event, "3:state:T: $temperature H: $humi F: $fanSpeed SPT: $setpointTemp O: $occupancy M: $motion"; - #request configuration massage - EnOcean_SndRadio(undef, $hash, "D2", "0211", AttrVal($name, "subDef", "00000000"), "00", $hash->{DEF}); - Log3 $name, 2, "EnOcean $name request send"; - - } elsif ($mid == 2) { - # configuration message - push @event, "3:config:$data"; - EnOcean_SndRadio(undef, $hash, "D2", "0000", AttrVal($name, "subDef", "00000000"), "00", $hash->{DEF}); - Log3 $name, 2, "EnOcean $name acknowledge send"; - - } elsif ($mid == 3) { - # room control setup - my $setpointComfort = "-"; - $setpointComfort = sprintf "%.1f", $db[1] / 255 * 40 if ($db[0] & 1); - push @event, "3:setpointComfort:$setpointComfort"; - my $setpointEconomy = "-"; - $setpointEconomy = sprintf "%.1f", $db[2] / 255 * 40 if ($db[0] & 2); - push @event, "3:setpointEconomy:$setpointEconomy"; - my $setpointPreComfort = "-"; - $setpointPreComfort = sprintf "%.1f", $db[3] / 255 * 40 if ($db[0] & 4); - push @event, "3:setpointPreComfort:$setpointPreComfort"; - my $setpointBuildingProtection = "-"; - $setpointBuildingProtection = sprintf "%.1f", $db[3] / 255 * 40 if ($db[0] & 8); - push @event, "3:setpointBuildingProtection:$setpointBuildingProtection"; - #push @event, "3:ctrlSetup:$data"; - EnOcean_SndRadio(undef, $hash, "D2", "0000", AttrVal($name, "subDef", "00000000"), "00", $hash->{DEF}); - Log3 $name, 2, "EnOcean $name acknowledge send"; - - } elsif ($mid == 4) { - # time program setup - push @event, "3:timeSetup:$data"; - EnOcean_SndRadio(undef, $hash, "D2", "0000", AttrVal($name, "subDef", "00000000"), "00", $hash->{DEF}); - Log3 $name, 2, "EnOcean $name acknowledge send"; - - } - + } elsif ($st eq "raw") { # raw push @event, "3:state:RORG: $rorg DATA: $data STATUS: $status ODATA: $odata"; @@ -4140,6 +4652,7 @@ EnOcean_Parse($$) $attr{$name}{devChannel} = $devChannel; $attr{$name}{comMode} = $comMode; $mid = $EnO_manuf{$mid} if($EnO_manuf{$mid}); + $attr{$name}{eep} = "$rorg-$func-$type"; push @event, "3:teach-in:EEP $rorg-$func-$type Manufacturer: $mid"; if (!($db[6] & 0x40)) { # EEP Teach-In-Response expected @@ -4151,9 +4664,7 @@ EnOcean_Parse($$) $subDef = EnOcean_CheckSenderID("getNextID", $defs{$name}{IODev}{NAME}, $subDef); } $attr{$name}{subDef} = $subDef; - # command will be sent with a delay - ###select(undef, undef, undef, 0.1); - EnOcean_SndRadio(undef, $hash, "D4", $data, $subDef, "00", $id); + EnOcean_SndRadio(undef, $hash, $packetType, "D4", $data, $subDef, "00", $id); Log3 $name, 2, "EnOcean $name UTE teach-in response send to $id"; } Log3 $name, 2, "EnOcean $name UTE teach-in EEP $rorg-$func-$type Manufacturer: $mid"; @@ -4169,9 +4680,7 @@ EnOcean_Parse($$) push @event, "3:teach-in:EEP $rorg-$func-$type Manufacturer: $mid not supported"; # send EEP Teach-In Response message $data = (sprintf "%02X", $db[6] & 0x80 | 0x31) . substr($data, 2, 12); - # command will be sent with a delay - ###select(undef, undef, undef, 0.1); - EnOcean_SndRadio(undef, $hash, "D4", $data, $defs{$name}{IODev}{BaseID}, "00", $id); + EnOcean_SndRadio(undef, $hash, $packetType, "D4", $data, $defs{$name}{IODev}{BaseID}, "00", $id); Log3 $name, 2, "EnOcean $name EEP $rorg-$func-$type not supported"; # store attr subType, manufID ... CommandSave(undef, undef); @@ -4181,9 +4690,7 @@ EnOcean_Parse($$) # send EEP Teach-In Deletion Response message $teachOut =1; $data = (sprintf "%02X", $db[6] & 0x80 | 0x21) . substr($data, 2, 12); - # command will be sent with a delay - ###select(undef, undef, undef, 0.1); - EnOcean_SndRadio(undef, $hash, "D4", $data, AttrVal($name, "subDef", $defs{$name}{IODev}{BaseID}), "00", $id); + EnOcean_SndRadio(undef, $hash, $packetType, "D4", $data, AttrVal($name, "subDef", $defs{$name}{IODev}{BaseID}), "00", $id); Log3 $name, 2, "EnOcean $name delete request executed"; } elsif ($teachInReq == 2) { # Deletion of Teach-In or Teach-In Request, not specified @@ -4194,8 +4701,7 @@ EnOcean_Parse($$) Log3 $name, 2, "EnOcean $name UTE teach-in respose telegram $data received"; } } elsif ($rorg eq "35" && $teach) { - Log3 $name, 2, "EnOcean $name sec_parseTeachIn Input RORG: $rorg DATA: $data"; - # Secure Teach-In + # Secure Teach-In ($err, $msg) = EnOcean_sec_parseTeachIn($hash, $data); if (defined $err) { Log3 $name, 2, "EnOcean $name secure teach-in ERROR: $err"; @@ -4203,7 +4709,141 @@ EnOcean_Parse($$) } Log3 $name, 2, "EnOcean $name secure teach-in $msg"; CommandSave(undef, undef); - return ""; + return ""; + + } elsif ($rorg eq "C5" && $packetType == 1) { + # remote management + ### + $data =~ m/^(..)(.*)$/; + my $seq = (hex($1) & 192) >> 6; + my $idx = hex($1) & 63; + $data = $2; + # save telegrams of the message + if ($idx == 0) { + $data =~ m/^(........)(........)$/; + delete $hash->{helper}{sysEx}{$seq}; + $hash->{helper}{sysEx}{$seq}{dataLength} = (hex($1) & 0xFF800000) >> 23; + $hash->{helper}{sysEx}{$seq}{dataLengthCntr} = 4; + $hash->{helper}{sysEx}{$seq}{manufID} = (hex($1) & 0x7FF000) >> 12; + $hash->{helper}{sysEx}{$seq}{fnNumber} = (hex($1) & 0xFFF); + $hash->{helper}{sysEx}{$seq}{data}{$idx} = $2; + } else { + if (defined $hash->{helper}{sysEx}{data}{$idx}) { + # receiving error > delete all massage parts + Log3 $name, 2, "EnOcean $name RMCC/RPC message SEQ: " . $hash->{helper}{sysEx}{$seq} . " wrong."; + delete $hash->{helper}{sysEx}{$seq}; + } else { + $hash->{helper}{sysEx}{$seq}{dataLengthCntr} += 8; + $hash->{helper}{sysEx}{$seq}{data}{$idx} = $data; + } + } + # merge telegrams and decode messsage + if (defined($hash->{helper}{sysEx}{$seq}{dataLength}) && + $hash->{helper}{sysEx}{$seq}{dataLengthCntr} >= $hash->{helper}{sysEx}{$seq}{dataLength}) { + $data = undef; + for (my $cntr = 0; $cntr < int($hash->{helper}{sysEx}{$seq}{dataLength} / 8) + 1; $cntr++) { + $data .= $hash->{helper}{sysEx}{$seq}{data}{$cntr}; + } + $data = substr($data, 0, $hash->{helper}{sysEx}{$seq}{dataLength} * 2); + + if ($hash->{helper}{sysEx}{$seq}{fnNumber} == 0x604 || $hash->{helper}{sysEx}{$seq}{fnNumber} == 0x606) { + # query ID answer, Ping answer + my $eep = substr($data, 0, 6) >> 3; + my $rorg = sprintf "%02X", $eep >> 13; + my $func = sprintf "%02X", ($eep & 0x1F80) >> 7; + my $type = sprintf "%02X", $eep & 127; + my $mid = $hash->{helper}{sysEx}{$seq}{manufID}; + $attr{$name}{manufID} = $mid; + $mid = $EnO_manuf{$mid} if($EnO_manuf{$mid}); + $attr{$name}{eep} = "$rorg-$func-$type"; + my $subType = "$rorg.$func.$type"; + $attr{$name}{subType} = $EnO_subType{$subType} if($EnO_manuf{$subType}); + if ($hash->{helper}{sysEx}{$seq}{fnNumber} == 0x606) { + $hash->{RSSI} = - substr($data, 6, 2); + } + push @event, "3:teach-in:EEP $rorg-$func-$type Manufacturer: $mid"; + Log3 $name, 2, "EnOcean $name RMCC Answer EEP $rorg-$func-$type Manufacturer: $mid"; + CommandSave(undef, undef); + + } elsif ($hash->{helper}{sysEx}{$seq}{fnNumber} == 0x607) { + # functions list answer + my ($fnList, $mid); + for (my $cntr = 0; $cntr < $hash->{helper}{sysEx}{$seq}{dataLength} / 4; $cntr++) { + $fnNumber = substr($data, $cntr * 4, 2); + $mid = sprintf "%02X", substr($data, $cntr * 4 + 4, 2); + $fnList .= "$fnNumber:$mid "; + } + chop($fnList); + push @event, "3:remoteFunct:$fnList"; + Log3 $name, 2, "EnOcean $name RMCC Answer Remote Functions: $fnList"; + + } elsif ($hash->{helper}{sysEx}{$seq}{fnNumber} == 0x608) { + # last function + $data =~ m/^(.)(.)(....)(..)$/; + my $codeSetFlag = $1 >> 3; + my $seqLast = $2; + my $fnNumberLast = $3; + my $fnRetCode = $4; + Log3 $name, 2, "EnOcean $name RMCC Answer Status: Code Set Flag: $codeSetFlag + SEQ: $seqLast Function Number: $fnNumberLast Return Code $fnRetCode"; + } else { + Log3 $name, 2, "EnOcean $name RMCC/RPC Function Number: " . + sprintf "%03X", $hash->{helper}{sysEx}{$seq}{fnNumber} . " not supported."; + } + delete $hash->{helper}{sysEx}{$seq}; + } + } elsif ($packetType == 7) { + $data =~ m/^(..)(..)(.*)$/; + ($fnNumber, $manufID, $data) = (hex($1), $2, $3); + if ($fnNumber == 1) { + Log3 $name, 2, "EnOcean $name RMCC Unlock Request with ManufacturerID: $manufID"; + + } elsif ($fnNumber == 2) { + Log3 $name, 2, "EnOcean $name RMCC Lock Request with ManufacturerID: $manufID"; + + } elsif ($fnNumber == 3) { + Log3 $name, 2, "EnOcean $name RMCC Set code Request with ManufacturerID: $manufID"; + + } elsif ($fnNumber == 4) { + Log3 $name, 2, "EnOcean $name RMCC Query ID with ManufacturerID: $manufID"; + + } elsif ($fnNumber == 5) { + Log3 $name, 2, "EnOcean $name RMCC Action Request with ManufacturerID: $manufID"; + + } elsif ($fnNumber == 6) { + Log3 $name, 2, "EnOcean $name RMCC Ping Request with ManufacturerID: $manufID"; + + } elsif ($fnNumber == 7) { + Log3 $name, 2, "EnOcean $name RMCC Query remote functions with ManufacturerID: $manufID"; + + } elsif ($fnNumber == 8) { + Log3 $name, 2, "EnOcean $name RMCC Query status with ManufacturerID: $manufID"; + + } elsif ($fnNumber == 0x201) { + $data =~ m/^(..)(..)(..)(..)$/; + my ($rorg, $func, $type, $flag) = ($1, $2, $3, hex($4)); + Log3 $name, 2, "EnOcean $name RPC Remote Learn with EEP $rorg-$func-$type ManufacturerID: $manufID Flag: $flag"; + + } elsif ($fnNumber == 0x604 || $fnNumber == 0x606) { + my $eep = substr($data, 0, 6) >> 3; + my $rorg = sprintf "%02X", $eep >> 13; + my $func = sprintf "%02X", ($eep & 0x1F80) >> 7; + my $type = sprintf "%02X", $eep & 127; + $attr{$name}{manufID} = $manufID; + $manufID = $EnO_manuf{$manufID} if($EnO_manuf{$manufID}); + $attr{$name}{eep} = "$rorg-$func-$type"; + my $subType = "$rorg.$func.$type"; + $attr{$name}{subType} = $EnO_subType{$subType} if($EnO_manuf{$subType}); + if ($fnNumber == 0x606) { + $hash->{RSSI} = - substr($data, 6, 2); + } + push @event, "3:teach-in:EEP $rorg-$func-$type Manufacturer: $manufID"; + Log3 $name, 2, "EnOcean $name RMCC Answer EEP $rorg-$func-$type Manufacturer: $manufID"; + CommandSave(undef, undef); + + } else { + Log3 $name, 2, "EnOcean $name RMCC/RPC Function Number: " . sprintf "%03X", $fnNumber . " not supported."; + } } readingsBeginUpdate($hash); @@ -4228,16 +4868,49 @@ EnOcean_Parse($$) sub EnOcean_Attr(@) { my ($cmd, $name, $attrName, $attrVal) = @_; my $hash = $defs{$name}; + my $waitingCmds = AttrVal($name, "waitingCmds", 0); if ($attrName eq "pollInterval") { if (!defined $attrVal) { - } elsif ($attrVal =~ m/^\d+(\.\d+)?$/) { + + } elsif ($attrVal =~ m/^\d+?$/) { + if (AttrVal($name, "subType", "") eq "roomCtrlPanel.00") { + $waitingCmds |= 64; + readingsSingleUpdate($hash, "waitingCmds", $waitingCmds, 0); + } } else { #RemoveInternalTimer($hash); - Log3 $name, 2, "EnOcean $name attribute-value [$attrName] = $attrVal is not a number with positive sign"; + Log3 $name, 2, "EnOcean $name attribute-value [$attrName] = $attrVal is not a integer number"; CommandDeleteAttr(undef, "$name pollInterval"); } + } elsif ($attrName =~ m/^block.*/) { + if (!defined $attrVal){ + + } elsif ($attrVal =~ m/^(no|yes)$/) { + if (AttrVal($name, "subType", "") eq "roomCtrlPanel.00") { + $waitingCmds |= 64; + readingsSingleUpdate($hash, "waitingCmds", $waitingCmds, 0); + } + } else { + Log3 $name, 2, "EnOcean $name attribute-value [$attrName] = $attrVal wrong"; + CommandDeleteAttr(undef, "$name devUpdate"); + } + + } elsif ($attrName eq "daylightSavingTime") { + if (!defined $attrVal){ + + } elsif ($attrVal =~ m/^(supported|not_supported)$/) { + if (AttrVal($name, "subType", "") eq "roomCtrlPanel.00") { + $waitingCmds |= 64; + readingsSingleUpdate($hash, "waitingCmds", $waitingCmds, 0); + } + } else { + Log3 $name, 2, "EnOcean $name attribute-value [$attrName] = $attrVal wrong"; + CommandDeleteAttr(undef, "$name devUpdate"); + } + + } elsif ($attrName eq "devUpdate") { if (!defined $attrVal){ @@ -4246,6 +4919,35 @@ sub EnOcean_Attr(@) { CommandDeleteAttr(undef, "$name devUpdate"); } + } elsif ($attrName eq "displayContent") { + if (!defined $attrVal){ + + } elsif ($attrVal =~ m/^(humidity|off|setPointTemp|tempertureExtern|temperatureIntern|time|default|no_change)$/) { + if (AttrVal($name, "subType", "") eq "roomCtrlPanel.00") { + $waitingCmds |= 64; + readingsSingleUpdate($hash, "waitingCmds", $waitingCmds, 0); + } + } else { + Log3 $name, 2, "EnOcean $name attribute-value [$attrName] = $attrVal wrong"; + CommandDeleteAttr(undef, "$name devUpdate"); + } + + } elsif ($attrName eq "eep") { + if (!defined $attrVal){ + + } elsif ($attrVal !~ m/^[\dA-Fa-f]{2}-[0-3][\dA-Fa-f]-[0-7][\dA-Fa-f]$/) { + Log3 $name, 2, "EnOcean $name attribute-value [$attrName] = $attrVal wrong"; + CommandDeleteAttr(undef, "$name devUpdate"); + } + + } elsif ($attrName eq "remoteManagement") { + if (!defined $attrVal){ + + } elsif ($attrVal !~ m/^(off|on)$/) { + Log3 $name, 2, "EnOcean $name attribute-value [$attrName] = $attrVal wrong"; + CommandDeleteAttr(undef, "$name devUpdate"); + } + } elsif ($attrName eq "summerMode") { if (!defined $attrVal){ @@ -4254,6 +4956,53 @@ sub EnOcean_Attr(@) { CommandDeleteAttr(undef, "$name devUpdate"); } + } elsif ($attrName eq "temperatureScale") { + if (!defined $attrVal){ + + } elsif ($attrVal =~ m/^(C|F|default|no_change)$/) { + if (AttrVal($name, "subType", "") eq "roomCtrlPanel.00") { + $waitingCmds |= 64; + readingsSingleUpdate($hash, "waitingCmds", $waitingCmds, 0); + } + } else { + Log3 $name, 2, "EnOcean $name attribute-value [$attrName] = $attrVal wrong"; + CommandDeleteAttr(undef, "$name devUpdate"); + } + + } elsif ($attrName eq "timeNotation") { + if (!defined $attrVal){ + + } elsif ($attrVal =~ m/^(12|24|default|no_change)$/) { + if (AttrVal($name, "subType", "") eq "roomCtrlPanel.00") { + $waitingCmds |= 64; + readingsSingleUpdate($hash, "waitingCmds", $waitingCmds, 0); + } + } else { + Log3 $name, 2, "EnOcean $name attribute-value [$attrName] = $attrVal wrong"; + CommandDeleteAttr(undef, "$name devUpdate"); + } + + } elsif ($attrName =~ m/^timeProgram[1-4]$/) { + if (!defined $attrVal){ + + } elsif ($attrVal =~ m/^(FrMo|FrSu|ThFr|WeFr|TuTh|MoWe|SaSu|MoFr|MoSu|Su|Sa|Fr|Th|We|Tu|Mo)\s+(\d*?):(00|15|30|45)\s+(\d*?):(00|15|30|45)\s+(comfort|economy|preComfort|buildingProtection)$/) { + if (AttrVal($name, "subType", "") eq "roomCtrlPanel.00") { + # delete remote and send new time program + delete $hash->{helper}{4}{telegramWait}; + $hash->{helper}{4}{telegramWait}{substr($attrName,-1,1) + 0} = 1; + for (my $messagePartCntr = 1; $messagePartCntr <= 4; $messagePartCntr ++) { + if (defined AttrVal($name, "timeProgram" . $messagePartCntr, undef)) { + $hash->{helper}{4}{telegramWait}{$messagePartCntr} = 1; + } + } + $waitingCmds |= 528; + readingsSingleUpdate($hash, "waitingCmds", $waitingCmds, 0); + } + } else { + Log3 $name, 2, "EnOcean $name attribute-value [$attrName] = $attrVal wrong"; + CommandDeleteAttr(undef, "$name devUpdate"); + } + } return undef; } @@ -4267,11 +5016,24 @@ sub EnOcean_Notify(@) { return undef; } +# ADT encapsulation +sub +EnOcean_encapsulation($$$$) +{ + my ($packetType, $rorg, $data, $destinationID) = @_; + if ($destinationID eq "FFFFFFFF") { + return ($rorg, $data); + } else { + $data = $rorg . $data . $destinationID; + return ("A6", $data); + } +} + # sent message to the actuator (EEP A5-20-01) sub -EnOcean_hvac_01Cmd($$) +EnOcean_hvac_01Cmd($$$) { - my ($hash, $db_1) = @_; + my ($hash, $packetType, $db_1) = @_; my $name = $hash->{NAME}; my $cmd = ReadingsVal($name, "CMD", undef); my $subDef = AttrVal($name, "subDef", "00000000"); @@ -4289,13 +5051,9 @@ EnOcean_hvac_01Cmd($$) my $summerMode = AttrVal($name, "summerMode", "off"); readingsSingleUpdate($hash, "temperature", (sprintf "%0.1f", $actualTemp), 1); if($cmd eq "actuator") { - #$msg = sprintf "%02X000008", $arg1; $msg = sprintf "%02X00%02X08", $arg1, ($summerMode eq "on" ? 8 : 0); } elsif($cmd eq "desired-temp") { - #$msg = sprintf "%02X%02X0408", $arg1 * 255 / 40, (40 - $actualTemp) * 255 / 40; $msg = sprintf "%02X%02X%02X08", $arg1 * 255 / 40, (40 - $actualTemp) * 255 / 40, ($summerMode eq "on" ? 12 : 4); - #} elsif($cmd eq "initialize") { - # $msg = "00006408"; # Maintenance commands } elsif($cmd eq "runInit") { $msg = "00008108"; @@ -4307,28 +5065,492 @@ EnOcean_hvac_01Cmd($$) $msg = "00001108"; } if($msg) { - # next commands will be sent with a delay, previously 0.2 sec - ###select(undef, undef, undef, 0.1); - EnOcean_SndRadio(undef, $hash, "A5", $msg, $subDef, "00", $hash->{DEF}); - #if($cmd eq "initialize") { - # delete($defs{$name}{READINGS}{CMD}); - # delete($defs{$name}{READINGS}{$cmd}); - #} + EnOcean_SndRadio(undef, $hash, $packetType, "A5", $msg, $subDef, "00", $hash->{DEF}); } } } +# sent message to Room Control Panel (EEP D2-10-xx) sub -EnOcean_roomCtrlPanel_00Cmd($$$$$$$) +EnOcean_roomCtrlPanel_00Snd($$$$$$$$) { - my ($crtl, $hash, $mid, $mcf, $irc, $fbc, $gmt) = @_; + my ($crtl, $hash, $packetType, $mid, $mcf, $irc, $fbc, $gmt) = @_; my $name = $hash->{NAME}; - my ($err, $response); + my ($data, $err, $response, $logLevel); + my $messagePart = 1; + + if ($mid == 0) { + # general massage + ($err, $response, $data, $logLevel) = EnOcean_roomCtrlPanel_00Cmd(undef, $hash, $mcf, $messagePart); + EnOcean_SndRadio(undef, $hash, $packetType, "D2", $data, AttrVal($name, "subDef", "00000000"), "00", $hash->{DEF}); + if ($err) { + Log3 $name, $logLevel, "EnOcean $name Error: $err"; + } else { + Log3 $name, $logLevel, "EnOcean $name $response"; + } + + if (!defined($irc)) { + + } elsif ($irc == 0) { + # acknowledge request + + } elsif ($irc == 1) { + # data request + + } elsif ($irc == 2) { + # configuration request + } elsif ($irc == 3) { + # room control request + + } elsif ($irc == 4) { + # time program request + + } + + if (!defined($fbc)) { + } elsif ($fbc == 0) { + # acknowledge / heartbeat + + } elsif ($fbc == 1) { + # telegram repetition request + + } elsif ($fbc == 2) { + # message repetition request + + } elsif ($fbc == 3) { + # reserved + + } + + if (!defined($gmt)) { + + } elsif ($gmt == 0) { + # information request + + } elsif ($gmt == 1) { + # feetback + + } + + + } elsif ($mid == 1) { + # data message + ($err, $response, $data, $logLevel) = EnOcean_roomCtrlPanel_00Cmd(undef, $hash, $mcf, $messagePart); + EnOcean_SndRadio(undef, $hash, $packetType, "D2", $data, AttrVal($name, "subDef", "00000000"), "00", $hash->{DEF}); + if ($err) { + Log3 $name, $logLevel, "EnOcean $name Error: $err"; + } else { + Log3 $name, $logLevel, "EnOcean $name $response"; + } + + } elsif ($mid == 2) { + # configuration message + ($err, $response, $data, $logLevel) = EnOcean_roomCtrlPanel_00Cmd(undef, $hash, $mcf, $messagePart); + EnOcean_SndRadio(undef, $hash, $packetType, "D2", $data, AttrVal($name, "subDef", "00000000"), "00", $hash->{DEF}); + if ($err) { + Log3 $name, $logLevel, "EnOcean $name Error: $err"; + } else { + Log3 $name, $logLevel, "EnOcean $name $response"; + } + + } elsif ($mid == 3) { + # room control setup + ($err, $response, $data, $logLevel) = EnOcean_roomCtrlPanel_00Cmd(undef, $hash, $mcf, $messagePart); + EnOcean_SndRadio(undef, $hash, $packetType, "D2", $data, AttrVal($name, "subDef", "00000000"), "00", $hash->{DEF}); + if ($err) { + Log3 $name, $logLevel, "EnOcean $name Error: $err"; + } else { + Log3 $name, $logLevel, "EnOcean $name $response"; + } + + } elsif ($mid == 4) { + # time program setup + ($err, $response, $data, $logLevel) = EnOcean_roomCtrlPanel_00Cmd(undef, $hash, $mcf, $messagePart); + EnOcean_SndRadio(undef, $hash, $packetType, "D2", $data, AttrVal($name, "subDef", "00000000"), "00", $hash->{DEF}); + if ($err) { + Log3 $name, $logLevel, "EnOcean $name Error: $err"; + } else { + Log3 $name, $logLevel, "EnOcean $name $response"; + } + + } + return ($err, $response); } +# generate command to Room Control Panel (EEP D2-10-xx) +sub +EnOcean_roomCtrlPanel_00Cmd($$$$) +{ + my ($crtl, $hash, $mcf, $messagePart) = @_; + my $name = $hash->{NAME}; + my $data = "0000"; + my $err; + my $response = "acknowledge send"; + my $logLevel = 4; + # Waitings Commands (waitingCmds) + # 1 = sent data request + # 2 = sent data message + # 4 = sent configuration message (time) + # 8 = sent room control setup + # 16 = sent time program setup + # 32 = sent configuration request + # 64 = sent configuration message + # 128 = sent room control setup request + # 256 = sent time program request + # 512 = sent delete time program + my $waitingCmds = ReadingsVal($name, "waitingCmds", 0); + if ($mcf == 0) { + # message complete + if ($waitingCmds & 8) { + # room control setup waiting + my ($db5, $db4, $db3, $db2, $db1, $db0) = (96, 0, 0, 0, 0, 0, 0); + if (defined ReadingsVal($name, "setpointComfortTempSet", undef)) { + $db1 = ReadingsVal($name, "setpointComfortTempSet", 0) * 255 / 40; + $db0 = $db0 | 1; + CommandDeleteReading(undef, "$name setpointComfortTempSet"); + } + if (defined ReadingsVal($name, "setpointEconomyTempSet", undef)) { + $db2 = ReadingsVal($name, "setpointEconomyTempSet", 0) * 255 / 40; + $db0 = $db0 | 2; + CommandDeleteReading(undef, "$name setpointEconomyTempSet"); + } + if (defined ReadingsVal($name, "setpointPreComfortTempSet", undef)) { + $db3 = ReadingsVal($name, "setpointPreComfortTempSet", 0) * 255 / 40; + $db0 = $db0 | 4; + CommandDeleteReading(undef, "$name setpointPreComfortTempSet"); + } + if (defined ReadingsVal($name, "setpointBuildingProtectionTempSet", undef)) { + $db4 = ReadingsVal($name, "setpointBuildingProtectionTempSet", 0) * 255 / 40; + $db0 = $db0 | 8; + CommandDeleteReading(undef, "$name setpointBuildingProtectionTempSet"); + } + $data = sprintf "%02X%02X%02X%02X%02X%02X", $db5, $db4, $db3, $db2, $db1, $db0; + # clear command + $waitingCmds = $waitingCmds & 247 + 0xFF00; + $response = "room control setup send $data"; + $logLevel = 2; + +# } elsif ($waitingCmds & 4) { +# # configuration message (set time) waiting +# my ($sec, $min, $hour, $day, $month, $year) = localtime(); +# $month += 1; +# $year += 1900; +# my ($db7, $db6, $db5, $db4, $db1, $db0) = (64, 0, 0, 0, $min << 2, $hour << 3 | 1); +# my $db32 = ($day << 4 | $month) << 7 | $year - 2000; +# $data = sprintf "%02X%02X%02X%02X%04X%02X%02X", $db7, $db6, $db5, $db4, $db32, $db1, $db0; +# $min = $min < 10 ? $min = "0" . $min : $min; +# $hour = $hour < 10 ? $hour = "0" . $hour : $hour; +# $day = $day < 10 ? $day = "0" . $day : $day; +# $month = $month < 10 ? $month = "0". $month : $month; +# # clear command +# $waitingCmds = $waitingCmds & 251 + 256; +# $response = "configuration message send $year-$month-$day $hour:$min $data"; + + } elsif ($waitingCmds & 64 || $waitingCmds & 4) { + # configuration message waiting + my ($sec, $min, $hour, $day, $month, $year) = localtime(); + my ($key, $val); + $month += 1; + $year += 1900; + my ($db7, $db6, $db5, $db4, $db1, $db0) = (64, 0, 0, 0, $min << 2, $hour << 3); + $db6 |= 1 if (AttrVal($name, "blockFanSpeed", "no") ne "yes" ); + $db6 |= 2 if (AttrVal($name, "blockSetpointTemp", "no") ne "yes" ); + $db6 |= 4 if (AttrVal($name, "blockOccupancy", "no") ne "yes" ); + $db6 |= 8 if (AttrVal($name, "blockTimeProgram", "no") ne "yes" ); + $db6 |= 16 if (AttrVal($name, "blockDateTime", "no") ne "yes" ); + $db6 |= 32 if (AttrVal($name, "blockDisplay", "no") ne "yes" ); + $db6 |= 64 if (AttrVal($name, "blockTemp", "no") ne "yes" ); + $db6 |= 128 if (AttrVal($name, "blockMotion", "no") ne "yes" ); + $db5 = AttrVal($name, "pollInterval", 10); + if ($db5 > 60 && $db5 <= 180) { + $db5 = 61; + } elsif ($db5 > 180 && $db5 <= 720) { + $db5 = 62; + } elsif ($db5 > 720) { + $db5 = 63; + } + $db5 = $db5 << 2; + $db5 |= 2 if (AttrVal($name, "blockKey", "no") ne "yes" ); + my $displayContent = AttrVal($name, "displayContent", "no_change"); + my $displayContentVal = 0; + my %displayContent = ("humidity" => 7, + "off" => 6, + "setPointTemp" => 5, + "tempertureExtern" => 4, + "temperatureIntern" => 3, + "time" => 2, + "default" => 1, + "no_change" => 0 + ); + while (($key, $val) = each(%displayContent)) { + $displayContentVal = $val if ($key eq $displayContent); + } + my $temperatureScale = AttrVal($name, "temperatureScale", "no_change"); + my $temperatureScaleVal = 0; + my %temperatureScale = ("F" => 3, + "C" => 2, + "default" => 1, + "no_change" => 0 + ); + while (($key, $val) = each(%temperatureScale)) { + $temperatureScaleVal = $val if ($key eq $temperatureScale); + } + my $daylightSavingTimeVal = 0; + $daylightSavingTimeVal = 1 if (AttrVal($name, "daylightSavingTime", "supported") eq "not_supported"); + my $timeNotation = AttrVal($name, "timeNotation", "no_change"); + my $timeNotationVal = 0; + if ($timeNotation eq "no_change") { + $timeNotationVal = 0; + } elsif ($timeNotation eq "default") { + $timeNotationVal = 1; + } elsif ($timeNotation == 24) { + $timeNotationVal = 2; + } elsif ($timeNotation == 12) { + $timeNotationVal = 3; + } + $db4 = (($displayContentVal << 2 | $temperatureScaleVal) << 1 | $daylightSavingTimeVal) << 2 | $timeNotationVal; + my $db32 = ($day << 4 | $month) << 7 | $year - 2000; + if ($waitingCmds & 4) { + $db0 |= 1; + # clear time command + $waitingCmds &= 251 + 0xFF00; + } + if ($waitingCmds & 64) { + # clear config command + $waitingCmds &= 191 + 0xFF00; + } + $data = sprintf "%02X%02X%02X%02X%04X%02X%02X", $db7, $db6, $db5, $db4, $db32, $db1, $db0; + $response = "configuration message send $data"; + $logLevel = 2; + + } elsif ($waitingCmds & 2) { + # data message waiting + my ($db7, $db6, $db5, $db4, $db3, $db2, $db1, $db0) = (32, 0, 0, 0, 0, 0, 0, 0, 0); + + if (defined ReadingsVal($name, "setpointTempSet", undef)) { + $db1 = ReadingsVal($name, "setpointTempSet", 0) * 255 / 40; + $db2 |= 2; + CommandDeleteReading(undef, "$name setpointTempSet"); + } + + my $heatingSet; + if (defined ReadingsVal($name, "heatingSet", undef)) { + $heatingSet = ReadingsVal($name, "heatingSet", 0); + } else { + $heatingSet = ReadingsVal($name, "heating", 0); + } + if ($heatingSet eq "no_change") { + $heatingSet = 0; + } elsif ($heatingSet eq "on") { + $heatingSet = 1; + } elsif ($heatingSet eq "off") { + $heatingSet = 2; + } elsif ($heatingSet eq "auto") { + $heatingSet = 3; + } + $db2 |= $heatingSet << 4; + CommandDeleteReading(undef, "$name heatingSet"); + + my $coolingSet; + if (defined ReadingsVal($name, "coolingSet", undef)) { + $coolingSet = ReadingsVal($name, "coolingSet", 0); + } else { + $coolingSet = ReadingsVal($name, "cooling", 0); + } + if ($coolingSet eq "no_change") { + $coolingSet = 0; + } elsif ($coolingSet eq "on") { + $coolingSet = 1; + } elsif ($coolingSet eq "off") { + $coolingSet = 2; + } elsif ($coolingSet eq "auto") { + $coolingSet = 3; + } + $db2 |= $coolingSet << 6; + CommandDeleteReading(undef, "$name coolingSet"); + + my $roomCtrlModeSet; + if (defined ReadingsVal($name, "roomCtrlModeSet", undef)) { + $roomCtrlModeSet = ReadingsVal($name, "roomCtrlModeSet", 0); + } else { + $roomCtrlModeSet = ReadingsVal($name, "roomCtrlMode", 0); + } + if ($roomCtrlModeSet eq "comfort") { + $roomCtrlModeSet = 0; + } elsif ($roomCtrlModeSet eq "economy") { + $roomCtrlModeSet = 1; + } elsif ($roomCtrlModeSet eq "preComfort") { + $roomCtrlModeSet = 2; + } elsif ($roomCtrlModeSet eq "buildingProtection") { + $roomCtrlModeSet = 3; + } + $db2 |= $roomCtrlModeSet << 2; + CommandDeleteReading(undef, "$name roomCtrlModeSet"); + + my $windowSet; + if (defined ReadingsVal($name, "windowSet", undef)) { + $windowSet = ReadingsVal($name, "windowSet", 0); + } else { + $windowSet = ReadingsVal($name, "window", 0); + } + if ($windowSet eq "no_change") { + $windowSet = 0; + } elsif ($windowSet eq "closed") { + $windowSet = 1; + } elsif ($windowSet eq "open") { + $windowSet = 2; + } elsif ($windowSet eq "reserved") { + $windowSet = 3; + } + $db4 |= $windowSet; + CommandDeleteReading(undef, "$name windowSet"); + + my $fanSpeedModeSet; + if (defined ReadingsVal($name, "fanSpeedModeSet", undef)) { + $fanSpeedModeSet = ReadingsVal($name, "fanSpeedModeSet", 0); + } else { + $fanSpeedModeSet = ReadingsVal($name, "fanSpeedMode", 0); + } + $db4 |= 64 if ($fanSpeedModeSet eq "local"); + CommandDeleteReading(undef, "$name fanSpeedModeSet"); + + if (defined ReadingsVal($name, "fanSpeedSet", undef)) { + $db5 = ReadingsVal($name, "fanSpeedSet", 0); + $db4 |= 128; + CommandDeleteReading(undef, "$name fanSpeedSet"); + } + + $data = sprintf "%02X%02X%02X%02X%02X%02X%02X%02X", $db7, $db6, $db5, $db4, $db3, $db2, $db1, $db0; + # clear command + $waitingCmds = $waitingCmds & 253 + 0xFF00; + $response = "data message send $data"; + $logLevel = 2; + + } elsif ($waitingCmds & 512) { + # delete time program command waiting + $data = "800000000001"; + # clear command + $waitingCmds = $waitingCmds & 0xFDFF; + $response = "delete time program send"; + $logLevel = 2; + + } elsif ($waitingCmds & 16) { + # time program setup waiting + my ($db5, $endMinute, $endHour, $startMinute, $startHour, $db0) = (128, 0, 0, 0, 0, 0, 0); + my ($key, $val); + my $messagePartCntr; + for ($messagePartCntr = 4; $messagePartCntr >= 1; $messagePartCntr --) { + if ($hash->{helper}{4}{telegramWait}{$messagePartCntr}) { + $hash->{helper}{4}{telegramWait}{$messagePartCntr} = 0; + my $timeProgram = AttrVal($name, "timeProgram" . $messagePartCntr, undef); + my @timeProgram = split("[ \t][ \t]*", $timeProgram); + my ($period, $roomCtrlMode) = ($timeProgram[0], $timeProgram[3]); + ($startHour, $startMinute) = split(":", $timeProgram[1]); + ($endHour, $endMinute) = split(":", $timeProgram[2]); + my $periodVal = 0; + my %period = ("FrMo" => 15, + "FrSu" => 14, + "ThFr" => 13, + "WeFr" => 12, + "TuTh" => 11, + "MoWe" => 10, + "Su" => 9, + "Sa" => 8, + "Fr" => 7, + "Th" => 6, + "We" => 5, + "Tu" => 4, + "Mo" => 3, + "SaSu" => 2, + "MoFr" => 1, + "MoSu" => 0 + ); + while (($key, $val) = each(%period)) { + $periodVal = $val if ($key eq $period); + } + if ($roomCtrlMode eq "buildingProtection") { + $roomCtrlMode = 3; + } elsif ($roomCtrlMode eq "preComfort") { + $roomCtrlMode = 2; + } elsif ($roomCtrlMode eq "economy") { + $roomCtrlMode = 1; + } else{ + $roomCtrlMode = 0; + } + if ($messagePartCntr > 1) { + # set mcf flag + $db5 |= 1; + } else { + # clear command + $waitingCmds = $waitingCmds & 239 + 0xFF00; + } + $data = sprintf "%02X%02X%02X%02X%02X%02X", $db5, $endMinute, $endHour, $startMinute, $startHour, $periodVal << 4 | $roomCtrlMode << 2; + $response = "time program setup send $data"; + $logLevel = 2; + last; + } + } + + } elsif ($waitingCmds & 1) { + # data request waiting + $data = "0009"; + # clear command + $waitingCmds = $waitingCmds & 254 + 0xFF00; + $response = "data request send"; + $logLevel = 2; + + } elsif ($waitingCmds & 32) { + # configuration request waiting + $data = "0011"; + # clear command + $waitingCmds = $waitingCmds & 223 + 0xFF00; + $response = "configuration request send"; + $logLevel = 2; + + } elsif ($waitingCmds & 128) { + # room control setup request waiting + $data = "0019"; + # clear command + $waitingCmds = $waitingCmds & 127 + 0xFF00; + $response = "room control setup request send"; + $logLevel = 2; + + } elsif ($waitingCmds & 256) { + # time program request waiting + $data = "0021"; + # clear command + $waitingCmds = $waitingCmds & 0xFEFF; + $response = "time program request send"; + $logLevel = 2; + + } + + if ($waitingCmds == 0) { + CommandDeleteReading(undef, "$name waitingCmds"); + } else { + readingsSingleUpdate($hash, "waitingCmds", $waitingCmds, 0); + } + + } elsif ($mcf == 1) { + # message incomplete + $response = "acknowledge send, wating for next part of the message"; + $logLevel = 2; + + } elsif ($mcf == 2) { + # automatic message control + $response = "acknowledge send, automatic message control"; + $logLevel = 2; + + } elsif ($mcf == 3) { + # reserved + } + + return ($err, $response, $data, $logLevel); +} + # Check SenderIDs sub EnOcean_CheckSenderID($$$) @@ -4436,25 +5658,38 @@ EnOcean_CheckSenderID($$$) # send EnOcean ESP3 Packet Type Radio sub -EnOcean_SndRadio($$$$$$$) +EnOcean_SndRadio($$$$$$$$) { - my ($ctrl, $hash, $rorg, $data, $senderID, $status, $destinationID) = @_; + my ($ctrl, $hash, $packetType, $rorg, $data, $senderID, $status, $destinationID) = @_; + my $header; my $odata = ""; my $odataLength = 0; - if (AttrVal($hash->{NAME}, "repeatingAllowed", "yes") eq "no") { - $status = substr($status, 0, 1) . "F"; - } - my $securityLevel = AttrVal($hash->{NAME}, "securityLevel", 0); - if ($securityLevel eq "unencrypted") {$securityLevel = 0;} - if ($destinationID ne "FFFFFFFF" || $securityLevel) { - # SubTelNum = 03, DestinationID:8, RSSI = FF, SecurityLevel:2 - $odata = sprintf "03%sFF%02X", $destinationID, $securityLevel; - $odataLength = 7; - } - # Data Length:4 Optional Length:2 Packet Type = 01 (radio) - my $header = sprintf "%04X%02X01", (length($data)/2 + 6), $odataLength; - Log3 $hash->{NAME}, 4, "EnOcean $hash->{NAME} sent PacketType: 1 RORG: $rorg DATA: $data ID: $senderID STATUS: $status ODATA: $odata"; - $data = $rorg . $data . $senderID . $status . $odata; + if ($packetType == 1) { + if (AttrVal($hash->{NAME}, "repeatingAllowed", "yes") eq "no") { + $status = substr($status, 0, 1) . "F"; + } + my $securityLevel = AttrVal($hash->{NAME}, "securityLevel", 0); + if ($securityLevel eq "unencrypted") {$securityLevel = 0;} + if ($destinationID ne "FFFFFFFF" || $securityLevel) { + # SubTelNum = 03, DestinationID:8, RSSI = FF, SecurityLevel:2 + $odata = sprintf "03%sFF%02X", $destinationID, $securityLevel; + $odataLength = 7; + } + # Data Length:4 Optional Length:2 Packet Type:2 + $header = sprintf "%04X%02X%02X", (length($data)/2 + 6), $odataLength, $packetType; + Log3 $hash->{NAME}, 5, "EnOcean $hash->{NAME} sent PacketType: $packetType RORG: $rorg DATA: $data SenderID: $senderID STATUS: $status ODATA: $odata"; + $data = $rorg . $data . $senderID . $status . $odata; + } elsif ($packetType == 7) { + my $delay = 0; + $delay = 1 if ($destinationID eq "FFFFFFFF"); + $senderID = "00000000"; + $odata = sprintf "%s%sFF%02X", $destinationID, $senderID, $delay; + $odataLength = 10; + # Data Length:4 Optional Length:2 Packet Type:2 + $header = sprintf "%04X%02X%02X", (length($data)/2 + 6), $odataLength, $packetType; + Log3 $hash->{NAME}, 3, "EnOcean $hash->{NAME} sent PacketType: $packetType DATA: $data ODATA: $odata"; + $data .= $odata; + } IOWrite($hash, $header, $data); } @@ -4479,7 +5714,19 @@ EnOcean_ReadingScaled($$$$) $valScaled = sprintf "$scaleDecimals", $valScaled; } return $valScaled; -} +} + +# Reorganize Strings +sub +EnOcean_ReorgList($) +{ + my ($list) = @_; + my @list = split("[ \t][ \t]*", $list); + my %list; + @list = sort grep(!$list{$_}++, @list); + $list = join(" ", @list) . " "; + return $list; +} # EnOcean_Set called from sub InternalTimer() sub @@ -4489,6 +5736,53 @@ EnOcean_TimerSet($) EnOcean_Set($par->{hash}, @{$par->{timerCmd}}); } +# +sub EnOcean_InternalTimer($$$$$) +{ + my ($modifier, $tim, $callback, $hash, $waitIfInitNotDone) = @_; + + my $mHash; + if ($modifier eq "") { + $mHash = $hash; + } else { + my $timerName = "$hash->{NAME}_$modifier"; + if (exists ($hash->{TIMER}{$timerName})) { + $mHash = $hash->{TIMER}{$timerName}; + } else { + $mHash = {HASH => $hash, NAME => "$hash->{NAME}_$modifier", MODIFIER => $modifier}; + $hash->{TIMER}{$timerName} = $mHash; + } + } + InternalTimer($tim, $callback, $mHash, $waitIfInitNotDone); +} + +# +sub EnOcean_RemoveInternalTimer($$) +{ + my ($modifier, $hash) = @_; + + my $timerName = "$hash->{NAME}_$modifier"; + if ($modifier eq "") { + RemoveInternalTimer($hash); + } else { + my $myHash = $hash->{TIMER}{$timerName}; + if (defined($myHash)) { + delete $hash->{TIMER}{$timerName}; + RemoveInternalTimer($myHash); + } + } +} + +# +sub xxx_calback($) +{ + my ($myHash) = @_; + my $hash = $myHash->{HASH}; + my $hashName = $hash->{NAME}; + Log 3, "hashName -> $hashName"; +} + + # Parse Secure Teach-In Telegrams sub EnOcean_sec_parseTeachIn($$) { my ($hash, $telegram) = @_; @@ -4542,11 +5836,13 @@ sub EnOcean_sec_parseTeachIn($$) { } } else { if ($info == 0) { + $attr{$name}{eep} = "D2-03-00"; $attr{$name}{subType} = $EnO_subType{"D2.03.00"}; $attr{$name}{manufID} = "7FF"; readingsSingleUpdate($hash, "teach-in", "EEP D2-03-00 Manufacturer: " . $EnO_manuf{"7FF"}, 1); Log3 $name, 2, "EnOcean $name teach-in EEP D2-03-00 Rocker A Manufacturer: " . $EnO_manuf{"7FF"}; } else { + $attr{$name}{eep} = "D2-03-00"; $attr{$name}{subType} = $EnO_subType{"D2.03.00"}; $attr{$name}{manufID} = "7FF"; readingsSingleUpdate($hash, "teach-in", "EEP D2-03-00 Manufacturer: " . $EnO_manuf{"7FF"}, 1); @@ -4749,7 +6045,7 @@ sub EnOcean_sec_getRLC($) { Log3 $name, 5, "EnOcean $name EnOcean_sec_getRLC RLC rollover"; $new_rlc = 0; $attr{$name}{rlc} = "0"; - CommandSave(undef, undef); + CommandSave(undef, undef); } readingsSingleUpdate($hash, ".rlc", uc(unpack('H4',pack('n', $new_rlc))), 0); $attr{$name}{rlc} = uc(unpack('H4',pack('n', $new_rlc))); @@ -4998,6 +6294,19 @@ sub EnOcean_sec_convertToNonsecure($$$) { return ("Can't verify or decrypt telegram", undef, undef); } +# +sub +EnOcean_NumericSort +{ + if ($a < $b) { + return -1; + } elsif ($a == $b) { + return 0; + } else { + return 1; + } +} + # Undef sub EnOcean_Undef($$) @@ -5597,7 +6906,7 @@ EnOcean_Undef($$) set the dimming time of timer 1 ... 3
  • local teachInDev enabled|disabled, disabled is default
    enable the taught-in devices with different EEP
  • -
  • measurement delta 0/s ... 4095/s, 0 is deflaut
    +
  • measurement delta 0/s ... 4095/s, 0 is default
    define the difference between two displayed measurements
  • measurement mode energy|power, energy is default
    define the measurand
  • @@ -5621,6 +6930,72 @@ EnOcean_Undef($$)

    +
  • Room Control Panels (D2-10-00 - D2-10-02)
    + [Kieback & Peter RBW322-FTL]
    +
    + Setpoint Range: t = 0 °C ... 40 °C
    + The room controller is configured using the following attributes:
    + + The attr subType must be roomCtrlPanel.00. This is done if the device was + created by autocreate. To control the device, it must be bidirectional paired, + see Bidirectional Teach-In / Teach-Out. +
  • +

    +
  • RAW Command


    @@ -5689,6 +7085,38 @@ EnOcean_Undef($$) the sunblind from one slat angle end position to the other end position.
    angleTime is supported for shutter.
  • +
  • blockDateTime yes|no, [blockDateTime] = no is default.
    + blockDateTime is supported for roomCtrlPanel.00. +
  • +
  • blockDisplay yes|no, [blockDisplay] = no is default.
    + blockDisplay is supported for roomCtrlPanel.00. +
  • +
  • blockFanSpeed yes|no, [blockFanSpeed] = no is default.
    + blockFanSpeed is supported for roomCtrlPanel.00. +
  • +
  • blockKey yes|no, [blockKey] = no is default.
    + blockKey is supported for roomCtrlPanel.00. +
  • +
  • blockMotion yes|no, [blockMotion] = no is default.
    + blockMotion is supported for roomCtrlPanel.00. +
  • +
  • blockOccupancy yes|no, [blockOccupancy] = no is default.
    + blockOccupancy is supported for roomCtrlPanel.00. +
  • +
  • blockTemp yes|no, [blockTemp] = no is default.
    + blockTemp is supported for roomCtrlPanel.00. +
  • +
  • blockTimeProgram yes|no, [blockTimeProgram] = no is default.
    + blockTimeProgram is supported for roomCtrlPanel.00. +
  • +
  • blockSetpointTemp yes|no, [blockSetpointTemp] = no is default.
    + blockSetPointTemp is supported for roomCtrlPanel.00. +
  • +
  • blockUnknownMSC yes|no, + [blockUnknownMSC] = no is default.
    + If the structure of the MSC telegrams can not interpret the raw data to be output. Setting this attribute to yes, + the output can be suppressed. +
  • comMode biDir|uniDir, [comMode] = uniDir is default.
    Communication Mode between an enabled EnOcean device and Fhem.
    Unidirectional communication means a point-to-multipoint communication @@ -5699,6 +7127,9 @@ EnOcean_Undef($$) involved to know the unique Sender ID of their partners. Bidirectional communication needs a teach-in / teach-out process, see Bidirectional Teach-In / Teach-Out.
  • +
  • daylightSavingTime supported|not_supported, [daylightSavingTime] = supported is default.
    + daylightSavingTime is supported for roomCtrlPanel.00. +
  • devChannel 00 ... FF, [devChannel] = FF is default
    Number of the individual device channel, FF = all channels supported by the device
  • @@ -5733,8 +7164,15 @@ EnOcean_Undef($$) 23:00-24:00 00:00-01:00 +
  • displayContent + humidity|off|setPointTemp|temperatureExtern|temperatureIntern|time|default|no_change, [displayContent] = no_change is default.
    + displayContent is supported for roomCtrlPanel.00. +
  • do_not_notify
  • eventMap
  • +
  • eep [00...FF].[00...3F].[00...7F]
    + EnOcean Equipment Profile (EEP) +
  • gwCmd switching|dimming|setpointShift|setpointBasic|controlVar|fanStage|blindCmd
    Gateway Command Type, see Gateway profile
  • @@ -5745,12 +7183,20 @@ EnOcean_Undef($$)
  • ignore
  • IODev
  • model
  • +
  • pollInterval t/s, [pollInterval] = 10 is default.
    + [pollInterval] = 1 ... 1440.
    + pollInterval is supported for roomCtrlPanel.00. +
  • rampTime t/s or relative, [rampTime] = 1 is default.
    No ramping or for Eltako dimming speed set on the dimmer if [rampTime] = 0.
    Ramping time 1 s to 255 s or relative fast to low dimming speed if [rampTime] = 1 ... 255.
    rampTime is supported for gateway, command dimming.
  • readingFnAttributes
  • +
  • remoteManagement off|on, + [remoteManagement] = off is default.
    + Enable Remote Management for the device. +
  • repeatingAllowed yes|no, [repeatingAllowed] = yes is default.
    EnOcean Repeater in the transmission range of Fhem may forward data messages @@ -5855,6 +7301,20 @@ EnOcean_Undef($$) Name of the device whose reference value is read. The reference values is the reading temperature.
  • +
  • temperatureScale F|C|default|no_change, [temperatureScale] = no_change is default.
    + temperatureScale is supported for roomCtrlPanel.00. +
  • +
  • timeNotation 12|24|default|no_change, [timeNotation] = no_change is default.
    + timeNotation is supported for roomCtrlPanel.00. +
  • +
  • timeProgram[1-4] <period> <starttime> <endtime> <roomCtrlMode>, [timeProgam[1-4]] = <none> is default.
    + [period] = FrMo|FrSu|ThFr|WeFr|TuTh|MoWe|SaSu|MoFr|MoSu|Su|Sa|Fr|Th|We|Tu|Mo
    + [starttime] = [00..23]:[00|15|30|45]
    + [endtime] = [00..23]:[00|15|30|45]
    + [roomCtrlMode] = buildingProtection|comfort|economy|preComfort
    + The Room Control Panel Kieback & Peter RBW322-FTL supports only [roomCtrlMode] = comfort.
    + timeProgram is supported for roomCtrlPanel.00. +
  • verbose
  • webCmd
  • @@ -5909,6 +7369,29 @@ EnOcean_Undef($$)

    +
  • Pushbutton Switch (EEP F6-3F-7F)
    + [Eltako FGW14/FAM14 with internal decryption and RS-485 communication]
    +
    + Set attr subType to switch.7F and manufID to 00D.
    + The status of the device may become "released", this is not the case for + a normal switch. Set attr sensorMode to pushbutton manually. +
  • +

    +
  • Pushbutton Switch (EEP D2-03-00)
    [EnOcean PTM 215 Modul]