############################################## # $Id$ package main; use strict; use warnings; use SetExtensions; sub EnOcean_Define($$); sub EnOcean_Initialize($); sub EnOcean_Parse($$); sub EnOcean_Set($@); sub EnOcean_MD15Cmd($$$); my %EnO_rorgname = ("F6"=>"switch", # org 05, RPS "D5"=>"contact", # org 06, 1BS "A5"=>"sensor", # org 07, 4BS ); my @EnO_ptm200btn = ("AI", "A0", "BI", "B0", "CI", "C0", "DI", "D0"); my %EnO_ptm200btn; # Peha House Control System (PHC) # PHC Gateway Commands my @EnO_phcCmd = ("switching", "dimming", "setpointShift", "setpointBasic", "controlVar", "fanStage"); # Some Manufacturers (e.g. Jaeger Direkt) also sell EnOcean products without an # entry in the table below. This table is only needed for A5 category devices. my %EnO_manuf = ( "001" => "Peha", "002" => "Thermokon", "003" => "Servodan", "004" => "EchoFlex Solutions", "005" => "Omnio AG", "006" => "Hardmeier electronics", "007" => "Regulvar Inc", "008" => "Ad Hoc Electronics", "009" => "Distech Controls", "00A" => "Kieback + Peter", "00B" => "EnOcean GmbH", "00C" => "Probare", "00D" => "Eltako", "00E" => "Leviton", "00F" => "Honeywell", "010" => "Spartan Peripheral Devices", "011" => "Siemens", "012" => "T-Mac", "013" => "Reliable Controls Corporation", "014" => "Elsner Elektronik GmbH", "015" => "Diehl Controls", "016" => "BSC Computer", "017" => "S+S Regeltechnik GmbH", "018" => "Masco Corporation", "019" => "Intesis Software SL", "01A" => "Res.", "01B" => "Lutuo Technology", "01C" => "CAN2GO", "7FF" => "Multi user Manufacturer ID", ); my %EnO_subType = ( "A5.02.01" => "tempSensor.01", "A5.02.02" => "tempSensor.02", "A5.02.03" => "tempSensor.03", "A5.02.04" => "tempSensor.04", "A5.02.05" => "tempSensor.05", "A5.02.06" => "tempSensor.06", "A5.02.07" => "tempSensor.07", "A5.02.08" => "tempSensor.08", "A5.02.09" => "tempSensor.09", "A5.02.0A" => "tempSensor.0A", "A5.02.0B" => "tempSensor.0B", "A5.02.10" => "tempSensor.10", "A5.02.11" => "tempSensor.11", "A5.02.12" => "tempSensor.12", "A5.02.13" => "tempSensor.13", "A5.02.14" => "tempSensor.14", "A5.02.15" => "tempSensor.15", "A5.02.16" => "tempSensor.16", "A5.02.17" => "tempSensor.17", "A5.02.18" => "tempSensor.18", "A5.02.19" => "tempSensor.19", "A5.02.1A" => "tempSensor.1A", "A5.02.1B" => "tempSensor.1B", "A5.02.20" => "tempSensor.20", "A5.02.30" => "tempSensor.30", "A5.04.01" => "roomSensorControl.01", "A5.04.02" => "tempHumiSensor.02", "A5.06.01" => "lightSensor.01", "A5.06.02" => "lightSensor.02", "A5.07.01" => "occupSensor.01", "A5.08.01" => "lightTempOccupSensor.01", "A5.08.02" => "lightTempOccupSensor.02", "A5.08.03" => "lightTempOccupSensor.03", "A5.09.01" => "COSensor.01", "A5.09.04" => "tempHumiCO2Sensor.01", "A5.10.01" => "roomSensorControl.05", "A5.10.02" => "roomSensorControl.05", "A5.10.03" => "roomSensorControl.05", "A5.10.04" => "roomSensorControl.05", "A5.10.05" => "roomSensorControl.05", "A5.10.06" => "roomSensorControl.05", "A5.10.07" => "roomSensorControl.05", "A5.10.08" => "roomSensorControl.05", "A5.10.09" => "roomSensorControl.05", "A5.10.0A" => "roomSensorControl.05", "A5.10.0B" => "roomSensorControl.05", "A5.10.0C" => "roomSensorControl.05", "A5.10.0D" => "roomSensorControl.05", "A5.10.10" => "roomSensorControl.01", "A5.10.11" => "roomSensorControl.01", "A5.10.12" => "roomSensorControl.01", "A5.10.13" => "roomSensorControl.01", "A5.10.14" => "roomSensorControl.01", "A5.10.15" => "roomSensorControl.02", "A5.10.16" => "roomSensorControl.02", "A5.10.17" => "roomSensorControl.02", "A5.12.00" => "autoMeterReading.00", "A5.12.01" => "autoMeterReading.01", "A5.12.02" => "autoMeterReading.02", "A5.12.03" => "autoMeterReading.03", "A5.13.01" => "weatherStation", "A5.13.02" => "weatherStation", "A5.13.03" => "weatherStation", "A5.13.04" => "weatherStation", "A5.13.05" => "weatherStation", "A5.13.06" => "weatherStation", "A5.20.01" => "MD15", "A5.30.01" => "digitalInput.01", "A5.30.02" => "digitalInput.02", "A5.38.08" => "phcGateway", "A5.3F.7F" => "manufProfile", 1 => "switch", 2 => "contact", 3 => "sensor", 4 => "windowHandle", 5 => "eltakoDimmer", 6 => "eltakoShutter", 7 => "FAH", 8 => "FBH", 9 => "FTF", 10 => "SR04", 11 => "FRW", 12 => "keycard", ); my @EnO_models = qw ( other MD15-FtL-HE SR04 SR04P SR04T SR04PT SR04PMS SR04PS SR04PST FT55 FAH60 FAH63 FIH63 FABH63 FBH63 FIBH63 FAFT60 FIFT63AP FMS14 FMS61 FSB12 FSB14 FSB61 FSB70 FSG70 FSM12 FSM61 FSR14 FSR61 FTF55 FTN14 FTS12 FUD12 FUD14 FUD61 FUD70 PM101 ); # Initialize sub EnOcean_Initialize($) { my ($hash) = @_; $hash->{Match} = "^EnOcean:"; $hash->{DefFn} = "EnOcean_Define"; $hash->{UndefFn} = "EnOcean_Undef"; $hash->{ParseFn} = "EnOcean_Parse"; $hash->{SetFn} = "EnOcean_Set"; $hash->{AttrList} = "IODev do_not_notify:1,0 ignore:0,1 dummy:0,1 " . "showtime:1,0 loglevel:0,1,2,3,4,5,6 ". "model:".join(",",@EnO_models)." ". "subType:".join(",",values %EnO_subType)." ". "actualTemp dimTime dimValueOn manufID phcCmd ". "rampTime shutTime subDef subDef0 subDefI ". "switchMode switchType ". $readingFnAttributes; for(my $i=0; $i<@EnO_ptm200btn;$i++) { $EnO_ptm200btn{$EnO_ptm200btn[$i]} = "$i:30"; } $EnO_ptm200btn{released} = "0:20"; return undef; } # Define sub EnOcean_Define($$) { my ($hash, $def) = @_; 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); $modules{EnOcean}{defptr}{uc($a[2])} = $hash; AssignIoPort($hash); # Help FHEMWEB split up devices $attr{$name}{subType} = $1 if($name =~ m/EnO_(.*)_$a[2]/); return undef; } # Set sub EnOcean_Set($@) { my ($hash, @a) = @_; return "no set value specified" if(@a < 2); my $updateState = 1; my $name = $hash->{NAME}; my $st = AttrVal($name, "subType", ""); my $manufID = AttrVal($name, "manufID", ""); my $model = AttrVal($name, "model", ""); my $ll2 = GetLogLevel($name, 2); my $sendCmd = "yes"; shift @a; my $tn = TimeNow(); for(my $i = 0; $i < @a; $i++) { my $cmd = $a[$i]; if($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 # Maintenance commands (runInit, liftSet, valveOpen, valveClosed) my %sets = ( "desired-temp" => "\\d+(\\.\\d)?", "actuator" => "\\d+", "unattended" => "", "initialize" => "", "runInit" => "", "liftSet" => "", "valveOpen" => "", "valveClosed" => "", ); my $re = $sets{$a[0]}; return "Unknown argument $cmd, choose one of ".join(" ", sort keys %sets) if(!defined($re)); return "Need a parameter" if ($re && @a < 2); return "Argument $a[1] is incorrect (expect $re)" if ($re && $a[1] !~ m/^$re$/); $hash->{CMD} = $cmd; $hash->{READINGS}{CMD}{TIME} = $tn; $hash->{READINGS}{CMD}{VAL} = $cmd; my $arg = "true"; if($re) { $arg = $a[1]; shift(@a); } $hash->{READINGS}{$cmd}{TIME} = $tn; $hash->{READINGS}{$cmd}{VAL} = $arg; } elsif($st eq "eltakoDimmer" && $model ne "FSG70") { # Dimmer my $sendDimCmd=0; my $dimTime=AttrVal($name, "dimTime", 1); my $onoff=1; my $subDef = AttrVal($name, "subDef", "$hash->{DEF}"); my $dimVal=$hash->{READINGS}{dimValue}{VAL}; if($cmd eq "teach") { my $data=sprintf("A502000000%s00", $subDef); Log $ll2, "EnOcean: set $name $cmd"; # len:000a optlen:00 pakettype:1(radio) IOWrite($hash, "000A0001", $data); } elsif($cmd eq "dim") { return "Usage: $cmd percent [dimspeed 1-100]" if(@a<2 or $a[1]>100); # for eltako relative (0-100) (but not compliant to EEP because DB0.2 # is 0) $dimVal=$a[1]; readingsSingleUpdate($hash,"dimValueStored",$dimVal,1); shift(@a); if(defined($a[1])) { $dimTime=sprintf("%X",(($a[1]*2.55)-255)*-1); shift(@a); } $sendDimCmd=1; } elsif($cmd eq "dimup") { return "Usage: $cmd percent [dimspeed 1-100]" if(@a<2 or $a[1]>100); $dimVal+=$a[1]; readingsSingleUpdate($hash,"dimValueStored",$dimVal,1); shift(@a); if(defined($a[1])) { $dimTime=sprintf("%X",(($a[1]*2.55)-255)*-1); shift(@a); } $sendDimCmd=1; } elsif($cmd eq "dimdown") { return "Usage: $cmd percent [dimspeed 1-100]" if(@a<2 or $a[1]>100); $dimVal-=$a[1]; readingsSingleUpdate($hash,"dimValueStored",$dimVal,1); shift(@a); if(defined($a[1])) { $dimTime=sprintf("%X",(($a[1]*2.55)-255)*-1); shift(@a); } $sendDimCmd=1; } elsif($cmd eq "on" || $cmd eq "B0") { $dimTime=1; $sendDimCmd=1; my $dimValueOn = AttrVal($name, "dimValueOn", 100); if ($dimValueOn eq "stored") { $dimVal = ReadingsVal($name, "dimValueStored", 100); if ($dimVal < 1) { $dimVal = 100; readingsSingleUpdate($hash, "dimValueStored", $dimVal, 1); } } elsif ($dimValueOn eq "last") { $dimVal = ReadingsVal($name, "dimValueLast", 100); if ($dimVal < 1) { $dimVal = 100; } } else { $dimVal = $dimValueOn; if($dimValueOn > 100) { $dimVal = 100; } if($dimValueOn < 1) { $dimVal = 1; } } } elsif($cmd eq "off" || $cmd eq "BI") { $dimTime=1; $onoff=0; $sendDimCmd=1; $dimVal=0; } else { my $list = "dim:slider,0,1,100 dimup:slider,0,1,100 ". "dimdown:slider,0,1,100 on off teach"; return SetExtensions($hash, $list, $name, @a); } if($sendDimCmd) { $updateState = 0; $a[0]="on"; if($dimVal > 100) { $dimVal=100; } if($dimVal <= 0) { $dimVal=0; $onoff=0; $a[0]="off"; } my $data=sprintf("A502%02X%02X%02X%s00", $dimVal, $dimTime, $onoff|0x08, $subDef); IOWrite($hash, "000A0001", $data); Log $ll2, "EnOcean: set $name $cmd $dimVal"; } } elsif($st eq "eltakoShutter") { # Shutter my $shutTime=AttrVal($name, "shutTime", 0); my $subDef = AttrVal($name, "subDef", "$hash->{DEF}"); my $shutCmd = 0x00; if($cmd eq "teach") { my $data=sprintf("A5FFF80D80%s00", $subDef); Log $ll2, "EnOcean: set $name $cmd"; # len:000a optlen:00 pakettype:1(radio) IOWrite($hash, "000A0001", $data); } elsif($cmd eq "stop") { $shutCmd = 0x00; } elsif($cmd eq "up" || $cmd eq "B0") { my $position = 100; if($a[1]) { $shutTime = $shutTime/100*$a[1]; $position = $hash->{READINGS}{position}{VAL}+$a[1]; if($position > 100) { $position = 100; }; } $hash->{READINGS}{position}{TIME} = $tn; $hash->{READINGS}{position}{VAL} = $position; $shutCmd = 0x01; } elsif($cmd eq "down" || $cmd eq "BI") { my $position = 0; if($a[1]) { $shutTime = $shutTime/100*$a[1]; $position = $hash->{READINGS}{position}{VAL}-$a[1]; if($position <= 0) { $position = 0; }; } $hash->{READINGS}{position}{TIME} = $tn; $hash->{READINGS}{position}{VAL} = $position; $shutCmd = 0x02; } else { return "Unknown argument " . $cmd . ", choose one of up down stop teach" } shift(@a); if($shutCmd || ($cmd eq "stop")) { $updateState = 0; my $data = sprintf("A5%02X%02X%02X%02X%s00", 0x00, $shutTime, $shutCmd, 0x08, $subDef); IOWrite($hash, "000A0001", $data); Log $ll2, "EnOcean: set $name $cmd"; } } elsif ($st eq "phcGateway") { # PHC Gateway (EEP A5-38-08) my $data; my $phcCmd = AttrVal($name, "phcCmd", ""); my $phcCmdID; my $setCmd = 0; my $subDef = AttrVal($name, "subDef", "$hash->{DEF}"); my $time = 0; if ($phcCmd eq "switching") { # Switching $phcCmdID = 1; if($cmd eq "teach") { $data = sprintf "A5%02X000000%s00", $phcCmdID, $subDef; } elsif ($cmd eq "on" || $cmd eq "B0") { $setCmd = 9; if ($a[1]) { return "Usage: $cmd [lock|unlock]" if (($a[1] ne "lock") && ($a[1] ne "unlock")); $setCmd = $setCmd | 4 if ($a[1] eq "lock"); shift(@a); } $updateState = 0; $data = sprintf "A5%02X%04X%02X%s00", $phcCmdID, $time, $setCmd, $subDef; } elsif ($cmd eq "off" || $cmd eq "BI") { $setCmd = 8; if ($a[1]) { return "Usage: $cmd [lock|unlock]" if (($a[1] ne "lock") && ($a[1] ne "unlock")); $setCmd = $setCmd | 4 if ($a[1] eq "lock"); shift(@a); } $updateState = 0; $data = sprintf "A5%02X%04X%02X%s00", $phcCmdID, $time, $setCmd, $subDef; } else { my $cmdList = "B0 BI off on teach"; return SetExtensions ($hash, $cmdList, $name, @a); $updateState = 0; $data = sprintf "A5%02X%04X%02X%s00", $phcCmdID, $time, $setCmd, $subDef; } } elsif ($phcCmd eq "dimming") { # Dimming $phcCmdID = 2; my $dimVal = $hash->{READINGS}{dimValue}{VAL}; my $rampTime = AttrVal($name, "rampTime", 1); my $sendDimCmd = 0; $setCmd = 9; my $subDef = AttrVal($name, "subDef", "$hash->{DEF}"); if ($cmd eq "teach") { $data = sprintf "A5%02X000000%s00", $phcCmdID, $subDef; } elsif ($cmd eq "dim") { return "Usage: $cmd dim/% [rampTime/s lock|unlock]" if((@a < 2) or ($a[1] < 0) or ($a[1] > 100) or ($a[1] !~ m/^[+-]?\d+$/)); # for eltako relative (0-100) (but not compliant to EEP because DB0.2 is 0) # >> if manufID needed: set DB2.0 $dimVal = $a[1]; readingsSingleUpdate ($hash, "dimValueStored", $dimVal, 1); shift(@a); if (defined($a[1])) { return "Usage: $cmd dim/% [rampTime/s lock|unlock]" if ($a[1] !~ m/^[+-]?\d+$/); $rampTime = $a[1]; shift(@a); } $sendDimCmd = 1; } elsif ($cmd eq "dimup") { return "Usage: $cmd dim/% [rampTime/s lock|unlock]" if((@a < 2) or ($a[1] < 0) or ($a[1] > 100) or ($a[1] !~ m/^[+-]?\d+$/)); $dimVal += $a[1]; readingsSingleUpdate ($hash, "dimValueStored", $dimVal, 1); shift(@a); if (defined($a[1])) { return "Usage: $cmd dim/% [rampTime/s lock|unlock]" if ($a[1] !~ m/^[+-]?\d+$/); $rampTime = $a[1]; shift(@a); } $sendDimCmd = 1; } elsif ($cmd eq "dimdown") { return "Usage: $cmd dim/% [rampTime/s lock|unlock]" if((@a < 2) or ($a[1] < 0) or ($a[1] > 100) or ($a[1] !~ m/^[+-]?\d+$/)); $dimVal -= $a[1]; readingsSingleUpdate ($hash, "dimValueStored", $dimVal,1); shift(@a); if (defined($a[1])) { return "Usage: $cmd dim/% [rampTime/s lock|unlock]" if ($a[1] !~ m/^[+-]?\d+$/); $rampTime = $a[1]; shift(@a); } $sendDimCmd = 1; } elsif ($cmd eq "on" || $cmd eq "B0") { $rampTime = 1; my $dimValueOn = AttrVal($name, "dimValueOn", 100); if ($dimValueOn eq "stored") { $dimVal = ReadingsVal($name, "dimValueStored", 100); if ($dimVal < 1) { $dimVal = 100; readingsSingleUpdate ($hash, "dimValueStored", $dimVal, 1); } } elsif ($dimValueOn eq "last") { $dimVal = ReadingsVal ($name, "dimValueLast", 100); if ($dimVal < 1) { $dimVal = 100; } } else { if ($dimValueOn !~ m/^[+-]?\d+$/) { $dimVal = 100; } elsif ($dimValueOn > 100) { $dimVal = 100; } elsif ($dimValueOn < 1) { $dimVal = 1; } else { $dimVal = $dimValueOn; } } $sendDimCmd = 1; } elsif ($cmd eq "off" || $cmd eq "BI") { $dimVal = 0; $rampTime = 1; $setCmd = 8; $sendDimCmd = 1; } else { my $cmdList = "dim:slider,0,1,100 B0 BI on off teach"; return SetExtensions ($hash, $cmdList, $name, @a); } if($sendDimCmd) { if (defined($a[1])) { return "Usage: $cmd dim/% [rampTime/s lock|unlock]" if (($a[1] ne "lock") && ($a[1] ne "unlock")); if ($manufID eq "OOD") { # Eltako devices: block dimming value if ($a[1] eq "lock") { $setCmd = $setCmd | 4; } } else { # Dimming value relative $setCmd = $setCmd | 4; } shift(@a); } else { if ($manufID ne "OOD") { $setCmd = $setCmd | 4; } } $a[0] = "on"; if ($dimVal > 100) { $dimVal=100; } if ($dimVal <= 0) { $dimVal=0; $setCmd = 8; $a[0]="off"; } if ($rampTime > 255) { $rampTime = 255; } if ($rampTime < 0) { $rampTime = 0; } $updateState = 0; $data = sprintf "A502%02X%02X%02X%s00", $dimVal, $rampTime, $setCmd, $subDef; } } elsif ($phcCmd eq "setpointShift") { $phcCmdID = 3; if($cmd eq "teach") { $data = sprintf "A5%02X000000%s00", $phcCmdID, $subDef; } else { } } elsif ($phcCmd eq "setpointBasic") { $phcCmdID = 4; if($cmd eq "teach") { $data = sprintf "A5%02X000000%s00", $phcCmdID, $subDef; } else { } } elsif ($phcCmd eq "controlVar") { $phcCmdID = 5; if($cmd eq "teach") { $data = printf "A5%02X000000%s00", $phcCmdID, $subDef; } else { } } elsif ($phcCmd eq "fanStage") { $phcCmdID = 6; if($cmd eq "teach") { $data = sprintf "A5%02X000000%s00", $phcCmdID, $subDef; } else { } } # write phcGateway command # len: 0x000A optlen: 0x00 pakettype: 0x01(radio) IOWrite($hash, "000A0001", $data); Log $ll2, "EnOcean: set $name $cmd"; } else { # Rocker Switch, simulate a PTM200 switch module # separate first and second action my ($c1,$c2) = split(",", $cmd, 2); # check values if(!defined($EnO_ptm200btn{$c1}) || ($c2 && !defined($EnO_ptm200btn{$c2}))) { my $list = join(" ", sort keys %EnO_ptm200btn); return SetExtensions($hash, $list, $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 $subDef = AttrVal($name, "subDef", "$hash->{DEF}"); my $subDef0 = AttrVal($name, "subDef0", "$hash->{DEF}"); my $subDefI = AttrVal($name, "subDefI", "$hash->{DEF}"); my $switchMode = AttrVal($name, "switchMode", "switch"); my $switchType = AttrVal($name, "switchType", "direction"); # first action if ($switchType eq "central") { if ($c1 =~ m/.0/ || $c1 eq "released") { $subDef = $subDef0; } else { $subDef = $subDefI; } } if ($switchType eq "universal") { if ($c1 =~ m/A0|AI/ && (!$channelA || ($c1 ne $channelA))) { $c1 = "A0"; } elsif ($c1 =~ m/B0|BI/ && (!$channelB || $c1 ne $channelB)) { $c1 = "B0"; } elsif ($c1 =~ m/C0|CI/ && (!$channelC || ($c1 ne $channelC))) { $c1 = "C0"; } elsif ($c1 =~ m/D0|DI/ && (!$channelD || ($c1 ne $channelD))) { $c1 = "D0"; } elsif ($c1 eq "released") { } else { $sendCmd = "no"; } } # second action if ($c2 && $switchType eq "universal") { if ($c2 =~ m/A0|AI/ && (!$channelA || ($c2 ne $channelA))) { $c2 = "A0"; } elsif ($c2 =~ m/B0|BI/ && (!$channelB || $c2 ne $channelB)) { $c2 = "B0"; } elsif ($c2 =~ m/C0|CI/ && (!$channelC || ($c2 ne $channelC))) { $c2 = "C0"; } elsif ($c2 =~ m/D0|DI/ && (!$channelD || ($c2 ne $channelD))) { $c2 = "D0"; } else { $c2 = undef; } if ($c2 && $sendCmd eq "no") { # only second action has changed, send as first action $c1 = $c2; $c2 = undef; $sendCmd = "yes"; } } # convert and send first and second command my ($db_3, $status) = split(":", $EnO_ptm200btn{$c1}, 2); $db_3 <<= 5; $db_3 |= 0x10 if($c1 ne "released"); # set the pressed flag if($c2 && $switchType ne "central") { my ($d2, undef) = split(":", $EnO_ptm200btn{$c2}, 2); $db_3 |= ($d2<<1) | 0x01; } if ($sendCmd ne "no") { IOWrite($hash, "", sprintf("6B05%02X000000%s%s", $db_3, $subDef, $status)); Log $ll2, "EnOcean: set $name $cmd"; if ($switchMode eq "pushbutton") { IOWrite($hash, "", sprintf("6B0500000000%s20", $subDef)); Log $ll2, "EnOcean: set $name released"; } } } select(undef, undef, undef, 0.2); # Tested by joerg. He prefers 0.3 :) } if($updateState == 1) { readingsSingleUpdate($hash, "state", join(" ", @a), 1); return undef; } } # Parse sub EnOcean_Parse($$) { my ($iohash, $msg) = @_; my (undef,$rorg,$data,$id,$status,$odata) = split(":", $msg); my $rorgname = $EnO_rorgname{$rorg}; if(!$rorgname) { Log 2, "Unknown EnOcean RORG ($rorg) received from $id"; return ""; } my $hash = $modules{EnOcean}{defptr}{$id}; if(!$hash) { Log 3, "EnOcean Unknown device with ID $id, please define it"; return "UNDEFINED EnO_${rorgname}_$id EnOcean $id"; } my $name = $hash->{NAME}; my $ll4 = GetLogLevel($name, 4); Log $ll4, "$name: ORG:$rorg DATA:$data ID:$id STATUS:$status"; my @event; my $dl = length($data); my $db_3 = hex substr($data,0,2); my $db_2 = hex substr($data,2,2) if($dl > 2); my $db_1 = hex substr($data,4,2) if($dl > 4); my $db_0 = hex substr($data,6,2) if($dl > 6); my $st = AttrVal($name, "subType", ""); my $manufID = AttrVal($name, "manufID", ""); my $model = AttrVal($name, "model", ""); if($rorg eq "F6") { # RPS Telegram (PTM200) # Rocker Switch (EEP F6-02-01 ... F6-03-02) # Position Switch, Home and Office Application (EEP F6-04-01) # Mechanical Handle (EEP F6-10-00) my $event = "state"; my $nu = ((hex($status)&0x10)>>4); # unused flags (AFAIK) #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_3&0xe0)>>5]; $msg .= ",".$EnO_ptm200btn[($db_3&0x0e)>>1] if($db_3 & 1); $msg .= " released" if(!($db_3 & 0x10)); } else { if($db_3 == 112) { # Key Card, not tested $msg = "keycard inserted"; } elsif($db_3 & 0xC0) { # Only a Mechanical Handle is setting these bits when nu=0 $msg = "closed" if($db_3 == 0xF0); $msg = "open" if($db_3 == 0xE0); $msg = "tilted" if($db_3 == 0xD0); $msg = "open from tilted" if($db_3 == 0xC0); } else { if($st eq "keycard") { $msg = "keycard removed"; } else { $msg = (($db_3&0x10) ? "pressed" : "released"); } } } if ($st eq "FRW") { # smoke detector Eltako FRW, untested if ($msg =~ m/A0$/) { push @event, "3:battery:low"; } elsif ($msg =~ m/AI$/) { push @event, "3:alarm:smoke-alarm"; $msg = "smoke-alarm"; } elsif ($msg =~ m/released$/) { push @event, "3:alarm:off"; push @event, "3:battery:ok"; $msg = "off"; } } else { if ($msg =~ m/A0$/) { push @event, "3:channelA:A0"; } elsif ($msg =~ m/AI$/) { push @event, "3:channelA:AI"; } elsif ($msg =~ m/B0$/) { push @event, "3:channelB:B0"; } elsif ($msg =~ m/BI$/) { push @event, "3:channelB:BI"; } elsif ($msg =~ m/C0$/) { push @event, "3:channelC:C0"; } elsif ($msg =~ m/CI$/) { push @event, "3:channelC:CI"; } elsif ($msg =~ m/D0$/) { push @event, "3:channelD:D0"; } 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"); } push @event, "3:$event:$msg"; } elsif($rorg eq "D5") { # 1BS Telegram # Single Input Contact (EEP D5-00-01) # [Eltako FTK, STM-250] push @event, "3:state:" . ($db_3 & 1 ? "closed" : "open"); push @event, "3:learnBtn:on" if (!($db_3 & 0x8)); } elsif($rorg eq "A5") { # 4BS Telegram if(($db_0 & 0x08) == 0) { # teach-in telegram if($db_0 & 0x80) { # teach-in telegram with EEP and Manufacturer ID my $fn = sprintf "%02x", ($db_3 >> 2); my $tp = sprintf "%02X", ((($db_3 & 3) << 5) | ($db_2 >> 3)); my $mf = sprintf "%03X", ((($db_2 & 7) << 8) | $db_1); # manufID to account for vendor-specific features $attr{$name}{manufID} = $mf; $mf = $EnO_manuf{$mf} if($EnO_manuf{$mf}); my $m = "teach-in:EEP A5-$fn-$tp Manufacturer: $mf"; Log 1, $m; push @event, "3:$m"; my $st = "A5.$fn.$tp"; $st = $EnO_subType{$st} if($EnO_subType{$st}); $attr{$name}{subType} = $st; if("$fn.$tp" eq "20.01" && $iohash->{pair}) { # MD15 select(undef, undef, undef, 0.1); # max 10 Seconds EnOcean_A5Cmd($hash, "800800F0", "00000000"); select(undef, undef, undef, 0.5); EnOcean_MD15Cmd($hash, $name, 128); # 128 == 20 degree C } # subType, manufID storing corrected CommandSave(undef, undef); } else { push @event, "3:teach-in:No EEP profile identifier and no Manufacturer ID"; } } elsif($st eq "MD15") { # Battery Powered Actuator (EEP A5-20-01) # [Kieback&Peter MD15-FTL-xx] push @event, "3:state:$db_3"; push @event, "3:currentValue:$db_3"; push @event, "3:serviceOn:" . (($db_2 & 0x80) ? "yes" : "no"); push @event, "3:energyInput:" . (($db_2 & 0x40) ? "enabled":"disabled"); push @event, "3:energyStorage:". (($db_2 & 0x20) ? "charged":"empty"); push @event, "3:battery:" . (($db_2 & 0x10) ? "ok" : "low"); push @event, "3:cover:" . (($db_2 & 0x08) ? "open" : "closed"); push @event, "3:tempSensor:" . (($db_2 & 0x04) ? "failed" : "ok"); push @event, "3:window:" . (($db_2 & 0x02) ? "open" : "closed"); 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); } elsif($model eq "PM101") { # Light and Presence Sensor [Omnio Ratio eagle-PM101] # The sensor also sends switching commands (RORG F6) with the senderID-1 # code by aicgazi # $db_2 is the illuminance where max value 0xFF stands for 1000 lx my $lux = sprintf "%3d", $db_2; $lux = sprintf "%04.2f", ( $lux * 1000 / 255 ) ; push @event, "3:brightness:$lux"; push @event, "3:channel1:" . ($db_0 & 0x01 ? "off" : "on"); push @event, "3:channel2:" . ($db_0 & 0x02 ? "off" : "on"); push @event, "3:motion:" . ($db_0 & 0x02 ? "off" : "on"); push @event, "3:state:" . ($db_0 & 0x02 ? "off" : "on"); } elsif($st eq "FAH" || $model =~ /^(FAH60|FAH63|FIH63)$/) { # Light Sensor # [Eltako FAH60, FAH63, FIH63] (EEP A5-06-01 plus Data_byte3) # $db_3 is the illuminance where min 0x00 = 0 lx, max 0xFF = 100 lx # $db_2 must be 0x00 if($db_2 eq 0x00) { my $luxlow = sprintf "%3d", $db_3; $luxlow = sprintf "%d", ( $luxlow * 100 / 255 ) ; push @event, "3:brightness:$luxlow"; push @event, "3:state:$luxlow"; } else { # $db_2 is the illuminance where min 0x00 = 300 lx, max 0xFF = 30000 lx my $lux = sprintf "%3d", $db_2; $lux = sprintf "%d", (( $lux * 116.48) + 300 ) ; push @event, "3:brightness:$lux"; push @event, "3:state:$lux"; } } elsif($st eq "FBH" || $model =~ /^(FABH63|FBH55|FBH63|FIBH63)$/) { # Light and Occupancy Sensor (no Temperature) # [Eltako FABH63, FBH55, FBH63, FIBH63] (EEP similar A5-08-01) # $db_0 motion detection where 0x0D = motion and 0x0F = no motion # (DB0_Bit1 = 1 or 0) if($db_0 eq 0x0D) { push @event, "3:motion:yes"; push @event, "3:state:yes"; } if($db_0 eq 0x0F) { push @event, "3:motion:no"; push @event, "3:state:no"; } # $db_2 is the illuminance where min 0x00 = 0 lx, max 0xFF = 2048 lx my $lux = sprintf "%3d", $db_2; $lux = sprintf "%d", ( $lux * 2048 / 255 ) ; push @event, "3:brightness:$lux"; # $db_3 is voltage in EEP A5-08-01 but not used by Eltako !? # push @event, "3:voltage:$db_3"; } elsif($st eq "FTF" || $model eq "FTF55") { # Temperature Sensor (EEP A5-02-05) # [Eltako FTF55, Thermokon SR04] # $db_1 is the temperature where 0x00 = 40°C and 0xFF = 0°C my $temp = sprintf "%3d", $db_1; $temp = sprintf "%0.1f", ( 40 - $temp * 40 / 255 ) ; push @event, "3:state:$temp"; push @event, "3:temperature:$temp"; } elsif($model =~ m/^SR04/ || $st eq "SR04") { # Room Sensor and Control Unit # [Thermokon SR04 *] my ($fspeed, $temp, $present, $solltemp); $fspeed = 3; $fspeed = 2 if($db_3 >= 145); $fspeed = 1 if($db_3 >= 165); $fspeed = 0 if($db_3 >= 190); $fspeed = "Auto" if($db_3 >= 210); $temp = sprintf("%0.1f", 40-$db_1/6.375); # 40..0 $present= $db_0&0x1 ? "no" : "yes"; $solltemp= sprintf("%0.1f", $db_2/6.375); push @event, "3:state:temperature $temp"; push @event, "3:set_point: $solltemp"; push @event, "3:setpoint:$db_2"; push @event, "3:fan:$fspeed"; push @event, "3:present:$present" if($present eq "yes"); push @event, "3:learnBtn:on" if(!($db_0&0x8)); push @event, "3:T:$temp SP: $db_3 F: $fspeed P: $present"; } elsif ($st =~ m/^tempSensor/) { # Temperature Sensor with with different ranges (EEP A5-02-01 ... A5-02-1B) # $db_1 is the temperature where 0x00 = max °C ... 0xFF = min °C my $temp; $temp = sprintf "%0.1f", -40 - $db_1 / 6.375 if ($st eq "tempSensor.01"); $temp = sprintf "%0.1f", -30 - $db_1 / 6.375 if ($st eq "tempSensor.02"); $temp = sprintf "%0.1f", -20 - $db_1 / 6.375 if ($st eq "tempSensor.03"); $temp = sprintf "%0.1f", -10 - $db_1 / 6.375 if ($st eq "tempSensor.04"); $temp = sprintf "%0.1f", 0 - $db_1 / 6.375 if ($st eq "tempSensor.05"); $temp = sprintf "%0.1f", 10 - $db_1 / 6.375 if ($st eq "tempSensor.06"); $temp = sprintf "%0.1f", 20 - $db_1 / 6.375 if ($st eq "tempSensor.07"); $temp = sprintf "%0.1f", 30 - $db_1 / 6.375 if ($st eq "tempSensor.08"); $temp = sprintf "%0.1f", 40 - $db_1 / 6.375 if ($st eq "tempSensor.09"); $temp = sprintf "%0.1f", 50 - $db_1 / 6.375 if ($st eq "tempSensor.0A"); $temp = sprintf "%0.1f", 60 - $db_1 / 6.375 if ($st eq "tempSensor.0B"); $temp = sprintf "%0.1f", -60 - $db_1 / 3.1875 if ($st eq "tempSensor.10"); $temp = sprintf "%0.1f", -50 - $db_1 / 3.1875 if ($st eq "tempSensor.11"); $temp = sprintf "%0.1f", -40 - $db_1 / 3.1875 if ($st eq "tempSensor.12"); $temp = sprintf "%0.1f", -30 - $db_1 / 3.1875 if ($st eq "tempSensor.13"); $temp = sprintf "%0.1f", -20 - $db_1 / 3.1875 if ($st eq "tempSensor.14"); $temp = sprintf "%0.1f", -10 - $db_1 / 3.1875 if ($st eq "tempSensor.15"); $temp = sprintf "%0.1f", 0 - $db_1 / 3.1875 if ($st eq "tempSensor.16"); $temp = sprintf "%0.1f", 10 - $db_1 / 3.1875 if ($st eq "tempSensor.17"); $temp = sprintf "%0.1f", 20 - $db_1 / 3.1875 if ($st eq "tempSensor.18"); $temp = sprintf "%0.1f", 30 - $db_1 / 3.1875 if ($st eq "tempSensor.19"); $temp = sprintf "%0.1f", 40 - $db_1 / 3.1875 if ($st eq "tempSensor.1A"); $temp = sprintf "%0.1f", 50 - $db_1 / 3.1875 if ($st eq "tempSensor.1B"); $temp = sprintf "%0.2f", -10 - (($db_2 << 8) | $db_1) / 19.98 if ($st eq "tempSensor.20"); $temp = sprintf "%0.1f", -40 - (($db_2 << 8) | $db_1) / 6.3 if ($st eq "tempSensor.30"); push @event, "3:temperature:$temp"; push @event, "3:state:$temp"; } elsif($st eq "COSensor.01") { # Gas Sensor, CO Sensor (EEP A5-09-01) # [untested] # $db_3 is the CO concentration where 0x00 = 0 ppm ... 0xFF = 255 ppm # $db_2 is the CO concentration where 0x00 = 0 ppm ... 0xFF = 255 ppm # $db_1 is the temperature where 0x00 = 0 °C ... 0xFF = 255 °C # $db_0 bit D1 temperature sensor available 0 = no, 1 = yes my $coChannel1 = $db_3; my $coChannel2 = $db_2; push @event, "3:Channel1:$coChannel1"; push @event, "3:Channel2:$coChannel2"; if ($coChannel1 == $coChannel2) { push @event, "3:state:$coChannel1"; } else { push @event, "3:state:measuring error"; } if ($db_0 & 2) { my $temp = $db_1; push @event, "3:temperature:$temp"; } } elsif($st eq "tempHumiCO2Sensor.01") { # Gas Sensor, CO2 Sensor (EEP A5-09-04) # [Thermokon SR04 CO2 *, 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 # $db_0 bit D2 humidity sensor available 0 = no, 1 = yes # $db_0 bit D1 temperature sensor available 0 = no, 1 = yes my $humi = "unknown"; my $temp = "unknown"; my $airQuality; if ($db_0 & 4) { $humi = $db_3 >> 1; push @event, "3:humidity:$humi"; } my $co2 = sprintf "%d", $db_2 * 10; push @event, "3:CO2:$co2"; if ($db_0 & 2) { $temp = sprintf "%0.1f", $db_1 * 51 / 255 ; push @event, "3:temperature:$temp"; } if ($co2 <= 400) { $airQuality = "high"; } elsif ($co2 <= 600) { $airQuality = "mean"; } elsif ($co2 <= 1000) { $airQuality = "moderate"; } else { $airQuality = "low"; } push @event, "3:airQuality:$airQuality"; push @event, "3:state:CO2 $co2 AQ: $airQuality T: $temp H: $humi"; } elsif ($st eq "roomSensorControl.05") { # Room Sensor and Control Unit (EEP A5-10-01 ... A5-10-0D) # [Eltako FTF55D, FTF55H, Thermokon SR04 *, Thanos SR *, untested] # $db_3 is the fan speed or night reduction for Eltako # $db_2 is the setpoint where 0x00 = min ... 0xFF = max or # reference temperature for Eltako whre 0x00 = 0°C ... 0xFF = 40°C # $db_1 is the temperature where 0x00 = +40°C ... 0xFF = 0°C # $db_0 bit D0 is the occupy button, pushbutton or slide switch my $temp = sprintf "%0.1f", 40 - $db_1 / 6.375; if ($manufID eq "00D") { my $nightReduction = 0; $nightReduction = 1 if ($db_3 == 0x06); $nightReduction = 2 if ($db_3 == 0x0c); $nightReduction = 3 if ($db_3 == 0x13); $nightReduction = 4 if ($db_3 == 0x19); $nightReduction = 5 if ($db_3 == 0x1f); my $setpointTemp = sprintf "%0.1f", $db_2 / 6.375; push @event, "3:state:T: $temp SPT: $setpointTemp NR: $nightReduction"; push @event, "3:nightReduction:$nightReduction"; push @event, "3:setpointTemp:$setpointTemp"; } else { my $fspeed = 3; $fspeed = 2 if ($db_3 >= 145); $fspeed = 1 if ($db_3 >= 165); $fspeed = 0 if ($db_3 >= 190); $fspeed = "Auto" if ($db_3 >= 210); my $switch = $db_0 & 1; push @event, "3:state:T: $temp SP: $db_2 F: $fspeed SW: $switch"; push @event, "3:fan:$fspeed"; push @event, "3:switch:$switch"; push @event, "3:setpoint:$db_2"; } push @event, "3:temperature:$temp"; } elsif($st eq "roomSensorControl.01") { # Room Sensor and Control Unit (EEP A5-04-01, A5-10-10 ... A5-10-14) # [Thermokon SR04 * rH, Thanus SR *, untested] # $db_3 is the setpoint where 0x00 = min ... 0xFF = max # $db_2 is the humidity where 0x00 = 0%rH ... 0xFA = 100%rH # $db_1 is the temperature where 0x00 = 0°C ... 0xFA = +40°C # $db_0 bit D0 is the occupy button, pushbutton or slide switch 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"; } elsif($st eq "roomSensorControl.02") { # Room Sensor and Control Unit (A5-10-15 ... A5-10-17) # [untested] # $db_2 bit D7 ... D2 is the setpoint where 0 = min ... 63 = max # $db_2 bit D1 ... $db_1 bit D0 is the temperature where 0 = -10°C ... 1023 = +41.2°C # $db_0_bit_0 is Occupany Button where 0 = pressed, 1 = released my $temp = sprintf "%0.2f", -10 + ((($db_2 & 3) << 8) | $db_1) / 19.98; my $setpoint = ($db_2 & 0xFC) >> 2; my $presence = $db_0 & 1 ? "present" : "absent"; push @event, "3:state:T: $temp P: $presence SP: $setpoint "; push @event, "3:presence:$presence"; push @event, "3:setpoint:$setpoint"; push @event, "3:temperature:$temp"; } elsif($st eq "tempHumiSensor.02") { # Temperatur and Humidity Sensor(EEP A5-04-02) # [Eltako FAFT60, FIFT63AP] # $db_3 is the voltage where 0x59 = 2.5V ... 0x9B = 4V, only at Eltako # $db_2 is the humidity where 0x00 = 0%rH ... 0xFA = 100%rH # $db_1 is the temperature where 0x00 = -20°C ... 0xFA = +60°C #my $temp = sprintf "%3d", $db_1; #my $voltage = sprintf "%3d", $db_3; my $humi = sprintf "%d", $db_2 / 2.5; my $temp = sprintf "%0.1f", -20 + $db_1 * 80 / 250; my $battery = "unknown"; if ($manufID eq "00D") { # Eltako sensor my $voltage = sprintf "%0.1f", $db_3 * 6.58 / 255; my $energyStorage = "unknown"; if ($db_3 <= 0x58) { $energyStorage = "empty"; $battery = "low"; } elsif ($db_3 <= 0xDC) { $energyStorage = "charged"; $battery = "ok"; } else { $energyStorage = "full"; $battery = "ok"; } push @event, "3:battery:$battery"; push @event, "3:energyStorage:$energyStorage"; push @event, "3:voltage:$voltage"; } push @event, "3:state:T: $temp H: $humi B: $battery"; push @event, "3:humidity:$humi"; push @event, "3:temperature:$temp"; } elsif ($st eq "lightSensor.01") { # Light Sensor (EEP A5-06-01) # [Eltako FAH60, FAH63, FIH63, Thermokon SR65 LI, untested] # $db_3 is the voltage where 0x00 = 0 V ... 0xFF = 5.1 V # $db_3 is the low illuminance for Eltako devices where # min 0x00 = 0 lx, max 0xFF = 100 lx, if $db_2 = 0 # $db_2 is the illuminance (ILL2) where min 0x00 = 300 lx, max 0xFF = 30000 lx # $db_1 is the illuminance (ILL1) where min 0x00 = 600 lx, max 0xFF = 60000 lx # $db_0_bit_0 is Range select where 0 = ILL1, 1 = ILL2 my $lux; my $voltage = "unknown"; # operation prüfen if ($manufID eq "00D") { if($db_2 == 0) { $lux = sprintf "%d", $db_3 * 100 / 255; } else { $lux = sprintf "%d", $db_2 * 116.48 + 300; } } else { $voltage = sprintf "%d", $db_3 * 0.02; if($db_0 & 1) { $lux = sprintf "%d", $db_2 * 116.48 + 300; } else { $lux = sprintf "%d", $db_1 * 232.94 + 600; } push @event, "3:voltage:$voltage"; } push @event, "3:brightness:$lux"; push @event, "3:state:$lux"; } elsif ($st eq "lightSensor.02") { # Light Sensor (EEP A5-06-02) # $db_3 is the voltage where 0x00 = 0 V ... 0xFF = 5.1 V # $db_2 is the illuminance (ILL2) where min 0x00 = 0 lx, max 0xFF = 510 lx # $db_1 is the illuminance (ILL1) where min 0x00 = 0 lx, max 0xFF = 1020 lx # $db_0_bit_0 is Range select where 0 = ILL1, 1 = ILL2 my $lux; my $voltage = sprintf "%d", $db_3 * 0.02; if($db_0 & 1) { $lux = $db_2 << 1; } else { $lux = $db_1 << 2; } push @event, "3:voltage:$voltage"; push @event, "3:brightness:$lux"; push @event, "3:state:$lux"; } elsif ($st eq "occupSensor.01") { # Occupancy Sensor (EEP A5-07-01) # $db_1 is PIR Status (motion) where 0 ... 127 = off, 128 ... 255 = on my $motion = "off"; if ($db_1 >= 128) {$motion = "on";} push @event, "3:motion:$motion"; push @event, "3:state:$motion"; } elsif ($st =~ m/^lightTempOccupSensor/) { # Light, Temperatur and Occupancy Sensor (EEP A5-08-01 ... A5-08-03) # $db_3 is the voltage where 0x00 = 0 V ... 0xFF = 5.1 V # $db_2 is the illuminance where min 0x00 = 0 lx, max 0xFF = 510 lx, 1020 lx, (2048 lx) # $db_1 is the temperature whrere 0x00 = 0 °C ... 0xFF = 51 °C or -30 °C ... 50°C # $db_0_bit_1 is PIR Status (motion) where 0 = on, 1 = off # $db_0_bit_0 is Occupany Button where 0 = pressed, 1 = released my $lux; my $temp; my $voltage = sprintf "%0.1f", $db_3 * 0.02; my $motion = $db_0 & 2 ? "off" : "on"; my $presence = $db_0 & 1 ? "present" : "absent"; if ($st eq "lightTempOccupSensor.01") { # Light, Temperatur and Occupancy Sensor (EEP A5-08-01) # [Eltako FABH63, FBH55, FBH63, FIBH63] if ($manufID eq "00D") { $lux = sprintf "%d", $db_2 * 2048 / 255; push @event, "3:state:M: $motion E: $lux"; } else { $lux = $db_2 << 1; $temp = sprintf "%0.1f", $db_1 * 0.2; push @event, "3:state:M: $motion E: $lux P: $presence T: $temp U: $voltage"; push @event, "3:presence:$presence"; push @event, "3:temperature:$temp"; push @event, "3:voltage:$voltage"; } } elsif ($st eq "lightTempOccupSensor.02") { # Light, Temperatur and Occupancy Sensor (EEP A5-08-02) $lux = $db_2 << 2; $temp = sprintf "%0.1f", $db_1 * 0.2; push @event, "3:state:M: $motion E: $lux P: $presence T: $temp U: $voltage"; push @event, "3:presence:$presence"; push @event, "3:temperature:$temp"; push @event, "3:voltage:$voltage"; } elsif ($st eq "lightTempOccupSensor.03") { # Light, Temperatur and Occupancy Sensor (EEP A5-08-03) $lux = $db_2 * 6; $temp = sprintf "%0.1f", -30 + $db_1 * 80 / 255; push @event, "3:state:M: $motion E: $lux P: $presence T: $temp U: $voltage"; push @event, "3:presence:$presence"; push @event, "3:temperature:$temp"; push @event, "3:voltage:$voltage"; } push @event, "3:brightness:$lux"; push @event, "3:motion:$motion"; } elsif ($st =~ m/^autoMeterReading/) { # Automated meter reading (AMR) (EEP A5-12-00 ... A5-12-03) # $db_3 (MSB) + $db_2 + $db_1 (LSB) is the Meter reading # $db_0_bit_7 ... $db_0_bit_4 is the Measurement channel # $db_0_bit_2 is the Data type where 0 = cumulative value, 1 = current value # $db_0_bit_1 ... $db_0_bit_0 is the Divisor where 0 = x/1, 1 = x/10, # 2 = x/100, 3 = x/1000 # my $meterReading = hex sprintf "%02x%02x%02x", $db_3, $db_2, $db_1; my $dataType = ($db_0 & 4) >> 3; my $divisor = $db_0 & 3; if ($divisor == 3) { $divisor = 1000; } elsif ($divisor == 2) { $divisor = 100; } elsif ($divisor == 1) { $divisor = 10; } else { $divisor = 1; } my $meterReading = sprintf "%0.1f", (($db_3 << 16) | ($db_2 << 8) | $db_1) / $divisor; my $channel = $db_0 >> 4; if ($st eq "autoMeterReading.00") { # Automated meter reading (AMR), Counter (EEP A5-12-01) # [Thermokon SR-MI-HS, untested] if ($dataType == 1) { # current value push @event, "3:currentValue:$meterReading"; push @event, "3:state:$meterReading"; } else { # cumulative counter push @event, "3:counter$channel:$meterReading"; } } elsif ($st eq "autoMeterReading.01") { # Automated meter reading (AMR), Electricity (EEP A5-12-01) # [Eltako FSS12, FWZ12, DSZ14DRS, DSZ14WDRS] # $db_0_bit_7 ... $db_0_bit_4 is the Tariff info # $db_0_bit_2 is the Data type where 0 = cumulative value kWh, # 1 = current value W if ($dataType == 1) { # momentary power push @event, "3:power:$meterReading"; push @event, "3:state:$meterReading"; } elsif ($db_0 == 0x8F && $manufID eq "00D") { # Eltako, read meter serial number my $serialNumber; if ($db_0 == 0) { # first 2 digits of the serial number $serialNumber = printf "S-%01x%01x", $db_3 >> 4, $db_3 & 0x0F; } else { # last 4 digits of the serial number $serialNumber = substr (ReadingsVal($name, "serialNumber", "S---"), 0, 4); $serialNumber = printf "%4c%01x%01x%01x%01x", $serialNumber, $db_2 >> 4, $db_2 & 0x0F, $db_3 >> 4, $db_3 & 0x0F; } push @event, "3:serialNumber:$serialNumber"; } else { # power consumption push @event, "3:energy$channel:$meterReading"; push @event, "3:currentTariff:$channel"; } } elsif ($st eq "autoMeterReading.02" | $st eq "autoMeterReading.03") { # Automated meter reading (AMR), Gas, Water (EEP A5-12-02, A5-12-03) if ($dataType == 1) { # current value push @event, "3:flowrate:$meterReading"; push @event, "3:state:$meterReading"; } else { # cumulative counter push @event, "3:consumption$channel:$meterReading"; push @event, "3:currentTariff:$channel"; } } } elsif ($st eq "weatherStation") { # Weather Station (EEP A5-13-01 ... EEP A5-13-06) # [Eltako FWS61, untested] # $db_0_bit_7 ... $db_0_bit_4 is the Identifier my $identifier = $db_0 >> 4; if ($identifier == 1) { # EEP A5-13-01 # $db_3 is the dawn sensor where 0x00 = 0 lx ... 0xFF = 999 lx # $db_2 is the temperature where 0x00 = -40 °C ... 0xFF = 80 °C # $db_1 is the wind speed where 0x00 = 0 m/s ... 0xFF = 70 m/s # $db_0_bit_2 is day / night where 0 = day, 1 = night # $db_0_bit_1 is rain indication where 0 = no (no rain), 1 = yes (rain) my $dawn = sprintf "%d", $db_3 * 999 / 255; my $temp = sprintf "%0.1f", -40 + $db_2 * 120 / 255; my $windSpeed = sprintf "%0.1f", $db_1 * 70 / 255; my $dayNight = $db_0 & 2 ? "night" : "day"; my $isRaining = $db_0 & 1 ? "yes" : "no"; push @event, "3:brightness:$dawn"; push @event, "3:dayNight:$dayNight"; push @event, "3:isRaining:$isRaining"; push @event, "3:temperature:$temp"; push @event, "3:windSpeed:$windSpeed"; push @event, "3:state:T: $temp B: $dawn W: $windSpeed IR: $isRaining"; } elsif ($identifier == 2) { # EEP A5-13-02 # $db_3 is the sun exposure west where 0x00 = 1 lx ... 0xFF = 150 klx # $db_2 is the sun exposure south where 0x00 = 1 lx ... 0xFF = 150 klx # $db_1 is the sun exposure east where 0x00 = 1 lx ... 0xFF = 150 klx my $sunWest = sprintf "%d", 1 + $db_3 * 149999 / 255; my $sunSouth = sprintf "%d", 1 + $db_2 * 149999 / 255; my $sunEast = sprintf "%d", 1 + $db_1 * 149999 / 255; push @event, "3:sunWest:$sunWest"; push @event, "3:sunSouth:$sunSouth"; push @event, "3:sunEast:$sunEast"; } else { # EEP A5-13-03 ... EEP A5-13-06 not implemented } } elsif ($st =~ m/^digitalInput/) { # Digital Input (EEP A5-30-01, A5-30-02) my $contact; if ($st eq "digtalInput.01") { # Single Input Contact, Batterie Monitor (EEP A5-30-01) # [Thermokon SR65 DI, untested] # $db_2 is the supply voltage, if >= 121 = battery ok # $db_1 is the input state, if <= 195 = contact closed my $battery = $db_2 >= 121 ? "ok" : "low"; $contact = $db_1 <= 195 ? "closed" : "open"; push @event, "3:battery:$battery"; } else { # Single Input Contact (EEP A5-30-01) # $db_0_bit_0 is the input state where 0 = closed, 1 = open $contact = $db_0 & 1 ? "open" : "closed"; } push @event, "3:contact:$contact"; push @event, "3:state:$contact"; } elsif ($st eq "phcGateway") { # PHC Gateway (EEP A5-38-08) # $db_3 is the command ID ($phcCmdID) # Eltako devices not send teach-in telegrams if(($db_0 & 8) == 0) { # teach-in, identify and store command type in attr phcCmd my $phcCmd = AttrVal($name, "phcCmd", undef); if (!$phcCmd) { $phcCmd = $EnO_phcCmd[$db_3]; $attr{$name}{phcCmd} = $phcCmd; } } if ($db_3 == 1) { # Switching # Eltako devices not send A5 telegrams push @event, "3:executeTime:" . sprintf "%0.1f", (($db_2 << 8) | $db_1) / 10; push @event, "3:lock:" . $db_0 & 4 ? "lock" : "unlock"; push @event, "3:executeType" . $db_0 & 2 ? "delay" : "duration"; push @event, "3:state:" . $db_0 & 1 ? "on" : "off"; } elsif ($db_3 == 2) { # Dimming # $db_0_bit_2 is store final value, not used, because # dimming value is always stored push @event, "3:rampTime:$db_1"; 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; } else { push @event, "3:dimValue:$db_2"; } push @event, "3:dimValueLast:$db_2" if ($db_2 > 0); } elsif ($db_3 == 3) { # Setpoint shift # $db1 is setpoint shift where 0 = -12.7 K ... 255 = 12.8 K my $setpointShift = sprintf "%0.1f", -12.7 + $db_1 / 10; push @event, "3:setpointShift:$setpointShift"; push @event, "3:state:$setpointShift"; } elsif ($db_3 == 4) { # Basic Setpoint # $db1 is setpoint where 0 = 0 °C ... 255 = 51.2 °C my $setpoint = sprintf "%0.1f", $db_1 / 5; push @event, "3:setpoint:$setpoint"; push @event, "3:state:$setpoint"; } elsif ($db_3 == 5) { # Control variable # $db1 is control variable override where 0 = 0 % ... 255 = 100 % push @event, "3:controlVar:" . sprintf "%d", $db_1 * 100 / 255; my $controllerMode = ($db_0 & 0x60) >> 5; if ($controllerMode == 0) { push @event, "3:controllerMode:auto"; push @event, "3:state:auto"; } elsif ($controllerMode == 1){ push @event, "3:controllerMode:heating"; push @event, "3:state:heating"; } elsif ($controllerMode == 2){ push @event, "3:controllerMode:colling"; push @event, "3:state:colling"; } elsif ($controllerMode == 3){ push @event, "3:controllerMode:off"; push @event, "3:state:off"; } push @event, "3:controllerState:" . $db_0 & 0x10 ? "override" : "auto"; push @event, "3:energyHoldOff:" . $db_0 & 4 ? "holdoff" : "normal"; my $occupancy = $db_0 & 3; if ($occupancy == 0) { push @event, "3:presence:present"; } elsif ($occupancy == 1){ push @event, "3:presence:absent"; } elsif ($occupancy == 2){ push @event, "3:presence:standby"; } } elsif ($db_3 == 6) { # Fan stage # if ($db_1 == 0) { push @event, "3:fanStage:0"; push @event, "3:state:0"; } elsif ($db_1 == 1){ push @event, "3:fanStage:1"; push @event, "3:state:1"; } elsif ($db_1 == 2){ push @event, "3:fanStage:2"; push @event, "3:state:2"; } elsif ($db_1 == 3){ push @event, "3:fanStage:3"; push @event, "3:state:3"; } elsif ($db_1 == 255){ push @event, "3:fanStage:auto"; push @event, "3:state:auto"; } } else { push @event, "3:state:PHC Gateway Command ID $db_3 unknown."; } } elsif ($st eq "manufProfile") { # Manufacturer Specific Applications (EEP A5-3F-7F) if ($manufID eq "002") { # [Thermokon SR65 3AI, untested] # $db_3 is the input 3 where 0x00 = 0 V ... 0xFF = 10 V # $db_2 is the input 2 where 0x00 = 0 V ... 0xFF = 10 V # $db_1 is the input 1 where 0x00 = 0 V ... 0xFF = 10 V my $input3 = sprintf "%0.2f", $db_3 * 10 / 255; my $input2 = sprintf "%0.2f", $db_2 * 10 / 255; my $input1 = sprintf "%0.2f", $db_1 * 10 / 255; push @event, "3:input1:$input1"; push @event, "3:input2:$input2"; push @event, "3:input3:$input3"; push @event, "3:state:I1: $input1 I2: $input2 I3: $input3"; } else { # Unknown Application push @event, "3:state:Manufacturer Specific Application unknown"; } } elsif ($st eq "eltakoDimmer") { # Dimmer # todo: create a more general solution for the central-command responses if($db_3 eq 0x02) { # dim push @event, "3:state:" . ($db_0 & 0x01 ? "on" : "off"); push @event, "3:dimValue:" . $db_2; if ($db_2 > 0) { push @event, "3:dimValueLast:" . $db_2; } } } else { # unknown devices push @event, "3:state:$db_3"; push @event, "3:sensor1:$db_3"; push @event, "3:sensor2:$db_2"; push @event, "3:sensor3:$db_1"; push @event, "3:D3:".(($db_0&0x8)?1:0); push @event, "3:D2:".(($db_0&0x4)?1:0); push @event, "3:D1:".(($db_0&0x2)?1:0); push @event, "3:D0:".(($db_0&0x1)?1:0); } } readingsBeginUpdate($hash); for(my $i = 0; $i < int(@event); $i++) { # Flag & 1: reading, Flag & 2: changed. Currently ignored. my ($flag, $vn, $vv) = split(":", $event[$i], 3); readingsBulkUpdate($hash, $vn, $vv); } readingsEndUpdate($hash, 1); return $name; } # MD15Cmd sub EnOcean_MD15Cmd($$$) { my ($hash, $name, $db_1) = @_; my $cmd = ReadingsVal($name, "CMD", undef); if($cmd) { my $msg; # Unattended my $arg1 = ReadingsVal($name, $cmd, 0); # Command-Argument if($cmd eq "actuator") { # $msg = sprintf("%02X000000", $arg1); $msg = sprintf("%02X7F0008", $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, AttrVal($name, "actualTemp", 127); } elsif($cmd eq "initialize") { # $msg = sprintf("00006400"); $msg = "00006408"; # Maintenance commands } elsif($cmd eq "runInit") { $msg = "00008108"; } elsif($cmd eq "liftSet") { $msg = "00004108"; } elsif($cmd eq "valveOpen") { $msg = "00002108"; } elsif($cmd eq "valveClosed") { $msg = "00001108"; } if($msg) { select(undef, undef, undef, 0.2); EnOcean_A5Cmd($hash, $msg, "00000000"); if($cmd eq "initialize") { delete($defs{$name}{READINGS}{CMD}); delete($defs{$name}{READINGS}{$cmd}); } } } } # A5Cmd sub EnOcean_A5Cmd($$$) { my ($hash, $msg, $org) = @_; IOWrite($hash, "000A0701", # varLen=0A optLen=07 msgType=01=radio, sprintf("A5%s%s0001%sFF00",$msg,$org,$hash->{DEF})); # type=A5 msg:4 senderId:4 status=00 subTelNum=01 destId:4 dBm=FF Security=00 } # Undef sub EnOcean_Undef($$) { my ($hash, $arg) = @_; delete $modules{EnOcean}{defptr}{uc($hash->{DEF})}; return undef; } 1; =pod =begin html

EnOcean

=end html =cut