当前科技似乎处于加速发展期,各个模块都在快速迭代,迭代的速度会让既有的一些经验产生问题,在用python实现modbus_tcp协议时,网上流传的一些代码中import语句会出现问题。导致pymodbus模块用起来很不好用。
这个原因出在一个子模块的命名上。
pymodbus为了区分同步和异步操作,使用了sync和async作为一整个子模块的分支节点名。然后因为asyn是python的关键字,然后呢,python发现了类似的问题,在3.7附近的某个版本,把这个问题改掉了。但是 pymodbus自己同样做了规避。它也改了,直接取消了这两个分支节点。将代码上移。然后既有的一些代码就都会在import阶段就会报错。
correct code after Dec12,2023
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
import struct
from pymodbus.client import ModbusTcpClient
# modbus_tcp 0x10 cmd
def gpModbusTcpWriteRegisters(SERVER_HOST, SERVER_PORT, base_addr, u16arrayInBigEndiant):
# Create a Modbus TCP client
client = ModbusTcpClient(SERVER_HOST, SERVER_PORT)
# Connect to the Modbus TCP server
client.connect()
# Write to a range of holding registers
slave_address = 1
response = client.write_registers(base_addr, u16arrayInBigEndiant, slave_address)
if response.isError():
print('Write error:', response)
else:
print('Write successful')
# Disconnect from the Modbus TCP server
client.close()
#sampleframe and comments
'''
frame of 0x10 write holding registers:
00 01 00 00 00 57 01 10 10 00 00 28 50 3F 99 99 9A BF 99 99 9A 3F 99 99 9A BF 99 99 9A 3F 99 99 9A BF 99 99 9A 3F 99 99 9A BF 99 99 9A 3F 99 99 9A BF 99 99 9A 3F 99 99 9A BF 99 99 9A 3F 99 99 9A BF 99 99 9A 3F 99 99 9A BF 99 99 9A 3F 99 99 9A BF 99 99 9A 3F 99 99 9A BF 99 99 9A
00 01:事务标识符(Transaction Identifier),用于标识通信的不同事务。
00 00:协议标识符(Protocol Identifier),用于指定使用的 Modbus 协议。
00 57:消息长度(Length),表示请求帧中除了事务标识符和协议标识符之外的字节数。即前六字节后的所有字节和。
01:单元标识符(Unit Identifier),用于指定 Modbus 设备的地址。
10:功能码(Function Code),表示该请求是写多个保持寄存器的请求。
10 00:起始地址(Starting Address),指定要写入的保持寄存器的起始地址。
00 28:寄存器数量(Quantity of Registers),指定要写入的连续保持寄存器的数量。
50:后续数据长度(Byte Count),表示紧随该字节之后的写入数据的字节数。(注意:0x50+0x07=0x57=消息长度(Length))
3F 99:写入数据,表示要写入的第一个保持寄存器的值。3F 99 99 9A = float(1.2)
99 9A:写入数据,表示要写入的第二个保持寄存器的值。
BF 99:写入数据,表示要写入的第三个保持寄存器的值。BF 99 99 9A = float(-1.2)
99 9A:写入数据,表示要写入的第四个保持寄存器的值。
....
注1:modbus_tcp无需校验,因为tcp协议会保证正确性。所以尾部没有追加的校验位。
注2:modbus_tcp没有加密和登陆机制,最简单的实现,可以使用向特定寄存器位置写入Magic Word实现,英文似乎称之为Kick off。
'''
上面是当前有效的命令0x10,区段寄存器写入的代码。其他依此类推。
Modbus协议遵从大端模式,如果自己整理寄存器数据,建议设置为大端模式,避免不必要的三方调试开销:
def floatArray2U16Array(float_array):
byte_array = bytes([b for f in float_array for b in bytearray(struct.pack('>f', f))])
u16_array = [int.from_bytes(byte_array[i:i+2], 'big', signed=False) for i in range(0, len(byte_array), 2)]
return u16_array
因为Modbus每个寄存器是16位,上面将floatArray转为能够供WriteRegisters使用的ushort数组的过程。其他的数据格式以此类推。