mcm-ddc-ble V1.00.02~08:新增CLI读写特征(...c72e0003,写命令读应答)与只读参数特征(...c72e0004,全项TLV);修复温度开机首读垃圾值(ADC初始化预热+丢弃首转);blelog补写回调/CCCD订阅可见日志并修正CCCD值解析;tools新增bleak测试客户端(ble_cli_test.py/ble_cli.bat/ble_temp_watch.py);make_package.bat改指mcm-ddc-ble;merge_image.py自动取APP_FW_VERSION_NUM写入OTA清单;AGENTS.md记录固件版本递增约定;开发日志第28节

This commit is contained in:
evan.liu 2026-09-04 08:52:44 +08:00
parent 77ec340db2
commit 2117c140fc
99 changed files with 6799 additions and 5698 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
tools/.venv-ble/
tools/__pycache__/

View File

@ -15,7 +15,7 @@
- `mcm-ddc-04/` — 主工程(应用代码、MDK 工程文件、开发日志)
- `mcm-ddc-ble/` — 独立 BLE 工程(rdtss 蓝本 + FreeRTOS,APP1 链接形态)
- `Boot/` — bootloader 工程(`src/main.c`、`dfu_layout.h`、`boot_crc32.c`;后两个被 APP 工程以相对路径 `..\..\Boot\src` 共用)
- `tools/` — 烧录包工具:`merge_image.py`(合并 boot + bootsetting 缺省记录 + APP 为整片 hex/bin,支持 `merge_image.py [app_bin] [输出名]`)、`make_package.bat`(一键全量重建 `-r` + 合并)、`flash_package.bat`(NSpyocd 整片烧录),产物在 `tools/out/`
- `tools/` — 烧录包工具:`merge_image.py`(合并 boot + bootsetting 缺省记录 + APP 为整片 hex/bin,支持 `merge_image.py [app_bin] [输出名]`,自动从 `app_version.h` 取 `APP_FW_VERSION_NUM` 写入 OTA 清单)、`make_package.bat`(一键全量重建 `-r` + 合并)、`flash_package.bat`(NSpyocd 整片烧录),产物在 `tools/out/`;BLE 测试工具:`ble_cli_test.py` + `ble_cli.bat`(PC 端 bleak 蓝牙 CLI 客户端,支持帧协议与 `...c72e0003` 读写特征两种方式,venv 在 `tools/.venv-ble`,首次运行 `ble_cli.bat` 自动创建)
- `nations-tec/` — 厂商资源:SDK V2.0.0、DFP 器件包、STB 开发板资料、官方 PDF 文档。**这些内容为只读参考资料,不要修改。**
- `app/`、`docs/` — 顶层空目录,预留占位
@ -74,6 +74,8 @@ SDK 源码(固件库、CMSIS、FreeRTOS V9.0.0、BLE 协议栈/profile/ns_libr
烧录与运行:
**固件版本号约定(重要)**:每次修改固件代码必须递增 `mcm-ddc-ble/inc/app_version.h` 中的 `APP_FW_VERSION` 与 `APP_FW_VERSION_NUM`(格式 `0x00MMmmpp`,通常补丁位 +1),用于识别板上实际运行的固件;固件工程名与出包文件名称保持不变。`tools/merge_image.py` 出包时自动把 `APP_FW_VERSION_NUM` 写入 OTA 清单(`_ota.json`,显式传第三个参数可覆盖)。
1. Keil 打开 `MDK-ARM/embeddedSrc.uvprojx`,F7 编译
2. NS-LINK 接 SWD(PA4/PA5),F8 下载——Boot 跳转不校验镜像 CRC,Keil 直接下载 APP 即可正常启动调试;**但 Keil 下载选项必须是"Erase Sectors",整片擦除会杀掉 Boot**(杀掉后独立运行不启动,需重烧整片包恢复)
3. 独立运行(脱调试器)需烧整片包:`tools\flash_package.bat tools\out\caiic_full.hex`(改代码后先 `make_package.bat` 重新出包)。**板子上烧过其他 0x01000000 起步的程序会覆盖 Boot/bootsetting,恢复靠重烧整片包**

Binary file not shown.

Binary file not shown.

View File

@ -3,7 +3,7 @@
<title>Static Call Graph - [.\Objects\caiic_boot.axf]</title></head>
<body><HR>
<H1>Static Call Graph for image .\Objects\caiic_boot.axf</H1><HR>
<BR><P>#&#060CALLGRAPH&#062# ARM Linker, 5060750: Last Updated: Wed Sep 02 21:53:18 2026
<BR><P>#&#060CALLGRAPH&#062# ARM Linker, 5060750: Last Updated: Fri Sep 04 08:45:45 2026
<BR><P>
<H3>Maximum Stack Usage = 736 bytes + Unknown(Cycles, Untraceable Function Pointers)</H3><H3>
Call chain for Maximum Stack Depth:</H3>

Binary file not shown.

View File

@ -702,3 +702,49 @@ mcm-ddc-04 目录已由用户删除,mcm-ddc-ble 成为唯一应用工程。本
- 8.3 字节级完整示例表(CLI/INFO/OTA 全业务,CRC 均为真实计算值);
- 8.4 时序与异常处理:CLI 串行执行、OTA 滑动窗口流控与断点重同步、
notify 跨包重组要求、温度/风扇特殊值约定。
## 27. BLE 打印开关 + 收发报文打印(2026-09-03)
- 新增 `blelog [on|off]` CLI 命令(缺省关),开关状态只存 RAM(复位即关)。
- 打开后:
- 每个完整收/发协议帧在串口 hex 打印:`[BLE RX]/[BLE TX] type= seq= len= + payload
预览(超 24B 截断打 ...`);RX 在 CRC 校验通过分发前打印,TX 在组帧入队前打印;
- 连接/断开事件打印(`[BLE] connected` / `disconnected, advertising restarted`)。
- 打印走 printf(fputc 轮询 UART),不经过 BLE 通道,避免 CLI-over-BLE 自激。
- 用途:手机 APP 联调时对照 ble_protocol.md §8.3 的示例帧逐字节核对。
- 本版 `Code=35104 RO=3672 RW=2016 ZI=26912`,整包/OTA 镜像已重新生成
(_ota.bin 40312B,crc32=0xCAE8E135),SP=0x2000B100 与 map 一致。
## 28. CLI 读写特征 + 只读参数特征 + 温度采样修复(2026-09-04,V1.00.02~V1.00.08)
- **CLI 读写特征 `...c72e0003`(V1.00.02)**:RD + WRITE_REQ(带响应写)。手机写原始
命令行(≤63B,不套 0xCA 帧),设备执行后读同一特征取回输出文本(≤512B,
ATT long read 由协议栈自动分段)。`app_ble_proto_cli_exec()` 捕获输出到静态缓冲,
`rdtss_value_req_ind_handler` 按 att_idx 分发读请求。整条链路不依赖 notify。
- **blelog 可见性增强(V1.00.02)**:rdtss 写回调原始 dump 从编译期剔除的
NS_LOG_DEBUG 改为 blelog 门控 printf(`[BLE] write ind: att_idx= len= ...`),
新增 CCCD 订阅/退订打印(`[BLE] notify enabled/disabled`);修正 CCCD 值解析
(`value[0]+value[1]` → 小端合成)。
- **只读参数特征 `...c72e0004`(V1.00.04)**:读出为全部信息项 TLV,与
INFO_RSP(all) 一致;`app_info.c` 抽出 `app_info_tlv_snapshot()` 与帧应答共用组包。
- **温度采样修复(V1.00.03/05/08)**:现象 `devinfo` 显示 287.6°C。定位过程:
首版加 5ms 稳定+丢弃首次转换(05 加到 20ms)无效;devinfo 加 `(adc= trim=)`
诊断后确认 adc≈115(贴近地的浮空读数,trim=681 正常)——**规律是每次开机后
第一次 TS 采样为垃圾值,之后全部正常**(V1.00.05 稳态 272 采样点 20~26°C)。
最终修复:ADC 初始化时做预热转换并丢弃(`info_adc_init` 尾部)。
- **教训记录(V1.00.06/07 → 08 撤回)**:曾按勘误表 7.1 条加
`*(uint32_t*)0x40011004 |= 0x40`(HSI 设计 ADC 时钟修复),但该地址是
AFEC+0x04,与 BLE 协议栈共享(rwip_driver.c 写 0x20,ns_sleep.c 保存/恢复,
pwr.c 的 bit6 是无 HSE 睡眠唤醒专用),误写导致首次 ADC 读取后 BLE 射频挂死、
设备停止广播。V1.00.08 已撤回该写操作。寄存器级勘误修复必须先在 SDK 源码
交叉验证地址用途。
- **工具**:新增 PC 端 bleak 测试客户端 `tools/ble_cli_test.py`(帧协议/读写特征
两种方式,venv `tools/.venv-ble`)、一键启动器 `tools/ble_cli.bat`、温度监测
`tools/ble_temp_watch.py`;`make_package.bat` 改指 mcm-ddc-ble(mcm-ddc-04 已删);
`merge_image.py` 出包自动从 `app_version.h` 取 `APP_FW_VERSION_NUM` 写入 OTA 清单。
- **版本号约定**(已写入 AGENTS.md):每次修改固件递增 `app_version.h` 的
`APP_FW_VERSION`/`APP_FW_VERSION_NUM`(0x00MMmmpp 补丁位 +1),文件名不变。
- 本版 V1.00.08 `Code=35864 RO=3760 RW=2020 ZI=27532`,整包/OTA 镜像已重新生成
(_ota.bin 41168B,crc32=0x26056D5B)。
- **已知未决**:notify 上行链路不通(帧协议 CLI/INFO_QUERY/OTA 应答收不到),
下行写入正常;新读写/只读特征已绕开 notify,OTA 仍依赖它,待后续定位。

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,6 @@
.\objects\app_ble.o: ..\src\app_ble.c
.\objects\app_ble.o: D:\Keil_v5\ARM\ARMCC\Bin\..\include\string.h
.\objects\app_ble.o: D:\Keil_v5\ARM\ARMCC\Bin\..\include\stdio.h
.\objects\app_ble.o: ..\..\nations-tec\N32WB03x_SDK_V2.0.0\firmware\CMSIS\device\n32wb03x.h
.\objects\app_ble.o: ..\..\nations-tec\N32WB03x_SDK_V2.0.0\firmware\CMSIS\core\core_cm0.h
.\objects\app_ble.o: D:\Keil_v5\ARM\ARMCC\Bin\..\include\stdint.h
@ -42,7 +43,6 @@
.\objects\app_ble.o: ..\..\nations-tec\N32WB03x_SDK_V2.0.0\middlewares\Nationstech\ble_library\ns_ble_stack\modules\rwip\api\rwip_config.h
.\objects\app_ble.o: ..\inc\app_user_config.h
.\objects\app_ble.o: ..\..\nations-tec\N32WB03x_SDK_V2.0.0\middlewares\Nationstech\ble_library\ns_library\adv\ns_adv_data_def.h
.\objects\app_ble.o: D:\Keil_v5\ARM\ARMCC\Bin\..\include\stdio.h
.\objects\app_ble.o: ..\..\nations-tec\N32WB03x_SDK_V2.0.0\middlewares\Nationstech\ble_library\ns_library\log\ns_log.h
.\objects\app_ble.o: ..\..\nations-tec\N32WB03x_SDK_V2.0.0\middlewares\Nationstech\ble_library\ns_ble_stack\ip\ble\ll\api\rwble_config.h
.\objects\app_ble.o: ..\..\nations-tec\N32WB03x_SDK_V2.0.0\middlewares\Nationstech\ble_library\ns_ble_stack\ip\ble\hl\inc\rwble_hl_config.h

