88_HMCCURPCPROC.pm 82 KB

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