# Smart-Dashboard
Die Anzeige und Bedienung des Hauses: Grundrisse mit Messwerten, Solar- und
Verbrauchsverläufe, das Fahrzeug, die Automatiken, die Einstellungen. Alles,
was einen offenen Browser braucht, liegt hier.
Gegenstück ist das Repo **`admin/SolarManager`** unter
`/volume1/homes/wagner/SolarManager` — dort laufen die Hintergrundprozesse,
die Messwerte einsammeln und die Automatiken ausführen. Beide Seiten reden
über zwei Dinge miteinander und nie direkt: den **MQTT-Broker** für alles
Aktuelle, die **Datenbanken** für alles, was bleiben soll. Wer wissen will,
woher eine Zahl ursprünglich kommt, liest die README dort; hier steht, was
mit ihr im Browser passiert.
Dieses Verzeichnis ist zugleich der Web-Root der NAS
(`/volume1/web/smart`, ausgeliefert unter `https://nas.el-wa.org/smart`).
Der Arbeitsordner *ist* die laufende Anwendung — jede gespeicherte Datei ist
sofort live.
Für zwei Teile reicht diese Landkarte nicht, weil ihr Verhalten aus dem
Zusammenspiel von Editor, Datenbank und Runner entsteht. Sie haben eigene,
ausführliche Dokumente unter [`doku/`](doku/README.md):
**[Automatiken](doku/automatiken.md)** und
**[Zeitleiste](doku/zeitleiste.md)**.
---
## Inhalt
1. [Auf einen Blick](#auf-einen-blick)
2. [Wie eine Seite entsteht](#wie-eine-seite-entsteht)
3. [Zwei Wege für Daten](#zwei-wege-für-daten)
4. [Die Seitenkarte](#die-seitenkarte)
5. [Die JavaScript-Schicht](#die-javascript-schicht)
6. [Die PHP-Schicht](#die-php-schicht)
7. [Die Datenbanken](#die-datenbanken)
8. [Vier Bausteine im Detail](#vier-bausteine-im-detail)
9. [Wiederkehrende Bauteile und Konventionen](#wiederkehrende-bauteile-und-konventionen)
10. [Wo fange ich an, wenn ich … ändern will](#wo-fange-ich-an-wenn-ich--ändern-will)
11. [Entwickeln und Prüfen](#entwickeln-und-prüfen)
---
## Auf einen Blick
```
smart/
├── index.php Router: Anmeldung prüfen, Seite auswählen, drei Includes
├── helper.php Session, Heimnetzerkennung, checkLogin(), httpGetJson()
├── authServer.php Passkeys (WebAuthn): Registrierung und Anmeldung
├── addUser.php Einmal-Link zum Anlegen eines neuen Schlüssels
├── .htaccess sperrt .git, *.py, *.sql, *.ini … für den Browser
│
├── restricted/ Vorlagen (was man sieht) und Modelle (was es weiß)
│ ├── 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
│ ├── einfuehrung.php ← die neun Seitenvorlagen
│ ├── rooms.php Etagen und Räume lesen (homeMesh), Kachelvorgabe, Symbole
│ ├── grundriss.php Etagen, Räume, Kachelpositionen, Bilder ändern (Reiter Grundriss)
│ ├── zeitleiste.php Automatiken eines Tages: Lage, Bahn, ob dran, Läufe
│ ├── meshdb.php meshDb(): Verbindung zu homeMesh
│ ├── costs.php Preistabellen + solarDb()
│ ├── 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
│ ├── wallboxen.php beide Wallboxen über MQTT
│ ├── skodaKeys.php MyŠkoda-Schlüssel in skoda.conf (nie zurück an den Browser)
│ ├── logdateien.php Logdateien lesen
│ ├── aussenplan.php Schrägbild des Außengeländes (SVG, gerechnet)
│ ├── reiter.php das eine Reiter-Bauteil der ganzen Seite
│ ├── config.php Zahlen, die an der Wirklichkeit hängen
│ ├── mysql.php Zugangsdaten — NICHT im Git (mysql.php.example)
│ └── deviceDiscovery/ Python: Geräte suchen und in homeMesh eintragen
│
├── ajax/ Endpunkte. Verteiler, keine Logik (Ausnahmen unten)
├── js/solar/ der eigene JavaScript-Code
├── js/ Fremdbibliotheken, alle lokal (kein CDN)
├── css/solar.css das eigene Stylesheet (Theme + alle Bauteile)
├── assets/img/ mitgelieferte Grundriss-Renderings dieses Hauses, Icons
├── tiles/ beschreibbar, nicht im Git: Kartenkacheln, grundriss/ (hochgeladene Grundrisse)
└── *.sql Schema-Dateien zum Nachziehen von Hand
```
---
## Wie eine Seite entsteht
```mermaid
sequenceDiagram
autonumber
participant B as Browser
participant I as index.php
participant H as helper.php
participant T as Vorlage + Modelle
participant F as footer.php
B->>I: GET index.php?action=home&floor=OG
I->>H: checkLogin()
H-->>I: Heimnetz? sonst Passkey-Sitzung
Note over I: $pages[...] wählt Vorlage,
Bibliotheken und Seitenskripte
I->>T: include header.php
T-->>B: Kopf und Menü, gebaut aus $floors
I->>T: include restricted/(seite).php
T-->>B: Karten, SVG, eingebettete JSON-Daten
I->>F: include footer.php
F-->>B: Bootstrap, ggf. mqtt.js/Chart.js/Leaflet,
common.js, dann die Seitenskripte
B->>B: Skripte verbinden sich mit Broker und holen per fetch nach
```
**Anmeldung.** `helper.php` kennt zwei Wege. Aus dem Heimnetz
(`LOCAL_NETWORKS`, geprüft binär über `inet_pton`, ausschließlich anhand von
`REMOTE_ADDR` — Weiterleitungs-Header sind fälschbar und werden bewusst
ignoriert) ist man ohne Anmeldung drin. Von außen zählt ein Passkey:
`authServer.php` mit der WebAuthn-Bibliothek unter `restricted/WebAuthn`,
Schlüssel in der Datenbank `Logins`. Ein neuer Schlüssel entsteht nur über
einen Einmal-Link (`addUser.php`, Tabelle `addUser`, eine Minute gültig).
**Die Seitentabelle** in `index.php` ist die einzige Stelle, an der steht,
welche Seite es gibt und was sie braucht:
```php
"home" => ["template" => "home.php", "mqtt" => true, "charts" => false,
"meteogram" => false, "leaflet" => false,
"scripts" => ["js/solar/autoActionFuncs.js", "js/solar/homeMQTT.js"]],
```
`footer.php` lädt daraus die Bibliotheken — Chart.js nur wo gezeichnet wird,
mqtt.js nur wo Livewerte laufen, Leaflet nur auf der Fahrzeugseite. Danach
kommt immer `common.js` und dann die Seitenskripte, jeweils mit
`?v=`, damit ein Browser nie eine alte Fassung behält.
**Vorlage und Modell sind getrennt.** Eine Seitenvorlage baut Markup und ruft
dafür Funktionen aus den Modelldateien; die Modelldateien enthalten kein
Markup außer dort, wo sie ein Bedienelement zeichnen (`roomControls.php`,
`reiter.php`). Die Endpunkte in `ajax/` benutzen dieselben Modellfunktionen —
so kann eine Maske nicht etwas anderes behaupten als der Endpunkt, der sie
speichert.
---
## Zwei Wege für Daten
```mermaid
flowchart LR
subgraph Browser
JS["js/solar/*.js"]
SVG["SVG-Grundriss
Diagramme
Masken"]
end
BROKER{{"MQTT-Broker
wss://mqtt.nas.el-wa.org:443"}}
AJAX["ajax/*.php"]
MODELL["restricted/*.php
Modell-Schicht"]
SOLARLOG[("solarLog
Verlauf")]
HOMEMESH[("homeMesh
Geräte, Automatiken")]
GERAETE["Geräte
Tahoma · WLED · Shelly · Wallboxen"]
BROKER -->|"laufende Werte"| JS
JS --> SVG
JS <-->|"fetch: Verlauf, Struktur, Masken"| AJAX
AJAX --> MODELL
MODELL --> SOLARLOG
MODELL --> HOMEMESH
MODELL -.->|"Kommandos"| BROKER
MODELL -.->|"HTTP · Tahoma · WLED"| GERAETE
classDef speicher fill:#eaf5ee,stroke:#6fa981
classDef code fill:#fff6e5,stroke:#d0a548
class SOLARLOG,HOMEMESH,BROKER speicher
class JS,AJAX,MODELL code
```
Die Regel dahinter, und sie gilt ohne Ausnahme:
| Was | Weg | Warum |
|---|---|---|
| Aktueller Messwert (Leistung, Temperatur, Ladestand) | **MQTT über `wss`, direkt vom Broker** | Er weiß es ohnehin, und er sagt es von selbst — kein Nachfragen im Takt |
| Verlauf, Statistik, Gerätelisten, Formulare | **`fetch` auf `ajax/*.php`** | Das weiß nur die Datenbank |
| Jeder Knopfdruck (schalten, speichern, Fahrzeugbefehl) | **immer über den Server** | Zugangsdaten dürfen die Seite nicht verlassen, und der Broker ist von außen nicht erreichbar |
Der Browser abonniert je nach Seite `solarManager/#`, `weatherStation/#`,
`wattpilot/#`, `go-eCharger/#`, `Gartenwasser/#` und — auf der Home-Seite —
genau die Topics, die auf den Kacheln stehen (`tileTopics()` in `rooms.php`).
---
## Die Seitenkarte
| Seite (`?action=`) | Vorlage | Seitenskripte | holt per fetch | hört auf MQTT |
|---|---|---|---|---|
| `solar` (Vorgabe) | `solar.php` | `jahresstatistik.js`, `anlageKatalog.js`, `anlage.js`, `speicher.js`, `energieflussKatalog.js`, `energiefluss.js`, `bewaesserungAnzeige.js`, `ladefenster.js`, `bedienfenster.js`, `solarMQTT.js` | `speicher`, `getProdData`, `getConsData`, `getForecastData`, `getSunrise`, `getStats`, `carEG`, `carOG`, `heater`, `watering` | `solarManager/#`, `weatherStation/#`, `wattpilot/#`, `go-eCharger/#`, `Gartenwasser/#` |
| `home` | `home.php` | `autoActionFuncs.js`, `zeitleiste.js`, `raumfenster.js`, `homeMQTT.js` | `room.php`, `AutoAction.php` | die Topics der Kacheln, `Raumtemp/#` |
| `heat` | `heat.php` | `heatMQTT.js` | `getHeaterData`, `getWaterData`, `getSunrise` | `solarManager/#`, `weatherStation/#`, Wallbox-Topics |
| `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 `