00_TCM.pm 61 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662
  1. ##############################################
  2. # $Id: 00_TCM.pm 17688 2018-11-05 18:01:41Z klaus.schauer $
  3. # This modules handles the communication with a TCM 120 or TCM 310 / TCM 400J /
  4. # TCM 515 EnOcean transceiver chip. As the protocols are radically different,
  5. # this is actually 2 drivers in one.
  6. # See also:
  7. # TCM_120_User_Manual_V1.53_02.pdf
  8. # EnOcean Serial Protocol 3 (ESP3) (for the TCM 310, TCM 400J, TCM 515)
  9. # TODO:
  10. # Check BSC Temp
  11. # Check Stick Temp
  12. # Check Stick WriteRadio
  13. package main;
  14. use strict;
  15. use warnings;
  16. use Time::HiRes qw(gettimeofday usleep);
  17. if( $^O =~ /Win/ ) {
  18. require Win32::SerialPort;
  19. } else {
  20. require Device::SerialPort;
  21. }
  22. sub TCM_Read($);
  23. sub TCM_ReadAnswer($$);
  24. sub TCM_Ready($);
  25. sub TCM_Write($$$);
  26. sub TCM_Parse120($$$);
  27. sub TCM_Parse310($$$);
  28. sub TCM_CRC8($);
  29. sub TCM_CSUM($);
  30. sub
  31. TCM_Initialize($)
  32. {
  33. my ($hash) = @_;
  34. require "$attr{global}{modpath}/FHEM/DevIo.pm";
  35. # Provider
  36. $hash->{ReadFn} = "TCM_Read";
  37. $hash->{WriteFn} = "TCM_Write";
  38. $hash->{ReadyFn} = "TCM_Ready";
  39. $hash->{Clients} = ":EnOcean:";
  40. my %matchList= (
  41. "1:EnOcean" => "^EnOcean:",
  42. );
  43. $hash->{MatchList} = \%matchList;
  44. # Normal devices
  45. $hash->{DefFn} = "TCM_Define";
  46. $hash->{FingerprintFn} = "TCM_Fingerprint";
  47. $hash->{UndefFn} = "TCM_Undef";
  48. $hash->{GetFn} = "TCM_Get";
  49. $hash->{SetFn} = "TCM_Set";
  50. $hash->{NotifyFn} = "TCM_Notify";
  51. $hash->{AttrFn} = "TCM_Attr";
  52. $hash->{AttrList} = "baseID blockSenderID:own,no comModeUTE:auto,biDir,uniDir comType:TCM,RS485 do_not_notify:1,0 " .
  53. "dummy:1,0 fingerprint:off,on learningMode:always,demand,nearfield " .
  54. "sendInterval:0,25,40,50,100,150,200,250 smartAckMailboxMax:slider,0,1,20 " .
  55. "smartAckLearnMode:simple,advance,advanceSelectRep";
  56. }
  57. # Define
  58. sub TCM_Define($$)
  59. {
  60. my ($hash, $def) = @_;
  61. my @a = split("[ \t][ \t]*", $def);
  62. my $name = $a[0];
  63. my $model = $a[2];
  64. return "TCM: wrong syntax, correct is: define <name> TCM [ESP2|ESP3] ".
  65. "{devicename[\@baudrate]|ip:port|none}"
  66. if(@a != 4 || $model !~ m/^(ESP2|ESP3|120|310)$/);
  67. $hash->{NOTIFYDEV} = "global";
  68. DevIo_CloseDev($hash);
  69. my $dev = $a[3];
  70. $hash->{DeviceName} = $dev;
  71. # old model names replaced
  72. $model = "ESP2" if ($model eq "120");
  73. $model = "ESP3" if ($model eq "310");
  74. $hash->{MODEL} = $model;
  75. $hash->{BaseID} = "00000000";
  76. $hash->{LastID} = "00000000";
  77. if($dev eq "none") {
  78. Log3 undef, 1, "TCM $name device is none, commands will be echoed only";
  79. $attr{$name}{dummy} = 1;
  80. return undef;
  81. }
  82. my $ret = DevIo_OpenDev($hash, 0, undef);
  83. return $ret;
  84. }
  85. # Initialize serial communication
  86. sub
  87. TCM_InitSerialCom($)
  88. {
  89. my ($hash) = @_;
  90. my $name = $hash->{NAME};
  91. if ($hash->{STATE} eq "disconnected") {
  92. Log3 $name, 2, "TCM $name not initialized";
  93. return undef;
  94. }
  95. my $attrVal;
  96. my $comType = AttrVal($name, "comType", "TCM");
  97. my $setCmdVal = "";
  98. my @setCmd = ("set", "reset", $setCmdVal);
  99. # read and discard receive buffer, modem reset
  100. if ($hash->{MODEL} eq "ESP2") {
  101. if ($comType eq "TCM") {
  102. TCM_ReadAnswer($hash, "set reset");
  103. #TCM_Read($hash);
  104. $hash->{PARTIAL} = '';
  105. TCM_Set($hash, @setCmd);
  106. }
  107. } else {
  108. #TCM_ReadAnswer($hash, "set reset");
  109. #TCM_Read($hash);
  110. #$hash->{PARTIAL} = '';
  111. delete $hash->{helper}{awaitCmdResp};
  112. TCM_Set($hash, @setCmd);
  113. }
  114. # default attributes
  115. my %setAttrInit;
  116. if ($comType eq "RS485" || $hash->{DeviceName} eq "none") {
  117. %setAttrInit = (sendInterval => {ESP2 => 100, ESP3 => 0},
  118. learningMode => {ESP2 => "always", ESP3 => "always"}
  119. );
  120. }else {
  121. %setAttrInit = ("sendInterval" => {ESP2 => 100, ESP3 => 0});
  122. }
  123. foreach(keys %setAttrInit) {
  124. $attrVal = AttrVal($name, $_, undef);
  125. if(!defined $attrVal && defined $setAttrInit{$_}{$hash->{MODEL}}) {
  126. $attr{$name}{$_} = $setAttrInit{$_}{$hash->{MODEL}};
  127. Log3 $name, 2, "TCM $name Attribute $_ $setAttrInit{$_}{$hash->{MODEL}} initialized";
  128. }
  129. }
  130. # 500 ms pause
  131. usleep(500 * 1000);
  132. # read transceiver IDs
  133. my $baseID = AttrVal($name, "baseID", undef);
  134. if (defined($baseID)) {
  135. $hash->{BaseID} = $baseID;
  136. $hash->{LastID} = sprintf "%08X", (hex $baseID) + 127;
  137. } elsif ($comType ne "RS485" && $hash->{DeviceName} ne "none") {
  138. my @getBaseID = ("get", "baseID");
  139. if (TCM_Get($hash, @getBaseID) =~ /[Ff]{2}[\dA-Fa-f]{6}/) {
  140. $hash->{BaseID} = sprintf "%08X", hex $&;
  141. $hash->{LastID} = sprintf "%08X", (hex $&) + 127;
  142. } else {
  143. $hash->{BaseID} = "00000000";
  144. $hash->{LastID} = "00000000";
  145. }
  146. }
  147. if ($hash->{MODEL} eq "ESP3" && $comType ne "RS485" && $hash->{DeviceName} ne "none") {
  148. # get chipID
  149. my @getChipID = ('get', 'version');
  150. if (TCM_Get($hash, @getChipID) =~ m/ChipID:.([\dA-Fa-f]{8})/) {
  151. $hash->{ChipID} = sprintf "%08X", hex $1;
  152. }
  153. }
  154. # default transceiver parameter
  155. if ($comType ne "RS485" && $hash->{DeviceName} ne "none") {
  156. my %setCmdRestore = (mode => "00",
  157. maturity => "01",
  158. repeater => "RepEnable: 00 RepLevel: 00",
  159. smartAckMailboxMax => 0
  160. );
  161. foreach(keys %setCmdRestore) {
  162. $setCmdVal = ReadingsVal($name, $_, AttrVal($name, $_, undef));
  163. if (defined $setCmdVal) {
  164. if ($_ eq "repeater") {
  165. $setCmdVal = substr($setCmdVal, 11, 2) . substr($setCmdVal, 24, 2);
  166. $setCmdVal = "0000" if ($setCmdVal eq "0001");
  167. }
  168. @setCmd = ("set", $_, $setCmdVal);
  169. TCM_Set($hash, @setCmd);
  170. Log3 $name, 2, "TCM $name $_ $setCmdVal restored";
  171. } else {
  172. if ($hash->{MODEL} eq "ESP2") {
  173. } else {
  174. if ($_ eq "repeater") {
  175. $setCmdVal = substr($setCmdRestore{$_}, 11, 2) . substr($setCmdRestore{$_}, 24, 2);
  176. } else {
  177. $setCmdVal = $setCmdRestore{$_};
  178. }
  179. @setCmd = ("set", $_, $setCmdVal);
  180. my $msg = TCM_Set($hash, @setCmd);
  181. Log3 $name, 2, "TCM $name $_ $setCmdVal initialized" if ($msg eq "");
  182. }
  183. }
  184. }
  185. }
  186. #CommandSave(undef, undef);
  187. readingsSingleUpdate($hash, "state", "initialized", 1);
  188. Log3 $name, 2, "TCM $name initialized";
  189. return undef;
  190. }
  191. sub
  192. TCM_Fingerprint($$)
  193. {
  194. my ($IODev, $msg) = @_;
  195. return ($IODev, $msg) if (AttrVal($IODev, "fingerprint", 'off') eq 'off');
  196. my @msg = split(":", $msg);
  197. if ($msg[1] == 1) {
  198. #EnOcean:PacketType:RORG:MessageData:SourceID:Status:OptionalData
  199. substr($msg[5], 1, 1, "0");
  200. substr($msg[6], 0, 2, "01");
  201. substr($msg[6], 10, 4, "0000");
  202. } elsif ($msg[1] == 2) {
  203. #EnOcean:PacketType:ResposeCode:MessageData:OptionalData
  204. } elsif ($msg[1] == 3) {
  205. } elsif ($msg[1] == 4) {
  206. #EnOcean:PacketType:eventCode:MessageData
  207. } elsif ($msg[1] == 5) {
  208. } elsif ($msg[1] == 6) {
  209. #EnOcean:PacketType:smartAckCode:MessageData
  210. } elsif ($msg[1] == 7) {
  211. #EnOcean:PacketType:RORG:MessageData:SourceID:DestinationID:FunctionNumber:ManufacturerID:RSSI:Delay
  212. substr($msg[8], 0, 2, "00");
  213. substr($msg[9], 0, 2, "00");
  214. } elsif ($msg[1] == 9) {
  215. } elsif ($msg[1] == 10) {
  216. } else {
  217. }
  218. $msg = join(":", @msg);
  219. #Log3 $IODev, 2, "TCM $IODev <TCM_Fingerprint> PacketType: $msg[1] Data: $msg";
  220. return ($IODev, $msg);
  221. }
  222. # Write
  223. # Input is header and data (HEX), without CRC
  224. sub
  225. TCM_Write($$$)
  226. {
  227. my ($hash,$fn,$msg) = @_;
  228. my $name = $hash->{NAME};
  229. return if (!defined($fn));
  230. my $bstring;
  231. if ($hash->{MODEL} eq "ESP2") {
  232. # TCM 120 (ESP2)
  233. if (!$fn) {
  234. # command with ESP2 format
  235. $bstring = $msg;
  236. } else {
  237. # command with ESP3 format
  238. my $packetType = hex(substr($fn, 6, 2));
  239. if ($packetType != 1) {
  240. Log3 $name, 1, "TCM $name Packet Type not supported.";
  241. return;
  242. }
  243. my $odataLen = hex(substr($fn, 4, 2));
  244. if ($odataLen != 0) {
  245. Log3 $name, 1, "TCM $name Radio Telegram with optional Data not supported.";
  246. return;
  247. }
  248. #my $mdataLen = hex(substr($fn, 0, 4));
  249. my $rorg = substr ($msg, 0, 2);
  250. # translate the RORG to ORG
  251. my %rorgmap = ("F6"=>"05",
  252. "D5"=>"06",
  253. "A5"=>"07",
  254. );
  255. if($rorgmap{$rorg}) {
  256. $rorg = $rorgmap{$rorg};
  257. } else {
  258. Log3 $name, 1, "TCM $name unknown RORG mapping for $rorg";
  259. }
  260. if ($rorg eq "05" || $rorg eq "06") {
  261. $bstring = "6B" . $rorg . substr ($msg, 2, 2) . "000000" . substr ($msg, 4);
  262. } else {
  263. $bstring = "6B" . $rorg . substr ($msg, 2);
  264. }
  265. }
  266. $bstring = "A55A" . $bstring . TCM_CSUM($bstring);
  267. } else {
  268. # TCM 310 (ESP3)
  269. $bstring = "55" . $fn . TCM_CRC8($fn) . $msg . TCM_CRC8($msg);
  270. if (exists($hash->{helper}{telegramSentTimeLast}) && $hash->{helper}{telegramSentTimeLast} < gettimeofday() - 6) {
  271. # clear outdated response control list
  272. delete $hash->{helper}{awaitCmdResp};
  273. }
  274. $hash->{helper}{telegramSentTimeLast} = gettimeofday();
  275. if (exists $hash->{helper}{SetAwaitCmdResp}) {
  276. push(@{$hash->{helper}{awaitCmdResp}}, 1);
  277. delete $hash->{helper}{SetAwaitCmdResp};
  278. } else {
  279. push(@{$hash->{helper}{awaitCmdResp}}, 0);
  280. }
  281. #Log3 $name, 5, "TCM $name awaitCmdResp: " . join(' ', @{$hash->{helper}{awaitCmdResp}});
  282. }
  283. Log3 $name, 5, "TCM $name sent ESP: $bstring";
  284. DevIo_SimpleWrite($hash, $bstring, 1);
  285. # next commands will be sent with a delay
  286. usleep(int(AttrVal($name, "sendInterval", 100)) * 1000);
  287. }
  288. # ESP2 CRC
  289. # Used in the TCM120
  290. sub
  291. TCM_CSUM($)
  292. {
  293. my $msg = shift;
  294. my $ml = length($msg);
  295. my @data;
  296. for(my $i = 0; $i < $ml; $i += 2) {
  297. push(@data, ord(pack('H*', substr($msg, $i, 2))));
  298. }
  299. my $sum = 0;
  300. map { $sum += $_; } @data;
  301. return sprintf("%02X", $sum & 0xFF);
  302. }
  303. # ESP3 CRC-Table
  304. my @u8CRC8Table = (
  305. 0x00, 0x07, 0x0e, 0x09, 0x1c, 0x1b, 0x12, 0x15, 0x38, 0x3f, 0x36, 0x31, 0x24,
  306. 0x23, 0x2a, 0x2d, 0x70, 0x77, 0x7e, 0x79, 0x6c, 0x6b, 0x62, 0x65, 0x48, 0x4f,
  307. 0x46, 0x41, 0x54, 0x53, 0x5a, 0x5d, 0xe0, 0xe7, 0xee, 0xe9, 0xfc, 0xfb, 0xf2,
  308. 0xf5, 0xd8, 0xdf, 0xd6, 0xd1, 0xc4, 0xc3, 0xca, 0xcd, 0x90, 0x97, 0x9e, 0x99,
  309. 0x8c, 0x8b, 0x82, 0x85, 0xa8, 0xaf, 0xa6, 0xa1, 0xb4, 0xb3, 0xba, 0xbd, 0xc7,
  310. 0xc0, 0xc9, 0xce, 0xdb, 0xdc, 0xd5, 0xd2, 0xff, 0xf8, 0xf1, 0xf6, 0xe3, 0xe4,
  311. 0xed, 0xea, 0xb7, 0xb0, 0xb9, 0xbe, 0xab, 0xac, 0xa5, 0xa2, 0x8f, 0x88, 0x81,
  312. 0x86, 0x93, 0x94, 0x9d, 0x9a, 0x27, 0x20, 0x29, 0x2e, 0x3b, 0x3c, 0x35, 0x32,
  313. 0x1f, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0d, 0x0a, 0x57, 0x50, 0x59, 0x5e, 0x4b,
  314. 0x4c, 0x45, 0x42, 0x6f, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7d, 0x7a, 0x89, 0x8e,
  315. 0x87, 0x80, 0x95, 0x92, 0x9b, 0x9c, 0xb1, 0xb6, 0xbf, 0xb8, 0xad, 0xaa, 0xa3,
  316. 0xa4, 0xf9, 0xfe, 0xf7, 0xf0, 0xe5, 0xe2, 0xeb, 0xec, 0xc1, 0xc6, 0xcf, 0xc8,
  317. 0xdd, 0xda, 0xd3, 0xd4, 0x69, 0x6e, 0x67, 0x60, 0x75, 0x72, 0x7b, 0x7c, 0x51,
  318. 0x56, 0x5f, 0x58, 0x4d, 0x4a, 0x43, 0x44, 0x19, 0x1e, 0x17, 0x10, 0x05, 0x02,
  319. 0x0b, 0x0c, 0x21, 0x26, 0x2f, 0x28, 0x3d, 0x3a, 0x33, 0x34, 0x4e, 0x49, 0x40,
  320. 0x47, 0x52, 0x55, 0x5c, 0x5b, 0x76, 0x71, 0x78, 0x7f, 0x6A, 0x6d, 0x64, 0x63,
  321. 0x3e, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2c, 0x2b, 0x06, 0x01, 0x08, 0x0f, 0x1a,
  322. 0x1d, 0x14, 0x13, 0xae, 0xa9, 0xa0, 0xa7, 0xb2, 0xb5, 0xbc, 0xbb, 0x96, 0x91,
  323. 0x98, 0x9f, 0x8a, 0x8D, 0x84, 0x83, 0xde, 0xd9, 0xd0, 0xd7, 0xc2, 0xc5, 0xcc,
  324. 0xcb, 0xe6, 0xe1, 0xe8, 0xef, 0xfa, 0xfd, 0xf4, 0xf3 );
  325. # ESP3 CRC
  326. # Used in the TCM310
  327. sub
  328. TCM_CRC8($)
  329. {
  330. my $msg = shift;
  331. my $ml = length($msg);
  332. my @data;
  333. for(my $i = 0; $i < $ml; $i += 2) {
  334. push(@data, ord(pack('H*', substr($msg, $i, 2))));
  335. }
  336. my $crc = 0;
  337. map { $crc = $u8CRC8Table[$crc ^ $_]; } @data;
  338. return sprintf("%02X", $crc);
  339. }
  340. # Read
  341. # called from the global loop, when the select for hash->{FD} reports data
  342. sub
  343. TCM_Read($)
  344. {
  345. my ($hash) = @_;
  346. my $buf = DevIo_SimpleRead($hash);
  347. return "" if(!defined($buf));
  348. my $name = $hash->{NAME};
  349. my $blockSenderID = AttrVal($name, "blockSenderID", "own");
  350. my $chipID = exists($hash->{ChipID}) ? hex $hash->{ChipID} : 0;
  351. my $baseID = exists($hash->{BaseID}) ? hex $hash->{BaseID} : 0;
  352. my $lastID = exists($hash->{LastID}) ? hex $hash->{LastID} : 0;
  353. my $data = $hash->{PARTIAL} . uc(unpack('H*', $buf));
  354. Log3 $name, 5, "TCM $name received ESP: $data";
  355. if($hash->{MODEL} eq "ESP2") {
  356. # TCM 120
  357. while($data =~ m/^A55A(.B.{20})(..)/) {
  358. my ($net, $crc) = ($1, $2);
  359. my $mycrc = TCM_CSUM($net);
  360. my $rest = substr($data, 28);
  361. if($crc ne $mycrc) {
  362. Log3 $name, 2, "TCM $name wrong checksum: got $crc, computed $mycrc" ;
  363. $data = $rest;
  364. next;
  365. }
  366. if($net =~ m/^0B(..)(........)(........)(..)/) {
  367. # Receive Radio Telegram (RRT)
  368. my ($org, $d1,$id,$status) = ($1, $2, $3, $4);
  369. my $packetType = 1;
  370. # Re-translate the ORG to RadioORG / TCM310 equivalent
  371. my %orgmap = ("05"=>"F6", "06"=>"D5", "07"=>"A5", );
  372. if($orgmap{$org}) {
  373. $org = $orgmap{$org};
  374. } else {
  375. Log3 $name, 2, "TCM $name unknown ORG mapping for $org";
  376. }
  377. if ($org ne "A5") {
  378. # extract db_0
  379. $d1 = substr($d1, 0, 2);
  380. }
  381. if ($blockSenderID eq "own" && ((hex($id) >= $baseID && hex($id) <= $lastID) || $chipID == hex($id))) {
  382. Log3 $name, 4, "TCM $name own telegram from $id blocked.";
  383. } else {
  384. Dispatch($hash, "EnOcean:$packetType:$org:$d1:$id:$status:01FFFFFFFF0000", undef);
  385. }
  386. } else {
  387. # Receive Message Telegram (RMT)
  388. my $msg = TCM_Parse120($hash, $net, 1);
  389. if (($msg eq 'OK') && ($net =~ m/^8B(..)(........)(........)(..)/)){
  390. my ($org, $d1,$id,$status) = ($1, $2, $3, $4);
  391. my $packetType = 1;
  392. # Re-translate the ORG to RadioORG / TCM310 equivalent
  393. my %orgmap = ("05" => "F6", "06" => "D5", "07" => "A5");
  394. if($orgmap{$org}) {
  395. $org = $orgmap{$org};
  396. } else {
  397. Log3 $name, 2, "TCM $name unknown ORG mapping for $org";
  398. }
  399. if ($org ne "A5") {
  400. # extract db_0
  401. $d1 = substr($d1, 0, 2);
  402. }
  403. if ($blockSenderID eq "own" && ((hex($id) >= $baseID && hex($id) <= $lastID) || $chipID == hex($id))) {
  404. Log3 $name, 4, "TCM $name own telegram from $id blocked.";
  405. } else {
  406. Dispatch($hash, "EnOcean:$packetType:$org:$d1:$id:$status:01FFFFFFFF0000", undef);
  407. }
  408. }
  409. }
  410. $data = $rest;
  411. }
  412. if(length($data) >= 4) {
  413. $data =~ s/.*A55A/A55A/ if($data !~ m/^A55A/);
  414. $data = "" if($data !~ m/^A55A/);
  415. }
  416. } else {
  417. # TCM310 / ESP3
  418. while($data =~ m/^55(....)(..)(..)(..)/) {
  419. my ($ldata, $lodata, $packetType, $crc) = (hex($1), hex($2), hex($3), $4);
  420. my $tlen = 2*(7+$ldata+$lodata);
  421. # data telegram incomplete
  422. last if(length($data) < $tlen);
  423. my $rest = substr($data, $tlen);
  424. $data = substr($data, 0, $tlen);
  425. my $hdr = substr($data, 2, 8);
  426. my $mdata = substr($data, 12, $ldata * 2);
  427. my $odata = substr($data, 12 + $ldata * 2, $lodata * 2);
  428. my $mycrc = TCM_CRC8($hdr);
  429. if($mycrc ne $crc) {
  430. Log3 $name, 2, "TCM $name wrong header checksum: got $crc, computed $mycrc" ;
  431. $data = $rest;
  432. next;
  433. }
  434. $mycrc = TCM_CRC8($mdata . $odata);
  435. $crc = substr($data, -2);
  436. if($mycrc ne $crc) {
  437. Log3 $name, 2, "TCM $name wrong data checksum: got $crc, computed $mycrc" ;
  438. $data = $rest;
  439. next;
  440. }
  441. if ($packetType == 1) {
  442. # packet type RADIO
  443. $mdata =~ m/^(..)(.*)(........)(..)$/;
  444. my ($org, $d1, $id, $status) = ($1,$2,$3,$4);
  445. my $repeatingCounter = hex substr($status, 1, 1);
  446. $odata =~ m/^(..)(........)(..)(..)$/;
  447. my ($RSSI, $receivingQuality) = (hex($3), "excellent");
  448. if ($RSSI > 87) {
  449. $receivingQuality = "bad";
  450. } elsif ($RSSI > 75) {
  451. $receivingQuality = "good";
  452. }
  453. my %addvals = (
  454. PacketType => $packetType,
  455. SubTelNum => hex($1),
  456. DestinationID => $2,
  457. RSSI => -$RSSI,
  458. ReceivingQuality => $receivingQuality,
  459. RepeatingCounter => $repeatingCounter,
  460. );
  461. $hash->{RSSI} = -$RSSI;
  462. if ($blockSenderID eq "own" && ((hex($id) >= $baseID && hex($id) <= $lastID) || $chipID == hex($id))) {
  463. Log3 $name, 4, "TCM $name own telegram from $id blocked.";
  464. } else {
  465. #EnOcean:PacketType:RORG:MessageData:SourceID:Status:OptionalData
  466. Dispatch($hash, "EnOcean:$packetType:$org:$d1:$id:$status:$odata", \%addvals);
  467. }
  468. } elsif ($packetType == 2) {
  469. # packet type RESPONSE
  470. if (defined $hash->{helper}{awaitCmdResp}[0] && $hash->{helper}{awaitCmdResp}[0]) {
  471. # do not execute if transceiver command answer is expected
  472. $data .= $rest;
  473. last;
  474. }
  475. shift(@{$hash->{helper}{awaitCmdResp}});
  476. $mdata =~ m/^(..)(.*)$/;
  477. my $rc = $1;
  478. my %codes = (
  479. "00" => "OK",
  480. "01" => "ERROR",
  481. "02" => "NOT_SUPPORTED",
  482. "03" => "WRONG_PARAM",
  483. "04" => "OPERATION_DENIED",
  484. "05" => "LOCK_SET",
  485. "82" => "FLASH_HW_ERROR",
  486. "90" => "BASEID_OUT_OF_RANGE",
  487. "91" => "BASEID_MAX_REACHED",
  488. );
  489. my $rcTxt = $codes{$rc} if($codes{$rc});
  490. Log3 $name, $rc eq "00" ? 5 : 2, "TCM $name RESPONSE: $rcTxt";
  491. #$packetType = sprintf "%01X", $packetType;
  492. #EnOcean:PacketType:ResposeCode:MessageData:OptionalData
  493. #Dispatch($hash, "EnOcean:$packetType:$1:$2:$odata", undef);
  494. } elsif ($packetType == 3) {
  495. # packet type RADIO_SUB_TEL
  496. Log3 $name, 2, "TCM $name packet type RADIO_SUB_TEL not supported: $data";
  497. } elsif ($packetType == 4) {
  498. # packet type EVENT
  499. $mdata =~ m/^(..)(.*)$/;
  500. $packetType = sprintf "%01X", $packetType;
  501. my $eventCode = $1;
  502. my $messageData = $2;
  503. if (hex($eventCode) <= 3) {
  504. #EnOcean:PacketType:eventCode:messageData
  505. Dispatch($hash, "EnOcean:$packetType:$eventCode:$messageData", undef);
  506. } elsif (hex($eventCode) == 4) {
  507. # CO_READY
  508. my @resetCause = ('voltage_supply_drop', 'reset_pin', 'watchdog', 'flywheel', 'parity_error', 'hw_parity_error', 'memory_request_error', 'wake_up', 'wake_up', 'unknown');
  509. my @secureMode = ('standard', 'extended');
  510. $hash->{RESET_CAUSE} = $resetCause[hex($messageData)];
  511. $hash->{SECURE_MODE} = $secureMode[hex($odata)];
  512. DoTrigger($name, "EVENT: RESET_CAUSE: $hash->{RESET_CAUSE}");
  513. DoTrigger($name, "EVENT: SECURE_MODE: $hash->{SECURE_MODE}");
  514. Log3 $name, 2, "TCM $name EVENT RESET_CAUSE: $hash->{RESET_CAUSE} SECURE_MODE: $hash->{SECURE_MODE}";
  515. } elsif (hex($eventCode) == 5) {
  516. # CO_EVENT_SECUREDEVICES
  517. } elsif (hex($eventCode) == 6) {
  518. # CO_DUTYCYCLE_LIMIT
  519. my @dutycycleLimit = ('released', 'reached');
  520. $hash->{DUTYCYCLE_LIMIT} = $dutycycleLimit[hex($messageData)];
  521. DoTrigger($name, "EVENT: DUTYCYCLE_LIMIT: $hash->{DUTYCYCLE_LIMIT}");
  522. Log3 $name, 2, "TCM $name EVENT DUTYCYCLE_LIMIT: $hash->{DUTYCYCLE_LIMIT}";
  523. } elsif (hex($eventCode) == 7) {
  524. # CO_TRANSMIT_FAILED
  525. my @transmitFailed = ('CSMA_failed', 'no_ack_received');
  526. $hash->{TRANSMIT_FAILED} = $transmitFailed[hex($messageData)];
  527. DoTrigger($name, "EVENT: TRANSMIT_FAILED: $hash->{TRANSMIT_FAILED}");
  528. Log3 $name, 2, "TCM $name EVENT TRANSMIT_FAILED: $hash->{TRANSMIT_FAILED}";
  529. } else {
  530. }
  531. } elsif ($packetType == 5) {
  532. # packet type COMMON_COMMAND
  533. Log3 $name, 2, "TCM $name packet type COMMON_COMMAND not supported: $data";
  534. } elsif ($packetType == 6) {
  535. # packet type SMART_ACK_COMMAND
  536. $mdata =~ m/^(..)(.*)$/;
  537. $packetType = sprintf "%01X", $packetType;
  538. #EnOcean:PacketType:smartAckCode:MessageData
  539. Dispatch($hash, "EnOcean:$packetType:$1:$2", undef);
  540. } elsif ($packetType == 7) {
  541. # packet type REMOTE_MAN_COMMAND
  542. $mdata =~ m/^(....)(....)(.*)$/;
  543. my ($function, $manufID, $messageData) = ($1, $2, $3);
  544. $odata =~ m/^(........)(........)(..)(..)$/;
  545. my ($RSSI, $receivingQuality) = ($3, "excellent");
  546. if (hex($RSSI) > 87) {
  547. $receivingQuality = "bad";
  548. } elsif (hex($RSSI) > 75) {
  549. $receivingQuality = "good";
  550. }
  551. my %addvals = (
  552. PacketType => $packetType,
  553. DestinationID => $1,
  554. RSSI => -hex($RSSI),
  555. ReceivingQuality => $receivingQuality,
  556. );
  557. $hash->{RSSI} = -hex($RSSI);
  558. $packetType = sprintf "%01X", $packetType;
  559. if ($blockSenderID eq "own" && ((hex($2) >= $baseID && hex($2) <= $lastID) || $chipID == hex($2))) {
  560. Log3 $name, 4, "TCM $name own telegram from $2 blocked.";
  561. } else {
  562. #EnOcean:PacketType:RORG:MessageData:SourceID:DestinationID:FunctionNumber:ManufacturerID:RSSI:Delay
  563. Dispatch($hash, "EnOcean:$packetType:C5:$messageData:$2:$1:$function:$manufID:$RSSI:$4", \%addvals);
  564. }
  565. } elsif ($packetType == 9) {
  566. # packet type RADIO_MESSAGE
  567. Log3 $name, 2, "TCM: $name packet type RADIO_MESSAGE not supported: $data";
  568. } elsif ($packetType == 10) {
  569. # packet type RADIO_ADVANCED
  570. Log3 $name, 2, "TCM $name packet type RADIO_ADVANCED not supported: $data";
  571. } else {
  572. Log3 $name, 2, "TCM $name unknown packet type $packetType: $data";
  573. }
  574. $data = $rest;
  575. }
  576. if(length($data) >= 4) {
  577. $data =~ s/.*55/55/ if($data !~ m/^55/);
  578. $data = "" if($data !~ m/^55/);
  579. }
  580. }
  581. $hash->{PARTIAL} = $data;
  582. }
  583. # Parse Table TCM 120
  584. my %parsetbl120 = (
  585. "8B05" => { msg=>"OK" },
  586. "8B06" => { msg=>"OK" },
  587. "8B07" => { msg=>"OK" },
  588. "8B08" => { msg=>"ERR_SYNTAX_H_SEQ" },
  589. "8B09" => { msg=>"ERR_SYNTAX_LENGTH" },
  590. "8B0A" => { msg=>"ERR_SYNTAX_CHKSUM" },
  591. "8B0B" => { msg=>"ERR_SYNTAX_ORG" },
  592. "8B0C" => { msg=>"ERR_MODEM_DUP_ID" },
  593. "8B19" => { msg=>"ERR" },
  594. "8B1A" => { msg=>"ERR_IDRANGE" },
  595. "8B22" => { msg=>"ERR_TX_IDRANGE" },
  596. "8B28" => { msg=>"ERR_MODEM_NOTWANTEDACK" },
  597. "8B29" => { msg=>"ERR_MODEM_NOTACK" },
  598. "8B58" => { msg=>"OK" },
  599. "8B8C" => { msg=>"INF_SW_VER", expr=>'"$a[2].$a[3].$a[4].$a[5]"' },
  600. "8B88" => { msg=>"INF_RX_SENSIVITY", expr=>'$a[2] ? "High (01)":"Low (00)"' },
  601. "8B89" => { msg=>"INFO", expr=>'substr($rawstr,2,9)' },
  602. "8B98" => { msg=>"INF_IDBASE",
  603. expr=>'sprintf("%02x%02x%02x%02x", $a[2], $a[3], $a[4], $a[5])' },
  604. "8BA8" => { msg=>"INF_MODEM_STATUS",
  605. expr=>'sprintf("%s, ID:%02x%02x", $a[2]?"on":"off", $a[3], $a[4])' },
  606. );
  607. # Parse TCM 120
  608. sub
  609. TCM_Parse120($$$)
  610. {
  611. my ($hash,$rawmsg,$ret) = @_;
  612. my $name = $hash->{NAME};
  613. Log3 $name, 5, "TCM $name Parse $rawmsg";
  614. my $msg = "";
  615. my $cmd = $parsetbl120{substr($rawmsg, 0, 4)};
  616. if(!$cmd) {
  617. $msg ="Unknown command: $rawmsg";
  618. } else {
  619. if($cmd->{expr}) {
  620. $msg = $cmd->{msg}." " if(!$ret);
  621. my $rawstr = pack('H*', $rawmsg);
  622. $rawstr =~ s/[\r\n]//g;
  623. my @a = map { ord($_) } split("", $rawstr);
  624. $msg .= eval $cmd->{expr};
  625. } else {
  626. return "" if($cmd ->{msg} eq "OK" && !$ret); # SKIP Ok
  627. $msg = $cmd->{msg};
  628. }
  629. }
  630. Log3 $name, 2, "TCM $name RESPONSE: $msg" if(!$ret);
  631. return $msg;
  632. }
  633. # Parse Table TCM 310
  634. my %rc310 = (
  635. "00" => "OK",
  636. "01" => "ERROR",
  637. "02" => "NOT_SUPPORTED",
  638. "03" => "WRONG_PARAM",
  639. "04" => "OPERATION_DENIED",
  640. "05" => "LOCK_SET",
  641. "82" => "FLASH_HW_ERROR",
  642. "90" => "BASEID_OUT_OF_RANGE",
  643. "91" => "BASEID_MAX_REACHED",
  644. );
  645. # Parse TCM 310
  646. sub
  647. TCM_Parse310($$$)
  648. {
  649. my ($hash,$rawmsg,$ptr) = @_;
  650. my $name = $hash->{NAME};
  651. Log3 $name, 5, "TCM_Parse $rawmsg";
  652. my $rc = substr($rawmsg, 0, 2);
  653. my $msg = "";
  654. if($rc ne "00") {
  655. $msg = $rc310{$rc};
  656. $msg = "Unknown return code $rc" if(!$msg);
  657. } else {
  658. my @ans;
  659. foreach my $k (sort keys %{$ptr}) {
  660. next if($k eq "cmd" || $k eq "oCmd" || $k eq "arg" || $k eq "packetType");
  661. my ($off, $len, $type) = split(",", $ptr->{$k});
  662. my $data;
  663. if ($len == 0) {
  664. $data = substr($rawmsg, $off*2);
  665. } else {
  666. $data = substr($rawmsg, $off*2, $len*2);
  667. }
  668. if($type) {
  669. if ($type eq "STR") {
  670. $data = pack('H*', $data);
  671. ####
  672. # remove trailing 0x00
  673. #$data =~ s/[^A-Za-z0-9#\.\-_]//g;
  674. $data =~ tr/A-Za-z0-9#.-_//cd;
  675. } else {
  676. my $dataLen = length($data);
  677. my $dataOut = '';
  678. my ($part1, $part2, $part3) = split(":", $type);
  679. $part1 *= 2;
  680. $part2 *= 2;
  681. if (defined $part3) {
  682. $part3 *= 2;
  683. while ($dataLen > 0) {
  684. $data =~ m/^(.{$part1})(.{$part2})(.{$part3})(.*)$/;
  685. $dataOut .= $1 . ':' . $2 . ':' . $3 . ' ';
  686. $data = $4;
  687. $dataLen -= $part1 + $part2 + $part3;
  688. }
  689. } else {
  690. while ($dataLen > 0) {
  691. $data =~ m/^(.{$part1})(.{$part2})(.*)$/;
  692. $dataOut .= $1 . ':' . $2 . ' ';
  693. $data = $3;
  694. $dataLen -= $part1 + $part2;
  695. }
  696. }
  697. chop($dataOut);
  698. $data = $dataOut;
  699. }
  700. }
  701. push @ans, "$k: $data";
  702. }
  703. $msg = join(" ", @ans);
  704. }
  705. if ($msg eq "") {
  706. Log3 $name, 2, "TCM $name RESPONSE: OK";
  707. } else {
  708. Log3 $name, 2, "TCM $name RESPONSE: $msg";
  709. }
  710. return $msg;
  711. }
  712. # Ready
  713. sub
  714. TCM_Ready($)
  715. {
  716. my ($hash) = @_;
  717. return DevIo_OpenDev($hash, 1, undef)
  718. # if($hash->{STATE} ne "opened");
  719. if($hash->{STATE} eq "disconnected");
  720. # This is relevant for windows/USB only
  721. my $po = $hash->{USBDev};
  722. return undef if(!$po);
  723. my ($BlockingFlags, $InBytes, $OutBytes, $ErrorFlags) = $po->status;
  724. return ($InBytes>0);
  725. }
  726. # Get commands TCM 120
  727. my %gets120 = (
  728. "sensitivity" => "AB48",
  729. "baseID" => "AB58",
  730. "modem_status" => "AB68",
  731. "version" => "AB4B",
  732. );
  733. # Get commands TCM 310
  734. my %gets310 = (
  735. "baseID" => {packetType => 5, cmd => "08", BaseID => "1,4", RemainingWriteCycles => "5,1"},
  736. "dutycycleLimit" => {packetType => 5, cmd => "23", DutyCycle => "1,1", Slots => "2,1", SlotPeriod => "3,2", ActualSlotLeft => "5,2", LoadAfterActual => "7,1"},
  737. "filter" => {packetType => 5, cmd => "0F", "Type:Value" => "1,0,1:4"},
  738. "frequencyInfo" => {packetType => 5, cmd => "25", Frequency => "1,1", Protocol => "2,1"},
  739. "noiseThreshold" => {packetType => 5, cmd => "33", NoiseThreshold => "1,1"},
  740. "numSecureDevicesIn" => {packetType => 5, cmd => "1D", oCmd => "00", Number => "1,1"},
  741. "numSecureDevicesOut" => {packetType => 5, cmd => "1D",oCmd => "01", Number => "1,1"},
  742. "remanRepeating" => {packetType => 5, cmd => "31", Repeated => "1,1"},
  743. "repeater" => {packetType => 5, cmd => "0A", RepEnable => "1,1", RepLevel => "2,1"},
  744. "stepCode" => {packetType => 5, cmd => "27", HWRevision => "1,1", Stepcode => "2,1"},
  745. "smartAckLearnMode" => {packetType => 6, cmd => "02", Enable => "1,1", Extended => "2,1"},
  746. "smartAckLearnedClients" => {packetType => 6, cmd => "06", "ClientID:CtrlID:Mailbox" => "1,0,4:4:1"},
  747. "version" => {packetType => 5, cmd => "03", APPVersion => "1,4", APIVersion => "5,4", ChipID => "9,4", ChipVersion => "13,4", Desc => "17,16,STR"},
  748. );
  749. # Get
  750. sub
  751. TCM_Get($@)
  752. {
  753. my ($hash, @a) = @_;
  754. my $name = $hash->{NAME};
  755. return if (AttrVal($name, "comType", "TCM") eq "RS485" || $hash->{DeviceName} eq "none");
  756. return "\"get $name\" needs one parameter" if(@a != 2);
  757. my $cmd = $a[1];
  758. my ($err, $msg, $packetType);
  759. if($hash->{MODEL} eq "ESP2") {
  760. # TCM 120
  761. my $rawcmd = $gets120{$cmd};
  762. return "Unknown argument $cmd, choose one of " . join(':noArg ', sort keys %gets120) . ':noArg' if(!defined($rawcmd));
  763. Log3 $name, 3, "TCM $name get $cmd";
  764. $rawcmd .= "000000000000000000";
  765. TCM_Write($hash, "", $rawcmd);
  766. ($err, $msg) = TCM_ReadAnswer($hash, "get $cmd");
  767. $msg = TCM_Parse120($hash, $msg, 1) if(!$err);
  768. } else {
  769. # TCM 310
  770. my $cmdhash = $gets310{$cmd};
  771. return "Unknown argument $cmd, choose one of " . join(':noArg ', sort keys %gets310) . ':noArg' if(!defined($cmdhash));
  772. Log3 $name, 3, "TCM $name get $cmd";
  773. my $cmdHex = $cmdhash->{cmd};
  774. my $oCmdHex = '';
  775. $oCmdHex = $cmdhash->{oCmd} if (exists $cmdhash->{oCmd});
  776. $hash->{helper}{SetAwaitCmdResp} = 1;
  777. #TCM_Write($hash, sprintf("%04X00%02X", length($cmdHex)/2, $cmdhash->{packetType}), $cmdHex);
  778. TCM_Write($hash, sprintf("%04X%02X%02X", length($cmdHex)/2, length($oCmdHex)/2, $cmdhash->{packetType}), $cmdHex . $oCmdHex);
  779. ($err, $msg) = TCM_ReadAnswer($hash, "get $cmd");
  780. $msg = TCM_Parse310($hash, $msg, $cmdhash) if(!$err);
  781. }
  782. if($err) {
  783. Log3 undef, 2, "TCM $name $err";
  784. return $err;
  785. }
  786. readingsSingleUpdate($hash, $cmd, $msg, 1);
  787. return $msg;
  788. }
  789. # clear teach in flag
  790. sub TCM_ClearTeach($)
  791. {
  792. my $hash = shift;
  793. delete($hash->{Teach});
  794. }
  795. # clear Smart ACK teach in flag
  796. sub TCM_ClearSmartAckLearn($)
  797. {
  798. my $hash = shift;
  799. delete($hash->{SmartAckLearn});
  800. readingsSingleUpdate($hash, "smartAckLearnMode", "Enable: 00 Extended: 00", 1);
  801. }
  802. # Set commands TCM 120
  803. my %sets120 = ( # Name, Data to send to the CUL, Regexp for the answer
  804. "teach" => {cmd => "AB18", arg => "\\d+"},
  805. "baseID" => {cmd => "AB18", arg => "FF[8-9A-F][0-9A-F]{5}"},
  806. "sensitivity" => {cmd => "AB08", arg => "0[01]"},
  807. "sleep" => {cmd => "AB09"},
  808. "wake" => {cmd => ""}, # Special
  809. "reset" => {cmd => "AB0A"},
  810. "modem_on" => {cmd => "AB28", arg => "[0-9A-F]{4}"},
  811. "modem_off" => {cmd => "AB2A"},
  812. );
  813. # Set commands TCM 310
  814. my %sets310 = (
  815. "baseID" => {packetType => 5, cmd => "07", arg => "FF[8-9A-F][0-9A-F]{5}"},
  816. "baudrate" => {packetType => 5, cmd => "24", arg => "0[0-3]"},
  817. "bist" => {packetType => 5, cmd => "06", BIST_Result => "1,1"},
  818. "filterAdd" => {packetType => 5, cmd => "0B", arg => "0[0-3][0-9A-F]{8}[048C]0"},
  819. "filterDel" => {packetType => 5, cmd => "0C", arg => "0[0-3][0-9A-F]{8}"},
  820. "filterDelAll" => {packetType => 5, cmd => "0D"},
  821. "filterEnable" => {packetType => 5, cmd => "0E", arg => "0[01]0[0189]"},
  822. "init" => {},
  823. "maturity" => {packetType => 5, cmd => "10", arg => "0[0-1]"},
  824. "mode" => {packetType => 5, cmd => "1C", arg => "0[0-1]"},
  825. "noiseThreshold" => {packetType => 5, cmd => "32", arg => "2E|2F|3[0-8]"},
  826. "remanCode" => {packetType => 5, cmd => "2E", arg => "[0-9A-F]{8}"},
  827. "remanRepeating" => {packetType => 5, cmd => "30", arg => "0[0-1]"},
  828. "reset" => {packetType => 5, cmd => "02"},
  829. "resetEvents" => {},
  830. "repeater" => {packetType => 5, cmd => "09", arg => "0[0-1]0[0-2]"},
  831. "sleep" => {packetType => 5, cmd => "01", arg => "00[0-9A-F]{6}"},
  832. "smartAckLearn" => {packetType => 6, cmd => "01", arg => "\\d+"},
  833. "smartAckMailboxMax" => {packetType => 6, cmd => "08", arg => "\\d+"},
  834. "startupDelay" => {packetType => 5, cmd => "2F", arg => "[0-9A-F]{2}"},
  835. "subtel" => {packetType => 5, cmd => "11", arg => "0[0-1]"},
  836. "teach" => {packetType => 5, arg=> "\\d+"},
  837. );
  838. # Set
  839. sub TCM_Set($@)
  840. {
  841. my ($hash, @a) = @_;
  842. my $name = $hash->{NAME};
  843. return if (AttrVal($name, "comType", "TCM") eq "RS485" || $hash->{DeviceName} eq "none");
  844. return "\"set $name\" needs at least one parameter" if(@a < 2);
  845. my $cmd = $a[1];
  846. my $arg = $a[2];
  847. my ($err, $msg);
  848. my $chash = ($hash->{MODEL} eq "ESP2" ? \%sets120 : \%sets310);
  849. my $cmdhash = $chash->{$cmd};
  850. return "Unknown argument $cmd, choose one of ".join(" ",sort keys %{$chash})
  851. if(!defined($cmdhash));
  852. my $cmdHex = $cmdhash->{cmd};
  853. my $argre = $cmdhash->{arg};
  854. my $logArg = defined($arg) ? $arg : '';
  855. if($argre) {
  856. return "Argument needed for set $name $cmd ($argre)" if (!defined($arg));
  857. return "Argument does not match the regexp ($argre)" if ($arg !~ m/$argre/i);
  858. if ($cmd eq "smartAckLearn") {
  859. if (($arg + 0) >= 0 && ($arg + 0) <= 4294967) {
  860. if ($arg == 0) {
  861. $arg = '0' x 12;
  862. readingsSingleUpdate($hash, "smartAckLearnMode", "Enable: 00 Extended: 00", 1);
  863. } else {
  864. my $smartAckLearnMode = AttrVal($name, "smartAckLearnMode", "simple");
  865. my %smartAckLearnMode = (simple => 0, advance => 1, advanceSelectRep => 2);
  866. $arg = sprintf "01%02X%08X", $smartAckLearnMode{$smartAckLearnMode}, $arg * 1000;
  867. readingsSingleUpdate($hash, "smartAckLearnMode", "Enable: 01 Extended: " . sprintf("%02X", $smartAckLearnMode{$smartAckLearnMode}), 1);
  868. }
  869. } else {
  870. return "Argument wrong, choose 0...4294967";
  871. }
  872. } elsif ($cmd eq "smartAckMailboxMax") {
  873. if (($arg + 0) >= 0 && ($arg + 0) <= 20) {
  874. $attr{$name}{smartAckMailboxMax} = $arg;
  875. $arg = sprintf "%02X", $arg;
  876. } else {
  877. return "Argument wrong, choose 0...20";
  878. }
  879. }
  880. $cmdHex .= $arg;
  881. }
  882. Log3 $name, 3, "TCM $name set $cmd $logArg";
  883. if($cmd eq "teach") {
  884. if ($arg == 0) {
  885. RemoveInternalTimer($hash, "TCM_ClearTeach");
  886. delete $hash->{Teach};
  887. return;
  888. } else {
  889. RemoveInternalTimer($hash, "TCM_ClearTeach");
  890. InternalTimer(gettimeofday() + $arg, "TCM_ClearTeach", $hash, 1);
  891. $hash->{Teach} = 1;
  892. return;
  893. }
  894. }
  895. if($hash->{MODEL} eq "ESP2") {
  896. # TCM 120
  897. if($cmdHex eq "") { # wake is very special
  898. DevIo_SimpleWrite($hash, "AA", 1);
  899. return "";
  900. }
  901. $cmdHex .= "0"x(22-length($cmdHex)); # Padding with 0
  902. TCM_Write($hash, "", $cmdHex);
  903. ($err, $msg) = TCM_ReadAnswer($hash, "get $cmd");
  904. $msg = TCM_Parse120($hash, $msg, 1) if(!$err);
  905. } else {
  906. # TCM310
  907. if($cmd eq "init") {
  908. TCM_InitSerialCom($hash);
  909. return;
  910. }
  911. if($cmd eq "resetEvents") {
  912. delete $hash->{RESET_CAUSE};
  913. delete $hash->{SECURE_MODE};
  914. delete $hash->{DUTYCYCLE_LIMIT};
  915. delete $hash->{TRANSMIT_FAILED};
  916. return;
  917. }
  918. $hash->{helper}{SetAwaitCmdResp} = 1;
  919. TCM_Write($hash, sprintf("%04X00%02X", length($cmdHex)/2, $cmdhash->{packetType}), $cmdHex);
  920. ($err, $msg) = TCM_ReadAnswer($hash, "set $cmd");
  921. if(!$err) {
  922. $msg = TCM_Parse310($hash, $msg, $cmdhash);
  923. if ($cmd eq "smartAckLearn") {
  924. if (substr($arg, 0, 2) eq '00') {
  925. # end Smart ACK learnmode
  926. RemoveInternalTimer($hash, "TCM_ClearSmartAckLearn");
  927. delete $hash->{SmartAckLearn};
  928. } else {
  929. RemoveInternalTimer($hash, "TCM_ClearSmartAckLearn");
  930. InternalTimer(gettimeofday() + hex(substr($arg, 4, 8)) * 0.001, "TCM_ClearSmartAckLearn", $hash, 1);
  931. $hash->{SmartAckLearn} = 1;
  932. }
  933. }
  934. }
  935. }
  936. if($err) {
  937. Log3 undef, 2, "TCM $name $err";
  938. return $err;
  939. }
  940. my @setCmdReadingsUpdate = ("repeater", "maturity", "mode");
  941. foreach(@setCmdReadingsUpdate) {
  942. if ($_ eq $cmd && $msg eq "") {
  943. if ($_ eq "repeater") {
  944. $arg = "RepEnable: " . substr($arg, 0, 2) . " RepLevel: " . substr($arg, 2, 2);
  945. }
  946. readingsSingleUpdate($hash, $cmd, $arg, 1);
  947. }
  948. }
  949. return $msg;
  950. }
  951. # read command response data
  952. sub TCM_ReadAnswer($$)
  953. {
  954. my ($hash, $arg) = @_;
  955. return ("No FD", undef) if(!$hash || ($^O !~ /Win/ && !defined($hash->{FD})));
  956. my $name = $hash->{NAME};
  957. my $blockSenderID = AttrVal($name, "blockSenderID", "own");
  958. my $chipID = exists($hash->{ChipID}) ? hex $hash->{ChipID} : 0;
  959. my $baseID = exists($hash->{BaseID}) ? hex $hash->{BaseID} : 0;
  960. my $lastID = exists($hash->{LastID}) ? hex $hash->{LastID} : 0;
  961. my ($data, $rin, $buf) = ($hash->{PARTIAL}, "", "");
  962. # 2 seconds timeout
  963. my $to = 2;
  964. for (;;) {
  965. if ($^O =~ m/Win/ && $hash->{USBDev}) {
  966. $hash->{USBDev}->read_const_time($to*1000); # set timeout (ms)
  967. # Read anstatt input sonst funzt read_const_time nicht.
  968. $buf = $hash->{USBDev}->read(999);
  969. if (length($buf) == 0) {
  970. if (length($data) == 0) {
  971. shift(@{$hash->{helper}{awaitCmdResp}});
  972. return ("Timeout reading answer for $arg", undef);
  973. }
  974. } else {
  975. $data .= uc(unpack('H*', $buf));
  976. }
  977. } else {
  978. if (!$hash->{FD}) {
  979. shift(@{$hash->{helper}{awaitCmdResp}});
  980. return ("Device lost when reading answer for $arg", undef);
  981. }
  982. vec($rin, $hash->{FD}, 1) = 1;
  983. my $nfound = select($rin, undef, undef, $to);
  984. if ($nfound < 0) {
  985. next if ($! == EAGAIN() || $! == EINTR() || $! == 0);
  986. my $err = $!;
  987. DevIo_Disconnected($hash);
  988. shift(@{$hash->{helper}{awaitCmdResp}});
  989. return("Device error: $err", undef);
  990. } elsif ($nfound == 0) {
  991. if (length($data) == 0) {
  992. shift(@{$hash->{helper}{awaitCmdResp}});
  993. return ("Timeout reading response for $arg", undef);
  994. }
  995. } else {
  996. $buf = DevIo_SimpleRead($hash);
  997. if(!defined($buf)) {
  998. shift(@{$hash->{helper}{awaitCmdResp}});
  999. return ("No response data for $arg", undef);
  1000. }
  1001. $data .= uc(unpack('H*', $buf));
  1002. }
  1003. }
  1004. if (length($data) > 4) {
  1005. Log3 $name, 5, "TCM $name received ESP: $data";
  1006. if ($hash->{MODEL} eq "ESP2") {
  1007. # TCM 120
  1008. if (length($data) >= 28) {
  1009. if ($data !~ m/^A55A(.B.{20})(..)/) {
  1010. $hash->{PARTIAL} = '';
  1011. return ("$arg: Bogus answer received: $data", undef);
  1012. }
  1013. my ($net, $crc) = ($1, $2);
  1014. my $mycrc = TCM_CSUM($net);
  1015. $hash->{PARTIAL} = substr($data, 28);
  1016. if ($crc ne $mycrc) {
  1017. return ("wrong checksum: got $crc, computed $mycrc", undef);
  1018. }
  1019. return (undef, $net);
  1020. }
  1021. } else {
  1022. # TCM 310
  1023. if($data !~ m/^55/) {
  1024. $data =~ s/.*55/55/;
  1025. if ($data !~ m/^55/) {
  1026. #$data = '';
  1027. $hash->{PARTIAL} = '';
  1028. shift(@{$hash->{helper}{awaitCmdResp}});
  1029. return ("$arg: Bogus answer received: $data", undef);
  1030. }
  1031. $hash->{PARTIAL} = $data;
  1032. }
  1033. next if ($data !~ m/^55(....)(..)(..)(..)/);
  1034. my ($ldata, $lodata, $packetType, $crc) = (hex($1), hex($2), hex($3), $4);
  1035. my $tlen = 2 * (7 + $ldata + $lodata);
  1036. # data telegram incomplete
  1037. next if (length($data) < $tlen);
  1038. my $rest = substr($data, $tlen);
  1039. $data = substr($data, 0, $tlen);
  1040. my $hdr = substr($data, 2, 8);
  1041. my $mdata = substr($data, 12, $ldata * 2);
  1042. my $odata = substr($data, 12 + $ldata * 2, $lodata * 2);
  1043. my $mycrc = TCM_CRC8($hdr);
  1044. if ($crc ne $mycrc) {
  1045. $hash->{PARTIAL} = $rest;
  1046. shift(@{$hash->{helper}{awaitCmdResp}});
  1047. return ("wrong header checksum: got $crc, computed $mycrc", undef);
  1048. }
  1049. $mycrc = TCM_CRC8($mdata . $odata);
  1050. $crc = substr($data, -2);
  1051. if ($crc ne $mycrc) {
  1052. $hash->{PARTIAL} = $rest;
  1053. shift(@{$hash->{helper}{awaitCmdResp}});
  1054. return ("wrong data checksum: got $crc, computed $mycrc", undef);
  1055. }
  1056. if ($packetType == 1) {
  1057. # packet type RADIO
  1058. $mdata =~ m/^(..)(.*)(........)(..)$/;
  1059. my ($org, $d1, $id, $status) = ($1, $2, $3, $4);
  1060. my $repeatingCounter = hex substr($status, 1, 1);
  1061. $odata =~ m/^(..)(........)(..)(..)$/;
  1062. my ($RSSI, $receivingQuality) = (hex($3), "excellent");
  1063. if ($RSSI > 87) {
  1064. $receivingQuality = "bad";
  1065. } elsif ($RSSI > 75) {
  1066. $receivingQuality = "good";
  1067. }
  1068. my %addvals = (
  1069. PacketType => $packetType,
  1070. SubTelNum => hex($1),
  1071. DestinationID => $2,
  1072. RSSI => -$RSSI,
  1073. ReceivingQuality => $receivingQuality,
  1074. RepeatingCounter => $repeatingCounter,
  1075. );
  1076. $hash->{RSSI} = -$RSSI;
  1077. if ($blockSenderID eq "own" && ((hex($id) >= $baseID && hex($id) <= $lastID) || $chipID == hex($id))) {
  1078. Log3 $name, 4, "TCM $name own telegram from $id blocked.";
  1079. } else {
  1080. #EnOcean:PacketType:RORG:MessageData:SourceID:Status:OptionalData
  1081. Dispatch($hash, "EnOcean:$packetType:$org:$d1:$id:$status:$odata", \%addvals);
  1082. }
  1083. $data = $rest;
  1084. $hash->{PARTIAL} = $rest;
  1085. next;
  1086. } elsif($packetType == 2) {
  1087. # packet type RESPONSE
  1088. $hash->{PARTIAL} = $rest;
  1089. if (defined $hash->{helper}{awaitCmdResp}[0] && $hash->{helper}{awaitCmdResp}[0]) {
  1090. shift(@{$hash->{helper}{awaitCmdResp}});
  1091. return (undef, $mdata . $odata);
  1092. } else {
  1093. shift(@{$hash->{helper}{awaitCmdResp}});
  1094. $mdata =~ m/^(..)(.*)$/;
  1095. my $rc = $1;
  1096. my %codes = (
  1097. "00" => "OK",
  1098. "01" => "ERROR",
  1099. "02" => "NOT_SUPPORTED",
  1100. "03" => "WRONG_PARAM",
  1101. "04" => "OPERATION_DENIED",
  1102. "05" => "LOCK_SET",
  1103. "82" => "FLASH_HW_ERROR",
  1104. "90" => "BASEID_OUT_OF_RANGE",
  1105. "91" => "BASEID_MAX_REACHED",
  1106. );
  1107. my $rcTxt = $codes{$rc} if($codes{$rc});
  1108. Log3 $name, $rc eq "00" ? 5 : 2, "TCM $name RESPONSE: $rcTxt";
  1109. #$packetType = sprintf "%01X", $packetType;
  1110. #EnOcean:PacketType:ResposeCode:MessageData:OptionalData
  1111. #Dispatch($hash, "EnOcean:$packetType:$1:$2:$odata", undef);
  1112. $data = $rest;
  1113. next;
  1114. }
  1115. } elsif ($packetType == 4) {
  1116. #####
  1117. # packet type EVENT
  1118. $mdata =~ m/^(..)(.*)$/;
  1119. my $eventCode = $1;
  1120. my $messageData = $2;
  1121. if (hex($eventCode) <= 3) {
  1122. my $packetTypeHex = sprintf "%01X", $packetType;
  1123. #EnOcean:PacketType:eventCode:messageData
  1124. Dispatch($hash, "EnOcean:$packetTypeHex:$eventCode:$messageData", undef);
  1125. } elsif (hex($eventCode) == 4) {
  1126. # CO_READY
  1127. my @resetCause = ('voltage_supply_drop', 'reset_pin', 'watchdog', 'flywheel', 'parity_error', 'hw_parity_error', 'memory_request_error', 'wake_up', 'wake_up', 'unknown');
  1128. my @secureMode = ('standard', 'extended');
  1129. $hash->{RESET_CAUSE} = $resetCause[hex($messageData)];
  1130. $hash->{SECURE_MODE} = $secureMode[hex($odata)];
  1131. Log3 $name, 2, "TCM $name EVENT RESET_CAUSE: $hash->{RESET_CAUSE} SECURE_MODE: $hash->{SECURE_MODE}";
  1132. } elsif (hex($eventCode) == 5) {
  1133. # CO_EVENT_SECUREDEVICES
  1134. } elsif (hex($eventCode) == 6) {
  1135. # CO_DUTYCYCLE_LIMIT
  1136. my @dutycycleLimit = ('released', 'reached');
  1137. $hash->{DUTYCYCLE_LIMIT} = $dutycycleLimit[hex($messageData)];
  1138. Log3 $name, 2, "TCM $name EVENT DUTYCYCLE_LIMIT: $hash->{DUTYCYCLE_LIMIT}";
  1139. } elsif (hex($eventCode) == 7) {
  1140. # CO_TRANSMIT_FAILED
  1141. my @transmitFailed = ('CSMA_failed', 'no_ack_received');
  1142. $hash->{TRANSMIT_FAILED} = $transmitFailed[hex($messageData)];
  1143. Log3 $name, 2, "TCM $name EVENT TRANSMIT_FAILED: $hash->{TRANSMIT_FAILED}";
  1144. }
  1145. $data = $rest;
  1146. $hash->{PARTIAL} = $rest;
  1147. next;
  1148. } else {
  1149. return ("$arg ERROR: received data telegram PacketType: $packetType Data: $mdata not supported.", undef)
  1150. }
  1151. }
  1152. }
  1153. }
  1154. }
  1155. #
  1156. sub TCM_Attr(@) {
  1157. my ($cmd, $name, $attrName, $attrVal) = @_;
  1158. my $hash = $defs{$name};
  1159. # return if attribute list is incomplete
  1160. return undef if (!$init_done);
  1161. if ($attrName eq "blockSenderID") {
  1162. if (!defined $attrVal) {
  1163. } elsif ($attrVal !~ m/^own|no$/) {
  1164. Log3 $name, 2, "EnOcean $name attribute-value [$attrName] = $attrVal wrong";
  1165. CommandDeleteAttr(undef, "$name $attrName");
  1166. }
  1167. } elsif ($attrName eq "baseID") {
  1168. if (!defined $attrVal){
  1169. } elsif ($attrVal !~ m/^[Ff]{2}[\dA-Fa-f]{6}$/) {
  1170. Log3 $name, 2, "EnOcean $name attribute-value [$attrName] = $attrVal wrong";
  1171. CommandDeleteAttr(undef, "$name $attrName");
  1172. } else {
  1173. $hash->{BaseID} = $attrVal;
  1174. $hash->{LastID} = sprintf "%08X", (hex $attrVal) + 127;
  1175. }
  1176. } elsif ($attrName eq "comModeUTE") {
  1177. if (!defined $attrVal){
  1178. } elsif ($attrVal !~ m/^auto|biDir|uniDir$/) {
  1179. Log3 $name, 2, "EnOcean $name attribute-value [$attrName] = $attrVal wrong";
  1180. CommandDeleteAttr(undef, "$name $attrName");
  1181. }
  1182. } elsif ($attrName eq "comType") {
  1183. if (!defined $attrVal){
  1184. } elsif ($attrVal !~ m/^TCM|RS485$/) {
  1185. Log3 $name, 2, "EnOcean $name attribute-value [$attrName] = $attrVal wrong";
  1186. CommandDeleteAttr(undef, "$name $attrName");
  1187. }
  1188. } elsif ($attrName eq "fingerprint") {
  1189. if (!defined $attrVal){
  1190. } elsif ($attrVal !~ m/^off|on$/) {
  1191. Log3 $name, 2, "EnOcean $name attribute-value [$attrName] = $attrVal wrong";
  1192. CommandDeleteAttr(undef, "$name $attrName");
  1193. }
  1194. } elsif ($attrName eq "learningMode") {
  1195. if (!defined $attrVal){
  1196. } elsif ($attrVal !~ m/^always|demand|nearfield$/) {
  1197. Log3 $name, 2, "EnOcean $name attribute-value [$attrName] = $attrVal wrong";
  1198. CommandDeleteAttr(undef, "$name $attrName");
  1199. }
  1200. } elsif ($attrName eq "sendInterval") {
  1201. if (!defined $attrVal){
  1202. } elsif (($attrVal + 0) < 0 || ($attrVal + 0) > 250) {
  1203. Log3 $name, 2, "EnOcean $name attribute-value [$attrName] = $attrVal wrong or out of range";
  1204. CommandDeleteAttr(undef, "$name $attrName");
  1205. }
  1206. } elsif ($attrName eq "smartAckLearnMode") {
  1207. if (!defined $attrVal){
  1208. } elsif ($attrVal !~ m/^simple|advance|advanceSelectRep$/) {
  1209. Log3 $name, 2, "EnOcean $name attribute-value [$attrName] = $attrVal wrong";
  1210. CommandDeleteAttr(undef, "$name $attrName");
  1211. }
  1212. } elsif ($attrName eq "smartAckMailboxMax") {
  1213. if (!defined $attrVal){
  1214. } elsif (($attrVal + 0) >= 0 && ($attrVal + 0) <= 20) {
  1215. TCM_Set($hash, ("set", "smartAckMailboxMax", $attrVal));
  1216. } else {
  1217. Log3 $name, 2, "EnOcean $name attribute-value [$attrName] = $attrVal wrong or out of range";
  1218. CommandDeleteAttr(undef, "$name $attrName");
  1219. }
  1220. }
  1221. return undef;
  1222. }
  1223. #
  1224. sub TCM_Notify(@) {
  1225. my ($hash, $dev) = @_;
  1226. if ($dev->{NAME} eq "global" && grep (m/^INITIALIZED|REREADCFG$/, @{$dev->{CHANGED}})){
  1227. TCM_InitSerialCom($hash);
  1228. }
  1229. return undef;
  1230. }
  1231. # Undef
  1232. sub
  1233. TCM_Undef($$)
  1234. {
  1235. my ($hash, $arg) = @_;
  1236. my $name = $hash->{NAME};
  1237. foreach my $d (sort keys %defs) {
  1238. if(defined($defs{$d}) &&
  1239. defined($defs{$d}{IODev}) &&
  1240. $defs{$d}{IODev} == $hash)
  1241. {
  1242. my $lev = ($reread_active ? 4 : 2);
  1243. Log3 $name, $lev, "TCM deleting port for $d";
  1244. delete $defs{$d}{IODev};
  1245. }
  1246. }
  1247. DevIo_CloseDev($hash);
  1248. return undef;
  1249. }
  1250. 1;
  1251. =pod
  1252. =item summary EnOcean Serial Protocol Inferface (ESP2/ESP3)
  1253. =item summary_DE EnOcean Serial Protocol Interface (ESP2/ESP3)
  1254. =begin html
  1255. <a name="TCM"></a>
  1256. <h3>TCM</h3>
  1257. <ul>
  1258. The TCM module serves an USB or TCP/IP connected TCM 120 or TCM 310x, TCM 410J, TCM 515
  1259. EnOcean Transceiver module. These are mostly packaged together with a serial to USB
  1260. chip and an antenna, e.g. the BSC BOR contains the TCM 120, the <a
  1261. href="http://www.enocean.com/de/enocean_module/usb-300-oem/">USB 300</a> from
  1262. EnOcean and the EUL from busware contains a TCM 310 or TCM 515. See also the datasheet
  1263. available from <a href="http://www.enocean.com">www.enocean.com</a>.
  1264. <br>
  1265. As the TCM 120 and the TCM 310, TCM 410J, TCM 515 speak completely different protocols, this
  1266. module implements 2 drivers in one. It is the "physical" part for the <a
  1267. href="#EnOcean">EnOcean</a> module.<br><br>
  1268. Please note that EnOcean repeaters also send Fhem data telegrams again. Use
  1269. <code>attr &lt;name&gt; <a href="#blockSenderID">blockSenderID</a> own</code>
  1270. to block receiving telegrams with TCM SenderIDs.<br>
  1271. The address range used by your transceiver module, can be found in the
  1272. parameters BaseID and LastID.
  1273. <br><br>
  1274. The transceiver moduls do not always support all commands. The supported range
  1275. of commands depends on the hardware and the firmware version. A firmware update
  1276. is usually not provided.
  1277. <br><br>
  1278. The TCM module enables also a wired connection to Eltako actuators over the
  1279. Eltako RS485 bus in the switchboard or distribution box via Eltako FGW14 RS232-RS485
  1280. gateway modules. These actuators are linked to an associated wireless antenna module
  1281. (FAM14) on the bus. The FAM14 device frequently polls the actuator status of all
  1282. associated devices if the FAM14 operating mode rotary switch is on position 4.
  1283. Therefore, actuator states can be retrieved more reliable, even after any fhem downtime,
  1284. when switch events or actuator confirmations could not have been tracked during the
  1285. downtime. As all actuators are polled approx. every 1-2 seconds, it should be avoided to
  1286. use event-on-update-reading. Use instead either event-on-change-reading or
  1287. event-min-interval.
  1288. The Eltako bus uses the EnOcean Serial Protocol version 2 (ESP2). For this reason,
  1289. a FGW14 can be configured as a ESP2. The attribute <a href="#TCM_comType">comType</a>
  1290. must be set to RS485.<br><br>
  1291. <a name="TCMdefine"></a>
  1292. <b>Define</b>
  1293. <ul>
  1294. <code>define &lt;name&gt; TCM [ESP2|ESP3] &lt;device&gt;</code> <br>
  1295. <br>
  1296. First you have to specify the type of the EnOcean Transceiver Chip, i.e
  1297. either ESP2 for the TCM 120 or ESP3 for the TCM 310x, TCM 410J, TCM 515, USB 300, USB400J, USB 500.<br><br>
  1298. <code>device</code> can take the same parameters (@baudrate, @directio,
  1299. TCP/IP, none) like the <a href="#CULdefine">CUL</a>, but you probably have
  1300. to specify the baudrate: the TCM 120 should be opened with 9600 Baud, the
  1301. TCM 310 and TCM 515 with 57600 baud. For Eltako FGW14 devices, type has to be set to 120 and
  1302. the baudrate has to be set to 57600 baud if the FGW14 operating mode
  1303. rotary switch is on position 6.<br><br>
  1304. Example:
  1305. <ul><code>
  1306. define BscBor TCM ESP2 /dev/ttyACM0@9600<br>
  1307. define FGW14 TCM ESP2 /dev/ttyS3@57600<br>
  1308. define TCM310 TCM ESP3 /dev/ttyACM0@57600<br>
  1309. define TCM310 TCM ESP3 COM1@57600 (Windows)<br>
  1310. </code></ul>
  1311. </ul>
  1312. <br>
  1313. <a name="TCMset"></a>
  1314. <b>Set</b><br>
  1315. <ul><b>ESP2 (TCM 120)</b><br>
  1316. <li>baseID [FF800000 ... FFFFFF80]<br>
  1317. Set the BaseID.<br>
  1318. Note: The firmware executes this command only up to then times to prevent misuse.</li>
  1319. <li>modem_off<br>
  1320. Deactivates TCM modem functionality</li>
  1321. <li>modem_on [0000 ... FFFF]<br>
  1322. Activates TCM modem functionality and sets the modem ID</li>
  1323. <li>teach &lt;t/s&gt;<br>
  1324. Set Fhem in learning mode, see <a href="#TCM_learningMode">learningMode</a>.<br>
  1325. The command is always required for UTE and to teach-in bidirectional actuators
  1326. e. g. EEP 4BS (RORG A5-20-XX),
  1327. see <a href="#EnOcean_teach-in">Teach-In / Teach-Out</a>.</li>
  1328. <li>reset<br>
  1329. Reset the device</li>
  1330. <li>sensitivity [00|01]<br>
  1331. Set the TCM radio sensitivity: low = 00, high = 01</li>
  1332. <li>sleep<br>
  1333. Enter the energy saving mode</li>
  1334. <li>wake<br>
  1335. Wakes up from sleep mode</li>
  1336. <br>
  1337. For details see the TCM 120 User Manual available from <a href="http://www.enocean.com">www.enocean.com</a>.
  1338. <br><br>
  1339. </ul>
  1340. <ul><b>ESP3 (TCM 310x, TCM 410J, TCM 515, USB 300, USB400J, USB 515)</b><br>
  1341. <li>baseID [FF800000 ... FFFFFF80]<br>
  1342. Set the BaseID.<br>
  1343. Note: The firmware executes this command only up to then times to prevent misuse.</li>
  1344. <li>baudrate [00|01|02|03]<br>
  1345. Modifies the baud rate of the EnOcean device.<br>
  1346. baudrate = 00: 56700 baud (default)<br>
  1347. baudrate = 01: 115200 baud<br>
  1348. baudrate = 02: 230400 baud<br>
  1349. baudrate = 03: 460800 baud</li>
  1350. <li>bist<br>
  1351. Perform Flash BIST operation (Built-in-self-test).</li>
  1352. <li>filterAdd &lt;FilterType&gt;&lt;FilterValue&gt;&lt;FilterKind&gt;<br>
  1353. Add filter to filter list. Description of the filter parameters and examples, see
  1354. <a href="https://www.enocean.com/esp">EnOcean Serial Protocol 3 (ESP3)</a></li>
  1355. <li>filterDel &lt;FilterType&gt;&lt;FilterValue&gt;<br>
  1356. Del filter from filter list. Description of the filter parameters, see
  1357. <a href="https://www.enocean.com/esp">EnOcean Serial Protocol 3 (ESP3)</a></li>
  1358. <li>filterDelAll<br>
  1359. Del all filter from filter list.</li>
  1360. <li>filterEnable &lt;FilterON/OFF&gt;&lt;FilterOperator&gt;<br>
  1361. Enable/Disable all supplied filters. Description of the filter parameters, see
  1362. <a href="https://www.enocean.com/esp">EnOcean Serial Protocol 3 (ESP3)</a></li>
  1363. <li>init<br>
  1364. Initialize serial communication and transceiver configuration</li>
  1365. <li>maturity [00|01]<br>
  1366. Waiting till end of maturity time before received radio telegrams will transmit:
  1367. radio telegrams are send immediately = 00, after the maturity time is elapsed = 01</li>
  1368. <li>mode [00|01]<br>
  1369. mode = 00: Compatible mode - ERP1 - gateway uses Packet Type 1 to transmit and receive radio telegrams<br>
  1370. mode = 01: Advanced mode - ERP2 - gateway uses Packet Type 10 to transmit and receive radio telegrams
  1371. (for FSK products with advanced protocol)</li>
  1372. <li>noiseThreshold [2E|2F|30|31|32|33|34|35|36|37|38]<br>
  1373. Modifies the noise threshold of the EnOcean device.<br>
  1374. noiseThreshold = 2E: -100 dBm<br>
  1375. noiseThreshold = 2F: -99 dBm<br>
  1376. noiseThreshold = 30: -98 dBm<br>
  1377. noiseThreshold = 31: -97 dBm<br>
  1378. noiseThreshold = 32: -96 dBm (default)<br>
  1379. noiseThreshold = 33: -95 dBm<br>
  1380. noiseThreshold = 34: -94 dBm<br>
  1381. noiseThreshold = 35: -93 dBm<br>
  1382. noiseThreshold = 36: -92 dBm<br>
  1383. noiseThreshold = 37: -91 dBm<br>
  1384. noiseThreshold = 38: -90 dBm</li>
  1385. <li>remanCode [00000000-FFFFFFFF]<br>
  1386. Sets secure code to unlock Remote Management functionality by radio.</li>
  1387. <li>remanRepeating [00|01]<br>
  1388. Select if REMAN telegrams originating from this module can be repeated: off = 00, on = 01.</li>
  1389. <li>reset<br>
  1390. Reset the device</li>
  1391. <li>resetEvents<br>
  1392. Reset generated events</li>
  1393. <li>repeater [0000|0101|0102]<br>
  1394. Set Repeater Level: off = 0000, 1 = 0101, 2 = 0102.</li>
  1395. <li>sleep &lt;t/10 ms&gt; (Range: 00000000 ... 00FFFFFF)<br>
  1396. Enter the energy saving mode</li>
  1397. <li>smartAckLearn &lt;t/s&gt;<br>
  1398. Set Fhem in Smart Ack learning mode.<br>
  1399. The post master fuctionality must be activated using the command <code>smartAckMailboxMax</code> in advance.<br>
  1400. The simple learnmode is supported, see <a href="#TCM_smartAckLearnMode">smartAckLearnMode</a><br>
  1401. A device, which is then also put in this state is to paired with
  1402. Fhem. Bidirectional learn in for 4BS, UTE and Generic Profiles are supported.<br>
  1403. <code>t/s</code> is the time for the learning period.</li>
  1404. <li>smartAckMailboxMax 0..20<br>
  1405. Enable the post master fuctionality and set amount of mailboxes available, 0 = disable post master functionality.
  1406. Maximum 28 mailboxes can be created. This upper limit is for each firmware restricted and may be smaller.</li>
  1407. <li>teach &lt;t/s&gt;<br>
  1408. Set Fhem in learning mode for RBS, 1BS, 4BS, GP, STE and UTE teach-in / teach-out, see <a href="#TCM_learningMode">learningMode</a>.<br>
  1409. The command is always required for STE, GB, UTE and to teach-in bidirectional actuators
  1410. e. g. EEP 4BS (RORG A5-20-XX)</li>
  1411. <li>startupDelay [00-FF]<br>
  1412. Sets the startup delay [10ms]: the time before the system initializes.</li>
  1413. <li>subtel [00|01]<br>
  1414. Transmitting additional subtelegram info: Enable = 01, Disable = 00</li>
  1415. <li>teach &lt;t/s&gt;<br>
  1416. Set Fhem in learning mode, see <a href="#TCM_learningMode">learningMode</a>.<br>
  1417. The command is always required for UTE and to teach-in bidirectional actuators
  1418. e. g. EEP 4BS (RORG A5-20-XX),
  1419. see <a href="#EnOcean_teach-in">Teach-In / Teach-Out</a>.</li>
  1420. <br>
  1421. For details see the EnOcean Serial Protocol 3 (ESP3) available from
  1422. <a href="http://www.enocean.com">www.enocean.com</a>.
  1423. <br><br>
  1424. </ul>
  1425. <a name="TCMget"></a>
  1426. <b>Get</b><br>
  1427. <ul><b>TCM 120</b><br>
  1428. <li>baseID<br>
  1429. Get the BaseID. You need this command in order to control EnOcean devices,
  1430. see the <a href="#EnOceandefine">EnOcean</a> paragraph.
  1431. </li>
  1432. <li>modem_status<br>
  1433. Requests the current modem status.</li>
  1434. <li>sensitivity<br>
  1435. Get the TCM radio sensitivity, low = 00, high = 01</li>
  1436. <li>version<br>
  1437. Read the device SW version / HW version, chip-ID, etc.</li>
  1438. <br>
  1439. For details see the TCM 120 User Manual available from <a href="http://www.enocean.com">www.enocean.com</a>.
  1440. <br><br>
  1441. </ul>
  1442. <ul><b>TCM 310</b><br>
  1443. <li>baseID<br>
  1444. Get the BaseID. You need this command in order to control EnOcean devices,
  1445. see the <a href="#EnOceandefine">EnOcean</a> paragraph.</li>
  1446. <li>dutycycleLimit<br>
  1447. Read actual duty cycle limit values.</li>
  1448. <li>filter<br>
  1449. Get supplied filters. Description of the filter parameters, see
  1450. <a href="https://www.enocean.com/esp">EnOcean Serial Protocol 3 (ESP3)</a></li>
  1451. <li>freqencyInfo<br>
  1452. Reads Frequency and protocol of the Device, see
  1453. <a href="https://www.enocean.com/esp">EnOcean Serial Protocol 3 (ESP3)</a></li>
  1454. <li>numSecureDev<br>
  1455. Read number of teached in secure devices.</li>
  1456. <li>remanRepeating<br>
  1457. REMAN telegrams originating from this module can be repeated: off = 00, on = 01.</li>
  1458. <li>repeater<br>
  1459. Read Repeater Level: off = 0000, 1 = 0101, 2 = 0102.</li>
  1460. <li>smartAckLearnMode<br>
  1461. Get current smart ack learn mode<br>
  1462. Enable: 00|01 = off|on<br>
  1463. Extended: 00|01|02 = simple|advance|advanceSelectRep</li>
  1464. <li>smartAckLearnedClients<br>
  1465. Get information about the learned smart ack clients</li>
  1466. <li>stepCode<br>
  1467. Reads Hardware Step code and Revision of the Device, see
  1468. <a href="https://www.enocean.com/esp">EnOcean Serial Protocol 3 (ESP3)</a></li>
  1469. <li>version<br>
  1470. Read the device SW version / HW version, chip-ID, etc.</li>
  1471. <br>
  1472. For details see the EnOcean Serial Protocol 3 (ESP3) available from
  1473. <a href="http://www.enocean.com">www.enocean.com</a>.
  1474. <br><br>
  1475. </ul>
  1476. <a name="TCMattr"></a>
  1477. <b>Attributes</b>
  1478. <ul>
  1479. <li><a name="TCM_blockSenderID">blockSenderID</a> &lt;own|no&gt;,
  1480. [blockSenderID] = own is default.<br>
  1481. Block receiving telegrams with a TCM SenderID sent by repeaters.
  1482. </li>
  1483. <li><a href="#attrdummy">dummy</a></li>
  1484. <li><a name="TCM_baseID">baseID</a> &lt;FF800000 ... FFFFFF80&gt;,
  1485. [baseID] = <none> is default.<br>
  1486. Set Transceiver baseID and override automatic allocation. Use this attribute only if the IODev does not allow automatic allocation.
  1487. </li>
  1488. <li><a name="TCM_fingerprint">fingerprint</a> &lt;off|on&gt;,
  1489. [fingerprint] = off is default.<br>
  1490. Activate the fingerprint function. The fingerprint function eliminates multiple identical data telegrams received via different TCM modules.<br>
  1491. The function must be activated for each TCM module.
  1492. </li>
  1493. <li><a name="TCM_comModeUTE">comModeUTE</a> &lt;auto|biDir|uniDir&gt;,
  1494. [comModeUTE] = auto is default.<br>
  1495. Presetting the communication method of actuators that be taught using the UTE teach-in. The automatic selection of the
  1496. communication method should only be overwrite manually, if this is explicitly required in the operating instructions of
  1497. the actuator. The parameters should then be immediately re-set to "auto".
  1498. </li>
  1499. <li><a name="TCM_comType">comType</a> &lt;TCM|RS485&gt;,
  1500. [comType] = TCM is default.<br>
  1501. Type of communication device
  1502. </li>
  1503. <li><a href="#do_not_notify">do_not_notify</a></li>
  1504. <li><a name="TCM_learningMode">learningMode</a> &lt;always|demand|nearfield&gt;,
  1505. [learningMode] = demand is default.<br>
  1506. Learning method for automatic setup of EnOcean devices:<br>
  1507. [learningMode] = always: Teach-In/Teach-Out telegrams always accepted, with the exception of bidirectional devices<br>
  1508. [learningMode] = demand: Teach-In/Teach-Out telegrams accepted if Fhem is in learning mode, see also <code>set &lt;IODev&gt; teach &lt;t/s&gt;</code><br>
  1509. [learningMode] = nearfield: Teach-In/Teach-Out telegrams accepted if Fhem is in learning mode and the signal strength RSSI >= -60 dBm.<be>
  1510. </li>
  1511. <li><a name="TCM_sendInterval">sendInterval</a> &lt;0 ... 250&gt;<br>
  1512. ESP2: [sendInterval] = 100 ms is default.<br>
  1513. ESP3: [sendInterval] = 0 ms is default.<br>
  1514. Smallest interval between two sending telegrams
  1515. </li>
  1516. <li><a name="TCM_smartAckLearnMode">smartAckLearnMode</a> &lt;simple|advance|advanceSelectRep&gt;<br>
  1517. select Smart Ack learn mode; only simple supported by Fhem
  1518. </li>
  1519. <li><a name="TCM_smartAckMailboxMax">smartAckMailboxMax</a> &lt;0 ... 28&gt;<br>
  1520. Amount of mailboxes available, 0 = disable post master functionality.
  1521. Maximum 28 mailboxes can be created. This upper limit is for each firmware restricted and may be smaller.
  1522. </li>
  1523. <li><a href="#verbose">verbose</a></li>
  1524. <br><br>
  1525. </ul>
  1526. <a name="TCMevents"></a>
  1527. <b>Generated events</b>
  1528. <ul>
  1529. <li>baseID &lt;transceiver response&gt;</li>
  1530. <li>maturity 00|01</li>
  1531. <li>modem_status &lt;transceiver response&gt;</li>
  1532. <li>numSecureDev &lt;transceiver response&gt;</li>
  1533. <li>repeater 0000|0101|0102</li>
  1534. <li>sensitivity 00|01</li>
  1535. <li>version &lt;transceiver response&gt;</li>
  1536. <li>state: opend|initialized</li>
  1537. <br><br>
  1538. </ul>
  1539. </ul>
  1540. =end html
  1541. =cut