88_HMCCURPCPROC.pm 78 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633
  1. ##############################################################################
  2. #
  3. # 88_HMCCURPCPROC.pm
  4. #
  5. # $Id: 88_HMCCURPCPROC.pm 16165 2018-02-13 10:55:01Z zap $
  6. #
  7. # Version 1.0.002
  8. #
  9. # Subprocess based RPC Server module for HMCCU.
  10. #
  11. # (c) 2018 by zap (zap01 <at> t-online <dot> de)
  12. #
  13. ##############################################################################
  14. #
  15. # Required perl modules:
  16. #
  17. # RPC::XML::Client
  18. # RPC::XML::Server
  19. #
  20. ##############################################################################
  21. package main;
  22. use strict;
  23. use warnings;
  24. use RPC::XML::Client;
  25. use RPC::XML::Server;
  26. use SetExtensions;
  27. ######################################################################
  28. # Constants
  29. ######################################################################
  30. # HMCCURPC version
  31. my $HMCCURPCPROC_VERSION = '1.0.002';
  32. # Maximum number of events processed per call of Read()
  33. my $HMCCURPCPROC_MAX_EVENTS = 100;
  34. # Maximum number of errors during socket write before log message is written
  35. my $HMCCURPCPROC_MAX_IOERRORS = 100;
  36. # Maximum number of elements in queue
  37. my $HMCCURPCPROC_MAX_QUEUESIZE = 500;
  38. # Maximum number of events to be send to FHEM within one function call
  39. my $HMCCURPCPROC_MAX_QUEUESEND = 70;
  40. # Time to wait after data processing loop in microseconds
  41. my $HMCCURPCPROC_TIME_WAIT = 100000;
  42. # Timeout for established CCU connection
  43. my $HMCCURPCPROC_TIMEOUT_CONNECTION = 1;
  44. # Timeout for TriggerIO()
  45. my $HMCCURPCPROC_TIMEOUT_WRITE = 0.001;
  46. # Timeout for accepting incoming connections (0 = default)
  47. my $HMCCURPCPROC_TIMEOUT_ACCEPT = 1;
  48. # Timeout for incoming CCU events
  49. my $HMCCURPCPROC_TIMEOUT_EVENT = 600;
  50. # Send statistic information after specified amount of events
  51. my $HMCCURPCPROC_STATISTICS = 500;
  52. # Default RPC Port = BidCos-RF
  53. my $HMCCURPCPROC_RPC_PORT_DEFAULT = 2001;
  54. # Default RPC server base port
  55. my $HMCCURPCPROC_SERVER_PORT = 5400;
  56. # Delay for RPC server start after FHEM is initialized
  57. my $HMCCURPCPROC_INIT_INTERVAL0 = 12;
  58. # Delay for RPC server cleanup after top
  59. my $HMCCURPCPROC_INIT_INTERVAL2 = 30;
  60. # Delay for RPC server functionality check after start
  61. my $HMCCURPCPROC_INIT_INTERVAL3 = 25;
  62. # Data types
  63. my $BINRPC_INTEGER = 1;
  64. my $BINRPC_BOOL = 2;
  65. my $BINRPC_STRING = 3;
  66. my $BINRPC_DOUBLE = 4;
  67. my $BINRPC_BASE64 = 17;
  68. my $BINRPC_ARRAY = 256;
  69. my $BINRPC_STRUCT = 257;
  70. # Message types
  71. my $BINRPC_REQUEST = 0x42696E00;
  72. my $BINRPC_RESPONSE = 0x42696E01;
  73. my $BINRPC_REQUEST_HEADER = 0x42696E40;
  74. my $BINRPC_ERROR = 0x42696EFF;
  75. ######################################################################
  76. # Functions
  77. ######################################################################
  78. # Standard functions
  79. sub HMCCURPCPROC_Initialize ($);
  80. sub HMCCURPCPROC_Define ($$);
  81. sub HMCCURPCPROC_Undef ($$);
  82. sub HMCCURPCPROC_Shutdown ($);
  83. sub HMCCURPCPROC_Attr ($@);
  84. sub HMCCURPCPROC_Set ($@);
  85. sub HMCCURPCPROC_Get ($@);
  86. sub HMCCURPCPROC_Read ($);
  87. sub HMCCURPCPROC_SetError ($$$);
  88. sub HMCCURPCPROC_SetState ($$);
  89. sub HMCCURPCPROC_ProcessEvent ($$);
  90. # RPC server functions
  91. sub HMCCURPCPROC_RegisterCallback ($$);
  92. sub HMCCURPCPROC_DeRegisterCallback ($$);
  93. sub HMCCURPCPROC_InitRPCServer ($$$$);
  94. sub HMCCURPCPROC_StartRPCServer ($);
  95. sub HMCCURPCPROC_RPCServerStarted ($);
  96. sub HMCCURPCPROC_RPCServerStopped ($);
  97. sub HMCCURPCPROC_CleanupProcess ($);
  98. sub HMCCURPCPROC_CleanupIO ($);
  99. sub HMCCURPCPROC_TerminateProcess ($);
  100. sub HMCCURPCPROC_CheckProcessState ($$);
  101. sub HMCCURPCPROC_IsRPCServerRunning ($);
  102. sub HMCCURPCPROC_Housekeeping ($);
  103. sub HMCCURPCPROC_StopRPCServer ($);
  104. sub HMCCURPCPROC_SendRequest ($@);
  105. sub HMCCURPCPROC_SetRPCState ($$$$);
  106. sub HMCCURPCPROC_ResetRPCState ($);
  107. sub HMCCURPCPROC_IsRPCStateBlocking ($);
  108. # Helper functions
  109. sub HMCCURPCPROC_GetAttribute ($$$$);
  110. sub HMCCURPCPROC_HexDump ($$);
  111. # RPC server functions
  112. sub HMCCURPCPROC_ProcessRequest ($$);
  113. sub HMCCURPCPROC_HandleConnection ($$$$);
  114. sub HMCCURPCPROC_SendQueue ($$$$);
  115. sub HMCCURPCPROC_SendData ($$);
  116. sub HMCCURPCPROC_Write ($$$$);
  117. sub HMCCURPCPROC_WriteStats ($$);
  118. sub HMCCURPCPROC_NewDevicesCB ($$$);
  119. sub HMCCURPCPROC_DeleteDevicesCB ($$$);
  120. sub HMCCURPCPROC_UpdateDeviceCB ($$$$);
  121. sub HMCCURPCPROC_ReplaceDeviceCB ($$$$);
  122. sub HMCCURPCPROC_ReaddDevicesCB ($$$);
  123. sub HMCCURPCPROC_EventCB ($$$$$);
  124. sub HMCCURPCPROC_ListDevicesCB ($$);
  125. # Binary RPC encoding functions
  126. sub HMCCURPCPROC_EncInteger ($);
  127. sub HMCCURPCPROC_EncBool ($);
  128. sub HMCCURPCPROC_EncString ($);
  129. sub HMCCURPCPROC_EncName ($);
  130. sub HMCCURPCPROC_EncDouble ($);
  131. sub HMCCURPCPROC_EncBase64 ($);
  132. sub HMCCURPCPROC_EncArray ($);
  133. sub HMCCURPCPROC_EncStruct ($);
  134. sub HMCCURPCPROC_EncType ($$);
  135. sub HMCCURPCPROC_EncodeRequest ($$);
  136. sub HMCCURPCPROC_EncodeResponse ($$);
  137. # Binary RPC decoding functions
  138. sub HMCCURPCPROC_DecInteger ($$$);
  139. sub HMCCURPCPROC_DecBool ($$);
  140. sub HMCCURPCPROC_DecString ($$);
  141. sub HMCCURPCPROC_DecDouble ($$);
  142. sub HMCCURPCPROC_DecBase64 ($$);
  143. sub HMCCURPCPROC_DecArray ($$);
  144. sub HMCCURPCPROC_DecStruct ($$);
  145. sub HMCCURPCPROC_DecType ($$);
  146. sub HMCCURPCPROC_DecodeRequest ($);
  147. sub HMCCURPCPROC_DecodeResponse ($);
  148. ######################################################################
  149. # Initialize module
  150. ######################################################################
  151. sub HMCCURPCPROC_Initialize ($)
  152. {
  153. my ($hash) = @_;
  154. $hash->{DefFn} = "HMCCURPCPROC_Define";
  155. $hash->{UndefFn} = "HMCCURPCPROC_Undef";
  156. $hash->{SetFn} = "HMCCURPCPROC_Set";
  157. $hash->{GetFn} = "HMCCURPCPROC_Get";
  158. $hash->{ReadFn} = "HMCCURPCPROC_Read";
  159. $hash->{AttrFn} = "HMCCURPCPROC_Attr";
  160. $hash->{ShutdownFn} = "HMCCURPCPROC_Shutdown";
  161. $hash->{parseParams} = 1;
  162. $hash->{AttrList} = "ccuflags:multiple-strict,expert,reconnect,logEvents,ccuInit,queueEvents".
  163. " rpcMaxEvents rpcQueueSend rpcQueueSize rpcMaxIOErrors".
  164. " rpcServerAddr rpcServerPort rpcWriteTimeout rpcAcceptTimeout".
  165. " rpcConnTimeout rpcStatistics rpcEventTimeout ".
  166. $readingFnAttributes;
  167. }
  168. ######################################################################
  169. # Define device
  170. ######################################################################
  171. sub HMCCURPCPROC_Define ($$)
  172. {
  173. my ($hash, $a, $h) = @_;
  174. my $name = $hash->{NAME};
  175. my $hmccu_hash;
  176. my $iface;
  177. my $usage = "Usage: define $name HMCCURPCPROC { CCUHost | iodev=Name } { RPCPort | RPCInterface }";
  178. if (exists ($h->{iodev})) {
  179. my $ioname = $h->{iodev};
  180. return $usage if (scalar (@$a) < 3);
  181. return "HMCCU I/O device $ioname not found" if (!exists ($defs{$ioname}));
  182. return "Device $ioname is no HMCCU device" if ($defs{$ioname}->{TYPE} ne 'HMCCU');
  183. $hmccu_hash = $defs{$ioname};
  184. $hash->{host} = $hmccu_hash->{host};
  185. $iface = $$a[2];
  186. }
  187. else {
  188. return $usage if (scalar (@$a) < 4);
  189. $hash->{host} = $$a[2];
  190. $iface = $$a[3];
  191. }
  192. # Find IO device
  193. for my $d (keys %defs) {
  194. my $dh = $defs{$d};
  195. next if (!exists ($dh->{TYPE}) || !exists ($dh->{NAME}));
  196. if ($dh->{TYPE} eq 'HMCCU' && $dh->{host} eq $hash->{host}) {
  197. $hmccu_hash = $dh;
  198. last;
  199. }
  200. }
  201. return "Can't find HMCCU I/O device" if (!defined ($hmccu_hash));
  202. # Check if interface is valid
  203. my $ifname = HMCCU_GetRPCServerInfo ($hmccu_hash, $iface, 'name');
  204. my $ifport = HMCCU_GetRPCServerInfo ($hmccu_hash, $iface, 'port');
  205. return "Invalid port or interface $iface" if (!defined ($ifname) || !defined ($ifport));
  206. # Check if RPC device with same interface already exists
  207. for my $d (keys %defs) {
  208. my $dh = $defs{$d};
  209. next if (!exists ($dh->{TYPE}) || !exists ($dh->{NAME}));
  210. if ($dh->{TYPE} eq 'HMCCURPCPROC' && $dh->{NAME} ne $name) {
  211. return "RPC device for CCU/port already exists"
  212. if ($hash->{host} eq $dh->{host} && $dh->{rpcport} == $ifport);
  213. }
  214. }
  215. # Set I/O device and store reference for RPC device in I/O device
  216. AssignIoPort ($hash, $hmccu_hash->{NAME});
  217. $hmccu_hash->{hmccu}{interfaces}{$ifname}{device} = $name;
  218. # Store internals
  219. $hash->{rpcport} = $ifport;
  220. $hash->{rpcinterface} = $ifname;
  221. $hash->{ccuip} = $hmccu_hash->{ccuip};
  222. $hash->{ccutype} = $hmccu_hash->{ccutype};
  223. $hash->{CCUNum} = $hmccu_hash->{CCUNum};
  224. $hash->{ccustate} = $hmccu_hash->{ccustate};
  225. $hash->{version} = $HMCCURPCPROC_VERSION;
  226. Log3 $name, 1, "HMCCURPCPROC: [$name] Initialized version $HMCCURPCPROC_VERSION for interface $ifname";
  227. # Set some attributes
  228. $attr{$name}{stateFormat} = "rpcstate/state";
  229. $attr{$name}{verbose} = 2;
  230. HMCCURPCPROC_ResetRPCState ($hash);
  231. HMCCURPCPROC_SetState ($hash, 'Initialized');
  232. return undef;
  233. }
  234. ######################################################################
  235. # Delete device
  236. ######################################################################
  237. sub HMCCURPCPROC_Undef ($$)
  238. {
  239. my ($hash, $arg) = @_;
  240. my $name = $hash->{NAME};
  241. my $hmccu_hash = $hash->{IODev};
  242. my $ifname = $hash->{rpcinterface};
  243. # Shutdown RPC server
  244. HMCCURPCPROC_Shutdown ($hash);
  245. # Delete RPC device name in I/O device
  246. if (exists ($hmccu_hash->{hmccu}{interfaces}{$ifname}{device}) &&
  247. $hmccu_hash->{hmccu}{interfaces}{$ifname}{device} eq $name) {
  248. delete $hmccu_hash->{hmccu}{interfaces}{$ifname}{device};
  249. }
  250. return undef;
  251. }
  252. ######################################################################
  253. # Shutdown FHEM
  254. ######################################################################
  255. sub HMCCURPCPROC_Shutdown ($)
  256. {
  257. my ($hash) = @_;
  258. # Shutdown RPC server
  259. HMCCURPCPROC_StopRPCServer ($hash);
  260. RemoveInternalTimer ($hash);
  261. return undef;
  262. }
  263. ######################################################################
  264. # Set attribute
  265. ######################################################################
  266. sub HMCCURPCPROC_Attr ($@)
  267. {
  268. my ($cmd, $name, $attrname, $attrval) = @_;
  269. my $hash = $defs{$name};
  270. if ($cmd eq 'set') {
  271. if (($attrname eq 'rpcAcceptTimeout' || $attrname eq 'rpcMaxEvents') && $attrval == 0) {
  272. return "HMCCURPCPROC: [$name] Value for attribute $attrname must be greater than 0";
  273. }
  274. }
  275. return undef;
  276. }
  277. ######################################################################
  278. # Set commands
  279. ######################################################################
  280. sub HMCCURPCPROC_Set ($@)
  281. {
  282. my ($hash, $a, $h) = @_;
  283. my $hmccu_hash = $hash->{IODev};
  284. my $name = shift @$a;
  285. my $opt = shift @$a;
  286. my $ccuflags = AttrVal ($name, 'ccuflags', 'null');
  287. my $options = $ccuflags =~ /expert/ ? "cleanup:noArg deregister:noArg rpcrequest rpcserver:on,off" : "";
  288. my $busyoptions = $ccuflags =~ /expert/ ? "rpcserver:off" : "";
  289. return "HMCCURPCPROC: CCU busy, choose one of $busyoptions"
  290. if ($opt ne 'rpcserver' && HMCCURPCPROC_IsRPCStateBlocking ($hash));
  291. if ($opt eq 'cleanup') {
  292. HMCCURPCPROC_Housekeeping ($hash);
  293. return undef;
  294. }
  295. elsif ($opt eq 'deregister') {
  296. my ($rc, $err) = HMCCURPCPROC_DeRegisterCallback ($hash, 1);
  297. return HMCCURPCPROC_SetError ($hash, $err, 2) if (!$rc);
  298. return HMCCURPCPROC_SetState ($hash, "OK");
  299. }
  300. elsif ($opt eq 'rpcrequest') {
  301. my $request = shift @$a;
  302. return HMCCURPCPROC_SetError ($hash, "Usage: set $name rpcrequest {request} [{parameter} ...]", 2)
  303. if (!defined ($request));
  304. my $response = HMCCURPCPROC_SendRequest ($hash, $request, @$a);
  305. return HMCCURPCPROC_SetError ($hash, "RPC request failed", 2) if (!defined ($response));
  306. return HMCCU_RefToString ($response);
  307. }
  308. elsif ($opt eq 'rpcserver') {
  309. my $action = shift @$a;
  310. return HMCCURPCPROC_SetError ($hash, "Usage: set $name rpcserver {on|off}", 2)
  311. if (!defined ($action) || $action !~ /^(on|off)$/);
  312. if ($action eq 'on') {
  313. return HMCCURPCPROC_SetError ($hash, "RPC server already running", 2)
  314. if ($hash->{RPCState} ne 'inactive' && $hash->{RPCState} ne 'error');
  315. $hmccu_hash->{hmccu}{interfaces}{$hash->{rpcinterface}}{manager} = 'HMCCURPCPROC';
  316. my ($rc, $info) = HMCCURPCPROC_StartRPCServer ($hash);
  317. if (!$rc) {
  318. HMCCURPCPROC_SetRPCState ($hash, 'error', undef, undef);
  319. return HMCCURPCPROC_SetError ($hash, $info, 1);
  320. }
  321. }
  322. elsif ($action eq 'off') {
  323. $hmccu_hash->{hmccu}{interfaces}{$hash->{rpcinterface}}{manager} = 'HMCCURPCPROC';
  324. HMCCURPCPROC_StopRPCServer ($hash);
  325. }
  326. return undef;
  327. }
  328. else {
  329. return "HMCCURPCPROC: Unknown argument $opt, choose one of ".$options;
  330. }
  331. }
  332. ######################################################################
  333. # Get commands
  334. ######################################################################
  335. sub HMCCURPCPROC_Get ($@)
  336. {
  337. my ($hash, $a, $h) = @_;
  338. my $name = shift @$a;
  339. my $opt = shift @$a;
  340. my $ccuflags = AttrVal ($name, 'ccuflags', 'null');
  341. my $options = "rpcevents:noArg rpcstate:noArg";
  342. return "HMCCURPCPROC: CCU busy, choose one of rpcstate:noArg"
  343. if ($opt ne 'rpcstate' && HMCCURPCPROC_IsRPCStateBlocking ($hash));
  344. my $result = 'Command not implemented';
  345. my $rc;
  346. if ($opt eq 'rpcevents') {
  347. my @eventtypes = ("EV", "ND", "DD", "RD", "RA", "UD", "IN", "EX", "SL", "TO");
  348. my $clkey = 'CB'.$hash->{rpcport};
  349. $result = "Event statistics for server $clkey\n";
  350. $result .= "Average event delay = ".$hash->{hmccu}{rpc}{avgdelay}."\n"
  351. if (defined ($hash->{hmccu}{rpc}{avgdelay}));
  352. $result .= "========================================\n";
  353. $result .= "ET Sent by RPC server Received by FHEM\n";
  354. $result .= "----------------------------------------\n";
  355. foreach my $et (@eventtypes) {
  356. my $snd = exists ($hash->{hmccu}{rpc}{snd}{$et}) ?
  357. sprintf ("%7d", $hash->{hmccu}{rpc}{snd}{$et}) : " n/a";
  358. my $rec = exists ($hash->{hmccu}{rpc}{rec}{$et}) ?
  359. sprintf ("%7d", $hash->{hmccu}{rpc}{rec}{$et}) : " n/a";
  360. $result .= "$et $snd $rec\n\n";
  361. }
  362. return $result eq '' ? "No event statistics found" : $result;
  363. }
  364. elsif ($opt eq 'rpcstate') {
  365. my $clkey = 'CB'.$hash->{rpcport};
  366. $result = "ID RPC-Process State \n";
  367. $result .= "-----------------------\n";
  368. my $sid = defined ($hash->{hmccu}{rpc}{pid}) ? sprintf ("%2d", $hash->{hmccu}{rpc}{pid}) : "N/A";
  369. my $sname = sprintf ("%-6s", $clkey);
  370. $result .= $sid." ".$sname." ".$hash->{hmccu}{rpc}{state}."\n";
  371. return $result;
  372. }
  373. else {
  374. return "HMCCURPCPROC: Unknown argument $opt, choose one of ".$options;
  375. }
  376. }
  377. ######################################################################
  378. # Read data from processes
  379. ######################################################################
  380. sub HMCCURPCPROC_Read ($)
  381. {
  382. my ($hash) = @_;
  383. my $name = $hash->{NAME};
  384. my $hmccu_hash = $hash->{IODev};
  385. my $eventcount = 0; # Total number of events
  386. my $devcount = 0; # Number of DD, ND or RD events
  387. my $evcount = 0; # Number of EV events
  388. my %events = ();
  389. my %devices = ();
  390. Log3 $name, 4, "HMCCURPCPROC: [$name] Read called";
  391. # Check if child socket exists
  392. if (!defined ($hash->{hmccu}{sockchild})) {
  393. Log3 $name, 2, "HMCCURPCPROC: [$name] Child socket does not exist";
  394. return;
  395. }
  396. # Get attributes
  397. my $rpcmaxevents = AttrVal ($name, 'rpcMaxEvents', $HMCCURPCPROC_MAX_EVENTS);
  398. my $ccuflags = AttrVal ($name, 'ccuflags', 'null');
  399. my $socktimeout = AttrVal ($name, 'rpcWriteTimeout', $HMCCURPCPROC_TIMEOUT_WRITE);
  400. # Read events from queue
  401. while (1) {
  402. my ($item, $err) = HMCCURPCPROC_ReceiveData ($hash->{hmccu}{sockchild}, $socktimeout);
  403. if (!defined ($item)) {
  404. Log3 $name, 4, "HMCCURPCPROC: [$name] Read stopped after $eventcount events $err";
  405. last;
  406. }
  407. Log3 $name, 4, "HMCCURPCPROC: [$name] read $item from queue" if ($ccuflags =~ /logEvents/);
  408. my ($et, $clkey, @par) = HMCCURPCPROC_ProcessEvent ($hash, $item);
  409. next if (!defined ($et));
  410. if ($et eq 'EV') {
  411. $events{$par[0]}{$par[1]}{$par[2]} = $par[3];
  412. $evcount++;
  413. $hash->{ccustate} = 'active' if ($hash->{ccustate} ne 'active');
  414. }
  415. elsif ($et eq 'EX') {
  416. # I/O already cleaned up. Leave Read()
  417. last;
  418. }
  419. elsif ($et eq 'ND') {
  420. $devices{$par[0]}{flag} = 'N';
  421. $devices{$par[0]}{version} = $par[3];
  422. if ($par[1] eq 'D') {
  423. $devices{$par[0]}{addtype} = 'dev';
  424. $devices{$par[0]}{type} = $par[2];
  425. $devices{$par[0]}{firmware} = $par[4];
  426. $devices{$par[0]}{rxmode} = $par[5];
  427. }
  428. else {
  429. $devices{$par[0]}{addtype} = 'chn';
  430. $devices{$par[0]}{usetype} = $par[2];
  431. }
  432. $devcount++;
  433. }
  434. elsif ($et eq 'DD') {
  435. $devices{$par[0]}{flag} = 'D';
  436. $devcount++;
  437. }
  438. elsif ($et eq 'RD') {
  439. $devices{$par[0]}{flag} = 'R';
  440. $devices{$par[0]}{newaddr} = $par[1];
  441. $devcount++;
  442. }
  443. $eventcount++;
  444. if ($eventcount > $rpcmaxevents) {
  445. Log3 $name, 4, "HMCCURPCPROC: [$name] Read stopped after $rpcmaxevents events";
  446. last;
  447. }
  448. }
  449. # Update device table and client device readings
  450. HMCCU_UpdateDeviceTable ($hmccu_hash, \%devices) if ($devcount > 0);
  451. HMCCU_UpdateMultipleDevices ($hmccu_hash, \%events) if ($evcount > 0);
  452. Log3 $name, 4, "HMCCURPCPROC: [$name] Read finished";
  453. }
  454. ######################################################################
  455. # Set error state and write log file message
  456. # Parameter level is optional. Default value for level is 1.
  457. ######################################################################
  458. sub HMCCURPCPROC_SetError ($$$)
  459. {
  460. my ($hash, $text, $level) = @_;
  461. my $name = $hash->{NAME};
  462. my $type = $hash->{TYPE};
  463. my $msg;
  464. $msg = defined ($text) ? $text : "unknown error";
  465. $msg = $type.": [".$name."] ". $msg;
  466. HMCCURPCPROC_SetState ($hash, "error");
  467. Log3 $name, (defined($level) ? $level : 1), $msg;
  468. return $msg;
  469. }
  470. ######################################################################
  471. # Set state of device
  472. ######################################################################
  473. sub HMCCURPCPROC_SetState ($$)
  474. {
  475. my ($hash, $state) = @_;
  476. my $name = $hash->{NAME};
  477. if (defined ($state)) {
  478. readingsSingleUpdate ($hash, "state", $state, 1);
  479. Log3 $name, 4, "HMCCURPCPROC: [$name] Set state to $state";
  480. }
  481. return undef;
  482. }
  483. ######################################################################
  484. # Set state of RPC server
  485. # Parameters msg and level are optional. Default for level is 1.
  486. ######################################################################
  487. sub HMCCURPCPROC_SetRPCState ($$$$)
  488. {
  489. my ($hash, $state, $msg, $level) = @_;
  490. my $name = $hash->{NAME};
  491. my $hmccu_hash = $hash->{IODev};
  492. return undef if (exists ($hash->{RPCState}) && $hash->{RPCState} eq $state);
  493. $hash->{hmccu}{rpc}{state} = $state;
  494. $hash->{RPCState} = $state;
  495. readingsSingleUpdate ($hash, "rpcstate", $state, 1);
  496. HMCCURPCPROC_SetState ($hash, 'busy') if ($state ne 'running' && $state ne 'inactive' &&
  497. $state ne 'error' && ReadingsVal ($name, 'state', '') ne 'busy');
  498. Log3 $name, (defined($level) ? $level : 1), "HMCCURPCPROC: [$name] $msg" if (defined ($msg));
  499. Log3 $name, 4, "HMCCURPCPROC: [$name] Set rpcstate to $state";
  500. # Set state of interface in I/O device
  501. HMCCU_SetRPCState ($hmccu_hash, $state, $hash->{rpcinterface});
  502. return undef;
  503. }
  504. ######################################################################
  505. # Reset RPC State
  506. ######################################################################
  507. sub HMCCURPCPROC_ResetRPCState ($)
  508. {
  509. my ($hash) = @_;
  510. my $name = $hash->{NAME};
  511. Log3 $name, 4, "HMCCURPCPROC: [$name] Reset RPC state";
  512. $hash->{RPCPID} = "0";
  513. $hash->{hmccu}{rpc}{pid} = undef;
  514. $hash->{hmccu}{evtime} = 0;
  515. $hash->{hmccu}{rpcstarttime} = 0;
  516. return HMCCURPCPROC_SetRPCState ($hash, 'inactive', undef, undef);
  517. }
  518. ######################################################################
  519. # Check if CCU is busy due to RPC start or stop
  520. ######################################################################
  521. sub HMCCURPCPROC_IsRPCStateBlocking ($)
  522. {
  523. my ($hash) = @_;
  524. return ($hash->{RPCState} eq "running" || $hash->{RPCState} eq "inactive") ? 0 : 1;
  525. }
  526. ######################################################################
  527. # Process RPC server event
  528. ######################################################################
  529. sub HMCCURPCPROC_ProcessEvent ($$)
  530. {
  531. my ($hash, $event) = @_;
  532. my $name = $hash->{NAME};
  533. my $rpcname = 'CB'.$hash->{rpcport};
  534. my $rh = \%{$hash->{hmccu}{rpc}}; # Just for code simplification
  535. my $hmccu_hash = $hash->{IODev};
  536. # Number of arguments in RPC events (without event type and clkey)
  537. my %rpceventargs = (
  538. "EV", 4,
  539. "ND", 6,
  540. "DD", 1,
  541. "RD", 2,
  542. "RA", 1,
  543. "UD", 2,
  544. "IN", 2,
  545. "EX", 2,
  546. "SL", 1,
  547. "TO", 1,
  548. "ST", 11
  549. );
  550. my $ccuflags = AttrVal ($name, 'ccuflags', 'null');
  551. my $evttimeout = HMCCURPCPROC_GetAttribute ($hash, 'rpcEventTimeout', 'rpcevtimeout',
  552. $HMCCURPCPROC_TIMEOUT_EVENT);
  553. # Parse event
  554. return undef if (!defined ($event) || $event eq '');
  555. my @t = split (/\|/, $event);
  556. my $et = shift @t;
  557. my $clkey = shift @t;
  558. my $tc = scalar (@t);
  559. # Log event
  560. Log3 $name, 2, "HMCCURPCPROC: [$name] CCUEvent = $event" if ($ccuflags =~ /logEvents/);
  561. # Check event data
  562. if (!defined ($clkey)) {
  563. Log3 $name, 2, "HMCCURPCPROC: [$name] Syntax error in RPC event data";
  564. return undef;
  565. }
  566. # Check for valid server
  567. if ($clkey ne $rpcname) {
  568. Log3 $name, 0, "HMCCURPCPROC: [$name] Received SL event for unknown RPC server $clkey";
  569. return undef;
  570. }
  571. # Check event type
  572. if (!exists ($rpceventargs{$et})) {
  573. $et =~ s/([\x00-\xFF])/sprintf("0x%X ",ord($1))/eg;
  574. Log3 $name, 2, "HMCCURPCPROC: [$name] Received unknown event from CCU: ".$et;
  575. return undef;
  576. }
  577. # Check event parameters
  578. if ($tc != $rpceventargs{$et}) {
  579. Log3 $name, 2, "HMCCURPCPROC: [$name] Wrong number of parameters in event $event. Expected ".
  580. $rpceventargs{$et};
  581. return undef;
  582. }
  583. # Update statistic counters
  584. $rh->{rec}{$et}++;
  585. $rh->{evtime} = time ();
  586. if ($et eq 'EV') {
  587. #
  588. # Update of datapoint
  589. # Input: EV|clkey|Time|Address|Datapoint|Value
  590. # Output: EV, clkey, DevAdd, ChnNo, Datapoint, Value
  591. #
  592. my $delay = $rh->{evtime}-$t[0];
  593. $rh->{sumdelay} += $delay;
  594. $rh->{avgdelay} = $rh->{sumdelay}/$rh->{rec}{$et};
  595. $hash->{ccustate} = 'active' if ($hash->{ccustate} ne 'active');
  596. Log3 $name, 2, "HMCCURPCPROC: [$name] Received CENTRAL event. ".$t[2]."=".$t[3] if ($t[1] eq 'CENTRAL');
  597. my ($add, $chn) = split (/:/, $t[1]);
  598. return defined ($chn) ? ($et, $clkey, $add, $chn, $t[2], $t[3]) : undef;
  599. }
  600. elsif ($et eq 'SL') {
  601. #
  602. # RPC server enters server loop
  603. # Input: SL|clkey|Pid
  604. # Output: SL, clkey, countWorking
  605. #
  606. if ($t[0] == $rh->{pid}) {
  607. HMCCURPCPROC_SetRPCState ($hash, 'working', "RPC server $clkey enters server loop", 2);
  608. my ($rc, $rcmsg) = HMCCURPCPROC_RegisterCallback ($hash, 0);
  609. if (!$rc) {
  610. HMCCURPCPROC_SetRPCState ($hash, 'error', $rcmsg, 1);
  611. return ($et, $clkey, 1, 0, 0, 0);
  612. }
  613. else {
  614. HMCCURPCPROC_SetRPCState ($hash, $rcmsg, "RPC server $clkey $rcmsg", 1);
  615. }
  616. my $srun = HMCCURPCPROC_RPCServerStarted ($hash);
  617. return ($et, $clkey, ($srun == 0 ? 1 : 0), $srun);
  618. }
  619. else {
  620. Log3 $name, 0, "HMCCURPCPROC: [$name] Received SL event. Wrong PID=".$t[0]." for RPC server $clkey";
  621. return undef;
  622. }
  623. }
  624. elsif ($et eq 'IN') {
  625. #
  626. # RPC server initialized
  627. # Input: IN|clkey|INIT|State
  628. # Output: IN, clkey, Running, ClientsUpdated, UpdateErrors
  629. #
  630. return ($et, $clkey, 0, 0, 0) if ($rh->{state} eq 'running');
  631. HMCCURPCPROC_SetRPCState ($hash, 'running', "RPC server $clkey running.", 1);
  632. my $run = HMCCURPCPROC_RPCServerStarted ($hash);
  633. return ($et, $clkey, $run);
  634. }
  635. elsif ($et eq 'EX') {
  636. #
  637. # Process stopped
  638. # Input: EX|clkey|SHUTDOWN|Pid
  639. # Output: EX, clkey, Pid, Stopped, All
  640. #
  641. HMCCURPCPROC_SetRPCState ($hash, 'inactive', "RPC server process $clkey terminated.", 1);
  642. HMCCURPCPROC_RPCServerStopped ($hash);
  643. return ($et, $clkey, $t[1], 1, 1);
  644. }
  645. elsif ($et eq 'ND') {
  646. #
  647. # CCU device added
  648. # Input: ND|clkey|C/D|Address|Type|Version|Firmware|RxMode
  649. # Output: ND, clkey, DevAdd, C/D, Type, Version, Firmware, RxMode
  650. #
  651. return ($et, $clkey, $t[1], $t[0], $t[2], $t[3], $t[4], $t[5]);
  652. }
  653. elsif ($et eq 'DD' || $et eq 'RA') {
  654. #
  655. # CCU device deleted or readded
  656. # Input: {DD,RA}|clkey|Address
  657. # Output: {DD,RA}, clkey, DevAdd
  658. #
  659. return ($et, $clkey, $t[0]);
  660. }
  661. elsif ($et eq 'UD') {
  662. #
  663. # CCU device updated
  664. # Input: UD|clkey|Address|Hint
  665. # Output: UD, clkey, DevAdd, Hint
  666. #
  667. return ($et, $clkey, $t[0], $t[1]);
  668. }
  669. elsif ($et eq 'RD') {
  670. #
  671. # CCU device replaced
  672. # Input: RD|clkey|Address1|Address2
  673. # Output: RD, clkey, Address1, Address2
  674. #
  675. return ($et, $clkey, $t[0], $t[1]);
  676. }
  677. elsif ($et eq 'ST') {
  678. #
  679. # Statistic data. Store snapshots of sent events.
  680. # Input: ST|clkey|nTotal|nEV|nND|nDD|nRD|nRA|nUD|nIN|nEX|nSL
  681. # Output: ST, clkey, ...
  682. #
  683. my @res = ($et, $clkey);
  684. push (@res, @t);
  685. my $total = shift @t;
  686. my @eventtypes = ("EV", "ND", "DD", "RD", "RA", "UD", "IN", "EX", "SL", "TO");
  687. for (my $i=0; $i<scalar(@eventtypes); $i++) {
  688. $hash->{hmccu}{rpc}{snd}{$eventtypes[$i]} += $t[$i];
  689. }
  690. return @res;
  691. }
  692. elsif ($et eq 'TO') {
  693. #
  694. # Event timeout
  695. # Input: TO|clkey|Time
  696. # Output: TO, clkey, Port, Time
  697. #
  698. if ($evttimeout > 0 && $evttimeout >= $t[0]) {
  699. Log3 $name, 2, "HMCCURPCPROC: [$name] Received no events from interface $clkey for ".$t[0]." seconds";
  700. $hash->{ccustate} = 'timeout';
  701. if ($hash->{RPCState} eq 'running' && $ccuflags =~ /reconnect/) {
  702. Log3 $name, 2, "HMCCURPCPROC: [$name] Reconnecting to CCU interface ".$hash->{rpcinterface};
  703. my ($rc, $rcmsg) = HMCCURPCPROC_RegisterCallback ($hash, 2);
  704. if ($rc) {
  705. $hash->{ccustate} = 'active';
  706. }
  707. else {
  708. Log3 $name, 1, "HMCCURPCPROC: [$name] $rcmsg";
  709. }
  710. }
  711. DoTrigger ($name, "No events from interface $clkey for ".$t[0]." seconds");
  712. }
  713. return ($et, $clkey, $hash->{rpcport}, $t[0]);
  714. }
  715. return undef;
  716. }
  717. ######################################################################
  718. # Get attribute with fallback to I/O device attribute
  719. ######################################################################
  720. sub HMCCURPCPROC_GetAttribute ($$$$)
  721. {
  722. my ($hash, $attr, $ioattr, $default) = @_;
  723. my $name = $hash->{NAME};
  724. my $hmccu_hash = $hash->{IODev};
  725. my $value = AttrVal ($name, $attr, 'null');
  726. return $value if ($value ne 'null');
  727. $value = AttrVal ($hmccu_hash->{NAME}, $ioattr, 'null');
  728. return $value if ($value ne 'null');
  729. return $default;
  730. }
  731. ######################################################################
  732. # Register callback for specified CCU interface port.
  733. # Parameter force:
  734. # 1: callback will be registered even if state is "running". State
  735. # will not be modified.
  736. # 2: CCU connectivity is checked before registering RPC server.
  737. # Return (1, new state) on success. New state is 'running' if flag
  738. # ccuInit is not set. Otherwise 'registered'.
  739. # Return (0, errormessage) on error.
  740. ######################################################################
  741. sub HMCCURPCPROC_RegisterCallback ($$)
  742. {
  743. my ($hash, $force) = @_;
  744. my $name = $hash->{NAME};
  745. my $hmccu_hash = $hash->{IODev};
  746. my $ccuflags = AttrVal ($name, 'ccuflags', 'null');
  747. my $port = $hash->{rpcport};
  748. my $serveraddr = $hash->{host};
  749. my $localaddr = $hash->{hmccu}{localaddr};
  750. my $clkey = 'CB'.$port;
  751. return (0, "RPC server $clkey not in state working")
  752. if ($hash->{hmccu}{rpc}{state} ne 'working' && $force == 0);
  753. if ($force == 2) {
  754. return (0, "CCU port $port not reachable") if (!HMCCU_TCPConnect ($hash->{host}, $port));
  755. }
  756. my $cburl = HMCCU_GetRPCCallbackURL ($hmccu_hash, $localaddr, $hash->{hmccu}{rpc}{cbport}, $clkey, $port);
  757. my $clurl = HMCCU_GetRPCServerInfo ($hmccu_hash, $port, 'url');
  758. my $rpctype = HMCCU_GetRPCServerInfo ($hmccu_hash, $port, 'type');
  759. return (0, "Can't get RPC parameters for ID $clkey") if (!defined ($cburl) || !defined ($clurl) || !defined ($rpctype));
  760. $hash->{hmccu}{rpc}{port} = $port;
  761. $hash->{hmccu}{rpc}{clurl} = $clurl;
  762. $hash->{hmccu}{rpc}{cburl} = $cburl;
  763. Log3 $name, 2, "HMCCURPCPROC: [$name] Registering callback $cburl of type $rpctype with ID $clkey at $clurl";
  764. my $rc;
  765. if ($rpctype eq 'A') {
  766. $rc = HMCCURPCPROC_SendRequest ($hash, "init", $cburl, $clkey);
  767. }
  768. else {
  769. $rc = HMCCURPCPROC_SendRequest ($hash, "init", $BINRPC_STRING, $cburl, $BINRPC_STRING, $clkey);
  770. }
  771. if (defined ($rc)) {
  772. return (1, $ccuflags !~ /ccuInit/ ? 'running' : 'registered');
  773. }
  774. else {
  775. return (0, "Failed to register callback for ID $clkey");
  776. }
  777. }
  778. ######################################################################
  779. # Deregister RPC callbacks at CCU
  780. ######################################################################
  781. sub HMCCURPCPROC_DeRegisterCallback ($$)
  782. {
  783. my ($hash, $force) = @_;
  784. my $name = $hash->{NAME};
  785. my $hmccu_hash = $hash->{IODev};
  786. my $port = $hash->{rpcport};
  787. my $clkey = 'CB'.$port;
  788. my $localaddr = $hash->{hmccu}{localaddr};
  789. my $cburl = '';
  790. my $clurl = '';
  791. my $rpchash = \%{$hash->{hmccu}{rpc}};
  792. return (0, "RPC server $clkey not in state registered or running")
  793. if ($rpchash->{state} ne 'registered' && $rpchash->{state} ne 'running' && $force == 0);
  794. $cburl = $rpchash->{cburl} if (exists ($rpchash->{cburl}));
  795. $clurl = $rpchash->{clurl} if (exists ($rpchash->{clurl}));
  796. $cburl = HMCCU_GetRPCCallbackURL ($hmccu_hash, $localaddr, $rpchash->{cbport}, $clkey, $port) if ($cburl eq '');
  797. $clurl = HMCCU_GetRPCServerInfo ($hmccu_hash, $port, 'url') if ($clurl eq '');
  798. return (0, "Can't get RPC parameters for ID $clkey") if ($cburl eq '' || $clurl eq '');
  799. Log3 $name, 1, "HMCCURPCPROC: [$name] Deregistering RPC server $cburl with ID $clkey at $clurl";
  800. my $rc;
  801. if (HMCCU_IsRPCType ($hmccu_hash, $port, 'A')) {
  802. $rc = HMCCURPCPROC_SendRequest ($hash, "init", $cburl);
  803. }
  804. else {
  805. $rc = HMCCURPCPROC_SendRequest ($hash, "init", $BINRPC_STRING, $cburl);
  806. }
  807. if (defined ($rc)) {
  808. HMCCURPCPROC_SetRPCState ($hash, $force == 0 ? 'deregistered' : $rpchash->{state},
  809. "Callback for RPC server $clkey deregistered", 1);
  810. $rpchash->{cburl} = '';
  811. $rpchash->{clurl} = '';
  812. $rpchash->{cbport} = 0;
  813. return (1, 'working');
  814. }
  815. else {
  816. return (0, "Failed to deregister RPC server $clkey");
  817. }
  818. }
  819. ######################################################################
  820. # Initialize RPC server for specified CCU port
  821. # Return server object or undef on error
  822. ######################################################################
  823. sub HMCCURPCPROC_InitRPCServer ($$$$)
  824. {
  825. my ($name, $serverport, $callbackport, $prot) = @_;
  826. my $clkey = 'CB'.$serverport;
  827. my $server;
  828. # Create binary RPC server
  829. if ($prot eq 'B') {
  830. $server->{__daemon} = IO::Socket::INET->new (LocalPort => $callbackport,
  831. Type => SOCK_STREAM, Reuse => 1, Listen => SOMAXCONN);
  832. if (!($server->{__daemon})) {
  833. Log3 $name, 1, "HMCCURPCPROC: [$name] Can't create RPC callback server $clkey on port $callbackport. Port in use?";
  834. return undef;
  835. }
  836. return $server;
  837. }
  838. # Create XML RPC server
  839. $server = RPC::XML::Server->new (port => $callbackport);
  840. if (!ref($server)) {
  841. Log3 $name, 1, "HMCCURPCPROC: [$name] Can't create RPC callback server $clkey on port $callbackport. Port in use?";
  842. return undef;
  843. }
  844. Log3 $name, 2, "HMCCURPCPROC: [$name] Callback server $clkey created. Listening on port $callbackport";
  845. # Callback for events
  846. Log3 $name, 4, "HMCCURPCPROC: [$name] Adding callback for events for server $clkey";
  847. $server->add_method (
  848. { name=>"event",
  849. signature=> ["string string string string string","string string string string int",
  850. "string string string string double","string string string string boolean",
  851. "string string string string i4"],
  852. code=>\&HMCCURPCPROC_EventCB
  853. }
  854. );
  855. # Callback for new devices
  856. Log3 $name, 4, "HMCCURPCPROC: [$name] Adding callback for new devices for server $clkey";
  857. $server->add_method (
  858. { name=>"newDevices",
  859. signature=>["string string array"],
  860. code=>\&HMCCURPCPROC_NewDevicesCB
  861. }
  862. );
  863. # Callback for deleted devices
  864. Log3 $name, 4, "HMCCURPCPROC: [$name] Adding callback for deleted devices for server $clkey";
  865. $server->add_method (
  866. { name=>"deleteDevices",
  867. signature=>["string string array"],
  868. code=>\&HMCCURPCPROC_DeleteDevicesCB
  869. }
  870. );
  871. # Callback for modified devices
  872. Log3 $name, 4, "HMCCURPCPROC: [$name] Adding callback for modified devices for server $clkey";
  873. $server->add_method (
  874. { name=>"updateDevice",
  875. signature=>["string string string int"],
  876. code=>\&HMCCURPCPROC_UpdateDeviceCB
  877. }
  878. );
  879. # Callback for replaced devices
  880. Log3 $name, 4, "HMCCURPCPROC: [$name] Adding callback for replaced devices for server $clkey";
  881. $server->add_method (
  882. { name=>"replaceDevice",
  883. signature=>["string string string string"],
  884. code=>\&HMCCURPCPROC_ReplaceDeviceCB
  885. }
  886. );
  887. # Callback for readded devices
  888. Log3 $name, 4, "HMCCURPCPROC: [$name] Adding callback for readded devices for server $clkey";
  889. $server->add_method (
  890. { name=>"replaceDevice",
  891. signature=>["string string array"],
  892. code=>\&HMCCURPCPROC_ReaddDeviceCB
  893. }
  894. );
  895. # Dummy implementation, always return an empty array
  896. Log3 $name, 4, "HMCCURPCPROC: [$name] Adding callback for list devices for server $clkey";
  897. $server->add_method (
  898. { name=>"listDevices",
  899. signature=>["array string"],
  900. code=>\&HMCCURPCPROC_ListDevicesCB
  901. }
  902. );
  903. return $server;
  904. }
  905. ######################################################################
  906. # Start RPC server processes
  907. # 1 process for processing event data in event queue
  908. # 1 process per CCU RPC interface for receiving data
  909. # Return (State, Msg)
  910. ######################################################################
  911. sub HMCCURPCPROC_StartRPCServer ($)
  912. {
  913. my ($hash) = @_;
  914. my $name = $hash->{NAME};
  915. my $hmccu_hash = $hash->{IODev};
  916. # Check if RPC server is already running
  917. return (0, "RPC server already running") if (HMCCURPCPROC_CheckProcessState ($hash, 'running'));
  918. # Get parameters and attributes
  919. my %procpar;
  920. my $localaddr = HMCCURPCPROC_GetAttribute ($hash, 'rpcServerAddr', 'rpcserveraddr', '');
  921. my $rpcserverport = HMCCURPCPROC_GetAttribute ($hash, 'rpcServerPort', 'rpcserverport', $HMCCURPCPROC_SERVER_PORT);
  922. my $evttimeout = HMCCURPCPROC_GetAttribute ($hash, 'rpcEventTimeout', 'rpcevtimeout', $HMCCURPCPROC_TIMEOUT_EVENT);
  923. my $ccunum = $hash->{CCUNum};
  924. my $rpcport = $hash->{rpcport};
  925. my $serveraddr = HMCCU_GetRPCServerInfo ($hmccu_hash, $rpcport, 'host');
  926. my $interface = HMCCU_GetRPCServerInfo ($hmccu_hash, $rpcport, 'name');
  927. # Store parameters for child process
  928. $procpar{socktimeout} = AttrVal ($name, 'rpcWriteTimeout', $HMCCURPCPROC_TIMEOUT_WRITE);
  929. $procpar{conntimeout} = AttrVal ($name, 'rpcConnTimeout', $HMCCURPCPROC_TIMEOUT_CONNECTION);
  930. $procpar{acctimeout} = AttrVal ($name, 'rpcAcceptTimeout', $HMCCURPCPROC_TIMEOUT_ACCEPT);
  931. $procpar{evttimeout} = AttrVal ($name, 'rpcEventTimeout', $HMCCURPCPROC_TIMEOUT_EVENT);
  932. $procpar{queuesize} = AttrVal ($name, 'rpcQueueSize', $HMCCURPCPROC_MAX_QUEUESIZE);
  933. $procpar{queuesend} = AttrVal ($name, 'rpcQueueSend', $HMCCURPCPROC_MAX_QUEUESEND);
  934. $procpar{statistics} = AttrVal ($name, 'rpcStatistics', $HMCCURPCPROC_STATISTICS);
  935. $procpar{maxioerrors} = AttrVal ($name, 'rpcMaxIOErrors', $HMCCURPCPROC_MAX_IOERRORS);
  936. $procpar{evttimeout} = AttrVal ($name, 'rpcEventTimeout', $HMCCURPCPROC_TIMEOUT_EVENT);
  937. $procpar{ccuflags} = AttrVal ($name, 'ccuflags', 'null');
  938. $procpar{interface} = $interface;
  939. $procpar{flags} = HMCCU_GetRPCServerInfo ($hmccu_hash, $rpcport, 'flags');
  940. $procpar{type} = HMCCU_GetRPCServerInfo ($hmccu_hash, $rpcport, 'type');
  941. $procpar{name} = $name;
  942. my @eventtypes = ("EV", "ND", "DD", "RD", "RA", "UD", "IN", "EX", "SL", "TO");
  943. # Detect local IP address
  944. if ($localaddr eq '') {
  945. my $socket = IO::Socket::INET->new (PeerAddr => $serveraddr, PeerPort => $rpcport);
  946. return (0, "Can't connect to CCU port $rpcport") if (!$socket);
  947. $localaddr = $socket->sockhost ();
  948. close ($socket);
  949. }
  950. $hash->{hmccu}{localaddr} = $localaddr;
  951. # Reset state of server processes
  952. $hash->{hmccu}{rpc}{state} = 'inactive';
  953. # Create socket pair for communication between RPC server process and FHEM process
  954. my ($sockchild, $sockparent);
  955. return (0, "Can't create I/O socket pair")
  956. if (!socketpair ($sockchild, $sockparent, AF_UNIX, SOCK_STREAM, PF_UNSPEC));
  957. $sockchild->autoflush (1);
  958. $sockparent->autoflush (1);
  959. $hash->{hmccu}{sockparent} = $sockparent;
  960. $hash->{hmccu}{sockchild} = $sockchild;
  961. # Enable FHEM I/O
  962. my $pid = $$;
  963. $hash->{FD} = fileno $sockchild;
  964. $selectlist{"RPC.$name.$pid"} = $hash;
  965. # Initialize RPC server
  966. my $err = '';
  967. my %srvprocpar;
  968. my $clkey = 'CB'.$rpcport;
  969. my $callbackport = $rpcserverport+$rpcport+($ccunum*10);
  970. # Start RPC server process
  971. my $rpcpid = fhemFork ();
  972. if (!defined ($rpcpid)) {
  973. close ($sockparent);
  974. close ($sockchild);
  975. return (0, "Can't create RPC server process for interface $interface");
  976. }
  977. if (!$rpcpid) {
  978. # Child process, only needs parent socket
  979. HMCCURPCPROC_HandleConnection ($rpcport, $callbackport, $sockparent, \%procpar);
  980. # Exit child process
  981. close ($sockparent);
  982. close ($sockchild);
  983. exit (0);
  984. }
  985. # Parent process
  986. Log3 $name, 2, "HMCCURPCPROC: [$name] RPC server process started for interface $interface with PID=$rpcpid";
  987. # Store process parameters
  988. $hash->{hmccu}{rpc}{cbport} = $callbackport;
  989. $hash->{hmccu}{rpc}{pid} = $rpcpid;
  990. $hash->{hmccu}{rpc}{state} = 'initialized';
  991. # Reset statistic counter
  992. foreach my $et (@eventtypes) {
  993. $hash->{hmccu}{rpc}{rec}{$et} = 0;
  994. $hash->{hmccu}{rpc}{snd}{$et} = 0;
  995. }
  996. $hash->{hmccu}{rpc}{sumdelay} = 0;
  997. $hash->{RPCPID} = $rpcpid;
  998. # Trigger Timer function for checking successful RPC start
  999. # Timer will be removed if event 'IN' is reveived
  1000. InternalTimer (gettimeofday()+$HMCCURPCPROC_INIT_INTERVAL3, "HMCCURPCPROC_IsRPCServerRunning",
  1001. $hash, 0);
  1002. HMCCURPCPROC_SetRPCState ($hash, "starting", "RPC server starting", 1);
  1003. DoTrigger ($name, "RPC server starting");
  1004. return (1, undef);
  1005. }
  1006. ######################################################################
  1007. # Set overall status if all RPC servers are running and update all
  1008. # FHEM devices.
  1009. # Return (State, updated devices, failed updates)
  1010. ######################################################################
  1011. sub HMCCURPCPROC_RPCServerStarted ($)
  1012. {
  1013. my ($hash) = @_;
  1014. my $name = $hash->{NAME};
  1015. my $hmccu_hash = $hash->{IODev};
  1016. my $clkey = 'CB'.$hash->{rpcport};
  1017. my $ifname = $hash->{rpcinterface};
  1018. # Check if RPC servers are running. Set overall status
  1019. if (HMCCURPCPROC_CheckProcessState ($hash, 'running')) {
  1020. $hash->{hmccu}{rpcstarttime} = time ();
  1021. HMCCURPCPROC_SetState ($hash, "OK");
  1022. if ($hmccu_hash->{hmccu}{interfaces}{$ifname}{manager} eq 'HMCCURPCPROC') {
  1023. my ($c_ok, $c_err) = HMCCU_UpdateClients ($hmccu_hash, '.*', 'Attr', 0, $ifname);
  1024. Log3 $name, 2, "HMCCURPCPROC: [$name] Updated devices. Success=$c_ok Failed=$c_err";
  1025. }
  1026. RemoveInternalTimer ($hash);
  1027. DoTrigger ($name, "RPC server $clkey running");
  1028. return 1;
  1029. }
  1030. return 0;
  1031. }
  1032. ######################################################################
  1033. # Cleanup if RPC server stopped
  1034. ######################################################################
  1035. sub HMCCURPCPROC_RPCServerStopped ($)
  1036. {
  1037. my ($hash) = @_;
  1038. my $name = $hash->{NAME};
  1039. my $clkey = 'CB'.$hash->{rpcport};
  1040. HMCCURPCPROC_CleanupProcess ($hash);
  1041. HMCCURPCPROC_CleanupIO ($hash);
  1042. HMCCURPCPROC_ResetRPCState ($hash);
  1043. HMCCURPCPROC_SetState ($hash, "OK");
  1044. RemoveInternalTimer ($hash);
  1045. DoTrigger ($name, "RPC server $clkey stopped");
  1046. }
  1047. ######################################################################
  1048. # Stop I/O Handling
  1049. ######################################################################
  1050. sub HMCCURPCPROC_CleanupIO ($)
  1051. {
  1052. my ($hash) = @_;
  1053. my $name = $hash->{NAME};
  1054. my $pid = $$;
  1055. if (exists ($selectlist{"RPC.$name.$pid"})) {
  1056. Log3 $name, 2, "HMCCURPCPROC: [$name] Stop I/O handling";
  1057. delete $selectlist{"RPC.$name.$pid"};
  1058. delete $hash->{FD} if (defined ($hash->{FD}));
  1059. }
  1060. if (defined ($hash->{hmccu}{sockchild})) {
  1061. Log3 $name, 3, "HMCCURPCPROC: [$name] Close child socket";
  1062. $hash->{hmccu}{sockchild}->close ();
  1063. delete $hash->{hmccu}{sockchild};
  1064. }
  1065. if (defined ($hash->{hmccu}{sockparent})) {
  1066. Log3 $name, 3, "HMCCURPCPROC: [$name] Close parent socket";
  1067. $hash->{hmccu}{sockparent}->close ();
  1068. delete $hash->{hmccu}{sockparent};
  1069. }
  1070. }
  1071. ######################################################################
  1072. # Terminate RPC server process by sending an INT signal.
  1073. # Return 0 if RPC server not running.
  1074. ######################################################################
  1075. sub HMCCURPCPROC_TerminateProcess ($)
  1076. {
  1077. my ($hash) = @_;
  1078. my $name = $hash->{NAME};
  1079. my $clkey = 'CB'.$hash->{rpcport};
  1080. # return 0 if ($hash->{hmccu}{rpc}{state} eq 'inactive');
  1081. my $pid = $hash->{hmccu}{rpc}{pid};
  1082. if (defined ($pid) && kill (0, $pid)) {
  1083. HMCCURPCPROC_SetRPCState ($hash, 'stopping', "Sending signal INT to RPC server process $clkey with PID=$pid", 2);
  1084. kill ('INT', $pid);
  1085. return 1;
  1086. }
  1087. else {
  1088. HMCCURPCPROC_SetRPCState ($hash, 'inactive', "RPC server process $clkey not runnning", 1);
  1089. return 0;
  1090. }
  1091. }
  1092. ######################################################################
  1093. # Cleanup inactive RPC server process.
  1094. # Return 0 if process is running.
  1095. ######################################################################
  1096. sub HMCCURPCPROC_CleanupProcess ($)
  1097. {
  1098. my ($hash) = @_;
  1099. my $name = $hash->{NAME};
  1100. my $clkey = 'CB'.$hash->{rpcport};
  1101. # return 1 if ($hash->{hmccu}{rpc}{state} eq 'inactive');
  1102. my $pid = $hash->{hmccu}{rpc}{pid};
  1103. if (defined ($pid) && kill (0, $pid)) {
  1104. Log3 $name, 1, "HMCCURPCPROC: [$name] Process $clkey with PID=$pid".
  1105. " still running. Killing it.";
  1106. kill ('KILL', $pid);
  1107. sleep (1);
  1108. if (kill (0, $pid)) {
  1109. Log3 $name, 1, "HMCCURPCPROC: [$name] Can't kill process $clkey with PID=$pid";
  1110. return 0;
  1111. }
  1112. }
  1113. HMCCURPCPROC_SetRPCState ($hash, 'inactive', "RPC server process $clkey deleted", 2);
  1114. $hash->{hmccu}{rpc}{pid} = undef;
  1115. return 1;
  1116. }
  1117. ######################################################################
  1118. # Check if RPC server process is in specified state.
  1119. # Parameter state is a regular expression. Valid states are:
  1120. # inactive
  1121. # starting
  1122. # working
  1123. # registered
  1124. # running
  1125. # stopping
  1126. # If state is 'running' the process is checked by calling kill() with
  1127. # signal 0.
  1128. ######################################################################
  1129. sub HMCCURPCPROC_CheckProcessState ($$)
  1130. {
  1131. my ($hash, $state) = @_;
  1132. my $prcname = 'CB'.$hash->{rpcport};
  1133. my $pstate = $hash->{hmccu}{rpc}{state};
  1134. if ($state eq 'running' || $state eq '.*') {
  1135. my $pid = $hash->{hmccu}{rpc}{pid};
  1136. return (defined ($pid) && $pid != 0 && kill (0, $pid) && $pstate =~ /$state/) ? $pid : 0
  1137. }
  1138. else {
  1139. return ($pstate =~ /$state/) ? 1 : 0;
  1140. }
  1141. }
  1142. ######################################################################
  1143. # Timer function to check if RPC server process is running.
  1144. # Call Housekeeping() if process is not running.
  1145. ######################################################################
  1146. sub HMCCURPCPROC_IsRPCServerRunning ($)
  1147. {
  1148. my ($hash, $cleanup) = @_;
  1149. my $name = $hash->{NAME};
  1150. Log3 $name, 2, "HMCCURPCPROC: [$name] Checking if RPC server process is running";
  1151. if (!HMCCURPCPROC_CheckProcessState ($hash, 'running')) {
  1152. Log3 $name, 1, "HMCCURPCPROC: [$name] RPC server process not running. Cleaning up";
  1153. HMCCURPCPROC_Housekeeping ($hash);
  1154. return 0;
  1155. }
  1156. Log3 $name, 2, "HMCCURPCPROC: [$name] RPC server process running";
  1157. return 1;
  1158. }
  1159. ######################################################################
  1160. # Cleanup RPC server environment.
  1161. ######################################################################
  1162. sub HMCCURPCPROC_Housekeeping ($)
  1163. {
  1164. my ($hash) = @_;
  1165. my $name = $hash->{NAME};
  1166. Log3 $name, 1, "HMCCURPCPROC: [$name] Housekeeping called. Cleaning up RPC environment";
  1167. # Deregister callback URLs in CCU
  1168. HMCCURPCPROC_DeRegisterCallback ($hash, 0);
  1169. # Terminate process by sending signal INT
  1170. sleep (2) if (HMCCURPCPROC_TerminateProcess ($hash));
  1171. # Next call will cleanup IO, processes and reset RPC state
  1172. HMCCURPCPROC_RPCServerStopped ($hash);
  1173. }
  1174. ######################################################################
  1175. # Stop RPC server processes.
  1176. ######################################################################
  1177. sub HMCCURPCPROC_StopRPCServer ($)
  1178. {
  1179. my ($hash) = @_;
  1180. my $name = $hash->{NAME};
  1181. my $clkey = 'CB'.$hash->{rpcport};
  1182. if (HMCCURPCPROC_CheckProcessState ($hash, 'running')) {
  1183. Log3 $name, 1, "HMCCURPCPROC: [$name] Stopping RPC server $clkey";
  1184. HMCCURPCPROC_SetState ($hash, "busy");
  1185. # Deregister callback URLs in CCU
  1186. my ($rc, $err) = HMCCURPCPROC_DeRegisterCallback ($hash, 0);
  1187. Log3 $name, 1, "HMCCURPCPROC: [$name] $err" if (!$rc);
  1188. # Stop RPC server process
  1189. HMCCURPCPROC_TerminateProcess ($hash);
  1190. # Trigger timer function for checking successful RPC stop
  1191. # Timer will be removed wenn receiving EX event from RPC server process
  1192. InternalTimer (gettimeofday()+$HMCCURPCPROC_INIT_INTERVAL2, "HMCCURPCPROC_Housekeeping",
  1193. $hash, 0);
  1194. # Give process the chance to terminate
  1195. sleep (1);
  1196. return 1;
  1197. }
  1198. else {
  1199. Log3 $name, 2, "HMCCURPCPROC: [$name] Found no running processes. Cleaning up ...";
  1200. HMCCURPCPROC_Housekeeping ($hash);
  1201. return 0;
  1202. }
  1203. }
  1204. ######################################################################
  1205. # Send RPC request to CCU.
  1206. # Return response or undef on error.
  1207. ######################################################################
  1208. sub HMCCURPCPROC_SendRequest ($@)
  1209. {
  1210. my ($hash, $request, @param) = @_;
  1211. my $name = $hash->{NAME};
  1212. my $hmccu_hash = $hash->{IODev};
  1213. my $port = $hash->{rpcport};
  1214. my $rc;
  1215. if (HMCCU_IsRPCType ($hmccu_hash, $port, 'A')) {
  1216. my $clurl = HMCCU_GetRPCServerInfo ($hmccu_hash, $port, 'url');
  1217. return HMCCU_Log ($hash, 2, "Can't get client URL for port $port", undef)
  1218. if (!defined ($clurl));
  1219. Log3 $name, 4, "HMCCURPCPROC: [$name] Send ASCII RPC request $request to $clurl";
  1220. my $rpcclient = RPC::XML::Client->new ($clurl);
  1221. $rc = $rpcclient->simple_request ($request, @param);
  1222. Log3 $name, 2, "HMCCURPCPROC: [$name] RPC request error ".$RPC::XML::ERROR if (!defined ($rc));
  1223. }
  1224. elsif (HMCCU_IsRPCType ($hmccu_hash, $port, 'B')) {
  1225. my $serveraddr = HMCCU_GetRPCServerInfo ($hmccu_hash, $port, 'host');
  1226. return HMCCU_Log ($hash, 2, "Can't get server address for port $port", undef)
  1227. if (!defined ($serveraddr));
  1228. my $ccuflags = AttrVal ($name, 'ccuflags', 'null');
  1229. my $verbose = GetVerbose ($name);
  1230. Log3 $name, 4, "HMCCURPCPROC: [$name] Send binary RPC request $request to $serveraddr:$port";
  1231. my $encreq = HMCCURPCPROC_EncodeRequest ($request, \@param);
  1232. return HMCCU_Log ($hash, 2, "Error encoding binary request", undef) if ($encreq eq '');
  1233. # auto-flush on socket
  1234. $| = 1;
  1235. # create a connecting socket
  1236. my $socket = new IO::Socket::INET (PeerHost => $serveraddr, PeerPort => $port,
  1237. Proto => 'tcp');
  1238. return HMCCU_Log ($hash, 2, "Can't create socket for $serveraddr:$port", undef) if (!$socket);
  1239. my $size = $socket->send ($encreq);
  1240. if (defined ($size)) {
  1241. my $encresp = <$socket>;
  1242. $socket->close ();
  1243. if (defined ($encresp)) {
  1244. if ($ccuflags =~ /logEvents/ && $verbose >= 4) {
  1245. Log3 $name, 4, "HMCCURPCPROC: [$name] Response";
  1246. HMCCURPCPROC_HexDump ($name, $encresp);
  1247. }
  1248. my ($response, $err) = HMCCURPCPROC_DecodeResponse ($encresp);
  1249. return $response;
  1250. }
  1251. else {
  1252. return '';
  1253. }
  1254. }
  1255. $socket->close ();
  1256. }
  1257. else {
  1258. Log3 $name, 2, "HMCCURPCPROC: [$name] Unknown RPC server type";
  1259. }
  1260. return $rc;
  1261. }
  1262. ######################################################################
  1263. # Process binary RPC request
  1264. ######################################################################
  1265. sub HMCCURPCPROC_ProcessRequest ($$)
  1266. {
  1267. my ($server, $connection) = @_;
  1268. my $name = $server->{hmccu}{name};
  1269. my $clkey = $server->{hmccu}{clkey};
  1270. my @methodlist = ('listDevices', 'listMethods', 'system.multicall');
  1271. my $verbose = GetVerbose ($name);
  1272. # Read request
  1273. my $request = '';
  1274. while (my $packet = <$connection>) {
  1275. $request .= $packet;
  1276. }
  1277. return if (!defined ($request) || $request eq '');
  1278. if ($server->{hmccu}{ccuflags} =~ /logEvents/ && $verbose >= 4) {
  1279. Log3 $name, 4, "CCURPC: [$name] $clkey raw request:";
  1280. HMCCURPCPROC_HexDump ($name, $request);
  1281. }
  1282. # Decode request
  1283. my ($method, $params) = HMCCURPCPROC_DecodeRequest ($request);
  1284. return if (!defined ($method));
  1285. Log3 $name, 4, "CCURPC: [$name] request method = $method";
  1286. if ($method eq 'listmethods') {
  1287. $connection->send (HMCCURPCPROC_EncodeResponse ($BINRPC_ARRAY, \@methodlist));
  1288. }
  1289. elsif ($method eq 'listdevices') {
  1290. HMCCURPCPROC_ListDevicesCB ($server, $clkey);
  1291. $connection->send (HMCCURPCPROC_EncodeResponse ($BINRPC_ARRAY, undef));
  1292. }
  1293. elsif ($method eq 'system.multicall') {
  1294. return if (ref ($params) ne 'ARRAY');
  1295. my $a = $$params[0];
  1296. foreach my $s (@$a) {
  1297. next if (!exists ($s->{methodName}) || !exists ($s->{params}));
  1298. next if ($s->{methodName} ne 'event');
  1299. next if (scalar (@{$s->{params}}) < 4);
  1300. HMCCURPCPROC_EventCB ($server, $clkey,
  1301. ${$s->{params}}[1], ${$s->{params}}[2], ${$s->{params}}[3]);
  1302. Log3 $name, 4, "CCURPC: [$name] Event ".${$s->{params}}[1]." ".${$s->{params}}[2]." "
  1303. .${$s->{params}}[3];
  1304. }
  1305. }
  1306. }
  1307. ######################################################################
  1308. # Subprocess function for handling incoming RPC requests
  1309. ######################################################################
  1310. sub HMCCURPCPROC_HandleConnection ($$$$)
  1311. {
  1312. my ($port, $callbackport, $sockparent, $procpar) = @_;
  1313. my $name = $procpar->{name};
  1314. my $iface = $procpar->{interface};
  1315. my $prot = $procpar->{type};
  1316. my $evttimeout = $procpar->{evttimeout};
  1317. my $conntimeout = $procpar->{conntimeout};
  1318. my $acctimeout = $procpar->{acctimeout};
  1319. my $socktimeout = $procpar->{socktimeout};
  1320. my $maxsnd = $procpar->{queuesend};
  1321. my $maxioerrors = $procpar->{maxioerrors};
  1322. my $ioerrors = 0;
  1323. my $sioerrors = 0;
  1324. my $run = 1;
  1325. my $pid = $$;
  1326. my $clkey = 'CB'.$port;
  1327. my @eventtypes = ("EV", "ND", "DD", "RD", "RA", "UD", "IN", "EX", "SL", "TO");
  1328. # Initialize RPC server
  1329. Log3 $name, 2, "CCURPC: [$name] Initializing RPC server $clkey for interface $iface";
  1330. my $rpcsrv = HMCCURPCPROC_InitRPCServer ($name, $port, $callbackport, $prot);
  1331. if (!defined ($rpcsrv)) {
  1332. Log3 $name, 1, "CCURPC: [$name] Can't initialize RPC server $clkey for interface $iface";
  1333. return;
  1334. }
  1335. if (!($rpcsrv->{__daemon})) {
  1336. Log3 $name, 1, "CCURPC: [$name] Server socket not found for port $port";
  1337. return;
  1338. }
  1339. # Event queue
  1340. my @queue = ();
  1341. # Store RPC server parameters
  1342. $rpcsrv->{hmccu}{name} = $name;
  1343. $rpcsrv->{hmccu}{clkey} = $clkey;
  1344. $rpcsrv->{hmccu}{eventqueue} = \@queue;
  1345. $rpcsrv->{hmccu}{queuesize} = $procpar->{queuesize};
  1346. $rpcsrv->{hmccu}{sockparent} = $sockparent;
  1347. $rpcsrv->{hmccu}{statistics} = $procpar->{statistics};
  1348. $rpcsrv->{hmccu}{ccuflags} = $procpar->{ccuflags};
  1349. $rpcsrv->{hmccu}{flags} = $procpar->{flags};
  1350. $rpcsrv->{hmccu}{evttime} = time ();
  1351. # Initialize statistic counters
  1352. foreach my $et (@eventtypes) {
  1353. $rpcsrv->{hmccu}{rec}{$et} = 0;
  1354. $rpcsrv->{hmccu}{snd}{$et} = 0;
  1355. }
  1356. $rpcsrv->{hmccu}{rec}{total} = 0;
  1357. $rpcsrv->{hmccu}{snd}{total} = 0;
  1358. # Signal handler
  1359. $SIG{INT} = sub { $run = 0; Log3 $name, 2, "CCURPC: [$name] $clkey received signal INT"; };
  1360. HMCCURPCPROC_Write ($rpcsrv, "SL", $clkey, $pid);
  1361. Log3 $name, 2, "CCURPC: [$name] $clkey accepting connections. PID=$pid";
  1362. $rpcsrv->{__daemon}->timeout ($acctimeout) if ($acctimeout > 0.0);
  1363. while ($run) {
  1364. if ($evttimeout > 0) {
  1365. my $difftime = time()-$rpcsrv->{hmccu}{evttime};
  1366. HMCCURPCPROC_Write ($rpcsrv, "TO", $clkey, $difftime) if ($difftime >= $evttimeout);
  1367. }
  1368. # Send queue entries to parent process
  1369. if (scalar (@queue) > 0) {
  1370. Log3 $name, 4, "CCURPC: [$name] RPC server $clkey sending data to FHEM";
  1371. my ($c, $m) = HMCCURPCPROC_SendQueue ($sockparent, $socktimeout, \@queue, $maxsnd);
  1372. if ($c < 0) {
  1373. $ioerrors++;
  1374. $sioerrors++;
  1375. if ($ioerrors >= $maxioerrors || $maxioerrors == 0) {
  1376. Log3 $name, 2, "CCURPC: [$name] Sending data to FHEM failed $ioerrors times. $m";
  1377. $ioerrors = 0;
  1378. }
  1379. }
  1380. }
  1381. # Next statement blocks for rpcAcceptTimeout seconds
  1382. Log3 $name, 5, "CCURPC: [$name] RPC server $clkey accepting connections";
  1383. my $connection = $rpcsrv->{__daemon}->accept ();
  1384. next if (! $connection);
  1385. last if (! $run);
  1386. $connection->timeout ($conntimeout) if ($conntimeout > 0.0);
  1387. Log3 $name, 4, "CCURPC: [$name] RPC server $clkey processing request";
  1388. if ($prot eq 'A') {
  1389. $rpcsrv->process_request ($connection);
  1390. }
  1391. else {
  1392. HMCCURPCPROC_ProcessRequest ($rpcsrv, $connection);
  1393. }
  1394. shutdown ($connection, 2);
  1395. close ($connection);
  1396. undef $connection;
  1397. }
  1398. Log3 $name, 1, "CCURPC: [$name] RPC server $clkey stopped handling connections. PID=$pid";
  1399. close ($rpcsrv->{__daemon}) if ($prot eq 'B');
  1400. # Send statistic info
  1401. HMCCURPCPROC_WriteStats ($rpcsrv, $clkey);
  1402. # Send exit information
  1403. HMCCURPCPROC_Write ($rpcsrv, "EX", $clkey, "SHUTDOWN|$pid");
  1404. # Send queue entries to parent process. Resend on error to ensure that EX event is sent
  1405. my ($c, $m) = HMCCURPCPROC_SendQueue ($sockparent, $socktimeout, \@queue, 0);
  1406. if ($c < 0) {
  1407. Log3 $name, 4, "CCURPC: [$name] Sending data to FHEM failed. $m";
  1408. # Wait 1 second and try again
  1409. sleep (1);
  1410. HMCCURPCPROC_SendQueue ($sockparent, $socktimeout, \@queue, 0);
  1411. }
  1412. # Log statistic counters
  1413. foreach my $et (@eventtypes) {
  1414. Log3 $name, 4, "CCURPC: [$name] $clkey event type = $et: ".$rpcsrv->{hmccu}{rec}{$et};
  1415. }
  1416. Log3 $name, 2, "CCURPC: [$name] Number of I/O errors = $sioerrors";
  1417. return;
  1418. }
  1419. ######################################################################
  1420. # Send queue data to parent process.
  1421. # Return number of queue elements sent to parent process or
  1422. # (-1, errormessage) on error.
  1423. ######################################################################
  1424. sub HMCCURPCPROC_SendQueue ($$$$)
  1425. {
  1426. my ($sockparent, $socktimeout, $queue, $maxsnd) = @_;
  1427. my $fd = fileno ($sockparent);
  1428. my $msg = '';
  1429. my $win = '';
  1430. vec ($win, $fd, 1) = 1;
  1431. my $nf = select (undef, $win, undef, $socktimeout);
  1432. if ($nf <= 0) {
  1433. $msg = $nf == 0 ? "select found no reader" : $!;
  1434. return (-1, $msg);
  1435. }
  1436. my $sndcnt = 0;
  1437. while (my $snddata = shift @{$queue}) {
  1438. my ($bytes, $err) = HMCCURPCPROC_SendData ($sockparent, $snddata);
  1439. if ($bytes == 0) {
  1440. # Put item back in queue
  1441. unshift @{$queue}, $snddata;
  1442. $msg = $err;
  1443. $sndcnt = -1;
  1444. last;
  1445. }
  1446. $sndcnt++;
  1447. last if ($sndcnt == $maxsnd && $maxsnd > 0);
  1448. }
  1449. return ($sndcnt, $msg);
  1450. }
  1451. ######################################################################
  1452. # Check if file descriptor is writeable and write data.
  1453. # Return number of bytes written and error message.
  1454. ######################################################################
  1455. sub HMCCURPCPROC_SendData ($$)
  1456. {
  1457. my ($sockparent, $data) = @_;
  1458. my $bytes = 0;
  1459. my $err = '';
  1460. my $size = pack ("N", length ($data));
  1461. my $msg = $size . $data;
  1462. $bytes = syswrite ($sockparent, $msg);
  1463. if (!defined ($bytes)) {
  1464. $err = $!;
  1465. $bytes = 0;
  1466. }
  1467. elsif ($bytes != length ($msg)) {
  1468. $err = "Sent incomplete data";
  1469. }
  1470. return ($bytes, $err);
  1471. }
  1472. ######################################################################
  1473. # Check if file descriptor is readable and read data.
  1474. # Return data and error message.
  1475. ######################################################################
  1476. sub HMCCURPCPROC_ReceiveData ($$)
  1477. {
  1478. my ($fh, $socktimeout) = @_;
  1479. my $header;
  1480. my $data;
  1481. my $err = '';
  1482. # Check if data is available
  1483. my $fd = fileno ($fh);
  1484. my $rin = '';
  1485. vec ($rin, $fd, 1) = 1;
  1486. my $nfound = select ($rin, undef, undef, $socktimeout);
  1487. if ($nfound < 0) {
  1488. return (undef, $!);
  1489. }
  1490. elsif ($nfound == 0) {
  1491. return (undef, "read: no data");
  1492. }
  1493. # Read datagram size
  1494. my $sbytes = sysread ($fh, $header, 4);
  1495. if (!defined ($sbytes)) {
  1496. return (undef, $!);
  1497. }
  1498. elsif ($sbytes != 4) {
  1499. return (undef, "read: short header");
  1500. }
  1501. # Read datagram
  1502. my $size = unpack ('N', $header);
  1503. my $bytes = sysread ($fh, $data, $size);
  1504. if (!defined ($bytes)) {
  1505. return (undef, $!);
  1506. }
  1507. elsif ($bytes != $size) {
  1508. return (undef, "read: incomplete data");
  1509. }
  1510. return ($data, $err);
  1511. }
  1512. ######################################################################
  1513. # Write event into queue.
  1514. ######################################################################
  1515. sub HMCCURPCPROC_Write ($$$$)
  1516. {
  1517. my ($server, $et, $cb, $msg) = @_;
  1518. my $name = $server->{hmccu}{name};
  1519. if (defined ($server->{hmccu}{eventqueue})) {
  1520. my $queue = $server->{hmccu}{eventqueue};
  1521. my $ev = $et."|".$cb."|".$msg;
  1522. $server->{hmccu}{evttime} = time ();
  1523. if (defined ($server->{hmccu}{queuesize}) &&
  1524. scalar (@{$queue}) >= $server->{hmccu}{queuesize}) {
  1525. Log3 $name, 1, "CCURPC: [$name] $cb maximum queue size reached. Dropping event.";
  1526. return;
  1527. }
  1528. Log3 $name, 2, "CCURPC: [$name] event = $ev" if ($server->{hmccu}{ccuflags} =~ /logEvents/);
  1529. # Try to send events immediately. Put them in queue if send fails
  1530. my $rc = 0;
  1531. my $err = '';
  1532. if ($et ne 'ND' && $server->{hmccu}{ccuflags} !~ /queueEvents/) {
  1533. ($rc, $err) = HMCCURPCPROC_SendData ($server->{hmccu}{sockparent}, $ev);
  1534. Log3 $name, 3, "CCURPC: [$name] SendData $ev $err" if ($rc == 0);
  1535. }
  1536. push (@{$queue}, $ev) if ($rc == 0);
  1537. # Event statistics
  1538. $server->{hmccu}{rec}{$et}++;
  1539. $server->{hmccu}{rec}{total}++;
  1540. $server->{hmccu}{snd}{$et}++;
  1541. $server->{hmccu}{snd}{total}++;
  1542. HMCCURPCPROC_WriteStats ($server, $cb)
  1543. if ($server->{hmccu}{snd}{total} % $server->{hmccu}{statistics} == 0);
  1544. }
  1545. }
  1546. ######################################################################
  1547. # Write statistics
  1548. ######################################################################
  1549. sub HMCCURPCPROC_WriteStats ($$)
  1550. {
  1551. my ($server, $clkey) = @_;
  1552. my $name = $server->{hmccu}{name};
  1553. my @eventtypes = ("EV", "ND", "DD", "RD", "RA", "UD", "IN", "EX", "SL", "TO");
  1554. if (defined ($server->{hmccu}{eventqueue})) {
  1555. my $queue = $server->{hmccu}{eventqueue};
  1556. # Send statistic info
  1557. my $st = $server->{hmccu}{snd}{total};
  1558. foreach my $et (@eventtypes) {
  1559. $st .= '|'.$server->{hmccu}{snd}{$et};
  1560. $server->{hmccu}{snd}{$et} = 0;
  1561. }
  1562. Log3 $name, 4, "CCURPC: [$name] Event statistics = $st";
  1563. push (@{$queue}, "ST|$clkey|$st");
  1564. }
  1565. }
  1566. ######################################################################
  1567. # Helper functions
  1568. ######################################################################
  1569. ######################################################################
  1570. # Dump variable content as hex/ascii combination
  1571. ######################################################################
  1572. sub HMCCURPCPROC_HexDump ($$)
  1573. {
  1574. my ($name, $data) = @_;
  1575. my $offset = 0;
  1576. foreach my $chunk (unpack "(a16)*", $data) {
  1577. my $hex = unpack "H*", $chunk; # hexadecimal magic
  1578. $chunk =~ tr/ -~/./c; # replace unprintables
  1579. $hex =~ s/(.{1,8})/$1 /gs; # insert spaces
  1580. Log3 $name, 4, sprintf "0x%08x (%05u) %-*s %s", $offset, $offset, 36, $hex, $chunk;
  1581. $offset += 16;
  1582. }
  1583. }
  1584. ######################################################################
  1585. # Callback functions
  1586. ######################################################################
  1587. ######################################################################
  1588. # Callback for new devices
  1589. ######################################################################
  1590. sub HMCCURPCPROC_NewDevicesCB ($$$)
  1591. {
  1592. my ($server, $cb, $a) = @_;
  1593. my $name = $server->{hmccu}{name};
  1594. my $devcount = scalar (@$a);
  1595. Log3 $name, 2, "CCURPC: [$name] $cb NewDevice received $devcount device and channel specifications";
  1596. foreach my $dev (@$a) {
  1597. my $msg = '';
  1598. if ($dev->{ADDRESS} =~ /:[0-9]{1,2}$/) {
  1599. $msg = "C|".$dev->{ADDRESS}."|".$dev->{TYPE}."|".$dev->{VERSION}."|null|null";
  1600. }
  1601. else {
  1602. # Wired devices do not have a RX_MODE attribute
  1603. my $rx = exists ($dev->{RX_MODE}) ? $dev->{RX_MODE} : 'null';
  1604. $msg = "D|".$dev->{ADDRESS}."|".$dev->{TYPE}."|".$dev->{VERSION}."|".
  1605. $dev->{FIRMWARE}."|".$rx;
  1606. }
  1607. HMCCURPCPROC_Write ($server, "ND", $cb, $msg);
  1608. }
  1609. return;
  1610. }
  1611. ##################################################
  1612. # Callback for deleted devices
  1613. ##################################################
  1614. sub HMCCURPCPROC_DeleteDevicesCB ($$$)
  1615. {
  1616. my ($server, $cb, $a) = @_;
  1617. my $name = $server->{hmccu}{name};
  1618. my $devcount = scalar (@$a);
  1619. Log3 $name, 2, "CCURPC: [$name] $cb DeleteDevice received $devcount device addresses";
  1620. foreach my $dev (@$a) {
  1621. HMCCURPCPROC_Write ($server, "DD", $cb, $dev);
  1622. }
  1623. return;
  1624. }
  1625. ##################################################
  1626. # Callback for modified devices
  1627. ##################################################
  1628. sub HMCCURPCPROC_UpdateDeviceCB ($$$$)
  1629. {
  1630. my ($server, $cb, $devid, $hint) = @_;
  1631. my $name = $server->{hmccu}{name};
  1632. Log3 $name, 2, "CCURPC: [$name] $cb updated device $devid with hint $hint";
  1633. HMCCURPCPROC_Write ($server, "UD", $cb, $devid."|".$hint);
  1634. return;
  1635. }
  1636. ##################################################
  1637. # Callback for replaced devices
  1638. ##################################################
  1639. sub HMCCURPCPROC_ReplaceDeviceCB ($$$$)
  1640. {
  1641. my ($server, $cb, $devid1, $devid2) = @_;
  1642. my $name = $server->{hmccu}{name};
  1643. Log3 $name, 2, "CCURPC: [$name] $cb device $devid1 replaced by $devid2";
  1644. HMCCURPCPROC_Write ($server, "RD", $cb, $devid1."|".$devid2);
  1645. return;
  1646. }
  1647. ##################################################
  1648. # Callback for readded devices
  1649. ##################################################
  1650. sub HMCCURPCPROC_ReaddDevicesCB ($$$)
  1651. {
  1652. my ($server, $cb, $a) = @_;
  1653. my $name = $server->{hmccu}{name};
  1654. my $devcount = scalar (@$a);
  1655. Log3 $name, 2, "CCURPC: [$name] $cb ReaddDevice received $devcount device addresses";
  1656. foreach my $dev (@$a) {
  1657. HMCCURPCPROC_Write ($server, "RA", $cb, $dev);
  1658. }
  1659. return;
  1660. }
  1661. ##################################################
  1662. # Callback for handling CCU events
  1663. ##################################################
  1664. sub HMCCURPCPROC_EventCB ($$$$$)
  1665. {
  1666. my ($server, $cb, $devid, $attr, $val) = @_;
  1667. my $name = $server->{hmccu}{name};
  1668. my $etime = time ();
  1669. HMCCURPCPROC_Write ($server, "EV", $cb, $etime."|".$devid."|".$attr."|".$val);
  1670. # Never remove this statement!
  1671. return;
  1672. }
  1673. ##################################################
  1674. # Callback for list devices
  1675. ##################################################
  1676. sub HMCCURPCPROC_ListDevicesCB ($$)
  1677. {
  1678. my ($server, $cb) = @_;
  1679. my $name = $server->{hmccu}{name};
  1680. if ($server->{hmccu}{ccuflags} =~ /ccuInit/) {
  1681. $cb = "unknown" if (!defined ($cb));
  1682. Log3 $name, 1, "CCURPC: [$name] $cb ListDevices. Sending init to HMCCU";
  1683. HMCCURPCPROC_Write ($server, "IN", $cb, "INIT|1");
  1684. }
  1685. return RPC::XML::array->new ();
  1686. }
  1687. ######################################################################
  1688. # Binary RPC encoding functions
  1689. ######################################################################
  1690. ######################################################################
  1691. # Encode integer (type = 1)
  1692. ######################################################################
  1693. sub HMCCURPCPROC_EncInteger ($)
  1694. {
  1695. my ($v) = @_;
  1696. return pack ('Nl', $BINRPC_INTEGER, $v);
  1697. }
  1698. ######################################################################
  1699. # Encode bool (type = 2)
  1700. ######################################################################
  1701. sub HMCCURPCPROC_EncBool ($)
  1702. {
  1703. my ($v) = @_;
  1704. return pack ('NC', $BINRPC_BOOL, $v);
  1705. }
  1706. ######################################################################
  1707. # Encode string (type = 3)
  1708. # Input is string. Empty string = void
  1709. ######################################################################
  1710. sub HMCCURPCPROC_EncString ($)
  1711. {
  1712. my ($v) = @_;
  1713. return pack ('NN', $BINRPC_STRING, length ($v)).$v;
  1714. }
  1715. ######################################################################
  1716. # Encode name
  1717. ######################################################################
  1718. sub HMCCURPCPROC_EncName ($)
  1719. {
  1720. my ($v) = @_;
  1721. return pack ('N', length ($v)).$v;
  1722. }
  1723. ######################################################################
  1724. # Encode double (type = 4)
  1725. ######################################################################
  1726. sub HMCCURPCPROC_EncDouble ($)
  1727. {
  1728. my ($v) = @_;
  1729. # my $s = $v < 0 ? -1.0 : 1.0;
  1730. # my $l = $v != 0.0 ? log (abs($v))/log (2) : 0.0;
  1731. # my $f = $l;
  1732. #
  1733. # if ($l-int ($l) > 0) {
  1734. # $f = ($l < 0) ? -int (abs ($l)+1.0) : int ($l);
  1735. # }
  1736. # my $e = $f+1;
  1737. # my $m = int ($v*2**-$e*0x40000000);
  1738. my $m = 0;
  1739. my $e = 0;
  1740. if ($v != 0.0) {
  1741. $e = int(log(abs($v))/log(2.0))+1;
  1742. $m = int($v/(2**$e)*0x40000000);
  1743. }
  1744. return pack ('NNN', $BINRPC_DOUBLE, $m, $e);
  1745. }
  1746. ######################################################################
  1747. # Encode base64 (type = 17)
  1748. # Input is base64 encoded string
  1749. ######################################################################
  1750. sub HMCCURPCPROC_EncBase64 ($)
  1751. {
  1752. my ($v) = @_;
  1753. return pack ('NN', $BINRPC_DOUBLE, length ($v)).$v;
  1754. }
  1755. ######################################################################
  1756. # Encode array (type = 256)
  1757. # Input is array reference. Array must contain (type, value) pairs
  1758. ######################################################################
  1759. sub HMCCURPCPROC_EncArray ($)
  1760. {
  1761. my ($a) = @_;
  1762. my $r = '';
  1763. my $s = 0;
  1764. if (defined ($a)) {
  1765. while (my $t = shift @$a) {
  1766. my $e = shift @$a;
  1767. if ($e) {
  1768. $r .= HMCCURPCPROC_EncType ($t, $e);
  1769. $s++;
  1770. }
  1771. }
  1772. }
  1773. return pack ('NN', $BINRPC_ARRAY, $s).$r;
  1774. }
  1775. ######################################################################
  1776. # Encode struct (type = 257)
  1777. # Input is hash reference. Hash elements:
  1778. # hash->{$element}{T} = Type
  1779. # hash->{$element}{V} = Value
  1780. ######################################################################
  1781. sub HMCCURPCPROC_EncStruct ($)
  1782. {
  1783. my ($h) = @_;
  1784. my $r = '';
  1785. my $s = 0;
  1786. foreach my $k (keys %{$h}) {
  1787. $r .= HMCCURPCPROC_EncName ($k);
  1788. $r .= HMCCURPCPROC_EncType ($h->{$k}{T}, $h->{$k}{V});
  1789. $s++;
  1790. }
  1791. return pack ('NN', $BINRPC_STRUCT, $s).$r;
  1792. }
  1793. ######################################################################
  1794. # Encode any type
  1795. # Input is type and value
  1796. # Return encoded data or empty string on error
  1797. ######################################################################
  1798. sub HMCCURPCPROC_EncType ($$)
  1799. {
  1800. my ($t, $v) = @_;
  1801. if ($t == $BINRPC_INTEGER) {
  1802. return HMCCURPCPROC_EncInteger ($v);
  1803. }
  1804. elsif ($t == $BINRPC_BOOL) {
  1805. return HMCCURPCPROC_EncBool ($v);
  1806. }
  1807. elsif ($t == $BINRPC_STRING) {
  1808. return HMCCURPCPROC_EncString ($v);
  1809. }
  1810. elsif ($t == $BINRPC_DOUBLE) {
  1811. return HMCCURPCPROC_EncDouble ($v);
  1812. }
  1813. elsif ($t == $BINRPC_BASE64) {
  1814. return HMCCURPCPROC_EncBase64 ($v);
  1815. }
  1816. elsif ($t == $BINRPC_ARRAY) {
  1817. return HMCCURPCPROC_EncArray ($v);
  1818. }
  1819. elsif ($t == $BINRPC_STRUCT) {
  1820. return HMCCURPCPROC_EncStruct ($v);
  1821. }
  1822. else {
  1823. return '';
  1824. }
  1825. }
  1826. ######################################################################
  1827. # Encode RPC request with method and optional parameters.
  1828. # Headers are not supported.
  1829. # Input is method name and reference to parameter array.
  1830. # Array must contain (type, value) pairs
  1831. # Return encoded data or empty string on error
  1832. ######################################################################
  1833. sub HMCCURPCPROC_EncodeRequest ($$)
  1834. {
  1835. my ($method, $args) = @_;
  1836. # Encode method
  1837. my $m = HMCCURPCPROC_EncName ($method);
  1838. # Encode parameters
  1839. my $r = '';
  1840. my $s = 0;
  1841. if (defined ($args)) {
  1842. while (my $t = shift @$args) {
  1843. my $e = shift @$args;
  1844. last if (!defined ($e));
  1845. $r .= HMCCURPCPROC_EncType ($t, $e);
  1846. $s++;
  1847. }
  1848. }
  1849. # Method, ParameterCount, Parameters
  1850. $r = $m.pack ('N', $s).$r;
  1851. # Identifier, ContentLength, Content
  1852. # Ggf. +8
  1853. $r = pack ('NN', $BINRPC_REQUEST, length ($r)+8).$r;
  1854. return $r;
  1855. }
  1856. ######################################################################
  1857. # Encode RPC response
  1858. # Input is type and value
  1859. ######################################################################
  1860. sub HMCCURPCPROC_EncodeResponse ($$)
  1861. {
  1862. my ($t, $v) = @_;
  1863. if (defined ($t) && defined ($v)) {
  1864. my $r = HMCCURPCPROC_EncType ($t, $v);
  1865. # Ggf. +8
  1866. return pack ('NN', $BINRPC_RESPONSE, length ($r)+8).$r;
  1867. }
  1868. else {
  1869. return pack ('NN', $BINRPC_RESPONSE);
  1870. }
  1871. }
  1872. ######################################################################
  1873. # Binary RPC decoding functions
  1874. ######################################################################
  1875. ######################################################################
  1876. # Decode integer (type = 1)
  1877. # Return (value, packetsize) or (undef, undef)
  1878. ######################################################################
  1879. sub HMCCURPCPROC_DecInteger ($$$)
  1880. {
  1881. my ($d, $i, $u) = @_;
  1882. return ($i+4 <= length ($d)) ? (unpack ($u, substr ($d, $i, 4)), 4) : (undef, undef);
  1883. }
  1884. ######################################################################
  1885. # Decode bool (type = 2)
  1886. # Return (value, packetsize) or (undef, undef)
  1887. ######################################################################
  1888. sub HMCCURPCPROC_DecBool ($$)
  1889. {
  1890. my ($d, $i) = @_;
  1891. return ($i+1 <= length ($d)) ? (unpack ('C', substr ($d, $i, 1)), 1) : (undef, undef);
  1892. }
  1893. ######################################################################
  1894. # Decode string or void (type = 3)
  1895. # Return (string, packet size) or (undef, undef)
  1896. # Return ('', 4) for special type 'void'
  1897. ######################################################################
  1898. sub HMCCURPCPROC_DecString ($$)
  1899. {
  1900. my ($d, $i) = @_;
  1901. my ($s, $o) = HMCCURPCPROC_DecInteger ($d, $i, 'N');
  1902. if (defined ($s) && $i+$s+4 <= length ($d)) {
  1903. return $s > 0 ? (substr ($d, $i+4, $s), $s+4) : ('', 4);
  1904. }
  1905. return (undef, undef);
  1906. }
  1907. ######################################################################
  1908. # Decode double (type = 4)
  1909. # Return (value, packetsize) or (undef, undef)
  1910. ######################################################################
  1911. sub HMCCURPCPROC_DecDouble ($$)
  1912. {
  1913. my ($d, $i) = @_;
  1914. return (undef, undef) if ($i+8 > length ($d));
  1915. my $m = unpack ('l', reverse (substr ($d, $i, 4)));
  1916. my $e = unpack ('l', reverse (substr ($d, $i+4, 4)));
  1917. $m = $m/(1<<30);
  1918. my $v = $m*(2**$e);
  1919. return (sprintf ("%.6f",$v), 8);
  1920. }
  1921. ######################################################################
  1922. # Decode base64 encoded string (type = 17)
  1923. # Return (string, packetsize) or (undef, undef)
  1924. ######################################################################
  1925. sub HMCCURPCPROC_DecBase64 ($$)
  1926. {
  1927. my ($d, $i) = @_;
  1928. return HMCCURPCPROC_DecString ($d, $i);
  1929. }
  1930. ######################################################################
  1931. # Decode array (type = 256)
  1932. # Return (arrayref, packetsize) or (undef, undef)
  1933. ######################################################################
  1934. sub HMCCURPCPROC_DecArray ($$)
  1935. {
  1936. my ($d, $i) = @_;
  1937. my @r = ();
  1938. my ($s, $x) = HMCCURPCPROC_DecInteger ($d, $i, 'N');
  1939. if (defined ($s)) {
  1940. my $j = $x;
  1941. for (my $n=0; $n<$s; $n++) {
  1942. my ($v, $o) = HMCCURPCPROC_DecType ($d, $i+$j);
  1943. return (undef, undef) if (!defined ($o));
  1944. push (@r, $v);
  1945. $j += $o;
  1946. }
  1947. return (\@r, $j);
  1948. }
  1949. return (undef, undef);
  1950. }
  1951. ######################################################################
  1952. # Decode struct (type = 257)
  1953. # Return (hashref, packetsize) or (undef, undef)
  1954. ######################################################################
  1955. sub HMCCURPCPROC_DecStruct ($$)
  1956. {
  1957. my ($d, $i) = @_;
  1958. my %r;
  1959. my ($s, $x) = HMCCURPCPROC_DecInteger ($d, $i, 'N');
  1960. if (defined ($s)) {
  1961. my $j = $x;
  1962. for (my $n=0; $n<$s; $n++) {
  1963. my ($k, $o1) = HMCCURPCPROC_DecString ($d, $i+$j);
  1964. return (undef, undef) if (!defined ($o1));
  1965. my ($v, $o2) = HMCCURPCPROC_DecType ($d, $i+$j+$o1);
  1966. return (undef, undef) if (!defined ($o2));
  1967. $r{$k} = $v;
  1968. $j += $o1+$o2;
  1969. }
  1970. return (\%r, $j);
  1971. }
  1972. return (undef, undef);
  1973. }
  1974. ######################################################################
  1975. # Decode any type
  1976. # Return (element, packetsize) or (undef, undef)
  1977. ######################################################################
  1978. sub HMCCURPCPROC_DecType ($$)
  1979. {
  1980. my ($d, $i) = @_;
  1981. return (undef, undef) if ($i+4 > length ($d));
  1982. my @r = ();
  1983. my $t = unpack ('N', substr ($d, $i, 4));
  1984. $i += 4;
  1985. if ($t == $BINRPC_INTEGER) {
  1986. # Integer
  1987. @r = HMCCURPCPROC_DecInteger ($d, $i, 'N');
  1988. }
  1989. elsif ($t == $BINRPC_BOOL) {
  1990. # Bool
  1991. @r = HMCCURPCPROC_DecBool ($d, $i);
  1992. }
  1993. elsif ($t == $BINRPC_STRING || $t == $BINRPC_BASE64) {
  1994. # String / Base64
  1995. @r = HMCCURPCPROC_DecString ($d, $i);
  1996. }
  1997. elsif ($t == $BINRPC_DOUBLE) {
  1998. # Double
  1999. @r = HMCCURPCPROC_DecDouble ($d, $i);
  2000. }
  2001. elsif ($t == $BINRPC_ARRAY) {
  2002. # Array
  2003. @r = HMCCURPCPROC_DecArray ($d, $i);
  2004. }
  2005. elsif ($t == $BINRPC_STRUCT) {
  2006. # Struct
  2007. @r = HMCCURPCPROC_DecStruct ($d, $i);
  2008. }
  2009. $r[1] += 4;
  2010. return @r;
  2011. }
  2012. ######################################################################
  2013. # Decode request.
  2014. # Return method, arguments. Arguments are returned as array.
  2015. ######################################################################
  2016. sub HMCCURPCPROC_DecodeRequest ($)
  2017. {
  2018. my ($data) = @_;
  2019. my @r = ();
  2020. my $i = 8;
  2021. return (undef, undef) if (length ($data) < 8);
  2022. # Decode method
  2023. my ($method, $o) = HMCCURPCPROC_DecString ($data, $i);
  2024. return (undef, undef) if (!defined ($method));
  2025. $i += $o;
  2026. my $c = unpack ('N', substr ($data, $i, 4));
  2027. $i += 4;
  2028. for (my $n=0; $n<$c; $n++) {
  2029. my ($d, $s) = HMCCURPCPROC_DecType ($data, $i);
  2030. return (undef, undef) if (!defined ($d) || !defined ($s));
  2031. push (@r, $d);
  2032. $i += $s;
  2033. }
  2034. return (lc ($method), \@r);
  2035. }
  2036. ######################################################################
  2037. # Decode response.
  2038. # Return (ref, type) or (undef, undef)
  2039. # type: 1=ok, 0=error
  2040. ######################################################################
  2041. sub HMCCURPCPROC_DecodeResponse ($)
  2042. {
  2043. my ($data) = @_;
  2044. return (undef, undef) if (length ($data) < 8);
  2045. my $id = unpack ('N', substr ($data, 0, 4));
  2046. if ($id == $BINRPC_RESPONSE) {
  2047. # Data
  2048. my ($result, $offset) = HMCCURPCPROC_DecType ($data, 8);
  2049. return ($result, 1);
  2050. }
  2051. elsif ($id == $BINRPC_ERROR) {
  2052. # Error
  2053. my ($result, $offset) = HMCCURPCPROC_DecType ($data, 8);
  2054. return ($result, 0);
  2055. }
  2056. # Response with header not supported
  2057. # elsif ($id == 0x42696E41) {
  2058. # }
  2059. return (undef, undef);
  2060. }
  2061. 1;
  2062. =pod
  2063. =item device
  2064. =item summary provides RPC server for connection between FHEM and Homematic CCU2
  2065. =begin html
  2066. <a name="HMCCURPCPROC"></a>
  2067. <h3>HMCCURPCPROC</h3>
  2068. <ul>
  2069. The module provides a subprocess based RPC server for receiving events from HomeMatic CCU2.
  2070. A HMCCURPCPROC device acts as a client device for a HMCCU I/O device. Normally RPC servers of
  2071. type HMCCURPCPROC are started or stopped from HMCCU I/O device via command 'set rpcserver on,off'.
  2072. HMCCURPCPROC devices will be created automatically by I/O device when RPC server is started.
  2073. There should be no need for creating HMCCURPCPROC devices manually.
  2074. </br></br>
  2075. <a name="HMCCURPCPROCdefine"></a>
  2076. <b>Define</b><br/><br/>
  2077. <ul>
  2078. <code>define &lt;name&gt; HMCCURPCPROC {&lt;HostOrIP&gt;|iodev=&lt;DeviceName&gt;}
  2079. {&lt;port&gt;|&lt;interface&gt;}</code>
  2080. <br/><br/>
  2081. Examples:<br/>
  2082. <code>define myccurpc HMCCURPCPROC 192.168.1.10 2001</code><br/>
  2083. <code>define myccurpc HMCCURPCPROC iodev=myccudev BidCos-RF</code><br/>
  2084. <br/><br/>
  2085. The parameter <i>HostOrIP</i> is the hostname or IP address of a Homematic CCU2.
  2086. The I/O device can also be specified with parameter iodev. Supported interfaces or
  2087. ports are:
  2088. <table>
  2089. <tr><td><b>Port</b></td><td><b>Interface</b></td></tr>
  2090. <tr><td>2000</td><td>BidCos-Wired</td></tr>
  2091. <tr><td>2001</td><td>BidCos-RF</td></tr>
  2092. <tr><td>2010</td><td>HmIP-RF</td></tr>
  2093. <tr><td>7000</td><td>HVL</td></tr>
  2094. <tr><td>8701</td><td>CUxD</td></tr>
  2095. <tr><td>9292</td><td>Virtual</td></tr>
  2096. </table>
  2097. </ul>
  2098. <br/>
  2099. <a name="HMCCURPCPROCset"></a>
  2100. <b>Set</b><br/><br/>
  2101. <ul>
  2102. <li><b> set &lt;name&gt; rpcrequest &lt;method&gt; [&lt;parameters&gt;]</b><br/>
  2103. Send RPC request to CCU. The result is displayed in FHEM browser window. See EQ-3
  2104. RPC XML documentation for mor information about valid methods and requests.
  2105. </li><br/>
  2106. <li><b>set &lt;name&gt; rpcserver { on | off }</b><br/>
  2107. Start or stop RPC server. This command is only available if expert mode is activated.
  2108. </li><br/>
  2109. </ul>
  2110. <a name="HMCCURPCPROCget"></a>
  2111. <b>Get</b><br/><br/>
  2112. <ul>
  2113. <li><b>get &lt;name&gt; rpcevent</b><br/>
  2114. Show RPC server events statistics.
  2115. </li><br/>
  2116. <li><b>get &lt;name&gt; rpcstate</b><br/>
  2117. Show RPC process state.
  2118. </li><br/>
  2119. </ul>
  2120. <a name="HMCCURPCPROCattr"></a>
  2121. <b>Attributes</b><br/><br/>
  2122. <ul>
  2123. <li><b>ccuflags { flag-list }</b><br/>
  2124. Set flags for controlling device behaviour. Meaning of flags is:<br/>
  2125. ccuInit - RPC server initialization depends on ListDevice RPC call issued by CCU.
  2126. This flag is not supported by interfaces CUxD and HVL.<br/>
  2127. expert - Activate expert mode<br/>
  2128. logEvents - Events are written into FHEM logfile if verbose is 4<br/>
  2129. queueEvents - Always write events into queue and send them asynchronously to FHEM.
  2130. Frequency of event transmission to FHEM depends on attribute rpcConnTimeout.<br/>
  2131. reconnect - Try to re-register at CCU if no events received for rpcEventTimeout seconds<br/>
  2132. </li><br/>
  2133. <li><b>rpcAcceptTimeout &lt;seconds&gt;</b><br/>
  2134. Specify timeout for accepting incoming connections. Default is 1 second. Increase this
  2135. value by 1 or 2 seconds on slow systems.
  2136. </li><br/>
  2137. <li><b>rpcConnTimeout &lt;seconds&gt;</b><br/>
  2138. Specify timeout of incoming CCU connections. Default is 1 second. Value must be greater than 0.
  2139. </li><br/>
  2140. <li><b>rpcEventTimeout &lt;seconds&gt;</b><br/>
  2141. Specify timeout for CCU events. Default is 600 seconds. If timeout occurs an event
  2142. is triggered. If set to 0 the timeout is ignored. If ccuflag reconnect is set the
  2143. RPC device tries to establish a new connection to the CCU.
  2144. </li><br/>
  2145. <li><b>rpcMaxEvents &lt;count&gt;</b><br/>
  2146. Specify maximum number of events read by FHEM during one I/O loop. If FHEM performance
  2147. slows down decrease this value and increase attribute rpcQueueSize. Default value is 100.
  2148. Value must be greater than 0.
  2149. </li><br/>
  2150. <li><b>rpcMaxIOErrors &lt;count&gt;</b><br/>
  2151. Specifiy maximum number of I/O errors allowed when sending events to FHEM before a
  2152. message is written into FHEM log file. Default value is 100. Set this attribute to 0
  2153. to disable error counting.
  2154. </li><br/>
  2155. <li><b>rpcQueueSend &lt;events&gt;</b><br/>
  2156. Maximum number of events sent to FHEM per accept loop. Default is 70. If set to 0
  2157. all events in queue are sent to FHEM. Transmission is stopped when an I/O error occurrs
  2158. or specified number of events has been sent.
  2159. </li><br/>
  2160. <li><b>rpcQueueSize &lt;count&gt;</b><br/>
  2161. Specify maximum size of event queue. When this limit is reached no more CCU events
  2162. are forwarded to FHEM. In this case increase this value or increase attribute
  2163. <b>rpcMaxEvents</b>. Default value is 500.
  2164. </li><br/>
  2165. <li><b>rpcServerAddr &lt;ip-address&gt;</b><br/>
  2166. Set local IP address of RPC servers on FHEM system. If attribute is missing the
  2167. corresponding attribute of I/O device (HMCCU device) is used or IP address is
  2168. detected automatically. This attribute should be set if FHEM is running on a system
  2169. with multiple network interfaces.
  2170. </li><br/>
  2171. <li><b>rpcServerPort &lt;port&gt;</b><br/>
  2172. Specify TCP port number used for calculation of real RPC server ports.
  2173. If attribute is missing the corresponding attribute of I/O device (HMCCU device)
  2174. is used. Default value is 5400.
  2175. </li><br/>
  2176. <li><b>rpcStatistics &lt;count&gt;</b><br/>
  2177. Specify amount of events after which statistic data is sent to FHEM. Default value
  2178. is 500.
  2179. </li><br/>
  2180. <li><b>rpcWriteTimeout &lt;seconds&gt;</b><br/>
  2181. Wait the specified time for socket to become readable or writeable. Default value
  2182. is 0.001 seconds.
  2183. </li>
  2184. </ul>
  2185. </ul>
  2186. =end html
  2187. =cut