diff --git a/.gitignore b/.gitignore index 242061c..65311da 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ PY32F040开发框架/* -.vscode/ \ No newline at end of file +.vscode/ +模组功能分期清单.md +.clang-format \ No newline at end of file diff --git a/Doc/MCU方案接入协议通讯文档.md b/Doc/MCU方案接入协议通讯文档.md index 049929e..d755b37 100644 --- a/Doc/MCU方案接入协议通讯文档.md +++ b/Doc/MCU方案接入协议通讯文档.md @@ -1,4 +1,4 @@ -# MCU方案接入协议通讯文档 +# MCU方案接入协议通讯文档 # 修订记录 diff --git a/Doc/SOC方案接入通信协议文档.md b/Doc/SOC方案接入通信协议文档.md new file mode 100644 index 0000000..4d3bf1a --- /dev/null +++ b/Doc/SOC方案接入通信协议文档.md @@ -0,0 +1,335 @@ +# SOC方案接入通信协议文档 + +# 修订记录 + +|文档版本|协议版本|日期|编写人|修订类型|修订内容| +|---|---|---|---|---|---| +|V1\.0\.0|V1\.0|2026\-07\-08|麦田|新建|首次发布SOC方案接入协议通讯文档| + +--- + +# 协议说明 + +- 数据采用**大端模式**(Big Endian)传输。 + +- 数据帧格式: **包头 \+ 包长度 \+ 命令 \+ 序列号 \+ 数据 \+ 校验和**。 + +- 包头固定为 `0x55AA`。 + +- 包长度表示 **命令开始至校验和结束** 的数据长度。 + +- 命令字(Command)用于区分不同功能。 + +- 序列号(Seq)由请求方递增生成,ACK需原样返回,用于匹配请求与响应。 + +- 默认所有命令采用 **一问一答机制**,发送方发送命令后,接收方必须返回对应 ACK。 + +- ACK 数据包包含 Result 字段,用于返回执行结果。 + +- 超时未收到 ACK 时,发送方可进行重试,最大重试次数建议为 3 次。 + +- 数据点标志(Bitmap)由 DP 配置文档自动生成,一个 Bit 对应一个 DP。 + +- 数据点值按照 Bitmap 中 Bit 置位顺序依次排列。 + +- 读取命令通过 Bitmap 指定需要读取的 DP,Bitmap 全 1 表示读取全部 DP。 + +- BLE 模组主要负责数据透传、蓝牙连接及状态管理,业务数据由 MCU 处理。 + +- 校验方式:累加和校验(Sum Check),计算范围为 **包长度字段至校验和前一个字节**。 + +## Result 定义: + +|值|含义| +|---|---| +|0x00|成功| +|0x01|参数错误| +|0x02|设备忙| +|0x03|不支持该操作| +|0x04|校验错误| +|0x05|执行失败| + +### OTA 专用错误码 + +|值|含义| +|---|---| +|0x20|无需升级(版本一致)| +|0x21|OTA 版本低于当前版本| +|0x22|Flash 空间不足| +|0x23|OTA 状态错误| +|0x24|OTA文件检验失败| +|0x25|升级失败| + +# 基本通讯协议 + +## 设备上报版本信息 + +> 设备绑定上网关之后,主动上报固件版本到服务器,此命令通过网关的应答格式判断是否发送成功。。 +> +> + +### 设备 =\> 网关 + +|序号|字段名称|字节长度|内容说明| +|---|---|---|---| +|1|包头|2|0x55AA| +|2|包长度|2|len\(命令\.\.\.校验和\)| +|3|命令|1|0x41| +|4|序列号|1|与请求的序列号一致| +|5|Result|1|执行结果(见 Result 定义)| +|6|BLE Version|3|固件版本号,格式:主版本号\(1Byte\)\+次版本号\(1Byte\)\+修订版本号\(1Byte\),显示格式为:`Vx.x.x`| +|7|MCU Version|3|固件版本号,格式:主版本号\(1Byte\)\+次版本号\(1Byte\)\+修订版本号\(1Byte\),显示格式为:`Vx.x.x`,如MCU不存在则填0| +|8|Hardware Version|3|硬件版本号,格式:硬件主版本号\(1Byte\)\+硬件次版本号\(1Byte\)\+硬件修订版本号\(1Byte\),显示格式:`Vx.x.x`| +|9|校验和|1|和校验(包长度\.\.\.校验和前一个字节)| + +#### Version版本格式说明 + +|字节|字段名称|内容说明| +|---|---|---| +|Byte0|主版本号|产品架构、通信协议等重大变化时递增| +|Byte1|次版本号|新增功能或功能优化时递增| +|Byte2|修订版本号|Bug修复、参数调整、性能优化时递增| + +#### 示例 + +|MCU Version|版本号| +|---|---| +|0x01 0x02 0x05|V1\.2\.5| +|0x05 0x02 0x21|V5\.2\.33| + +### 网关 =\> 设备 + +通过网关的应答格式判断是否发送成功 + +## APP控制设备 + +### APP =\> 设备 + +|序号|字段名称|字节长度|内容说明| +|---|---|---|---| +|1|包头|2|0x55AA| +|2|包长度|2|len\(命令\.\.\.校验和\)| +|3|命令|1|0x03(下发控制)| +|4|序列号|1|APP递增生成,ACK原样回传| +|5|数据点标志|x(\(DP数量\+7\)/8)|一个字节可代表8个数据点| +|6|数据点值|x|数据值| +|7|校验和|1|和校验(包长度\.\.\.校验和前一个字节)| + +### 设备 =\> APP + +|序号|字段名称|字节长度|内容说明| +|---|---|---|---| +|1|包头|2|0x55AA| +|2|包长度|2|len\(命令\.\.\.校验和\)| +|3|命令|1|0x04(应答下发控制)| +|4|序列号|1|与请求的序列号一致| +|5|Result|1|执行结果(见 Result 定义)| +|6|校验和|1|和校验(包长度\.\.\.校验和前一个字节)| + +--- + +## APP读取设备 + +### APP =\> 设备 + +|序号|字段名称|字节长度|内容说明| +|---|---|---|---| +|1|包头|2|0x55AA| +|2|包长度|2|len\(命令\.\.\.校验和\)| +|3|命令|1|0x05(下发读取)| +|4|序列号|1|APP递增生成,ACK原样回传| +|5|数据点标志|x(\(DP数量\+7\)/8)|指定需要读取的 DP,Bitmap 全 1 表示读取全部 DP| +|6|校验和|1|和校验(包长度\.\.\.校验和前一个字节)| + +### 设备=\> APP + +|序号|字段名称|字节长度|内容说明| +|---|---|---|---| +|1|包头|2|0x55AA| +|2|包长度|2|len\(命令\.\.\.校验和\)| +|3|命令|1|0x06(应答读取)| +|4|序列号|1|与请求的序列号一致| +|5|数据点标志|x(\(DP数量\+7\)/8)|返回的数据点标志| +|6|数据点值|x|数据值| +|7|校验和|1|和校验(包长度\.\.\.校验和前一个字节)| + +--- + +## 设备主动上报数据 + +此命令直接上报到服务器,通过网关的应答格式判断是否发送成功。 + +### 设备 =\> APP/IPC + +|序号|字段名称|字节长度|内容说明| +|---|---|---|---| +|1|包头|2|0x55AA| +|2|包长度|2|len\(命令\.\.\.校验和\)| +|3|命令|1|0x07(主动上报)| +|4|序列号|1|设备递增生成| +|5|数据点标志|x(\(DP数量\+7\)/8)|一个字节可代表8个数据点| +|6|数据点值|x|数据值| +|7|校验和|1|和校验(包长度\.\.\.校验和前一个字节)| + +### APP/IPC =\> 设备 + +通过网关的应答格式判断是否发送成功 + +--- + +# 扩展通讯协议 + +## APP向设备推送OTA升级 + +> **功能说明:** +APP 将与设备升级相关的信息发送给设备,设备根据这些信息判断是否允许OTA升级,并返回升级准备结果。 + +### APP =\> 设备 + +|序号|字段名称|字节长度|内容说明| +|---|---|---|---| +|1|包头|2|固定 `0x55AA`| +|2|包长度|2|len\(命令\.\.\.校验和\)| +|3|命令|1|0x30| +|4|序列号|1|数据包序列号| +|5|固件类型(Type)|1|0x01:MCU
0x02:BLE| +|6|固件版本(Firmware Version)|3|OTA 固件版本号| +|7|固件长度(Payload Length)|4|OTA Bin 数据长度| +|8|固件 CRC32(Payload CRC32)|4|OTA Bin 数据 CRC32 校验值| +|9|校验和|1|和校验(包长度\.\.\.校验和前一个字节)| + +### 设备 =\> APP + +|序号|字段名称|字节长度|内容说明| +|---|---|---|---| +|1|包头|2|固定 `0x55AA`| +|2|包长度|2|`len(命令...校验和)`| +|3|命令|1|0x31| +|4|序列号|1|与请求保持一致| +|5|Result|1|执行结果(见 Result OTA 专用错误码定义)| +|6|Max Data Length|2|设备 单次允许接收的最大 Payload 长度(Byte)| +|7|校验和|1|和校验(包长度\.\.\.校验和前一个字节)| + +--- + +## APP向设备推送OTA数据 + +> **功能说明:** +APP 根据 设备 返回的 **Max Data Length** 对 OTA Bin 进行分片,并按顺序发送给设备。 +设备 每接收一包数据后立即返回应答,APP 收到成功应答后继续发送下一包。 + +### APP =\> 设备 + +|序号|字段名称|字节长度|内容说明| +|---|---|---|---| +|1|包头|2|固定 `0x55AA`| +|2|包长度|2|`len(命令...校验和)`| +|3|命令|1|0x32| +|4|序列号|1|数据包序列号| +|5|分片序号|2|当前数据包的分片序号,分片序号从1开始计算| +|6|总分片数|2|| +|7|Payload Data|x|OTA Bin 数据,长度 ≤ Max Data Length| +|8|校验和|1|和校验(包长度\.\.\.校验和前一个字节)| + +### 设备 =\> APP + +> **功能说明:** +设备 成功接收并处理当前 OTA 数据包后返回 Result。 +APP收到成功应答后继续发送下一包 OTA 数据;若返回失败,则停止本次 OTA 升级。 + +|序号|字段名称|字节长度|内容说明| +|---|---|---|---| +|1|包头|2|固定 `0x55AA`| +|2|包长度|2|`len(命令...校验和)`| +|3|命令|1|0x33| +|4|序列号|1|与请求保持一致| +|5|Result|1|执行结果(见 Result 定义)| +|6|校验和|1|和校验(包长度\.\.\.校验和前一个字节)| + +## 设备通知APP模组OTA升级完成 + +> **功能说明: +** 设备完成OTA升级后,主动通知APP当前升级结果及固件版本信息。 + +### 设备 =\> APP + +|序号|字段名称|字节长度|内容说明| +|---|---|---|---| +|1|包头|2|固定 `0x55AA`| +|2|包长度|2|`len(命令...校验和)`| +|3|命令|1|0x34| +|4|序列号|1|数据包序列号| +|5|类型(Type)|1|0x01:MCU
0x02:BLE| +|6|OTA Result|1|OTA结果| +|7|Firmware Version|3|当前固件版本| +|8|校验和|1|和校验(包长度\.\.\.校验和前一个字节)| + +### APP =\> 设备 + +|序号|字段名称|字节长度|内容说明| +|---|---|---|---| +|1|包头|2|固定 `0x55AA`| +|2|包长度|2|`len(命令...校验和)`| +|3|命令|1|0x35| +|4|序列号|1|与请求保持一致| +|5|Result|1|执行结果(见 Result 定义)| +|6|校验和|1|和校验(包长度\.\.\.校验和前一个字节)| + +--- + +## APP向设备通知OTA停止 + +> **功能说明:** +APP 主动通知设备终止当前 OTA 升级流程。设备收到后立即停止 OTA 数据接收,释放 OTA 相关资源,并返回处理结果。 + +### APP =\> 设备 + +|序号|字段名称|字节长度|内容说明| +|---|---|---|---| +|1|包头|2|固定 `0x55AA`| +|2|包长度|2|`len(命令...校验和)`| +|3|命令|1|0x36| +|4|序列号|1|数据包序列号| +|5|校验和|1|和校验(包长度\.\.\.校验和前一个字节)| + +### 设备 =\> APP + +|序号|字段名称|字节长度|内容说明| +|---|---|---|---| +|1|包头|2|固定 `0x55AA`| +|2|包长度|2|`len(命令...校验和)`| +|3|命令|1|0x37| +|4|序列号|1|与请求保持一致| +|5|Result|1|执行结果(见 Result 定义)| +|6|校验和|1|和校验(包长度\.\.\.校验和前一个字节)| + +--- + +## 设备向APP通知OTA停止 + +> **功能说明:** +设备在 OTA 过程中因内部异常无法继续升级时,主动通知 APP 停止当前 OTA。APP 收到通知后立即停止发送 OTA 数据,并返回应答。 + +### 设备 =\> APP + +|序号|字段名称|字节长度|内容说明| +|---|---|---|---| +|1|包头|2|固定 `0x55AA`| +|2|包长度|2|`len(命令...校验和)`| +|3|命令|1|0x38| +|4|序列号|1|数据包序列号| +|5|校验和|1|和校验(包长度\.\.\.校验和前一个字节)| + +### APP =\> 设备 + +|序号|字段名称|字节长度|内容说明| +|---|---|---|---| +|1|包头|2|固定 `0x55AA`| +|2|包长度|2|`len(命令...校验和)`| +|3|命令|1|0x39| +|4|序列号|1|与通知保持一致| +|5|Result|1|`0x00`:已收到并停止 OTA| +|6|校验和|1|和校验(包长度\.\.\.校验和前一个字节)| + + + diff --git a/JBao_JL7016_MOD_FW/AC701N.cbp b/JBao_JL7016_MOD_FW/AC701N.cbp index 90dc304..2863999 100644 --- a/JBao_JL7016_MOD_FW/AC701N.cbp +++ b/JBao_JL7016_MOD_FW/AC701N.cbp @@ -177,6 +177,7 @@ + @@ -759,43 +760,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -821,6 +785,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1271,10 +1285,6 @@ - - - - - + diff --git a/JBao_JL7016_MOD_FW/Makefile b/JBao_JL7016_MOD_FW/Makefile index 9aebae2..ffcd9bd 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_periph \ -I$(SYS_INC_DIR) \ @@ -624,6 +625,10 @@ c_SRC_FILES := \ apps/usr_mcu_bridge/business/mcu_pairing.c \ apps/usr_mcu_bridge/business/mcu_link_status.c \ apps/usr_mcu_bridge/business/mcu_business.c \ + 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_periph/usr_rtc.c \ apps/usr_le_code/ble_proto.c \ apps/usr_le_code/bleproto.c \ apps/usr_le_code/bleproto_packer.c \ diff --git a/JBao_JL7016_MOD_FW/apps/common/third_party_profile/jieli/JL_rcsp/bt_trans_data/le_rcsp_adv_module.c b/JBao_JL7016_MOD_FW/apps/common/third_party_profile/jieli/JL_rcsp/bt_trans_data/le_rcsp_adv_module.c index e94d728..bf37547 100644 --- a/JBao_JL7016_MOD_FW/apps/common/third_party_profile/jieli/JL_rcsp/bt_trans_data/le_rcsp_adv_module.c +++ b/JBao_JL7016_MOD_FW/apps/common/third_party_profile/jieli/JL_rcsp/bt_trans_data/le_rcsp_adv_module.c @@ -526,6 +526,11 @@ static int att_write_callback(hci_con_handle_t connection_handle, uint16_t att_h void usr_ble_send_data(u8* data,u16 len) { + /* CCC 未写时 ATT 发数会失败(常见 app_send_fail:-93),等主机使能通知再发 */ + if (get_ble_work_state() != BLE_ST_NOTIFY_IDICATE) { + log_info("ble send skip, wait CCC"); + return; + } if (app_send_user_data_check(len)) { app_send_user_data(ATT_CHARACTERISTIC_15f1e601_a277_43fc_a484_dd39ef8a9100_01_VALUE_HANDLE, data, len, ATT_OP_AUTO_READ_CCC); @@ -538,7 +543,6 @@ void usr_ble_send_data(u8* data,u16 len) // printf("%c",data[i]); // } // printf("\n"); - } static int app_send_user_data(u16 handle, u8 *data, u16 len, u8 handle_type) { @@ -961,6 +965,15 @@ u16 usr_get_conn_service() { return con_handle; } + +/** + * @brief 主机是否已写 CCC,允许 GATT notify/indicate + * @return 1=可发数(BLE_ST_NOTIFY_IDICATE),0=还不行 + */ +u8 usr_ble_is_notify_ready(void) +{ + return (get_ble_work_state() == BLE_ST_NOTIFY_IDICATE) ? 1 : 0; +} void ble_app_disconnect(void) { ble_disconnect(NULL); diff --git a/JBao_JL7016_MOD_FW/apps/earphone/app_main.c b/JBao_JL7016_MOD_FW/apps/earphone/app_main.c index 5bbca73..0082712 100644 --- a/JBao_JL7016_MOD_FW/apps/earphone/app_main.c +++ b/JBao_JL7016_MOD_FW/apps/earphone/app_main.c @@ -17,6 +17,7 @@ #include "dev_manager/dev_manager.h" #include "update_loader_download.h" #include "mcu_bridge.h" +#include "usr_rtc.h" #ifndef CONFIG_MEDIA_NEW_ENABLE #ifndef CONFIG_MEDIA_DEVELOP_ENABLE @@ -292,6 +293,7 @@ void app_main() } app_var_init(); + usr_rtc_init(); /* 打开硬件 RTC,后续对时/0x1D 都走这里 */ mcu_bridge_init(); #if TCFG_MC_BIAS_AUTO_ADJUST diff --git a/JBao_JL7016_MOD_FW/apps/earphone/board/br28/board_jl701n_demo.c b/JBao_JL7016_MOD_FW/apps/earphone/board/br28/board_jl701n_demo.c index 6d682a1..51529d6 100644 --- a/JBao_JL7016_MOD_FW/apps/earphone/board/br28/board_jl701n_demo.c +++ b/JBao_JL7016_MOD_FW/apps/earphone/board/br28/board_jl701n_demo.c @@ -586,9 +586,9 @@ GSENSOR_PLATFORM_DATA_END(); -#if 1 +#if TCFG_APP_RTC_EN -#define RTC_CLK_RES_SEL CLK_SEL_32K //rtc时钟源选择:CLK_SEL_32K/CLK_SEL_12M/CLK_SEL_24M +#define RTC_CLK_RES_SEL CLK_SEL_LRC // 开发板无 32K,用 LRC;量产模组有晶振时改 CLK_SEL_32K const struct sys_time def_sys_time = { //初始一下当前时间 .year = 2020, @@ -615,7 +615,7 @@ RTC_DEV_PLATFORM_DATA_BEGIN(rtc_data) .cbfun = NULL, //闹钟中断的回调函数,用户自行定义 /* .cbfun = alarm_isr_user_cbfun, */ RTC_DEV_PLATFORM_DATA_END() -#endif // 0 +#endif /* TCFG_APP_RTC_EN */ @@ -632,7 +632,9 @@ REGISTER_DEVICES(device_table) = { #if TCFG_OTG_MODE { "otg", &usb_dev_ops, (void *)&otg_data}, #endif +#if TCFG_APP_RTC_EN { "rtc", &rtc_dev_ops , (void *)&rtc_data}, +#endif }; /************************** imu sensor ****************************/ diff --git a/JBao_JL7016_MOD_FW/apps/earphone/include/app_main.h b/JBao_JL7016_MOD_FW/apps/earphone/include/app_main.h index f7998ef..6eee49e 100644 --- a/JBao_JL7016_MOD_FW/apps/earphone/include/app_main.h +++ b/JBao_JL7016_MOD_FW/apps/earphone/include/app_main.h @@ -1,6 +1,8 @@ #ifndef APP_MAIN_H #define APP_MAIN_H +#include "generic/typedef.h" /* u8/u16/u32 等杰理类型,本头必须自包含 */ + typedef struct _USR_VAR { u8 usr_start_write_flash ; u8 usr_plan_cnt ; @@ -37,12 +39,10 @@ typedef struct _USR_VAR { u32 usr_data_index ; u32 usr_allow_to_flash ; u32 usr_ota_data_size ; - u32 usr_cureent_utc ; int usr_double_ota_timer_id ; int ota_timer ; int usr_ota_data_id ; - int usr_timezoneOffset ; } USR_VAR; diff --git a/JBao_JL7016_MOD_FW/apps/usr_le_code/bleproto_packer.h b/JBao_JL7016_MOD_FW/apps/usr_le_code/bleproto_packer.h index 92df46f..9e4ce41 100644 --- a/JBao_JL7016_MOD_FW/apps/usr_le_code/bleproto_packer.h +++ b/JBao_JL7016_MOD_FW/apps/usr_le_code/bleproto_packer.h @@ -381,7 +381,7 @@ typedef struct bleproto_ycmd { // E_BLEPROTO_SERVICE_ID_DEVICEINFO ////////////////////////////////////////////////////////////////////// typedef struct bleproto_deviceinfo_req { - uint64_t t; ///< utcms时间戳 + uint64_t t; ///< Unix 时间戳,单位秒。网关从子设备连上起计时,每 12 小时同步一次 int gmtoff; ///< 时区偏移,单位秒,例如东八区(8x60x60=28800秒) } bleproto_deviceinfo_req_t; 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 fc54e89..e0d81d6 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 @@ -99,7 +99,7 @@ void usr_write_pair_info() */ void usr_clear_pair_info() { - printf("usr_clear_pair_info\n"); + printf("usr_clear_pair_info_reset\n"); memset(&usr_auth_data,0x00,sizeof(usr_auth_data)); usr_auth_data.paired_flag=0; /* 回到未配网 */ syscfg_write(CFG_USER_PAIR_INFO, (u8 *)&usr_auth_data, sizeof(usr_auth_data)); @@ -112,7 +112,7 @@ void usr_clear_pair_info() */ void usr_clear_pair_info_noreset() { - printf("usr_clear_pair_info2\n"); + printf("usr_clear_pair_info_noreset\n"); memset(&usr_auth_data,0x00,sizeof(usr_auth_data)); usr_auth_data.paired_flag=0; /* 回到未配网,后续由 usr_goto_pair 决定是否绑广播 */ syscfg_write(CFG_USER_PAIR_INFO, (u8 *)&usr_auth_data, sizeof(usr_auth_data)); @@ -138,6 +138,18 @@ void usr_goto_pair_mode() usr_ble_adv_init(); } +/** + * @brief 断开 BLE 并停止广播(模组复位/重启前) + */ +void usr_ble_disconnect_and_stop_adv(void) +{ + printf("usr_ble_disconnect_and_stop_adv\n"); + ble_multi_trans_disconnect(); + usr_var.usr_goto_pair = 0; + usr_var.usr_adv_flag = 0; + ble_trans_module_enable(0); +} + /** * @brief 按当前标志刷新广播策略(不一定开广播) * @note 见文件头「三个标志」说明;未拿身份 / 未配网时保持不广播 @@ -199,14 +211,6 @@ void usr_ble_adv_init() if(le_timer_handle==0)le_timer_handle = sys_timer_add(NULL, usr_ble_adv_updata, 500); } -/** - * @brief 获取是否已配网 - * @return 1=已配网(paired_flag),0=未配网 - */ -/** - * @brief 获取是否已配网 - * @return 1=已配网(paired_flag),0=未配网 - */ /** * @brief 获取是否已配网 * @return paired_flag:1=已配网,0=未配网 @@ -239,8 +243,9 @@ void usr_ble_adv_updata() static u16 usr_goto_slow_adv=0; if(usr_var.usr_adv_flag) { + /* usr_adv_flag=1:把 muti_adv_data 刷进协议栈并开广播,用完即清 */ ble_trans_module_enable(0); - usr_var.usr_adv_flag=0; /* 一次性触发,用完即清 */ + usr_var.usr_adv_flag=0; muti_make_set_adv_data(muti_adv_data,muti_adv_data_len); printf("###muti_adv_data = "); printf_buf(muti_adv_data,muti_adv_data_len); @@ -249,10 +254,12 @@ void usr_ble_adv_updata() if(usr_get_conn_service()==0) { + /* 未连接:回连快广播(80)持续约 60s(500ms×120)后切慢广播 */ if(usr_get_adv_interval()==80) { if((usr_var.usr_goto_pair==0)&&(usr_auth_data.paired_flag)) { + /* 已配网回连,且不是绑广播、不是 OTA 成功态,才开始计时 */ if(usr_var.usr_ota_succ_flag==0) { usr_goto_slow_adv++; @@ -262,23 +269,24 @@ void usr_ble_adv_updata() { usr_goto_slow_adv=0; usr_set_adv_interval(80*10); - usr_var.usr_adv_flag=1; + usr_var.usr_adv_flag=1; /* 下一拍刷新成慢间隔 */ } } } else { - usr_goto_slow_adv=0; + usr_goto_slow_adv=0; /* 已经是慢广播,计数清掉 */ } } else { - usr_goto_slow_adv=0; + usr_goto_slow_adv=0; /* 已连接,不需要慢广播计时 */ } if(usr_get_ota_status()==0) { + /* 不在 OTA:未连接则退出 OTA 残留(ota_succ 时不退) */ if(usr_get_conn_service()==0) { if(usr_var.usr_ota_succ_flag==0)usr_ota_exit(); @@ -312,7 +320,7 @@ void usr_ota_process() static u8 cnt = 0; if (cnt == 0) { - ble_multi_trans_disconnect(); + ble_multi_trans_disconnect(); /* 第一次:先断开,好换 OTA 广播地址 */ } if (usr_get_conn_service() == 0) @@ -320,17 +328,17 @@ void usr_ota_process() cnt++; if (cnt == 2) { - usr_ble_adv_updata_OTA(); + usr_ble_adv_updata_OTA(); /* 断连后第二拍:改 MAC 并发 OTA 广播 */ } if (cnt >= 60) { cnt = 0; - cpu_reset(); + cpu_reset(); /* 约 60s 仍没连上 OTA,复位退出 */ } } else { - cnt = 1; + cnt = 1; /* 已连上:保持断开过的状态,不再累加复位计时 */ } } 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 71e3b9c..7264919 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,11 +2,12 @@ * @file usr_le_api.h * @brief usr_le_code 对外 API(广播 / 收发 / 配网 / MCU 帧转发) * @author cyWu <1917507415@qq.com> - * @date 2026.08.06 - * @version V1.1.0 + * @date 2026.08.13 + * @version V1.2.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 上报 ******************************************************************************/ #ifndef __USR_LE_API_H__ @@ -39,9 +40,10 @@ void usr_le_data_recieve(uint8_t *data, uint16_t len); * @brief 将 MCU 协议 55AA 帧封装为 bleproto 后经 GATT 发送 * @param data 完整 55AA 帧 * @param len 帧长度 - * @note 0x07/0x34/0x38 走 REPORTCMD_V2 主动上报;其余走 RUNCMD_V2 应答 + * @note 0x07/0x34/0x38/0x41 走 REPORTCMD_V2 主动上报;其余走 RUNCMD_V2 应答 + * @note 主机未写 CCC 时不会发出,返回 0 */ -void usr_le_send_mcu_frame(uint8_t *data, uint16_t len); +uint8_t usr_le_send_mcu_frame(uint8_t *data, uint16_t len); /** * @brief 进入配对/配网模式 @@ -49,6 +51,11 @@ void usr_le_send_mcu_frame(uint8_t *data, uint16_t len); */ void usr_goto_pair_mode(void); +/** + * @brief 断开 BLE 连接并停止广播(复位/重启前调用) + */ +void usr_ble_disconnect_and_stop_adv(void); + /** * @brief 清除配对信息但不复位(paired_flag→0) */ @@ -65,6 +72,12 @@ void usr_clear_pair_info(void); */ uint8_t usr_get_pair_flag(void); +/** + * @brief GATT 是否已允许发数(主机已写 CCC) + * @return 1=可 notify/indicate,0=刚连上还不能发 + */ +uint8_t usr_ble_is_notify_ready(void); + /** * @brief 启动应用侧 OTA 流程 */ 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 1afebb2..247283d 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 @@ -17,6 +17,7 @@ #include "bleproto_api.h" #include "ble_proto.h" #include "mcu_frame.h" +#include "usr_le_api.h" #define LOG_TAG_CONST APP #define LOG_TAG "[APP]" #define LOG_ERROR_ENABLE @@ -50,6 +51,7 @@ 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); @@ -113,6 +115,12 @@ void usr_data_ana(bleproto_appdata_desc_t *desc) 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; @@ -479,11 +487,12 @@ void usr_run_cmd_code_v2(ble_proto_packet_t *runcmd_par, u8 usr_msg_id) * @brief 将 MCU 协议帧封装为 bleproto V2 后通过 BLE 发送 * @param data 55AA 完整帧 * @param len 帧长度 - * @note 主动上报(0x07/0x34/0x38) → REPORTCMD_V2;其余 → RUNCMD_V2 应答 + * @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 未就绪/组包失败 */ -void usr_le_send_mcu_frame(u8 *data, u16 len) +uint8_t usr_le_send_mcu_frame(u8 *data, u16 len) { ble_proto_packet_t pkt; bleproto_appdata_header_t hdr; @@ -497,16 +506,26 @@ void usr_le_send_mcu_frame(u8 *data, u16 len) if (!data || len < 5) { printf("usr_le_send_mcu_frame invalid param, len=%d\n", len); - return; + return 0; } if (len > MCU_FRAME_MAX_LEN) { printf("usr_le_send_mcu_frame frame too long %u\n", len); - return; + return 0; + } + /* 已配网/已连接不等于能发:主机写完 CCC 才允许 notify */ + if (!usr_ble_is_notify_ready()) + { + printf("usr_le_send_mcu_frame wait CCC cmd=0x%02X\n", data[4]); + return 0; } mcu_cmd = data[4]; - is_active_send = (mcu_cmd == 0x07) || (mcu_cmd == 0x34) || (mcu_cmd == 0x38); + /* 主动上报走 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); memset(&pkt, 0, sizeof(pkt)); memset(&hdr, 0, sizeof(hdr)); @@ -517,6 +536,7 @@ void usr_le_send_mcu_frame(u8 *data, u16 len) if (is_active_send) { + /* 设备→网关主动上报,等网关 ACK,不带 runcmd msgid */ hdr.msgtype = BLEPROTO_APPDATA_MSGTYPE_REQ; hdr.msgid = 0; hdr.serviceid = E_BLEPROTO_SERVICE_ID_REPORTCMD_V2; @@ -526,6 +546,7 @@ void usr_le_send_mcu_frame(u8 *data, u16 len) } else { + /* APP 下发的应答:msgid 必须对上最近一次 RUNCMD,网关才认 */ hdr.msgtype = BLEPROTO_APPDATA_MSGTYPE_RSP; hdr.msgid = s_last_runcmd_msgid; hdr.serviceid = E_BLEPROTO_SERVICE_ID_RUNCMD_V2; @@ -543,16 +564,18 @@ void usr_le_send_mcu_frame(u8 *data, u16 len) if (plen <= 0) { printf("usr_le_send_mcu_frame pack fail %d\n", plen); - return; + return 0; } elen = bleproto_appdata_wrap_raw(txbuf, sizeof(txbuf), &hdr, v2_buf, (uint16_t)plen); printf("usr_le_send_mcu_frame cmd=0x%02X active=%d encode_len=%d msgid=%d\n", mcu_cmd, is_active_send, elen, hdr.msgid); - if (elen > 0) + if (elen <= 0) { - usr_ble_send_data(txbuf, elen); - printf("send data:"); - printf_buf(txbuf, elen); + return 0; } + usr_ble_send_data(txbuf, elen); + printf("send data:"); + printf_buf(txbuf, elen); + return 1; } 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 c2797d7..73e05c4 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 @@ -2,22 +2,26 @@ * @file mcu_business.c * @brief 业务门面实现 * @author cyWu <1917507415@qq.com> - * @date 2026.08.12 - * @version V1.0.0 + * @date 2026.08.13 + * @version V1.1.0 * @history * - V1.0.0, 2026.08.12, cyWu, 取代原 mcu_link,编排三个业务子模块 + * - V1.1.0, 2026.08.13, cyWu, 编排 P1 管理/对时/版本 ******************************************************************************/ #include "system/includes.h" #include "app_main.h" #include "mcu_business.h" -#include "mcu_identity.h" -#include "mcu_pairing.h" -#include "mcu_link_status.h" #include "mcu_cmd.h" +#include "mcu_identity.h" +#include "mcu_link_status.h" +#include "mcu_mgmt.h" +#include "mcu_pairing.h" +#include "mcu_time.h" +#include "mcu_version.h" -#define LOG_TAG_CONST APP -#define LOG_TAG "[MCU_BIZ]" +#define LOG_TAG_CONST APP +#define LOG_TAG "[MCU_BIZ]" #define LOG_ERROR_ENABLE #define LOG_INFO_ENABLE #include "debug.h" @@ -32,6 +36,9 @@ void mcu_business_init(void) mcu_identity_init(); mcu_pairing_init(); mcu_link_status_init(); + mcu_mgmt_init(); + mcu_time_init(); + mcu_version_init(); usr_var.usr_goto_pair = 0; usr_var.usr_adv_flag = 0; @@ -45,6 +52,9 @@ void mcu_business_periodic(void) mcu_identity_periodic(); mcu_link_status_periodic(); mcu_pairing_periodic(); + mcu_mgmt_periodic(); + mcu_time_periodic(); + mcu_version_periodic(); } /** @@ -92,8 +102,10 @@ void mcu_business_on_paired(void) { usr_var.usr_goto_pair = 0; mcu_pairing_stop(); - mcu_link_status_set_phase(MCU_WS_CONFIGURED); - log_info("paired -> CONFIGURED"); + /* 三元组已写入 VM,但 GATT 未必可发。退出 PAIRING, + * CONFIGURED 交给 mcu_link_status_periodic:连接+CCC 才置位。 */ + mcu_link_status_set_phase(MCU_WS_RECONNECTING); + log_info("paired, wait link up"); } /** @@ -103,7 +115,76 @@ void mcu_business_on_paired(void) */ void mcu_business_on_pair_cleared(void) { - if (mcu_link_status_get_phase() != MCU_WS_PAIRING) { + /* 正在走 0x10 配网:清配对表是配网流程的一部分,过程态保持 PAIRING。 + * 其它情况(单纯清空配对)才回到未配网空闲。 */ + if (mcu_link_status_get_phase() != MCU_WS_PAIRING) + { mcu_link_status_set_phase(0); } } + +/** + * @brief 恢复出厂 + */ +void mcu_business_on_factory_reset(void) +{ + mcu_mgmt_on_factory_reset(); +} + +/** + * @brief 模组重启 + */ +void mcu_business_on_mod_reboot(void) +{ + mcu_mgmt_on_mod_reboot(); +} + +/** + * @brief 0x1B + * @param frame 完整帧 + * @param len 长度 + */ +void mcu_business_on_mcu_reboot_ack(const uint8_t *frame, uint16_t len) +{ + mcu_mgmt_on_mcu_reboot_ack(frame, len); +} + +/** + * @brief 组 0x1D + * @param payload 输出 + * @param plen 长度 + */ +void mcu_business_build_time_ack(uint8_t *payload, uint16_t *plen) +{ + mcu_time_build_ack(payload, plen); +} + +/** + * @brief 网络时间 + * @param unix_s UTC Unix 时间戳(秒) + * @param gmtoff_s 时区偏移 + */ +void mcu_business_on_network_time(uint64_t unix_s, int32_t gmtoff_s) +{ + mcu_time_set_from_gateway(unix_s, gmtoff_s); +} + +/** + * @brief UART 0x41 + * @param frame 完整帧 + * @param len 长度 + */ +void mcu_business_on_mcu_info_ack(const uint8_t *frame, uint16_t len) +{ + mcu_version_on_mcu_info_ack(frame, len); +} + +/** + * @brief 网关 ACK + * @param success 成功 + * @param mcu_report_consumed MCU 0x07 已消费 + */ +void mcu_business_on_gateway_ack(uint8_t success, uint8_t mcu_report_consumed) +{ + mcu_version_on_gateway_ack(success, mcu_report_consumed); +} 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 cad777f..4f10464 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 @@ -1,12 +1,13 @@ /****************************************************************************** * @file mcu_business.h - * @brief 业务门面:协调身份 / 配网 / 连接状态三个业务子模块 + * @brief 业务门面:协调身份 / 配网 / 连接 / 管理 / 对时 / 版本 * @author cyWu <1917507415@qq.com> - * @date 2026.08.12 - * @version V1.0.0 + * @date 2026.08.13 + * @version V1.1.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 管理/对时/版本子模块 ******************************************************************************/ #ifndef __MCU_BUSINESS_H__ @@ -20,16 +21,19 @@ /* mcu_identity.c —— 身份获取 */ /* mcu_pairing.c —— 配网请求 / 超时 */ /* mcu_link_status.c —— 连接状态位图(Bit0/1/2) */ +/* mcu_mgmt.c —— 出厂复位 / 重启 / 通知 MCU 重启 */ +/* mcu_time.c —— 对时(写 RTC / 0x1D 读回) */ +/* mcu_version.c —— UART 0x40/0x41 与 SOC 0x41 上报 */ /* 须在协议任务(mcu_protocol_task)单一上下文中调用。 */ /*============================================================================*/ /** - * @brief 初始化业务层(身份 + 配网 + 连接状态) + * @brief 初始化业务层 */ void mcu_business_init(void); /** - * @brief 周期处理:依次驱动身份 / 连接状态 / 配网三个子业务 + * @brief 周期处理:依次驱动各子业务 */ void mcu_business_periodic(void); @@ -69,4 +73,50 @@ void mcu_business_on_paired(void); */ void mcu_business_on_pair_cleared(void); +/** + * @brief MCU 请求恢复出厂(协议层已 ACK) + */ +void mcu_business_on_factory_reset(void); + +/** + * @brief MCU 请求模组重启(协议层已 ACK) + */ +void mcu_business_on_mod_reboot(void); + +/** + * @brief 处理 UART 0x1B + * @param frame 完整帧 + * @param len 长度 + */ +void mcu_business_on_mcu_reboot_ack(const uint8_t *frame, uint16_t len); + +/** + * @brief 组 0x1D 载荷 + * @param payload 输出缓冲 + * @param plen 输出长度 + */ +void mcu_business_build_time_ack(uint8_t *payload, uint16_t *plen); + +/** + * @brief 写入网关网络时间 + * @param unix_s UTC Unix 时间戳(秒) + * @param gmtoff_s 时区偏移秒 + * @note 网关从子设备连上开始计时,每 12 小时同步一次 + */ +void mcu_business_on_network_time(uint64_t unix_s, int32_t gmtoff_s); + +/** + * @brief 处理 UART 0x41(仅本地解析) + * @param frame 完整帧 + * @param len 长度 + */ +void mcu_business_on_mcu_info_ack(const uint8_t *frame, uint16_t len); + +/** + * @brief 网关 REPORTCMD_V2 应答 + * @param success 1=成功 + * @param mcu_report_consumed 1=已按 MCU 0x07 回了 0x08 + */ +void mcu_business_on_gateway_ack(uint8_t success, uint8_t mcu_report_consumed); + #endif /* __MCU_BUSINESS_H__ */ diff --git a/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/business/mcu_identity.c b/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/business/mcu_identity.c index 30dda8b..c89e2b6 100644 --- a/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/business/mcu_identity.c +++ b/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/business/mcu_identity.c @@ -10,14 +10,14 @@ #include "system/includes.h" #include "mcu_identity.h" -#include "mcu_protocol.h" #include "mcu_cmd.h" +#include "mcu_protocol.h" #include "mcu_util.h" #include "usr_le_api.h" #include "usr_le_product.h" -#define LOG_TAG_CONST APP -#define LOG_TAG "[MCU_ID]" +#define LOG_TAG_CONST APP +#define LOG_TAG "[MCU_ID]" #define LOG_ERROR_ENABLE #define LOG_DEBUG_ENABLE #define LOG_INFO_ENABLE @@ -27,8 +27,8 @@ /* 私有宏 */ /*============================================================================*/ -#define MCU_IDENTITY_RETRY_MS (1000u) -#define MCU_IDENTITY_PAYLOAD_MIN (2 + 8 + 8 + 16 + 8) /* Ver+Cat+Prod+SN+Cap */ +#define MCU_IDENTITY_RETRY_MS (1000u) +#define MCU_IDENTITY_PAYLOAD_MIN (2 + 8 + 8 + 16 + 8) /* Ver+Cat+Prod+SN+Cap */ /*============================================================================*/ /* 公有变量:运行时可改写的产品身份(供 BLE 广播使用) */ @@ -42,8 +42,8 @@ uint8_t g_usr_le_pcode[USR_LE_PCODE_LEN]; /*============================================================================*/ typedef struct { - uint8_t ready; - uint32_t last_tx_ms; + uint8_t ready; /* 1=已解析 0x02,广播可用产品码 */ + uint32_t last_tx_ms; /* 最近一次发 0x01 的时刻 */ } McuIdentity_t; static McuIdentity_t s_identity; @@ -69,17 +69,22 @@ void mcu_identity_periodic(void) { uint32_t now; - if (s_identity.ready) { + /* 已经拿到身份,不再发 0x01 */ + if (s_identity.ready) + { return; } now = mcu_util_get_ms(); - if ((now - s_identity.last_tx_ms) < MCU_IDENTITY_RETRY_MS) { + /* 未就绪则每 1s 问一次 MCU,无限轮询,没有次数上限 */ + if ((now - s_identity.last_tx_ms) < MCU_IDENTITY_RETRY_MS) + { return; } s_identity.last_tx_ms = now; - if (mcu_protocol_send_cmd(MCU_CMD_GET_IDENTITY, NULL, 0) > 0) { + if (mcu_protocol_send_cmd(MCU_CMD_GET_IDENTITY, NULL, 0) > 0) + { log_debug("identity req"); } } @@ -103,14 +108,19 @@ void mcu_identity_on_ack(const uint8_t *frame, uint16_t len) { int i; - if (!frame || len < 7) { + if (!frame || len < 7) + { return; } - if (frame[6] != MCU_RESULT_OK) { + /* frame[6]=Result,MCU 拒绝则继续 periodic 里每秒重问 */ + if (frame[6] != MCU_RESULT_OK) + { log_error("identity ack result=0x%02X", frame[6]); return; } - if (len < (7 + MCU_IDENTITY_PAYLOAD_MIN + 1)) { + /* 7 字节头 + 身份载荷 + 1 字节校验 */ + if (len < (7 + MCU_IDENTITY_PAYLOAD_MIN + 1)) + { log_error("identity ack too short %u", len); return; } @@ -123,12 +133,14 @@ void mcu_identity_on_ack(const uint8_t *frame, uint16_t len) s_identity.ready = 1; printf("identity ok prodcode:"); - for (i = 0; i < USR_LE_PRODCODE_LEN; i++) { + for (i = 0; i < USR_LE_PRODCODE_LEN; i++) + { printf("%c", g_usr_le_prodcode[i]); } printf("\n"); printf("identity ok pcode:"); - for (i = 0; i < USR_LE_PCODE_LEN; i++) { + for (i = 0; i < USR_LE_PCODE_LEN; i++) + { printf("%c", g_usr_le_pcode[i]); } printf("\n"); 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 b086ed1..2315fa6 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 @@ -11,12 +11,12 @@ #include "system/includes.h" #include "app_main.h" #include "mcu_link_status.h" -#include "mcu_identity.h" #include "mcu_cmd.h" +#include "mcu_identity.h" #include "usr_le_api.h" -#define LOG_TAG_CONST APP -#define LOG_TAG "[MCU_LNK]" +#define LOG_TAG_CONST APP +#define LOG_TAG "[MCU_LNK]" #define LOG_ERROR_ENABLE #define LOG_DEBUG_ENABLE #define LOG_INFO_ENABLE @@ -33,8 +33,8 @@ extern u16 usr_get_conn_service(void); /*============================================================================*/ typedef struct { - uint8_t work_status; - uint8_t last_ble_conn; + uint8_t work_status; /* 0x14 用的整字节:Bit0/1/2 过程态 + Bit6 OTA */ + uint8_t last_ble_conn; /* 上一拍 BLE 是否已连接,用来检测边沿 */ } McuLinkStatus_t; static McuLinkStatus_t s_status; @@ -76,7 +76,8 @@ uint8_t mcu_link_status_get_phase(void) void mcu_link_status_set_phase(uint8_t phase) { s_status.work_status &= (uint8_t)~MCU_WS_LINK_MASK; - if (phase & MCU_WS_LINK_MASK) { + if (phase & MCU_WS_LINK_MASK) + { s_status.work_status |= (uint8_t)(phase & MCU_WS_LINK_MASK); } } @@ -87,9 +88,12 @@ void mcu_link_status_set_phase(uint8_t phase) */ void mcu_link_status_set_ota(uint8_t on) { - if (on) { + if (on) + { s_status.work_status |= MCU_WS_OTA_UPGRADING; - } else { + } + else + { s_status.work_status &= (uint8_t)~MCU_WS_OTA_UPGRADING; } } @@ -97,33 +101,46 @@ void mcu_link_status_set_ota(uint8_t on) /** * @brief 周期处理:轮询 BLE 连接,维护 Bit0/1/2,已配网断连时开回连广播 * @note - 配网中(phase==PAIRING)由配网业务维护,此处不覆盖 - * - 已配网:已连接→CONFIGURED,未连接→RECONNECTING + * - VM 已绑定:链路可发数(连接+CCC)→CONFIGURED,否则→RECONNECTING * - Bit3~5 预留固定 0,不在此处更新 */ void mcu_link_status_periodic(void) { - uint8_t conn = (usr_get_conn_service() != 0) ? 1 : 0; - uint8_t paired = usr_get_pair_flag(); + uint8_t conn = (usr_get_conn_service() != 0) ? 1 : 0; /* 1=BLE 已连接 */ + uint8_t paired = usr_get_pair_flag(); /* 1=VM 已绑定三元组 */ + uint8_t link_up; - if (conn != s_status.last_ble_conn) { + /* 只在连接状态发生变化时处理广播,避免每拍都重开广播 */ + if (conn != s_status.last_ble_conn) + { s_status.last_ble_conn = conn; - if (!conn && paired) { - /* 已配网断连:发回连广播(非绑定广播) */ + if (!conn && paired) + { + /* 已绑定后断连:发回连广播(usr_goto_pair=0 → 非绑定广播) */ usr_var.usr_goto_pair = 0; - if (mcu_identity_is_ready()) { + if (mcu_identity_is_ready()) + { usr_ble_adv_init(); } } } - if (mcu_link_status_get_phase() == MCU_WS_PAIRING) { - return; /* 配网流程中不切换到回连/已配网 */ + /* 配网中由 pairing 模块维护过程态,这里不要改成回连/已配网 */ + if (mcu_link_status_get_phase() == MCU_WS_PAIRING) + { + return; } - if (paired) { - mcu_link_status_set_phase(conn ? MCU_WS_CONFIGURED : MCU_WS_RECONNECTING); - } else { - mcu_link_status_set_phase(0); /* 未配网空闲 */ + /* VM 已绑定 ≠ 已配网可用。MCU 看到 CONFIGURED 才会上报 0x07, + * 必须等 GATT CCC 写完,整条链路才能发到网关。 */ + link_up = (uint8_t)(paired && conn && usr_ble_is_notify_ready()); + if (paired) + { + mcu_link_status_set_phase(link_up ? MCU_WS_CONFIGURED : MCU_WS_RECONNECTING); + } + else + { + mcu_link_status_set_phase(0); /* 未绑定空闲,Bit0/1/2 全 0 */ } } diff --git a/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/business/mcu_link_status.h b/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/business/mcu_link_status.h index a0be37c..26c319b 100644 --- a/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/business/mcu_link_status.h +++ b/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/business/mcu_link_status.h @@ -19,8 +19,9 @@ void mcu_link_status_init(void); /** - * @brief 周期处理:轮询 BLE 连接,维护 Bit0/1/2,已配网断连时触发回连广播 - * @note 配网中(phase==PAIRING)时不会被本函数覆盖 + * @brief 周期处理:轮询 BLE 连接/CCC,维护 Bit0/1/2,已绑定断连时触发回连广播 + * @note 配网中(phase==PAIRING)时不会被本函数覆盖; + * CONFIGURED 要求 VM 已绑定 + BLE 已连接 + CCC 已写 */ void mcu_link_status_periodic(void); diff --git a/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/business/mcu_mgmt.c b/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/business/mcu_mgmt.c new file mode 100644 index 0000000..7cb785c --- /dev/null +++ b/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/business/mcu_mgmt.c @@ -0,0 +1,198 @@ +/****************************************************************************** + * @file mcu_mgmt.c + * @brief 模组管理业务实现 + * @author cyWu <1917507415@qq.com> + * @date 2026.08.13 + * @version V1.0.0 + * @history + * - V1.0.0, 2026.08.13, cyWu, 首次发布 + ******************************************************************************/ + +#include "system/includes.h" +#include "asm/cpu.h" +#include "mcu_mgmt.h" +#include "mcu_cmd.h" +#include "mcu_protocol.h" +#include "mcu_util.h" +#include "usr_le_api.h" +#include "usr_rtc.h" + +#define LOG_TAG_CONST APP +#define LOG_TAG "[MCU_MGMT]" +#define LOG_ERROR_ENABLE +#define LOG_DEBUG_ENABLE +#define LOG_INFO_ENABLE +#include "debug.h" + +/*============================================================================*/ +/* 私有宏 */ +/*============================================================================*/ + +#define MCU_MGMT_RESET_DELAY_MS (600u) +#define MCU_MGMT_ACK_WAIT_MS (500u) +#define MCU_MGMT_MAX_RETRY (3u) + +typedef enum +{ + MCU_MGMT_RESET_NONE = 0, /* 无待执行复位 */ + MCU_MGMT_RESET_FACTORY, /* 恢复出厂后复位 */ + MCU_MGMT_RESET_REBOOT /* 普通重启 */ +} McuMgmtResetKind_t; + +/*============================================================================*/ +/* 私有类型/变量 */ +/*============================================================================*/ + +typedef struct { + McuMgmtResetKind_t reset_kind; /* 延迟复位类型,NONE 表示没有 */ + uint32_t reset_due_ms; /* 到点执行 cpu_reset */ + uint8_t notify_waiting; /* 1=已发 0x1A,等 MCU 0x1B */ + uint8_t notify_retry; /* 0x1A 已发送次数(含首次) */ + uint32_t notify_tx_ms; /* 最近一次 0x1A 发出时刻 */ +} McuMgmt_t; + +static McuMgmt_t s_mgmt; + +/*============================================================================*/ +/* 私有函数 */ +/*============================================================================*/ + +/** + * @brief 启动延迟复位 + * @param kind 复位类型 + */ +static void mcu_mgmt_arm_reset(McuMgmtResetKind_t kind) +{ + s_mgmt.reset_kind = kind; + s_mgmt.reset_due_ms = mcu_util_get_ms() + MCU_MGMT_RESET_DELAY_MS; +} + +/*============================================================================*/ +/* 公有函数 */ +/*============================================================================*/ + +/** + * @brief 初始化模组管理业务 + */ +void mcu_mgmt_init(void) +{ + memset(&s_mgmt, 0, sizeof(s_mgmt)); +} + +/** + * @brief 周期处理:0x1A 重试、延迟复位 + */ +void mcu_mgmt_periodic(void) +{ + uint32_t now = mcu_util_get_ms(); + + /* 已安排延迟复位:到点 cpu_reset,期间不再发 0x1A */ + if (s_mgmt.reset_kind != MCU_MGMT_RESET_NONE) + { + if ((int32_t)(now - s_mgmt.reset_due_ms) >= 0) + { + log_info("cpu_reset kind=%u", (unsigned)s_mgmt.reset_kind); + cpu_reset(); + } + return; + } + + /* 没在等 0x1B,无事可做 */ + if (!s_mgmt.notify_waiting) + { + return; + } + /* 500ms 窗口内继续等 MCU ACK */ + if ((now - s_mgmt.notify_tx_ms) <= MCU_MGMT_ACK_WAIT_MS) + { + return; + } + if (s_mgmt.notify_retry >= MCU_MGMT_MAX_RETRY) + { + log_error("notify MCU reboot give up"); + s_mgmt.notify_waiting = 0; + return; + } + /* UART 被心跳/0x14 占用则下周期再试 */ + if (mcu_protocol_is_ble_pending()) + { + return; + } + if (mcu_protocol_send_cmd_wait_ack(MCU_CMD_MCU_REBOOT, NULL, 0, + MCU_CMD_MCU_REBOOT_ACK) > 0) + { + s_mgmt.notify_retry++; + s_mgmt.notify_tx_ms = now; + log_debug("notify MCU reboot retry %u", s_mgmt.notify_retry); + } +} + +/** + * @brief 恢复出厂:清配对后延迟复位 + */ +void mcu_mgmt_on_factory_reset(void) +{ + log_info("factory reset"); + usr_ble_disconnect_and_stop_adv(); + usr_clear_pair_info_noreset(); + usr_rtc_clear_tz(); /* 时区是上次配网对时留下的,出厂一并清掉 */ + mcu_mgmt_arm_reset(MCU_MGMT_RESET_FACTORY); +} + +/** + * @brief 模组重启:保留配对后延迟复位 + */ +void mcu_mgmt_on_mod_reboot(void) +{ + log_info("mod reboot"); + usr_ble_disconnect_and_stop_adv(); + mcu_mgmt_arm_reset(MCU_MGMT_RESET_REBOOT); +} + +/** + * @brief 发送 0x1A 通知 MCU 重启 + */ +void mcu_mgmt_notify_mcu_reboot(void) +{ + /* 已经在等 0x1B,或马上要复位,不要重复发 0x1A */ + if (s_mgmt.notify_waiting || s_mgmt.reset_kind != MCU_MGMT_RESET_NONE) + { + log_error("notify MCU reboot busy"); + return; + } + if (mcu_protocol_is_ble_pending()) + { + log_error("notify MCU reboot uart busy"); + return; + } + if (mcu_protocol_send_cmd_wait_ack(MCU_CMD_MCU_REBOOT, NULL, 0, + MCU_CMD_MCU_REBOOT_ACK) <= 0) + { + log_error("notify MCU reboot tx fail"); + return; + } + s_mgmt.notify_waiting = 1; + s_mgmt.notify_retry = 1; + s_mgmt.notify_tx_ms = mcu_util_get_ms(); + log_info("notify MCU reboot tx"); +} + +/** + * @brief 处理 0x1B + * @param frame 完整帧 + * @param len 长度 + */ +void mcu_mgmt_on_mcu_reboot_ack(const uint8_t *frame, uint16_t len) +{ + /* 不是自己发出去的 0x1A 对应的应答,忽略 */ + if (!s_mgmt.notify_waiting) + { + return; + } + s_mgmt.notify_waiting = 0; + if (!frame || len < 7) + { + return; + } + log_info("MCU reboot ack result=0x%02X", frame[6]); +} diff --git a/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/business/mcu_mgmt.h b/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/business/mcu_mgmt.h new file mode 100644 index 0000000..497f3a7 --- /dev/null +++ b/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/business/mcu_mgmt.h @@ -0,0 +1,52 @@ +/****************************************************************************** + * @file mcu_mgmt.h + * @brief 模组管理业务:出厂复位 / 模组重启 / 通知 MCU 重启 + * @author cyWu <1917507415@qq.com> + * @date 2026.08.13 + * @version V1.0.0 + * @history + * - V1.0.0, 2026.08.13, cyWu, 首次发布(P1:0x16/0x18/0x1A) + ******************************************************************************/ + +#ifndef __MCU_MGMT_H__ +#define __MCU_MGMT_H__ + +#include + +/** + * @brief 初始化模组管理业务 + */ +void mcu_mgmt_init(void); + +/** + * @brief 周期处理:0x1A 重试、延迟复位倒计时 + * @note 仅在协议任务上下文调用 + */ +void mcu_mgmt_periodic(void); + +/** + * @brief MCU 请求恢复出厂(协议层已回 0x17 OK) + * @note 清配对、清时区 → 断 BLE/停广播 → 600ms 后复位;起来后不广播 + */ +void mcu_mgmt_on_factory_reset(void); + +/** + * @brief MCU 请求模组重启(协议层已回 0x19 OK) + * @note 保留配对 → 断 BLE/停广播 → 600ms 后复位 + */ +void mcu_mgmt_on_mod_reboot(void); + +/** + * @brief 主动通知 MCU 重启(发 0x1A,等 0x1B) + * @note P1 无自动触发,供后续 OTA 等调用;busy 时忽略 + */ +void mcu_mgmt_notify_mcu_reboot(void); + +/** + * @brief 处理 MCU 对 0x1A 的应答 0x1B + * @param frame 完整帧 + * @param len 长度 + */ +void mcu_mgmt_on_mcu_reboot_ack(const uint8_t *frame, uint16_t len); + +#endif /* __MCU_MGMT_H__ */ diff --git a/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/business/mcu_pairing.c b/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/business/mcu_pairing.c index 6a6bae9..d4ff011 100644 --- a/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/business/mcu_pairing.c +++ b/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/business/mcu_pairing.c @@ -83,14 +83,17 @@ void mcu_pairing_periodic(void) uint32_t now; uint32_t timeout_ms; + /* 当前不是配网过程态,不管超时 */ if (mcu_link_status_get_phase() != MCU_WS_PAIRING) { return; } + /* timeout_s=0 表示 MCU 没给超时,一直等到配上或被 stop */ if (s_pairing.timeout_s == 0) { return; } + /* 已经配上:切态由 mcu_business_on_paired 做,这里别抢 */ if (usr_get_pair_flag()) { - return; /* 已配网成功,交由 mcu_business_on_paired 处理切态 */ + return; } now = mcu_util_get_ms(); @@ -99,6 +102,7 @@ void mcu_pairing_periodic(void) return; } + /* 超时仍未配上:停绑定广播,过程态回到未配网空闲 */ log_info("pairing timeout, stop adv"); usr_var.usr_goto_pair = 0; s_pairing.timeout_s = 0; diff --git a/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/business/mcu_time.c b/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/business/mcu_time.c new file mode 100644 index 0000000..a58722d --- /dev/null +++ b/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/business/mcu_time.c @@ -0,0 +1,107 @@ +/****************************************************************************** + * @file mcu_time.c + * @brief 对时业务:网关时间写入 RTC,0x1D 从 RTC 读回 + * @author cyWu <1917507415@qq.com> + * @date 2026.08.14 + * @version V1.1.0 + * @history + * - V1.0.0, 2026.08.13, cyWu, 首次发布(RAM 缓存) + * - V1.1.0, 2026.08.14, cyWu, 改为写硬件 RTC,周期读回 UTC + ******************************************************************************/ + +#include "system/includes.h" +#include "mcu_time.h" +#include "mcu_cmd.h" +#include "mcu_util.h" +#include "usr_rtc.h" + +#define LOG_TAG_CONST APP +#define LOG_TAG "[MCU_TIME]" +#define LOG_ERROR_ENABLE +#define LOG_DEBUG_ENABLE +#define LOG_INFO_ENABLE +#include "debug.h" + +/*============================================================================*/ +/* 公有函数 */ +/*============================================================================*/ + +/** + * @brief 初始化对时业务(RTC 已由 app_main 打开则直接返回 OK) + */ +void mcu_time_init(void) +{ + if (usr_rtc_init() != USR_RTC_OK) + { + log_error("rtc not ready"); + } +} + +/** + * @brief 周期读 RTC,刷新 UTC 给业务 + */ +void mcu_time_periodic(void) +{ + usr_rtc_run(); +} + +/** + * @brief 网关 DEVICEINFO 对时:UTC + 时区写入 RTC + * @param unix_s UTC Unix 秒 + * @param gmtoff_s 时区偏移秒 + * @note 网关从子设备连上开始计时,每 12 小时同步一次 + */ +void mcu_time_set_from_gateway(uint64_t unix_s, int32_t gmtoff_s) +{ + UsrRtc_Ret_t ret; + + ret = usr_rtc_set_from_unix(unix_s, gmtoff_s); + if (ret != USR_RTC_OK) + { + log_debug("set rtc fail ret=%d", (int)ret); + } +} + +/** + * @brief 组 0x1D 载荷:本地日历 + UTC + * @param payload 输出 + * @param plen 输出长度 + */ +void mcu_time_build_ack(uint8_t *payload, uint16_t *plen) +{ + struct sys_time t; + uint32_t unix_utc; + + if (!payload || !plen) + { + return; + } + + payload[0] = MCU_RESULT_OK; + /* 年份不在 2025~2098 或还没有时区:载荷后面全 0 */ + if (!usr_rtc_is_valid() || (usr_rtc_read(&t) != USR_RTC_OK)) + { + memset(&payload[1], 0, 12); + *plen = MCU_TIME_ACK_LEN; + return; + } + + unix_utc = usr_rtc_get_unix_utc(); + mcu_util_write_u16_be(&payload[1], t.year); + payload[3] = t.month; + payload[4] = t.day; + payload[5] = t.hour; + payload[6] = t.min; + payload[7] = t.sec; + mcu_util_write_u32_be(&payload[8], unix_utc); + *plen = MCU_TIME_ACK_LEN; +} + +/** + * @brief 是否已有可用 RTC 时间 + * @return 1/0 + */ +uint8_t mcu_time_is_valid(void) +{ + return usr_rtc_is_valid(); +} diff --git a/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/business/mcu_time.h b/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/business/mcu_time.h new file mode 100644 index 0000000..8346a63 --- /dev/null +++ b/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/business/mcu_time.h @@ -0,0 +1,53 @@ +/****************************************************************************** + * @file mcu_time.h + * @brief 对时业务:网关时间写入 RTC,应答 MCU 0x1C/0x1D + * @author cyWu <1917507415@qq.com> + * @date 2026.08.14 + * @version V1.1.0 + * @history + * - V1.0.0, 2026.08.13, cyWu, 首次发布(P1:0x1C/0x1D) + * - V1.1.0, 2026.08.14, cyWu, 对接 usr_rtc 硬件 RTC + ******************************************************************************/ + +#ifndef __MCU_TIME_H__ +#define __MCU_TIME_H__ + +#include + +/** 0x1D 载荷长度:Result(1)+年(2)+月日时分秒(5)+Unix(4) */ +#define MCU_TIME_ACK_LEN (13u) + +/** + * @brief 初始化对时业务(确保 RTC 已打开) + */ +void mcu_time_init(void); + +/** + * @brief 周期读 RTC,刷新 UTC + */ +void mcu_time_periodic(void); + +/** + * @brief 写入网关 DEVICEINFO 下发的网络时间 + * @param unix_s UTC Unix 时间戳(秒);0 视为无效 + * @param gmtoff_s 时区偏移(秒),如东八区 28800 + * @note 网关从子设备连上开始计时,每 12 小时同步一次; + * 模组收到后转为本地日历 write_sys_time,时区存 VM + */ +void mcu_time_set_from_gateway(uint64_t unix_s, int32_t gmtoff_s); + +/** + * @brief 组 0x1D 载荷(Result 起,不含帧头/校验) + * @param payload 输出缓冲,至少 MCU_TIME_ACK_LEN + * @param plen 输出载荷长度 + * @note 无有效时间时 Result=OK,日历与 Unix 全 0 + */ +void mcu_time_build_ack(uint8_t *payload, uint16_t *plen); + +/** + * @brief RTC 时间是否可用(年份合法且已有时区) + * @return 1=有效,0=无 + */ +uint8_t mcu_time_is_valid(void); + +#endif /* __MCU_TIME_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 new file mode 100644 index 0000000..37a45ab --- /dev/null +++ b/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/business/mcu_version.c @@ -0,0 +1,331 @@ +/****************************************************************************** + * @file mcu_version.c + * @brief 版本业务实现:拉 MCU 信息并按 SOC 0x41 上报网关 + * @author cyWu <1917507415@qq.com> + * @date 2026.08.13 + * @version V1.0.0 + * @history + * - V1.0.0, 2026.08.13, cyWu, 首次发布 + ******************************************************************************/ + +#include "system/includes.h" +#include "mcu_version.h" +#include "mcu_cmd.h" +#include "mcu_frame.h" +#include "mcu_protocol.h" +#include "mcu_util.h" +#include "usr_le_api.h" + +#define LOG_TAG_CONST APP +#define LOG_TAG "[MCU_VER]" +#define LOG_ERROR_ENABLE +#define LOG_DEBUG_ENABLE +#define LOG_INFO_ENABLE +#include "debug.h" + +/*============================================================================*/ +/* 外部符号 */ +/*============================================================================*/ + +extern u16 usr_get_conn_service(void); + +/*============================================================================*/ +/* 私有宏 */ +/*============================================================================*/ + +#define MCU_VER_ACK_WAIT_MS (500u) /* UART 0x40 等 MCU 应答 */ +#define MCU_VER_REPORT_WAIT_MS (2000u) /* SOC 0x41 走网关,等 ACK 需更长 */ +#define MCU_VER_MAX_RETRY (3u) +#define MCU_VER_UART_ACK_MIN_LEN (14u) /* 55AA+Len+Cmd+Sn+Result+MCU3+HW3+Sum */ +#define MCU_VER_SOC_PAYLOAD_LEN (10u) /* Result+BLE3+MCU3+HW3 */ + +typedef enum +{ + MCU_VER_STATE_IDLE = 0, /* 空闲:无进行中的拉取/上报 */ + MCU_VER_STATE_FETCH, /* 正在 UART 发 0x40,等 MCU 回 0x41 */ + MCU_VER_STATE_REPORT /* 正在 BLE 发 SOC 0x41,等网关 ACK */ +} McuVerState_t; + +/*============================================================================*/ +/* 私有类型/变量 */ +/*============================================================================*/ + +typedef struct +{ + McuVerState_t state; /* 当前阶段 */ + uint8_t cached; /* 1=已从 MCU 拿到版本,RAM 里有 mcu_ver/hw_ver */ + uint8_t reported; /* 1=SOC 0x41 已被网关成功 ACK,本连接不再报 */ + uint8_t last_conn; /* 上一拍 BLE 是否已连接,仅作记录 */ + uint8_t give_up; /* 1=本轮 3 次已用尽,等断连后再试,避免连着打 */ + uint8_t fetch_retry; /* 0x40 已发送次数(含首次) */ + uint8_t report_retry; /* SOC 0x41 已发送次数(含首次) */ + uint8_t report_sn; /* SOC 0x41 序列号,每次上报递增 */ + uint32_t last_tx_ms; /* 最近一次 0x40 或 SOC 0x41 发出时刻 */ + uint8_t mcu_ver[3]; /* MCU 软件版本 Vx.x.x */ + uint8_t hw_ver[3]; /* 硬件版本 Vx.x.x */ +} McuVersion_t; + +static McuVersion_t s_ver; + +/*============================================================================*/ +/* 私有函数 */ +/*============================================================================*/ + +/** + * @brief 发送 UART 0x40 + * @return 1=已发出,0=忙或失败 + */ +static uint8_t mcu_version_tx_fetch(void) +{ + /* UART 正在等别的命令 ACK(心跳/0x14 等),先不抢 */ + if (mcu_protocol_is_ble_pending()) + { + return 0; + } + /* 发 0x40,期望 MCU 回 0x41 */ + if (mcu_protocol_send_cmd_wait_ack(MCU_CMD_GET_MCU_INFO, NULL, 0, + MCU_CMD_GET_MCU_INFO_ACK) <= 0) + { + return 0; + } + s_ver.last_tx_ms = mcu_util_get_ms(); + s_ver.fetch_retry++; + log_debug("0x40 fetch retry=%u", s_ver.fetch_retry); + return 1; +} + +/** + * @brief 组 SOC 0x41 并走 cmd 100 上报 + * @return 1=已发出,0=失败 + */ +static uint8_t mcu_version_tx_report(void) +{ + uint8_t payload[MCU_VER_SOC_PAYLOAD_LEN]; + uint8_t frame[MCU_FRAME_MAX_LEN]; + int32_t total; + + /* MCU 0x07 正在等网关 ACK,通道被占用,版本上报让路 */ + if (mcu_protocol_is_mcu_report_waiting()) + { + return 0; + } + /* 刚连上、主机还没写 CCC:发了也会 app_send_fail,不占重试次数 */ + if (!usr_ble_is_notify_ready()) + { + log_debug("SOC 0x41 wait CCC"); + return 0; + } + + payload[0] = MCU_RESULT_OK; + payload[1] = MCU_BLE_FW_VER_MAJOR; + payload[2] = MCU_BLE_FW_VER_MINOR; + payload[3] = MCU_BLE_FW_VER_PATCH; + memcpy(&payload[4], s_ver.mcu_ver, 3); + memcpy(&payload[7], s_ver.hw_ver, 3); + + s_ver.report_sn++; + total = mcu_frame_pack(frame, sizeof(frame), MCU_CMD_GET_MCU_INFO_ACK, + s_ver.report_sn, payload, MCU_VER_SOC_PAYLOAD_LEN); + if (total <= 0) + { + return 0; + } + + if (usr_le_send_mcu_frame(frame, (uint16_t)total) == 0) + { + return 0; + } + s_ver.last_tx_ms = mcu_util_get_ms(); + s_ver.report_retry++; + log_info("SOC 0x41 report sn=%u retry=%u ble=%u.%u.%u mcu=%u.%u.%u hw=%u.%u.%u", + s_ver.report_sn, s_ver.report_retry, + payload[1], payload[2], payload[3], + s_ver.mcu_ver[0], s_ver.mcu_ver[1], s_ver.mcu_ver[2], + s_ver.hw_ver[0], s_ver.hw_ver[1], s_ver.hw_ver[2]); + return 1; +} + +/** + * @brief 进入拉取或上报(缓存空则拉,有缓存未上报则报) + */ +static void mcu_version_start_session(void) +{ + s_ver.fetch_retry = 0; + s_ver.report_retry = 0; + if (!s_ver.cached) + { + /* 还没有 MCU 版本:先 UART 拉 0x40 */ + s_ver.state = MCU_VER_STATE_FETCH; + (void)mcu_version_tx_fetch(); + return; + } + if (!s_ver.reported) + { + /* 已有缓存但网关还没 ACK:直接报 SOC 0x41 */ + s_ver.state = MCU_VER_STATE_REPORT; + (void)mcu_version_tx_report(); + return; + } + /* 已拉过也已报过,保持空闲 */ + s_ver.state = MCU_VER_STATE_IDLE; +} + +/*============================================================================*/ +/* 公有函数 */ +/*============================================================================*/ + +/** + * @brief 初始化版本业务 + */ +void mcu_version_init(void) +{ + memset(&s_ver, 0, sizeof(s_ver)); +} + +/** + * @brief 周期:已配网且已连接时拉版本/上报;超时重试 + * @note 流程:BLE 已连 + 已配网 → 拉 MCU 0x40 → 报网关 SOC 0x41 → 等 ACK + */ +void mcu_version_periodic(void) +{ + uint8_t conn = (usr_get_conn_service() != 0) ? 1 : 0; /* 1=BLE 已连接 */ + uint8_t paired = usr_get_pair_flag(); /* 1=已配网/已绑定 */ + uint32_t now = mcu_util_get_ms(); + + s_ver.last_conn = conn; + + /* BLE 断了:清 give_up,下次连上允许再试;进行中的拉取/上报也停掉 */ + if (!conn) + { + s_ver.give_up = 0; + if (s_ver.state != MCU_VER_STATE_IDLE) + { + s_ver.state = MCU_VER_STATE_IDLE; + } + return; + } + + /* 已连接但未配网:版本要报给网关,没绑定就不上报。 + * give_up=1:本轮 3 次已失败,连着重打没用,等下次断连再清标志。 + * CCC 未就绪:UART 可以先拉 0x40,但 SOC 0x41 等主机使能通知后再发。 */ + if (!paired || s_ver.give_up) + { + return; + } + + /* 空闲且还有事没做完:没缓存就去拉,有缓存没上报就去报 */ + if (s_ver.state == MCU_VER_STATE_IDLE) + { + if (!s_ver.cached || !s_ver.reported) + { + mcu_version_start_session(); + } + return; + } + + /* 等 MCU 回 UART 0x41:500ms 窗口内不重发;满 3 次则本轮放弃 */ + if (s_ver.state == MCU_VER_STATE_FETCH) + { + if ((s_ver.fetch_retry > 0) && + ((now - s_ver.last_tx_ms) <= MCU_VER_ACK_WAIT_MS)) + { + return; + } + if (s_ver.fetch_retry >= MCU_VER_MAX_RETRY) + { + log_error("0x40 give up, wait next connect"); + s_ver.state = MCU_VER_STATE_IDLE; + s_ver.give_up = 1; + return; + } + (void)mcu_version_tx_fetch(); + return; + } + + /* 等网关 ACK:2s 窗口内不重发(走网关比 UART 慢);满 3 次则本轮放弃 */ + if (s_ver.state == MCU_VER_STATE_REPORT) + { + if ((s_ver.report_retry > 0) && + ((now - s_ver.last_tx_ms) <= MCU_VER_REPORT_WAIT_MS)) + { + return; + } + if (s_ver.report_retry >= MCU_VER_MAX_RETRY) + { + log_error("SOC 0x41 give up, wait next connect"); + s_ver.state = MCU_VER_STATE_IDLE; + s_ver.give_up = 1; + return; + } + (void)mcu_version_tx_report(); + } +} + +/** + * @brief 处理 UART 0x41 + * @param frame 完整帧 + * @param len 长度 + */ +void mcu_version_on_mcu_info_ack(const uint8_t *frame, uint16_t len) +{ + if (!frame || len < MCU_VER_UART_ACK_MIN_LEN) + { + log_error("0x41 too short %u", len); + return; + } + /* frame[6] 是 Result,非 0 表示 MCU 拒绝,不缓存、不上报 */ + if (frame[6] != MCU_RESULT_OK) + { + log_error("0x41 result=0x%02X", frame[6]); + return; + } + + memcpy(s_ver.mcu_ver, &frame[7], 3); + memcpy(s_ver.hw_ver, &frame[10], 3); + s_ver.cached = 1; + s_ver.reported = 0; /* 新版本尚未报给网关 */ + s_ver.report_retry = 0; + s_ver.state = MCU_VER_STATE_REPORT; + log_info("MCU ver %u.%u.%u hw %u.%u.%u", + s_ver.mcu_ver[0], s_ver.mcu_ver[1], s_ver.mcu_ver[2], + s_ver.hw_ver[0], s_ver.hw_ver[1], s_ver.hw_ver[2]); + (void)mcu_version_tx_report(); +} + +/** + * @brief 网关 ACK:MCU 0x07 优先消费,版本上报绝不回 MCU 0x08 + * @param success 成功 + * @param mcu_report_consumed 已被 MCU 上报占用 + */ +void mcu_version_on_gateway_ack(uint8_t success, uint8_t mcu_report_consumed) +{ + /* 当前不是在等版本上报的 ACK,忽略 */ + if (s_ver.state != MCU_VER_STATE_REPORT) + { + return; + } + /* 这条 ACK 已经给 MCU 0x07 回过 0x08 了,不是给版本的,版本继续等 */ + if (mcu_report_consumed) + { + log_debug("gateway ack used by MCU 0x07, version waits"); + return; + } + /* 网关明确失败:不置 reported,周期函数超时后会重发 SOC 0x41 */ + if (!success) + { + log_error("SOC 0x41 gateway ack fail"); + return; + } + s_ver.reported = 1; + s_ver.state = MCU_VER_STATE_IDLE; + log_info("SOC 0x41 report ok"); +} + +/** + * @brief 是否已缓存 MCU 版本 + * @return 1/0 + */ +uint8_t mcu_version_is_cached(void) +{ + return s_ver.cached; +} diff --git a/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/business/mcu_version.h b/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/business/mcu_version.h new file mode 100644 index 0000000..a89b462 --- /dev/null +++ b/JBao_JL7016_MOD_FW/apps/usr_mcu_bridge/business/mcu_version.h @@ -0,0 +1,52 @@ +/****************************************************************************** + * @file mcu_version.h + * @brief 版本业务:UART 0x40/0x41 拉 MCU 信息,SOC 0x41 上报网关 + * @author cyWu <1917507415@qq.com> + * @date 2026.08.13 + * @version V1.0.0 + * @history + * - V1.0.0, 2026.08.13, cyWu, 首次发布(P1:0x40/0x41 + SOC 0x41) + ******************************************************************************/ + +#ifndef __MCU_VERSION_H__ +#define __MCU_VERSION_H__ + +#include + +/** BLE 模组软件版本 V1.0.0(三字节,对应显示 Vx.x.x) */ +#define MCU_BLE_FW_VER_MAJOR (1u) +#define MCU_BLE_FW_VER_MINOR (0u) +#define MCU_BLE_FW_VER_PATCH (0u) + +/** + * @brief 初始化版本业务(清空 RAM 缓存) + */ +void mcu_version_init(void); + +/** + * @brief 周期处理:已配网且已连接时拉 MCU 版本,成功后上报网关 + * @note 仅在协议任务上下文调用 + */ +void mcu_version_periodic(void); + +/** + * @brief 处理 UART 0x41(MCU 信息应答),只本地解析,不转发网关 + * @param frame 完整帧 + * @param len 长度 + */ +void mcu_version_on_mcu_info_ack(const uint8_t *frame, uint16_t len); + +/** + * @brief 网关对 REPORTCMD_V2 的应答 + * @param success 1=网关成功 ACK + * @param mcu_report_consumed 1=该 ACK 已按 MCU 0x07 消费(回了 0x08) + */ +void mcu_version_on_gateway_ack(uint8_t success, uint8_t mcu_report_consumed); + +/** + * @brief MCU 版本缓存是否就绪 + * @return 1=已缓存,0=无 + */ +uint8_t mcu_version_is_cached(void); + +#endif /* __MCU_VERSION_H__ */ 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 3facdc4..18c5e06 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 @@ -2,8 +2,8 @@ * @file mcu_bridge.c * @brief BLE+MCU 接入模块对外总入口实现(组合门面,仅转调) * @author cyWu <1917507415@qq.com> - * @date 2026.08.12 - * @version V2.0.0 + * @date 2026.08.13 + * @version V2.1.0 * @history * - V1.0.0, 2026.08.06, cyWu, 首次发布 * - V1.1.0, 2026.08.06, cyWu, 改为事件队列 + 独立任务驱动 @@ -12,6 +12,7 @@ * - V1.4.0, 2026.08.12, cyWu, 工作状态 Bit0/1/2 互斥 * - V2.0.0, 2026.08.12, cyWu, 拆分为 protocol/ + business/ 分层, * 本文件收敛为纯组合门面,不再持有任何状态 + * - V2.1.0, 2026.08.13, cyWu, P1 增加网络时间入口 ******************************************************************************/ #include "mcu_bridge.h" @@ -84,3 +85,11 @@ void mcu_bridge_on_gateway_report_ack(uint16_t cmd, { mcu_protocol_notify_gateway_ack(cmd, gateway_int_cnt, gateway_int0); } + +/** + * @brief 网关网络时间:经协议层事件队列串行化 + */ +void mcu_bridge_on_network_time(uint64_t unix_s, int32_t gmtoff_s) +{ + mcu_protocol_notify_network_time(unix_s, gmtoff_s); +} 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 7993176..af7793c 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 @@ -2,8 +2,8 @@ * @file mcu_bridge.h * @brief BLE+MCU 接入模块对外总入口(组合门面) * @author cyWu <1917507415@qq.com> - * @date 2026.08.12 - * @version V2.0.0 + * @date 2026.08.13 + * @version V2.1.0 * @history * - V1.0.0, 2026.08.06, cyWu, 首次发布 * - V1.1.0, 2026.08.06, cyWu, 内部改为事件队列 + 独立任务驱动 @@ -11,6 +11,7 @@ * - V2.0.0, 2026.08.12, cyWu, 整体重构为 protocol/ + business/ 分层: * 本文件不再包含任何实现逻辑,只做“组合根”,对外 API 保持不变, * 外部调用方(app_main.c / usr_le_adv.c / usr_le_recieve.c 等)无需改动 + * - V2.1.0, 2026.08.13, cyWu, P1 增加网络时间入口;业务子模块扩到管理/对时/版本 ******************************************************************************/ #ifndef __MCU_BRIDGE_H__ @@ -34,7 +35,10 @@ /* ├── mcu_identity.c/h 身份业务(0x01/0x02) */ /* ├── mcu_pairing.c/h 配网业务(0x10、超时) */ /* ├── mcu_link_status.c/h 连接状态业务(Bit0/1/2/6 位图) */ -/* └── mcu_business.c/h 业务门面:协调以上三者,供协议层调用 */ +/* ├── mcu_mgmt.c/h 模组管理(0x16/0x18/0x1A) */ +/* ├── mcu_time.c/h 对时(0x1C/0x1D,写入 usr_rtc) */ +/* ├── mcu_version.c/h MCU 信息 + SOC 0x41 上报 */ +/* └── mcu_business.c/h 业务门面:协调子模块,供协议层调用 */ /* */ /* 依赖方向(单向,禁止反向 include): */ /* mcu_bridge → protocol → business(子模块) → business(门面) */ @@ -110,4 +114,12 @@ void mcu_bridge_on_gateway_report_ack(uint16_t cmd, uint8_t gateway_int_cnt, int32_t gateway_int0); +/** + * @brief 网关 DEVICEINFO 中的网络时间(BLE 上下文可调,内部入队) + * @param unix_s UTC Unix 时间戳(秒) + * @param gmtoff_s 时区偏移(秒) + * @note 网关从子设备连上开始计时,每 12 小时同步一次 + */ +void mcu_bridge_on_network_time(uint64_t unix_s, int32_t gmtoff_s); + #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 3c4f07f..0e05f7a 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 @@ -24,8 +24,8 @@ /*============================================================================*/ #define MCU_WS_PAIRING (1u << 0) /**< 配网中(绑定广播) */ -#define MCU_WS_CONFIGURED (1u << 1) /**< 已配网正常态(已连接/稳定工作) */ -#define MCU_WS_RECONNECTING (1u << 2) /**< 断线重连中(回连广播) */ +#define MCU_WS_CONFIGURED (1u << 1) /**< 已配网且链路可发数(已连接+CCC) */ +#define MCU_WS_RECONNECTING (1u << 2) /**< 已绑定但链路未通(断连,或已连未写 CCC) */ #define MCU_WS_CLOUD_ONLINE (1u << 3) /**< 已连接到云,预留:暂固定 0 */ #define MCU_WS_APP_ONLINE (1u << 4) /**< APP 在线,预留:暂固定 0 */ #define MCU_WS_APP_DIRECT (1u << 5) /**< APP 直连,预留:暂固定 0 */ @@ -60,7 +60,7 @@ typedef enum { MCU_CMD_WORK_STATUS = 0x14, MCU_CMD_WORK_STATUS_ACK = 0x15, - /* 模组管理(后续扩展) */ + /* 模组管理 */ MCU_CMD_RESET = 0x16, MCU_CMD_RESET_ACK = 0x17, MCU_CMD_REBOOT = 0x18, 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 3d4c086..1e4a89b 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 @@ -2,27 +2,28 @@ * @file mcu_protocol.c * @brief MCU 接入协议引擎实现 * @author cyWu <1917507415@qq.com> - * @date 2026.08.12 - * @version V1.1.0 + * @date 2026.08.13 + * @version V1.2.0 * @history * - V1.0.0, 2026.08.12, cyWu, 从 mcu_bridge.c 拆出(历史版本见原文件): * 事件队列 + 独立任务驱动、LP target、心跳/透传重传均原样保留 * - V1.1.0, 2026.08.12, cyWu, 0x07 上报改为不缓存、不超时重发: * MCU 自身已有超时重发机制,模组侧只记 SN 转发+等 ACK 回 0x08, * 去掉 report_pending 整包缓存与 mcu_protocol_check_report_pending() + * - V1.2.0, 2026.08.13, cyWu, P1 管理命令分发、pending 发送、网络时间入队 ******************************************************************************/ #include "system/includes.h" #include "asm/power/power_api.h" #include "mcu_protocol.h" -#include "mcu_uart.h" +#include "mcu_business.h" #include "mcu_frame.h" +#include "mcu_uart.h" #include "mcu_util.h" #include "usr_le_api.h" -#include "mcu_business.h" #define LOG_TAG_CONST APP -#define LOG_TAG "[MCU_PROTO]" +#define LOG_TAG "[MCU_PROTO]" #define LOG_ERROR_ENABLE #define LOG_DEBUG_ENABLE #define LOG_INFO_ENABLE @@ -34,16 +35,16 @@ /* 私有宏 */ /*============================================================================*/ -#define MCU_HEARTBEAT_IDLE_MS (55000u) +#define MCU_HEARTBEAT_IDLE_MS (55000u) #define MCU_HEARTBEAT_MAX_RETRY (3u) -#define MCU_UART_ACK_WAIT_MS (500u) /* 等 MCU UART ACK 超时(心跳/透传/0x14 等) */ -#define MCU_APP_ACK_MAX_RETRY (3u) +#define MCU_UART_ACK_WAIT_MS (500u) /* 等 MCU UART ACK 超时(心跳/透传/0x14 等) */ +#define MCU_APP_ACK_MAX_RETRY (3u) #define MCU_PROTOCOL_TICK_MS (200u) /* 杰理 os_sem_pend 超时单位是 OS tick,OS_TICKS_PER_SEC=100 → 1 tick = 10ms */ -#define MCU_PROTOCOL_TICK_OS ((MCU_PROTOCOL_TICK_MS + 9u) / 10u) -#define MCU_EVT_QUEUE_DEPTH (8u) -#define MCU_PROTOCOL_TASK_PRIO (31u) +#define MCU_PROTOCOL_TICK_OS ((MCU_PROTOCOL_TICK_MS + 9u) / 10u) +#define MCU_EVT_QUEUE_DEPTH (8u) +#define MCU_PROTOCOL_TASK_PRIO (31u) #define MCU_PROTOCOL_TASK_STACK (2048u) /*============================================================================*/ @@ -52,12 +53,12 @@ typedef struct { - uint8_t active; - uint8_t sn; - uint8_t expect_ack; - uint8_t retry; - uint32_t send_time; - uint8_t buf[MCU_FRAME_MAX_LEN]; + uint8_t active; /* 1=正在等 MCU UART ACK */ + uint8_t sn; /* 发出去的 SN,用来对 ACK */ + uint8_t expect_ack; /* 期望的 ACK 命令字,如 0x04/0x13 */ + uint8_t retry; /* 已重发次数(不含首次) */ + uint32_t send_time; /* 最近一次发出时刻 */ + uint8_t buf[MCU_FRAME_MAX_LEN]; /* 仅 app_pending 缓存整包,便于重发 */ uint16_t len; } McuPending_t; @@ -67,7 +68,8 @@ typedef enum MCU_EVT_APP_FRAME, MCU_EVT_GATEWAY_ACK, MCU_EVT_BLE_PAIRED, - MCU_EVT_BLE_PAIR_CLEARED + MCU_EVT_BLE_PAIR_CLEARED, + MCU_EVT_NET_TIME } McuEvtType_t; typedef struct @@ -78,22 +80,24 @@ typedef struct uint16_t gw_cmd; uint8_t gw_int_cnt; int32_t gw_int0; + uint64_t unix_s; + int32_t gmtoff_s; } McuEvt_t; typedef struct { uint8_t initialized; - uint8_t sn; - uint8_t last_pushed_status; - uint8_t hb_fail_cnt; - uint8_t hb_waiting; - uint8_t mcu_comm_alarm; - uint32_t last_mcu_rx_ms; - uint32_t last_hb_tx_ms; - McuPending_t app_pending; /* APP→MCU 透传等待 */ - McuPending_t ble_pending; /* BLE 主动请求等待(心跳/状态等) */ - uint8_t report_waiting; /* 1=已转网关,等 ACK 后再回 MCU 0x08 */ - uint8_t report_sn; /* 等待回 0x08 的 SN */ + uint8_t sn; /* 模组侧发出帧的 SN 发生器 */ + uint8_t last_pushed_status; /* 上次已推给 MCU 的 0x14 状态,0xFF=强制再推 */ + uint8_t hb_fail_cnt; /* 心跳连续失败次数 */ + uint8_t hb_waiting; /* 1=已发 0x12,正在等 0x13 */ + uint8_t mcu_comm_alarm; /* 1=心跳连失败 3 次,MCU 通信告警 */ + uint32_t last_mcu_rx_ms; /* 最近一次收到 MCU 任意合法帧 */ + uint32_t last_hb_tx_ms; /* 最近一次发出心跳 */ + McuPending_t app_pending; /* APP 0x03/0x05 透传,等 MCU 0x04/0x06 */ + McuPending_t ble_pending; /* 模组主动 UART(心跳/0x14/0x40/0x1A),一次只能占一个 */ + uint8_t report_waiting; /* 1=MCU 0x07 已转网关,等 ACK 后再回 0x08 */ + uint8_t report_sn; /* 等待回 0x08 的那条 0x07 的 SN */ } McuProtocol_t; typedef struct @@ -125,6 +129,7 @@ static int32_t mcu_protocol_send_frame(uint8_t cmd, uint8_t sn, static void mcu_protocol_send_ack(uint8_t cmd, uint8_t sn, uint8_t result); static void mcu_protocol_handle_frame(const uint8_t *frame, uint16_t len); static void mcu_protocol_handle_pair_cfg(const uint8_t *frame, uint16_t len); +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); static void mcu_protocol_handle_app_frame(const uint8_t *data, uint16_t len); static void mcu_protocol_handle_gateway_ack(uint16_t cmd, uint8_t gateway_int_cnt, @@ -218,6 +223,9 @@ static void mcu_protocol_dispatch_event(const McuEvt_t *evt) case MCU_EVT_BLE_PAIR_CLEARED: mcu_business_on_pair_cleared(); break; + case MCU_EVT_NET_TIME: + mcu_business_on_network_time(evt->unix_s, evt->gmtoff_s); + break; default: log_error("unknown evt type=%d", evt->type); break; @@ -315,6 +323,59 @@ 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 发送并占用 ble_pending + * @param cmd 命令 + * @param payload 载荷 + * @param plen 长度 + * @param expect_ack 期望 ACK + * @return 帧长;<0 忙或失败 + */ +int32_t mcu_protocol_send_cmd_wait_ack(uint8_t cmd, const uint8_t *payload, + uint16_t plen, uint8_t expect_ack) +{ + uint8_t sn; + int32_t total; + + /* UART 一次只等一条 ACK,已被心跳/0x14/0x40 等占用则拒绝 */ + if (s_proto.ble_pending.active) + { + return -1; + } + + sn = mcu_protocol_next_sn(); + total = mcu_protocol_send_frame(cmd, sn, payload, plen); + if (total <= 0) + { + return -1; + } + + s_proto.ble_pending.active = 1; + s_proto.ble_pending.sn = sn; + s_proto.ble_pending.expect_ack = expect_ack; + s_proto.ble_pending.send_time = mcu_util_get_ms(); + s_proto.ble_pending.retry = 0; + return total; +} + +/** + * @brief ble_pending 是否占用 + * @return 1/0 + */ +uint8_t mcu_protocol_is_ble_pending(void) +{ + return s_proto.ble_pending.active ? 1 : 0; +} + +/** + * @brief MCU 0x07 是否在等网关 ACK + * @return 1/0 + */ +uint8_t mcu_protocol_is_mcu_report_waiting(void) +{ + return s_proto.report_waiting ? 1 : 0; +} + /** * @brief 发送仅含 Result 的 ACK 帧 * @param cmd ACK 命令 @@ -357,11 +418,13 @@ static void mcu_protocol_handle_pair_cfg(const uint8_t *frame, uint16_t len) mode = frame[6]; timeout_s = mcu_util_read_u16_be(&frame[7]); + /* 协议只认 mode=1/2,其它直接回参数错误 */ if (mode != 1 && mode != 2) { mcu_protocol_send_ack(MCU_CMD_PAIR_CFG_ACK, sn, MCU_RESULT_PARAM_ERR); return; } + /* 还没拿到 MCU 身份,广播都发不了,回忙让 MCU 稍后再试 */ if (!mcu_business_is_identity_ready()) { mcu_protocol_send_ack(MCU_CMD_PAIR_CFG_ACK, sn, MCU_RESULT_BUSY); @@ -372,6 +435,32 @@ static void mcu_protocol_handle_pair_cfg(const uint8_t *frame, uint16_t len) mcu_business_on_pair_cfg(mode, timeout_s); } +/** + * @brief 处理 0x1C:组 0x1D(时间由 business 填充) + * @param frame 完整帧 + * @param len 长度 + */ +static void mcu_protocol_handle_get_time(const uint8_t *frame, uint16_t len) +{ + uint8_t sn; + uint8_t payload[16]; + uint16_t plen = 0; + + (void)len; + if (!frame) + { + return; + } + sn = frame[5]; + mcu_business_build_time_ack(payload, &plen); + if (plen == 0) + { + plen = 1; + payload[0] = MCU_RESULT_OK; + } + mcu_protocol_send_frame(MCU_CMD_GET_TIME_ACK, sn, payload, plen); +} + /** * @brief 处理 MCU 上报 0x07:转网关,等网关 ACK 后再回 0x08 * @param frame 完整帧 @@ -383,9 +472,20 @@ static void mcu_protocol_handle_report(const uint8_t *frame, uint16_t len) { uint8_t sn = frame[5]; + /* 已配网也会在 CCC 之前连上;这时发出去会失败,不置 waiting,让 MCU 自己重发 0x07 */ + if (!usr_ble_is_notify_ready()) + { + log_debug("MCU 0x07 wait CCC sn=%u, MCU will retry", sn); + return; + } + + if (usr_le_send_mcu_frame((u8 *)frame, len) == 0) + { + log_error("MCU 0x07 send fail sn=%u", sn); + return; + } s_proto.report_sn = sn; s_proto.report_waiting = 1; - usr_le_send_mcu_frame((u8 *)frame, len); log_debug("MCU report sn=%u -> gateway, wait ACK", sn); } @@ -404,6 +504,7 @@ static void mcu_protocol_handle_frame(const uint8_t *frame, uint16_t len) return; } + /* 收到任意合法 MCU 帧:MCU 还活着,清心跳失败与告警 */ s_proto.last_mcu_rx_ms = mcu_util_get_ms(); s_proto.hb_fail_cnt = 0; s_proto.hb_waiting = 0; @@ -413,6 +514,7 @@ static void mcu_protocol_handle_frame(const uint8_t *frame, uint16_t len) sn = frame[5]; log_debug("MCU RX cmd=0x%02X sn=%u len=%u", cmd, sn, len); + /* APP 透传在等这条 ACK(命令+SN 都对上):转给 APP,不再往下分发 */ if (s_proto.app_pending.active && (cmd == s_proto.app_pending.expect_ack) && (sn == s_proto.app_pending.sn)) @@ -422,6 +524,7 @@ static void mcu_protocol_handle_frame(const uint8_t *frame, uint16_t len) return; } + /* 模组主动 UART 在等这条 ACK:先释放 pending,再落到下面 switch */ if (s_proto.ble_pending.active && (cmd == s_proto.ble_pending.expect_ack) && (sn == s_proto.ble_pending.sn)) @@ -432,7 +535,7 @@ static void mcu_protocol_handle_frame(const uint8_t *frame, uint16_t len) s_proto.hb_waiting = 0; s_proto.hb_fail_cnt = 0; } - return; + /* 0x41/0x1B 等其余 ACK 继续走 switch,交给 business */ } switch (cmd) @@ -444,20 +547,37 @@ 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: - mcu_protocol_handle_report(frame, len); + mcu_protocol_handle_report(frame, len); /* 转网关,等 ACK 再回 0x08 */ break; case MCU_CMD_CONTROL_ACK: case MCU_CMD_READ_ACK: + /* 没匹配上 app_pending(SN 不对或没在等)仍转给 APP,避免 MCU 应答丢掉 */ usr_le_send_mcu_frame((u8 *)frame, len); break; + case MCU_CMD_RESET: + mcu_protocol_send_ack(MCU_CMD_RESET_ACK, sn, MCU_RESULT_OK); + mcu_business_on_factory_reset(); + break; + case MCU_CMD_REBOOT: + mcu_protocol_send_ack(MCU_CMD_REBOOT_ACK, sn, MCU_RESULT_OK); + mcu_business_on_mod_reboot(); + break; + case MCU_CMD_GET_TIME: + mcu_protocol_handle_get_time(frame, len); + break; + case MCU_CMD_GET_MCU_INFO_ACK: + mcu_business_on_mcu_info_ack(frame, len); /* 只本地解析,不原样转发网关 */ + break; + case MCU_CMD_MCU_REBOOT_ACK: + mcu_business_on_mcu_reboot_ack(frame, len); + break; case MCU_CMD_HEARTBEAT_ACK: case MCU_CMD_WORK_STATUS_ACK: - break; + break; /* pending 已在上面释放,无需再处理载荷 */ default: - if (cmd == MCU_CMD_RESET || cmd == MCU_CMD_REBOOT || - cmd == MCU_CMD_GET_TIME || cmd == MCU_CMD_FACTORY || - cmd == MCU_CMD_PHOTO || cmd == MCU_CMD_RECORD || - cmd == MCU_CMD_AUDIO) + /* 工厂/拍照/录音/音频:协议有命令字但模组未实现,回不支持 */ + 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); } @@ -479,6 +599,8 @@ static void mcu_protocol_handle_app_frame(const uint8_t *data, uint16_t len) cmd = data[4]; sn = data[5]; + /* 0x03/0x05 必须等 MCU ACK 再回 APP,所以进 pending 可超时重发。 + * 其它 APP 帧直接透传到 UART,模组不管应答。 */ if (cmd == MCU_CMD_CONTROL) { expect_ack = MCU_CMD_CONTROL_ACK; @@ -493,6 +615,7 @@ static void mcu_protocol_handle_app_frame(const uint8_t *data, uint16_t len) return; } + /* 上一条 0x03/0x05 还没回,丢掉新的,避免 SN 对乱 */ if (s_proto.app_pending.active) { log_error("app frame busy, drop cmd=0x%02X", cmd); @@ -523,23 +646,29 @@ static void mcu_protocol_handle_gateway_ack(uint16_t cmd, int32_t gateway_int0) { uint8_t sn; + uint8_t success; + uint8_t mcu_consumed = 0; - if (!s_proto.report_waiting) - { - return; - } + /* 网关成功 ACK 约定:cmd=0、只有 1 个 int、且 int0=0 */ + success = (cmd == 0 && gateway_int_cnt == 1 && gateway_int0 == 0) ? 1 : 0; - if (cmd == 0 && gateway_int_cnt == 1 && gateway_int0 == 0) + /* MCU 0x07 优先:成功才回 0x08;失败不回,让 MCU 自己超时重发 0x07 */ + if (s_proto.report_waiting && success) { sn = s_proto.report_sn; mcu_protocol_send_ack(MCU_CMD_REPORT_ACK, sn, MCU_RESULT_OK); s_proto.report_waiting = 0; + mcu_consumed = 1; log_info("gateway report ack ok, MCU 0x08 sn=%u", sn); - return; + } + else if (s_proto.report_waiting) + { + log_error("gateway report ack fail cmd=%u cnt=%u int0=%ld", + (unsigned)cmd, (unsigned)gateway_int_cnt, (long)gateway_int0); } - log_error("gateway report ack fail cmd=%u cnt=%u int0=%ld", - (unsigned)cmd, (unsigned)gateway_int_cnt, (long)gateway_int0); + /* 再交给版本上报:若已被 0x07 占用,版本侧会继续等,不会误当成自己的 ACK */ + mcu_business_on_gateway_ack(success, mcu_consumed); } /*============================================================================*/ @@ -556,13 +685,21 @@ static void mcu_protocol_try_heartbeat(void) uint8_t buf[MCU_FRAME_MAX_LEN]; int32_t total; + /* 身份都还没通,先别发心跳 */ if (!mcu_business_is_identity_ready()) { return; } + /* ble_pending 被 0x14/0x40 等占用时不插心跳;自己等 0x13 时除外 */ + if (s_proto.ble_pending.active && !s_proto.hb_waiting) + { + return; + } + if (s_proto.hb_waiting) { + /* 500ms 内还在等 0x13,不重发 */ if ((now - s_proto.last_hb_tx_ms) <= MCU_UART_ACK_WAIT_MS) { return; @@ -571,6 +708,7 @@ static void mcu_protocol_try_heartbeat(void) s_proto.ble_pending.active = 0; s_proto.hb_fail_cnt++; log_error("heartbeat timeout fail=%u", s_proto.hb_fail_cnt); + /* 连续 3 次没回:置告警,并刷新 last_rx 避免立刻再打心跳 */ if (s_proto.hb_fail_cnt >= MCU_HEARTBEAT_MAX_RETRY) { s_proto.mcu_comm_alarm = 1; @@ -581,6 +719,7 @@ static void mcu_protocol_try_heartbeat(void) } else if ((now - s_proto.last_mcu_rx_ms) < MCU_HEARTBEAT_IDLE_MS) { + /* 55s 内 MCU 有过任何合法帧,视为还活着,不发心跳 */ return; } @@ -623,10 +762,12 @@ static void mcu_protocol_try_work_status(void) } ws = mcu_business_get_work_status(); + /* 状态没变就不再推 0x14;0xFF 表示强制重推 */ if (ws == s_proto.last_pushed_status) { return; } + /* UART 正在等别的 ACK,让下个周期再推 */ if (s_proto.ble_pending.active) { return; @@ -671,11 +812,13 @@ static void mcu_protocol_check_app_pending(void) } now = mcu_util_get_ms(); + /* 500ms 窗口内继续等 MCU ACK */ if ((now - s_proto.app_pending.send_time) <= MCU_UART_ACK_WAIT_MS) { return; } + /* 重发 3 次仍无 ACK:主动给 APP 回 FAIL,避免 APP 一直干等 */ if (s_proto.app_pending.retry >= MCU_APP_ACK_MAX_RETRY) { log_error("app pending give up cmd expect=0x%02X, notify APP FAIL", @@ -709,6 +852,7 @@ static void mcu_protocol_check_ble_pending(void) { return; } + /* 心跳超时由 try_heartbeat 自己处理,这里不管 */ if (s_proto.ble_pending.expect_ack == MCU_CMD_HEARTBEAT_ACK) { return; @@ -717,6 +861,7 @@ static void mcu_protocol_check_ble_pending(void) now = mcu_util_get_ms(); if ((now - s_proto.ble_pending.send_time) > MCU_UART_ACK_WAIT_MS) { + /* 0x14 超时:清 pending,并把 last 置 0xFF,下周期会再推一次 */ if (s_proto.ble_pending.expect_ack == MCU_CMD_WORK_STATUS_ACK) { log_error("work_status ack timeout, will retry"); @@ -879,3 +1024,19 @@ void mcu_protocol_notify_gateway_ack(uint16_t cmd, evt.gw_int0 = gateway_int0; mcu_protocol_post_event(&evt); } + +/** + * @brief 网络时间入队 + * @param unix_s UTC Unix 时间戳(秒) + * @param gmtoff_s 时区偏移秒 + */ +void mcu_protocol_notify_network_time(uint64_t unix_s, int32_t gmtoff_s) +{ + McuEvt_t evt; + + memset(&evt, 0, sizeof(evt)); + evt.type = MCU_EVT_NET_TIME; + evt.unix_s = unix_s; + evt.gmtoff_s = gmtoff_s; + mcu_protocol_post_event(&evt); +} 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 0d44c8e..4fae81f 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 @@ -2,11 +2,12 @@ * @file mcu_protocol.h * @brief MCU 接入协议引擎:事件队列 / 任务 / 命令分发 / ACK / 透传重传 / 心跳 * @author cyWu <1917507415@qq.com> - * @date 2026.08.12 - * @version V1.0.0 + * @date 2026.08.13 + * @version V1.1.0 * @history * - V1.0.0, 2026.08.12, cyWu, 从 mcu_bridge.c 拆出,仅保留协议机制, * 不含任何业务判断(身份/配网/连接状态见 ../business) + * - V1.1.0, 2026.08.13, cyWu, P1:管理命令分发、pending 发送、网关时间入队 ******************************************************************************/ #ifndef __MCU_PROTOCOL_H__ @@ -62,6 +63,13 @@ void mcu_protocol_notify_gateway_ack(uint16_t cmd, uint8_t gateway_int_cnt, int32_t gateway_int0); +/** + * @brief 网关 DEVICEINFO 网络时间(入队,BLE 上下文可调) + * @param unix_s UTC Unix 时间戳(秒) + * @param gmtoff_s 时区偏移(秒) + */ +void mcu_protocol_notify_network_time(uint64_t unix_s, int32_t gmtoff_s); + /*============================================================================*/ /* 供 business 层调用的协议发送接口 */ /*============================================================================*/ @@ -75,6 +83,29 @@ void mcu_protocol_notify_gateway_ack(uint16_t cmd, */ int32_t mcu_protocol_send_cmd(uint8_t cmd, const uint8_t *payload, uint16_t plen); +/** + * @brief 发送并占用 ble_pending,等待 expect_ack + * @param cmd 命令字 + * @param payload 载荷,可为 NULL + * @param plen 载荷长度 + * @param expect_ack 期望的 MCU ACK 命令 + * @return 帧长;<0 忙或失败 + */ +int32_t mcu_protocol_send_cmd_wait_ack(uint8_t cmd, const uint8_t *payload, + uint16_t plen, uint8_t expect_ack); + +/** + * @brief BLE 主动 UART 请求是否正在等 ACK(心跳/0x14/0x40 等) + * @return 1=忙,0=空闲 + */ +uint8_t mcu_protocol_is_ble_pending(void); + +/** + * @brief MCU 0x07 是否已转发、正在等网关 ACK + * @return 1=是,0=否 + */ +uint8_t mcu_protocol_is_mcu_report_waiting(void); + /** * @brief 强制下一轮重新推送 0x14(业务状态变化后调用) */ diff --git a/JBao_JL7016_MOD_FW/apps/usr_periph/usr_rtc.c b/JBao_JL7016_MOD_FW/apps/usr_periph/usr_rtc.c new file mode 100644 index 0000000..5fef500 --- /dev/null +++ b/JBao_JL7016_MOD_FW/apps/usr_periph/usr_rtc.c @@ -0,0 +1,540 @@ +/****************************************************************************** + * @file usr_rtc.c + * @brief 板级 RTC 外设实现(BR28 硬件 rtc_dev_ops + 32K) + * @author cyWu <1917507415@qq.com> + * @date 2026.08.14 + * @version V1.0.0 + * @history + * - V1.0.0, 2026.08.14, cyWu, 首次发布 + ******************************************************************************/ + +#include "system/includes.h" +#include "app_config.h" +#include "usr_rtc.h" +#include "asm/rtc.h" + +#define LOG_TAG_CONST APP +#define LOG_TAG "[USR_RTC]" +#define LOG_ERROR_ENABLE +#define LOG_DEBUG_ENABLE +#define LOG_INFO_ENABLE +#include "debug.h" + +/*============================================================================*/ +/* 私有宏 */ +/*============================================================================*/ + +#define USR_RTC_TZ_MAGIC (0x52544331u) /**< 'RTC1',校验 VM 时区 */ +#define USR_RTC_LOG_PERIOD_MS (60000u) /**< 周期日志间隔 */ + +/*============================================================================*/ +/* 私有类型 */ +/*============================================================================*/ + +typedef struct { + uint32_t magic; + int32_t tz_s; +} UsrRtcTzVm_t; + +typedef struct { + uint8_t inited; + uint8_t tz_valid; + uint8_t time_valid; + int32_t tz_s; + uint32_t unix_utc; + uint32_t last_log_ms; + void *dev; +} UsrRtc_t; + +/*============================================================================*/ +/* 私有变量 */ +/*============================================================================*/ + +static UsrRtc_t s_rtc; + +/*============================================================================*/ +/* 私有函数声明 */ +/*============================================================================*/ + +static uint8_t usr_rtc_is_leap(uint16_t year); +static uint8_t usr_rtc_days_in_month(uint16_t year, uint8_t month); +static uint8_t usr_rtc_year_ok(uint16_t year); +static void usr_rtc_unix_to_calendar(uint32_t unix_utc, int32_t tz_s, + struct sys_time *t); +static uint32_t usr_rtc_calendar_to_utc(const struct sys_time *t, int32_t tz_s); +static void usr_rtc_load_tz(void); +static void usr_rtc_save_tz(int32_t tz_s); +static void usr_rtc_refresh_cache(void); + +/*============================================================================*/ +/* 公有函数 */ +/*============================================================================*/ + +/** + * @brief 打开 rtc 设备并加载时区 + * @return UsrRtc_Ret_t + */ +UsrRtc_Ret_t usr_rtc_init(void) +{ + struct sys_time t; + + if (s_rtc.inited) + { + return USR_RTC_OK; + } + +#if TCFG_APP_RTC_EN + s_rtc.dev = dev_open("rtc", NULL); + if (!s_rtc.dev) + { + log_error("open rtc fail"); + return USR_RTC_ERR_NOT_INIT; + } +#else + log_error("TCFG_APP_RTC_EN=0"); + return USR_RTC_ERR_NOT_INIT; +#endif + + usr_rtc_load_tz(); + s_rtc.inited = 1; + + memset(&t, 0, sizeof(t)); + read_sys_time(&t); + log_info("rtc open %u-%02u-%02u %02u:%02u:%02u tz=%ld tz_ok=%u", + t.year, t.month, t.day, t.hour, t.min, t.sec, + (long)s_rtc.tz_s, s_rtc.tz_valid); + + usr_rtc_refresh_cache(); + return USR_RTC_OK; +} + +/** + * @brief 周期读 RTC,刷新 UTC + */ +void usr_rtc_run(void) +{ + uint32_t now; + + if (!s_rtc.inited) + { + return; + } + + usr_rtc_refresh_cache(); + + now = timer_get_ms(); + if ((now - s_rtc.last_log_ms) >= USR_RTC_LOG_PERIOD_MS) + { + struct sys_time t; + s_rtc.last_log_ms = now; + if (usr_rtc_read(&t) == USR_RTC_OK) + { + log_info("rtc %u-%02u-%02u %02u:%02u:%02u utc=%u valid=%u", + t.year, t.month, t.day, t.hour, t.min, t.sec, + s_rtc.unix_utc, s_rtc.time_valid); + } + } +} + +/** + * @brief 获取对时状态 + * @return UsrRtc_Ret_t + */ +UsrRtc_Ret_t usr_rtc_get_status(void) +{ + if (!s_rtc.inited) + { + return USR_RTC_ERR_NOT_INIT; + } + return s_rtc.time_valid ? USR_RTC_OK : USR_RTC_ERR_INVALID; +} + +/** + * @brief 读硬件日历 + * @param t 输出 + * @return UsrRtc_Ret_t + */ +UsrRtc_Ret_t usr_rtc_read(struct sys_time *t) +{ + if (!t) + { + return USR_RTC_ERR_PARAM; + } + if (!s_rtc.inited) + { + return USR_RTC_ERR_NOT_INIT; + } + read_sys_time(t); + return USR_RTC_OK; +} + +/** + * @brief 写硬件日历 + * @param t 输入 + * @return UsrRtc_Ret_t + */ +UsrRtc_Ret_t usr_rtc_write(const struct sys_time *t) +{ + struct sys_time tmp; + + if (!t) + { + return USR_RTC_ERR_PARAM; + } + if (!s_rtc.inited) + { + return USR_RTC_ERR_NOT_INIT; + } + if (!usr_rtc_year_ok(t->year) || (t->month < 1) || (t->month > 12) || + (t->day < 1) || (t->hour > 23) || (t->min > 59) || (t->sec > 59)) + { + return USR_RTC_ERR_PARAM; + } + if (t->day > usr_rtc_days_in_month(t->year, t->month)) + { + return USR_RTC_ERR_PARAM; + } + + tmp = *t; + write_sys_time(&tmp); + usr_rtc_refresh_cache(); + return USR_RTC_OK; +} + +/** + * @brief UTC 秒 + 时区 → 本地日历写入 RTC + * @param unix_utc UTC 秒 + * @param tz_s 时区偏移秒 + * @return UsrRtc_Ret_t + */ +UsrRtc_Ret_t usr_rtc_set_from_unix(uint64_t unix_utc, int32_t tz_s) +{ + struct sys_time t; + UsrRtc_Ret_t ret; + + if (!s_rtc.inited) + { + return USR_RTC_ERR_NOT_INIT; + } + if (unix_utc == 0) + { + log_debug("unix=0, ignore"); + return USR_RTC_ERR_PARAM; + } + /* 网关采样到模组写入大约慢 1s,在 UTC 上补偿,跨分跨时由日历换算进位 */ + unix_utc += USR_RTC_SYNC_COMPENSATE_S; + if ((tz_s > USR_RTC_TZ_ABS_MAX_S) || (tz_s < -USR_RTC_TZ_ABS_MAX_S)) + { + log_error("tz out of range %ld", (long)tz_s); + return USR_RTC_ERR_PARAM; + } + if (unix_utc > 0xFFFFFFFFull) + { + log_error("unix too large"); + return USR_RTC_ERR_PARAM; + } + + usr_rtc_unix_to_calendar((uint32_t)unix_utc, tz_s, &t); + if (!usr_rtc_year_ok(t.year)) + { + log_error("year %u not in %u~%u, wait sync", + t.year, USR_RTC_YEAR_MIN, USR_RTC_YEAR_MAX); + return USR_RTC_ERR_INVALID; + } + + ret = usr_rtc_write(&t); + if (ret != USR_RTC_OK) + { + return ret; + } + + usr_rtc_save_tz(tz_s); + usr_rtc_refresh_cache(); + log_info("set rtc %u-%02u-%02u %02u:%02u:%02u utc=%u tz=%ld", + t.year, t.month, t.day, t.hour, t.min, t.sec, + s_rtc.unix_utc, (long)tz_s); + return USR_RTC_OK; +} + +/** + * @brief 时间是否可用 + * @return 1/0 + */ +uint8_t usr_rtc_is_valid(void) +{ + return (s_rtc.inited && s_rtc.time_valid) ? 1 : 0; +} + +/** + * @brief 当前 UTC 秒 + * @return Unix 秒,无效为 0 + */ +uint32_t usr_rtc_get_unix_utc(void) +{ + return s_rtc.time_valid ? s_rtc.unix_utc : 0; +} + +/** + * @brief 当前时区偏移 + * @return 秒 + */ +int32_t usr_rtc_get_timezone(void) +{ + return s_rtc.tz_valid ? s_rtc.tz_s : 0; +} + +/** + * @brief 恢复出厂:清 VM 时区和 RAM 对时状态 + */ +void usr_rtc_clear_tz(void) +{ + UsrRtcTzVm_t vm; + + s_rtc.tz_valid = 0; + s_rtc.tz_s = 0; + s_rtc.time_valid = 0; + s_rtc.unix_utc = 0; + + memset(&vm, 0, sizeof(vm)); + syscfg_write(CFG_USER_TIM_FLAG, (u8 *)&vm, sizeof(vm)); + log_info("tz cleared"); +} + +/** + * @brief 计算星期 + * @param t 日历 + * @return 0=周日 … 6=周六 + */ +uint8_t usr_rtc_get_weekday(const struct sys_time *t) +{ + static const uint8_t sakamoto[12] = { + 0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4}; + uint16_t y; + + if (!t || (t->month < 1) || (t->month > 12) || (t->day < 1)) + { + return 0xFF; + } + + y = t->year; + if (t->month < 3) + { + y--; + } + return (uint8_t)((y + y / 4u - y / 100u + y / 400u + + sakamoto[t->month - 1] + t->day) % 7u); +} + +/*============================================================================*/ +/* 私有函数 */ +/*============================================================================*/ + +/** + * @brief 是否闰年 + * @param year 公历年 + * @return 1/0 + */ +static uint8_t usr_rtc_is_leap(uint16_t year) +{ + if ((year % 400u) == 0) + { + return 1; + } + if ((year % 100u) == 0) + { + return 0; + } + return ((year % 4u) == 0) ? 1 : 0; +} + +/** + * @brief 某月天数 + * @param year 年 + * @param month 月 1~12 + * @return 天数,非法月返回 0 + */ +static uint8_t usr_rtc_days_in_month(uint16_t year, uint8_t month) +{ + static const uint8_t days[12] = { + 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + + if ((month < 1) || (month > 12)) + { + return 0; + } + if ((month == 2) && usr_rtc_is_leap(year)) + { + return 29; + } + return days[month - 1]; +} + +/** + * @brief 年份是否在合法窗口 + * @param year 年 + * @return 1/0 + */ +static uint8_t usr_rtc_year_ok(uint16_t year) +{ + return ((year >= USR_RTC_YEAR_MIN) && (year <= USR_RTC_YEAR_MAX)) ? 1 : 0; +} + +/** + * @brief UTC 秒 + 时区 → 本地日历 + * @param unix_utc UTC 秒 + * @param tz_s 时区偏移 + * @param t 输出 + */ +static void usr_rtc_unix_to_calendar(uint32_t unix_utc, int32_t tz_s, + struct sys_time *t) +{ + int64_t local; + uint32_t days; + uint32_t tod; + uint16_t y; + uint8_t m; + uint8_t dim; + + memset(t, 0, sizeof(*t)); + local = (int64_t)unix_utc + (int64_t)tz_s; + if (local < 0) + { + local = 0; + } + + tod = (uint32_t)(local % 86400); + days = (uint32_t)(local / 86400); + t->sec = (uint8_t)(tod % 60u); + t->min = (uint8_t)((tod / 60u) % 60u); + t->hour = (uint8_t)(tod / 3600u); + + y = 1970; + while (1) + { + uint32_t diy = usr_rtc_is_leap(y) ? 366u : 365u; + if (days < diy) + { + break; + } + days -= diy; + y++; + if (y > 4095u) + { + break; + } + } + t->year = y; + + for (m = 1; m <= 12u; m++) + { + dim = usr_rtc_days_in_month(y, m); + if (days < dim) + { + break; + } + days -= dim; + } + t->month = m; + t->day = (uint8_t)(days + 1u); +} + +/** + * @brief 本地日历 − 时区 → UTC 秒 + * @param t 本地日历 + * @param tz_s 时区偏移 + * @return UTC Unix 秒 + */ +static uint32_t usr_rtc_calendar_to_utc(const struct sys_time *t, int32_t tz_s) +{ + uint32_t days = 0; + uint16_t y; + uint8_t m; + int64_t local_s; + int64_t utc; + + for (y = 1970; y < t->year; y++) + { + days += usr_rtc_is_leap(y) ? 366u : 365u; + } + for (m = 1; m < t->month; m++) + { + days += usr_rtc_days_in_month(t->year, m); + } + days += (uint32_t)(t->day - 1u); + + local_s = (int64_t)days * 86400 + + (int64_t)t->hour * 3600 + + (int64_t)t->min * 60 + + (int64_t)t->sec; + utc = local_s - (int64_t)tz_s; + if (utc < 0) + { + utc = 0; + } + return (uint32_t)utc; +} + +/** + * @brief 从 VM 加载时区(CFG_USER_TIM_FLAG) + */ +static void usr_rtc_load_tz(void) +{ + UsrRtcTzVm_t vm; + int ret; + + s_rtc.tz_valid = 0; + s_rtc.tz_s = 0; + memset(&vm, 0, sizeof(vm)); + ret = syscfg_read(CFG_USER_TIM_FLAG, (u8 *)&vm, sizeof(vm)); + if ((ret == (int)sizeof(vm)) && (vm.magic == USR_RTC_TZ_MAGIC)) + { + if ((vm.tz_s <= USR_RTC_TZ_ABS_MAX_S) && (vm.tz_s >= -USR_RTC_TZ_ABS_MAX_S)) + { + s_rtc.tz_s = vm.tz_s; + s_rtc.tz_valid = 1; + log_info("tz from vm %ld", (long)s_rtc.tz_s); + return; + } + } + log_debug("tz vm empty"); +} + +/** + * @brief 时区写入 VM + * @param tz_s 时区偏移秒 + */ +static void usr_rtc_save_tz(int32_t tz_s) +{ + UsrRtcTzVm_t vm; + + s_rtc.tz_s = tz_s; + s_rtc.tz_valid = 1; + vm.magic = USR_RTC_TZ_MAGIC; + vm.tz_s = tz_s; + syscfg_write(CFG_USER_TIM_FLAG, (u8 *)&vm, sizeof(vm)); +} + +/** + * @brief 读 RTC,年份+时区都合法才刷新 UTC + */ +static void usr_rtc_refresh_cache(void) +{ + struct sys_time t; + + s_rtc.time_valid = 0; + s_rtc.unix_utc = 0; + if (!s_rtc.inited || !s_rtc.tz_valid) + { + return; + } + + memset(&t, 0, sizeof(t)); + read_sys_time(&t); + if (!usr_rtc_year_ok(t.year)) + { + return; + } + + s_rtc.unix_utc = usr_rtc_calendar_to_utc(&t, s_rtc.tz_s); + s_rtc.time_valid = 1; +} diff --git a/JBao_JL7016_MOD_FW/apps/usr_periph/usr_rtc.h b/JBao_JL7016_MOD_FW/apps/usr_periph/usr_rtc.h new file mode 100644 index 0000000..c60c241 --- /dev/null +++ b/JBao_JL7016_MOD_FW/apps/usr_periph/usr_rtc.h @@ -0,0 +1,117 @@ +/****************************************************************************** + * @file usr_rtc.h + * @brief 板级 RTC 外设:打开 "rtc" 设备,读写系统时间,UTC↔本地日历 + * @author cyWu <1917507415@qq.com> + * @date 2026.08.14 + * @version V1.0.0 + * @history + * - V1.0.0, 2026.08.14, cyWu, 首次发布(硬件 RTC,不对闹钟) + ******************************************************************************/ + +#ifndef __USR_RTC_H__ +#define __USR_RTC_H__ + +#include +#include "system/sys_time.h" + +/*============================================================================*/ +/* 宏定义 */ +/*============================================================================*/ + +#define USR_RTC_YEAR_MIN (2026u) /**< 合法年份下限 */ +#define USR_RTC_YEAR_MAX (2098u) /**< 合法年份上限 */ +#define USR_RTC_TZ_ABS_MAX_S (57600) /**< 时区偏移绝对值上限(±16h) */ +#define USR_RTC_SYNC_COMPENSATE_S (1u) /**< 网关对时延迟补偿,写入 RTC 前加的秒数 */ + +/*============================================================================*/ +/* 错误码 */ +/*============================================================================*/ + +typedef enum { + USR_RTC_OK = 0, + USR_RTC_ERR_PARAM, + USR_RTC_ERR_BUSY, + USR_RTC_ERR_TIMEOUT, + USR_RTC_ERR_NOT_INIT, + USR_RTC_ERR_INVALID, /**< 年份非法,等待 App 对时 */ + USR_RTC_ERR_UNKNOWN +} UsrRtc_Ret_t; + +/*============================================================================*/ +/* 外部接口 */ +/*============================================================================*/ + +/** + * @brief 打开 "rtc" 设备,加载时区,读一次硬件时间 + * @return UsrRtc_Ret_t + * @note 可重复调用;板级需已 REGISTER_DEVICES 注册 rtc + */ +UsrRtc_Ret_t usr_rtc_init(void); + +/** + * @brief 周期读 RTC,刷新 UTC 缓存(给业务用) + */ +void usr_rtc_run(void); + +/** + * @brief 获取对时状态 + * @return USR_RTC_OK=时间合法,ERR_INVALID=等待对时,ERR_NOT_INIT=未打开 + */ +UsrRtc_Ret_t usr_rtc_get_status(void); + +/** + * @brief 读硬件日历(本地时区) + * @param t 输出 + * @return UsrRtc_Ret_t + */ +UsrRtc_Ret_t usr_rtc_read(struct sys_time *t); + +/** + * @brief 写硬件日历(本地时区) + * @param t 输入 + * @return UsrRtc_Ret_t + */ +UsrRtc_Ret_t usr_rtc_write(const struct sys_time *t); + +/** + * @brief App/网关下发 UTC 秒 + 时区偏移,写入 RTC + * @param unix_utc UTC Unix 秒;0 视为无效 + * @param tz_s 时区偏移秒(东八区 28800) + * @return UsrRtc_Ret_t + * @note 网关从子设备连上开始计时,每 12 小时经 DEVICEINFO 同步一次; + * timestamp + tz → 本地年月日时分秒 → write_sys_time;tz 写入 VM + */ +UsrRtc_Ret_t usr_rtc_set_from_unix(uint64_t unix_utc, int32_t tz_s); + +/** + * @brief 年份合法且已有时区 + * @return 1/0 + */ +uint8_t usr_rtc_is_valid(void); + +/** + * @brief 当前 UTC Unix 秒(日历换算后再减时区) + * @return 无效时返回 0 + */ +uint32_t usr_rtc_get_unix_utc(void); + +/** + * @brief 当前时区偏移秒 + * @return 未保存过时返回 0 + */ +int32_t usr_rtc_get_timezone(void); + +/** + * @brief 恢复出厂:清 VM 时区和 RAM 对时状态 + * @note 硬件日历不改;下次需网关重新对时。0x1D 在此之后回全 0 + */ +void usr_rtc_clear_tz(void); + +/** + * @brief 由日历计算星期 + * @param t 本地日历 + * @return 0=周日 … 6=周六;参数非法返回 0xFF + */ +uint8_t usr_rtc_get_weekday(const struct sys_time *t); + +#endif /* __USR_RTC_H__ */