mirror of
https://github.com/fhem/fhem-mirror.git
synced 2025-01-31 06:39:11 +00:00
74_AutomowerConnect: Common.pm, add additional API polling
git-svn-id: https://svn.fhem.de/fhem/trunk@27696 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
parent
45d82a85e3
commit
f046eb65c5
@ -1,5 +1,6 @@
|
|||||||
# Add changes at the top of the list. Keep it in ASCII, and 80-char wide.
|
# Add changes at the top of the list. Keep it in ASCII, and 80-char wide.
|
||||||
# Do not insert empty lines here, update check depends on it.
|
# Do not insert empty lines here, update check depends on it.
|
||||||
|
- feature: 74_AutomowerConnect: Common.pm, add additional API polling
|
||||||
- feature: 76_SMAInverter: bugfix DC-Power
|
- feature: 76_SMAInverter: bugfix DC-Power
|
||||||
- change: 74_AutomowerConnect: Common.pm, automowerconnect.js
|
- change: 74_AutomowerConnect: Common.pm, automowerconnect.js
|
||||||
handle userattr for API timeouts and logleveldevio, measure
|
handle userattr for API timeouts and logleveldevio, measure
|
||||||
|
@ -77,6 +77,8 @@ sub Initialize() {
|
|||||||
"propertyLimits:textField-long " .
|
"propertyLimits:textField-long " .
|
||||||
"weekdaysToResetWayPoints " .
|
"weekdaysToResetWayPoints " .
|
||||||
"numberOfWayPointsToDisplay " .
|
"numberOfWayPointsToDisplay " .
|
||||||
|
"addPollingMinInterval " .
|
||||||
|
"addPositionPolling:1,0 " .
|
||||||
$::readingFnAttributes;
|
$::readingFnAttributes;
|
||||||
|
|
||||||
$::data{FWEXT}{AutomowerConnect}{SCRIPT} = "automowerconnect.js";
|
$::data{FWEXT}{AutomowerConnect}{SCRIPT} = "automowerconnect.js";
|
||||||
@ -122,7 +124,7 @@ __END__
|
|||||||
<br><br>
|
<br><br>
|
||||||
<ul>
|
<ul>
|
||||||
<li>To get access to the API an application has to be created in the <a target="_blank" href="https://developer.husqvarnagroup.cloud/docs/get-started">Husqvarna Developer Portal</a>. The application has to be connected with the AutomowerConnect API.</li>
|
<li>To get access to the API an application has to be created in the <a target="_blank" href="https://developer.husqvarnagroup.cloud/docs/get-started">Husqvarna Developer Portal</a>. The application has to be connected with the AutomowerConnect API.</li>
|
||||||
<li>During registration an application key (client_id) and an application secret (client secret) is provided. Use these for for the module.</li>
|
<li>During registration an application key (client_id) and an application secret (client secret) is provided. Use these for the module.</li>
|
||||||
<li>The module uses client credentials as grant type for authorization.</li>
|
<li>The module uses client credentials as grant type for authorization.</li>
|
||||||
</ul>
|
</ul>
|
||||||
<br>
|
<br>
|
||||||
@ -243,9 +245,6 @@ __END__
|
|||||||
<a id="AutomowerConnectAttributes"></a>
|
<a id="AutomowerConnectAttributes"></a>
|
||||||
<b>Attributes</b>
|
<b>Attributes</b>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a id='AutomowerConnect-attr-interval'>interval</a><br>
|
|
||||||
<code>attr <name> interval <time in seconds></code><br>
|
|
||||||
Time in seconds that is used to get new data from Husqvarna Cloud. Default: 420</li>
|
|
||||||
<li><a id='AutomowerConnect-attr-mapImagePath'>mapImagePath</a><br>
|
<li><a id='AutomowerConnect-attr-mapImagePath'>mapImagePath</a><br>
|
||||||
<code>attr <name> mapImagePath <path to image></code><br>
|
<code>attr <name> mapImagePath <path to image></code><br>
|
||||||
Path of a raster image file for an area the mower path has to be drawn to.<br>
|
Path of a raster image file for an area the mower path has to be drawn to.<br>
|
||||||
@ -272,7 +271,7 @@ __END__
|
|||||||
<li>mower path for activity MOWING: red</li>
|
<li>mower path for activity MOWING: red</li>
|
||||||
<li>path in CS, activity CHARGING,PARKED_IN_CS: grey</li>
|
<li>path in CS, activity CHARGING,PARKED_IN_CS: grey</li>
|
||||||
<li>path for activity LEAVING: green</li>
|
<li>path for activity LEAVING: green</li>
|
||||||
<li>path for activityGOING_HOME: blue</li>
|
<li>path for activity GOING_HOME: blue</li>
|
||||||
<li>path for interval with error (all activities with error): kind of magenta</li>
|
<li>path for interval with error (all activities with error): kind of magenta</li>
|
||||||
<li>all other activities: grey</li>
|
<li>all other activities: grey</li>
|
||||||
</ul>
|
</ul>
|
||||||
@ -377,6 +376,14 @@ __END__
|
|||||||
}'<br>
|
}'<br>
|
||||||
</code></li>
|
</code></li>
|
||||||
|
|
||||||
|
<li><a id='AutomowerConnect-attr-addPollingMinInterval'>addPollingMinInterval</a><br>
|
||||||
|
<code>attr <name> addPollingMinInterval <interval in seconds></code><br>
|
||||||
|
Set minimum intervall for additional polling, default 0 (no polling). Gets periodically statistics data from mower. Make sure to be within API limits.</li>
|
||||||
|
|
||||||
|
<li><a id='AutomowerConnect-attr-addPositionPolling'>addPositionPolling</a><br>
|
||||||
|
<code>attr <name> addPositionPolling <[1|<b>0</b>]></code><br>
|
||||||
|
Set position polling, default 0 (no position polling). Gets periodically position data from mower, instead from websocket. Must not be set without setting attribute addPollingMinInterval.</li>
|
||||||
|
|
||||||
<li><a href="disable">disable</a></li>
|
<li><a href="disable">disable</a></li>
|
||||||
|
|
||||||
<li><a href="disabledForIntervals">disabledForIntervals</a></li>
|
<li><a href="disabledForIntervals">disabledForIntervals</a></li>
|
||||||
@ -398,7 +405,7 @@ __END__
|
|||||||
<code>attr <name> timeoutGetMower <[6 to 60]></code><br>
|
<code>attr <name> timeoutGetMower <[6 to 60]></code><br>
|
||||||
Set timeout for API call, default 5 s. </li>
|
Set timeout for API call, default 5 s. </li>
|
||||||
|
|
||||||
<li><a id='AutomowerConnect-attr-timeoutApiAuth'>ApiAuth</a><br>
|
<li><a id='AutomowerConnect-attr-timeoutApiAuth'>timeoutApiAuth</a><br>
|
||||||
<code>attr <name> timeoutApiAuth <[6 to 60]></code><br>
|
<code>attr <name> timeoutApiAuth <[6 to 60]></code><br>
|
||||||
Set timeout for API call, default 5 s. </li>
|
Set timeout for API call, default 5 s. </li>
|
||||||
|
|
||||||
@ -415,6 +422,7 @@ __END__
|
|||||||
<b>Readings</b>
|
<b>Readings</b>
|
||||||
<ul>
|
<ul>
|
||||||
<li>api_MowerFound - all mower registered under the application key (client_id) </li>
|
<li>api_MowerFound - all mower registered under the application key (client_id) </li>
|
||||||
|
<li>api_callsThisMonth - counts monthly API calls, if attribute addPollingMinInterval is set.</li>
|
||||||
<li>api_token_expires - date when session of Husqvarna Cloud expires</li>
|
<li>api_token_expires - date when session of Husqvarna Cloud expires</li>
|
||||||
<li>batteryPercent - battery state of charge in percent</li>
|
<li>batteryPercent - battery state of charge in percent</li>
|
||||||
<li>mower_activity - current activity "UNKNOWN" | "NOT_APPLICABLE" | "MOWING" | "GOING_HOME" | "CHARGING" | "LEAVING" | "PARKED_IN_CS" | "STOPPED_IN_GARDEN"</li>
|
<li>mower_activity - current activity "UNKNOWN" | "NOT_APPLICABLE" | "MOWING" | "GOING_HOME" | "CHARGING" | "LEAVING" | "PARKED_IN_CS" | "STOPPED_IN_GARDEN"</li>
|
||||||
@ -596,10 +604,6 @@ __END__
|
|||||||
<a id="AutomowerConnectAttributes"></a>
|
<a id="AutomowerConnectAttributes"></a>
|
||||||
<b>Attributes</b>
|
<b>Attributes</b>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a id='AutomowerConnect-attr-interval'>interval</a><br>
|
|
||||||
<code>attr <name> interval <time in seconds></code><br>
|
|
||||||
Zeit in Sekunden nach denen neue Daten aus der Husqvarna Cloud abgerufen werden. Standard: 420</li>
|
|
||||||
|
|
||||||
<li><a id='AutomowerConnect-attr-mapImagePath'>mapImagePath</a><br>
|
<li><a id='AutomowerConnect-attr-mapImagePath'>mapImagePath</a><br>
|
||||||
<code>attr <name> mapImagePath <path to image></code><br>
|
<code>attr <name> mapImagePath <path to image></code><br>
|
||||||
Pfad zur Bilddatei. Auf das Bild werden Pfad, Anfangs- u. Endpunkte gezeichnet.<br>
|
Pfad zur Bilddatei. Auf das Bild werden Pfad, Anfangs- u. Endpunkte gezeichnet.<br>
|
||||||
@ -735,6 +739,14 @@ __END__
|
|||||||
}'<br>
|
}'<br>
|
||||||
</code></li>
|
</code></li>
|
||||||
|
|
||||||
|
<li><a id='AutomowerConnect-attr-addPollingMinInterval'>addPollingMinInterval</a><br>
|
||||||
|
<code>attr <name> addPollingMinInterval <interval in seconds></code><br>
|
||||||
|
Setzt das Mindestintervall für zusätzliches Polling der API, default 0 (kein Polling). Liest periodisch statistische Daten vom Mäher. Es muss sichergestellt werden, das die API Begrenzung (10000 Anfragen pro Monat) eingehalten wird.</li>
|
||||||
|
|
||||||
|
<li><a id='AutomowerConnect-attr-addPositionPolling'>addPositionPolling</a><br>
|
||||||
|
<code>attr <name> addPositionPolling <[1|<b>0</b>]></code><br>
|
||||||
|
Setzt das Positionspolling, default 0 (kein Positionpolling). Liest periodisch Positiondaten des Mähers, an Stelle der über Websocket gelieferten Daten. Darf nur im Zusammenhang mit dem Attribut addPollingMinInterval verwendet werden.</li>
|
||||||
|
|
||||||
<li><a href="disable">disable</a></li>
|
<li><a href="disable">disable</a></li>
|
||||||
|
|
||||||
<li><a href="disabledForIntervals">disabledForIntervals</a></li>
|
<li><a href="disabledForIntervals">disabledForIntervals</a></li>
|
||||||
@ -773,6 +785,7 @@ __END__
|
|||||||
<b>Readings</b>
|
<b>Readings</b>
|
||||||
<ul>
|
<ul>
|
||||||
<li>api_MowerFound - Alle Mähroboter, die unter dem genutzten Application Key (client_id) registriert sind.</li>
|
<li>api_MowerFound - Alle Mähroboter, die unter dem genutzten Application Key (client_id) registriert sind.</li>
|
||||||
|
<li>api_callsThisMonth - Zählt die im Monat erfolgten API Aufrufe, wenn das Attribut addPollingMinInterval gesetzt ist.</li>
|
||||||
<li>api_token_expires - Datum wann die Session der Husqvarna Cloud abläuft</li>
|
<li>api_token_expires - Datum wann die Session der Husqvarna Cloud abläuft</li>
|
||||||
<li>batteryPercent - Batterieladung in Prozent</li>
|
<li>batteryPercent - Batterieladung in Prozent</li>
|
||||||
<li>mower_activity - aktuelle Aktivität "UNKNOWN" | "NOT_APPLICABLE" | "MOWING" | "GOING_HOME" | "CHARGING" | "LEAVING" | "PARKED_IN_CS" | "STOPPED_IN_GARDEN"</li>
|
<li>mower_activity - aktuelle Aktivität "UNKNOWN" | "NOT_APPLICABLE" | "MOWING" | "GOING_HOME" | "CHARGING" | "LEAVING" | "PARKED_IN_CS" | "STOPPED_IN_GARDEN"</li>
|
||||||
|
@ -94,9 +94,11 @@ if ($@) {
|
|||||||
}
|
}
|
||||||
$errorjson = undef;
|
$errorjson = undef;
|
||||||
|
|
||||||
use constant AUTHURL => 'https://api.authentication.husqvarnagroup.dev/v1';
|
use constant {
|
||||||
use constant APIURL => 'https://api.amc.husqvarna.dev/v1';
|
AUTHURL => 'https://api.authentication.husqvarnagroup.dev/v1',
|
||||||
use constant WSDEVICENAME => 'wss:ws.openapi.husqvarna.dev:443/v1';
|
APIURL => 'https://api.amc.husqvarna.dev/v1',
|
||||||
|
WSDEVICENAME => 'wss:ws.openapi.husqvarna.dev:443/v1'
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
##############################################################
|
##############################################################
|
||||||
@ -173,11 +175,13 @@ my $mapZonesTpl = '{
|
|||||||
interval => 840,
|
interval => 840,
|
||||||
interval_ws => 7110,
|
interval_ws => 7110,
|
||||||
interval_ping => 60,
|
interval_ping => 60,
|
||||||
|
use_position_polling => 0,
|
||||||
|
additional_polling => 0,
|
||||||
retry_interval_apiauth => 840,
|
retry_interval_apiauth => 840,
|
||||||
retry_interval_getmower => 840,
|
retry_interval_getmower => 840,
|
||||||
timeout_apiauth => 5,
|
timeout_apiauth => 5,
|
||||||
timeout_getmower => 5,
|
timeout_getmower => 5,
|
||||||
timeout_cmd => 15,
|
timeout_cmd => 10,
|
||||||
midnightCycle => 1,
|
midnightCycle => 1,
|
||||||
client_id => $client_id,
|
client_id => $client_id,
|
||||||
grant_type => 'client_credentials',
|
grant_type => 'client_credentials',
|
||||||
@ -197,6 +201,7 @@ my $mapZonesTpl = '{
|
|||||||
newdatasets => 0,
|
newdatasets => 0,
|
||||||
newzonedatasets => 0,
|
newzonedatasets => 0,
|
||||||
positionsTime => 0,
|
positionsTime => 0,
|
||||||
|
storesum => 0,
|
||||||
statusTime => 0,
|
statusTime => 0,
|
||||||
cspos => [],
|
cspos => [],
|
||||||
areapos => [],
|
areapos => [],
|
||||||
@ -680,6 +685,8 @@ sub APIAuth {
|
|||||||
|
|
||||||
my $header = "Content-Type: application/x-www-form-urlencoded\r\nAccept: application/json";
|
my $header = "Content-Type: application/x-www-form-urlencoded\r\nAccept: application/json";
|
||||||
my $data = 'grant_type=' . $grant_type.'&client_id=' . $client_id . '&client_secret=' . $client_secret;
|
my $data = 'grant_type=' . $grant_type.'&client_id=' . $client_id . '&client_secret=' . $client_secret;
|
||||||
|
readingsSingleUpdate( $hash, 'api_callsThisMonth' , ReadingsVal( $name, 'api_callsThisMonth', 0 ) + 1, 0) if ( $hash->{helper}{additional_polling} );
|
||||||
|
|
||||||
::HttpUtils_NonblockingGet( {
|
::HttpUtils_NonblockingGet( {
|
||||||
url => AUTHURL . '/oauth2/token',
|
url => AUTHURL . '/oauth2/token',
|
||||||
timeout => $timeout,
|
timeout => $timeout,
|
||||||
@ -792,21 +799,147 @@ sub getMower {
|
|||||||
|
|
||||||
my $header = "Accept: application/vnd.api+json\r\nX-Api-Key: " . $client_id . "\r\nAuthorization: Bearer " . $access_token . "\r\nAuthorization-Provider: " . $provider;
|
my $header = "Accept: application/vnd.api+json\r\nX-Api-Key: " . $client_id . "\r\nAuthorization: Bearer " . $access_token . "\r\nAuthorization-Provider: " . $provider;
|
||||||
Log3 $name, 5, "$iam header [ $header ]";
|
Log3 $name, 5, "$iam header [ $header ]";
|
||||||
|
readingsSingleUpdate( $hash, 'api_callsThisMonth' , ReadingsVal( $name, 'api_callsThisMonth', 0 ) + 1, 0) if ( $hash->{helper}{additional_polling} );
|
||||||
|
|
||||||
::HttpUtils_NonblockingGet({
|
::HttpUtils_NonblockingGet({
|
||||||
url => APIURL . "/mowers",
|
url => APIURL . '/mowers',
|
||||||
timeout => $timeout,
|
timeout => $timeout,
|
||||||
hash => $hash,
|
hash => $hash,
|
||||||
method => "GET",
|
method => "GET",
|
||||||
header => $header,
|
header => $header,
|
||||||
callback => \&getMowerResponse,
|
callback => \&getMowerResponse,
|
||||||
t_begin => scalar gettimeofday()
|
t_begin => scalar gettimeofday()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
return undef;
|
return undef;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#########################
|
||||||
|
sub getMowerWs {
|
||||||
|
|
||||||
|
my ( $hash ) = @_;
|
||||||
|
my $name = $hash->{NAME};
|
||||||
|
my $type = $hash->{TYPE};
|
||||||
|
my $iam = "$type $name getMowerWs:";
|
||||||
|
my $access_token = ReadingsVal($name,".access_token","");
|
||||||
|
my $provider = ReadingsVal($name,".provider","");
|
||||||
|
my $client_id = $hash->{helper}->{client_id};
|
||||||
|
my $timeout = AttrVal( $name, 'timeoutGetMower', $hash->{helper}->{timeout_getmower} );
|
||||||
|
|
||||||
|
my $header = "Accept: application/vnd.api+json\r\nX-Api-Key: " . $client_id . "\r\nAuthorization: Bearer " . $access_token . "\r\nAuthorization-Provider: " . $provider;
|
||||||
|
Log3 $name, 5, "$iam header [ $header ]";
|
||||||
|
readingsSingleUpdate( $hash, 'api_callsThisMonth' , ReadingsVal( $name, 'api_callsThisMonth', 0 ) + 1, 0) if ( $hash->{helper}{additional_polling} );
|
||||||
|
|
||||||
|
::HttpUtils_NonblockingGet( {
|
||||||
|
url => APIURL . '/mowers/' . $hash->{helper}{mower}{id},
|
||||||
|
timeout => $timeout,
|
||||||
|
hash => $hash,
|
||||||
|
method => "GET",
|
||||||
|
header => $header,
|
||||||
|
callback => \&getMowerResponseWs,
|
||||||
|
t_begin => scalar gettimeofday()
|
||||||
|
} );
|
||||||
|
|
||||||
|
return undef;
|
||||||
|
}
|
||||||
|
|
||||||
|
#########################
|
||||||
|
sub getMowerResponseWs {
|
||||||
|
|
||||||
|
my ( $param, $err, $data ) = @_;
|
||||||
|
my $hash = $param->{hash};
|
||||||
|
my $name = $hash->{NAME};
|
||||||
|
my $type = $hash->{TYPE};
|
||||||
|
my $statuscode = $param->{code} // '';
|
||||||
|
my $iam = "$type $name getMowerResponseWs:";
|
||||||
|
|
||||||
|
Log3 $name, 1, "$iam response time ". sprintf( "%.2f", ( gettimeofday() - $param->{t_begin} ) ) . ' s' if ( $param->{timeout} == 60 );
|
||||||
|
Log3 $name, 1, "debug $iam \$statuscode [$statuscode]\n\$err [$err],\n \$data [$data] \n\$param->url $param->{url}" if ( AttrVal($name, 'debug', '') );
|
||||||
|
|
||||||
|
if( !$err && $statuscode == 200 && $data) {
|
||||||
|
|
||||||
|
if ( $data eq '' ) {
|
||||||
|
|
||||||
|
Log3 $name, 2, "$iam no mower data present";
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
my $result = eval { decode_json($data) };
|
||||||
|
|
||||||
|
if ($@) {
|
||||||
|
|
||||||
|
Log3( $name, 2, "$iam - JSON error while request: $@");
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
$hash->{helper}{wsResult}{mower} = dclone( $result->{data} ) if ( AttrVal($name, 'debug', '') );
|
||||||
|
$hash->{helper}{mower}{attributes}{statistics} = dclone( $result->{data}{attributes}{statistics} );
|
||||||
|
|
||||||
|
if ( $hash->{helper}{use_position_polling} ) {
|
||||||
|
|
||||||
|
my $cnt = 0;
|
||||||
|
my $tmp = [];
|
||||||
|
my $poslen = @{ $result->{data}{attributes}{positions} };
|
||||||
|
|
||||||
|
for ( $cnt = 0; $cnt < $poslen; $cnt++ ) {
|
||||||
|
|
||||||
|
if ( $hash->{helper}{searchpos}[ 0 ]{longitude} == $result->{data}{attributes}{positions}[ $cnt ]{longitude}
|
||||||
|
&& $hash->{helper}{searchpos}[ 0 ]{latitude} == $result->{data}{attributes}{positions}[ $cnt ]{latitude} ) {
|
||||||
|
|
||||||
|
if ( $cnt > 0 ) {
|
||||||
|
|
||||||
|
my @ar;
|
||||||
|
push @ar, @{ $result->{data}{attributes}{positions} }[ 0 .. $cnt-1 ];
|
||||||
|
$hash->{helper}{mower}{attributes}{positions} = dclone( \@ar );
|
||||||
|
AlignArray( $hash );
|
||||||
|
FW_detailFn_Update ($hash);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
$hash->{helper}{mower}{attributes}{positions} = [];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
last;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
isErrorThanPrepare( $hash );
|
||||||
|
resetLastErrorIfCorrected( $hash );
|
||||||
|
|
||||||
|
# Update readings
|
||||||
|
readingsBeginUpdate($hash);
|
||||||
|
|
||||||
|
fillReadings( $hash );
|
||||||
|
# readingsBulkUpdate( $hash, 'mower_wsEvent', $hash->{helper}{wsResult}{type} ); #to do check what event
|
||||||
|
readingsBulkUpdate( $hash, 'mower_wsEvent', 'status-event' );
|
||||||
|
|
||||||
|
readingsEndUpdate($hash, 1);
|
||||||
|
|
||||||
|
$hash->{helper}{searchpos} = [ dclone $result->{data}{attributes}{positions}[ 0 ] ];
|
||||||
|
|
||||||
|
return undef;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
readingsSingleUpdate( $hash, 'device_state', "additional Polling error statuscode $statuscode", 1 );
|
||||||
|
Log3 $name, 1, "$iam \$statuscode [$statuscode]\n\$err [$err],\n \$data [$data] \n\$param->url $param->{url}";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return undef;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
#########################
|
#########################
|
||||||
sub getMowerResponse {
|
sub getMowerResponse {
|
||||||
|
|
||||||
@ -866,6 +999,7 @@ sub getMowerResponse {
|
|||||||
$hash->{helper}{mowerold}{attributes}{mower}{activity} = $hash->{helper}{mowers}[$mowerNumber]{attributes}{mower}{activity};
|
$hash->{helper}{mowerold}{attributes}{mower}{activity} = $hash->{helper}{mowers}[$mowerNumber]{attributes}{mower}{activity};
|
||||||
$hash->{helper}{mowerold}{attributes}{statistics}{numberOfCollisions} = $hash->{helper}{mowers}[$mowerNumber]{attributes}{statistics}{numberOfCollisions};
|
$hash->{helper}{mowerold}{attributes}{statistics}{numberOfCollisions} = $hash->{helper}{mowers}[$mowerNumber]{attributes}{statistics}{numberOfCollisions};
|
||||||
$hash->{helper}{statistics}{numberOfCollisionsOld} = $hash->{helper}{mowers}[$mowerNumber]{attributes}{statistics}{numberOfCollisions};
|
$hash->{helper}{statistics}{numberOfCollisionsOld} = $hash->{helper}{mowers}[$mowerNumber]{attributes}{statistics}{numberOfCollisions};
|
||||||
|
$hash->{helper}{searchpos} = [ dclone $hash->{helper}{mowers}[$mowerNumber]{attributes}{positions}[0] ];
|
||||||
|
|
||||||
if ( AttrVal( $name, 'mapImageCoordinatesToRegister', '' ) eq '' ) {
|
if ( AttrVal( $name, 'mapImageCoordinatesToRegister', '' ) eq '' ) {
|
||||||
posMinMax( $hash, $hash->{helper}{mowers}[$mowerNumber]{attributes}{positions} );
|
posMinMax( $hash, $hash->{helper}{mowers}[$mowerNumber]{attributes}{positions} );
|
||||||
@ -959,7 +1093,6 @@ sub CMD {
|
|||||||
|
|
||||||
|
|
||||||
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";
|
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' }
|
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 "ParkUntilNextSchedule") { $json = '{"data": {"type":"'.$cmd[0].'"}}'; $post = 'actions' }
|
||||||
@ -970,7 +1103,7 @@ my $header = "Accept: application/vnd.api+json\r\nX-Api-Key: ".$client_id."\r\nA
|
|||||||
elsif ($cmd[0] eq "headlight") { $json = '{"data": {"type":"settings","attributes":{"'.$cmd[0].'": {"mode": "'.$cmd[1].'"}}}}'; $post = 'settings' }
|
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 "cuttingHeight") { $json = '{"data": {"type":"settings","attributes":{"'.$cmd[0].'": '.$cmd[1].'}}}'; $post = 'settings' }
|
||||||
elsif ($cmd[0] eq "sendScheduleFromAttributeToMower" && AttrVal( $name, 'mowerSchedule', '')) {
|
elsif ($cmd[0] eq "sendScheduleFromAttributeToMower" && AttrVal( $name, 'mowerSchedule', '')) {
|
||||||
|
|
||||||
my $perl = eval { decode_json (AttrVal( $name, 'mowerSchedule', '')) };
|
my $perl = eval { decode_json (AttrVal( $name, 'mowerSchedule', '')) };
|
||||||
if ($@) {
|
if ($@) {
|
||||||
return "$iam decode error: $@ \n $perl";
|
return "$iam decode error: $@ \n $perl";
|
||||||
@ -984,8 +1117,9 @@ my $header = "Accept: application/vnd.api+json\r\nX-Api-Key: ".$client_id."\r\nA
|
|||||||
}
|
}
|
||||||
|
|
||||||
Log3 $name, 5, "$iam $header \n $cmd[0] \n $json";
|
Log3 $name, 5, "$iam $header \n $cmd[0] \n $json";
|
||||||
|
readingsSingleUpdate( $hash, 'api_callsThisMonth' , ReadingsVal( $name, 'api_callsThisMonth', 0 ) + 1, 0) if ( $hash->{helper}{additional_polling} );
|
||||||
|
|
||||||
::HttpUtils_NonblockingGet({
|
::HttpUtils_NonblockingGet( {
|
||||||
url => APIURL . "/mowers/". $mower_id . "/".$post,
|
url => APIURL . "/mowers/". $mower_id . "/".$post,
|
||||||
timeout => $timeout,
|
timeout => $timeout,
|
||||||
hash => $hash,
|
hash => $hash,
|
||||||
@ -993,9 +1127,9 @@ my $header = "Accept: application/vnd.api+json\r\nX-Api-Key: ".$client_id."\r\nA
|
|||||||
header => $header,
|
header => $header,
|
||||||
data => $json,
|
data => $json,
|
||||||
callback => \&CMDResponse,
|
callback => \&CMDResponse,
|
||||||
t_begin => scalar gettimeofday()
|
t_begin => scalar gettimeofday()
|
||||||
});
|
} );
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
##############################################################
|
##############################################################
|
||||||
@ -1266,6 +1400,37 @@ sub Attr {
|
|||||||
|
|
||||||
}
|
}
|
||||||
##########
|
##########
|
||||||
|
} elsif( $attrName eq 'addPollingMinInterval' ) {
|
||||||
|
|
||||||
|
if( $cmd eq "set" ) {
|
||||||
|
|
||||||
|
return "$iam $attrVal is invalid, allowed time in seconds as integer 0, 29 and higher." unless( $attrVal =~ /^\d+$/ && ( $attrVal == 0 || $attrVal > 28 ) );
|
||||||
|
$hash->{helper}{additional_polling} = $attrVal;
|
||||||
|
Log3 $name, 4, "$iam $cmd $attrName $attrVal";
|
||||||
|
|
||||||
|
} elsif( $cmd eq "del" ) {
|
||||||
|
|
||||||
|
$hash->{helper}{additional_polling} = 0;
|
||||||
|
readingsDelete( $hash, 'api_callsThisMonth' );
|
||||||
|
Log3 $name, 3, "$iam $cmd $attrName and set default value 0.";
|
||||||
|
|
||||||
|
}
|
||||||
|
##########
|
||||||
|
} elsif( $attrName eq 'addPositionPolling' ) {
|
||||||
|
|
||||||
|
if( $cmd eq "set" ) {
|
||||||
|
|
||||||
|
return "$iam $attrVal is invalid, allowed time in seconds as integer 0, 29 and higher." unless( $attrVal == 0 || $attrVal == 1 );
|
||||||
|
$hash->{helper}{use_position_polling} = $attrVal;
|
||||||
|
Log3 $name, 4, "$iam $cmd $attrName $attrVal";
|
||||||
|
|
||||||
|
} elsif( $cmd eq "del" ) {
|
||||||
|
|
||||||
|
$hash->{helper}{use_position_polling} = 0;
|
||||||
|
Log3 $name, 3, "$iam $cmd $attrName and set default value 0.";
|
||||||
|
|
||||||
|
}
|
||||||
|
##########
|
||||||
} elsif ( $attrName eq 'numberOfWayPointsToDisplay' ) {
|
} elsif ( $attrName eq 'numberOfWayPointsToDisplay' ) {
|
||||||
|
|
||||||
my $icurr = scalar @{$hash->{helper}{areapos}};
|
my $icurr = scalar @{$hash->{helper}{areapos}};
|
||||||
@ -1434,6 +1599,7 @@ sub Attr {
|
|||||||
sub AlignArray {
|
sub AlignArray {
|
||||||
my ($hash) = @_;
|
my ($hash) = @_;
|
||||||
my $name = $hash->{NAME};
|
my $name = $hash->{NAME};
|
||||||
|
my $use_position_polling = AttrVal( $name, 'addPositionPolling', 0 );
|
||||||
my $act = $hash->{helper}{mower}{attributes}{mower}{activity};
|
my $act = $hash->{helper}{mower}{attributes}{mower}{activity};
|
||||||
my $actold = $hash->{helper}{mowerold}{attributes}{mower}{activity};
|
my $actold = $hash->{helper}{mowerold}{attributes}{mower}{activity};
|
||||||
my $cnt = @{ $hash->{helper}{mower}{attributes}{positions} };
|
my $cnt = @{ $hash->{helper}{mower}{attributes}{positions} };
|
||||||
@ -1455,9 +1621,12 @@ sub AlignArray {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ar = reverse @ar if ( $cnt > 1 ); # positions seem to be in reversed order
|
if ( !$use_position_polling ) {
|
||||||
|
|
||||||
# @ar = @ar if ( $cnt > 1 ); # positions seem to be not in reversed order
|
@ar = reverse @ar if ( $cnt > 1 ); # positions seem to be in reversed order
|
||||||
|
# @ar = @ar if ( $cnt > 1 ); # positions seem to be not in reversed order
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
$tmp = dclone( \@ar );
|
$tmp = dclone( \@ar );
|
||||||
|
|
||||||
@ -1972,6 +2141,8 @@ sub calculateStatistics {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
readingsSingleUpdate( $hash, 'api_callsThisMonth' , 0, 0) if ( $hash->{helper}{additional_polling} && $time[3] == 1 ); # reset monthly API calls
|
||||||
|
|
||||||
#clear position arrays
|
#clear position arrays
|
||||||
if ( AttrVal( $name, 'weekdaysToResetWayPoints', 1 ) =~ $time[6] ) {
|
if ( AttrVal( $name, 'weekdaysToResetWayPoints', 1 ) =~ $time[6] ) {
|
||||||
|
|
||||||
@ -2356,7 +2527,8 @@ sub wsRead {
|
|||||||
my $name = $hash->{NAME};
|
my $name = $hash->{NAME};
|
||||||
my $type = $hash->{TYPE};
|
my $type = $hash->{TYPE};
|
||||||
my $iam = "$type $name wsRead:";
|
my $iam = "$type $name wsRead:";
|
||||||
|
my $additional_polling = $hash->{helper}{additional_polling} * 1000;
|
||||||
|
my $use_position_polling = $hash->{helper}{use_position_polling};
|
||||||
my $buf = DevIo_SimpleRead( $hash );
|
my $buf = DevIo_SimpleRead( $hash );
|
||||||
return "" if ( !defined($buf) );
|
return "" if ( !defined($buf) );
|
||||||
|
|
||||||
@ -2393,35 +2565,44 @@ sub wsRead {
|
|||||||
$hash->{helper}{mower}{attributes}{mower} = dclone( $result->{attributes}{mower} );
|
$hash->{helper}{mower}{attributes}{mower} = dclone( $result->{attributes}{mower} );
|
||||||
$hash->{helper}{mower}{attributes}{planner} = dclone( $result->{attributes}{planner} );
|
$hash->{helper}{mower}{attributes}{planner} = dclone( $result->{attributes}{planner} );
|
||||||
$hash->{helper}{storediff} = $hash->{helper}{mower}{attributes}{metadata}{statusTimestamp} - $hash->{helper}{mowerold}{attributes}{metadata}{statusTimestamp};
|
$hash->{helper}{storediff} = $hash->{helper}{mower}{attributes}{metadata}{statusTimestamp} - $hash->{helper}{mowerold}{attributes}{metadata}{statusTimestamp};
|
||||||
|
$hash->{helper}{storesum} += $hash->{helper}{storediff} if ( $additional_polling );
|
||||||
|
|
||||||
isErrorThanPrepare( $hash );
|
if ( !$additional_polling ) {
|
||||||
resetLastErrorIfCorrected( $hash );
|
|
||||||
|
isErrorThanPrepare( $hash );
|
||||||
|
resetLastErrorIfCorrected( $hash );
|
||||||
|
|
||||||
|
} elsif ( $additional_polling < $hash->{helper}{storesum} && !$hash->{helper}{midnightCycle} ) {
|
||||||
|
|
||||||
|
$hash->{helper}{storesum} = 0;
|
||||||
|
# RemoveInternalTimer( $hash, \&getMowerWs );
|
||||||
|
# InternalTimer(gettimeofday() + 2, \&getMowerWs, $hash, 0 );
|
||||||
|
getMowerWs( $hash );
|
||||||
|
Log3 $name, 4, "$iam received websocket data, and polling is on: >$buf<";
|
||||||
|
$hash->{First_Read} = 0;
|
||||||
|
return;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( $result->{type} eq "positions-event" ) {
|
if ( $result->{type} eq "positions-event" ) {
|
||||||
|
|
||||||
|
if ( !$use_position_polling || $use_position_polling && !$additional_polling ) {
|
||||||
|
|
||||||
$hash->{helper}{positionsTime} = gettimeofday();
|
$hash->{helper}{positionsTime} = gettimeofday();
|
||||||
# for ( my $i=0;$i<@{$result->{attributes}{positions}};$i++ ) {
|
|
||||||
# $result->{attributes}{positions}[ $i ]->{nr}=$i;
|
|
||||||
# };
|
|
||||||
$hash->{helper}{mower}{attributes}{positions} = dclone( $result->{attributes}{positions} );
|
$hash->{helper}{mower}{attributes}{positions} = dclone( $result->{attributes}{positions} );
|
||||||
|
|
||||||
AlignArray( $hash );
|
AlignArray( $hash );
|
||||||
|
FW_detailFn_Update ($hash);
|
||||||
|
|
||||||
# my $deltaTime = $hash->{helper}{positionsTime} - $hash->{helper}{statusTime};
|
} elsif ( $use_position_polling && $additional_polling ) {
|
||||||
|
|
||||||
# if encounter positions shortly after status-event count it as error positions
|
Log3 $name, 4, "$iam received websocket data, but position polling is on: >$buf<";
|
||||||
# if ( $hash->{helper}{mower}{attributes}{mower}{errorCode} && $deltaTime > 0 && $deltaTime < 0.29 && @{ $result->{attributes}{positions} } < 3) {
|
$hash->{First_Read} = 0;
|
||||||
|
return;
|
||||||
|
|
||||||
# $hash->{helper}{areapos}[ 0 ]{act} = 'N';
|
}
|
||||||
# $hash->{helper}{areapos}[ 1 ]{act} = 'N';
|
|
||||||
# $hash->{helper}{lasterror}{positions} = [dclone( $hash->{helper}{areapos}[ 0 ] ), dclone( $hash->{helper}{areapos}[ 1 ] ) ];
|
|
||||||
# $hash->{helper}{errorstack}[0]{positions} = [dclone( $hash->{helper}{areapos}[ 0 ] ), dclone( $hash->{helper}{areapos}[ 1 ] ) ];
|
|
||||||
|
|
||||||
# }
|
|
||||||
|
|
||||||
FW_detailFn_Update ($hash);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2446,8 +2627,8 @@ sub wsRead {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
Log3 $name, 4, "$iam received websocket data: >$buf<";
|
|
||||||
|
|
||||||
|
Log3 $name, 4, "$iam received websocket data: >$buf<";
|
||||||
$hash->{First_Read} = 0;
|
$hash->{First_Read} = 0;
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user