89_FULLY.pm 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559
  1. ##############################################################################
  2. #
  3. # 89_FULLY.pm 0.5
  4. #
  5. # $Id: 89_FULLY.pm 15428 2017-11-13 12:46:53Z zap $
  6. #
  7. # Control Fully browser on Android tablets from FHEM.
  8. # Requires Fully Plus license!
  9. #
  10. ##############################################################################
  11. package main;
  12. use strict;
  13. use warnings;
  14. use Blocking;
  15. use SetExtensions;
  16. # Declare functions
  17. sub FULLY_Initialize ($);
  18. sub FULLY_Define ($$);
  19. sub FULLY_Undef ($$);
  20. sub FULLY_Shutdown ($);
  21. sub FULLY_Set ($@);
  22. sub FULLY_Get ($@);
  23. sub FULLY_Attr ($@);
  24. sub FULLY_UpdateDeviceInfo ($);
  25. sub FULLY_Execute ($$$);
  26. sub FULLY_GetDeviceInfo ($);
  27. sub FULLY_ProcessDeviceInfo ($$);
  28. sub FULLY_GotDeviceInfo ($);
  29. sub FULLY_Abort ($);
  30. sub FULLY_UpdateReadings ($$);
  31. my $FULLY_VERSION = "0.5";
  32. my $FULLY_TIMEOUT = 4;
  33. my $FULLY_POLL_INTERVAL = 3600;
  34. ##################################################
  35. # Initialize module
  36. ##################################################
  37. sub FULLY_Initialize ($)
  38. {
  39. my ($hash) = @_;
  40. $hash->{DefFn} = "FULLY_Define";
  41. $hash->{UndefFn} = "FULLY_Undef";
  42. $hash->{SetFn} = "FULLY_Set";
  43. $hash->{GetFn} = "FULLY_Get";
  44. $hash->{AttrFn} = "FULLY_Attr";
  45. $hash->{ShutdownFn} = "FULLY_Shutdown";
  46. $hash->{parseParams} = 1;
  47. $hash->{AttrList} = "pollInterval requestTimeout " . $readingFnAttributes;
  48. }
  49. ##################################################
  50. # Define device
  51. ##################################################
  52. sub FULLY_Define ($$)
  53. {
  54. my ($hash, $a, $h) = @_;
  55. my $name = $hash->{NAME};
  56. my $rc = 0;
  57. return "Usage: define devname FULLY IP_or_Hostname password [poll-interval]"
  58. if (@$a < 4);
  59. return "FULLY: polling interval must be in range 10 - 86400"
  60. if (@$a == 5 && ($$a[4] !~ /^[1-9][0-9]+$/ || $$a[4] < 10 || $$a[4] > 86400));
  61. $hash->{host} = $$a[2];
  62. $hash->{version} = $FULLY_VERSION;
  63. $hash->{fully}{password} = $$a[3];
  64. $hash->{fully}{schedule} = 0;
  65. Log3 $name, 1, "FULLY: Opening device ".$hash->{host};
  66. my $result = FULLY_GetDeviceInfo ($name);
  67. if (!FULLY_UpdateReadings ($hash, $result)) {
  68. Log3 $name, 2, "FULLY: Update of device info failed";
  69. }
  70. if (@$a == 5) {
  71. $attr{$name}{'pollInterval'} = $$a[4];
  72. $hash->{nextUpdate} = strftime "%d.%m.%Y %H:%M:%S", localtime (time+$$a[4]);
  73. InternalTimer (gettimeofday()+$$a[4], "FULLY_UpdateDeviceInfo", $hash, 0);
  74. }
  75. else {
  76. $hash->{nextUpdate} = 'off';
  77. }
  78. return undef;
  79. }
  80. #####################################
  81. # Set or delete attribute
  82. #####################################
  83. sub FULLY_Attr ($@)
  84. {
  85. my ($cmd, $name, $attrname, $attrval) = @_;
  86. my $hash = $defs{$name};
  87. if ($cmd eq 'set') {
  88. if ($attrname eq 'pollInterval') {
  89. if ($attrval >= 10 && $attrval <= 86400) {
  90. my $curval = AttrVal ($name, 'pollInterval', $FULLY_POLL_INTERVAL);
  91. if ($attrval != $curval) {
  92. Log3 $name, 2, "FULLY: Polling interval set to $attrval";
  93. RemoveInternalTimer ($hash);
  94. $hash->{nextUpdate} = strftime "%d.%m.%Y %H:%M:%S", localtime (time+$attrval);
  95. InternalTimer (gettimeofday()+$attrval, "FULLY_UpdateDeviceInfo", $hash, 0);
  96. }
  97. }
  98. elsif ($attrval == 0) {
  99. RemoveInternalTimer ($hash);
  100. $hash->{nextUpdate} = 'off';
  101. }
  102. else {
  103. return "FULLY: Polling interval must be in range 10-86400";
  104. }
  105. }
  106. elsif ($attrname eq 'requestTimeout') {
  107. return "FULLY: Timeout must be greater than 0" if ($attrval < 1);
  108. }
  109. }
  110. elsif ($cmd eq 'del') {
  111. if ($attrname eq 'pollInterval') {
  112. RemoveInternalTimer ($hash);
  113. $hash->{nextUpdate} = 'off';
  114. }
  115. }
  116. return undef;
  117. }
  118. #####################################
  119. # Delete device
  120. #####################################
  121. sub FULLY_Undef ($$)
  122. {
  123. my ($hash, $arg) = @_;
  124. RemoveInternalTimer ($hash);
  125. BlockingKill ($hash->{fully}{bc}) if (defined ($hash->{fully}{bc}));
  126. return undef;
  127. }
  128. #####################################
  129. # Shutdown FHEM
  130. #####################################
  131. sub FULLY_Shutdown ($)
  132. {
  133. my ($hash) = @_;
  134. RemoveInternalTimer ($hash);
  135. BlockingKill ($hash->{fully}{bc}) if (defined ($hash->{fully}{bc}));
  136. return undef;
  137. }
  138. #####################################
  139. # Set commands
  140. #####################################
  141. sub FULLY_Set ($@)
  142. {
  143. my ($hash, $a, $h) = @_;
  144. my $name = shift @$a;
  145. my $opt = shift @$a;
  146. my $options = "brightness clearCache:noArg exit:noArg lock:noArg motionDetection:on,off ".
  147. "off:noArg on:noArg restart:noArg unlock:noArg speak url";
  148. my $response;
  149. # Fully commands without argument
  150. my %cmds = (
  151. "clearCache" => "clearCache",
  152. "exit" => "exitApp", "restart" => "restartApp",
  153. "on" => "screenOn", "off" => "screenOff",
  154. "lock" => "enabledLockedMode", "unlock" => "disableLockedMode"
  155. );
  156. if (exists ($cmds{$opt})) {
  157. $response = FULLY_Execute ($hash, $cmds{$opt}, undef);
  158. }
  159. elsif ($opt eq 'brightness') {
  160. my $value = shift @$a;
  161. return "Usage: set $name brightness 0-255" if (!defined ($value));
  162. $value = 255 if ($value > 255);
  163. $response = FULLY_Execute ($hash, "setStringSetting",
  164. { "key" => "screenBrightness", "value" => "$value" });
  165. }
  166. elsif ($opt eq 'motionDetection') {
  167. my $state = shift @$a;
  168. return "Usage: set $name motionDetection {on|off}" if (!defined ($state));
  169. my $value = $state eq 'on' ? 'true' : 'false';
  170. $response = FULLY_Execute ($hash, "setBooleanSetting",
  171. { "key" => "motionDetection", "value" => "$value" });
  172. }
  173. elsif ($opt eq 'speak') {
  174. my $text = shift @$a;
  175. return "Usage: set $name speak {Text}" if (!defined ($text));
  176. while ($text =~ /\[(.+):(.+)\]/) {
  177. my ($device, $reading) = ($1, $2);
  178. my $value = ReadingsVal ($device, $reading, '');
  179. $text =~ s/\[$device:$reading\]/$value/g;
  180. }
  181. my $enctext = urlEncode ($text);
  182. $response = FULLY_Execute ($hash, "textToSpeech", { "text" => "$enctext" });
  183. }
  184. elsif ($opt eq 'url') {
  185. my $url = shift @$a;
  186. my $cmd = defined ($url) ? "loadURL" : "loadStartURL";
  187. $response = FULLY_Execute ($hash, $cmd, { "url" => "$url" });
  188. }
  189. else {
  190. return "FULLY: Unknown argument $opt, choose one of ".$options;
  191. }
  192. my $result = FULLY_ProcessDeviceInfo ($name, $response);
  193. if (!FULLY_UpdateReadings ($hash, $result)) {
  194. Log3 $name, 2, "FULLY: Command failed";
  195. return "FULLY: Command failed";
  196. }
  197. return undef;
  198. }
  199. #####################################
  200. # Get commands
  201. #####################################
  202. sub FULLY_Get ($@)
  203. {
  204. my ($hash, $a, $h) = @_;
  205. my $name = shift @$a;
  206. my $opt = shift @$a;
  207. my $options = "info:noArg stats:noArg update:noArg";
  208. my $response;
  209. if ($opt eq 'info') {
  210. my $result = FULLY_Execute ($hash, 'deviceInfo', undef);
  211. if (!defined ($result) || $result eq '') {
  212. Log3 $name, 2, "FULLY: Command failed";
  213. return "FULLY: Command failed";
  214. }
  215. elsif ($response =~ /Wrong password/) {
  216. Log3 $name, 2, "FULLY: Wrong password";
  217. return "FULLY: Wrong password";
  218. }
  219. $response = '';
  220. while ($result =~ /table-cell\">([^<]+)<\/td><td class="table-cell">([^<]+)</g) {
  221. $response .= "$1 = $2<br/>\n";
  222. }
  223. return $response;
  224. }
  225. elsif ($opt eq 'stats') {
  226. return "FULLY: Command not implemented";
  227. }
  228. elsif ($opt eq 'update') {
  229. my $result = FULLY_GetDeviceInfo ($name);
  230. if (!FULLY_UpdateReadings ($hash, $result)) {
  231. Log3 $name, 2, "FULLY: Command failed";
  232. return "FULLY: Command failed";
  233. }
  234. }
  235. else {
  236. return "FULLY: Unknown argument $opt, choose one of ".$options;
  237. }
  238. return undef;
  239. }
  240. #####################################
  241. # Execute Fully command
  242. #####################################
  243. sub FULLY_Execute ($$$)
  244. {
  245. my ($hash, $command, $param) = @_;
  246. my $name = $hash->{NAME};
  247. my $timeout = AttrVal ($name, 'requestTimeout', $FULLY_TIMEOUT);
  248. my $url = "http://".$hash->{host}.":2323/?cmd=$command";
  249. if (defined ($param)) {
  250. foreach my $parname (keys %$param) {
  251. if (defined ($param->{$parname})) {
  252. $url .= "&$parname=".$param->{$parname};
  253. }
  254. }
  255. }
  256. return GetFileFromURL ("$url&password=".$hash->{fully}{password}, $timeout);
  257. }
  258. #####################################
  259. # Timer function: Read device info
  260. #####################################
  261. sub FULLY_UpdateDeviceInfo ($)
  262. {
  263. my ($hash) = @_;
  264. my $name = $hash->{NAME};
  265. if (!exists ($hash->{fully}{bc})) {
  266. $hash->{fully}{bc} = BlockingCall ("FULLY_GetDeviceInfo", $name, "FULLY_GotDeviceInfo",
  267. 120, "FULLY_Abort", $hash);
  268. }
  269. }
  270. #####################################
  271. # Get tablet device information
  272. #####################################
  273. sub FULLY_GetDeviceInfo ($)
  274. {
  275. my ($name) = @_;
  276. my $hash = $defs{$name};
  277. my $result = FULLY_Execute ($hash, 'deviceInfo', undef);
  278. return FULLY_ProcessDeviceInfo ($name, $result);
  279. }
  280. #####################################
  281. # Extract parameters from HTML code
  282. #####################################
  283. sub FULLY_ProcessDeviceInfo ($$)
  284. {
  285. my ($name, $result) = @_;
  286. return "$name|0|state=failed" if (!defined ($result) || $result eq '');
  287. return "$name|0|state=wrong password" if ($result =~ /Wrong password/);
  288. my $parameters = "$name|1";
  289. while ($result =~ /table-cell\">([^<]+)<\/td><td class="table-cell">([^<]+)</g) {
  290. my $rn = lc($1);
  291. my $rv = $2;
  292. $rn =~ s/\:/\./g;
  293. $rn =~ s/[^A-Za-z\d_\.-]+/_/g;
  294. $rn =~ s/[_]+$//;
  295. if ($rn eq 'battery_level') {
  296. if ($rv =~ /^([0-9]+)% \(([^\)]+)\)$/) {
  297. $parameters .= "|$rn=$1|power=$2";
  298. next;
  299. }
  300. }
  301. elsif ($rn eq 'screen_brightness') {
  302. $rn = "brightness";
  303. }
  304. elsif ($rn eq 'screen_status') {
  305. $parameters .= "|state=$rv";
  306. }
  307. $parameters .= "|$rn=$rv";
  308. }
  309. return $parameters;
  310. }
  311. #####################################
  312. # Success function for blocking call
  313. #####################################
  314. sub FULLY_GotDeviceInfo ($)
  315. {
  316. my ($string) = @_;
  317. my ($name, $result) = split ('\|', $string, 2);
  318. my $hash = $defs{$name};
  319. my $pollInterval = AttrVal ($name, 'pollInterval', $FULLY_POLL_INTERVAL);
  320. my $timeout = AttrVal ($name, 'requestTimeout', $FULLY_TIMEOUT);
  321. delete $hash->{fully}{bc} if (exists ($hash->{fully}{bc}));
  322. my $rc = FULLY_UpdateReadings ($hash, $string);
  323. if (!$rc) {
  324. Log3 $name, 2, "FULLY: Request timed out";
  325. if ($hash->{fully}{schedule} == 0) {
  326. $hash->{fully}{schedule} += 1;
  327. Log3 $name, 2, "FULLY: Rescheduling in $timeout seconds.";
  328. $pollInterval = $timeout;
  329. }
  330. else {
  331. $hash->{fully}{schedule} = 0;
  332. }
  333. }
  334. $hash->{nextUpdate} = strftime "%d.%m.%Y %H:%M:%S", localtime (time+$pollInterval);
  335. InternalTimer (gettimeofday()+$pollInterval, "FULLY_UpdateDeviceInfo", $hash, 0)
  336. if ($pollInterval > 0);
  337. }
  338. #####################################
  339. # Abort function for blocking call
  340. #####################################
  341. sub FULLY_Abort ($)
  342. {
  343. my ($hash) = @_;
  344. my $name = $hash->{NAME};
  345. my $pollInterval = AttrVal ($name, 'pollInterval', $FULLY_POLL_INTERVAL);
  346. my $timeout = AttrVal ($name, 'requestTimeout', $FULLY_TIMEOUT);
  347. delete $hash->{fully}{bc} if (exists ($hash->{fully}{bc}));
  348. Log3 $name, 2, "FULLY: request timed out";
  349. if ($hash->{fully}{schedule} == 0) {
  350. $hash->{fully}{schedule} += 1;
  351. Log3 $name, 2, "FULLY: Rescheduling in $timeout seconds.";
  352. $pollInterval = $timeout;
  353. }
  354. else {
  355. $hash->{fully}{schedule} = 0;
  356. }
  357. $hash->{nextUpdate} = strftime "%d.%m.%Y %H:%M:%S", localtime (time+$pollInterval);
  358. InternalTimer (gettimeofday()+$pollInterval, "FULLY_UpdateDeviceInfo", $hash, 0)
  359. if ($pollInterval > 0);
  360. }
  361. #####################################
  362. # Update readings
  363. #####################################
  364. sub FULLY_UpdateReadings ($$)
  365. {
  366. my ($hash, $result) = @_;
  367. my $name = $hash->{NAME};
  368. my $rc = 1;
  369. if (!defined ($result) || $result eq '') {
  370. Log3 $name, 2, "FULLY: empty response";
  371. return 0;
  372. }
  373. my @parameters = split ('\|', $result);
  374. if (scalar (@parameters) == 0) {
  375. Log3 $name, 2, "FULLY: empty response";
  376. return 0;
  377. }
  378. if ($parameters[0] eq $name) {
  379. my $n = shift @parameters;
  380. $rc = shift @parameters;
  381. }
  382. readingsBeginUpdate ($hash);
  383. foreach my $parval (@parameters) {
  384. my ($rn, $rv) = split ('=', $parval);
  385. readingsBulkUpdate ($hash, $rn, $rv);
  386. }
  387. readingsEndUpdate ($hash, 1);
  388. return $rc;
  389. }
  390. 1;
  391. =pod
  392. =item device
  393. =item summary FULLY Browser Integration
  394. =begin html
  395. <a name="FULLY"></a>
  396. <h3>FULLY</h3>
  397. <ul>
  398. Module for controlling of Fully browser on Android tablets.
  399. </br></br>
  400. <a name="HMCCUdefine"></a>
  401. <b>Define</b><br/><br/>
  402. <ul>
  403. <code>define &lt;name&gt; FULLY &lt;HostOrIP&gt; &lt;password&gt; [&lt;poll-interval&gt;]</code>
  404. <br/><br/>
  405. The parameter <i>password</i> is the password set in Fully browser.
  406. </ul>
  407. <br/>
  408. <a name="FULLYset"></a>
  409. <b>Set</b><br/><br/>
  410. <ul>
  411. <li><b>set &lt;name&gt; brightness 0-255</b><br/>
  412. Adjust screen brightness.
  413. </li><br/>
  414. <li><b>set &lt;name&gt; clearCache</b><br/>
  415. Clear browser cache.
  416. </li><br/>
  417. <li><b>set &lt;name&gt; exit</b><br/>
  418. Terminate Fully.
  419. </li><br/>
  420. <li><b>set &lt;name&gt; motionDetection { on | off }</b><br/>
  421. Turn motion detection by camera on or off.
  422. </li><br/>
  423. <li><b>set &lt;name&gt; { lock | unlock }</b><br/>
  424. Lock or unlock display.
  425. </li><br/>
  426. <li><b>set &lt;name&gt; { on | off }</b><br/>
  427. Turn tablet display on or off.
  428. </li><br/>
  429. <li><b>set &lt;name&gt; restart</b><br/>
  430. Restart Fully.
  431. </li><br/>
  432. <li><b>set &lt;name&gt; speak &lt;text&gt;</b><br/>
  433. Audio output of <i>text</i>. If <i>text</i> contains blanks it must be enclosed
  434. in double quotes. The text can contain device readings in format [device:reading].
  435. </li><br/>
  436. <li><b>set &lt;name&gt; url [&lt;URL&gt;]</b><br/>
  437. Navigate to <i>URL</i>. If no URL is specified navigate to start URL.
  438. </li><br/>
  439. </ul>
  440. <br/>
  441. <a name="FULLYget"></a>
  442. <b>Get</b><br/><br/>
  443. <ul>
  444. <li><b>get &lt;name&gt; info</b><br/>
  445. Display Fully information.
  446. </li><br/>
  447. <li><b>get &lt;name&gt; stats</b><br/>
  448. Show Fully statistics.
  449. </li><br/>
  450. <li><b>get &lt;name&gt; update</b><br/>
  451. Update readings.
  452. </li><br/>
  453. </ul>
  454. <br/>
  455. <a name="FULLYattr"></a>
  456. <b>Attributes</b><br/>
  457. <br/>
  458. <ul>
  459. <li><b>pollInterval &lt;seconds&gt;</b><br/>
  460. Set polling interval for FULLY device information.
  461. If <i>seconds</i> is 0 polling is turned off. Valid values are from 10 to
  462. 86400 seconds.
  463. </li><br/>
  464. <li><b>requestTimeout &lt;seconds&gt;</b><br/>
  465. Set timeout for http requests. Default is 4 seconds.
  466. </li><br/>
  467. </ul>
  468. </ul>
  469. =end html
  470. =cut