3. 自动化仪器初始化序列

自动化仪器初始化序列 学习卡片

任务说明

在自动化测试系统中,仪器初始化是测试开始前的关键步骤。本案例要求实现一个通用的仪器初始化序列管理器,能够按预定义顺序执行多台仪器的初始化操作,并在任一步骤失败时立即停止后续初始化,同时提供清晰的错误定位信息。初始化序列需支持:SCPI命令发送、延时等待、状态查询三种操作类型,且序列配置与执行逻辑分离,便于后续扩展新仪器。

实现方案

采用Python实现一个轻量级初始化序列引擎,核心设计如下:

  • 操作抽象:将初始化步骤抽象为send(发送SCPI命令)、delay(延时等待)、query(查询并校验返回)三种原语;
  • 序列配置:使用JSON文件描述初始化步骤,与执行引擎解耦;
  • 失败即停:任一步骤抛出异常即终止整个序列,并报告失败步骤编号与原因;
  • 仪器通信:通过VISA库(pyvisa)与真实仪器通信,同时提供模拟模式用于无硬件环境下的逻辑验证。

代码实现

import json
import time
from typing import Dict, List, Any, Callable, Optional

class InitStepError(Exception):
    """初始化步骤执行失败异常"""
    def __init__(self, step_index: int, step_desc: str, reason: str):
        self.step_index = step_index
        self.step_desc = step_desc
        self.reason = reason
        super().__init__(f"步骤{step_index}失败 [{step_desc}]: {reason}")

class InstrumentInitSequence:
    """仪器初始化序列执行器"""
    
    def __init__(self, device: Any, config_path: str):
        """
        :param device: 仪器设备对象,需实现 write()/query()/close() 方法
        :param config_path: 初始化序列JSON配置文件路径
        """
        self.device = device
        with open(config_path, 'r', encoding='utf-8') as f:
            self.sequence = json.load(f)
        self._handlers: Dict[str, Callable] = {
            'send': self._handle_send,
            'delay': self._handle_delay,
            'query': self._handle_query,
        }
    
    def _handle_send(self, step: Dict[str, Any]) -> None:
        """发送SCPI命令,不校验返回"""
        cmd = step['command']
        self.device.write(cmd)
    
    def _handle_delay(self, step: Dict[str, Any]) -> None:
        """延时等待,单位秒"""
        seconds = float(step['seconds'])
        time.sleep(seconds)
    
    def _handle_query(self, step: Dict[str, Any]) -> None:
        """发送查询命令并校验返回内容"""
        cmd = step['command']
        expected = step['expected']
        resp = self.device.query(cmd).strip()
        if expected not in resp:
            raise ValueError(f"查询返回 '{resp}' 不包含期望值 '{expected}'")
    
    def run(self) -> None:
        """按顺序执行全部初始化步骤,失败即终止"""
        steps = self.sequence['steps']
        for idx, step in enumerate(steps, start=1):
            op_type = step['type']
            handler = self._handlers.get(op_type)
            if handler is None:
                raise InitStepError(idx, step.get('desc', ''), f"未知操作类型: {op_type}")
            try:
                handler(step)
                print(f"[OK] 步骤{idx}: {step.get('desc', op_type)}")
            except Exception as e:
                raise InitStepError(idx, step.get('desc', op_type), str(e)) from e
        print("初始化序列全部完成")

class MockInstrument:
    """模拟仪器设备,用于无硬件环境验证"""
    def __init__(self):
        self.buffer = []
    
    def write(self, cmd: str) -> None:
        self.buffer.append(("WRITE", cmd))
        # 模拟:若发送 *RST 则重置状态
        if cmd == "*RST":
            self.buffer.append(("STATE", "RESET"))
    
    def query(self, cmd: str) -> str:
        self.buffer.append(("QUERY", cmd))
        # 模拟 *IDN? 返回仪器标识
        if cmd == "*IDN?": 
            return "MOCK_INSTRUMENT_V1.0"
        # 模拟 SYST:ERR? 返回无错误
        if cmd == "SYST:ERR?":
            return '0,"No error"'
        return "UNKNOWN_RESPONSE"
    
    def close(self) -> None:
        pass

def main():
    """演示入口:使用模拟仪器执行初始化序列"""
    import os
    
    # 初始化序列配置(实际使用时建议独立JSON文件)
    config = {
        "instrument": "MockInstrument",
        "steps": [
            {"type": "send", "command": "*RST", "desc": "复位仪器"},
            {"type": "delay", "seconds": 0.5, "desc": "等待复位完成"},
            {"type": "query", "command": "*IDN?", "expected": "MOCK", "desc": "查询仪器标识"},
            {"type": "send", "command": "SYST:REM", "desc": "切换到远程控制模式"},
            {"type": "query", "command": "SYST:ERR?", "expected": "No error", "desc": "确认无错误"}
        ]
    }
    
    # 将配置写入临时文件
    config_path = "init_sequence.json"
    with open(config_path, 'w', encoding='utf-8') as f:
        json.dump(config, f, ensure_ascii=False, indent=2)
    
    # 创建模拟仪器并执行初始化
    mock = MockInstrument()
    seq = InstrumentInitSequence(mock, config_path)
    
    try:
        seq.run()
        print("\n=== 初始化成功 ===")
    except InitStepError as e:
        print(f"\n=== 初始化失败: {e} ===")
    finally:
        mock.close()
        os.remove(config_path)  # 清理临时配置

if __name__ == "__main__":
    main()

落地与验证

依赖安装:若需连接真实仪器,执行 pip install pyvisa pyvisa-py,并通过USB/GPIB/LAN连接仪器;若仅验证逻辑,直接运行上述代码即可(使用内置MockInstrument)。

运行方式:将代码保存为 init_sequence.py,执行 python init_sequence.py。

验证方法:

  1. 正常路径:观察控制台依次输出5个步骤的[OK]标记,最后输出“初始化序列全部完成”;
  2. 失败路径:修改配置中某步骤的expected为错误值(如将"No error"改为"Error"),重新运行,程序应在该步骤抛出InitStepError并终止,不再执行后续步骤;
  3. 判定标准:步骤执行顺序与JSON配置完全一致;失败时错误信息包含步骤编号、描述和具体原因;模拟模式下MockInstrument的buffer记录了所有写入/查询命令,可据此核对实际发送的SCPI指令。
仪器初始化序列执行流程 加载JSON配置 循环执行 steps[0..N-1] 步骤1: send *RST 步骤2: delay 0.5s 步骤3: query *IDN? 步骤4: send SYST:REM 步骤5: query SYST:ERR? 异常抛出 InitStepError 序列完成 / 终止 成功步骤 失败步骤 失败跳转