From 7c7985fe2416ca2a11e24831d0adfa3866633277 Mon Sep 17 00:00:00 2001 From: Beta-User Date: Wed, 26 Nov 2025 21:33:54 +0000 Subject: [PATCH] FULLY: maintainer change + commandref update git-svn-id: https://svn.fhem.de/fhem/trunk@30558 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/FHEM/89_FULLY.pm | 1739 +++++++++++++++++++++-------------------- fhem/MAINTAINER.txt | 2 +- 2 files changed, 883 insertions(+), 858 deletions(-) diff --git a/fhem/FHEM/89_FULLY.pm b/fhem/FHEM/89_FULLY.pm index 1cd415b43..7236dd948 100755 --- a/fhem/FHEM/89_FULLY.pm +++ b/fhem/FHEM/89_FULLY.pm @@ -7,10 +7,22 @@ # Control Fully browser on Android tablets from FHEM. # Requires Fully App Plus license! # -# This program free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License V2. +# This file is part of fhem. # -# (c) 2022 by zap (zap01 t-online de) +# Fhem is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# Fhem is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with fhem. If not, see . +# +# (c) 2022-2025 by original autor zap # ############################################################################## @@ -22,28 +34,6 @@ use HttpUtils; use JSON; use SetExtensions; -# Declare functions -sub FULLY_Initialize ($); -sub FULLY_Define ($$); -sub FULLY_Undef ($$); -sub FULLY_Shutdown ($); -sub FULLY_Set ($@); -sub FULLY_Get ($@); -sub FULLY_Attr ($@); -sub FULLY_Detail ($@); -sub FULLY_Notify ($$); -sub FULLY_UpdateDeviceInfo ($); -sub FULLY_Execute ($$$$); -sub FULLY_ExecuteNB ($$$$); -sub FULLY_ExecuteCB ($$$); -sub FULLY_ScreenOff ($); -sub FULLY_GetDeviceInfo ($); -sub FULLY_UpdateReadings ($$); -sub FULLY_Encrypt ($); -sub FULLY_Decrypt ($); -sub FULLY_Ping ($$); -sub FULLY_SetPolling ($$;$); - my $FULLY_VERSION = '2.3'; # Timeout for Fully requests @@ -68,20 +58,20 @@ my $FULLY_DEFAULT_PORT = '2323'; sub FULLY_Initialize ($) { - my ($hash) = @_; + my ($hash) = @_; - $hash->{DefFn} = "FULLY_Define"; - $hash->{UndefFn} = "FULLY_Undef"; - $hash->{SetFn} = "FULLY_Set"; - $hash->{GetFn} = "FULLY_Get"; - $hash->{AttrFn} = "FULLY_Attr"; - $hash->{NotifyFn} = "FULLY_Notify"; - $hash->{ShutdownFn} = "FULLY_Shutdown"; - $hash->{FW_detailFn} = "FULLY_Detail"; + $hash->{DefFn} = "FULLY_Define"; + $hash->{UndefFn} = "FULLY_Undef"; + $hash->{SetFn} = "FULLY_Set"; + $hash->{GetFn} = "FULLY_Get"; + $hash->{AttrFn} = "FULLY_Attr"; + $hash->{NotifyFn} = "FULLY_Notify"; + $hash->{ShutdownFn} = "FULLY_Shutdown"; + $hash->{FW_detailFn} = "FULLY_Detail"; - $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 updateAfterCommand:0,1 " . - $readingFnAttributes; + $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 updateAfterCommand:0,1 " . + $readingFnAttributes; } ###################################################################### @@ -90,74 +80,74 @@ sub FULLY_Initialize ($) sub FULLY_Define ($$) { - my ($hash, $def) = @_; - my @a = split( "[ \t][ \t]*", $def); - my $name = $a[0]; - my $host = ''; - - return "Usage: define devname FULLY [http|https]://IP_or_Hostname [password] [poll-interval]" - if (@a < 3); + my ($hash, $def) = @_; + my @a = split( "[ \t][ \t]*", $def); + my $name = $a[0]; + my $host = ''; - if ($a[2] =~ /^(https?):\/\/(.+)/) { - $hash->{prot} = $1; - $host = $2; - } - else { - $hash->{prot} = $FULLY_DEFAULT_PROT; - $host = $a[2]; - } + return "Usage: define devname FULLY [http|https]://IP_or_Hostname [password] [poll-interval]" + if (@a < 3); - if ($host =~ /^([^:]+):([0-9]+)$/) { - $hash->{host} = $1; - $hash->{port} = $2; - } - else { - $hash->{host} = $host; - $hash->{port} = $FULLY_DEFAULT_PORT; - } + if ($a[2] =~ /^(https?):\/\/(.+)/) { + $hash->{prot} = $1; + $host = $2; + } + else { + $hash->{prot} = $FULLY_DEFAULT_PROT; + $host = $a[2]; + } - $hash->{version} = $FULLY_VERSION; - $hash->{NOTIFYDEV} = 'global,TYPE=FULLY'; - $hash->{onForTimer} = 'off'; - $hash->{nextUpdate} = 'off'; - $hash->{fully}{schedule} = 0; - - if (@a == 4) { - if ($a[3] =~ /^[0-9]+$/) { - $hash->{fully}{interval} = $a[3]; - } - else { - $hash->{fully}{password} = $a[3]; - } - } - elsif (@a == 5) { - $hash->{fully}{password} = $a[3]; - $hash->{fully}{interval} = $a[4]; - } + if ($host =~ /^([^:]+):([0-9]+)$/) { + $hash->{host} = $1; + $hash->{port} = $2; + } + else { + $hash->{host} = $host; + $hash->{port} = $FULLY_DEFAULT_PORT; + } - if (!exists($hash->{fully}{password})) { - my ($errpass, $encpass) = getKeyValue ($name.'_password'); - if (!defined($errpass) && defined($encpass)) { - $hash->{fully}{password} = FULLY_Decrypt ($encpass); - } - else { - FULLY_Log ($hash, 2, "Fully password not defined"); - } - } + $hash->{version} = $FULLY_VERSION; + $hash->{NOTIFYDEV} = 'global,TYPE=FULLY'; + $hash->{onForTimer} = 'off'; + $hash->{nextUpdate} = 'off'; + $hash->{fully}{schedule} = 0; - if (!$init_done && exists($hash->{fully}{password})) { - FULLY_Log ($hash, 1, "Version $FULLY_VERSION Opening device ".$hash->{host}); - FULLY_GetDeviceInfo ($name); - if (exists($hash->{fully}{interval})) { - FULLY_SetPolling ($hash, 1, $hash->{fully}{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; + if (@a == 4) { + if ($a[3] =~ /^[0-9]+$/) { + $hash->{fully}{interval} = $a[3]; + } + else { + $hash->{fully}{password} = $a[3]; + } + } + elsif (@a == 5) { + $hash->{fully}{password} = $a[3]; + $hash->{fully}{interval} = $a[4]; + } + + if (!exists($hash->{fully}{password})) { + my ($errpass, $encpass) = getKeyValue ($name.'_password'); + if (!defined($errpass) && defined($encpass)) { + $hash->{fully}{password} = FULLY_Decrypt ($encpass); + } + else { + FULLY_Log ($hash, 2, "Fully password not defined"); + } + } + + if (!$init_done && exists($hash->{fully}{password})) { + FULLY_Log ($hash, 1, "Version $FULLY_VERSION Opening device ".$hash->{host}); + FULLY_GetDeviceInfo ($name); + if (exists($hash->{fully}{interval})) { + FULLY_SetPolling ($hash, 1, $hash->{fully}{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; } ###################################################################### @@ -166,49 +156,49 @@ sub FULLY_Define ($$) sub FULLY_Attr ($@) { - my ($cmd, $name, $attrname, $attrval) = @_; - my $hash = $defs{$name}; + my ($cmd, $name, $attrname, $attrval) = @_; + my $hash = $defs{$name}; - if ($cmd eq 'set') { - if ($attrname eq 'pollInterval') { - if ($attrval >= $FULLY_POLL_RANGE[0] && $attrval <= $FULLY_POLL_RANGE[1]) { - FULLY_SetPolling ($hash, 1, $attrval); - } - elsif ($attrval == 0) { - FULLY_SetPolling ($hash, 0); - } - else { - return "FULLY: Polling interval must be in range ".$FULLY_POLL_RANGE[0]."-".$FULLY_POLL_RANGE[1]; - } - } - elsif ($attrname eq 'requestTimeout') { - 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') { - if ($attrname eq 'pollInterval') { - # Set the polling interval to default - 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; + if ($cmd eq 'set') { + if ($attrname eq 'pollInterval') { + if ($attrval >= $FULLY_POLL_RANGE[0] && $attrval <= $FULLY_POLL_RANGE[1]) { + FULLY_SetPolling ($hash, 1, $attrval); + } + elsif ($attrval == 0) { + FULLY_SetPolling ($hash, 0); + } + else { + return "FULLY: Polling interval must be in range ".$FULLY_POLL_RANGE[0]."-".$FULLY_POLL_RANGE[1]; + } + } + elsif ($attrname eq 'requestTimeout') { + 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') { + if ($attrname eq 'pollInterval') { + # Set the polling interval to default + 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; } ###################################################################### @@ -217,32 +207,32 @@ sub FULLY_Attr ($@) sub FULLY_SetPolling ($$;$) { - my ($hash, $mode, $interval) = @_; - - return if (!$init_done); - - my $name = $hash->{NAME}; - $interval //= AttrVal ($name, 'pollInterval', $hash->{fully}{interval} // $FULLY_POLL_INTERVAL); - - if ($mode == 0 || $interval == 0) { - RemoveInternalTimer ($hash, 'FULLY_UpdateDeviceInfo'); - FULLY_Log ($hash, 2, "Polling deactivated") - if (!exists($hash->{nextUpdate}) || $hash->{nextUpdate} ne 'off'); - $hash->{nextUpdate} = 'off'; - } - elsif ($mode == 1) { - RemoveInternalTimer ($hash, 'FULLY_UpdateDeviceInfo'); - if (!exists($hash->{fully}{password})) { - FULLY_Log ($hash, 2, "Polling not activated. Fully password not defined"); - return; - } - $interval = $FULLY_POLL_RANGE[0] if ($interval < $FULLY_POLL_RANGE[0]); - $interval = $FULLY_POLL_RANGE[1] if ($interval > $FULLY_POLL_RANGE[1]); - FULLY_Log ($hash, 2, "Polling activated") - if (exists($hash->{nextUpdate}) && $hash->{nextUpdate} eq 'off'); - $hash->{nextUpdate} = strftime "%d.%m.%Y %H:%M:%S", localtime (time+$interval); - InternalTimer (gettimeofday()+$interval, "FULLY_UpdateDeviceInfo", $hash, 0); - } + my ($hash, $mode, $interval) = @_; + + return if (!$init_done); + + my $name = $hash->{NAME}; + $interval //= AttrVal ($name, 'pollInterval', $hash->{fully}{interval} // $FULLY_POLL_INTERVAL); + + if ($mode == 0 || $interval == 0) { + RemoveInternalTimer ($hash, 'FULLY_UpdateDeviceInfo'); + FULLY_Log ($hash, 2, "Polling deactivated") + if (!exists($hash->{nextUpdate}) || $hash->{nextUpdate} ne 'off'); + $hash->{nextUpdate} = 'off'; + } + elsif ($mode == 1) { + RemoveInternalTimer ($hash, 'FULLY_UpdateDeviceInfo'); + if (!exists($hash->{fully}{password})) { + FULLY_Log ($hash, 2, "Polling not activated. Fully password not defined"); + return; + } + $interval = $FULLY_POLL_RANGE[0] if ($interval < $FULLY_POLL_RANGE[0]); + $interval = $FULLY_POLL_RANGE[1] if ($interval > $FULLY_POLL_RANGE[1]); + FULLY_Log ($hash, 2, "Polling activated") + if (exists($hash->{nextUpdate}) && $hash->{nextUpdate} eq 'off'); + $hash->{nextUpdate} = strftime "%d.%m.%Y %H:%M:%S", localtime (time+$interval); + InternalTimer (gettimeofday()+$interval, "FULLY_UpdateDeviceInfo", $hash, 0); + } } ###################################################################### @@ -251,11 +241,11 @@ sub FULLY_SetPolling ($$;$) sub FULLY_Undef ($$) { - my ($hash, $arg) = @_; + my ($hash, $arg) = @_; - RemoveInternalTimer ($hash); - - return undef; + RemoveInternalTimer ($hash); + + return undef; } ###################################################################### @@ -264,11 +254,11 @@ sub FULLY_Undef ($$) sub FULLY_Shutdown ($) { - my ($hash) = @_; + my ($hash) = @_; - RemoveInternalTimer ($hash); + RemoveInternalTimer ($hash); - return undef; + return undef; } ###################################################################### @@ -277,16 +267,16 @@ sub FULLY_Shutdown ($) sub FULLY_Notify ($$) { - my ($hash, $devhash) = @_; + my ($hash, $devhash) = @_; - return if (AttrVal ($hash->{NAME}, 'disable', 0) == 1); - - my $events = deviceEvents ($devhash, 1); - return if (!$events); - - if ($devhash->{NAME} eq 'global' && grep (/INITIALIZED/, @$events)) { - FULLY_SetPolling ($hash, 1); - } + return if (AttrVal ($hash->{NAME}, 'disable', 0) == 1); + + my $events = deviceEvents ($devhash, 1); + return if (!$events); + + if ($devhash->{NAME} eq 'global' && grep (/INITIALIZED/, @$events)) { + FULLY_SetPolling ($hash, 1); + } } ###################################################################### @@ -295,227 +285,227 @@ sub FULLY_Notify ($$) sub FULLY_Detail ($@) { - my ($FW_wname, $name, $room, $pageHash) = @_; - my $hash = $defs{$name}; - - my $html = qq( - Device Administration - - - - -
- ); - - return $html; + my ($FW_wname, $name, $room, $pageHash) = @_; + my $hash = $defs{$name}; + + my $html = qq( + Device Administration + + + + +
+ ); + + return $html; } - + ###################################################################### # Set commands ###################################################################### sub FULLY_Set ($@) { - my ($hash, $name, $opt, @a) = @_; - my $options = "brightness:slider,0,1,255 photo:noArg clearCache:noArg clearWebstorage:noArg ". - "clearCookies:noArg exit:noArg foreground:noArg lock:noArg startApp ". - "motionDetection:on,off off:noArg on:noArg on-for-timer playSound playVideo restart:noArg ". - "screenOffTimer screenSaver:start,stop screenSaverTimer screenSaverURL speak startURL ". - "stopSound:noArg stopVideo:noArg lockKiosk:noArg unlockKiosk:noArg unlock:noArg url ". - "volume overlayMessage authentication"; - - # Fully commands without argument - my %cmds = ( - "clearCache" => "clearCache", - "clearWebstorage" => "clearWebstorage", - "clearCookies" => "clearCookies", - "photo" => "getCamshot", - "exit" => "exitApp", - "restart" => "restartApp", - "on" => "screenOn", - "off" => "screenOff", - "lock" => "enabledLockedMode", - "unlock" => "disableLockedMode", - "lockKiosk" => "lockKiosk", - "unlockKiosk" => "unlockKiosk", - "stopSound" => "stopSound", - "stopVideo" => "stopVideo", - "foreground" => "toForeground" - ); - - my @c = (); - my @p = (); - - return "Device disabled" if (AttrVal ($name, 'disable', 0) == 1); - return "FULLY: Missing password, choose one of authentication" - if (!exists($hash->{fully}{password}) && $opt ne 'authentication'); - - my $expert = AttrVal ($name, 'expert', 0); - $options .= " setStringSetting setBooleanSetting" if ($expert); - my $updateAfterCommand = AttrVal ($name, 'updateAfterCommand', 0); - - if (exists ($cmds{$opt})) { - push (@c, $cmds{$opt}); - } - elsif ($opt eq 'authentication') { - my $password = shift @a; + my ($hash, $name, $opt, @a) = @_; + my $options = "brightness:slider,0,1,255 photo:noArg clearCache:noArg clearWebstorage:noArg ". + "clearCookies:noArg exit:noArg foreground:noArg lock:noArg startApp ". + "motionDetection:on,off off:noArg on:noArg on-for-timer playSound playVideo restart:noArg ". + "screenOffTimer screenSaver:start,stop screenSaverTimer screenSaverURL speak startURL ". + "stopSound:noArg stopVideo:noArg lockKiosk:noArg unlockKiosk:noArg unlock:noArg url ". + "volume overlayMessage authentication"; - if (!defined($password)) { - setKeyValue ($name."_password", undef); - delete $hash->{fully}{password}; - return 'Password for FULLY authentication deleted'; - } + # Fully commands without argument + my %cmds = ( + "clearCache" => "clearCache", + "clearWebstorage" => "clearWebstorage", + "clearCookies" => "clearCookies", + "photo" => "getCamshot", + "exit" => "exitApp", + "restart" => "restartApp", + "on" => "screenOn", + "off" => "screenOff", + "lock" => "enabledLockedMode", + "unlock" => "disableLockedMode", + "lockKiosk" => "lockKiosk", + "unlockKiosk" => "unlockKiosk", + "stopSound" => "stopSound", + "stopVideo" => "stopVideo", + "foreground" => "toForeground" + ); - 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; - FULLY_SetPolling ($hash, 1); - - return 'Password for FULLY authentication stored'; - } - elsif ($opt eq 'on-for-timer') { - my $par = shift @a // "forever"; + my @c = (); + my @p = (); - if ($par eq 'forever') { - push (@c, "setBooleanSetting", "screenOn"); - push (@p, { "key" => "keepScreenOn", "value" => "true" }, undef); - RemoveInternalTimer ($hash, "FULLY_ScreenOff"); - } - elsif ($par eq 'off') { - push (@c, "setBooleanSetting", "setStringSetting"); - push (@p, { "key" => "keepScreenOn", "value" => "false" }, - { "key" => "timeToScreenOffV2", "value" => "0" }); - RemoveInternalTimer ($hash, "FULLY_ScreenOff"); - } - elsif ($par =~ /^[0-9]+$/) { - push (@c, "setBooleanSetting", "screenOn"); - push (@p, { "key" => "keepScreenOn", "value" => "false" }, undef); - InternalTimer (gettimeofday()+$par, "FULLY_ScreenOff", $hash, 0); - } - else { - return "Usage: set $name on-for-timer [{ Seconds | forever | off }]"; - } - - $hash->{onForTimer} = $par; - } - elsif ($opt eq 'screenOffTimer') { - my $value = shift @a // return "Usage: set $name $opt {seconds}"; - push (@c, "setStringSetting"); - push (@p, { "key" => "timeToScreenOffV2", "value" => "$value" }); - } - elsif ($opt eq 'screenSaver') { - my $state = shift @a; - return "Usage: set $name $opt { start | stop }" if (!defined ($state) || $state !~ /^(start|stop)$/); - push (@c, ($state eq 'start') ? "startScreensaver" : "stopScreensaver"); - } - elsif ($opt eq 'screenSaverTimer') { - my $value = shift @a // return "Usage: set $name $opt {seconds}"; - push (@c, "setStringSetting"); - push (@p, { "key" => "timeToScreensaverV2", "value" => "$value" }); - } - elsif ($opt eq 'screenSaverURL') { - my $value = shift @a // return "Usage: set $name $opt {URL}"; - push (@c, "setStringSetting"); - push (@p, { "key" => "screensaverURL", "value" => "$value" }); - } - elsif ($opt eq 'startURL') { - my $value = shift @a // return "Usage: set $name $opt {URL}"; - push (@c, "setStringSetting"); - push (@p, { "key" => "startURL", "value" => "$value" }); - } - elsif ($opt eq 'startApp') { - my $app = shift @a // return "Usage set $name $opt {APK-Name}"; - push (@c, "startApplication"); - push (@p, { "package" => "$app" } ); - } - elsif ($opt eq 'brightness') { - my $value = shift @a // return "Usage: set $name brightness 0-255"; - $value = 255 if ($value > 255); - push (@c, "setStringSetting"); - push (@p, { "key" => "screenBrightness", "value" => "$value" }); - } - elsif ($opt eq 'motionDetection') { - my $state = shift @a // return "Usage: set $name motionDetection { on | off }"; - my $value = $state eq 'on' ? 'true' : 'false'; - push (@c, "setBooleanSetting"); - push (@p, { "key" => "motionDetection", "value" => "$value" }); - } - elsif ($opt eq 'speak') { - my $text = join(' ',@a); - return 'Usage: set $name speak {Text}' if (!defined($text) || $text eq ''); - my $enctext = FULLY_SubstDeviceReading ($text); - push (@c, "textToSpeech"); - push (@p, { "text" => "$enctext" }); - } - elsif ($opt eq 'overlayMessage') { - my $text = join(' ',@a); - return 'Usage: set $name overlayMessage [{Text}]' if (!defined($text));; - my $enctext = $text ne '' ? FULLY_SubstDeviceReading ($text) : ''; - push (@c, "setOverlayMessage"); - push (@p, { "text" => "$enctext" }); - } - elsif ($opt eq 'playSound') { - my $url = shift @a // return "Usage: set $name playSound {url} [loop]"; - my $loop = shift @a; - $loop = defined ($loop) ? 'true' : 'false'; - push (@c, "playSound"); - push (@p, { "url" => "$url", "loop" => "$loop"}); - } - elsif ($opt eq 'playVideo') { - my $url = shift @a // return "Usage: set $name $opt {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') { - my $level = shift @a; - my $stream = shift @a; - return "Usage: set $name volume {level} {stream}" - if (!defined ($stream) || $level !~ /^[0-9]+$/ || $stream !~ /^[0-9]+$/); - push (@c, "setAudioVolume"); - push (@p, { "level" => "$level", "stream" => "$stream"}); - } - elsif ($opt eq 'url') { - my $url = shift @a; - if (defined ($url)) { - push (@c, "loadURL"); - push (@p, { "url" => "$url" }); - } - else { - push (@c, "loadStartURL"); - } - } - elsif ($opt eq 'setStringSetting' || $opt eq 'setBooleanSetting') { - return "FULLY: Command $opt only available in expert mode" if ($expert == 0); - my $key = shift @a; - my $value = join(' ',@a); - return "Usage: set $name $opt {key} {value}" if (!defined($value) || $value eq ''); - push (@c, $opt); - push (@p, { "key" => "$key", "value" => "$value" }); - } - else { - return "FULLY: Unknown argument $opt, choose one of ".$options; - } - - # Execute command requests - if ($updateAfterCommand) { - push (@c, 'deviceInfo'); - push (@p, undef); - } - FULLY_ExecuteNB ($hash, \@c, \@p, 1) if (scalar (@c) > 0); - - return undef; + return "Device disabled" if (AttrVal ($name, 'disable', 0) == 1); + return "FULLY: Missing password, choose one of authentication" + if (!exists($hash->{fully}{password}) && $opt ne 'authentication'); + + my $expert = AttrVal ($name, 'expert', 0); + $options .= " setStringSetting setBooleanSetting" if ($expert); + my $updateAfterCommand = AttrVal ($name, 'updateAfterCommand', 0); + + if (exists ($cmds{$opt})) { + push (@c, $cmds{$opt}); + } + elsif ($opt eq 'authentication') { + my $password = shift @a; + + if (!defined($password)) { + setKeyValue ($name."_password", undef); + delete $hash->{fully}{password}; + 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; + FULLY_SetPolling ($hash, 1); + + return 'Password for FULLY authentication stored'; + } + elsif ($opt eq 'on-for-timer') { + my $par = shift @a // "forever"; + + if ($par eq 'forever') { + push (@c, "setBooleanSetting", "screenOn"); + push (@p, { "key" => "keepScreenOn", "value" => "true" }, undef); + RemoveInternalTimer ($hash, "FULLY_ScreenOff"); + } + elsif ($par eq 'off') { + push (@c, "setBooleanSetting", "setStringSetting"); + push (@p, { "key" => "keepScreenOn", "value" => "false" }, + { "key" => "timeToScreenOffV2", "value" => "0" }); + RemoveInternalTimer ($hash, "FULLY_ScreenOff"); + } + elsif ($par =~ /^[0-9]+$/) { + push (@c, "setBooleanSetting", "screenOn"); + push (@p, { "key" => "keepScreenOn", "value" => "false" }, undef); + InternalTimer (gettimeofday()+$par, "FULLY_ScreenOff", $hash, 0); + } + else { + return "Usage: set $name on-for-timer [{ Seconds | forever | off }]"; + } + + $hash->{onForTimer} = $par; + } + elsif ($opt eq 'screenOffTimer') { + my $value = shift @a // return "Usage: set $name $opt {seconds}"; + push (@c, "setStringSetting"); + push (@p, { "key" => "timeToScreenOffV2", "value" => "$value" }); + } + elsif ($opt eq 'screenSaver') { + my $state = shift @a; + return "Usage: set $name $opt { start | stop }" if (!defined ($state) || $state !~ /^(start|stop)$/); + push (@c, ($state eq 'start') ? "startScreensaver" : "stopScreensaver"); + } + elsif ($opt eq 'screenSaverTimer') { + my $value = shift @a // return "Usage: set $name $opt {seconds}"; + push (@c, "setStringSetting"); + push (@p, { "key" => "timeToScreensaverV2", "value" => "$value" }); + } + elsif ($opt eq 'screenSaverURL') { + my $value = shift @a // return "Usage: set $name $opt {URL}"; + push (@c, "setStringSetting"); + push (@p, { "key" => "screensaverURL", "value" => "$value" }); + } + elsif ($opt eq 'startURL') { + my $value = shift @a // return "Usage: set $name $opt {URL}"; + push (@c, "setStringSetting"); + push (@p, { "key" => "startURL", "value" => "$value" }); + } + elsif ($opt eq 'startApp') { + my $app = shift @a // return "Usage set $name $opt {APK-Name}"; + push (@c, "startApplication"); + push (@p, { "package" => "$app" } ); + } + elsif ($opt eq 'brightness') { + my $value = shift @a // return "Usage: set $name brightness 0-255"; + $value = 255 if ($value > 255); + push (@c, "setStringSetting"); + push (@p, { "key" => "screenBrightness", "value" => "$value" }); + } + elsif ($opt eq 'motionDetection') { + my $state = shift @a // return "Usage: set $name motionDetection { on | off }"; + my $value = $state eq 'on' ? 'true' : 'false'; + push (@c, "setBooleanSetting"); + push (@p, { "key" => "motionDetection", "value" => "$value" }); + } + elsif ($opt eq 'speak') { + my $text = join(' ',@a); + return 'Usage: set $name speak {Text}' if (!defined($text) || $text eq ''); + my $enctext = FULLY_SubstDeviceReading ($text); + push (@c, "textToSpeech"); + push (@p, { "text" => "$enctext" }); + } + elsif ($opt eq 'overlayMessage') { + my $text = join(' ',@a); + return 'Usage: set $name overlayMessage [{Text}]' if (!defined($text));; + my $enctext = $text ne '' ? FULLY_SubstDeviceReading ($text) : ''; + push (@c, "setOverlayMessage"); + push (@p, { "text" => "$enctext" }); + } + elsif ($opt eq 'playSound') { + my $url = shift @a // return "Usage: set $name playSound {url} [loop]"; + my $loop = shift @a; + $loop = defined ($loop) ? 'true' : 'false'; + push (@c, "playSound"); + push (@p, { "url" => "$url", "loop" => "$loop"}); + } + elsif ($opt eq 'playVideo') { + my $url = shift @a // return "Usage: set $name $opt {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') { + my $level = shift @a; + my $stream = shift @a; + return "Usage: set $name volume {level} {stream}" + if (!defined ($stream) || $level !~ /^[0-9]+$/ || $stream !~ /^[0-9]+$/); + push (@c, "setAudioVolume"); + push (@p, { "level" => "$level", "stream" => "$stream"}); + } + elsif ($opt eq 'url') { + my $url = shift @a; + if (defined ($url)) { + push (@c, "loadURL"); + push (@p, { "url" => "$url" }); + } + else { + push (@c, "loadStartURL"); + } + } + elsif ($opt eq 'setStringSetting' || $opt eq 'setBooleanSetting') { + return "FULLY: Command $opt only available in expert mode" if ($expert == 0); + my $key = shift @a; + my $value = join(' ',@a); + return "Usage: set $name $opt {key} {value}" if (!defined($value) || $value eq ''); + push (@c, $opt); + push (@p, { "key" => "$key", "value" => "$value" }); + } + else { + return "FULLY: Unknown argument $opt, choose one of ".$options; + } + + # Execute command requests + if ($updateAfterCommand) { + push (@c, 'deviceInfo'); + push (@p, undef); + } + FULLY_ExecuteNB ($hash, \@c, \@p, 1) if (scalar (@c) > 0); + + return undef; } ###################################################################### @@ -524,28 +514,28 @@ sub FULLY_Set ($@) sub FULLY_Get ($@) { - my ($hash, $name, $opt, @a) = @_; + my ($hash, $name, $opt, @a) = @_; - my $options = "info:noArg update:noArg"; - - return "Device disabled" if (AttrVal ($name, 'disable', 0) == 1); - return "No password defined for Fully access" if (!exists($hash->{fully}{password})); - - if ($opt eq 'info') { - my $result = FULLY_Execute ($hash, 'deviceInfo', undef, 1) // - return FULLY_Log ($hash, 2, 'Command deviceInfo failed'); - return FULLY_Log ($hash, 2, $result->{'statustext'} // $result->{'status'}) - if (exists($result->{'status'})); - return join ("\n", map { "$_ = $result->{$_}" } sort keys %$result); - } - elsif ($opt eq 'update') { - FULLY_GetDeviceInfo ($name); - } - else { - return "FULLY: Unknown argument $opt, choose one of ".$options; - } + my $options = "info:noArg update:noArg"; - return undef; + return "Device disabled" if (AttrVal ($name, 'disable', 0) == 1); + return "No password defined for Fully access" if (!exists($hash->{fully}{password})); + + if ($opt eq 'info') { + my $result = FULLY_Execute ($hash, 'deviceInfo', undef, 1) // + return FULLY_Log ($hash, 2, 'Command deviceInfo failed'); + return FULLY_Log ($hash, 2, $result->{'statustext'} // $result->{'status'}) + if (exists($result->{'status'})); + return join ("\n", map { "$_ = $result->{$_}" } sort keys %$result); + } + elsif ($opt eq 'update') { + FULLY_GetDeviceInfo ($name); + } + else { + return "FULLY: Unknown argument $opt, choose one of ".$options; + } + + return undef; } ###################################################################### @@ -554,12 +544,12 @@ sub FULLY_Get ($@) sub FULLY_Log ($$$;$) { - my ($hash, $level, $message, $retval) = @_; - $retval //= $message; - my $name = $hash->{NAME}; - - Log3 $name, $level, "FULLY: [$name] $message"; - return $retval; + my ($hash, $level, $message, $retval) = @_; + $retval //= $message; + my $name = $hash->{NAME}; + + Log3 $name, $level, "FULLY: [$name] $message"; + return $retval; } ###################################################################### @@ -568,45 +558,45 @@ sub FULLY_Log ($$$;$) sub FULLY_Execute ($$$$) { - my ($hash, $command, $param, $doping) = @_; - my $name = $hash->{NAME}; + my ($hash, $command, $param, $doping) = @_; + 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 - my $timeout = AttrVal ($name, 'requestTimeout', $FULLY_TIMEOUT); - my $repeatCommand = minNum (AttrVal ($name, 'repeatCommand', 0), 2); - my $ping = minNum (AttrVal ($name, 'pingBeforeCmd', 0), 2); - - my $response = ''; - my $url = $hash->{prot}.'://'.$hash->{host}.':'.$hash->{port}."/?cmd=$command"; - - if (defined ($param)) { - foreach my $parname (keys %$param) { - if (defined($param->{$parname})) { - $url .= "&$parname=".$param->{$parname}; - } - } - } + 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; + } - # Ping tablet device - FULLY_Ping ($hash, $ping) if ($doping && $ping > 0); - - my $i = 0; - while ($i <= $repeatCommand && (!defined ($response) || $response eq '')) { - $response = GetFileFromURL ("$url&password=".$hash->{fully}{password}.'&type=json', $timeout); - FULLY_Log ($hash, 4, "HTTP response empty") if (defined($response) && $response eq ''); - $i++; - } - - my $result = eval { decode_json ($response) }; - FULLY_Log ($hash, 2, "Error in JSON data") if (!defined($result)); + # Get attributes + my $timeout = AttrVal ($name, 'requestTimeout', $FULLY_TIMEOUT); + my $repeatCommand = minNum (AttrVal ($name, 'repeatCommand', 0), 2); + my $ping = minNum (AttrVal ($name, 'pingBeforeCmd', 0), 2); - return $result; + my $response = ''; + my $url = $hash->{prot}.'://'.$hash->{host}.':'.$hash->{port}."/?cmd=$command"; + + if (defined ($param)) { + foreach my $parname (keys %$param) { + if (defined($param->{$parname})) { + $url .= "&$parname=".$param->{$parname}; + } + } + } + + # Ping tablet device + FULLY_Ping ($hash, $ping) if ($doping && $ping > 0); + + my $i = 0; + while ($i <= $repeatCommand && (!defined ($response) || $response eq '')) { + $response = GetFileFromURL ("$url&password=".$hash->{fully}{password}.'&type=json', $timeout); + FULLY_Log ($hash, 4, "HTTP response empty") if (defined($response) && $response eq ''); + $i++; + } + + my $result = eval { decode_json ($response) }; + FULLY_Log ($hash, 2, "Error in JSON data") if (!defined($result)); + + return $result; } ###################################################################### @@ -615,15 +605,15 @@ sub FULLY_Execute ($$$$) 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)); + my ($text) = @_; + + while ($text =~ /\[(.+):(.+)\]/) { + my ($device, $reading) = ($1, $2); + my $value = ReadingsVal ($device, $reading, ''); + $text =~ s/\[$device:$reading\]/$value/g; + } + + return (urlEncode ($text)); } ###################################################################### @@ -632,50 +622,50 @@ sub FULLY_SubstDeviceReading ($) sub FULLY_ExecuteNB ($$$$) { - my ($hash, $command, $param, $doping) = @_; - my $name = $hash->{NAME}; + my ($hash, $command, $param, $doping) = @_; + my $name = $hash->{NAME}; - # Get attributes - my $timeout = AttrVal ($name, 'requestTimeout', $FULLY_TIMEOUT); - my $repeatCommand = minNum (AttrVal ($name, 'repeatCommand', 0), 2); - my $ping = minNum (AttrVal ($name, 'pingBeforeCmd', 0), 2); + # Get attributes + my $timeout = AttrVal ($name, 'requestTimeout', $FULLY_TIMEOUT); + my $repeatCommand = minNum (AttrVal ($name, 'repeatCommand', 0), 2); + my $ping = minNum (AttrVal ($name, 'pingBeforeCmd', 0), 2); - my @urllist; - my $nc = scalar (@$command); - for (my $i=0; $i<$nc; $i++) { - my $url = $hash->{prot}.'://'.$hash->{host}.':'.$hash->{port}."/?cmd=".$$command[$i]; - - if (defined ($param) && defined ($$param[$i])) { - foreach my $parname (keys %{$$param[$i]}) { - if (defined ($$param[$i]->{$parname})) { - $url .= "&$parname=".$$param[$i]->{$parname}; - } - } - } - - FULLY_Log ($hash, 4, "Pushing $url on command stack"); - push (@urllist, "$url&password=".$hash->{fully}{password}."&type=json"); - } + my @urllist; + my $nc = scalar (@$command); + for (my $i=0; $i<$nc; $i++) { + my $url = $hash->{prot}.'://'.$hash->{host}.':'.$hash->{port}."/?cmd=".$$command[$i]; - # Ping tablet device - FULLY_Ping ($hash, $ping) if ($doping && $ping > 0); - - my $reqpar = { - url => $urllist[0], - orgurl => $urllist[0], - urllist => [@urllist], - timeout => $timeout, - method => "GET", - hash => $hash, - cmdno => 1, - cmdcnt => $nc, - repeat => $repeatCommand, - execcnt => 0, - callback => \&FULLY_ExecuteCB - }; + if (defined ($param) && defined ($$param[$i])) { + foreach my $parname (keys %{$$param[$i]}) { + if (defined ($$param[$i]->{$parname})) { + $url .= "&$parname=".$$param[$i]->{$parname}; + } + } + } - FULLY_Log ($hash, 4, "Executing command ".$urllist[0]); - HttpUtils_NonblockingGet ($reqpar); + FULLY_Log ($hash, 4, "Pushing $url on command stack"); + push (@urllist, "$url&password=".$hash->{fully}{password}."&type=json"); + } + + # Ping tablet device + FULLY_Ping ($hash, $ping) if ($doping && $ping > 0); + + my $reqpar = { + url => $urllist[0], + orgurl => $urllist[0], + urllist => [@urllist], + timeout => $timeout, + method => "GET", + hash => $hash, + cmdno => 1, + cmdcnt => $nc, + repeat => $repeatCommand, + execcnt => 0, + callback => \&FULLY_ExecuteCB + }; + + FULLY_Log ($hash, 4, "Executing command ".$urllist[0]); + HttpUtils_NonblockingGet ($reqpar); } ###################################################################### @@ -684,99 +674,99 @@ sub FULLY_ExecuteNB ($$$$) sub FULLY_ExecuteCB ($$$) { - my ($param, $err, $data) = @_; - my $hash = $param->{hash}; - my $name = $hash->{NAME}; + my ($param, $err, $data) = @_; + my $hash = $param->{hash}; + my $name = $hash->{NAME}; - if ($err eq '') { - # Process response - FULLY_Log ($hash, 5, $data, 0); - my $result = eval { decode_json ($data) }; - if (!defined($result)) { - FULLY_Log ($hash, 2, "Error in JSON data"); - return; - } - - if (!exists($result->{status})) { - $result->{status} = 'OK'; - $result->{statustext} //= 'N/A'; - } - $result->{execstate} = $result->{status}; - if (exists($result->{statustext})) { - $result->{statustext} =~ s/password=[^&]+//; - $result->{execstate} .= " $result->{statustext}"; - } - 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 ($err eq '') { + # Process response + FULLY_Log ($hash, 5, $data, 0); + my $result = eval { decode_json ($data) }; + if (!defined($result)) { + FULLY_Log ($hash, 2, "Error in JSON data"); + return; + } - if ($param->{cmdno} < $param->{cmdcnt}) { - # Execute next request - my @urllist = @{$param->{urllist}}; - my $reqpar = { - url => $urllist[$param->{cmdno}], - orgurl => $urllist[$param->{cmdno}], - urllist => $param->{urllist}, - timeout => $param->{timeout}, - method => "GET", - hash => $hash, - cmdno => $param->{cmdno}+1, - cmdcnt => $param->{cmdcnt}, - repeat => $param->{repeat}, - execcnt => 0, - callback => \&FULLY_ExecuteCB - }; + if (!exists($result->{status})) { + $result->{status} = 'OK'; + $result->{statustext} //= 'N/A'; + } + $result->{execstate} = $result->{status}; + if (exists($result->{statustext})) { + $result->{statustext} =~ s/password=[^&]+//; + $result->{execstate} .= " $result->{statustext}"; + } + else { + $result->{statustext} = 'N/A'; + } + FULLY_UpdateReadings ($hash, $result); - FULLY_Log ($hash, 4, "Executing command ".$urllist[$param->{cmdno}]); - HttpUtils_NonblockingGet ($reqpar); - } - else { - FULLY_Log ($hash, 4, 'Last command executed.'); - return; - } - } - else { - # Repeat failed request - if ($param->{execcnt} < $param->{repeat}) { - my $reqpar = { - url => $param->{orgurl}, - orgurl => $param->{orgurl}, - urllist => $param->{urllist}, - timeout => $param->{timeout}, - method => "GET", - hash => $hash, - cmdno => $param->{cmdno}, - cmdcnt => $param->{cmdcnt}, - repeat => $param->{repeat}, - execcnt => $param->{execcnt}+1, - callback => \&FULLY_ExecuteCB - }; + 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}"); + } - FULLY_Log ($hash, 4, "Repeating command ".$param->{orgurl}); - HttpUtils_NonblockingGet ($reqpar); - } - else { - if ($err =~ /^empty answer/) { - $err .= ' (probable reason: timeout)'; - } - FULLY_UpdateReadings ($hash, { - "status" => "Error", - "statustext" => "$err", - "execstate" => "Error $err" - }); - FULLY_Log ($hash, 2, "Error during request $param->{orgurl}. $err"); - } - } + if ($param->{cmdno} < $param->{cmdcnt}) { + # Execute next request + my @urllist = @{$param->{urllist}}; + my $reqpar = { + url => $urllist[$param->{cmdno}], + orgurl => $urllist[$param->{cmdno}], + urllist => $param->{urllist}, + timeout => $param->{timeout}, + method => "GET", + hash => $hash, + cmdno => $param->{cmdno}+1, + cmdcnt => $param->{cmdcnt}, + repeat => $param->{repeat}, + execcnt => 0, + callback => \&FULLY_ExecuteCB + }; + + FULLY_Log ($hash, 4, "Executing command ".$urllist[$param->{cmdno}]); + HttpUtils_NonblockingGet ($reqpar); + } + else { + FULLY_Log ($hash, 4, 'Last command executed.'); + return; + } + } + else { + # Repeat failed request + if ($param->{execcnt} < $param->{repeat}) { + my $reqpar = { + url => $param->{orgurl}, + orgurl => $param->{orgurl}, + urllist => $param->{urllist}, + timeout => $param->{timeout}, + method => "GET", + hash => $hash, + cmdno => $param->{cmdno}, + cmdcnt => $param->{cmdcnt}, + repeat => $param->{repeat}, + execcnt => $param->{execcnt}+1, + callback => \&FULLY_ExecuteCB + }; + + FULLY_Log ($hash, 4, "Repeating command ".$param->{orgurl}); + HttpUtils_NonblockingGet ($reqpar); + } + else { + if ($err =~ /^empty answer/) { + $err .= ' (probable reason: timeout)'; + } + FULLY_UpdateReadings ($hash, { + "status" => "Error", + "statustext" => "$err", + "execstate" => "Error $err" + }); + FULLY_Log ($hash, 2, "Error during request $param->{orgurl}. $err"); + } + } } ###################################################################### @@ -785,12 +775,12 @@ sub FULLY_ExecuteCB ($$$) sub FULLY_ScreenOff ($) { - my ($hash) = @_; - - my @c = ("setBooleanSetting", "screenOff"); - my @p = ({ "key" => "keepScreenOn", "value" => "false" }, undef); - FULLY_ExecuteNB ($hash, \@c, \@p, 1); - $hash->{onForTimer} = 'off'; + my ($hash) = @_; + + my @c = ("setBooleanSetting", "screenOff"); + my @p = ({ "key" => "keepScreenOn", "value" => "false" }, undef); + FULLY_ExecuteNB ($hash, \@c, \@p, 1); + $hash->{onForTimer} = 'off'; } ###################################################################### @@ -799,12 +789,12 @@ sub FULLY_ScreenOff ($) sub FULLY_UpdateDeviceInfo ($) { - my ($hash) = @_; + my ($hash) = @_; - return if (AttrVal ($hash->{NAME}, 'disable', 0) == 1); - - FULLY_ExecuteNB ($hash, ['deviceInfo'], undef, 1); - FULLY_SetPolling ($hash, 1); + return if (AttrVal ($hash->{NAME}, 'disable', 0) == 1); + + FULLY_ExecuteNB ($hash, ['deviceInfo'], undef, 1); + FULLY_SetPolling ($hash, 1); } ###################################################################### @@ -813,10 +803,10 @@ sub FULLY_UpdateDeviceInfo ($) sub FULLY_GetDeviceInfo ($) { - my ($name) = @_; - my $hash = $defs{$name}; - - FULLY_ExecuteNB ($hash, ['deviceInfo'], undef, 1); + my ($name) = @_; + my $hash = $defs{$name}; + + FULLY_ExecuteNB ($hash, ['deviceInfo'], undef, 1); } ###################################################################### @@ -825,62 +815,62 @@ sub FULLY_GetDeviceInfo ($) sub FULLY_UpdateReadings ($$) { - my ($hash, $result) = @_; - - my %readings = ( - 'isdeviceadmin' => 'bool', - 'isdeviceowner' => 'bool', - 'isindaydream' => 'bool', - 'isinforcedsleep' => 'bool', - 'isinscreensaver' => 'bool', - 'islicensed' => 'bool', - 'ismenuopen' => 'bool', - '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); - foreach my $rn (keys %$result) { - my $key = lc($rn); - next if (exists($readings{$key}) && $readings{$key} eq 'ignore'); - if (ref($result->{$rn}) eq 'ARRAY') { - if ($key eq 'sensorinfo') { - foreach my $e (@{$result->{$rn}}) { - $key = lc($e->{name}); - $key =~ s/ /_/g; - my $rv = ref($e->{values}) eq 'ARRAY' ? join(',', @{$e->{values}}) : $e->{values}; - readingsBulkUpdate ($hash, $key, $rv); - } - } - } - else { - readingsBulkUpdate ($hash, $key, exists($readings{$key}) && $readings{$key} eq 'bool' ? - ($result->{$rn} eq '0' ? 'no' : 'yes') : $result->{$rn}); - } - } - - my $screenOn = $result->{isScreenOn} // $result->{screenOn}; - if (defined($screenOn)) { - readingsBulkUpdate ($hash, 'state', $screenOn eq '0' ? 'off' : 'on'); - } - 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); + my ($hash, $result) = @_; + + my %readings = ( + 'isdeviceadmin' => 'bool', + 'isdeviceowner' => 'bool', + 'isindaydream' => 'bool', + 'isinforcedsleep' => 'bool', + 'isinscreensaver' => 'bool', + 'islicensed' => 'bool', + 'ismenuopen' => 'bool', + '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); + foreach my $rn (keys %$result) { + my $key = lc($rn); + next if (exists($readings{$key}) && $readings{$key} eq 'ignore'); + if (ref($result->{$rn}) eq 'ARRAY') { + if ($key eq 'sensorinfo') { + foreach my $e (@{$result->{$rn}}) { + $key = lc($e->{name}); + $key =~ s/ /_/g; + my $rv = ref($e->{values}) eq 'ARRAY' ? join(',', @{$e->{values}}) : $e->{values}; + readingsBulkUpdate ($hash, $key, $rv); + } + } + } + else { + readingsBulkUpdate ($hash, $key, exists($readings{$key}) && $readings{$key} eq 'bool' ? + ($result->{$rn} eq '0' ? 'no' : 'yes') : $result->{$rn}); + } + } + + my $screenOn = $result->{isScreenOn} // $result->{screenOn}; + if (defined($screenOn)) { + readingsBulkUpdate ($hash, 'state', $screenOn eq '0' ? 'off' : 'on'); + } + 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); } ###################################################################### @@ -889,23 +879,23 @@ sub FULLY_UpdateReadings ($$) 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)); - } + my ($istr) = @_; + my $ostr = ''; - return $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; } ###################################################################### @@ -914,23 +904,23 @@ sub FULLY_Encrypt ($) sub FULLY_Decrypt ($) { - my ($istr) = @_; - my $ostr = ''; + my ($istr) = @_; + my $ostr = ''; - my $id = getUniqueId() // ''; - return '' if ($id eq ''); + 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)); - } + 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; + return $ostr; } ###################################################################### @@ -941,228 +931,263 @@ sub FULLY_Decrypt ($) sub FULLY_Ping ($$) { - my ($hash, $count) = @_; - my $name = $hash->{NAME}; - my $host = $hash->{host}; - my $temp; + my ($hash, $count) = @_; + my $name = $hash->{NAME}; + my $host = $hash->{host}; + my $temp; - my $waitAfterPing = minNum (AttrVal ($name, 'waitAfterPing', 0), 2); - - my $os = $^O; - FULLY_Log ($hash, 4, "Sending $count ping request(s) to tablet $host. OS=$os"); - - if ($^O =~ m/(Win|cygwin)/) { - $temp = qx(ping -n $count -4 $host >nul); - } - elsif ($^O =~ m/solaris/) { - $temp = qx(ping $host $count 2>&1 >/dev/null); - } - elsif ($^O =~ m/darwin/) { - $temp = qx(ping -c $count -t 1 $host 2>&1 >/dev/null); - } - else { - $temp = qx(ping -c $count -W 1 $host 2>&1 >/dev/null); - } - - sleep ($waitAfterPing) if ($waitAfterPing > 0); - - return $temp; + my $waitAfterPing = minNum (AttrVal ($name, 'waitAfterPing', 0), 2); + + my $os = $^O; + FULLY_Log ($hash, 4, "Sending $count ping request(s) to tablet $host. OS=$os"); + + if ($^O =~ m/(Win|cygwin)/) { + $temp = qx(ping -n $count -4 $host >nul); + } + elsif ($^O =~ m/solaris/) { + $temp = qx(ping $host $count 2>&1 >/dev/null); + } + elsif ($^O =~ m/darwin/) { + $temp = qx(ping -c $count -t 1 $host 2>&1 >/dev/null); + } + else { + $temp = qx(ping -c $count -W 1 $host 2>&1 >/dev/null); + } + + sleep ($waitAfterPing) if ($waitAfterPing > 0); + + return $temp; } 1; +__END__ =pod =item device =item summary FULLY Browser Integration =begin html - +

