2
0
mirror of https://github.com/fhem/fhem-mirror.git synced 2025-02-25 03:44:52 +00:00

74_AutomowerConnect.pm: Common.pm, automowerconnect.js include rev. 28836,39,44 from contrib/, hull.js loaded if needed for calculation only.

git-svn-id: https://svn.fhem.de/fhem/trunk@28865 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
Ellert 2024-05-11 22:30:14 +00:00
parent bf48f98ae1
commit 7a7335561d
5 changed files with 638 additions and 366 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: Common.pm, automoverconnect.js
include rev. 28836,39,44 from contrib/, hull.js loaded if
needed for calculation only
- feature: 76_SolarForecast: graphicBeamXContent: keys energycosts, feedincome
- change: 49_SSCam(STRM): optimize memory consumption
- change: 93_DbRep: SQLITE: change PRAGMA temp_store=MEMORY to FILE

View File

@ -58,7 +58,6 @@ sub Initialize() {
$hash->{SetFn} = \&FHEM::Devices::AMConnect::Common::Set;
$hash->{AttrFn} = \&FHEM::Devices::AMConnect::Common::Attr;
$hash->{AttrList} = "disable:1,0 " .
"debug:1,0 " .
"disabledForIntervals " .
"mapImagePath " .
"mapImageWidthHeight " .
@ -85,7 +84,6 @@ sub Initialize() {
$::readingFnAttributes;
$::data{FWEXT}{AutomowerConnect}{SCRIPT} = 'automowerconnect.js';
$::data{FWEXT}{AutomowerConnectA}{SCRIPT} = '/automowerconnect/hull.js';
return undef;
}
@ -150,6 +148,13 @@ __END__
</ul>
<br>
<a id='AutomowerConnect-Hints'></a>
<b>Hints</b>
<ul>
<li>The available setter, attributes, Readings and and the map depend on the mower capabilities ( cutting height, headlights, position, stay out zones, work areas ).</li>
<br>
</ul>
<b>Button</b>
<ul>
<li><a id='AutomowerConnect-button-mowerschedule'>Mower Schedule</a><br>
@ -623,10 +628,16 @@ __END__
<code>define myMower AutomowerConnect 123456789012345678901234567890123456</code> Erstes Gerät: die Defaultmähernummer ist 0.<br>
Es muss ein <b>client_secret</b> gesetzt werden. Es ist das Application Secret vom <a target="_blank" href="https://developer.husqvarnagroup.cloud/docs/get-started">Husqvarna Developer Portal</a>.<br>
<code>set myMower client_secret &lt;client secret&gt;</code><br>
<br>
</ul>
<br>
<a id='AutomowerConnect-Hints'></a>
<b>Hinweise</b>
<ul>
<li> Die verfügbaren Setter, Attribute und Readings, so wie die Karte, werden durch die im Mähertyp vorhandenen Fähigkeiten ( cutting height, headlights, position, stay out zones, work areas ) bestimmt.</li>
<br>
</ul>
<b>Button</b>
<ul>
<li><a id='AutomowerConnect-button-mowerschedule'>Mower Schedule</a><br>

View File

@ -1,6 +1,6 @@
if ( !(typeof FW_version === 'undefined') )
FW_version["automowerconnect.js"] = "$Id: automowerconnect.js 28823c 2024-04-26 13:14:53Z Ellert $";
FW_version["automowerconnect.js"] = "$Id: automowerconnect.js 28823d 2024-04-26 13:14:53Z Ellert $";
{ window.onload = ( ()=>{
let room = document.querySelector("#content");
@ -391,7 +391,7 @@ function AutomowerConnectGetHull ( path ) {
const div = document.getElementById(data.type+'_'+data.name+'_div');
const pos =data.posxy;
if ( div && div.getAttribute( 'data-hullCalculate' ) && typeof hull === "function" ){
if ( div && div.getAttribute( 'data-hullCalculate' ) ){
const wypts = [];
for ( let i = 0; i < pos.length; i+=3 ){
@ -403,8 +403,21 @@ function AutomowerConnectGetHull ( path ) {
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)} );
if ( typeof hull === "function" ){
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)} );
} else if ( typeof loadScript === 'function' ){
loadScript('automowerconnect/hull.js', ()=>{
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)} );
});
}
}
@ -425,34 +438,36 @@ function AutomowerConnectSubtractHull ( path ) {
const div = document.getElementById(data.type+'_'+data.name+'_div');
const pos =data.posxy;
if ( div && div.getAttribute( 'data-hullSubtract' ) && typeof hull === "function" ){
const wypts = [];
const hsub = div.getAttribute( 'data-hullSubtract' );
const wyres = div.getAttribute( 'data-hullResolution' );
var hullpts = [];
if ( div && div.getAttribute( 'data-hullSubtract' ) ){
for ( let i = 0; i < pos.length; i+=3 ){
if ( typeof hull === "function" ) {
const wypts = [];
const hsub = div.getAttribute( 'data-hullSubtract' );
const wyres = div.getAttribute( 'data-hullResolution' );
var hullpts = [];
if ( pos[i+2] == "M") wypts.push( [ pos[i], pos[i+1] ] );
for ( let i = 0; i < pos.length; i+=3 ){
}
if ( pos[i+2] == "M") wypts.push( [ pos[i], pos[i+1] ] );
for ( let i = 0; i < hsub; i++ ){
}
if ( wypts.length > 50 ) {
for ( let i = 0; i < hsub; i++ ){
hullpts = hull( wypts, wyres );
if ( wypts.length > 50 ) {
for ( let k = 0; k < hullpts.length; k++ ){
hullpts = hull( wypts, wyres );
for ( let m = 0; m < wypts.length; m++ ){
for ( let k = 0; k < hullpts.length; k++ ){
if ( hullpts[k][0] == wypts[m][0] && hullpts[k][1] == wypts[m][1] ) {
for ( let m = 0; m < wypts.length; m++ ){
wypts.splice( m, 1 );
break;
//~ m--;
//~ k++;
if ( hullpts[k][0] == wypts[m][0] && hullpts[k][1] == wypts[m][1] ) {
wypts.splice( m, 1 );
break;
}
}
@ -460,14 +475,59 @@ function AutomowerConnectSubtractHull ( path ) {
}
hullpts = hull( wypts, wyres );
}
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)} );
} else if (typeof loadScript === 'function' ) {
loadScript( 'automowerconnect/hull.js', ()=>{
const wypts = [];
const hsub = div.getAttribute( 'data-hullSubtract' );
const wyres = div.getAttribute( 'data-hullResolution' );
var hullpts = [];
for ( let i = 0; i < pos.length; i+=3 ){
if ( pos[i+2] == "M") wypts.push( [ pos[i], pos[i+1] ] );
}
for ( let i = 0; i < hsub; i++ ){
if ( wypts.length > 50 ) {
hullpts = hull( wypts, wyres );
for ( let k = 0; k < hullpts.length; k++ ){
for ( let m = 0; m < wypts.length; m++ ){
if ( hullpts[k][0] == wypts[m][0] && hullpts[k][1] == wypts[m][1] ) {
wypts.splice( m, 1 );
break;
}
}
}
}
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)} );
});
}
FW_cmd( FW_root+"?cmd=attr "+data.name+" mowingAreaHull "+JSON.stringify( hullpts )+"&XHR=1",function(data){setTimeout(()=>{window.location.reload()},500)} );
}
}
@ -584,7 +644,7 @@ function AutomowerConnectHandleInput ( dev, hasWorkAreaId, workAreaId ) {
shdl += "<td><input id='amc_"+dev+"_friday' type='checkbox' "+(cal[cali].friday?"checked='checked'":"")+" /></td>";
shdl += "<td><input id='amc_"+dev+"_saturday' type='checkbox' "+(cal[cali].saturday?"checked='checked'":"")+" /></td>";
shdl += "<td><input id='amc_"+dev+"_sunday' type='checkbox' "+(cal[cali].sunday?"checked='checked'":"")+" /></td>";
shdl += "<td><button id='amc_"+dev+"_schedule_button_plus' title='add: prepare a data set and click &plusmn;&#013;delete: unckeck each weekday and click &plusmn;&#013;reset: fill any time field with -- and click &plusmn;' onclick=' AutomowerConnectHandleInput ( \""+dev+"\", \""+hasWorkAreaId+"\", \""+workAreaId+"\" )' style='padding-bottom:4px; font-weight:bold; font-size:16pt; ' >&ensp;&plusmn;&ensp;</button></td>";
shdl += "<td><button id='amc_"+dev+"_schedule_button_plus' title='add: prepare a data set and click &plusmn;&#013;delete: unckeck each weekday and click &plusmn;&#013;reset: fill any time field with -- and click &plusmn;' onclick=' AutomowerConnectHandleInput ( \""+dev+"\", "+hasWorkAreaId+", "+workAreaId+" )' style='padding-bottom:4px; font-weight:bold; font-size:16pt; ' >&ensp;&plusmn;&ensp;</button></td>";
shdl += "</tr><tr style='border-bottom:1px solid black'><td colspan='100%'></td></tr>";
for (let i=0; i< cal.length; i++){
@ -599,7 +659,6 @@ function AutomowerConnectHandleInput ( dev, hasWorkAreaId, workAreaId ) {
shdl += "<td>&thinsp;"+(cal[i].friday?"&#x2611;":"&#x2610;")+"</td>";
shdl += "<td>&thinsp;"+(cal[i].saturday?"&#x2611;":"&#x2610;")+"</td>";
shdl += "<td>&thinsp;"+(cal[i].sunday?"&#x2611;":"&#x2610;")+"</td>";
if ( hasWorkAreaId ) shdl += "<td>&thinsp;"+workAreaId+"</td>";
shdl += "<td></td>";
shdl += "</tr>";
}
@ -654,7 +713,7 @@ function AutomowerConnectSchedule ( dev ) {
shdl += "<td><input id='amc_"+dev+"_friday' type='checkbox' "+(cal[cali].friday?"checked='checked'":"")+" /></td>";
shdl += "<td><input id='amc_"+dev+"_saturday' type='checkbox' "+(cal[cali].saturday?"checked='checked'":"")+" /></td>";
shdl += "<td><input id='amc_"+dev+"_sunday' type='checkbox' "+(cal[cali].sunday?"checked='checked'":"")+" /></td>";
shdl += "<td><button id='amc_"+dev+"_schedule_button_plus' title='add: prepare a data set and click &plusmn;&#013;delete: unckeck each weekday and click &plusmn;&#013;reset: fill any time field with -- and click &plusmn;' onclick='AutomowerConnectHandleInput ( \""+dev+"\", \""+hasWorkAreaId+"\", \""+workAreaId+"\" )' style='padding-bottom:4px; font-weight:bold; font-size:16pt; ' >&ensp;&plusmn;&ensp;</button></td>";
shdl += "<td><button id='amc_"+dev+"_schedule_button_plus' title='add: prepare a data set and click &plusmn;&#013;delete: unckeck each weekday and click &plusmn;&#013;reset: fill any time field with -- and click &plusmn;' onclick='AutomowerConnectHandleInput ( \""+dev+"\", "+hasWorkAreaId+", "+workAreaId+" )' style='padding-bottom:4px; font-weight:bold; font-size:16pt; ' >&ensp;&plusmn;&ensp;</button></td>";
shdl += "</tr><tr style='border-bottom:1px solid black'><td colspan='100%'></td></tr>";
for (let i=0; i< cal.length; i++){
@ -680,7 +739,7 @@ function AutomowerConnectSchedule ( dev ) {
shdl += "</div>";
let schedule = new DOMParser().parseFromString( shdl, "text/html" ).querySelector( '#amc_'+dev+'_schedule_div' );
document.querySelector('body').append( schedule );
document.querySelector( "#amc_"+dev+"_schedule_button_plus" ).setAttribute( "onclick", "AutomowerConnectHandleInput( '"+dev+"', '"+hasWorkAreaId+"', '"+workAreaId+"' )" );
document.querySelector( "#amc_"+dev+"_schedule_button_plus" ).setAttribute( "onclick", "AutomowerConnectHandleInput( '"+dev+"', "+hasWorkAreaId+", "+workAreaId+" )" );
$(schedule).dialog({
dialogClass:"no-close", modal:true, width:"auto", closeOnEscape:true,
@ -742,20 +801,47 @@ function AutomowerConnectUpdateDetail (dev, type, detailfnfirst, picx, picy, sca
if ( plixy.length > 0 ) AutomowerConnectLimits( ctx0, div, plixy, 'property' );
// draw hull
if ( div.getAttribute( 'data-hullCalculate' ) && typeof hull === "function" && hullxy.length == 0 ) {
const pts = [];
if ( div.getAttribute( 'data-hullCalculate' ) && hullxy.length == 0 ) {
for ( let i = 0; i < pos.length; i+=3 ){
if ( typeof hull === "function" ) {
if ( pos[i+2] == "M") pts.push( [ pos[i], pos[i+1] ] );
const pts = [];
}
for ( let i = 0; i < pos.length; i+=3 ){
if ( pts.length > 50 ) {
if ( pos[i+2] == "M") pts.push( [ pos[i], pos[i+1] ] );
const res = div.getAttribute( 'data-hullResolution' );
const hullpts = hull( pts, res );
AutomowerConnectHull( ctx0, div, hullpts, 'hull' );
}
if ( pts.length > 50 ) {
const res = div.getAttribute( 'data-hullResolution' );
const hullpts = hull( pts, res );
AutomowerConnectHull( ctx0, div, hullpts, 'hull' );
}
} else if ( typeof loadScript === "function" ) {
loadScript('automowerconnect/hull.js', ()=> {
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' );
}
});
}

View File

@ -30,7 +30,7 @@ use strict;
use warnings;
use POSIX;
# wird für den Import der FHEM Funktionen aus der fhem.pl benötigt
# wird für den Import der FHEM Funktionen aus der fhem.pl benötigt
use GPUtils qw(:all);
use FHEM::Core::Authentication::Passwords qw(:ALL);
@ -68,7 +68,9 @@ BEGIN {
readingsSingleUpdate
ReadingsVal
RemoveInternalTimer
setDevAttrList
setKeyValue
unicodeEncoding
defs
attr
modules
@ -90,7 +92,7 @@ require HttpUtils;
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 ) };
our $errortable = eval { JSON::XS->new->decode ( $errorjson ) };
if ($@) {
return "FHEM::Devices::AMConnect::Common \$errortable: $@";
}
@ -179,18 +181,23 @@ mowingPathShowCollisions=""
}
}';
my ( $path, $file) = $::data{FWEXT}{AutomowerConnectA}{SCRIPT} =~ /\/(.*)\/(.*)/;
my $noPositionAttr = "disable:1,0 " .
"disabledForIntervals " .
"mowerPanel:textField-long,85 " .
"mowerSchedule:textField-long " .
"addPollingMinInterval " .
$::readingFnAttributes;
%$hash = (%$hash,
helper => {
passObj => FHEM::Core::Authentication::Passwords->new($type),
FWEXTA => {
path => $path,
file => $file,
path => 'automowerconnect/',
file => 'hull.js',
url => 'https://raw.githubusercontent.com/AndriiHeonia/hull/master/dist/hull.js'
},
interval => 840,
no_position_attr => $noPositionAttr,
interval_ws => 7110,
interval_ping => 570,
use_position_polling => 0,
@ -335,7 +342,7 @@ mowingPathShowCollisions=""
my $attrVal = $attr{$name}{mapImagePath};
if ($attrVal =~ '(webp|png|jpg|jpeg)$' ) {
if ( $attrVal || $attrVal =~ '(webp|png|jpg|jpeg)$' ) {
$hash->{helper}{MAP_PATH} = $attrVal;
$hash->{helper}{MAP_MIME} = "image/".$1;
@ -346,6 +353,8 @@ mowingPathShowCollisions=""
}
my $url = $hash->{helper}{FWEXTA}{url};
my $path = $hash->{helper}{FWEXTA}{path};
my $file = $hash->{helper}{FWEXTA}{file};
mkdir( "$FW_dir/$path" ) if ( ! -d "$FW_dir/$path" );
getTpFile( $hash, $url, "$FW_dir/$path", $file ) if ( ! -e "$FW_dir/$path/$file");
@ -398,7 +407,6 @@ sub 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);
@ -446,17 +454,12 @@ sub FW_detailFn {
my $iam = "$type $name FW_detailFn:";
return '' if( AttrVal($name, 'disable', 0) || !$::init_done || !$FW_ME );
my $calendarjson = eval {
require JSON::PP;
my %ORDER=(start=>1,duration=>2,monday=>3,tuesday=>4,wednesday=>5,thursday=>6,friday=>7,saturday=>8,sunday=>9);
JSON::PP->new->sort_by(
sub {($ORDER{$JSON::PP::a} // 999) <=> ($ORDER{$JSON::PP::b} // 999) or $JSON::PP::a cmp $JSON::PP::b})
->encode ($hash->{helper}{mower}{attributes}{calendar}{tasks})
};
return "$iam $@" if ($@);
my $reta = "<div id='amc_${name}_schedule_buttons' name='fhem_amc_mower_schedule_buttons' ><button id='amc_${name}_schedule_button' onclick='AutomowerConnectSchedule( \"$name\", $calendarjson )' style='font-size:16px; ' >Mower Schedule</button></div>";
return $reta if( !AttrVal ($name, 'showMap', 1 ) );
my $reta = "<div id='amc_${name}_schedule_buttons' name='fhem_amc_mower_schedule_buttons' ><button id='amc_${name}_schedule_button' onclick='AutomowerConnectSchedule( \"$name\" )' style='font-size:16px; ' >Mower Schedule</button>";
# $reta .= "<label for='amc_${name}_select_workareas' > for Work Area: </label><select id='amc_${name}_select_workareas' name=work_areas_select>";
# $reta .= "<option value='-1' selected >default</option>";
# $reta .= "<select/>";
$reta .= "</div>";
return $reta if( !AttrVal ($name, 'showMap', 1 ) || !$hash->{helper}{mower}{attributes}{capabilities}{position} );
my $img = "$FW_ME/$type/$name/map";
my $zoom=AttrVal( $name,"mapImageZoom", 0.7 );
@ -531,7 +534,7 @@ sub FW_detailFn {
# MOWING AREA HULL
my $hulljson = AttrVal($name, 'mowingAreaHull', '[]');
my $hull = eval { decode_json( $hulljson ) };
my $hull = eval { JSON::XS->new->decode( $hulljson ) };
if ( $@ ) {
Log3 $name, 1, "$type $name FW_detailFn: decode error: $@ \n $hulljson";
$hull = [];
@ -564,9 +567,9 @@ sub FW_detailFn {
$ret .= $reta if( AttrVal ($name, 'showMap', 1 ) );
$ret .= "<div class='fhem_amc_hull_buttons' >";
$ret .= "<button class='fhem_amc_hull_button' title='Sends the hull polygon points to attribute mowingAreaHull.' onclick='AutomowerConnectGetHull( \"$FW_ME/$type/$name/json\" )' style='font-weight:bold; font-size:16pt; ' >mowingAreaHullToAttribute</button>"
$ret .= "<button class='fhem_amc_hull_button' title='Sends the hull polygon points to attribute mowingAreaHull.' onclick='AutomowerConnectGetHull( \"$FW_ME/$type/$name/json\" )' style='font-size:12pt; ' >mowingAreaHullToAttribute</button>"
if ( -e "$FW_dir/$hash->{helper}{FWEXTA}{path}/$hash->{helper}{FWEXTA}{file}" && !AttrVal( $name,'mowingAreaHull','' ) && $$mapDesign =~ m/hullCalculate="1"/g );
$ret .= "<button class='fhem_amc_hull_button' title='Subtracts hull polygon points from way points. To hide button set hullSubtract=\"\".' onclick='AutomowerConnectSubtractHull( \"$FW_ME/$type/$name/json\" )' style='font-weight:bold; font-size:16pt; ' >Subtract Hull</button>"
$ret .= "<button class='fhem_amc_hull_button' title='Subtracts hull polygon points from way points. To hide button set hullSubtract=\"\".' onclick='AutomowerConnectSubtractHull( \"$FW_ME/$type/$name/json\" )' style='font-size:12pt; ' >Subtract Hull</button>"
if ( -e "$FW_dir/$hash->{helper}{FWEXTA}{path}/$hash->{helper}{FWEXTA}{file}" && AttrVal( $name,'mowingAreaHull','' ) && $$mapDesign =~ m/hullSubtract="\d+"/g );
$ret .= "</div>";
$ret .= $content if ( !$contentflg );
@ -584,7 +587,7 @@ sub FW_detailFn_Update {
my ($hash) = @_;
my $name = $hash->{NAME};
my $type = $hash->{TYPE};
return undef if( AttrVal($name, 'disable', 0) || !AttrVal($name, 'showMap', 1) );
return undef if( AttrVal($name, 'disable', 0) || !AttrVal($name, 'showMap', 1) || !$hash->{helper}{mower}{attributes}{capabilities}{position} );
my @pos = @{ $hash->{helper}{areapos} };
my @poserr = @{ $hash->{helper}{lasterror}{positions} };
@ -746,11 +749,11 @@ sub APIAuthResponse {
my $iam = "$type $name APIAuthResponse:";
Log3 $name, 1, "$iam response time ". sprintf( "%.2f", ( gettimeofday() - $param->{t_begin} ) ) . ' s' if ( $param->{timeout} == 60 );
Log3 $name, 1, "\ndebug $iam \n\$statuscode [$statuscode]\n\$err [$err],\n \$data [$data] \n\$param->url $param->{url}" if ( AttrVal($name, 'debug', '') );
Log3 $name, 5, "$iam \n\$statuscode [$statuscode]\n\$err [$err],\n \$data [$data] \n\$param->url $param->{url}";
if( !$err && $statuscode == 200 && $data) {
my $result = eval { decode_json($data) };
my $result = eval { JSON::XS->new->utf8( not $unicodeEncoding )->decode( $data ) };
if ($@) {
Log3 $name, 2, "$iam JSON error [ $@ ]";
@ -843,133 +846,6 @@ sub getMower {
return undef;
}
#########################
sub getMowerWs {
my ( $hash ) = @_;
my $name = $hash->{NAME};
my $type = $hash->{TYPE};
my $iam = "$type $name getMowerWs:";
my $access_token = ReadingsVal($name,".access_token","");
my $provider = ReadingsVal($name,".provider","");
my $client_id = $hash->{helper}->{client_id};
my $timeout = AttrVal( $name, 'timeoutGetMower', $hash->{helper}->{timeout_getmower} );
my $header = "Accept: application/vnd.api+json\r\nX-Api-Key: " . $client_id . "\r\nAuthorization: Bearer " . $access_token . "\r\nAuthorization-Provider: " . $provider;
Log3 $name, 5, "$iam header [ $header ]";
readingsSingleUpdate( $hash, 'api_callsThisMonth' , ReadingsVal( $name, 'api_callsThisMonth', 0 ) + 1, 0) if ( $hash->{helper}{additional_polling} );
::HttpUtils_NonblockingGet( {
url => APIURL . '/mowers/' . $hash->{helper}{mower}{id},
timeout => $timeout,
hash => $hash,
method => "GET",
header => $header,
callback => \&getMowerResponseWs,
t_begin => scalar gettimeofday()
} );
return undef;
}
#########################
sub getMowerResponseWs {
my ( $param, $err, $data ) = @_;
my $hash = $param->{hash};
my $name = $hash->{NAME};
my $type = $hash->{TYPE};
my $statuscode = $param->{code} // '';
my $iam = "$type $name getMowerResponseWs:";
Log3 $name, 1, "$iam response time ". sprintf( "%.2f", ( gettimeofday() - $param->{t_begin} ) ) . ' s' if ( $param->{timeout} == 60 );
Log3 $name, 4, "$iam response polling after status-event \$statuscode >$statuscode<, \$err >$err<, \$param->url $param->{url} \n \$data >$data<";
if( !$err && $statuscode == 200 && $data) {
if ( $data eq '' ) {
Log3 $name, 2, "$iam no mower data present";
} else {
my $result = eval { decode_json($data) };
if ($@) {
Log3( $name, 2, "$iam - JSON error while request: $@");
} else {
$hash->{helper}{wsResult}{mower} = dclone( $result->{data} ) if ( AttrVal($name, 'debug', '') );
$hash->{helper}{mower}{attributes}{statistics} = dclone( $result->{data}{attributes}{statistics} );
if ( $hash->{helper}{use_position_polling} ) {
my $cnt = 0;
my $tmp = [];
my $poslen = @{ $result->{data}{attributes}{positions} };
for ( $cnt = 0; $cnt < $poslen; $cnt++ ) {
if ( $hash->{helper}{searchpos}[ 0 ]{longitude} == $result->{data}{attributes}{positions}[ $cnt ]{longitude}
&& $hash->{helper}{searchpos}[ 0 ]{latitude} == $result->{data}{attributes}{positions}[ $cnt ]{latitude} || $cnt == $poslen -1) { # if nothing found take all
if ( $cnt > 0 ) {
my @ar;
push @ar, @{ $result->{data}{attributes}{positions} }[ 0 .. $cnt-1 ];
$hash->{helper}{mower}{attributes}{positions} = dclone( \@ar );
AlignArray( $hash );
FW_detailFn_Update ($hash);
} else {
$hash->{helper}{mower}{attributes}{positions} = [];
}
last;
}
}
}
isErrorThanPrepare( $hash );
resetLastErrorIfCorrected( $hash );
# Update readings
readingsBeginUpdate($hash);
fillReadings( $hash );
# readingsBulkUpdate( $hash, 'mower_wsEvent', $hash->{helper}{wsResult}{type} ); #to do check what event
readingsBulkUpdate( $hash, 'mower_wsEvent', 'status-event' );
readingsBulkUpdateIfChanged( $hash, 'device_state', 'connected' );
readingsEndUpdate($hash, 1);
$hash->{helper}{searchpos} = [ dclone $result->{data}{attributes}{positions}[ 0 ] ];
return undef;
}
}
} else {
readingsSingleUpdate( $hash, 'device_state', "additional Polling error statuscode $statuscode", 1 );
Log3 $name, 1, "$iam \$statuscode [$statuscode]\n\$err [$err],\n \$data [$data] \n\$param->url $param->{url}";
}
return undef;
}
#########################
sub getMowerResponse {
@ -992,7 +868,7 @@ sub getMowerResponse {
} else {
my $result = eval { decode_json($data) };
my $result = eval { JSON::XS->new->utf8( not $::unicodeEncoding )->decode( $data ) };
if ($@) {
Log3( $name, 2, "$iam - JSON error while request: $@");
@ -1029,19 +905,29 @@ sub getMowerResponse {
$hash->{helper}{mowerold}{attributes}{mower}{activity} = $hash->{helper}{mowers}[$mowerNumber]{attributes}{mower}{activity};
$hash->{helper}{mowerold}{attributes}{statistics}{numberOfCollisions} = $hash->{helper}{mowers}[$mowerNumber]{attributes}{statistics}{numberOfCollisions};
$hash->{helper}{statistics}{numberOfCollisionsOld} = $hash->{helper}{mowers}[$mowerNumber]{attributes}{statistics}{numberOfCollisions};
$hash->{helper}{searchpos} = [ dclone $hash->{helper}{mowers}[$mowerNumber]{attributes}{positions}[0] ];
if ( AttrVal( $name, 'mapImageCoordinatesToRegister', '' ) eq '' ) {
posMinMax( $hash, $hash->{helper}{mowers}[$mowerNumber]{attributes}{positions} );
if ( $hash->{helper}{mowers}[$mowerNumber]{attributes}{capabilities}{position} ) {
$hash->{helper}{searchpos} = [ dclone $hash->{helper}{mowers}[$mowerNumber]{attributes}{positions}[0] ];
if ( AttrVal( $name, 'mapImageCoordinatesToRegister', '' ) eq '' ) {
posMinMax( $hash, $hash->{helper}{mowers}[$mowerNumber]{attributes}{positions} );
}
}
}
$hash->{helper}{mower} = dclone( $hash->{helper}{mowers}[$mowerNumber] );
$hash->{helper}{mower}{attributes}{positions}[0]{getMower} = 'from polling';
$hash->{helper}{mower_id} = $hash->{helper}{mower}{id};
$hash->{helper}{newdatasets} = 0;
if ( $hash->{helper}{mower}{attributes}{capabilities}{position} ) {
setDevAttrList( $name );
} else {
setDevAttrList( $name, $hash->{helper}{no_position_attr} );
}
$hash->{helper}{storediff} = $hash->{helper}{mower}{attributes}{metadata}{statusTimestamp} - $hash->{helper}{mowerold}{attributes}{metadata}{statusTimestamp};
calculateStatistics( $hash ) if ( $hash->{helper}{midnightCycle} );
@ -1085,6 +971,133 @@ sub getMowerResponse {
}
#########################
sub getMowerWs {
my ( $hash ) = @_;
my $name = $hash->{NAME};
my $type = $hash->{TYPE};
my $iam = "$type $name getMowerWs:";
my $access_token = ReadingsVal($name,".access_token","");
my $provider = ReadingsVal($name,".provider","");
my $client_id = $hash->{helper}->{client_id};
my $timeout = AttrVal( $name, 'timeoutGetMower', $hash->{helper}->{timeout_getmower} );
my $header = "Accept: application/vnd.api+json\r\nX-Api-Key: " . $client_id . "\r\nAuthorization: Bearer " . $access_token . "\r\nAuthorization-Provider: " . $provider;
Log3 $name, 5, "$iam header [ $header ]";
readingsSingleUpdate( $hash, 'api_callsThisMonth' , ReadingsVal( $name, 'api_callsThisMonth', 0 ) + 1, 0) if ( $hash->{helper}{additional_polling} );
::HttpUtils_NonblockingGet( {
url => APIURL . '/mowers/' . $hash->{helper}{mower}{id},
timeout => $timeout,
hash => $hash,
method => "GET",
header => $header,
callback => \&getMowerResponseWs,
t_begin => scalar gettimeofday()
} );
return undef;
}
#########################
sub getMowerResponseWs {
my ( $param, $err, $data ) = @_;
my $hash = $param->{hash};
my $name = $hash->{NAME};
my $type = $hash->{TYPE};
my $statuscode = $param->{code} // '';
my $iam = "$type $name getMowerResponseWs:";
Log3 $name, 1, "$iam response time ". sprintf( "%.2f", ( gettimeofday() - $param->{t_begin} ) ) . ' s' if ( $param->{timeout} == 60 );
Log3 $name, 5, "$iam response polling after status-event \$statuscode >$statuscode<, \$err >$err<, \$param->url $param->{url} \n \$data >$data<";
if( !$err && $statuscode == 200 && $data) {
if ( $data eq '' ) {
Log3 $name, 2, "$iam no mower data present";
} else {
my $result = eval { JSON::XS->new->utf8( not $::unicodeEncoding )->decode( $data ) };
if ($@) {
Log3( $name, 2, "$iam - JSON error while request: $@");
} else {
$hash->{helper}{wsResult}{mower} = dclone( $result->{data} ) if ( AttrVal($name, 'debug', '') );
$hash->{helper}{mower}{attributes}{statistics} = dclone( $result->{data}{attributes}{statistics} );
if ( $hash->{helper}{use_position_polling} && $hash->{helper}{mower}{attributes}{capabilities}{position} ) {
my $cnt = 0;
my $tmp = [];
my $poslen = @{ $result->{data}{attributes}{positions} };
for ( $cnt = 0; $cnt < $poslen; $cnt++ ) {
if ( $hash->{helper}{searchpos}[ 0 ]{longitude} == $result->{data}{attributes}{positions}[ $cnt ]{longitude}
&& $hash->{helper}{searchpos}[ 0 ]{latitude} == $result->{data}{attributes}{positions}[ $cnt ]{latitude} || $cnt == $poslen -1) { # if nothing found take all
if ( $cnt > 0 ) {
my @ar;
push @ar, @{ $result->{data}{attributes}{positions} }[ 0 .. $cnt-1 ];
$hash->{helper}{mower}{attributes}{positions} = dclone( \@ar );
AlignArray( $hash );
FW_detailFn_Update ($hash);
} else {
$hash->{helper}{mower}{attributes}{positions} = [];
}
last;
}
}
$hash->{helper}{searchpos} = [ dclone $result->{data}{attributes}{positions}[ 0 ] ];
}
isErrorThanPrepare( $hash );
resetLastErrorIfCorrected( $hash );
# Update readings
readingsBeginUpdate($hash);
fillReadings( $hash );
# readingsBulkUpdate( $hash, 'mower_wsEvent', $hash->{helper}{wsResult}{type} ); #to do check what event
readingsBulkUpdate( $hash, 'mower_wsEvent', 'status-event' );
readingsBulkUpdateIfChanged( $hash, 'device_state', 'connected' );
readingsEndUpdate($hash, 1);
return undef;
}
}
} else {
readingsSingleUpdate( $hash, 'device_state', "additional Polling error statuscode $statuscode", 1 );
Log3 $name, 1, "$iam \$statuscode [$statuscode]\n\$err [$err],\n \$data [$data] \n\$param->url $param->{url}";
}
return undef;
}
#########################
sub getNewAccessToken {
my ($hash) = @_;
@ -1165,7 +1178,7 @@ sub Set {
return undef;
##########
} elsif ( $setName eq 'chargingStationPositionToAttribute' ) {
} elsif ( $setName eq 'chargingStationPositionToAttribute' && $hash->{helper}{mower}{attributes}{capabilities}{position} ) {
my $xm = $hash->{helper}{chargingStation}{longitude} // 10.1165;
my $ym = $hash->{helper}{chargingStation}{latitude} // 51.28;
@ -1173,14 +1186,14 @@ sub Set {
return undef;
##########
} elsif ( $setName eq 'defaultDesignAttributesToAttribute' ) {
} elsif ( $setName eq 'defaultDesignAttributesToAttribute' && $hash->{helper}{mower}{attributes}{capabilities}{position} ) {
my $design = $hash->{helper}{mapdesign};
CommandAttr( $hash, "$name mapDesignAttributes $design" );
return undef;
##########
} elsif ( $setName eq 'mapZonesTemplateToAttribute' ) {
} elsif ( $setName eq 'mapZonesTemplateToAttribute' && $hash->{helper}{mower}{attributes}{capabilities}{position} ) {
my $tpl = $hash->{helper}{mapZonesTpl};
CommandAttr( $hash, "$name mapZones $tpl" );
@ -1190,7 +1203,11 @@ sub Set {
} elsif ( ReadingsVal( $name, 'device_state', 'defined' ) !~ /defined|initialized|authentification|authenticated|update/ && $setName eq 'mowerScheduleToAttribute' ) {
my $calendarjson = eval {
JSON::XS->new->pretty(1)->encode ($hash->{helper}{mower}{attributes}{calendar}{tasks});
require JSON::PP;
my %ORDER=(start=>1,duration=>2,monday=>3,tuesday=>4,wednesday=>5,thursday=>6,friday=>7,saturday=>8,sunday=>9,workAreaId=>10);
JSON::PP->new->sort_by(
sub {($ORDER{$JSON::PP::a} // 999) <=> ($ORDER{$JSON::PP::b} // 999) or $JSON::PP::a cmp $JSON::PP::b})
->pretty(1)->utf8( not $unicodeEncoding )->encode( $hash->{helper}{mower}{attributes}{calendar}{tasks} )
};
return "$iam $@" if ($@);
@ -1226,7 +1243,16 @@ sub Set {
}
##########
} elsif ( ReadingsVal( $name, 'device_state', 'defined' ) !~ /defined|initialized|authentification|authenticated|update/ && $setName eq 'headlight' ) {
} elsif ( ReadingsVal( $name, 'device_state', 'defined' ) !~ /defined|initialized|authentification|authenticated|update/ && $setName =~ /^cuttingHeight$/ && defined $hash->{helper}{mower}{attributes}{settings}{cuttingHeight} ) {
if ( $setVal =~ /^(\d+)$/) {
CMD($hash ,$setName, $setVal);
return undef;
}
##########
} elsif ( ReadingsVal( $name, 'device_state', 'defined' ) !~ /defined|initialized|authentification|authenticated|update/ && $setName eq 'headlight' && $hash->{helper}{mower}{attributes}{capabilities}{headlights}) {
if ( $setVal =~ /^(ALWAYS_OFF|ALWAYS_ON|EVENING_ONLY|EVENING_AND_NIGHT)$/) {
CMD($hash ,$setName, $setVal);
@ -1256,14 +1282,14 @@ sub Set {
##########
} elsif ( ReadingsVal( $name, 'device_state', 'defined' ) !~ /defined|initialized|authentification|authenticated|update/ && $setName eq "sendJsonScheduleToAttribute" ) {
my $calendarjson = eval { decode_json ( $setVal ) };
my $calendarjson = eval { JSON::XS->new->decode ( $setVal ) };
return "$iam decode error: $@ \n $setVal" if ($@);
$calendarjson = eval {
require JSON::PP;
my %ORDER=(start=>1,duration=>2,monday=>3,tuesday=>4,wednesday=>5,thursday=>6,friday=>7,saturday=>8,sunday=>9);
my %ORDER=(start=>1,duration=>2,monday=>3,tuesday=>4,wednesday=>5,thursday=>6,friday=>7,saturday=>8,sunday=>9,workAreaId=>10);
JSON::PP->new->sort_by(
sub {($ORDER{$JSON::PP::a} // 999) <=> ($ORDER{$JSON::PP::b} // 999) or $JSON::PP::a cmp $JSON::PP::b})
->pretty(1)->encode( $calendarjson )
->pretty(1)->utf8( not $unicodeEncoding )->encode( $calendarjson )
};
return "$iam encode error: $@ in \$calendarjson" if ($@);
CommandAttr($hash,"$name mowerSchedule $calendarjson");
@ -1283,13 +1309,14 @@ sub Set {
return undef;
##########
} elsif ( ReadingsVal( $name, 'device_state', 'defined' ) !~ /defined|initialized|authentification|authenticated|update/ && $setName =~ /^(StartInWorkArea|cuttingHeightInWorkArea)$/ && AttrVal( $name, 'testing', '' ) ) {
} elsif ( ReadingsVal( $name, 'device_state', 'defined' ) !~ /defined|initialized|authentification|authenticated|update/
&& $setName =~ /^(StartInWorkArea|cuttingHeightInWorkArea)$/ && $hash->{helper}{mower}{attributes}{capabilities}{workAreas} && AttrVal( $name, 'testing', '' ) ) {
( $setVal, $setVal2 ) = $setVal =~ /(.*),(\d+)/ if ( $setVal =~/,/ && ! defined( $setVal2 ) );
my $id = undef;
$id = name2id( $hash, $setVal, 'workAreas' ) if ( $setVal !~ /^(\d+)$/ );
$setVal = $id // $setVal;
if ( $setVal =~ /^(\d+)$/ && ( $setVal2 =~ /^(\d+)$/ or !$setVal2 ) ) { # && $hash->{helper}{mower}{attributes}{capabilities}{workAreas}
if ( $setVal =~ /^(\d+)$/ && ( $setVal2 =~ /^(\d+)$/ or !$setVal2 ) ) { #
CMD($hash ,$setName, $setVal, $setVal2);
return undef;
@ -1299,13 +1326,14 @@ sub Set {
Log3 $name, 2, "$iam $setName : no valid Id or zone name for $setVal .";
##########
} elsif ( ReadingsVal( $name, 'device_state', 'defined' ) !~ /defined|initialized|authentification|authenticated|update/ && $setName =~ /^stayOutZone$/ && AttrVal( $name, 'testing', '' ) ) {
} elsif ( ReadingsVal( $name, 'device_state', 'defined' ) !~ /defined|initialized|authentification|authenticated|update/
&& $setName =~ /^stayOutZone$/ && $hash->{helper}{mower}{attributes}{capabilities}{stayOutZones} && AttrVal( $name, 'testing', '' ) ) {
( $setVal, $setVal2 ) = $setVal =~ /(.*),(enable|disable)/ if ( $setVal =~/,/ && ! defined( $setVal2 ) );
my $id = undef;
$id = name2id( $hash, $setVal, 'stayOutZones' ) if ( $setVal !~ /\b[0-9a-f]{8}\b-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-\b[0-9a-f]{12}\b/ );
$setVal = $id // $setVal;
if ( $setVal =~ /\b[0-9a-f]{8}\b-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-\b[0-9a-f]{12}\b/ ) { # && $hash->{helper}{mower}{attributes}{capabilities}{stayOutZones}
if ( $setVal =~ /\b[0-9a-f]{8}\b-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-\b[0-9a-f]{12}\b/ ) {
$setVal2 = $setVal2 eq 'enable' ? 'true' : 'false';
CMD($hash ,$setName, $setVal, $setVal2);
@ -1318,8 +1346,10 @@ sub Set {
}
##########
my $ret = " getNewAccessToken:noArg ParkUntilFurtherNotice:noArg ParkUntilNextSchedule:noArg Pause:noArg Start:selectnumbers,30,30,600,0,lin Park:selectnumbers,30,30,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 .= "mowerScheduleToAttribute:noArg sendScheduleFromAttributeToMower:noArg ";
$ret .= "cuttingHeight:1,2,3,4,5,6,7,8,9 " if ( defined $hash->{helper}{mower}{attributes}{settings}{cuttingHeight} );
$ret .= "defaultDesignAttributesToAttribute:noArg mapZonesTemplateToAttribute:noArg chargingStationPositionToAttribute:noArg " if ( $hash->{helper}{mower}{attributes}{capabilities}{position} );
$ret .= "headlight:ALWAYS_OFF,ALWAYS_ON,EVENING_ONLY,EVENING_AND_NIGHT " if ( $hash->{helper}{mower}{attributes}{capabilities}{headlights} );
##########
if ( $hash->{helper}{mower}{attributes}{capabilities}{workAreas} && defined ( $hash->{helper}{mower}{attributes}{workAreas} ) && AttrVal( $name, 'testing', '' ) ) {
@ -1396,27 +1426,24 @@ my $header = "Accept: application/vnd.api+json\r\nX-Api-Key: ".$client_id."\r\nA
elsif ($cmd[0] eq "confirmError") { $json = '{}'; $post = 'errors/confirm' }
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";
}
my $perl = eval { JSON::XS->new->decode (AttrVal( $name, 'mowerSchedule', '')) };
return "$iam decode error: $@ \n $perl" if ($@);
my $jsonSchedule = eval { JSON::XS->new->utf8( not $unicodeEncoding )->encode ($perl) };
return "$iam encode error: $@ \n $jsonSchedule" if ($@);
$hash->{helper}{mower_commandSend} .= ' '. $jsonSchedule;
$json = '{"data":{"type": "calendar","attributes":{"tasks":'.$jsonSchedule.'}}}';
$post = 'calendar';
}
elsif ($cmd[0] eq "sendJsonScheduleToMower" && $cmd[1]) {
my $perl = eval { decode_json ( $cmd[1] ) };
if ($@) {
return "$iam decode error: $@ \n $perl";
}
my $jsonSchedule = eval { encode_json ($perl) };
if ($@) {
return "$iam encode error: $@ \n $json";
}
my $perl = eval { JSON::XS->new->decode ( $cmd[1] ) };
return "$iam decode error: $@ \n $perl" if ($@);
my $jsonSchedule = eval { JSON::XS->new->utf8( not $unicodeEncoding )->encode ($perl) };
return "$iam encode error: $@ \n $jsonSchedule" if ($@);
$json = '{"data":{"type": "calendar","attributes":{"tasks":'.$jsonSchedule.'}}}';
$post = 'calendar';
}
@ -1447,11 +1474,11 @@ sub CMDResponse {
my $iam = "$type $name CMDResponse:";
Log3 $name, 1, "$iam response time ". sprintf( "%.2f", ( gettimeofday() - $param->{t_begin} ) ) . ' s' if ( $param->{timeout} == 60 );
Log3 $name, 1, "\ndebug $iam \n\$statuscode >$statuscode<\n\$err >$err<,\n \$data >$data< \n\$param->url $param->{url}" if ( AttrVal($name, 'debug', '') );
Log3 $name, 5, "$iam \n\$statuscode >$statuscode<\n\$err >$err<,\n \$data >$data< \n\$param->url $param->{url}";
if( !$err && $statuscode == 202 && $data ) {
my $result = eval { decode_json($data) };
my $result = eval { JSON::XS->new->decode($data) };
if ($@) {
Log3( $name, 2, "$iam - JSON error while request: $@");
@ -1494,7 +1521,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}<\n\$param->{data} >$param->{data}<";
Log3 $name, 2, "$iam \n\$statuscode >$statuscode<\n\$err >$err<,\n\$data >$data<\n\$param->{url} >$param->{url}<\n\$param->{data} >$param->{data}<";
return undef;
}
@ -1562,7 +1589,7 @@ sub Attr {
if( $cmd eq "set" ) {
my $perl = eval { decode_json ( $attrVal ) };
my $perl = eval { JSON::XS->new->decode ( $attrVal ) };
return "$iam $cmd $attrName decode error: $@ \n $attrVal" if ($@);
Log3 $name, 4, "$iam $cmd $attrName";
@ -1766,12 +1793,12 @@ sub Attr {
} elsif( $attrName eq "mowerSchedule" ) {
if( $cmd eq "set" ) {
my $perl = eval { decode_json ($attrVal) };
my $perl = eval { JSON::XS->new->decode ($attrVal) };
return "$iam $cmd $attrName decode error: $@ \n $perl" if ($@);
$attrVal = eval {
require JSON::PP;
my %ORDER=(start=>1,duration=>2,monday=>3,tuesday=>4,wednesday=>5,thursday=>6,friday=>7,saturday=>8,sunday=>9);
my %ORDER=(start=>1,duration=>2,monday=>3,tuesday=>4,wednesday=>5,thursday=>6,friday=>7,saturday=>8,sunday=>9,workAreaId=>10);
JSON::PP->new->sort_by(
sub {($ORDER{$JSON::PP::a} // 999) <=> ($ORDER{$JSON::PP::b} // 999) or $JSON::PP::a cmp $JSON::PP::b})
->pretty(1)->encode( $perl )
@ -1787,7 +1814,7 @@ sub Attr {
my $longitude = 10;
my $latitude = 52;
my $perl = eval { decode_json ($attrVal) };
my $perl = eval { JSON::XS->new->decode ($attrVal) };
return "$iam $cmd $attrName decode error: $@ \n $attrVal" if ($@);
@ -1975,15 +2002,20 @@ sub isErrorThanPrepare {
if ( $hash->{helper}{mower}{attributes}{mower}{errorCodeTimestamp} ) {
if ( ( $hash->{helper}{lasterror}{timestamp} != $hash->{helper}{mower}{attributes}{mower}{errorCodeTimestamp} ) && @{ $hash->{helper}{areapos} } > 1) {
if ( ( $hash->{helper}{lasterror}{timestamp} != $hash->{helper}{mower}{attributes}{mower}{errorCodeTimestamp} ) ) {
if ( $hash->{helper}{mower}{attributes}{capabilities}{position} && @{ $hash->{helper}{areapos} } > 1 ) {
$hash->{helper}{areapos}[ 0 ]{act} = 'N';
$hash->{helper}{areapos}[ 1 ]{act} = 'N';
$hash->{helper}{lasterror}{positions} = [ dclone( $hash->{helper}{areapos}[ 0 ] ), dclone( $hash->{helper}{areapos}[ 1 ] ) ];
}
my $ect = $hash->{helper}{mower}{attributes}{mower}{errorCodeTimestamp};
$hash->{helper}{areapos}[ 0 ]{act} = 'N';
$hash->{helper}{areapos}[ 1 ]{act} = 'N';
$hash->{helper}{lasterror}{positions} = [ dclone( $hash->{helper}{areapos}[ 0 ] ), dclone( $hash->{helper}{areapos}[ 1 ] ) ];
$hash->{helper}{lasterror}{timestamp} = $ect;
my $errc = $hash->{helper}{mower}{attributes}{mower}{errorCode};
$hash->{helper}{lasterror}{errordesc} = $::FHEM::Devices::AMConnect::Common::errortable->{$errc};
$hash->{helper}{lasterror}{errordesc} = $errortable->{$errc};
$hash->{helper}{lasterror}{errordate} = FmtDateTimeGMT( $ect / 1000 );
$hash->{helper}{lasterror}{errorstate} = $hash->{helper}{mower}{attributes}{mower}{state};
$hash->{helper}{lasterror}{errorzone} = $hash->{helper}{currentZone} if ( defined( $hash->{helper}{currentZone} ) );
@ -1991,7 +2023,7 @@ sub isErrorThanPrepare {
my $tmp = dclone( $hash->{helper}{lasterror} );
unshift ( @{ $hash->{helper}{errorstack} }, $tmp );
pop ( @{ $hash->{helper}{errorstack} } ) if ( @{ $hash->{helper}{errorstack} } > $hash->{helper}{errorstackmax} );
::FHEM::Devices::AMConnect::Common::FW_detailFn_Update ($hash);
FW_detailFn_Update ($hash);
}
@ -2011,7 +2043,7 @@ sub resetLastErrorIfCorrected {
$hash->{helper}{lasterror}{errordesc} = '-';
$hash->{helper}{lasterror}{errordate} = '';
$hash->{helper}{lasterror}{errorstate} = '';
::FHEM::Devices::AMConnect::Common::FW_detailFn_Update ($hash);
FW_detailFn_Update ($hash);
}
@ -2234,10 +2266,10 @@ sub GetJson() {
my $name = $2;
my $hash = $::defs{$name};
my $jsonMime = "application/json";
my $jsonData = eval { encode_json ( $hash->{helper}{mapupdate} ) };
my $jsonData = eval { JSON::XS->new->encode ( $hash->{helper}{mapupdate} ) };
if ($@) {
Log3 $name, 2, "$type $name encode_json error: $@";
Log3 $name, 2, "$type $name encode error: $@";
return ( "text/plain; charset=utf-8", "No AutomowerConnect device for webhook $request" );
}
@ -2370,7 +2402,8 @@ sub fillReadings {
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, "model", $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} ) if ( $hash->{helper}{mower}{attributes}{$pref}{override}{action} );
@ -2382,10 +2415,10 @@ sub fillReadings {
$pref = 'statistics';
my $noCol = $hash->{helper}{statistics}{currentDayCollisions};
readingsBulkUpdateIfChanged( $hash, $pref."_numberOfCollisions", '(' . $noCol . '/' . $hash->{helper}{statistics}{lastDayCollisions} . '/' . $hash->{helper}{mower}{attributes}{$pref}{numberOfCollisions} . ')' );
readingsBulkUpdateIfChanged( $hash, $pref."_newGeoDataSets", $hash->{helper}{newdatasets} );
readingsBulkUpdateIfChanged( $hash, $pref."_newGeoDataSets", $hash->{helper}{newdatasets} ) if ( $hash->{helper}{mower}{attributes}{capabilities}{position} );
$pref = 'settings';
readingsBulkUpdateIfChanged( $hash, $pref."_headlight", $hash->{helper}{mower}{attributes}{$pref}{headlight}{mode} );
readingsBulkUpdateIfChanged( $hash, $pref."_cuttingHeight", $hash->{helper}{mower}{attributes}{$pref}{cuttingHeight} );
readingsBulkUpdateIfChanged( $hash, $pref."_headlight", $hash->{helper}{mower}{attributes}{$pref}{headlight}{mode} ) if ( $hash->{helper}{mower}{attributes}{capabilities}{headlights} );
readingsBulkUpdateIfChanged( $hash, $pref."_cuttingHeight", $hash->{helper}{mower}{attributes}{$pref}{cuttingHeight} ) if ( defined $hash->{helper}{mower}{attributes}{$pref}{cuttingHeight} );
$pref = 'status';
my $connected = $hash->{helper}{mower}{attributes}{metadata}{connected};
readingsBulkUpdateIfChanged( $hash, $pref."_connected", ( $connected ? "CONNECTED($connected)" : "OFFLINE($connected)") );
@ -2402,16 +2435,19 @@ sub calculateStatistics {
my $name = $hash->{NAME};
my @time = localtime();
$hash->{helper}{statistics}{lastDayTrack} = $hash->{helper}{statistics}{currentDayTrack};
$hash->{helper}{statistics}{lastDayArea} = $hash->{helper}{statistics}{currentDayArea};
$hash->{helper}{statistics}{lastDayTime} = $hash->{helper}{statistics}{currentDayTime};
$hash->{helper}{statistics}{lastDayCollisions} = $hash->{helper}{mower}{attributes}{statistics}{numberOfCollisions} - $hash->{helper}{statistics}{numberOfCollisionsOld};
$hash->{helper}{statistics}{numberOfCollisionsOld} = $hash->{helper}{mower}{attributes}{statistics}{numberOfCollisions};
$hash->{helper}{statistics}{currentWeekTrack} += $hash->{helper}{statistics}{currentDayTrack};
$hash->{helper}{statistics}{currentWeekArea} += $hash->{helper}{statistics}{currentDayArea};
$hash->{helper}{statistics}{currentWeekTime} += $hash->{helper}{statistics}{currentDayTime};
$hash->{helper}{statistics}{currentWeekCollisions} += $hash->{helper}{statistics}{lastDayCollisions};
if ( $hash->{helper}{mower}{attributes}{capabilities}{position} ) {
$hash->{helper}{statistics}{lastDayTrack} = $hash->{helper}{statistics}{currentDayTrack};
$hash->{helper}{statistics}{lastDayArea} = $hash->{helper}{statistics}{currentDayArea};
$hash->{helper}{statistics}{lastDayTime} = $hash->{helper}{statistics}{currentDayTime};
$hash->{helper}{statistics}{currentWeekTrack} += $hash->{helper}{statistics}{currentDayTrack};
$hash->{helper}{statistics}{currentWeekArea} += $hash->{helper}{statistics}{currentDayArea};
$hash->{helper}{statistics}{currentWeekTime} += $hash->{helper}{statistics}{currentDayTime};
}
$hash->{helper}{statistics}{currentDayTrack} = 0;
$hash->{helper}{statistics}{currentDayArea} = 0;
$hash->{helper}{statistics}{currentDayTime} = 0;
@ -2627,11 +2663,13 @@ sub listMowerData {
$ret .= '<tr class="column '.( $cnt++ % 2 ? 'odd' : 'even' ).'"><td> $hash->{helper}{mower}{attributes}{planner}{restrictedReason} &emsp;</td><td> ' . $hash->{helper}{mower}{attributes}{planner}{restrictedReason} . ' </td><td> </td></tr>';
$ret .= '<tr class="column '.( $cnt++ % 2 ? 'odd' : 'even' ).'"><td> $hash->{helper}{mower}{attributes}{metadata}{connected} &emsp;</td><td> ' . $hash->{helper}{mower}{attributes}{metadata}{connected} . ' </td><td> </td></tr>';
$ret .= '<tr class="column '.( $cnt++ % 2 ? 'odd' : 'even' ).'"><td> $hash->{helper}{mower}{attributes}{metadata}{statusTimestamp} &emsp;</td><td> ' . $hash->{helper}{mower}{attributes}{metadata}{statusTimestamp} . ' </td><td> ms </td></tr>';
$ret .= '<tr class="column '.( $cnt++ % 2 ? 'odd' : 'even' ).'"><td> $hash->{helper}{mower}{attributes}{positions}[0]{longitude} &emsp;</td><td> ' . $hash->{helper}{mower}{attributes}{positions}[0]{longitude} . ' </td><td> decimal degree </td></tr>';
$ret .= '<tr class="column '.( $cnt++ % 2 ? 'odd' : 'even' ).'"><td> $hash->{helper}{mower}{attributes}{positions}[0]{latitude} &emsp;</td><td> ' . $hash->{helper}{mower}{attributes}{positions}[0]{latitude} . ' </td><td> decimal degree </td></tr>';
$ret .= '<tr class="column '.( $cnt++ % 2 ? 'odd' : 'even' ).'"><td> $hash->{helper}{mower}{attributes}{settings}{cuttingHeight} &emsp;</td><td> ' . $hash->{helper}{mower}{attributes}{settings}{cuttingHeight} . ' </td><td> </td></tr>';
$ret .= '<tr class="column '.( $cnt++ % 2 ? 'odd' : 'even' ).'"><td> $hash->{helper}{mower}{attributes}{settings}{headlight}{mode} &emsp;</td><td> ' . $hash->{helper}{mower}{attributes}{settings}{headlight}{mode} . ' </td><td> </td></tr>';
# $ret .= '<tr class="column '.( $cnt++ % 2 ? 'odd' : 'even' ).'"><td> $hash->{helper}{mower}{attributes}{statistics}{cuttingBladeUsageTime} &emsp;</td><td> ' . $hash->{helper}{mower}{attributes}{statistics}{cuttingBladeUsageTime} . ' </td><td> </td></tr>';
if ( $hash->{helper}{mower}{attributes}{capabilities}{position} ) {
$ret .= '<tr class="column '.( $cnt++ % 2 ? 'odd' : 'even' ).'"><td> $hash->{helper}{mower}{attributes}{positions}[0]{longitude} &emsp;</td><td> ' . $hash->{helper}{mower}{attributes}{positions}[0]{longitude} . ' </td><td> decimal degree </td></tr>';
$ret .= '<tr class="column '.( $cnt++ % 2 ? 'odd' : 'even' ).'"><td> $hash->{helper}{mower}{attributes}{positions}[0]{latitude} &emsp;</td><td> ' . $hash->{helper}{mower}{attributes}{positions}[0]{latitude} . ' </td><td> decimal degree </td></tr>';
}
$ret .= '<tr class="column '.( $cnt++ % 2 ? 'odd' : 'even' ).'"><td> $hash->{helper}{mower}{attributes}{settings}{cuttingHeight} &emsp;</td><td> ' . $hash->{helper}{mower}{attributes}{settings}{cuttingHeight} . ' </td><td> </td></tr>' if ( defined $hash->{helper}{mower}{attributes}{settings}{cuttingHeight} );
$ret .= '<tr class="column '.( $cnt++ % 2 ? 'odd' : 'even' ).'"><td> $hash->{helper}{mower}{attributes}{settings}{headlight}{mode} &emsp;</td><td> ' . $hash->{helper}{mower}{attributes}{settings}{headlight}{mode} . ' </td><td> </td></tr>' if ( $hash->{helper}{mower}{attributes}{settings}{headlight}{mode} );
$ret .= '<tr class="column '.( $cnt++ % 2 ? 'odd' : 'even' ).'"><td> $hash->{helper}{mower}{attributes}{statistics}{cuttingBladeUsageTime} &emsp;</td><td> ' . $hash->{helper}{mower}{attributes}{statistics}{cuttingBladeUsageTime} . ' </td><td> </td></tr>' if ( defined $hash->{helper}{mower}{attributes}{statistics}{cuttingBladeUsageTime} );
$ret .= '<tr class="column '.( $cnt++ % 2 ? 'odd' : 'even' ).'"><td> $hash->{helper}{mower}{attributes}{statistics}{numberOfChargingCycles} &emsp;</td><td> ' . $hash->{helper}{mower}{attributes}{statistics}{numberOfChargingCycles} . ' </td><td> </td></tr>';
$ret .= '<tr class="column '.( $cnt++ % 2 ? 'odd' : 'even' ).'"><td> $hash->{helper}{mower}{attributes}{statistics}{numberOfCollisions} &emsp;</td><td> ' . $hash->{helper}{mower}{attributes}{statistics}{numberOfCollisions} . ' </td><td> </td></tr>';
$ret .= '<tr class="column '.( $cnt++ % 2 ? 'odd' : 'even' ).'"><td> $hash->{helper}{mower}{attributes}{statistics}{totalChargingTime} &emsp;</td><td> ' . $hash->{helper}{mower}{attributes}{statistics}{totalChargingTime} . ' </td><td> s </td></tr>';
@ -2861,6 +2899,21 @@ sub getTpFile {
return undef;
}
#########################
sub getDefaultScheduleAsJSON {
my ( $name ) = @_;
my $hash = $defs{$name};
my $json = eval {
require JSON::PP;
my %ORDER=(start=>1,duration=>2,monday=>3,tuesday=>4,wednesday=>5,thursday=>6,friday=>7,saturday=>8,sunday=>9,workAreaId=>10);
JSON::PP->new->sort_by(
sub {($ORDER{$JSON::PP::a} // 999) <=> ($ORDER{$JSON::PP::b} // 999) or $JSON::PP::a cmp $JSON::PP::b})
->utf8( not $unicodeEncoding )->encode( $hash->{helper}{mower}{attributes}{calendar}{tasks} )
};
return "$name getDefaultScheduleAsJSON: $@" if ($@);
return $json;
}
#########################
sub getDesignAttr {
my ( $hash ) = @_;
@ -2965,7 +3018,7 @@ sub wsRead {
$buf =~ s/}\{/},{/g;
$buf = "[${buf}]";
my $bufres = eval { decode_json( $buf ) };
my $bufres = eval { JSON::XS->new->decode( $buf ) };
if ( $@ ) {

View File

@ -17,6 +17,7 @@ if ( !(typeof FW_version === 'undefined') )
});
}
function AutomowerConnectShowError( ctx, div, dev, picx, picy, errdesc, erray ) {
// ERROR BANNER
ctx.beginPath();
@ -390,7 +391,7 @@ function AutomowerConnectGetHull ( path ) {
const div = document.getElementById(data.type+'_'+data.name+'_div');
const pos =data.posxy;
if ( div && div.getAttribute( 'data-hullCalculate' ) && typeof hull === "function" ){
if ( div && div.getAttribute( 'data-hullCalculate' ) ){
const wypts = [];
for ( let i = 0; i < pos.length; i+=3 ){
@ -402,8 +403,21 @@ function AutomowerConnectGetHull ( path ) {
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)} );
if ( typeof hull === "function" ){
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)} );
} else if ( typeof loadScript === 'function' ){
loadScript('automowerconnect/hull.js', ()=>{
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)} );
});
}
}
@ -424,34 +438,36 @@ function AutomowerConnectSubtractHull ( path ) {
const div = document.getElementById(data.type+'_'+data.name+'_div');
const pos =data.posxy;
if ( div && div.getAttribute( 'data-hullSubtract' ) && typeof hull === "function" ){
const wypts = [];
const hsub = div.getAttribute( 'data-hullSubtract' );
const wyres = div.getAttribute( 'data-hullResolution' );
var hullpts = [];
if ( div && div.getAttribute( 'data-hullSubtract' ) ){
for ( let i = 0; i < pos.length; i+=3 ){
if ( typeof hull === "function" ) {
const wypts = [];
const hsub = div.getAttribute( 'data-hullSubtract' );
const wyres = div.getAttribute( 'data-hullResolution' );
var hullpts = [];
if ( pos[i+2] == "M") wypts.push( [ pos[i], pos[i+1] ] );
for ( let i = 0; i < pos.length; i+=3 ){
}
if ( pos[i+2] == "M") wypts.push( [ pos[i], pos[i+1] ] );
for ( let i = 0; i < hsub; i++ ){
}
if ( wypts.length > 50 ) {
for ( let i = 0; i < hsub; i++ ){
hullpts = hull( wypts, wyres );
if ( wypts.length > 50 ) {
for ( let k = 0; k < hullpts.length; k++ ){
hullpts = hull( wypts, wyres );
for ( let m = 0; m < wypts.length; m++ ){
for ( let k = 0; k < hullpts.length; k++ ){
if ( hullpts[k][0] == wypts[m][0] && hullpts[k][1] == wypts[m][1] ) {
for ( let m = 0; m < wypts.length; m++ ){
wypts.splice( m, 1 );
break;
//~ m--;
//~ k++;
if ( hullpts[k][0] == wypts[m][0] && hullpts[k][1] == wypts[m][1] ) {
wypts.splice( m, 1 );
break;
}
}
@ -459,14 +475,59 @@ function AutomowerConnectSubtractHull ( path ) {
}
hullpts = hull( wypts, wyres );
}
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)} );
} else if (typeof loadScript === 'function' ) {
loadScript( 'automowerconnect/hull.js', ()=>{
const wypts = [];
const hsub = div.getAttribute( 'data-hullSubtract' );
const wyres = div.getAttribute( 'data-hullResolution' );
var hullpts = [];
for ( let i = 0; i < pos.length; i+=3 ){
if ( pos[i+2] == "M") wypts.push( [ pos[i], pos[i+1] ] );
}
for ( let i = 0; i < hsub; i++ ){
if ( wypts.length > 50 ) {
hullpts = hull( wypts, wyres );
for ( let k = 0; k < hullpts.length; k++ ){
for ( let m = 0; m < wypts.length; m++ ){
if ( hullpts[k][0] == wypts[m][0] && hullpts[k][1] == wypts[m][1] ) {
wypts.splice( m, 1 );
break;
}
}
}
}
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)} );
});
}
FW_cmd( FW_root+"?cmd=attr "+data.name+" mowingAreaHull "+JSON.stringify( hullpts )+"&XHR=1",function(data){setTimeout(()=>{window.location.reload()},500)} );
}
}
@ -480,13 +541,20 @@ function AutomowerConnectPanelCmd ( panelcmd ) {
FW_cmd( FW_root+"?cmd="+panelcmd+"&XHR=1" );
}
function AutomowerConnectHandleInput ( dev ) {
function AutomowerConnectHandleInput ( dev, hasWorkAreaId, workAreaId ) {
let cal = JSON.parse( document.querySelector( '#amc_'+dev+'_schedule_div' ).getAttribute( 'data-amc_schedule' ) );
let cali = document.querySelector('#amc_'+dev+'_index').value || cal.length;
if ( cali > cal.length ) cali = cal.length;
if ( cali > 13 ) cali = 13;
for (let i=cal.length;i<=cali;i++) { cal.push( { "start":0, "duration":1439, "monday":false, "tuesday":false, "wednesday":false, "thursday":false, "friday":false, "saturday":false, "sunday":false } ) }
for (let i=cal.length;i<=cali;i++) {
if ( hasWorkAreaId ) {
cal.push( { "start":0, "duration":1439, "monday":false, "tuesday":false, "wednesday":false, "thursday":false, "friday":false, "saturday":false, "sunday":false, "workAreaId":workAreaId } )
} else {
cal.push( { "start":0, "duration":1439, "monday":false, "tuesday":false, "wednesday":false, "thursday":false, "friday":false, "saturday":false, "sunday":false } )
}
}
//~ console.log('cali: '+cali+' cal.length: '+cal.length);
let elements = ["start", "duration"];
@ -497,7 +565,7 @@ function AutomowerConnectHandleInput ( dev ) {
if ( isNaN( hour ) && item == "start" ) hour = 0;
if ( isNaN( min ) && item == "start" ) min = 0;
if ( isNaN( hour ) && item == "duration" ) hour = 23;
if ( isNaN( hour ) && item == "duration" ) hour = 23*60;
if ( isNaN( min ) && item == "duration" ) min = 59;
cal[cali][item] = hour + min;
@ -546,7 +614,11 @@ function AutomowerConnectHandleInput ( dev ) {
if ( cali > cal.length -1 ) cali = cal.length -1;
if ( !cal[cali] ) {
cal = [ { "start":0, "duration":1440, "monday":true, "tuesday":true, "wednesday":true, "thursday":true, "friday":true, "saturday":true, "sunday":true } ];
if ( hasWorkAreaId ) {
cal = [ { "start":0, "duration":1440, "monday":true, "tuesday":true, "wednesday":true, "thursday":true, "friday":true, "saturday":true, "sunday":true, "workAreaId":workAreaId } ];
} else {
cal = [ { "start":0, "duration":1440, "monday":true, "tuesday":true, "wednesday":true, "thursday":true, "friday":true, "saturday":true, "sunday":true } ];
}
cali = 0;
}
@ -556,8 +628,11 @@ function AutomowerConnectHandleInput ( dev ) {
shdl += "<style>";
shdl += ".amc_schedule_tabth{margin:auto; width:50%; text-align:left;}";
shdl += "</style>";
shdl += "<table id='amc_"+dev+"_schedule_table0' class='amc_schedule_table col_bg block wide' ><tbody>";
shdl += "<table id='amc_"+dev+"_schedule_table0' class='amc_schedule_table col_bg block wide'><thead>";
//~ if ( hasWorkAreaId ) shdl += "<td><input id='amc_"+dev+"_workareaid' type='number' value='"+workAreaId+"' readonly /></td>";
if ( hasWorkAreaId ) shdl += "<tr class='even amc_schedule_tabth' ><th colspan='100%' >Calendar for Work Area Id: "+workAreaId+"</th></tr>";
shdl += "<tr class='even amc_schedule_tabth' ><th>Index</th><th>Start</th><th>Duration</th><th>Mon.</th><th>Tue.</th><th>Wed.</th><th>Thu.</th><th>Fri.</th><th>Sat.</th><th>Sun.</th><th></th></tr>";
shdl += "</thead><tbody>";
shdl += "<tr class='even'>";
shdl += "<td><input id='amc_"+dev+"_index' type='number' value='"+cali+"' min='0' max='13' step='1' size='3' /></td>";
shdl += "<td><input id='amc_"+dev+"_start' type='time' value='"+("0"+parseInt(cal[cali].start/60)).slice(-2)+":"+("0"+cal[cali].start%60).slice(-2)+"' /></td>";
@ -569,7 +644,7 @@ function AutomowerConnectHandleInput ( dev ) {
shdl += "<td><input id='amc_"+dev+"_friday' type='checkbox' "+(cal[cali].friday?"checked='checked'":"")+" /></td>";
shdl += "<td><input id='amc_"+dev+"_saturday' type='checkbox' "+(cal[cali].saturday?"checked='checked'":"")+" /></td>";
shdl += "<td><input id='amc_"+dev+"_sunday' type='checkbox' "+(cal[cali].sunday?"checked='checked'":"")+" /></td>";
shdl += "<td><button id='amc_"+dev+"_schedule_button_plus' title='add: prepare a data set and click &plusmn;&#013;delete: unckeck each weekday and click &plusmn;&#013;reset: fill any time field with -- and click &plusmn;' onclick=' AutomowerConnectHandleInput ( \""+dev+"\" )' style='padding-bottom:4px; font-weight:bold; font-size:16pt; ' >&ensp;&plusmn;&ensp;</button></td>";
shdl += "<td><button id='amc_"+dev+"_schedule_button_plus' title='add: prepare a data set and click &plusmn;&#013;delete: unckeck each weekday and click &plusmn;&#013;reset: fill any time field with -- and click &plusmn;' onclick=' AutomowerConnectHandleInput ( \""+dev+"\", "+hasWorkAreaId+", "+workAreaId+" )' style='padding-bottom:4px; font-weight:bold; font-size:16pt; ' >&ensp;&plusmn;&ensp;</button></td>";
shdl += "</tr><tr style='border-bottom:1px solid black'><td colspan='100%'></td></tr>";
for (let i=0; i< cal.length; i++){
@ -583,13 +658,14 @@ function AutomowerConnectHandleInput ( dev ) {
shdl += "<td>&thinsp;"+(cal[i].thursday?"&#x2611;":"&#x2610;")+"</td>";
shdl += "<td>&thinsp;"+(cal[i].friday?"&#x2611;":"&#x2610;")+"</td>";
shdl += "<td>&thinsp;"+(cal[i].saturday?"&#x2611;":"&#x2610;")+"</td>";
shdl += "<td>&thinsp;"+(cal[i].sunday?"&#x2611;":"&#x2610;")+"</td><td></td>";
shdl += "<td>&thinsp;"+(cal[i].sunday?"&#x2611;":"&#x2610;")+"</td>";
shdl += "<td></td>";
shdl += "</tr>";
}
shdl += "<tr>";
let nrows = cal.length*11+2;
shdl += "<td colspan='12' ><textarea style='font-size:10pt; ' readOnly wrap='off' cols='62' rows='"+(nrows > 35 ? 35 : nrows)+"'>"+JSON.stringify(cal,null," ")+"</textarea></td>";
let nrows = cal.length*(hasWorkAreaId?12:11)+2;
shdl += "<td colspan='12' ><textarea style='font-size:10pt; width:98%; ' readOnly wrap='off' cols='62' rows='"+(nrows > (3*(hasWorkAreaId?12:11)+2) ? (3*(hasWorkAreaId?12:11)+2) : nrows)+"'>"+JSON.stringify(cal,null," ")+"</textarea></td>";
shdl += "</tr>";
shdl += "</tbody></table>";
shdl += "</div>";
@ -600,72 +676,88 @@ function AutomowerConnectHandleInput ( dev ) {
}
function AutomowerConnectSchedule ( dev, cal ) {
function AutomowerConnectSchedule ( dev ) {
let el = document.getElementById('amc_'+dev+'_schedule_div');
if ( el ) el.remove();
let cali = 0;
let shdl = "<div id='amc_"+dev+"_schedule_div' data-amc_schedule='"+JSON.stringify(cal)+"' title='Schedule editor'>";
shdl += "<style>";
shdl += ".amc_schedule_tabth{text-align:left;}";
shdl += "</style>";
shdl += "<table id='amc_"+dev+"_schedule_table0' class='amc_schedule_table col_bg block wide'><tbody>";
shdl += "<tr class='even amc_schedule_tabth ' ><th>Index</th><th>Start</th><th>Duration</th><th>Mon.</th><th>Tue.</th><th>Wed.</th><th>Thu.</th><th>Fri.</th><th>Sat.</th><th>Sun.</td><th></th></tr>";
shdl += "<tr class='even'>";
shdl += "<td><input id='amc_"+dev+"_index' type='number' value='"+cali+"' min='0' max='13' step='1' size='3' /></td>";
shdl += "<td><input id='amc_"+dev+"_start' type='time' value='"+("0"+parseInt(cal[cali].start/60)).slice(-2)+":"+("0"+cal[cali].start%60).slice(-2)+"' /></td>";
shdl += "<td><input id='amc_"+dev+"_duration' type='time' value='"+("0"+parseInt(cal[cali].duration/60)).slice(-2)+":"+("0"+cal[cali].duration%60).slice(-2)+"' /></td>";
shdl += "<td><input id='amc_"+dev+"_monday' type='checkbox' "+(cal[cali].monday?"checked='checked'":"")+" /></td>";
shdl += "<td><input id='amc_"+dev+"_tuesday' type='checkbox' "+(cal[cali].tuesday?"checked='checked'":"")+" /></td>";
shdl += "<td><input id='amc_"+dev+"_wednesday' type='checkbox' "+(cal[cali].wednesday?"checked='checked'":"")+" /></td>";
shdl += "<td><input id='amc_"+dev+"_thursday' type='checkbox' "+(cal[cali].thursday?"checked='checked'":"")+" /></td>";
shdl += "<td><input id='amc_"+dev+"_friday' type='checkbox' "+(cal[cali].friday?"checked='checked'":"")+" /></td>";
shdl += "<td><input id='amc_"+dev+"_saturday' type='checkbox' "+(cal[cali].saturday?"checked='checked'":"")+" /></td>";
shdl += "<td><input id='amc_"+dev+"_sunday' type='checkbox' "+(cal[cali].sunday?"checked='checked'":"")+" /></td>";
shdl += "<td><button id='amc_"+dev+"_schedule_button_plus' title='add: prepare a data set and click &plusmn;&#013;delete: unckeck each weekday and click &plusmn;&#013;reset: fill any time field with -- and click &plusmn;' onclick=' AutomowerConnectHandleInput ( \"am430x1\" )' style='padding-bottom:4px; font-weight:bold; font-size:16pt; ' >&ensp;&plusmn;&ensp;</button></td>";
shdl += "</tr><tr style='border-bottom:1px solid black'><td colspan='100%'></td></tr>";
let hasWorkAreaId = false;
let workAreaId = null;
for (let i=0; i< cal.length; i++){
shdl += "<tr class='"+( i % 2 ? 'even' : 'odd' )+"' >";
shdl += "<td >&thinsp;"+i+"</td>";
shdl += "<td>&thinsp;"+("0"+parseInt(cal[i].start/60)).slice(-2)+":"+("0"+cal[i].start%60).slice(-2)+"</td>";
shdl += "<td>&thinsp;"+("0"+parseInt(cal[i].duration/60)).slice(-2)+":"+("0"+cal[i].duration%60).slice(-2)+"</td>";
shdl += "<td>&thinsp;"+(cal[i].monday?"&#x2611;":"&#x2610;")+"</td>";
shdl += "<td>&thinsp;"+(cal[i].tuesday?"&#x2611;":"&#x2610;")+"</td>";
shdl += "<td>&thinsp;"+(cal[i].wednesday?"&#x2611;":"&#x2610;")+"</td>";
shdl += "<td>&thinsp;"+(cal[i].thursday?"&#x2611;":"&#x2610;")+"</td>";
shdl += "<td>&thinsp;"+(cal[i].friday?"&#x2611;":"&#x2610;")+"</td>";
shdl += "<td>&thinsp;"+(cal[i].saturday?"&#x2611;":"&#x2610;")+"</td>";
shdl += "<td>&thinsp;"+(cal[i].sunday?"&#x2611;":"&#x2610;")+"</td><td></td>";
FW_cmd( FW_root+"?cmd={ FHEM::Devices::AMConnect::Common::getDefaultScheduleAsJSON( \""+dev+"\" ) }&XHR=1",( cal ) => {
cal = JSON.parse( cal );
if ( cal.length == 0 ) cal = [ { "start":0, "duration":1440, "monday":true, "tuesday":true, "wednesday":true, "thursday":true, "friday":true, "saturday":true, "sunday":true } ];
if ( cal[0].workAreaId != null ) {
hasWorkAreaId = true;
workAreaId = cal[0].workAreaId
}
let cali = 0;
let shdl = "<div id='amc_"+dev+"_schedule_div' data-amc_schedule='"+JSON.stringify(cal)+"' title='Schedule editor' class='col_fg'>";
shdl += "<style>";
shdl += ".amc_schedule_tabth{text-align:left;}";
shdl += "</style>";
shdl += "<table id='amc_"+dev+"_schedule_table0' class='amc_schedule_table col_bg block wide'><thead>";
//~ if ( hasWorkAreaId ) shdl += "<td><input id='amc_"+dev+"_workareaid' type='number' value='"+workAreaId+"' readonly /></td>";
if ( hasWorkAreaId ) shdl += "<tr class='even amc_schedule_tabth' ><th colspan='100%' >Calendar for Work Area Id: "+workAreaId+"</th></tr>";
shdl += "<tr class='even amc_schedule_tabth' ><th>Index</th><th>Start</th><th>Duration</th><th>Mon.</th><th>Tue.</th><th>Wed.</th><th>Thu.</th><th>Fri.</th><th>Sat.</th><th>Sun.</th><th></th></tr>";
shdl += "</thead><tbody>";
shdl += "<tr class='even'>";
shdl += "<td><input id='amc_"+dev+"_index' type='number' value='"+cali+"' min='0' max='13' step='1' size='3' /></td>";
shdl += "<td><input id='amc_"+dev+"_start' type='time' value='"+("0"+parseInt(cal[cali].start/60)).slice(-2)+":"+("0"+cal[cali].start%60).slice(-2)+"' /></td>";
shdl += "<td><input id='amc_"+dev+"_duration' type='time' value='"+("0"+parseInt(cal[cali].duration/60)).slice(-2)+":"+("0"+cal[cali].duration%60).slice(-2)+"' /></td>";
shdl += "<td><input id='amc_"+dev+"_monday' type='checkbox' "+(cal[cali].monday?"checked='checked'":"")+" /></td>";
shdl += "<td><input id='amc_"+dev+"_tuesday' type='checkbox' "+(cal[cali].tuesday?"checked='checked'":"")+" /></td>";
shdl += "<td><input id='amc_"+dev+"_wednesday' type='checkbox' "+(cal[cali].wednesday?"checked='checked'":"")+" /></td>";
shdl += "<td><input id='amc_"+dev+"_thursday' type='checkbox' "+(cal[cali].thursday?"checked='checked'":"")+" /></td>";
shdl += "<td><input id='amc_"+dev+"_friday' type='checkbox' "+(cal[cali].friday?"checked='checked'":"")+" /></td>";
shdl += "<td><input id='amc_"+dev+"_saturday' type='checkbox' "+(cal[cali].saturday?"checked='checked'":"")+" /></td>";
shdl += "<td><input id='amc_"+dev+"_sunday' type='checkbox' "+(cal[cali].sunday?"checked='checked'":"")+" /></td>";
shdl += "<td><button id='amc_"+dev+"_schedule_button_plus' title='add: prepare a data set and click &plusmn;&#013;delete: unckeck each weekday and click &plusmn;&#013;reset: fill any time field with -- and click &plusmn;' onclick='AutomowerConnectHandleInput ( \""+dev+"\", "+hasWorkAreaId+", "+workAreaId+" )' style='padding-bottom:4px; font-weight:bold; font-size:16pt; ' >&ensp;&plusmn;&ensp;</button></td>";
shdl += "</tr><tr style='border-bottom:1px solid black'><td colspan='100%'></td></tr>";
for (let i=0; i< cal.length; i++){
shdl += "<tr class='"+( i % 2 ? 'even' : 'odd' )+"' >";
shdl += "<td >&thinsp;"+i+"</td>";
shdl += "<td>&thinsp;"+("0"+parseInt(cal[i].start/60)).slice(-2)+":"+("0"+cal[i].start%60).slice(-2)+"</td>";
shdl += "<td>&thinsp;"+("0"+parseInt(cal[i].duration/60)).slice(-2)+":"+("0"+cal[i].duration%60).slice(-2)+"</td>";
shdl += "<td>&thinsp;"+(cal[i].monday?"&#x2611;":"&#x2610;")+"</td>";
shdl += "<td>&thinsp;"+(cal[i].tuesday?"&#x2611;":"&#x2610;")+"</td>";
shdl += "<td>&thinsp;"+(cal[i].wednesday?"&#x2611;":"&#x2610;")+"</td>";
shdl += "<td>&thinsp;"+(cal[i].thursday?"&#x2611;":"&#x2610;")+"</td>";
shdl += "<td>&thinsp;"+(cal[i].friday?"&#x2611;":"&#x2610;")+"</td>";
shdl += "<td>&thinsp;"+(cal[i].saturday?"&#x2611;":"&#x2610;")+"</td>";
shdl += "<td>&thinsp;"+(cal[i].sunday?"&#x2611;":"&#x2610;")+"</td>";
shdl += "<td></td>";
shdl += "</tr>";
}
shdl += "<tr>";
let nrows = cal.length*(hasWorkAreaId?12:11)+2;
shdl += "<td colspan='12' ><textarea style='font-size:10pt; width:98%; ' readOnly wrap='off' cols='62' rows='"+(nrows > (3*(hasWorkAreaId?12:11)+2) ? (3*(hasWorkAreaId?12:11)+2) : nrows)+"'>"+JSON.stringify(cal,null," ")+"</textarea></td>";
shdl += "</tr>";
}
shdl += "<tr>";
let nrows = cal.length*11+2;
shdl += "<td colspan='12' ><textarea style='font-size:10pt; ' readOnly wrap='off' cols='62' rows='"+(nrows > 35 ? 35 : nrows)+"'>"+JSON.stringify(cal,null," ")+"</textarea></td>";
shdl += "</tr>";
shdl += "</tbody></table>";
shdl += "</div>";
let schedule = new DOMParser().parseFromString( shdl, "text/html" ).querySelector( '#amc_'+dev+'_schedule_div' );
document.querySelector('body').append( schedule );
document.querySelector( "#amc_"+dev+"_schedule_button_plus" ).setAttribute( "onclick", "AutomowerConnectHandleInput( '"+dev+"' )" );
shdl += "</tbody></table>";
shdl += "</div>";
let schedule = new DOMParser().parseFromString( shdl, "text/html" ).querySelector( '#amc_'+dev+'_schedule_div' );
document.querySelector('body').append( schedule );
document.querySelector( "#amc_"+dev+"_schedule_button_plus" ).setAttribute( "onclick", "AutomowerConnectHandleInput( '"+dev+"', "+hasWorkAreaId+", "+workAreaId+" )" );
$(schedule).dialog({
dialogClass:"no-close", modal:true, width:"auto", closeOnEscape:true,
maxWidth:$(window).width()*0.9, maxHeight:$(window).height()*0.9,
buttons: [{text:"Send To Attribute", click:function(){
schedule = document.querySelector( '#amc_'+dev+'_schedule_div' );
cal = JSON.parse( schedule.getAttribute( 'data-amc_schedule' ) );
FW_cmd( FW_root+"?cmd=set "+dev+" sendJsonScheduleToAttribute "+JSON.stringify( cal )+"+&XHR=1" );
$(schedule).dialog({
dialogClass:"no-close", modal:true, width:"auto", closeOnEscape:true,
maxWidth:$(window).width()*0.9, maxHeight:$(window).height()*0.9,
buttons: [{text:"Send To Attribute", click:function(){
schedule = document.querySelector( '#amc_'+dev+'_schedule_div' );
cal = JSON.parse( schedule.getAttribute( 'data-amc_schedule' ) );
FW_cmd( FW_root+"?cmd=set "+dev+" sendJsonScheduleToAttribute "+JSON.stringify( cal )+"+&XHR=1" );
}},{text:"Send To Mower", click:function(){
schedule = document.querySelector( '#amc_'+dev+'_schedule_div' );
cal = JSON.parse( schedule.getAttribute( 'data-amc_schedule' ) );
FW_cmd( FW_root+"?cmd=set "+dev+" sendJsonScheduleToMower "+JSON.stringify( cal )+"&XHR=1" );
}},{text:"Close", click:function(){
$(this).dialog("close");
document.querySelector( '#amc_'+dev+'_schedule_div' ).remove();
}}]
}},{text:"Send To Mower", click:function(){
schedule = document.querySelector( '#amc_'+dev+'_schedule_div' );
cal = JSON.parse( schedule.getAttribute( 'data-amc_schedule' ) );
FW_cmd( FW_root+"?cmd=set "+dev+" sendJsonScheduleToMower "+JSON.stringify( cal )+"&XHR=1" );
}},{text:"Close", click:function(){
$(this).dialog("close");
document.querySelector( '#amc_'+dev+'_schedule_div' ).remove();
}}]
});
});
}
@ -709,20 +801,47 @@ function AutomowerConnectUpdateDetail (dev, type, detailfnfirst, picx, picy, sca
if ( plixy.length > 0 ) AutomowerConnectLimits( ctx0, div, plixy, 'property' );
// draw hull
if ( div.getAttribute( 'data-hullCalculate' ) && typeof hull === "function" && hullxy.length == 0 ) {
const pts = [];
if ( div.getAttribute( 'data-hullCalculate' ) && hullxy.length == 0 ) {
for ( let i = 0; i < pos.length; i+=3 ){
if ( typeof hull === "function" ) {
if ( pos[i+2] == "M") pts.push( [ pos[i], pos[i+1] ] );
const pts = [];
}
for ( let i = 0; i < pos.length; i+=3 ){
if ( pts.length > 50 ) {
if ( pos[i+2] == "M") pts.push( [ pos[i], pos[i+1] ] );
const res = div.getAttribute( 'data-hullResolution' );
const hullpts = hull( pts, res );
AutomowerConnectHull( ctx0, div, hullpts, 'hull' );
}
if ( pts.length > 50 ) {
const res = div.getAttribute( 'data-hullResolution' );
const hullpts = hull( pts, res );
AutomowerConnectHull( ctx0, div, hullpts, 'hull' );
}
} else if ( typeof loadScript === "function" ) {
loadScript('automowerconnect/hull.js', ()=> {
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' );
}
});
}