74_AutomowerConnect.pm: mower schedule editor added.

git-svn-id: https://svn.fhem.de/fhem/trunk@28823 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
Ellert 2024-04-26 13:14:53 +00:00
parent 85b96d5f81
commit 99c2bd7d79
4 changed files with 566 additions and 253 deletions

View File

@ -1,5 +1,6 @@
# 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: new mower schedule editor
- bugfix: 76_SolarForecast: possible Illegal division by zero if Attr
ctrlGenPVdeviation is set to continuously
- feature: 74_AutomowerConnect.pm: new attribute mowerPanel

View File

@ -105,7 +105,6 @@ __END__
=begin html
<a id="74_AutomowerConnect.pm" ></a>
<a id="AutomowerConnect" ></a>
<h3>AutomowerConnect</h3>
<ul>
@ -151,6 +150,15 @@ __END__
</ul>
<br>
<b>Button</b>
<ul>
<li><a id='AutomowerConnect-button-mowerschedule'>Mower Schedule</a><br>
The Button <button >Mower Schedule</button> opens GUI to maintain the mower schedule..<br>
Add/change entry: fill out the schedule fields and press <button >&plusmn;</button>.<br>
Delete entry: unselect each weekday and press <button >&plusmn;</button>.<br>
Reset entry: fill any time field with -- and press <button >&plusmn;</button>.</li>
</ul>
<a id="AutomowerConnectSet"></a>
<b>Set</b>
<ul>
@ -373,7 +381,7 @@ __END__
<li><a id='AutomowerConnect-attr-mowerSchedule'>mowerSchedule</a><br>
<code>attr &lt;name&gt; mowerSchedule &lt;schedule array&gt;</code><br>
This attribute provides the possebility to edit the mower schedule in form of an JSON array.<br>The actual schedule can be loaded with the command <code>set &lt;name&gt; mowerScheduleToAttribute</code>. <br>The command <code>set &lt;name&gt; sendScheduleFromAttributeToMower</code> sends the schedule to the mower. The maximum of array elements is 14 and 2 each day, so every day of a week can have 2 time spans. Each array element consists of 7 unsorted day values (<code>monday</code> to <code>sunday</code>) which can be <code>true</code> or <code>false</code>, a <code>start</code> and <code>duration</code> value in minutes. Start time counts from midnight. NOTE: Do not use for 550 EPOS and Ceora. Delete the attribute after the schedule is successfully uploaded.</li>
This attribute provides the possebility to edit the mower schedule in form of an JSON array.<br>The actual schedule can be loaded with the command <code>set &lt;name&gt; mowerScheduleToAttribute</code>. <br>The command <code>set &lt;name&gt; sendScheduleFromAttributeToMower</code> sends the schedule to the mower. The maximum of array elements is 14 and 2 each day, so every day of a week can have 2 time spans. Each array element consists of 7 day values (<code>monday</code> to <code>sunday</code>) which can be <code>true</code> or <code>false</code>, a <code>start</code> and <code>duration</code> value in minutes. Start time counts from midnight. NOTE: Do not use for 550 EPOS and Ceora. Delete the attribute after the schedule is successfully uploaded.</li>
<li><a id='AutomowerConnect-attr-mowingAreaLimits'>mowingAreaLimits</a><br>
<code>attr &lt;name&gt; mowingAreaLimits &lt;positions list&gt;</code><br>
@ -472,14 +480,25 @@ __END__
Shows user defined html beneath the map. usefull for a panel with shortcuts<br>
The command attribute has to contain the mower command, without set &lt;name&gt;<br>
<code>command="Start 210"</code> stands for <code>set &lt;name&gt; Start 210</code><br>
Directives as comment in the first line allow positioning.<br>
A directive as comment in the first line allows positioning.<br>
<ul>
<code>ON_TOP</code> shows html above map<br>
<li>
&lt;!-- ON_TOP --&gt; shows html above map</li>
</ul>
Panel has to be enclosed by a div-tag with a mandatory HTML-attribute <code>data-amc_panel_inroom=&lt;"1"|""&gt;</code>. Panel is shown in room view, i.e. for uiTable, weblink, etc., for value "1" and hidden for value "" look at example.<br>
Example:<br>
<code>
&lt;!-- ON_TOP --&gt;<br>
&lt;button command="Start 210" &gt;Start für 3 1/2 h&lt;/button&gt;<br>
&lt;style&gt;<br>
.amc_panel_button {height:50px; width:150px;}<br>
.amc_panel_div {position:relative; left:348px; top:-330px; z-index: 2; width:150px; height:1px}<br>
&lt;/style&gt;<br>
&lt;div class="amc_panel_div" data-amc_panel_inroom="1" &gt;<br>
&lt;button class="amc_panel_button" command="Start 210" &gt;Start für 3 1/2 h&lt;/button&gt;<br>
&lt;button class="amc_panel_button" command="Pause" &gt;Pause bis auf Weiteres&lt;/button&gt;<br>
&lt;button class="amc_panel_button" command="ResumeSchedule" &gt;Weiter nach Plan&lt;/button&gt;<br>
&lt;button class="amc_panel_button" command="ParkUntilNextSchedule" &gt;Parken bis nächsten Termin&lt;/button&gt;<br>
&lt;button class="amc_panel_button" command="ParkUntilNextSchedule" &gt;Parken bis auf Weiteres&lt;/button&gt;<br>
&lt;/div&gt;<br>
</code>
</li>
@ -608,8 +627,17 @@ __END__
</ul>
<br>
<a id="AutomowerConnectSet"></a>
<b>Set</b>
<b>Button</b>
<ul>
<li><a id='AutomowerConnect-button-mowerschedule'>Mower Schedule</a><br>
Über den Button <button >Mower Schedule</button> kann eine Benutzeroberfläche zur Bearbeitung des Mähplans geöffnet werden.<br>
Eintrag zufügen/ändern: Die gewünschten Angaben eintragen und <button >&plusmn;</button> betätigen.<br>
Eintrag löschen: Alle Wochentage abwählen und <button >&plusmn;</button> betätigen.<br>
Eintrag zurücksetzen: Irgend ein Zeitfeld mit -- füllen und <button >&plusmn;</button> betätigen.</li>
</ul>
<a id="AutomowerConnectSet"></a>
<b>Set</b>
<ul>
<li><a id='AutomowerConnect-set-Park'>Park</a><br>
<code>set &lt;name&gt; Park &lt;number of minutes&gt;</code><br>
@ -834,7 +862,7 @@ __END__
<li><a id='AutomowerConnect-attr-mowerSchedule'>mowerSchedule</a><br>
<code>attr &lt;name&gt; mowerSchedule &lt;schedule array&gt;</code><br>
Dieses Attribut bietet die Möglichkeit den Mähplan zu ändern, er liegt als JSON Array vor.<br>Der aktuelleMähplan kann mit dem Befehl <code>set &lt;name&gt; mowerScheduleToAttrbute</code> ins Attribut geschrieben werden. <br>Der Befehl <code>set &lt;name&gt; sendScheduleFromAttributeToMower</code> sendet den Mähplan an den Mäher. Das Maximum der Arrayelemente beträgt 14, 2 für jeden Tag, so daß jeden Tag zwei Intervalle geplant werden können. Jedes Arrayelement besteht aus 7 unsortierten Tageswerten (<code>monday</code> bis <code>sunday</code>) die auf <code>true</code> oder <code>false</code> gesetzt werden können, einen <code>start</code> Wert und einen <code>duration</code> Wert in Minuten. Die Startzeit <code>start</code> wird von Mitternacht an gezählt. HINWEIS: Nicht für 550 EPOS und Ceora geeignet.</li>
Dieses Attribut bietet die Möglichkeit den Mähplan zu ändern, er liegt als JSON Array vor.<br>Der aktuelle Mähplan kann mit dem Befehl <code>set &lt;name&gt; mowerScheduleToAttrbute</code> ins Attribut geschrieben werden. <br>Der Befehl <code>set &lt;name&gt; sendScheduleFromAttributeToMower</code> sendet den Mähplan an den Mäher. Das Maximum der Arrayelemente beträgt 14, 2 für jeden Tag, so daß jeden Tag zwei Intervalle geplant werden können. Jedes Arrayelement besteht aus 7 Tageswerten (<code>monday</code> bis <code>sunday</code>) die auf <code>true</code> oder <code>false</code> gesetzt werden können, einen <code>start</code> Wert und einen <code>duration</code> Wert in Minuten. Die Startzeit <code>start</code> wird von Mitternacht an gezählt. HINWEIS: Nicht für 550 EPOS und Ceora geeignet.</li>
<li><a id='AutomowerConnect-attr-mowingAreaLimits'>mowingAreaLimits</a><br>
<code>attr &lt;name&gt; mowingAreaLimits &lt;positions list&gt;</code><br>
@ -934,14 +962,25 @@ __END__
Zeigt HTML Kode unterhalb der Karte z.B. für ein Panel mit Kurzbefehlen.<br>
Das command Attribut beinhaltet den Mäherbefehl, ohne set &lt;name&gt;<br>
<code>command="Start 210"</code> steht für <code>set &lt;name&gt; Start 210</code><br>
Direktiven als Kommentar erlauben die Positionierung.<br>
Eine Direktive als Kommentar in der ersten Zeile erlaubt die Positionierung:<br>
<ul>
<code>ON_TOP</code> zeigt Buttons über der Karte<br>
<li>
&lt;!-- ON_TOP --&gt; zeigt das Panel über der Karte an.</li>
</ul>
Das Panel muss in einem div-Element eingebettet sein das ein HTML-Attribut <code>data-amc_panel_inroom=&lt;"1"|""&gt;</code> enthält. Das Panel wird in der Raumansicht, z.B. bei uiTable, weblink, usw., angezeigt wenn der Wert "1" ist und versteckt falls der Wert "" ist, s. Bsp.<br>
Beispiel:<br>
<code>
&lt;!-- ON_TOP --&gt;<br>
&lt;button command="Start 210" &gt;Start für 3 1/2 h&lt;/button&gt;<br>
&lt;style&gt;<br>
.amc_panel_button {height:50px; width:150px;}<br>
.amc_panel_div {position:relative; left:348px; top:-330px; z-index: 2; width:150px; height:1px}<br>
&lt;/style&gt;<br>
&lt;div class="amc_panel_div" data-amc_panel_inroom="1" &gt;<br>
&lt;button class="amc_panel_button" command="Start 210" &gt;Start für 3 1/2 h&lt;/button&gt;<br>
&lt;button class="amc_panel_button" command="Pause" &gt;Pause bis auf Weiteres&lt;/button&gt;<br>
&lt;button class="amc_panel_button" command="ResumeSchedule" &gt;Weiter nach Plan&lt;/button&gt;<br>
&lt;button class="amc_panel_button" command="ParkUntilNextSchedule" &gt;Parken bis nächsten Termin&lt;/button&gt;<br>
&lt;button class="amc_panel_button" command="ParkUntilNextSchedule" &gt;Parken bis auf Weiteres&lt;/button&gt;<br>
&lt;/div&gt;<br>
</code>
</li>

