diff --git a/fhem/FHEM/00_TCM.pm b/fhem/FHEM/00_TCM.pm index f6cd72206..37b7d6475 100755 --- a/fhem/FHEM/00_TCM.pm +++ b/fhem/FHEM/00_TCM.pm @@ -3,14 +3,14 @@ # by r.koenig at koeniglich.de # -# This modules handles the communication with a TCM120 or TCM310 EnOcean +# This modules handles the communication with a TCM 120 or TCM 310 / TCM 400J EnOcean # transceiver chip. As the protocols are radically different, this is actually 2 # drivers in one. # See also: # TCM_120_User_Manual_V1.53_02.pdf -# EnOcean Serial Protocol 3 (ESP3) (for the TCM310) +# EnOcean Serial Protocol 3 (ESP3) (for the TCM 310, TCM 400J) -# TODO: +# TODO: # Check BSC Temp # Check Stick Temp # Check Stick WriteRadio @@ -20,13 +20,12 @@ package main; use strict; use warnings; -use Time::HiRes qw(gettimeofday); +use Time::HiRes qw(gettimeofday usleep); if( $^O =~ /Win/ ) { require Win32::SerialPort; } else { require Device::SerialPort; -} - +} sub TCM_Read($); sub TCM_ReadAnswer($$); sub TCM_Ready($); @@ -55,12 +54,14 @@ TCM_Initialize($) $hash->{MatchList} = \%matchList; # Normal devices - $hash->{DefFn} = "TCM_Define"; - $hash->{UndefFn} = "TCM_Undef"; - $hash->{GetFn} = "TCM_Get"; - $hash->{SetFn} = "TCM_Set"; - $hash->{AttrFn} = "TCM_Attr"; - $hash->{AttrList}= "do_not_notify:1,0 dummy:1,0 blockSenderID:own,no learningMode:always,demand,nearfield"; + $hash->{DefFn} = "TCM_Define"; + $hash->{UndefFn} = "TCM_Undef"; + $hash->{GetFn} = "TCM_Get"; + $hash->{SetFn} = "TCM_Set"; + $hash->{NotifyFn} = "TCM_Notify"; + $hash->{AttrFn} = "TCM_Attr"; + $hash->{AttrList} = "do_not_notify:1,0 dummy:1,0 blockSenderID:own,no learningMode:always,demand,nearfield " . + "sendInterval:0,25,40,50,100,150,200,250"; } # Define @@ -71,37 +72,29 @@ TCM_Define($$) my @a = split("[ \t][ \t]*", $def); my $name = $a[0]; my $model = $a[2]; - my $baseID; - return "TCM: wrong syntax, correct is: define TCM [120|310] ". + return "TCM: wrong syntax, correct is: define TCM [ESP2|ESP3] ". "{devicename[\@baudrate]|ip:port|none}" - if(@a != 4 || $model !~ m/^(120|310)$/); + if(@a != 4 || $model !~ m/^(ESP2|ESP3|120|310)$/); DevIo_CloseDev($hash); my $dev = $a[3]; $hash->{DeviceName} = $dev; + # old model names replaced + $model = "ESP2" if ($model eq "120"); + $model = "ESP3" if ($model eq "310"); $hash->{MODEL} = $model; - + $hash->{BaseID} = "00000000"; + $hash->{LastID} = "00000000"; if($dev eq "none") { - #Log 1, "TCM: $name device is none, commands will be echoed only"; Log3 undef, 1, "TCM $name device is none, commands will be echoed only"; $attr{$name}{dummy} = 1; return undef; } - my $ret = DevIo_OpenDev($hash, 0, undef); - my @getBaseID = ("get", "baseID"); - if (TCM_Get($hash, @getBaseID) =~ /[Ff]{2}[\dA-Fa-f]{6}/ ) { - $hash->{BaseID} = sprintf "%08X", hex $&; - $hash->{LastID} = sprintf "%08X", (hex $&) + 127; - } else { - $hash->{BaseID} = "00000000"; - $hash->{LastID} = "00000000"; - } return $ret; } - # Write # Input is header and data (HEX), without CRC sub @@ -113,7 +106,7 @@ TCM_Write($$$) return if(!defined($fn)); my $bstring; - if($hash->{MODEL} eq "120") { + if($hash->{MODEL} eq "ESP2") { # TCM 120 (ESP2) if (!$fn) { # command with ESP2 format @@ -122,14 +115,14 @@ TCM_Write($$$) # command with ESP3 format my $packetType = hex(substr($fn, 6, 2)); if ($packetType != 1) { - Log3 $name, 1, "TCM $name: Packet Type not supported."; + Log3 $name, 1, "TCM $name Packet Type not supported."; return; } my $odataLen = hex(substr($fn, 4, 2)); if ($odataLen != 0) { - Log3 $name, 1, "TCM $name: Radio Telegram with optional Data not supported."; + Log3 $name, 1, "TCM $name Radio Telegram with optional Data not supported."; return; - } + } #my $mdataLen = hex(substr($fn, 0, 4)); my $rorg = substr ($msg, 0, 2); # translate the RORG to ORG @@ -140,13 +133,13 @@ TCM_Write($$$) if($rorgmap{$rorg}) { $rorg = $rorgmap{$rorg}; } else { - Log3 $name, 1, "TCM $name: unknown RORG mapping for $rorg"; + Log3 $name, 1, "TCM $name unknown RORG mapping for $rorg"; } if ($rorg eq "05" || $rorg eq "06") { - $bstring = "6B" . $rorg . substr ($msg, 2, 2) . "000000" . substr ($msg, 4); + $bstring = "6B" . $rorg . substr ($msg, 2, 2) . "000000" . substr ($msg, 4); } else { - $bstring = "6B" . $rorg . substr ($msg, 2); - } + $bstring = "6B" . $rorg . substr ($msg, 2); + } } $bstring = "A55A" . $bstring . TCM_CSUM($bstring); } else { @@ -155,6 +148,8 @@ TCM_Write($$$) } Log3 $name, 5, "TCM $name sending $bstring"; DevIo_SimpleWrite($hash, $bstring, 1); + # next commands will be sent with a delay + usleep(int(AttrVal($name, "sendInterval", 100))); } # ESP2 CRC @@ -232,7 +227,7 @@ TCM_Read($) my $data = $hash->{PARTIAL} . uc(unpack('H*', $buf)); Log3 $name, 5, "TCM $name RAW: $data"; - if($hash->{MODEL} == 120) { + if($hash->{MODEL} eq "ESP2") { # TCM 120 while($data =~ m/^A55A(.B.{20})(..)/) { @@ -255,7 +250,6 @@ TCM_Read($) if($orgmap{$org}) { $org = $orgmap{$org}; } else { - #Log 1, "TCM120: unknown ORG mapping for $org"; Log3 undef, 1, "TCM unknown ORG mapping for $org"; } if ($org ne "A5") { @@ -263,7 +257,7 @@ TCM_Read($) $d1 = substr($d1, 0, 2); } if ($blockSenderID eq "own" && (hex $id) >= $baseID && (hex $id) <= $lastID) { - Log3 $name, 4, "TCM $name Telegram from $id blocked."; + Log3 $name, 4, "TCM $name Telegram from $id blocked."; } else { Dispatch($hash, "EnOcean:$packetType:$org:$d1:$id:$status:01FFFFFFFF0000", undef); } @@ -279,7 +273,6 @@ TCM_Read($) if($orgmap{$org}) { $org = $orgmap{$org}; } else { - #Log 1, "TCM120: unknown ORG mapping for $org"; Log3 undef, 1, "TCM unknown ORG mapping for $org"; } if ($org ne "A5") { @@ -287,7 +280,7 @@ TCM_Read($) $d1 = substr($d1, 0, 2); } if ($blockSenderID eq "own" && (hex $id) >= $baseID && (hex $id) <= $lastID) { - Log3 $name, 4, "TCM $name Telegram from $id blocked."; + Log3 $name, 4, "TCM $name Telegram from $id blocked."; } else { Dispatch($hash, "EnOcean:$packetType:$org:$d1:$id:$status:01FFFFFFFF0000", undef); } @@ -341,7 +334,7 @@ TCM_Read($) if ($RSSI > 87) { $receivingQuality = "bad"; } elsif ($RSSI > 75) { - $receivingQuality = "good"; + $receivingQuality = "good"; } my %addvals = ( PacketType => $packetType, @@ -350,12 +343,11 @@ TCM_Read($) RSSI => -$RSSI, ReceivingQuality => $receivingQuality, RepeatingCounter => $repeatingCounter, - SecurityLevel => hex($4), ); $hash->{RSSI} = -$RSSI; if ($blockSenderID eq "own" && (hex $id) >= $baseID && (hex $id) <= $lastID) { - Log3 $name, 4, "TCM $name Telegram from $id blocked."; + Log3 $name, 4, "TCM $name Telegram from $id blocked."; } else { Dispatch($hash, "EnOcean:$packetType:$org:$d1:$id:$status:$odata", \%addvals); } @@ -374,7 +366,6 @@ TCM_Read($) "91" => "BASEID_MAX_REACHED", ); $rc = $codes{$rc} if($codes{$rc}); - #Log (($rc eq "OK") ? $ll5 : $ll2, "TCM $name RESPONSE: $rc"); Log3 ($name, ($rc eq "OK") ? 5 : 2, "TCM $name RESPONSE: $rc"); } elsif($packetType == 3) { @@ -397,6 +388,14 @@ TCM_Read($) # packet type REMOTE_MAN_COMMAND Log3 $name, 1, "TCM: $name packet type REMOTE_MAN_COMMAND not supported: $data"; + } elsif($packetType == 9) { + # packet type RADIO_MESSAGE + Log3 $name, 1, "TCM: $name packet type RADIO_MESSAGE not supported: $data"; + + } elsif($packetType == 10) { + # packet type RADIO_ADVANCED + Log3 $name, 1, "TCM: $name packet type RADIO_ADVANCED not supported: $data"; + } else { Log3 $name, 1, "TCM $name unknown packet type $packetType: $data"; @@ -445,15 +444,11 @@ TCM_Parse120($$$) { my ($hash,$rawmsg,$ret) = @_; my $name = $hash->{NAME}; - - Log3 $name, 5, "TCM Parse $rawmsg"; - + Log3 $name, 5, "TCM $name Parse $rawmsg"; my $msg = ""; my $cmd = $parsetbl120{substr($rawmsg, 0, 4)}; - if(!$cmd) { $msg ="Unknown command: $rawmsg"; - } else { if($cmd->{expr}) { $msg = $cmd->{msg}." " if(!$ret); @@ -461,16 +456,12 @@ TCM_Parse120($$$) $rawstr =~ s/[\r\n]//g; my @a = map { ord($_) } split("", $rawstr); $msg .= eval $cmd->{expr}; - } else { return "" if($cmd ->{msg} eq "OK" && !$ret); # SKIP Ok $msg = $cmd->{msg}; - } - } - - Log3 $name, 2, "TCM $name $msg" if(!$ret); + Log3 $name, 2, "TCM $name RESPONSE: $msg" if(!$ret); return $msg; } @@ -495,7 +486,6 @@ TCM_Parse310($$$) Log3 $name, 5, "TCM Parse $rawmsg"; my $rc = substr($rawmsg, 0, 2); my $msg = ""; - if($rc ne "00") { $msg = $rc310{$rc}; $msg = "Unknown return code $rc" if(!$msg); @@ -506,16 +496,20 @@ TCM_Parse310($$$) my ($off, $len, $type) = split(",", $ptr->{$k}); my $data = substr($rawmsg, $off*2, $len*2); $data = pack('H*', $data) if($type && $type eq "STR"); - push @ans, "$k=$data"; + #push @ans, "$k=$data"; + push @ans, "$k: $data"; } - $msg = join(",", @ans); + $msg = join(" ", @ans); + #$msg = join(",", @ans); + } + if ($msg eq "") { + Log3 $name, 2, "TCM $name RESPONSE: OK"; + } else { + Log3 $name, 2, "TCM $name RESPONSE: $msg"; } - - Log3 $name, 2, "TCM $name $msg"; return $msg; } - # Ready sub TCM_Ready($) @@ -534,40 +528,30 @@ TCM_Ready($) # Get commands TCM 120 my %gets120 = ( "sensitivity" => "AB48", - "idbase" => "AB58", "baseID" => "AB58", "modem_status" => "AB68", - "sw_ver" => "AB4B", + "version" => "AB4B", ); # Get commands TCM 310 my %gets310 = ( - "sw_ver" => {cmd => "03", - APPVersion => "1,4", - APIVersion => "5,4", - ChipID => "9,4", - ChipVersion => "13,4", - Desc => "17,16,STR",}, "version" => {cmd => "03", APPVersion => "1,4", APIVersion => "5,4", ChipID => "9,4", ChipVersion => "13,4", Desc => "17,16,STR",}, - "idbase" => {cmd => "08", - BaseID => "1,4", - RemainingWriteCycles => "5,1",}, "baseID" => {cmd => "08", BaseID => "1,4", RemainingWriteCycles => "5,1",}, "repeater" => {cmd => "0A", - repEnable => "1,1", - repLevel => "2,1",}, + RepEnable => "1,1", + RepLevel => "2,1",}, # "secureDev" => {cmd => "1B01", # SLF => "1,1", # devID => "2,4",}, "numSecureDev" => {cmd => "1D", - number => "1,1",}, + Number => "1,1",}, ); # Get @@ -581,40 +565,33 @@ TCM_Get($@) my $cmd = $a[1]; my ($err, $msg); - if($hash->{MODEL} eq "120") { + if($hash->{MODEL} eq "ESP2") { # TCM 120 my $rawcmd = $gets120{$cmd}; return "Unknown argument $cmd, choose one of " . join(" ", sort keys %gets120) if(!defined($rawcmd)); - + Log3 $name, 2, "TCM get $name $cmd"; $rawcmd .= "000000000000000000"; TCM_Write($hash, "", $rawcmd); - ($err, $msg) = TCM_ReadAnswer($hash, "get $cmd"); $msg = TCM_Parse120($hash, $msg, 1) if(!$err); - } else { # TCM 310 my $cmdhash = $gets310{$cmd}; return "Unknown argument $cmd, choose one of " . join(" ", sort keys %gets310) if(!defined($cmdhash)); - + Log3 $name, 2, "TCM get $name $cmd"; my $cmdHex = $cmdhash->{cmd}; TCM_Write($hash, sprintf("%04X0005", length($cmdHex)/2), $cmdHex); ($err, $msg) = TCM_ReadAnswer($hash, "get $cmd"); $msg = TCM_Parse310($hash, $msg, $cmdhash) if(!$err); - } - if($err) { - #Log 1, $err; - Log3 undef, 1, $err; + Log3 undef, 1, TCM $name $err; return $err; } - $hash->{READINGS}{$cmd}{VAL} = $msg; - $hash->{READINGS}{$cmd}{TIME} = TimeNow(); + readingsSingleUpdate($hash, $cmd, $msg, 1); return $msg; - } # RemovePair @@ -627,9 +604,7 @@ TCM_RemovePair($) # Set commands TCM 120 my %sets120 = ( # Name, Data to send to the CUL, Regexp for the answer - "pairForSec" => { cmd => "AB18", arg => "\\d+" }, "teach" => { cmd => "AB18", arg => "\\d+" }, - "idbase" => { cmd => "AB18", arg => "FF[8-9A-F][0-9A-F]{5}" }, "baseID" => { cmd => "AB18", arg => "FF[8-9A-F][0-9A-F]{5}" }, "sensitivity" => { cmd => "AB08", arg => "0[01]" }, "sleep" => { cmd => "AB09" }, @@ -641,16 +616,15 @@ my %sets120 = ( # Name, Data to send to the CUL, Regexp for the answer # Set commands TCM 310 my %sets310 = ( - "pairForSec" => { cmd => "AB18", arg=> "\\d+" }, "teach" => { cmd => "AB18", arg=> "\\d+" }, "sleep" => { cmd => "01", arg => "00[0-9A-F]{6}" }, "reset" => { cmd => "02" }, "bist" => { cmd => "06", BIST_Result => "1,1", }, - "idbase" => { cmd => "07", arg => "FF[8-9A-F][0-9A-F]{5}" }, "baseID" => { cmd => "07", arg => "FF[8-9A-F][0-9A-F]{5}" }, - "repeater" => { cmd => "09", arg => "0[0-1]0[0-2]" }, - "maturity" => { cmd => "10", arg => "0[0-1]" }, - "subtel" => { cmd => "11", arg => "0[0-1]" }, + "repeater" => { cmd => "09", arg => "0[0-1]0[0-2]" }, + "maturity" => { cmd => "10", arg => "0[0-1]" }, + "subtel" => { cmd => "11", arg => "0[0-1]" }, + "mode" => { cmd => "1C", arg => "0[0-1]" }, ); # Set @@ -665,7 +639,7 @@ TCM_Set($@) my $arg = $a[2]; my ($err, $msg); - my $chash = ($hash->{MODEL} eq "120" ? \%sets120 : \%sets310); + my $chash = ($hash->{MODEL} eq "ESP2" ? \%sets120 : \%sets310); my $cmdhash = $chash->{$cmd}; return "Unknown argument $cmd, choose one of ".join(" ",sort keys %{$chash}) if(!defined($cmdhash)); @@ -678,14 +652,15 @@ TCM_Set($@) if($arg !~ m/$argre/i); $cmdHex .= $arg; } + Log3 $name, 2, "TCM set $name $cmd $arg"; - if($cmd eq "pairForSec" || $cmd eq "teach") { + if($cmd eq "teach") { $hash->{Teach} = 1; InternalTimer(gettimeofday()+$arg, "TCM_RemovePair", $hash, 1); return; } - if($hash->{MODEL} eq "120") { + if($hash->{MODEL} eq "ESP2") { # TCM 120 if($cmdHex eq "") { # wake is very special DevIo_SimpleWrite($hash, "AA", 1); @@ -695,23 +670,29 @@ TCM_Set($@) $cmdHex .= "0"x(22-length($cmdHex)); # Padding with 0 TCM_Write($hash, "", $cmdHex); ($err, $msg) = TCM_ReadAnswer($hash, "get $cmd"); - $msg = TCM_Parse120($hash, $msg, 1) - if(!$err); + $msg = TCM_Parse120($hash, $msg, 1) if(!$err); } else { # TCM310 TCM_Write($hash, sprintf("%04X0005", length($cmdHex)/2), $cmdHex); ($err, $msg) = TCM_ReadAnswer($hash, "set $cmd"); - $msg = TCM_Parse310($hash, $msg, $cmdhash) - if(!$err); + $msg = TCM_Parse310($hash, $msg, $cmdhash) if(!$err); } - if($err) { - #Log 1, $err; - Log3 undef, 1, $err; + Log3 undef, 1, "TCM $name $err"; return $err; } + + my @setCmdReadingsUpdate = ("repeater", "maturity", "mode"); + foreach(@setCmdReadingsUpdate) { + if ($_ eq $cmd && $msg eq "") { + if ($_ eq "repeater") { + $arg = "RepEnable: " . substr($arg, 0, 2) . " RepLevel: " . substr($arg, 2, 2); + } + readingsSingleUpdate($hash, $cmd, $arg, 1); + } + } return $msg; } @@ -732,7 +713,7 @@ TCM_ReadAnswer($$) if($^O =~ m/Win/ && $hash->{USBDev}) { $hash->{USBDev}->read_const_time($to*1000); # set timeout (ms) # Read anstatt input sonst funzt read_const_time nicht. - $buf = $hash->{USBDev}->read(999); + $buf = $hash->{USBDev}->read(999); return ("$name Timeout reading answer for $arg", undef) if(length($buf) == 0); @@ -757,9 +738,9 @@ TCM_ReadAnswer($$) if(defined($buf)) { $data .= uc(unpack('H*', $buf)); - Log3 $name, 5, "TCM RAW ReadAnswer: $data"; + Log3 $name, 5, "TCM $name RAW ReadAnswer: $data"; - if($hash->{MODEL} eq "120") { + if($hash->{MODEL} eq "ESP2") { # TCM 120 if(length($data) >= 28) { return ("$arg: Bogus answer received: $data", undef) @@ -806,6 +787,7 @@ TCM_Attr(@) { if ($attrName eq "blockSenderID") { if (!defined $attrVal) { + } elsif ($attrVal !~ m/^(own|no)$/) { Log3 $name, 2, "EnOcean $name attribute-value [$attrName] = $attrVal wrong"; CommandDeleteAttr(undef, "$name $attrName"); @@ -819,6 +801,82 @@ TCM_Attr(@) { CommandDeleteAttr(undef, "$name $attrName"); } + } elsif ($attrName eq "sendInterval") { + if (!defined $attrVal){ + + } elsif (($attrVal + 0) < 0 || ($attrVal + 0) > 250) { + Log3 $name, 2, "EnOcean $name attribute-value [$attrName] = $attrVal wrong or out of range"; + CommandDeleteAttr(undef, "$name $attrName"); + } + + } + return undef; +} + +sub TCM_Notify(@) { + my ($hash, $dev) = @_; + my $name = $hash->{NAME}; + if ($dev->{NAME} eq "global" && grep (m/^INITIALIZED$/,@{$dev->{CHANGED}})){ + my $attrVal; + my $setCmdVal = ""; + my @setCmd = ("set", "reset", $setCmdVal); + # read and discard receive buffer, modem reset + if ($hash->{MODEL} eq "ESP2") { + } else { + TCM_ReadAnswer($hash, "set reset"); + TCM_Set($hash, @setCmd); + #usleep(200); + } + # default attributes + my %setAttrInit = ("sendInterval" => {ESP2 => 100, ESP3 => 0}); + foreach(keys %setAttrInit) { + $attrVal = AttrVal($name, $_, undef); + if(!defined $attrVal && defined $setAttrInit{$_}{$hash->{MODEL}}) { + $attr{$name}{$_} = $setAttrInit{$_}{$hash->{MODEL}}; + Log3 $name, 2, "TCM $name Attribute $_ $setAttrInit{$_}{$hash->{MODEL}} initialized"; + } + } + # default transceiver parameter + my %setCmdRestore = ("mode" => "00", + "maturity" => "01", + "repeater" => "RepEnable: 00 RepLevel: 00" + ); + foreach(keys %setCmdRestore) { + $setCmdVal = ReadingsVal($name, $_, undef); + if (defined $setCmdVal) { + if ($_ eq "repeater") { + $setCmdVal = substr($setCmdVal, 11, 2) . substr($setCmdVal, 24, 2); + $setCmdVal = "0000" if ($setCmdVal eq "0001"); + } + @setCmd = ("set", $_, $setCmdVal); + TCM_Set($hash, @setCmd); + Log3 $name, 2, "TCM $name $_ $setCmdVal restored"; + } else { + if ($hash->{MODEL} eq "ESP2") { + + } else { + if ($_ eq "repeater") { + $setCmdVal = substr($setCmdRestore{$_}, 11, 2) . substr($setCmdRestore{$_}, 24, 2); + } else { + $setCmdVal = $setCmdRestore{$_}; + } + @setCmd = ("set", $_, $setCmdVal); + my $msg = TCM_Set($hash, @setCmd); + Log3 $name, 2, "TCM $name $_ $setCmdVal initialized" if ($msg eq ""); + } + } + } + my @getBaseID = ("get", "baseID"); + if (TCM_Get($hash, @getBaseID) =~ /[Ff]{2}[\dA-Fa-f]{6}/ ) { + $hash->{BaseID} = sprintf "%08X", hex $&; + $hash->{LastID} = sprintf "%08X", (hex $&) + 127; + } else { + $hash->{BaseID} = "00000000"; + $hash->{LastID} = "00000000"; + } + CommandSave(undef, undef); + readingsSingleUpdate($hash, "state", "initialized", 1); + Log3 $name, 2, "TCM $name initialized"; } return undef; } @@ -836,12 +894,11 @@ TCM_Undef($$) $defs{$d}{IODev} == $hash) { my $lev = ($reread_active ? 4 : 2); - #Log GetLogLevel($name,$lev), "deleting port for $d"; Log3 $name, $lev, "TCM deleting port for $d"; delete $defs{$d}{IODev}; } } - DevIo_CloseDev($hash); + DevIo_CloseDev($hash); return undef; } @@ -853,14 +910,14 @@ TCM_Undef($$)

TCM