00_FBAHAHTTP.pm 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375
  1. ##############################################
  2. # $Id: 00_FBAHAHTTP.pm 17700 2018-11-07 11:35:48Z rudolfkoenig $
  3. package main;
  4. # Documentation: AHA-HTTP-Interface.pdf, AVM_Technical_Note_-_Session_ID.pdf
  5. use strict;
  6. use warnings;
  7. use Time::HiRes qw(gettimeofday);
  8. use FritzBoxUtils;
  9. sub
  10. FBAHAHTTP_Initialize($)
  11. {
  12. my ($hash) = @_;
  13. $hash->{WriteFn} = "FBAHAHTTP_Write";
  14. $hash->{DefFn} = "FBAHAHTTP_Define";
  15. $hash->{SetFn} = "FBAHAHTTP_Set";
  16. $hash->{AttrFn} = "FBAHAHTTP_Attr";
  17. $hash->{ReadyFn} = "FBAHAHTTP_Ready";
  18. $hash->{RenameFn} = "FBAHAHTTP_RenameFn";
  19. $hash->{DeleteFn} = "FBAHAHTTP_Delete";
  20. $hash->{AttrList} = "dummy:1,0 fritzbox-user polltime async_delay ".
  21. "disable:0,1 disabledForIntervals fbTimeout";
  22. }
  23. #####################################
  24. sub
  25. FBAHAHTTP_Define($$)
  26. {
  27. my ($hash, $def) = @_;
  28. my @a = split("[ \t][ \t]*", $def);
  29. return "wrong syntax: define <name> FBAHAHTTP hostname"
  30. if(@a != 3);
  31. $hash->{Clients} = ":FBDECT:";
  32. my %matchList = ( "1:FBDECT" => ".*" );
  33. $hash->{MatchList} = \%matchList;
  34. # Moving definition from FBAHA to FBAHAHTTP
  35. for my $d (devspec2array("TYPE=FBDECT")) {
  36. if($defs{$d}{IODev} && $defs{$d}{IODev}{TYPE} eq "FBAHA") {
  37. my $n = $defs{$d}{IODev}{NAME};
  38. CommandAttr(undef, "$d IODev $hash->{NAME}");
  39. CommandDelete(undef, $n) if($defs{$n});
  40. $defs{$d}{IODev} = $hash;
  41. my $oldNr = $defs{$d}{IODev}{NR}; # Forum #92286
  42. $hash->{NR} = $oldNr if($hash->{NR} > $oldNr);
  43. }
  44. }
  45. $hash->{CmdStack} = ();
  46. return undef if($hash->{DEF} eq "none"); # DEBUGGING
  47. InternalTimer(1, "FBAHAHTTP_Poll", $hash);
  48. $hash->{STATE} = "defined";
  49. return undef;
  50. }
  51. #####################################
  52. sub
  53. FBAHAHTTP_Delete($)
  54. {
  55. my ($hash) = @_;
  56. my $name = $hash->{NAME};
  57. my ($err, $fb_pw) = setKeyValue("FBAHAHTTP_PASSWORD_$name", undef);
  58. return $err;
  59. }
  60. sub
  61. FBAHAHTTP_connect($)
  62. {
  63. my ($hash) = @_;
  64. my $name = $hash->{NAME};
  65. my $dev = $hash->{DEF};
  66. my $dr = sub {
  67. $hash->{STATE} = $_[0];
  68. Log 2, $hash->{STATE};
  69. $hash->{CmdStack} = ();
  70. return $hash->{STATE};
  71. };
  72. my $fb_user = AttrVal($name, "fritzbox-user", '');
  73. return $dr->("MISSING: attr $name fritzbox-user") if(!$fb_user);
  74. my ($err, $fb_pw) = getKeyValue("FBAHAHTTP_PASSWORD_$name");
  75. return $dr->("ERROR: $err") if($err);
  76. return $dr->("MISSING: set $name password") if(!$fb_pw);
  77. my $sid = FB_doCheckPW($hash->{DEF}, $fb_user, $fb_pw);
  78. if(!$sid) {
  79. $hash->{NEXT_OPEN} = time()+60;
  80. $readyfnlist{"$name.$dev"} = $hash;
  81. return $dr->("$name error: cannot get SID, ".
  82. "check connection/hostname/fritzbox-user/password")
  83. }
  84. delete($hash->{RetriedCmd});
  85. delete($readyfnlist{"$name.$dev"});
  86. $hash->{".SID"} = $sid;
  87. $hash->{STATE} = "connected";
  88. Log3 $name, 4, "FBAHAHTTP_connect $name: got SID $sid";
  89. return undef;
  90. }
  91. sub
  92. FBAHAHTTP_RenameFn($$)
  93. {
  94. my ($new, $old) = @_;
  95. for my $d (devspec2array("TYPE=FBDECT")) {
  96. my $hash = $defs{$d};
  97. next if(!$hash);
  98. $hash->{DEF} =~ s/^$old:/$new:/;
  99. $attr{$d}{IODev} = $new if(AttrVal($d,"IODev","") eq $old);
  100. }
  101. FBDECT_renameIoDev($new, $old);
  102. }
  103. #####################################
  104. sub
  105. FBAHAHTTP_Poll($)
  106. {
  107. my ($hash) = @_;
  108. my $name = $hash->{NAME};
  109. return if(IsDisabled($name));
  110. if(!$hash->{".SID"}) {
  111. my $ret = FBAHAHTTP_connect($hash);
  112. return $ret if($ret);
  113. }
  114. my $sid = $hash->{".SID"};
  115. my $host = ($hash->{DEF} =~ m/^http/i ? $hash->{DEF} : "http://$hash->{DEF}");
  116. HttpUtils_NonblockingGet({
  117. url=>"$host/webservices/homeautoswitch.lua?sid=$sid".
  118. "&switchcmd=getdevicelistinfos",
  119. loglevel => AttrVal($name, "verbose", 4),
  120. timeout => AttrVal($name, "fbTimeout", 4),
  121. callback => sub {
  122. if($_[1]) {
  123. Log3 $name, 3, "$name: $_[1]";
  124. delete $hash->{".SID"};
  125. return;
  126. }
  127. Log 5, $_[2] if(AttrVal($name, "verbose", 1) >= 5);
  128. if($_[2] !~ m,^<devicelist.*</devicelist>$,s) {
  129. Log3 $name, 3, "$name: unexpected reply from device: $_[2]";
  130. delete $hash->{".SID"};
  131. return;
  132. }
  133. $_[2] =~ s+<(device|group) (.*?)</\g1>+
  134. Dispatch($hash, "<$1 $2</$1>", undef);""+gse; # Quick&Hack
  135. }
  136. });
  137. my $polltime = AttrVal($name, "polltime", 300);
  138. RemoveInternalTimer($hash);
  139. InternalTimer(gettimeofday()+$polltime, "FBAHAHTTP_Poll", $hash);
  140. return;
  141. }
  142. #####################################
  143. sub
  144. FBAHAHTTP_Ready($)
  145. {
  146. my ($hash) = @_;
  147. return if($hash->{NEXT_OPEN} && time() < $hash->{NEXT_OPEN});
  148. FBAHAHTTP_Poll($hash);
  149. }
  150. #####################################
  151. sub
  152. FBAHAHTTP_Attr($@)
  153. {
  154. my ($type, $devName, $attrName, @param) = @_;
  155. my $hash = $defs{$devName};
  156. if($attrName eq "fritzbox-user") {
  157. return "Cannot delete fritzbox-user" if($type eq "del");
  158. if($init_done) {
  159. delete($hash->{".SID"});
  160. InternalTimer(1, sub { FBAHAHTTP_Poll($hash); }, 0);
  161. }
  162. }
  163. return undef;
  164. }
  165. #####################################
  166. sub
  167. FBAHAHTTP_Set($@)
  168. {
  169. my ($hash, @a) = @_;
  170. my $name = shift @a;
  171. my %sets = (password=>2, refreshstate=>1);
  172. return "set $name needs at least one parameter" if(@a < 1);
  173. my $type = shift @a;
  174. return "Unknown argument $type, choose one of refreshstate:noArg password"
  175. if(!defined($sets{$type}));
  176. return "Missing argument for $type" if(int(@a) < $sets{$type}-1);
  177. if($type eq "password") {
  178. setKeyValue("FBAHAHTTP_PASSWORD_$name", $a[0]);
  179. delete($hash->{".SID"});
  180. FBAHAHTTP_Poll($hash);
  181. return;
  182. }
  183. if($type eq "refreshstate") {
  184. FBAHAHTTP_Poll($hash);
  185. return;
  186. }
  187. return undef;
  188. }
  189. sub
  190. FBAHAHTTP_ProcessStack($)
  191. {
  192. my ($hash) = @_;
  193. my $name = $hash->{NAME};
  194. my $msg = $hash->{CmdStack}->[0];
  195. my $host = ($hash->{DEF} =~ m/^http/i ? $hash->{DEF} : "http://$hash->{DEF}");
  196. my $sid = $hash->{".SID"};
  197. return if(!$sid);
  198. HttpUtils_NonblockingGet({
  199. url=>"$host/webservices/homeautoswitch.lua?sid=$sid&$msg",
  200. loglevel => AttrVal($name, "verbose", 4),
  201. timeout => AttrVal($name, "fbTimeout", 4),
  202. callback => sub {
  203. if($_[1]) {
  204. Log3 $name, 3, "$name: $_[1]";
  205. delete $hash->{".SID"};
  206. $hash->{CmdStack} = ();
  207. return;
  208. }
  209. Log3 $name, 5, "FBAHAHTTP_Write reply for $name: $_[2]";
  210. if(!defined($_[2]) || $_[2] eq "") {
  211. if($hash->{RetriedCmd}) {
  212. Log3 $name, 1, "No sensible respone after reconnect, giving up";
  213. $hash->{CmdStack} = ();
  214. return;
  215. }
  216. return if(FBAHAHTTP_connect($hash));
  217. $hash->{RetriedCmd} = $msg;
  218. FBAHAHTTP_ProcessStack($hash);
  219. return;
  220. }
  221. delete($hash->{RetriedCmd});
  222. shift @{$hash->{CmdStack}};
  223. if(@{$hash->{CmdStack}} > 0) {
  224. my $ad = AttrVal($name, "async_delay", 0);
  225. InternalTimer(gettimeofday()+$ad, sub(){
  226. FBAHAHTTP_ProcessStack($hash);
  227. }, $hash);
  228. }
  229. }
  230. });
  231. }
  232. #####################################
  233. sub
  234. FBAHAHTTP_Write($$$)
  235. {
  236. my ($hash,$fn,$msg) = @_;
  237. my $name = $hash->{NAME};
  238. return if(IsDisabled($name));
  239. my $sid = $hash->{".SID"};
  240. if(!$sid) {
  241. my $ret = FBAHAHTTP_connect($hash); # try to reconnect
  242. return $ret if($ret);
  243. $sid = $hash->{".SID"};
  244. }
  245. push(@{$hash->{CmdStack}}, "ain=$fn&switchcmd=$msg");
  246. FBAHAHTTP_ProcessStack($hash) if(@{$hash->{CmdStack}} == 1);
  247. }
  248. 1;
  249. =pod
  250. =item summary connection to the Fritz!OS AHA Server via HTTP
  251. =item summary_DE Anbindung des Fritz!OS AHA Servers &uuml;ber HTTP
  252. =begin html
  253. <a name="FBAHAHTTP"></a>
  254. <h3>FBAHAHTTP</h3>
  255. <ul>
  256. This module connects to the AHA server (AVM Home Automation) on a FRITZ!Box
  257. via HTTP, it is a successor/drop-in replacement for the FBAHA module. It is
  258. necessary, as the FBAHA interface is deprecated by AVM. Since the AHA HTTP
  259. interface do not offer any notification mechanism, the module is regularly
  260. polling the FRITZ!Box.<br>
  261. Important: For an existing installation with an FBAHA device, defining a
  262. new FBAHAHTTP device will change the IODev of all FBDECT devices from the
  263. old FBAHA to this FBAHAHTTP device, and it will delete the FBAHA device.<br>
  264. This module serves as the "physical" counterpart to the <a
  265. href="#FBDECT">FBDECT</a> devices. Note: you have to enable the access to
  266. Smart Home in the FRITZ!Box frontend for the fritzbox-user, and take care
  267. to configure the login in the home network with username AND password.
  268. <br><br>
  269. <a name="FBAHAHTTPdefine"></a>
  270. <b>Define</b>
  271. <ul>
  272. <code>define &lt;name&gt; FBAHAHTTP &lt;hostname&gt;</code><br>
  273. <br>
  274. &lt;hostnamedevice&gt; is most probably fritz.box.
  275. Example:
  276. <ul>
  277. <code>define fb1 FBAHAHTTP fritz.box</code><br>
  278. </ul>
  279. Note: to specify HTTPS for the connection use https://fritz.box as
  280. hostname. To explicitly specify the port, postfix the hostname with :port,
  281. as in https://fritz.box:443
  282. </ul>
  283. <br>
  284. <a name="FBAHAHTTPset"></a>
  285. <b>Set</b>
  286. <ul>
  287. <li>password &lt;password&gt;<br>
  288. This is the only way to set the password
  289. </li>
  290. <li>refreshstate<br>
  291. The state of all devices is polled every &lt;polltime&gt; seconds (default
  292. is 300). This command forces a state-refresh.
  293. </li>
  294. </ul>
  295. <br>
  296. <a name="FBAHAHTTPget"></a>
  297. <b>Get</b>
  298. <ul>N/A</ul>
  299. <br>
  300. <a name="FBAHAHTTPattr"></a>
  301. <b>Attributes</b>
  302. <ul>
  303. <li><a href="#async_delay">async_delay</a><br>
  304. additional delay inserted, when switching more than one device, default
  305. is 0.2 seconds. Note: even with async_delay 0 there will be a delay, as
  306. FHEM avoids sending commands in parallel, to avoid malfunctioning of the
  307. Fritz!BOX AHA server).
  308. </li>
  309. <li><a href="#disable">disable</a></li>
  310. <li><a href="#disabledForIntervals">disabledForIntervals</a></li>
  311. <li><a href="#dummy">dummy</a></li>
  312. <li><a href="#fritzbox-user">fritzbox-user</a></li>
  313. <li><a name="polltime">polltime</a><br>
  314. measured in seconds, default is 300 i.e. 5 minutes
  315. </li>
  316. <li><a name="fbTimeout">fbTimeout</a><br>
  317. timeout for getting answer from the Fritz!BOX. Default is 4 (seconds).
  318. </li>
  319. </ul>
  320. <br>
  321. </ul>
  322. =end html
  323. =cut