00_HMRPC.pm 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. ###########################################################
  2. #
  3. # HomeMatic XMLRPC API Device Provider
  4. # Written by Oliver Wagner <owagner@vapor.com>
  5. #
  6. # V0.5
  7. #
  8. ###########################################################
  9. #
  10. # This module implements the documented XML-RPC based API
  11. # of the Homematic system software (currently offered as
  12. # part of the CCU1 and of the LAN config adapter software)
  13. #
  14. # This module operates a http server to receive incoming
  15. # xmlrpc event notifications from the HM software.
  16. #
  17. # Individual devices are then handled by 01_HMDEV.pm
  18. #
  19. package main;
  20. use strict;
  21. use warnings;
  22. use Time::HiRes qw(gettimeofday);
  23. use RPC::XML::Server;
  24. use RPC::XML::Client;
  25. use Dumpvalue;
  26. my $dumper=new Dumpvalue;
  27. $dumper->veryCompact(1);
  28. sub HMRPC_Initialize($)
  29. {
  30. my ($hash) = @_;
  31. $hash->{DefFn} = "HMRPC_Define";
  32. $hash->{ShutdownFn} = "HMRPC_Shutdown";
  33. $hash->{ReadFn} = "HMRPC_Read";
  34. $hash->{SetFn} = "HMRPC_Set";
  35. $hash->{GetFn} = "HMRPC_Get";
  36. $hash->{Clients} = ":HMDEV:";
  37. }
  38. #####################################
  39. sub
  40. HMRPC_Shutdown($)
  41. {
  42. my ($hash) = @_;
  43. # Uninitialize again
  44. if($hash->{callbackurl})
  45. {
  46. Log(2,"HMRPC unitializing callback ".$hash->{callbackurl});
  47. $hash->{client}->send_request("init",$hash->{callbackurl});
  48. }
  49. return undef;
  50. }
  51. #####################################
  52. sub
  53. HMRPC_Define($$)
  54. {
  55. my ($hash, $def) = @_;
  56. my @a = split("[ \t][ \t]*", $def);
  57. if(@a != 4) {
  58. my $msg = "wrong syntax: define <name> HMRPC remote_host remote_port";
  59. Log 2, $msg;
  60. return $msg;
  61. }
  62. $hash->{serveraddr}=$a[2];
  63. $hash->{serverport}=$a[3];
  64. $hash->{client}=RPC::XML::Client->new("http://$a[2]:$a[3]/");
  65. my $callbackport=5400+$hash->{serverport};
  66. $hash->{server}=RPC::XML::Server->new(port=>$callbackport);
  67. if(!ref($hash->{server}))
  68. {
  69. # Creating the server failed, perhaps because the port was
  70. # already in use. Just return the message
  71. Log 1,"Can't create HMRPC callback server on port $callbackport. Port in use?";
  72. return $hash->{server};
  73. }
  74. $hash->{server}->{fhemdef}=$hash;
  75. # Add the XMLRPC methods we do expose
  76. $hash->{server}->add_method(
  77. {name=>"event",signature=> ["string string string string int","string string string string double","string string string string boolean","string string string string i4"],code=>\&HMRPC_EventCB}
  78. );
  79. $hash->{server}->add_method(
  80. {name=>"newDevices",signature=>["array string array"],code=>\&HMRPC_NewDevicesCB }
  81. );
  82. #
  83. # Dummy implementation, always return an empty array
  84. #
  85. $hash->{server}->add_method(
  86. {name=>"listDevices",signature=>["array string"],code=>sub{return RPC::XML::array->new()} }
  87. );
  88. $hash->{STATE} = "Initialized";
  89. $hash->{SERVERSOCKET}=$hash->{server}->{__daemon};
  90. $hash->{FD}=$hash->{SERVERSOCKET}->fileno();
  91. $hash->{PORT}=$hash->{server}->{__daemon}->sockport();
  92. # This will also register the callback
  93. HMRPC_CheckCallback($hash);
  94. $selectlist{"$hash->{serveraddr}.$hash->{serverport}"} = $hash;
  95. #
  96. # All is well
  97. #
  98. return 0;
  99. }
  100. sub
  101. HMRPC_CheckCallback($)
  102. {
  103. my ($hash) = @_;
  104. # We recheck the callback every 15 minutes. If we didn't receive anything
  105. # inbetween, we re-init just to make sure (CCU reboots etc.)
  106. InternalTimer(gettimeofday()+(15*60), "HMRPC_CheckCallback", $hash, 0);
  107. if(!$hash->{lastcallbackts})
  108. {
  109. HMRPC_RegisterCallback($hash);
  110. return;
  111. }
  112. my $age=int(gettimeofday()-$hash->{lastcallbackts});
  113. if($age>(15*60))
  114. {
  115. Log 5,"HMRPC Last callback received more than $age seconds ago, re-init-ing";
  116. HMRPC_RegisterCallback($hash);
  117. }
  118. }
  119. sub
  120. HMRPC_RegisterCallback($)
  121. {
  122. my ($hash) = @_;
  123. #
  124. # We need to find out our local address. In order to do so,
  125. # we establish a dummy connection to the remote xmlrpc server
  126. # and then look at the local socket address assigned to us.
  127. #
  128. my $dummysock=IO::Socket::INET->new(PeerAddr=>$hash->{serveraddr},PeerPort=>$hash->{serverport});
  129. if(!$dummysock)
  130. {
  131. Log(2,"HMRPC unable to connect to ".$hash->{serveraddr}.":".$hash->{serverport}." ($!), will retry later");
  132. return;
  133. }
  134. $hash->{callbackurl}="http://".$dummysock->sockhost().":".$hash->{PORT}."/fh";
  135. $dummysock->close();
  136. Log(2, "HMRPC callback listening on $hash->{callbackurl}");
  137. # We need to fork here, as the xmlrpc server will synchronously call us
  138. if(!fork())
  139. {
  140. $hash->{client}->send_request("init",$hash->{callbackurl},"CB1");
  141. Log(2, "HMRPC callback with URL ".$hash->{callbackurl}." initialized");
  142. exit(0);
  143. }
  144. }
  145. #####################################
  146. # Process device info
  147. sub
  148. HMRPC_NewDevicesCB($$$)
  149. {
  150. my ($server, $cb, $a) = @_;
  151. my $hash=$server->{fhemdef};
  152. Log(2,"HMRPC received ".scalar(@$a)." device specifications");
  153. # We receive an array of hashes with the device information. We
  154. # store those hashes again in a hash, keyed by address, for later
  155. # use by the individual devices
  156. for my $dev (@$a)
  157. {
  158. my $addr=$dev->{ADDRESS};
  159. $hash->{devicespecs}{$addr}=$dev;
  160. }
  161. return RPC::XML::array->new();
  162. }
  163. #####################################
  164. sub
  165. HMRPC_EventCB($$$$$)
  166. {
  167. my ($server,$cb,$devid,$attr,$val)=@_;
  168. Log(5, "Processing event setting $devid->$attr=$val" );
  169. Dispatch($server->{fhemdef},"HMDEV $devid $attr $val",undef);
  170. $server->{fhemdef}->{lastcallbackts}=gettimeofday();
  171. }
  172. sub
  173. HMRPC_Read($)
  174. {
  175. my ($hash) = @_;
  176. #
  177. # Handle an incoming callback
  178. #
  179. my $conn=$hash->{server}->{__daemon}->accept();
  180. $conn->timeout(20);
  181. $hash->{server}->process_request($conn);
  182. $conn->close;
  183. undef $conn;
  184. }
  185. ################################
  186. #
  187. #
  188. sub
  189. HMRPC_Set($@)
  190. {
  191. my ($hash, @a) = @_;
  192. #return "invalid set specification @a" if(@a != 4 && @a != 5);
  193. my $cmd=$a[1];
  194. if($cmd eq "req")
  195. {
  196. # Send a raw xmlrpc request and return the result in
  197. # text form. This is mainly useful for diagnostics.
  198. shift @a;
  199. shift @a;
  200. my $ret=$hash->{client}->simple_request(@a);
  201. # We convert using Dumpvalue. As this only prints, we need
  202. # to temporarily redirect STDOUT
  203. my $res="";
  204. open(my $temp,"+>",\$res);
  205. my $oldout=select($temp);
  206. $dumper->dumpValue($ret);
  207. close(select($oldout));
  208. return $res;
  209. }
  210. my $ret;
  211. if(@a==5)
  212. {
  213. my $paramset={$a[3]=>$a[4]};
  214. $ret=$hash->{client}->simple_request("putParamset",$a[1],$a[2],$paramset);
  215. }
  216. else
  217. {
  218. $ret=$hash->{client}->simple_request("setValue",$a[1],$a[2],$a[3]);
  219. }
  220. if($ret)
  221. {
  222. return $ret->{faultCode}.": ".$ret->{faultString};
  223. }
  224. else
  225. {
  226. return undef;
  227. }
  228. }
  229. ################################
  230. #
  231. #
  232. sub
  233. HMRPC_Get($@)
  234. {
  235. my ($hash,@a) = @_;
  236. return "argument missing, usage is <id> <attribute> @a" if(@a!=3);
  237. my $ret=$hash->{client}->simple_request("getValue",$a[1],$a[2]);
  238. if(ref($ret))
  239. {
  240. return $ret->{faultCode}.": ".$ret->{faultString};
  241. }
  242. return $ret;
  243. }
  244. 1;