FULLY: New commands

git-svn-id: https://svn.fhem.de/fhem/trunk@22983 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
zap
2020-10-17 14:55:40 +00:00
parent 527b19a43a
commit 54553fc0d5
2 changed files with 347 additions and 203 deletions

View File

@@ -1,5 +1,6 @@
# Add changes at the top of the list. Keep it in ASCII, and 80-char wide. # Add changes at the top of the list. Keep it in ASCII, and 80-char wide.
# Do not insert empty lines here, update check depends on it. # Do not insert empty lines here, update check depends on it.
- feature: 89_FULLY: Several new commands. Now using JSON.
- change: 59_Weather: add forcast attributs to english commandref - change: 59_Weather: add forcast attributs to english commandref
- bugfix: TimeSeries: fix for calculation of standard deviation - bugfix: TimeSeries: fix for calculation of standard deviation
- feature: 76_SMAPortal: new relative time arguments for attr balanceDay, - feature: 76_SMAPortal: new relative time arguments for attr balanceDay,

View File

@@ -1,6 +1,6 @@
############################################################################## ##############################################################################
# #
# 89_FULLY.pm 1.41 # 89_FULLY.pm 2.0
# #
# $Id$ # $Id$
# #
@@ -16,6 +16,7 @@ package main;
use strict; use strict;
use warnings; use warnings;
use HttpUtils; use HttpUtils;
use JSON;
use SetExtensions; use SetExtensions;
# Declare functions # Declare functions
@@ -33,11 +34,13 @@ sub FULLY_ExecuteNB ($$$$);
sub FULLY_ExecuteCB ($$$); sub FULLY_ExecuteCB ($$$);
sub FULLY_ScreenOff ($); sub FULLY_ScreenOff ($);
sub FULLY_GetDeviceInfo ($); sub FULLY_GetDeviceInfo ($);
sub FULLY_ProcessDeviceInfo ($$);
sub FULLY_UpdateReadings ($$); sub FULLY_UpdateReadings ($$);
sub FULLY_Encrypt ($);
sub FULLY_Decrypt ($);
sub FULLY_Ping ($$); sub FULLY_Ping ($$);
sub FULLY_SetPolling ($$;$);
my $FULLY_VERSION = '1.41'; my $FULLY_VERSION = '2.0';
# Timeout for Fully requests # Timeout for Fully requests
my $FULLY_TIMEOUT = 5; my $FULLY_TIMEOUT = 5;
@@ -47,7 +50,7 @@ my $FULLY_POLL_INTERVAL = 3600;
my @FULLY_POLL_RANGE = (10, 86400); my @FULLY_POLL_RANGE = (10, 86400);
# Minimum version of Fully app # Minimum version of Fully app
my $FULLY_REQUIRED_VERSION = 1.27; my $FULLY_REQUIRED_VERSION = 1.42;
# Default protocol and port for Fully requests # Default protocol and port for Fully requests
my $FULLY_DEFAULT_PROT = 'http'; my $FULLY_DEFAULT_PROT = 'http';
@@ -85,8 +88,8 @@ sub FULLY_Initialize ($)
$hash->{FW_detailFn} = "FULLY_Detail"; $hash->{FW_detailFn} = "FULLY_Detail";
$hash->{parseParams} = 1; $hash->{parseParams} = 1;
$hash->{AttrList} = "pingBeforeCmd:0,1,2 pollInterval requestTimeout repeatCommand:0,1,2 " . $hash->{AttrList} = "pingBeforeCmd:0,1,2 pollInterval:slider,10,10,86400 requestTimeout:slider,1,1,20 repeatCommand:0,1,2 " .
"disable:0,1 expert:0,1 waitAfterPing:0,1,2" . "disable:0,1 expert:0,1 waitAfterPing:0,1,2 updateAfterCommand:0,1 " .
$readingFnAttributes; $readingFnAttributes;
} }
@@ -98,14 +101,10 @@ sub FULLY_Define ($$)
{ {
my ($hash, $a, $h) = @_; my ($hash, $a, $h) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
my $rc = 0;
my $host = ''; my $host = '';
return "Usage: define devname [http|https]://IP_or_Hostname password [poll-interval]" return "Usage: define devname FULLY [http|https]://IP_or_Hostname [password] [poll-interval]"
if (@$a < 4); if (@$a < 3);
return "FULLY: polling interval must be in range ".$FULLY_POLL_RANGE[0]."-".$FULLY_POLL_RANGE[1]
if (@$a == 5 &&
($$a[4] !~ /^[1-9][0-9]+$/ || $$a[4] < $FULLY_POLL_RANGE[0] || $$a[4] > $FULLY_POLL_RANGE[1]));
if ($$a[2] =~ /^(https?):\/\/(.+)/) { if ($$a[2] =~ /^(https?):\/\/(.+)/) {
$hash->{prot} = $1; $hash->{prot} = $1;
@@ -127,22 +126,37 @@ sub FULLY_Define ($$)
$hash->{version} = $FULLY_VERSION; $hash->{version} = $FULLY_VERSION;
$hash->{onForTimer} = 'off'; $hash->{onForTimer} = 'off';
$hash->{fully}{password} = $$a[3];
$hash->{fully}{schedule} = 0; $hash->{fully}{schedule} = 0;
Log3 $name, 1, "FULLY: [$name] Version $FULLY_VERSION Opening device ".$hash->{host};
FULLY_GetDeviceInfo ($name); my $interval = 0;
if (@$a == 4) {
if (@$a == 5) { if ($$a[3] =~ /^[0-9]+$/) {
$attr{$name}{'pollInterval'} = $$a[4]; $interval = $$a[3];
$hash->{nextUpdate} = strftime "%d.%m.%Y %H:%M:%S", localtime (time+$$a[4]); }
InternalTimer (gettimeofday()+$$a[4], "FULLY_UpdateDeviceInfo", $hash, 0); else {
$hash->{fully}{password} = $$a[3];
}
} }
else { elsif (@$a == 5) {
$hash->{nextUpdate} = 'off'; $hash->{fully}{password} = $$a[3];
$interval = $$a[4];
} }
if (!exists($hash->{fully}{password})) {
my ($errpass, $encpass) = getKeyValue ($name.'_password');
$hash->{fully}{password} = FULLY_Decrypt ($encpass) if (!defined($errpass) && defined($encpass));
}
if (!$init_done || exists($hash->{fully}{password})) {
FULLY_Log ($hash, 1, "Version $FULLY_VERSION Opening device ".$hash->{host});
FULLY_GetDeviceInfo ($name);
FULLY_SetPolling ($hash, 1, $interval);
}
if ($init_done && !exists($hash->{fully}{password}) && exists($hash->{CL})) {
asyncOutput ($hash->{CL}, "Please use command 'set $name authentication' to set the Fully password");
}
return undef; return undef;
} }
@@ -158,17 +172,10 @@ sub FULLY_Attr ($@)
if ($cmd eq 'set') { if ($cmd eq 'set') {
if ($attrname eq 'pollInterval') { if ($attrname eq 'pollInterval') {
if ($attrval >= $FULLY_POLL_RANGE[0] && $attrval <= $FULLY_POLL_RANGE[1]) { if ($attrval >= $FULLY_POLL_RANGE[0] && $attrval <= $FULLY_POLL_RANGE[1]) {
my $curval = AttrVal ($name, 'pollInterval', $FULLY_POLL_INTERVAL); FULLY_SetPolling ($hash, 1, $attrval);
if ($attrval != $curval) {
Log3 $name, 2, "FULLY: [$name] Polling interval set to $attrval";
RemoveInternalTimer ($hash);
$hash->{nextUpdate} = strftime "%d.%m.%Y %H:%M:%S", localtime (time+$attrval);
InternalTimer (gettimeofday()+$attrval, "FULLY_UpdateDeviceInfo", $hash, 0);
}
} }
elsif ($attrval == 0) { elsif ($attrval == 0) {
RemoveInternalTimer ($hash); FULLY_SetPolling ($hash, 0);
$hash->{nextUpdate} = 'off';
} }
else { else {
return "FULLY: Polling interval must be in range ".$FULLY_POLL_RANGE[0]."-".$FULLY_POLL_RANGE[1]; return "FULLY: Polling interval must be in range ".$FULLY_POLL_RANGE[0]."-".$FULLY_POLL_RANGE[1];
@@ -177,17 +184,58 @@ sub FULLY_Attr ($@)
elsif ($attrname eq 'requestTimeout') { elsif ($attrname eq 'requestTimeout') {
return "FULLY: Timeout must be greater than 0" if ($attrval < 1); return "FULLY: Timeout must be greater than 0" if ($attrval < 1);
} }
elsif ($attrname eq 'disable') {
if ($attrval eq '0') {
# Set the polling interval to default or the value specified in pollInterval
FULLY_Log ($hash, 2, "Device activated");
FULLY_SetPolling ($hash, 1);
}
elsif ($attrval eq '1') {
FULLY_SetPolling ($hash, 0);
FULLY_Log ($hash, 2, "Device deactivated");
}
}
} }
elsif ($cmd eq 'del') { elsif ($cmd eq 'del') {
if ($attrname eq 'pollInterval') { if ($attrname eq 'pollInterval') {
RemoveInternalTimer ($hash); # Set the polling interval to default
$hash->{nextUpdate} = 'off'; FULLY_SetPolling ($hash, 1);
}
elsif ($attrname eq 'disable') {
# Set the polling interval to default or the value specified in pollInterval
FULLY_Log ($hash, 2, "Device activated");
FULLY_SetPolling ($hash, 1);
} }
} }
return undef; return undef;
} }
######################################################################
# Set polling on or off
######################################################################
sub FULLY_SetPolling ($$;$)
{
my ($hash, $mode, $interval) = @_;
my $name = $hash->{NAME};
$interval //= AttrVal ($name, 'pollInterval', $FULLY_POLL_INTERVAL);
if ($mode == 0 || $interval == 0) {
RemoveInternalTimer ($hash, 'FULLY_UpdateDeviceInfo');
$hash->{nextUpdate} = 'off';
FULLY_Log ($hash, 2, "Polling deactivated");
}
elsif ($mode == 1) {
RemoveInternalTimer ($hash, 'FULLY_UpdateDeviceInfo');
$interval = $FULLY_POLL_RANGE[0] if ($interval < $FULLY_POLL_RANGE[0]);
$interval = $FULLY_POLL_RANGE[1] if ($interval > $FULLY_POLL_RANGE[1]);
$hash->{nextUpdate} = strftime "%d.%m.%Y %H:%M:%S", localtime (time+$interval);
InternalTimer (gettimeofday()+$interval, "FULLY_UpdateDeviceInfo", $hash, 0);
FULLY_Log ($hash, 2, "Polling activated");
}
}
###################################################################### ######################################################################
# Delete device # Delete device
###################################################################### ######################################################################
@@ -246,10 +294,11 @@ sub FULLY_Set ($@)
my ($hash, $a, $h) = @_; my ($hash, $a, $h) = @_;
my $name = shift @$a; my $name = shift @$a;
my $opt = shift @$a; my $opt = shift @$a;
my $options = "brightness photo:noArg clearCache:noArg exit:noArg foreground:noArg lock:noArg ". my $options = "brightness:slider,0,1,255 photo:noArg clearCache:noArg exit:noArg foreground:noArg lock:noArg ".
"motionDetection:on,off off:noArg on:noArg on-for-timer playSound restart:noArg screenOffTimer ". "motionDetection:on,off off:noArg on:noArg on-for-timer playSound playVideo restart:noArg ".
"screenSaver:start,stop screenSaverTimer screenSaverURL speak startURL stopSound:noArg ". "screenOffTimer screenSaver:start,stop screenSaverTimer screenSaverURL speak startURL ".
"unlock:noArg url volume"; "stopSound:noArg stopVideo:noArg lockKiosk:noArg unlockKiosk:noArg unlock:noArg url ".
"volume overlayMessage authentication";
# Fully commands without argument # Fully commands without argument
my %cmds = ( my %cmds = (
@@ -259,21 +308,43 @@ sub FULLY_Set ($@)
"restart" => "restartApp", "restart" => "restartApp",
"on" => "screenOn", "off" => "screenOff", "on" => "screenOn", "off" => "screenOff",
"lock" => "enabledLockedMode", "unlock" => "disableLockedMode", "lock" => "enabledLockedMode", "unlock" => "disableLockedMode",
"lockKiosk" => "lockKiosk", "unlockKiosk" => "unlockKiosk",
"stopSound" => "stopSound", "stopSound" => "stopSound",
"stopVideo" => "stopVideo",
"foreground" => "toForeground" "foreground" => "toForeground"
); );
my @c = (); my @c = ();
my @p = (); my @p = ();
my $disable = AttrVal ($name, 'disable', 0); return "Device disabled" if (AttrVal ($name, 'disable', 0) == 1);
return undef if ($disable); return "No password defined for Fully access" if (!exists($hash->{fully}{password}));
my $expert = AttrVal ($name, 'expert', 0); my $expert = AttrVal ($name, 'expert', 0);
$options .= " setStringSetting setBooleanSetting" if ($expert); $options .= " setStringSetting setBooleanSetting" if ($expert);
my $updateAfterCommand = AttrVal ($name, 'updateAfterCommand', 0);
if (exists ($cmds{$opt})) { if (exists ($cmds{$opt})) {
push (@c, $cmds{$opt}); push (@c, $cmds{$opt});
} }
elsif ($opt eq 'authentication') {
my $password = shift @$a;
if (!defined($password)) {
setKeyValue ($name."_password", undef);
return 'Password for FULLY authentication deleted';
}
my $encpass = FULLY_Encrypt ($password);
return 'Encryption of password failed' if ($encpass eq '');
my $err = setKeyValue ($name."_password", $encpass);
return "Can't store credentials. $err" if (defined($err));
$hash->{fully}{password} = $password;
return 'Password for FULLY authentication stored';
}
elsif ($opt eq 'on-for-timer') { elsif ($opt eq 'on-for-timer') {
my $par = shift @$a // "forever"; my $par = shift @$a // "forever";
@@ -338,15 +409,16 @@ sub FULLY_Set ($@)
} }
elsif ($opt eq 'speak') { elsif ($opt eq 'speak') {
my $text = shift @$a // return 'Usage: set $name speak "{Text}"'; my $text = shift @$a // return 'Usage: set $name speak "{Text}"';
while ($text =~ /\[(.+):(.+)\]/) { my $enctext = FULLY_SubstDeviceReading ($text);
my ($device, $reading) = ($1, $2);
my $value = ReadingsVal ($device, $reading, '');
$text =~ s/\[$device:$reading\]/$value/g;
}
my $enctext = urlEncode ($text);
push (@c, "textToSpeech"); push (@c, "textToSpeech");
push (@p, { "text" => "$enctext" }); push (@p, { "text" => "$enctext" });
} }
elsif ($opt eq 'overlayMessage') {
my $text = shift @$a // return 'Usage: set $name overlayMessage "{Text}"';
my $enctext = FULLY_SubstDeviceReading ($text);
push (@c, "setOverlayMessage");
push (@p, { "text" => "$enctext" });
}
elsif ($opt eq 'playSound') { elsif ($opt eq 'playSound') {
my $url = shift @$a // return "Usage: set $name playSound {url} [loop]"; my $url = shift @$a // return "Usage: set $name playSound {url} [loop]";
my $loop = shift @$a; my $loop = shift @$a;
@@ -354,6 +426,17 @@ sub FULLY_Set ($@)
push (@c, "playSound"); push (@c, "playSound");
push (@p, { "url" => "$url", "loop" => "$loop"}); push (@p, { "url" => "$url", "loop" => "$loop"});
} }
elsif ($opt eq 'playVideo') {
my $url = shift @$a // return "Usage: set $name playVideo {url} [showControls] [exitOnTouch] [exitOnCompletion] [loop]";
my %pvo = ('loop' => 0, 'showControls' => 0, 'exitOnTouch' => 0, 'exitOnCompletion' => 0);
while (my $pvf = shift @$a) {
return "Illegal option $pvf" if (!exists($pvo{$pvf}));
$pvo{$pvf} = 1;
}
$pvo{'url'} = $url;
push (@c, "playVideo");
push (@p, \%pvo);
}
elsif ($opt eq 'volume') { elsif ($opt eq 'volume') {
my $level = shift @$a; my $level = shift @$a;
my $stream = shift @$a; my $stream = shift @$a;
@@ -385,6 +468,10 @@ sub FULLY_Set ($@)
} }
# Execute command requests # Execute command requests
if ($updateAfterCommand) {
push (@c, 'deviceInfo');
push (@p, undef);
}
FULLY_ExecuteNB ($hash, \@c, \@p, 1) if (scalar (@c) > 0); FULLY_ExecuteNB ($hash, \@c, \@p, 1) if (scalar (@c) > 0);
return undef; return undef;
@@ -400,39 +487,19 @@ sub FULLY_Get ($@)
my $name = shift @$a; my $name = shift @$a;
my $opt = shift @$a; my $opt = shift @$a;
my $options = "info:noArg stats:noArg update:noArg"; my $options = "info:noArg stats:noArg update:noArg";
my $response;
my $disable = AttrVal ($name, 'disable', 0); return "Device disabled" if (AttrVal ($name, 'disable', 0) == 1);
return undef if ($disable); return "No password defined for Fully access" if (!exists($hash->{fully}{password}));
if ($opt eq 'info') { if ($opt eq 'info') {
my $result = FULLY_Execute ($hash, 'deviceInfo', undef, 1); my $result = FULLY_Execute ($hash, 'deviceInfo', undef, 1) //
if (!defined ($result) || $result eq '') { return FULLY_Log ($hash, 2, 'Command deviceInfo failed');
Log3 $name, 2, "FULLY: [$name] Command failed"; return FULLY_Log ($hash, 2, $result->{'statustext'} // $result->{'status'})
return "FULLY: Command failed"; if (exists($result->{'status'}));
} return join ("\n", map { "$_ = $result->{$_}" } sort keys %$result);
elsif ($result =~ /Wrong password/) {
Log3 $name, 2, "FULLY: [$name] Wrong password";
return "FULLY: Wrong password";
}
$response = '';
while ($result =~ /table-cell.>([^<]+)<\/td><td class=.table-cell.>(.*?)<\/td>/g) {
my ($in, $iv) = ($1, $2);
if ($iv =~ /^<a .*?>(.*?)<\/a>/) {
$iv = $1;
}
elsif ($iv =~ /(.*?)</) {
$iv = $1;
}
$iv =~ s/[ ]+$//;
$response .= "$in = $iv<br/>\n";
}
return $response;
} }
elsif ($opt eq 'stats') { elsif ($opt eq 'stats') {
return "FULLY: Command not implemented"; return FULLY_Log ($hash, 2, 'Command stats not implemented');
} }
elsif ($opt eq 'update') { elsif ($opt eq 'update') {
FULLY_GetDeviceInfo ($name); FULLY_GetDeviceInfo ($name);
@@ -445,7 +512,21 @@ sub FULLY_Get ($@)
} }
###################################################################### ######################################################################
# Execute Fully command # Write error message to FHEM log and return specified value
######################################################################
sub FULLY_Log ($$$;$)
{
my ($hash, $level, $message, $retval) = @_;
$retval //= $message;
my $name = $hash->{NAME};
Log3 $name, $level, "FULLY: [$name] $message";
return $retval;
}
######################################################################
# Execute Fully command (blocking)
###################################################################### ######################################################################
sub FULLY_Execute ($$$$) sub FULLY_Execute ($$$$)
@@ -453,6 +534,12 @@ sub FULLY_Execute ($$$$)
my ($hash, $command, $param, $doping) = @_; my ($hash, $command, $param, $doping) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
if (!exists($hash->{fully}{password})) {
asyncOutput ($hash->{CL}, "Please use command 'set $name authentication' to set the Fully password")
if (exists($hash->{CL}));
return undef;
}
# Get attributes # Get attributes
my $timeout = AttrVal ($name, 'requestTimeout', $FULLY_TIMEOUT); my $timeout = AttrVal ($name, 'requestTimeout', $FULLY_TIMEOUT);
my $repeatCommand = minNum (AttrVal ($name, 'repeatCommand', 0), 2); my $repeatCommand = minNum (AttrVal ($name, 'repeatCommand', 0), 2);
@@ -463,7 +550,7 @@ sub FULLY_Execute ($$$$)
if (defined ($param)) { if (defined ($param)) {
foreach my $parname (keys %$param) { foreach my $parname (keys %$param) {
if (defined ($param->{$parname})) { if (defined($param->{$parname})) {
$url .= "&$parname=".$param->{$parname}; $url .= "&$parname=".$param->{$parname};
} }
} }
@@ -474,16 +561,33 @@ sub FULLY_Execute ($$$$)
my $i = 0; my $i = 0;
while ($i <= $repeatCommand && (!defined ($response) || $response eq '')) { while ($i <= $repeatCommand && (!defined ($response) || $response eq '')) {
$response = GetFileFromURL ("$url&password=".$hash->{fully}{password}, $timeout); $response = GetFileFromURL ("$url&password=".$hash->{fully}{password}.'&type=json', $timeout);
Log3 $name, 4, "FULLY: [$name] HTTP response empty" if (defined ($response) && $response eq ''); FULLY_Log ($hash, 4, "HTTP response empty") if (defined($response) && $response eq '');
$i++; $i++;
} }
return $response; return (defined($response) && $response ne '') ? decode_json ($response) : undef;
} }
###################################################################### ######################################################################
# Execute Fully commands non blocking # Substitute device readings in string
######################################################################
sub FULLY_SubstDeviceReading ($)
{
my ($text) = @_;
while ($text =~ /\[(.+):(.+)\]/) {
my ($device, $reading) = ($1, $2);
my $value = ReadingsVal ($device, $reading, '');
$text =~ s/\[$device:$reading\]/$value/g;
}
return (urlEncode ($text));
}
######################################################################
# Execute Fully commands (non blocking)
###################################################################### ######################################################################
sub FULLY_ExecuteNB ($$$$) sub FULLY_ExecuteNB ($$$$)
@@ -509,8 +613,8 @@ sub FULLY_ExecuteNB ($$$$)
} }
} }
Log3 $name, 4, "FULLY: [$name] Pushing $url on command stack"; FULLY_Log ($hash, 4, "Pushing $url on command stack");
push (@urllist, "$url&password=".$hash->{fully}{password}); push (@urllist, "$url&password=".$hash->{fully}{password}."&type=json");
} }
# Ping tablet device # Ping tablet device
@@ -530,7 +634,7 @@ sub FULLY_ExecuteNB ($$$$)
callback => \&FULLY_ExecuteCB callback => \&FULLY_ExecuteCB
}; };
Log3 $name, 4, "FULLY: [$name] Executing command ".$urllist[0]; FULLY_Log ($hash, 4, "Executing command ".$urllist[0]);
HttpUtils_NonblockingGet ($reqpar); HttpUtils_NonblockingGet ($reqpar);
} }
@@ -545,17 +649,33 @@ sub FULLY_ExecuteCB ($$$)
my $name = $hash->{NAME}; my $name = $hash->{NAME};
if ($err eq '') { if ($err eq '') {
if ($param->{cmdno} == $param->{cmdcnt}) { # Process response
# Last request, update readings FULLY_Log ($hash, 5, $data, 0);
Log3 $name, 4, "FULLY: [$name] Last command executed. Processing results"; my $result = decode_json ($data);
Log3 $name, 5, "FULLY: [$name] $data"; if (!exists($result->{status})) {
my $result = FULLY_ProcessDeviceInfo ($name, $data); $result->{status} = 'OK';
Log3 $name, 4, "FULLY: [$name] $result"; $result->{statustext} //= 'N/A';
if (!FULLY_UpdateReadings ($hash, $result)) { }
Log3 $name, 2, "FULLY: [$name] Command failed"; $result->{execstate} = $result->{status};
} if (exists($result->{statustext})) {
$result->{statustext} =~ s/password=[^&]+//;
$result->{execstate} .= " $result->{statustext}";
} }
else { else {
$result->{statustext} = 'N/A';
}
FULLY_UpdateReadings ($hash, $result);
if ($result->{status} =~ /^Error/i) {
FULLY_Log ($hash, 2, "Command $param->{orgurl} failed: $result->{status} $result->{statustext}");
}
else {
$hash->{lastUpdate} = strftime "%d.%m.%Y %H:%M:%S", localtime (time)
if ($param->{orgurl} =~ /deviceInfo/);
FULLY_Log ($hash, 4, "Command $param->{orgurl} executed: $result->{status} $result->{statustext}");
}
if ($param->{cmdno} < $param->{cmdcnt}) {
# Execute next request # Execute next request
my @urllist = @{$param->{urllist}}; my @urllist = @{$param->{urllist}};
my $reqpar = { my $reqpar = {
@@ -572,9 +692,13 @@ sub FULLY_ExecuteCB ($$$)
callback => \&FULLY_ExecuteCB callback => \&FULLY_ExecuteCB
}; };
Log3 $name, 4, "FULLY: [$name] Executing command ".$urllist[$param->{cmdno}]; FULLY_Log ($hash, 4, "Executing command ".$urllist[$param->{cmdno}]);
HttpUtils_NonblockingGet ($reqpar); HttpUtils_NonblockingGet ($reqpar);
} }
else {
FULLY_Log ($hash, 4, 'Last command executed.');
return;
}
} }
else { else {
# Repeat failed request # Repeat failed request
@@ -593,12 +717,16 @@ sub FULLY_ExecuteCB ($$$)
callback => \&FULLY_ExecuteCB callback => \&FULLY_ExecuteCB
}; };
Log3 $name, 4, "FULLY: [$name] Repeating command ".$param->{orgurl}; FULLY_Log ($hash, 4, "Repeating command ".$param->{orgurl});
HttpUtils_NonblockingGet ($reqpar); HttpUtils_NonblockingGet ($reqpar);
} }
else { else {
readingsSingleUpdate ($hash, "execState", "error", 1); FULLY_UpdateReadings ($hash, {
Log3 $name, 2, "FULLY: [$name] Error during request. $err"; "status" => "Error",
"statustext" => "$err",
"execstate" => "Error $err"
});
FULLY_Log ($hash, 2, "Error during request $param->{orgurl}. $err");
} }
} }
} }
@@ -624,24 +752,11 @@ sub FULLY_ScreenOff ($)
sub FULLY_UpdateDeviceInfo ($) sub FULLY_UpdateDeviceInfo ($)
{ {
my ($hash) = @_; my ($hash) = @_;
my $name = $hash->{NAME};
my $disable = AttrVal ($name, 'disable', 0); return if (AttrVal ($hash->{NAME}, 'disable', 0) == 1);
return if ($disable);
my $pollInterval = AttrVal ($name, 'pollInterval', $FULLY_POLL_INTERVAL); FULLY_ExecuteNB ($hash, ['deviceInfo'], undef, 1);
FULLY_SetPolling ($hash, 1);
my @c = ("deviceInfo");
FULLY_ExecuteNB ($hash, \@c, undef, 1);
if ($pollInterval > 0) {
$hash->{nextUpdate} = strftime "%d.%m.%Y %H:%M:%S", localtime (time+$pollInterval);
InternalTimer (gettimeofday()+$pollInterval, "FULLY_UpdateDeviceInfo", $hash, 0);
}
else {
$hash->{nextUpdate} = "none";
}
} }
###################################################################### ######################################################################
@@ -653,68 +768,7 @@ sub FULLY_GetDeviceInfo ($)
my ($name) = @_; my ($name) = @_;
my $hash = $defs{$name}; my $hash = $defs{$name};
my @c = ("deviceInfo"); FULLY_ExecuteNB ($hash, ['deviceInfo'], undef, 1);
FULLY_ExecuteNB ($hash, \@c, undef, 1);
}
######################################################################
# Extract parameters from HTML code
######################################################################
sub FULLY_ProcessDeviceInfo ($$)
{
my ($name, $result) = @_;
return "$name|0|state=failed" if (!defined ($result) || $result eq '');
return "$name|0|state=wrong password" if ($result =~ /Wrong password/);
# HTML code format
# <td class='table-cell'>Kiosk mode</td><td class='table-cell'>off</td>
my $parameters = "$name|1";
while ($result =~ /table-cell.>([^<]+)<\/td><td class=.table-cell.>(.*?)<\/td>/g) {
my $rn = lc($1);
my $rv = $2;
if ($rv =~ /^<a .*?>(.*?)<\/a>/) {
$rv = $1;
}
elsif ($rv =~ /(.*?)</) {
$rv = $1;
}
$rv =~ s/[ ]+$//;
$rv =~ s/\s+$//;
$rn =~ s/\:/\./g;
$rn =~ s/[^A-Za-z\d_\.-]+/_/g;
$rn =~ s/[_]+$//;
next if ($rn eq 'webview_ua');
if ($rn eq 'battery_level') {
if ($rv =~ /^([0-9]+)% \(([^\)]+)\)$/) {
$parameters .= "|$rn=$1|power=$2";
next;
}
}
elsif ($rn eq 'screen_brightness') {
$rn = "brightness";
}
elsif ($rn eq 'screen_status') {
$parameters .= "|state=$rv";
}
elsif ($rn eq 'fully_version') {
if ($rv =~ /^([0-9]\.[0-9]+).*/) {
my $cv = $1;
Log3 $name, 1, "FULLY: [$name] Version of fully browser is $rv. Version $FULLY_REQUIRED_VERSION is required."
if ($cv < $FULLY_REQUIRED_VERSION);
}
else {
Log3 $name, 2, "FULLY: [$name] Cannot detect version of fully browser.";
}
}
$parameters .= "|$rn=$rv";
}
return $parameters;
} }
###################################################################### ######################################################################
@@ -724,34 +778,97 @@ sub FULLY_ProcessDeviceInfo ($$)
sub FULLY_UpdateReadings ($$) sub FULLY_UpdateReadings ($$)
{ {
my ($hash, $result) = @_; my ($hash, $result) = @_;
my $name = $hash->{NAME};
my $rc = 1;
if (!defined ($result) || $result eq '') {
Log3 $name, 2, "FULLY: [$name] empty response";
return 0;
}
my @parameters = split ('\|', $result); my %readings = (
if (scalar (@parameters) == 0) { 'isdeviceadmin' => 'bool',
Log3 $name, 2, "FULLY: [$name] empty response"; 'isdeviceowner' => 'bool',
return 0; 'isindaydream' => 'bool',
} 'isinforcedsleep' => 'bool',
'isinscreensaver' => 'bool',
if ($parameters[0] eq $name) { 'islicensed' => 'bool',
my $n = shift @parameters; 'ismenuopen' => 'bool',
$rc = shift @parameters; 'ismobiledataenabled' => 'bool',
} 'isplugged' => 'bool',
'isrooted' => 'bool',
'keyguardlocked' => 'bool',
'kiosklocked' => 'bool',
'kioskmode' => 'bool',
'maintenancemode' => 'bool',
'motiondetectorstatus' => 'bool',
'mqttconnected' => 'bool',
'plugged' => 'bool',
'screenlocked' => 'bool',
'screenon' => 'bool',
'webviewua' => 'ignore'
);
readingsBeginUpdate ($hash); readingsBeginUpdate ($hash);
foreach my $parval (@parameters) { foreach my $rn (keys %$result) {
my ($rn, $rv) = split ('=', $parval); my $key = lc($rn);
readingsBulkUpdate ($hash, $rn, $rv); next if (exists($readings{$key}) && $readings{$key} eq 'ignore');
readingsBulkUpdate ($hash, $key, exists($readings{$key}) && $readings{$key} eq 'bool' ?
($result->{$rn} eq '0' ? 'no' : 'yes') : $result->{$rn});
} }
readingsBulkUpdate ($hash, "execState", "success");
readingsEndUpdate ($hash, 1);
return $rc; my $screenOn = $result->{isScreenOn} // $result->{screenOn};
readingsBulkUpdate ($hash, 'state', defined($screenOn) ? ($screenOn eq '0' ? 'off' : 'on') : 'unknown');
if (exists($result->{appVersionName}) && $result->{appVersionName} =~ /^([0-9]\.[0-9]+).*/) {
if ($1 < $FULLY_REQUIRED_VERSION && !exists($hash->{fully}{versionWarn})) {
FULLY_Log ($hash, 1, "Version of fully browser is $1. Version $FULLY_REQUIRED_VERSION is required.");
$hash->{fully}{versionWarn} = 1;
}
}
readingsEndUpdate ($hash, 1);
}
######################################################################
# Encrypt string with FHEM unique ID
######################################################################
sub FULLY_Encrypt ($)
{
my ($istr) = @_;
my $ostr = '';
my $id = getUniqueId() // '';
return '' if ($id eq '');
my $key = $id;
foreach my $c (split //, $istr) {
my $k = chop($key);
if ($k eq '') {
$key = $id;
$k = chop($key);
}
$ostr .= sprintf ("%.2x",ord($c)^ord($k));
}
return $ostr;
}
######################################################################
# Decrypt string with FHEM unique ID
######################################################################
sub FULLY_Decrypt ($)
{
my ($istr) = @_;
my $ostr = '';
my $id = getUniqueId() // '';
return '' if ($id eq '');
my $key = $id;
for my $c (map { pack('C', hex($_)) } ($istr =~ /(..)/g)) {
my $k = chop($key);
if ($k eq '') {
$key = $id;
$k = chop($key);
}
$ostr .= chr(ord($c)^ord($k));
}
return $ostr;
} }
###################################################################### ######################################################################
@@ -770,7 +887,7 @@ sub FULLY_Ping ($$)
my $waitAfterPing = minNum (AttrVal ($name, 'waitAfterPing', 0), 2); my $waitAfterPing = minNum (AttrVal ($name, 'waitAfterPing', 0), 2);
my $os = $^O; my $os = $^O;
Log3 $name, 4, "FULLY: [$name] Sending $count ping request(s) to tablet $host. OS=$os"; FULLY_Log ($hash, 4, "Sending $count ping request(s) to tablet $host. OS=$os");
if ($^O =~ m/(Win|cygwin)/) { if ($^O =~ m/(Win|cygwin)/) {
$temp = qx(ping -n $count -4 $host >nul); $temp = qx(ping -n $count -4 $host >nul);
@@ -806,20 +923,26 @@ sub FULLY_Ping ($$)
must be enabled in Fully app. Requires Fully app version 1.27 or later. must be enabled in Fully app. Requires Fully app version 1.27 or later.
</br></br> </br></br>
<a name="HMCCUdefine"></a> <a name="FULLYdefine"></a>
<b>Define</b><br/><br/> <b>Define</b><br/><br/>
<ul> <ul>
<code>define &lt;name&gt; FULLY [&lt;Protocol&gt;://]&lt;HostOrIP&gt;[:&lt;Port&gt;] &lt;password&gt; [&lt;poll-interval&gt;]</code> <code>define &lt;name&gt; FULLY [&lt;Protocol&gt;://]&lt;HostOrIP&gt;[:&lt;Port&gt;] [&lt;password&gt;] [&lt;poll-interval&gt;]</code>
<br/><br/> <br/><br/>
The parameter <i>password</i> is the password set in Fully browser. Parameter <i>Protocol</i> is The parameter <i>password</i> is the password set in Fully browser. Parameter <i>Protocol</i> is
optional. Valid protocols are 'http' and 'https'. Default protocol is 'http'. optional. Valid protocols are 'http' and 'https'. Default protocol is 'http'.
Default <i>Port</i> is 2323. Default <i>Port</i> is 2323.<br/>
The password is optional. If you don't want the password to appear in the device definition,
set the password by using command 'set authentication'.
</ul> </ul>
<br/> <br/>
<a name="FULLYset"></a> <a name="FULLYset"></a>
<b>Set</b><br/><br/> <b>Set</b><br/><br/>
<ul> <ul>
<li><b>set &lt;name&gt; authentication [&lt;password&gt;]</b><br/>
Set Fully password. This password is used for each Fully opteration.
If no password is specified, the current password is deleted.
</li><br/>
<li><b>set &lt;name&gt; brightness 0-255</b><br/> <li><b>set &lt;name&gt; brightness 0-255</b><br/>
Adjust screen brightness. Adjust screen brightness.
</li><br/> </li><br/>
@@ -832,6 +955,9 @@ sub FULLY_Ping ($$)
<li><b>set &lt;name&gt; foreground</b><br/> <li><b>set &lt;name&gt; foreground</b><br/>
Bring fully app to foreground. Bring fully app to foreground.
</li><br/> </li><br/>
<li><b>set &lt;name&gt; lockKiosk</b><br/>
Lock kiosk mode.
</li><br/>
<li><b>set &lt;name&gt; motionDetection { on | off }</b><br/> <li><b>set &lt;name&gt; motionDetection { on | off }</b><br/>
Turn motion detection by camera on or off. Turn motion detection by camera on or off.
</li><br/> </li><br/>
@@ -844,6 +970,10 @@ sub FULLY_Ping ($$)
<li><b>set &lt;name&gt; on-for-timer [{ &lt;Seconds&gt; | <u>forever</u> | off }]</b><br/> <li><b>set &lt;name&gt; on-for-timer [{ &lt;Seconds&gt; | <u>forever</u> | off }]</b><br/>
Set timer for display. Default is forever. Set timer for display. Default is forever.
</li><br/> </li><br/>
<li><b>set &lt;name&gt; overlayMessage { text }</b><br/>
Show overlay message. Placeholders in format [device:reading] are supported and will
be substituted by the corresponding reading value.
</li><br/>
<li><b>set &lt;name&gt; photo</b><br/> <li><b>set &lt;name&gt; photo</b><br/>
Take a picture with device cam. Setting motion detection must be enabled. Picture Take a picture with device cam. Setting motion detection must be enabled. Picture
can be viewed in remote admin interface under device info. can be viewed in remote admin interface under device info.
@@ -851,6 +981,9 @@ sub FULLY_Ping ($$)
<li><b>set &lt;name&gt; playSound &lt;url&gt; [loop]</b><br/> <li><b>set &lt;name&gt; playSound &lt;url&gt; [loop]</b><br/>
Play sound from URL. Play sound from URL.
</li><br/> </li><br/>
<li><b>set &lt;name&gt; playVideo &lt;url&gt; [loop] [showControls] [exitOnTouch] [exitOnCompletion]</b><br/>
Play video from URL.
</li><br/>
<li><b>set &lt;name&gt; restart</b><br/> <li><b>set &lt;name&gt; restart</b><br/>
Restart Fully. Restart Fully.
</li><br/> </li><br/>
@@ -884,6 +1017,12 @@ sub FULLY_Ping ($$)
<li><b>set &lt;name&gt; stopSound</b><br/> <li><b>set &lt;name&gt; stopSound</b><br/>
Stop playback of sound if playback has been started with option <i>loop</i>. Stop playback of sound if playback has been started with option <i>loop</i>.
</li><br/> </li><br/>
<li><b>set &lt;name&gt; stopVideo</b><br/>
Stop playback of video if playback has been started with option <i>loop</i>.
</li><br/>
<li><b>set &lt;name&gt; unlockKiosk</b><br/>
Unlock kiosk mode.
</li><br/>
<li><b>set &lt;name&gt; url [&lt;URL&gt;]</b><br/> <li><b>set &lt;name&gt; url [&lt;URL&gt;]</b><br/>
Navigate to <i>URL</i>. If no URL is specified navigate to start URL. Navigate to <i>URL</i>. If no URL is specified navigate to start URL.
</li><br/> </li><br/>
@@ -901,7 +1040,7 @@ sub FULLY_Ping ($$)
Display Fully information. This is command blocks FHEM until completion. Display Fully information. This is command blocks FHEM until completion.
</li><br/> </li><br/>
<li><b>get &lt;name&gt; stats</b><br/> <li><b>get &lt;name&gt; stats</b><br/>
Show Fully statistics. Show Fully statistics. Will be implemented later.
</li><br/> </li><br/>
<li><b>get &lt;name&gt; update</b><br/> <li><b>get &lt;name&gt; update</b><br/>
Update readings. Update readings.
@@ -933,7 +1072,11 @@ sub FULLY_Ping ($$)
is 0 (do not repeat commands). is 0 (do not repeat commands).
</li><br/> </li><br/>
<li><b>requestTimeout &lt;seconds&gt;</b><br/> <li><b>requestTimeout &lt;seconds&gt;</b><br/>
Set timeout for http requests. Default is 5 seconds. Set timeout for http requests. Default is 5 seconds. Increase this value if commands
are failing with a timeout error.
</li><br/>
<li><b>updateAfterCommand &lt;0 | 1&gt;</b><br/>
When set to 1 update readings after a set command. Default is 0.
</li><br/> </li><br/>
<li><b>waitAfterPing &lt;Seconds&gt;</b><br/> <li><b>waitAfterPing &lt;Seconds&gt;</b><br/>
Wait specified amount of time after sending ping request to tablet device. Valid Wait specified amount of time after sending ping request to tablet device. Valid