View File

@ -427,57 +427,6 @@ sub Rename {
return undef;
}
#########################
sub Get {
my ($hash,@val) = @_;
my $type = $hash->{TYPE};
my $name = $hash->{NAME};
my $iam = "$type $name Get:";
return "$iam needs at least one argument" if ( @val < 2 );
return "$iam disabled" if ( IsDisabled( $name ) );
my ($pname,$setName,$setVal,$setVal2,$setVal3) = @val;
Log3 $name, 4, "$iam called with $setName " . ($setVal ? $setVal : "");
if ( $setName eq 'html' ) {
my $ret = '<html>' . FW_detailFn( undef, $name, undef, undef) . '</html>';
return $ret;
} elsif ( $setName eq 'errorCodes' ) {
my $ret = listErrorCodes();
return $ret;
} elsif ( $setName eq 'InternalData' ) {
my $ret = listInternalData($hash);
return $ret;
} elsif ( $setName eq 'MowerData' ) {
my $ret = listMowerData($hash);
return $ret;
} elsif ( $setName eq 'StatisticsData' ) {
my $ret = listStatisticsData($hash);
return $ret;
} elsif ( $setName eq 'errorStack' ) {
my $ret = listErrorStack($hash);
return $ret;
} else {
return "Unknown argument $setName, choose one of StatisticsData:noArg MowerData:noArg InternalData:noArg errorCodes:noArg errorStack:noArg ";
}
}
#########################
sub FW_summaryFn {
my ($FW_wname, $name, $room, $pageHash) = @_; # pageHash is set for summaryFn.
@ -494,7 +443,20 @@ sub FW_detailFn {
my ($FW_wname, $name, $room, $pageHash) = @_; # pageHash is set for summaryFn.
my $hash = $defs{$name};
my $type = $hash->{TYPE};
return '' if( AttrVal($name, 'disable', 0) || !AttrVal($name, 'showMap', 1) || !$::init_done || !$FW_ME );
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 $img = "$FW_ME/$type/$name/map";
my $zoom=AttrVal( $name,"mapImageZoom", 0.7 );
@ -580,7 +542,7 @@ sub FW_detailFn {
my $ret = "";
$ret .= "<style>
.${type}_${name}_div{padding:0px !important;
.${type}_devname_div{padding:0px !important;
$bgstyle background-image: url('$img');
background-size: ${picx}px ${picy}px;
background-repeat: no-repeat;
@ -595,16 +557,20 @@ sub FW_detailFn {
my $contentflg = $content =~ /ON_TOP/;
$content =~ s/command=['"](.*?)['"]/onclick="AutomowerConnectPanelCmd('set $name $1')"/g;
$ret .= $content if ( $contentflg );
$ret .= "<div id='${type}_${name}_div' class='${type}_${name}_div' $$mapDesign $csdata $limi $propli width='$picx' height='$picy' >";
$ret .= "<div id='${type}_${name}_div' class='${type}_devname_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>"
$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>"
if ( -e "$FW_dir/$hash->{helper}{FWEXTA}{path}/$hash->{helper}{FWEXTA}{file}" && !AttrVal( $name,'mowingAreaHull','' ) && $$mapDesign =~ m/hullCalculate="1"/g );
$ret .= "<button title='Subtracts hull polygon points from way points. To hide button set hullSubtract=\"\".' onclick='AutomowerConnectSubtractHull( \"$FW_ME/$type/$name/json\" )'>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-weight:bold; font-size:16pt; ' >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 );
$ret .= "<br>";
$hash->{helper}{detailFnFirst} = 1;
my $mid = $hash->{helper}{map_init_delay};
InternalTimer( gettimeofday() + $mid, \&FW_detailFn_Update, $hash, 0 );
@ -1126,6 +1092,259 @@ sub getNewAccessToken {
APIAuth( $hash );
}
#########################
sub Get {
my ($hash,@val) = @_;
my $type = $hash->{TYPE};
my $name = $hash->{NAME};
my $iam = "$type $name Get:";
return "$iam needs at least one argument" if ( @val < 2 );
return "$iam disabled" if ( IsDisabled( $name ) );
my ($pname,$setName,$setVal,$setVal2,$setVal3) = @val;
Log3 $name, 4, "$iam called with $setName " . ($setVal ? $setVal : "");
if ( $setName eq 'html' ) {
my $ret = '<html>' . FW_detailFn( undef, $name, undef, undef) . '</html>';
return $ret;
} elsif ( $setName eq 'errorCodes' ) {
my $ret = listErrorCodes();
return $ret;
} elsif ( $setName eq 'InternalData' ) {
my $ret = listInternalData($hash);
return $ret;
} elsif ( $setName eq 'MowerData' ) {
my $ret = listMowerData($hash);
return $ret;
} elsif ( $setName eq 'StatisticsData' ) {
my $ret = listStatisticsData($hash);
return $ret;
} elsif ( $setName eq 'errorStack' ) {
my $ret = listErrorStack($hash);
return $ret;
} else {
return "Unknown argument $setName, choose one of StatisticsData:noArg MowerData:noArg InternalData:noArg errorCodes:noArg errorStack:noArg ";
}
}
#########################
sub Set {
my ($hash,@val) = @_;
my $type = $hash->{TYPE};
my $name = $hash->{NAME};
my $iam = "$type $name Set:";
return "$iam: needs at least one argument" if ( @val < 2 );
return "Unknown argument, $iam is disabled, choose one of none:noArg" if ( IsDisabled( $name ) );
my ($pname,$setName,$setVal,$setVal2,$setVal3) = @val;
Log3 $name, 4, "$iam called with $setName " . ($setVal ? $setVal : "") if ($setName !~ /^(\?|client_secret)$/);
##########
if ( !$hash->{helper}{midnightCycle} && $setName eq 'getUpdate' ) {
RemoveInternalTimer($hash, \&APIAuth);
APIAuth($hash);
return undef;
##########
} elsif ( $setName eq 'chargingStationPositionToAttribute' ) {
my $xm = $hash->{helper}{chargingStation}{longitude} // 10.1165;
my $ym = $hash->{helper}{chargingStation}{latitude} // 51.28;
CommandAttr( $hash, "$name chargingStationCoordinates $xm $ym" );
return undef;
##########
} elsif ( $setName eq 'defaultDesignAttributesToAttribute' ) {
my $design = $hash->{helper}{mapdesign};
CommandAttr( $hash, "$name mapDesignAttributes $design" );
return undef;
##########
} elsif ( $setName eq 'mapZonesTemplateToAttribute' ) {
my $tpl = $hash->{helper}{mapZonesTpl};
CommandAttr( $hash, "$name mapZones $tpl" );
return undef;
##########
} 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});
};
return "$iam $@" if ($@);
CommandAttr($hash,"$name mowerSchedule $calendarjson");
return undef;
##########
} elsif ( $setName eq 'client_secret' ) {
if ( $setVal ) {
my ($passResp, $passErr) = $hash->{helper}->{passObj}->setStorePassword($name, $setVal);
Log3 $name, 1, "$iam error: $passErr" if ($passErr);
return "$iam $passErr" if( $passErr );
readingsBeginUpdate($hash);
readingsBulkUpdateIfChanged( $hash, '.access_token', '', 0 );
readingsBulkUpdateIfChanged( $hash, 'device_state', 'initialized');
readingsBulkUpdateIfChanged( $hash, 'mower_commandStatus', 'cleared');
readingsEndUpdate($hash, 1);
RemoveInternalTimer($hash, \&APIAuth);
APIAuth($hash);
return undef;
}
##########
} elsif ( ReadingsVal( $name, 'device_state', 'defined' ) !~ /defined|initialized|authentification|authenticated|update/ && $setName =~ /^(Start|Park|cuttingHeight)$/ ) {
if ( $setVal =~ /^(\d+)$/) {
CMD($hash ,$setName, $setVal);
return undef;
}
##########
} elsif ( ReadingsVal( $name, 'device_state', 'defined' ) !~ /defined|initialized|authentification|authenticated|update/ && $setName eq 'headlight' ) {
if ( $setVal =~ /^(ALWAYS_OFF|ALWAYS_ON|EVENING_ONLY|EVENING_AND_NIGHT)$/) {
CMD($hash ,$setName, $setVal);
return undef;
}
##########
} elsif ( $setName eq 'getNewAccessToken' ) {
readingsBeginUpdate($hash);
readingsBulkUpdateIfChanged( $hash, '.access_token', '', 0 );
readingsBulkUpdateIfChanged( $hash, 'device_state', 'initialized');
readingsBulkUpdateIfChanged( $hash, 'mower_commandStatus', 'cleared');
readingsEndUpdate($hash, 1);
RemoveInternalTimer($hash, \&APIAuth);
APIAuth($hash);
return undef;
##########
} elsif ( ReadingsVal( $name, 'device_state', 'defined' ) !~ /defined|initialized|authentification|authenticated|update/ && $setName =~ /ParkUntilFurtherNotice|ParkUntilNextSchedule|Pause|ResumeSchedule|sendScheduleFromAttributeToMower/ ) {
CMD($hash,$setName);
return undef;
##########
} elsif ( ReadingsVal( $name, 'device_state', 'defined' ) !~ /defined|initialized|authentification|authenticated|update/ && $setName eq "sendJsonScheduleToAttribute" ) {
my $calendarjson = eval { decode_json ( $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);
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 )
};
return "$iam encode error: $@ in \$calendarjson" if ($@);
CommandAttr($hash,"$name mowerSchedule $calendarjson");
return undef;
##########
} elsif ( ReadingsVal( $name, 'device_state', 'defined' ) !~ /defined|initialized|authentification|authenticated|update/ && $setName eq "sendJsonScheduleToMower" ) {
CMD($hash,$setName,$setVal);
return undef;
##########
} elsif ( ReadingsVal( $name, 'device_state', 'defined' ) !~ /defined|initialized|authentification|authenticated|update/ && $setName =~ /confirmError/ && AttrVal( $name, 'testing', '' ) ) {
CMD($hash,$setName);
return undef;
##########
} elsif ( ReadingsVal( $name, 'device_state', 'defined' ) !~ /defined|initialized|authentification|authenticated|update/ && $setName =~ /^(StartInWorkArea|cuttingHeightInWorkArea)$/ && 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}
CMD($hash ,$setName, $setVal, $setVal2);
return undef;
}
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', '' ) ) {
( $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}
$setVal2 = $setVal2 eq 'enable' ? 'true' : 'false';
CMD($hash ,$setName, $setVal, $setVal2);
return undef;
}
Log3 $name, 2, "$iam $setName : no valid Id or zone name for $setVal .";
}
##########
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 ";
##########
if ( $hash->{helper}{mower}{attributes}{capabilities}{workAreas} && defined ( $hash->{helper}{mower}{attributes}{workAreas} ) && AttrVal( $name, 'testing', '' ) ) {
my @ar = @{ $hash->{helper}{mower}{attributes}{workAreas} };
my @anlist = map { ','.$_->{name} } @ar;
$ret .= "cuttingHeightInWorkArea:widgetList,".(scalar @anlist + 1).",select".join('',@anlist).",6,selectnumbers,0,10,100,0,lin ";
$ret .= "StartInWorkArea:widgetList,".(scalar @anlist + 1).",select".join('',@anlist).",6,selectnumbers,0,30,600,0,lin ";
}
##########
if ( $hash->{helper}{mower}{attributes}{capabilities}{stayOutZones} && defined ( $hash->{helper}{mower}{attributes}{stayOutZones}{zones} ) && AttrVal( $name, 'testing', '' ) ) {
my @so = @{ $hash->{helper}{mower}{attributes}{stayOutZones}{zones} };
my @solist = map { ','.$_->{name} } @so;
$ret .= "stayOutZone:widgetList,".(scalar @solist + 1).",select".join('',@solist).",3,select,enable,disable ";
}
$ret .= "confirmError:noArg " if ( AttrVal( $name, 'testing', '' ) );
return "Unknown argument $setName, choose one of".$ret;
}
##############################################################
#
# SEND COMMAND
@ -1188,6 +1407,19 @@ my $header = "Accept: application/vnd.api+json\r\nX-Api-Key: ".$client_id."\r\nA
$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";
}
$json = '{"data":{"type": "calendar","attributes":{"tasks":'.$jsonSchedule.'}}}';
$post = 'calendar';
}
Log3 $name, 5, "$iam $header \n $cmd[0] \n $json";
readingsSingleUpdate( $hash, 'api_callsThisMonth' , ReadingsVal( $name, 'api_callsThisMonth', 0 ) + 1, 0) if ( $hash->{helper}{additional_polling} );
@ -1266,168 +1498,6 @@ sub CMDResponse {
return undef;
}
#########################
sub Set {
my ($hash,@val) = @_;
my $type = $hash->{TYPE};
my $name = $hash->{NAME};
my $iam = "$type $name Set:";
return "$iam: needs at least one argument" if ( @val < 2 );
return "Unknown argument, $iam is disabled, choose one of none:noArg" if ( IsDisabled( $name ) );
my ($pname,$setName,$setVal,$setVal2,$setVal3) = @val;
Log3 $name, 4, "$iam called with $setName " . ($setVal ? $setVal : "") if ($setName !~ /^(\?|client_secret)$/);
if ( !$hash->{helper}{midnightCycle} && $setName eq 'getUpdate' ) {
RemoveInternalTimer($hash, \&APIAuth);
APIAuth($hash);
return undef;
} elsif ( $setName eq 'chargingStationPositionToAttribute' ) {
my $xm = $hash->{helper}{chargingStation}{longitude} // 10.1165;
my $ym = $hash->{helper}{chargingStation}{latitude} // 51.28;
CommandAttr( $hash, "$name chargingStationCoordinates $xm $ym" );
return undef;
} elsif ( $setName eq 'defaultDesignAttributesToAttribute' ) {
my $design = $hash->{helper}{mapdesign};
CommandAttr( $hash, "$name mapDesignAttributes $design" );
return undef;
} elsif ( $setName eq 'mapZonesTemplateToAttribute' ) {
my $tpl = $hash->{helper}{mapZonesTpl};
CommandAttr( $hash, "$name mapZones $tpl" );
return undef;
} 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}) };
if ( $@ ) {
return "$iam $@";
}
CommandAttr($hash,"$name mowerSchedule $calendarjson");
return undef;
} elsif ( $setName eq 'client_secret' ) {
if ( $setVal ) {
my ($passResp, $passErr) = $hash->{helper}->{passObj}->setStorePassword($name, $setVal);
Log3 $name, 1, "$iam error: $passErr" if ($passErr);
return "$iam $passErr" if( $passErr );
readingsBeginUpdate($hash);
readingsBulkUpdateIfChanged( $hash, '.access_token', '', 0 );
readingsBulkUpdateIfChanged( $hash, 'device_state', 'initialized');
readingsBulkUpdateIfChanged( $hash, 'mower_commandStatus', 'cleared');
readingsEndUpdate($hash, 1);
RemoveInternalTimer($hash, \&APIAuth);
APIAuth($hash);
return undef;
}
} elsif ( ReadingsVal( $name, 'device_state', 'defined' ) !~ /defined|initialized|authentification|authenticated|update/ && $setName =~ /^(Start|Park|cuttingHeight)$/ ) {
if ( $setVal =~ /^(\d+)$/) {
CMD($hash ,$setName, $setVal);
return undef;
}
} elsif ( ReadingsVal( $name, 'device_state', 'defined' ) !~ /defined|initialized|authentification|authenticated|update/ && $setName eq 'headlight' ) {
if ( $setVal =~ /^(ALWAYS_OFF|ALWAYS_ON|EVENING_ONLY|EVENING_AND_NIGHT)$/) {
CMD($hash ,$setName, $setVal);
return undef;
}
} elsif ( $setName eq 'getNewAccessToken' ) {
readingsBeginUpdate($hash);
readingsBulkUpdateIfChanged( $hash, '.access_token', '', 0 );
readingsBulkUpdateIfChanged( $hash, 'device_state', 'initialized');
readingsBulkUpdateIfChanged( $hash, 'mower_commandStatus', 'cleared');
readingsEndUpdate($hash, 1);
RemoveInternalTimer($hash, \&APIAuth);
APIAuth($hash);
return undef;
} elsif ( ReadingsVal( $name, 'device_state', 'defined' ) !~ /defined|initialized|authentification|authenticated|update/ && $setName =~ /ParkUntilFurtherNotice|ParkUntilNextSchedule|Pause|ResumeSchedule|sendScheduleFromAttributeToMower/ ) {
CMD($hash,$setName);
return undef;
} elsif ( ReadingsVal( $name, 'device_state', 'defined' ) !~ /defined|initialized|authentification|authenticated|update/ && $setName =~ /confirmError/ && AttrVal( $name, 'testing', '' ) ) {
CMD($hash,$setName);
return undef;
} elsif ( ReadingsVal( $name, 'device_state', 'defined' ) !~ /defined|initialized|authentification|authenticated|update/ && $setName =~ /^(StartInWorkArea|cuttingHeightInWorkArea)$/ && 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}
CMD($hash ,$setName, $setVal, $setVal2);
return undef;
}
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', '' ) ) {
( $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}
$setVal2 = $setVal2 eq 'enable' ? 'true' : 'false';
CMD($hash ,$setName, $setVal, $setVal2);
return undef;
}
Log3 $name, 2, "$iam $setName : no valid Id or zone name for $setVal .";
}
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 ";
if ( $hash->{helper}{mower}{attributes}{capabilities}{workAreas} && defined ( $hash->{helper}{mower}{attributes}{workAreas} ) && AttrVal( $name, 'testing', '' ) ) {
my @ar = @{ $hash->{helper}{mower}{attributes}{workAreas} };
my @anlist = map { ','.$_->{name} } @ar;
$ret .= "cuttingHeightInWorkArea:widgetList,".(scalar @anlist + 1).",select".join('',@anlist).",6,selectnumbers,0,10,100,0,lin ";
$ret .= "StartInWorkArea:widgetList,".(scalar @anlist + 1).",select".join('',@anlist).",6,selectnumbers,0,30,600,0,lin ";
}
if ( $hash->{helper}{mower}{attributes}{capabilities}{stayOutZones} && defined ( $hash->{helper}{mower}{attributes}{stayOutZones}{zones} ) && AttrVal( $name, 'testing', '' ) ) {
my @so = @{ $hash->{helper}{mower}{attributes}{stayOutZones}{zones} };
my @solist = map { ','.$_->{name} } @so;
$ret .= "stayOutZone:widgetList,".(scalar @solist + 1).",select".join('',@solist).",3,select,enable,disable ";
}
$ret .= "confirmError:noArg " if ( AttrVal( $name, 'testing', '' ) );
return "Unknown argument $setName, choose one of".$ret;
}
#########################
sub Attr {
@ -1493,10 +1563,7 @@ sub Attr {
if( $cmd eq "set" ) {
my $perl = eval { decode_json ( $attrVal ) };
if ($@) {
return "$iam $cmd $attrName decode error: $@ \n $attrVal";
}
return "$iam $cmd $attrName decode error: $@ \n $attrVal" if ($@);
Log3 $name, 4, "$iam $cmd $attrName";
}
@ -1700,14 +1767,17 @@ sub Attr {
if( $cmd eq "set" ) {
my $perl = eval { decode_json ($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);
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 )
};
return "$iam $cmd $attrName encode error: $@ \n $attrVal" if ($@);
if ($@) {
return "$iam $cmd $attrName decode error: $@ \n $perl";
}
my $json = eval { encode_json ($perl) };
if ($@) {
return "$iam $cmd $attrName encode error: $@ \n $json";
}
Log3 $name, 4, "$iam $cmd $attrName mower schedule array";
}
@ -1719,19 +1789,14 @@ sub Attr {
my $latitude = 52;
my $perl = eval { decode_json ($attrVal) };
if ($@) {
return "$iam $cmd $attrName decode error: $@ \n $attrVal";
}
return "$iam $cmd $attrName decode error: $@ \n $attrVal" if ($@);
for ( keys %{$perl} ) {
$perl->{$_}{zoneCnt} = 0;
$perl->{$_}{zoneLength} = 0;
my $cond = eval "($perl->{$_}{condition})";
if ($@) {
return "$iam $cmd $attrName syntax error in condition: $@ \n $perl->{$_}{condition}";
}
return "$iam $cmd $attrName syntax error in condition: $@ \n $perl->{$_}{condition}" if ($@);
}

View File

@ -1,7 +1,22 @@
if ( !(typeof FW_version === 'undefined') )
FW_version["automowerconnect.js"] = "$Id$";
{ window.onload = ( ()=>{
let room = document.querySelector("#content");
room = room.getAttribute("room");
if ( room ) {
let invis = document.querySelectorAll( "div[name='fhem_amc_mower_schedule_buttons'], div.fhem_amc_hull_buttons " ).forEach( (item, index, invis) => { // do not display schedule and hull buttons
item.style.display = "none";
});
}
let invis = document.querySelectorAll( "div.amc_panel_div" ).forEach( (item, index, invis) => { // do not display panel
let ivipan = item.getAttribute("data-amc_panel_inroom");
item.style.display = ( room && !ivipan ? "none" : "" );
});
});
}
function AutomowerConnectShowError( ctx, div, dev, picx, picy, errdesc, erray ) {
// ERROR BANNER
ctx.beginPath();
@ -356,9 +371,12 @@ function AutomowerConnectUpdateJson ( path ) {
function AutomowerConnectUpdateJsonFtui ( path ) {
$.getJSON( path, function( data, textStatus ) {
console.log( 'AutomowerConnectUpdateJsonFtui ( \''+path+'\' ): status '+textStatus );
if ( textStatus == 'success')
if ( textStatus == 'success') {
AutomowerConnectUpdateDetail ( data.name, data.type, 1, data.picx, data.picy, data.scalx, data.scaly, data.errdesc, data.posxy, data.poserrxy, data.hullxy );
let invis = document.querySelectorAll( "div[name='fhem_amc_mower_schedule_buttons'], div.amc_panel_div, div.fhem_amc_hull_buttons" ).forEach((item, index, invis) => { // do not display buttons
item.style.display = "none";
});
}
});
}
@ -462,6 +480,196 @@ function AutomowerConnectPanelCmd ( panelcmd ) {
FW_cmd( FW_root+"?cmd="+panelcmd+"&XHR=1" );
}
function AutomowerConnectHandleInput ( dev ) {
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 } ) }
//~ console.log('cali: '+cali+' cal.length: '+cal.length);
let elements = ["start", "duration"];
elements.forEach((item, index) => {
let val = document.getElementById('amc_'+dev+'_'+item).value;
let hour = parseInt(val.slice(0,2)) * 60;
let min = parseInt(val.slice(-2));
if ( isNaN( hour ) && item == "start" ) hour = 0;
if ( isNaN( min ) && item == "start" ) min = 0;
if ( isNaN( hour ) && item == "duration" ) hour = 23;
if ( isNaN( min ) && item == "duration" ) min = 59;
cal[cali][item] = hour + min;
});
elements = ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"];
elements.forEach((item, index) => {
cal[cali][item] = (document.getElementById('amc_'+dev+'_'+item).checked ? true : false);
});
let daysum = cal[cali].start + cal[cali].duration;
if ( ! ( cal[cali].monday || cal[cali].tuesday || cal[cali].wednesday || cal[cali].thursday || cal[cali].friday || cal[cali].saturday || cal[cali].sunday ) ) {
cal.splice( cali, 1 );
} else {
if ( daysum > 1439 ) {
cal[cali].start = 1439 - cal[cali].duration;
}
elements = ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"];
elements.forEach((item, index) => {
let cnt = 0;
for (let i=0;i<cal.length;i++) {
if ( cal[cali][item] && cal[i][item] ) cnt++;
}
if ( cnt > 2 ) cal[cali][item] = false;
});
let cnt = 0;
elements.forEach((item, index) => {
if ( cal[cali][item] ) cnt++;
});
if ( cnt == 0 ) cal.splice( cali, 1 );
cal.forEach((item, index) => {
if ( JSON.stringify( cal[cali] ) == JSON.stringify( item ) && cali != index ) {
cal.splice( cali, 1 );
}
});
}
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 } ];
cali = 0;
}
//~ console.log('index: '+cali+' start: '+cal[cali].start+' duration: '+cal[cali].duration+' monday: '+cal[cali].monday+' tuesday: '+cal[cali].tuesday+' wednesday: '+cal[cali].wednesday+' thursday: '+cal[cali].thursday+' friday: '+cal[cali].friday+' saturday: '+cal[cali].saturday+' sunday: '+cal[cali].sunday);
let shdl ='';
shdl = "<div id='amc_"+dev+"_schedule_div' class='ui-dialog-content ui-widget-content' data-amc_schedule='"+JSON.stringify(cal)+"' style='width:auto; height:auto; ' title='Schedule editor'>";
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 += "<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 += "<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+"\" )' 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><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>";
shdl += "</tr>";
shdl += "</tbody></table>";
shdl += "</div>";
const newdiv = new DOMParser().parseFromString( shdl, "text/html" ).querySelector( '#amc_'+dev+'_schedule_div' );
const olddiv = document.querySelector( '#amc_'+dev+'_schedule_div' );
olddiv.parentNode.replaceChild( newdiv, olddiv );
}
function AutomowerConnectSchedule ( dev, cal ) {
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>";
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>";
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+"' )" );
$(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();
}}]
});
}
//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 = {
@ -576,7 +784,7 @@ function AutomowerConnectUpdateDetail (dev, type, detailfnfirst, picx, picy, sca
if ( errdesc[0] != '-' ) AutomowerConnectShowError( ctx, div, dev, picx, picy, errdesc, erray );
} else {
setTimeout(()=>{
setTimeout ( ()=>{
console.log('AutomowerConnectUpdateDetail loop: div && canvas && canvas_0 false '+ type+' '+dev );
AutomowerConnectUpdateDetail (dev, type, detailfnfirst, picx, picy, scalx, errdesc, pos, erray);
}, 100);