diff --git a/fhem/CHANGED b/fhem/CHANGED index 27773dd8e..0f658958a 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +1,7 @@ # Add changes at the top of the list. Keep it in ASCII, and 80-char wide. # Do not insert empty lines here, update check depends on it. + - feature: AutomowerConnectFamily: set cutting height for user defined zones + automatically, code cleaning, some fixes - feature: 72_FRITZBOX: neue Readings: dect_NoRingTime - Ruhezeiten des DECT Telefons dect_NoRingWithNightSetting - Bei aktiver Klingelsperre diff --git a/fhem/FHEM/74_AutomowerConnect.pm b/fhem/FHEM/74_AutomowerConnect.pm index d43f93097..bc60eea90 100644 --- a/fhem/FHEM/74_AutomowerConnect.pm +++ b/fhem/FHEM/74_AutomowerConnect.pm @@ -44,6 +44,7 @@ BEGIN { qw( AttrVal CommandAttr + CommandDeleteReading FmtDateTime getKeyValue InternalTimer @@ -358,156 +359,18 @@ sub getMowerResponse { # Update readings readingsBeginUpdate($hash); - readingsBulkUpdateIfChanged($hash, "batteryPercent", $hash->{helper}{mower}{attributes}{battery}{batteryPercent} ); - readingsBulkUpdateIfChanged($hash, 'api_MowerFound', $foundMower ); - 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, 'api_MowerFound', $foundMower ); # host only + ::FHEM::Devices::AMConnect::Common::fillReadings( $hash ); - 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} . ')' ); - } - - my $tstamp = $hash->{helper}{mower}{attributes}{$pref}{errorCodeTimestamp}; - my $timestamp = ::FHEM::Devices::AMConnect::Common::FmtDateTimeGMT($tstamp/1000); - readingsBulkUpdateIfChanged($hash, $pref."_errorCodeTimestamp", $tstamp ? $timestamp : '-' ); - - my $errc = $hash->{helper}{mower}{attributes}{$pref}{errorCode}; - readingsBulkUpdateIfChanged($hash, $pref.'_errorCode', $tstamp ? $errc : '-'); - - my $errd = $::FHEM::Devices::AMConnect::Common::errortable->{$errc}; - readingsBulkUpdateIfChanged($hash, $pref.'_errorDescription', $tstamp ? $errd : '-'); - - $pref = 'system'; - readingsBulkUpdateIfChanged($hash, $pref."_name", $hash->{helper}{mower}{attributes}{$pref}{name} ); - my $model = $hash->{helper}{mower}{attributes}{$pref}{model}; - $model =~ s/AUTOMOWER./AM/; - # $hash->{MODEL} = '' if (!defined $hash->{MODEL}); - $hash->{MODEL} = $model if ( $model && $hash->{MODEL} ne $model ); - $pref = 'planner'; - readingsBulkUpdateIfChanged($hash, "planner_restrictedReason", $hash->{helper}{mower}{attributes}{$pref}{restrictedReason} ); - readingsBulkUpdateIfChanged($hash, "planner_overrideAction", $hash->{helper}{mower}{attributes}{$pref}{override}{action} ); - - $tstamp = $hash->{helper}{mower}{attributes}{$pref}{nextStartTimestamp}; - $timestamp = ::FHEM::Devices::AMConnect::Common::FmtDateTimeGMT($tstamp/1000); - readingsBulkUpdateIfChanged($hash, "planner_nextStart", $tstamp ? $timestamp : '-' ); - - $pref = 'statistics'; - readingsBulkUpdateIfChanged($hash, $pref."_numberOfCollisions", $hash->{helper}->{mower}{attributes}{$pref}{numberOfCollisions} ); - readingsBulkUpdateIfChanged($hash, $pref."_newGeoDataSets", $hash->{helper}{newdatasets} ); - $pref = 'settings'; - 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."_Timestamp", FmtDateTime( $hash->{helper}{mower}{attributes}{metadata}{statusTimestamp}/1000 )); - readingsBulkUpdateIfChanged($hash, $pref."_TimestampDiff", $storediff/1000 ); - readingsBulkUpdateIfChanged($hash, $pref."_TimestampOld", FmtDateTime( $hash->{helper}{mowerold}{attributes}{metadata}{statusTimestamp}/1000 )); readingsEndUpdate($hash, 1); - my @time = localtime(); - my $secs = ( $time[2] * 3600 ) + ( $time[1] * 60 ) + $time[0]; - my $interval = $hash->{helper}->{interval}; - # do at midnight - if ( $secs <= $interval ) { + ::FHEM::Devices::AMConnect::Common::calculateStatistics( $hash ); - $hash->{helper}{statistics}{lastDayTrack} = $hash->{helper}{statistics}{currentDayTrack}; - $hash->{helper}{statistics}{lastDayArea} = $hash->{helper}{statistics}{currentDayArea}; - $hash->{helper}{statistics}{currentWeekTrack} += $hash->{helper}{statistics}{currentDayTrack}; - $hash->{helper}{statistics}{currentWeekArea} += $hash->{helper}{statistics}{currentDayArea}; - $hash->{helper}{statistics}{currentDayTrack} = 0; - $hash->{helper}{statistics}{currentDayArea} = 0; - - if ( AttrVal($name, 'mapZones', 0) && defined( $hash->{helper}{mapZones} ) ) { - - my @zonekeys = sort (keys %{$hash->{helper}{mapZones}}); - my $sumLastDayCnt=0; - my $sumCurrentWeekCnt=0; - my $sumLastDayArea=0; - my $sumCurrentWeekArea=0; - map { - $hash->{helper}{mapZones}{$_}{lastDayCnt} = $hash->{helper}{mapZones}{$_}{zoneCnt}; - $sumLastDayCnt += $hash->{helper}{mapZones}{$_}{lastDayCnt}; - $hash->{helper}{mapZones}{$_}{currentWeekCnt} += $hash->{helper}{mapZones}{$_}{lastDayCnt}; - $sumCurrentWeekCnt += $hash->{helper}{mapZones}{$_}{currentWeekCnt}; - $hash->{helper}{mapZones}{$_}{zoneCnt} = 0; - - $hash->{helper}{mapZones}{$_}{lastDayArea} = $hash->{helper}{mapZones}{$_}{zoneLength}; - $sumLastDayArea += $hash->{helper}{mapZones}{$_}{lastDayArea}; - $hash->{helper}{mapZones}{$_}{currentWeekArea} += $hash->{helper}{mapZones}{$_}{lastDayArea}; - $sumCurrentWeekArea += $hash->{helper}{mapZones}{$_}{currentWeekArea}; - $hash->{helper}{mapZones}{$_}{zoneLength} = 0; - } @zonekeys; - - map { - $hash->{helper}{mapZones}{$_}{lastDayCntPct} = sprintf( "%.0f", $hash->{helper}{mapZones}{$_}{lastDayCnt} / $sumLastDayCnt * 100 ); - } @zonekeys if( $sumLastDayCnt ); - - map { - $hash->{helper}{mapZones}{$_}{currentWeekCntPct} = sprintf( "%.0f", $hash->{helper}{mapZones}{$_}{currentWeekCnt} / $sumCurrentWeekCnt * 100 ); - } @zonekeys if( $sumCurrentWeekCnt ); - - map { - $hash->{helper}{mapZones}{$_}{lastDayAreaPct} = sprintf( "%.0f", $hash->{helper}{mapZones}{$_}{lastDayArea} / $sumLastDayArea * 100 ); - } @zonekeys if( $sumLastDayArea ); - - map { - $hash->{helper}{mapZones}{$_}{currentWeekAreaPct} = sprintf( "%.0f", $hash->{helper}{mapZones}{$_}{currentWeekArea} / $sumCurrentWeekArea * 100 ); - } @zonekeys if( $sumCurrentWeekArea ); - - } - # do on days - if ( $time[6] == 1 ) { - - $hash->{helper}{statistics}{lastWeekTrack} = $hash->{helper}{statistics}{currentWeekTrack}; - $hash->{helper}{statistics}{lastWeekArea} = $hash->{helper}{statistics}{currentWeekArea}; - $hash->{helper}{statistics}{currentWeekTrack} = 0; - $hash->{helper}{statistics}{currentWeekArea} = 0; - - if ( AttrVal($name, 'mapZones', 0) && defined( $hash->{helper}{mapZones} ) ) { - - my @zonekeys = sort (keys %{$hash->{helper}{mapZones}}); - my $sumLastWeekCnt=0; - my $sumLastWeekArea=0; - map { - $hash->{helper}{mapZones}{$_}{lastWeekCnt} = $hash->{helper}{mapZones}{$_}{currentWeekCnt}; - $sumLastWeekCnt += $hash->{helper}{mapZones}{$_}{lastWeekCnt}; - $hash->{helper}{mapZones}{$_}{currentWeekCnt} = 0; - $hash->{helper}{mapZones}{$_}{lastWeekArea} = $hash->{helper}{mapZones}{$_}{currentWeekArea}; - $sumLastWeekArea += $hash->{helper}{mapZones}{$_}{lastWeekArea}; - $hash->{helper}{mapZones}{$_}{currentWeekArea} = 0; - } @zonekeys; - - map { - $hash->{helper}{mapZones}{$_}{lastWeekCntPct} = sprintf( "%.0f", $hash->{helper}{mapZones}{$_}{lastWeekCnt} / $sumLastWeekCnt * 100 ); - } @zonekeys if( $sumLastWeekCnt ); - - map { - $hash->{helper}{mapZones}{$_}{lastWeekAreaPct} = sprintf( "%.0f", $hash->{helper}{mapZones}{$_}{lastWeekArea} / $sumLastWeekArea * 100 ); - } @zonekeys if( $sumLastWeekArea ); - - } - - } - - #clear position arrays - if ( AttrVal( $name, 'weekdaysToResetWayPoints', 1 ) =~ $time[6] ) { - - $hash->{helper}{areapos} = []; - $hash->{helper}{otherpos} = []; - - } - - } readingsSingleUpdate($hash, 'state', 'connected', 1 ); - + RemoveInternalTimer( $hash, \&APIAuth ); InternalTimer( gettimeofday() + $interval, \&APIAuth, $hash, 0 ); + return undef; } @@ -713,14 +576,14 @@ sub Attr { if( $cmd eq "set" ) { - return "$iam $cmd $attrName $attrVal Interval must be greater than 0, recommended 600" unless($attrVal > 0); + return "$iam $cmd $attrName $attrVal Interval must be greater than 0, recommended 420" unless($attrVal > 0); $hash->{helper}->{interval} = $attrVal; Log3 $name, 3, "$iam $cmd $attrName $attrVal"; } elsif( $cmd eq "del" ) { - $hash->{helper}->{interval} = 600; - Log3 $name, 3, "$iam $cmd $attrName and set default 600"; + $hash->{helper}->{interval} = 420; + Log3 $name, 3, "$iam $cmd $attrName and set default 420"; } ########## @@ -901,6 +764,8 @@ __END__
  • An arbitrary map can be used as background for the mower path.
  • The map has to be a raster image in webp, png or jpg format.
  • It's possible to control everything the API offers, e.g. schedule, headlight, cutting height and actions like start, pause, park etc.
  • +
  • Zones are definable.
  • +
  • Cutting height can be set for each zone differently.
  • All API data is stored in the device hash, the last and the second last one. Use {Dumper $defs{<name>}} in the commandline to find the data and build userReadings out of it.

  • Limits for the Automower Connect API @@ -1036,7 +901,7 @@ __END__
    • interval
      attr <name> interval <time in seconds>
      - Time in seconds that is used to get new data from Husqvarna Cloud. Default: 600
    • + Time in seconds that is used to get new data from Husqvarna Cloud. Default: 420
    • mapImagePath
      attr <name> mapImagePath <path to image>
      Path of a raster image file for an area the mower path has to be drawn to.
      @@ -1132,30 +997,38 @@ __END__ '{
          "<name_1>" : {
      -       "condition" : "<condition to separate name_1 from other zones>"
      +       "condition" : "<condition to separate name_1 from other zones>",
      +       "cuttingHeight" : "<cutting height for the first zone>"
        },
          "<name_2>" : {
      -       "condition" : "<condition to separate name_2 from other zones, except name_1>"
      +       "condition" : "<condition to separate name_2 from other zones, except name_1>",
      +       "cuttingHeight" : "<cutting height for the second zone>"
        },
          "<name_3>" : {
      -       "condition" : "<condition to separate name_3 from other zones, except name_1 and name_2>"
      +       "condition" : "<condition to separate name_3 from other zones, except name_1 and name_2>",
      +       "cuttingHeight" : "<cutting height for the third zone>"
        },
          "<name_n-1>" : {
      -       "condition" : "<condition to separate name_n-1 from other zones ,except the zones already seperated>"
      +       "condition" : "<condition to separate name_n-1 from other zones ,except the zones already seperated>",
      +       "cuttingHeight" : "<cutting height for the nth-1 zone>"
        },
          "<name n>" : {
      -       "condition" : "Use 'undef' because the last zone remains."
      +       "condition" : "Use 'undef' because the last zone remains.",
      +       "cuttingHeight" : "<cutting height for the nth zone>"
        }
      }'

      Example with two Zones and virtual lines defined by latitude 52.6484600648553, 52.64839739580418 (horizontal) and longitude 9.54799477359984 (vertikal). all way points above 52.6484600648553 or all way points above 52.64839739580418 and all way points to the right of 9.54799477359984 belong to zone 01_oben. All other way points belong to zone 02_unten.
      + There are different cutting heightts each zone '{
          "01_oben" : {
      -       "condition" : "$latitude > 52.6484600648553 || $longitude > 9.54799477359984 && $latitude > 52.64839739580418"
      +       "condition" : "$latitude > 52.6484600648553 || $longitude > 9.54799477359984 && $latitude > 52.64839739580418",
      +       "cuttingHeight" : "7"
        },
          "02_unten" : {
      -       "condition" : "undef"
      +       "condition" : "undef",
      +       "cuttingHeight" : "3"
        }
      }'
    • @@ -1233,6 +1106,8 @@ __END__
    • Der Pfad kann mit einer beliebigen Karte hinterlegt werden.
    • Die Karte muss als Rasterbild im webp, png oder jpg Format vorliegen.
    • Es ist möglich alles was die API anbietet zu steuern, z.B. Mähplan,Scheinwerfer, Schnitthöhe und Aktionen wie, Start, Pause, Parken usw.
    • +
    • Zonen können selbst definiert werden.
    • +
    • Die Schnitthöhe kann je selbstdefinierter Zone eingestellt werden.
    • Die letzten und vorletzten Daten aus der API sind im Gerätehash gespeichert, Mit {Dumper $defs{<device name>}} in der Befehlezeile können die Daten angezeigt werden und daraus userReadings erstellt werden.

    Limit Automower Connect API @@ -1366,7 +1241,7 @@ __END__
    • interval
      attr <name> interval <time in seconds>
      - Zeit in Sekunden nach denen neue Daten aus der Husqvarna Cloud abgerufen werden. Standard: 600
    • + Zeit in Sekunden nach denen neue Daten aus der Husqvarna Cloud abgerufen werden. Standard: 420
    • mapImagePath
      attr <name> mapImagePath <path to image>
      @@ -1465,30 +1340,39 @@ __END__ '{
          "<name_1>" : {
      -       "condition" : "<condition to separate name_1 from other zones>"
      +       "condition" : "<condition to separate name_1 from other zones>",
      +       "cuttingHeight" : "<cutting height for the first zone>"
        },
          "<name_2>" : {
      -       "condition" : "<condition to separate name_2 from other zones, except name_1>"
      +       "condition" : "<condition to separate name_2 from other zones, except name_1>",
      +       "cuttingHeight" : "<cutting height for the second zone>"
        },
          "<name_3>" : {
      -       "condition" : "<condition to separate name_3 from other zones, except name_1 and name_2>"
      +       "condition" : "<condition to separate name_3 from other zones, except name_1 and name_2>",
      +       "cuttingHeight" : "<cutting height for the third zone>"
        },
          "<name_n-1>" : {
      -       "condition" : "<condition to separate name_n-1 from other zones ,except the zones already seperated>"
      +       "condition" : "<condition to separate name_n-1 from other zones ,except the zones already seperated>",
      +       "cuttingHeight" : "<cutting height for the nth-1 zone>"
        },
          "<name n>" : {
      -       "condition" : "Use 'undef' because the last zone remains."
      +       "condition" : "Use 'undef' because the last zone remains.",
      +       "cuttingHeight" : "<cutting height for the nth zone>"
        }
      }'

      - Beispiel mit zwei Zonen und gedachten Linien bestimmt durch die Punkte Latitude 52.6484600648553, 52.64839739580418 (horizontal) und 9.54799477359984 (vertikal). Alle Wegpunkte deren Latitude über einer horizontalen Linie mit der Latitude 52.6484600648553 liegen oder alle Wegpunkte deren Latitude über einer horizontalen Linie mit der Latitude 52.64839739580418 liegen und deren Longitude rechts von einer vertikale Linie mit der Longitude 9.54799477359984 liegen, gehören zur Zone 01_oben. Alle anderen Wegpunkte gehören zur Zone 02_unten. + Beispiel mit zwei Zonen und gedachten Linien bestimmt durch die Punkte Latitude 52.6484600648553, 52.64839739580418 (horizontal) und 9.54799477359984 (vertikal). Alle Wegpunkte deren Latitude über einer horizontalen Linie mit der Latitude 52.6484600648553 liegen oder alle Wegpunkte deren Latitude über einer horizontalen Linie mit der Latitude 52.64839739580418 liegen und deren Longitude rechts von einer vertikale Linie mit der Longitude 9.54799477359984 liegen, gehören zur Zone 01_oben. Alle anderen Wegpunkte gehören zur Zone 02_unten.
      + In den Zonen sind unterschiedliche Schnitthöhen eingestellt.
      + '{
          "01_oben" : {
      -       "condition" : "$latitude > 52.6484600648553 || $longitude > 9.54799477359984 && $latitude > 52.64839739580418"
      +       "condition" : "$latitude > 52.6484600648553 || $longitude > 9.54799477359984 && $latitude > 52.64839739580418",
      +       "cuttingHeight" : "7"
        },
          "02_unten" : {
      -       "condition" : "undef"
      +       "condition" : "undef",
      +       "cuttingHeight" : "3"
        }
      }'
    • diff --git a/fhem/FHEM/75_AutomowerConnectDevice.pm b/fhem/FHEM/75_AutomowerConnectDevice.pm index e019e82e2..b7af8c41b 100644 --- a/fhem/FHEM/75_AutomowerConnectDevice.pm +++ b/fhem/FHEM/75_AutomowerConnectDevice.pm @@ -40,6 +40,7 @@ BEGIN { qw( AttrVal CommandAttr + CommandDeleteReading fhemTzOffset FmtDateTime getKeyValue @@ -190,153 +191,12 @@ sub Notify { readingsBeginUpdate($hash); - 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' ); + ::FHEM::Devices::AMConnect::Common::fillReadings( $hash ); - 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} . ')' ); - } - - my $tstamp = $hash->{helper}{mower}{attributes}{$pref}{errorCodeTimestamp}; - my $timestamp = ::FHEM::Devices::AMConnect::Common::FmtDateTimeGMT($tstamp/1000); - readingsBulkUpdateIfChanged($hash, $pref."_errorCodeTimestamp", $tstamp ? $timestamp : '-' ); - - my $errc = $hash->{helper}{mower}{attributes}{$pref}{errorCode}; - readingsBulkUpdateIfChanged($hash, $pref.'_errorCode', $tstamp ? $errc : '-'); - - my $errd = $::FHEM::Devices::AMConnect::Common::errortable->{$errc}; - readingsBulkUpdateIfChanged($hash, $pref.'_errorDescription', $tstamp ? $errd : '-'); - - $pref = 'system'; - readingsBulkUpdateIfChanged($hash, $pref."_name", $hash->{helper}{mower}{attributes}{$pref}{name} ); - my $model = $hash->{helper}{mower}{attributes}{$pref}{model}; - $model =~ s/AUTOMOWER./AM/; - # $hash->{MODEL} = '' if (!defined $hash->{MODEL}); - $hash->{MODEL} = $model if ( $model and $hash->{MODEL} ne $model ); - readingsBulkUpdateIfChanged($hash, $pref."_serialNumber", $hash->{helper}{mower}{attributes}{$pref}{serialNumber} ); - $pref = 'planner'; - readingsBulkUpdateIfChanged($hash, "planner_restrictedReason", $hash->{helper}{mower}{attributes}{$pref}{restrictedReason} ); - readingsBulkUpdateIfChanged($hash, "planner_overrideAction", $hash->{helper}{mower}{attributes}{$pref}{override}{action} ); - - $tstamp = $hash->{helper}{mower}{attributes}{$pref}{nextStartTimestamp}; - $timestamp = ::FHEM::Devices::AMConnect::Common::FmtDateTimeGMT($tstamp/1000); - readingsBulkUpdateIfChanged($hash, "planner_nextStart", $tstamp ? $timestamp : '-' ); - $pref = 'statistics'; - readingsBulkUpdateIfChanged($hash, $pref."_numberOfCollisions", $hash->{helper}->{mower}{attributes}{$pref}{numberOfCollisions} ); - readingsBulkUpdateIfChanged($hash, $pref."_newGeoDataSets", $hash->{helper}{newdatasets} ); - $pref = 'settings'; - 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."_Timestamp", FmtDateTime( $hash->{helper}{mower}{attributes}{metadata}{statusTimestamp}/1000 )); - readingsBulkUpdateIfChanged($hash, $pref."_TimestampDiff", $storediff/1000 ); - readingsBulkUpdateIfChanged($hash, $pref."_TimestampOld", FmtDateTime( $hash->{helper}{mowerold}{attributes}{metadata}{statusTimestamp}/1000 )); - $pref = 'positions'; - readingsBulkUpdate($hash, 'state', 'connected',1); readingsEndUpdate($hash, 1); - my @time = localtime(); - my $secs = ( $time[2] * 3600 ) + ( $time[1] * 60 ) + $time[0]; - my $interval = $hosthash->{helper}->{interval}; - # do at midnight - if ( $secs <= $interval ) { + ::FHEM::Devices::AMConnect::Common::calculateStatistics( $hash ); - $hash->{helper}{statistics}{lastDayTrack} = $hash->{helper}{statistics}{currentDayTrack}; - $hash->{helper}{statistics}{lastDayArea} = $hash->{helper}{statistics}{currentDayArea}; - $hash->{helper}{statistics}{currentWeekTrack} += $hash->{helper}{statistics}{currentDayTrack}; - $hash->{helper}{statistics}{currentWeekArea} += $hash->{helper}{statistics}{currentDayArea}; - $hash->{helper}{statistics}{currentDayTrack} = 0; - $hash->{helper}{statistics}{currentDayArea} = 0; - - if ( AttrVal($name, 'mapZones', 0) && defined( $hash->{helper}{mapZones} ) ) { - - my @zonekeys = sort (keys %{$hash->{helper}{mapZones}}); - my $sumLastDayCnt=0; - my $sumCurrentWeekCnt=0; - my $sumLastDayArea=0; - my $sumCurrentWeekArea=0; - map { - $hash->{helper}{mapZones}{$_}{lastDayCnt} = $hash->{helper}{mapZones}{$_}{zoneCnt}; - $sumLastDayCnt += $hash->{helper}{mapZones}{$_}{lastDayCnt}; - $hash->{helper}{mapZones}{$_}{currentWeekCnt} += $hash->{helper}{mapZones}{$_}{lastDayCnt}; - $sumCurrentWeekCnt += $hash->{helper}{mapZones}{$_}{currentWeekCnt}; - $hash->{helper}{mapZones}{$_}{zoneCnt} = 0; - - $hash->{helper}{mapZones}{$_}{lastDayArea} = $hash->{helper}{mapZones}{$_}{zoneLength}; - $sumLastDayArea += $hash->{helper}{mapZones}{$_}{lastDayArea}; - $hash->{helper}{mapZones}{$_}{currentWeekArea} += $hash->{helper}{mapZones}{$_}{lastDayArea}; - $sumCurrentWeekArea += $hash->{helper}{mapZones}{$_}{currentWeekArea}; - $hash->{helper}{mapZones}{$_}{zoneLength} = 0; - } @zonekeys; - - map { - $hash->{helper}{mapZones}{$_}{lastDayCntPct} = sprintf( "%.0f", $hash->{helper}{mapZones}{$_}{lastDayCnt} / $sumLastDayCnt * 100 ); - } @zonekeys if( $sumLastDayCnt ); - - map { - $hash->{helper}{mapZones}{$_}{currentWeekCntPct} = sprintf( "%.0f", $hash->{helper}{mapZones}{$_}{currentWeekCnt} / $sumCurrentWeekCnt * 100 ); - } @zonekeys if( $sumCurrentWeekCnt ); - - map { - $hash->{helper}{mapZones}{$_}{lastDayAreaPct} = sprintf( "%.0f", $hash->{helper}{mapZones}{$_}{lastDayArea} / $sumLastDayArea * 100 ); - } @zonekeys if( $sumLastDayArea ); - - map { - $hash->{helper}{mapZones}{$_}{currentWeekAreaPct} = sprintf( "%.0f", $hash->{helper}{mapZones}{$_}{currentWeekArea} / $sumCurrentWeekArea * 100 ); - } @zonekeys if( $sumCurrentWeekArea ); - - } - # do on days - if ( $time[6] == 1 ) { - - $hash->{helper}{statistics}{lastWeekTrack} = $hash->{helper}{statistics}{currentWeekTrack}; - $hash->{helper}{statistics}{lastWeekArea} = $hash->{helper}{statistics}{currentWeekArea}; - $hash->{helper}{statistics}{currentWeekTrack} = 0; - $hash->{helper}{statistics}{currentWeekArea} = 0; - - if ( AttrVal($name, 'mapZones', 0) && defined( $hash->{helper}{mapZones} ) ) { - - my @zonekeys = sort (keys %{$hash->{helper}{mapZones}}); - my $sumLastWeekCnt=0; - my $sumLastWeekArea=0; - map { - $hash->{helper}{mapZones}{$_}{lastWeekCnt} = $hash->{helper}{mapZones}{$_}{currentWeekCnt}; - $sumLastWeekCnt += $hash->{helper}{mapZones}{$_}{lastWeekCnt}; - $hash->{helper}{mapZones}{$_}{currentWeekCnt} = 0; - $hash->{helper}{mapZones}{$_}{lastWeekArea} = $hash->{helper}{mapZones}{$_}{currentWeekArea}; - $sumLastWeekArea += $hash->{helper}{mapZones}{$_}{lastWeekArea}; - $hash->{helper}{mapZones}{$_}{currentWeekArea} = 0; - } @zonekeys; - - map { - $hash->{helper}{mapZones}{$_}{lastWeekCntPct} = sprintf( "%.0f", $hash->{helper}{mapZones}{$_}{lastWeekCnt} / $sumLastWeekCnt * 100 ); - } @zonekeys if( $sumLastWeekCnt ); - - map { - $hash->{helper}{mapZones}{$_}{lastWeekAreaPct} = sprintf( "%.0f", $hash->{helper}{mapZones}{$_}{lastWeekArea} / $sumLastWeekArea * 100 ); - } @zonekeys if( $sumLastWeekArea ); - - } - - } - - #clear position arrays - if ( AttrVal( $name, 'weekdaysToResetWayPoints', 1 ) =~ $time[6] ) { - - $hash->{helper}{areapos} = []; - $hash->{helper}{otherpos} = []; - - } - - } readingsSingleUpdate($hash, 'state', 'connected',1); } @@ -688,6 +548,8 @@ __END__
    • An arbitrary map can be used as background for the mower path.
    • The map has to be a raster image in webp, png or jpg format.
    • It's possible to control everything the API offers, e.g. schedule, headlight, cutting height and actions like start, pause, park etc.
    • +
    • Zones are definable.
    • +
    • Cutting height can be set for each zone differently.
    • All API data is stored in the device hash, the last and the second last one. Use {Dumper $defs{<name>}} in the commandline to find the data and build userReadings out of it.

    Requirements @@ -887,30 +749,38 @@ __END__ '{
        "<name_1>" : {
    -       "condition" : "<condition to separate name_1 from other zones>"
    +       "condition" : "<condition to separate name_1 from other zones>",
    +       "cuttingHeight" : "<cutting height for the first zone>"
      },
        "<name_2>" : {
    -       "condition" : "<condition to separate name_2 from other zones, except name_1>"
    +       "condition" : "<condition to separate name_2 from other zones, except name_1>",
    +       "cuttingHeight" : "<cutting height for the second zone>"
      },
        "<name_3>" : {
    -       "condition" : "<condition to separate name_3 from other zones, except name_1 and name_2>"
    +       "condition" : "<condition to separate name_3 from other zones, except name_1 and name_2>",
    +       "cuttingHeight" : "<cutting height for the third zone>"
      },
        "<name_n-1>" : {
    -       "condition" : "<condition to separate name_n-1 from other zones ,except the zones already seperated>"
    +       "condition" : "<condition to separate name_n-1 from other zones ,except the zones already seperated>",
    +       "cuttingHeight" : "<cutting height for the nth-1 zone>"
      },
        "<name n>" : {
    -       "condition" : "Use undef because the last zone remains."
    +       "condition" : "Use 'undef' because the last zone remains.",
    +       "cuttingHeight" : "<cutting height for the nth zone>"
      }
    }'

    Example with two Zones and virtual lines defined by latitude 52.6484600648553, 52.64839739580418 (horizontal) and longitude 9.54799477359984 (vertikal). all way points above 52.6484600648553 or all way points above 52.64839739580418 and all way points to the right of 9.54799477359984 belong to zone 01_oben. All other way points belong to zone 02_unten.
    + There are different cutting heightts each zone.
    '{
        "01_oben" : {
    -       "condition" : "$latitude > 52.6484600648553 || $longitude > 9.54799477359984 && $latitude > 52.64839739580418"
    +       "condition" : "$latitude > 52.6484600648553 || $longitude > 9.54799477359984 && $latitude > 52.64839739580418",
    +       "cuttingHeight" : "7"
      },
        "02_unten" : {
    -       "condition" : "undef"
    +       "condition" : "undef",
    +       "cuttingHeight" : "3"
      }
    }'
    @@ -983,12 +853,14 @@ __END__
    • Dieses Modul nutzt eine Istanz des AutomowerConnect Moduls als Host, um einen weiteren Husqvarna Automower, dessen Daten dort gehostet werden und der mit einem Connect Modul (SIM) ausgerüstet ist, zu steuern.
    • Die Instanzen dieses Moduls bilden die FHEM-Geräte weiterer Mähroboter.
    • -
    • Dieses Modul wird also erst benötigt, wenn mehrere Mähroboter unter einem Application Key registriert sind.
    • +
    • Dieses Modul wird also erst benötigt, wenn mehrere Mähroboter unter einem Application Key registriert sind.
    • Der Pfad des Mähroboters wird in der Detailansicht des FHEMWEB Frontends angezeigt.
    • Die Zahl der anzuzeigenden Wegpunkte des Pfades kann frei gewählt werden.
    • Der Pfad kann mit einer beliebigen Karte hinterlegt werden.
    • Die Karte muss als Rasterbild im webp, png oder jpg Format vorliegen.
    • Es ist möglich alles was Die API anbietet zu steuern, z.B. Mähplan,Scheinwerfer, Schnitthöhe und Aktionen wie, Start, Pause, Parken usw.
    • +
    • Zonen können selbst definiert werden.
    • +
    • Die Schnitthöhe kann je selbstdefinierter Zone eingestellt werden.
    • Die letzten und vorletzten Daten aus dem Host sind im Gerätehash gespeichert, Mit {Dumper $defs{<device name>}} in der Befehlszeile können die Daten angezeigt werden und daraus userReadings erstellt werden.

    Anforderungen @@ -1190,30 +1062,38 @@ __END__ '{
        "<name_1>" : {
    -       "condition" : "<condition to separate name_1 from other zones>"
    +       "condition" : "<condition to separate name_1 from other zones>",
    +       "cuttingHeight" : "<cutting height for the first zone>"
      },
        "<name_2>" : {
    -       "condition" : "<condition to separate name_2 from other zones, except name_1>"
    +       "condition" : "<condition to separate name_2 from other zones, except name_1>",
    +       "cuttingHeight" : "<cutting height for the second zone>"
      },
        "<name_3>" : {
    -       "condition" : "<condition to separate name_3 from other zones, except name_1 and name_2>"
    +       "condition" : "<condition to separate name_3 from other zones, except name_1 and name_2>",
    +       "cuttingHeight" : "<cutting height for the third zone>"
      },
        "<name_n-1>" : {
    -       "condition" : "<condition to separate name_n-1 from other zones ,except the zones already seperated>"
    +       "condition" : "<condition to separate name_n-1 from other zones ,except the zones already seperated>",
    +       "cuttingHeight" : "<cutting height for the nth-1 zone>"
      },
        "<name n>" : {
    -       "condition" : "Use undef because the last zone remains."
    +       "condition" : "Use 'undef' because the last zone remains.",
    +       "cuttingHeight" : "<cutting height for the nth zone>"
      }
    }'

    - Beispiel mit zwei Zonen und gedachten Linien bestimmt durch die Punkte Latitude 52.6484600648553, 52.64839739580418 (horizontal) und 9.54799477359984 (vertikal). Alle Wegpunkte deren Latitude über einer horizontalen Linie mit der Latitude 52.6484600648553 liegen oder alle Wegpunkte deren Latitude über einer horizontalen Linie mit der Latitude 52.64839739580418 liegen und deren Longitude rechts von einer vertikale Linie mit der Longitude 9.54799477359984 liegen, gehören zur Zone 01_oben Alle anderen Wegpunkte gehören zur Zone 02_unten. + Beispiel mit zwei Zonen und gedachten Linien bestimmt durch die Punkte Latitude 52.6484600648553, 52.64839739580418 (horizontal) und 9.54799477359984 (vertikal). Alle Wegpunkte deren Latitude über einer horizontalen Linie mit der Latitude 52.6484600648553 liegen oder alle Wegpunkte deren Latitude über einer horizontalen Linie mit der Latitude 52.64839739580418 liegen und deren Longitude rechts von einer vertikale Linie mit der Longitude 9.54799477359984 liegen, gehören zur Zone 01_oben. Alle anderen Wegpunkte gehören zur Zone 02_unten.
    + In den Zonen sind unterschiedliche Schnitthöhen eingestellt.
    '{
        "01_oben" : {
    -       "condition" : "$latitude > 52.6484600648553 || $longitude > 9.54799477359984 && $latitude > 52.64839739580418"
    +       "condition" : "$latitude > 52.6484600648553 || $longitude > 9.54799477359984 && $latitude > 52.64839739580418",
    +       "cuttingHeight" : "7"
      },
        "02_unten" : {
    -       "condition" : "undef"
    +       "condition" : "undef",
    +       "cuttingHeight" : "3"
      }
    }'
    diff --git a/fhem/lib/FHEM/Devices/AMConnect/Common.pm b/fhem/lib/FHEM/Devices/AMConnect/Common.pm index 75917de18..160c20d6e 100644 --- a/fhem/lib/FHEM/Devices/AMConnect/Common.pm +++ b/fhem/lib/FHEM/Devices/AMConnect/Common.pm @@ -34,7 +34,7 @@ use POSIX; use GPUtils qw(:all); use Time::HiRes qw(gettimeofday); -use Blocking; +# use Blocking; use Storable qw(dclone retrieve store); # Import der FHEM Funktionen @@ -148,19 +148,24 @@ mowingPathLineWidth="1"'; my $mapZonesTpl = '{ "A_Zone_1" : { - "condition" : "" + "condition" : "", + "cuttingHeight" : "" }, "B_Zone_2" : { - "condition" : "" + "condition" : "", + "cuttingHeight" : "" }, "C_Zone_3" : { - "condition" : "" + "condition" : "", + "cuttingHeight" : "" }, "D_Zone_x" : { - "condition" : "" + "condition" : "", + "cuttingHeight" : "" }, "E_LastZone" : { - "condition" : "Use undef because the last zone remains." + "condition" : "Use undef because the last zone remains.", + "cuttingHeight" : "" } }'; @@ -168,7 +173,7 @@ my $mapZonesTpl = '{ %$hash = (%$hash, helper => { passObj => FHEM::Core::Authentication::Passwords->new($type), - interval => 600, + interval => 420, client_id => $client_id, grant_type => 'client_credentials', mowerNumber => $mowerNumber, @@ -734,6 +739,14 @@ sub AlignArray { $tmp = dclone( \@ar ); ZoneHandling ( $hash, $tmp, $cnt ); + } + # set cutting height per zone + if ( AttrVal($name, 'mapZones', 0) && $act =~ /^MOWING$/ && $actold =~ /^MOWING$/ + && defined( $hash->{helper}{currentZone} ) && defined( $hash->{helper}{mapZones}{$hash->{helper}{currentZone}}{cuttingHeight} )) { + + CMD( $hash ,'cuttingHeight', $hash->{helper}{mapZones}{$hash->{helper}{currentZone}}{cuttingHeight} ) + if ( $hash->{helper}{mapZones}{$hash->{helper}{currentZone}}{cuttingHeight} != $hash->{helper}{mower}{attributes}{settings}{cuttingHeight} ); + } if ( $act =~ /^(CHARGING|PARKED_IN_CS)$/ && $actold =~ /^(PARKED_IN_CS|CHARGING)$/ ) { @@ -773,6 +786,7 @@ sub AlignArray { return undef; } + ######################### sub isErrorThanPrepare { my ( $hash, $poshash ) = @_; @@ -858,8 +872,8 @@ sub ZoneHandling { } - $hash->{helper}{mapZones}{$zonekeys[$k]}{zoneLength} += calcPathLength( $hash, $i, $i + 1 ); $hash->{helper}{mapZones}{$zonekeys[$k]}{zoneCnt}++; + $hash->{helper}{mapZones}{$zonekeys[$k]}{zoneLength} += calcPathLength( $hash, $i, $i + 1 ); last; } elsif ( $k == @zonekeys-2 ) { # last zone @@ -871,8 +885,8 @@ sub ZoneHandling { } - $hash->{helper}{mapZones}{$zonekeys[$k+1]}{zoneLength} += calcPathLength( $hash, $i, $i + 1 ); $hash->{helper}{mapZones}{$zonekeys[$k+1]}{zoneCnt}++; + $hash->{helper}{mapZones}{$zonekeys[$k+1]}{zoneLength} += calcPathLength( $hash, $i, $i + 1 ); } @@ -881,12 +895,80 @@ sub ZoneHandling { } my $sumDayCnt=0; - map { $sumDayCnt += $hash->{helper}{mapZones}{$_}{zoneCnt} } @zonekeys; - map { $hash->{helper}{mapZones}{$_}{currentDayCntPct} = sprintf( "%.0f", $hash->{helper}{mapZones}{$_}{zoneCnt} / $sumDayCnt * 100 ) } @zonekeys if ( $sumDayCnt ); - my $sumDayArea=0; - map { $sumDayArea += $hash->{helper}{mapZones}{$_}{zoneLength} } @zonekeys; - map { $hash->{helper}{mapZones}{$_}{currentDayAreaPct} = sprintf( "%.0f", $hash->{helper}{mapZones}{$_}{zoneLength} / $sumDayArea * 100 ) } @zonekeys if ( $sumDayArea ); + + map { $sumDayCnt += $hash->{helper}{mapZones}{$_}{zoneCnt}; + $sumDayArea += $hash->{helper}{mapZones}{$_}{zoneLength}; + } @zonekeys; + + map { $hash->{helper}{mapZones}{$_}{currentDayCntPct} = ( $sumDayCnt ? sprintf( "%.0f", $hash->{helper}{mapZones}{$_}{zoneCnt} / $sumDayCnt * 100 ) : 0 ); + $hash->{helper}{mapZones}{$_}{currentDayAreaPct} = ( $sumDayArea ? sprintf( "%.0f", $hash->{helper}{mapZones}{$_}{zoneLength} / $sumDayArea * 100 ) : 0 ); + } @zonekeys; + + $hash->{helper}{newzonedatasets} = $cnt; + +} + +######################### +sub setCuttingHeight { + my ( $hash, $poshash, $cnt ) = @_; + my $name = $hash->{NAME}; + my $zone = ''; + my $nextzone = ''; + my @pos = @$poshash; + my $longitude = 0; + my $latitude = 0; + my @zonekeys = sort (keys %{$hash->{helper}{mapZones}}); + my $i = 0; + my $k = 0; + + map{ $hash->{helper}{mapZones}{$_}{curZoneCnt} = 0 } @zonekeys; + + for ( $i = 0; $i < $cnt; $i++){ + + $longitude = $pos[$i]{longitude}; + $latitude = $pos[$i]{latitude}; + + for ( $k = 0; $k < @zonekeys-1; $k++){ + + if ( eval ("$hash->{helper}{mapZones}{$zonekeys[$k]}{condition}") ) { + + if ( $hash->{helper}{mapZones}{$zonekeys[$k]}{curZoneCnt} == $i) { # find current zone and count consecutive way points + + $hash->{helper}{mapZones}{$zonekeys[$k]}{curZoneCnt}++; + $hash->{helper}{currentZone} = $zonekeys[$k]; + + } + + $hash->{helper}{mapZones}{$zonekeys[$k]}{zoneCnt}++; + $hash->{helper}{mapZones}{$zonekeys[$k]}{zoneLength} += calcPathLength( $hash, $i, $i + 1 ); + last; + + } elsif ( $k == @zonekeys-2 ) { # last zone + + if ( $hash->{helper}{mapZones}{$zonekeys[$k+1]}{curZoneCnt} == $i) { # find current zone and count consecutive way points + + $hash->{helper}{mapZones}{$zonekeys[$k+1]}{curZoneCnt}++; + $hash->{helper}{currentZone} = $zonekeys[$k+1]; + + } + + $hash->{helper}{mapZones}{$zonekeys[$k+1]}{zoneCnt}++; + $hash->{helper}{mapZones}{$zonekeys[$k+1]}{zoneLength} += calcPathLength( $hash, $i, $i + 1 ); + + } + + } + + } + + my $sumDayCnt=0; + my $sumDayArea=0; + map { $sumDayCnt += $hash->{helper}{mapZones}{$_}{zoneCnt}; + $hash->{helper}{mapZones}{$_}{currentDayCntPct} = ( $sumDayCnt ? sprintf( "%.0f", $hash->{helper}{mapZones}{$_}{zoneCnt} / $sumDayCnt * 100 ) : 0 ); + $sumDayArea += $hash->{helper}{mapZones}{$_}{zoneLength}; + $hash->{helper}{mapZones}{$_}{currentDayAreaPct} = ( $sumDayArea ? sprintf( "%.0f", $hash->{helper}{mapZones}{$_}{zoneLength} / $sumDayArea * 100 ) : 0 ); + } @zonekeys; $hash->{helper}{newzonedatasets} = $cnt; @@ -1080,6 +1162,141 @@ sub posMinMax { return undef; } +######################### +sub fillReadings { + my ( $hash ) = @_; + my $name = $hash->{NAME}; + + 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' ); + + 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} . ')' ); + } + + my $tstamp = $hash->{helper}{mower}{attributes}{$pref}{errorCodeTimestamp}; + 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 : '-'); + + my $errd = $errortable->{$errc}; + readingsBulkUpdateIfChanged($hash, $pref.'_errorDescription', $tstamp ? $errd : '-'); + + $pref = 'system'; + 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} ); + + $tstamp = $hash->{helper}{mower}{attributes}{$pref}{nextStartTimestamp}; + $timestamp = FmtDateTimeGMT($tstamp/1000); + readingsBulkUpdateIfChanged($hash, "planner_nextStart", $tstamp ? $timestamp : '-' ); + + $pref = 'statistics'; + readingsBulkUpdateIfChanged($hash, $pref."_numberOfCollisions", $hash->{helper}->{mower}{attributes}{$pref}{numberOfCollisions} ); + readingsBulkUpdateIfChanged($hash, $pref."_newGeoDataSets", $hash->{helper}{newdatasets} ); + $pref = 'settings'; + 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)") ); + + my $storediff = $hash->{helper}{mower}{attributes}{metadata}{statusTimestamp} - $hash->{helper}{mowerold}{attributes}{metadata}{statusTimestamp}; + readingsBulkUpdateIfChanged($hash, $pref."_Timestamp", FmtDateTime( $hash->{helper}{mower}{attributes}{metadata}{statusTimestamp}/1000 )); + readingsBulkUpdateIfChanged($hash, $pref."_TimestampDiff", $storediff/1000 ); + readingsBulkUpdateIfChanged($hash, $pref."_TimestampOld", FmtDateTime( $hash->{helper}{mowerold}{attributes}{metadata}{statusTimestamp}/1000 )); + + return undef; +} + +######################### +sub calculateStatistics { + my ( $hash ) = @_; + my $name = $hash->{NAME}; + my @time = localtime(); + my $secs = ( $time[2] * 3600 ) + ( $time[1] * 60 ) + $time[0]; + my $interval = $hash->{helper}->{interval}; + # do at midnight + if ( $secs <= $interval ) { + + $hash->{helper}{statistics}{lastDayTrack} = $hash->{helper}{statistics}{currentDayTrack}; + $hash->{helper}{statistics}{lastDayArea} = $hash->{helper}{statistics}{currentDayArea}; + $hash->{helper}{statistics}{currentWeekTrack} += $hash->{helper}{statistics}{currentDayTrack}; + $hash->{helper}{statistics}{currentWeekArea} += $hash->{helper}{statistics}{currentDayArea}; + $hash->{helper}{statistics}{currentDayTrack} = 0; + $hash->{helper}{statistics}{currentDayArea} = 0; + + if ( AttrVal($name, 'mapZones', 0) && defined( $hash->{helper}{mapZones} ) ) { + + my @zonekeys = sort (keys %{$hash->{helper}{mapZones}}); + my $sumCurrentWeekCnt=0; + my $sumCurrentWeekArea=0; + map { + $hash->{helper}{mapZones}{$_}{currentWeekCnt} += $hash->{helper}{mapZones}{$_}{zoneCnt}; + $sumCurrentWeekCnt += $hash->{helper}{mapZones}{$_}{currentWeekCnt}; + $hash->{helper}{mapZones}{$_}{currentWeekArea} += $hash->{helper}{mapZones}{$_}{zoneLength}; + $sumCurrentWeekArea += $hash->{helper}{mapZones}{$_}{currentWeekArea}; + $hash->{helper}{mapZones}{$_}{zoneCnt} = 0; + $hash->{helper}{mapZones}{$_}{zoneLength} = 0; + } @zonekeys; + + map { + $hash->{helper}{mapZones}{$_}{lastDayCntPct} = $hash->{helper}{mapZones}{$_}{currentDayCntPct}; + $hash->{helper}{mapZones}{$_}{currentWeekCntPct} = ( $sumCurrentWeekCnt ? sprintf( "%.0f", $hash->{helper}{mapZones}{$_}{currentWeekCnt} / $sumCurrentWeekCnt * 100 ) : '' ); + $hash->{helper}{mapZones}{$_}{lastDayAreaPct} = $hash->{helper}{mapZones}{$_}{currentDayAreaPct}; + $hash->{helper}{mapZones}{$_}{currentWeekAreaPct} = ( $sumCurrentWeekArea ? sprintf( "%.0f", $hash->{helper}{mapZones}{$_}{currentWeekArea} / $sumCurrentWeekArea * 100 ) : '' ); + $hash->{helper}{mapZones}{$_}{currentDayCntPct} = ''; + $hash->{helper}{mapZones}{$_}{currentDayAreaPct} = ''; + } @zonekeys; + + } + # do on days + if ( $time[6] == 1 ) { + + $hash->{helper}{statistics}{lastWeekTrack} = $hash->{helper}{statistics}{currentWeekTrack}; + $hash->{helper}{statistics}{lastWeekArea} = $hash->{helper}{statistics}{currentWeekArea}; + $hash->{helper}{statistics}{currentWeekTrack} = 0; + $hash->{helper}{statistics}{currentWeekArea} = 0; + + if ( AttrVal($name, 'mapZones', 0) && defined( $hash->{helper}{mapZones} ) ) { + + my @zonekeys = sort (keys %{$hash->{helper}{mapZones}}); + map { + $hash->{helper}{mapZones}{$_}{lastWeekCntPct} = $hash->{helper}{mapZones}{$_}{currentWeekCntPct}; + $hash->{helper}{mapZones}{$_}{lastWeekAreaPct} = $hash->{helper}{mapZones}{$_}{currentWeekAreaPct}; + $hash->{helper}{mapZones}{$_}{currentWeekCntPct} = ''; + $hash->{helper}{mapZones}{$_}{currentWeekAreaPct} = ''; + } @zonekeys; + + } + + } + + #clear position arrays + if ( AttrVal( $name, 'weekdaysToResetWayPoints', 1 ) =~ $time[6] ) { + + $hash->{helper}{areapos} = []; + $hash->{helper}{otherpos} = []; + + } + + } + + return undef; +} + ######################### sub listStatisticsData { my ( $hash ) = @_; @@ -1094,10 +1311,10 @@ sub listStatisticsData { $ret .= ' Hash Path Value Unit '; $cnt++;$ret .= ' $hash->{helper}{mower}{attributes}{statistics}{numberOfChargingCycles}   ' . $hash->{helper}{mower}{attributes}{statistics}{numberOfChargingCycles} . ' '; $cnt++;$ret .= ' $hash->{helper}{mower}{attributes}{statistics}{numberOfCollisions}   ' . $hash->{helper}{mower}{attributes}{statistics}{numberOfCollisions} . ' '; - $cnt++;$ret .= ' $hash->{helper}{mower}{attributes}{statistics}{totalChargingTime}   ' . $hash->{helper}{mower}{attributes}{statistics}{totalChargingTime} . ' s '; - $cnt++;$ret .= ' $hash->{helper}{mower}{attributes}{statistics}{totalCuttingTime}   ' . $hash->{helper}{mower}{attributes}{statistics}{totalCuttingTime} . ' s '; - $cnt++;$ret .= ' $hash->{helper}{mower}{attributes}{statistics}{totalRunningTime}   ' . $hash->{helper}{mower}{attributes}{statistics}{totalRunningTime} . '1 s '; - $cnt++;$ret .= ' $hash->{helper}{mower}{attributes}{statistics}{totalSearchingTime}   ' . $hash->{helper}{mower}{attributes}{statistics}{totalSearchingTime} . ' s '; + $cnt++;$ret .= ' $hash->{helper}{mower}{attributes}{statistics}{totalChargingTime}   ' . sprintf( "%.0f", $hash->{helper}{mower}{attributes}{statistics}{totalChargingTime} / 3600 ) . ' h '; + $cnt++;$ret .= ' $hash->{helper}{mower}{attributes}{statistics}{totalCuttingTime}   ' . sprintf( "%.0f", $hash->{helper}{mower}{attributes}{statistics}{totalCuttingTime} / 3600 ) . ' h '; + $cnt++;$ret .= ' $hash->{helper}{mower}{attributes}{statistics}{totalRunningTime}   ' . sprintf( "%.0f", $hash->{helper}{mower}{attributes}{statistics}{totalRunningTime} / 3600 ) . '1 h '; + $cnt++;$ret .= ' $hash->{helper}{mower}{attributes}{statistics}{totalSearchingTime}   ' . sprintf( "%.0f", $hash->{helper}{mower}{attributes}{statistics}{totalSearchingTime} / 3600 ) . ' h '; # $cnt++;$ret .= ' $hash->{helper}{statistics}{currentSpeed}   ' . $hash->{helper}{statistics}{currentSpeed} . ' m/s '; $cnt++;$ret .= ' $hash->{helper}{statistics}{currentDayTrack}   ' . sprintf( "%.0f", $hash->{helper}{statistics}{currentDayTrack} ) . ' m ';