fix(vd960DBN): OTA 刷写成功误判未启动 — 状态回 idle + 补 ota_report 主动上报
现场: 远程平台发起 OTA, 设备物理刷写成功 (70块全ACK, flash DONE), 但平台 ota_status 轮询只见 state=ready → 误判'刷写未启动'。 根因: 1. ota_flash_done 刷写成功后台侧状态置 READY (与协议状态机 'FLASHING──成功──▶IDLE(清槽/保留)' 不符), 平台无法区分 '待刷 ready' 与 '已刷完' → 持续 ready 判未启动 2. 固件漏实现协议 §5.5 ota_report 主动上报 (done/failed 重发 3次×5s), 平台收不到成功信号, 只能靠 ota_status 轮询兜底 修复 (协议 V1.09 + 固件): - 协议: 刷写成功状态明确回 idle (镜像保留, last_result=0, 可重刷); ota_report 升级为刷写结果主依据; 平台判定指引 (idle+size>0+last_result=0 =成功; 不得以轮询未见 flashing 或持续 ready 判未启动) - ota_srv.c: ota_flash_done → OTA_STATE_IDLE (镜像保留) + 上报 done; ota_flash_fail → 补 ota_report failed (保留 event_report 告警); ota_cmd_begin 兼容 idle+size/crc32 一致 → 直接回 ready (免下载重刷) - iot_mqtt_srv.c: 新增 ota_report 上报状态机 (立即首发 + 5s×3 重发, 同 msg_id/ts), 共享 _iot_pub_payload (event_report 复用, RAM 零新增) - 单测: test_flash_flow/test_flash_ready_timeout 断言 ota_report 上报, state 断言 READY→IDLE; 新增 test_begin_reflash; 10/10 全过 + 22 py 断言
This commit is contained in:
@@ -53,6 +53,7 @@ void iot_mqtt_handle_sock_int(uint8_t socketid, uint8_t intstat); // Socket
|
||||
void iot_mqtt_publish_sensor(void); // 推送传感器数据到 MQTT
|
||||
void iot_evt_handle_ack(uint32_t msg_id, int code); // event_report 平台应答入口 (V1.04)
|
||||
void iot_evt_report_ota_error(uint32_t err); // OTA 刷写失败告警入队 (V1.08, 走 event_report 闭环)
|
||||
void iot_ota_report_result(uint8_t ok, uint32_t err); // OTA 刷写结果上报 ota_report (V1.09: ok=1 done / ok=0 failed)
|
||||
uint8_t iot_any_car(void); // 任一通道有车 (ota_flash 安全窗口检查)
|
||||
void iot_watchdog_init(void); // 初始化硬件 IWDG (无条件, 主循环卡死兜底)
|
||||
void iot_watchdog_kick(void); // 喂硬件 IWDG (主循环调用)
|
||||
|
||||
@@ -59,6 +59,10 @@ static LUP_SensorReport _cached_sr; // 最新传感器数据缓存
|
||||
static uint8_t _cached_sr_valid; // 缓存有效标志
|
||||
static uint32_t _last_publish_ms; // 上次上报时刻 (ms)
|
||||
|
||||
/* 上行发布共享缓冲 (V1.09): event_report / ota_report 复用, 互斥调用
|
||||
(RAM 90% 环境: 独立数组挤栈, 文件级共享零新增) */
|
||||
static char _iot_pub_payload[512];
|
||||
|
||||
/*===========================================================================
|
||||
* 事件上报 event_report — 协议 V1.04: 平台必答 + 设备重发
|
||||
*
|
||||
@@ -194,8 +198,8 @@ static void iot_evt_sensor_cb(const uint8_t *pkg, uint16_t len) {
|
||||
/* 序列化并发布队列头 n 条事件 (首发与重发共用: 同 id/ts 同内容) */
|
||||
static void iot_evt_send(uint32_t id, uint32_t ts, uint8_t n) {
|
||||
static const char *evt_names[] = {"car_enter", "car_leave", "loop_cut", "loop_restore", "ota_error"};
|
||||
static char payload[512]; // static: 避免 2KB 栈溢出
|
||||
int w, rem = sizeof(payload);
|
||||
char *payload = _iot_pub_payload; // V1.09: 文件级共享缓冲
|
||||
int w, rem = sizeof(_iot_pub_payload);
|
||||
char *p = payload;
|
||||
uint8_t i;
|
||||
|
||||
@@ -250,6 +254,77 @@ void iot_evt_report_ota_error(uint32_t err)
|
||||
PRINT("EVT: ota_error enqueue err=0x%lX\n", (unsigned long)err);
|
||||
}
|
||||
|
||||
/*===========================================================================
|
||||
* OTA 结果上报 ota_report — 协议 V1.08 §5.5 / V1.09 平台判定主依据
|
||||
*
|
||||
* done/failed 主动推送, 无 ACK 闭环 → 定时重发 3 次×5s (同 msg_id/ts),
|
||||
* 平台按 (sn,msg_id) 去重 (窗口建议 10min)。共享 _iot_pub_payload 缓冲。
|
||||
* iot_ota_report_result(): 记录结果 + 立即首发; iot_ota_report_poll():
|
||||
* 主循环每轮调用, 到 5s 且未超 3 次 → 重发。
|
||||
*===========================================================================*/
|
||||
#define IOT_OTA_REP_MAX_RETRY 3 /* 重发次数 (含首发共 4 次) */
|
||||
#define IOT_OTA_REP_INTERVAL_MS 5000
|
||||
|
||||
static uint8_t _ota_rep_pend; /* 1=有未完成的 ota_report */
|
||||
static uint8_t _ota_rep_ok; /* 1=done 0=failed */
|
||||
static uint32_t _ota_rep_err; /* failed 的错误码 (OTA_ERR_*) */
|
||||
static uint32_t _ota_rep_id; /* 固定 msg_id (重发不变) */
|
||||
static uint32_t _ota_rep_ts; /* 固定 ts (重发不变) */
|
||||
static uint8_t _ota_rep_times; /* 已发次数 (首发=1) */
|
||||
static uint32_t _ota_rep_sent_ms; /* 上次发送时刻 */
|
||||
|
||||
/* 序列化并发布 ota_report (首发与重发共用: 同 id/ts 同内容) */
|
||||
static void iot_ota_report_send(void)
|
||||
{
|
||||
char *p = _iot_pub_payload;
|
||||
int w, rem = sizeof(_iot_pub_payload);
|
||||
|
||||
w = snprintf(p, rem,
|
||||
"{\"msg_id\":%lu,\"cmd\":\"ota_report\",\"ts\":%lu,"
|
||||
"\"data\":{\"target\":\"loop\",\"stage\":\"%s\","
|
||||
"\"progress\":{\"sent\":%lu,\"total\":%lu},\"code\":%lu,\"msg\":\"\"}}",
|
||||
_ota_rep_id, _ota_rep_ts,
|
||||
_ota_rep_ok ? "done" : "failed",
|
||||
(unsigned long)ota_flash_sent(), (unsigned long)ota_flash_total(),
|
||||
_ota_rep_ok ? 0UL : (unsigned long)_ota_rep_err);
|
||||
if (w < 0 || w >= rem) return;
|
||||
|
||||
mqtt_publish((char *)g_iot_topic.topic_pub, p, 1);
|
||||
_ota_rep_times++;
|
||||
_ota_rep_sent_ms = mstick();
|
||||
PRINT("IOT: ota_report %s msg_id=%lu sent=%lu/%lu times=%u\n",
|
||||
_ota_rep_ok ? "done" : "failed", _ota_rep_id,
|
||||
(unsigned long)ota_flash_sent(), (unsigned long)ota_flash_total(),
|
||||
_ota_rep_times);
|
||||
}
|
||||
|
||||
/* OTA 刷写结果上报入口 (ota_srv.c ota_flash_done/fail 调用):
|
||||
ok=1 → stage=done, ok=0 → stage=failed */
|
||||
void iot_ota_report_result(uint8_t ok, uint32_t err)
|
||||
{
|
||||
_ota_rep_pend = 1;
|
||||
_ota_rep_ok = ok ? 1 : 0;
|
||||
_ota_rep_err = err;
|
||||
_ota_rep_id = ++_evt_msg_id; /* 与 event_report 同源递增 (唯一性) */
|
||||
_ota_rep_ts = dev_time_now();
|
||||
_ota_rep_times = 0;
|
||||
iot_ota_report_send();
|
||||
}
|
||||
|
||||
/* 主循环轮询: 5s×3 定时重发 (无 ACK, 纯推送 + 平台去重) */
|
||||
static void iot_ota_report_poll(void)
|
||||
{
|
||||
if (!_ota_rep_pend) return;
|
||||
if (g_iot_state != IOT_STATE_READY) return; /* 断网不消耗次数 */
|
||||
if (_ota_rep_times > IOT_OTA_REP_MAX_RETRY) {
|
||||
_ota_rep_pend = 0; /* 重发耗尽 (共 4 次) */
|
||||
PRINT("IOT: ota_report retry exhausted\n");
|
||||
return;
|
||||
}
|
||||
if (mstick() - _ota_rep_sent_ms < IOT_OTA_REP_INTERVAL_MS) return;
|
||||
iot_ota_report_send();
|
||||
}
|
||||
|
||||
/* 任一通道有车 (ota_flash 安全窗口检查用, 基于最新 0xC0 缓存) */
|
||||
uint8_t iot_any_car(void)
|
||||
{
|
||||
@@ -1111,6 +1186,9 @@ void iot_mqtt_publish_sensor(void) {
|
||||
/*--- 事件上报状态机: 发送/重发/重连补报 ---*/
|
||||
iot_evt_process();
|
||||
|
||||
/*--- OTA 结果上报重发 (V1.09): done/failed 5s×3, 同 msg_id/ts ---*/
|
||||
iot_ota_report_poll();
|
||||
|
||||
if (g_iot_state != IOT_STATE_READY) return;
|
||||
if (!g_report_cfg.enable) return;
|
||||
|
||||
|
||||
@@ -248,6 +248,13 @@ int ota_cmd_begin(uint32_t size, uint32_t crc32, const char *version, uint8_t fo
|
||||
/* 镜像已就绪, 平台可直接 ota_flash */
|
||||
return 0; /* data.offset = size */
|
||||
}
|
||||
if (_meta.state == OTA_STATE_IDLE) {
|
||||
/* V1.09: 刷写完成后的 idle 且镜像一致 → 直接回 ready, 免下载可重刷 */
|
||||
_meta.state = OTA_STATE_READY;
|
||||
ota_meta_write(&_meta);
|
||||
PRINT("OTA: begin idle+same image → ready (reflash)\n");
|
||||
return 0; /* data.offset = size */
|
||||
}
|
||||
if (_meta.state == OTA_STATE_DOWNLOADING) {
|
||||
_meta.state = OTA_STATE_DOWNLOADING;
|
||||
PRINT("OTA: begin resume received=%lu\n", (unsigned long)_meta.received);
|
||||
@@ -454,7 +461,7 @@ static void ota_send_9f(uint8_t sub_l, uint8_t sub_h, uint8_t cmd,
|
||||
UART2_SendString(_ota_buf.frame, (uint16_t)(5 + datalen + 1));
|
||||
}
|
||||
|
||||
/* 刷写失败收尾: 状态落盘 + event_report 告警 + 恢复 0x7F 模式 */
|
||||
/* 刷写失败收尾 (V1.09): 状态落盘 + ota_report failed + event_report 告警 + 恢复 0x7F 模式 */
|
||||
static void ota_flash_fail(uint32_t err)
|
||||
{
|
||||
PRINT("OTA: flash FAILED err=0x%lX\n", (unsigned long)err);
|
||||
@@ -467,16 +474,20 @@ static void ota_flash_fail(uint32_t err)
|
||||
g_flag_counter_ota.flag = 0;
|
||||
lup_frame_reset();
|
||||
_fs = OTA_FLASH_IDLE;
|
||||
/* ota_report stage=failed (平台判定主依据, V1.09) */
|
||||
extern void iot_ota_report_result(uint8_t ok, uint32_t err);
|
||||
iot_ota_report_result(0, err);
|
||||
/* event_report ota_error 告警 (平台必答, 由 iot_mqtt_srv 发送) */
|
||||
extern void iot_evt_report_ota_error(uint32_t err);
|
||||
iot_evt_report_ota_error(err);
|
||||
}
|
||||
|
||||
/* 刷写成功收尾: 保留镜像 (state=ready 可重刷), 恢复 0x7F 模式 */
|
||||
/* 刷写成功收尾 (V1.09): 状态回 idle 但镜像保留 (可重刷, last_result=0),
|
||||
上报 ota_report stage=done (平台判定主依据), 恢复 0x7F 模式 */
|
||||
static void ota_flash_done(void)
|
||||
{
|
||||
PRINT("OTA: flash DONE sent=%lu\n", (unsigned long)_fs_sent);
|
||||
_meta.state = OTA_STATE_READY;
|
||||
_meta.state = OTA_STATE_IDLE; /* V1.09: idle+size>0 = 已刷写完成 (区别于 ready=待刷) */
|
||||
_meta.last_result = 0;
|
||||
ota_meta_write(&_meta);
|
||||
offlog_ota_result(0);
|
||||
@@ -484,6 +495,9 @@ static void ota_flash_done(void)
|
||||
g_flag_counter_ota.flag = 0;
|
||||
lup_frame_reset();
|
||||
_fs = OTA_FLASH_IDLE;
|
||||
/* ota_report stage=done (平台判定主依据, V1.09) */
|
||||
extern void iot_ota_report_result(uint8_t ok, uint32_t err);
|
||||
iot_ota_report_result(1, 0);
|
||||
}
|
||||
|
||||
/* UART2 收到 bootloader 0x9F ACK 帧 (uart_srv OTA 分支调用, 主循环上下文)
|
||||
|
||||
Reference in New Issue
Block a user