From 29ac29f6bfd3d03cfda88a474f0750f92d91f2e7 Mon Sep 17 00:00:00 2001 From: klaus-schauer <> Date: Thu, 20 Feb 2014 15:51:29 +0000 Subject: [PATCH] 10_EnOcean: # Added new EEP 2.5 profiles: experimental: D2-01-00 - D2-01-11 (VLD) # UTE: protocol implemented # teach-in optimized # MD15: subType renamed to hvac.01, manufID now 7FF # manufProfile/Eltako shutter: errors fixed, additional functions installed # gateway/switching,dimming: reading dimValue changed to dim, commands BI/B0 replaced by on/off # roomSensorControl.01: manufacturer specific extensions # commandref: further explanations added git-svn-id: https://svn.fhem.de/fhem/trunk@4999 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/FHEM/10_EnOcean.pm | 1371 ++++++++++++++++++++++++++++++++------- 1 file changed, 1151 insertions(+), 220 deletions(-) diff --git a/fhem/FHEM/10_EnOcean.pm b/fhem/FHEM/10_EnOcean.pm index 20a853529..5996ac19b 100755 --- a/fhem/FHEM/10_EnOcean.pm +++ b/fhem/FHEM/10_EnOcean.pm @@ -10,8 +10,9 @@ use SetExtensions; sub EnOcean_Define($$); sub EnOcean_Initialize($); sub EnOcean_Parse($$); +sub EnOcean_Get($@); sub EnOcean_Set($@); -sub EnOcean_MD15Cmd($$$); +sub EnOcean_hvac_01Cmd($$$); sub EnOcean_CheckSenderID($$$); sub EnOcean_SndRadio($$$$$$$); sub EnOcean_ReadingScaled($$$$); @@ -70,6 +71,7 @@ my %EnO_manuf = ( "01A" => "Res.", "01B" => "Lutuo Technology", "01C" => "CAN2GO", + "039" => "alphaEOS AG", "7FF" => "Multi user Manufacturer ID", ); @@ -111,6 +113,7 @@ my %EnO_subType = ( "A5.08.02" => "lightTempOccupSensor.02", "A5.08.03" => "lightTempOccupSensor.03", "A5.09.01" => "COSensor.01", + "A5.09.02" => "COSensor.02", "A5.09.04" => "tempHumiCO2Sensor.01", "A5.09.05" => "vocSensor.01", "A5.09.06" => "radonSensor.01", @@ -165,24 +168,48 @@ my %EnO_subType = ( "A5.14.04" => "multiFuncSensor", "A5.14.05" => "multiFuncSensor", "A5.14.06" => "multiFuncSensor", - "A5.20.01" => "MD15", + "A5.20.01" => "hvac.01", + #"A5.20.02" => "hvac.02", + #"A5.20.03" => "hvac.03", + #"A5.20.10" => "hvac.04", + #"A5.20.11" => "hvac.11", + #"A5.20.12" => "hvac.12", "A5.30.01" => "digitalInput.01", "A5.30.02" => "digitalInput.02", "A5.38.08" => "gateway", "A5.3F.7F" => "manufProfile", + "D2.01.00" => "actuator.01", + "D2.01.01" => "actuator.01", + "D2.01.02" => "actuator.01", + "D2.01.03" => "actuator.01", + "D2.01.04" => "actuator.01", + "D2.01.05" => "actuator.01", + "D2.01.06" => "actuator.01", + "D2.01.07" => "actuator.01", + "D2.01.08" => "actuator.01", + "D2.01.09" => "actuator.01", + "D2.01.10" => "actuator.01", + "D2.01.11" => "actuator.01", "D5.00.01" => "contact", + "F6.02.01" => "switch", + "F6.02.02" => "switch", + "F6.02.03" => "switch", + #"F6.02.04" => "switch.04", + "F6.03.01" => "switch", + "F6.03.02" => "switch", "F6.04.01" => "keycard", + #"F6.04.02" => "keycard.02", "F6.10.00" => "windowHandle", - 1 => "switch", - 2 => "sensor", - 3 => "FRW", - 4 => "PM101", - 5 => "raw", + #"F6.10.01" => "windowHandle.01", + 1 => "sensor", + 2 => "FRW", + 3 => "PM101", + 4 => "raw", ); my @EnO_models = qw ( other - FAE14 FHK14 + FAE14 FHK14 FHK61 FSB14 FSB61 FSB70 FSM12 FSM61 FT55 @@ -202,15 +229,18 @@ EnOcean_Initialize($) $hash->{UndefFn} = "EnOcean_Undef"; $hash->{ParseFn} = "EnOcean_Parse"; $hash->{SetFn} = "EnOcean_Set"; + $hash->{GetFn} = "EnOcean_Get"; + #$hash->{NotifyFn} = "EnOcean_Notify"; + $hash->{AttrFn} = "EnOcean_Attr"; $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 destinationID " . - "devChannel dimValueOn " . + "devChannel devUpdate:off,auto,demand,polling,interrupt dimValueOn " . "gwCmd:" . join(",", sort @EnO_gwCmd) . " humidityRefDev " . "manufID:" . join(",", sort keys %EnO_manuf) . " " . "model:" . join(",", @EnO_models) . " " . - "rampTime repeatingAllowed:yes,no " . + "pollInterval rampTime repeatingAllowed:yes,no " . "scaleDecimals:0,1,2,3,4,5,6,7,8,9 scaleMax scaleMin " . "securityLevel:unencrypted sensorMode:switch,pushbutton " . "shutTime shutTimeCloses subDef " . @@ -236,22 +266,106 @@ EnOcean_Define($$) my @a = split("[ \t][ \t]*", $def); my $name = $hash->{NAME}; return "wrong syntax: define EnOcean 8-digit-hex-code" - if(int(@a) != 3 || $a[2] !~ m/^[A-F0-9]{8}$/i); - + if(int(@a) < 3 || int(@a) > 4 || $a[2] !~ m/^[A-F0-9]{8}$/i); $modules{EnOcean}{defptr}{uc($a[2])} = $hash; AssignIoPort($hash); # Help FHEMWEB split up devices $attr{$name}{subType} = $1 if($name =~ m/EnO_(.*)_$a[2]/); + $hash->{NOTIFYDEV} = "global"; + if (int(@a) == 4) { + # parse received device data + $hash->{DEF} = uc($a[2]); + EnOcean_Parse($hash, $a[3]); + } return undef; } +# Get +sub +EnOcean_Get ($@) +{ + my ($hash, @a) = @_; + return "no get value specified" if (@a < 2); + my $name = $hash->{NAME}; + my $data; + my $destinationID = AttrVal($name, "destinationID", undef); + if (!defined $destinationID || $destinationID eq "multicast") { + $destinationID = "FFFFFFFF"; + } elsif ($destinationID eq "unicast") { + $destinationID = $hash->{DEF}; + } elsif ($destinationID !~ m/^[\dA-F]{8}$/) { + return "DestinationID $destinationID wrong, choose <8-digit-hex-code>."; + } + my $manufID = AttrVal($name, "manufID", ""); + my $model = AttrVal($name, "model", ""); + my $rorg; + my $status = "00"; + my $st = AttrVal($name, "subType", ""); + my $stSet = AttrVal($name, "subTypeSet", undef); + if (defined $stSet) {$st = $stSet;} + my $subDef = AttrVal($name, "subDef", $hash->{DEF}); + if ($subDef !~ m/^[\dA-F]{8}$/) {return "SenderID $subDef wrong, choose <8-digit-hex-code>.";} + my $tn = TimeNow(); + shift @a; + + for (my $i = 0; $i < @a; $i++) { + my $cmd = $a[$i]; + + if ($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); + if (!defined $channel || $channel eq "all") { + $channel = 30; + } elsif ($channel >= 0 || $channel <= 29) { + + } elsif ($channel eq "input") { + $channel = 31; + } else { + return "$cmd wrong, choose 0...29|all|input."; + } + + if ($cmd eq "state") { + $cmdID = 3; + $data = sprintf "%02X%02X", $cmdID, $channel; + + } elsif ($cmd eq "measurement") { + $cmdID = 6; + my $query = shift(@a); + if ($query eq "energy") { + $query = 0; + } elsif ($query eq "power") { + $query = 1; + } else { + return "$cmd wrong, choose energy|power."; + } + $data = sprintf "%02X%02X", $cmdID, $query << 5 | $channel; + + } else { + return "Unknown argument $cmd, choose one of state measurement"; + } + Log3 $name, 2, "EnOcean get $name $cmd"; + + } else { + # subtype does not support get commands + return; + + } + EnOcean_SndRadio(undef, $hash, $rorg, $data, $subDef, $status, $destinationID); + # next commands will be sent with a delay + select(undef, undef, undef, 0.2); + } +} + # Set sub EnOcean_Set($@) { my ($hash, @a) = @_; - return "no set value specified" if(@a < 2); - + return "no set value specified" if (@a < 2); my $name = $hash->{NAME}; my $data; my $destinationID = AttrVal($name, "destinationID", undef); @@ -576,7 +690,7 @@ EnOcean_Set($@) } Log3 $name, 2, "EnOcean set $name $cmd"; - } elsif ($st eq "MD15") { + } elsif ($st eq "hvac.01" || $st eq "MD15") { # Battery Powered Actuator (EEP A5-20-01) # [Kieback&Peter MD15-FTL-xx] # See also http://www.oscat.de/community/index.php/topic,985.30.html @@ -642,7 +756,7 @@ EnOcean_Set($@) # teach-in EEP A5-38-08, Manufacturer "Multi user Manufacturer ID" #$data = sprintf "%02X000000", $gwCmdID; $data = "E047FF80"; - } elsif ($cmd eq "on" || $cmd eq "B0") { + } elsif ($cmd eq "on") { $setCmd = 9; readingsSingleUpdate($hash, "block", "unlock", 1); if ($a[1]) { @@ -655,7 +769,7 @@ EnOcean_Set($@) } $updateState = 0; $data = sprintf "%02X%04X%02X", $gwCmdID, $time, $setCmd; - } elsif ($cmd eq "off" || $cmd eq "BI") { + } elsif ($cmd eq "off") { $setCmd = 8; readingsSingleUpdate($hash, "block", "unlock", 1); if ($a[1]) { @@ -669,7 +783,7 @@ EnOcean_Set($@) $updateState = 0; $data = sprintf "%02X%04X%02X", $gwCmdID, $time, $setCmd; } else { - my $cmdList = "B0 BI teach"; + my $cmdList = "on:noArg off:noArg teach:noArg"; return SetExtensions ($hash, $cmdList, $name, @a); $updateState = 0; $data = sprintf "%02X%04X%02X", $gwCmdID, $time, $setCmd; @@ -678,7 +792,7 @@ EnOcean_Set($@) } elsif ($gwCmd eq "dimming") { # Dimming $gwCmdID = 2; - my $dimVal = $hash->{READINGS}{dimValue}{VAL}; + my $dimVal = ReadingsVal($name, "dim", undef); my $rampTime = AttrVal($name, "rampTime", 1); my $sendDimCmd = 0; $setCmd = 9; @@ -734,7 +848,7 @@ EnOcean_Set($@) } $sendDimCmd = 1; - } elsif ($cmd eq "on" || $cmd eq "B0") { + } elsif ($cmd eq "on") { $rampTime = 1; my $dimValueOn = AttrVal($name, "dimValueOn", 100); if ($dimValueOn eq "stored") { @@ -759,14 +873,14 @@ EnOcean_Set($@) } $sendDimCmd = 1; - } elsif ($cmd eq "off" || $cmd eq "BI") { + } elsif ($cmd eq "off") { $dimVal = 0; $rampTime = 1; $setCmd = 8; $sendDimCmd = 1; } else { - my $cmdList = "dim:slider,0,1,100 B0 BI teach"; + my $cmdList = "dim:slider,0,1,100 on:noArg off:noArg teach:noArg"; return SetExtensions ($hash, $cmdList, $name, @a); } if ($sendDimCmd) { @@ -805,7 +919,7 @@ EnOcean_Set($@) return "Usage: $a[1] is not numeric or out of range"; } } else { - return "Unknown argument $cmd, choose one of teach shift"; + return "Unknown argument $cmd, choose one of teach:noArg shift"; } } elsif ($gwCmd eq "setpointBasic") { @@ -822,7 +936,7 @@ EnOcean_Set($@) return "Usage: $cmd parameter is not numeric or out of range."; } } else { - return "Unknown argument $cmd, choose one of teach basic"; + return "Unknown argument $cmd, choose one of teach:noArg basic"; } } elsif ($gwCmd eq "controlVar") { @@ -885,7 +999,7 @@ EnOcean_Set($@) $updateState = 0; $data = sprintf "%02X00%02X%02X", $gwCmdID, $controlVar, $setCmd; } else { - return "Unknown argument, choose one of teach presence:absent,present,standby energyHoldOff:holdoff,normal controllerMode:cooling,heating,off controllerState:auto,override"; + return "Unknown argument, choose one of teach:noArg presence:absent,present,standby energyHoldOff:holdoff,normal controllerMode:cooling,heating,off controllerState:auto,override"; } } elsif ($gwCmd eq "fanStage") { @@ -905,7 +1019,7 @@ EnOcean_Set($@) } shift(@a); } else { - return "Unknown argument, choose one of teach stage:auto,0,1,2,3"; + return "Unknown argument, choose one of teach:noArg stage:auto,0,1,2,3"; } } elsif ($gwCmd eq "blindCmd") { @@ -1075,8 +1189,27 @@ EnOcean_Set($@) my $angleMax = AttrVal($name, "angleMax", 90); my $angleMin = AttrVal($name, "angleMin", -90); my $anglePos = ReadingsVal($name, "anglePos", undef); + my $anglePosStart; my $angleTime = AttrVal($name, "angleTime", 0); my $position = ReadingsVal($name, "position", undef); + my $positionStart; + if ($cmd ne "?") { + # check actual shutter position + my $actualState = ReadingsVal($name, "state", undef); + if (defined $actualState) { + if ($actualState eq "open") { + $position = 0; + $anglePos = 0; + } elsif ($actualState eq "closed") { + $position = 100; + $anglePos = $angleMax; + } + } + $anglePosStart = $anglePos; + $positionStart = $position; + readingsSingleUpdate($hash, ".anglePosStart", $anglePosStart, 0); + readingsSingleUpdate($hash, ".positionStart", $positionStart, 0); + } $rorg = "A5"; my $shutTime = AttrVal($name, "shutTime", 255); my $shutTimeCloses = AttrVal($name, "shutTimeCloses", $shutTime); @@ -1111,8 +1244,8 @@ EnOcean_Set($@) } elsif ($cmd eq "opens") { # opens >> B0 $anglePos = 0; - readingsSingleUpdate($hash, "anglePos", $anglePos, 1); $position = 0; + readingsSingleUpdate($hash, "anglePos", $anglePos, 1); readingsSingleUpdate($hash, "position", $position, 1); readingsSingleUpdate($hash, "endPosition", "open", 1); $cmd = "open"; @@ -1122,8 +1255,8 @@ EnOcean_Set($@) } elsif ($cmd eq "closes") { # closes >> BI $anglePos = $angleMax; - readingsSingleUpdate($hash, "anglePos", $anglePos, 1); $position = 100; + readingsSingleUpdate($hash, "anglePos", $anglePos, 1); readingsSingleUpdate($hash, "position", $position, 1); readingsSingleUpdate($hash, "endPosition", "closed", 1); $cmd = "closed"; @@ -1134,9 +1267,9 @@ EnOcean_Set($@) # up if (defined $a[1]) { if ($a[1] =~ m/^[+-]?\d+$/ && $a[1] >= 0 && $a[1] <= 255) { - $position -= $a[1] / $shutTime * 100; + $position = $positionStart - $a[1] / $shutTime * 100; if ($angleTime) { - $anglePos -= ($angleMax - $angleMin) * $shutTime / $angleTime; + $anglePos = $anglePosStart - ($angleMax - $angleMin) * $a[1] / $angleTime; if ($anglePos < $angleMin) { $anglePos = $angleMin; } @@ -1148,12 +1281,13 @@ EnOcean_Set($@) $position = 0; readingsSingleUpdate($hash, "endPosition", "open", 1); $cmd = "open"; + } else { + readingsSingleUpdate($hash, "endPosition", "not_reached", 1); + $cmd = "not_reached"; } - readingsSingleUpdate($hash, "endPosition", "not_reached", 1); - $cmd = "not_reached"; $shutTime = $a[1]; shift(@a); - } else { + } else { return "Usage: $a[1] is not numeric or out of range"; } } else { @@ -1169,23 +1303,24 @@ EnOcean_Set($@) # down if (defined $a[1]) { if ($a[1] =~ m/^[+-]?\d+$/ && $a[1] >= 0 && $a[1] < 255) { - $position += $a[1] / $shutTime * 100; + $position = $positionStart + $a[1] / $shutTime * 100; if ($angleTime) { - $anglePos += ($angleMax - $angleMin) * $shutTime / $angleTime; + $anglePos = $anglePosStart + ($angleMax - $angleMin) * $a[1] / $angleTime; if ($anglePos > $angleMax) { $anglePos = $angleMax; } } else { $anglePos = $angleMax; } - if($position > 100) { + if($position >= 100) { $anglePos = $angleMax; $position = 100; readingsSingleUpdate($hash, "endPosition", "closed", 1); $cmd = "closed"; + } else { + readingsSingleUpdate($hash, "endPosition", "not_reached", 1); + $cmd = "not_reached"; } - readingsSingleUpdate($hash, "endPosition", "not_reached", 1); - $cmd = "not_reached"; $shutTime = $a[1]; shift(@a); } else { @@ -1201,23 +1336,11 @@ EnOcean_Set($@) readingsSingleUpdate($hash, "position", sprintf("%d", $position), 1); $shutCmd = 2; } elsif ($cmd eq "position") { - if (!defined $position) { + if (!defined $positionStart) { return "Position unknown, please first opens the blinds completely." - } elsif ($angleTime > 0 && !defined $anglePos){ + } elsif ($angleTime > 0 && !defined $anglePosStart){ return "Slats angle position unknown, please first opens the blinds completely." } else { - # check actual shutter position - my $actualState = ReadingsVal($name, "state", undef); - if (defined $actualState) { - if ($actualState eq "open") { - $position = 0; - $anglePos = 0; - } elsif ($actualState eq "closed") { - $position = 100; - $anglePos = $angleMax; - } - } - my $anglePosLast = $anglePos; my $shutTimeSet = $shutTime; if (defined $a[2]) { if ($a[2] =~ m/^[+-]?\d+$/ && $a[2] >= $angleMin && $a[2] <= $angleMax) { @@ -1229,11 +1352,17 @@ EnOcean_Set($@) } else { $anglePos = $angleMax; } + if ($positionStart <= $angleTime * $angleMax / ($angleMax - $angleMin) / $shutTimeSet * 100) { + $anglePosStart = $angleMax; + } if (defined $a[1] && $a[1] =~ m/^[+-]?\d+$/ && $a[1] >= 0 && $a[1] <= 100) { - if ($position < $a[1]) { + if ($positionStart < $a[1]) { # down - $angleTime = $angleTime * ($angleMax - $anglePos)/($angleMax - $angleMin); - $shutTime = $shutTime * ($a[1] - $position) / 100 + $angleTime; + $angleTime = $angleTime * ($angleMax - $anglePos) / ($angleMax - $angleMin); + $shutTime = $shutTime * ($a[1] - $positionStart) / 100 + $angleTime; + # round up + $angleTime = int($angleTime) + 1 if ($angleTime > int($angleTime)); + $shutTime = int($shutTime) + 1 if ($shutTime > int($shutTime)); $position = $a[1] + $angleTime / $shutTimeSet * 100; if ($position >= 100) { $position = 100; @@ -1244,10 +1373,13 @@ EnOcean_Set($@) my %par = (hash => $hash, timerCmd => \@timerCmd); InternalTimer(gettimeofday() + $shutTime + 1, "EnOcean_TimerSet", \%par, 0); } - } elsif ($position > $a[1]) { + } elsif ($positionStart > $a[1]) { # up $angleTime = $angleTime * ($anglePos - $angleMin) /($angleMax - $angleMin); - $shutTime = $shutTime * ($position - $a[1]) / 100 + $angleTime; + $shutTime = $shutTime * ($positionStart - $a[1]) / 100 + $angleTime; + # round up + $angleTime = int($angleTime) + 1 if ($angleTime > int($angleTime)); + $shutTime = int($shutTime) + 1 if ($shutTime > int($shutTime)); $position = $a[1] - $angleTime / $shutTimeSet * 100; if ($position <= 0) { $position = 0; @@ -1260,13 +1392,17 @@ EnOcean_Set($@) InternalTimer(gettimeofday() + $shutTime + 1, "EnOcean_TimerSet", \%par, 0); } } else { - if ($anglePosLast > $anglePos) { - # up - $shutTime = $angleTime * ($anglePosLast - $anglePos)/($angleMax - $angleMin); + if ($anglePosStart > $anglePos) { + # up >> reduce slats angle + $shutTime = $angleTime * ($anglePosStart - $anglePos)/($angleMax - $angleMin); + # round up + $shutTime = int($shutTime) + 1 if ($shutTime > int($shutTime)); $shutCmd = 1; - } elsif ($anglePosLast < $anglePos) { - # down - $shutTime = $angleTime * ($anglePos - $anglePosLast) /($angleMax - $angleMin); + } elsif ($anglePosStart < $anglePos) { + # down >> enlarge slats angle + $shutTime = $angleTime * ($anglePos - $anglePosStart) /($angleMax - $angleMin); + # round up + $shutTime = int($shutTime) + 1 if ($shutTime > int($shutTime)); $shutCmd = 2; } else { # position and slats angle ok @@ -1291,7 +1427,7 @@ EnOcean_Set($@) } } } else { - return "Unknown argument " . $cmd . ", choose one of closes down opens position:slider,0,5,100 stop teach up" + return "Unknown argument " . $cmd . ", choose one of closes:noArg down opens:noArg position:slider,0,5,100 stop:noArg teach:noArg up" } if ($shutCmd || $cmd eq "stop") { $updateState = 0; @@ -1300,6 +1436,440 @@ EnOcean_Set($@) Log3 $name, 2, "EnOcean set $name $cmd"; } + } elsif ($st eq "actuator.01") { + # Electronic switches and dimmers with Energy Measurement and Local Control + # (D2-01-00 - D2-01-11) + $rorg = "D2"; + $updateState = 0; + my $cmdID; + my $channel; + my $dimValTimer = 0; + my $outputVal; + + if ($cmd eq "on") { + shift(@a); + $cmdID = 1; + my $dimValueOn = AttrVal($name, "dimValueOn", 100); + if ($dimValueOn eq "stored") { + $outputVal = ReadingsVal($name, "dimValueStored", 100); + if ($outputVal < 1) { + $outputVal = 100; + readingsSingleUpdate ($hash, "dimValueStored", $outputVal, 1); + } + } elsif ($dimValueOn eq "last") { + $outputVal = ReadingsVal ($name, "dimValueLast", 100); + if ($outputVal < 1) { $outputVal = 100; } + } else { + if ($dimValueOn !~ m/^[+-]?\d+$/) { + $outputVal = 100; + } elsif ($dimValueOn > 100) { + $outputVal = 100; + } elsif ($dimValueOn < 1) { + $outputVal = 1; + } else { + $outputVal = $dimValueOn; + } + } + $channel = shift(@a); + if (!defined $channel || $channel eq "all") { + CommandDeleteReading(undef, "$name channel.*"); + CommandDeleteReading(undef, "$name dimValue.*"); + readingsSingleUpdate($hash, "channelAll", "on", 1); + readingsSingleUpdate($hash, "dimValueAll", $outputVal, 1); + $channel = 30; + } elsif ($channel eq "input") { + readingsSingleUpdate($hash, "channelInput", "on", 1); + readingsSingleUpdate($hash, "dimValueInput", $outputVal, 1); + $channel = 31; + } elsif ($channel >= 0 && $channel <= 29) { + readingsSingleUpdate($hash, "channel" . $channel, "on", 1); + readingsSingleUpdate($hash, "dimValue" . $channel, $outputVal, 1); + } else { + return "$cmd $channel wrong, choose 0...39|all|input."; + } + readingsSingleUpdate($hash, "state", "on", 1); + $data = sprintf "%02X%02X%02X", $cmdID, $dimValTimer << 5 | $channel, $outputVal; + + } elsif ($cmd eq "off") { + shift(@a); + $cmdID = 1; + $outputVal = 0; + $channel = shift(@a); + if (!defined $channel || $channel eq "all") { + CommandDeleteReading(undef, "$name channel.*"); + CommandDeleteReading(undef, "$name dimValue.*"); + readingsSingleUpdate($hash, "channelAll", "off", 1); + readingsSingleUpdate($hash, "dimValueAll", $outputVal, 1); + $channel = 30; + } elsif ($channel eq "input") { + readingsSingleUpdate($hash, "channelInput", "off", 1); + readingsSingleUpdate($hash, "dimValueInput", $outputVal, 1); + $channel = 31; + } elsif ($channel >= 0 && $channel <= 29) { + readingsSingleUpdate($hash, "channel" . $channel, "off", 1); + readingsSingleUpdate($hash, "dimValue" . $channel, $outputVal, 1); + } else { + return "$cmd $channel wrong, choose 0...39|all|input."; + } + readingsSingleUpdate($hash, "state", "off", 1); + $data = sprintf "%02X%02X%02X", $cmdID, $dimValTimer << 5 | $channel, $outputVal; + + } elsif ($cmd eq "dim") { + shift(@a); + $cmdID = 1; + $outputVal = shift(@a); + if (!defined $outputVal || $outputVal !~ m/^[+-]?\d+$/ || $outputVal < 0 || $outputVal > 100) { + return "Usage: $cmd variable is not numeric or out of range."; + } + $channel = shift(@a); + if (!defined $channel) { + CommandDeleteReading(undef, "$name channel.*"); + CommandDeleteReading(undef, "$name dimValue.*"); + if ($outputVal == 0) { + readingsSingleUpdate($hash, "channelAll", "off", 1); + } else { + readingsSingleUpdate($hash, "channelAll", "on", 1); + } + readingsSingleUpdate($hash, "dimValueAll", $outputVal, 1); + $channel = 30; + } else { + if ($channel eq "all") { + CommandDeleteReading(undef, "$name channel.*"); + CommandDeleteReading(undef, "$name dimValue.*"); + if ($outputVal == 0) { + readingsSingleUpdate($hash, "channelAll", "off", 1); + } else { + readingsSingleUpdate($hash, "channelAll", "on", 1); + } + readingsSingleUpdate($hash, "dimValueAll", $outputVal, 1); + $channel = 30; + } elsif ($channel eq "input") { + if ($outputVal == 0) { + readingsSingleUpdate($hash, "channelInput", "off", 1); + } else { + readingsSingleUpdate($hash, "channelInput", "on", 1); + } + readingsSingleUpdate($hash, "dimValueInput", $outputVal, 1); + $channel = 31; + } elsif ($channel >= 0 && $channel <= 29) { + if ($outputVal == 0) { + readingsSingleUpdate($hash, "channel" . $channel, "off", 1); + } else { + readingsSingleUpdate($hash, "channel" . $channel, "on", 1); + } + readingsSingleUpdate($hash, "dimValue" . $channel, $outputVal, 1); + } else { + return "Usage: $cmd $channel wrong, choose 0...39|all|input."; + } + $dimValTimer = shift(@a); + if (defined $dimValTimer) { + if ($dimValTimer eq "switch") { + $dimValTimer = 0; + } elsif ($dimValTimer eq "stop") { + $dimValTimer = 4; + } elsif ($dimValTimer =~ m/^[1-3]$/) { + + } else { + return "Usage: $cmd $dimValTimer wrong, choose 1..3|switch|stop."; + } + } else { + $dimValTimer = 0; + } + } + if ($outputVal == 0) { + readingsSingleUpdate($hash, "state", "off", 1); + } else { + readingsSingleUpdate($hash, "state", "on", 1); + } + $data = sprintf "%02X%02X%02X", $cmdID, $dimValTimer << 5 | $channel, $outputVal; + + } elsif ($cmd eq "local") { + shift(@a); + $cmdID = 2; + # same configuration for all channels + $channel = 30; + my $dayNight = ReadingsVal($name, "dayNight", "day"); + my $dayNightCmd = ($dayNight eq "night")? 1:0; + my $defaultState = ReadingsVal($name, "defaultState", "off"); + my $defaultStateCmd; + if ($defaultState eq "off") { + $defaultStateCmd = 0; + } elsif ($defaultState eq "on") { + $defaultStateCmd = 1; + } elsif ($defaultState eq "last") { + $defaultStateCmd = 2; + } else { + $defaultStateCmd = 0; + } + my $localControl = ReadingsVal($name, "localControl", "disabled"); + my $localControlCmd = ($localControl eq "enabled")? 1:0; + my $overCurrentShutdown = ReadingsVal($name, "overCurrentShutdown", "off"); + my $overCurrentShutdownCmd = ($overCurrentShutdown eq "restart")? 1:0; + my $overCurrentShutdownReset = "not_active"; + my $overCurrentShutdownResetCmd = 0; + my $rampTime1 = ReadingsVal($name, "rampTime1", 0); + my $rampTime1Cmd = $rampTime1 * 2; + if ($rampTime1Cmd <= 0) { + $rampTime1Cmd = 0; + } elsif ($rampTime1Cmd >= 15) { + $rampTime1Cmd = 15; + } + my $rampTime2 = ReadingsVal($name, "rampTime2", 0); + my $rampTime2Cmd = $rampTime2 * 2; + if ($rampTime2Cmd <= 0) { + $rampTime2Cmd = 0; + } elsif ($rampTime2Cmd >= 15) { + $rampTime2Cmd = 15; + } + my $rampTime3 = ReadingsVal($name, "rampTime3", 0); + my $rampTime3Cmd = $rampTime3 * 2; + if ($rampTime3Cmd <= 0) { + $rampTime3Cmd = 0; + } elsif ($rampTime3Cmd >= 15) { + $rampTime3Cmd = 15; + } + my $teachInDev = ReadingsVal($name, "teachInDev", "disabled"); + my $teachInDevCmd = ($teachInDev eq "enabled")? 1:0; + my $localCmd = shift(@a); + my $localCmdVal = shift(@a); + if ($localCmd eq "dayNight") { + if ($localCmdVal eq "day") { + $dayNight = "day"; + $dayNightCmd = 0; + } elsif ($localCmdVal eq "night") { + $dayNight = "night"; + $dayNightCmd = 1; + } else { + return "Usage: $cmd $localCmd wrong, choose day night."; + } + } elsif ($localCmd eq "defaultState"){ + if ($localCmdVal eq "off") { + $defaultState = "off"; + $defaultStateCmd = 0; + } elsif ($localCmdVal eq "on") { + $defaultState = "on"; + $defaultStateCmd = 1; + } elsif ($localCmdVal eq "last") { + $defaultState = "last"; + $defaultStateCmd = 2; + } else { + return "Usage: $cmd $localCmd wrong, choose on off last."; + } + } elsif ($localCmd eq "localControl"){ + if ($localCmdVal eq "disabled") { + $localControl = "disabled"; + $localControlCmd = 0; + } elsif ($localCmdVal eq "enabled") { + $localControl = "enabled"; + $localControlCmd = 1; + } else { + return "Usage: $cmd $localCmd wrong, choose disabled enabled."; + } + } elsif ($localCmd eq "overCurrentShutdown"){ + if ($localCmdVal eq "off") { + $overCurrentShutdown = "off"; + $overCurrentShutdownCmd = 0; + } elsif ($localCmdVal eq "restart") { + $overCurrentShutdown = "restart"; + $overCurrentShutdownCmd = 1; + } else { + return "Usage: $cmd $localCmd wrong, choose off restart."; + } + } elsif ($localCmd eq "overCurrentShutdownReset"){ + if ($localCmdVal eq "not_active") { + $overCurrentShutdownReset = "not_active"; + $overCurrentShutdownResetCmd = 0; + } elsif ($localCmdVal eq "trigger") { + $overCurrentShutdownReset = "trigger"; + $overCurrentShutdownResetCmd = 1; + } else { + return "Usage: $cmd $localCmd wrong, choose not_active trigger."; + } + } elsif ($localCmd eq "rampTime1"){ + if ($localCmdVal >= 0 || $localCmdVal <= 7.5) { + $rampTime1 = $localCmdVal; + $rampTime1Cmd = $localCmdVal * 2; + } else { + return "Usage: $cmd $localCmd wrong, choose 0, 0.5, ..., 7, 7.5"; + } + } elsif ($localCmd eq "rampTime2"){ + if ($localCmdVal >= 0 || $localCmdVal <= 7.5) { + $rampTime2 = $localCmdVal; + $rampTime2Cmd = $localCmdVal * 2; + } else { + return "Usage: $cmd $localCmd wrong, choose 0, 0.5, ..., 7, 7.5"; + } + } elsif ($localCmd eq "rampTime3"){ + if ($localCmdVal >= 0 || $localCmdVal <= 7.5) { + $rampTime3 = $localCmdVal; + $rampTime3Cmd = $localCmdVal * 2; + } else { + return "Usage: $cmd $localCmd wrong, choose 0, 0.5, ..., 7, 7.5"; + } + } elsif ($localCmd eq "teachInDev"){ + if ($localCmdVal eq "disabled") { + $teachInDev = "disabled"; + $teachInDevCmd = 0; + } elsif ($localCmdVal eq "enabled") { + $teachInDev = "enabled"; + $teachInDevCmd = 1; + } else { + return "Usage: $cmd $localCmd wrong, choose disabled enabled."; + } + } else { + return "Usage: $cmd wrong, choose defaultState|localControl|" . + "overCurrentShutdown|overCurrentShutdownReset|rampTime1|rampTime2|rampTime3|teachInDev."; + } + readingsSingleUpdate($hash, "dayNight", $dayNight, 1); + readingsSingleUpdate($hash, "defaultState", $defaultState, 1); + readingsSingleUpdate($hash, "localControl", $localControl, 1); + readingsSingleUpdate($hash, "overCurrentShutdown", $overCurrentShutdown, 1); + readingsSingleUpdate($hash, "overCurrentShutdownReset", $overCurrentShutdownReset, 1); + readingsSingleUpdate($hash, "rampTime1", $rampTime1, 1); + readingsSingleUpdate($hash, "rampTime2", $rampTime2, 1); + readingsSingleUpdate($hash, "rampTime3", $rampTime3, 1); + readingsSingleUpdate($hash, "teachInDev", $teachInDev, 1); + $data = sprintf "%02X%02X%02X%02X", $teachInDevCmd << 7 | $cmdID, + $overCurrentShutdownCmd << 7 | $overCurrentShutdownResetCmd << 6 | $localControlCmd << 5 | $channel, + int($rampTime2Cmd) << 4 | int($rampTime3Cmd), + $dayNightCmd << 7 | $defaultStateCmd << 4 | int($rampTime1Cmd); + + } elsif ($cmd eq "measurement") { + shift(@a); + $cmdID = 5; + # same configuration for all channels + $channel = 30; + my $measurementMode = ReadingsVal($name, "measurementMode", "energy"); + my $measurementModeCmd = ($measurementMode eq "power")? 0:1; + my $measurementReport = ReadingsVal($name, "measurementReport", "query"); + my $measurementReportCmd = ($measurementReport eq "auto")? 0:1; + my $measurementReset = "not_active"; + my $measurementResetCmd = 0; + my $measurementDelta = int(ReadingsVal($name, "measurementDelta", 0)); + if ($measurementDelta <= 0) { + $measurementDelta = 0; + } elsif ($measurementDelta >= 4095) { + $measurementDelta = 4095; + } + my $unit = ReadingsVal($name, "measurementUnit", "Ws"); + my $unitCmd; + if ($unit eq "Ws") { + $unitCmd = 0; + } elsif ($unit eq "Wh") { + $unitCmd = 1; + } elsif ($unit eq "KWh") { + $unitCmd = 2; + } elsif ($unit eq "W") { + $unitCmd = 3; + } elsif ($unit eq "KW") { + $unitCmd = 4; + } else { + $unitCmd = 0; + } + my $responseTimeMax = ReadingsVal($name, "responseTimeMax", 10); + my $responseTimeMaxCmd = $responseTimeMax / 10; + if ($responseTimeMaxCmd <= 0) { + $responseTimeMaxCmd = 0; + } elsif ($responseTimeMaxCmd >= 255) { + $responseTimeMaxCmd = 255; + } + my $responseTimeMin = ReadingsVal($name, "responseTimeMin", 0); + if ($responseTimeMin <= 0) { + $responseTimeMin = 0; + } elsif ($responseTimeMin >= 255) { + $responseTimeMin = 255; + } + my $measurementCmd = shift(@a); + my $measurementCmdVal = shift(@a); + if ($measurementCmd eq "mode") { + if ($measurementCmdVal eq "energy") { + $measurementMode = "energy"; + $measurementModeCmd = 0; + } elsif ($measurementCmdVal eq "power") { + $measurementMode = "power"; + $measurementModeCmd = 1; + } else { + return "Usage: $cmd $measurementCmd wrong, choose energy power."; + } + } elsif ($measurementCmd eq "report"){ + if ($measurementCmdVal eq "query") { + $measurementReport = "query"; + $measurementReportCmd = 0; + } elsif ($measurementCmdVal eq "auto") { + $measurementReport = "auto"; + $measurementReportCmd = 1; + } else { + return "Usage: $cmd $measurementCmd wrong, choose query auto."; + } + } elsif ($measurementCmd eq "reset"){ + if ($measurementCmdVal eq "not_active") { + $measurementReset = "not_active"; + $measurementResetCmd = 0; + } elsif ($measurementCmdVal eq "trigger") { + $measurementReset = "trigger"; + $measurementResetCmd = 1; + } else { + return "Usage: $cmd $measurementCmd wrong, choose not_active trigger."; + } + } elsif ($measurementCmd eq "unit"){ + if ($measurementCmdVal eq "Ws") { + $unit = "Ws"; + $unitCmd = 0; + } elsif ($measurementCmdVal eq "Wh") { + $unit = "Wh"; + $unitCmd = 1; + } elsif ($measurementCmdVal eq "KWh") { + $unit = "KWh"; + $unitCmd = 2; + } elsif ($measurementCmdVal eq "W") { + $unit = "W"; + $unitCmd = 3; + } elsif ($measurementCmdVal eq "KW") { + $unit = "KW"; + $unitCmd = 4; + } else { + return "Usage: $cmd $measurementCmd wrong, choose Ws Wh KWh W KW."; + } + } elsif ($measurementCmd eq "delta"){ + if ($measurementCmdVal >= 0 || $measurementCmdVal <= 4095) { + $measurementDelta = int($measurementCmdVal); + } else { + return "Usage: $cmd $measurementCmd wrong, choose 0 ... 4095"; + } + } elsif ($measurementCmd eq "responseTimeMax"){ + if ($measurementCmdVal >= 10 || $measurementCmdVal <= 2550) { + $responseTimeMax = int($measurementCmdVal); + $responseTimeMaxCmd = int($measurementCmdVal) / 10; + } else { + return "Usage: $cmd $measurementCmd wrong, choose 10 ... 2550"; + } + } elsif ($measurementCmd eq "responseTimeMin"){ + if ($measurementCmdVal >= 0 || $measurementCmdVal <= 255) { + $responseTimeMin = int($measurementCmdVal); + } else { + return "Usage: $cmd $measurementCmd wrong, choose 0 ... 255"; + } + } else { + return "Usage: $cmd wrong, choose mode|report|" . + "reset|delta|unit|responseTimeMax|responseTimeMin."; + } + readingsSingleUpdate($hash, "measurementMode", $measurementMode, 1); + readingsSingleUpdate($hash, "measurementReport", $measurementReport, 1); + readingsSingleUpdate($hash, "measurementReset", $measurementReset, 1); + readingsSingleUpdate($hash, "measurementDelta", $measurementDelta, 1); + readingsSingleUpdate($hash, "measurementUnit", $unit, 1); + readingsSingleUpdate($hash, "responseTimeMax", $responseTimeMax, 1); + readingsSingleUpdate($hash, "responseTimeMin", $responseTimeMin, 1); + $data = sprintf "%02X%02X%02X%02X%02X%02X", $cmdID, + $measurementReportCmd << 7 | $measurementResetCmd << 6 | $measurementModeCmd << 5 | $channel, + ($measurementDelta | 0x0F) << 4 | $unitCmd, ($measurementDelta | 0xFF00) >> 8, + $responseTimeMax, $responseTimeMin; + } else { + my $cmdList = "dim:slider,0,1,100 on:noArg off:noArg local measurement"; + return SetExtensions ($hash, $cmdList, $name, @a); + } + Log3 $name, 3, "EnOcean set $name $cmd $data"; + } elsif ($st eq "contact") { # 1BS Telegram # Single Input Contact (EEP D5-00-01) @@ -1312,7 +1882,7 @@ EnOcean_Set($@) } elsif ($cmd eq "open") { $setCmd = 8; } else { - return "Unknown argument $cmd, choose one of open closed teach"; + return "Unknown argument $cmd, choose one of open:noArg closed:noArg teach:noArg"; } $data = sprintf "%02X", $setCmd; Log3 $name, 2, "EnOcean set $name $cmd"; @@ -1353,7 +1923,7 @@ EnOcean_Set($@) } } elsif ($cmd eq "UTE") { # UTE Telegram - if ($a[1] && $a[1] =~ m/^[\dA-F]{7}$/) { + if ($a[1] && $a[1] =~ m/^[\dA-F]{14}$/) { $data = $a[1]; $rorg = "D4"; } else { @@ -1463,8 +2033,7 @@ EnOcean_Set($@) } else { # subtype does not support set commands $updateState = -1; - return; - + return; } # send commands @@ -1500,17 +2069,28 @@ EnOcean_Parse($$) my (undef, $packetType, $rorg, $data, $id, $status, $odata) = split(":", $msg); my $rorgname = $EnO_rorgname{$rorg}; if (!$rorgname) { - #Log 2, "EnOcean RORG ($rorg) received from $id unknown."; Log3 undef, 2, "EnOcean RORG ($rorg) received from $id unknown."; return ""; } my $hash = $modules{EnOcean}{defptr}{$id}; if (!$hash) { - #Log 3, "EnOcean Unknown device with ID $id, please define it"; - Log3 undef, 3, "EnOcean Unknown device with ID $id, please define it"; - return "UNDEFINED EnO_${rorgname}_$id EnOcean $id"; + if ($rorgname eq "UTE") { + if ($iohash->{Teach}) { + Log3 undef, 3, "EnOcean Unknown device with ID $id and RORG $rorgname, please define it."; + return "UNDEFINED EnO_${rorgname}_$id EnOcean $id $msg"; + } else { + Log3 undef, 3, "EnOcean Unknown device with ID $id and RORG $rorgname, set transceiver in teach mode."; + return ""; + } + } else { + Log3 undef, 3, "EnOcean Unknown device with ID $id and RORG $rorgname, please define it."; + return "UNDEFINED EnO_${rorgname}_$id EnOcean $id $msg"; + } } my $name = $hash->{NAME}; + my $teach = $defs{$name}{IODev}{Teach}; + my $teachOut; + # extract data bytes $db[x] ... $db[0] my @db; my $dbCntr = 0; @@ -1522,7 +2102,7 @@ EnOcean_Parse($$) my $model = AttrVal($name, "model", ""); my $manufID = AttrVal($name, "manufID", ""); my $st = AttrVal($name, "subType", ""); - Log3 $name, 5, "EnOcean $name PacketType: $packetType RORG:$rorg DATA:$data ID:$id STATUS:$status"; + Log3 $name, 5, "EnOcean $name PacketType:$packetType RORG:$rorg DATA:$data ID:$id STATUS:$status"; if ($rorg eq "F6") { # RPS Telegram (PTM200) @@ -1535,88 +2115,107 @@ EnOcean_Parse($$) #push @event, "1:T21:".((hex($status) & 0x20) >> 5); #push @event, "1:NU:$nu"; - if ($nu) { - # Theoretically there can be a released event with some of the A0,BI - # pins set, but with the plastic cover on this wont happen. - $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"); - - } else { - if ($db[0] == 112) { - # Key Card, not tested - $msg = "keycard inserted"; - - } elsif ($db[0] & 0xC0) { - # Only a Mechanical Handle is setting these bits when nu=0 - $msg = "closed" if ($db[0] == 0xF0); - $msg = "open" if ($db[0] == 0xE0); - $msg = "tilted" if ($db[0] == 0xD0); - $msg = "open from tilted" if ($db[0] == 0xC0); - - } else { - if($st eq "keycard") { - $msg = "keycard removed"; - } - else { - $msg = (($db[0] & 0x10) ? "pressed" : "released"); - } - } - push @event, "3:buttons:" . ($db[0] & 0x10 ? "pressed" : "released"); - } - if ($st eq "FRW") { - # smoke detector Eltako FRW, untested - if ($msg =~ m/A0$/) { + # smoke detector Eltako FRW + if ($db[0] == 0x30) { push @event, "3:battery:low"; - } elsif ($msg =~ m/AI$/) { + } elsif ($db[0] == 0x10) { push @event, "3:alarm:smoke-alarm"; $msg = "smoke-alarm"; - } elsif ($msg =~ m/released$/) { + } elsif ($db[0] == 0) { push @event, "3:alarm:off"; push @event, "3:battery:ok"; $msg = "off"; } - } elsif ($model eq "FAE14" || $model eq "FHK14") { + } elsif ($model eq "FAE14" || $model eq "FHK14" || $model eq "FHK61") { # heating/cooling relay FAE14, FHK14, untested $event = "controllerMode"; - if ($msg =~ m/A0$/) { + if ($db[0] == 0x30) { # night reduction 2 K push @event, "3:energyHoldOff:holdoff"; $msg = "auto"; - } elsif ($msg =~ m/AI$/) { + } elsif ($db[0] == 0x10) { # off push @event, "3:energyHoldOff:normal"; $msg = "off"; - } elsif ($msg =~ m/B0$/) { + } elsif ($db[0] == 0x70) { # on push @event, "3:energyHoldOff:normal"; $msg = "auto"; - } elsif ($msg =~ m/BI$/) { + } elsif ($db[0] == 0x50) { # night reduction 4 K push @event, "3:energyHoldOff:holdoff"; $msg = "auto"; } + } elsif ($st eq "gateway") { + # Eltako switching, dimming + if ($db[0] == 0x70) { + # on + $msg = "on"; + } elsif ($db[0] == 0x50) { + # off + $msg = "off"; + } + } elsif ($st eq "manufProfile" && $manufID eq "00D") { - # shutter - if ($msg =~ m/B0$/) { + # Eltako shutter + if ($db[0] == 0x70) { # open - push @event, "3:endPosition:open"; - $msg = "open"; - } elsif ($msg =~ m/BI$/) { + push @event, "3:endPosition:open_ack"; + $msg = "open_ack"; + } elsif ($db[0] == 0x50) { # closed + push @event, "3:position:100"; + push @event, "3:anglePos:" . AttrVal($name, "angleMax", 90); push @event, "3:endPosition:closed"; $msg = "closed"; - } elsif ($msg =~ m/released$/) { + } elsif ($db[0] == 0) { # not reached or not available push @event, "3:endPosition:not_reached"; $msg = "not_reached"; + } elsif ($db[0] == 1) { + # up + push @event, "3:endPosition:not_reached"; + $msg = "up"; + } elsif ($db[0] == 2) { + # down + push @event, "3:endPosition:not_reached"; + $msg = "down"; } } else { + if ($nu) { + # Theoretically there can be a released event with some of the A0, BI + # pins set, but with the plastic cover on this wont happen. + $msg = $EnO_ptm200btn[($db[0] & 0xE0) >> 5]; + $msg .= "," . $EnO_ptm200btn[($db[0] & 0x0E) >> 1] if ($db[0] & 1); + $msg .= " released" if (!($db[0] & 0x10)); + + } else { + if ($db[0] == 112) { + # Key Card, not tested + $msg = "keycard inserted"; + + } elsif ($db[0] & 0xC0) { + # Only a Mechanical Handle is setting these bits when nu=0 + $msg = "closed" if ($db[0] == 0xF0); + $msg = "open" if ($db[0] == 0xE0); + $msg = "tilted" if ($db[0] == 0xD0); + $msg = "open from tilted" if ($db[0] == 0xC0); + + } else { + if($st eq "keycard") { + $msg = "keycard removed"; + } + else { + $msg = (($db[0] & 0x10) ? "pressed" : "released"); + } + } + } + push @event, "3:buttons:" . ($db[0] & 0x10 ? "pressed" : "released"); + if ($msg =~ m/A0$/) { push @event, "3:channelA:A0"; } elsif ($msg =~ m/AI$/) { @@ -1634,15 +2233,15 @@ EnOcean_Parse($$) } elsif ($msg =~ m/DI$/) { push @event, "3:channelD:DI"; } - # released events are disturbing when using a remote, since it overwrites - # the "real" state immediately. In the case of an Eltako FSB14, FSB61 ... - # the state should remain released. (by Thomas) - $event = "buttons" if ($msg =~ m/released$/ && - $model ne "FT55" && $model ne "FSB14" && - $model ne "FSB61" && $model ne "FSB70" && - $model ne "FSM12" && $model ne "FSM61" && - $model ne "FTS12" && - AttrVal($name, "sensorMode", "switch") ne "pushbutton"); + # released events are disturbing when using a remote, since it overwrites + # the "real" state immediately. In the case of an Eltako FSB14, FSB61 ... + # the state should remain released. (by Thomas) + $event = "buttons" if ($msg =~ m/released$/ && + $model ne "FT55" && $model ne "FSB14" && + $model ne "FSB61" && $model ne "FSB70" && + $model ne "FSM12" && $model ne "FSM61" && + $model ne "FTS12" && + AttrVal($name, "sensorMode", "switch") ne "pushbutton"); } push @event, "3:$event:$msg"; @@ -1665,25 +2264,60 @@ EnOcean_Parse($$) # manufID to account for vendor-specific features $attr{$name}{manufID} = $mf; $mf = $EnO_manuf{$mf} if($EnO_manuf{$mf}); - #Log 1, "EnOcean $name teach-in EEP A5-$fn-$tp Manufacturer: $mf"; - Log3 undef, 1, "EnOcean $name teach-in EEP A5-$fn-$tp Manufacturer: $mf"; - push @event, "3:teach-in:EEP A5-$fn-$tp Manufacturer: $mf"; my $st = "A5.$fn.$tp"; - $st = $EnO_subType{$st} if($EnO_subType{$st}); - $attr{$name}{subType} = $st; + if($EnO_subType{$st}) { + $st = $EnO_subType{$st}; + push @event, "3:teach-in:EEP A5-$fn-$tp Manufacturer: $mf"; + Log3 $name, 1, "EnOcean $name teach-in EEP A5-$fn-$tp Manufacturer: $mf"; + $attr{$name}{subType} = $st; + } else { + push @event, "3:teach-in:EEP A5-$fn-$tp Manufacturer: $mf not supported"; + Log3 $name, 1, "EnOcean $name teach-in EEP A5-$fn-$tp Manufacturer: $mf not supported"; + $attr{$name}{subType} = "raw"; + } - if ("$fn.$tp" eq "20.01" && $iohash->{pair}) { - # bidirectional Teach-In for EEP A5-20-01 (MD15) - $attr{$name}{comMode} = "biDir"; - $attr{$name}{destinationID} = "unicast"; - # SenderID = BaseID - $attr{$name}{subDef} = "00000000"; - # 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, "800800F0", "00000000", "00", $hash->{DEF}); - select(undef, undef, undef, 0.5); - EnOcean_MD15Cmd($hash, $name, 128); # 128 == 20 degree C + if ($st eq "hvac.01" || $st eq "MD15") { + if ($teach) { + # bidirectional Teach-In for EEP A5-20-01 (MD15) + $attr{$name}{comMode} = "biDir"; + $attr{$name}{destinationID} = "unicast"; + # SenderID = BaseID + $attr{$name}{subDef} = "00000000"; + # 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", "00000000", "00", $hash->{DEF}); + #EnOcean_SndRadio(undef, $hash, $rorg, "800800F0", "00000000", "00", $hash->{DEF}); + select(undef, undef, undef, 0.5); + EnOcean_hvac_01Cmd($hash, $name, 128); # 128 == 20 degree C + } else { + Log3 $name, 3, "EnOcean Unknown device $name and subType $st, set transceiver in teach mode."; + return ""; + } + } elsif ($st eq "hvac.02") { + if ($teach) { + } else { + Log3 $name, 3, "EnOcean Unknown device $name and subType $st, set transceiver in teach mode."; + return ""; + } + } elsif ($st eq "hvac.03") { + if ($teach) { + } else { + Log3 $name, 3, "EnOcean Unknown device $name and subType $st, set transceiver in teach mode."; + return ""; + } + } elsif ($st eq "hvac.10") { + if ($teach) { + } else { + Log3 $name, 3, "EnOcean Unknown device $name and subType $st, set transceiver in teach mode."; + return ""; + } + } elsif ($st eq "hvac.11") { + if ($teach) { + } else { + Log3 $name, 3, "EnOcean Unknown device $name and subType $st, set transceiver in teach mode."; + return ""; + } } # store attr subType, manufID ... CommandSave(undef, undef); @@ -1694,7 +2328,7 @@ EnOcean_Parse($$) push @event, "3:teach-in:No EEP profile identifier and no Manufacturer ID"; } - } elsif ($st eq "MD15") { + } elsif ($st eq "hvac.01" || $st eq "MD15") { # Battery Powered Actuator (EEP A5-20-01) # [Kieback&Peter MD15-FTL-xx] push @event, "3:state:$db[3]"; @@ -1709,7 +2343,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_MD15Cmd($hash, $name, $db[1]); + EnOcean_hvac_01Cmd($hash, $name, $db[1]); } elsif ($st eq "PM101") { # Light and Presence Sensor [Omnio Ratio eagle-PM101] @@ -1787,7 +2421,7 @@ EnOcean_Parse($$) } elsif ($st eq "tempHumiCO2Sensor.01") { # Gas Sensor, CO2 Sensor (EEP A5-09-04) - # [Thermokon SR04 CO2 *, untested] + # [Thermokon SR04 CO2 *, Eltako FCOTF63, untested] # $db[3] is the humidity where 0x00 = 0 %rH ... 0xC8 = 100 %rH # $db[2] is the CO2 concentration where 0x00 = 0 ppm ... 0xFF = 2500 ppm # $db[1] is the temperature where 0x00 = 0°C ... 0xFF = +51 °C @@ -1951,14 +2585,20 @@ EnOcean_Parse($$) my $temp = sprintf "%0.1f", $db[1] * 40 / 250; my $humi = sprintf "%d", $db[2] / 2.5; my $switch = $db[0] & 1; - push @event, "3:state:T: $temp H: $humi SP: $db[2] SW: $switch"; push @event, "3:humidity:$humi"; - push @event, "3:switch:$switch"; - push @event, "3:setpoint:$db[2]"; push @event, "3:temperature:$temp"; - my $setpointScaled = EnOcean_ReadingScaled($hash, $db[2], 0, 255); - if (defined $setpointScaled) { - push @event, "3:setpointScaled:" . $setpointScaled; + if ($manufID eq "039") { + my $brightness = sprintf "%d", $db[3] * 117; + push @event, "3:brightness:$brightness"; + push @event, "3:state:T: $temp H: $humi B: $brightness"; + } else { + push @event, "3:setpoint:$db[3]"; + push @event, "3:state:T: $temp H: $humi SP: $db[3] SW: $switch"; + push @event, "3:switch:$switch"; + my $setpointScaled = EnOcean_ReadingScaled($hash, $db[3], 0, 255); + if (defined $setpointScaled) { + push @event, "3:setpointScaled:" . $setpointScaled; + } } } elsif ($st eq "roomSensorControl.02") { @@ -2763,9 +3403,9 @@ EnOcean_Parse($$) push @event, "3:state:" . ($db[0] & 0x01 ? "on" : "off"); if ($db[0] & 4) { # Relative Dimming Range - push @event, "3:dimValue:" . sprintf "%d", $db[2] * 100 / 255; + push @event, "3:dim:" . sprintf "%d", $db[2] * 100 / 255; } else { - push @event, "3:dimValue:$db[2]"; + push @event, "3:dim:$db[2]"; } push @event, "3:dimValueLast:$db[2]" if ($db[2] > 0); } elsif ($db[3] == 3) { @@ -2846,16 +3486,85 @@ EnOcean_Parse($$) push @event, "3:state:I1: $input1 I2: $input2 I3: $input3"; } elsif ($manufID eq "00D") { - # [Eltako FVS telegram for shutters] + # [Eltako shutters, untested] + my $angleMax = AttrVal($name, "angleMax", 90); + my $angleMin = AttrVal($name, "angleMin", -90); + my $anglePos = ReadingsVal($name, ".anglePosStart", undef); + my $angleTime = AttrVal($name, "angleTime", 0); + my $position = ReadingsVal($name, ".positionStart", undef); + my $shutTime = AttrVal($name, "shutTime", 255); + my $shutTimeStop = ($db[3] << 8 | $db[2]) * 0.1; my $state; - if ($db[1] == 1) { - $state = "up"; - } elsif ($db[1] == 2) { - $state = "down"; - } else { - $state = "stop"; + $angleMax = 90 if ($angleMax !~ m/^[+-]?\d+$/); + $angleMax = 180 if ($angleMax > 180); + $angleMax = -180 if ($angleMax < -180); + $angleMin = -90 if ($angleMin !~ m/^[+-]?\d+$/); + $angleMin = 180 if ($angleMin > 180); + $angleMin = -180 if ($angleMin < -180); + ($angleMax, $angleMin) = ($angleMin, $angleMax) if ($angleMin > $angleMax); + $angleMax ++ if ($angleMin == $angleMax); + $angleTime = 6 if ($angleTime !~ m/^[+-]?\d+$/); + $angleTime = 6 if ($angleTime > 6); + $angleTime = 0 if ($angleTime < 0); + $shutTime = 255 if ($shutTime !~ m/^[+-]?\d+$/); + $shutTime = 255 if ($shutTime > 255); + $shutTime = 1 if ($shutTime < 1); + + if ($db[0] == 0x0A) { + push @event, "3:block:unlock"; + } elsif ($db[0] == 0x0E) { + push @event, "3:block:lock"; } + if (defined $position) { + if ($db[1] == 1) { + # up + $position -= $shutTimeStop / $shutTime * 100; + if ($angleTime) { + $anglePos -= ($angleMax - $angleMin) * $shutTimeStop / $angleTime; + if ($anglePos < $angleMin) { + $anglePos = $angleMin; + } + } else { + $anglePos = $angleMin; + } + if ($position <= 0) { + $anglePos = 0; + $position = 0; + push @event, "3:endPosition:open"; + $state = "open"; + } else { + push @event, "3:endPosition:not_reached"; + $state = "stop"; + } + push @event, "3:anglePos:" . sprintf("%d", $anglePos); + push @event, "3:position:" . sprintf("%d", $position); + } elsif ($db[1] == 2) { + # down + $position += $shutTimeStop / $shutTime * 100; + if ($angleTime) { + $anglePos += ($angleMax - $angleMin) * $shutTimeStop / $angleTime; + if ($anglePos > $angleMax) { + $anglePos = $angleMax; + } + } else { + $anglePos = $angleMax; + } + if($position > 100) { + $anglePos = $angleMax; + $position = 100; + push @event, "3:endPosition:closed"; + $state = "closed"; + } else { + push @event, "3:endPosition:not_reached"; + $state = "stop"; + } + push @event, "3:anglePos:" . sprintf("%d", $anglePos); + push @event, "3:position:" . sprintf("%d", $position); + } else { + $state = "not_reached"; + } push @event, "3:state:$state"; + } } else { # Unknown Application @@ -2900,6 +3609,79 @@ EnOcean_Parse($$) # VLD telegram if ($st eq "test") { + } elsif ($st eq "actuator.01") { + # Electronic switches and dimmers with Energy Measurement and Local Control + # (D2-01-00 - D2-01-11) + my $channel = (hex substr($data, 2, 2)) | 0x1F; + if ($channel == 31) {$channel = "Input";} + my $cmd = hex substr($data, 1, 1); + + if ($cmd == 4) { + # actuator status response + my $overCurrentOff; + my $error; + my $localControl; + my $dim; + if ( hex(substr($data, 2, 2)) | 0x80 == 1) { + $overCurrentOff = "executed"; + } else { + $overCurrentOff = "ready"; + } + push @event, "3:overCurrentOff" . $channel . ":" . $overCurrentOff; + if (substr($data, 2, 2) | 0x60 == 1) { + $error = "warning"; + } elsif (hex(substr($data, 2, 2)) | 0x60 == 2) { + $error = "failure"; + } else { + $error = "ok"; + } + push @event, "3:error" . $channel . ":" . $error; + if (hex(substr($data, 4, 2)) | 0x80 == 1) { + $localControl = "enabled"; + } else { + $localControl = "disabled"; + } + push @event, "3:localControl" . $channel . ":" . $localControl; + my $dimValue = hex(substr($data, 4, 2)) | 0x1F; + if (hex(substr($data, 4, 2)) | 0x1F == 0) { + push @event, "3:channel" . $channel . ":off"; + push @event, "3:state:off"; + } else { + push @event, "3:channel" . $channel . ":on"; + push @event, "3:state:on"; + } + push @event, "3:dimValue" . $channel . ":" . $dimValue; + + } elsif ($cmd == 7) { + # actuator measurement response + my $unit = hex(substr($data, 2, 2)) | 0xE0; + if ($unit == 1) { + $unit = "Wh"; + push @event, "3:energyUnit" . $channel . ":" . $unit; + push @event, "3:energy" . $channel . ":" . hex substr($data, 4, 8); + } elsif ($unit == 2) { + $unit = "KWh"; + push @event, "3:energyUnit" . $channel . ":" . $unit; + push @event, "3:energy" . $channel . ":" . hex substr($data, 4, 8); + } elsif ($unit == 3) { + $unit = "W"; + push @event, "3:powerUnit" . $channel . ":" . $unit; + push @event, "3:power" . $channel . ":" . hex substr($data, 4, 8); + } elsif ($unit == 4) { + $unit = "KW"; + push @event, "3:powerUnit" . $channel . ":" . $unit; + push @event, "3:power" . $channel . ":" . hex substr($data, 4, 8); + } else { + $unit = "Ws"; + push @event, "3:engergyUnit" . $channel . ":" . $unit; + push @event, "3:energy" . $channel . ":" . hex substr($data, 4, 8); + } + + } else { + # unknown response + + } + } elsif ($st eq "raw") { # raw push @event, "3:state:RORG: $rorg DATA: $data STATUS: $status ODATA: $odata"; @@ -2911,9 +3693,10 @@ EnOcean_Parse($$) # unknown devices push @event, "3:state:$data"; } - } elsif ($rorg eq "D4") { - # UTE - Universal Uni- und Bidirectional Teach-In - if (!($db[6] & 1)) { + } elsif ($rorg eq "D4" && $teach) { + # UTE - Universal Uni- and Bidirectional Teach-In / Teach Out + # + if (($db[6] & 1) == 0) { # Teach-In Query telegram received my $rorg = sprintf "%02X", $db[0]; my $func = sprintf "%02X", $db[1]; @@ -2930,57 +3713,62 @@ EnOcean_Parse($$) $subType = $EnO_subType{$subType}; $attr{$name}{subType} = $subType; $attr{$name}{manufID} = $mid; - $mid = $EnO_manuf{$mid} if($EnO_manuf{$mid}); - #Log 1, "EnOcean $name teach-in EEP $rorg-$func-$type Manufacturer: $mid"; - Log3 undef, 1, "EnOcean $name teach-in EEP $rorg-$func-$type Manufacturer: $mid"; - push @event, "3:teach-in:EEP $rorg-$func-$type Manufacturer: $mid"; $attr{$name}{devChannel} = $devChannel; $attr{$name}{comMode} = $comMode; + $mid = $EnO_manuf{$mid} if($EnO_manuf{$mid}); + push @event, "3:teach-in:EEP $rorg-$func-$type Manufacturer: $mid"; if (!($db[6] & 0x40)) { # EEP Teach-In-Response expected # send EEP Teach-In-Response message - $data = (sprintf "%02X", $db[6] & 0x80 | 0x11) . substr($data, -1, 12); + $data = (sprintf "%02X", $db[6] & 0x80 | 0x11) . substr($data, 2, 12); my $subDef = "00000000"; if ($comMode eq "biDir") { - ### select a free SenderID - $subDef = EnOcean_CheckSenderID("getNextID", $defs{$hash->{NAME}}{IODev}{NAME}, $subDef); + # select a free SenderID + $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, $rorg, $data, $subDef, "00", $id); + EnOcean_SndRadio(undef, $hash, "D4", $data, $subDef, "00", $id); + Log3 $name, 1, "EnOcean $name UTE teach-in-response send to $id"; } + Log3 $name, 1, "EnOcean $name UTE teach-in EEP $rorg-$func-$type Manufacturer: $mid"; # store attr subType, manufID ... CommandSave(undef, undef); } else { # EEP type not supported + $attr{$name}{subType} = "raw"; + $attr{$name}{manufID} = $mid; + $attr{$name}{devChannel} = $devChannel; + $attr{$name}{comMode} = $comMode; + $mid = $EnO_manuf{$mid} if($EnO_manuf{$mid}); + 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, -1, 12); + $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, $rorg, $data, "00000000", "00", $id); + EnOcean_SndRadio(undef, $hash, "D4", $data, $defs{$name}{IODev}{BaseID}, "00", $id); + Log3 $name, 1, "EnOcean $name EEP $rorg-$func-$type not supported"; + # store attr subType, manufID ... + CommandSave(undef, undef); } - } elsif ($teachInReq == 1) { # Teach-In Deletion Request # send EEP Teach-In Deletion Response message - $data = (sprintf "%02X", $db[6] & 0x80 | 0x21) . substr($data, -1, 12); + $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, $rorg, $data, AttrVal($name, "subDef", "00000000"), "00", $id); - ### device erst am Ende der Parse-Routine loeschen! - #CommandDelete(undef, $name); - + EnOcean_SndRadio(undef, $hash, "D4", $data, AttrVal($name, "subDef", $defs{$name}{IODev}{BaseID}), "00", $id); + Log3 $name, 1, "EnOcean $name delete request executed"; } elsif ($teachInReq == 2) { - # Deletion of Teach-In or Teach-In Request, not specified - - } - + # Deletion of Teach-In or Teach-In Request, not specified + } } else { # Teach-In Respose telegram received # no action - } - + Log3 $name, 1, "EnOcean $name $data UTE Teach-In Respose telegram received"; + } } readingsBeginUpdate($hash); @@ -2990,13 +3778,55 @@ EnOcean_Parse($$) readingsBulkUpdate($hash, $vn, $vv); } readingsEndUpdate($hash, 1); + + if ($teachOut) { + # delete device and save config + CommandDelete(undef, $name); + CommandDelete(undef, "FileLog_" . $name); + CommandSave(undef, undef); + return ""; + } return $name; } -# MD15Cmd +sub EnOcean_Attr(@) { + my ($cmd, $name, $attrName, $attrVal) = @_; + my $hash = $defs{$name}; + + if ($attrName eq "pollInterval") { + if (!defined $attrVal) { + } elsif ($attrVal =~ m/^\d+(\.\d+)?$/) { + } else { + #RemoveInternalTimer($hash); + Log3 $name, 3, "EnOcean $name attribute-value [$attrName] = $attrVal is not a number with positive sign"; + CommandDeleteAttr(undef, "$name pollInterval"); + } + + } elsif ($attrName eq "devUpdate") { + if (!defined $attrVal){ + + } elsif ($attrVal !~ m/^(off|auto|demand|polling|interrupt)$/) { + Log3 $name, 3, "EnOcean $name attribute-value [$attrName] = $attrVal wrong"; + CommandDeleteAttr(undef, "$name devUpdate"); + } + + } + return undef; +} + +sub EnOcean_Notify(@) { + my ($hash, $dev) = @_; + my $name = $hash->{NAME}; + if ($dev->{NAME} eq "global" && grep (m/^INITIALIZED$/,@{$dev->{CHANGED}})){ + Log3($name, 3, "EnOcean $name initialized"); + } + return undef; +} + +# Message from Fhem to the actuator (EEP A5-20-01) sub -EnOcean_MD15Cmd($$$) +EnOcean_hvac_01Cmd($$$) { my ($hash, $name, $db_1) = @_; my $cmd = ReadingsVal($name, "CMD", undef); @@ -3014,16 +3844,10 @@ EnOcean_MD15Cmd($$$) readingsSingleUpdate($hash, "temperature", (sprintf "%0.1f", $actualTemp), 1); if($cmd eq "actuator") { - #$msg = sprintf("%02X000000", $arg1); - #$msg = sprintf "%02X7F0008", $arg1; $msg = sprintf "%02X000008", $arg1; } elsif($cmd eq "desired-temp") { - #$msg = sprintf "%02X%02X0400", $arg1*255/40, AttrVal($name, "actualTemp", ($db_1*40/255)) * 255/40; - #$msg = sprintf "%02X%02X0408", $arg1*255/40, AttrVal($name, "actualTemp", (255 - $db_1)*40/255) *255/40; - #$msg = sprintf "%02X7F0408", $arg1*255/40; $msg = sprintf "%02X%02X0408", $arg1 * 255 / 40, (40 - $actualTemp) * 255 / 40; } elsif($cmd eq "initialize") { - #$msg = sprintf("00006400"); $msg = "00006408"; # Maintenance commands } elsif($cmd eq "runInit") { @@ -3159,7 +3983,7 @@ EnOcean_SndRadio($$$$$$$) my $securityLevel = AttrVal($hash->{NAME}, "securityLevel", 0); if ($securityLevel eq "unencrypted") {$securityLevel = 0;} if ($destinationID ne "FFFFFFFF" || $securityLevel) { - # SubTelNum = 03, DestinationID:4, RSSI = FF, SecurityLevel:2 + # SubTelNum = 03, DestinationID:8, RSSI = FF, SecurityLevel:2 $odata = sprintf "03%sFF%02X", $destinationID, $securityLevel; $odataLength = 7; } @@ -3249,7 +4073,7 @@ EnOcean_Undef($$) model. If the EEP profile identifier and the manufacturer ID are sent the device is clearly identifiable. Fhem automatically assigns these devices to the correct profile. Some 4BS, VLD or MSC devices must be paired - bidirectional, see Bidirectional Teach-In / Teach-Out.

