From 8a2106c25714e99bdb440b94679221d506c2a3d2 Mon Sep 17 00:00:00 2001 From: Ellert <> Date: Mon, 30 Jan 2023 18:56:01 +0000 Subject: [PATCH] AutomowerConnect(Device): add features getter, reduce readings git-svn-id: https://svn.fhem.de/fhem/trunk@27151 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/CHANGED | 5 +- fhem/FHEM/74_AutomowerConnect.pm | 258 +++++++++++++++++++----- fhem/FHEM/75_AutomowerConnectDevice.pm | 262 ++++++++++++++++++++----- 3 files changed, 425 insertions(+), 100 deletions(-) diff --git a/fhem/CHANGED b/fhem/CHANGED index e973c59ea..6026fccdc 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,12 +1,15 @@ # 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: reduce readings, new getter for error codes + and internal data, show mower path without registered image + - feature: 75_AutomowerConnectDevice: same as AutomowerConnect - feature: 72_FB_CALLMONITOR: tellows.de integriert kleinere Korrekturen commandRef überarbeitet / ergänzt - feature: 59_Weather: mod packages, add new modul path in to pre-commit - bugfix: 00_KNXIO: PBP fixes - bugfix: 10_KNX: fix define-parsing problem, PBP fixes - - bugfix: 74_AutomowerConnect: improved error handling, fix missig password bug + - bugfix: 74_AutomowerConnect: improved error handling, fix missig passwd bug - feature: 74_AutomowerConnect: add disabledForIntervals, Internal MODEL - feature: 75_AutomowerConnectDevice: add disabledForIntervals, Internal MODEL - bugfix: 98_WeekdayTimer: !$we handling diff --git a/fhem/FHEM/74_AutomowerConnect.pm b/fhem/FHEM/74_AutomowerConnect.pm index 2ab968e8c..19bd89ee3 100644 --- a/fhem/FHEM/74_AutomowerConnect.pm +++ b/fhem/FHEM/74_AutomowerConnect.pm @@ -50,6 +50,8 @@ BEGIN { IsDisabled Log3 Log + minNum + maxNum readingFnAttributes readingsBeginUpdate readingsBulkUpdate @@ -147,6 +149,14 @@ sub Define{ mowerNumber => $mowerNumber, scaleToMeterLongitude => 67425, scaleToMeterLatitude => 108886, + minLon => 180, + maxLon => -180, + minLat => 90, + maxLat => -90, + imageHeight => 650, + imageWidthHeight => '350 650', + posMinMax => "-180 90\n 180 -90", + newdatasets => 0, client_id => $client_id, grant_type => 'client_credentials', MAP_PATH => '', @@ -175,7 +185,7 @@ sub Define{ }, CHARGING => { arrayName => 'cspos', - maxLength => 500, + maxLength => 100, callFn => \&ChargingStationPosition }, LEAVING => { @@ -185,7 +195,7 @@ sub Define{ }, PARKED_IN_CS => { arrayName => 'cspos', - maxLength => 50, + maxLength => 100, callFn => \&ChargingStationPosition }, STOPPED_IN_GARDEN => { @@ -196,13 +206,10 @@ sub Define{ } ); - # my $helper = retrieve( $name.'_helper' ); - # my $hashhelper = $hash->{helper}; - # %$hashhelper = (%$helper, %$hashhelper); - my $errorjson = <<'EOF'; {"23":"Wheel drive problem, left","24":"Cutting system blocked","123":"Destination not reachable","710":"SIM card locked","50":"Guide 1 not found","717":"SMS could not be sent","108":"Folding cutting deck sensor defect","4":"Loop sensor problem, front","15":"Lifted","29":"Slope too steep","1":"Outside working area","45":"Cutting height problem, dir","52":"Guide 3 not found","28":"Memory circuit problem","95":"Folding sensor activated","9":"Trapped","114":"Too high discharge current","103":"Cutting drive motor 2 defect","65":"Temporary battery problem","119":"Zone generator problem","6":"Loop sensor problem, left","82":"Wheel motor blocked, rear right","714":"Geofence problem","703":"Connectivity problem","708":"SIM card locked","75":"Connection changed","7":"Loop sensor problem, right","35":"Wheel motor overloaded, right","3":"Wrong loop signal","117":"High internal power loss","0":"Unexpected error","80":"Cutting system imbalance - Warning","110":"Collision sensor error","100":"Ultrasonic Sensor 3 defect","79":"Invalid battery combination - Invalid combination of different battery types.","724":"Communication circuit board SW must be updated","86":"Wheel motor overloaded, rear right","81":"Safety function faulty","78":"Slipped - Mower has Slipped.Situation not solved with moving pattern","107":"Docking sensor defect","33":"Mower tilted","69":"Alarm! Mower switched off","68":"Temporary battery problem","34":"Cutting stopped - slope too steep","127":"Battery problem","73":"Alarm! Mower in motion","74":"Alarm! Outside geofence","713":"Geofence problem","87":"Wheel motor overloaded, rear left","120":"Internal voltage error","39":"Cutting motor problem","704":"Connectivity problem","63":"Temporary battery problem","109":"Loop sensor defect","38":"Electronic problem","64":"Temporary battery problem","113":"Complex working area","93":"No accurate position from satellites","104":"Cutting drive motor 3 defect","709":"SIM card not found","94":"Reference station communication problem","43":"Cutting height problem, drive","13":"No drive","44":"Cutting height problem, curr","118":"Charging system problem","14":"Mower lifted","57":"Guide calibration failed","707":"SIM card requires PIN","99":"Ultrasonic Sensor 2 defect","98":"Ultrasonic Sensor 1 defect","51":"Guide 2 not found","56":"Guide calibration accomplished","49":"Ultrasonic problem","2":"No loop signal","124":"Destination blocked","25":"Cutting system blocked","19":"Collision sensor problem, front","18":"Collision sensor problem, rear","48":"No response from charger","105":"Lift Sensor defect","111":"No confirmed position","10":"Upside down","40":"Limited cutting height range","716":"Connectivity problem","27":"Settings restored","90":"No power in charging station","21":"Wheel motor blocked, left","26":"Invalid sub-device combination","92":"Work area not valid","702":"Connectivity settings restored","125":"Battery needs replacement","5":"Loop sensor problem, rear","12":"Empty battery","55":"Difficult finding home","42":"Limited cutting height range","30":"Charging system problem","72":"Alarm! Mower tilted","85":"Wheel drive problem, rear left","8":"Wrong PIN code","62":"Temporary battery problem","102":"Cutting drive motor 1 defect","116":"High charging power loss","122":"CAN error","60":"Temporary battery problem","705":"Connectivity problem","711":"SIM card locked","70":"Alarm! Mower stopped","32":"Tilt sensor problem","37":"Charging current too high","89":"Invalid system configuration","76":"Connection NOT changed","71":"Alarm! Mower lifted","88":"Angular sensor problem","701":"Connectivity problem","715":"Connectivity problem","61":"Temporary battery problem","66":"Battery problem","106":"Collision sensor defect","67":"Battery problem","112":"Cutting system major imbalance","83":"Wheel motor blocked, rear left","84":"Wheel drive problem, rear right","126":"Battery near end of life","77":"Com board not available","36":"Wheel motor overloaded, left","31":"STOP button problem","17":"Charging station blocked","54":"Weak GPS signal","47":"Cutting height problem","53":"GPS navigation problem","121":"High internal temerature","97":"Left brush motor overloaded","712":"SIM card locked","20":"Wheel motor blocked, right","91":"Switch cord problem","96":"Right brush motor overloaded","58":"Temporary battery problem","59":"Temporary battery problem","22":"Wheel drive problem, right","706":"Poor signal quality","41":"Unexpected cutting height adj","46":"Cutting height blocked","11":"Low battery","16":"Stuck in charging station","101":"Ultrasonic Sensor 4 defect","115":"Too high internal current"} EOF + my $errortable = eval { decode_json ($errorjson) }; if ($@) { return "$iam $@"; @@ -434,11 +441,11 @@ sub getMowerResponse { } Log3 $name, 5, "$iam found $foundMower "; - if ( defined ($hash->{helper}{mower}{id}) ){ + if ( defined ($hash->{helper}{mower}{id}) ){ # update dataset $hash->{helper}{mowerold} = dclone( $hash->{helper}{mower} ); - } else { + } else { # first data set $hash->{helper}{mowerold} = dclone( $hash->{helper}{mowers}[$mowerNumber] ); @@ -451,9 +458,17 @@ sub getMowerResponse { $hash->{helper}{cspos} = [ dclone( $hash->{helper}{mowerold}{attributes}{positions}[0] ), dclone( $hash->{helper}{mowerold}{attributes}{positions}[1] ) ]; $hash->{helper}{cspos}[0]{statusTimestamp} = $hash->{helper}{mowerold}{attributes}{metadata}{statusTimestamp}; $hash->{helper}{cspos}[1]{statusTimestamp} = $hash->{helper}{mowerold}{attributes}{metadata}{statusTimestamp} - 600000; + + if ( AttrVal( $name, 'mapImageCoordinatesToRegister', '' ) eq '' ) { + posMinMax( $hash, $hash->{helper}{mowerold}{attributes}{positions} ); + } + } $hash->{helper}{mower} = dclone( $hash->{helper}{mowers}[$mowerNumber] ); + # add alignment data set to the end + push( @{ $hash->{helper}{mower}{attributes}{positions} }, @{ dclone( $hash->{helper}{searchpos} ) } ); + my $storediff = $hash->{helper}{mower}{attributes}{metadata}{statusTimestamp} - $hash->{helper}{mowerold}{attributes}{metadata}{statusTimestamp}; if ($storediff) { @@ -510,8 +525,7 @@ sub getMowerResponse { readingsBulkUpdateIfChanged($hash, $pref."_TimestampDiff", $storediff/1000 ); readingsBulkUpdateIfChanged($hash, $pref."_TimestampOld", FmtDateTime( $hash->{helper}{mowerold}{attributes}{metadata}{statusTimestamp}/1000 )); $pref = 'positions'; - readingsBulkUpdateIfChanged($hash, $pref."_lastLatitude", $hash->{helper}{mower}{attributes}{$pref}[0]{latitude} ); - readingsBulkUpdateIfChanged($hash, $pref."_lastLongitude", $hash->{helper}{mower}{attributes}{$pref}[0]{longitude} ); + readingsBulkUpdateIfChanged($hash, $pref."_lastLonLat", $hash->{helper}{mower}{attributes}{$pref}[0]{longitude} . ' ' . $hash->{helper}{mower}{attributes}{$pref}[0]{latitude} ); readingsBulkUpdateIfChanged($hash, 'state', 'connected' ); my @time = localtime(); @@ -684,9 +698,24 @@ sub Get { Log3 $name, 4, "$iam called with $setName " . ($setVal ? $setVal : ""); if ( $setName eq 'html' ) { + my $ret = '' . FW_detailFn( undef, $name, undef, undef) . ''; return $ret; + } elsif ( $setName eq 'listErrorCodes' ) { + + my $ret = listErrorCodes($hash); + return $ret; + + } elsif ( $setName eq 'listInternalData' ) { + + my $ret = listInternalData($hash); + return $ret; + + } else { + + return "Unknown argument $setName, choose one of listErrorCodes:noArg listInternalData:noArg "; + } } @@ -710,7 +739,8 @@ sub Set { } elsif ( $setName eq 'chargingStationPositionToAttribute' ) { - my ($xm, $ym, $n) = split(/,\s/,ReadingsVal($name,'status_calcChargingStationPositionXYn','10.1165, 51.28, 0')); + my $xm = $hash->{helper}{chargingStation}{longitude} // 10.1165; + my $ym = $hash->{helper}{chargingStation}{latitude} // 51.28; CommandAttr($hash,"$name chargingStationCoordinates $xm $ym"); return undef; @@ -774,12 +804,12 @@ sub FW_detailFn { my ($FW_wname, $name, $room, $pageHash) = @_; # pageHash is set for summaryFn. my $hash = $defs{$name}; my $type = $hash->{TYPE}; - return undef if( AttrVal($name, 'disable', 0) || !AttrVal($name, 'showMap', 1) ); + return '' if( AttrVal($name, 'disable', 0) || !AttrVal($name, 'showMap', 1) ); if ( $hash->{helper} && $hash->{helper}{mower} && $hash->{helper}{mower}{attributes} && $hash->{helper}{mower}{attributes}{positions} && @{$hash->{helper}{mower}{attributes}{positions}} > 0 ) { my $img = "./fhem/$type/$name/map"; - my $zoom=AttrVal($name,"mapImageZoom",0.7); + my $zoom=AttrVal( $name,"mapImageZoom", 0.7 ); - AttrVal($name,"mapImageWidthHeight",'100 200') =~ /(\d+)\s(\d+)/; + AttrVal( $name,"mapImageWidthHeight", $hash->{helper}{imageWidthHeight} ) =~ /(\d+)\s(\d+)/; my ($picx,$picy) = ($1, $2); $picx=int($picx*$zoom); @@ -794,7 +824,7 @@ sub FW_detailFn { return $ret; } - return undef; + return ''; } ######################### @@ -806,17 +836,16 @@ sub FW_detailFn_Update { my @pos = (); my @posc = (); - # @pos = @{$hash->{helper}{mower}{attributes}{positions}}; # developement mode @pos = @{$hash->{helper}{areapos}}; # operational mode @posc =@{$hash->{helper}{cspos}}; # maybe operational mode my $img = "./fhem/$type/$name/map"; - AttrVal($name,"mapImageCoordinatesToRegister","0 90\n90 0") =~ /(\d*\.?\d+)\s(\d*\.?\d+)(\R|\s)(\d*\.?\d+)\s(\d*\.?\d+)/; - my ( $lonlo, $latlo, $lonru, $latru ) = ($1, $2, $4, $5); + AttrVal( $name,"mapImageCoordinatesToRegister",$hash->{helper}{posMinMax} ) =~ /(-?\d*\.?\d+)\s(-?\d*\.?\d+)(\R|-?\s)(\d*\.?\d+)\s(-?\d*\.?\d+)/; + my ( $lonlo, $latlo, $lonru, $latru ) = ( $1, $2, $4, $5 ); - my $zoom = AttrVal($name,"mapImageZoom",0.7); + my $zoom = AttrVal( $name,"mapImageZoom", 0.7 ); - AttrVal($name,"mapImageWidthHeight",'100 200') =~ /(\d+)\s(\d+)/; + AttrVal( $name,"mapImageWidthHeight", $hash->{helper}{imageWidthHeight} ) =~ /(\d+)\s(\d+)/; my ($picx,$picy) = ($1, $2); AttrVal($name,'scaleToMeterXY', $hash->{helper}{scaleToMeterLongitude} . ' ' .$hash->{helper}{scaleToMeterLatitude}) =~ /(\d+)\s+(\d+)/; @@ -833,17 +862,15 @@ sub FW_detailFn_Update { use strict "refs"; } - my $csimgpos = AttrVal($name,"chargingStationImagePosition","right"); + my $csimgpos = AttrVal( $name,"chargingStationImagePosition","right" ); + my $xm = $hash->{helper}{chargingStation}{longitude} // 10.1165; + my $ym = $hash->{helper}{chargingStation}{latitude} // 51.28; - AttrVal($name,"chargingStationCoordinates",'10.1165 51.28') =~ /(\d*\.?\d+)\s(\d*\.?\d+)/; + AttrVal( $name,"chargingStationCoordinates","$xm $ym" ) =~ /(-?\d*\.?\d+)\s(-?\d*\.?\d+)/; my ($cslo,$csla) = ($1, $2); my $cslon = int(($lonlo-$cslo) * $picx / $mapx); my $cslat = int(($latlo-$csla) * $picy / $mapy); - # my $lon = int(($lonlo-$pos[0]{longitude}) * $picx / $mapx); - # my $lat = int(($latlo-$pos[0]{latitude}) * $picy / $mapy); - # my $lastx = int(($lonlo-$pos[$#pos]{longitude}) * $picx / $mapx); - # my $lasty = int(($latlo-$pos[$#pos]{latitude}) * $picy / $mapy); # MOWING PATH my $posxy = int(($lonlo-$pos[0]{longitude}) * $picx / $mapx).",".int(($latlo-$pos[0]{latitude}) * $picy / $mapy); @@ -977,10 +1004,10 @@ sub Attr { if( $cmd eq "set" ) { - if ( AttrVal( $name,'mapImageCoordinatesToRegister', '' ) && $attrVal =~ /(\d*\.?\d+)\s(\d*\.?\d+)(\R|\s)(\d*\.?\d+)\s(\d*\.?\d+)/ ) { + if ( AttrVal( $name,'mapImageCoordinatesToRegister', '' ) && $attrVal =~ /(-?\d*\.?\d+)\s(-?\d*\.?\d+)(\R|\s)(-?\d*\.?\d+)\s(-?\d*\.?\d+)/ ) { my ( $x1, $y1, $x2, $y2 ) = ( $1, $2, $4, $5 ); - AttrVal( $name,'mapImageCoordinatesToRegister', '' ) =~ /(\d*\.?\d+)\s(\d*\.?\d+)(\R|\s)(\d*\.?\d+)\s(\d*\.?\d+)/; + AttrVal( $name,'mapImageCoordinatesToRegister', '' ) =~ /(-?\d*\.?\d+)\s(-?\d*\.?\d+)(\R|\s)(-?\d*\.?\d+)\s(-?\d*\.?\d+)/; my ( $lo1, $la1, $lo2, $la2 ) = ( $1, $2, $4, $5 ); my $scx = int( ( $x1 - $x2) / ( $lo1 - $lo2 ) ); my $scy = int( ( $y1 - $y2 ) / ( $la1 - $la2 ) ); @@ -1001,7 +1028,7 @@ sub Attr { if( $cmd eq "set" ) { - return "$iam $attrName has a wrong format use linewise pairs " unless($attrVal =~ /(\d*\.?\d+)\s(\d*\.?\d+)(\R|\s)(\d*\.?\d+)\s(\d*\.?\d+)/); + return "$iam $attrName has a wrong format use linewise pairs " unless( $attrVal =~ /(-?\d*\.?\d+)\s(-?\d*\.?\d+)(\R|\s)(-?\d*\.?\d+)\s(-?\d*\.?\d+)/ ); Log3 $name, 3, "$iam $cmd $attrName $attrVal"; } elsif( $cmd eq "del" ) { @@ -1014,7 +1041,7 @@ sub Attr { if( $cmd eq "set" ) { - return "$iam $attrName has a wrong format use " unless($attrVal =~ /(\d*\.?\d+)\s(\d*\.?\d+)/); + return "$iam $attrName has a wrong format use " unless( $attrVal =~ /(-?\d*\.?\d+)\s(-?\d*\.?\d+)/ ); Log3 $name, 3, "$iam $cmd $attrName $attrVal"; } elsif( $cmd eq "del" ) { @@ -1027,7 +1054,7 @@ sub Attr { if( $cmd eq "set" ) { - return "$iam $attrName has a wrong format use " unless($attrVal =~ /(\d+)\s(\d+)/); + return "$iam $attrName has a wrong format use " unless( $attrVal =~ /(\d+)\s(\d+)/ ); Log3 $name, 3, "$iam $cmd $attrName $attrVal"; } elsif( $cmd eq "del" ) { @@ -1040,7 +1067,7 @@ sub Attr { if( $cmd eq "set" ) { - return "$iam $attrName has a wrong format use " unless($attrVal =~ /(\d+)\s(\d+)/); + return "$iam $attrName has a wrong format use " unless( $attrVal =~ /(\d+)\s(\d+)/ ); Log3 $name, 3, "$iam $cmd $attrName $attrVal"; } elsif( $cmd eq "del" ) { @@ -1124,13 +1151,13 @@ sub AlignArray { my $activity = $hash->{helper}{mower}{attributes}{mower}{activity}; my $arrayName = $hash->{helper}{$activity}{arrayName}; my $maxLength = $hash->{helper}{$activity}{maxLength}; - for ( $i = 0; $i < $poslen-1; $i++ ) { + for ( $i = 0; $i < $poslen-3; $i++ ) { # 3 instead of 1 due to new alignment if ( $searchposlon[0] == $hash->{helper}{mower}{attributes}{positions}[ $i ]{longitude} && $searchposlat[0] == $hash->{helper}{mower}{attributes}{positions}[ $i ]{latitude} && $searchposlon[1] == $hash->{helper}{mower}{attributes}{positions}[ $i+1 ]{longitude} - && $searchposlat[1] == $hash->{helper}{mower}{attributes}{positions}[ $i+1 ]{latitude} - || $i == $poslen-2 ) { - $i++ if ($i == $poslen-2); + && $searchposlat[1] == $hash->{helper}{mower}{attributes}{positions}[ $i+1 ]{latitude} ) { + # || $i == $poslen-2 ) { # not nessecary due to new alignment + # $i++ if ( $i == $poslen-2 ); # not nessecary due to new alignment # timediff per step my $dt = 0; $dt = int(($hash->{helper}{mower}{attributes}{metadata}{statusTimestamp} - $hash->{helper}{$arrayName}[0]{statusTimestamp})/$i) if ($i); @@ -1142,16 +1169,29 @@ sub AlignArray { unshift (@{$hash->{helper}{searchpos}}, dclone($hash->{helper}{mower}{attributes}{positions}[ $k ]) ); pop (@{$hash->{helper}{searchpos}}) if (@{$hash->{helper}{searchpos}} > $searchlen); + + my @temp = (); + unshift ( @temp, dclone( $hash->{helper}{mower}{attributes}{positions}[ $k ] ) ); + posMinMax( $hash, \@temp ); + } + #callFn if present if ($hash->{helper}{$activity}{callFn}) { + $hash->{helper}{$activity}{cnt} = $i; no strict "refs"; &{$hash->{helper}{$activity}{callFn}}($hash); use strict "refs"; + } + + $hash->{helper}{newdatasets} = $i; + readingsSingleUpdate($hash, "statistics_newGeoDataSets", $i, 1); last; + } + } } } @@ -1166,13 +1206,11 @@ sub ChargingStationPosition { my $ym = 0; map { $ym += $_->{latitude} } @{$hash->{helper}{cspos}}; $ym = $ym/$n; - $hash->{helper}{chargingStation}{longitude} = $xm; - $hash->{helper}{chargingStation}{latitude} = $ym; - readingsSingleUpdate($hash, "statistics_ChargingStationPositionXYn", (int($xm * 10000000 + 0.5) / 10000000).", ".(int($ym * 10000000 + 0.5) / 10000000).", ".$n, 0); + $hash->{helper}{chargingStation}{longitude} = sprintf("%.8f",$xm); + $hash->{helper}{chargingStation}{latitude} = sprintf("%.8f",$ym); return undef; } - ######################### sub AreaStatistics { my ($hash) = @_; @@ -1192,7 +1230,7 @@ sub AreaStatistics { } $asum = $lsum * AttrVal($name,'mowerCuttingWidth',0.24); my $td = $xyarr[ 0 ]{storedTimestamp} - $xyarr[ $k ]{storedTimestamp}; - $vm = int($lsum / $td * 1000000 + 0.5)/1000 if ($td); + $vm = sprintf( '%.6f', $lsum / $td ) * 1000 if ($td); $lsum += int( ReadingsNum( $name, 'statistics_currentDayTrack', 0 ) ); $asum += int( ReadingsNum( $name, 'statistics_currentDayArea', 0 ) ); readingsBeginUpdate($hash); @@ -1275,6 +1313,114 @@ sub readMap { } } +######################### +sub posMinMax { + my ($hash, $poshash) = @_; + my $minLon = $hash->{helper}{minLon}; + my $maxLon = $hash->{helper}{maxLon}; + my $minLat = $hash->{helper}{minLat}; + my $maxLat = $hash->{helper}{maxLat}; + + for ( @{$poshash} ) { + $minLon = minNum( $minLon,$_->{longitude} ); + $maxLon = maxNum( $maxLon,$_->{longitude} ); + $minLat = minNum( $minLat,$_->{latitude} ); + $maxLat = maxNum( $maxLat,$_->{latitude} ); + } + + $hash->{helper}{minLon} = $minLon; + $hash->{helper}{maxLon} = $maxLon; + $hash->{helper}{minLat} = $minLat; + $hash->{helper}{maxLat} = $maxLat; + $hash->{helper}{posMinMax} = "$minLon $maxLat\n$maxLon $minLat"; + $hash->{helper}{imageWidthHeight} = int($hash->{helper}{imageHeight} * ($maxLon-$minLon) / ($maxLat-$minLat)) . ' ' . $hash->{helper}{imageHeight} if ($maxLon-$minLon); + + return undef; +} + +######################### +sub listInternalData { + my ( $hash ) = @_; + my $name = $hash->{NAME}; + my $rowCount = 1; + my $ret = ''; + $ret .= ''; + + $hash->{helper}{posMinMax} =~ /(-?\d*\.?\d+)\s(-?\d*\.?\d+)(\R|-?\s)(\d*\.?\d+)\s(-?\d*\.?\d+)/; + + my $xm = $hash->{helper}{chargingStation}{longitude} // 0; + my $ym = $hash->{helper}{chargingStation}{latitude} // 0; + my $csnr = scalar @{$hash->{helper}{cspos}}; + my $csnrmax = $hash->{helper}{PARKED_IN_CS}{maxLength}; + my $arnr = 0; + $arnr = scalar @{$hash->{helper}{areapos}} if( scalar @{$hash->{helper}{areapos}} > 2 ); + my $arnrmax = $hash->{helper}{MOWING}{maxLength}; + + $ret .= ''; + $ret .= ''; + $ret .= ''; + + $ret .= '
Calculated Coordinates For Automatic Registration
Data Sets ( max )  Corner Longitude Latitude
' . ($csnr + $arnr) . ' ( ' . ($csnrmax + $arnrmax) . ' )  Upper Left ' . $1 . ' ' . $2 . '
Lower Right ' . $4 . ' ' . $5 . '

