最近做了一個modbus tcp 傳輸浮點數(shù)的項目,參考了一些CSDN大佬的文章,這里做一個 整合和記錄。
modbus 協(xié)議
modbus 通信過程
- 摘自詳解Modbus通信協(xié)議—清晰易懂
- 一主多從的通信協(xié)議:Modbus 通信中只有主機可以發(fā)送請求。其他從設備接收主機發(fā)送的數(shù)據(jù)來進行響應——處理信息和使用 Modbus 將其數(shù)據(jù)發(fā)送給主站。從機不會主動發(fā)送消息給主站。
- Modbus 不能同步進行通信,主機在同一時間內(nèi)只能向一個從機發(fā)送請求,總線上每次只有一個數(shù)據(jù)進行傳輸,即主機發(fā)送,從機應答,主機不發(fā)送,總線上就沒有數(shù)據(jù)通信。
- Modbus沒有忙機制判斷,比方說主機給從機發(fā)送命令, 從機沒有收到或者正在處理其他東西,這時候就不能響應主機,因為 modbus 的總線只是傳輸數(shù)據(jù),沒有其他仲裁機制,所以需要通過軟件的方式來判斷是否正常接收。
Modbus 數(shù)據(jù)傳輸?shù)姆绞?,可以簡單地理解成打電話。并且是單向通信的打電?/p>
主機發(fā)送數(shù)據(jù),首先需要從機的電話號碼(區(qū)分每個從機,每個地址必須唯一),告訴從機打電話要干什么事情,然后是需要發(fā)送的內(nèi)容,最后再問問從機,我說的話你都聽清楚了沒有呀,沒有聽錯吧?
然后從機這里,得到了主機打過來的電話,從機回復主機需要的內(nèi)容,主機得到從機數(shù)據(jù),這樣就是一個主機到從機的通信過程
modbus 存儲區(qū)
-
忘記從哪找的了hhh
-
從機存儲數(shù)據(jù) -> 存儲區(qū)
- 文件操作 -> 只讀(-r)和讀寫(-wr)
- 數(shù)據(jù)類型 -> 布爾量 和 16 位寄存器
- 布爾量比如 IO 口的電平高低,燈的開關(guān)狀態(tài)等。
- 16 位寄存器比如 傳感器的溫度數(shù)據(jù),存儲的密碼等。
-
Modbus 協(xié)議規(guī)定了 4 個存儲區(qū),分別是 0 1 3 4 區(qū) 其中 1 區(qū)和 4 區(qū)是可讀可寫,1 區(qū)和 3 區(qū)是只讀
區(qū)號 名稱 讀寫 地址范圍 0 區(qū) 輸出線圈 可讀可寫布爾量 00001-09999 1 區(qū) 輸入線圈 只讀布爾量 10001-19999 3 區(qū) 輸入寄存器 只讀寄存器 30001-39999 4 區(qū) 保持寄存器 可讀可寫寄存器 40001-49999 -
Modbus 給每個區(qū)都劃分了地址范圍,主機向從機獲取數(shù)據(jù)時,只需要告訴從機數(shù)據(jù)的起始地址,還有獲取多少字節(jié)的數(shù)據(jù),從機就可以發(fā)送數(shù)據(jù)給主機
-
每一個從機,都有實際的物理存儲,跟 modbus 的存儲區(qū)相對應,主機讀寫從機的存儲區(qū),實際上就是對從機設備對應的實際存儲空間進行讀寫
Modbus-TCP 協(xié)議
Modbus-TCP 報文幀結(jié)構(gòu)
-
摘自ModbusTCP協(xié)議報文詳細分析
-
報文幀結(jié)構(gòu)
> > > MBAP 報文頭(7 bytes) > 協(xié)議數(shù)據(jù)單元(PDU) 事務處理標識符 協(xié)議標識符 長度 單元標識符 功能碼 數(shù)據(jù) 2 bytes 2 bytes 2 bytes 1 byte 1 byte N bytes -
MBAP 報文頭
域 長度 說明 客戶機 服務器 事務處理標識符 2 字節(jié) Modbus 請求/響應事務處理的標識 客戶機啟動 復制響應 協(xié)議標識符 2 字節(jié) 0=Modbus 協(xié)議 客戶機啟動 復制響應 長度 2 字節(jié) 長度之后的字節(jié)總數(shù) 客戶機啟動 服務器啟動 單元標識符 1 字節(jié) 串行鏈路或其它總線的從站識別 客戶端啟動 復制響應 事務處理標識符 and 協(xié)議標識符 正常使用設置為 0 即可,長度為 4 個字節(jié) -> 0x00000000
-
功能碼
功能碼 功能說明 01H 讀取輸出線圈 02H 讀取輸入線圈 03H 讀取保持寄存器 04H 讀取輸入寄存器 05H 寫入單線圈 06H 寫入單寄存器 0FH 寫入多線圈 10H 寫入多寄存器 -
每次可讀數(shù)據(jù)最長長度(260 字節(jié) - 9 字節(jié) = 251 字節(jié))
- Modbus TCP 報文幀最長為 260 字節(jié) -> MBAP 7 字節(jié) + PDU 253 字節(jié)
- 返回報文的 PDU 中: 功能碼 1 字節(jié) + 字節(jié)計數(shù) 1 字節(jié) + 數(shù)據(jù) 251 字節(jié)
- 一個寄存器 2 字節(jié)
- 當數(shù)據(jù)為 32 位浮點數(shù),一個數(shù)據(jù)占用兩個寄存器 -> 一個數(shù)據(jù) 4 字節(jié)
- 每次最多可讀取:數(shù)據(jù) 251 字節(jié) -> 125 個寄存器 -> 62 個 32 位浮點數(shù)(讀取 124 個寄存器)
- 從站返回報文格式詳解
> > 返回報文編碼格式詳解 字節(jié)位 結(jié)構(gòu) 編碼格式 前 6 個字節(jié) 事務/協(xié)議和數(shù)據(jù)長度 ‘>HHH’ 第 7 個字節(jié) 單元標識 ‘>B’ 第 8 個字節(jié) 功能碼 ‘>B’ 第 9 個字節(jié) 數(shù)據(jù)長度 ‘>B’ 剩余字節(jié) 數(shù)據(jù) data_format(default or 自定義)
mosbus_tk庫
介紹
- 安裝:
pip install modbus_tk
- github主頁
- 主站示例代碼
- 從站示例代碼
從站記錄的數(shù)據(jù)格式
timestamp | lon | lat | cog |
---|---|---|---|
1651924800 | 108.6030967 | 20.57094167 | 169.9 |
1651925400 | 108.6061227 | 20.56535943 | 170.7 |
1651926000 | 108.6091487 | 20.5597772 | 171.7 |
1651926600 | 108.6121746 | 20.55419496 | 143.8 |
1651927200 | 108.6152006 | 20.54861273 | 203.8 |
1651927800 | 108.6182266 | 20.5430305 | 207.8 |
1651928400 | 108.6212526 | 20.53744826 | 181.4 |
1651929000 | 108.6242786 | 20.53186603 | 180.6 |
1651929600 | 108.6273046 | 20.52628379 | 179.2 |
1651930200 | 108.6303306 | 20.52070156 | 172.9 |
1651930800 | 108.6333566 | 20.51511933 | 172.6 |
1651931400 | 108.6363826 | 20.50953709 | 173 |
主站
- 根據(jù)示例改的,直接上代碼
import modbus_tk
import modbus_tk.defines as cst
from modbus_tk import modbus_tcp, hooks
import numpy as np
import pandas as pd
master = modbus_tcp.TcpMaster()
master.set_timeout(5.0)
print("connected")
# 連接從站讀取數(shù)據(jù),一次最多讀取125個寄存器,由于2個寄存器為一個數(shù)據(jù),故 size 設置為124
data = [] # 存放讀取的數(shù)據(jù)
data += master.execute( # 向從站發(fā)報文讀取[0,123]區(qū)間的寄存器數(shù)據(jù)
1, # 從站標識符
cst.READ_HOLDING_REGISTERS, # 功能碼
0, # 起始寄存器地址
124, # 讀取的寄存器數(shù)量
data_format='62f', # 數(shù)據(jù)解碼格式
)
data += master.execute( # 向從站發(fā)送報文讀取[124,247]區(qū)間的寄存器數(shù)據(jù)
1, cst.READ_HOLDING_REGISTERS, 124, 124, data_format='62f'
)
# 將數(shù)據(jù)保存到csv文件中
if len(data) % 4 != 0: # 如果數(shù)據(jù)長度不是4的倍數(shù),則用 0 補齊
for i in range(0, 4 - len(data) % 4):
data.append(0)
data = np.reshape(data, (-1, 4)) # 將數(shù)據(jù)重新轉(zhuǎn)為4列的二維數(shù)組
# print(data, data.shape) # 打印數(shù)據(jù)
df = pd.DataFrame(
data, columns=['timestamp', 'lon', 'lat', 'cog']
) # 將數(shù)據(jù)轉(zhuǎn)為DataFrame,設置列名
df.to_csv('data_recv.csv', index=False) # 將數(shù)據(jù)保存到csv文件中
- 常見問題:
-
Modbus Error: Exception code = 3
: 看看是不是接收的數(shù)據(jù)超出最大長度了 -
struct.error: unpack requires a buffer of xx bytes
: 如果在master.execute()
時設置了data_format
,注意data_format
必須與接收到的數(shù)據(jù)長度匹配!文章來源:http://www.zghlxwxcb.cn/news/detail-780546.html- 例如傳輸?shù)臄?shù)據(jù)為32位float,每個數(shù)據(jù)為4個字節(jié),收到24字節(jié)的數(shù)據(jù),那么收到了6個數(shù)據(jù),那么
data_format
必須為'6f'
- 例如傳輸?shù)臄?shù)據(jù)為32位float,每個數(shù)據(jù)為4個字節(jié),收到24字節(jié)的數(shù)據(jù),那么收到了6個數(shù)據(jù),那么
-
從站
- 從站負責接收主站的請求并返回數(shù)據(jù),
modbut_tk
庫已經(jīng)集成好了這些功能,所有從站的代碼非常簡單。 - 當我們給從站存入數(shù)據(jù)時,一個寄存器只有2個字節(jié),那么怎么存入4個字節(jié)的數(shù)據(jù)呢?
- 當量縮放:量程固定時不傳輸具體數(shù)據(jù),只傳輸百分比0-100%
- 用兩個寄存器存一個數(shù)據(jù):把32位浮點數(shù)分為兩部分(代碼中就是這種方法)
pi_bytes = [int(a_byte) for a_byte in struct.pack("f", num)]
pi_register1 = pi_bytes[0] * 256 + pi_bytes[1]
pi_register2 = pi_bytes[2] * 256 + pi_bytes[3]
registers_list.append(pi_register1)
registers_list.append(pi_register2)
- 上完整代碼!
import sys
import modbus_tk
import modbus_tk.defines as cst
from modbus_tk import modbus_tcp, hooks
import struct
import pandas as pd
'''
可使用的函數(shù):
創(chuàng)建從站: server.add_slave(slave_id)
slave_id(int):從站id
為從站添加存儲區(qū): slave.add_block(block_name, block_type, starting_address, size)
block_name(str):block名
block_type(int):block類型,COILS = 1,DISCRETE_INPUTS = 2,HOLDING_REGISTERS = 3,ANALOG_INPUTS = 4
starting_address(int):起始地址
size(int):block大小
設置block值:slave.set_values(block_name, address, values)
block_name(str):block名
address(int):開始修改的地址
values(a list or a tuple or a number):要修改的一個(a number)或多個(a list or a tuple)值
獲取block值:slave.get_values(block_name, address, size)
block_name(str):block名
address(int):開始獲取的地址
size(int):要獲取的值的數(shù)量
'''
# 創(chuàng)建從站總服務器
server = modbus_tcp.TcpServer(address='127.0.0.1') # address必須設置,port默認為502
print("running...")
print("enter 'quit' for closing the server")
server.start()
# 創(chuàng)建從站
slave_1 = server.add_slave(1) # slave_id = 1
# 為從站添加存儲區(qū)
slave_1.add_block(
'0', cst.HOLDING_REGISTERS, 0, 1200
) # block_name = '0', block_type = cst.HOLDING_REGISTERS, starting_address = 0, size = 1200
# 將數(shù)據(jù)存入寄存器
data = pd.read_csv('modbus_tcp.csv').values # 讀取數(shù)據(jù)data
num_array = data.flatten() # 將data壓為一維數(shù)組
registers_list = [] # 要存入寄存器的數(shù)據(jù)
# 將數(shù)據(jù)轉(zhuǎn)化為32位float格式,每個數(shù)據(jù)4個字節(jié) -> 占2個寄存器
for num in num_array:
pi_bytes = [int(a_byte) for a_byte in struct.pack("f", num)]
pi_register1 = pi_bytes[0] * 256 + pi_bytes[1]
pi_register2 = pi_bytes[2] * 256 + pi_bytes[3]
registers_list.append(pi_register1)
registers_list.append(pi_register2)
slave_1.set_values(
'0', 0, registers_list
) # 將數(shù)據(jù)存入寄存器, block_name = '0', address = 0, values = registers_list
while True:
cmd = sys.stdin.readline() # input
args = cmd.split(' ') # 按空格分割輸入
if cmd.find('quit') == 0: # 指令 quit -> 退出服務器
print('bye-bye')
break
- 可以看到,從站的代碼非常簡單!從站只需要一直開著(
while True
),當主站發(fā)送請求時,從站就會自動處理請求
hook函數(shù)
- 主站從站都可以設置多個hook函數(shù),實現(xiàn)自定義的功能
def on_before_connect(args):
'''
鉤子函數(shù),連接前輸出主站信息
Args:
args (tuple): (self(主站對象),)
'''
master = args[0]
print("host: {0},port: {1}".format(master._host, master._port))
hooks.install_hook("modbus_tcp.TcpMaster.before_connect", on_before_connect)
def on_after_recv(args):
'''
鉤子函數(shù),在收到報文后輸出報文總長度
Args:
args (tuple): (self(主站對象), response(從站返回的數(shù)據(jù)))
'''
response = args[1]
print("{0} bytes received".format(len(response)))
hooks.install_hook("modbus_tcp.TcpMaster.after_recv", on_after_recv)
- 設置hook函數(shù)分三步
- 定義函數(shù)
- 參數(shù)
args
是一個元組,是主站/從站在執(zhí)行完某個操作,如發(fā)送數(shù)據(jù),后使用hook函數(shù)時傳入的參數(shù),元祖中包含的參數(shù)不固定,可以自己查看源碼
- 參數(shù)
- 載入函數(shù):
hooks.install_hook('觸發(fā)hook函數(shù)的操作名',函數(shù)名)
- 愉快的使用!
- 定義函數(shù)
-
modbus_tk.hooks
中定義了哪些操作可以觸發(fā)hook
,具體請自行查看源碼!
有問題隨時留言!文章來源地址http://www.zghlxwxcb.cn/news/detail-780546.html
到了這里,關(guān)于python 基于modbus_tk庫實現(xiàn)modbusTCP 主站和從站[非常詳細]的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!