PLC寄存器写入验证工具
概述
PLC寄存器写入验证工具是一个基于PyQt5和modbus_tk库开发的图形界面应用程序,用于通过Modbus TCP协议与PLC设备进行通信,实现对PLC寄存器的写入操作及结果验证。
该工具提供了简洁直观的用户界面,支持连接到PLC设备、写入数据到指定寄存器,并自动验证写入结果,适用于工业自动化领域的设备调试和数据验证工作。
功能特点
- 通过Modbus TCP协议与PLC设备建立连接
- 支持指定寄存器地址和写入值
- 写入数据后自动读取验证,确保数据正确写入
- 所有网络操作在后台线程执行,避免UI界面卡顿
- 实时显示连接状态和操作结果
- 友好的错误提示和状态反馈
依赖环境
运行本工具需要以下依赖库:
- Python 3.x
- PyQt5:用于构建图形用户界面
- modbus_tk:用于实现Modbus通信功能
可通过以下命令安装所需依赖:
pip install PyQt5 modbus-tk
使用方法
1. 连接PLC设备
- 在"PLC IP地址"输入框中填写目标PLC的IP地址(默认值:192.168.1.1)
- 在"端口"输入框中填写Modbus端口号(默认值:502,通常无需修改)
- 点击"连接"按钮建立与PLC的连接
- 连接成功后,状态会显示"成功连接到[IP地址:端口]",“连接"按钮变为"断开”
2. 写入数据到寄存器
- 确保已成功连接到PLC
- 在"寄存器地址"输入框中填写目标寄存器的地址(整数)
- 在"写入值"输入框中填写要写入的值(整数)
- 点击"写入数据"按钮执行写入操作
- 系统会自动完成写入并验证结果,操作结果会通过弹窗和状态标签显示
3. 断开连接
- 点击"断开"按钮可断开与PLC的连接
- 关闭应用程序时会自动断开连接
代码结构说明
主要类和功能
Communicate类:继承自QObject,用于在非UI线程和UI线程之间传递信号,避免线程安全问题
PLCModbusWriter类:主窗口类,继承自QMainWindow,包含以下核心方法:
__init__()
:初始化窗口界面和变量toggle_connection()
:切换连接状态(连接/断开)connect_to_plc()
:建立与PLC的连接(在后台线程执行)disconnect_from_plc()
:断开与PLC的连接write_data()
:处理写入数据的用户请求_write_data_thread()
:实际执行写入和验证操作(在后台线程执行)show_message()
:显示消息提示并更新状态标签update_ui_after_connection()
:根据连接状态更新UI元素closeEvent()
:窗口关闭时确保断开连接
线程处理
为避免网络操作导致UI界面卡顿,所有Modbus通信操作(连接、写入、读取)均在独立线程中执行:
- 连接操作通过
threading.Thread
在后台执行 - 写入和验证操作同样在单独线程中执行
- 使用信号槽机制(Communicate类)在后台线程和UI线程之间传递消息
注意事项
- 确保PLC设备已正确配置Modbus TCP服务器功能
- 确保PLC的IP地址和端口号输入正确,且与工具在同一网络中
- 寄存器地址和写入值必须为整数,否则会收到错误提示
- 操作超时时间设置为5秒,若网络环境较差可适当调整
- 连接状态会实时显示在窗口底部的状态标签中
错误处理
程序会捕获并显示以下常见错误:
- 无效的IP地址或端口号
- 无法连接到PLC设备(网络问题或设备未响应)
- 无效的寄存器地址或写入值
- 写入操作失败(权限问题或设备限制)
- 读取验证失败
所有错误信息都会通过弹窗和状态标签显示,便于问题诊断和排查。
import sys
import threading
import warnings
# 忽略特定警告
warnings.filterwarnings("ignore", message="sipPyTypeDict() is deprecated")
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
QHBoxLayout, QLabel, QLineEdit, QPushButton, QMessageBox)
from PyQt5.QtCore import Qt, pyqtSignal, QObject
import modbus_tk
import modbus_tk.defines as cst
from modbus_tk import modbus_tcp
# 用于在非UI线程中发送信号到UI线程
class Communicate(QObject):
signal = pyqtSignal(str)
class PLCModbusWriter(QMainWindow):
def __init__(self):
super().__init__()
# 初始化变量
self.master = None
self.is_connected = False
self.com = Communicate()
self.com.signal.connect(self.show_message)
# 设置窗口
self.setWindowTitle("PLC寄存器写入验证工具")
self.setGeometry(100, 100, 1200, 800)
# 创建中心部件和布局
central_widget = QWidget()
self.setCentralWidget(central_widget)
main_layout = QVBoxLayout(central_widget)
# IP地址和端口设置
ip_port_layout = QHBoxLayout()
ip_port_layout.addWidget(QLabel("PLC IP地址:"))
self.ip_edit = QLineEdit("192.168.1.1")
ip_port_layout.addWidget(self.ip_edit)
ip_port_layout.addWidget(QLabel("端口:"))
self.port_edit = QLineEdit("502") # 添加端口编辑框,默认502
self.port_edit.setMaximumWidth(80) # 限制端口输入框宽度
ip_port_layout.addWidget(self.port_edit)
main_layout.addLayout(ip_port_layout)
# 寄存器地址设置
addr_layout = QHBoxLayout()
addr_layout.addWidget(QLabel("寄存器地址:"))
self.addr_edit = QLineEdit("0")
addr_layout.addWidget(self.addr_edit)
main_layout.addLayout(addr_layout)
# 写入值设置
value_layout = QHBoxLayout()
value_layout.addWidget(QLabel("写入值:"))
self.value_edit = QLineEdit("0")
value_layout.addWidget(self.value_edit)
main_layout.addLayout(value_layout)
# 按钮布局
btn_layout = QHBoxLayout()
self.connect_btn = QPushButton("连接")
self.connect_btn.clicked.connect(self.toggle_connection)
btn_layout.addWidget(self.connect_btn)
self.write_btn = QPushButton("写入数据")
self.write_btn.clicked.connect(self.write_data)
self.write_btn.setEnabled(False) # 初始禁用
btn_layout.addWidget(self.write_btn)
main_layout.addLayout(btn_layout)
# 状态标签
self.status_label = QLabel("状态: 未连接")
self.status_label.setAlignment(Qt.AlignCenter)
main_layout.addWidget(self.status_label)
def toggle_connection(self):
if not self.is_connected:
# 连接到PLC
threading.Thread(target=self.connect_to_plc, daemon=True).start()
else:
# 断开连接
self.disconnect_from_plc()
def connect_to_plc(self):
try:
ip_address = self.ip_edit.text()
# 从输入框获取端口号,默认为502
try:
port = int(self.port_edit.text())
except ValueError:
self.com.signal.emit("端口号必须是整数")
return
# 创建Modbus TCP主站
self.master = modbus_tcp.TcpMaster(host=ip_address, port=port)
self.master.set_timeout(5.0)
# 尝试读取一个寄存器来验证连接
self.master.execute(1, cst.READ_HOLDING_REGISTERS, 0, 1)
# 连接成功
self.is_connected = True
self.com.signal.emit(f"成功连接到 {ip_address}:{port}")
self.update_ui_after_connection(True)
except Exception as e:
self.com.signal.emit(f"连接失败: {str(e)}")
self.is_connected = False
self.master = None
self.update_ui_after_connection(False)
def disconnect_from_plc(self):
if self.master:
self.master.close()
self.master = None
self.is_connected = False
self.show_message("已断开连接")
self.update_ui_after_connection(False)
def write_data(self):
if not self.is_connected or not self.master:
self.show_message("请先连接到PLC")
return
try:
# 获取输入值
addr = int(self.addr_edit.text())
value = int(self.value_edit.text())
# 在新线程中执行写入操作,避免UI卡顿
threading.Thread(target=self._write_data_thread, args=(addr, value), daemon=True).start()
except ValueError:
self.show_message("寄存器地址和值必须是整数")
except Exception as e:
self.show_message(f"写入错误: {str(e)}")
def _write_data_thread(self, addr, value):
try:
# 写入数据到保持寄存器
self.master.execute(1, cst.WRITE_SINGLE_REGISTER, addr, output_value=value)
self.com.signal.emit(f"成功写入: 地址 {addr} = {value}")
# 验证写入结果
result = self.master.execute(1, cst.READ_HOLDING_REGISTERS, addr, 1)
self.com.signal.emit(f"验证结果: 地址 {addr} = {result[0]}")
except Exception as e:
self.com.signal.emit(f"写入失败: {str(e)}")
def show_message(self, text):
QMessageBox.information(self, "信息", text)
self.status_label.setText(f"状态: {text}")
def update_ui_after_connection(self, connected):
if connected:
self.connect_btn.setText("断开")
self.write_btn.setEnabled(True)
self.ip_edit.setEnabled(False) # 连接后禁用IP编辑
self.port_edit.setEnabled(False) # 连接后禁用端口编辑
else:
self.connect_btn.setText("连接")
self.write_btn.setEnabled(False)
self.ip_edit.setEnabled(True) # 断开后启用IP编辑
self.port_edit.setEnabled(True) # 断开后启用端口编辑
def closeEvent(self, event):
# 关闭窗口时断开连接
if self.is_connected:
self.disconnect_from_plc()
event.accept()
if __name__ == "__main__":
app = QApplication(sys.argv)
window = PLCModbusWriter()
window.show()
sys.exit(app.exec_())