python實現(xiàn)基于串口通信的ModBusRTU服務端是一件簡單的事情,只要通過pymodbus模塊就可以實現(xiàn)。
一、一個Demo及其引發(fā)的問題
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
# 創(chuàng)建數(shù)據(jù)存儲區(qū)
# 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
# 定義函數(shù)參數(shù)
write_address = 0 # 起始地址
read_address = 10
read_count = 10
values = [115] # 觸發(fā)指令s 要設置的多個值列表,如[10, 20, 30]
# 調(diào)用函數(shù),設置0地址為觸發(fā)指令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 通信協(xié)議中,數(shù)據(jù)以幀的形式進行傳輸。ModbusRtuFramer 是 pymodbus 庫中的一個類,它的作用是負責處理 Modbus RTU 幀的編碼和解碼。
具體來說,ModbusRtuFramer 完成了以下幾個主要任務:
①編碼(封裝)Modbus RTU 幀:當你需要發(fā)送 Modbus RTU 請求或響應時,ModbusRtuFramer 負責將請求或響應的數(shù)據(jù)按照 Modbus RTU 協(xié)議的格式進行封裝,生成符合 Modbus RTU 規(guī)范的幀,以便發(fā)送到 Modbus 設備。
②解碼(解析)Modbus RTU 幀:當你從 Modbus 設備接收到數(shù)據(jù)時,ModbusRtuFramer 負責將接收到的二進制數(shù)據(jù)按照 Modbus RTU 協(xié)議的格式進行解析,提取出請求或響應的數(shù)據(jù),以便進行后續(xù)的處理和分析。
③錯誤檢測和糾正:ModbusRtuFramer 也負責檢測 Modbus RTU 幀中的錯誤,比如奇偶校驗錯誤、幀起始符和結束符錯誤等。如果幀中存在錯誤,ModbusRtuFramer 會幫助你識別錯誤的位置和類型,便于進行糾正或錯誤處理。
總之,ModbusRtuFramer 是一個處理 Modbus RTU 幀的工具,它確保了在 Modbus RTU 通信中,數(shù)據(jù)的正確封裝和解析,以及錯誤的檢測和處理。
(2)ModbusSequentialDataBlock、ModbusSlaveContext與ModbusServerContext的作用
在 pymodbus 庫中,ModbusSequentialDataBlock、ModbusSlaveContext 和 ModbusServerContext 是用來處理 Modbus 數(shù)據(jù)存儲和上下文的類,它們的作用如下:
①ModbusSequentialDataBlock:
ModbusSequentialDataBlock 是一個用于創(chuàng)建順序排列的 Modbus 寄存器塊的類。
它通常用于模擬設備的保持寄存器(Holding Registers)或輸入寄存器(Input Registers)。
這個類允許你定義寄存器的起始地址和初始化寄存器的值。
例如:ModbusSequentialDataBlock(0, [0] * 100) 創(chuàng)建了一個從地址 0 開始,包含 100 個初始值為 0 的保持寄存器的塊。
②ModbusSlaveContext:
ModbusSlaveContext 是一個用于表示 Modbus 從設備的類。
它包含一個或多個數(shù)據(jù)存儲塊(比如保持寄存器塊、輸入寄存器塊等)。
這個類可以用來創(chuàng)建一個模擬的 Modbus 從設備上下文。
你可以在這個上下文中添加多個不同類型的數(shù)據(jù)塊,模擬一個完整的 Modbus 從設備。
③ModbusServerContext:
ModbusServerContext 是一個用于表示整個 Modbus 服務器的類。
它包含一個或多個 Modbus 從設備的上下文(ModbusSlaveContext 實例)。
這個類可以用來創(chuàng)建一個完整的 Modbus 服務器環(huán)境,包含多個模擬的 Modbus 從設備。
在搭建 Modbus 通信環(huán)境時,你通常會創(chuàng)建 ModbusSequentialDataBlock 實例作為寄存器的存儲,然后將它們添加到 ModbusSlaveContext 中。最后,將多個 ModbusSlaveContext 實例添加到 ModbusServerContext 中,以構建一個包含多個從設備的 Modbus 服務器環(huán)境。這樣的架構可以讓你模擬多個不同類型的 Modbus 從設備。
(3)context = ModbusServerContext(slaves={1: data_block}, single=False)的解釋
在這個代碼行中,你創(chuàng)建了一個 ModbusServerContext
對象,該對象用于模擬一個 Modbus 服務器的上下文。ModbusServerContext
是 pymodbus
模塊中的一個類,它用于存儲和管理 Modbus 服務器的數(shù)據(jù)。在這個特定的代碼行中,你傳遞了一些參數(shù)給 ModbusServerContext
構造函數(shù):
slaves={1: data_block}
: 這個參數(shù)是一個字典,表示 Modbus 服務器的從設備。在這里,你創(chuàng)建了一個從設備,其 Modbus 地址為1
,并且將這個從設備關聯(lián)到了一個data_block
對象上。data_block
可能是一個ModbusSequentialDataBlock
對象,用于存儲 Modbus 寄存器的數(shù)據(jù)。single=False
: 這個參數(shù)用于確定是否將所有的從設備視為一個整體。當single=True
時,所有的從設備共享相同的 Modbus 地址空間,它們沒有獨立的地址范圍。而當single=False
時,每個從設備都有獨立的 Modbus 地址空間,它們的地址范圍是相互獨立的。
所以,這一行代碼的意義是創(chuàng)建了一個 ModbusServerContext
對象,該對象包含一個從設備(Modbus 地址為 1
),并且這個從設備擁有獨立的 Modbus 地址空間。這個上下文可以在模擬多個獨立的 Modbus 設備時使用。
(4)關于StartSerialServer啟動服務器時必須的參數(shù)
必須傳入以下參數(shù)才能正常啟動
"context": context
表示傳入的 Modbus 上下文。"framer": framer
表示 Modbus 使用的幀格式。"port": port
表示串口號,例如"COM46"
。"baudrate": baudrate
表示波特率,即每秒傳輸?shù)奈粩?shù)。
這些參數(shù)將被傳遞給 StartSerialServer
函數(shù),用于啟動 Modbus RTU 服務器。
這需要根據(jù)客戶端的設置來對應設置。
2、權限問題
建議使用管理員權限運行腳本。
3、端口占用問題
確保端口沒有被其他軟件占用。
4、pymodbus版本問題
網(wǎng)上的示例代碼,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 庫中用于創(chuàng)建稀疏數(shù)據(jù)塊(sparse data block)的類。在 Modbus 協(xié)議中,數(shù)據(jù)通常被組織成多個數(shù)據(jù)塊,而每個數(shù)據(jù)塊包含一定數(shù)量的數(shù)據(jù)寄存器或者線圈。
ModbusSparseDataBlock
允許您創(chuàng)建包含不連續(xù)地址的數(shù)據(jù)塊。具體來說,您可以在數(shù)據(jù)塊中指定特定地址的數(shù)據(jù),而無需為數(shù)據(jù)塊的每個地址都分配內(nèi)存。這種方式可以有效地節(jié)省內(nèi)存空間,尤其是在處理大量數(shù)據(jù)時。
以下是 ModbusSparseDataBlock
的初始化參數(shù):
values
:一個字典,包含要存儲的數(shù)據(jù)。字典的鍵是地址,值是相應地址的數(shù)據(jù)值。address
:數(shù)據(jù)塊的起始地址。size
:數(shù)據(jù)塊的大小,即包含的地址數(shù)量。