feat: ESP32-S3 OTA stack — partitions, Gitea checker, self-update, UART IO, display, Orin serial trigger (6 beads) #731
241
esp32s3/balance/main/uart_ota.c
Normal file
241
esp32s3/balance/main/uart_ota.c
Normal file
@ -0,0 +1,241 @@
|
||||
/* uart_ota.c — UART OTA sender: Balance→IO board (bd-21hv)
|
||||
*
|
||||
* Downloads io-firmware.bin from Gitea, then sends to IO board via UART1.
|
||||
* IO board must update itself BEFORE Balance self-update (per spec).
|
||||
*/
|
||||
|
||||
#include "uart_ota.h"
|
||||
#include "gitea_ota.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_http_client.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "mbedtls/sha256.h"
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
static const char *TAG = "uart_ota";
|
||||
|
||||
volatile uart_ota_send_state_t g_uart_ota_state = UART_OTA_S_IDLE;
|
||||
volatile uint8_t g_uart_ota_progress = 0;
|
||||
|
||||
/* ── CRC8-SMBUS ── */
|
||||
static uint8_t crc8(const uint8_t *d, uint16_t len)
|
||||
{
|
||||
uint8_t crc = 0;
|
||||
for (uint16_t i = 0; i < len; i++) {
|
||||
crc ^= d[i];
|
||||
for (uint8_t b = 0; b < 8; b++)
|
||||
crc = (crc & 0x80u) ? (uint8_t)((crc << 1u) ^ 0x07u) : (uint8_t)(crc << 1u);
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
/* ── Build and send one UART OTA frame ── */
|
||||
static void send_frame(uint8_t type, uint16_t seq,
|
||||
const uint8_t *payload, uint16_t plen)
|
||||
{
|
||||
/* [TYPE:1][SEQ:2 BE][LEN:2 BE][PAYLOAD][CRC8:1] */
|
||||
uint8_t hdr[5];
|
||||
hdr[0] = type;
|
||||
hdr[1] = (uint8_t)(seq >> 8u);
|
||||
hdr[2] = (uint8_t)(seq);
|
||||
hdr[3] = (uint8_t)(plen >> 8u);
|
||||
hdr[4] = (uint8_t)(plen);
|
||||
|
||||
/* CRC over hdr + payload */
|
||||
uint8_t crc_buf[5 + OTA_UART_CHUNK_SIZE];
|
||||
memcpy(crc_buf, hdr, 5);
|
||||
if (plen > 0 && payload) memcpy(crc_buf + 5, payload, plen);
|
||||
uint8_t crc = crc8(crc_buf, (uint16_t)(5 + plen));
|
||||
|
||||
uart_write_bytes(UART_OTA_PORT, (char *)hdr, 5);
|
||||
if (plen > 0 && payload)
|
||||
uart_write_bytes(UART_OTA_PORT, (char *)payload, plen);
|
||||
uart_write_bytes(UART_OTA_PORT, (char *)&crc, 1);
|
||||
}
|
||||
|
||||
/* ── Wait for ACK/NACK from IO board ── */
|
||||
static bool wait_ack(uint16_t expected_seq)
|
||||
{
|
||||
/* Response frame: [TYPE:1][SEQ:2][LEN:2][PAYLOAD][CRC:1] */
|
||||
uint8_t buf[16];
|
||||
int timeout = OTA_UART_ACK_TIMEOUT_MS;
|
||||
int got = 0;
|
||||
|
||||
while (timeout > 0 && got < 6) {
|
||||
int r = uart_read_bytes(UART_OTA_PORT, buf + got, 1, pdMS_TO_TICKS(50));
|
||||
if (r > 0) got++;
|
||||
else timeout -= 50;
|
||||
}
|
||||
|
||||
if (got < 3) return false;
|
||||
|
||||
uint8_t type = buf[0];
|
||||
uint16_t seq = (uint16_t)((buf[1] << 8u) | buf[2]);
|
||||
|
||||
if (type == UART_OTA_ACK && seq == expected_seq) return true;
|
||||
if (type == UART_OTA_NACK) {
|
||||
uint8_t err = (got >= 6) ? buf[5] : 0;
|
||||
ESP_LOGW(TAG, "NACK seq=%u err=%u", seq, err);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* ── Download firmware to RAM buffer (max 1.75 MB) ── */
|
||||
static uint8_t *download_io_firmware(uint32_t *out_size)
|
||||
{
|
||||
const char *url = g_io_update.download_url;
|
||||
ESP_LOGI(TAG, "downloading IO fw: %s", url);
|
||||
|
||||
esp_http_client_config_t cfg = {
|
||||
.url = url, .timeout_ms = 30000,
|
||||
.skip_cert_common_name_check = true,
|
||||
};
|
||||
esp_http_client_handle_t client = esp_http_client_init(&cfg);
|
||||
if (esp_http_client_open(client, 0) != ESP_OK) {
|
||||
esp_http_client_cleanup(client);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int content_len = esp_http_client_fetch_headers(client);
|
||||
if (content_len <= 0 || content_len > (int)(0x1B0000)) {
|
||||
ESP_LOGE(TAG, "bad content-length: %d", content_len);
|
||||
esp_http_client_cleanup(client);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint8_t *buf = malloc(content_len);
|
||||
if (!buf) {
|
||||
ESP_LOGE(TAG, "malloc %d failed", content_len);
|
||||
esp_http_client_cleanup(client);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int total = 0, rd;
|
||||
while ((rd = esp_http_client_read(client, (char *)buf + total,
|
||||
content_len - total)) > 0) {
|
||||
total += rd;
|
||||
g_uart_ota_progress = (uint8_t)((total * 50) / content_len); /* 0-50% for download */
|
||||
}
|
||||
esp_http_client_cleanup(client);
|
||||
|
||||
if (total != content_len) {
|
||||
free(buf);
|
||||
return NULL;
|
||||
}
|
||||
*out_size = (uint32_t)total;
|
||||
return buf;
|
||||
}
|
||||
|
||||
/* ── UART OTA send task ── */
|
||||
static void uart_ota_task(void *arg)
|
||||
{
|
||||
g_uart_ota_state = UART_OTA_S_DOWNLOADING;
|
||||
g_uart_ota_progress = 0;
|
||||
|
||||
uint32_t fw_size = 0;
|
||||
uint8_t *fw = download_io_firmware(&fw_size);
|
||||
if (!fw) {
|
||||
ESP_LOGE(TAG, "download failed");
|
||||
g_uart_ota_state = UART_OTA_S_FAILED;
|
||||
vTaskDelete(NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Compute SHA256 of downloaded firmware */
|
||||
uint8_t digest[32];
|
||||
mbedtls_sha256_context sha;
|
||||
mbedtls_sha256_init(&sha);
|
||||
mbedtls_sha256_starts(&sha, 0);
|
||||
mbedtls_sha256_update(&sha, fw, fw_size);
|
||||
mbedtls_sha256_finish(&sha, digest);
|
||||
mbedtls_sha256_free(&sha);
|
||||
|
||||
g_uart_ota_state = UART_OTA_S_SENDING;
|
||||
|
||||
/* Send OTA_BEGIN: uint32 size + uint8[32] sha256 */
|
||||
uint8_t begin_payload[36];
|
||||
begin_payload[0] = (uint8_t)(fw_size >> 24u);
|
||||
begin_payload[1] = (uint8_t)(fw_size >> 16u);
|
||||
begin_payload[2] = (uint8_t)(fw_size >> 8u);
|
||||
begin_payload[3] = (uint8_t)(fw_size);
|
||||
memcpy(&begin_payload[4], digest, 32);
|
||||
|
||||
for (int retry = 0; retry < OTA_UART_MAX_RETRIES; retry++) {
|
||||
send_frame(UART_OTA_BEGIN, 0, begin_payload, 36);
|
||||
if (wait_ack(0)) goto send_data;
|
||||
ESP_LOGW(TAG, "BEGIN retry %d", retry);
|
||||
}
|
||||
ESP_LOGE(TAG, "BEGIN failed");
|
||||
free(fw);
|
||||
g_uart_ota_state = UART_OTA_S_FAILED;
|
||||
vTaskDelete(NULL);
|
||||
return;
|
||||
|
||||
send_data: {
|
||||
uint32_t offset = 0;
|
||||
uint16_t seq = 1;
|
||||
while (offset < fw_size) {
|
||||
uint16_t chunk = (uint16_t)((fw_size - offset) < OTA_UART_CHUNK_SIZE
|
||||
? (fw_size - offset) : OTA_UART_CHUNK_SIZE);
|
||||
bool acked = false;
|
||||
for (int retry = 0; retry < OTA_UART_MAX_RETRIES; retry++) {
|
||||
send_frame(UART_OTA_DATA, seq, fw + offset, chunk);
|
||||
if (wait_ack(seq)) { acked = true; break; }
|
||||
ESP_LOGW(TAG, "DATA seq=%u retry=%d", seq, retry);
|
||||
}
|
||||
if (!acked) {
|
||||
ESP_LOGE(TAG, "DATA seq=%u failed", seq);
|
||||
send_frame(UART_OTA_ABORT, seq, NULL, 0);
|
||||
free(fw);
|
||||
g_uart_ota_state = UART_OTA_S_FAILED;
|
||||
vTaskDelete(NULL);
|
||||
return;
|
||||
}
|
||||
offset += chunk;
|
||||
seq++;
|
||||
/* 50-100% for sending phase */
|
||||
g_uart_ota_progress = (uint8_t)(50u + (offset * 50u) / fw_size);
|
||||
}
|
||||
|
||||
/* Send OTA_END */
|
||||
for (int retry = 0; retry < OTA_UART_MAX_RETRIES; retry++) {
|
||||
send_frame(UART_OTA_END, seq, NULL, 0);
|
||||
if (wait_ack(seq)) break;
|
||||
}
|
||||
}
|
||||
|
||||
free(fw);
|
||||
g_uart_ota_progress = 100;
|
||||
g_uart_ota_state = UART_OTA_S_DONE;
|
||||
ESP_LOGI(TAG, "IO OTA complete — %lu bytes sent", (unsigned long)fw_size);
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
bool uart_ota_trigger(void)
|
||||
{
|
||||
if (!g_io_update.available) {
|
||||
ESP_LOGW(TAG, "no IO update available");
|
||||
return false;
|
||||
}
|
||||
if (g_uart_ota_state != UART_OTA_S_IDLE) {
|
||||
ESP_LOGW(TAG, "UART OTA busy (state=%d)", g_uart_ota_state);
|
||||
return false;
|
||||
}
|
||||
/* Init UART1 for OTA */
|
||||
uart_config_t ucfg = {
|
||||
.baud_rate = UART_OTA_BAUD,
|
||||
.data_bits = UART_DATA_8_BITS,
|
||||
.parity = UART_PARITY_DISABLE,
|
||||
.stop_bits = UART_STOP_BITS_1,
|
||||
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
|
||||
};
|
||||
uart_param_config(UART_OTA_PORT, &ucfg);
|
||||
uart_set_pin(UART_OTA_PORT, UART_OTA_TX_GPIO, UART_OTA_RX_GPIO,
|
||||
UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
|
||||
uart_driver_install(UART_OTA_PORT, 2048, 0, 0, NULL, 0);
|
||||
|
||||
xTaskCreate(uart_ota_task, "uart_ota", 16384, NULL, 4, NULL);
|
||||
return true;
|
||||
}
|
||||
64
esp32s3/balance/main/uart_ota.h
Normal file
64
esp32s3/balance/main/uart_ota.h
Normal file
@ -0,0 +1,64 @@
|
||||
#pragma once
|
||||
/* uart_ota.h — UART OTA protocol for Balance→IO firmware update (bd-21hv)
|
||||
*
|
||||
* Balance downloads io-firmware.bin from Gitea, then streams it to the IO
|
||||
* board over UART1 (GPIO17/18, 460800 baud) in 1 KB chunks with ACK.
|
||||
*
|
||||
* Protocol frame format (both directions):
|
||||
* [TYPE:1][SEQ:2 BE][LEN:2 BE][PAYLOAD:LEN][CRC8:1]
|
||||
* CRC8-SMBUS over TYPE+SEQ+LEN+PAYLOAD.
|
||||
*
|
||||
* Balance→IO:
|
||||
* OTA_BEGIN (0xC0) payload: uint32 total_size BE + uint8[32] sha256
|
||||
* OTA_DATA (0xC1) payload: uint8[] chunk (up to 1024 bytes)
|
||||
* OTA_END (0xC2) no payload
|
||||
* OTA_ABORT (0xC3) no payload
|
||||
*
|
||||
* IO→Balance:
|
||||
* OTA_ACK (0xC4) payload: uint16 acked_seq BE
|
||||
* OTA_NACK (0xC5) payload: uint16 failed_seq BE + uint8 err_code
|
||||
* OTA_STATUS (0xC6) payload: uint8 state + uint8 progress%
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/* UART for Balance→IO OTA */
|
||||
#include "driver/uart.h"
|
||||
#define UART_OTA_PORT UART_NUM_1
|
||||
#define UART_OTA_BAUD 460800
|
||||
#define UART_OTA_TX_GPIO 17
|
||||
#define UART_OTA_RX_GPIO 18
|
||||
|
||||
#define OTA_UART_CHUNK_SIZE 1024
|
||||
#define OTA_UART_ACK_TIMEOUT_MS 3000
|
||||
#define OTA_UART_MAX_RETRIES 3
|
||||
|
||||
/* Frame type bytes */
|
||||
#define UART_OTA_BEGIN 0xC0u
|
||||
#define UART_OTA_DATA 0xC1u
|
||||
#define UART_OTA_END 0xC2u
|
||||
#define UART_OTA_ABORT 0xC3u
|
||||
#define UART_OTA_ACK 0xC4u
|
||||
#define UART_OTA_NACK 0xC5u
|
||||
#define UART_OTA_STATUS 0xC6u
|
||||
|
||||
/* NACK error codes */
|
||||
#define OTA_ERR_BAD_CRC 0x01u
|
||||
#define OTA_ERR_WRITE 0x02u
|
||||
#define OTA_ERR_SIZE 0x03u
|
||||
|
||||
typedef enum {
|
||||
UART_OTA_S_IDLE = 0,
|
||||
UART_OTA_S_DOWNLOADING, /* downloading from Gitea */
|
||||
UART_OTA_S_SENDING, /* sending to IO board */
|
||||
UART_OTA_S_DONE,
|
||||
UART_OTA_S_FAILED,
|
||||
} uart_ota_send_state_t;
|
||||
|
||||
extern volatile uart_ota_send_state_t g_uart_ota_state;
|
||||
extern volatile uint8_t g_uart_ota_progress;
|
||||
|
||||
/* Trigger IO firmware update. Uses g_io_update (from gitea_ota).
|
||||
* Downloads bin, then streams via UART. Returns false if busy or no update. */
|
||||
bool uart_ota_trigger(void);
|
||||
4
esp32s3/io/main/CMakeLists.txt
Normal file
4
esp32s3/io/main/CMakeLists.txt
Normal file
@ -0,0 +1,4 @@
|
||||
idf_component_register(
|
||||
SRCS "main.c" "uart_ota_recv.c"
|
||||
INCLUDE_DIRS "."
|
||||
)
|
||||
42
esp32s3/io/main/main.c
Normal file
42
esp32s3/io/main/main.c
Normal file
@ -0,0 +1,42 @@
|
||||
/* main.c — ESP32-S3 IO board app_main */
|
||||
|
||||
#include "uart_ota_recv.h"
|
||||
#include "config.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_ota_ops.h"
|
||||
#include "driver/uart.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
static const char *TAG = "io_main";
|
||||
|
||||
static void uart_init(void)
|
||||
{
|
||||
uart_config_t cfg = {
|
||||
.baud_rate = IO_UART_BAUD,
|
||||
.data_bits = UART_DATA_8_BITS,
|
||||
.parity = UART_PARITY_DISABLE,
|
||||
.stop_bits = UART_STOP_BITS_1,
|
||||
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
|
||||
};
|
||||
uart_param_config(IO_UART_PORT, &cfg);
|
||||
uart_set_pin(IO_UART_PORT, IO_UART_TX_GPIO, IO_UART_RX_GPIO,
|
||||
UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
|
||||
uart_driver_install(IO_UART_PORT, 4096, 0, 0, NULL, 0);
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
ESP_LOGI(TAG, "ESP32-S3 IO v%s starting", IO_FW_VERSION);
|
||||
|
||||
/* Mark running image valid (OTA rollback support) */
|
||||
esp_ota_mark_app_valid_cancel_rollback();
|
||||
|
||||
uart_init();
|
||||
uart_ota_recv_init();
|
||||
|
||||
/* IO board main loop placeholder — RC/motor/sensor tasks added in later beads */
|
||||
while (1) {
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
}
|
||||
}
|
||||
210
esp32s3/io/main/uart_ota_recv.c
Normal file
210
esp32s3/io/main/uart_ota_recv.c
Normal file
@ -0,0 +1,210 @@
|
||||
/* uart_ota_recv.c — IO board OTA receiver (bd-21hv)
|
||||
*
|
||||
* Listens on UART0 for OTA frames from Balance board.
|
||||
* Writes incoming chunks to the inactive OTA partition, verifies SHA256,
|
||||
* then reboots into new firmware.
|
||||
*/
|
||||
|
||||
#include "uart_ota_recv.h"
|
||||
#include "config.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_ota_ops.h"
|
||||
#include "driver/uart.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "mbedtls/sha256.h"
|
||||
#include <string.h>
|
||||
|
||||
static const char *TAG = "io_ota";
|
||||
|
||||
volatile io_ota_state_t g_io_ota_state = IO_OTA_IDLE;
|
||||
volatile uint8_t g_io_ota_progress = 0;
|
||||
|
||||
/* Frame type bytes (same as uart_ota.h sender side) */
|
||||
#define OTA_BEGIN 0xC0u
|
||||
#define OTA_DATA 0xC1u
|
||||
#define OTA_END 0xC2u
|
||||
#define OTA_ABORT 0xC3u
|
||||
#define OTA_ACK 0xC4u
|
||||
#define OTA_NACK 0xC5u
|
||||
|
||||
#define CHUNK_MAX 1024
|
||||
|
||||
static uint8_t crc8(const uint8_t *d, uint16_t len)
|
||||
{
|
||||
uint8_t crc = 0;
|
||||
for (uint16_t i = 0; i < len; i++) {
|
||||
crc ^= d[i];
|
||||
for (uint8_t b = 0; b < 8; b++)
|
||||
crc = (crc & 0x80u) ? (uint8_t)((crc << 1u) ^ 0x07u) : (uint8_t)(crc << 1u);
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
static void send_ack(uint16_t seq)
|
||||
{
|
||||
uint8_t frame[6];
|
||||
frame[0] = OTA_ACK;
|
||||
frame[1] = (uint8_t)(seq >> 8u);
|
||||
frame[2] = (uint8_t)(seq);
|
||||
frame[3] = 0; frame[4] = 0; /* LEN=0 */
|
||||
uint8_t crc = crc8(frame, 5);
|
||||
frame[5] = crc;
|
||||
uart_write_bytes(IO_UART_PORT, (char *)frame, 6);
|
||||
}
|
||||
|
||||
static void send_nack(uint16_t seq, uint8_t err)
|
||||
{
|
||||
uint8_t frame[8];
|
||||
frame[0] = OTA_NACK;
|
||||
frame[1] = (uint8_t)(seq >> 8u);
|
||||
frame[2] = (uint8_t)(seq);
|
||||
frame[3] = 0; frame[4] = 1; /* LEN=1 */
|
||||
frame[5] = err;
|
||||
uint8_t crc = crc8(frame, 6);
|
||||
frame[6] = crc;
|
||||
uart_write_bytes(IO_UART_PORT, (char *)frame, 7);
|
||||
}
|
||||
|
||||
/* Read exact n bytes with timeout */
|
||||
static bool uart_read_exact(uint8_t *buf, int n, int timeout_ms)
|
||||
{
|
||||
int got = 0;
|
||||
while (got < n && timeout_ms > 0) {
|
||||
int r = uart_read_bytes(IO_UART_PORT, buf + got, n - got,
|
||||
pdMS_TO_TICKS(50));
|
||||
if (r > 0) got += r;
|
||||
else timeout_ms -= 50;
|
||||
}
|
||||
return got == n;
|
||||
}
|
||||
|
||||
static void ota_recv_task(void *arg)
|
||||
{
|
||||
esp_ota_handle_t handle = 0;
|
||||
const esp_partition_t *ota_part = esp_ota_get_next_update_partition(NULL);
|
||||
mbedtls_sha256_context sha;
|
||||
mbedtls_sha256_init(&sha);
|
||||
uint32_t expected_size = 0;
|
||||
uint8_t expected_digest[32] = {0};
|
||||
uint32_t received = 0;
|
||||
bool ota_started = false;
|
||||
static uint8_t payload[CHUNK_MAX];
|
||||
|
||||
for (;;) {
|
||||
/* Read frame header: TYPE(1) + SEQ(2) + LEN(2) = 5 bytes */
|
||||
uint8_t hdr[5];
|
||||
if (!uart_read_exact(hdr, 5, 5000)) continue;
|
||||
|
||||
uint8_t type = hdr[0];
|
||||
uint16_t seq = (uint16_t)((hdr[1] << 8u) | hdr[2]);
|
||||
uint16_t plen = (uint16_t)((hdr[3] << 8u) | hdr[4]);
|
||||
|
||||
if (plen > CHUNK_MAX + 36) {
|
||||
ESP_LOGW(TAG, "oversized frame plen=%u", plen);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Read payload + CRC */
|
||||
if (plen > 0 && !uart_read_exact(payload, plen, 2000)) continue;
|
||||
uint8_t crc_rx;
|
||||
if (!uart_read_exact(&crc_rx, 1, 500)) continue;
|
||||
|
||||
/* Verify CRC over hdr+payload */
|
||||
uint8_t crc_buf[5 + CHUNK_MAX + 36];
|
||||
memcpy(crc_buf, hdr, 5);
|
||||
if (plen > 0) memcpy(crc_buf + 5, payload, plen);
|
||||
uint8_t expected_crc = crc8(crc_buf, (uint16_t)(5 + plen));
|
||||
if (crc_rx != expected_crc) {
|
||||
ESP_LOGW(TAG, "CRC fail seq=%u", seq);
|
||||
send_nack(seq, 0x01u); /* OTA_ERR_BAD_CRC */
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case OTA_BEGIN:
|
||||
if (plen < 36) { send_nack(seq, 0x03u); break; }
|
||||
expected_size = ((uint32_t)payload[0] << 24u) |
|
||||
((uint32_t)payload[1] << 16u) |
|
||||
((uint32_t)payload[2] << 8u) |
|
||||
(uint32_t)payload[3];
|
||||
memcpy(expected_digest, &payload[4], 32);
|
||||
|
||||
if (!ota_part || esp_ota_begin(ota_part, OTA_WITH_SEQUENTIAL_WRITES,
|
||||
&handle) != ESP_OK) {
|
||||
send_nack(seq, 0x02u);
|
||||
break;
|
||||
}
|
||||
mbedtls_sha256_starts(&sha, 0);
|
||||
received = 0;
|
||||
ota_started = true;
|
||||
g_io_ota_state = IO_OTA_RECEIVING;
|
||||
g_io_ota_progress = 0;
|
||||
ESP_LOGI(TAG, "OTA begin: %lu bytes", (unsigned long)expected_size);
|
||||
send_ack(seq);
|
||||
break;
|
||||
|
||||
case OTA_DATA:
|
||||
if (!ota_started) { send_nack(seq, 0x02u); break; }
|
||||
if (esp_ota_write(handle, payload, plen) != ESP_OK) {
|
||||
send_nack(seq, 0x02u);
|
||||
esp_ota_abort(handle);
|
||||
ota_started = false;
|
||||
g_io_ota_state = IO_OTA_FAILED;
|
||||
break;
|
||||
}
|
||||
mbedtls_sha256_update(&sha, payload, plen);
|
||||
received += plen;
|
||||
if (expected_size > 0)
|
||||
g_io_ota_progress = (uint8_t)((received * 100u) / expected_size);
|
||||
send_ack(seq);
|
||||
break;
|
||||
|
||||
case OTA_END: {
|
||||
if (!ota_started) { send_nack(seq, 0x02u); break; }
|
||||
g_io_ota_state = IO_OTA_VERIFYING;
|
||||
|
||||
uint8_t digest[32];
|
||||
mbedtls_sha256_finish(&sha, digest);
|
||||
if (memcmp(digest, expected_digest, 32) != 0) {
|
||||
ESP_LOGE(TAG, "SHA256 mismatch");
|
||||
esp_ota_abort(handle);
|
||||
send_nack(seq, 0x01u);
|
||||
g_io_ota_state = IO_OTA_FAILED;
|
||||
break;
|
||||
}
|
||||
|
||||
if (esp_ota_end(handle) != ESP_OK ||
|
||||
esp_ota_set_boot_partition(ota_part) != ESP_OK) {
|
||||
send_nack(seq, 0x02u);
|
||||
g_io_ota_state = IO_OTA_FAILED;
|
||||
break;
|
||||
}
|
||||
|
||||
g_io_ota_state = IO_OTA_REBOOTING;
|
||||
g_io_ota_progress = 100;
|
||||
ESP_LOGI(TAG, "OTA done — rebooting");
|
||||
send_ack(seq);
|
||||
vTaskDelay(pdMS_TO_TICKS(500));
|
||||
esp_restart();
|
||||
break;
|
||||
}
|
||||
|
||||
case OTA_ABORT:
|
||||
if (ota_started) { esp_ota_abort(handle); ota_started = false; }
|
||||
g_io_ota_state = IO_OTA_IDLE;
|
||||
ESP_LOGW(TAG, "OTA aborted");
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void uart_ota_recv_init(void)
|
||||
{
|
||||
/* UART0 already initialized for inter-board comms; just create the task */
|
||||
xTaskCreate(ota_recv_task, "io_ota_recv", 8192, NULL, 6, NULL);
|
||||
ESP_LOGI(TAG, "OTA receiver task started");
|
||||
}
|
||||
20
esp32s3/io/main/uart_ota_recv.h
Normal file
20
esp32s3/io/main/uart_ota_recv.h
Normal file
@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
/* uart_ota_recv.h — IO board: receives OTA firmware from Balance (bd-21hv) */
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef enum {
|
||||
IO_OTA_IDLE = 0,
|
||||
IO_OTA_RECEIVING,
|
||||
IO_OTA_VERIFYING,
|
||||
IO_OTA_APPLYING,
|
||||
IO_OTA_REBOOTING,
|
||||
IO_OTA_FAILED,
|
||||
} io_ota_state_t;
|
||||
|
||||
extern volatile io_ota_state_t g_io_ota_state;
|
||||
extern volatile uint8_t g_io_ota_progress;
|
||||
|
||||
/* Start listening for OTA frames on UART0 */
|
||||
void uart_ota_recv_init(void);
|
||||
Loading…
x
Reference in New Issue
Block a user