Initialize WiFi and ESP-NOW from shared app config; master broadcasts discover packets and collects slave info, slaves respond on matching network while UART commands stay master-only. Co-authored-by: Cursor <cursoragent@cursor.com>
Command handler
Generic command dispatch for Powerpod. Transports (UART today, ESP-NOW later) enqueue messages on a shared FreeRTOS queue; a dispatcher task invokes registered callbacks by message ID.
Architecture
UART / ESP-NOW → generic_msg_t queue → vCmdDispatcherTask → registered handler
cmd_handler— queue, registration, dispatcher taskuart— framed serial input, converts packets togeneric_msg_tpowerpod.c— creates the queue, callsinit_cmdHandler()theninit_uart()
Initialize the command handler before UART so the dispatcher is running when packets arrive.
cmd_queue = xQueueCreate(10, sizeof(generic_msg_t));
init_cmdHandler(cmd_queue);
init_uart(cmd_queue);
UART frame format
Packets on UART1 (921600 baud, TX=GPIO3, RX=GPIO2 — USB adapter on /dev/ttyUSB0):
| Field | Value |
|---|---|
| Start | 0xAA |
| Length | 1 byte, payload size (1–252), non-zero |
| Payload | length bytes |
| Checksum | XOR of all payload bytes |
| Stop | 0xCC |
Payload layout for the command handler:
| Offset | Meaning |
|---|---|
| 0 | Command ID (msg_id, uint8/16) |
| 1… | Arguments (passed to handler) |
Example: command 0x01 with arguments 0x02 0x03 → payload 01 02 03, length = 3.
The dispatcher strips the first byte; handlers receive only the argument bytes.
API
msg_register_handler(uint16_t id, msg_callback_t cb)
Register a callback for a command ID. Up to 32 handlers. Re-registering the same ID updates the callback.
static void on_ping(const uint8_t *data, size_t len) {
ESP_LOGI("app", "ping, %u bytes", (unsigned)len);
}
msg_register_handler(0x01, on_ping);
Callback signature:
typedef void (*msg_callback_t)(const uint8_t *data, size_t len);
msg_post(uint16_t id, const uint8_t *data, size_t len)
Enqueue a command from firmware (e.g. ESP-NOW receive path) without UART. Copies data into heap memory; the dispatcher frees it after the handler returns.
uint8_t args[] = {0x02, 0x03};
msg_post(0x01, args, sizeof(args));
Returns ESP_OK, ESP_ERR_NO_MEM, ESP_ERR_TIMEOUT (queue full), or ESP_ERR_INVALID_STATE.
Adding a new command
- Pick a command ID (first byte of UART payload).
- Implement a handler in
powerpod.c(or a dedicated module). - Call
msg_register_handler()afterinit_cmdHandler(). - From a host tool, send a framed UART packet with that ID in byte 0.
VERSION command (MessageType.VERSION = 3)
Implemented in cmd_version.c. Request is a UART frame with payload 03 (command byte only).
Response frame payload:
| Byte 0 | Bytes 1… |
|---|---|
0x03 |
nanopb-encoded UartMessage with type = VERSION and version_response set |
VersionResponse fields:
version—POWERPOD_FW_VERSION(default1, override at compile time)git_hash— short git hash from build (POWERPOD_GIT_HASH, fromgit rev-parse)
Register additional proto commands the same way: handler + uart_send_uart_message() for replies.
ESP-NOW (planned)
Parse incoming ESP-NOW data in the Wi-Fi layer and call msg_post() with the same ID + payload layout as UART (ID separate, arguments in data). No changes to cmd_handler required.
Files
| File | Role |
|---|---|
cmd_handler.h |
Types and public API |
cmd_handler.c |
Queue dispatch, registration, msg_post |
uart.c |
Framed UART parser → queue |
powerpod.c |
Queue creation and init order |
cmd_version.c |
VERSION command handler |
uart_proto.c |
Encode/send UartMessage over UART |