View File

@ -6,3 +6,4 @@
.\objects\app_ble_proto.o: ..\inc\app_ota.h
.\objects\app_ble_proto.o: ..\inc\app_info.h
.\objects\app_ble_proto.o: D:\Keil_v5\ARM\ARMCC\Bin\..\include\string.h
.\objects\app_ble_proto.o: D:\Keil_v5\ARM\ARMCC\Bin\..\include\stdio.h

View File

@ -125,4 +125,5 @@
.\objects\app_rdtss.o: ..\..\nations-tec\N32WB03x_SDK_V2.0.0\middlewares\Nationstech\ble_library\ns_ble_profile\rdts\rdtss\api\rdtss.h
.\objects\app_rdtss.o: ..\..\nations-tec\N32WB03x_SDK_V2.0.0\middlewares\Nationstech\ble_library\ns_ble_profile\rdts\rdts_common.h
.\objects\app_rdtss.o: ..\inc\app_ble_proto.h
.\objects\app_rdtss.o: ..\inc\app_info.h
.\objects\app_rdtss.o: ..\inc\ble_up.h

Binary file not shown.

Binary file not shown.

View File

@ -32,6 +32,7 @@
.\objects\cli_core.o: ..\..\nations-tec\N32WB03x_SDK_V2.0.0\firmware\n32wb03x_std_periph_driver\inc\misc.h
.\objects\cli_core.o: ..\inc\app_gpio.h
.\objects\cli_core.o: ..\inc\app_info.h
.\objects\cli_core.o: ..\inc\app_ble_proto.h
.\objects\cli_core.o: D:\Keil_v5\ARM\ARMCC\Bin\..\include\stdio.h
.\objects\cli_core.o: D:\Keil_v5\ARM\ARMCC\Bin\..\include\string.h
.\objects\cli_core.o: D:\Keil_v5\ARM\ARMCC\Bin\..\include\stdarg.h

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -29,28 +29,28 @@ Project File Date: 09/03/2026
Rebuild target 'N32WB03x'
assembling startup_n32wb03x.s...
compiling system_n32wb03x.c...
compiling n32wb03x_rcc.c...
compiling misc.c...
compiling n32wb03x_crc.c...
compiling n32wb03x_adc.c...
compiling n32wb03x_exti.c...
compiling n32wb03x_dma.c...
compiling n32wb03x_usart.c...
compiling misc.c...
compiling n32wb03x_gpio.c...
compiling n32wb03x_spi.c...
compiling n32wb03x_adc.c...
compiling n32wb03x_rcc.c...
compiling n32wb03x_tim.c...
compiling n32wb03x_lpuart.c...
compiling n32wb03x_qflash.c...
compiling lib_att.c...
compiling bass.c...
compiling bass_task.c...
compiling prf.c...
compiling bass.c...
compiling diss.c...
compiling prf_utils.c...
compiling diss_task.c...
compiling rdtss.c...
compiling ns_error.c...
compiling rdtss_task.c...
compiling ns_error.c...
compiling rdts_common.c...
compiling app_batt.c...
compiling rwip.c...
@ -58,30 +58,30 @@ compiling rwip_driver.c...
compiling bsp_usart.c...
compiling cli_core.c...
compiling ns_sleep.c...
compiling ns_sec.c...
compiling app_cli.c...
compiling ns_timer.c...
compiling ble_up.c...
compiling app_cli.c...
compiling app_rdtss.c...
compiling app_ble.c...
compiling app_dis.c...
compiling ns_sec.c...
compiling ns_ble_task.c...
compiling ns_ble.c...
compiling app_ble_proto.c...
compiling app_rdtss.c...
compiling n32wb03x_it.c...
compiling boot_crc32.c...
compiling app_ble.c...
compiling app_info.c...
compiling app_ota.c...
compiling main.c...
compiling app_ble_proto.c...
compiling boot_crc32.c...
compiling app_info.c...
compiling list.c...
compiling timers.c...
compiling heap_4.c...
compiling app_gpio.c...
compiling app_ota.c...
compiling queue.c...
compiling tasks.c...
compiling app_gpio.c...
compiling port.c...
linking...
Program Size: Code=34564 RO-data=3592 RW-data=2016 ZI-data=26912
Program Size: Code=35864 RO-data=3760 RW-data=2020 ZI-data=27532
After Build - User command #1: fromelf --bin --output=.\bin\mcm-ddc-ble.bin .\Objects\mcm-ddc-ble.axf
".\Objects\mcm-ddc-ble.axf" - 0 Error(s), 0 Warning(s).
@ -96,7 +96,7 @@ Package Vendor: Nationstech
D:\Keil_v5\Arm\Packs\Nationstech\N32WB03X_DFP\1.4.0\firmware\CMSIS\device
<h2>Collection of Component Files used:</h2>
Build Time Elapsed: 00:00:05
Build Time Elapsed: 00:00:06
</pre>
</body>
</html>

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -2,28 +2,28 @@
Rebuild target 'N32WB03x'
assembling startup_n32wb03x.s...
compiling system_n32wb03x.c...
compiling n32wb03x_rcc.c...
compiling misc.c...
compiling n32wb03x_crc.c...
compiling n32wb03x_adc.c...
compiling n32wb03x_exti.c...
compiling n32wb03x_dma.c...
compiling n32wb03x_usart.c...
compiling misc.c...
compiling n32wb03x_gpio.c...
compiling n32wb03x_spi.c...
compiling n32wb03x_adc.c...
compiling n32wb03x_rcc.c...
compiling n32wb03x_tim.c...
compiling n32wb03x_lpuart.c...
compiling n32wb03x_qflash.c...
compiling lib_att.c...
compiling bass.c...
compiling bass_task.c...
compiling prf.c...
compiling bass.c...
compiling diss.c...
compiling prf_utils.c...
compiling diss_task.c...
compiling rdtss.c...
compiling ns_error.c...
compiling rdtss_task.c...
compiling ns_error.c...
compiling rdts_common.c...
compiling app_batt.c...
compiling rwip.c...
@ -31,30 +31,30 @@ compiling rwip_driver.c...
compiling bsp_usart.c...
compiling cli_core.c...
compiling ns_sleep.c...
compiling ns_sec.c...
compiling app_cli.c...
compiling ns_timer.c...
compiling ble_up.c...
compiling app_cli.c...
compiling app_rdtss.c...
compiling app_ble.c...
compiling app_dis.c...
compiling ns_sec.c...
compiling ns_ble_task.c...
compiling ns_ble.c...
compiling app_ble_proto.c...
compiling app_rdtss.c...
compiling n32wb03x_it.c...
compiling boot_crc32.c...
compiling app_ble.c...
compiling app_info.c...
compiling app_ota.c...
compiling main.c...
compiling app_ble_proto.c...
compiling boot_crc32.c...
compiling app_info.c...
compiling list.c...
compiling timers.c...
compiling heap_4.c...
compiling app_gpio.c...
compiling app_ota.c...
compiling queue.c...
compiling tasks.c...
compiling app_gpio.c...
compiling port.c...
linking...
Program Size: Code=34564 RO-data=3592 RW-data=2016 ZI-data=26912
Program Size: Code=35864 RO-data=3760 RW-data=2020 ZI-data=27532
After Build - User command #1: fromelf --bin --output=.\bin\mcm-ddc-ble.bin .\Objects\mcm-ddc-ble.axf
".\Objects\mcm-ddc-ble.axf" - 0 Error(s), 0 Warning(s).
Build Time Elapsed: 00:00:05
Build Time Elapsed: 00:00:06

