2
0
mirror of https://github.com/fhem/fhem-mirror.git synced 2025-01-31 06:39:11 +00:00

74_AutomowerConnect.pm: calculation of mowing area hull polygon, some more info in InternalData and StatisticsData, downloading of third party library for hull calculation

git-svn-id: https://svn.fhem.de/fhem/trunk@28772 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
Ellert 2024-04-08 15:15:28 +00:00
parent efef531fec
commit 781cd40464
5 changed files with 422 additions and 55 deletions

View File

@ -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

View File

@ -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
<a id="74_AutomowerConnect.pm" ></a>
<a id="AutomowerConnect" ></a>
<h3>AutomowerConnect</h3>
<ul>
@ -126,6 +129,9 @@ __END__
<li>To get access to the API an application has to be created in the <a target="_blank" href="https://developer.husqvarnagroup.cloud/docs/get-started">Husqvarna Developer Portal</a>. The application has to be connected with the AutomowerConnect API.</li>
<li>During registration an application key (client_id) and an application secret (client secret) is provided. Use these for the module.</li>
<li>The module uses client credentials as grant type for authorization.</li>
<br>
<li>The module downloads third party software from external server necessary to calculate the hull of mowing area.</li>
<br>
</ul>
<br>
<a id="AutomowerConnectDefine"></a>
@ -188,6 +194,10 @@ __END__
<code>set &lt;name&gt; cuttingHeight &lt;1..9&gt;</code><br>
Sets the cutting height. NOTE: Do not use for 550 EPOS and Ceora.</li>
<li><a id='AutomowerConnect-set-cuttingHeightInWorkArea'>cuttingHeightInWorkArea</a><br>
<code>set &lt;name&gt; cuttingHeightInWorkArea &lt;Id|name&gt; &lt;0..100&gt;</code><br>
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.</li>
<li><a id='AutomowerConnect-set-stayOutZone_enable'>stayOutZone_enable</a><br>
<code>set &lt;name&gt; stayOutZone_enable &lt;Id|name&gt;</code><br>
Testing: Enables stay out zone by Id or zone name. Zone name must not include space and contain at least one alphabetic character.</li>
@ -283,14 +293,51 @@ __END__
<li><a id='AutomowerConnect-attr-mapDesignAttributes'>mapDesignAttributes</a><br>
<code>attr &lt;name&gt; mapDesignAttributes &lt;complete list of design-attributes&gt;</code><br>
Load the list of attributes by <code>set &lt;name&gt; defaultDesignAttributesToAttribute</code> to change its values. Some default values are
Load the list of attributes by <code>set &lt;name&gt; defaultDesignAttributesToAttribute</code> to change its values. Design attributes with changed default values are mandatory in this attribute.<br>
Default values:
<ul>
<li>mower path for activity MOWING: red</li>
<li>path in CS, activity CHARGING,PARKED_IN_CS: grey</li>
<li>path for activity LEAVING: green</li>
<li>path for activity GOING_HOME: blue</li>
<li>path for interval with error (all activities with error): kind of magenta</li>
<li>all other activities: grey</li>
<code>
areaLimitsColor="#ff8000"<br>
areaLimitsLineWidth="1"<br>
areaLimitsConnector=""<br>
hullColor="#0066ff"<br>
hullLineWidth="1"<br>
hullConnector="1"<br>
hullResolution="40"<br>
hullCalculate=""<br>
propertyLimitsColor="#33cc33"<br>
propertyLimitsLineWidth="1"<br>
propertyLimitsConnector="1"<br>
errorBackgroundColor="#3d3d3d"<br>
errorFont="14px Courier New"<br>
errorFontColor="#ff8000"<br>
errorPathLineColor="#ff00bf"<br>
errorPathLineDash=""<br>
errorPathLineWidth="2"<br>
chargingStationPathLineColor="#999999"<br>
chargingStationPathLineDash="6,2"<br>
chargingStationPathLineWidth="1"<br>
chargingStationPathDotWidth="2"<br>
otherActivityPathLineColor="#999999"<br>
otherActivityPathLineDash="6,2"<br>
otherActivityPathLineWidth="1"<br>
otherActivityPathDotWidth="4"<br>
leavingPathLineColor="#33cc33"<br>
leavingPathLineDash="6,2"<br>
leavingPathLineWidth="2"<br>
leavingPathDotWidth="4"<br>
goingHomePathLineColor="#0099ff"<br>
goingHomePathLineDash="6,2"<br>
goingHomePathLineWidth="2"<br>
goingHomePathDotWidth="4"<br>
mowingPathDisplayStart=""<br>
mowingPathLineColor="#ff0000"<br>
mowingPathLineDash="6,2"<br>
mowingPathLineWidth="1"<br>
mowingPathDotWidth="2"<br>
mowingPathUseDots=""<br>
mowingPathShowCollisions=""
</code>
</ul>
</li>
@ -402,6 +449,18 @@ __END__
<code>attr &lt;name&gt; addPositionPolling &lt;[1|<b>0</b>]&gt;</code><br>
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.</li>
<li><a id='AutomowerConnect-attr-mowingAreaHull'>mowingAreaHull</a><br>
<code>attr &lt;name&gt; mowingAreaHull &lt;use button 'mowingAreaHullToAttribute' to fill the attribute&gt;</code><br>
Contains the calculated hull coordinates as JSON string and is set by button 'mowingAreaHullToAttribute' under the dislpayed map.<br>
The stored hull polygon is displayed like the other limits.<br>
Use the design attribute 'hullResolution' to change the number of fractions &#8469;<sub>0</sub><br>.
The hull polygon is calculated when the design attribut is set to 1 <code>hullCalculate="1"</code> and there are more than 50 Points for activity MOWING.<br>
The calculation is done only after site reload.<br>
The calculation of hull is stopped when the attribute ist set and starts again when attribute is deleted.<br>
The attribute <code>weekdaysToResetWayPoints</code> should be set to - and also the design attribute <code>mowingPathUseDots</code> should be set to "1" until the hull is sufficient.
</li>
<li><a href="disable">disable</a></li>
<li><a href="disabledForIntervals">disabledForIntervals</a></li>
@ -470,6 +529,7 @@ __END__
<li>status_statusTimestamp - local time of last status update</li>
<li>status_statusTimestampDiff - time difference in seconds between the last and second last status update</li>
<li>system_name - name of the mower</li>
<li>third_party_library - notice about downloaded JS library. Deleting the reading has no side effects.</li>
</ul>
</ul>
@ -507,6 +567,8 @@ __END__
<li>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.</li>
<li>Das Modul nutzt Client Credentials als Granttype zur Authorisierung.</li>
<br>
<li>Das Modul läd Drittsoftware, die zur Berechnung der Hüllkurve des Mähbereiches erforderlich ist, von einem externem Server.</li>
<br>
</ul>
<br>
<a id="AutomowerConnectDefine"></a>
@ -565,6 +627,10 @@ __END__
<code>set &lt;name&gt; cuttingHeight &lt;1..9&gt;</code><br>
Setzt die Schnitthöhe. HINWEIS: Nicht für 550 EPOS und Ceora geeignet.</li>
<li><a id='AutomowerConnect-set-cuttingHeightInWorkArea'>cuttingHeightInWorkArea</a><br>
<code>set &lt;name&gt; cuttingHeightInWorkArea &lt;Id|name&gt; &lt;0..100&gt;</code><br>
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.</li>
<li><a id='AutomowerConnect-set-stayOutZone_enable'>stayOutZone_enable</a><br>
<code>set &lt;name&gt; stayOutZone_enable &lt;Id|zone name&gt;</code><br>
Testing: Enabled stayOutZone für die Id oder den Namen der Zone, er darf keine Leerzeichen beinhalten und muss mindestens einen Buchstaben enthalten.</li>
@ -664,14 +730,51 @@ __END__
<li><a id='AutomowerConnect-attr-mapDesignAttributes'>mapDesignAttributes</a><br>
<code>attr &lt;name&gt; mapDesignAttributes &lt;complete list of design-attributes&gt;</code><br>
Lade die Attributliste mit <code>set &lt;name&gt; defaultDesignAttributesToAttribute</code> um die Werte zu ändern. Einige Vorgabewerte:
Lade die Attributliste mit <code>set &lt;name&gt; defaultDesignAttributesToAttribute</code> um die Werte zu ändern. Nur Designattribute mit geänderten Standartwerten müssen in diesem Attribut enthalten sein.<br>
Vorgabe Werte:
<ul>
<li>Pfad beim mähen, Aktivität MOWING: rot</li>
<li>In der Ladestation, Aktivität CHARGING,PARKED_IN_CS: grau</li>
<li>Pfad für die Aktivität LEAVING: grün</li>
<li>Pfad für Aktivität GOING_HOME: blau</li>
<li>Pfad eines Intervalls mit Fehler (alle Aktivitäten with error): Eine Art Magenta</li>
<li>Pfad aller anderen Aktivitäten: grau</li>
<code>
areaLimitsColor="#ff8000"<br>
areaLimitsLineWidth="1"<br>
areaLimitsConnector=""<br>
hullColor="#0066ff"<br>
hullLineWidth="1"<br>
hullConnector="1"<br>
hullResolution="40"<br>
hullCalculate=""<br>
propertyLimitsColor="#33cc33"<br>
propertyLimitsLineWidth="1"<br>
propertyLimitsConnector="1"<br>
errorBackgroundColor="#3d3d3d"<br>
errorFont="14px Courier New"<br>
errorFontColor="#ff8000"<br>
errorPathLineColor="#ff00bf"<br>
errorPathLineDash=""<br>
errorPathLineWidth="2"<br>
chargingStationPathLineColor="#999999"<br>
chargingStationPathLineDash="6,2"<br>
chargingStationPathLineWidth="1"<br>
chargingStationPathDotWidth="2"<br>
otherActivityPathLineColor="#999999"<br>
otherActivityPathLineDash="6,2"<br>
otherActivityPathLineWidth="1"<br>
otherActivityPathDotWidth="4"<br>
leavingPathLineColor="#33cc33"<br>
leavingPathLineDash="6,2"<br>
leavingPathLineWidth="2"<br>
leavingPathDotWidth="4"<br>
goingHomePathLineColor="#0099ff"<br>
goingHomePathLineDash="6,2"<br>
goingHomePathLineWidth="2"<br>
goingHomePathDotWidth="4"<br>
mowingPathDisplayStart=""<br>
mowingPathLineColor="#ff0000"<br>
mowingPathLineDash="6,2"<br>
mowingPathLineWidth="1"<br>
mowingPathDotWidth="2"<br>
mowingPathUseDots=""<br>
mowingPathShowCollisions=""
</code>
</ul>
</li>
@ -787,6 +890,17 @@ __END__
<code>attr &lt;name&gt; addPositionPolling &lt;[1|<b>0</b>]&gt;</code><br>
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.</li>
<li><a id='AutomowerConnect-attr-mowingAreaHull'>mowingAreaHull</a><br>
<code>attr &lt;name&gt; mowingAreaHull &lt;use button 'mowingAreaHullToAttribute' to fill the attribute&gt;</code><br><br>
Enthält die berechneten Hüllenkooordinaten als JSON String und wird gesetzt durch den Button 'mowingAreaHullToAttribute' unterhalb der angezeigten Karte.<br>
Das gespeicherte Hüllenpolygon wird wie die anderen Grenzen angezeigt.<br>
Mit dem Designattribut 'hullResolution' kann die Anzahl der Brechungen beeinflusst werden &#8469;<sub>0</sub>, Default 40.<br>
Das Hüllenpolygon wird berechnet wenn das Designattribute gesetzt ist, <code>hullCalculate="1"</code> und es mehr als 50 Wegpunkte der Aktivität MOWING gibt.<br>
Die Berechnung wird beim Laden oder Wiederladen der Website ausgeführt.<br>
Die Berechnung stopt wenn dieses Attribut gesetzt ist und startet wenn das Attibut gelöst wird.<br>
Das Attribut <code>weekdaysToResetWayPoints</code> sollte auf <code>-</code> und das Designattribut <code>mowingPathUseDots</code> sollte auf <code>"1"</code> gesetzt werden, bis das Polygon die Hülle der Mähfläche zufriedenstellend abbildet.
</li>
<li><a href="disable">disable</a></li>
<li><a href="disabledForIntervals">disabledForIntervals</a></li>
@ -855,6 +969,7 @@ __END__
<li>status_statusTimestamp - Lokalzeit des letzten Statusupdates in der API</li>
<li>status_statusTimestampDiff - Zeitdifferenz zwischen dem letzten und vorletzten Statusupdate.</li>
<li>system_name - Name des Automowers</li>
<li>third_party_library - Info, dass die JS-Bibliothek geladen wurde. Das Reading kann bedenkenlos gelöscht werden.</li>
</ul>
</ul>