+ bidirectional, see Bidirectional Teach-In / Teach-Out.

Fhem supports many of most common EnOcean profiles and manufacturer-specific devices. Additional profiles and devices can be added if required.

@@ -3288,7 +4112,7 @@ EnOcean_Undef($$) additional SenderIDs you can use the attributes subDef, subDef0 and subDefI.
Fhem communicates unicast with the BaseID, if the 4BS devices are teached-in with the - Bidirectional Teach-In / Teach-Out procedure. In this case + Bidirectional Teach-In / Teach-Out procedure. In this case Fhem send telegrams with its SenderID (BaseID) and the DestinationID of the device.

@@ -3296,20 +4120,22 @@ EnOcean_Undef($$) Set
    -
  • Bidirectional Teach-In / Teach-Out +
  • Bidirectional Teach-In / Teach-Out
      - set <name> pairForSec <t/s> + set <name> teach <t/s>

      - Set the EnOcean Transceiver module (TCM Modul) in the bidirectional pairing - mode. A device, which is then also put in this state is to paired with - Fhem bidirectional. Bidirectional pearing is used for some 4BS, VLD and MSC devices, - e. g. EEP 4BS, RORG A5-20-01 (Battery Powered Actuator). + Set the EnOcean Transceiver module (TCM Modul) in the pairing mode. + A device, which is then also put in this state is to paired with + Fhem. Pearing is used for some 4BS, VLD and MSC devices, + e. g. EEP 4BS, RORG A5-20-01 (Battery Powered Actuator).
      + Bidirectional 4BS Teach-In and UTE - Universal Uni- and Bidirectional + Teach-In are supported.
      name is the name of the TCM Module . t/s is the time for the teach-in period.

      Example: -
        set TCM_0 pairForSec 600 +
          set TCM_0 teach 600

      @@ -3458,7 +4284,7 @@ EnOcean_Undef($$)

    The attr subType must be MD15. This is done if the device was created by autocreate. To control the device, it must be bidirectional paired, - see Bidirectional A5 Teach-In.
    + see Bidirectional Teach-In / Teach-Out.
    The command is not sent until the device wakes up and sends a mesage, usually every 10 minutes.
  • @@ -3716,7 +4542,56 @@ EnOcean_Undef($$) Use the sensor type "Szenentaster/PC" for Eltako devices.

    - + +
  • Electronic switches and dimmers with Energy Measurement and Local Control (D2-01-00 - D2-01-11)
    + [Telefunken Funktionsstecker]
    +
      + set <name> <value> +

      + where value is +
    • on [<channel>]
      + issue switch on command
    • +
    • off [<channel>]
      + issue switch off command
    • +
    • dim dim/% [<channel> [<rampTime>]]
      + issue dimming command
    • +
    • local dayNight day|night, day is default
      + set the user interface indication
    • +
    • local defaultState on|off|last, off is default
      + set the default setting of the output channels when switch on
    • +
    • local localControl enabled|disabled, disabled is default
      + enable the local control of the device
    • +
    • local overCurrentShutdown off|restart, off is default
      + set the behavior after a shutdown due to an overcurrent
    • +
    • local overCurrentShutdownReset not_active|trigger, not_active is default
      + trigger a reset after an overcurrent
    • +
    • local rampTime<1...3> 0/s, 0.5/s ... 7/s, 7.5/s, 0 is default
      + 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
      + define the difference between two displayed measurements
    • +
    • measurement mode energy|power, energy is default
      + define the measurand
    • +
    • measurement report query|auto, query is default
      + specify the measurement method
    • +
    • measurement reset not_active|trigger, not_active is default
      + resetting the measured values
    • +
    • measurement responseTimeMax 10/s ... 2550/s, 10 is default
      + set the maximum time between two outputs of measured values
    • +
    • measurement responseTimeMin 0/s ... 255/s, 0 is default
      + set the minimum time between two outputs of measured values
    • +
    • measurement unit Ws|Wh|KWh|W|KW, Ws is default
      + specify the measurement unit
    • +

    + [channel] = 0...29|all|input, all is default
    + [rampTime] = 1..3|switch|stop, switch is default
    + The attr subType must be actuator.01. 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

      @@ -3737,7 +4612,26 @@ EnOcean_Undef($$) Get -
        N/A

      +
        +
      • Electronic switches and dimmers with Energy Measurement and Local Control (D2-01-00 - D2-01-11)
        + [Telefunken Funktionsstecker]
        +
          + get <name> <value> +

          + where value is +
        • state [<channel>]
          +
        • +
        • measurement <channel> energy|power
          +
        • +

        +
        + The attr subType must be actuator.01. 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. +
      • +

        + +

      Attributes @@ -3745,9 +4639,9 @@ EnOcean_Undef($$)
      • actualTemp t/°C
        The value of the actual temperature, used by a Room Sensor and Control Unit - or when controlling Battery Powered Actuators (MD15 devices). Should by + or when controlling HVAC components e. g. Battery Powered Actuators (MD15 devices). Should by filled via a notify from a distinct temperature sensor.
        - If absent, the reported temperature from the MD15 is used. + If absent, the reported temperature from the HVAC components is used.
      • angleMax αs/°, [αs] = -180 ... 180, 90 is default.
        Slat angle end position maximum.
        @@ -3770,7 +4664,7 @@ EnOcean_Undef($$) Bidirectional communication means a point-to-point communication relationship between an enabled EnOcean device and Fhem. It requires all parties 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. + needs a teach-in / teach-out process, see Bidirectional Teach-In / Teach-Out.
      • devChannel 00 ... FF, [devChannel] = FF is default
        Number of the individual device channel, FF = all channels supported by the device @@ -3958,7 +4852,7 @@ EnOcean_Undef($$)

      • Smoke Detector (EEP F6-02-01 ... F6-02-02)
        - [Eltako FRW, untested]
        + [Eltako FRW]
        • smoke-alarm
        • off
        • @@ -3999,7 +4893,7 @@ EnOcean_Undef($$)

        • Window Handle (EEP F6-10-00)
          - [HOPPE SecuSignal]
          + [HOPPE SecuSignal, Eltako FHF, Eltako FTKE]
          • closed
          • open
          • @@ -4168,7 +5062,7 @@ EnOcean_Undef($$)

          • Gas Sensor, CO2 Sensor (EEP A5-09-04)
            - [Thermokon SR04 CO2 *, untested]
            + [Thermokon SR04 CO2 *, Eltako FCOTF63, untested]
            • airQuality: high|mean|moderate|low (Air Quality Classes DIN EN 13779)
            • CO2: c/ppm (Sensor Range: c = 0 ppm ... 2550 ppm)
            • @@ -4656,7 +5550,7 @@ EnOcean_Undef($$)
            • on
            • off
            • block: lock|unlock
            • -
            • dimValue: dim/% (Sensor Range: dim = 0 % ... 100 %)
            • +
            • dim: dim/% (Sensor Range: dim = 0 % ... 100 %)
            • dimValueLast: dim/%
              Last value received from the bidirectional dimmer.
            • dimValueStored: dim/%
              @@ -4748,7 +5642,7 @@ EnOcean_Undef($$)
              • teach
                Teach-In is sent
              • -
              • open
                +
              • open|open_ack
                The status of the device will become "open" after the TOP endpoint is reached, or it has finished an "opens" or "position 0" command.
              • closed
                @@ -4760,18 +5654,55 @@ EnOcean_Undef($$) The status of the device become "not_reached" between one of the endpoints.
              • anglePos: α/° (Sensor Range: α = -180 ° ... 180 °)
              • buttons: pressed|released
              • -
              • endPosition: open|closed|not_reached|not_available
              • +
              • endPosition: open|open_ack|closed|not_reached|not_available
              • position: pos/% (Sensor Range: pos = 0 % ... 100 %)
              • -
              • state: open|closed|not_reached|stop|teach
              • +
              • state: open|open_ack|closed|not_reached|stop|teach

              The values of the reading position and anglePos are updated automatically, - if the command position is sent and the reading state was changed + if the command position is sent or the reading state was changed manually to open or closed.
              Set attr subType to manufProfile, attr manufID to 00D and attr model to FSB14|FSB61|FSB70 manually.


            • +
            • Electronic switches and dimmers with Energy Measurement and Local Control (D2-01-00 - D2-01-11)
              + [Telefunken Funktionsstecker]
              +
                +
              • on
              • +
              • off
              • +
              • channel<0...29|All|Input>: on|off
              • +
              • dayNight: day|night
              • +
              • defaultState: on|off|last
              • +
              • dimValue<0...29|All|Input>: dim/% (Sensor Range: dim = 0 % ... 100 %)
              • +
              • energy<channel>: 1/[Ws|Wh|KWh]
              • +
              • energyUnit<channel>: Ws|Wh|KWh
              • +
              • error<channel>: ok|warning|failure
              • +
              • localControl<channel>: enabled|disabled
              • +
              • measurementMode: energy|power
              • +
              • measurementReport: auto|query
              • +
              • measurementReset: not_active|trigger
              • +
              • measurementDelta: 1/[Ws|Wh|KWh|W|KW]
              • +
              • measurementUnit: Ws|Wh|KWh|W|KW
              • +
              • overCurrentOff<channel>: executed|ready
              • +
              • overCurrentShutdown<channel>: off|restart
              • +
              • overCurrentShutdownReset<channel>: not_active|trigger
              • +
              • power<channel>: 1/[W|KW]
              • +
              • powerUnit<channel>: W|KW
              • +
              • rampTime<1...3l>: 1/s
              • +
              • responseTimeMax: 1/s
              • +
              • responseTimeMin: 1/s
              • +
              • teachInDev: enabled|disabled
              • + +
              • state: on|off
              • +

              +
              + The attr subType must be actuator.01. 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