项目概述
这是一个用于与PLC(可编程逻辑控制器)进行Modbus TCP通信的Python项目。项目提供了完整的Modbus TCP客户端功能,包括连接管理、数据读写等操作。
代码逐句解释
1. 导入模块
from pymodbus.client import ModbusTcpClient
import logging
from typing import Optional, Tuple, Union
解释:
pymodbus.client.ModbusTcpClient
: 这是pymodbus库提供的Modbus TCP客户端类,用于建立与PLC的TCP连接logging
: Python标准库,用于记录日志信息typing
: Python类型提示模块,提供类型注解功能Optional
: 表示一个值可以是某种类型或NoneTuple
: 表示元组类型Union
: 表示联合类型(Python 3.10+可用|
替代)
2. 类定义和初始化
class ModbusCommunication:
def __init__(self, plc_ip: str, plc_port: int = 502):
解释:
- 定义了一个
ModbusCommunication
类来封装所有Modbus通信功能 __init__
方法接收PLC的IP地址和端口号plc_port: int = 502
: 默认端口502是Modbus TCP的标准端口
self.plc_ip = plc_ip
self.plc_port = plc_port
self.client = None
self.connected = False
解释:
- 存储PLC的IP地址和端口号
self.client
: 将存储ModbusTcpClient实例self.connected
: 跟踪连接状态
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
self.logger = logging.getLogger(__name__)
解释:
- 配置日志系统,设置日志级别为INFO
- 日志格式包含时间戳、日志级别和消息内容
logging.getLogger(__name__)
: 获取当前模块的logger实例
3. 连接管理方法
connect() 方法
def connect(self) -> bool:
"""
连接到PLC
:return: 连接是否成功
"""
try:
self.client = ModbusTcpClient(self.plc_ip, port=self.plc_port)
self.connected = self.client.connect()
解释:
- 创建ModbusTcpClient实例,传入PLC的IP地址和端口
self.client.connect()
: 尝试建立TCP连接,返回布尔值表示连接状态
if self.connected:
self.logger.info(f"成功连接到PLC {self.plc_ip}:{self.plc_port}")
else:
self.logger.error("连接PLC失败")
return self.connected
except Exception as e:
self.logger.error(f"连接PLC失败: {str(e)}")
self.connected = False
return False
解释:
- 根据连接结果记录相应的日志信息
- 使用异常处理捕获连接过程中的错误
- 返回连接状态
disconnect() 方法
def disconnect(self):
"""
断开与PLC的连接
"""
if self.client:
try:
self.client.close()
self.connected = False
self.logger.info("已断开与PLC的连接")
except Exception as e:
self.logger.error(f"断开连接时发生错误: {str(e)}")
解释:
- 检查client是否存在,然后调用
close()
方法断开连接 - 更新连接状态并记录日志
4. 数据读写方法
read_holding_registers() 方法
def read_holding_registers(self, address: int, count: int = 1) -> Tuple[bool, Optional[list]]:
"""
读取保持寄存器
:param address: 起始地址
:param count: 读取数量
:return: (是否成功, 读取的数据)
"""
if not self.connected:
self.logger.error("未连接到PLC")
return False, None
try:
result = self.client.read_holding_registers(address, count)
if result.isError():
self.logger.error(f"读取保持寄存器失败: {result}")
return False, None
return True, result.registers
except Exception as e:
self.logger.error(f"读取保持寄存器失败: {str(e)}")
return False, None
解释:
- 保持寄存器(Holding Registers): Modbus协议中的一种数据类型,用于存储16位数据
read_holding_registers(address, count)
: pymodbus的方法,读取指定地址开始的多个寄存器result.isError()
: 检查操作是否成功result.registers
: 获取读取到的寄存器数据列表
write_holding_register() 方法
def write_holding_register(self, address: int, value: int) -> bool:
"""
写入保持寄存器
:param address: 地址
:param value: 要写入的值
:return: 是否成功
"""
if not self.connected:
self.logger.error("未连接到PLC")
return False
try:
result = self.client.write_register(address, value)
if result.isError():
self.logger.error(f"写入保持寄存器失败: {result}")
return False
return True
except Exception as e:
self.logger.error(f"写入保持寄存器失败: {str(e)}")
return False
解释:
write_register(address, value)
: 向指定地址写入单个寄存器值- 返回布尔值表示操作是否成功
read_coils() 方法
def read_coils(self, address: int, count: int = 1) -> Tuple[bool, Optional[list]]:
"""
读取线圈
:param address: 起始地址
:param count: 读取数量
:return: (是否成功, 读取的数据)
"""
if not self.connected:
self.logger.error("未连接到PLC")
return False, None
try:
result = self.client.read_coils(address, count)
if result.isError():
self.logger.error(f"读取线圈失败: {result}")
return False, None
return True, result.bits
except Exception as e:
self.logger.error(f"读取线圈失败: {str(e)}")
return False, None
解释:
- 线圈(Coils): Modbus协议中的另一种数据类型,用于存储布尔值(开/关状态)
read_coils(address, count)
: 读取指定地址开始的多个线圈状态result.bits
: 获取读取到的线圈状态列表(布尔值)
write_coil() 方法
def write_coil(self, address: int, value: bool) -> bool:
"""
写入线圈
:param address: 地址
:param value: 要写入的值
:return: 是否成功
"""
if not self.connected:
self.logger.error("未连接到PLC")
return False
try:
result = self.client.write_coil(address, value)
if result.isError():
self.logger.error(f"写入线圈失败: {result}")
return False
return True
except Exception as e:
self.logger.error(f"写入线圈失败: {str(e)}")
return False
解释:
write_coil(address, value)
: 向指定地址写入单个线圈状态value
参数是布尔值,表示开(True)或关(False)
Modbus TCP 详细知识点
1. Modbus协议概述
Modbus是一种工业通信协议,广泛应用于工业自动化系统中。它支持多种传输方式:
- Modbus RTU: 基于串行通信(RS-485)
- Modbus ASCII: 基于串行通信的ASCII编码
- Modbus TCP: 基于TCP/IP网络通信
2. pymodbus库详解
主要类和函数
ModbusTcpClient类
from pymodbus.client import ModbusTcpClient
构造函数参数:
host
: PLC的IP地址port
: 端口号(默认502)timeout
: 连接超时时间retries
: 重试次数
主要方法:
connect()
: 建立TCP连接close()
: 关闭连接read_holding_registers()
: 读取保持寄存器write_register()
: 写入单个寄存器read_coils()
: 读取线圈write_coil()
: 写入单个线圈
3. Modbus数据类型
保持寄存器(Holding Registers)
- 功能码: 03(读)、06(写单个)、16(写多个)
- 数据类型: 16位无符号整数
- 地址范围: 40001-49999
- 用途: 存储配置参数、传感器数据等
线圈(Coils)
- 功能码: 01(读)、05(写单个)、15(写多个)
- 数据类型: 布尔值(开/关)
- 地址范围: 00001-09999
- 用途: 控制开关、继电器等
输入寄存器(Input Registers)
- 功能码: 04(读)
- 数据类型: 16位无符号整数
- 地址范围: 30001-39999
- 用途: 只读传感器数据
离散输入(Discrete Inputs)
- 功能码: 02(读)
- 数据类型: 布尔值
- 地址范围: 10001-19999
- 用途: 只读开关状态
4. 错误处理
pymodbus提供了完善的错误处理机制:
result = client.read_holding_registers(0, 10)
if result.isError():
print(f"错误: {result}")
else:
print(f"数据: {result.registers}")
5. 连接管理最佳实践
- 连接检查: 每次操作前检查连接状态
- 异常处理: 使用try-catch捕获网络异常
- 日志记录: 记录所有操作和错误信息
- 资源清理: 及时关闭连接释放资源
6. 性能优化建议
- 连接复用: 避免频繁建立/断开连接
- 批量操作: 一次读取多个寄存器而不是逐个读取
- 超时设置: 合理设置连接和操作超时时间
- 重试机制: 实现自动重试机制处理临时网络问题
使用示例
# 创建通信实例
modbus = ModbusCommunication("192.168.1.100", 502)
# 连接到PLC
if modbus.connect():
# 读取保持寄存器
success, data = modbus.read_holding_registers(0, 10)
if success:
print(f"读取到的数据: {data}")
# 写入线圈
if modbus.write_coil(0, True):
print("线圈写入成功")
# 断开连接
modbus.disconnect()
依赖安装
pip install pymodbus
注意事项
- 确保PLC的IP地址和端口号正确
- 检查网络连接和防火墙设置
- 确认PLC支持Modbus TCP协议
- 注意寄存器地址的起始偏移(有些PLC从0开始,有些从1开始)
- 数据类型转换时注意字节序(大端/小端)