Default brightness is ~5%; UART blink mode and green/red pulses mark OTA success or failure. Failed UART uploads skip ESP-NOW distribution. Co-authored-by: Cursor <cursoragent@cursor.com>
234 lines
5.9 KiB
C
234 lines
5.9 KiB
C
#include "ota_uart.h"
|
|
#include "esp_log.h"
|
|
#include "esp_ota_ops.h"
|
|
#include <string.h>
|
|
|
|
static const char *TAG = "[OTA_UART]";
|
|
|
|
typedef struct {
|
|
bool active;
|
|
esp_ota_handle_t handle;
|
|
const esp_partition_t *update_partition;
|
|
uint32_t total_size;
|
|
uint32_t received;
|
|
uint32_t written;
|
|
int target_slot;
|
|
uint8_t block_buf[OTA_UART_FLASH_BLOCK_SIZE];
|
|
size_t block_len;
|
|
} ota_uart_state_t;
|
|
|
|
static ota_uart_state_t s_ota;
|
|
|
|
static struct {
|
|
bool valid;
|
|
const esp_partition_t *partition;
|
|
uint32_t size;
|
|
int target_slot;
|
|
} s_staged;
|
|
|
|
static int partition_slot(const esp_partition_t *part) {
|
|
if (part == NULL) {
|
|
return -1;
|
|
}
|
|
if (part->subtype == ESP_PARTITION_SUBTYPE_APP_OTA_0) {
|
|
return 0;
|
|
}
|
|
if (part->subtype == ESP_PARTITION_SUBTYPE_APP_OTA_1) {
|
|
return 1;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
bool ota_uart_is_active(void) { return s_ota.active; }
|
|
|
|
int ota_uart_target_slot(void) {
|
|
return s_ota.active ? s_ota.target_slot : -1;
|
|
}
|
|
|
|
void ota_uart_abort(void) {
|
|
if (!s_ota.active) {
|
|
return;
|
|
}
|
|
esp_ota_abort(s_ota.handle);
|
|
memset(&s_ota, 0, sizeof(s_ota));
|
|
}
|
|
|
|
static esp_err_t flush_block(void) {
|
|
if (s_ota.block_len == 0) {
|
|
return ESP_OK;
|
|
}
|
|
esp_err_t err =
|
|
esp_ota_write(s_ota.handle, s_ota.block_buf, s_ota.block_len);
|
|
if (err != ESP_OK) {
|
|
ESP_LOGE(TAG, "esp_ota_write %u bytes failed: %s", (unsigned)s_ota.block_len,
|
|
esp_err_to_name(err));
|
|
return err;
|
|
}
|
|
s_ota.written += (uint32_t)s_ota.block_len;
|
|
s_ota.block_len = 0;
|
|
return ESP_OK;
|
|
}
|
|
|
|
int ota_uart_prepare(uint32_t total_size) {
|
|
if (s_ota.active) {
|
|
ESP_LOGW(TAG, "OTA already active");
|
|
return -1;
|
|
}
|
|
|
|
const esp_partition_t *running = esp_ota_get_running_partition();
|
|
const esp_partition_t *update_partition =
|
|
esp_ota_get_next_update_partition(NULL);
|
|
if (update_partition == NULL) {
|
|
ESP_LOGE(TAG, "no OTA update partition");
|
|
return -1;
|
|
}
|
|
|
|
ESP_LOGI(TAG, "running=%s, update=%s, image_size=%lu",
|
|
running != NULL ? running->label : "?",
|
|
update_partition->label, (unsigned long)total_size);
|
|
|
|
if (total_size > 0 && total_size > update_partition->size) {
|
|
ESP_LOGE(TAG, "image too large (%lu > %lu)", (unsigned long)total_size,
|
|
(unsigned long)update_partition->size);
|
|
return -1;
|
|
}
|
|
|
|
esp_ota_handle_t handle;
|
|
esp_err_t err = esp_ota_begin(update_partition, OTA_SIZE_UNKNOWN, &handle);
|
|
if (err != ESP_OK) {
|
|
ESP_LOGE(TAG, "esp_ota_begin failed: %s", esp_err_to_name(err));
|
|
return -1;
|
|
}
|
|
|
|
memset(&s_ota, 0, sizeof(s_ota));
|
|
s_ota.active = true;
|
|
s_ota.handle = handle;
|
|
s_ota.update_partition = update_partition;
|
|
s_ota.total_size = total_size;
|
|
s_ota.target_slot = partition_slot(update_partition);
|
|
|
|
ESP_LOGI(TAG, "OTA prepared, target slot %d (%s) — send 4 KiB chunks",
|
|
s_ota.target_slot, update_partition->label);
|
|
return s_ota.target_slot;
|
|
}
|
|
|
|
ota_feed_result_t ota_uart_feed(const uint8_t *data, size_t len) {
|
|
if (!s_ota.active || data == NULL || len == 0) {
|
|
return OTA_FEED_ERROR;
|
|
}
|
|
if (len > OTA_UART_HOST_CHUNK_SIZE) {
|
|
ESP_LOGW(TAG, "chunk %u > %u, truncating", (unsigned)len,
|
|
OTA_UART_HOST_CHUNK_SIZE);
|
|
len = OTA_UART_HOST_CHUNK_SIZE;
|
|
}
|
|
|
|
bool block_written = false;
|
|
size_t offset = 0;
|
|
while (offset < len) {
|
|
size_t space = OTA_UART_FLASH_BLOCK_SIZE - s_ota.block_len;
|
|
size_t n = len - offset;
|
|
if (n > space) {
|
|
n = space;
|
|
}
|
|
memcpy(s_ota.block_buf + s_ota.block_len, data + offset, n);
|
|
s_ota.block_len += n;
|
|
s_ota.received += (uint32_t)n;
|
|
offset += n;
|
|
|
|
if (s_ota.block_len < OTA_UART_FLASH_BLOCK_SIZE) {
|
|
continue;
|
|
}
|
|
|
|
if (flush_block() != ESP_OK) {
|
|
ota_uart_abort();
|
|
return OTA_FEED_ERROR;
|
|
}
|
|
block_written = true;
|
|
}
|
|
|
|
return block_written ? OTA_FEED_BLOCK_WRITTEN : OTA_FEED_OK;
|
|
}
|
|
|
|
uint32_t ota_uart_bytes_written(void) { return s_ota.written; }
|
|
|
|
uint32_t ota_uart_bytes_received(void) { return s_ota.received; }
|
|
|
|
uint32_t ota_uart_total_size(void) { return s_ota.active ? s_ota.total_size : 0; }
|
|
|
|
bool ota_uart_get_staged_image(const esp_partition_t **partition_out,
|
|
uint32_t *size_out) {
|
|
if (!s_staged.valid || s_staged.partition == NULL) {
|
|
return false;
|
|
}
|
|
if (partition_out != NULL) {
|
|
*partition_out = s_staged.partition;
|
|
}
|
|
if (size_out != NULL) {
|
|
*size_out = s_staged.size;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void ota_uart_clear_staged(void) { memset(&s_staged, 0, sizeof(s_staged)); }
|
|
|
|
esp_err_t ota_uart_apply_boot(void) {
|
|
if (!s_staged.valid || s_staged.partition == NULL) {
|
|
return ESP_ERR_INVALID_STATE;
|
|
}
|
|
esp_err_t err = esp_ota_set_boot_partition(s_staged.partition);
|
|
if (err != ESP_OK) {
|
|
ESP_LOGE(TAG, "esp_ota_set_boot_partition failed: %s", esp_err_to_name(err));
|
|
return err;
|
|
}
|
|
ESP_LOGI(TAG, "boot partition set to %s (slot %d)",
|
|
s_staged.partition->label, s_staged.target_slot);
|
|
ota_uart_clear_staged();
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t ota_uart_finish(bool set_boot, bool *success_out) {
|
|
if (success_out != NULL) {
|
|
*success_out = false;
|
|
}
|
|
if (!s_ota.active) {
|
|
return ESP_ERR_INVALID_STATE;
|
|
}
|
|
|
|
esp_err_t err = flush_block();
|
|
if (err != ESP_OK) {
|
|
ota_uart_abort();
|
|
return err;
|
|
}
|
|
|
|
err = esp_ota_end(s_ota.handle);
|
|
if (err != ESP_OK) {
|
|
ESP_LOGE(TAG, "esp_ota_end failed: %s", esp_err_to_name(err));
|
|
ota_uart_abort();
|
|
return err;
|
|
}
|
|
|
|
ota_uart_clear_staged();
|
|
s_staged.valid = true;
|
|
s_staged.partition = s_ota.update_partition;
|
|
s_staged.size = s_ota.written;
|
|
s_staged.target_slot = s_ota.target_slot;
|
|
|
|
ESP_LOGI(TAG, "OTA image staged: %lu bytes on %s (slot %d)",
|
|
(unsigned long)s_staged.size, s_staged.partition->label,
|
|
s_staged.target_slot);
|
|
|
|
memset(&s_ota, 0, sizeof(s_ota));
|
|
|
|
if (set_boot) {
|
|
err = ota_uart_apply_boot();
|
|
if (err != ESP_OK) {
|
|
return err;
|
|
}
|
|
}
|
|
|
|
if (success_out != NULL) {
|
|
*success_out = true;
|
|
}
|
|
return ESP_OK;
|
|
}
|