Fix web OTA upload and isolate OTA sessions across firmware and goTool.
Split ESP-NOW into core/master/slave modules, block non-OTA UART traffic during updates, and hold the host serial port exclusively so dashboard polling cannot interleave with firmware uploads. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+160
-41
@@ -35,7 +35,28 @@ static const char *TAG = "[OTA_ESPNOW]";
|
||||
|
||||
#define OTA_MAX_TARGETS CLIENT_REGISTRY_MAX
|
||||
|
||||
#define OTA_SLAVE_WORK_QUEUE_LEN 12
|
||||
#define OTA_SLAVE_WORK_STACK 8192
|
||||
#define OTA_SLAVE_WORK_PRIO 5
|
||||
|
||||
typedef enum {
|
||||
OTA_SLAVE_WORK_STATUS = 1,
|
||||
OTA_SLAVE_WORK_PAYLOAD,
|
||||
OTA_SLAVE_WORK_END,
|
||||
} ota_slave_work_op_t;
|
||||
|
||||
typedef struct {
|
||||
ota_slave_work_op_t op;
|
||||
uint8_t master_mac[6];
|
||||
uint32_t status;
|
||||
uint32_t bytes_written;
|
||||
uint32_t error;
|
||||
alox_EspNowOtaPayload payload;
|
||||
} ota_slave_work_t;
|
||||
|
||||
static EventGroupHandle_t s_eg;
|
||||
static QueueHandle_t s_slave_work_queue;
|
||||
static bool s_distribution_active;
|
||||
|
||||
typedef struct {
|
||||
uint8_t count;
|
||||
@@ -152,57 +173,38 @@ static bool wait_target_bits(uint32_t want_bits, uint32_t timeout_ms) {
|
||||
return (got & want_bits) == want_bits;
|
||||
}
|
||||
|
||||
bool ota_espnow_distribution_active(void) { return s_distribution_active; }
|
||||
|
||||
static void send_slave_status(const uint8_t master_mac[6], uint32_t status,
|
||||
uint32_t bytes_written, uint32_t error) {
|
||||
esp_now_comm_send_ota_status(master_mac, status, bytes_written, error);
|
||||
}
|
||||
|
||||
static void ota_slave_prepare_task(void *param) {
|
||||
uint32_t total_size = (uint32_t)(uintptr_t)param;
|
||||
uint8_t master_mac[6];
|
||||
|
||||
if (!esp_now_comm_get_master_mac(master_mac)) {
|
||||
vTaskDelete(NULL);
|
||||
return;
|
||||
static bool queue_slave_work(const ota_slave_work_t *work) {
|
||||
if (work == NULL || s_slave_work_queue == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
send_slave_status(master_mac, OTA_ST_PREPARING, 0, 0);
|
||||
|
||||
int slot = ota_uart_prepare(total_size);
|
||||
if (slot < 0) {
|
||||
send_slave_status(master_mac, OTA_ST_FAILED, 0, 1);
|
||||
vTaskDelete(NULL);
|
||||
return;
|
||||
if (xQueueSend(s_slave_work_queue, work, 0) != pdTRUE) {
|
||||
ESP_LOGW(TAG, "slave OTA work queue full (op=%d)", (int)work->op);
|
||||
return false;
|
||||
}
|
||||
|
||||
send_slave_status(master_mac, OTA_ST_READY, 0, 0);
|
||||
led_ring_show_ota_progress(0, total_size, OTA_LED_ESPNOW_RX_R, OTA_LED_ESPNOW_RX_G,
|
||||
OTA_LED_ESPNOW_RX_B);
|
||||
vTaskDelete(NULL);
|
||||
return true;
|
||||
}
|
||||
|
||||
void ota_espnow_slave_on_start(const uint8_t master_mac[6],
|
||||
const alox_EspNowOtaStart *start) {
|
||||
if (start == NULL || start->total_size == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "ESP-NOW OTA_START (%lu bytes)", (unsigned long)start->total_size);
|
||||
|
||||
if (ota_uart_is_active()) {
|
||||
send_slave_status(master_mac, OTA_ST_FAILED, 0, 4);
|
||||
return;
|
||||
}
|
||||
|
||||
if (xTaskCreate(ota_slave_prepare_task, "ota_esp_prep", OTA_ESPNOW_PREPARE_STACK,
|
||||
(void *)(uintptr_t)start->total_size, OTA_ESPNOW_PREPARE_PRIO,
|
||||
NULL) != pdPASS) {
|
||||
send_slave_status(master_mac, OTA_ST_FAILED, 0, 5);
|
||||
}
|
||||
static void queue_slave_status(const uint8_t master_mac[6], uint32_t status,
|
||||
uint32_t bytes_written, uint32_t error) {
|
||||
ota_slave_work_t work = {
|
||||
.op = OTA_SLAVE_WORK_STATUS,
|
||||
.status = status,
|
||||
.bytes_written = bytes_written,
|
||||
.error = error,
|
||||
};
|
||||
memcpy(work.master_mac, master_mac, 6);
|
||||
(void)queue_slave_work(&work);
|
||||
}
|
||||
|
||||
void ota_espnow_slave_on_payload(const uint8_t master_mac[6],
|
||||
const alox_EspNowOtaPayload *payload) {
|
||||
static void process_slave_payload(const uint8_t master_mac[6],
|
||||
const alox_EspNowOtaPayload *payload) {
|
||||
if (payload == NULL || payload->data.size == 0) {
|
||||
send_slave_status(master_mac, OTA_ST_FAILED, 0, 11);
|
||||
return;
|
||||
@@ -246,7 +248,7 @@ void ota_espnow_slave_on_payload(const uint8_t master_mac[6],
|
||||
}
|
||||
}
|
||||
|
||||
void ota_espnow_slave_on_end(const uint8_t master_mac[6]) {
|
||||
static void process_slave_end(const uint8_t master_mac[6]) {
|
||||
ESP_LOGI(TAG, "ESP-NOW OTA_END");
|
||||
if (!ota_uart_is_active()) {
|
||||
send_slave_status(master_mac, OTA_ST_FAILED, 0, 20);
|
||||
@@ -268,6 +270,113 @@ void ota_espnow_slave_on_end(const uint8_t master_mac[6]) {
|
||||
(unsigned long)written);
|
||||
}
|
||||
|
||||
static void ota_slave_work_task(void *param) {
|
||||
(void)param;
|
||||
ota_slave_work_t work;
|
||||
|
||||
while (1) {
|
||||
if (xQueueReceive(s_slave_work_queue, &work, portMAX_DELAY) != pdTRUE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (work.op) {
|
||||
case OTA_SLAVE_WORK_STATUS:
|
||||
send_slave_status(work.master_mac, work.status, work.bytes_written,
|
||||
work.error);
|
||||
break;
|
||||
case OTA_SLAVE_WORK_PAYLOAD:
|
||||
process_slave_payload(work.master_mac, &work.payload);
|
||||
break;
|
||||
case OTA_SLAVE_WORK_END:
|
||||
process_slave_end(work.master_mac);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ota_espnow_slave_init(void) {
|
||||
if (s_slave_work_queue != NULL) {
|
||||
return;
|
||||
}
|
||||
s_slave_work_queue = xQueueCreate(OTA_SLAVE_WORK_QUEUE_LEN, sizeof(ota_slave_work_t));
|
||||
if (s_slave_work_queue == NULL) {
|
||||
ESP_LOGE(TAG, "failed to create slave OTA work queue");
|
||||
return;
|
||||
}
|
||||
if (xTaskCreate(ota_slave_work_task, "ota_slave_wrk", OTA_SLAVE_WORK_STACK, NULL,
|
||||
OTA_SLAVE_WORK_PRIO, NULL) != pdPASS) {
|
||||
ESP_LOGE(TAG, "failed to create slave OTA work task");
|
||||
}
|
||||
}
|
||||
|
||||
static void ota_slave_prepare_task(void *param) {
|
||||
uint32_t total_size = (uint32_t)(uintptr_t)param;
|
||||
uint8_t master_mac[6];
|
||||
|
||||
if (!esp_now_comm_get_master_mac(master_mac)) {
|
||||
vTaskDelete(NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
send_slave_status(master_mac, OTA_ST_PREPARING, 0, 0);
|
||||
|
||||
int slot = ota_uart_prepare(total_size);
|
||||
if (slot < 0) {
|
||||
send_slave_status(master_mac, OTA_ST_FAILED, 0, 1);
|
||||
vTaskDelete(NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
send_slave_status(master_mac, OTA_ST_READY, 0, 0);
|
||||
led_ring_show_ota_progress(0, total_size, OTA_LED_ESPNOW_RX_R, OTA_LED_ESPNOW_RX_G,
|
||||
OTA_LED_ESPNOW_RX_B);
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
void ota_espnow_slave_on_start(const uint8_t master_mac[6],
|
||||
const alox_EspNowOtaStart *start) {
|
||||
if (start == NULL || start->total_size == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "ESP-NOW OTA_START (%lu bytes)", (unsigned long)start->total_size);
|
||||
|
||||
if (ota_uart_is_active()) {
|
||||
queue_slave_status(master_mac, OTA_ST_FAILED, 0, 4);
|
||||
return;
|
||||
}
|
||||
|
||||
if (xTaskCreate(ota_slave_prepare_task, "ota_esp_prep", OTA_ESPNOW_PREPARE_STACK,
|
||||
(void *)(uintptr_t)start->total_size, OTA_ESPNOW_PREPARE_PRIO,
|
||||
NULL) != pdPASS) {
|
||||
queue_slave_status(master_mac, OTA_ST_FAILED, 0, 5);
|
||||
}
|
||||
}
|
||||
|
||||
void ota_espnow_slave_on_payload(const uint8_t master_mac[6],
|
||||
const alox_EspNowOtaPayload *payload) {
|
||||
if (payload == NULL) {
|
||||
queue_slave_status(master_mac, OTA_ST_FAILED, 0, 11);
|
||||
return;
|
||||
}
|
||||
|
||||
ota_slave_work_t work = {.op = OTA_SLAVE_WORK_PAYLOAD, .payload = *payload};
|
||||
memcpy(work.master_mac, master_mac, 6);
|
||||
if (!queue_slave_work(&work)) {
|
||||
queue_slave_status(master_mac, OTA_ST_FAILED, 0, 14);
|
||||
}
|
||||
}
|
||||
|
||||
void ota_espnow_slave_on_end(const uint8_t master_mac[6]) {
|
||||
ota_slave_work_t work = {.op = OTA_SLAVE_WORK_END};
|
||||
memcpy(work.master_mac, master_mac, 6);
|
||||
if (!queue_slave_work(&work)) {
|
||||
queue_slave_status(master_mac, OTA_ST_FAILED, 0, 15);
|
||||
}
|
||||
}
|
||||
|
||||
void ota_espnow_master_on_status(const uint8_t slave_mac[6],
|
||||
const alox_EspNowOtaStatus *status) {
|
||||
if (status == NULL || s_eg == NULL) {
|
||||
@@ -342,6 +451,8 @@ static esp_err_t distribute_image(const esp_partition_t *partition,
|
||||
}
|
||||
}
|
||||
|
||||
s_distribution_active = true;
|
||||
|
||||
memset(&s_dist.progress, 0, sizeof(s_dist.progress));
|
||||
if (progress != NULL) {
|
||||
s_dist.progress = *progress;
|
||||
@@ -362,6 +473,7 @@ static esp_err_t distribute_image(const esp_partition_t *partition,
|
||||
ESP_LOGW(TAG, "OTA_START to slave %lu failed",
|
||||
(unsigned long)s_dist.id[i]);
|
||||
prog_end();
|
||||
s_distribution_active = false;
|
||||
return err;
|
||||
}
|
||||
}
|
||||
@@ -369,6 +481,7 @@ static esp_err_t distribute_image(const esp_partition_t *partition,
|
||||
if (!wait_target_bits(target_mask, OTA_PREPARE_TIMEOUT_MS)) {
|
||||
ESP_LOGE(TAG, "timeout waiting for slave OTA ready");
|
||||
prog_end();
|
||||
s_distribution_active = false;
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
|
||||
@@ -392,6 +505,7 @@ static esp_err_t distribute_image(const esp_partition_t *partition,
|
||||
ESP_LOGE(TAG, "partition read @%lu failed: %s", (unsigned long)offset,
|
||||
esp_err_to_name(err));
|
||||
prog_end();
|
||||
s_distribution_active = false;
|
||||
return err;
|
||||
}
|
||||
|
||||
@@ -407,6 +521,7 @@ static esp_err_t distribute_image(const esp_partition_t *partition,
|
||||
block_buf + sent, chunk);
|
||||
if (err != ESP_OK) {
|
||||
prog_end();
|
||||
s_distribution_active = false;
|
||||
return err;
|
||||
}
|
||||
}
|
||||
@@ -424,6 +539,7 @@ static esp_err_t distribute_image(const esp_partition_t *partition,
|
||||
ESP_LOGE(TAG, "timeout block ack @%lu bytes",
|
||||
(unsigned long)s_dist.expected_bytes);
|
||||
prog_end();
|
||||
s_distribution_active = false;
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
ESP_LOGI(TAG, "block ack @%lu/%lu (%lu%%)",
|
||||
@@ -445,6 +561,7 @@ static esp_err_t distribute_image(const esp_partition_t *partition,
|
||||
err = esp_now_comm_send_ota_end(s_dist.mac[i]);
|
||||
if (err != ESP_OK) {
|
||||
prog_end();
|
||||
s_distribution_active = false;
|
||||
return err;
|
||||
}
|
||||
}
|
||||
@@ -452,11 +569,13 @@ static esp_err_t distribute_image(const esp_partition_t *partition,
|
||||
if (!wait_target_bits(target_mask, OTA_END_TIMEOUT_MS)) {
|
||||
ESP_LOGE(TAG, "timeout waiting for slave OTA success");
|
||||
prog_end();
|
||||
s_distribution_active = false;
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
|
||||
prog_set_aggregate(size);
|
||||
prog_end();
|
||||
s_distribution_active = false;
|
||||
ESP_LOGI(TAG, "ESP-NOW OTA complete for %u slave(s)", (unsigned)s_dist.count);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user