Einstellungen -> Grundriss: Etagen, Raeume und Kacheln am Plan pflegen

Neuer Reiter mit Plan-Editor: Etagen anlegen, umbenennen, sortieren,
loeschen und als Standard markieren; Grundrissbild hochladen; Raeume mit
Name, Kuerzel und Thermostat-Zweig anlegen und bearbeiten; Kacheln im Plan
ziehen oder einen Raum ohne Kachel mit "Platzieren" und einem Klick setzen.
Loest tools/kachelpositionen.php ab - Raster, Geisterkachel und die
Umrechnung Mitte -> linke obere Ecke sind von dort uebernommen.

- restricted/grundriss.php: alle Pruefungen und Schreibzugriffe. Eine
  Etage mit Raeumen oder Automatiken laesst sich nicht loeschen, die
  Ablehnung nennt die Namen. Raeume loeschen laesst ihre Geraete ohne Raum.
- Grundrissbilder nach tiles/grundriss/ (beschreibbar, nicht im Git),
  nur PNG/JPEG/WebP, am Inhalt geprueft; SVG bewusst nicht. Prüfsumme im
  Dateinamen, alte hochgeladene Bilder werden weggeraeumt.
- ajax/settings.php: ?action=grundriss und sieben POST-Aktionen, jede
  antwortet mit dem ganzen neuen Stand.
- "Werte" springt in den Reiter Home-Kacheln und oeffnet die Kachel;
  Home-Kacheln und Geraete laden nach einer Grundriss-Aenderung nach.
- Startseite ohne Etage mit platziertem Raum: Hinweis auf die
  Einstellungen statt einer leeren Zeichenflaeche.
- README: Grundriss als Inhalt statt Code, neue Dateien und Endpunkte,
  Reihenfolge der Schema-Dateien fuer ein neues Haus.

