00_HMUARTLGW.pm 85 KB

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