【Flask-10】modbus tcp通信

Modbus TCP数据包由两部分组成:

  • MBAP头(Modbus Application Protocol Header) - 7字节

  • PDU(Protocol Data Unit) - 可变长度

完整数据帧结构:

事务标识符 \]\[ 协议标识符 \]\[ 长度字段 \]\[ 单元标识符 \]\[ 功能码 \]\[ 数据

2字节 2字节 2字节 1字节 1字节 N字节

逐字节解析:

字节位置 值(十六进制) 说明
0-1 00 01 事务标识符(Transaction ID):0x0001,用于匹配请求和响应
2-3 00 00 协议标识符(Protocol ID):0x0000,Modbus协议固定值
4-5 00 06 长度字段:0x0006 = 6,表示后面还有6个字节
6 01 单元标识符(Unit ID/设备地址):0x01 = 设备地址1
7 03 功能码(Function Code):0x03 = 读保持寄存器
8-9 01 90 起始地址:0x0190 = 十进制400(Modbus地址从0开始,对应40001)
10-11 00 0C 寄存器数量:0x000C = 十进制12

逐字节解析响应:

字节位置 值(十六进制) 说明
0-1 00 01 事务标识符:与请求匹配,0x0001
2-3 00 00 协议标识符:0x0000
4-5 00 19 长度字段:0x0019 = 25,后面有25个字节
6 01 单元标识符:设备地址1
7 03 功能码:0x03
8 18 字节计数:0x18 = 24字节(12个寄存器 × 2字节/寄存器)
9-32 00 00 ... 寄存器数据:每个寄存器2字节,共12个寄存器

代码解析:

功能码已包含在各方法中,不需要另外指定。

复制代码
# 读取保持寄存器 - 功能码 0x03,不需要指定function_code=0x03,方法名已包含
result = self.client.read_holding_registers(...)

# 读取输入寄存器 - 功能码 0x04
result = self.client.read_input_registers(...)

# 读取线圈状态 - 功能码 0x01
result = self.client.read_coils(...)

# 读取离散输入 - 功能码 0x02
result = self.client.read_discrete_inputs(...)

real_vol_int = (registers[0] << 16) | registers[1]
1、registers[0] << 16:将第一个寄存器的值左移16位
假设 registers[0] = 0x1234(16位)
左移16位后变成:0x12340000(32位)
2、registers[1]:与第二个寄存器值进行按位或操作
假设 registers[1] = 0x5678(16位)
0x12340000 | 0x5678 = 0x12345678
3、最终结果:0x12345678(32位整数)

完整示例代码:

复制代码
from pymodbus.client import ModbusTcpClient
import struct

class ModbusdeviceClient:
    def __init__(self, host='*.*.*.*', port=7890, device_address=1):
        self.host = host
        self.port = port
        self.device_address = device_address
        self.client = None
        self.connect()
    
    def connect(self):
        """连接Modbus设备"""
        try:
            self.client = ModbusTcpClient(self.host, port=self.port)
            if self.client.connect():
                print(f"Modbus连接成功 - 主机: {self.host}:{self.port}, 设备地址: {self.device_address}")
            else:
                print("Modbus连接失败")
        except Exception as e:
            print(f"Modbus连接错误: {e}")
  

    def read_device_data(self):
        try:
            # 使用read_holding_registers读取保持寄存器
            result = self.client.read_holding_registers(
                address=0x0190,  # 开始地址,使用偏移地址400
                count=12,         # 寄存器数量
                # unit=self.device_address  # 设备地址
                device_id = self.device_address
            )

            if not result.isError():
                registers = result.registers
                # 解析数据
                parsed_data = self._parse_device_data(registers)
                return parsed_data
            else:
                print(f"读取数据错误: {result}")
                return None
                
        except Exception as e:
            print(f"读取设备数据错误: {e}")
            # 尝试重新连接
            self.connect()
            return None
        
    def _parse_device_data(self, registers):
        """
        正确解析流量数据
        registers: 包含12个WORD值的列表
        """
        if len(registers) < 12:
            return None
        
        results = {}
        
        # 1. 实时流量(体积) - 寄存器0,1
        real_vol_int = (registers[0] << 16) | registers[1]
        results['instant_flow_volume'] = real_vol_int / 100.0 # m³/h
        
        。。。
        
        return results

    
    
    def build_request_message(self):
        print("\n请求报文结构分析:")
        print("00 01 - 事务标识符 (Transaction ID)")
        print("00 00 - 协议标识符 (Protocol ID)")
        print("00 06 - 长度 (Length)")
        print(f"{self.device_address:02X} - 单元标识符 (Unit ID/设备地址)")  # {:02X} 是 Python 的 格式化字符串
        print("03 - 功能码 (Function Code - 读保持寄存器)")
        print("9D D0 - 起始地址 (Start Address - 0x000A)")
        print("00 0C - 寄存器数量 (Number of Registers)")
        
        # 返回报文结构字典
        return {
            'transaction_id': 0x0001,
            'protocol_id': 0x0000,
            'length': 0x0006,
            'unit_id': self.device_address,
            'function_code': 0x03,
            'start_address': 0x9DD0,
            'register_count': 0x000C
        }
    
    
    def is_connected(self):
        """检查连接状态"""
        return self.client.is_socket_open() if self.client else False
    
    def close(self):
        """关闭连接"""
        if self.client:
            self.client.close()
            print("Modbus连接已关闭")


def main():
    print("=" * 70)
    print("Modbus设备2数据读取演示")
    print("=" * 70)
    
    # 创建Modbus客户端
    modbus_client = ModbusdeviceClient('*.*.*.*', 503, 1)
    
    if modbus_client.is_connected():
        print("\n" + "=" * 70)
        print("实际数据读取")
        print("=" * 70)
        
        # 实际读取设备数据
        device_data = modbus_client.read_device_data()
        
        if device_data:
            print("\n设备数据解析结果:")
            print(f"实时流量(体积): {device_data['instant_flow_volume']}m³/h")
        else:
            print("实际数据读取失败")

        
        # 关闭连接
        modbus_client.close()
    else:
        print("无法连接到Modbus设备")
     

if __name__ == "__main__":
    
    # 运行主演示
    main()
相关推荐
540_5402 小时前
ADVANCE Day31
人工智能·python·机器学习
The star"'2 小时前
Python
开发语言·python·pygame
luoluoal2 小时前
基于python的文件销毁工具(源码+文档)
python·mysql·django·毕业设计·源码
量化风云2 小时前
2026量化新基建(二) - sqlite 与 sqlite-utils
数据库·python·sqlite·量化交易·量化交易课程
yuhaiqun19892 小时前
发现前端性能瓶颈的巧妙方法:建立“现象归因→分析定位→优化验证”的闭环思维
前端·经验分享·笔记·python·学习·课程设计·学习方法
Python大数据分析@2 小时前
使用Dify搭建工作流,实现自动化商品采集分析
运维·python·自动化·网络爬虫
code tsunami2 小时前
如何将 Helium 与 CapSolver 集成,实现无缝 CAPTCHA 自动化解决
运维·数据库·人工智能·爬虫·python·自动化
玄同7652 小时前
Python 异常捕获与处理:从基础语法到工程化实践的万字深度指南
开发语言·人工智能·python·自然语言处理·正则表达式·nlp·知识图谱
quant_19862 小时前
BTC 行情预警系统实战教程
开发语言·后端·python·websocket·程序人生·金融