From abb139d4135aa84ae90595e37bcd3212c55cedaa Mon Sep 17 00:00:00 2001 From: Ellert <> Date: Fri, 25 Oct 2024 17:02:10 +0000 Subject: [PATCH] 74_AutomowerConnect: new statistics values upTime, downTime; new setter getMessages, resetCuttingBladeUsageTime; added messages to listErrorStack. git-svn-id: https://svn.fhem.de/fhem/trunk@29292 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/CHANGED | 3 + fhem/FHEM/74_AutomowerConnect.pm | 26 +++- fhem/lib/FHEM/Devices/AMConnect/Common.pm | 151 +++++++++++++++++++--- 3 files changed, 161 insertions(+), 19 deletions(-) diff --git a/fhem/CHANGED b/fhem/CHANGED index 9bde4ba0e..0e318d5f5 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +1,8 @@ # 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: 74_AutomowerConnect: new statistics values upTime, downTime + new setter getMessages, resetCuttingBladeUsageTime + added messages to listErrorStack - bugfix: 36_Shelly: ShellyPro3EM: div by zero when attr Periods is not set - bugfix: 36_Shelly: diverse Korrekturen/Ergänzungen/siehe Forum - bugfix: 76_SolarForecast: minor fix in Flowgraphic diff --git a/fhem/FHEM/74_AutomowerConnect.pm b/fhem/FHEM/74_AutomowerConnect.pm index 4b5f0c07a..b8ef7fe71 100644 --- a/fhem/FHEM/74_AutomowerConnect.pm +++ b/fhem/FHEM/74_AutomowerConnect.pm @@ -194,7 +194,7 @@ __END__
  • dateTime
    set <name> dateTime <timestamp / s>
    - Syncronize the mower time to timestamp. The default (empty Input field) timestamp is for local time of the machine the mower is defined.
  • + Syncronize the mower time to timestamp. The default (empty Input field) timestamp is for local time of the machine the mower is defined, see also mowerAutoSyncTime.
  • confirmError
    set <name> confirmError
    @@ -218,6 +218,10 @@ __END__ set <name> cuttingHeight <1..9>
    Sets the cutting height. NOTE: Do not use for 550 EPOS and Ceora.
  • +
  • resetCuttingBladeUsageTime
    + set <name> resetCuttingBladeUsageTime
    + Resets the cutting blade usage time if the mower provides it.
  • +
  • cuttingHeightInWorkArea
    set <name> cuttingHeightInWorkArea <Id|name> <0..100>
    Testing: Sets the cutting height for Id or zone name from 0 to 100. Zone name must not include space and contain at least one alphabetic character.
  • @@ -234,6 +238,10 @@ __END__ set <name> getUpdate
    For debug purpose only. +

  • + get <name> getMessages
    + Gets messages from mower if supported, see also
    errorStack.
  • +
  • headlight
    set <name> headlight <ALWAYS_OFF|ALWAYS_ON|EVENIG_ONLY|EVENING_AND_NIGHT>
  • @@ -284,8 +292,9 @@ __END__
  • errorStack
    get <name> errorStack
    - Lists error stack.
  • -

    + Lists error stack and messages, see also getMessages. + +

    @@ -715,6 +724,10 @@ __END__ set <name> cuttingHeight <1..9>
    Setzt die Schnitthöhe. HINWEIS: Nicht für 550 EPOS und Ceora geeignet. +
  • resetCuttingBladeUsageTime
    + set <name> resetCuttingBladeUsageTime
    + Setzt die Benutzungszeit der Messer zurück, falls der Mäher es unterstützt.
  • +
  • cuttingHeightInWorkArea
    set <name> cuttingHeightInWorkArea <Id|name> <0..100>
    Testing: Setzt die Schnitthöhe für Id oder Zonennamen von 0 bis 100. Der Zonenname darf keine Leerzeichen beinhalten und muss mindestens einen Buchstaben enthalten.
  • @@ -741,6 +754,10 @@ __END__ set <name> getUpdate
    Nur zur Fehlerbehebung. +
  • getMessages
    + get <name> getMessages
    + Läd die im Mäher gespeicherten Meldungen, sofern unterstützt, siehe auch errorStack.
  • +
  • headlight
    set <name> headlight <ALWAYS_OFF|ALWAYS_ON|EVENIG_ONLY|EVENING_AND_NIGHT>
    Setzt den Scheinwerfermode
  • @@ -791,7 +808,8 @@ __END__
  • errorStack
    get <name> errorStack
    - Listet die gespeicherten Fehler auf.
  • + Listet die gespeicherten Fehler und die geladenen Meldungen auf, siehe auch getMessages. +

    diff --git a/fhem/lib/FHEM/Devices/AMConnect/Common.pm b/fhem/lib/FHEM/Devices/AMConnect/Common.pm index 2f647a5b8..9ab3e8774 100644 --- a/fhem/lib/FHEM/Devices/AMConnect/Common.pm +++ b/fhem/lib/FHEM/Devices/AMConnect/Common.pm @@ -981,8 +981,7 @@ sub getMowerResponse { ######################### sub getMowerWs { - - my ( $hash ) = @_; + my ( $hash, $endpoint ) = @_; my $name = $hash->{NAME}; my $type = $hash->{TYPE}; my $iam = "$type $name getMowerWs:"; @@ -990,24 +989,100 @@ sub getMowerWs { my $provider = ReadingsVal($name,".provider",""); my $client_id = $hash->{helper}->{client_id}; my $timeout = AttrVal( $name, 'timeoutGetMower', $hash->{helper}->{timeout_getmower} ); + my $callback = \&getMowerResponseWs; 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} ); + if ( $endpoint eq 'messages') { $callback = \&getEndpointResponse } + ::HttpUtils_NonblockingGet( { - url => APIURL . '/mowers/' . $hash->{helper}{mower}{id}, + url => APIURL . '/mowers/' . $hash->{helper}{mower}{id} . ($endpoint ? '/' . $endpoint : ''), timeout => $timeout, hash => $hash, method => "GET", header => $header, - callback => \&getMowerResponseWs, + callback => $callback, + endpoint => $endpoint, t_begin => scalar gettimeofday() } ); return undef; } +######################### +sub getEndpointResponse { + + my ( $param, $err, $data ) = @_; + my $hash = $param->{hash}; + my $name = $hash->{NAME}; + my $type = $hash->{TYPE}; + my $statuscode = $param->{code} // ''; + my $endpoint = $param->{endpoint} // ''; + my $iam = "$type $name getEndpointResponse:"; + + Log3 $name, 1, "$iam response time ". sprintf( "%.2f", ( gettimeofday() - $param->{t_begin} ) ) . ' s' if ( $param->{timeout} == 60 ); + Log3 $name, 4, "$iam response calling \$endpoint >$endpoint<, \$statuscode >$statuscode<, \$err >$err<, \$param->url $param->{url} \n \$data >$data<"; + + if ( !$err && $statuscode == 200 && $data) { + + if ( $data eq '' ) { + + Log3 $name, 2, "$iam no mower data present"; + + } else { + + my $result = eval { JSON::XS->new->allow_nonref(0)->utf8( not $unicodeEncoding )->decode( $data ) }; + + if ( $@ ) { + + Log3( $name, 2, "$iam - JSON error while request: $@"); + + } elsif ( $endpoint eq 'messages' ) { + + if ( defined $result->{data}{attributes}{messages} ) { + + $hash->{helper}{endpoints}{$endpoint} = $result->{data}; + $hash->{helper}->{mower_commandStatus} = 'OK - messages'; + $hash->{helper}->{mower_commandSend} = 'messages'; + + } else { + + $hash->{helper}->{mower_commandStatus} = 'OK - no messages recieved'; + $hash->{helper}->{mower_commandSend} = 'messages'; + + } + + } + + readingsBeginUpdate($hash); + + readingsBulkUpdateIfChanged( $hash, 'mower_commandStatus', $hash->{helper}{mower_commandStatus}, 1 ); + readingsBulkUpdateIfChanged( $hash, 'mower_commandSend', $hash->{helper}{mower_commandSend}, 1 ); + + readingsEndUpdate($hash, 1); + + } + + } else { + + readingsBeginUpdate($hash); + + readingsBulkUpdateIfChanged( $hash, 'mower_commandStatus', "ERROR statuscode $statuscode", 1 ); + readingsBulkUpdateIfChanged( $hash, 'mower_commandSend', $hash->{helper}{mower_commandSend}, 1 ); + + readingsEndUpdate($hash, 1); + + Log3 $name, 1, "$iam \$statuscode >$statuscode<, \$err >$err<,\n \$data [$data] \n\$param->url $param->{url}"; + DoTrigger($name, "MOWERAPI ERROR"); + + } + + return undef; + +} + ######################### sub getMowerResponseWs { @@ -1144,7 +1219,7 @@ sub Get { my $ret = listInternalData($hash); return $ret; - } elsif ( $setName eq 'MowerData' ) { + } elsif ( $setName eq 'MowerData' ) { my $ret = listMowerData($hash); return $ret; @@ -1313,11 +1388,25 @@ sub Set { return undef; ########## - } elsif ( ReadingsVal( $name, 'device_state', 'defined' ) !~ /defined|initialized|authentification|authenticated|update/ && $setName =~ /confirmError/ && AttrVal( $name, 'testing', '' ) ) { + } elsif ( ReadingsVal( $name, 'device_state', 'defined' ) !~ /defined|initialized|authentification|authenticated|update/ + && $setName =~ /confirmError/ && $hash->{helper}{mower}{attributes}{capabilities}{canConfirmError} && AttrVal( $name, 'testing', '' ) ) { CMD($hash,$setName); return undef; + ########## + } elsif ( ReadingsVal( $name, 'device_state', 'defined' ) !~ /defined|initialized|authentification|authenticated|update/ + && $setName =~ /resetCuttingBladeUsageTime/ && defined( $hash->{helper}{mower}{attributes}{statistics}{cuttingBladeUsageTime} ) ) { + + CMD($hash,$setName); + return undef; + + ########## + } elsif ( ReadingsVal( $name, 'device_state', 'defined' ) !~ /defined|initialized|authentification|authenticated|update/ && $setName =~ /getMessages/ ) { + + getMowerWs( $hash, 'messages' ); + return undef; + ########## } elsif ( ReadingsVal( $name, 'device_state', 'defined' ) !~ /defined|initialized|authentification|authenticated|update/ && $setName =~ /^(StartInWorkArea|cuttingHeightInWorkArea)$/ && $hash->{helper}{mower}{attributes}{capabilities}{workAreas} && AttrVal( $name, 'testing', '' ) ) { @@ -1355,7 +1444,7 @@ sub Set { } ########## - my $ret = " getNewAccessToken:noArg ParkUntilFurtherNotice:noArg ParkUntilNextSchedule:noArg Pause:noArg Start:selectnumbers,30,30,600,0,lin Park:selectnumbers,30,30,600,0,lin ResumeSchedule:noArg getUpdate:noArg client_secret dateTime "; + my $ret = " getNewAccessToken:noArg ParkUntilFurtherNotice:noArg ParkUntilNextSchedule:noArg Pause:noArg Start:selectnumbers,30,30,600,0,lin Park:selectnumbers,30,30,600,0,lin ResumeSchedule:noArg getUpdate:noArg client_secret dateTime getMessages:noArg "; $ret .= "mowerScheduleToAttribute:noArg sendScheduleFromAttributeToMower:noArg "; $ret .= "cuttingHeight:1,2,3,4,5,6,7,8,9 " if ( defined $hash->{helper}{mower}{attributes}{settings}{cuttingHeight} ); $ret .= "defaultDesignAttributesToAttribute:noArg mapZonesTemplateToAttribute:noArg chargingStationPositionToAttribute:noArg " if ( $hash->{helper}{mower}{attributes}{capabilities}{position} ); @@ -1380,7 +1469,8 @@ sub Set { } - $ret .= "confirmError:noArg " if ( AttrVal( $name, 'testing', '' ) ); + $ret .= "confirmError:noArg " if ( $hash->{helper}{mower}{attributes}{capabilities}{canConfirmError} && AttrVal( $name, 'testing', '' ) ); + $ret .= "resetCuttingBladeUsageTime " if ( defined( $hash->{helper}{mower}{attributes}{statistics}{cuttingBladeUsageTime} ) ); return "Unknown argument $setName, choose one of".$ret; } @@ -1435,6 +1525,7 @@ my $header = "Accept: application/vnd.api+json\r\nX-Api-Key: ".$client_id."\r\nA elsif ($cmd[0] eq "cuttingHeight") { $json = '{"data": {"type":"settings","attributes":{"'.$cmd[0].'": '.$cmd[1].'}}}'; $post = 'settings' } elsif ($cmd[0] eq "stayOutZone") { $json = '{"data": {"type":"stayOutZone","id":"'.$cmd[1].'","attributes":{"enable": '.$cmd[2].'}}}'; $post = 'stayOutZones/' . $cmd[1]; $method = 'PATCH' } elsif ($cmd[0] eq "confirmError") { $json = '{}'; $post = 'errors/confirm' } + elsif ($cmd[0] eq "resetCuttingBladeUsageTime") { $json = '{}'; $post = 'statistics/resetCuttingBladeUsageTime' } elsif ($cmd[0] eq "sendScheduleFromAttributeToMower" && AttrVal( $name, 'mowerSchedule', '')) { my $perl = eval { JSON::XS->new->decode (AttrVal( $name, 'mowerSchedule', '')) }; @@ -1490,6 +1581,7 @@ sub CMDResponse { if( !$err && $statuscode == 202 && $data ) { my $result = eval { JSON::XS->new->decode($data) }; + if ($@) { Log3( $name, 2, "$iam - JSON error while request: $@"); @@ -1497,6 +1589,7 @@ sub CMDResponse { } else { $hash->{helper}{CMDResponse} = $result; + if ($result->{data}) { Log3 $name, 5, $data; @@ -1533,6 +1626,8 @@ sub CMDResponse { readingsEndUpdate($hash, 1); Log3 $name, 2, "$iam \n\$statuscode >$statuscode<\n\$err >$err<,\n\$data >$data<\n\$param->{url} >$param->{url}<\n\$param->{data} >$param->{data}<"; + DoTrigger($name, "MOWERAPI ERROR"); + return undef; } @@ -2581,6 +2676,9 @@ sub listStatisticsData { $ret .= ' $hash->{helper}{mower}{attributes}{statistics}{totalDriveDistance}   ' . sprintf( "%.0f", $hash->{helper}{mower}{attributes}{statistics}{totalDriveDistance} / 1000 ) . '1 km '; $ret .= ' $hash->{helper}{mower}{attributes}{statistics}{totalRunningTime}   ' . sprintf( "%.0f", $hash->{helper}{mower}{attributes}{statistics}{totalRunningTime} / 3600 ) . '2 h '; $ret .= ' $hash->{helper}{mower}{attributes}{statistics}{totalSearchingTime}   ' . sprintf( "%.0f", $hash->{helper}{mower}{attributes}{statistics}{totalSearchingTime} / 3600 ) . ' h '; + $ret .= ' $hash->{helper}{mower}{attributes}{statistics}{cuttingBladeUsageTime}   ' . sprintf( "%.0f", $hash->{helper}{mower}{attributes}{statistics}{cuttingBladeUsageTime} / 3600 ) . ' h ' if ( defined $hash->{helper}{mower}{attributes}{statistics}{cuttingBladeUsageTime} ); + $ret .= ' $hash->{helper}{mower}{attributes}{statistics}{upTime}   ' . sprintf( "%.0f", $hash->{helper}{mower}{attributes}{statistics}{upTime} / 3600 ) . ' h ' if ( defined $hash->{helper}{mower}{attributes}{statistics}{upTime} ); + $ret .= ' $hash->{helper}{mower}{attributes}{statistics}{downTime}   ' . sprintf( "%.0f", $hash->{helper}{mower}{attributes}{statistics}{downTime} / 3600 ) . ' h ' if ( defined $hash->{helper}{mower}{attributes}{statistics}{downTime} ); my $prop = ''; for my $item ( @items ) { @@ -2718,13 +2816,13 @@ sub listErrorStack { my ( $hash ) = @_; my $name = $hash->{NAME}; my $cnt = 0; - my $ret = ''; + my $ret = ''; if ( $::init_done && defined( $hash->{helper}{mower}{type} ) && @{ $hash->{helper}{errorstack} } ) { - $ret .= ''; + $ret .= '
    '; $ret .= ''; - $ret .= ''; + $ret .= ''; for ( my $i = 0; $i < @{ $hash->{helper}{errorstack} }; $i++ ) { @@ -2733,15 +2831,38 @@ sub listErrorStack { } $ret .= '
    Last Errors
    Timestamp Description  Zone   Position
    Timestamp Description  Zone   Longitude / Latitude
    '; - $ret .= ''; - - return $ret; } else { - return '
    No error in stack.
    '; + $ret .= '
    No error in stack.
    '; } + + if ( $::init_done && defined ( $hash->{helper}{endpoints}{messages}{attributes}{messages} ) && ref $hash->{helper}{endpoints}{messages}{attributes}{messages} eq 'ARRAY' && @{ $hash->{helper}{endpoints}{messages}{attributes}{messages} } > 0 ) { + + + my @msg = @{ $hash->{helper}{endpoints}{messages}{attributes}{messages} }; + $ret .= ''; + $ret .= ''; + + $ret .= ''; + + + for ( my $i = 0; $i < @{ $hash->{helper}{endpoints}{messages}{attributes}{messages} }; $i++ ) { + + $ret .= ''; + + } + + } else { + + $ret .= '
    Last Messages
    Timestamp Description Longitude / Latitude
    ' . FmtDateTimeGMT( $msg[$i]{time} ) . ' ' . $msg[$i]{severity} . ' - ' . $errortable->{ $msg[$i]{code} } . ' ' . ( defined $msg[$i]{longitude} ? $msg[$i]{longitude} : '-' ) . ' / ' . ( defined $msg[$i]{latitude} ? $msg[$i]{latitude} : '-' ) . '
    No messages available.
    '; + + } + + $ret .= ''; + return $ret; + } #########################