Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/contactor-guide.de.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ GND ─────────────────────────
```

**Freilaufdioden 1N4007** parallel zu jeder Schützspule:
- Kathode (Strichseite) an **A1 (+)**
- Kathode (Strichseite) an **A1 (+)**
- Anode an **A2 (GND)**

Die Dioden löschen den Spannungsrückschlag (Back-EMF) der Schützspule beim
Expand Down
105 changes: 105 additions & 0 deletions docs/multicore-architecture.de.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
---
title: Multicore-Architektur
summary: Wie die Firmware beide ESP32-Kerne nutzt — dedizierte I/O-Tasks (Sensoren, Display, MQTT-Telemetrie) auf Kern 0 und eine deterministische Regelschleife auf Kern 1
date: "2026-08-01"
lastmod: "2026-08-01"
draft: false
toc: true
type: docs
featured: false
tags: ["docs", "controller", "architektur", "multicore", "tasks"]
menu:
docs:
parent: Pool Controller
name: Multicore-Architektur
weight: 33
---

## Überblick

Der ESP32 hat zwei Xtensa-LX6-Kerne, aber ein Single-Loop-Arduino-Sketch nutzt nur
einen: Der WiFi/BT-Stack läuft auf Kern 0, die Arduino-`loop()` auf Kern 1. Alles
andere — Sensor-Messungen, Display-Update, Regeln, Netzwerk, MQTT — läuft seriell
innerhalb von `loop()`.

Die Firmware wird zu einer **Task-Architektur mit expliziter Kern-Trennung**
umstrukturiert:

| Kern | Rolle | Inhalt |
| ---- | ----- | ------ |
| **Kern 0** (PRO_CPU) | I/O-Kern | SensorTask (DS18B20 + interner Temperatursensor), DisplayTask (OLED-Rendering, nur NORVI), PublishTask (MQTT-Telemetrie-Serialisierung) |
| **Kern 1** (APP_CPU) | Regel-Kern | Arduino-`loop()`: Watchdog, Degradation, Regeln, Relais, Status-LED, asynchrone Netzwerk-Manager, OTA, Frontpanel-Tasterabfrage |

## Warum

Blockierende Arbeit blockierte bisher die gesamte Regelschleife. Die teuerste
Operation ist die DS18B20-Temperaturkonvertierung (`requestTemperatures()`), die bei
12-Bit-Auflösung etwa **750 ms** blockiert. In dieser Zeit warten Watchdog-Feeding,
Regelauswertung und Relais-Ansteuerung.

Das Auslagern dieser I/O-Arbeit in dedizierte Tasks auf Kern 0 bringt drei Vorteile:

1. **Geringe Loop-Latenz** — die Regelschleife bleibt im niedrigen Millisekundenbereich.
2. **Isolation** — ein hängender Sensor-Bus oder ein I2C-Display kann die
sicherheitskritische Regellogik auf Kern 1 nicht mehr blockieren.
3. **Headroom** — Kapazität für zukünftige Funktionen (mehr Sensoren, Web-UI, Logging).

## Task-Modell

Alle I/O-Tasks werden in `setup()` vom `CoreScheduler` erzeugt und bleiben statisch
(keine dynamische Task-Erzeugung zur Laufzeit, kein Heap-Wachstum).

| Task | Kern | Priorität | Stack | Läuft auf |
| ---- | ---- | --------- | ----- | --------- |
| SensorTask | 0 | 2 | 6 KB | allen Builds |
| PublishTask | 0 | 1 | 4 KB | allen Builds |
| DisplayTask | 0 | 1 | 3 KB | nur NORVI (`#ifdef NORVI_AE01_R`) |

FreeRTOS-Prioritäten gelten nur innerhalb eines Kerns: Die I/O-Tasks geben per
`vTaskDelay` nach und bleiben unterhalb der WiFi-Stack-Tasks auf Kern 0 — sie können
die Regelschleife auf Kern 1 also nie verdrängen.

