2
0
mirror of https://github.com/fhem/fhem-mirror.git synced 2025-01-31 06:39:11 +00:00

70_BOTVAC.pm: Optimize error message handling; Renew accessToken if necessary; PBP issues

git-svn-id: https://svn.fhem.de/fhem/trunk@21819 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
vuffiraa 2020-04-30 17:29:42 +00:00
parent 94a10ab42d
commit c7c714a945
2 changed files with 203 additions and 146 deletions

View File

@ -1,5 +1,8 @@
# 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.
- change: 70_BOTVAC: Optimze error message handling
Renew accessToken if necessary
PBP issues
- change: 57_Calendar: reactivated random delay for calendar updates on start
- bugfix 98_todoist: Forum: #1048705
- bugfix: 74_GardenaSmartDevice: setter from mower not visible

View File

@ -24,32 +24,7 @@
#
##############################################################################
package main;
use strict;
use warnings;
sub BOTVAC_Initialize($) {
my ($hash) = @_;
our $readingFnAttributes;
$hash->{DefFn} = "BOTVAC::Define";
$hash->{GetFn} = "BOTVAC::Get";
$hash->{SetFn} = "BOTVAC::Set";
$hash->{UndefFn} = "BOTVAC::Undefine";
$hash->{DeleteFn} = "BOTVAC::Delete";
$hash->{ReadFn} = "BOTVAC::wsRead";
$hash->{ReadyFn} = "BOTVAC::wsReady";
$hash->{AttrFn} = "BOTVAC::Attr";
$hash->{AttrList} = "disable:0,1 " .
"actionInterval " .
"boundaries:textField-long " .
"sslVerify:0,1 " .
$readingFnAttributes;
}
package BOTVAC;
package FHEM::BOTVAC;
use strict;
use warnings;
@ -58,40 +33,55 @@ use POSIX;
use GPUtils qw(:all); # wird für den Import der FHEM Funktionen aus der fhem.pl benötigt
use Time::HiRes qw(gettimeofday);
use Time::Local qw(timelocal);
use JSON qw(decode_json encode_json);
use Digest::SHA qw(hmac_sha256_hex sha1_hex);
use Encode qw(encode_utf8);
use MIME::Base64;
require "DevIo.pm";
require "HttpUtils.pm";
require DevIo;
require HttpUtils;
## Import der FHEM Funktionen
BEGIN {
GP_Import(qw(
AttrVal
createUniqueId
FmtDateTime
FmtDateTimeRFC1123
fhemTzOffset
getKeyValue
setKeyValue
getUniqueId
InternalTimer
InternalVal
readingsSingleUpdate
readingsBulkUpdate
readingsBulkUpdateIfChanged
readingsBeginUpdate
readingsDelete
readingsEndUpdate
ReadingsNum
ReadingsVal
RemoveInternalTimer
Log3
trim
))
};
AttrVal
CommandAttr
createUniqueId
fhemTzOffset
FmtDateTime
FmtDateTimeRFC1123
getKeyValue
getUniqueId
InternalTimer
InternalVal
Log3
readingFnAttributes
readingsBeginUpdate
readingsBulkUpdate
readingsBulkUpdateIfChanged
readingsDelete
readingsEndUpdate
ReadingsNum
readingsSingleUpdate
ReadingsVal
RemoveInternalTimer
setKeyValue
trim
));
}
GP_Export(
qw(
Initialize
)
);
my $useDigestMD5 = 0;
if ( eval { require Digest::MD5; 1 } ) {
$useDigestMD5 = 1;
Digest::MD5->import();
}
my %opcode = ( # Opcode interpretation of the ws "Payload data
'continuation' => 0x00,
@ -103,7 +93,28 @@ my %opcode = ( # Opcode interpretation of the ws "Payload data
);
###################################
sub Define($$) {
sub Initialize {
my ($hash) = @_;
$hash->{DefFn} = \&Define;
$hash->{GetFn} = \&Get;
$hash->{SetFn} = \&Set;
$hash->{UndefFn} = \&Undefine;
$hash->{DeleteFn} = \&Delete;
$hash->{ReadFn} = \&wsRead;
$hash->{ReadyFn} = \&wsReady;
$hash->{AttrFn} = \&Attr;
$hash->{AttrList} = "disable:0,1 " .
"actionInterval " .
"boundaries:textField-long " .
"sslVerify:0,1 " .
$readingFnAttributes;
return;
}
###################################
sub Define {
my ( $hash, $def ) = @_;
my @a = split( "[ \t][ \t]*", $def );
my $name = $hash->{NAME};
@ -147,22 +158,20 @@ sub Define($$) {
$hash->{VENDOR} = $vendor;
$hash->{INTERVAL} = $interval;
unless ( defined( AttrVal( $name, "webCmd", undef ) ) ) {
no warnings "once";
$::attr{$name}{webCmd} = 'startCleaning:stop:sendToBase';
}
CommandAttr($hash, "$name webCmd startCleaning:stop:sendToBase")
if (AttrVal( $name, 'webCmd', 'none' ) eq 'none');
# start the status update timer
RemoveInternalTimer($hash);
InternalTimer( gettimeofday() + 2, "BOTVAC::GetStatus", $hash, 1 );
InternalTimer( gettimeofday() + 2, \&GetStatus, $hash, 1 );
AddExtension($name, "BOTVAC::GetMap", "BOTVAC/$name/map");
AddExtension($name, \&GetMap, "BOTVAC/$name/map");
return;
}
#####################################
sub GetStatus($;$) {
sub GetStatus {
my ( $hash, $update ) = @_;
my $name = $hash->{NAME};
my $interval = $hash->{INTERVAL};
@ -171,10 +180,10 @@ sub GetStatus($;$) {
Log3($name, 5, "BOTVAC $name: called function GetStatus()");
# use actionInterval if state is busy or paused
$interval = AttrVal($name, "actionInterval", $interval) if (ReadingsVal($name, "stateId", "0") =~ /2|3/);
$interval = AttrVal($name, "actionInterval", $interval) if (ReadingsVal($name, "stateId", "0") =~ /2|3/x);
RemoveInternalTimer($hash);
InternalTimer( gettimeofday() + $interval, "BOTVAC::GetStatus", $hash, 0 );
InternalTimer( gettimeofday() + $interval, \&GetStatus, $hash, 0 );
return if ( AttrVal($name, "disable", 0) == 1 or ReadingsVal($name,"pollingMode",1) == 0);
@ -187,8 +196,8 @@ sub GetStatus($;$) {
push(@successor, ["dashboard", undef]) if ($secs <= $interval);
push(@successor, ["messages", "getSchedule"]);
push(@successor, ["messages", "getGeneralInfo"]) if (GetServiceVersion($hash, "generalInfo") =~ /.*-1/);
push(@successor, ["messages", "getPreferences"]) if (GetServiceVersion($hash, "preferences") ne "");
push(@successor, ["messages", "getGeneralInfo"]) if (GetServiceVersion($hash, "generalInfo") =~ /.*-1/x);
push(@successor, ["messages", "getPreferences"]) if (GetServiceVersion($hash, "preferences") ne '');
SendCommand($hash, "messages", "getRobotState", undef, @successor);
}
@ -201,7 +210,7 @@ sub GetStatus($;$) {
}
###################################
sub Get($@) {
sub Get {
my ( $hash, @a ) = @_;
my $name = $hash->{NAME};
my $what;
@ -230,7 +239,7 @@ sub Get($@) {
}
###################################
sub Set($@) {
sub Set {
my ( $hash, @a ) = @_;
my $name = $hash->{NAME};
@ -315,7 +324,7 @@ sub Set($@) {
my @names;
for (my $i = 0; $i < @Boundaries; $i++) {
my $name = $Boundaries[$i]->{name};
push @names,$name if (!(grep { $_ eq $name } @names) and ($name ne ""));
push @names,$name if (!(grep { $_ eq $name } @names) && ($name ne ""));
}
my $BoundariesList = @names ? "multiple-strict,".join(",", @names) : "textField";
$usage .= " setBoundariesOnFloorplan_0:".$BoundariesList if (ReadingsVal($name, "floorplan_0_id" ,"") ne "");
@ -574,7 +583,7 @@ sub Set($@) {
}
###################################
sub Undefine($$) {
sub Undefine {
my ( $hash, $arg ) = @_;
my $name = $hash->{NAME};
@ -589,7 +598,7 @@ sub Undefine($$) {
}
###################################
sub Delete($$) {
sub Delete {
my ( $hash, $arg ) = @_;
my $name = $hash->{NAME};
@ -602,7 +611,7 @@ sub Delete($$) {
}
###################################
sub Attr(@)
sub Attr
{
my ($cmd,$name,$attr_name,$attr_value) = @_;
my $hash = $::defs{$name};
@ -637,7 +646,7 @@ sub Attr(@)
############################################################################################################
#########################
sub AddExtension($$$) {
sub AddExtension {
my ( $name, $func, $link ) = @_;
my $url = "/$link";
@ -645,20 +654,24 @@ sub AddExtension($$$) {
$::data{FWEXT}{$url}{deviceName} = $name;
$::data{FWEXT}{$url}{FUNC} = $func;
$::data{FWEXT}{$url}{LINK} = $link;
return;
}
#########################
sub RemoveExtension($) {
sub RemoveExtension {
my ($link) = @_;
my $url = "/$link";
my $name = $::data{FWEXT}{$url}{deviceName};
Log3($name, 2, "Unregistering BOTVAC $name for URL $url...");
delete $::data{FWEXT}{$url};
return;
}
###################################
sub SendCommand($$;$$@) {
sub SendCommand {
my ( $hash, $service, $cmd, $option, @successor ) = @_;
my $name = $hash->{NAME};
my $email = $hash->{EMAIL};
@ -694,11 +707,11 @@ sub SendCommand($$;$$@) {
my $sslVerify= AttrVal($name, "sslVerify", undef);
if(defined($sslVerify)) {
eval "use IO::Socket::SSL";
eval {use IO::Socket::SSL};
if($@) {
Log3($name, 2, $@);
} else {
my $sslVerifyMode= eval("$sslVerify ? SSL_VERIFY_PEER : SSL_VERIFY_NONE");
my $sslVerifyMode= eval {$sslVerify ? SSL_VERIFY_PEER : SSL_VERIFY_NONE};
Log3($name, 5, "SSL verify mode set to $sslVerifyMode");
$sslArgs{SSL_verify_mode} = $sslVerifyMode;
}
@ -835,7 +848,7 @@ sub SendCommand($$;$$@) {
if ( defined($data) );
Log3($name, 5, "BOTVAC $name: GET $URL")
if ( !defined($data) );
Log3($name, 5, "BOTVAC $name: header ".join("\n", map(($_.': '.$header{$_}), keys %header)))
Log3($name, 5, "BOTVAC $name: header ".join("\n", map {$_.': '.$header{$_}} keys %header))
if ( %header );
my $params = {
@ -865,7 +878,7 @@ sub SendCommand($$;$$@) {
}
###################################
sub ReceiveCommand($$$) {
sub ReceiveCommand {
my ( $param, $err, $data ) = @_;
my $hash = $param->{hash};
my $name = $hash->{NAME};
@ -905,6 +918,9 @@ sub ReceiveCommand($$$) {
# stop pulling for current interval
Log3($name, 4, "BOTVAC $name: drop successors");
LogSuccessors($hash, @successor);
readingsEndUpdate( $hash, 1 );
return;
}
@ -921,11 +937,30 @@ sub ReceiveCommand($$$) {
if ( $data ne "" ) {
if ( $service eq "loadmap" ) {
# use $data later
} elsif ( $data =~ /^{"message":"Could not find robot_serial for specified vendor_name"}$/ ) {
} elsif ( $data eq '{"message":"Could not find robot_serial for specified vendor_name"}' ) {
# currently no data available
readingsBulkUpdateIfChanged($hash, "state", "Couldn't find robot");
readingsEndUpdate( $hash, 1 );
return;
} elsif ( $data eq '{"message":"Bad credentials"}' ||
$data eq '{"message":"Not allowed"}' ) {
if ( !defined($cmd) || $cmd eq "" ) {
Log3($name, 3, "BOTVAC $name: RES $service - $data");
} else {
Log3($name, 3, "BOTVAC $name: RES $service/$cmd - $data");
}
# remove invalid access token
readingsDelete($hash, ".accessToken");
readingsEndUpdate( $hash, 1 );
if ( $service ne "sessions") {
# put last command back into queue
unshift(@successor, [$service, $cmd]);
# send registration
SendCommand($hash, "sessions", undef, undef, @successor);
}
return;
} elsif ( $data =~ /^{/ || $data =~ /^\[/ ) {
if ( !defined($cmd) || $cmd eq "" ) {
Log3($name, 4, "BOTVAC $name: RES $service - $data");
@ -940,7 +975,8 @@ sub ReceiveCommand($$$) {
} else {
Log3($name, 5, "BOTVAC $name: RES ERROR $service/$cmd\n$data");
}
return undef;
readingsEndUpdate( $hash, 1 );
return;
}
}
@ -1294,7 +1330,7 @@ sub ReceiveCommand($$$) {
readingsEndUpdate( $hash, 1 );
if (defined($hash->{helper}{".HTTP_CONNECTION"}) and
(($keepalive and $closeConnection) or !@successor)) {
(($keepalive && $closeConnection) || !@successor)) {
Log3($name, 4, "BOTVAC $name: Close connection");
::HttpUtils_Close($hash->{helper}{".HTTP_CONNECTION"});
undef($hash->{helper}{".HTTP_CONNECTION"});
@ -1330,31 +1366,29 @@ sub ReceiveCommand($$$) {
return;
}
sub GetTimeFromString($) {
sub GetTimeFromString {
my ($timeStr) = @_;
eval {
use Time::Local;
if(defined($timeStr) and $timeStr =~ m/^(\d{4})-(\d{2})-(\d{2})T([0-2]\d):([0-5]\d):([0-5]\d)Z$/) {
my $time = timelocal($6, $5, $4, $3, $2 - 1, $1 - 1900);
return FmtDateTime($time + fhemTzOffset($time));
}
if(defined($timeStr) and $timeStr =~ m/^(\d{4})-(\d{2})-(\d{2})T([0-2]\d):([0-5]\d):([0-5]\d)Z$/x) {
my $time = timelocal($6, $5, $4, $3, $2 - 1, $1 - 1900);
return FmtDateTime($time + fhemTzOffset($time));
}
return;
}
sub GetSecondsFromString($) {
sub GetSecondsFromString {
my ($timeStr) = @_;
eval {
use Time::Local;
if(defined($timeStr) and $timeStr =~ m/^(\d{4})-(\d{2})-(\d{2})T([0-2]\d):([0-5]\d):([0-5]\d)Z$/) {
my $time = timelocal($6, $5, $4, $3, $2 - 1, $1 - 1900);
return $time;
}
if(defined($timeStr) and $timeStr =~ m/^(\d{4})-(\d{2})-(\d{2})T([0-2]\d):([0-5]\d):([0-5]\d)Z$/x) {
my $time = timelocal($6, $5, $4, $3, $2 - 1, $1 - 1900);
return $time;
}
return;
}
sub SetRobot($$) {
sub SetRobot {
my ( $hash, $robot ) = @_;
my $name = $hash->{NAME};
@ -1370,9 +1404,11 @@ sub SetRobot($$) {
readingsBulkUpdateIfChanged($hash, "macAddr", $robots[$robot]->{macAddr});
readingsBulkUpdateIfChanged($hash, "nucleoUrl", $robots[$robot]->{nucleoUrl});
readingsBulkUpdateIfChanged($hash, "robot", $robot);
return;
}
sub GetCleaningParameter($$$) {
sub GetCleaningParameter {
my ($hash, $param, $default) = @_;
my $name = $hash->{NAME};
@ -1380,7 +1416,7 @@ sub GetCleaningParameter($$$) {
return ReadingsVal($name, $nextReading, ReadingsVal($name, $param, $default));
}
sub GetServiceVersion($$) {
sub GetServiceVersion {
my ($hash, $service) = @_;
my $name = $hash->{NAME};
@ -1396,16 +1432,18 @@ sub SetServices {
my $name = $hash->{NAME};
my $serviceList = join(", ", map { "$_:$services->{$_}" } keys %$services);
$hash->{SERVICES} = $serviceList if (!defined($hash->{SERVICES}) or $hash->{SERVICES} ne $serviceList);
$hash->{SERVICES} = $serviceList if (!defined($hash->{SERVICES}) || $hash->{SERVICES} ne $serviceList);
return;
}
sub StorePassword($$) {
sub StorePassword {
my ($hash, $password) = @_;
my $index = $hash->{TYPE}."_".$hash->{NAME}."_passwd";
my $key = getUniqueId().$index;
my $enc_pwd = "";
if(eval "use Digest::MD5;1") {
if($useDigestMD5) {
$key = Digest::MD5::md5_hex(unpack "H*", $key);
$key .= Digest::MD5::md5_hex($key);
}
@ -1422,7 +1460,7 @@ sub StorePassword($$) {
return "password successfully saved";
}
sub ReadPassword($) {
sub ReadPassword {
my ($hash) = @_;
my $name = $hash->{NAME};
my $index = $hash->{TYPE}."_".$hash->{NAME}."_passwd";
@ -1435,16 +1473,16 @@ sub ReadPassword($) {
if ( defined($err) ) {
Log3($name, 3, "BOTVAC $name: unable to read password from file: $err");
return undef;
return;
}
if ( defined($password) ) {
if ( eval "use Digest::MD5;1" ) {
if($useDigestMD5) {
$key = Digest::MD5::md5_hex(unpack "H*", $key);
$key .= Digest::MD5::md5_hex($key);
}
my $dec_pwd = '';
for my $char (map { pack('C', hex($_)) } ($password =~ /(..)/g)) {
for my $char (map { pack('C', hex($_)) } ($password =~ /(..)/gx)) {
my $decode=chop($key);
$dec_pwd.=chr(ord($char)^ord($decode));
$key=$decode.$key;
@ -1452,11 +1490,11 @@ sub ReadPassword($) {
return $dec_pwd;
} else {
Log3($name, 3, "BOTVAC $name: No password in file");
return undef;
return;
}
}
sub CheckRegistration($$$$$) {
sub CheckRegistration {
my ( $hash, $service, $cmd, $option, @successor ) = @_;
my $name = $hash->{NAME};
@ -1482,7 +1520,7 @@ sub CheckRegistration($$$$$) {
return;
}
sub GetBoolean($) {
sub GetBoolean {
my ($value) = @_;
my $booleans = {
'0' => "0",
@ -1498,7 +1536,7 @@ sub GetBoolean($) {
}
}
sub SetBoolean($) {
sub SetBoolean {
my ($value) = @_;
my $booleans = {
'0' => "false",
@ -1514,7 +1552,7 @@ sub SetBoolean($) {
}
}
sub BuildState($$$$) {
sub BuildState {
my ($hash,$state,$action,$error) = @_;
my $states = {
'0' => "Invalid",
@ -1539,7 +1577,7 @@ sub BuildState($$$$) {
}
}
sub GetActionText($) {
sub GetActionText {
my ($action) = @_;
my $actions = {
'0' => "Invalid",
@ -1567,7 +1605,7 @@ sub GetActionText($) {
}
}
sub GetErrorText($) {
sub GetErrorText {
my ($error) = @_;
my $errors = {
'ui_alert_invalid' => 'Ok',
@ -1589,7 +1627,7 @@ sub GetErrorText($) {
}
}
sub GetDayText($) {
sub GetDayText {
my ($day) = @_;
my $days = {
'0' => "Sunday",
@ -1608,7 +1646,7 @@ sub GetDayText($) {
}
}
sub GetCategoryText($) {
sub GetCategoryText {
my ($category) = @_;
my $categories = {
'1' => 'manual',
@ -1624,7 +1662,7 @@ sub GetCategoryText($) {
}
}
sub GetModeText($) {
sub GetModeText {
my ($mode) = @_;
my $modes = {
'1' => 'eco',
@ -1638,7 +1676,7 @@ sub GetModeText($) {
}
}
sub GetModifierText($) {
sub GetModifierText {
my ($modifier) = @_;
my $modifiers = {
'1' => 'normal',
@ -1652,7 +1690,7 @@ sub GetModifierText($) {
}
}
sub GetNavigationModeText($) {
sub GetNavigationModeText {
my ($navMode) = @_;
my $navModes = {
'1' => 'normal',
@ -1667,7 +1705,7 @@ sub GetNavigationModeText($) {
}
}
sub GetAuthStatusText($) {
sub GetAuthStatusText {
my ($authStatus) = @_;
my $authStatusHash = {
'0' => 'not supported',
@ -1682,7 +1720,7 @@ sub GetAuthStatusText($) {
}
}
sub GetBeehiveHost($) {
sub GetBeehiveHost {
my ($vendor) = @_;
my $vendors = {
'neato' => 'beehive.neatocloud.com',
@ -1696,7 +1734,7 @@ sub GetBeehiveHost($) {
}
}
sub GetNucleoHost($) {
sub GetNucleoHost {
my ($vendor) = @_;
my $vendors = {
'neato' => 'nucleo.neatocloud.com',
@ -1710,12 +1748,12 @@ sub GetNucleoHost($) {
}
}
sub GetValidityEnd($) {
sub GetValidityEnd {
my ($validFor) = @_;
return ($validFor =~ /\d+/ ? FmtDateTime(time() + $validFor) : $validFor);
}
sub LogSuccessors($@) {
sub LogSuccessors {
my ($hash,@successor) = @_;
my $name = $hash->{NAME};
@ -1727,9 +1765,11 @@ sub LogSuccessors($@) {
$msg .= join(",", map { defined($_) ? $_ : '' } @succ_item);
}
Log3($name, 4, $msg) if (@successor > 0);
return;
}
sub ShowMap($;$$) {
sub ShowMap {
my ($name,$width,$height) = @_;
my $img = '<img src="/fhem/BOTVAC/'.$name.'/map"';
@ -1755,17 +1795,17 @@ sub GetMap() {
}
sub ShowStatistics($) {
sub ShowStatistics {
my ($name) = @_;
my $hash = $::defs{$name};
return "maps for statistics are not available yet"
if (!defined($hash->{helper}{MAPS}) or @{$hash->{helper}{MAPS}} == 0);
if (!defined($hash->{helper}{MAPS}) || @{$hash->{helper}{MAPS}} == 0);
return GetStatistics($hash);
}
sub GetStatistics($) {
sub GetStatistics {
my($hash) = @_;
my $name = $hash->{NAME};
my $mapcount = @{$hash->{helper}{MAPS}};
@ -1866,7 +1906,7 @@ sub GetStatistics($) {
#######################################
# Websocket Functions
#######################################
sub wsOpen($$$) {
sub wsOpen {
my ($hash,$ip_address,$port) = @_;
my $name = $hash->{NAME};
@ -1875,16 +1915,18 @@ sub wsOpen($$$) {
::DevIo_CloseDev($hash) if(::DevIo_IsOpen($hash));
if (::DevIo_OpenDev($hash, 0, "BOTVAC::wsHandshake")) {
if (::DevIo_OpenDev($hash, 0, \&wsHandshake)) {
Log3($name, 2, "BOTVAC(ws) $name: ERROR: Can't open websocket to $hash->{DeviceName}");
readingsSingleUpdate($hash,'result','ws_connect_error',1);
readingsSingleUpdate($hash,'result','ws_ko',1);
} else {
readingsSingleUpdate($hash,'result','ws_ok',1);
}
return;
}
sub wsClose($) {
sub wsClose {
my $hash = shift;
my $name = $hash->{NAME};
my $normal_closure = pack("H*", "03e8"); #code 1000
@ -1894,10 +1936,12 @@ sub wsClose($) {
wsEncode($hash, $normal_closure, "close");
delete $hash->{HELPER}{WEBSOCKETS};
delete $hash->{HELPER}{wsKey};
readingsSingleUpdate($hash,'state','ws_closed',1) if (::DevIo_CloseDev($hash))
readingsSingleUpdate($hash,'state','ws_closed',1) if (::DevIo_CloseDev($hash));
return;
}
sub wsHandshake($) {
sub wsHandshake {
my $hash = shift;
my $name = $hash->{NAME};
my $host = ReadingsVal($name, "wlanIpAddress", "");
@ -1926,10 +1970,10 @@ sub wsHandshake($) {
$hash->{HELPER}{wsKey} = $wsKey;
return undef;
return;
}
sub wsCheckHandshake($$) {
sub wsCheckHandshake {
my ($hash,$response) = @_;
my $name = $hash->{NAME};
@ -1959,20 +2003,20 @@ sub wsCheckHandshake($$) {
readingsSingleUpdate($hash,'state','ws_handshake-error',1);
}
}
return undef;
return;
}
sub wsWrite($@) {
sub wsWrite {
my ($hash,$string) = @_;
my $name = $hash->{NAME};
Log3($name, 4, "BOTVAC(ws) $name: WriteFn called:\n$string");
::DevIo_SimpleWrite($hash, $string, 0);
return undef;
return;
}
sub wsRead($) {
sub wsRead {
my $hash = shift;
my $name = $hash->{NAME};
my $buf;
@ -1987,13 +2031,15 @@ sub wsRead($) {
wsDecode($hash,$buf);
} elsif( $buf =~ /HTTP\/1.1 101 Switching Protocols/ ) {
Log3($name, 4, "BOTVAC(ws) $name: received HTTP data string, start response processing:\n$buf");
BOTVAC::wsCheckHandshake($hash,$buf);
wsCheckHandshake($hash,$buf);
} else {
Log3($name, 1, "BOTVAC(ws) $name: corrupted data found:\n$buf");
}
return;
}
sub wsCallback(@) {
sub wsCallback {
my ($param, $err, $data) = @_;
my $hash = $param->{hash};
my $name = $hash->{NAME};
@ -2009,12 +2055,15 @@ sub wsCallback(@) {
} else {
Log3($name, 2, "received callback without Data and Error String!!!");
}
return undef;
return;
}
sub wsReady($) {
sub wsReady {
my ($hash) = @_;
return ::DevIo_OpenDev($hash, 1, "BOTVAC::wsHandshake") if ( $hash->{STATE} eq "disconnected" );
if ( $hash->{STATE} eq "disconnected" ) {
return ::DevIo_OpenDev($hash, 1, \&wsHandshake);
}
return;
}
# 0 1 2 3
@ -2036,7 +2085,7 @@ sub wsReady($) {
# | Payload Data continued ... |
# +---------------------------------------------------------------+
# https://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17
sub wsEncode($$;$$) {
sub wsEncode {
my ($hash, $payload, $type, $masked) = @_;
my $name = $hash->{NAME};
$type //= "text";
@ -2072,16 +2121,19 @@ sub wsEncode($$;$$) {
Log3($name, 3, "BOTVAC(ws) $name: String: " . unpack('H*',$wsString));
wsWrite($hash, $wsString);
return;
}
sub wsPong($) {
sub wsPong {
my $hash = shift;
my $name = $hash->{NAME};
Log3($name, 3, "BOTVAC(ws) $name: wsPong");
wsEncode($hash, undef, "pong");
return;
}
sub wsDecode($$) {
sub wsDecode {
my ($hash,$wsString) = @_;
my $name = $hash->{NAME};
@ -2122,9 +2174,11 @@ sub wsDecode($$) {
wsPong($hash) if ($OPCODE == $opcode{"ping"});
}
}
return;
}
sub wsMasking($$) {
sub wsMasking {
my ($payload, $mask) = @_;
$mask = $mask x (int(length($payload) / 4) + 1);
$mask = substr($mask, 0, length($payload));
@ -2346,7 +2400,7 @@ sub wsMasking($$) {
Even though an internet connection is necessary as the initialization is triggered by a remote call.
<br>
<em>Note:</em> If the robot does not receive any messages for 30 seconds it will exit Manual Cleaning,
but it will not close the websocket connection automaticaly.
but it will not close the websocket connection automatically.
</li>
<br>
<li>