python 基于modbus_tk库实现modbusTCP 主站和从站
最近做了一个modbus tcp 传输浮点数的项目,参考了一些CSDN大佬的文章,这里做一个
整合和记录。
Modbus 数据传输的方式,可以简单地理解成打电话。并且是单向通信的打电话
主机发送数据,首先需要从机的电话号码(区分每个从机,每个地址必须唯一),告诉从机打电话要干什么事情,然后是需要发送的内容,最后再问问从机,我说的话你都听清楚了没有呀,没有听错吧?
然后从机这里,得到了主机打过来的电话,从机回复主机需要的内容,主机得到从机数据,这样就是一个主机到从机的通信过程
忘记从哪找的了hhh
从机存储数据 -> 存储区
Modbus 协议规定了 4 个存储区,分别是 0 1 3 4 区 其中 1 区和 4 区是可读可写,1 区和 3 区是只读
| 区号 | 名称 | 读写 | 地址范围 |
|---|---|---|---|
| 0 区 | 输出线圈 | 可读可写布尔量 | 00001-09999 |
| 1 区 | 输入线圈 | 只读布尔量 | 10001-19999 |
| 3 区 | 输入寄存器 | 只读寄存器 | 30001-39999 |
| 4 区 | 保持寄存器 | 可读可写寄存器 | 40001-49999 |
Modbus 给每个区都划分了地址范围,主机向从机获取数据时,只需要告诉从机数据的起始地址,还有获取多少字节的数据,从机就可以发送数据给主机
每一个从机,都有实际的物理存储,跟 modbus 的存储区相对应,主机读写从机的存储区,实际上就是对从机设备对应的实际存储空间进行读写
报文帧结构
| > | > | > | MBAP 报文头(7 bytes) | > | 协议数据单元(PDU) |
|---|---|---|---|---|---|
| 事务处理标识符 | 协议标识符 | 长度 | 单元标识符 | 功能码 | 数据 |
| 2 bytes | 2 bytes | 2 bytes | 1 byte | 1 byte | N bytes |
MBAP 报文头
| 域 | 长度 | 说明 | 客户机 | 服务器 |
|---|---|---|---|---|
| 事务处理标识符 | 2 字节 | Modbus 请求/响应事务处理的标识 | 客户机启动 | 复制响应 |
| 协议标识符 | 2 字节 | 0=Modbus 协议 | 客户机启动 | 复制响应 |
| 长度 | 2 字节 | 长度之后的字节总数 | 客户机启动 | 服务器启动 |
| 单元标识符 | 1 字节 | 串行链路或其它总线的从站识别 | 客户端启动 | 复制响应 |
事务处理标识符 and 协议标识符 正常使用设置为 0 即可,长度为 4 个字节 -> 0x00000000
功能码
| 功能码 | 功能说明 |
|---|---|
| 01H | 读取输出线圈 |
| 02H | 读取输入线圈 |
| 03H | 读取保持寄存器 |
| 04H | 读取输入寄存器 |
| 05H | 写入单线圈 |
| 06H | 写入单寄存器 |
| 0FH | 写入多线圈 |
| 10H | 写入多寄存器 |
每次可读数据最长长度(260 字节 - 9 字节 = 251 字节)
| > | > | 返回报文编码格式详解 |
|---|---|---|
| 字节位 | 结构 | 编码格式 |
| 前 6 个字节 | 事务/协议和数据长度 | ‘>HHH’ |
| 第 7 个字节 | 单元标识 | ‘>B’ |
| 第 8 个字节 | 功能码 | ‘>B’ |
| 第 9 个字节 | 数据长度 | ‘>B’ |
| 剩余字节 | 数据 | data_format(default or 自定义) |
| 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 |
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")
# 连接从站读取数据,一次最多读取125个寄存器,由于2个寄存器为一个数据,故 size 设置为124
data = [] # 存放读取的数据
data += master.execute( # 向从站发报文读取[0,123]区间的寄存器数据
1, # 从站标识符
cst.READ_HOLDING_REGISTERS, # 功能码
0, # 起始寄存器地址
124, # 读取的寄存器数量
data_format='62f', # 数据解码格式
)
data += master.execute( # 向从站发送报文读取[124,247]区间的寄存器数据
1, cst.READ_HOLDING_REGISTERS, 124, 124, data_format='62f'
)
# 将数据保存到csv文件中
if len(data) % 4 != 0: # 如果数据长度不是4的倍数,则用 0 补齐
for i in range(0, 4 - len(data) % 4):
data.append(0)
data = np.reshape(data, (-1, 4)) # 将数据重新转为4列的二维数组
# print(data, data.shape) # 打印数据
df = pd.DataFrame(
data, columns=['timestamp', 'lon', 'lat', 'cog']
) # 将数据转为DataFrame,设置列名
df.to_csv('data_recv.csv', index=False) # 将数据保存到csv文件中
Modbus Error: Exception code = 3: 看看是不是接收的数据超出最大长度了
struct.error: unpack requires a buffer of xx bytes: 如果在master.execute()时设置了data_format,注意data_format必须与接收到的数据长度匹配!
data_format必须为'6f'modbut_tk库已经集成好了这些功能,所有从站的代码非常简单。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
'''
可使用的函数:
创建从站: server.add_slave(slave_id)
slave_id(int):从站id
为从站添加存储区: 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):要获取的值的数量
'''
# 创建从站总服务器
server = modbus_tcp.TcpServer(address='127.0.0.1') # address必须设置,port默认为502
print("running...")
print("enter 'quit' for closing the server")
server.start()
# 创建从站
slave_1 = server.add_slave(1) # slave_id = 1
# 为从站添加存储区
slave_1.add_block(
'0', cst.HOLDING_REGISTERS, 0, 1200
) # block_name = '0', block_type = cst.HOLDING_REGISTERS, starting_address = 0, size = 1200
# 将数据存入寄存器
data = pd.read_csv('modbus_tcp.csv').values # 读取数据data
num_array = data.flatten() # 将data压为一维数组
registers_list = [] # 要存入寄存器的数据
# 将数据转化为32位float格式,每个数据4个字节 -> 占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
) # 将数据存入寄存器, 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),当主站发送请求时,从站就会自动处理请求def on_before_connect(args):
'''
钩子函数,连接前输出主站信息
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):
'''
钩子函数,在收到报文后输出报文总长度
Args:
args (tuple): (self(主站对象), response(从站返回的数据))
'''
response = args[1]
print("{0} bytes received".format(len(response)))
hooks.install_hook("modbus_tcp.TcpMaster.after_recv", on_after_recv)
args是一个元组,是主站/从站在执行完某个操作,如发送数据,后使用hook函数时传入的参数,元祖中包含的参数不固定,可以自己查看源码hooks.install_hook('触发hook函数的操作名',函数名)modbus_tk.hooks中定义了哪些操作可以触发hook,具体请自行查看源码!有问题随时留言!
关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。
我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden
这个问题在这里已经有了答案:关闭10年前。PossibleDuplicate:Pythonconditionalassignmentoperator对于这样一个简单的问题表示歉意,但是谷歌搜索||=并不是很有帮助;)Python中是否有与Ruby和Perl中的||=语句等效的语句?例如:foo="hey"foo||="what"#assignfooifit'sundefined#fooisstill"hey"bar||="yeah"#baris"yeah"另外,类似这样的东西的通用术语是什么?条件分配是我的第一个猜测,但Wikipediapage跟我想的不太一样。
什么是ruby的rack或python的Java的wsgi?还有一个路由库。 最佳答案 来自Python标准PEP333:Bycontrast,althoughJavahasjustasmanywebapplicationframeworksavailable,Java's"servlet"APImakesitpossibleforapplicationswrittenwithanyJavawebapplicationframeworktoruninanywebserverthatsupportstheservletAPI.ht
导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵
华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o
我想解析一个已经存在的.mid文件,改变它的乐器,例如从“acousticgrandpiano”到“violin”,然后将它保存回去或作为另一个.mid文件。根据我在文档中看到的内容,该乐器通过program_change或patch_change指令进行了更改,但我找不到任何在已经存在的MIDI文件中执行此操作的库.他们似乎都只支持从头开始创建的MIDI文件。 最佳答案 MIDIpackage会为您完成此操作,但具体方法取决于midi文件的原始内容。一个MIDI文件由一个或多个音轨组成,每个音轨是十六个channel中任何一个上的
C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.
本文主要介绍在使用Selenium进行自动化测试或者任务时,对于使用了iframe的页面,如何定位iframe中的元素文章目录场景描述解决方案具体代码场景描述当我们在使用Selenium进行自动化测试的时候,可能会遇到一些界面或者窗体是使用HTML的iframe标签进行承载的。对于iframe中的标签,如果直接查找是无法找到的,会抛出没有找到元素的异常。比如近在咫尺的例子就是,CSDN的登录窗体就是使用的iframe,大家可以尝试通过F12开发者模式查看到的tag_name,class_name,id或者xpath来定位中的页面元素,会抛出NoSuchElementException异常。解决
MIMO技术的优缺点优点通过下面三个增益来总体概括:阵列增益。阵列增益是指由于接收机通过对接收信号的相干合并而活得的平均SNR的提高。在发射机不知道信道信息的情况下,MIMO系统可以获得的阵列增益与接收天线数成正比复用增益。在采用空间复用方案的MIMO系统中,可以获得复用增益,即信道容量成倍增加。信道容量的增加与min(Nt,Nr)成正比分集增益。在采用空间分集方案的MIMO系统中,可以获得分集增益,即可靠性性能的改善。分集增益用独立衰落支路数来描述,即分集指数。在使用了空时编码的MIMO系统中,由于接收天线或发射天线之间的间距较远,可认为它们各自的大尺度衰落是相互独立的,因此分布式MIMO