Zweck: Regeln und Standards für einen Coding-Agent, der PlatformIO-basierten IoT-Firmware für ESP32 entwickelt, refaktoriert, analysiert und stabilisiert. Fokus: Architektur, Linting, Speicher- und Ressourcenmanagement, Sicherheit, 24/7-Betrieb, Wartbarkeit, Tests, Releases und OTA.
Der Agent:
- liefert minimal-invasive Änderungen mit Begründung, Impact-Analyse (RAM/Flash/CPU), Risiken, Rollback-Hinweisen.
- verhindert blockierende Patterns im Laufzeit-Code (long
delay(), busy waits, blockierende Netzwerk-Calls im Main-Loop). - vermeidet unsichere dynamische Allokationen in Hot-Paths und ungeprüfte Heap-Nutzung.
- folgt einem CI-regelbasierten Prozess (Build + Tests + Lint).
- Plattform: ESP32 (inkl. Varianten wie S3, C3); Frameworks: Arduino oder ESP-IDF.
- PlatformIO: Single-Source für Build-Konfiguration (
platformio.ini), Projekt-Environments, Lib-Pins.
Empfohlene Schichten (auch wenn das repository aktuell anders aussieht, Ziel ist schrittweise Annäherung):
src/app/Anwendungslogik (Use-Cases, State Machines)src/drivers/Hardware-Treiber (GPIO, I2C, SPI, ADC), keine Business-Logiksrc/services/Netzwerk, MQTT/HTTP, Time, Storage, OTA, Telemetrysrc/platform/Board-spezifische Adapter,#ifdefnur hier, nicht in App/Servicesinclude/Öffentliche Header, klare Interfacestest/Unit-/Component-Tests (PlatformIO Unity)
Architekturregeln:
- App kennt Services über Interfaces, keine direkten Implementierungen.
- Drivers ohne Abhängigkeiten zu Services/App-State.
- Scheduler/Timer/State Machines zentral geplant, nicht verteilt über
loop(). - Fehler-Resilienz und Fallback-Strategien eingebaut.
- Sprache: C++17 (wenn möglich), keine unkontrollierten Exceptions auf eingebetteten Targets.
- Header:
include-what-you-use, kein globalesusing namespace. - Konstanten:
constexpr,enum class. - Ownership: RAII oder klar definierte Allokations-/Deallokationsverantwortung.
- Schnittstellen: prefer Span-artige Übergaben, keine impliziten Kopien.
- Fehlerbehandlung immer explizit, kein stilles Ignorieren.
- Logging: niemals in ISR, niedrige Frequenz in Loop-Hot-Paths.
- Format:
clang-formatrepo-weit einheitlich. - Lint:
clang-tidywo möglich, sonstcppcheck. - Statische Checks: Warnungen auf Maximum, keine neuen Warnungen akzeptieren.
- CI: Build (
pio run), Tests (pio test), Lint/Format-Checks grün.
Commit Messages müssen dem Conventional Commits-Standard folgen:
<type>[optional scope]: <description>
Commit Types umfassen mindestens:
featfür neue Funktionenfixfür Fehlerbehebungendocsfür Änderungen an Dokumentationstylefür Formatierung/Code Stylerefactorfür Code-Umstrukturierungen ohne funktionale Änderungperffür Performance-Optimierungentestfür Test-Änderungenchorefür Wartung/Tooling/Build-Änderungen
Commit Message Body kann Motivation und Kontext enthalten. Breaking Changes
müssen mit BREAKING CHANGE: im Footer markiert werden.
Commit Messages müssen dem Format entsprechen, damit automatische Changelog-Generierung, Versionierung und CI-Checks funktionieren.:contentReference[oaicite:1]{index=1}
Use semble search to find code by describing what it does or naming a symbol/identifier, instead of grep:
bash semble search "authentication flow" ./my-project semble search "save_pretrained" ./my-project semble search "save model to disk" ./my-project --top-k 10
Use semble find-related to discover code similar to a known location (pass file_path and line from a prior search result):
bash semble find-related src/auth.py 42 ./my-project
path defaults to the current directory when omitted; git URLs are accepted.
If semble is not on $PATH, use uvx --from "semble[mcp]" semble in its place.
- Start with
semble searchto find relevant chunks. - Inspect full files only when the returned chunk is not enough context.
- Optionally use
semble find-relatedwith a promising result'sfile_pathandlineto discover related implementations. - Use grep only when you need exhaustive literal matches or quick confirmation of an exact string.
platformio.ini: zentrale Flags, Versions-Defines (FW_NAME,FW_VERSION).- Build-Artefakte: Debug vs Release:
- Debug: intensiver Logging, Heap/Stack Checks.
- Release: optimiert, gedämpftes Logging, Sicherheits-Features aktiv.
- Build-Flags:
-D LOG_LEVEL,-D NDEBUGsteuerbar über Environments.
- Kein unbounded dynamic Heap/Fragmentierung:
- Statische Puffer wo möglich, wiederverwendbare Ring-Buffers.
- Vermeide String-Objekte (
String) in Loops.
- JSON:
StaticJsonDocumentmit statischem Speicher vorab dimensionieren. - Heap/Stack-Metriken überwachen (
ESP.getFreeHeap(),heap_caps_get_free_size, Task-Stack-High-Watermarks). - PSRAM gezielt nutzen, nicht blind, mit Metriken.
- Memory-Pools statt häufige Allokationen.
ESP32:
- FreeRTOS Tasks mit klarer Verantwortlichkeit.
- Kommunikation über Queues/Semaphores; keine globals ohne Schutz.
- Prioritäten bewusst setzen, Priority Inheritance bei Mutex.
- Task-Stack dimensionieren und überwachen.
ESP32 Hardware-Security:
- Secure Boot: Boot-Image-Verifikation vor Start, Schlüssel offline erzeugen, eFuse planen. :contentReference[oaicite:0]{index=0}
- Flash Encryption: Schutz des Flash-Inhalts (Firmware, Credentials, Zertifikate), Release-Mode vor Produktion. :contentReference[oaicite:1]{index=1}
- Debug Interfaces (JTAG/UART) im Produktions-Build deaktivieren. :contentReference[oaicite:2]{index=2}
- TLS für Netzverbindungen (MQTTS/HTTPS) mit CA/Key-Validation.
- Secrets nicht im repository.
- OTA mit mindestens zwei Partitions-Slots, Anti-Rollback/Checksum/Validity. :contentReference[oaicite:3]{index=3}
- Sicherer OTA: HTTPS, Signaturen, Rollback-Mechanismus.
- Update-Failure Detection (Task Init + Health-Checks vor Markieren aktiv).
- Watchdogs aktiv (Loop/Tasks).
- Netzwerk-Resilienz: Reconnect-Backoff + Jitter, Offline-Betrieb möglich.
- Time via NTP mit Fallback.
- Persistenz: Flash-Writes minimieren, Bundling, Debounce.
- Health-Metrics sammeln: Uptime, Heap/Stack, Reset-Reason, Wifi/MQTT Status.
- Strukturierte Logs (KV-Form).
- Rate Limits für wiederkehrende Events.
- Health Endpoints oder Telemetrie-Reports.
- Defaults in
config_defaults.h. - Runtime-Konfiguration über Filesystem (LittleFS/NVS) validieren.
- Keine hartkodierten Secrets.
- Unit-Tests für Parser, Protocol/State, Backoff, Scheduler.
- Native Tests (
platform = native) bevorzugt für CI. - Komponententests mit Mocks/Simulations.
- Minimiert, begründet, Version-Pinned.
- Lizenz-Checks; Updates mit CI-Absicherung.
- Release: Sicherheitsfeatures, Monitoring, Debug ausschalten.
- CI: Lint, Build, Tests, Heap/Stack Reports, Memory-Analyse.
- Unlimitierte
delay(), Busy-Wait, blockierende Netzwerk-Calls im Loop. - Häufige Heap-Allokationen in Hot-Paths.
- String-Objekte in Zyklus-Code.
- Globale state ohne Synchronisation.
- Große Puffer (
> 512 B) als file-scopestatic, wenn sie nur einmalig (z. B. Setup-Pfad) benötigt werden — stattdessen als function-local Variable deklarieren, damit der Speicher nur während der Ausführung belegt ist. - Pointer-Member ohne In-Class-Initialisierung (
= nullptr); uninitialisierte Pointer sind UB und führen zu schwer reproduzierbaren Crashes. - Statische
String-Puffer mit.concat()ohne vorheriges Reset — führt bei mehrfachem Aufruf zu verdoppelten Inhalten.
Beim Einsatz von ESP-IDF-APIs immer mit ESP_IDF_VERSION_VAL absichern:
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
// Neue API (ESP-IDF 5.x)
#else
// Legacy-API (ESP-IDF 4.x)
#endifBekannte API-Brüche zwischen ESP-IDF 4 und 5:
| API | ESP-IDF 4.x | ESP-IDF 5.x |
|---|---|---|
| Task Watchdog Init | esp_task_wdt_init(uint32_t s, bool panic) |
esp_task_wdt_init(const esp_task_wdt_config_t *) |
| Task Watchdog (already initialised) | — | esp_task_wdt_reconfigure(const esp_task_wdt_config_t *) |
| WPS Start | esp_wifi_wps_start(int) |
esp_wifi_wps_start() |
Das Arduino-Framework für ESP32 (espressif32 ≥ 6.x) verwendet ESP-IDF 5.x und
initialisiert den TWDT bereits vor setup(). Daher muss beim Anpassen des
WDT-Timeouts esp_task_wdt_reconfigure() statt esp_task_wdt_init() verwendet
werden.
ArduinoJson-Regeln für ESP32-Projekte:
StaticJsonDocument<N>für N anhand des tatsächlichen JSON-Inhalts bemessen (ArduinoJson Assistant: https://arduinojson.org/v6/assistant/).- Den Serialisierungspuffer (
char buffer[]) mindestens 25 % größer als der erwartete maximale JSON-Output bemessen; bei optionalen Feldern mehr Reserve einplanen. - Niemals
StaticJsonDocument<1024>in einenchar buffer[512]serialisieren ohne korrekte Truncation-Prüfung (len >= sizeof(buffer) - 1 → return false). - Einmalig genutzte große Dokumente (> 1 KB) als function-local statt
file-scope
staticdeklarieren.
- Sicherheit: Secure Boot + Flash Encryption aktivieren, Debug-Schnittstellen deaktivieren.
- OTA: Partition-basierte Updates mit Anti-Rollback/Checksum.
- Speicher: Heap/Stack Überwachung & Static Buffer.
- ESP-IDF v5: WDT-API gebrochen —
esp_task_wdt_reconfigurestattesp_task_wdt_initverwenden wenn Arduino-Framework WDT vorinitialisiert hat. - JSON Discovery-Puffer: Serialisierungspuffer muss ausreichend Margin zum tatsächlichen JSON-Output haben, sonst schlägt HA-Discovery lautlos fehl.