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__ */