Document UART command flow, ESP-NOW data paths, and module map for developers without covering goTool; link from main/README. Co-authored-by: Cursor <cursoragent@cursor.com>
16 KiB
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 - Neues Feature end-to-end:
adding-a-feature.md
Inhaltsverzeichnis
- Projektziel
- Hardware und Pins
- Build und Flash
- Boot-Konfiguration
- UART-Protokoll
- ESP-NOW-Protokoll
- Befehlsreferenz (UART)
- OTA
- BMA456 Beschleunigungssensor
- LED-Ring
- Board-Input und Batterie
- Persistenz (NVS)
- Protobuf und Code-Generierung
- Modulreferenz
- 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.hals 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).
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_HASHeingebettet (main/CMakeLists.txt). - Firmware-Version:
POWERPOD_FW_VERSION(Default1inesp_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)
pod_settings_init()— NVS- DIP →
app_config.master - I2C + IO-Expander →
app_config.network(1–8) init_bma456()— optional, bei Fehler weiter ohne Sensorpod_settings_apply_accel_deadzone()wenn Sensor da- OTA-Partition →
app_config.running_partition board_input_init()esp_now_comm_init(&app_config)— immer (Master + Slave)led_ring_init()- Nur Master:
cmd_queue→init_cmdHandler→init_uart→ allecmd_*_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 §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
uart_read_taskparst Frames →uart_enqueue_packetgeneric_msg_t→cmd_queuevCmdDispatcherTask→ registriertermsg_callback_t- Handler:
uart_cmd_decode(data, len, &msg)—dataist nur protobuf-Teil - Antwort:
uart_cmd_init_response()+ Felder setzen +uart_cmd_send()
Hilfsmakro für Request-Felder:
UART_CMD_REQ(&uart_msg, alox_UartMessage_accel_deadzone_request_tag, accel_deadzone_request)
6. ESP-NOW-Protokoll
(Join, Tasks, Registry — 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
0x03oder leerer protobuf-Body. - Response:
version,git_hash,running_partition.
7.2 CLIENT_INFO (4)
- Response: wiederholtes
ClientInfopro 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_deadzonepro Slave; lokalbma456+ NVS. - Slave-Empfang:
handle_slave_accel_deadzoneinesp_now_comm.c.
7.4 ACCEL_STREAM (25)
- Master setzt
client_registry_set_accel_streamund sendetESPNOW_SET_ACCEL_STREAM. - Slave-Task
slave_accel_stream_tasksendetESPNOW_ACCEL_SAMPLEalle 16 ms.
7.5 TAP_NOTIFY (27)
- Konfiguriert auf Slave welche Tap-Arten
ESPNOW_TAP_EVENTauslösen. - Tap-Quelle: BMA456-Interrupt →
on_bma456_tapinesp_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.
ESPNOW_OTA_START+total_size→ Slaveota_espnow_slave_on_startESPNOW_OTA_PAYLOADbis 200 B → 4 KiB Buffer auf SlaveESPNOW_OTA_END→ Boot-Partition setzen- Slave antwortet
ESPNOW_OTA_STATUS(mitsend_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 | BMA456H (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-NOWESPNOW_LED_RING. - Helligkeit:
intensity0 → 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_taskaktualisiert Master-Batterie alle 30 s. - Slave:
slave_send_battery_report_to_masternach 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
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
uart_messages.protoerweitern →make proto_generate_uartcmd/cmd_neu.cmithandle_*+uart_cmd_register()- In
powerpod.ccmd_neu_register()aufrufen (nur Master-Zweig) - Bei Slave-Bedarf:
esp_now_messages.proto+esp_now_comm_send_*+ Slave-Zweig inespnow_recv_cb
Siehe adding-a-feature.md für ein vollständiges Beispiel (Find Me).