1. 本代码实现的功能与主要用途
本项目是一套面向 射频/微波自动测试 的 Qt 桌面软件。核心能力包括:从 Config/Template 加载 .sc 测试脚本,在任务配置中组批测试;通过 Config/SCPI 的 JSON 描述仪器命令并动态生成频谱仪、信号源、矩阵开关等操作界面;解析并执行脚本,驱动 SCPI/TCP 与设备通信;在自动测试流程中按任务队列逐项跑脚本、采集「统计/监视」结果并入库、出报告。校准及发射接收等指标测试,降低重复搭测试序列和改参数的成本。
2. 使用 JSON 配置界面的目的与优点
仪器侧界面由 /*.json 的 Layout + Params 驱动,LayoutManager 按字符串描述动态拼控件,而非每种测量模式写一套固定 UI。优点:改 SCPI 或增参数往往只改 JSON,无需重新编译界面;同一套框架可适配多厂家 JSON;仪器插件与参数管理器共用同一参数名,保证手动操作与脚本一致。
3. 脚本配置测试项的目的与优点
测试项以 .sc 文本存储:顶部 \\ 说明、var 定义参数与结果、#设备:方法(...) 描述流程。将其拆成任务 JSON,供任务配置 UI 生成参数表单并执行。优点:测试逻辑与 C++ 解耦 ,现场可改频率、门限、流程而不发版;同一脚本可在任务里复制多组参数(按步进添加);仪器指令统一为「设备名 + 方法名」,可读性好;版本可用 Git 管理 .sc;复杂流程支持 for/if、迹线运算、上报。适合指标多、步骤固定但参数常变的计量和验收测试。
一、自动测试:如何用脚本跑一个
1.1 数据从磁盘到运行实例
一条可执行的「测试项」在运行时不是直接 open(.sc),而是三层数据:
| 层级 | 内容 | 来源 |
|---|---|---|
| 模板定义 | TaskName、ScriptContext、Params、Threshold、Instruction |
Config/Template/.../xxx.sc → ScriptToJsonObject → m_qTemplatesMap |
| 任务实例 | 同上,但 Params 里 Value 被用户改掉 |
① 任务配置「添加」→ 树节点 Datas |
| 运行文本 | 拼好的完整脚本文本 | UITaskTest::ExcuteScrip 动态拼接 |
.sc 文件在解析时被拆成三块(见 ScriptToJsonObject):
- 以
\\开头的连续行 → 说明 (Instruction,仅展示,不参与执行) var ...且带「监视/统计/门限」→ 结果/门限 (Threshold)var ...普通参数 → Params(任务配置表单)- 其余非
var行 → ScriptContext(真正执行的流程)
自动测试不会 把说明区拼进执行文本;会把 Params + Threshold 的 var 行(带当前值) 再拼上 ScriptContext。
1.2 自动测试主流程(UITaskTest)
仪器 MetaTree/设备适配器 Executer链 ScriptParser后台线程 UITaskTest 仪器 MetaTree/设备适配器 Executer链 ScriptParser后台线程 UITaskTest loop [每个勾选的子任务项] StartTest → 全局脚本「开始测试」 ExcuteScrip(item) 拼接 var 行 + ScriptContext RunScript(text, TaskName) ScriptAnalysis → Executer 列表 RunExes(顺序执行) SCPI/TCP Publish「执行完成」 exeComplete=true,写回结果/截图 StopTest →「停止测试」全局脚本
要点:
StartTest:先ScriptAction跑Config/GlobalScript/开始测试.sc(预热、初始化等),再双层循环遍历treeWidget_task下已勾选的子节点。ExcuteScrip:- 从
item->data(UserRole)取QJsonObject; - 遍历
Params、Threshold,把每项还原为var 名=值 ...一行(保留原Text里的单位、监视标记等,只替换=后的值); - 末尾追加
ScriptContext; - 调用
ScriptParser::RunScript(text, ..., TaskName)。
- 从
- 同步等待 :主线程
while(!exeComplete) Sleep(500),直到后台ScriptParser::Run结束并Publish("执行完成", "done!")。 - 结果回写 :
执行完成时把m_obj(含迹线、截图等)写回当前树节点UserRole,用于结果显示。
1.3 脚本引擎:异步队列 + 解释执行
RunScript 不在 UI 线程里直接跑,而是:
110:118:Template/scriptparser.cpp
bool ScriptParser::RunScript(QString script,QStringList args,QString scopeFlag,QString scriptName)
{
auto scriptWithParam = AddParamsIntoScript(script,args);
...
_tasks->enqueue(sc);
InitHandler();
}
后台 QtConcurrent 循环取队列 → ScriptAnalysis → Run(exes, scope, scriptName, context) → Executer::RunExes → Publish("执行完成")。
与 ScriptAction::RunScript (模板编辑器里跑「开始测试」全局脚本)不同:后者在调用线程同步执行,不入队。
StopTest 调 ScriptParser::StopRun(),把全局 _context->IsKeepRunning = false,各 Executer::Exec 里会检测并退出。
1.4 #设备:方法 如何变成仪器动作
流程区一行如 #频谱仪:相噪起始偏移(相噪起始偏移) 在 DoCompute 里识别 action 含 ::
- 按
:分段,去掉#,在MetaTree上找子节点(如「频谱仪」)。 - 最后一段用正则取方法名
相噪起始偏移,找MethodNode。 - 参数列表
{相噪起始偏移}经context->GetValue求值(支持@变量、表达式)。 MethodNode::InvokeFun→ 对应MSAadapter等 →send("相噪起始偏移", value)→Config/SCPI/频谱仪_玖锦.json里的 SCPI。
这是名词化调用:脚本里写业务名,不写 C++ 类名或裸 SCPI。
1.5 结果如何进入报告
var xxx=0 ... 监视:执行中UpDataParaValue→Publish("参数监视", "名:值"),自动测试 UI 可刷新监视标签。var xxx=0 ... 统计:任务配置标记为统计量;PublishDivideResultData(键名)把当前上下文变量快照发到「数据发布」,供历史/报告拆分。
二、文件监视逻辑(任务配置页)
实现位置:CTestItemConfigWidget + QFileSystemWatcher。
2.1 初始化
111:134:Plugins/UIProfessionalPlugin/TestItemConfig/CTestItemConfigWidget.cpp
m_pSystemWatcher = new QFileSystemWatcher();
...
connect(m_pSystemWatcher, &QFileSystemWatcher::fileChanged, this, &CTestItemConfigWidget::SlotFileUpdated);
connect(m_pFileModel, &QFileSystemModel::directoryLoaded, this, &CTestItemConfigWidget::SlotDirectoryLoaded);
左侧树:QFileSystemModel 根路径为 应用程序目录/Config/Template/。
2.2 目录加载(SlotDirectoryLoaded)
某目录 directoryLoaded 后,对该目录每一项(文件 + 子目录):
- 若是
.sc文件 :读全文 →ScriptToJsonObject→m_qTemplatesMap[相对路径] = obj(含Path、TaskName)。 - 不论文件或目录 :
m_pSystemWatcher->addPath(绝对路径)。
因此「已监视」= 该路径已 addPath,OS 在文件内容变更时会发 fileChanged。
2.3 文件变更(SlotFileUpdated)
- 重新读
.sc,更新TemplatesMap。 - 清空说明区、参数堆栈控件,提示「已同步到配置列表」。
- 若该
Path已在右侧「已添加测试项目」里,弹窗问是否用新脚本同步任务参数(会对比节点数据并批量更新)。
2.4 局限(容易误解的点)
| 情况 | 行为 |
|---|---|
修改已有 且已被 directoryLoaded 扫过的 .sc |
监视生效,m_qTemplatesMap 热更新 |
新建 .sc 后未重新加载目录 |
可能未 addPath、树不刷新;常需重进页面或重启 |
| 监视更新 | 更新内存定义;左侧树节点不一定会自动增删,需模型刷新 |
监视的是磁盘文件变更,不是数据库里的任务记录。
三、脚本编辑器:执行与断点
入口:UITemplateEdit::DoExcute(智能测试脚本窗口)。
3.1 执行步骤
- 合并三段文本:
变量定义+结果定义+流程(与存盘.sc结构一致)。 ScriptAction同步执行全局 「开始测试」。- 从
CodeEditor(流程区)读取断点表GetSupendPoints()→debugInfo.suspendPoints(键为流程区行号,从 1 起)。 rowOffset= 变量区 + 结果区行数(合并后流程区起始行偏移)。ScriptParser::RunScriptDebug(textStr, debugInfo):入队时带debugInfo,DataContext::SetDebugInfomation。
停止:点「停止」→ 全局「停止测试」+ ScriptParser::StopRun() + 清箭头。
3.2 断点如何生效
设置 UI :在流程编辑器左侧行号区双击 → LineNumberArea 切换 _selectRows 中该行号 → SelectRowChanged → ScriptParser::SetSuspendPoints(可在一轮运行中动态改断点表)。
运行中 :每个 Executer::Exec 开头:
329:331:Template/executer.cpp
bool Executer::Exec(DataContext* context)
{
context->WaitForResume(this->_rowIndex);
WaitForResume:
- 无
debugInfo→ 直接返回(正式自动测试走RunScript,不带 debugInfo,不断点)。 - 有 debugInfo:计算
rowNumber = rowIndex + 1 - rowOffset,若在suspendPoints中 →_isSuspend=true,Publish("中断", rowNumber)→ UISetArrows显示黄箭头、启用「继续」。 btn_suspend→SetResume()清_isSuspend,脚本从该行继续。
rowIndex 来自 ScriptAnalysis 时 RowText(strValue, i) 的合并全文行号 ,rowOffset 用于把「流程编辑器行号」对齐到「合并全文行号」。
3.3 调试时的变量监视
UpDataParaValue 会 Publish("参数监视"/"变量监视", "名:值"),模板编辑器订阅后在 tab_Values 表格刷新------与断点配合做单步观察。
四、脚本语法全集与原理
本项目的 .sc 不是 Python/Lua,而是自研单行脚本语言 :按行解析 → 构建 Executer 树 → 顺序/分支/循环执行。
4.1 文件结构(语法层)
\\说明行(可连续多行,仅文件开头一段)
var 参数名=默认值 范围 单位
var 结果名=0 范围 单位 监视|统计|门限
#设备:方法(参数)
赋值表达式
if / for / while / foreach 块
存盘时合并为单个 .sc;ScriptToJsonObject 再拆回各段供 UI 使用。
4.2 注释与说明
| 形式 | 作用 |
|---|---|
\\文本 |
说明(Instruction),不执行 ;解析时遇到非 \\ 行即结束说明段 |
行首 # |
设备调用(见下),不是 C 注释 |
4.3 变量定义 var
语法:
text
var 名称=初值 [范围] [单位] [监视|统计|门限|变量]
原理:
- 解析阶段:
AnalysisSystemFunction把var 名=表达式拆成Executer,AnalysisExpression处理右侧。 - 执行阶段:
Executer名称为var时,求值后AddVariableTable(名, 值, 原文行)写入_variables。 ParseParam(给任务 UI 用)按空格切分,识别~范围、|枚举、监视/统计等。
任务配置覆盖: 自动测试不执行文件里的原始 var 块,而用 JSON 里用户填的值重写成 var 行再执行。
4.4 设备调用 #设备:方法(参数...)
语法:
text
#频谱仪:相噪起始偏移(相噪起始偏移)
#信号源:频率(输入频率)
#全局配置:PublishDivideResultData(谐波抑制)
行首第一个 token 含 : 即视为设备调用(# 在解析时去掉)。
参数:
- 逗号分隔;
- 可以是数字、变量名、
@变量、子表达式; - 支持
>>输出:#频谱仪:查询光标Y值()>>峰值功率把返回值写入变量「峰值功率」。
原理: AnalysisNormalFunction 把整行拆成原子操作 map,最终在 DoCompute 走 MetaTree 反射调用。
4.5 赋值与普通表达式
赋值:
text
谐波抑制=谐波功率差值1
测试频率=i
输出绑定:
text
某表达式>>目标变量名
运算符优先级 (AnalysisExpression / handlExpression,注释里写明的分层):
- 最外层:
>>赋值方向 &&||!- 比较:
()、><==>=<= +-*/
括号由内向外先拆子表达式,再合并为带唯一 ID 的原子操作链,按 key 顺序在 DoCompute 里求值。
4.6 变量引用 @
text
#信号源:SetPower(@校准功率)
GetValue:去掉 @(@@ 保留一个 @),查 _variables → _tempVariables → _stringMap(字符串占位符还原)。
若值形如 {8位数字ID},再递归 GetValue(MappingString 引入的引号字符串占位)。
4.7 字符串与 MappingString(解析原理)
ScriptAnalysis 每行先 MappingString:
- 正则
"[^"]*"找到引号串; - 换成唯一 ID
{uuid}存入 map; - 行内不再被空格、
=等误拆。
执行前 context->SetStringMap(map),求值时通过 ID 取回真实字符串。
用于 var 模式=CW USB|LSB|CW 这类带 |、空格的枚举定义。
4.8 条件分支 if / elseif / else / endif
语法:
text
if 条件表达式
...语句...
elseif 条件表达式
...
else
...
endif
原理:
DepartIfBlock用if/endif嵌套层数切块;AnalysisSystemFunction为每个if/elseif建Executer,条件在_actions;- 真分支挂
_trueExecuter链表,假分支链到_falseExecuter(elseif/else); Exec时先算条件布尔值,再RunExes对应分支。
4.9 循环 for ... endfor
语法(固定 4 段,空格分隔):
text
for i=起始频率 i<=终止频率 i=i+频率步进
...
endfor
对应 params[0]=for, params[1]=初值, params[2]=条件, params[3]=步进。
原理:
action1:初值(如i=起始频率);_forActions2:条件(_breakKey指向条件结果,为 true 继续循环);_forActions3:步进;- 循环体:
_trueExecuter子Executer列表;每轮末尾执行步进再判条件。
4.10 while ... endwhile
text
while 条件表达式
...
endwhile
条件在 _actions,_breakKey 为条件变量;每轮体执行完再重新求条件。
4.11 foreach ... endforeach
text
foreach 迭代变量,数组变量名
...
endforeach
SetForForeachWhat("i,arr"):arr 经 GetValue 得到逗号分隔字符串,逐项 AddTempVariableTable(i, item) 执行体。
4.12 流程控制 break / continue / return
在 DoCompute 中识别:
break→IsBreak=true,跳出当前 for/while/foreach;continue→IsContinue=true;return→IsKeepRunning=false,终止整段脚本。
4.13 嵌套脚本与系统脚本
text
#脚本解析器:RunSystemScript("噪声系数",1)
通过 ScriptAdapter 加载 Config/SystemScript/ 下 .sc 并 RunScript。
全局 开始测试 / 停止测试 在自动测试与模板调试前后自动调用,用于统一复位仪器、联动 UI。
4.14 与「仅流程区」相关的行识别规则
Executer::CreateWithText / InputText 对每一行:
- 首 token 含
:→ 设备调用或带冒号的表达式; - 首 token 为
if/elseif/else/for/while/var/foreach→ 块语句; - 否则整行当作单行表达式
Executer(AnalysisExpression(...))。
空行跳过;不支持 # 行尾注释。
五、三块机制对比表
| 维度 | 自动测试 RunScript |
模板调试 RunScriptDebug |
全局 ScriptAction::RunScript |
|---|---|---|---|
| 线程 | 后台队列 | 后台队列 | 调用线程同步 |
| 断点 | 无 | 有 suspendPoints |
无 |
| 结束通知 | 执行完成 |
执行完成 + 中断 |
一般无(或脚本提示) |
| 文本来源 | Params+Threshold+ScriptContext | 编辑器三区合并 | 全局 .sc 全文 |
六、写测试项时的语法建议
- 可改参数 → 写在
var(无监视/统计),任务配置才能表单编辑。 - 只跑流程、不进表单 → 可只在
#频谱仪:...里写死或引用仪器面板同名参数。 - 多频点 → 用
for+PublishDivideResultData,或任务里「按步进添加」生成多条实例(每条一组 Params)。 - 调试 → 在智能测试脚本流程区行号区双击断点,用「执行/继续」;自动测试不会停断点。
- 改
.sc后 → 已监视则m_qTemplatesMap自动更新;已加入任务的可能要确认同步弹窗。
.