File diff suppressed because one or more lines are too long

View File

@ -1114,7 +1114,7 @@
<Group>
<GroupName>USER</GroupName>
<tvExp>0</tvExp>
<tvExp>1</tvExp>
<tvExpOptDlg>0</tvExpOptDlg>
<cbSel>0</cbSel>
<RteFlg>0</RteFlg>

View File

@ -65,6 +65,28 @@ void app_ble_proto_rx_bytes(const uint8_t* data, uint16_t len);
*/
void app_ble_proto_on_disconnect(void);
/**
* @brief BLE traffic log switch (default off). When on, every complete
* RX/TX frame is hex-dumped to the UART via printf, and BLE
* connect/disconnect events are printed too.
*/
void app_ble_proto_log_set(uint8_t on);
uint8_t app_ble_proto_log_get(void);
/**
* @brief Execute one raw CLI command line written to the CLI read/write
* characteristic (no frame, no notify). The command output text is
* captured into an internal buffer for app_ble_proto_cli_rsp().
* Called from the rdtss write indication handler (BLE task context).
*/
void app_ble_proto_cli_exec(const uint8_t* cmd, uint16_t len);
/**
* @brief Get the output of the last app_ble_proto_cli_exec() call.
* Served as the ATT read value of the CLI read/write characteristic.
*/
const uint8_t* app_ble_proto_cli_rsp(uint16_t* out_len);
#ifdef __cplusplus
}
#endif

View File

@ -32,11 +32,21 @@ extern "C" {
*/
void app_info_handle_query(uint8_t seq, const uint8_t* ids, uint16_t n);
/**
* @brief TLV snapshot of ALL info items (identical content to an
* INFO_RSP(all) frame), served as the read value of the read-only
* device info characteristic. Returns a static buffer.
*/
const uint8_t* app_info_tlv_snapshot(uint16_t* out_len);
/* Data sources (also usable from the CLI) */
/** Chip temperature in 0.1 degC; 0x7FFF when the ADC read timed out. */
int16_t app_info_chip_temp_c10(void);
/** Last raw TS ADC reading (debug; 0xFFFF = timeout). */
uint16_t app_info_last_temp_raw(void);
/** VDD voltage in mV; 0 when the ADC read timed out. */
uint16_t app_info_vdd_mv(void);

View File

@ -82,6 +82,8 @@
#define ATT_SERVICE_AM_SPEED_128 {0x01,0x10,0x2E,0xC7,0x8a,0x0E, 0x73,0x90, 0xE1,0x11, 0xC2,0x08, 0x60,0x27,0x00,0x00} /*!< Service UUID */
#define ATT_CHAR_AM_SPEED_WRITE_128 {0x01,0x00,0x2E,0xC7,0x8a,0x0E, 0x73,0x90, 0xE1,0x11, 0xC2,0x08, 0x60,0x27,0x00,0x00} /*!< Characteristic value UUID */
#define ATT_CHAR_AM_SPEED_NTF_128 {0x02,0x00,0x2E,0xC7,0x8a,0x0E, 0x73,0x90, 0xE1,0x11, 0xC2,0x08, 0x60,0x27,0x00,0x00} /*!< Characteristic value UUID */
#define ATT_CHAR_AM_SPEED_CLI_128 {0x03,0x00,0x2E,0xC7,0x8a,0x0E, 0x73,0x90, 0xE1,0x11, 0xC2,0x08, 0x60,0x27,0x00,0x00} /*!< CLI Characteristic value UUID (read + write) */
#define ATT_CHAR_AM_SPEED_INFO_128 {0x04,0x00,0x2E,0xC7,0x8a,0x0E, 0x73,0x90, 0xE1,0x11, 0xC2,0x08, 0x60,0x27,0x00,0x00} /*!< Device info Characteristic value UUID (read-only TLV) */
/// rdtss Service Attributes Indexes
@ -97,6 +99,12 @@ enum
RDTSS_IDX_NTF_VAL,
RDTSS_IDX_NTF_CFG,
RDTSS_IDX_CLI_CHAR,
RDTSS_IDX_CLI_VAL,
RDTSS_IDX_INFO_CHAR,
RDTSS_IDX_INFO_VAL,
RDTSS_IDX_NB,
};

