Qt 自研测控软件-配置测试项

1. 本代码实现的功能与主要用途

本项目是一套面向 射频/微波自动测试 的 Qt 桌面软件。核心能力包括:从 Config/Template 加载 .sc 测试脚本,在任务配置中组批测试;通过 Config/SCPI 的 JSON 描述仪器命令并动态生成频谱仪、信号源、矩阵开关等操作界面;解析并执行脚本,驱动 SCPI/TCP 与设备通信;在自动测试流程中按任务队列逐项跑脚本、采集「统计/监视」结果并入库、出报告。校准及发射接收等指标测试,降低重复搭测试序列和改参数的成本。


2. 使用 JSON 配置界面的目的与优点

仪器侧界面由 /*.jsonLayout + Params 驱动,LayoutManager 按字符串描述动态拼控件,而非每种测量模式写一套固定 UI。优点:改 SCPI 或增参数往往只改 JSON,无需重新编译界面;同一套框架可适配多厂家 JSON;仪器插件与参数管理器共用同一参数名,保证手动操作与脚本一致。


3. 脚本配置测试项的目的与优点

测试项以 .sc 文本存储:顶部 \\ 说明、var 定义参数与结果、#设备:方法(...) 描述流程。将其拆成任务 JSON,供任务配置 UI 生成参数表单并执行。优点:测试逻辑与 C++ 解耦 ,现场可改频率、门限、流程而不发版;同一脚本可在任务里复制多组参数(按步进添加);仪器指令统一为「设备名 + 方法名」,可读性好;版本可用 Git 管理 .sc;复杂流程支持 for/if、迹线运算、上报。适合指标多、步骤固定但参数常变的计量和验收测试。


一、自动测试:如何用脚本跑一个

1.1 数据从磁盘到运行实例

一条可执行的「测试项」在运行时不是直接 open(.sc),而是三层数据:

层级 内容 来源
模板定义 TaskNameScriptContextParamsThresholdInstruction Config/Template/.../xxx.scScriptToJsonObjectm_qTemplatesMap
任务实例 同上,但 ParamsValue 被用户改掉 ① 任务配置「添加」→ 树节点 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 →「停止测试」全局脚本

要点:

  1. StartTest :先 ScriptActionConfig/GlobalScript/开始测试.sc(预热、初始化等),再双层循环遍历 treeWidget_task已勾选的子节点。
  2. ExcuteScrip
    • item->data(UserRole)QJsonObject
    • 遍历 ParamsThreshold,把每项还原为 var 名=值 ... 一行(保留原 Text 里的单位、监视标记等,只替换 = 后的值);
    • 末尾追加 ScriptContext
    • 调用 ScriptParser::RunScript(text, ..., TaskName)
  3. 同步等待 :主线程 while(!exeComplete) Sleep(500),直到后台 ScriptParser::Run 结束并 Publish("执行完成", "done!")
  4. 结果回写执行完成 时把 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 循环取队列 → ScriptAnalysisRun(exes, scope, scriptName, context)Executer::RunExesPublish("执行完成")

ScriptAction::RunScript (模板编辑器里跑「开始测试」全局脚本)不同:后者在调用线程同步执行,不入队。

StopTestScriptParser::StopRun(),把全局 _context->IsKeepRunning = false,各 Executer::Exec 里会检测并退出。

1.4 #设备:方法 如何变成仪器动作

流程区一行如 #频谱仪:相噪起始偏移(相噪起始偏移)DoCompute 里识别 action:

  1. : 分段,去掉 #,在 MetaTree 上找子节点(如「频谱仪」)。
  2. 最后一段用正则取方法名 相噪起始偏移,找 MethodNode
  3. 参数列表 {相噪起始偏移}context->GetValue 求值(支持 @变量、表达式)。
  4. MethodNode::InvokeFun → 对应 MSAadapter 等 → send("相噪起始偏移", value)Config/SCPI/频谱仪_玖锦.json 里的 SCPI。

这是名词化调用:脚本里写业务名,不写 C++ 类名或裸 SCPI。

1.5 结果如何进入报告

  • var xxx=0 ... 监视:执行中 UpDataParaValuePublish("参数监视", "名:值"),自动测试 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 后,对该目录每一项(文件 + 子目录):

  1. 若是 .sc 文件 :读全文 → ScriptToJsonObjectm_qTemplatesMap[相对路径] = obj(含 PathTaskName)。
  2. 不论文件或目录m_pSystemWatcher->addPath(绝对路径)

因此「已监视」= 该路径已 addPath,OS 在文件内容变更时会发 fileChanged

2.3 文件变更(SlotFileUpdated

  1. 重新读 .sc,更新 TemplatesMap
  2. 清空说明区、参数堆栈控件,提示「已同步到配置列表」。
  3. 若该 Path 已在右侧「已添加测试项目」里,弹窗问是否用新脚本同步任务参数(会对比节点数据并批量更新)。

2.4 局限(容易误解的点)

情况 行为
修改已有 且已被 directoryLoaded 扫过的 .sc 监视生效,m_qTemplatesMap 热更新
新建 .sc 后未重新加载目录 可能未 addPath、树不刷新;常需重进页面或重启
监视更新 更新内存定义;左侧树节点不一定会自动增删,需模型刷新

监视的是磁盘文件变更,不是数据库里的任务记录。


三、脚本编辑器:执行与断点

入口:UITemplateEdit::DoExcute(智能测试脚本窗口)。

3.1 执行步骤

  1. 合并三段文本:变量定义 + 结果定义 + 流程(与存盘 .sc 结构一致)。
  2. ScriptAction 同步执行全局 「开始测试」
  3. CodeEditor(流程区)读取断点表 GetSupendPoints()debugInfo.suspendPoints(键为流程区行号,从 1 起)。
  4. rowOffset = 变量区 + 结果区行数(合并后流程区起始行偏移)。
  5. ScriptParser::RunScriptDebug(textStr, debugInfo):入队时带 debugInfoDataContext::SetDebugInfomation

停止:点「停止」→ 全局「停止测试」+ ScriptParser::StopRun() + 清箭头。

3.2 断点如何生效

设置 UI :在流程编辑器左侧行号区双击LineNumberArea 切换 _selectRows 中该行号 → SelectRowChangedScriptParser::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=truePublish("中断", rowNumber) → UI SetArrows 显示黄箭头、启用「继续」。
  • btn_suspendSetResume()_isSuspend,脚本从该行继续。

rowIndex 来自 ScriptAnalysisRowText(strValue, i)合并全文行号rowOffset 用于把「流程编辑器行号」对齐到「合并全文行号」。

3.3 调试时的变量监视

UpDataParaValuePublish("参数监视"/"变量监视", "名:值"),模板编辑器订阅后在 tab_Values 表格刷新------与断点配合做单步观察。


四、脚本语法全集与原理

本项目的 .sc 不是 Python/Lua,而是自研单行脚本语言 :按行解析 → 构建 Executer 树 → 顺序/分支/循环执行。

4.1 文件结构(语法层)

复制代码
\\说明行(可连续多行,仅文件开头一段)
var 参数名=默认值 范围 单位
var 结果名=0 范围 单位 监视|统计|门限
#设备:方法(参数)
赋值表达式
if / for / while / foreach 块

存盘时合并为单个 .scScriptToJsonObject 再拆回各段供 UI 使用。

4.2 注释与说明

形式 作用
\\文本 说明(Instruction),不执行 ;解析时遇到非 \\ 行即结束说明段
行首 # 设备调用(见下),不是 C 注释

4.3 变量定义 var

语法:

text 复制代码
var 名称=初值 [范围] [单位] [监视|统计|门限|变量]

原理:

  • 解析阶段:AnalysisSystemFunctionvar 名=表达式 拆成 ExecuterAnalysisExpression 处理右侧。
  • 执行阶段: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,注释里写明的分层):

  1. 最外层:>> 赋值方向
  2. && || !
  3. 比较:()> < == >= <=
  4. + -
  5. * /

括号由内向外先拆子表达式,再合并为带唯一 ID 的原子操作链,按 key 顺序在 DoCompute 里求值。

4.6 变量引用 @

text 复制代码
#信号源:SetPower(@校准功率)

GetValue:去掉 @@@ 保留一个 @),查 _variables_tempVariables_stringMap(字符串占位符还原)。

若值形如 {8位数字ID},再递归 GetValueMappingString 引入的引号字符串占位)。

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

原理:

  • DepartIfBlockif/endif 嵌套层数切块;
  • AnalysisSystemFunction 为每个 if/elseifExecuter,条件在 _actions
  • 真分支挂 _trueExecuter 链表,假分支链到 _falseExecuterelseif/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:步进;
  • 循环体:_trueExecuterExecuter 列表;每轮末尾执行步进再判条件。

4.10 while ... endwhile

text 复制代码
while 条件表达式
    ...
endwhile

条件在 _actions_breakKey 为条件变量;每轮体执行完再重新求条件。

4.11 foreach ... endforeach

text 复制代码
foreach 迭代变量,数组变量名
    ...
endforeach

SetForForeachWhat("i,arr")arrGetValue 得到逗号分隔字符串,逐项 AddTempVariableTable(i, item) 执行体。

4.12 流程控制 break / continue / return

DoCompute 中识别:

  • breakIsBreak=true,跳出当前 for/while/foreach;
  • continueIsContinue=true
  • returnIsKeepRunning=false,终止整段脚本。

4.13 嵌套脚本与系统脚本

text 复制代码
#脚本解析器:RunSystemScript("噪声系数",1)

通过 ScriptAdapter 加载 Config/SystemScript/.scRunScript

全局 开始测试 / 停止测试 在自动测试与模板调试前后自动调用,用于统一复位仪器、联动 UI。

4.14 与「仅流程区」相关的行识别规则

Executer::CreateWithText / InputText 对每一行:

  1. 首 token 含 : → 设备调用或带冒号的表达式;
  2. 首 token 为 if/elseif/else/for/while/var/foreach → 块语句;
  3. 否则整行当作单行表达式 Executer(AnalysisExpression(...))

空行跳过;不支持 # 行尾注释。


五、三块机制对比表

维度 自动测试 RunScript 模板调试 RunScriptDebug 全局 ScriptAction::RunScript
线程 后台队列 后台队列 调用线程同步
断点 suspendPoints
结束通知 执行完成 执行完成 + 中断 一般无(或脚本提示)
文本来源 Params+Threshold+ScriptContext 编辑器三区合并 全局 .sc 全文

六、写测试项时的语法建议

  1. 可改参数 → 写在 var(无监视/统计),任务配置才能表单编辑。
  2. 只跑流程、不进表单 → 可只在 #频谱仪:... 里写死或引用仪器面板同名参数。
  3. 多频点 → 用 for + PublishDivideResultData,或任务里「按步进添加」生成多条实例(每条一组 Params)。
  4. 调试 → 在智能测试脚本流程区行号区双击断点,用「执行/继续」;自动测试不会停断点。
  5. .sc → 已监视则 m_qTemplatesMap 自动更新;已加入任务的可能要确认同步弹窗。
    .
相关推荐
装不满的克莱因瓶11 小时前
【项目亮点四】支付订单超时处理与状态补偿机制设计
java·开发语言·后端·rabbitmq·消息中间件
@Murphy11 小时前
java 面试
java·开发语言·面试
lsx20240611 小时前
Scala 字符串处理指南
开发语言
biter down11 小时前
6:控件操作与鼠标模拟
开发语言·python
郝学胜-神的一滴11 小时前
CMake 011:跨平台动态库编译
开发语言·c++·嵌入式硬件·qt·程序人生·cmake·liunx
xifangge202511 小时前
jdk版本不一样怎么办?一台电脑如何完美共存 JDK 8/11/17/21?多版本无缝切换与 IDEA 环境隔离实战指南
java·开发语言·jdk·intellij-idea
敲代码的小王!11 小时前
Python 核心语法 —— 数据、流程与容器
开发语言·python
覆东流11 小时前
python环境搭建
开发语言·python
lsx20240611 小时前
传输对象模式
开发语言