README: Aufbau und Datenwege des Dashboards
Das Repo hatte keine. Wer hier etwas suchte, musste sich von index.php aus durchhangeln - und die Frage "welches Skript laedt eigentlich welche Daten" war nur zu beantworten, indem man alle elf durchsah. Die Datei beschreibt den Weg einer Anfrage (Router, Anmeldung, Vorlage, Skripte), die Trennung der beiden Datenwege (laufende Werte ueber den Broker, alles mit Verlauf ueber ajax/), die Seitenkarte mit Skripten, Endpunkten und Topics je Seite, die JavaScript- und PHP-Schicht mit ihren Zustaendigkeiten, die drei Datenbanken und vier Bausteine im Detail: Home-Kacheln, Raum-Modal, Automatiken, Geraete-Erkennung. Dazu sieben Mermaid-Diagramme - Ablauf, Datenwege, Abhaengigkeiten, Include-Graph, Tabellen, Kachelaufloesung, Kommandoweg. Am Ende zwei Tabellen fuer den taeglichen Gebrauch: "wo fange ich an, wenn ich X aendern will" und die Pruefbefehle (php82 -l, node --check, readDB). Gegenstueck ist die README im SolarManager-Repo; beide verweisen aufeinander, damit man von jeder Seite aus zur anderen findet. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,641 @@
|
|||||||
|
# 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 Räume, Etagen, Kachelwerte, Symbole
|
||||||
|
│ ├── costs.php Preistabellen + solarDb()
|
||||||
|
│ ├── automations.php Automatiken, Gerätekatalog + meshDb()
|
||||||
|
│ ├── 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
|
||||||
|
│ ├── 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/ Grundriss-Renderings OG/EG/UG/AG, Icons
|
||||||
|
├── tiles/ Kartenkacheln-Zwischenspeicher (nicht im Git)
|
||||||
|
└── *.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,<br/>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,<br/>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=<filemtime>`, 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<br/>Diagramme<br/>Masken"]
|
||||||
|
end
|
||||||
|
|
||||||
|
BROKER{{"MQTT-Broker<br/><small>wss://mqtt.nas.el-wa.org:443</small>"}}
|
||||||
|
AJAX["ajax/*.php"]
|
||||||
|
MODELL["restricted/*.php<br/><small>Modell-Schicht</small>"]
|
||||||
|
SOLARLOG[("solarLog<br/><small>Verlauf</small>")]
|
||||||
|
HOMEMESH[("homeMesh<br/><small>Geräte, Automatiken</small>")]
|
||||||
|
GERAETE["Geräte<br/><small>Tahoma · WLED · Shelly · Wallboxen</small>"]
|
||||||
|
|
||||||
|
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`, `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 `<iframe>` (`js/meteogram.js`) | `weatherStation/#` |
|
||||||
|
| `settings` | `settings.php` | `settings.js` | `settings.php?action=…` | — |
|
||||||
|
| `logs` | `logs.php` | `logs.js` | `logs.php?action=…` | — |
|
||||||
|
|
||||||
|
Und dasselbe als Abhängigkeitsbild — von der Seite bis in den Speicher:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TB
|
||||||
|
subgraph S["Seiten"]
|
||||||
|
HOME["home"]; SOLAR["solar"]; HIST["history"]; HEAT["heat"]
|
||||||
|
SKODA["skoda"]; SET["settings"]; LOGS["logs"]; WEA["weather"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph J["js/solar"]
|
||||||
|
COMMON["common.js<br/><small>immer geladen</small>"]
|
||||||
|
HOMEJS["homeMQTT.js"]; AUTOJS["autoActionFuncs.js"]; SOLJS["solarMQTT.js"]
|
||||||
|
HISTJS["historyMQTT.js"]; STATJS["jahresstatistik.js"]; HEATJS["heatMQTT.js"]
|
||||||
|
SKODAJS["skodaMQTT.js"]; SETJS["settings.js"]; LOGJS["logs.js"]; WEAJS["weatherMQTT.js"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph A["ajax"]
|
||||||
|
ROOM["room.php"]; AUTOA["AutoAction.php"]; SETA["settings.php"]
|
||||||
|
STATS["getStats.php"]; EHIST["energyHistory.php"]; PROD["getProdData<br/>getConsData<br/>getForecastData"]
|
||||||
|
HEATA["getHeaterData<br/>getWaterData"]; SKODAA["skoda.php<br/>skodaCmd.php<br/>tile.php"]; LOGA["logs.php"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph M["restricted (Modell)"]
|
||||||
|
ROOMS["rooms.php"]; COSTS["costs.php"]; AUTOM["automations.php"]
|
||||||
|
KACH["kacheln.php"]; RCTRL["roomControls.php"]; CMD["commands.php"]; LOGD["logdateien.php"]
|
||||||
|
end
|
||||||
|
|
||||||
|
SOLARLOG[("solarLog")]; HOMEMESH[("homeMesh")]; DATEIEN[/"Logdateien"/]; BROKER{{"Broker"}}
|
||||||
|
|
||||||
|
HOME --> HOMEJS & AUTOJS
|
||||||
|
SOLAR --> SOLJS & STATJS
|
||||||
|
HIST --> HISTJS & STATJS
|
||||||
|
HEAT --> HEATJS
|
||||||
|
SKODA --> SKODAJS
|
||||||
|
SET --> SETJS
|
||||||
|
LOGS --> LOGJS
|
||||||
|
WEA --> WEAJS
|
||||||
|
S -.-> COMMON
|
||||||
|
|
||||||
|
HOMEJS --> ROOM
|
||||||
|
AUTOJS --> AUTOA
|
||||||
|
SOLJS --> PROD
|
||||||
|
HISTJS --> EHIST
|
||||||
|
STATJS --> STATS
|
||||||
|
HEATJS --> HEATA
|
||||||
|
SKODAJS --> SKODAA
|
||||||
|
SETJS --> SETA
|
||||||
|
LOGJS --> LOGA
|
||||||
|
|
||||||
|
ROOM --> RCTRL & CMD & AUTOM
|
||||||
|
AUTOA --> AUTOM
|
||||||
|
SETA --> COSTS & KACH & AUTOM
|
||||||
|
STATS --> COSTS
|
||||||
|
KACH --> ROOMS
|
||||||
|
ROOMS --> COSTS
|
||||||
|
LOGA --> LOGD
|
||||||
|
|
||||||
|
COSTS --> SOLARLOG
|
||||||
|
AUTOM --> HOMEMESH
|
||||||
|
CMD --> HOMEMESH
|
||||||
|
CMD -.-> BROKER
|
||||||
|
PROD --> SOLARLOG
|
||||||
|
EHIST --> SOLARLOG
|
||||||
|
HEATA --> SOLARLOG
|
||||||
|
SKODAA --> SOLARLOG
|
||||||
|
LOGD --> DATEIEN
|
||||||
|
|
||||||
|
classDef speicher fill:#eaf5ee,stroke:#6fa981
|
||||||
|
class SOLARLOG,HOMEMESH,DATEIEN,BROKER speicher
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Die JavaScript-Schicht
|
||||||
|
|
||||||
|
Kein Framework, kein Bundler. Jede Datei ist ein Skript, das der Browser so
|
||||||
|
lädt, wie es dasteht; geteilt wird über globale Funktionen aus `common.js`.
|
||||||
|
Die Namen sind deutsch, sobald es um die Sache geht — englisch nur dort, wo
|
||||||
|
eine Bibliothek es vorgibt.
|
||||||
|
|
||||||
|
### `common.js` — immer geladen
|
||||||
|
|
||||||
|
| Funktion | wofür |
|
||||||
|
|---|---|
|
||||||
|
| `powerToString(w)` | Leistung als `W` oder `kW`, dieselbe Rundung überall |
|
||||||
|
| `getData(chart, url, opt)` | Diagrammdaten holen, eintragen, alle 5 min auffrischen; optional Sonnenauf-/-untergang aus `getSunrise.php` dazu |
|
||||||
|
| `submitFormAjax(form)` | Formular abschicken, ohne die Seite zu verlassen |
|
||||||
|
| `bindModalSlider()` | Schieberegler im Modal an ihre Anzeige koppeln |
|
||||||
|
| `mountMeteogram()`, `updateWeatherCards()` | Wetterteile der Solar- und Wetterseite |
|
||||||
|
| `reiterMerken()` | offener Reiter steht in der Adresse und übersteht ein Neuladen |
|
||||||
|
| `loadingHTML(text)` | der eine Ladezustand für alle Modals |
|
||||||
|
|
||||||
|
### Die Seitenskripte
|
||||||
|
|
||||||
|
**`homeMQTT.js`** — Grundriss und Kacheln. Zwei Objekte: `homeMQTT` hält die
|
||||||
|
Verbindung und den Nachrichtenbaum (`mqttData`), `homeSVG` schreibt in die
|
||||||
|
SVG-Kacheln. Was auf einer Kachel steht, kommt nicht aus dem Skript, sondern
|
||||||
|
aus `homeRooms` — der Raumtabelle, die `home.php` als JSON in die Seite
|
||||||
|
schreibt. `homeSVG.kachelWert()` ist die Stelle, an der aus einer Nachricht
|
||||||
|
Text wird: Topic oder Wechselrichtersumme lesen, ggf. JSON-Pfad
|
||||||
|
herausgreifen, Vorzeichen drehen (`negativ`), skalieren (`format:
|
||||||
|
"leistung"`), runden (`stellen`), Einheit anhängen. Klick auf eine Kachel
|
||||||
|
öffnet das Raum-Modal über `ajax/room.php?room=EG_Bad`.
|
||||||
|
|
||||||
|
**`solarMQTT.js`** — die große Anlagenübersicht. `solarSVG` zeichnet den
|
||||||
|
Energiefluss (Breite der Pfeile aus der Leistung), darunter hängen die drei
|
||||||
|
Diagramme Prognose, Verbrauch und Erzeugung über `getData()`. Änderungen am SVG laufen über kleine Setter
|
||||||
|
(`setText`, `setAttr`, `setFlowWidth`) und werden pro Bildrahmen gesammelt
|
||||||
|
(`solarSvgUpdatePending`), damit ein Schwall MQTT-Nachrichten nicht hundertmal
|
||||||
|
Layout auslöst.
|
||||||
|
|
||||||
|
**`skodaMQTT.js`** — Fahrzeugseite. Holt alles über `ajax/skoda.php?was=…`
|
||||||
|
(live, ladungen, kurve, gesundheit, fahrten, strecke), zeichnet das Fahrzeug
|
||||||
|
als SVG-Draufsicht mit Ladestand im Akku, Ladeknopf und Klimaknopf, und die
|
||||||
|
Fahrtenkarte mit Leaflet über den eigenen Kachelspeicher `ajax/tile.php`.
|
||||||
|
Befehle gehen per POST an `ajax/skodaCmd.php` — nie direkt an die API.
|
||||||
|
|
||||||
|
**`jahresstatistik.js`** — ein Bauteil, zwei Seiten. `baueJahresstatistik()`
|
||||||
|
baut aus der Antwort von `getStats.php` die Vergleichstabelle (Jahre
|
||||||
|
nebeneinander, Gesamtspalte, Δ in Prozentpunkten bei Quoten, Sparklines je
|
||||||
|
Monat). Die Kennzahlen stehen **nicht** im Skript: sie beschreiben sich in der
|
||||||
|
Antwort selbst.
|
||||||
|
|
||||||
|
**`autoActionFuncs.js`** — der Automatik-Editor: Gerätewähler mit Suche,
|
||||||
|
Bedingungen in UND-/ODER-Gruppen, Aktionen mit Parametern, Klartext-Vorschau.
|
||||||
|
Er bekommt Gerätekatalog und Datensatz in einem Dokument von
|
||||||
|
`AutoAction.php?action=editor` — früher kamen je Bedingung zwei weitere
|
||||||
|
Anfragen dazu, und die Auswahlfelder füllten sich asynchron.
|
||||||
|
|
||||||
|
**`settings.js`** — drei Reiter in einer Datei, deutlich getrennt: Preistabellen,
|
||||||
|
Home-Kacheln, Geräte. Jeder Abschnitt hat sein Modell (`kachelModell`,
|
||||||
|
`geraeteModell`), seine `zeichne…()`-Funktion und eine `…Binden()`-Funktion,
|
||||||
|
die genau einen Ereignisbehandler auf den Container legt (Delegation) —
|
||||||
|
deshalb überlebt Bedienung jedes Neuzeichnen.
|
||||||
|
|
||||||
|
**`heatMQTT.js`, `historyMQTT.js`, `weatherMQTT.js`, `logs.js`** — jeweils
|
||||||
|
klein: Diagramme füllen bzw. Logzeilen nachladen und einfärben.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Die PHP-Schicht
|
||||||
|
|
||||||
|
### Endpunkte (`ajax/`)
|
||||||
|
|
||||||
|
| Endpunkt | Aufruf | liefert / tut |
|
||||||
|
|---|---|---|
|
||||||
|
| `room.php` | `?room=EG_Bad`, `POST ?action=command\|temp` | Raum-Modal bauen; ein Kommando oder eine Solltemperatur senden |
|
||||||
|
| `AutoAction.php` | `?action=editor\|list`, `POST save\|delete\|toggle` | Automatik-Editor und -Übersicht |
|
||||||
|
| `settings.php` | `?action=list\|kacheln\|geraete`, `POST save\|kacheln-save\|geraete-raeume\|geraet-loeschen` | Einstellungsseite |
|
||||||
|
| `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 |
|
||||||
|
| `getSunrise.php` | `?FROM=&TO=` | Sonnenauf- und -untergänge als Diagramm-Markierungen |
|
||||||
|
| `skoda.php` | `?was=live\|ladungen\|kurve\|gesundheit\|fahrten\|strecke` | Fahrzeugauswertungen aus `solarLog.skoda` |
|
||||||
|
| `skodaCmd.php` | `POST befehl=…` | Laden/Klima/Lüftung am Fahrzeug |
|
||||||
|
| `tile.php` | `?z=&x=&y=` | Kartenkachel aus dem eigenen Zwischenspeicher, sonst einmalig von OSM |
|
||||||
|
| `logs.php` | `?action=list\|read` | Zustand und Zeilen der Logdateien |
|
||||||
|
| `carEG.php` `carOG.php` `carSteuerung.php` `heater.php` `watering.php` `tahoma.php` | | Wallboxen, Heizstab, Bewässerung, Tahoma-Bedienung |
|
||||||
|
| `chartData.php` | (kein Endpunkt) | gemeinsame Bausteine der Chart-Endpunkte |
|
||||||
|
| `phpMQTT.php` | (Bibliothek) | MQTT aus PHP heraus, für Kommandos |
|
||||||
|
|
||||||
|
Die meisten Endpunkte sind **Verteiler**: Eingabe prüfen, Modellfunktion
|
||||||
|
rufen, JSON zurückgeben. Wo Logik in einem Endpunkt steht, hat das einen
|
||||||
|
Grund, der oben in der Datei erklärt ist (`getStats.php` rechnet die
|
||||||
|
Kennzahlen, `energyHistory.php` wählt die Quelle nach Zeitraum,
|
||||||
|
`skoda.php` bildet Fahrten und Ladungen aus einer unregelmäßigen Zeitreihe).
|
||||||
|
|
||||||
|
### Modelle (`restricted/`)
|
||||||
|
|
||||||
|
| Datei | Zuständig für | Wichtige Funktionen |
|
||||||
|
|---|---|---|
|
||||||
|
| `rooms.php` | Räume, Etagen, Kachelwerte, Symbole | `allRooms()`, `roomsWithTile()`, `tileTopics()`, `kachelVorgabe()`, `kachelUeberschreibungen()`, `bootstrapIcons()`, `kachelIcon()` |
|
||||||
|
| `costs.php` | Preistabellen, Verbindung zu `solarLog` | `solarDb()`, `preiseLaden()`, `preiseSpeichern()`, `grundpreisImZeitraum()` |
|
||||||
|
| `automations.php` | Automatiken, Gerätekatalog, Verbindung zu `homeMesh` | `meshDb()`, `deviceCatalog()`, `loadAutomation()`, `saveAutomation()`, `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()` |
|
||||||
|
| `wallboxen.php` | Beide Wallboxen über MQTT | `wallboxen()`, `wallboxSetzen()` |
|
||||||
|
| `logdateien.php` | Logdateien vom Ende her lesen | `logDateien()`, `logLesen()`, `logZerlegen()` |
|
||||||
|
| `aussenplan.php` | Schrägbild des Außengeländes | Projektion + SVG |
|
||||||
|
| `reiter.php` | Reiter | `reiterLeiste()`, `reiterFlaecheAuf/Zu()`, `reiterBlock()` |
|
||||||
|
| `config.php` | Zahlen, die an der Wirklichkeit hängen (welches Auto an welcher Box) | |
|
||||||
|
| `mysql.php` | Zugangsdaten, **nicht im Git** | Vorlage: `mysql.php.example` |
|
||||||
|
|
||||||
|
Include-Graph der Modellschicht — flach gehalten, damit man einer Zeile bis zur
|
||||||
|
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
|
||||||
|
KACH["kacheln.php"] --> ROOMS
|
||||||
|
KACH --> COSTS
|
||||||
|
KACH -. "nur wenn vorhanden" .-> AUTOM["automations.php"]
|
||||||
|
AUTOM --> MYSQL
|
||||||
|
AUTOM --> ROOMS
|
||||||
|
CMD["commands.php"] --> MYSQL
|
||||||
|
CMD --> TAH["tahoma_EG.php"]
|
||||||
|
CMD --> PHPMQTT["ajax/phpMQTT.php"]
|
||||||
|
RCTRL["roomControls.php"] --> REITER["reiter.php"]
|
||||||
|
HOME["home.php"] --> ROOMS
|
||||||
|
HOME --> REITER
|
||||||
|
SET["settings.php"] --> COSTS & KACH & REITER
|
||||||
|
HEADER["header.php"] --> ROOMS
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Die Datenbanken
|
||||||
|
|
||||||
|
Drei Schemata, klar getrennte Zuständigkeit:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
erDiagram
|
||||||
|
actors ||--o{ actor_states : "meldet"
|
||||||
|
actors ||--o{ actor_commands : "kann"
|
||||||
|
actor_commands ||--o{ command_parameters : "nimmt"
|
||||||
|
automations ||--o{ automation_conditions : "wenn"
|
||||||
|
automations ||--o{ automation_actions : "dann"
|
||||||
|
automation_conditions }o--|| actor_states : "vergleicht"
|
||||||
|
automation_actions }o--|| actor_commands : "schickt"
|
||||||
|
automation_actions ||--o{ automation_action_params : "mit"
|
||||||
|
```
|
||||||
|
|
||||||
|
**`homeMesh`** — Geräte und Automatiken (Schema: `homeMesh_DB-layout.sql`,
|
||||||
|
`homeMesh_automations.sql`).
|
||||||
|
|
||||||
|
| Tabelle | Inhalt | geschrieben von |
|
||||||
|
|---|---|---|
|
||||||
|
| `actors` | ein Gerät je Zeile, `url` bestimmt den Weg (`mqtt://`, `http://`, `wled://`, `io://`, `Logic`) | Discovery; `room` von Hand (Einstellungen → Geräte) |
|
||||||
|
| `actor_states` | Messwerte: `url` = Topic **oder** Feldname, `value_path` = Schlüssel darin, `current_value` | Discovery (Adressen), Runner (Werte) |
|
||||||
|
| `actor_commands`, `command_parameters` | was ein Gerät kann und welche Parameter dazugehören | Discovery |
|
||||||
|
| `automations`, `automation_conditions`, `automation_actions`, `automation_action_params` | das Regelwerk | Web-Editor |
|
||||||
|
| `automation_log` | was wann ausgelöst hat | Runner |
|
||||||
|
| `calendar_days` | Feiertage und Ferien | `fetch_calendar.py` (SolarManager) |
|
||||||
|
| `state_types` | Datentypen der Messwerte (Zahl, Text, Auswahl …) | fest |
|
||||||
|
|
||||||
|
**`solarLog`** — alles, was einen Verlauf hat.
|
||||||
|
|
||||||
|
| Tabelle | Inhalt | geschrieben von |
|
||||||
|
|---|---|---|
|
||||||
|
| `EnergyFlow`, `EnergyFlow_hourly` | Leistungen im Rohtakt und als Stundenarchiv | `solarManager.py` |
|
||||||
|
| `stats_daily` | Tageswerte für die Jahresstatistik | `solarManager.py` |
|
||||||
|
| `Heater`, `puffertemp`, `wasser`, `zisterne`, `windrad`, `kiga_windrad` | Heizung, Speicher, Wasser, Windrad | `solarManager.py` |
|
||||||
|
| `skoda`, `skoda_raw` | Fahrzeugzustand im Verlauf | `gatherSkodaData.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).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Vier Bausteine im Detail
|
||||||
|
|
||||||
|
### 1. Die Home-Kacheln
|
||||||
|
|
||||||
|
Was auf einer Kachel steht, hat drei Stufen: die Vorgabe in `rooms.php`, die
|
||||||
|
Überschreibung in `kachelWerte`, und die Anzeige im Browser.
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
ROOMS["rooms.php<br/><small>$rooms: Vorgabe je Raum</small>"] --> MIT["mitWerten()"]
|
||||||
|
KW[("kachelWerte<br/><small>je Raum, JSON</small>")] --> MIT
|
||||||
|
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>"]
|
||||||
|
TT["tileTopics()"] -->|"abonniert genau diese"| BROKER{{Broker}}
|
||||||
|
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
|
||||||
|
KACHM -->|"Auswahlliste"| KAT["deviceCatalog()<br/><small>automations.php</small>"]
|
||||||
|
```
|
||||||
|
|
||||||
|
Eine Wertdefinition ist ein kleines JSON-Objekt, überall in derselben Form:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "topic": "Power_UG/status/em:0", "pfad": "total_act_power",
|
||||||
|
"einheit": "W", "stellen": 0, "negativ": true,
|
||||||
|
"icon": "box-arrow-up", "gross": true }
|
||||||
|
```
|
||||||
|
|
||||||
|
`wechselrichter` + `feld` statt `topic` summiert mehrere Wechselrichter;
|
||||||
|
`format: "leistung"` skaliert selbst zwischen W und kW; `negativ` dreht das
|
||||||
|
Vorzeichen (für einen Zähler, der verkehrt herum eingebaut ist); `icon` ist
|
||||||
|
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
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant B as Browser
|
||||||
|
participant R as ajax/room.php
|
||||||
|
participant RC as roomControls.php
|
||||||
|
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
|
||||||
|
RC-->>B: fertiges Markup (Rollladen, Licht, Messwerte …)
|
||||||
|
B->>R: POST ?action=command {command_id, params}
|
||||||
|
R->>C: executeCommand()
|
||||||
|
alt mqtt://
|
||||||
|
C-->>G: Nutzlast auf das Topic
|
||||||
|
else wled://
|
||||||
|
C-->>G: JSON an /json/state
|
||||||
|
else http://
|
||||||
|
C-->>G: Abfrageargumente an die Geräte-URL
|
||||||
|
else Tahoma
|
||||||
|
C-->>G: exec/apply an die Box
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
`roomControls.php` entscheidet **an den Fähigkeiten**, nicht am Typnamen: Hat
|
||||||
|
das Gerät ein `setClosure`, wird es ein Rollladen mit Auf/Stop/Zu und zwei
|
||||||
|
Reglern; hat es drei Parameter rot/grün/blau, wird es eine Lampe mit
|
||||||
|
Farbwähler. Ein neu gefundenes Gerät bekommt so ohne Codeänderung ein
|
||||||
|
passendes Bedienelement.
|
||||||
|
|
||||||
|
`commands.php` ist das Gegenstück zu `transports.py` im SolarManager-Repo —
|
||||||
|
dieselbe Zuordnung „welche URL gehört zu welchem Weg“, einmal für den Runner,
|
||||||
|
einmal für den Browser.
|
||||||
|
|
||||||
|
### 3. Die Automatiken
|
||||||
|
|
||||||
|
Editor (`autoActionFuncs.js` + `AutoAction.php` + `automations.php`) schreibt
|
||||||
|
das Regelwerk nach `homeMesh`; ausgeführt wird es von
|
||||||
|
`autoActions/autoaction_runner.py` im anderen Repo. **Die Weboberfläche
|
||||||
|
schaltet nichts von sich aus** — sie beschreibt nur, was gelten soll.
|
||||||
|
|
||||||
|
Bedingungen mit derselben `group_no` sind mit UND verknüpft, verschiedene
|
||||||
|
Gruppen mit ODER: ausgewertet wird `any(all(gruppe))`. Eine Bedingung zeigt
|
||||||
|
auf einen `actor_states`-Eintrag, eine Aktion auf ein `actor_commands` samt
|
||||||
|
Parametern. Deshalb dürfen Zustands-Ids nicht wandern — siehe die
|
||||||
|
Geräte-Erkennung im nächsten Abschnitt.
|
||||||
|
|
||||||
|
### 4. Die Geräte-Erkennung
|
||||||
|
|
||||||
|
`restricted/deviceDiscovery/device_discovery.py` wird **von Hand** gestartet.
|
||||||
|
Die Module suchen je eine Geräteart (Tahoma, WLED, Shelly, MQTT/Home-Assistant-
|
||||||
|
Discovery, Gartenwasser, SolarManager-Werte, Logic); die Datenbanklogik liegt
|
||||||
|
allein im Hauptskript.
|
||||||
|
|
||||||
|
Zwei Eigenschaften, die man kennen muss:
|
||||||
|
|
||||||
|
* **Ein Suchlauf löscht nichts.** `clear_tables` steht in `config.ini` auf
|
||||||
|
`false`, weil die Automatiken auf die Ids von `actor_states` zeigen.
|
||||||
|
Bestehende Zeilen werden über den Schlüssel `(actor_id, state_name)`
|
||||||
|
aktualisiert, neue kommen dazu. Abgebaute Geräte verschwinden nur über
|
||||||
|
**Einstellungen → Geräte → Papierkorb** (und auch dort nur, wenn keine
|
||||||
|
Automatik mehr auf sie zeigt).
|
||||||
|
* **Shelly, zwei Wege.** Alte Shellys können kein MQTT und bleiben beim
|
||||||
|
HTTP-Weg: in `actor_states.url` steht ein Feldname der JSON-Antwort. Neuere
|
||||||
|
(Gen2 mit eingeschaltetem `status_ntf`) melden von selbst an den Broker —
|
||||||
|
dann steht in `url` das Topic (`Power_EG/status/em:0`) und in `value_path`
|
||||||
|
der Schlüssel. Geschaltet werden auch sie weiter über HTTP. Der Runner teilt
|
||||||
|
seine Messwerte deshalb je Wert zu, nicht je Gerät.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Wiederkehrende Bauteile und Konventionen
|
||||||
|
|
||||||
|
**Reiter.** Es gibt genau eine Bauart, `restricted/reiter.php`. Umgeschaltet
|
||||||
|
wird von Bootstrap (`data-bs-toggle="tab"`), gemerkt wird der offene Reiter von
|
||||||
|
`reiterMerken()` in `common.js`. Weniger als zwei Reiter ergeben keine Leiste.
|
||||||
|
|
||||||
|
**Stylesheet-Reihenfolge — die häufigste Falle.** `header.php` lädt
|
||||||
|
`css/solar.css` **vor** `css/adminlte.min.css`. Eine eigene Regel mit gleicher
|
||||||
|
Spezifität wie eine AdminLTE-Regel verliert deshalb. Wenn eine Änderung
|
||||||
|
„nicht wirkt“, ist das fast immer der Grund: Selektor verlängern
|
||||||
|
(`.nav-tabs.reiter .nav-link` statt `.reiter .nav-link`), nicht `!important`.
|
||||||
|
|
||||||
|
**Zwischenspeicher.** Jedes eigene Skript und `solar.css` werden mit
|
||||||
|
`?v=<filemtime>` eingebunden — gespeichert heißt geladen.
|
||||||
|
|
||||||
|
**Sprache.** Bezeichner und Kommentare sind deutsch, wo es um die Sache geht;
|
||||||
|
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.
|
||||||
|
|
||||||
|
**Zugriffsschutz.** `.htaccess` sperrt Punkt-Dateien (allen voran `.git`) und
|
||||||
|
alle Dateitypen, die der Browser nie anfordert (`.py`, `.sql`, `.ini`, `.md`
|
||||||
|
…). `restricted/mysql.php` steht in `.gitignore`; Vorlage ist
|
||||||
|
`mysql.php.example`. Zugangsdaten gehören nirgendwo sonst hin.
|
||||||
|
|
||||||
|
**Zeilenenden.** `.gitattributes` erzwingt LF — der Ordner wird von Linux
|
||||||
|
ausgeliefert.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Wo fange ich an, wenn ich … ändern will
|
||||||
|
|
||||||
|
| 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` |
|
||||||
|
| 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()` |
|
||||||
|
| Neue Seite | Zeile in `$pages` (`index.php`), Vorlage unter `restricted/`, Skript unter `js/solar/` |
|
||||||
|
| Anderes Bedienelement für ein Gerät | `bedienform()` in `roomControls.php` (nach Fähigkeit, nicht nach Typname) |
|
||||||
|
| Neue Geräteart erkennen | neues Modul unter `restricted/deviceDiscovery/modules/` + Eintrag in der Liste in `device_discovery.py` |
|
||||||
|
| Neuer Geräteweg zum Schalten | `commands.php` (Browser) **und** `transports.py` im SolarManager-Repo (Runner) |
|
||||||
|
| Aussehen der ganzen Seite | `css/solar.css`, oberster Abschnitt: die `--ton-*`-Tokens und die Bootstrap-Zuordnung |
|
||||||
|
| Reiter irgendwo | `reiterLeiste()`/`reiterBlock()` aus `restricted/reiter.php` benutzen, keine eigene Variante bauen |
|
||||||
|
| Neue Einstellung | Reiter in `$reiter` (`restricted/settings.php`), Abschnitt in `settings.js`, Aktion in `ajax/settings.php`, Modellfunktion daneben |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Entwickeln und Prüfen
|
||||||
|
|
||||||
|
Der Ordner ist die laufende Anwendung — es gibt keinen Build und keine
|
||||||
|
Staging-Stufe. Entsprechend gilt:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# PHP-Syntax (auf der NAS; /usr/bin/php ist 8.1 ohne mysqli)
|
||||||
|
/usr/local/bin/php82 -l restricted/kacheln.php
|
||||||
|
|
||||||
|
# JavaScript-Syntax
|
||||||
|
/var/packages/Node.js_v20/target/usr/local/bin/node --check js/solar/settings.js
|
||||||
|
|
||||||
|
# Modellfunktion einzeln ausprobieren, ohne Browser
|
||||||
|
/usr/local/bin/php82 -r 'require "restricted/automations.php"; print_r(geraeteListe()[0]);'
|
||||||
|
|
||||||
|
# Datenbank ansehen (readDB, nur SELECT)
|
||||||
|
/usr/local/mariadb10/bin/mysql -h 127.0.0.1 -e 'SELECT * FROM homeMesh.actors LIMIT 5;'
|
||||||
|
```
|
||||||
|
|
||||||
|
Für Masken lohnt eine **Probeseite**: eine kleine HTML-Datei, die `solar.css`
|
||||||
|
und das Skript lädt und `window.fetch` durch eine Attrappe mit echten Daten
|
||||||
|
ersetzt. Damit lässt sich das Zeichnen prüfen, ohne angemeldet zu sein und
|
||||||
|
ohne etwas zu speichern.
|
||||||
|
|
||||||
|
Zwei Repositories, beide auf Gitea (`gitea.nas.el-wa.org`):
|
||||||
|
|
||||||
|
| Repo | Ort | Inhalt |
|
||||||
|
|---|---|---|
|
||||||
|
| `admin/Smart-Dashboard` | `/volume1/web/smart` | dieses hier |
|
||||||
|
| `admin/SolarManager` | `/volume1/homes/wagner/SolarManager` | Sammler, Runner, Wecker |
|
||||||
|
|
||||||
|
Rekursive Suchen (`grep -r`) laufen über die SMB-Freigabe in Zeitüberschreitungen —
|
||||||
|
auf der NAS selbst sind sie sofort fertig.
|
||||||
Reference in New Issue
Block a user