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

4_AutomowerConnect: Common.pm, automowerconnect.js improved disabled handling, reworked use of differential data for map update

git-svn-id: https://svn.fhem.de/fhem/trunk@27672 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
Ellert 2023-06-11 07:38:14 +00:00
parent 87857ad1d7
commit 28159b648c
4 changed files with 124 additions and 134 deletions

View File

@ -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.
- change: 74_AutomowerConnect: Common.pm, automowerconnect.js
improved disabled handling, reworked use only differential data
- feature: OpenWeatherMapAPI: add human-readable text of daily forecast
- feature: 48_BlinkCamera: Support for doorbell added
- change: 50_Signalbot: Minor change in event behavior and catch dbus error

View File

@ -121,9 +121,8 @@ __END__
<u><b>Requirements</b></u>
<br><br>
<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>.</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>Don't forget to connect you API key to the Automower API.</li>
<li>The module uses client credentials as grant type for authorization.</li>
</ul>
<br>
@ -180,11 +179,11 @@ __END__
<li><a id='AutomowerConnect-set-getNewAccessToken'>getNewAccessToken</a><br>
<code>set &lt;name&gt; getNewAccessToken</code><br>
Gets a new access token</li>
For debug purpose only.</li>
<li><a id='AutomowerConnect-set-getUpdate'>getUpdate</a><br>
<code>set &lt;name&gt; getUpdate</code><br>
Gets data from the API. This is done each intervall automatically.</li>
For debug purpose only.</li>
<li><a id='AutomowerConnect-set-headlight'>headlight</a><br>
<code>set &lt;name&gt; headlight &lt;ALWAYS_OFF|ALWAYS_ON|EVENIG_ONLY|EVENING_AND_NIGHT&gt;</code><br>
@ -410,8 +409,8 @@ __END__
<li>statistics_newGeoDataSets - number of new data sets between the last two different time stamps</li>
<li>statistics_numberOfCollisions - Number of collisions (last day/all days)</li>
<li>status_connected - state of connetion between mower and Husqvarna Cloud.</li>
<li>status_statusTimestamp - local time of last change of the API content</li>
<li>status_statusTimestampDiff - time difference in seconds between the last and second last change of the API content</li>
<li>status_statusTimestamp - local time of last status update</li>
<li>status_statusTimestampDiff - time difference in seconds between the last and second last status update</li>
<li>system_name - name of the mower</li>
</ul>
@ -446,9 +445,8 @@ __END__
<u><b>Anforderungen</b></u>
<br><br>
<ul>
<li>Für den Zugriff auf die API muss eine Application angelegt werden, im <a target="_blank" href="https://developer.husqvarnagroup.cloud/docs/get-started">Husqvarna Developer Portal</a>.</li>
<li>Für den Zugriff auf die API muss eine Application angelegt werden, im <a target="_blank" href="https://developer.husqvarnagroup.cloud/docs/get-started">Husqvarna Developer Portal</a>angelegt und mit der Automower Connect API verbunden werden.</li>
<li>Währenddessen wird ein Application Key (client_id) und ein Application Secret (client secret) bereitgestellt. Diese sind für dieses Modul zu nutzen.</li>
<li>Der Application Key muss im Portal mit der Automower API verbunden werden.</li>
<li>Das Modul nutzt Client Credentials als Granttype zur Authorisierung.</li>
<br>
</ul>
@ -506,11 +504,11 @@ __END__
<li><a id='AutomowerConnect-set-getNewAccessToken'>getNewAccessToken</a><br>
<code>set &lt;name&gt; getNewAccessToken</code><br>
Holt ein neues Access Token.</li>
Nur zur Fehlerbehebung.</li>
<li><a id='AutomowerConnect-set-getUpdate'>getUpdate</a><br>
<code>set &lt;name&gt; getUpdate</code><br>
Liest die Daten von der API. Das passiert jedes Interval automatisch.</li>
Nur zur Fehlerbehebung.</li>
<li><a id='AutomowerConnect-set-headlight'>headlight</a><br>
<code>set &lt;name&gt; headlight &lt;ALWAYS_OFF|ALWAYS_ON|EVENIG_ONLY|EVENING_AND_NIGHT&gt;</code><br>
@ -741,8 +739,8 @@ __END__
<li>statistics_newGeoDataSets - Anzahl der neuen Datensätze zwischen den letzten zwei unterschiedlichen Zeitstempeln</li>
<li>statistics_numberOfCollisions - Anzahl der Kollisionen (letzter Tag/alle Tage)</li>
<li>status_connected - Status der Verbindung zwischen dem Automower und der Husqvarna Cloud.</li>
<li>status_statusTimestamp - Lokalzeit der letzten Änderung der Daten in der API</li>
<li>status_statusTimestampDiff - Zeitdifferenz zwischen den beiden letzten Änderungen im Inhalt der Daten aus der API</li>
<li>status_statusTimestamp - Lokalzeit des letzten Statusupdates in der API</li>
<li>status_statusTimestampDiff - Zeitdifferenz zwischen dem letzten und vorletzten Statusupdate.</li>
<li>system_name - Name des Automowers</li>
</ul>
</ul>

