Waveform Loading

Waveform Loading 工作流

概述

Waveform Loading 功能实现了从波形库微服务下载 IQ 波形数据,并上传到 R&S SGT100A 信号发生器进行播放的完整链路。该功能集成在 RFStimuliServicer._process_signal() 中,当 DUT 请求调制信号(非 CW)时自动触发。

架构

DUT (FAAP)
    │ gRPC: RequestExternalStimuli
    ▼
RFStimuliServicer._process_signal()
    │
    ├── CW 信号 → configure_cw(freq, level) → 完成
    │
    └── 调制/多音信号 → WaveformLoader
                            │
                            ├── 1. 查询 WaveformLibraryService (gRPC)
                            ├── 2. 下载元数据 JSON + IQ 文本文件
                            ├── 3. 解析 IQ 数据 (IQParser)
                            ├── 4. 生成波形密钥 (WaveformKeyGenerator)
                            ├── 5. 检查 ARB 缓存 (is_waveform_stored)
                            ├── 6. 上传 .wv 文件 (ARBUploader → MMEM:DATA)
                            ├── 7. 选择波形 (BB:ARB:WAVeform:SELect)
                            └── 8. configure_modulated(freq, level) → RF ON

模块结构

test-interface-client/
├── service_client/
│   ├── rf_stimuli.py           # SignalGeneratorController + RFStimuliServicer
│   └── waveform_loader.py      # IQParser, WaveformKeyGenerator, ARBUploader,
│                                # WaveformLibraryClient, WaveformLoader
├── generated/
│   ├── waveform_library_pb2.py      # protobuf 消息定义
│   └── waveform_library_pb2_grpc.py # gRPC stub
└── test_case/
    └── test_waveform_loading_verification.py  # 端到端验证脚本

核心类职责

文件 职责
IQParser waveform_loader.py 解析制表符分隔的 IQ 文本 ↔ float 对列表
WaveformKeyGenerator waveform_loader.py 根据 IQ 内容 + 元数据生成 8 字符唯一密钥
ARBUploader waveform_loader.py 构建 .wv 文件、通过 MMEM:DATA 上传到仪器
WaveformLibraryClient waveform_loader.py gRPC 客户端,查询/下载波形文件
WaveformLoader waveform_loader.py 编排完整流程的协调器
SignalGeneratorController rf_stimuli.py SCPI 命令封装(含 ARB 扩展方法)

详细工作流

步骤 1: 查询波形库

# WaveformLibraryClient.query_standard_waveform()
WaveformRequest {
    session_id: SessionId,
    usage: [WAVEFORM_USAGE_UPLINK],
    standard: {test_model, carrier_type, frequency_range}
}
→ GetFilePackageInfo RPC → 返回 package_id

步骤 2: 下载文件

# WaveformLibraryClient.download_metadata() → dict
# WaveformLibraryClient.download_iq_file() → str
FileDownloadRequest {session_id, package_id, kind_of_file}
→ DownloadFile RPC (streaming) → 拼接 data 块 → 完整文件内容

步骤 3: 解析 IQ 数据

# IQParser.parse(iq_text_content)
输入: "0.123456\t-0.654321\n0.789012\t0.345678\n..."
输出: [(0.123456, -0.654321), (0.789012, 0.345678), ...]
  • 每行制表符分隔的 I 和 Q 值
  • 跳过空行和纯空白行
  • 格式错误抛出 ValueError(含行号)

步骤 4: 生成波形密钥

# WaveformKeyGenerator.generate_key(iq_content, metadata_json)
算法: MD5(MD5(iq_content) + MD5(metadata_json)) 取最后 8 字符
输出: "f3fae887"
波形名: "f3fae887.wf"

步骤 5: 检查 ARB 缓存

# SignalGeneratorController.is_waveform_stored("f3fae887.wf")
SCPI: :BB:ARB:WAVeform:CATalog?
→ 解析返回列表,检查是否已存在
→ 命中则跳过上传,直接到步骤 7

步骤 6: 上传 .wv 文件

# ARBUploader.upload_waveform(iq_data, "f3fae887.wf", sample_rate)

6a. 构建 R&S .wv 文件格式:

