# 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. --- ## 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 ← die acht 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`, `solarMQTT.js` | `getProdData`, `getConsData`, `getForecastData`, `getSunrise`, `getStats`, `carEG`, `carOG`, `heater`, `watering` | `solarManager/#`, `weatherStation/#`, `wattpilot/#`, `go-eCharger/#`, `Gartenwasser/#` | | `home` | `home.php` | `autoActionFuncs.js`, `zeitleiste.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 `