88_HMCCURPC.pm 88 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988
  1. ##############################################################################
  2. #
  3. # 88_HMCCURPC.pm
  4. #
  5. # $Id: 88_HMCCURPC.pm 16165 2018-02-13 10:55:01Z zap $
  6. #
  7. # Version 1.0
  8. #
  9. # Thread based RPC Server module for HMCCU.
  10. #
  11. # (c) 2017 by zap (zap01 <at> t-online <dot> de)
  12. #
  13. ##############################################################################
  14. # Requires modules:
  15. #
  16. # threads
  17. # Thread::Queue
  18. # Time::HiRes
  19. # RPC::XML::Client
  20. # RPC::XML::Server
  21. ##############################################################################
  22. package main;
  23. use strict;
  24. use warnings;
  25. use threads;
  26. use Thread::Queue;
  27. # use Data::Dumper;
  28. use Time::HiRes qw(usleep);
  29. use RPC::XML::Client;
  30. use RPC::XML::Server;
  31. use SetExtensions;
  32. ######################################################################
  33. # Constants
  34. ######################################################################
  35. # HMCCURPC version
  36. my $HMCCURPC_VERSION = '1.0';
  37. # Maximum number of events processed per call of Read()
  38. my $HMCCURPC_MAX_EVENTS = 50;
  39. # Maximum number of errors during TriggerIO() before log message is written
  40. my $HMCCURPC_MAX_IOERRORS = 100;
  41. # Maximum number of elements in queue
  42. my $HMCCURPC_MAX_QUEUESIZE = 500;
  43. # Time to wait after data processing loop
  44. my $HMCCURPC_TIME_WAIT = 100000;
  45. # Time to wait before calling TriggerIO() again after I/O error
  46. my $HMCCURPC_TIME_TRIGGER = 10;
  47. # Timeout for established CCU connection
  48. my $HMCCURPC_TIMEOUT_CONNECTION = 10;
  49. # Timeout for TriggerIO()
  50. my $HMCCURPC_TIMEOUT_WRITE = 0.001;
  51. # Timeout for accepting incoming connections
  52. my $HMCCURPC_TIMEOUT_ACCEPT = 1;
  53. # Timeout for incoming CCU events
  54. my $HMCCURPC_TIMEOUT_EVENT = 600;
  55. # Send statistic information after specified amount of events
  56. my $HMCCURPC_STATISTICS = 500;
  57. # Default RPC Port = BidCos-RF
  58. my $HMCCURPC_RPC_PORT_DEFAULT = 2001;
  59. my $HMCCURPC_RPC_INTERFACE_DEFAULT = 'BidCos-RF';
  60. # Default RPC server base port
  61. my $HMCCURPC_SERVER_PORT = 5400;
  62. # RPC ports by protocol name
  63. my @HMCCURPC_RPC_INTERFACES = (
  64. 'BidCos-Wired', 'BidCos-RF', 'HmIP-RF', 'VirtualDevices', 'Homegear', 'CUxD', 'HVL'
  65. );
  66. # Initial intervals for registration of RPC callbacks and reading RPC queue
  67. #
  68. # X = Start RPC server
  69. # X+HMCCURPC_INIT_INTERVAL1 = Register RPC callback
  70. # X+HMCCURPC_INIT_INTERVAL2 = Read RPC Queue
  71. #
  72. my $HMCCURPC_INIT_INTERVAL0 = 12;
  73. my $HMCCURPC_INIT_INTERVAL1 = 7;
  74. my $HMCCURPC_INIT_INTERVAL2 = 5;
  75. my $HMCCURPC_INIT_INTERVAL3 = 25;
  76. # Thread type flags
  77. my $HMCCURPC_THREAD_DATA = 1;
  78. my $HMCCURPC_THREAD_ASCII = 2;
  79. my $HMCCURPC_THREAD_BINARY = 4;
  80. my $HMCCURPC_THREAD_SERVER = 6;
  81. my $HMCCURPC_THREAD_ALL = 7;
  82. # Data types
  83. my $BINRPC_INTEGER = 1;
  84. my $BINRPC_BOOL = 2;
  85. my $BINRPC_STRING = 3;
  86. my $BINRPC_DOUBLE = 4;
  87. my $BINRPC_BASE64 = 17;
  88. my $BINRPC_ARRAY = 256;
  89. my $BINRPC_STRUCT = 257;
  90. # Message types
  91. my $BINRPC_REQUEST = 0x42696E00;
  92. my $BINRPC_RESPONSE = 0x42696E01;
  93. my $BINRPC_REQUEST_HEADER = 0x42696E40;
  94. my $BINRPC_ERROR = 0x42696EFF;
  95. ######################################################################
  96. # Functions
  97. ######################################################################
  98. # Standard functions
  99. sub HMCCURPC_Initialize ($);
  100. sub HMCCURPC_Define ($$);
  101. sub HMCCURPC_Undef ($$);
  102. sub HMCCURPC_Shutdown ($);
  103. sub HMCCURPC_Attr ($@);
  104. sub HMCCURPC_Set ($@);
  105. sub HMCCURPC_Get ($@);
  106. sub HMCCURPC_Notify ($$);
  107. sub HMCCURPC_Read ($);
  108. sub HMCCURPC_SetError ($$);
  109. sub HMCCURPC_SetState ($$);
  110. sub HMCCURPC_SetRPCState ($$$);
  111. sub HMCCURPC_ResetRPCState ($$);
  112. sub HMCCURPC_IsRPCStateBlocking ($);
  113. sub HMCCURPC_FindHMCCUDevice ($);
  114. sub HMCCURPC_ProcessEvent ($$);
  115. # RPC server management functions
  116. sub HMCCURPC_GetAttribute ($$$$);
  117. sub HMCCURPC_GetRPCInterfaceList ($);
  118. sub HMCCURPC_GetRPCPortList ($);
  119. sub HMCCURPC_GetEventTimeout ($$);
  120. sub HMCCURPC_RegisterCallback ($);
  121. sub HMCCURPC_RegisterSingleCallback ($$$);
  122. sub HMCCURPC_DeRegisterCallback ($);
  123. sub HMCCURPC_DeRegisterSingleCallback ($$$);
  124. sub HMCCURPC_InitRPCServer ($$$$);
  125. sub HMCCURPC_StartRPCServer ($);
  126. sub HMCCURPC_RPCServerStarted ($$);
  127. sub HMCCURPC_CleanupThreads ($$$);
  128. sub HMCCURPC_CleanupThreadIO ($);
  129. sub HMCCURPC_TerminateThreads ($$);
  130. sub HMCCURPC_CheckThreadState ($$$$);
  131. sub HMCCURPC_IsRPCServerRunning ($);
  132. sub HMCCURPC_Housekeeping ($);
  133. sub HMCCURPC_StopRPCServer ($);
  134. sub HMCCURPC_SendRequest ($@);
  135. sub HMCCURPC_SendBinRequest ($@);
  136. # Helper functions
  137. sub HMCCURPC_HexDump ($$);
  138. # RPC server functions
  139. sub HMCCURPC_ProcessRequest ($$$);
  140. sub HMCCURPC_HandleConnection ($$$$);
  141. sub HMCCURPC_TriggerIO ($$$);
  142. sub HMCCURPC_ProcessData ($$$$);
  143. sub HMCCURPC_Write ($$$$);
  144. sub HMCCURPC_WriteStats ($$);
  145. sub HMCCURPC_NewDevicesCB ($$$);
  146. sub HMCCURPC_DeleteDevicesCB ($$$);
  147. sub HMCCURPC_UpdateDeviceCB ($$$$);
  148. sub HMCCURPC_ReplaceDeviceCB ($$$$);
  149. sub HMCCURPC_ReaddDevicesCB ($$$);
  150. sub HMCCURPC_EventCB ($$$$$);
  151. sub HMCCURPC_ListDevicesCB ($$);
  152. # Binary RPC encoding functions
  153. sub HMCCURPC_EncInteger ($);
  154. sub HMCCURPC_EncBool ($);
  155. sub HMCCURPC_EncString ($);
  156. sub HMCCURPC_EncName ($);
  157. sub HMCCURPC_EncDouble ($);
  158. sub HMCCURPC_EncBase64 ($);
  159. sub HMCCURPC_EncArray ($);
  160. sub HMCCURPC_EncStruct ($);
  161. sub HMCCURPC_EncType ($$);
  162. sub HMCCURPC_EncodeRequest ($$);
  163. sub HMCCURPC_EncodeResponse ($$);
  164. # Binary RPC decoding functions
  165. sub HMCCURPC_DecInteger ($$$);
  166. sub HMCCURPC_DecBool ($$);
  167. sub HMCCURPC_DecString ($$);
  168. sub HMCCURPC_DecDouble ($$);
  169. sub HMCCURPC_DecBase64 ($$);
  170. sub HMCCURPC_DecArray ($$);
  171. sub HMCCURPC_DecStruct ($$);
  172. sub HMCCURPC_DecType ($$);
  173. sub HMCCURPC_DecodeRequest ($);
  174. sub HMCCURPC_DecodeResponse ($);
  175. ######################################################################
  176. # Initialize module
  177. ######################################################################
  178. sub HMCCURPC_Initialize ($)
  179. {
  180. my ($hash) = @_;
  181. $hash->{DefFn} = "HMCCURPC_Define";
  182. $hash->{UndefFn} = "HMCCURPC_Undef";
  183. $hash->{SetFn} = "HMCCURPC_Set";
  184. $hash->{GetFn} = "HMCCURPC_Get";
  185. $hash->{ReadFn} = "HMCCURPC_Read";
  186. $hash->{AttrFn} = "HMCCURPC_Attr";
  187. $hash->{NotifyFn} = "HMCCURPC_Notify";
  188. $hash->{ShutdownFn} = "HMCCURPC_Shutdown";
  189. $hash->{parseParams} = 1;
  190. $hash->{AttrList} = "rpcInterfaces:multiple-strict,".join(',',@HMCCURPC_RPC_INTERFACES).
  191. " ccuflags:multiple-strict,expert,keepThreads,logEvents,reconnect".
  192. " rpcMaxEvents rpcQueueSize rpcTriggerTime".
  193. " rpcServer:on,off rpcServerAddr rpcServerPort rpcWriteTimeout rpcAcceptTimeout".
  194. " rpcConnTimeout rpcWaitTime rpcStatistics rpcEventTimeout ".
  195. $readingFnAttributes;
  196. }
  197. ######################################################################
  198. # Define device
  199. ######################################################################
  200. sub HMCCURPC_Define ($$)
  201. {
  202. my ($hash, $a, $h) = @_;
  203. my $name = $hash->{NAME};
  204. my $hmccu_hash;
  205. my $usage = "Usage: define $name HMCCURPC { CCUHost | iodev=Device_Name }";
  206. $hash->{version} = $HMCCURPC_VERSION;
  207. if (exists ($h->{iodev})) {
  208. my $ioname = $h->{iodev};
  209. return "HMCCURPC: HMCCU I/O device $ioname not found" if (!exists ($defs{$ioname}));
  210. return "HMCCURPC: Device $ioname is not a HMCCU device" if ($defs{$ioname}->{TYPE} ne 'HMCCU');
  211. $hmccu_hash = $defs{$ioname};
  212. $hash->{host} = $hmccu_hash->{host};
  213. }
  214. else {
  215. return $usage if (scalar (@$a) < 3);
  216. $hash->{host} = $$a[2];
  217. }
  218. # Try to find I/O device
  219. if (!defined ($hmccu_hash)) {
  220. $hmccu_hash = HMCCURPC_FindHMCCUDevice ($hash);
  221. return "HMCCURPC: Can't find HMCCU I/O device" if (!defined ($hmccu_hash));
  222. }
  223. if (defined ($hmccu_hash)) {
  224. # Set I/O device and store reference for RPC device in I/O device
  225. AssignIoPort ($hash, $hmccu_hash->{NAME});
  226. $hmccu_hash->{RPCDEV} = $name;
  227. $hash->{ccuip} = $hmccu_hash->{ccuip};
  228. $hash->{ccutype} = $hmccu_hash->{ccutype};
  229. $hash->{CCUNum} = $hmccu_hash->{CCUNum};
  230. $hash->{ccustate} = $hmccu_hash->{ccustate};
  231. }
  232. else {
  233. # Count CCU devices
  234. my $ccucount = 0;
  235. foreach my $d (keys %defs) {
  236. my $ch = $defs{$d};
  237. next if (!exists ($ch->{TYPE}));
  238. $ccucount++ if ($ch->{TYPE} eq 'HMCCU');
  239. $ccucount++ if ($ch->{TYPE} eq 'HMCCURPC' && $ch != $hash);
  240. }
  241. $hash->{CCUNum} = $ccucount+1;
  242. $hash->{ccutype} = "CCU2";
  243. $hash->{ccustate} = 'initialized';
  244. }
  245. Log3 $name, 1, "HMCCURPC: Device $name. Initialized version $HMCCURPC_VERSION";
  246. # Set some attributes
  247. $attr{$name}{stateFormat} = "rpcstate/state";
  248. $attr{$name}{verbose} = 2;
  249. HMCCURPC_ResetRPCState ($hash, "initialized");
  250. return undef;
  251. }
  252. ######################################################################
  253. # Delete device
  254. ######################################################################
  255. sub HMCCURPC_Undef ($$)
  256. {
  257. my ($hash, $arg) = @_;
  258. # Delete RPC device name in I/O device
  259. my $hmccu_hash;
  260. $hmccu_hash = $hash->{IODev} if (exists ($hash->{IODev}));
  261. delete $hmccu_hash->{RPCDEV} if (defined ($hmccu_hash) && exists ($hmccu_hash->{RPCDEV}));
  262. # Shutdown RPC server
  263. HMCCURPC_Shutdown ($hash);
  264. return undef;
  265. }
  266. ######################################################################
  267. # Shutdown FHEM
  268. ######################################################################
  269. sub HMCCURPC_Shutdown ($)
  270. {
  271. my ($hash) = @_;
  272. # Shutdown RPC server
  273. HMCCURPC_StopRPCServer ($hash);
  274. RemoveInternalTimer ($hash);
  275. return undef;
  276. }
  277. ######################################################################
  278. # Set attribute
  279. ######################################################################
  280. sub HMCCURPC_Attr ($@)
  281. {
  282. my ($cmd, $name, $attrname, $attrval) = @_;
  283. my $hash = $defs{$name};
  284. my $rc = 0;
  285. my $hmccu_hash = (exists ($hash->{IODev})) ? $hash->{IODev} : undef;
  286. return "HMCCURPC: Can't find HMCCU I/O device" if (!defined ($hmccu_hash));
  287. if ($attrname eq 'rpcInterfaces') {
  288. my ($run, $all) = HMCCURPC_CheckThreadState ($hash, $HMCCURPC_THREAD_ALL, 'running', undef);
  289. return 'Stop RPC server before modifying rpcInterfaces' if ($run > 0);
  290. }
  291. if ($cmd eq 'set') {
  292. if ($attrname eq 'rpcInterfaces') {
  293. my @ports = split (',', $attrval);
  294. my @plist = ();
  295. foreach my $p (@ports) {
  296. my $pn = HMCCU_GetRPCServerInfo ($hmccu_hash, $p, 'port');
  297. return "HMCCURPC: Illegal RPC interface $p" if (!defined ($pn));
  298. push (@plist, $pn);
  299. }
  300. return "HMCCURPC: No RPC interface specified" if (scalar (@plist) == 0);
  301. $hash->{hmccu}{rpcports} = join (',', @plist);
  302. }
  303. }
  304. elsif ($cmd eq 'del') {
  305. if ($attrname eq 'rpcInterfaces' && exists ($hash->{hmccu}{rpcports})) {
  306. delete $hash->{hmccu}{rpcports};
  307. }
  308. }
  309. return undef;
  310. }
  311. ######################################################################
  312. # Set commands
  313. ######################################################################
  314. sub HMCCURPC_Set ($@)
  315. {
  316. my ($hash, $a, $h) = @_;
  317. my $name = shift @$a;
  318. my $opt = shift @$a;
  319. my $ccuflags = AttrVal ($name, 'ccuflags', 'null');
  320. my $options = $ccuflags =~ /expert/ ? "deregister:".join(',', HMCCURPC_GetRPCInterfaceList ($hash)).
  321. " rpcrequest rpcserver:on,off" : "";
  322. my $busyoptions = $ccuflags =~ /expert/ ? "rpcserver:off" : "";
  323. if ($opt ne 'rpcserver' && HMCCURPC_IsRPCStateBlocking ($hash)) {
  324. # HMCCURPC_SetState ($hash, "busy");
  325. return "HMCCURPC: CCU busy, choose one of $busyoptions";
  326. }
  327. if ($opt eq 'deregister') {
  328. my $interface = shift @$a;
  329. return "Usage: set $name deregister {Interface}" if (!defined ($interface));
  330. return "HMCCURPC: Can't find HMCCU I/O device" if (!exists ($hash->{IODev}));
  331. my $port = HMCCU_GetRPCServerInfo ($hash->{IODev}, $interface, 'port');
  332. return "HMCCURPC: Illegal RPC interface $interface" if (!defined ($port));
  333. if (!HMCCURPC_DeRegisterSingleCallback ($hash, $port, 1)) {
  334. return HMCCURPC_SetError ($hash, "Degistering RPC callback failed");
  335. }
  336. return HMCCURPC_SetState ($hash, "OK");
  337. }
  338. elsif ($opt eq 'rpcrequest') {
  339. my $port = shift @$a;
  340. my $request = shift @$a;
  341. return "Usage: set $name rpcrequest {port} {request} [{parameter} ...]"
  342. if (!defined ($request));
  343. return "HMCCURPC: Can't find HMCCU I/O device" if (!exists ($hash->{IODev}));
  344. my $response;
  345. if (HMCCU_IsRPCType ($hash->{IODev}, $port, 'A')) {
  346. $response = HMCCURPC_SendRequest ($hash, $port, $request, @$a);
  347. }
  348. elsif (HMCCU_IsRPCType ($hash->{IODev}, $port, 'B')) {
  349. $response = HMCCURPC_SendBinRequest ($hash, $port, $request, @$a);
  350. }
  351. else {
  352. return HMCCURPC_SetError ($hash, "Invalid RPC port $port");
  353. }
  354. return HMCCURPC_SetError ($hash, "RPC request failed") if (!defined ($response));
  355. return HMCCU_RefToString ($response);
  356. }
  357. elsif ($opt eq 'rpcserver') {
  358. my $action = shift @$a;
  359. return HMCCURPC_SetError ($hash, "Usage: set $name rpcserver {on|off}")
  360. if (!defined ($action) || $action !~ /^(on|off)$/);
  361. if ($action eq 'on') {
  362. return HMCCURPC_SetError ($hash, "RPC server already running")
  363. if ($hash->{RPCState} ne 'inactive');
  364. my ($rc, $info) = HMCCURPC_StartRPCServer ($hash);
  365. return HMCCURPC_SetError ($hash, $info) if (!$rc);
  366. }
  367. elsif ($action eq 'off') {
  368. HMCCURPC_StopRPCServer ($hash);
  369. }
  370. return HMCCURPC_SetState ($hash, "OK");
  371. }
  372. else {
  373. return "HMCCURPC: Unknown argument $opt, choose one of ".$options;
  374. }
  375. }
  376. ######################################################################
  377. # Get commands
  378. ######################################################################
  379. sub HMCCURPC_Get ($@)
  380. {
  381. my ($hash, $a, $h) = @_;
  382. my $name = shift @$a;
  383. my $opt = shift @$a;
  384. my $ccuflags = AttrVal ($name, 'ccuflags', 'null');
  385. my $options = "rpcevents:noArg rpcstate:noArg";
  386. if ($opt ne 'rpcstate' && HMCCURPC_IsRPCStateBlocking ($hash)) {
  387. # HMCCURPC_SetState ($hash, "busy");
  388. return "HMCCURPC: CCU busy, choose one of rpcstate:noArg";
  389. }
  390. my $result = 'Command not implemented';
  391. my $rc;
  392. if ($opt eq 'rpcevents') {
  393. my @eventtypes = ("EV", "ND", "DD", "RD", "RA", "UD", "IN", "EX", "SL", "TO");
  394. $result = '';
  395. foreach my $clkey (keys %{$hash->{hmccu}{rpc}}) {
  396. next if ($clkey eq 'DATA');
  397. $result .= "Event statistics for server $clkey\n";
  398. $result .= "Average event delay = ".$hash->{hmccu}{rpc}{$clkey}{avgdelay}."\n"
  399. if (defined ($hash->{hmccu}{rpc}{$clkey}{avgdelay}));
  400. $result .= "========================================\n";
  401. $result .= "ET Sent by RPC server Received by FHEM\n";
  402. $result .= "----------------------------------------\n";
  403. foreach my $et (@eventtypes) {
  404. my $snd = exists ($hash->{hmccu}{rpc}{$clkey}{snd}{$et}) ?
  405. sprintf ("%7d", $hash->{hmccu}{rpc}{$clkey}{snd}{$et}) : " n/a";
  406. my $rec = exists ($hash->{hmccu}{rpc}{$clkey}{rec}{$et}) ?
  407. sprintf ("%7d", $hash->{hmccu}{rpc}{$clkey}{rec}{$et}) : " n/a";
  408. $result .= "$et $snd $rec\n\n";
  409. }
  410. }
  411. return $result eq '' ? "No event statistics found" : $result;
  412. }
  413. elsif ($opt eq 'rpcstate') {
  414. $result = '';
  415. foreach my $clkey (keys %{$hash->{hmccu}{rpc}}) {
  416. if ($result eq '') {
  417. $result .= "ID RPC-Thread State \n";
  418. $result .= "-----------------------\n";
  419. }
  420. my $sid = sprintf ("%2d", $hash->{hmccu}{rpc}{$clkey}{tid});
  421. my $sname = sprintf ("%-6s", $clkey);
  422. $result .= $sid." ".$sname." ".$hash->{hmccu}{rpc}{$clkey}{state}."\n";
  423. }
  424. $result = "No RPC server running" if ($result eq '');
  425. return $result;
  426. }
  427. else {
  428. return "HMCCURPC: Unknown argument $opt, choose one of ".$options;
  429. }
  430. }
  431. ######################################################################
  432. # Handle FHEM events
  433. ######################################################################
  434. sub HMCCURPC_Notify ($$)
  435. {
  436. my ($hash, $devhash) = @_;
  437. my $name = $hash->{NAME};
  438. my $devname = $devhash->{NAME};
  439. my $devtype = $devhash->{TYPE};
  440. my $disable = AttrVal ($name, 'disable', 0);
  441. my $rpcserver = AttrVal ($name, 'rpcServer', 'off');
  442. return if ($disable);
  443. my $events = deviceEvents ($devhash, 1);
  444. return if (! $events);
  445. # Process events
  446. foreach my $event (@{$events}) {
  447. if ($devname eq 'global') {
  448. if ($event eq 'INITIALIZED') {
  449. if (!exists ($hash->{IODev})) {
  450. my $hmccu_hash = HMCCURPC_FindHMCCUDevice ($hash);
  451. if (defined ($hmccu_hash)) {
  452. $hash->{IODev} = $hmccu_hash;
  453. $hash->{CCUNum} = $hmccu_hash->{CCUNum};
  454. AssignIoPort ($hash, $hmccu_hash->{NAME});
  455. }
  456. else {
  457. Log3 $name, 0, "HMCCURPC: FHEM initialized but HMCCU IO device not found";
  458. }
  459. }
  460. # return if ($rpcserver eq 'off');
  461. # my $delay = $HMCCURPC_INIT_INTERVAL0;
  462. # Log3 $name, 0, "HMCCURPC: Start of RPC server after FHEM initialization in $delay seconds";
  463. # if ($ccuflags =~ /threads/) {
  464. # InternalTimer (gettimeofday()+$delay, "HMCCURPC_StartRPCServer", $hash, 0);
  465. # }
  466. last;
  467. }
  468. }
  469. }
  470. return;
  471. }
  472. ######################################################################
  473. # Read data from thread
  474. ######################################################################
  475. sub HMCCURPC_Read ($)
  476. {
  477. my ($hash) = @_;
  478. my $name = $hash->{NAME};
  479. my @termtids;
  480. my $eventcount = 0; # Total number of events
  481. my $devcount = 0; # Number of DD, ND or RD events
  482. my $evcount = 0; # Number of EV events
  483. my %events = ();
  484. my %devices = ();
  485. Log3 $name, 4, "HMCCURPC: Read called";
  486. # Check if child socket, event queue and IO device exists
  487. return if (!defined ($hash->{hmccu}{sockchild}));
  488. my $child = $hash->{hmccu}{sockchild};
  489. return if (!defined ($hash->{hmccu}{eventqueue}));
  490. my $queue = $hash->{hmccu}{eventqueue};
  491. my $hmccu_hash = (exists ($hash->{IODev})) ? $hash->{IODev} : undef;
  492. if (!defined ($hmccu_hash)) {
  493. Log3 $name, 4, "HMCCURPC: Can't find I/O device";
  494. return;
  495. }
  496. # Get attributes
  497. my $rpcmaxevents = AttrVal ($name, 'rpcMaxEvents', $HMCCURPC_MAX_EVENTS);
  498. my $ccuflags = AttrVal ($name, 'ccuflags', 'null');
  499. # Data read from child socket is only a trigger for reading data from event queue
  500. my $buffer = '';
  501. my $res = sysread ($child, $buffer, 4096);
  502. if (!defined ($res) || length ($buffer) == 0) {
  503. Log3 $name, 4, "HMCCURPC: read failed";
  504. return;
  505. }
  506. else {
  507. Log3 $name, 4, "HMCCURPC: read $buffer from child socket";
  508. }
  509. # Read events from queue
  510. $hash->{hmccu}{readqueue}->enqueue (1);
  511. while (my $item = $queue->dequeue_nb ()) {
  512. Log3 $name, 4, "HMCCURPC: read $item from queue";
  513. my ($et, $clkey, @par) = HMCCURPC_ProcessEvent ($hash, $item);
  514. next if (!defined ($et));
  515. if ($et eq 'EV') {
  516. $events{$par[0]}{$par[1]}{$par[2]} = $par[3];
  517. $evcount++;
  518. $hmccu_hash->{ccustate} = 'active' if ($hmccu_hash->{ccustate} ne 'active');
  519. }
  520. elsif ($et eq 'EX') {
  521. last;
  522. }
  523. elsif ($et eq 'ND') {
  524. $devices{$par[0]}{flag} = 'N';
  525. $devices{$par[0]}{version} = $par[3];
  526. if ($par[1] eq 'D') {
  527. $devices{$par[0]}{addtype} = 'dev';
  528. $devices{$par[0]}{type} = $par[2];
  529. $devices{$par[0]}{firmware} = $par[4];
  530. $devices{$par[0]}{rxmode} = $par[5];
  531. }
  532. else {
  533. $devices{$par[0]}{addtype} = 'chn';
  534. $devices{$par[0]}{usetype} = $par[2];
  535. }
  536. $devcount++;
  537. }
  538. elsif ($et eq 'DD') {
  539. $devices{$par[0]}{flag} = 'D';
  540. $devcount++;
  541. }
  542. elsif ($et eq 'RD') {
  543. $devices{$par[0]}{flag} = 'R';
  544. $devices{$par[0]}{newaddr} = $par[1];
  545. $devcount++;
  546. }
  547. elsif ($et eq 'TO') {
  548. $hmccu_hash->{ccustate} = 'timeout';
  549. if ($hash->{RPCState} eq 'running' && $ccuflags =~ /reconnect/) {
  550. my $serveraddr = HMCCU_GetRPCServerInfo ($hmccu_hash, $par[0], 'host');
  551. my $ifname = HMCCU_GetRPCServerInfo ($hmccu_hash, $par[0], 'name');
  552. if (defined ($serveraddr) && defined ($ifname)) {
  553. if (HMCCU_TCPConnect ($serveraddr, $par[0])) {
  554. $hmccu_hash->{ccustate} = 'active';
  555. Log3 $name, 2, "HMCCURPC: Reconnecting to RPC interface $ifname".
  556. " on host $serveraddr";
  557. HMCCURPC_RegisterSingleCallback ($hash, $par[0], 1);
  558. }
  559. else {
  560. $hmccu_hash->{ccustate} = 'unreachable';
  561. Log3 $name, 1, "HMCCURPC: CCU not reachable on port ".$par[0];
  562. }
  563. }
  564. else {
  565. Log3 $name, 1, "HMCCURPC: Can't get ip address for port ".$par[0];
  566. }
  567. }
  568. }
  569. $eventcount++;
  570. if ($eventcount > $rpcmaxevents) {
  571. Log3 $name, 4, "HMCCURPC: Read stopped after $rpcmaxevents events";
  572. last;
  573. }
  574. }
  575. # Update device table and client device readings
  576. if (defined ($hmccu_hash)) {
  577. HMCCU_UpdateDeviceTable ($hmccu_hash, \%devices) if ($devcount > 0);
  578. HMCCU_UpdateMultipleDevices ($hmccu_hash, \%events) if ($evcount > 0);
  579. }
  580. $hash->{hmccu}{readqueue}->dequeue_nb ();
  581. Log3 $name, 4, "HMCCURPC: Read finished";
  582. }
  583. ######################################################################
  584. # Set error state and write log file message
  585. ######################################################################
  586. sub HMCCURPC_SetError ($$)
  587. {
  588. my ($hash, $text) = @_;
  589. my $name = $hash->{NAME};
  590. my $type = $hash->{TYPE};
  591. my $msg;
  592. $msg = defined ($text) ? $text : "unknown error";
  593. $msg = $type.": ".$name." ". $msg;
  594. HMCCURPC_SetState ($hash, "Error");
  595. Log3 $name, 1, $msg;
  596. return $msg;
  597. }
  598. ######################################################################
  599. # Set state of device
  600. ######################################################################
  601. sub HMCCURPC_SetState ($$)
  602. {
  603. my ($hash, $text) = @_;
  604. my $name = $hash->{NAME};
  605. if (defined ($hash) && defined ($text)) {
  606. readingsSingleUpdate ($hash, "state", $text, 1);
  607. }
  608. return ($text eq "busy") ? "HMCCURPC: CCU busy" : undef;
  609. }
  610. ######################################################################
  611. # Set state of RPC server
  612. ######################################################################
  613. sub HMCCURPC_SetRPCState ($$$)
  614. {
  615. my ($hash, $state, $msg) = @_;
  616. my $name = $hash->{NAME};
  617. # Search HMCCU device and check for running RPC servers
  618. my $hmccu_hash;
  619. $hmccu_hash = $hash->{IODev} if (exists ($hash->{IODev}));
  620. $hash->{RPCState} = $state;
  621. readingsSingleUpdate ($hash, "rpcstate", $state, 1);
  622. HMCCURPC_SetState ($hash, 'busy') if ($state ne 'running' && $state ne 'inactive' &&
  623. $state ne 'error' && ReadingsVal ($name, 'state', '') ne 'busy');
  624. Log3 $hash->{NAME}, 1, "HMCCURPC: $msg" if (defined ($msg));
  625. # Update internals of I/O device
  626. HMCCU_SetRPCState ($hmccu_hash, $state) if (defined ($hmccu_hash));
  627. }
  628. ######################################################################
  629. # Reset RPC State
  630. ######################################################################
  631. sub HMCCURPC_ResetRPCState ($$)
  632. {
  633. my ($hash, $state) = @_;
  634. $hash->{RPCTID} = "0"; # List of RPC server thread IDs
  635. $hash->{hmccu}{evtime} = 0; # Timestamp of last event from CCU
  636. $hash->{hmccu}{rpcstarttime} = 0; # Timestamp of RPC server start
  637. HMCCURPC_SetState ($hash, $state);
  638. HMCCURPC_SetRPCState ($hash, 'inactive', undef);
  639. }
  640. ######################################################################
  641. # Check if CCU is busy due to RPC start or stop
  642. ######################################################################
  643. sub HMCCURPC_IsRPCStateBlocking ($)
  644. {
  645. my ($hash) = @_;
  646. return ($hash->{RPCState} eq "running" || $hash->{RPCState} eq "inactive") ? 0 : 1;
  647. }
  648. ######################################################################
  649. # Return hash of corresponding HMCCU device.
  650. # CCU name in HMCCU device must match CCU name in HMCCURPC device.
  651. ######################################################################
  652. sub HMCCURPC_FindHMCCUDevice ($)
  653. {
  654. my ($hash) = @_;
  655. return $hash->{IODev} if (defined ($hash->{IODev}));
  656. for my $d (keys %defs) {
  657. my $h = $defs{$d};
  658. next if (!exists ($h->{TYPE}) || !exists ($h->{NAME}));
  659. next if ($h->{TYPE} ne 'HMCCU');
  660. return $h if ($h->{host} eq $hash->{host});
  661. }
  662. return undef;
  663. }
  664. ######################################################################
  665. # Process RPC server event
  666. ######################################################################
  667. sub HMCCURPC_ProcessEvent ($$)
  668. {
  669. my ($hash, $event) = @_;
  670. my $name = $hash->{NAME};
  671. my $rh = \%{$hash->{hmccu}{rpc}}; # Just for code simplification
  672. my $hmccu_hash;
  673. $hmccu_hash = $hash->{IODev} if (exists ($hash->{IODev}));
  674. # Number of arguments in RPC events (without event type and clkey)
  675. my %rpceventargs = (
  676. "EV", 4,
  677. "ND", 6,
  678. "DD", 1,
  679. "RD", 2,
  680. "RA", 1,
  681. "UD", 2,
  682. "IN", 2,
  683. "EX", 2,
  684. "SL", 1,
  685. "TO", 1,
  686. "ST", 11
  687. );
  688. my $ccuflags = AttrVal ($name, 'ccuflags', 'null');
  689. # Parse event
  690. return undef if (!defined ($event) || $event eq '');
  691. my @t = split (/\|/, $event);
  692. my $et = shift @t;
  693. my $clkey = shift @t;
  694. my $tc = scalar (@t);
  695. # Log event
  696. Log3 $name, 2, "HMCCURPC: CCUEvent = $event" if ($ccuflags =~ /logEvents/);
  697. # Check event data
  698. if (!defined ($clkey)) {
  699. Log3 $name, 2, "HMCCURPC: Syntax error in RPC event data";
  700. return undef;
  701. }
  702. # Check for valid server
  703. if (!exists ($rh->{$clkey})) {
  704. Log3 $name, 0, "HMCCURPC: Received event of type $et for unknown RPC server $clkey";
  705. return undef;
  706. }
  707. # Check event type
  708. if (!exists ($rpceventargs{$et})) {
  709. $et =~ s/([\x00-\xFF])/sprintf("0x%X ",ord($1))/eg;
  710. Log3 $name, 2, "HMCCURPC: Received unknown event from CCU: ".$et;
  711. return undef;
  712. }
  713. # Check event parameters
  714. if ($tc != $rpceventargs{$et}) {
  715. Log3 $name, 2, "HMCCURPC: Wrong number of parameters in event $event. Expected ".
  716. $rpceventargs{$et};
  717. return undef;
  718. }
  719. # Update statistic counters
  720. $rh->{$clkey}{rec}{$et}++;
  721. $rh->{$clkey}{evtime} = time ();
  722. if ($et eq 'EV') {
  723. #
  724. # Update of datapoint
  725. # Input: EV|clkey|Time|Address|Datapoint|Value
  726. # Output: EV, clkey, DevAdd, ChnNo, Datapoint, Value
  727. #
  728. my $delay = $rh->{$clkey}{evtime}-$t[0];
  729. $rh->{$clkey}{sumdelay} += $delay;
  730. $rh->{$clkey}{avgdelay} = $rh->{$clkey}{sumdelay}/$rh->{$clkey}{rec}{$et};
  731. if (defined ($hmccu_hash) && $hmccu_hash->{ccustate} ne 'active') {
  732. $hmccu_hash->{ccustate} = 'active';
  733. }
  734. Log3 $name, 2, "HMCCURPC: Received CENTRAL event. ".$t[2]."=".$t[3] if ($t[1] eq 'CENTRAL');
  735. my ($add, $chn) = split (/:/, $t[1]);
  736. return defined ($chn) ? ($et, $clkey, $add, $chn, $t[2], $t[3]) : undef;
  737. }
  738. elsif ($et eq 'SL') {
  739. #
  740. # RPC server enters server loop
  741. # Input: SL|clkey|Tid
  742. # Output: SL, clkey, countWorking, countRunning, ClientsUpdated, UpdateErrors
  743. #
  744. if ($t[0] == $rh->{$clkey}{tid}) {
  745. Log3 $name, 1, "HMCCURPC: Received SL event. Process $clkey enters server loop";
  746. $rh->{$clkey}{state} = $clkey eq 'DATA' ? 'running' : 'working';
  747. my ($run, $alld) = HMCCURPC_CheckThreadState ($hash, $HMCCURPC_THREAD_DATA, 'running', undef);
  748. my ($work, $alls) = HMCCURPC_CheckThreadState ($hash, $HMCCURPC_THREAD_SERVER, 'working', undef);
  749. if ($work == $alls && $run == $alld) {
  750. Log3 $name, 1, "HMCCURPC: All threads working";
  751. if (!HMCCURPC_RegisterCallback ($hash)) {
  752. Log3 $name, 1, "HMCCURPC: No RPC callbacks registered";
  753. }
  754. }
  755. my ($srun, $c_ok, $c_err) = HMCCURPC_RPCServerStarted ($hash, $hmccu_hash);
  756. return ($et, $clkey, $work, $srun, $c_ok, $c_err);
  757. }
  758. else {
  759. Log3 $name, 0, "HMCCURPC: Received SL event. Wrong TID=".$t[0]." for RPC server $clkey";
  760. return undef;
  761. }
  762. }
  763. elsif ($et eq 'IN') {
  764. #
  765. # RPC server initialized
  766. # Input: IN|clkey|INIT|State
  767. # Output: IN, clkey, countRunning, ClientsUpdated, UpdateErrors
  768. #
  769. Log3 $name, 1, "HMCCURPC: Received IN event. RPC server $clkey running.";
  770. return ($et, $clkey, 0, 0, 0) if ($rh->{$clkey}{state} eq 'running');
  771. $rh->{$clkey}{state} = "running";
  772. # Set binary RPC interfaces to 'running' if all ascii interfaces are in state 'running'
  773. # my ($runa, $alla) = HMCCURPC_CheckThreadState ($hash, $HMCCURPC_THREAD_ASCII, 'running', undef);
  774. # if ($runa == $alla) {
  775. # foreach my $sn (keys %{$rh}) {
  776. # $rh->{$sn}{state} = "running"
  777. # if ($rh->{$sn}{type} == $HMCCURPC_THREAD_BINARY && $rh->{$sn}{state} eq 'registered');
  778. # }
  779. # }
  780. my ($run, $c_ok, $c_err) = HMCCURPC_RPCServerStarted ($hash, $hmccu_hash);
  781. return ($et, $clkey, $run, $c_ok, $c_err);
  782. }
  783. elsif ($et eq 'EX') {
  784. #
  785. # Thread stopped
  786. # Input: EX|clkey|SHUTDOWN|Tid
  787. # Output: EX, clkey, Tid, Stopped, All
  788. #
  789. Log3 $name, 1, "HMCCURPC: Received EX event. Thread $clkey terminated.";
  790. my $stopped = 0;
  791. my $all = 0;
  792. $rh->{$clkey}{state} = 'inactive';
  793. # Check if all threads were terminated. Set overall status
  794. if ($clkey ne 'DATA') {
  795. ($stopped, $all) = HMCCURPC_CleanupThreads ($hash, $HMCCURPC_THREAD_SERVER, 'inactive');
  796. if ($stopped == $all) {
  797. # Terminate data processing thread if all server threads stopped
  798. Log3 $name, 2, "HMCCURPC: All RPC servers stopped. Terminating data processing thread";
  799. HMCCURPC_TerminateThreads ($hash, $HMCCURPC_THREAD_DATA);
  800. sleep (1);
  801. }
  802. }
  803. else {
  804. ($stopped, $all) = HMCCURPC_CleanupThreads ($hash, $HMCCURPC_THREAD_DATA, '.*');
  805. if ($stopped == $all) {
  806. HMCCURPC_CleanupThreadIO ($hash);
  807. HMCCURPC_ResetRPCState ($hash, "OK");
  808. RemoveInternalTimer ($hash);
  809. Log3 $name, 1, "HMCCURPC: All threads stopped";
  810. DoTrigger ($name, "RPC server stopped");
  811. }
  812. else {
  813. Log3 $name, 1, "HMCCURPC: Data processing thread still running";
  814. }
  815. }
  816. return ($et, $clkey, $t[1], $stopped, $all);
  817. }
  818. elsif ($et eq 'ND') {
  819. #
  820. # CCU device added
  821. # Input: ND|clkey|C/D|Address|Type|Version|Firmware|RxMode
  822. # Output: ND, clkey, DevAdd, C/D, Type, Version, Firmware, RxMode
  823. #
  824. return ($et, $clkey, $t[1], $t[0], $t[2], $t[3], $t[4], $t[5]);
  825. }
  826. elsif ($et eq 'DD' || $et eq 'RA') {
  827. #
  828. # CCU device deleted or readded
  829. # Input: {DD,RA}|clkey|Address
  830. # Output: {DD,RA}, clkey, DevAdd
  831. #
  832. return ($et, $clkey, $t[0]);
  833. }
  834. elsif ($et eq 'UD') {
  835. #
  836. # CCU device updated
  837. # Input: UD|clkey|Address|Hint
  838. # Output: UD, clkey, DevAdd, Hint
  839. #
  840. return ($et, $clkey, $t[0], $t[1]);
  841. }
  842. elsif ($et eq 'RD') {
  843. #
  844. # CCU device replaced
  845. # Input: RD|clkey|Address1|Address2
  846. # Output: RD, clkey, Address1, Address2
  847. #
  848. return ($et, $clkey, $t[0], $t[1]);
  849. }
  850. elsif ($et eq 'ST') {
  851. #
  852. # Statistic data. Store snapshots of sent events.
  853. # Input: ST|clkey|nTotal|nEV|nND|nDD|nRD|nRA|nUD|nIN|nEX|nSL
  854. # Output: ST, clkey, ...
  855. #
  856. my @res = ($et, $clkey);
  857. push (@res, @t);
  858. my $total = shift @t;
  859. my @eventtypes = ("EV", "ND", "DD", "RD", "RA", "UD", "IN", "EX", "SL", "TO");
  860. for (my $i=0; $i<scalar(@eventtypes); $i++) {
  861. $hash->{hmccu}{rpc}{$clkey}{snd}{$eventtypes[$i]} += $t[$i];
  862. }
  863. return @res;
  864. }
  865. elsif ($et eq 'TO') {
  866. #
  867. # Event timeout
  868. # Input: TO|clkey|Time
  869. # Output: TO, clkey, Port, Time
  870. #
  871. Log3 $name, 2, "HMCCURPC: Received no events from interface $clkey for ".$t[0]." seconds";
  872. DoTrigger ($name, "No events from interface $clkey for ".$t[0]." seconds");
  873. return ($et, $clkey, $hash->{hmccu}{rpc}{$clkey}{port}, $t[0]);
  874. }
  875. return undef;
  876. }
  877. ######################################################################
  878. # Get list of RPC interfaces.
  879. # If no interfaces defined in HMCCURPC device get interfaces list from
  880. # I/O device.
  881. ######################################################################
  882. sub HMCCURPC_GetRPCInterfaceList ($)
  883. {
  884. my ($hash) = @_;
  885. my @interfaces = ($HMCCURPC_RPC_INTERFACE_DEFAULT);
  886. my $hmccu_hash = HMCCURPC_FindHMCCUDevice ($hash);
  887. return @interfaces if (!defined ($hmccu_hash));
  888. if (defined ($hash->{hmccu}{rpcports})) {
  889. foreach my $p (split (',', $hash->{hmccu}{rpcports})) {
  890. my $ifname = HMCCU_GetRPCServerInfo ($hmccu_hash, $p, 'name');
  891. next if (!defined ($ifname) || $ifname eq $HMCCURPC_RPC_INTERFACE_DEFAULT);
  892. push (@interfaces, $ifname);
  893. }
  894. }
  895. else {
  896. @interfaces = HMCCU_GetRPCInterfaceList ($hmccu_hash);
  897. }
  898. return @interfaces;
  899. }
  900. ######################################################################
  901. # Get list of RPC ports.
  902. # If no ports defined in HMCCURPC device get port list from I/O
  903. # device.
  904. ######################################################################
  905. sub HMCCURPC_GetRPCPortList ($)
  906. {
  907. my ($hash) = @_;
  908. my @ports = ($HMCCURPC_RPC_PORT_DEFAULT);
  909. if (defined ($hash->{hmccu}{rpcports})) {
  910. @ports = split (',', $hash->{hmccu}{rpcports});
  911. }
  912. else {
  913. my $hmccu_hash = HMCCURPC_FindHMCCUDevice ($hash);
  914. if (defined ($hmccu_hash)) {
  915. @ports = HMCCU_GetRPCPortList ($hmccu_hash);
  916. }
  917. }
  918. return @ports;
  919. }
  920. ######################################################################
  921. # Get attribute with fallback to I/O device attribute
  922. ######################################################################
  923. sub HMCCURPC_GetAttribute ($$$$)
  924. {
  925. my ($hash, $attr, $ioattr, $default) = @_;
  926. my $name = $hash->{NAME};
  927. my $value = AttrVal ($hash->{NAME}, $attr, 'null');
  928. return $value if ($value ne 'null');
  929. my $hmccu_hash = HMCCURPC_FindHMCCUDevice ($hash);
  930. if (defined ($hmccu_hash)) {
  931. $value = AttrVal ($hmccu_hash->{NAME}, $ioattr, 'null');
  932. return $value if ($value ne 'null');
  933. }
  934. return $default;
  935. }
  936. ######################################################################
  937. # Get event timeout for interface
  938. ######################################################################
  939. sub HMCCURPC_GetEventTimeout ($$)
  940. {
  941. my ($evttimeout, $interface) = @_;
  942. return $evttimeout if ($evttimeout =~ /^[0-9]+$/);
  943. my $seconds = -1;
  944. my $defseconds = $HMCCURPC_TIMEOUT_EVENT;
  945. foreach my $to (split (',', $evttimeout)) {
  946. my ($toint, $tosec) = split (':', $to);
  947. if (!defined ($tosec)) {
  948. $defseconds = $toint if ($toint =~ /^[0-9]+$/);
  949. }
  950. else {
  951. return $tosec if ($toint eq $interface && $tosec =~ /^[0-9]+$/);
  952. }
  953. }
  954. return $defseconds;
  955. }
  956. ######################################################################
  957. # Register RPC callbacks at CCU if RPC-Server is in state
  958. # 'working'.
  959. # Return number of registered callbacks.
  960. ######################################################################
  961. sub HMCCURPC_RegisterCallback ($)
  962. {
  963. my ($hash) = @_;
  964. my $name = $hash->{NAME};
  965. my @rpcports = HMCCURPC_GetRPCPortList ($hash);
  966. my $regcount = 0;
  967. foreach my $port (@rpcports) {
  968. my ($rc, $msg) = HMCCURPC_RegisterSingleCallback ($hash, $port, 0);
  969. Log3 $name, 1, "HMCCURPC: $msg";
  970. $regcount++ if ($rc);
  971. }
  972. return $regcount;
  973. }
  974. ######################################################################
  975. # Register callback for specified CCU interface port.
  976. # If parameter 'force' is 1 callback will be registered even if state
  977. # is "running". State will not be modified.
  978. # Return 0 on error.
  979. ######################################################################
  980. sub HMCCURPC_RegisterSingleCallback ($$$)
  981. {
  982. my ($hash, $port, $force) = @_;
  983. my $name = $hash->{NAME};
  984. return (0, "Can't find IO device") if (!exists ($hash->{IODev}));
  985. my $hmccu_hash = $hash->{IODev};
  986. my $localaddr = $hash->{hmccu}{localaddr};
  987. my $clkey = 'CB'.$port;
  988. return (0, "RPC server $clkey not found") if (!exists ($hash->{hmccu}{rpc}{$clkey}));
  989. return (0, "RPC server $clkey not in state working") if ($hash->{hmccu}{rpc}{$clkey}{state} ne 'working' && $force == 0);
  990. my $cburl = HMCCU_GetRPCCallbackURL ($hash, $localaddr, $hash->{hmccu}{rpc}{$clkey}{cbport}, $clkey, $port);
  991. my $clurl = HMCCU_GetRPCServerInfo ($hmccu_hash, $port, 'url');
  992. my $rpctype = HMCCU_GetRPCServerInfo ($hmccu_hash, $port, 'type');
  993. return (0, "Can't get RPC parameters for ID $clkey") if (!defined ($cburl) || !defined ($clurl) || !defined ($rpctype));
  994. my $rpcflags = HMCCU_GetRPCServerInfo ($hmccu_hash, $port, 'flags');
  995. $hash->{hmccu}{rpc}{$clkey}{port} = $port;
  996. $hash->{hmccu}{rpc}{$clkey}{clurl} = $clurl;
  997. $hash->{hmccu}{rpc}{$clkey}{cburl} = $cburl;
  998. Log3 $name, 2, "HMCCURPC: Registering callback $cburl with ID $clkey at $clurl";
  999. my $rc;
  1000. if ($rpctype eq 'A') {
  1001. $rc = HMCCURPC_SendRequest ($hash, $port, "init", $cburl, $clkey);
  1002. }
  1003. else {
  1004. $rc = HMCCURPC_SendBinRequest ($hash, $port, "init",
  1005. $BINRPC_STRING, $cburl, $BINRPC_STRING, $clkey);
  1006. }
  1007. if (defined ($rc)) {
  1008. if ($force == 0) {
  1009. $hash->{hmccu}{rpc}{$clkey}{state} = $rpcflags =~ /forceInit/ ? 'running' : 'registered';
  1010. }
  1011. return (1, "RPC callback with URL $cburl for ID $clkey registered");
  1012. }
  1013. else {
  1014. return (0, "Failed to register callback for ID $clkey");
  1015. }
  1016. }
  1017. ######################################################################
  1018. # Deregister RPC callbacks at CCU
  1019. # Return number of deregistered callbacks
  1020. ######################################################################
  1021. sub HMCCURPC_DeRegisterCallback ($)
  1022. {
  1023. my ($hash) = @_;
  1024. my $name = $hash->{NAME};
  1025. my $deregcount = 0;
  1026. foreach my $clkey (keys %{$hash->{hmccu}{rpc}}) {
  1027. my $rpchash = \%{$hash->{hmccu}{rpc}{$clkey}};
  1028. if (defined ($rpchash->{port})) {
  1029. $deregcount++ if (HMCCURPC_DeRegisterSingleCallback ($hash, $rpchash->{port}, 0));
  1030. }
  1031. }
  1032. return $deregcount;
  1033. }
  1034. ######################################################################
  1035. # Deregister single RPC callback
  1036. ######################################################################
  1037. sub HMCCURPC_DeRegisterSingleCallback ($$$)
  1038. {
  1039. my ($hash, $port, $force) = @_;
  1040. my $name = $hash->{NAME};
  1041. return 0 if (!exists ($hash->{IODev}));
  1042. my $hmccu_hash = $hash->{IODev};
  1043. my $clkey = 'CB'.$port;
  1044. my $localaddr = $hash->{hmccu}{localaddr};
  1045. my $cburl = '';
  1046. my $clurl = '';
  1047. my $rpchash;
  1048. if (exists ($hash->{hmccu}{rpc}{$clkey})) {
  1049. $rpchash = \%{$hash->{hmccu}{rpc}{$clkey}};
  1050. return 0 if ($rpchash->{state} ne 'registered' && $rpchash->{state} ne 'running' && $force == 0);
  1051. $cburl = $rpchash->{cburl} if (exists ($rpchash->{cburl}));
  1052. $clurl = $rpchash->{clurl} if (exists ($rpchash->{clurl}));
  1053. }
  1054. else {
  1055. return 0 if ($force == 0);
  1056. }
  1057. $cburl = HMCCU_GetRPCCallbackURL ($hash, $localaddr, $rpchash->{cbport}, $clkey, $port) if ($cburl eq '');
  1058. $clurl = HMCCU_GetRPCServerInfo ($hmccu_hash, $port, 'url') if ($clurl eq '');
  1059. return 0 if ($cburl eq '' || $clurl eq '');
  1060. Log3 $name, 1, "HMCCURPC: Deregistering RPC server $cburl with ID $clkey at $clurl";
  1061. if (HMCCU_IsRPCType ($hmccu_hash, $port, 'A')) {
  1062. HMCCURPC_SendRequest ($hash, $port, "init", $cburl);
  1063. }
  1064. else {
  1065. HMCCURPC_SendBinRequest ($hash, $port, "init", $BINRPC_STRING, $cburl);
  1066. }
  1067. if (defined ($rpchash)) {
  1068. $rpchash->{port} = 0;
  1069. $rpchash->{cburl} = '';
  1070. $rpchash->{clurl} = '';
  1071. $rpchash->{cbport} = 0;
  1072. $rpchash->{state} = 'deregistered';
  1073. }
  1074. Log3 $name, 1, "HMCCURPC: RPC callback for server $clkey deregistered";
  1075. return 1;
  1076. }
  1077. ######################################################################
  1078. # Initialize RPC server for specified CCU port
  1079. # Return server object or undef on error
  1080. ######################################################################
  1081. sub HMCCURPC_InitRPCServer ($$$$)
  1082. {
  1083. my ($name, $serverport, $callbackport, $prot) = @_;
  1084. my $clkey = 'CB'.$serverport;
  1085. my $server;
  1086. # Create binary RPC server
  1087. if ($prot eq 'B') {
  1088. $server->{__daemon} = IO::Socket::INET->new (LocalPort => $callbackport,
  1089. Type => SOCK_STREAM, Reuse => 1, Listen => SOMAXCONN);
  1090. if (!($server->{__daemon})) {
  1091. Log3 $name, 1, "HMCCURPC: Can't create RPC callback server $clkey on port $callbackport. Port in use?";
  1092. return undef;
  1093. }
  1094. return $server;
  1095. }
  1096. # Create ASCII RPC server
  1097. $server = RPC::XML::Server->new (port => $callbackport);
  1098. if (!ref($server)) {
  1099. Log3 $name, 1, "HMCCURPC: Can't create RPC callback server $clkey on port $callbackport. Port in use?";
  1100. return undef;
  1101. }
  1102. Log3 $name, 2, "HMCCURPC: Callback server $clkey created. Listening on port $callbackport";
  1103. # Add callbacks
  1104. # Signature is: ReturnType ParType1 ...
  1105. # ReturnType void = string
  1106. # Server parameter is not part of the signature!
  1107. # Callback for events
  1108. # Parameters: Server, InterfaceId, Address, ValueKey, Value
  1109. Log3 $name, 4, "HMCCURPC: Adding callback for events for server $clkey";
  1110. $server->add_method (
  1111. { name=>"event",
  1112. signature=>["string string string string string","string string string string int",
  1113. "string string string string double","string string string string boolean",
  1114. "string string string string i4"],
  1115. code=>\&HMCCURPC_EventCB
  1116. }
  1117. );
  1118. # Callback for new devices
  1119. # Parameters: Server, InterfaceId, DeviceDescriptions[]
  1120. Log3 $name, 4, "HMCCURPC: Adding callback for new devices for server $clkey";
  1121. $server->add_method (
  1122. { name=>"newDevices",
  1123. signature=>["string string array"],
  1124. code=>\&HMCCURPC_NewDevicesCB
  1125. }
  1126. );
  1127. # Callback for deleted devices
  1128. # Parameters: Server, InterfaceId, Addresses[]
  1129. Log3 $name, 4, "HMCCURPC: Adding callback for deleted devices for server $clkey";
  1130. $server->add_method (
  1131. { name=>"deleteDevices",
  1132. signature=>["string string array"],
  1133. code=>\&HMCCURPC_DeleteDevicesCB
  1134. }
  1135. );
  1136. # Callback for modified devices
  1137. # Parameters: Server, InterfaceId, Address, Hint
  1138. Log3 $name, 4, "HMCCURPC: Adding callback for modified devices for server $clkey";
  1139. $server->add_method (
  1140. { name=>"updateDevice",
  1141. signature=>["string string string int"],
  1142. code=>\&HMCCURPC_UpdateDeviceCB
  1143. }
  1144. );
  1145. # Callback for replaced devices
  1146. # Parameters: Server, InterfaceId, OldAddress, NewAddress
  1147. Log3 $name, 4, "HMCCURPC: Adding callback for replaced devices for server $clkey";
  1148. $server->add_method (
  1149. { name=>"replaceDevice",
  1150. signature=>["string string string string"],
  1151. code=>\&HMCCURPC_ReplaceDeviceCB
  1152. }
  1153. );
  1154. # Callback for readded devices
  1155. # Parameters: Server, InterfaceId, Addresses[]
  1156. Log3 $name, 4, "HMCCURPC: Adding callback for readded devices for server $clkey";
  1157. $server->add_method (
  1158. { name=>"readdedDevice",
  1159. signature=>["string string array"],
  1160. code=>\&HMCCURPC_ReaddDeviceCB
  1161. }
  1162. );
  1163. # Dummy implementation, always return an empty array
  1164. # Parameters: Server, InterfaceId
  1165. Log3 $name, 4, "HMCCURPC: Adding callback for list devices for server $clkey";
  1166. $server->add_method (
  1167. { name=>"listDevices",
  1168. signature=>["array string"],
  1169. code=>\&HMCCURPC_ListDevicesCB
  1170. }
  1171. );
  1172. return $server;
  1173. }
  1174. ######################################################################
  1175. # Start RPC server threads
  1176. # 1 thread for processing event data in event queue
  1177. # 1 thread per CCU RPC interface for receiving data
  1178. # Return number of started RPC server threads or 0 on error.
  1179. ######################################################################
  1180. sub HMCCURPC_StartRPCServer ($)
  1181. {
  1182. my ($hash) = @_;
  1183. my $name = $hash->{NAME};
  1184. return (0, "Can't find HMCCU I/O device") if (!exists ($hash->{IODev}));
  1185. my $hmccu_hash = $hash->{IODev};
  1186. my @hm_pids = ();
  1187. my @ex_pids = ();
  1188. return (0, "RPC server already running for device ".$hmccu_hash->{NAME})
  1189. if (HMCCU_IsRPCServerRunning ($hmccu_hash, \@hm_pids, \@ex_pids));
  1190. # Get parameters and attributes
  1191. my %thrpar;
  1192. my @rpcports = HMCCURPC_GetRPCPortList ($hash);
  1193. my $localaddr = HMCCURPC_GetAttribute ($hash, 'rpcServerAddr', 'rpcserveraddr', '');
  1194. my $rpcserverport = HMCCURPC_GetAttribute ($hash, 'rpcServerPort', 'rpcserverport', $HMCCURPC_SERVER_PORT);
  1195. my $evttimeout = HMCCURPC_GetAttribute ($hash, 'rpcEventTimeout', 'rpcevtimeout', $HMCCURPC_TIMEOUT_EVENT);
  1196. my $ccuflags = AttrVal ($name, 'ccuflags', 'null');
  1197. $thrpar{socktimeout} = AttrVal ($name, 'rpcWriteTimeout', $HMCCURPC_TIMEOUT_WRITE);
  1198. $thrpar{conntimeout} = AttrVal ($name, 'rpcConnTimeout', $HMCCURPC_TIMEOUT_CONNECTION);
  1199. $thrpar{acctimeout} = AttrVal ($name, 'rpcAcceptTimeout', $HMCCURPC_TIMEOUT_ACCEPT);
  1200. $thrpar{waittime} = AttrVal ($name, 'rpcWaitTime', $HMCCURPC_TIME_WAIT);
  1201. $thrpar{queuesize} = AttrVal ($name, 'rpcQueueSize', $HMCCURPC_MAX_QUEUESIZE);
  1202. $thrpar{triggertime} = AttrVal ($name, 'rpcTriggerTime', $HMCCURPC_TIME_TRIGGER);
  1203. $thrpar{statistics} = AttrVal ($name, 'rpcStatistics', $HMCCURPC_STATISTICS);
  1204. $thrpar{name} = $name;
  1205. my $ccunum = $hash->{CCUNum};
  1206. my $serveraddr = HMCCU_GetRPCServerInfo ($hmccu_hash, $rpcports[0], 'host');
  1207. my @eventtypes = ("EV", "ND", "DD", "RD", "RA", "UD", "IN", "EX", "SL", "TO");
  1208. # Get or detect local IP address
  1209. if ($localaddr eq '') {
  1210. my $socket = IO::Socket::INET->new (PeerAddr => $serveraddr, PeerPort => $rpcports[0]);
  1211. return (0, "Can't connect to RPC host $serveraddr port ".$rpcports[0]) if (!$socket);
  1212. $localaddr = $socket->sockhost ();
  1213. close ($socket);
  1214. }
  1215. $hash->{hmccu}{localaddr} = $localaddr;
  1216. # Create socket pair for communication between data processing thread and FHEM
  1217. my ($sockchild, $sockparent);
  1218. return (0, "Can't create I/O socket pair") if (!socketpair ($sockchild, $sockparent,
  1219. AF_UNIX, SOCK_STREAM, PF_UNSPEC));
  1220. $sockchild->autoflush (1);
  1221. $sockparent->autoflush (1);
  1222. $hash->{hmccu}{sockchild} = $sockchild;
  1223. $hash->{hmccu}{sockparent} = $sockparent;
  1224. my $fd_child = fileno $sockchild;
  1225. my $fd_parent = fileno $sockparent;
  1226. # Enable FHEM I/O
  1227. my $pid = $$;
  1228. $hash->{FD} = $fd_child;
  1229. $selectlist{"RPC.$name.$pid"} = $hash;
  1230. # Create event data queue
  1231. my $equeue = Thread::Queue->new ();
  1232. $hash->{hmccu}{eventqueue} = $equeue;
  1233. # Create queue for controlling data processing
  1234. my $rqueue = Thread::Queue->new ();
  1235. $hash->{hmccu}{readqueue} = $rqueue;
  1236. # Start thread for data processing
  1237. Log3 $name, 2, "HMCCURPC: Starting thread for data processing";
  1238. my $pthread = threads->create ('HMCCURPC_ProcessData', $equeue, $rqueue, $sockparent, \%thrpar);
  1239. return (0, "Can't start data processing thread") if (!defined ($pthread));
  1240. Log3 $name, 2, "HMCCURPC: Started thread for data processing. TID=" . $pthread->tid ();
  1241. $pthread->detach ();
  1242. $hash->{hmccu}{rpc}{DATA}{type} = $HMCCURPC_THREAD_DATA;
  1243. $hash->{hmccu}{rpc}{DATA}{child} = $pthread;
  1244. $hash->{hmccu}{rpc}{DATA}{cbport} = 0;
  1245. $hash->{hmccu}{rpc}{DATA}{tid} = $pthread->tid ();
  1246. $hash->{hmccu}{rpc}{DATA}{state} = 'initialized';
  1247. # Reset state of all RPC server threads
  1248. foreach my $clkey (keys %{$hash->{hmccu}{rpc}}) {
  1249. $hash->{hmccu}{rpc}{$clkey}{state} = 'inactive';
  1250. }
  1251. # Initialize RPC servers
  1252. my @hm_tids;
  1253. my $err = '';
  1254. foreach my $port (@rpcports) {
  1255. my $clkey = 'CB'.$port;
  1256. my $callbackport = $rpcserverport+$port+($ccunum*10);
  1257. my $interface = HMCCU_GetRPCServerInfo ($hmccu_hash, $port, 'name');
  1258. my $flags = HMCCU_GetRPCServerInfo ($hmccu_hash, $port, 'flags');
  1259. # Additional interface specific thread parameters
  1260. $thrpar{interface} = $interface;
  1261. $thrpar{flags} = $flags;
  1262. $thrpar{type} = HMCCU_GetRPCServerInfo ($hmccu_hash, $port, 'type');
  1263. $thrpar{evttimeout} = HMCCURPC_GetEventTimeout ($evttimeout, $interface);
  1264. # Start RPC server thread
  1265. my $thr = threads->create ('HMCCURPC_HandleConnection',
  1266. $port, $callbackport, $equeue, \%thrpar);
  1267. if (!defined ($thr)) {
  1268. $err = "Can't create RPC server thread for interface $interface";
  1269. last;
  1270. }
  1271. $thr->detach ();
  1272. Log3 $name, 2, "HMCCURPC: RPC server thread started for interface $interface with TID=".
  1273. $thr->tid ();
  1274. # Store thread parameters
  1275. $hash->{hmccu}{rpc}{$clkey}{type} = HMCCU_IsRPCType ($hmccu_hash, $port, 'B') ?
  1276. $HMCCURPC_THREAD_BINARY : $HMCCURPC_THREAD_ASCII;
  1277. $hash->{hmccu}{rpc}{$clkey}{flags} = $flags;
  1278. $hash->{hmccu}{rpc}{$clkey}{child} = $thr;
  1279. $hash->{hmccu}{rpc}{$clkey}{cbport} = $callbackport;
  1280. $hash->{hmccu}{rpc}{$clkey}{tid} = $thr->tid ();
  1281. $hash->{hmccu}{rpc}{$clkey}{state} = 'initialized';
  1282. push (@hm_tids, $thr->tid ());
  1283. # Reset statistic counter
  1284. foreach my $et (@eventtypes) {
  1285. $hash->{hmccu}{rpc}{$clkey}{rec}{$et} = 0;
  1286. $hash->{hmccu}{rpc}{$clkey}{snd}{$et} = 0;
  1287. }
  1288. $hash->{hmccu}{rpc}{$clkey}{sumdelay} = 0;
  1289. }
  1290. sleep (1);
  1291. # Cleanup if one or more threads are not initialized (ignore thread state)
  1292. my ($run, $all) = HMCCURPC_CheckThreadState ($hash, $HMCCURPC_THREAD_ALL, '.*', undef);
  1293. if ($run != $all) {
  1294. Log3 $name, 0, "HMCCURPC: Only $run from $all threads are running. Cleaning up";
  1295. HMCCURPC_Housekeeping ($hash);
  1296. return (0, $err);
  1297. }
  1298. $hash->{RPCTID} = join (',', @hm_tids);
  1299. # $hash->{hmccu}{rpcstarttime} = time ();
  1300. # Trigger timer function for checking successful RPC start
  1301. # Timer will be removed if event 'IN' is reveived
  1302. InternalTimer (gettimeofday()+$HMCCURPC_INIT_INTERVAL3*$run, "HMCCURPC_IsRPCServerRunning",
  1303. $hash, 0);
  1304. HMCCURPC_SetRPCState ($hash, "starting", "RPC server(s) starting");
  1305. DoTrigger ($name, "RPC server starting");
  1306. return ($run, undef);
  1307. }
  1308. ######################################################################
  1309. # Set overall status if all RPC servers are running and update all
  1310. # FHEM devices.
  1311. # Return (running servers, updated devices, failed updates)
  1312. ######################################################################
  1313. sub HMCCURPC_RPCServerStarted ($$)
  1314. {
  1315. my ($hash, $hmccu_hash) = @_;
  1316. my $name = $hash->{NAME};
  1317. my $c_ok = 0;
  1318. my $c_err = 0;
  1319. # Check if all RPC servers were initialized. Set overall status
  1320. my ($run, $all) = HMCCURPC_CheckThreadState ($hash, $HMCCURPC_THREAD_ALL, 'running', undef);
  1321. if ($run == $all) {
  1322. # $hash->{hmccu}{rpcstarttime} = 0;
  1323. HMCCURPC_SetRPCState ($hash, "running", "All RPC servers running");
  1324. HMCCURPC_SetState ($hash, "OK");
  1325. # if (defined ($hmccu_hash)) {
  1326. # HMCCU_SetState ($hmccu_hash, "OK");
  1327. # ($c_ok, $c_err) = HMCCU_UpdateClients ($hmccu_hash, '.*', 'Attr', 0, undef);
  1328. # Log3 $name, 2, "HMCCURPC: Updated devices. Success=$c_ok Failed=$c_err";
  1329. # }
  1330. RemoveInternalTimer ($hash);
  1331. # DoTrigger ($name, "RPC server running");
  1332. }
  1333. return ($run, $c_ok, $c_err);
  1334. }
  1335. ######################################################################
  1336. # Stop I/O Handling
  1337. ######################################################################
  1338. sub HMCCURPC_CleanupThreadIO ($)
  1339. {
  1340. my ($hash) = @_;
  1341. my $name = $hash->{NAME};
  1342. my $pid = $$;
  1343. if (exists ($selectlist{"RPC.$name.$pid"})) {
  1344. Log3 $name, 2, "HMCCURPC: Stop I/O handling";
  1345. delete $selectlist{"RPC.$name.$pid"};
  1346. delete $hash->{FD} if (defined ($hash->{FD}));
  1347. }
  1348. if (defined ($hash->{hmccu}{sockchild})) {
  1349. Log3 $name, 2, "HMCCURPC: Close child socket";
  1350. $hash->{hmccu}{sockchild}->close ();
  1351. delete $hash->{hmccu}{sockchild};
  1352. }
  1353. if (defined ($hash->{hmccu}{sockparent})) {
  1354. Log3 $name, 2, "HMCCURPC: Close parent socket";
  1355. $hash->{hmccu}{sockparent}->close ();
  1356. delete $hash->{hmccu}{sockparent};
  1357. }
  1358. }
  1359. ######################################################################
  1360. # Terminate RPC server threads and data processing thread by sending
  1361. # an INT signal.
  1362. # Parameter mode specifies which threads should be terminated:
  1363. # 1 - Terminate data processing thread
  1364. # 2 - Terminate server threads
  1365. # 3 - Terminate all threads
  1366. # Number of threads with INT sent
  1367. ######################################################################
  1368. sub HMCCURPC_TerminateThreads ($$)
  1369. {
  1370. my ($hash, $mode) = @_;
  1371. my $name = $hash->{NAME};
  1372. my $count = 0;
  1373. foreach my $clkey (keys %{$hash->{hmccu}{rpc}}) {
  1374. next if ($hash->{hmccu}{rpc}{$clkey}{state} eq 'inactive');
  1375. next if (!($hash->{hmccu}{rpc}{$clkey}{type} & $mode));
  1376. next if (!exists ($hash->{hmccu}{rpc}{$clkey}{child}));
  1377. my $thr = $hash->{hmccu}{rpc}{$clkey}{child};
  1378. if (defined ($thr) && $thr->is_running ()) {
  1379. $hash->{hmccu}{rpc}{$clkey}{state} = "stopping";
  1380. Log3 $name, 2, "HMCCURPC: Sending signal INT to thread $clkey TID=".$thr->tid ();
  1381. $thr->kill ('INT');
  1382. $count++;
  1383. }
  1384. }
  1385. return $count;
  1386. }
  1387. ######################################################################
  1388. # Cleanup threads in specified state.
  1389. # Parameter state is a regular expression.
  1390. # Return number of deleted threads and number of active threads
  1391. ######################################################################
  1392. sub HMCCURPC_CleanupThreads ($$$)
  1393. {
  1394. my ($hash, $mode, $state) = @_;
  1395. my $name = $hash->{NAME};
  1396. my $ccuflags = AttrVal ($name, 'ccuflags', 'null');
  1397. my $count = 0;
  1398. my $all = 0;
  1399. # Check if threads has been stopped
  1400. my @thrlist = keys %{$hash->{hmccu}{rpc}};
  1401. foreach my $clkey (@thrlist) {
  1402. my $tst = $hash->{hmccu}{rpc}{$clkey}{state};
  1403. next if ($tst eq 'inactive');
  1404. next if (!($hash->{hmccu}{rpc}{$clkey}{type} & $mode));
  1405. $all++;
  1406. if (exists ($hash->{hmccu}{rpc}{$clkey}{child})) {
  1407. my $thr = $hash->{hmccu}{rpc}{$clkey}{child};
  1408. if (defined ($thr)) {
  1409. my $tid = $thr->tid();
  1410. if ($thr->is_running () || $tst !~ /$state/) {
  1411. Log3 $name, 1, "HMCCURPC: Thread $clkey with TID=$tid still running. Can't delete it";
  1412. next;
  1413. }
  1414. if ($ccuflags !~ /keepThreads/) {
  1415. if ($tst eq 'inactive' || $tst eq 'stopping') {
  1416. Log3 $name, 2, "HMCCURPC: Thread $clkey with TID=$tid stopped. Deleting it";
  1417. delete $hash->{hmccu}{rpc}{$clkey}{child};
  1418. }
  1419. else {
  1420. Log3 $name, 2, "HMCCURPC: Thread $clkey with TID=$tid is in state $tst. Can't delete it";
  1421. }
  1422. }
  1423. else {
  1424. Log3 $name, 2, "HMCCURPC: Flag keepThreads set. Keeping thread $clkey with TID=$tid";
  1425. }
  1426. # delete $hash->{hmccu}{rpc}{$clkey};
  1427. }
  1428. }
  1429. $count++;
  1430. }
  1431. return ($count, $all);
  1432. }
  1433. ######################################################################
  1434. # Count threads in specified state.
  1435. # Parameter state is a regular expression.
  1436. # Parameter mode specifies which threads should be counted:
  1437. # If state is empty thread state is ignored and only running threads
  1438. # are counted by calling thread function is_running().
  1439. # Return number of threads in specified state and total number of
  1440. # threads. Also return IDs of running threads if parameter tids is
  1441. # defined and parameter state is 'running' or '.*'.
  1442. ######################################################################
  1443. sub HMCCURPC_CheckThreadState ($$$$)
  1444. {
  1445. my ($hash, $mode, $state, $tids) = @_;
  1446. my $count = 0;
  1447. my $all = 0;
  1448. $mode = $HMCCURPC_THREAD_ALL if (!defined ($mode));
  1449. $state = '' if (!defined ($state));
  1450. foreach my $clkey (keys %{$hash->{hmccu}{rpc}}) {
  1451. my $tst = $hash->{hmccu}{rpc}{$clkey}{state};
  1452. next if ($tst eq 'inactive');
  1453. next if (!($hash->{hmccu}{rpc}{$clkey}{type} & $mode));
  1454. $all++;
  1455. if ($state eq 'running' || $state eq '.*') {
  1456. next if (!exists ($hash->{hmccu}{rpc}{$clkey}{child}));
  1457. my $thr = $hash->{hmccu}{rpc}{$clkey}{child};
  1458. if (defined ($thr) && $thr->is_running () && ($state eq '' || $tst =~ /$state/)) {
  1459. $count++;
  1460. push (@$tids, $thr->tid()) if (defined ($tids));
  1461. }
  1462. }
  1463. else {
  1464. $count++ if ($tst =~ /$state/);
  1465. }
  1466. }
  1467. return ($count, $all);
  1468. }
  1469. ######################################################################
  1470. # Timer function to check if all threads are running
  1471. ######################################################################
  1472. sub HMCCURPC_IsRPCServerRunning ($)
  1473. {
  1474. my ($hash) = @_;
  1475. my $name = $hash->{NAME};
  1476. Log3 $name, 2, "HMCCURPC: Checking if all threads are running";
  1477. my ($run, $all) = HMCCURPC_CheckThreadState ($hash, $HMCCURPC_THREAD_ALL, 'running', undef);
  1478. if ($run != $all) {
  1479. Log3 $name, 1, "HMCCURPC: Only $run of $all threads are running. Cleaning up";
  1480. HMCCURPC_Housekeeping ($hash);
  1481. return 0;
  1482. }
  1483. Log3 $name, 2, "HMCCURPC: $run of $all threads are running";
  1484. return 1;
  1485. }
  1486. ######################################################################
  1487. # Cleanup all threads
  1488. ######################################################################
  1489. sub HMCCURPC_Housekeeping ($)
  1490. {
  1491. my ($hash) = @_;
  1492. my $name = $hash->{NAME};
  1493. Log3 $name, 1, "HMCCURPC: Housekeeping called. Cleaning up RPC environment";
  1494. # Deregister callback URLs in CCU
  1495. HMCCURPC_DeRegisterCallback ($hash);
  1496. # Stop I/O handling
  1497. HMCCURPC_CleanupThreadIO ($hash);
  1498. my $count = HMCCURPC_TerminateThreads ($hash, $HMCCURPC_THREAD_ALL);
  1499. sleep (1) if ($count > 0);
  1500. my ($del, $total) = HMCCURPC_CleanupThreads ($hash, $HMCCURPC_THREAD_ALL, '.*');
  1501. $count = $total-$del;
  1502. if ($count == 0) {
  1503. HMCCURPC_ResetRPCState ($hash, "OK");
  1504. }
  1505. else {
  1506. HMCCURPC_SetRPCState ($hash, "error", "Clean up failed for $count threads");
  1507. }
  1508. }
  1509. ######################################################################
  1510. # Stop RPC server threads
  1511. # Data processing thread is stopped when receiving 'EX' event.
  1512. ######################################################################
  1513. sub HMCCURPC_StopRPCServer ($)
  1514. {
  1515. my ($hash) = @_;
  1516. my $name = $hash->{NAME};
  1517. my ($run, $all) = HMCCURPC_CheckThreadState ($hash, $HMCCURPC_THREAD_ALL, 'running', undef);
  1518. if ($run > 0) {
  1519. HMCCURPC_SetRPCState ($hash, "stopping", "Found $run threads. Stopping ...");
  1520. # Deregister callback URLs in CCU
  1521. HMCCURPC_DeRegisterCallback ($hash);
  1522. # Stop RPC server threads
  1523. HMCCURPC_TerminateThreads ($hash, $HMCCURPC_THREAD_SERVER);
  1524. # Trigger timer function for checking successful RPC stop
  1525. # Timer will be removed wenn receiving EX event from data processing thread
  1526. InternalTimer (gettimeofday()+$HMCCURPC_INIT_INTERVAL3*$all, "HMCCURPC_Housekeeping",
  1527. $hash, 0);
  1528. # Give threads the chance to terminate
  1529. sleep (1);
  1530. }
  1531. elsif ($run == 0 && $hash->{RPCState} ne 'inactive') {
  1532. Log3 $name, 2, "HMCCURPC: Found no running threads. Cleaning up ...";
  1533. HMCCURPC_CleanupThreadIO ($hash);
  1534. HMCCURPC_CleanupThreads ($hash, $HMCCURPC_THREAD_ALL, '.*');
  1535. HMCCURPC_ResetRPCState ($hash, "OK");
  1536. }
  1537. return 1;
  1538. }
  1539. ######################################################################
  1540. # Send ascii RPC request to CCU
  1541. # Return response or undef on error.
  1542. ######################################################################
  1543. sub HMCCURPC_SendRequest ($@)
  1544. {
  1545. my ($hash, $port, $request, @param) = @_;
  1546. my $name = $hash->{NAME};
  1547. return undef if (!exists ($hash->{IODev}));
  1548. return undef if (!HMCCU_IsRPCType ($hash->{IODev}, $port, 'A'));
  1549. my $clurl = HMCCU_GetRPCServerInfo ($hash->{IODev}, $port, 'url');
  1550. return undef if (!defined ($clurl));
  1551. Log3 $name, 4, "HMCCURPC: Send ASCII RPC request $request to $clurl";
  1552. my $rpcclient = RPC::XML::Client->new ($clurl);
  1553. my $rc = $rpcclient->simple_request ($request, @param);
  1554. Log3 $name, 2, "HMCCURPC: RPC request error ".$RPC::XML::ERROR if (!defined ($rc));
  1555. return $rc;
  1556. }
  1557. ######################################################################
  1558. # Send binary RPC request to CCU
  1559. # Return response or undef on error. Return empty string on missing
  1560. # server response.
  1561. ######################################################################
  1562. sub HMCCURPC_SendBinRequest ($@)
  1563. {
  1564. my ($hash, $port, $request, @param) = @_;
  1565. my $name = $hash->{NAME};
  1566. return undef if (!exists ($hash->{IODev}));
  1567. return undef if (!HMCCU_IsRPCType ($hash->{IODev}, $port, 'B'));
  1568. my $serveraddr = HMCCU_GetRPCServerInfo ($hash->{IODev}, $port, 'host');
  1569. return undef if (!defined ($serveraddr));
  1570. my $verbose = GetVerbose ($name);
  1571. Log3 $name, 4, "HMCCURPC: Send binary RPC request $request to $serveraddr:$port";
  1572. my $encreq = HMCCURPC_EncodeRequest ($request, \@param);
  1573. return undef if ($encreq eq '');
  1574. # auto-flush on socket
  1575. $| = 1;
  1576. # create a connecting socket
  1577. my $socket = new IO::Socket::INET (PeerHost => $serveraddr, PeerPort => $port,
  1578. Proto => 'tcp');
  1579. return undef if (!$socket);
  1580. my $size = $socket->send ($encreq);
  1581. if (defined ($size)) {
  1582. my $encresp = <$socket>;
  1583. $socket->close ();
  1584. if (defined ($encresp)) {
  1585. Log3 $name, 4, "HMCCURPC: Response";
  1586. HMCCURPC_HexDump ($name, $encresp) if ($verbose >= 4);
  1587. my ($response, $rc) = HMCCURPC_DecodeResponse ($encresp);
  1588. return $response;
  1589. }
  1590. else {
  1591. return '';
  1592. }
  1593. }
  1594. $socket->close ();
  1595. return undef;
  1596. }
  1597. ######################################################################
  1598. # Process binary RPC request
  1599. ######################################################################
  1600. sub HMCCURPC_ProcessRequest ($$$)
  1601. {
  1602. my ($server, $connection, $rpcflags) = @_;
  1603. my $name = $server->{hmccu}{name};
  1604. my $clkey = $server->{hmccu}{clkey};
  1605. my $port = $server->{hmccu}{port};
  1606. my @methodlist = ('listDevices', 'system.listMethods', 'system.multicall');
  1607. my $verbose = GetVerbose ($name);
  1608. # Read request
  1609. my $request = '';
  1610. while (my $packet = <$connection>) {
  1611. $request .= $packet;
  1612. }
  1613. return if (!defined ($request) || $request eq '');
  1614. Log3 $name, 4, "CCURPC: $clkey raw request:";
  1615. HMCCURPC_HexDump ($name, $request) if ($verbose >= 4);
  1616. # Decode request
  1617. my ($method, $params) = HMCCURPC_DecodeRequest ($request);
  1618. return if (!defined ($method));
  1619. Log3 $name, 4, "CCURPC: request method = $method";
  1620. if ($method =~ /listmethods/i) {
  1621. $connection->send (HMCCURPC_EncodeResponse ($BINRPC_ARRAY, \@methodlist));
  1622. }
  1623. elsif ($method =~ /listdevices/i) {
  1624. HMCCURPC_ListDevicesCB ($server, $clkey);
  1625. $connection->send (HMCCURPC_EncodeResponse ($BINRPC_ARRAY, undef));
  1626. }
  1627. elsif ($method eq 'system.multicall') {
  1628. return if (ref ($params) ne 'ARRAY');
  1629. my $a = $$params[0];
  1630. foreach my $s (@$a) {
  1631. next if (!exists ($s->{methodName}) || !exists ($s->{params}));
  1632. next if ($s->{methodName} ne 'event');
  1633. next if (scalar (@{$s->{params}}) < 4);
  1634. HMCCURPC_EventCB ($server, $clkey,
  1635. ${$s->{params}}[1], ${$s->{params}}[2], ${$s->{params}}[3]);
  1636. Log3 $name, 4, "CCURPC: Event ".${$s->{params}}[1]." ".${$s->{params}}[2]." "
  1637. .${$s->{params}}[3];
  1638. }
  1639. }
  1640. }
  1641. ######################################################################
  1642. # Thread function for handling incoming RPC requests
  1643. # thrpar - Hash reference with thread parameters:
  1644. # waittime - Time to wait after each loop in microseconds
  1645. # name - FHEM module name for log function
  1646. # socktimeout - Time to wait for socket to become ready
  1647. # queuesize - Maximum number of queue entries
  1648. # triggertime - Time to wait before retriggering I/O
  1649. ######################################################################
  1650. sub HMCCURPC_HandleConnection ($$$$)
  1651. {
  1652. my ($port, $callbackport, $queue, $thrpar) = @_;
  1653. my $name = $thrpar->{name};
  1654. my $evttimeout = $thrpar->{evttimeout};
  1655. my $conntimeout = $thrpar->{conntimeout};
  1656. my $iface = $thrpar->{interface};
  1657. my $rpcflags = $thrpar->{flags};
  1658. my $prot = $thrpar->{type};
  1659. my $run = 1;
  1660. my $tid = threads->tid ();
  1661. my $clkey = 'CB'.$port;
  1662. my @eventtypes = ("EV", "ND", "DD", "RD", "RA", "UD", "IN", "EX", "SL", "TO");
  1663. # Initialize RPC server
  1664. Log3 $name, 2, "CCURPC: Initializing RPC server $clkey for interface $iface.";
  1665. my $rpcsrv = HMCCURPC_InitRPCServer ($name, $port, $callbackport, $prot);
  1666. if (!defined ($rpcsrv)) {
  1667. Log3 $name, 1, "CCURPC: Can't initialize RPC server $clkey for interface $iface";
  1668. return;
  1669. }
  1670. if (!($rpcsrv->{__daemon})) {
  1671. Log3 $name, 1, "CCURPC: Server socket not found for port $port";
  1672. return;
  1673. }
  1674. # Store RPC server parameters
  1675. $rpcsrv->{hmccu}{name} = $name;
  1676. $rpcsrv->{hmccu}{clkey} = $clkey;
  1677. $rpcsrv->{hmccu}{eventqueue} = $queue;
  1678. $rpcsrv->{hmccu}{queuesize} = $thrpar->{queuesize};
  1679. $rpcsrv->{hmccu}{statistics} = $thrpar->{statistics};
  1680. $rpcsrv->{hmccu}{running} = 0;
  1681. $rpcsrv->{hmccu}{evttime} = time ();
  1682. $rpcsrv->{hmccu}{port} = $port;
  1683. # Initialize statistic counters
  1684. foreach my $et (@eventtypes) {
  1685. $rpcsrv->{hmccu}{rec}{$et} = 0;
  1686. $rpcsrv->{hmccu}{snd}{$et} = 0;
  1687. }
  1688. $rpcsrv->{hmccu}{rec}{total} = 0;
  1689. $rpcsrv->{hmccu}{snd}{total} = 0;
  1690. $SIG{INT} = sub { $run = 0; };
  1691. HMCCURPC_Write ($rpcsrv, "SL", $clkey, $tid);
  1692. Log3 $name, 2, "CCURPC: $clkey accepting connections. TID=$tid, EventTimeout=$evttimeout";
  1693. # Send INIT to FHEM if flag forceInit ist set. Some RPC clients won't send a ListDevice
  1694. # request
  1695. if ($rpcflags =~ /forceInit/ && $rpcsrv->{hmccu}{running} == 0) {
  1696. $rpcsrv->{hmccu}{running} = 1;
  1697. # Log3 $name, 1, "CCURPC: RPC $clkey. Forced init to HMCCURPC";
  1698. # HMCCURPC_Write ($rpcsrv, "IN", $clkey, "INIT|1");
  1699. }
  1700. $rpcsrv->{__daemon}->timeout ($thrpar->{acctimeout});
  1701. while ($run) {
  1702. if ($evttimeout > 0) {
  1703. my $difftime = time()-$rpcsrv->{hmccu}{evttime};
  1704. HMCCURPC_Write ($rpcsrv, "TO", $clkey, $difftime) if ($difftime >= $evttimeout);
  1705. }
  1706. # Next statement blocks for timeout seconds
  1707. my $connection = $rpcsrv->{__daemon}->accept ();
  1708. next if (! $connection);
  1709. last if (! $run);
  1710. $connection->timeout ($conntimeout);
  1711. if ($prot eq 'A') {
  1712. Log3 $name, 4, "CCURPC: $clkey processing CCU request";
  1713. $rpcsrv->process_request ($connection);
  1714. }
  1715. else {
  1716. HMCCURPC_ProcessRequest ($rpcsrv, $connection, $rpcflags);
  1717. }
  1718. shutdown ($connection, 2);
  1719. close ($connection);
  1720. undef $connection;
  1721. }
  1722. close ($rpcsrv->{__daemon}) if ($prot eq 'B');
  1723. # Send statistic info
  1724. HMCCURPC_WriteStats ($rpcsrv, $clkey);
  1725. # Send exit information
  1726. HMCCURPC_Write ($rpcsrv, "EX", $clkey, "SHUTDOWN|$tid");
  1727. Log3 $name, 2, "CCURPC: RPC server $clkey stopped handling connections. TID=$tid";
  1728. # Log statistic counters
  1729. push (@eventtypes, 'EV');
  1730. foreach my $et (@eventtypes) {
  1731. Log3 $name, 4, "CCURPC: $clkey event type = $et: ".$rpcsrv->{hmccu}{rec}{$et};
  1732. }
  1733. return;
  1734. }
  1735. ######################################################################
  1736. # Check if file descriptor is writeable and write data.
  1737. # Only to inform FHEM I/O loop about data available in thread queue.
  1738. # Return 0 on error or trigger time.
  1739. ######################################################################
  1740. sub HMCCURPC_TriggerIO ($$$)
  1741. {
  1742. my ($fh, $num_items, $socktimeout) = @_;
  1743. my $fd = fileno ($fh);
  1744. my $err = '';
  1745. my $win = '';
  1746. vec ($win, $fd, 1) = 1;
  1747. my $nf = select (undef, $win, undef, $socktimeout);
  1748. if ($nf < 0) {
  1749. $err = $!;
  1750. }
  1751. elsif ($nf == 0) {
  1752. $err = "Select found no reader";
  1753. }
  1754. else {
  1755. my $bytes= syswrite ($fh, "IT|$num_items;");
  1756. if (!defined ($bytes)) {
  1757. $err = $!;
  1758. }
  1759. elsif ($bytes != length ("IT|$num_items;")) {
  1760. $err = "Wrote incomplete data";
  1761. }
  1762. }
  1763. return (($err eq '') ? time () : 0, $err);
  1764. }
  1765. ######################################################################
  1766. # Thread function for processing RPC events
  1767. # equeue - Event queue
  1768. # rqueue - Read control queue
  1769. # socket - Parent socket
  1770. # thrpar - Hash reference with thread parameters:
  1771. # waittime - Time to wait after each loop in microseconds
  1772. # name - FHEM module name for log function
  1773. # socktimeout - Time to wait for socket to become ready
  1774. # queuesize - Maximum number of queue entries
  1775. # triggertime - Time to wait before retriggering I/O
  1776. ######################################################################
  1777. sub HMCCURPC_ProcessData ($$$$)
  1778. {
  1779. my ($equeue, $rqueue, $socket, $thrpar) = @_;
  1780. my $name = $thrpar->{name};
  1781. my $queuesize = $thrpar->{queuesize};
  1782. my $waittime = $thrpar->{waittime};
  1783. my $triggertime = $thrpar->{triggertime};
  1784. my $socktimeout = $thrpar->{socktimeout};
  1785. my $threadname = "DATA";
  1786. my $run = 1;
  1787. my $warn = 0;
  1788. my $ec = 0;
  1789. my $tid = threads->tid ();
  1790. $SIG{INT} = sub { $run = 0; };
  1791. # Inform FHEM that data processing is ready
  1792. $equeue->enqueue ("SL|$threadname|".$tid);
  1793. Log3 $name, 2, "CCURPC: Thread $threadname processing RPC events. TID=$tid";
  1794. while ($run) {
  1795. # Do nothing as long as HMCCURPC_Read() is reading events from queue
  1796. my $num_read = $rqueue->pending ();
  1797. if ($num_read == 0) {
  1798. # Do nothing if no more items in event queue
  1799. my $num_items = $equeue->pending ();
  1800. if ($num_items > 0) {
  1801. # Check max queue size
  1802. if ($num_items >= $queuesize && $warn == 0) {
  1803. Log3 $name, 2, "CCURPC: Size of event queue exceeds $queuesize";
  1804. $warn = 1;
  1805. }
  1806. else {
  1807. $warn = 0 if ($warn == 1);
  1808. }
  1809. # Inform reader about new items in queue
  1810. Log3 $name, 4, "CCURPC: Trigger I/O for $num_items items";
  1811. my ($ttime, $err) = HMCCURPC_TriggerIO ($socket, $num_items, $socktimeout);
  1812. if ($triggertime > 0) {
  1813. if ($ttime == 0) {
  1814. $ec++;
  1815. Log3 $name, 2, "CCURPC: I/O error during data processing ($err)" if ($ec == 1);
  1816. $ec = 0 if ($ec == $HMCCURPC_MAX_IOERRORS);
  1817. sleep ($triggertime);
  1818. }
  1819. else {
  1820. $ec = 0;
  1821. }
  1822. }
  1823. }
  1824. }
  1825. threads->yield ();
  1826. usleep ($waittime);
  1827. }
  1828. $equeue->enqueue ("EX|$threadname|SHUTDOWN|".$tid);
  1829. Log3 $name, 2, "CCURPC: $threadname stopped event processing. TID=$tid";
  1830. # Inform FHEM about the EX event in queue
  1831. for (my $i=0; $i<10; $i++) {
  1832. my ($ttime, $err) = HMCCURPC_TriggerIO ($socket, 1, $socktimeout);
  1833. last if ($ttime > 0);
  1834. usleep ($waittime);
  1835. }
  1836. return;
  1837. }
  1838. ######################################################################
  1839. # Write event into queue
  1840. ######################################################################
  1841. sub HMCCURPC_Write ($$$$)
  1842. {
  1843. my ($server, $et, $cb, $msg) = @_;
  1844. my $name = $server->{hmccu}{name};
  1845. if (defined ($server->{hmccu}{eventqueue})) {
  1846. my $queue = $server->{hmccu}{eventqueue};
  1847. $server->{hmccu}{evttime} = time ();
  1848. if (defined ($server->{hmccu}{queuesize}) &&
  1849. $queue->pending () >= $server->{hmccu}{queuesize}) {
  1850. Log3 $name, 1, "CCURPC: $cb maximum queue size reached";
  1851. return;
  1852. }
  1853. Log3 $name, 4, "CCURPC: $cb enqueue event $et. parameter = $msg";
  1854. $queue->enqueue ($et."|".$cb."|".$msg);
  1855. $server->{hmccu}{rec}{$et}++;
  1856. $server->{hmccu}{rec}{total}++;
  1857. $server->{hmccu}{snd}{$et}++;
  1858. $server->{hmccu}{snd}{total}++;
  1859. HMCCURPC_WriteStats ($server, $cb)
  1860. if ($server->{hmccu}{snd}{total} % $server->{hmccu}{statistics} == 0);
  1861. }
  1862. }
  1863. ######################################################################
  1864. # Write statistics
  1865. ######################################################################
  1866. sub HMCCURPC_WriteStats ($$)
  1867. {
  1868. my ($server, $clkey) = @_;
  1869. my $name = $server->{hmccu}{name};
  1870. my @eventtypes = ("EV", "ND", "DD", "RD", "RA", "UD", "IN", "EX", "SL", "TO");
  1871. # Send statistic info
  1872. my $st = $server->{hmccu}{snd}{total};
  1873. foreach my $et (@eventtypes) {
  1874. $st .= '|'.$server->{hmccu}{snd}{$et};
  1875. $server->{hmccu}{snd}{$et} = 0;
  1876. }
  1877. Log3 $name, 4, "CCURPC: Event statistics = $st";
  1878. my $queue = $server->{hmccu}{eventqueue};
  1879. $queue->enqueue ("ST|$clkey|$st");
  1880. }
  1881. ######################################################################
  1882. # Helper functions
  1883. ######################################################################
  1884. ######################################################################
  1885. # Dump variable content as hex/ascii combination
  1886. ######################################################################
  1887. sub HMCCURPC_HexDump ($$)
  1888. {
  1889. my ($name, $data) = @_;
  1890. my $offset = 0;
  1891. foreach my $chunk (unpack "(a16)*", $data) {
  1892. my $hex = unpack "H*", $chunk; # hexadecimal magic
  1893. $chunk =~ tr/ -~/./c; # replace unprintables
  1894. $hex =~ s/(.{1,8})/$1 /gs; # insert spaces
  1895. Log3 $name, 4, sprintf "0x%08x (%05u) %-*s %s", $offset, $offset, 36, $hex, $chunk;
  1896. $offset += 16;
  1897. }
  1898. }
  1899. ######################################################################
  1900. # Callback functions
  1901. ######################################################################
  1902. ######################################################################
  1903. # Callback for new devices
  1904. ######################################################################
  1905. sub HMCCURPC_NewDevicesCB ($$$)
  1906. {
  1907. my ($server, $cb, $a) = @_;
  1908. my $name = $server->{hmccu}{name};
  1909. my $devcount = scalar (@$a);
  1910. Log3 $name, 2, "CCURPC: $cb NewDevice received $devcount device and channel specifications";
  1911. foreach my $dev (@$a) {
  1912. my $msg = '';
  1913. if ($dev->{ADDRESS} =~ /:[0-9]{1,2}$/) {
  1914. $msg = "C|".$dev->{ADDRESS}."|".$dev->{TYPE}."|".$dev->{VERSION}."|null|null";
  1915. }
  1916. else {
  1917. # Wired devices do not have a RX_MODE attribute
  1918. my $rx = exists ($dev->{RX_MODE}) ? $dev->{RX_MODE} : 'null';
  1919. $msg = "D|".$dev->{ADDRESS}."|".$dev->{TYPE}."|".$dev->{VERSION}."|".
  1920. $dev->{FIRMWARE}."|".$rx;
  1921. }
  1922. HMCCURPC_Write ($server, "ND", $cb, $msg);
  1923. }
  1924. return;
  1925. }
  1926. ##################################################
  1927. # Callback for deleted devices
  1928. ##################################################
  1929. sub HMCCURPC_DeleteDevicesCB ($$$)
  1930. {
  1931. my ($server, $cb, $a) = @_;
  1932. my $name = $server->{hmccu}{name};
  1933. my $devcount = scalar (@$a);
  1934. Log3 $name, 2, "CCURPC: $cb DeleteDevice received $devcount device addresses";
  1935. foreach my $dev (@$a) {
  1936. HMCCURPC_Write ($server, "DD", $cb, $dev);
  1937. }
  1938. return;
  1939. }
  1940. ##################################################
  1941. # Callback for modified devices
  1942. ##################################################
  1943. sub HMCCURPC_UpdateDeviceCB ($$$$)
  1944. {
  1945. my ($server, $cb, $devid, $hint) = @_;
  1946. my $name = $server->{hmccu}{name};
  1947. Log3 $name, 2, "CCURPC: $cb updated device $devid with hint $hint";
  1948. HMCCURPC_Write ($server, "UD", $cb, $devid."|".$hint);
  1949. return;
  1950. }
  1951. ##################################################
  1952. # Callback for replaced devices
  1953. ##################################################
  1954. sub HMCCURPC_ReplaceDeviceCB ($$$$)
  1955. {
  1956. my ($server, $cb, $devid1, $devid2) = @_;
  1957. my $name = $server->{hmccu}{name};
  1958. Log3 $name, 2, "CCURPC: $cb device $devid1 replaced by $devid2";
  1959. HMCCURPC_Write ($server, "RD", $cb, $devid1."|".$devid2);
  1960. return;
  1961. }
  1962. ##################################################
  1963. # Callback for readded devices
  1964. ##################################################
  1965. sub HMCCURPC_ReaddDevicesCB ($$$)
  1966. {
  1967. my ($server, $cb, $a) = @_;
  1968. my $name = $server->{hmccu}{name};
  1969. my $devcount = scalar (@$a);
  1970. Log3 $name, 2, "CCURPC: $cb ReaddDevice received $devcount device addresses";
  1971. foreach my $dev (@$a) {
  1972. HMCCURPC_Write ($server, "RA", $cb, $dev);
  1973. }
  1974. return;
  1975. }
  1976. ##################################################
  1977. # Callback for handling CCU events
  1978. ##################################################
  1979. sub HMCCURPC_EventCB ($$$$$)
  1980. {
  1981. my ($server, $cb, $devid, $attr, $val) = @_;
  1982. my $name = $server->{hmccu}{name};
  1983. my $etime = time ();
  1984. HMCCURPC_Write ($server, "EV", $cb, $etime."|".$devid."|".$attr."|".$val);
  1985. # Never remove this statement!
  1986. return;
  1987. }
  1988. ##################################################
  1989. # Callback for list devices
  1990. ##################################################
  1991. sub HMCCURPC_ListDevicesCB ($$)
  1992. {
  1993. my ($server, $cb) = @_;
  1994. my $name = $server->{hmccu}{name};
  1995. if ($server->{hmccu}{running} == 0) {
  1996. $server->{hmccu}{running} = 1;
  1997. $cb = "unknown" if (!defined ($cb));
  1998. Log3 $name, 1, "CCURPC: $cb ListDevices. Sending init to HMCCU";
  1999. HMCCURPC_Write ($server, "IN", $cb, "INIT|1");
  2000. }
  2001. else {
  2002. Log3 $name, 1, "CCURPC: $cb ListDevices ignored. Server already running.";
  2003. }
  2004. return RPC::XML::array->new ();
  2005. }
  2006. ######################################################################
  2007. # Binary RPC encoding functions
  2008. ######################################################################
  2009. ######################################################################
  2010. # Encode integer (type = 1)
  2011. ######################################################################
  2012. sub HMCCURPC_EncInteger ($)
  2013. {
  2014. my ($v) = @_;
  2015. return pack ('Nl', $BINRPC_INTEGER, $v);
  2016. }
  2017. ######################################################################
  2018. # Encode bool (type = 2)
  2019. ######################################################################
  2020. sub HMCCURPC_EncBool ($)
  2021. {
  2022. my ($v) = @_;
  2023. return pack ('NC', $BINRPC_BOOL, $v);
  2024. }
  2025. ######################################################################
  2026. # Encode string (type = 3)
  2027. # Input is string. Empty string = void
  2028. ######################################################################
  2029. sub HMCCURPC_EncString ($)
  2030. {
  2031. my ($v) = @_;
  2032. return pack ('NN', $BINRPC_STRING, length ($v)).$v;
  2033. }
  2034. ######################################################################
  2035. # Encode name
  2036. ######################################################################
  2037. sub HMCCURPC_EncName ($)
  2038. {
  2039. my ($v) = @_;
  2040. return pack ('N', length ($v)).$v;
  2041. }
  2042. ######################################################################
  2043. # Encode double (type = 4)
  2044. ######################################################################
  2045. sub HMCCURPC_EncDouble ($)
  2046. {
  2047. my ($v) = @_;
  2048. # my $s = $v < 0 ? -1.0 : 1.0;
  2049. # my $l = log (abs($v))/log (2);
  2050. # my $f = $l;
  2051. #
  2052. # if ($l-int ($l) > 0) {
  2053. # $f = ($l < 0) ? -int (abs ($l)+1.0) : int ($l);
  2054. # }
  2055. # my $e = $f+1;
  2056. # my $m = int ($s*$v*2**-$e*0x40000000);
  2057. my $m = 0;
  2058. my $e = 0;
  2059. if ($v != 0.0) {
  2060. $e = int(log(abs($v))/log(2.0))+1;
  2061. $m = int($v/(2**$e)*0x40000000);
  2062. }
  2063. return pack ('NNN', $BINRPC_DOUBLE, $m, $e);
  2064. }
  2065. ######################################################################
  2066. # Encode base64 (type = 17)
  2067. # Input is base64 encoded string
  2068. ######################################################################
  2069. sub HMCCURPC_EncBase64 ($)
  2070. {
  2071. my ($v) = @_;
  2072. return pack ('NN', $BINRPC_DOUBLE, length ($v)).$v;
  2073. }
  2074. ######################################################################
  2075. # Encode array (type = 256)
  2076. # Input is array reference. Array must contain (type, value) pairs
  2077. ######################################################################
  2078. sub HMCCURPC_EncArray ($)
  2079. {
  2080. my ($a) = @_;
  2081. my $r = '';
  2082. my $s = 0;
  2083. if (defined ($a)) {
  2084. while (my $t = shift @$a) {
  2085. my $e = shift @$a;
  2086. if ($e) {
  2087. $r .= HMCCURPC_EncType ($t, $e);
  2088. $s++;
  2089. }
  2090. }
  2091. }
  2092. return pack ('NN', $BINRPC_ARRAY, $s).$r;
  2093. }
  2094. ######################################################################
  2095. # Encode struct (type = 257)
  2096. # Input is hash reference. Hash elements:
  2097. # hash->{$element}{T} = Type
  2098. # hash->{$element}{V} = Value
  2099. ######################################################################
  2100. sub HMCCURPC_EncStruct ($)
  2101. {
  2102. my ($h) = @_;
  2103. my $r = '';
  2104. my $s = 0;
  2105. foreach my $k (keys %{$h}) {
  2106. $r .= HMCCURPC_EncName ($k);
  2107. $r .= HMCCURPC_EncType ($h->{$k}{T}, $h->{$k}{V});
  2108. $s++;
  2109. }
  2110. return pack ('NN', $BINRPC_STRUCT, $s).$r;
  2111. }
  2112. ######################################################################
  2113. # Encode any type
  2114. # Input is type and value
  2115. # Return encoded data or empty string on error
  2116. ######################################################################
  2117. sub HMCCURPC_EncType ($$)
  2118. {
  2119. my ($t, $v) = @_;
  2120. if ($t == $BINRPC_INTEGER) {
  2121. return HMCCURPC_EncInteger ($v);
  2122. }
  2123. elsif ($t == $BINRPC_BOOL) {
  2124. return HMCCURPC_EncBool ($v);
  2125. }
  2126. elsif ($t == $BINRPC_STRING) {
  2127. return HMCCURPC_EncString ($v);
  2128. }
  2129. elsif ($t == $BINRPC_DOUBLE) {
  2130. return HMCCURPC_EncDouble ($v);
  2131. }
  2132. elsif ($t == $BINRPC_BASE64) {
  2133. return HMCCURPC_EncBase64 ($v);
  2134. }
  2135. elsif ($t == $BINRPC_ARRAY) {
  2136. return HMCCURPC_EncArray ($v);
  2137. }
  2138. elsif ($t == $BINRPC_STRUCT) {
  2139. return HMCCURPC_EncStruct ($v);
  2140. }
  2141. else {
  2142. return '';
  2143. }
  2144. }
  2145. ######################################################################
  2146. # Encode RPC request with method and optional parameters.
  2147. # Headers are not supported.
  2148. # Input is method name and reference to parameter array.
  2149. # Array must contain (type, value) pairs
  2150. # Return encoded data or empty string on error
  2151. ######################################################################
  2152. sub HMCCURPC_EncodeRequest ($$)
  2153. {
  2154. my ($method, $args) = @_;
  2155. # Encode method
  2156. my $m = HMCCURPC_EncName ($method);
  2157. # Encode parameters
  2158. my $r = '';
  2159. my $s = 0;
  2160. if (defined ($args)) {
  2161. while (my $t = shift @$args) {
  2162. my $e = shift @$args;
  2163. last if (!defined ($e));
  2164. $r .= HMCCURPC_EncType ($t, $e);
  2165. $s++;
  2166. }
  2167. }
  2168. # Method, ParameterCount, Parameters
  2169. $r = $m.pack ('N', $s).$r;
  2170. # Identifier, ContentLength, Content
  2171. # Ggf. +8
  2172. $r = pack ('NN', $BINRPC_REQUEST, length ($r)+8).$r;
  2173. return $r;
  2174. }
  2175. ######################################################################
  2176. # Encode RPC response
  2177. # Input is type and value
  2178. ######################################################################
  2179. sub HMCCURPC_EncodeResponse ($$)
  2180. {
  2181. my ($t, $v) = @_;
  2182. if (defined ($t) && defined ($v)) {
  2183. my $r = HMCCURPC_EncType ($t, $v);
  2184. # Ggf. +8
  2185. return pack ('NN', $BINRPC_RESPONSE, length ($r)+8).$r;
  2186. }
  2187. else {
  2188. return pack ('NN', $BINRPC_RESPONSE);
  2189. }
  2190. }
  2191. ######################################################################
  2192. # Binary RPC decoding functions
  2193. ######################################################################
  2194. ######################################################################
  2195. # Decode integer (type = 1)
  2196. # Return (value, packetsize) or (undef, undef)
  2197. ######################################################################
  2198. sub HMCCURPC_DecInteger ($$$)
  2199. {
  2200. my ($d, $i, $u) = @_;
  2201. return ($i+4 <= length ($d)) ? (unpack ($u, substr ($d, $i, 4)), 4) : (undef, undef);
  2202. }
  2203. ######################################################################
  2204. # Decode bool (type = 2)
  2205. # Return (value, packetsize) or (undef, undef)
  2206. ######################################################################
  2207. sub HMCCURPC_DecBool ($$)
  2208. {
  2209. my ($d, $i) = @_;
  2210. return ($i+1 <= length ($d)) ? (unpack ('C', substr ($d, $i, 1)), 1) : (undef, undef);
  2211. }
  2212. ######################################################################
  2213. # Decode string or void (type = 3)
  2214. # Return (string, packet size) or (undef, undef)
  2215. # Return ('', 4) for special type 'void'
  2216. ######################################################################
  2217. sub HMCCURPC_DecString ($$)
  2218. {
  2219. my ($d, $i) = @_;
  2220. my ($s, $o) = HMCCURPC_DecInteger ($d, $i, 'N');
  2221. if (defined ($s) && $i+$s+4 <= length ($d)) {
  2222. return $s > 0 ? (substr ($d, $i+4, $s), $s+4) : ('', 4);
  2223. }
  2224. return (undef, undef);
  2225. }
  2226. ######################################################################
  2227. # Decode double (type = 4)
  2228. # Return (value, packetsize) or (undef, undef)
  2229. ######################################################################
  2230. sub HMCCURPC_DecDouble ($$)
  2231. {
  2232. my ($d, $i) = @_;
  2233. return (undef, undef) if ($i+8 > length ($d));
  2234. # my $m = unpack ('N', substr ($d, $i, 4));
  2235. # my $e = unpack ('N', substr ($d, $i+4, 4));
  2236. #
  2237. # return (sprintf ("%.6f",$m/0x40000000*(2**$e)), 8);
  2238. my $m = unpack ('l', reverse (substr ($d, $i, 4)));
  2239. my $e = unpack ('l', reverse (substr ($d, $i+4, 4)));
  2240. $m = $m/(1<<30);
  2241. my $v = $m*(2**$e);
  2242. return (sprintf ("%.6f",$v), 8);
  2243. }
  2244. ######################################################################
  2245. # Decode base64 encoded string (type = 17)
  2246. # Return (string, packetsize) or (undef, undef)
  2247. ######################################################################
  2248. sub HMCCURPC_DecBase64 ($$)
  2249. {
  2250. my ($d, $i) = @_;
  2251. return HMCCURPC_DecString ($d, $i);
  2252. }
  2253. ######################################################################
  2254. # Decode array (type = 256)
  2255. # Return (arrayref, packetsize) or (undef, undef)
  2256. ######################################################################
  2257. sub HMCCURPC_DecArray ($$)
  2258. {
  2259. my ($d, $i) = @_;
  2260. my @r = ();
  2261. my ($s, $x) = HMCCURPC_DecInteger ($d, $i, 'N');
  2262. if (defined ($s)) {
  2263. my $j = $x;
  2264. for (my $n=0; $n<$s; $n++) {
  2265. my ($v, $o) = HMCCURPC_DecType ($d, $i+$j);
  2266. return (undef, undef) if (!defined ($o));
  2267. push (@r, $v);
  2268. $j += $o;
  2269. }
  2270. return (\@r, $j);
  2271. }
  2272. return (undef, undef);
  2273. }
  2274. ######################################################################
  2275. # Decode struct (type = 257)
  2276. # Return (hashref, packetsize) or (undef, undef)
  2277. ######################################################################
  2278. sub HMCCURPC_DecStruct ($$)
  2279. {
  2280. my ($d, $i) = @_;
  2281. my %r;
  2282. my ($s, $x) = HMCCURPC_DecInteger ($d, $i, 'N');
  2283. if (defined ($s)) {
  2284. my $j = $x;
  2285. for (my $n=0; $n<$s; $n++) {
  2286. my ($k, $o1) = HMCCURPC_DecString ($d, $i+$j);
  2287. return (undef, undef) if (!defined ($o1));
  2288. my ($v, $o2) = HMCCURPC_DecType ($d, $i+$j+$o1);
  2289. return (undef, undef) if (!defined ($o2));
  2290. $r{$k} = $v;
  2291. $j += $o1+$o2;
  2292. }
  2293. return (\%r, $j);
  2294. }
  2295. return (undef, undef);
  2296. }
  2297. ######################################################################
  2298. # Decode any type
  2299. # Return (element, packetsize) or (undef, undef)
  2300. ######################################################################
  2301. sub HMCCURPC_DecType ($$)
  2302. {
  2303. my ($d, $i) = @_;
  2304. return (undef, undef) if ($i+4 > length ($d));
  2305. my @r = ();
  2306. my $t = unpack ('N', substr ($d, $i, 4));
  2307. $i += 4;
  2308. if ($t == $BINRPC_INTEGER) {
  2309. # Integer
  2310. @r = HMCCURPC_DecInteger ($d, $i, 'N');
  2311. }
  2312. elsif ($t == $BINRPC_BOOL) {
  2313. # Bool
  2314. @r = HMCCURPC_DecBool ($d, $i);
  2315. }
  2316. elsif ($t == $BINRPC_STRING || $t == $BINRPC_BASE64) {
  2317. # String / Base64
  2318. @r = HMCCURPC_DecString ($d, $i);
  2319. }
  2320. elsif ($t == $BINRPC_DOUBLE) {
  2321. # Double
  2322. @r = HMCCURPC_DecDouble ($d, $i);
  2323. }
  2324. elsif ($t == $BINRPC_ARRAY) {
  2325. # Array
  2326. @r = HMCCURPC_DecArray ($d, $i);
  2327. }
  2328. elsif ($t == $BINRPC_STRUCT) {
  2329. # Struct
  2330. @r = HMCCURPC_DecStruct ($d, $i);
  2331. }
  2332. $r[1] += 4;
  2333. return @r;
  2334. }
  2335. ######################################################################
  2336. # Decode request.
  2337. # Return method, arguments. Arguments are returned as array.
  2338. ######################################################################
  2339. sub HMCCURPC_DecodeRequest ($)
  2340. {
  2341. my ($data) = @_;
  2342. my @r = ();
  2343. my $i = 8;
  2344. return (undef, undef) if (length ($data) < 8);
  2345. # Decode method
  2346. my ($method, $o) = HMCCURPC_DecString ($data, $i);
  2347. return (undef, undef) if (!defined ($method));
  2348. $i += $o;
  2349. my $c = unpack ('N', substr ($data, $i, 4));
  2350. $i += 4;
  2351. for (my $n=0; $n<$c; $n++) {
  2352. my ($d, $s) = HMCCURPC_DecType ($data, $i);
  2353. return (undef, undef) if (!defined ($d) || !defined ($s));
  2354. push (@r, $d);
  2355. $i += $s;
  2356. }
  2357. return (lc ($method), \@r);
  2358. }
  2359. ######################################################################
  2360. # Decode response.
  2361. # Return (ref, type) or (undef, undef)
  2362. # type: 1=ok, 0=error
  2363. ######################################################################
  2364. sub HMCCURPC_DecodeResponse ($)
  2365. {
  2366. my ($data) = @_;
  2367. return (undef, undef) if (length ($data) < 8);
  2368. my $id = unpack ('N', substr ($data, 0, 4));
  2369. if ($id == $BINRPC_RESPONSE) {
  2370. # Data
  2371. my ($result, $offset) = HMCCURPC_DecType ($data, 8);
  2372. return ($result, 1);
  2373. }
  2374. elsif ($id == $BINRPC_ERROR) {
  2375. # Error
  2376. my ($result, $offset) = HMCCURPC_DecType ($data, 8);
  2377. return ($result, 0);
  2378. }
  2379. # Response with header not supported
  2380. # elsif ($id == 0x42696E41) {
  2381. # }
  2382. return (undef, undef);
  2383. }
  2384. 1;
  2385. =pod
  2386. =item device
  2387. =item summary provides RPC server for connection between FHEM and Homematic CCU2
  2388. =begin html
  2389. <a name="HMCCURPC"></a>
  2390. <h3>HMCCURPC</h3>
  2391. <ul>
  2392. The module provides thread based RPC servers for receiving events from HomeMatic CCU2.
  2393. A HMCCURPC device acts as a client device for a HMCCU I/O device. Normally RPC servers of
  2394. HMCCURPC are started from HMCCU I/O device.
  2395. </br></br>
  2396. <a name="HMCCURPCdefine"></a>
  2397. <b>Define</b><br/><br/>
  2398. <ul>
  2399. <code>define &lt;name&gt; HMCCURPC {&lt;HostOrIP&gt;|iodev=&lt;DeviceName&gt;}</code>
  2400. <br/><br/>
  2401. Examples:<br/>
  2402. <code>define myccurpc HMCCURPC 192.168.1.10</code><br/>
  2403. <code>define myccurpc HMCCURPC iodev=myccudev</code><br/>
  2404. <br/><br/>
  2405. The parameter <i>HostOrIP</i> is the hostname or IP address of a Homematic CCU2.
  2406. The I/O device can also be specified with parameter iodev.
  2407. </ul>
  2408. <br/>
  2409. <a name="HMCCURPCset"></a>
  2410. <b>Set</b><br/><br/>
  2411. <ul>
  2412. <li><b> set &lt;name&gt; deregister &lt;interface&gt;</b><br/>
  2413. Deregister RPC server for <i>interface</i>. Parameter <i>interface</i> is a valid
  2414. CCU interface name (i.e. BidCos-RF).
  2415. </li><br/>
  2416. <li><b> set &lt;name&gt; rpcrequest &lt;port&gt; &lt;method&gt; [&lt;parameters&gt;]</b><br/>
  2417. Send RPC request to CCU. The result is displayed in FHEM browser window. Parameter
  2418. &lt;port&gt; is a valid RPC port (i.e. 2001 for BidCos).
  2419. </li><br/>
  2420. <li><b>set &lt;name&gt; rpcserver { on | off }</b><br/>
  2421. Start or stop RPC server(s). This command is only available if expert mode is activated.
  2422. </li><br/>
  2423. </ul>
  2424. <a name="HMCCURPCget"></a>
  2425. <b>Get</b><br/><br/>
  2426. <ul>
  2427. <li><b>get &lt;name&gt; rpcevent</b><br/>
  2428. Show RPC server events statistics.
  2429. </li><br/>
  2430. <li><b>get &lt;name&gt; rpcstate</b><br/>
  2431. Show RPC thread states.
  2432. </li><br/>
  2433. </ul>
  2434. <a name="HMCCURPCattr"></a>
  2435. <b>Attributes</b><br/><br/>
  2436. <ul>
  2437. <li><b>ccuflags { expert }</b><br/>
  2438. Set flags for controlling device behaviour. Meaning of flags is:<br/>
  2439. expert - Activate expert mode<br/>
  2440. keepThreads - Do not delete thread objects after RPC server has been stopped<br/>
  2441. reconnect - Try to re-register at CCU if no events received for rpcEventTimeout seconds<br/>
  2442. </li><br/>
  2443. <li><b>rpcAcceptTimeout &lt;seconds&gt;</b><br/>
  2444. Specify timeout for accepting incoming connections. Default is 1 second. Increase this
  2445. value by 1 or 2 seconds on slow systems.
  2446. </li><br/>
  2447. <li><b>rpcConnTimeout &lt;seconds&gt;</b><br/>
  2448. Specify timeout of CCU connection handling. Default is 10 second.
  2449. </li><br/>
  2450. <li><b>rpcEventTimeout {&lt;seconds&gt;|&lt;interface:seconds&gt;}[,...]</b><br/>
  2451. Specify timeout for CCU events. Default is 600 seconds. If timeout occurs an event
  2452. is triggered. If set to 0 the timeout is ignored. If no <i>interface</i> is specified
  2453. timeout is applied to all interfaces. For valid values for <i>interface</i> see
  2454. attribute rpcInterfaces.
  2455. </li><br/>
  2456. <li><b>rpcInterfaces { BidCos-Wired, BidCos-RF, HmIP-RF, VirtualDevices, CUxD, Homegear, HVL }</b><br/>
  2457. Select RPC interfaces. If attribute is missing the corresponding attribute of I/O device
  2458. (HMCCU device) is used. Interface BidCos-RF is default and always active.
  2459. </li><br/>
  2460. <li><b>rpcMaxEvents &lt;count&gt;</b><br/>
  2461. Specify maximum number of events read by FHEM during one I/O loop. If FHEM performance
  2462. slows down decrease this value. On a fast system this value can be increased to 100.
  2463. Default value is 50.
  2464. </li><br/>
  2465. <li><b>rpcQueueSize &lt;count&gt;</b><br/>
  2466. Specify maximum size of event queue. When this limit is reached no more CCU events
  2467. are forwarded to FHEM. In this case increase this attribute or increase attribute
  2468. <b>rpcMaxEvents</b>. Default value is 500.
  2469. </li><br/>
  2470. <li><b>rpcServer { on | off }</b><br/>
  2471. If set to 'on' start RPC server(s) after FHEM start. Default is 'off'.
  2472. </li><br/>
  2473. <li><b>rpcServerAddr &lt;ip-address&gt;</b><br/>
  2474. Set local IP address of RPC servers on FHEM system. If attribute is missing the
  2475. corresponding attribute of I/O device (HMCCU device) is used or IP address is
  2476. detected automatically. This attribute should be set if FHEM is running on a system
  2477. with multiple network interfaces.
  2478. </li><br/>
  2479. <li><b>rpcServerPort &lt;port&gt;</b><br/>
  2480. Specify TCP port number used for calculation of real RPC server ports.
  2481. If attribute is missing the corresponding attribute of I/O device (HMCCU device)
  2482. is used. Default value is 5400.
  2483. </li><br/>
  2484. <li><b>rpcTriggerTime &lt;seconds&gt;</b><br/>
  2485. Set time to wait before triggering I/O again after an I/O error "no reader" occurred.
  2486. Default value is 10 seconds, 0 will deactivate error handling for this kind of error.
  2487. On fast systems this value can be set to 5 seconds. Higher values Reduce number of
  2488. log messages written if FHEM is busy and not able to read data from CCU.
  2489. </li><br/>
  2490. <li><b>rpcWaitTime &lt;microseconds&gt;</b><br/>
  2491. Specify time to wait for data processing thread after each loop. Default value is
  2492. 100000 microseconds.
  2493. </li><br/>
  2494. <li><b>rpcWriteTimeout &lt;seconds&gt;</b><br/>
  2495. The data processing thread will wait the specified time for FHEM input socket to
  2496. become writeable. Default value is 0.001 seconds.
  2497. </li>
  2498. </ul>
  2499. </ul>
  2500. =end html
  2501. =cut