Balance side (uart_ota.c): downloads io-firmware.bin from Gitea to RAM, computes SHA256, then streams to IO over UART1 (GPIO17/18, 460800 baud) as OTA_BEGIN/OTA_DATA/OTA_END frames with CRC8 + per-chunk ACK/retry (×3). IO side (uart_ota_recv.c): receives frames, writes to inactive OTA partition via esp_ota_write, verifies SHA256 on OTA_END, sets boot partition, reboots. IO board main.c + CMakeLists.txt scaffold included. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
211 lines
6.5 KiB
C
211 lines
6.5 KiB
C
/* 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");
|
|
}
|