diff --git a/README.md b/README.md
new file mode 100644
index 0000000..ea51085
--- /dev/null
+++ b/README.md
@@ -0,0 +1,182 @@
+# SolarManager
+
+Die Hintergrundprozesse des Hauses: Messwerte einsammeln, Wallbox und Heizung
+regeln, die Automatiken auswerten. Gegenstück ist das Web-Repo
+`admin/Smart-Dashboard` unter `/volume1/web/smart` — dort liegt die Anzeige,
+hier liegt alles, was ohne offenen Browser weiterlaufen muss.
+
+Beide Seiten reden über zwei Dinge miteinander: den **MQTT-Broker** für alles
+Aktuelle und die **Datenbanken** für alles, was bleiben soll. Kein Prozess
+ruft einen anderen direkt auf.
+
+## Woher die Zahlen kommen
+
+```mermaid
+flowchart LR
+
+ WR["Wechselrichter
GoodWe · OpenDTU · DTU-BI"]
+ EM3["Stromzähler
2× Shelly EM3"]
+ HEIZ["Heizung"]
+ GOE["go-eCharger"]
+ WPILOT["Wattpilot"]
+ SKODA["MySkoda-API"]
+ STATION["Wetterstation"]
+ METEO["Open-Meteo"]
+ VENTILE["Ventilsteuerungen
2× ESP32"]
+ HAUSGER["Thermostate · Shellys · Schalter
melden sich per
Home-Assistant-Discovery"]
+
+ MGR["solarManager.py
gatherModbusData · gatherOpenDTUData
gatherDTUBIData · gatherShellyEM3Data EG/UG
gatherHeaterData · gatherSkodaData
gatherWaterData · charger_goE"]
+ WPB["wattpilot_bruecke.py"]
+ WSB["wsMQTTbridge.py"]
+ RAIN["gatherRainData.py"]
+
+ BROKER{{"MQTT-Broker"}}
+ SOLARLOG[("solarLog")]
+
+ WR -->|Modbus · HTTP| MGR
+ EM3 -->|HTTP| MGR
+ HEIZ -->|HTTP| MGR
+ GOE -->|HTTP| MGR
+ WPILOT -->|WebSocket| MGR
+ SKODA -->|HTTPS| MGR
+ WPILOT -->|WebSocket| WPB
+ STATION -->|WebSocket| WSB
+ METEO -->|HTTPS| RAIN
+
+ SOLARLOG -->|zisterne| MGR
+ MGR -->|"EnergyFlow · skoda"| SOLARLOG
+
+ MGR -->|"solarManager/#"| BROKER
+ WPB -->|"wattpilot/#"| BROKER
+ WSB -->|"weatherStation/#"| BROKER
+ RAIN -->|"Wetter/Regen"| BROKER
+ GOE -->|"go-eCharger/#"| BROKER
+ VENTILE -->|"Gartenwasser/#"| BROKER
+ HAUSGER -->|"Raumtemp/# · Power_EG/#
Power_UG/# · wasser/#"| BROKER
+
+ classDef quelle fill:#eef4fb,stroke:#7f9dc0
+ classDef skript fill:#fff6e5,stroke:#d0a548
+ classDef speicher fill:#eaf5ee,stroke:#6fa981
+ class WR,EM3,HEIZ,GOE,WPILOT,SKODA,STATION,METEO,VENTILE,HAUSGER quelle
+ class MGR,WPB,WSB,RAIN skript
+ class BROKER,SOLARLOG speicher
+```
+
+Vier Prozesse holen aktiv etwas ab. Alles andere meldet sich von selbst: die
+Ventilsteuerungen, die Wallbox und jedes Gerät, das
+Home-Assistant-Discovery spricht, schreiben ohne Umweg auf den Broker. Für
+deren Messwerte ist also **kein Skript** zuständig — wer sie sucht, sucht am
+Gerät, nicht im Quelltext.
+
+`solarManager.py` ist der Sonderfall: ein Prozess, aber acht
+Sammler-Module, jedes für eine Anlage. Sie laufen nicht einzeln, sondern
+werden importiert; ihr gemeinsames Ergebnis geht als ein Baum nach
+`solarManager/#`.
+
+Die Zisterne fällt aus der Reihe — ihr Stand steht in `solarLog`, und
+`gatherWaterData` liest ihn von dort. Die Daten laufen also durch die
+Datenbank hindurch von einem Prozess zum nächsten.
+
+## Wer sie benutzt
+
+```mermaid
+flowchart TB
+
+ BROKER{{"MQTT-Broker"}}
+ SOLARLOG[("solarLog
Verlauf")]
+ HOMEMESH[("homeMesh
Geräte · Automatiken")]
+ ALARM[("alarm
Weckzeiten")]
+
+ RUNNER["autoaction_runner.py
SolarManager"]
+ WECKER["wecker.py
SolarManager"]
+ KALENDER["fetch_calendar.py
SolarManager · Cronjob, jährlich"]
+ DISCOVERY["device_discovery.py
Web · von Hand gestartet"]
+ AJAX["ajax/*.php
Web"]
+ BROWSER["js/solar/*.js
Web · im Browser"]
+
+ HAUS["Rollläden · Licht · Schalter
Tahoma · WLED · Shelly"]
+ VENTILE["Ventilsteuerungen"]
+ FERIEN["openholidaysapi.org"]
+
+ BROKER <-->|"Messwerte ↓ Kommandos ↑"| RUNNER
+ HOMEMESH <-->|"Regelwerk ↓ Verlauf ↑"| RUNNER
+ RUNNER -.->|"HTTP · WLED · Tahoma"| HAUS
+ BROKER -.->|"Gartenwasser/…/set"| VENTILE
+
+ ALARM --> WECKER
+ FERIEN --> KALENDER
+ KALENDER -->|"calendar_days"| HOMEMESH
+ WECKER -.->|HTTP| HAUS
+
+ BROKER -->|"homeassistant/#"| DISCOVERY
+ HAUS -->|"mDNS · Tahoma"| DISCOVERY
+ DISCOVERY -->|"Geräte, Messwerte, Befehle"| HOMEMESH
+
+ SOLARLOG --> AJAX
+ HOMEMESH --> AJAX
+ AJAX <--> BROWSER
+ AJAX -.->|Kommandos| BROKER
+ BROKER -->|wss| BROWSER
+
+ classDef skript fill:#fff6e5,stroke:#d0a548
+ classDef speicher fill:#eaf5ee,stroke:#6fa981
+ classDef geraet fill:#eef4fb,stroke:#7f9dc0
+ class RUNNER,WECKER,DISCOVERY,AJAX,BROWSER,KALENDER skript
+ class BROKER,SOLARLOG,HOMEMESH,ALARM speicher
+ class HAUS,VENTILE,FERIEN geraet
+```
+
+**Durchgezogen fließen Daten, gestrichelt gehen Befehle.**
+
+Der Browser bekommt seine laufenden Werte direkt vom Broker über
+`wss://mqtt.nas.el-wa.org`, nicht über PHP. Über PHP läuft nur, was der
+Broker nicht weiß: der Verlauf aus `solarLog` und alles aus `homeMesh` — und
+umgekehrt jeder Knopfdruck, denn schalten darf nur der Server.
+
+`device_discovery.py` ist kein Dauerläufer. Es sucht per mDNS nach Shellys
+und WLEDs, hört die Discovery-Nachrichten auf `homeassistant/#` mit, fragt
+die Tahoma-Box und legt daraus die Geräte in `homeMesh` an. Gestartet wird es
+von Hand, wenn sich am Bestand etwas geändert hat.
+
+## Wem welche Daten gehören
+
+| Daten | wird gefüllt von | wird gelesen von |
+|---|---|---|
+| `solarManager/#` | `solarManager.py` | Browser, Runner |
+| `wattpilot/#` | `wattpilot_bruecke.py` | Browser, Runner |
+| `weatherStation/#` | `wsMQTTbridge.py` | Browser, Runner |
+| `Wetter/Regen` | `gatherRainData.py` | Runner, Browser |
+| `go-eCharger/#` | die Wallbox selbst | Browser, Runner |
+| `Gartenwasser/#` | die beiden ESP32 | Browser, Runner |
+| `Raumtemp/#`, `Power_*/#`, `wasser/#` | die Geräte selbst | Browser, Runner |
+| `homeassistant/#` | die Geräte selbst | `device_discovery.py` |
+| **`solarLog`** | `solarManager.py` | `ajax/*.php`, `gatherWaterData` |
+| **`homeMesh`** | `device_discovery.py`, Runner, Web-Editor | Runner, `ajax/*.php` |
+| **`alarm`** | Web-Oberfläche | `wecker.py` |
+
+## Was wann startet
+
+| Prozess | gestartet von |
+|---|---|
+| `solarManager.py` | `startSolarServer.sh` (Aufgabenplaner, beim Hochfahren) |
+| `autoActions/autoaction_runner.py` | dito |
+| `gatherRainData.py` | dito |
+| `wsMQTTbridge.py` | `startMQTTbridge.sh` |
+| `wattpilot_bruecke.py` | `startWattpilotMQTT.sh` |
+| `wecker.py` | `startWecker.sh` |
+| `autoActions/fetch_calendar.py` | Cronjob, einmal im Jahr |
+| `device_discovery.py` (Web-Repo) | von Hand |
+
+`startSolarServer.sh` beendet eine schon laufende Instanz, bevor es neu
+startet — beim Runner ist das wichtig, zwei Instanzen würden jedes Kommando
+doppelt schicken.
+
+## Konfiguration
+
+Zugangsdaten und Standort stehen in `config.ini` (Vorlage:
+`config.ini.example`), gelesen über `konfig.py`; der Runner hat seine eigene
+unter `autoActions/`. Beide sind per `.gitignore` ausgenommen — nichts davon
+gehört in den Quelltext.
+
+Mehr zum Runner selbst, zum Aufbau einer Automatik und zu den Transporten
+steht in [autoActions/README.md](autoActions/README.md).