simon a1629fb3db Fix UART1 GPIO mapping and simplify command logging.
Use TX=GPIO3 and RX=GPIO2 at 921600 to match the terminal adapter wiring;
log received message id and which handler command is triggered.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-18 22:06:49 +02:00
..
2026-05-05 17:36:29 +02:00
2026-05-05 17:36:29 +02:00
2026-05-05 17:36:29 +02:00

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 task
  • uart — framed serial input, converts packets to generic_msg_t
  • powerpod.c — creates the queue, calls init_cmdHandler() then init_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 (1252), 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

  1. Pick a command ID (first byte of UART payload).
  2. Implement a handler in powerpod.c (or a dedicated module).
  3. Call msg_register_handler() after init_cmdHandler().
  4. 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:

  • versionPOWERPOD_FW_VERSION (default 1, override at compile time)
  • git_hash — short git hash from build (POWERPOD_GIT_HASH, from git 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