From c0b5fcc4c5a60fe00e614d6f6a67b1d198eb8cbb Mon Sep 17 00:00:00 2001
From: Wuchuyuan <1917507415@qq.com>
Date: Thu, 20 Aug 2026 16:12:08 +0800
Subject: [PATCH] =?UTF-8?q?=E5=8A=A0=E4=B8=8A=E6=A8=A1=E7=BB=84=20OTA?=
=?UTF-8?q?=EF=BC=9AAPP=20=E7=94=A8=2055AA=20=E7=9A=84=200x30~0x39=20?=
=?UTF-8?q?=E5=8D=87=E7=BA=A7=EF=BC=8CMCU=20=E5=92=8C=E6=A8=A1=E7=BB=84?=
=?UTF-8?q?=E5=88=86=E5=BC=80=E5=8D=87=E3=80=82=20-=20MCU=20=E5=8D=87?=
=?UTF-8?q?=E7=BA=A7=EF=BC=9A=E6=A8=A1=E7=BB=84=E5=8F=AA=E5=8E=9F=E6=A0=B7?=
=?UTF-8?q?=E8=BD=AC=E5=8F=91=EF=BC=8C=E4=B8=8D=E5=AD=98=20MCU=20=E5=9B=BA?=
=?UTF-8?q?=E4=BB=B6=20-=20=E6=A8=A1=E7=BB=84=E8=87=AA=E5=B7=B1=E5=8D=87?=
=?UTF-8?q?=E7=BA=A7=EF=BC=9A=E5=86=99=E5=88=B0=E5=A4=87=E7=94=A8=E5=8C=BA?=
=?UTF-8?q?=EF=BC=8C=E6=A0=A1=E9=AA=8C=E9=80=9A=E8=BF=87=E5=90=8E=E5=86=8D?=
=?UTF-8?q?=E5=88=87=E8=BF=87=E5=8E=BB=EF=BC=8C=E6=88=90=E5=8A=9F=E5=9B=9E?=
=?UTF-8?q?=200x34=EF=BC=8C=E7=AD=89=20APP=20=E5=9B=9E=200x35=20=E5=86=8D?=
=?UTF-8?q?=E9=87=8D=E5=90=AF=20-=20=E5=8D=87=E6=A8=A1=E7=BB=84=E6=97=B6?=
=?UTF-8?q?=E5=85=88=E9=80=9A=E7=9F=A5=20MCU=20=E5=85=88=E5=88=AB=E4=B8=8A?=
=?UTF-8?q?=E6=8A=A5=EF=BC=9B=E5=8D=87=E7=9A=84=E8=BF=87=E7=A8=8B=E4=B8=AD?=
=?UTF-8?q?=E4=B8=8D=E8=B7=91=E5=BF=83=E8=B7=B3=E3=80=81=E4=B8=8D=E5=A4=84?=
=?UTF-8?q?=E7=90=86=E6=99=AE=E9=80=9A=E6=8E=A7=E5=88=B6=20-=20=E5=85=B3?=
=?UTF-8?q?=E6=8E=89=E5=8E=9F=E6=9D=A5=E7=9A=84=20JSON=20OTA=EF=BC=8C?=
=?UTF-8?q?=E6=94=B9=E8=B5=B0=E8=BF=99=E6=9D=A1=E5=8D=8F=E8=AE=AE?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.gitignore | 1 -
Doc/OTA升级流程.md | 466 +++++++++
JBao_JL7016_MOD_FW/AC701N.cbp | 9 +
JBao_JL7016_MOD_FW/Makefile | 3 +
.../apps/usr_le_code/usr_le_adv.c | 25 +-
.../apps/usr_le_code/usr_le_api.h | 26 +-
.../apps/usr_le_code/usr_le_recieve.c | 502 +++-------
.../usr_mcu_bridge/business/mcu_business.c | 59 +-
.../usr_mcu_bridge/business/mcu_business.h | 44 +-
.../usr_mcu_bridge/business/mcu_link_status.c | 8 +-
.../apps/usr_mcu_bridge/business/mcu_ota.c | 942 ++++++++++++++++++
.../apps/usr_mcu_bridge/business/mcu_ota.h | 62 ++
.../usr_mcu_bridge/business/mcu_version.c | 8 +-
.../apps/usr_mcu_bridge/mcu_bridge.c | 20 +
.../apps/usr_mcu_bridge/mcu_bridge.h | 22 +-
.../apps/usr_mcu_bridge/protocol/mcu_cmd.h | 33 +-
.../apps/usr_mcu_bridge/protocol/mcu_frame.c | 34 +
.../apps/usr_mcu_bridge/protocol/mcu_frame.h | 23 +
.../usr_mcu_bridge/protocol/mcu_protocol.c | 244 ++++-
.../usr_mcu_bridge/protocol/mcu_protocol.h | 25 +
.../apps/usr_mcu_bridge/protocol/mcu_uart.c | 8 +-
JBao_JL7016_MOD_FW/apps/usr_ota/usr_ota.c | 481 +++++++++
JBao_JL7016_MOD_FW/apps/usr_ota/usr_ota.h | 101 ++
23 files changed, 2700 insertions(+), 446 deletions(-)
create mode 100644 Doc/OTA升级流程.md
create mode 100644 JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/business/mcu_ota.c
create mode 100644 JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/business/mcu_ota.h
create mode 100644 JBao_JL7016_MOD_FW/apps/usr_ota/usr_ota.c
create mode 100644 JBao_JL7016_MOD_FW/apps/usr_ota/usr_ota.h
diff --git a/.gitignore b/.gitignore
index 65311da..c0ab9d4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,3 @@
-PY32F040开发框架/*
.vscode/
模组功能分期清单.md
.clang-format
\ No newline at end of file
diff --git a/Doc/OTA升级流程.md b/Doc/OTA升级流程.md
new file mode 100644
index 0000000..190162f
--- /dev/null
+++ b/Doc/OTA升级流程.md
@@ -0,0 +1,466 @@
+# OTA升级流程
+
+# 修订记录
+
+|文档版本|日期|编写人|修订类型|修订内容|
+|---|---|---|---|---|
+|V1\.0\.0|2026\-07\-09|麦田|新建|首次发布OTA升级流程文档|
+
+# APP获取设备版本
+
+设备连接服务器后,主动上报当前版本给服务器,服务器进行维护。
+
+服务器维护:
+
+|**字段**|**说明**|
+|---|---|
+|固件类型|BLE / MCU |
+|固件版本|当前版本号|
+
+APP进入设备页面后,找服务器获取当前固件版本。
+
+服务器返回:
+
+```Plain Text
+MCU固件
+当前版本:V1.0.0
+
+BLE固件
+当前版本:V2.0.0
+```
+
+---
+
+# 服务器推送OTA版本给APP
+
+用户在服务器上传了bin文件,并填写了版本号和固件类型,点击发布。APP进入设备页面之后,服务器推送OTA文件信息给APP。
+
+服务器推送:
+
+```Plain Text
+MCU固件
+OTA版本:V1.2.0
+长度:128KB
+CRC32:0x12345678
+
+BLE固件
+OTA版本:V2.0.1
+长度:256KB
+CRC32:0x87654321
+```
+
+# APP进行版本比较
+
+APP比较:
+
+```Plain Text
+OTA版本 VS 设备当前版本
+```
+
+例如:
+
+|**固件**|**当前版本**|**OTA版本**|**是否升级**|
+|---|---|---|---|
+|MCU|V1\.0\.0|V1\.2\.0|是|
+|BLE|V2\.0\.0|V2\.0\.1|是|
+
+如果两个固件都需要升级:
+
+升级顺序:
+
+```Plain Text
+第一步:MCU升级
+第二步:BLE升级
+```
+
+避免 BLE 升级过程中影响 MCU OTA 通讯。
+
+---
+
+# APP通知BLE准备OTA
+
+APP发送**OTA升级请求**告诉 BLE 本次升级目标。
+
+例如升级 MCU:
+
+```Plain Text
+Target:MCU
+Firmware Version:V1.2.0
+Payload Length:128KB
+Payload CRC32:xxxx
+```
+
+---
+
+# BLE判断升级目标
+
+BLE收到:**OTA升级请求**
+
+进行固件类型判断:
+
+## 情况1:升级BLE
+
+流程:
+
+```Plain Text
+APP
+ |
+ | OTA升级请求
+ |
+BLE
+ |
+ | 自身检查
+ |
+BLE返回准备结果
+```
+
+检查:
+
+- Flash空间
+
+- OTA状态
+
+- 固件信息
+
+返回:OTA请求应答
+
+成功:
+
+```Plain Text
+Result = 0x00
+Max Data Length = xxx
+```
+
+然后APP开始发送BLE Bin。
+
+---
+
+## 情况2:升级MCU
+
+流程:
+
+```Plain Text
+APP
+↓
+BLE
+↓
+MCU
+```
+
+BLE收到后:
+
+提取:
+
+- Firmware Version
+
+- Payload Length
+
+- Payload CRC32
+
+发送:
+
+```Plain Text
+BLE
+|
+| OTA升级请求
+↓
+MCU
+```
+
+---
+
+# MCU检查OTA条件
+
+MCU收到升级请求。
+
+检查:
+
+1. 固件版本 :新版本 \> 当前版本
+
+2. Flash空间
+
+3. OTA状态 返回:OTA请求应答
+
+结果:
+
+|**Result**|含义|
+|---|---|
+|0x00|允许升级|
+|0x20|无需升级(版本一致)|
+|0x21|OTA 版本低于当前版本|
+|0x22|Flash 空间不足|
+|0x23|Bootloader 不支持|
+|0x24|OTA 状态错误|
+|0x25|固件类型不支持|
+
+同时返回:
+
+```Plain Text
+Max Data Length
+```
+
+例如:
+
+```Plain Text
+512 Byte
+```
+
+---
+
+# APP发送OTA Bin数据
+
+APP按照:
+
+```Plain Text
+Max Data Length
+```
+
+进行分包。
+
+例如:
+
+MCU允许:
+
+```Plain Text
+512 Byte
+```
+
+发送:
+
+```Plain Text
+当前分包序号
+总分包数
+OTA数据
+```
+
+流程:
+
+```Plain Text
+APP
+↓
+OTA_DATA
+↓
+BLE
+↓
+OTA_DATA
+↓
+MCU
+```
+
+---
+
+# MCU接收OTA数据
+
+MCU每收到一包:
+
+1. 校验数据包
+
+2. 写入OTA区域
+
+3. 累计接收长度
+
+例如:
+
+第一次:
+
+```Plain Text
+received = 512
+```
+
+第二次:
+
+```Plain Text
+received = 1024
+```
+
+直到:
+
+```Plain Text
+received == Payload Length
+```
+
+表示:
+
+数据接收完成。
+
+---
+
+# OTA数据包应答
+
+每包数据:
+
+MCU返回:OTA数据应答
+
+例如
+
+成功:
+
+```Plain Text
+Result=0x00
+```
+
+失败:
+
+```Plain Text
+Result=错误码
+```
+
+如果失败:
+
+BLE停止发送。
+
+---
+
+# MCU完成OTA校验
+
+全部数据接收完成。
+
+MCU:
+
+计算:CRC32
+
+比较:CRC32 VS OTA升级请求中的CRC32
+
+---
+
+## 校验成功
+
+MCU:保存升级信息。
+
+返回:OTA\_RESULT
+
+然后:重启。
+
+---
+
+## 校验失败
+
+返回:OTA\_RESULT
+
+错误码:
+
+例如:
+
+```Plain Text
+CRC错误
+```
+
+---
+
+# MCU通知BLE升级结果
+
+MCU升级完成后主动发送:MCU OTA完成通知
+
+协议:0x34
+
+内容:
+
+```Plain Text
+OTA Result
+MCU Version
+```
+
+BLE收到:添加上类型(0x01 MCU)
+
+上传APP。
+
+---
+
+# BLE通知APP升级结果
+
+BLE发送:OTA完成通知
+
+告诉APP:
+
+```Plain Text
+MCU升级完成
+版本:V1.2.0
+结果:成功
+```
+
+---
+
+# 如果BLE也需要升级
+
+MCU升级成功后:
+
+APP继续:
+
+```Plain Text
+Target = BLE
+```
+
+流程重新开始:
+
+```Plain Text
+APP
+↓
+BLE
+↓
+BLE自身OTA
+```
+
+完成后:
+
+BLE通知:
+
+```Plain Text
+OTA成功
+```
+
+APP更新最终状态。
+
+---
+
+# 完整流程图
+
+```Plain Text
+云服务器
+ |
+ |
+ ▼
+ 获取最新固件信息
+ |
+ ▼
+ APP比较版本,决定升级顺序
+ |
+ ┌───────────┴───────────┐
+ │ │
+ MCU升级 BLE升级
+ │ │
+ OTA升级请求 OTA升级请求
+ │ │
+ ▼ ▼
+ BLE BLE
+ │ │
+ │ 解包 ▼
+ ▼ 自身准备
+ OTA升级请求
+ │
+ ▼
+ MCU
+ │
+ ▼
+ OTA请求应答
+ │
+ ▼
+ OTA数据
+ │
+ ▼
+ OTA数据应答
+ │
+ ▼
+ CRC校验
+ │
+ ▼
+ OTA_RESULT
+ │
+ ▼
+ MCU通知BLE
+ │
+ ▼
+ BLE通知APP
+```
+
+
+
diff --git a/JBao_JL7016_MOD_FW/AC701N.cbp b/JBao_JL7016_MOD_FW/AC701N.cbp
index 2863999..f3e0222 100644
--- a/JBao_JL7016_MOD_FW/AC701N.cbp
+++ b/JBao_JL7016_MOD_FW/AC701N.cbp
@@ -177,6 +177,7 @@
+
@@ -800,6 +801,14 @@
+
+
+
+
+
+
+
+
diff --git a/JBao_JL7016_MOD_FW/Makefile b/JBao_JL7016_MOD_FW/Makefile
index ffcd9bd..f47b2f9 100644
--- a/JBao_JL7016_MOD_FW/Makefile
+++ b/JBao_JL7016_MOD_FW/Makefile
@@ -251,6 +251,7 @@ INCLUDES := \
-Iapps/usr_mcu_bridge \
-Iapps/usr_mcu_bridge/protocol \
-Iapps/usr_mcu_bridge/business \
+ -Iapps/usr_ota \
-Iapps/usr_periph \
-I$(SYS_INC_DIR) \
@@ -628,6 +629,8 @@ c_SRC_FILES := \
apps/usr_mcu_bridge/business/mcu_mgmt.c \
apps/usr_mcu_bridge/business/mcu_time.c \
apps/usr_mcu_bridge/business/mcu_version.c \
+ apps/usr_mcu_bridge/business/mcu_ota.c \
+ apps/usr_ota/usr_ota.c \
apps/usr_periph/usr_rtc.c \
apps/usr_le_code/ble_proto.c \
apps/usr_le_code/bleproto.c \
diff --git a/JBao_JL7016_MOD_FW/apps/usr_le_code/usr_le_adv.c b/JBao_JL7016_MOD_FW/apps/usr_le_code/usr_le_adv.c
index e0d81d6..96acc84 100644
--- a/JBao_JL7016_MOD_FW/apps/usr_le_code/usr_le_adv.c
+++ b/JBao_JL7016_MOD_FW/apps/usr_le_code/usr_le_adv.c
@@ -53,10 +53,19 @@ extern u8 muti_adv_data_len;
extern bleproto_authsetup_req_t usr_auth_data;
extern u8 usr_get_full();
-extern u16 usr_get_conn_service();
+extern u16 usr_get_conn_service(void);
extern int muti_make_set_adv_data(u8* data,u8 data_len);
extern u8 usr_get_ldoin_all();
+/**
+ * @brief BLE 是否已连接
+ * @return 1=已连接,0=未连接
+ */
+uint8_t usr_ble_is_connected(void)
+{
+ return (usr_get_conn_service() != 0) ? 1u : 0u;
+}
+
void usr_ble_adv_updata();
static int le_timer_handle=0;
@@ -231,7 +240,6 @@ void usr_reconn_adv()
muti_adv_data_len=bleproto_advdata_encode4reconn(muti_adv_data,txpower,gmac,manucode,pcode,prodcode);
usr_adv_flag=1;
}*/
-extern u8 usr_get_ota_status();
/**
* @brief 周期处理广播刷新(由 500ms 定时器调用)
@@ -252,7 +260,7 @@ void usr_ble_adv_updata()
ble_trans_module_enable(1);
}
- if(usr_get_conn_service()==0)
+ if (!usr_ble_is_connected())
{
/* 未连接:回连快广播(80)持续约 60s(500ms×120)后切慢广播 */
if(usr_get_adv_interval()==80)
@@ -284,13 +292,9 @@ void usr_ble_adv_updata()
}
- if(usr_get_ota_status()==0)
+ if (!usr_ble_is_connected())
{
- /* 不在 OTA:未连接则退出 OTA 残留(ota_succ 时不退) */
- if(usr_get_conn_service()==0)
- {
- if(usr_var.usr_ota_succ_flag==0)usr_ota_exit();
- }
+ mcu_bridge_on_ble_disconnected();
}
}
@@ -323,7 +327,7 @@ void usr_ota_process()
ble_multi_trans_disconnect(); /* 第一次:先断开,好换 OTA 广播地址 */
}
- if (usr_get_conn_service() == 0)
+ if (!usr_ble_is_connected())
{
cnt++;
if (cnt == 2)
@@ -344,6 +348,7 @@ void usr_ota_process()
void usr_app_ota_init()
{
+ /* 旧版 RCSP/JSON OTA 入口;产品 55AA dual_bank 路径不要调用 */
usr_var.usr_goto_updata = 1;
if (usr_var.ota_timer == 0)
{
diff --git a/JBao_JL7016_MOD_FW/apps/usr_le_code/usr_le_api.h b/JBao_JL7016_MOD_FW/apps/usr_le_code/usr_le_api.h
index 7264919..b08156c 100644
--- a/JBao_JL7016_MOD_FW/apps/usr_le_code/usr_le_api.h
+++ b/JBao_JL7016_MOD_FW/apps/usr_le_code/usr_le_api.h
@@ -2,12 +2,13 @@
* @file usr_le_api.h
* @brief usr_le_code 对外 API(广播 / 收发 / 配网 / MCU 帧转发)
* @author cyWu <1917507415@qq.com>
- * @date 2026.08.13
- * @version V1.2.0
+ * @date 2026.08.15
+ * @version V1.3.0
* @history
* - V1.0.0, 2026.08.03, cyWu, 首次发布
* - V1.1.0, 2026.08.06, cyWu, 增加 MCU 帧 BLE 发送接口,解耦模组桥
* - V1.2.0, 2026.08.13, cyWu, P1:复位前断连停广播;0x41 走 cmd100 上报
+ * - V1.3.0, 2026.08.15, cyWu, 连接查询/写配对/BLE 发送收口到本头文件
******************************************************************************/
#ifndef __USR_LE_API_H__
@@ -79,7 +80,26 @@ uint8_t usr_get_pair_flag(void);
uint8_t usr_ble_is_notify_ready(void);
/**
- * @brief 启动应用侧 OTA 流程
+ * @brief BLE 是否已建立连接
+ * @return 1=已连接,0=未连接
+ */
+uint8_t usr_ble_is_connected(void);
+
+/**
+ * @brief 标记已配网并保存三元组到 VM
+ */
+void usr_write_pair_info(void);
+
+/**
+ * @brief BLE 协议栈发送(实现于协议栈适配层)
+ * @param data 发送缓冲
+ * @param len 长度
+ */
+void usr_ble_send_data(uint8_t *data, uint16_t len);
+
+/**
+ * @brief 旧版 RCSP/JSON OTA:改 MAC 发 OTA 广播
+ * @note 产品路径走 SOC 55AA 0x30~0x39 + dual_bank,不要调用本函数
*/
void usr_app_ota_init(void);
diff --git a/JBao_JL7016_MOD_FW/apps/usr_le_code/usr_le_recieve.c b/JBao_JL7016_MOD_FW/apps/usr_le_code/usr_le_recieve.c
index 247283d..c57f6b2 100644
--- a/JBao_JL7016_MOD_FW/apps/usr_le_code/usr_le_recieve.c
+++ b/JBao_JL7016_MOD_FW/apps/usr_le_code/usr_le_recieve.c
@@ -1,3 +1,14 @@
+/******************************************************************************
+ * @file usr_le_recieve.c
+ * @brief BLE 收包入口:bleproto 解析后转交 mcu_bridge / 组应答
+ * @author cyWu <1917507415@qq.com>
+ * @date 2026.08.15
+ * @version V1.1.0
+ * @history
+ * - V1.0.0, 2026.08.03, cyWu, 首次发布
+ * - V1.1.0, 2026.08.15, cyWu, 只经 mcu_bridge.h 访问桥;JSON OTA 废弃
+ ******************************************************************************/
+
#include "system/includes.h"
#include "app_config.h"
#include "asm/pwm_led.h"
@@ -11,67 +22,103 @@
#include "user_cfg.h"
#include "key_event_deal.h"
#include "audio.h"
-#include "update.h"
#include "vm.h"
#include "bleproto.h"
#include "bleproto_api.h"
#include "ble_proto.h"
#include "mcu_frame.h"
+#include "mcu_bridge.h"
#include "usr_le_api.h"
+
#define LOG_TAG_CONST APP
-#define LOG_TAG "[APP]"
+#define LOG_TAG "[APP]"
#define LOG_ERROR_ENABLE
#define LOG_DEBUG_ENABLE
#define LOG_INFO_ENABLE
-/* #define LOG_DUMP_ENABLE */
-#define LOG_CLI_ENABLE
#include "debug.h"
-#define FIRM_PACK_LEN 2048 // 128
+/* 拼 GATT 分包用:最大业务包 ≈ bleproto头8 + V2开销~24 + MCU 0x32(640) ≈ 672,
+ * AUTHSETUP JSON 约数百字节。1024 覆盖产品路径并留余量(不再按 JSON OTADATA 的 2KB+ 预留)。 */
+#define USR_LE_RX_BUF_LEN 1024
+#define USR_LE_V2_WRAP_OVERHEAD 32u /* V2 头开销,MCU 帧最大 128 → 约 151 */
+#define USR_LE_V2_CMD_REPORT 100u /* REPORTCMD_V2 内层 cmd */
+#define USR_LE_V2_CMD_RUN 110u /* RUNCMD_V2 内层 cmd */
-static u8 start_data[10] = {0x55, 0xAA, 0xFE, 0x61, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00};
-static u8 end_data[2] = {0xaa, 0x55};
static int encode_len = 0x00;
static u8 txbuf[384] = {0};
-static u8 rxbuf[FIRM_PACK_LEN + 1024] = {0};
-static u8 ota_source_data[FIRM_PACK_LEN * 2] = {0};
-
-extern u16 usr_get_conn_service();
-extern void ble_multi_trans_disconnect(void);
-extern void usr_ble_send_data(u8 *data, u16 len); /// BLE发送接口,MTU 244
-extern void usr_write_pair_info();
-extern void usr_clear_pair_info();
-extern void app_audio_volume_set(u8 value);
-extern int dual_ota_app_data_deal(u32 msg, u8 *buf, u32 len);
-extern int uart_tr_send_data(u8 *data, u32 len);
-extern void usr_intterupt_close();
-extern void mcu_bridge_on_paired(void);
-extern void mcu_bridge_on_pair_cleared(void);
-extern void mcu_bridge_on_app_frame(const uint8_t *data, uint16_t len);
-extern void mcu_bridge_on_gateway_report_ack(uint16_t cmd,
- uint8_t gateway_int_cnt,
- int32_t gateway_int0);
-extern void mcu_bridge_on_network_time(uint64_t unix_s, int32_t gmtoff_s);
-
-void usr_ota_deal_task();
-void usr_data_ana(bleproto_appdata_desc_t *desc);
-void usr_run_cmd_code_v2(ble_proto_packet_t *runcmd_par, u8 usr_msg_id);
+static u8 rxbuf[USR_LE_RX_BUF_LEN] = {0};
+static u8 s_last_runcmd_msgid = 0; /* RUNCMD_V2 应答须回填最近 msgid */
bleproto_authsetup_req_t usr_auth_data;
-void usr_le_data_recieve(u8 *data, u16 len) /// BLE数据接收
+static void usr_data_ana(bleproto_appdata_desc_t *desc);
+static void usr_run_cmd_code_v2(ble_proto_packet_t *runcmd_par, u8 usr_msg_id);
+
+/**
+ * @brief 填 bleproto 应答头
+ * @param out 输出 desc
+ * @param in 请求 desc(取 msgid)
+ * @param serviceid 服务号
+ */
+static void usr_le_fill_rsp_header(bleproto_appdata_desc_t *out,
+ const bleproto_appdata_desc_t *in,
+ uint8_t serviceid)
+{
+ out->header.version = 0;
+ out->header.datafmt = BLEPROTO_APPDATA_DATAFMT_JSON;
+ out->header.msgtype = BLEPROTO_APPDATA_MSGTYPE_RSP;
+ out->header.msgid = in->header.msgid;
+ out->header.encrypt = 0;
+ out->header.serviceid = serviceid;
+}
+
+/**
+ * @brief 编码并经 BLE 发出
+ * @param desc 已填好的应答
+ */
+static void usr_le_encode_and_send(bleproto_appdata_desc_t *desc)
+{
+ encode_len = bleproto_appdata_encode(txbuf, sizeof(txbuf), sizeof(txbuf), desc);
+ if (encode_len > 0)
+ {
+ printf("encode_len = %d\n", encode_len);
+ usr_ble_send_data(txbuf, encode_len);
+ }
+ else
+ {
+ printf("encode_len fail %d\n", encode_len);
+ }
+}
+
+/**
+ * @brief 是否走网关主动上报(REPORTCMD_V2)
+ * @param mcu_cmd 55AA 命令字
+ * @return 1=主动上报,0=RUNCMD 应答
+ */
+static uint8_t usr_le_is_gateway_report_cmd(uint8_t mcu_cmd)
+{
+ return (mcu_cmd == MCU_CMD_REPORT ||
+ mcu_cmd == MCU_CMD_OTA_COMPLETE ||
+ mcu_cmd == MCU_CMD_OTA_STOP_MCU ||
+ mcu_cmd == MCU_CMD_GET_MCU_INFO_ACK)
+ ? 1u
+ : 0u;
+}
+
+/**
+ * @brief BLE 收包入口
+ * @param data 收包数据
+ * @param len 数据长度
+ */
+void usr_le_data_recieve(u8 *data, u16 len)
{
- // log_info("le recieve: %d",len);
- // printf_buf(data, len);
static u16 usr_len = 0;
/* desc 含 ycmd 等 union,放 static,避免每次收包在栈上开大块 */
static bleproto_appdata_desc_t s_rx_desc;
-
int rc_result = 0;
if ((u32)usr_len + len > sizeof(rxbuf))
{
- /* 累积超界:多半是丢包/半包导致 decode 一直返回 0,防止缓冲区溢出 */
printf("### rxbuf overflow, usr_len=%u len=%u, reset\n", usr_len, len);
usr_len = 0;
return;
@@ -80,128 +127,77 @@ void usr_le_data_recieve(u8 *data, u16 len) /// BLE数据接收
memcpy(&rxbuf[usr_len], data, len);
usr_len += len;
rc_result = bleproto_appdata_decode(rxbuf, usr_len, &s_rx_desc);
- // printf("rc_result_le## = %d\n",rc_result);
if (rc_result < 0)
{
- // 接收到了非法的数据包,无法解析
printf("### invild pack\n");
usr_len = 0;
}
else if (rc_result == 0)
{
- // 接收的数据不完整,继续接收数据
printf("### keep recieving\n");
}
- else //(rc_result > 0)
+ else
{
- // 数据解析成功
printf("### pack recieved ok\n");
usr_len = 0;
usr_data_ana(&s_rx_desc);
}
}
-void usr_data_ana(bleproto_appdata_desc_t *desc)
+/**
+ * @brief 解析 bleproto 服务并分发
+ * @param desc 已 decode 的包
+ */
+static void usr_data_ana(bleproto_appdata_desc_t *desc)
{
- /* 应答侧 desc 同样较大,用 static,避免 BLE 任务栈峰值 */
static bleproto_appdata_desc_t s_encode_desc;
memset(&s_encode_desc, 0, sizeof(s_encode_desc));
- printf("#############334 desc->header.serviceid = %d\n", desc->header.serviceid);
- if (desc->header.msgtype /*desc.header.msgtype*/ == BLEPROTO_APPDATA_MSGTYPE_REQ)
+ printf("desc serviceid=%d msgtype=%d\n",
+ desc->header.serviceid, desc->header.msgtype);
+
+ if (desc->header.msgtype == BLEPROTO_APPDATA_MSGTYPE_REQ)
{
switch (desc->header.serviceid)
{
-
- case E_BLEPROTO_SERVICE_ID_DEVICEINFO:
-
- {
- bleproto_deviceinfo_req_t *req = &desc->datast.deviceinfo_req;
- /* 网关从子设备连上开始计时,每 12 小时经 DEVICEINFO 同步一次时间戳+时区 */
- mcu_bridge_on_network_time(req->t, req->gmtoff);
- }
-
- s_encode_desc.header.version = 0;
- s_encode_desc.header.datafmt = BLEPROTO_APPDATA_DATAFMT_JSON;
- s_encode_desc.header.msgtype = BLEPROTO_APPDATA_MSGTYPE_RSP;
- s_encode_desc.header.msgid = desc->header.msgid; // 这里的msg_id为上面解析到的包的msg_id;
- s_encode_desc.header.encrypt = 0;
- s_encode_desc.header.serviceid = E_BLEPROTO_SERVICE_ID_DEVICEINFO;
-
- // deviceinfo rsp序列化
+ case E_BLEPROTO_SERVICE_ID_DEVICEINFO: {
+ bleproto_deviceinfo_req_t *req = &desc->datast.deviceinfo_req;
bleproto_deviceinfo_rsp_t *rsp = &s_encode_desc.datast.deviceinfo_rsp;
- {
- // 填写设备相关信息
- rsp->hbcycle = 1000;
- strcpy(rsp->swv, "V1.0.0.00");
- strcpy(rsp->hwv, "V1.0.0.00");
- rsp->longcon = 1;
- rsp->prototype = 4;
- }
- encode_len = bleproto_appdata_encode(txbuf, sizeof(txbuf), sizeof(txbuf), &s_encode_desc);
- if (encode_len > 0)
- {
- printf("encode_len = %d\n", encode_len);
- // 调用蓝牙协议栈发送数据接口,将数据一包一包的发送出去
- // 第一包:packetlen
- // 第二包:packetlen
- // ......
- // 第N包: totallen - (packetlen * (N -1))
- usr_ble_send_data(txbuf, encode_len);
- }
- else
- {
- printf("2encode_len = %d\n", encode_len);
- }
- // printf("E_BLEPROTO_SERVICE_ID_DEVICEINFO\n");
- break;
- case E_BLEPROTO_SERVICE_ID_AUTHSETUP:
- // 设备处理,回复响应
- printf("E_BLEPROTO_SERVICE_ID_AUTHSETUP\n");
- s_encode_desc.header.version = 0;
- s_encode_desc.header.datafmt = BLEPROTO_APPDATA_DATAFMT_JSON;
- s_encode_desc.header.msgtype = BLEPROTO_APPDATA_MSGTYPE_RSP;
- s_encode_desc.header.msgid = desc->header.msgid; // 这里的msg_id为上面解析到的包的msg_id;
- s_encode_desc.header.encrypt = 0;
- s_encode_desc.header.serviceid = E_BLEPROTO_SERVICE_ID_AUTHSETUP;
+ /* 网关从子设备连上开始计时,每 12 小时经 DEVICEINFO 同步一次 */
+ mcu_bridge_on_network_time(req->t, req->gmtoff);
+
+ usr_le_fill_rsp_header(&s_encode_desc, desc, E_BLEPROTO_SERVICE_ID_DEVICEINFO);
+ rsp->hbcycle = 1000;
+ strcpy(rsp->swv, "V1.0.0.00");
+ strcpy(rsp->hwv, "V1.0.0.00");
+ rsp->longcon = 1;
+ rsp->prototype = 4;
+ usr_le_encode_and_send(&s_encode_desc);
+ break;
+ }
+ case E_BLEPROTO_SERVICE_ID_AUTHSETUP: {
bleproto_authsetup_rsp_t *au_rsp = &s_encode_desc.datast.authsetup_rsp;
+
+ printf("E_BLEPROTO_SERVICE_ID_AUTHSETUP\n");
+ usr_le_fill_rsp_header(&s_encode_desc, desc, E_BLEPROTO_SERVICE_ID_AUTHSETUP);
au_rsp->errcode = 0;
-
memcpy(&usr_auth_data, &desc->datast.authsetup_req, sizeof(usr_auth_data));
+ if (usr_auth_data.paired_flag == 0)
{
- // 保存三元组
- if (usr_auth_data.paired_flag == 0)
- {
- usr_write_pair_info();
- mcu_bridge_on_paired();
- printf("usr_auth_data %s %s ", usr_auth_data.authcode, usr_auth_data.devid);
- printf_buf(usr_auth_data.gwmac, 6);
- }
- }
-
- encode_len = bleproto_appdata_encode(txbuf, sizeof(txbuf), sizeof(txbuf), &s_encode_desc);
- if (encode_len > 0)
- {
- printf("encode_len = %d\n", encode_len);
- usr_ble_send_data(txbuf, encode_len);
- }
- else
- {
- printf("2encode_len = %d\n", encode_len);
+ usr_write_pair_info();
+ mcu_bridge_on_paired();
+ printf("usr_auth_data %s %s ", usr_auth_data.authcode, usr_auth_data.devid);
+ printf_buf(usr_auth_data.gwmac, 6);
}
+ usr_le_encode_and_send(&s_encode_desc);
break;
- case E_BLEPROTO_SERVICE_ID_AUTHDELETE:
- // 设备处理,回复响应
- printf("E_BLEPROTO_SERVICE_ID_AUTHDELETE\n");
- s_encode_desc.header.version = 0;
- s_encode_desc.header.datafmt = BLEPROTO_APPDATA_DATAFMT_JSON;
- s_encode_desc.header.msgtype = BLEPROTO_APPDATA_MSGTYPE_RSP;
- s_encode_desc.header.msgid = desc->header.msgid; // 这里的msg_id为上面解析到的包的msg_id;
- s_encode_desc.header.encrypt = 0;
- s_encode_desc.header.serviceid = E_BLEPROTO_SERVICE_ID_AUTHSETUP;
-
+ }
+ case E_BLEPROTO_SERVICE_ID_AUTHDELETE: {
bleproto_authdelete_rsp_t *aude_rsp = &s_encode_desc.datast.authdelete_rsp;
+
+ printf("E_BLEPROTO_SERVICE_ID_AUTHDELETE\n");
+ usr_le_fill_rsp_header(&s_encode_desc, desc, E_BLEPROTO_SERVICE_ID_AUTHDELETE);
memcpy(aude_rsp->gwmac, usr_auth_data.gwmac, 6);
if (!memcmp(usr_auth_data.authcode, desc->datast.authdelete_req.authcode, 32))
{
@@ -212,20 +208,10 @@ void usr_data_ana(bleproto_appdata_desc_t *desc)
{
aude_rsp->errcode = 501;
}
-
- encode_len = bleproto_appdata_encode(txbuf, sizeof(txbuf), sizeof(txbuf), &s_encode_desc);
- if (encode_len > 0)
- {
- printf("encode_len = %d\n", encode_len);
- usr_ble_send_data(txbuf, encode_len);
- }
- else
- {
- printf("2encode_len = %d\n", encode_len);
- }
+ usr_le_encode_and_send(&s_encode_desc);
break;
+ }
case E_BLEPROTO_SERVICE_ID_RUNCMD:
- // 设备处理,回复响应
printf("E_BLEPROTO_SERVICE_ID_RUNCMD\n");
break;
case E_BLEPROTO_SERVICE_ID_RUNCMD_V2:
@@ -233,107 +219,28 @@ void usr_data_ana(bleproto_appdata_desc_t *desc)
usr_run_cmd_code_v2(&desc->datast.runcmd_v2_req, desc->header.msgid);
break;
case E_BLEPROTO_SERVICE_ID_OTANOTIFY:
- printf("E_BLEPROTO_SERVICE_ID_OTANOTIFY\n");
- s_encode_desc.header.version = 0;
- s_encode_desc.header.datafmt = BLEPROTO_APPDATA_DATAFMT_JSON;
- s_encode_desc.header.msgtype = BLEPROTO_APPDATA_MSGTYPE_RSP;
- s_encode_desc.header.msgid = desc->header.msgid; // 这里的msg_id为上面解析到的包的msg_id;
- s_encode_desc.header.encrypt = 0;
- s_encode_desc.header.serviceid = E_BLEPROTO_SERVICE_ID_OTANOTIFY;
- s_encode_desc.datast.doaction_rsp.errcode = 0;
-
- bleproto_otanotify_rsp_t *in_ota_rsp = &s_encode_desc.datast.otanotify_rsp;
-
- bleproto_otanotify_req_t *in_ota_data2 = &desc->datast.otanotify_req;
- printf("in_ota_data id = %d\n", in_ota_data2->id);
- printf("in_ota_data ver = %s\n", in_ota_data2->version);
- printf("in_ota_data size= %d\n", in_ota_data2->size);
- printf("in_ota_data md5 = %s\n", in_ota_data2->md5);
- {
- in_ota_rsp->errcode = 0;
- // 执行命令
- }
-
- usr_var.usr_ota_data_id = in_ota_data2->id; // int
- usr_var.usr_ota_data_size = in_ota_data2->size;
- encode_len = bleproto_appdata_encode(txbuf, sizeof(txbuf), sizeof(txbuf), &s_encode_desc);
- if (encode_len > 0)
- {
- printf("encode_len = %d\n", encode_len);
- usr_ble_send_data(txbuf, encode_len);
- }
- else
- {
- printf("2encode_len = %d\n", encode_len);
- }
-
- usr_var.usr_allow_to_flash = 0;
- usr_var.usr_data_index = 0;
- start_data[2] = usr_var.usr_ota_data_size & 0x000000ff;
- start_data[3] = (usr_var.usr_ota_data_size >> 8) & 0x000000ff;
- start_data[4] = (usr_var.usr_ota_data_size >> 16) & 0x000000ff;
- start_data[5] = (usr_var.usr_ota_data_size >> 24) & 0x000000ff;
- dual_ota_app_data_deal(0, start_data, 10);
- printf("#### buf total size = %d\n", usr_var.usr_ota_data_size);
- printf_buf(start_data, 10);
-
- s_encode_desc.header.version = 0;
- s_encode_desc.header.datafmt = BLEPROTO_APPDATA_DATAFMT_JSON;
- s_encode_desc.header.msgtype = BLEPROTO_APPDATA_MSGTYPE_REQ;
- s_encode_desc.header.msgid = 0; // desc->header.msgid;
- s_encode_desc.header.encrypt = 0;
- s_encode_desc.header.serviceid = E_BLEPROTO_SERVICE_ID_OTADATA;
- s_encode_desc.datast.doaction_rsp.errcode = 0;
-
- s_encode_desc.datast.otadata_req.id = usr_var.usr_ota_data_id;
- s_encode_desc.datast.otadata_req.offset = usr_var.usr_data_index;
- s_encode_desc.datast.otadata_req.maxsize = FIRM_PACK_LEN;
- encode_len = bleproto_appdata_encode(txbuf, sizeof(txbuf), sizeof(txbuf), &s_encode_desc);
- if (encode_len > 0)
- {
- usr_ble_send_data(txbuf, encode_len);
- }
- else
- {
- printf("2encode_len err = %d\n", encode_len);
- }
-
- if (usr_var.usr_double_ota_timer_id == 0)
- {
- clk_set("sys", 48 * 1000000L);
- usr_var.usr_double_ota_timer_id = 1;
- // clk_set("sys", 120 * 1000000L);
- os_task_create(usr_ota_deal_task, NULL, 31, FIRM_PACK_LEN, 0, "usr_ota");
- }
-
- break;
case E_BLEPROTO_SERVICE_ID_OTADATA:
- printf("E_BLEPROTO_SERVICE_ID_OTADATA\n");
- break;
case E_BLEPROTO_SERVICE_ID_OTADATA_V2:
- printf("E_BLEPROTO_SERVICE_ID_OTADATA\n");
+ printf("JSON OTA deprecated, use SOC 0x30/0x32\n");
break;
-
default:
printf("err !!! default event desc->header.serviceid =%d\n", desc->header.serviceid);
break;
}
+ return;
}
- else if (desc->header.msgtype == BLEPROTO_APPDATA_MSGTYPE_RSP)
+
+ if (desc->header.msgtype == BLEPROTO_APPDATA_MSGTYPE_RSP)
{
- printf("BLEPROTO_APPDATA_MSGTYPE_RSP\n");
switch (desc->header.serviceid)
{
case E_BLEPROTO_SERVICE_ID_REPORTCMD:
- // 主控回复的响应,设备处理
- printf("E_BLEPROTO_SERVICE_ID_REPORTCMD\n");
- printf("cmd id = %d", desc->datast.reportcmd_rsp.errcode);
+ printf("E_BLEPROTO_SERVICE_ID_REPORTCMD errcode=%d\n", desc->datast.reportcmd_rsp.errcode);
break;
- case E_BLEPROTO_SERVICE_ID_REPORTCMD_V2:
- {
- /* 网关对 MCU 主动上报的应答:成功后由桥接层回 MCU 0x08 */
+ case E_BLEPROTO_SERVICE_ID_REPORTCMD_V2: {
ble_proto_packet_t *rpt_ack = &desc->datast.reportcmd_v2_rsp;
- printf("E_BLEPROTO_SERVICE_ID_REPORTCMD_V2 cmd=%u cnt=%u int0=%ld\n",
+
+ printf("REPORTCMD_V2 cmd=%u cnt=%u int0=%ld\n",
(unsigned)rpt_ack->cmd,
(unsigned)rpt_ack->gateway_int_cnt,
(long)((rpt_ack->gateway_int_cnt > 0) ? rpt_ack->gateway_int[0] : 0));
@@ -344,121 +251,22 @@ void usr_data_ana(bleproto_appdata_desc_t *desc)
break;
}
case E_BLEPROTO_SERVICE_ID_OTADATA:
- bleproto_otadata_rsp_t *ota_data_rsp = &desc->datast.otadata_rsp;
- // printf("errcode = %d offset = %d len = %d\n",ota_data_rsp->errcode,ota_data_rsp->offset,ota_data_rsp->len);
- uart_tr_send_data((u8 *)ota_data_rsp->data, ota_data_rsp->len);
- memcpy(&ota_source_data[usr_var.usr_allow_to_flash], ota_data_rsp->data, ota_data_rsp->len);
- usr_var.usr_allow_to_flash += ota_data_rsp->len;
- usr_var.usr_start_write_flash = 0;
- if (usr_var.usr_allow_to_flash >= (FIRM_PACK_LEN * 2)) // 0 - 1024
- // 1024 - 2048
- // 2048 - 3072
- // 3072 - 4096
- {
- usr_var.usr_start_write_flash = 1;
- }
- else
- {
- usr_var.usr_data_index += FIRM_PACK_LEN;
- if (usr_var.usr_data_index >= usr_var.usr_ota_data_size)
- {
- usr_var.usr_start_write_flash = 2;
- break;
- }
- // bleproto_appdata_desc_t encode_desc = { 0 };
- s_encode_desc.header.version = 0;
- s_encode_desc.header.datafmt = BLEPROTO_APPDATA_DATAFMT_JSON;
- s_encode_desc.header.msgtype = BLEPROTO_APPDATA_MSGTYPE_REQ;
- s_encode_desc.header.msgid = 0; // desc->header.msgid;
- s_encode_desc.header.encrypt = 0;
- s_encode_desc.header.serviceid = E_BLEPROTO_SERVICE_ID_OTADATA;
- s_encode_desc.datast.doaction_rsp.errcode = 0;
-
- s_encode_desc.datast.otadata_req.id = usr_var.usr_ota_data_id;
- s_encode_desc.datast.otadata_req.offset = usr_var.usr_data_index;
- s_encode_desc.datast.otadata_req.maxsize = FIRM_PACK_LEN;
- encode_len = bleproto_appdata_encode(txbuf, sizeof(txbuf), sizeof(txbuf), &s_encode_desc);
- if (encode_len > 0)
- {
- usr_ble_send_data(txbuf, encode_len);
- }
- else
- {
- printf("2encode_len err = %d\n", encode_len);
- }
- }
+ printf("JSON OTA deprecated, use SOC 0x32\n");
break;
default:
- printf("E_BLEPROTO_SERVICE_ID_REPORTCMD\n");
+ printf("unhandled RSP serviceid=%d\n", desc->header.serviceid);
break;
}
}
}
-void usr_ota_deal_task()
+/**
+ * @brief APP RUNCMD_V2:55AA 字节流转交桥接层
+ * @param runcmd_par 内层包
+ * @param usr_msg_id bleproto msgid
+ */
+static void usr_run_cmd_code_v2(ble_proto_packet_t *runcmd_par, u8 usr_msg_id)
{
- os_time_dly(5);
- static bleproto_appdata_desc_t s_ota_encode_desc;
- while (1)
- {
- if (usr_var.usr_ota_succ_flag == 0)
- {
- if (usr_var.usr_start_write_flash == 1)
- {
- // OS_ENTER_CRITICAL();
- usr_var.usr_data_index += FIRM_PACK_LEN;
- usr_var.usr_start_write_flash = 0;
- dual_ota_app_data_deal(0, ota_source_data, usr_var.usr_allow_to_flash);
- os_time_dly(1);
- // OS_EXIT_CRITICAL();
- // printf("usr_var.usr_allow_to_flash = %d\n",usr_var.usr_allow_to_flash);
- usr_var.usr_allow_to_flash = 0;
-
- memset(&s_ota_encode_desc, 0, sizeof(s_ota_encode_desc));
- s_ota_encode_desc.header.version = 0;
- s_ota_encode_desc.header.datafmt = BLEPROTO_APPDATA_DATAFMT_JSON;
- s_ota_encode_desc.header.msgtype = BLEPROTO_APPDATA_MSGTYPE_REQ;
- s_ota_encode_desc.header.msgid = 0; // desc->header.msgid;
- s_ota_encode_desc.header.encrypt = 0;
- s_ota_encode_desc.header.serviceid = E_BLEPROTO_SERVICE_ID_OTADATA;
- s_ota_encode_desc.datast.doaction_rsp.errcode = 0;
-
- s_ota_encode_desc.datast.otadata_req.id = usr_var.usr_ota_data_id;
- s_ota_encode_desc.datast.otadata_req.offset = usr_var.usr_data_index;
- s_ota_encode_desc.datast.otadata_req.maxsize = FIRM_PACK_LEN;
- encode_len = bleproto_appdata_encode(txbuf, sizeof(txbuf), sizeof(txbuf), &s_ota_encode_desc);
- if (encode_len > 0)
- {
- usr_ble_send_data(txbuf, encode_len);
- }
- else
- {
- printf("2encode_len err = %d\n", encode_len);
- }
- }
- else if (usr_var.usr_start_write_flash == 2)
- {
- printf("usr_start_write_flash = %d\n", usr_var.usr_start_write_flash);
- usr_var.usr_start_write_flash = 0;
- // OS_ENTER_CRITICAL();
- dual_ota_app_data_deal(0, ota_source_data, usr_var.usr_allow_to_flash);
- // OS_EXIT_CRITICAL();
- printf("usr_var.usr_allow_to_flash = %d\n", usr_var.usr_allow_to_flash);
- usr_var.usr_allow_to_flash = 0;
- os_time_dly(30);
- dual_ota_app_data_deal(0, end_data, 2);
- }
- }
- os_time_dly(10);
- // putchar('6');
- }
-}
-
-/* 最近一次收到 RUNCMD_V2 请求的 msgid,应答时回填 */
-static u8 s_last_runcmd_msgid = 0;
-void usr_run_cmd_code_v2(ble_proto_packet_t *runcmd_par, u8 usr_msg_id)
-{
- u8 mcu_frame[MCU_FRAME_MAX_LEN];
u16 copy_len;
if (!runcmd_par || !runcmd_par->bytes_data || runcmd_par->device_bytes_len == 0)
@@ -472,15 +280,14 @@ void usr_run_cmd_code_v2(ble_proto_packet_t *runcmd_par, u8 usr_msg_id)
runcmd_par->cmd, runcmd_par->device_bytes_len);
copy_len = runcmd_par->device_bytes_len;
- if (copy_len > sizeof(mcu_frame))
+ if (copy_len > MCU_OTA_FRAME_MAX_LEN)
{
printf("usr_run_cmd_code_v2 bytes too long %u\n", copy_len);
return;
}
- memcpy(mcu_frame, runcmd_par->bytes_data, copy_len);
s_last_runcmd_msgid = usr_msg_id;
- mcu_bridge_on_app_frame(mcu_frame, copy_len);
+ mcu_bridge_on_app_frame(runcmd_par->bytes_data, copy_len);
}
/**
@@ -488,22 +295,19 @@ void usr_run_cmd_code_v2(ble_proto_packet_t *runcmd_par, u8 usr_msg_id)
* @param data 55AA 完整帧
* @param len 帧长度
* @note 主动上报(0x07/0x34/0x38/0x41) → REPORTCMD_V2;其余 → RUNCMD_V2 应答
- * @note 轻量路径:栈上仅 ble_proto_packet_t + 小 v2 缓冲,
- * 不走 bleproto_appdata_desc_t / bleproto_appdata_encode(约 2.3KB×2)
* @return 1=已交给协议栈,0=参数非法/超长/CCC 未就绪/组包失败
*/
uint8_t usr_le_send_mcu_frame(u8 *data, u16 len)
{
ble_proto_packet_t pkt;
bleproto_appdata_header_t hdr;
- /* V2开销:Hdr6+gw_cnt1+gw_int8+dev6+CRC2=23,MCU帧最大128 → 需约151 */
- u8 v2_buf[MCU_FRAME_MAX_LEN + 32];
+ u8 v2_buf[MCU_FRAME_MAX_LEN + USR_LE_V2_WRAP_OVERHEAD];
u8 mcu_cmd;
u8 is_active_send;
int plen;
int elen;
- if (!data || len < 5)
+ if (!data || len < MCU_FRAME_OVERHEAD)
{
printf("usr_le_send_mcu_frame invalid param, len=%d\n", len);
return 0;
@@ -513,45 +317,35 @@ uint8_t usr_le_send_mcu_frame(u8 *data, u16 len)
printf("usr_le_send_mcu_frame frame too long %u\n", len);
return 0;
}
- /* 已配网/已连接不等于能发:主机写完 CCC 才允许 notify */
if (!usr_ble_is_notify_ready())
{
- printf("usr_le_send_mcu_frame wait CCC cmd=0x%02X\n", data[4]);
+ printf("usr_le_send_mcu_frame wait CCC cmd=0x%02X\n", data[MCU_FRAME_OFF_CMD]);
return 0;
}
- mcu_cmd = data[4];
- /* 主动上报走 REPORTCMD_V2(cmd100),应答走 RUNCMD_V2(cmd110) */
- is_active_send = (mcu_cmd == MCU_CMD_REPORT) ||
- (mcu_cmd == MCU_CMD_OTA_COMPLETE) ||
- (mcu_cmd == MCU_CMD_OTA_STOP_MCU) ||
- (mcu_cmd == MCU_CMD_GET_MCU_INFO_ACK);
+ mcu_cmd = data[MCU_FRAME_OFF_CMD];
+ is_active_send = usr_le_is_gateway_report_cmd(mcu_cmd);
memset(&pkt, 0, sizeof(pkt));
memset(&hdr, 0, sizeof(hdr));
-
hdr.version = 0;
hdr.datafmt = BLEPROTO_APPDATA_DATAFMT_JSON;
hdr.encrypt = 0;
if (is_active_send)
{
- /* 设备→网关主动上报,等网关 ACK,不带 runcmd msgid */
hdr.msgtype = BLEPROTO_APPDATA_MSGTYPE_REQ;
hdr.msgid = 0;
hdr.serviceid = E_BLEPROTO_SERVICE_ID_REPORTCMD_V2;
-
- pkt.cmd = 100;
+ pkt.cmd = USR_LE_V2_CMD_REPORT;
pkt.gateway_int_cnt = 0;
}
else
{
- /* APP 下发的应答:msgid 必须对上最近一次 RUNCMD,网关才认 */
hdr.msgtype = BLEPROTO_APPDATA_MSGTYPE_RSP;
hdr.msgid = s_last_runcmd_msgid;
hdr.serviceid = E_BLEPROTO_SERVICE_ID_RUNCMD_V2;
-
- pkt.cmd = 110;
+ pkt.cmd = USR_LE_V2_CMD_RUN;
pkt.gateway_int_cnt = 2;
pkt.gateway_int[0] = 0;
pkt.gateway_int[1] = 0;
diff --git a/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/business/mcu_business.c b/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/business/mcu_business.c
index 73e05c4..3f66a13 100644
--- a/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/business/mcu_business.c
+++ b/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/business/mcu_business.c
@@ -7,6 +7,7 @@
* @history
* - V1.0.0, 2026.08.12, cyWu, 取代原 mcu_link,编排三个业务子模块
* - V1.1.0, 2026.08.13, cyWu, 编排 P1 管理/对时/版本
+ * - V1.2.0, 2026.08.15, cyWu, 编排 P2 OTA;版本周期在 OTA 时让路
******************************************************************************/
#include "system/includes.h"
@@ -19,6 +20,7 @@
#include "mcu_pairing.h"
#include "mcu_time.h"
#include "mcu_version.h"
+#include "mcu_ota.h"
#define LOG_TAG_CONST APP
#define LOG_TAG "[MCU_BIZ]"
@@ -39,6 +41,7 @@ void mcu_business_init(void)
mcu_mgmt_init();
mcu_time_init();
mcu_version_init();
+ mcu_ota_init();
usr_var.usr_goto_pair = 0;
usr_var.usr_adv_flag = 0;
@@ -54,7 +57,11 @@ void mcu_business_periodic(void)
mcu_pairing_periodic();
mcu_mgmt_periodic();
mcu_time_periodic();
- mcu_version_periodic();
+ mcu_ota_periodic();
+ /* OTA 独占 UART/BLE 时,版本拉取会抢 ble_pending,让路 */
+ if (!mcu_ota_is_active()) {
+ mcu_version_periodic();
+ }
}
/**
@@ -188,3 +195,53 @@ void mcu_business_on_gateway_ack(uint8_t success, uint8_t mcu_report_consumed)
{
mcu_version_on_gateway_ack(success, mcu_report_consumed);
}
+
+/**
+ * @brief APP OTA 帧
+ */
+void mcu_business_on_app_ota_frame(const uint8_t *frame, uint16_t len)
+{
+ mcu_ota_on_app_frame(frame, len);
+}
+
+/**
+ * @brief MCU OTA 帧
+ */
+void mcu_business_on_mcu_ota_frame(const uint8_t *frame, uint16_t len)
+{
+ mcu_ota_on_uart_frame(frame, len);
+}
+
+/**
+ * @brief 0x15
+ */
+void mcu_business_on_ota_ws_ack(void)
+{
+ mcu_ota_on_work_status_ack();
+}
+
+/**
+ * @brief BLE 断连
+ */
+void mcu_business_on_ble_disconnected(void)
+{
+ mcu_ota_on_disconnect();
+}
+
+/**
+ * @brief OTA 是否进行中
+ * @return 1/0
+ */
+uint8_t mcu_business_ota_is_active(void)
+{
+ return mcu_ota_is_active();
+}
+
+/**
+ * @brief 是否模组 OTA
+ * @return 1/0
+ */
+uint8_t mcu_business_ota_is_ble_active(void)
+{
+ return mcu_ota_is_ble_active();
+}
diff --git a/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/business/mcu_business.h b/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/business/mcu_business.h
index 4f10464..e6508e6 100644
--- a/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/business/mcu_business.h
+++ b/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/business/mcu_business.h
@@ -3,11 +3,12 @@
* @brief 业务门面:协调身份 / 配网 / 连接 / 管理 / 对时 / 版本
* @author cyWu <1917507415@qq.com>
* @date 2026.08.13
- * @version V1.1.0
+ * @version V1.2.0
* @history
* - V1.0.0, 2026.08.12, cyWu, 取代原 mcu_link,供协议层(mcu_protocol)
* 单点调用;协议层不直接依赖 identity/pairing/link_status 三个子模块
* - V1.1.0, 2026.08.13, cyWu, 接入 P1 管理/对时/版本子模块
+ * - V1.2.0, 2026.08.15, cyWu, 接入 P2 OTA;协议层只经本门面访问 OTA
******************************************************************************/
#ifndef __MCU_BUSINESS_H__
@@ -24,6 +25,7 @@
/* mcu_mgmt.c —— 出厂复位 / 重启 / 通知 MCU 重启 */
/* mcu_time.c —— 对时(写 RTC / 0x1D 读回) */
/* mcu_version.c —— UART 0x40/0x41 与 SOC 0x41 上报 */
+/* mcu_ota.c —— OTA Type 分流 / MCU 透传 / 模组 dual_bank */
/* 须在协议任务(mcu_protocol_task)单一上下文中调用。 */
/*============================================================================*/
@@ -119,4 +121,44 @@ void mcu_business_on_mcu_info_ack(const uint8_t *frame, uint16_t len);
*/
void mcu_business_on_gateway_ack(uint8_t success, uint8_t mcu_report_consumed);
+/*============================================================================*/
+/* OTA(协议层只调门面,不直接 include mcu_ota.h) */
+/*============================================================================*/
+
+/**
+ * @brief APP 下发 OTA 帧(0x30/0x32/0x35/0x36)
+ * @param frame 完整 55AA
+ * @param len 长度
+ */
+void mcu_business_on_app_ota_frame(const uint8_t *frame, uint16_t len);
+
+/**
+ * @brief MCU 上报 OTA 帧(0x31/0x33/0x34/0x37/0x38)
+ * @param frame 完整 55AA
+ * @param len 长度
+ */
+void mcu_business_on_mcu_ota_frame(const uint8_t *frame, uint16_t len);
+
+/**
+ * @brief UART 0x15 到达(BLE OTA 置 Bit6 后)
+ */
+void mcu_business_on_ota_ws_ack(void);
+
+/**
+ * @brief BLE 断连:中止 OTA 会话(不切 bank、不复位)
+ */
+void mcu_business_on_ble_disconnected(void);
+
+/**
+ * @brief 是否有 OTA 会话(心跳/0x14/版本上报据此让路)
+ * @return 1=是
+ */
+uint8_t mcu_business_ota_is_active(void);
+
+/**
+ * @brief 是否模组(BLE)OTA(据此丢弃 MCU 0x07)
+ * @return 1=是
+ */
+uint8_t mcu_business_ota_is_ble_active(void);
+
#endif /* __MCU_BUSINESS_H__ */
diff --git a/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/business/mcu_link_status.c b/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/business/mcu_link_status.c
index 2315fa6..5032b65 100644
--- a/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/business/mcu_link_status.c
+++ b/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/business/mcu_link_status.c
@@ -22,12 +22,6 @@
#define LOG_INFO_ENABLE
#include "debug.h"
-/*============================================================================*/
-/* 外部符号 */
-/*============================================================================*/
-
-extern u16 usr_get_conn_service(void);
-
/*============================================================================*/
/* 私有变量 */
/*============================================================================*/
@@ -106,7 +100,7 @@ void mcu_link_status_set_ota(uint8_t on)
*/
void mcu_link_status_periodic(void)
{
- uint8_t conn = (usr_get_conn_service() != 0) ? 1 : 0; /* 1=BLE 已连接 */
+ uint8_t conn = usr_ble_is_connected(); /* 1=BLE 已连接 */
uint8_t paired = usr_get_pair_flag(); /* 1=VM 已绑定三元组 */
uint8_t link_up;
diff --git a/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/business/mcu_ota.c b/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/business/mcu_ota.c
new file mode 100644
index 0000000..4afb90a
--- /dev/null
+++ b/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/business/mcu_ota.c
@@ -0,0 +1,942 @@
+/******************************************************************************
+ * @file mcu_ota.c
+ * @brief OTA 协议适配实现
+ * @author cyWu <1917507415@qq.com>
+ * @date 2026.08.15
+ * @version V1.0.0
+ * @history
+ * - V1.0.0, 2026.08.15, cyWu, 首次发布
+ ******************************************************************************/
+
+#include "system/includes.h"
+#include "mcu_ota.h"
+#include "mcu_cmd.h"
+#include "mcu_frame.h"
+#include "mcu_protocol.h"
+#include "mcu_link_status.h"
+#include "mcu_util.h"
+#include "mcu_version.h"
+#include "usr_ota.h"
+#include "usr_le_api.h"
+
+#define LOG_TAG_CONST APP
+#define LOG_TAG "[MCU_OTA]"
+#define LOG_ERROR_ENABLE
+#define LOG_DEBUG_ENABLE
+#define LOG_INFO_ENABLE
+#include "debug.h"
+
+/*============================================================================*/
+/* 私有宏 */
+/*============================================================================*/
+
+#define MCU_OTA_TYPE_MCU (1u) /* MCU 类型 */
+#define MCU_OTA_TYPE_BLE (2u) /* BLE 类型 */
+
+#define MCU_OTA_BLE_MAX_DATA (512u) /* BLE 最大数据 */
+#define MCU_OTA_UART_ACK_MS (2000u) /* UART ACK 时间 */
+#define MCU_OTA_IDLE_MS (30000u) /* 空闲时间 */
+#define MCU_OTA_APP_ACK_MS (1000u) /* APP ACK 时间 */
+#define MCU_OTA_WS_WAIT_MS (500u) /* Bit6 ACK 时间 */
+
+/* SOC 0x30 载荷:Type(1)+Ver(3)+Len(4)+CRC32(4) */
+#define MCU_OTA_SOC_NOTIFY_PLEN (12u) /* SOC 通知长度 */
+#define MCU_OTA_UART_NOTIFY_PLEN (11u) /* 无 Type 通知长度 */
+#define MCU_OTA_DATA_HDR_LEN (4u) /* frag(2)+total(2) 数据头长度 */
+#define MCU_OTA_DONE_UART_PLEN (4u) /* Result+Ver3 完成长度 */
+#define MCU_OTA_DONE_SOC_PLEN (5u) /* Type+Result+Ver3 完成长度 */
+
+#define MCU_OTA_PL_TYPE (0u) /* 类型 */
+#define MCU_OTA_PL_VER (1u) /* 版本 */
+#define MCU_OTA_PL_SIZE (4u) /* 大小 */
+#define MCU_OTA_PL_CRC32 (8u) /* CRC32 */
+
+#define MCU_OTA_DATA_PL_FRAG (0u) /* 分片 */
+#define MCU_OTA_DATA_PL_BODY (4u) /* 主体 */
+
+typedef enum
+{
+ MCU_OTA_ST_IDLE = 0, /* 空闲 */
+ MCU_OTA_ST_MCU_WAIT_NOTIFY, /* 已发 0x30 给 MCU,等 MCU 回 0x31 */
+ MCU_OTA_ST_MCU_WRITE, /* 可以收 APP 下一包 0x32 */
+ MCU_OTA_ST_MCU_WAIT_DATA, /* 已把 0x32 转给 MCU,等 MCU 回 0x33 */
+ MCU_OTA_ST_MCU_WAIT_APP_DONE, /* MCU 已报 0x34,等 APP 回 0x35 */
+ MCU_OTA_ST_BLE_WAIT_WS, /* BLE 等待 Bit6 ACK */
+ MCU_OTA_ST_BLE_WRITE, /* BLE 写入 */
+ MCU_OTA_ST_BLE_WAIT_FEED, /* BLE 等待喂饱 */
+ MCU_OTA_ST_BLE_WAIT_APP_DONE, /* BLE 等待 APP 完成 */
+} McuOtaState_t;
+
+/*============================================================================*/
+/* 私有变量 */
+/*============================================================================*/
+
+typedef struct {
+ uint8_t active; /* 活动 */
+ uint8_t expect; /* 期望命令 */
+ uint8_t sn; /* SN */
+ uint32_t tx_ms; /* 发送时间 */
+} McuOtaUartWait_t;
+
+typedef struct {
+ McuOtaState_t state; /* 状态 */
+ uint8_t type; /* 固件类型 */
+ uint8_t ver[3]; /* 版本 */
+ uint8_t app_sn; /* APP SN */
+ uint16_t expect_frag; /* 期望分片 */
+ uint32_t file_size; /* 文件大小 */
+ uint32_t file_crc32; /* 文件 CRC32 */
+ uint32_t last_data_ms; /* 最后一次数据时间 */
+ McuOtaUartWait_t uart_wait; /* UART ACK 等待 */
+ uint8_t ws_wait; /* Bit6 等待 */
+ uint32_t ws_tx_ms; /* 进入 WAIT_WS 时刻:最多等 500ms 就给 APP 0x31 */
+ uint8_t app_done_wait; /* APP 完成等待 */
+ uint32_t app_done_ms; /* APP 完成时间 */
+ /* 写 Flash 的回调只把结果记在这里,协议任务回头再处理、再回 APP */
+ volatile uint8_t usr_evt; /* 待处理结果(UsrOtaEvt_t) */
+ volatile uint8_t usr_evt_valid; /* 1=有待处理结果 */
+} McuOta_t;
+
+static McuOta_t s_ota;
+
+/**
+ * @brief 开始等 MCU UART ACK
+ * @param expect 期望命令
+ * @param sn SN
+ */
+static void mcu_ota_arm_uart_wait(uint8_t expect, uint8_t sn)
+{
+ s_ota.uart_wait.active = 1;
+ s_ota.uart_wait.expect = expect;
+ s_ota.uart_wait.sn = sn;
+ s_ota.uart_wait.tx_ms = mcu_util_get_ms();
+}
+
+/**
+ * @brief 清除 UART ACK 等待
+ */
+static void mcu_ota_clear_uart_wait(void)
+{
+ s_ota.uart_wait.active = 0;
+}
+
+/**
+ * @brief 是否匹配当前 UART ACK 等待
+ * @param cmd 命令
+ * @param sn 序列号
+ * @return 1=匹配
+ */
+static uint8_t mcu_ota_uart_wait_match(uint8_t cmd, uint8_t sn)
+{
+ return (s_ota.uart_wait.active &&
+ (cmd == s_ota.uart_wait.expect) &&
+ (sn == s_ota.uart_wait.sn))
+ ? 1u
+ : 0u;
+}
+
+/*============================================================================*/
+/* 私有函数 */
+/*============================================================================*/
+
+/**
+ * @brief 回 APP 一帧
+ * @param cmd 命令
+ * @param sn 序列号
+ * @param pl 载荷
+ * @param plen 长度
+ */
+static void mcu_ota_reply_app(uint8_t cmd, uint8_t sn,
+ const uint8_t *pl, uint16_t plen)
+{
+ uint8_t buf[MCU_FRAME_MAX_LEN];
+ int32_t total;
+
+ total = mcu_frame_pack(buf, sizeof(buf), cmd, sn, pl, plen);
+ if (total > 0)
+ {
+ usr_le_send_mcu_frame(buf, (uint16_t)total);
+ }
+}
+
+/**
+ * @brief 回 APP 单字节 Result
+ * @param cmd 命令
+ * @param sn SN
+ * @param result 结果
+ */
+static void mcu_ota_reply_app_result(uint8_t cmd, uint8_t sn, uint8_t result)
+{
+ mcu_ota_reply_app(cmd, sn, &result, 1);
+}
+
+/**
+ * @brief 结束会话并恢复后台 UART
+ * @param clear_bit6 1=清模组 OTA Bit6 并强制再推 0x14
+ */
+static void mcu_ota_session_end(uint8_t clear_bit6)
+{
+ if (clear_bit6)
+ {
+ mcu_link_status_set_ota(0);
+ mcu_protocol_invalidate_work_status_push();
+ }
+ memset(&s_ota, 0, sizeof(s_ota));
+ s_ota.state = MCU_OTA_ST_IDLE;
+ log_info("session end");
+}
+
+/**
+ * @brief 失败停止:MCU 发 0x36,BLE 发 0x38
+ */
+static void mcu_ota_fail_stop(uint8_t result)
+{
+ uint8_t type = s_ota.type;
+
+ log_error("fail stop result=0x%02X type=%u", result, type);
+
+ if (type == MCU_OTA_TYPE_MCU)
+ {
+ mcu_protocol_send_cmd(MCU_CMD_OTA_STOP_BLE, NULL, 0);
+ }
+ else if (type == MCU_OTA_TYPE_BLE)
+ {
+ usr_ota_abort();
+ mcu_ota_reply_app(MCU_CMD_OTA_STOP_MCU, s_ota.app_sn, NULL, 0);
+ }
+
+ mcu_ota_session_end(type == MCU_OTA_TYPE_BLE ? 1 : 0);
+}
+
+/**
+ * @brief 比较 BLE 版本:1=低于当前
+ * @param ver 三字节版本
+ * @return 1=过低
+ */
+static uint8_t mcu_ota_ble_ver_low(const uint8_t ver[3])
+{
+ uint32_t in;
+ uint32_t cur;
+
+ in = ((uint32_t)ver[0] << 16) | ((uint32_t)ver[1] << 8) | ver[2];
+ cur = ((uint32_t)MCU_BLE_FW_VER_MAJOR << 16) |
+ ((uint32_t)MCU_BLE_FW_VER_MINOR << 8) |
+ MCU_BLE_FW_VER_PATCH;
+ return (in < cur) ? 1 : 0;
+}
+
+/**
+ * @brief 推 Bit6 的 0x14
+ * @return 1=已发出
+ */
+static uint8_t mcu_ota_push_bit6(void)
+{
+ uint8_t payload[2];
+
+ if (mcu_protocol_is_ble_pending())
+ {
+ return 0;
+ }
+
+ mcu_link_status_set_ota(1);
+ payload[0] = mcu_link_status_get();
+ payload[1] = 0x00;
+ if (mcu_protocol_send_cmd_wait_ack(MCU_CMD_WORK_STATUS, payload, 2,
+ MCU_CMD_WORK_STATUS_ACK) <= 0)
+ {
+ return 0;
+ }
+ s_ota.ws_wait = 1;
+ log_info("push Bit6 work_status=0x%02X", payload[0]);
+ return 1;
+}
+
+/**
+ * @brief 回 APP BLE 0x31
+ * @param result 结果
+ */
+static void mcu_ota_reply_ble_notify(uint8_t result)
+{
+ uint8_t pl[3];
+
+ pl[0] = result;
+ mcu_util_write_u16_be(&pl[1], MCU_OTA_BLE_MAX_DATA);
+ mcu_ota_reply_app(MCU_CMD_OTA_NOTIFY_ACK, s_ota.app_sn, pl, 3);
+}
+
+/**
+ * @brief BLE 0x31 成功,进入 WRITE
+ */
+static void mcu_ota_ble_enter_write(void)
+{
+ s_ota.ws_wait = 0;
+ s_ota.state = MCU_OTA_ST_BLE_WRITE;
+ s_ota.expect_frag = 1;
+ s_ota.last_data_ms = mcu_util_get_ms();
+ mcu_ota_reply_ble_notify(MCU_RESULT_OK);
+ log_info("BLE OTA write start");
+}
+
+/**
+ * @brief 处理 APP 0x30
+ * @param frame 帧
+ * @param len 长度
+ */
+static void mcu_ota_handle_notify(const uint8_t *frame, uint16_t len)
+{
+ uint8_t type;
+ uint8_t sn;
+ uint8_t mcu_pl[MCU_OTA_UART_NOTIFY_PLEN];
+ UsrOta_Ret_t ret;
+
+ if (len < (MCU_FRAME_OVERHEAD + MCU_OTA_SOC_NOTIFY_PLEN))
+ {
+ mcu_ota_reply_app_result(MCU_CMD_OTA_NOTIFY_ACK,
+ frame[MCU_FRAME_OFF_SN],
+ MCU_RESULT_PARAM_ERR);
+ return;
+ }
+
+ sn = frame[MCU_FRAME_OFF_SN];
+ type = frame[MCU_FRAME_OFF_PAYLOAD + MCU_OTA_PL_TYPE];
+
+ if (s_ota.state != MCU_OTA_ST_IDLE)
+ {
+ mcu_ota_reply_app_result(MCU_CMD_OTA_NOTIFY_ACK, sn,
+ MCU_RESULT_OTA_STATE_ERR);
+ return;
+ }
+
+ s_ota.app_sn = sn;
+ memcpy(s_ota.ver, &frame[MCU_FRAME_OFF_PAYLOAD + MCU_OTA_PL_VER], 3);
+ s_ota.file_size = mcu_util_read_u32_be(&frame[MCU_FRAME_OFF_PAYLOAD + MCU_OTA_PL_SIZE]);
+ s_ota.file_crc32 = mcu_util_read_u32_be(&frame[MCU_FRAME_OFF_PAYLOAD + MCU_OTA_PL_CRC32]);
+
+ if (type == MCU_OTA_FW_TYPE_MCU)
+ {
+ memcpy(mcu_pl, &frame[MCU_FRAME_OFF_PAYLOAD + MCU_OTA_PL_VER],
+ MCU_OTA_UART_NOTIFY_PLEN);
+ if (mcu_protocol_send_cmd_sn(MCU_CMD_OTA_NOTIFY, sn,
+ mcu_pl, MCU_OTA_UART_NOTIFY_PLEN) < 0)
+ {
+ mcu_ota_reply_app_result(MCU_CMD_OTA_NOTIFY_ACK, sn,
+ MCU_RESULT_FAIL);
+ return;
+ }
+ s_ota.type = MCU_OTA_TYPE_MCU;
+ s_ota.state = MCU_OTA_ST_MCU_WAIT_NOTIFY;
+ mcu_ota_arm_uart_wait(MCU_CMD_OTA_NOTIFY_ACK, sn);
+ log_info("MCU OTA notify size=%u", s_ota.file_size);
+ return;
+ }
+
+ if (type != MCU_OTA_FW_TYPE_BLE)
+ {
+ mcu_ota_reply_app_result(MCU_CMD_OTA_NOTIFY_ACK, sn,
+ MCU_RESULT_PARAM_ERR);
+ return;
+ }
+
+ if (mcu_ota_ble_ver_low(s_ota.ver))
+ {
+ mcu_ota_reply_app_result(MCU_CMD_OTA_NOTIFY_ACK, sn,
+ MCU_RESULT_OTA_VER_LOW);
+ return;
+ }
+
+ ret = usr_ota_start(s_ota.file_size, s_ota.file_crc32);
+ if (ret == USR_OTA_ERR_SPACE)
+ {
+ mcu_ota_reply_ble_notify(MCU_RESULT_OTA_SPACE_ERR);
+ return;
+ }
+ if (ret != USR_OTA_OK)
+ {
+ mcu_ota_reply_ble_notify(MCU_RESULT_OTA_FAIL);
+ return;
+ }
+
+ s_ota.type = MCU_OTA_TYPE_BLE;
+ s_ota.state = MCU_OTA_ST_BLE_WAIT_WS;
+ /* 从此刻起算 500ms:0x14 没发出去时 push_bit6 不会写时间戳,避免 ws_tx_ms=0 立刻超时 */
+ s_ota.ws_tx_ms = mcu_util_get_ms();
+ if (!mcu_ota_push_bit6())
+ {
+ log_debug("Bit6 push busy, retry later");
+ }
+}
+
+/**
+ * @brief APP 0x32:MCU 透传
+ * @param frame 帧
+ * @param len 长度
+ * @param sn SN
+ * @param frag 分片序号
+ */
+static void mcu_ota_handle_data_mcu(const uint8_t *frame, uint16_t len,
+ uint8_t sn, uint16_t frag)
+{
+ if (s_ota.state != MCU_OTA_ST_MCU_WRITE)
+ {
+ mcu_ota_reply_app_result(MCU_CMD_OTA_DATA_ACK, sn,
+ MCU_RESULT_OTA_STATE_ERR);
+ return;
+ }
+ if (frag != s_ota.expect_frag)
+ {
+ log_error("MCU frag %u expect %u", frag, s_ota.expect_frag);
+ mcu_ota_reply_app_result(MCU_CMD_OTA_DATA_ACK, sn,
+ MCU_RESULT_OTA_FILE_ERR);
+ mcu_ota_fail_stop(MCU_RESULT_OTA_FILE_ERR);
+ return;
+ }
+ if (mcu_protocol_send_raw(frame, len) < 0)
+ {
+ mcu_ota_reply_app_result(MCU_CMD_OTA_DATA_ACK, sn, MCU_RESULT_FAIL);
+ mcu_ota_fail_stop(MCU_RESULT_FAIL);
+ return;
+ }
+ s_ota.state = MCU_OTA_ST_MCU_WAIT_DATA;
+ mcu_ota_arm_uart_wait(MCU_CMD_OTA_DATA_ACK, sn);
+ s_ota.last_data_ms = s_ota.uart_wait.tx_ms;
+}
+
+/**
+ * @brief APP 0x32:喂入模组 dual_bank
+ * @param frame 帧
+ * @param len 长度
+ * @param sn SN
+ * @param frag 分片序号
+ */
+static void mcu_ota_handle_data_ble(const uint8_t *frame, uint16_t len,
+ uint8_t sn, uint16_t frag)
+{
+ uint16_t data_len;
+ UsrOta_Ret_t ret;
+
+ if (s_ota.state != MCU_OTA_ST_BLE_WRITE)
+ {
+ mcu_ota_reply_app_result(MCU_CMD_OTA_DATA_ACK, sn,
+ MCU_RESULT_OTA_STATE_ERR);
+ return;
+ }
+ if (frag != s_ota.expect_frag)
+ {
+ log_error("BLE frag %u expect %u", frag, s_ota.expect_frag);
+ mcu_ota_reply_app_result(MCU_CMD_OTA_DATA_ACK, sn,
+ MCU_RESULT_OTA_FILE_ERR);
+ mcu_ota_fail_stop(MCU_RESULT_OTA_FILE_ERR);
+ return;
+ }
+
+ data_len = MCU_FRAME_PAYLOAD_LEN(len);
+ if (data_len < MCU_OTA_DATA_HDR_LEN)
+ {
+ mcu_ota_reply_app_result(MCU_CMD_OTA_DATA_ACK, sn, MCU_RESULT_PARAM_ERR);
+ return;
+ }
+ data_len = (uint16_t)(data_len - MCU_OTA_DATA_HDR_LEN);
+
+ ret = usr_ota_feed(&frame[MCU_FRAME_OFF_PAYLOAD + MCU_OTA_DATA_PL_BODY],
+ data_len);
+ s_ota.last_data_ms = mcu_util_get_ms();
+ if (ret == USR_OTA_OK)
+ {
+ s_ota.expect_frag++;
+ mcu_ota_reply_app_result(MCU_CMD_OTA_DATA_ACK, sn, MCU_RESULT_OK);
+ return;
+ }
+ if (ret == USR_OTA_FEED_PENDING)
+ {
+ s_ota.state = MCU_OTA_ST_BLE_WAIT_FEED;
+ return;
+ }
+
+ log_error("usr_ota_feed ret=%d", ret);
+ mcu_ota_reply_app_result(MCU_CMD_OTA_DATA_ACK, sn, MCU_RESULT_OTA_FAIL);
+ mcu_ota_fail_stop(MCU_RESULT_OTA_FAIL);
+}
+
+/**
+ * @brief 处理 APP 0x32
+ * @param frame 帧
+ * @param len 长度
+ */
+static void mcu_ota_handle_data(const uint8_t *frame, uint16_t len)
+{
+ uint8_t sn;
+ uint16_t frag;
+
+ if (len < (MCU_FRAME_OVERHEAD + MCU_OTA_DATA_HDR_LEN))
+ {
+ mcu_ota_reply_app_result(MCU_CMD_OTA_DATA_ACK, frame[MCU_FRAME_OFF_SN],
+ MCU_RESULT_PARAM_ERR);
+ return;
+ }
+
+ sn = frame[MCU_FRAME_OFF_SN];
+ frag = mcu_util_read_u16_be(&frame[MCU_FRAME_OFF_PAYLOAD + MCU_OTA_DATA_PL_FRAG]);
+ s_ota.app_sn = sn;
+
+ if (s_ota.type == MCU_OTA_TYPE_MCU)
+ {
+ mcu_ota_handle_data_mcu(frame, len, sn, frag);
+ return;
+ }
+ if (s_ota.type == MCU_OTA_TYPE_BLE)
+ {
+ mcu_ota_handle_data_ble(frame, len, sn, frag);
+ return;
+ }
+ mcu_ota_reply_app_result(MCU_CMD_OTA_DATA_ACK, sn, MCU_RESULT_OTA_STATE_ERR);
+}
+
+/**
+ * @brief 处理 APP 0x35
+ * @param frame 帧
+ * @param len 长度
+ */
+static void mcu_ota_handle_complete_ack(const uint8_t *frame, uint16_t len)
+{
+ uint8_t sn = frame[MCU_FRAME_OFF_SN];
+ uint16_t plen = MCU_FRAME_PAYLOAD_LEN(len);
+
+ if (s_ota.state == MCU_OTA_ST_MCU_WAIT_APP_DONE)
+ {
+ mcu_protocol_send_cmd_sn(MCU_CMD_OTA_COMPLETE_ACK, sn,
+ &frame[MCU_FRAME_OFF_PAYLOAD], plen);
+ mcu_ota_session_end(0);
+ return;
+ }
+ if (s_ota.state == MCU_OTA_ST_BLE_WAIT_APP_DONE)
+ {
+ s_ota.app_done_wait = 0;
+ usr_ota_request_reboot();
+ mcu_ota_session_end(1);
+ return;
+ }
+ log_info("ignore 0x35, state=%d", s_ota.state);
+}
+
+/**
+ * @brief 处理 APP 0x36
+ * @param sn 序列号
+ */
+static void mcu_ota_handle_stop(uint8_t sn)
+{
+ /* boot 已写入:0x36 也按成功收尾复位,禁止 abort 清 bank */
+ if (s_ota.state == MCU_OTA_ST_BLE_WAIT_APP_DONE)
+ {
+ mcu_ota_reply_app_result(MCU_CMD_OTA_STOP_BLE_ACK, sn, MCU_RESULT_OK);
+ s_ota.app_done_wait = 0;
+ usr_ota_request_reboot();
+ mcu_ota_session_end(1);
+ return;
+ }
+ if (s_ota.type == MCU_OTA_TYPE_MCU)
+ {
+ mcu_protocol_send_cmd_sn(MCU_CMD_OTA_STOP_BLE, sn, NULL, 0);
+ mcu_ota_arm_uart_wait(MCU_CMD_OTA_STOP_BLE_ACK, sn);
+ return;
+ }
+ if (s_ota.type == MCU_OTA_TYPE_BLE)
+ {
+ usr_ota_abort();
+ mcu_ota_reply_app_result(MCU_CMD_OTA_STOP_BLE_ACK, sn, MCU_RESULT_OK);
+ mcu_ota_session_end(1);
+ return;
+ }
+ mcu_ota_reply_app_result(MCU_CMD_OTA_STOP_BLE_ACK, sn, MCU_RESULT_OK);
+}
+
+/**
+ * @brief usr_ota 写 Flash 完成回调(跑在 OTA 任务,不是协议任务)
+ * @param evt 写 Flash / 校验 / 烧 boot 结果
+ * @note 这就是回调:Flash 做完会喊这一声。
+ * 但不能在这里发 BLE(上下文不对),只把结果记下来,
+ * 等协议任务 mcu_ota_handle_usr_evt() 再回 0x33/0x34。
+ */
+static void mcu_ota_on_usr_evt(UsrOtaEvt_t evt)
+{
+ s_ota.usr_evt = (uint8_t)evt;
+ s_ota.usr_evt_valid = 1;
+}
+
+/**
+ * @brief 协议任务处理 usr_ota 记下的结果,并回 APP
+ *
+ * @note 谁调用:mcu_ota_periodic() 每次先跑本函数。
+ * 事件从哪来:OTA 任务写完 Flash / 校验 / 写启动信息后,
+ * 回调 mcu_ota_on_usr_evt() 只把结果记在 usr_evt,
+ * 真正发 55AA 必须在本函数(协议任务)里做。
+ *
+ * 和 APP 的对应关系:
+ * WRITE_OK 一包 4096 已写入备用 bank → 回 0x33 成功,继续收 0x32
+ * WRITE_FAIL 这一包写入失败 → 回 0x33 失败,再发 0x38 结束
+ * VERIFY_OK 整份固件校验通过 → 不回 APP,usr_ota 接着写启动信息
+ * VERIFY_FAIL 整份固件校验失败 → 发 0x38 结束
+ * BURN_OK 启动信息已写入 → 发 0x34,等 APP 0x35 再复位
+ * BURN_FAIL 启动信息写入失败 → 发 0x38 结束
+ */
+static void mcu_ota_handle_usr_evt(void)
+{
+ UsrOtaEvt_t evt;
+
+ if (!s_ota.usr_evt_valid)
+ {
+ return;
+ }
+ evt = (UsrOtaEvt_t)s_ota.usr_evt;
+ s_ota.usr_evt_valid = 0;
+
+ switch (evt)
+ {
+ case USR_OTA_EVT_WRITE_OK:
+ /* 凑满 4096(或最后一包)时,handle_data_ble 当时没回 0x33,
+ * 卡在 BLE_WAIT_FEED 等写 Flash。现在这一包已经写进备用 bank,
+ * 回 0x33 成功,切回 BLE_WRITE 继续收下一包 0x32。
+ * 未满 4096 的包在 feed 时已经回过 0x33,不会进 WAIT_FEED。 */
+ if (s_ota.state == MCU_OTA_ST_BLE_WAIT_FEED)
+ {
+ s_ota.expect_frag++;
+ s_ota.state = MCU_OTA_ST_BLE_WRITE;
+ s_ota.last_data_ms = mcu_util_get_ms();
+ mcu_ota_reply_app_result(MCU_CMD_OTA_DATA_ACK, s_ota.app_sn,
+ MCU_RESULT_OK);
+ }
+ break;
+
+ case USR_OTA_EVT_WRITE_FAIL:
+ /* 这一包 dual_bank 写入失败:先回 0x33 失败(0x25),再发 0x38 停会话 */
+ mcu_ota_reply_app_result(MCU_CMD_OTA_DATA_ACK, s_ota.app_sn,
+ MCU_RESULT_OTA_FAIL);
+ mcu_ota_fail_stop(MCU_RESULT_OTA_FAIL);
+ break;
+
+ case USR_OTA_EVT_VERIFY_FAIL:
+ /* 整份固件已写完,但 CRC 或 dual_bank 校验失败:发 0x38,错误码 0x24 */
+ mcu_ota_fail_stop(MCU_RESULT_OTA_FILE_ERR);
+ break;
+
+ case USR_OTA_EVT_BURN_FAIL:
+ /* 固件校验已过,但启动信息写入失败,复位后仍会跑旧 bank:发 0x38 */
+ mcu_ota_fail_stop(MCU_RESULT_OTA_FILE_ERR);
+ break;
+
+ case USR_OTA_EVT_BURN_OK: {
+ uint8_t pl[5];
+
+ /* 走到这里三件事都已完成:
+ * 1) 新固件全部写入备用 bank
+ * 2) CRC / dual_bank 校验通过
+ * 3) 启动信息已写入(下次复位从新 bank 启动)
+ * 发 SOC 0x34(Type=BLE),然后等 APP 0x35;超时也会复位。 */
+ pl[0] = MCU_OTA_FW_TYPE_BLE;
+ pl[1] = MCU_RESULT_OK;
+ pl[2] = MCU_BLE_FW_VER_MAJOR;
+ pl[3] = MCU_BLE_FW_VER_MINOR;
+ pl[4] = MCU_BLE_FW_VER_PATCH;
+ s_ota.state = MCU_OTA_ST_BLE_WAIT_APP_DONE;
+ s_ota.app_done_wait = 1;
+ s_ota.app_done_ms = mcu_util_get_ms();
+ mcu_ota_reply_app(MCU_CMD_OTA_COMPLETE, s_ota.app_sn, pl, 5);
+ break;
+ }
+
+ case USR_OTA_EVT_VERIFY_OK:
+ /* 整份固件校验通过。usr_ota 任务里已经接着调 burn_boot,
+ * 成功会再来 BURN_OK 才发 0x34,这里不回 APP。 */
+ break;
+
+ default:
+ break;
+ }
+}
+
+/*============================================================================*/
+/* 公有函数 */
+/*============================================================================*/
+
+/**
+ * @brief 初始化
+ */
+void mcu_ota_init(void)
+{
+ memset(&s_ota, 0, sizeof(s_ota));
+ usr_ota_init();
+ usr_ota_set_evt_cb(mcu_ota_on_usr_evt); /* 注册:写 Flash 完成时回调过来 */
+ log_info("init ok");
+}
+
+/**
+ * @brief 周期处理
+ * @note 先看有没有写 Flash 的待处理结果,再跑超时
+ */
+void mcu_ota_periodic(void)
+{
+ uint32_t now = mcu_util_get_ms();
+
+ mcu_ota_handle_usr_evt();
+
+ if (s_ota.state == MCU_OTA_ST_IDLE)
+ {
+ return;
+ }
+
+ if (s_ota.state == MCU_OTA_ST_BLE_WAIT_WS)
+ {
+ if (!s_ota.ws_wait)
+ {
+ (void)mcu_ota_push_bit6();
+ }
+ if ((now - s_ota.ws_tx_ms) > MCU_OTA_WS_WAIT_MS)
+ {
+ log_error("Bit6 0x15 timeout, continue BLE OTA");
+ mcu_ota_ble_enter_write();
+ }
+ return;
+ }
+
+ if (s_ota.uart_wait.active &&
+ (now - s_ota.uart_wait.tx_ms) > MCU_OTA_UART_ACK_MS)
+ {
+ log_error("UART OTA ack timeout expect=0x%02X", s_ota.uart_wait.expect);
+ if (s_ota.state == MCU_OTA_ST_MCU_WAIT_DATA)
+ {
+ mcu_ota_reply_app_result(MCU_CMD_OTA_DATA_ACK, s_ota.app_sn,
+ MCU_RESULT_FAIL);
+ }
+ else if (s_ota.state == MCU_OTA_ST_MCU_WAIT_NOTIFY)
+ {
+ mcu_ota_reply_app_result(MCU_CMD_OTA_NOTIFY_ACK, s_ota.app_sn,
+ MCU_RESULT_FAIL);
+ }
+ mcu_ota_fail_stop(MCU_RESULT_FAIL);
+ return;
+ }
+
+ if (s_ota.state == MCU_OTA_ST_MCU_WAIT_APP_DONE &&
+ (now - s_ota.app_done_ms) > MCU_OTA_APP_ACK_MS)
+ {
+ log_error("APP 0x35 timeout after MCU 0x34, end session");
+ mcu_ota_session_end(0);
+ return;
+ }
+
+ if (s_ota.app_done_wait &&
+ (now - s_ota.app_done_ms) > MCU_OTA_APP_ACK_MS)
+ {
+ log_error("APP 0x35 timeout, reboot anyway");
+ s_ota.app_done_wait = 0;
+ usr_ota_request_reboot();
+ mcu_ota_session_end(1);
+ return;
+ }
+
+ if ((s_ota.state == MCU_OTA_ST_MCU_WRITE ||
+ s_ota.state == MCU_OTA_ST_BLE_WRITE) &&
+ (now - s_ota.last_data_ms) > MCU_OTA_IDLE_MS)
+ {
+ log_error("OTA data idle timeout");
+ mcu_ota_fail_stop(MCU_RESULT_FAIL);
+ }
+}
+
+/**
+ * @brief APP OTA 帧
+ * @param frame 帧
+ * @param len 长度
+ */
+void mcu_ota_on_app_frame(const uint8_t *frame, uint16_t len)
+{
+ uint8_t cmd;
+
+ if (!frame || len < MCU_FRAME_OVERHEAD)
+ {
+ return;
+ }
+ cmd = frame[MCU_FRAME_OFF_CMD];
+ log_info("APP OTA cmd=0x%02X len=%u", cmd, len);
+
+ switch (cmd)
+ {
+ case MCU_CMD_OTA_NOTIFY:
+ mcu_ota_handle_notify(frame, len);
+ break;
+ case MCU_CMD_OTA_DATA:
+ mcu_ota_handle_data(frame, len);
+ break;
+ case MCU_CMD_OTA_COMPLETE_ACK:
+ mcu_ota_handle_complete_ack(frame, len);
+ break;
+ case MCU_CMD_OTA_STOP_BLE:
+ mcu_ota_handle_stop(frame[MCU_FRAME_OFF_SN]);
+ break;
+ default:
+ log_error("unexpected APP OTA cmd=0x%02X", cmd);
+ break;
+ }
+}
+
+/**
+ * @brief MCU OTA 帧
+ * @param frame 帧
+ * @param len 长度
+ */
+void mcu_ota_on_uart_frame(const uint8_t *frame, uint16_t len)
+{
+ uint8_t cmd;
+ uint8_t sn;
+ uint8_t result;
+
+ if (!frame || len < MCU_FRAME_OVERHEAD)
+ {
+ return;
+ }
+ cmd = frame[MCU_FRAME_OFF_CMD];
+ sn = frame[MCU_FRAME_OFF_SN];
+ log_info("MCU OTA cmd=0x%02X sn=%u", cmd, sn);
+
+ if (mcu_ota_uart_wait_match(cmd, sn))
+ {
+ mcu_ota_clear_uart_wait();
+ }
+
+ switch (cmd)
+ {
+ case MCU_CMD_OTA_NOTIFY_ACK:
+ usr_le_send_mcu_frame((u8 *)frame, len);
+ result = (MCU_FRAME_PAYLOAD_LEN(len) > 0) ? frame[MCU_FRAME_OFF_PAYLOAD] : MCU_RESULT_FAIL;
+ if (result == MCU_RESULT_OK)
+ {
+ s_ota.state = MCU_OTA_ST_MCU_WRITE;
+ s_ota.expect_frag = 1;
+ s_ota.last_data_ms = mcu_util_get_ms();
+ }
+ else
+ {
+ mcu_ota_session_end(0);
+ }
+ break;
+ case MCU_CMD_OTA_DATA_ACK:
+ usr_le_send_mcu_frame((u8 *)frame, len);
+ result = (MCU_FRAME_PAYLOAD_LEN(len) > 0) ? frame[MCU_FRAME_OFF_PAYLOAD] : MCU_RESULT_FAIL;
+ if (result == MCU_RESULT_OK)
+ {
+ s_ota.expect_frag++;
+ s_ota.state = MCU_OTA_ST_MCU_WRITE;
+ s_ota.last_data_ms = mcu_util_get_ms();
+ }
+ else
+ {
+ mcu_ota_fail_stop(result);
+ }
+ break;
+ case MCU_CMD_OTA_COMPLETE: {
+ uint8_t soc[MCU_FRAME_MAX_LEN];
+ uint8_t pl[MCU_OTA_DONE_SOC_PLEN];
+ int32_t total;
+
+ /* MCU: Result+Ver3 → SOC: Type+Result+Ver3 */
+ pl[0] = MCU_OTA_FW_TYPE_MCU;
+ if (MCU_FRAME_PAYLOAD_LEN(len) >= MCU_OTA_DONE_UART_PLEN)
+ {
+ memcpy(&pl[1], &frame[MCU_FRAME_OFF_PAYLOAD], MCU_OTA_DONE_UART_PLEN);
+ }
+ else
+ {
+ pl[1] = MCU_RESULT_FAIL;
+ memset(&pl[2], 0, 3);
+ }
+ total = mcu_frame_pack(soc, sizeof(soc), MCU_CMD_OTA_COMPLETE,
+ sn, pl, MCU_OTA_DONE_SOC_PLEN);
+ if (total > 0)
+ {
+ usr_le_send_mcu_frame(soc, (uint16_t)total);
+ }
+ s_ota.state = MCU_OTA_ST_MCU_WAIT_APP_DONE;
+ s_ota.app_done_ms = mcu_util_get_ms();
+ break;
+ }
+ case MCU_CMD_OTA_STOP_BLE_ACK:
+ usr_le_send_mcu_frame((u8 *)frame, len);
+ mcu_ota_session_end(0);
+ break;
+ case MCU_CMD_OTA_STOP_MCU:
+ usr_le_send_mcu_frame((u8 *)frame, len);
+ if (s_ota.type == MCU_OTA_TYPE_MCU)
+ {
+ mcu_ota_session_end(0);
+ }
+ break;
+ default:
+ log_error("unexpected MCU OTA cmd=0x%02X", cmd);
+ break;
+ }
+}
+
+/**
+ * @brief 0x15
+ */
+void mcu_ota_on_work_status_ack(void)
+{
+ if (s_ota.state == MCU_OTA_ST_BLE_WAIT_WS && s_ota.ws_wait)
+ {
+ log_info("Bit6 ack");
+ mcu_ota_ble_enter_write();
+ }
+}
+
+/**
+ * @brief 断连中止
+ */
+void mcu_ota_on_disconnect(void)
+{
+ uint8_t type = s_ota.type;
+
+ if (s_ota.state == MCU_OTA_ST_IDLE)
+ {
+ return;
+ }
+ log_info("disconnect abort type=%u state=%d", type, s_ota.state);
+
+ /* boot 已写入:断连也走复位,禁止 abort 清 bank */
+ if (s_ota.state == MCU_OTA_ST_BLE_WAIT_APP_DONE)
+ {
+ s_ota.app_done_wait = 0;
+ usr_ota_request_reboot();
+ mcu_ota_session_end(1);
+ return;
+ }
+
+ if (type == MCU_OTA_TYPE_MCU)
+ {
+ mcu_protocol_send_cmd(MCU_CMD_OTA_STOP_BLE, NULL, 0);
+ }
+ else if (type == MCU_OTA_TYPE_BLE)
+ {
+ usr_ota_abort();
+ }
+ mcu_ota_session_end(type == MCU_OTA_TYPE_BLE ? 1 : 0);
+}
+
+/**
+ * @brief 是否有会话
+ * @return 1/0
+ */
+uint8_t mcu_ota_is_active(void)
+{
+ return (s_ota.state != MCU_OTA_ST_IDLE) ? 1 : 0;
+}
+
+/**
+ * @brief 是否 BLE OTA
+ * @return 1/0
+ */
+uint8_t mcu_ota_is_ble_active(void)
+{
+ return (s_ota.type == MCU_OTA_TYPE_BLE &&
+ s_ota.state != MCU_OTA_ST_IDLE)
+ ? 1
+ : 0;
+}
diff --git a/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/business/mcu_ota.h b/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/business/mcu_ota.h
new file mode 100644
index 0000000..da3731c
--- /dev/null
+++ b/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/business/mcu_ota.h
@@ -0,0 +1,62 @@
+/******************************************************************************
+ * @file mcu_ota.h
+ * @brief OTA 协议适配:Type 分流、MCU 透传、模组 dual_bank 衔接
+ * @author cyWu <1917507415@qq.com>
+ * @date 2026.08.15
+ * @version V1.0.0
+ * @history
+ * - V1.0.0, 2026.08.15, cyWu, 首次发布
+ ******************************************************************************/
+
+#ifndef __MCU_OTA_H__
+#define __MCU_OTA_H__
+
+#include
+
+/**
+ * @brief 初始化 OTA 业务(含 usr_ota 引擎)
+ */
+void mcu_ota_init(void);
+
+/**
+ * @brief 周期:UART/空闲超时、0x14 重试、usr_ota 事件、0x35 超时复位
+ */
+void mcu_ota_periodic(void);
+
+/**
+ * @brief 处理 APP 下发的 OTA 帧(0x30/0x32/0x35/0x36)
+ * @param frame 完整 55AA
+ * @param len 长度
+ */
+void mcu_ota_on_app_frame(const uint8_t *frame, uint16_t len);
+
+/**
+ * @brief 处理 MCU 上报的 OTA 帧(0x31/0x33/0x34/0x37/0x38)
+ * @param frame 完整 55AA
+ * @param len 长度
+ */
+void mcu_ota_on_uart_frame(const uint8_t *frame, uint16_t len);
+
+/**
+ * @brief 0x15 到达(BLE OTA 置 Bit6 后)
+ */
+void mcu_ota_on_work_status_ack(void);
+
+/**
+ * @brief BLE 断连:中止会话,不切 bank、不复位
+ */
+void mcu_ota_on_disconnect(void);
+
+/**
+ * @brief 是否有 OTA 会话
+ * @return 1=是
+ */
+uint8_t mcu_ota_is_active(void);
+
+/**
+ * @brief 是否模组(BLE)OTA
+ * @return 1=是
+ */
+uint8_t mcu_ota_is_ble_active(void);
+
+#endif /* __MCU_OTA_H__ */
diff --git a/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/business/mcu_version.c b/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/business/mcu_version.c
index 37a45ab..8b362fb 100644
--- a/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/business/mcu_version.c
+++ b/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/business/mcu_version.c
@@ -23,12 +23,6 @@
#define LOG_INFO_ENABLE
#include "debug.h"
-/*============================================================================*/
-/* 外部符号 */
-/*============================================================================*/
-
-extern u16 usr_get_conn_service(void);
-
/*============================================================================*/
/* 私有宏 */
/*============================================================================*/
@@ -188,7 +182,7 @@ void mcu_version_init(void)
*/
void mcu_version_periodic(void)
{
- uint8_t conn = (usr_get_conn_service() != 0) ? 1 : 0; /* 1=BLE 已连接 */
+ uint8_t conn = usr_ble_is_connected(); /* 1=BLE 已连接 */
uint8_t paired = usr_get_pair_flag(); /* 1=已配网/已绑定 */
uint32_t now = mcu_util_get_ms();
diff --git a/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/mcu_bridge.c b/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/mcu_bridge.c
index 18c5e06..065eb5f 100644
--- a/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/mcu_bridge.c
+++ b/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/mcu_bridge.c
@@ -93,3 +93,23 @@ void mcu_bridge_on_network_time(uint64_t unix_s, int32_t gmtoff_s)
{
mcu_protocol_notify_network_time(unix_s, gmtoff_s);
}
+
+/**
+ * @brief BLE 断连:仅在 OTA 进行中入队,避免 adv 周期空转塞队列
+ */
+void mcu_bridge_on_ble_disconnected(void)
+{
+ if (!mcu_business_ota_is_active()) {
+ return;
+ }
+ mcu_protocol_notify_disconnected();
+}
+
+/**
+ * @brief 是否有 OTA 会话
+ * @return 1/0
+ */
+uint8_t mcu_bridge_ota_is_active(void)
+{
+ return mcu_business_ota_is_active();
+}
diff --git a/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/mcu_bridge.h b/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/mcu_bridge.h
index af7793c..ea55b30 100644
--- a/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/mcu_bridge.h
+++ b/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/mcu_bridge.h
@@ -12,6 +12,7 @@
* 本文件不再包含任何实现逻辑,只做“组合根”,对外 API 保持不变,
* 外部调用方(app_main.c / usr_le_adv.c / usr_le_recieve.c 等)无需改动
* - V2.1.0, 2026.08.13, cyWu, P1 增加网络时间入口;业务子模块扩到管理/对时/版本
+ * - V2.2.0, 2026.08.15, cyWu, P2 OTA 入口走本门面,usr_le_code 不再 include mcu_ota.h
******************************************************************************/
#ifndef __MCU_BRIDGE_H__
@@ -38,11 +39,16 @@
/* ├── mcu_mgmt.c/h 模组管理(0x16/0x18/0x1A) */
/* ├── mcu_time.c/h 对时(0x1C/0x1D,写入 usr_rtc) */
/* ├── mcu_version.c/h MCU 信息 + SOC 0x41 上报 */
+/* ├── mcu_ota.c/h P2 OTA:Type 分流 / MCU 透传 / Bit6 */
/* └── mcu_business.c/h 业务门面:协调子模块,供协议层调用 */
/* */
/* 依赖方向(单向,禁止反向 include): */
-/* mcu_bridge → protocol → business(子模块) → business(门面) */
-/* business 子模块只经 mcu_protocol_send_cmd 等窄接口回调协议层发送 */
+/* usr_le_code / app_main → mcu_bridge.h */
+/* mcu_bridge → mcu_protocol + mcu_business(门面) */
+/* mcu_protocol → mcu_business.h(不 include 业务子模块) */
+/* mcu_business 门面 → identity/pairing/link/mgmt/time/version/ota */
+/* 业务子模块互不 include;只经 mcu_protocol_send_* 回调协议层发送 */
+/* mcu_ota → usr_ota(协议无关 dual_bank 引擎,不感知 55AA) */
/*============================================================================*/
/*============================================================================*/
@@ -122,4 +128,16 @@ void mcu_bridge_on_gateway_report_ack(uint16_t cmd,
*/
void mcu_bridge_on_network_time(uint64_t unix_s, int32_t gmtoff_s);
+/**
+ * @brief BLE 断连:入队后中止 OTA(不切 bank、不复位;烧 boot 成功后仍复位)
+ * @note 可从 adv 周期调用;无会话时立即返回,不会塞满事件队列
+ */
+void mcu_bridge_on_ble_disconnected(void);
+
+/**
+ * @brief 是否有 OTA 会话
+ * @return 1=是
+ */
+uint8_t mcu_bridge_ota_is_active(void);
+
#endif /* __MCU_BRIDGE_H__ */
diff --git a/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/protocol/mcu_cmd.h b/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/protocol/mcu_cmd.h
index 0e05f7a..181a3d3 100644
--- a/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/protocol/mcu_cmd.h
+++ b/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/protocol/mcu_cmd.h
@@ -80,7 +80,7 @@ typedef enum {
MCU_CMD_AUDIO = 0x26,
MCU_CMD_AUDIO_ACK = 0x27,
- /* OTA(后续扩展) */
+ /* OTA */
MCU_CMD_OTA_NOTIFY = 0x30,
MCU_CMD_OTA_NOTIFY_ACK = 0x31,
MCU_CMD_OTA_DATA = 0x32,
@@ -97,25 +97,32 @@ typedef enum {
MCU_CMD_GET_MCU_INFO_ACK = 0x41
} McuCmd_t;
+/*============================================================================*/
+/* OTA 固件类型(SOC 0x30/0x34 的 Type 字节;UART 侧 0x30 无此字段) */
+/*============================================================================*/
+
+#define MCU_OTA_FW_TYPE_MCU (0x01u)
+#define MCU_OTA_FW_TYPE_BLE (0x02u)
+
/*============================================================================*/
/* 结果码 */
/*============================================================================*/
typedef enum {
- MCU_RESULT_OK = 0x00,
- MCU_RESULT_PARAM_ERR = 0x01,
- MCU_RESULT_BUSY = 0x02,
- MCU_RESULT_NOT_SUPPORT = 0x03,
- MCU_RESULT_CRC_ERR = 0x04,
- MCU_RESULT_FAIL = 0x05,
+ MCU_RESULT_OK = 0x00, /**< 成功 */
+ MCU_RESULT_PARAM_ERR = 0x01, /**< 参数错误 */
+ MCU_RESULT_BUSY = 0x02, /**< 忙 */
+ MCU_RESULT_NOT_SUPPORT = 0x03, /**< 不支持 */
+ MCU_RESULT_CRC_ERR = 0x04, /**< 校验错误 */
+ MCU_RESULT_FAIL = 0x05, /**< 失败 */
/* OTA 专用 */
- MCU_RESULT_OTA_NO_NEED = 0x20,
- MCU_RESULT_OTA_VER_LOW = 0x21,
- MCU_RESULT_OTA_SPACE_ERR = 0x22,
- MCU_RESULT_OTA_STATE_ERR = 0x23,
- MCU_RESULT_OTA_FILE_ERR = 0x24,
- MCU_RESULT_OTA_FAIL = 0x25
+ MCU_RESULT_OTA_NO_NEED = 0x20, /**< 不需要 */
+ MCU_RESULT_OTA_VER_LOW = 0x21, /**< 版本过低 */
+ MCU_RESULT_OTA_SPACE_ERR = 0x22, /**< 空间不足 */
+ MCU_RESULT_OTA_STATE_ERR = 0x23, /**< 状态错误 */
+ MCU_RESULT_OTA_FILE_ERR = 0x24, /**< 文件错误 */
+ MCU_RESULT_OTA_FAIL = 0x25, /**< 失败 */
} McuResult_t;
#endif /* __MCU_CMD_H__ */
diff --git a/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/protocol/mcu_frame.c b/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/protocol/mcu_frame.c
index cc1f171..63ae026 100644
--- a/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/protocol/mcu_frame.c
+++ b/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/protocol/mcu_frame.c
@@ -138,3 +138,37 @@ int8_t mcu_frame_feed(uint8_t byte, uint8_t *out, uint16_t *out_len)
}
return 0;
}
+
+/**
+ * @brief 校验一帧完整 55AA(非流式)
+ * @param data 完整帧
+ * @param len 实际长度
+ * @param max_len 允许的最大帧长
+ * @return 1=合法,0=非法
+ */
+uint8_t mcu_frame_check(const uint8_t *data, uint16_t len, uint16_t max_len)
+{
+ uint16_t len_field;
+ uint16_t total;
+
+ if (!data || len < MCU_FRAME_OVERHEAD || max_len < MCU_FRAME_OVERHEAD) {
+ return 0;
+ }
+ if (data[0] != MCU_PKT_HEAD_0 || data[1] != MCU_PKT_HEAD_1) {
+ return 0;
+ }
+
+ len_field = mcu_util_read_u16_be(&data[2]);
+ /* LEN = CMD + SN + payload + CKSUM,至少 3 */
+ if (len_field < 3u) {
+ return 0;
+ }
+ total = (uint16_t)(len_field + 4u);
+ if (total != len || total > max_len) {
+ return 0;
+ }
+ if (mcu_util_checksum(data, total) != data[total - 1u]) {
+ return 0;
+ }
+ return 1;
+}
diff --git a/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/protocol/mcu_frame.h b/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/protocol/mcu_frame.h
index 09139d0..565cae6 100644
--- a/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/protocol/mcu_frame.h
+++ b/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/protocol/mcu_frame.h
@@ -6,6 +6,7 @@
* @version V1.0.0
* @history
* - V1.0.0, 2026.08.06, cyWu, 首次发布
+ * - V1.1.0, 2026.08.15, cyWu, 增加整包校验 mcu_frame_check,供 APP/BLE 完整帧使用
******************************************************************************/
#ifndef __MCU_FRAME_H__
@@ -21,6 +22,17 @@
#define MCU_PKT_HEAD_0 (0x55)
#define MCU_PKT_HEAD_1 (0xAA)
#define MCU_FRAME_MAX_LEN (128)
+#define MCU_OTA_FRAME_MAX_LEN (640) /**< 仅 OTA 0x32:512 载荷 + 帧头约 523 */
+
+#define MCU_FRAME_OFF_CMD (4) /**< 命令字 */
+#define MCU_FRAME_OFF_SN (5) /**< 序列号 */
+#define MCU_FRAME_OFF_PAYLOAD (6) /**< 载荷起始 */
+#define MCU_FRAME_OVERHEAD (7) /**< 55AA + Len2 + Cmd + Sn + Cks,无载荷时的最短帧 */
+
+/** 由整帧长度得到载荷长度;帧不完整则返回 0 */
+#define MCU_FRAME_PAYLOAD_LEN(total_len) \
+ (((total_len) >= MCU_FRAME_OVERHEAD) ? \
+ (uint16_t)((total_len) - MCU_FRAME_OVERHEAD) : 0u)
/*============================================================================*/
/* 外部接口 */
@@ -49,6 +61,17 @@ int32_t mcu_frame_pack(uint8_t *out, uint16_t out_max,
*/
int8_t mcu_frame_feed(uint8_t byte, uint8_t *out, uint16_t *out_len);
+/**
+ * @brief 校验一帧完整 55AA(非流式)
+ * @param data 完整帧
+ * @param len 实际长度
+ * @param max_len 允许的最大帧长(普通帧 128,OTA 0x32 为 640)
+ * @return 1=合法(头/LEN 与 len 一致/校验和),0=非法
+ * @note UART 是字节流用 mcu_frame_feed;APP/BLE 已是整包,用本函数,
+ * 不要喂给 feed,以免和 UART 解析状态机抢同一份静态缓冲
+ */
+uint8_t mcu_frame_check(const uint8_t *data, uint16_t len, uint16_t max_len);
+
/**
* @brief 复位帧解析状态机
*/
diff --git a/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/protocol/mcu_protocol.c b/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/protocol/mcu_protocol.c
index 1e4a89b..db56380 100644
--- a/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/protocol/mcu_protocol.c
+++ b/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/protocol/mcu_protocol.c
@@ -62,6 +62,13 @@ typedef struct
uint16_t len;
} McuPending_t;
+typedef struct
+{
+ uint8_t buf[MCU_OTA_FRAME_MAX_LEN];
+ uint16_t len;
+ uint8_t busy;
+} McuOtaBypass_t;
+
typedef enum
{
MCU_EVT_UART_FRAME = 0,
@@ -69,7 +76,9 @@ typedef enum
MCU_EVT_GATEWAY_ACK,
MCU_EVT_BLE_PAIRED,
MCU_EVT_BLE_PAIR_CLEARED,
- MCU_EVT_NET_TIME
+ MCU_EVT_NET_TIME,
+ MCU_EVT_APP_OTA_DATA,
+ MCU_EVT_BLE_DISCONNECTED
} McuEvtType_t;
typedef struct
@@ -98,6 +107,10 @@ typedef struct
McuPending_t ble_pending; /* 模组主动 UART(心跳/0x14/0x40/0x1A),一次只能占一个 */
uint8_t report_waiting; /* 1=MCU 0x07 已转网关,等 ACK 后再回 0x08 */
uint8_t report_sn; /* 等待回 0x08 的那条 0x07 的 SN */
+ uint8_t parse_out[MCU_FRAME_MAX_LEN]; /* UART 组帧临时输出 */
+ McuOtaBypass_t ota_rx; /* APP 0x32 旁路,不进 128 事件槽 */
+ volatile uint8_t disc_posted; /* 断连事件已在队列中,避免 adv 周期重复入队 */
+ volatile uint8_t lp_busy; /* 协议任务忙,禁止 sleep */
} McuProtocol_t;
typedef struct
@@ -115,8 +128,6 @@ typedef struct
/*============================================================================*/
static McuProtocol_t s_proto;
-static uint8_t s_parse_out[MCU_FRAME_MAX_LEN];
-static volatile u8 s_protocol_busy = 0;
static McuEvtQueue_t s_evt_q;
/*============================================================================*/
@@ -226,6 +237,14 @@ static void mcu_protocol_dispatch_event(const McuEvt_t *evt)
case MCU_EVT_NET_TIME:
mcu_business_on_network_time(evt->unix_s, evt->gmtoff_s);
break;
+ case MCU_EVT_APP_OTA_DATA:
+ mcu_protocol_handle_app_frame(s_proto.ota_rx.buf, s_proto.ota_rx.len);
+ s_proto.ota_rx.busy = 0;
+ break;
+ case MCU_EVT_BLE_DISCONNECTED:
+ s_proto.disc_posted = 0;
+ mcu_business_on_ble_disconnected();
+ break;
default:
log_error("unknown evt type=%d", evt->type);
break;
@@ -247,13 +266,13 @@ static void mcu_protocol_task(void *arg)
{
os_sem_pend(&s_evt_q.sem, (int)MCU_PROTOCOL_TICK_OS);
- s_protocol_busy = 1;
+ s_proto.lp_busy = 1;
while (mcu_protocol_pop_event(&evt))
{
mcu_protocol_dispatch_event(&evt);
}
mcu_protocol_periodic();
- s_protocol_busy = 0;
+ s_proto.lp_busy = 0;
}
}
@@ -263,7 +282,7 @@ static void mcu_protocol_task(void *arg)
*/
static u8 mcu_protocol_lp_idle_query(void)
{
- return s_protocol_busy ? 0 : 1;
+ return s_proto.lp_busy ? 0 : 1;
}
REGISTER_LP_TARGET(mcu_protocol_lp_target) = {
@@ -323,6 +342,37 @@ int32_t mcu_protocol_send_cmd(uint8_t cmd, const uint8_t *payload, uint16_t plen
return mcu_protocol_send_frame(cmd, mcu_protocol_next_sn(), payload, plen);
}
+/**
+ * @brief 指定 SN 发送
+ * @param cmd 命令
+ * @param sn 序列号
+ * @param payload 载荷
+ * @param plen 长度
+ * @return 帧长;<0 失败
+ */
+int32_t mcu_protocol_send_cmd_sn(uint8_t cmd, uint8_t sn,
+ const uint8_t *payload, uint16_t plen)
+{
+ return mcu_protocol_send_frame(cmd, sn, payload, plen);
+}
+
+/**
+ * @brief 原样转发完整帧
+ * @param data 帧
+ * @param len 长度
+ * @return 帧长;<0 失败
+ */
+int32_t mcu_protocol_send_raw(const uint8_t *data, uint16_t len)
+{
+ if (!data || len == 0) {
+ return -1;
+ }
+ if (mcu_uart_send(data, len) != MCU_UART_OK) {
+ return -1;
+ }
+ return (int32_t)len;
+}
+
/**
* @brief 发送并占用 ble_pending
* @param cmd 命令
@@ -387,6 +437,23 @@ static void mcu_protocol_send_ack(uint8_t cmd, uint8_t sn, uint8_t result)
mcu_protocol_send_frame(cmd, sn, &result, 1);
}
+/**
+ * @brief 组一小帧回 APP(OTA 独占时拒 0x03/0x05)
+ * @param cmd ACK 命令
+ * @param sn SN
+ * @param result 结果
+ */
+static void mcu_protocol_reply_app_result(uint8_t cmd, uint8_t sn, uint8_t result)
+{
+ uint8_t buf[MCU_FRAME_MAX_LEN];
+ int32_t total;
+
+ total = mcu_frame_pack(buf, sizeof(buf), cmd, sn, &result, 1);
+ if (total > 0) {
+ usr_le_send_mcu_frame(buf, (uint16_t)total);
+ }
+}
+
/**
* @brief 强制重推工作状态
*/
@@ -410,13 +477,13 @@ static void mcu_protocol_handle_pair_cfg(const uint8_t *frame, uint16_t len)
uint8_t mode;
uint16_t timeout_s;
- if (len < 10)
+ if (len < (MCU_FRAME_OVERHEAD + 3u))
{
return;
}
- sn = frame[5];
- mode = frame[6];
- timeout_s = mcu_util_read_u16_be(&frame[7]);
+ sn = frame[MCU_FRAME_OFF_SN];
+ mode = frame[MCU_FRAME_OFF_PAYLOAD];
+ timeout_s = mcu_util_read_u16_be(&frame[MCU_FRAME_OFF_PAYLOAD + 1]);
/* 协议只认 mode=1/2,其它直接回参数错误 */
if (mode != 1 && mode != 2)
@@ -451,7 +518,7 @@ static void mcu_protocol_handle_get_time(const uint8_t *frame, uint16_t len)
{
return;
}
- sn = frame[5];
+ sn = frame[MCU_FRAME_OFF_SN];
mcu_business_build_time_ack(payload, &plen);
if (plen == 0)
{
@@ -470,7 +537,7 @@ static void mcu_protocol_handle_get_time(const uint8_t *frame, uint16_t len)
*/
static void mcu_protocol_handle_report(const uint8_t *frame, uint16_t len)
{
- uint8_t sn = frame[5];
+ uint8_t sn = frame[MCU_FRAME_OFF_SN];
/* 已配网也会在 CCC 之前连上;这时发出去会失败,不置 waiting,让 MCU 自己重发 0x07 */
if (!usr_ble_is_notify_ready())
@@ -499,7 +566,7 @@ static void mcu_protocol_handle_frame(const uint8_t *frame, uint16_t len)
uint8_t cmd;
uint8_t sn;
- if (!frame || len < 6)
+ if (!frame || len < MCU_FRAME_OVERHEAD)
{
return;
}
@@ -510,8 +577,8 @@ static void mcu_protocol_handle_frame(const uint8_t *frame, uint16_t len)
s_proto.hb_waiting = 0;
s_proto.mcu_comm_alarm = 0;
- cmd = frame[4];
- sn = frame[5];
+ cmd = frame[MCU_FRAME_OFF_CMD];
+ sn = frame[MCU_FRAME_OFF_SN];
log_debug("MCU RX cmd=0x%02X sn=%u len=%u", cmd, sn, len);
/* APP 透传在等这条 ACK(命令+SN 都对上):转给 APP,不再往下分发 */
@@ -547,6 +614,10 @@ static void mcu_protocol_handle_frame(const uint8_t *frame, uint16_t len)
mcu_protocol_handle_pair_cfg(frame, len);
break;
case MCU_CMD_REPORT:
+ if (mcu_business_ota_is_ble_active()) {
+ log_info("drop MCU 0x07 during BLE OTA");
+ return;
+ }
mcu_protocol_handle_report(frame, len); /* 转网关,等 ACK 再回 0x08 */
break;
case MCU_CMD_CONTROL_ACK:
@@ -572,14 +643,30 @@ static void mcu_protocol_handle_frame(const uint8_t *frame, uint16_t len)
mcu_business_on_mcu_reboot_ack(frame, len);
break;
case MCU_CMD_HEARTBEAT_ACK:
+ break;
case MCU_CMD_WORK_STATUS_ACK:
- break; /* pending 已在上面释放,无需再处理载荷 */
+ mcu_business_on_ota_ws_ack();
+ break;
+ case MCU_CMD_OTA_NOTIFY_ACK:
+ case MCU_CMD_OTA_DATA_ACK:
+ case MCU_CMD_OTA_COMPLETE:
+ case MCU_CMD_OTA_STOP_BLE_ACK:
+ case MCU_CMD_OTA_STOP_MCU:
+ mcu_business_on_mcu_ota_frame(frame, len);
+ break;
+ case MCU_CMD_FACTORY:
+ case MCU_CMD_PHOTO:
+ case MCU_CMD_RECORD:
+ case MCU_CMD_AUDIO:
+ if (mcu_business_ota_is_active()) {
+ log_info("drop MCU cmd=0x%02X during OTA", cmd);
+ break;
+ }
+ mcu_protocol_send_ack((uint8_t)(cmd + 1), sn, MCU_RESULT_NOT_SUPPORT);
+ break;
default:
- /* 工厂/拍照/录音/音频:协议有命令字但模组未实现,回不支持 */
- if (cmd == MCU_CMD_FACTORY || cmd == MCU_CMD_PHOTO ||
- cmd == MCU_CMD_RECORD || cmd == MCU_CMD_AUDIO)
- {
- mcu_protocol_send_ack((uint8_t)(cmd + 1), sn, MCU_RESULT_NOT_SUPPORT);
+ if (mcu_business_ota_is_active()) {
+ log_info("drop MCU cmd=0x%02X during OTA", cmd);
}
break;
}
@@ -596,26 +683,55 @@ static void mcu_protocol_handle_app_frame(const uint8_t *data, uint16_t len)
uint8_t sn;
uint8_t expect_ack;
- cmd = data[4];
- sn = data[5];
+ cmd = data[MCU_FRAME_OFF_CMD];
+ sn = data[MCU_FRAME_OFF_SN];
- /* 0x03/0x05 必须等 MCU ACK 再回 APP,所以进 pending 可超时重发。
- * 其它 APP 帧直接透传到 UART,模组不管应答。 */
- if (cmd == MCU_CMD_CONTROL)
+ switch (cmd)
{
+ case MCU_CMD_OTA_NOTIFY:
+ case MCU_CMD_OTA_DATA:
+ case MCU_CMD_OTA_COMPLETE_ACK:
+ case MCU_CMD_OTA_STOP_BLE:
+ mcu_business_on_app_ota_frame(data, len);
+ return;
+
+ /* 反向 OTA 命令:模组→APP,或会话已结束后的 0x39,禁止透传到 UART */
+ case MCU_CMD_OTA_NOTIFY_ACK:
+ case MCU_CMD_OTA_DATA_ACK:
+ case MCU_CMD_OTA_COMPLETE:
+ case MCU_CMD_OTA_STOP_BLE_ACK:
+ case MCU_CMD_OTA_STOP_MCU:
+ case MCU_CMD_OTA_STOP_MCU_ACK:
+ log_info("ignore APP OTA dir cmd=0x%02X", cmd);
+ return;
+
+ case MCU_CMD_CONTROL:
+ if (mcu_business_ota_is_active()) {
+ mcu_protocol_reply_app_result(MCU_CMD_CONTROL_ACK, sn, MCU_RESULT_BUSY);
+ return;
+ }
expect_ack = MCU_CMD_CONTROL_ACK;
- }
- else if (cmd == MCU_CMD_READ)
- {
+ break;
+
+ case MCU_CMD_READ:
+ if (mcu_business_ota_is_active()) {
+ mcu_protocol_reply_app_result(MCU_CMD_READ_ACK, sn, MCU_RESULT_BUSY);
+ return;
+ }
expect_ack = MCU_CMD_READ_ACK;
- }
- else
- {
+ break;
+
+ default:
+ if (mcu_business_ota_is_active()) {
+ log_info("drop APP cmd=0x%02X during OTA", cmd);
+ return;
+ }
+ /* 其它 APP 帧直接透传到 UART,模组不管应答 */
mcu_uart_send(data, len);
return;
}
- /* 上一条 0x03/0x05 还没回,丢掉新的,避免 SN 对乱 */
+ /* 0x03/0x05:等 MCU ACK 再回 APP,进 pending 可超时重发 */
if (s_proto.app_pending.active)
{
log_error("app frame busy, drop cmd=0x%02X", cmd);
@@ -686,7 +802,7 @@ static void mcu_protocol_try_heartbeat(void)
int32_t total;
/* 身份都还没通,先别发心跳 */
- if (!mcu_business_is_identity_ready())
+ if (!mcu_business_is_identity_ready() || mcu_business_ota_is_active())
{
return;
}
@@ -756,7 +872,7 @@ static void mcu_protocol_try_work_status(void)
uint32_t now;
uint8_t ws;
- if (!mcu_business_is_identity_ready())
+ if (!mcu_business_is_identity_ready() || mcu_business_ota_is_active())
{
return;
}
@@ -942,12 +1058,12 @@ void mcu_protocol_on_uart_rx(const uint8_t *data, uint16_t len)
for (i = 0; i < len; i++)
{
- ret = mcu_frame_feed(data[i], s_parse_out, &out_len);
+ ret = mcu_frame_feed(data[i], s_proto.parse_out, &out_len);
if (ret == 1)
{
evt.type = MCU_EVT_UART_FRAME;
evt.len = out_len;
- memcpy(evt.buf, s_parse_out, out_len);
+ memcpy(evt.buf, s_proto.parse_out, out_len);
printf("UART valid data:");
printf_buf(evt.buf, evt.len);
mcu_protocol_post_event(&evt);
@@ -963,15 +1079,38 @@ void mcu_protocol_on_uart_rx(const uint8_t *data, uint16_t len)
void mcu_protocol_on_app_frame(const uint8_t *data, uint16_t len)
{
McuEvt_t evt;
+ uint8_t cmd;
+ uint16_t max_len;
- if (!s_proto.initialized || !data || len < 6 || len > sizeof(evt.buf))
- {
- log_error("app frame invalid len=%u", len);
+ if (!s_proto.initialized || !data) {
+ log_error("app frame invalid");
return;
}
- if (data[0] != MCU_PKT_HEAD_0 || data[1] != MCU_PKT_HEAD_1)
- {
- log_error("app frame head err");
+
+ /* 先看命令字,决定最大帧长;再做与 UART 同等的头/LEN/校验和检查 */
+ if (len < MCU_FRAME_OVERHEAD) {
+ log_error("app frame too short len=%u", len);
+ return;
+ }
+ cmd = data[MCU_FRAME_OFF_CMD];
+ max_len = (cmd == MCU_CMD_OTA_DATA) ? MCU_OTA_FRAME_MAX_LEN : MCU_FRAME_MAX_LEN;
+ if (!mcu_frame_check(data, len, max_len)) {
+ log_error("app frame check fail cmd=0x%02X len=%u", cmd, len);
+ return;
+ }
+
+ /* 0x32 可能超过 128,走独立缓冲,事件队列只带通知 */
+ if (cmd == MCU_CMD_OTA_DATA) {
+ if (s_proto.ota_rx.busy) {
+ log_error("OTA data buf busy, drop");
+ return;
+ }
+ memcpy(s_proto.ota_rx.buf, data, len);
+ s_proto.ota_rx.len = len;
+ s_proto.ota_rx.busy = 1;
+ memset(&evt, 0, sizeof(evt));
+ evt.type = MCU_EVT_APP_OTA_DATA;
+ mcu_protocol_post_event(&evt);
return;
}
@@ -1040,3 +1179,22 @@ void mcu_protocol_notify_network_time(uint64_t unix_s, int32_t gmtoff_s)
evt.gmtoff_s = gmtoff_s;
mcu_protocol_post_event(&evt);
}
+
+/**
+ * @brief BLE 断连入队(adv 周期可调,内部合并重复)
+ */
+void mcu_protocol_notify_disconnected(void)
+{
+ McuEvt_t evt;
+
+ if (s_proto.disc_posted) {
+ return;
+ }
+ s_proto.disc_posted = 1;
+ memset(&evt, 0, sizeof(evt));
+ evt.type = MCU_EVT_BLE_DISCONNECTED;
+ if (!mcu_protocol_post_event(&evt)) {
+ s_proto.disc_posted = 0;
+ log_error("disconnect evt queue full");
+ }
+}
diff --git a/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/protocol/mcu_protocol.h b/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/protocol/mcu_protocol.h
index 4fae81f..d49a389 100644
--- a/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/protocol/mcu_protocol.h
+++ b/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/protocol/mcu_protocol.h
@@ -8,6 +8,7 @@
* - V1.0.0, 2026.08.12, cyWu, 从 mcu_bridge.c 拆出,仅保留协议机制,
* 不含任何业务判断(身份/配网/连接状态见 ../business)
* - V1.1.0, 2026.08.13, cyWu, P1:管理命令分发、pending 发送、网关时间入队
+ * - V1.2.0, 2026.08.15, cyWu, P2:OTA 大包旁路、按 SN 发送、原始帧转发
******************************************************************************/
#ifndef __MCU_PROTOCOL_H__
@@ -70,6 +71,11 @@ void mcu_protocol_notify_gateway_ack(uint16_t cmd,
*/
void mcu_protocol_notify_network_time(uint64_t unix_s, int32_t gmtoff_s);
+/**
+ * @brief BLE 断连通知(入队,交由协议任务处理 OTA 中止;可从 adv 周期调用)
+ */
+void mcu_protocol_notify_disconnected(void);
+
/*============================================================================*/
/* 供 business 层调用的协议发送接口 */
/*============================================================================*/
@@ -83,6 +89,25 @@ void mcu_protocol_notify_network_time(uint64_t unix_s, int32_t gmtoff_s);
*/
int32_t mcu_protocol_send_cmd(uint8_t cmd, const uint8_t *payload, uint16_t plen);
+/**
+ * @brief 使用指定 SN 组帧发送(OTA 转发需与 APP SN 一致)
+ * @param cmd 命令字
+ * @param sn 序列号
+ * @param payload 载荷,可为 NULL
+ * @param plen 载荷长度
+ * @return 帧长;<0 失败
+ */
+int32_t mcu_protocol_send_cmd_sn(uint8_t cmd, uint8_t sn,
+ const uint8_t *payload, uint16_t plen);
+
+/**
+ * @brief 原样发送完整 55AA(用于 0x32 大包,不经 128 组帧缓冲)
+ * @param data 完整帧
+ * @param len 长度
+ * @return 帧长;<0 失败
+ */
+int32_t mcu_protocol_send_raw(const uint8_t *data, uint16_t len);
+
/**
* @brief 发送并占用 ble_pending,等待 expect_ack
* @param cmd 命令字
diff --git a/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/protocol/mcu_uart.c b/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/protocol/mcu_uart.c
index 4e15314..8dd46ba 100644
--- a/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/protocol/mcu_uart.c
+++ b/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/protocol/mcu_uart.c
@@ -20,7 +20,7 @@
#include "asm/gpio.h"
#include "asm/power/power_api.h"
#include "mcu_uart.h"
-#include "mcu_bridge.h"
+#include "mcu_protocol.h"
#include "mcu_frame.h"
#define LOG_TAG_CONST APP
@@ -53,7 +53,7 @@ static volatile u8 s_usr_uart_recieve = 0;
/*============================================================================*/
/**
- * @brief UART 接收任务:阻塞读串口,喂入协议桥
+ * @brief UART 接收任务:阻塞读串口,喂入协议层
* @param arg 未使用
*/
static void mcu_uart_rx_task(void *arg)
@@ -75,7 +75,7 @@ static void mcu_uart_rx_task(void *arg)
log_debug("uart rx cnt=%d", rxcnt);
printf("uart rx data:");
printf_buf(s_rx_tmp, rxcnt);
- mcu_bridge_on_uart_rx(s_rx_tmp, (uint16_t)rxcnt);
+ mcu_protocol_on_uart_rx(s_rx_tmp, (uint16_t)rxcnt);
}
}
}
@@ -146,7 +146,7 @@ McuUart_Ret_t mcu_uart_send(const uint8_t *data, uint16_t len)
if (!s_initialized || !s_uart_bus) {
return MCU_UART_ERR_NOT_INIT;
}
- if (!data || len == 0 || len > MCU_FRAME_MAX_LEN) {
+ if (!data || len == 0 || len > MCU_OTA_FRAME_MAX_LEN) {
return MCU_UART_ERR_PARAM;
}
diff --git a/JBao_JL7016_MOD_FW/apps/usr_ota/usr_ota.c b/JBao_JL7016_MOD_FW/apps/usr_ota/usr_ota.c
new file mode 100644
index 0000000..90ea9ab
--- /dev/null
+++ b/JBao_JL7016_MOD_FW/apps/usr_ota/usr_ota.c
@@ -0,0 +1,481 @@
+/******************************************************************************
+ * @file usr_ota.c
+ * @brief 模组双备份 OTA 引擎实现(独立任务调用 dual_bank API)
+ * @author cyWu <1917507415@qq.com>
+ * @date 2026.08.15
+ * @version V1.1.0
+ * @history
+ * - V1.0.0, 2026.08.15, cyWu, 首次发布
+ * - V1.1.0, 2026.08.15, cyWu, 会话状态收拢到 UsrOta_t;失败路径共用退出
+ ******************************************************************************/
+
+#include "system/includes.h"
+#include "asm/cpu.h"
+#include "asm/clock.h"
+#include "asm/power/power_api.h"
+#include "dual_bank_updata_api.h"
+#include "usr_ota.h"
+#include
+
+#define LOG_TAG_CONST APP
+#define LOG_TAG "[USR_OTA]"
+#define LOG_ERROR_ENABLE
+#define LOG_DEBUG_ENABLE
+#define LOG_INFO_ENABLE
+#include "debug.h"
+
+/*============================================================================*/
+/* 私有宏 */
+/*============================================================================*/
+
+#define USR_OTA_CRC32_POLY (0xEDB88320u)
+#define USR_OTA_REBOOT_DELAY_MS (300u)
+
+typedef enum
+{
+ USR_OTA_ST_IDLE = 0, /**< 空闲 */
+ USR_OTA_ST_WRITE, /**< 写入 */
+ USR_OTA_ST_VERIFY, /**< 校验 */
+ USR_OTA_ST_BURN, /**< 烧 boot */
+ USR_OTA_ST_WAIT_REBOOT /**< 等待重启 */
+} UsrOtaState_t;
+
+extern u32 dual_bank_update_verify_without_crc(void);
+
+/*============================================================================*/
+/* 私有变量 */
+/*============================================================================*/
+
+typedef struct {
+ uint8_t initialized; /**< 初始化 */
+ uint8_t task_created; /**< 任务创建 */
+ volatile uint8_t lp_busy; /**< 低功耗忙 */
+ UsrOtaState_t state; /**< 状态 */
+ UsrOtaEvtCb_t evt_cb; /**< 事件回调 */
+ uint32_t file_size; /**< 文件大小 */
+ uint32_t file_crc32; /**< 文件 CRC32 */
+ uint32_t written; /**< 写入 */
+ uint32_t crc32; /**< CRC32 */
+ uint16_t pkt_len; /**< 包长度 */
+ volatile uint8_t write_req; /**< 写请求 */
+ volatile uint8_t abort_req; /**< 中止请求 */
+} UsrOta_t;
+
+static UsrOta_t s_ota;
+static uint8_t s_pkt_buf[USR_OTA_WRITE_PKT_LEN] __attribute__((aligned(4)));
+
+/*============================================================================*/
+/* 私有函数 */
+/*============================================================================*/
+
+/**
+ * @brief IEEE 802.3 CRC32 复位
+ */
+static void usr_ota_crc32_reset(void)
+{
+ s_ota.crc32 = 0xFFFFFFFFu;
+}
+
+/**
+ * @brief 累加 CRC32
+ * @param data 数据
+ * @param len 长度
+ */
+static void usr_ota_crc32_update(const uint8_t *data, uint32_t len)
+{
+ uint32_t crc = s_ota.crc32;
+ uint32_t i;
+
+ while (len--)
+ {
+ crc ^= *data++;
+ for (i = 0; i < 8u; i++)
+ {
+ if (crc & 1u)
+ {
+ crc = (crc >> 1) ^ USR_OTA_CRC32_POLY;
+ }
+ else
+ {
+ crc >>= 1;
+ }
+ }
+ }
+ s_ota.crc32 = crc;
+}
+
+/**
+ * @brief 取最终 CRC32
+ * @return CRC32
+ */
+static uint32_t usr_ota_crc32_final(void)
+{
+ return s_ota.crc32 ^ 0xFFFFFFFFu;
+}
+
+/**
+ * @brief 抛事件给协议适配层
+ * @param evt 事件
+ */
+static void usr_ota_post_evt(UsrOtaEvt_t evt)
+{
+ if (s_ota.evt_cb)
+ {
+ s_ota.evt_cb(evt);
+ }
+}
+
+/**
+ * @brief 清 bank、退出 dual_bank、回到空闲
+ */
+static void usr_ota_hw_abort(void)
+{
+ flash_update_clr_boot_info(CLEAR_APP_UPDATE_BANK);
+ dual_bank_passive_update_exit(NULL);
+ s_ota.pkt_len = 0;
+ s_ota.write_req = 0;
+ s_ota.abort_req = 0;
+ s_ota.state = USR_OTA_ST_IDLE;
+ s_ota.lp_busy = 0;
+}
+
+/**
+ * @brief 失败退出并抛事件
+ * @param evt 失败事件
+ */
+static void usr_ota_fail_exit(UsrOtaEvt_t evt)
+{
+ usr_ota_hw_abort();
+ usr_ota_post_evt(evt);
+}
+
+/**
+ * @brief 低功耗查询:OTA 忙则不许睡
+ * @return 1=可睡,0=保持唤醒
+ */
+static u8 usr_ota_lp_idle_query(void)
+{
+ return s_ota.lp_busy ? 0 : 1;
+}
+
+REGISTER_LP_TARGET(usr_ota_lp_target) = {
+ .name = "usr_ota",
+ .is_idle = usr_ota_lp_idle_query,
+};
+
+/**
+ * @brief 写 Flash 完成回调(dual_bank 任务上下文)
+ * @param priv 0=成功,非 0=失败
+ * @return 0
+ */
+static int usr_ota_write_cb(void *priv)
+{
+ if (priv)
+ {
+ log_error("dual_bank write fail");
+ usr_ota_fail_exit(USR_OTA_EVT_WRITE_FAIL);
+ return 0;
+ }
+
+ s_ota.written += s_ota.pkt_len;
+ s_ota.pkt_len = 0;
+ log_info("write ok, written=%u/%u", s_ota.written, s_ota.file_size);
+ usr_ota_post_evt(USR_OTA_EVT_WRITE_OK);
+
+ if (s_ota.written >= s_ota.file_size)
+ {
+ s_ota.state = USR_OTA_ST_VERIFY;
+ }
+ return 0;
+}
+
+/**
+ * @brief 烧 boot 完成回调
+ * @param err 0=成功
+ * @return 0
+ */
+static int usr_ota_burn_cb(int err)
+{
+ if (err != 0)
+ {
+ log_error("burn boot fail err=%d", err);
+ usr_ota_fail_exit(USR_OTA_EVT_BURN_FAIL);
+ return 0;
+ }
+
+ s_ota.state = USR_OTA_ST_WAIT_REBOOT;
+ s_ota.lp_busy = 0;
+ log_info("burn boot ok, wait reboot");
+ usr_ota_post_evt(USR_OTA_EVT_BURN_OK);
+ return 0;
+}
+
+/**
+ * @brief 校验并烧 boot(OTA 任务)
+ */
+static void usr_ota_do_verify_burn(void)
+{
+ uint32_t calc;
+ u32 ret;
+
+ calc = usr_ota_crc32_final();
+ if (calc != s_ota.file_crc32)
+ {
+ log_error("crc32 mismatch calc=0x%08X expect=0x%08X",
+ calc, s_ota.file_crc32);
+ usr_ota_fail_exit(USR_OTA_EVT_VERIFY_FAIL);
+ return;
+ }
+
+ ret = dual_bank_update_verify_without_crc();
+ if (ret != 0)
+ {
+ log_error("dual_bank verify fail ret=%u", ret);
+ usr_ota_fail_exit(USR_OTA_EVT_VERIFY_FAIL);
+ return;
+ }
+
+ log_info("verify ok, burn boot");
+ s_ota.state = USR_OTA_ST_BURN;
+ usr_ota_post_evt(USR_OTA_EVT_VERIFY_OK);
+ dual_bank_update_burn_boot_info(usr_ota_burn_cb);
+}
+
+/**
+ * @brief 处理中止请求
+ */
+static void usr_ota_do_abort(void)
+{
+ if (s_ota.state == USR_OTA_ST_IDLE ||
+ s_ota.state == USR_OTA_ST_BURN ||
+ s_ota.state == USR_OTA_ST_WAIT_REBOOT)
+ {
+ s_ota.abort_req = 0;
+ return;
+ }
+ log_info("abort, state=%d", s_ota.state);
+ usr_ota_hw_abort();
+}
+
+/**
+ * @brief OTA 任务:写 Flash / 校验 / 烧 boot
+ * @param arg 未使用
+ */
+static void usr_ota_task(void *arg)
+{
+ (void)arg;
+ log_info("ota task start");
+
+ while (1)
+ {
+ if (s_ota.abort_req)
+ {
+ usr_ota_do_abort();
+ }
+
+ if (s_ota.write_req && s_ota.state == USR_OTA_ST_WRITE)
+ {
+ s_ota.write_req = 0;
+ if (((uint32_t)s_pkt_buf & 0x3u) != 0)
+ {
+ log_error("pkt buf not 4-byte aligned");
+ usr_ota_write_cb((void *)1);
+ }
+ else
+ {
+ dual_bank_update_write(s_pkt_buf, s_ota.pkt_len, usr_ota_write_cb);
+ }
+ }
+
+ if (s_ota.state == USR_OTA_ST_VERIFY)
+ {
+ usr_ota_do_verify_burn();
+ }
+
+ os_time_dly(1);
+ }
+}
+
+/*============================================================================*/
+/* 公有函数 */
+/*============================================================================*/
+
+/**
+ * @brief 初始化
+ * @return UsrOta_Ret_t
+ */
+UsrOta_Ret_t usr_ota_init(void)
+{
+ if (s_ota.initialized)
+ {
+ return USR_OTA_OK;
+ }
+
+ memset(&s_ota, 0, sizeof(s_ota));
+ memset(s_pkt_buf, 0, sizeof(s_pkt_buf));
+ s_ota.state = USR_OTA_ST_IDLE;
+ s_ota.initialized = 1;
+ log_info("init ok, pkt_buf=%p", s_pkt_buf);
+ return USR_OTA_OK;
+}
+
+/**
+ * @brief 注册事件回调
+ * @param cb 回调
+ */
+void usr_ota_set_evt_cb(UsrOtaEvtCb_t cb)
+{
+ s_ota.evt_cb = cb;
+}
+
+/**
+ * @brief 开始升级
+ * @param file_size 总长度
+ * @param file_crc32 CRC32
+ * @return UsrOta_Ret_t
+ */
+UsrOta_Ret_t usr_ota_start(uint32_t file_size, uint32_t file_crc32)
+{
+ u32 ret;
+
+ if (!s_ota.initialized)
+ {
+ return USR_OTA_ERR_NOT_INIT;
+ }
+ if (file_size == 0)
+ {
+ return USR_OTA_ERR_PARAM;
+ }
+ if (s_ota.state != USR_OTA_ST_IDLE)
+ {
+ return USR_OTA_ERR_BUSY;
+ }
+
+ if (!s_ota.task_created)
+ {
+ os_task_create(usr_ota_task, NULL, USR_OTA_TASK_PRIO,
+ USR_OTA_TASK_STACK, 0, "usr_ota");
+ s_ota.task_created = 1;
+ }
+
+ clk_set("sys", 48 * 1000000L);
+ flash_update_clr_boot_info(CLEAR_APP_UPDATE_BANK);
+
+ ret = dual_bank_passive_update_init(0, file_size, USR_OTA_WRITE_PKT_LEN, NULL);
+ if (ret != 0)
+ {
+ log_error("passive init fail %u", ret);
+ dual_bank_passive_update_exit(NULL);
+ return USR_OTA_ERR_SPACE;
+ }
+
+ ret = dual_bank_update_allow_check(file_size);
+ if (ret != 0)
+ {
+ log_error("allow_check fail %u size=%u", ret, file_size);
+ dual_bank_passive_update_exit(NULL);
+ return USR_OTA_ERR_SPACE;
+ }
+
+ s_ota.file_size = file_size;
+ s_ota.file_crc32 = file_crc32;
+ s_ota.written = 0;
+ s_ota.pkt_len = 0;
+ s_ota.write_req = 0;
+ s_ota.abort_req = 0;
+ usr_ota_crc32_reset();
+ s_ota.lp_busy = 1;
+ s_ota.state = USR_OTA_ST_WRITE;
+ log_info("start size=%u crc32=0x%08X", file_size, file_crc32);
+ return USR_OTA_OK;
+}
+
+/**
+ * @brief 喂入固件分片
+ * @param buf 数据
+ * @param len 长度
+ * @return UsrOta_Ret_t
+ */
+UsrOta_Ret_t usr_ota_feed(const uint8_t *buf, uint16_t len)
+{
+ uint32_t remain;
+
+ if (!buf || len == 0)
+ {
+ return USR_OTA_ERR_PARAM;
+ }
+ if (s_ota.state != USR_OTA_ST_WRITE)
+ {
+ return USR_OTA_ERR_STATE;
+ }
+ if (s_ota.write_req)
+ {
+ return USR_OTA_ERR_BUSY;
+ }
+ if ((uint32_t)s_ota.pkt_len + len > USR_OTA_WRITE_PKT_LEN)
+ {
+ return USR_OTA_ERR_PARAM;
+ }
+
+ memcpy(&s_pkt_buf[s_ota.pkt_len], buf, len);
+ s_ota.pkt_len = (uint16_t)(s_ota.pkt_len + len);
+ usr_ota_crc32_update(buf, len);
+
+ remain = (s_ota.written < s_ota.file_size)
+ ? (s_ota.file_size - s_ota.written)
+ : 0u;
+ /* 凑满 4096,或已经拿到文件剩下的全部字节 → 交给 OTA 任务写 Flash */
+ if (s_ota.pkt_len >= USR_OTA_WRITE_PKT_LEN ||
+ (uint32_t)s_ota.pkt_len >= remain)
+ {
+ s_ota.write_req = 1;
+ return USR_OTA_FEED_PENDING;
+ }
+
+ return USR_OTA_OK;
+}
+
+/**
+ * @brief 中止升级
+ */
+void usr_ota_abort(void)
+{
+ /* boot 已写入或正在烧:禁止清 bank */
+ if (s_ota.state == USR_OTA_ST_IDLE ||
+ s_ota.state == USR_OTA_ST_BURN ||
+ s_ota.state == USR_OTA_ST_WAIT_REBOOT)
+ {
+ return;
+ }
+ s_ota.abort_req = 1;
+}
+
+/**
+ * @brief 延时复位
+ */
+static void usr_ota_do_cpu_reset(void *priv)
+{
+ (void)priv;
+ cpu_reset();
+}
+
+/**
+ * @brief 烧 boot 成功后 exit + 复位
+ */
+void usr_ota_request_reboot(void)
+{
+ log_info("request reboot");
+ dual_bank_passive_update_exit(NULL);
+ s_ota.state = USR_OTA_ST_IDLE;
+ s_ota.lp_busy = 0;
+ sys_timeout_add(NULL, usr_ota_do_cpu_reset, USR_OTA_REBOOT_DELAY_MS);
+}
+
+/**
+ * @brief 是否忙
+ * @return 1/0
+ */
+uint8_t usr_ota_is_busy(void)
+{
+ return (s_ota.state != USR_OTA_ST_IDLE) ? 1 : 0;
+}
diff --git a/JBao_JL7016_MOD_FW/apps/usr_ota/usr_ota.h b/JBao_JL7016_MOD_FW/apps/usr_ota/usr_ota.h
new file mode 100644
index 0000000..e14cd21
--- /dev/null
+++ b/JBao_JL7016_MOD_FW/apps/usr_ota/usr_ota.h
@@ -0,0 +1,101 @@
+/******************************************************************************
+ * @file usr_ota.h
+ * @brief 模组双备份 OTA 引擎(协议无关):START/WRITE/VERIFY/BURN
+ * @author cyWu <1917507415@qq.com>
+ * @date 2026.08.15
+ * @version V1.0.0
+ * @history
+ * - V1.0.0, 2026.08.15, cyWu, 首次发布
+ ******************************************************************************/
+
+#ifndef __USR_OTA_H__
+#define __USR_OTA_H__
+
+#include
+
+/*============================================================================*/
+/* 宏定义 */
+/*============================================================================*/
+
+#define USR_OTA_WRITE_PKT_LEN (4096u) /**< dual_bank 单次写入长度 */
+#define USR_OTA_TASK_PRIO (31u)
+#define USR_OTA_TASK_STACK (2048u)
+
+/*============================================================================*/
+/* 错误码 / 事件 */
+/*============================================================================*/
+
+typedef enum
+{
+ USR_OTA_OK = 0, /**< 成功 */
+ USR_OTA_ERR_PARAM, /**< 参数错误 */
+ USR_OTA_ERR_BUSY, /**< 忙 */
+ USR_OTA_ERR_STATE, /**< 状态错误 */
+ USR_OTA_ERR_SPACE, /**< 空间不足 */
+ USR_OTA_ERR_NOT_INIT, /**< 未初始化 */
+ USR_OTA_FEED_PENDING, /**< 本包已缓存,等写 Flash 完成后再 ACK */
+ USR_OTA_UNKNOWN /**< 未知错误 */
+} UsrOta_Ret_t;
+
+typedef enum
+{
+ USR_OTA_EVT_WRITE_OK = 0, /**< 一包写入成功,可回 0x33 */
+ USR_OTA_EVT_WRITE_FAIL, /**< 一包写入失败 */
+ USR_OTA_EVT_VERIFY_OK, /**< 校验成功 */
+ USR_OTA_EVT_VERIFY_FAIL, /**< 校验失败 */
+ USR_OTA_EVT_BURN_OK, /**< 烧 boot 成功 */
+ USR_OTA_EVT_BURN_FAIL, /**< 烧 boot 失败 */
+} UsrOtaEvt_t;
+
+typedef void (*UsrOtaEvtCb_t)(UsrOtaEvt_t evt);
+
+/*============================================================================*/
+/* 外部接口 */
+/*============================================================================*/
+
+/**
+ * @brief 初始化双备份 OTA 引擎(创建任务、注册 LP)
+ * @return UsrOta_Ret_t
+ */
+UsrOta_Ret_t usr_ota_init(void);
+
+/**
+ * @brief 注册事件回调(写完/校验/烧 boot)
+ * @param cb 回调,可在 OTA 任务上下文触发
+ */
+void usr_ota_set_evt_cb(UsrOtaEvtCb_t cb);
+
+/**
+ * @brief 开始模组 OTA(协议任务上下文调用)
+ * @param file_size 固件总长度
+ * @param file_crc32 协议 CRC32(IEEE 802.3)
+ * @return OK / BUSY / SPACE / PARAM
+ */
+UsrOta_Ret_t usr_ota_start(uint32_t file_size, uint32_t file_crc32);
+
+/**
+ * @brief 喂入一包固件(只缓存;满 4096 或最后一包才写 Flash)
+ * @param buf 数据
+ * @param len 长度
+ * @return OK=可立刻 ACK;FEED_PENDING=等 WRITE_OK;其它=失败
+ */
+UsrOta_Ret_t usr_ota_feed(const uint8_t *buf, uint16_t len);
+
+/**
+ * @brief 中止升级:清 UPDATE_BANK boot、exit,不复位
+ * @note 正在烧 boot 或已烧成功时为 no-op,避免把已提交的 bank 清掉
+ */
+void usr_ota_abort(void);
+
+/**
+ * @brief 烧 boot 成功后收尾:exit + 延时复位
+ */
+void usr_ota_request_reboot(void);
+
+/**
+ * @brief 是否正在模组 OTA
+ * @return 1=忙,0=空闲
+ */
+uint8_t usr_ota_is_busy(void);
+
+#endif /* __USR_OTA_H__ */