python实现基于串口通信的ModBusRTU服务端是一件简单的事情,只要通过pymodbus模块就可以实现。
一、一个Demo及其引发的问题
1、一个Demo
import asyncio
import json
import threading
import time
from pymodbus.server import StartSerialServer, ServerAsyncStop
from pymodbus.datastore import ModbusSequentialDataBlock, ModbusServerContext, ModbusSlaveContext
from pymodbus.transaction import ModbusRtuFramer
from Application.common.private_socket import Request
if __name__ == "__main__":
# 定义串口配置
port = "COM46"
# Serial(port="COM46", baudrate=115200, timeout=2, bytesize=8, parity="N", stopbits=1)
framer = ModbusRtuFramer
# 创建数据存储区
# data_block = ModbusSequentialDataBlock(0, [0] * 100) # 100个保持寄存器?
data_block = ModbusSlaveContext(
hr=ModbusSequentialDataBlock(0, [0] * 100)
)
# store = ModbusSlaveContext(hr=data_block)
# context = ModbusServerContext(slaves=store)
context = ModbusServerContext(slaves={1: data_block}, single=False)
# 启动Modbus RTU服务器
# StartSerialServer(context=context, framer=framer, port="COM46")
# thread = threading.Thread(target=StartSerialServer, kwargs={"context": context, "framer": framer, "port": port, "baudrate":9600, "timeout":2, "bytesize":8, "parity":"N", "stopbits":1})
thread = threading.Thread(target=StartSerialServer, kwargs={"context": context, "framer": framer, "port": port, "baudrate":9600})
thread.start()
# 设置保持寄存器的0地址的值为s
# 定义函数参数
write_address = 0 # 起始地址
read_address = 10
read_count = 10
values = [115] # 触发指令s 要设置的多个值列表,如[10, 20, 30]
# 调用函数,设置0地址为触发指令s
data_block.setValues(16, write_address, values)
while True:
time.sleep(3)
# 获取保持寄存器的值并打印
hr_values = data_block.getValues(3, 0, count=20)
print("Hold Register Values:", hr_values)
asyncio.run(ServerAsyncStop()) # 停止服务器
在这里简单说明上述程序涉及到的对象:
(1)ModbusRtuFramer的作用
在 Modbus 通信协议中,数据以帧的形式进行传输。ModbusRtuFramer 是 pymodbus 库中的一个类,它的作用是负责处理 Modbus RTU 帧的编码和解码。
具体来说,ModbusRtuFramer 完成了以下几个主要任务:
①编码(封装)Modbus RTU 帧:当你需要发送 Modbus RTU 请求或响应时,ModbusRtuFramer 负责将请求或响应的数据按照 Modbus RTU 协议的格式进行封装,生成符合 Modbus RTU 规范的帧,以便发送到 Modbus 设备。
②解码(解析)Modbus RTU 帧:当你从 Modbus 设备接收到数据时,ModbusRtuFramer 负责将接收到的二进制数据按照 Modbus RTU 协议的格式进行解析,提取出请求或响应的数据,以便进行后续的处理和分析。
③错误检测和纠正:ModbusRtuFramer 也负责检测 Modbus RTU 帧中的错误,比如奇偶校验错误、帧起始符和结束符错误等。如果帧中存在错误,ModbusRtuFramer 会帮助你识别错误的位置和类型,便于进行纠正或错误处理。
总之,ModbusRtuFramer 是一个处理 Modbus RTU 帧的工具,它确保了在 Modbus RTU 通信中,数据的正确封装和解析,以及错误的检测和处理。
(2)ModbusSequentialDataBlock、ModbusSlaveContext与ModbusServerContext的作用
在 pymodbus 库中,ModbusSequentialDataBlock、ModbusSlaveContext 和 ModbusServerContext 是用来处理 Modbus 数据存储和上下文的类,它们的作用如下:
①ModbusSequentialDataBlock:
ModbusSequentialDataBlock 是一个用于创建顺序排列的 Modbus 寄存器块的类。
它通常用于模拟设备的保持寄存器(Holding Registers)或输入寄存器(Input Registers)。
这个类允许你定义寄存器的起始地址和初始化寄存器的值。
例如:ModbusSequentialDataBlock(0, [0] * 100) 创建了一个从地址 0 开始,包含 100 个初始值为 0 的保持寄存器的块。
②ModbusSlaveContext:
ModbusSlaveContext 是一个用于表示 Modbus 从设备的类。
它包含一个或多个数据存储块(比如保持寄存器块、输入寄存器块等)。
这个类可以用来创建一个模拟的 Modbus 从设备上下文。
你可以在这个上下文中添加多个不同类型的数据块,模拟一个完整的 Modbus 从设备。
③ModbusServerContext:
ModbusServerContext 是一个用于表示整个 Modbus 服务器的类。
它包含一个或多个 Modbus 从设备的上下文(ModbusSlaveContext 实例)。
这个类可以用来创建一个完整的 Modbus 服务器环境,包含多个模拟的 Modbus 从设备。
在搭建 Modbus 通信环境时,你通常会创建 ModbusSequentialDataBlock 实例作为寄存器的存储,然后将它们添加到 ModbusSlaveContext 中。最后,将多个 ModbusSlaveContext 实例添加到 ModbusServerContext 中,以构建一个包含多个从设备的 Modbus 服务器环境。这样的架构可以让你模拟多个不同类型的 Modbus 从设备。
(3)context = ModbusServerContext(slaves={1: data_block}, single=False)的解释
在这个代码行中,你创建了一个 ModbusServerContext
对象,该对象用于模拟一个 Modbus 服务器的上下文。ModbusServerContext
是 pymodbus
模块中的一个类,它用于存储和管理 Modbus 服务器的数据。在这个特定的代码行中,你传递了一些参数给 ModbusServerContext
构造函数:
slaves={1: data_block}
: 这个参数是一个字典,表示 Modbus 服务器的从设备。在这里,你创建了一个从设备,其 Modbus 地址为1
,并且将这个从设备关联到了一个data_block
对象上。data_block
可能是一个ModbusSequentialDataBlock
对象,用于存储 Modbus 寄存器的数据。single=False
: 这个参数用于确定是否将所有的从设备视为一个整体。当single=True
时,所有的从设备共享相同的 Modbus 地址空间,它们没有独立的地址范围。而当single=False
时,每个从设备都有独立的 Modbus 地址空间,它们的地址范围是相互独立的。
所以,这一行代码的意义是创建了一个 ModbusServerContext
对象,该对象包含一个从设备(Modbus 地址为 1
),并且这个从设备拥有独立的 Modbus 地址空间。这个上下文可以在模拟多个独立的 Modbus 设备时使用。
(4)关于StartSerialServer启动服务器时必须的参数
必须传入以下参数才能正常启动
"context": context
表示传入的 Modbus 上下文。"framer": framer
表示 Modbus 使用的帧格式。"port": port
表示串口号,例如"COM46"
。"baudrate": baudrate
表示波特率,即每秒传输的位数。
这些参数将被传递给 StartSerialServer
函数,用于启动 Modbus RTU 服务器。
这需要根据客户端的设置来对应设置。
2、权限问题
建议使用管理员权限运行脚本。
3、端口占用问题
确保端口没有被其他软件占用。
4、pymodbus版本问题
网上的示例代码,StartSerialServer要求传入serial模块的Serial(port=COM, baudrate=Baudrate, timeout=2, bytesize=Databits, parity=Parity, stopbits=Stopbits)实例,我不确定是哪个版本,你可以试一下,目前在新版本只要传入波特率和端口即可。
二、pymodbus相关文档
1、官方文档地址
Datastore — PyModbus 3.6.0dev documentation
2、Datastore对象
Datastore is responsible for managing registers for a server.
(1)pymodbus.datastore.ModbusSparseDataBlock
pymodbus.datastore.ModbusSparseDataBlock
是 PyModbus 库中用于创建稀疏数据块(sparse data block)的类。在 Modbus 协议中,数据通常被组织成多个数据块,而每个数据块包含一定数量的数据寄存器或者线圈。
ModbusSparseDataBlock
允许您创建包含不连续地址的数据块。具体来说,您可以在数据块中指定特定地址的数据,而无需为数据块的每个地址都分配内存。这种方式可以有效地节省内存空间,尤其是在处理大量数据时。
以下是 ModbusSparseDataBlock
的初始化参数:
values
:一个字典,包含要存储的数据。字典的键是地址,值是相应地址的数据值。address
:数据块的起始地址。size
:数据块的大小,即包含的地址数量。