{TYPE:SMU-WV,0}
{COMMENT:Kiro waveform loader}
{CLOCK:15360000.0}
{LEVEL OFFS:3.123456,0.000000}
{SAMPLES:153600}
{WAVEFORM-614400:#<binary_data>}
  • IQ 数据编码为 16-bit 有符号整数(little-endian,交错 I,Q)
  • 浮点值 × 32767 → int16
  • 每个样本 4 字节(2 bytes I + 2 bytes Q)

6b. 通过 MMEM:DATA 上传到仪器:

SCPI: :MMEM:DATA "/var/user/f3fae887.wv",#<ieee488_block>
  • 使用 IEEE 488.2 确定长度数据块格式
  • 通过 GPIB send_raw() 发送二进制数据

步骤 7: 选择并启用波形

SCPI: :BB:ARB:WAVeform:SELect "/var/user/f3fae887.wv"
SCPI: :BB:ARB:CLOCk 15360000.0
SCPI: :BB:ARB:STATe ON

步骤 8: 配置 RF 输出

# SignalGeneratorController.configure_modulated(freq_hz, level_dbm)
SCPI: :FREQ 3500000000
SCPI: :POW -20.00 dBm
SCPI: :IQ:STAT ON
SCPI: :OUTP ON

数据格式

IQ 文本文件 (.wf)

0.123456	-0.654321
0.789012	0.345678
-0.234567	0.890123

R&S .wv 二进制文件

区域 格式 说明
头部 ASCII {TYPE:SMU-WV,0}{CLOCK:...}{SAMPLES:...}...
数据标记 ASCII {WAVEFORM-<byte_count>:#
IQ 数据 Binary int16 LE 交错: I0,Q0,I1,Q1,...
结束 ASCII }

波形元数据 JSON

{
    "Samples": 153600,
    "SampleRate": 15360000.0,
    "IsMultitone": false,
    "CarrierType": "L50007680",
    "TestModel": "Tm1P1"
}

SCPI 命令汇总

操作 SCPI 命令
查询波形目录 :BB:ARB:WAVeform:CATalog?
上传文件 :MMEM:DATA "/var/user/{name}.wv",#<block>
选择波形 :BB:ARB:WAVeform:SELect "/var/user/{name}.wv"
设置采样率 :BB:ARB:CLOCk {rate}
启用 ARB :BB:ARB:STATe ON
启用 IQ 调制 :IQ:STATe ON
设置频率 :FREQ {hz}
设置功率 :POW {dbm} dBm
启用 RF 输出 :OUTP ON

缓存机制

波形密钥基于 IQ 文件内容和元数据的 MD5 哈希生成,确保:

  • 相同波形数据 → 相同密钥 → 跳过重复上传
  • 不同波形数据 → 不同密钥 → 触发新上传
首次加载: 查询 → 下载 → 解析 → 生成密钥 → 未缓存 → 上传 → 选择
再次加载: 查询 → 下载 → 解析 → 生成密钥 → 已缓存 → 直接选择

错误处理

错误场景 行为
gRPC 连接失败 抛出异常,_process_signal 捕获,返回 ABORTED
波形查询无匹配 RuntimeError,阻止信号配置
IQ 文件格式错误 ValueError(含行号),阻止信号配置
GPIB 连接断开 ConnectionError,阻止信号配置
无 WaveformLibraryService waveform_loader 为 None,跳过波形加载

配置

配置项 方式 说明
波形库地址 waveform_library_address 参数或 WAVEFORM_LIBRARY_ADDRESS 环境变量 host:port 格式
信号源连接 VISA resource string TCPIP0::192.168.1.61::hislip0::INSTR

验证方法

无硬件验证

python test-interface-client/tests/verify_waveform_loading.py

验证核心逻辑:IQ 解析 round-trip、二进制转换、IEEE 488.2 块、密钥生成。

有硬件验证

python test-interface-client/test_case/test_waveform_loading_verification.py

连接真实 SGT100A,验证完整流程:上传 → 选择 → 配置 → RF 输出 → 仪器回读。

单元测试

cd test-interface-client
python -m pytest tests/test_signal_generator_arb.py -v

Mock 测试 SCPI 命令序列和参数正确性。

开发过程中遇到的问题及解决方法

问题 1: 模块导入路径错误

现象: 运行测试脚本时报 ModuleNotFoundError: No module named 'service_client'

原因: 测试脚本从 repo 根目录运行,但 service_client 包位于 test-interface-client/ 子目录中,Python 找不到模块。

解决: 在测试脚本顶部添加 sys.path.insert,将 test-interface-client/ 目录加入搜索路径:

_project_root = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..')
if _project_root not in sys.path:
    sys.path.insert(0, _project_root)

问题 2: protobuf 枚举名称不正确

现象: AttributeError: module 'enums_pb2' has no attribute 'TEST_MODEL_TM1P1'

原因: 枚举名称大小写敏感。实际生成的枚举是 TEST_MODEL_TM1p1(小写 p),不是 TEST_MODEL_TM1P1。同样 CARRIER_TYPE_L5 不存在,正确名称是 CARRIER_TYPE_L5000_7680

解决: 通过 dir(enums_pb2) 查询实际可用的枚举值,使用正确名称:

# 错误
enums_pb2.TEST_MODEL_TM1P1
enums_pb2.CARRIER_TYPE_L5

# 正确
enums_pb2.TEST_MODEL_TM1p1
enums_pb2.CARRIER_TYPE_L5000_7680

问题 3: BB:ARB:WAVeform:DATA 命令无法正确存储波形

现象: 仪器报错 Cannot read file;/var/user/kiro_test.wf.wvSettings conflict;no or empty waveform selected

原因: R&S SGT100A 的 BB:ARB:WAVeform:DATA 命令不是用来直接上传裸 IQ 二进制数据的。SGT100A 要求波形以 .wv 文件格式存储在仪器文件系统中,该格式包含特定的 ASCII 头部 + int16 二进制数据。

错误的做法:

:BB:ARB:WAVeform:DATA "name",#<ieee488_block_of_raw_float32>

正确的做法: 构建完整的 R&S .wv 文件,通过 MMEM:DATA 命令传输到仪器文件系统:

:MMEM:DATA "/var/user/name.wv",#<ieee488_block_of_wv_file>

问题 4: 文件扩展名冲突导致双重后缀

现象: 仪器报错 Cannot read file;/var/user/kiro_test.wf.wv

原因: 波形逻辑名称是 kiro_test.wf,但 R&S 仪器内部使用 .wv 扩展名。如果直接用 kiro_test.wf 作为文件名上传,仪器会存为 kiro_test.wf.wv(双重扩展名),导致后续 SELect 找不到文件。

解决: 上传时去掉 .wf 后缀,只用密钥作为文件名:

file_stem = waveform_name.replace('.wf', '')  # "f3fae887"
remote_path = f"/var/user/{file_stem}.wv"     # "/var/user/f3fae887.wv"

问题 5: TRIGger:SOURce IMMediate 命令无效

现象: 仪器报错 Invalid character data;IMMEDIATEInvalid character data;AUTO

原因: SGT100A 的 ARB 触发源命令参数值与预期不同。尝试了 IMMediateAUTOINT 等值均报错。

解决: 最终方案中移除了单独的触发源设置命令。R&S .wv 文件上传后,仪器默认以连续模式运行(从截图可见 "Running" 状态),无需额外设置触发源。


问题 6: :SOURce 前缀问题

现象: 部分 SCPI 命令加了 :SOURce 前缀后行为异常。

原因: SGT100A 的 BB(基带)子系统命令不需要 :SOURce 前缀。:SOURce:BB:ARB:...:BB:ARB:... 在某些仪器上等价,但在 SGT100A 上直接用 :BB:ARB:... 更可靠。

解决: BB 子系统命令统一去掉 :SOURce 前缀:

# 之前
":SOURce:BB:ARB:WAVeform:CATalog?"
":SOURce:BB:ARB:STATe ON"

# 之后
":BB:ARB:WAVeform:CATalog?"
":BB:ARB:STATe ON"

问题 7: RF 输出未开启

现象: 波形上传成功、ARB Running、I/Q Mod On,但 RF Output 显示 Off。

原因: ARBUploader.upload_waveform() 只负责上传和启用 ARB,不负责开启 RF 输出。在正常集成流程中,RF 输出由 configure_modulated() 方法控制(调用 set_output(True))。但在单独测试 upload_waveform 时,没有后续调用 configure_modulated

解决: 这不是 bug。在完整集成流程中,_process_signal 会在波形加载后调用 configure_modulated(freq, level),该方法内部会执行 set_output(True) 开启 RF。测试脚本中需要手动调用 sg.set_output(True)sg.configure_modulated(freq, level)


问题 8: R&S .wv 文件格式

最终确认的 .wv 文件格式:

{TYPE:SMU-WV,0}{COMMENT:...}{CLOCK:采样率}{LEVEL OFFS:par,0.000000}{SAMPLES:样本数}{WAVEFORM-字节数:#<int16_LE_binary>}

关键点:

  • IQ 数据用 int16 有符号整数(不是 float32)
  • 浮点值范围 [-1.0, 1.0] 映射到 [-32767, 32767]
  • 交错格式:I0(2B), Q0(2B), I1(2B), Q1(2B), ...
  • 每样本 4 字节(不是 float32 的 8 字节)
  • 文件以 } 结尾

经验总结

教训 说明
不要猜测 SCPI 命令 不同厂商/型号的命令差异很大,必须参考具体仪器文档
R&S 仪器用 .wv 文件格式 不能直接发送裸二进制 IQ 数据,需要构建带头部的 .wv 文件
用 MMEM:DATA 传输文件 这是 R&S 仪器接收外部波形文件的标准方式
分层验证 先验证纯逻辑(无硬件),再验证 SCPI 通信,最后验证完整流程
检查仪器错误队列 :SYST:ERR?*CLS 来诊断问题
posted @ 2026-05-15 16:40  mo686  阅读(1)  评论(0)    收藏  举报