Move UART command handlers into main/cmd/ for clearer layout.

Add cmd/ to CMake include paths and update documentation paths.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-19 22:49:11 +02:00
co-authored by Cursor
parent 95d5a9747a
commit 16c521f71c
24 changed files with 42 additions and 41 deletions
+122
View File
@@ -0,0 +1,122 @@
#include "bosch456.h"
#include "client_registry.h"
#include "cmd_accel_deadzone.h"
#include "esp_log.h"
#include "esp_now_comm.h"
#include "pod_settings.h"
#include "uart_cmd.h"
static const char *TAG = "[ACCEL_DZ]";
static void reply(uint32_t deadzone, uint32_t client_id, bool success,
uint32_t slaves_updated) {
alox_UartMessage response;
uart_cmd_init_response(&response, alox_MessageType_ACCEL_DEADZONE,
alox_UartMessage_accel_deadzone_response_tag);
response.payload.accel_deadzone_response.deadzone = deadzone;
response.payload.accel_deadzone_response.client_id = client_id;
response.payload.accel_deadzone_response.success = success;
response.payload.accel_deadzone_response.slaves_updated = slaves_updated;
uart_cmd_send(&response, TAG);
}
static void apply_local_deadzone(uint32_t deadzone) {
bma456_set_accel_deadzone(deadzone);
if (pod_settings_save_accel_deadzone(deadzone) != ESP_OK) {
ESP_LOGW(TAG, "deadzone %lu applied but not saved to NVS",
(unsigned long)deadzone);
}
}
static esp_err_t push_deadzone_to_slave(const client_info_t *client,
uint32_t deadzone) {
if (client == NULL) {
return ESP_ERR_INVALID_ARG;
}
esp_err_t err = client_registry_set_accel_deadzone(client->id, deadzone);
if (err != ESP_OK) {
return err;
}
return esp_now_comm_send_accel_deadzone(client->mac, client->id, deadzone);
}
static void handle_accel_deadzone(const uint8_t *data, size_t len) {
alox_UartMessage uart_msg;
alox_AccelDeadzoneRequest req = alox_AccelDeadzoneRequest_init_zero;
if (uart_cmd_decode(data, len, &uart_msg) != ESP_OK) {
ESP_LOGW(TAG, "decode failed");
reply(BMA456_DEFAULT_ACCEL_DEADZONE, 0, false, 0);
return;
}
const alox_AccelDeadzoneRequest *req_ptr = UART_CMD_REQ(
&uart_msg, alox_UartMessage_accel_deadzone_request_tag,
accel_deadzone_request);
if (req_ptr != NULL) {
req = *req_ptr;
}
if (req.write) {
if (req.all_clients) {
size_t n = client_registry_set_accel_deadzone_all(req.deadzone);
uint32_t sent = 0;
for (size_t i = 0; i < client_registry_count(); i++) {
const client_info_t *client = client_registry_at(i);
if (client == NULL) {
continue;
}
if (esp_now_comm_send_accel_deadzone(client->mac, client->id,
req.deadzone) == ESP_OK) {
sent++;
}
}
if (bma456_is_ready()) {
apply_local_deadzone(req.deadzone);
}
ESP_LOGI(TAG, "set deadzone %lu via unicast to %u/%u slaves",
(unsigned long)req.deadzone, (unsigned)sent, (unsigned)n);
reply(req.deadzone, 0, sent > 0 || bma456_is_ready(), sent);
return;
}
if (req.client_id == 0) {
apply_local_deadzone(req.deadzone);
ESP_LOGI(TAG, "set local deadzone %lu (no ESP-NOW; use -client or -all "
"for slaves)",
(unsigned long)req.deadzone);
reply(req.deadzone, 0, true, 0);
return;
}
const client_info_t *client = client_registry_find_by_id(req.client_id);
if (client == NULL) {
ESP_LOGW(TAG, "client id %lu not found",
(unsigned long)req.client_id);
reply(req.deadzone, req.client_id, false, 0);
return;
}
esp_err_t err = push_deadzone_to_slave(client, req.deadzone);
reply(req.deadzone, req.client_id, err == ESP_OK, err == ESP_OK);
return;
}
if (req.all_clients || req.client_id == 0) {
reply(bma456_get_accel_deadzone(), 0, true, 0);
return;
}
uint32_t dz = 0;
esp_err_t err = client_registry_get_accel_deadzone(req.client_id, &dz);
reply(dz, req.client_id, err == ESP_OK, 0);
}
void cmd_accel_deadzone_register(void) {
uart_cmd_register(alox_MessageType_ACCEL_DEADZONE, handle_accel_deadzone);
}
+6
View File
@@ -0,0 +1,6 @@
#ifndef CMD_ACCEL_DEADZONE_H
#define CMD_ACCEL_DEADZONE_H
void cmd_accel_deadzone_register(void);
#endif
+59
View File
@@ -0,0 +1,59 @@
#include "client_registry.h"
#include "cmd_client_info.h"
#include "esp_log.h"
#include "pb_encode.h"
#include "uart_cmd.h"
static const char *TAG = "[CLIENT_INFO]";
static bool encode_clients_list(pb_ostream_t *stream, const pb_field_t *field,
void *const *arg) {
(void)arg;
size_t count = client_registry_count();
for (size_t i = 0; i < count; i++) {
const client_info_t *client = client_registry_at(i);
if (client == NULL) {
continue;
}
uart_cmd_bytes_t mac = {.data = client->mac, .len = CLIENT_MAC_LEN};
alox_ClientInfo proto = alox_ClientInfo_init_zero;
proto.id = client->id;
proto.available = client->available;
proto.used = client->used;
proto.last_ping = client_registry_ms_since(client->last_ping_at);
proto.last_success_ping =
client_registry_ms_since(client->last_success_ping_at);
proto.version = client->version;
proto.mac.funcs.encode = uart_cmd_encode_bytes;
proto.mac.arg = &mac;
if (!pb_encode_tag_for_field(stream, field)) {
return false;
}
if (!pb_encode_submessage(stream, alox_ClientInfo_fields, &proto)) {
return false;
}
}
return true;
}
static void handle_client_info(const uint8_t *data, size_t len) {
(void)data;
(void)len;
alox_UartMessage response;
uart_cmd_init_response(&response, alox_MessageType_CLIENT_INFO,
alox_UartMessage_client_info_response_tag);
response.payload.client_info_response.clients.funcs.encode = encode_clients_list;
response.payload.client_info_response.clients.arg = NULL;
ESP_LOGI(TAG, "sending %u clients", (unsigned)client_registry_count());
uart_cmd_send(&response, TAG);
}
void cmd_client_info_register(void) {
uart_cmd_register(alox_MessageType_CLIENT_INFO, handle_client_info);
}
+6
View File
@@ -0,0 +1,6 @@
#ifndef CMD_CLIENT_INFO_H
#define CMD_CLIENT_INFO_H
void cmd_client_info_register(void);
#endif
+64
View File
@@ -0,0 +1,64 @@
#include "client_registry.h"
#include "cmd_espnow_find_me.h"
#include "esp_log.h"
#include "esp_now_comm.h"
#include "led_ring.h"
#include "uart_cmd.h"
static const char *TAG = "[FIND_ME]";
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) {
ESP_LOGW(TAG, "decode failed");
reply(false, 0);
return;
}
const alox_EspNowFindMeRequest *req = UART_CMD_REQ(
&uart_msg, alox_UartMessage_espnow_find_me_request_tag,
espnow_find_me_request);
if (req == NULL) {
ESP_LOGW(TAG, "missing find_me request");
reply(false, 0);
return;
}
if (req->client_id == 0) {
led_ring_find_me();
ESP_LOGI(TAG, "find-me on master");
reply(true, 0);
return;
}
const client_info_t *client = client_registry_find_by_id(req->client_id);
if (client == NULL) {
ESP_LOGW(TAG, "client id %lu not in registry",
(unsigned long)req->client_id);
reply(false, req->client_id);
return;
}
esp_err_t err = esp_now_comm_send_find_me(client->mac, req->client_id);
if (err == ESP_OK) {
ESP_LOGI(TAG, "find-me sent to slave %lu", (unsigned long)req->client_id);
} else {
ESP_LOGW(TAG, "find-me to slave %lu failed: %s",
(unsigned long)req->client_id, esp_err_to_name(err));
}
reply(err == ESP_OK, req->client_id);
}
void cmd_espnow_find_me_register(void) {
uart_cmd_register(alox_MessageType_FIND_ME, handle_find_me);
}
+6
View File
@@ -0,0 +1,6 @@
#ifndef CMD_ESPNOW_FIND_ME_H
#define CMD_ESPNOW_FIND_ME_H
void cmd_espnow_find_me_register(void);
#endif
+51
View File
@@ -0,0 +1,51 @@
#include "client_registry.h"
#include "cmd_espnow_unicast_test.h"
#include "esp_log.h"
#include "esp_now_comm.h"
#include "uart_cmd.h"
static const char *TAG = "[UNICAST_TEST]";
static void reply(bool success, uint32_t seq) {
alox_UartMessage response;
uart_cmd_init_response(&response, alox_MessageType_ESPNOW_UNICAST_TEST,
alox_UartMessage_espnow_unicast_test_response_tag);
response.payload.espnow_unicast_test_response.success = success;
response.payload.espnow_unicast_test_response.seq = seq;
uart_cmd_send(&response, TAG);
}
static void handle_espnow_unicast_test(const uint8_t *data, size_t len) {
alox_UartMessage uart_msg;
if (uart_cmd_decode(data, len, &uart_msg) != ESP_OK) {
ESP_LOGW(TAG, "decode failed");
reply(false, 0);
return;
}
const alox_EspNowUnicastTestRequest *req = UART_CMD_REQ(
&uart_msg, alox_UartMessage_espnow_unicast_test_request_tag,
espnow_unicast_test_request);
if (req == NULL || req->client_id == 0) {
ESP_LOGW(TAG, "need client_id in request");
reply(false, 0);
return;
}
const client_info_t *client = client_registry_find_by_id(req->client_id);
if (client == NULL) {
ESP_LOGW(TAG, "client id %lu not in registry",
(unsigned long)req->client_id);
reply(false, req->seq);
return;
}
esp_err_t err = esp_now_comm_send_unicast_test(client->mac, req->seq);
reply(err == ESP_OK, req->seq);
}
void cmd_espnow_unicast_test_register(void) {
uart_cmd_register(alox_MessageType_ESPNOW_UNICAST_TEST,
handle_espnow_unicast_test);
}
+6
View File
@@ -0,0 +1,6 @@
#ifndef CMD_ESPNOW_UNICAST_TEST_H
#define CMD_ESPNOW_UNICAST_TEST_H
void cmd_espnow_unicast_test_register(void);
#endif
+127
View File
@@ -0,0 +1,127 @@
#include "cmd_handler.h"
#include "esp_err.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/idf_additions.h"
#include "uart_messages.pb.h"
#include <stdlib.h>
#include <string.h>
#define MAX_HANDLERS 32
static const char *TAG = "[CMDH]";
static QueueHandle_t cmd_queue;
static msg_binding_t handlers[MAX_HANDLERS];
static int handler_count;
static const char *message_type_name(uint16_t id) {
switch ((alox_MessageType)id) {
case alox_MessageType_ACK:
return "ACK";
case alox_MessageType_ECHO:
return "ECHO";
case alox_MessageType_VERSION:
return "VERSION";
case alox_MessageType_CLIENT_INFO:
return "CLIENT_INFO";
case alox_MessageType_CLIENT_INPUT:
return "CLIENT_INPUT";
case alox_MessageType_ACCEL_DEADZONE:
return "ACCEL_DEADZONE";
case alox_MessageType_ESPNOW_UNICAST_TEST:
return "ESPNOW_UNICAST_TEST";
case alox_MessageType_LED_RING:
return "LED_RING";
case alox_MessageType_OTA_START:
return "OTA_START";
case alox_MessageType_OTA_PAYLOAD:
return "OTA_PAYLOAD";
case alox_MessageType_OTA_END:
return "OTA_END";
case alox_MessageType_OTA_STATUS:
return "OTA_STATUS";
case alox_MessageType_OTA_START_ESPNOW:
return "OTA_START_ESPNOW";
case alox_MessageType_OTA_SLAVE_PROGRESS:
return "OTA_SLAVE_PROGRESS";
case alox_MessageType_FIND_ME:
return "FIND_ME";
case alox_MessageType_RESTART:
return "RESTART";
default:
return "UNKNOWN";
}
}
void init_cmdHandler(QueueHandle_t queue) {
cmd_queue = queue;
if (xTaskCreate(vCmdDispatcherTask, "cmd_dispatch", 8192, NULL, 5, NULL) !=
pdPASS) {
ESP_LOGE(TAG, "failed to create cmd_dispatch task");
}
}
esp_err_t msg_register_handler(uint16_t id, msg_callback_t cb) {
if (cb == NULL) {
return ESP_ERR_INVALID_ARG;
}
for (int i = 0; i < handler_count; i++) {
if (handlers[i].msg_id == id) {
handlers[i].callback = cb;
return ESP_OK;
}
}
if (handler_count >= MAX_HANDLERS) {
return ESP_ERR_NO_MEM;
}
handlers[handler_count].msg_id = id;
handlers[handler_count].callback = cb;
handler_count++;
return ESP_OK;
}
esp_err_t msg_post(uint16_t id, const uint8_t *data, size_t len) {
if (cmd_queue == NULL) {
return ESP_ERR_INVALID_STATE;
}
generic_msg_t msg = {.msg_id = id, .len = len, .payload = NULL};
if (len > 0) {
msg.payload = malloc(len);
if (msg.payload == NULL) {
return ESP_ERR_NO_MEM;
}
memcpy(msg.payload, data, len);
}
if (xQueueSend(cmd_queue, &msg, pdMS_TO_TICKS(100)) != pdPASS) {
free(msg.payload);
return ESP_ERR_TIMEOUT;
}
return ESP_OK;
}
void vCmdDispatcherTask(void *param) {
(void)param;
generic_msg_t msg;
while (1) {
if (xQueueReceive(cmd_queue, &msg, portMAX_DELAY) == pdPASS) {
bool handled = false;
for (int i = 0; i < handler_count; i++) {
if (handlers[i].msg_id == msg.msg_id) {
ESP_LOGI(TAG, "trigger command %s (0x%02x)", message_type_name(msg.msg_id),
(unsigned)msg.msg_id);
handlers[i].callback(msg.payload, msg.len);
handled = true;
break;
}
}
if (!handled) {
ESP_LOGW(TAG, "no handler for %s (0x%02x)", message_type_name(msg.msg_id),
(unsigned)msg.msg_id);
}
free(msg.payload);
}
}
}
+26
View File
@@ -0,0 +1,26 @@
#ifndef CMD_HANDLER_H
#define CMD_HANDLER_H
#include "esp_err.h"
#include "freertos/idf_additions.h"
typedef struct {
uint16_t msg_id;
uint8_t *payload;
size_t len;
} generic_msg_t;
typedef void (*msg_callback_t)(const uint8_t *data, size_t len);
typedef struct {
uint16_t msg_id;
msg_callback_t callback;
} msg_binding_t;
void init_cmdHandler(QueueHandle_t queue);
void vCmdDispatcherTask(void *param);
esp_err_t msg_register_handler(uint16_t id, msg_callback_t cb);
esp_err_t msg_post(uint16_t id, const uint8_t *data, size_t len);
#endif
+146
View File
@@ -0,0 +1,146 @@
#include "cmd_led_ring.h"
#include "esp_log.h"
#include "led_ring.h"
#include "uart_cmd.h"
static const char *TAG = "[LED_RING_CMD]";
#define LED_RING_MODE_CLEAR 0
#define LED_RING_MODE_PROGRESS 1
#define LED_RING_MODE_DIGIT 2
#define LED_RING_MODE_BLINK 3
#define LED_RING_MODE_FIND_ME 4
static uint8_t clamp_u8(uint32_t v) {
if (v > 255) {
return 255;
}
return (uint8_t)v;
}
static uint8_t clamp_progress(uint32_t v) {
if (v > 100) {
return 100;
}
return (uint8_t)v;
}
static uint8_t resolve_intensity(uint32_t intensity) {
if (intensity == 0) {
return LED_RING_DEFAULT_INTENSITY;
}
return clamp_u8(intensity);
}
static void reply(bool success, uint32_t mode, uint32_t progress, uint32_t digit) {
alox_UartMessage response;
uart_cmd_init_response(&response, alox_MessageType_LED_RING,
alox_UartMessage_led_ring_progress_response_tag);
response.payload.led_ring_progress_response.success = success;
response.payload.led_ring_progress_response.mode = mode;
response.payload.led_ring_progress_response.progress = progress;
response.payload.led_ring_progress_response.digit = digit;
uart_cmd_send(&response, TAG);
}
static void handle_led_ring(const uint8_t *data, size_t len) {
alox_UartMessage uart_msg;
alox_LedRingProgressRequest req = alox_LedRingProgressRequest_init_zero;
if (uart_cmd_decode(data, len, &uart_msg) != ESP_OK) {
ESP_LOGW(TAG, "decode failed");
reply(false, 0, 0, 0);
return;
}
const alox_LedRingProgressRequest *req_ptr = UART_CMD_REQ(
&uart_msg, alox_UartMessage_led_ring_progress_request_tag,
led_ring_progress_request);
if (req_ptr != NULL) {
req = *req_ptr;
}
uint32_t mode = req.mode;
uint8_t r = clamp_u8(req.r);
uint8_t g = clamp_u8(req.g);
uint8_t b = clamp_u8(req.b);
uint8_t intensity = resolve_intensity(req.intensity);
led_command_t cmd = {0};
switch (mode) {
case LED_RING_MODE_CLEAR:
cmd.mode = LED_CMD_CLEAR;
led_ring_send_command(&cmd);
ESP_LOGI(TAG, "clear");
reply(true, mode, 0, 0);
return;
case LED_RING_MODE_PROGRESS: {
uint8_t progress = clamp_progress(req.progress);
cmd.mode = LED_CMD_PROGRESS;
cmd.progress = progress;
cmd.r = r;
cmd.g = g;
cmd.b = b;
cmd.intensity = intensity;
led_ring_send_command(&cmd);
ESP_LOGI(TAG, "progress %u%% rgb=%u,%u,%u", (unsigned)progress,
(unsigned)r, (unsigned)g, (unsigned)b);
reply(true, mode, progress, 0);
return;
}
case LED_RING_MODE_DIGIT: {
if (req.digit > 10) {
ESP_LOGW(TAG, "digit %lu out of range", (unsigned long)req.digit);
reply(false, mode, 0, req.digit);
return;
}
cmd.mode = LED_CMD_SET_DIGIT;
cmd.value = (uint8_t)req.digit;
cmd.r = r;
cmd.g = g;
cmd.b = b;
cmd.intensity = intensity;
led_ring_send_command(&cmd);
ESP_LOGI(TAG, "digit %u rgb=%u,%u,%u", (unsigned)cmd.value, (unsigned)r,
(unsigned)g, (unsigned)b);
reply(true, mode, 0, req.digit);
return;
}
case LED_RING_MODE_FIND_ME:
led_ring_find_me();
ESP_LOGI(TAG, "find-me");
reply(true, mode, 0, 0);
return;
case LED_RING_MODE_BLINK: {
cmd.mode = LED_CMD_BLINK;
cmd.r = r;
cmd.g = g;
cmd.b = b;
cmd.intensity = intensity;
cmd.blink_ms = (uint16_t)(req.blink_ms > 0 ? req.blink_ms : 350);
cmd.blink_count = req.blink_count > 0 ? (uint8_t)req.blink_count : 1;
if (cmd.blink_count == 0) {
cmd.blink_count = 1;
}
led_ring_send_command(&cmd);
ESP_LOGI(TAG, "blink x%u %u ms rgb=%u,%u,%u", (unsigned)cmd.blink_count,
(unsigned)cmd.blink_ms, (unsigned)r, (unsigned)g, (unsigned)b);
reply(true, mode, 0, 0);
return;
}
default:
ESP_LOGW(TAG, "unknown mode %lu", (unsigned long)mode);
reply(false, mode, 0, 0);
return;
}
}
void cmd_led_ring_register(void) {
uart_cmd_register(alox_MessageType_LED_RING, handle_led_ring);
}
+6
View File
@@ -0,0 +1,6 @@
#ifndef CMD_LED_RING_H
#define CMD_LED_RING_H
void cmd_led_ring_register(void);
#endif
+339
View File
@@ -0,0 +1,339 @@
#include "cmd_ota.h"
#include "led_ring.h"
#include "ota_espnow.h"
#include "ota_uart.h"
#include "uart_cmd.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/idf_additions.h"
#include <stdlib.h>
#include <string.h>
static const char *TAG = "[OTA_CMD]";
#define OTA_PREPARE_STACK 8192
#define OTA_PREPARE_PRIO 5
#define OTA_DIST_STACK 8192
#define OTA_DIST_PRIO 5
/** UART OTA upload to this node (master). */
#define OTA_LED_UART_R 0
#define OTA_LED_UART_G 0
#define OTA_LED_UART_B 255
/** ESP-NOW distribution from master to slaves. */
#define OTA_LED_ESPNOW_TX_R 0
#define OTA_LED_ESPNOW_TX_G 255
#define OTA_LED_ESPNOW_TX_B 0
typedef struct {
uint32_t written;
int slot;
} ota_dist_job_t;
static void send_ota_status(ota_uart_status_t status, uint32_t err_code) {
alox_UartMessage response;
uart_cmd_init_response(&response, alox_MessageType_OTA_STATUS,
alox_UartMessage_ota_status_tag);
response.payload.ota_status.status = (uint32_t)status;
response.payload.ota_status.bytes_written = ota_uart_bytes_written();
int slot = ota_uart_target_slot();
response.payload.ota_status.target_slot =
slot >= 0 ? (uint32_t)slot : 0;
response.payload.ota_status.error = err_code;
uart_cmd_send(&response, TAG);
}
static void send_ota_failed(uint32_t err_code) {
led_ring_ota_failed();
send_ota_status(OTA_UART_ST_FAILED, err_code);
}
static void send_ota_distributing(uint32_t kind, uint32_t bytes_done,
uint32_t target_slot) {
alox_UartMessage response;
uart_cmd_init_response(&response, alox_MessageType_OTA_STATUS,
alox_UartMessage_ota_status_tag);
response.payload.ota_status.status = (uint32_t)OTA_UART_ST_DISTRIBUTING;
response.payload.ota_status.bytes_written = bytes_done;
response.payload.ota_status.target_slot = target_slot;
response.payload.ota_status.error = kind;
uart_cmd_send(&response, TAG);
}
static void ota_dist_aggregate(uint32_t bytes_done, uint32_t total_bytes,
uint8_t slave_count) {
(void)slave_count;
led_ring_show_ota_progress(bytes_done, total_bytes, OTA_LED_ESPNOW_TX_R,
OTA_LED_ESPNOW_TX_G, OTA_LED_ESPNOW_TX_B);
send_ota_distributing(OTA_DIST_AGGREGATE, bytes_done, (uint32_t)slave_count);
}
static void ota_dist_per_slave(uint32_t slave_id, uint32_t bytes_done,
uint32_t total_bytes) {
(void)total_bytes;
send_ota_distributing(OTA_DIST_PER_SLAVE, bytes_done, slave_id);
}
static const ota_espnow_progress_cbs_t s_dist_progress = {
.aggregate = ota_dist_aggregate,
.per_slave = ota_dist_per_slave,
};
static void ota_prepare_task(void *param) {
uint32_t total_size = (uint32_t)(uintptr_t)param;
send_ota_status(OTA_UART_ST_PREPARING, 0);
int slot = ota_uart_prepare(total_size);
if (slot < 0) {
send_ota_failed(1);
vTaskDelete(NULL);
return;
}
alox_UartMessage response;
uart_cmd_init_response(&response, alox_MessageType_OTA_STATUS,
alox_UartMessage_ota_status_tag);
response.payload.ota_status.status = (uint32_t)OTA_UART_ST_READY;
response.payload.ota_status.bytes_written = 0;
response.payload.ota_status.target_slot = (uint32_t)slot;
response.payload.ota_status.error = 0;
uart_cmd_send(&response, TAG);
led_ring_show_ota_progress(0, total_size, OTA_LED_UART_R, OTA_LED_UART_G,
OTA_LED_UART_B);
vTaskDelete(NULL);
}
static void handle_ota_start(const uint8_t *data, size_t len) {
alox_UartMessage uart_msg;
alox_OtaStartPayload req = alox_OtaStartPayload_init_zero;
if (uart_cmd_decode(data, len, &uart_msg) != ESP_OK) {
send_ota_failed( 2);
return;
}
const alox_OtaStartPayload *req_ptr =
UART_CMD_REQ(&uart_msg, alox_UartMessage_ota_start_tag, ota_start);
if (req_ptr != NULL) {
req = *req_ptr;
}
if (req.total_size == 0) {
ESP_LOGW(TAG, "OTA_START: total_size required");
send_ota_failed( 3);
return;
}
if (ota_uart_is_active()) {
ESP_LOGW(TAG, "OTA_START while session active");
send_ota_failed( 4);
return;
}
if (xTaskCreate(ota_prepare_task, "ota_prepare", OTA_PREPARE_STACK,
(void *)(uintptr_t)req.total_size, OTA_PREPARE_PRIO,
NULL) != pdPASS) {
ESP_LOGE(TAG, "failed to create ota_prepare task");
send_ota_failed( 5);
}
}
static void handle_ota_payload(const uint8_t *data, size_t len) {
alox_UartMessage uart_msg;
if (uart_cmd_decode(data, len, &uart_msg) != ESP_OK) {
ESP_LOGW(TAG, "OTA_PAYLOAD decode failed");
send_ota_failed( 10);
return;
}
const alox_OtaPayload *req_ptr =
UART_CMD_REQ(&uart_msg, alox_UartMessage_ota_payload_tag, ota_payload);
if (req_ptr == NULL) {
ESP_LOGW(TAG, "OTA_PAYLOAD: missing ota_payload (which=%u)",
(unsigned)uart_msg.which_payload);
send_ota_failed( 11);
return;
}
if (req_ptr->data.size == 0) {
ESP_LOGW(TAG, "OTA_PAYLOAD: empty data (seq=%lu)",
(unsigned long)req_ptr->seq);
send_ota_failed( 11);
return;
}
if (!ota_uart_is_active()) {
ESP_LOGW(TAG, "OTA_PAYLOAD without active session (seq=%lu)",
(unsigned long)req_ptr->seq);
send_ota_failed( 12);
return;
}
ota_feed_result_t r =
ota_uart_feed(req_ptr->data.bytes, req_ptr->data.size);
if (r == OTA_FEED_ERROR) {
send_ota_failed( 13);
return;
}
if (r == OTA_FEED_BLOCK_WRITTEN) {
uint32_t total = ota_uart_total_size();
uint32_t done = ota_uart_bytes_written();
ESP_LOGI(TAG, "OTA block ack (%lu bytes in flash)",
(unsigned long)done);
led_ring_show_ota_progress(done, total, OTA_LED_UART_R, OTA_LED_UART_G,
OTA_LED_UART_B);
send_ota_status(OTA_UART_ST_BLOCK_ACK, 0);
return;
}
if (r == OTA_FEED_OK) {
uint32_t total = ota_uart_total_size();
if (total > 0) {
led_ring_show_ota_progress(ota_uart_bytes_received(), total,
OTA_LED_UART_R, OTA_LED_UART_G, OTA_LED_UART_B);
}
}
}
static void ota_distribute_task(void *param) {
ota_dist_job_t *job = (ota_dist_job_t *)param;
if (job == NULL) {
vTaskDelete(NULL);
return;
}
const esp_partition_t *part = NULL;
uint32_t image_size = 0;
if (!ota_uart_get_staged_image(&part, &image_size)) {
send_ota_failed( 30);
free(job);
vTaskDelete(NULL);
return;
}
led_ring_show_ota_progress(0, image_size, OTA_LED_ESPNOW_TX_R, OTA_LED_ESPNOW_TX_G,
OTA_LED_ESPNOW_TX_B);
send_ota_distributing(OTA_DIST_AGGREGATE, 0, 0);
esp_err_t err = ota_espnow_distribute(part, image_size, &s_dist_progress);
if (err != ESP_OK) {
ESP_LOGE(TAG, "slave OTA distribution failed: %s", esp_err_to_name(err));
ota_uart_clear_staged();
send_ota_failed(31);
free(job);
vTaskDelete(NULL);
return;
}
err = ota_uart_apply_boot();
if (err != ESP_OK) {
send_ota_failed((uint32_t)err);
free(job);
vTaskDelete(NULL);
return;
}
led_ring_show_ota_progress(image_size, image_size, OTA_LED_ESPNOW_TX_R,
OTA_LED_ESPNOW_TX_G, OTA_LED_ESPNOW_TX_B);
alox_UartMessage response;
uart_cmd_init_response(&response, alox_MessageType_OTA_STATUS,
alox_UartMessage_ota_status_tag);
response.payload.ota_status.status = (uint32_t)OTA_UART_ST_SUCCESS;
response.payload.ota_status.bytes_written = job->written;
response.payload.ota_status.target_slot =
job->slot >= 0 ? (uint32_t)job->slot : 0;
response.payload.ota_status.error = 0;
uart_cmd_send(&response, TAG);
led_ring_ota_success();
free(job);
vTaskDelete(NULL);
}
static void handle_ota_end(const uint8_t *data, size_t len) {
(void)data;
(void)len;
if (!ota_uart_is_active()) {
send_ota_failed( 20);
return;
}
ota_dist_job_t *job = calloc(1, sizeof(*job));
if (job == NULL) {
send_ota_failed( 21);
return;
}
job->written = ota_uart_bytes_written();
job->slot = ota_uart_target_slot();
uint32_t uart_total = ota_uart_total_size();
bool success = false;
esp_err_t err = ota_uart_finish(false, &success);
if (err != ESP_OK || !success) {
send_ota_failed((uint32_t)err);
free(job);
return;
}
if (uart_total > 0) {
led_ring_show_ota_progress(job->written, uart_total, OTA_LED_UART_R,
OTA_LED_UART_G, OTA_LED_UART_B);
}
if (xTaskCreate(ota_distribute_task, "ota_dist", OTA_DIST_STACK, job,
OTA_DIST_PRIO, NULL) != pdPASS) {
ESP_LOGE(TAG, "failed to create ota_dist task");
send_ota_failed( 22);
free(job);
}
}
static void handle_ota_start_espnow(const uint8_t *data, size_t len) {
(void)data;
(void)len;
if (ota_uart_is_active()) {
send_ota_failed( 40);
return;
}
const esp_partition_t *part = NULL;
uint32_t image_size = 0;
if (!ota_uart_get_staged_image(&part, &image_size)) {
send_ota_failed( 41);
return;
}
esp_err_t err = ota_espnow_distribute(part, image_size, &s_dist_progress);
if (err != ESP_OK) {
send_ota_failed( 42);
return;
}
err = ota_uart_apply_boot();
if (err != ESP_OK) {
send_ota_failed( (uint32_t)err);
return;
}
alox_UartMessage response;
uart_cmd_init_response(&response, alox_MessageType_OTA_STATUS,
alox_UartMessage_ota_status_tag);
response.payload.ota_status.status = (uint32_t)OTA_UART_ST_SUCCESS;
response.payload.ota_status.bytes_written = image_size;
response.payload.ota_status.error = 0;
uart_cmd_send(&response, TAG);
led_ring_ota_success();
}
void cmd_ota_register(void) {
uart_cmd_register(alox_MessageType_OTA_START, handle_ota_start);
uart_cmd_register(alox_MessageType_OTA_PAYLOAD, handle_ota_payload);
uart_cmd_register(alox_MessageType_OTA_END, handle_ota_end);
uart_cmd_register(alox_MessageType_OTA_START_ESPNOW, handle_ota_start_espnow);
}
+6
View File
@@ -0,0 +1,6 @@
#ifndef CMD_OTA_H
#define CMD_OTA_H
void cmd_ota_register(void);
#endif
+37
View File
@@ -0,0 +1,37 @@
#include "cmd_ota_slave_progress.h"
#include "ota_espnow.h"
#include "uart_cmd.h"
#include "esp_log.h"
static const char *TAG = "[OTA_PROG]";
static void handle_ota_slave_progress(const uint8_t *data, size_t len) {
alox_UartMessage uart_msg;
uint32_t filter = 0;
if (uart_cmd_decode(data, len, &uart_msg) == ESP_OK) {
const alox_OtaSlaveProgressRequest *req =
UART_CMD_REQ(&uart_msg, alox_UartMessage_ota_slave_progress_request_tag,
ota_slave_progress_request);
if (req != NULL) {
filter = req->client_id;
}
}
alox_UartMessage response;
uart_cmd_init_response(
&response, alox_MessageType_OTA_SLAVE_PROGRESS,
alox_UartMessage_ota_slave_progress_response_tag);
ota_espnow_progress_query(filter, &response.payload.ota_slave_progress_response);
ESP_LOGI(TAG, "query client_id=%lu -> %u slave(s) active=%d",
(unsigned long)filter,
(unsigned)response.payload.ota_slave_progress_response.slaves_count,
(int)response.payload.ota_slave_progress_response.active);
uart_cmd_send(&response, TAG);
}
void cmd_ota_slave_progress_register(void) {
uart_cmd_register(alox_MessageType_OTA_SLAVE_PROGRESS,
handle_ota_slave_progress);
}
+6
View File
@@ -0,0 +1,6 @@
#ifndef CMD_OTA_SLAVE_PROGRESS_H
#define CMD_OTA_SLAVE_PROGRESS_H
void cmd_ota_slave_progress_register(void);
#endif
+63
View File
@@ -0,0 +1,63 @@
#include "client_registry.h"
#include "cmd_restart.h"
#include "esp_log.h"
#include "esp_now_comm.h"
#include "pod_reboot.h"
#include "uart_cmd.h"
static const char *TAG = "[RESTART_CMD]";
static void reply(bool success, uint32_t client_id) {
alox_UartMessage response;
uart_cmd_init_response(&response, alox_MessageType_RESTART,
alox_UartMessage_restart_response_tag);
response.payload.restart_response.success = success;
response.payload.restart_response.client_id = client_id;
uart_cmd_send(&response, TAG);
}
static void handle_restart(const uint8_t *data, size_t len) {
alox_UartMessage uart_msg;
if (uart_cmd_decode(data, len, &uart_msg) != ESP_OK) {
ESP_LOGW(TAG, "decode failed");
reply(false, 0);
return;
}
const alox_RestartRequest *req = UART_CMD_REQ(
&uart_msg, alox_UartMessage_restart_request_tag, restart_request);
if (req == NULL) {
ESP_LOGW(TAG, "missing restart request");
reply(false, 0);
return;
}
if (req->client_id == 0) {
ESP_LOGI(TAG, "restart master");
reply(true, 0);
pod_schedule_restart();
return;
}
const client_info_t *client = client_registry_find_by_id(req->client_id);
if (client == NULL) {
ESP_LOGW(TAG, "client id %lu not in registry",
(unsigned long)req->client_id);
reply(false, req->client_id);
return;
}
esp_err_t err = esp_now_comm_send_restart(client->mac, req->client_id);
if (err == ESP_OK) {
ESP_LOGI(TAG, "restart sent to slave %lu", (unsigned long)req->client_id);
} else {
ESP_LOGW(TAG, "restart to slave %lu failed: %s",
(unsigned long)req->client_id, esp_err_to_name(err));
}
reply(err == ESP_OK, req->client_id);
}
void cmd_restart_register(void) {
uart_cmd_register(alox_MessageType_RESTART, handle_restart);
}
+6
View File
@@ -0,0 +1,6 @@
#ifndef CMD_RESTART_H
#define CMD_RESTART_H
void cmd_restart_register(void);
#endif
+37
View File
@@ -0,0 +1,37 @@
#include "cmd_version.h"
#include "app_config.h"
#include "uart_cmd.h"
#ifndef POWERPOD_FW_VERSION
#define POWERPOD_FW_VERSION 1u
#endif
#ifndef POWERPOD_GIT_HASH
#define POWERPOD_GIT_HASH "unknown"
#endif
static const char *TAG = "[VERSION]";
static void handle_version(const uint8_t *data, size_t len) {
(void)data;
(void)len;
alox_UartMessage response;
uart_cmd_init_response(&response, alox_MessageType_VERSION,
alox_UartMessage_version_response_tag);
response.payload.version_response.version = POWERPOD_FW_VERSION;
response.payload.version_response.git_hash.funcs.encode = uart_cmd_encode_string;
response.payload.version_response.git_hash.arg = (void *)POWERPOD_GIT_HASH;
const app_config_t *cfg = app_config_get();
if (cfg != NULL && cfg->running_partition[0] != '\0') {
response.payload.version_response.running_partition.funcs.encode =
uart_cmd_encode_string;
response.payload.version_response.running_partition.arg =
(void *)cfg->running_partition;
}
uart_cmd_send(&response, TAG);
}
void cmd_version_register(void) {
uart_cmd_register(alox_MessageType_VERSION, handle_version);
}
+6
View File
@@ -0,0 +1,6 @@
#ifndef CMD_VERSION_H
#define CMD_VERSION_H
void cmd_version_register(void);
#endif