diff --git a/fhem/FHEM/10_EnOcean.pm b/fhem/FHEM/10_EnOcean.pm
index 72cb7dd78..b2aa2c2bd 100755
--- a/fhem/FHEM/10_EnOcean.pm
+++ b/fhem/FHEM/10_EnOcean.pm
@@ -450,10 +450,12 @@ my %EnO_eepConfig = (
"I5.38.08" => {attr => {subType => "gateway", comMode => "confirm", eep => "A5-38-08", gwCmd => "dimming", manufID => "00D", model => "Eltako_FUD14", teachMethod => "confirm", webCmd => "on:off:dim"}, GPLOT => "EnO_dim4:Dim,"},
"G5.3F.7F" => {attr => {subType => "manufProfile", eep => "A5-3F-7F", manufID => "00D", webCmd => "opens:stop:closes"}},
"H5.3F.7F" => {attr => {subType => "manufProfile", comMode => "confirm", eep => "A5-3F-7F", manufID => "00D", model => "Eltako_TF", sensorMode => 'pushbutton', settingAccuracy => "high", teachMethod => "confirm", webCmd => "opens:stop:closes"}},
+ "I5.3F.7F" => {attr => {subType => "manufProfile", comMode => "confirm", eep => "A5-3F-7F", manufID => "00D", model => "Eltako_FRM60", sensorMode => 'pushbutton', teachMethod => "confirm", webCmd => "opens:stop:closes:position"}},
"M5.38.08" => {attr => {subType => "gateway", eep => "A5-38-08", gwCmd => "switching", manufID => "00D", webCmd => "on:off"}},
"N5.38.08" => {attr => {subType => "gateway", comMode => "confirm", eep => "A5-38-08", gwCmd => "switching", manufID => "00D", model => "Eltako_TF", teachMethod => "confirm", webCmd => "on:off"}},
"O5.38.08" => {attr => {subType => "gateway", comMode => "confirm", eep => "A5-38-08", gwCmd => "switching", manufID => "00D", model => "Eltako_FSR14", teachMethod => "confirm", webCmd => "on:off"}},
"G5.ZZ.ZZ" => {attr => {subType => "PM101", manufID => "005"}, GPLOT => "EnO_motion:Motion,EnO_brightness4:Brightness,"},
+ "G6.02.01" => {attr => {subType => "switch", eep => "F6-02-01", manufID => "00D", model => "Eltako_F4CT55", sensorMode => 'pushbutton'}},
"L6.02.01" => {attr => {subType => "smokeDetector.02", eep => "F6-05-02", manufID => "00D"}},
"ZZ.13.03" => {attr => {subType => "environmentApp", eep => "A5-13-03", devMode => "master", manufID => "7FF"}},
"ZZ.13.04" => {attr => {subType => "environmentApp", eep => "A5-13-04", devMode => "master", manufID => "7FF"}},
@@ -490,6 +492,7 @@ my %EnO_models = (
"Eltako_FBHF65SB" => {attr => {manufID => "00D"}},
"Eltako_FHK14" => {attr => {manufID => "00D"}},
"Eltako_FHK61" => {attr => {manufID => "00D"}},
+ "Eltako_FRM60" => {attr => {manufID => "00D"}},
"Eltako_FSA12" => {attr => {manufID => "00D"}},
"Eltako_FSB14" => {attr => {manufID => "00D"}},
"Eltako_FSB61" => {attr => {manufID => "00D"}},
@@ -500,16 +503,22 @@ my %EnO_models = (
"Eltako_FSM61" => {attr => {manufID => "00D"}},
"Eltako_FT55" => {attr => {manufID => "00D"}},
"Eltako_FTS12" => {attr => {manufID => "00D"}},
- "Eltako_TF"=> {attr => {manufID => "00D"}},
- "Eltako_TF_RWB"=> {attr => {manufID => "00D"}},
"Eltako_FUD14" => {attr => {manufID => "00D"}},
"Eltako_FUD61" => {attr => {manufID => "00D"}},
+ "Eltako_F4CT55"=> {attr => {manufID => "00D"}},
+ "Eltako_TF"=> {attr => {manufID => "00D"}},
+ "Eltako_TF_RWB"=> {attr => {manufID => "00D"}},
"Holter_OEM" => {attr => {pidCtrl => "off"}},
"Micropelt_MVA004" => {attr => {remoteCode => "FFFFFFFE", remoteEEP => "A5-20-01", remoteID => "getNextID", remoteManagement => "manager"}, xml => {productID => "0x004900000000", xmlDescrLocation => "/FHEM/lib/EnO_ReCom_Device_Descr.xml"}},
other => {},
tracker => {}
);
+my %EnO_mscRefID = (
+ "0000045C" => {model => "Eltako_F4CT55", version => "0000", attr => {eep => "G6.02.01"}},
+ "0000043E" => {model => "Eltako_FRM60", teachIn => "FFF80D80", version => "0000", attr => {eep => "I5.3F.7F"}}
+);
+
my @EnO_defaultChannel = ("all", "input", 0..29);
my %wakeUpCycle = (
@@ -822,7 +831,7 @@ sub EnOcean_Initialize($) {
"pollInterval postmasterID productID rampTime rcvRespAction:textField-long ".
"releasedChannel:A,B,C,D,I,0,auto repeatingAllowed:yes,no remoteCode remoteEEP remoteID remoteManufID " .
"remoteManagement:client,manager,off rlcAlgo:no,2++,3++,4++ rlcRcv rlcSnd rlcTX:true,false " .
- "reposition:directly,opens,closes rltRepeat:16,32,64,128,256 rltType:1BS,4BS " .
+ "reposition:directly,opens,closes rltRepeat:16,32,64,128,256 rltType:1BS,4BS rotationSpeed:select,high,low " .
"scaleDecimals:0,1,2,3,4,5,6,7,8,9 scaleMax scaleMin secMode:rcv,snd,bidir " .
"secLevel:encapsulation,encryption,off sendDevStatus:no,yes sendTimePeriodic sensorMode:switch,pushbutton " .
"serviceOn:no,yes setCmdTrigger:man,refDev setpointRefDev setpointSummerMode:slider,0,5,100 settingAccuracy:high,low " .
@@ -1039,7 +1048,7 @@ sub EnOcean_Define($$) {
my $packetType = hex $msg[1];
if ($packetType == 1) {
- my ($data, $rorg, $status);
+ my ($data, $rorg, $sourceID, $status);
#EnOcean:PacketType:RORG:MessageData:SourceID:Status:OptionalData
(undef, undef, $rorg, $data, undef, $status, undef) = @msg;
$attr{$name}{subType} = $EnO_rorgname{$rorg};
@@ -1089,8 +1098,41 @@ sub EnOcean_Define($$) {
readingsSingleUpdate($hash, "teach", "UTE teach-in is missing", 1);
Log3 $name, 2, "EnOcean $name UTE teach-in is missing";
} elsif ($attr{$name}{subType} eq "MSC") {
- readingsSingleUpdate($hash, "teach", "MSC not supported", 1);
- Log3 $name, 2, "EnOcean $name MSC not supported";
+ $attr{$name}{manufID} = substr($data, 0, 3);
+ if ($attr{$name}{manufID} eq '00D') {
+ if (substr($data, 4, 2) eq 'FF') {
+ # Eltako MSC teachIn
+ my $refID = substr($data, 6, 8);
+ if (exists $EnO_mscRefID{$refID}) {
+ $hash->{helper}{teachInWait} = "MSC" if (exists $EnO_mscRefID{$refID}{teachIn});
+ if (!exists $hash->{IODev}) {
+ (defined $ioDev) ? AssignIoPort($hash, $ioDev) : AssignIoPort($hash);
+ $attr{$name}{IODev} = $hash->{IODev}{NAME};
+ }
+ $hash->{DEF} = $def;
+ $modules{EnOcean}{defptr}{$def} = $hash;
+ $attr{$name}{modelVersion} = $EnO_mscRefID{$refID}{version};
+ $attr{$name}{mscRefID} = $refID;
+ $attr{$name}{room} = $autocreateDeviceRoom;
+ $attr{$name}{subDef} = EnOcean_CheckSenderID("getNextID", $hash->{IODev}{NAME}, "00000000") if (!exists $attr{$name}{subDef});
+ foreach my $attrCntr (keys %{$EnO_eepConfig{$EnO_mscRefID{$refID}{attr}{eep}}{attr}}) {
+ $attr{$name}{$attrCntr} = $EnO_eepConfig{$EnO_mscRefID{$refID}{attr}{eep}}{attr}{$attrCntr} if ($attrCntr ne "subDef");
+ }
+ EnOcean_CreateSVG(undef, $hash, $attr{$name}{eep});
+ readingsSingleUpdate($hash, "teach", "MSC teach-in EEP $attr{$name}{eep} requested Manufacturer " . $EnO_manuf{$attr{$name}{manufID}}, 1);
+ Log3 $name, 2, "EnOcean $name MSC teach-in EEP $attr{$name}{eep} requested Manufacturer " . $EnO_manuf{$attr{$name}{manufID}};
+ } else {
+ readingsSingleUpdate($hash, "teach", "MSC teach-in REF-ID $refID not supported Manufacturer " . $EnO_manuf{$attr{$name}{manufID}}, 1);
+ Log3 $name, 2, "EnOcean $name MSC teach-in REF-ID $refID not supported Manufacturer " . $EnO_manuf{$attr{$name}{manufID}};
+ }
+ } else {
+ readingsSingleUpdate($hash, "teach", "MSC teach-in is missing Manufacturer " . $EnO_manuf{$attr{$name}{manufID}}, 1);
+ Log3 $name, 2, "EnOcean $name MSC teach-in is missing Manufacturer " . $EnO_manuf{$attr{$name}{manufID}};
+ }
+ } else {
+ readingsSingleUpdate($hash, "teach", "MSC not supported", 1);
+ Log3 $name, 2, "EnOcean $name MSC not supported";
+ }
} elsif ($attr{$name}{subType} =~ m/^SEC|ENC$/) {
$hash->{helper}{teachInWait} = "STE";
readingsSingleUpdate($hash, "teach", "STE teach-in is missing", 1);
@@ -2294,135 +2336,167 @@ sub EnOcean_Set($@) {
return "Usage: $cmd arguments needed or wrong.";
}
} elsif ($st eq "switch") {
- # Rocker Switch, simulate a PTM200 switch module
- # separate first and second action
- ($cmd1, $cmd2) = split(",", $cmd, 2);
- # check values
- if (!defined($EnO_ptm200btn{$cmd1}) || ($cmd2 && !defined($EnO_ptm200btn{$cmd2}))) {
- $cmdList .= join(":noArg ", sort keys %EnO_ptm200btn) . ':noArg';
- return SetExtensions($hash, $cmdList, $name, @a);
- }
- my $channelA = ReadingsVal($name, "channelA", undef);
- my $channelB = ReadingsVal($name, "channelB", undef);
- my $channelC = ReadingsVal($name, "channelC", undef);
- my $channelD = ReadingsVal($name, "channelD", undef);
- my $lastChannel = ReadingsVal($name, ".lastChannel", "A");
- my $releasedChannel = AttrVal($name, "releasedChannel", "auto");
- my $subDefA = AttrVal($name, "subDefA", $subDef);
- my $subDefB = AttrVal($name, "subDefB", $subDef);
- my $subDefC = AttrVal($name, "subDefC", $subDef);
- my $subDefD = AttrVal($name, "subDefD", $subDef);
- my $subDefI = AttrVal($name, "subDefI", $subDef);
- my $subDef0 = AttrVal($name, "subDef0", $subDef);
- my $switchType = AttrVal($name, "switchType", "direction");
-
- # first action
- if ($cmd1 eq "released") {
- if ($switchType eq "central") {
- if ($releasedChannel eq "auto") {
- if ($lastChannel =~ m/0|I/) {
- $releasedChannel = $lastChannel;
+ if ($model eq "Eltako_F4CT55") {
+ my %cmdList = ("colourA0" => "08", "colourAI" => "09","colourB0" => "0A","colourBI" => "0B","colourAll" => "0C");
+ if (!exists $cmdList{$cmd}) {
+ $cmdList .= join(":colorpicker,RGB ", sort keys %cmdList) . ':colorpicker,RGB';
+ return "Unknown argument " . $cmd . ", choose one of " . $cmdList;
+ }
+ if (defined $a[1]) {
+ if ($a[1] =~ m/^[\dA-Fa-f]{6}$/) {
+ readingsBeginUpdate($hash);
+ if ($cmd eq "colourAll") {
+ while (my ($key, undef) = each(%cmdList)) {
+ readingsBulkUpdate($hash, $key, uc($a[1]));
+ }
} else {
+ readingsBulkUpdate($hash, $cmd, uc($a[1]));
+ #readingsDelete($hash, "colourAll");
+ }
+ #readingsBulkUpdate($hash, 'state', uc($a[1]));
+ readingsEndUpdate($hash, 1);
+ $data = uc($a[1]) . $cmdList{$cmd};
+ $rorg = "A5";
+ shift(@a);
+ $updateState = 0;
+ Log3 $name, 3, "EnOcean set $name $cmd";
+ } else {
+ return "Usage: $cmd value is not hexadecimal or out of range.";
+ }
+ } else {
+ return "Usage: $cmd values are missing";
+ }
+ } else {
+ # Rocker Switch, simulate a PTM200 switch module
+ # separate first and second action
+ ($cmd1, $cmd2) = split(",", $cmd, 2);
+ # check values
+ if (!defined($EnO_ptm200btn{$cmd1}) || ($cmd2 && !defined($EnO_ptm200btn{$cmd2}))) {
+ $cmdList .= join(":noArg ", sort keys %EnO_ptm200btn) . ':noArg';
+ return SetExtensions($hash, $cmdList, $name, @a);
+ }
+ my $channelA = ReadingsVal($name, "channelA", undef);
+ my $channelB = ReadingsVal($name, "channelB", undef);
+ my $channelC = ReadingsVal($name, "channelC", undef);
+ my $channelD = ReadingsVal($name, "channelD", undef);
+ my $lastChannel = ReadingsVal($name, ".lastChannel", "A");
+ my $releasedChannel = AttrVal($name, "releasedChannel", "auto");
+ my $subDefA = AttrVal($name, "subDefA", $subDef);
+ my $subDefB = AttrVal($name, "subDefB", $subDef);
+ my $subDefC = AttrVal($name, "subDefC", $subDef);
+ my $subDefD = AttrVal($name, "subDefD", $subDef);
+ my $subDefI = AttrVal($name, "subDefI", $subDef);
+ my $subDef0 = AttrVal($name, "subDef0", $subDef);
+ my $switchType = AttrVal($name, "switchType", "direction");
+
+ # first action
+ if ($cmd1 eq "released") {
+ if ($switchType eq "central") {
+ if ($releasedChannel eq "auto") {
+ if ($lastChannel =~ m/0|I/) {
+ $releasedChannel = $lastChannel;
+ } else {
+ $releasedChannel = 0;
+ }
+ } elsif ($releasedChannel !~ m/0|I/) {
$releasedChannel = 0;
}
- } elsif ($releasedChannel !~ m/0|I/) {
- $releasedChannel = 0;
- }
- } elsif ($switchType eq "channel") {
- if ($releasedChannel eq "auto") {
- if ($lastChannel =~ m/A|B|C|D/) {
- $releasedChannel = $lastChannel;
- } else {
+ } elsif ($switchType eq "channel") {
+ if ($releasedChannel eq "auto") {
+ if ($lastChannel =~ m/A|B|C|D/) {
+ $releasedChannel = $lastChannel;
+ } else {
+ $releasedChannel = "A";
+ }
+ } elsif ($releasedChannel !~ m/A|B|C|D/) {
$releasedChannel = "A";
}
- } elsif ($releasedChannel !~ m/A|B|C|D/) {
- $releasedChannel = "A";
+ }
+ $subDef = AttrVal($name, "subDef" . $releasedChannel, $subDef);
+ } elsif ($switchType eq "central") {
+ if ($cmd1 =~ m/.0/) {
+ $subDef = $subDef0;
+ $lastChannel = 0;
+ } elsif ($cmd1 =~ m/.I/) {
+ $subDef = $subDefI;
+ $lastChannel = "I";
+ }
+ } elsif ($switchType eq "channel") {
+ $lastChannel = substr($cmd1, 0, 1);
+ $subDef = AttrVal($name, "subDef" . $lastChannel, $subDefA);
+ } else {
+ $lastChannel = substr($cmd1, 0, 1);
+ }
+
+ if ($switchType eq "universal") {
+ if ($cmd1 =~ m/A./ && (!defined($channelA) || $cmd1 ne $channelA)) {
+ $cmd1 = "A0";
+ } elsif ($cmd1 =~ m/B./ && (!defined($channelB) || $cmd1 ne $channelB)) {
+ $cmd1 = "B0";
+ } elsif ($cmd1 =~ m/C./ && (!defined($channelC) || $cmd1 ne $channelC)) {
+ $cmd1 = "C0";
+ } elsif ($cmd1 =~ m/D./ && (!defined($channelD) || $cmd1 ne $channelD)) {
+ $cmd1 = "D0";
+ } elsif ($cmd1 eq "released") {
+
+ } else {
+ $sendCmd = undef;
}
}
- $subDef = AttrVal($name, "subDef" . $releasedChannel, $subDef);
- } elsif ($switchType eq "central") {
- if ($cmd1 =~ m/.0/) {
- $subDef = $subDef0;
- $lastChannel = 0;
- } elsif ($cmd1 =~ m/.I/) {
- $subDef = $subDefI;
- $lastChannel = "I";
+ # second action
+ if ($cmd2 && $switchType eq "universal") {
+ if ($cmd2 =~ m/A./ && (!defined($channelA) || $cmd2 ne $channelA)) {
+ $cmd2 = "A0";
+ } elsif ($cmd2 =~ m/B./ && (!defined($channelB) || $cmd2 ne $channelB)) {
+ $cmd2 = "B0";
+ } elsif ($cmd2 =~ m/C./ && (!defined($channelC) || $cmd2 ne $channelC)) {
+ $cmd2 = "C0";
+ } elsif ($cmd2 =~ m/D./ && (!defined($channelD) || $cmd2 ne $channelD)) {
+ $cmd2 = "D0";
+ } else {
+ $cmd2 = undef;
+ }
+ if ($cmd2 && undef($sendCmd)) {
+ # only second action has changed, send as first action
+ $cmd1 = $cmd2;
+ $cmd2 = undef;
+ $sendCmd = 1;
+ }
}
- } elsif ($switchType eq "channel") {
- $lastChannel = substr($cmd1, 0, 1);
- $subDef = AttrVal($name, "subDef" . $lastChannel, $subDefA);
- } else {
- $lastChannel = substr($cmd1, 0, 1);
- }
-
- if ($switchType eq "universal") {
- if ($cmd1 =~ m/A./ && (!defined($channelA) || $cmd1 ne $channelA)) {
- $cmd1 = "A0";
- } elsif ($cmd1 =~ m/B./ && (!defined($channelB) || $cmd1 ne $channelB)) {
- $cmd1 = "B0";
- } elsif ($cmd1 =~ m/C./ && (!defined($channelC) || $cmd1 ne $channelC)) {
- $cmd1 = "C0";
- } elsif ($cmd1 =~ m/D./ && (!defined($channelD) || $cmd1 ne $channelD)) {
- $cmd1 = "D0";
- } elsif ($cmd1 eq "released") {
-
- } else {
- $sendCmd = undef;
+ # convert and send first and second command
+ my $switchCmd;
+ ($switchCmd, $status) = split(':', $EnO_ptm200btn{$cmd1}, 2);
+ # reset T21 status flag if 4 rocker
+ $status = '10' if ($switchCmd > 3);
+ $switchCmd <<= 5;
+ if ($cmd1 ne "released") {
+ # set the pressed flag
+ $switchCmd |= 0x10 ;
}
- }
- # second action
- if ($cmd2 && $switchType eq "universal") {
- if ($cmd2 =~ m/A./ && (!defined($channelA) || $cmd2 ne $channelA)) {
- $cmd2 = "A0";
- } elsif ($cmd2 =~ m/B./ && (!defined($channelB) || $cmd2 ne $channelB)) {
- $cmd2 = "B0";
- } elsif ($cmd2 =~ m/C./ && (!defined($channelC) || $cmd2 ne $channelC)) {
- $cmd2 = "C0";
- } elsif ($cmd2 =~ m/D./ && (!defined($channelD) || $cmd2 ne $channelD)) {
- $cmd2 = "D0";
- } else {
- $cmd2 = undef;
+ if($cmd2) {
+ # execute second action
+ if ($switchType =~ m/^central|channel$/) {
+ # second action not supported
+ $cmd = $cmd1;
+ } else {
+ my ($d2, undef) = split(':', $EnO_ptm200btn{$cmd2}, 2);
+ # reset T21 status flag if 4 rocker
+ $status = '10' if ($d2 > 3);
+ $switchCmd |= ($d2 << 1) | 0x01;
+ }
}
- if ($cmd2 && undef($sendCmd)) {
- # only second action has changed, send as first action
- $cmd1 = $cmd2;
- $cmd2 = undef;
- $sendCmd = 1;
+ if (defined $sendCmd) {
+ $data = sprintf "%02X", $switchCmd;
+ $rorg = "F6";
+ SetExtensionsCancel($hash);
+ Log3 $name, 3, "EnOcean set $name $cmd";
+ if ($updateState) {
+ readingsSingleUpdate($hash, "channel" . $1, $cmd1, 1) if ($cmd1 =~ m/^([A-D])./);
+ readingsSingleUpdate($hash, "channel" . $1, $cmd2, 1) if ($cmd2 && $cmd2 =~ m/^([A-D])./);
+ }
+ readingsSingleUpdate($hash, ".lastChannel", $lastChannel, 0);
}
}
- # convert and send first and second command
- my $switchCmd;
- ($switchCmd, $status) = split(':', $EnO_ptm200btn{$cmd1}, 2);
- # reset T21 status flag if 4 rocker
- $status = '10' if ($switchCmd > 3);
- $switchCmd <<= 5;
- if ($cmd1 ne "released") {
- # set the pressed flag
- $switchCmd |= 0x10 ;
- }
- if($cmd2) {
- # execute second action
- if ($switchType =~ m/^central|channel$/) {
- # second action not supported
- $cmd = $cmd1;
- } else {
- my ($d2, undef) = split(':', $EnO_ptm200btn{$cmd2}, 2);
- # reset T21 status flag if 4 rocker
- $status = '10' if ($d2 > 3);
- $switchCmd |= ($d2 << 1) | 0x01;
- }
- }
- if (defined $sendCmd) {
- $data = sprintf "%02X", $switchCmd;
- $rorg = "F6";
- SetExtensionsCancel($hash);
- Log3 $name, 3, "EnOcean set $name $cmd";
- if ($updateState) {
- readingsSingleUpdate($hash, "channel" . $1, $cmd1, 1) if ($cmd1 =~ m/^([A-D])./);
- readingsSingleUpdate($hash, "channel" . $1, $cmd2, 1) if ($cmd2 && $cmd2 =~ m/^([A-D])./);
- }
- readingsSingleUpdate($hash, ".lastChannel", $lastChannel, 0);
- }
} elsif ($st eq "switch.00") {
my $switchCmd = join(",", sort split(",", $cmd, 2));
@@ -4435,6 +4509,68 @@ sub EnOcean_Set($@) {
} elsif ($st eq "manufProfile") {
if ($manufID eq "00D") {
+ if ($model eq "Eltako_FRM60") {
+ my $position = 0;
+ my $setCmd = ReadingsVal($name, 'block', 'unlook') eq 'look' ? 0x0C : 8;
+ my $shutCmd = AttrVal($name, 'rotationSpeed', 'high') eq 'low' ? 0x13 : 3;
+ $rorg = "A5";
+ if ($cmd =~ m/^\d+$/) {
+ # interpretive numeric value as position
+ unshift(@a, 'position');
+ $cmd = 'position';
+ }
+ if ($cmd eq "teach") {
+ # teach-in EEP A5-3F-7F, Manufacturer "Eltako"
+ $data = "FFF80D80";
+ $attr{$name}{eep} = "A5-3F-7F";
+ CommandDeleteReading(undef, "$name .*");
+ readingsSingleUpdate($hash, "teach", "4BS teach-in sent", 1);
+ $shutCmd = 0;
+ $updateState = 0;
+ ($err, $subDef) = EnOcean_AssignSenderID(undef, $hash, "subDef", "confirm");
+ EnOcean_setTeachConfirmWaitHash(undef, $hash);
+ } elsif ($cmd eq "closes") {
+ $position = 0;
+ } elsif ($cmd eq "opens") {
+ $position = 100;
+ } elsif ($cmd eq "position") {
+ # closed: 100, open: 0
+ if (defined $a[1] && $a[1] =~ m/^\d+$/ && $a[1] <= 100) {
+ $position = $a[1] * 2;
+ shift (@a);
+ if (defined $a[1]) {
+ if ($a[1] =~ m/^high|low$/) {
+ $shutCmd = $a[1] eq 'low' ? 0x13 : 3;
+ shift (@a);
+ if (defined $a[1]) {
+ if ($a[1] =~ m/^lock|unlock$/) {
+ $setCmd = $a[1] eq 'lock' ? 0x0C : 8;
+ shift (@a);
+ } else {
+ return "Usage: $a[1] is wrong";
+ }
+ }
+ } else {
+ return "Usage: $a[1] is wrong";
+ }
+ }
+ } else {
+ return "Usage: $a[1] is not numeric or out of range";
+ }
+ } elsif ($cmd eq "stop") {
+ # stop
+ $shutCmd = 0;
+ } else {
+ return "Unknown argument " . $cmd . ", choose one of " . $cmdList . "closes:noArg opens:noArg position:slider,0,5,100 stop:noArg teach:noArg"
+ }
+ if ($shutCmd || $cmd eq "stop") {
+ $updateState = 0;
+ # invert position
+ $data = sprintf "%02X00%02X%02X", 200 - $position, $shutCmd, $setCmd;
+ }
+ Log3 $name, 3, "EnOcean set $name $cmd";
+
+ } else {
# Eltako Shutter
my $angleMax = AttrVal($name, "angleMax", 90);
my $angleMin = AttrVal($name, "angleMin", -90);
@@ -4754,6 +4890,7 @@ sub EnOcean_Set($@) {
}
Log3 $name, 3, "EnOcean set $name $cmd";
}
+ }
} elsif ($st eq "actuator.01") {
# Electronic switches and dimmers with Energy Measurement and Local Control
@@ -6186,7 +6323,7 @@ sub EnOcean_Set($@) {
$window = $window eq 'open' ? 0x10 : 0;
$setpointShift = int(($setpointShift + $setpointShiftMax) * 255 / ($setpointShiftMax * 2));
#$setpointShift = unpack('C', pack('c', $setpointShift));
- my %fanSpeed = ('auto' => 0, 'off' =>1, 1 => 2, 2 => 3, 3 => 4);
+ my %fanSpeed = ('auto' => 0, 'off' => 1, 1 => 2, 2 => 3, 3 => 4);
$occupancy = $occupancy eq 'occupied' ? 1 : 0;
$data = sprintf "%02X%02X%02X%02X", $setpointType | $heating | $cooling | $window | 1,
$setpointShift, $setpointBase,
@@ -7388,9 +7525,17 @@ sub EnOcean_Parse($$) {
Log3 $name, 2, "EnOcean $name remote device with SenderID $senderID assigned";
return '';
- } elsif ($learningDev eq 'teachMsg' && ($rorgname =~ m/^VLD|MSC|SEC|ENC$/ || $rorgname eq '4BS' && (hex(substr($data, 6, 2))) & 8)) {
- Log3 undef, 4, "EnOcean Received $rorgname telegram to the unknown device with SenderID $senderID.";
- return '';
+ } elsif ($learningDev eq 'teachMsg' && ($rorgname =~ m/^VLD|SEC|ENC$/ || $rorgname eq '4BS' && (hex(substr($data, 6, 2))) & 8)) {
+ Log3 undef, 4, "EnOcean Unknown device with SenderID $senderID and $rorgname telegram.";
+ return '';
+ } elsif ($learningDev eq 'teachMsg' && $rorgname eq "MSC") {
+ if ($teach && substr($data, 0, 3) eq '00D' && substr($data, 4, 2) eq 'FF') {
+ Log3 undef, 1, "EnOcean Unknown device with SenderID $senderID and $rorgname telegram, please define it.";
+ return $ret;
+ } else {
+ Log3 undef, 4, "EnOcean Unknown device with SenderID $senderID and $rorgname telegram.";
+ return '';
+ }
} elsif ($rorg eq 'A5' &&
hex(substr($data, 0, 2)) >> 2 == 0x3F &&
@@ -8148,6 +8293,17 @@ sub EnOcean_Parse($$) {
return "";
}
+ } elsif (exists($hash->{helper}{teachInWait}) && $hash->{helper}{teachInWait} eq 'MSC') {
+ # Eltako MSC teach-in response
+ my $ownSenderID = defined($attr{$name}{subDef}) ? $attr{$name}{subDef} : $hash->{DEF};
+ if (substr($data, 0, 6) ne substr($ownSenderID, 2, 6)) {
+ # wrong response, device rejected, clear teach-in request
+ delete $hash->{helper}{teachInWait};
+ readingsSingleUpdate($hash, 'teach', '4BS teach-in response wrong, device rejected', 1);
+ Log3 $name, 2, "EnOcean $name 4BS teach-in rejected by SenderID $senderID";
+ }
+ return '';
+
} elsif ($st eq "hvac.01" || $st eq "MD15") {
# Battery Powered Actuator (EEP A5-20-01)
# [Kieback&Peter MD15-FTL-xx]
@@ -9404,7 +9560,6 @@ sub EnOcean_Parse($$) {
} elsif ($st eq "particlesSensor.01") {
# Gas Sensor, Particles Sensor (EEP A5-09-07)
- # [untested]
# $db[3]_bit_7 ... $db[2]_bit_7 is the particle concentration < 10 µm
# where 0 = 0 µg/m3 ... 511 = 511 µg/m3
# $db[2]_bit_6 ... $db[1]_bit_6 is the particle concentration < 2.5 µm
@@ -9418,7 +9573,7 @@ sub EnOcean_Parse($$) {
my $pm_2_5 = "inactive";
my $pm_1 = "inactive";
if ($db[0] & 4) {$pm_10 = $db[3] << 1 | $db[2] >> 7;}
- if ($db[0] & 2) {$pm_2_5 = ($db[2] & 0x7F) << 1 | $db[1] >> 7;}
+ if ($db[0] & 2) {$pm_2_5 = ($db[2] & 0x7F) << 2 | $db[1] >> 6;}
if ($db[0] & 1) {$pm_1 = ($db[1] & 0x3F) << 3 | $db[0] >> 5;}
push @event, "3:particles_10:$pm_10";
push @event, "3:particles_2_5:$pm_2_5";
@@ -10999,88 +11154,104 @@ sub EnOcean_Parse($$) {
}
} elsif ($manufID eq "00D") {
+ if ($model eq "Eltako_FRM60") {
+ my ($position, $state);
+ # invert position
+ $position = 200 - $db[3];
+ $position = $db[3] == 1 ? 1 : int($db[3] / 2);
+ if ($position == 100) {
+ push @event, "3:endPosition:closed";
+ $state = "closed";
+ } elsif ($position == 0) {
+ push @event, "3:endPosition:open";
+ $state = "open";
+ } else {
+ push @event, "3:endPosition:not_reached";
+ $state = $position;
+ }
+ push @event, "1:block:" . ($db[0] & 4 ? 'lock' : 'unlock');
+ push @event, "3:position:$position";
+ push @event, "3:state:$state";
+ } else {
# [Eltako shutter]
- 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;
- $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 = 0 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);
+ 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;
+ $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 = 0 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) {
+ push @event, "1:block:" . ($db[0] & 4 ? 'lock' : 'unlock');
+ 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;
}
- } 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);
- push @event, "3:.anglePosStart:" . sprintf("%d", $anglePos);
- push @event, "3:.positionStart:" . sprintf("%d", $position);
- } elsif ($db[1] == 2) {
- # down
- $position += $shutTimeStop / $shutTime * 100;
- if ($angleTime) {
- $anglePos += ($angleMax - $angleMin) * $shutTimeStop / $angleTime;
- if ($anglePos > $angleMax) {
+ 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);
+ push @event, "3:.anglePosStart:" . sprintf("%d", $anglePos);
+ push @event, "3:.positionStart:" . 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);
+ push @event, "3:.anglePosStart:" . sprintf("%d", $anglePos);
+ push @event, "3:.positionStart:" . sprintf("%d", $position);
} else {
- $anglePos = $angleMax;
+ $state = "not_reached";
}
- 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);
- push @event, "3:.anglePosStart:" . sprintf("%d", $anglePos);
- push @event, "3:.positionStart:" . sprintf("%d", $position);
- } else {
- $state = "not_reached";
+ push @event, "3:state:$state";
}
- push @event, "3:state:$state";
}
} else {
@@ -12714,6 +12885,32 @@ sub EnOcean_Parse($$) {
push @event, "3:taughtInDevID" . sprintf('%02d', $db[4]) . ":" . substr($data, 8, 8);
}
}
+ } elsif ($manufID eq "00D") {
+ if (substr($data, 4, 2) eq 'FE') {
+ # teachin states
+ if (substr($data, 6, 6) eq '030301') {
+ # teachin started
+ if (exists($hash->{helper}{teachInWait}) && $hash->{helper}{teachInWait} eq "MSC" &&
+ exists($attr{$name}{mscRefID}) &&
+ exists($EnO_mscRefID{$attr{$name}{mscRefID}})) {
+ EnOcean_SndRadio(undef, $hash, $packetType, "A5", $EnO_mscRefID{$attr{$name}{mscRefID}}{teachIn}, AttrVal($name, "subDef", "00000000"), "00", $hash->{DEF});
+ push @event, "3:teach:4BS teach-in response sent";
+ Log3 $name, 2, "EnOcean $name 4BS teach-in response sent to " . $hash->{DEF};
+ } else {
+ delete $hash->{helper}{teachInWait};
+ }
+ } elsif (substr($data, 6, 6) eq '030300') {
+ delete $hash->{helper}{teachInWait};
+ } elsif (substr($data, 6, 6) eq '030400') {
+ delete $hash->{helper}{teachInWait};
+ push @event, "3:teach:4BS teach-in accepted EEP: $attr{$name}{eep} Manufacturer: $EnO_manuf{$manufID}";
+ Log3 $name, 2, "EnOcean $name 4BS teach-in accepted EEP: $attr{$name}{eep} Manufacturer: $EnO_manuf{$manufID}";
+ } elsif (substr($data, 6, 6) eq 'FC0100') {
+ delete $hash->{helper}{teachInWait};
+ push @event, "3:teach:4BS teach-in not accepted by $hash->{DEF}";
+ Log3 $name, 2, "EnOcean $name 4BS teach-in not accepted by $hash->{DEF}";
+ }
+ }
} elsif ($st eq "raw") {
# raw
@@ -12736,7 +12933,7 @@ sub EnOcean_Parse($$) {
if ($signalMID == 1) {
push @event, "3:smartAckMailbox:empty";
} elsif ($signalMID == 2) {
- push @event, "3:smartAckMailbox:not_exits";
+ push @event, "3:smartAckMailbox:not_exists";
} elsif ($signalMID == 3) {
push @event, "3:smartAckMailbox:reset";
} elsif ($signalMID == 4) {
@@ -14501,6 +14698,13 @@ sub EnOcean_Attr(@) {
$err = "attribute-value [$attrName] = $attrVal wrong";
}
+ } elsif ($attrName eq "rotationSpeed") {
+ if (!defined $attrVal) {
+
+ } elsif ($attrVal !~ m/^high|low$/) {
+ $err = "attribute-value [$attrName] = $attrVal is not valid";
+ }
+
} elsif ($attrName eq "secLevel") {
if (!defined $attrVal){
@@ -18478,7 +18682,9 @@ sub EnOcean_Delete($$) {
these devices to the correct profile.
4BS devices can also be taught in special cases by using of confirmation telegrams. This method
- is used for the EnOcean Tipp-Funk devices. The function is activated via the attribute [teachMethod] = confirm.
+ is used for the EnOcean Tipp-Funk devices. The function is activated via the attribute
+ [teachMethod] = confirm. Some Eltako devices are fully preconfigured via
+ Inofficial EEP for this procedure.
For example the remote device Eltako TF100D can be learned as follows
define <name> EnOcean H5-38-08set <IODev> teach <t/s>
+ set <name> <value>
+ value is
+ set <name> <value>
set <name> <value>
+ value is
+