'; + $ret .= ''; + $ret .= ''; + + $ret .= ''; + $ret .= ''; + + $ret .= '
Calculated Charging Station Coordinates
Data Sets (max)  Longitude  Latitude 
' . $csnr . ' ( ' . $csnrmax . ' )  ' . $xm . ' ' . $ym . ' 

'; + $ret .= ''; + $ret .= ''; + + $ret .= ''; + $ret .= ''; + $ret .= ''; + + $ret .= '
Way Point Stacks
Used For Action  Stack Name  Current Size  Max Size 
PARKED_IN_CS, CHARGING  cspos  ' . $csnr . ' ' . $csnrmax . ' 
MOWING  areapos  ' . $arnr . ' ' . $arnrmax . ' 
'; + if ( $hash->{TYPE} eq 'AutomowerConnect' ) { + + $ret .= '

'; + $ret .= ''; + + $ret .= ''; + + $ret .= '
Access Token ( expires: ' . ReadingsVal( $name, 'api_token_expires', 'none') . ' )
' . ReadingsVal( $name, '.access_token', 'none') . '
'; + + } + $ret .= ''; + + return $ret; +} + +######################### +sub listErrorCodes { + my ( $hash ) = @_; + my $rowCount = 1; + my %ec = (); + my $ec = \%ec; + for ( keys %{$hash->{helper}{errortable}} ) { + $ec->{sprintf("%03d",$_)} = $hash->{helper}{errortable}{$_} ; + } + my $ret = ''; + $ret .= ''; + $ret .= ''; + $ret .= ''; + $ret .= '
API-Response Status Codes
200, 201, 202
204
response o.k.
400, 401, 402
403, 404, 415
500, 503
error, detailed information see logfile

