# 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. ```c 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. ```c 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: ```c 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. ```c 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: - `version` — `POWERPOD_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 |