Backend mit einer Probe-Etage durchgespielt (auch Fehlerfaelle, Upload,
Aufraeumen); Oberflaeche auf einer Probeseite mit echten Daten und
Zeiger-Ereignissen geprueft: Ziehen, Antippen, Platzieren, neuer Raum.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-15 11:11:10 +02:00
co-authored by Claude Opus 5
parent 62a8af3fc6
commit 8d626abc6d
12 changed files with 1365 additions and 255 deletions
+77 -30
View File
@@ -49,9 +49,11 @@ smart/
│ ├── header.php footer.php Rahmen jeder Seite, Menü, Skript-Einbindung
│ ├── home.php solar.php heat.php history.php
│ ├── skoda.php weather.php settings.php logs.php ← die acht Seitenvorlagen
│ ├── rooms.php Räume, Etagen, Kachelwerte, Symbole
│ ├── rooms.php Etagen und Räume lesen (homeMesh), Kachelvorgabe, Symbole
│ ├── grundriss.php Etagen, Räume, Kachelpositionen, Bilder ändern (Reiter Grundriss)
│ ├── meshdb.php meshDb(): Verbindung zu homeMesh
│ ├── costs.php Preistabellen + solarDb()
│ ├── automations.php Automatiken, Gerätekatalog + meshDb()
│ ├── automations.php Automatiken, Gerätekatalog
│ ├── kacheln.php Anzeigewerte der Home-Kacheln (Modell zur Maske)
│ ├── roomControls.php Bedienelemente des Raum-Modals
│ ├── commands.php ein Kommando tatsächlich abschicken
@@ -68,8 +70,8 @@ smart/
├── js/solar/ der eigene JavaScript-Code
├── js/ Fremdbibliotheken, alle lokal (kein CDN)
├── css/solar.css das eigene Stylesheet (Theme + alle Bauteile)
├── assets/img/ Grundriss-Renderings OG/EG/UG/AG, Icons
├── tiles/ Kartenkacheln-Zwischenspeicher (nicht im Git)
├── assets/img/ mitgelieferte Grundriss-Renderings dieses Hauses, Icons
├── tiles/ beschreibbar, nicht im Git: Kartenkacheln, grundriss/ (hochgeladene Grundrisse)
└── *.sql Schema-Dateien zum Nachziehen von Hand
```
@@ -185,7 +187,7 @@ genau die Topics, die auf den Kacheln stehen (`tileTopics()` in `rooms.php`).
| `history` | `history.php` | `jahresstatistik.js`, `historyMQTT.js` | `energyHistory` (6 ×), `getStats` | — |
| `skoda` | `skoda.php` | `skodaMQTT.js` | `skoda.php?was=…`, `skodaCmd.php`, `tile.php` | `solarManager/#` |
| `weather` | `weather.php` | `weatherMQTT.js` | nichts — das Meteogramm ist ein fremdes Dokument im `<iframe>` (`js/meteogram.js`) | `weatherStation/#` |
| `settings` | `settings.php` | `settings.js` | `settings.php?action=…` | — |
| `settings` | `settings.php` | `settings.js`, `grundriss.js` | `settings.php?action=…` | — |
| `logs` | `logs.php` | `logs.js` | `logs.php?action=…` | — |
Und dasselbe als Abhängigkeitsbild — von der Seite bis in den Speicher:
@@ -241,8 +243,9 @@ flowchart TB
AUTOA --> AUTOM
SETA --> COSTS & KACH & AUTOM
STATS --> COSTS
SETA --> GRM["grundriss.php"]
KACH --> ROOMS
ROOMS --> COSTS
GRM --> ROOMS
LOGA --> LOGD
COSTS --> SOLARLOG
@@ -291,7 +294,8 @@ Text wird: Topic oder Wechselrichtersumme lesen, ggf. JSON-Pfad
herausgreifen, Vorzeichen drehen (`negativ`), skalieren (`format:
"leistung"` für Watt-Quellen, `"leistung_kw"` für Quellen, die schon
Kilowatt melden — die Wallboxen), runden (`stellen`), Einheit anhängen. Klick auf eine Kachel
öffnet das Raum-Modal über `ajax/room.php?room=EG_Bad`.
öffnet das Raum-Modal über `ajax/room.php?room=<Raumnummer>`; Heizsymbol,
Regler und Ist-Werte lesen aus dem Thermostat-Zweig des Raums (`mqttZweig()`).
**`solarMQTT.js`** — die große Anlagenübersicht. `solarSVG` zeichnet den
Energiefluss (Breite der Pfeile aus der Leistung), darunter hängen die drei
@@ -320,6 +324,10 @@ Anfragen dazu, und die Auswahlfelder füllten sich asynchron. Dazu die
Übersicht: Nachfolger einklappen (`toggleAutomationGroup`) und die Rückfrage
vor dem Löschen (`frageNachfolger`).
**`grundriss.js`** — der Reiter Grundriss: Etagen, Räume, Grundrissbilder und
der Plan-Editor, in dem Kacheln gezogen und gesetzt werden. Eigene Datei, weil
`settings.js` schon für vier Reiter reicht; es nutzt `textSchuetzen()` von dort.
**`settings.js`** — vier Reiter in einer Datei, deutlich getrennt: Preistabellen,
Home-Kacheln, Geräte, Fahrzeug. Jeder Abschnitt hat sein Modell (`kachelModell`,
`geraeteModell`, `skodaModell`), seine `zeichne…()`-Funktion und eine `…Binden()`-Funktion,
@@ -337,9 +345,9 @@ klein: Diagramme füllen bzw. Logzeilen nachladen und einfärben.
| Endpunkt | Aufruf | liefert / tut |
|---|---|---|
| `room.php` | `?room=EG_Bad`, `POST ?action=command\|temp` | Raum-Modal bauen; ein Kommando oder eine Solltemperatur senden |
| `room.php` | `?room=<rooms.id>`, `POST ?action=command\|temp\|jalousie` | Raum-Modal bauen; ein Kommando oder eine Solltemperatur senden |
| `AutoAction.php` | `?action=editor\|list\|followers\|werte`, `POST save\|delete\|toggle` | Automatik-Editor und -Übersicht |
| `settings.php` | `?action=list\|kacheln\|geraete\|skoda-keys`, `POST save\|kacheln-save\|geraete-raeume\|geraet-loeschen\|skoda-keys-save` | Einstellungsseite; `skoda-keys` meldet nur, ob ein Schlüssel hinterlegt ist und worauf er endet |
| `settings.php` | `?action=list\|kacheln\|geraete\|grundriss\|skoda-keys`, `POST save\|kacheln-save\|geraete-raeume\|geraet-loeschen\|etage-speichern\|etagen-reihenfolge\|etage-loeschen\|etage-bild\|raum-speichern\|raum-position\|raum-loeschen\|skoda-keys-save` | Einstellungsseite; die Grundriss-Aktionen antworten mit dem ganzen neuen Stand, `skoda-keys` meldet nur, ob ein Schlüssel hinterlegt ist und worauf er endet |
| `getStats.php` | `GET` | Jahresstatistik, alle Jahre, mit Metadaten je Kennzahl |
| `energyHistory.php` | `?series=prod\|cons&range=month\|year\|decade` | Verbrauchs-/Erzeugungsverlauf (Rohdaten oder Stundenarchiv) |
| `getProdData` `getConsData` `getForecastData` `getHeaterData` `getWaterData` | `?FROM=&TO=` | Chart.js-Datensätze für die jeweilige Karte |
@@ -362,9 +370,11 @@ Kennzahlen, `energyHistory.php` wählt die Quelle nach Zeitraum,
| Datei | Zuständig für | Wichtige Funktionen |
|---|---|---|
| `rooms.php` | Räume, Etagen, Kachelwerte, Symbole | `allRooms()`, `roomsWithTile()`, `tileTopics()`, `kachelVorgabe()`, `kachelUeberschreibungen()`, `bootstrapIcons()`, `kachelIcon()` |
| `rooms.php` | Etagen und Räume lesen (`homeMesh`), Kachelvorgabe, Symbole | `grundriss()`, `allRooms()`, `roomsOnFloor()`, `floorsWithPlan()`, `standardEtage()`, `etageBild()`, `roomsWithTile()`, `tileTopics()`, `kachelVorgabe()`, `bootstrapIcons()`, `kachelIcon()` |
| `grundriss.php` | Etagen, Räume, Kachelpositionen und Grundrissbilder ändern | `grundrissStand()`, `etageSpeichern()`, `etagenReihenfolge()`, `etageLoeschen()`, `etageBildSpeichern()`, `raumSpeichern()`, `raumPosition()`, `raumLoeschen()` |
| `meshdb.php` | Verbindung zu `homeMesh` | `meshDb()` |
| `costs.php` | Preistabellen, Verbindung zu `solarLog` | `solarDb()`, `preiseLaden()`, `preiseSpeichern()`, `grundpreisImZeitraum()` |
| `automations.php` | Automatiken, Gerätekatalog, Verbindung zu `homeMesh` | `meshDb()`, `deviceCatalog()`, `loadAutomation()`, `saveAutomation()`, `deleteAutomation()`, `automationFollowers()`, `pruefeKreis()`, `kalendertagChoices()`, `tagVorlagen()`, `stateValues()`, `geraeteListe()`, `geraetLoeschen()`, `saveRooms()` |
| `automations.php` | Automatiken, Gerätekatalog, Raumzuordnung der Geräte | `deviceCatalog()`, `raumListe()`, `loadAutomation()`, `saveAutomation()`, `deleteAutomation()`, `automationFollowers()`, `pruefeKreis()`, `kalendertagChoices()`, `tagVorlagen()`, `stateValues()`, `geraeteListe()`, `geraetLoeschen()`, `saveRooms()` |
| `kacheln.php` | Anzeigewerte der Home-Kacheln (Maskenseite) | `kachelListe()`, `kachelKatalog()`, `kachelWertPruefen()`, `kachelWerteSpeichern()`, `kachelSymbole()` |
| `roomControls.php` | Welches Gerät welches Bedienelement bekommt | `bedienform()`, `zeichneBeschattung()`, `zeichneLicht()`, `zeichneMesswerte()` |
| `commands.php` | Ein Kommando abschicken (MQTT/WLED/HTTP/Tahoma) | `executeCommand()`, `sendeMqtt()`, `sendeWled()`, `sendeHttp()`, `sendeTahoma()` |
@@ -382,11 +392,13 @@ Datenbank folgen kann, ohne zu springen:
```mermaid
flowchart LR
HELPER["helper.php"] --> MYSQL["mysql.php<br/><small>Zugangsdaten</small>"]
ROOMS["rooms.php"] --> COSTS["costs.php"] --> MYSQL
COSTS["costs.php"] --> MYSQL
MESH["meshdb.php<br/><small>meshDb()</small>"] --> MYSQL
ROOMS["rooms.php"] --> MESH
GRM["grundriss.php"] --> ROOMS
KACH["kacheln.php"] --> ROOMS
KACH --> COSTS
KACH -. "nur wenn vorhanden" .-> AUTOM["automations.php"]
AUTOM --> MYSQL
AUTOM --> MESH
AUTOM --> ROOMS
CMD["commands.php"] --> MYSQL
CMD --> TAH["tahoma_EG.php"]
@@ -441,7 +453,6 @@ erDiagram
| `skoda_ladepunkte` | Wallbox-Verlauf während einer Ladung, im Minutentakt, wird nicht ausgedünnt (`solarLog_skoda_ladepunkte.sql`) | `gatherSkodaData.py`; ältere Ladungen `skoda_ladepunkte_nachtragen.py` |
| `weatherStation`, `weatherHours`, `weatherDays`, `daylight`, `simPower` | Wetter, Sonnenzeiten, Ertragsprognose | Wetterbrücke, Open-Meteo, Prognose |
| `gridCosts`, `gasCosts`, `fuelCosts` | Preiszeitreihen (Stichtag, kein Enddatum) | Einstellungsseite (`costs.php`) |
| `kachelWerte` | Überschreibungen der Home-Kacheln, JSON je Raum | Einstellungsseite (`kacheln.php`) |
**`Logins`** — `users` (Passkeys) und `addUser` (Einmal-Links).
@@ -449,15 +460,50 @@ erDiagram
## Vier Bausteine im Detail
### 1. Die Home-Kacheln
### 1. Grundriss und Home-Kacheln
Was auf einer Kachel steht, hat drei Stufen: die Vorgabe in `rooms.php`, die
Überschreibung in `kachelWerte`, und die Anzeige im Browser.
Etagen, Räume, Kachelpositionen und Kachelwerte sind **Inhalt, kein Code**.
Sie liegen in `homeMesh.floors` und `homeMesh.rooms` (`homeMesh_grundriss.sql`)
und werden unter **Einstellungen → Grundriss** und **→ Home-Kacheln** gepflegt.
Bis September 2026 standen sie als Tabelle in `rooms.php`; für ein anderes
Haus hätte man den Quelltext umschreiben müssen.
| Tabelle | trägt |
|---|---|
| `floors` | `code` (fest: URL, SVG-Ids, `automations.floor`), `label`, Reihenfolge, Grundrissbild, Standard-Etage |
| `rooms` | feste Nummer `id`, Etage, Name, `kuerzel` (SVG-Ids), Kachel `x`/`y` (leer = keine Kachel), `thermostat` (MQTT-Zweig), `werte` (JSON, leer = Vorgabe) |
Auf die feste Raumnummer zeigen `actors.room_id` und das Raum-Modal
(`room.php?room=12`). Ein umbenannter Raum behält damit Geräte, Regler und
Kachel. `automations.floor` ist ein Fremdschlüssel auf `floors`: eine Etage mit
Automatiken lässt sich nicht löschen.
**Die Vorgabe einer Kachel** ist allein das Thermostat: mit `thermostat` Soll,
Ist und Feuchte aus diesem Zweig, sonst nur der Name. Derselbe Zweig trägt
Heizsymbol und Temperaturregler (`mqttZweig()` in `homeMQTT.js`,
`<zweig>/changeSetTemp` in `room.php`) — nichts setzt mehr voraus, dass ein
Thermostat unter `Raumtemp/<Etage>/<Raum>` meldet.
**Grundrissbilder** kommen per Upload nach `tiles/grundriss/` — das einzige
Verzeichnis, in das der Webserver schreiben darf, und nicht im Git. PNG, JPEG
oder WebP, geprüft am Inhalt; SVG bewusst nicht, es könnte Skript enthalten.
Der Name trägt einen Teil der Prüfsumme, ein neues Bild hat also eine neue
Adresse. Die mitgelieferten unter `assets/img` bleiben daneben gültig.
**Der Plan-Editor** (`js/solar/grundriss.js`, Modell `restricted/grundriss.php`)
löst `tools/kachelpositionen.php` ab: Kacheln werden gezogen, ein Raum ohne
Kachel mit „Platzieren“ und einem Klick gesetzt. Übernommen sind Raster,
Geisterkachel und die Umrechnung von der gezeigten Mitte zur gespeicherten
linken oberen Ecke. Jede Aktion antwortet mit dem ganzen neuen Stand. Nach
einer Änderung laden „Home-Kacheln“ und „Geräte“ beim nächsten Öffnen nach.
Ohne Etage mit platziertem Raum zeigt die Startseite statt der Zeichenfläche
den Weg zu den Einstellungen — der Anfang jeder neuen Installation.
```mermaid
flowchart LR
ROOMS["rooms.php<br/><small>$rooms: Vorgabe je Raum</small>"] --> MIT["mitWerten()"]
KW[("kachelWerte<br/><small>je Raum, JSON</small>")] --> MIT
DB[("homeMesh.floors<br/>homeMesh.rooms")] --> GR["grundriss()<br/><small>rooms.php</small>"]
GR --> MIT["mitWerten()"]
MIT --> RWT["roomsWithTile()"]
RWT -->|"als JSON in die Seite"| HOMEJS["homeMQTT.js<br/><small>homeRooms</small>"]
RWT --> SVG["home.php<br/><small>SVG-Kacheln</small>"]
@@ -465,8 +511,10 @@ flowchart LR
BROKER --> HOMEJS
HOMEJS -->|"kachelWert()"| SVG
SETJS["settings.js<br/><small>Reiter Kacheln</small>"] --> SETA["ajax/settings.php"]
SETA --> KACHM["kacheln.php<br/><small>prüfen, speichern</small>"] --> KW
GRJS["grundriss.js<br/><small>Reiter Grundriss</small>"] --> SETA["ajax/settings.php"]
SETJS["settings.js<br/><small>Reiter Kacheln</small>"] --> SETA
SETA --> GRM["grundriss.php<br/><small>Etagen, Räume, Bilder</small>"] --> DB
SETA --> KACHM["kacheln.php<br/><small>Werte prüfen, speichern</small>"] --> DB
KACHM -->|"Auswahlliste"| KAT["deviceCatalog()<br/><small>automations.php</small>"]
```
@@ -486,8 +534,6 @@ der Name **irgendeines** Bootstrap-Icons — die Codepunkte liest
`bootstrapIcons()` aus `css/bootstrap-icons.min.css`, gezeichnet wird als
SVG-Text in der Icon-Schrift.
Fehlt die Tabelle `kachelWerte`, zeigt jede Kachel ihre Vorgabe: die
Startseite ist keine Einstellung, ohne die man das Haus nicht mehr sieht.
### 2. Das Raum-Modal
@@ -499,8 +545,8 @@ sequenceDiagram
participant C as commands.php
participant G as Gerät
B->>R: GET ?room=EG_Bad
R->>RC: Geräte des Raums (actors.room) → Bedienformen
B->>R: GET ?room=12 (rooms.id)
R->>RC: Geräte des Raums (actors.room_id) → Bedienformen
RC-->>B: fertiges Markup (Rollladen, Licht, Messwerte …)
B->>R: POST ?action=command {command_id, params}
R->>C: executeCommand()
@@ -733,8 +779,8 @@ englisch bleibt, was von außen vorgegeben ist (`floor`, `topic`, Chart.js-
Optionen, Spaltennamen). Kommentare erklären **warum**, nicht was.
**Fehlertoleranz vor Vollständigkeit.** Was eine Seite nicht zwingend braucht,
wird in `try/catch` geholt: fehlt die Mesh-Datenbank, bleibt die Startseite
sichtbar; fehlt `kachelWerte`, gilt die Vorgabe.
wird in `try/catch` geholt: fehlt die Mesh-Datenbank oder der Grundriss, zeigt
die Startseite einen Hinweis statt eines Fatal Errors.
**Zugriffsschutz.** `.htaccess` sperrt Punkt-Dateien (allen voran `.git`) und
alle Dateitypen, die der Browser nie anfordert (`.py`, `.sql`, `.ini`, `.md`
@@ -750,8 +796,9 @@ ausgeliefert.
| Vorhaben | Anlaufstelle |
|---|---|
| Ein Raum kommt dazu oder heißt anders | `restricted/rooms.php`, Tabelle `$rooms` — Kachel, Menü und MQTT-Abo ergeben sich daraus |
| Andere Werte auf einer Kachel | Einstellungen → Kacheln (keine Codeänderung). Vorgabe für neue Räume: `kachelVorgabe()` in `rooms.php` |
| Ein Raum oder eine Etage kommt dazu, heißt anders, bekommt ein neues Grundrissbild | Einstellungen → Grundriss (keine Codeänderung) — Kachel, Menü und MQTT-Abo ergeben sich daraus |
| Andere Werte auf einer Kachel | Einstellungen → Home-Kacheln (keine Codeänderung). Vorgabe ohne eigene Werte: `kachelVorgabe()` in `rooms.php` |
| Ein anderes Haus einrichten | `homeMesh_DB-layout.sql`, `homeMesh_automations.sql`, `homeMesh_grundriss.sql` in dieser Reihenfolge; dann Einstellungen → Grundriss |
| Neues Symbol auf einer Kachel | Name eines Bootstrap-Icons ins Feld tippen. Vorschlagsliste: `kachelSymbole()` in `kacheln.php` |
| Neue Kennzahl in der Jahresstatistik | `KENNZAHLEN` **und** `kennzahlenAus()` in `ajax/getStats.php` — sonst nichts |
| Neues Diagramm auf einer Seite | Karte in der Vorlage, `<canvas>`, dazu ein Endpunkt nach dem Muster von `getProdData.php`, geladen mit `getData()` |