From 9541e45ed9799ca022a1d41812dabac47c88d4ef Mon Sep 17 00:00:00 2001 From: "klaus.schauer" <> Date: Sun, 1 Jan 2017 09:18:17 +0000 Subject: [PATCH] 10_EnOcean: Added new EEP: # F6-01-01 (switch), F6-05-00 (windSpeed.00), F6-05-02 (smokeDetector.02), A5-09-0C (vocSensor.01), A5-14-07, A5-14-08 (doorContact), A5-14-09, A514-0A (windowContact), D2-05-02 (blindsCtrl.00), D2-B0-51 (liquidLeakage.51) # security function: decrypt messages with R-ORG encapsulation # commandref: further explanations added git-svn-id: https://svn.fhem.de/fhem/trunk@12924 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/FHEM/10_EnOcean.pm | 623 +++++++++++++++++++++++++--------------- 1 file changed, 388 insertions(+), 235 deletions(-) diff --git a/fhem/FHEM/10_EnOcean.pm b/fhem/FHEM/10_EnOcean.pm index 23ad6a565..120613d1f 100755 --- a/fhem/FHEM/10_EnOcean.pm +++ b/fhem/FHEM/10_EnOcean.pm @@ -1,15 +1,5 @@ ############################################## # $Id$ -# 2016-12-06 - -# Added new EEP: -# Remote Management: -# EEP changed: -# EnOcean_Notify(): -# EnOcean_Attr(): -# function SetExtensionsCancel() added -# subType hvac.01: attribute pidCtrl, model added -# commandref: further explanations added package main; @@ -223,6 +213,7 @@ my %EnO_eepConfig = ( "A5.09.09" => {attr => {subType => "CO2Sensor.01"}, GPLOT => "EnO_CO2:CO2,"}, "A5.09.0A" => {attr => {subType => "HSensor.01"}, GPLOT => "EnO_A5-09-0A:H/Temp,EnO_voltage4:Voltage,"}, "A5.09.0B" => {attr => {subType => "radiationSensor.01"}, GPLOT => "EnO_radioactivity4/Radioactivity,EnO_voltage4:Voltage,"}, + "A5.09.0C" => {attr => {subType => "vocSensor.01"}, GPLOT => "EnO_A5-09-05:Concentration,"}, "A5.10.01" => {attr => {subType => "roomSensorControl.05"}, GPLOT => "EnO_temp4:Temp,"}, "A5.10.02" => {attr => {subType => "roomSensorControl.05"}, GPLOT => "EnO_temp4:Temp,"}, "A5.10.03" => {attr => {subType => "roomSensorControl.05", comMode => "confirm", subDef => "getNextID"}, GPLOT => "EnO_temp4:Temp,"}, @@ -283,6 +274,10 @@ my %EnO_eepConfig = ( "A5.14.04" => {attr => {subType => "multiFuncSensor"}, GPLOT => "EnO_A5-14-xx:Voltage/Brightness,EnO_A5-14-xx_2:Contact/Vibration,"}, "A5.14.05" => {attr => {subType => "multiFuncSensor"}, GPLOT => "EnO_A5-14-xx:Voltage/Brightness,EnO_A5-14-xx_2:Contact/Vibration,"}, "A5.14.06" => {attr => {subType => "multiFuncSensor"}, GPLOT => "EnO_A5-14-xx:Voltage/Brightness,EnO_A5-14-xx_2:Contact/Vibration,"}, + "A5.14.07" => {attr => {subType => "doorContact"}, GPLOT => "EnO_A5-14-xx:Voltage/Brightness,EnO_A5-14-xx_2:Contact/Vibration,"}, + "A5.14.08" => {attr => {subType => "doorContact"}, GPLOT => "EnO_A5-14-xx:Voltage/Brightness,EnO_A5-14-xx_2:Contact/Vibration,"}, + "A5.14.09" => {attr => {subType => "windowContact"}, GPLOT => "EnO_A5-14-xx:Voltage/Brightness,EnO_A5-14-xx_2:Contact/Vibration,"}, + "A5.14.0A" => {attr => {subType => "windowContact"}, GPLOT => "EnO_A5-14-xx:Voltage/Brightness,EnO_A5-14-xx_2:Contact/Vibration,"}, "A5.20.01" => {attr => {subType => "hvac.01", webCmd => "setpointTemp"}, GPLOT => "EnO_A5-20-01:Temp/SetpointTemp/Setpoint,EnO_A5-20-01_2:PID,"}, #"A5.20.02" => {attr => {subType => "hvac.02"}}, #"A5.20.03" => {attr => {subType => "hvac.03"}}, @@ -327,6 +322,7 @@ my %EnO_eepConfig = ( "D2.03.10" => {attr => {subType => "windowHandle.10"}, GPLOT => "EnO_windowHandle:WindowHandle,"}, "D2.05.00" => {attr => {subType => "blindsCtrl.00", webCmd => "opens:stop:closes:position"}, GPLOT => "EnO_position4angle4:Position/AnglePos,"}, "D2.05.01" => {attr => {subType => "blindsCtrl.01", webCmd => "opens:stop:closes:position"}}, + "D2.05.02" => {attr => {subType => "blindsCtrl.00", webCmd => "opens:stop:closes:position"}, GPLOT => "EnO_position4angle4:Position/AnglePos,"}, "D2.06.01" => {attr => {subType => "multisensor.01"}, GPLOT => "EnO_temp4humi4:Temp/Humi,EnO_brightness4:Brightness,"}, "D2.10.00" => {attr => {subType => "roomCtrlPanel.00", webCmd => "setpointTemp"}, GPLOT => "EnO_D2-10-xx:Temp/SPT/Humi,"}, "D2.10.01" => {attr => {subType => "roomCtrlPanel.00", webCmd => "setpointTemp"}, GPLOT => "EnO_D2-10-xx:Temp/SPT/Humi,"}, @@ -350,7 +346,9 @@ my %EnO_eepConfig = ( "D2.50.10" => {attr => {subType => "heatRecovery.00", webCmd => "ventilation"}, GPLOT => "EnO_D2-50-xx:Temp/AirQuality,EnO_D2-50-xx_2:AirFlow/FanSpeed,"}, "D2.50.11" => {attr => {subType => "heatRecovery.00", webCmd => "ventilation"}, GPLOT => "EnO_D2-50-xx:Temp/AirQuality,EnO_D2-50-xx_2:AirFlow/FanSpeed,"}, "D2.A0.01" => {attr => {subType => "valveCtrl.00", defaultChannel => 0, webCmd => "opens:closes"}, GPLOT => "EnO_valveCtrl:Valve,"}, + "D2.B0.51" => {attr => {subType => "liquidLeakage.51"}, GPLOT => "EnO_liquidLeakage:LiquidLeakage,"}, "D5.00.01" => {attr => {subType => "contact", manufID => "7FF"}, GPLOT => "EnO_contact:Contact,"}, + "F6.01.01" => {attr => {subType => "switch", sensorMode => "pushbutton"}}, "F6.02.01" => {attr => {subType => "switch"}}, "F6.02.02" => {attr => {subType => "switch"}}, "F6.02.03" => {attr => {subType => "switch"}}, @@ -359,7 +357,9 @@ my %EnO_eepConfig = ( "F6.03.02" => {attr => {subType => "switch"}}, "F6.04.01" => {attr => {subType => "keycard"}, GPLOT => "EnO_keycard:Keycard,"}, #"F6.04.02" => {attr => {subType => "keycard.02"}, GPLOT => "EnO_keycard:Keycard,"}, + "F6.05.00" => {attr => {subType => "windSpeed.00"}}, "F6.05.01" => {attr => {subType => "liquidLeakage"}, GPLOT => "EnO_liquidLeakage:LiquidLeakage,"}, + "F6.05.02" => {attr => {subType => "smokeDetector.02"}}, "F6.10.00" => {attr => {subType => "windowHandle"}, GPLOT => "EnO_windowHandle:WindowHandle,"}, #"F6.10.01" => {attr => {subType => "windowHandle.01"}, GPLOT => "EnO_windowHandle:WindowHandle,"}, "F6.3F.7F" => {attr => {subType => "switch.7F"}}, @@ -373,7 +373,7 @@ my %EnO_eepConfig = ( "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 => "TF", teachMethod => "confirm", webCmd => "on:off"}}, "G5.ZZ.ZZ" => {attr => {subType => "PM101", manufID => "005"}, GPLOT => "EnO_motion:Motion,EnO_brightness4:Brightness,"}, - "L6.02.01" => {attr => {subType => "FRW", eep => "F6-02-01", manufID => "00D"}}, + "L6.02.01" => {attr => {subType => "smokeDetector.02", eep => "F6-05-02", manufID => "00D"}}, "ZZ.ZZ.ZZ" => {attr => {subType => "raw"}}, ); @@ -6443,8 +6443,35 @@ sub EnOcean_Set($@) return "Wrong parameter, choose GPSD [status 1 Byte hex]"; } + } elsif ($cmd eq "SEC") { + # secure telegram + if ($a[1] && $a[1] =~ m/^[\dA-Fa-f]{2,28}$/ && !(length($a[1]) % 2)) { + $data = uc($a[1]); + $rorg = "30"; + } else { + return "Wrong parameter, choose SEC [status 1 Byte hex]"; + } + + } elsif ($cmd eq "ENC") { + # secure telegram with encapsulation + if ($a[1] && $a[1] =~ m/^[\dA-Fa-f]{2,28}$/ && !(length($a[1]) % 2)) { + $data = uc($a[1]); + $rorg = "31"; + } else { + return "Wrong parameter, choose ENC [status 1 Byte hex]"; + } + + } elsif ($cmd eq "STE") { + # secure Teach-In + if ($a[1] && $a[1] =~ m/^[\dA-Fa-f]{2,28}$/ && !(length($a[1]) % 2)) { + $data = uc($a[1]); + $rorg = "35"; + } else { + return "Wrong parameter, choose STE [status 1 Byte hex]"; + } + } else { - return "Unknown argument $cmd, choose one of 1BS 4BS GPCD GPSD GPTI GPTR MSC RPS UTE VLD"; + return "Unknown argument $cmd, choose one of 1BS 4BS ENC GPCD GPSD GPTI GPTR MSC RPS SEC STE UTE VLD"; } if ($a[2]) { if ($a[2] !~ m/^[\dA-Fa-f]{2}$/) { @@ -6938,6 +6965,12 @@ sub EnOcean_Parse($$) Log3 $name, 2, "EnOcean $name security ERROR: $err"; return ""; } + } elsif ($rorg eq "35") { + # pass second teach-in telegram + + } else { + Log3 $name, 2, "EnOcean $name unsecure telegram locked"; + return ""; } if ($rorg eq "32") { if (defined $eep) { @@ -6955,12 +6988,6 @@ sub EnOcean_Parse($$) return ""; } } - } elsif ($rorg eq "35") { - # pass second teach-in telegram - - } else { - Log3 $name, 2, "EnOcean $name unsecure telegram locked"; - return ""; } } @@ -6996,29 +7023,58 @@ sub EnOcean_Parse($$) $st = $subtypeReading if (defined $subtypeReading); 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) + # RPS Telegram 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 ($st eq "FRW") { - # smoke detector Eltako FRW - if ($db[0] == 0x30) { - push @event, "3:battery:low"; - } elsif ($db[0] == 0x10) { - push @event, "3:alarm:smoke-alarm"; - $msg = "smoke-alarm"; - } elsif ($db[0] == 0) { - push @event, "3:alarm:off"; - push @event, "3:battery:ok"; - $msg = "off"; + if ($st eq "FRW" || $st eq "smokeDetector.02") { + # smoke detector + if (!exists($hash->{helper}{lastEvent}) || $hash->{helper}{lastEvent} != $db[0]) { + if ($db[0] == 0x30) { + push @event, "3:battery:low"; + } elsif ($db[0] == 0x10) { + push @event, "3:alarm:smoke-alarm"; + $msg = "smoke-alarm"; + } elsif ($db[0] == 0) { + push @event, "3:alarm:off"; + push @event, "3:battery:ok"; + $msg = "off"; + } + push @event, "3:$event:$msg"; + $hash->{helper}{lastEvent} = $db[0]; } - push @event, "3:$event:$msg"; + @{$hash->{helper}{alarmTimer}} = ($hash, 'alarm', 'dead_sensor', 1, 5); + @{$hash->{helper}{stateTimer}} = ($hash, 'state', 'dead_sensor', 1, 5); + RemoveInternalTimer($hash->{helper}{alarmTimer}); + RemoveInternalTimer($hash->{helper}{stateTimer}); + InternalTimer(gettimeofday() + 1320, 'EnOcean_readingsSingleUpdate', $hash->{helper}{alarmTimer}, 0); + InternalTimer(gettimeofday() + 1320, 'EnOcean_readingsSingleUpdate', $hash->{helper}{stateTimer}, 0); + + } elsif ($st eq "windSpeed.00") { + # wind speed threshold detector + if (!exists($hash->{helper}{lastEvent}) || $hash->{helper}{lastEvent} != $db[0]) { + if ($db[0] == 0x30) { + push @event, "3:battery:low"; + } elsif ($db[0] == 0x10) { + push @event, "3:windSpeed:on"; + $msg = "on"; + } elsif ($db[0] == 0) { + push @event, "3:windSpeed:off"; + push @event, "3:battery:ok"; + $msg = "off"; + } + push @event, "3:$event:$msg"; + $hash->{helper}{lastEvent} = $db[0]; + } + @{$hash->{helper}{windSpeedTimer}} = ($hash, 'windSpeed', 'dead_sensor', 1, 5); + @{$hash->{helper}{stateTimer}} = ($hash, 'state', 'dead_sensor', 1, 5); + RemoveInternalTimer($hash->{helper}{windSpeedTimer}); + RemoveInternalTimer($hash->{helper}{stateTimer}); + InternalTimer(gettimeofday() + 1320, 'EnOcean_readingsSingleUpdate', $hash->{helper}{windSpeedTimer}, 0); + InternalTimer(gettimeofday() + 1320, 'EnOcean_readingsSingleUpdate', $hash->{helper}{stateTimer}, 0); } elsif ($model eq "FAE14" || $model eq "FHK14" || $model eq "FHK61") { # heating/cooling relay FAE14, FHK14, untested @@ -8216,7 +8272,7 @@ sub EnOcean_Parse($$) push @event, "3:state:$rn"; } elsif ($st eq "vocSensor.01") { - # Gas Sensor, VOC Sensor (EEP A5-09-05) + # Gas Sensor, VOC Sensor (EEP A5-09-05, A5-09-0C) # [untested] # $db[3]_bit_7 ... $db[2]_bit_0 is the VOC concentration where 0 = 0 ppb ... 65535 = 65535 ppb # $db[1] is the VOC identification @@ -8259,6 +8315,15 @@ sub EnOcean_Parse($$) 24 => "2-Butanol", 25 => "2-Methylpropanol", 26 => "Diethyl Ether", + 27 => "Naphthalene", + 28 => "4-Phenylcyclohexene", + 29 => "Limonene", + 30 => "Tricloroethylene", + 31 => "Isovaleric Acid", + 32 => "Indole", + 33 => "Cadaverine", + 34 => "Putrescine", + 35 => "Caproic Acid", 255 => "Ozone", ); if (exists $vocID{$db[1]}) { @@ -8267,6 +8332,7 @@ sub EnOcean_Parse($$) push @event, "3:vocName:unknown"; } push @event, "3:concentration:$vocConc"; + push @event, "3:concentrationUnit:" . $db[0] & 4 ? 'ug/m3' : 'ppb'; push @event, "3:state:$vocConc"; } elsif ($st eq "particlesSensor.01") { @@ -9466,6 +9532,43 @@ sub EnOcean_Parse($$) push @event, "3:voltage:$voltage"; push @event, "3:state:C: $contact V: $vibration E: $lux U: $voltage"; + } elsif ($st eq "doorContact") { + # dual door contact (EEP A5-14-07, A5-14-08) + if (!exists($hash->{helper}{lastEvent}) || $hash->{helper}{lastEvent} ne $data) { + my $voltage = sprintf "%0.1f", $db[3] * 0.02; + my $doorContact = $db[0] & 4 ? 'open' : 'closed'; + my $lockContact = $db[0] & 2 ? 'unlocked' : 'locked'; + my $vibration = $db[0] & 1 ? 'on' : 'off'; + push @event, "3:voltage:$voltage"; + push @event, "3:contact:$doorContact"; + push @event, "3:block:$lockContact"; + push @event, "3:vibration:$vibration"; + push @event, "3:state:C: $doorContact B: $lockContact V: $vibration U: $voltage"; + $hash->{helper}{lastEvent} = $data; + } + CommandDeleteReading(undef, "$name alarm"); + @{$hash->{helper}{alarmTimer}} = ($hash, 'alarm', 'dead_sensor', 1, 5); + RemoveInternalTimer($hash->{helper}{alarmTimer}); + InternalTimer(gettimeofday() + 66, 'EnOcean_readingsSingleUpdate', $hash->{helper}{alarmTimer}, 0); + + } elsif ($st eq "windowContact") { + # window contact (EEP A5-14-09, A5-14-0A) + if (!exists($hash->{helper}{lastEvent}) || $hash->{helper}{lastEvent} ne $data) { + my $voltage = sprintf "%0.1f", $db[3] * 0.02; + my %window = (0 => 'closed', 1 => 'tilt', 2 => 'reserved', 3 => 'open'); + my $window = $window{(($db[0] & 6) >> 1)}; + my $vibration = $db[0] & 1 ? 'on' : 'off'; + push @event, "3:voltage:$voltage"; + push @event, "3:window:$window"; + push @event, "3:vibration:$vibration"; + push @event, "3:state:W: $window V: $vibration U: $voltage"; + $hash->{helper}{lastEvent} = $data; + } + CommandDeleteReading(undef, "$name alarm"); + @{$hash->{helper}{alarmTimer}} = ($hash, 'alarm', 'dead_sensor', 1, 5); + RemoveInternalTimer($hash->{helper}{alarmTimer}); + InternalTimer(gettimeofday() + 66, 'EnOcean_readingsSingleUpdate', $hash->{helper}{alarmTimer}, 0); + } elsif ($st =~ m/^digitalInput\.0[12]$/) { # Digital Input (EEP A5-30-01, A5-30-02) my $contact; @@ -10931,6 +11034,10 @@ sub EnOcean_Parse($$) } } + } elsif ($st eq "liquidLeakage.51") { + # liquid leakage sensor + push @event, "3:state:" . $db[0] & 3 ? 'wet' : 'dry'; + } elsif ($st eq "raw") { # raw push @event, "3:state:RORG: $rorg DATA: $data STATUS: $status ODATA: $odata"; @@ -10938,6 +11045,7 @@ sub EnOcean_Parse($$) for (my $dbCntr = 0; $dbCntr <= $#db; $dbCntr++) { push @event, "3:DB_" . $dbCntr . ":" . $db[$dbCntr]; } + } else { # unknown devices push @event, "3:state:$data"; @@ -15234,79 +15342,73 @@ sub EnOcean_sec_parseTeachIn($$$$) { my $key1; # First part of private key my $key2; # Second part of private key - # Extract byte fields from telegram - # TEACH_IN_INFO, SLF, RLC/KEY/variable - $telegram =~ /^(..)(..)(.*)/; # TODO Parse error handling? - my $teach_bin = unpack('B8',pack('H2', $1)); # Parse as ASCII HEX, unpack to bitstring - my $slf_bin = unpack('B8',pack('H2', $2)); # Parse as ASCII HEX, unpack to bitstring - my $crypt = $3; + # Extract byte fields from telegram + # TEACH_IN_INFO, SLF, RLC/KEY/variable + $telegram =~ /^(..)(..)(.*)/; # TODO Parse error handling? + my $teach_bin = unpack('B8',pack('H2', $1)); # Parse as ASCII HEX, unpack to bitstring + my $slf_bin = unpack('B8',pack('H2', $2)); # Parse as ASCII HEX, unpack to bitstring + my $crypt = $3; - # Extract bit fields from teach-in info field - # IDX, CNT, PSK, TYPE, INFO - $teach_bin =~ /(..)(..)(.)(.)(..)/; # TODO Parse error handling? - my $idx = unpack('C',pack('B8', '000000'.$1)); # Padd to byte, parse as unsigned char - my $cnt = unpack('C',pack('B8', '000000'.$2)); # Padd to byte, parse as unsigned char - my $psk = $3; - my $type = $4; - my $info = unpack('C',pack('B8', '000000'.$5)); # Padd to byte, parse as unsigned char + # Extract bit fields from teach-in info field + # IDX, CNT, PSK, TYPE, INFO + $teach_bin =~ /(..)(..)(.)(.)(..)/; # TODO Parse error handling? + my $idx = unpack('C',pack('B8', '000000'.$1)); # Padd to byte, parse as unsigned char + my $cnt = unpack('C',pack('B8', '000000'.$2)); # Padd to byte, parse as unsigned char + my $psk = $3; + my $type = $4; + my $info = unpack('C',pack('B8', '000000'.$5)); # Padd to byte, parse as unsigned char - # Extract bit fields from SLF field - # RLC_ALGO, RLC_TX, MAC_ALGO, DATA_ENC - $slf_bin =~ /(..)(.)(..)(...)/; # TODO Parse error handling? - my $rlc_algo = unpack('C',pack('B8', '000000'.$1)); # Padd to byte, parse as unsigned char - my $rlc_tx = $2; - my $mac_algo = unpack('C',pack('B8', '000000'.$3)); # Padd to byte, parse as unsigned char - my $data_enc = unpack('C',pack('B8', '00000'.$4)); # Padd to byte, parse as unsigned char + # Extract bit fields from SLF field + # RLC_ALGO, RLC_TX, MAC_ALGO, DATA_ENC + $slf_bin =~ /(..)(.)(..)(...)/; # TODO Parse error handling? + my $rlc_algo = unpack('C',pack('B8', '000000'.$1)); # Padd to byte, parse as unsigned char + my $rlc_tx = $2; + my $mac_algo = unpack('C',pack('B8', '000000'.$3)); # Padd to byte, parse as unsigned char + my $data_enc = unpack('C',pack('B8', '00000'.$4)); # Padd to byte, parse as unsigned char - #print "IDX: $idx, CNT: $cnt, PSK: $psk, TYPE: $type, INFO: $info\n" + # The teach-in information is split in two telegrams due to the ERP1 limitations on telegram length + # So we should get a telegram with index 0 and count 2 with the first half of the infos needed + if ($idx == 0 && $cnt == 2) { + # First part of the teach in message - # The teach-in information is split in two telegrams due to the ERP1 limitations on telegram length - # So we should get a telegram with index 0 and count 2 with the first half of the infos needed - if ($idx == 0 && $cnt == 2) { - # First part of the teach in message - #print "First part of 2 part teach in message received\n"; - #print "RLC_ALGO: $rlc_algo, RLC_TX: $rlc_tx, MAC_ALGO: $mac_algo, DATA_ENC: $data_enc\n"; - #print "RLC and KEY are ". ($psk == 1 ? "" : "not") . " encrypted\n"; - #print "Application is ". ($type == 1 ? "a PTM" : "non-specfic") . "\n"; + # Decode teach in type + if ($type == 0) { + # 1BS, 4BS, UTE or GP teach-in expected + if ($info == 0) { + $attr{$name}{comMode} = "uniDir"; + $attr{$name}{secMode} = "rcv"; + } else { + $attr{$name}{comMode} = "biDir"; + $attr{$name}{secMode} = "biDir"; + } + $hash->{helper}{teachInWait} = "STE"; + } else { + # switch teach-in + $attr{$name}{teachMethod} = 'STE'; + if ($info == 0) { + $attr{$name}{comMode} = "uniDir"; + $attr{$name}{eep} = "D2-03-00"; + $attr{$name}{manufID} = "7FF"; + $attr{$name}{secMode} = "rcv"; + foreach my $attrCntr (keys %{$EnO_eepConfig{"D2.03.00"}{attr}}) { + $attr{$name}{$attrCntr} = $EnO_eepConfig{"D2.03.00"}{attr}{$attrCntr}; + } + readingsSingleUpdate($hash, "teach", "STE teach-in accepted EEP D2-03-00 Manufacturer: " . $EnO_manuf{"7FF"}, 1); + Log3 $name, 2, "EnOcean $name STE teach-in accepted EEP D2-03-00 Rocker A Manufacturer: " . $EnO_manuf{"7FF"}; + } else { + $attr{$name}{comMode} = "uniDir"; + $attr{$name}{eep} = "D2-03-00"; + $attr{$name}{manufID} = "7FF"; + $attr{$name}{secMode} = "rcv"; + foreach my $attrCntr (keys %{$EnO_eepConfig{"D2.03.00"}{attr}}) { + $attr{$name}{$attrCntr} = $EnO_eepConfig{"D2.03.00"}{attr}{$attrCntr}; + } + readingsSingleUpdate($hash, "teach", "STE teach-in accepted EEP D2-03-00 Manufacturer: " . $EnO_manuf{"7FF"}, 1); + Log3 $name, 2, "EnOcean $name STE teach-in accepted EEP D2-03-00 Rocker B Manufacturer: " . $EnO_manuf{"7FF"}; + } + } - # Decode teach in type - if ($type == 0) { - # 1BS, 4BS, UTE or GP teach-in expected - if ($info == 0) { - $attr{$name}{comMode} = "uniDir"; - $attr{$name}{secMode} = "rcv"; - } else { - $attr{$name}{comMode} = "biDir"; - $attr{$name}{secMode} = "biDir"; - } - $hash->{helper}{teachInWait} = "STE"; - } else { - # switch teach-in - $attr{$name}{teachMethod} = 'STE'; - if ($info == 0) { - $attr{$name}{comMode} = "uniDir"; - $attr{$name}{eep} = "D2-03-00"; - $attr{$name}{manufID} = "7FF"; - $attr{$name}{secMode} = "rcv"; - foreach my $attrCntr (keys %{$EnO_eepConfig{"D2.03.00"}{attr}}) { - $attr{$name}{$attrCntr} = $EnO_eepConfig{"D2.03.00"}{attr}{$attrCntr}; - } - readingsSingleUpdate($hash, "teach", "STE teach-in accepted EEP D2-03-00 Manufacturer: " . $EnO_manuf{"7FF"}, 1); - Log3 $name, 2, "EnOcean $name STE teach-in accepted EEP D2-03-00 Rocker A Manufacturer: " . $EnO_manuf{"7FF"}; - } else { - $attr{$name}{comMode} = "uniDir"; - $attr{$name}{eep} = "D2-03-00"; - $attr{$name}{manufID} = "7FF"; - $attr{$name}{secMode} = "rcv"; - foreach my $attrCntr (keys %{$EnO_eepConfig{"D2.03.00"}{attr}}) { - $attr{$name}{$attrCntr} = $EnO_eepConfig{"D2.03.00"}{attr}{$attrCntr}; - } - readingsSingleUpdate($hash, "teach", "STE teach-in accepted EEP D2-03-00 Manufacturer: " . $EnO_manuf{"7FF"}, 1); - Log3 $name, 2, "EnOcean $name STE teach-in accepted EEP D2-03-00 Rocker B Manufacturer: " . $EnO_manuf{"7FF"}; - } - } - - # Decode RLC algorithm and extract RLC and private key (only first part most likely) + # Decode RLC algorithm and extract RLC and private key (only first part most likely) if ($rlc_algo == 0) { # No RLC used in telegram or internally in memory, use case untested return ("Secure modes without RLC not tested or supported", undef); @@ -15406,9 +15508,11 @@ sub EnOcean_sec_parseTeachIn($$$$) { return ("Could not parse data encryption information, $data_enc", undef); } - # Ok we got a lots of infos and the first part of the private key - return (undef, "part1: $name"); - } elsif ($idx == 1 && $cnt == 0) { + $hash->{helper}{teachInSTE} = $cnt - 1; + # Ok we got a lots of infos and the first part of the private key + return (undef, "part1: $name"); + + } elsif ($idx == 1 && exists($hash->{helper}{teachInSTE})) { # Second part of the teach-in telegrams # Extract byte fields from telegram @@ -15443,7 +15547,7 @@ sub EnOcean_sec_parseTeachIn($$$$) { Log3 $name, $logLevel, "EnOcean $name $response"; } } - # We're done + delete $hash->{helper}{teachInSTE}; return (undef, "part2: $name"); } @@ -15647,124 +15751,116 @@ sub EnOcean_sec_generateMAC($$$) { # lack of hardware suporting this. # sub EnOcean_sec_convertToNonsecure($$$) { - my ($hash, $rorg, $crypt_data) = @_; - my $name = $hash->{NAME}; - if ($cryptFunc == 0) { - return ("Cryptographic functions are not available", undef, undef); - } - my $private_key; + my ($hash, $rorg, $crypt_data) = @_; + my $name = $hash->{NAME}; + if ($cryptFunc == 0) { + return ("Cryptographic functions are not available", undef, undef); + } + my $private_key; + # Prefix of pattern to extract the different cryptographic infos + my $crypt_pattern = "^(.*)";; + # Flags and infos for fields to expect + my $expect_rlc = 0; + my $expect_mac = 0; + my $mac_len; + my $expect_enc = 0; - # Prefix of pattern to extract the different cryptographic infos - my $crypt_pattern = "^(.*)";; - - # Flags and infos for fields to expect - my $expect_rlc = 0; - my $expect_mac = 0; - my $mac_len; - my $expect_enc = 0; - - # Check if the RORG is supported - if ($rorg ne '30') { - return ("RORG $rorg unsupported", undef, undef); + # Check if RLC is transmitted and when, which length to expect + if($attr{$name}{rlcTX} eq 'true') { + # Message should contain RLC + if ($attr{$name}{rlcAlgo} eq '2++') { + $crypt_pattern .= "(....)"; + $expect_rlc = 1; + } elsif ($attr{$name}{rlcAlgo} eq '3++') { + $crypt_pattern .= "(......)"; + $expect_rlc = 1; + } else { + # RLC_TX but no info on RLC length + return ("RLC_TX and RLC_ALGO inconsistent", undef, undef); } - #$attr{$name}{rlcAlgo} = '2++'; - # Check if RLC is transmitted and when, which length to expect - if($attr{$name}{rlcTX} eq 'true') { - # Message should contain RLC - if ($attr{$name}{rlcAlgo} eq '2++') { - $crypt_pattern .= "(....)"; - $expect_rlc = 1; - } elsif ($attr{$name}{rlcAlgo} eq '3++') { - $crypt_pattern .= "(......)"; - $expect_rlc = 1; - } else { - # RLC_TX but no info on RLC length - return ("RLC_TX and RLC_ALGO inconsistent", undef, undef); - } - } + } - # Check what length of MAC to expect - if($attr{$name}{macAlgo} eq '3') { - $crypt_pattern .= "(......)"; - $mac_len = 3; - $expect_mac = 1; - } elsif ($attr{$name}{macAlgo} eq '4') { - $crypt_pattern .= "(........)"; - $mac_len = 4; - $expect_mac = 1; - } else { - # According to the specification it's possible to transmit no MAC, bt we don't implement this for now - return ("Secure mode messages without MAC unsupported", undef, undef); - } + # Check what length of MAC to expect + if($attr{$name}{macAlgo} eq '3') { + $crypt_pattern .= "(......)"; + $mac_len = 3; + $expect_mac = 1; + } elsif ($attr{$name}{macAlgo} eq '4') { + $crypt_pattern .= "(........)"; + $mac_len = 4; + $expect_mac = 1; + } else { + # According to the specification it's possible to transmit no MAC, bt we don't implement this for now + return ("Secure mode messages without MAC unsupported", undef, undef); + } - # Suffix for crypt pattern - $crypt_pattern .= '$'; + # Suffix for crypt pattern + $crypt_pattern .= '$'; - #print "Crypt_pattern: $crypt_pattern\n"; + # Extract byte fields from message payload + $crypt_data =~ /$crypt_pattern/; + my $data_enc = $1; + my $dataLength = length($data_enc); + return ("Telegrams with a length of more than 16 bytes are not supported", undef, undef) if ($dataLength > 32); + my $rlc; + my $mac; + if ($expect_rlc == 1 && $expect_mac == 1) { + $rlc = $2; + $mac = $3; + } elsif ($expect_rlc == 0 && $expect_mac == 1) { + $mac = $2; + } - # Extract byte fields from message payload - $crypt_data =~ /$crypt_pattern/; - my $data_enc = $1; - my $rlc; - my $mac; - if ($expect_rlc == 1 && $expect_mac == 1) { - $rlc = $2; - $mac = $3; - } elsif ($expect_rlc == 0 && $expect_mac == 1) { - $mac = $2; - } + Log3 $name, 5, "EnOcean $name EnOcean_sec_convertToNonsecure RORG: $rorg DATA_ENC: $data_enc"; + if ($expect_rlc == 1) { + Log3 $name, 5, "EnOcean $name EnOcean_sec_convertToNonsecure RLC: $rlc"; + }; + Log3 $name, 5, "EnOcean $name EnOcean_sec_convertToNonsecure MAC: $mac"; - #print "DATA: $data_enc\n"; - Log3 $name, 5, "EnOcean $name EnOcean_sec_convertToNonsecure DATA: $data_enc"; - #if ($expect_rlc == 1) { print "RLC: $rlc\n";}; - if ($expect_rlc == 1) { Log3 $name, 5, "EnOcean $name EnOcean_sec_convertToNonsecure RLC: $rlc";}; - #print "MAC: $mac\n"; - Log3 $name, 5, "EnOcean $name EnOcean_sec_convertToNonsecure MAC: $mac"; + # TODO RLC could be transmitted with data, could not test this + #if(!defined($rlc)) { + # print "No RLC in message, using stored value\n"; + # $rlc = getRLC($senderID); + #} - # TODO RLC could be transmitted with data, could not test this - #if(!defined($rlc)) { - # print "No RLC in message, using stored value\n"; - # $rlc = getRLC($senderID); - #} + # Maximum RLC search window is 128 + foreach my $rlc_window (0..128) { + #print "Trying RLC offset $rlc_window\n"; + # Fetch stored RLC + $rlc = EnOcean_sec_getRLC($hash, "rlcRcv"); + # Fetch private Key for VAES + if ($attr{$name}{keyRcv} =~ /[\dA-F]{32}/) { + $private_key = pack('H32',$attr{$name}{keyRcv}); + } else { + return ("private key wrong, please teach-in the device new", undef, undef); + } - # Maximum RLC search window is 128 - foreach my $rlc_window (0..128) { - #print "Trying RLC offset $rlc_window\n"; + # Generate and check MAC over RORG+DATA+RLC fields + if ($mac eq EnOcean_sec_generateMAC($private_key, $rorg.$data_enc.$rlc, $mac_len)) { + #print "RLC verfified\n"; + # Expand RLC to 16byte + my $rlc_expanded = pack('H32',$rlc); - # Fetch stored RLC - $rlc = EnOcean_sec_getRLC($hash, "rlcRcv"); + # Expand data to 16byte + my $data_expanded = pack('H32',$data_enc); - # Fetch private Key for VAES - - if ($attr{$name}{keyRcv} =~ /[\dA-F]{32}/) { - $private_key = pack('H32',$attr{$name}{keyRcv}); - } else { - return ("private key wrong, please teach-in the device new", undef, undef); - } - - # Generate and check MAC over RORG+DATA+RLC fields - if($mac eq EnOcean_sec_generateMAC($private_key, $rorg.$data_enc.$rlc, $mac_len)) { - #print "RLC verfified\n"; - - # Expand RLC to 16byte - my $rlc_expanded = pack('H32',$rlc); - - # Expand data to 16byte - my $data_expanded = pack('H32',$data_enc); - - # Decode data using VAES - my $data_dec = EnOcean_sec_decodeVAES($rlc_expanded, $private_key, $data_expanded); - - # Extract one nibble of data - my $data_end = unpack('H32', $data_dec); - $data_end =~ /^.(.)/; - - #print "MSG: $1\n"; - return (undef, '32', "0" . uc($1)); - } - } - # Couldn't verify or decrypt message in RLC window - return ("Can't verify or decrypt telegram", undef, undef); + # Decode data using VAES + my $data_dec = EnOcean_sec_decodeVAES($rlc_expanded, $private_key, $data_expanded); + my $data_end = unpack('H32', $data_dec); + if ($rorg eq '30') { + # Extract one nibble of data + $data_end =~ /^.(.)/; + return (undef, '32', "0" . uc($1)); + } else { + $dataLength -= 2; + $data_end =~ /^(..)(.{$dataLength})/; + Log3 $name, 5, "EnOcean $name EnOcean_sec_convertToNonsecure RORG: " . uc($1) . " DATA: " . uc($2); + return (undef, uc($1), uc($2)); + } + } + } + # Couldn't verify or decrypt message in RLC window + return ("Can't verify or decrypt telegram", undef, undef); } # @@ -18530,7 +18626,7 @@ EnOcean_Delete($$)