'; + $ret .= ''; + for (sort keys %{$ec}) { + $ret .= '{$_}; + $ret .= ''; + $rowCount++; + } + $ret .= '
Mower Error Table
'; + + return $ret; +} + ############################################################## 1; @@ -1397,8 +1543,16 @@ sub readMap { Get

  • html
    - get <name> html
    + get <name> html
    Returns the mower area image as html code. For use in uiTable, TabletUI, Floorplan, readingsGroup, weblink etc.
  • + +
  • listInternalData
    + get <name> listInternalData
    + Lists some device internal data
  • + +
  • listErrorCodes
    + get <name> listErrorCodes
    + Lists API response status codes and mower error codes



@@ -1502,13 +1656,12 @@ sub readMap {
  • planner_nextStart - next start time
  • planner_restrictedReason - reason for parking NONE, WEEK_SCHEDULE, PARK_OVERRIDE, SENSOR, DAILY_LIMIT, FOTA, FROST
  • planner_overrideAction - reason for override a planned action NOT_ACTIVE, FORCE_PARK, FORCE_MOW
  • -
  • positions_lastLatitude - last known position (latitude)
  • -
  • positions_lastLongitude - last known position (longitude)
  • +
  • positions_lastLonLat - last known position (longitude latitude)
  • state - status of connection FHEM to Husqvarna Cloud API and device state(e.g. defined, authorization, authorized, connected, error, update)
  • status_statusTimestampOld - local time of second last change of the API content
  • settings_cuttingHeight - actual cutting height from API
  • settings_headlight - actual headlight mode from API
  • -
  • statistics_ChargingStationPositionXYn - calculated position of the carging station (longitude, latitude, number of datasets) during mower_activity PARKED_IN_CS and CHARGING
  • +
  • statistics_newGeoDataSets - number of new data sets between the last two different time stamps
  • statistics_numberOfChargingCycles - number of charging cycles
  • statistics_numberOfCollisions - number of collisions
  • statistics_totalChargingTime - total charging time in hours
  • @@ -1648,6 +1801,14 @@ sub readMap {
  • html
    get <name> html
    Gibt das Bild des Mäherbereiches html kodiert zurück, zur Verwendung in uiTable, TabletUI, Floorplan, readingsGroup, weblink usw.
  • + +
  • listErrorCodes
    + get <name> listErrorCodes
    + Listet die Statuscode der API-Anfrage und die Fehlercodes des Mähroboters auf.
  • + +
  • listInternalData
    + get <name> listErrorCodes
    + Listet einige Daten des FHEM-Gerätes auf.



  • @@ -1757,12 +1918,11 @@ sub readMap {
  • planner_nextStart - nächste Startzeit
  • planner_restrictedReason - Grund für Parken NONE, WEEK_SCHEDULE, PARK_OVERRIDE, SENSOR, DAILY_LIMIT, FOTA, FROST
  • planner_overrideAction - Grund für vorrangige Aktion NOT_ACTIVE, FORCE_PARK, FORCE_MOW
  • -
  • positions_lastLatitude - letzte bekannte Position (Breitengrad)
  • -
  • positions_lastLongitude - letzte bekannte Position (Längengrad)
  • -
  • state - Status der Verbindung des FHEM-Gerätes zur Husqvarna Cloud API (defined, authorization, authorized, connected, error, update).
  • +
  • positions_lastLonLat - letzte bekannte Position (Längengrad Breitengrad)
  • +
  • state - Status der Verbindung des FHEM-Gerätes zur Husqvarna Cloud API (defined, authentification, authentified, connected, error, update).
  • settings_cuttingHeight - aktuelle Schnitthöhe aus der API
  • settings_headlight - aktueller Scheinwerfermode aus der API
  • -
  • statistics_ChargingStationPositionXYn - berechnete Position der Ladestation mit den Werten Longitude, Latitude und Anzahl der verwendeten Datensätze wähend der Mower_activity PARKED_IN_CS und CHARGING
  • +
  • statistics_newGeoDataSets - Anzahl der neuen Datensätze zwischen den letzten zwei unterschiedlichen Zeitstempeln
  • statistics_numberOfChargingCycles - Anzahl der Ladezyklen
  • statistics_numberOfCollisions - Anzahl der Kollisionen
  • statistics_totalChargingTime - Gesamtladezeit in Stunden
  • diff --git a/fhem/FHEM/75_AutomowerConnectDevice.pm b/fhem/FHEM/75_AutomowerConnectDevice.pm index 161309e85..20f81196a 100644 --- a/fhem/FHEM/75_AutomowerConnectDevice.pm +++ b/fhem/FHEM/75_AutomowerConnectDevice.pm @@ -46,6 +46,8 @@ BEGIN { InternalVal IsDisabled Log3 + minNum + maxNum readingFnAttributes readingsBeginUpdate readingsBulkUpdate @@ -76,7 +78,6 @@ my $missingModul = ""; eval "use JSON;1" or $missingModul .= "JSON "; require HttpUtils; - use constant AUTHURL => 'https://api.authentication.husqvarnagroup.dev/v1'; use constant APIURL => 'https://api.amc.husqvarna.dev/v1'; @@ -141,6 +142,14 @@ sub Define{ mowerNumber => $mowerNumber, scaleToMeterLongitude => 67425, scaleToMeterLatitude => 108886, + minLon => 180, + maxLon => -180, + minLat => 90, + maxLat => -90, + imageHeight => 650, + imageWidthHeight => '350 650', + posMinMax => "-180 90\n 180 -90", + newdatasets => 0, MAP_PATH => '', MAP_MIME => '', MAP_CACHE => '', @@ -167,7 +176,7 @@ sub Define{ }, CHARGING => { arrayName => 'cspos', - maxLength => 500, + maxLength => 100, callFn => \&ChargingStationPosition }, LEAVING => { @@ -177,7 +186,7 @@ sub Define{ }, PARKED_IN_CS => { arrayName => 'cspos', - maxLength => 50, + maxLength => 100, callFn => \&ChargingStationPosition }, STOPPED_IN_GARDEN => { @@ -202,7 +211,7 @@ EOF $errortable = undef; $hash->{MODEL} = ''; - $attr{$name}{room} = $type if( !defined( $attr{$name}{room} ) ); + $attr{$name}{room} = 'AutomowerConnect' if( !defined( $attr{$name}{room} ) ); $attr{$name}{icon} = 'automower' if( !defined( $attr{$name}{icon} ) ); if (::AnalyzeCommandChain(undef,"version 75_AutomowerConnectDevice.pm noheader") =~ "^75_AutomowerConnectDevice.pm (.*)Z") { $hash->{VERSION}=$1; @@ -255,11 +264,11 @@ sub Notify { my $mowerhash = $hosthash->{helper}{mowers}[$mowerNumber]; my $myMower = dclone( $mowerhash ); - if ( defined ($hash->{helper}{mower}{id}) ){ + if ( defined ($hash->{helper}{mower}{id}) ){ # update dataset $hash->{helper}{mowerold} = dclone( $hash->{helper}{mower} ); - } else { + } else { # first data set $hash->{helper}{mowerold} = $myMower; @@ -272,9 +281,17 @@ sub Notify { $hash->{helper}{cspos} = [ dclone( $hash->{helper}{mowerold}{attributes}{positions}[0] ), dclone( $hash->{helper}{mowerold}{attributes}{positions}[1] ) ]; $hash->{helper}{cspos}[0]{statusTimestamp} = $hash->{helper}{mowerold}{attributes}{metadata}{statusTimestamp}; $hash->{helper}{cspos}[1]{statusTimestamp} = $hash->{helper}{mowerold}{attributes}{metadata}{statusTimestamp} - 600000; + + if ( AttrVal( $name, 'mapImageCoordinatesToRegister', '' ) eq '' ) { + posMinMax( $hash, $hash->{helper}{mowerold}{attributes}{positions} ); + } + } $hash->{helper}{mower} = $myMower; + # add alignment data set to the end + push( @{ $hash->{helper}{mower}{attributes}{positions} }, @{ dclone( $hash->{helper}{searchpos} ) } ); + my $storediff = $hash->{helper}{mower}{attributes}{metadata}{statusTimestamp} - $hash->{helper}{mowerold}{attributes}{metadata}{statusTimestamp}; if ($storediff) { AlignArray( $hash ); @@ -330,8 +347,7 @@ sub Notify { readingsBulkUpdateIfChanged($hash, $pref."_TimestampDiff", $storediff/1000 ); readingsBulkUpdateIfChanged($hash, $pref."_TimestampOld", FmtDateTime( $hash->{helper}{mowerold}{attributes}{metadata}{statusTimestamp}/1000 )); $pref = 'positions'; - readingsBulkUpdateIfChanged($hash, $pref."_lastLatitude", $hash->{helper}{mower}{attributes}{$pref}[0]{latitude} ); - readingsBulkUpdateIfChanged($hash, $pref."_lastLongitude", $hash->{helper}{mower}{attributes}{$pref}[0]{longitude} ); + readingsBulkUpdateIfChanged($hash, $pref."_lastLonLat", $hash->{helper}{mower}{attributes}{$pref}[0]{longitude} . ' ' . $hash->{helper}{mower}{attributes}{$pref}[0]{latitude} ); readingsBulkUpdateIfChanged($hash, 'state', 'connected' ); my @time = localtime(); @@ -500,9 +516,23 @@ sub Get { Log3 $name, 4, "$iam called with $setName " . ($setVal ? $setVal : ""); if ( $setName eq 'html' ) { + my $ret = '' . FW_detailFn( undef, $name, undef, undef) . ''; return $ret; + } elsif ( $setName eq 'listErrorCodes' ) { + + my $ret = listErrorCodes($hash); + return $ret; + + } elsif ( $setName eq 'listInternalData' ) { + + my $ret = listInternalData($hash); + return $ret; + } else { + + return "Unknown argument $setName, choose one of listInternalData:noArg listErrorCodes:noArg "; + } } @@ -518,9 +548,11 @@ sub Set { Log3 $name, 4, "$iam set called with $setName " . ($setVal ? $setVal : "") if ($setName !~ /^\?$/); if ( $setName eq 'chargingStationPositionToAttribute' ) { - my ($xm, $ym, $n) = split(/,\s/,ReadingsVal($name,'status_calcChargingStationPositionXYn','10.1165, 51.28, 0')); + + my $xm = $hash->{helper}{chargingStation}{longitude} // 10.1165; + my $ym = $hash->{helper}{chargingStation}{latitude} // 51.28; CommandAttr($hash,"$name chargingStationCoordinates $xm $ym"); - return undef; + return undef; ################ } elsif ( ReadingsVal( $name, 'state', 'defined' ) !~ /defined|initialized/ && $setName eq 'mowerScheduleToAttribute' ) { @@ -563,12 +595,12 @@ sub FW_detailFn { my ($FW_wname, $name, $room, $pageHash) = @_; # pageHash is set for summaryFn. my $hash = $defs{$name}; my $type = $hash->{TYPE}; - return undef if( IsDisabled($name) || !AttrVal($name, 'showMap', 1) ); + return '' if( IsDisabled($name) || !AttrVal($name, 'showMap', 1) ); if ( $hash->{helper} && $hash->{helper}{mower} && $hash->{helper}{mower}{attributes} && $hash->{helper}{mower}{attributes}{positions} && @{$hash->{helper}{mower}{attributes}{positions}} > 0 ) { my $img = "./fhem/$type/$name/map"; - my $zoom=AttrVal($name,"mapImageZoom",0.7); + my $zoom=AttrVal( $name,"mapImageZoom", 0.7 ); - AttrVal($name,"mapImageWidthHeight",'100 200') =~ /(\d+)\s(\d+)/; + AttrVal( $name,"mapImageWidthHeight", $hash->{helper}{imageWidthHeight} ) =~ /(\d+)\s(\d+)/; my ($picx,$picy) = ($1, $2); $picx=int($picx*$zoom); @@ -583,7 +615,7 @@ sub FW_detailFn { return $ret; } - return undef; + return ''; } ######################### @@ -595,20 +627,19 @@ sub FW_detailFn_Update { my @pos = (); my @posc = (); - # @pos = @{$hash->{helper}{mower}{attributes}{positions}}; # developement mode @pos = @{$hash->{helper}{areapos}}; # operational mode @posc =@{$hash->{helper}{cspos}}; # maybe operational mode my $img = "./fhem/$type/$name/map"; - AttrVal($name,"mapImageCoordinatesToRegister","0 90\n90 0") =~ /(\d*\.?\d+)\s(\d*\.?\d+)(\R|\s)(\d*\.?\d+)\s(\d*\.?\d+)/; + AttrVal( $name,"mapImageCoordinatesToRegister",$hash->{helper}{posMinMax} ) =~ /(-?\d*\.?\d+)\s(-?\d*\.?\d+)(\R|-?\s)(\d*\.?\d+)\s(-?\d*\.?\d+)/; my ( $lonlo, $latlo, $lonru, $latru ) = ($1, $2, $4, $5); - my $zoom = AttrVal($name,"mapImageZoom",0.7); + my $zoom = AttrVal( $name,"mapImageZoom",0.7 ); - AttrVal($name,"mapImageWidthHeight",'100 200') =~ /(\d+)\s(\d+)/; + AttrVal( $name,"mapImageWidthHeight", $hash->{helper}{imageWidthHeight} ) =~ /(\d+)\s(\d+)/; my ($picx,$picy) = ($1, $2); - AttrVal($name,'scaleToMeterXY', $hash->{helper}{scaleToMeterLongitude} . ' ' .$hash->{helper}{scaleToMeterLatitude}) =~ /(\d+)\s+(\d+)/; + AttrVal( $name,'scaleToMeterXY', $hash->{helper}{scaleToMeterLongitude} . ' ' .$hash->{helper}{scaleToMeterLatitude} ) =~ /(\d+)\s+(\d+)/; my $scalx = ($lonru-$lonlo) * $1; $picx = int($picx*$zoom); @@ -618,21 +649,19 @@ sub FW_detailFn_Update { if ( ($hash->{helper}{PARKED_IN_CS}{callFn} || $hash->{helper}{CHARGING}{callFn}) && (!$hash->{helper}{chargingStation}{longitude} || !$hash->{helper}{chargingStation}{latitude}) ) { no strict "refs"; - &{$hash->{helper}{PARKED_IN_CS}{callFn}}($hash); + &{$hash->{helper}{PARKED_IN_CS}{callFn}}( $hash ); use strict "refs"; } - my $csimgpos = AttrVal($name,"chargingStationImagePosition","right"); + my $csimgpos = AttrVal( $name,"chargingStationImagePosition","right" ); + my $xm = $hash->{helper}{chargingStation}{longitude} // 10.1165; + my $ym = $hash->{helper}{chargingStation}{latitude} // 51.28; - AttrVal($name,"chargingStationCoordinates",'10.1165 51.28') =~ /(\d*\.?\d+)\s(\d*\.?\d+)/; + AttrVal( $name,"chargingStationCoordinates","$xm $ym" ) =~ /(-?\d*\.?\d+)\s(-?\d*\.?\d+)/; my ($cslo,$csla) = ($1, $2); my $cslon = int(($lonlo-$cslo) * $picx / $mapx); my $cslat = int(($latlo-$csla) * $picy / $mapy); - # my $lon = int(($lonlo-$pos[0]{longitude}) * $picx / $mapx); - # my $lat = int(($latlo-$pos[0]{latitude}) * $picy / $mapy); - # my $lastx = int(($lonlo-$pos[$#pos]{longitude}) * $picx / $mapx); - # my $lasty = int(($latlo-$pos[$#pos]{latitude}) * $picy / $mapy); # MOWING PATH my $posxy = int(($lonlo-$pos[0]{longitude}) * $picx / $mapx).",".int(($latlo-$pos[0]{latitude}) * $picy / $mapy); @@ -758,10 +787,10 @@ sub Attr { if( $cmd eq "set" ) { - if ( AttrVal( $name,'mapImageCoordinatesToRegister', '' ) && $attrVal =~ /(\d*\.?\d+)\s(\d*\.?\d+)(\R|\s)(\d*\.?\d+)\s(\d*\.?\d+)/ ) { + if ( AttrVal( $name,'mapImageCoordinatesToRegister', '' ) && $attrVal =~ /(-?\d*\.?\d+)\s(-?\d*\.?\d+)(\R|\s)(-?\d*\.?\d+)\s(-?\d*\.?\d+)/ ) { my ( $x1, $y1, $x2, $y2 ) = ( $1, $2, $4, $5 ); - AttrVal( $name,'mapImageCoordinatesToRegister', '' ) =~ /(\d*\.?\d+)\s(\d*\.?\d+)(\R|\s)(\d*\.?\d+)\s(\d*\.?\d+)/; + AttrVal( $name,'mapImageCoordinatesToRegister', '' ) =~ /(-?\d*\.?\d+)\s(-?\d*\.?\d+)(\R|\s)(-?\d*\.?\d+)\s(-?\d*\.?\d+)/; my ( $lo1, $la1, $lo2, $la2 ) = ( $1, $2, $4, $5 ); my $scx = int( ( $x1 - $x2) / ( $lo1 - $lo2 ) ); my $scy = int( ( $y1 - $y2 ) / ( $la1 - $la2 ) ); @@ -774,7 +803,7 @@ sub Attr { } elsif( $cmd eq "del" ) { - Log3 $name, 3, "$iam $cmd $attrName and set default 0 9090 0"; + Log3 $name, 3, "$iam $cmd $attrName and set default"; } ########## @@ -782,12 +811,12 @@ sub Attr { if( $cmd eq "set" ) { - return "$iam $attrName has a wrong format use linewise pairs " unless($attrVal =~ /(\d*\.?\d+)\s(\d*\.?\d+)(\R|\s)(\d*\.?\d+)\s(\d*\.?\d+)/); + return "$iam $attrName has a wrong format use linewise pairs " unless( $attrVal =~ /(-?\d*\.?\d+)\s(-?\d*\.?\d+)(\R|\s)(-?\d*\.?\d+)\s(-?\d*\.?\d+)/ ); Log3 $name, 3, "$iam $cmd $attrName $attrVal"; } elsif( $cmd eq "del" ) { - Log3 $name, 3, "$iam $cmd $attrName and set default 0 9090 0"; + Log3 $name, 3, "$iam $cmd $attrName and set default"; } ########## @@ -795,12 +824,12 @@ sub Attr { if( $cmd eq "set" ) { - return "$iam $attrName has a wrong format use " unless($attrVal =~ /(\d*\.?\d+)\s(\d*\.?\d+)/); + return "$iam $attrName has a wrong format use " unless( $attrVal =~ /(-?\d*\.?\d+)\s(-?\d*\.?\d+)/ ); Log3 $name, 3, "$iam $cmd $attrName $attrVal"; } elsif( $cmd eq "del" ) { - Log3 $name, 3, "$iam $cmd $attrName and set default 10.1165 51.28"; + Log3 $name, 3, "$iam $cmd $attrName and set default"; } ########## @@ -808,12 +837,12 @@ sub Attr { if( $cmd eq "set" ) { - return "$iam $attrName has a wrong format use " unless($attrVal =~ /(\d+)\s(\d+)/); + return "$iam $attrName has a wrong format use " unless( $attrVal =~ /(\d+)\s(\d+)/ ); Log3 $name, 3, "$iam $cmd $attrName $attrVal"; } elsif( $cmd eq "del" ) { - Log3 $name, 3, "$iam $cmd $attrName and set default 100 200"; + Log3 $name, 3, "$iam $cmd $attrName and set default"; } ########## @@ -879,13 +908,13 @@ sub AlignArray { my $activity = $hash->{helper}{mower}{attributes}{mower}{activity}; my $arrayName = $hash->{helper}{$activity}{arrayName}; my $maxLength = $hash->{helper}{$activity}{maxLength}; - for ( $i = 0; $i < $poslen-1; $i++ ) { + for ( $i = 0; $i < $poslen-3; $i++ ) { # 3 instead of 1 due to new alignment if ( $searchposlon[0] == $hash->{helper}{mower}{attributes}{positions}[ $i ]{longitude} && $searchposlat[0] == $hash->{helper}{mower}{attributes}{positions}[ $i ]{latitude} && $searchposlon[1] == $hash->{helper}{mower}{attributes}{positions}[ $i+1 ]{longitude} - && $searchposlat[1] == $hash->{helper}{mower}{attributes}{positions}[ $i+1 ]{latitude} - || $i == $poslen-2 ) { - $i++ if ($i == $poslen-2); + && $searchposlat[1] == $hash->{helper}{mower}{attributes}{positions}[ $i+1 ]{latitude} ) { + # || $i == $poslen-2 ) { # not nessecary due to new alignment + # $i++ if ( $i == $poslen-2 ); # not nessecary due to new alignment # timediff per step my $dt = 0; $dt = int(($hash->{helper}{mower}{attributes}{metadata}{statusTimestamp} - $hash->{helper}{$arrayName}[0]{statusTimestamp})/$i) if ($i); @@ -897,15 +926,27 @@ sub AlignArray { unshift (@{$hash->{helper}{searchpos}}, dclone($hash->{helper}{mower}{attributes}{positions}[ $k ]) ); pop (@{$hash->{helper}{searchpos}}) if (@{$hash->{helper}{searchpos}} > $searchlen); + + my @temp = (); + unshift ( @temp, dclone( $hash->{helper}{mower}{attributes}{positions}[ $k ] ) ); + posMinMax( $hash, \@temp ); + } + #callFn if present if ($hash->{helper}{$activity}{callFn}) { + $hash->{helper}{$activity}{cnt} = $i; no strict "refs"; &{$hash->{helper}{$activity}{callFn}}($hash); use strict "refs"; + } + + $hash->{helper}{newdatasets} = $i; + readingsSingleUpdate($hash, "statistics_newGeoDataSets", $i, 1); last; + } } } @@ -921,9 +962,8 @@ sub ChargingStationPosition { my $ym = 0; map { $ym += $_->{latitude} } @{$hash->{helper}{cspos}}; $ym = $ym/$n; - $hash->{helper}{chargingStation}{longitude} = $xm; - $hash->{helper}{chargingStation}{latitude} = $ym; - readingsSingleUpdate($hash, "statistics_ChargingStationPositionXYn", (int($xm * 10000000 + 0.5) / 10000000).", ".(int($ym * 10000000 + 0.5) / 10000000).", ".$n, 0); + $hash->{helper}{chargingStation}{longitude} = int($xm * 10000000 + 0.5) / 10000000; + $hash->{helper}{chargingStation}{latitude} = int($ym * 10000000 + 0.5) / 10000000; return undef; } @@ -1031,6 +1071,114 @@ sub readMap { } } +######################### +sub posMinMax { + my ($hash, $poshash) = @_; + my $minLon = $hash->{helper}{minLon}; + my $maxLon = $hash->{helper}{maxLon}; + my $minLat = $hash->{helper}{minLat}; + my $maxLat = $hash->{helper}{maxLat}; + + for ( @{$poshash} ) { + $minLon = minNum( $minLon,$_->{longitude} ); + $maxLon = maxNum( $maxLon,$_->{longitude} ); + $minLat = minNum( $minLat,$_->{latitude} ); + $maxLat = maxNum( $maxLat,$_->{latitude} ); + } + + $hash->{helper}{minLon} = $minLon; + $hash->{helper}{maxLon} = $maxLon; + $hash->{helper}{minLat} = $minLat; + $hash->{helper}{maxLat} = $maxLat; + $hash->{helper}{posMinMax} = "$minLon $maxLat\n$maxLon $minLat"; + $hash->{helper}{imageWidthHeight} = int($hash->{helper}{imageHeight} * ($maxLon-$minLon) / ($maxLat-$minLat)) . ' ' . $hash->{helper}{imageHeight} if ($maxLon-$minLon); + + return undef; +} + +######################### +sub listInternalData { + my ( $hash ) = @_; + my $name = $hash->{NAME}; + my $rowCount = 1; + my $ret = ''; + $ret .= ''; + + $hash->{helper}{posMinMax} =~ /(-?\d*\.?\d+)\s(-?\d*\.?\d+)(\R|-?\s)(\d*\.?\d+)\s(-?\d*\.?\d+)/; + + my $xm = $hash->{helper}{chargingStation}{longitude} // 0; + my $ym = $hash->{helper}{chargingStation}{latitude} // 0; + my $csnr = scalar @{$hash->{helper}{cspos}}; + my $csnrmax = $hash->{helper}{PARKED_IN_CS}{maxLength}; + my $arnr = 0; + $arnr = scalar @{$hash->{helper}{areapos}} if( scalar @{$hash->{helper}{areapos}} > 2 ); + my $arnrmax = $hash->{helper}{MOWING}{maxLength}; + + $ret .= ''; + $ret .= ''; + $ret .= ''; + + $ret .= '
    Calculated Coordinates For Automatic Registration
    Data Sets ( max )  Corner Longitude Latitude
    ' . ($csnr + $arnr) . ' ( ' . ($csnrmax + $arnrmax) . ' )  Upper Left ' . $1 . ' ' . $2 . '
    Lower Right ' . $4 . ' ' . $5 . '

    '; + $ret .= ''; + $ret .= ''; + + $ret .= ''; + $ret .= ''; + + $ret .= '
    Calculated Charging Station Coordinates
    Data Sets (max)  Longitude  Latitude 
    ' . $csnr . ' ( ' . $csnrmax . ' )  ' . $xm . ' ' . $ym . ' 

    '; + $ret .= ''; + $ret .= ''; + + $ret .= ''; + $ret .= ''; + $ret .= ''; + + $ret .= '
    Way Point Stacks
    Used For Action  Stack Name  Current Size  Max Size 
    PARKED_IN_CS, CHARGING  cspos  ' . $csnr . ' ' . $csnrmax . ' 
    MOWING  areapos  ' . $arnr . ' ' . $arnrmax . ' 
    '; + if ( $hash->{TYPE} eq 'AutomowerConnect' ) { + + $ret .= '

    '; + $ret .= ''; + + $ret .= ''; + + $ret .= '
    Access Token ( expires: ' . ReadingsVal( $name, 'api_token_expires', 'none') . ' )
    ' . ReadingsVal( $name, '.access_token', 'none') . '
    '; + + } + $ret .= ''; + + return $ret; +} + +######################### +sub listErrorCodes { + my ( $hash ) = @_; + my $rowCount = 1; + my %ec = (); + my $ec = \%ec; + for ( keys %{$hash->{helper}{errortable}} ) { + $ec->{sprintf("%03d",$_)} = $hash->{helper}{errortable}{$_} ; + } + my $ret = ''; + $ret .= ''; + $ret .= ''; + $ret .= ''; + $ret .= '
    API-Response Status Codes
    200, 201, 202
    204
    response o.k.
    400, 401, 402
    403, 404, 415
    500, 503
    error, detailed information see logfile

    '; + $ret .= ''; + for (sort keys %{$ec}) { + $ret .= '{$_}; + $ret .= ''; + $rowCount++; + } + $ret .= '
    Mower Error Table
    '; + + return $ret; +} + ############################################################## 1; @@ -1130,6 +1278,14 @@ sub readMap {

  • html
    get <name> html
    Returns the mower area image as html code. For use in uiTable, TabletUI, Floorplan, readingsGroup, weblink etc.
  • + +
  • listInternalData
    + get <name> listInternalData
    + Lists some device internal data
  • + +
  • listErrorCodes
    + get <name> listErrorCodes
    + Lists API response status codes and mower error codes



  • @@ -1227,13 +1383,12 @@ sub readMap {
  • planner_nextStart - next start time
  • planner_restrictedReason - reason for parking NONE, WEEK_SCHEDULE, PARK_OVERRIDE, SENSOR, DAILY_LIMIT, FOTA, FROST
  • planner_overrideAction - reason for override a planned action NOT_ACTIVE, FORCE_PARK, FORCE_MOW
  • -
  • positions_lastLatitude - last known position (latitude)
  • -
  • positions_lastLongitude - last known position (longitude)
  • -
  • state - status of connection FHEM to Husqvarna Cloud API and device state(e.g. defined, authorization, authorized, connected, error, update)
  • +
  • positions_lastLonLat - last known position (longitude latitude)
  • +
  • state - status of connection FHEM to Husqvarna Cloud API and device state(e.g. defined, connected, error)
  • status_statusTimestampOld - local time of second last change of the API content
  • settings_cuttingHeight - actual cutting height from API
  • settings_headlight - actual headlight mode from API
  • -
  • statistics_ChargingStationPositionXYn - calculated position of the carging station (longitude, latitude, number of datasets) during mower_activity PARKED_IN_CS and CHARGING
  • +
  • statistics_newGeoDataSets - number of new data sets between the last two different time stamps
  • statistics_numberOfChargingCycles - number of charging cycles
  • statistics_numberOfCollisions - number of collisions
  • statistics_totalChargingTime - total charging time in hours
  • @@ -1350,6 +1505,14 @@ sub readMap {
  • html
    get <name> html
    Gibt das Bild des Mäherbereiches html kodiert zurück, zur Verwendung in uiTable, TabletUI, Floorplan, readingsGroup, weblink usw.
  • + +
  • listErrorCodes
    + get <name> listErrorCodes
    + Listet die Statuscode der API-Anfrage und die Fehlercodes des Mähroboters auf.
  • + +
  • listInternalData
    + get <name> listErrorCodes
    + Listet einige Daten des FHEM-Gerätes auf.



  • @@ -1457,12 +1620,11 @@ sub readMap {
  • planner_nextStart - nächste Startzeit
  • planner_restrictedReason - Grund für Parken NONE, WEEK_SCHEDULE, PARK_OVERRIDE, SENSOR, DAILY_LIMIT, FOTA, FROST
  • planner_overrideAction - Grund für vorrangige Aktion NOT_ACTIVE, FORCE_PARK, FORCE_MOW
  • -
  • positions_lastLatitude - letzte bekannte Position (Breitengrad)
  • -
  • positions_lastLongitude - letzte bekannte Position (Längengrad)
  • -
  • state - Status der Verbindung des FHEM-Gerätes zur Husqvarna Cloud API (defined, connected).
  • +
  • positions_lastLonLat - letzte bekannte Position (Längengrad Breitengrad)
  • +
  • state - Status der Verbindung des FHEM-Gerätes zur Husqvarna Cloud API (defined, connected, error).
  • settings_cuttingHeight - aktuelle Schnitthöhe aus der API
  • settings_headlight - aktueller Scheinwerfermode aus der API
  • -
  • statistics_ChargingStationPositionXYn - berechnete Position der Ladestation mit den Werten Longitude, Latitude und Anzahl der verwendeten Datensätze wähend der Mower_activity PARKED_IN_CS und CHARGING
  • +
  • statistics_newGeoDataSets - Anzahl der neuen Datensätze zwischen den letzten zwei unterschiedlichen Zeitstempeln
  • statistics_numberOfChargingCycles - Anzahl der Ladezyklen
  • statistics_numberOfCollisions - Anzahl der Kollisionen
  • statistics_totalChargingTime - Gesamtladezeit in Stunden