## Datenfluss

```text
SensorTask (Kern 0) ── lock-free Slots ──▶ Regelschleife (Kern 1): Regeln/Relais/Watchdog
SensorTask ── Status ────────────────────▶ DegradationManager (Kern 1)
Regelschleife ── update() + Render-Anforderung ─▶ DisplayTask (Kern 0, NORVI)
Tasterabfrage bleibt in der Regelschleife (Kern 1) — Callbacks mutieren Loop-Singletons
Regelschleife ── Telemetrie-Queue ────────▶ PublishTask (Kern 0) ──▶ MQTT
Regelschleife ── asynchrones Netzwerk/OTA ── (unverändert, Kern 1)
```

Jeder task-übergreifende Datenpfad ist **Single-Writer**:

- Sensorwerte: lock-free Slots (atomar/ein Wort) — SensorTask schreibt, Regelschleife liest.
- Display-Zustand: `volatile`-Render-Anforderungs-Flag — Regelschleife fordert an,
DisplayTask rendert (Wortzugriff ist auf dem ESP32 atomar).
- Taster-Eingaben: bleiben in der Regelschleife — die Taster-Callbacks mutieren
Loop-Singletons (`operationModeNode`, `poolPumpNode`); eine Abfrage auf Kern 0
würde die Single-Writer-Regel verletzen. Ausgelagert ist nur das OLED-*Rendering*
(blockierende I2C-Arbeit).
- Telemetrie: SPSC-Ringpuffer mit fester Kapazität — Regelschleife stellt ein,
PublishTask serialisiert und publiziert.

Die MQTT-*Verbindung* und die Web-/OTA-Manager bleiben in der Regelschleife — sie
sind bereits nicht-blockierend (`AsyncMqttClient`, asynchroner Webserver). Ausgelagert
wird nur die Telemetrie-Serialisierung (JSON-Aufbau, HA-Discovery-Payloads) in den
PublishTask.

## Zuverlässigkeit

- Die Regelschleife füttert den Task-Watchdog weiterhin; I/O-Tasks füttern ihn bei
langen Wartezeiten (DS18B20-Konvertierung, OTA-Pause).
- `SystemMonitor` meldet die Stack-High-Water-Marks der Tasks, sodass die
Stack-Größen in Logs und Degradation sichtbar sind.
- Safe-Mode- und Degradations-Semantik bleiben unverändert — Sensorfehler werden dem
`DegradationManager` über einen thread-sicheren Statuskanal gemeldet.
- Während OTA pausiert der PublishTask das Publizieren, leert seine Queue aber
weiter.

## Design-Dokument

Das vollständige Design inklusive Thread-Safety-Audit der bestehenden Singletons,
Migrationsphasen, Risiken und Erfolgskriterien liegt unter
[`docs/superpowers/specs/2026-08-01-multicore-task-architecture-design.md`](../superpowers/specs/2026-08-01-multicore-task-architecture-design.md).
104 changes: 104 additions & 0 deletions docs/multicore-architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
---
title: Multicore Architecture
summary: How the firmware uses both ESP32 cores — dedicated I/O tasks (sensors, display, MQTT telemetry) on Core 0 and a deterministic control loop on Core 1
date: "2026-08-01"
lastmod: "2026-08-01"
draft: false
toc: true
type: docs
featured: false
tags: ["docs", "controller", "architecture", "multicore", "tasks"]
menu:
docs:
parent: Pool Controller
name: Multicore Architecture
weight: 33
---

## Overview

The ESP32 has two Xtensa LX6 cores, but a single-loop Arduino sketch only uses one:
the WiFi/BT stack runs on Core 0 and the Arduino `loop()` on Core 1. Everything
else — sensor reads, display updates, rules, network, MQTT — runs serially inside
`loop()`.

The firmware restructures this into a **task architecture with explicit core
separation**:

