2. Modbus TCP客户端实现

任务目标

实现一个Modbus TCP客户端,连接远程PLC(或Modbus TCP从站模拟器),读取保持寄存器(Holding Register,功能码0x03)数据,并能够识别和处理异常响应(如非法地址、非法功能码等)。

环境与依赖

  • Python 3.8+
  • 依赖库:pymodbus(安装命令:pip install pymodbus)
  • 验证工具:可使用 Modbus Slave 模拟器或任意Modbus TCP从站设备

完整实现代码

from pymodbus.client import ModbusTcpClient
from pymodbus.exceptions import ModbusException, ConnectionException
from pymodbus.pdu import ExceptionResponse

def read_holding_registers(host: str, port: int, start_addr: int, count: int, unit_id: int = 1):
    """
    Modbus TCP客户端:读取保持寄存器并处理异常响应。
    返回: (成功标志, 寄存器列表或错误信息)
    """
    client = ModbusTcpClient(host, port=port, timeout=5)
    try:
        # 建立TCP连接
        if not client.connect():
            return False, f"连接失败: {host}:{port}"

        # 发送读保持寄存器请求(功能码0x03)
        response = client.read_holding_registers(address=start_addr, count=count, slave=unit_id)

        # 检查是否收到Modbus异常响应帧
        if isinstance(response, ExceptionResponse):
            # 提取异常码并映射为可读信息
            exc_code = response.exception_code
            exc_map = {
                1: "非法功能码",
                2: "非法数据地址",
                3: "非法数据值",
                4: "从站设备故障"
            }
            msg = exc_map.get(exc_code, f"未知异常码({exc_code})")
            return False, f"Modbus异常响应: {msg}"

        # 检查响应是否有效
        if response.isError():
            return False, f"响应错误: {response}"

        # 正常返回寄存器值列表
        return True, response.registers

    except ConnectionException as e:
        return False, f"TCP连接异常: {e}"
    except ModbusException as e:
        return False, f"Modbus协议错误: {e}"
    except Exception as e:
        return False, f"未预期错误: {e}"
    finally:
        client.close()

if __name__ == "__main__":
    # 配置参数(按实际环境修改)
    PLC_HOST = "127.0.0.1"   # 远程PLC或模拟器IP
    PLC_PORT = 502           # Modbus TCP标准端口
    START_ADDR = 0           # 起始寄存器地址
    REG_COUNT = 10           # 读取寄存器数量

    ok, result = read_holding_registers(PLC_HOST, PLC_PORT, START_ADDR, REG_COUNT)
    if ok:
        print(f"读取成功,共{len(result)}个寄存器:")
        for i, val in enumerate(result):
            print(f"  寄存器[{START_ADDR + i}] = {val}")
    else:
        print(f"读取失败: {result}")

运行与验证

  1. 准备从站:启动Modbus TCP从站模拟器(如Modbus Slave),在地址0~9处预置10个保持寄存器值(如100~109),监听端口502。
  2. 运行客户端:执行 python modbus_tcp_client.py。
  3. 正常场景验证:观察输出应打印10个寄存器的值,且与从站预置值一致。
  4. 异常场景验证:将从站起始地址改为不存在的大地址(如3000),重新运行,程序应输出类似 Modbus异常响应: 非法数据地址 的错误信息,而不会崩溃。

判定标准

  • 正常读取时,程序能正确打印从站返回的寄存器值,数值与从站设置一致;
  • 从站返回异常响应时,程序能捕获并解析异常码,输出对应的中文错误描述;
  • 从站离线或端口不可达时,程序能捕获连接异常并给出明确提示,不抛出未处理异常。

关键说明

  • 本实现使用 pymodbus 库的同步客户端接口,timeout=5 指定TCP连接超时时间;
  • ExceptionResponse 是Modbus协议层返回的异常帧,需在业务逻辑前优先判断;
  • 异常码映射表覆盖了Modbus标准中最常见的4种异常类型,实际工程中可按需扩展。