【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()
相关推荐
幻云20104 小时前
Python深度学习:从筑基到登仙
前端·javascript·vue.js·人工智能·python
仰望星空@脚踏实地4 小时前
本地Python脚本是否存在命令注入风险
python·datakit·命令注入
LOnghas12115 小时前
果园环境中道路与树木结构检测的YOLO11-Faster语义分割方法
python
科技块儿6 小时前
IP定位技术:游戏反外挂体系中的精准识别引擎
数据库·tcp/ip·游戏
2501_944526427 小时前
Flutter for OpenHarmony 万能游戏库App实战 - 蜘蛛纸牌游戏实现
android·java·python·flutter·游戏
飞Link7 小时前
【Django】Django的静态文件相关配置与操作
后端·python·django
Ulyanov8 小时前
从桌面到云端:构建Web三维战场指挥系统
开发语言·前端·python·tkinter·pyvista·gui开发
CCPC不拿奖不改名9 小时前
两种完整的 Git 分支协作流程
大数据·人工智能·git·python·elasticsearch·搜索引擎·自然语言处理
上海云盾安全满满9 小时前
选择高防IP时需要重点关注哪些因素
网络·网络协议·tcp/ip
a努力。9 小时前
字节Java面试被问:TCP的BBR拥塞控制算法原理
java·开发语言·python·tcp/ip·elasticsearch·面试·职场和发展