| Core | Role | Contents |
| ---- | ---- | -------- |
| **Core 0** (PRO_CPU) | I/O core | SensorTask (DS18B20 + internal temp), DisplayTask (OLED rendering, NORVI only), PublishTask (MQTT telemetry serialization) |
| **Core 1** (APP_CPU) | Control core | Arduino `loop()`: watchdog, degradation, rules, relays, StatusLED, async network managers, OTA, front-panel button scan |

## Why

Blocking work used to stall the entire control loop. The most expensive operation is
the DS18B20 temperature conversion (`requestTemperatures()`), which blocks for about
**750 ms** at 12-bit resolution. During that time the watchdog feeding, rule
evaluation, and relay actuation all wait.

Moving that I/O to dedicated tasks on Core 0 gives three benefits:

1. **Low loop latency** — the control loop stays in the low millisecond range.
2. **Isolation** — a hung sensor bus or I2C display can no longer block the
safety-critical control logic on Core 1.
3. **Headroom** — capacity for future features (more sensors, web UI, logging).

## Task model

All I/O tasks are created in `setup()` by the `CoreScheduler` and stay static (no
dynamic task creation at runtime, no heap growth).

| Task | Core | Priority | Stack | Runs on |
| ---- | ---- | -------- | ----- | ------- |
| SensorTask | 0 | 2 | 6 KB | all builds |
| PublishTask | 0 | 1 | 4 KB | all builds |
| DisplayTask | 0 | 1 | 3 KB | NORVI only (`#ifdef NORVI_AE01_R`) |

FreeRTOS priorities only matter within a core: the I/O tasks yield via
`vTaskDelay` and stay below the WiFi-stack tasks on Core 0, so they never preempt
the control loop on Core 1.

## Data flow

```text
SensorTask (Core 0) ── lock-free slots ──▶ control loop (Core 1): rules/relays/watchdog
SensorTask ── status ────────────────────▶ DegradationManager (Core 1)
control loop ── update() + render request ─▶ DisplayTask (Core 0, NORVI)
button scan stays on the control loop (Core 1) — callbacks mutate loop singletons
control loop ── telemetry queue ─────────▶ PublishTask (Core 0) ──▶ MQTT
control loop ── async network/OTA ──────── (unchanged, Core 1)
```

Every cross-task data path is **single-writer**:

- Sensor values: lock-free slots (atomic/single-word) — SensorTask writes, control
loop reads.
- Display state: `volatile` render-request flag — control loop requests, DisplayTask
renders (word-sized access is atomic on ESP32).
- Button input: stays on the control loop — button callbacks mutate control-loop
singletons (`operationModeNode`, `poolPumpNode`), so scanning on Core 0 would
violate the single-writer rule. The OLED *rendering* (blocking I2C work) is what
runs on Core 0.
- Telemetry: fixed-capacity SPSC ring buffer — control loop enqueues, PublishTask
serializes and publishes.

The MQTT *connection* and the web/OTA managers stay on the control loop — they are
already non-blocking (`AsyncMqttClient`, async web server). Only the telemetry
serialization (JSON build, HA Discovery payloads) is offloaded to PublishTask.

## Reliability

- The control loop keeps feeding the task watchdog; I/O tasks feed it during long
waits (DS18B20 conversion, OTA pause).
- `SystemMonitor` reports task stack high-water marks so stack sizing is visible in
logs and degradation.
- Safe mode and degradation semantics are unchanged — sensor faults are reported to
`DegradationManager` over a thread-safe status channel.
- During OTA, PublishTask pauses publishing but keeps draining its queue.

## Design document

The full design, including the thread-safety audit of the existing singletons,
migration phases, risks, and success criteria, lives in
[`docs/superpowers/specs/2026-08-01-multicore-task-architecture-design.md`](../superpowers/specs/2026-08-01-multicore-task-architecture-design.md).
Loading