feat(relay): OTA可用版本,添加OTA中继阶段健康检查和APP存活验证功能

- 新增health_check_before_ota_relay函数,用于OTA中继转发阶段的健康检查,
  跳过WiFi相关检查,只检查温度等本地状态
- 添加APP存活验证阶段,OTA完成后等待GD32 APP启动并通过ping确认存活
- 实现更精确的响应帧类型匹配和轮询机制,提高协议可靠性
- 添加多个阶段的响应匹配器(is_handshake_echo_ack、is_challenge_payload_ack等)
- 优化poll_until_expected_ack_shape函数,处理残留ACK和超时情况
- 将调试日志级别从INFO降级为DBG,减少日志输出
- 修复handshake阶段重试机制,添加最大重试次数限制
- 优化Flash擦除期间的WATI状态处理,使用短超时避免长阻塞
- 添加完整的会话数据清理,防止状态污染
- 增加详细的注释说明各阶段的处理逻辑
This commit is contained in:
小龙虾
2026-05-30 20:04:04 +08:00
parent 235b9c0420
commit 6f4be9189c
13 changed files with 923 additions and 111 deletions
+86 -6
View File
@@ -373,13 +373,13 @@ esp_err_t spi_ota_poll_response(uint8_t *resp_type, uint8_t *resp_payload,
TickType_t deadline = xTaskGetTickCount() + pdMS_TO_TICKS(timeout_ms);
uint32_t attempt = 0;
while (xTaskGetTickCount() <= deadline && attempt < 10) {
while (xTaskGetTickCount() <= deadline) {
++attempt;
memset(rx_raw, 0xFF, sizeof(rx_raw));
esp_err_t ret = spi_transfer_clocked(dummy, sizeof(dummy), rx_raw, sizeof(rx_raw));
if (ret != ESP_OK) {
DEBUG_WARN("SPI", "SPI OTA 响应读取失败[%u]: %s", attempt, esp_err_to_name(ret));
vTaskDelay(pdMS_TO_TICKS(2));
vTaskDelay(pdMS_TO_TICKS(1));
continue;
}
@@ -388,7 +388,7 @@ esp_err_t spi_ota_poll_response(uint8_t *resp_type, uint8_t *resp_payload,
size_t parsed_len = 0;
size_t frame_offset = 0;
if (spi_ota_find_frame(rx_raw, sizeof(rx_raw), &parsed_type, &parsed_payload, &parsed_len, &frame_offset)) {
DEBUG_INFO("SPI", "OTA响应: type=0x%02X len=%u offset=%u attempt=%u",
DEBUG_DBG("SPI", "OTA响应: type=0x%02X len=%u offset=%u attempt=%u",
parsed_type, (unsigned)parsed_len, (unsigned)frame_offset, (unsigned)attempt);
if (resp_type) *resp_type = parsed_type;
if (resp_payload && resp_max > 0) {
@@ -413,10 +413,10 @@ esp_err_t spi_ota_poll_response(uint8_t *resp_type, uint8_t *resp_payload,
rx_raw[0], rx_raw[1], rx_raw[2], rx_raw[3],
rx_raw[4], rx_raw[5], rx_raw[6], rx_raw[7],
rx_raw[8], rx_raw[9]);
vTaskDelay(pdMS_TO_TICKS(2));
vTaskDelay(pdMS_TO_TICKS(1));
}
DEBUG_ERR("SPI", "OTA响应超时: timeout=%ums", (unsigned)timeout_ms);
DEBUG_DBG("SPI", "OTA响应超时: timeout=%ums", (unsigned)timeout_ms);
return ESP_ERR_TIMEOUT;
}
@@ -448,7 +448,7 @@ esp_err_t spi_ota_transfer(uint8_t type, const uint8_t *payload, size_t payload_
return ESP_ERR_INVALID_ARG;
}
DEBUG_INFO("SPI", "OTA事务: req_type=0x%02X req_len=%u", type, (unsigned)payload_len);
DEBUG_DBG("SPI", "OTA事务: req_type=0x%02X req_len=%u", type, (unsigned)payload_len);
/* ============================================================
* 第1次 CS 周期:发送请求帧(只发不收)
@@ -634,6 +634,86 @@ esp_err_t gd32_app_request_bootloader(uint32_t timeout_ms)
return ESP_ERR_TIMEOUT;
}
esp_err_t gd32_app_ping(uint32_t timeout_ms, uint32_t *uptime_sec)
{
/* APP 侧 SPI 从机当前是中断逐字节收发,不适合像 BOOT 那样一次读 96B 长轮询。
* 这里改为:每次都完整重发一帧 PING 请求,然后只读一个短窗口寻找 ACK。
* 这样即便某次响应窗口错过,也会在下一次重发时重新生成 APP_ALIVE。 */
constexpr uint8_t kAppPingType = 0xA1U;
constexpr size_t kAppPingPollBytes = 32U;
uint8_t tx_frame[8] = {0};
size_t tx_len = 0U;
if (!spi_ota_build_frame(kAppPingType, NULL, 0U, tx_frame, &tx_len)) {
return ESP_ERR_INVALID_ARG;
}
TickType_t deadline = xTaskGetTickCount() + pdMS_TO_TICKS(timeout_ms);
uint32_t attempt = 0U;
while (xTaskGetTickCount() <= deadline) {
++attempt;
esp_err_t err = spi_transfer_clocked(tx_frame, tx_len, NULL, 0U);
if (err != ESP_OK) {
DEBUG_DBG("SPI", "APP PING 请求发送失败[%lu]: %s",
(unsigned long)attempt, esp_err_to_name(err));
vTaskDelay(pdMS_TO_TICKS(5));
continue;
}
esp_rom_delay_us(200);
uint8_t rx_raw[kAppPingPollBytes];
uint8_t dummy[kAppPingPollBytes];
memset(dummy, 0xFF, sizeof(dummy));
memset(rx_raw, 0xFF, sizeof(rx_raw));
err = spi_transfer_clocked(dummy, sizeof(dummy), rx_raw, sizeof(rx_raw));
if (err != ESP_OK) {
DEBUG_DBG("SPI", "APP PING 响应读取失败[%lu]: %s",
(unsigned long)attempt, esp_err_to_name(err));
vTaskDelay(pdMS_TO_TICKS(5));
continue;
}
uint8_t resp_type = 0U;
const uint8_t *resp_payload = nullptr;
size_t resp_len = 0U;
size_t frame_offset = 0U;
if (spi_ota_find_frame(rx_raw, sizeof(rx_raw),
&resp_type, &resp_payload, &resp_len, &frame_offset)) {
if (resp_type == SPI_OTA_RSP_ACK && resp_len >= 3U && resp_payload[0] == kAppPingType) {
if (uptime_sec) {
*uptime_sec = (uint32_t)resp_payload[1] | ((uint32_t)resp_payload[2] << 8);
}
DEBUG_INFO("SPI", "APP PING 成功: uptime=%lu sec, attempt=%lu, offset=%u",
uptime_sec ? (unsigned long)*uptime_sec : 0UL,
(unsigned long)attempt,
(unsigned)frame_offset);
return ESP_OK;
}
DEBUG_DBG("SPI", "APP PING 收到无效帧[%lu]: type=0x%02X len=%u offset=%u payload0=0x%02X",
(unsigned long)attempt,
resp_type,
(unsigned)resp_len,
(unsigned)frame_offset,
resp_len > 0U ? resp_payload[0] : 0xFFU);
} else {
DEBUG_DBG("SPI", "APP PING 未找到有效帧[%lu]: first8=%02X%02X%02X%02X%02X%02X%02X%02X",
(unsigned long)attempt,
rx_raw[0], rx_raw[1], rx_raw[2], rx_raw[3],
rx_raw[4], rx_raw[5], rx_raw[6], rx_raw[7]);
}
vTaskDelay(pdMS_TO_TICKS(5));
}
DEBUG_DBG("SPI", "APP PING 超时: timeout=%ums", (unsigned)timeout_ms);
return ESP_ERR_TIMEOUT;
}
// 带重试的 SPI 传输
/**
* @brief 执行带重试的 SPI 同步传输。