View File

@ -5,8 +5,8 @@
#ifndef __APP_VERSION_H__
#define __APP_VERSION_H__
#define APP_FW_VERSION "V1.00.01"
#define APP_FW_VERSION "V1.00.08"
/* Numeric form used by the BLE info query / OTA records: 0x00MMmmpp */
#define APP_FW_VERSION_NUM 0x00010001u
#define APP_FW_VERSION_NUM 0x00010008u
#endif /* __APP_VERSION_H__ */

View File

@ -54,6 +54,7 @@
* @copyright Copyright (c) 2025, NSING Technologies Inc. All rights reserved.
*/
#include <string.h>
#include <stdio.h>
#include "n32wb03x.h"
#include "gapm_task.h" // GAP Manager Task API
#include "ns_sec.h"
@ -315,6 +316,10 @@ void app_ble_connected(void)
{
LedOn(LED2_PORT,LED2_PIN);
notify_en = 0;
if (app_ble_proto_log_get())
{
printf("[BLE] connected\r\n");
}
/* Raise the ATT MTU so OTA_DATA frames can carry ~233B per write
* (device-initiated exchange; the phone may accept a smaller value) */
ns_ble_mtu_set(247);
@ -338,6 +343,10 @@ void app_ble_disconnected(void)
app_ble_proto_on_disconnect(); /* drop half RX frame + uplink buffer */
LedOff(LED2_PORT,LED2_PIN);
notify_en = 0;
if (app_ble_proto_log_get())
{
printf("[BLE] disconnected, advertising restarted\r\n");
}
}

View File

