Document UART-to-ESP-NOW flow using Find me; align proto_generate_espnow with uart (python3, -I .). Co-authored-by: Cursor <cursoragent@cursor.com>
11 KiB
Feature hinzufügen — von UART bis ESP-NOW
Dieser Guide beschreibt die komplette Kette am Beispiel Find me (Commit efd6260): Host sendet ein UART-Kommando an den Master, der die Aktion lokal ausführt oder per ESP-NOW an einen Slave weiterleitet.
Architektur (Überblick)
sequenceDiagram
participant Host as Host (goTool / Dashboard)
participant Master as Master (UART + ESP-NOW)
participant Slave as Slave (ESP-NOW)
Host->>Master: UART-Frame [cmd_id + UartMessage protobuf]
Master->>Master: cmd_* Handler (Queue)
alt client_id == 0
Master->>Master: z.B. led_ring_find_me()
else client_id > 0
Master->>Slave: ESP-NOW EspNowMessage (unicast)
Slave->>Slave: esp_now_recv_cb → Handler
end
Master->>Host: UART-Response (UartMessage)
| Schicht | Dateien | Rolle |
|---|---|---|
| Schema UART | main/proto/uart_messages.proto |
MessageType, Request/Response für Host ↔ Master |
| Schema ESP-NOW | main/proto/esp_now_messages.proto |
Master ↔ Slave (ohne UART) |
| Transport UART | main/uart.c, goTool/uart/ |
Rahmen 0xAA … 0xCC, Byte 0 = Command-ID |
| Dispatch | main/cmd_handler.c, main/uart_cmd.c |
Queue + uart_cmd_register() |
| Master-Logik | main/cmd_*.c |
Decode, Registry, ESP-NOW senden |
| ESP-NOW | main/esp_now_comm.c |
Encode/Decode, Send, Slave-recv_cb |
| Geräte-Funktion | z.B. main/led_ring.c |
Wiederverwendbare Aktion (Master + Slave) |
| Host | goTool/cmd_*.go, api_serve.go, webui/ |
CLI, HTTP, UI |
Wichtig: UART-Befehle laufen auf dem Master (nur wenn app_config.master). Slaves haben keinen UART-Command-Handler; sie reagieren auf ESP-NOW. Die eigentliche Wirkung (LED, Sensor, …) liegt in gemeinsamen Modulen (led_ring, bosch456, …).
Wann braucht man was?
| Ziel | UART (uart_messages.proto) |
ESP-NOW (esp_now_messages.proto) |
|---|---|---|
| Nur Master (angeschlossener Pod) | Ja | Nein |
| Einen registrierten Slave ansteuern | Ja (Master leitet weiter) | Ja |
| Slave → Master Rückmeldung | Optional (UART-Status) | Ja, wenn Slave antworten soll |
Find me: UART FIND_ME mit client_id; 0 = Master-Ring, >0 = Unicast ESPNOW_FIND_ME an die Slave-MAC aus der Registry.
Referenz für nur ESP-NOW-Weiterleitung ohne lokale Wirkung: cmd_espnow_unicast_test.c
Referenz für Master lokal + Slave + client_id: cmd_espnow_find_me.c, cmd_accel_deadzone.c
Schritt 1 — Protobuf (UART)
Datei: main/proto/uart_messages.proto
- Neue ID in
enum MessageType(freie Nummer wählen, z.B.22):
FIND_ME = 22;
- Request/Response definieren und im
UartMessage-oneofeintragen (neue Feldnummern, nicht wiederverwenden):
message EspNowFindMeRequest {
uint32 client_id = 1;
}
message EspNowFindMeResponse {
bool success = 1;
uint32 client_id = 2;
}
// in message UartMessage { oneof payload { …
EspNowFindMeRequest espnow_find_me_request = 19;
EspNowFindMeResponse espnow_find_me_response = 20;
}
- Generieren:
make proto_generate_uart
make gotool-proto
Erzeugt u.a. main/proto/uart_messages.pb.h, .pb.c und goTool/pb/uart_messages.pb.go.
Schritt 2 — Protobuf (ESP-NOW), falls Slaves betroffen
Datei: main/proto/esp_now_messages.proto
- Neuer Wert in
enum EspNowMessageType:
ESPNOW_FIND_ME = 10;
- Payload-Nachricht + Eintrag im
EspNowMessage-oneof:
message EspNowFindMe {
uint32 client_id = 1; // 0 = alle; sonst nur passende slave_id
}
// EspNowMessage.oneof:
EspNowFindMe find_me = 11;
- Generieren:
make proto_generate_espnow
# oder: make proto_generate
Hinweis: Master und alle Slaves müssen dieselbe ESP-NOW-Proto-Version flashen, sobald sich esp_now_messages.proto ändert.
Schritt 3 — Geräte-Logik (gemeinsam)
Funktion, die auf Master und Slave gleich wirken soll, gehört nicht in den UART-Handler, sondern in ein Modul (z.B. led_ring).
Find me:
led_ring_find_me()inled_ring.c— sequenzLED_CMD_FIND_ME(3× rot/grün/blau, volle Helligkeit)- Optional separater UART-Pfad nur für Ring-Steuerung:
LED_RINGmode4incmd_led_ring.c(ohne ESP-NOW)
Schritt 4 — UART-Command-Handler (nur Master)
Neue Dateien: main/cmd_espnow_find_me.c, main/cmd_espnow_find_me.h
Muster (gekürzt):
static void reply(bool success, uint32_t client_id) {
alox_UartMessage response;
uart_cmd_init_response(&response, alox_MessageType_FIND_ME,
alox_UartMessage_espnow_find_me_response_tag);
response.payload.espnow_find_me_response.success = success;
response.payload.espnow_find_me_response.client_id = client_id;
uart_cmd_send(&response, TAG);
}
static void handle_find_me(const uint8_t *data, size_t len) {
alox_UartMessage uart_msg;
if (uart_cmd_decode(data, len, &uart_msg) != ESP_OK) { … }
const alox_EspNowFindMeRequest *req = UART_CMD_REQ(
&uart_msg, alox_UartMessage_espnow_find_me_request_tag,
espnow_find_me_request);
if (req == NULL) { … }
if (req->client_id == 0) {
led_ring_find_me(); // lokal
reply(true, 0);
return;
}
const client_info_t *client = client_registry_find_by_id(req->client_id);
if (client == NULL) { reply(false, req->client_id); return; }
esp_err_t err = esp_now_comm_send_find_me(client->mac, req->client_id);
reply(err == ESP_OK, req->client_id);
}
void cmd_espnow_find_me_register(void) {
uart_cmd_register(alox_MessageType_FIND_ME, handle_find_me);
}
Hilfs-APIs (uart_cmd.h):
| API | Zweck |
|---|---|
uart_cmd_decode() |
Protobuf-Body (ohne führendes Command-Byte) dekodieren |
UART_CMD_REQ() |
Sicheres Lesen des oneof-Zweigs |
uart_cmd_init_response() |
Response-Typ + which_payload setzen |
uart_cmd_send() |
Antwort UART raus |
Registrierung in main/powerpod.c (nur im if (app_config.master)-Block):
cmd_espnow_find_me_register();
Build: main/CMakeLists.txt → "cmd_espnow_find_me.c"
Logging-Namen: main/cmd_handler.c → case alox_MessageType_FIND_ME: return "FIND_ME";
Ablauf UART intern
uart_read_taskliest Frame, legtmsg_id = payload[0]und Rest in Queue.vCmdDispatcherTaskruft den registrierten Handler mitdata= Bytes nach der ID auf.- Handler antwortet synchron über
uart_cmd_send(die LED-Animation läuft danach imled_taskweiter).
Schritt 5 — ESP-NOW senden (Master)
In main/esp_now_comm.c:
- Statische Sendefunktion (wie
send_unicast_test):
static esp_err_t send_find_me(const uint8_t *dest_mac, uint32_t client_id) {
alox_EspNowMessage msg = alox_EspNowMessage_init_zero;
msg.type = alox_EspNowMessageType_ESPNOW_FIND_ME;
msg.which_payload = alox_EspNowMessage_find_me_tag;
msg.payload.find_me.client_id = client_id;
return send_message(dest_mac, &msg);
}
- Öffentliche API in
esp_now_comm.h/.c:
esp_err_t esp_now_comm_send_find_me(const uint8_t mac[CLIENT_MAC_LEN],
uint32_t client_id);
Prüfungen: mac != NULL, s_config.master, Peer per ensure_peer() (passiert in send_message).
Schritt 6 — ESP-NOW empfangen (Slave)
Im Slave-Zweig von espnow_recv_cb (!s_config.master):
- Handler-Funktion, typisch mit Master-MAC-Check und
client_id-Filter (wie Deadzone):
static void handle_slave_find_me(const uint8_t *master_mac,
const alox_EspNowFindMe *req) {
uint32_t my_id = s_own_mac[5];
if (req->client_id != 0 && req->client_id != my_id) return;
if (s_slave_joined && !mac_equal(master_mac, s_master_mac)) return;
led_ring_find_me();
}
- Im
switch (msg.which_payload):
case alox_EspNowMessage_find_me_tag:
if (!s_slave_joined || !mac_equal(info->src_addr, s_master_mac)) break;
handle_slave_find_me(info->src_addr, &msg.payload.find_me);
break;
Slaves führen led_ring_init() in powerpod.c ebenfalls aus — Ring-Hardware ist auf beiden Rollen vorhanden.
Schritt 7 — Host (goTool)
CLI
goTool/cmd_find_me.go:
UartMessagemitType: MessageType_FIND_MEundEspnowFindMeRequest- Payload =
[byte(MessageType_FIND_ME)] + proto.Marshal(msg) exchangePayload→ Response dekodieren
main.go: Command find-me registrieren, -port Pflicht.
go run . -port /dev/ttyUSB0 find-me
go run . -port /dev/ttyUSB0 find-me -client 16
Dashboard / HTTP
managedSerial.FindMe(clientID)inclient_api.go(UART-Serial mutex wie andere Befehle).POST /api/find-memit{"client_id": 0}inapi_serve.go.- Button in
goTool/webui/index.html(Master + pro Slave-Zeile).
Serial-Format (Host)
Entspricht main/README.md:
| Byte | Inhalt |
|---|---|
| Frame | 0xAA, Länge, Payload, XOR-Checksum, 0xCC |
| Payload[0] | MessageType (z.B. 22 = FIND_ME) |
| Payload[1…] | Nanopb-UartMessage |
Schritt 8 — Dokumentation & Test
main/README.md: Zeile in UART-Tabelle, ggf. ESP-NOW-Tabelle, eigener Abschnitt mit Beispiel-go run.goTool/README.md: Command-Tabelle + HTTP-API.- Build:
make proto_generate
cd goTool && go build .
idf.py build
- Manuell testen:
| Test | Erwartung |
|---|---|
find-me (client 0) |
Master-Ring blinkt RGB |
find-me -client <id> |
Nur dieser Slave blinkt; Log Master: unicast FIND_ME |
| Slave offline / unbekannte ID | success=false in UART-Response |
| Dashboard-Buttons | Gleiches Verhalten wie CLI |
Checkliste (Kurz)
uart_messages.proto:MessageType, Request, Response,oneofesp_now_messages.proto(falls Slave):EspNowMessageType, Message,oneofmake proto_generate+make gotool-proto- Geräte-Modul (z.B.
led_ring_*) cmd_*.c+uart_cmd_registeresp_now_comm:send_*+ Slave-recvcaseCMakeLists.txt,powerpod.c,cmd_handler.cName- goTool: CLI,
client_api, optionalapi_serve+ WebUI - README aktualisieren
- Master + Slave flashen bei ESP-NOW-Proto-Änderung
Referenz-Dateien (Find me)
| Bereich | Datei |
|---|---|
| UART Proto | main/proto/uart_messages.proto |
| ESP-NOW Proto | main/proto/esp_now_messages.proto |
| UART Handler | main/cmd_espnow_find_me.c |
| ESP-NOW | main/esp_now_comm.c, main/esp_now_comm.h |
| LED | main/led_ring.c, main/led_ring.h |
| Host CLI | goTool/cmd_find_me.go |
| HTTP/UI | goTool/api_serve.go, goTool/webui/index.html |
Ähnliche Features zum Abgucken:
- Nur Master, kein ESP-NOW:
cmd_version.c,cmd_led_ring.c - Nur Slave per ESP-NOW (Master leitet nur durch):
cmd_espnow_unicast_test.c - Master + alle Slaves / Filter:
cmd_accel_deadzone.c - Großer ESP-NOW-Fluss mit Status:
ota_espnow.c,cmd_ota.c