当前位置:   article > 正文

modbus_tcp的实现 through python._python modbus tcp server

python modbus tcp server

0.引言

当前科技似乎处于加速发展期,各个模块都在快速迭代,迭代的速度会让既有的一些经验产生问题,在用python实现modbus_tcp协议时,网上流传的一些代码中import语句会出现问题。导致pymodbus模块用起来很不好用。

这个原因出在一个子模块的命名上。

pymodbus为了区分同步和异步操作,使用了sync和async作为一整个子模块的分支节点名。然后因为asyn是python的关键字,然后呢,python发现了类似的问题,在3.7附近的某个版本,把这个问题改掉了。但是 pymodbus自己同样做了规避。它也改了,直接取消了这两个分支节点。将代码上移。然后既有的一些代码就都会在import阶段就会报错。

用于物联网的数据传输的这些协议,是协议设计的范本。建议通读相关的原始协议细节。吃透。

1. 示例:写寄存器区域0x10指令的实现

correct code after Dec12,2023

  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. import sys
  4. import struct
  5. from pymodbus.client import ModbusTcpClient
  6. # modbus_tcp 0x10 cmd
  7. def gpModbusTcpWriteRegisters(SERVER_HOST, SERVER_PORT, base_addr, u16arrayInBigEndiant):
  8. # Create a Modbus TCP client
  9. client = ModbusTcpClient(SERVER_HOST, SERVER_PORT)
  10. # Connect to the Modbus TCP server
  11. client.connect()
  12. # Write to a range of holding registers
  13. slave_address = 1
  14. response = client.write_registers(base_addr, u16arrayInBigEndiant, slave_address)
  15. if response.isError():
  16. print('Write error:', response)
  17. else:
  18. print('Write successful')
  19. # Disconnect from the Modbus TCP server
  20. client.close()
  21. #sample frame and comments
  22. '''
  23. frame of 0x10 write holding registers:(write to 0x1000, 40 registers = 80Bytes)
  24. 去:
  25. 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
  26. 回:
  27. 00 01 00 00 00 06 01 10 10 00 00 28
  28. ~~~~~ 06字节(这是协议载荷)
  29. ~~~~~ 0x28=40个寄存器(这是协议载荷)
  30. 去帧注释:
  31. 00 01:事务标识符(Transaction Identifier),用于标识通信的不同事务。
  32. 00 00:协议标识符(Protocol Identifier),用于指定使用的 Modbus 协议。
  33. 00 57:消息长度(Length),表示请求帧中除了事务标识符和协议标识符之外的字节数。即前六字节后的所有字节和。
  34. 01:单元标识符(Unit Identifier),用于指定 Modbus 设备的地址。
  35. 10:功能码(Function Code),表示该请求是写多个保持寄存器的请求。
  36. 10 00:起始地址(Starting Address),指定要写入的保持寄存器的起始地址。
  37. 00 28:寄存器数量(Quantity of Registers),指定要写入的连续保持寄存器的数量。
  38. 50:后续数据长度(Byte Count),表示紧随该字节之后的写入数据的字节数。(注意:0x50+0x07=0x57=消息长度(Length))
  39. 3F 99:写入数据,表示要写入的第一个保持寄存器的值。3F 99 99 9A = float(1.2)
  40. 99 9A:写入数据,表示要写入的第二个保持寄存器的值。
  41. BF 99:写入数据,表示要写入的第三个保持寄存器的值。BF 99 99 9A = float(-1.2)
  42. 99 9A:写入数据,表示要写入的第四个保持寄存器的值。
  43. ....
  44. 注1:modbus_tcp无需校验,因为tcp协议会保证正确性。所以尾部没有追加的校验位。
  45. 注2:modbus_tcp没有加密和登陆机制,最简单的实现,可以使用向特定寄存器位置写入Magic Word实现,英文似乎称之为Kick off。
  46. 注3:计数器:
  47. (byte.offset04) 00 57 是字节。(它是帧载荷)
  48. (byte.offset10) 00 28 是寄存器。(这是协议载荷)
  49. (byte.offset12) 50 是字节。(这是帧载荷)
  50. '''

上面是当前有效的命令0x10,区段寄存器写入的代码。其他依此类推。

Modbus协议遵从大端模式,如果自己整理寄存器数据,建议设置为大端模式,避免不必要的三方调试开销:

  1. def floatArray2U16Array(float_array):
  2. byte_array = bytes([b for f in float_array for b in bytearray(struct.pack('>f', f))])
  3. u16_array = [int.from_bytes(byte_array[i:i+2], 'big', signed=False) for i in range(0, len(byte_array), 2)]
  4. return u16_array
