83_IOhomecontrol.pm 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  1. # $Id: 83_IOhomecontrol.pm 15083 2017-09-17 16:43:41Z neubert $
  2. ##############################################################################
  3. #
  4. # 83_IOhomecontrol.pm
  5. # Copyright by Dr. Boris Neubert
  6. # e-mail: omega at online dot de
  7. #
  8. # This file is part of fhem.
  9. #
  10. # Fhem is free software: you can redistribute it and/or modify
  11. # it under the terms of the GNU General Public License as published by
  12. # the Free Software Foundation, either version 2 of the License, or
  13. # (at your option) any later version.
  14. #
  15. # Fhem is distributed in the hope that it will be useful,
  16. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. # GNU General Public License for more details.
  19. #
  20. # You should have received a copy of the GNU General Public License
  21. # along with fhem. If not, see <http://www.gnu.org/licenses/>.
  22. #
  23. ##############################################################################
  24. package main;
  25. use strict;
  26. use warnings;
  27. use HttpUtils;
  28. use JSON;
  29. use Data::Dumper;
  30. #####################################
  31. sub IOhomecontrol_Initialize($) {
  32. my ($hash) = @_;
  33. $hash->{DefFn} = "IOhomecontrol_Define";
  34. $hash->{UndefFn} = "IOhomecontrol_Undef";
  35. $hash->{GetFn} = "IOhomecontrol_Get";
  36. $hash->{SetFn} = "IOhomecontrol_Set";
  37. $hash->{parseParams} = 1;
  38. #$hash->{AttrFn} = "IOhomecontrol_Attr";
  39. $hash->{AttrList} = "setCmds " . $readingFnAttributes;
  40. }
  41. #####################################
  42. sub IOhomecontrol_Define($$) {
  43. # define <name> IOhomecontrol <model> <host> <pwfile>
  44. my ($hash, $argref, undef) = @_;
  45. my @def= @{$argref};
  46. if($#def != 4) {
  47. my $msg = "wrong syntax: define <name> IOhomecontrol <model> <host> <pwfile>";
  48. Log 2, $msg;
  49. return $msg;
  50. }
  51. my $name = $def[0];
  52. my $unused = $def[2];
  53. my $host = $def[3];
  54. my $pwfile = $def[4];
  55. $hash->{"host"}= $host;
  56. $hash->{"pwfile"}= $pwfile;
  57. $hash->{fhem}{".token"}= undef;
  58. $hash->{fhem}{".scenes"}= undef;
  59. $hash->{STATE} = "Initialized";
  60. return undef;
  61. }
  62. #####################################
  63. sub IOhomecontrol_Undef($$) {
  64. return undef;
  65. }
  66. #####################################
  67. # Internals
  68. #####################################
  69. sub IOhomecontrol_Exec($$$$) {
  70. my ($hash, $api, $action, $params) = @_;
  71. my $name = $hash->{NAME};
  72. my $host = $hash->{"host"};
  73. my $token = $hash->{fhem}{".token"};
  74. # build header
  75. my $header = {
  76. "Accept" => "application/json",
  77. "Content-Type" => "application/json;charset=utf-8",
  78. };
  79. if(defined($token)) {
  80. $header->{"Authorization"} = "Bearer $token";
  81. };
  82. # build payload
  83. my $payload = {
  84. "action" => $action,
  85. "params" => $params,
  86. };
  87. my $json = encode_json $payload;
  88. #Debug "IOhomecontrol $name: sending $json";
  89. # build HTTP request
  90. my $httpParams = {
  91. url => "http://$host/api/v1/$api",
  92. timeout => 2,
  93. method => "POST",
  94. noshutdown => 1,
  95. keepalive => 0,
  96. httpversion => "1.1",
  97. header => $header,
  98. data => $json,
  99. };
  100. my ($err, $data)= HttpUtils_BlockingGet($httpParams);
  101. if(defined($err) && $err) {
  102. Log3 $hash, 2, "IOhomecontrol $name returned error: $err";
  103. return undef;
  104. } else {
  105. if(defined($data) && $data) {
  106. # strip junk from the beginning
  107. $data =~ s/^\)\]\}\',//;
  108. #Debug "IOhomecontrol $name: data $data";
  109. my $result = decode_json $data;
  110. #Debug Dumper $result;
  111. my $errorsref= $result->{errors};
  112. my @errors= @{$errorsref};
  113. $err= "";
  114. if(@errors) {
  115. $err= join(" ", @errors);
  116. Log3 $hash, 2, "IOhomecontrol $name: API $api, action $action returned errors ($err).";
  117. };
  118. readingsBeginUpdate($hash);
  119. readingsBulkUpdate($hash, "deviceStatus", $result->{deviceStatus});
  120. readingsBulkUpdate($hash, "errors", $err);
  121. readingsEndUpdate($hash, 1);
  122. return undef if(@errors);
  123. return $result; # this is a hash reference
  124. } else {
  125. Log3 $hash, 2, "IOhomecontrol $name returned no data.";
  126. return undef;
  127. }
  128. }
  129. }
  130. #####################################
  131. sub IOhomecontrol_getPassword($) {
  132. my $hash = shift;
  133. my $pwfile= $hash->{"pwfile"};
  134. if(open(PWFILE, $pwfile)) {
  135. my @contents= <PWFILE>;
  136. close(PWFILE);
  137. return undef unless @contents;
  138. my $password = $contents[0];
  139. chomp $password;
  140. return $password;
  141. } else {
  142. return undef;
  143. }
  144. }
  145. sub IOhomecontrol_Login($) {
  146. my $hash = shift;
  147. my $name = $hash->{NAME};
  148. Log3 $hash, 4, "IOhomecontrol $name: Logging in...";
  149. my $password = IOhomecontrol_getPassword($hash);
  150. if(!defined($password)) {
  151. Log3 $hash, 2, "IOhomecontrol $name: No password.";
  152. return 0;
  153. }
  154. my $params = { "password" => $password };
  155. my $result= IOhomecontrol_Exec($hash, "auth", "login", $params);
  156. if(defined($result)) {
  157. my $token= $result->{token};
  158. Log3 $hash, 4, "IOhomecontrol $name got token: $token";
  159. $hash->{fhem}{".token"}= $token;
  160. return 1;
  161. }
  162. return 0;
  163. }
  164. sub IOhomecontrol_Logout($) {
  165. my $hash = shift;
  166. my $name = $hash->{NAME};
  167. Log3 $hash, 4, "IOhomecontrol $name: Logging out...";
  168. my $params = { };
  169. my $result= IOhomecontrol_Exec($hash, "auth", "logout", $params);
  170. if(defined($result)) {
  171. Log3 $hash, 4, "IOhomecontrol $name logged out.";
  172. $hash->{fhem}{".token"}= undef;
  173. }
  174. return undef;
  175. }
  176. sub IOhomecontrol_Action($$$$) {
  177. my ($hash, $api, $action, $params) = @_;
  178. my $name = $hash->{NAME};
  179. Log3 $hash, 4, "IOhomecontrol $name: API $api, action $action";
  180. if(IOhomecontrol_Login($hash)) {
  181. my $result= IOhomecontrol_Exec($hash, $api, $action, $params);
  182. IOhomecontrol_Logout($hash);
  183. return $result;
  184. }
  185. return undef;
  186. }
  187. #####################################
  188. sub IOhomecontrol_makeScenes($) {
  189. my $hash= shift;
  190. my $scenes= $hash->{fhem}{".scenes"};
  191. if(!defined($scenes)) {
  192. $scenes= IOhomecontrol_getScenes($hash);
  193. $hash->{fhem}{".scenes"} = $scenes;
  194. }
  195. my $sc= {};
  196. if(defined($scenes)) {
  197. my $data= $scenes->{data};
  198. #Debug "data: " . Dumper $data;
  199. foreach my $item (@{$data}) {
  200. #Debug "data item: " . Dumper $item;
  201. my $name= $item->{name};
  202. my $id= $item->{id};
  203. #Debug "$id: $name";
  204. $sc->{$id}= $name;
  205. }
  206. my $sns= "";
  207. foreach my $id (sort keys %{$sc}) {
  208. $sns.="," if($sns);
  209. $sns.= sprintf("%d: %s", $id, $sc->{$id});
  210. }
  211. readingsSingleUpdate($hash, "scenes", $sns, 1);
  212. }
  213. return $sc; # a hash id => name
  214. }
  215. sub IOhomecontrol_getScenes($) {
  216. my $hash= shift;
  217. my $scenes= IOhomecontrol_Action($hash, "scenes", "get", {});
  218. #Debug Dumper $scenes;
  219. return $scenes;
  220. }
  221. sub IOhomecontrol_runSceneById($$$) {
  222. my ($hash, $id, $name)= @_;
  223. IOhomecontrol_Action($hash, "scenes", "run", { id => $id });
  224. readingsSingleUpdate($hash, "lastScene", $name, 1);
  225. }
  226. #####################################
  227. sub IOhomecontrol_Get($@) {
  228. my ($hash, $argsref, undef) = @_;
  229. my @a= @{$argsref};
  230. return "get needs at least one parameter" if(@a < 2);
  231. my $name = $a[0];
  232. my $cmd= $a[1];
  233. my $arg = ($a[2] ? $a[2] : "");
  234. my @args= @a; shift @args; shift @args;
  235. my $answer= "";
  236. if($cmd eq "scenes") {
  237. $hash->{fhem}{".scenes"}= undef; # forget scenes forces get from device
  238. my $sc= IOhomecontrol_makeScenes($hash);
  239. foreach my $id (sort keys %{$sc}) {
  240. $answer.="\n" if($answer);
  241. $answer.= sprintf("%2d: %s", $id, $sc->{$id});
  242. }
  243. } else {
  244. return "Unknown argument $cmd, choose one of scenes:noArg";
  245. }
  246. return $answer;
  247. }
  248. #####################################
  249. # sub IOhomecontrol_Attr($@) {
  250. #
  251. # my @a = @_;
  252. # my $hash= $defs{$a[1]};
  253. # my $name= $hash->{NAME};
  254. #
  255. # if($a[0] eq "set") {
  256. # if($a[2] eq "") {
  257. # }
  258. # }
  259. #
  260. # return undef;
  261. # }
  262. #####################################
  263. sub IOhomecontrol_getSetCmds($) {
  264. my $hash= shift;
  265. my $name= $hash->{NAME};
  266. my $attr= AttrVal($name, "setCmds", "");
  267. my (undef, $setCmds)= parseParams($attr,",");
  268. return $setCmds;
  269. }
  270. sub IOhomecontrol_Set($$$) {
  271. my ($hash, $argsref, undef) = @_;
  272. my @a= @{$argsref};
  273. return "set needs at least one parameter" if(@a < 2);
  274. my $name = shift @a;
  275. my $cmd= shift @a;
  276. my $setCmds= IOhomecontrol_getSetCmds($hash);
  277. my $usage= "Unknown argument $cmd, choose one of scene " .
  278. join(" ", (keys %{$setCmds}));
  279. if(exists($setCmds->{$cmd})) {
  280. readingsSingleUpdate($hash, "state", $cmd, 1);
  281. my $subst= $setCmds->{$cmd};
  282. Log3 $hash, 4, "IOhomecontrol $name: substitute set command $cmd by $subst";
  283. ($argsref, undef)= parseParams($subst);
  284. @a= @{$argsref};
  285. $cmd= shift @a;
  286. }
  287. if($cmd eq "scene") {
  288. if($#a) {
  289. Debug Dumper @a;
  290. return "Command scene needs exactly one argument.";
  291. } else {
  292. my $sc= IOhomecontrol_makeScenes($hash);
  293. my $id= $a[0];
  294. if($id !~ /^\d+$/) {
  295. #Debug "IOhomecontrol $name: looking up scene $id by name...";
  296. my %cs= reverse %{$sc};
  297. $id= $cs{$id};
  298. }
  299. my $sn= $sc->{$id};
  300. if(defined($sn)) {
  301. Log3 $hash, 4, "IOhomecontrol $name: running scene id $id, name $sn";
  302. IOhomecontrol_runSceneById($hash, $id, $sn);
  303. } else {
  304. return "No such scene $id";
  305. }
  306. }
  307. } else {
  308. return $usage
  309. }
  310. return undef;
  311. }
  312. #####################################
  313. 1;
  314. =pod
  315. =item device
  316. =item summary control IOhomecontrol devices via REST API
  317. =item summary_DE IOhomecontrol-Ger&auml;te mittels REST-API steuern
  318. =begin html
  319. <a name="IOhomecontrol"></a>
  320. <h3>IOhomecontrol</h3>
  321. <ul>
  322. <a name="IOhomecontroldefine"></a>
  323. <b>Define</b><br><br>
  324. <ul>
  325. <code>define &lt;name&gt; IOhomecontrol &lt;model&gt; &lt;host&gt; &lt;pwfile&gt; </code><br><br>
  326. Defines a IOhomecontrol device. <code>&lt;model&gt;</code> is a placeholder for future amendments. <code>&lt;host&gt;</code> is the IP address or hostname of the IOhomecontrol device. <code>&lt;pwfile&gt;</code> is a file that contains the password to log into the device.<br><br>
  327. Example:
  328. <ul>
  329. <code>define velux IOhomecontrol KLF200 192.168.0.91 /opt/fhem/etc/veluxpw.txt</code><br>
  330. </ul>
  331. <br><br>
  332. </ul>
  333. <a name="IOhomecontrolset"></a>
  334. <b>Set</b><br><br>
  335. <ul>
  336. <code>set &lt;name&gt; scene &lt;id&gt;</code>
  337. <br><br>
  338. Runs the scene identified by <code>&lt;id&gt;</code> which can be either the numeric id of the scene or the scene's name.
  339. <br><br>
  340. Examples:
  341. <ul>
  342. <code>set velux scene 1</code><br>
  343. <code>set velux scene "all shutters down"</code><br>
  344. </ul>
  345. <br>
  346. Scene names with blanks must be enclosed in double quotes.
  347. <br><br>
  348. </ul>
  349. <a name="IOhomecontrolget"></a>
  350. <b>Get</b><br><br>
  351. <ul>
  352. <code>get &lt;name&gt; scenes</code>
  353. <br><br>
  354. Retrieves the ids and names of the scenes from the device.
  355. <br><br>
  356. Example:
  357. <ul>
  358. <code>get velux scenes</code><br>
  359. </ul>
  360. </ul>
  361. <br><br>
  362. <a name="IOhomecontrolattr"></a>
  363. <b>Attributes</b>
  364. <ul>
  365. <li>setCmds: a comma-separated list of set command definitions.
  366. Every definition is of the form <code>&lt;shorthand&gt;=&lt;command&gt;</code>. This defines a new single-word command <code>&lt;shorthand&gt</code> as a substitute for <code>&lt;command&gt;</code>.<br>
  367. Example: <code>attr velux setCmds up=scene "3.dz.roll2 100%",down=scene "3.dz.roll2 0%"</code><br>
  368. Substituted commands (and only these) are shown in the state reading. This is useful in conjunction with the <code>devStateIcon</code> attribute, e.g. <code>attr velux devStateIcon down:shutter_closed up:shutter_open</code>.</li>
  369. <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
  370. </ul>
  371. <br><br>
  372. </ul>
  373. =end html
  374. =cut