View File

@ -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

View File

@ -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 .= "<style>
.${type}_${name}_div{padding:0px !important;
@ -526,10 +579,13 @@ sub FW_detailFn {
.${type}_${name}_canvas_1{
position: absolute; left: 0; top: 0; z-index: 1;}
</style>";
$ret .= "<div id='${type}_${name}_div' class='${type}_${name}_div' $mapDesign $csdata $limi $propli >";
$ret .= "<div id='${type}_${name}_div' class='${type}_${name}_div' $$mapDesign $csdata $limi $propli width='$picx' height='$picy' >";
$ret .= "<canvas id='${type}_${name}_canvas_0' class='${type}_${name}_canvas_0' width='$picx' height='$picy' ></canvas>";
$ret .= "<canvas id='${type}_${name}_canvas_1' class='${type}_${name}_canvas_1' width='$picx' height='$picy' ></canvas>";
$ret .= "</div>";
$ret .= "<button title='Sends the hull polygon points to attribute mowingAreaHull.' onclick='AutomowerConnectGetHull( \"$FW_ME/$type/$name/json\" )'>mowingAreaHullToAttribute</button>"
if ( -e "$FW_dir/$hash->{helper}{FWEXTA}{path}/$hash->{helper}{FWEXTA}{file}" && !AttrVal( $name,'mowingAreaHull','' ) && $$mapDesign =~ m/hullCalculate="1"/g );
$ret .= "<br>";
$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 .= '<tr class="column '.( $cnt++ % 2 ? 'odd' : 'even' ).'"><td> <b> calculated '.$item.' area </b> &emsp;</td><td> ' . $hash->{helper}{statistics}{$item.'Area'} . ' </td><td> qm </td></tr>' if ( $hash->{helper}{statistics}{$item.'Area'} );
}
$ret .= '</tbody></table>';
$ret .= '<p><sup>1</sup> totalDriveDistance = totalRunningTime * '. sprintf( "%.2f", $hash->{helper}{mower}{attributes}{statistics}{totalDriveDistance} / $hash->{helper}{mower}{attributes}{statistics}{totalRunningTime} ) if ( $hash->{helper}{mower}{attributes}{statistics}{totalRunningTime} );
$ret .= '<p><sup>2</sup> totalRunningTime = totalCuttingTime + totalSearchingTime';
@ -2549,8 +2639,6 @@ sub listInternalData {
$ret .= '<tr class="column odd"><td>NOT_APPLICABLE with error time stamp&emsp;</td><td> lasterror/positions&emsp;</td><td> ' . $ernr . ' </td><td> -&emsp;</td></tr>';
$ret .= '</tbody></table>';
if ( $hash->{TYPE} eq 'AutomowerConnect' ) {
$ret .= '<p><table class="block wide">';
$ret .= '<caption><b>Rest API Data</b></caption><tbody>';
@ -2567,9 +2655,21 @@ sub listInternalData {
$ret .= '<tr class="column ' . ( $cnt++ % 2 ? "odd" : "even" ) . '"><td> Token Expires</td><td> ' . FmtDateTime( ReadingsVal($name, '.expires', '0') ) . '</td></tr>';
$ret .= '<tr class="column ' . ( $cnt++ % 2 ? "odd" : "even" ) . '"><td> Access Token</td><td style="word-wrap:break-word; max-width:40em">' . ReadingsVal($name, '.access_token', '0') . '</td></tr>';
$ret .= '</tbody></table>';
$ret .= '</tbody></table>';
$ret .= '<p><table class="block wide">';
$ret .= '<caption><b>Default mapDesignAttributes</b></caption><tbody>';
}
my $mapdesign = $hash->{helper}{mapdesign};
$mapdesign =~ s/\n/<br>/g;
$ret .= '<tr class="column ' . ( $cnt++ % 2 ? "odd" : "even" ) . '"><td style="word-wrap:break-word; max-width:40em">' . $mapdesign . '</td></tr>';
$ret .= '</tbody></table>';
$ret .= '<p><table class="block wide">';
$ret .= '<caption><b>Third Party Software</b></caption><tbody>';
$ret .= '<tr class="column ' . ( $cnt++ % 2 ? "odd" : "even" ) . '"><td>hull calculation (hull.js)</td><td style="word-wrap:break-word; max-width:40em"> Server: ' . $hash->{helper}{FWEXTA}{url} . '</td></tr>';
$ret .= '</tbody></table>';
$ret .= '</html>';
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

View File

@ -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 (<devicename>, <type>, <detailfnfirst>, <imagesize x>, <imagesize y>,<scale x>, <error description>, <path array>, <error array>)
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 (<devicename>, <type>, <detailfnfirst>, <imagesize x>, <imagesize y>, <scale x>, <scale y>, <error description>, <path array>, <error array>, <hull array>)
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 );