mirror of
https://github.com/fhem/fhem-mirror.git
synced 2025-02-25 03:44:52 +00:00
AutomowerConnect(family): error message and location on map, various settings for map design, mowerpath for all activities
git-svn-id: https://svn.fhem.de/fhem/trunk@27203 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
parent
f1bb86fcec
commit
3ff82bfd12
@ -1,5 +1,7 @@
|
||||
# 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.
|
||||
- feature: AutomowerConnect-family: error message and location on map
|
||||
various settings for map design, mowerpath for all activities
|
||||
- feature: 72_FRITZBOX: neue Attr. deviceInfo / enableWanInfo (s. commandRef)
|
||||
Fehlerkorrekturen
|
||||
- bugfix: 72_FB_CALLMONITOR: RegEx für Suche dasOertliche angepasst
|
||||
|
@ -25,6 +25,7 @@
|
||||
################################################################################
|
||||
|
||||
package FHEM::AutomowerConnect;
|
||||
my $cvsid = '$Id$';
|
||||
use strict;
|
||||
use warnings;
|
||||
use POSIX;
|
||||
@ -91,14 +92,13 @@ use constant APIURL => 'https://api.amc.husqvarna.dev/v1';
|
||||
sub Initialize() {
|
||||
my ($hash) = @_;
|
||||
|
||||
$hash->{DefFn} = \&Define;
|
||||
$hash->{SetFn} = \&Set;
|
||||
$hash->{AttrFn} = \&Attr;
|
||||
$hash->{DefFn} = \&FHEM::Devices::AMConnect::Common::Define;
|
||||
$hash->{GetFn} = \&FHEM::Devices::AMConnect::Common::Get;
|
||||
$hash->{UndefFn} = \&FHEM::Devices::AMConnect::Common::Undefine;
|
||||
$hash->{DeleteFn} = \&FHEM::Devices::AMConnect::Common::Delete;
|
||||
$hash->{RenameFn} = \&FHEM::Devices::AMConnect::Common::Rename;
|
||||
$hash->{FW_detailFn}= \&FHEM::Devices::AMConnect::Common::FW_detailFn;
|
||||
$hash->{SetFn} = \&Set;
|
||||
$hash->{AttrFn} = \&Attr;
|
||||
$hash->{AttrList} = "interval " .
|
||||
"disable:1,0 " .
|
||||
@ -109,6 +109,8 @@ sub Initialize() {
|
||||
"mapImageCoordinatesToRegister:textField-long " .
|
||||
"mapImageCoordinatesUTM:textField-long " .
|
||||
"mapImageZoom " .
|
||||
"mapBackgroundColor " .
|
||||
"mapDesignAttributes:textField-long " .
|
||||
"showMap:1,0 " .
|
||||
"chargingStationCoordinates " .
|
||||
"chargingStationImagePosition:left,top,right,bottom,center " .
|
||||
@ -126,214 +128,6 @@ sub Initialize() {
|
||||
}
|
||||
|
||||
|
||||
##############################################################
|
||||
#
|
||||
# DEFINE
|
||||
#
|
||||
##############################################################
|
||||
|
||||
sub Define{
|
||||
my ( $hash, $def ) = @_;
|
||||
my @val = split( "[ \t]+", $def );
|
||||
my $name = $val[0];
|
||||
my $type = $val[1];
|
||||
my $iam = "$type $name Define:";
|
||||
|
||||
return "$iam too few parameters: define <NAME> $type <client_id> [<mower number>]" if( @val < 3 ) ;
|
||||
return "$iam Cannot define $type device. Perl modul $missingModul is missing." if ( $missingModul );
|
||||
|
||||
my $client_id =$val[2];
|
||||
my $mowerNumber = $val[3] ? $val[3] : 0;
|
||||
|
||||
%$hash = (%$hash,
|
||||
helper => {
|
||||
passObj => FHEM::Core::Authentication::Passwords->new($type),
|
||||
interval => 600,
|
||||
mowerNumber => $mowerNumber,
|
||||
scaleToMeterLongitude => 67425,
|
||||
scaleToMeterLatitude => 108886,
|
||||
minLon => 180,
|
||||
maxLon => -180,
|
||||
minLat => 90,
|
||||
maxLat => -90,
|
||||
imageHeight => 650,
|
||||
imageWidthHeight => '350 650',
|
||||
posMinMax => "-180 90\n180 -90",
|
||||
newdatasets => 0,
|
||||
client_id => $client_id,
|
||||
grant_type => 'client_credentials',
|
||||
MAP_PATH => '',
|
||||
MAP_MIME => '',
|
||||
MAP_CACHE => '',
|
||||
cspos => [],
|
||||
areapos => [],
|
||||
searchpos => [],
|
||||
UNKNOWN => {
|
||||
arrayName => '',
|
||||
maxLength => 0,
|
||||
callFn => ''
|
||||
},
|
||||
NOT_APPLICABLE => {
|
||||
arrayName => '',
|
||||
maxLength => 0,
|
||||
callFn => ''
|
||||
},
|
||||
MOWING => {
|
||||
arrayName => 'areapos',
|
||||
maxLength => 500,
|
||||
maxLengthDefault => 500,
|
||||
callFn => \&FHEM::Devices::AMConnect::Common::AreaStatistics
|
||||
},
|
||||
GOING_HOME => {
|
||||
arrayName => '',
|
||||
maxLength => 0,
|
||||
callFn => ''
|
||||
},
|
||||
CHARGING => {
|
||||
arrayName => 'cspos',
|
||||
maxLength => 100,
|
||||
callFn => \&FHEM::Devices::AMConnect::Common::ChargingStationPosition
|
||||
},
|
||||
LEAVING => {
|
||||
arrayName => '',
|
||||
maxLength => 0,
|
||||
callFn => ''
|
||||
},
|
||||
PARKED_IN_CS => {
|
||||
arrayName => 'cspos',
|
||||
maxLength => 100,
|
||||
callFn => \&FHEM::Devices::AMConnect::Common::ChargingStationPosition
|
||||
},
|
||||
STOPPED_IN_GARDEN => {
|
||||
arrayName => '',
|
||||
maxLength => 0,
|
||||
callFn => ''
|
||||
},
|
||||
statistics => {
|
||||
currentSpeed => 0,
|
||||
currentDayTrack => 0,
|
||||
currentDayArea => 0,
|
||||
lastDayTrack => 0,
|
||||
lastDayArea => 0,
|
||||
currentWeekTrack => 0,
|
||||
currentWeekArea => 0,
|
||||
lastWeekTrack => 0,
|
||||
lastWeekArea => 0
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
$hash->{MODEL} = '';
|
||||
$attr{$name}{room} = $type if( !defined( $attr{$name}{room} ) );
|
||||
$attr{$name}{icon} = 'automower' if( !defined( $attr{$name}{icon} ) );
|
||||
my ($modname) = __FILE__ =~ /(\d\d_.*\.pm)/;
|
||||
|
||||
if (::AnalyzeCommandChain(undef,"version $modname noheader") =~ "^$modname (.*)Z") {
|
||||
$hash->{VERSION}=$1;
|
||||
}
|
||||
|
||||
::FHEM::Devices::AMConnect::Common::AddExtension( $name, \&FHEM::Devices::AMConnect::Common::GetMap, "$type/$name/map" );
|
||||
|
||||
if( $hash->{helper}->{passObj}->getReadPassword($name) ) {
|
||||
|
||||
RemoveInternalTimer($hash);
|
||||
InternalTimer( gettimeofday() + 2, \&APIAuth, $hash, 1);
|
||||
InternalTimer( gettimeofday() + 30, \&FHEM::Devices::AMConnect::Common::readMap, $hash, 0);
|
||||
readingsSingleUpdate( $hash, 'state', 'defined', 1 );
|
||||
|
||||
} else {
|
||||
|
||||
readingsSingleUpdate( $hash, 'state', 'defined - client_secret missing', 1 );
|
||||
|
||||
}
|
||||
|
||||
return undef;
|
||||
|
||||
}
|
||||
|
||||
|
||||
#########################
|
||||
sub AlignArray {
|
||||
my ($hash) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
my $activity = $hash->{helper}{mower}{attributes}{mower}{activity};
|
||||
my $arrayName = $hash->{helper}{$activity}{arrayName};
|
||||
my $searchlen = 2;
|
||||
my $i = 0;
|
||||
my @temp = ();
|
||||
|
||||
if ( isGoodActivity( $hash ) ) {
|
||||
|
||||
my $k = -1;
|
||||
my $poslen = @{$hash->{helper}{mower}{attributes}{positions}};
|
||||
my @searchposlon = ($hash->{helper}{searchpos}[0]{longitude}, $hash->{helper}{searchpos}[1]{longitude});
|
||||
my @searchposlat = ($hash->{helper}{searchpos}[0]{latitude}, $hash->{helper}{searchpos}[1]{latitude});
|
||||
my $maxLength = $hash->{helper}{$activity}{maxLength};
|
||||
for ( $i = 0; $i < $poslen-2; $i++ ) { # -2 due to 2 alignment data sets at the end
|
||||
if ( $searchposlon[ 0 ] == $hash->{helper}{mower}{attributes}{positions}[ $i ]{longitude}
|
||||
&& $searchposlat[ 0 ] == $hash->{helper}{mower}{attributes}{positions}[ $i ]{latitude}
|
||||
&& $searchposlon[ 1 ] == $hash->{helper}{mower}{attributes}{positions}[ $i+1 ]{longitude}
|
||||
&& $searchposlat[ 1 ] == $hash->{helper}{mower}{attributes}{positions}[ $i+1 ]{latitude} ) {
|
||||
# timediff per step
|
||||
my $dt = 0;
|
||||
$dt = int( ( $hash->{helper}{mower}{attributes}{metadata}{statusTimestamp} - $hash->{helper}{$arrayName}[ 0 ]{statusTimestamp} ) / $i) if ( $i && @{ $hash->{helper}{$arrayName} } );
|
||||
for ($k=$i-1;$k>-1;$k--) {
|
||||
|
||||
}
|
||||
for ($k=$i-1;$k>-1;$k--) {
|
||||
|
||||
if ( @{ $hash->{helper}{$arrayName} } ) {
|
||||
|
||||
unshift ( @{$hash->{helper}{$arrayName}}, dclone( $hash->{helper}{mower}{attributes}{positions}[ $k ] ) );
|
||||
|
||||
} else {
|
||||
|
||||
$hash->{helper}{$arrayName}[ 0 ] = dclone( $hash->{helper}{mower}{attributes}{positions}[ $k ] );
|
||||
|
||||
}
|
||||
|
||||
pop ( @{ $hash->{helper}{$arrayName} } ) if ( @{ $hash->{helper}{$arrayName} } > $maxLength );
|
||||
$hash->{helper}{$arrayName}[ 0 ]{statusTimestamp} = $hash->{helper}{mower}{attributes}{metadata}{statusTimestamp} - $dt * $k;
|
||||
|
||||
push ( @temp, dclone( $hash->{helper}{mower}{attributes}{positions}[ $k ] ) );
|
||||
|
||||
}
|
||||
|
||||
::FHEM::Devices::AMConnect::Common::posMinMax( $hash, \@temp );
|
||||
#callFn if present
|
||||
if ( $hash->{helper}{$activity}{callFn} && @{ $hash->{helper}{$arrayName} } > 1 ) {
|
||||
|
||||
$hash->{helper}{$activity}{cnt} = $i;
|
||||
no strict "refs";
|
||||
&{$hash->{helper}{$activity}{callFn}}($hash);
|
||||
use strict "refs";
|
||||
|
||||
}
|
||||
|
||||
last;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$hash->{helper}{newdatasets} = $i;
|
||||
$hash->{helper}{searchpos} = [ dclone( $hash->{helper}{mower}{attributes}{positions}[ 0 ] ), dclone( $hash->{helper}{mower}{attributes}{positions}[ 1 ] ) ];
|
||||
return undef;
|
||||
|
||||
}
|
||||
|
||||
#########################
|
||||
sub isGoodActivity {
|
||||
|
||||
my ( $hash ) = @_;
|
||||
my $act = $hash->{helper}{mower}{attributes}{mower}{activity};
|
||||
my $ret = $hash->{helper}{$act}{arrayName} && $act eq $hash->{helper}{mowerold}{attributes}{mower}{activity};
|
||||
return $ret;
|
||||
|
||||
}
|
||||
|
||||
##############################################################
|
||||
#
|
||||
# API AUTHENTICATION
|
||||
@ -346,6 +140,8 @@ sub APIAuth {
|
||||
my $type = $hash->{TYPE};
|
||||
my $iam = "$type $name APIAuth:";
|
||||
my $interval = $hash->{helper}{interval};
|
||||
( $hash->{VERSION} ) = $cvsid =~ /\.pm (.*)Z/ if ( !$hash->{VERSION} );
|
||||
|
||||
if ( IsDisabled($name) ) {
|
||||
|
||||
readingsSingleUpdate($hash,'state','disabled',1) if( ReadingsVal($name,'state','') ne 'disabled' );
|
||||
@ -397,7 +193,7 @@ sub APIAuthResponse {
|
||||
my $hash = $param->{hash};
|
||||
my $name = $hash->{NAME};
|
||||
my $type = $hash->{TYPE};
|
||||
my $statuscode = $param->{code};
|
||||
my $statuscode = $param->{code} // '';
|
||||
my $interval = $hash->{helper}{interval};
|
||||
my $iam = "$type $name APIAuthResponse:";
|
||||
|
||||
@ -438,6 +234,7 @@ sub APIAuthResponse {
|
||||
|
||||
} else {
|
||||
|
||||
|
||||
readingsSingleUpdate( $hash, 'state', "error statuscode $statuscode", 1 );
|
||||
Log3 $name, 1, "\n$iam\n\$statuscode [$statuscode]\n\$err [$err],\n\$data [$data]\n\$param->url $param->{url}";
|
||||
|
||||
@ -533,8 +330,8 @@ sub getMowerResponse {
|
||||
} else { # first data set
|
||||
|
||||
$hash->{helper}{mowerold} = dclone( $hash->{helper}{mowers}[$mowerNumber] );
|
||||
|
||||
$hash->{helper}{searchpos} = [ dclone( $hash->{helper}{mowerold}{attributes}{positions}[0] ), dclone( $hash->{helper}{mowerold}{attributes}{positions}[1] ) ];
|
||||
$hash->{helper}{timestamps}[ 0 ] = $hash->{helper}{mowerold}{attributes}{metadata}{statusTimestamp};
|
||||
|
||||
if ( AttrVal( $name, 'mapImageCoordinatesToRegister', '' ) eq '' ) {
|
||||
::FHEM::Devices::AMConnect::Common::posMinMax( $hash, $hash->{helper}{mowerold}{attributes}{positions} );
|
||||
@ -543,15 +340,16 @@ sub getMowerResponse {
|
||||
}
|
||||
|
||||
$hash->{helper}{mower} = dclone( $hash->{helper}{mowers}[$mowerNumber] );
|
||||
# add alignment data set to the end
|
||||
# add alignment data set (last matched search positions) to the end
|
||||
push( @{ $hash->{helper}{mower}{attributes}{positions} }, @{ dclone( $hash->{helper}{searchpos} ) } );
|
||||
$hash->{helper}{newdatasets} = 0;
|
||||
|
||||
my $storediff = $hash->{helper}{mower}{attributes}{metadata}{statusTimestamp} - $hash->{helper}{mowerold}{attributes}{metadata}{statusTimestamp};
|
||||
if ($storediff) {
|
||||
# collect timestamps for analysis
|
||||
unshift ( @{ $hash->{helper}{timestamps} }, $hash->{helper}{mower}{attributes}{metadata}{statusTimestamp} );
|
||||
|
||||
::FHEM::Devices::AMConnect::Common::AlignArray( $hash ); #release
|
||||
# AlignArray( $hash ); # developement
|
||||
::FHEM::Devices::AMConnect::Common::AlignArray( $hash );
|
||||
::FHEM::Devices::AMConnect::Common::FW_detailFn_Update ($hash) if (AttrVal($name,'showMap',1));
|
||||
|
||||
}
|
||||
@ -568,7 +366,7 @@ sub getMowerResponse {
|
||||
readingsBulkUpdateIfChanged($hash, $pref.'_commandStatus', 'cleared' );
|
||||
|
||||
my $tstamp = $hash->{helper}{mower}{attributes}{$pref}{errorCodeTimestamp};
|
||||
my $timestamp = FmtDateTimeGMT($tstamp/1000);
|
||||
my $timestamp = ::FHEM::Devices::AMConnect::Common::FmtDateTimeGMT($tstamp/1000);
|
||||
readingsBulkUpdateIfChanged($hash, $pref."_errorCodeTimestamp", $tstamp ? $timestamp : '-' );
|
||||
|
||||
my $errc = $hash->{helper}{mower}{attributes}{$pref}{errorCode};
|
||||
@ -581,14 +379,16 @@ sub getMowerResponse {
|
||||
readingsBulkUpdateIfChanged($hash, $pref."_name", $hash->{helper}{mower}{attributes}{$pref}{name} );
|
||||
my $model = $hash->{helper}{mower}{attributes}{$pref}{model};
|
||||
$model =~ s/AUTOMOWER./AM/;
|
||||
# $hash->{MODEL} = '' if (!defined $hash->{MODEL});
|
||||
$hash->{MODEL} = $model if ( $model && $hash->{MODEL} ne $model );
|
||||
$pref = 'planner';
|
||||
readingsBulkUpdateIfChanged($hash, "planner_restrictedReason", $hash->{helper}{mower}{attributes}{$pref}{restrictedReason} );
|
||||
readingsBulkUpdateIfChanged($hash, "planner_overrideAction", $hash->{helper}{mower}{attributes}{$pref}{override}{action} );
|
||||
|
||||
|
||||
$tstamp = $hash->{helper}{mower}{attributes}{$pref}{nextStartTimestamp};
|
||||
$timestamp = FmtDateTimeGMT($tstamp/1000);
|
||||
readingsBulkUpdateIfChanged($hash, "planner_nextStart", $tstamp ? $timestamp : '-' );
|
||||
$timestamp = ::FHEM::Devices::AMConnect::Common::FmtDateTimeGMT($tstamp/1000);
|
||||
readingsBulkUpdateIfChanged($hash, "planner_nextStart", $tstamp ? $timestamp : '-' );
|
||||
|
||||
$pref = 'statistics';
|
||||
readingsBulkUpdateIfChanged($hash, $pref."_numberOfCollisions", $hash->{helper}->{mower}{attributes}{$pref}{numberOfCollisions} );
|
||||
readingsBulkUpdateIfChanged($hash, $pref."_newGeoDataSets", $hash->{helper}{newdatasets} );
|
||||
@ -596,7 +396,8 @@ sub getMowerResponse {
|
||||
readingsBulkUpdateIfChanged($hash, $pref."_headlight", $hash->{helper}->{mower}{attributes}{$pref}{headlight}{mode} );
|
||||
readingsBulkUpdateIfChanged($hash, $pref."_cuttingHeight", $hash->{helper}->{mower}{attributes}{$pref}{cuttingHeight} );
|
||||
$pref = 'status';
|
||||
readingsBulkUpdateIfChanged($hash, $pref."_connected", ( $hash->{helper}{mower}{attributes}{metadata}{connected} ? 'CONNECTED' : 'OFFLINE') );
|
||||
my $connected = $hash->{helper}{mower}{attributes}{metadata}{connected};
|
||||
readingsBulkUpdateIfChanged($hash, $pref."_connected", ( $connected ? "CONNECTED($connected)" : "OFFLINE($connected)") );
|
||||
readingsBulkUpdateIfChanged($hash, $pref."_Timestamp", FmtDateTime( $hash->{helper}{mower}{attributes}{metadata}{statusTimestamp}/1000 ));
|
||||
readingsBulkUpdateIfChanged($hash, $pref."_TimestampDiff", $storediff/1000 );
|
||||
readingsBulkUpdateIfChanged($hash, $pref."_TimestampOld", FmtDateTime( $hash->{helper}{mowerold}{attributes}{metadata}{statusTimestamp}/1000 ));
|
||||
@ -615,7 +416,7 @@ sub getMowerResponse {
|
||||
$hash->{helper}{statistics}{currentDayTrack} = 0;
|
||||
$hash->{helper}{statistics}{currentDayArea} = 0;
|
||||
# do on mondays
|
||||
if ( $time[6] == 1 && $secs <= $interval ) {
|
||||
if ( $time[6] == 1 ) {
|
||||
|
||||
$hash->{helper}{statistics}{lastWeekTrack} = $hash->{helper}{statistics}{currentWeekTrack};
|
||||
$hash->{helper}{statistics}{lastWeekArea} = $hash->{helper}{statistics}{currentWeekArea};
|
||||
@ -646,120 +447,7 @@ sub getMowerResponse {
|
||||
}
|
||||
|
||||
|
||||
##############################################################
|
||||
#
|
||||
# SEND COMMAND
|
||||
#
|
||||
##############################################################
|
||||
|
||||
sub sendCMD {
|
||||
my ($hash,@cmd) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
my $type = $hash->{TYPE};
|
||||
my $iam = "$type $name sendCMD:";
|
||||
|
||||
if ( IsDisabled($name) ) {
|
||||
Log3 $name, 3, "$iam disabled";
|
||||
return undef
|
||||
}
|
||||
|
||||
my $client_id = $hash->{helper}->{client_id};
|
||||
my $token = ReadingsVal($name,".access_token","");
|
||||
my $provider = ReadingsVal($name,".provider","");
|
||||
my $mower_id = $hash->{helper}{mower}{id};
|
||||
|
||||
my $json = '';
|
||||
my $post = '';
|
||||
|
||||
|
||||
my $header = "Accept: application/vnd.api+json\r\nX-Api-Key: ".$client_id."\r\nAuthorization: Bearer " . $token . "\r\nAuthorization-Provider: " . $provider . "\r\nContent-Type: application/vnd.api+json";
|
||||
|
||||
|
||||
if ($cmd[0] eq "ParkUntilFurtherNotice") { $json = '{"data":{"type":"'.$cmd[0].'"}}'; $post = 'actions' }
|
||||
elsif ($cmd[0] eq "ParkUntilNextSchedule") { $json = '{"data": {"type":"'.$cmd[0].'"}}'; $post = 'actions' }
|
||||
elsif ($cmd[0] eq "ResumeSchedule") { $json = '{"data": {"type":"'.$cmd[0].'"}}'; $post = 'actions' }
|
||||
elsif ($cmd[0] eq "Pause") { $json = '{"data": {"type":"'.$cmd[0].'"}}'; $post = 'actions' }
|
||||
elsif ($cmd[0] eq "Park") { $json = '{"data": {"type":"'.$cmd[0].'","attributes":{"duration":'.$cmd[1].'}}}'; $post = 'actions' }
|
||||
elsif ($cmd[0] eq "Start") { $json = '{"data": {"type":"'.$cmd[0].'","attributes":{"duration":'.$cmd[1].'}}}'; $post = 'actions' }
|
||||
elsif ($cmd[0] eq "headlight") { $json = '{"data": {"type":"settings","attributes":{"'.$cmd[0].'": {"mode": "'.$cmd[1].'"}}}}'; $post = 'settings' }
|
||||
elsif ($cmd[0] eq "cuttingHeight") { $json = '{"data": {"type":"settings","attributes":{"'.$cmd[0].'": '.$cmd[1].'}}}'; $post = 'settings' }
|
||||
elsif ($cmd[0] eq "sendScheduleFromAttributeToMower" && AttrVal( $name, 'mowerSchedule', '')) {
|
||||
|
||||
my $perl = eval { decode_json (AttrVal( $name, 'mowerSchedule', '')) };
|
||||
if ($@) {
|
||||
return "$iam decode error: $@ \n $perl";
|
||||
}
|
||||
my $jsonSchedule = eval { encode_json ($perl) };
|
||||
if ($@) {
|
||||
return "$iam encode error: $@ \n $json";
|
||||
}
|
||||
$json = '{"data":{"type": "calendar","attributes":{"tasks":'.$jsonSchedule.'}}}';
|
||||
$post = 'calendar';
|
||||
}
|
||||
|
||||
Log3 $name, 5, "$iam $header \n $cmd[0] \n $json";
|
||||
|
||||
::HttpUtils_NonblockingGet({
|
||||
url => APIURL . "/mowers/". $mower_id . "/".$post,
|
||||
timeout => 20,
|
||||
hash => $hash,
|
||||
method => "POST",
|
||||
header => $header,
|
||||
data => $json,
|
||||
callback => \&CMDResponse,
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
#########################
|
||||
sub CMDResponse {
|
||||
my ($param, $err, $data) = @_;
|
||||
my $hash = $param->{hash};
|
||||
my $name = $hash->{NAME};
|
||||
my $type = $hash->{TYPE};
|
||||
my $statuscode = $param->{code};
|
||||
my $iam = "$type $name CMDResponse:";
|
||||
|
||||
Log3 $name, 1, "\ndebug $iam \n\$statuscode [$statuscode]\n\$err [$err],\n \$data [$data] \n\$param->url $param->{url}" if ( AttrVal($name, 'debug', '') );
|
||||
|
||||
if( !$err && $statuscode == 202 && $data ) {
|
||||
|
||||
my $result = eval { decode_json($data) };
|
||||
if ($@) {
|
||||
|
||||
Log3( $name, 2, "$iam - JSON error while request: $@");
|
||||
|
||||
} else {
|
||||
|
||||
$hash->{helper}{CMDResponse} = $result;
|
||||
if ($result->{data}) {
|
||||
|
||||
Log3 $name, 5, $data;
|
||||
if ( ref ($result->{data}) eq 'ARRAY') {
|
||||
|
||||
$hash->{helper}->{mower_commandStatus} = 'OK - '. $result->{data}[0]{type};
|
||||
|
||||
} else {
|
||||
|
||||
$hash->{helper}->{mower_commandStatus} = 'OK - '. $result->{data}{type};
|
||||
|
||||
}
|
||||
|
||||
readingsSingleUpdate($hash, 'mower_commandStatus', $hash->{helper}->{mower_commandStatus} ,1);
|
||||
return undef;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
readingsSingleUpdate($hash, 'mower_commandStatus', "ERROR statuscode $statuscode" ,1);
|
||||
Log3 $name, 2, "\n$iam \n\$statuscode [$statuscode]\n\$err [$err],\n\$data [$data]\n\$param->url $param->{url}";
|
||||
return undef;
|
||||
}
|
||||
|
||||
|
||||
sub Set {
|
||||
my ($hash,@val) = @_;
|
||||
my $type = $hash->{TYPE};
|
||||
@ -781,7 +469,13 @@ sub Set {
|
||||
|
||||
my $xm = $hash->{helper}{chargingStation}{longitude} // 10.1165;
|
||||
my $ym = $hash->{helper}{chargingStation}{latitude} // 51.28;
|
||||
CommandAttr($hash,"$name chargingStationCoordinates $xm $ym");
|
||||
CommandAttr( $hash, "$name chargingStationCoordinates $xm $ym" );
|
||||
return undef;
|
||||
|
||||
} elsif ( $setName eq 'defaultDesignAttributesToAttribute' ) {
|
||||
|
||||
my $design = $hash->{helper}{mapdesign};
|
||||
CommandAttr( $hash, "$name mapDesignAttributes $design" );
|
||||
return undef;
|
||||
|
||||
} elsif ( ReadingsVal( $name, 'state', 'defined' ) !~ /defined|initialized|authentification|authenticated|update/ && $setName eq 'mowerScheduleToAttribute' ) {
|
||||
@ -806,13 +500,17 @@ sub Set {
|
||||
|
||||
} elsif ( ReadingsVal( $name, 'state', 'defined' ) !~ /defined|initialized|authentification|authenticated|update/ && $setName =~ /^(Start|Park|cuttingHeight)$/ ) {
|
||||
if ( $setVal =~ /^(\d+)$/) {
|
||||
sendCMD($hash ,$setName, $setVal);
|
||||
|
||||
::FHEM::Devices::AMConnect::Common::CMD($hash ,$setName, $setVal);
|
||||
return undef;
|
||||
|
||||
}
|
||||
|
||||
} elsif ( ReadingsVal( $name, 'state', 'defined' ) !~ /defined|initialized|authentification|authenticated|update/ && $setName eq 'headlight' ) {
|
||||
if ( $setVal =~ /^(ALWAYS_OFF|ALWAYS_ON|EVENING_ONLY|EVENING_AND_NIGHT)$/) {
|
||||
sendCMD($hash ,$setName, $setVal);
|
||||
|
||||
::FHEM::Devices::AMConnect::Common::CMD($hash ,$setName, $setVal);
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
@ -829,12 +527,14 @@ sub Set {
|
||||
return undef;
|
||||
|
||||
} elsif (ReadingsVal( $name, 'state', 'defined' ) !~ /defined|initialized|authentification|authenticated|update/ && $setName =~ /ParkUntilFurtherNotice|ParkUntilNextSchedule|Pause|ResumeSchedule|sendScheduleFromAttributeToMower/) {
|
||||
sendCMD($hash,$setName);
|
||||
|
||||
::FHEM::Devices::AMConnect::Common::CMD($hash,$setName);
|
||||
return undef;
|
||||
|
||||
}
|
||||
my $ret = " getNewAccessToken:noArg ParkUntilFurtherNotice:noArg ParkUntilNextSchedule:noArg Pause:noArg Start:selectnumbers,60,60,600,0,lin Park:selectnumbers,60,60,600,0,lin ResumeSchedule:noArg getUpdate:noArg client_secret ";
|
||||
$ret .= "chargingStationPositionToAttribute:noArg headlight:ALWAYS_OFF,ALWAYS_ON,EVENING_ONLY,EVENING_AND_NIGHT cuttingHeight:1,2,3,4,5,6,7,8,9 mowerScheduleToAttribute:noArg ";
|
||||
$ret .= "sendScheduleFromAttributeToMower:noArg ";
|
||||
$ret .= "sendScheduleFromAttributeToMower:noArg defaultDesignAttributesToAttribute:noArg ";
|
||||
return "Unknown argument $setName, choose one of".$ret;
|
||||
|
||||
}
|
||||
@ -1021,11 +721,6 @@ sub Attr {
|
||||
return undef;
|
||||
}
|
||||
|
||||
#########################
|
||||
sub FmtDateTimeGMT {
|
||||
my $ret = POSIX::strftime( "%F %H:%M:%S", gmtime( shift ) );
|
||||
}
|
||||
|
||||
##############################################################
|
||||
|
||||
|
||||
@ -1207,7 +902,15 @@ __END__
|
||||
|
||||
<li><a id='AutomowerConnect-attr-mapImageZoom'>mapImageZoom</a><br>
|
||||
<code>attr <name> mapImageZoom <zoom factor></code><br>
|
||||
Zoom of a raster image for an area the mower path has to be drawn to. Default: 0.5</li>
|
||||
Zoom of a raster image for an area the mower path has to be drawn to.</li>
|
||||
|
||||
<li><a id='AutomowerConnect-attr-mapBackgroundColor'>mapBackgroundColor</a><br>
|
||||
<code>attr <name> mapBackgroundColor <background-color></code><br>
|
||||
The value is used as background-color.</li>
|
||||
|
||||
<li><a id='AutomowerConnect-attr-mapDesignAttributes'>mapDesignAttributes</a><br>
|
||||
<code>attr <name> mapDesignAttributes <complete list of design-attributes></code><br>
|
||||
Load the list of attributes by <code>set <name> defaultDesignAttributesToAttribute</code> to change its values</li>
|
||||
|
||||
<li><a id='AutomowerConnect-attr-mapImageCoordinatesToRegister'>mapImageCoordinatesToRegister</a><br>
|
||||
<code>attr <name> mapImageCoordinatesToRegister <upper left longitude><space><upper left latitude><line feed><lower right longitude><space><lower right latitude></code><br>
|
||||
@ -1470,6 +1173,14 @@ __END__
|
||||
<code>attr <name> mapImageZoom <zoom factor></code><br>
|
||||
Zoomfaktor zur Salierung des Bildes auf das Pfad, Anfangs- u. Endpunkte gezeichnet werden. Standard: 0.5</li>
|
||||
|
||||
<li><a id='AutomowerConnect-attr-mapBackgroundColor'>mapBackgroundColor</a><br>
|
||||
<code>attr <name> mapBackgroundColor <color value></code><br>
|
||||
Der Wert wird als Hintergrungfarbe benutzt.</li>
|
||||
|
||||
<li><a id='AutomowerConnect-attr-mapDesignAttributes'>mapDesignAttributes</a><br>
|
||||
<code>attr <name> mapDesignAttributes <complete list of design-attributes></code><br>
|
||||
Lade die Attributliste mit <code>set <name> defaultDesignAttributesToAttribute</code> um die Werte zu ändern.</li>
|
||||
|
||||
<li><a id='AutomowerConnect-attr-mapImageCoordinatesToRegister'>mapImageCoordinatesToRegister</a><br>
|
||||
<code>attr <name> mapImageCoordinatesToRegister <upper left longitude><space><upper left latitude><line feed><lower right longitude><space><lower right latitude></code><br>
|
||||
Obere linke und untere rechte Ecke der Fläche auf der Erde, die durch das Bild dargestellt wird um das Bild auf der Fläche zu registrieren (oder einzupassen).<br>
|
||||
@ -1510,7 +1221,7 @@ __END__
|
||||
|
||||
<li><a id='AutomowerConnect-attr-propertyLimits'>propertyLimits</a><br>
|
||||
<code>attr <name> propertyLimits <positions list></code><br>
|
||||
Liste von Positionen, um die Grundstücksgrenze zu beschreiben. Format: Zeilenweise Paare von Longitude- u. Latitudewerten getrennt durch 1 Leerzeichen. Eine Zeile wird aufgeteilt durch (<code>/\s|\R$/</code>).<br>Die genaue Position der Grenzpunkte kann man über die <a target="_blank" href="https://geoportal.de/Anwendungen/Geoportale%20der%20L%C3%A4nder.html">Geoportale der Länder</a> finden. Eine Umrechnung der UTM32 Daten in Meter nach ETRS89 in Dezimalgrad kann über das <a href"https://gdz.bkg.bund.de/koordinatentransformation">BKG-Geodatenzentrum</a> erfolgen.</li>
|
||||
Liste von Positionen, um die Grundstücksgrenze zu beschreiben. Format: Zeilenweise Paare von Longitude- u. Latitudewerten getrennt durch 1 Leerzeichen. Eine Zeile wird aufgeteilt durch (<code>/\s|\R$/</code>).<br>Die genaue Position der Grenzpunkte kann man über die <a target="_blank" href="https://geoportal.de/Anwendungen/Geoportale%20der%20L%C3%A4nder.html">Geoportale der Länder</a> finden. Eine Umrechnung der UTM32 Daten in Meter nach ETRS89 in Dezimalgrad kann über das <a target="_blank" href="https://gdz.bkg.bund.de/koordinatentransformation">BKG-Geodatenzentrum</a> erfolgen.</li>
|
||||
|
||||
<li><a id='AutomowerConnect-attr-numberOfWayPointsToDisplay'>numberOfWayPointsToDisplay</a><br>
|
||||
<code>attr <name> numberOfWayPointsToDisplay <number of way points></code><br>
|
||||
|
@ -22,6 +22,7 @@
|
||||
################################################################################
|
||||
|
||||
package FHEM::AutomowerConnectDevice;
|
||||
my $cvsid = '$Id$';
|
||||
use strict;
|
||||
use warnings;
|
||||
use POSIX;
|
||||
@ -80,19 +81,19 @@ eval "use JSON;1" or $missingModul .= "JSON ";
|
||||
require HttpUtils;
|
||||
require FHEM::Devices::AMConnect::Common;
|
||||
|
||||
use constant APIURL => 'https://api.amc.husqvarna.dev/v1';
|
||||
|
||||
##############################################################
|
||||
|
||||
sub Initialize() {
|
||||
my ($hash) = @_;
|
||||
|
||||
$hash->{SetFn} = \&Set;
|
||||
$hash->{GetFn} = \&FHEM::Devices::AMConnect::Common::Get;
|
||||
$hash->{DefFn} = \&Define;
|
||||
$hash->{DefFn} = \&FHEM::Devices::AMConnect::Common::Define;
|
||||
$hash->{UndefFn} = \&FHEM::Devices::AMConnect::Common::Undefine;
|
||||
$hash->{NotifyFn} = \&Notify;
|
||||
$hash->{RenameFn} = \&FHEM::Devices::AMConnect::Common::Rename;
|
||||
$hash->{GetFn} = \&FHEM::Devices::AMConnect::Common::Get;
|
||||
$hash->{FW_detailFn}= \&FHEM::Devices::AMConnect::Common::FW_detailFn;
|
||||
$hash->{SetFn} = \&Set;
|
||||
$hash->{NotifyFn} = \&Notify;
|
||||
$hash->{AttrFn} = \&Attr;
|
||||
$hash->{AttrList} = "disable:1,0 " .
|
||||
"debug:1,0 " .
|
||||
@ -102,6 +103,8 @@ sub Initialize() {
|
||||
"mapImageCoordinatesToRegister:textField-long " .
|
||||
"mapImageCoordinatesUTM:textField-long " .
|
||||
"mapImageZoom " .
|
||||
"mapBackgroundColor " .
|
||||
"mapDesignAttributes:textField-long " .
|
||||
"showMap:1,0 " .
|
||||
"chargingStationCoordinates " .
|
||||
"chargingStationImagePosition:left,top,right,bottom,center " .
|
||||
@ -116,125 +119,6 @@ sub Initialize() {
|
||||
return undef;
|
||||
}
|
||||
|
||||
|
||||
##############################################################
|
||||
#
|
||||
# DEFINE
|
||||
#
|
||||
##############################################################
|
||||
|
||||
sub Define{
|
||||
my ( $hash, $def ) = @_;
|
||||
my @val = split( "[ \t]+", $def );
|
||||
my $name = $val[0];
|
||||
my $type = $val[1];
|
||||
my $iam = "$type $name Define:";
|
||||
|
||||
return "$iam too few parameters: define <NAME> $type <host name> <mower number>" if( @val < 4 ) ;
|
||||
return "$iam Cannot define $type device. Perl modul $missingModul is missing." if ( $missingModul );
|
||||
my $hostname =$val[2];
|
||||
my $mowerNumber = $val[3];
|
||||
|
||||
::notifyRegexpChanged($hash, $hostname.':state:.connected');
|
||||
|
||||
%$hash = (%$hash,
|
||||
helper => {
|
||||
hostname => $hostname,
|
||||
mowerNumber => $mowerNumber,
|
||||
scaleToMeterLongitude => 67425,
|
||||
scaleToMeterLatitude => 108886,
|
||||
minLon => 180,
|
||||
maxLon => -180,
|
||||
minLat => 90,
|
||||
maxLat => -90,
|
||||
imageHeight => 650,
|
||||
imageWidthHeight => '350 650',
|
||||
posMinMax => "-180 90\n180 -90",
|
||||
newdatasets => 0,
|
||||
MAP_PATH => '',
|
||||
MAP_MIME => '',
|
||||
MAP_CACHE => '',
|
||||
cspos => [],
|
||||
areapos => [],
|
||||
searchpos => [],
|
||||
UNKNOWN => {
|
||||
arrayName => '',
|
||||
maxLength => 0,
|
||||
callFn => ''
|
||||
},
|
||||
NOT_APPLICABLE => {
|
||||
arrayName => '',
|
||||
maxLength => 0,
|
||||
callFn => ''
|
||||
},
|
||||
MOWING => {
|
||||
arrayName => 'areapos',
|
||||
maxLength => 500,
|
||||
maxLengthDefault => 500,
|
||||
callFn => \&FHEM::Devices::AMConnect::Common::AreaStatistics
|
||||
},
|
||||
GOING_HOME => {
|
||||
arrayName => '',
|
||||
maxLength => 0,
|
||||
callFn => ''
|
||||
},
|
||||
CHARGING => {
|
||||
arrayName => 'cspos',
|
||||
maxLength => 100,
|
||||
callFn => \&FHEM::Devices::AMConnect::Common::ChargingStationPosition
|
||||
},
|
||||
LEAVING => {
|
||||
arrayName => '',
|
||||
maxLength => 0,
|
||||
callFn => ''
|
||||
},
|
||||
PARKED_IN_CS => {
|
||||
arrayName => 'cspos',
|
||||
maxLength => 100,
|
||||
callFn => \&FHEM::Devices::AMConnect::Common::ChargingStationPosition
|
||||
},
|
||||
STOPPED_IN_GARDEN => {
|
||||
arrayName => '',
|
||||
maxLength => 0,
|
||||
callFn => ''
|
||||
},
|
||||
statistics => {
|
||||
currentSpeed => 0,
|
||||
currentDayTrack => 0,
|
||||
currentDayArea => 0,
|
||||
lastDayTrack => 0,
|
||||
lastDayArea => 0,
|
||||
currentWeekTrack => 0,
|
||||
currentWeekArea => 0,
|
||||
lastWeekTrack => 0,
|
||||
lastWeekArea => 0
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
$hash->{MODEL} = '';
|
||||
$attr{$name}{room} = 'AutomowerConnect' if( !defined( $attr{$name}{room} ) );
|
||||
$attr{$name}{icon} = 'automower' if( !defined( $attr{$name}{icon} ) );
|
||||
my ($modname) = __FILE__ =~ /(\d\d_.*\.pm)/;
|
||||
|
||||
if (::AnalyzeCommandChain(undef,"version $modname noheader") =~ "^$modname (.*)Z") {
|
||||
$hash->{VERSION}=$1;
|
||||
}
|
||||
|
||||
RemoveInternalTimer($hash);
|
||||
InternalTimer( gettimeofday() + 25, \&FHEM::Devices::AMConnect::Common::readMap, $hash, 0);
|
||||
|
||||
::FHEM::Devices::AMConnect::Common::AddExtension( $name, \&FHEM::Devices::AMConnect::Common::GetMap, "$type/$name/map" );
|
||||
|
||||
|
||||
readingsSingleUpdate( $hash, 'state', 'defined', 1 );
|
||||
|
||||
return undef;
|
||||
|
||||
}
|
||||
|
||||
|
||||
##############################################################
|
||||
#
|
||||
# GET MOWER
|
||||
@ -249,6 +133,7 @@ sub Notify {
|
||||
my $iam = "$type $name Notify:";
|
||||
my $mowerNumber = $hash->{helper}{mowerNumber};
|
||||
my $events = ::deviceEvents($hosthash,1);
|
||||
( $hash->{VERSION} ) = $cvsid =~ /\.pm (.*)Z/ if ( !$hash->{VERSION} );
|
||||
|
||||
if ( IsDisabled($name) ) {
|
||||
|
||||
@ -276,8 +161,8 @@ sub Notify {
|
||||
} else { # first data set
|
||||
|
||||
$hash->{helper}{mowerold} = $myMower;
|
||||
|
||||
$hash->{helper}{searchpos} = [ dclone( $hash->{helper}{mowerold}{attributes}{positions}[0] ), dclone( $hash->{helper}{mowerold}{attributes}{positions}[1] ) ];
|
||||
$hash->{helper}{timestamps}[ 0 ] = $hash->{helper}{mowerold}{attributes}{metadata}{statusTimestamp};
|
||||
|
||||
if ( AttrVal( $name, 'mapImageCoordinatesToRegister', '' ) eq '' ) {
|
||||
::FHEM::Devices::AMConnect::Common::posMinMax( $hash, $hash->{helper}{mowerold}{attributes}{positions} );
|
||||
@ -288,10 +173,12 @@ sub Notify {
|
||||
$hash->{helper}{mower} = $myMower;
|
||||
# add alignment data set to the end
|
||||
push( @{ $hash->{helper}{mower}{attributes}{positions} }, @{ dclone( $hash->{helper}{searchpos} ) } );
|
||||
$hash->{helper}{newdatasets} = 0;
|
||||
$hash->{helper}{newdatasets} = 0;
|
||||
|
||||
my $storediff = $hash->{helper}{mower}{attributes}{metadata}{statusTimestamp} - $hash->{helper}{mowerold}{attributes}{metadata}{statusTimestamp};
|
||||
if ($storediff) {
|
||||
# collect timestamps for analysis
|
||||
unshift ( @{ $hash->{helper}{timestamps} }, $hash->{helper}{mower}{attributes}{metadata}{statusTimestamp} );
|
||||
|
||||
::FHEM::Devices::AMConnect::Common::AlignArray( $hash );
|
||||
::FHEM::Devices::AMConnect::Common::FW_detailFn_Update ($hash) if (AttrVal($name,'showMap',1));
|
||||
@ -310,7 +197,7 @@ sub Notify {
|
||||
readingsBulkUpdateIfChanged($hash, $pref.'_commandStatus', 'cleared' );
|
||||
|
||||
my $tstamp = $hash->{helper}{mower}{attributes}{$pref}{errorCodeTimestamp};
|
||||
my $timestamp = FmtDateTimeGMT($tstamp/1000);
|
||||
my $timestamp = ::FHEM::Devices::AMConnect::Common::FmtDateTimeGMT($tstamp/1000);
|
||||
readingsBulkUpdateIfChanged($hash, $pref."_errorCodeTimestamp", $tstamp ? $timestamp : '-' );
|
||||
|
||||
my $errc = $hash->{helper}{mower}{attributes}{$pref}{errorCode};
|
||||
@ -323,15 +210,15 @@ sub Notify {
|
||||
readingsBulkUpdateIfChanged($hash, $pref."_name", $hash->{helper}{mower}{attributes}{$pref}{name} );
|
||||
my $model = $hash->{helper}{mower}{attributes}{$pref}{model};
|
||||
$model =~ s/AUTOMOWER./AM/;
|
||||
$hash->{MODEL} = $model if ( $model && $hash->{MODEL} ne $model );
|
||||
# readingsBulkUpdateIfChanged($hash, $pref."_model", $model );
|
||||
# $hash->{MODEL} = '' if (!defined $hash->{MODEL});
|
||||
$hash->{MODEL} = $model if ( $model and $hash->{MODEL} ne $model );
|
||||
readingsBulkUpdateIfChanged($hash, $pref."_serialNumber", $hash->{helper}{mower}{attributes}{$pref}{serialNumber} );
|
||||
$pref = 'planner';
|
||||
readingsBulkUpdateIfChanged($hash, "planner_restrictedReason", $hash->{helper}{mower}{attributes}{$pref}{restrictedReason} );
|
||||
readingsBulkUpdateIfChanged($hash, "planner_overrideAction", $hash->{helper}{mower}{attributes}{$pref}{override}{action} );
|
||||
|
||||
$tstamp = $hash->{helper}{mower}{attributes}{$pref}{nextStartTimestamp};
|
||||
$timestamp = FmtDateTimeGMT($tstamp/1000);
|
||||
$timestamp = ::FHEM::Devices::AMConnect::Common::FmtDateTimeGMT($tstamp/1000);
|
||||
readingsBulkUpdateIfChanged($hash, "planner_nextStart", $tstamp ? $timestamp : '-' );
|
||||
$pref = 'statistics';
|
||||
readingsBulkUpdateIfChanged($hash, $pref."_numberOfCollisions", $hash->{helper}->{mower}{attributes}{$pref}{numberOfCollisions} );
|
||||
@ -340,7 +227,8 @@ sub Notify {
|
||||
readingsBulkUpdateIfChanged($hash, $pref."_headlight", $hash->{helper}->{mower}{attributes}{$pref}{headlight}{mode} );
|
||||
readingsBulkUpdateIfChanged($hash, $pref."_cuttingHeight", $hash->{helper}->{mower}{attributes}{$pref}{cuttingHeight} );
|
||||
$pref = 'status';
|
||||
readingsBulkUpdateIfChanged($hash, $pref."_connected", ( $hash->{helper}{mower}{attributes}{metadata}{connected} ? 'CONNECTED' : 'OFFLINE' ) );
|
||||
my $connected = $hash->{helper}{mower}{attributes}{metadata}{connected};
|
||||
readingsBulkUpdateIfChanged($hash, $pref."_connected", ( $connected ? "CONNECTED($connected)" : "OFFLINE($connected)") );
|
||||
readingsBulkUpdateIfChanged($hash, $pref."_Timestamp", FmtDateTime( $hash->{helper}{mower}{attributes}{metadata}{statusTimestamp}/1000 ));
|
||||
readingsBulkUpdateIfChanged($hash, $pref."_TimestampDiff", $storediff/1000 );
|
||||
readingsBulkUpdateIfChanged($hash, $pref."_TimestampOld", FmtDateTime( $hash->{helper}{mowerold}{attributes}{metadata}{statusTimestamp}/1000 ));
|
||||
@ -361,7 +249,7 @@ sub Notify {
|
||||
$hash->{helper}{statistics}{currentDayTrack} = 0;
|
||||
$hash->{helper}{statistics}{currentDayArea} = 0;
|
||||
# do on mondays
|
||||
if ( $time[6] == 1 && $secs <= $interval ) {
|
||||
if ( $time[6] == 1 ) {
|
||||
|
||||
$hash->{helper}{statistics}{lastWeekTrack} = $hash->{helper}{statistics}{currentWeekTrack};
|
||||
$hash->{helper}{statistics}{lastWeekArea} = $hash->{helper}{statistics}{currentWeekArea};
|
||||
@ -379,129 +267,6 @@ sub Notify {
|
||||
}
|
||||
|
||||
|
||||
##############################################################
|
||||
#
|
||||
# SEND COMMAND
|
||||
#
|
||||
##############################################################
|
||||
|
||||
sub CMD {
|
||||
my ($hash,@cmd) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
my $type = $hash->{TYPE};
|
||||
my $iam = "$type $name CMD:";
|
||||
my $hostname = $hash->{helper}{hostname};
|
||||
my $hosthash = $defs{$hostname};
|
||||
|
||||
if ( IsDisabled($hostname) ) {
|
||||
|
||||
Log3 $name, 3, "$iam Host $hostname disabled";
|
||||
return undef
|
||||
|
||||
}
|
||||
if ( IsDisabled($name) ) {
|
||||
|
||||
Log3 $name, 3, "$iam disabled";
|
||||
return undef
|
||||
|
||||
}
|
||||
|
||||
my $client_id = $hosthash->{helper}->{client_id};
|
||||
my $token = ReadingsVal($hostname,".access_token","");
|
||||
my $provider = ReadingsVal($hostname,".provider","");
|
||||
my $mower_id = $hash->{helper}{mower}{id};
|
||||
|
||||
my $json = '';
|
||||
my $post = '';
|
||||
|
||||
|
||||
my $header = "Accept: application/vnd.api+json\r\nX-Api-Key: ".$client_id."\r\nAuthorization: Bearer " . $token . "\r\nAuthorization-Provider: " . $provider . "\r\nContent-Type: application/vnd.api+json";
|
||||
|
||||
|
||||
if ($cmd[0] eq "ParkUntilFurtherNotice") { $json = '{"data":{"type":"'.$cmd[0].'"}}'; $post = 'actions' }
|
||||
elsif ($cmd[0] eq "ParkUntilNextSchedule") { $json = '{"data": {"type":"'.$cmd[0].'"}}'; $post = 'actions' }
|
||||
elsif ($cmd[0] eq "ResumeSchedule") { $json = '{"data": {"type":"'.$cmd[0].'"}}'; $post = 'actions' }
|
||||
elsif ($cmd[0] eq "Pause") { $json = '{"data": {"type":"'.$cmd[0].'"}}'; $post = 'actions' }
|
||||
elsif ($cmd[0] eq "Park") { $json = '{"data": {"type":"'.$cmd[0].'","attributes":{"duration":'.$cmd[1].'}}}'; $post = 'actions' }
|
||||
elsif ($cmd[0] eq "Start") { $json = '{"data": {"type":"'.$cmd[0].'","attributes":{"duration":'.$cmd[1].'}}}'; $post = 'actions' }
|
||||
elsif ($cmd[0] eq "headlight") { $json = '{"data": {"type":"settings","attributes":{"'.$cmd[0].'": {"mode": "'.$cmd[1].'"}}}}'; $post = 'settings' }
|
||||
elsif ($cmd[0] eq "cuttingHeight") { $json = '{"data": {"type":"settings","attributes":{"'.$cmd[0].'": '.$cmd[1].'}}}'; $post = 'settings' }
|
||||
elsif ($cmd[0] eq "sendScheduleFromAttributeToMower" && AttrVal( $name, 'mowerSchedule', '')) {
|
||||
|
||||
my $perl = eval { decode_json (AttrVal( $name, 'mowerSchedule', '')) };
|
||||
if ($@) {
|
||||
return "$iam decode error: $@ \n $perl";
|
||||
}
|
||||
my $jsonSchedule = eval { encode_json ($perl) };
|
||||
if ($@) {
|
||||
return "$iam encode error: $@ \n $json";
|
||||
}
|
||||
$json = '{"data":{"type": "calendar","attributes":{"tasks":'.$jsonSchedule.'}}}';
|
||||
$post = 'calendar';
|
||||
}
|
||||
|
||||
Log3 $name, 5, "$iam $header \n $cmd[0] \n $json";
|
||||
|
||||
::HttpUtils_NonblockingGet({
|
||||
url => APIURL . "/mowers/". $mower_id . "/".$post,
|
||||
timeout => 10,
|
||||
hash => $hash,
|
||||
method => "POST",
|
||||
header => $header,
|
||||
data => $json,
|
||||
callback => \&CMDResponse,
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
##############################################################
|
||||
sub CMDResponse {
|
||||
my ($param, $err, $data) = @_;
|
||||
my $hash = $param->{hash};
|
||||
my $name = $hash->{NAME};
|
||||
my $type = $hash->{TYPE};
|
||||
my $statuscode = $param->{code};
|
||||
my $iam = "$type $name CMDResponse:";
|
||||
|
||||
Log3 $name, 1, "\ndebug $iam \n\$statuscode [$statuscode]\n\$err [$err],\n \$data [$data] \n\$param->url $param->{url}" if ( AttrVal($name, 'debug', '') );
|
||||
|
||||
if( !$err && $statuscode == 202 && $data ) {
|
||||
|
||||
my $result = eval { decode_json($data) };
|
||||
if ($@) {
|
||||
|
||||
Log3( $name, 2, "$iam - JSON error while request: $@");
|
||||
|
||||
} else {
|
||||
|
||||
$hash->{helper}{CMDResponse} = $result;
|
||||
if ($result->{data}) {
|
||||
|
||||
Log3 $name, 5, $data;
|
||||
if ( ref ($result->{data}) eq 'ARRAY') {
|
||||
|
||||
$hash->{helper}->{mower_commandStatus} = 'OK - '. $result->{data}[0]{type};
|
||||
|
||||
} else {
|
||||
|
||||
$hash->{helper}->{mower_commandStatus} = 'OK - '. $result->{data}{type};
|
||||
|
||||
}
|
||||
|
||||
readingsSingleUpdate($hash, 'mower_commandStatus', $hash->{helper}->{mower_commandStatus} ,1);
|
||||
return undef;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
readingsSingleUpdate($hash, 'mower_commandStatus', "ERROR statuscode $statuscode" ,1);
|
||||
Log3 $name, 2, "\n$iam \n\$statuscode [$statuscode]\n\$err [$err],\n\$data [$data]\n\$param->url $param->{url}";
|
||||
return undef;
|
||||
}
|
||||
|
||||
##############################################################
|
||||
sub Set {
|
||||
my ($hash,@a) = @_;
|
||||
@ -530,28 +295,41 @@ sub Set {
|
||||
CommandAttr($hash,"$name mowerSchedule $calendarjson");
|
||||
return undef;
|
||||
|
||||
################
|
||||
} elsif ( $setName eq 'defaultDesignAttributesToAttribute' ) {
|
||||
|
||||
my $design = $hash->{helper}{mapdesign};
|
||||
CommandAttr( $hash, "$name mapDesignAttributes $design" );
|
||||
return undef;
|
||||
|
||||
################
|
||||
} elsif ( ReadingsVal( $name, 'state', 'defined' ) !~ /defined|initialized/ && $setName =~ /^(Start|Park|cuttingHeight)$/ ) {
|
||||
if ( $setVal =~ /^(\d+)$/) {
|
||||
CMD($hash ,$setName, $setVal);
|
||||
|
||||
::FHEM::Devices::AMConnect::Common::CMD($hash ,$setName, $setVal);
|
||||
return undef;
|
||||
|
||||
}
|
||||
|
||||
################
|
||||
} elsif ( ReadingsVal( $name, 'state', 'defined' ) !~ /defined|initialized/ && $setName eq 'headlight' ) {
|
||||
if ( $setVal =~ /^(ALWAYS_OFF|ALWAYS_ON|EVENING_ONLY|EVENING_AND_NIGHT)$/) {
|
||||
CMD($hash ,$setName, $setVal);
|
||||
|
||||
::FHEM::Devices::AMConnect::Common::CMD($hash ,$setName, $setVal);
|
||||
return undef;
|
||||
|
||||
}
|
||||
|
||||
################
|
||||
} elsif (ReadingsVal( $name, 'state', 'defined' ) !~ /defined|initialized/ && $setName =~ /ParkUntilFurtherNotice|ParkUntilNextSchedule|Pause|ResumeSchedule|sendScheduleFromAttributeToMower/) {
|
||||
CMD($hash,$setName);
|
||||
|
||||
::FHEM::Devices::AMConnect::Common::CMD($hash,$setName);
|
||||
return undef;
|
||||
|
||||
}
|
||||
my $ret = " ParkUntilFurtherNotice:noArg ParkUntilNextSchedule:noArg Pause:noArg Start:selectnumbers,60,60,600,0,lin Park:selectnumbers,60,60,600,0,lin ResumeSchedule:noArg ";
|
||||
$ret .= "chargingStationPositionToAttribute:noArg headlight:ALWAYS_OFF,ALWAYS_ON,EVENING_ONLY,EVENING_AND_NIGHT cuttingHeight:1,2,3,4,5,6,7,8,9 mowerScheduleToAttribute:noArg ";
|
||||
$ret .= "sendScheduleFromAttributeToMower:noArg ";
|
||||
$ret .= "sendScheduleFromAttributeToMower:noArg defaultDesignAttributesToAttribute:noArg ";
|
||||
return "Unknown argument $setName, choose one of".$ret;
|
||||
|
||||
}
|
||||
@ -729,14 +507,9 @@ sub Attr {
|
||||
return undef;
|
||||
}
|
||||
|
||||
#########################
|
||||
sub FmtDateTimeGMT {
|
||||
my $ret = POSIX::strftime( "%F %H:%M:%S", gmtime( shift ) );
|
||||
}
|
||||
|
||||
|
||||
##############################################################
|
||||
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
@ -884,6 +657,14 @@ __END__
|
||||
<code>attr <name> mapImageZoom <height in pixel></code><br>
|
||||
Zoom of a raster image for an area the mower path has to be drawn to. Default: 0.5</li>
|
||||
|
||||
<li><a id='AutomowerConnectDevice-attr-mapBackgroundColor'>mapBackgroundColor</a><br>
|
||||
<code>attr <name> mapBackgroundColor <color valuer></code><br>
|
||||
The value is used as background-color.</li>
|
||||
|
||||
<li><a id='AutomowerConnectDevice-attr-mapDesignAttributes'>mapDesignAttributes</a><br>
|
||||
<code>attr <name> mapDesignAttributes <complete list of design-attributes></code><br>
|
||||
Load the list of attributes by <code>set <name> defaultDesignAttributesToAttribute</code> to change its values</li>
|
||||
|
||||
<li><a id='AutomowerConnectDevice-attr-mapImageCoordinatesToRegister'>mapImageCoordinatesToRegister</a><br>
|
||||
<code>attr <name> mapImageCoordinatesToRegister <upper left longitude><space><upper left latitude><line feed><lower right longitude><space><lower right latitude></code><br>
|
||||
Upper left and lower right coordinates to register (or to fit to earth) the image. Format: linewise longitude and latitude values separated by 1 space.<br>
|
||||
@ -1111,14 +892,18 @@ __END__
|
||||
<code>attr <name> mapImageWidthHeight <width in pixel><separator><height in pixel></code><br>
|
||||
Bildbreite in Pixel des Bildes auf das Pfad, Anfangs- u. Endpunkte gezeichnet werden. <separator> ist 1 Leerzeichen.</li>
|
||||
|
||||
<li><a id='AutomowerConnectDevice-attr-mapImageHeight'>mapImageHeight</a><br>
|
||||
<code>attr <name> <></code><br>
|
||||
Bildhöhe in Pixel des Bildes auf das Pfad, Anfangs- u. Endpunkte gezeichnet werden.</li>
|
||||
|
||||
<li><a id='AutomowerConnectDevice-attr-mapImageZoom'>mapImageZoom</a><br>
|
||||
<code>attr <name> mapImageHeight <height in pixel></code><br>
|
||||
Zoomfaktor zur Salierung des Bildes auf das Pfad, Anfangs- u. Endpunkte gezeichnet werden. Standard: 0.5</li>
|
||||
|
||||
<li><a id='AutomowerConnectDevice-attr-mapBackgroundColor'>mapBackgroundColor</a><br>
|
||||
<code>attr <name> mapBackgroundColor <color value></code><br>
|
||||
Der Wert wird als Hintergrungfarbe benutzt.</li>
|
||||
|
||||
<li><a id='AutomowerConnectDevice-attr-mapDesignAttributes'>mapDesignAttributes</a><br>
|
||||
<code>attr <name> mapDesignAttributes <complete list of design-attributes></code><br>
|
||||
Lade die Attributliste mit <code>set <name> defaultDesignAttributesToAttribute</code> um die Werte zu ändern.</li>
|
||||
|
||||
<li><a id='AutomowerConnectDevice-attr-mapImageCoordinatesToRegister'>mapImageCoordinatesToRegister</a><br>
|
||||
<code>attr <name> mapImageCoordinatesToRegister <upper left longitude><space><upper left latitude><line feed><lower right longitude><space><lower right latitude></code><br>
|
||||
Obere linke und untere rechte Ecke der Fläche auf der Erde, die durch das Bild dargestellt wird um das Bild auf der Fläche zu registrieren (oder einzupassen).<br>
|
||||
@ -1159,7 +944,7 @@ __END__
|
||||
|
||||
<li><a id='AutomowerConnectDevice-attr-propertyLimits'>propertyLimits</a><br>
|
||||
<code>attr <name> propertyLimits <positions list></code><br>
|
||||
Liste von Positionen, um die Grundstücksgrenze zu beschreiben. Format: Zeilenweise Paare von Longitude- u. Latitudewerten getrennt durch 1 Leerzeichen. Eine Zeile wird aufgeteilt durch (<code>/\s|,|\R$/</code>).<br>Die genaue Position der Grenzpunkte kann man über die <a target="_blank" href="https://geoportal.de/Anwendungen/Geoportale%20der%20L%C3%A4nder.html">Geoportale der Länder</a> finden. Eine Umrechnung der UTM32 Daten in Meter nach ETRS89 in Dezimalgrad kann über das <a href"https://gdz.bkg.bund.de/koordinatentransformation">BKG-Geodatenzentrum</a> erfolgen.</li>
|
||||
Liste von Positionen, um die Grundstücksgrenze zu beschreiben. Format: Zeilenweise Paare von Longitude- u. Latitudewerten getrennt durch 1 Leerzeichen. Eine Zeile wird aufgeteilt durch (<code>/\s|,|\R$/</code>).<br>Die genaue Position der Grenzpunkte kann man über die <a target="_blank" href="https://geoportal.de/Anwendungen/Geoportale%20der%20L%C3%A4nder.html">Geoportale der Länder</a> finden. Eine Umrechnung der UTM32 Daten in Meter nach ETRS89 in Dezimalgrad kann über das <a target="_href="https://gdz.bkg.bund.de/koordinatentransformation">BKG-Geodatenzentrum</a> erfolgen.</li>
|
||||
|
||||
<li><a id='AutomowerConnectDevice-attr-numberOfWayPointsToDisplay'>numberOfWayPointsToDisplay</a><br>
|
||||
<code>attr <name> numberOfWayPointsToDisplay <number of way points></code><br>
|
||||
|
@ -75,7 +75,7 @@ my $missingModul = "";
|
||||
|
||||
eval "use JSON;1" or $missingModul .= "JSON ";
|
||||
|
||||
my $errorjson = '{"23":"Wheel drive problem, left","24":"Cutting system blocked","123":"Destination not reachable","710":"SIM card locked","50":"Guide 1 not found","717":"SMS could not be sent","108":"Folding cutting deck sensor defect","4":"Loop sensor problem, front","15":"Lifted","29":"Slope too steep","1":"Outside working area","45":"Cutting height problem, dir","52":"Guide 3 not found","28":"Memory circuit problem","95":"Folding sensor activated","9":"Trapped","114":"Too high discharge current","103":"Cutting drive motor 2 defect","65":"Temporary battery problem","119":"Zone generator problem","6":"Loop sensor problem, left","82":"Wheel motor blocked, rear right","714":"Geofence problem","703":"Connectivity problem","708":"SIM card locked","75":"Connection changed","7":"Loop sensor problem, right","35":"Wheel motor overloaded, right","3":"Wrong loop signal","117":"High internal power loss","0":"Unexpected error","80":"Cutting system imbalance - Warning","110":"Collision sensor error","100":"Ultrasonic Sensor 3 defect","79":"Invalid battery combination - Invalid combination of different battery types.","724":"Communication circuit board SW must be updated","86":"Wheel motor overloaded, rear right","81":"Safety function faulty","78":"Slipped - Mower has Slipped.Situation not solved with moving pattern","107":"Docking sensor defect","33":"Mower tilted","69":"Alarm! Mower switched off","68":"Temporary battery problem","34":"Cutting stopped - slope too steep","127":"Battery problem","73":"Alarm! Mower in motion","74":"Alarm! Outside geofence","713":"Geofence problem","87":"Wheel motor overloaded, rear left","120":"Internal voltage error","39":"Cutting motor problem","704":"Connectivity problem","63":"Temporary battery problem","109":"Loop sensor defect","38":"Electronic problem","64":"Temporary battery problem","113":"Complex working area","93":"No accurate position from satellites","104":"Cutting drive motor 3 defect","709":"SIM card not found","94":"Reference station communication problem","43":"Cutting height problem, drive","13":"No drive","44":"Cutting height problem, curr","118":"Charging system problem","14":"Mower lifted","57":"Guide calibration failed","707":"SIM card requires PIN","99":"Ultrasonic Sensor 2 defect","98":"Ultrasonic Sensor 1 defect","51":"Guide 2 not found","56":"Guide calibration accomplished","49":"Ultrasonic problem","2":"No loop signal","124":"Destination blocked","25":"Cutting system blocked","19":"Collision sensor problem, front","18":"Collision sensor problem, rear","48":"No response from charger","105":"Lift Sensor defect","111":"No confirmed position","10":"Upside down","40":"Limited cutting height range","716":"Connectivity problem","27":"Settings restored","90":"No power in charging station","21":"Wheel motor blocked, left","26":"Invalid sub-device combination","92":"Work area not valid","702":"Connectivity settings restored","125":"Battery needs replacement","5":"Loop sensor problem, rear","12":"Empty battery","55":"Difficult finding home","42":"Limited cutting height range","30":"Charging system problem","72":"Alarm! Mower tilted","85":"Wheel drive problem, rear left","8":"Wrong PIN code","62":"Temporary battery problem","102":"Cutting drive motor 1 defect","116":"High charging power loss","122":"CAN error","60":"Temporary battery problem","705":"Connectivity problem","711":"SIM card locked","70":"Alarm! Mower stopped","32":"Tilt sensor problem","37":"Charging current too high","89":"Invalid system configuration","76":"Connection NOT changed","71":"Alarm! Mower lifted","88":"Angular sensor problem","701":"Connectivity problem","715":"Connectivity problem","61":"Temporary battery problem","66":"Battery problem","106":"Collision sensor defect","67":"Battery problem","112":"Cutting system major imbalance","83":"Wheel motor blocked, rear left","84":"Wheel drive problem, rear right","126":"Battery near end of life","77":"Com board not available","36":"Wheel motor overloaded, left","31":"STOP button problem","17":"Charging station blocked","54":"Weak GPS signal","47":"Cutting height problem","53":"GPS navigation problem","121":"High internal temerature","97":"Left brush motor overloaded","712":"SIM card locked","20":"Wheel motor blocked, right","91":"Switch cord problem","96":"Right brush motor overloaded","58":"Temporary battery problem","59":"Temporary battery problem","22":"Wheel drive problem, right","706":"Poor signal quality","41":"Unexpected cutting height adj","46":"Cutting height blocked","11":"Low battery","16":"Stuck in charging station","101":"Ultrasonic Sensor 4 defect","115":"Too high internal current"}';
|
||||
my $errorjson = '{"23":"Wheel drive problem, left","24":"Cutting system blocked","123":"Destination not reachable","710":"SIM card locked","50":"Guide 1 not found","717":"SMS could not be sent","108":"Folding cutting deck sensor defect","4":"Loop sensor problem - front","15":"Lifted","29":"Slope too steep","1":"Outside working area","45":"Cutting height problem - dir","52":"Guide 3 not found","28":"Memory circuit problem","95":"Folding sensor activated","9":"Trapped","114":"Too high discharge current","103":"Cutting drive motor 2 defect","65":"Temporary battery problem","119":"Zone generator problem","6":"Loop sensor problem - left","82":"Wheel motor blocked - rear right","714":"Geofence problem","703":"Connectivity problem","708":"SIM card locked","75":"Connection changed","7":"Loop sensor problem - right","35":"Wheel motor overloaded - right","3":"Wrong loop signal","117":"High internal power loss","0":"Unexpected error","80":"Cutting system imbalance - Warning","110":"Collision sensor error","100":"Ultrasonic Sensor 3 defect","79":"Invalid battery combination - Invalid combination of different battery types.","724":"Communication circuit board SW must be updated","86":"Wheel motor overloaded - rear right","81":"Safety function faulty","78":"Slipped - Mower has Slipped. Situation not solved with moving pattern","107":"Docking sensor defect","33":"Mower tilted","69":"Alarm! Mower switched off","68":"Temporary battery problem","34":"Cutting stopped - slope too steep","127":"Battery problem","73":"Alarm! Mower in motion","74":"Alarm! Outside geofence","713":"Geofence problem","87":"Wheel motor overloaded - rear left","120":"Internal voltage error","39":"Cutting motor problem","704":"Connectivity problem","63":"Temporary battery problem","109":"Loop sensor defect","38":"Electronic problem","64":"Temporary battery problem","113":"Complex working area","93":"No accurate position from satellites","104":"Cutting drive motor 3 defect","709":"SIM card not found","94":"Reference station communication problem","43":"Cutting height problem - drive","13":"No drive","44":"Cutting height problem - curr","118":"Charging system problem","14":"Mower lifted","57":"Guide calibration failed","707":"SIM card requires PIN","99":"Ultrasonic Sensor 2 defect","98":"Ultrasonic Sensor 1 defect","51":"Guide 2 not found","56":"Guide calibration accomplished","49":"Ultrasonic problem","2":"No loop signal","124":"Destination blocked","25":"Cutting system blocked","19":"Collision sensor problem, front","18":"Collision sensor problem - rear","48":"No response from charger","105":"Lift Sensor defect","111":"No confirmed position","10":"Upside down","40":"Limited cutting height range","716":"Connectivity problem","27":"Settings restored","90":"No power in charging station","21":"Wheel motor blocked - left","26":"Invalid sub-device combination","92":"Work area not valid","702":"Connectivity settings restored","125":"Battery needs replacement","5":"Loop sensor problem - rear","12":"Empty battery","55":"Difficult finding home","42":"Limited cutting height range","30":"Charging system problem","72":"Alarm! Mower tilted","85":"Wheel drive problem - rear left","8":"Wrong PIN code","62":"Temporary battery problem","102":"Cutting drive motor 1 defect","116":"High charging power loss","122":"CAN error","60":"Temporary battery problem","705":"Connectivity problem","711":"SIM card locked","70":"Alarm! Mower stopped","32":"Tilt sensor problem","37":"Charging current too high","89":"Invalid system configuration","76":"Connection NOT changed","71":"Alarm! Mower lifted","88":"Angular sensor problem","701":"Connectivity problem","715":"Connectivity problem","61":"Temporary battery problem","66":"Battery problem","106":"Collision sensor defect","67":"Battery problem","112":"Cutting system major imbalance","83":"Wheel motor blocked - rear left","84":"Wheel drive problem - rear right","126":"Battery near end of life","77":"Com board not available","36":"Wheel motor overloaded - left","31":"STOP button problem","17":"Charging station blocked","54":"Weak GPS signal","47":"Cutting height problem","53":"GPS navigation problem","121":"High internal temerature","97":"Left brush motor overloaded","712":"SIM card locked","20":"Wheel motor blocked - right","91":"Switch cord problem","96":"Right brush motor overloaded","58":"Temporary battery problem","59":"Temporary battery problem","22":"Wheel drive problem - right","706":"Poor signal quality","41":"Unexpected cutting height adj","46":"Cutting height blocked","11":"Low battery","16":"Stuck in charging station","101":"Ultrasonic Sensor 4 defect","115":"Too high internal current"}';
|
||||
|
||||
our $errortable = eval { decode_json ( $errorjson ) };
|
||||
if ($@) {
|
||||
@ -83,6 +83,198 @@ if ($@) {
|
||||
}
|
||||
$errorjson = undef;
|
||||
|
||||
use constant AUTHURL => 'https://api.authentication.husqvarnagroup.dev/v1';
|
||||
use constant APIURL => 'https://api.amc.husqvarna.dev/v1';
|
||||
|
||||
##############################################################
|
||||
#
|
||||
# DEFINE
|
||||
#
|
||||
##############################################################
|
||||
|
||||
sub Define{
|
||||
my ( $hash, $def ) = @_;
|
||||
my @val = split( "[ \t]+", $def );
|
||||
my $name = $val[0];
|
||||
my $type = $val[1];
|
||||
my $iam = "$type $name Define:";
|
||||
my $client_id = '';
|
||||
my $mowerNumber = 0;
|
||||
my $hostname ='';
|
||||
|
||||
return "$iam Cannot define $type device. Perl modul $missingModul is missing." if ( $missingModul );
|
||||
|
||||
if ( $type eq 'AutomowerConnect' ) {
|
||||
|
||||
return "$iam too few parameters: define <NAME> $type <client_id> [<mower number>]" if( @val < 3 );
|
||||
|
||||
$client_id =$val[2];
|
||||
$mowerNumber = $val[3] ? $val[3] : 0;
|
||||
|
||||
} elsif ( $type eq 'AutomowerConnectDevice' ) {
|
||||
|
||||
return "$iam too few parameters: define <NAME> $type <host name> <mower number>" if( @val < 4 );
|
||||
|
||||
$hostname = $val[2];
|
||||
$mowerNumber = $val[3];
|
||||
|
||||
::notifyRegexpChanged($hash, $hostname.':state:.connected');
|
||||
|
||||
|
||||
}
|
||||
|
||||
my $mapAttr = 'areaLimitsColor="#ff8000"
|
||||
areaLimitsLineWidth="1"
|
||||
areaLimitsConnector=""
|
||||
propertyLimitsColor="#33cc33"
|
||||
propertyLimitsLineWidth="1"
|
||||
propertyLimitsConnector="1"
|
||||
errorBackgroundColor="#3d3d3d"
|
||||
errorFont="14px Courier New"
|
||||
errorFontColor="#ff8000"
|
||||
errorPathLineColor="#ff00bf"
|
||||
errorPathLineDash=""
|
||||
errorPathLineWidth="2"
|
||||
chargingStationPathLineColor="#999999"
|
||||
chargingStationPathLineDash="6,2"
|
||||
chargingStationPathLineWidth="1"
|
||||
otherActivityPathLineColor="#33cc33"
|
||||
otherActivityPathLineDash="6,2"
|
||||
otherActivityPathLineWidth="1"
|
||||
mowingPathLineColor="#ff0000"
|
||||
mowingPathLineDash="6,2"
|
||||
mowingPathLineWidth="1"';
|
||||
|
||||
|
||||
%$hash = (%$hash,
|
||||
helper => {
|
||||
passObj => FHEM::Core::Authentication::Passwords->new($type),
|
||||
interval => 600,
|
||||
client_id => $client_id,
|
||||
grant_type => 'client_credentials',
|
||||
mowerNumber => $mowerNumber,
|
||||
hostname => $hostname,
|
||||
scaleToMeterLongitude => 67425,
|
||||
scaleToMeterLatitude => 108886,
|
||||
minLon => 180,
|
||||
maxLon => -180,
|
||||
minLat => 90,
|
||||
maxLat => -90,
|
||||
imageHeight => 650,
|
||||
imageWidthHeight => '350 650',
|
||||
mapdesign => $mapAttr,
|
||||
posMinMax => "-180 90\n180 -90",
|
||||
newdatasets => 0,
|
||||
MAP_PATH => '',
|
||||
MAP_MIME => '',
|
||||
MAP_CACHE => '',
|
||||
cspos => [],
|
||||
otherpos => [],
|
||||
areapos => [],
|
||||
searchpos => [],
|
||||
timestamps => [],
|
||||
lasterror => {
|
||||
positions => [],
|
||||
timestamp => 0,
|
||||
errordesc => '-',
|
||||
errordate => '',
|
||||
sizex => 0,
|
||||
sizey => 0,
|
||||
olLon => 0,
|
||||
olLat => 0
|
||||
},
|
||||
UNKNOWN => {
|
||||
arrayName => 'otherpos',
|
||||
maxLength => 50,
|
||||
callFn => ''
|
||||
},
|
||||
NOT_APPLICABLE => {
|
||||
arrayName => 'otherpos',
|
||||
maxLength => 50,
|
||||
callFn => ''
|
||||
},
|
||||
MOWING => {
|
||||
arrayName => 'areapos',
|
||||
maxLength => 500,
|
||||
maxLengthDefault => 500,
|
||||
callFn => \&FHEM::Devices::AMConnect::Common::AreaStatistics
|
||||
},
|
||||
GOING_HOME => {
|
||||
arrayName => 'otherpos',
|
||||
maxLength => 50,
|
||||
callFn => ''
|
||||
},
|
||||
CHARGING => {
|
||||
arrayName => 'cspos',
|
||||
maxLength => 100,
|
||||
callFn => \&FHEM::Devices::AMConnect::Common::ChargingStationPosition
|
||||
},
|
||||
LEAVING => {
|
||||
arrayName => 'otherpos',
|
||||
maxLength => 50,
|
||||
callFn => ''
|
||||
},
|
||||
PARKED_IN_CS => {
|
||||
arrayName => 'cspos',
|
||||
maxLength => 100,
|
||||
callFn => \&FHEM::Devices::AMConnect::Common::ChargingStationPosition
|
||||
},
|
||||
STOPPED_IN_GARDEN => {
|
||||
arrayName => 'otherpos',
|
||||
maxLength => 20,
|
||||
callFn => ''
|
||||
},
|
||||
statistics => {
|
||||
currentSpeed => 0,
|
||||
currentDayTrack => 0,
|
||||
currentDayArea => 0,
|
||||
lastDayTrack => 0,
|
||||
lastDayArea => 0,
|
||||
currentWeekTrack => 0,
|
||||
currentWeekArea => 0,
|
||||
lastWeekTrack => 0,
|
||||
lastWeekArea => 0
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
$hash->{MODEL} = '';
|
||||
$hash->{VERSION} = '';
|
||||
$attr{$name}{room} = 'AutomowerConnect' if( !defined( $attr{$name}{room} ) );
|
||||
$attr{$name}{icon} = 'automower' if( !defined( $attr{$name}{icon} ) );
|
||||
( $hash->{LIBRARY_VERSION} ) = $cvsid =~ /\.pm (.*)Z/;
|
||||
|
||||
AddExtension( $name, \&GetMap, "$type/$name/map" );
|
||||
|
||||
if ( $type eq 'AutomowerConnect' ) {
|
||||
|
||||
if( $hash->{helper}->{passObj}->getReadPassword($name) ) {
|
||||
|
||||
RemoveInternalTimer($hash);
|
||||
InternalTimer( gettimeofday() + 2, \&::FHEM::AutomowerConnect::APIAuth, $hash, 1);
|
||||
InternalTimer( gettimeofday() + 30, \&readMap, $hash, 0);
|
||||
|
||||
readingsSingleUpdate( $hash, 'state', 'defined', 1 );
|
||||
|
||||
} else {
|
||||
|
||||
readingsSingleUpdate( $hash, 'state', 'defined - client_secret missing', 1 );
|
||||
|
||||
}
|
||||
|
||||
} elsif ( $type eq 'AutomowerConnectDevice' ) {
|
||||
|
||||
RemoveInternalTimer($hash);
|
||||
InternalTimer( gettimeofday() + 25, \&readMap, $hash, 0);
|
||||
|
||||
readingsSingleUpdate( $hash, 'state', 'defined', 1 );
|
||||
|
||||
}
|
||||
|
||||
return undef;
|
||||
|
||||
}
|
||||
|
||||
#########################
|
||||
sub Undefine {
|
||||
my ( $hash, $arg ) = @_;
|
||||
@ -112,15 +304,22 @@ sub Delete {
|
||||
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);
|
||||
RemoveExtension("$type/$oldname/map");
|
||||
AddExtension( $newname, \&GetMap, "$type/$newname/map" );
|
||||
|
||||
if ( $type eq 'AutomowerConnect' ) {
|
||||
|
||||
my ( $passResp, $passErr ) = $hash->{helper}->{passObj}->setRename( $newname, $oldname );
|
||||
Log3 $newname, 2, "$newname password rename error: $passErr" if ($passErr);
|
||||
|
||||
}
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
#########################
|
||||
|
||||
sub Get {
|
||||
my ($hash,@val) = @_;
|
||||
my $type = $hash->{TYPE};
|
||||
@ -173,15 +372,21 @@ sub FW_detailFn {
|
||||
if ( $hash->{helper} && $hash->{helper}{mower} && $hash->{helper}{mower}{attributes} && $hash->{helper}{mower}{attributes}{positions} && @{$hash->{helper}{mower}{attributes}{positions}} > 0 ) {
|
||||
my $img = "./fhem/$type/$name/map";
|
||||
my $zoom=AttrVal( $name,"mapImageZoom", 0.7 );
|
||||
|
||||
my $backgroundcolor = AttrVal($name, 'mapBackgroundColor','');
|
||||
my $bgstyle = $backgroundcolor ? " background-color:$backgroundcolor;" : '';
|
||||
my $design = AttrVal( $name, 'mapDesignAttributes', $hash->{helper}{mapdesign} );
|
||||
my @adesign = split(/\R/,$design);
|
||||
my $mapDesign = 'data-'.join("data-",@adesign);
|
||||
Log3 $name,1 , $mapDesign;
|
||||
my ($picx,$picy) = AttrVal( $name,"mapImageWidthHeight", $hash->{helper}{imageWidthHeight} ) =~ /(\d+)\s(\d+)/;
|
||||
|
||||
$picx=int($picx*$zoom);
|
||||
$picy=int($picy*$zoom);
|
||||
|
||||
my $ret = "";
|
||||
$ret .= "<style> ." . $type . "_" . $name . "_div{background-image: url('$img');background-size: ".$picx."px ".$picy."px;background-repeat: no-repeat; width:".$picx."px;height:".$picy."px;}</style>";
|
||||
$ret .= "<div class='" . $type . "_" . $name . "_div' >";
|
||||
$ret .= "<canvas id= '" . $type . "_" . $name . "_canvas' width='$picx' height='$picy' ></canvas>";
|
||||
$ret .= "<style> .${type}_${name}_div{padding:0px !important;$bgstyle background-image: url('$img');background-size: ${picx}px ${picy}px; background-repeat: no-repeat; width: ${picx}px; height: ${picy}px; }</style>";
|
||||
$ret .= "<div id='${type}_${name}_div' class='${type}_${name}_div' $mapDesign >";
|
||||
$ret .= "<canvas id='${type}_${name}_canvas' width='$picx' height='$picy' ></canvas>";
|
||||
$ret .= "</div>";
|
||||
|
||||
InternalTimer( gettimeofday() + 2.0, \&FW_detailFn_Update, $hash, 0 );
|
||||
@ -198,9 +403,11 @@ sub FW_detailFn_Update {
|
||||
my $type = $hash->{TYPE};
|
||||
if ( $hash->{helper} && $hash->{helper}{mower} && $hash->{helper}{mower}{attributes} && $hash->{helper}{mower}{attributes}{positions} && @{$hash->{helper}{mower}{attributes}{positions}} > 0 ) {
|
||||
|
||||
my @pos = @{$hash->{helper}{areapos}}; # operational mode
|
||||
my @posc = @{$hash->{helper}{cspos}}; # maybe operational mode
|
||||
my $img = "./fhem/$type/$name/map";
|
||||
my @pos = @{ $hash->{helper}{areapos} };
|
||||
my @posc = @{ $hash->{helper}{cspos} };
|
||||
my @posother = @{ $hash->{helper}{otherpos} };
|
||||
my @poserr = @{ $hash->{helper}{lasterror}{positions} };
|
||||
my $img = "./fhem/$type/$name/map";
|
||||
|
||||
my ( $lonlo, $latlo, $dummy, $lonru, $latru ) = AttrVal( $name,"mapImageCoordinatesToRegister",$hash->{helper}{posMinMax} ) =~ /(-?\d*\.?\d+)\s(-?\d*\.?\d+)(\R|\s)(-?\d*\.?\d+)\s(-?\d*\.?\d+)/;
|
||||
|
||||
@ -209,7 +416,8 @@ sub FW_detailFn_Update {
|
||||
my ($picx,$picy) = AttrVal( $name,"mapImageWidthHeight", $hash->{helper}{imageWidthHeight} ) =~ /(\d+)\s(\d+)/;
|
||||
|
||||
AttrVal($name,'scaleToMeterXY', $hash->{helper}{scaleToMeterLongitude} . ' ' .$hash->{helper}{scaleToMeterLatitude}) =~ /(-?\d+)\s+(-?\d+)/;
|
||||
my $scalx = ($lonru-$lonlo) * $1;
|
||||
my $scalx = ( $lonru - $lonlo ) * $1;
|
||||
my $scaly = ( $latlo - $latru ) * $2;
|
||||
|
||||
$picx = int($picx*$zoom);
|
||||
$picy = int($picy*$zoom);
|
||||
@ -222,6 +430,7 @@ sub FW_detailFn_Update {
|
||||
use strict "refs";
|
||||
}
|
||||
|
||||
# CHARGING STATION POSITION
|
||||
my $csimgpos = AttrVal( $name,"chargingStationImagePosition","right" );
|
||||
my $xm = $hash->{helper}{chargingStation}{longitude} // 10.1165;
|
||||
my $ym = $hash->{helper}{chargingStation}{latitude} // 51.28;
|
||||
@ -242,8 +451,19 @@ sub FW_detailFn_Update {
|
||||
|
||||
}
|
||||
|
||||
# OTHER PATH
|
||||
my $posoxy = int($lonlo * $picx / $mapx).",".int($latlo * $picy / $mapy);
|
||||
if ( @posother > 1 ) {
|
||||
|
||||
$posoxy = int(($lonlo-$posother[0]{longitude}) * $picx / $mapx).",".int(($latlo-$posother[0]{latitude}) * $picy / $mapy);
|
||||
for (my $i=1;$i<@posother;$i++){
|
||||
$posoxy .= ",".int(($lonlo-$posother[$i]{longitude}) * $picx / $mapx).",".int(($latlo-$posother[$i]{latitude}) * $picy / $mapy);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
# CHARGING STATION PATH
|
||||
my $poscxy = int( $lonlo * $picx / $mapx ).",".int( $latlo * $picy / $mapy );
|
||||
my $poscxy = int( ( $lonru-$lonlo ) * $picx / $mapx ).",".int( ( $latlo - $latru ) * $picy / $mapy );
|
||||
if ( @posc > 1 ) {
|
||||
|
||||
$poscxy = int( ( $lonlo-$posc[0]{longitude} ) * $picx / $mapx ).",".int( ( $latlo-$posc[0]{latitude} ) * $picy / $mapy );
|
||||
@ -258,9 +478,9 @@ sub FW_detailFn_Update {
|
||||
my $limi = '';
|
||||
if ($arealimits) {
|
||||
my @lixy = (split(/\s|,|\R$/,$arealimits));
|
||||
$limi = int(($lonlo-$lixy[0]) * $picx / $mapx).",".int(($latlo-$lixy[1]) * $picy / $mapy);
|
||||
$limi = int( ( $lonlo - $lixy[ 0 ] ) * $picx / $mapx ) . "," . int( ( $latlo - $lixy[ 1 ] ) * $picy / $mapy );
|
||||
for (my $i=2;$i<@lixy;$i+=2){
|
||||
$limi .= ",".int(($lonlo-$lixy[$i]) * $picx / $mapx).",".int(($latlo-$lixy[$i+1]) * $picy / $mapy);
|
||||
$limi .= ",".int( ( $lonlo - $lixy[ $i ] ) * $picx / $mapx).",".int( ( $latlo-$lixy[$i+1] ) * $picy / $mapy);
|
||||
}
|
||||
}
|
||||
|
||||
@ -275,13 +495,161 @@ sub FW_detailFn_Update {
|
||||
}
|
||||
}
|
||||
|
||||
# ERROR MESSAGE
|
||||
my $errlon = int( ( $lonlo - $hash->{helper}{lasterror}{olLon} ) * $picx / $mapx );
|
||||
my $errlat = int( ( $latlo - $hash->{helper}{lasterror}{olLat} ) * $picy / $mapy );
|
||||
my $errx = int( $hash->{helper}{lasterror}{sizex} * $picx / -$mapx );
|
||||
my $erry = int( $hash->{helper}{lasterror}{sizey} * $picy / $mapy );
|
||||
my $errdesc = $hash->{helper}{lasterror}{errordesc};
|
||||
my $errdate = $hash->{helper}{lasterror}{errordate};
|
||||
|
||||
# ERROR PATH
|
||||
my $poserrxy = int( ( $lonru-$lonlo ) / 2 * $picx / $mapx ).",".int( ( $latlo - $latru ) / 2 * $picy / $mapy );;
|
||||
|
||||
if ( @poserr > 0 ) {
|
||||
|
||||
$poserrxy = int( ( $lonlo - $poserr[ 0 ]{longitude} ) * $picx / $mapx ) . "," . int( ( $latlo - $poserr[ 0 ]{latitude} ) * $picy / $mapy );
|
||||
|
||||
for ( my $i = 1; $i < @poserr; $i++ ){
|
||||
$poserrxy .= ",".int( ( $lonlo - $poserr[ $i ]{longitude} ) * $picx / $mapx) . "," . int( ( $latlo - $poserr[ $i ]{latitude} ) * $picy / $mapy );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
my $erray = "$errlon,$errlat,$errx,$erry,$poserrxy";
|
||||
|
||||
# Log3 $name, 1, "AutomowerConnectUpdateDetail ( '$name', '$type', '$img', $picx, $picy, $cslon, $cslat, '$csimgpos', $scalx, '$errdesc', [ $posxy ], [ $limi ], [ $propli ], [ $poscxy ], [ $erray ] )";
|
||||
|
||||
map {
|
||||
::FW_directNotify("#FHEMWEB:$_", "AutomowerConnectUpdateDetail ( '$name', '$type', '$img', $picx, $picy, $cslon, $cslat, '$csimgpos', $scalx, [ $posxy ], [ $limi ], [ $propli ], [ $poscxy ] )","");
|
||||
::FW_directNotify("#FHEMWEB:$_", "AutomowerConnectUpdateDetail ( '$name', '$type', '$img', $picx, $picy, $cslon, $cslat, '$csimgpos', $scalx, [ '$errdesc', '$errdate' ], [ $posxy ], [ $limi ], [ $propli ], [ $poscxy ], [ $erray ], [ $posoxy ] )","");
|
||||
} devspec2array("TYPE=FHEMWEB");
|
||||
}
|
||||
return undef;
|
||||
}
|
||||
|
||||
##############################################################
|
||||
#
|
||||
# SEND COMMAND
|
||||
#
|
||||
##############################################################
|
||||
|
||||
sub CMD {
|
||||
my ($hash,@cmd) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
my $type = $hash->{TYPE};
|
||||
my $iam = "$type $name CMD:";
|
||||
my $hostname = $hash->{helper}{hostname} ? $hash->{helper}{hostname} : $name;
|
||||
my $hosthash = $defs{$hostname};
|
||||
|
||||
if ( IsDisabled($hostname) ) {
|
||||
|
||||
Log3 $name, 3, "$iam Host $hostname disabled";
|
||||
return undef
|
||||
|
||||
}
|
||||
if ( IsDisabled($name) ) {
|
||||
|
||||
Log3 $name, 3, "$iam disabled";
|
||||
return undef
|
||||
|
||||
}
|
||||
|
||||
my $client_id = $hosthash->{helper}->{client_id};
|
||||
my $token = ReadingsVal($hostname,".access_token","");
|
||||
my $provider = ReadingsVal($hostname,".provider","");
|
||||
my $mower_id = $hash->{helper}{mower}{id};
|
||||
|
||||
my $json = '';
|
||||
my $post = '';
|
||||
|
||||
|
||||
my $header = "Accept: application/vnd.api+json\r\nX-Api-Key: ".$client_id."\r\nAuthorization: Bearer " . $token . "\r\nAuthorization-Provider: " . $provider . "\r\nContent-Type: application/vnd.api+json";
|
||||
|
||||
|
||||
if ($cmd[0] eq "ParkUntilFurtherNotice") { $json = '{"data":{"type":"'.$cmd[0].'"}}'; $post = 'actions' }
|
||||
elsif ($cmd[0] eq "ParkUntilNextSchedule") { $json = '{"data": {"type":"'.$cmd[0].'"}}'; $post = 'actions' }
|
||||
elsif ($cmd[0] eq "ResumeSchedule") { $json = '{"data": {"type":"'.$cmd[0].'"}}'; $post = 'actions' }
|
||||
elsif ($cmd[0] eq "Pause") { $json = '{"data": {"type":"'.$cmd[0].'"}}'; $post = 'actions' }
|
||||
elsif ($cmd[0] eq "Park") { $json = '{"data": {"type":"'.$cmd[0].'","attributes":{"duration":'.$cmd[1].'}}}'; $post = 'actions' }
|
||||
elsif ($cmd[0] eq "Start") { $json = '{"data": {"type":"'.$cmd[0].'","attributes":{"duration":'.$cmd[1].'}}}'; $post = 'actions' }
|
||||
elsif ($cmd[0] eq "headlight") { $json = '{"data": {"type":"settings","attributes":{"'.$cmd[0].'": {"mode": "'.$cmd[1].'"}}}}'; $post = 'settings' }
|
||||
elsif ($cmd[0] eq "cuttingHeight") { $json = '{"data": {"type":"settings","attributes":{"'.$cmd[0].'": '.$cmd[1].'}}}'; $post = 'settings' }
|
||||
elsif ($cmd[0] eq "sendScheduleFromAttributeToMower" && AttrVal( $name, 'mowerSchedule', '')) {
|
||||
|
||||
my $perl = eval { decode_json (AttrVal( $name, 'mowerSchedule', '')) };
|
||||
if ($@) {
|
||||
return "$iam decode error: $@ \n $perl";
|
||||
}
|
||||
my $jsonSchedule = eval { encode_json ($perl) };
|
||||
if ($@) {
|
||||
return "$iam encode error: $@ \n $json";
|
||||
}
|
||||
$json = '{"data":{"type": "calendar","attributes":{"tasks":'.$jsonSchedule.'}}}';
|
||||
$post = 'calendar';
|
||||
}
|
||||
|
||||
Log3 $name, 5, "$iam $header \n $cmd[0] \n $json";
|
||||
|
||||
::HttpUtils_NonblockingGet({
|
||||
url => APIURL . "/mowers/". $mower_id . "/".$post,
|
||||
timeout => 10,
|
||||
hash => $hash,
|
||||
method => "POST",
|
||||
header => $header,
|
||||
data => $json,
|
||||
callback => \&CMDResponse,
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
##############################################################
|
||||
sub CMDResponse {
|
||||
my ($param, $err, $data) = @_;
|
||||
my $hash = $param->{hash};
|
||||
my $name = $hash->{NAME};
|
||||
my $type = $hash->{TYPE};
|
||||
my $statuscode = $param->{code};
|
||||
my $iam = "$type $name CMDResponse:";
|
||||
|
||||
Log3 $name, 1, "\ndebug $iam \n\$statuscode [$statuscode]\n\$err [$err],\n \$data [$data] \n\$param->url $param->{url}" if ( AttrVal($name, 'debug', '') );
|
||||
|
||||
if( !$err && $statuscode == 202 && $data ) {
|
||||
|
||||
my $result = eval { decode_json($data) };
|
||||
if ($@) {
|
||||
|
||||
Log3( $name, 2, "$iam - JSON error while request: $@");
|
||||
|
||||
} else {
|
||||
|
||||
$hash->{helper}{CMDResponse} = $result;
|
||||
if ($result->{data}) {
|
||||
|
||||
Log3 $name, 5, $data;
|
||||
if ( ref ($result->{data}) eq 'ARRAY') {
|
||||
|
||||
$hash->{helper}->{mower_commandStatus} = 'OK - '. $result->{data}[0]{type};
|
||||
|
||||
} else {
|
||||
|
||||
$hash->{helper}->{mower_commandStatus} = 'OK - '. $result->{data}{type};
|
||||
|
||||
}
|
||||
|
||||
readingsSingleUpdate($hash, 'mower_commandStatus', $hash->{helper}->{mower_commandStatus} ,1);
|
||||
return undef;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
readingsSingleUpdate($hash, 'mower_commandStatus', "ERROR statuscode $statuscode" ,1);
|
||||
Log3 $name, 2, "\n$iam \n\$statuscode [$statuscode]\n\$err [$err],\n\$data [$data]\n\$param->url $param->{url}";
|
||||
return undef;
|
||||
}
|
||||
|
||||
#########################
|
||||
sub AlignArray {
|
||||
my ($hash) = @_;
|
||||
@ -290,52 +658,46 @@ sub AlignArray {
|
||||
my $arrayName = $hash->{helper}{$activity}{arrayName};
|
||||
my $searchlen = 2;
|
||||
my $i = 0;
|
||||
my @temp = ();
|
||||
my $tmp = [];
|
||||
|
||||
if ( isGoodActivity( $hash ) ) {
|
||||
|
||||
my $k = -1;
|
||||
my $poslen = @{$hash->{helper}{mower}{attributes}{positions}};
|
||||
my @searchposlon = ($hash->{helper}{searchpos}[0]{longitude}, $hash->{helper}{searchpos}[1]{longitude});
|
||||
my @searchposlat = ($hash->{helper}{searchpos}[0]{latitude}, $hash->{helper}{searchpos}[1]{latitude});
|
||||
my $poslen = @{ $hash->{helper}{mower}{attributes}{positions} };
|
||||
my @searchposlon = ( $hash->{helper}{searchpos}[0]{longitude}, $hash->{helper}{searchpos}[1]{longitude} );
|
||||
my @searchposlat = ( $hash->{helper}{searchpos}[0]{latitude}, $hash->{helper}{searchpos}[1]{latitude} );
|
||||
my $maxLength = $hash->{helper}{$activity}{maxLength};
|
||||
for ( $i = 0; $i < $poslen-2; $i++ ) { # 2 due to 2 alignment data sets at the end
|
||||
for ( $i = 0; $i < $poslen-2; $i++ ) { # -2 due to 2 alignment data sets at the end
|
||||
if ( $searchposlon[ 0 ] == $hash->{helper}{mower}{attributes}{positions}[ $i ]{longitude}
|
||||
&& $searchposlat[ 0 ] == $hash->{helper}{mower}{attributes}{positions}[ $i ]{latitude}
|
||||
&& $searchposlon[ 1 ] == $hash->{helper}{mower}{attributes}{positions}[ $i+1 ]{longitude}
|
||||
&& $searchposlat[ 1 ] == $hash->{helper}{mower}{attributes}{positions}[ $i+1 ]{latitude} ) {
|
||||
# timediff per step
|
||||
my $dt = 0;
|
||||
$dt = int(($hash->{helper}{mower}{attributes}{metadata}{statusTimestamp} - $hash->{helper}{$arrayName}[0]{statusTimestamp})/$i) if ( $i && @{ $hash->{helper}{$arrayName} } );
|
||||
|
||||
my @ar = @{ $hash->{helper}{mower}{attributes}{positions} }[ 0 .. $i-1 ];
|
||||
$tmp = dclone( \@ar );
|
||||
|
||||
|
||||
|
||||
for ($k=$i-1;$k>-1;$k--) {
|
||||
if ( $i && @{ $hash->{helper}{$arrayName} } ) {
|
||||
|
||||
if ( @{ $hash->{helper}{$arrayName} } ) {
|
||||
unshift ( @{ $hash->{helper}{$arrayName} }, @$tmp );
|
||||
|
||||
unshift (@{$hash->{helper}{$arrayName}}, dclone( $hash->{helper}{mower}{attributes}{positions}[ $k ] ) );
|
||||
} elsif ( $i ) {
|
||||
|
||||
} else {
|
||||
|
||||
$hash->{helper}{$arrayName}[ 0 ] = dclone( $hash->{helper}{mower}{attributes}{positions}[ $k ] );
|
||||
|
||||
}
|
||||
|
||||
pop (@{$hash->{helper}{$arrayName}}) if (@{$hash->{helper}{$arrayName}} > $maxLength);
|
||||
$hash->{helper}{$arrayName}[ 0 ]{statusTimestamp} = $hash->{helper}{mower}{attributes}{metadata}{statusTimestamp} - $dt * $k;
|
||||
|
||||
push ( @temp, dclone( $hash->{helper}{mower}{attributes}{positions}[ $k ] ) );
|
||||
$hash->{helper}{$arrayName} = $tmp;
|
||||
|
||||
}
|
||||
|
||||
posMinMax( $hash, \@temp );
|
||||
while ( @{ $hash->{helper}{$arrayName} } > $maxLength ) {
|
||||
|
||||
pop ( @{ $hash->{helper}{$arrayName}} );
|
||||
|
||||
}
|
||||
|
||||
posMinMax( $hash, $tmp );
|
||||
#callFn if present
|
||||
if ( $hash->{helper}{$activity}{callFn} && @{$hash->{helper}{$arrayName}} > 1) {
|
||||
if ( $hash->{helper}{$activity}{callFn} && @{ $hash->{helper}{$arrayName} } > 1 ) {
|
||||
|
||||
$hash->{helper}{$activity}{cnt} = $i;
|
||||
no strict "refs";
|
||||
&{$hash->{helper}{$activity}{callFn}}($hash);
|
||||
&{ $hash->{helper}{$activity}{callFn} }( $hash );
|
||||
use strict "refs";
|
||||
|
||||
}
|
||||
@ -346,12 +708,60 @@ sub AlignArray {
|
||||
|
||||
}
|
||||
|
||||
isErrorThanPrepare( $hash, $tmp );
|
||||
|
||||
}
|
||||
|
||||
$hash->{helper}{newdatasets} = $i;
|
||||
$hash->{helper}{searchpos} = [ dclone( $hash->{helper}{mower}{attributes}{positions}[0] ), dclone( $hash->{helper}{mower}{attributes}{positions}[1] ) ];
|
||||
return undef;
|
||||
|
||||
}
|
||||
#########################
|
||||
sub isErrorThanPrepare {
|
||||
my ( $hash, $poshash ) = @_;
|
||||
if ( $hash->{helper}{mower}{attributes}{mower}{errorCodeTimestamp} ) {
|
||||
|
||||
if ( ( $hash->{helper}{lasterror}{timestamp} != $hash->{helper}{mower}{attributes}{mower}{errorCodeTimestamp} ) && @$poshash) {
|
||||
|
||||
my $minLon = minNum( 180, $poshash->[ 0 ]{longitude} );
|
||||
my $maxLon = maxNum( -180, $poshash->[ 0 ]{longitude} );
|
||||
my $minLat = minNum( 90, $poshash->[ 0 ]{latitude} );
|
||||
my $maxLat = maxNum( -90, $poshash->[ 0 ]{latitude} );
|
||||
|
||||
for ( @{ $poshash } ) {
|
||||
$minLon = minNum( $minLon, $_->{longitude} );
|
||||
$maxLon = maxNum( $maxLon, $_->{longitude} );
|
||||
$minLat = minNum( $minLat, $_->{latitude} );
|
||||
$maxLat = maxNum( $maxLat, $_->{latitude} );
|
||||
}
|
||||
|
||||
my $ect = $hash->{helper}{mower}{attributes}{mower}{errorCodeTimestamp};
|
||||
$hash->{helper}{lasterror}{positions} = dclone $poshash;
|
||||
$hash->{helper}{lasterror}{timestamp} = $ect;
|
||||
$hash->{helper}{lasterror}{olLon} = $minLon;
|
||||
$hash->{helper}{lasterror}{olLat} = $maxLat;
|
||||
$hash->{helper}{lasterror}{sizex} = sprintf('%.7f',$maxLon - $minLon);
|
||||
$hash->{helper}{lasterror}{sizey} = sprintf('%.7f',$maxLat - $minLat);
|
||||
my $errc = $hash->{helper}{mower}{attributes}{mower}{errorCode};
|
||||
$hash->{helper}{lasterror}{errordesc} = $::FHEM::Devices::AMConnect::Common::errortable->{$errc};
|
||||
$hash->{helper}{lasterror}{errordate} = FmtDateTimeGMT( $ect / 1000 );
|
||||
|
||||
}
|
||||
|
||||
} elsif (!$hash->{helper}{mower}{attributes}{mower}{errorCodeTimestamp} && $hash->{helper}{lasterror}{timestamp} ) {
|
||||
|
||||
$hash->{helper}{lasterror}{positions} = [];
|
||||
$hash->{helper}{lasterror}{timestamp} = 0;
|
||||
$hash->{helper}{lasterror}{olLon} = 0;
|
||||
$hash->{helper}{lasterror}{olLat} = 0;
|
||||
$hash->{helper}{lasterror}{sizex} = 0;
|
||||
$hash->{helper}{lasterror}{sizey} = 0;
|
||||
$hash->{helper}{lasterror}{errordesc} = '-';
|
||||
$hash->{helper}{lasterror}{errordate} = '';
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#########################
|
||||
@ -361,8 +771,10 @@ sub isGoodActivity {
|
||||
my $act = $hash->{helper}{mower}{attributes}{mower}{activity};
|
||||
my $actold = $hash->{helper}{mowerold}{attributes}{mower}{activity};
|
||||
|
||||
my $ret = $hash->{helper}{$act}{arrayName} && $act eq $actold
|
||||
|| $act =~ /^(CHARGING|PARKED_IN_CS)$/ && $actold =~ /^(PARKED_IN_CS|CHARGING)$/;
|
||||
my $ret = $hash->{helper}{$act}{arrayName} && (
|
||||
$act eq $actold
|
||||
|| $act =~ /^(CHARGING|PARKED_IN_CS)$/ && $actold =~ /^(PARKED_IN_CS|CHARGING)$/
|
||||
|| $act =~ /^(NOT_APPLICABLE)$/ && $actold =~ /^(UNKNOWN|NOT_APPLICABLE|MOWING|GOING_HOME|CHARGING|LEAVING|PARKED_IN_CS|STOPPED_IN_GARDEN)$/);
|
||||
return $ret;
|
||||
|
||||
}
|
||||
@ -405,15 +817,14 @@ sub AreaStatistics {
|
||||
}
|
||||
|
||||
$asum = $lsum * AttrVal($name,'mowerCuttingWidth',0.24);
|
||||
my $td = $xyarr[ 0 ]{storedTimestamp} - $xyarr[ $k ]{storedTimestamp};
|
||||
$vm = sprintf( '%.6f', $lsum / $td ) * 1000 if ($td); # m/s
|
||||
$hash->{helper}{$activity}{speed} = $vm;
|
||||
# my $td = $xyarr[ 0 ]{storedTimestamp} - $xyarr[ $k ]{storedTimestamp};
|
||||
# $vm = sprintf( '%.6f', $lsum / $td ) * 1000 if ($td); # m/s
|
||||
# $hash->{helper}{$activity}{speed} = $vm;
|
||||
$hash->{helper}{$activity}{track} = $lsum;
|
||||
$hash->{helper}{$activity}{area} = $asum;
|
||||
$hash->{helper}{statistics}{currentSpeed} = $vm;
|
||||
# $hash->{helper}{statistics}{currentSpeed} = $vm;
|
||||
$hash->{helper}{statistics}{currentDayTrack} += $lsum;
|
||||
$hash->{helper}{statistics}{currentDayArea} += $asum;
|
||||
$hash->{helper}{statistics}{currentSpeed} = $vm;
|
||||
|
||||
return undef;
|
||||
}
|
||||
@ -438,10 +849,8 @@ sub RemoveExtension {
|
||||
my ($link) = @_;
|
||||
my $url = "/$link";
|
||||
my $name = $::data{FWEXT}{$url}{deviceName};
|
||||
my $hash = $defs{$name};
|
||||
my $type = $hash->{TYPE};
|
||||
|
||||
Log3( $name, 2, "Unregistering $type $name for URL $url..." );
|
||||
Log3( $name, 2, "Unregistering URL $url..." );
|
||||
delete $::data{FWEXT}{$url};
|
||||
|
||||
return;
|
||||
@ -463,7 +872,7 @@ sub GetMap() {
|
||||
return ( $mapMime, $mapData );
|
||||
|
||||
}
|
||||
return ( "text/plain; charset=utf-8","No AutomowerConnect(Device) device for webhook $request" );
|
||||
return ( "text/plain; charset=utf-8", "No AutomowerConnect(Device) device for webhook $request" );
|
||||
|
||||
}
|
||||
|
||||
@ -536,7 +945,7 @@ sub listStatisticsData {
|
||||
$cnt++;$ret .= '<tr class="column '.( $cnt % 2 ? 'odd' : 'even' ).'"><td> $hash->{helper}{mower}{attributes}{statistics}{totalRunningTime}  </td><td> ' . $hash->{helper}{mower}{attributes}{statistics}{totalRunningTime} . '<sup>1</sup> </td><td> s </td></tr>';
|
||||
$cnt++;$ret .= '<tr class="column '.( $cnt % 2 ? 'odd' : 'even' ).'"><td> $hash->{helper}{mower}{attributes}{statistics}{totalSearchingTime}  </td><td> ' . $hash->{helper}{mower}{attributes}{statistics}{totalSearchingTime} . ' </td><td> s </td></tr>';
|
||||
|
||||
$cnt++;$ret .= '<tr class="column '.( $cnt % 2 ? 'odd' : 'even' ).'"><td> $hash->{helper}{statistics}{currentSpeed}  </td><td> ' . $hash->{helper}{statistics}{currentSpeed} . ' </td><td> m/s </td></tr>';
|
||||
# $cnt++;$ret .= '<tr class="column '.( $cnt % 2 ? 'odd' : 'even' ).'"><td> $hash->{helper}{statistics}{currentSpeed}  </td><td> ' . $hash->{helper}{statistics}{currentSpeed} . ' </td><td> m/s </td></tr>';
|
||||
$cnt++;$ret .= '<tr class="column '.( $cnt % 2 ? 'odd' : 'even' ).'"><td> $hash->{helper}{statistics}{currentDayTrack}  </td><td> ' . $hash->{helper}{statistics}{currentDayTrack} . ' </td><td> m </td></tr>';
|
||||
$cnt++;$ret .= '<tr class="column '.( $cnt % 2 ? 'odd' : 'even' ).'"><td> $hash->{helper}{statistics}{currentDayArea}  </td><td> ' . $hash->{helper}{statistics}{currentDayArea} . ' </td><td> qm </td></tr>';
|
||||
$cnt++;$ret .= '<tr class="column '.( $cnt % 2 ? 'odd' : 'even' ).'"><td> $hash->{helper}{statistics}{lastDayTrack}  </td><td> ' . $hash->{helper}{statistics}{lastDayTrack} . ' </td><td> m </td></tr>';
|
||||
@ -626,12 +1035,17 @@ sub listInternalData {
|
||||
|
||||
my $xm = $hash->{helper}{chargingStation}{longitude} // 0;
|
||||
my $ym = $hash->{helper}{chargingStation}{latitude} // 0;
|
||||
my $csnr = scalar @{$hash->{helper}{cspos}};
|
||||
my $csnr = scalar @{ $hash->{helper}{cspos} };
|
||||
my $csnrmax = $hash->{helper}{PARKED_IN_CS}{maxLength};
|
||||
my $arnr = 0;
|
||||
$arnr = scalar @{$hash->{helper}{areapos}} if( scalar @{$hash->{helper}{areapos}} > 2 );
|
||||
$arnr = scalar @{ $hash->{helper}{areapos} } if( scalar @{ $hash->{helper}{areapos} } > 2 );
|
||||
my $arnrmax = $hash->{helper}{MOWING}{maxLength};
|
||||
|
||||
my $ornr = scalar @{ $hash->{helper}{otherpos} };
|
||||
my $ornrmax = $hash->{helper}{UNKNOWN}{maxLength};
|
||||
|
||||
my $ernr = scalar @{ $hash->{helper}{lasterror}{positions} };
|
||||
|
||||
$hash->{helper}{posMinMax} =~ /(-?\d*\.?\d+)\s(-?\d*\.?\d+)(\R|\s)(-?\d*\.?\d+)\s(-?\d*\.?\d+)/;
|
||||
|
||||
if ( $::init_done && $1 && $2 && $4 && $5 ) {
|
||||
@ -654,6 +1068,10 @@ sub listInternalData {
|
||||
$ret .= '<tr class="col_header"><td> Used For Action </td><td> Stack Name </td><td> Current Size </td><td> Max Size </td></tr>';
|
||||
$ret .= '<tr class="column odd"><td>PARKED_IN_CS, CHARGING </td><td> cspos </td><td> ' . $csnr . ' </td><td> ' . $csnrmax . ' </td></tr>';
|
||||
$ret .= '<tr class="column even"><td>MOWING </td><td> areapos </td><td> ' . $arnr . ' </td><td> ' . $arnrmax . ' </td></tr>';
|
||||
$ret .= '<tr class="column odd"><td>UNKNOWN, NOT_APPLICABLE, LEAVING,<br>GOING_HOME, STOPPED_IN_GARDEN </td>
|
||||
<td style="vertical-align:middle;" > otherpos </td><td style="vertical-align:middle;" > ' . $ornr . ' </td>
|
||||
<td style="vertical-align:middle;" > ' . $ornrmax . ' </td></tr>';
|
||||
$ret .= '<tr class="column even"><td>NOT_APPLICABLE with error time stamp </td><td> lasterror/positions </td><td> ' . $ernr . ' </td><td> - </td></tr>';
|
||||
|
||||
$ret .= '</tbody></table>';
|
||||
if ( $hash->{TYPE} eq 'AutomowerConnect' ) {
|
||||
@ -724,6 +1142,14 @@ sub listErrorCodes {
|
||||
}
|
||||
}
|
||||
|
||||
#########################
|
||||
# Format mower timestamp assuming mower time is always set to daylight saving time, because it is the mowing period.
|
||||
sub FmtDateTimeGMT {
|
||||
my $ti = shift // 0;
|
||||
my $ret = POSIX::strftime( "%F %H:%M:%S", gmtime( $ti ) );
|
||||
}
|
||||
|
||||
|
||||
##############################################################
|
||||
|
||||
1;
|
||||
|
@ -1,22 +1,69 @@
|
||||
|
||||
FW_version["automowerconnect.js"] = "$Id$";
|
||||
|
||||
function AutomowerConnectLimits( ctx, pos, format ) {
|
||||
function AutomowerConnectShowError( ctx, div, dev, picx, picy, errdesc, erray ) {
|
||||
//~ log( 'AutomowerConnectShowError: ' + erray[0]+' '+erray[1]+' '+erray[2]+' '+erray[3]+' '+erray[4]+' '+erray[5]);
|
||||
// ERROR BANNER
|
||||
ctx.beginPath();
|
||||
ctx.fillStyle = div.getAttribute( 'data-errorBackgroundColor' );;
|
||||
ctx.font = div.getAttribute( 'data-errorFont' );
|
||||
var m = ctx.measureText( errdesc[ 1 ] + ', ' + dev + ': ' + errdesc[ 0 ] ).width > picy - 6;
|
||||
|
||||
if ( m ) {
|
||||
|
||||
ctx.fillRect( 0, 0, picx, 35);
|
||||
|
||||
} else {
|
||||
|
||||
ctx.fillRect( 0, 0, picx, 20);
|
||||
|
||||
}
|
||||
|
||||
ctx.fillStyle = div.getAttribute( 'data-errorFontColor' );
|
||||
ctx.textAlign = "left";
|
||||
|
||||
if ( m ) {
|
||||
|
||||
ctx.fillText( errdesc[ 1 ] + ', ' + dev + ':', 3, 15 );
|
||||
ctx.fillText( errdesc[ 0 ], 3, 30 );
|
||||
|
||||
} else {
|
||||
|
||||
ctx.fillText( errdesc[ 1 ] + ', ' + dev + ': ' + errdesc[ 0 ], 3, 15 );
|
||||
|
||||
}
|
||||
|
||||
ctx.stroke();
|
||||
|
||||
if ( erray[ 0 ] && erray[ 1 ] && erray.length > 3) {
|
||||
|
||||
AutomowerConnectIcon( ctx, erray[ 4 ], erray[ 5 ], 'top', 'E' );
|
||||
|
||||
}
|
||||
|
||||
if ( erray.length > 8 ) {
|
||||
|
||||
var pos = erray.slice(4);
|
||||
AutomowerConnectDrawPath ( ctx, div, pos, 'errorPath' );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function AutomowerConnectLimits( ctx, div, pos, type ) {
|
||||
// log("array length: "+pos.length);
|
||||
if ( pos.length > 3 ) {
|
||||
// draw limits
|
||||
ctx.beginPath();
|
||||
|
||||
if ( format == 0 ) {
|
||||
ctx.lineWidth=1;
|
||||
ctx.strokeStyle = '#ff8000';
|
||||
ctx.setLineDash([]);
|
||||
}
|
||||
if ( format == 1 ) {
|
||||
ctx.lineWidth=1;
|
||||
ctx.strokeStyle = '#33cc33';
|
||||
ctx.setLineDash([]);
|
||||
}
|
||||
ctx.lineWidth = div.getAttribute( 'data-'+ type + 'limitsLineWidth' );
|
||||
ctx.strokeStyle = div.getAttribute( 'data-'+ type + 'limitsColor' );
|
||||
ctx.setLineDash( [] );
|
||||
//~ if ( type == 'property' ) {
|
||||
//~ ctx.lineWidth=1;
|
||||
//~ ctx.strokeStyle = '#33cc33';
|
||||
//~ ctx.setLineDash( [] );
|
||||
//~ }
|
||||
|
||||
ctx.moveTo(parseInt(pos[0]),parseInt(pos[1]));
|
||||
for (var i=2;i < pos.length - 1; i+=2 ) {
|
||||
@ -26,12 +73,12 @@ function AutomowerConnectLimits( ctx, pos, format ) {
|
||||
ctx.stroke();
|
||||
|
||||
// limits connector
|
||||
if ( format == 1 ) {
|
||||
for (var i=0;i < pos.length - 1; i+=2 ) {
|
||||
if ( div.getAttribute( 'data-'+ type + 'limitsConnector' ) ) {
|
||||
for ( var i =0 ; i < pos.length - 1; i += 2 ) {
|
||||
ctx.beginPath();
|
||||
ctx.setLineDash([]);
|
||||
ctx.lineWidth=1;
|
||||
ctx.strokeStyle = '#33cc33';
|
||||
ctx.setLineDash( [] );
|
||||
ctx.lineWidth = 1;
|
||||
ctx.strokeStyle = div.getAttribute( 'data-'+ type + 'limitsColor' );
|
||||
ctx.fillStyle= 'white';
|
||||
ctx.moveTo(parseInt(pos[i]),parseInt(pos[i+1]));
|
||||
ctx.arc(parseInt(pos[i]), parseInt(pos[i+1]), 2, 0, 2 * Math.PI, false);
|
||||
@ -74,9 +121,9 @@ function AutomowerConnectScale( ctx, picx, picy, scalx ) {
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
function AutomowerConnectChargingStation( ctx, csx, csy, csrel ) {
|
||||
function AutomowerConnectIcon( ctx, csx, csy, csrel, type ) {
|
||||
if (parseInt(csx) > 0 && parseInt(csy) > 0) {
|
||||
// draw chargingstation
|
||||
// draw icon
|
||||
ctx.beginPath();
|
||||
ctx.setLineDash([]);
|
||||
ctx.lineWidth=3;
|
||||
@ -90,14 +137,16 @@ function AutomowerConnectChargingStation( ctx, csx, csy, csrel ) {
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
|
||||
ctx.font = "16px Arial";
|
||||
if(type == 'CS') ctx.font = "16px Arial";
|
||||
if(type == 'M' ) ctx.font = "20px Arial";
|
||||
if(type == 'E' ) ctx.font = "20px Arial";
|
||||
ctx.fillStyle = "#f15422";
|
||||
ctx.textAlign = "center";
|
||||
if (csrel == 'right') ctx.fillText("CS", parseInt(csx)+13, parseInt(csy)+6);
|
||||
if (csrel == 'bottom') ctx.fillText("CS", parseInt(csx), parseInt(csy)+6+13);
|
||||
if (csrel == 'left') ctx.fillText("CS", parseInt(csx)-13, parseInt(csy)+6);
|
||||
if (csrel == 'top') ctx.fillText("CS", parseInt(csx), parseInt(csy)+6-13);
|
||||
if (csrel == 'center') ctx.fillText("CS", parseInt(csx), parseInt(csy)+6);
|
||||
if (csrel == 'right') ctx.fillText(type, parseInt(csx)+13, parseInt(csy)+6);
|
||||
if (csrel == 'bottom') ctx.fillText(type, parseInt(csx), parseInt(csy)+6+13);
|
||||
if (csrel == 'left') ctx.fillText(type, parseInt(csx)-13, parseInt(csy)+6);
|
||||
if (csrel == 'top') ctx.fillText(type, parseInt(csx), parseInt(csy)+6-13);
|
||||
if (csrel == 'center') ctx.fillText(type, parseInt(csx), parseInt(csy)+6);
|
||||
|
||||
// draw mark
|
||||
ctx.beginPath();
|
||||
@ -111,50 +160,49 @@ function AutomowerConnectChargingStation( ctx, csx, csy, csrel ) {
|
||||
}
|
||||
}
|
||||
|
||||
function AutomowerConnectChargingStationPath ( ctx, pos ) {
|
||||
function AutomowerConnectDrawPath ( ctx, div, pos, type ) {
|
||||
// draw path
|
||||
ctx.beginPath();
|
||||
ctx.lineWidth=1;
|
||||
ctx.setLineDash([6, 2]);
|
||||
ctx.strokeStyle = '#999999';
|
||||
ctx.strokeStyle = div.getAttribute( 'data-'+ type + 'LineColor' );
|
||||
ctx.lineWidth=div.getAttribute( 'data-'+ type + 'LineWidth' );
|
||||
ctx.setLineDash([ div.getAttribute( 'data-'+ type + 'LineDash' ) ]);
|
||||
|
||||
ctx.moveTo(parseInt(pos[0]),parseInt(pos[1]));
|
||||
for (var i=2;i<pos.length-1;i+=2){
|
||||
ctx.lineTo(parseInt(pos[i]),parseInt(pos[i+1]));
|
||||
}
|
||||
ctx.stroke();
|
||||
|
||||
}
|
||||
|
||||
//AutomowerConnectUpdateDetail (<devicename>, <type> <background-image path>, <imagesize x>, <imagesize y>, <relative positio of CS marker>,scalx <path array>, <property limits array>, <property limits array>)
|
||||
function AutomowerConnectUpdateDetail (dev, type, imgsrc, picx, picy, csx, csy, csrel, scalx, pos, lixy, plixy, posc) {
|
||||
//AutomowerConnectUpdateDetail (<devicename>, <type> <background-image path>, <imagesize x>, <imagesize y>, <relative positio of CS marker>,<scale x>, <error description>, <path array>, <area limits array>, <property limits array>, <error array>, <other positions>)
|
||||
function AutomowerConnectUpdateDetail (dev, type, imgsrc, picx, picy, csx, csy, csrel, scalx, errdesc, pos, lixy, plixy, posc, erray, poso) {
|
||||
// log('pos.length '+pos.length+' lixy.length '+lixy.length+', scalx '+scalx );
|
||||
// log('loop: Start '+ type+' '+dev );
|
||||
if (FW_urlParams.detail == dev || 1) {
|
||||
// if (FW_urlParams.detail == dev) {
|
||||
const canvas = document.getElementById(type+'_'+dev+'_canvas');
|
||||
const div = document.getElementById(type+'_'+dev+'_div');
|
||||
if (canvas) {
|
||||
// log('loop: canvas true '+ type+' '+dev );
|
||||
const ctx = canvas.getContext('2d');
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
// draw limits
|
||||
if ( lixy.length > 0 ) AutomowerConnectLimits( ctx, lixy, 0 );
|
||||
if ( plixy.length > 0 ) AutomowerConnectLimits( ctx, plixy, 1 );
|
||||
// draw property limits
|
||||
if ( lixy.length > 0 ) AutomowerConnectLimits( ctx, div, lixy, 'area' );
|
||||
// draw area limits
|
||||
if ( plixy.length > 0 ) AutomowerConnectLimits( ctx, div, plixy, 'property' );
|
||||
// draw scale
|
||||
AutomowerConnectScale( ctx, picx, picy, scalx );
|
||||
AutomowerConnectScale( ctx, div, picx, picy, scalx, 'scale' );
|
||||
// draw charging station path
|
||||
AutomowerConnectChargingStationPath ( ctx, posc );
|
||||
AutomowerConnectDrawPath ( ctx, div, posc, 'chargingStationPath' );
|
||||
// draw path for other activity
|
||||
if ( poso.length > 1 ) AutomowerConnectDrawPath ( ctx, div, poso, 'otherActivityPath' );
|
||||
|
||||
if ( pos.length > 4 ) {
|
||||
// draw path
|
||||
ctx.beginPath();
|
||||
ctx.lineWidth=1;
|
||||
ctx.setLineDash([6, 2]);
|
||||
ctx.strokeStyle = '#ff0000';
|
||||
ctx.moveTo(parseInt(pos[2]),parseInt(pos[3]));
|
||||
for (var i=4;i<pos.length-1;i+=2){
|
||||
ctx.lineTo(parseInt(pos[i]),parseInt(pos[i+1]));
|
||||
}
|
||||
ctx.stroke();
|
||||
// draw mowing path
|
||||
var mowpos = pos.slice(4);
|
||||
AutomowerConnectDrawPath ( ctx, div, mowpos, 'mowingPath' );
|
||||
|
||||
// draw start
|
||||
ctx.beginPath();
|
||||
@ -166,29 +214,9 @@ function AutomowerConnectUpdateDetail (dev, type, imgsrc, picx, picy, csx, csy,
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
|
||||
// draw mower
|
||||
ctx.beginPath();
|
||||
ctx.setLineDash([]);
|
||||
ctx.lineWidth=3;
|
||||
ctx.strokeStyle = '#ffffff';
|
||||
ctx.fillStyle= '#3d3d3d';
|
||||
ctx.arc(parseInt(pos[0]), parseInt(pos[1]-13), 13, 0, 2 * Math.PI, false);
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
// draw mower icon
|
||||
AutomowerConnectIcon( ctx, pos[0], pos[1], 'bottom', 'M' );
|
||||
|
||||
ctx.font = "20px Arial";
|
||||
ctx.fillStyle = "#f15422";
|
||||
ctx.textAlign = "center";
|
||||
ctx.fillText("M", parseInt(pos[0]), parseInt(pos[1]-7));
|
||||
// draw mark
|
||||
ctx.beginPath();
|
||||
ctx.setLineDash([]);
|
||||
ctx.lineWidth=1;
|
||||
ctx.strokeStyle = '#f15422';
|
||||
ctx.fillStyle= '#3d3d3d';
|
||||
ctx.arc(parseInt(pos[0]), parseInt(pos[1]), 2, 0, 2 * Math.PI, false);
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
//draw last line
|
||||
ctx.beginPath();
|
||||
ctx.lineWidth=1;
|
||||
@ -197,22 +225,25 @@ function AutomowerConnectUpdateDetail (dev, type, imgsrc, picx, picy, csx, csy,
|
||||
ctx.lineTo(parseInt(pos[2]),parseInt(pos[3]));
|
||||
ctx.strokeStyle = '#ff0000';
|
||||
ctx.stroke();
|
||||
|
||||
}
|
||||
|
||||
// draw charging station
|
||||
AutomowerConnectChargingStation( ctx, csx, csy, csrel );
|
||||
AutomowerConnectIcon( ctx, csx, csy, csrel, 'CS' );
|
||||
// draw error icon and path
|
||||
if ( errdesc[0] != '-' ) AutomowerConnectShowError( ctx, div, dev, picx, picy, errdesc, erray );
|
||||
// }
|
||||
// img.src = imgsrc;
|
||||
} else {
|
||||
setTimeout(()=>{
|
||||
// log('loop: canvas false '+ type+' '+dev );
|
||||
AutomowerConnectUpdateDetail (dev, type, imgsrc, picx, picy, csx, csy, csrel, scalx, pos, lixy, plixy);
|
||||
AutomowerConnectUpdateDetail (dev, type, imgsrc, picx, picy, csx, csy, csrel, scalx, errdesc, pos, lixy, plixy, posc, erray, poso);
|
||||
}, 100);
|
||||
}
|
||||
} else {
|
||||
setTimeout(()=>{
|
||||
// log('loop: detail false '+ type+' '+dev );
|
||||
AutomowerConnectUpdateDetail (dev, type, imgsrc, picx, picy, csx, csy, csrel, scalx, pos, lixy, plixy);
|
||||
AutomowerConnectUpdateDetail (dev, type, imgsrc, picx, picy, csx, csy, csrel, scalx, errdesc, pos, lixy, plixy, posc, erray, poso);
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user