10_EnOcean.pm: new Profil EEP D2-06-40, new model Eltako_DSZ14DRSZ, Eltako_FSR14M-2x, mew inofficial EEP P5-38-08, new Signal Telegram functions, preparatory work for PSK support and extended chrypt functions

git-svn-id: https://svn.fhem.de/fhem/trunk@27785 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
klaus.schauer
2023-07-21 04:47:47 +00:00
parent b1598e2151
commit 4aa2317cd6

View File

@@ -70,10 +70,10 @@ my %EnO_rorgname = (
"D5" => "contact", # 1BS, org 06 "D5" => "contact", # 1BS, org 06
"F6" => "switch", # RPS, org 05 "F6" => "switch", # RPS, org 05
"30" => "SEC", # secure telegram "30" => "SEC", # secure telegram
"31" => "ENC", # secure telegram with encapsulation "31" => "ENC", # SEC_R secure telegram with encapsulation
"32" => "SECD", # decrypted secure telegram "32" => "SECD", # SEC_D decrypted secure telegram
"33" => "SECCDM", # secure chained data message "33" => "SECCDM", # SEC_CDM secure chained data message
"35" => "STE", # secure Teach-In "35" => "STE", # SEC_TI secure Teach-In
"40" => "CDM", # chained data message "40" => "CDM", # chained data message
); );
@@ -396,6 +396,7 @@ my %EnO_eepConfig = (
"D2.05.04" => {attr => {subType => "blindsCtrl.00", defaultChannel => 1, webCmd => "opens:stop:closes:position"}, GPLOT => "EnO_position4angle4:Position/AnglePos,"}, "D2.05.04" => {attr => {subType => "blindsCtrl.00", defaultChannel => 1, webCmd => "opens:stop:closes:position"}, GPLOT => "EnO_position4angle4:Position/AnglePos,"},
"D2.05.05" => {attr => {subType => "blindsCtrl.01", webCmd => "opens:stop:closes:position"}}, "D2.05.05" => {attr => {subType => "blindsCtrl.01", webCmd => "opens:stop:closes:position"}},
"D2.06.01" => {attr => {subType => "multisensor.01"}, GPLOT => "EnO_temp4humi4:Temp/Humi,EnO_brightness4:Brightness,"}, "D2.06.01" => {attr => {subType => "multisensor.01"}, GPLOT => "EnO_temp4humi4:Temp/Humi,EnO_brightness4:Brightness,"},
"D2.06.40" => {attr => {subType => "multisensor.40"}},
"D2.06.50" => {attr => {subType => "multisensor.50"}}, "D2.06.50" => {attr => {subType => "multisensor.50"}},
"D2.10.00" => {attr => {subType => "roomCtrlPanel.00", webCmd => "setpointTemp"}, GPLOT => "EnO_D2-10-xx:Temp/SPT/Humi,"}, "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,"}, "D2.10.01" => {attr => {subType => "roomCtrlPanel.00", webCmd => "setpointTemp"}, GPLOT => "EnO_D2-10-xx:Temp/SPT/Humi,"},
@@ -457,6 +458,7 @@ my %EnO_eepConfig = (
"M5.38.08" => {attr => {subType => "gateway", eep => "A5-38-08", gwCmd => "switching", manufID => "00D", webCmd => "on:off"}}, "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"}}, "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"}}, "O5.38.08" => {attr => {subType => "gateway", comMode => "confirm", eep => "A5-38-08", gwCmd => "switching", manufID => "00D", model => "Eltako_FSR14", teachMethod => "confirm", webCmd => "on:off"}},
"P5.38.08" => {attr => {subType => "autoMeterReading.01", comMode => "confirm", eep => "A5-38-08", eventMap => "on:B0 off:BI", gwCmd => "switching", manufID => "00D", model => "Eltako_FSR14M", subTypeSet => "gateway", teachMethod => "confirm", webCmd => "on:off"}, GPLOT => "EnO_power4energy4:Power/Energie,"},
"G5.ZZ.ZZ" => {attr => {subType => "PM101", manufID => "005"}, GPLOT => "EnO_motion:Motion,EnO_brightness4:Brightness,"}, "G5.ZZ.ZZ" => {attr => {subType => "PM101", manufID => "005"}, GPLOT => "EnO_motion:Motion,EnO_brightness4:Brightness,"},
"G6.02.01" => {attr => {subType => "switch", destinationID => "unicast", eep => "F6-02-01", manufID => "00D", model => "Eltako_F4CT55", sensorMode => 'pushbutton', subTypeSet => "switch"}}, "G6.02.01" => {attr => {subType => "switch", destinationID => "unicast", eep => "F6-02-01", manufID => "00D", model => "Eltako_F4CT55", sensorMode => 'pushbutton', subTypeSet => "switch"}},
"L6.02.01" => {attr => {subType => "smokeDetector.02", eep => "F6-05-02", manufID => "00D"}}, "L6.02.01" => {attr => {subType => "smokeDetector.02", eep => "F6-05-02", manufID => "00D"}},
@@ -488,6 +490,7 @@ my %EnO_extendedRemoteFunctionCode = (
); );
my %EnO_models = ( my %EnO_models = (
"Eltako_DSZ14DRSZ" => {attr => {manufID => "00D"}},
"Eltako_FAE14" => {attr => {manufID => "00D"}}, "Eltako_FAE14" => {attr => {manufID => "00D"}},
"Eltako_FAH60" => {attr => {manufID => "00D"}}, "Eltako_FAH60" => {attr => {manufID => "00D"}},
"Eltako_FBH55SB" => {attr => {manufID => "00D"}}, "Eltako_FBH55SB" => {attr => {manufID => "00D"}},
@@ -501,9 +504,10 @@ my %EnO_models = (
"Eltako_FSB61" => {attr => {manufID => "00D"}}, "Eltako_FSB61" => {attr => {manufID => "00D"}},
"Eltako_FSB70" => {attr => {manufID => "00D"}}, "Eltako_FSB70" => {attr => {manufID => "00D"}},
"Eltako_FSB_ACK" => {attr => {manufID => "00D"}}, "Eltako_FSB_ACK" => {attr => {manufID => "00D"}},
"Eltako_FSR14" => {attr => {manufID => "00D"}},
"Eltako_FSM12" => {attr => {manufID => "00D"}}, "Eltako_FSM12" => {attr => {manufID => "00D"}},
"Eltako_FSM61" => {attr => {manufID => "00D"}}, "Eltako_FSM61" => {attr => {manufID => "00D"}},
"Eltako_FSR14" => {attr => {manufID => "00D"}},
"Eltako_FSR14M" => {attr => {manufID => "00D"}},
"Eltako_FT55" => {attr => {manufID => "00D"}}, "Eltako_FT55" => {attr => {manufID => "00D"}},
"Eltako_FTS12" => {attr => {manufID => "00D"}}, "Eltako_FTS12" => {attr => {manufID => "00D"}},
"Eltako_FUD14" => {attr => {manufID => "00D"}}, "Eltako_FUD14" => {attr => {manufID => "00D"}},
@@ -835,7 +839,7 @@ sub EnOcean_Initialize($) {
"releasedChannel:A,B,C,D,I,0,auto repeatingAllowed:yes,no remoteCode remoteEEP remoteID remoteManufID " . "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 " . "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 rotationSpeed:select,high,low " . "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 " . "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 " . "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 " . "serviceOn:no,yes setCmdTrigger:man,refDev setpointRefDev setpointSummerMode:slider,0,5,100 settingAccuracy:high,low " .
"signal:off,on signOfLife:off,on signOfLifeInterval setpointTempRefDev shutTime shutTimeCloses subDef " . "signal:off,on signOfLife:off,on signOfLifeInterval setpointTempRefDev shutTime shutTimeCloses subDef " .
@@ -1982,7 +1986,7 @@ sub EnOcean_Set($@) {
# control set actions # control set actions
# $updateState = -1: no set commands available e. g. sensors # $updateState = -1: no set commands available e. g. sensors
# 0: execute set commands # 0: execute set commands
# 1: execute set commands and and update reading state # 1: execute set commands and update reading state
# 2: execute set commands delayed # 2: execute set commands delayed
# 3: internal command # 3: internal command
my $updateState = AttrVal($name, "comMode", "uniDir") eq "uniDir" ? 1 : 0; my $updateState = AttrVal($name, "comMode", "uniDir") eq "uniDir" ? 1 : 0;
@@ -2522,7 +2526,7 @@ sub EnOcean_Set($@) {
} elsif ($switchCmd eq "teachInSec") { } elsif ($switchCmd eq "teachInSec") {
($err, $subDef) = EnOcean_AssignSenderID(undef, $hash, "subDef", "confirm"); ($err, $subDef) = EnOcean_AssignSenderID(undef, $hash, "subDef", "confirm");
($err, $response, $logLevel) = EnOcean_sec_createTeachIn(undef, $hash, "uniDir", "VAES", "D2-03-00", 3, ($err, $response, $logLevel) = EnOcean_sec_createTeachIn(undef, $hash, "uniDir", "VAES", "D2-03-00", 3,
"2++", "false", "encryption", $subDef, $destinationID); "2++", "false", "encryption", 'A', $subDef, $destinationID);
if ($err) { if ($err) {
Log3 $name, $logLevel, "EnOcean $name Error: $err"; Log3 $name, $logLevel, "EnOcean $name Error: $err";
return $err; return $err;
@@ -6035,6 +6039,36 @@ sub EnOcean_Set($@) {
return "Unknown argument $cmd, choose one of $cmdList"; return "Unknown argument $cmd, choose one of $cmdList";
} }
} elsif ($st eq "multisensor.40") {
# Multisensor Lockable Windows Handle
# (D2-06-40)
$rorg = "D2";
$updateState = 0;
if ($cmd eq "block") {
# set unlock allowance info
if (defined $a[1]) {
if ($a[1] =~ m/^(lock|unlock)$/) {
readingsSingleUpdate($hash, 'block', $a[1], 1);
Log3 $name, 3, "EnOcean set $name $a[1]";
shift(@a);
} else {
return "Usage: $a[1] is wrong.";
}
} else {
return "Usage: argument needed.";
}
} elsif ($cmd eq "cryptTest") {
my $dataOut = EnOcean_sec_VAES('01020304', 'E50880CF67790D5D66AA7F3B7AD77A3F', 'D1000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D', 1);
my $dataCheck = $dataOut eq 'BB17C17A05CAF5575DE208302FB572A0FD3A4434A41096F102E60DC20D777A' ? 'ok' : 'error';
Log3 undef, 3, "EnOcean_sec_VAES: encypt dataCheck: $dataCheck dataOut: $dataOut";
$dataOut = EnOcean_sec_VAES('01020304', 'E50880CF67790D5D66AA7F3B7AD77A3F', 'BB17C17A05CAF5575DE208302FB572A0FD3A4434A41096F102E60DC20D777A', 1);
$dataCheck = $dataOut eq 'D1000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D' ? 'ok' : 'error';
Log3 undef, 3, "EnOcean_sec_VAES: decrypt dataCheck: $dataCheck dataOut: $dataOut";
} else {
$cmdList .= "block:lock,unlock cryptTest";
return "Unknown argument $cmd, choose one of $cmdList";
}
} elsif ($st eq "roomCtrlPanel.00") { } elsif ($st eq "roomCtrlPanel.00") {
# Room Control Panel # Room Control Panel
# (D2-10-00 - D2-10-02) # (D2-10-00 - D2-10-02)
@@ -7206,11 +7240,11 @@ sub EnOcean_Set($@) {
} }
} elsif ($cmd eq "MSC") { } elsif ($cmd eq "MSC") {
# MSC Telegram # MSC Telegram
if ($a[1] && $a[1] =~ m/^[\dA-Fa-f]{2,28}$/ && !(length($a[1]) % 2)) { if ($a[1] && $a[1] =~ m/^[\dA-Fa-f]{2,128}$/ && !(length($a[1]) % 2)) {
$data = uc($a[1]); $data = uc($a[1]);
$rorg = "D1"; $rorg = "D1";
} else { } else {
return "Wrong parameter, choose MSC <data 1 ... 14 Byte hex> [status 1 Byte hex]"; return "Wrong parameter, choose MSC <data 1 ... 128 Byte hex> [status 1 Byte hex]";
} }
} elsif ($cmd eq "UTE") { } elsif ($cmd eq "UTE") {
# UTE Telegram # UTE Telegram
@@ -10323,10 +10357,7 @@ sub EnOcean_Parse($$) {
InternalTimer(gettimeofday() + AttrVal($name, "signOfLifeInterval", 1320), 'EnOcean_readingsSingleUpdate', $hash->{helper}{timer}{alarm}, 0); InternalTimer(gettimeofday() + AttrVal($name, "signOfLifeInterval", 1320), 'EnOcean_readingsSingleUpdate', $hash->{helper}{timer}{alarm}, 0);
} }
} }
if (!exists($hash->{helper}{lastVoltage}) || $hash->{helper}{lastVoltage} != $db[3]) { push @event, "1:battery:" . ($db[3] * 0.02 > 2.8 ? "ok" : "low");
push @event, "3:battery:" . ($db[3] * 0.02 > 2.8 ? "ok" : "low");
$hash->{helper}{lastVoltage} = $db[3];
}
push @event, "3:button:" . ($db[0] & 4 ? "released" : "pressed") if ($manufID eq "7FF"); push @event, "3:button:" . ($db[0] & 4 ? "released" : "pressed") if ($manufID eq "7FF");
push @event, "3:motion:$motion"; push @event, "3:motion:$motion";
push @event, "3:state:$motion"; push @event, "3:state:$motion";
@@ -10661,10 +10692,9 @@ sub EnOcean_Parse($$) {
} }
} elsif ($st eq "autoMeterReading.01" || $st eq "actuator.01" && $manufID eq "033") { } elsif ($st eq "autoMeterReading.01" || $st eq "actuator.01" && $manufID eq "033") {
# Automated meter reading (AMR), Electricity (EEP A5-12-01) # Automated meter reading (AMR), Electricity (EEP A5-12-01)
# [Eltako FSS12, FWZ12, DSZ14DRS, DSZ14WDRS, DWZ61] # [Eltako FSS12, FWZ12, DSZ14DRS, DSZ14WDRS, DWZ61 FSR14M]
# $db[0]_bit_7 ... $db[0]_bit_4 is the Tariff info # $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, # $db[0]_bit_2 is the Data type where 0 = cumulative value kWh, 1 = current value W
# 1 = current value W
if ($db[0] == 0x8F && $manufID eq "00D") { if ($db[0] == 0x8F && $manufID eq "00D") {
# Eltako, read meter serial number # Eltako, read meter serial number
my $serialNumber; my $serialNumber;
@@ -10681,10 +10711,9 @@ sub EnOcean_Parse($$) {
push @event, "1:serialNumber:$serialNumber"; push @event, "1:serialNumber:$serialNumber";
} elsif ($dataType == 1) { } elsif ($dataType == 1) {
# momentary power # momentary power
$meterReading = $meterReading * -1 if ($model eq "Eltako_DSZ14DRSZ" && $channel == 1);
push @event, "3:power:$meterReading"; push @event, "3:power:$meterReading";
if (!($st eq "actuator.01" && $manufID eq "033")) { push @event, "3:state:$meterReading" if (!($st eq "actuator.01" && $manufID eq "033") && $model ne "Eltako_FSR14M");
push @event, "3:state:$meterReading";
}
} else { } else {
# power consumption # power consumption
push @event, "3:energy$channel:$meterReading"; push @event, "3:energy$channel:$meterReading";
@@ -12177,6 +12206,48 @@ sub EnOcean_Parse($$) {
#EnOcean_multisensor_01Snd($ctrl, $hash, $packetType); #EnOcean_multisensor_01Snd($ctrl, $hash, $packetType);
} }
} elsif ($st eq "multisensor.40") {
# Multisensor Lockable Windows Handle
# (D2-06-40)
# message type
my $msgType = ($db[0] & 0xC0) >> 6;
if ($msgType == 1) {
# windows status / request
my %handlePosition = (
0 => ["closed", 'F0'],
1 => ["open", 'E0'],
2 => ["tilted", 'D0'],
3 => ["unknown", undef]
);
my $position = ($db[0] & 0x30) >> 4;
my $handle;
if (exists $handlePosition{$position}) {
$handle = $handlePosition{$position}[0];
} else {
$handle = "unknown";
}
push @event, "1:handle:$handle";
push @event, "1:state:$handle";
push @event, "1:mechanicsState:" . ($db[0] & 0x08 ? 'ok' : 'error');
my $blockState = ($db[0] & 0x06) >> 1;
my @blockState = ('unlocked', 'locked', 'unknown', 'reserved');
push @event, "1:blockState:" . $blockState[$blockState];
my $reply = sprintf "%02X", 0x80 | (ReadingsVal($name, 'block', 'unlock') eq 'lock' ? 0 : 1);
EnOcean_SndRadio(undef, $hash, $packetType, 'D2', $reply, AttrVal($name, "subDef", "0" x 8), '00', $hash->{DEF});
# forward handle position (RPS telegam)
if (defined($handlePosition{$position}[1]) && defined(AttrVal($name, "subDefH", undef))) {
EnOcean_SndRadio(undef, $hash, $packetType, "F6", $handlePosition{$position}[1], AttrVal($name, "subDefH", "0" x 8), '20', 'FFFFFFFF');
}
} else {
}
readingsDelete($hash, "alarm");
if (AttrVal($name, "signOfLife", 'on') eq 'on') {
RemoveInternalTimer($hash->{helper}{timer}{alarm}) if(exists $hash->{helper}{timer}{alarm});
@{$hash->{helper}{timer}{alarm}} = ($hash, 'alarm', 'dead_sensor', 1, 5);
InternalTimer(gettimeofday() + AttrVal($name, "signOfLifeInterval", 1980), 'EnOcean_readingsSingleUpdate', $hash->{helper}{timer}{alarm}, 0);
}
} elsif ($st eq "multisensor.50") { } elsif ($st eq "multisensor.50") {
# Multisensor Windows Handle # Multisensor Windows Handle
# (D2-06-50) # (D2-06-50)
@@ -13006,11 +13077,11 @@ sub EnOcean_Parse($$) {
# Signal Telegram # Signal Telegram
my $signalMID = hex(substr($data, 0, 2)); my $signalMID = hex(substr($data, 0, 2));
if ($signalMID == 1) { if ($signalMID == 1) {
push @event, "3:smartAckMailbox:empty"; push @event, "1:smartAckMailbox:empty";
} elsif ($signalMID == 2) { } elsif ($signalMID == 2) {
push @event, "3:smartAckMailbox:not_exists"; push @event, "1:smartAckMailbox:not_exists";
} elsif ($signalMID == 3) { } elsif ($signalMID == 3) {
push @event, "3:smartAckMailbox:reset"; push @event, "1:smartAckMailbox:reset";
} elsif ($signalMID == 4) { } elsif ($signalMID == 4) {
my $responseID = $subDef eq $hash->{DEF} ? $ioHash->{ChipID} : $subDef; my $responseID = $subDef eq $hash->{DEF} ? $ioHash->{ChipID} : $subDef;
if ($db[0] == 0) { if ($db[0] == 0) {
@@ -13041,10 +13112,10 @@ sub EnOcean_Parse($$) {
$hash->{Dev_ACK} = 'signal'; $hash->{Dev_ACK} = 'signal';
DoTrigger($name, "SIGNAL: Dev_ACK", 1); DoTrigger($name, "SIGNAL: Dev_ACK", 1);
} elsif ($signalMID == 6) { } elsif ($signalMID == 6) {
push @event, "3:batteryPercent:$db[0]"; push @event, "1:batteryPercent:$db[0]";
} elsif ($signalMID == 7) { } elsif ($signalMID == 7) {
push @event, "3:hwVersion:" . substr($data, 10, 8); push @event, "1:hwVersion:" . substr($data, 10, 8);
push @event, "3:swVersion:" . substr($data, 2, 8); push @event, "1:swVersion:" . substr($data, 2, 8);
} elsif ($signalMID == 8) { } elsif ($signalMID == 8) {
push @event, "3:trigger:heartbeat"; push @event, "3:trigger:heartbeat";
} elsif ($signalMID == 9) { } elsif ($signalMID == 9) {
@@ -13065,20 +13136,25 @@ sub EnOcean_Parse($$) {
Log3 $name, 2, "EnOcean $name SIGNAL Dev_CHANGED"; Log3 $name, 2, "EnOcean $name SIGNAL Dev_CHANGED";
} elsif ($signalMID == 13) { } elsif ($signalMID == 13) {
my @harvester = ('very_good', 'good', 'average', 'bad', 'very_bad'); my @harvester = ('very_good', 'good', 'average', 'bad', 'very_bad');
push @event, "3:harvester:" . $harvester[$db[0] & 0x0F]; push @event, "1:harvester:" . $harvester[$db[0] & 0x0F];
} elsif ($signalMID == 14) { } elsif ($signalMID == 14) {
DoTrigger($name, "SIGNAL: TX_MODE_OFF", 1); DoTrigger($name, "SIGNAL: TX_MODE_OFF", 1);
} elsif ($signalMID == 15) { } elsif ($signalMID == 15) {
DoTrigger($name, "SIGNAL: TX_MODE_ON", 1); DoTrigger($name, "SIGNAL: TX_MODE_ON", 1);
} elsif ($signalMID == 16) { } elsif ($signalMID == 16) {
if ($db[0] <= 100) { if ($db[0] <= 100) {
push @event, "3:batteryPercentBackup:$db[0]"; push @event, "1:batteryPercentBackup:$db[0]";
} else { } else {
readingsDelete($hash, "batteryPercentBackup"); readingsDelete($hash, "batteryPercentBackup");
} }
} elsif ($signalMID == 17) { } elsif ($signalMID == 17) {
# learn mode status # learn mode status
#push @event, "3:batteryPercent:$db[0]"; #push @event, "3:batteryPercent:$db[0]";
} elsif ($signalMID == 18) {
# product ID
$data =~ /^.{2}(.{4})(.{8})$/;
push @event, "1:manufID:$1";
push @event, "1:productID:$2";
} }
} elsif ($rorg eq "B2") { } elsif ($rorg eq "B2") {
@@ -14790,7 +14866,7 @@ sub EnOcean_Attr(@) {
} elsif ($attrName eq "secMode") { } elsif ($attrName eq "secMode") {
if (!defined $attrVal){ if (!defined $attrVal){
} elsif ($attrVal !~ m/^rcv|snd|bidir$/) { } elsif ($attrVal !~ m/^rcv|snd|biDir$/) {
$err = "attribute-value [$attrName] = $attrVal wrong"; $err = "attribute-value [$attrName] = $attrVal wrong";
} }
@@ -16882,7 +16958,8 @@ sub EnOcean_SndCdm($$$$$$$$) {
"PacketType: $packetType RORG: $rorg DATA: undef STATUS: $status"; "PacketType: $packetType RORG: $rorg DATA: undef STATUS: $status";
return; return;
} }
my ($seq, $idx, $len, $dataPart, $dataPartLen) = (1, 0, length($data) / 2, undef, 14); # DATA payload max: RADIO = 14, RADIO ADT = 9
my ($seq, $idx, $len, $dataPart, $dataPartLen, $dataPartMask) = (1, 0, length($data), '', $destinationID eq "FFFFFFFF" ? 28 : 18, undef);
if (exists $ioHash->{helper}{cdmSeq}) { if (exists $ioHash->{helper}{cdmSeq}) {
if ($ioHash->{helper}{cdmSeq} < 3) { if ($ioHash->{helper}{cdmSeq} < 3) {
$ioHash->{helper}{cdmSeq} ++; $ioHash->{helper}{cdmSeq} ++;
@@ -16893,35 +16970,23 @@ sub EnOcean_SndCdm($$$$$$$$) {
} else { } else {
$ioHash->{helper}{cdmSeq} = $seq; $ioHash->{helper}{cdmSeq} = $seq;
} }
# split telelegram with optional data
$dataPartLen = 9 if ($destinationID ne "FFFFFFFF");
if ($packetType == 1 && $len > $dataPartLen) { if ($packetType == 1 && $len > $dataPartLen) {
# first CDM telegram # first CDM telegram
if ($dataPartLen == 14) { $dataPartMask = $dataPartLen - 8;
$data =~ m/^(....................)(.*)$/; $data =~ m/^(.{$dataPartMask})(.*)$/;
$dataPart = (sprintf "%02X", $seq << 6 | $idx) . (sprintf "%04X", $len) . $rorg . $1; $dataPart = (sprintf "%02X", $seq << 6 | $idx) . (sprintf "%04X", $len / 2) . $rorg . $1;
$data = $2; $data = $2;
} else {
$data =~ m/^(..........)(.*)$/;
$dataPart = (sprintf "%02X", $seq << 6 | $idx) . (sprintf "%04X", $len) . $rorg . $1;
$data = $2;
}
$idx ++; $idx ++;
$len -= $dataPartLen - 5; $len -= $dataPartMask;
EnOcean_SndRadio($ctrl, $hash, $packetType, "40", $dataPart, $senderID, $status, $destinationID); EnOcean_SndRadio($ctrl, $hash, $packetType, "40", $dataPart, $senderID, $status, $destinationID);
$dataPartMask = $dataPartLen - 2;
while ($len > 0) { while ($len > 0) {
if ($len > $dataPartLen - 2) { if ($len > $dataPartLen - 2) {
if ($dataPartLen == 14) { $data =~ m/^(.{$dataPartMask})(.*)$/;
$data =~ m/^(..........................)(.*)$/;
$dataPart = (sprintf "%02X", $seq << 6 | $idx) . $1; $dataPart = (sprintf "%02X", $seq << 6 | $idx) . $1;
$data = $2; $data = $2;
} else {
$data =~ m/^(................)(.*)$/;
$dataPart = (sprintf "%02X", $seq << 6 | $idx) . $1;
$data = $2;
}
$idx ++; $idx ++;
$len -= $dataPartLen - 2; $len -= $dataPartMask;
} else { } else {
$dataPart = (sprintf "%02X", $seq << 6 | $idx) . $data; $dataPart = (sprintf "%02X", $seq << 6 | $idx) . $data;
$len = 0; $len = 0;
@@ -16960,7 +17025,7 @@ sub EnOcean_SndRadio($$$$$$$$) {
# ADT telegram # ADT telegram
$data .= $destinationID; $data .= $destinationID;
} elsif ($destinationID ne "FFFFFFFF") { } elsif ($destinationID ne "FFFFFFFF") {
# SubTelNum = 03, DestinationID:8, RSSI = FF, secLevel = 00 # SubTelNum = 03, DestinationID: 8 bytes, RSSI = FF, secLevel = 00
$odata = sprintf "03%sFF00", $destinationID; $odata = sprintf "03%sFF00", $destinationID;
$odataLength = 7; $odataLength = 7;
} }
@@ -17032,8 +17097,7 @@ sub EnOcean_SndPeriodic($) {
} }
# Scale Readings # Scale Readings
sub EnOcean_ReadingScaled($$$$) sub EnOcean_ReadingScaled($$$$) {
{
my ($hash, $readingVal, $readingMin, $readingMax) = @_; my ($hash, $readingVal, $readingMin, $readingMax) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
my $valScaled; my $valScaled;
@@ -17956,7 +18020,9 @@ sub EnOcean_sec_parseTeachIn($$$$) {
my $rlc; # Rolling code my $rlc; # Rolling code
my $key1; # First part of private key my $key1; # First part of private key
my $key2; # Second part of private key my $key2; # Second part of private key
if ($cryptFunc == 0) {
return ("Cryptographic functions are not available", undef, undef);
}
# Extract byte fields from telegram # Extract byte fields from telegram
# TEACH_IN_INFO, SLF, RLC/KEY/variable # TEACH_IN_INFO, SLF, RLC/KEY/variable
$telegram =~ /^(..)(..)(.*)/; # TODO Parse error handling? $telegram =~ /^(..)(..)(.*)/; # TODO Parse error handling?
@@ -17986,6 +18052,15 @@ sub EnOcean_sec_parseTeachIn($$$$) {
if ($idx == 0 && $cnt == 2) { if ($idx == 0 && $cnt == 2) {
# First part of the teach in message # First part of the teach in message
# check if RLC and PK are encrypted with PSK and assign it
if (defined $psk) {
if (exists $modules{$ioModulesType}{STE}{psk}) {
$attr{$name}{psk} = $modules{$ioModulesType}{STE}{psk};
} else {
return ("Undefined PSK", undef);
}
}
# Decode teach in type # Decode teach in type
if ($type == 0) { if ($type == 0) {
# 1BS, 4BS, UTE or GP teach-in expected # 1BS, 4BS, UTE or GP teach-in expected
@@ -18004,6 +18079,7 @@ sub EnOcean_sec_parseTeachIn($$$$) {
$attr{$name}{comMode} = "uniDir"; $attr{$name}{comMode} = "uniDir";
$attr{$name}{eep} = "D2-03-00"; $attr{$name}{eep} = "D2-03-00";
$attr{$name}{manufID} = "7FF"; $attr{$name}{manufID} = "7FF";
$attr{$name}{rocker} = "A";
$attr{$name}{secMode} = "rcv"; $attr{$name}{secMode} = "rcv";
foreach my $attrCntr (keys %{$EnO_eepConfig{"D2.03.00"}{attr}}) { foreach my $attrCntr (keys %{$EnO_eepConfig{"D2.03.00"}{attr}}) {
$attr{$name}{$attrCntr} = $EnO_eepConfig{"D2.03.00"}{attr}{$attrCntr}; $attr{$name}{$attrCntr} = $EnO_eepConfig{"D2.03.00"}{attr}{$attrCntr};
@@ -18014,6 +18090,7 @@ sub EnOcean_sec_parseTeachIn($$$$) {
$attr{$name}{comMode} = "uniDir"; $attr{$name}{comMode} = "uniDir";
$attr{$name}{eep} = "D2-03-00"; $attr{$name}{eep} = "D2-03-00";
$attr{$name}{manufID} = "7FF"; $attr{$name}{manufID} = "7FF";
$attr{$name}{rocker} = "B";
$attr{$name}{secMode} = "rcv"; $attr{$name}{secMode} = "rcv";
foreach my $attrCntr (keys %{$EnO_eepConfig{"D2.03.00"}{attr}}) { foreach my $attrCntr (keys %{$EnO_eepConfig{"D2.03.00"}{attr}}) {
$attr{$name}{$attrCntr} = $EnO_eepConfig{"D2.03.00"}{attr}{$attrCntr}; $attr{$name}{$attrCntr} = $EnO_eepConfig{"D2.03.00"}{attr}{$attrCntr};
@@ -18131,7 +18208,6 @@ sub EnOcean_sec_parseTeachIn($$$$) {
} elsif ($idx == 1 && exists($hash->{helper}{teachInSTE})) { } elsif ($idx == 1 && exists($hash->{helper}{teachInSTE})) {
# Second part of the teach-in telegrams # Second part of the teach-in telegrams
# Extract byte fields from telegram # Extract byte fields from telegram
# Don't care about info fields, KEY, ID, don't care about status # Don't care about info fields, KEY, ID, don't care about status
$telegram =~ /^..(.*)$/; # TODO Parse error handling? $telegram =~ /^..(.*)$/; # TODO Parse error handling?
@@ -18146,9 +18222,19 @@ sub EnOcean_sec_parseTeachIn($$$$) {
# Append second part of private key to first part of private key # Append second part of private key to first part of private key
$attr{$name}{keyRcv} .= $key2; $attr{$name}{keyRcv} .= $key2;
if (defined $attr{$name}{psk}) {
# RLC and PK must be encrypted with PSK
my $pskData = $attr{$name}{rlcRcv} . $attr{$name}{keyRcv};
$pskData = EnOcean_sec_VAES('0000', $attr{$name}{psk}, $pskData, 1);
$pskData =~ /^(.*)(.{32})$/;
$attr{$name}{keyRcv} = $2;
readingsSingleUpdate($hash, ".rlcRcv", $1, 0);
$attr{$name}{rlcRcv} = $1;
}
if ($attr{$name}{secMode} eq "biDir") { if ($attr{$name}{secMode} eq "biDir") {
# bidirectional secure teach-in # bidirectional secure teach-in
if (!defined $subDef) { if (!defined($attr{$name}{subDef})) {
$subDef = EnOcean_CheckSenderID("getNextID", $defs{$name}{IODev}{NAME}, "00000000"); $subDef = EnOcean_CheckSenderID("getNextID", $defs{$name}{IODev}{NAME}, "00000000");
$attr{$name}{subDef} = $subDef; $attr{$name}{subDef} = $subDef;
} }
@@ -18156,7 +18242,8 @@ sub EnOcean_sec_parseTeachIn($$$$) {
$attr{$name}{dataEnc}, $attr{$name}{eep}, $attr{$name}{dataEnc}, $attr{$name}{eep},
$attr{$name}{macAlgo}, $attr{$name}{rlcAlgo}, $attr{$name}{macAlgo}, $attr{$name}{rlcAlgo},
$attr{$name}{rlcTX}, $attr{$name}{secLevel}, $attr{$name}{rlcTX}, $attr{$name}{secLevel},
$subDef, $destinationID); undef, $subDef, $hash->{DEF});
#$hash->{DEF} statt $destinationID liefert Tranceiver Fehler wegen zu großem Datenpaket
if ($err) { if ($err) {
Log3 $name, $logLevel, "EnOcean $name Error: $err"; Log3 $name, $logLevel, "EnOcean $name Error: $err";
return $err; return $err;
@@ -18172,6 +18259,80 @@ sub EnOcean_sec_parseTeachIn($$$$) {
return ("teach-in sequence error IDC: $idx CNT: $cnt", undef); return ("teach-in sequence error IDC: $idx CNT: $cnt", undef);
} }
# VAES encryption or decryption
sub EnOcean_sec_VAES($$$$) {
# $rlc: rolling code, hex string, variable length, max 16 bytes
# $privateKey: private key, hex string 16 bytes
# $dataIn: input data with hex string variable length
# $cryptMode: 0 = decrypt, 1 = encrypt
# $dataOut: encrypted or decrypted output data with hex string variable length
my ($rlc, $privateKey, $dataIn, $cryptMode) = @_;
my @aesIn;
my @aesOut;
my @dataIn;
my $dataInCount = 0;
my $dataLength = length($dataIn);
my @dataOut;
Log3 undef, 3, "EnOcean_sec_VAES: RLC: $rlc KEY: $dataIn";
# split dataIn
while ($dataLength > 0) {
if ($dataLength > 32) {
$dataIn =~ /^(.{32})(.*)$/;
$dataIn[$dataInCount] = $1;
$dataIn = $2;
$dataInCount ++;
$dataLength = length($dataIn);
} else {
$dataIn[$dataInCount] = $dataIn;
last;
}
}
Log3 undef, 3, "EnOcean_sec_VAES: RLC KEY array IN: $dataIn[0] $dataIn[1]";
$dataInCount = 1;
# EnOcean public key
my $publicKey = pack('H32', '3410de8f1aba3eff9f5a117172eacabd');
#$rlc = pack('H32', $rlc . '0' x (32 - length($rlc)));
$rlc = pack('H32', $rlc);
$privateKey = pack('H32', $privateKey);
# variable init vector, first input for all VAES
$aesIn[0] = $publicKey ^ $rlc;
Log3 undef, 3, "EnOcean_sec_VAES: RLC " . unpack('H32', $rlc) . " aesIn0: " . unpack('H32', $aesIn[0]);
my $cipher = Crypt::Rijndael->new($privateKey);
# encryption or decryption vector 1
if ($cryptMode) {
$aesOut[0] = $cipher->encrypt($aesIn[0]);
} else {
$aesOut[0] = $cipher->decrypt($aesIn[0]);
}
Log3 undef, 3, "EnOcean_sec_VAES: RLC " . unpack('H32', $rlc) . " aesOut0: " . unpack('H32', $aesOut[0]);
# data out 1
$dataLength = length($dataIn[0]);
#$dataIn[0] = pack('H32', $dataIn[0] . '0' x (32 - $dataLength));
$dataIn[0] = pack('H32', $dataIn[0]);
$dataOut[0] = $aesOut[0] ^ $dataIn[0];
$dataOut[0] = substr(unpack('H32', $dataOut[0]), 0, $dataLength);
# data out n
while ($dataIn[$dataInCount]) {
# init vector n (input $aesIn[0] for all VAES)
$dataLength = length($dataIn[$dataInCount]);
#$dataIn[$dataInCount] = pack('H32', $dataIn[$dataInCount] . '0' x (32 - $dataLength));
$dataIn[$dataInCount] = pack('H32', $dataIn[$dataInCount]);
$aesIn[$dataInCount] = $aesOut[$dataInCount - 1] ^ $aesIn[0];
# encryption or decryption vector n
if ($cryptMode) {
$aesOut[$dataInCount] = $cipher->encrypt($aesIn[$dataInCount]);
} else {
$aesOut[$dataInCount] = $cipher->decrypt($aesIn[$dataInCount]);
}
# data out n
$dataOut[$dataInCount] = $aesOut[$dataInCount] ^ $dataIn[$dataInCount];
$dataOut[$dataInCount] = substr(unpack('H32', $dataOut[$dataInCount]), 0, $dataLength);
$dataInCount ++;
}
Log3 undef, 3, "EnOcean_sec_VAES: RLC KEY array OUT: $dataOut[0] $dataOut[1]";
return uc(join('', @dataOut));
}
# Do VAES decyrption # Do VAES decyrption
# All parameters need to be passed as byte strings # All parameters need to be passed as byte strings
# #
@@ -18200,7 +18361,7 @@ sub EnOcean_sec_decodeVAES($$$) {
#print "--\n"; #print "--\n";
#print "Private Key ".unpack('H32', $private_key)."\n"; #print "Private Key ".unpack('H32', $private_key)."\n";
my $cipher = Crypt::Rijndael->new( $private_key ); my $cipher = Crypt::Rijndael->new($private_key);
my $aes_out = $cipher->encrypt($aes_in); my $aes_out = $cipher->encrypt($aes_in);
#print "AES output ".unpack('H32', $aes_out)."\n"; #print "AES output ".unpack('H32', $aes_out)."\n";
@@ -18366,15 +18527,6 @@ sub EnOcean_sec_generateMAC($$$) {
} }
# Verify (MAC) and decode/decrypt secure mode message # Verify (MAC) and decode/decrypt secure mode message
#
# Parameter 1: content of radio telegram in hexadecimal format
#
# Returns: "ERROR-" + error description, "OK-" + EEP D2-03-00 telegram in hexadecimal format
#
# Right now we only decode PTM215 telegrams which are transmitted as RORG 30 and without
# encapsulation. Encapsulation of other telegrams is possible and specified but untested due to the
# lack of hardware suporting this.
#
sub EnOcean_sec_convertToNonsecure($$$) { sub EnOcean_sec_convertToNonsecure($$$) {
my ($hash, $rorg, $crypt_data) = @_; my ($hash, $rorg, $crypt_data) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
@@ -18383,7 +18535,7 @@ sub EnOcean_sec_convertToNonsecure($$$) {
} }
my $private_key; my $private_key;
# Prefix of pattern to extract the different cryptographic infos # Prefix of pattern to extract the different cryptographic infos
my $crypt_pattern = "^(.*)";; my $crypt_pattern = "^(.*)";
# Flags and infos for fields to expect # Flags and infos for fields to expect
my $expect_rlc = 0; my $expect_rlc = 0;
my $expect_mac = 0; my $expect_mac = 0;
@@ -18454,26 +18606,26 @@ sub EnOcean_sec_convertToNonsecure($$$) {
$rlc = EnOcean_sec_getRLC($hash, "rlcRcv", $expect_rlc, $rlc); $rlc = EnOcean_sec_getRLC($hash, "rlcRcv", $expect_rlc, $rlc);
# Fetch private Key for VAES # Fetch private Key for VAES
if ($attr{$name}{keyRcv} =~ /[\dA-F]{32}/) { if ($attr{$name}{keyRcv} =~ /[\dA-F]{32}/) {
$private_key = pack('H32',$attr{$name}{keyRcv}); $private_key = pack('H32', $attr{$name}{keyRcv});
} else { } else {
return ("private key wrong, please teach-in the device new", undef, undef); return ("private key wrong, please teach-in the device new", undef, undef);
} }
# Generate and check MAC over RORG+DATA+RLC fields # Generate and check MAC over RORG+DATA+RLC fields
if ($mac eq EnOcean_sec_generateMAC($private_key, $rorg.$data_enc.$rlc, $mac_len)) { if ($mac eq EnOcean_sec_generateMAC($private_key, $rorg . $data_enc . $rlc, $mac_len)) {
#print "RLC verfified\n"; #print "RLC verfified\n";
# Expand RLC to 16byte # Expand RLC to 16byte
my $rlc_expanded = pack('H32',$rlc); my $rlc_expanded = pack('H32', $rlc);
# Expand data to 16byte # Expand data to 16byte
my $data_expanded = pack('H32',$data_enc); my $data_expanded = pack('H32', $data_enc);
# Decode data using VAES # Decode data using VAES
my $data_dec = EnOcean_sec_decodeVAES($rlc_expanded, $private_key, $data_expanded); my $data_dec = EnOcean_sec_decodeVAES($rlc_expanded, $private_key, $data_expanded);
my $data_end = unpack('H32', $data_dec); my $data_end = uc(unpack('H32', $data_dec));
if ($rorg eq '30') { if ($rorg eq '30') {
$data_end =~ /^(.{$dataLength})/; $data_end =~ /^(.{$dataLength})/;
return (undef, '32', uc($1)); return (undef, '32', $1);
#if ($dataLength == 2) { #if ($dataLength == 2) {
# Extract one nibble of data # Extract one nibble of data
# $data_end =~ /^.(.)/; # $data_end =~ /^.(.)/;
@@ -18485,8 +18637,8 @@ sub EnOcean_sec_convertToNonsecure($$$) {
} else { } else {
$dataLength -= 2; $dataLength -= 2;
$data_end =~ /^(..)(.{$dataLength})/; $data_end =~ /^(..)(.{$dataLength})/;
Log3 $name, 5, "EnOcean $name EnOcean_sec_convertToNonsecure RORG: " . uc($1) . " DATA: " . uc($2); Log3 $name, 5, "EnOcean $name EnOcean_sec_convertToNonsecure RORG: " . $1 . " DATA: " . $2;
return (undef, uc($1), uc($2)); return (undef, $1, $2);
} }
# Couldn't verify or decrypt message, only one calculation if rlcTX = true # Couldn't verify or decrypt message, only one calculation if rlcTX = true
return ("Can't verify or decrypt telegram", undef, undef) if ($expect_rlc == 1); return ("Can't verify or decrypt telegram", undef, undef) if ($expect_rlc == 1);
@@ -18501,12 +18653,13 @@ sub EnOcean_sec_convertToNonsecure($$$) {
} }
sub EnOcean_sec_createTeachIn($$$$$$$$$$$) { sub EnOcean_sec_createTeachIn($$$$$$$$$$$) {
my ($ctrl, $hash, $comMode, $dataEnc, $eep, $macAlgo, $rlcAlgo, $rlcTX, $secLevel, $subDef, $destinationID) = @_; my ($ctrl, $hash, $comMode, $dataEnc, $eep, $macAlgo, $rlcAlgo, $rlcTX, $secLevel, $rocker, $subDef, $destinationID) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
my ($data, $err, $response, $loglevel); my ($data, $err, $response, $loglevel);
# Info CNT = 2
# THIS IS A BASIC IMPLEMENTATION WITH HARDCODED VALUES FOR my $info = 0x20;
# THE SECURITY PARAMETERS, WILL BE CUSTOMIZABLE IN FUTURE # DATA_ENC = VAES
my $slf = 3;
if ($cryptFunc == 0) { if ($cryptFunc == 0) {
return ("Cryptographic functions are not available", undef, 2); return ("Cryptographic functions are not available", undef, 2);
@@ -18517,12 +18670,7 @@ sub EnOcean_sec_createTeachIn($$$$$$$$$$$) {
$pKey .= uc(unpack('H8', pack('L', makerandom(Size => 32, Strength => 1)))); $pKey .= uc(unpack('H8', pack('L', makerandom(Size => 32, Strength => 1))));
} }
$attr{$name}{keySnd} = AttrVal($name, "keySnd", $pKey); $attr{$name}{keySnd} = AttrVal($name, "keySnd", $pKey);
$pKey = $attr{$name}{keySnd};
#generate random rlc, save to fhem.cfg and update readings
my $rlc = ReadingsVal($name, ".rlcSnd", uc(unpack('H4', pack('n', makerandom(Size => 16, Strength => 1)))));
readingsSingleUpdate($hash, ".rlcSnd", $rlc, 0);
$attr{$name}{rlcSnd} = $rlc;
$attr{$name}{comMode} = AttrVal($name, "comMode", $comMode); $attr{$name}{comMode} = AttrVal($name, "comMode", $comMode);
$attr{$name}{dataEnc} = AttrVal($name, "dataEnc", $dataEnc); $attr{$name}{dataEnc} = AttrVal($name, "dataEnc", $dataEnc);
$attr{$name}{eep} = $eep; $attr{$name}{eep} = $eep;
@@ -18530,72 +18678,100 @@ sub EnOcean_sec_createTeachIn($$$$$$$$$$$) {
$attr{$name}{manufID} = "7FF"; $attr{$name}{manufID} = "7FF";
$attr{$name}{rlcAlgo} = AttrVal($name, "rlcAlgo", $rlcAlgo); $attr{$name}{rlcAlgo} = AttrVal($name, "rlcAlgo", $rlcAlgo);
$attr{$name}{rlcTX} = AttrVal($name, "rlcTX", $rlcTX); $attr{$name}{rlcTX} = AttrVal($name, "rlcTX", $rlcTX);
$attr{$name}{rocker} = AttrVal($name, "rocker", $rocker) if (defined $rocker);
$attr{$name}{secLevel} = AttrVal($name, "secLevel", $secLevel); $attr{$name}{secLevel} = AttrVal($name, "secLevel", $secLevel);
if (AttrVal($name, "secMode", "") =~ m/^rcv|bidir$/) {
#generate random rlc, save to fhem.cfg and update readings
my %rlcAlgo = ('2++' => 'H4', '3++' => 'H6', '4++' => 'H8');
my $rlcSize = exists($rlcAlgo{$attr{$name}{rlcAlgo}}) ? $rlcAlgo{$attr{$name}{rlcAlgo}} : 'H4';
my $rlc = ReadingsVal($name, ".rlcSnd", uc(unpack($rlcSize, pack('L', makerandom(Size => 32, Strength => 1)))));
readingsSingleUpdate($hash, ".rlcSnd", $rlc, 0);
$attr{$name}{rlcSnd} = $rlc;
# prepare 1st telegram with teach-in info, slf, rlc and get first 5 bytes of private key
# old fixed parameters for rocker
#set TEACH_IN_INFO = 25 -> 0001.0101 -> IDX =0, CNT = 2, PSK = 0, TYPE = 1, INFO = 1
#set SLF = 4B -> 0100.1011 -> RLC_ALGO=16bit, RLC-TX=0, MAC-ALGO = AES3BYTE, DATA_ENC = VAES128
if (defined $rocker) {
$info |= 4;
$info |= 1 if ($rocker eq 'B');
} else {
if (AttrVal($name, "secMode", "") =~ m/^rcv|biDir$/) {
$attr{$name}{secMode} = "biDir"; $attr{$name}{secMode} = "biDir";
$info |= 1;
} else { } else {
$attr{$name}{secMode} = "snd"; $attr{$name}{secMode} = "snd";
} }
}
# prepare 1st telegram %rlcAlgo = ('2++' => 0x40, '3++' => 0x80, '4++' => 0xC0);
$slf |= $rlcAlgo{$attr{$name}{rlcAlgo}} if (exists $rlcAlgo{$attr{$name}{rlcAlgo}});
$slf |= 0x20 if ($rlcTX eq 'true');
my %macAlgo = (3 => 8, 4 => 0x10);
$slf |= $macAlgo{$attr{$name}{macAlgo}} if (exists $macAlgo{$attr{$name}{macAlgo}});
#RORG = 35, TEACH_IN_INFO_0, SLF, RLC, KEY, ID, STATUS as defined in Security_of_EnOcean_Radio_Networks.pdf page 17 if (defined $attr{$name}{psk}) {
#set TEACH_IN_INFO = 25 -> 0001.0101 -> IDX =0, CNT = 2, PSK = 0, TYPE = 1, INFO = 1 # RLC and PK must be decrypted with PSK
#set SLF = 4B -> 0100.1011 -> RLC_ALGO=16bit, RLC-TX=0, MAC-ALGO = AES3BYTE, DATA_ENC = VAES128 $info |= 8;
#save the fixed security parameters to fhem.cfg my $pskData = $attr{$name}{rlcSnd} . $attr{$name}{keySnd};
$pskData = EnOcean_sec_VAES('0000', $attr{$name}{psk}, $pskData, 1);
#get first 5 bytes of private key $pskData =~ /^(.*)(.{32})$/;
#data 1: 25 4B r1 r2 k1 k2 k3 k4 k5 $pKey = $2;
$data = "254B" . $rlc . substr($attr{$name}{keySnd}, 0, 5*2); $rlc = $1;
EnOcean_SndRadio(undef, $hash, 1, "35", $data, $subDef, "00", $destinationID); }
Log3 $hash->{NAME}, 3, "EnOcean_sec_createTeachIn: send Info0: " . sprintf("%02X", $info) . " SLF: " . sprintf("%02X", $slf) . " RLC: $rlc pKey: $pKey";
# prepare 2nd telegram
#RORG = 35, TEACH_IN_INFO_1, KEY, ID, STATUS as defined in Security_of_EnOcean_Radio_Networks.pdf page 17 #RORG = 35, TEACH_IN_INFO_1, KEY, ID, STATUS as defined in Security_of_EnOcean_Radio_Networks.pdf page 17
#data 1: info slf rlc k1 k2 k3 k4 k5
$data = sprintf("%02X%02X", $info, $slf) . $rlc . substr($pKey, 0, 5*2);
EnOcean_SndCdm(undef, $hash, 1, "35", $data, $subDef, "00", $destinationID);
#EnOcean_SndCdm <> EnOcean_SndRadio
# prepare 2nd telegram
#set TEACH_IN_INFO = 40 -> 0100.0000 -> IDX =1, CNT = 0, PSK = 0, TYPE = 0, INFO = 0 #set TEACH_IN_INFO = 40 -> 0100.0000 -> IDX =1, CNT = 0, PSK = 0, TYPE = 0, INFO = 0
#sent 2nd 11 bytes of private key
#get 2nd 11 bytes of private key
#data 2: 40 k6 k7 k8 k9 k10 k11 k12 k13 k14 k15 k16 #data 2: 40 k6 k7 k8 k9 k10 k11 k12 k13 k14 k15 k16
$data = "40" . substr($attr{$name}{keySnd}, 10, 11*2); $data = "40" . substr($pKey, 10, 11*2);
EnOcean_SndRadio(undef, $hash, 1, "35", $data, $subDef, "00", $destinationID); EnOcean_SndCdm(undef, $hash, 1, "35", $data, $subDef, "00", $destinationID);
#EnOcean_SndCdm <> EnOcean_SndRadio
return (undef, "secure teach-in", 2); return (undef, "secure teach-in", 2);
} }
sub EnOcean_sec_convertToSecure($$$$) { sub EnOcean_sec_convertToSecure($$$$) {
my ($hash, $packetType, $rorg, $data) = @_; my ($hash, $packetType, $rorg, $data) = @_;
my ($err, $response, $loglevel); my ($data_end, $dataLength, $err, $response, $rorgs, $loglevel);
my $name = $hash->{NAME}; my $name = $hash->{NAME};
my $secLevel = AttrVal($name, "secLevel", "off"); my $secLevel = AttrVal($name, "secLevel", "off");
my $subType = AttrVal($name, "subType", "");
# no encryption with different set profile, required for the LED control of the model Eltako_F4CT55 # no encryption with different set profile, required for the LED control of the model Eltako_F4CT55
my $subTypeSet = AttrVal($name, "subTypeSet", ""); my $subTypeSet = AttrVal($name, "subTypeSet", "");
# encryption needed? # encryption needed?
return ($err, $rorg, $data, $response, 5) if ($rorg =~ m/^F6|35$/ || $secLevel !~ m/^encapsulation|encryption$/ || $subTypeSet eq "switch"); return ($err, $rorg, $data, $response, 5) if ($rorg =~ m/^F6|35$/ || $secLevel !~ m/^encapsulation|encryption$/ || $subType eq "STE" || $subTypeSet eq "switch");
return ("Cryptographic functions are not available", undef, undef, $response, 2) if ($cryptFunc == 0); return ("Cryptographic functions are not available", undef, undef, $response, 2) if ($cryptFunc == 0);
my $dataEnc = AttrVal($name, "dataEnc", undef); my $dataEnc = AttrVal($name, "dataEnc", undef);
my $subType = AttrVal($name, "subType", "");
# subType specific actions
if ($subType eq "switch.00" || $subType eq "windowHandle.10") {
# securemode for D2-03-00 and D2-03-10
return ("wrong data byte", $rorg, $data, $response, 2) if (hex($data) > 15);
# set rorg to secure telegram
$rorg = "30";
} else {
return ("Cryptographic functions for $subType not available", $rorg, $data, $response, 2);
}
#Get and update RLC #Get and update RLC
my $rlc = EnOcean_sec_getRLC($hash, "rlcSnd", 0, undef); my $rlc = EnOcean_sec_getRLC($hash, "rlcSnd", 0, undef);
#Log3 $hash->{NAME}, 5, "EnOcean_sec_convertToSecure: Got actual RLC: $rlc"; Log3 $name, 5, "EnOcean $name EnOcean_sec_convertToSecure: Got actual RLC: $rlc";
my $rlc_expanded = pack('H32', $rlc);
#Get key of device #Get key of device
my $pKey = AttrVal($name, "keySnd", undef); my $pKey = AttrVal($name, "keySnd", undef);
return("private key not defined", $rorg, $data, $response, 2) if (!defined $pKey); return("private key not defined", $rorg, $data, $response, 2) if (!defined($pKey));
Log3 $name, 5, "EnOcean $name EnOcean_sec_convertToSecure: key: $pKey";
$pKey = pack('H32', $pKey); $pKey = pack('H32', $pKey);
#Log3 $hash->{NAME}, 5, "EnOcean_sec_convertToSecure: key: " . AttrVal($name, "keySnd", "");
#prepare data #prepare data
my $rlc_expanded = pack('H32', $rlc); if ($subType eq "switch.00" || $subType eq "windowHandle.10") {
# securemode for PTM switch (D2-03-00 and D2-03-10)
return ("wrong data byte", $rorg, $data, $response, 2) if (hex($data) > 15);
# set rorg to secure telegram
$rorg = '';
$rorgs = "30";
} else {
# securemode for 1BS, 4BS, VLD
$data = $rorg . $data;
$rorgs = "31";
}
$dataLength = length($data);
my $data_expanded = pack('H32', $data); my $data_expanded = pack('H32', $data);
my $data_dec; my $data_dec;
if ($dataEnc eq "VAES") { if ($dataEnc eq "VAES") {
@@ -18603,19 +18779,26 @@ sub EnOcean_sec_convertToSecure($$$$) {
} else { } else {
return("Cryptographic functions not available", $rorg, $data, $response, 2); return("Cryptographic functions not available", $rorg, $data, $response, 2);
} }
my $data_end = unpack('H32', $data_dec); $data_end = unpack('H32', $data_dec);
$data_end = substr($data_end, 0, $dataLength);
if ($subType eq "switch.00" || $subType eq "windowHandle.10") {
#get the correct nibble #get the correct nibble
$data_end =~ /^.(.)/; $data_end =~ /^.(.)/;
$data_end = uc("0$1"); $data_end = "0$1";
#Log3 $hash->{NAME}, 5, "EnOcean_sec_convertToSecure: Crypted Data: $data_end"; }
Log3 $name, 5, "EnOcean $name EnOcean_sec_convertToSecure: RORG-S: $rorgs Crypted Data: $data_end";
# calc MAC # calc MAC
my $macAlgo = AttrVal($name, "macAlgo", undef); my $macAlgo = AttrVal($name, "macAlgo", undef);
return("MAC Algorithm not defined", $rorg, $data, $response, 2) if (!defined $macAlgo); return("MAC Algorithm not defined", $rorgs, $data, $response, 2) if (!defined($macAlgo));
my $mac = EnOcean_sec_generateMAC($pKey, $rorg . $data_end . $rlc, $macAlgo); my $mac = EnOcean_sec_generateMAC($pKey, $rorgs . $data_end . $rlc, $macAlgo);
# combine message # combine message
$data = $data_end . uc($mac); if (AttrVal($name, 'rlcTX', 'false') eq 'true') {
#Log3 $hash->{NAME}, 5, "EnOcean_sec_convertToSecure: Crypted Payload: $data"; $data = uc($data_end . $rlc . $mac);
return(undef, $rorg, $data, $response, 5); } else {
$data = uc($data_end . $mac);
}
Log3 $name, 5, "EnOcean $name EnOcean_sec_convertToSecure: RORG-S: $rorgs Crypted Payload: $data";
return(undef, $rorgs, $data, $response, 5);
} }
sub EnOcean_NumericSort { sub EnOcean_NumericSort {
@@ -19140,6 +19323,8 @@ sub EnOcean_Delete($$) {
<li>M5-38-08 Gateway, Switching [Eltako FSR14] old version<br></li> <li>M5-38-08 Gateway, Switching [Eltako FSR14] old version<br></li>
<li>N5-38-08 Gateway, Switching [Eltako TF61L, TF61R, TF100A, TF100L]<br></li> <li>N5-38-08 Gateway, Switching [Eltako TF61L, TF61R, TF100A, TF100L]<br></li>
<li>O5-38-08 Gateway, Switching [Eltako FSR14] with teachMethod confirm<br></li> <li>O5-38-08 Gateway, Switching [Eltako FSR14] with teachMethod confirm<br></li>
<li>P5-38-08 Gateway, Switching [Eltako FSR14M] with teachMethod confirm.<br>
In the second step, the automated meter reading electricity function must be taught in via <a href="#EnOcean-teach-in">Teach-In / Teach-Out</a>.<br></li>
<li>G5-3F-7F Shutter [Eltako FSB]<br></li> <li>G5-3F-7F Shutter [Eltako FSB]<br></li>
<li>H5-3F-7F Shutter [Eltako TF61J]<br></li> <li>H5-3F-7F Shutter [Eltako TF61J]<br></li>
<li>I5-3F-7F Shutter [Eltako FRM60] - MSC teach-in supported<br></li> <li>I5-3F-7F Shutter [Eltako FRM60] - MSC teach-in supported<br></li>
@@ -21525,7 +21710,7 @@ sub EnOcean_Delete($$) {
<li><a id="EnOcean-attr-secLevel">secLevel</a> encapsulation|encryption|off, [secLevel] = off is default<br> <li><a id="EnOcean-attr-secLevel">secLevel</a> encapsulation|encryption|off, [secLevel] = off is default<br>
Security level of the data Security level of the data
</li> </li>
<li><a id="EnOcean-attr-secMode">secMode</a> rcv|snd|bidir<br> <li><a id="EnOcean-attr-secMode">secMode</a> rcv|snd|biDir<br>
Telegram direction, which is secured Telegram direction, which is secured
</li> </li>
<li><a id="EnOcean-attr-sendDevStatus">sendDevStatus</a> no|yes, [sendDevStatus] = no is default.<br> <li><a id="EnOcean-attr-sendDevStatus">sendDevStatus</a> no|yes, [sendDevStatus] = no is default.<br>
@@ -22750,7 +22935,7 @@ sub EnOcean_Delete($$) {
<li>Automated meter reading (AMR), Electricity (EEP A5-12-01)<br> <li>Automated meter reading (AMR), Electricity (EEP A5-12-01)<br>
[Eltako DSZ14WDRS, FSS12, Thermokon SR-MI-HS, untested]<br> [Eltako DSZ14WDRS, FSS12, Thermokon SR-MI-HS, untested]<br>
[Eltako DSZ14DRS, FWZ12-16A tested]<br> [Eltako DSZ14DRS, DSZ14DRSZ, FWZ12-16A tested]<br>
<ul> <ul>
<li>P/W</li> <li>P/W</li>
<li>power: P/W</li> <li>power: P/W</li>
@@ -22761,7 +22946,9 @@ sub EnOcean_Delete($$) {
</ul><br> </ul><br>
The attr subType must be autoMeterReading.01 and attr The attr subType must be autoMeterReading.01 and attr
manufID must be 00D for Eltako Devices. This is done if the device was manufID must be 00D for Eltako Devices. This is done if the device was
created by autocreate. created by autocreate.<br>
For the Eltako meter DSZ14DRSZ, the attribute model must be manually set to Eltako_DSZ14DRSZ.<br>
The Eltako device Eltako FSR14M should be taught in via <a href="#EnOcean-Inofficial-EEP">Inofficial EEP</a> P5-38-08.<br>
</li> </li>
<br><br> <br><br>