00_HMUARTLGW.pm 88 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721
  1. ##############################################
  2. # $Id: 00_HMUARTLGW.pm 16166 2018-02-13 19:52:08Z mgernoth $
  3. #
  4. # HMUARTLGW provides support for the eQ-3 HomeMatic Wireless LAN Gateway
  5. # (HM-LGW-O-TW-W-EU) and the eQ-3 HomeMatic UART module (HM-MOD-UART), which
  6. # is part of the HomeMatic wireless module for the Raspberry Pi
  7. # (HM-MOD-RPI-PCB).
  8. #
  9. # TODO:
  10. # - Filter out "A112" from CUL_HM and synthesize response
  11. package main;
  12. use strict;
  13. use warnings;
  14. use Digest::MD5;
  15. use Time::HiRes qw(gettimeofday time);
  16. use Time::Local;
  17. eval "use Crypt::Rijndael";
  18. my $cryptFunc = ($@)?0:1;
  19. use constant {
  20. HMUARTLGW_OS_GET_APP => "00",
  21. HMUARTLGW_OS_GET_FIRMWARE => "02",
  22. HMUARTLGW_OS_CHANGE_APP => "03",
  23. HMUARTLGW_OS_ACK => "04",
  24. HMUARTLGW_OS_UPDATE_FIRMWARE => "05",
  25. HMUARTLGW_OS_UNSOL_CREDITS => "05",
  26. HMUARTLGW_OS_NORMAL_MODE => "06",
  27. HMUARTLGW_OS_UPDATE_MODE => "07",
  28. HMUARTLGW_OS_GET_CREDITS => "08",
  29. HMUARTLGW_OS_ENABLE_CREDITS => "09",
  30. HMUARTLGW_OS_ENABLE_CSMACA => "0A",
  31. HMUARTLGW_OS_GET_SERIAL => "0B",
  32. HMUARTLGW_OS_SET_TIME => "0E",
  33. HMUARTLGW_APP_SET_HMID => "00",
  34. HMUARTLGW_APP_GET_HMID => "01",
  35. HMUARTLGW_APP_SEND => "02",
  36. HMUARTLGW_APP_SET_CURRENT_KEY => "03", #key index, 00x17 when no key
  37. HMUARTLGW_APP_ACK => "04",
  38. HMUARTLGW_APP_RECV => "05",
  39. HMUARTLGW_APP_ADD_PEER => "06",
  40. HMUARTLGW_APP_REMOVE_PEER => "07",
  41. HMUARTLGW_APP_GET_PEERS => "08",
  42. HMUARTLGW_APP_PEER_ADD_AES => "09",
  43. HMUARTLGW_APP_PEER_REMOVE_AES => "0A",
  44. HMUARTLGW_APP_SET_TEMP_KEY => "0B", #key index, 00x17 when no key
  45. HMUARTLGW_APP_SET_PREVIOUS_KEY => "0F", #key index, 00x17 when no key
  46. HMUARTLGW_APP_DEFAULT_HMID => "10",
  47. HMUARTLGW_DUAL_GET_APP => "01",
  48. HMUARTLGW_DUAL_CHANGE_APP => "02",
  49. HMUARTLGW_ACK_NACK => "00",
  50. HMUARTLGW_ACK => "01",
  51. HMUARTLGW_ACK_INFO => "02",
  52. HMUARTLGW_ACK_WITH_RESPONSE => "03",
  53. HMUARTLGW_ACK_EUNKNOWN => "04",
  54. HMUARTLGW_ACK_ENOCREDITS => "05",
  55. HMUARTLGW_ACK_ECSMACA => "06",
  56. HMUARTLGW_ACK_WITH_MULTIPART_DATA => "07", #04 07 XX YY: part XX of YY
  57. HMUARTLGW_ACK_EINPROGRESS => "08",
  58. HMUARTLGW_ACK_WITH_RESPONSE_AES_OK => "0C",
  59. HMUARTLGW_ACK_WITH_RESPONSE_AES_KO => "0D",
  60. HMUARTLGW_RECV_RESP => "01",
  61. HMUARTLGW_RECV_RESP_WITH_AES_OK => "02",
  62. HMUARTLGW_RECV_RESP_WITH_AES_KO => "03",
  63. HMUARTLGW_RECV_TRIG => "11",
  64. HMUARTLGW_RECV_TRIG_WITH_AES_OK => "12",
  65. HMUARTLGW_DST_OS => 0,
  66. HMUARTLGW_DST_APP => 1,
  67. HMUARTLGW_DST_DUAL => 254,
  68. HMUARTLGW_DST_DUAL_ERR => 255,
  69. HMUARTLGW_STATE_NONE => 0,
  70. HMUARTLGW_STATE_QUERY_APP => 1,
  71. HMUARTLGW_STATE_ENTER_APP => 2,
  72. HMUARTLGW_STATE_GETSET_PARAMETERS => 3,
  73. HMUARTLGW_STATE_SET_HMID => 4,
  74. HMUARTLGW_STATE_GET_HMID => 5,
  75. HMUARTLGW_STATE_GET_DEFAULT_HMID => 6,
  76. HMUARTLGW_STATE_SET_TIME => 7,
  77. HMUARTLGW_STATE_GET_FIRMWARE => 8,
  78. HMUARTLGW_STATE_GET_SERIAL => 9,
  79. HMUARTLGW_STATE_SET_NORMAL_MODE => 10,
  80. HMUARTLGW_STATE_ENABLE_CSMACA => 11,
  81. HMUARTLGW_STATE_ENABLE_CREDITS => 12,
  82. HMUARTLGW_STATE_GET_INIT_CREDITS => 13,
  83. HMUARTLGW_STATE_SET_CURRENT_KEY => 14,
  84. HMUARTLGW_STATE_SET_PREVIOUS_KEY => 15,
  85. HMUARTLGW_STATE_SET_TEMP_KEY => 16,
  86. HMUARTLGW_STATE_UPDATE_PEER => 90,
  87. HMUARTLGW_STATE_UPDATE_PEER_AES1 => 91,
  88. HMUARTLGW_STATE_UPDATE_PEER_AES2 => 92,
  89. HMUARTLGW_STATE_UPDATE_PEER_CFG => 93,
  90. HMUARTLGW_STATE_SET_UPDATE_MODE => 95,
  91. HMUARTLGW_STATE_KEEPALIVE_INIT => 96,
  92. HMUARTLGW_STATE_KEEPALIVE_SENT => 97,
  93. HMUARTLGW_STATE_GET_CREDITS => 98,
  94. HMUARTLGW_STATE_RUNNING => 99,
  95. HMUARTLGW_STATE_SEND => 100,
  96. HMUARTLGW_STATE_SEND_NOACK => 101,
  97. HMUARTLGW_STATE_SEND_TIMED => 102,
  98. HMUARTLGW_STATE_UPDATE_COPRO => 200,
  99. HMUARTLGW_STATE_UNSUPPORTED_FW => 999,
  100. HMUARTLGW_CMD_TIMEOUT => 3,
  101. HMUARTLGW_CMD_RETRY_CNT => 3,
  102. HMUARTLGW_FIRMWARE_TIMEOUT => 10,
  103. HMUARTLGW_SEND_TIMEOUT => 10,
  104. HMUARTLGW_SEND_RETRY_SECONDS => 3,
  105. HMUARTLGW_BUSY_RETRY_MS => 50,
  106. HMUARTLGW_CSMACA_RETRY_MS => 200,
  107. HMUARTLGW_KEEPALIVE_SECONDS => 10,
  108. HMUARTLGW_KEEPALIVE_WARN_LATE_S => 4,
  109. };
  110. my %sets = (
  111. "hmPairForSec" => "",
  112. "hmPairSerial" => "",
  113. "reopen" => "noArg",
  114. "open" => "noArg",
  115. "close" => "noArg",
  116. "restart" => "noArg",
  117. "updateCoPro" => "",
  118. );
  119. my %gets = (
  120. "assignIDs" => "noArg",
  121. );
  122. sub HMUARTLGW_Initialize($)
  123. {
  124. my ($hash) = @_;
  125. require "$attr{global}{modpath}/FHEM/DevIo.pm";
  126. $hash->{ReadyFn} = "HMUARTLGW_Ready";
  127. $hash->{ReadFn} = "HMUARTLGW_Read";
  128. $hash->{WriteFn} = "HMUARTLGW_Write";
  129. $hash->{DefFn} = "HMUARTLGW_Define";
  130. $hash->{UndefFn} = "HMUARTLGW_Undefine";
  131. $hash->{SetFn} = "HMUARTLGW_Set";
  132. $hash->{GetFn} = "HMUARTLGW_Get";
  133. $hash->{AttrFn} = "HMUARTLGW_Attr";
  134. $hash->{RenameFn} = "HMUARTLGW_Rename";
  135. $hash->{ShutdownFn}= "HMUARTLGW_Shutdown";
  136. $hash->{AttrList}= "hmId " .
  137. "lgwPw " .
  138. "hmKey hmKey2 hmKey3 " .
  139. "dutyCycle:1,0 " .
  140. "csmaCa:0,1 " .
  141. "qLen " .
  142. "logIDs ".
  143. "dummy:1 ".
  144. "loadEvents:0,1 ".
  145. $readingFnAttributes;
  146. }
  147. sub HMUARTLGW_Connect($$);
  148. sub HMUARTLGW_SendPendingCmd($);
  149. sub HMUARTLGW_SendCmd($$);
  150. sub HMUARTLGW_GetSetParameterReq($);
  151. sub HMUARTLGW_getAesKeys($);
  152. sub HMUARTLGW_updateMsgLoad($$);
  153. sub HMUARTLGW_Read($);
  154. sub HMUARTLGW_RemoveHMPair($);
  155. sub HMUARTLGW_send($$$;$);
  156. sub HMUARTLGW_send_frame($$);
  157. sub HMUARTLGW_crc16($;$);
  158. sub HMUARTLGW_encrypt($$);
  159. sub HMUARTLGW_decrypt($$);
  160. sub HMUARTLGW_getVerbLvl($$$$);
  161. sub HMUARTLGW_firmwareGetBlock($$$);
  162. sub HMUARTLGW_updateCoPro($$);
  163. sub HMUARTLGW_DoInit($)
  164. {
  165. my ($hash) = @_;
  166. my $name = $hash->{NAME};
  167. $hash->{CNT} = 0x00;
  168. delete($hash->{DEVCNT});
  169. delete($hash->{'.crypto'});
  170. delete($hash->{keepAlive});
  171. delete($hash->{Helper});
  172. delete($hash->{AssignedPeerCnt});
  173. delete($hash->{msgLoadCurrent});
  174. delete($hash->{msgLoadHistory});
  175. delete($hash->{msgLoadHistoryAbs});
  176. delete($hash->{owner});
  177. $hash->{DevState} = HMUARTLGW_STATE_NONE;
  178. $hash->{XmitOpen} = 0;
  179. $hash->{LastOpen} = gettimeofday();
  180. $hash->{LGW_Init} = 1 if ($hash->{DevType} =~ m/^LGW/);
  181. $hash->{Helper}{Log}{IDs} = [ split(/,/, AttrVal($name, "logIDs", "")) ];
  182. $hash->{Helper}{Log}{Resolve} = 1;
  183. RemoveInternalTimer($hash);
  184. if ($hash->{DevType} eq "LGW") {
  185. my $keepAlive = {
  186. NR => $devcount++,
  187. NAME => "${name}:keepAlive",
  188. STATE => "uninitialized",
  189. TYPE => $hash->{TYPE},
  190. TEMPORARY => 1,
  191. directReadFn => \&HMUARTLGW_Read,
  192. DevType => "LGW-KeepAlive",
  193. '.lgwHash' => $hash,
  194. };
  195. $attr{$keepAlive->{NAME}}{room} = "hidden";
  196. $attr{$keepAlive->{NAME}}{verbose} = AttrVal($name, "verbose", undef);
  197. $defs{$keepAlive->{NAME}} = $keepAlive;
  198. DevIo_CloseDev($keepAlive);
  199. my ($ip, $port) = split(/:/, $hash->{DeviceName});
  200. $keepAlive->{DeviceName} = "${ip}:" . ($port + 1);
  201. DevIo_OpenDev($keepAlive, 0, "HMUARTLGW_DoInit", \&HMUARTLGW_Connect);
  202. $hash->{keepAlive} = $keepAlive;
  203. }
  204. InternalTimer(gettimeofday()+1, "HMUARTLGW_StartInit", $hash, 0);
  205. return;
  206. }
  207. sub HMUARTLGW_Connect($$)
  208. {
  209. my ($hash, $err) = @_;
  210. Log3($hash, 5, "HMUARTLGW $hash->{NAME}: ${err}") if ($err);
  211. }
  212. sub HMUARTLGW_Define($$)
  213. {
  214. my ($hash, $def) = @_;
  215. my @a = split("[ \t][ \t]*", $def);
  216. if (@a != 3) {
  217. return "wrong syntax: define <name> HMUARTLGW /path/to/port|hostname";
  218. }
  219. my $name = $a[0];
  220. my $dev = $a[2];
  221. HMUARTLGW_Undefine($hash, $name);
  222. if ($dev !~ m/\//) {
  223. $dev .= ":2000" if ($dev !~ m/:/);
  224. $hash->{DevType} = "LGW";
  225. } else {
  226. if ($dev =~ m/^uart:\/\/(.*)$/) {
  227. $dev = $1;
  228. } elsif ($dev !~ m/\@/) {
  229. $dev .= "\@115200";
  230. }
  231. $hash->{DevType} = "UART";
  232. $hash->{model} = "HM-MOD-UART";
  233. readingsBeginUpdate($hash);
  234. delete($hash->{READINGS}{"D-LANfirmware"});
  235. readingsBulkUpdate($hash, "D-type", $hash->{model});
  236. readingsEndUpdate($hash, 1);
  237. }
  238. $hash->{DeviceName} = $dev;
  239. $hash->{Clients} = ":CUL_HM:";
  240. my %ml = ( "1:CUL_HM" => "^A......................" );
  241. $hash->{MatchList} = \%ml;
  242. if (defined(AttrVal($name, "dummy", undef))) {
  243. readingsSingleUpdate($hash, "state", "dummy", 1);
  244. HMUARTLGW_updateCondition($hash);
  245. return;
  246. }
  247. return DevIo_OpenDev($hash, 0, "HMUARTLGW_DoInit", \&HMUARTLGW_Connect);
  248. }
  249. sub HMUARTLGW_Undefine($$;$)
  250. {
  251. my ($hash, $name, $noclose) = @_;
  252. RemoveInternalTimer($hash);
  253. RemoveInternalTimer("HMUARTLGW_CheckCredits:$name");
  254. RemoveInternalTimer("hmPairForSec:$name");
  255. if ($hash->{keepAlive}) {
  256. RemoveInternalTimer($hash->{keepAlive});
  257. DevIo_CloseDev($hash->{keepAlive});
  258. delete($attr{$hash->{keepAlive}->{NAME}});
  259. delete($defs{$hash->{keepAlive}->{NAME}});
  260. delete($hash->{keepAlive});
  261. }
  262. if (!$noclose) {
  263. DevIo_CloseDev($hash);
  264. Log3($hash, 3, "${name} device closed") if (!defined($hash->{FD}));
  265. }
  266. $hash->{DevState} = HMUARTLGW_STATE_NONE;
  267. $hash->{XmitOpen} = 0;
  268. HMUARTLGW_updateCondition($hash);
  269. }
  270. sub HMUARTLGW_Reopen($;$)
  271. {
  272. my ($hash, $noclose) = @_;
  273. $hash = $hash->{'.lgwHash'} if ($hash->{'.lgwHash'});
  274. my $name = $hash->{NAME};
  275. Log3($hash, 4, "HMUARTLGW ${name} Reopen");
  276. HMUARTLGW_Undefine($hash, $name, $noclose);
  277. return DevIo_OpenDev($hash, 1, "HMUARTLGW_DoInit", \&HMUARTLGW_Connect);
  278. }
  279. sub HMUARTLGW_Ready($)
  280. {
  281. my ($hash) = @_;
  282. my $name = $hash->{NAME};
  283. my $state = ReadingsVal($name, "state", "unknown");
  284. Log3($hash, 4, "HMUARTLGW ${name} ready: ${state}");
  285. if ((!$hash->{'.lgwHash'}) && $state eq "disconnected") {
  286. #don't immediately reconnect when we just connected, delay
  287. #for 5s because remote closed the connection on us
  288. if (defined($hash->{LastOpen}) &&
  289. $hash->{LastOpen} + 5 >= gettimeofday()) {
  290. return 0;
  291. }
  292. return HMUARTLGW_Reopen($hash, 1);
  293. }
  294. return 0;
  295. }
  296. sub HMUARTLGW_Rename($$)
  297. {
  298. my ($name, $old_name) = @_;
  299. my $hash = $defs{$name};
  300. if (defined($hash->{Helper}{Initialized})) {
  301. RemoveInternalTimer("HMUARTLGW_CheckCredits:${old_name}");
  302. InternalTimer(gettimeofday()+1, "HMUARTLGW_CheckCredits", "HMUARTLGW_CheckCredits:${name}", 0);
  303. }
  304. if ($hash->{hmPair}) {
  305. HMUARTLGW_RemoveHMPair("hmPairForSec:${old_name}");
  306. }
  307. }
  308. sub HMUARTLGW_Shutdown($)
  309. {
  310. my ($hash) = @_;
  311. my $name = $hash->{NAME};
  312. #switch to bootloader to stop the module from interfering
  313. HMUARTLGW_send($hash, HMUARTLGW_OS_CHANGE_APP, HMUARTLGW_DST_OS)
  314. if ($hash->{DevState} > HMUARTLGW_STATE_ENTER_APP);
  315. DevIo_CloseDev($hash->{keepAlive}) if ($hash->{keepAlive});
  316. DevIo_CloseDev($hash);
  317. return undef;
  318. }
  319. #HM-LGW communicates line-based during init
  320. sub HMUARTLGW_LGW_Init($)
  321. {
  322. my ($hash) = @_;
  323. my $name = $hash->{NAME};
  324. my $p = pack("H*", $hash->{PARTIAL});
  325. while($p =~ m/\n/) {
  326. (my $line, $p) = split(/\n/, $p, 2);
  327. $line =~ s/\r$//;
  328. Log3($hash, HMUARTLGW_getVerbLvl($hash, undef, undef, 5), "HMUARTLGW ${name} read (".length($line)."): ${line}");
  329. my $msg;
  330. if ($line =~ m/^H(..),01,([^,]*),([^,]*),([^,]*)$/) {
  331. $hash->{DEVCNT} = hex($1);
  332. $hash->{CNT} = hex($1);
  333. if ($hash->{DevType} eq "LGW") {
  334. $hash->{model} = $2;
  335. readingsBeginUpdate($hash);
  336. readingsBulkUpdate($hash, "D-type", $2);
  337. readingsBulkUpdate($hash, "D-serialNr", $4);
  338. my $fw = $3;
  339. if ($fw =~ m/^(\d+)\.(\d+)\.(\d+)$/) {
  340. my $fwver = (int($1) << 16) | (int($2) << 8) | int($3);
  341. $fw .= " (outdated)" if ($fwver < 0x010105);
  342. }
  343. readingsBulkUpdate($hash, "D-LANfirmware", $fw);
  344. readingsEndUpdate($hash, 1);
  345. }
  346. } elsif ($line =~ m/^V(..),(................................)$/) {
  347. $hash->{DEVCNT} = hex($1);
  348. $hash->{CNT} = hex($1);
  349. my $lgwName = $name;
  350. $lgwName = $hash->{'.lgwHash'}->{NAME} if ($hash->{'.lgwHash'});
  351. my $lgwPw = AttrVal($lgwName, "lgwPw", undef);
  352. if (!$cryptFunc) {
  353. Log3($hash, 1, "HMUARTLGW ${name} wants to initiate encrypted communication, but Crypt::Rijndael is not installed.");
  354. } elsif (!$lgwPw) {
  355. Log3($hash, 1, "HMUARTLGW ${name} wants to initiate encrypted communication, but no lgwPw set!");
  356. } else {
  357. my($s,$us) = gettimeofday();
  358. my $myiv = sprintf("%08x%06x%s", ($s & 0xffffffff), ($us & 0xffffff), scalar(reverse(substr($2, 14)))); #FIXME...
  359. my $key = Digest::MD5::md5($lgwPw);
  360. $hash->{'.crypto'}{cipher} = Crypt::Rijndael->new($key, Crypt::Rijndael::MODE_ECB());
  361. $hash->{'.crypto'}{encrypt}{keystream} = '';
  362. $hash->{'.crypto'}{encrypt}{ciphertext} = pack("H*", $2);
  363. $hash->{'.crypto'}{decrypt}{keystream} = '';
  364. $hash->{'.crypto'}{decrypt}{ciphertext} = pack("H*", $myiv);
  365. $msg = "V%02x,${myiv}\r\n";
  366. }
  367. } elsif ($line =~ m/^S(..),([^-]*)-/) {
  368. $hash->{DEVCNT} = hex($1);
  369. $hash->{CNT} = hex($1);
  370. if ($2 eq "BidCoS") {
  371. Log3($hash, 3, "HMUARTLGW ${name} BidCoS-port opened");
  372. } elsif ($2 eq "SysCom") {
  373. Log3($hash, 3, "HMUARTLGW ${name} KeepAlive-port opened");
  374. } else {
  375. Log3($hash, 1, "HMUARTLGW ${name} Unknown port identification received: ${2}, reopening");
  376. HMUARTLGW_Reopen($hash);
  377. return;
  378. }
  379. $msg = ">%02x,0000\r\n";
  380. delete($hash->{LGW_Init});
  381. }
  382. HMUARTLGW_sendAscii($hash, $msg) if ($msg);
  383. }
  384. $hash->{PARTIAL} = unpack("H*", $p);
  385. }
  386. #LGW KeepAlive
  387. sub HMUARTLGW_LGW_HandleKeepAlive($)
  388. {
  389. my ($hash) = @_;
  390. my $name = $hash->{NAME};
  391. my $p = pack("H*", $hash->{PARTIAL});
  392. while($p =~ m/\n/) {
  393. (my $line, $p) = split(/\n/, $p, 2);
  394. $line =~ s/\r$//;
  395. Log3($hash, HMUARTLGW_getVerbLvl($hash, undef, undef, 5), "HMUARTLGW ${name} read (".length($line)."): ${line}");
  396. my $msg;
  397. if ($line =~ m/^>L(..)/) {
  398. $hash->{DEVCNT} = hex($1);
  399. RemoveInternalTimer($hash);
  400. $hash->{DevState} = HMUARTLGW_STATE_KEEPALIVE_SENT;
  401. $msg = "K%02x\r\n";
  402. InternalTimer(gettimeofday()+HMUARTLGW_CMD_TIMEOUT, "HMUARTLGW_CheckCmdResp", $hash, 0);
  403. } elsif ($line =~ m/^>K(..)/) {
  404. $hash->{DEVCNT} = hex($1);
  405. RemoveInternalTimer($hash);
  406. $hash->{DevState} = HMUARTLGW_STATE_RUNNING;
  407. #now we have 15s
  408. $hash->{Helper}{NextKeepAlive} = gettimeofday() + HMUARTLGW_KEEPALIVE_SECONDS;
  409. InternalTimer($hash->{Helper}{NextKeepAlive}, "HMUARTLGW_SendKeepAlive", $hash, 0);
  410. }
  411. HMUARTLGW_sendAscii($hash, $msg) if ($msg);
  412. }
  413. $hash->{PARTIAL} = unpack("H*", $p);
  414. return;
  415. }
  416. sub HMUARTLGW_SendKeepAlive($)
  417. {
  418. my ($hash) = @_;
  419. my $name = $hash->{NAME};
  420. RemoveInternalTimer($hash);
  421. $hash->{DevState} = HMUARTLGW_STATE_KEEPALIVE_SENT;
  422. HMUARTLGW_sendAscii($hash, "K%02x\r\n");
  423. my $diff = gettimeofday() - $hash->{Helper}{NextKeepAlive};
  424. Log3($hash, 1, "HMUARTLGW ${name} KeepAlive sent " .
  425. sprintf("%.3f", $diff) .
  426. "s too late, this might cause a disconnect!")
  427. if ($diff > HMUARTLGW_KEEPALIVE_WARN_LATE_S);
  428. InternalTimer(gettimeofday()+HMUARTLGW_CMD_TIMEOUT, "HMUARTLGW_CheckCmdResp", $hash, 0);
  429. return;
  430. }
  431. sub HMUARTLGW_CheckCredits($)
  432. {
  433. my ($in) = shift;
  434. my (undef, $name) = split(':',$in);
  435. my $hash = $defs{$name};
  436. my $next = 15;
  437. if ($hash->{DevState} == HMUARTLGW_STATE_RUNNING) {
  438. Log3($hash, 5, "HMUARTLGW ${name} checking credits (from timer)");
  439. $hash->{Helper}{OneParameterOnly} = 1;
  440. if (++$hash->{Helper}{CreditTimer} % (4*60*2)) { #about every 2h
  441. $hash->{DevState} = HMUARTLGW_STATE_GET_CREDITS;
  442. } else {
  443. $hash->{DevState} = HMUARTLGW_STATE_SET_TIME;
  444. $next = 1;
  445. }
  446. HMUARTLGW_GetSetParameterReq($hash);
  447. } else {
  448. $next = 1;
  449. }
  450. RemoveInternalTimer("HMUARTLGW_CheckCredits:$name");
  451. InternalTimer(gettimeofday()+$next, "HMUARTLGW_CheckCredits", "HMUARTLGW_CheckCredits:$name", 0);
  452. }
  453. sub HMUARTLGW_SendPendingCmd($)
  454. {
  455. my ($hash) = @_;
  456. my $name = $hash->{NAME};
  457. if (defined($hash->{XmitOpen}) &&
  458. $hash->{XmitOpen} == 2) {
  459. if ($hash->{Helper}{PendingCMD}) {
  460. my $qLen = AttrVal($name, "qLen", 60);
  461. if (scalar(@{$hash->{Helper}{PendingCMD}}) < $qLen) {
  462. $hash->{XmitOpen} = 1;
  463. }
  464. } else {
  465. $hash->{XmitOpen} = 1;
  466. }
  467. }
  468. if ($hash->{DevState} == HMUARTLGW_STATE_RUNNING &&
  469. defined($hash->{Helper}{PendingCMD}) &&
  470. @{$hash->{Helper}{PendingCMD}}) {
  471. my $cmd = $hash->{Helper}{PendingCMD}->[0];
  472. if ($cmd->{cmd} eq "AESkeys") {
  473. Log3($hash, 5, "HMUARTLGW ${name} setting keys");
  474. $hash->{Helper}{OneParameterOnly} = 1;
  475. $hash->{DevState} = HMUARTLGW_STATE_SET_CURRENT_KEY;
  476. HMUARTLGW_GetSetParameterReq($hash);
  477. shift(@{$hash->{Helper}{PendingCMD}}); #retry will be handled by GetSetParameter
  478. } elsif ($cmd->{cmd} eq "Credits") {
  479. Log3($hash, 5, "HMUARTLGW ${name} checking credits (from send)");
  480. $hash->{Helper}{OneParameterOnly} = 1;
  481. $hash->{DevState} = HMUARTLGW_STATE_GET_CREDITS;
  482. HMUARTLGW_GetSetParameterReq($hash);
  483. shift(@{$hash->{Helper}{PendingCMD}}); #retry will be handled by GetSetParameter
  484. } elsif ($cmd->{cmd} eq "HMID") {
  485. Log3($hash, 5, "HMUARTLGW ${name} setting hmId");
  486. $hash->{Helper}{OneParameterOnly} = 1;
  487. $hash->{DevState} = HMUARTLGW_STATE_SET_HMID;
  488. HMUARTLGW_GetSetParameterReq($hash);
  489. shift(@{$hash->{Helper}{PendingCMD}}); #retry will be handled by GetSetParameter
  490. } elsif ($cmd->{cmd} eq "DutyCycle") {
  491. Log3($hash, 5, "HMUARTLGW ${name} Enabling/Disabling DutyCycle");
  492. $hash->{Helper}{OneParameterOnly} = 1;
  493. $hash->{DevState} = HMUARTLGW_STATE_ENABLE_CREDITS;
  494. HMUARTLGW_GetSetParameterReq($hash);
  495. shift(@{$hash->{Helper}{PendingCMD}}); #retry will be handled by GetSetParameter
  496. } elsif ($cmd->{cmd} eq "CSMACA") {
  497. Log3($hash, 5, "HMUARTLGW ${name} Enabling/Disabling CSMA/CA");
  498. $hash->{Helper}{OneParameterOnly} = 1;
  499. $hash->{DevState} = HMUARTLGW_STATE_ENABLE_CSMACA;
  500. HMUARTLGW_GetSetParameterReq($hash);
  501. shift(@{$hash->{Helper}{PendingCMD}}); #retry will be handled by GetSetParameter
  502. } elsif ($cmd->{cmd} eq "UpdateMode") {
  503. Log3($hash, 5, "HMUARTLGW ${name} Entering HM update mode (100k)");
  504. $hash->{Helper}{OneParameterOnly} = 1;
  505. $hash->{DevState} = HMUARTLGW_STATE_SET_UPDATE_MODE;
  506. HMUARTLGW_GetSetParameterReq($hash);
  507. shift(@{$hash->{Helper}{PendingCMD}}); #retry will be handled by GetSetParameter
  508. } elsif ($cmd->{cmd} eq "NormalMode") {
  509. Log3($hash, 5, "HMUARTLGW ${name} Entering HM normal mode (10k)");
  510. $hash->{Helper}{OneParameterOnly} = 1;
  511. $hash->{DevState} = HMUARTLGW_STATE_SET_NORMAL_MODE;
  512. HMUARTLGW_GetSetParameterReq($hash);
  513. shift(@{$hash->{Helper}{PendingCMD}}); #retry will be handled by GetSetParameter
  514. } else {
  515. #try for HMUARTLGW_SEND_RETRY_SECONDS, packet was not sent wirelessly yet!
  516. if (defined($cmd->{RetryStart}) &&
  517. $cmd->{RetryStart} + HMUARTLGW_SEND_RETRY_SECONDS <= gettimeofday()) {
  518. my $oldmsg = shift(@{$hash->{Helper}{PendingCMD}});
  519. Log3($hash, 1, "HMUARTLGW ${name} resend failed too often, dropping packet: 01 $oldmsg->{cmd}");
  520. #try next command
  521. return HMUARTLGW_SendPendingCmd($hash);
  522. } elsif ($cmd->{RetryStart}) {
  523. Log3($hash, 5, "HMUARTLGW ${name} Retry, initial retry initiated at: ".$cmd->{RetryStart});
  524. }
  525. RemoveInternalTimer($hash);
  526. my $dst = substr($cmd->{cmd}, 20, 6);
  527. if ((!defined($cmd->{delayed})) &&
  528. $modules{CUL_HM}{defptr}{$dst}{helper}{io}{nextSend}){
  529. my $tn = gettimeofday();
  530. my $dDly = $modules{CUL_HM}{defptr}{$dst}{helper}{io}{nextSend} - $tn;
  531. #$dDly -= 0.05 if ($typ eq "02");# delay at least 50ms for ACK, but not 100
  532. if ($dDly > 0.01) {
  533. Log3($hash, 5, "HMUARTLGW ${name} delaying send to ${dst} for ${dDly}");
  534. $hash->{DevState} = HMUARTLGW_STATE_SEND_TIMED;
  535. InternalTimer($tn + $dDly, "HMUARTLGW_SendPendingTimer", $hash, 0);
  536. $cmd->{delayed} = 1;
  537. return;
  538. }
  539. }
  540. delete($cmd->{delayed}) if (defined($cmd->{delayed}));
  541. if (hex(substr($cmd->{cmd}, 10, 2)) & (1 << 5)) { #BIDI
  542. InternalTimer(gettimeofday()+HMUARTLGW_SEND_TIMEOUT, "HMUARTLGW_CheckCmdResp", $hash, 0);
  543. $hash->{DevState} = HMUARTLGW_STATE_SEND;
  544. } else {
  545. Log3($hash, 5, "HMUARTLGW ${name} !BIDI");
  546. InternalTimer(gettimeofday()+0.3, "HMUARTLGW_CheckCmdResp", $hash, 0);
  547. $hash->{DevState} = HMUARTLGW_STATE_SEND_NOACK;
  548. }
  549. $cmd->{CNT} = HMUARTLGW_send($hash, $cmd->{cmd}, HMUARTLGW_DST_APP);
  550. }
  551. }
  552. }
  553. sub HMUARTLGW_SendPendingTimer($)
  554. {
  555. my ($hash) = @_;
  556. $hash->{DevState} = HMUARTLGW_STATE_RUNNING;
  557. return HMUARTLGW_SendPendingCmd($hash);
  558. }
  559. sub HMUARTLGW_SendCmd($$)
  560. {
  561. my ($hash, $cmd) = @_;
  562. #Drop commands when device is not active
  563. return if ($hash->{DevState} == HMUARTLGW_STATE_NONE);
  564. push @{$hash->{Helper}{PendingCMD}}, { cmd => $cmd };
  565. return HMUARTLGW_SendPendingCmd($hash);
  566. }
  567. sub HMUARTLGW_UpdatePeerReq($;$) {
  568. my ($hash, $peer) = @_;
  569. my $name = $hash->{NAME};
  570. $peer = $hash->{Helper}{UpdatePeer} if (!$peer);
  571. Log3($hash, 4, "HMUARTLGW ${name} UpdatePeerReq: ".$peer->{id}.", state ".$hash->{DevState});
  572. my $msg;
  573. if ($hash->{DevState} == HMUARTLGW_STATE_UPDATE_PEER) {
  574. $hash->{Helper}{UpdatePeer} = $peer;
  575. if ($peer->{operation} eq "+") {
  576. my $flags = hex($peer->{flags});
  577. $msg = HMUARTLGW_APP_ADD_PEER .
  578. $peer->{id} .
  579. $peer->{kNo} .
  580. (($flags & 0x02) ? "01" : "00") . #Wakeup?
  581. "00"; #setting this causes "0013" messages for thermostats on wakeup ?!
  582. } else {
  583. $msg = HMUARTLGW_APP_REMOVE_PEER . $peer->{id};
  584. }
  585. $hash->{Helper}{UpdatePeer}{msg} = $msg;
  586. } elsif ($hash->{DevState} == HMUARTLGW_STATE_UPDATE_PEER_AES1) {
  587. my $offset = 0;
  588. foreach my $c (reverse(unpack "(A2)*", $hash->{Helper}{UpdatePeer}{aes})) {
  589. $c = ~hex($c);
  590. for (my $chan = 0; $chan < 8; $chan++) {
  591. if ($c & (1 << $chan)) {
  592. Log3($hash, 4, "HMUARTLGW ${name} Disabling AES for channel " . ($chan+$offset));
  593. $msg .= sprintf("%02x", $chan+$offset);
  594. }
  595. }
  596. $offset += 8;
  597. }
  598. if (defined($msg)) {
  599. $msg = HMUARTLGW_APP_PEER_REMOVE_AES . $hash->{Helper}{UpdatePeer}{id} . ${msg};
  600. } else {
  601. return HMUARTLGW_GetSetParameters($hash);
  602. }
  603. } elsif ($hash->{DevState} == HMUARTLGW_STATE_UPDATE_PEER_AES2) {
  604. if ($peer->{operation} eq "+" && defined($peer->{aesChannels})) {
  605. Log3($hash, 4, "HMUARTLGW ${name} AESchannels: " . $peer->{aesChannels});
  606. my $offset = 0;
  607. foreach my $c (unpack "(A2)*", $peer->{aesChannels}) {
  608. $c = hex($c);
  609. for (my $chan = 0; $chan < 8; $chan++) {
  610. if ($c & (1 << $chan)) {
  611. Log3($hash, 4, "HMUARTLGW ${name} Enabling AES for channel " . ($chan+$offset));
  612. $msg .= sprintf("%02x", $chan+$offset);
  613. }
  614. }
  615. $offset += 8;
  616. }
  617. }
  618. if (defined($msg)) {
  619. $msg = HMUARTLGW_APP_PEER_ADD_AES . $peer->{id} . $msg;
  620. } else {
  621. return HMUARTLGW_GetSetParameters($hash);
  622. }
  623. } elsif ($hash->{DevState} == HMUARTLGW_STATE_UPDATE_PEER_CFG) {
  624. $msg = $hash->{Helper}{UpdatePeer}{msg};
  625. }
  626. if ($msg) {
  627. HMUARTLGW_send($hash, $msg, HMUARTLGW_DST_APP, $peer->{id});
  628. RemoveInternalTimer($hash);
  629. InternalTimer(gettimeofday()+HMUARTLGW_CMD_TIMEOUT, "HMUARTLGW_CheckCmdResp", $hash, 0);
  630. }
  631. }
  632. sub HMUARTLGW_UpdatePeer($$) {
  633. my ($hash, $peer) = @_;
  634. if ($hash->{DevState} == HMUARTLGW_STATE_RUNNING) {
  635. $hash->{DevState} = HMUARTLGW_STATE_UPDATE_PEER;
  636. HMUARTLGW_UpdatePeerReq($hash, $peer);
  637. } else {
  638. #enqueue for next update
  639. push @{$hash->{Helper}{PeerQueue}}, $peer;
  640. }
  641. }
  642. sub HMUARTLGW_UpdateQueuedPeer($) {
  643. my ($hash) = @_;
  644. if ($hash->{DevState} == HMUARTLGW_STATE_RUNNING &&
  645. $hash->{Helper}{PeerQueue} &&
  646. @{$hash->{Helper}{PeerQueue}}) {
  647. return HMUARTLGW_UpdatePeer($hash, shift(@{$hash->{Helper}{PeerQueue}}));
  648. }
  649. }
  650. sub HMUARTLGW_ParsePeer($$) {
  651. my ($hash, $msg) = @_;
  652. #040701010002fffffffffffffff9
  653. $hash->{AssignedPeerCnt} = hex(substr($msg, 8, 4));
  654. if (length($msg) > 12) {
  655. $hash->{Peers}{$hash->{Helper}{UpdatePeer}->{id}} = $hash->{Helper}{UpdatePeer}->{config};
  656. $hash->{Helper}{UpdatePeer}{aes} = substr($msg, 12);
  657. Log3($hash, HMUARTLGW_getVerbLvl($hash, $hash->{Helper}{UpdatePeer}->{id}, $hash->{Helper}{UpdatePeer}->{id}, 4),
  658. "HMUARTLGW $hash->{NAME} added peer: " . $hash->{Helper}{UpdatePeer}->{id} .
  659. ", aesChannels: " . $hash->{Helper}{UpdatePeer}{aes});
  660. } else {
  661. delete($hash->{Peers}{$hash->{Helper}{UpdatePeer}->{id}});
  662. Log3($hash, HMUARTLGW_getVerbLvl($hash, $hash->{Helper}{UpdatePeer}->{id}, $hash->{Helper}{UpdatePeer}->{id}, 4),
  663. "HMUARTLGW $hash->{NAME} remove peer: ". $hash->{Helper}{UpdatePeer}->{id});
  664. }
  665. }
  666. sub HMUARTLGW_GetSetParameterReq($) {
  667. my ($hash) = @_;
  668. my $name = $hash->{NAME};
  669. RemoveInternalTimer($hash);
  670. if ($hash->{DevState} == HMUARTLGW_STATE_SET_HMID) {
  671. my $hmId = AttrVal($name, "hmId", undef);
  672. if (!defined($hmId)) {
  673. $hash->{DevState} = HMUARTLGW_STATE_GET_HMID;
  674. return HMUARTLGW_GetSetParameterReq($hash);
  675. }
  676. HMUARTLGW_send($hash, HMUARTLGW_APP_SET_HMID . $hmId, HMUARTLGW_DST_APP);
  677. } elsif ($hash->{DevState} == HMUARTLGW_STATE_GET_HMID) {
  678. HMUARTLGW_send($hash, HMUARTLGW_APP_GET_HMID, HMUARTLGW_DST_APP);
  679. } elsif ($hash->{DevState} == HMUARTLGW_STATE_GET_DEFAULT_HMID) {
  680. HMUARTLGW_send($hash, HMUARTLGW_APP_DEFAULT_HMID, HMUARTLGW_DST_APP);
  681. } elsif ($hash->{DevState} == HMUARTLGW_STATE_SET_TIME) {
  682. my $tmsg = HMUARTLGW_OS_SET_TIME;
  683. my $t = time();
  684. my @l = localtime($t);
  685. my $off = (timegm(@l) - timelocal(@l)) / 1800;
  686. $tmsg .= sprintf("%04x%02x", $t, $off & 0xff);
  687. HMUARTLGW_send($hash, $tmsg, HMUARTLGW_DST_OS);
  688. } elsif ($hash->{DevState} == HMUARTLGW_STATE_GET_FIRMWARE) {
  689. HMUARTLGW_send($hash, HMUARTLGW_OS_GET_FIRMWARE, HMUARTLGW_DST_OS);
  690. } elsif ($hash->{DevState} == HMUARTLGW_STATE_GET_SERIAL) {
  691. HMUARTLGW_send($hash, HMUARTLGW_OS_GET_SERIAL, HMUARTLGW_DST_OS);
  692. } elsif ($hash->{DevState} == HMUARTLGW_STATE_SET_NORMAL_MODE) {
  693. HMUARTLGW_send($hash, HMUARTLGW_OS_NORMAL_MODE, HMUARTLGW_DST_OS);
  694. } elsif ($hash->{DevState} == HMUARTLGW_STATE_ENABLE_CSMACA) {
  695. my $csma_ca = AttrVal($name, "csmaCa", 0);
  696. HMUARTLGW_send($hash, HMUARTLGW_OS_ENABLE_CSMACA . sprintf("%02x", $csma_ca), HMUARTLGW_DST_OS);
  697. } elsif ($hash->{DevState} == HMUARTLGW_STATE_ENABLE_CREDITS) {
  698. my $dutyCycle = AttrVal($name, "dutyCycle", 1);
  699. HMUARTLGW_send($hash, HMUARTLGW_OS_ENABLE_CREDITS . sprintf("%02x", $dutyCycle), HMUARTLGW_DST_OS);
  700. } elsif ($hash->{DevState} == HMUARTLGW_STATE_GET_INIT_CREDITS) {
  701. HMUARTLGW_send($hash, HMUARTLGW_OS_GET_CREDITS, HMUARTLGW_DST_OS);
  702. } elsif ($hash->{DevState} == HMUARTLGW_STATE_SET_CURRENT_KEY) {
  703. #current key is key with highest idx
  704. @{$hash->{Helper}{AESKeyQueue}} = HMUARTLGW_getAesKeys($hash);
  705. my $key = shift(@{$hash->{Helper}{AESKeyQueue}});
  706. HMUARTLGW_send($hash, HMUARTLGW_APP_SET_CURRENT_KEY . ($key?$key:"00"x17), HMUARTLGW_DST_APP);
  707. } elsif ($hash->{DevState} == HMUARTLGW_STATE_SET_PREVIOUS_KEY) {
  708. #previous key has second highest index
  709. my $key = shift(@{$hash->{Helper}{AESKeyQueue}});
  710. HMUARTLGW_send($hash, HMUARTLGW_APP_SET_PREVIOUS_KEY . ($key?$key:"00"x17), HMUARTLGW_DST_APP);
  711. } elsif ($hash->{DevState} == HMUARTLGW_STATE_SET_TEMP_KEY) {
  712. #temp key has third highest index
  713. my $key = shift(@{$hash->{Helper}{AESKeyQueue}});
  714. delete($hash->{Helper}{AESKeyQueue});
  715. HMUARTLGW_send($hash, HMUARTLGW_APP_SET_TEMP_KEY . ($key?$key:"00"x17), HMUARTLGW_DST_APP);
  716. } elsif ($hash->{DevState} >= HMUARTLGW_STATE_UPDATE_PEER &&
  717. $hash->{DevState} <= HMUARTLGW_STATE_UPDATE_PEER_CFG) {
  718. HMUARTLGW_UpdatePeerReq($hash);
  719. return;
  720. } elsif ($hash->{DevState} == HMUARTLGW_STATE_GET_CREDITS) {
  721. $hash->{Helper}{RoundTrip}{Calc} = 1;
  722. HMUARTLGW_send($hash, HMUARTLGW_OS_GET_CREDITS, HMUARTLGW_DST_OS);
  723. } elsif ($hash->{DevState} == HMUARTLGW_STATE_SET_UPDATE_MODE) {
  724. #E9CA is magic
  725. HMUARTLGW_send($hash, HMUARTLGW_OS_UPDATE_MODE . "E9CA", HMUARTLGW_DST_OS);
  726. } else {
  727. return;
  728. }
  729. InternalTimer(gettimeofday()+HMUARTLGW_CMD_TIMEOUT, "HMUARTLGW_CheckCmdResp", $hash, 0);
  730. }
  731. sub HMUARTLGW_GetSetParameters($;$$)
  732. {
  733. my ($hash, $msg, $recvtime) = @_;
  734. my $name = $hash->{NAME};
  735. my $oldState = $hash->{DevState};
  736. my $hmId = AttrVal($name, "hmId", undef);
  737. my $ack = substr($msg, 2, 2) if ($msg);
  738. RemoveInternalTimer($hash);
  739. Log3($hash, HMUARTLGW_getVerbLvl($hash, undef, undef, 5), "HMUARTLGW ${name} GetSet Ack: ${ack}, state ".$hash->{DevState}) if ($ack);
  740. Log3($hash, 1, "HMUARTLGW ${name} GetSet NACK: ${ack}, state ".$hash->{DevState}) if ($ack && $ack =~ m/^0400/);
  741. if ($ack && ($ack eq HMUARTLGW_ACK_EINPROGRESS)) {
  742. if (defined($hash->{Helper}{GetSetRetry}) &&
  743. $hash->{Helper}{GetSetRetry} > 10) {
  744. delete($hash->{Helper}{GetSetRetry});
  745. #Reboot device
  746. HMUARTLGW_send($hash, HMUARTLGW_OS_CHANGE_APP, HMUARTLGW_DST_OS);
  747. return;
  748. }
  749. $hash->{Helper}{GetSetRetry}++;
  750. #Retry
  751. InternalTimer(gettimeofday()+0.5, "HMUARTLGW_GetSetParameterReq", $hash, 0);
  752. return;
  753. }
  754. delete($hash->{Helper}{GetSetRetry});
  755. if ($hash->{DevState} == HMUARTLGW_STATE_GETSET_PARAMETERS) {
  756. if ($hmId) {
  757. $hash->{DevState} = HMUARTLGW_STATE_SET_HMID;
  758. } else {
  759. $hash->{DevState} = HMUARTLGW_STATE_GET_HMID;
  760. }
  761. } elsif ($hash->{DevState} == HMUARTLGW_STATE_SET_HMID) {
  762. $hash->{DevState} = HMUARTLGW_STATE_GET_HMID;
  763. } elsif ($hash->{DevState} == HMUARTLGW_STATE_GET_HMID) {
  764. if ($ack eq HMUARTLGW_ACK_WITH_MULTIPART_DATA) {
  765. readingsSingleUpdate($hash, "D-HMIdAssigned", uc(substr($msg, 8)), 1);
  766. $hash->{owner} = uc(substr($msg, 8));
  767. }
  768. $hash->{DevState} = HMUARTLGW_STATE_GET_DEFAULT_HMID;
  769. } elsif ($hash->{DevState} == HMUARTLGW_STATE_GET_DEFAULT_HMID) {
  770. if ($ack eq HMUARTLGW_ACK_WITH_MULTIPART_DATA) {
  771. readingsSingleUpdate($hash, "D-HMIdOriginal", uc(substr($msg, 8)), 1);
  772. }
  773. $hash->{DevState} = HMUARTLGW_STATE_SET_TIME;
  774. } elsif ($hash->{DevState} == HMUARTLGW_STATE_SET_TIME) {
  775. $hash->{DevState} = HMUARTLGW_STATE_GET_FIRMWARE;
  776. } elsif ($hash->{DevState} == HMUARTLGW_STATE_GET_FIRMWARE) {
  777. if ($ack eq HMUARTLGW_ACK_INFO) {
  778. my $fw = hex(substr($msg, 10, 2)).".".
  779. hex(substr($msg, 12, 2)).".".
  780. hex(substr($msg, 14, 2));
  781. $hash->{Helper}{FW} = hex((substr($msg, 10, 6)));
  782. $fw .= " (outdated)" if ($hash->{Helper}{FW} < 0x010401);
  783. readingsSingleUpdate($hash, "D-firmware", $fw, 1);
  784. }
  785. $hash->{DevState} = HMUARTLGW_STATE_SET_NORMAL_MODE;
  786. } elsif ($hash->{DevState} == HMUARTLGW_STATE_SET_NORMAL_MODE) {
  787. $hash->{DevState} = HMUARTLGW_STATE_GET_SERIAL;
  788. } elsif ($hash->{DevState} == HMUARTLGW_STATE_GET_SERIAL) {
  789. if ($ack eq HMUARTLGW_ACK_INFO && $hash->{DevType} eq "UART") {
  790. readingsSingleUpdate($hash, "D-serialNr", pack("H*", substr($msg, 4)), 1);
  791. }
  792. $hash->{DevState} = HMUARTLGW_STATE_ENABLE_CSMACA;
  793. } elsif ($hash->{DevState} == HMUARTLGW_STATE_ENABLE_CSMACA) {
  794. $hash->{DevState} = HMUARTLGW_STATE_ENABLE_CREDITS;
  795. } elsif ($hash->{DevState} == HMUARTLGW_STATE_ENABLE_CREDITS) {
  796. $hash->{DevState} = HMUARTLGW_STATE_GET_INIT_CREDITS;
  797. } elsif ($hash->{DevState} == HMUARTLGW_STATE_GET_INIT_CREDITS) {
  798. if ($ack eq HMUARTLGW_ACK_INFO) {
  799. HMUARTLGW_updateMsgLoad($hash, hex(substr($msg, 4)));
  800. }
  801. $hash->{DevState} = HMUARTLGW_STATE_SET_CURRENT_KEY;
  802. } elsif ($hash->{DevState} == HMUARTLGW_STATE_SET_CURRENT_KEY) {
  803. $hash->{DevState} = HMUARTLGW_STATE_SET_PREVIOUS_KEY;
  804. } elsif ($hash->{DevState} == HMUARTLGW_STATE_SET_PREVIOUS_KEY) {
  805. $hash->{DevState} = HMUARTLGW_STATE_SET_TEMP_KEY;
  806. } elsif ($hash->{DevState} == HMUARTLGW_STATE_SET_TEMP_KEY) {
  807. $hash->{DevState} = HMUARTLGW_STATE_RUNNING;
  808. } elsif ($hash->{DevState} == HMUARTLGW_STATE_GET_CREDITS) {
  809. if (defined($recvtime) &&
  810. defined($hash->{Helper}{AckPending}{$hash->{DEVCNT}}) &&
  811. defined($hash->{Helper}{RoundTrip}{Calc})) {
  812. delete($hash->{Helper}{RoundTrip}{Calc});
  813. my $delay = $recvtime - $hash->{Helper}{AckPending}{$hash->{DEVCNT}}->{time};
  814. $hash->{Helper}{RoundTrip}{Delay} = $delay if ($delay < 0.2);
  815. Log3($hash, HMUARTLGW_getVerbLvl($hash, undef, undef, 5), "HMUARTLGW ${name} roundtrip delay: " . sprintf("%.4f", ${delay}));
  816. }
  817. if ($ack eq HMUARTLGW_ACK_INFO) {
  818. HMUARTLGW_updateMsgLoad($hash, hex(substr($msg, 4)));
  819. }
  820. delete($hash->{Helper}{CreditFailed});
  821. $hash->{DevState} = HMUARTLGW_STATE_RUNNING;
  822. } elsif ($hash->{DevState} == HMUARTLGW_STATE_SET_UPDATE_MODE) {
  823. $hash->{DevState} = HMUARTLGW_STATE_RUNNING;
  824. }
  825. if ($hash->{DevState} == HMUARTLGW_STATE_RUNNING &&
  826. $oldState != HMUARTLGW_STATE_RUNNING &&
  827. (!$hash->{Helper}{OneParameterOnly})) {
  828. #Init sequence over, add known peers
  829. $hash->{AssignedPeerCnt} = 0;
  830. foreach my $peer (keys(%{$hash->{Peers}})) {
  831. if ($modules{CUL_HM}{defptr}{$peer} &&
  832. $modules{CUL_HM}{defptr}{$peer}{helper}{io}{newChn}) {
  833. my ($id, $flags, $kNo, $aesChannels) = split(/,/, $modules{CUL_HM}{defptr}{$peer}{helper}{io}{newChn});
  834. my $p = {
  835. id => substr($id, 1),
  836. operation => substr($id, 0, 1),
  837. flags => $flags,
  838. kNo => $kNo,
  839. aesChannels => $aesChannels,
  840. config => $modules{CUL_HM}{defptr}{$peer}{helper}{io}{newChn},
  841. };
  842. #enqueue for later
  843. if ($p->{operation} eq "+") {
  844. $hash->{Peers}{$peer} = "pending";
  845. push @{$hash->{Helper}{PeerQueue}}, $p;
  846. } else {
  847. delete($hash->{Peers}{$peer});
  848. }
  849. } else {
  850. delete($hash->{Peers}{$peer});
  851. }
  852. }
  853. #start credit checker
  854. RemoveInternalTimer("HMUARTLGW_CheckCredits:$name");
  855. InternalTimer(gettimeofday()+1, "HMUARTLGW_CheckCredits", "HMUARTLGW_CheckCredits:$name", 0);
  856. $hash->{Helper}{Initialized} = 1;
  857. HMUARTLGW_updateCondition($hash);
  858. }
  859. if ($hash->{DevState} == HMUARTLGW_STATE_UPDATE_PEER) {
  860. if ($ack eq HMUARTLGW_ACK_WITH_MULTIPART_DATA) {
  861. HMUARTLGW_ParsePeer($hash, $msg);
  862. } else {
  863. if ($hash->{Helper}{UpdatePeer}{operation} eq "+") {
  864. Log3($hash, 1, "HMUARTLGW ${name} Adding peer $hash->{Helper}{UpdatePeer}{id} failed! " .
  865. "You have probably forced an unknown aesKey for this device.");
  866. } else {
  867. Log3($hash, HMUARTLGW_getVerbLvl($hash, $hash->{Helper}{UpdatePeer}{id}, $hash->{Helper}{UpdatePeer}{id}, 4),
  868. "HMUARTLGW ${name} Removing peer $hash->{Helper}{UpdatePeer}{id} failed!");
  869. }
  870. $hash->{Helper}{UpdatePeer}{operation} = "";
  871. }
  872. if ($hash->{Helper}{UpdatePeer}{operation} eq "+") {
  873. $hash->{DevState} = HMUARTLGW_STATE_UPDATE_PEER_AES1;
  874. } else {
  875. delete($hash->{Helper}{UpdatePeer});
  876. $hash->{DevState} = HMUARTLGW_STATE_RUNNING;
  877. }
  878. } elsif ($hash->{DevState} == HMUARTLGW_STATE_UPDATE_PEER_AES1) {
  879. $hash->{DevState} = HMUARTLGW_STATE_UPDATE_PEER_AES2;
  880. } elsif ($hash->{DevState} == HMUARTLGW_STATE_UPDATE_PEER_AES2) {
  881. if ($hash->{Helper}{UpdatePeer}->{operation} eq "+") {
  882. $hash->{DevState} = HMUARTLGW_STATE_UPDATE_PEER_CFG;
  883. } else {
  884. delete($hash->{Helper}{UpdatePeer});
  885. $hash->{DevState} = HMUARTLGW_STATE_RUNNING;
  886. }
  887. } elsif ($hash->{DevState} == HMUARTLGW_STATE_UPDATE_PEER_CFG) {
  888. if ($ack eq HMUARTLGW_ACK_WITH_MULTIPART_DATA) {
  889. HMUARTLGW_ParsePeer($hash, $msg);
  890. }
  891. delete($hash->{Helper}{UpdatePeer});
  892. $hash->{DevState} = HMUARTLGW_STATE_RUNNING;
  893. }
  894. #Don't continue in state-machine if only one parameter should be
  895. #set/queried, SET_HMID is special, as we have to query it again
  896. #to update readings. SET_CURRENT_KEY is always followed by
  897. #SET_PREVIOUS_KEY and SET_TEMP_KEY.
  898. if ($hash->{Helper}{OneParameterOnly} &&
  899. $oldState != $hash->{DevState} &&
  900. $oldState != HMUARTLGW_STATE_SET_HMID &&
  901. $oldState != HMUARTLGW_STATE_SET_CURRENT_KEY &&
  902. $oldState != HMUARTLGW_STATE_SET_PREVIOUS_KEY) {
  903. $hash->{DevState} = HMUARTLGW_STATE_RUNNING;
  904. delete($hash->{Helper}{OneParameterOnly});
  905. }
  906. if ($hash->{DevState} != HMUARTLGW_STATE_RUNNING) {
  907. HMUARTLGW_GetSetParameterReq($hash);
  908. } else {
  909. HMUARTLGW_UpdateQueuedPeer($hash);
  910. HMUARTLGW_SendPendingCmd($hash);
  911. }
  912. }
  913. sub HMUARTLGW_Parse($$$$)
  914. {
  915. my ($hash, $msg, $dst, $recvtime) = @_;
  916. my $name = $hash->{NAME};
  917. my $recv;
  918. my $CULinfo = '';
  919. $hash->{RAWMSG} = $msg;
  920. Log3($hash, HMUARTLGW_getVerbLvl($hash, undef, undef, 5),
  921. "HMUARTLGW ${name} recv: ".sprintf("%02X", $dst)." ${msg}, state ".$hash->{DevState})
  922. if ($dst == HMUARTLGW_DST_OS || $dst == HMUARTLGW_DST_DUAL ||
  923. $dst == HMUARTLGW_DST_DUAL_ERR || ($msg !~ m/^05/ && $msg !~ m/^040[3C]/));
  924. #Minimally handle DualCopro-Firmware
  925. if ($dst == HMUARTLGW_DST_DUAL) {
  926. if (($msg =~ m/^00(.*)$/ || $msg =~ m/^0501(.*)$/) &&
  927. $hash->{DevState} <= HMUARTLGW_STATE_ENTER_APP) {
  928. if (pack("H*", $1) eq "DualCoPro_App") {
  929. $hash->{DevState} = HMUARTLGW_STATE_UNSUPPORTED_FW;
  930. readingsSingleUpdate($hash, "D-firmware", "unsupported", 1);
  931. HMUARTLGW_updateCondition($hash);
  932. RemoveInternalTimer($hash);
  933. Log3($hash, 0, "HMUARTLGW ${name} is running unsupported firmware, please install a supported version");
  934. }
  935. }
  936. return;
  937. }
  938. #Re-send commands for DualCopro Firmware
  939. if ($dst == HMUARTLGW_DST_DUAL_ERR) {
  940. if ($hash->{DevState} == HMUARTLGW_STATE_QUERY_APP) {
  941. Log3($hash, HMUARTLGW_getVerbLvl($hash, undef, undef, 4),
  942. "HMUARTLGW ${name} Re-sending app-query for unsupported firmware");
  943. HMUARTLGW_send($hash, HMUARTLGW_DUAL_GET_APP, HMUARTLGW_DST_DUAL);
  944. } elsif (defined($hash->{Helper}{AckPending}{$hash->{DEVCNT}}) &&
  945. $hash->{Helper}{AckPending}{$hash->{DEVCNT}}->{dst} == HMUARTLGW_DST_OS &&
  946. $hash->{Helper}{AckPending}{$hash->{DEVCNT}}->{cmd} eq HMUARTLGW_OS_CHANGE_APP) {
  947. Log3($hash, HMUARTLGW_getVerbLvl($hash, undef, undef, 4),
  948. "HMUARTLGW ${name} Re-sending switch to bootloader for unsupported firmare");
  949. HMUARTLGW_send($hash, HMUARTLGW_DUAL_CHANGE_APP, HMUARTLGW_DST_DUAL);
  950. }
  951. return;
  952. }
  953. if ($msg =~ m/^04/ &&
  954. $hash->{CNT} != $hash->{DEVCNT}) {
  955. if (defined($hash->{Helper}{AckPending}{$hash->{DEVCNT}})) {
  956. Log3($hash, HMUARTLGW_getVerbLvl($hash, undef, undef, 5),
  957. "HMUARTLGW ${name} got delayed ACK for request " .
  958. $hash->{DEVCNT}.": ".$hash->{Helper}{AckPending}{$hash->{DEVCNT}}->{dst} .
  959. " " . $hash->{Helper}{AckPending}{$hash->{DEVCNT}}->{cmd} .
  960. sprintf(" (%.3f", (gettimeofday() - $hash->{Helper}{AckPending}{$hash->{DEVCNT}}->{time})) .
  961. "s late)");
  962. delete($hash->{Helper}{AckPending}{$hash->{DEVCNT}});
  963. return;
  964. }
  965. #Firmware sometimes send additional ACK when receiving the
  966. #next frame from a device after a command, even if it has
  967. #already ACKed the command.
  968. Log3($hash, HMUARTLGW_getVerbLvl($hash, undef, undef, 5),
  969. "HMUARTLGW ${name} Ack with invalid/old counter received, dropping. We: $hash->{CNT}, device: $hash->{DEVCNT}, " .
  970. "state: $hash->{DevState}, msg: ${dst} ${msg}");
  971. return;
  972. }
  973. if ($msg =~ m/^04/ &&
  974. $hash->{DevState} >= HMUARTLGW_STATE_GETSET_PARAMETERS &&
  975. $hash->{DevState} < HMUARTLGW_STATE_RUNNING) {
  976. HMUARTLGW_GetSetParameters($hash, $msg, $recvtime);
  977. return;
  978. }
  979. if (defined($hash->{Helper}{RoundTrip}{Calc})) {
  980. #We have received another message while calculating delay.
  981. #This will skew the calculation, so don't do it now
  982. delete($hash->{Helper}{RoundTrip}{Calc});
  983. }
  984. if ($dst == HMUARTLGW_DST_OS) {
  985. if ($msg =~ m/^00(..)/) {
  986. my $running = pack("H*", substr($msg, 2));
  987. if ($hash->{DevState} <= HMUARTLGW_STATE_ENTER_APP) {
  988. Log3($hash, HMUARTLGW_getVerbLvl($hash, undef, undef, 4), "HMUARTLGW ${name} currently running ${running}");
  989. if ($running eq "Co_CPU_App") {
  990. $hash->{DevState} = HMUARTLGW_STATE_GETSET_PARAMETERS;
  991. RemoveInternalTimer($hash);
  992. InternalTimer(gettimeofday()+1, "HMUARTLGW_GetSetParameters", $hash, 0);
  993. } else {
  994. if ($hash->{DevState} == HMUARTLGW_STATE_QUERY_APP) {
  995. $hash->{DevState} = HMUARTLGW_STATE_ENTER_APP;
  996. HMUARTLGW_send($hash, HMUARTLGW_OS_CHANGE_APP, HMUARTLGW_DST_OS);
  997. RemoveInternalTimer($hash);
  998. InternalTimer(gettimeofday()+HMUARTLGW_CMD_TIMEOUT, "HMUARTLGW_CheckCmdResp", $hash, 0);
  999. } else {
  1000. Log3($hash, 1, "HMUARTLGW ${name} failed to enter App!");
  1001. }
  1002. }
  1003. } elsif ($hash->{DevState} > HMUARTLGW_STATE_ENTER_APP) {
  1004. Log3($hash, 1, "HMUARTLGW ${name} unexpected info about ${running} received (module crashed?), reopening")
  1005. if (!defined($hash->{FirmwareFile}));
  1006. HMUARTLGW_Reopen($hash);
  1007. return;
  1008. }
  1009. } elsif ($msg =~ m/^04(..)/) {
  1010. my $ack = $1;
  1011. if ($hash->{DevState} == HMUARTLGW_STATE_UPDATE_COPRO) {
  1012. HMUARTLGW_updateCoPro($hash, $msg);
  1013. return;
  1014. }
  1015. if ($ack eq HMUARTLGW_ACK_INFO && $hash->{DevState} == HMUARTLGW_STATE_QUERY_APP) {
  1016. my $running = pack("H*", substr($msg, 4));
  1017. Log3($hash, HMUARTLGW_getVerbLvl($hash, undef, undef, 4), "HMUARTLGW ${name} currently running ${running}");
  1018. if ($running eq "Co_CPU_App") {
  1019. #Reset module
  1020. HMUARTLGW_send($hash, HMUARTLGW_OS_CHANGE_APP, HMUARTLGW_DST_OS);
  1021. RemoveInternalTimer($hash);
  1022. InternalTimer(gettimeofday()+HMUARTLGW_CMD_TIMEOUT, "HMUARTLGW_CheckCmdResp", $hash, 0);
  1023. } else {
  1024. if (defined($hash->{FirmwareFile}) && $hash->{FirmwareFile} ne "") {
  1025. Log3($hash, 1, "HMUARTLGW ${name} starting firmware upgrade");
  1026. $hash->{FirmwareBlock} = 0;
  1027. $hash->{DevState} = HMUARTLGW_STATE_UPDATE_COPRO;
  1028. HMUARTLGW_updateCondition($hash);
  1029. HMUARTLGW_updateCoPro($hash, $msg);
  1030. return;
  1031. }
  1032. $hash->{DevState} = HMUARTLGW_STATE_ENTER_APP;
  1033. HMUARTLGW_send($hash, HMUARTLGW_OS_CHANGE_APP, HMUARTLGW_DST_OS);
  1034. RemoveInternalTimer($hash);
  1035. InternalTimer(gettimeofday()+HMUARTLGW_CMD_TIMEOUT, "HMUARTLGW_CheckCmdResp", $hash, 0);
  1036. }
  1037. } elsif ($ack eq HMUARTLGW_ACK_NACK && $hash->{DevState} == HMUARTLGW_STATE_ENTER_APP) {
  1038. Log3($hash, 1, "HMUARTLGW ${name} application switch failed, application-firmware probably corrupted!");
  1039. HMUARTLGW_Reopen($hash);
  1040. return;
  1041. }
  1042. } elsif ($msg =~ m/^05(..)$/) {
  1043. HMUARTLGW_updateMsgLoad($hash, hex($1));
  1044. }
  1045. } elsif ($dst == HMUARTLGW_DST_APP) {
  1046. if ($msg =~ m/^04(..)(.*)$/) {
  1047. my $ack = $1;
  1048. my $oldMsg;
  1049. if ($hash->{DevState} == HMUARTLGW_STATE_SEND ||
  1050. $hash->{DevState} == HMUARTLGW_STATE_SEND_NOACK) {
  1051. RemoveInternalTimer($hash);
  1052. $hash->{DevState} = HMUARTLGW_STATE_RUNNING;
  1053. $oldMsg = shift @{$hash->{Helper}{PendingCMD}};
  1054. }
  1055. if ($ack eq HMUARTLGW_ACK_WITH_RESPONSE ||
  1056. $ack eq HMUARTLGW_ACK_WITH_RESPONSE_AES_OK) {
  1057. $recv = $msg;
  1058. } elsif ($ack eq HMUARTLGW_ACK_WITH_RESPONSE_AES_KO) {
  1059. if ($2 =~ m/^FE/) { #challenge msg
  1060. $recv = $msg;
  1061. } elsif ($oldMsg) {
  1062. #Need to produce our own "failed" challenge
  1063. $recv = substr($msg, 0, 6) . "01" .
  1064. substr($oldMsg->{cmd}, 8, 2) .
  1065. "A002" .
  1066. substr($oldMsg->{cmd}, 20, 6) .
  1067. substr($oldMsg->{cmd}, 14, 6) .
  1068. "04000000000000" .
  1069. sprintf("%02X", hex(substr($msg, 4, 2))*2);
  1070. }
  1071. $CULinfo = "AESpending";
  1072. } elsif ($ack eq HMUARTLGW_ACK_EINPROGRESS && $oldMsg) {
  1073. Log3($hash, HMUARTLGW_getVerbLvl($hash, undef, undef, 5),
  1074. "HMUARTLGW ${name} IO currently busy, trying again in a bit");
  1075. if ($hash->{DevState} == HMUARTLGW_STATE_RUNNING) {
  1076. $oldMsg->{RetryStart} = gettimeofday() if (!defined($oldMsg->{RetryStart}));
  1077. RemoveInternalTimer($hash);
  1078. unshift @{$hash->{Helper}{PendingCMD}}, $oldMsg;
  1079. $hash->{DevState} = HMUARTLGW_STATE_SEND_TIMED;
  1080. InternalTimer(gettimeofday()+(HMUARTLGW_BUSY_RETRY_MS / 1000), "HMUARTLGW_SendPendingTimer", $hash, 0);
  1081. }
  1082. return;
  1083. } elsif ($ack eq HMUARTLGW_ACK_ENOCREDITS) {
  1084. Log3($hash, 1, "HMUARTLGW ${name} IO in overload!");
  1085. $hash->{XmitOpen} = 0;
  1086. HMUARTLGW_updateCondition($hash);
  1087. } elsif ($ack eq HMUARTLGW_ACK_ECSMACA && $oldMsg) {
  1088. Log3($hash, HMUARTLGW_getVerbLvl($hash, undef, undef, 5),
  1089. "HMUARTLGW ${name} can't send due to CSMA/CA, trying again in a bit");
  1090. if ($hash->{DevState} == HMUARTLGW_STATE_RUNNING) {
  1091. $oldMsg->{RetryStart} = gettimeofday() if (!defined($oldMsg->{RetryStart}));
  1092. RemoveInternalTimer($hash);
  1093. unshift @{$hash->{Helper}{PendingCMD}}, $oldMsg;
  1094. $hash->{DevState} = HMUARTLGW_STATE_SEND_TIMED;
  1095. InternalTimer(gettimeofday()+(HMUARTLGW_CSMACA_RETRY_MS / 1000), "HMUARTLGW_SendPendingTimer", $hash, 0);
  1096. }
  1097. return;
  1098. } elsif ($ack eq HMUARTLGW_ACK_EUNKNOWN && $oldMsg) {
  1099. Log3($hash, HMUARTLGW_getVerbLvl($hash, undef, undef, 5),
  1100. "HMUARTLGW ${name} can't send due to unknown problem (no response?)");
  1101. } else {
  1102. Log3($hash, HMUARTLGW_getVerbLvl($hash, undef, undef, 5),
  1103. "HMUARTLGW ${name} Ack: ${ack} ".(($2)?$2:""));
  1104. $recv = $msg;
  1105. }
  1106. } elsif ($msg =~ m/^(05.*)$/) {
  1107. $recv = $1;
  1108. }
  1109. if ($recv && $recv =~ m/^(..)(..)(..)(..)(..)(..)(..)(......)(......)(.*)$/) {
  1110. my ($type, $status, $info, $rssi, $mNr, $flags, $cmd, $src, $dst, $payload) = ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10);
  1111. Log3($hash, HMUARTLGW_getVerbLvl($hash, $src, $dst, 5),
  1112. "HMUARTLGW ${name} recv: 01 ${type} ${status} ${info} ${rssi} msg: ${mNr} ${flags} ${cmd} ${src} ${dst} ${payload}");
  1113. return if (!$hash->{Helper}{Initialized});
  1114. $rssi = 0 - hex($rssi);
  1115. my %addvals = (RAWMSG => $msg);
  1116. if ($rssi < -1) {
  1117. $addvals{RSSI} = $rssi;
  1118. $hash->{RSSI} = $rssi;
  1119. } else {
  1120. $rssi = "";
  1121. }
  1122. my $dmsg;
  1123. my $m = $mNr . $flags . $cmd . $src . $dst . $payload;
  1124. if ($type eq HMUARTLGW_APP_ACK && $status eq HMUARTLGW_ACK_WITH_RESPONSE_AES_OK) {
  1125. #Fake AES challenge for CUL_HM
  1126. my $kNo = sprintf("%02X", (hex($info) * 2));
  1127. my $c = "${mNr}A002${src}${dst}04000000000000${kNo}";
  1128. $dmsg = sprintf("A%02X%s:AESpending:${rssi}:${name}", length($c)/2, uc($c));
  1129. $CULinfo = "AESCom-ok";
  1130. } elsif ($type eq HMUARTLGW_APP_RECV && ($status eq HMUARTLGW_RECV_RESP_WITH_AES_OK ||
  1131. $status eq HMUARTLGW_RECV_TRIG_WITH_AES_OK)) {
  1132. #Fake AES response for CUL_HM
  1133. $dmsg = sprintf("A%02X%s:AESpending:${rssi}:${name}", length($m)/2, uc($m));
  1134. $CULinfo = "AESCom-ok";
  1135. } elsif ($type eq HMUARTLGW_APP_RECV && $status eq HMUARTLGW_RECV_RESP_WITH_AES_KO) {
  1136. #Fake AES response for CUL_HM
  1137. $dmsg = sprintf("A%02X%s:AESpending:${rssi}:${name}", length($m)/2, uc($m));
  1138. $CULinfo = "AESCom-fail";
  1139. }
  1140. if ($dmsg) {
  1141. Log3($hash, 5, "HMUARTLGW ${name} Dispatch: ${dmsg}");
  1142. Dispatch($hash, $dmsg, \%addvals);
  1143. }
  1144. $dmsg = sprintf("A%02X%s:${CULinfo}:${rssi}:${name}", length($m)/2, uc($m));
  1145. Log3($hash, 5, "HMUARTLGW ${name} Dispatch: ${dmsg}");
  1146. my $wait = 0;
  1147. if (!(hex($flags) & (1 << 5))) {
  1148. #!BIDI
  1149. $wait = 0.100;
  1150. } else {
  1151. $wait = 0.300;
  1152. }
  1153. $wait -= $hash->{Helper}{RoundTrip}{Delay} if (defined($hash->{Helper}{RoundTrip}{Delay}));
  1154. $modules{CUL_HM}{defptr}{$src}{helper}{io}{nextSend} = $recvtime + $wait
  1155. if ($modules{CUL_HM}{defptr}{$src} && $wait > 0);
  1156. Dispatch($hash, $dmsg, \%addvals);
  1157. }
  1158. }
  1159. if ($hash->{DevState} == HMUARTLGW_STATE_RUNNING) {
  1160. HMUARTLGW_UpdateQueuedPeer($hash);
  1161. HMUARTLGW_SendPendingCmd($hash);
  1162. }
  1163. return;
  1164. }
  1165. sub HMUARTLGW_Read($)
  1166. {
  1167. my ($hash) = @_;
  1168. my $name = $hash->{NAME};
  1169. my $recvtime = gettimeofday();
  1170. my $buf = DevIo_SimpleRead($hash);
  1171. return "" if (!defined($buf));
  1172. $buf = HMUARTLGW_decrypt($hash, $buf) if ($hash->{'.crypto'});
  1173. Log3($hash, 5, "HMUARTLGW ${name} read raw (".length($buf)."): ".unpack("H*", $buf));
  1174. my $p = pack("H*", $hash->{PARTIAL}) . $buf;
  1175. $hash->{PARTIAL} .= unpack("H*", $buf);
  1176. return HMUARTLGW_LGW_Init($hash) if ($hash->{LGW_Init});
  1177. return HMUARTLGW_LGW_HandleKeepAlive($hash) if ($hash->{DevType} eq "LGW-KeepAlive");
  1178. #need at least one frame delimiter
  1179. return if (!($p =~ m/\xfd/));
  1180. #garbage in the beginning?
  1181. if (!($p =~ m/^\xfd/)) {
  1182. $p = substr($p, index($p, chr(0xfd)));
  1183. }
  1184. my $unprocessed;
  1185. while (defined($p) && $p =~ m/^\xfd/) {
  1186. $unprocessed = $p;
  1187. (undef, my $frame, $p) = split(/\xfd/, $unprocessed, 3);
  1188. $p = chr(0xfd) . $p if ($p);
  1189. my $unescaped = '';
  1190. my $unescape_next = 0;
  1191. foreach my $byte (split(//, $frame)) {
  1192. if (ord($byte) == 0xfc) {
  1193. $unescape_next = 1;
  1194. next;
  1195. }
  1196. if ($unescape_next) {
  1197. $byte = chr(ord($byte)|0x80);
  1198. $unescape_next = 0;
  1199. }
  1200. $unescaped .= $byte;
  1201. }
  1202. next if (length($unescaped) < 6); #len len dst cnt crc crc
  1203. (my $len) = unpack("n", substr($unescaped, 0, 2));
  1204. if (length($unescaped) > $len + 4) {
  1205. Log3($hash, 1, "HMUARTLGW ${name} frame with wrong length received: ".length($unescaped).", should: ".($len + 4).": FD".uc(unpack("H*", $unescaped)));
  1206. next;
  1207. }
  1208. next if (length($unescaped) < $len + 4); #short read
  1209. my $crc = HMUARTLGW_crc16(chr(0xfd).$unescaped);
  1210. if ($crc != 0x0000 &&
  1211. $hash->{DevState} != HMUARTLGW_STATE_RUNNING &&
  1212. defined($hash->{Helper}{LastSendLen})) {
  1213. #When writing to the device while it prepares to write a frame to
  1214. #the host, the device seems to initialize the crc with 0x827f or
  1215. #0x8281 plus the length of the frame being received (firmware bug).
  1216. foreach my $slen (reverse(@{$hash->{Helper}{LastSendLen}})) {
  1217. $crc = HMUARTLGW_crc16(chr(0xfd).$unescaped, 0x827f + $slen);
  1218. Log3($hash, 5, "HMUARTLGW ${name} invalid checksum received, recalculated with slen ${slen}: ${crc}");
  1219. last if ($crc == 0x0000);
  1220. $crc = HMUARTLGW_crc16(chr(0xfd).$unescaped, 0x8281 + $slen);
  1221. Log3($hash, 5, "HMUARTLGW ${name} invalid checksum received, recalculated with slen ${slen}: ${crc}");
  1222. last if ($crc == 0x0000);
  1223. }
  1224. }
  1225. if ($crc != 0x0000) {
  1226. Log3($hash, 1, "HMUARTLGW ${name} invalid checksum received, dropping frame (FD".uc(unpack("H*", $unescaped)).")!");
  1227. undef($unprocessed);
  1228. next;
  1229. }
  1230. Log3($hash, 5, "HMUARTLGW ${name} read (".length($unescaped)."): fd".unpack("H*", $unescaped)." crc OK");
  1231. my $dst = ord(substr($unescaped, 2, 1));
  1232. $hash->{DEVCNT} = ord(substr($unescaped, 3, 1));
  1233. my $msg = uc(unpack("H*", substr($unescaped, 4, -2)));
  1234. HMUARTLGW_Parse($hash, $msg, $dst, $recvtime);
  1235. delete($hash->{Helper}{AckPending}{$hash->{DEVCNT}})
  1236. if (($msg =~ m/^04/) &&
  1237. defined($hash->{Helper}{AckPending}) &&
  1238. defined($hash->{Helper}{AckPending}{$hash->{DEVCNT}}));
  1239. undef($unprocessed);
  1240. }
  1241. if (defined($unprocessed)) {
  1242. $hash->{PARTIAL} = unpack("H*", $unprocessed);
  1243. } else {
  1244. $hash->{PARTIAL} = '';
  1245. }
  1246. }
  1247. sub HMUARTLGW_Write($$$)
  1248. {
  1249. my ($hash, $fn, $msg) = @_;
  1250. my $name = $hash->{NAME};
  1251. Log3($hash, 5, "HMUARTLGW ${name} HMUARTLGW_Write: ${msg}");
  1252. if($msg =~ m/init:(......)/) {
  1253. my $dst = $1;
  1254. if ($modules{CUL_HM}{defptr}{$dst} &&
  1255. $modules{CUL_HM}{defptr}{$dst}{helper}{io}{newChn}) {
  1256. my ($id, $flags, $kNo, $aesChannels) = split(/,/, $modules{CUL_HM}{defptr}{$dst}{helper}{io}{newChn});
  1257. my $peer = {
  1258. id => substr($id, 1),
  1259. operation => substr($id, 0, 1),
  1260. flags => $flags,
  1261. kNo => $kNo,
  1262. aesChannels => $aesChannels,
  1263. config => $modules{CUL_HM}{defptr}{$dst}{helper}{io}{newChn},
  1264. };
  1265. $hash->{Peers}{$peer->{id}} = "pending";
  1266. HMUARTLGW_UpdatePeer($hash, $peer);
  1267. }
  1268. return;
  1269. } elsif ($msg =~ m/remove:(......)/) {
  1270. my $peer = {
  1271. id => $1,
  1272. operation => "-",
  1273. };
  1274. delete($hash->{Peers}{$peer->{id}});
  1275. HMUARTLGW_UpdatePeer($hash, $peer);
  1276. } elsif ($msg =~ m/^([+-])(.*)$/) {
  1277. my ($id, $flags, $kNo, $aesChannels) = split(/,/, $msg);
  1278. my $peer = {
  1279. id => substr($id, 1),
  1280. operation => substr($id, 0, 1),
  1281. flags => $flags,
  1282. kNo => $kNo,
  1283. aesChannels => $aesChannels,
  1284. config => $msg,
  1285. };
  1286. if ($peer->{operation} eq "+") {
  1287. $hash->{Peers}{$peer->{id}} = "pending";
  1288. } else {
  1289. delete($hash->{Peers}{$peer->{id}});
  1290. }
  1291. HMUARTLGW_UpdatePeer($hash, $peer);
  1292. return;
  1293. } elsif ($msg =~ m/^writeAesKey:(.*)$/) {
  1294. HMUARTLGW_writeAesKey($1);
  1295. return;
  1296. } elsif ($msg =~ /^G(..)$/) {
  1297. my $speed = hex($1);
  1298. if ($speed == 100) {
  1299. HMUARTLGW_SendCmd($hash, "UpdateMode");
  1300. } else {
  1301. HMUARTLGW_SendCmd($hash, "NormalMode");
  1302. }
  1303. } elsif (length($msg) > 21) {
  1304. my ($flags, $mtype,$src,$dst) = (substr($msg, 6, 2),
  1305. substr($msg, 8, 2),
  1306. substr($msg, 10, 6),
  1307. substr($msg, 16, 6));
  1308. if (!defined($hash->{owner}) ||
  1309. !defined($hash->{Helper}{FW})) {
  1310. Log3($hash, 1, "HMUARTLGW ${name}: Device not initialized (state: $hash->{DevState}, " .
  1311. ReadingsVal($name, "cond", "").") but asked to send data. Dropping: ${msg}");
  1312. return;
  1313. }
  1314. if ($mtype eq "02" && $src eq $hash->{owner} && length($msg) == 24 &&
  1315. defined($hash->{Peers}{$dst})) {
  1316. # Acks are generally send by HMUARTLGW autonomously
  1317. # Special
  1318. Log3($hash, 5, "HMUARTLGW ${name}: Skip ACK");
  1319. return;
  1320. } elsif ($mtype eq "02" && $src ne $hash->{owner} &&
  1321. defined($hash->{Peers}{$dst})) {
  1322. Log3($hash, 0, "HMUARTLGW ${name}: Can't send ACK not originating from my hmId (firmware bug), please use a VCCU virtual device!");
  1323. return;
  1324. } elsif ($flags eq "A1" && $mtype eq "12") {
  1325. Log3($hash, 5, "HMUARTLGW ${name}: FIXME: filter out A112 message (it's automatically generated by the device)");
  1326. #return;
  1327. }
  1328. my $qLen = AttrVal($name, "qLen", 60);
  1329. #Queue full?
  1330. if ($hash->{Helper}{PendingCMD} &&
  1331. scalar(@{$hash->{Helper}{PendingCMD}}) >= $qLen) {
  1332. if ($hash->{XmitOpen} == 2) {
  1333. Log3($hash, 1, "HMUARTLGW ${name}: queue is full, dropping packet");
  1334. return;
  1335. } elsif ($hash->{XmitOpen} == 1) {
  1336. $hash->{XmitOpen} = 2;
  1337. }
  1338. }
  1339. if (!$hash->{Peers}{$dst} && $dst ne "000000"){
  1340. #add id and enqueue command
  1341. my $peer = {
  1342. id => $dst,
  1343. operation => "+",
  1344. flags => "00",
  1345. kNo => "00",
  1346. config => "+${dst}",
  1347. };
  1348. if ($modules{CUL_HM}{defptr}{$dst} &&
  1349. $modules{CUL_HM}{defptr}{$dst}{helper}{io}{newChn}) {
  1350. my (undef, $flags, $kNo, $aesChannels) = split(/,/, $modules{CUL_HM}{defptr}{$dst}{helper}{io}{newChn});
  1351. $peer->{flags} = $flags;
  1352. $peer->{kNo} = $kNo;
  1353. $peer->{aesChannels} = $aesChannels;
  1354. $peer->{config} = $modules{CUL_HM}{defptr}{$dst}{helper}{io}{newChn};
  1355. }
  1356. $hash->{Peers}{$dst} = "pending";
  1357. HMUARTLGW_UpdatePeer($hash, $peer);
  1358. }
  1359. my $cmd = HMUARTLGW_APP_SEND . "0000";
  1360. if ($hash->{Helper}{FW} > 0x010006) { #TODO: Find real version which adds this
  1361. $cmd .= ((hex(substr($msg, 6, 2)) & 0x10) ? "01" : "00");
  1362. }
  1363. $cmd .= substr($msg, 4);
  1364. HMUARTLGW_SendCmd($hash, $cmd);
  1365. HMUARTLGW_SendCmd($hash, "Credits") if ((++$hash->{Helper}{SendCnt} % 10) == 0);
  1366. # Check queue again
  1367. if ($hash->{Helper}{PendingCMD} &&
  1368. scalar(@{$hash->{Helper}{PendingCMD}}) >= $qLen) {
  1369. $hash->{XmitOpen} = 2 if ($hash->{XmitOpen} == 1);
  1370. }
  1371. } else {
  1372. Log3($hash, 1, "HMUARTLGW ${name} write:${fn} ${msg}");
  1373. }
  1374. return;
  1375. }
  1376. sub HMUARTLGW_StartInit($)
  1377. {
  1378. my ($hash) = @_;
  1379. my $name = $hash->{NAME};
  1380. if ($hash->{LGW_Init}) {
  1381. if ($hash->{LGW_Init} >= 10) {
  1382. Log3($hash, 1, "HMUARTLGW ${name} LGW init did not complete after 10s".($hash->{'.crypto'}?", probably wrong password":""));
  1383. HMUARTLGW_Reopen($hash);
  1384. return;
  1385. }
  1386. $hash->{LGW_Init}++;
  1387. RemoveInternalTimer($hash);
  1388. InternalTimer(gettimeofday()+1, "HMUARTLGW_StartInit", $hash, 0);
  1389. return;
  1390. }
  1391. Log3($hash, 4, "HMUARTLGW ${name} StartInit");
  1392. RemoveInternalTimer($hash);
  1393. InternalTimer(gettimeofday()+HMUARTLGW_CMD_TIMEOUT, "HMUARTLGW_CheckCmdResp", $hash, 0);
  1394. if ($hash->{DevType} eq "LGW-KeepAlive") {
  1395. $hash->{DevState} = HMUARTLGW_STATE_KEEPALIVE_INIT;
  1396. HMUARTLGW_sendAscii($hash, "L%02x,02,00ff,00\r\n");
  1397. return;
  1398. }
  1399. $hash->{DevState} = HMUARTLGW_STATE_QUERY_APP;
  1400. HMUARTLGW_send($hash, HMUARTLGW_OS_GET_APP, HMUARTLGW_DST_OS);
  1401. HMUARTLGW_updateCondition($hash);
  1402. return;
  1403. }
  1404. sub HMUARTLGW_CheckCmdResp($)
  1405. {
  1406. my ($hash) = @_;
  1407. my $name = $hash->{NAME};
  1408. RemoveInternalTimer($hash);
  1409. #The data we wait for might have already been received but never
  1410. #read from the FD. Do a last check now and process new data.
  1411. if (defined($hash->{FD})) {
  1412. my $rin = '';
  1413. vec($rin, $hash->{FD}, 1) = 1;
  1414. my $n = select($rin, undef, undef, 0);
  1415. if ($n > 0) {
  1416. Log3($hash, HMUARTLGW_getVerbLvl($hash, undef, undef, 5),
  1417. "HMUARTLGW ${name} HMUARTLGW_CheckCmdResp: FD is readable, this might be the data we are looking for!");
  1418. #We will be back very soon!
  1419. InternalTimer(gettimeofday()+0, "HMUARTLGW_CheckCmdResp", $hash, 0);
  1420. HMUARTLGW_Read($hash);
  1421. return;
  1422. }
  1423. }
  1424. if ($hash->{DevState} == HMUARTLGW_STATE_SEND) {
  1425. $hash->{Helper}{PendingCMD}->[0]->{RetryStart} = gettimeofday()
  1426. if (!defined($hash->{Helper}{PendingCMD}->[0]->{RetryStart}));
  1427. $hash->{DevState} = HMUARTLGW_STATE_RUNNING;
  1428. return HMUARTLGW_SendPendingCmd($hash);
  1429. } elsif ($hash->{DevState} == HMUARTLGW_STATE_SEND_NOACK) {
  1430. shift(@{$hash->{Helper}{PendingCMD}});
  1431. $hash->{DevState} = HMUARTLGW_STATE_RUNNING;
  1432. #try next command
  1433. return HMUARTLGW_SendPendingCmd($hash);
  1434. } elsif ($hash->{DevState} == HMUARTLGW_STATE_GET_CREDITS &&
  1435. (!defined($hash->{Helper}{CreditFailed}) || ($hash->{Helper}{CreditFailed} < 3))) {
  1436. $hash->{Helper}{CreditFailed}++;
  1437. $hash->{DevState} = HMUARTLGW_STATE_RUNNING;
  1438. RemoveInternalTimer("HMUARTLGW_CheckCredits:$name");
  1439. InternalTimer(gettimeofday()+1, "HMUARTLGW_CheckCredits", "HMUARTLGW_CheckCredits:$name", 0);
  1440. } elsif ($hash->{DevState} != HMUARTLGW_STATE_RUNNING) {
  1441. if ((!defined($hash->{Helper}{AckPending}{$hash->{CNT}}{frame})) ||
  1442. (defined($hash->{Helper}{AckPending}{$hash->{CNT}}{resend}) &&
  1443. $hash->{Helper}{AckPending}{$hash->{CNT}}{resend} >= HMUARTLGW_CMD_RETRY_CNT)) {
  1444. Log3($hash, 1, "HMUARTLGW ${name} did not respond after all, reopening");
  1445. HMUARTLGW_Reopen($hash);
  1446. } else {
  1447. $hash->{Helper}{AckPending}{$hash->{CNT}}{resend}++;
  1448. Log3($hash, 1, "HMUARTLGW ${name} did not respond for the " .
  1449. $hash->{Helper}{AckPending}{$hash->{CNT}}{resend} .
  1450. ". time, resending");
  1451. HMUARTLGW_send_frame($hash, pack("H*", $hash->{Helper}{AckPending}{$hash->{CNT}}{frame}));
  1452. InternalTimer(gettimeofday()+HMUARTLGW_CMD_TIMEOUT, "HMUARTLGW_CheckCmdResp", $hash, 0);
  1453. }
  1454. }
  1455. return;
  1456. }
  1457. sub HMUARTLGW_Get($@)
  1458. {
  1459. my ( $hash, $name, $cmd, @args ) = @_;
  1460. my $ret = "";
  1461. return "Unknown argument ${cmd}, choose one of " if ($hash->{DevType} eq "LGW-KeepAlive");
  1462. if ($cmd eq "assignIDs") {
  1463. foreach my $peer (keys(%{$hash->{Peers}})) {
  1464. next if ($hash->{Peers}{$peer} !~ m/^\+/);
  1465. $ret .= "\n${peer} : " . CUL_HM_id2Name($peer);
  1466. }
  1467. $ret = "assignedIDs: ". ($ret =~ tr/\n//) . $ret;
  1468. } else {
  1469. $ret = "Unknown argument ${cmd}, choose one of " .
  1470. join(" ",map {"$_" . ($gets{$_} ? ":$gets{$_}" : "")} keys %gets);
  1471. }
  1472. return $ret;
  1473. }
  1474. sub HMUARTLGW_RemoveHMPair($)
  1475. {
  1476. my ($in) = shift;
  1477. my (undef,$name) = split(':',$in);
  1478. my $hash = $defs{$name};
  1479. RemoveInternalTimer("hmPairForSec:$name");
  1480. Log3($hash, 3, "HMUARTLGW ${name} left pairing-mode") if ($hash->{hmPair});
  1481. delete($hash->{hmPair});
  1482. delete($hash->{hmPairSerial});
  1483. }
  1484. sub HMUARTLGW_Set($@)
  1485. {
  1486. my ($hash, $name, $cmd, @a) = @_;
  1487. my $arg = join(" ", @a);
  1488. return "\"set\" needs at least one parameter" if (!$cmd);
  1489. return "Unknown argument ${cmd}, choose one of " if ($hash->{DevType} eq "LGW-KeepAlive");
  1490. if ($cmd eq "hmPairForSec") {
  1491. $arg = 60 if(!$arg || $arg !~ m/^\d+$/);
  1492. HMUARTLGW_RemoveHMPair("hmPairForSec:$name");
  1493. $hash->{hmPair} = 1;
  1494. InternalTimer(gettimeofday()+$arg, "HMUARTLGW_RemoveHMPair", "hmPairForSec:$name", 0);
  1495. Log3($hash, 3, "HMUARTLGW ${name} entered pairing-mode");
  1496. } elsif ($cmd eq "hmPairSerial") {
  1497. return "Usage: set $name hmPairSerial <10-character-serialnumber>"
  1498. if(!$arg || $arg !~ m/^.{10}$/);
  1499. my $id = InternalVal($hash->{NAME}, "owner", "123456");
  1500. $hash->{HM_CMDNR} = $hash->{HM_CMDNR} ? ($hash->{HM_CMDNR}+1)%256 : 1;
  1501. HMUARTLGW_Write($hash, undef, sprintf("As15%02X8401%s000000010A%s",
  1502. $hash->{HM_CMDNR}, $id, unpack('H*', $arg)));
  1503. HMUARTLGW_RemoveHMPair("hmPairForSec:$name");
  1504. $hash->{hmPair} = 1;
  1505. $hash->{hmPairSerial} = $arg;
  1506. InternalTimer(gettimeofday()+20, "HMUARTLGW_RemoveHMPair", "hmPairForSec:".$name, 0);
  1507. } elsif ($cmd eq "reopen") {
  1508. HMUARTLGW_Reopen($hash);
  1509. } elsif ($cmd eq "close") {
  1510. #switch to bootloader to stop the module from interfering
  1511. HMUARTLGW_send($hash, HMUARTLGW_OS_CHANGE_APP, HMUARTLGW_DST_OS)
  1512. if ($hash->{DevState} > HMUARTLGW_STATE_ENTER_APP);
  1513. HMUARTLGW_Undefine($hash, $name);
  1514. readingsSingleUpdate($hash, "state", "closed", 1);
  1515. $hash->{XmitOpen} = 0;
  1516. } elsif ($cmd eq "open") {
  1517. DevIo_OpenDev($hash, 0, "HMUARTLGW_DoInit", \&HMUARTLGW_Connect);
  1518. } elsif ($cmd eq "restart") {
  1519. HMUARTLGW_send($hash, HMUARTLGW_OS_CHANGE_APP, HMUARTLGW_DST_OS);
  1520. } elsif ($cmd eq "updateCoPro") {
  1521. return "Usage: set $name updateCoPro </path/to/firmware.eq3>"
  1522. if(!$arg);
  1523. my $block = HMUARTLGW_firmwareGetBlock($hash, $arg, 0);
  1524. return "${arg} is not a valid firmware file!"
  1525. if (!defined($block) || $block eq "");
  1526. $hash->{FirmwareFile} = $arg;
  1527. HMUARTLGW_send($hash, HMUARTLGW_OS_CHANGE_APP, HMUARTLGW_DST_OS);
  1528. } else {
  1529. return "Unknown argument ${cmd}, choose one of " .
  1530. join(" ",map {"$_" . ($sets{$_} ? ":$sets{$_}" : "")} keys %sets);
  1531. }
  1532. return undef;
  1533. }
  1534. sub HMUARTLGW_Attr(@)
  1535. {
  1536. my ($cmd, $name, $aName, $aVal) = @_;
  1537. my $hash = $defs{$name};
  1538. my $retVal;
  1539. Log3($hash, 5, "HMUARTLGW ${name} Attr ${cmd} ${aName} ".(($aVal)?$aVal:""));
  1540. return "Attribute ${cmd} not supported on keepAlive-subdevice" if ($hash->{DevType} eq "LGW-KeepAlive");
  1541. if ($aName eq "hmId") {
  1542. if ($cmd eq "set") {
  1543. my $owner_ccu = InternalVal($name, "owner_CCU", undef);
  1544. return "device owned by $owner_ccu" if ($owner_ccu);
  1545. return "wrong syntax: hmId must be 6-digit-hex-code (3 byte)"
  1546. if ($aVal !~ m/^[A-F0-9]{6}$/i);
  1547. $attr{$name}{$aName} = $aVal;
  1548. if ($init_done) {
  1549. HMUARTLGW_SendCmd($hash, "HMID");
  1550. }
  1551. }
  1552. } elsif ($aName eq "lgwPw") {
  1553. if ($init_done) {
  1554. if ($hash->{DevType} eq "LGW") {
  1555. HMUARTLGW_Reopen($hash);
  1556. }
  1557. }
  1558. } elsif ($aName =~ m/^hmKey(.?)$/) {
  1559. if ($cmd eq "set") {
  1560. my $kNo = 1;
  1561. $kNo = $1 if ($1);
  1562. my ($no,$val) = (sprintf("%02X",$kNo),$aVal);
  1563. if ($aVal =~ m/:/){#number given
  1564. ($no,$val) = split ":",$aVal;
  1565. return "illegal number:$no" if (hex($no) < 1 || hex($no) > 255 || length($no) != 2);
  1566. }
  1567. $attr{$name}{$aName} = "$no:".
  1568. (($val =~ m /^[0-9A-Fa-f]{32}$/ )
  1569. ? $val
  1570. : unpack('H*', Digest::MD5::md5($val)));
  1571. $retVal = "$aName set to $attr{$name}{$aName}"
  1572. if($aVal ne $attr{$name}{$aName});
  1573. } else {
  1574. delete $attr{$name}{$aName};
  1575. }
  1576. HMUARTLGW_writeAesKey($name) if ($init_done);
  1577. } elsif ($aName eq "dutyCycle") {
  1578. if ($cmd eq "set") {
  1579. return "wrong syntax: dutyCycle must be 1 or 0"
  1580. if ($aVal !~ m/^[01]$/);
  1581. $attr{$name}{$aName} = $aVal;
  1582. #$retVal = "Please make sure to be in compliance with local regulations when disabling dutyCycle!"
  1583. # if (!($aVal));
  1584. } else {
  1585. delete $attr{$name}{$aName};
  1586. }
  1587. if ($init_done) {
  1588. HMUARTLGW_SendCmd($hash, "DutyCycle");
  1589. }
  1590. } elsif ($aName eq "csmaCa") {
  1591. if ($cmd eq "set") {
  1592. return "wrong syntax: csmaCa must be 1 or 0"
  1593. if ($aVal !~ m/^[01]$/);
  1594. $attr{$name}{$aName} = $aVal;
  1595. } else {
  1596. delete $attr{$name}{$aName};
  1597. }
  1598. if ($init_done) {
  1599. HMUARTLGW_SendCmd($hash, "CSMACA");
  1600. }
  1601. } elsif ($aName eq "qLen") {
  1602. if ($cmd eq "set") {
  1603. return "wrong syntax: qLen must be between 1 and 200"
  1604. if ($aVal !~ m/^\d+$/ || $aVal < 1 || $aVal > 200);
  1605. $attr{$name}{$aName} = $aVal;
  1606. } else {
  1607. delete $attr{$name}{$aName};
  1608. }
  1609. } elsif ($aName eq "logIDs") {
  1610. if ($cmd eq "set") {
  1611. my @ids = split(/,/, $aVal);
  1612. $hash->{Helper}{Log}{IDs} = \@ids;
  1613. $hash->{Helper}{Log}{Resolve} = 1;
  1614. $attr{$name}{$aName} = $aVal;
  1615. } else {
  1616. delete $attr{$name}{$aName};
  1617. delete $hash->{Helper}{Log};
  1618. }
  1619. } elsif ($aName eq "verbose") {
  1620. if ($hash->{keepAlive}) {
  1621. if ($cmd eq "set") {
  1622. $attr{$hash->{keepAlive}->{NAME}}{$aName} = $aVal;
  1623. } else {
  1624. delete $attr{$hash->{keepAlive}->{NAME}}{$aName};
  1625. }
  1626. }
  1627. } elsif ($aName eq "dummy") {
  1628. if ($cmd eq "set") {
  1629. if (!defined($attr{$name}{$aName})) {
  1630. #switch to bootloader to stop the module from interfering
  1631. HMUARTLGW_send($hash, HMUARTLGW_OS_CHANGE_APP, HMUARTLGW_DST_OS)
  1632. if ($hash->{DevState} > HMUARTLGW_STATE_ENTER_APP);
  1633. HMUARTLGW_Undefine($hash, $name);
  1634. readingsSingleUpdate($hash, "state", "dummy", 1);
  1635. HMUARTLGW_updateCondition($hash);
  1636. $hash->{XmitOpen} = 0;
  1637. }
  1638. } else {
  1639. if (defined($attr{$name}{$aName})) {
  1640. delete $attr{$name}{$aName};
  1641. DevIo_OpenDev($hash, 0, "HMUARTLGW_DoInit", \&HMUARTLGW_Connect);
  1642. }
  1643. }
  1644. } elsif ($aName eq "loadEvents") {
  1645. if ($cmd eq "set") {
  1646. return "wrong syntax: loadEvents must be 1 or 0"
  1647. if ($aVal !~ m/^[01]$/);
  1648. $attr{$name}{$aName} = $aVal;
  1649. } else {
  1650. delete $attr{$name}{$aName};
  1651. }
  1652. }
  1653. return $retVal;
  1654. }
  1655. sub HMUARTLGW_getAesKeys($) {
  1656. my ($hash) = @_;
  1657. my $name = $hash->{NAME};
  1658. my @k;
  1659. my %keys = ();
  1660. my $vccu = InternalVal($name,"owner_CCU",$name);
  1661. $vccu = $name if(!AttrVal($vccu,"hmKey",""));
  1662. foreach my $i (1..3){
  1663. my ($kNo,$k) = split(":",AttrVal($vccu,"hmKey".($i== 1?"":$i),""));
  1664. if (defined($kNo) && defined($k)) {
  1665. $keys{$kNo} = $k;
  1666. }
  1667. }
  1668. my @kNos = reverse(sort(keys(%keys)));
  1669. foreach my $kNo (@kNos) {
  1670. Log3($hash, 4, "HMUARTLGW ${name} key: ".$keys{$kNo}.", idx: ".$kNo);
  1671. push @k, $keys{$kNo} . $kNo;
  1672. }
  1673. return @k;
  1674. }
  1675. sub HMUARTLGW_writeAesKey($) {
  1676. my ($name) = @_;
  1677. return if (!$name || !$defs{$name} || $defs{$name}{TYPE} ne "HMUARTLGW");
  1678. my $hash = $defs{$name};
  1679. HMUARTLGW_SendCmd($hash, "AESkeys");
  1680. HMUARTLGW_SendPendingCmd($hash);
  1681. }
  1682. sub HMUARTLGW_updateCondition($)
  1683. {
  1684. my ($hash) = @_;
  1685. my $name = $hash->{NAME};
  1686. my $cond = "disconnected";
  1687. my $loadLvl = "suspended";
  1688. my $oldLoad = ReadingsVal($name, "load", -1);
  1689. if (defined($hash->{msgLoadCurrent})) {
  1690. my $load = $hash->{msgLoadCurrent};
  1691. readingsSingleUpdate($hash, "load", $load, AttrVal($name, "loadEvents", 0));
  1692. $cond = "ok";
  1693. #FIXME: Dynamic levels
  1694. if ($load >= 100) {
  1695. $cond = "ERROR-Overload";
  1696. $loadLvl = "suspended";
  1697. } elsif ($oldLoad >= 100) {
  1698. $cond = "Overload-released";
  1699. $loadLvl = "high";
  1700. } elsif ($load >= 90) {
  1701. $cond = "Warning-HighLoad";
  1702. $loadLvl = "high";
  1703. } elsif ($load >= 40) {
  1704. #FIXME: batchLevel != 40 needs to be in {helper}{loadLvl}{bl}
  1705. $loadLvl = "batchLevel";
  1706. } else {
  1707. $loadLvl = "low";
  1708. }
  1709. }
  1710. if ((!defined($hash->{XmitOpen})) || $hash->{XmitOpen} == 0) {
  1711. $cond = "ERROR-Overload";
  1712. $loadLvl = "suspended";
  1713. }
  1714. if (!defined($hash->{Helper}{Initialized})) {
  1715. $cond = "init";
  1716. $loadLvl = "suspended";
  1717. }
  1718. if ($hash->{DevState} == HMUARTLGW_STATE_NONE) {
  1719. $cond = "disconnected";
  1720. $loadLvl = "suspended";
  1721. } elsif ($hash->{DevState} == HMUARTLGW_STATE_UPDATE_COPRO) {
  1722. $cond = "fwupdate";
  1723. $loadLvl = "suspended";
  1724. } elsif ($hash->{DevState} == HMUARTLGW_STATE_UNSUPPORTED_FW) {
  1725. $cond = "unsupported firmware";
  1726. $loadLvl = "suspended";
  1727. }
  1728. if ((defined($cond) && $cond ne ReadingsVal($name, "cond", "")) ||
  1729. (defined($loadLvl) && $loadLvl ne ReadingsVal($name, "loadLvl", ""))) {
  1730. readingsBeginUpdate($hash);
  1731. readingsBulkUpdate($hash, "cond", $cond)
  1732. if (defined($cond) && $cond ne ReadingsVal($name, "cond", ""));
  1733. readingsBulkUpdate($hash, "loadLvl", $loadLvl)
  1734. if (defined($loadLvl) && $loadLvl ne ReadingsVal($name, "loadLvl", ""));
  1735. readingsEndUpdate($hash, 1);
  1736. my $ccu = InternalVal($name,"owner_CCU","");
  1737. CUL_HM_UpdtCentralState($ccu) if ($ccu);
  1738. }
  1739. }
  1740. sub HMUARTLGW_updateMsgLoad($$) {
  1741. my ($hash, $load) = @_;
  1742. if ($hash->{XmitOpen} != 2) {
  1743. if ($load >= 199) {
  1744. $hash->{XmitOpen} = 0;
  1745. } else {
  1746. $hash->{XmitOpen} = 1;
  1747. }
  1748. }
  1749. my $adjustedLoad = int(($load + 1) / 2);
  1750. my $histSlice = 5 * 60;
  1751. my $histNo = 3600 / $histSlice;
  1752. if ((!defined($hash->{Helper}{loadLvl}{lastHistory})) ||
  1753. ($hash->{Helper}{loadLvl}{lastHistory} + $histSlice) <= gettimeofday()) {
  1754. my @abshist = ("-") x $histNo;
  1755. unshift @abshist, split("/", $hash->{msgLoadHistoryAbs}) if (defined($hash->{msgLoadHistoryAbs}));
  1756. unshift @abshist, $adjustedLoad;
  1757. my $last;
  1758. my @hist = ("-") x $histNo;
  1759. foreach my $l (reverse(@abshist)) {
  1760. next if ($l eq "-");
  1761. unshift @hist, $l - $last if (defined($last));
  1762. $last = $l;
  1763. }
  1764. $hash->{msgLoadHistory} = join("/", @hist[0..($histNo - 1)]);
  1765. $hash->{msgLoadHistoryAbs} = join("/", @abshist[0..($histNo)]);
  1766. if (!defined($hash->{Helper}{loadLvl}{lastHistory})) {
  1767. $hash->{Helper}{loadLvl}{lastHistory} = gettimeofday();
  1768. } else {
  1769. $hash->{Helper}{loadLvl}{lastHistory} += $histSlice;
  1770. }
  1771. }
  1772. if ((!defined($hash->{msgLoadCurrent})) ||
  1773. $hash->{msgLoadCurrent} != $adjustedLoad) {
  1774. $hash->{msgLoadCurrent} = $adjustedLoad;
  1775. HMUARTLGW_updateCondition($hash);
  1776. }
  1777. }
  1778. sub HMUARTLGW_send($$$;$)
  1779. {
  1780. my ($hash, $msg, $dst, $peer) = @_;
  1781. my $name = $hash->{NAME};
  1782. my $log;
  1783. my $v;
  1784. if ($dst == HMUARTLGW_DST_APP && uc($msg) =~ m/^(02)(..)(..)(.*)$/) {
  1785. $log = "01 ${1} ${2} ${3} ";
  1786. my $m = $4;
  1787. if ($hash->{Helper}{FW} > 0x010006) {
  1788. $log .= substr($m, 0, 2, '') . " ";
  1789. } else {
  1790. $log .= "XX ";
  1791. }
  1792. if ($m =~ m/^(..)(..)(..)(......)(......)(.*)$/) {
  1793. $log .= "msg: ${1} ${2} ${3} ${4} ${5} ${6}";
  1794. } else {
  1795. $log .= $m;
  1796. }
  1797. $v = HMUARTLGW_getVerbLvl($hash, $4, $5, 5);
  1798. } elsif ($dst == HMUARTLGW_DST_APP && uc($msg) =~ m/^(0[3BF]).*[^0].*(..)$/) {
  1799. #Key, do not log
  1800. $log = sprintf("%02X", $dst). " ${1}" . ("XX"x16) . $2;
  1801. $v = HMUARTLGW_getVerbLvl($hash, undef, undef, 5);
  1802. } else {
  1803. $log = sprintf("%02X", $dst). " ".uc($msg);
  1804. $v = HMUARTLGW_getVerbLvl($hash, $peer, $peer, 5);
  1805. }
  1806. Log3($hash, $v, "HMUARTLGW ${name} send: ${log}");
  1807. $hash->{CNT} = ($hash->{CNT} + 1) & 0xff;
  1808. my $frame = pack("CnCCH*", 0xfd,
  1809. (length($msg) / 2) + 2,
  1810. $dst,
  1811. $hash->{CNT},
  1812. $msg);
  1813. $frame .= pack("n", HMUARTLGW_crc16($frame));
  1814. my $sendtime = HMUARTLGW_send_frame($hash, $frame);
  1815. if (defined($hash->{Helper}{AckPending}{$hash->{CNT}})) {
  1816. Log3($hash, HMUARTLGW_getVerbLvl($hash, undef, undef, 5),
  1817. "HMUARTLGW ${name} never got an ACK for request ".
  1818. $hash->{CNT}.": ".$hash->{Helper}{AckPending}{$hash->{CNT}}->{dst} .
  1819. " " . $hash->{Helper}{AckPending}{$hash->{CNT}}->{cmd} .
  1820. sprintf(" (%.3f", ($sendtime - $hash->{Helper}{AckPending}{$hash->{CNT}}->{time})).
  1821. "s ago)");
  1822. }
  1823. $hash->{Helper}{AckPending}{$hash->{CNT}} = {
  1824. cmd => uc($msg),
  1825. frame => uc(unpack("H*", $frame)),
  1826. dst => $dst,
  1827. time => $sendtime,
  1828. };
  1829. push @{$hash->{Helper}{LastSendLen}}, (length($hash->{Helper}{AckPending}{$hash->{CNT}}->{cmd}) / 2) + 2;
  1830. shift @{$hash->{Helper}{LastSendLen}} if (scalar(@{$hash->{Helper}{LastSendLen}}) > 2);
  1831. delete($hash->{Helper}{Resend});
  1832. return $hash->{CNT};
  1833. }
  1834. sub HMUARTLGW_send_frame($$)
  1835. {
  1836. my ($hash, $frame) = @_;
  1837. my $name = $hash->{NAME};
  1838. Log3($hash, 5, "HMUARTLGW ${name} send: (".length($frame)."): ".unpack("H*", $frame));
  1839. my $escaped = substr($frame, 0, 1);
  1840. foreach my $byte (split(//, substr($frame, 1))) {
  1841. if (ord($byte) != 0xfc && ord($byte) != 0xfd) {
  1842. $escaped .= $byte;
  1843. next;
  1844. }
  1845. $escaped .= chr(0xfc);
  1846. $escaped .= chr(ord($byte) & 0x7f);
  1847. }
  1848. $escaped = HMUARTLGW_encrypt($hash, $escaped) if ($hash->{'.crypto'});
  1849. my $sendtime = scalar(gettimeofday());
  1850. DevIo_SimpleWrite($hash, $escaped, 0);
  1851. $sendtime;
  1852. }
  1853. sub HMUARTLGW_sendAscii($$)
  1854. {
  1855. my ($hash, $msg) = @_;
  1856. my $name = $hash->{NAME};
  1857. $msg = sprintf($msg, $hash->{CNT});
  1858. my $logmsg = $msg;
  1859. $logmsg =~ s/\r\n$//;
  1860. Log3($hash, HMUARTLGW_getVerbLvl($hash, undef, undef, 5),
  1861. "HMUARTLGW ${name} send (".length($logmsg)."): ". $logmsg);
  1862. $msg = HMUARTLGW_encrypt($hash, $msg) if ($hash->{'.crypto'} && !($msg =~ m/^V/));
  1863. $hash->{CNT} = ($hash->{CNT} + 1) & 0xff;
  1864. DevIo_SimpleWrite($hash, $msg, ($hash->{'.crypto'} && !($msg =~ m/^V/))? 0 : 2);
  1865. }
  1866. sub HMUARTLGW_crc16($;$)
  1867. {
  1868. my ($msg, $crc) = @_;
  1869. $crc = 0xd77f if (!defined($crc));
  1870. foreach my $byte (split(//, $msg)) {
  1871. $crc ^= (ord($byte) << 8) & 0xff00;
  1872. for (my $i = 0; $i < 8; $i++) {
  1873. if ($crc & 0x8000) {
  1874. $crc = ($crc << 1) & 0xffff;
  1875. $crc ^= 0x8005;
  1876. } else {
  1877. $crc = ($crc << 1) & 0xffff;
  1878. }
  1879. }
  1880. }
  1881. return $crc;
  1882. }
  1883. sub HMUARTLGW_encrypt($$)
  1884. {
  1885. my ($hash, $plaintext) = @_;
  1886. my $ciphertext = '';
  1887. while(length($plaintext)) {
  1888. if(length($hash->{'.crypto'}{encrypt}{keystream})) {
  1889. my $len = length($plaintext);
  1890. $len = length($hash->{'.crypto'}{encrypt}{keystream})
  1891. if (length($hash->{'.crypto'}{encrypt}{keystream}) < $len);
  1892. my $ppart = substr($plaintext, 0, $len, '');
  1893. my $kpart = substr($hash->{'.crypto'}{encrypt}{keystream}, 0, $len, '');
  1894. $hash->{'.crypto'}{encrypt}{ciphertext} .= $ppart ^ $kpart;
  1895. $ciphertext .= $ppart ^ $kpart;
  1896. } else {
  1897. $hash->{'.crypto'}{encrypt}{keystream} =
  1898. $hash->{'.crypto'}{cipher}->encrypt($hash->{'.crypto'}{encrypt}{ciphertext});
  1899. $hash->{'.crypto'}{encrypt}{ciphertext} = '';
  1900. }
  1901. }
  1902. $ciphertext;
  1903. }
  1904. sub HMUARTLGW_decrypt($$)
  1905. {
  1906. my ($hash, $ciphertext) = @_;
  1907. my $plaintext = '';
  1908. while(length($ciphertext)) {
  1909. if(length($hash->{'.crypto'}{decrypt}{keystream})) {
  1910. my $len = length($ciphertext);
  1911. $len = length($hash->{'.crypto'}{decrypt}{keystream})
  1912. if (length($hash->{'.crypto'}{decrypt}{keystream}) < $len);
  1913. my $cpart = substr($ciphertext, 0, $len, '');
  1914. my $kpart = substr($hash->{'.crypto'}{decrypt}{keystream}, 0, $len, '');
  1915. $hash->{'.crypto'}{decrypt}{ciphertext} .= $cpart;
  1916. $plaintext .= $cpart ^ $kpart;
  1917. } else {
  1918. $hash->{'.crypto'}{decrypt}{keystream} =
  1919. $hash->{'.crypto'}{cipher}->encrypt($hash->{'.crypto'}{decrypt}{ciphertext});
  1920. $hash->{'.crypto'}{decrypt}{ciphertext} = '';
  1921. }
  1922. }
  1923. $plaintext;
  1924. }
  1925. sub HMUARTLGW_firmwareGetBlock($$$) {
  1926. my ($hash, $file, $id) = @_;
  1927. my $name = $hash->{NAME};
  1928. my $block = "";
  1929. my $ret = open(my $fd, "<", $file);
  1930. if (!$ret) {
  1931. Log3($hash, 1, "HMUARTLGW ${name} can't open firmware file ${file}: $!");
  1932. return undef;
  1933. }
  1934. my $fw = "";
  1935. while(<$fd>) {
  1936. $fw .= $_;
  1937. }
  1938. close($fd);
  1939. my $n = 0;
  1940. while(length($fw)) {
  1941. my $len = unpack('n', pack('H4', $fw));
  1942. if ($n eq $id) {
  1943. $block = substr($fw, 4, $len * 2);
  1944. last;
  1945. }
  1946. $fw = substr($fw, 4 + ($len * 2));
  1947. $n++;
  1948. }
  1949. if ($n != $id) {
  1950. Log3($hash, 1, "HMUARTLGW ${name} invalid block ${id} requested");
  1951. return undef;
  1952. }
  1953. $block;
  1954. }
  1955. sub HMUARTLGW_updateCoPro($$) {
  1956. my ($hash, $msg) = @_;
  1957. my $name = $hash->{NAME};
  1958. RemoveInternalTimer($hash);
  1959. if (($hash->{FirmwareBlock} > 0) && ($msg !~ /^0401$/)) {
  1960. Log3($hash, 1, "HMUARTLGW ${name} firmware flash failed on block " . ($hash->{FirmwareBlock} - 1));
  1961. delete($hash->{FirmwareFile});
  1962. delete($hash->{FirmwareBlock});
  1963. $hash->{DevState} = HMUARTLGW_STATE_QUERY_APP;
  1964. HMUARTLGW_send($hash, HMUARTLGW_OS_GET_APP, HMUARTLGW_DST_OS);
  1965. InternalTimer(gettimeofday()+HMUARTLGW_CMD_TIMEOUT, "HMUARTLGW_CheckCmdResp", $hash, 0);
  1966. return;
  1967. }
  1968. my $block = HMUARTLGW_firmwareGetBlock($hash, $hash->{FirmwareFile}, $hash->{FirmwareBlock});
  1969. if (!defined($block)) {
  1970. Log3($hash, 1, "HMUARTLGW ${name} firmware update aborted");
  1971. delete($hash->{FirmwareFile});
  1972. delete($hash->{FirmwareBlock});
  1973. $hash->{DevState} = HMUARTLGW_STATE_QUERY_APP;
  1974. HMUARTLGW_send($hash, HMUARTLGW_OS_GET_APP, HMUARTLGW_DST_OS);
  1975. InternalTimer(gettimeofday()+HMUARTLGW_CMD_TIMEOUT, "HMUARTLGW_CheckCmdResp", $hash, 0);
  1976. return;
  1977. } elsif ($block eq "") {
  1978. Log3($hash, 1, "HMUARTLGW ${name} firmware update successfull");
  1979. delete($hash->{FirmwareFile});
  1980. delete($hash->{FirmwareBlock});
  1981. $hash->{DevState} = HMUARTLGW_STATE_QUERY_APP;
  1982. HMUARTLGW_send($hash, HMUARTLGW_OS_GET_APP, HMUARTLGW_DST_OS);
  1983. InternalTimer(gettimeofday()+HMUARTLGW_CMD_TIMEOUT, "HMUARTLGW_CheckCmdResp", $hash, 0);
  1984. return;
  1985. }
  1986. #strip CRC from block
  1987. $block = substr($block, 0, -4);
  1988. HMUARTLGW_send($hash, HMUARTLGW_OS_UPDATE_FIRMWARE . ${block}, HMUARTLGW_DST_OS);
  1989. $hash->{FirmwareBlock}++;
  1990. InternalTimer(gettimeofday()+HMUARTLGW_FIRMWARE_TIMEOUT, "HMUARTLGW_CheckCmdResp", $hash, 0);
  1991. }
  1992. sub HMUARTLGW_getVerbLvl($$$$) {
  1993. my ($hash, $src, $dst, $def) = @_;
  1994. $hash = $hash->{'.lgwHash'} if (defined($hash->{'.lgwHash'}));
  1995. #Lookup IDs on change
  1996. if (defined($hash->{Helper}{Log}{Resolve}) && $init_done) {
  1997. foreach my $id (@{$hash->{Helper}{Log}{IDs}}) {
  1998. next if ($id =~ /^([\da-f]{6}|sys|all)$/i);
  1999. my $newId = substr(CUL_HM_name2Id($id),0,6);
  2000. next if ($newId !~ /^[\da-f]{6}$/i);
  2001. $id = $newId;
  2002. }
  2003. delete($hash->{Helper}{Log}{Resolve});
  2004. }
  2005. return (grep /^sys$/i, @{$hash->{Helper}{Log}{IDs}}) ? 0 : $def
  2006. if ((!defined($src)) || (!defined($dst)));
  2007. return (grep /^($src|$dst|all)$/i, @{$hash->{Helper}{Log}{IDs}}) ? 0 : $def;
  2008. }
  2009. 1;
  2010. =pod
  2011. =item summary support for the HomeMatic UART module (RPi) and Wireless LAN Gateway
  2012. =item summary_DE Anbindung von HomeMatic UART Modul (RPi) und Wireless LAN Gateway
  2013. =begin html
  2014. <a name="HMUARTLGW"></a>
  2015. <h3>HMUARTLGW</h3>
  2016. <ul>
  2017. HMUARTLGW provides support for the eQ-3 HomeMatic Wireless LAN Gateway
  2018. (HM-LGW-O-TW-W-EU) and the eQ-3 HomeMatic UART module (HM-MOD-UART), which
  2019. is part of the HomeMatic wireless module for the Raspberry Pi
  2020. (HM-MOD-RPI-PCB).<br>
  2021. <br><br>
  2022. <a name="HMUARTLGHW_define"></a>
  2023. <b>Define</b>
  2024. <ul>
  2025. <code>define &lt;name&gt; HMUARTLGW &lt;device&gt;</code><br><br>
  2026. The &lt;device&gt;-parameter depends on the device-type:
  2027. <ul>
  2028. <li>HM-MOD-UART: &lt;device&gt; specifies the serial port to communicate
  2029. with. The baud-rate is fixed at 115200 and does not need to be
  2030. specified.<br>
  2031. If the HM-MOD-UART is connected to the network by a serial bridge,
  2032. the connection has to be defined in an URL-like format
  2033. (<code>uart://ip:port</code>).</li>
  2034. <li>HM-LGW-O-TW-W-EU: &lt;device&gt; specifies the IP address or hostname
  2035. of the gateway, optionally followed by : and the port number of the
  2036. BidCoS-port (default when not specified: 2000).</li>
  2037. </ul>
  2038. <br><br>
  2039. Examples:<br>
  2040. <ul>
  2041. <li>Local HM-MOD-UART at <code>/dev/ttyAMA0</code>:<br>
  2042. <code>define myHmUART HMUARTLGW /dev/ttyAMA0</code><br>&nbsp;</li>
  2043. <li>LAN Gateway at <code>192.168.42.23</code>:<br>
  2044. <code>define myHmLGW HMUARTLGW 192.168.42.23</code><br>&nbsp;</li>
  2045. <li>Remote HM-MOD-UART using <code>socat</code> on a Raspberry Pi:<br>
  2046. <code>define myRemoteHmUART HMUARTLGW uart://192.168.42.23:12345</code><br><br>
  2047. Remote Raspberry Pi:<br><code>$ socat TCP4-LISTEN:12345,fork,reuseaddr /dev/ttyAMA0,raw,echo=0,b115200</code></li>
  2048. </ul>
  2049. </ul>
  2050. <br>
  2051. <a name="HMUARTLGW_set"></a>
  2052. <p><b>Set</b></p>
  2053. <ul>
  2054. <li>close<br>
  2055. Closes the connection to the device.
  2056. </li>
  2057. <li><a href="#hmPairForSec">hmPairForSec</a></li>
  2058. <li><a href="#hmPairSerial">hmPairSerial</a></li>
  2059. <li>open<br>
  2060. Opens the connection to the device and initializes it.
  2061. </li>
  2062. <li>reopen<br>
  2063. Reopens the connection to the device and reinitializes it.
  2064. </li>
  2065. <li>restart<br>
  2066. Reboots the device.
  2067. </li>
  2068. <li>updateCoPro &lt;/path/to/firmware.eq3&gt;<br>
  2069. Update the coprocessor-firmware (reading D-firmware) from the
  2070. supplied file. Source for firmware-images (version 1.4.1, official
  2071. eQ-3 repository):<br>
  2072. <ul>
  2073. <li>HM-MOD-UART: <a href="https://raw.githubusercontent.com/eq-3/occu/28045df83480122f90ab92f7c6e625f9bf3b61aa/firmware/HM-MOD-UART/coprocessor_update.eq3">coprocessor_update.eq3</a> (version 1.4.1)</li>
  2074. <li>HM-LGW-O-TW-W-EU: <a href="https://raw.githubusercontent.com/eq-3/occu/28045df83480122f90ab92f7c6e625f9bf3b61aa/firmware/coprocessor_update_hm_only.eq3">coprocessor_update_hm_only.eq3</a> (version 1.4.1)<br>
  2075. Please also make sure that D-LANfirmware is at least at version
  2076. 1.1.5. To update to this version, use the eQ-3 CLI tools (see wiki)
  2077. or use the eQ-3 netfinder with this firmware image: <a href="https://github.com/eq-3/occu/raw/28045df83480122f90ab92f7c6e625f9bf3b61aa/firmware/hm-lgw-o-tw-w-eu_update.eq3">hm-lgw-o-tw-w-eu_update.eq3</a><br>
  2078. <b>Do not flash hm-lgw-o-tw-w-eu_update.eq3 with updateCoPro!</b></li>
  2079. </ul>
  2080. </li>
  2081. </ul>
  2082. <br>
  2083. <a name="HMUARTLGW_get"></a>
  2084. <p><b>Get</b></p>
  2085. <ul>
  2086. <li>assignIDs<br>
  2087. Returns the HomeMatic devices currently assigned to this IO-device.
  2088. </li>
  2089. </ul>
  2090. <br>
  2091. <a name="HMUARTLGW_attr"></a>
  2092. <b>Attributes</b>
  2093. <ul>
  2094. <li>csmaCa<br>
  2095. Enable or disable CSMA/CA (Carrier sense multiple access with collision
  2096. avoidance), also known as listen-before-talk.<br>
  2097. Default: 0 (disabled)
  2098. </li>
  2099. <li>dummy<br>
  2100. Do not interact with the device at all, only define it.<br>
  2101. Default: not set
  2102. </li>
  2103. <li>dutyCycle<br>
  2104. Enable or disable the duty-cycle check (1% rule) performed by the
  2105. wireless module.<br>
  2106. Disabling this might be illegal in your country, please check with local
  2107. regulations!<br>
  2108. Default: 1 (enabled)
  2109. </li>
  2110. <li><a href="#hmId">hmId</a></li>
  2111. <li><a name="HMLANhmKey">hmKey</a></li>
  2112. <li><a name="HMLANhmKey2">hmKey2</a></li>
  2113. <li><a name="HMLANhmKey3">hmKey3</a></li>
  2114. <li>lgwPw<br>
  2115. AES password for the eQ-3 HomeMatic Wireless LAN Gateway. The default
  2116. password is printed on the back of the device (but can be changed by
  2117. the user). If AES communication is enabled on the LAN Gateway (default),
  2118. this attribute has to be set to the correct value or communication will
  2119. not be possible. In addition, the perl-module Crypt::Rijndael (which
  2120. provides the AES cipher) must be installed.
  2121. </li>
  2122. <li>loadEvents<br>
  2123. Enables logging of the wireless load (in percent of the allowed maximum
  2124. sending-time) of the interface.
  2125. Default: 0 (disabled)
  2126. </li>
  2127. <li>logIDs<br>
  2128. Enables selective logging of HMUARTLGW messages. A list of comma separated
  2129. HMIds or HM device names/channel names can be entered which shall be logged.<br>
  2130. <ul>
  2131. <li><i>all</i>: will log raw messages for all HMIds</li>
  2132. <li><i>sys</i>: will log system related messages like keep-alive</li>
  2133. </ul>
  2134. In order to enable all messages set: <i>all,sys</i>
  2135. </li>
  2136. <li>qLen<br>
  2137. Maximum number of commands in the internal queue of the HMUARTLGW module.
  2138. New commands when the queue is full are dropped. Each command has a maximum
  2139. lifetime of 3s when active, so the worst-case delay of a command is qLen * 3s
  2140. (3 minutes with default settings).<br>
  2141. Default: 60
  2142. </li>
  2143. </ul>
  2144. <br>
  2145. </ul>
  2146. =end html
  2147. =begin html_DE
  2148. <a name="HMUARTLGW"></a>
  2149. <h3>HMUARTLGW</h3>
  2150. <ul>
  2151. Das Modul HMUARTLGW erm&ouml;glicht die Anbindung des eQ-3 HomeMatic Wireless
  2152. LAN Gateways (HM-LGW-O-TW-W-EU) und des eQ-3 HomeMatic UART Moduls
  2153. (HM-MOD-UART), welches Teil des HomeMatic-Moduls f&uuml;r den Raspberry Pi
  2154. (HM-MOD-RPI-PCB) ist.<br>
  2155. <br><br>
  2156. <a name="HMUARTLGHW_define"></a>
  2157. <b>Define</b>
  2158. <ul>
  2159. <code>define &lt;name&gt; HMUARTLGW &lt;device&gt;</code><br><br>
  2160. Der Parameter &lt;device&gt; h&auml;ngt vom eingesetzten Ger&auml;tetyp ab:
  2161. <ul>
  2162. <li>HM-MOD-UART: &lt;device&gt; ist die zu benutzende serielle
  2163. Schnittstelle. Die Baudrate ist fest auf 115200 eingestellt und muss
  2164. nicht angegeben werden.<br>
  2165. Falls der HM-MOD-UART &uuml;ber einen Seriell-zu-Ethernet-Konverter
  2166. mit dem Netzwerk verbunden ist, muss die Definition in einem
  2167. an URLs angelehnten Format geschehen
  2168. (<code>uart://ip:port</code>).</li>
  2169. <li>HM-LGW-O-TW-W-EU: &lt;device&gt; gibt die IP-Adresse oder den
  2170. Hostnamen des Gateways an, optional gefolgt von einem Doppelpunkt
  2171. und der Portnummer des BidCos-Ports (Default falls nicht angegeben:
  2172. 2000).</li>
  2173. </ul>
  2174. <br><br>
  2175. Beispiele:<br>
  2176. <ul>
  2177. <li>Lokaler HM-MOD-UART an der Schnittstelle <code>/dev/ttyAMA0</code>:<br>
  2178. <code>define myHmUART HMUARTLGW /dev/ttyAMA0</code><br>&nbsp;</li>
  2179. <li>LAN Gateway mit der IP-Adresse <code>192.168.42.23</code>:<br>
  2180. <code>define myHmLGW HMUARTLGW 192.168.42.23</code><br>&nbsp;</li>
  2181. <li>Entfernter HM-MOD-UART unter Verwendung von <code>socat</code> auf einem Raspberry Pi:<br>
  2182. <code>define myRemoteHmUART HMUARTLGW uart://192.168.42.23:12345</code><br><br>
  2183. Entfernter Raspberry Pi:<br><code>$ socat TCP4-LISTEN:12345,fork,reuseaddr /dev/ttyAMA0,raw,echo=0,b115200</code></li>
  2184. </ul>
  2185. </ul>
  2186. <br>
  2187. <a name="HMUARTLGW_set"></a>
  2188. <p><b>Set</b></p>
  2189. <ul>
  2190. <li>close<br>
  2191. Schlie&szlig;t die Verbindung zum Ger&auml;t.
  2192. </li>
  2193. <li><a href="#hmPairForSec">hmPairForSec</a></li>
  2194. <li><a href="#hmPairSerial">hmPairSerial</a></li>
  2195. <li>open<br>
  2196. &Ouml;ffnet die Verbindung zum Ger&auml;t und initialisiert es.
  2197. </li>
  2198. <li>reopen<br>
  2199. Schli&szlig;t und &ouml;ffnet die Verbindung zum Ger&auml;t und re-initialisiert es.
  2200. </li>
  2201. <li>restart<br>
  2202. Rebootet das Ger&auml;t.
  2203. </li>
  2204. <li>updateCoPro &lt;/path/to/firmware.eq3&gt;<br>
  2205. Aktualisierung der Koprozessor-Firmware (Reading D-firmware) mit der
  2206. angegebenen Datei. Quelle f&uuml;r Firmware-Images (Version 1.4.1,
  2207. offizielles eQ-3 Repository):<br>
  2208. <ul>
  2209. <li>HM-MOD-UART: <a href="https://raw.githubusercontent.com/eq-3/occu/28045df83480122f90ab92f7c6e625f9bf3b61aa/firmware/HM-MOD-UART/coprocessor_update.eq3">coprocessor_update.eq3</a> (Version 1.4.1)</li>
  2210. <li>HM-LGW-O-TW-W-EU: <a href="https://raw.githubusercontent.com/eq-3/occu/28045df83480122f90ab92f7c6e625f9bf3b61aa/firmware/coprocessor_update_hm_only.eq3">coprocessor_update_hm_only.eq3</a> (Version 1.4.1)<br>
  2211. Bitte zus&auml;tzlich sicherstellen, dass die Version der
  2212. D-LANfirmware mindestens 1.1.5 betr&auml;gt. Um auf diese Version
  2213. zu aktualisieren k&ouml;nnen die eQ-3 CLI Tools (siehe Wiki) oder
  2214. der eQ-3 Netfinder genutzt werden. Das passende Image ist:
  2215. <a href="https://github.com/eq-3/occu/raw/28045df83480122f90ab92f7c6e625f9bf3b61aa/firmware/hm-lgw-o-tw-w-eu_update.eq3">hm-lgw-o-tw-w-eu_update.eq3</a><br>
  2216. <b>Die Datei hm-lgw-o-tw-w-eu_update.eq3 nicht mit updateCoPro flashen!</b></li>
  2217. </ul>
  2218. </li>
  2219. </ul>
  2220. <br>
  2221. <a name="HMUARTLGW_get"></a>
  2222. <p><b>Get</b></p>
  2223. <ul>
  2224. <li>assignIDs<br>
  2225. Gibt die aktuell diesem IO-Ger&auml;t zugeordneten HomeMatic-Ger&auml;te
  2226. zur&uuml;ck.
  2227. </li>
  2228. </ul>
  2229. <br>
  2230. <a name="HMUARTLGW_attr"></a>
  2231. <b>Attribute</b>
  2232. <ul>
  2233. <li>csmaCa<br>
  2234. Aktiviert oder deaktiviert CSMA/CA (Carrier sense multiple access with
  2235. collision avoidance), auch bekannt als Listen-Before-Talk.<br>
  2236. Default: 0 (deaktiviert)
  2237. </li>
  2238. <li>dummy<br>
  2239. Erm&ouml;glicht die Definition des Ger&auml;ts ohne jegliche Interaktion
  2240. mit einem physikalischen Ger&auml;t.<br>
  2241. Default: nicht gesetzt
  2242. </li>
  2243. <li>dutyCycle<br>
  2244. Aktiviert oder deaktiviert die &Uuml;berpr&uuml;fung des Arbeitszyklus
  2245. (1%-Regel) durch das Sendemodul.<br>
  2246. Die Abschaltung dieser Funktion kann in verschiedenen L&auml;ndern gegen
  2247. das Gesetz verstossen, weshalb zuerst die Situation anhand lokaler
  2248. Richtlinien zu pr&uuml;fen ist!<br>
  2249. Default: 1 (aktiviert)
  2250. </li>
  2251. <li><a href="#hmId">hmId</a></li>
  2252. <li><a name="HMLANhmKey">hmKey</a></li>
  2253. <li><a name="HMLANhmKey2">hmKey2</a></li>
  2254. <li><a name="HMLANhmKey3">hmKey3</a></li>
  2255. <li>lgwPw<br>
  2256. AES-Passwort f&uuml;r das eQ-3 HomeMatic Wireless LAN Gateway. Das initiale
  2257. Passwort befindet sich auf der R&uuml;ckseite des Ger&auml;ts, kann aber
  2258. durch den Benutzer ge&auml;ndert werden. Falls die AES gesicherte
  2259. Kommunikation aktiviert ist (Auslieferungszustand), muss dieses Attribut
  2260. auf den richtigen Wert gesetzt werden, da ansonsten keine Kommunikation
  2261. m&ouml;glich ist. Zus&auml;tzlich muss das Perl-Modul Crypt::Rijndael
  2262. (stellt den AES-Algorithmus bereit) installiert sein.
  2263. </li>
  2264. <li>loadEvents<br>
  2265. Aktiviert die Erzeugung von Log-Nachrichten &uuml;ber die Funklast
  2266. des Interfaces (in Prozent der erlaubten Sendezeit).
  2267. Default: 0 (deaktiviert)
  2268. </li>
  2269. <li>logIDs<br>
  2270. Aktiviert die gezielte Erzeugung von Log-Nachrichten. Der Parameter ist
  2271. eine durch Komma getrennte Liste an HMIds oder HM Ger&auml;te-/Kanalnamen,
  2272. deren Nachrichten aufgezeichnet werden sollen.<br>
  2273. <ul>
  2274. <li><i>all</i>: Zeichnet die Rohnachrichten aller HMIds auf</li>
  2275. <li><i>sys</i>: Zeichnet Systemnachrichten (z.B. Keep-Alive) auf</li>
  2276. </ul>
  2277. Um alle m&ouml;glichen Nachrichten aufzuzeichnen, kann <i>all,sys</i>
  2278. genutzt werden.
  2279. </li>
  2280. <li>qLen<br>
  2281. Maximale Anzahl an Kommandos in der internen Warteschlange des
  2282. HMUARTLGW-Moduls. Neue Kommandos werden verworfen, wenn die Warteschlange
  2283. gef&uuml;llt ist. Jedes Kommando hat eine Lebensdauer von 3s, sobald es
  2284. aktiv verarbeitet wird. Die Verz&ouml;gerung eines Kommandos betr&auml;gt
  2285. im schlechtesten Fall also qLen * 3s (3 Minuten mit den Defaulteinstellungen).<br>
  2286. Default: 60
  2287. </li>
  2288. </ul>
  2289. <br>
  2290. </ul>
  2291. =end html_DE
  2292. =cut