diff --git a/fhem/CHANGED b/fhem/CHANGED index 37077452a..2bbc7a6e1 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: AutomowerConnect-family: error message and location on map + various settings for map design, mowerpath for all activities - feature: 72_FRITZBOX: neue Attr. deviceInfo / enableWanInfo (s. commandRef) Fehlerkorrekturen - bugfix: 72_FB_CALLMONITOR: RegEx für Suche dasOertliche angepasst diff --git a/fhem/FHEM/74_AutomowerConnect.pm b/fhem/FHEM/74_AutomowerConnect.pm index fe4f12a78..251d3aef5 100644 --- a/fhem/FHEM/74_AutomowerConnect.pm +++ b/fhem/FHEM/74_AutomowerConnect.pm @@ -25,6 +25,7 @@ ################################################################################ package FHEM::AutomowerConnect; +my $cvsid = '$Id$'; use strict; use warnings; use POSIX; @@ -91,14 +92,13 @@ use constant APIURL => 'https://api.amc.husqvarna.dev/v1'; sub Initialize() { my ($hash) = @_; - $hash->{DefFn} = \&Define; - $hash->{SetFn} = \&Set; - $hash->{AttrFn} = \&Attr; + $hash->{DefFn} = \&FHEM::Devices::AMConnect::Common::Define; $hash->{GetFn} = \&FHEM::Devices::AMConnect::Common::Get; $hash->{UndefFn} = \&FHEM::Devices::AMConnect::Common::Undefine; $hash->{DeleteFn} = \&FHEM::Devices::AMConnect::Common::Delete; $hash->{RenameFn} = \&FHEM::Devices::AMConnect::Common::Rename; $hash->{FW_detailFn}= \&FHEM::Devices::AMConnect::Common::FW_detailFn; + $hash->{SetFn} = \&Set; $hash->{AttrFn} = \&Attr; $hash->{AttrList} = "interval " . "disable:1,0 " . @@ -109,6 +109,8 @@ sub Initialize() { "mapImageCoordinatesToRegister:textField-long " . "mapImageCoordinatesUTM:textField-long " . "mapImageZoom " . + "mapBackgroundColor " . + "mapDesignAttributes:textField-long " . "showMap:1,0 " . "chargingStationCoordinates " . "chargingStationImagePosition:left,top,right,bottom,center " . @@ -126,214 +128,6 @@ sub Initialize() { } -############################################################## -# -# DEFINE -# -############################################################## - -sub Define{ - my ( $hash, $def ) = @_; - my @val = split( "[ \t]+", $def ); - my $name = $val[0]; - my $type = $val[1]; - my $iam = "$type $name Define:"; - - return "$iam too few parameters: define $type []" if( @val < 3 ) ; - return "$iam Cannot define $type device. Perl modul $missingModul is missing." if ( $missingModul ); - - my $client_id =$val[2]; - my $mowerNumber = $val[3] ? $val[3] : 0; - - %$hash = (%$hash, - helper => { - passObj => FHEM::Core::Authentication::Passwords->new($type), - interval => 600, - mowerNumber => $mowerNumber, - scaleToMeterLongitude => 67425, - scaleToMeterLatitude => 108886, - minLon => 180, - maxLon => -180, - minLat => 90, - maxLat => -90, - imageHeight => 650, - imageWidthHeight => '350 650', - posMinMax => "-180 90\n180 -90", - newdatasets => 0, - client_id => $client_id, - grant_type => 'client_credentials', - MAP_PATH => '', - MAP_MIME => '', - MAP_CACHE => '', - cspos => [], - areapos => [], - searchpos => [], - UNKNOWN => { - arrayName => '', - maxLength => 0, - callFn => '' - }, - NOT_APPLICABLE => { - arrayName => '', - maxLength => 0, - callFn => '' - }, - MOWING => { - arrayName => 'areapos', - maxLength => 500, - maxLengthDefault => 500, - callFn => \&FHEM::Devices::AMConnect::Common::AreaStatistics - }, - GOING_HOME => { - arrayName => '', - maxLength => 0, - callFn => '' - }, - CHARGING => { - arrayName => 'cspos', - maxLength => 100, - callFn => \&FHEM::Devices::AMConnect::Common::ChargingStationPosition - }, - LEAVING => { - arrayName => '', - maxLength => 0, - callFn => '' - }, - PARKED_IN_CS => { - arrayName => 'cspos', - maxLength => 100, - callFn => \&FHEM::Devices::AMConnect::Common::ChargingStationPosition - }, - STOPPED_IN_GARDEN => { - arrayName => '', - maxLength => 0, - callFn => '' - }, - statistics => { - currentSpeed => 0, - currentDayTrack => 0, - currentDayArea => 0, - lastDayTrack => 0, - lastDayArea => 0, - currentWeekTrack => 0, - currentWeekArea => 0, - lastWeekTrack => 0, - lastWeekArea => 0 - } - } - ); - - $hash->{MODEL} = ''; - $attr{$name}{room} = $type if( !defined( $attr{$name}{room} ) ); - $attr{$name}{icon} = 'automower' if( !defined( $attr{$name}{icon} ) ); - my ($modname) = __FILE__ =~ /(\d\d_.*\.pm)/; - - if (::AnalyzeCommandChain(undef,"version $modname noheader") =~ "^$modname (.*)Z") { - $hash->{VERSION}=$1; - } - - ::FHEM::Devices::AMConnect::Common::AddExtension( $name, \&FHEM::Devices::AMConnect::Common::GetMap, "$type/$name/map" ); - - if( $hash->{helper}->{passObj}->getReadPassword($name) ) { - - RemoveInternalTimer($hash); - InternalTimer( gettimeofday() + 2, \&APIAuth, $hash, 1); - InternalTimer( gettimeofday() + 30, \&FHEM::Devices::AMConnect::Common::readMap, $hash, 0); - readingsSingleUpdate( $hash, 'state', 'defined', 1 ); - - } else { - - readingsSingleUpdate( $hash, 'state', 'defined - client_secret missing', 1 ); - - } - - return undef; - -} - - -######################### -sub AlignArray { - my ($hash) = @_; - my $name = $hash->{NAME}; - my $activity = $hash->{helper}{mower}{attributes}{mower}{activity}; - my $arrayName = $hash->{helper}{$activity}{arrayName}; - my $searchlen = 2; - my $i = 0; - my @temp = (); - - if ( isGoodActivity( $hash ) ) { - - my $k = -1; - my $poslen = @{$hash->{helper}{mower}{attributes}{positions}}; - my @searchposlon = ($hash->{helper}{searchpos}[0]{longitude}, $hash->{helper}{searchpos}[1]{longitude}); - my @searchposlat = ($hash->{helper}{searchpos}[0]{latitude}, $hash->{helper}{searchpos}[1]{latitude}); - my $maxLength = $hash->{helper}{$activity}{maxLength}; - for ( $i = 0; $i < $poslen-2; $i++ ) { # -2 due to 2 alignment data sets at the end - 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} ) { - # timediff per step - my $dt = 0; - $dt = int( ( $hash->{helper}{mower}{attributes}{metadata}{statusTimestamp} - $hash->{helper}{$arrayName}[ 0 ]{statusTimestamp} ) / $i) if ( $i && @{ $hash->{helper}{$arrayName} } ); - for ($k=$i-1;$k>-1;$k--) { - - } - for ($k=$i-1;$k>-1;$k--) { - - if ( @{ $hash->{helper}{$arrayName} } ) { - - unshift ( @{$hash->{helper}{$arrayName}}, dclone( $hash->{helper}{mower}{attributes}{positions}[ $k ] ) ); - - } else { - - $hash->{helper}{$arrayName}[ 0 ] = dclone( $hash->{helper}{mower}{attributes}{positions}[ $k ] ); - - } - - pop ( @{ $hash->{helper}{$arrayName} } ) if ( @{ $hash->{helper}{$arrayName} } > $maxLength ); - $hash->{helper}{$arrayName}[ 0 ]{statusTimestamp} = $hash->{helper}{mower}{attributes}{metadata}{statusTimestamp} - $dt * $k; - - push ( @temp, dclone( $hash->{helper}{mower}{attributes}{positions}[ $k ] ) ); - - } - - ::FHEM::Devices::AMConnect::Common::posMinMax( $hash, \@temp ); - #callFn if present - if ( $hash->{helper}{$activity}{callFn} && @{ $hash->{helper}{$arrayName} } > 1 ) { - - $hash->{helper}{$activity}{cnt} = $i; - no strict "refs"; - &{$hash->{helper}{$activity}{callFn}}($hash); - use strict "refs"; - - } - - last; - - } - - } - - } - - $hash->{helper}{newdatasets} = $i; - $hash->{helper}{searchpos} = [ dclone( $hash->{helper}{mower}{attributes}{positions}[ 0 ] ), dclone( $hash->{helper}{mower}{attributes}{positions}[ 1 ] ) ]; - return undef; - -} - -######################### -sub isGoodActivity { - - my ( $hash ) = @_; - my $act = $hash->{helper}{mower}{attributes}{mower}{activity}; - my $ret = $hash->{helper}{$act}{arrayName} && $act eq $hash->{helper}{mowerold}{attributes}{mower}{activity}; - return $ret; - -} - ############################################################## # # API AUTHENTICATION @@ -346,6 +140,8 @@ sub APIAuth { my $type = $hash->{TYPE}; my $iam = "$type $name APIAuth:"; my $interval = $hash->{helper}{interval}; + ( $hash->{VERSION} ) = $cvsid =~ /\.pm (.*)Z/ if ( !$hash->{VERSION} ); + if ( IsDisabled($name) ) { readingsSingleUpdate($hash,'state','disabled',1) if( ReadingsVal($name,'state','') ne 'disabled' ); @@ -397,7 +193,7 @@ sub APIAuthResponse { my $hash = $param->{hash}; my $name = $hash->{NAME}; my $type = $hash->{TYPE}; - my $statuscode = $param->{code}; + my $statuscode = $param->{code} // ''; my $interval = $hash->{helper}{interval}; my $iam = "$type $name APIAuthResponse:"; @@ -438,6 +234,7 @@ sub APIAuthResponse { } else { + readingsSingleUpdate( $hash, 'state', "error statuscode $statuscode", 1 ); Log3 $name, 1, "\n$iam\n\$statuscode [$statuscode]\n\$err [$err],\n\$data [$data]\n\$param->url $param->{url}"; @@ -533,8 +330,8 @@ sub getMowerResponse { } else { # first data set $hash->{helper}{mowerold} = dclone( $hash->{helper}{mowers}[$mowerNumber] ); - $hash->{helper}{searchpos} = [ dclone( $hash->{helper}{mowerold}{attributes}{positions}[0] ), dclone( $hash->{helper}{mowerold}{attributes}{positions}[1] ) ]; + $hash->{helper}{timestamps}[ 0 ] = $hash->{helper}{mowerold}{attributes}{metadata}{statusTimestamp}; if ( AttrVal( $name, 'mapImageCoordinatesToRegister', '' ) eq '' ) { ::FHEM::Devices::AMConnect::Common::posMinMax( $hash, $hash->{helper}{mowerold}{attributes}{positions} ); @@ -543,15 +340,16 @@ sub getMowerResponse { } $hash->{helper}{mower} = dclone( $hash->{helper}{mowers}[$mowerNumber] ); - # add alignment data set to the end + # add alignment data set (last matched search positions) to the end push( @{ $hash->{helper}{mower}{attributes}{positions} }, @{ dclone( $hash->{helper}{searchpos} ) } ); $hash->{helper}{newdatasets} = 0; my $storediff = $hash->{helper}{mower}{attributes}{metadata}{statusTimestamp} - $hash->{helper}{mowerold}{attributes}{metadata}{statusTimestamp}; if ($storediff) { + # collect timestamps for analysis + unshift ( @{ $hash->{helper}{timestamps} }, $hash->{helper}{mower}{attributes}{metadata}{statusTimestamp} ); - ::FHEM::Devices::AMConnect::Common::AlignArray( $hash ); #release - # AlignArray( $hash ); # developement + ::FHEM::Devices::AMConnect::Common::AlignArray( $hash ); ::FHEM::Devices::AMConnect::Common::FW_detailFn_Update ($hash) if (AttrVal($name,'showMap',1)); } @@ -568,7 +366,7 @@ sub getMowerResponse { readingsBulkUpdateIfChanged($hash, $pref.'_commandStatus', 'cleared' ); my $tstamp = $hash->{helper}{mower}{attributes}{$pref}{errorCodeTimestamp}; - my $timestamp = FmtDateTimeGMT($tstamp/1000); + my $timestamp = ::FHEM::Devices::AMConnect::Common::FmtDateTimeGMT($tstamp/1000); readingsBulkUpdateIfChanged($hash, $pref."_errorCodeTimestamp", $tstamp ? $timestamp : '-' ); my $errc = $hash->{helper}{mower}{attributes}{$pref}{errorCode}; @@ -581,14 +379,16 @@ sub getMowerResponse { 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 = FmtDateTimeGMT($tstamp/1000); - readingsBulkUpdateIfChanged($hash, "planner_nextStart", $tstamp ? $timestamp : '-' ); + $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} ); @@ -596,7 +396,8 @@ sub getMowerResponse { readingsBulkUpdateIfChanged($hash, $pref."_headlight", $hash->{helper}->{mower}{attributes}{$pref}{headlight}{mode} ); readingsBulkUpdateIfChanged($hash, $pref."_cuttingHeight", $hash->{helper}->{mower}{attributes}{$pref}{cuttingHeight} ); $pref = 'status'; - readingsBulkUpdateIfChanged($hash, $pref."_connected", ( $hash->{helper}{mower}{attributes}{metadata}{connected} ? 'CONNECTED' : 'OFFLINE') ); + 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 )); @@ -615,7 +416,7 @@ sub getMowerResponse { $hash->{helper}{statistics}{currentDayTrack} = 0; $hash->{helper}{statistics}{currentDayArea} = 0; # do on mondays - if ( $time[6] == 1 && $secs <= $interval ) { + if ( $time[6] == 1 ) { $hash->{helper}{statistics}{lastWeekTrack} = $hash->{helper}{statistics}{currentWeekTrack}; $hash->{helper}{statistics}{lastWeekArea} = $hash->{helper}{statistics}{currentWeekArea}; @@ -646,120 +447,7 @@ sub getMowerResponse { } -############################################################## -# -# SEND COMMAND -# -############################################################## - -sub sendCMD { - my ($hash,@cmd) = @_; - my $name = $hash->{NAME}; - my $type = $hash->{TYPE}; - my $iam = "$type $name sendCMD:"; - - if ( IsDisabled($name) ) { - Log3 $name, 3, "$iam disabled"; - return undef - } - - my $client_id = $hash->{helper}->{client_id}; - my $token = ReadingsVal($name,".access_token",""); - my $provider = ReadingsVal($name,".provider",""); - my $mower_id = $hash->{helper}{mower}{id}; - - my $json = ''; - my $post = ''; - - -my $header = "Accept: application/vnd.api+json\r\nX-Api-Key: ".$client_id."\r\nAuthorization: Bearer " . $token . "\r\nAuthorization-Provider: " . $provider . "\r\nContent-Type: application/vnd.api+json"; - - - if ($cmd[0] eq "ParkUntilFurtherNotice") { $json = '{"data":{"type":"'.$cmd[0].'"}}'; $post = 'actions' } - elsif ($cmd[0] eq "ParkUntilNextSchedule") { $json = '{"data": {"type":"'.$cmd[0].'"}}'; $post = 'actions' } - elsif ($cmd[0] eq "ResumeSchedule") { $json = '{"data": {"type":"'.$cmd[0].'"}}'; $post = 'actions' } - elsif ($cmd[0] eq "Pause") { $json = '{"data": {"type":"'.$cmd[0].'"}}'; $post = 'actions' } - elsif ($cmd[0] eq "Park") { $json = '{"data": {"type":"'.$cmd[0].'","attributes":{"duration":'.$cmd[1].'}}}'; $post = 'actions' } - elsif ($cmd[0] eq "Start") { $json = '{"data": {"type":"'.$cmd[0].'","attributes":{"duration":'.$cmd[1].'}}}'; $post = 'actions' } - elsif ($cmd[0] eq "headlight") { $json = '{"data": {"type":"settings","attributes":{"'.$cmd[0].'": {"mode": "'.$cmd[1].'"}}}}'; $post = 'settings' } - elsif ($cmd[0] eq "cuttingHeight") { $json = '{"data": {"type":"settings","attributes":{"'.$cmd[0].'": '.$cmd[1].'}}}'; $post = 'settings' } - elsif ($cmd[0] eq "sendScheduleFromAttributeToMower" && AttrVal( $name, 'mowerSchedule', '')) { - - my $perl = eval { decode_json (AttrVal( $name, 'mowerSchedule', '')) }; - if ($@) { - return "$iam decode error: $@ \n $perl"; - } - my $jsonSchedule = eval { encode_json ($perl) }; - if ($@) { - return "$iam encode error: $@ \n $json"; - } - $json = '{"data":{"type": "calendar","attributes":{"tasks":'.$jsonSchedule.'}}}'; - $post = 'calendar'; - } - - Log3 $name, 5, "$iam $header \n $cmd[0] \n $json"; - - ::HttpUtils_NonblockingGet({ - url => APIURL . "/mowers/". $mower_id . "/".$post, - timeout => 20, - hash => $hash, - method => "POST", - header => $header, - data => $json, - callback => \&CMDResponse, - }); - -} - ######################### -sub CMDResponse { - my ($param, $err, $data) = @_; - my $hash = $param->{hash}; - my $name = $hash->{NAME}; - my $type = $hash->{TYPE}; - my $statuscode = $param->{code}; - my $iam = "$type $name CMDResponse:"; - - Log3 $name, 1, "\ndebug $iam \n\$statuscode [$statuscode]\n\$err [$err],\n \$data [$data] \n\$param->url $param->{url}" if ( AttrVal($name, 'debug', '') ); - - if( !$err && $statuscode == 202 && $data ) { - - my $result = eval { decode_json($data) }; - if ($@) { - - Log3( $name, 2, "$iam - JSON error while request: $@"); - - } else { - - $hash->{helper}{CMDResponse} = $result; - if ($result->{data}) { - - Log3 $name, 5, $data; - if ( ref ($result->{data}) eq 'ARRAY') { - - $hash->{helper}->{mower_commandStatus} = 'OK - '. $result->{data}[0]{type}; - - } else { - - $hash->{helper}->{mower_commandStatus} = 'OK - '. $result->{data}{type}; - - } - - readingsSingleUpdate($hash, 'mower_commandStatus', $hash->{helper}->{mower_commandStatus} ,1); - return undef; - - } - - } - - } - - readingsSingleUpdate($hash, 'mower_commandStatus', "ERROR statuscode $statuscode" ,1); - Log3 $name, 2, "\n$iam \n\$statuscode [$statuscode]\n\$err [$err],\n\$data [$data]\n\$param->url $param->{url}"; - return undef; -} - - sub Set { my ($hash,@val) = @_; my $type = $hash->{TYPE}; @@ -781,7 +469,13 @@ sub Set { my $xm = $hash->{helper}{chargingStation}{longitude} // 10.1165; my $ym = $hash->{helper}{chargingStation}{latitude} // 51.28; - CommandAttr($hash,"$name chargingStationCoordinates $xm $ym"); + CommandAttr( $hash, "$name chargingStationCoordinates $xm $ym" ); + return undef; + + } elsif ( $setName eq 'defaultDesignAttributesToAttribute' ) { + + my $design = $hash->{helper}{mapdesign}; + CommandAttr( $hash, "$name mapDesignAttributes $design" ); return undef; } elsif ( ReadingsVal( $name, 'state', 'defined' ) !~ /defined|initialized|authentification|authenticated|update/ && $setName eq 'mowerScheduleToAttribute' ) { @@ -806,13 +500,17 @@ sub Set { } elsif ( ReadingsVal( $name, 'state', 'defined' ) !~ /defined|initialized|authentification|authenticated|update/ && $setName =~ /^(Start|Park|cuttingHeight)$/ ) { if ( $setVal =~ /^(\d+)$/) { - sendCMD($hash ,$setName, $setVal); + + ::FHEM::Devices::AMConnect::Common::CMD($hash ,$setName, $setVal); return undef; + } } elsif ( ReadingsVal( $name, 'state', 'defined' ) !~ /defined|initialized|authentification|authenticated|update/ && $setName eq 'headlight' ) { if ( $setVal =~ /^(ALWAYS_OFF|ALWAYS_ON|EVENING_ONLY|EVENING_AND_NIGHT)$/) { - sendCMD($hash ,$setName, $setVal); + + ::FHEM::Devices::AMConnect::Common::CMD($hash ,$setName, $setVal); + return undef; } @@ -829,12 +527,14 @@ sub Set { return undef; } elsif (ReadingsVal( $name, 'state', 'defined' ) !~ /defined|initialized|authentification|authenticated|update/ && $setName =~ /ParkUntilFurtherNotice|ParkUntilNextSchedule|Pause|ResumeSchedule|sendScheduleFromAttributeToMower/) { - sendCMD($hash,$setName); + + ::FHEM::Devices::AMConnect::Common::CMD($hash,$setName); return undef; + } my $ret = " getNewAccessToken:noArg ParkUntilFurtherNotice:noArg ParkUntilNextSchedule:noArg Pause:noArg Start:selectnumbers,60,60,600,0,lin Park:selectnumbers,60,60,600,0,lin ResumeSchedule:noArg getUpdate:noArg client_secret "; $ret .= "chargingStationPositionToAttribute:noArg headlight:ALWAYS_OFF,ALWAYS_ON,EVENING_ONLY,EVENING_AND_NIGHT cuttingHeight:1,2,3,4,5,6,7,8,9 mowerScheduleToAttribute:noArg "; - $ret .= "sendScheduleFromAttributeToMower:noArg "; + $ret .= "sendScheduleFromAttributeToMower:noArg defaultDesignAttributesToAttribute:noArg "; return "Unknown argument $setName, choose one of".$ret; } @@ -1021,11 +721,6 @@ sub Attr { return undef; } -######################### -sub FmtDateTimeGMT { - my $ret = POSIX::strftime( "%F %H:%M:%S", gmtime( shift ) ); -} - ############################################################## @@ -1207,7 +902,15 @@ __END__
  • mapImageZoom
    attr <name> mapImageZoom <zoom factor>
    - Zoom of a raster image for an area the mower path has to be drawn to. Default: 0.5
  • + Zoom of a raster image for an area the mower path has to be drawn to. + +
  • mapBackgroundColor
    + attr <name> mapBackgroundColor <background-color>
    + The value is used as background-color.
  • + +
  • mapDesignAttributes
    + attr <name> mapDesignAttributes <complete list of design-attributes>
    + Load the list of attributes by set <name> defaultDesignAttributesToAttribute to change its values
  • mapImageCoordinatesToRegister
    attr <name> mapImageCoordinatesToRegister <upper left longitude><space><upper left latitude><line feed><lower right longitude><space><lower right latitude>
    @@ -1470,6 +1173,14 @@ __END__ attr <name> mapImageZoom <zoom factor>
    Zoomfaktor zur Salierung des Bildes auf das Pfad, Anfangs- u. Endpunkte gezeichnet werden. Standard: 0.5
  • +
  • mapBackgroundColor
    + attr <name> mapBackgroundColor <color value>
    + Der Wert wird als Hintergrungfarbe benutzt.
  • + +
  • mapDesignAttributes
    + attr <name> mapDesignAttributes <complete list of design-attributes>
    + Lade die Attributliste mit set <name> defaultDesignAttributesToAttribute um die Werte zu ändern.
  • +
  • mapImageCoordinatesToRegister
    attr <name> mapImageCoordinatesToRegister <upper left longitude><space><upper left latitude><line feed><lower right longitude><space><lower right latitude>
    Obere linke und untere rechte Ecke der Fläche auf der Erde, die durch das Bild dargestellt wird um das Bild auf der Fläche zu registrieren (oder einzupassen).
    @@ -1510,7 +1221,7 @@ __END__
  • propertyLimits
    attr <name> propertyLimits <positions list>
    - Liste von Positionen, um die Grundstücksgrenze zu beschreiben. Format: Zeilenweise Paare von Longitude- u. Latitudewerten getrennt durch 1 Leerzeichen. Eine Zeile wird aufgeteilt durch (/\s|\R$/).
    Die genaue Position der Grenzpunkte kann man über die Geoportale der Länder finden. Eine Umrechnung der UTM32 Daten in Meter nach ETRS89 in Dezimalgrad kann über das BKG-Geodatenzentrum erfolgen.
  • + Liste von Positionen, um die Grundstücksgrenze zu beschreiben. Format: Zeilenweise Paare von Longitude- u. Latitudewerten getrennt durch 1 Leerzeichen. Eine Zeile wird aufgeteilt durch (/\s|\R$/).
    Die genaue Position der Grenzpunkte kann man über die Geoportale der Länder finden. Eine Umrechnung der UTM32 Daten in Meter nach ETRS89 in Dezimalgrad kann über das BKG-Geodatenzentrum erfolgen.
  • numberOfWayPointsToDisplay
    attr <name> numberOfWayPointsToDisplay <number of way points>
    diff --git a/fhem/FHEM/75_AutomowerConnectDevice.pm b/fhem/FHEM/75_AutomowerConnectDevice.pm index d520348b9..19f77b999 100644 --- a/fhem/FHEM/75_AutomowerConnectDevice.pm +++ b/fhem/FHEM/75_AutomowerConnectDevice.pm @@ -22,6 +22,7 @@ ################################################################################ package FHEM::AutomowerConnectDevice; +my $cvsid = '$Id$'; use strict; use warnings; use POSIX; @@ -80,19 +81,19 @@ eval "use JSON;1" or $missingModul .= "JSON "; require HttpUtils; require FHEM::Devices::AMConnect::Common; -use constant APIURL => 'https://api.amc.husqvarna.dev/v1'; ############################################################## sub Initialize() { my ($hash) = @_; - $hash->{SetFn} = \&Set; - $hash->{GetFn} = \&FHEM::Devices::AMConnect::Common::Get; - $hash->{DefFn} = \&Define; + $hash->{DefFn} = \&FHEM::Devices::AMConnect::Common::Define; $hash->{UndefFn} = \&FHEM::Devices::AMConnect::Common::Undefine; - $hash->{NotifyFn} = \&Notify; + $hash->{RenameFn} = \&FHEM::Devices::AMConnect::Common::Rename; + $hash->{GetFn} = \&FHEM::Devices::AMConnect::Common::Get; $hash->{FW_detailFn}= \&FHEM::Devices::AMConnect::Common::FW_detailFn; + $hash->{SetFn} = \&Set; + $hash->{NotifyFn} = \&Notify; $hash->{AttrFn} = \&Attr; $hash->{AttrList} = "disable:1,0 " . "debug:1,0 " . @@ -102,6 +103,8 @@ sub Initialize() { "mapImageCoordinatesToRegister:textField-long " . "mapImageCoordinatesUTM:textField-long " . "mapImageZoom " . + "mapBackgroundColor " . + "mapDesignAttributes:textField-long " . "showMap:1,0 " . "chargingStationCoordinates " . "chargingStationImagePosition:left,top,right,bottom,center " . @@ -116,125 +119,6 @@ sub Initialize() { return undef; } - -############################################################## -# -# DEFINE -# -############################################################## - -sub Define{ - my ( $hash, $def ) = @_; - my @val = split( "[ \t]+", $def ); - my $name = $val[0]; - my $type = $val[1]; - my $iam = "$type $name Define:"; - - return "$iam too few parameters: define $type " if( @val < 4 ) ; - return "$iam Cannot define $type device. Perl modul $missingModul is missing." if ( $missingModul ); - my $hostname =$val[2]; - my $mowerNumber = $val[3]; - - ::notifyRegexpChanged($hash, $hostname.':state:.connected'); - - %$hash = (%$hash, - helper => { - hostname => $hostname, - mowerNumber => $mowerNumber, - scaleToMeterLongitude => 67425, - scaleToMeterLatitude => 108886, - minLon => 180, - maxLon => -180, - minLat => 90, - maxLat => -90, - imageHeight => 650, - imageWidthHeight => '350 650', - posMinMax => "-180 90\n180 -90", - newdatasets => 0, - MAP_PATH => '', - MAP_MIME => '', - MAP_CACHE => '', - cspos => [], - areapos => [], - searchpos => [], - UNKNOWN => { - arrayName => '', - maxLength => 0, - callFn => '' - }, - NOT_APPLICABLE => { - arrayName => '', - maxLength => 0, - callFn => '' - }, - MOWING => { - arrayName => 'areapos', - maxLength => 500, - maxLengthDefault => 500, - callFn => \&FHEM::Devices::AMConnect::Common::AreaStatistics - }, - GOING_HOME => { - arrayName => '', - maxLength => 0, - callFn => '' - }, - CHARGING => { - arrayName => 'cspos', - maxLength => 100, - callFn => \&FHEM::Devices::AMConnect::Common::ChargingStationPosition - }, - LEAVING => { - arrayName => '', - maxLength => 0, - callFn => '' - }, - PARKED_IN_CS => { - arrayName => 'cspos', - maxLength => 100, - callFn => \&FHEM::Devices::AMConnect::Common::ChargingStationPosition - }, - STOPPED_IN_GARDEN => { - arrayName => '', - maxLength => 0, - callFn => '' - }, - statistics => { - currentSpeed => 0, - currentDayTrack => 0, - currentDayArea => 0, - lastDayTrack => 0, - lastDayArea => 0, - currentWeekTrack => 0, - currentWeekArea => 0, - lastWeekTrack => 0, - lastWeekArea => 0 - } - } - ); - - - $hash->{MODEL} = ''; - $attr{$name}{room} = 'AutomowerConnect' if( !defined( $attr{$name}{room} ) ); - $attr{$name}{icon} = 'automower' if( !defined( $attr{$name}{icon} ) ); - my ($modname) = __FILE__ =~ /(\d\d_.*\.pm)/; - - if (::AnalyzeCommandChain(undef,"version $modname noheader") =~ "^$modname (.*)Z") { - $hash->{VERSION}=$1; - } - - RemoveInternalTimer($hash); - InternalTimer( gettimeofday() + 25, \&FHEM::Devices::AMConnect::Common::readMap, $hash, 0); - - ::FHEM::Devices::AMConnect::Common::AddExtension( $name, \&FHEM::Devices::AMConnect::Common::GetMap, "$type/$name/map" ); - - - readingsSingleUpdate( $hash, 'state', 'defined', 1 ); - - return undef; - -} - - ############################################################## # # GET MOWER @@ -249,6 +133,7 @@ sub Notify { my $iam = "$type $name Notify:"; my $mowerNumber = $hash->{helper}{mowerNumber}; my $events = ::deviceEvents($hosthash,1); + ( $hash->{VERSION} ) = $cvsid =~ /\.pm (.*)Z/ if ( !$hash->{VERSION} ); if ( IsDisabled($name) ) { @@ -276,8 +161,8 @@ sub Notify { } else { # first data set $hash->{helper}{mowerold} = $myMower; - $hash->{helper}{searchpos} = [ dclone( $hash->{helper}{mowerold}{attributes}{positions}[0] ), dclone( $hash->{helper}{mowerold}{attributes}{positions}[1] ) ]; + $hash->{helper}{timestamps}[ 0 ] = $hash->{helper}{mowerold}{attributes}{metadata}{statusTimestamp}; if ( AttrVal( $name, 'mapImageCoordinatesToRegister', '' ) eq '' ) { ::FHEM::Devices::AMConnect::Common::posMinMax( $hash, $hash->{helper}{mowerold}{attributes}{positions} ); @@ -288,10 +173,12 @@ sub Notify { $hash->{helper}{mower} = $myMower; # add alignment data set to the end push( @{ $hash->{helper}{mower}{attributes}{positions} }, @{ dclone( $hash->{helper}{searchpos} ) } ); - $hash->{helper}{newdatasets} = 0; + $hash->{helper}{newdatasets} = 0; my $storediff = $hash->{helper}{mower}{attributes}{metadata}{statusTimestamp} - $hash->{helper}{mowerold}{attributes}{metadata}{statusTimestamp}; if ($storediff) { + # collect timestamps for analysis + unshift ( @{ $hash->{helper}{timestamps} }, $hash->{helper}{mower}{attributes}{metadata}{statusTimestamp} ); ::FHEM::Devices::AMConnect::Common::AlignArray( $hash ); ::FHEM::Devices::AMConnect::Common::FW_detailFn_Update ($hash) if (AttrVal($name,'showMap',1)); @@ -310,7 +197,7 @@ sub Notify { readingsBulkUpdateIfChanged($hash, $pref.'_commandStatus', 'cleared' ); my $tstamp = $hash->{helper}{mower}{attributes}{$pref}{errorCodeTimestamp}; - my $timestamp = FmtDateTimeGMT($tstamp/1000); + my $timestamp = ::FHEM::Devices::AMConnect::Common::FmtDateTimeGMT($tstamp/1000); readingsBulkUpdateIfChanged($hash, $pref."_errorCodeTimestamp", $tstamp ? $timestamp : '-' ); my $errc = $hash->{helper}{mower}{attributes}{$pref}{errorCode}; @@ -323,15 +210,15 @@ sub Notify { 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 ); - # readingsBulkUpdateIfChanged($hash, $pref."_model", $model ); + # $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 = FmtDateTimeGMT($tstamp/1000); + $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} ); @@ -340,7 +227,8 @@ sub Notify { readingsBulkUpdateIfChanged($hash, $pref."_headlight", $hash->{helper}->{mower}{attributes}{$pref}{headlight}{mode} ); readingsBulkUpdateIfChanged($hash, $pref."_cuttingHeight", $hash->{helper}->{mower}{attributes}{$pref}{cuttingHeight} ); $pref = 'status'; - readingsBulkUpdateIfChanged($hash, $pref."_connected", ( $hash->{helper}{mower}{attributes}{metadata}{connected} ? 'CONNECTED' : 'OFFLINE' ) ); + 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 )); @@ -361,7 +249,7 @@ sub Notify { $hash->{helper}{statistics}{currentDayTrack} = 0; $hash->{helper}{statistics}{currentDayArea} = 0; # do on mondays - if ( $time[6] == 1 && $secs <= $interval ) { + if ( $time[6] == 1 ) { $hash->{helper}{statistics}{lastWeekTrack} = $hash->{helper}{statistics}{currentWeekTrack}; $hash->{helper}{statistics}{lastWeekArea} = $hash->{helper}{statistics}{currentWeekArea}; @@ -379,129 +267,6 @@ sub Notify { } -############################################################## -# -# SEND COMMAND -# -############################################################## - -sub CMD { - my ($hash,@cmd) = @_; - my $name = $hash->{NAME}; - my $type = $hash->{TYPE}; - my $iam = "$type $name CMD:"; - my $hostname = $hash->{helper}{hostname}; - my $hosthash = $defs{$hostname}; - - if ( IsDisabled($hostname) ) { - - Log3 $name, 3, "$iam Host $hostname disabled"; - return undef - - } - if ( IsDisabled($name) ) { - - Log3 $name, 3, "$iam disabled"; - return undef - - } - - my $client_id = $hosthash->{helper}->{client_id}; - my $token = ReadingsVal($hostname,".access_token",""); - my $provider = ReadingsVal($hostname,".provider",""); - my $mower_id = $hash->{helper}{mower}{id}; - - my $json = ''; - my $post = ''; - - -my $header = "Accept: application/vnd.api+json\r\nX-Api-Key: ".$client_id."\r\nAuthorization: Bearer " . $token . "\r\nAuthorization-Provider: " . $provider . "\r\nContent-Type: application/vnd.api+json"; - - - if ($cmd[0] eq "ParkUntilFurtherNotice") { $json = '{"data":{"type":"'.$cmd[0].'"}}'; $post = 'actions' } - elsif ($cmd[0] eq "ParkUntilNextSchedule") { $json = '{"data": {"type":"'.$cmd[0].'"}}'; $post = 'actions' } - elsif ($cmd[0] eq "ResumeSchedule") { $json = '{"data": {"type":"'.$cmd[0].'"}}'; $post = 'actions' } - elsif ($cmd[0] eq "Pause") { $json = '{"data": {"type":"'.$cmd[0].'"}}'; $post = 'actions' } - elsif ($cmd[0] eq "Park") { $json = '{"data": {"type":"'.$cmd[0].'","attributes":{"duration":'.$cmd[1].'}}}'; $post = 'actions' } - elsif ($cmd[0] eq "Start") { $json = '{"data": {"type":"'.$cmd[0].'","attributes":{"duration":'.$cmd[1].'}}}'; $post = 'actions' } - elsif ($cmd[0] eq "headlight") { $json = '{"data": {"type":"settings","attributes":{"'.$cmd[0].'": {"mode": "'.$cmd[1].'"}}}}'; $post = 'settings' } - elsif ($cmd[0] eq "cuttingHeight") { $json = '{"data": {"type":"settings","attributes":{"'.$cmd[0].'": '.$cmd[1].'}}}'; $post = 'settings' } - elsif ($cmd[0] eq "sendScheduleFromAttributeToMower" && AttrVal( $name, 'mowerSchedule', '')) { - - my $perl = eval { decode_json (AttrVal( $name, 'mowerSchedule', '')) }; - if ($@) { - return "$iam decode error: $@ \n $perl"; - } - my $jsonSchedule = eval { encode_json ($perl) }; - if ($@) { - return "$iam encode error: $@ \n $json"; - } - $json = '{"data":{"type": "calendar","attributes":{"tasks":'.$jsonSchedule.'}}}'; - $post = 'calendar'; - } - - Log3 $name, 5, "$iam $header \n $cmd[0] \n $json"; - - ::HttpUtils_NonblockingGet({ - url => APIURL . "/mowers/". $mower_id . "/".$post, - timeout => 10, - hash => $hash, - method => "POST", - header => $header, - data => $json, - callback => \&CMDResponse, - }); - -} - -############################################################## -sub CMDResponse { - my ($param, $err, $data) = @_; - my $hash = $param->{hash}; - my $name = $hash->{NAME}; - my $type = $hash->{TYPE}; - my $statuscode = $param->{code}; - my $iam = "$type $name CMDResponse:"; - - Log3 $name, 1, "\ndebug $iam \n\$statuscode [$statuscode]\n\$err [$err],\n \$data [$data] \n\$param->url $param->{url}" if ( AttrVal($name, 'debug', '') ); - - if( !$err && $statuscode == 202 && $data ) { - - my $result = eval { decode_json($data) }; - if ($@) { - - Log3( $name, 2, "$iam - JSON error while request: $@"); - - } else { - - $hash->{helper}{CMDResponse} = $result; - if ($result->{data}) { - - Log3 $name, 5, $data; - if ( ref ($result->{data}) eq 'ARRAY') { - - $hash->{helper}->{mower_commandStatus} = 'OK - '. $result->{data}[0]{type}; - - } else { - - $hash->{helper}->{mower_commandStatus} = 'OK - '. $result->{data}{type}; - - } - - readingsSingleUpdate($hash, 'mower_commandStatus', $hash->{helper}->{mower_commandStatus} ,1); - return undef; - - } - - } - - } - - readingsSingleUpdate($hash, 'mower_commandStatus', "ERROR statuscode $statuscode" ,1); - Log3 $name, 2, "\n$iam \n\$statuscode [$statuscode]\n\$err [$err],\n\$data [$data]\n\$param->url $param->{url}"; - return undef; -} - ############################################################## sub Set { my ($hash,@a) = @_; @@ -530,28 +295,41 @@ sub Set { CommandAttr($hash,"$name mowerSchedule $calendarjson"); return undef; + ################ + } elsif ( $setName eq 'defaultDesignAttributesToAttribute' ) { + + my $design = $hash->{helper}{mapdesign}; + CommandAttr( $hash, "$name mapDesignAttributes $design" ); + return undef; + ################ } elsif ( ReadingsVal( $name, 'state', 'defined' ) !~ /defined|initialized/ && $setName =~ /^(Start|Park|cuttingHeight)$/ ) { if ( $setVal =~ /^(\d+)$/) { - CMD($hash ,$setName, $setVal); + + ::FHEM::Devices::AMConnect::Common::CMD($hash ,$setName, $setVal); return undef; + } ################ } elsif ( ReadingsVal( $name, 'state', 'defined' ) !~ /defined|initialized/ && $setName eq 'headlight' ) { if ( $setVal =~ /^(ALWAYS_OFF|ALWAYS_ON|EVENING_ONLY|EVENING_AND_NIGHT)$/) { - CMD($hash ,$setName, $setVal); + + ::FHEM::Devices::AMConnect::Common::CMD($hash ,$setName, $setVal); return undef; + } ################ } elsif (ReadingsVal( $name, 'state', 'defined' ) !~ /defined|initialized/ && $setName =~ /ParkUntilFurtherNotice|ParkUntilNextSchedule|Pause|ResumeSchedule|sendScheduleFromAttributeToMower/) { - CMD($hash,$setName); + + ::FHEM::Devices::AMConnect::Common::CMD($hash,$setName); return undef; + } my $ret = " ParkUntilFurtherNotice:noArg ParkUntilNextSchedule:noArg Pause:noArg Start:selectnumbers,60,60,600,0,lin Park:selectnumbers,60,60,600,0,lin ResumeSchedule:noArg "; $ret .= "chargingStationPositionToAttribute:noArg headlight:ALWAYS_OFF,ALWAYS_ON,EVENING_ONLY,EVENING_AND_NIGHT cuttingHeight:1,2,3,4,5,6,7,8,9 mowerScheduleToAttribute:noArg "; - $ret .= "sendScheduleFromAttributeToMower:noArg "; + $ret .= "sendScheduleFromAttributeToMower:noArg defaultDesignAttributesToAttribute:noArg "; return "Unknown argument $setName, choose one of".$ret; } @@ -729,14 +507,9 @@ sub Attr { return undef; } -######################### -sub FmtDateTimeGMT { - my $ret = POSIX::strftime( "%F %H:%M:%S", gmtime( shift ) ); -} - - ############################################################## + 1; __END__ @@ -884,6 +657,14 @@ __END__ attr <name> mapImageZoom <height in pixel>
    Zoom of a raster image for an area the mower path has to be drawn to. Default: 0.5
  • +
  • mapBackgroundColor
    + attr <name> mapBackgroundColor <color valuer>
    + The value is used as background-color.
  • + +
  • mapDesignAttributes
    + attr <name> mapDesignAttributes <complete list of design-attributes>
    + Load the list of attributes by set <name> defaultDesignAttributesToAttribute to change its values
  • +
  • mapImageCoordinatesToRegister
    attr <name> mapImageCoordinatesToRegister <upper left longitude><space><upper left latitude><line feed><lower right longitude><space><lower right latitude>
    Upper left and lower right coordinates to register (or to fit to earth) the image. Format: linewise longitude and latitude values separated by 1 space.
    @@ -1111,14 +892,18 @@ __END__ attr <name> mapImageWidthHeight <width in pixel><separator><height in pixel>
    Bildbreite in Pixel des Bildes auf das Pfad, Anfangs- u. Endpunkte gezeichnet werden. <separator> ist 1 Leerzeichen.
  • -
  • mapImageHeight
    - attr <name> <>
    - Bildhöhe in Pixel des Bildes auf das Pfad, Anfangs- u. Endpunkte gezeichnet werden.
  • -
  • mapImageZoom
    attr <name> mapImageHeight <height in pixel>
    Zoomfaktor zur Salierung des Bildes auf das Pfad, Anfangs- u. Endpunkte gezeichnet werden. Standard: 0.5
  • +
  • mapBackgroundColor
    + attr <name> mapBackgroundColor <color value>
    + Der Wert wird als Hintergrungfarbe benutzt.
  • + +
  • mapDesignAttributes
    + attr <name> mapDesignAttributes <complete list of design-attributes>
    + Lade die Attributliste mit set <name> defaultDesignAttributesToAttribute um die Werte zu ändern.
  • +
  • mapImageCoordinatesToRegister
    attr <name> mapImageCoordinatesToRegister <upper left longitude><space><upper left latitude><line feed><lower right longitude><space><lower right latitude>
    Obere linke und untere rechte Ecke der Fläche auf der Erde, die durch das Bild dargestellt wird um das Bild auf der Fläche zu registrieren (oder einzupassen).
    @@ -1159,7 +944,7 @@ __END__
  • propertyLimits
    attr <name> propertyLimits <positions list>
    - Liste von Positionen, um die Grundstücksgrenze zu beschreiben. Format: Zeilenweise Paare von Longitude- u. Latitudewerten getrennt durch 1 Leerzeichen. Eine Zeile wird aufgeteilt durch (/\s|,|\R$/).
    Die genaue Position der Grenzpunkte kann man über die Geoportale der Länder finden. Eine Umrechnung der UTM32 Daten in Meter nach ETRS89 in Dezimalgrad kann über das BKG-Geodatenzentrum erfolgen.
  • + Liste von Positionen, um die Grundstücksgrenze zu beschreiben. Format: Zeilenweise Paare von Longitude- u. Latitudewerten getrennt durch 1 Leerzeichen. Eine Zeile wird aufgeteilt durch (/\s|,|\R$/).
    Die genaue Position der Grenzpunkte kann man über die Geoportale der Länder finden. Eine Umrechnung der UTM32 Daten in Meter nach ETRS89 in Dezimalgrad kann über das BKG-Geodatenzentrum erfolgen.
  • numberOfWayPointsToDisplay
    attr <name> numberOfWayPointsToDisplay <number of way points>
    diff --git a/fhem/lib/FHEM/Devices/AMConnect/Common.pm b/fhem/lib/FHEM/Devices/AMConnect/Common.pm index 7448fc672..1f4ce4788 100644 --- a/fhem/lib/FHEM/Devices/AMConnect/Common.pm +++ b/fhem/lib/FHEM/Devices/AMConnect/Common.pm @@ -75,7 +75,7 @@ my $missingModul = ""; eval "use JSON;1" or $missingModul .= "JSON "; -my $errorjson = '{"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"}'; +my $errorjson = '{"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"}'; our $errortable = eval { decode_json ( $errorjson ) }; if ($@) { @@ -83,6 +83,198 @@ if ($@) { } $errorjson = undef; +use constant AUTHURL => 'https://api.authentication.husqvarnagroup.dev/v1'; +use constant APIURL => 'https://api.amc.husqvarna.dev/v1'; + +############################################################## +# +# DEFINE +# +############################################################## + +sub Define{ + my ( $hash, $def ) = @_; + my @val = split( "[ \t]+", $def ); + my $name = $val[0]; + my $type = $val[1]; + my $iam = "$type $name Define:"; + my $client_id = ''; + my $mowerNumber = 0; + my $hostname =''; + + return "$iam Cannot define $type device. Perl modul $missingModul is missing." if ( $missingModul ); + + if ( $type eq 'AutomowerConnect' ) { + + return "$iam too few parameters: define $type []" if( @val < 3 ); + + $client_id =$val[2]; + $mowerNumber = $val[3] ? $val[3] : 0; + + } elsif ( $type eq 'AutomowerConnectDevice' ) { + + return "$iam too few parameters: define $type " if( @val < 4 ); + + $hostname = $val[2]; + $mowerNumber = $val[3]; + + ::notifyRegexpChanged($hash, $hostname.':state:.connected'); + + + } + +my $mapAttr = 'areaLimitsColor="#ff8000" +areaLimitsLineWidth="1" +areaLimitsConnector="" +propertyLimitsColor="#33cc33" +propertyLimitsLineWidth="1" +propertyLimitsConnector="1" +errorBackgroundColor="#3d3d3d" +errorFont="14px Courier New" +errorFontColor="#ff8000" +errorPathLineColor="#ff00bf" +errorPathLineDash="" +errorPathLineWidth="2" +chargingStationPathLineColor="#999999" +chargingStationPathLineDash="6,2" +chargingStationPathLineWidth="1" +otherActivityPathLineColor="#33cc33" +otherActivityPathLineDash="6,2" +otherActivityPathLineWidth="1" +mowingPathLineColor="#ff0000" +mowingPathLineDash="6,2" +mowingPathLineWidth="1"'; + + + %$hash = (%$hash, + helper => { + passObj => FHEM::Core::Authentication::Passwords->new($type), + interval => 600, + client_id => $client_id, + grant_type => 'client_credentials', + mowerNumber => $mowerNumber, + hostname => $hostname, + scaleToMeterLongitude => 67425, + scaleToMeterLatitude => 108886, + minLon => 180, + maxLon => -180, + minLat => 90, + maxLat => -90, + imageHeight => 650, + imageWidthHeight => '350 650', + mapdesign => $mapAttr, + posMinMax => "-180 90\n180 -90", + newdatasets => 0, + MAP_PATH => '', + MAP_MIME => '', + MAP_CACHE => '', + cspos => [], + otherpos => [], + areapos => [], + searchpos => [], + timestamps => [], + lasterror => { + positions => [], + timestamp => 0, + errordesc => '-', + errordate => '', + sizex => 0, + sizey => 0, + olLon => 0, + olLat => 0 + }, + UNKNOWN => { + arrayName => 'otherpos', + maxLength => 50, + callFn => '' + }, + NOT_APPLICABLE => { + arrayName => 'otherpos', + maxLength => 50, + callFn => '' + }, + MOWING => { + arrayName => 'areapos', + maxLength => 500, + maxLengthDefault => 500, + callFn => \&FHEM::Devices::AMConnect::Common::AreaStatistics + }, + GOING_HOME => { + arrayName => 'otherpos', + maxLength => 50, + callFn => '' + }, + CHARGING => { + arrayName => 'cspos', + maxLength => 100, + callFn => \&FHEM::Devices::AMConnect::Common::ChargingStationPosition + }, + LEAVING => { + arrayName => 'otherpos', + maxLength => 50, + callFn => '' + }, + PARKED_IN_CS => { + arrayName => 'cspos', + maxLength => 100, + callFn => \&FHEM::Devices::AMConnect::Common::ChargingStationPosition + }, + STOPPED_IN_GARDEN => { + arrayName => 'otherpos', + maxLength => 20, + callFn => '' + }, + statistics => { + currentSpeed => 0, + currentDayTrack => 0, + currentDayArea => 0, + lastDayTrack => 0, + lastDayArea => 0, + currentWeekTrack => 0, + currentWeekArea => 0, + lastWeekTrack => 0, + lastWeekArea => 0 + } + } + ); + + $hash->{MODEL} = ''; + $hash->{VERSION} = ''; + $attr{$name}{room} = 'AutomowerConnect' if( !defined( $attr{$name}{room} ) ); + $attr{$name}{icon} = 'automower' if( !defined( $attr{$name}{icon} ) ); + ( $hash->{LIBRARY_VERSION} ) = $cvsid =~ /\.pm (.*)Z/; + + AddExtension( $name, \&GetMap, "$type/$name/map" ); + + if ( $type eq 'AutomowerConnect' ) { + + if( $hash->{helper}->{passObj}->getReadPassword($name) ) { + + RemoveInternalTimer($hash); + InternalTimer( gettimeofday() + 2, \&::FHEM::AutomowerConnect::APIAuth, $hash, 1); + InternalTimer( gettimeofday() + 30, \&readMap, $hash, 0); + + readingsSingleUpdate( $hash, 'state', 'defined', 1 ); + + } else { + + readingsSingleUpdate( $hash, 'state', 'defined - client_secret missing', 1 ); + + } + + } elsif ( $type eq 'AutomowerConnectDevice' ) { + + RemoveInternalTimer($hash); + InternalTimer( gettimeofday() + 25, \&readMap, $hash, 0); + + readingsSingleUpdate( $hash, 'state', 'defined', 1 ); + + } + + return undef; + +} + ######################### sub Undefine { my ( $hash, $arg ) = @_; @@ -112,15 +304,22 @@ sub Delete { sub Rename { my ( $newname, $oldname ) = @_; my $hash = $defs{$newname}; + my $type = $hash->{TYPE}; - my ( $passResp, $passErr ) = $hash->{helper}->{passObj}->setRename( $newname, $oldname ); - Log3 $newname, 2, "$newname password rename error: $passErr" if ($passErr); + RemoveExtension("$type/$oldname/map"); + AddExtension( $newname, \&GetMap, "$type/$newname/map" ); + + if ( $type eq 'AutomowerConnect' ) { + + my ( $passResp, $passErr ) = $hash->{helper}->{passObj}->setRename( $newname, $oldname ); + Log3 $newname, 2, "$newname password rename error: $passErr" if ($passErr); + + } return undef; } ######################### - sub Get { my ($hash,@val) = @_; my $type = $hash->{TYPE}; @@ -173,15 +372,21 @@ sub FW_detailFn { 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 $backgroundcolor = AttrVal($name, 'mapBackgroundColor',''); + my $bgstyle = $backgroundcolor ? " background-color:$backgroundcolor;" : ''; + my $design = AttrVal( $name, 'mapDesignAttributes', $hash->{helper}{mapdesign} ); + my @adesign = split(/\R/,$design); + my $mapDesign = 'data-'.join("data-",@adesign); + Log3 $name,1 , $mapDesign; my ($picx,$picy) = AttrVal( $name,"mapImageWidthHeight", $hash->{helper}{imageWidthHeight} ) =~ /(\d+)\s(\d+)/; $picx=int($picx*$zoom); $picy=int($picy*$zoom); + my $ret = ""; - $ret .= ""; - $ret .= "
    "; - $ret .= ""; + $ret .= ""; + $ret .= "
    "; + $ret .= ""; $ret .= "
    "; InternalTimer( gettimeofday() + 2.0, \&FW_detailFn_Update, $hash, 0 ); @@ -198,9 +403,11 @@ sub FW_detailFn_Update { my $type = $hash->{TYPE}; if ( $hash->{helper} && $hash->{helper}{mower} && $hash->{helper}{mower}{attributes} && $hash->{helper}{mower}{attributes}{positions} && @{$hash->{helper}{mower}{attributes}{positions}} > 0 ) { - my @pos = @{$hash->{helper}{areapos}}; # operational mode - my @posc = @{$hash->{helper}{cspos}}; # maybe operational mode - my $img = "./fhem/$type/$name/map"; + my @pos = @{ $hash->{helper}{areapos} }; + my @posc = @{ $hash->{helper}{cspos} }; + my @posother = @{ $hash->{helper}{otherpos} }; + my @poserr = @{ $hash->{helper}{lasterror}{positions} }; + my $img = "./fhem/$type/$name/map"; my ( $lonlo, $latlo, $dummy, $lonru, $latru ) = AttrVal( $name,"mapImageCoordinatesToRegister",$hash->{helper}{posMinMax} ) =~ /(-?\d*\.?\d+)\s(-?\d*\.?\d+)(\R|\s)(-?\d*\.?\d+)\s(-?\d*\.?\d+)/; @@ -209,7 +416,8 @@ sub FW_detailFn_Update { my ($picx,$picy) = AttrVal( $name,"mapImageWidthHeight", $hash->{helper}{imageWidthHeight} ) =~ /(\d+)\s(\d+)/; AttrVal($name,'scaleToMeterXY', $hash->{helper}{scaleToMeterLongitude} . ' ' .$hash->{helper}{scaleToMeterLatitude}) =~ /(-?\d+)\s+(-?\d+)/; - my $scalx = ($lonru-$lonlo) * $1; + my $scalx = ( $lonru - $lonlo ) * $1; + my $scaly = ( $latlo - $latru ) * $2; $picx = int($picx*$zoom); $picy = int($picy*$zoom); @@ -222,6 +430,7 @@ sub FW_detailFn_Update { use strict "refs"; } + # CHARGING STATION POSITION my $csimgpos = AttrVal( $name,"chargingStationImagePosition","right" ); my $xm = $hash->{helper}{chargingStation}{longitude} // 10.1165; my $ym = $hash->{helper}{chargingStation}{latitude} // 51.28; @@ -242,8 +451,19 @@ sub FW_detailFn_Update { } + # OTHER PATH + my $posoxy = int($lonlo * $picx / $mapx).",".int($latlo * $picy / $mapy); + if ( @posother > 1 ) { + + $posoxy = int(($lonlo-$posother[0]{longitude}) * $picx / $mapx).",".int(($latlo-$posother[0]{latitude}) * $picy / $mapy); + for (my $i=1;$i<@posother;$i++){ + $posoxy .= ",".int(($lonlo-$posother[$i]{longitude}) * $picx / $mapx).",".int(($latlo-$posother[$i]{latitude}) * $picy / $mapy); + } + + } + # CHARGING STATION PATH - my $poscxy = int( $lonlo * $picx / $mapx ).",".int( $latlo * $picy / $mapy ); + my $poscxy = int( ( $lonru-$lonlo ) * $picx / $mapx ).",".int( ( $latlo - $latru ) * $picy / $mapy ); if ( @posc > 1 ) { $poscxy = int( ( $lonlo-$posc[0]{longitude} ) * $picx / $mapx ).",".int( ( $latlo-$posc[0]{latitude} ) * $picy / $mapy ); @@ -258,9 +478,9 @@ sub FW_detailFn_Update { my $limi = ''; if ($arealimits) { my @lixy = (split(/\s|,|\R$/,$arealimits)); - $limi = int(($lonlo-$lixy[0]) * $picx / $mapx).",".int(($latlo-$lixy[1]) * $picy / $mapy); + $limi = int( ( $lonlo - $lixy[ 0 ] ) * $picx / $mapx ) . "," . int( ( $latlo - $lixy[ 1 ] ) * $picy / $mapy ); for (my $i=2;$i<@lixy;$i+=2){ - $limi .= ",".int(($lonlo-$lixy[$i]) * $picx / $mapx).",".int(($latlo-$lixy[$i+1]) * $picy / $mapy); + $limi .= ",".int( ( $lonlo - $lixy[ $i ] ) * $picx / $mapx).",".int( ( $latlo-$lixy[$i+1] ) * $picy / $mapy); } } @@ -275,13 +495,161 @@ sub FW_detailFn_Update { } } + # ERROR MESSAGE + my $errlon = int( ( $lonlo - $hash->{helper}{lasterror}{olLon} ) * $picx / $mapx ); + my $errlat = int( ( $latlo - $hash->{helper}{lasterror}{olLat} ) * $picy / $mapy ); + my $errx = int( $hash->{helper}{lasterror}{sizex} * $picx / -$mapx ); + my $erry = int( $hash->{helper}{lasterror}{sizey} * $picy / $mapy ); + my $errdesc = $hash->{helper}{lasterror}{errordesc}; + my $errdate = $hash->{helper}{lasterror}{errordate}; + + # ERROR PATH + my $poserrxy = int( ( $lonru-$lonlo ) / 2 * $picx / $mapx ).",".int( ( $latlo - $latru ) / 2 * $picy / $mapy );; + + if ( @poserr > 0 ) { + + $poserrxy = int( ( $lonlo - $poserr[ 0 ]{longitude} ) * $picx / $mapx ) . "," . int( ( $latlo - $poserr[ 0 ]{latitude} ) * $picy / $mapy ); + + for ( my $i = 1; $i < @poserr; $i++ ){ + $poserrxy .= ",".int( ( $lonlo - $poserr[ $i ]{longitude} ) * $picx / $mapx) . "," . int( ( $latlo - $poserr[ $i ]{latitude} ) * $picy / $mapy ); + } + + } + + my $erray = "$errlon,$errlat,$errx,$erry,$poserrxy"; + + # Log3 $name, 1, "AutomowerConnectUpdateDetail ( '$name', '$type', '$img', $picx, $picy, $cslon, $cslat, '$csimgpos', $scalx, '$errdesc', [ $posxy ], [ $limi ], [ $propli ], [ $poscxy ], [ $erray ] )"; + map { - ::FW_directNotify("#FHEMWEB:$_", "AutomowerConnectUpdateDetail ( '$name', '$type', '$img', $picx, $picy, $cslon, $cslat, '$csimgpos', $scalx, [ $posxy ], [ $limi ], [ $propli ], [ $poscxy ] )",""); + ::FW_directNotify("#FHEMWEB:$_", "AutomowerConnectUpdateDetail ( '$name', '$type', '$img', $picx, $picy, $cslon, $cslat, '$csimgpos', $scalx, [ '$errdesc', '$errdate' ], [ $posxy ], [ $limi ], [ $propli ], [ $poscxy ], [ $erray ], [ $posoxy ] )",""); } devspec2array("TYPE=FHEMWEB"); } return undef; } +############################################################## +# +# SEND COMMAND +# +############################################################## + +sub CMD { + my ($hash,@cmd) = @_; + my $name = $hash->{NAME}; + my $type = $hash->{TYPE}; + my $iam = "$type $name CMD:"; + my $hostname = $hash->{helper}{hostname} ? $hash->{helper}{hostname} : $name; + my $hosthash = $defs{$hostname}; + + if ( IsDisabled($hostname) ) { + + Log3 $name, 3, "$iam Host $hostname disabled"; + return undef + + } + if ( IsDisabled($name) ) { + + Log3 $name, 3, "$iam disabled"; + return undef + + } + + my $client_id = $hosthash->{helper}->{client_id}; + my $token = ReadingsVal($hostname,".access_token",""); + my $provider = ReadingsVal($hostname,".provider",""); + my $mower_id = $hash->{helper}{mower}{id}; + + my $json = ''; + my $post = ''; + + +my $header = "Accept: application/vnd.api+json\r\nX-Api-Key: ".$client_id."\r\nAuthorization: Bearer " . $token . "\r\nAuthorization-Provider: " . $provider . "\r\nContent-Type: application/vnd.api+json"; + + + if ($cmd[0] eq "ParkUntilFurtherNotice") { $json = '{"data":{"type":"'.$cmd[0].'"}}'; $post = 'actions' } + elsif ($cmd[0] eq "ParkUntilNextSchedule") { $json = '{"data": {"type":"'.$cmd[0].'"}}'; $post = 'actions' } + elsif ($cmd[0] eq "ResumeSchedule") { $json = '{"data": {"type":"'.$cmd[0].'"}}'; $post = 'actions' } + elsif ($cmd[0] eq "Pause") { $json = '{"data": {"type":"'.$cmd[0].'"}}'; $post = 'actions' } + elsif ($cmd[0] eq "Park") { $json = '{"data": {"type":"'.$cmd[0].'","attributes":{"duration":'.$cmd[1].'}}}'; $post = 'actions' } + elsif ($cmd[0] eq "Start") { $json = '{"data": {"type":"'.$cmd[0].'","attributes":{"duration":'.$cmd[1].'}}}'; $post = 'actions' } + elsif ($cmd[0] eq "headlight") { $json = '{"data": {"type":"settings","attributes":{"'.$cmd[0].'": {"mode": "'.$cmd[1].'"}}}}'; $post = 'settings' } + elsif ($cmd[0] eq "cuttingHeight") { $json = '{"data": {"type":"settings","attributes":{"'.$cmd[0].'": '.$cmd[1].'}}}'; $post = 'settings' } + elsif ($cmd[0] eq "sendScheduleFromAttributeToMower" && AttrVal( $name, 'mowerSchedule', '')) { + + my $perl = eval { decode_json (AttrVal( $name, 'mowerSchedule', '')) }; + if ($@) { + return "$iam decode error: $@ \n $perl"; + } + my $jsonSchedule = eval { encode_json ($perl) }; + if ($@) { + return "$iam encode error: $@ \n $json"; + } + $json = '{"data":{"type": "calendar","attributes":{"tasks":'.$jsonSchedule.'}}}'; + $post = 'calendar'; + } + + Log3 $name, 5, "$iam $header \n $cmd[0] \n $json"; + + ::HttpUtils_NonblockingGet({ + url => APIURL . "/mowers/". $mower_id . "/".$post, + timeout => 10, + hash => $hash, + method => "POST", + header => $header, + data => $json, + callback => \&CMDResponse, + }); + +} + +############################################################## +sub CMDResponse { + my ($param, $err, $data) = @_; + my $hash = $param->{hash}; + my $name = $hash->{NAME}; + my $type = $hash->{TYPE}; + my $statuscode = $param->{code}; + my $iam = "$type $name CMDResponse:"; + + Log3 $name, 1, "\ndebug $iam \n\$statuscode [$statuscode]\n\$err [$err],\n \$data [$data] \n\$param->url $param->{url}" if ( AttrVal($name, 'debug', '') ); + + if( !$err && $statuscode == 202 && $data ) { + + my $result = eval { decode_json($data) }; + if ($@) { + + Log3( $name, 2, "$iam - JSON error while request: $@"); + + } else { + + $hash->{helper}{CMDResponse} = $result; + if ($result->{data}) { + + Log3 $name, 5, $data; + if ( ref ($result->{data}) eq 'ARRAY') { + + $hash->{helper}->{mower_commandStatus} = 'OK - '. $result->{data}[0]{type}; + + } else { + + $hash->{helper}->{mower_commandStatus} = 'OK - '. $result->{data}{type}; + + } + + readingsSingleUpdate($hash, 'mower_commandStatus', $hash->{helper}->{mower_commandStatus} ,1); + return undef; + + } + + } + + } + + readingsSingleUpdate($hash, 'mower_commandStatus', "ERROR statuscode $statuscode" ,1); + Log3 $name, 2, "\n$iam \n\$statuscode [$statuscode]\n\$err [$err],\n\$data [$data]\n\$param->url $param->{url}"; + return undef; +} + ######################### sub AlignArray { my ($hash) = @_; @@ -290,52 +658,46 @@ sub AlignArray { my $arrayName = $hash->{helper}{$activity}{arrayName}; my $searchlen = 2; my $i = 0; - my @temp = (); + my $tmp = []; if ( isGoodActivity( $hash ) ) { - my $k = -1; - my $poslen = @{$hash->{helper}{mower}{attributes}{positions}}; - my @searchposlon = ($hash->{helper}{searchpos}[0]{longitude}, $hash->{helper}{searchpos}[1]{longitude}); - my @searchposlat = ($hash->{helper}{searchpos}[0]{latitude}, $hash->{helper}{searchpos}[1]{latitude}); + my $poslen = @{ $hash->{helper}{mower}{attributes}{positions} }; + my @searchposlon = ( $hash->{helper}{searchpos}[0]{longitude}, $hash->{helper}{searchpos}[1]{longitude} ); + my @searchposlat = ( $hash->{helper}{searchpos}[0]{latitude}, $hash->{helper}{searchpos}[1]{latitude} ); my $maxLength = $hash->{helper}{$activity}{maxLength}; - for ( $i = 0; $i < $poslen-2; $i++ ) { # 2 due to 2 alignment data sets at the end + for ( $i = 0; $i < $poslen-2; $i++ ) { # -2 due to 2 alignment data sets at the end 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} ) { - # timediff per step - my $dt = 0; - $dt = int(($hash->{helper}{mower}{attributes}{metadata}{statusTimestamp} - $hash->{helper}{$arrayName}[0]{statusTimestamp})/$i) if ( $i && @{ $hash->{helper}{$arrayName} } ); + + my @ar = @{ $hash->{helper}{mower}{attributes}{positions} }[ 0 .. $i-1 ]; + $tmp = dclone( \@ar ); - - - for ($k=$i-1;$k>-1;$k--) { + if ( $i && @{ $hash->{helper}{$arrayName} } ) { - if ( @{ $hash->{helper}{$arrayName} } ) { + unshift ( @{ $hash->{helper}{$arrayName} }, @$tmp ); - unshift (@{$hash->{helper}{$arrayName}}, dclone( $hash->{helper}{mower}{attributes}{positions}[ $k ] ) ); + } elsif ( $i ) { - } else { - - $hash->{helper}{$arrayName}[ 0 ] = dclone( $hash->{helper}{mower}{attributes}{positions}[ $k ] ); - - } - - pop (@{$hash->{helper}{$arrayName}}) if (@{$hash->{helper}{$arrayName}} > $maxLength); - $hash->{helper}{$arrayName}[ 0 ]{statusTimestamp} = $hash->{helper}{mower}{attributes}{metadata}{statusTimestamp} - $dt * $k; - - push ( @temp, dclone( $hash->{helper}{mower}{attributes}{positions}[ $k ] ) ); + $hash->{helper}{$arrayName} = $tmp; } - posMinMax( $hash, \@temp ); + while ( @{ $hash->{helper}{$arrayName} } > $maxLength ) { + + pop ( @{ $hash->{helper}{$arrayName}} ); + + } + + posMinMax( $hash, $tmp ); #callFn if present - if ( $hash->{helper}{$activity}{callFn} && @{$hash->{helper}{$arrayName}} > 1) { + if ( $hash->{helper}{$activity}{callFn} && @{ $hash->{helper}{$arrayName} } > 1 ) { $hash->{helper}{$activity}{cnt} = $i; no strict "refs"; - &{$hash->{helper}{$activity}{callFn}}($hash); + &{ $hash->{helper}{$activity}{callFn} }( $hash ); use strict "refs"; } @@ -346,12 +708,60 @@ sub AlignArray { } + isErrorThanPrepare( $hash, $tmp ); + } $hash->{helper}{newdatasets} = $i; $hash->{helper}{searchpos} = [ dclone( $hash->{helper}{mower}{attributes}{positions}[0] ), dclone( $hash->{helper}{mower}{attributes}{positions}[1] ) ]; return undef; +} +######################### +sub isErrorThanPrepare { + my ( $hash, $poshash ) = @_; + if ( $hash->{helper}{mower}{attributes}{mower}{errorCodeTimestamp} ) { + + if ( ( $hash->{helper}{lasterror}{timestamp} != $hash->{helper}{mower}{attributes}{mower}{errorCodeTimestamp} ) && @$poshash) { + + my $minLon = minNum( 180, $poshash->[ 0 ]{longitude} ); + my $maxLon = maxNum( -180, $poshash->[ 0 ]{longitude} ); + my $minLat = minNum( 90, $poshash->[ 0 ]{latitude} ); + my $maxLat = maxNum( -90, $poshash->[ 0 ]{latitude} ); + + for ( @{ $poshash } ) { + $minLon = minNum( $minLon, $_->{longitude} ); + $maxLon = maxNum( $maxLon, $_->{longitude} ); + $minLat = minNum( $minLat, $_->{latitude} ); + $maxLat = maxNum( $maxLat, $_->{latitude} ); + } + + my $ect = $hash->{helper}{mower}{attributes}{mower}{errorCodeTimestamp}; + $hash->{helper}{lasterror}{positions} = dclone $poshash; + $hash->{helper}{lasterror}{timestamp} = $ect; + $hash->{helper}{lasterror}{olLon} = $minLon; + $hash->{helper}{lasterror}{olLat} = $maxLat; + $hash->{helper}{lasterror}{sizex} = sprintf('%.7f',$maxLon - $minLon); + $hash->{helper}{lasterror}{sizey} = sprintf('%.7f',$maxLat - $minLat); + my $errc = $hash->{helper}{mower}{attributes}{mower}{errorCode}; + $hash->{helper}{lasterror}{errordesc} = $::FHEM::Devices::AMConnect::Common::errortable->{$errc}; + $hash->{helper}{lasterror}{errordate} = FmtDateTimeGMT( $ect / 1000 ); + + } + + } elsif (!$hash->{helper}{mower}{attributes}{mower}{errorCodeTimestamp} && $hash->{helper}{lasterror}{timestamp} ) { + + $hash->{helper}{lasterror}{positions} = []; + $hash->{helper}{lasterror}{timestamp} = 0; + $hash->{helper}{lasterror}{olLon} = 0; + $hash->{helper}{lasterror}{olLat} = 0; + $hash->{helper}{lasterror}{sizex} = 0; + $hash->{helper}{lasterror}{sizey} = 0; + $hash->{helper}{lasterror}{errordesc} = '-'; + $hash->{helper}{lasterror}{errordate} = ''; + + } + } ######################### @@ -361,8 +771,10 @@ sub isGoodActivity { my $act = $hash->{helper}{mower}{attributes}{mower}{activity}; my $actold = $hash->{helper}{mowerold}{attributes}{mower}{activity}; - my $ret = $hash->{helper}{$act}{arrayName} && $act eq $actold - || $act =~ /^(CHARGING|PARKED_IN_CS)$/ && $actold =~ /^(PARKED_IN_CS|CHARGING)$/; + my $ret = $hash->{helper}{$act}{arrayName} && ( + $act eq $actold + || $act =~ /^(CHARGING|PARKED_IN_CS)$/ && $actold =~ /^(PARKED_IN_CS|CHARGING)$/ + || $act =~ /^(NOT_APPLICABLE)$/ && $actold =~ /^(UNKNOWN|NOT_APPLICABLE|MOWING|GOING_HOME|CHARGING|LEAVING|PARKED_IN_CS|STOPPED_IN_GARDEN)$/); return $ret; } @@ -405,15 +817,14 @@ sub AreaStatistics { } $asum = $lsum * AttrVal($name,'mowerCuttingWidth',0.24); - my $td = $xyarr[ 0 ]{storedTimestamp} - $xyarr[ $k ]{storedTimestamp}; - $vm = sprintf( '%.6f', $lsum / $td ) * 1000 if ($td); # m/s - $hash->{helper}{$activity}{speed} = $vm; + # my $td = $xyarr[ 0 ]{storedTimestamp} - $xyarr[ $k ]{storedTimestamp}; + # $vm = sprintf( '%.6f', $lsum / $td ) * 1000 if ($td); # m/s + # $hash->{helper}{$activity}{speed} = $vm; $hash->{helper}{$activity}{track} = $lsum; $hash->{helper}{$activity}{area} = $asum; - $hash->{helper}{statistics}{currentSpeed} = $vm; + # $hash->{helper}{statistics}{currentSpeed} = $vm; $hash->{helper}{statistics}{currentDayTrack} += $lsum; $hash->{helper}{statistics}{currentDayArea} += $asum; - $hash->{helper}{statistics}{currentSpeed} = $vm; return undef; } @@ -438,10 +849,8 @@ sub RemoveExtension { my ($link) = @_; my $url = "/$link"; my $name = $::data{FWEXT}{$url}{deviceName}; - my $hash = $defs{$name}; - my $type = $hash->{TYPE}; - Log3( $name, 2, "Unregistering $type $name for URL $url..." ); + Log3( $name, 2, "Unregistering URL $url..." ); delete $::data{FWEXT}{$url}; return; @@ -463,7 +872,7 @@ sub GetMap() { return ( $mapMime, $mapData ); } - return ( "text/plain; charset=utf-8","No AutomowerConnect(Device) device for webhook $request" ); + return ( "text/plain; charset=utf-8", "No AutomowerConnect(Device) device for webhook $request" ); } @@ -536,7 +945,7 @@ sub listStatisticsData { $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}{statistics}{currentSpeed}   ' . $hash->{helper}{statistics}{currentSpeed} . ' m/s '; + # $cnt++;$ret .= ' $hash->{helper}{statistics}{currentSpeed}   ' . $hash->{helper}{statistics}{currentSpeed} . ' m/s '; $cnt++;$ret .= ' $hash->{helper}{statistics}{currentDayTrack}   ' . $hash->{helper}{statistics}{currentDayTrack} . ' m '; $cnt++;$ret .= ' $hash->{helper}{statistics}{currentDayArea}   ' . $hash->{helper}{statistics}{currentDayArea} . ' qm '; $cnt++;$ret .= ' $hash->{helper}{statistics}{lastDayTrack}   ' . $hash->{helper}{statistics}{lastDayTrack} . ' m '; @@ -626,12 +1035,17 @@ sub listInternalData { my $xm = $hash->{helper}{chargingStation}{longitude} // 0; my $ym = $hash->{helper}{chargingStation}{latitude} // 0; - my $csnr = scalar @{$hash->{helper}{cspos}}; + 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 ); + $arnr = scalar @{ $hash->{helper}{areapos} } if( scalar @{ $hash->{helper}{areapos} } > 2 ); my $arnrmax = $hash->{helper}{MOWING}{maxLength}; + my $ornr = scalar @{ $hash->{helper}{otherpos} }; + my $ornrmax = $hash->{helper}{UNKNOWN}{maxLength}; + + my $ernr = scalar @{ $hash->{helper}{lasterror}{positions} }; + $hash->{helper}{posMinMax} =~ /(-?\d*\.?\d+)\s(-?\d*\.?\d+)(\R|\s)(-?\d*\.?\d+)\s(-?\d*\.?\d+)/; if ( $::init_done && $1 && $2 && $4 && $5 ) { @@ -654,6 +1068,10 @@ sub listInternalData { $ret .= ' Used For Action  Stack Name  Current Size  Max Size '; $ret .= 'PARKED_IN_CS, CHARGING  cspos  ' . $csnr . ' ' . $csnrmax . ' '; $ret .= 'MOWING  areapos  ' . $arnr . ' ' . $arnrmax . ' '; + $ret .= 'UNKNOWN, NOT_APPLICABLE, LEAVING,
    GOING_HOME, STOPPED_IN_GARDEN  + otherpos  ' . $ornr . ' + ' . $ornrmax . ' '; + $ret .= 'NOT_APPLICABLE with error time stamp  lasterror/positions  ' . $ernr . ' - '; $ret .= ''; if ( $hash->{TYPE} eq 'AutomowerConnect' ) { @@ -724,6 +1142,14 @@ sub listErrorCodes { } } +######################### +# Format mower timestamp assuming mower time is always set to daylight saving time, because it is the mowing period. +sub FmtDateTimeGMT { + my $ti = shift // 0; + my $ret = POSIX::strftime( "%F %H:%M:%S", gmtime( $ti ) ); +} + + ############################################################## 1; diff --git a/fhem/www/pgm2/automowerconnect.js b/fhem/www/pgm2/automowerconnect.js index 68ab1fcf1..469fe14f8 100644 --- a/fhem/www/pgm2/automowerconnect.js +++ b/fhem/www/pgm2/automowerconnect.js @@ -1,22 +1,69 @@ FW_version["automowerconnect.js"] = "$Id$"; -function AutomowerConnectLimits( ctx, pos, format ) { +function AutomowerConnectShowError( ctx, div, dev, picx, picy, errdesc, erray ) { +//~ log( 'AutomowerConnectShowError: ' + erray[0]+' '+erray[1]+' '+erray[2]+' '+erray[3]+' '+erray[4]+' '+erray[5]); + // ERROR BANNER + ctx.beginPath(); + ctx.fillStyle = div.getAttribute( 'data-errorBackgroundColor' );; + ctx.font = div.getAttribute( 'data-errorFont' ); + var m = ctx.measureText( errdesc[ 1 ] + ', ' + dev + ': ' + errdesc[ 0 ] ).width > picy - 6; + + if ( m ) { + + ctx.fillRect( 0, 0, picx, 35); + + } else { + + ctx.fillRect( 0, 0, picx, 20); + + } + + ctx.fillStyle = div.getAttribute( 'data-errorFontColor' ); + ctx.textAlign = "left"; + + if ( m ) { + + ctx.fillText( errdesc[ 1 ] + ', ' + dev + ':', 3, 15 ); + ctx.fillText( errdesc[ 0 ], 3, 30 ); + + } else { + + ctx.fillText( errdesc[ 1 ] + ', ' + dev + ': ' + errdesc[ 0 ], 3, 15 ); + + } + + ctx.stroke(); + + if ( erray[ 0 ] && erray[ 1 ] && erray.length > 3) { + + AutomowerConnectIcon( ctx, erray[ 4 ], erray[ 5 ], 'top', 'E' ); + + } + + if ( erray.length > 8 ) { + + var pos = erray.slice(4); + AutomowerConnectDrawPath ( ctx, div, pos, 'errorPath' ); + + } + +} + +function AutomowerConnectLimits( ctx, div, pos, type ) { // log("array length: "+pos.length); if ( pos.length > 3 ) { // draw limits ctx.beginPath(); - if ( format == 0 ) { - ctx.lineWidth=1; - ctx.strokeStyle = '#ff8000'; - ctx.setLineDash([]); - } - if ( format == 1 ) { - ctx.lineWidth=1; - ctx.strokeStyle = '#33cc33'; - ctx.setLineDash([]); - } + ctx.lineWidth = div.getAttribute( 'data-'+ type + 'limitsLineWidth' ); + ctx.strokeStyle = div.getAttribute( 'data-'+ type + 'limitsColor' ); + ctx.setLineDash( [] ); + //~ if ( type == 'property' ) { + //~ ctx.lineWidth=1; + //~ ctx.strokeStyle = '#33cc33'; + //~ ctx.setLineDash( [] ); + //~ } ctx.moveTo(parseInt(pos[0]),parseInt(pos[1])); for (var i=2;i < pos.length - 1; i+=2 ) { @@ -26,12 +73,12 @@ function AutomowerConnectLimits( ctx, pos, format ) { ctx.stroke(); // limits connector - if ( format == 1 ) { - for (var i=0;i < pos.length - 1; i+=2 ) { + if ( div.getAttribute( 'data-'+ type + 'limitsConnector' ) ) { + for ( var i =0 ; i < pos.length - 1; i += 2 ) { ctx.beginPath(); - ctx.setLineDash([]); - ctx.lineWidth=1; - ctx.strokeStyle = '#33cc33'; + ctx.setLineDash( [] ); + ctx.lineWidth = 1; + ctx.strokeStyle = div.getAttribute( 'data-'+ type + 'limitsColor' ); ctx.fillStyle= 'white'; ctx.moveTo(parseInt(pos[i]),parseInt(pos[i+1])); ctx.arc(parseInt(pos[i]), parseInt(pos[i+1]), 2, 0, 2 * Math.PI, false); @@ -74,9 +121,9 @@ function AutomowerConnectScale( ctx, picx, picy, scalx ) { ctx.stroke(); } -function AutomowerConnectChargingStation( ctx, csx, csy, csrel ) { +function AutomowerConnectIcon( ctx, csx, csy, csrel, type ) { if (parseInt(csx) > 0 && parseInt(csy) > 0) { - // draw chargingstation + // draw icon ctx.beginPath(); ctx.setLineDash([]); ctx.lineWidth=3; @@ -90,14 +137,16 @@ function AutomowerConnectChargingStation( ctx, csx, csy, csrel ) { ctx.fill(); ctx.stroke(); - ctx.font = "16px Arial"; + if(type == 'CS') ctx.font = "16px Arial"; + if(type == 'M' ) ctx.font = "20px Arial"; + if(type == 'E' ) ctx.font = "20px Arial"; ctx.fillStyle = "#f15422"; ctx.textAlign = "center"; - if (csrel == 'right') ctx.fillText("CS", parseInt(csx)+13, parseInt(csy)+6); - if (csrel == 'bottom') ctx.fillText("CS", parseInt(csx), parseInt(csy)+6+13); - if (csrel == 'left') ctx.fillText("CS", parseInt(csx)-13, parseInt(csy)+6); - if (csrel == 'top') ctx.fillText("CS", parseInt(csx), parseInt(csy)+6-13); - if (csrel == 'center') ctx.fillText("CS", parseInt(csx), parseInt(csy)+6); + if (csrel == 'right') ctx.fillText(type, parseInt(csx)+13, parseInt(csy)+6); + if (csrel == 'bottom') ctx.fillText(type, parseInt(csx), parseInt(csy)+6+13); + if (csrel == 'left') ctx.fillText(type, parseInt(csx)-13, parseInt(csy)+6); + if (csrel == 'top') ctx.fillText(type, parseInt(csx), parseInt(csy)+6-13); + if (csrel == 'center') ctx.fillText(type, parseInt(csx), parseInt(csy)+6); // draw mark ctx.beginPath(); @@ -111,50 +160,49 @@ function AutomowerConnectChargingStation( ctx, csx, csy, csrel ) { } } -function AutomowerConnectChargingStationPath ( ctx, pos ) { +function AutomowerConnectDrawPath ( ctx, div, pos, type ) { // draw path ctx.beginPath(); - ctx.lineWidth=1; - ctx.setLineDash([6, 2]); - ctx.strokeStyle = '#999999'; + ctx.strokeStyle = div.getAttribute( 'data-'+ type + 'LineColor' ); + ctx.lineWidth=div.getAttribute( 'data-'+ type + 'LineWidth' ); + ctx.setLineDash([ div.getAttribute( 'data-'+ type + 'LineDash' ) ]); + ctx.moveTo(parseInt(pos[0]),parseInt(pos[1])); for (var i=2;i, , , , ,scalx , , ) -function AutomowerConnectUpdateDetail (dev, type, imgsrc, picx, picy, csx, csy, csrel, scalx, pos, lixy, plixy, posc) { +//AutomowerConnectUpdateDetail (, , , , ,, , , , , , ) +function AutomowerConnectUpdateDetail (dev, type, imgsrc, picx, picy, csx, csy, csrel, scalx, errdesc, pos, lixy, plixy, posc, erray, poso) { // log('pos.length '+pos.length+' lixy.length '+lixy.length+', scalx '+scalx ); // log('loop: Start '+ type+' '+dev ); if (FW_urlParams.detail == dev || 1) { // if (FW_urlParams.detail == dev) { const canvas = document.getElementById(type+'_'+dev+'_canvas'); + const div = document.getElementById(type+'_'+dev+'_div'); if (canvas) { // log('loop: canvas true '+ type+' '+dev ); const ctx = canvas.getContext('2d'); - ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.clearRect(0, 0, canvas.width, canvas.height); - // draw limits - if ( lixy.length > 0 ) AutomowerConnectLimits( ctx, lixy, 0 ); - if ( plixy.length > 0 ) AutomowerConnectLimits( ctx, plixy, 1 ); + // draw property limits + if ( lixy.length > 0 ) AutomowerConnectLimits( ctx, div, lixy, 'area' ); + // draw area limits + if ( plixy.length > 0 ) AutomowerConnectLimits( ctx, div, plixy, 'property' ); // draw scale - AutomowerConnectScale( ctx, picx, picy, scalx ); + AutomowerConnectScale( ctx, div, picx, picy, scalx, 'scale' ); // draw charging station path - AutomowerConnectChargingStationPath ( ctx, posc ); + AutomowerConnectDrawPath ( ctx, div, posc, 'chargingStationPath' ); + // draw path for other activity + if ( poso.length > 1 ) AutomowerConnectDrawPath ( ctx, div, poso, 'otherActivityPath' ); if ( pos.length > 4 ) { - // draw path - ctx.beginPath(); - ctx.lineWidth=1; - ctx.setLineDash([6, 2]); - ctx.strokeStyle = '#ff0000'; - ctx.moveTo(parseInt(pos[2]),parseInt(pos[3])); - for (var i=4;i{ // log('loop: canvas false '+ type+' '+dev ); - AutomowerConnectUpdateDetail (dev, type, imgsrc, picx, picy, csx, csy, csrel, scalx, pos, lixy, plixy); + AutomowerConnectUpdateDetail (dev, type, imgsrc, picx, picy, csx, csy, csrel, scalx, errdesc, pos, lixy, plixy, posc, erray, poso); }, 100); } } else { setTimeout(()=>{ // log('loop: detail false '+ type+' '+dev ); - AutomowerConnectUpdateDetail (dev, type, imgsrc, picx, picy, csx, csy, csrel, scalx, pos, lixy, plixy); + AutomowerConnectUpdateDetail (dev, type, imgsrc, picx, picy, csx, csy, csrel, scalx, errdesc, pos, lixy, plixy, posc, erray, poso); }, 100); } }