Grundriss aus der Datenbank: Etagen und Raeume in homeMesh
Etagen, Raeume, Kachelpositionen und Kachelwerte standen als Tabelle in restricted/rooms.php, die Werte zusaetzlich ueberschreibbar in solarLog.kachelWerte. Fuer ein anderes Haus haette man den Quelltext umschreiben muessen. Jetzt liegen sie in homeMesh.floors und homeMesh.rooms (homeMesh_grundriss.sql). rooms.php liest sie und gibt sie in der bisherigen Form weiter; Startseite, Menue, Editor und Einstellungen sind unveraendert - vorher und nachher als HTML verglichen, gleich bis auf Livewerte und die zwei neuen Felder nr und thermostat in homeRooms. - Vorgabe einer Kachel ist jetzt allein das Thermostat (rooms.thermostat): Soll, Ist, Feuchte - sonst nichts. Was bisher im Code als Vorgabe stand, gilt damit als selbst gesetzt. - Grundrissbild je Etage (floors.bild) statt assets/img/<code>.png. - Standard-Etage (floors.standard) statt "OG" an vier Stellen. - meshDb() in eigener Datei, sonst Ring zwischen rooms.php und automations.php. - solarLog_kacheln.sql entfaellt; die Tabelle kachelWerte liest niemand mehr. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -122,7 +122,7 @@ if ($action === "kacheln") {
|
|||||||
"katalog" => kachelKatalog(),
|
"katalog" => kachelKatalog(),
|
||||||
// Fuer die Eingabe von Hand, wenn etwas nicht im Katalog steht.
|
// Fuer die Eingabe von Hand, wenn etwas nicht im Katalog steht.
|
||||||
"topics" => kachelTopicVorschlaege(),
|
"topics" => kachelTopicVorschlaege(),
|
||||||
"tabelleDa" => kachelTabelleDa(),
|
"tabelleDa" => grundrissDa(),
|
||||||
"max" => KACHEL_MAX,
|
"max" => KACHEL_MAX,
|
||||||
]);
|
]);
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
-- Grundriss der Home-Ansicht: Etagen, Raeume, Kacheln - Datenbank `homeMesh`
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
-- Vorher stand das alles fest in restricted/rooms.php, und die Werte auf den
|
||||||
|
-- Kacheln ließen sich getrennt davon in solarLog.kachelWerte ueberschreiben.
|
||||||
|
-- Fuer ein anderes Haus haette man den Quelltext umschreiben muessen. Jetzt
|
||||||
|
-- ist es Inhalt: eingestellt unter Einstellungen -> Grundriss, gelesen von
|
||||||
|
-- rooms.php.
|
||||||
|
--
|
||||||
|
-- In homeMesh und nicht in solarLog: actors.room und automations.floor zeigen
|
||||||
|
-- auf Raeume und Etagen, und Fremdschluessel greifen nur innerhalb einer
|
||||||
|
-- Datenbank.
|
||||||
|
--
|
||||||
|
-- Die Tabellen duerfen leer sein. Dann zeigt die Startseite einen Hinweis auf
|
||||||
|
-- die Einstellungen statt eines Hauses - das ist der Anfang jeder neuen
|
||||||
|
-- Installation.
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
SET NAMES utf8mb4;
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
-- Etagen. `code` ist der Schluessel und bleibt es: er steht in URLs
|
||||||
|
-- (?floor=EG), in SVG-Element-Ids (EG_Info), in automations.floor und im
|
||||||
|
-- MQTT-Baum. Umbenennen heisst deshalb `label` aendern, nicht `code`.
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
CREATE TABLE IF NOT EXISTS `floors` (
|
||||||
|
`code` varchar(4) NOT NULL COMMENT 'Kuerzel: URL, SVG-Ids, automations.floor. Nach dem Anlegen fest',
|
||||||
|
`label` varchar(40) NOT NULL COMMENT 'Ausgeschriebener Name, z.B. Erdgeschoss',
|
||||||
|
`position` int(11) NOT NULL DEFAULT 0 COMMENT 'Reihenfolge in Menue, Reitern und Etagenschaltern',
|
||||||
|
`bild` varchar(255) DEFAULT NULL COMMENT 'Grundriss relativ zum Web-Root, z.B. tiles/grundriss/EG-1a2b.png. NULL = noch keins',
|
||||||
|
`standard` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'Diese Etage oeffnet, wenn keine gewaehlt ist. Hoechstens eine',
|
||||||
|
PRIMARY KEY (`code`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
-- Raeume. `id` ist die feste Nummer, auf die Geraete zeigen sollen; Name und
|
||||||
|
-- Kuerzel duerfen sich aendern.
|
||||||
|
--
|
||||||
|
-- kuerzel Teil der SVG-Element-Ids: <floor>_w0_<kuerzel>. Je Etage
|
||||||
|
-- eindeutig, sonst schriebe ein Wert in zwei Kacheln.
|
||||||
|
-- x, y linke obere Ecke der 24 x 19 grossen Kachel in der 400 x 300
|
||||||
|
-- grossen Zeichenflaeche. NULL = Raum ohne Kachel.
|
||||||
|
-- thermostat Zweig der Thermostatwerte, z.B. Raumtemp/EG/Bad. Traegt das
|
||||||
|
-- Heizsymbol und die Vorgabewerte der Kachel (Soll, Ist,
|
||||||
|
-- Feuchte). NULL = kein Thermostat.
|
||||||
|
-- werte JSON-Liste der Kachelwerte (Form siehe restricted/kacheln.php).
|
||||||
|
-- NULL = die Vorgabe aus dem Thermostat; [] = bewusst kein
|
||||||
|
-- Messwert, die Kachel traegt nur ihren Namen.
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
CREATE TABLE IF NOT EXISTS `rooms` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`floor` varchar(4) NOT NULL,
|
||||||
|
`name` varchar(40) NOT NULL COMMENT 'Anzeigename, z.B. Wohnzimmer',
|
||||||
|
`kuerzel` varchar(40) NOT NULL COMMENT 'Teil der SVG-Ids, je Etage eindeutig',
|
||||||
|
`position` int(11) NOT NULL DEFAULT 0 COMMENT 'Reihenfolge innerhalb der Etage',
|
||||||
|
`x` smallint(6) DEFAULT NULL COMMENT 'Kachel: linke obere Ecke, NULL = keine Kachel',
|
||||||
|
`y` smallint(6) DEFAULT NULL,
|
||||||
|
`thermostat` varchar(120) DEFAULT NULL COMMENT 'MQTT-Zweig des Thermostats, NULL = keins',
|
||||||
|
`werte` text DEFAULT NULL COMMENT 'JSON der Kachelwerte, NULL = Vorgabe',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uq_rooms_kuerzel` (`floor`, `kuerzel`),
|
||||||
|
UNIQUE KEY `uq_rooms_name` (`floor`, `name`),
|
||||||
|
CONSTRAINT `fk_rooms_floor` FOREIGN KEY (`floor`) REFERENCES `floors` (`code`)
|
||||||
|
ON UPDATE CASCADE ON DELETE RESTRICT
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||||
@@ -203,7 +203,9 @@ const homeSVG = {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var currentFloor = "OG";
|
// Die Etage, mit der die Seite oeffnet - standardEtage() in rooms.php, von
|
||||||
|
// home.php in die Seite geschrieben. Stand frueher fest auf "OG".
|
||||||
|
var currentFloor = (typeof homeStandardEtage !== "undefined") ? homeStandardEtage : "";
|
||||||
|
|
||||||
function addClass(el, classNameToAdd){
|
function addClass(el, classNameToAdd){
|
||||||
el.className += ' ' + classNameToAdd;
|
el.className += ' ' + classNameToAdd;
|
||||||
|
|||||||
@@ -353,10 +353,9 @@ function zeichneKacheln() {
|
|||||||
if (!ziel || !kachelModell) return;
|
if (!ziel || !kachelModell) return;
|
||||||
|
|
||||||
if (!kachelModell.tabelleDa) {
|
if (!kachelModell.tabelleDa) {
|
||||||
ziel.innerHTML = '<div class="alert alert-warning mb-0">Die Tabelle '
|
ziel.innerHTML = '<div class="alert alert-warning mb-0">Etagen und Räume '
|
||||||
+ '<code>kachelWerte</code> fehlt noch. Sie steht in '
|
+ 'lassen sich nicht lesen – die Datenbank <code>homeMesh</code> ist nicht '
|
||||||
+ '<code>solarLog_kacheln.sql</code> – bis dahin zeigen alle Kacheln '
|
+ 'erreichbar, oder <code>homeMesh_grundriss.sql</code> fehlt noch.</div>';
|
||||||
+ 'ihre Vorgabe, und Speichern ist nicht möglich.</div>';
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,21 +14,9 @@
|
|||||||
* ausgewertet wird any(all(gruppe)).
|
* ausgewertet wird any(all(gruppe)).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
require_once(__DIR__ . "/mysql.php");
|
require_once(__DIR__ . "/meshdb.php");
|
||||||
require_once(__DIR__ . "/rooms.php");
|
require_once(__DIR__ . "/rooms.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
|
* Operatoren, die zu einem Datentyp passen. Immer ASCII - die huebschen
|
||||||
* Zeichen macht erst die Anzeige daraus. Frueher schickte der Editor sein
|
* Zeichen macht erst die Anzeige daraus. Frueher schickte der Editor sein
|
||||||
@@ -796,7 +784,7 @@ function emptyAutomation($floor)
|
|||||||
"name" => "",
|
"name" => "",
|
||||||
// Aufgerufen wird der Editor immer aus einem Etagenreiter. Fehlt die
|
// Aufgerufen wird der Editor immer aus einem Etagenreiter. Fehlt die
|
||||||
// Angabe trotzdem, gilt dieselbe Etage wie auf der Startseite.
|
// Angabe trotzdem, gilt dieselbe Etage wie auf der Startseite.
|
||||||
"floor" => in_array($floor, $GLOBALS["floors"], true) ? $floor : "OG",
|
"floor" => in_array($floor, $GLOBALS["floors"], true) ? $floor : standardEtage(),
|
||||||
"enabled" => true,
|
"enabled" => true,
|
||||||
"window_from" => "00:00",
|
"window_from" => "00:00",
|
||||||
"window_to" => "23:59",
|
"window_to" => "23:59",
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ require_once(__DIR__ . "/rooms.php");
|
|||||||
|
|
||||||
// $_GET["action"] ist von index.php bereits auf eine gueltige Seite gesetzt.
|
// $_GET["action"] ist von index.php bereits auf eine gueltige Seite gesetzt.
|
||||||
if (!isset($_GET["floor"]))
|
if (!isset($_GET["floor"]))
|
||||||
$_GET["floor"] = "OG";
|
$_GET["floor"] = standardEtage();
|
||||||
?>
|
?>
|
||||||
<head>
|
<head>
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||||
|
|||||||
+4
-3
@@ -74,7 +74,7 @@ require_once(__DIR__ . "/reiter.php");
|
|||||||
to=<?= $andere === $etage ? "1.0" : "0.0" ?> dur="0.7s"
|
to=<?= $andere === $etage ? "1.0" : "0.0" ?> dur="0.7s"
|
||||||
repeatCount="1" calcMode="spline" keyTimes="0;1" keySplines=".1,0,.27,1" fill="freeze" />
|
repeatCount="1" calcMode="spline" keyTimes="0;1" keySplines=".1,0,.27,1" fill="freeze" />
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
<image href="assets/img/<?= $etage ?>.png" height="100%" width="100%" x=0 y=0 />
|
<image href="<?= htmlspecialchars((string)etageBild($etage)) ?>" height="100%" width="100%" x=0 y=0 />
|
||||||
</g>
|
</g>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
<?php
|
<?php
|
||||||
@@ -114,7 +114,7 @@ require_once(__DIR__ . "/reiter.php");
|
|||||||
</g>
|
</g>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
<?php foreach (floorsWithPlan() as $floor): ?>
|
<?php foreach (floorsWithPlan() as $floor): ?>
|
||||||
<g id="<?= $floor ?>_Info" <?php if ($floor != "OG") echo 'opacity="0" '; ?><?php if($_GET["floor"] != $floor){echo "display='none'";} ?> >
|
<g id="<?= $floor ?>_Info" <?php if ($floor != standardEtage()) echo 'opacity="0" '; ?><?php if($_GET["floor"] != $floor){echo "display='none'";} ?> >
|
||||||
<?php foreach (array_map("mitWerten", roomsOnFloor($floor)) as $room): ?>
|
<?php foreach (array_map("mitWerten", roomsOnFloor($floor)) as $room): ?>
|
||||||
<?php
|
<?php
|
||||||
/*
|
/*
|
||||||
@@ -247,7 +247,8 @@ require_once(__DIR__ . "/reiter.php");
|
|||||||
// die der Browser dafuer beim Broker anmelden muss.
|
// die der Browser dafuer beim Broker anmelden muss.
|
||||||
const homeRooms = <?= json_encode(roomsWithTile(), JSON_UNESCAPED_UNICODE) ?>;
|
const homeRooms = <?= json_encode(roomsWithTile(), JSON_UNESCAPED_UNICODE) ?>;
|
||||||
const homeTopics = <?= json_encode(tileTopics(), JSON_UNESCAPED_UNICODE) ?>;
|
const homeTopics = <?= json_encode(tileTopics(), JSON_UNESCAPED_UNICODE) ?>;
|
||||||
<?php if ($_GET["floor"] != "OG" && in_array($_GET["floor"], floorsWithPlan())): ?>
|
const homeStandardEtage = <?= json_encode(standardEtage()) ?>;
|
||||||
|
<?php if ($_GET["floor"] != standardEtage() && in_array($_GET["floor"], floorsWithPlan())): ?>
|
||||||
document.addEventListener('readystatechange', function () {
|
document.addEventListener('readystatechange', function () {
|
||||||
if (event.target.readyState === 'complete') {
|
if (event.target.readyState === 'complete') {
|
||||||
switchFloor('<?= $_GET["floor"] ?>');
|
switchFloor('<?= $_GET["floor"] ?>');
|
||||||
|
|||||||
+21
-41
@@ -2,13 +2,13 @@
|
|||||||
/**
|
/**
|
||||||
* Modell-Schicht für die Anzeigewerte der Home-Kacheln.
|
* Modell-Schicht für die Anzeigewerte der Home-Kacheln.
|
||||||
*
|
*
|
||||||
* Was auf einer Kachel steht, stand bisher fest in restricted/rooms.php. Das
|
* Gespeichert wird in homeMesh.rooms.werte. Steht dort NULL, gilt die Vorgabe
|
||||||
* ist weiterhin die Vorgabe - hier kommt nur die Möglichkeit dazu, sie zu
|
* (kachelVorgabe() in rooms.php: mit Thermostat Soll, Ist und Feuchte, sonst
|
||||||
* überschreiben, ohne die Datei anzufassen. Eine Kachel ohne Zeile in
|
* nichts). Bis September 2026 lagen die Werte getrennt in solarLog.kachelWerte
|
||||||
* kachelWerte zeigt also genau das, was sie immer gezeigt hat.
|
* und überschrieben eine Tabelle im Quelltext - beides gibt es nicht mehr.
|
||||||
*
|
*
|
||||||
* Die Form einer Wertdefinition ist dieselbe wie in rooms.php, damit zwischen
|
* Die Form einer Wertdefinition ist überall dieselbe, damit zwischen Maske,
|
||||||
* Maske, Datenbank und Anzeige nichts übersetzt werden muss:
|
* Datenbank und Anzeige nichts übersetzt werden muss:
|
||||||
*
|
*
|
||||||
* topic MQTT-Topic, aus dem der Wert kommt
|
* topic MQTT-Topic, aus dem der Wert kommt
|
||||||
* pfad optional: Schlüssel in einer JSON-Nachricht
|
* pfad optional: Schlüssel in einer JSON-Nachricht
|
||||||
@@ -26,21 +26,15 @@
|
|||||||
* das "bi-" davor ("thermometer-half"). Erlaubt ist jedes,
|
* das "bi-" davor ("thermometer-half"). Erlaubt ist jedes,
|
||||||
* das im Stylesheet steht - siehe bootstrapIcons() in
|
* das im Stylesheet steht - siehe bootstrapIcons() in
|
||||||
* rooms.php; kachelSymbole() ist nur die Vorschlagsliste.
|
* rooms.php; kachelSymbole() ist nur die Vorschlagsliste.
|
||||||
*
|
|
||||||
* Die Tabelle darf fehlen. Ist sie nicht angelegt (solarLog_kacheln.sql), so
|
|
||||||
* verhält sich alles wie vorher, statt dass die Startseite mit einem
|
|
||||||
* SQL-Fehler stehenbleibt - die Kacheln sind keine Einstellung, ohne die man
|
|
||||||
* das Haus nicht mehr sieht.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Geladen wird nicht hier, sondern in rooms.php: kachelVorgabe() und
|
* Geladen wird nicht hier, sondern in rooms.php: kachelVorgabe() und das
|
||||||
* kachelUeberschreibungen() stehen dort, weil die Startseite sie braucht und
|
* Lesen der Werte stehen dort, weil die Startseite sie braucht und diese
|
||||||
* diese Datei nicht. So bleibt der Weg zur Anzeige kurz - und es gibt keine
|
* Datei nicht. So bleibt der Weg zur Anzeige kurz - und es gibt keine
|
||||||
* Ringabhängigkeit zwischen Raum-Modell und Maske.
|
* Ringabhängigkeit zwischen Raum-Modell und Maske.
|
||||||
*/
|
*/
|
||||||
require_once(__DIR__ . "/rooms.php"); // Räume, Vorgaben, Überschreibungen
|
require_once(__DIR__ . "/rooms.php"); // Räume, Vorgaben, meshDb()
|
||||||
require_once(__DIR__ . "/costs.php"); // solarDb()
|
|
||||||
|
|
||||||
/** Höchstens drei Werte je Kachel - mehr passt nicht auf 24 x 19 Einheiten. */
|
/** Höchstens drei Werte je Kachel - mehr passt nicht auf 24 x 19 Einheiten. */
|
||||||
const KACHEL_MAX = 3;
|
const KACHEL_MAX = 3;
|
||||||
@@ -153,20 +147,18 @@ function kachelIconNamen()
|
|||||||
*/
|
*/
|
||||||
function kachelListe()
|
function kachelListe()
|
||||||
{
|
{
|
||||||
$ueber = kachelUeberschreibungen();
|
|
||||||
$liste = [];
|
$liste = [];
|
||||||
foreach (allRooms() as $room) {
|
foreach (allRooms() as $room) {
|
||||||
if (!isset($room["x"])) {
|
if (!isset($room["x"])) {
|
||||||
continue; // kein Platz im Grundriss, keine Kachel
|
continue; // kein Platz im Grundriss, keine Kachel
|
||||||
}
|
}
|
||||||
$key = $room["floor"] . "|" . $room["id"];
|
|
||||||
$liste[] = [
|
$liste[] = [
|
||||||
"floor" => $room["floor"],
|
"floor" => $room["floor"],
|
||||||
"floorLabel" => floorLabel($room["floor"]),
|
"floorLabel" => floorLabel($room["floor"]),
|
||||||
"room" => $room["id"],
|
"room" => $room["id"],
|
||||||
"name" => $room["mqtt"],
|
"name" => $room["mqtt"],
|
||||||
"eigen" => isset($ueber[$key]),
|
"eigen" => isset($room["werte"]),
|
||||||
"werte" => $ueber[$key] ?? kachelVorgabe($room),
|
"werte" => $room["werte"] ?? kachelVorgabe($room),
|
||||||
"vorgabe" => kachelVorgabe($room),
|
"vorgabe" => kachelVorgabe($room),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -275,14 +267,7 @@ function kachelTopicVorschlaege()
|
|||||||
$topics = [];
|
$topics = [];
|
||||||
|
|
||||||
foreach (allRooms() as $room) {
|
foreach (allRooms() as $room) {
|
||||||
foreach (kachelVorgabe($room) as $wert) {
|
foreach (array_merge(kachelVorgabe($room), $room["werte"] ?? []) as $wert) {
|
||||||
if (!empty($wert["topic"])) {
|
|
||||||
$topics[$wert["topic"]] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
foreach (kachelUeberschreibungen() as $werte) {
|
|
||||||
foreach ($werte as $wert) {
|
|
||||||
if (!empty($wert["topic"])) {
|
if (!empty($wert["topic"])) {
|
||||||
$topics[$wert["topic"]] = true;
|
$topics[$wert["topic"]] = true;
|
||||||
}
|
}
|
||||||
@@ -381,19 +366,15 @@ function kachelWertPruefen($wert, $nummer)
|
|||||||
/**
|
/**
|
||||||
* Die Werte einer Kachel speichern.
|
* Die Werte einer Kachel speichern.
|
||||||
*
|
*
|
||||||
* $werte === null setzt auf die Vorgabe zurück - die Zeile verschwindet, statt
|
* $werte === null setzt auf die Vorgabe zurück - in der Spalte steht dann
|
||||||
* die Vorgabe hineinzukopieren. Sonst stünde in der Datenbank eine Kopie, die
|
* NULL, statt die Vorgabe hineinzukopieren. Sonst stünde dort eine Kopie, die
|
||||||
* nicht mitzöge, wenn sich rooms.php ändert.
|
* nicht mitzöge, wenn sich das Thermostat des Raums ändert.
|
||||||
*
|
*
|
||||||
* Eine leere Liste ist etwas anderes: die Kachel zeigt dann bewusst keinen
|
* Eine leere Liste ist etwas anderes: die Kachel zeigt dann bewusst keinen
|
||||||
* Messwert, sondern nur ihren Namen.
|
* Messwert, sondern nur ihren Namen.
|
||||||
*/
|
*/
|
||||||
function kachelWerteSpeichern($floor, $room, $werte)
|
function kachelWerteSpeichern($floor, $room, $werte)
|
||||||
{
|
{
|
||||||
if (!kachelTabelleDa()) {
|
|
||||||
throw new RuntimeException(
|
|
||||||
"Die Tabelle kachelWerte fehlt. Sie steht in solarLog_kacheln.sql.");
|
|
||||||
}
|
|
||||||
$treffer = null;
|
$treffer = null;
|
||||||
foreach (allRooms() as $r) {
|
foreach (allRooms() as $r) {
|
||||||
if ($r["floor"] === $floor && $r["id"] === $room && isset($r["x"])) {
|
if ($r["floor"] === $floor && $r["id"] === $room && isset($r["x"])) {
|
||||||
@@ -404,10 +385,10 @@ function kachelWerteSpeichern($floor, $room, $werte)
|
|||||||
throw new InvalidArgumentException("Diese Kachel gibt es nicht.");
|
throw new InvalidArgumentException("Diese Kachel gibt es nicht.");
|
||||||
}
|
}
|
||||||
|
|
||||||
$db = solarDb();
|
$db = meshDb();
|
||||||
if ($werte === null) {
|
if ($werte === null) {
|
||||||
$stmt = $db->prepare("DELETE FROM kachelWerte WHERE floor = ? AND room = ?");
|
$stmt = $db->prepare("UPDATE rooms SET werte = NULL WHERE id = ?");
|
||||||
$stmt->bind_param("ss", $floor, $room);
|
$stmt->bind_param("i", $treffer["nr"]);
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -432,9 +413,8 @@ function kachelWerteSpeichern($floor, $room, $werte)
|
|||||||
}
|
}
|
||||||
|
|
||||||
$json = json_encode($sauber, JSON_UNESCAPED_UNICODE);
|
$json = json_encode($sauber, JSON_UNESCAPED_UNICODE);
|
||||||
$stmt = $db->prepare("INSERT INTO kachelWerte (floor, room, werte) VALUES (?, ?, ?)
|
$stmt = $db->prepare("UPDATE rooms SET werte = ? WHERE id = ?");
|
||||||
ON DUPLICATE KEY UPDATE werte = VALUES(werte)");
|
$stmt->bind_param("si", $json, $treffer["nr"]);
|
||||||
$stmt->bind_param("sss", $floor, $room, $json);
|
|
||||||
if (!$stmt->execute()) {
|
if (!$stmt->execute()) {
|
||||||
throw new RuntimeException($stmt->error);
|
throw new RuntimeException($stmt->error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Verbindung zur Datenbank homeMesh: Geraete, Automatiken, Grundriss.
|
||||||
|
*
|
||||||
|
* Stand frueher in automations.php. Seit Etagen und Raeume ebenfalls in
|
||||||
|
* homeMesh liegen, braucht auch rooms.php die Verbindung - und automations.php
|
||||||
|
* bindet rooms.php ein. Als eigene Datei gibt es dafuer keinen Ring.
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once(__DIR__ . "/mysql.php");
|
||||||
|
|
||||||
|
/** Verbindung zur Geraete- und Automatik-Datenbank, einmal je Anfrage. */
|
||||||
|
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;
|
||||||
|
}
|
||||||
+155
-244
@@ -1,180 +1,103 @@
|
|||||||
<?php
|
<?php
|
||||||
/*
|
/*
|
||||||
* Einzige Quelle für die Räume der Home-Ansicht.
|
* Etagen und Räume der Home-Ansicht.
|
||||||
*
|
*
|
||||||
* Die Werte auf den Kacheln sind hier die Vorgabe; überschrieben werden sie
|
* Bis September 2026 standen sie hier als Tabelle im Quelltext - für ein
|
||||||
* auf der Einstellungsseite (Reiter "Home-Kacheln"). Was dort gesetzt wurde,
|
* anderes Haus hätte man die Datei umschreiben müssen. Jetzt stehen sie in
|
||||||
* steht in der Tabelle kachelWerte und hat Vorrang - alles Übrige zeigt
|
* homeMesh (floors, rooms; Aufbau in homeMesh_grundriss.sql) und werden unter
|
||||||
* weiterhin, was hier steht.
|
* Einstellungen -> Grundriss gepflegt. Diese Datei liest sie und reicht sie in
|
||||||
|
* der Form weiter, die Startseite, Menü, Automatik-Editor und Einstellungen
|
||||||
|
* seit jeher erwarten.
|
||||||
*
|
*
|
||||||
* Aus dieser Tabelle entstehen sowohl die SVG-Kacheln in home.php als auch
|
* Ein Raum sieht deshalb aus wie früher, mit zwei Feldern mehr:
|
||||||
* die Aktualisierungsschleife in js/solar/homeMQTT.js (die Tabelle wird dafür
|
|
||||||
* als JSON in die Seite geschrieben). Ein neuer oder umbenannter Raum ist
|
|
||||||
* damit eine Zeile hier statt drei Stellen im Code.
|
|
||||||
*
|
*
|
||||||
* floor Etage; bestimmt zugleich den Zweig Raumtemp/<floor>/
|
* nr feste Nummer (rooms.id) - bleibt, wenn der Raum umbenannt wird
|
||||||
* mqtt Name des Raums im MQTT-Baum: Raumtemp/<floor>/<mqtt>/...
|
* floor Etage; dieselben Kürzel wie in floors.code
|
||||||
* id Kürzel in den SVG-Element-IDs: <floor>_w0_<id>, <floor>_<id>_heater
|
* mqtt Name des Raums (rooms.name). Der Schlüssel heißt aus
|
||||||
* x, y Position der Kachel im SVG (viewBox 400x300) - fehlt bei Räumen
|
* Gewohnheit weiter "mqtt": er stand früher zugleich im Topic
|
||||||
* ohne Kachel
|
* Raumtemp/<floor>/<mqtt>/..., und zwanzig Stellen lesen ihn so.
|
||||||
* werte Was auf der Kachel steht, sofern auf der Einstellungsseite nichts
|
* id Kürzel in den SVG-Element-IDs: <floor>_w0_<id>, <floor>_<id>_heater
|
||||||
* anderes gewählt wurde (Tabelle kachelWerte, siehe
|
* x, y Position der Kachel im SVG (viewBox 400x300) - fehlt bei Räumen
|
||||||
* restricted/kacheln.php): bis zu drei Messwerte, jeder mit
|
* ohne Kachel
|
||||||
* topic (oder wechselrichter, siehe Carport), optional pfad
|
* thermostat Zweig der Thermostatwerte (Raumtemp/EG/Bad) oder null
|
||||||
* (Schlüssel in einer JSON-Nachricht) und entweder format
|
* werte die auf der Einstellungsseite gesetzten Kachelwerte - fehlt,
|
||||||
* ("leistung" skaliert selbst zwischen W und kW, "leistung_kw"
|
* solange die Vorgabe gilt (siehe kachelVorgabe())
|
||||||
* ebenso fuer Quellen, die schon Kilowatt melden) oder dem Paar
|
|
||||||
* einheit und stellen (Nachkommastellen). Fehlt der Eintrag, sind
|
|
||||||
* es die drei Thermostatwerte - Soll, Ist, Feuchte - wie bisher.
|
|
||||||
* Eine leere Liste heisst: kein Messwert, die Kachel trägt nur
|
|
||||||
* ihren Namen und öffnet den Raum.
|
|
||||||
*
|
*
|
||||||
* Ein Raum ohne x/y ist trotzdem ein Raum: Geräte lassen sich ihm zuordnen
|
* Ein Raum ohne x/y ist trotzdem ein Raum: Geräte lassen sich ihm zuordnen
|
||||||
* (siehe die Spalte actors.room und den Zuordnungsdialog in der Karte
|
* (actors.room), er erscheint nur nicht als Kachel im Grundriss.
|
||||||
* "Automatismen"), er erscheint nur nicht als Kachel im Grundriss. Derzeit
|
|
||||||
* hat jeder Raum eine Kachel; die Unterscheidung bleibt, weil ein neuer Raum
|
|
||||||
* zuerst als Zeile ohne Position entsteht und erst dann seinen Platz im
|
|
||||||
* Grundriss bekommt.
|
|
||||||
*
|
*
|
||||||
* Wo eine Kachel hingehört, misst tools/kachelpositionen.php aus: Grundriss
|
* Ohne Datenbank oder ohne die beiden Tabellen gibt es keine Etagen - die
|
||||||
* mit Raster, Geisterkachel am Zeiger, fertige Zeile zum Abschreiben. x/y
|
* Startseite zeigt dann einen Hinweis statt eines Fatal Errors.
|
||||||
* ist die linke obere Ecke der 24 x 19 grossen Kachel, nicht ihre Mitte.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// solarDb() für die Kachelwerte. costs.php definiert nur Funktionen und
|
require_once(__DIR__ . "/meshdb.php");
|
||||||
// verbindet sich erst, wenn eine davon gerufen wird.
|
|
||||||
require_once(__DIR__ . "/costs.php");
|
|
||||||
|
|
||||||
// Reihenfolge im Menü und in der Anzeige; OG ist die Standardetage.
|
/**
|
||||||
//
|
* Etagen und Räume aus homeMesh, einmal je Anfrage.
|
||||||
// AG ist das Außengelände - eine Etage ohne Grundriss. Was draußen hängt,
|
*
|
||||||
// stand vorher als Pseudo-Raum "aussen" in automations.php und war damit ein
|
* Gibt [etagen, raeume] zurück: etagen ist code => Zeile aus floors in der
|
||||||
// einziger Topf: Carport, Pergola, Garage und die Wechselrichter alle
|
* eingestellten Reihenfolge, raeume die Liste im Format oben.
|
||||||
// nebeneinander. Als Etage bekommt jeder davon einen Raum, und die
|
*/
|
||||||
// Geräteauswahl im Automatik-Editor gruppiert danach wie überall sonst.
|
function grundriss()
|
||||||
$floors = ["OG", "EG", "UG", "AG"];
|
{
|
||||||
|
static $stand = null;
|
||||||
|
if ($stand !== null) {
|
||||||
|
return $stand;
|
||||||
|
}
|
||||||
|
$etagen = [];
|
||||||
|
$raeume = [];
|
||||||
|
try {
|
||||||
|
$db = meshDb();
|
||||||
|
$res = $db->query("SELECT code, label, position, bild, standard FROM floors
|
||||||
|
ORDER BY position, code");
|
||||||
|
while ($res && $z = $res->fetch_assoc()) {
|
||||||
|
$z["standard"] = (bool)$z["standard"];
|
||||||
|
$etagen[$z["code"]] = $z;
|
||||||
|
}
|
||||||
|
$res = $db->query("SELECT r.id, r.floor, r.name, r.kuerzel, r.x, r.y, r.thermostat, r.werte
|
||||||
|
FROM rooms r JOIN floors f ON f.code = r.floor
|
||||||
|
ORDER BY f.position, r.position, r.id");
|
||||||
|
while ($res && $z = $res->fetch_assoc()) {
|
||||||
|
$raum = [
|
||||||
|
"nr" => intval($z["id"]),
|
||||||
|
"floor" => $z["floor"],
|
||||||
|
"mqtt" => $z["name"],
|
||||||
|
"id" => $z["kuerzel"],
|
||||||
|
"thermostat" => $z["thermostat"] !== null && $z["thermostat"] !== ""
|
||||||
|
? $z["thermostat"] : null,
|
||||||
|
];
|
||||||
|
// Nur mit Position eine Kachel - isset($room["x"]) ist die Frage,
|
||||||
|
// die überall gestellt wird.
|
||||||
|
if ($z["x"] !== null && $z["y"] !== null) {
|
||||||
|
$raum["x"] = intval($z["x"]);
|
||||||
|
$raum["y"] = intval($z["y"]);
|
||||||
|
}
|
||||||
|
if ($z["werte"] !== null) {
|
||||||
|
$liste = json_decode($z["werte"], true);
|
||||||
|
if (is_array($liste)) {
|
||||||
|
$raum["werte"] = array_slice(array_values($liste), 0, 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$raeume[] = $raum;
|
||||||
|
}
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
// Siehe oben: lieber ein leeres Haus mit Hinweis als ein Abbruch.
|
||||||
|
$etagen = [];
|
||||||
|
$raeume = [];
|
||||||
|
$GLOBALS["grundrissFehler"] = $e->getMessage();
|
||||||
|
}
|
||||||
|
$stand = [$etagen, $raeume];
|
||||||
|
return $stand;
|
||||||
|
}
|
||||||
|
|
||||||
// Ausgeschriebene Namen für Beschriftungen, wo Platz ist. Das Kürzel steht in
|
// Die drei globalen Namen, die seit jeher gelesen werden: Reihenfolge der
|
||||||
// URLs (?floor=AG), in SVG-Element-Ids (AG_Info) und im MQTT-Baum - dort will
|
// Etagen, ihre Namen und die Raumliste.
|
||||||
// man weder Umlaute noch Länge.
|
// Über $GLOBALS und nicht als schlichte Zuweisung: wird diese Datei zuerst aus
|
||||||
$floorLabels = [
|
// einer Funktion heraus eingebunden, landeten die Namen sonst in deren
|
||||||
"OG" => "Obergeschoss",
|
// lokalem Gültigkeitsbereich.
|
||||||
"EG" => "Erdgeschoss",
|
$GLOBALS["rooms"] = grundriss()[1];
|
||||||
"UG" => "Untergeschoss",
|
$GLOBALS["floors"] = array_keys(grundriss()[0]);
|
||||||
"AG" => "Außengelände",
|
$GLOBALS["floorLabels"] = array_column(grundriss()[0], "label", "code");
|
||||||
];
|
|
||||||
|
|
||||||
$rooms = [
|
|
||||||
["floor" => "OG", "mqtt" => "Wohnzimmer", "id" => "wozi", "x" => 180, "y" => 140],
|
|
||||||
["floor" => "OG", "mqtt" => "KiZi", "id" => "kizi", "x" => 130, "y" => 80],
|
|
||||||
["floor" => "OG", "mqtt" => "Schlafzimmer", "id" => "schlafen", "x" => 235, "y" => 70],
|
|
||||||
["floor" => "OG", "mqtt" => "Bad", "id" => "bad", "x" => 260, "y" => 120],
|
|
||||||
["floor" => "OG", "mqtt" => "Buero", "id" => "buero", "x" => 265, "y" => 175],
|
|
||||||
// Kein Thermostat, und noch kein Gerät zugeordnet - die Kachel trägt
|
|
||||||
// vorerst nur ihren Namen.
|
|
||||||
["floor" => "OG", "mqtt" => "Kueche", "id" => "kueche", "x" => 122, "y" => 138,
|
|
||||||
"werte" => []],
|
|
||||||
["floor" => "OG", "mqtt" => "Technik", "id" => "technik", "x" => 173, "y" => 67,
|
|
||||||
"werte" => []],
|
|
||||||
["floor" => "EG", "mqtt" => "Wohnzimmer", "id" => "wozi", "x" => 185, "y" => 150],
|
|
||||||
["floor" => "EG", "mqtt" => "Florian", "id" => "kizi", "x" => 130, "y" => 90],
|
|
||||||
["floor" => "EG", "mqtt" => "Schlafen", "id" => "schlafen", "x" => 235, "y" => 80],
|
|
||||||
["floor" => "EG", "mqtt" => "Bad", "id" => "bad", "x" => 253, "y" => 130],
|
|
||||||
["floor" => "EG", "mqtt" => "Magdalena", "id" => "buero", "x" => 258, "y" => 180],
|
|
||||||
["floor" => "EG", "mqtt" => "Kueche", "id" => "kueche", "x" => 126, "y" => 146,
|
|
||||||
"werte" => []],
|
|
||||||
// Der Shelly-Zähler des EG meldet seine Werte als JSON in einer einzigen
|
|
||||||
// Nachricht; "pfad" greift den Schlüssel daraus heraus.
|
|
||||||
["floor" => "EG", "mqtt" => "Technik", "id" => "technik", "x" => 172, "y" => 78,
|
|
||||||
"werte" => [
|
|
||||||
["topic" => "Power_EG/status/em:0", "pfad" => "total_act_power",
|
|
||||||
"einheit" => "W", "stellen" => 0, "gross" => true],
|
|
||||||
]],
|
|
||||||
["floor" => "UG", "mqtt" => "Buero", "id" => "buero", "x" => 140, "y" => 140],
|
|
||||||
["floor" => "UG", "mqtt" => "Bad", "id" => "bad", "x" => 210, "y" => 80],
|
|
||||||
["floor" => "UG", "mqtt" => "Kueche", "id" => "kueche", "x" => 190, "y" => 195],
|
|
||||||
["floor" => "UG", "mqtt" => "Technik", "id" => "technik", "x" => 238, "y" => 93,
|
|
||||||
"werte" => [
|
|
||||||
["topic" => "Power_UG/status/em:0", "pfad" => "total_act_power",
|
|
||||||
"einheit" => "W", "stellen" => 0, "gross" => true],
|
|
||||||
["topic" => "wasser/main/value", "einheit" => "m³", "stellen" => 1],
|
|
||||||
]],
|
|
||||||
// Außengelände: der Grundriss ist ein freigestelltes Rendering des
|
|
||||||
// Grundstücks (assets/img/AG.png). Die Kachelpositionen sind darauf
|
|
||||||
// eingemessen; nachklicken geht mit tools/kachelpositionen.php.
|
|
||||||
//
|
|
||||||
// Messwerte nur, wo es etwas zu messen gibt: die beiden Wallboxen und
|
|
||||||
// der Regen. Veranda, Terrasse und Pergola tragen ihren Namen - dort
|
|
||||||
// haengen Licht und Rollladen, und die zeigt das Raum-Modal.
|
|
||||||
//
|
|
||||||
// Die Namen stammen von den Geräten selbst: die Hoymiles heißen seit jeher
|
|
||||||
// "Carport 1-6" und "Veranda OG2-4", das Sonnensegel sitzt an der Pergola.
|
|
||||||
// Zwei Carports, einer je Wohnpartei - deshalb dieselben Kuerzel wie die
|
|
||||||
// Etagen. Die Wattpilot der EG-Partei steht dagegen in der Garage, siehe
|
|
||||||
// restricted/config.php; am Carport OG haengt der go-e Charger.
|
|
||||||
// Die PV des Carports haengt an mehreren Hoymiles. Sie werden ueber
|
|
||||||
// ihren Namen gesucht und aufsummiert, nicht ueber ihre Nummer: die
|
|
||||||
// Topics heissen solarManager/invertersN, und N ist eine Position in
|
|
||||||
// einer Liste. Aendert der SolarManager die Reihenfolge, zeigte eine
|
|
||||||
// fest eingetragene Nummer stillschweigend das falsche Dach an.
|
|
||||||
//
|
|
||||||
// Die Namen muessen genau so lauten wie unter invertersN/name. Passt
|
|
||||||
// einer nicht, schreibt homeMQTT.js die vorhandenen Namen in die
|
|
||||||
// Browser-Konsole - dann steht dort, was hier hingehoert.
|
|
||||||
["floor" => "AG", "mqtt" => "Carport EG", "id" => "carport_eg", "x" => 179, "y" => 207,
|
|
||||||
"werte" => [
|
|
||||||
["wechselrichter" => ["Carport 1-6", "Carport 7-12", "Carport 13-16",
|
|
||||||
"Carport 17-19"],
|
|
||||||
"feld" => "p_AC", "format" => "leistung", "icon" => "sun"],
|
|
||||||
]],
|
|
||||||
// Am Carport OG haengt der go-e Charger. Die Ladeleistung ist das
|
|
||||||
// Einzige, was sich hier messen laesst - und sie beantwortet die Frage,
|
|
||||||
// die man sich vor dem Carport stellt: laedt gerade jemand?
|
|
||||||
["floor" => "AG", "mqtt" => "Carport OG", "id" => "carport_og", "x" => 72, "y" => 154,
|
|
||||||
"werte" => [
|
|
||||||
["topic" => "solarManager/evPowerOG", "format" => "leistung_kw",
|
|
||||||
"icon" => "ev-station"],
|
|
||||||
]],
|
|
||||||
// Veranda und Terrasse gibt es zweimal, je Wohnpartei eine. In der
|
|
||||||
// Draufsicht laege das obere Paar genau auf dem unteren - deshalb die
|
|
||||||
// Schraegsicht, siehe restricted/aussenplan.php.
|
|
||||||
//
|
|
||||||
// Die beiden Veranden haben bewusst kein x/y: ihre Kacheln lagen auf der
|
|
||||||
// Westfassade des Hauses so dicht beieinander, dass sie mehr gestiftet
|
|
||||||
// als erklaert haben. Als Raeume bleiben sie bestehen - Geraete sind
|
|
||||||
// ihnen zugeordnet und Automatiken laufen darauf, das haengt allein
|
|
||||||
// daran, dass sie hier stehen.
|
|
||||||
//
|
|
||||||
// Kommen die Kacheln zurueck, sind es zwei Zahlen je Zeile (gemessen mit
|
|
||||||
// tools/kachelpositionen.php, zuletzt 156/132 und 150/92) und fuer die
|
|
||||||
// Veranda OG diese Wechselrichter:
|
|
||||||
//
|
|
||||||
// ["wechselrichter" => ["Veranda OG2-4", "Veranda UG/OG1"],
|
|
||||||
// "feld" => "p_AC", "format" => "leistung", "icon" => "sun"]
|
|
||||||
["floor" => "AG", "mqtt" => "Veranda EG", "id" => "veranda_eg", "werte" => []],
|
|
||||||
["floor" => "AG", "mqtt" => "Veranda OG", "id" => "veranda_og", "werte" => []],
|
|
||||||
["floor" => "AG", "mqtt" => "Terrasse EG", "id" => "terrasse_eg", "x" => 229, "y" => 158, "werte" => []],
|
|
||||||
["floor" => "AG", "mqtt" => "Terrasse OG", "id" => "terrasse_og", "x" => 223, "y" => 124, "werte" => []],
|
|
||||||
["floor" => "AG", "mqtt" => "Pergola", "id" => "pergola", "x" => 344, "y" => 111, "werte" => []],
|
|
||||||
// In der Garage steht die Wattpilot der EG-Partei, siehe fahrzeuge() in
|
|
||||||
// restricted/config.php. Ohne Kuerzel im Topic ist das die EG-Wallbox.
|
|
||||||
["floor" => "AG", "mqtt" => "Garage", "id" => "garage", "x" => 131, "y" => 135,
|
|
||||||
"werte" => [
|
|
||||||
["topic" => "solarManager/evPower", "format" => "leistung_kw",
|
|
||||||
"icon" => "ev-station"],
|
|
||||||
]],
|
|
||||||
// Der Garten ist kein Bauteil wie die anderen, aber ein Ort: hier haengen
|
|
||||||
// der Regensensor und die beiden Ventilsteuerungen.
|
|
||||||
["floor" => "AG", "mqtt" => "Garten", "id" => "garten", "x" => 298, "y" => 176,
|
|
||||||
"werte" => [
|
|
||||||
// gatherRainData.py im SolarManager legt auf Wetter/Regen ein JSON
|
|
||||||
// mit tage1 bis tage7 und dem Stand des Sammlers. "tage1" ist der
|
|
||||||
// laufende Tag - die Zahl, die man im Garten wissen will. Ein
|
|
||||||
// laengeres Fenster ist ein geaenderter Pfad und sonst nichts.
|
|
||||||
["topic" => "Wetter/Regen", "pfad" => "tage1", "einheit" => "mm",
|
|
||||||
"stellen" => 1, "icon" => "cloud-rain"],
|
|
||||||
]],
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Räume einer Etage, die eine Kachel im Grundriss haben.
|
* Räume einer Etage, die eine Kachel im Grundriss haben.
|
||||||
@@ -185,21 +108,64 @@ $rooms = [
|
|||||||
*/
|
*/
|
||||||
function roomsOnFloor($floor)
|
function roomsOnFloor($floor)
|
||||||
{
|
{
|
||||||
return array_values(array_filter($GLOBALS["rooms"], function ($room) use ($floor) {
|
return array_values(array_filter(grundriss()[1], function ($room) use ($floor) {
|
||||||
return $room["floor"] == $floor && isset($room["x"]);
|
return $room["floor"] == $floor && isset($room["x"]);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ließen sich Etagen und Räume lesen? Nein heißt: Datenbank weg oder
|
||||||
|
* homeMesh_grundriss.sql noch nicht eingespielt - nicht "noch nichts
|
||||||
|
* angelegt", das ist ein leeres, aber gültiges Haus.
|
||||||
|
*/
|
||||||
|
function grundrissDa()
|
||||||
|
{
|
||||||
|
grundriss();
|
||||||
|
return !isset($GLOBALS["grundrissFehler"]);
|
||||||
|
}
|
||||||
|
|
||||||
/** Alle Räume, auch die ohne Kachel - für die Geräte-Zuordnung. */
|
/** Alle Räume, auch die ohne Kachel - für die Geräte-Zuordnung. */
|
||||||
function allRooms()
|
function allRooms()
|
||||||
{
|
{
|
||||||
return $GLOBALS["rooms"];
|
return grundriss()[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Ausgeschriebener Name einer Etage; unbekannte bleiben ihr Kürzel. */
|
/** Ausgeschriebener Name einer Etage; unbekannte bleiben ihr Kürzel. */
|
||||||
function floorLabel($floor)
|
function floorLabel($floor)
|
||||||
{
|
{
|
||||||
return $GLOBALS["floorLabels"][$floor] ?? $floor;
|
return grundriss()[0][$floor]["label"] ?? $floor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Etage, die öffnet, wenn keine gewählt ist.
|
||||||
|
*
|
||||||
|
* Stand früher als "OG" an vier Stellen. Ist keine als Standard markiert,
|
||||||
|
* gilt die erste mit Grundriss, sonst die erste überhaupt.
|
||||||
|
*/
|
||||||
|
function standardEtage()
|
||||||
|
{
|
||||||
|
$etagen = grundriss()[0];
|
||||||
|
foreach ($etagen as $code => $e) {
|
||||||
|
if ($e["standard"]) {
|
||||||
|
return $code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$mitPlan = floorsWithPlan();
|
||||||
|
if ($mitPlan) {
|
||||||
|
return $mitPlan[0];
|
||||||
|
}
|
||||||
|
return array_key_first($etagen) ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Der Grundriss einer Etage als Pfad relativ zum Web-Root, oder null.
|
||||||
|
*
|
||||||
|
* Mitgelieferte Bilder liegen unter assets/img, hochgeladene unter
|
||||||
|
* tiles/grundriss - der Webserver darf nur dort schreiben.
|
||||||
|
*/
|
||||||
|
function etageBild($floor)
|
||||||
|
{
|
||||||
|
return grundriss()[0][$floor]["bild"] ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -257,13 +223,12 @@ function kachelIcon($name)
|
|||||||
/**
|
/**
|
||||||
* Etagen mit Grundriss - die, die im Menü und im SVG auftauchen.
|
* Etagen mit Grundriss - die, die im Menü und im SVG auftauchen.
|
||||||
*
|
*
|
||||||
* Das Außengelände ist eine Etage ohne Bild: seine Räume haben keine Kachel,
|
* Eine Etage ohne einen einzigen platzierten Raum führte auf eine leere
|
||||||
* und ein Menüpunkt dorthin führte auf eine leere Zeichenfläche. Sobald der
|
* Zeichenfläche. Sobald ihr erster Raum ein x/y bekommt, erscheint sie von
|
||||||
* erste AG-Raum ein x/y bekommt, erscheint die Etage von selbst auch dort -
|
* selbst - es gibt keine zweite Liste, die man nachziehen müsste.
|
||||||
* es gibt keine zweite Liste, die man nachziehen müsste.
|
|
||||||
*
|
*
|
||||||
* Für die Zuordnung und die Automatiken zählt dagegen $floors: dort ist das
|
* Für die Zuordnung und die Automatiken zählt dagegen $floors: dort ist jede
|
||||||
* Außengelände von Anfang an dabei.
|
* Etage von Anfang an dabei.
|
||||||
*/
|
*/
|
||||||
function floorsWithPlan()
|
function floorsWithPlan()
|
||||||
{
|
{
|
||||||
@@ -275,7 +240,7 @@ function floorsWithPlan()
|
|||||||
/** Nur die Räume mit Kachel, für die Aktualisierungsschleife im Browser. */
|
/** Nur die Räume mit Kachel, für die Aktualisierungsschleife im Browser. */
|
||||||
function roomsWithTile()
|
function roomsWithTile()
|
||||||
{
|
{
|
||||||
return array_values(array_map("mitWerten", array_filter($GLOBALS["rooms"], function ($room) {
|
return array_values(array_map("mitWerten", array_filter(grundriss()[1], function ($room) {
|
||||||
return isset($room["x"]);
|
return isset($room["x"]);
|
||||||
})));
|
})));
|
||||||
}
|
}
|
||||||
@@ -283,16 +248,16 @@ function roomsWithTile()
|
|||||||
/**
|
/**
|
||||||
* Was eine Kachel ohne Zutun zeigt.
|
* Was eine Kachel ohne Zutun zeigt.
|
||||||
*
|
*
|
||||||
* Ohne eigene Angabe sind es die drei Thermostatwerte, die dort schon immer
|
* Mit Thermostat die drei Werte, die dort schon immer standen: Solltemperatur
|
||||||
* standen: Solltemperatur klein darüber, Isttemperatur gross, Feuchte klein
|
* klein darüber, Isttemperatur gross, Feuchte klein darunter. Ohne Thermostat
|
||||||
* darunter.
|
* nichts - die Kachel trägt dann ihren Namen.
|
||||||
*/
|
*/
|
||||||
function kachelVorgabe($room)
|
function kachelVorgabe($room)
|
||||||
{
|
{
|
||||||
if (isset($room["werte"])) {
|
if (empty($room["thermostat"])) {
|
||||||
return array_slice($room["werte"], 0, 3);
|
return [];
|
||||||
}
|
}
|
||||||
$zweig = "Raumtemp/" . $room["floor"] . "/" . $room["mqtt"] . "/";
|
$zweig = rtrim($room["thermostat"], "/") . "/";
|
||||||
return [
|
return [
|
||||||
["topic" => $zweig . "Set Temp[degC]", "einheit" => "°C", "stellen" => 0],
|
["topic" => $zweig . "Set Temp[degC]", "einheit" => "°C", "stellen" => 0],
|
||||||
["topic" => $zweig . "Temp[degC]", "einheit" => "°C", "gross" => true],
|
["topic" => $zweig . "Temp[degC]", "einheit" => "°C", "gross" => true],
|
||||||
@@ -300,78 +265,19 @@ function kachelVorgabe($room)
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gibt es die Tabelle mit den selbst gewählten Kachelwerten?
|
|
||||||
*
|
|
||||||
* Sie darf fehlen (solarLog_kacheln.sql legt sie an). Dann gilt überall die
|
|
||||||
* Vorgabe - die Startseite bleibt nicht mit einem SQL-Fehler stehen, nur weil
|
|
||||||
* eine Einstellung noch nie benutzt wurde.
|
|
||||||
*/
|
|
||||||
function kachelTabelleDa()
|
|
||||||
{
|
|
||||||
static $da = null;
|
|
||||||
if ($da === null) {
|
|
||||||
/*
|
|
||||||
* Auch der Ausfall der Datenbank fuehrt hier nur zur Vorgabe. Die
|
|
||||||
* Kacheln holen ihre Zahlen vom Broker, nicht aus MySQL - die
|
|
||||||
* Startseite lief bisher ohne Datenbank, und das soll so bleiben.
|
|
||||||
* Ohne dieses try stuende dort im Ernstfall ein Fatal Error statt des
|
|
||||||
* Hauses.
|
|
||||||
*/
|
|
||||||
try {
|
|
||||||
$res = solarDb()->query("SHOW TABLES LIKE 'kachelWerte'");
|
|
||||||
$da = $res && $res->num_rows > 0;
|
|
||||||
} catch (Throwable $e) {
|
|
||||||
$da = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $da;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Die von Hand gesetzten Werte, nach "Etage|Raum" abgelegt.
|
|
||||||
*
|
|
||||||
* Eine Abfrage je Seitenaufbau, nicht eine je Kachel - die Startseite zeichnet
|
|
||||||
* zwanzig davon.
|
|
||||||
*/
|
|
||||||
function kachelUeberschreibungen()
|
|
||||||
{
|
|
||||||
static $werte = null;
|
|
||||||
if ($werte !== null) {
|
|
||||||
return $werte;
|
|
||||||
}
|
|
||||||
$werte = [];
|
|
||||||
if (!kachelTabelleDa()) {
|
|
||||||
return $werte;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
$res = solarDb()->query("SELECT floor, room, werte FROM kachelWerte");
|
|
||||||
while ($res && $row = $res->fetch_assoc()) {
|
|
||||||
$liste = json_decode($row["werte"], true);
|
|
||||||
if (is_array($liste)) {
|
|
||||||
$werte[$row["floor"] . "|" . $row["room"]] = array_slice($liste, 0, 3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Throwable $e) {
|
|
||||||
$werte = []; // siehe kachelTabelleDa(): dann eben die Vorgabe
|
|
||||||
}
|
|
||||||
return $werte;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Die Messwerte einer Kachel, fertig ausgeschrieben.
|
* Die Messwerte einer Kachel, fertig ausgeschrieben.
|
||||||
*
|
*
|
||||||
* Zuerst das, was auf der Einstellungsseite gewählt wurde; sonst die Vorgabe.
|
* Zuerst das, was auf der Einstellungsseite gewählt wurde; sonst die Vorgabe.
|
||||||
*
|
*
|
||||||
* Die Symbole für "heizt" und "Pufferbetrieb" hängen am Raum, nicht an der
|
* Die Symbole für "heizt" und "Pufferbetrieb" hängen am Thermostat, nicht an
|
||||||
* Anzeige: ein Thermostatraum bleibt einer, auch wenn auf seiner Kachel
|
* der Anzeige: ein Thermostatraum bleibt einer, auch wenn auf seiner Kachel
|
||||||
* inzwischen etwas anderes steht. Anderswo gibt es nichts zu heizen.
|
* inzwischen etwas anderes steht. Anderswo gibt es nichts zu heizen.
|
||||||
*/
|
*/
|
||||||
function mitWerten($room)
|
function mitWerten($room)
|
||||||
{
|
{
|
||||||
$room["heizung"] = !isset($room["werte"]);
|
$room["heizung"] = !empty($room["thermostat"]);
|
||||||
$eigen = kachelUeberschreibungen()[$room["floor"] . "|" . $room["id"]] ?? null;
|
$room["werte"] = $room["werte"] ?? kachelVorgabe($room);
|
||||||
$room["werte"] = $eigen !== null ? $eigen : kachelVorgabe($room);
|
|
||||||
return $room;
|
return $room;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -397,6 +303,11 @@ function tileTopics()
|
|||||||
: "solarManager";
|
: "solarManager";
|
||||||
$zweige[$zweig . "/#"] = true;
|
$zweige[$zweig . "/#"] = true;
|
||||||
}
|
}
|
||||||
|
// Das Heizsymbol braucht den Thermostatzweig, auch wenn kein Wert der
|
||||||
|
// Kachel daraus stammt.
|
||||||
|
if (!empty($room["thermostat"])) {
|
||||||
|
$zweige[explode("/", $room["thermostat"])[0] . "/#"] = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return array_keys($zweige);
|
return array_keys($zweige);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
-- Anzeigewerte der Home-Kacheln.
|
|
||||||
--
|
|
||||||
-- Eine Zeile je Kachel, und nur fuer die Kacheln, an denen etwas geaendert
|
|
||||||
-- wurde. Steht keine Zeile da, gilt die Vorgabe aus restricted/rooms.php: bei
|
|
||||||
-- Thermostatraeumen die drei Werte Soll, Ist und Feuchte, sonst die dort
|
|
||||||
-- eingetragene Liste. Die Tabelle ist damit reine Ueberschreibung - sie muss
|
|
||||||
-- weder vollstaendig sein noch mitwachsen, wenn ein Raum hinzukommt.
|
|
||||||
--
|
|
||||||
-- Die Werte stehen als JSON in einer Spalte und nicht in Einzelspalten. Eine
|
|
||||||
-- Wertdefinition ist heterogen: mal ein Topic mit Pfad in einer
|
|
||||||
-- JSON-Nachricht, mal eine Liste von Wechselrichternamen, dazu Einheit,
|
|
||||||
-- Nachkommastellen, Symbol und ob die Zahl gross steht. In Spalten gegossen
|
|
||||||
-- waeren das ein Dutzend Felder, von denen je nach Quelle die Haelfte leer
|
|
||||||
-- bleibt - und jede neue Anzeigeoption waere eine Schemaaenderung. So ist es
|
|
||||||
-- genau die Form, die restricted/rooms.php ohnehin verarbeitet.
|
|
||||||
--
|
|
||||||
-- Geprueft wird beim Speichern in restricted/kacheln.php, nicht hier: die
|
|
||||||
-- Datenbank kann JSON zwar halten, aber nicht wissen, welche Symbole es gibt.
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS `kachelWerte` (
|
|
||||||
`floor` varchar(4) NOT NULL COMMENT 'Etage, wie in rooms.php',
|
|
||||||
`room` varchar(40) NOT NULL COMMENT 'Kuerzel des Raums (rooms.id)',
|
|
||||||
`werte` text NOT NULL COMMENT 'JSON-Liste der Wertdefinitionen, hoechstens drei',
|
|
||||||
`aktualisiert` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
|
|
||||||
PRIMARY KEY (`floor`, `room`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
||||||
@@ -121,7 +121,7 @@ const PLAN_HOCH = 300;
|
|||||||
<path d="M10 0H0V10" fill="none" stroke="#ffffff22" stroke-width=".4"/>
|
<path d="M10 0H0V10" fill="none" stroke="#ffffff22" stroke-width=".4"/>
|
||||||
</pattern>
|
</pattern>
|
||||||
</defs>
|
</defs>
|
||||||
<image href="../assets/img/<?= $floor ?>.png" x="0" y="0"
|
<image href="../<?= htmlspecialchars((string)etageBild($floor)) ?>" x="0" y="0"
|
||||||
width="<?= PLAN_BREIT ?>" height="<?= PLAN_HOCH ?>"/>
|
width="<?= PLAN_BREIT ?>" height="<?= PLAN_HOCH ?>"/>
|
||||||
<rect x="0" y="0" width="<?= PLAN_BREIT ?>" height="<?= PLAN_HOCH ?>"
|
<rect x="0" y="0" width="<?= PLAN_BREIT ?>" height="<?= PLAN_HOCH ?>"
|
||||||
fill="url(#raster<?= $floor ?>)"/>
|
fill="url(#raster<?= $floor ?>)"/>
|
||||||
|
|||||||
Reference in New Issue
Block a user