diff --git a/fhem/CHANGED b/fhem/CHANGED index ed03a3041..c184e5b12 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +1,8 @@ # Add changes at the top of the list. Keep it in ASCII, and 80-char wide. # Do not insert empty lines here, update check depends on it + - feature: 74_AutomowerConnect.pm: calculation of mowing area hull polygon, + some more info in InternalData and StatisticsData, + downloading of third party library for hull calculation - feature: 76_SolarForecast: options for conprice, feedprice and more - change: 49_SSCam: check SSChatBot/TelegramBot is disabled before send data - feature: 70_PylonLowVoltage: add specific Alarm readings, support of US5000 diff --git a/fhem/FHEM/74_AutomowerConnect.pm b/fhem/FHEM/74_AutomowerConnect.pm index 520af2ca7..7a73d306f 100644 --- a/fhem/FHEM/74_AutomowerConnect.pm +++ b/fhem/FHEM/74_AutomowerConnect.pm @@ -74,6 +74,7 @@ sub Initialize() { "mowerCuttingWidth " . "mowerSchedule:textField-long " . "mowingAreaLimits:textField-long " . + "mowingAreaHull:textField-long " . "propertyLimits:textField-long " . "weekdaysToResetWayPoints " . "numberOfWayPointsToDisplay " . @@ -81,7 +82,8 @@ sub Initialize() { "addPositionPolling:1,0 " . $::readingFnAttributes; - $::data{FWEXT}{AutomowerConnect}{SCRIPT} = "automowerconnect.js"; + $::data{FWEXT}{AutomowerConnect}{SCRIPT} = 'automowerconnect.js'; + $::data{FWEXT}{AutomowerConnectA}{SCRIPT} = '/automowerconnect/hull.js'; return undef; } @@ -101,6 +103,7 @@ __END__ =begin html +

AutomowerConnect


