2
0
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:
Ellert 2023-06-20 22:24:43 +00:00
parent 45d82a85e3
commit f046eb65c5
3 changed files with 238 additions and 43 deletions

View File

@ -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

View File

@ -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 &lt;name&gt; interval &lt;time in seconds&gt;</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 &lt;name&gt; mapImagePath &lt;path to image&gt;</code><br> <code>attr &lt;name&gt; mapImagePath &lt;path to image&gt;</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 &lt;name&gt; addPollingMinInterval &lt;interval in seconds&gt;</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 &lt;name&gt; addPositionPolling &lt;[1|<b>0</b>]&gt;</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 &lt;name&gt; timeoutGetMower &lt;[6 to 60]&gt;</code><br> <code>attr &lt;name&gt; timeoutGetMower &lt;[6 to 60]&gt;</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 &lt;name&gt; timeoutApiAuth &lt;[6 to 60]&gt;</code><br> <code>attr &lt;name&gt; timeoutApiAuth &lt;[6 to 60]&gt;</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 &lt;name&gt; interval &lt;time in seconds&gt;</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 &lt;name&gt; mapImagePath &lt;path to image&gt;</code><br> <code>attr &lt;name&gt; mapImagePath &lt;path to image&gt;</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 &lt;name&gt; addPollingMinInterval &lt;interval in seconds&gt;</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 &lt;name&gt; addPositionPolling &lt;[1|<b>0</b>]&gt;</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>

View File

@ -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;