powerpods/main/README.md
simon 43a85ce697 Add command queue dispatcher and VERSION UART handler.
Centralize command dispatch over a FreeRTOS queue so UART and future
ESP-NOW transports can register handlers; implement the protobuf VERSION
command with framed nanopb responses including build git hash.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-18 21:46:51 +02:00

4.0 KiB
Raw Blame History

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, pins TX=2 / RX=3):

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