-
  • Pushbutton Switch, Pushbutton Input Module (EEP F6-02-01 ... F6-02-02)
    +
  • Pushbutton Switch, Pushbutton Input Module (EEP F6-02-01 ... F6-02-02, F6-01-01)
    [Eltako FT55, FSM12, FSM61, FTS12]
    • A0
    • @@ -18595,20 +18691,6 @@ EnOcean_Delete($$)

      -
    • Smoke Detector (EEP F6-02-01 ... F6-02-02)
      - [Eltako FRW]
      -
        -
      • smoke-alarm
      • -
      • off
      • -
      • alarm: smoke-alarm|off
      • -
      • battery: low|ok
      • -
      • buttons: pressed|released
      • -
      • state: smoke-alarm|off
      • -

      - Set attr subType to FRW manually. -
    • -

      -
    • Heating/Cooling Relay (EEP F6-02-01 ... F6-02-02)
      [Eltako FAE14, FHK14, untested]
        @@ -18635,6 +18717,19 @@ EnOcean_Delete($$)

        +
      • Wind Speed Threshold Detector (EEP F6-05-00)
        +
          +
        • dead_sensor
        • +
        • on
        • +
        • off
        • +
        • windSpeed: dead_sensor|on|off
        • +
        • battery: low|ok
        • +
        • state: dead_sensor|on|off
        • +

        + Set attr subType to windSpeed.00 manually. +
      • +

        +
      • Liquid Leakage Sensor (EEP F6-05-01)
        [untested]
          @@ -18646,6 +18741,20 @@ EnOcean_Delete($$)

          +
        • Smoke Detector (EEP F6-05-02)
          + [Eltako FRW]
          +
            +
          • dead_sensor
          • +
          • smoke-alarm
          • +
          • off
          • +
          • alarm: dead_sensor|smoke-alarm|off
          • +
          • battery: low|ok
          • +
          • state: dead_sensor|smoke-alarm|off
          • +

          + Set attr subType to smokeDetector.02 manually. +
        • +

          +
        • Window Handle (EEP F6-10-00, D2-03-10)
          [HOPPE SecuSignal, Eltako FHF, Eltako FTKE]
            @@ -18903,12 +19012,13 @@ EnOcean_Delete($$)

            -
          • Gas Sensor, Volatile organic compounds (VOC) Sensor (EEP A5-09-05)
            +
          • Gas Sensor, Volatile organic compounds (VOC) Sensor (EEP A5-09-05, A5-09-0C)
            [untested]
              -
            • concentration: c/ppb (Sensor Range: c = 0 ppb ... 655350 ppb)
            • +
            • concentration: c/[unit] (Sensor Range: c = 0 ... 655350
            • +
            • concentrationUnit: ppb|μ/m3
            • vocName: Name of last measured VOC
            • -
            • state: c/ppb
            • +
            • state: c/[unit]

            The attr subType must be vocSensor.01. This is done if the device was created by autocreate. @@ -19497,7 +19607,7 @@ EnOcean_Delete($$)


          • -
          • Multi-Func Sensor (EEP A5-14-01 ... A5-14-06)
            +
          • Multi-Func Sensor (EEP A5-14-01 ... A5-14-06)
            [untested]
            • C: open|closed V: on|off E: E/lx U: U/V
            • @@ -19513,6 +19623,37 @@ EnOcean_Delete($$)

              +
            • Dual Door Contact (EEP A5-14-07, A5-14-08)
              + [untested]
              +
                +
              • C: open|closed B: unlocked|locked V: on|off U: U/V
              • +
              • alarm: dead_sensor
              • +
              • block: unlocked|locked
              • +
              • contact: open|closed
              • +
              • vibration: on|off
              • +
              • voltage: U/V (Sensor Range: U = 0 V ... 5.0 V)
              • +
              • state: C: open|closed B: unlocked|locked V: on|off U: U/V
              • +

              + The attr subType must be doorContact. This is done if the device was + created by autocreate. +
            • +

              + +
            • Window/Door Contact (EEP A5-14-09, A5-14-0A)
              + [untested]
              +
                +
              • W: open|tilt|closed B: unlocked|locked V: on|off U: U/V
              • +
              • alarm: dead_sensor
              • +
              • vibration: on|off
              • +
              • voltage: U/V (Sensor Range: U = 0 V ... 5.0 V)
              • +
              • window: open|tilt|closed
              • +
              • state: W: open|tilt|closed V: on|off U: U/V
              • +

              + The attr subType must be windowContact. This is done if the device was + created by autocreate. +
            • +

              +
            • Battery Powered Actuator (EEP A5-20-01)
              [Kieback&Peter MD15-FTL-xx]
                @@ -20187,6 +20328,18 @@ EnOcean_Delete($$)

                +
              • Liquid Leakage Sensor (EEP D2-B0-51)
                + [untested]
                +
                  +
                • dry
                • +
                • wet
                • +
                • state: dry|wet
                • +

                + The attr subType must be liquidLeakage.51. This is done if the device was + created by autocreate. +
              • +

                +
              • Generic Profiles
                • <00...64>-<channel name>: <value>
                • @@ -20203,8 +20356,8 @@ EnOcean_Delete($$)
                • RAW Command
                    -
                  • RORG: 1BS|4BS|MCS|RPS|UTE|VLD
                  • -
                  • dataSent: data (Range: 1-byte hex ... 28-byte hex)
                  • +
                  • RORG: 1BS|4BS|ENC|MCS|RPS|SEC|STE|UTE|VLD
                  • +
                  • dataSent: data (Range: 1 Byte hex ... 512 Byte hex)
                  • statusSent: status (Range: 0x00 ... 0xFF)
                  • state: RORG: rorg DATA: data STATUS: status ODATA: odata