FULLY

    Module for controlling of Fully browser on Android tablets. Requires a Plus license of Fully browser app. Remote device management and remote admin in local network must be enabled in Fully app. Requires Fully app version 1.27 or later.

    - - + + Define

      define <name> FULLY [<Protocol>://]<HostOrIP>[:<Port>] [<password>] [<poll-interval>]

      - The parameter password is the password set in Fully browser. Parameter Protocol is - optional. Valid protocols are 'http' and 'https'. Default protocol is 'http'. - Default Port is 2323.
      - 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'. + The parameter password is the password set in Fully browser. Parameter Protocol is + optional. Valid protocols are 'http' and 'https'. Default protocol is 'http'. + Default Port is 2323.
      + 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'.

    - - + + Set

      -
    • set <name> authentication [<password>]
      - Set Fully password. This password is used for each Fully opteration. - If no password is specified, the current password is deleted. -

    • -
    • set <name> brightness 0-255
      - Adjust screen brightness. -

    • -
    • set <name> clearCache
      - Clear browser cache. -

    • -
    • set <name> clearCookies
      - Clear cookies. -

    • -
    • set <name> clearWebstorage
      - Clear web storage. -

    • -
    • set <name> exit
      - Terminate Fully. -

    • -
    • set <name> foreground
      - Bring fully app to foreground. -

    • -
    • set <name> lockKiosk
      - Lock kiosk mode. -

    • -
    • set <name> motionDetection { on | off }
      - Turn motion detection by camera on or off. -

    • -
    • set <name> { lock | unlock }
      - Lock or unlock display. -

    • -
    • set <name> { on | off }
      - Turn tablet display on or off. -

    • -
    • set <name> on-for-timer [{ <Seconds> | forever | off }]
      - Set timer for display. Default is forever. -

    • -
    • set <name> overlayMessage { text }
      - Show overlay message. Placeholders in format [device:reading] are supported and will - be substituted by the corresponding reading value. -

    • -
    • set <name> photo
      - Take a picture with device cam. Setting motion detection must be enabled. Picture - can be viewed in remote admin interface under device info. -

    • -
    • set <name> playSound <url> [loop]
      - Play sound from URL. -

    • -
    • set <name> playVideo <url> [loop] [showControls] [exitOnTouch] [exitOnCompletion]
      - Play video from URL. -

    • -
    • set <name> restart
      - Restart Fully. -

    • -
    • set <name> screenOffTimer <seconds>
      - Turn screen off after some idle seconds, set to 0 to disable timer. -

    • -
    • set <name> screenSaver { start | stop }
      - Start or stop screen saver. Screen saver URL can be set with command set screenSaverURL. -

    • -
    • set <name> screenSaverTimer <seconds>
      - Show screen saver URL after some idle seconds, set to 0 to disable timer. -

    • -
    • set <name> screenSaverURL <URL>
      - Show this URL when screensaver starts, set daydream: for Android daydream or dim: for black.
      -

    • -
    • set <name> setBooleanSetting <Key> <Value>
      - Set boolean value in Fully app. Command is ony available if attribute expert is 1. - Valid keys can be found in Fully remote admin interface. -

    • -
    • set <name> setStringSetting <Key> <Value>
      - Set string value in Fully app. Command is ony available if attribute expert is 1. - Valid keys can be found in Fully remote admin interface. -

    • -
    • set <name> speak <text>
      - Audio output of text. If text contains blanks it must be enclosed - in double quotes. The text can contain device readings in format [device:reading]. -

    • -
    • set <name> startApp <PackageName>
      - Start an app. App must be installed on the tablet and package name (not appname!) - must be specified. -

    • -
    • set <name> startURL <URL>
      - Show this URL when FULLY starts.
      -

    • -
    • set <name> stopSound
      - Stop playback of sound if playback has been started with option loop. -

    • -
    • set <name> stopVideo
      - Stop playback of video if playback has been started with option loop. -

    • -
    • set <name> unlockKiosk
      - Unlock kiosk mode. -

    • -
    • set <name> url [<URL>]
      - Navigate to URL. If no URL is specified navigate to start URL. -

    • -
    • set <name> volume <level> <stream>
      - Set audio volume. Range of parameter level is 0-100, range of parameter - stream is 1-10. -

    • + +
    • set <name> authentication [<password>]
      + Set Fully password. This password is used for each Fully opteration. + If no password is specified, the current password is deleted. +

    • + +
    • set <name> brightness 0-255
      + Adjust screen brightness. +

    • + +
    • set <name> clearCache
      + Clear browser cache. +

    • + +
    • set <name> clearCookies
      + Clear cookies. +

    • + +
    • set <name> clearWebstorage
      + Clear web storage. +

    • + +
    • set <name> exit
      + Terminate Fully. +

    • + +
    • set <name> foreground
      + Bring fully app to foreground. +

    • + +
    • set <name> lockKiosk
      + Lock kiosk mode. +

    • + +
    • set <name> motionDetection { on | off }
      + Turn motion detection by camera on or off. +

    • + +
    • set <name> { lock | unlock }
      + Lock or unlock display. +

    • + +
    • set <name> { on | off }
      + Turn tablet display on or off. +

    • + +
    • set <name> on-for-timer [{ <Seconds> | forever | off }]
      + Set timer for display. Default is forever. +

    • + +
    • set <name> overlayMessage { text }
      + Show overlay message. Placeholders in format [device:reading] are supported and will + be substituted by the corresponding reading value. +

    • + +
    • set <name> photo
      + Take a picture with device cam. Setting motion detection must be enabled. Picture + can be viewed in remote admin interface under device info. +

    • + +
    • set <name> playSound <url> [loop]
      + Play sound from URL. +

    • + +
    • set <name> playVideo <url> [loop] [showControls] [exitOnTouch] [exitOnCompletion]
      + Play video from URL. +

    • + +
    • set <name> restart
      + Restart Fully. +

    • + +
    • set <name> screenOffTimer <seconds>
      + Turn screen off after some idle seconds, set to 0 to disable timer. +

    • + +
    • set <name> screenSaver { start | stop }
      + Start or stop screen saver. Screen saver URL can be set with command set screenSaverURL. +

    • + +
    • set <name> screenSaverTimer <seconds>
      + Show screen saver URL after some idle seconds, set to 0 to disable timer. +

    • + +
    • set <name> screenSaverURL <URL>
      + Show this URL when screensaver starts, set daydream: for Android daydream or dim: for black.
      +

    • + +
    • set <name> setBooleanSetting <Key> <Value>
      + Set boolean value in Fully app. Command is ony available if attribute expert is 1. + Valid keys can be found in Fully remote admin interface. +

    • + +
    • set <name> setStringSetting <Key> <Value>
      + Set string value in Fully app. Command is ony available if attribute expert is 1. + Valid keys can be found in Fully remote admin interface. +

    • + +
    • set <name> speak <text>
      + Audio output of text. If text contains blanks it must be enclosed + in double quotes. The text can contain device readings in format [device:reading]. +

    • + +
    • set <name> startApp <PackageName>
      + Start an app. App must be installed on the tablet and package name (not appname!) + must be specified. +

    • + +
    • set <name> startURL <URL>
      + Show this URL when FULLY starts.
      +

    • + +
    • set <name> stopSound
      + Stop playback of sound if playback has been started with option loop. +

    • + +
    • set <name> stopVideo
      + Stop playback of video if playback has been started with option loop. +

    • + +
    • set <name> unlockKiosk
      + Unlock kiosk mode. +

    • + +
    • set <name> url [<URL>]
      + Navigate to URL. If no URL is specified navigate to start URL. +

    • + +
    • set <name> volume <level> <stream>
      + Set audio volume. Range of parameter level is 0-100, range of parameter + stream is 1-10. +


    - - + + Get

      +
    • get <name> info
      - Display Fully information. This is command blocks FHEM until completion. + Display Fully information. This is command blocks FHEM until completion.

    • +
    • get <name> stats
      - Show Fully statistics. Will be implemented later. + Show Fully statistics. Will be implemented later.

    • +
    • get <name> update
      - Update readings. + Update readings.


    - - + + Attributes

      - +
    • disable <0 | 1>
      - Disable device and automatic polling. + Disable device and automatic polling.

    • - +
    • expert <0 | 1>
      - Activate expert mode. + Activate expert mode.

    • - -
    • pingBeforeCmd <Count>
      - Send Count ping request to tablet before executing commands. Valid values - for Count are 0,1,2. Default is 0 (do not send ping request). -

    • - + +
    • pingBeforeCmd <Count>
      + Send Count ping request to tablet before executing commands. Valid values + for Count are 0,1,2. Default is 0 (do not send ping request). +

    • +
    • pollInterval <seconds>
      Set polling interval for FULLY device information. If seconds is 0 polling is turned off. Valid values are from 10 to 86400 seconds.

    • - +
    • repeatCommand <Count>
      Repeat fully command on failure. Valid values for Count are 0,1,2. Default is 0 (do not repeat commands).

    • - +
    • requestTimeout <seconds>
      Set timeout for http requests. Default is 5 seconds. Increase this value if commands are failing with a timeout error.

    • - +
    • updateAfterCommand <0 | 1>
      - When set to 1 update readings after a set command. Default is 0. + When set to 1 update readings after a set command. Default is 0.

    • - +
    • waitAfterPing <Seconds>
      - Wait specified amount of time after sending ping request to tablet device. Valid - values for Seconds are 0,1,2. Default is 0 (do not wait). Only used if - attribute pingBeforeCmd is greater than 0. + Wait specified amount of time after sending ping request to tablet device. Valid + values for Seconds are 0,1,2. Default is 0 (do not wait). Only used if + attribute pingBeforeCmd is greater than 0.

diff --git a/fhem/MAINTAINER.txt b/fhem/MAINTAINER.txt index dfd1ff104..405f077d4 100644 --- a/fhem/MAINTAINER.txt +++ b/fhem/MAINTAINER.txt @@ -455,7 +455,7 @@ FHEM/88_xs1Dev.pm HomeAuto_User Sonstige Systeme (Link als PM FHEM/89_AndroidDB.pm zap Multimedia FHEM/89_AndroidDBHost.pm zap Multimedia FHEM/89_ESPEInk.pm eki Sonstige Systeme -FHEM/89_FULLY.pm zap Frontends +FHEM/89_FULLY.pm Beta-User Frontends https://forum.fhem.de/index.php?topic=143143.0 FHEM/89_HEATRONIC.pm heikoranft Sonstige Systeme FHEM/89_VCLIENT.pm andies Heizungssteuerung/Raumklima FHEM/89_VCONTROL.pm adamwit Heizungssteuerung/Raumklima