| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721 |
- ##############################################
- # $Id: 00_HMUARTLGW.pm 16166 2018-02-13 19:52:08Z mgernoth $
- #
- # HMUARTLGW provides support for the eQ-3 HomeMatic Wireless LAN Gateway
- # (HM-LGW-O-TW-W-EU) and the eQ-3 HomeMatic UART module (HM-MOD-UART), which
- # is part of the HomeMatic wireless module for the Raspberry Pi
- # (HM-MOD-RPI-PCB).
- #
- # TODO:
- # - Filter out "A112" from CUL_HM and synthesize response
- package main;
- use strict;
- use warnings;
- use Digest::MD5;
- use Time::HiRes qw(gettimeofday time);
- use Time::Local;
- eval "use Crypt::Rijndael";
- my $cryptFunc = ($@)?0:1;
- use constant {
- HMUARTLGW_OS_GET_APP => "00",
- HMUARTLGW_OS_GET_FIRMWARE => "02",
- HMUARTLGW_OS_CHANGE_APP => "03",
- HMUARTLGW_OS_ACK => "04",
- HMUARTLGW_OS_UPDATE_FIRMWARE => "05",
- HMUARTLGW_OS_UNSOL_CREDITS => "05",
- HMUARTLGW_OS_NORMAL_MODE => "06",
- HMUARTLGW_OS_UPDATE_MODE => "07",
- HMUARTLGW_OS_GET_CREDITS => "08",
- HMUARTLGW_OS_ENABLE_CREDITS => "09",
- HMUARTLGW_OS_ENABLE_CSMACA => "0A",
- HMUARTLGW_OS_GET_SERIAL => "0B",
- HMUARTLGW_OS_SET_TIME => "0E",
- HMUARTLGW_APP_SET_HMID => "00",
- HMUARTLGW_APP_GET_HMID => "01",
- HMUARTLGW_APP_SEND => "02",
- HMUARTLGW_APP_SET_CURRENT_KEY => "03", #key index, 00x17 when no key
- HMUARTLGW_APP_ACK => "04",
- HMUARTLGW_APP_RECV => "05",
- HMUARTLGW_APP_ADD_PEER => "06",
- HMUARTLGW_APP_REMOVE_PEER => "07",
- HMUARTLGW_APP_GET_PEERS => "08",
- HMUARTLGW_APP_PEER_ADD_AES => "09",
- HMUARTLGW_APP_PEER_REMOVE_AES => "0A",
- HMUARTLGW_APP_SET_TEMP_KEY => "0B", #key index, 00x17 when no key
- HMUARTLGW_APP_SET_PREVIOUS_KEY => "0F", #key index, 00x17 when no key
- HMUARTLGW_APP_DEFAULT_HMID => "10",
- HMUARTLGW_DUAL_GET_APP => "01",
- HMUARTLGW_DUAL_CHANGE_APP => "02",
- HMUARTLGW_ACK_NACK => "00",
- HMUARTLGW_ACK => "01",
- HMUARTLGW_ACK_INFO => "02",
- HMUARTLGW_ACK_WITH_RESPONSE => "03",
- HMUARTLGW_ACK_EUNKNOWN => "04",
- HMUARTLGW_ACK_ENOCREDITS => "05",
- HMUARTLGW_ACK_ECSMACA => "06",
- HMUARTLGW_ACK_WITH_MULTIPART_DATA => "07", #04 07 XX YY: part XX of YY
- HMUARTLGW_ACK_EINPROGRESS => "08",
- HMUARTLGW_ACK_WITH_RESPONSE_AES_OK => "0C",
- HMUARTLGW_ACK_WITH_RESPONSE_AES_KO => "0D",
- HMUARTLGW_RECV_RESP => "01",
- HMUARTLGW_RECV_RESP_WITH_AES_OK => "02",
- HMUARTLGW_RECV_RESP_WITH_AES_KO => "03",
- HMUARTLGW_RECV_TRIG => "11",
- HMUARTLGW_RECV_TRIG_WITH_AES_OK => "12",
- HMUARTLGW_DST_OS => 0,
- HMUARTLGW_DST_APP => 1,
- HMUARTLGW_DST_DUAL => 254,
- HMUARTLGW_DST_DUAL_ERR => 255,
- HMUARTLGW_STATE_NONE => 0,
- HMUARTLGW_STATE_QUERY_APP => 1,
- HMUARTLGW_STATE_ENTER_APP => 2,
- HMUARTLGW_STATE_GETSET_PARAMETERS => 3,
- HMUARTLGW_STATE_SET_HMID => 4,
- HMUARTLGW_STATE_GET_HMID => 5,
- HMUARTLGW_STATE_GET_DEFAULT_HMID => 6,
- HMUARTLGW_STATE_SET_TIME => 7,
- HMUARTLGW_STATE_GET_FIRMWARE => 8,
- HMUARTLGW_STATE_GET_SERIAL => 9,
- HMUARTLGW_STATE_SET_NORMAL_MODE => 10,
- HMUARTLGW_STATE_ENABLE_CSMACA => 11,
- HMUARTLGW_STATE_ENABLE_CREDITS => 12,
- HMUARTLGW_STATE_GET_INIT_CREDITS => 13,
- HMUARTLGW_STATE_SET_CURRENT_KEY => 14,
- HMUARTLGW_STATE_SET_PREVIOUS_KEY => 15,
- HMUARTLGW_STATE_SET_TEMP_KEY => 16,
- HMUARTLGW_STATE_UPDATE_PEER => 90,
- HMUARTLGW_STATE_UPDATE_PEER_AES1 => 91,
- HMUARTLGW_STATE_UPDATE_PEER_AES2 => 92,
- HMUARTLGW_STATE_UPDATE_PEER_CFG => 93,
- HMUARTLGW_STATE_SET_UPDATE_MODE => 95,
- HMUARTLGW_STATE_KEEPALIVE_INIT => 96,
- HMUARTLGW_STATE_KEEPALIVE_SENT => 97,
- HMUARTLGW_STATE_GET_CREDITS => 98,
- HMUARTLGW_STATE_RUNNING => 99,
- HMUARTLGW_STATE_SEND => 100,
- HMUARTLGW_STATE_SEND_NOACK => 101,
- HMUARTLGW_STATE_SEND_TIMED => 102,
- HMUARTLGW_STATE_UPDATE_COPRO => 200,
- HMUARTLGW_STATE_UNSUPPORTED_FW => 999,
- HMUARTLGW_CMD_TIMEOUT => 3,
- HMUARTLGW_CMD_RETRY_CNT => 3,
- HMUARTLGW_FIRMWARE_TIMEOUT => 10,
- HMUARTLGW_SEND_TIMEOUT => 10,
- HMUARTLGW_SEND_RETRY_SECONDS => 3,
- HMUARTLGW_BUSY_RETRY_MS => 50,
- HMUARTLGW_CSMACA_RETRY_MS => 200,
- HMUARTLGW_KEEPALIVE_SECONDS => 10,
- HMUARTLGW_KEEPALIVE_WARN_LATE_S => 4,
- };
- my %sets = (
- "hmPairForSec" => "",
- "hmPairSerial" => "",
- "reopen" => "noArg",
- "open" => "noArg",
- "close" => "noArg",
- "restart" => "noArg",
- "updateCoPro" => "",
- );
- my %gets = (
- "assignIDs" => "noArg",
- );
- sub HMUARTLGW_Initialize($)
- {
- my ($hash) = @_;
- require "$attr{global}{modpath}/FHEM/DevIo.pm";
- $hash->{ReadyFn} = "HMUARTLGW_Ready";
- $hash->{ReadFn} = "HMUARTLGW_Read";
- $hash->{WriteFn} = "HMUARTLGW_Write";
- $hash->{DefFn} = "HMUARTLGW_Define";
- $hash->{UndefFn} = "HMUARTLGW_Undefine";
- $hash->{SetFn} = "HMUARTLGW_Set";
- $hash->{GetFn} = "HMUARTLGW_Get";
- $hash->{AttrFn} = "HMUARTLGW_Attr";
- $hash->{RenameFn} = "HMUARTLGW_Rename";
- $hash->{ShutdownFn}= "HMUARTLGW_Shutdown";
- $hash->{AttrList}= "hmId " .
- "lgwPw " .
- "hmKey hmKey2 hmKey3 " .
- "dutyCycle:1,0 " .
- "csmaCa:0,1 " .
- "qLen " .
- "logIDs ".
- "dummy:1 ".
- "loadEvents:0,1 ".
- $readingFnAttributes;
- }
- sub HMUARTLGW_Connect($$);
- sub HMUARTLGW_SendPendingCmd($);
- sub HMUARTLGW_SendCmd($$);
- sub HMUARTLGW_GetSetParameterReq($);
- sub HMUARTLGW_getAesKeys($);
- sub HMUARTLGW_updateMsgLoad($$);
- sub HMUARTLGW_Read($);
- sub HMUARTLGW_RemoveHMPair($);
- sub HMUARTLGW_send($$$;$);
- sub HMUARTLGW_send_frame($$);
- sub HMUARTLGW_crc16($;$);
- sub HMUARTLGW_encrypt($$);
- sub HMUARTLGW_decrypt($$);
- sub HMUARTLGW_getVerbLvl($$$$);
- sub HMUARTLGW_firmwareGetBlock($$$);
- sub HMUARTLGW_updateCoPro($$);
- sub HMUARTLGW_DoInit($)
- {
- my ($hash) = @_;
- my $name = $hash->{NAME};
- $hash->{CNT} = 0x00;
- delete($hash->{DEVCNT});
- delete($hash->{'.crypto'});
- delete($hash->{keepAlive});
- delete($hash->{Helper});
- delete($hash->{AssignedPeerCnt});
- delete($hash->{msgLoadCurrent});
- delete($hash->{msgLoadHistory});
- delete($hash->{msgLoadHistoryAbs});
- delete($hash->{owner});
- $hash->{DevState} = HMUARTLGW_STATE_NONE;
- $hash->{XmitOpen} = 0;
- $hash->{LastOpen} = gettimeofday();
- $hash->{LGW_Init} = 1 if ($hash->{DevType} =~ m/^LGW/);
- $hash->{Helper}{Log}{IDs} = [ split(/,/, AttrVal($name, "logIDs", "")) ];
- $hash->{Helper}{Log}{Resolve} = 1;
- RemoveInternalTimer($hash);
- if ($hash->{DevType} eq "LGW") {
- my $keepAlive = {
- NR => $devcount++,
- NAME => "${name}:keepAlive",
- STATE => "uninitialized",
- TYPE => $hash->{TYPE},
- TEMPORARY => 1,
- directReadFn => \&HMUARTLGW_Read,
- DevType => "LGW-KeepAlive",
- '.lgwHash' => $hash,
- };
- $attr{$keepAlive->{NAME}}{room} = "hidden";
- $attr{$keepAlive->{NAME}}{verbose} = AttrVal($name, "verbose", undef);
- $defs{$keepAlive->{NAME}} = $keepAlive;
- DevIo_CloseDev($keepAlive);
- my ($ip, $port) = split(/:/, $hash->{DeviceName});
- $keepAlive->{DeviceName} = "${ip}:" . ($port + 1);
- DevIo_OpenDev($keepAlive, 0, "HMUARTLGW_DoInit", \&HMUARTLGW_Connect);
- $hash->{keepAlive} = $keepAlive;
- }
- InternalTimer(gettimeofday()+1, "HMUARTLGW_StartInit", $hash, 0);
- return;
- }
- sub HMUARTLGW_Connect($$)
- {
- my ($hash, $err) = @_;
- Log3($hash, 5, "HMUARTLGW $hash->{NAME}: ${err}") if ($err);
- }
- sub HMUARTLGW_Define($$)
- {
- my ($hash, $def) = @_;
- my @a = split("[ \t][ \t]*", $def);
- if (@a != 3) {
- return "wrong syntax: define <name> HMUARTLGW /path/to/port|hostname";
- }
- my $name = $a[0];
- my $dev = $a[2];
- HMUARTLGW_Undefine($hash, $name);
- if ($dev !~ m/\//) {
- $dev .= ":2000" if ($dev !~ m/:/);
- $hash->{DevType} = "LGW";
- } else {
- if ($dev =~ m/^uart:\/\/(.*)$/) {
- $dev = $1;
- } elsif ($dev !~ m/\@/) {
- $dev .= "\@115200";
- }
- $hash->{DevType} = "UART";
- $hash->{model} = "HM-MOD-UART";
- readingsBeginUpdate($hash);
- delete($hash->{READINGS}{"D-LANfirmware"});
- readingsBulkUpdate($hash, "D-type", $hash->{model});
- readingsEndUpdate($hash, 1);
- }
- $hash->{DeviceName} = $dev;
- $hash->{Clients} = ":CUL_HM:";
- my %ml = ( "1:CUL_HM" => "^A......................" );
- $hash->{MatchList} = \%ml;
- if (defined(AttrVal($name, "dummy", undef))) {
- readingsSingleUpdate($hash, "state", "dummy", 1);
- HMUARTLGW_updateCondition($hash);
- return;
- }
- return DevIo_OpenDev($hash, 0, "HMUARTLGW_DoInit", \&HMUARTLGW_Connect);
- }
- sub HMUARTLGW_Undefine($$;$)
- {
- my ($hash, $name, $noclose) = @_;
- RemoveInternalTimer($hash);
- RemoveInternalTimer("HMUARTLGW_CheckCredits:$name");
- RemoveInternalTimer("hmPairForSec:$name");
- if ($hash->{keepAlive}) {
- RemoveInternalTimer($hash->{keepAlive});
- DevIo_CloseDev($hash->{keepAlive});
- delete($attr{$hash->{keepAlive}->{NAME}});
- delete($defs{$hash->{keepAlive}->{NAME}});
- delete($hash->{keepAlive});
- }
- if (!$noclose) {
- DevIo_CloseDev($hash);
- Log3($hash, 3, "${name} device closed") if (!defined($hash->{FD}));
- }
- $hash->{DevState} = HMUARTLGW_STATE_NONE;
- $hash->{XmitOpen} = 0;
- HMUARTLGW_updateCondition($hash);
- }
- sub HMUARTLGW_Reopen($;$)
- {
- my ($hash, $noclose) = @_;
- $hash = $hash->{'.lgwHash'} if ($hash->{'.lgwHash'});
- my $name = $hash->{NAME};
- Log3($hash, 4, "HMUARTLGW ${name} Reopen");
- HMUARTLGW_Undefine($hash, $name, $noclose);
- return DevIo_OpenDev($hash, 1, "HMUARTLGW_DoInit", \&HMUARTLGW_Connect);
- }
- sub HMUARTLGW_Ready($)
- {
- my ($hash) = @_;
- my $name = $hash->{NAME};
- my $state = ReadingsVal($name, "state", "unknown");
- Log3($hash, 4, "HMUARTLGW ${name} ready: ${state}");
- if ((!$hash->{'.lgwHash'}) && $state eq "disconnected") {
- #don't immediately reconnect when we just connected, delay
- #for 5s because remote closed the connection on us
- if (defined($hash->{LastOpen}) &&
- $hash->{LastOpen} + 5 >= gettimeofday()) {
- return 0;
- }
- return HMUARTLGW_Reopen($hash, 1);
- }
- return 0;
- }
- sub HMUARTLGW_Rename($$)
- {
- my ($name, $old_name) = @_;
- my $hash = $defs{$name};
- if (defined($hash->{Helper}{Initialized})) {
- RemoveInternalTimer("HMUARTLGW_CheckCredits:${old_name}");
- InternalTimer(gettimeofday()+1, "HMUARTLGW_CheckCredits", "HMUARTLGW_CheckCredits:${name}", 0);
- }
- if ($hash->{hmPair}) {
- HMUARTLGW_RemoveHMPair("hmPairForSec:${old_name}");
- }
- }
- sub HMUARTLGW_Shutdown($)
- {
- my ($hash) = @_;
- my $name = $hash->{NAME};
- #switch to bootloader to stop the module from interfering
- HMUARTLGW_send($hash, HMUARTLGW_OS_CHANGE_APP, HMUARTLGW_DST_OS)
- if ($hash->{DevState} > HMUARTLGW_STATE_ENTER_APP);
- DevIo_CloseDev($hash->{keepAlive}) if ($hash->{keepAlive});
- DevIo_CloseDev($hash);
- return undef;
- }
- #HM-LGW communicates line-based during init
- sub HMUARTLGW_LGW_Init($)
- {
- my ($hash) = @_;
- my $name = $hash->{NAME};
- my $p = pack("H*", $hash->{PARTIAL});
- while($p =~ m/\n/) {
- (my $line, $p) = split(/\n/, $p, 2);
- $line =~ s/\r$//;
- Log3($hash, HMUARTLGW_getVerbLvl($hash, undef, undef, 5), "HMUARTLGW ${name} read (".length($line)."): ${line}");
- my $msg;
- if ($line =~ m/^H(..),01,([^,]*),([^,]*),([^,]*)$/) {
- $hash->{DEVCNT} = hex($1);
- $hash->{CNT} = hex($1);
- if ($hash->{DevType} eq "LGW") {
- $hash->{model} = $2;
- readingsBeginUpdate($hash);
- readingsBulkUpdate($hash, "D-type", $2);
- readingsBulkUpdate($hash, "D-serialNr", $4);
- my $fw = $3;
- if ($fw =~ m/^(\d+)\.(\d+)\.(\d+)$/) {
- my $fwver = (int($1) << 16) | (int($2) << 8) | int($3);
- $fw .= " (outdated)" if ($fwver < 0x010105);
- }
- readingsBulkUpdate($hash, "D-LANfirmware", $fw);
- readingsEndUpdate($hash, 1);
- }
- } elsif ($line =~ m/^V(..),(................................)$/) {
- $hash->{DEVCNT} = hex($1);
- $hash->{CNT} = hex($1);
- my $lgwName = $name;
- $lgwName = $hash->{'.lgwHash'}->{NAME} if ($hash->{'.lgwHash'});
- my $lgwPw = AttrVal($lgwName, "lgwPw", undef);
- if (!$cryptFunc) {
- Log3($hash, 1, "HMUARTLGW ${name} wants to initiate encrypted communication, but Crypt::Rijndael is not installed.");
- } elsif (!$lgwPw) {
- Log3($hash, 1, "HMUARTLGW ${name} wants to initiate encrypted communication, but no lgwPw set!");
- } else {
- my($s,$us) = gettimeofday();
- my $myiv = sprintf("%08x%06x%s", ($s & 0xffffffff), ($us & 0xffffff), scalar(reverse(substr($2, 14)))); #FIXME...
- my $key = Digest::MD5::md5($lgwPw);
- $hash->{'.crypto'}{cipher} = Crypt::Rijndael->new($key, Crypt::Rijndael::MODE_ECB());
- $hash->{'.crypto'}{encrypt}{keystream} = '';
- $hash->{'.crypto'}{encrypt}{ciphertext} = pack("H*", $2);
- $hash->{'.crypto'}{decrypt}{keystream} = '';
- $hash->{'.crypto'}{decrypt}{ciphertext} = pack("H*", $myiv);
- $msg = "V%02x,${myiv}\r\n";
- }
- } elsif ($line =~ m/^S(..),([^-]*)-/) {
- $hash->{DEVCNT} = hex($1);
- $hash->{CNT} = hex($1);
- if ($2 eq "BidCoS") {
- Log3($hash, 3, "HMUARTLGW ${name} BidCoS-port opened");
- } elsif ($2 eq "SysCom") {
- Log3($hash, 3, "HMUARTLGW ${name} KeepAlive-port opened");
- } else {
- Log3($hash, 1, "HMUARTLGW ${name} Unknown port identification received: ${2}, reopening");
- HMUARTLGW_Reopen($hash);
- return;
- }
- $msg = ">%02x,0000\r\n";
- delete($hash->{LGW_Init});
- }
- HMUARTLGW_sendAscii($hash, $msg) if ($msg);
- }
- $hash->{PARTIAL} = unpack("H*", $p);
- }
- #LGW KeepAlive
- sub HMUARTLGW_LGW_HandleKeepAlive($)
- {
- my ($hash) = @_;
- my $name = $hash->{NAME};
- my $p = pack("H*", $hash->{PARTIAL});
- while($p =~ m/\n/) {
- (my $line, $p) = split(/\n/, $p, 2);
- $line =~ s/\r$//;
- Log3($hash, HMUARTLGW_getVerbLvl($hash, undef, undef, 5), "HMUARTLGW ${name} read (".length($line)."): ${line}");
- my $msg;
- if ($line =~ m/^>L(..)/) {
- $hash->{DEVCNT} = hex($1);
- RemoveInternalTimer($hash);
- $hash->{DevState} = HMUARTLGW_STATE_KEEPALIVE_SENT;
- $msg = "K%02x\r\n";
- InternalTimer(gettimeofday()+HMUARTLGW_CMD_TIMEOUT, "HMUARTLGW_CheckCmdResp", $hash, 0);
- } elsif ($line =~ m/^>K(..)/) {
- $hash->{DEVCNT} = hex($1);
- RemoveInternalTimer($hash);
- $hash->{DevState} = HMUARTLGW_STATE_RUNNING;
- #now we have 15s
- $hash->{Helper}{NextKeepAlive} = gettimeofday() + HMUARTLGW_KEEPALIVE_SECONDS;
- InternalTimer($hash->{Helper}{NextKeepAlive}, "HMUARTLGW_SendKeepAlive", $hash, 0);
- }
- HMUARTLGW_sendAscii($hash, $msg) if ($msg);
- }
- $hash->{PARTIAL} = unpack("H*", $p);
- return;
- }
- sub HMUARTLGW_SendKeepAlive($)
- {
- my ($hash) = @_;
- my $name = $hash->{NAME};
- RemoveInternalTimer($hash);
- $hash->{DevState} = HMUARTLGW_STATE_KEEPALIVE_SENT;
- HMUARTLGW_sendAscii($hash, "K%02x\r\n");
- my $diff = gettimeofday() - $hash->{Helper}{NextKeepAlive};
- Log3($hash, 1, "HMUARTLGW ${name} KeepAlive sent " .
- sprintf("%.3f", $diff) .
- "s too late, this might cause a disconnect!")
- if ($diff > HMUARTLGW_KEEPALIVE_WARN_LATE_S);
- InternalTimer(gettimeofday()+HMUARTLGW_CMD_TIMEOUT, "HMUARTLGW_CheckCmdResp", $hash, 0);
- return;
- }
- sub HMUARTLGW_CheckCredits($)
- {
- my ($in) = shift;
- my (undef, $name) = split(':',$in);
- my $hash = $defs{$name};
- my $next = 15;
- if ($hash->{DevState} == HMUARTLGW_STATE_RUNNING) {
- Log3($hash, 5, "HMUARTLGW ${name} checking credits (from timer)");
- $hash->{Helper}{OneParameterOnly} = 1;
- if (++$hash->{Helper}{CreditTimer} % (4*60*2)) { #about every 2h
- $hash->{DevState} = HMUARTLGW_STATE_GET_CREDITS;
- } else {
- $hash->{DevState} = HMUARTLGW_STATE_SET_TIME;
- $next = 1;
- }
- HMUARTLGW_GetSetParameterReq($hash);
- } else {
- $next = 1;
- }
- RemoveInternalTimer("HMUARTLGW_CheckCredits:$name");
- InternalTimer(gettimeofday()+$next, "HMUARTLGW_CheckCredits", "HMUARTLGW_CheckCredits:$name", 0);
- }
- sub HMUARTLGW_SendPendingCmd($)
- {
- my ($hash) = @_;
- my $name = $hash->{NAME};
- if (defined($hash->{XmitOpen}) &&
- $hash->{XmitOpen} == 2) {
- if ($hash->{Helper}{PendingCMD}) {
- my $qLen = AttrVal($name, "qLen", 60);
- if (scalar(@{$hash->{Helper}{PendingCMD}}) < $qLen) {
- $hash->{XmitOpen} = 1;
- }
- } else {
- $hash->{XmitOpen} = 1;
- }
- }
- if ($hash->{DevState} == HMUARTLGW_STATE_RUNNING &&
- defined($hash->{Helper}{PendingCMD}) &&
- @{$hash->{Helper}{PendingCMD}}) {
- my $cmd = $hash->{Helper}{PendingCMD}->[0];
- if ($cmd->{cmd} eq "AESkeys") {
- Log3($hash, 5, "HMUARTLGW ${name} setting keys");
- $hash->{Helper}{OneParameterOnly} = 1;
- $hash->{DevState} = HMUARTLGW_STATE_SET_CURRENT_KEY;
- HMUARTLGW_GetSetParameterReq($hash);
- shift(@{$hash->{Helper}{PendingCMD}}); #retry will be handled by GetSetParameter
- } elsif ($cmd->{cmd} eq "Credits") {
- Log3($hash, 5, "HMUARTLGW ${name} checking credits (from send)");
- $hash->{Helper}{OneParameterOnly} = 1;
- $hash->{DevState} = HMUARTLGW_STATE_GET_CREDITS;
- HMUARTLGW_GetSetParameterReq($hash);
- shift(@{$hash->{Helper}{PendingCMD}}); #retry will be handled by GetSetParameter
- } elsif ($cmd->{cmd} eq "HMID") {
- Log3($hash, 5, "HMUARTLGW ${name} setting hmId");
- $hash->{Helper}{OneParameterOnly} = 1;
- $hash->{DevState} = HMUARTLGW_STATE_SET_HMID;
- HMUARTLGW_GetSetParameterReq($hash);
- shift(@{$hash->{Helper}{PendingCMD}}); #retry will be handled by GetSetParameter
- } elsif ($cmd->{cmd} eq "DutyCycle") {
- Log3($hash, 5, "HMUARTLGW ${name} Enabling/Disabling DutyCycle");
- $hash->{Helper}{OneParameterOnly} = 1;
- $hash->{DevState} = HMUARTLGW_STATE_ENABLE_CREDITS;
- HMUARTLGW_GetSetParameterReq($hash);
- shift(@{$hash->{Helper}{PendingCMD}}); #retry will be handled by GetSetParameter
- } elsif ($cmd->{cmd} eq "CSMACA") {
- Log3($hash, 5, "HMUARTLGW ${name} Enabling/Disabling CSMA/CA");
- $hash->{Helper}{OneParameterOnly} = 1;
- $hash->{DevState} = HMUARTLGW_STATE_ENABLE_CSMACA;
- HMUARTLGW_GetSetParameterReq($hash);
- shift(@{$hash->{Helper}{PendingCMD}}); #retry will be handled by GetSetParameter
- } elsif ($cmd->{cmd} eq "UpdateMode") {
- Log3($hash, 5, "HMUARTLGW ${name} Entering HM update mode (100k)");
- $hash->{Helper}{OneParameterOnly} = 1;
- $hash->{DevState} = HMUARTLGW_STATE_SET_UPDATE_MODE;
- HMUARTLGW_GetSetParameterReq($hash);
- shift(@{$hash->{Helper}{PendingCMD}}); #retry will be handled by GetSetParameter
- } elsif ($cmd->{cmd} eq "NormalMode") {
- Log3($hash, 5, "HMUARTLGW ${name} Entering HM normal mode (10k)");
- $hash->{Helper}{OneParameterOnly} = 1;
- $hash->{DevState} = HMUARTLGW_STATE_SET_NORMAL_MODE;
- HMUARTLGW_GetSetParameterReq($hash);
- shift(@{$hash->{Helper}{PendingCMD}}); #retry will be handled by GetSetParameter
- } else {
- #try for HMUARTLGW_SEND_RETRY_SECONDS, packet was not sent wirelessly yet!
- if (defined($cmd->{RetryStart}) &&
- $cmd->{RetryStart} + HMUARTLGW_SEND_RETRY_SECONDS <= gettimeofday()) {
- my $oldmsg = shift(@{$hash->{Helper}{PendingCMD}});
- Log3($hash, 1, "HMUARTLGW ${name} resend failed too often, dropping packet: 01 $oldmsg->{cmd}");
- #try next command
- return HMUARTLGW_SendPendingCmd($hash);
- } elsif ($cmd->{RetryStart}) {
- Log3($hash, 5, "HMUARTLGW ${name} Retry, initial retry initiated at: ".$cmd->{RetryStart});
- }
- RemoveInternalTimer($hash);
- my $dst = substr($cmd->{cmd}, 20, 6);
- if ((!defined($cmd->{delayed})) &&
- $modules{CUL_HM}{defptr}{$dst}{helper}{io}{nextSend}){
- my $tn = gettimeofday();
- my $dDly = $modules{CUL_HM}{defptr}{$dst}{helper}{io}{nextSend} - $tn;
- #$dDly -= 0.05 if ($typ eq "02");# delay at least 50ms for ACK, but not 100
- if ($dDly > 0.01) {
- Log3($hash, 5, "HMUARTLGW ${name} delaying send to ${dst} for ${dDly}");
- $hash->{DevState} = HMUARTLGW_STATE_SEND_TIMED;
- InternalTimer($tn + $dDly, "HMUARTLGW_SendPendingTimer", $hash, 0);
- $cmd->{delayed} = 1;
- return;
- }
- }
- delete($cmd->{delayed}) if (defined($cmd->{delayed}));
- if (hex(substr($cmd->{cmd}, 10, 2)) & (1 << 5)) { #BIDI
- InternalTimer(gettimeofday()+HMUARTLGW_SEND_TIMEOUT, "HMUARTLGW_CheckCmdResp", $hash, 0);
- $hash->{DevState} = HMUARTLGW_STATE_SEND;
- } else {
- Log3($hash, 5, "HMUARTLGW ${name} !BIDI");
- InternalTimer(gettimeofday()+0.3, "HMUARTLGW_CheckCmdResp", $hash, 0);
- $hash->{DevState} = HMUARTLGW_STATE_SEND_NOACK;
- }
- $cmd->{CNT} = HMUARTLGW_send($hash, $cmd->{cmd}, HMUARTLGW_DST_APP);
- }
- }
- }
- sub HMUARTLGW_SendPendingTimer($)
- {
- my ($hash) = @_;
- $hash->{DevState} = HMUARTLGW_STATE_RUNNING;
- return HMUARTLGW_SendPendingCmd($hash);
- }
- sub HMUARTLGW_SendCmd($$)
- {
- my ($hash, $cmd) = @_;
- #Drop commands when device is not active
- return if ($hash->{DevState} == HMUARTLGW_STATE_NONE);
- push @{$hash->{Helper}{PendingCMD}}, { cmd => $cmd };
- return HMUARTLGW_SendPendingCmd($hash);
- }
- sub HMUARTLGW_UpdatePeerReq($;$) {
- my ($hash, $peer) = @_;
- my $name = $hash->{NAME};
- $peer = $hash->{Helper}{UpdatePeer} if (!$peer);
- Log3($hash, 4, "HMUARTLGW ${name} UpdatePeerReq: ".$peer->{id}.", state ".$hash->{DevState});
- my $msg;
- if ($hash->{DevState} == HMUARTLGW_STATE_UPDATE_PEER) {
- $hash->{Helper}{UpdatePeer} = $peer;
- if ($peer->{operation} eq "+") {
- my $flags = hex($peer->{flags});
- $msg = HMUARTLGW_APP_ADD_PEER .
- $peer->{id} .
- $peer->{kNo} .
- (($flags & 0x02) ? "01" : "00") . #Wakeup?
- "00"; #setting this causes "0013" messages for thermostats on wakeup ?!
- } else {
- $msg = HMUARTLGW_APP_REMOVE_PEER . $peer->{id};
- }
- $hash->{Helper}{UpdatePeer}{msg} = $msg;
- } elsif ($hash->{DevState} == HMUARTLGW_STATE_UPDATE_PEER_AES1) {
- my $offset = 0;
- foreach my $c (reverse(unpack "(A2)*", $hash->{Helper}{UpdatePeer}{aes})) {
- $c = ~hex($c);
- for (my $chan = 0; $chan < 8; $chan++) {
- if ($c & (1 << $chan)) {
- Log3($hash, 4, "HMUARTLGW ${name} Disabling AES for channel " . ($chan+$offset));
- $msg .= sprintf("%02x", $chan+$offset);
- }
- }
- $offset += 8;
- }
- if (defined($msg)) {
- $msg = HMUARTLGW_APP_PEER_REMOVE_AES . $hash->{Helper}{UpdatePeer}{id} . ${msg};
- } else {
- return HMUARTLGW_GetSetParameters($hash);
- }
- } elsif ($hash->{DevState} == HMUARTLGW_STATE_UPDATE_PEER_AES2) {
- if ($peer->{operation} eq "+" && defined($peer->{aesChannels})) {
- Log3($hash, 4, "HMUARTLGW ${name} AESchannels: " . $peer->{aesChannels});
- my $offset = 0;
- foreach my $c (unpack "(A2)*", $peer->{aesChannels}) {
- $c = hex($c);
- for (my $chan = 0; $chan < 8; $chan++) {
- if ($c & (1 << $chan)) {
- Log3($hash, 4, "HMUARTLGW ${name} Enabling AES for channel " . ($chan+$offset));
- $msg .= sprintf("%02x", $chan+$offset);
- }
- }
- $offset += 8;
- }
- }
- if (defined($msg)) {
- $msg = HMUARTLGW_APP_PEER_ADD_AES . $peer->{id} . $msg;
- } else {
- return HMUARTLGW_GetSetParameters($hash);
- }
- } elsif ($hash->{DevState} == HMUARTLGW_STATE_UPDATE_PEER_CFG) {
- $msg = $hash->{Helper}{UpdatePeer}{msg};
- }
- if ($msg) {
- HMUARTLGW_send($hash, $msg, HMUARTLGW_DST_APP, $peer->{id});
- RemoveInternalTimer($hash);
- InternalTimer(gettimeofday()+HMUARTLGW_CMD_TIMEOUT, "HMUARTLGW_CheckCmdResp", $hash, 0);
- }
- }
- sub HMUARTLGW_UpdatePeer($$) {
- my ($hash, $peer) = @_;
- if ($hash->{DevState} == HMUARTLGW_STATE_RUNNING) {
- $hash->{DevState} = HMUARTLGW_STATE_UPDATE_PEER;
- HMUARTLGW_UpdatePeerReq($hash, $peer);
- } else {
- #enqueue for next update
- push @{$hash->{Helper}{PeerQueue}}, $peer;
- }
- }
- sub HMUARTLGW_UpdateQueuedPeer($) {
- my ($hash) = @_;
- if ($hash->{DevState} == HMUARTLGW_STATE_RUNNING &&
- $hash->{Helper}{PeerQueue} &&
- @{$hash->{Helper}{PeerQueue}}) {
- return HMUARTLGW_UpdatePeer($hash, shift(@{$hash->{Helper}{PeerQueue}}));
- }
- }
- sub HMUARTLGW_ParsePeer($$) {
- my ($hash, $msg) = @_;
- #040701010002fffffffffffffff9
- $hash->{AssignedPeerCnt} = hex(substr($msg, 8, 4));
- if (length($msg) > 12) {
- $hash->{Peers}{$hash->{Helper}{UpdatePeer}->{id}} = $hash->{Helper}{UpdatePeer}->{config};
- $hash->{Helper}{UpdatePeer}{aes} = substr($msg, 12);
- Log3($hash, HMUARTLGW_getVerbLvl($hash, $hash->{Helper}{UpdatePeer}->{id}, $hash->{Helper}{UpdatePeer}->{id}, 4),
- "HMUARTLGW $hash->{NAME} added peer: " . $hash->{Helper}{UpdatePeer}->{id} .
- ", aesChannels: " . $hash->{Helper}{UpdatePeer}{aes});
- } else {
- delete($hash->{Peers}{$hash->{Helper}{UpdatePeer}->{id}});
- Log3($hash, HMUARTLGW_getVerbLvl($hash, $hash->{Helper}{UpdatePeer}->{id}, $hash->{Helper}{UpdatePeer}->{id}, 4),
- "HMUARTLGW $hash->{NAME} remove peer: ". $hash->{Helper}{UpdatePeer}->{id});
- }
- }
- sub HMUARTLGW_GetSetParameterReq($) {
- my ($hash) = @_;
- my $name = $hash->{NAME};
- RemoveInternalTimer($hash);
- if ($hash->{DevState} == HMUARTLGW_STATE_SET_HMID) {
- my $hmId = AttrVal($name, "hmId", undef);
- if (!defined($hmId)) {
- $hash->{DevState} = HMUARTLGW_STATE_GET_HMID;
- return HMUARTLGW_GetSetParameterReq($hash);
- }
- HMUARTLGW_send($hash, HMUARTLGW_APP_SET_HMID . $hmId, HMUARTLGW_DST_APP);
- } elsif ($hash->{DevState} == HMUARTLGW_STATE_GET_HMID) {
- HMUARTLGW_send($hash, HMUARTLGW_APP_GET_HMID, HMUARTLGW_DST_APP);
- } elsif ($hash->{DevState} == HMUARTLGW_STATE_GET_DEFAULT_HMID) {
- HMUARTLGW_send($hash, HMUARTLGW_APP_DEFAULT_HMID, HMUARTLGW_DST_APP);
- } elsif ($hash->{DevState} == HMUARTLGW_STATE_SET_TIME) {
- my $tmsg = HMUARTLGW_OS_SET_TIME;
- my $t = time();
- my @l = localtime($t);
- my $off = (timegm(@l) - timelocal(@l)) / 1800;
- $tmsg .= sprintf("%04x%02x", $t, $off & 0xff);
- HMUARTLGW_send($hash, $tmsg, HMUARTLGW_DST_OS);
- } elsif ($hash->{DevState} == HMUARTLGW_STATE_GET_FIRMWARE) {
- HMUARTLGW_send($hash, HMUARTLGW_OS_GET_FIRMWARE, HMUARTLGW_DST_OS);
- } elsif ($hash->{DevState} == HMUARTLGW_STATE_GET_SERIAL) {
- HMUARTLGW_send($hash, HMUARTLGW_OS_GET_SERIAL, HMUARTLGW_DST_OS);
- } elsif ($hash->{DevState} == HMUARTLGW_STATE_SET_NORMAL_MODE) {
- HMUARTLGW_send($hash, HMUARTLGW_OS_NORMAL_MODE, HMUARTLGW_DST_OS);
- } elsif ($hash->{DevState} == HMUARTLGW_STATE_ENABLE_CSMACA) {
- my $csma_ca = AttrVal($name, "csmaCa", 0);
- HMUARTLGW_send($hash, HMUARTLGW_OS_ENABLE_CSMACA . sprintf("%02x", $csma_ca), HMUARTLGW_DST_OS);
- } elsif ($hash->{DevState} == HMUARTLGW_STATE_ENABLE_CREDITS) {
- my $dutyCycle = AttrVal($name, "dutyCycle", 1);
- HMUARTLGW_send($hash, HMUARTLGW_OS_ENABLE_CREDITS . sprintf("%02x", $dutyCycle), HMUARTLGW_DST_OS);
- } elsif ($hash->{DevState} == HMUARTLGW_STATE_GET_INIT_CREDITS) {
- HMUARTLGW_send($hash, HMUARTLGW_OS_GET_CREDITS, HMUARTLGW_DST_OS);
- } elsif ($hash->{DevState} == HMUARTLGW_STATE_SET_CURRENT_KEY) {
- #current key is key with highest idx
- @{$hash->{Helper}{AESKeyQueue}} = HMUARTLGW_getAesKeys($hash);
- my $key = shift(@{$hash->{Helper}{AESKeyQueue}});
- HMUARTLGW_send($hash, HMUARTLGW_APP_SET_CURRENT_KEY . ($key?$key:"00"x17), HMUARTLGW_DST_APP);
- } elsif ($hash->{DevState} == HMUARTLGW_STATE_SET_PREVIOUS_KEY) {
- #previous key has second highest index
- my $key = shift(@{$hash->{Helper}{AESKeyQueue}});
- HMUARTLGW_send($hash, HMUARTLGW_APP_SET_PREVIOUS_KEY . ($key?$key:"00"x17), HMUARTLGW_DST_APP);
- } elsif ($hash->{DevState} == HMUARTLGW_STATE_SET_TEMP_KEY) {
- #temp key has third highest index
- my $key = shift(@{$hash->{Helper}{AESKeyQueue}});
- delete($hash->{Helper}{AESKeyQueue});
- HMUARTLGW_send($hash, HMUARTLGW_APP_SET_TEMP_KEY . ($key?$key:"00"x17), HMUARTLGW_DST_APP);
- } elsif ($hash->{DevState} >= HMUARTLGW_STATE_UPDATE_PEER &&
- $hash->{DevState} <= HMUARTLGW_STATE_UPDATE_PEER_CFG) {
- HMUARTLGW_UpdatePeerReq($hash);
- return;
- } elsif ($hash->{DevState} == HMUARTLGW_STATE_GET_CREDITS) {
- $hash->{Helper}{RoundTrip}{Calc} = 1;
- HMUARTLGW_send($hash, HMUARTLGW_OS_GET_CREDITS, HMUARTLGW_DST_OS);
- } elsif ($hash->{DevState} == HMUARTLGW_STATE_SET_UPDATE_MODE) {
- #E9CA is magic
- HMUARTLGW_send($hash, HMUARTLGW_OS_UPDATE_MODE . "E9CA", HMUARTLGW_DST_OS);
- } else {
- return;
- }
- InternalTimer(gettimeofday()+HMUARTLGW_CMD_TIMEOUT, "HMUARTLGW_CheckCmdResp", $hash, 0);
- }
- sub HMUARTLGW_GetSetParameters($;$$)
- {
- my ($hash, $msg, $recvtime) = @_;
- my $name = $hash->{NAME};
- my $oldState = $hash->{DevState};
- my $hmId = AttrVal($name, "hmId", undef);
- my $ack = substr($msg, 2, 2) if ($msg);
- RemoveInternalTimer($hash);
- Log3($hash, HMUARTLGW_getVerbLvl($hash, undef, undef, 5), "HMUARTLGW ${name} GetSet Ack: ${ack}, state ".$hash->{DevState}) if ($ack);
- Log3($hash, 1, "HMUARTLGW ${name} GetSet NACK: ${ack}, state ".$hash->{DevState}) if ($ack && $ack =~ m/^0400/);
- if ($ack && ($ack eq HMUARTLGW_ACK_EINPROGRESS)) {
- if (defined($hash->{Helper}{GetSetRetry}) &&
- $hash->{Helper}{GetSetRetry} > 10) {
- delete($hash->{Helper}{GetSetRetry});
- #Reboot device
- HMUARTLGW_send($hash, HMUARTLGW_OS_CHANGE_APP, HMUARTLGW_DST_OS);
- return;
- }
- $hash->{Helper}{GetSetRetry}++;
- #Retry
- InternalTimer(gettimeofday()+0.5, "HMUARTLGW_GetSetParameterReq", $hash, 0);
- return;
- }
- delete($hash->{Helper}{GetSetRetry});
- if ($hash->{DevState} == HMUARTLGW_STATE_GETSET_PARAMETERS) {
- if ($hmId) {
- $hash->{DevState} = HMUARTLGW_STATE_SET_HMID;
- } else {
- $hash->{DevState} = HMUARTLGW_STATE_GET_HMID;
- }
- } elsif ($hash->{DevState} == HMUARTLGW_STATE_SET_HMID) {
- $hash->{DevState} = HMUARTLGW_STATE_GET_HMID;
- } elsif ($hash->{DevState} == HMUARTLGW_STATE_GET_HMID) {
- if ($ack eq HMUARTLGW_ACK_WITH_MULTIPART_DATA) {
- readingsSingleUpdate($hash, "D-HMIdAssigned", uc(substr($msg, 8)), 1);
- $hash->{owner} = uc(substr($msg, 8));
- }
- $hash->{DevState} = HMUARTLGW_STATE_GET_DEFAULT_HMID;
- } elsif ($hash->{DevState} == HMUARTLGW_STATE_GET_DEFAULT_HMID) {
- if ($ack eq HMUARTLGW_ACK_WITH_MULTIPART_DATA) {
- readingsSingleUpdate($hash, "D-HMIdOriginal", uc(substr($msg, 8)), 1);
- }
- $hash->{DevState} = HMUARTLGW_STATE_SET_TIME;
- } elsif ($hash->{DevState} == HMUARTLGW_STATE_SET_TIME) {
- $hash->{DevState} = HMUARTLGW_STATE_GET_FIRMWARE;
- } elsif ($hash->{DevState} == HMUARTLGW_STATE_GET_FIRMWARE) {
- if ($ack eq HMUARTLGW_ACK_INFO) {
- my $fw = hex(substr($msg, 10, 2)).".".
- hex(substr($msg, 12, 2)).".".
- hex(substr($msg, 14, 2));
- $hash->{Helper}{FW} = hex((substr($msg, 10, 6)));
- $fw .= " (outdated)" if ($hash->{Helper}{FW} < 0x010401);
- readingsSingleUpdate($hash, "D-firmware", $fw, 1);
- }
- $hash->{DevState} = HMUARTLGW_STATE_SET_NORMAL_MODE;
- } elsif ($hash->{DevState} == HMUARTLGW_STATE_SET_NORMAL_MODE) {
- $hash->{DevState} = HMUARTLGW_STATE_GET_SERIAL;
- } elsif ($hash->{DevState} == HMUARTLGW_STATE_GET_SERIAL) {
- if ($ack eq HMUARTLGW_ACK_INFO && $hash->{DevType} eq "UART") {
- readingsSingleUpdate($hash, "D-serialNr", pack("H*", substr($msg, 4)), 1);
- }
- $hash->{DevState} = HMUARTLGW_STATE_ENABLE_CSMACA;
- } elsif ($hash->{DevState} == HMUARTLGW_STATE_ENABLE_CSMACA) {
- $hash->{DevState} = HMUARTLGW_STATE_ENABLE_CREDITS;
- } elsif ($hash->{DevState} == HMUARTLGW_STATE_ENABLE_CREDITS) {
- $hash->{DevState} = HMUARTLGW_STATE_GET_INIT_CREDITS;
- } elsif ($hash->{DevState} == HMUARTLGW_STATE_GET_INIT_CREDITS) {
- if ($ack eq HMUARTLGW_ACK_INFO) {
- HMUARTLGW_updateMsgLoad($hash, hex(substr($msg, 4)));
- }
- $hash->{DevState} = HMUARTLGW_STATE_SET_CURRENT_KEY;
- } elsif ($hash->{DevState} == HMUARTLGW_STATE_SET_CURRENT_KEY) {
- $hash->{DevState} = HMUARTLGW_STATE_SET_PREVIOUS_KEY;
- } elsif ($hash->{DevState} == HMUARTLGW_STATE_SET_PREVIOUS_KEY) {
- $hash->{DevState} = HMUARTLGW_STATE_SET_TEMP_KEY;
- } elsif ($hash->{DevState} == HMUARTLGW_STATE_SET_TEMP_KEY) {
- $hash->{DevState} = HMUARTLGW_STATE_RUNNING;
- } elsif ($hash->{DevState} == HMUARTLGW_STATE_GET_CREDITS) {
- if (defined($recvtime) &&
- defined($hash->{Helper}{AckPending}{$hash->{DEVCNT}}) &&
- defined($hash->{Helper}{RoundTrip}{Calc})) {
- delete($hash->{Helper}{RoundTrip}{Calc});
- my $delay = $recvtime - $hash->{Helper}{AckPending}{$hash->{DEVCNT}}->{time};
- $hash->{Helper}{RoundTrip}{Delay} = $delay if ($delay < 0.2);
- Log3($hash, HMUARTLGW_getVerbLvl($hash, undef, undef, 5), "HMUARTLGW ${name} roundtrip delay: " . sprintf("%.4f", ${delay}));
- }
- if ($ack eq HMUARTLGW_ACK_INFO) {
- HMUARTLGW_updateMsgLoad($hash, hex(substr($msg, 4)));
- }
- delete($hash->{Helper}{CreditFailed});
- $hash->{DevState} = HMUARTLGW_STATE_RUNNING;
- } elsif ($hash->{DevState} == HMUARTLGW_STATE_SET_UPDATE_MODE) {
- $hash->{DevState} = HMUARTLGW_STATE_RUNNING;
- }
- if ($hash->{DevState} == HMUARTLGW_STATE_RUNNING &&
- $oldState != HMUARTLGW_STATE_RUNNING &&
- (!$hash->{Helper}{OneParameterOnly})) {
- #Init sequence over, add known peers
- $hash->{AssignedPeerCnt} = 0;
- foreach my $peer (keys(%{$hash->{Peers}})) {
- if ($modules{CUL_HM}{defptr}{$peer} &&
- $modules{CUL_HM}{defptr}{$peer}{helper}{io}{newChn}) {
- my ($id, $flags, $kNo, $aesChannels) = split(/,/, $modules{CUL_HM}{defptr}{$peer}{helper}{io}{newChn});
- my $p = {
- id => substr($id, 1),
- operation => substr($id, 0, 1),
- flags => $flags,
- kNo => $kNo,
- aesChannels => $aesChannels,
- config => $modules{CUL_HM}{defptr}{$peer}{helper}{io}{newChn},
- };
- #enqueue for later
- if ($p->{operation} eq "+") {
- $hash->{Peers}{$peer} = "pending";
- push @{$hash->{Helper}{PeerQueue}}, $p;
- } else {
- delete($hash->{Peers}{$peer});
- }
- } else {
- delete($hash->{Peers}{$peer});
- }
- }
- #start credit checker
- RemoveInternalTimer("HMUARTLGW_CheckCredits:$name");
- InternalTimer(gettimeofday()+1, "HMUARTLGW_CheckCredits", "HMUARTLGW_CheckCredits:$name", 0);
- $hash->{Helper}{Initialized} = 1;
- HMUARTLGW_updateCondition($hash);
- }
- if ($hash->{DevState} == HMUARTLGW_STATE_UPDATE_PEER) {
- if ($ack eq HMUARTLGW_ACK_WITH_MULTIPART_DATA) {
- HMUARTLGW_ParsePeer($hash, $msg);
- } else {
- if ($hash->{Helper}{UpdatePeer}{operation} eq "+") {
- Log3($hash, 1, "HMUARTLGW ${name} Adding peer $hash->{Helper}{UpdatePeer}{id} failed! " .
- "You have probably forced an unknown aesKey for this device.");
- } else {
- Log3($hash, HMUARTLGW_getVerbLvl($hash, $hash->{Helper}{UpdatePeer}{id}, $hash->{Helper}{UpdatePeer}{id}, 4),
- "HMUARTLGW ${name} Removing peer $hash->{Helper}{UpdatePeer}{id} failed!");
- }
- $hash->{Helper}{UpdatePeer}{operation} = "";
- }
- if ($hash->{Helper}{UpdatePeer}{operation} eq "+") {
- $hash->{DevState} = HMUARTLGW_STATE_UPDATE_PEER_AES1;
- } else {
- delete($hash->{Helper}{UpdatePeer});
- $hash->{DevState} = HMUARTLGW_STATE_RUNNING;
- }
- } elsif ($hash->{DevState} == HMUARTLGW_STATE_UPDATE_PEER_AES1) {
- $hash->{DevState} = HMUARTLGW_STATE_UPDATE_PEER_AES2;
- } elsif ($hash->{DevState} == HMUARTLGW_STATE_UPDATE_PEER_AES2) {
- if ($hash->{Helper}{UpdatePeer}->{operation} eq "+") {
- $hash->{DevState} = HMUARTLGW_STATE_UPDATE_PEER_CFG;
- } else {
- delete($hash->{Helper}{UpdatePeer});
- $hash->{DevState} = HMUARTLGW_STATE_RUNNING;
- }
- } elsif ($hash->{DevState} == HMUARTLGW_STATE_UPDATE_PEER_CFG) {
- if ($ack eq HMUARTLGW_ACK_WITH_MULTIPART_DATA) {
- HMUARTLGW_ParsePeer($hash, $msg);
- }
- delete($hash->{Helper}{UpdatePeer});
- $hash->{DevState} = HMUARTLGW_STATE_RUNNING;
- }
- #Don't continue in state-machine if only one parameter should be
- #set/queried, SET_HMID is special, as we have to query it again
- #to update readings. SET_CURRENT_KEY is always followed by
- #SET_PREVIOUS_KEY and SET_TEMP_KEY.
- if ($hash->{Helper}{OneParameterOnly} &&
- $oldState != $hash->{DevState} &&
- $oldState != HMUARTLGW_STATE_SET_HMID &&
- $oldState != HMUARTLGW_STATE_SET_CURRENT_KEY &&
- $oldState != HMUARTLGW_STATE_SET_PREVIOUS_KEY) {
- $hash->{DevState} = HMUARTLGW_STATE_RUNNING;
- delete($hash->{Helper}{OneParameterOnly});
- }
- if ($hash->{DevState} != HMUARTLGW_STATE_RUNNING) {
- HMUARTLGW_GetSetParameterReq($hash);
- } else {
- HMUARTLGW_UpdateQueuedPeer($hash);
- HMUARTLGW_SendPendingCmd($hash);
- }
- }
- sub HMUARTLGW_Parse($$$$)
- {
- my ($hash, $msg, $dst, $recvtime) = @_;
- my $name = $hash->{NAME};
- my $recv;
- my $CULinfo = '';
- $hash->{RAWMSG} = $msg;
- Log3($hash, HMUARTLGW_getVerbLvl($hash, undef, undef, 5),
- "HMUARTLGW ${name} recv: ".sprintf("%02X", $dst)." ${msg}, state ".$hash->{DevState})
- if ($dst == HMUARTLGW_DST_OS || $dst == HMUARTLGW_DST_DUAL ||
- $dst == HMUARTLGW_DST_DUAL_ERR || ($msg !~ m/^05/ && $msg !~ m/^040[3C]/));
- #Minimally handle DualCopro-Firmware
- if ($dst == HMUARTLGW_DST_DUAL) {
- if (($msg =~ m/^00(.*)$/ || $msg =~ m/^0501(.*)$/) &&
- $hash->{DevState} <= HMUARTLGW_STATE_ENTER_APP) {
- if (pack("H*", $1) eq "DualCoPro_App") {
- $hash->{DevState} = HMUARTLGW_STATE_UNSUPPORTED_FW;
- readingsSingleUpdate($hash, "D-firmware", "unsupported", 1);
- HMUARTLGW_updateCondition($hash);
- RemoveInternalTimer($hash);
- Log3($hash, 0, "HMUARTLGW ${name} is running unsupported firmware, please install a supported version");
- }
- }
- return;
- }
- #Re-send commands for DualCopro Firmware
- if ($dst == HMUARTLGW_DST_DUAL_ERR) {
- if ($hash->{DevState} == HMUARTLGW_STATE_QUERY_APP) {
- Log3($hash, HMUARTLGW_getVerbLvl($hash, undef, undef, 4),
- "HMUARTLGW ${name} Re-sending app-query for unsupported firmware");
- HMUARTLGW_send($hash, HMUARTLGW_DUAL_GET_APP, HMUARTLGW_DST_DUAL);
- } elsif (defined($hash->{Helper}{AckPending}{$hash->{DEVCNT}}) &&
- $hash->{Helper}{AckPending}{$hash->{DEVCNT}}->{dst} == HMUARTLGW_DST_OS &&
- $hash->{Helper}{AckPending}{$hash->{DEVCNT}}->{cmd} eq HMUARTLGW_OS_CHANGE_APP) {
- Log3($hash, HMUARTLGW_getVerbLvl($hash, undef, undef, 4),
- "HMUARTLGW ${name} Re-sending switch to bootloader for unsupported firmare");
- HMUARTLGW_send($hash, HMUARTLGW_DUAL_CHANGE_APP, HMUARTLGW_DST_DUAL);
- }
- return;
- }
- if ($msg =~ m/^04/ &&
- $hash->{CNT} != $hash->{DEVCNT}) {
- if (defined($hash->{Helper}{AckPending}{$hash->{DEVCNT}})) {
- Log3($hash, HMUARTLGW_getVerbLvl($hash, undef, undef, 5),
- "HMUARTLGW ${name} got delayed ACK for request " .
- $hash->{DEVCNT}.": ".$hash->{Helper}{AckPending}{$hash->{DEVCNT}}->{dst} .
- " " . $hash->{Helper}{AckPending}{$hash->{DEVCNT}}->{cmd} .
- sprintf(" (%.3f", (gettimeofday() - $hash->{Helper}{AckPending}{$hash->{DEVCNT}}->{time})) .
- "s late)");
- delete($hash->{Helper}{AckPending}{$hash->{DEVCNT}});
- return;
- }
- #Firmware sometimes send additional ACK when receiving the
- #next frame from a device after a command, even if it has
- #already ACKed the command.
- Log3($hash, HMUARTLGW_getVerbLvl($hash, undef, undef, 5),
- "HMUARTLGW ${name} Ack with invalid/old counter received, dropping. We: $hash->{CNT}, device: $hash->{DEVCNT}, " .
- "state: $hash->{DevState}, msg: ${dst} ${msg}");
- return;
- }
- if ($msg =~ m/^04/ &&
- $hash->{DevState} >= HMUARTLGW_STATE_GETSET_PARAMETERS &&
- $hash->{DevState} < HMUARTLGW_STATE_RUNNING) {
- HMUARTLGW_GetSetParameters($hash, $msg, $recvtime);
- return;
- }
- if (defined($hash->{Helper}{RoundTrip}{Calc})) {
- #We have received another message while calculating delay.
- #This will skew the calculation, so don't do it now
- delete($hash->{Helper}{RoundTrip}{Calc});
- }
- if ($dst == HMUARTLGW_DST_OS) {
- if ($msg =~ m/^00(..)/) {
- my $running = pack("H*", substr($msg, 2));
- if ($hash->{DevState} <= HMUARTLGW_STATE_ENTER_APP) {
- Log3($hash, HMUARTLGW_getVerbLvl($hash, undef, undef, 4), "HMUARTLGW ${name} currently running ${running}");
- if ($running eq "Co_CPU_App") {
- $hash->{DevState} = HMUARTLGW_STATE_GETSET_PARAMETERS;
- RemoveInternalTimer($hash);
- InternalTimer(gettimeofday()+1, "HMUARTLGW_GetSetParameters", $hash, 0);
- } else {
- if ($hash->{DevState} == HMUARTLGW_STATE_QUERY_APP) {
- $hash->{DevState} = HMUARTLGW_STATE_ENTER_APP;
- HMUARTLGW_send($hash, HMUARTLGW_OS_CHANGE_APP, HMUARTLGW_DST_OS);
- RemoveInternalTimer($hash);
- InternalTimer(gettimeofday()+HMUARTLGW_CMD_TIMEOUT, "HMUARTLGW_CheckCmdResp", $hash, 0);
- } else {
- Log3($hash, 1, "HMUARTLGW ${name} failed to enter App!");
- }
- }
- } elsif ($hash->{DevState} > HMUARTLGW_STATE_ENTER_APP) {
- Log3($hash, 1, "HMUARTLGW ${name} unexpected info about ${running} received (module crashed?), reopening")
- if (!defined($hash->{FirmwareFile}));
- HMUARTLGW_Reopen($hash);
- return;
- }
- } elsif ($msg =~ m/^04(..)/) {
- my $ack = $1;
- if ($hash->{DevState} == HMUARTLGW_STATE_UPDATE_COPRO) {
- HMUARTLGW_updateCoPro($hash, $msg);
- return;
- }
- if ($ack eq HMUARTLGW_ACK_INFO && $hash->{DevState} == HMUARTLGW_STATE_QUERY_APP) {
- my $running = pack("H*", substr($msg, 4));
- Log3($hash, HMUARTLGW_getVerbLvl($hash, undef, undef, 4), "HMUARTLGW ${name} currently running ${running}");
- if ($running eq "Co_CPU_App") {
- #Reset module
- HMUARTLGW_send($hash, HMUARTLGW_OS_CHANGE_APP, HMUARTLGW_DST_OS);
- RemoveInternalTimer($hash);
- InternalTimer(gettimeofday()+HMUARTLGW_CMD_TIMEOUT, "HMUARTLGW_CheckCmdResp", $hash, 0);
- } else {
- if (defined($hash->{FirmwareFile}) && $hash->{FirmwareFile} ne "") {
- Log3($hash, 1, "HMUARTLGW ${name} starting firmware upgrade");
- $hash->{FirmwareBlock} = 0;
- $hash->{DevState} = HMUARTLGW_STATE_UPDATE_COPRO;
- HMUARTLGW_updateCondition($hash);
- HMUARTLGW_updateCoPro($hash, $msg);
- return;
- }
- $hash->{DevState} = HMUARTLGW_STATE_ENTER_APP;
- HMUARTLGW_send($hash, HMUARTLGW_OS_CHANGE_APP, HMUARTLGW_DST_OS);
- RemoveInternalTimer($hash);
- InternalTimer(gettimeofday()+HMUARTLGW_CMD_TIMEOUT, "HMUARTLGW_CheckCmdResp", $hash, 0);
- }
- } elsif ($ack eq HMUARTLGW_ACK_NACK && $hash->{DevState} == HMUARTLGW_STATE_ENTER_APP) {
- Log3($hash, 1, "HMUARTLGW ${name} application switch failed, application-firmware probably corrupted!");
- HMUARTLGW_Reopen($hash);
- return;
- }
- } elsif ($msg =~ m/^05(..)$/) {
- HMUARTLGW_updateMsgLoad($hash, hex($1));
- }
- } elsif ($dst == HMUARTLGW_DST_APP) {
- if ($msg =~ m/^04(..)(.*)$/) {
- my $ack = $1;
- my $oldMsg;
- if ($hash->{DevState} == HMUARTLGW_STATE_SEND ||
- $hash->{DevState} == HMUARTLGW_STATE_SEND_NOACK) {
- RemoveInternalTimer($hash);
- $hash->{DevState} = HMUARTLGW_STATE_RUNNING;
- $oldMsg = shift @{$hash->{Helper}{PendingCMD}};
- }
- if ($ack eq HMUARTLGW_ACK_WITH_RESPONSE ||
- $ack eq HMUARTLGW_ACK_WITH_RESPONSE_AES_OK) {
- $recv = $msg;
- } elsif ($ack eq HMUARTLGW_ACK_WITH_RESPONSE_AES_KO) {
- if ($2 =~ m/^FE/) { #challenge msg
- $recv = $msg;
- } elsif ($oldMsg) {
- #Need to produce our own "failed" challenge
- $recv = substr($msg, 0, 6) . "01" .
- substr($oldMsg->{cmd}, 8, 2) .
- "A002" .
- substr($oldMsg->{cmd}, 20, 6) .
- substr($oldMsg->{cmd}, 14, 6) .
- "04000000000000" .
- sprintf("%02X", hex(substr($msg, 4, 2))*2);
- }
- $CULinfo = "AESpending";
- } elsif ($ack eq HMUARTLGW_ACK_EINPROGRESS && $oldMsg) {
- Log3($hash, HMUARTLGW_getVerbLvl($hash, undef, undef, 5),
- "HMUARTLGW ${name} IO currently busy, trying again in a bit");
- if ($hash->{DevState} == HMUARTLGW_STATE_RUNNING) {
- $oldMsg->{RetryStart} = gettimeofday() if (!defined($oldMsg->{RetryStart}));
- RemoveInternalTimer($hash);
- unshift @{$hash->{Helper}{PendingCMD}}, $oldMsg;
- $hash->{DevState} = HMUARTLGW_STATE_SEND_TIMED;
- InternalTimer(gettimeofday()+(HMUARTLGW_BUSY_RETRY_MS / 1000), "HMUARTLGW_SendPendingTimer", $hash, 0);
- }
- return;
- } elsif ($ack eq HMUARTLGW_ACK_ENOCREDITS) {
- Log3($hash, 1, "HMUARTLGW ${name} IO in overload!");
- $hash->{XmitOpen} = 0;
- HMUARTLGW_updateCondition($hash);
- } elsif ($ack eq HMUARTLGW_ACK_ECSMACA && $oldMsg) {
- Log3($hash, HMUARTLGW_getVerbLvl($hash, undef, undef, 5),
- "HMUARTLGW ${name} can't send due to CSMA/CA, trying again in a bit");
- if ($hash->{DevState} == HMUARTLGW_STATE_RUNNING) {
- $oldMsg->{RetryStart} = gettimeofday() if (!defined($oldMsg->{RetryStart}));
- RemoveInternalTimer($hash);
- unshift @{$hash->{Helper}{PendingCMD}}, $oldMsg;
- $hash->{DevState} = HMUARTLGW_STATE_SEND_TIMED;
- InternalTimer(gettimeofday()+(HMUARTLGW_CSMACA_RETRY_MS / 1000), "HMUARTLGW_SendPendingTimer", $hash, 0);
- }
- return;
- } elsif ($ack eq HMUARTLGW_ACK_EUNKNOWN && $oldMsg) {
- Log3($hash, HMUARTLGW_getVerbLvl($hash, undef, undef, 5),
- "HMUARTLGW ${name} can't send due to unknown problem (no response?)");
- } else {
- Log3($hash, HMUARTLGW_getVerbLvl($hash, undef, undef, 5),
- "HMUARTLGW ${name} Ack: ${ack} ".(($2)?$2:""));
- $recv = $msg;
- }
- } elsif ($msg =~ m/^(05.*)$/) {
- $recv = $1;
- }
- if ($recv && $recv =~ m/^(..)(..)(..)(..)(..)(..)(..)(......)(......)(.*)$/) {
- my ($type, $status, $info, $rssi, $mNr, $flags, $cmd, $src, $dst, $payload) = ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10);
- Log3($hash, HMUARTLGW_getVerbLvl($hash, $src, $dst, 5),
- "HMUARTLGW ${name} recv: 01 ${type} ${status} ${info} ${rssi} msg: ${mNr} ${flags} ${cmd} ${src} ${dst} ${payload}");
- return if (!$hash->{Helper}{Initialized});
- $rssi = 0 - hex($rssi);
- my %addvals = (RAWMSG => $msg);
- if ($rssi < -1) {
- $addvals{RSSI} = $rssi;
- $hash->{RSSI} = $rssi;
- } else {
- $rssi = "";
- }
- my $dmsg;
- my $m = $mNr . $flags . $cmd . $src . $dst . $payload;
- if ($type eq HMUARTLGW_APP_ACK && $status eq HMUARTLGW_ACK_WITH_RESPONSE_AES_OK) {
- #Fake AES challenge for CUL_HM
- my $kNo = sprintf("%02X", (hex($info) * 2));
- my $c = "${mNr}A002${src}${dst}04000000000000${kNo}";
- $dmsg = sprintf("A%02X%s:AESpending:${rssi}:${name}", length($c)/2, uc($c));
- $CULinfo = "AESCom-ok";
- } elsif ($type eq HMUARTLGW_APP_RECV && ($status eq HMUARTLGW_RECV_RESP_WITH_AES_OK ||
- $status eq HMUARTLGW_RECV_TRIG_WITH_AES_OK)) {
- #Fake AES response for CUL_HM
- $dmsg = sprintf("A%02X%s:AESpending:${rssi}:${name}", length($m)/2, uc($m));
- $CULinfo = "AESCom-ok";
- } elsif ($type eq HMUARTLGW_APP_RECV && $status eq HMUARTLGW_RECV_RESP_WITH_AES_KO) {
- #Fake AES response for CUL_HM
- $dmsg = sprintf("A%02X%s:AESpending:${rssi}:${name}", length($m)/2, uc($m));
- $CULinfo = "AESCom-fail";
- }
- if ($dmsg) {
- Log3($hash, 5, "HMUARTLGW ${name} Dispatch: ${dmsg}");
- Dispatch($hash, $dmsg, \%addvals);
- }
- $dmsg = sprintf("A%02X%s:${CULinfo}:${rssi}:${name}", length($m)/2, uc($m));
- Log3($hash, 5, "HMUARTLGW ${name} Dispatch: ${dmsg}");
- my $wait = 0;
- if (!(hex($flags) & (1 << 5))) {
- #!BIDI
- $wait = 0.100;
- } else {
- $wait = 0.300;
- }
- $wait -= $hash->{Helper}{RoundTrip}{Delay} if (defined($hash->{Helper}{RoundTrip}{Delay}));
- $modules{CUL_HM}{defptr}{$src}{helper}{io}{nextSend} = $recvtime + $wait
- if ($modules{CUL_HM}{defptr}{$src} && $wait > 0);
- Dispatch($hash, $dmsg, \%addvals);
- }
- }
- if ($hash->{DevState} == HMUARTLGW_STATE_RUNNING) {
- HMUARTLGW_UpdateQueuedPeer($hash);
- HMUARTLGW_SendPendingCmd($hash);
- }
- return;
- }
- sub HMUARTLGW_Read($)
- {
- my ($hash) = @_;
- my $name = $hash->{NAME};
- my $recvtime = gettimeofday();
- my $buf = DevIo_SimpleRead($hash);
- return "" if (!defined($buf));
- $buf = HMUARTLGW_decrypt($hash, $buf) if ($hash->{'.crypto'});
- Log3($hash, 5, "HMUARTLGW ${name} read raw (".length($buf)."): ".unpack("H*", $buf));
- my $p = pack("H*", $hash->{PARTIAL}) . $buf;
- $hash->{PARTIAL} .= unpack("H*", $buf);
- return HMUARTLGW_LGW_Init($hash) if ($hash->{LGW_Init});
- return HMUARTLGW_LGW_HandleKeepAlive($hash) if ($hash->{DevType} eq "LGW-KeepAlive");
- #need at least one frame delimiter
- return if (!($p =~ m/\xfd/));
- #garbage in the beginning?
- if (!($p =~ m/^\xfd/)) {
- $p = substr($p, index($p, chr(0xfd)));
- }
- my $unprocessed;
- while (defined($p) && $p =~ m/^\xfd/) {
- $unprocessed = $p;
- (undef, my $frame, $p) = split(/\xfd/, $unprocessed, 3);
- $p = chr(0xfd) . $p if ($p);
- my $unescaped = '';
- my $unescape_next = 0;
- foreach my $byte (split(//, $frame)) {
- if (ord($byte) == 0xfc) {
- $unescape_next = 1;
- next;
- }
- if ($unescape_next) {
- $byte = chr(ord($byte)|0x80);
- $unescape_next = 0;
- }
- $unescaped .= $byte;
- }
- next if (length($unescaped) < 6); #len len dst cnt crc crc
- (my $len) = unpack("n", substr($unescaped, 0, 2));
- if (length($unescaped) > $len + 4) {
- Log3($hash, 1, "HMUARTLGW ${name} frame with wrong length received: ".length($unescaped).", should: ".($len + 4).": FD".uc(unpack("H*", $unescaped)));
- next;
- }
- next if (length($unescaped) < $len + 4); #short read
- my $crc = HMUARTLGW_crc16(chr(0xfd).$unescaped);
- if ($crc != 0x0000 &&
- $hash->{DevState} != HMUARTLGW_STATE_RUNNING &&
- defined($hash->{Helper}{LastSendLen})) {
- #When writing to the device while it prepares to write a frame to
- #the host, the device seems to initialize the crc with 0x827f or
- #0x8281 plus the length of the frame being received (firmware bug).
- foreach my $slen (reverse(@{$hash->{Helper}{LastSendLen}})) {
- $crc = HMUARTLGW_crc16(chr(0xfd).$unescaped, 0x827f + $slen);
- Log3($hash, 5, "HMUARTLGW ${name} invalid checksum received, recalculated with slen ${slen}: ${crc}");
- last if ($crc == 0x0000);
- $crc = HMUARTLGW_crc16(chr(0xfd).$unescaped, 0x8281 + $slen);
- Log3($hash, 5, "HMUARTLGW ${name} invalid checksum received, recalculated with slen ${slen}: ${crc}");
- last if ($crc == 0x0000);
- }
- }
- if ($crc != 0x0000) {
- Log3($hash, 1, "HMUARTLGW ${name} invalid checksum received, dropping frame (FD".uc(unpack("H*", $unescaped)).")!");
- undef($unprocessed);
- next;
- }
- Log3($hash, 5, "HMUARTLGW ${name} read (".length($unescaped)."): fd".unpack("H*", $unescaped)." crc OK");
- my $dst = ord(substr($unescaped, 2, 1));
- $hash->{DEVCNT} = ord(substr($unescaped, 3, 1));
- my $msg = uc(unpack("H*", substr($unescaped, 4, -2)));
- HMUARTLGW_Parse($hash, $msg, $dst, $recvtime);
- delete($hash->{Helper}{AckPending}{$hash->{DEVCNT}})
- if (($msg =~ m/^04/) &&
- defined($hash->{Helper}{AckPending}) &&
- defined($hash->{Helper}{AckPending}{$hash->{DEVCNT}}));
- undef($unprocessed);
- }
- if (defined($unprocessed)) {
- $hash->{PARTIAL} = unpack("H*", $unprocessed);
- } else {
- $hash->{PARTIAL} = '';
- }
- }
- sub HMUARTLGW_Write($$$)
- {
- my ($hash, $fn, $msg) = @_;
- my $name = $hash->{NAME};
- Log3($hash, 5, "HMUARTLGW ${name} HMUARTLGW_Write: ${msg}");
- if($msg =~ m/init:(......)/) {
- my $dst = $1;
- if ($modules{CUL_HM}{defptr}{$dst} &&
- $modules{CUL_HM}{defptr}{$dst}{helper}{io}{newChn}) {
- my ($id, $flags, $kNo, $aesChannels) = split(/,/, $modules{CUL_HM}{defptr}{$dst}{helper}{io}{newChn});
- my $peer = {
- id => substr($id, 1),
- operation => substr($id, 0, 1),
- flags => $flags,
- kNo => $kNo,
- aesChannels => $aesChannels,
- config => $modules{CUL_HM}{defptr}{$dst}{helper}{io}{newChn},
- };
- $hash->{Peers}{$peer->{id}} = "pending";
- HMUARTLGW_UpdatePeer($hash, $peer);
- }
- return;
- } elsif ($msg =~ m/remove:(......)/) {
- my $peer = {
- id => $1,
- operation => "-",
- };
- delete($hash->{Peers}{$peer->{id}});
- HMUARTLGW_UpdatePeer($hash, $peer);
- } elsif ($msg =~ m/^([+-])(.*)$/) {
- my ($id, $flags, $kNo, $aesChannels) = split(/,/, $msg);
- my $peer = {
- id => substr($id, 1),
- operation => substr($id, 0, 1),
- flags => $flags,
- kNo => $kNo,
- aesChannels => $aesChannels,
- config => $msg,
- };
- if ($peer->{operation} eq "+") {
- $hash->{Peers}{$peer->{id}} = "pending";
- } else {
- delete($hash->{Peers}{$peer->{id}});
- }
- HMUARTLGW_UpdatePeer($hash, $peer);
- return;
- } elsif ($msg =~ m/^writeAesKey:(.*)$/) {
- HMUARTLGW_writeAesKey($1);
- return;
- } elsif ($msg =~ /^G(..)$/) {
- my $speed = hex($1);
- if ($speed == 100) {
- HMUARTLGW_SendCmd($hash, "UpdateMode");
- } else {
- HMUARTLGW_SendCmd($hash, "NormalMode");
- }
- } elsif (length($msg) > 21) {
- my ($flags, $mtype,$src,$dst) = (substr($msg, 6, 2),
- substr($msg, 8, 2),
- substr($msg, 10, 6),
- substr($msg, 16, 6));
- if (!defined($hash->{owner}) ||
- !defined($hash->{Helper}{FW})) {
- Log3($hash, 1, "HMUARTLGW ${name}: Device not initialized (state: $hash->{DevState}, " .
- ReadingsVal($name, "cond", "").") but asked to send data. Dropping: ${msg}");
- return;
- }
- if ($mtype eq "02" && $src eq $hash->{owner} && length($msg) == 24 &&
- defined($hash->{Peers}{$dst})) {
- # Acks are generally send by HMUARTLGW autonomously
- # Special
- Log3($hash, 5, "HMUARTLGW ${name}: Skip ACK");
- return;
- } elsif ($mtype eq "02" && $src ne $hash->{owner} &&
- defined($hash->{Peers}{$dst})) {
- Log3($hash, 0, "HMUARTLGW ${name}: Can't send ACK not originating from my hmId (firmware bug), please use a VCCU virtual device!");
- return;
- } elsif ($flags eq "A1" && $mtype eq "12") {
- Log3($hash, 5, "HMUARTLGW ${name}: FIXME: filter out A112 message (it's automatically generated by the device)");
- #return;
- }
- my $qLen = AttrVal($name, "qLen", 60);
- #Queue full?
- if ($hash->{Helper}{PendingCMD} &&
- scalar(@{$hash->{Helper}{PendingCMD}}) >= $qLen) {
- if ($hash->{XmitOpen} == 2) {
- Log3($hash, 1, "HMUARTLGW ${name}: queue is full, dropping packet");
- return;
- } elsif ($hash->{XmitOpen} == 1) {
- $hash->{XmitOpen} = 2;
- }
- }
- if (!$hash->{Peers}{$dst} && $dst ne "000000"){
- #add id and enqueue command
- my $peer = {
- id => $dst,
- operation => "+",
- flags => "00",
- kNo => "00",
- config => "+${dst}",
- };
- if ($modules{CUL_HM}{defptr}{$dst} &&
- $modules{CUL_HM}{defptr}{$dst}{helper}{io}{newChn}) {
- my (undef, $flags, $kNo, $aesChannels) = split(/,/, $modules{CUL_HM}{defptr}{$dst}{helper}{io}{newChn});
- $peer->{flags} = $flags;
- $peer->{kNo} = $kNo;
- $peer->{aesChannels} = $aesChannels;
- $peer->{config} = $modules{CUL_HM}{defptr}{$dst}{helper}{io}{newChn};
- }
- $hash->{Peers}{$dst} = "pending";
- HMUARTLGW_UpdatePeer($hash, $peer);
- }
- my $cmd = HMUARTLGW_APP_SEND . "0000";
- if ($hash->{Helper}{FW} > 0x010006) { #TODO: Find real version which adds this
- $cmd .= ((hex(substr($msg, 6, 2)) & 0x10) ? "01" : "00");
- }
- $cmd .= substr($msg, 4);
- HMUARTLGW_SendCmd($hash, $cmd);
- HMUARTLGW_SendCmd($hash, "Credits") if ((++$hash->{Helper}{SendCnt} % 10) == 0);
- # Check queue again
- if ($hash->{Helper}{PendingCMD} &&
- scalar(@{$hash->{Helper}{PendingCMD}}) >= $qLen) {
- $hash->{XmitOpen} = 2 if ($hash->{XmitOpen} == 1);
- }
- } else {
- Log3($hash, 1, "HMUARTLGW ${name} write:${fn} ${msg}");
- }
- return;
- }
- sub HMUARTLGW_StartInit($)
- {
- my ($hash) = @_;
- my $name = $hash->{NAME};
- if ($hash->{LGW_Init}) {
- if ($hash->{LGW_Init} >= 10) {
- Log3($hash, 1, "HMUARTLGW ${name} LGW init did not complete after 10s".($hash->{'.crypto'}?", probably wrong password":""));
- HMUARTLGW_Reopen($hash);
- return;
- }
- $hash->{LGW_Init}++;
- RemoveInternalTimer($hash);
- InternalTimer(gettimeofday()+1, "HMUARTLGW_StartInit", $hash, 0);
- return;
- }
- Log3($hash, 4, "HMUARTLGW ${name} StartInit");
- RemoveInternalTimer($hash);
- InternalTimer(gettimeofday()+HMUARTLGW_CMD_TIMEOUT, "HMUARTLGW_CheckCmdResp", $hash, 0);
- if ($hash->{DevType} eq "LGW-KeepAlive") {
- $hash->{DevState} = HMUARTLGW_STATE_KEEPALIVE_INIT;
- HMUARTLGW_sendAscii($hash, "L%02x,02,00ff,00\r\n");
- return;
- }
- $hash->{DevState} = HMUARTLGW_STATE_QUERY_APP;
- HMUARTLGW_send($hash, HMUARTLGW_OS_GET_APP, HMUARTLGW_DST_OS);
- HMUARTLGW_updateCondition($hash);
- return;
- }
- sub HMUARTLGW_CheckCmdResp($)
- {
- my ($hash) = @_;
- my $name = $hash->{NAME};
- RemoveInternalTimer($hash);
- #The data we wait for might have already been received but never
- #read from the FD. Do a last check now and process new data.
- if (defined($hash->{FD})) {
- my $rin = '';
- vec($rin, $hash->{FD}, 1) = 1;
- my $n = select($rin, undef, undef, 0);
- if ($n > 0) {
- Log3($hash, HMUARTLGW_getVerbLvl($hash, undef, undef, 5),
- "HMUARTLGW ${name} HMUARTLGW_CheckCmdResp: FD is readable, this might be the data we are looking for!");
- #We will be back very soon!
- InternalTimer(gettimeofday()+0, "HMUARTLGW_CheckCmdResp", $hash, 0);
- HMUARTLGW_Read($hash);
- return;
- }
- }
- if ($hash->{DevState} == HMUARTLGW_STATE_SEND) {
- $hash->{Helper}{PendingCMD}->[0]->{RetryStart} = gettimeofday()
- if (!defined($hash->{Helper}{PendingCMD}->[0]->{RetryStart}));
- $hash->{DevState} = HMUARTLGW_STATE_RUNNING;
- return HMUARTLGW_SendPendingCmd($hash);
- } elsif ($hash->{DevState} == HMUARTLGW_STATE_SEND_NOACK) {
- shift(@{$hash->{Helper}{PendingCMD}});
- $hash->{DevState} = HMUARTLGW_STATE_RUNNING;
- #try next command
- return HMUARTLGW_SendPendingCmd($hash);
- } elsif ($hash->{DevState} == HMUARTLGW_STATE_GET_CREDITS &&
- (!defined($hash->{Helper}{CreditFailed}) || ($hash->{Helper}{CreditFailed} < 3))) {
- $hash->{Helper}{CreditFailed}++;
- $hash->{DevState} = HMUARTLGW_STATE_RUNNING;
- RemoveInternalTimer("HMUARTLGW_CheckCredits:$name");
- InternalTimer(gettimeofday()+1, "HMUARTLGW_CheckCredits", "HMUARTLGW_CheckCredits:$name", 0);
- } elsif ($hash->{DevState} != HMUARTLGW_STATE_RUNNING) {
- if ((!defined($hash->{Helper}{AckPending}{$hash->{CNT}}{frame})) ||
- (defined($hash->{Helper}{AckPending}{$hash->{CNT}}{resend}) &&
- $hash->{Helper}{AckPending}{$hash->{CNT}}{resend} >= HMUARTLGW_CMD_RETRY_CNT)) {
- Log3($hash, 1, "HMUARTLGW ${name} did not respond after all, reopening");
- HMUARTLGW_Reopen($hash);
- } else {
- $hash->{Helper}{AckPending}{$hash->{CNT}}{resend}++;
- Log3($hash, 1, "HMUARTLGW ${name} did not respond for the " .
- $hash->{Helper}{AckPending}{$hash->{CNT}}{resend} .
- ". time, resending");
- HMUARTLGW_send_frame($hash, pack("H*", $hash->{Helper}{AckPending}{$hash->{CNT}}{frame}));
- InternalTimer(gettimeofday()+HMUARTLGW_CMD_TIMEOUT, "HMUARTLGW_CheckCmdResp", $hash, 0);
- }
- }
- return;
- }
- sub HMUARTLGW_Get($@)
- {
- my ( $hash, $name, $cmd, @args ) = @_;
- my $ret = "";
- return "Unknown argument ${cmd}, choose one of " if ($hash->{DevType} eq "LGW-KeepAlive");
- if ($cmd eq "assignIDs") {
- foreach my $peer (keys(%{$hash->{Peers}})) {
- next if ($hash->{Peers}{$peer} !~ m/^\+/);
- $ret .= "\n${peer} : " . CUL_HM_id2Name($peer);
- }
- $ret = "assignedIDs: ". ($ret =~ tr/\n//) . $ret;
- } else {
- $ret = "Unknown argument ${cmd}, choose one of " .
- join(" ",map {"$_" . ($gets{$_} ? ":$gets{$_}" : "")} keys %gets);
- }
- return $ret;
- }
- sub HMUARTLGW_RemoveHMPair($)
- {
- my ($in) = shift;
- my (undef,$name) = split(':',$in);
- my $hash = $defs{$name};
- RemoveInternalTimer("hmPairForSec:$name");
- Log3($hash, 3, "HMUARTLGW ${name} left pairing-mode") if ($hash->{hmPair});
- delete($hash->{hmPair});
- delete($hash->{hmPairSerial});
- }
- sub HMUARTLGW_Set($@)
- {
- my ($hash, $name, $cmd, @a) = @_;
- my $arg = join(" ", @a);
- return "\"set\" needs at least one parameter" if (!$cmd);
- return "Unknown argument ${cmd}, choose one of " if ($hash->{DevType} eq "LGW-KeepAlive");
- if ($cmd eq "hmPairForSec") {
- $arg = 60 if(!$arg || $arg !~ m/^\d+$/);
- HMUARTLGW_RemoveHMPair("hmPairForSec:$name");
- $hash->{hmPair} = 1;
- InternalTimer(gettimeofday()+$arg, "HMUARTLGW_RemoveHMPair", "hmPairForSec:$name", 0);
- Log3($hash, 3, "HMUARTLGW ${name} entered pairing-mode");
- } elsif ($cmd eq "hmPairSerial") {
- return "Usage: set $name hmPairSerial <10-character-serialnumber>"
- if(!$arg || $arg !~ m/^.{10}$/);
- my $id = InternalVal($hash->{NAME}, "owner", "123456");
- $hash->{HM_CMDNR} = $hash->{HM_CMDNR} ? ($hash->{HM_CMDNR}+1)%256 : 1;
- HMUARTLGW_Write($hash, undef, sprintf("As15%02X8401%s000000010A%s",
- $hash->{HM_CMDNR}, $id, unpack('H*', $arg)));
- HMUARTLGW_RemoveHMPair("hmPairForSec:$name");
- $hash->{hmPair} = 1;
- $hash->{hmPairSerial} = $arg;
- InternalTimer(gettimeofday()+20, "HMUARTLGW_RemoveHMPair", "hmPairForSec:".$name, 0);
- } elsif ($cmd eq "reopen") {
- HMUARTLGW_Reopen($hash);
- } elsif ($cmd eq "close") {
- #switch to bootloader to stop the module from interfering
- HMUARTLGW_send($hash, HMUARTLGW_OS_CHANGE_APP, HMUARTLGW_DST_OS)
- if ($hash->{DevState} > HMUARTLGW_STATE_ENTER_APP);
- HMUARTLGW_Undefine($hash, $name);
- readingsSingleUpdate($hash, "state", "closed", 1);
- $hash->{XmitOpen} = 0;
- } elsif ($cmd eq "open") {
- DevIo_OpenDev($hash, 0, "HMUARTLGW_DoInit", \&HMUARTLGW_Connect);
- } elsif ($cmd eq "restart") {
- HMUARTLGW_send($hash, HMUARTLGW_OS_CHANGE_APP, HMUARTLGW_DST_OS);
- } elsif ($cmd eq "updateCoPro") {
- return "Usage: set $name updateCoPro </path/to/firmware.eq3>"
- if(!$arg);
-
- my $block = HMUARTLGW_firmwareGetBlock($hash, $arg, 0);
- return "${arg} is not a valid firmware file!"
- if (!defined($block) || $block eq "");
- $hash->{FirmwareFile} = $arg;
- HMUARTLGW_send($hash, HMUARTLGW_OS_CHANGE_APP, HMUARTLGW_DST_OS);
- } else {
- return "Unknown argument ${cmd}, choose one of " .
- join(" ",map {"$_" . ($sets{$_} ? ":$sets{$_}" : "")} keys %sets);
- }
- return undef;
- }
- sub HMUARTLGW_Attr(@)
- {
- my ($cmd, $name, $aName, $aVal) = @_;
- my $hash = $defs{$name};
- my $retVal;
- Log3($hash, 5, "HMUARTLGW ${name} Attr ${cmd} ${aName} ".(($aVal)?$aVal:""));
- return "Attribute ${cmd} not supported on keepAlive-subdevice" if ($hash->{DevType} eq "LGW-KeepAlive");
- if ($aName eq "hmId") {
- if ($cmd eq "set") {
- my $owner_ccu = InternalVal($name, "owner_CCU", undef);
- return "device owned by $owner_ccu" if ($owner_ccu);
- return "wrong syntax: hmId must be 6-digit-hex-code (3 byte)"
- if ($aVal !~ m/^[A-F0-9]{6}$/i);
- $attr{$name}{$aName} = $aVal;
- if ($init_done) {
- HMUARTLGW_SendCmd($hash, "HMID");
- }
- }
- } elsif ($aName eq "lgwPw") {
- if ($init_done) {
- if ($hash->{DevType} eq "LGW") {
- HMUARTLGW_Reopen($hash);
- }
- }
- } elsif ($aName =~ m/^hmKey(.?)$/) {
- if ($cmd eq "set") {
- my $kNo = 1;
- $kNo = $1 if ($1);
- my ($no,$val) = (sprintf("%02X",$kNo),$aVal);
- if ($aVal =~ m/:/){#number given
- ($no,$val) = split ":",$aVal;
- return "illegal number:$no" if (hex($no) < 1 || hex($no) > 255 || length($no) != 2);
- }
- $attr{$name}{$aName} = "$no:".
- (($val =~ m /^[0-9A-Fa-f]{32}$/ )
- ? $val
- : unpack('H*', Digest::MD5::md5($val)));
- $retVal = "$aName set to $attr{$name}{$aName}"
- if($aVal ne $attr{$name}{$aName});
- } else {
- delete $attr{$name}{$aName};
- }
- HMUARTLGW_writeAesKey($name) if ($init_done);
- } elsif ($aName eq "dutyCycle") {
- if ($cmd eq "set") {
- return "wrong syntax: dutyCycle must be 1 or 0"
- if ($aVal !~ m/^[01]$/);
- $attr{$name}{$aName} = $aVal;
- #$retVal = "Please make sure to be in compliance with local regulations when disabling dutyCycle!"
- # if (!($aVal));
- } else {
- delete $attr{$name}{$aName};
- }
- if ($init_done) {
- HMUARTLGW_SendCmd($hash, "DutyCycle");
- }
- } elsif ($aName eq "csmaCa") {
- if ($cmd eq "set") {
- return "wrong syntax: csmaCa must be 1 or 0"
- if ($aVal !~ m/^[01]$/);
- $attr{$name}{$aName} = $aVal;
- } else {
- delete $attr{$name}{$aName};
- }
- if ($init_done) {
- HMUARTLGW_SendCmd($hash, "CSMACA");
- }
- } elsif ($aName eq "qLen") {
- if ($cmd eq "set") {
- return "wrong syntax: qLen must be between 1 and 200"
- if ($aVal !~ m/^\d+$/ || $aVal < 1 || $aVal > 200);
- $attr{$name}{$aName} = $aVal;
- } else {
- delete $attr{$name}{$aName};
- }
- } elsif ($aName eq "logIDs") {
- if ($cmd eq "set") {
- my @ids = split(/,/, $aVal);
- $hash->{Helper}{Log}{IDs} = \@ids;
- $hash->{Helper}{Log}{Resolve} = 1;
- $attr{$name}{$aName} = $aVal;
- } else {
- delete $attr{$name}{$aName};
- delete $hash->{Helper}{Log};
- }
- } elsif ($aName eq "verbose") {
- if ($hash->{keepAlive}) {
- if ($cmd eq "set") {
- $attr{$hash->{keepAlive}->{NAME}}{$aName} = $aVal;
- } else {
- delete $attr{$hash->{keepAlive}->{NAME}}{$aName};
- }
- }
- } elsif ($aName eq "dummy") {
- if ($cmd eq "set") {
- if (!defined($attr{$name}{$aName})) {
- #switch to bootloader to stop the module from interfering
- HMUARTLGW_send($hash, HMUARTLGW_OS_CHANGE_APP, HMUARTLGW_DST_OS)
- if ($hash->{DevState} > HMUARTLGW_STATE_ENTER_APP);
- HMUARTLGW_Undefine($hash, $name);
- readingsSingleUpdate($hash, "state", "dummy", 1);
- HMUARTLGW_updateCondition($hash);
- $hash->{XmitOpen} = 0;
- }
- } else {
- if (defined($attr{$name}{$aName})) {
- delete $attr{$name}{$aName};
- DevIo_OpenDev($hash, 0, "HMUARTLGW_DoInit", \&HMUARTLGW_Connect);
- }
- }
- } elsif ($aName eq "loadEvents") {
- if ($cmd eq "set") {
- return "wrong syntax: loadEvents must be 1 or 0"
- if ($aVal !~ m/^[01]$/);
- $attr{$name}{$aName} = $aVal;
- } else {
- delete $attr{$name}{$aName};
- }
- }
- return $retVal;
- }
- sub HMUARTLGW_getAesKeys($) {
- my ($hash) = @_;
- my $name = $hash->{NAME};
- my @k;
- my %keys = ();
- my $vccu = InternalVal($name,"owner_CCU",$name);
- $vccu = $name if(!AttrVal($vccu,"hmKey",""));
- foreach my $i (1..3){
- my ($kNo,$k) = split(":",AttrVal($vccu,"hmKey".($i== 1?"":$i),""));
- if (defined($kNo) && defined($k)) {
- $keys{$kNo} = $k;
- }
- }
- my @kNos = reverse(sort(keys(%keys)));
- foreach my $kNo (@kNos) {
- Log3($hash, 4, "HMUARTLGW ${name} key: ".$keys{$kNo}.", idx: ".$kNo);
- push @k, $keys{$kNo} . $kNo;
- }
- return @k;
- }
- sub HMUARTLGW_writeAesKey($) {
- my ($name) = @_;
- return if (!$name || !$defs{$name} || $defs{$name}{TYPE} ne "HMUARTLGW");
- my $hash = $defs{$name};
- HMUARTLGW_SendCmd($hash, "AESkeys");
- HMUARTLGW_SendPendingCmd($hash);
- }
- sub HMUARTLGW_updateCondition($)
- {
- my ($hash) = @_;
- my $name = $hash->{NAME};
- my $cond = "disconnected";
- my $loadLvl = "suspended";
- my $oldLoad = ReadingsVal($name, "load", -1);
- if (defined($hash->{msgLoadCurrent})) {
- my $load = $hash->{msgLoadCurrent};
- readingsSingleUpdate($hash, "load", $load, AttrVal($name, "loadEvents", 0));
- $cond = "ok";
- #FIXME: Dynamic levels
- if ($load >= 100) {
- $cond = "ERROR-Overload";
- $loadLvl = "suspended";
- } elsif ($oldLoad >= 100) {
- $cond = "Overload-released";
- $loadLvl = "high";
- } elsif ($load >= 90) {
- $cond = "Warning-HighLoad";
- $loadLvl = "high";
- } elsif ($load >= 40) {
- #FIXME: batchLevel != 40 needs to be in {helper}{loadLvl}{bl}
- $loadLvl = "batchLevel";
- } else {
- $loadLvl = "low";
- }
- }
- if ((!defined($hash->{XmitOpen})) || $hash->{XmitOpen} == 0) {
- $cond = "ERROR-Overload";
- $loadLvl = "suspended";
- }
- if (!defined($hash->{Helper}{Initialized})) {
- $cond = "init";
- $loadLvl = "suspended";
- }
- if ($hash->{DevState} == HMUARTLGW_STATE_NONE) {
- $cond = "disconnected";
- $loadLvl = "suspended";
- } elsif ($hash->{DevState} == HMUARTLGW_STATE_UPDATE_COPRO) {
- $cond = "fwupdate";
- $loadLvl = "suspended";
- } elsif ($hash->{DevState} == HMUARTLGW_STATE_UNSUPPORTED_FW) {
- $cond = "unsupported firmware";
- $loadLvl = "suspended";
- }
- if ((defined($cond) && $cond ne ReadingsVal($name, "cond", "")) ||
- (defined($loadLvl) && $loadLvl ne ReadingsVal($name, "loadLvl", ""))) {
- readingsBeginUpdate($hash);
- readingsBulkUpdate($hash, "cond", $cond)
- if (defined($cond) && $cond ne ReadingsVal($name, "cond", ""));
- readingsBulkUpdate($hash, "loadLvl", $loadLvl)
- if (defined($loadLvl) && $loadLvl ne ReadingsVal($name, "loadLvl", ""));
- readingsEndUpdate($hash, 1);
- my $ccu = InternalVal($name,"owner_CCU","");
- CUL_HM_UpdtCentralState($ccu) if ($ccu);
- }
- }
- sub HMUARTLGW_updateMsgLoad($$) {
- my ($hash, $load) = @_;
- if ($hash->{XmitOpen} != 2) {
- if ($load >= 199) {
- $hash->{XmitOpen} = 0;
- } else {
- $hash->{XmitOpen} = 1;
- }
- }
- my $adjustedLoad = int(($load + 1) / 2);
- my $histSlice = 5 * 60;
- my $histNo = 3600 / $histSlice;
- if ((!defined($hash->{Helper}{loadLvl}{lastHistory})) ||
- ($hash->{Helper}{loadLvl}{lastHistory} + $histSlice) <= gettimeofday()) {
- my @abshist = ("-") x $histNo;
- unshift @abshist, split("/", $hash->{msgLoadHistoryAbs}) if (defined($hash->{msgLoadHistoryAbs}));
- unshift @abshist, $adjustedLoad;
- my $last;
- my @hist = ("-") x $histNo;
- foreach my $l (reverse(@abshist)) {
- next if ($l eq "-");
- unshift @hist, $l - $last if (defined($last));
- $last = $l;
- }
- $hash->{msgLoadHistory} = join("/", @hist[0..($histNo - 1)]);
- $hash->{msgLoadHistoryAbs} = join("/", @abshist[0..($histNo)]);
- if (!defined($hash->{Helper}{loadLvl}{lastHistory})) {
- $hash->{Helper}{loadLvl}{lastHistory} = gettimeofday();
- } else {
- $hash->{Helper}{loadLvl}{lastHistory} += $histSlice;
- }
- }
- if ((!defined($hash->{msgLoadCurrent})) ||
- $hash->{msgLoadCurrent} != $adjustedLoad) {
- $hash->{msgLoadCurrent} = $adjustedLoad;
- HMUARTLGW_updateCondition($hash);
- }
- }
- sub HMUARTLGW_send($$$;$)
- {
- my ($hash, $msg, $dst, $peer) = @_;
- my $name = $hash->{NAME};
- my $log;
- my $v;
- if ($dst == HMUARTLGW_DST_APP && uc($msg) =~ m/^(02)(..)(..)(.*)$/) {
- $log = "01 ${1} ${2} ${3} ";
- my $m = $4;
- if ($hash->{Helper}{FW} > 0x010006) {
- $log .= substr($m, 0, 2, '') . " ";
- } else {
- $log .= "XX ";
- }
- if ($m =~ m/^(..)(..)(..)(......)(......)(.*)$/) {
- $log .= "msg: ${1} ${2} ${3} ${4} ${5} ${6}";
- } else {
- $log .= $m;
- }
- $v = HMUARTLGW_getVerbLvl($hash, $4, $5, 5);
- } elsif ($dst == HMUARTLGW_DST_APP && uc($msg) =~ m/^(0[3BF]).*[^0].*(..)$/) {
- #Key, do not log
- $log = sprintf("%02X", $dst). " ${1}" . ("XX"x16) . $2;
- $v = HMUARTLGW_getVerbLvl($hash, undef, undef, 5);
- } else {
- $log = sprintf("%02X", $dst). " ".uc($msg);
- $v = HMUARTLGW_getVerbLvl($hash, $peer, $peer, 5);
- }
- Log3($hash, $v, "HMUARTLGW ${name} send: ${log}");
- $hash->{CNT} = ($hash->{CNT} + 1) & 0xff;
- my $frame = pack("CnCCH*", 0xfd,
- (length($msg) / 2) + 2,
- $dst,
- $hash->{CNT},
- $msg);
- $frame .= pack("n", HMUARTLGW_crc16($frame));
- my $sendtime = HMUARTLGW_send_frame($hash, $frame);
- if (defined($hash->{Helper}{AckPending}{$hash->{CNT}})) {
- Log3($hash, HMUARTLGW_getVerbLvl($hash, undef, undef, 5),
- "HMUARTLGW ${name} never got an ACK for request ".
- $hash->{CNT}.": ".$hash->{Helper}{AckPending}{$hash->{CNT}}->{dst} .
- " " . $hash->{Helper}{AckPending}{$hash->{CNT}}->{cmd} .
- sprintf(" (%.3f", ($sendtime - $hash->{Helper}{AckPending}{$hash->{CNT}}->{time})).
- "s ago)");
- }
- $hash->{Helper}{AckPending}{$hash->{CNT}} = {
- cmd => uc($msg),
- frame => uc(unpack("H*", $frame)),
- dst => $dst,
- time => $sendtime,
- };
- push @{$hash->{Helper}{LastSendLen}}, (length($hash->{Helper}{AckPending}{$hash->{CNT}}->{cmd}) / 2) + 2;
- shift @{$hash->{Helper}{LastSendLen}} if (scalar(@{$hash->{Helper}{LastSendLen}}) > 2);
- delete($hash->{Helper}{Resend});
- return $hash->{CNT};
- }
- sub HMUARTLGW_send_frame($$)
- {
- my ($hash, $frame) = @_;
- my $name = $hash->{NAME};
- Log3($hash, 5, "HMUARTLGW ${name} send: (".length($frame)."): ".unpack("H*", $frame));
- my $escaped = substr($frame, 0, 1);
- foreach my $byte (split(//, substr($frame, 1))) {
- if (ord($byte) != 0xfc && ord($byte) != 0xfd) {
- $escaped .= $byte;
- next;
- }
- $escaped .= chr(0xfc);
- $escaped .= chr(ord($byte) & 0x7f);
- }
- $escaped = HMUARTLGW_encrypt($hash, $escaped) if ($hash->{'.crypto'});
- my $sendtime = scalar(gettimeofday());
- DevIo_SimpleWrite($hash, $escaped, 0);
- $sendtime;
- }
- sub HMUARTLGW_sendAscii($$)
- {
- my ($hash, $msg) = @_;
- my $name = $hash->{NAME};
- $msg = sprintf($msg, $hash->{CNT});
- my $logmsg = $msg;
- $logmsg =~ s/\r\n$//;
- Log3($hash, HMUARTLGW_getVerbLvl($hash, undef, undef, 5),
- "HMUARTLGW ${name} send (".length($logmsg)."): ". $logmsg);
- $msg = HMUARTLGW_encrypt($hash, $msg) if ($hash->{'.crypto'} && !($msg =~ m/^V/));
- $hash->{CNT} = ($hash->{CNT} + 1) & 0xff;
- DevIo_SimpleWrite($hash, $msg, ($hash->{'.crypto'} && !($msg =~ m/^V/))? 0 : 2);
- }
- sub HMUARTLGW_crc16($;$)
- {
- my ($msg, $crc) = @_;
- $crc = 0xd77f if (!defined($crc));
- foreach my $byte (split(//, $msg)) {
- $crc ^= (ord($byte) << 8) & 0xff00;
- for (my $i = 0; $i < 8; $i++) {
- if ($crc & 0x8000) {
- $crc = ($crc << 1) & 0xffff;
- $crc ^= 0x8005;
- } else {
- $crc = ($crc << 1) & 0xffff;
- }
- }
- }
- return $crc;
- }
- sub HMUARTLGW_encrypt($$)
- {
- my ($hash, $plaintext) = @_;
- my $ciphertext = '';
- while(length($plaintext)) {
- if(length($hash->{'.crypto'}{encrypt}{keystream})) {
- my $len = length($plaintext);
- $len = length($hash->{'.crypto'}{encrypt}{keystream})
- if (length($hash->{'.crypto'}{encrypt}{keystream}) < $len);
- my $ppart = substr($plaintext, 0, $len, '');
- my $kpart = substr($hash->{'.crypto'}{encrypt}{keystream}, 0, $len, '');
- $hash->{'.crypto'}{encrypt}{ciphertext} .= $ppart ^ $kpart;
- $ciphertext .= $ppart ^ $kpart;
- } else {
- $hash->{'.crypto'}{encrypt}{keystream} =
- $hash->{'.crypto'}{cipher}->encrypt($hash->{'.crypto'}{encrypt}{ciphertext});
- $hash->{'.crypto'}{encrypt}{ciphertext} = '';
- }
- }
- $ciphertext;
- }
- sub HMUARTLGW_decrypt($$)
- {
- my ($hash, $ciphertext) = @_;
- my $plaintext = '';
- while(length($ciphertext)) {
- if(length($hash->{'.crypto'}{decrypt}{keystream})) {
- my $len = length($ciphertext);
- $len = length($hash->{'.crypto'}{decrypt}{keystream})
- if (length($hash->{'.crypto'}{decrypt}{keystream}) < $len);
- my $cpart = substr($ciphertext, 0, $len, '');
- my $kpart = substr($hash->{'.crypto'}{decrypt}{keystream}, 0, $len, '');
- $hash->{'.crypto'}{decrypt}{ciphertext} .= $cpart;
- $plaintext .= $cpart ^ $kpart;
- } else {
- $hash->{'.crypto'}{decrypt}{keystream} =
- $hash->{'.crypto'}{cipher}->encrypt($hash->{'.crypto'}{decrypt}{ciphertext});
- $hash->{'.crypto'}{decrypt}{ciphertext} = '';
- }
- }
- $plaintext;
- }
- sub HMUARTLGW_firmwareGetBlock($$$) {
- my ($hash, $file, $id) = @_;
- my $name = $hash->{NAME};
- my $block = "";
- my $ret = open(my $fd, "<", $file);
- if (!$ret) {
- Log3($hash, 1, "HMUARTLGW ${name} can't open firmware file ${file}: $!");
- return undef;
- }
- my $fw = "";
- while(<$fd>) {
- $fw .= $_;
- }
- close($fd);
- my $n = 0;
- while(length($fw)) {
- my $len = unpack('n', pack('H4', $fw));
- if ($n eq $id) {
- $block = substr($fw, 4, $len * 2);
- last;
- }
- $fw = substr($fw, 4 + ($len * 2));
- $n++;
- }
- if ($n != $id) {
- Log3($hash, 1, "HMUARTLGW ${name} invalid block ${id} requested");
- return undef;
- }
- $block;
- }
- sub HMUARTLGW_updateCoPro($$) {
- my ($hash, $msg) = @_;
- my $name = $hash->{NAME};
- RemoveInternalTimer($hash);
- if (($hash->{FirmwareBlock} > 0) && ($msg !~ /^0401$/)) {
- Log3($hash, 1, "HMUARTLGW ${name} firmware flash failed on block " . ($hash->{FirmwareBlock} - 1));
- delete($hash->{FirmwareFile});
- delete($hash->{FirmwareBlock});
- $hash->{DevState} = HMUARTLGW_STATE_QUERY_APP;
- HMUARTLGW_send($hash, HMUARTLGW_OS_GET_APP, HMUARTLGW_DST_OS);
- InternalTimer(gettimeofday()+HMUARTLGW_CMD_TIMEOUT, "HMUARTLGW_CheckCmdResp", $hash, 0);
- return;
- }
- my $block = HMUARTLGW_firmwareGetBlock($hash, $hash->{FirmwareFile}, $hash->{FirmwareBlock});
- if (!defined($block)) {
- Log3($hash, 1, "HMUARTLGW ${name} firmware update aborted");
- delete($hash->{FirmwareFile});
- delete($hash->{FirmwareBlock});
- $hash->{DevState} = HMUARTLGW_STATE_QUERY_APP;
- HMUARTLGW_send($hash, HMUARTLGW_OS_GET_APP, HMUARTLGW_DST_OS);
- InternalTimer(gettimeofday()+HMUARTLGW_CMD_TIMEOUT, "HMUARTLGW_CheckCmdResp", $hash, 0);
- return;
- } elsif ($block eq "") {
- Log3($hash, 1, "HMUARTLGW ${name} firmware update successfull");
- delete($hash->{FirmwareFile});
- delete($hash->{FirmwareBlock});
- $hash->{DevState} = HMUARTLGW_STATE_QUERY_APP;
- HMUARTLGW_send($hash, HMUARTLGW_OS_GET_APP, HMUARTLGW_DST_OS);
- InternalTimer(gettimeofday()+HMUARTLGW_CMD_TIMEOUT, "HMUARTLGW_CheckCmdResp", $hash, 0);
- return;
- }
- #strip CRC from block
- $block = substr($block, 0, -4);
- HMUARTLGW_send($hash, HMUARTLGW_OS_UPDATE_FIRMWARE . ${block}, HMUARTLGW_DST_OS);
- $hash->{FirmwareBlock}++;
- InternalTimer(gettimeofday()+HMUARTLGW_FIRMWARE_TIMEOUT, "HMUARTLGW_CheckCmdResp", $hash, 0);
- }
- sub HMUARTLGW_getVerbLvl($$$$) {
- my ($hash, $src, $dst, $def) = @_;
- $hash = $hash->{'.lgwHash'} if (defined($hash->{'.lgwHash'}));
- #Lookup IDs on change
- if (defined($hash->{Helper}{Log}{Resolve}) && $init_done) {
- foreach my $id (@{$hash->{Helper}{Log}{IDs}}) {
- next if ($id =~ /^([\da-f]{6}|sys|all)$/i);
- my $newId = substr(CUL_HM_name2Id($id),0,6);
- next if ($newId !~ /^[\da-f]{6}$/i);
- $id = $newId;
- }
- delete($hash->{Helper}{Log}{Resolve});
- }
- return (grep /^sys$/i, @{$hash->{Helper}{Log}{IDs}}) ? 0 : $def
- if ((!defined($src)) || (!defined($dst)));
- return (grep /^($src|$dst|all)$/i, @{$hash->{Helper}{Log}{IDs}}) ? 0 : $def;
- }
- 1;
- =pod
- =item summary support for the HomeMatic UART module (RPi) and Wireless LAN Gateway
- =item summary_DE Anbindung von HomeMatic UART Modul (RPi) und Wireless LAN Gateway
- =begin html
- <a name="HMUARTLGW"></a>
- <h3>HMUARTLGW</h3>
- <ul>
- HMUARTLGW provides support for the eQ-3 HomeMatic Wireless LAN Gateway
- (HM-LGW-O-TW-W-EU) and the eQ-3 HomeMatic UART module (HM-MOD-UART), which
- is part of the HomeMatic wireless module for the Raspberry Pi
- (HM-MOD-RPI-PCB).<br>
- <br><br>
- <a name="HMUARTLGHW_define"></a>
- <b>Define</b>
- <ul>
- <code>define <name> HMUARTLGW <device></code><br><br>
- The <device>-parameter depends on the device-type:
- <ul>
- <li>HM-MOD-UART: <device> specifies the serial port to communicate
- with. The baud-rate is fixed at 115200 and does not need to be
- specified.<br>
- If the HM-MOD-UART is connected to the network by a serial bridge,
- the connection has to be defined in an URL-like format
- (<code>uart://ip:port</code>).</li>
- <li>HM-LGW-O-TW-W-EU: <device> specifies the IP address or hostname
- of the gateway, optionally followed by : and the port number of the
- BidCoS-port (default when not specified: 2000).</li>
- </ul>
- <br><br>
- Examples:<br>
- <ul>
- <li>Local HM-MOD-UART at <code>/dev/ttyAMA0</code>:<br>
- <code>define myHmUART HMUARTLGW /dev/ttyAMA0</code><br> </li>
- <li>LAN Gateway at <code>192.168.42.23</code>:<br>
- <code>define myHmLGW HMUARTLGW 192.168.42.23</code><br> </li>
- <li>Remote HM-MOD-UART using <code>socat</code> on a Raspberry Pi:<br>
- <code>define myRemoteHmUART HMUARTLGW uart://192.168.42.23:12345</code><br><br>
- Remote Raspberry Pi:<br><code>$ socat TCP4-LISTEN:12345,fork,reuseaddr /dev/ttyAMA0,raw,echo=0,b115200</code></li>
- </ul>
- </ul>
- <br>
- <a name="HMUARTLGW_set"></a>
- <p><b>Set</b></p>
- <ul>
- <li>close<br>
- Closes the connection to the device.
- </li>
- <li><a href="#hmPairForSec">hmPairForSec</a></li>
- <li><a href="#hmPairSerial">hmPairSerial</a></li>
- <li>open<br>
- Opens the connection to the device and initializes it.
- </li>
- <li>reopen<br>
- Reopens the connection to the device and reinitializes it.
- </li>
- <li>restart<br>
- Reboots the device.
- </li>
- <li>updateCoPro </path/to/firmware.eq3><br>
- Update the coprocessor-firmware (reading D-firmware) from the
- supplied file. Source for firmware-images (version 1.4.1, official
- eQ-3 repository):<br>
- <ul>
- <li>HM-MOD-UART: <a href="https://raw.githubusercontent.com/eq-3/occu/28045df83480122f90ab92f7c6e625f9bf3b61aa/firmware/HM-MOD-UART/coprocessor_update.eq3">coprocessor_update.eq3</a> (version 1.4.1)</li>
- <li>HM-LGW-O-TW-W-EU: <a href="https://raw.githubusercontent.com/eq-3/occu/28045df83480122f90ab92f7c6e625f9bf3b61aa/firmware/coprocessor_update_hm_only.eq3">coprocessor_update_hm_only.eq3</a> (version 1.4.1)<br>
- Please also make sure that D-LANfirmware is at least at version
- 1.1.5. To update to this version, use the eQ-3 CLI tools (see wiki)
- 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>
- <b>Do not flash hm-lgw-o-tw-w-eu_update.eq3 with updateCoPro!</b></li>
- </ul>
- </li>
- </ul>
- <br>
- <a name="HMUARTLGW_get"></a>
- <p><b>Get</b></p>
- <ul>
- <li>assignIDs<br>
- Returns the HomeMatic devices currently assigned to this IO-device.
- </li>
- </ul>
- <br>
- <a name="HMUARTLGW_attr"></a>
- <b>Attributes</b>
- <ul>
- <li>csmaCa<br>
- Enable or disable CSMA/CA (Carrier sense multiple access with collision
- avoidance), also known as listen-before-talk.<br>
- Default: 0 (disabled)
- </li>
- <li>dummy<br>
- Do not interact with the device at all, only define it.<br>
- Default: not set
- </li>
- <li>dutyCycle<br>
- Enable or disable the duty-cycle check (1% rule) performed by the
- wireless module.<br>
- Disabling this might be illegal in your country, please check with local
- regulations!<br>
- Default: 1 (enabled)
- </li>
- <li><a href="#hmId">hmId</a></li>
- <li><a name="HMLANhmKey">hmKey</a></li>
- <li><a name="HMLANhmKey2">hmKey2</a></li>
- <li><a name="HMLANhmKey3">hmKey3</a></li>
- <li>lgwPw<br>
- AES password for the eQ-3 HomeMatic Wireless LAN Gateway. The default
- password is printed on the back of the device (but can be changed by
- the user). If AES communication is enabled on the LAN Gateway (default),
- this attribute has to be set to the correct value or communication will
- not be possible. In addition, the perl-module Crypt::Rijndael (which
- provides the AES cipher) must be installed.
- </li>
- <li>loadEvents<br>
- Enables logging of the wireless load (in percent of the allowed maximum
- sending-time) of the interface.
- Default: 0 (disabled)
- </li>
- <li>logIDs<br>
- Enables selective logging of HMUARTLGW messages. A list of comma separated
- HMIds or HM device names/channel names can be entered which shall be logged.<br>
- <ul>
- <li><i>all</i>: will log raw messages for all HMIds</li>
- <li><i>sys</i>: will log system related messages like keep-alive</li>
- </ul>
- In order to enable all messages set: <i>all,sys</i>
- </li>
- <li>qLen<br>
- Maximum number of commands in the internal queue of the HMUARTLGW module.
- New commands when the queue is full are dropped. Each command has a maximum
- lifetime of 3s when active, so the worst-case delay of a command is qLen * 3s
- (3 minutes with default settings).<br>
- Default: 60
- </li>
- </ul>
- <br>
- </ul>
- =end html
- =begin html_DE
- <a name="HMUARTLGW"></a>
- <h3>HMUARTLGW</h3>
- <ul>
- Das Modul HMUARTLGW ermöglicht die Anbindung des eQ-3 HomeMatic Wireless
- LAN Gateways (HM-LGW-O-TW-W-EU) und des eQ-3 HomeMatic UART Moduls
- (HM-MOD-UART), welches Teil des HomeMatic-Moduls für den Raspberry Pi
- (HM-MOD-RPI-PCB) ist.<br>
- <br><br>
- <a name="HMUARTLGHW_define"></a>
- <b>Define</b>
- <ul>
- <code>define <name> HMUARTLGW <device></code><br><br>
- Der Parameter <device> hängt vom eingesetzten Gerätetyp ab:
- <ul>
- <li>HM-MOD-UART: <device> ist die zu benutzende serielle
- Schnittstelle. Die Baudrate ist fest auf 115200 eingestellt und muss
- nicht angegeben werden.<br>
- Falls der HM-MOD-UART über einen Seriell-zu-Ethernet-Konverter
- mit dem Netzwerk verbunden ist, muss die Definition in einem
- an URLs angelehnten Format geschehen
- (<code>uart://ip:port</code>).</li>
- <li>HM-LGW-O-TW-W-EU: <device> gibt die IP-Adresse oder den
- Hostnamen des Gateways an, optional gefolgt von einem Doppelpunkt
- und der Portnummer des BidCos-Ports (Default falls nicht angegeben:
- 2000).</li>
- </ul>
- <br><br>
- Beispiele:<br>
- <ul>
- <li>Lokaler HM-MOD-UART an der Schnittstelle <code>/dev/ttyAMA0</code>:<br>
- <code>define myHmUART HMUARTLGW /dev/ttyAMA0</code><br> </li>
- <li>LAN Gateway mit der IP-Adresse <code>192.168.42.23</code>:<br>
- <code>define myHmLGW HMUARTLGW 192.168.42.23</code><br> </li>
- <li>Entfernter HM-MOD-UART unter Verwendung von <code>socat</code> auf einem Raspberry Pi:<br>
- <code>define myRemoteHmUART HMUARTLGW uart://192.168.42.23:12345</code><br><br>
- Entfernter Raspberry Pi:<br><code>$ socat TCP4-LISTEN:12345,fork,reuseaddr /dev/ttyAMA0,raw,echo=0,b115200</code></li>
- </ul>
- </ul>
- <br>
- <a name="HMUARTLGW_set"></a>
- <p><b>Set</b></p>
- <ul>
- <li>close<br>
- Schließt die Verbindung zum Gerät.
- </li>
- <li><a href="#hmPairForSec">hmPairForSec</a></li>
- <li><a href="#hmPairSerial">hmPairSerial</a></li>
- <li>open<br>
- Öffnet die Verbindung zum Gerät und initialisiert es.
- </li>
- <li>reopen<br>
- Schlißt und öffnet die Verbindung zum Gerät und re-initialisiert es.
- </li>
- <li>restart<br>
- Rebootet das Gerät.
- </li>
- <li>updateCoPro </path/to/firmware.eq3><br>
- Aktualisierung der Koprozessor-Firmware (Reading D-firmware) mit der
- angegebenen Datei. Quelle für Firmware-Images (Version 1.4.1,
- offizielles eQ-3 Repository):<br>
- <ul>
- <li>HM-MOD-UART: <a href="https://raw.githubusercontent.com/eq-3/occu/28045df83480122f90ab92f7c6e625f9bf3b61aa/firmware/HM-MOD-UART/coprocessor_update.eq3">coprocessor_update.eq3</a> (Version 1.4.1)</li>
- <li>HM-LGW-O-TW-W-EU: <a href="https://raw.githubusercontent.com/eq-3/occu/28045df83480122f90ab92f7c6e625f9bf3b61aa/firmware/coprocessor_update_hm_only.eq3">coprocessor_update_hm_only.eq3</a> (Version 1.4.1)<br>
- Bitte zusätzlich sicherstellen, dass die Version der
- D-LANfirmware mindestens 1.1.5 beträgt. Um auf diese Version
- zu aktualisieren können die eQ-3 CLI Tools (siehe Wiki) oder
- der eQ-3 Netfinder genutzt werden. Das passende Image ist:
- <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>
- <b>Die Datei hm-lgw-o-tw-w-eu_update.eq3 nicht mit updateCoPro flashen!</b></li>
- </ul>
- </li>
- </ul>
- <br>
- <a name="HMUARTLGW_get"></a>
- <p><b>Get</b></p>
- <ul>
- <li>assignIDs<br>
- Gibt die aktuell diesem IO-Gerät zugeordneten HomeMatic-Geräte
- zurück.
- </li>
- </ul>
- <br>
- <a name="HMUARTLGW_attr"></a>
- <b>Attribute</b>
- <ul>
- <li>csmaCa<br>
- Aktiviert oder deaktiviert CSMA/CA (Carrier sense multiple access with
- collision avoidance), auch bekannt als Listen-Before-Talk.<br>
- Default: 0 (deaktiviert)
- </li>
- <li>dummy<br>
- Ermöglicht die Definition des Geräts ohne jegliche Interaktion
- mit einem physikalischen Gerät.<br>
- Default: nicht gesetzt
- </li>
- <li>dutyCycle<br>
- Aktiviert oder deaktiviert die Überprüfung des Arbeitszyklus
- (1%-Regel) durch das Sendemodul.<br>
- Die Abschaltung dieser Funktion kann in verschiedenen Ländern gegen
- das Gesetz verstossen, weshalb zuerst die Situation anhand lokaler
- Richtlinien zu prüfen ist!<br>
- Default: 1 (aktiviert)
- </li>
- <li><a href="#hmId">hmId</a></li>
- <li><a name="HMLANhmKey">hmKey</a></li>
- <li><a name="HMLANhmKey2">hmKey2</a></li>
- <li><a name="HMLANhmKey3">hmKey3</a></li>
- <li>lgwPw<br>
- AES-Passwort für das eQ-3 HomeMatic Wireless LAN Gateway. Das initiale
- Passwort befindet sich auf der Rückseite des Geräts, kann aber
- durch den Benutzer geändert werden. Falls die AES gesicherte
- Kommunikation aktiviert ist (Auslieferungszustand), muss dieses Attribut
- auf den richtigen Wert gesetzt werden, da ansonsten keine Kommunikation
- möglich ist. Zusätzlich muss das Perl-Modul Crypt::Rijndael
- (stellt den AES-Algorithmus bereit) installiert sein.
- </li>
- <li>loadEvents<br>
- Aktiviert die Erzeugung von Log-Nachrichten über die Funklast
- des Interfaces (in Prozent der erlaubten Sendezeit).
- Default: 0 (deaktiviert)
- </li>
- <li>logIDs<br>
- Aktiviert die gezielte Erzeugung von Log-Nachrichten. Der Parameter ist
- eine durch Komma getrennte Liste an HMIds oder HM Geräte-/Kanalnamen,
- deren Nachrichten aufgezeichnet werden sollen.<br>
- <ul>
- <li><i>all</i>: Zeichnet die Rohnachrichten aller HMIds auf</li>
- <li><i>sys</i>: Zeichnet Systemnachrichten (z.B. Keep-Alive) auf</li>
- </ul>
- Um alle möglichen Nachrichten aufzuzeichnen, kann <i>all,sys</i>
- genutzt werden.
- </li>
- <li>qLen<br>
- Maximale Anzahl an Kommandos in der internen Warteschlange des
- HMUARTLGW-Moduls. Neue Kommandos werden verworfen, wenn die Warteschlange
- gefüllt ist. Jedes Kommando hat eine Lebensdauer von 3s, sobald es
- aktiv verarbeitet wird. Die Verzögerung eines Kommandos beträgt
- im schlechtesten Fall also qLen * 3s (3 Minuten mit den Defaulteinstellungen).<br>
- Default: 60
- </li>
- </ul>
- <br>
- </ul>
- =end html_DE
- =cut
|