After UART OTA the master reads the staged partition in 4 KiB blocks (200 B ESP-NOW chunks), waits for per-slave block ACKs, and fixes the final partial block. Slaves reuse ota_uart; send pacing and logging improve reliability. Co-authored-by: Cursor <cursoragent@cursor.com>
228 lines
6.7 KiB
C
228 lines
6.7 KiB
C
#include "cmd_ota.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 <string.h>
|
|
|
|
static const char *TAG = "[OTA_CMD]";
|
|
|
|
#define OTA_PREPARE_STACK 8192
|
|
#define OTA_PREPARE_PRIO 5
|
|
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 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_status(OTA_UART_ST_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);
|
|
|
|
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_status(OTA_UART_ST_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_status(OTA_UART_ST_FAILED, 3);
|
|
return;
|
|
}
|
|
|
|
if (ota_uart_is_active()) {
|
|
ESP_LOGW(TAG, "OTA_START while session active");
|
|
send_ota_status(OTA_UART_ST_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_status(OTA_UART_ST_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_status(OTA_UART_ST_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_status(OTA_UART_ST_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_status(OTA_UART_ST_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_status(OTA_UART_ST_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_status(OTA_UART_ST_FAILED, 13);
|
|
return;
|
|
}
|
|
if (r == OTA_FEED_BLOCK_WRITTEN) {
|
|
ESP_LOGI(TAG, "OTA block ack (%lu bytes in flash)",
|
|
(unsigned long)ota_uart_bytes_written());
|
|
send_ota_status(OTA_UART_ST_BLOCK_ACK, 0);
|
|
}
|
|
}
|
|
|
|
static esp_err_t finish_master_ota_and_distribute(void) {
|
|
uint32_t written = ota_uart_bytes_written();
|
|
int slot = ota_uart_target_slot();
|
|
bool success = false;
|
|
esp_err_t err = ota_uart_finish(false, &success);
|
|
if (err != ESP_OK || !success) {
|
|
send_ota_status(OTA_UART_ST_FAILED, (uint32_t)err);
|
|
return err;
|
|
}
|
|
|
|
const esp_partition_t *part = NULL;
|
|
uint32_t image_size = 0;
|
|
if (!ota_uart_get_staged_image(&part, &image_size)) {
|
|
send_ota_status(OTA_UART_ST_FAILED, 30);
|
|
return ESP_ERR_INVALID_STATE;
|
|
}
|
|
|
|
err = ota_espnow_distribute(part, image_size);
|
|
if (err != ESP_OK) {
|
|
ESP_LOGE(TAG, "slave OTA distribution failed: %s", esp_err_to_name(err));
|
|
ota_uart_clear_staged();
|
|
send_ota_status(OTA_UART_ST_FAILED, 31);
|
|
return err;
|
|
}
|
|
|
|
err = ota_uart_apply_boot();
|
|
if (err != ESP_OK) {
|
|
send_ota_status(OTA_UART_ST_FAILED, (uint32_t)err);
|
|
return err;
|
|
}
|
|
|
|
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 = written;
|
|
response.payload.ota_status.target_slot = slot >= 0 ? (uint32_t)slot : 0;
|
|
response.payload.ota_status.error = 0;
|
|
uart_cmd_send(&response, TAG);
|
|
return ESP_OK;
|
|
}
|
|
|
|
static void handle_ota_end(const uint8_t *data, size_t len) {
|
|
(void)data;
|
|
(void)len;
|
|
|
|
if (!ota_uart_is_active()) {
|
|
send_ota_status(OTA_UART_ST_FAILED, 20);
|
|
return;
|
|
}
|
|
|
|
(void)finish_master_ota_and_distribute();
|
|
}
|
|
|
|
static void handle_ota_start_espnow(const uint8_t *data, size_t len) {
|
|
(void)data;
|
|
(void)len;
|
|
|
|
if (ota_uart_is_active()) {
|
|
send_ota_status(OTA_UART_ST_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_status(OTA_UART_ST_FAILED, 41);
|
|
return;
|
|
}
|
|
|
|
esp_err_t err = ota_espnow_distribute(part, image_size);
|
|
if (err != ESP_OK) {
|
|
send_ota_status(OTA_UART_ST_FAILED, 42);
|
|
return;
|
|
}
|
|
|
|
err = ota_uart_apply_boot();
|
|
if (err != ESP_OK) {
|
|
send_ota_status(OTA_UART_ST_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);
|
|
}
|
|
|
|
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);
|
|
}
|