diff --git a/ajax/AutoAction.php b/ajax/AutoAction.php
index c582852..5575ff8 100644
--- a/ajax/AutoAction.php
+++ b/ajax/AutoAction.php
@@ -312,7 +312,6 @@ $nutzdaten = json_encode([
"automation" => $auto,
"devices" => deviceCatalog(),
"lockouts" => lockoutChoices(),
- "haltezeiten" => haltezeitChoices(),
"ferien" => kalendertagChoices("Ferien", "in den Ferien"),
"feiertage" => kalendertagChoices("Feiertage", "an Feiertagen"),
"tagvorlagen" => tagVorlagen(),
@@ -467,14 +466,21 @@ $nutzdaten = json_encode([
diff --git a/doku/automatiken.md b/doku/automatiken.md
index 9d4d0b2..1b2cfbf 100644
--- a/doku/automatiken.md
+++ b/doku/automatiken.md
@@ -170,7 +170,7 @@ sonst steht in der Übersicht etwas anderes als im Editor.
| `next_day` | Der Rahmen gilt für **morgen** (Vorabend-Form) | Uhrzeit und Fenster bleiben bei heute |
| `window_from`/`window_to` | Zeitfenster; `von > bis` heißt über Mitternacht | |
| `lockout_secs` | Sperrzeit nach einem Lauf (0 / 60 / 900) | |
-| `hold_secs` | Haltezeit: so lange muss die Bedingung **ununterbrochen** erfüllt sein (0 = sofort) | Zählt im Speicher des Runners — ein Neustart fängt die Zeit von vorn an |
+| `hold_secs` | Haltezeit: so lange muss die Bedingung **ununterbrochen** erfüllt sein (0 = sofort, frei in Minuten bis 24 Std.) | Zählt im Speicher des Runners — ein Neustart fängt die Zeit von vorn an |
| `once_per_day` | höchstens ein Lauf je Kalendertag | |
| `force_once` | am Ende des Fensters notfalls doch ausführen | greift nur, wenn im Fenster gar nichts lief |
@@ -447,5 +447,6 @@ räumt der Runner selbst weg.
| … eine neue Geräteart anschließen | eine Klasse in `SolarManager/autoActions/transports.py` (Lesen + Senden), Erkennung an der Aktor-URL |
| … verstehen, warum etwas lief | `automation_log`, dann die Zeitleiste des Tages (→ [zeitleiste.md](zeitleiste.md)) |
| … das Nachholfenster ändern | `catchup_minutes` in `config.ini` des Runners |
-| … eine weitere Stufe für Sperre oder Haltezeit | `lockoutChoices()` / `haltezeitChoices()` in `restricted/automations.php` — eine Zeile, der Rest zieht nach |
+| … eine weitere Stufe für die Sperre | `lockoutChoices()` in `restricted/automations.php` — eine Zeile, der Rest zieht nach |
+| … die Grenzen der Haltezeit ändern | `haltezeitPruefen()` in `restricted/automations.php` **und** `max` am Feld `holdMin` |
| … auf Dauerzustände reagieren (Wasser läuft, Tür steht offen) | Haltezeit im Rahmen, dazu eine Aktion des Geräts „Benachrichtigungen“ (→ [benachrichtigungen.md](benachrichtigungen.md)) |
diff --git a/js/solar/autoActionFuncs.js b/js/solar/autoActionFuncs.js
index 565ae9a..d9505a9 100644
--- a/js/solar/autoActionFuncs.js
+++ b/js/solar/autoActionFuncs.js
@@ -26,7 +26,6 @@
let autoModel = null;
let autoDevices = [];
let autoLockouts = [];
-let autoHaltezeiten = [];
let autoFerien = [];
let autoFeiertage = [];
let autoTagVorlagen = [];
@@ -1182,9 +1181,7 @@ function fuelleFormular() {
const sperre = document.getElementById("lockoutSecs");
optionListe(sperre, autoLockouts.map(o => ({ value: o.value, text: o.label })),
autoModel.lockout_secs);
- optionListe(document.getElementById("holdSecs"),
- autoHaltezeiten.map(o => ({ value: o.value, text: o.label })),
- autoModel.hold_secs);
+ document.getElementById("holdMin").value = autoModel.hold_secs / 60;
document.getElementById("autoName").value = autoModel.name;
// Kennt die Liste die Etage nicht, bleibt eine Auswahlliste sonst leer
// stehen und schickt beim Speichern "" mit.
@@ -1219,7 +1216,10 @@ function leseFormular() {
autoModel.once_per_day = document.getElementById("oncePerDay").checked;
autoModel.next_day = document.getElementById("nextDay").checked;
autoModel.lockout_secs = Number(document.getElementById("lockoutSecs").value);
- autoModel.hold_secs = Number(document.getElementById("holdSecs").value);
+ // Minuten im Feld, Sekunden im Modell - und nichts Unmoegliches: ein
+ // leeres Feld ist 0, ein Tag ist die Obergrenze (siehe haltezeitPruefen()).
+ const halten = Math.round(Number(document.getElementById("holdMin").value) || 0);
+ autoModel.hold_secs = Math.min(Math.max(halten, 0), 1440) * 60;
}
/**
@@ -1227,11 +1227,16 @@ function leseFormular() {
* Ohne das stuende dort "rund um die Uhr", waehrend darunter 08:45 steht.
*/
function rahmenFelderVerdrahten() {
- ["tSpanFrom", "tSpanTo", "lockoutSecs", "holdSecs",
+ ["tSpanFrom", "tSpanTo", "lockoutSecs", "holdMin",
"runOnce", "oncePerDay", "nextDay"].forEach(id => {
const feld = document.getElementById(id);
if (feld) feld.addEventListener("change", () => { leseFormular(); renderRahmen(); });
});
+ // Bei der Haltezeit schon beim Tippen: aus "210" wird in der Kopfzeile
+ // "erst nach 3,5 Std." - genau dafuer gibt man die Zahl ein. "change"
+ // allein kaeme erst beim Verlassen des Feldes.
+ document.getElementById("holdMin")
+ .addEventListener("input", () => { leseFormular(); renderRahmen(); });
// Zugeklappt steht in der Kopfzeile, was gilt; aufgeklappt stehen dort die
// drei Vorlagen. Beides gleichzeitig waere eine volle Zeile aus
@@ -1256,7 +1261,6 @@ function loadAutomatic() {
const daten = JSON.parse(document.getElementById("autoActionData").textContent);
autoDevices = daten.devices;
autoLockouts = daten.lockouts;
- autoHaltezeiten = daten.haltezeiten || [];
autoFerien = daten.ferien || [];
autoFeiertage = daten.feiertage || [];
autoTagVorlagen = daten.tagvorlagen || [];
diff --git a/restricted/automations.php b/restricted/automations.php
index b9d2397..a179b5e 100644
--- a/restricted/automations.php
+++ b/restricted/automations.php
@@ -224,21 +224,30 @@ function lockoutChoices()
*
* 0 ist die Vorgabe und heisst: wie bisher, sofort.
*/
-function haltezeitChoices()
+/**
+ * Eine eingegebene Haltezeit auf etwas Brauchbares zurechtstutzen.
+ *
+ * Anders als die Sperre ist sie frei einstellbar: Wonach man sucht, haengt am
+ * Messwert - eine offene Tuer faellt nach zwei Minuten auf, ein laufender
+ * Wasserhahn erst nach Stunden. Eine Stufenliste traefe immer nur die Haelfte
+ * der Faelle.
+ *
+ * Grenzen gibt es trotzdem. Unter null ist keine Zeit; oberhalb eines Tages
+ * waere es keine Haltezeit mehr, sondern ein Dauerzustand, den man besser
+ * anders abfragt - und das Zeitfenster schneidet sie ohnehin ab. Gerechnet
+ * wird in ganzen Minuten, weil der Editor Minuten anbietet und Sekunden hier
+ * nichts gewinnen: die Messwerte kommen viel seltener.
+ */
+function haltezeitPruefen($sekunden)
{
- // Die 3,5 Stunden fallen aus der Reihe, weil sie aus einem konkreten Fall
- // stammen: dauerhafter Wasserverbrauch. Kuerzer waere Fehlalarm (eine
- // lange Dusche, eine Waschmaschine), laenger liefe ein offener Hahn eine
- // halbe Nacht.
- $stufen = [0, 300, 900, 1800, 3600, 7200, 12600];
- $aus = [];
- foreach ($stufen as $sek) {
- $aus[] = [
- "value" => $sek,
- "label" => $sek ? "nach " . haltezeitKurz($sek) . " ununterbrochen" : "sofort",
- ];
+ $sekunden = intval($sekunden);
+ if ($sekunden < 0) {
+ $sekunden = 0;
}
- return $aus;
+ if ($sekunden > 86400) {
+ $sekunden = 86400;
+ }
+ return intdiv($sekunden, 60) * 60;
}
/**
@@ -985,10 +994,7 @@ function saveAutomation($data)
if (!in_array($lockout, array_column(lockoutChoices(), "value"), true)) {
$lockout = 60;
}
- $halten = intval($data["hold_secs"] ?? 0);
- if (!in_array($halten, array_column(haltezeitChoices(), "value"), true)) {
- $halten = 0;
- }
+ $halten = haltezeitPruefen($data["hold_secs"] ?? 0);
$db->begin_transaction();
try {