AutoAction-Editor: Speichern, Laden und Loeschen
Der Editor las seine Geraete aus homeMesh, schrieb aber nach solarLog in
Tabellen, deren Fremdschluessel auf solarLog.actors/sensors zeigten. Jedes
INSERT lief damit gegen den Fremdschluessel - autoactionsSensors und
autoactionsActors sind deshalb leer geblieben, und die Aktor-Schleife war
ohnehin nie geschrieben worden.
Die Automatiken liegen jetzt in homeMesh neben dem Geraetemodell
(homeMesh_automations.sql). Eine Bedingung verweist mit einem einzigen
Fremdschluessel auf actor_states statt auf Geraet, State-URL und ein
unklares valID; Kommandoparameter stehen zeilenweise statt in vier festen
Spalten "Wert 1" bis "Wert 4". Verknuepft wird ueber group_no: gleiche
Nummer UND, verschiedene ODER, ausgewertet als any(all(gruppe)).
Im Editor ist eine Gruppe ein gerahmter Block mit eigenem "+ Bedingung",
dazwischen steht ODER - die Klammerung ist damit gezeichnet und nicht
vereinbart, und darunter steht derselbe Satz noch einmal in Worten. Die
Uebersicht zeigt ihn in der Spalte "Ausloeser" und ersetzt die bisher fest
verdrahtete Beispielzeile.
Nebenbei behoben: die Operator-Knoepfe schickten ihr innerHTML ("≠",
">") an eine Whitelist, die "!=" erwartete - jede Bedingung "ungleich"
wurde still zu "gleich". Ausgeblendete Wertfelder sendeten ihren Inhalt
trotzdem mit. Beides entfaellt, weil der Editor jetzt JSON aus einem
Modell schickt statt durchnummerierter Formularfelder.
Die vier Endpunkte fillSensorDD, fillActorDD, sensorDetails und
actorDetails entfallen: der Geraetekatalog reist einmal mit dem Editor
mit, statt je Bedingungszeile zwei Anfragen nachzuladen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,566 @@
|
||||
<?php
|
||||
/**
|
||||
* Modell-Schicht fuer die Automatiken (AutoActions).
|
||||
*
|
||||
* Hier liegt alles, was Editor und Uebersicht gemeinsam brauchen: die
|
||||
* Verbindung zur homeMesh-Datenbank, der Geraetekatalog, Laden und Speichern
|
||||
* einer Automatik und die Beschreibung in Alltagssprache. Die Ajax-Endpunkte
|
||||
* sind damit reine Verteiler.
|
||||
*
|
||||
* Tabellen siehe homeMesh_automations.sql. Kurzfassung: eine Automatik hat
|
||||
* Bedingungen (Verweis auf actor_states) und Aktionen (Verweis auf
|
||||
* actor_commands, Werte je command_parameters). Bedingungen mit derselben
|
||||
* group_no sind mit UND verknuepft, verschiedene Gruppen mit ODER -
|
||||
* ausgewertet wird any(all(gruppe)).
|
||||
*/
|
||||
|
||||
require_once(__DIR__ . "/mysql.php");
|
||||
|
||||
/** Verbindung zur Geraete- und Automatik-Datenbank. */
|
||||
function meshDb()
|
||||
{
|
||||
static $db = null;
|
||||
if ($db === null) {
|
||||
$db = new mysqli($GLOBALS["mysql_server"], $GLOBALS["mysql_MeshUser"],
|
||||
$GLOBALS["mysql_MeshPass"], $GLOBALS["mysql_MeshDB"]);
|
||||
$db->set_charset("utf8mb4");
|
||||
}
|
||||
return $db;
|
||||
}
|
||||
|
||||
/**
|
||||
* Operatoren, die zu einem Datentyp passen. Immer ASCII - die huebschen
|
||||
* Zeichen macht erst die Anzeige daraus. Frueher schickte der Editor sein
|
||||
* innerHTML ("≠", ">") an den Server, wo eine Whitelist mit "!=" stand:
|
||||
* jede Bedingung "ungleich" wurde still zu "gleich".
|
||||
*/
|
||||
function operatorsForType($type)
|
||||
{
|
||||
switch ($type) {
|
||||
case "integer":
|
||||
case "float":
|
||||
return ["=", "!=", ">", "<"];
|
||||
case "deltatime": // Sonnenauf-/-untergang: Wert ist ein Versatz
|
||||
return ["+", "-"];
|
||||
case "bool":
|
||||
return ["="]; // JA/NEIN steht im Wertfeld, nicht im Operator
|
||||
default:
|
||||
return ["=", "!="];
|
||||
}
|
||||
}
|
||||
|
||||
/** Wie das Wertfeld im Editor aussieht. */
|
||||
function inputForType($type, $hasOptions)
|
||||
{
|
||||
if ($type === "bool") {
|
||||
return "bool";
|
||||
}
|
||||
if ($hasOptions) {
|
||||
return "select";
|
||||
}
|
||||
switch ($type) {
|
||||
case "integer":
|
||||
case "float":
|
||||
return "number";
|
||||
case "time":
|
||||
case "deltatime":
|
||||
return "time";
|
||||
case "date":
|
||||
return "date";
|
||||
case "datetime":
|
||||
return "datetime-local";
|
||||
default:
|
||||
return "text";
|
||||
}
|
||||
}
|
||||
|
||||
/** Anzeigeform eines Operators. */
|
||||
function operatorLabel($op)
|
||||
{
|
||||
return $op === "!=" ? "≠" : $op;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alle Geraete mit ihren Messwerten und Kommandos in einem Rutsch.
|
||||
*
|
||||
* Der Editor holt den Katalog einmal und baut daraus jede Zeile - frueher
|
||||
* kostete jede neue Bedingung zwei zusaetzliche Anfragen (Geraeteliste und
|
||||
* Details), und die Dropdowns fuellten sich asynchron nach.
|
||||
*/
|
||||
function deviceCatalog()
|
||||
{
|
||||
$db = meshDb();
|
||||
$devices = [];
|
||||
|
||||
$res = $db->query("SELECT id, name, type, url FROM actors ORDER BY name");
|
||||
while ($row = $res->fetch_assoc()) {
|
||||
$devices[intval($row["id"])] = [
|
||||
"id" => intval($row["id"]),
|
||||
"name" => $row["name"],
|
||||
"type" => $row["type"],
|
||||
"url" => $row["url"],
|
||||
"states" => [],
|
||||
"commands" => [],
|
||||
];
|
||||
}
|
||||
|
||||
$res = $db->query("SELECT s.id, s.actor_id, s.state_name, s.unit, s.current_value,
|
||||
s.url, s.possible_values, t.type
|
||||
FROM actor_states s
|
||||
LEFT JOIN state_types t ON s.state_type = t.id
|
||||
ORDER BY s.actor_id, s.id");
|
||||
while ($row = $res->fetch_assoc()) {
|
||||
$id = intval($row["actor_id"]);
|
||||
if (!isset($devices[$id])) {
|
||||
continue;
|
||||
}
|
||||
$options = json_decode($row["possible_values"], true);
|
||||
if (!is_array($options)) {
|
||||
$options = [];
|
||||
}
|
||||
$type = $row["type"] ?: "string";
|
||||
$devices[$id]["states"][] = [
|
||||
"id" => intval($row["id"]),
|
||||
"name" => $row["state_name"],
|
||||
"type" => $type,
|
||||
"input" => inputForType($type, count($options) > 0),
|
||||
"operators" => operatorsForType($type),
|
||||
"options" => array_values($options),
|
||||
"unit" => $row["unit"],
|
||||
"value" => $row["current_value"],
|
||||
"url" => $row["url"],
|
||||
];
|
||||
}
|
||||
|
||||
$res = $db->query("SELECT id, actor_id, command_name FROM actor_commands
|
||||
ORDER BY actor_id, id");
|
||||
$commands = [];
|
||||
while ($row = $res->fetch_assoc()) {
|
||||
$id = intval($row["actor_id"]);
|
||||
if (!isset($devices[$id])) {
|
||||
continue;
|
||||
}
|
||||
$commands[intval($row["id"])] = $id;
|
||||
$devices[$id]["commands"][] = [
|
||||
"id" => intval($row["id"]),
|
||||
"name" => $row["command_name"],
|
||||
"params" => [],
|
||||
];
|
||||
}
|
||||
|
||||
if ($commands) {
|
||||
$res = $db->query("SELECT p.id, p.command_id, p.parameter_name, p.min_value,
|
||||
p.max_value, p.possible_values, p.url, t.type
|
||||
FROM command_parameters p
|
||||
LEFT JOIN state_types t ON p.parameter_type = t.id
|
||||
ORDER BY p.command_id, p.id");
|
||||
while ($row = $res->fetch_assoc()) {
|
||||
$cmdId = intval($row["command_id"]);
|
||||
if (!isset($commands[$cmdId])) {
|
||||
continue;
|
||||
}
|
||||
$options = json_decode($row["possible_values"], true);
|
||||
if (!is_array($options)) {
|
||||
$options = [];
|
||||
}
|
||||
$type = $row["type"] ?: "string";
|
||||
// Das passende Kommando im Geraet suchen. Wenige Kommandos je
|
||||
// Geraet, deshalb reicht die lineare Suche.
|
||||
foreach ($devices[$commands[$cmdId]]["commands"] as &$cmd) {
|
||||
if ($cmd["id"] === $cmdId) {
|
||||
$cmd["params"][] = [
|
||||
"id" => intval($row["id"]),
|
||||
"name" => $row["parameter_name"],
|
||||
"type" => $type,
|
||||
"input" => inputForType($type, count($options) > 0),
|
||||
"options" => array_values($options),
|
||||
"min" => $row["min_value"] === null ? null : floatval($row["min_value"]),
|
||||
"max" => $row["max_value"] === null ? null : floatval($row["max_value"]),
|
||||
"url" => $row["url"],
|
||||
];
|
||||
break;
|
||||
}
|
||||
}
|
||||
unset($cmd);
|
||||
}
|
||||
}
|
||||
|
||||
return array_values($devices);
|
||||
}
|
||||
|
||||
/** Leere Automatik, wie sie der Editor fuer "Neu anlegen" bekommt. */
|
||||
function emptyAutomation($floor)
|
||||
{
|
||||
return [
|
||||
"id" => 0,
|
||||
"name" => "",
|
||||
"floor" => $floor,
|
||||
"enabled" => true,
|
||||
"window_from" => "00:00",
|
||||
"window_to" => "23:59",
|
||||
"weekdays" => 127,
|
||||
"on_vacation" => true,
|
||||
"on_holiday" => true,
|
||||
"force_once" => false,
|
||||
"last_run" => null,
|
||||
"conditions" => [],
|
||||
"actions" => [],
|
||||
];
|
||||
}
|
||||
|
||||
/** Eine Automatik mit Bedingungen und Aktionen laden. */
|
||||
function loadAutomation($id)
|
||||
{
|
||||
$db = meshDb();
|
||||
$stmt = $db->prepare("SELECT * FROM automations WHERE id = ?");
|
||||
$stmt->bind_param("i", $id);
|
||||
$stmt->execute();
|
||||
$row = $stmt->get_result()->fetch_assoc();
|
||||
$stmt->close();
|
||||
if (!$row) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$auto = [
|
||||
"id" => intval($row["id"]),
|
||||
"name" => $row["name"],
|
||||
"floor" => $row["floor"],
|
||||
"enabled" => (bool)$row["enabled"],
|
||||
"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"],
|
||||
"force_once" => (bool)$row["force_once"],
|
||||
"last_run" => $row["last_run"],
|
||||
"conditions" => [],
|
||||
"actions" => [],
|
||||
];
|
||||
|
||||
$stmt = $db->prepare("SELECT id, group_no, state_id, operator, value
|
||||
FROM automation_conditions WHERE automation_id = ?
|
||||
ORDER BY group_no, position, id");
|
||||
$stmt->bind_param("i", $id);
|
||||
$stmt->execute();
|
||||
$res = $stmt->get_result();
|
||||
while ($c = $res->fetch_assoc()) {
|
||||
$auto["conditions"][] = [
|
||||
"group" => intval($c["group_no"]),
|
||||
"state_id" => intval($c["state_id"]),
|
||||
"operator" => $c["operator"],
|
||||
"value" => $c["value"],
|
||||
];
|
||||
}
|
||||
$stmt->close();
|
||||
|
||||
$stmt = $db->prepare("SELECT a.id, a.command_id, p.parameter_id, p.value
|
||||
FROM automation_actions a
|
||||
LEFT JOIN automation_action_params p ON p.action_id = a.id
|
||||
WHERE a.automation_id = ?
|
||||
ORDER BY a.position, a.id, p.parameter_id");
|
||||
$stmt->bind_param("i", $id);
|
||||
$stmt->execute();
|
||||
$res = $stmt->get_result();
|
||||
$actions = [];
|
||||
while ($a = $res->fetch_assoc()) {
|
||||
$aid = intval($a["id"]);
|
||||
if (!isset($actions[$aid])) {
|
||||
$actions[$aid] = ["command_id" => intval($a["command_id"]), "params" => []];
|
||||
}
|
||||
if ($a["parameter_id"] !== null) {
|
||||
$actions[$aid]["params"][strval($a["parameter_id"])] = $a["value"];
|
||||
}
|
||||
}
|
||||
$stmt->close();
|
||||
$auto["actions"] = array_values($actions);
|
||||
|
||||
return $auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* Eine Automatik anlegen oder ueberschreiben. Gibt die ID zurueck.
|
||||
*
|
||||
* Bedingungen und Aktionen werden komplett neu geschrieben statt einzeln
|
||||
* abgeglichen: sie haben keine Identitaet, die der Benutzer kennt, und der
|
||||
* Editor schickt ohnehin immer den vollstaendigen Stand. Alles laeuft in
|
||||
* einer Transaktion, damit eine halb ersetzte Regel nie sichtbar wird - und
|
||||
* schon gar nicht ausgefuehrt.
|
||||
*/
|
||||
function saveAutomation($data)
|
||||
{
|
||||
$db = meshDb();
|
||||
|
||||
$name = trim(strval($data["name"] ?? ""));
|
||||
if ($name === "") {
|
||||
throw new InvalidArgumentException("Bitte einen Namen vergeben.");
|
||||
}
|
||||
$floor = strval($data["floor"] ?? "");
|
||||
if (!in_array($floor, ["", "UG", "EG", "OG"], true)) {
|
||||
$floor = "";
|
||||
}
|
||||
$conditions = is_array($data["conditions"] ?? null) ? $data["conditions"] : [];
|
||||
$actions = is_array($data["actions"] ?? null) ? $data["actions"] : [];
|
||||
if (!$conditions) {
|
||||
throw new InvalidArgumentException("Ohne Ausloeser wuerde die Automatik nie starten.");
|
||||
}
|
||||
if (!$actions) {
|
||||
throw new InvalidArgumentException("Ohne Aktion haette die Automatik nichts zu tun.");
|
||||
}
|
||||
|
||||
$id = intval($data["id"] ?? 0);
|
||||
$enabled = !empty($data["enabled"]) ? 1 : 0;
|
||||
$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;
|
||||
$forceOnce = !empty($data["force_once"]) ? 1 : 0;
|
||||
|
||||
$db->begin_transaction();
|
||||
try {
|
||||
if ($id > 0) {
|
||||
// Erst nachsehen, ob es die Automatik noch gibt. Ohne das liefe
|
||||
// das UPDATE ins Leere und erst der Fremdschluessel der
|
||||
// Bedingungen wuerde meckern - mit einer Meldung, die niemandem
|
||||
// weiterhilft.
|
||||
$stmt = $db->prepare("SELECT id FROM automations WHERE id = ?");
|
||||
$stmt->bind_param("i", $id);
|
||||
$stmt->execute();
|
||||
$exists = $stmt->get_result()->fetch_assoc();
|
||||
$stmt->close();
|
||||
if (!$exists) {
|
||||
throw new RuntimeException("Automatik " . $id . " gibt es nicht mehr.");
|
||||
}
|
||||
|
||||
$stmt = $db->prepare("UPDATE automations SET name = ?, floor = ?, enabled = ?,
|
||||
window_from = ?, window_to = ?, weekdays = ?,
|
||||
on_vacation = ?, on_holiday = ?, force_once = ?
|
||||
WHERE id = ?");
|
||||
$stmt->bind_param("ssissiiiii", $name, $floor, $enabled, $windowFrom, $windowTo,
|
||||
$weekdays, $onVacation, $onHoliday, $forceOnce, $id);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
$db->query("DELETE FROM automation_conditions WHERE automation_id = " . $id);
|
||||
$db->query("DELETE FROM automation_actions WHERE automation_id = " . $id);
|
||||
} else {
|
||||
$stmt = $db->prepare("INSERT INTO automations
|
||||
(name, floor, enabled, window_from, window_to, weekdays,
|
||||
on_vacation, on_holiday, force_once)
|
||||
VALUES (?,?,?,?,?,?,?,?,?)");
|
||||
$stmt->bind_param("ssissiiii", $name, $floor, $enabled, $windowFrom, $windowTo,
|
||||
$weekdays, $onVacation, $onHoliday, $forceOnce);
|
||||
$stmt->execute();
|
||||
$id = $db->insert_id;
|
||||
$stmt->close();
|
||||
}
|
||||
|
||||
// Gruppennummern luecken lassen waere erlaubt, aber die Uebersicht
|
||||
// liest sich besser, wenn sie bei 0 anfangen und dicht sind.
|
||||
$groupMap = [];
|
||||
foreach ($conditions as $c) {
|
||||
$g = intval($c["group"] ?? 0);
|
||||
if (!isset($groupMap[$g])) {
|
||||
$groupMap[$g] = count($groupMap);
|
||||
}
|
||||
}
|
||||
|
||||
$stmt = $db->prepare("INSERT INTO automation_conditions
|
||||
(automation_id, group_no, position, state_id, operator, value)
|
||||
VALUES (?,?,?,?,?,?)");
|
||||
$positions = [];
|
||||
foreach ($conditions as $c) {
|
||||
$group = $groupMap[intval($c["group"] ?? 0)];
|
||||
$stateId = intval($c["state_id"] ?? 0);
|
||||
$operator = strval($c["operator"] ?? "=");
|
||||
$value = strval($c["value"] ?? "");
|
||||
if ($stateId <= 0) {
|
||||
throw new InvalidArgumentException("Eine Bedingung hat keinen Messwert.");
|
||||
}
|
||||
if (!in_array($operator, ["=", "!=", ">", "<", ">=", "<=", "+", "-"], true)) {
|
||||
throw new InvalidArgumentException("Unbekannter Operator: " . $operator);
|
||||
}
|
||||
$positions[$group] = ($positions[$group] ?? -1) + 1;
|
||||
$position = $positions[$group];
|
||||
$stmt->bind_param("iiiiss", $id, $group, $position, $stateId, $operator, $value);
|
||||
$stmt->execute();
|
||||
}
|
||||
$stmt->close();
|
||||
|
||||
$stmtAction = $db->prepare("INSERT INTO automation_actions
|
||||
(automation_id, position, command_id) VALUES (?,?,?)");
|
||||
$stmtParam = $db->prepare("INSERT INTO automation_action_params
|
||||
(action_id, parameter_id, value) VALUES (?,?,?)");
|
||||
$position = 0;
|
||||
foreach ($actions as $a) {
|
||||
$commandId = intval($a["command_id"] ?? 0);
|
||||
if ($commandId <= 0) {
|
||||
throw new InvalidArgumentException("Eine Aktion hat kein Kommando.");
|
||||
}
|
||||
$stmtAction->bind_param("iii", $id, $position, $commandId);
|
||||
$stmtAction->execute();
|
||||
$actionId = $db->insert_id;
|
||||
$position++;
|
||||
|
||||
$params = is_array($a["params"] ?? null) ? $a["params"] : [];
|
||||
foreach ($params as $paramId => $value) {
|
||||
$paramId = intval($paramId);
|
||||
$value = strval($value);
|
||||
if ($paramId <= 0) {
|
||||
continue;
|
||||
}
|
||||
$stmtParam->bind_param("iis", $actionId, $paramId, $value);
|
||||
$stmtParam->execute();
|
||||
}
|
||||
}
|
||||
$stmtAction->close();
|
||||
$stmtParam->close();
|
||||
|
||||
$db->commit();
|
||||
} catch (Throwable $e) {
|
||||
$db->rollback();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return $id;
|
||||
}
|
||||
|
||||
/** "16:30" oder "16:30:00" auf das Spaltenformat bringen. */
|
||||
function normalizeTime($value, $fallback)
|
||||
{
|
||||
if (preg_match('/^([01]\d|2[0-3]):([0-5]\d)(:([0-5]\d))?$/', trim(strval($value)), $t)) {
|
||||
return $t[1] . ":" . $t[2] . ":" . (isset($t[4]) ? $t[4] : "00");
|
||||
}
|
||||
return $fallback;
|
||||
}
|
||||
|
||||
function deleteAutomation($id)
|
||||
{
|
||||
$db = meshDb();
|
||||
$stmt = $db->prepare("DELETE FROM automations WHERE id = ?");
|
||||
$stmt->bind_param("i", $id);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
}
|
||||
|
||||
/** Pausieren oder wieder aufnehmen. Gibt den neuen Zustand zurueck. */
|
||||
function toggleAutomation($id)
|
||||
{
|
||||
$db = meshDb();
|
||||
$stmt = $db->prepare("UPDATE automations SET enabled = 1 - enabled WHERE id = ?");
|
||||
$stmt->bind_param("i", $id);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
$stmt = $db->prepare("SELECT enabled FROM automations WHERE id = ?");
|
||||
$stmt->bind_param("i", $id);
|
||||
$stmt->execute();
|
||||
$row = $stmt->get_result()->fetch_assoc();
|
||||
$stmt->close();
|
||||
return $row ? (bool)$row["enabled"] : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alle Automatiken einer Etage, angereichert um lesbare Beschreibungen fuer
|
||||
* die Uebersichtstabelle.
|
||||
*/
|
||||
function listAutomations($floor)
|
||||
{
|
||||
$db = meshDb();
|
||||
$stmt = $db->prepare("SELECT id FROM automations WHERE floor = ? ORDER BY name");
|
||||
$stmt->bind_param("s", $floor);
|
||||
$stmt->execute();
|
||||
$res = $stmt->get_result();
|
||||
$ids = [];
|
||||
while ($row = $res->fetch_assoc()) {
|
||||
$ids[] = intval($row["id"]);
|
||||
}
|
||||
$stmt->close();
|
||||
|
||||
$namesState = stateNames();
|
||||
$namesCommand = commandNames();
|
||||
|
||||
$list = [];
|
||||
foreach ($ids as $id) {
|
||||
$auto = loadAutomation($id);
|
||||
if (!$auto) {
|
||||
continue;
|
||||
}
|
||||
$auto["conditionText"] = describeConditions($auto["conditions"], $namesState);
|
||||
$auto["actionText"] = describeActions($auto["actions"], $namesCommand);
|
||||
$list[] = $auto;
|
||||
}
|
||||
return $list;
|
||||
}
|
||||
|
||||
/** id => "Geraet: Messwert" fuer alle Messwerte. */
|
||||
function stateNames()
|
||||
{
|
||||
$db = meshDb();
|
||||
$res = $db->query("SELECT s.id, s.state_name, s.unit, a.name AS actor_name
|
||||
FROM actor_states s JOIN actors a ON a.id = s.actor_id");
|
||||
$names = [];
|
||||
while ($row = $res->fetch_assoc()) {
|
||||
$names[intval($row["id"])] = [
|
||||
"text" => $row["actor_name"] . ": " . $row["state_name"],
|
||||
"unit" => $row["unit"],
|
||||
];
|
||||
}
|
||||
return $names;
|
||||
}
|
||||
|
||||
/** id => "Geraet: Kommando" fuer alle Kommandos. */
|
||||
function commandNames()
|
||||
{
|
||||
$db = meshDb();
|
||||
$res = $db->query("SELECT c.id, c.command_name, a.name AS actor_name
|
||||
FROM actor_commands c JOIN actors a ON a.id = c.actor_id");
|
||||
$names = [];
|
||||
while ($row = $res->fetch_assoc()) {
|
||||
$names[intval($row["id"])] = $row["actor_name"] . ": " . $row["command_name"];
|
||||
}
|
||||
return $names;
|
||||
}
|
||||
|
||||
/**
|
||||
* Die Bedingungen als Satz, so geklammert wie sie im Editor gezeichnet sind:
|
||||
* "(A und B) oder C". Denselben Text zeigt der Editor unter den Bloecken und
|
||||
* die Uebersicht in der Spalte "Ausloeser" - was man liest, ist damit genau
|
||||
* das, was der Runner rechnet.
|
||||
*/
|
||||
function describeConditions($conditions, $names)
|
||||
{
|
||||
$groups = [];
|
||||
foreach ($conditions as $c) {
|
||||
$state = $names[$c["state_id"]] ?? ["text" => "Messwert " . $c["state_id"], "unit" => null];
|
||||
$text = $state["text"] . " " . operatorLabel($c["operator"]) . " " . $c["value"];
|
||||
if ($state["unit"]) {
|
||||
$text .= " " . $state["unit"];
|
||||
}
|
||||
$groups[$c["group"]][] = $text;
|
||||
}
|
||||
if (!$groups) {
|
||||
return "";
|
||||
}
|
||||
$parts = [];
|
||||
foreach ($groups as $group) {
|
||||
$text = implode(" und ", $group);
|
||||
if (count($groups) > 1 && count($group) > 1) {
|
||||
$text = "(" . $text . ")";
|
||||
}
|
||||
$parts[] = $text;
|
||||
}
|
||||
return implode(" oder ", $parts);
|
||||
}
|
||||
|
||||
function describeActions($actions, $names)
|
||||
{
|
||||
$parts = [];
|
||||
foreach ($actions as $a) {
|
||||
$text = $names[$a["command_id"]] ?? ("Kommando " . $a["command_id"]);
|
||||
$values = array_values($a["params"]);
|
||||
if ($values) {
|
||||
$text .= " (" . implode(", ", $values) . ")";
|
||||
}
|
||||
$parts[] = $text;
|
||||
}
|
||||
return implode(", ", $parts);
|
||||
}
|
||||
Reference in New Issue
Block a user