'
运行

因为Modbus每个寄存器是16位,上面将floatArray转为能够供WriteRegisters使用的ushort数组的过程。其他的数据格式以此类推。

2.0x03 读寄存器区域(写寄存器区域0x10的逆操作)

frame of 0x03帧 及其回应帧
去:00 01 00 00 00 06 01 03 00 00 00 14 
注3:去帧的的计数器 
     (byte.offset04) 00 06 是字节。(它是帧载荷)
     (byte.offset10) 00 14 是寄存器数量: = 20(0x0014 = 20)个寄存器,(这是协议载荷)

回:00 01 00 00 00 2B 01 03 28 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
注4:回帧的计数器都是字节:
      (byte.offset04)00 2B 是字节;(它是帧载荷)
      (byte.offset08)28 也是字节,(它是帧载荷)

附录A 故障纠错

Modbus调试过程中最容易出现问题有这么几个:

  • 各种计数值的单位,是字节还是寄存器数量。
  • 是否加校验位。
  • 设备id.

A1可直接用于调试的测帧

 A1.1 图例和数据识读

00 57,这是字节尺寸,同一个帧的字节尺寸要彼此匹配。

10 00,这是寄存器地址,可能涉及回应帧之间的匹配。

00 28,这是寄存器数量,可能涉及回应帧字节尺寸和寄存器数量之间的匹配;当前帧字节尺寸的匹配。

  • 字节顺序Offset 04的两个字节,= 整个数据帧长度-6.即帧头6字节以后的所有字节数量。它只和当前待发送的数据帧尺寸有关。
  • 在命令帧之后,真正的寄存器数据之前,还有一个字节长度。这个长度仅仅与数据区段的字节长度有关。仅仅与当前帧有关。

A1.2 0x10 区段寄存器写

  1. 去:
    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 
  2. 回:
    00 01 00 00 00 06 01 10 10 00 00 28 

A1.3 0x03 区段寄存器读

  1. 去:
    00 01 00 00 00 06 01 03 00 00 00 14
  2. 回:
    00 01 00 00 00 2B 01 03 28 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 

附录B 一个测试用的modbus_tcp服务器

pymodbus目前似乎在尝试把服务器功能再次封装。而不是通过直接封装在pymodbus里的StartAsyncTcpServer来实现。当前的doc甚至把这些函数的传入参数隐藏了起来。

注意:pyModbusTCP是一个独立的组件。

  1. #!/usr/bin/env python3
  2. """
  3. Modbus/TCP server
  4. ~~~~~~~~~~~~~~~~~
  5. Run this as root to listen on TCP privileged ports (<= 1024).
  6. Add "--host 0.0.0.0" to listen on all available IPv4 addresses of the host.
  7. $ sudo ./server.py --host 0.0.0.0
  8. """
  9. import argparse
  10. import logging
  11. from pyModbusTCP.server import ModbusServer
  12. # init logging
  13. logging.basicConfig()
  14. # parse args
  15. parser = argparse.ArgumentParser()
  16. parser.add_argument('-H', '--host', type=str, default='localhost', help='Host (default: localhost)')
  17. parser.add_argument('-p', '--port', type=int, default=502, help='TCP port (default: 502)')
  18. parser.add_argument('-d', '--debug', action='store_true', help='set debug mode')
  19. args = parser.parse_args()
  20. # logging setup
  21. if args.debug:
  22. logging.getLogger('pyModbusTCP.server').setLevel(logging.DEBUG)
  23. # start modbus server
  24. server = ModbusServer(host=args.host, port=args.port)
  25. server.start()

附录C python相关的组件安装指令:

这里有一个用于组件升级的语法,还有一个用于组件安装的语法。国内访问国外原始的python组件库速度会太慢。这里实际引用的是国内镜像站。另外,使用python打头的语法,可以控制当前设备上具体的python示例(你的机器上可能安装有多个Python版本),你可以把最前面的python替换为某个特定目录下的python.exe的全路径。

python -m pip install --upgrade pip --timeout 200 -i http://mirrors.aliyun.com/pypi/simple/  --trusted-host mirrors.aliyun.com


python -m pip install pymodbus  --timeout 200 -i http://mirrors.aliyun.com/pypi/simple/  --trusted-host mirrors.aliyun.com

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Guff_9hys/article/detail/991522
推荐阅读
相关标签
  

闽ICP备14008679号