Python中的Modbus通信

492 阅读5分钟

项目开发中遇到了一些需要与工业设备和PLC通信的需求,简单总结一下用到的工业协议及其在Python里的集成。

Modbus协议

Modbus 是OSI模型第 7 层上的应用层报文传输协议,它在连接至不同类型总线或网络的设备之间提供客户机/服务器通信。

物理接口

Modbus协议用到的物理接口有RS232,RS422,RS485,以太网口四种。

其中RS232,RS422,RS485使用的接口都是DP9针的接口,但是每根引脚在不同物理接口的作用不同,接入设备时注意分辨。

DP9针线引脚图

image.png

下表是三种接口线缆的引脚作用

image.png

PS:RS232母头接口和公头的区别就是2和3引脚交换

下表是三种通信接口区别

image.png

以太网口使用常规的网线即可通信

协议类型

Modbus协议目前存在用于串口、以太网以及其他支持互联网协议的网络的版本。

Modbus RTU(串口): 二进制表示数据,采用循环冗余校验的校验和

[地址码]  [功能码1字节] [数据]  [CRC校验码2字节]

Modbus ASCII(串口): 采用人类可读的、冗长的表示输入,采用纵向冗余校验的校验和

[起始冒号] [地址码]  [功能码1字节] [数据]  [LRC校验码] [回车换行]

Modbus TCP(网口): 基于TCP通信,与Modbus RTU相似,取消循环冗余校验的校验和

[事务处理标识] [协议标识] [长度] [单元标识符]  [功能码1字节] [数据]

Modbus UDP(网口): 基于UDP通信,与Modbus RTU相似,取消循环冗余校验的校验和

[事务处理标识] [协议标识] [长度] [单元标识符]  [功能码1字节] [数据]

Modbus 协议定义了一个与基础通信层无关的简单协议数据单元(PDU)。

PDU =  [功能码] [数据]  ,功能码为1字节,数据长度不定,由具体功能决定。

寄存器类型

Modbus寄存器根据存放的数据类型以及各自读写特性,将寄存器分为4个部分,这4个 部分可以连续也可以不连续,由开发者决定。

image.png

功能码

image.png

Python通信

Python需要安装pymodbus模块来实现modbus通信:

pip install pymodbus

Modbus_TCP

import modbus_tk.modbus_tcp as mt  # 核心组件
import modbus_tk.defines as cst  # 通信参数
import logging


class ModebusTcp:
    def __init__(self, host="127.0.0.1", port=502, timeout=1):
        self.host = host
        self.port = port 
        self.timeout = timeout
        self.master = mt.TcpMaster(host=self.host, port=self.port, timeout_in_sec)
    
    '''
    参数含义
    slave: Modbus从站地址,1到247,0为广播所有从站
    function_code: 功能码
    starting_address: 寄存器起始地址
    quality_of_x: 寄存器读写数量,写可为0,读至少为1
    output_value: 输出值,读操作无效,写操作是整数值或者一个列表
    data_format:数据格式,'>'为大端模式,'<'为小端模式
    '''
    
    def _write(self, slave, function_code, address, value, data_format):
        try:
            write_value = self.master.execute(slave=slave, function=function_code, starting_address=address, output_value=[value], data_fotmat='>L')
            logging.debug("Write success!")
        except Exception as e:
            logging.error("Write failed,info:{0}".format(e))
    
    def _read(self, slave, function_code, address, data_format)
        try:
            read_value = self.master.execute(slave=slave, function=function_code, starting_address=address, quantity_of_x=2, data_format='>L')
            logging.debug("Read success!")
        except Exception as e:
            logging.error("Read failed,info:{0}".format(e))

通信参数格式

slave: 从站ID,服务端设置

function_code: 根据功能设置,可以从modbus_tk.defines里查询

starting_address: 根据想要通信的数据地址设置

quality_of_x: 读/写的寄存器数量,Modbus 以16位为一个字进行编址,寄存器是16位的,可以存放两个字节 ,即1寄存器 = 2字节

output_value: (仅写)输出数据,一个int值或者一个列表

data_format: 数据格式

image.png

Mosbus_RTU

from modbus_tk import modbus_rtu
import modbus_tk.defines as cst
import logging


class ModbusRtu:
    def __init__(self, port, baudrate, stopbits=1, parity="N", bytesize=8, timeout=1, xonxoff=0):
        self.port = port
        self.baudrate = baudrate
        self.client = modbus_rtu.RtuMaster(serial.Serial(port=port, baudrate=baudrate, bytesize=bytesize, parity=parity, stopbits=stopbits))
    
    def _write(self, slave, function_code, address, value, data_format):
        try:
            write_value = self.master.execute(slave=slave, function=function_code, starting_address=address, output_value=[value], data_fotmat='>L')
            logging.debug("Write success!")
        except Exception as e:
            logging.error("Write failed,info:{0}".format(e))
    
    def _read(self, slave, function_code, address, data_format)
        try:
            read_value = self.master.execute(slave=slave, function=function_code, starting_address=address, quantity_of_x=2, data_format='>L')
            logging.debug("Read success!")
        except Exception as e:
            logging.error("Read failed,info:{0}".format(e))

参数配置除波特率和端口外与Modebus—tcp参数含义相同。

ETHERNET协议

ETHERNET同样也是网络协议,与Modbus_Tcp又很多相同之处。

ETHERNET和MODBUS TCP都是工业通讯协议,但它们的应用场景和特点不同:

ETHERNET。在TCP/IP层之上增加了用于实时数据交换和运行实时应用的CIP协议(Common Industrial Protocol)。在物理层和数据链路层使用以太网技术,在网络层和传输层使用IP协议和TCP、UDP协议来传输数据。

Modbus_Tcp。是一种中立厂商的用于管理和控制自动化设备的MODBUS系列通讯协议的派生产品。它覆盖了使用TCP/IP协议的 "Intranet"和"Internet"环境中MODBUS报文的用途。

PS:ETHERNET的默认端口是23,Modbus_Tcp的默认端口是502。

在Python中可以通过套接字来实现通信。

import socket
import logging


class Ethernet:
    def __init__(self, host, port=23):
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        
    def send_data(self, data)
        try:
            self.socket.connect()
            data = data.encode()
            self.socket.send(data)
            self.socket.close()
            logging.debug("Send data success!")
        except Exception as e:
            logging.error("Send data failed!")
        
    def get_data(self, bufsize)
        try:    
            self.socket.connect()
            re = self.socket.recv(bufsize)
            self.socket.close()
            logging.info("Get data:{0}".format(re))
        except:
            logging.error("Get data failed!")

Prifibus暂未用到,以后补充。