From 0cbc4d0644d3b67cd21ed6555ba0fb08a460afbc Mon Sep 17 00:00:00 2001 From: simon Date: Sun, 31 May 2026 15:55:32 +0200 Subject: [PATCH] Add ESP firmware architecture and reference documentation. Document UART command flow, ESP-NOW data paths, and module map for developers without covering goTool; link from main/README. Co-authored-by: Cursor --- docs/ARCHITECTURE.md | 398 ++++++++++++++++++++++++++++++++++ docs/DOCUMENTATION.md | 481 ++++++++++++++++++++++++++++++++++++++++++ main/README.md | 2 + 3 files changed, 881 insertions(+) create mode 100644 docs/ARCHITECTURE.md create mode 100644 docs/DOCUMENTATION.md diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..d7c19b4 --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,398 @@ +# Powerpod — Architektur-Spezifikation + +Dieses Dokument beschreibt die Firmware-Architektur des ESP32-S3-Projekts: Rollen, Schichten, Datenflüsse und die wichtigsten Implementierungsstellen. **goTool** (Host-CLI) ist bewusst ausgeschlossen — es nutzt nur das UART-Protokoll des Masters. + +--- + +## 1. Systemüberblick + +Master und Slave laufen mit **demselben Binary**. Die Rolle wird beim Boot per DIP-Schalter und I2C-IO-Expander festgelegt; danach verzweigt die Initialisierung. + +```mermaid +flowchart TB + subgraph host["Externer Host (UART)"] + PC[PC / beliebiger UART-Client] + end + + subgraph master["Master ESP32-S3"] + UART_RX[uart_read_task] + Q[cmd_queue] + DISP[vCmdDispatcherTask] + HAND[cmd/*.c Handler] + REG[client_registry] + ENOW_M[esp_now_comm Master] + end + + subgraph slaves["Slave ESP32-S3 × N"] + ENOW_S[esp_now_comm Slave] + BMA[BMA456 + LED Ring] + end + + PC <-->|UART1 921600 framed + protobuf| UART_RX + UART_RX --> Q --> DISP --> HAND + HAND --> REG + HAND --> ENOW_M + ENOW_M <-->|ESP-NOW nanopb| ENOW_S + ENOW_S --> BMA + ENOW_S -->|Accel/Tap/Battery| ENOW_M + ENOW_M --> REG +``` + +| Rolle | UART | ESP-NOW | Zentrale Datenhaltung | +|-------|------|---------|------------------------| +| **Master** | Ja — einziger Befehlseingang von außen | Discover (Broadcast), Unicast zu Slaves | `client_registry.c` | +| **Slave** | Nein | Antwort auf Discover, Heartbeat, Events zum Master | Lokaler Zustand in `esp_now_comm.c` | + +**Einstieg:** `main/powerpod.c` → `app_main()`. + +--- + +## 2. Boot und Konfiguration + +### 2.1 Ablauf + +```mermaid +sequenceDiagram + participant AM as app_main + participant NVS as pod_settings + participant I2C as I2C / IO-Expander + participant BMA as bosch456 + participant EN as esp_now_comm + participant LR as led_ring + participant BI as board_input + participant CMD as cmd_handler + uart + + AM->>NVS: pod_settings_init() + AM->>AM: GPIO DIP_MASTER → master/slave + AM->>I2C: Bus + Expander 0x20 → network 1–8 + AM->>BMA: init_bma456() (Master + Slave) + AM->>AM: app_config_t füllen + AM->>BI: board_input_init() + AM->>EN: esp_now_comm_init(&app_config) + AM->>LR: led_ring_init() + alt master == true + AM->>CMD: Queue, Dispatcher, UART, Handler registrieren + end +``` + +### 2.2 `app_config_t` + +| Feld | Quelle | Bedeutung | +|------|--------|-----------| +| `master` | `DIP_MASTER` (GPIO 4): Low = Master | Steuert UART + Registry-Nutzung | +| `network` | IO-Expander, Bits 5–8 (nibble reversed) | 1–8 → WiFi/ESP-NOW-Kanal | +| `running_partition` | `esp_ota_get_running_partition()` | Aktives OTA-Label (`ota_0` / `ota_1`) | + +**Dateien:** `main/app_config.h`, `main/powerpod.c`, `main/powerpod.h` (Pins). + +--- + +## 3. Schichtenmodell + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Befehlshandler (main/cmd/cmd_*.c) │ +│ Decode/Encode: uart_cmd.c, Antwort: uart_proto.c │ +├─────────────────────────────────────────────────────────────┤ +│ Dispatch: cmd_handler.c (Queue + vCmdDispatcherTask) │ +├─────────────────────────────────────────────────────────────┤ +│ Transport UART: uart.c (Framing) │ ESP-NOW: esp_now_comm │ +├─────────────────────────────────────────────────────────────┤ +│ Protokoll: uart_messages.proto │ esp_now_messages.proto │ +│ Codec: nanopb (proto/*.pb.c) │ esp_now_proto.c │ +├─────────────────────────────────────────────────────────────┤ +│ Domäne: client_registry, bosch456, led_ring, ota_*, board │ +├─────────────────────────────────────────────────────────────┤ +│ ESP-IDF: UART, WiFi, ESP-NOW, I2C, ADC, OTA, NVS, FreeRTOS │ +└─────────────────────────────────────────────────────────────┘ +``` + +--- + +## 4. Datenfluss: Commands (Befehle) + +Commands sind **asynchron über eine FreeRTOS-Queue** entkoppelt; der UART-Reader blockiert nicht auf Handler-Logik. + +### 4.1 Eingang (UART → Handler) + +```mermaid +flowchart LR + A[Bytes UART1] --> B[parse_uart_byte] + B --> C{Frame vollständig?} + C -->|ja| D[uart_enqueue_packet] + D --> E["generic_msg_t
msg_id = payload[0]
payload = Rest"] + E --> F[cmd_queue xQueueSend] + F --> G[vCmdDispatcherTask] + G --> H{msg_register_handler} + H --> I[cmd_*.c callback] + I --> J[free payload] +``` + +**Wichtige Stellen:** + +| Schritt | Datei | Funktion | +|---------|--------|----------| +| Byte-Parser + Timeout 50 ms | `main/uart.c` | `parse_uart_byte()`, `uart_read_task()` | +| Queue-Eintrag | `main/uart.c` | `uart_enqueue_packet()` | +| Dispatcher | `main/cmd/cmd_handler.c` | `vCmdDispatcherTask()`, `msg_register_handler()` | +| Registrierung Master | `main/powerpod.c` | `cmd_*_register()` nach `init_uart()` | + +**Nachrichten-ID:** Byte 0 des UART-Payloads = `alox_MessageType` (siehe `main/proto/uart_messages.proto`). Bytes 1… = nanopb-kodiertes `UartMessage` **ohne** wiederholtes Type-Feld — Handler erhalten nur den protobuf-Teil (`msg.payload` ab Offset 1). + +### 4.2 Ausgang (Handler → UART) + +```mermaid +flowchart LR + H[Handler baut alox_UartMessage] --> U[uart_cmd_send] + U --> P[uart_send_uart_message] + P --> E[pb_encode + Byte0 = type] + E --> F[uart_send_framed] + F --> W[uart_write_bytes] +``` + +**Wichtige Stellen:** + +| Schritt | Datei | Funktion | +|---------|--------|----------| +| Handler-Hilfen | `main/uart_cmd.c` | `uart_cmd_decode()`, `uart_cmd_init_response()`, `uart_cmd_send()` | +| Protobuf + Framing | `main/uart_proto.c` | `uart_send_uart_message()` | +| XOR-Rahmen | `main/uart.c` | `uart_send_framed()` | + +### 4.3 Interner Befehlspost (ohne UART) + +`msg_post(id, data, len)` in `cmd_handler.c` legt dieselbe `generic_msg_t`-Struktur in `cmd_queue` — für zukünftige Firmware-interne Quellen (z. B. ESP-NOW → PC-Pfad). Aktuell ist **UART der einzige Produktiv-Eingang**. + +### 4.4 Bridge-Muster: UART-Befehl → ESP-NOW + +Viele Master-Handler folgen demselben Muster: + +1. `uart_cmd_decode()` → Request aus `UartMessage` +2. Lokale Aktion und/oder `client_registry_*` aktualisieren +3. `esp_now_comm_send_*()` mit MAC aus Registry +4. `uart_cmd_send()` mit Response + +```mermaid +sequenceDiagram + participant Host + participant UART as uart.c + participant CMD as cmd_accel_deadzone.c + participant REG as client_registry + participant EN as esp_now_comm.c + participant SL as Slave + + Host->>UART: Frame ACCEL_DEADZONE + UART->>CMD: generic_msg_t + CMD->>REG: set_accel_deadzone / lookup MAC + CMD->>EN: esp_now_comm_send_accel_deadzone + EN->>SL: ESPNOW_SET_ACCEL_DEADZONE + SL->>SL: bma456 + NVS + CMD->>Host: uart_cmd_send Response +``` + +**Beispiel-Implementierung:** `main/cmd/cmd_accel_deadzone.c` +**Weitere Bridge-Handler:** `cmd_accel_stream.c`, `cmd_tap_notify.c`, `cmd_led_ring.c`, `cmd_espnow_find_me.c`, `cmd_restart.c`, `cmd_espnow_unicast_test.c`, `cmd/cmd_ota.c` (OTA + `ota_espnow.c`). + +**Nur Master / nur Cache (kein Slave-Roundtrip):** `cmd_client_info.c`, `cmd_battery.c`, `cmd_cache_status.c`, `cmd_version.c`. + +--- + +## 5. Datenfluss: UART + +### 5.1 Rahmenformat (Transport) + +Unabhängig von Protobuf — reines Bytestream-Framing: + +| Feld | Wert | +|------|------| +| Start | `0xAA` | +| Länge | 1 Byte (1–252) | +| Payload | `length` Bytes | +| Prüfsumme | XOR aller Payload-Bytes | +| Stopp | `0xCC` | + +**Parameter:** `main/uart.h` — `UART_NUM_1`, `921600` Baud, TX GPIO **2**, RX GPIO **3**, `MAX_PAYLOAD_SIZE` 248. + +### 5.2 Nutzlast (Anwendung) + +``` +Payload[0] = MessageType (enum, 1 Byte) +Payload[1…] = nanopb UartMessage (Felder ab type/payload oneof) +``` + +**Schema:** `main/proto/uart_messages.proto` +**Generiert:** `main/proto/uart_messages.pb.c/h` (`make proto_generate_uart`) + +### 5.3 Tasks und Prioritäten + +| Task | Stack | Priorität | Datei | +|------|-------|-----------|--------| +| `uart_rx` | 4096 | 5 | `uart.c` | +| `cmd_dispatch` | 8192 | 5 | `cmd_handler.c` | + +Queue-Größe Master: **64** Einträge (`powerpod.c`); volle Queue → Warnung, Payload wird freigegeben. + +--- + +## 6. Datenfluss: ESP-NOW + +### 6.1 Stack-Initialisierung + +`esp_now_comm_init()` (`main/esp_now_comm.c`): + +1. `client_registry_init()` +2. WiFi STA, Kanal = `network` (1–13) +3. `esp_now_init()`, `esp_now_register_recv_cb(espnow_recv_cb)` +4. **Master:** Broadcast-Peer, Tasks `espnow_disc` (500 ms), `espnow_mon` (1 s) +5. **Slave:** `slave_tx_task`, `slave_heartbeat_task`, `slave_accel_stream_task` (16 ms) + +**Codec:** Rohes Paket = ein nanopb `EspNowMessage` — **kein** zusätzliches Framing (`main/esp_now_proto.c`). + +### 6.2 Discovery und Join (Slave) + +```mermaid +sequenceDiagram + participant M as Master espnow_disc + participant S as Slave recv_cb + participant TX as slave_tx_task + + loop alle 500 ms + M->>M: ESPNOW_DISCOVER → FF:FF:…:FF + end + S->>S: handle_discover (network match) + S->>S: s_slave_joined, s_master_mac + Note over S,TX: Kein Send aus recv_cb! + S->>TX: SLAVE_TX_SLAVE_INFO + TX->>M: ESPNOW_SLAVE_INFO + S->>TX: SLAVE_TX_BATTERY + TX->>M: ESPNOW_BATTERY_REPORT + loop alle 1 s + S->>M: ESPNOW_HEARTBEAT + end +``` + +**Registry-Schlüssel:** immer `recv_info.src_addr` (WiFi-MAC des Senders), **nicht** optionales `mac`-Feld in der Protobuf-Nachricht. + +**Slave-ID:** `mac[5]` (letztes Oktett) — kann kollidieren; eindeutig ist die volle MAC in der Registry. + +**Master-Verlust:** Slave setzt Join zurück, wenn **5 s** kein Discover vom gleichen Master (`SLAVE_MASTER_LOST_MS`). + +### 6.3 Master → Slave (Unicast) + +Alle Master-Sends laufen über `send_message()` / `send_message_ex()`: + +1. `esp_now_proto_encode()` +2. `ensure_peer(dest_mac)` +3. `esp_now_send()` + +Öffentliche API: `main/esp_now_comm.h` (`esp_now_comm_send_*`). + +**Empfang Slave:** `espnow_recv_cb()` → Switch auf `which_payload` → Handler (`handle_slave_*`). + +### 6.4 Slave → Master (Events) + +| Nachricht | Task / Trigger | Master-Ziel | +|-----------|----------------|-------------| +| `ESPNOW_ACCEL_SAMPLE` | `slave_accel_stream_task` 16 ms | `client_registry_update_accel()` | +| `ESPNOW_TAP_EVENT` | BMA456-IRQ → `on_bma456_tap` | `client_registry_update_tap()` | +| `ESPNOW_BATTERY_REPORT` | Heartbeat + nach Join | `client_registry_update_battery()` | +| `ESPNOW_SLAVE_INFO` / `HEARTBEAT` | `slave_tx_task` / Heartbeat-Task | `client_registry_heartbeat()` | +| `ESPNOW_OTA_STATUS` | `ota_espnow_slave_*` | `ota_espnow_master_on_status()` | + +**Master recv:** `espnow_recv_cb()` — Presence, Accel, Tap, Battery, OTA-Status. + +### 6.5 OTA über ESP-NOW + +Nach erfolgreichem UART-OTA auf dem Master (oder `OTA_START_ESPNOW`): `ota_espnow.c` liest gestagte Partition, sendet `OTA_START` / `PAYLOAD` (≤200 B, `send_message_ex` mit ACK-Semaphore) / `OTA_END` an alle **available** Slaves. + +Gemeinsamer Flash-Puffer: `ota_uart.c` (4 KiB Blockgröße). + +--- + +## 7. Client Registry (Master-Datenhub) + +Die Registry ist die **zentrale Brücke** zwischen ESP-NOW-Echtzeitdaten und UART-Abfragen. + +```mermaid +flowchart TB + EN_RX[espnow_recv_cb] --> REG[client_registry] + CMD_R[cmd_client_info / battery / cache_status] --> REG + CMD_W[cmd_* write paths] --> REG + REG --> CMD_R + EN_TX[esp_now_comm_send_*] --> MAC[MAC aus Registry] +``` + +| Daten | Aktualisiert durch | Gelesen durch UART | +|-------|-------------------|-------------------| +| Presence, Version | HEARTBEAT / SLAVE_INFO | `CLIENT_INFO` | +| Accel-Stream-Samples | `ESPNOW_ACCEL_SAMPLE` | `CACHE_STATUS` | +| Tap-Events (16 ms Cache) | `ESPNOW_TAP_EVENT` | `CACHE_STATUS` | +| Batterie | `ESPNOW_BATTERY_REPORT` + Master-ADC | `BATTERY_STATUS` | +| Konfig-Flags | Handler + ESP-NOW | `CLIENT_INFO`, diverse GET | + +**Datei:** `main/client_registry.c`, `main/client_registry.h` (max. 16 Clients, Timeout 3 s ohne Heartbeat). + +--- + +## 8. FreeRTOS-Tasks (Übersicht) + +| Task | Rolle | Intervall / Trigger | +|------|-------|---------------------| +| `uart_rx` | Master | UART read 20 ms timeout | +| `cmd_dispatch` | Master | Queue blocking | +| `espnow_disc` | Master | 500 ms Discover | +| `espnow_mon` | Master | 1 s Timeout + Master-Battery | +| `espnow_stx` | Slave | Queue: SLAVE_INFO, Battery nach Join | +| `espnow_hb` | Slave | 1 s Heartbeat + 30 s Battery | +| `espnow_accel` | Slave | 16 ms Accel wenn Stream an | +| `bma456_poll` | Beide | 10 Hz (`bosch456.c`) | +| `vTaskLedRing` | Beide | LED-Ring-Befehle | + +`app_main` endet in `while(1) vTaskDelay(portMAX_DELAY)` — alle Arbeit läuft in Tasks. + +--- + +## 9. Implementierungskarte (wichtige Dateien) + +| Bereich | Pfad | Verantwortung | +|---------|------|----------------| +| Einstieg / Init | `main/powerpod.c` | Boot-Reihenfolge, Master-Handler-Register | +| UART Transport | `main/uart.c`, `main/uart.h` | Framing RX/TX | +| UART Protobuf TX | `main/uart_proto.c` | `uart_send_uart_message` | +| UART Handler-Boilerplate | `main/uart_cmd.c`, `main/uart_cmd.h` | Decode, Register, Send | +| Command Dispatch | `main/cmd/cmd_handler.c` | Queue, Dispatcher | +| UART Commands | `main/cmd/cmd_*.c` | Pro Befehl ein Modul | +| ESP-NOW Kern | `main/esp_now_comm.c`, `.h` | WiFi, Tasks, recv/send, Slave-Join | +| ESP-NOW Codec | `main/esp_now_proto.c` | nanopb encode/decode | +| Registry | `main/client_registry.c` | Slave-Tabelle + Caches | +| BMA456 | `main/bosch456.c` | Sensor, Tap, Deadzone-Filter | +| LED | `main/led_ring.c` | 95 LEDs, Digit-Maps | +| OTA UART | `main/ota_uart.c`, `cmd/cmd_ota.c` | A/B-Partition Upload | +| OTA ESP-NOW | `main/ota_espnow.c` | Verteilung an Slaves | +| Einstellungen NVS | `main/pod_settings.c` | Accel-Deadzone persistent | +| Board | `main/board_input.c` | Taster, LiPo-ADC | +| Protobuf Schemas | `main/proto/*.proto` | Vertrag UART / ESP-NOW | +| Vendor BMA456 | `components/bma456/` | Nur `bma4.c` + `bma456h.c` gelinkt | + +--- + +## 10. Protokoll-Verträge (Kurzreferenz) + +- **UART:** `main/proto/uart_messages.proto` — Host ↔ Master +- **ESP-NOW:** `main/proto/esp_now_messages.proto` — Master ↔ Slave + +Regenerierung: `make proto_generate` (siehe `DOCUMENTATION.md`). + +--- + +## 11. Design-Entscheidungen + +1. **Eine Queue für alle Commands** — einheitliches Modell für UART und künftige interne Quellen. +2. **Kein ESP-NOW-Send im `recv_cb`** — Slave antwortet auf Discover über `slave_tx_task` (Deadlock-/Stack-Risiko vermeiden). +3. **Registry-MAC = ESP-NOW-Quelladresse** — Protobuf-MAC ist optional/informativ. +4. **Gleiches Binary** — Konfiguration nur Hardware (DIP + Expander); reduziert Release-Komplexität. +5. **Nanopb statt vollem protobuf-c** — passend für ESP-RAM/Flash. +6. **Master aggregiert Sensor-Daten** — Slaves streamen; Host pollt Cache per UART (`CACHE_STATUS`), kein Durchreichen jedes Accel-Samples über UART. + +--- + +Weitere Details (Befehlsliste, GPIO, Build, BMA456): [`DOCUMENTATION.md`](DOCUMENTATION.md). diff --git a/docs/DOCUMENTATION.md b/docs/DOCUMENTATION.md new file mode 100644 index 0000000..e2853a3 --- /dev/null +++ b/docs/DOCUMENTATION.md @@ -0,0 +1,481 @@ +# Powerpod ESP-Firmware — Dokumentation + +Vollständige Referenz für das ESP-IDF-Projekt unter `main/` und `components/`. Diese Doku beschreibt **nur die Firmware** — kein goTool, keine Host-Implementierung. + +- **Architektur & Datenflüsse:** [`ARCHITECTURE.md`](ARCHITECTURE.md) +- **Neues Feature end-to-end:** [`adding-a-feature.md`](adding-a-feature.md) + +--- + +## Inhaltsverzeichnis + +1. [Projektziel](#1-projektziel) +2. [Hardware und Pins](#2-hardware-und-pins) +3. [Build und Flash](#3-build-und-flash) +4. [Boot-Konfiguration](#4-boot-konfiguration) +5. [UART-Protokoll](#5-uart-protokoll) +6. [ESP-NOW-Protokoll](#6-esp-now-protokoll) +7. [Befehlsreferenz (UART)](#7-befehlsreferenz-uart) +8. [OTA](#8-ota) +9. [BMA456 Beschleunigungssensor](#9-bma456-beschleunigungssensor) +10. [LED-Ring](#10-led-ring) +11. [Board-Input und Batterie](#11-board-input-und-batterie) +12. [Persistenz (NVS)](#12-persistenz-nvs) +13. [Protobuf und Code-Generierung](#13-protobuf-und-code-generierung) +14. [Modulreferenz](#14-modulreferenz) +15. [Logging-Tags](#15-logging-tags) + +--- + +## 1. Projektziel + +**Powerpod** ist Firmware für ESP32-S3-Knoten in einem verteilten System: + +- Ein **Master** spricht per **UART** mit einem externen Host und verwaltet bis zu **16 Slaves** über **ESP-NOW**. +- **Slaves** haben keinen UART-Befehlspfad; sie joinen über periodisches **Discover**, senden Heartbeats und liefern Sensor-/Statusdaten. +- Master und Slave nutzen **identisches Firmware-Image**; Rolle und Funknetz werden beim Boot erkannt. + +Zielbild für den Host: Befehle an den Master senden; der Master steuert Slaves und **cached** deren Telemetrie (`client_registry`) für schnelle UART-Abfragen. + +--- + +## 2. Hardware und Pins + +> Pinbelegung ist in `powerpod.h` als vorläufig markiert — mit Schaltplan abgleichen. + +| Signal | GPIO | Datei / Modul | +|--------|------|----------------| +| DIP Master/Slave | 4 | `powerpod.h` — Low = Master | +| I2C SCL / SDA | 5 / 6 | IO-Expander `0x20`, BMA456 `0x18` | +| UART1 TX / RX | **2 / 3** | `uart.h` (Adapter: ESP-TX → Host-RX) | +| LED-Ring (WS2812) | 7 | `led_ring.c` | +| BMA456 Interrupt | 10 | `bosch456.c` | +| Taster | 12 | `board_input.c` | +| LiPo ADC 1 | 1 | `board_input.c` | +| LiPo ADC 2 | 12 | Entfällt wenn = Taster-GPIO | + +**UART:** `UART_NUM_1`, **921600** Baud, 8N1, kein Flow-Control. + +**I2C:** 100 kHz, interne Pull-ups, gemeinsamer Bus für Expander und BMA456H. + +--- + +## 3. Build und Flash + +**Ziel-Chip:** ESP32-S3 (ESP-IDF). + +```bash +source ~/esp/esp-idf/export.sh # oder export.fish +cd /pfad/zu/powerpod +idf.py build +idf.py -p /dev/ttyUSB0 flash monitor +``` + +- **Git-Hash** wird beim Build in `POWERPOD_GIT_HASH` eingebettet (`main/CMakeLists.txt`). +- **Firmware-Version:** `POWERPOD_FW_VERSION` (Default `1` in `esp_now_comm.c` / `cmd_version.c`). + +**Komponenten:** + +| Pfad | Inhalt | +|------|--------| +| `main/` | Anwendungslogik | +| `components/bma456/` | Bosch BMA456H-Treiber (Vendor) | +| `libs/nanopb/` | Protobuf-Codec für Embedded | + +--- + +## 4. Boot-Konfiguration + +### 4.1 Initialisierungsreihenfolge (`powerpod.c`) + +1. `pod_settings_init()` — NVS +2. DIP → `app_config.master` +3. I2C + IO-Expander → `app_config.network` (1–8) +4. `init_bma456()` — optional, bei Fehler weiter ohne Sensor +5. `pod_settings_apply_accel_deadzone()` wenn Sensor da +6. OTA-Partition → `app_config.running_partition` +7. `board_input_init()` +8. `esp_now_comm_init(&app_config)` — **immer** (Master + Slave) +9. `led_ring_init()` +10. **Nur Master:** `cmd_queue` → `init_cmdHandler` → `init_uart` → alle `cmd_*_register()` + +### 4.2 Master vs. Slave + +| Funktion | Master | Slave | +|----------|--------|-------| +| UART Command-Handler | Ja | Nein | +| ESP-NOW Discover senden | Ja (Broadcast) | Nein | +| ESP-NOW auf Discover reagieren | Nein | Ja | +| `client_registry` für Fremdknoten | Ja | Nein (nur eigener Join-State) | + +--- + +## 5. UART-Protokoll + +*(Transport + Anwendung — Datenfluss-Diagramme in [`ARCHITECTURE.md`](ARCHITECTURE.md) §4–5.)* + +### 5.1 Rahmen + +| Byte | Inhalt | +|------|--------| +| 0 | `0xAA` Start | +| 1 | Länge N (1–252) | +| 2…N+1 | Payload | +| N+2 | XOR-Checksum über Payload | +| N+3 | `0xCC` Stopp | + +Implementierung: `uart.c` — `parse_uart_byte()`, `uart_send_framed()`. + +### 5.2 Anwendungs-Payload + +| Offset | Bedeutung | +|--------|-----------| +| 0 | `MessageType` (siehe Enum in `uart_messages.proto`) | +| 1… | nanopb `UartMessage` (ohne separates type-Feld im protobuf-Teil) | + +**Antworten** nutzen dieselbe Struktur: Byte 0 = Response-Typ, Rest = kodierte `UartMessage`. + +### 5.3 Handler-Pipeline + +1. `uart_read_task` parst Frames → `uart_enqueue_packet` +2. `generic_msg_t` → `cmd_queue` +3. `vCmdDispatcherTask` → registrierter `msg_callback_t` +4. Handler: `uart_cmd_decode(data, len, &msg)` — `data` ist **nur** protobuf-Teil +5. Antwort: `uart_cmd_init_response()` + Felder setzen + `uart_cmd_send()` + +Hilfsmakro für Request-Felder: + +```c +UART_CMD_REQ(&uart_msg, alox_UartMessage_accel_deadzone_request_tag, accel_deadzone_request) +``` + +--- + +## 6. ESP-NOW-Protokoll + +*(Join, Tasks, Registry — [`ARCHITECTURE.md`](ARCHITECTURE.md) §6.)* + +### 6.1 Grundlagen + +- WiFi **STA**, keine AP-Verbindung. +- **Kanal** = `app_config.network` (1–13). +- Payload = ein `EspNowMessage` (nanopb), max. `ESP_NOW_MAX_DATA_LEN`. +- Schema: `main/proto/esp_now_messages.proto`. + +### 6.2 Nachrichtentypen + +| Type | Richtung | Payload | Zweck | +|------|----------|---------|--------| +| `ESPNOW_DISCOVER` | M→Broadcast | `discover.network` | Slaves finden Master | +| `ESPNOW_SLAVE_INFO` | S→M | `slave_info` | Erste Registrierung | +| `ESPNOW_HEARTBEAT` | S→M | `heartbeat` | Keepalive 1 s | +| `ESPNOW_SET_ACCEL_DEADZONE` | M→S | `accel_deadzone` | Deadzone LSB | +| `ESPNOW_SET_ACCEL_STREAM` | M→S | `accel_stream` | Stream ~16 ms | +| `ESPNOW_ACCEL_SAMPLE` | S→M | `accel_sample` | x/y/z Roh-LSB | +| `ESPNOW_SET_TAP_NOTIFY` | M→S | `tap_notify` | Tap-Arten filtern | +| `ESPNOW_TAP_EVENT` | S→M | `tap_event` | kind 1/2/3 | +| `ESPNOW_BATTERY_QUERY` | M→S | `battery_query` | On-demand | +| `ESPNOW_BATTERY_REPORT` | S→M | `battery_report` | mV LiPo 1/2 | +| `ESPNOW_LED_RING` | M→S | `led_ring` | Wie UART LED_RING | +| `ESPNOW_FIND_ME` | M→S | `find_me` | LED-Locate | +| `ESPNOW_RESTART` | M→S | `restart` | Reboot Slave | +| `ESPNOW_UNICAST_TEST` | M→S | `unicast_test` | Link-Test | +| `ESPNOW_OTA_*` | M↔S | `ota_*` | Firmware-Verteilung | + +### 6.3 Zeitkonstanten (`esp_now_comm.c`) + +| Konstante | Wert | +|-----------|------| +| Discover-Intervall | 500 ms | +| Heartbeat | 1000 ms | +| Client-Timeout (Master) | 3 × Heartbeat = 3 s → `available=false` | +| Master-Verlust (Slave) | 5 s ohne Discover | +| Accel-Stream | 16 ms | +| Batterie-Report | 30 s (+ einmal 150 ms nach Join) | + +### 6.4 `EspNowSlavePresence` + +Felder: `network`, `mac` (6 B), `version`, `slave_id`, `available`, `used`. + +- `slave_id` = letztes Oktett der STA-MAC. +- Registry auf dem Master indexiert über **Sender-MAC** der ESP-NOW-Callback. + +--- + +## 7. Befehlsreferenz (UART) + +Nur auf dem **Master** registriert (`powerpod.c`). IDs aus `MessageType` in `uart_messages.proto`. + +| ID | Name | Modul | Kurzbeschreibung | +|----|------|-------|------------------| +| 1 | ACK | — | Reserviert | +| 2 | ECHO | — | Reserviert | +| 3 | VERSION | `cmd_version.c` | FW-Version, Git-Hash, OTA-Partition | +| 4 | CLIENT_INFO | `cmd_client_info.c` | Liste `client_registry` | +| 5 | CLIENT_INPUT | — | Geplant | +| 6 | ACCEL_DEADZONE | `cmd_accel_deadzone.c` | Get/Set Deadzone lokal + ESP-NOW | +| 7 | ESPNOW_UNICAST_TEST | `cmd_espnow_unicast_test.c` | Link-Test zu Slave | +| 8 | LED_RING | `cmd_led_ring.c` | Ring lokal / ESP-NOW | +| 16 | OTA_START | `cmd_ota.c` | UART-OTA beginnen | +| 17 | OTA_PAYLOAD | `cmd_ota.c` | Chunk ≤200 B | +| 18 | OTA_END | `cmd_ota.c` | Abschluss + ESP-NOW-Verteilung | +| 19 | OTA_STATUS | `cmd_ota.c` | Gerät → Host Status | +| 20 | OTA_START_ESPNOW | `cmd_ota.c` | Nur ESP-NOW aus Staging | +| 21 | OTA_SLAVE_PROGRESS | `cmd_ota_slave_progress.c` | Fortschritt pro Slave | +| 22 | FIND_ME | `cmd_espnow_find_me.c` | LED Locate | +| 23 | RESTART | `cmd_restart.c` | Master oder Slave reboot | +| 25 | ACCEL_STREAM | `cmd_accel_stream.c` | ESP-NOW Accel-Stream an/aus | +| 26 | BATTERY_STATUS | `cmd_battery.c` | Cache LiPo Master + Slaves | +| 27 | TAP_NOTIFY | `cmd_tap_notify.c` | Tap-Weiterleitung konfigurieren | +| 29 | CACHE_STATUS | `cmd_cache_status.c` | Accel + Tap Cache (ein Round-Trip) | + +### 7.1 VERSION (3) + +- **Request:** nur Typ-Byte `0x03` oder leerer protobuf-Body. +- **Response:** `version`, `git_hash`, `running_partition`. + +### 7.2 CLIENT_INFO (4) + +- **Response:** wiederholtes `ClientInfo` pro Registry-Eintrag: `id`, `mac`, `version`, `available`, `used`, `last_ping`, `last_success_ping`, Tap-/Stream-Flags. + +### 7.3 ACCEL_DEADZONE (6) + +- **Request:** `write`, `deadzone`, `client_id` (0=lokal), `all_clients`. +- **Write Master:** Registry + `esp_now_comm_send_accel_deadzone` pro Slave; lokal `bma456` + NVS. +- **Slave-Empfang:** `handle_slave_accel_deadzone` in `esp_now_comm.c`. + +### 7.4 ACCEL_STREAM (25) + +- Master setzt `client_registry_set_accel_stream` und sendet `ESPNOW_SET_ACCEL_STREAM`. +- Slave-Task `slave_accel_stream_task` sendet `ESPNOW_ACCEL_SAMPLE` alle 16 ms. + +### 7.5 TAP_NOTIFY (27) + +- Konfiguriert auf Slave welche Tap-Arten `ESPNOW_TAP_EVENT` auslösen. +- Tap-Quelle: BMA456-Interrupt → `on_bma456_tap` in `esp_now_comm.c`. + +### 7.6 CACHE_STATUS (29) + +- **Request:** leer. +- **Response:** pro Slave mit Stream und/oder Tap-Notify: gecachte Accel-Werte (`age_ms`) und konsumierte Tap-Events (`client_registry_take_tap`). + +### 7.7 BATTERY_STATUS (26) + +- Liest **nur Cache** — keine ESP-NOW-Roundtrip-Pflicht pro Abfrage. +- Master-Batterie: `client_registry_set_master_battery` (Monitor-Task). + +### 7.8 LED_RING (8) + +| mode | Bedeutung | +|------|-----------| +| 0 | Clear | +| 1 | Progress 0–100 % | +| 2 | Digit 0–10 | +| 3 | Blink | +| 4 | Find-me (RGB-Sequenz) | +| 5 | Solid color | + +`client_id=0` nur Master-Ring; `>0` ESP-NOW; `all_clients` Broadcast über Registry. + +### 7.9 FIND_ME (22) / RESTART (23) + +- `client_id=0`: lokaler Master (`led_ring_find_me` / `pod_schedule_restart`). +- `client_id>0`: ESP-NOW Unicast zum Slave. + +### 7.10 ESPNOW_UNICAST_TEST (7) + +Minimaler Master→Slave-Ping; Slave loggt `UNICAST TEST OK`. + +--- + +## 8. OTA + +### 8.1 UART-OTA (nur Master) + +Implementierung: `ota_uart.c`, Steuerung `cmd/cmd_ota.c`. + +| Phase | Host → Master | Master → Host | +|-------|---------------|---------------| +| Start | `OTA_START` + `total_size` | `OTA_STATUS` preparing → ready | +| Daten | `OTA_PAYLOAD` ≤200 B, `seq` | `block_ack` je 4096 B Flash | +| Ende | `OTA_END` | success/failed; startet ESP-NOW-Verteilung | + +- Inaktive Partition: `esp_ota_get_next_update_partition()`. +- LED-Ring zeigt Fortschritt (blau Schreiben, grün Verteilung). + +### 8.2 ESP-NOW-OTA (Master → Slaves) + +`ota_espnow.c` — gleiche Status-Codes wie UART. + +1. `ESPNOW_OTA_START` + `total_size` → Slave `ota_espnow_slave_on_start` +2. `ESPNOW_OTA_PAYLOAD` bis 200 B → 4 KiB Buffer auf Slave +3. `ESPNOW_OTA_END` → Boot-Partition setzen +4. Slave antwortet `ESPNOW_OTA_STATUS` (mit `send_message_ex` + Semaphore auf Master) + +Nach Erfolg: **alle Knoten neu starten**. + +### 8.3 OTA_SLAVE_PROGRESS (21) + +Abfrage laufender oder letzter ESP-NOW-Verteilung: pro Slave `bytes_written`, `status`, `error`. + +--- + +## 9. BMA456 Beschleunigungssensor + +| Thema | Detail | +|-------|--------| +| Wrapper | `bosch456.c` / `bosch456.h` | +| Chip-Variante | BMA456**H** (`bma456h.c` im Component) | +| I2C | Adresse 0x18, 100 kHz | +| Polling | Task ~10 Hz | +| Interrupt GPIO 10 | Single/Double/Triple Tap | +| Deadzone | Software-Filter für Logs/Stream (Default 100 LSB) | +| API | `bma456_read_accel`, `bma456_set_accel_deadzone`, `bma456_set_tap_handler` | + +Ohne Sensor: `bma456_is_ready() == false`, Firmware läuft weiter. + +--- + +## 10. LED-Ring + +- **95 LEDs**, WS2812 über RMT (`led_ring.c`). +- Segment-Karten für Ziffern 0–10 in `digit_lookup[]`. +- **Kein** lokaler Demo-Loop — Anzeige nur über UART (`cmd_led_ring.c`) oder ESP-NOW `ESPNOW_LED_RING`. +- Helligkeit: `intensity` 0 → Default ~5 % (`LED_RING_DEFAULT_INTENSITY`). + +--- + +## 11. Board-Input und Batterie + +`board_input.c`: + +- **Taster** GPIO 12 — Logging bei Druck. +- **LiPo-ADC** GPIO 1 (und optional 2, wenn nicht Taster). +- Master: `master_monitor_task` aktualisiert Master-Batterie alle 30 s. +- Slave: `slave_send_battery_report_to_master` nach Join, Heartbeat und Query. + +Spannungen in Millivolt in Registry und `BATTERY_STATUS`-UART. + +--- + +## 12. Persistenz (NVS) + +`pod_settings.c` — Namespace `powerpod`: + +| Key | Inhalt | +|-----|--------| +| `accel_dz` | Accel-Deadzone LSB | + +Gespeichert bei lokalem Set (UART `client_id=0`, ESP-NOW auf Slave). Geladen nach `init_bma456()`. + +--- + +## 13. Protobuf und Code-Generierung + +```bash +make proto_generate # beide Schemas +make proto_generate_uart # nur uart_messages.proto +make proto_generate_espnow # nur esp_now_messages.proto +``` + +**Ausgabe:** `main/proto/*.pb.c`, `*.pb.h` +**Optionen:** `main/proto/uart_messages.options` (nanopb max_size etc.) + +Includes in generierten `.pb.c` müssen `"uart_messages.pb.h"` heißen (nicht `main/proto/...`). + +**Paketname protobuf:** `alox` → C-Prefix `alox_`. + +--- + +## 14. Modulreferenz + +### 14.1 Kern + +| Datei | Rolle | +|-------|--------| +| `powerpod.c` | `app_main`, Init, Master-Register | +| `app_config.h` | Laufzeit-Konfiguration | +| `cmd_handler.c` | Queue + Dispatcher | +| `uart.c` | Framing | +| `uart_proto.c` | UartMessage send | +| `uart_cmd.c` | Handler-Hilfen | + +### 14.2 Kommunikation + +| Datei | Rolle | +|-------|--------| +| `esp_now_comm.c` | WiFi, ESP-NOW, alle Rollen-Tasks | +| `esp_now_proto.c` | nanopb für EspNowMessage | +| `client_registry.c` | Slave-Tabelle + Telemetrie-Cache | + +### 14.3 Befehle (`main/cmd/`) + +| Datei | UART-Typ | +|-------|----------| +| `cmd_version.c` | VERSION | +| `cmd_client_info.c` | CLIENT_INFO | +| `cmd_accel_deadzone.c` | ACCEL_DEADZONE | +| `cmd_accel_stream.c` | ACCEL_STREAM | +| `cmd_tap_notify.c` | TAP_NOTIFY | +| `cmd_cache_status.c` | CACHE_STATUS | +| `cmd_battery.c` | BATTERY_STATUS | +| `cmd_led_ring.c` | LED_RING | +| `cmd_espnow_find_me.c` | FIND_ME | +| `cmd_restart.c` | RESTART | +| `cmd_espnow_unicast_test.c` | ESPNOW_UNICAST_TEST | +| `cmd_ota.c` | OTA_* | +| `cmd_ota_slave_progress.c` | OTA_SLAVE_PROGRESS | + +### 14.4 Domäne + +| Datei | Rolle | +|-------|--------| +| `bosch456.c` | Accelerometer + Tap | +| `led_ring.c` | LED-Anzeige | +| `board_input.c` | Taster, ADC | +| `pod_settings.c` | NVS | +| `pod_reboot.c` | Verzögerter Restart | +| `ota_uart.c` | Flash-Puffer UART-OTA | +| `ota_espnow.c` | OTA an Slaves | + +### 14.5 Protobuf + +| Datei | Rolle | +|-------|--------| +| `proto/uart_messages.proto` | UART-Vertrag | +| `proto/esp_now_messages.proto` | ESP-NOW-Vertrag | +| `proto/pb_encode.c`, `pb_decode.c`, `pb_common.c` | nanopb Runtime | + +--- + +## 15. Logging-Tags + +| Tag | Modul | +|-----|--------| +| `[Main]` | powerpod.c | +| `[UART]` | uart.c | +| `[CMDH]` | cmd_handler.c | +| `[UART_CMD]` | uart_cmd.c | +| `[ESPNOW]` | esp_now_comm.c | +| `[CLIENTS]` | client_registry.c | +| `[BMA456]` | bosch456.c | +| `[VERSION]` etc. | jeweiliger cmd_* | + +Typischer Ablauf bei UART-Befehl: + +``` +[UART] received message cmd=0x06 len=… +[CMDH] trigger command ACCEL_DEADZONE (0x06) +[ACCEL_DZ] … +``` + +--- + +## Anhang: Neues UART-Kommando hinzufügen + +1. `uart_messages.proto` erweitern → `make proto_generate_uart` +2. `cmd/cmd_neu.c` mit `handle_*` + `uart_cmd_register()` +3. In `powerpod.c` `cmd_neu_register()` aufrufen (nur Master-Zweig) +4. Bei Slave-Bedarf: `esp_now_messages.proto` + `esp_now_comm_send_*` + Slave-Zweig in `espnow_recv_cb` + +Siehe [`adding-a-feature.md`](adding-a-feature.md) für ein vollständiges Beispiel (Find Me). diff --git a/main/README.md b/main/README.md index 9dee202..9602156 100644 --- a/main/README.md +++ b/main/README.md @@ -2,6 +2,8 @@ ESP32-S3 firmware for Powerpod nodes. Master and slave devices run the **same binary**; role and ESP-NOW network are selected at boot via DIP switches and an I2C IO expander. +**Architektur & ESP-only Doku (ohne goTool):** [`../docs/ARCHITECTURE.md`](../docs/ARCHITECTURE.md) (Datenflüsse UART/Commands/ESP-NOW), [`../docs/DOCUMENTATION.md`](../docs/DOCUMENTATION.md) (vollständige Referenz). + ## System overview ```