From 57762e50beb42ffb70d8af7ccf7b7db10102bda2 Mon Sep 17 00:00:00 2001 From: "evan.liu" <583229071@qq.com> Date: Sat, 5 Sep 2026 10:34:59 +0800 Subject: [PATCH] tools: self-contained portable production package - tools/python-embed/: embedded Python 3.12.10 runtime with bleak/pyserial preinstalled (git-ignored, fetched from the huaweicloud mirror); bats resolve Python in order: python-embed -> .venv-ble -> system - NSpyocd.exe + DFP pack copied into tools/ (git-ignored); flash_package.py prefers the in-tools copies - make_release.bat/ps1: one-click zip to out/mothercup_tools_.zip (staged in TEMP to avoid self-inclusion; zip excluded from git) - all bats converted to CRLF (LF-only breaks cmd block parsing and %~dp0) - verified: zip extracted outside the repo runs ble_ota --uart end-to-end (PASS, 27 kB/s) and flash_package via the bundled NSpyocd - docs: dev log section 49, AGENTS.md tools section + heap 18KB fix --- .gitignore | 6 ++++++ AGENTS.md | 10 ++++++---- docs/开发日志.md | 19 +++++++++++++++++++ tools/ble_cli.bat | 6 ++++-- tools/ble_ota.bat | 6 ++++-- tools/flash_package.bat | 8 ++++++-- tools/flash_package.py | 14 +++++++++++--- tools/make_package.bat | 6 +++++- tools/make_release.bat | 11 +++++++++++ tools/make_release.ps1 | 14 ++++++++++++++ 10 files changed, 86 insertions(+), 14 deletions(-) create mode 100644 tools/make_release.bat create mode 100644 tools/make_release.ps1 diff --git a/.gitignore b/.gitignore index 60e9715..5040cbf 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,9 @@ mcm-ddc-ble/MDK-ARM/Objects mcm-ddc-ble/MDK-ARM/build.log mcm-ddc-ble/MDK-ARM/build-app2.log mcm-ddc-ble/MDK-ARM/Objects-app2 + +# portable production runtime (bundled, not committed) +tools/python-embed/ +tools/NSpyocd/ +tools/N32WB03x_DFP.1.4.0.pack +tools/out/mothercup_tools_*.zip diff --git a/AGENTS.md b/AGENTS.md index 5d7cc31..cd37a86 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -148,17 +148,19 @@ mcm-ddc-ble/ ## PC 工具(tools/) -- `merge_image.py` — 纯 Python 无三方依赖;合并 boot+bootsetting(缺省记录,脚本生成)+APP 为整片 hex/bin,同时产出 `_ota.bin`/`_ota.json`(bank1 载荷);`--ota-app2` 追加 bank2 载荷 `_ota_app2.bin/.json`;`--app2 --with-appdata` 生成双 bank 整包(make_dual_package.bat 使用);自动从 `app_version.h` 取 `APP_FW_VERSION_NUM` 写入清单(含 `target_bank` 字段) -- `make_package.bat` / `flash_package.bat` — 一键出包 / NSpyocd 整片烧录(脚本会自动找 Python312 全路径,本机 `python` 可能是商店占位 stub) +**自包含生产包(§49)**:tools/ 内嵌 Python 运行时(`python-embed/`,bleak+pyserial 预装)+ NSpyocd 烧录器副本 + DFP pack,`make_release.bat` 一键打 zip 到 `out/mothercup_tools_<时间戳>.zip`——解压到任何 Windows 电脑即可用,无需安装 Python/任何环境。三件套不入库(gitignore):`python-embed/`、`NSpyocd/`、`N32WB03x_DFP.1.4.0.pack`;**bat 文件必须保持 CRLF 行尾**(LF 会导致 cmd 解析错乱)。Python 解析优先级:`python-embed` → `.venv-ble`(开发机自动建)→ 系统 Python。 + +- `merge_image.py` — 纯 Python 无三方依赖;合并 boot+bootsetting(缺省记录,脚本生成)+APP 为整片 hex/bin,同时产出 `_ota.bin`/`_ota.json`(bank1 载荷);`--ota-app2` 追加 bank2 载荷 `_ota_app2.bin/.json`;`--app2 --with-appdata` 生成双 bank 整包(make_dual_package.bat 使用);自动从 `app_version.h` 取 `APP_FW_VERSION_NUM` 写入清单(含 `target_bank` 字段);**出包硬卡:镜像初始 SP 必须在 (0x20004000, 0x2000C000](SRAM 悬崖,§47)** +- `make_package.bat` / `flash_package.bat` — 一键出包 / NSpyocd 整片烧录(flash_package.py 流式转发输出并把厂商横幅替换为 "CAIIC NSLINK UMP") - `make_dual_package.bat` — 双 APP 整包(Boot+bootsetting 双 bank+APP_DATA+APP1+APP2,APP2 升一版链接 0x01024000),用于 appsw 切换测试 - `ble_cli_test.py` + `ble_cli.bat` — PC 端 bleak 蓝牙 CLI 测试客户端(帧协议模式 + `-rw` 读写特征模式,交互模式 `-i`,venv 在 `tools/.venv-ble`,不入库;首次运行 bat 按 `tools/requirements-ble.txt`(bleak+pyserial)自动创建) -- `ble_ota_update.py` + `ble_ota.bat` — BLE OTA 升级器(解析单文件升级包 `mothercup_ble_ota.bin`、按 CUR_BANK 选对侧 bank 载荷、经 OTA 特征 `...e0005` 逐帧锁步、复位检测+重连校验;`--uart COMx` 走串口二进制模式:握手标记进入/ABORT 退出;V1.00.24 起默认流式(8KB 窗口+扇区 ack+go-back-N),`--lockstep` 或旧固件自动回退锁步)。**BLE 与 UART 双通道均已实测通过(§42/§44),流式见 §47** +- `ble_ota_update.py` + `ble_ota.bat` — BLE OTA 升级器(解析单文件升级包 `mothercup_ble_ota.bin`、按 CUR_BANK 选对侧 bank 载荷、经 OTA 特征 `...e0005` 逐帧锁步、复位检测+重连校验;`--uart COMx` 走串口二进制模式(460800 8N1,V1.00.26 起):握手标记进入/ABORT 退出;V1.00.24 起默认流式(8KB 窗口+扇区 ack+go-back-N),`--lockstep` 或旧固件自动回退锁步)。**BLE 与 UART 双通道均已实测通过(§42/§44),流式见 §47/§48** - `ble_temp_watch.py` — 温度监测小工具 ## 实时架构约定(固件) - 使用 FreeRTOS V9.0.0 原生 API,**不使用 cmsis_os 封装**。 -- 任务:BLE 调度任务(栈 512 字,优先级 2,循环 `rwip_schedule(); vTaskDelay(1ms);`——**ke_msg/ke_timer 只允许在该任务上下文运行**);LED 任务(LED1 闪烁半周期读 APP_DATA 参数 `led1_blink_ms`);CliTask(优先级 3);CLI 输出经环形缓冲由 BLE 任务上下文发 notify(`ble_up.c`)。`configTOTAL_HEAP_SIZE = 20KB`(heap_4)。 +- 任务:BLE 调度任务(栈 512 字,优先级 2,循环 `rwip_schedule(); vTaskDelay(1ms);`——**ke_msg/ke_timer 只允许在该任务上下文运行**);LED 任务(LED1 闪烁半周期读 APP_DATA 参数 `led1_blink_ms`);CliTask(优先级 3);CLI 输出经环形缓冲由 BLE 任务上下文发 notify(`ble_up.c`)。`configTOTAL_HEAP_SIZE = 18KB`(heap_4;SRAM 悬崖约束见 Flash/RAM 布局铁律)。 - SysTick 由 FreeRTOS port 接管:`main` 中不要手动 `SysTick_Config`,`n32wb03x_it.c` 中不要重复定义 SVC/PendSV/SysTick_Handler。 - USART1_IRQn 优先级为 3(最低),因 ISR 内调用 FreeRTOS FromISR API,必须 ≥ `configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY`(=3)。BLE_SW/FIFO IRQ 优先级 0,其 ISR 内禁止调用 FromISR API。 - 串口输出并发:多任务/CLI 输出分别持有 `bsp_usart_tx_lock()` 互斥锁;调度器启动前为空操作。不要在中断或多个任务里直接 printf。 diff --git a/docs/开发日志.md b/docs/开发日志.md index a439920..d74be49 100644 --- a/docs/开发日志.md +++ b/docs/开发日志.md @@ -1245,3 +1245,22 @@ APP2 链接的镜像发向 APP1 区。设备端 END 校验(向量表 Reset PC 在 460800 下正常(devinfo/ota 握手/复位后复核全走文本 CLI)。 - 对比:115200 锁步 39s → 115200 流式 5.9s → 460800 流式 2.6s。 - 注意:串口终端/工具必须同步改 460800,否则 CLI 全是乱码。 + +## 49. tools 自包含生产包(2026-09-05,无固件改动) + +- 目标:tools/ 打包即可发布到任何 Windows 产线电脑,无需安装任何环境。 +- **内嵌 Python 运行时**:`tools/python-embed/`(python-3.12.10-embed-amd64, + 华为云镜像下载;python312._pth 启用 Lib/site-packages + import site, + bleak 3.0.2/pyserial 3.5 用 pip --target 预装,29MB)。bat 优先级: + python-embed → .venv-ble(开发机自动建)→ 系统 Python。 +- **烧录器收编**:NSpyocd.exe + DFP pack 复制进 tools/(flash_package.py + 优先用 tools 内副本,找不到才回退仓库 nations-tec 路径)。 +- **一键打包**:`make_release.bat`(PowerShell Compress-Archive,排除 + .venv-ble/__pycache__,zip 暂存 %TEMP% 再移入 out/——直接写 out/ 会 + 自读自写报错)。 +- **坑**:bat 必须 CRLF 行尾——LF-only 时 cmd 对括号块/%~dp0 的解析会 + 错乱(standalone 复测时 venv 分支被误触发且 PYREAL 为空),本次全部 + 转 CRLF。zip 用 Windows 资源管理器/Expand-Archive 解压(Info-ZIP + unzip 对 Compress-Archive 的反斜杠分隔符不兼容)。 +- 验证:zip 解到仓库外独立目录,ble_ota.bat --uart COM4 全流程 PASS + (2.2s,27 kB/s,0 重传);flash_package.bat 用包内 NSpyocd 烧录 PASS。 diff --git a/tools/ble_cli.bat b/tools/ble_cli.bat index 1bd6d4a..6dc94a2 100644 --- a/tools/ble_cli.bat +++ b/tools/ble_cli.bat @@ -13,8 +13,10 @@ rem Requires tools\.venv-ble (created automatically on first run). rem ============================================================ setlocal set TOOLS=%~dp0 -set PYEXE=%TOOLS%.venv-ble\Scripts\python.exe - +rem 优先使用随包内嵌的 Python 运行时(tools\python-embed,免安装); +rem 没有则回退到自动创建的 .venv-ble(开发机) +set PYEXE=%TOOLS%python-embed\python.exe +if not exist "%PYEXE%" set PYEXE=%TOOLS%.venv-ble\Scripts\python.exe if not exist "%PYEXE%" ( echo [setup] creating venv %TOOLS%.venv-ble ... set PYREAL= diff --git a/tools/ble_ota.bat b/tools/ble_ota.bat index 8b2b8fe..0599adb 100644 --- a/tools/ble_ota.bat +++ b/tools/ble_ota.bat @@ -13,8 +13,10 @@ rem Requires tools\.venv-ble (created automatically on first run). rem ============================================================ setlocal set TOOLS=%~dp0 -set PYEXE=%TOOLS%.venv-ble\Scripts\python.exe - +rem 优先使用随包内嵌的 Python 运行时(tools\python-embed,免安装); +rem 没有则回退到自动创建的 .venv-ble(开发机) +set PYEXE=%TOOLS%python-embed\python.exe +if not exist "%PYEXE%" set PYEXE=%TOOLS%.venv-ble\Scripts\python.exe if not exist "%PYEXE%" ( echo [setup] creating venv %TOOLS%.venv-ble ... set PYREAL= diff --git a/tools/flash_package.bat b/tools/flash_package.bat index 8bb5944..d91bd43 100644 --- a/tools/flash_package.bat +++ b/tools/flash_package.bat @@ -9,8 +9,12 @@ rem and CLOSE Keil (it holds the probe). rem ============================================================ setlocal -set PYEXE= -python --version >nul 2>&1 && set PYEXE=python +rem 优先使用随包内嵌的 Python 运行时(tools\python-embed,免安装) +set PYEXE=%~dp0python-embed\python.exe +if not exist "%PYEXE%" ( + set PYEXE= + python --version >nul 2>&1 && set PYEXE=python +) if not defined PYEXE ( if exist "%LOCALAPPDATA%\Programs\Python\Python312\python.exe" ( set PYEXE="%LOCALAPPDATA%\Programs\Python\Python312\python.exe" diff --git a/tools/flash_package.py b/tools/flash_package.py index 247cd26..6f97fe2 100644 --- a/tools/flash_package.py +++ b/tools/flash_package.py @@ -26,12 +26,20 @@ BANNER_PATTERNS = [ ] ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -PYOCD = os.path.join(ROOT, "nations-tec", "N32WB03x_SDK_V2.0.0", - "utilities", "dfu", "NSpyocd", "NSpyocd.exe") +TOOLS = os.path.dirname(os.path.abspath(__file__)) + +# Prefer the copies shipped inside tools/ (portable production package); +# fall back to the repo's nations-tec/ SDK layout (developer machine). +_local_pyocd = os.path.join(TOOLS, "NSpyocd", "NSpyocd.exe") +_local_pack = os.path.join(TOOLS, "N32WB03x_DFP.1.4.0.pack") +PYOCD = _local_pyocd if os.path.isfile(_local_pyocd) else \ + os.path.join(ROOT, "nations-tec", "N32WB03x_SDK_V2.0.0", + "utilities", "dfu", "NSpyocd", "NSpyocd.exe") # The builtin "n32wb031" target only maps 256KB of flash (fails at # 0x01040000 on the dual-bank package); the DFP pack target # "n32wb031keq6_2" maps the full 512KB of the KEQ6-2 we actually use. -PACK = os.path.join(ROOT, "nations-tec", "N32WB03x_DFP.1.4.0.pack") +PACK = _local_pack if os.path.isfile(_local_pack) else \ + os.path.join(ROOT, "nations-tec", "N32WB03x_DFP.1.4.0.pack") TARGET = "n32wb031keq6_2" diff --git a/tools/make_package.bat b/tools/make_package.bat index bf02dab..53c3ce4 100644 --- a/tools/make_package.bat +++ b/tools/make_package.bat @@ -26,8 +26,12 @@ if errorlevel 1 goto :fail findstr /C:"0 Error(s), 0 Warning(s)" "%ROOT%\mcm-ddc-ble\MDK-ARM\build-app2.log" >nul || goto :fail echo [4/4] Merging images... +rem 优先随包内嵌的 Python 运行时(merge_image.py 只需标准库) set PYEXE= -python --version >nul 2>&1 && set PYEXE=python +if exist "%~dp0python-embed\python.exe" set PYEXE="%~dp0python-embed\python.exe" +if not defined PYEXE ( + python --version >nul 2>&1 && set PYEXE=python +) if not defined PYEXE ( if exist "%LOCALAPPDATA%\Programs\Python\Python312\python.exe" ( set PYEXE="%LOCALAPPDATA%\Programs\Python\Python312\python.exe" diff --git a/tools/make_release.bat b/tools/make_release.bat new file mode 100644 index 0000000..df6207b --- /dev/null +++ b/tools/make_release.bat @@ -0,0 +1,11 @@ +@echo off +rem ============================================================ +rem make_release.bat - pack tools\ into out\mothercup_tools_.zip +rem Self-contained production package: embedded Python runtime +rem (python-embed\) + NSpyocd flasher included - the target PC +rem needs nothing installed. Unzip anywhere and run the .bat files. +rem ============================================================ +setlocal +set PS=powershell +%PS% -noprofile -command "exit 0" >nul 2>&1 || set PS=%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe +%PS% -noprofile -executionpolicy bypass -file "%~dp0make_release.ps1" diff --git a/tools/make_release.ps1 b/tools/make_release.ps1 new file mode 100644 index 0000000..ae0a8e9 --- /dev/null +++ b/tools/make_release.ps1 @@ -0,0 +1,14 @@ +# make_release.ps1 - pack tools/ into a self-contained production zip. +# The zip holds the embedded Python runtime (python-embed/, bleak+pyserial +# preinstalled) and NSpyocd, so the target PC needs NO installation. +# Excludes the dev-only venv and caches; keeps out/ (the firmware images). +# NOTE: the zip is staged in $TEMP first - writing it into out/ while +# reading out/ would make Compress-Archive read its own half-written file. +$tools = Split-Path -Parent $MyInvocation.MyCommand.Path +$stamp = Get-Date -Format "yyyyMMdd_HHmm" +$stage = Join-Path $env:TEMP "mothercup_tools_$stamp.zip" +$final = Join-Path $tools "out\mothercup_tools_$stamp.zip" +$items = Get-ChildItem $tools | Where-Object { $_.Name -notin @('.venv-ble', '__pycache__') } +Compress-Archive -Path ($items | ForEach-Object { $_.FullName }) -DestinationPath $stage -Force -ErrorAction Stop +Move-Item $stage $final -Force +Write-Output "packed: $final"