88_HMCCURPC.pm 84 KB

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