Automatik-Editor als drei Baender, mit laufenden Werten
Das Akkordeon schloss beim Oeffnen die anderen Faecher - Ausloeser und
Aktion waren also nie gleichzeitig zu sehen, ausgerechnet bei einer
Regel, die genau daraus besteht. Und das mittlere Fach hiess
"Bedingungen", enthielt aber die Rahmenbedingungen; die echten standen
im ersten. Jetzt drei sichtbare Baender: Wenn, Wann, Dann, gebaut aus
.card mit farbigem Rand und .card-header.
Die Bedingungszeile magert von fuenf gleich schweren Kaesten auf zwei
ab. Das Geraet steht klein und matt ueber dem Messwert - es beantwortet
"woher", nicht "was" -, der Messwert ist Text mit gepunkteter Linie,
und nur Vergleich und Wert sehen noch wie Eingabefelder aus. Dort
steckt die Aussage der Bedingung, dort tippt man auch. Die Einheit
sitzt im Wertfeld statt in einem sechsten Kasten, der Papierkorb
erscheint erst beim Ueberfahren.
Neben jeder Bedingung laeuft ihr aktueller Wert mit. ?action=werte
liefert alle acht Sekunden {state_id: {value, unit}} aus actor_states -
bewusst von dort und nicht ueber MQTT, weil dort nur ein Teil der
Geraete auftaucht: die Jalousien haengen an der Tahoma-Box, die
gerechneten Werte an gar nichts. Das ersetzt den Satz, der frueher am
Ende stand und die Regel Wort fuer Wort nacherzaehlte: statt zu
wiederholen, was darueber steht, beantwortet die Zeile die Frage, die
man wirklich hat - warum laeuft die Automatik gerade nicht?
Bei time, date, datetime, deltatime und elapsed faellt der Browser
bewusst kein Urteil. Ob "Uhrzeit um 07:30" zutrifft, haengt am
Nachholfenster, am Tagesrand und bei elapsed am echten Abstand seit der
letzten Ausloesung - das steht im Runner. Es hier nachzubauen hiesse,
eine zweite Wahrheit zu pflegen, die auseinanderlaeuft. Diese Zeilen
bekommen einen gestrichelten Punkt statt einer Aussage.
Der Takt malt nur nach, statt neu zu bauen: renderConditions() ersetzt
den ganzen Block, damit waeren alle acht Sekunden eine offene
Geraeteliste weg und der Cursor aus einem Wertfeld, in das man gerade
tippt.
Der Rahmen steht zusammengefaltet da, solange nichts von der Vorgabe
abweicht - und das ist der Normalfall; vorher nahm er die meiste
Flaeche fuer den seltensten Inhalt. Aufgeklappt tauschen die drei
Vorlagen (taeglich, werktags, Wochenende) mit der Kurzfassung den
Platz. Sie stehen ausserhalb des Elements mit data-bs-toggle, und das
ist kein Zufall: Bootstrap haelt seinen Umschalter am Dokument in der
Capture-Phase, ein stopPropagation() im Knopf kaeme grundsaetzlich zu
spaet.
Getragen wird das von vier Variablen in solar.css, damit der Rest der
Seite mitzieht statt hinterherzuhinken: --ton-radius auf .875rem,
--ton-radius-klein neu auf .5rem (beide an Bootstraps
--bs-border-radius und -sm), --font-anzeige auf Poppins fuer alle
Ueberschriften. Poppins lag seit jeher unter assets/fonts und trug
bisher die vier Etagenknoepfe im Aussenplan - eine ganze Schrift fuer
vier Buchstaben. Fliesstext und Zahlenkolonnen bleiben beim Theme, weil
Poppins keine Tabellenziffern mitbringt.
Drei neue Klassen stehen absichtlich in solar.css und nicht im Editor.
.feld-als-text ist ein Auswahlfeld, das wie Text aussieht, bis man es
anfaehrt - dieselbe Not haben das Raum-Modal und die
Kachel-Einstellungen. .punkt ist an oder aus. .wert-jetzt ist die eine
echte Ausnahme und als einzelne Klasse auch als solche erkennbar. Der
Satz im Wann-Band braucht gar keine: .callout gibt es in AdminLTE, und
seine Toene kommen aus Bootstraps *-bg-subtle-Variablen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+130
-13
@@ -93,6 +93,115 @@ function operatorChoices($type)
|
||||
return $liste;
|
||||
}
|
||||
|
||||
/**
|
||||
* Die aktuellen Werte zu einer Liste von Messwerten.
|
||||
*
|
||||
* Der Editor zeigt neben jeder Bedingung, was der Messwert gerade sagt, und
|
||||
* holt sich das alle paar Sekunden nach. Bewusst aus actor_states und nicht
|
||||
* aus MQTT: ueber den Broker kommt nur ein Teil der Geraete: die Jalousien
|
||||
* haengen an der Tahoma-Box, die gerechneten Werte an gar nichts. Der Runner
|
||||
* schreibt dagegen jeden Messwert zurueck, egal ueber welchen Weg er ihn
|
||||
* geholt hat - eine Tabelle, ein Zugriff, alle Geraetearten.
|
||||
*
|
||||
* Die Ids kommen aus dem Browser und werden deshalb zu Zahlen gemacht, bevor
|
||||
* sie in die Abfrage gehen.
|
||||
*/
|
||||
function stateValues(array $ids)
|
||||
{
|
||||
$ids = array_values(array_unique(array_filter(array_map("intval", $ids))));
|
||||
if (!$ids) {
|
||||
return [];
|
||||
}
|
||||
$db = meshDb();
|
||||
$liste = implode(",", $ids);
|
||||
$res = $db->query("SELECT id, current_value, unit FROM actor_states
|
||||
WHERE id IN (" . $liste . ")");
|
||||
$werte = [];
|
||||
while ($row = $res->fetch_assoc()) {
|
||||
$werte[intval($row["id"])] = [
|
||||
"value" => $row["current_value"],
|
||||
"unit" => $row["unit"],
|
||||
];
|
||||
}
|
||||
return $werte;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ferien und Feiertage sind dreiwertig, nicht ja/nein.
|
||||
*
|
||||
* Zwei Zustaende gab es immer: "egal" (der Tag aendert nichts) und "nie"
|
||||
* (an solchen Tagen laeuft die Automatik nicht - so ist "werktags" gebaut).
|
||||
* "Zusaetzlich" ist der dritte und zaehlt wie ein passender Wochentag.
|
||||
*
|
||||
* Ohne ihn liess sich "an Wochenenden und Feiertagen" gar nicht schreiben:
|
||||
* die Wochentagsmaske kennt nur Samstag und Sonntag, und ein Feiertag am
|
||||
* Dienstag ist eben ein Dienstag. Jetzt genuegt Sa+So angehakt und
|
||||
* "Feiertage: zusaetzlich".
|
||||
*
|
||||
* Ein Verbot schlaegt eine Erweiterung: wer in den Ferien nie laeuft und an
|
||||
* Feiertagen zusaetzlich, laeuft an einem Feiertag in den Ferien nicht.
|
||||
* Andersherum liesse sich "nie" nicht mehr verlassen. Der Runner haelt
|
||||
* dieselbe Reihenfolge ein (tag_passt in autoaction_runner.py).
|
||||
*/
|
||||
function kalendertagChoices($was, $wann)
|
||||
{
|
||||
// Drei kurze Worte auf einer Achse, von eng nach weit: nie - egal -
|
||||
// immer. Sie stehen als Schalterreihe neben "Ferien" bzw. "Feiertage",
|
||||
// die Erklaerung haengt als title daran und der ganze Rahmen steht
|
||||
// darunter noch einmal als Satz.
|
||||
//
|
||||
// Vorher stand die Erklaerung *im* Etikett ("zusaetzlich, auch am
|
||||
// falschen Wochentag"). Ein Auswahlfeld, dessen Eintraege Saetze sind,
|
||||
// liest sich wie eine Bedienungsanleitung und zeigt trotzdem nur den
|
||||
// gewaehlten Satz - man sieht nie, was es sonst noch gibt.
|
||||
return [
|
||||
["value" => 0, "label" => "nie",
|
||||
"title" => "Läuft " . $wann . " nicht."],
|
||||
["value" => 1, "label" => "egal",
|
||||
"title" => $was . " ändern nichts."],
|
||||
["value" => 2, "label" => "immer",
|
||||
"title" => "Läuft " . $wann . ", auch wenn kein Wochentag angehakt ist."],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Vorlagen fuer die Tagesauswahl - die drei Faelle, die man wirklich meint.
|
||||
*
|
||||
* "Werktags" heisst nicht nur Mo-Fr, sondern auch "nicht an Feiertagen", und
|
||||
* "Wochenende" heisst fast immer "Wochenende und Feiertage". Wer das aus
|
||||
* Wochentagsmaske und zwei Dreifachschaltern von Hand zusammensetzt, vergisst
|
||||
* den zweiten Teil - deshalb steht er hier mit drin.
|
||||
*/
|
||||
function tagVorlagen()
|
||||
{
|
||||
return [
|
||||
["label" => "täglich", "weekdays" => 127, "on_vacation" => 1, "on_holiday" => 1,
|
||||
"title" => "Jeden Tag, Feiertage eingeschlossen."],
|
||||
["label" => "werktags", "weekdays" => 31, "on_vacation" => 1, "on_holiday" => 0,
|
||||
"title" => "Montag bis Freitag, aber nicht an Feiertagen."],
|
||||
["label" => "Wochenende", "weekdays" => 96, "on_vacation" => 1, "on_holiday" => 2,
|
||||
"title" => "Samstag und Sonntag, dazu alle Feiertage."],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Ein Kalenderwert aus dem Editor - alles Krumme wird zu "egal".
|
||||
*
|
||||
* is_numeric davor, und zwar mit Absicht: intval("") ist 0, und 0 heisst hier
|
||||
* "nie". Eine leere oder verunglueckte Angabe wuerde eine Automatik sonst
|
||||
* stillschweigend an Feiertagen abschalten - eine Regelaenderung, die niemand
|
||||
* angeordnet hat und die man erst am naechsten Feiertag bemerkt. "Egal" ist
|
||||
* die harmlose Vorgabe: der Tag aendert dann nichts.
|
||||
*/
|
||||
function kalendertagWert($wert)
|
||||
{
|
||||
if (!is_numeric($wert)) {
|
||||
return 1;
|
||||
}
|
||||
$zahl = intval($wert);
|
||||
return in_array($zahl, [0, 1, 2], true) ? $zahl : 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sperrzeit: so lange nach einer Ausloesung wird nicht erneut geschaltet.
|
||||
*
|
||||
@@ -692,9 +801,10 @@ function emptyAutomation($floor)
|
||||
"window_from" => "00:00",
|
||||
"window_to" => "23:59",
|
||||
"weekdays" => 127,
|
||||
"on_vacation" => true,
|
||||
"on_holiday" => true,
|
||||
"on_vacation" => 1,
|
||||
"on_holiday" => 1,
|
||||
"force_once" => false,
|
||||
"once_per_day" => false,
|
||||
"lockout_secs" => 60,
|
||||
"last_run" => null,
|
||||
"conditions" => [],
|
||||
@@ -723,9 +833,12 @@ function loadAutomation($id)
|
||||
"window_from" => substr($row["window_from"], 0, 5),
|
||||
"window_to" => substr($row["window_to"], 0, 5),
|
||||
"weekdays" => intval($row["weekdays"]),
|
||||
"on_vacation" => (bool)$row["on_vacation"],
|
||||
"on_holiday" => (bool)$row["on_holiday"],
|
||||
"on_vacation" => intval($row["on_vacation"]),
|
||||
"on_holiday" => intval($row["on_holiday"]),
|
||||
"force_once" => (bool)$row["force_once"],
|
||||
// ?? 0, damit der Editor auch vor der Migration noch aufgeht:
|
||||
// rahmen_erweitern.sql legt die Spalte erst an.
|
||||
"once_per_day" => (bool)($row["once_per_day"] ?? 0),
|
||||
"lockout_secs" => intval($row["lockout_secs"]),
|
||||
"last_run" => $row["last_run"],
|
||||
"conditions" => [],
|
||||
@@ -810,9 +923,10 @@ function saveAutomation($data)
|
||||
$windowFrom = normalizeTime($data["window_from"] ?? "00:00", "00:00:00");
|
||||
$windowTo = normalizeTime($data["window_to"] ?? "23:59", "23:59:00");
|
||||
$weekdays = intval($data["weekdays"] ?? 127) & 127;
|
||||
$onVacation = !empty($data["on_vacation"]) ? 1 : 0;
|
||||
$onHoliday = !empty($data["on_holiday"]) ? 1 : 0;
|
||||
$onVacation = kalendertagWert($data["on_vacation"] ?? 1);
|
||||
$onHoliday = kalendertagWert($data["on_holiday"] ?? 1);
|
||||
$forceOnce = !empty($data["force_once"]) ? 1 : 0;
|
||||
$oncePerDay = !empty($data["once_per_day"]) ? 1 : 0;
|
||||
// Nur die angebotenen Stufen, sonst die Vorgabe. Eine krumme Zahl kaeme
|
||||
// hier nur aus einer selbstgebauten Anfrage.
|
||||
$lockout = intval($data["lockout_secs"] ?? 60);
|
||||
@@ -844,10 +958,11 @@ function saveAutomation($data)
|
||||
$stmt = $db->prepare("UPDATE automations SET name = ?, floor = ?, enabled = ?,
|
||||
window_from = ?, window_to = ?, weekdays = ?,
|
||||
on_vacation = ?, on_holiday = ?, force_once = ?,
|
||||
lockout_secs = ?, changed = NOW()
|
||||
once_per_day = ?, lockout_secs = ?, changed = NOW()
|
||||
WHERE id = ?");
|
||||
$stmt->bind_param("ssissiiiiii", $name, $floor, $enabled, $windowFrom, $windowTo,
|
||||
$weekdays, $onVacation, $onHoliday, $forceOnce, $lockout, $id);
|
||||
$stmt->bind_param("ssissiiiiiii", $name, $floor, $enabled, $windowFrom, $windowTo,
|
||||
$weekdays, $onVacation, $onHoliday, $forceOnce, $oncePerDay,
|
||||
$lockout, $id);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
$db->query("DELETE FROM automation_conditions WHERE automation_id = " . $id);
|
||||
@@ -855,10 +970,12 @@ function saveAutomation($data)
|
||||
} else {
|
||||
$stmt = $db->prepare("INSERT INTO automations
|
||||
(name, floor, enabled, window_from, window_to, weekdays,
|
||||
on_vacation, on_holiday, force_once, lockout_secs)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?)");
|
||||
$stmt->bind_param("ssissiiiii", $name, $floor, $enabled, $windowFrom, $windowTo,
|
||||
$weekdays, $onVacation, $onHoliday, $forceOnce, $lockout);
|
||||
on_vacation, on_holiday, force_once, once_per_day,
|
||||
lockout_secs)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?)");
|
||||
$stmt->bind_param("ssissiiiiii", $name, $floor, $enabled, $windowFrom, $windowTo,
|
||||
$weekdays, $onVacation, $onHoliday, $forceOnce, $oncePerDay,
|
||||
$lockout);
|
||||
$stmt->execute();
|
||||
$id = $db->insert_id;
|
||||
$stmt->close();
|
||||
|
||||
Reference in New Issue
Block a user