ccurpcd.pl 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444
  1. #!/usr/bin/perl
  2. #########################################################
  3. # ccurpcd.pl
  4. #
  5. # $Id:
  6. #
  7. # Version 2.1
  8. #
  9. # FHEM RPC server for Homematic CCU.
  10. #
  11. # (C) 2016 by zap
  12. #--------------------------------------------------------
  13. # Usage:
  14. #
  15. # ccurpcd.pl Hostname Port QueueFile LogFile
  16. # ccurpcd.pl shutdown Hostname Port PID
  17. #--------------------------------------------------------
  18. # Queue file entries:
  19. #
  20. # Code|Data[|...]
  21. #
  22. # Server Loop: SL|pid|Server
  23. # Initialized: IN|INIT|1|Server
  24. # New device: ND|Address|Type
  25. # Updated device: UD|Address|Hint
  26. # Deleted device: DD|Address
  27. # Replace device: RD|Address1|Address2
  28. # Readd device: RA|Address
  29. # Event: EV|Address|Attribute|Value
  30. # Shutdown: EX|SHUTDOWN|pid|Server
  31. #########################################################
  32. use strict;
  33. use warnings;
  34. # use File::Queue;
  35. use RPC::XML::Server;
  36. use RPC::XML::Client;
  37. use IO::Socket::INET;
  38. use FindBin qw($Bin);
  39. use lib "$Bin";
  40. use RPCQueue;
  41. # Global variables
  42. my $client;
  43. my $server;
  44. my $queue;
  45. my $logfile;
  46. my $shutdown = 0;
  47. my $eventcount = 0;
  48. my $totalcount = 0;
  49. my $loglevel = 0;
  50. my %ev = ('total', 0, 'EV', 0, 'ND', 0, 'DD', 0, 'UD', 0, 'RD', 0, 'RA', 0, 'SL', 0, 'IN', 0, 'EX', 0);
  51. # Functions
  52. sub CheckProcess ($$);
  53. sub Log ($);
  54. sub WriteQueue ($);
  55. sub CCURPC_Shutdown ($$$);
  56. sub CCURPC_Initialize ($$);
  57. #####################################
  58. # Get PID of running RPC server or 0
  59. #####################################
  60. sub CheckProcess ($$)
  61. {
  62. my ($prcname, $port) = @_;
  63. my $filename = $prcname;
  64. my $pdump = `ps ax | grep $prcname | grep -v grep`;
  65. my @plist = split "\n", $pdump;
  66. foreach my $proc (@plist) {
  67. # Remove leading blanks, fix for MacOS. Thanks to mcdeck
  68. $proc =~ s/^\s+//;
  69. my @procattr = split /\s+/, $proc;
  70. if ($procattr[0] != $$ && $procattr[4] =~ /perl$/ &&
  71. ($procattr[5] eq $prcname || $procattr[5] =~ /\/ccurpcd\.pl$/) &&
  72. $procattr[7] eq "$port") {
  73. Log "Process $proc is running connected to CCU port $port";
  74. return $procattr[0];
  75. }
  76. }
  77. return 0;
  78. }
  79. #####################################
  80. # Write logfile entry
  81. #####################################
  82. sub Log ($)
  83. {
  84. my @messages = @_;
  85. my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime ();
  86. if (open (LOGFILE, '>>', $logfile)) {
  87. printf LOGFILE "%02d.%02d.%04d %02d:%02d:%02d ",
  88. $mday,$mon+1,$year+1900,$hour,$min,$sec;
  89. foreach my $token (@messages) {
  90. print LOGFILE $token;
  91. }
  92. print LOGFILE "\n";
  93. close LOGFILE;
  94. }
  95. }
  96. #####################################
  97. # Write queue entry
  98. #####################################
  99. sub WriteQueue ($)
  100. {
  101. my ($message) = @_;
  102. $queue->enq ($message);
  103. return 1;
  104. }
  105. #####################################
  106. # Shutdown RPC connection
  107. #####################################
  108. sub CCURPC_Shutdown ($$$)
  109. {
  110. my ($serveraddr, $serverport, $pid) = @_;
  111. # Detect local IP
  112. my $socket = IO::Socket::INET->new (PeerAddr => $serveraddr, PeerPort => $serverport);
  113. if (!$socket) {
  114. print "Can't connect to CCU port $serverport";
  115. return undef;
  116. }
  117. my $localaddr = $socket->sockhost ();
  118. close ($socket);
  119. my $ccurpcport = 5400+$serverport;
  120. my $callbackurl = "http://".$localaddr.":".$ccurpcport."/fh".$serverport;
  121. $client = RPC::XML::Client->new ("http://$serveraddr:$serverport/");
  122. print "Trying to deregister RPC server $callbackurl\n";
  123. $client->send_request("init", $callbackurl);
  124. sleep (3);
  125. print "Sending SIGINT to PID $pid\n";
  126. kill ('INT', $pid);
  127. return undef;
  128. }
  129. #####################################
  130. # Initialize RPC connection
  131. #####################################
  132. sub CCURPC_Initialize ($$)
  133. {
  134. my ($serveraddr, $serverport) = @_;
  135. my $callbackport = 5400+$serverport;
  136. # Create RPC server
  137. $server = RPC::XML::Server->new (port=>$callbackport);
  138. if (!ref($server))
  139. {
  140. Log "Can't create RPC callback server on port $callbackport. Port in use?";
  141. return undef;
  142. }
  143. else {
  144. Log "Callback server created listening on port $callbackport";
  145. }
  146. # Callback for events
  147. Log "Adding callback for events";
  148. $server->add_method (
  149. { name=>"event",
  150. signature=> ["string string string string int","string string string string double","string string string string boolean","string string string string i4"],
  151. code=>\&CCURPC_EventCB
  152. }
  153. );
  154. # Callback for new devices
  155. Log "Adding callback for new devices";
  156. $server->add_method (
  157. { name=>"newDevices",
  158. signature=>["string string array"],
  159. code=>\&CCURPC_NewDevicesCB
  160. }
  161. );
  162. # Callback for deleted devices
  163. Log "Adding callback for deleted devices";
  164. $server->add_method (
  165. { name=>"deleteDevices",
  166. signature=>["string string array"],
  167. code=>\&CCURPC_DeleteDevicesCB
  168. }
  169. );
  170. # Callback for modified devices
  171. Log "Adding callback for modified devices";
  172. $server->add_method (
  173. { name=>"updateDevice",
  174. signature=>["string string string int"],
  175. code=>\&CCURPC_UpdateDeviceCB
  176. }
  177. );
  178. # Callback for replaced devices
  179. Log "Adding callback for replaced devices";
  180. $server->add_method (
  181. { name=>"replaceDevice",
  182. signature=>["string string string string"],
  183. code=>\&CCURPC_ReplaceDeviceCB
  184. }
  185. );
  186. # Callback for readded devices
  187. Log "Adding callback for readded devices";
  188. $server->add_method (
  189. { name=>"replaceDevice",
  190. signature=>["string string array"],
  191. code=>\&CCURPC_ReaddDeviceCB
  192. }
  193. );
  194. # Dummy implementation, always return an empty array
  195. $server->add_method (
  196. { name=>"listDevices",
  197. signature=>["array string"],
  198. code=>\&CCURPC_ListDevicesCB
  199. }
  200. );
  201. return "OK";
  202. }
  203. #####################################
  204. # Callback for new devices
  205. #####################################
  206. sub CCURPC_NewDevicesCB ($$$)
  207. {
  208. my ($server, $cb, $a) = @_;
  209. Log "NewDevice: received ".scalar(@$a)." device specifications";
  210. for my $dev (@$a) {
  211. $ev{total}++;
  212. $ev{ND}++;
  213. WriteQueue ("ND|".$dev->{ADDRESS}."|".$dev->{TYPE});
  214. }
  215. # return RPC::XML::array->new();
  216. return;
  217. }
  218. #####################################
  219. # Callback for deleted devices
  220. #####################################
  221. sub CCURPC_DeleteDevicesCB ($$$)
  222. {
  223. my ($server, $cb, $a) = @_;
  224. Log "DeleteDevice: received ".scalar(@$a)." device addresses";
  225. for my $dev (@$a) {
  226. $ev{total}++;
  227. $ev{DD}++;
  228. WriteQueue ("DD|".$dev);
  229. }
  230. return;
  231. }
  232. #####################################
  233. # Callback for modified devices
  234. #####################################
  235. sub CCURPC_UpdateDeviceCB ($$$$)
  236. {
  237. my ($server, $cb, $devid, $hint) = @_;
  238. $ev{total}++;
  239. $ev{UD}++;
  240. WriteQueue ("UD|".$devid."|".$hint);
  241. return;
  242. }
  243. #####################################
  244. # Callback for replaced devices
  245. #####################################
  246. sub CCURPC_ReplaceDeviceCB ($$$$)
  247. {
  248. my ($server, $cb, $devid1, $devid2) = @_;
  249. $ev{total}++;
  250. $ev{RD}++;
  251. WriteQueue ("RD|".$devid1."|".$devid2);
  252. return;
  253. }
  254. #####################################
  255. # Callback for readded devices
  256. #####################################
  257. sub CCURPC_ReaddDevicesCB ($$$)
  258. {
  259. my ($server, $cb, $a) = @_;
  260. Log "ReaddDevice: received ".scalar(@$a)." device addresses";
  261. for my $dev (@$a) {
  262. $ev{total}++;
  263. $ev{RA}++;
  264. WriteQueue ("RA|".$dev);
  265. }
  266. return;
  267. }
  268. #####################################
  269. # Callback for handling CCU events
  270. #####################################
  271. sub CCURPC_EventCB ($$$$$)
  272. {
  273. my ($server,$cb,$devid,$attr,$val) = @_;
  274. $ev{total}++;
  275. $ev{EV}++;
  276. WriteQueue ("EV|".$devid."|".$attr."|".$val);
  277. $eventcount++;
  278. if (($eventcount % 500) == 0 && $loglevel == 2) {
  279. Log "Received $eventcount events from CCU since last check";
  280. my @stkeys = ('total', 'EV', 'ND', 'DD', 'RD', 'RA', 'UD', 'IN', 'SL', 'EX');
  281. my $msg = "ST";
  282. foreach my $stkey (@stkeys) {
  283. $msg .= "|".$ev{$stkey};
  284. }
  285. WriteQueue ($msg);
  286. }
  287. # Never remove this statement!
  288. return;
  289. }
  290. #####################################
  291. # Callback for list devices
  292. #####################################
  293. sub CCURPC_ListDevicesCB ()
  294. {
  295. my ($server, $cb) = @_;
  296. $ev{total}++;
  297. $ev{IN}++;
  298. $cb = "unknown" if (!defined ($cb));
  299. Log "ListDevices $cb. Sending init to HMCCU";
  300. WriteQueue ("IN|INIT|1|$cb");
  301. return RPC::XML::array->new();
  302. }
  303. #####################################
  304. # MAIN
  305. #####################################
  306. my $name = $0;
  307. # Process command line arguments
  308. if ($#ARGV+1 < 4) {
  309. print "Usage: $name CCU-Host Port QueueFile LogFile LogLevel\n";
  310. print " $name shutdown CCU-Host Port PID\n";
  311. exit 1;
  312. }
  313. #
  314. # Manually shutdown RPC server
  315. #
  316. if ($ARGV[0] eq 'shutdown') {
  317. CCURPC_Shutdown ($ARGV[1], $ARGV[2], $ARGV[3]);
  318. exit 0;
  319. }
  320. #
  321. # Start RPC server
  322. #
  323. my $ccuhost = $ARGV[0];
  324. my $ccuport = $ARGV[1];
  325. my $queuefile = $ARGV[2];
  326. $logfile = $ARGV[3];
  327. $loglevel = $ARGV[4] if ($#ARGV+1 == 5);
  328. my $pid = CheckProcess ($name, $ccuport);
  329. if ($pid > 0) {
  330. Log "Error: ccurpcd.pl is already running (PID=$pid) for CCU port $ccuport";
  331. die "Error: ccurpcd.pl is already running (PID=$pid) for CCU port $ccuport\n";
  332. }
  333. # Create or open queue
  334. Log "Creating file queue";
  335. $queue = new RPCQueue (File => $queuefile, Mode => 0666);
  336. if (!defined ($queue)) {
  337. Log "Error: Can't create queue";
  338. die "Error: Can't create queue\n";
  339. }
  340. else {
  341. $queue->reset ();
  342. while ($queue->deq ()) { }
  343. }
  344. # Initialize RPC server
  345. Log "Initializing RPC server";
  346. my $callbackurl = CCURPC_Initialize ($ccuhost, $ccuport);
  347. if (!defined ($callbackurl)) {
  348. Log "Error: Can't initialize RPC server";
  349. die "Error: Can't initialize RPC server\n";
  350. }
  351. # Server loop is interruptable bei signal SIGINT
  352. Log "Entering server loop. Use kill -SIGINT $$ to terminate program";
  353. WriteQueue ("SL|$$|CB".$ccuport);
  354. $server->server_loop;
  355. $totalcount++;
  356. WriteQueue ("EX|SHUTDOWN|$$|CB".$ccuport);
  357. $ev{total}++;
  358. $ev{EX}++;
  359. Log "RPC server terminated";
  360. if ($loglevel == 2) {
  361. foreach my $cnt (sort keys %ev) {
  362. Log "Events $cnt = ".$ev{$cnt};
  363. }
  364. }