/* 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 #include 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; }