@@ -188,6 +194,10 @@ __END__ set <name> cuttingHeight <1..9>
Sets the cutting height. NOTE: Do not use for 550 EPOS and Ceora. +
  • cuttingHeightInWorkArea
    + set <name> cuttingHeightInWorkArea <Id|name> <0..100>
    + Testing: Sets the cutting height for Id or zone name from 0 to 100. Zone name must not include space and contain at least one alphabetic character.
  • +
  • stayOutZone_enable
    set <name> stayOutZone_enable <Id|name>
    Testing: Enables stay out zone by Id or zone name. Zone name must not include space and contain at least one alphabetic character.
  • @@ -283,14 +293,51 @@ __END__
  • mapDesignAttributes
    attr <name> mapDesignAttributes <complete list of design-attributes>
    - Load the list of attributes by set <name> defaultDesignAttributesToAttribute to change its values. Some default values are + Load the list of attributes by set <name> defaultDesignAttributesToAttribute to change its values. Design attributes with changed default values are mandatory in this attribute.
    + Default values:
  • @@ -402,6 +449,18 @@ __END__ attr <name> addPositionPolling <[1|0]>
    Set position polling, default 0 (no position polling). Gets periodically position data from mower, instead from websocket. It has no effect without setting attribute addPollingMinInterval. +
  • mowingAreaHull
    + attr <name> mowingAreaHull <use button 'mowingAreaHullToAttribute' to fill the attribute>
    + Contains the calculated hull coordinates as JSON string and is set by button 'mowingAreaHullToAttribute' under the dislpayed map.
    + The stored hull polygon is displayed like the other limits.
    + Use the design attribute 'hullResolution' to change the number of fractions ℕ0
    . + The hull polygon is calculated when the design attribut is set to 1 hullCalculate="1" and there are more than 50 Points for activity MOWING.
    + The calculation is done only after site reload.
    + The calculation of hull is stopped when the attribute ist set and starts again when attribute is deleted.
    + The attribute weekdaysToResetWayPoints should be set to - and also the design attribute mowingPathUseDots should be set to "1" until the hull is sufficient. + +
  • +
  • disable
  • disabledForIntervals
  • @@ -470,6 +529,7 @@ __END__
  • status_statusTimestamp - local time of last status update
  • status_statusTimestampDiff - time difference in seconds between the last and second last status update
  • system_name - name of the mower
  • +
  • third_party_library - notice about downloaded JS library. Deleting the reading has no side effects.
  • @@ -506,6 +566,8 @@ __END__
  • Für den Zugriff auf die API muss eine Application im Husqvarna Developer Portal angelegt und mit der Automower Connect API verbunden werden.
  • Währenddessen wird ein Application Key (client_id) und ein Application Secret (client secret) bereitgestellt. Diese Angaben sind im Zusammenhang mit der Definition eines Gerätes erforderlich.
  • Das Modul nutzt Client Credentials als Granttype zur Authorisierung.
  • +
    +
  • Das Modul läd Drittsoftware, die zur Berechnung der Hüllkurve des Mähbereiches erforderlich ist, von einem externem Server.


  • @@ -565,6 +627,10 @@ __END__ set <name> cuttingHeight <1..9>
    Setzt die Schnitthöhe. HINWEIS: Nicht für 550 EPOS und Ceora geeignet. +
  • cuttingHeightInWorkArea
    + set <name> cuttingHeightInWorkArea <Id|name> <0..100>
    + Testing: Setzt die Schnitthöhe für Id oder Zonennamen von 0 bis 100. Der Zonenname darf keine Leerzeichen beinhalten und muss mindestens einen Buchstaben enthalten.
  • +
  • stayOutZone_enable
    set <name> stayOutZone_enable <Id|zone name>
    Testing: Enabled stayOutZone für die Id oder den Namen der Zone, er darf keine Leerzeichen beinhalten und muss mindestens einen Buchstaben enthalten.
  • @@ -664,14 +730,51 @@ __END__
  • mapDesignAttributes
    attr <name> mapDesignAttributes <complete list of design-attributes>
    - Lade die Attributliste mit set <name> defaultDesignAttributesToAttribute um die Werte zu ändern. Einige Vorgabewerte: + Lade die Attributliste mit set <name> defaultDesignAttributesToAttribute um die Werte zu ändern. Nur Designattribute mit geänderten Standartwerten müssen in diesem Attribut enthalten sein.
    + Vorgabe Werte:
  • @@ -787,6 +890,17 @@ __END__ attr <name> addPositionPolling <[1|0]>
    Setzt das Positionspolling, default 0 (kein Positionpolling). Liest periodisch Positiondaten des Mähers, an Stelle der über Websocket gelieferten Daten. Das Attribut ist nur wirksam, wenn durch das Attribut addPollingMinInterval das Polling eingeschaltet ist. +
  • mowingAreaHull
    + attr <name> mowingAreaHull <use button 'mowingAreaHullToAttribute' to fill the attribute>

    + Enthält die berechneten Hüllenkooordinaten als JSON String und wird gesetzt durch den Button 'mowingAreaHullToAttribute' unterhalb der angezeigten Karte.
    + Das gespeicherte Hüllenpolygon wird wie die anderen Grenzen angezeigt.
    + Mit dem Designattribut 'hullResolution' kann die Anzahl der Brechungen beeinflusst werden ℕ0, Default 40.
    + Das Hüllenpolygon wird berechnet wenn das Designattribute gesetzt ist, hullCalculate="1" und es mehr als 50 Wegpunkte der Aktivität MOWING gibt.
    + Die Berechnung wird beim Laden oder Wiederladen der Website ausgeführt.
    + Die Berechnung stopt wenn dieses Attribut gesetzt ist und startet wenn das Attibut gelöst wird.
    + Das Attribut weekdaysToResetWayPoints sollte auf - und das Designattribut mowingPathUseDots sollte auf "1" gesetzt werden, bis das Polygon die Hülle der Mähfläche zufriedenstellend abbildet. +
  • +
  • disable
  • disabledForIntervals
  • @@ -855,6 +969,7 @@ __END__
  • status_statusTimestamp - Lokalzeit des letzten Statusupdates in der API
  • status_statusTimestampDiff - Zeitdifferenz zwischen dem letzten und vorletzten Statusupdate.
  • system_name - Name des Automowers
  • +
  • third_party_library - Info, dass die JS-Bibliothek geladen wurde. Das Reading kann bedenkenlos gelöscht werden.
  • diff --git a/fhem/MAINTAINER.txt b/fhem/MAINTAINER.txt index 62824064a..3354fd535 100644 --- a/fhem/MAINTAINER.txt +++ b/fhem/MAINTAINER.txt @@ -420,7 +420,6 @@ FHEM/74_UnifiClient.pm wuehler Automatisierung FHEM/74_UnifiProtect.pm justme1968 Sonstiges FHEM/74_UnifiVideo.pm justme1968 Sonstiges FHEM/74_XiaomiBTLESens.pm CoolTux Sonstige Systeme -FHEM/75_AutomowerConnectDevice.pm Ellert Sonstige Systeme https://forum.fhem.de/index.php/topic,131661.0.html FHEM/75_MSG.pm loredo Automatisierung FHEM/75_msgConfig.pm loredo Automatisierung FHEM/76_msgDialog.pm orphan/Beta-User Frontends/Sprachsteuerung https://forum.fhem.de/index.php/topic,125710.0.html diff --git a/fhem/lib/FHEM/Devices/AMConnect/Common.pm b/fhem/lib/FHEM/Devices/AMConnect/Common.pm index 913dfb695..3848144d2 100644 --- a/fhem/lib/FHEM/Devices/AMConnect/Common.pm +++ b/fhem/lib/FHEM/Devices/AMConnect/Common.pm @@ -48,6 +48,8 @@ BEGIN { CommandDeleteReading FmtDateTime FW_ME + FW_dir + FW_wname getKeyValue InternalTimer InternalVal @@ -123,9 +125,14 @@ sub Define{ $client_id =$val[2]; $mowerNumber = $val[3] ? $val[3] : 0; -my $mapAttr = 'areaLimitsColor="#ff8000" + my $mapAttr = 'areaLimitsColor="#ff8000" areaLimitsLineWidth="1" areaLimitsConnector="" +hullColor="#0066ff" +hullLineWidth="1" +hullConnector="1" +hullResolution="40" +hullCalculate="" propertyLimitsColor="#33cc33" propertyLimitsLineWidth="1" propertyLimitsConnector="1" @@ -160,7 +167,7 @@ mowingPathUseDots="" mowingPathShowCollisions="" '; -my $mapZonesTpl = '{ + my $mapZonesTpl = '{ "01_oben" : { "condition" : "$latitude > 52.6484600648553 || $longitude > 9.54799477359984 && $latitude > 52.64839739580418", "cuttingHeight" : "7" @@ -169,11 +176,19 @@ my $mapZonesTpl = '{ "condition" : "undef", "cuttingHeight" : "3" } -}'; + }'; + + my ( $path, $file) = $::data{FWEXT}{AutomowerConnectA}{SCRIPT} =~ /\/(.*)\/(.*)/; + %$hash = (%$hash, helper => { passObj => FHEM::Core::Authentication::Passwords->new($type), + FWEXTA => { + path => $path, + file => $file, + url => 'https://raw.githubusercontent.com/AndriiHeonia/hull/master/dist/hull.js' + }, interval => 840, interval_ws => 7110, interval_ping => 570, @@ -295,7 +310,10 @@ my $mapZonesTpl = '{ currentWeekTime => 0, lastWeekTrack => 0, lastWeekArea => 0, - lastWeekTime => 0 + lastWeekTime => 0, + propertyArea => 0, + mowingArea => 0, + hullArea => 0 } } ); @@ -326,6 +344,10 @@ my $mapZonesTpl = '{ } + my $url = $hash->{helper}{FWEXTA}{url}; + mkdir( "$FW_dir/$path" ) if ( ! -d "$FW_dir/$path" ); + getTpFile( $hash, $url, "$FW_dir/$path", $file ) if ( ! -e "$FW_dir/$path/$file"); + if( $hash->{helper}->{passObj}->getReadPassword($name) ) { RemoveInternalTimer($hash); @@ -373,7 +395,10 @@ sub Delete { my $type = $hash->{TYPE}; my $iam ="$type $name Delete: "; Log3( $name, 5, "$iam called" ); - + if ( scalar devspec2array( "TYPE=$type" ) == 1 ) { + delete $::data{FWEXT}{AutomowerConnect}; + delete $::data{FWEXT}{AutomowerConnectA}; + } my ($passResp,$passErr) = $hash->{helper}->{passObj}->setDeletePassword($name); Log3( $name, 1, "$iam error: $passErr" ) if ($passErr); @@ -463,9 +488,8 @@ sub FW_detailFn { 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); + + my $mapDesign = getDesignAttr( $hash ); my ($picx,$picy) = AttrVal( $name,"mapImageWidthHeight", $hash->{helper}{imageWidthHeight} ) =~ /(\d+)\s(\d+)/; $picx=int($picx*$zoom); @@ -475,9 +499,9 @@ sub FW_detailFn { my $mapx = $lonlo-$lonru; my $mapy = $latlo-$latru; - AttrVal($name,'scaleToMeterXY', $hash->{helper}{scaleToMeterLongitude} . ' ' .$hash->{helper}{scaleToMeterLatitude}) =~ /(-?\d+)\s+(-?\d+)/; - my $scalx = ( $lonru - $lonlo ) * $1; - my $scaly = ( $latlo - $latru ) * $2; + my ( $scx, $scy ) = AttrVal($name,'scaleToMeterXY', $hash->{helper}{scaleToMeterLongitude} . ' ' .$hash->{helper}{scaleToMeterLatitude}) =~ /(-?\d+)\s+(-?\d+)/; + my $scalx = ( $lonru - $lonlo ) * $scx; + my $scaly = ( $latlo - $latru ) * $scy; # CHARGING STATION POSITION my $csimgpos = AttrVal( $name,"chargingStationImagePosition","right" ); @@ -494,10 +518,19 @@ sub FW_detailFn { my $limi = ''; if ($arealimits) { my @lixy = (split(/\s|,|\R$/,$arealimits)); + my @liar = (); $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); + my $x = ( $lonlo - $lixy[ $i ] ) * $scx; + my $y = ( $latlo - $lixy[$i+1] ) * $scy; + push( @liar, [ $x, $y ]); } + my $x0 = ( $lonlo - $lixy[ 0 ] ) * $scx; + my $y0 = ( $latlo - $lixy[ 1] ) * $scy; + unshift( @liar, [ $x0, $y0 ]); + push( @liar, [ $x0, $y0 ]); + $hash->{helper}{statistics}{mowingArea} = int( abs( polygonArea( \@liar, 1, 1) ) ); } $limi = 'data-areaLimitsPath="'.$limi.'"'; @@ -506,13 +539,33 @@ sub FW_detailFn { my $propli = ''; if ($propertylimits) { my @propxy = (split(/\s|,|\R$/,$propertylimits)); + my @liar = (); $propli = int(($lonlo-$propxy[0]) * $picx / $mapx).",".int(($latlo-$propxy[1]) * $picy / $mapy); for (my $i=2;$i<@propxy;$i+=2){ $propli .= ",".int(($lonlo-$propxy[$i]) * $picx / $mapx).",".int(($latlo-$propxy[$i+1]) * $picy / $mapy); + my $x = ( $lonlo - $propxy[ $i ] ) * $scx; + my $y = ( $latlo - $propxy[$i+1] ) * $scy; + push( @liar, [ $x, $y ]); } + my $x0 = ( $lonlo - $propxy[ 0 ] ) * $scx; + my $y0 = ( $latlo - $propxy[ 1] ) * $scy; + unshift( @liar, [ $x0, $y0 ]); + push( @liar, [ $x0, $y0 ]); + $hash->{helper}{statistics}{propertyArea} = int( abs( polygonArea( \@liar, 1, 1) ) ); } $propli = 'data-propertyLimitsPath="'.$propli.'"'; + # MOWING AREA HULL + my $hulljson = AttrVal($name, 'mowingAreaHull', '[]'); + my $hull = eval { decode_json( $hulljson ) }; + if ( $@ ) { + Log3 $name, 1, "$type $name FW_detailFn: decode error: $@ \n $hulljson"; + $hull = []; + } + + $hash->{helper}{statistics}{hullArea} = int( polygonArea( $hull, $scalx/$picx, $scaly/$picy ) ); + $hash->{helper}{mapupdate}{hullxy} = $hull; + my $ret = ""; $ret .= ""; - $ret .= "
    "; + $ret .= "
    "; $ret .= ""; $ret .= ""; $ret .= "
    "; + $ret .= "" + if ( -e "$FW_dir/$hash->{helper}{FWEXTA}{path}/$hash->{helper}{FWEXTA}{file}" && !AttrVal( $name,'mowingAreaHull','' ) && $$mapDesign =~ m/hullCalculate="1"/g ); + $ret .= "
    "; $hash->{helper}{detailFnFirst} = 1; my $mid = $hash->{helper}{map_init_delay}; InternalTimer( gettimeofday() + $mid, \&FW_detailFn_Update, $hash, 0 ); @@ -608,6 +664,7 @@ sub FW_detailFn_Update { $hash->{helper}{mapupdate}{picx} = $picx; $hash->{helper}{mapupdate}{picy} = $picy; $hash->{helper}{mapupdate}{scalx} = $scalx; + $hash->{helper}{mapupdate}{scaly} = $scaly; $hash->{helper}{mapupdate}{errdesc} = [ "$errdesc", "$errdate", "$errstate" ]; $hash->{helper}{mapupdate}{posxy} = \@posxy; $hash->{helper}{mapupdate}{poserrxy} = \@poserrxy; @@ -1057,7 +1114,7 @@ sub getNewAccessToken { ############################################################## sub CMD { - my ($hash,@cmd) = @_; + my ( $hash, @cmd ) = @_; my $name = $hash->{NAME}; my $type = $hash->{TYPE}; my $iam = "$type $name CMD:"; @@ -1089,6 +1146,8 @@ my $header = "Accept: application/vnd.api+json\r\nX-Api-Key: ".$client_id."\r\nA 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 "cuttingHeightInWorkArea") + { $json = '{"data": {"type":"workArea","id":"'.$cmd[1].'","attributes":{"cuttingHight":'.$cmd[2].'}}}'; $post = 'workAreas/'.$cmd[1]; $method = 'PATCH' } elsif ($cmd[0] eq "StartInWorkArea" && $cmd[2]) { $json = '{"data": {"type":"'.$cmd[0].'","attributes":{"workAreaId":'.$cmd[1].',"duration":'.$cmd[2].'}}}'; $post = 'actions' } elsif ($cmd[0] eq "StartInWorkArea" && !$cmd[2]) @@ -1185,7 +1244,7 @@ sub CMDResponse { readingsEndUpdate($hash, 1); - Log3 $name, 2, "\n$iam \n\$statuscode >$statuscode<\n\$err >$err<,\n\$data >$data<\n\$param->url $param->{url}"; + Log3 $name, 2, "\n$iam \n\$statuscode >$statuscode<\n\$err >$err<,\n\$data >$data<\n\$param->{url} >$param->{url}<\n\$param->{data} >$param->{data}<"; return undef; } @@ -1216,6 +1275,15 @@ sub Set { CommandAttr( $hash, "$name chargingStationCoordinates $xm $ym" ); return undef; + # } elsif ( $setName eq 'mowingAreaHullToAttribute' ) { + + # if ( $FW_ME ) { + # map { + # ::FW_directNotify("#FHEMWEB:$_", "AutomowerConnectGetHull ( '$FW_ME/$type/$name/json' )",""); + # } devspec2array("TYPE=FHEMWEB"); + # return undef; + # } + } elsif ( $setName eq 'defaultDesignAttributesToAttribute' ) { my $design = $hash->{helper}{mapdesign}; @@ -1293,7 +1361,7 @@ sub Set { CMD($hash,$setName); return undef; - } elsif ( ReadingsVal( $name, 'device_state', 'defined' ) !~ /defined|initialized|authentification|authenticated|update/ && $setName =~ /^(StartInWorkArea)$/ && AttrVal( $name, 'testing', '' ) ) { + } elsif ( ReadingsVal( $name, 'device_state', 'defined' ) !~ /defined|initialized|authentification|authenticated|update/ && $setName =~ /^(StartInWorkArea|cuttingHeightInWorkArea)$/ && AttrVal( $name, 'testing', '' ) ) { my $id = undef; $id = name2id( $hash, $setVal, 'workAreas' ) if ( $setVal !~ /^(\d+)$/ ); @@ -1325,7 +1393,7 @@ sub Set { 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 defaultDesignAttributesToAttribute:noArg mapZonesTemplateToAttribute:noArg "; - $ret .= "StartInWorkArea " if ( $hash->{helper}{mower}{attributes}{capabilities}{workAreas} && AttrVal( $name, 'testing', '' ) ); + $ret .= "StartInWorkArea cuttingHeightInWorkArea " if ( $hash->{helper}{mower}{attributes}{capabilities}{workAreas} && AttrVal( $name, 'testing', '' ) ); $ret .= "confirmError:noArg " if ( AttrVal( $name, 'testing', '' ) ); $ret .= "stayOutZone_enable stayOutZone_disable " if ( $hash->{helper}{mower}{attributes}{capabilities}{stayOutZones} && AttrVal( $name, 'testing', '' ) ); return "Unknown argument $setName, choose one of".$ret; @@ -1391,6 +1459,20 @@ sub Attr { } + ########## + } elsif( $attrName eq "mowingAreaHull" ) { + + if( $cmd eq "set" ) { + + my $perl = eval { decode_json ( $attrVal ) }; + + if ($@) { + return "$iam $cmd $attrName decode error: $@ \n $attrVal"; + } + Log3 $name, 4, "$iam $cmd $attrName"; + + } + ########## } elsif( $attrName eq "weekdaysToResetWayPoints" ) { @@ -2398,6 +2480,14 @@ sub listStatisticsData { } + my @fences = qw(hull mowing property); + + for my $item ( @fences ) { + + $ret .= ' calculated '.$item.' area   ' . $hash->{helper}{statistics}{$item.'Area'} . ' qm ' if ( $hash->{helper}{statistics}{$item.'Area'} ); + + } + $ret .= ''; $ret .= '

    1 totalDriveDistance = totalRunningTime * '. sprintf( "%.2f", $hash->{helper}{mower}{attributes}{statistics}{totalDriveDistance} / $hash->{helper}{mower}{attributes}{statistics}{totalRunningTime} ) if ( $hash->{helper}{mower}{attributes}{statistics}{totalRunningTime} ); $ret .= '

    2 totalRunningTime = totalCuttingTime + totalSearchingTime'; @@ -2541,7 +2631,7 @@ sub listInternalData { $ret .= '

    '; $ret .= ''; - $ret .= ''; + $ret .= ''; $ret .= ''; $ret .= ''; @@ -2549,27 +2639,37 @@ sub listInternalData { $ret .= ''; $ret .= '
    Way Point StacksWay Point Stacks
    Used For Activities  Stack Name  Current Size  Max Size 
    PARKED_IN_CS, CHARGING  cspos  ' . $csnr . ' ' . $csnrmax . ' 
    NOT_APPLICABLE with error time stamp  lasterror/positions  ' . $ernr . ' - 
    '; - if ( $hash->{TYPE} eq 'AutomowerConnect' ) { + $ret .= '

    '; + $ret .= ''; - $ret .= '

    Rest API Data
    '; - $ret .= ''; + $ret .= ''; + $ret .= ''; + $ret .= ''; + $ret .= ''; + $ret .= ''; + $ret .= ''; + $ret .= ''; + $ret .= ''; + $ret .= ''; + $ret .= ''; + $ret .= ''; + $ret .= ''; - $ret .= ''; - $ret .= ''; - $ret .= ''; - $ret .= ''; - $ret .= ''; - $ret .= ''; - $ret .= ''; - $ret .= ''; - $ret .= ''; - $ret .= ''; - $ret .= ''; - $ret .= ''; +$ret .= '
    Rest API Data
    Link to APIsHusqvarna Developer
    Authentification API URL' . AUTHURL . '
    Automower Connect API URL' . APIURL . '
    Websocket IO Device name' . WSDEVICENAME . '
    Client-Id' . $hash->{helper}{client_id} . '
    Grant-Type' . $hash->{helper}{grant_type} . '
    User-Id' . ReadingsVal($name, '.user_id', '-') . '
    Provider' . ReadingsVal($name, '.provider', '-') . '
    Scope' . ReadingsVal($name, '.scope', '-') . '
    Token Type' . ReadingsVal($name, '.token_type', '-') . '
    Token Expires ' . FmtDateTime( ReadingsVal($name, '.expires', '0') ) . '
    Access Token' . ReadingsVal($name, '.access_token', '0') . '
    Link to APIsHusqvarna Developer
    Authentification API URL' . AUTHURL . '
    Automower Connect API URL' . APIURL . '
    Websocket IO Device name' . WSDEVICENAME . '
    Client-Id' . $hash->{helper}{client_id} . '
    Grant-Type' . $hash->{helper}{grant_type} . '
    User-Id' . ReadingsVal($name, '.user_id', '-') . '
    Provider' . ReadingsVal($name, '.provider', '-') . '
    Scope' . ReadingsVal($name, '.scope', '-') . '
    Token Type' . ReadingsVal($name, '.token_type', '-') . '
    Token Expires ' . FmtDateTime( ReadingsVal($name, '.expires', '0') ) . '
    Access Token' . ReadingsVal($name, '.access_token', '0') . '
    '; + $ret .= '

    '; + $ret .= ''; - $ret .= '
    Default mapDesignAttributes
    '; +my $mapdesign = $hash->{helper}{mapdesign}; + $mapdesign =~ s/\n/
    /g; + $ret .= '' . $mapdesign . ''; - } + $ret .= ''; + $ret .= '

    '; + $ret .= ''; + + $ret .= ''; + + $ret .= '
    Third Party Software
    hull calculation (hull.js) Server: ' . $hash->{helper}{FWEXTA}{url} . '
    '; $ret .= ''; return $ret; @@ -2624,6 +2724,66 @@ sub FmtDateTimeGMT { my $ret = POSIX::strftime( "%F %H:%M:%S", gmtime( $ti ) ); } +######################### +sub polygonArea { + my ( $ptsref, $sx, $sy ) = @_; + my $sumarea = 0; + my @pts = @{$ptsref}; + + for (my $i = 0; $i < @pts; $i++) { + my $addX = $pts[$i][0]*$sx; + my $addY = $pts[$i == @pts - 1 ? 0 : $i + 1][1]*$sy; + my $subX = $pts[$i == @pts - 1 ? 0 : $i + 1][0]*$sx; + my $subY = $pts[$i][1]*$sy; + $sumarea += ($addX * $addY * 0.5); + $sumarea -= ($subX * $subY * 0.5); + + } + return $sumarea; +} + +######################### +sub getTpFile { + my ( $hash, $url, $path, $file ) = @_; + my $name = $hash->{NAME}; + my $msg = ::GetFileFromURL( $url ); + if ( $msg ) { + my $fh; + + if( !open( $fh, ">", "$path/$file" ) ) { + + Log3 $name, 1, "$name getTpFile: Can't open $path/$file"; + + } else { + + print $fh $msg; + close( $fh ); + readingsSingleUpdate( $hash, 'third_party_library', "$file downloaded to: $path", 1 ); + Log3 $name, 1, "$name getTpFile: third party library downloaded from $url to $path"; + + + } + + } + return undef; +} + +######################### +sub getDesignAttr { + my ( $hash ) = @_; + my $name = $hash->{NAME}; + my @designDefault = split( /\R/,$hash->{helper}{mapdesign} ); + my @designAttr = split( /\R/, AttrVal( $name, 'mapDesignAttributes', '' ) ); + my $hsh = ''; + my $val = ''; + my %desDef = map { ( $hsh, $val ) = $_ =~ /(.*)=(.*)/; $hsh => $val } @designDefault; + %desDef = map { ( $hsh, $val ) = $_ =~ /(.*)=(.*)/; $hsh => $val } @designAttr; + my $desDef = \%desDef; + my @mergedDesign = map { "$_=$desDef->{$_}" } sort keys %desDef; + my $design = 'data-' . join( 'data-', @mergedDesign ); + return \$design; +} + ############################################################## # # WEBSOCKET diff --git a/fhem/www/pgm2/automowerconnect.js b/fhem/www/pgm2/automowerconnect.js index 2b8a13e5a..25f113465 100644 --- a/fhem/www/pgm2/automowerconnect.js +++ b/fhem/www/pgm2/automowerconnect.js @@ -44,6 +44,38 @@ function AutomowerConnectShowError( ctx, div, dev, picx, picy, errdesc, erray ) } +function AutomowerConnectHull( ctx, div, pos, type ) { +// log("array length: "+pos.length); + if ( pos.length > 3 ) { + // draw limits + ctx.beginPath(); + + ctx.lineWidth = div.getAttribute( 'data-'+ type + 'LineWidth' ); + ctx.strokeStyle = div.getAttribute( 'data-'+ type + 'Color' ); + ctx.setLineDash( [] ); + + for (var i=0;i < pos.length; i++ ) { + ctx.lineTo( pos[i][0], pos[i][1]); + } + ctx.stroke(); + + // hull connector + if ( div.getAttribute( 'data-'+ type + 'Connector' ) ) { + for ( var i = 0; i < pos.length; i++ ) { + ctx.beginPath(); + ctx.setLineDash( [] ); + ctx.lineWidth = 1; + ctx.strokeStyle = div.getAttribute( 'data-'+ type + 'Color' ); + ctx.fillStyle= 'white'; + ctx.moveTo( pos[i][0], pos[i][1]); + ctx.arc( pos[i][0], pos[i][1], 2, 0, 2 * Math.PI, false); + ctx.fill(); + ctx.stroke(); + } + } + } +} + function AutomowerConnectLimits( ctx, div, pos, type ) { // log("array length: "+pos.length); if ( pos.length > 3 ) { @@ -315,7 +347,7 @@ function AutomowerConnectUpdateJson ( path ) { $.getJSON( path, function( data, textStatus ) { console.log( 'AutomowerConnectUpdateJson ( \''+path+'\' ): status '+textStatus ); if ( textStatus == 'success') - AutomowerConnectUpdateDetail ( data.name, data.type, data.detailfnfirst, data.picx, data.picy, data.scalx, data.errdesc, data.posxy, data.poserrxy ); + AutomowerConnectUpdateDetail ( data.name, data.type, data.detailfnfirst, data.picx, data.picy, data.scalx, data.scaly, data.errdesc, data.posxy, data.poserrxy, data.hullxy ); }); @@ -325,14 +357,48 @@ function AutomowerConnectUpdateJsonFtui ( path ) { $.getJSON( path, function( data, textStatus ) { console.log( 'AutomowerConnectUpdateJsonFtui ( \''+path+'\' ): status '+textStatus ); if ( textStatus == 'success') - AutomowerConnectUpdateDetail ( data.name, data.type, 1, data.picx, data.picy, data.scalx, data.errdesc, data.posxy, data.poserrxy ); + AutomowerConnectUpdateDetail ( data.name, data.type, 1, data.picx, data.picy, data.scalx, data.scaly, data.errdesc, data.posxy, data.poserrxy, data.hullxy ); }); } -//AutomowerConnectUpdateDetail (, , , , ,, , , ) -function AutomowerConnectUpdateDetail (dev, type, detailfnfirst, picx, picy, scalx, errdesc, pos, erray) { +function AutomowerConnectGetHull ( path ) { + $.getJSON( path, function( data, textStatus ) { + console.log( 'AutomowerConnectGetHull ( \''+path+'\' ): status '+textStatus ); + + if ( textStatus == 'success') { + // data.name, data.type, data.picx, data.picy, data.scalx, data.scaly, data.errdesc, data.posxy, data.poserrxy ); + const div = document.getElementById(data.type+'_'+data.name+'_div'); + const pos =data.posxy; + + if ( div && div.getAttribute( 'data-hullCalculate' ) && typeof hull === "function" ){ + const wypts = []; + + for ( let i = 0; i < pos.length; i+=3 ){ + + if ( pos[i+2] == "M") wypts.push( [ pos[i], pos[i+1] ] ); + + } + + if ( wypts.length > 50 ) { + + const wyres = div.getAttribute( 'data-hullResolution' ); + const hullpts = hull( wypts, wyres ); + FW_cmd( FW_root+"?cmd=attr "+data.name+" mowingAreaHull "+JSON.stringify( hullpts )+"&XHR=1",function(data){setTimeout(()=>{window.location.reload()},500)} ); + + } + + } + + } + + }); + +} + +//AutomowerConnectUpdateDetail (, , , , , , , , , , ) +function AutomowerConnectUpdateDetail (dev, type, detailfnfirst, picx, picy, scalx, scaly, errdesc, pos, erray, hullxy) { const colorat = { "U" : "otherActivityPath", "N" : "errorPath", @@ -369,6 +435,30 @@ function AutomowerConnectUpdateDetail (dev, type, detailfnfirst, picx, picy, sca const plixy = div.getAttribute( 'data-propertyLimitsPath' ).split( "," ); if ( plixy.length > 0 ) AutomowerConnectLimits( ctx0, div, plixy, 'property' ); + // draw hull + if ( div.getAttribute( 'data-hullCalculate' ) && typeof hull === "function" && hullxy.length == 0 ) { + const pts = []; + + for ( let i = 0; i < pos.length; i+=3 ){ + + if ( pos[i+2] == "M") pts.push( [ pos[i], pos[i+1] ] ); + + } + + if ( pts.length > 50 ) { + + const res = div.getAttribute( 'data-hullResolution' ); + const hullpts = hull( pts, res ); + AutomowerConnectHull( ctx0, div, hullpts, 'hull' ); + + } + + } else if ( hullxy.length > 0 ) { + + AutomowerConnectHull( ctx0, div, hullxy, 'hull' ); + + } + // draw scale AutomowerConnectScale( ctx0, picx, picy, scalx );