powerpods/docs/DOCUMENTATION.md
simon 490e0ee61f Add UART SET_LOG_LEVEL for runtime master ESP-IDF logging.
Expose the command via goTool CLI/REST and dashboard controls so log verbosity can be tuned without reflashing.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 18:03:34 +02:00

19 KiB
Raw Permalink Blame History

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.


Inhaltsverzeichnis

  1. Projektziel
  2. Hardware und Pins
  3. Build und Flash
  4. Boot-Konfiguration
  5. UART-Protokoll
  6. ESP-NOW-Protokoll
  7. Befehlsreferenz (UART)
  8. OTA
  9. BMA456 Beschleunigungssensor
  10. LED-Ring
  11. Board-Input und Batterie
  12. Persistenz (NVS)
  13. Protobuf und Code-Generierung
  14. Modulreferenz
  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 (Host-Protokoll): UART_NUM_1, 921600 Baud, 8N1, kein Flow-Control.

ESP-IDF-Log (Debug): esp_log_* geht auf UART0 (Standard-Konsole, typisch USB am Dev-Board, 115200 Baud, CONFIG_ESP_CONSOLE_UART_NUM=0). Das ist getrennt vom Host-UART1 — goTool liest keine esp_log-Ausgabe. Ohne angeschlossenes Debug-Kabel werden aktivierte Logs trotzdem formatiert und an UART0 gesendet (CPU-Overhead bleibt); nur ESP_LOG_NONE / Level-Filter vermeiden die Arbeit.

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_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 (18)
  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_queueinit_cmdHandlerinit_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 §45.)

5.1 Rahmen

Byte Inhalt
0 0xAA Start
1 Länge N (1252)
2…N+1 Payload
N+2 XOR-Checksum über Payload
N+3 0xCC Stopp

Implementierung: uart.cparse_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_tcmd_queue
  3. vCmdDispatcherTask → registrierter msg_callback_t
  4. Handler: uart_cmd_decode(data, len, &msg)data ist nur protobuf-Teil; bei len > 0 strikt (Decode/which_payload-Fehler → Fehlerantwort)
  5. ota_session_uart_cmd_allowed() — während OTA nur OTA-Befehle
  6. 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 (113).
  • 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_ECHO_PING M→S echo_ping Latenztest (Host-Timestamp + master_time_us)
ESPNOW_ECHO_PONG S→M echo_pong Echo unverändert zurück zum Master
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)
Echo-Ping Timeout (Master) 500 ms (ESPNOW_ECHO_PING_TIMEOUT_MS)

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)
30 ESPNOW_ECHO_PING cmd_espnow_echo_ping.c Timestamp-Echo über ESP-NOW (Latenztest)
31 SET_LOG_LEVEL cmd_set_log_level.c ESP-IDF-Log-Level global ("*") lesen/setzen

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 0100 %
2 Digit 010
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.

7.11 ESPNOW_ECHO_PING (30)

Round-Trip-Latenztest zu einem Slave (client_id > 0, muss in der Registry sein).

Ablauf:

  1. Host (goTool) setzt timestamp_us (Unix-µs) und sendet EspNowEchoPingRequest per UART.
  2. Master (cmd_espnow_echo_ping.c) löst MAC aus Registry auf, ruft esp_now_comm_echo_ping() auf.
  3. Master sendet ESPNOW_ECHO_PING mit host_timestamp_us und master_time_us (esp_timer_get_time() kurz vor Send).
  4. Slave (handle_echo_ping) antwortet mit ESPNOW_ECHO_PONG (Felder unverändert).
  5. Master empfängt Pong, berechnet esp_rtt_us = esp_timer_get_time() - master_time_us, antwortet per UART.

Request: client_id, timestamp_us (vom Host gesetzt).

Response: success, client_id, timestamp_us (echoed), esp_rtt_us (nur bei Erfolg).

Feld Quelle Bedeutung
timestamp_us Host → Slave → Host Korrelations-ID; muss mit Request übereinstimmen
esp_rtt_us Master Rohe µs-Differenz von esp_timer_get_time() (Ping-Send → Pong-Empfang im recv_cb)

Implementierung: main/cmd/cmd_espnow_echo_ping.c, esp_now_comm_echo_ping() in esp_now_master.c, Slave handle_echo_ping in esp_now_slave.c.

Host-seitig (goTool): rtt_ms = volle UART-Kette (Send bis Response-Empfang); unabhängig von esp_rtt_us.

7.12 SET_LOG_LEVEL (31)

Laufzeit-Steuerung des globalen ESP-IDF-Log-Levels auf dem Master (esp_log_level_set("*", …)). Kein ESP-NOW, kein NVS — nur Master.

Request: write, level (esp_log_level_t: 0=NONE, 1=ERROR, 2=WARN, 3=INFO, 4=DEBUG, 5=VERBOSE).

write Verhalten
false / leer Aktuelles Level lesen (esp_log_level_get("*"))
true Level setzen; ungültige Werte (>5) → success=false

Response: success, level (aktuell bzw. gesetzt).

Boot-Default: CONFIG_LOG_DEFAULT_LEVEL in sdkconfig (aktuell INFO = 3). Kann per UART/Web-UI zur Laufzeit geändert werden; nach Reboot gilt wieder der sdkconfig-Wert.

Ausgabe: Logs erscheinen auf UART0 (Debug-USB), nicht auf dem Host-UART1 (goTool).

go run . -port /dev/ttyUSB0 log-level
go run . -port /dev/ttyUSB0 log-level -set -level 3

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 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 010 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

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 Init, recv-Router
esp_now_core.c WiFi, Peer, gemeinsamer Send
esp_now_master.c Master-Tasks, Registry, Unicast send
esp_now_slave.c Join, Heartbeat, Accel/Tap send
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_espnow_echo_ping.c ESPNOW_ECHO_PING
cmd_set_log_level.c SET_LOG_LEVEL
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 Master/Slave; Slave-Work-Queue
ota_session.c OTA-Sperre für UART-Dispatcher

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

Level zur Laufzeit: UART SET_LOG_LEVEL (31) oder Dashboard „ESP Log-Level“ — steuert nur die Konsolen-Ausgabe auf UART0, nicht das Host-Protokoll.

Tag Modul
[Main] powerpod.c
[LOG_LVL] cmd_set_log_level.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 für ein vollständiges Beispiel (Find Me).