@ -20,6 +20,7 @@
#include "app_info.h"
#include <string.h>
#include <stdio.h>
/* Frame layout */
#define BLE_FRAME_SOF 0xCA
@ -52,6 +53,41 @@ static uint8_t s_rxState;
static uint8_t s_txSeq; /* device -> phone seq counter */
static uint8_t s_txFrame[BLE_PROTO_TX_FRAME_SIZE];
static uint8_t s_logEn; /* traffic log switch (default off) */
/* ------------------------------------------------------------------ */
/* Traffic log (UART printf; payload preview capped to keep lines short) */
/* ------------------------------------------------------------------ */
#define BLE_LOG_DUMP_MAX 24u
static void proto_dump(const char* dir, uint8_t type, uint8_t seq,
const uint8_t* payload, uint16_t len)
{
uint16_t i;
uint16_t n = (len > BLE_LOG_DUMP_MAX) ? BLE_LOG_DUMP_MAX : len;
printf("[BLE %s] type=0x%02X seq=%u len=%u", dir, type, seq, len);
for (i = 0; i < n; i++)
{
printf(" %02X", payload[i]);
}
if (len > n)
{
printf(" ...");
}
printf("\r\n");
}
void app_ble_proto_log_set(uint8_t on)
{
s_logEn = (on != 0) ? 1u : 0u;
}
uint8_t app_ble_proto_log_get(void)
{
return s_logEn;
}
/* ------------------------------------------------------------------ */
/* CRC16-CCITT (init 0xFFFF, poly 0x1021) */
@ -105,6 +141,10 @@ int app_ble_proto_send_frame(uint8_t type, uint8_t seq, const uint8_t* payload,
s_txFrame[5 + len] = (uint8_t)(crc & 0xFFu);
s_txFrame[5 + len + 1] = (uint8_t)(crc >> 8);
if (s_logEn)
{
proto_dump("TX", type, seq, payload, len);
}
ble_up_write(s_txFrame, frameLen);
return 0;
}
@ -127,6 +167,67 @@ static void proto_cli_out(const char* str)
}
}
/* ------------------------------------------------------------------ */
/* CLI over the read/write characteristic: the phone writes a raw */
/* command line (write-with-response) and reads the output text back. */
/* ------------------------------------------------------------------ */
#define CLI_RW_CMD_MAX 63 /* same line limit as the frame CLI */
#define CLI_RW_RSP_MAX 512 /* ATT value length budget (0x200) */
static uint8_t s_cliRsp[CLI_RW_RSP_MAX];
static uint16_t s_cliRspLen;
/* cli_out_fn capturing the command output into s_cliRsp */
static void cli_rsp_out(const char* str)
{
while (*str != '\0' && s_cliRspLen < CLI_RW_RSP_MAX)
{
s_cliRsp[s_cliRspLen++] = (uint8_t)*str++;
}
}
void app_ble_proto_cli_exec(const uint8_t* cmd, uint16_t len)
{
static char s_line[CLI_RW_CMD_MAX + 1];
int status = 1; /* default: rejected */
s_cliRspLen = 0;
if (len > 0 && len <= CLI_RW_CMD_MAX)
{
memcpy(s_line, cmd, len);
s_line[len] = '\0';
/* same set/exec/restore policy as the frame CLI path */
cli_set_output(cli_rsp_out);
status = cli_exec_line(s_line);
cli_set_output(NULL);
}
if (s_cliRspLen == 0)
{
const char* fb = (status == 0) ? "ok\r\n" : "unknown command\r\n";
uint16_t n = (uint16_t)strlen(fb);
memcpy(s_cliRsp, fb, n);
s_cliRspLen = n;
}
if (s_logEn)
{
uint16_t i;
printf("[BLE] cli exec: \"");
for (i = 0; i < len && i < CLI_RW_CMD_MAX; i++)
{
printf("%c", (cmd[i] >= 32 && cmd[i] < 127) ? cmd[i] : '.');
}
printf("\" -> %uB rsp\r\n", s_cliRspLen);
}
}
const uint8_t* app_ble_proto_cli_rsp(uint16_t* out_len)
{
*out_len = s_cliRspLen;
return s_cliRsp;
}
/* ------------------------------------------------------------------ */
/* RX path */
/* ------------------------------------------------------------------ */
@ -136,6 +237,11 @@ static void proto_cli_out(const char* str)
*/
static void proto_dispatch_frame(uint8_t type, uint8_t seq, const uint8_t* payload, uint16_t len)
{
if (s_logEn)
{
proto_dump("RX", type, seq, payload, len);
}
switch (type)
{
case BLE_FRAME_CLI_REQ:

View File

@ -25,6 +25,15 @@
#define INFO_ADC_TIMEOUT 100000u /* ADC_DONE poll loop limit */
static uint8_t s_adc_ready;
static uint16_t s_last_temp_raw = 0xFFFFu; /* last raw TS ADC reading (debug) */
/** Last raw TS ADC reading taken by app_info_chip_temp_c10 (0xFFFF = timeout). */
uint16_t app_info_last_temp_raw(void)
{
return s_last_temp_raw;
}
static uint16_t info_adc_read(uint16_t channel);
/* ------------------------------------------------------------------ */
/* ADC data sources */
@ -44,6 +53,15 @@ static void info_adc_init(void)
ADC_SetOverSampleCounter(ADC, 3);
ADC_EnableBypassFilter(ADC, ENABLE);
s_adc_ready = 1;
/* warm-up: the first conversion(s) after ADC power-on/config are garbage
* (observed on board: first TS read after boot returns near-ground codes,
* later reads are correct); run the TS channel twice and discard */
ADC_EnableTS(ADC, ENABLE);
vTaskDelay(pdMS_TO_TICKS(20));
(void)info_adc_read(ADC_CTRL_CH_7);
(void)info_adc_read(ADC_CTRL_CH_7);
ADC_EnableTS(ADC, DISABLE);
}
/**
@ -78,8 +96,15 @@ int16_t app_info_chip_temp_c10(void)
uint16_t raw;
ADC_EnableTS(ADC, ENABLE);
/* the temperature sensor is powered down between reads and its output
* starts near 0V on enable; give it time to settle, then discard the
* first (mid-ramp) conversion - otherwise the reading is far too low
* and the formula reports absurd temperatures like 287.6 C */
vTaskDelay(pdMS_TO_TICKS(20));
(void)info_adc_read(ADC_CTRL_CH_7);
raw = info_adc_read(ADC_CTRL_CH_7);
ADC_EnableTS(ADC, DISABLE);
s_last_temp_raw = raw;
if (raw == 0xFFFFu)
{
return 0x7FFF;
@ -166,6 +191,32 @@ static uint16_t info_fill_item(uint8_t* buf, uint16_t fill, uint8_t id)
}
}
/* all info items, in report order */
static const uint8_t s_all_items[] = {
BLE_INFO_FW_VERSION, BLE_INFO_CHIP_TEMP, BLE_INFO_FAN_RPM,
BLE_INFO_VDD_MV, BLE_INFO_UPTIME_S, BLE_INFO_FREE_HEAP,
};
/**
* @brief Build the TLV sequence of ALL info items into a static buffer.
* Served as the ATT read value of the read-only info characteristic
* (same content as an INFO_RSP(all) frame).
*/
const uint8_t* app_info_tlv_snapshot(uint16_t* out_len)
{
/* worst case: 6 items x (2B header + 4B value) = 36B */
static uint8_t s_tlv[40];
uint16_t fill = 0;
uint16_t i;
for (i = 0; i < sizeof(s_all_items); i++)
{
fill = info_fill_item(s_tlv, fill, s_all_items[i]);
}
*out_len = fill;
return s_tlv;
}
void app_info_handle_query(uint8_t seq, const uint8_t* ids, uint16_t n)
{
/* worst case: 6 items x (2B header + 4B value) = 36B */
@ -173,15 +224,10 @@ void app_info_handle_query(uint8_t seq, const uint8_t* ids, uint16_t n)
uint16_t fill = 0;
uint16_t i;
static const uint8_t s_all[] = {
BLE_INFO_FW_VERSION, BLE_INFO_CHIP_TEMP, BLE_INFO_FAN_RPM,
BLE_INFO_VDD_MV, BLE_INFO_UPTIME_S, BLE_INFO_FREE_HEAP,
};
if (n == 0 || (n == 1 && ids[0] == 0xFFu))
{
ids = s_all;
n = sizeof(s_all);
ids = s_all_items;
n = sizeof(s_all_items);
}
for (i = 0; i < n; i++)

View File

@ -74,6 +74,7 @@
#include "stdio.h"
#include "rdtss.h"
#include "app_ble_proto.h"
#include "app_info.h"
#include "ble_up.h"
/* Private typedef -----------------------------------------------------------*/
@ -100,6 +101,18 @@ const struct attm_desc_128 app_rdts_att_db[RDTSS_IDX_NB] =
[5] = {ATT_CHAR_AM_SPEED_NTF_128, PERM(NTF, ENABLE), PERM(RI, ENABLE)| PERM_VAL(UUID_LEN, 0x02), 0x200 },
/* Client Characteristic Configuration Descriptor */
[6] = {{0x02,0x29}, PERM(RD, ENABLE) |PERM(WRITE_REQ, ENABLE), PERM(RI, ENABLE), 20 },
/* Characteristic Declaration */
[7] = {{0x03,0x28}, PERM(RD, ENABLE) | PERM(WRITE_REQ, ENABLE), 0, 0 },
/* CLI Characteristic Value: raw command line in (write-with-response),
* last command output text out (read; long read handled by the stack) */
[8] = {ATT_CHAR_AM_SPEED_CLI_128, PERM(RD, ENABLE) | PERM(WRITE_REQ, ENABLE), PERM(RI, ENABLE)| PERM_VAL(UUID_LEN, 0x02), 0x200 },
/* Characteristic Declaration */
[9] = {{0x03,0x28}, PERM(RD, ENABLE) | PERM(WRITE_REQ, ENABLE), 0, 0 },
/* Device info Characteristic Value (read-only): TLV snapshot of all
* info items, identical content to an INFO_RSP(all) frame */
[10] = {ATT_CHAR_AM_SPEED_INFO_128, PERM(RD, ENABLE), PERM(RI, ENABLE)| PERM_VAL(UUID_LEN, 0x02), 0x200 },
};
/* Private function prototypes -----------------------------------------------*/
@ -154,14 +167,25 @@ static int rdtss_value_req_ind_handler(ke_msg_id_t const msgid,
NS_LOG_DEBUG("%s\r\n",__func__);
// Initialize length
uint8_t len = 0;
uint16_t len = 0;
// Pointer to the data
uint8_t *data = NULL;
len = APP_RDTSS_MANUFACTURER_NAME_LEN;
data = (uint8_t *)APP_RDTSS_MANUFACTURER_NAME;
const uint8_t *data = NULL;
if (req_value->att_idx == RDTSS_IDX_CLI_VAL)
{
// CLI read/write characteristic: return the last command's output text
data = app_ble_proto_cli_rsp(&len);
}
else if (req_value->att_idx == RDTSS_IDX_INFO_VAL)
{
// device info characteristic: TLV snapshot of all info items
data = app_info_tlv_snapshot(&len);
}
else
{
len = APP_RDTSS_MANUFACTURER_NAME_LEN;
data = (uint8_t *)APP_RDTSS_MANUFACTURER_NAME;
}
// Allocate confirmation to send the value
struct rdtss_value_req_rsp *rsp_value = KE_MSG_ALLOC_DYN(RDTSS_VALUE_REQ_RSP,
src_id, dest_id,
@ -193,33 +217,52 @@ static int rdtss_val_write_ind_handler(ke_msg_id_t const msgid,
ke_task_id_t const dest_id,
ke_task_id_t const src_id)
{
NS_LOG_DEBUG("%s,write handle = %x,length = %x\r\n",__func__ ,ind_value->handle, ind_value->length);
for(uint16_t i=0; i<ind_value->length; i++)
{
NS_LOG_DEBUG("%x ",ind_value->value[i]);
}
NS_LOG_DEBUG("\r\n");
uint16_t handle = ind_value->handle;
uint16_t length = ind_value->length;
/* Raw write dump, gated by the "blelog" CLI switch (the former NS_LOG_DEBUG
* dump was compiled out, leaving the downlink path completely silent) */
if (app_ble_proto_log_get())
{
uint16_t i;
uint16_t n = (length > 24) ? 24 : length;
printf("[BLE] write ind: att_idx=%u len=%u", handle, length);
for (i = 0; i < n; i++)
{
printf(" %02X", ind_value->value[i]);
}
if (length > n)
{
printf(" ...");
}
printf("\r\n");
}
switch (handle)
{
case RDTSS_IDX_NTF_CFG:
if(length == 2)
{
uint16_t cfg_value = ind_value->value[0] + ind_value->value[1];
uint16_t cfg_value = (uint16_t)ind_value->value[0] |
((uint16_t)ind_value->value[1] << 8);
if(cfg_value == PRF_CLI_START_NTF)
{
//enabled notify
notify_en = 1;
notify_en = 1;
if (app_ble_proto_log_get())
{
printf("[BLE] notify enabled\r\n");
}
}
else if(cfg_value == PRF_CLI_STOP_NTFIND)
{
notify_en = 0;
notify_en = 0;
if (app_ble_proto_log_get())
{
printf("[BLE] notify disabled\r\n");
}
}
}
@ -228,6 +271,12 @@ static int rdtss_val_write_ind_handler(ke_msg_id_t const msgid,
//ble data receive -> frame reassembler (CLI_REQ etc.)
app_ble_proto_rx_bytes(ind_value->value, ind_value->length);
break;
case RDTSS_IDX_CLI_VAL:
// raw CLI command line (write-with-response); output is fetched
// back through an ATT read of this same characteristic
app_ble_proto_cli_exec(ind_value->value, ind_value->length);
break;
default:
break;

View File

@ -11,6 +11,8 @@
#include "bsp_usart.h"
#include "app_gpio.h"
#include "app_info.h"
#include "app_ble_proto.h"
#include "n32wb03x.h" /* SystemTrimValueGet (temp debug) */
#include <stdio.h>
#include <string.h>
@ -190,7 +192,11 @@ static void CmdDevInfo(int argc, char* argv[])
}
else
{
cli_printf("chip temp: %d.%d C\r\n", (int)(temp / 10), (int)(temp % 10));
trim_stored_t *p_trim = SystemTrimValueGet();
cli_printf("chip temp: %d.%d C (adc=%u trim=%lu)\r\n",
(int)(temp / 10), (int)(temp % 10),
(unsigned int)app_info_last_temp_raw(),
(p_trim != NULL) ? (unsigned long)p_trim->rc_adc_ts_25c : 0ul);
}
cli_printf("vdd: %u mV\r\n", (unsigned int)app_info_vdd_mv());
if (fan == APP_FAN_RPM_INVALID)
@ -206,12 +212,38 @@ static void CmdDevInfo(int argc, char* argv[])
cli_printf("free heap: %u B\r\n", (unsigned int)xPortGetFreeHeapSize());
}
/**
* @brief "blelog [on|off]": BLE traffic log switch (connect/disconnect
* events + hex dump of every RX/TX protocol frame on the UART).
*/
static void CmdBleLog(int argc, char* argv[])
{
if (argc >= 2)
{
if (strcmp(argv[1], "on") == 0)
{
app_ble_proto_log_set(1);
}
else if (strcmp(argv[1], "off") == 0)
{
app_ble_proto_log_set(0);
}
else
{
cli_write("usage: blelog [on|off]\r\n");
return;
}
}
cli_printf("ble log: %s\r\n", app_ble_proto_log_get() ? "on" : "off");
}
/* Command table; add new commands here */
static const CliCmd_t s_cmds[] = {
{"help", "list all commands", CmdHelp},
{"version", "show firmware version", CmdVersion},
{"sysinfo", "show FreeRTOS runtime info (uptime/tasks/heap/task list)", CmdSysInfo},
{"devinfo", "show device info (temp/vdd/fan, same as BLE INFO_QUERY all)", CmdDevInfo},
{"blelog", "blelog [on|off]: BLE traffic log (events + frame hex dump)", CmdBleLog},
{"led", "led <1|2> <on|off|toggle>: control board LED", CmdLed},
};

54
tools/ble_cli.bat Normal file
View File

@ -0,0 +1,54 @@
@echo off
rem ============================================================
rem ble_cli.bat - BLE CLI test client for CAIIC-MCM devices
rem
rem ble_cli.bat interactive mode (rw characteristic ...0003)
rem ble_cli.bat help one-shot single-word command over rw char
rem ble_cli.bat version sysinfo
rem ble_cli.bat -f interactive, legacy framed protocol (notify)
rem ble_cli.bat -f help one-shot framed command
rem
rem Multi-word commands (e.g. led 1 on): use interactive mode.
rem Requires tools\.venv-ble (created automatically on first run).
rem ============================================================
setlocal
set TOOLS=%~dp0
set PYEXE=%TOOLS%.venv-ble\Scripts\python.exe
if not exist "%PYEXE%" (
echo [setup] creating venv %TOOLS%.venv-ble ...
set PYREAL=
if exist "%LOCALAPPDATA%\Programs\Python\Python312\python.exe" set PYREAL="%LOCALAPPDATA%\Programs\Python\Python312\python.exe"
if not defined PYREAL (
py -3 --version >nul 2>&1 && set PYREAL=py -3
)
if not defined PYREAL set PYREAL=python
%PYREAL% -m venv "%TOOLS%.venv-ble" || goto :fail
"%PYEXE%" -m pip install --quiet bleak || goto :fail
)
set MODE=-rw
if "%~1"=="-f" (
set MODE=
shift
)
set ARGS=
:build
if "%~1"=="" goto :run
set ARGS=%ARGS% %1
shift
goto :build
:run
if "%ARGS%"=="" (
"%PYEXE%" "%TOOLS%ble_cli_test.py" %MODE% -i
) else (
"%PYEXE%" "%TOOLS%ble_cli_test.py" %MODE%%ARGS%
)
goto :eof
:fail
echo.
echo *** setup failed - check python installation ***
exit /b 1

267
tools/ble_cli_test.py Normal file
View File

@ -0,0 +1,267 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
ble_cli_test.py - PC-side BLE test client for the CAIIC-MCM frame protocol
(V1.1), used to verify the mcm-ddc-ble firmware downlink/uplink without a
phone in the loop.
venv: .venv-ble (python -m venv .venv-ble && pip install bleak)
usage: .venv-ble/Scripts/python ble_cli_test.py # framed CLI: send "help"
.venv-ble/Scripts/python ble_cli_test.py "sysinfo" # framed CLI: one command
.venv-ble/Scripts/python ble_cli_test.py -i # interactive framed mode
.venv-ble/Scripts/python ble_cli_test.py -rw help # CLI via the new read/write
# characteristic ...0003
.venv-ble/Scripts/python ble_cli_test.py -rw -i # interactive rw mode
in framed interactive mode: "info" sends an INFO_QUERY (all items)
Watch the device UART (blelog on) in parallel: a healthy exchange prints
[BLE] notify enabled / [BLE] write ind: att_idx=2 ... / [BLE RX] / [BLE TX].
"""
import asyncio
import sys
from bleak import BleakClient, BleakScanner
NAME_PREFIX = "CAIIC-MCM-20260902"
SERVICE_UUID = "00002760-08c2-11e1-9073-0e8ac72e1001"
WRITE_UUID = "00002760-08c2-11e1-9073-0e8ac72e0001" # Write Without Response (framed protocol)
NOTIFY_UUID = "00002760-08c2-11e1-9073-0e8ac72e0002" # Notify
CLI_RW_UUID = "00002760-08c2-11e1-9073-0e8ac72e0003" # Read + Write: raw CLI line in, output text out
INFO_RD_UUID = "00002760-08c2-11e1-9073-0e8ac72e0004" # Read-only: device info TLV (all items)
TYPE_CLI_REQ = 0x01
TYPE_CLI_RSP = 0x02
TYPE_CLI_RSP_END = 0x03
TYPE_INFO_QUERY = 0x20
TYPE_INFO_RSP = 0x21
FRAME_SOF = 0xCA
MAX_PAYLOAD = 480
CLI_TIMEOUT_S = 5.0
INFO_TIMEOUT_S = 3.0
def crc16_ccitt(data):
"""CRC16-CCITT, poly 0x1021, init 0xFFFF (same as firmware proto_crc16)."""
crc = 0xFFFF
for b in data:
crc ^= b << 8
for _ in range(8):
if crc & 0x8000:
crc = ((crc << 1) ^ 0x1021) & 0xFFFF
else:
crc = (crc << 1) & 0xFFFF
return crc
def encode_frame(ftype, seq, payload):
"""SOF + TYPE + SEQ + LEN(u16 LE) + PAYLOAD + CRC16(over TYPE..PAYLOAD, LE)."""
out = bytearray([FRAME_SOF, ftype & 0xFF, seq & 0xFF,
len(payload) & 0xFF, (len(payload) >> 8) & 0xFF])
out += bytes(payload)
crc = crc16_ccitt(out[1:])
out += bytes([crc & 0xFF, (crc >> 8) & 0xFF])
return bytes(out)
class FrameDecoder:
"""Byte-stream reassembly, mirrors firmware app_ble_proto_rx_bytes."""
def __init__(self):
self.buf = bytearray()
def feed(self, data):
frames = []
self.buf += bytes(data)
while True:
while self.buf and self.buf[0] != FRAME_SOF:
del self.buf[0]
if len(self.buf) < 5:
break
plen = self.buf[3] | (self.buf[4] << 8)
if plen > MAX_PAYLOAD:
del self.buf[0]
continue
total = 5 + plen + 2
if len(self.buf) < total:
break
crc = crc16_ccitt(self.buf[1:5 + plen])
rx_crc = self.buf[5 + plen] | (self.buf[5 + plen + 1] << 8)
if crc != rx_crc:
del self.buf[0]
continue
frames.append((self.buf[1], self.buf[2], bytes(self.buf[5:5 + plen])))
del self.buf[:total]
return frames
class McmTestClient:
def __init__(self):
self.decoder = FrameDecoder()
self.tx_seq = 0
self.cli_chunks = []
self.cli_done = None # asyncio.Event, set on CLI_RSP_END
self.cli_status = -1
self.info_done = None # asyncio.Event, set on INFO_RSP
self.info_payload = None
def on_notify(self, _sender, data):
frames = self.decoder.feed(data)
for ftype, seq, payload in frames:
print("[frame] type=0x%02X seq=%u payload=%s"
% (ftype, seq, payload.hex(" ")))
if ftype == TYPE_CLI_RSP:
self.cli_chunks.append(payload.decode("ascii", "replace"))
elif ftype == TYPE_CLI_RSP_END:
self.cli_status = payload[0] if payload else 1
if self.cli_done:
self.cli_done.set()
elif ftype == TYPE_INFO_RSP:
self.info_payload = payload
if self.info_done:
self.info_done.set()
async def send_frame(self, client, ftype, payload):
frame = encode_frame(ftype, self.tx_seq, payload)
self.tx_seq = (self.tx_seq + 1) & 0xFF
print("[tx] %s" % frame.hex(" "))
# downlink characteristic is Write Without Response only
await client.write_gatt_char(WRITE_UUID, frame, response=False)
async def run_cli(self, client, cmd):
payload = cmd.encode("ascii")
if len(payload) > 63:
print("command too long (>63 bytes)")
return
self.cli_chunks = []
self.cli_status = -1
self.cli_done = asyncio.Event()
await self.send_frame(client, TYPE_CLI_REQ, payload)
try:
await asyncio.wait_for(self.cli_done.wait(), CLI_TIMEOUT_S)
except asyncio.TimeoutError:
print("[rx] TIMEOUT - no CLI_RSP_END within %.0fs" % CLI_TIMEOUT_S)
return
text = "".join(self.cli_chunks)
print("[rx] status=%d%s" % (self.cli_status, ("\n" + text) if text else " (ok)"))
async def run_info_query(self, client):
self.info_payload = None
self.info_done = asyncio.Event()
await self.send_frame(client, TYPE_INFO_QUERY, b"")
try:
await asyncio.wait_for(self.info_done.wait(), INFO_TIMEOUT_S)
except asyncio.TimeoutError:
print("[rx] TIMEOUT - no INFO_RSP within %.0fs" % INFO_TIMEOUT_S)
return
print("[rx] INFO_RSP TLV: %s" % self.info_payload.hex(" "))
async def run_cli_rw(client, cmd):
"""CLI over the read/write characteristic ...0003: write the raw command
line (ATT write-with-response), then read the output text back."""
print("[tx rw] %r" % cmd)
await client.write_gatt_char(CLI_RW_UUID, cmd.encode("ascii"), response=True)
await asyncio.sleep(0.1) # one conn. interval for the device to execute
rsp = await client.read_gatt_char(CLI_RW_UUID)
print("[rx rw] %dB:\n%s" % (len(rsp), rsp.decode("ascii", "replace")))
_INFO_ITEM_NAMES = {0x01: "fw_version", 0x02: "chip_temp", 0x03: "fan_rpm",
0x04: "vdd_mv", 0x05: "uptime_s", 0x06: "free_heap"}
async def run_param_read(client):
"""Read the read-only device-info characteristic ...0004 and decode the
TLV sequence {id u8, len u8, value LE}*n."""
data = bytes(await client.read_gatt_char(INFO_RD_UUID))
print("[rx rd] %dB TLV: %s" % (len(data), data.hex(" ")))
i = 0
while i + 2 <= len(data):
iid, ilen = data[i], data[i + 1]
if i + 2 + ilen > len(data):
break
raw = data[i + 2:i + 2 + ilen]
name = _INFO_ITEM_NAMES.get(iid, "0x%02X" % iid)
val = int.from_bytes(raw, "little")
if iid == 0x01 and ilen == 4:
val = "V%d.%02d.%02d" % (val >> 16, (val >> 8) & 0xFF, val & 0xFF)
elif iid == 0x02 and ilen == 2:
t = int.from_bytes(raw, "little", signed=True)
val = "invalid" if t == 0x7FFF else "%.1f C" % (t / 10.0)
elif iid == 0x03 and ilen == 2 and val == 0xFFFF:
val = "n/a"
print(" %-10s %s" % (name, val))
i += 2 + ilen
async def find_device():
print("scanning for %s* ..." % NAME_PREFIX)
while True:
dev = await BleakScanner.find_device_by_filter(
lambda d, ad: d.name and d.name.startswith(NAME_PREFIX),
timeout=10.0)
if dev is not None:
return dev
print(" not found, retrying (device advertising? phone disconnected?)")
async def main():
args = sys.argv[1:]
interactive = "-i" in args
use_rw = "-rw" in args
cmds = [a for a in args if a not in ("-i", "-rw")] or ["help"]
dev = await find_device()
print("connecting %s (%s) ..." % (dev.name, dev.address))
async with BleakClient(dev) as client:
print("connected, mtu=%d" % client.mtu_size)
tester = McmTestClient()
if not use_rw:
await client.start_notify(NOTIFY_UUID, tester.on_notify)
print("notify subscribed on %s" % NOTIFY_UUID)
# give the device a moment; it also initiates its own MTU exchange
await asyncio.sleep(0.5)
if interactive:
print("interactive mode (%s), type a CLI command ('exit'):"
% ("rw-char, 'param' = read info char" if use_rw
else "framed, 'info' = INFO_QUERY, 'param' = read info char"))
while True:
line = (await asyncio.to_thread(input, "ble> ")).strip()
if line in ("exit", "quit"):
break
if not line:
continue
if use_rw:
if line == "param":
await run_param_read(client)
else:
await run_cli_rw(client, line)
elif line == "info":
await tester.run_info_query(client)
elif line == "param":
await run_param_read(client)
else:
await tester.run_cli(client, line)
else:
for cmd in cmds:
if cmd == "param":
await run_param_read(client)
elif use_rw:
await run_cli_rw(client, cmd)
elif cmd == "info":
await tester.run_info_query(client)
else:
await tester.run_cli(client, cmd)
if not use_rw:
await client.stop_notify(NOTIFY_UUID)
print("disconnected")
if __name__ == "__main__":
try:
asyncio.run(main())
except KeyboardInterrupt:
pass

49
tools/ble_temp_watch.py Normal file
View File

@ -0,0 +1,49 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
ble_temp_watch.py - poll the device "devinfo" CLI command over the read/write
characteristic every 2s and print chip temperature + raw ADC/trim values.
usage: .venv-ble/Scripts/python ble_temp_watch.py
Use it to verify the TS reading: warm the chip with a finger and watch the
adc value rise / temperature change. Ctrl+C to stop.
"""
import asyncio
import re
import time
from bleak import BleakClient
from ble_cli_test import CLI_RW_UUID, find_device
POLL_S = 2.0
TEMP_RE = re.compile(r"chip temp: (\S+) C \(adc=(\d+) trim=(\d+)\)")
async def main():
dev = await find_device()
print("connecting %s (%s) ..." % (dev.name, dev.address))
async with BleakClient(dev) as client:
print("connected, mtu=%d - polling devinfo every %.0fs, Ctrl+C to stop"
% (client.mtu_size, POLL_S))
print("time temp adc trim")
while True:
await client.write_gatt_char(CLI_RW_UUID, b"devinfo", response=True)
await asyncio.sleep(0.1)
rsp = (await client.read_gatt_char(CLI_RW_UUID)).decode("ascii", "replace")
m = TEMP_RE.search(rsp)
if m:
print("%s %-7s %-5s %s"
% (time.strftime("%H:%M:%S"), m.group(1), m.group(2), m.group(3)))
else:
print("%s (parse failed) %r" % (time.strftime("%H:%M:%S"), rsp[:80]))
await asyncio.sleep(POLL_S)
if __name__ == "__main__":
try:
asyncio.run(main())
except KeyboardInterrupt:
pass

View File

@ -1,7 +1,7 @@
@echo off
rem ============================================================
rem make_package.bat - build bootloader + APP, then merge into
rem one flash package (tools/out/caiic_full.*)
rem one flash package (tools/out/caiic_ble_full.*)
rem Run from anywhere: tools\make_package.bat
rem ============================================================
setlocal
@ -14,9 +14,9 @@ if errorlevel 1 goto :fail
findstr /C:"0 Error(s), 0 Warning(s)" "%ROOT%\Boot\MDK-ARM\build.log" >nul || goto :fail
echo [2/3] Building APP...
%UV4% -r "%ROOT%\mcm-ddc-04\MDK-ARM\embeddedSrc.uvprojx" -j0 -o "%ROOT%\mcm-ddc-04\MDK-ARM\build.log"
%UV4% -r "%ROOT%\mcm-ddc-ble\MDK-ARM\mcm-ddc-ble.uvprojx" -j0 -o "%ROOT%\mcm-ddc-ble\MDK-ARM\build.log"
if errorlevel 1 goto :fail
findstr /C:"0 Error(s), 0 Warning(s)" "%ROOT%\mcm-ddc-04\MDK-ARM\build.log" >nul || goto :fail
findstr /C:"0 Error(s), 0 Warning(s)" "%ROOT%\mcm-ddc-ble\MDK-ARM\build.log" >nul || goto :fail
echo [3/3] Merging images...
set PYEXE=
@ -30,10 +30,10 @@ if not defined PYEXE (
echo python not found - install Python 3 or fix PATH
goto :fail
)
%PYEXE% "%~dp0merge_image.py" || goto :fail
%PYEXE% "%~dp0merge_image.py" "%ROOT%\mcm-ddc-ble\MDK-ARM\bin\mcm-ddc-ble.bin" caiic_ble_full || goto :fail
echo.
echo Package ready: tools\out\caiic_full.hex / caiic_full.bin
echo Package ready: tools\out\caiic_ble_full.hex / caiic_ble_full.bin
goto :eof
:fail

View File

@ -33,6 +33,7 @@ Run from anywhere; paths are resolved relative to this script.
"""
import json
import os
import re
import struct
import sys
import zlib
@ -100,12 +101,28 @@ def load_bin(path, desc):
return f.read()
def read_app_fw_version(root):
"""Extract APP_FW_VERSION_NUM from the APP project's app_version.h.
Returns None when not found."""
hdr = os.path.join(root, "mcm-ddc-ble", "inc", "app_version.h")
if not os.path.isfile(hdr):
return None
m = re.search(r"#define\s+APP_FW_VERSION_NUM\s+(0x[0-9a-fA-F]+|\d+)u?",
open(hdr, encoding="utf-8").read())
return int(m.group(1), 0) if m else None
def main():
root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
app_rel = sys.argv[1] if len(sys.argv) > 1 else DEFAULT_APP_BIN
out_name = sys.argv[2] if len(sys.argv) > 2 else "caiic_full"
version = int(sys.argv[3], 0) if len(sys.argv) > 3 else 0
# version: explicit argv wins, otherwise take APP_FW_VERSION_NUM from the
# firmware source so the OTA manifest always matches the flashed image
if len(sys.argv) > 3:
version = int(sys.argv[3], 0)
else:
version = read_app_fw_version(root) or 0
boot_blob = load_bin(os.path.join(root, BOOT_BIN), "bootloader")
app_path = app_rel if os.path.isabs(app_rel) else os.path.join(root, app_rel)

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@ -1,6 +1,6 @@
{
"file": "caiic_ble_full_ota.bin",
"size": 39692,
"crc32": "0xF15790D8",
"version": 65537
"size": 41168,
"crc32": "0x26056D5B",
"version": 65544
}