SimpleSCPI项目技术分享:基于PyQt5的SCPI仪器控制工具开发实战
前言
在自动化测试和仪器控制领域,SCPI(Standard Commands for Programmable Instruments)协议是一个广泛使用的标准。本文将分享我开发的一个开源项目——SimpleSCPI,这是一个基于PyQt5的图形化SCPI仪器控制工具。通过这个项目,您不仅可以获得一个实用的仪器控制工具,还能学习到PyQt5桌面应用开发的核心技术。
项目概述
🎯 项目特点
SimpleSCPI是一个功能完整的SCPI仪器控制软件,具有以下特点:
- 多协议支持:支持TCP/IP、USB、串口等多种连接方式
- 图形化界面:基于PyQt5的现代化深色主题界面
- 命令管理:可视化的SCPI命令编辑、保存和批量执行
- 实时监控:显示命令执行状态、响应时间和通信日志
- 命令类型管理:支持Write和Query两种命令类型的可视化选择
- 一键打包:使用PyInstaller打包成独立可执行文件
📸 界面预览
现代化的深色主题界面,支持实时I/O监控
技术架构
🏗️ 项目结构
SimpleSCPI/
├── src/
│ ├── main.py # 程序入口
│ ├── core/ # 核心功能模块
│ │ ├── instrument.py # 仪器控制类
│ │ ├── base.py # 基础类
│ │ └── exceptions.py # 异常处理
│ ├── ui/ # 用户界面
│ │ ├── main_window.py # 主窗口逻辑
│ │ └── MainUI.py # UI定义
│ └── resources/ # 资源文件
├── SimpleSCPI.spec # PyInstaller配置
├── requirements.txt # 依赖包
└── environment.yml # Conda环境配置
🔧 技术栈
- GUI框架:PyQt5 - 跨平台GUI开发
- 仪器通信:PyVISA - 标准仪器通信库
- 界面主题:QDarkStyle - 现代化深色主题
- 打包工具:PyInstaller - 生成独立可执行文件
核心技术实现
1. 仪器通信模块
仪器通信是整个项目的核心,我们使用PyVISA库来实现与SCPI仪器的通信:
class Instrument(BaseObject):
"""仪器控制类"""
def __init__(self, visa_dll_path='c:/windows/system32/visa32.dll'):
super().__init__()
try:
self.resource_manager = visa.ResourceManager(visa_dll_path)
except Exception as e:
raise ConnectionError(f"Failed to initialize VISA resource manager: {e}")
self.instrument_ctrl = None
self.instrument_id = None
self.is_connected = False
def open(self, resource_name, timeout=5000, termination=''):
"""打开仪器连接"""
try:
self.instrument_ctrl = self.resource_manager.open_resource(
resource_name,
read_termination=termination
)
self.is_connected = True
self.instrument_ctrl.timeout = timeout
self.instrument_ctrl.clear()
self.instrument_id = self.query("*IDN?")
return True
except Exception as e:
raise ConnectionError(f"Failed to connect to {resource_name}: {e}")
def write(self, command):
"""向仪器写入命令"""
if not self.is_connected:
raise CommunicationError("Instrument not connected")
self.instrument_ctrl.write(command)
def query(self, command):
"""查询命令并返回结果"""
if not self.is_connected:
raise CommunicationError("Instrument not connected")
self.instrument_ctrl.clear()
return self.instrument_ctrl.query(command).strip()
技术要点:
- 使用工厂模式管理VISA资源
- 完善的异常处理机制
- 支持多种连接参数配置
- 自动清除缓冲区避免数据污染
2. PyQt5界面设计
主界面采用分割窗口布局,实现了命令管理、实时监控和日志显示的分离:
class MainWindow(QMainWindow, Ui_MainWindow, BaseObject):
"""主窗口类"""
def __init__(self):
super().__init__()
self.setupUi(self)
# 设置窗口标题包含版本信息
self.setWindowTitle("SimpleSCPI v1.0.0")
# 配置分割窗口比例
self.splitter.setSizes([300, 200])
self.splitter_2.setSizes([400, 180])
# 初始化表格和控件
self.TableWidgetInit()
self.ToolBarSplit()
# 设置仪器控制对象
self.ins = Instrument()
def TableWidgetInit(self):
"""初始化命令表格"""
self.tableWidget_2.setColumnCount(5)
self.tableWidget_2.setHorizontalHeaderLabels(
["Checked", "Command", "Comment", "Type", "Action"]
)
# 设置列宽和行高
self.tableWidget_2.setColumnWidth(3, 80) # Type列
self.tableWidget_2.setColumnWidth(4, 150) # Action列
self.tableWidget_2.verticalHeader().setMinimumSectionSize(35)
# 设置右键菜单
self.tableWidget_2.setContextMenuPolicy(Qt.CustomContextMenu)
self.tableWidget_2.customContextMenuRequested.connect(self.SelectMenu)
技术要点:
- 使用QSplitter实现可调节的分割布局
- QTableWidget实现可编辑的命令列表
- 自定义右键菜单提供便捷操作
- QComboBox实现命令类型选择
3. 命令类型管理系统
为了支持Write和Query两种不同的命令类型,我实现了一个可视化的类型管理系统:
def CreateTypeComboBox(self, row, current_type="write"):
"""创建类型选择下拉框"""
combo = QComboBox()
combo.addItems(["write", "query"])
combo.setCurrentText(current_type)
# 设置样式
combo.setStyleSheet("""
QComboBox {
border: 1px solid #555;
border-radius: 3px;
padding: 2px 5px;
background-color: #2b2b2b;
color: white;
}
QComboBox:hover {
border-color: #777;
background-color: #3c3c3c;
}
QComboBox::drop-down {
border: none;
width: 20px;
}
""")
# 连接信号
combo.currentTextChanged.connect(lambda text: self.TypeComboChanged(row, text))
return combo
def TypeComboChanged(self, row, new_type):
"""处理类型选择变化"""
if row < len(self.cmdList):
self.cmdList[row]["Type"] = new_type
self.Log(f"命令 {row+1} 类型已更改为: {new_type}")
技术要点:
- 动态创建QComboBox控件
- 信号槽机制处理用户交互
- 自定义样式适配深色主题
- 实时更新数据模型
4. 缓冲区清除功能
为了解决仪器通信中的数据残留问题,实现了缓冲区清除功能:
def clear_buffer(self):
"""
清除仪器接收缓冲区
Raises:
CommunicationError: 通信失败时抛出
"""
if not self.is_connected or not self.instrument_ctrl:
raise CommunicationError("Instrument not connected")
try:
self.instrument_ctrl.clear()
if self.logger:
self.logger.info("Instrument buffer cleared")
except Exception as e:
error_msg = f"Clear buffer failed: {e}"
if self.logger:
self.logger.error(error_msg)
raise CommunicationError(error_msg)
def ClearBuffer(self):
"""清除缓冲区按钮处理"""
try:
self.ins.clear_buffer()
self.Log("仪器缓冲区已清除")
except Exception as e:
self.Log(f"清除缓冲区失败: {e}")
技术要点:
- 调用PyVISA的clear()方法清除缓冲区
- 完善的异常处理和日志记录
- 工具栏按钮提供便捷操作
- 解决查询命令执行错误导致的通信问题
5. 配置文件管理
实现了JSON格式的配置文件保存和加载:
def SaveData(self):
"""保存命令配置"""
config_data = {
"commands": self.cmdList,
"saved_time": datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
"version": "1.0",
"description": "SimpleSCPI命令配置文件"
}
default_name = f"SCPI_Config_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
filename, _ = QFileDialog.getSaveFileName(
self, '保存命令配置', f'./{default_name}',
'JSON Config File(*.json);;All Files(*.*)'
)
if filename:
with open(filename, 'w', encoding='utf-8') as f:
json.dump(config_data, f, ensure_ascii=False, indent=2)
self.Log(f"配置已保存到: {filename}")
def AutoSaveConfig(self):
"""自动保存配置"""
try:
config_data = {
"commands": self.cmdList,
"saved_time": datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
"auto_saved": True
}
with open("scpi_config_auto.json", 'w', encoding='utf-8') as f:
json.dump(config_data, f, ensure_ascii=False, indent=2)
except Exception as e:
self.Log(f"自动保存失败: {e}")
技术要点:
- JSON格式存储配置数据
- 自动保存机制防止数据丢失
- 文件对话框提供友好的用户体验
- UTF-8编码支持中文注释
6. 多线程批量执行
实现了多线程的命令批量执行功能:
class TestThread(QThread):
"""测试线程类"""
finished_signal = pyqtSignal(str)
progress_signal = pyqtSignal(int, int)
def __init__(self, cmd_list, instrument):
super().__init__()
self.cmd_list = cmd_list
self.instrument = instrument
self.is_running = True
def run(self):
"""执行批量测试"""
total_commands = len(self.cmd_list)
for i, cmd_info in enumerate(self.cmd_list):
if not self.is_running:
break
command = cmd_info["Command"]
cmd_type = cmd_info["Type"]
try:
if cmd_type == "query":
result = self.instrument.query(command)
elif cmd_type == "write":
self.instrument.write(command)
# 发送进度信号
self.progress_signal.emit(i + 1, total_commands)
except Exception as e:
self.finished_signal.emit(f"Command failed: {e}")
return
self.finished_signal.emit("Batch execution completed")
技术要点:
- QThread实现多线程处理
- 信号槽机制更新UI状态
- 异常处理确保线程安全
- 进度反馈提升用户体验
PyInstaller打包配置
为了方便分发,我使用PyInstaller将程序打包成独立的可执行文件:
# SimpleSCPI.spec
a = Analysis(
['src/main.py'],
pathex=[],
binaries=[],
datas=[
('src/resources', 'resources'),
],
hiddenimports=[
'PyQt5.QtCore',
'PyQt5.QtGui',
'PyQt5.QtWidgets',
'pyvisa',
'qdarkstyle'
],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=None,
noarchive=False,
)
pyz = PYZ(a.pure, a.zipped_data, cipher=None)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[],
name='SimpleSCPI',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=False,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
icon='src/resources/icon.ico'
)
打包命令:
pyinstaller SimpleSCPI.spec
项目亮点与学习价值
🎯 PyQt5学习要点
- 界面布局管理:掌握QSplitter、QTableWidget等控件的使用
- 信号槽机制:理解Qt的事件处理模式
- 自定义控件:学会创建和管理自定义UI组件
- 多线程编程:掌握QThread在GUI应用中的应用
- 样式定制:学习QSS样式表的使用
🔧 实用技术技巧
- 模块化设计:清晰的项目结构和模块划分
- 异常处理:完善的错误处理和用户反馈机制
- 配置管理:JSON格式的配置文件存储方案
- 打包部署:PyInstaller的配置和使用技巧
- 跨平台兼容:处理不同操作系统的兼容性问题
📚 扩展学习方向
- 仪器通信协议:深入学习SCPI、VISA等标准
- 自动化测试:将工具集成到测试流程中
- 数据可视化:添加图表和数据分析功能
- 插件系统:设计可扩展的插件架构
- 云端集成:支持远程仪器控制和数据同步
安装使用
🚀 快速开始
- 克隆项目:
git clone https://github.com/Alen2013/SimpleSCPI.git
cd SimpleSCPI
- 安装依赖:
# 使用conda(推荐)
conda env create -f environment.yml
conda activate simplescpi
# 或使用pip
pip install -r requirements.txt
- 运行程序:
cd src
python main.py
📦 直接下载
如果您不想配置开发环境,可以直接下载打包好的可执行文件:
- SimpleSCPI-v1.0.0.exe (~37MB)
🔧 基本使用
- 连接仪器:在工具栏输入仪器地址(如:
TCPIP0::192.168.1.100::5001::SOCKET
) - 添加命令:右键命令列表选择"Add Item"
- 执行命令:点击"Send"或"Query"按钮
- 查看结果:在右侧I/O面板查看通信记录
项目总结
SimpleSCPI项目展示了如何使用PyQt5开发一个功能完整的桌面应用程序。通过这个项目,您可以学习到:
- PyQt5桌面应用开发的核心技术
- 仪器通信编程的实践经验
- 软件架构设计的最佳实践
- 项目打包部署的完整流程
项目采用MIT开源协议,欢迎大家使用、学习和贡献代码。无论您是PyQt5初学者还是仪器控制开发者,这个项目都能为您提供有价值的参考。
开源地址
- GitHub: https://github.com/Alen2013/SimpleSCPI
技术交流
如果您在使用过程中遇到问题,或者有好的建议,欢迎通过以下方式联系:
- 提交GitHub Issues
- 发送邮件至:mail_along@163.com
- 关注我的优快云博客获取更多技术分享
本文介绍的SimpleSCPI项目是一个完全开源的实用工具,希望能够帮助到需要进行仪器控制开发的朋友们。如果觉得有用,请给项目点个Star支持一下!
#仪器控制 #自动化测试 #scpi