V1.5: 修复OTA确认通知发送失败(确认延后至+9s避开上报风暴,本地确认先于通知落盘,通知失败30s重试,开机上报拉开300ms)
This commit is contained in:
parent
bf05a80f39
commit
9fe86a5111
|
|
@ -442,23 +442,25 @@ static int ota_ver_save(uint16_t ver) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/*==== MQTT Publish: OTA进度/结果上报 ====*/
|
/*==== MQTT Publish: OTA进度/结果上报 ====*/
|
||||||
void air780e_mqtt_report(const char *header, const char *ota_id, int val) {
|
int air780e_mqtt_report(const char *header, const char *ota_id, int val) {
|
||||||
if (!header || !ota_id || !ota_id[0]) return;
|
if (!header || !ota_id || !ota_id[0]) return -1;
|
||||||
static char cmd[300];
|
static char cmd[300];
|
||||||
if (strstr(header, "progress")) {
|
if (strstr(header, "progress")) {
|
||||||
/* 进度上报:允许丢包,发了就走 */
|
/* 进度上报:允许丢包,发了就走 */
|
||||||
sprintf(cmd, "AT+MPUB=\"/iot/data/up/%s\",0,0,\"{\\22header\\22:\\22%s\\22,\\22body\\22:{\\22id\\22:\\22%.16s\\22,\\22progress\\22:%d}}\"",
|
sprintf(cmd, "AT+MPUB=\"/iot/data/up/%s\",0,0,\"{\\22header\\22:\\22%s\\22,\\22body\\22:{\\22id\\22:\\22%.16s\\22,\\22progress\\22:%d}}\"",
|
||||||
MqttInfoStr.ClientID, header, ota_id, val);
|
MqttInfoStr.ClientID, header, ota_id, val);
|
||||||
CAT1_printf("%s", cmd);
|
CAT1_printf("%s", cmd);
|
||||||
|
return 0;
|
||||||
} else {
|
} else {
|
||||||
/* 结果上报:平台据此判定升级完成,必须可靠送达——等 OK 并重试 */
|
/* 结果上报:平台据此判定升级完成,必须可靠送达——等 OK 并重试 */
|
||||||
sprintf(cmd, "AT+MPUB=\"/iot/data/up/%s\",0,0,\"{\\22header\\22:\\22%s\\22,\\22body\\22:{\\22id\\22:\\22%.16s\\22,\\22success\\22:%s}}\"",
|
sprintf(cmd, "AT+MPUB=\"/iot/data/up/%s\",0,0,\"{\\22header\\22:\\22%s\\22,\\22body\\22:{\\22id\\22:\\22%.16s\\22,\\22success\\22:%s}}\"",
|
||||||
MqttInfoStr.ClientID, header, ota_id, val ? "true" : "false");
|
MqttInfoStr.ClientID, header, ota_id, val ? "true" : "false");
|
||||||
if (catSendCmd(cmd, "OK", 3, 30) != 0) {
|
if (catSendCmd(cmd, "OK", 3, 30) != 0) {
|
||||||
log_warn("> OTA 结果上报失败(重试 3 次无 OK)");
|
log_warn("> OTA 结果上报失败(重试 3 次无 OK)");
|
||||||
} else {
|
return -1;
|
||||||
log_info("> OTA 结果已上报: success=%d", val);
|
|
||||||
}
|
}
|
||||||
|
log_info("> OTA 结果已上报: success=%d", val);
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -715,22 +717,16 @@ static void air780e_trigger_ota_body(const char *host, int port, const char *pat
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 固件更新后由 APP 调用,确认运行稳定后写入 EEPROM 版本号 */
|
/* 固件更新后由 APP 调用,确认运行稳定后写入 EEPROM 版本号 */
|
||||||
void air780e_ota_confirm(void)
|
/* 返回 0=确认通知已送达平台;1=无挂起 OTA;-1=本地已确认但通知未送达(上层应稍后重试) */
|
||||||
|
int air780e_ota_confirm(void)
|
||||||
{
|
{
|
||||||
W25Q64_OtaInfo info;
|
W25Q64_OtaInfo info;
|
||||||
if (W25Q64_OTA_ReadInfo(&info) != 0) return;
|
if (W25Q64_OTA_ReadInfo(&info) != 0) return 1;
|
||||||
if (info.update_state != W25Q64_STATE_INSTALLED) return;
|
if (info.update_state != W25Q64_STATE_INSTALLED) return 1;
|
||||||
if (info.app_confirmed) return;
|
|
||||||
|
|
||||||
/* 先向服务器上报“确认成功”(走网络层路由,4G/WiFi 通道均可) */
|
|
||||||
char id_buf[24] = {0};
|
|
||||||
snprintf(id_buf, sizeof(id_buf), "%u", info.new_version); /* 用版本号作为 id 上报 */
|
|
||||||
NET_mqtt_report("iot.ota.confirm.post", id_buf, 1);
|
|
||||||
|
|
||||||
|
if (!info.app_confirmed) {
|
||||||
|
/* 本地确认先行(版本号 + 确认标志落盘),防止通知发送失败导致 BL 误判回滚 */
|
||||||
if (info.new_version > 0) ota_ver_save(info.new_version);
|
if (info.new_version > 0) ota_ver_save(info.new_version);
|
||||||
|
|
||||||
/* 确认标志必须可靠落盘,否则 BL 会一直认为未确认;
|
|
||||||
写后回读验证,失败重试 3 次 */
|
|
||||||
int ok = 0;
|
int ok = 0;
|
||||||
for (int i = 0; i < 3 && !ok; i++) {
|
for (int i = 0; i < 3 && !ok; i++) {
|
||||||
W25Q64_OTA_SetConfirmed();
|
W25Q64_OTA_SetConfirmed();
|
||||||
|
|
@ -742,6 +738,12 @@ void air780e_ota_confirm(void)
|
||||||
else log_error("> OTA 确认写入失败(已重试 3 次)");
|
else log_error("> OTA 确认写入失败(已重试 3 次)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 再向服务器上报"确认成功"(走网络层路由,4G/WiFi 通道均可) */
|
||||||
|
char id_buf[24] = {0};
|
||||||
|
snprintf(id_buf, sizeof(id_buf), "%u", info.new_version); /* 用版本号作为 id 上报 */
|
||||||
|
return NET_mqtt_report("iot.ota.confirm.post", id_buf, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*==== Init ====*/
|
/*==== Init ====*/
|
||||||
void air780e_init(void) {
|
void air780e_init(void) {
|
||||||
|
|
@ -780,11 +782,11 @@ void air780e_init(void) {
|
||||||
char pub[256];
|
char pub[256];
|
||||||
sprintf(pub, "AT+MPUB=\"/iot/data/up/%s\",0,0,\"{\\22header\\22:\\22iot.prop.post\\22,\\22body\\22:{\\22pow\\22:1}}\"", MqttInfoStr.ClientID);
|
sprintf(pub, "AT+MPUB=\"/iot/data/up/%s\",0,0,\"{\\22header\\22:\\22iot.prop.post\\22,\\22body\\22:{\\22pow\\22:1}}\"", MqttInfoStr.ClientID);
|
||||||
CAT1_printf("%s", pub);
|
CAT1_printf("%s", pub);
|
||||||
HAL_Delay(50);
|
HAL_Delay(300); /* 开机上报拉开间隔,避免模组 MQTT 发送排队撞车 */
|
||||||
if (s_iccid[0]) {
|
if (s_iccid[0]) {
|
||||||
sprintf(pub, "AT+MPUB=\"/iot/data/up/%s\",0,0,\"{\\22header\\22:\\22iot.prop.post\\22,\\22body\\22:{\\22iccid\\22:\\22%s\\22}}\"", MqttInfoStr.ClientID, s_iccid);
|
sprintf(pub, "AT+MPUB=\"/iot/data/up/%s\",0,0,\"{\\22header\\22:\\22iot.prop.post\\22,\\22body\\22:{\\22iccid\\22:\\22%s\\22}}\"", MqttInfoStr.ClientID, s_iccid);
|
||||||
CAT1_printf("%s", pub);
|
CAT1_printf("%s", pub);
|
||||||
HAL_Delay(50);
|
HAL_Delay(300);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,8 +44,8 @@ uint16_t crc16_modbus_update(uint16_t crc, uint8_t data); /* MODBUS CRC16
|
||||||
void air780e_init(void);
|
void air780e_init(void);
|
||||||
void air780e_process(void);
|
void air780e_process(void);
|
||||||
void air780e_trigger_ota(const char *host, int port, const char *path, const char *ota_id, uint16_t new_ver, uint16_t crc16);
|
void air780e_trigger_ota(const char *host, int port, const char *path, const char *ota_id, uint16_t new_ver, uint16_t crc16);
|
||||||
void air780e_mqtt_report(const char *header, const char *ota_id, int percent_or_success);
|
int air780e_mqtt_report(const char *header, const char *ota_id, int percent_or_success); /* ·µ»Ø 0=ËÍ´ï */
|
||||||
void air780e_ota_confirm(void);
|
int air780e_ota_confirm(void); /* 0=֪ͨÒÑËÍ´ï 1=ÎÞ¹ÒÆð -1=֪ͨδËÍ´ï(ÐèÖØÊÔ) */
|
||||||
uint16_t ota_get_version(void);
|
uint16_t ota_get_version(void);
|
||||||
void CAT1_printf(const char *fmt, ...);
|
void CAT1_printf(const char *fmt, ...);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -352,16 +352,16 @@ void NET_publish_energy(float kwh)
|
||||||
net_at_unlock();
|
net_at_unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
void NET_mqtt_report(const char *header, const char *ota_id, int val)
|
int NET_mqtt_report(const char *header, const char *ota_id, int val)
|
||||||
{
|
{
|
||||||
if (g_net_tx_busy) { log_info("> NET: 进度/结果上报被丢弃 (发送忙)"); return; }
|
if (g_net_tx_busy) { log_info("> NET: 进度/结果上报被丢弃 (发送忙)"); return -1; }
|
||||||
if (net_at_trylock() != 0) { log_info("> NET: 进度/结果上报被丢弃 (AT 忙)"); return; }
|
if (net_at_trylock() != 0) { log_info("> NET: 进度/结果上报被丢弃 (AT 忙)"); return -1; }
|
||||||
if (s_backend == NET_BACKEND_4G) {
|
if (s_backend == NET_BACKEND_4G) {
|
||||||
air780e_mqtt_report(header, ota_id, val);
|
int rc = air780e_mqtt_report(header, ota_id, val);
|
||||||
net_at_unlock();
|
net_at_unlock();
|
||||||
return;
|
return rc;
|
||||||
}
|
}
|
||||||
if (!header || !ota_id || !ota_id[0]) { net_at_unlock(); return; }
|
if (!header || !ota_id || !ota_id[0]) { net_at_unlock(); return -1; }
|
||||||
char json[200];
|
char json[200];
|
||||||
if (strstr(header, "progress")) {
|
if (strstr(header, "progress")) {
|
||||||
snprintf(json, sizeof(json),
|
snprintf(json, sizeof(json),
|
||||||
|
|
@ -374,13 +374,14 @@ void NET_mqtt_report(const char *header, const char *ota_id, int val)
|
||||||
}
|
}
|
||||||
net_wifi_publish_up(json);
|
net_wifi_publish_up(json);
|
||||||
net_at_unlock();
|
net_at_unlock();
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NET_ota_confirm(void)
|
int NET_ota_confirm(void)
|
||||||
{
|
{
|
||||||
/* 两通道复用同一确认流程:无挂起 OTA 时内部直接返回;
|
/* 两通道复用同一确认流程:无挂起 OTA 时内部直接返回;
|
||||||
* 其中的上报已改为 NET_mqtt_report,按当前通道路由 */
|
* 其中的上报已改为 NET_mqtt_report,按当前通道路由 */
|
||||||
air780e_ota_confirm();
|
return air780e_ota_confirm();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*==== 下行消息统一分发(从 air780e.c 上提,与模组无关)====
|
/*==== 下行消息统一分发(从 air780e.c 上提,与模组无关)====
|
||||||
|
|
|
||||||
|
|
@ -54,8 +54,8 @@ void NET_publish_status(int sw1, int feeding, int sw2, int sw3, int sw4,
|
||||||
void NET_publish_response(const char *cmd_id, int ok, const char *msg);
|
void NET_publish_response(const char *cmd_id, int ok, const char *msg);
|
||||||
void NET_publish_power(int on);
|
void NET_publish_power(int on);
|
||||||
void NET_publish_energy(float kwh); /* 上报累计电量,标识符 energy,单位 kWh */
|
void NET_publish_energy(float kwh); /* 上报累计电量,标识符 energy,单位 kWh */
|
||||||
void NET_mqtt_report(const char *header, const char *ota_id, int val);
|
int NET_mqtt_report(const char *header, const char *ota_id, int val); /* ·µ»Ø 0=ËÍ´ï */
|
||||||
void NET_ota_confirm(void);
|
int NET_ota_confirm(void); /* 0=È·ÈÏ֪ͨÒÑËÍ´ï 1=ÎÞ¹ÒÆð -1=δËÍ´ï(ÐèÖØÊÔ) */
|
||||||
|
|
||||||
/* 下行消息统一分发(与模组无关):id去重 → iot.prop.set继电器控制 → iot.ota升级。
|
/* 下行消息统一分发(与模组无关):id去重 → iot.prop.set继电器控制 → iot.ota升级。
|
||||||
* 4G 后端切出 +MSUB 片段后调用;WiFi 后端解出 MQTT PUBLISH payload 后调用。
|
* 4G 后端切出 +MSUB 片段后调用;WiFi 后端解出 MQTT PUBLISH payload 后调用。
|
||||||
|
|
|
||||||
|
|
@ -139,6 +139,8 @@ static void app_task(void *arg)
|
||||||
static uint8_t ota_simulate_fail = 0; /* =1测试:模拟 OTA 确认失败(验证回退) */
|
static uint8_t ota_simulate_fail = 0; /* =1测试:模拟 OTA 确认失败(验证回退) */
|
||||||
static uint8_t ota_fail_logged = 0;
|
static uint8_t ota_fail_logged = 0;
|
||||||
static uint32_t ota_confirm_tick = 0;
|
static uint32_t ota_confirm_tick = 0;
|
||||||
|
static uint8_t ota_report_pending = 0;
|
||||||
|
static uint32_t ota_report_tick = 0;
|
||||||
static uint32_t mqtt_ready_tick = 0;
|
static uint32_t mqtt_ready_tick = 0;
|
||||||
static uint8_t mqtt_ready_recorded = 0;
|
static uint8_t mqtt_ready_recorded = 0;
|
||||||
static uint8_t mqtt_first_status_reported = 0;
|
static uint8_t mqtt_first_status_reported = 0;
|
||||||
|
|
@ -160,9 +162,9 @@ static void app_task(void *arg)
|
||||||
ota_confirm_tick = HAL_GetTick();
|
ota_confirm_tick = HAL_GetTick();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 首次发布成功后延迟 3s 再 OTA 确认 */
|
/* 首次发布成功后延迟 9s 再 OTA 确认(避开开机上报风暴:pow/ICCID/状态/电能) */
|
||||||
if (!ota_confirmed && g_net_mqtt_ready && g_net_first_publish_ok &&
|
if (!ota_confirmed && g_net_mqtt_ready && g_net_first_publish_ok &&
|
||||||
(HAL_GetTick() - ota_confirm_tick) > 3000) {
|
(HAL_GetTick() - ota_confirm_tick) > 9000) {
|
||||||
if (ota_simulate_fail) {
|
if (ota_simulate_fail) {
|
||||||
if (!ota_fail_logged) {
|
if (!ota_fail_logged) {
|
||||||
ota_fail_logged = 1;
|
ota_fail_logged = 1;
|
||||||
|
|
@ -171,8 +173,23 @@ static void app_task(void *arg)
|
||||||
/* 不置位 ota_confirmed,让 1 分钟健康超时触发复位 */
|
/* 不置位 ota_confirmed,让 1 分钟健康超时触发复位 */
|
||||||
} else {
|
} else {
|
||||||
ota_confirmed = 1;
|
ota_confirmed = 1;
|
||||||
NET_ota_confirm();
|
if (NET_ota_confirm() < 0) { /* <0 才是未送达;1=无挂起 OTA 属正常 */
|
||||||
log_info("> OTA 已确认(MQTT Ready + 首次发布成功 + 延迟3s)");
|
ota_report_pending = 1; /* 通知未送达,30s 后重试 */
|
||||||
|
ota_report_tick = HAL_GetTick();
|
||||||
|
log_warn("> OTA 确认通知未送达,稍后重试");
|
||||||
|
} else {
|
||||||
|
log_info("> OTA 已确认(MQTT Ready + 首次发布成功 + 延迟9s)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* OTA 确认通知未送达:每 30s 重试直到成功(本地确认标志已落盘,仅补平台通知) */
|
||||||
|
if (ota_report_pending && g_net_mqtt_ready &&
|
||||||
|
(HAL_GetTick() - ota_report_tick) > 30000) {
|
||||||
|
ota_report_tick = HAL_GetTick();
|
||||||
|
if (NET_ota_confirm() >= 0) {
|
||||||
|
ota_report_pending = 0;
|
||||||
|
log_info("> OTA 确认通知重试成功");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -252,6 +252,12 @@ W25Q64 外部 Flash(8MB):
|
||||||
| 槽 B | `0x040000` (256KB) | 新固件下载暂存 |
|
| 槽 B | `0x040000` (256KB) | 新固件下载暂存 |
|
||||||
| 信息区 A/B | `0x080000` / `0x081000` (各 4KB) | OTA 状态双副本(magic + 序号 + checksum) |
|
| 信息区 A/B | `0x080000` / `0x081000` (各 4KB) | OTA 状态双副本(magic + 序号 + checksum) |
|
||||||
|
|
||||||
|
### OTA 确认通知可靠性
|
||||||
|
|
||||||
|
- 确认通知(`iot.ota.confirm.post`)延后到首次发布成功后 **9 秒**发送,避开开机上报风暴(pow/ICCID/状态/电能挤占模组 MQTT 发送窗口)
|
||||||
|
- 本地确认标志和版本号**先于**通知落盘——通知发送失败不会导致 BL 误判回滚
|
||||||
|
- 通知未送达时每 30s 自动重试直到成功(仅补平台通知);开机 pow/ICCID 上报间隔拉开到 300ms
|
||||||
|
|
||||||
### 健康检查与回退
|
### 健康检查与回退
|
||||||
|
|
||||||
新固件"健康" = 4G 附着 → MQTT 连接 → 首次发布成功。回退判据(**3 次真实故障**才回退):
|
新固件"健康" = 4G 附着 → MQTT 连接 → 首次发布成功。回退判据(**3 次真实故障**才回退):
|
||||||
|
|
|
||||||
|
|
@ -137,3 +137,6 @@
|
||||||
(标识符 iccid,字符串;平台物模型加字符串字段即可,便于物联网卡套餐到期查询缴费)
|
(标识符 iccid,字符串;平台物模型加字符串字段即可,便于物联网卡套餐到期查询缴费)
|
||||||
- 4G 运行时关闭 WiFi 模块:ESP_EN(PA6) 默认低电平,切 WiFi 才拉高、切回 4G 拉低断电(省电/延长法拉电容续航)
|
- 4G 运行时关闭 WiFi 模块:ESP_EN(PA6) 默认低电平,切 WiFi 才拉高、切回 4G 拉低断电(省电/延长法拉电容续航)
|
||||||
- 断电消息提速:电量 <10% 判定从 3s 去抖改为 100ms 快速复核,确认后优先发 pow:0、再写记录、留 500ms 让模组发完再复位(5.5F 电容下原逻辑来不及上报)
|
- 断电消息提速:电量 <10% 判定从 3s 去抖改为 100ms 快速复核,确认后优先发 pow:0、再写记录、留 500ms 让模组发完再复位(5.5F 电容下原逻辑来不及上报)
|
||||||
|
- 修复 OTA 确认通知发送失败("重试 3 次无 OK"):确认从首次发布+3s 延后到 +9s 避开开机上报风暴;
|
||||||
|
本地确认标志/版本号先于通知落盘(通知失败不再影响防回滚);通知未送达每 30s 重试直到成功;
|
||||||
|
开机 pow/ICCID 上报间隔拉开到 300ms
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue