Skill:TSMaster 二次开发通用机制(小程序 + 面板)
> 读完本 Skill,AI 能在一个陌生的 TSMaster 工程里从零建起「可控、可编译、可观测、可复用」的小程序与面板。
> 本文只写跨工程成立的机制;与具体被测对象/硬件/工程约定有关的内容一律用 <占位符>,用时按工程自身文档填。
0. 何时用
主要功能是写小程序(MiniProgram,C/.mp)和做测试面板(Panel)。
| 用户怎么说 | 去向 |
|---|---|
| 「写个测试程序/插件」「这段 C 怎么写进 MiniProgram」 | §3 全流程 |
| 「做个测试面板」「按钮/进度条/输入框怎么绑变量」 | §3 步骤 7、REFERENCE.md §5、TEMPLATES.md §7 |
| 「面板按钮点了没反应 / 变量改了面板不动 / 进度条不动」 | TROUBLESHOOTING.md §4 |
| 「怎么把代码和面板接起来」「这值该放系统变量还是全局变量」 | §3 步骤 2 |
| 「编译不过 / 编译成功但行为没变 / .mp 写不进去」 | §3 步骤 5、§5 |
| 「报告少了一条用例 / 中文乱码 / CSV 解析失败」 | TROUBLESHOOTING.md §5~§6 |
| 「MCP 调不动 / 报门禁错误」 | 铁律 15、TROUBLESHOOTING.md §2 |
> 涉及具体硬件 SDK(干扰仪、程控电源等)或被测评据时,机制照本 Skill,参数按工程自身文档补。
1. 铁律速查表
| # | 铁律 | 违反后果 |
|---|---|---|
| 1 | MiniProgram/CSrc/<程序名>.cpp 是唯一权威源,Build/<程序名>/ 全是派生物 |
编的是 Build 里的旧副本,改了「不生效」 |
| 2 | 只改 // CODE BLOCK BEGIN … END 之间;GEN BLOCK 与区块外一律会被重写 |
声明被静默删除,报「未声明」 |
| 3 | 一个 Custom_Function 区块只放一个函数,返回值必须是 s32;参数可以带(写进区块名后的 base64 签名) |
返回非 s32 → UI 不显示;参数用自定义 struct 指针 → <程序名>Extern.cpp 报 unknown type name(详见 REFERENCE.md §3.1) |
| 4 | On_Start 只做初始化:无等待、无循环、无阻塞调用 |
启动卡死,永远进不到 step |
| 5 | step 单次必须快速返回;长任务拆相位,单次耗时控制在看门狗阈值 1/10 内 |
停止按钮失灵、日志像重启(被看门狗杀) |
| 6 | 每个函数 try { } catch (...) 兜底,catch 里必须记日志并主动终止 |
未捕获异常直接崩宿主进程,且无日志 |
| 7 | 不用 goto 跳过变量初始化;不定义嵌套函数 |
clang 报 cannot jump past initialization / 嵌套定义非法 |
| 8 | 编译只认 bin/<程序名>.mp 时间戳与真实退出码,不认界面「成功」提示 |
产物没更新却以为编好了 |
| 9 | 编译前先查 bin/<程序名>.mp 是否被运行中的小程序占用 |
Permission denied,且界面可能仍提示成功 |
| 10 | 中文缓冲区按最坏长度留足(≥1024),写报告前修 UTF-8 尾部 | 截断出非法编码 → 整份报告生成失败 |
| 11 | 未跑通/未实现的用例返回 NA,绝不返回 PASS |
报告出现「假绿」,缺陷被掩盖 |
| 12 | 面板按钮是电平不是事件:瞬时动作必须读后清零;持续状态(勾选)不能清零 | 每帧触发一次,用例被启动 N 次 |
| 13 | 一切自己拼的路径以 get_configuration_file_path() 为基准,工程自定义「根变量」只作回退 |
拼出 \DB\CAN\*.pdbc 之类的错路径,全部用例 N/A |
| 14 | 系统变量清单的 Count= 必须等于实际条目数;变量全名一旦定下不许改 |
新变量静默不生效;改名后面板整片断绑 |
| 15 | code.run 之后必须读 Logging/<程序名>/system_messages/*.log |
门禁 10082 锁死全部 coding 动作,含只读的 block.counts |
2. 环境与前置事实
| 事实 | 值 | 验证方法 |
|---|---|---|
| 宿主进程 | 32 位(x86),任何自备 DLL 必须 x86 | instance_list 的 bitness |
| 产物形态 | bin/<程序名>.mp = 32 位 Windows DLL,导出 initialize_miniprogram / finalize_miniprogram / retrieve_mp_abilities / write_api_document_auto |
mp.def、dumpbin /exports |
| 内置编译器 | <TSMASTER_HOME>\Data\compilers\llvm_mingw_x86\bin\clang++.exe |
文件存在 |
| 官方构建脚本 | MiniProgram/Build/<程序名>/build_mp_x86.bat + build_mp_x86.rsp(由 TSMaster 生成,含绝对路径) |
目录内 ls |
| 构建单元 | 5 个 .cpp:Database / <程序名> / <程序名>Extern / TSMasterBaseSource / TSMasterCustomAPIDoc |
build_mp_x86.rsp 末尾 |
| 运行证据目录 | <工程根>\Logging\<程序名>\system_messages\ |
目录是否存在、有无新 .log |
| 报告目录 | <工程根>\AutoReport\<时间戳>\ |
目录内 testjsonreport.csv |
| 面板相关文件 | UI.ini(面板本体)、TSMaster.ini(变量定义 + 窗口位置 + 停靠布局)、Panels/*.TPnl |
见 REFERENCE.md §5 |
| 配置文件编码 | UI.ini / TSMaster.ini = UTF-8 with BOM,CRLF |
前 3 字节 EF BB BF |
占位符约定:
| 占位符 | 含义 | 如何获得 |
|---|---|---|
<工程根> |
工程根目录,不带结尾反斜杠 | app.get_configuration_file_path() 或 get_context.active_project_root |
<程序名> |
MiniProgram 名 = .mp 文件名去扩展名 |
bin/*.mp |
<TSMASTER_HOME> |
TSMaster 安装目录 | 进程 exe_path 的上级 |
<组> |
系统变量分组前缀,如 <组>.Ctrl_Start |
工程约定,全工程唯一 |
<用例名> |
用例可读名,放进 test_title 第 3 参数 |
必须全局唯一 |
<时间戳> |
报告目录名 yyyy_MM_dd_HH-mm-ss |
AutoReport 下最新子目录 |
<TS> |
TSMaster 版本 | get_context |
前置条件(缺一项先停下):
- [ ] 已知
<工程根>与<程序名>,且<工程根>/MiniProgram/Build/<程序名>/存在 - [ ] 改面板前 TSMaster 已完全退出(任务管理器确认无
TSMaster*进程) - [ ] 编译前已确认
.mp未被运行中的小程序占用
> Custom_Function 的返回值与参数规则(含迁移决策表)见 REFERENCE.md §3.1。
> 一句话:返回值必须 s32,参数可以带(写进区块名后的 base64 签名),
> 但自定义 struct 指针 / 函数指针 / 非 s32 返回值的函数只能留在 Global_Definitions。
3. 标准流程
<a id="step-1-source"></a>
步骤 1 — 定权威源与产物边界
- 动作:确认三件事——
<程序名>、CSrc/<程序名>.cpp是否存在、bin/<程序名>.mp是否已存在 - 产物:一张「唯一手改文件 / 派生物」清单
- 判据:能明确回答「我改哪个文件、产物落在哪」
- 失败:目录结构与
REFERENCE.md §1不符 → 先摸清实际结构,不要按文档硬套
<a id="step-2-contract"></a>
步骤 2 — 先定变量契约(L0/L1/L2)
- 动作:把要「用户可配 / 面板可见」的项写进系统变量(L0),把跨
step的运行态放s_静态全局(L1),case 内部状态留局部(L2) - 产物:变量表(全名、类型、默认值、界面控件)
- 判据:每条 L0 都能回答「谁在面板上改它」;每条 L1 都能回答「哪些 step 之间共享」
- 失败:变量命名/类型/
Count规则见REFERENCE.md §4
> 先定契约再写代码。变量名后续改动会同时打断面板绑定与代码引用。
<a id="step-3-skeleton"></a>
步骤 3 — 先写「能启动、能停止、能观测」的骨架
- 动作:贴
TEMPLATES.md §1骨架:On_Start复位状态、step心跳 + tick 安全阀 + 空状态机、On_Stop释放 - 产物:可编译、可运行、能自动退出、日志有心跳的空程序
- 判据:跑起来能看到
alive tick=…,且到TICK_LIMIT自动terminate - 失败:起不来先查
On_Start是否含等待/循环;停不下来查step是否阻塞(TROUBLESHOOTING.md §3)
> 不要跳这一步。 业务未完成时也要能安全退出,否则后续调试全部失去现场。
<a id="step-4-business"></a>
步骤 4 — 填业务:一次只加一个环节
- 动作:每个需要「等待外部响应」的用例都写成相位状态机(
TEMPLATES.md §2),
每个对外能力收敛到一个实现函数,未就绪时返回E_NOT_READY由用例判 NA - 产物:每个 case 一个
Custom_Function区块 + 判定写回报告 - 判据:单次
step内不出现Sleep/死等;每个 case 都有显式超时兜底 - 失败:卡死查相位是否漏
tc_arm_wait/漏超时定时器
<a id="step-5-build"></a>
步骤 5 — 同步 + 编译 + 查产物
- 动作三选一(判据见
REFERENCE.md §2):
1. TSMaster UI 内Ctrl+F9(自己处理占用,最省事)
2.ASSETS/compile_mp.ps1(参数化,能拿到完整 stderr)
3.build_mp_x86.bat(成功无 pause,失败会 pause,非交互调用会挂住) - 产物:
bin/<程序名>.mp时间戳更新 - 判据:退出码 0 且 产物时间戳晚于源文件
- 失败:先查占用,再读 stderr(
TROUBLESHOOTING.md §1)
<a id="step-6-evidence"></a>
步骤 6 — 跑 + 取证据
- 动作:运行 → 读
Logging/<程序名>/system_messages/*.log(过程)→ 解析AutoReport/<时间戳>/testjsonreport.csv(结论) - 产物:日志正文 +
TSPass/TSFail/TSNA计数 - 判据:报告条目数 = 预期用例数;日志无
log_nok级别的异常 - 失败:见
TROUBLESHOOTING.md §5、§6
> 程序必须自己在 step 首次进入时开 start_system_message_log(TEMPLATES.md §5),
> 否则证据目录永不产生,工具链门禁会被永久卡住。
<a id="step-7-panel"></a>
步骤 7 — 接面板(变量 → 控件 → 边沿)
- 动作:控件绑变量全名;瞬时按钮走边沿检测 + 读后清零;持续状态(勾选/进度)只写不清零
- 产物:可操作面板
- 判据:点一次按钮只触发一次;改代码里的变量名后,面板同步改绑定
- 失败:见
TROUBLESHOOTING.md §4
<a id="step-8-close"></a>
步骤 8 — 收尾核对
- 动作:停程序 → 确认
.mp可写(没被锁)→ 删探针残留 → 过 §4 清单 - 判据:§4 全部通过
- 失败:未通过项回对应步骤,不要带着问题进下一轮
4. 验证清单
- [ ] 改动全部落在
CODE BLOCK内(判据:diff 里无GEN BLOCK段被改) - [ ]
CSrc/<程序名>.cpp与Build/<程序名>/<程序名>.cpp差异只有构建时间戳(判据:Compare-Object后仅剩该行) - [ ] 编译退出码 = 0 且
bin/<程序名>.mp时间戳晚于源文件(判据:Get-Item比较) - [ ] 无重复函数定义(判据:
TEMPLATES.md §7查重命令输出为空) - [ ]
step单次耗时 < 看门狗阈值 1/10(判据:心跳日志间隔稳定,无「像重启」现象) - [ ] 每个函数都有
try/catch且 catch 有日志(判据:grepcatch (...)数量 = 函数数) - [ ]
Logging/<程序名>/system_messages/下有本轮新.log,且已读取内容 - [ ] 报告
TSPass + TSFail + TSNA= 预期用例数(判据:ASSETS/parse_report.py输出) - [ ] 报告中文正常、无未替换的
{{ }}占位符(判据:打开 html 抽查) - [ ] 面板按钮点一次只触发一次(判据:日志里同一动作只出现一条)
- [ ] 写面板前 TSMaster 已完全退出(判据:
Get-Process TSMaster*为空) - [ ] 探针残留 6 处已删(
CSrc/CSrcExtern/Build/libs/Logging/bin无同名项)
5. 排错表
| 现象(原文/可观测) | 根因 | 处置 |
|---|---|---|
ld.lld: error: failed to write output 'bin\<程序名>.mp': Permission denied |
小程序正在运行,DLL 被进程映射 | 先停小程序再编(#step-5-build) |
success:false / 退出码 1,但日志写「编译成功」、issues 为空 |
产物写不进去(占用),不是语法错 | 查 .mp 可写性,见 TROUBLESHOOTING.md §1 |
use of undeclared identifier 'xxx' |
声明写在 CODE BLOCK 外被生成器删了 | 移到 Global_Definitions |
cannot jump past initialization |
goto 跳过变量初始化 |
变量声明提前到 goto 之前 |
redefinition of 'xxx' |
同一函数被定义两次(脚本 + MCP 混用最常见) | 查重后删重复,再编 |
| 改了代码行为不变 | 编的是 Build/ 里的旧副本 |
同步 CSrc → Build 再编 |
| 点停止无响应 / 日志狂刷像重启 | 单次 step 阻塞超过看门狗阈值 |
长任务拆相位或移工作线程 |
| 启动即崩、无日志 | 未捕获异常 | 补 try/catch + 日志 |
| 按钮点一次触发 N 次 | 只读电平未清零 | 边沿检测 + 读后清零 |
| 面板变量改了不生效 | 变量名拼错 / 有重复定义 / 未重编 / Rate 太大 |
见 TROUBLESHOOTING.md §4 |
报告只生成 html、无 testjsonreport.json |
testjsonreport.csv 被中文截断或半截残留污染 |
缓冲区 ≥1024 + 修 UTF-8;删上一轮报告目录重跑 |
| 报告少一条用例 | 两个用例 test_title 用了同一个名字(后者被合并) |
用例名全局唯一,新增前先查重 |
> 未命中查 TROUBLESHOOTING.md(六类分组,含诊断命令)。
⛔ 不要做
| 不要做 | 会发生什么 | 改成 |
|---|---|---|
⛔ 在 On_Start 里 Sleep / 跑业务流程 |
主线程被占满,停止失效 | 只初始化,业务进 step |
⛔ 用 measurement stop 当「停用例」 |
它断的是应用连接,整条测量体系停掉 | 停用例用 code.stop |
| ⛔ 重启 TSMaster 清 10082 | 下次 code.run 立刻再锁,现场丢失 |
读 Logging/<程序名>/system_messages |
⛔ 手工伪造 Logging/<程序名>/system_messages 文件 |
占住真实路径,日志永不再产生 | 程序内调 start_system_message_log |
⛔ 同一文件同一轮混用 MCP block.set 与脚本改文本 |
生成器覆盖改动,或重复定义 | 区块用 MCP,文本用脚本,二选一 |
| ⛔ 手写新建面板控件段 | 格式随版本变,极易「加载出来了但不生效」 | 首次在 UI 里拖,手改只用于批量调整 |
参考文件
| 文件 | 什么时候读 | 规模 |
|---|---|---|
REFERENCE.md |
查目录结构、区块类型与参数规则(§3.1)、API、变量行格式、面板 ini 结构、报告 CSV、规模建议 | ~430 行 |
TEMPLATES.md |
要可复制的骨架/范式代码、查重命令、ini 片段 | ~415 行 |
TROUBLESHOOTING.md |
§5 表未命中,或要按类系统排查 | ~270 行 |
VALIDATION.md |
核对结论来源、日期、环境版本、未覆盖项 | ~100 行 |
ASSETS/compile_mp.ps1 |
命令行编译(带占用预检、完整 stderr) | 脚本 |
ASSETS/parse_report.py |
解析报告 CSV 出 PASS/FAIL/NA 统计 | 脚本 |