View File

@ -152,7 +152,7 @@ mowingPathDisplayStart=""
mowingPathLineColor="#ff0000"
mowingPathLineDash="6,2"
mowingPathLineWidth="1"
mowingPathDotWidth="4"
mowingPathDotWidth="2"
mowingPathUseDots=""';
my $mapZonesTpl = '{
@ -169,10 +169,12 @@ my $mapZonesTpl = '{
%$hash = (%$hash,
helper => {
passObj => FHEM::Core::Authentication::Passwords->new($type),
interval => 420,
interval_auth => 86345,
interval => 840,
interval_ws => 7110,
interval_ping => 60,
retry_interval_apiauth => 840,
retry_interval_getmower => 840,
midnightCycle => 1,
client_id => $client_id,
grant_type => 'client_credentials',
mowerNumber => $mowerNumber,
@ -186,8 +188,6 @@ my $mapZonesTpl = '{
imageWidthHeight => '350 650',
mapdesign => $mapAttr,
detailFnFirst => 0,
detailFnNewPos => 0,
detailFnAttrMaxPos => 5000,
mapZonesTpl => $mapZonesTpl,
posMinMax => "-180 90\n180 -90",
newdatasets => 0,
@ -289,6 +289,7 @@ my $mapZonesTpl = '{
AddExtension( $name, \&GetMap, "$type/$name/map" );
# AddExtension( $name, \&GetJson, "$type/$name/json" );
if ( $::init_done ) {
@ -379,12 +380,14 @@ sub Rename {
sub Get {
my ($hash,@val) = @_;
my $type = $hash->{TYPE};
return "$type $hash->{NAME} Get: needs at least one argument" if ( @val < 2 );
my ($name,$setName,$setVal,$setVal2,$setVal3) = @val;
my $name = $hash->{NAME};
my $iam = "$type $name Get:";
return "$iam needs at least one argument" if ( @val < 2 );
return "$iam disabled" if ( IsDisabled( $name ) );
my ($pname,$setName,$setVal,$setVal2,$setVal3) = @val;
Log3 $name, 4, "$iam called with $setName " . ($setVal ? $setVal : "");
if ( $setName eq 'html' ) {
@ -487,26 +490,6 @@ sub FW_detailFn {
}
$propli = 'data-propertyLimitsPath="'.$propli.'"';
# MOWING PATH
my @pos = @{ $hash->{helper}{areapos} };
# my $posxy = $cslon . "," . $cslat . ",P";
my $posxy = '';
if ( @pos > 1 ) {
$posxy = int( ( $lonlo-$pos[ 0 ]{longitude} ) * $picx / $mapx ).",".int( ( $latlo-$pos[ 0 ]{latitude} ) * $picy / $mapy ).",".$pos[ 0 ]{act};
for ( my $i = 1; $i < ( @pos > 5000 ? 5000 : @pos ); $i++ ){
$posxy .= ",".int( ( $lonlo - $pos[ $i ]{longitude} ) * $picx / $mapx ).",".int( ( $latlo - $pos[ $i ]{latitude} ) * $picy / $mapy ).",".$pos[ $i ]{act};
}
}
$posxy = 'data-mowingPath="'.$posxy.'"';
my $ret = "";
$ret .= "<style>
.${type}_${name}_div{padding:0px !important;
@ -520,12 +503,12 @@ sub FW_detailFn {
.${type}_${name}_canvas_1{
position: absolute; left: 0; top: 0; z-index: 1;}
</style>";
$ret .= "<div id='${type}_${name}_div' class='${type}_${name}_div' $mapDesign $csdata $limi $propli $posxy >";
$ret .= "<div id='${type}_${name}_div' class='${type}_${name}_div' $mapDesign $csdata $limi $propli >";
$ret .= "<canvas id='${type}_${name}_canvas_0' class='${type}_${name}_canvas_0' width='$picx' height='$picy' ></canvas>";
$ret .= "<canvas id='${type}_${name}_canvas_1' class='${type}_${name}_canvas_1' width='$picx' height='$picy' ></canvas>";
$ret .= "</div>";
$hash->{helper}{detailFnFirst} = 1;
InternalTimer( gettimeofday() + 1.5, \&FW_detailFn_Update, $hash, 0 );
InternalTimer( gettimeofday() + 2, \&FW_detailFn_Update, $hash, 0 );
return $ret;
@ -563,12 +546,11 @@ sub FW_detailFn_Update {
# MOWING PATH
my $posxy = '';
if ( @pos > 0 && $hash->{helper}{detailFnNewPos}) {
if ( @pos > 0 ) {
$posxy = int( ( $lonlo-$pos[ 0 ]{longitude} ) * $picx / $mapx ).",".int( ( $latlo-$pos[ 0 ]{latitude} ) * $picy / $mapy ).",'".$pos[ 0 ]{act}."'";
my $imax = ( @pos > 5000 ? @pos - 5000 + $hash->{helper}{detailFnNewPos} : $hash->{helper}{detailFnNewPos} );
for ( my $i = 1; $i < $imax; $i++ ){
for ( my $i = 1; $i < @pos; $i++ ){
$posxy .= ",".int( ( $lonlo - $pos[ $i ]{longitude} ) * $picx / $mapx ).",".int( ( $latlo - $pos[ $i ]{latitude} ) * $picy / $mapy ).",'".$pos[ $i ]{act}."'";
@ -615,22 +597,8 @@ sub APIAuth {
my $name = $hash->{NAME};
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,'device_state','disabled',1) if ( ReadingsVal( $name, 'device_state', '' ) ne 'disabled' );
RemoveInternalTimer( $hash, \&wsReopen );
RemoveInternalTimer( $hash, \&wsKeepAlive );
DevIo_CloseDev( $hash ) if ( DevIo_IsOpen( $hash ) );
RemoveInternalTimer( $hash, \&APIAuth );
InternalTimer( gettimeofday() + $interval, \&APIAuth, $hash, 0 );
return undef;
}
return if( IsDisabled( $name ) );
if ( !$update && $::init_done ) {
@ -665,7 +633,7 @@ sub APIAuth {
} else {
RemoveInternalTimer( $hash, \&APIAuth );
InternalTimer( gettimeofday() + 10, \&APIAuth, $hash, 0 );
InternalTimer( gettimeofday() + 15, \&APIAuth, $hash, 0 );
}
return undef;
@ -678,7 +646,6 @@ sub APIAuthResponse {
my $name = $hash->{NAME};
my $type = $hash->{TYPE};
my $statuscode = $param->{code} // '';
my $interval = $hash->{helper}{interval};
my $iam = "$type $name APIAuthResponse:";
Log3 $name, 1, "\ndebug $iam \n\$statuscode [$statuscode]\n\$err [$err],\n \$data [$data] \n\$param->url $param->{url}" if ( AttrVal($name, 'debug', '') );
@ -724,7 +691,8 @@ sub APIAuthResponse {
readingsBulkUpdateIfChanged($hash,'mower_commandStatus', 'cleared');
readingsEndUpdate($hash, 1);
getMower( $hash );
RemoveInternalTimer( $hash, \&getMower );
InternalTimer( gettimeofday() + 1.5, \&getMower, $hash, 0 );
return undef;
}
@ -736,7 +704,8 @@ sub APIAuthResponse {
}
RemoveInternalTimer( $hash, \&APIAuth );
InternalTimer( gettimeofday() + $interval, \&APIAuth, $hash, 0 );
InternalTimer( gettimeofday() + $hash->{helper}{retry_interval_apiauth}, \&APIAuth, $hash, 0 );
Log3 $name, 1, "$iam failed retry in $hash->{helper}{retry_interval_apiauth} seconds.";
return undef;
}
@ -785,7 +754,7 @@ sub getMowerResponse {
my $iam = "$type $name getMowerResponse:";
my $mowerNumber = $hash->{helper}{mowerNumber};
Log3 $name, 1, "\ndebug $iam \n\$statuscode [$statuscode]\n\$err [$err],\n \$data [$data] \n\$param->url $param->{url}" if ( AttrVal($name, 'debug', '') );
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) {
@ -820,20 +789,20 @@ sub getMowerResponse {
}
Log3 $name, 5, "$iam found $foundMower ";
if ( defined ($hash->{helper}{mower}{id}) ) { # update dataset
if ( defined ($hash->{helper}{mower}{id}) && $hash->{helper}{midnightCycle} ) { # update dataset
$hash->{helper}{mowerold}{attributes}{metadata}{statusTimestamp} = $hash->{helper}{mower}{attributes}{metadata}{statusTimestamp};
$hash->{helper}{mowerold}{attributes}{mower}{activity} = $hash->{helper}{mower}{attributes}{mower}{activity};
$hash->{helper}{mowerold}{attributes}{statistics}{numberOfCollisions} = $hash->{helper}{mower}{attributes}{statistics}{numberOfCollisions};
} else { # first data set
} elsif ( !defined ($hash->{helper}{mower}{id}) ) { # first data set
$hash->{helper}{mowerold}{attributes}{metadata}{statusTimestamp} = $hash->{helper}{mowers}[$mowerNumber]{attributes}{metadata}{statusTimestamp};
$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};
if ( AttrVal( $name, 'mapImageCoordinatesToRegister', '' ) eq '' ) {
::FHEM::Devices::AMConnect::Common::posMinMax( $hash, $hash->{helper}{mowers}[$mowerNumber]{attributes}{positions} );
posMinMax( $hash, $hash->{helper}{mowers}[$mowerNumber]{attributes}{positions} );
}
}
@ -845,43 +814,54 @@ sub getMowerResponse {
$hash->{helper}{storediff} = $hash->{helper}{mower}{attributes}{metadata}{statusTimestamp} - $hash->{helper}{mowerold}{attributes}{metadata}{statusTimestamp};
::FHEM::Devices::AMConnect::Common::calculateStatistics($hash);
calculateStatistics($hash) if ( $hash->{helper}{midnightCycle} );
# Update readings
readingsBeginUpdate($hash);
readingsBulkUpdateIfChanged($hash, 'api_MowerFound', $foundMower );
::FHEM::Devices::AMConnect::Common::fillReadings( $hash );
fillReadings( $hash );
readingsBulkUpdate($hash, 'device_state', 'connected' );
readingsEndUpdate($hash, 1);
readingsSingleUpdate($hash, 'device_state', 'connected', 1 );
# schedule new access token
RemoveInternalTimer( $hash, \&APIAuth );
InternalTimer( ReadingsVal($name, '.expires', 600)-37, \&APIAuth, $hash, 0 );
RemoveInternalTimer( $hash, \&getNewAccessToken );
InternalTimer( ReadingsVal($name, '.expires', 600)-37, \&getNewAccessToken, $hash, 0 );
# Websocket initialisieren, schedule ping, reopen
RemoveInternalTimer( $hash, \&wsReopen );
InternalTimer( gettimeofday() + 1.5, \&wsReopen, $hash, 0 );
$hash->{helper}{midnightCycle} = 0;
return undef;
}
}
} else {
readingsSingleUpdate( $hash, 'device_state', "error statuscode $statuscode", 1 );
Log3 $name, 1, "\ndebug $iam \n\$statuscode [$statuscode]\n\$err [$err],\n \$data [$data] \n\$param->url $param->{url}";
Log3 $name, 1, "$iam \$statuscode [$statuscode]\n\$err [$err],\n \$data [$data] \n\$param->url $param->{url}";
}
RemoveInternalTimer( $hash, \&APIAuth );
InternalTimer( gettimeofday() + $hash->{helper}{interval}, \&APIAuth, $hash, 0 );
InternalTimer( gettimeofday() + $hash->{helper}{retry_interval_getmower}, \&APIAuth, $hash, 0 );
Log3 $name, 1, "$iam failed retry in $hash->{helper}{retry_interval_getmower} seconds.";
return undef;
}
#########################
sub getNewAccessToken {
my ($hash) = @_;
$hash->{helper}{midnightCycle} = 1;
APIAuth( $hash );
}
##############################################################
#
# SEND COMMAND
@ -895,7 +875,7 @@ sub CMD {
my $iam = "$type $name CMD:";
$hash->{helper}{mower_commandSend} = $cmd[ 0 ] . ' ' . ( $cmd[ 1 ] ? $cmd[ 1 ] : '' );
if ( IsDisabled($name) ) {
if ( IsDisabled( $name ) ) {
Log3 $name, 3, "$iam disabled";
return undef
@ -1014,15 +994,17 @@ sub CMDResponse {
sub Set {
my ($hash,@val) = @_;
my $type = $hash->{TYPE};
return "$type $hash->{NAME} Set: needs at least one argument" if ( @val < 2 );
my ($name,$setName,$setVal,$setVal2,$setVal3) = @val;
my $name = $hash->{NAME};
my $iam = "$type $name Set:";
return "$iam: needs at least one argument" if ( @val < 2 );
return "Unknown argument, $iam is disabled, choose one of none:noArg" if ( IsDisabled( $name ) );
my ($pname,$setName,$setVal,$setVal2,$setVal3) = @val;
Log3 $name, 4, "$iam called with $setName " . ($setVal ? $setVal : "") if ($setName !~ /^(\?|client_secret)$/);
if ( !IsDisabled($name) && $setName eq 'getUpdate' ) {
if ( !$hash->{helper}{midnightCycle} && $setName eq 'getUpdate' ) {
RemoveInternalTimer($hash, \&APIAuth);
APIAuth($hash);
@ -1090,7 +1072,7 @@ sub Set {
return undef;
}
} elsif ( !IsDisabled($name) && $setName eq 'getNewAccessToken' ) {
} elsif ( $setName eq 'getNewAccessToken' ) {
readingsBeginUpdate($hash);
readingsBulkUpdateIfChanged( $hash, '.access_token', '', 0 );
@ -1126,10 +1108,16 @@ sub Attr {
if( $attrName eq "disable" ) {
if( $cmd eq "set" and $attrVal eq "1" ) {
readingsSingleUpdate( $hash,'device_state','disabled',1);
RemoveInternalTimer( $hash );
DevIo_CloseDev( $hash ) if ( DevIo_IsOpen( $hash ) );
DevIo_setStates( $hash, "closed" );
Log3 $name, 3, "$iam $cmd $attrName disabled";
} elsif( $cmd eq "del" or $cmd eq 'set' and !$attrVal ) {
RemoveInternalTimer( $hash, \&APIAuth);
InternalTimer( gettimeofday() + 1, \&APIAuth, $hash, 0 );
Log3 $name, 3, "$iam $cmd $attrName enabled";
}
@ -1183,8 +1171,8 @@ sub Attr {
##########
} elsif ( $attrName eq 'numberOfWayPointsToDisplay' ) {
my $icurr = @{$hash->{helper}{areapos}};
if( $cmd eq "set" && $attrVal =~ /\d+/ && $attrVal > $hash->{helper}{MOWING}{maxLengthDefault}) {
my $icurr = scalar @{$hash->{helper}{areapos}};
if( $cmd eq "set" && $attrVal =~ /\d+/ && $attrVal > 100 ) {
# reduce array
$hash->{helper}{MOWING}{maxLength} = $attrVal;
@ -1648,7 +1636,7 @@ sub RemoveExtension {
sub GetMap() {
my ($request) = @_;
if ( $request =~ /^\/(AutomowerConnectDevice|AutomowerConnect)\/(\w+)\/map/ ) {
if ( $request =~ /^\/(AutomowerConnect)\/(\w+)\/map/ ) {
my $type = $1;
my $name = $2;
@ -1660,10 +1648,28 @@ 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 for webhook $request" );
}
#########################
# sub GetJson() {
# my ($request) = @_;
# if ( $request =~ /^\/(AutomowerConnect)\/(\w+)\/json/ ) {
# my $type = $1;
# my $name = $2;
# my $hash = $::defs{$name};
# my $jsonMime = "application/json";
# my $jsonData = eval { encode_json ( $hash->{helper}{areapos} ) };
# return ( $jsonMime, $jsonData );
# }
# return ( "text/plain; charset=utf-8", "No AutomowerConnect device for webhook $request" );
# }
#########################
sub readMap {
my ($hash) = @_;
@ -1737,57 +1743,56 @@ sub fillReadings {
my ( $hash ) = @_;
my $name = $hash->{NAME};
readingsBulkUpdateIfChanged($hash, '.mower_id', $hash->{helper}{mower}{id}, 0 );
readingsBulkUpdateIfChanged($hash, "batteryPercent", $hash->{helper}{mower}{attributes}{battery}{batteryPercent} );
readingsBulkUpdateIfChanged( $hash, '.mower_id', $hash->{helper}{mower}{id}, 0 );
readingsBulkUpdateIfChanged( $hash, "batteryPercent", $hash->{helper}{mower}{attributes}{battery}{batteryPercent} );
my $pref = 'mower';
readingsBulkUpdateIfChanged($hash, $pref.'_mode', $hash->{helper}{mower}{attributes}{$pref}{mode} );
readingsBulkUpdateIfChanged($hash, $pref.'_activity', $hash->{helper}{mower}{attributes}{$pref}{activity} );
readingsBulkUpdateIfChanged($hash, $pref.'_state', $hash->{helper}{mower}{attributes}{$pref}{state} );
readingsBulkUpdateIfChanged($hash, $pref.'_commandStatus', 'cleared' );
readingsBulkUpdateIfChanged($hash, $pref.'_commandSend', ( $hash->{helper}{mower_commandSend} ? $hash->{helper}{mower_commandSend} : '-' ) );
readingsBulkUpdateIfChanged($hash, $pref.'_wsEvent', $hash->{helper}{wsResult}{type} );
readingsBulkUpdateIfChanged( $hash, $pref.'_mode', $hash->{helper}{mower}{attributes}{$pref}{mode} );
readingsBulkUpdateIfChanged( $hash, $pref.'_activity', $hash->{helper}{mower}{attributes}{$pref}{activity} );
readingsBulkUpdateIfChanged( $hash, $pref.'_state', $hash->{helper}{mower}{attributes}{$pref}{state} );
readingsBulkUpdateIfChanged( $hash, $pref.'_commandStatus', 'cleared' );
readingsBulkUpdateIfChanged( $hash, $pref.'_commandSend', ( $hash->{helper}{mower_commandSend} ? $hash->{helper}{mower_commandSend} : '-' ) );
if ( AttrVal($name, 'mapZones', 0) && $hash->{helper}{currentZone} && $hash->{helper}{mapZones}{$hash->{helper}{currentZone}}{curZoneCnt} ) {
my $curZon = $hash->{helper}{currentZone};
my $curZonCnt = $hash->{helper}{mapZones}{$curZon}{curZoneCnt};
readingsBulkUpdateIfChanged($hash, $pref.'_currentZone', $curZon . '(' . $curZonCnt . '/' . $hash->{helper}{newzonedatasets} . ')' );
readingsBulkUpdateIfChanged( $hash, $pref.'_currentZone', $curZon . '(' . $curZonCnt . '/' . $hash->{helper}{newzonedatasets} . ')' );
}
my $tstamp = $hash->{helper}{mower}{attributes}{$pref}{errorCodeTimestamp};
my $timestamp = FmtDateTimeGMT($tstamp/1000);
readingsBulkUpdateIfChanged($hash, $pref."_errorCodeTimestamp", $tstamp ? $timestamp : '-' );
my $timestamp = FmtDateTimeGMT( $tstamp/1000 );
readingsBulkUpdateIfChanged( $hash, $pref."_errorCodeTimestamp", $tstamp ? $timestamp : '-' );
my $errc = $hash->{helper}{mower}{attributes}{$pref}{errorCode};
readingsBulkUpdateIfChanged($hash, $pref.'_errorCode', $tstamp ? $errc : '-');
readingsBulkUpdateIfChanged( $hash, $pref.'_errorCode', $tstamp ? $errc : '-');
my $errd = $errortable->{$errc};
readingsBulkUpdateIfChanged($hash, $pref.'_errorDescription', $tstamp ? $errd : '-');
readingsBulkUpdateIfChanged( $hash, $pref.'_errorDescription', $tstamp ? $errd : '-');
$pref = 'system';
readingsBulkUpdateIfChanged($hash, $pref."_name", $hash->{helper}{mower}{attributes}{$pref}{name} );
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 );
$pref = 'planner';
readingsBulkUpdateIfChanged($hash, "planner_restrictedReason", $hash->{helper}{mower}{attributes}{$pref}{restrictedReason} );
readingsBulkUpdateIfChanged($hash, "planner_overrideAction", $hash->{helper}{mower}{attributes}{$pref}{override}{action} );
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 = FmtDateTimeGMT( $tstamp/1000 );
readingsBulkUpdateIfChanged($hash, "planner_nextStart", $tstamp ? $timestamp : '-' );
$pref = 'statistics';
readingsBulkUpdateIfChanged($hash, $pref."_numberOfCollisions", '(' . $hash->{helper}{statistics}{lastDayCollisions} . '/' . $hash->{helper}{mower}{attributes}{$pref}{numberOfCollisions} . ')' );
readingsBulkUpdateIfChanged($hash, $pref."_newGeoDataSets", $hash->{helper}{newdatasets} );
readingsBulkUpdateIfChanged( $hash, $pref."_numberOfCollisions", '(' . $hash->{helper}{statistics}{lastDayCollisions} . '/' . $hash->{helper}{mower}{attributes}{$pref}{numberOfCollisions} . ')' );
readingsBulkUpdateIfChanged( $hash, $pref."_newGeoDataSets", $hash->{helper}{newdatasets} );
$pref = 'settings';
readingsBulkUpdateIfChanged($hash, $pref."_headlight", $hash->{helper}{mower}{attributes}{$pref}{headlight}{mode} );
readingsBulkUpdateIfChanged($hash, $pref."_cuttingHeight", $hash->{helper}{mower}{attributes}{$pref}{cuttingHeight} );
readingsBulkUpdateIfChanged( $hash, $pref."_headlight", $hash->{helper}{mower}{attributes}{$pref}{headlight}{mode} );
readingsBulkUpdateIfChanged( $hash, $pref."_cuttingHeight", $hash->{helper}{mower}{attributes}{$pref}{cuttingHeight} );
$pref = 'status';
my $connected = $hash->{helper}{mower}{attributes}{metadata}{connected};
readingsBulkUpdateIfChanged($hash, $pref."_connected", ( $connected ? "CONNECTED($connected)" : "OFFLINE($connected)") );
readingsBulkUpdateIfChanged( $hash, $pref."_connected", ( $connected ? "CONNECTED($connected)" : "OFFLINE($connected)") );
readingsBulkUpdateIfChanged($hash, $pref."_Timestamp", FmtDateTime( $hash->{helper}{mower}{attributes}{metadata}{statusTimestamp}/1000 )); # verschieben nach websocket fill
readingsBulkUpdateIfChanged($hash, $pref."_TimestampDiff", $hash->{helper}{storediff}/1000 );# verschieben nach websocket fill
readingsBulkUpdateIfChanged( $hash, $pref."_Timestamp", FmtDateTime( $hash->{helper}{mower}{attributes}{metadata}{statusTimestamp}/1000 ) ); # verschieben nach websocket fill
readingsBulkUpdateIfChanged( $hash, $pref."_TimestampDiff", $hash->{helper}{storediff}/1000 );# verschieben nach websocket fill
return undef;
}
@ -2207,8 +2212,8 @@ sub wsInit {
$hash->{First_Read} = 1;
RemoveInternalTimer( $hash, \&wsReopen );
RemoveInternalTimer( $hash, \&wsKeepAlive );
InternalTimer( gettimeofday() + 7110, \&wsReopen, $hash, 0 );
InternalTimer( gettimeofday() + 60, \&wsKeepAlive, $hash, 0 );
InternalTimer( gettimeofday() + $hash->{helper}{interval_ws}, \&wsReopen, $hash, 0 );
InternalTimer( gettimeofday() + $hash->{helper}{interval_ping}, \&wsKeepAlive, $hash, 0 );
return undef;
}
@ -2279,7 +2284,6 @@ sub wsRead {
$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}{detailFnNewPos} = 0;
isErrorThanPrepare( $hash );
resetLastErrorIfCorrected( $hash );
@ -2307,7 +2311,6 @@ sub wsRead {
}
$hash->{helper}{detailFnNewPos} = scalar @{ $result->{attributes}{positions} };
FW_detailFn_Update ($hash);
}
@ -2324,6 +2327,7 @@ sub wsRead {
readingsBeginUpdate($hash);
fillReadings( $hash );
readingsBulkUpdate( $hash, 'mower_wsEvent', $hash->{helper}{wsResult}{type} );
readingsEndUpdate($hash, 1);

View File

@ -4,7 +4,7 @@ FW_version["automowerconnect.js"] = "$Id$";
function AutomowerConnectShowError( ctx, div, dev, picx, picy, errdesc, erray ) {
// ERROR BANNER
ctx.beginPath();
ctx.fillStyle = div.getAttribute( 'data-errorBackgroundColor' );;
ctx.fillStyle = div.getAttribute( 'data-errorBackgroundColor' );
ctx.font = div.getAttribute( 'data-errorFont' );
var m = ctx.measureText( errdesc[ 1 ] + ', ' + dev + ': ' + errdesc[ 0 ] ).width > picy - 6;
@ -273,6 +273,9 @@ function AutomowerConnectTor ( x0, y0, x1, y1 ) {
}
//AutomowerConnectUpdateDetail (<devicename>, <type>, <detailfnfirst>, <imagesize x>, <imagesize y>,<scale x>, <error description>, <path array>, <error array>)
function AutomowerConnectUpdateDetail (dev, type, detailfnfirst, picx, picy, scalx, errdesc, pos, erray) {
//~ $.getJSON('./fhem/AutomowerConnect/am430x/json', function(data) {
//~ log(data[0].longitude+' '+data[0].latitude+' '+data[0].act);
//~ });
const colorat = {
"U" : "otherActivityPath",
"N" : "errorPath",
@ -321,23 +324,6 @@ function AutomowerConnectUpdateDetail (dev, type, detailfnfirst, picx, picy, sca
const ctx = canvas.getContext( '2d' );
ctx.clearRect( 0, 0, canvas.width, canvas.height );
const attrpath = div.getAttribute( 'data-mowingPath' );
var oldpath = "";
if ( attrpath ) {
oldpath = attrpath.split( "," );
pos.push( ...oldpath );
while ( pos.length > 5000 ) {
pos.pop();
}
}
div.setAttribute( 'data-mowingPath', pos.join( "," ) );
if ( pos.length > 3 ) {
@ -359,7 +345,7 @@ function AutomowerConnectUpdateDetail (dev, type, detailfnfirst, picx, picy, sca
ctx.lineWidth=3;
ctx.strokeStyle = 'white';
ctx.fillStyle= 'black';
ctx.arc(parseInt(pos[pos.length-3]), parseInt(pos[pos.length-2]), 4, 0, 2 * Math.PI, false);
ctx.arc( parseInt( pos[ pos.length-3 ] ), parseInt( pos[ pos.length-2 ] ), 4, 0, 2 * Math.PI, false );
ctx.fill();
ctx.stroke();
}