Haltezeit frei in Minuten eingeben statt aus Stufen waehlen
Wonach man sucht, haengt am Messwert: eine offene Tuer faellt nach zwei Minuten auf, ein laufender Wasserhahn erst nach Stunden. Eine Stufenliste traf damit immer nur die Haelfte der Faelle. Das Feld nimmt Minuten, das Modell rechnet in Sekunden weiter. Die Kopfzeile des Rahmens rechnet beim Tippen mit - aus "210" wird dort "erst nach 3,5 Std.", also steht die lesbare Fassung neben der eingebbaren. haltezeitPruefen() ersetzt die Whitelist: nicht unter null, nicht ueber einen Tag, auf ganze Minuten gerundet. Sekunden gewinnen hier nichts, die Messwerte kommen viel seltener. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+12
-6
@@ -312,7 +312,6 @@ $nutzdaten = json_encode([
|
|||||||
"automation" => $auto,
|
"automation" => $auto,
|
||||||
"devices" => deviceCatalog(),
|
"devices" => deviceCatalog(),
|
||||||
"lockouts" => lockoutChoices(),
|
"lockouts" => lockoutChoices(),
|
||||||
"haltezeiten" => haltezeitChoices(),
|
|
||||||
"ferien" => kalendertagChoices("Ferien", "in den Ferien"),
|
"ferien" => kalendertagChoices("Ferien", "in den Ferien"),
|
||||||
"feiertage" => kalendertagChoices("Feiertage", "an Feiertagen"),
|
"feiertage" => kalendertagChoices("Feiertage", "an Feiertagen"),
|
||||||
"tagvorlagen" => tagVorlagen(),
|
"tagvorlagen" => tagVorlagen(),
|
||||||
@@ -467,14 +466,21 @@ $nutzdaten = json_encode([
|
|||||||
</div>
|
</div>
|
||||||
<!--
|
<!--
|
||||||
Haltezeit. Sie steht neben der Sperre, weil beide dasselbe Mittel
|
Haltezeit. Sie steht neben der Sperre, weil beide dasselbe Mittel
|
||||||
benutzen - Sekunden - und trotzdem entgegengesetzt wirken: die
|
benutzen - Zeit - und trotzdem entgegengesetzt wirken: die Sperre
|
||||||
Sperre bremst die Wiederholung, die Haltezeit den ersten Lauf.
|
bremst die Wiederholung, die Haltezeit den ersten Lauf.
|
||||||
"Auslösen" davor gelesen ergibt den Satz.
|
|
||||||
|
Freie Minuten statt einer Auswahlliste: Wonach man sucht, haengt am
|
||||||
|
Messwert. Die Zeile über dem Rahmen rechnet beim Tippen mit und
|
||||||
|
zeigt "erst nach 3,5 Std." - dort steht die lesbare Fassung, hier
|
||||||
|
die eingebbare.
|
||||||
-->
|
-->
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
<div class="input-group input-group-sm">
|
<div class="input-group input-group-sm">
|
||||||
<span class="input-group-text">Auslösen</span>
|
<span class="input-group-text">Auslösen erst nach</span>
|
||||||
<select class="form-select" id="holdSecs"></select>
|
<input type="number" class="form-control" id="holdMin"
|
||||||
|
min="0" max="1440" step="5" value="0"
|
||||||
|
title="So lange muss die Bedingung ohne Unterbrechung erfüllt sein. 0 = sofort.">
|
||||||
|
<span class="input-group-text">Min. am Stück</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+3
-2
@@ -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 |
|
| `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 | |
|
| `window_from`/`window_to` | Zeitfenster; `von > bis` heißt über Mitternacht | |
|
||||||
| `lockout_secs` | Sperrzeit nach einem Lauf (0 / 60 / 900) | |
|
| `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 | |
|
| `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 |
|
| `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 |
|
| … 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)) |
|
| … 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 |
|
| … 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)) |
|
| … 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)) |
|
||||||
|
|||||||
@@ -26,7 +26,6 @@
|
|||||||
let autoModel = null;
|
let autoModel = null;
|
||||||
let autoDevices = [];
|
let autoDevices = [];
|
||||||
let autoLockouts = [];
|
let autoLockouts = [];
|
||||||
let autoHaltezeiten = [];
|
|
||||||
let autoFerien = [];
|
let autoFerien = [];
|
||||||
let autoFeiertage = [];
|
let autoFeiertage = [];
|
||||||
let autoTagVorlagen = [];
|
let autoTagVorlagen = [];
|
||||||
@@ -1182,9 +1181,7 @@ function fuelleFormular() {
|
|||||||
const sperre = document.getElementById("lockoutSecs");
|
const sperre = document.getElementById("lockoutSecs");
|
||||||
optionListe(sperre, autoLockouts.map(o => ({ value: o.value, text: o.label })),
|
optionListe(sperre, autoLockouts.map(o => ({ value: o.value, text: o.label })),
|
||||||
autoModel.lockout_secs);
|
autoModel.lockout_secs);
|
||||||
optionListe(document.getElementById("holdSecs"),
|
document.getElementById("holdMin").value = autoModel.hold_secs / 60;
|
||||||
autoHaltezeiten.map(o => ({ value: o.value, text: o.label })),
|
|
||||||
autoModel.hold_secs);
|
|
||||||
document.getElementById("autoName").value = autoModel.name;
|
document.getElementById("autoName").value = autoModel.name;
|
||||||
// Kennt die Liste die Etage nicht, bleibt eine Auswahlliste sonst leer
|
// Kennt die Liste die Etage nicht, bleibt eine Auswahlliste sonst leer
|
||||||
// stehen und schickt beim Speichern "" mit.
|
// stehen und schickt beim Speichern "" mit.
|
||||||
@@ -1219,7 +1216,10 @@ function leseFormular() {
|
|||||||
autoModel.once_per_day = document.getElementById("oncePerDay").checked;
|
autoModel.once_per_day = document.getElementById("oncePerDay").checked;
|
||||||
autoModel.next_day = document.getElementById("nextDay").checked;
|
autoModel.next_day = document.getElementById("nextDay").checked;
|
||||||
autoModel.lockout_secs = Number(document.getElementById("lockoutSecs").value);
|
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.
|
* Ohne das stuende dort "rund um die Uhr", waehrend darunter 08:45 steht.
|
||||||
*/
|
*/
|
||||||
function rahmenFelderVerdrahten() {
|
function rahmenFelderVerdrahten() {
|
||||||
["tSpanFrom", "tSpanTo", "lockoutSecs", "holdSecs",
|
["tSpanFrom", "tSpanTo", "lockoutSecs", "holdMin",
|
||||||
"runOnce", "oncePerDay", "nextDay"].forEach(id => {
|
"runOnce", "oncePerDay", "nextDay"].forEach(id => {
|
||||||
const feld = document.getElementById(id);
|
const feld = document.getElementById(id);
|
||||||
if (feld) feld.addEventListener("change", () => { leseFormular(); renderRahmen(); });
|
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
|
// Zugeklappt steht in der Kopfzeile, was gilt; aufgeklappt stehen dort die
|
||||||
// drei Vorlagen. Beides gleichzeitig waere eine volle Zeile aus
|
// drei Vorlagen. Beides gleichzeitig waere eine volle Zeile aus
|
||||||
@@ -1256,7 +1261,6 @@ function loadAutomatic() {
|
|||||||
const daten = JSON.parse(document.getElementById("autoActionData").textContent);
|
const daten = JSON.parse(document.getElementById("autoActionData").textContent);
|
||||||
autoDevices = daten.devices;
|
autoDevices = daten.devices;
|
||||||
autoLockouts = daten.lockouts;
|
autoLockouts = daten.lockouts;
|
||||||
autoHaltezeiten = daten.haltezeiten || [];
|
|
||||||
autoFerien = daten.ferien || [];
|
autoFerien = daten.ferien || [];
|
||||||
autoFeiertage = daten.feiertage || [];
|
autoFeiertage = daten.feiertage || [];
|
||||||
autoTagVorlagen = daten.tagvorlagen || [];
|
autoTagVorlagen = daten.tagvorlagen || [];
|
||||||
|
|||||||
+23
-17
@@ -224,21 +224,30 @@ function lockoutChoices()
|
|||||||
*
|
*
|
||||||
* 0 ist die Vorgabe und heisst: wie bisher, sofort.
|
* 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
|
$sekunden = intval($sekunden);
|
||||||
// stammen: dauerhafter Wasserverbrauch. Kuerzer waere Fehlalarm (eine
|
if ($sekunden < 0) {
|
||||||
// lange Dusche, eine Waschmaschine), laenger liefe ein offener Hahn eine
|
$sekunden = 0;
|
||||||
// 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",
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
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)) {
|
if (!in_array($lockout, array_column(lockoutChoices(), "value"), true)) {
|
||||||
$lockout = 60;
|
$lockout = 60;
|
||||||
}
|
}
|
||||||
$halten = intval($data["hold_secs"] ?? 0);
|
$halten = haltezeitPruefen($data["hold_secs"] ?? 0);
|
||||||
if (!in_array($halten, array_column(haltezeitChoices(), "value"), true)) {
|
|
||||||
$halten = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
$db->begin_transaction();
|
$db->begin_transaction();
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user