mirror of
https://github.com/fhem/fhem-mirror.git
synced 2025-01-31 06:39:11 +00:00
e1df192634
git-svn-id: https://svn.fhem.de/fhem/trunk@29250 2b470e98-0d58-463d-a4d8-8e2adae1ed80
803 lines
23 KiB
Perl
803 lines
23 KiB
Perl
###############################################################################
|
|
#
|
|
# $Id$
|
|
#
|
|
# This script 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
|
|
# any later version.
|
|
#
|
|
# The GNU General Public License can be found at
|
|
# http://www.gnu.org/copyleft/gpl.html.
|
|
# A copy is found in the textfile GPL.txt and important notices to the license
|
|
# from the author is found in LICENSE.txt distributed with these scripts.
|
|
#
|
|
# This script 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.
|
|
#
|
|
#
|
|
#
|
|
# Husqvarnas unofficial app API is used to in crease websocket events
|
|
#
|
|
################################################################################
|
|
|
|
package FHEM::AMConnectTools;
|
|
our $cvsid = '$Id$';
|
|
use strict;
|
|
use warnings;
|
|
use POSIX;
|
|
use GPUtils qw(:all);
|
|
use FHEM::Core::Authentication::Passwords qw(:ALL);
|
|
use Time::HiRes qw(gettimeofday);
|
|
use Time::Local;
|
|
use Storable qw(dclone retrieve store);
|
|
|
|
BEGIN {
|
|
GP_Import(
|
|
qw(
|
|
AttrVal
|
|
CommandAttr
|
|
CommandDeleteReading
|
|
FmtDateTime
|
|
FW_ME
|
|
getKeyValue
|
|
InternalTimer
|
|
InternalVal
|
|
IsDisabled
|
|
Log3
|
|
Log
|
|
attr
|
|
defs
|
|
devspec2array
|
|
deviceEvents
|
|
init_done
|
|
minNum
|
|
maxNum
|
|
modules
|
|
readingFnAttributes
|
|
readingsBeginUpdate
|
|
readingsBulkUpdate
|
|
readingsBulkUpdateIfChanged
|
|
readingsDelete
|
|
readingsEndUpdate
|
|
ReadingsNum
|
|
readingsSingleUpdate
|
|
ReadingsVal
|
|
RemoveInternalTimer
|
|
setKeyValue
|
|
setNotifyDev
|
|
)
|
|
);
|
|
}
|
|
|
|
GP_Export(
|
|
qw(
|
|
Initialize
|
|
)
|
|
);
|
|
|
|
my $missingModul = "";
|
|
|
|
eval "use JSON;1" or $missingModul .= "JSON ";
|
|
require HttpUtils;
|
|
|
|
use constant {
|
|
AUTHURL => 'https://iam-api.dss.husqvarnagroup.net/api/v3/',
|
|
APIURL => 'https://amc-api.dss.husqvarnagroup.net/app/v1/',
|
|
};
|
|
|
|
##############################################################
|
|
sub Initialize() {
|
|
my ($hash) = @_;
|
|
|
|
$hash->{DefFn} = \&Define;
|
|
$hash->{UndefFn} = \&Undefine;
|
|
$hash->{DeleteFn} = \&Delete;
|
|
$hash->{RenameFn} = \&Rename;
|
|
$hash->{NotifyFn} = \&Notify;
|
|
$hash->{SetFn} = \&Set;
|
|
$hash->{AttrFn} = \&Attr;
|
|
$hash->{AttrList} = "disable:1,0 " .
|
|
"notifiedByMowerDevices " .
|
|
$::readingFnAttributes;
|
|
|
|
return undef;
|
|
}
|
|
|
|
#########################
|
|
sub Define{
|
|
my ( $hash, $def ) = @_;
|
|
my @val = split( "[ \t]+", $def );
|
|
my $name = $val[0];
|
|
my $type = $val[1];
|
|
my $iam = "$type $name Define:";
|
|
my $username = '';
|
|
|
|
return "$iam multiple definitions are not allowed." if( scalar devspec2array( "TYPE=$type" ) > 1 );
|
|
|
|
return "$iam Cannot define $type device. Perl modul $missingModul is missing." if ( $missingModul );
|
|
|
|
return "$iam too few parameters: define <NAME> $type <email>" if( @val < 3 );
|
|
|
|
$username = $val[2];
|
|
|
|
%$hash = (%$hash,
|
|
helper => {
|
|
passObj => FHEM::Core::Authentication::Passwords->new($type),
|
|
calltype => 'status',
|
|
interval => 440,
|
|
timeout_apiauth => 5,
|
|
timeout_getmower => 5,
|
|
retry_interval_apiauth => 120,
|
|
retry_count_apiauth => 0,
|
|
retry_max_apiauth => 10,
|
|
retry_interval_getmower => 60,
|
|
retry_int_getmowerstatus => 60,
|
|
username => $username
|
|
}
|
|
);
|
|
|
|
$attr{$name}{room} = 'AutomowerConnect' if( !defined( $attr{$name}{room} ) );
|
|
$attr{$name}{icon} = 'helper_automower' if( !defined( $attr{$name}{icon} ) );
|
|
$attr{$name}{notifiedByMowerDevices} = 'TYPE=AutomowerConnect' if( !defined( $attr{$name}{notifiedByMowerDevices} ) );
|
|
( $hash->{VERSION} ) = $cvsid =~ /\.pm (.*)Z/;
|
|
setNotifyDev( $hash, 'global,' . $attr{$name}{notifiedByMowerDevices} );
|
|
|
|
if( $hash->{helper}->{passObj}->getReadPassword($name) ) {
|
|
|
|
RemoveInternalTimer($hash);
|
|
InternalTimer( gettimeofday() + 2, \&APIAuth, $hash, 1);
|
|
|
|
readingsSingleUpdate( $hash, 'state', 'defined', 1 );
|
|
|
|
} else {
|
|
|
|
readingsSingleUpdate( $hash, 'state', 'defined - client_secret missing', 1 );
|
|
|
|
}
|
|
if ( $init_done ) {
|
|
|
|
my $paw = join( ' ', devspec2array( "TYPE=AutomowerConnect" ) );
|
|
readingsSingleUpdate( $hash, '.associatedWith', $paw, 0 );
|
|
|
|
}
|
|
|
|
return undef;
|
|
|
|
}
|
|
|
|
#########################
|
|
sub Notify {
|
|
my ($hash, $dev_hash) = @_;
|
|
my $name = $hash->{NAME}; # me
|
|
return if ( IsDisabled( $name ) == 1 );
|
|
my $dev_name = $dev_hash->{NAME};
|
|
|
|
my $events = deviceEvents($dev_hash,1);
|
|
return if( !$events );
|
|
|
|
my $runningDevices = ReadingsVal( $name, 'runningDevices', '' );
|
|
|
|
foreach my $event (@{$events}) {
|
|
|
|
$event = '' if(!defined($event));
|
|
|
|
if ( $event =~ /^mower_activity: (LEAVING|MOWING|GOING_HOME)$/ ) {
|
|
|
|
readingsBeginUpdate($hash);
|
|
|
|
if ( !$runningDevices ) {
|
|
|
|
RemoveInternalTimer( $hash );
|
|
InternalTimer( gettimeofday() + 2, \&APIAuth, $hash, 0 );
|
|
readingsBulkUpdateIfChanged( $hash, 'state', 'update' );
|
|
|
|
}
|
|
|
|
$runningDevices .= $dev_name . ' ' if ( $runningDevices !~ /(^|\s)$dev_name\s/ );
|
|
readingsBulkUpdateIfChanged( $hash, 'runningDevices', $runningDevices );
|
|
|
|
readingsEndUpdate( $hash, 1);
|
|
|
|
} elsif ( $event =~ /^mower_activity: PARKED_IN_CS$/ ) {
|
|
|
|
$runningDevices =~ s/(^|\s)$dev_name\s/$1/;
|
|
|
|
if ( !$runningDevices ) {
|
|
|
|
readingsDelete( $hash, 'runningDevices' );
|
|
readingsSingleUpdate( $hash, 'state', 'inactive', 1 );
|
|
|
|
}
|
|
|
|
} elsif ( $dev_name eq 'global' && $event =~ /^(INITIALIZED|MODIFIED $name|ATTR $name disable 0|DELETEATTR $name disable)$/ ) {
|
|
|
|
foreach my $keyx ( devspec2array('TYPE=AutomowerConnect') ) {
|
|
|
|
if ( !defined( $defs{$keyx}->{VERSION_AMConnectTools} ) || defined( $defs{$keyx}->{VERSION_AMConnectTools} ) && $defs{$keyx}->{VERSION_AMConnectTools} ne $defs{AMConnectTools}->{VERSION} ) {
|
|
|
|
$defs{$keyx}->{VERSION_AMConnectTools} = $defs{AMConnectTools}->{VERSION}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
my $runningDevices = '';
|
|
my @mowers = devspec2array( AttrVal( $name, 'notifiedByMowerDevices', '' ) );
|
|
|
|
for my $item ( @mowers ) {
|
|
|
|
if ( ReadingsVal( $item, 'mower_activity', '' ) =~ /^(LEAVING|MOWING|GOING_HOME)$/ ) {
|
|
|
|
$runningDevices .= $item . ' ';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ( !$runningDevices ) {
|
|
|
|
readingsDelete( $hash, 'runningDevices' );
|
|
readingsSingleUpdate( $hash, 'state', 'inactive', 1 );
|
|
|
|
} else {
|
|
|
|
readingsSingleUpdate( $hash, 'runningDevices', $runningDevices, 1 );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#########################
|
|
sub APIAuth {
|
|
my ( $hash, $update ) = @_;
|
|
my $name = $hash->{NAME};
|
|
my $type = $hash->{TYPE};
|
|
my $iam = "$type $name APIAuth:";
|
|
my @states = ('undefined', 'disabled', 'temporarily disabled', 'inactive' );
|
|
|
|
if( IsDisabled( $name ) ) {
|
|
readingsSingleUpdate( $hash, 'state', $states[ IsDisabled( $name ) ], 1 ) if ( ReadingsVal( $name, 'state', '' ) !~ /disabled|inactive/ );
|
|
RemoveInternalTimer( $hash );
|
|
InternalTimer( gettimeofday() + $hash->{helper}{interval}, \&APIAuth, $hash, 0 );
|
|
return undef;
|
|
|
|
}
|
|
|
|
if ( !$update && $::init_done ) {
|
|
|
|
if ( ReadingsVal( $name,'.token','' ) and gettimeofday() < (ReadingsVal( $name, '.token_expires', 0 ) - $hash->{helper}{interval} - 45 ) ) {
|
|
|
|
readingsSingleUpdate( $hash, 'state', 'update', 1 );
|
|
|
|
if ( !ReadingsVal( $name,'.apiMowerFound','' ) ) {
|
|
|
|
getMower( $hash );
|
|
|
|
} else {
|
|
|
|
getMowerStatus( $hash );
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
readingsSingleUpdate( $hash, 'state', 'authentification', 1 );
|
|
my $username = $hash->{helper}->{username};
|
|
my $password = $hash->{helper}->{passObj}->getReadPassword( $name );
|
|
my $timeout = $hash->{helper}->{timeout_apiauth};
|
|
|
|
my $header = "Content-Type: application/json\r\nAccept: application/json";
|
|
my $data = '{
|
|
"data" : {
|
|
"type" : "token",
|
|
"attributes" : {
|
|
"username" : "' . $username. '",
|
|
"password" : "' . $password. '"
|
|
}
|
|
}
|
|
}';
|
|
|
|
::HttpUtils_NonblockingGet( {
|
|
url => AUTHURL . 'token',
|
|
timeout => $timeout,
|
|
hash => $hash,
|
|
method => 'POST',
|
|
header => $header,
|
|
data => $data,
|
|
callback => \&APIAuthResponse,
|
|
t_begin => scalar gettimeofday()
|
|
} );
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
RemoveInternalTimer( $hash, \&APIAuth );
|
|
InternalTimer( gettimeofday() + $hash->{helper}{interval}, \&APIAuth, $hash, 0 );
|
|
|
|
}
|
|
return undef;
|
|
}
|
|
|
|
#########################
|
|
sub APIAuthResponse {
|
|
my ($param, $err, $data) = @_;
|
|
my $hash = $param->{hash};
|
|
my $name = $hash->{NAME};
|
|
my $type = $hash->{TYPE};
|
|
my $statuscode = $param->{code} // '';
|
|
my $iam = "$type $name APIAuthResponse:";
|
|
|
|
Log3 $name, 4, "$iam response time ". sprintf( "%.2f", ( gettimeofday() - $param->{t_begin} ) ) . ' s';
|
|
Log3 $name, 4, "$iam \$statuscode >$statuscode< \$err >$err< \$param->url $param->{url}\n\$data >$data<\n";
|
|
|
|
if( !$err && $statuscode == 201 && $data ) {
|
|
|
|
my $result = eval { decode_json( $data ) };
|
|
if ($@) {
|
|
|
|
Log3 $name, 2, "$iam JSON error [ $@ ]";
|
|
readingsSingleUpdate( $hash, 'state', 'error JSON', 1 );
|
|
|
|
} else {
|
|
|
|
$hash->{helper}->{auth} = $result->{data};
|
|
$hash->{helper}{auth}{expires} = gettimeofday() + $hash->{helper}{auth}{attributes}{expires_in};
|
|
my $paw = join( ' ', devspec2array( "TYPE=AutomowerConnect" ) );
|
|
$hash->{helper}{retry_count_apiauth} = 0;
|
|
|
|
# Update readings
|
|
readingsBeginUpdate($hash);
|
|
readingsBulkUpdateIfChanged($hash,'.associatedWith', $paw, 0 ) if ( $paw );
|
|
readingsBulkUpdateIfChanged($hash,'.token', $hash->{helper}{auth}{id}, 0 );
|
|
readingsBulkUpdateIfChanged($hash,'.provider', $hash->{helper}{auth}{attributes}{provider}, 0 );
|
|
readingsBulkUpdateIfChanged($hash,'.user_id', $hash->{helper}{auth}{attributes}{user_id}, 0 );
|
|
readingsBulkUpdateIfChanged($hash,'.scope', $hash->{helper}{auth}{attributes}{scope}, 0 );
|
|
readingsBulkUpdateIfChanged($hash,'.client_id', $hash->{helper}{auth}{attributes}{client_id}, 0 );
|
|
readingsBulkUpdateIfChanged($hash,'.refresh_token', $hash->{helper}{auth}{attributes}{expires_in}, 0 );
|
|
readingsBulkUpdateIfChanged($hash,'.expires_in', $hash->{helper}{auth}{attributes}{expires_in}, 0 );
|
|
|
|
readingsBulkUpdateIfChanged($hash,'.token_expires',$ hash->{helper}{auth}{expires}, 0 );
|
|
readingsBulkUpdateIfChanged($hash,'token_expires_fmt', FmtDateTime( $hash->{helper}{auth}{expires}), 0 );
|
|
readingsBulkUpdateIfChanged($hash,'state', 'authenticated');
|
|
readingsEndUpdate($hash, 1);
|
|
|
|
RemoveInternalTimer( $hash, \&getMower );
|
|
InternalTimer( gettimeofday() + 1.5, \&getMower, $hash, 0 );
|
|
return undef;
|
|
}
|
|
|
|
} else {
|
|
|
|
readingsSingleUpdate( $hash, 'state', "error statuscode $statuscode", 1 );
|
|
Log3 $name, 1, "$iam \$statuscode >$statuscode< \$err >$err< \$param->url $param->{url}\n\$data >$data<\n";
|
|
|
|
if ( $statuscode == 400 ) {
|
|
|
|
$hash->{helper}{retry_count_apiauth}++;
|
|
|
|
if ( $hash->{helper}{retry_count_apiauth} > $hash->{helper}{retry_max_apiauth} ) {
|
|
|
|
CommandAttr( $hash, "$name disable 1" );
|
|
$hash->{helper}{retry_count_apiauth} = 0;
|
|
|
|
}
|
|
|
|
} elsif ( $statuscode == 429 ) {
|
|
|
|
CommandAttr( $hash, "$name disable 1" );
|
|
$hash->{helper}{retry_count_apiauth} = 0;
|
|
|
|
}
|
|
|
|
RemoveInternalTimer( $hash, \&APIAuth );
|
|
InternalTimer( gettimeofday() + $hash->{helper}{retry_interval_apiauth}, \&APIAuth, $hash, 0 );
|
|
Log3 $name, 1, "$iam failed retry in $hash->{helper}{retry_interval_apiauth} seconds.";
|
|
return undef;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#########################
|
|
sub getMower {
|
|
|
|
my ( $hash ) = @_;
|
|
my $name = $hash->{NAME};
|
|
my $type = $hash->{TYPE};
|
|
my $iam = "$type $name getMower:";
|
|
my $token = ReadingsVal($name,'.token','');
|
|
my $provider = ReadingsVal($name,'.provider','');
|
|
my $client_id = $hash->{helper}->{client_id};
|
|
my $timeout = $hash->{helper}->{timeout_getmower};
|
|
|
|
my $header = "Content-Type: application/json\r\nAccept: application/json\r\nAuthorization: Bearer " . $token . "\r\nAuthorization-Provider: " . $provider;
|
|
Log3 $name, 5, "$iam \$header >$header<";
|
|
|
|
::HttpUtils_NonblockingGet({
|
|
url => APIURL . 'mowers',
|
|
timeout => $timeout,
|
|
hash => $hash,
|
|
method => "GET",
|
|
header => $header,
|
|
callback => \&getMowerResponse,
|
|
t_begin => scalar gettimeofday()
|
|
});
|
|
|
|
return undef;
|
|
}
|
|
|
|
#########################
|
|
sub getMowerResponse {
|
|
|
|
my ( $param, $err, $data ) = @_;
|
|
my $hash = $param->{hash};
|
|
my $name = $hash->{NAME};
|
|
my $type = $hash->{TYPE};
|
|
my $statuscode = $param->{code} // '';
|
|
my $iam = "$type $name getMowerResponse:";
|
|
|
|
Log3 $name, 4, "$iam response time ". sprintf( "%.2f", ( gettimeofday() - $param->{t_begin} ) ) . ' s';
|
|
Log3 $name, 4, "$iam response \$statuscode >$statuscode<, \$err >$err<, \$param->url $param->{url} \n\$data >$data<";
|
|
|
|
if( !$err && $statuscode == 200 && $data) {
|
|
|
|
if ( $data eq "[]" ) {
|
|
|
|
Log3 $name, 2, "$iam no mower data present";
|
|
|
|
} else {
|
|
|
|
my $result = eval { decode_json($data) };
|
|
if ($@) {
|
|
|
|
Log3( $name, 2, "$iam - JSON error while request: $@");
|
|
|
|
} else {
|
|
|
|
my @mowers = @{ dclone( $result ) };
|
|
my $mcnt = @mowers;
|
|
my $fMs = '';
|
|
|
|
map {
|
|
|
|
$fMs .= $_->{id} .' ';
|
|
|
|
} @mowers;
|
|
|
|
chop $fMs;
|
|
Log3 $name, 4, "$iam found $fMs";
|
|
|
|
readingsBeginUpdate($hash);
|
|
|
|
readingsBulkUpdateIfChanged($hash, '.apiMowerFound', $fMs, 0 );
|
|
readingsBulkUpdate($hash, 'state', 'connected' );
|
|
|
|
readingsEndUpdate($hash, 1);
|
|
|
|
RemoveInternalTimer( $hash, \&getMowerStatus );
|
|
InternalTimer( gettimeofday() + 1.5, \&getMowerStatus, $hash, 0 );
|
|
|
|
return undef;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
readingsSingleUpdate( $hash, 'state', "error statuscode $statuscode", 1 );
|
|
Log3 $name, 1, "$iam \$statuscode >$statuscode<, \$err >$err<, \$param->url $param->{url} \n\$data >$data<";
|
|
|
|
}
|
|
|
|
RemoveInternalTimer( $hash, \&APIAuth );
|
|
InternalTimer( gettimeofday() + $hash->{helper}{retry_interval_getmower}, \&APIAuth, $hash, 0 );
|
|
Log3 $name, 1, "$iam failed retry in $hash->{helper}{retry_interval_getmower} seconds.";
|
|
return undef;
|
|
|
|
}
|
|
|
|
#########################
|
|
sub getMowerStatus {
|
|
|
|
my ( $hash ) = @_;
|
|
my $name = $hash->{NAME};
|
|
my $type = $hash->{TYPE};
|
|
my $iam = "$type $name getMowerStatus:";
|
|
my $token = ReadingsVal($name,'.token','');
|
|
my $provider = ReadingsVal($name,'.provider','');
|
|
my @mower_id = split( / /, ReadingsVal( $name,'.apiMowerFound','' ) );
|
|
my $timeout = $hash->{helper}{timeout_getmower};
|
|
|
|
my $header = "Content-Type: application/json\r\nAccept: application/json\r\nAuthorization: Bearer " . $token . "\r\nAuthorization-Provider: " . $provider;
|
|
Log3 $name, 5, "$iam \$header >$header<";
|
|
|
|
::HttpUtils_NonblockingGet({
|
|
url => APIURL . 'mowers/' . $mower_id[0] . '/status',
|
|
timeout => $timeout,
|
|
hash => $hash,
|
|
method => "GET",
|
|
header => $header,
|
|
callback => \&getMowerStatusResponse,
|
|
t_begin => scalar gettimeofday()
|
|
});
|
|
|
|
return undef;
|
|
}
|
|
|
|
#########################
|
|
sub getMowerStatusResponse {
|
|
|
|
my ( $param, $err, $data ) = @_;
|
|
my $hash = $param->{hash};
|
|
my $name = $hash->{NAME};
|
|
my $type = $hash->{TYPE};
|
|
my $statuscode = $param->{code} // '';
|
|
my $iam = "$type $name getMowerStatusResponse:";
|
|
|
|
Log3 $name, 4, "$iam response time ". sprintf( "%.2f", ( gettimeofday() - $param->{t_begin} ) ) . ' s';
|
|
Log3 $name, 4, "$iam response \$statuscode >$statuscode<, \$err >$err<, \$param->url $param->{url} \n\$data >$data<";
|
|
|
|
if( !$err && $statuscode == 200 && $data) {
|
|
|
|
if ( !$data ) {
|
|
|
|
Log3 $name, 2, "$iam no mower data present";
|
|
|
|
} else {
|
|
|
|
my $result = eval { decode_json($data) };
|
|
if ($@) {
|
|
|
|
Log3( $name, 2, "$iam - JSON error while request: $@");
|
|
|
|
} else {
|
|
|
|
readingsBeginUpdate($hash);
|
|
|
|
readingsBulkUpdate($hash,'nextRound', FmtDateTime( time() + $hash->{helper}{interval} ) );
|
|
readingsBulkUpdate($hash, 'state', 'connected' );
|
|
|
|
readingsEndUpdate($hash, 1);
|
|
|
|
# schedule next round
|
|
RemoveInternalTimer( $hash, \&APIAuth );
|
|
InternalTimer( gettimeofday() + $hash->{helper}{interval}, \&APIAuth, $hash, 0 );
|
|
|
|
return undef;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
readingsSingleUpdate( $hash, 'state', "error statuscode $statuscode", 1 );
|
|
Log3 $name, 1, "$iam \$statuscode >$statuscode<, \$err >$err<, \$param->url $param->{url} \n\$data >$data<";
|
|
|
|
}
|
|
|
|
RemoveInternalTimer( $hash, \&APIAuth );
|
|
InternalTimer( gettimeofday() + $hash->{helper}{retry_int_getmowerstatus}, \&APIAuth, $hash, 0 );
|
|
Log3 $name, 1, "$iam failed retry in $hash->{helper}{retry_int_getmowerstatus} seconds.";
|
|
return undef;
|
|
|
|
}
|
|
|
|
#########################
|
|
sub Set {
|
|
my ($hash,@val) = @_;
|
|
my $type = $hash->{TYPE};
|
|
my $name = $hash->{NAME};
|
|
my $iam = "$type $name Set:";
|
|
|
|
return "$iam: needs at least one argument" if ( @val < 2 );
|
|
return "Unknown argument, $iam is disabled, choose one of none:noArg" if ( IsDisabled( $name ) );
|
|
|
|
my ($pname,$setName,$setVal,$setVal2,$setVal3) = @val;
|
|
|
|
Log3 $name, 4, "$iam called with $setName " . ($setVal ? $setVal : "") if ($setName !~ /^(\?|password)$/);
|
|
|
|
if ( $setName eq 'password' ) {
|
|
if ( $setVal ) {
|
|
|
|
my ($passResp, $passErr) = $hash->{helper}->{passObj}->setStorePassword($name, $setVal);
|
|
Log3 $name, 1, "$iam error: $passErr" if ($passErr);
|
|
return "$iam $passErr" if( $passErr );
|
|
|
|
readingsBeginUpdate($hash);
|
|
readingsBulkUpdateIfChanged( $hash, '.access_token', '', 0 );
|
|
readingsBulkUpdateIfChanged( $hash, 'state', 'initialized');
|
|
readingsEndUpdate($hash, 1);
|
|
|
|
RemoveInternalTimer($hash, \&APIAuth);
|
|
APIAuth($hash);
|
|
return undef;
|
|
}
|
|
|
|
}
|
|
|
|
my $ret = " password ";
|
|
return "Unknown argument $setName, choose one of".$ret;
|
|
|
|
}
|
|
|
|
#########################
|
|
sub Undefine {
|
|
|
|
my ( $hash, $arg ) = @_;
|
|
my $name = $hash->{NAME};
|
|
my $type = $hash->{TYPE};
|
|
|
|
RemoveInternalTimer( $hash );
|
|
readingsSingleUpdate( $hash, 'state', 'disconnected', 1 );
|
|
return undef;
|
|
}
|
|
|
|
##########################
|
|
sub Delete {
|
|
|
|
my ( $hash, $arg ) = @_;
|
|
my $name = $hash->{NAME};
|
|
my $type = $hash->{TYPE};
|
|
my $iam ="$type $name Delete: ";
|
|
Log3( $name, 5, "$iam called" );
|
|
|
|
foreach my $keyx ( devspec2array('TYPE=AutomowerConnect') ) {
|
|
|
|
delete( $defs{$keyx}->{VERSION_AMConnectTools} ) if ( defined( $defs{$keyx}->{VERSION_AMConnectTools} ) );
|
|
|
|
}
|
|
|
|
my ($passResp,$passErr) = $hash->{helper}->{passObj}->setDeletePassword($name);
|
|
Log3( $name, 1, "$iam error: $passErr" ) if ($passErr);
|
|
|
|
return;
|
|
}
|
|
|
|
##########################
|
|
sub Rename {
|
|
|
|
my ( $newname, $oldname ) = @_;
|
|
my $hash = $defs{$newname};
|
|
my $type = $hash->{TYPE};
|
|
|
|
my ( $passResp, $passErr ) = $hash->{helper}->{passObj}->setRename( $newname, $oldname );
|
|
Log3 $newname, 2, "$newname password rename error: $passErr" if ($passErr);
|
|
|
|
return undef;
|
|
}
|
|
|
|
#########################
|
|
sub Attr {
|
|
|
|
my ( $cmd, $name, $attrName, $attrVal ) = @_;
|
|
my $hash = $defs{$name};
|
|
my $type = $hash->{TYPE};
|
|
my $iam = "$type $name Attr:";
|
|
##########
|
|
if ( $attrName eq "disable" ) {
|
|
|
|
if( $cmd eq "set" and $attrVal eq "1" ) {
|
|
|
|
RemoveInternalTimer( $hash );
|
|
readingsSingleUpdate( $hash,'state','disabled',1);
|
|
readingsDelete( $hash, 'runningDevices' );
|
|
Log3 $name, 3, "$iam $cmd $attrName disabled";
|
|
|
|
} elsif( $cmd eq "del" or $cmd eq 'set' and !$attrVal ) {
|
|
|
|
RemoveInternalTimer( $hash, \&APIAuth);
|
|
InternalTimer( gettimeofday() + 1, \&APIAuth, $hash, 0 );
|
|
Log3 $name, 3, "$iam $cmd $attrName enabled";
|
|
|
|
}
|
|
|
|
}
|
|
##########
|
|
if ( $attrName eq "notifiedByMowerDevices" ) {
|
|
|
|
if( $cmd eq "set" ) {
|
|
|
|
setNotifyDev( $hash, 'global,' . $attrVal );
|
|
my $paw = join( ' ', devspec2array( "TYPE=AutomowerConnect" ) );
|
|
readingsSingleUpdate( $hash, '.associatedWith', $paw, 0 );
|
|
Log3 $name, 3, "$iam $cmd $attrName $attrVal";
|
|
|
|
} elsif( $cmd eq "del" ) {
|
|
|
|
setNotifyDev( $hash, 'global' );
|
|
readingsDelete( $hash, 'runningDevices' );
|
|
Log3 $name, 3, "$iam $cmd $attrName deleted";
|
|
|
|
}
|
|
|
|
}
|
|
##########
|
|
}
|
|
##############################################################
|
|
|
|
|
|
1;
|
|
|
|
__END__
|
|
|
|
=pod
|
|
|
|
=item helper
|
|
=item summary Increases the number of websocket events for the module AutomowerConnect
|
|
=item summary_DE Erhöht die Zahl der Websocketevents für das Modul AutomowerConnect
|
|
|
|
=begin html
|
|
|
|
<a id="AMConnectTools" ></a>
|
|
<h3>AMConnectTools</h3>
|
|
<ul>
|
|
<u><b>FHEM-FORUM:</b></u> <a target="_blank" href="https://forum.fhem.de/index.php/topic,131661.0.html"> AutomowerConnect</a><br>
|
|
<br><br>
|
|
<u><b>Introduction</b></u>
|
|
<br><br>
|
|
<ul>
|
|
<li>This module increases the number of websocket events for the module AutomowerConnect if the mower activity is LEAVING, MOWING or GOING_HOME just by questioning the unoffical smartphone app API.</li>
|
|
</ul>
|
|
<br>
|
|
<u><b>Requirements</b></u>
|
|
<br><br>
|
|
<ul>
|
|
<li>To get access to the API use your smartphone apps username and password.</li>
|
|
</ul>
|
|
<br>
|
|
<a id="AMConnectToolsDefine"></a>
|
|
<b>Define</b>
|
|
<ul>
|
|
<code>define <device name> AMConnectTools <email adress></code><br>
|
|
Example:<br>
|
|
<code>define AMCT AMConnectTools <email adress></code><br>
|
|
It has to be set a <b>password</b>. Use your smartphone app's username and password.<br>
|
|
<code>set AMCT <password></code>
|
|
<br>
|
|
</ul>
|
|
<br>
|
|
|
|
<a id="AMConnectToolsSet"></a>
|
|
<b>Set</b>
|
|
<ul>
|
|
<li><a id='AMConnectTools-set-password'>password</a><br>
|
|
<code>set <name> password <password></code><br>
|
|
Sets the mandatory password.</li>
|
|
<br>
|
|
</ul>
|
|
<br>
|
|
|
|
<a id="AMConnectToolsAttributes"></a>
|
|
<b>Attributes</b>
|
|
<ul>
|
|
<li><a id='AMConnectTools-attr-notifiedByMowerDevices'>notifiedByMowerDevice</a><br>
|
|
<code>attr <name> notifiedByMowerDevice <devspec for automower devices></code><br>
|
|
Sets the notify devices and starts increasing websocket events on first running mower's event LEAVING and stops it with the last running mower's event PARKED_IN_CS </li>
|
|
|
|
<li><a href="disable">disable</a></li>
|
|
<br><br>
|
|
</ul>
|
|
<br>
|
|
|
|
|
|
<a id="AMConnectToolsReadings"></a>
|
|
<b>Readings</b>
|
|
<ul>
|
|
<li>nextRound - time of the next API call.</li>
|
|
<li>state - state of device</li>
|
|
<li>token_expires_fmt - formated token expiration time</li>
|
|
</ul>
|
|
</ul>
|
|
|
|
=end html
|