Python Modbus 库完全指南:从入门到精通

무료무료 기술 자료

이 글은 바로 읽을 수 있으며 기초 학습과 검색 유입에 적합합니다.

Python Modbus 库完全指南:从入门到精通

Python Modbus库完全指南:从入门到精通

서론:为什么选择Python进行Modbus개발?

在工业物联网和自动化领域,Python凭借其简洁的语法、丰富的库生态系统和强大的社区支持,已经成为개발者的首选언어之一。对于Modbus프로토콜개발,Python提供了多个成熟的오픈소스库,能够满足从简单设备통신到复杂工业系统통합的各种需求。 本文将全面介绍Python中主流的Modbus库,包括pymodbus、minimalmodbus、uModbus等,通过实际코드 예제和最佳实践,帮助您快速掌握Python Modbus개발的核心技能。

一、主流Python Modbus库对比

1.1 pymodbus:功能最全面的选择

特点: - 支持Modbus TCP、RTU、ASCII等多种프로토콜 - 同时支持클라이언트(마스터)和服务器(슬레이브)모드 - 异步/同步两种编程모드 - 活跃的社区和持续更新 适用시나리오: - 复杂的工业控制系统 - 需要同时作为마스터和슬레이브的시나리오 - 高性能要求的적용

1.2 minimalmodbus:轻量级솔루션

特点: - 代码简洁,易于학습和使用 - 专注于Modbus RTU프로토콜 - 依赖少,部署简单 - 适合嵌入式系统和자료受限환경 适用시나리오: - 简单的设备통신 - 嵌入式系统和单板계산机 - 快速原型개발

1.3 uModbus:现代化设计

特点: - 现代化的API设计 - 支持异步编程 - 良好的유형提示 - 模块化架构 适用시나리오: - 需要异步处理的적용 - 大型项目개발 - 需要良好代码维护性的시나리오

二、pymodbus深度분석

2.1 安装和基础설정

# 安装pymodbus
pip install pymodbus

# 或者安装包含异步支持的버전
pip install pymodbus[asyncio]

2.2 Modbus TCP클라이언트예제

from pymodbus.client import ModbusTcpClient

# 创建TCP클라이언트
client = ModbusTcpClient(
    host='192.168.1.100',  # 设备IP주소
    port=502,              # Modbus TCP포트
    timeout=3,             # 超时时间(秒)
    retries=3              # 重试次数
)

try:
    # 연결设备
    if client.connect():
        print("成功연결到Modbus设备")

        # 유지 레지스터 읽기 (FC03)(주소0,数量10)
        result = client.read_holding_registers(주소=0, count=10, slave=1)
        if not result.is오답():
            print(f"읽기到的레지스터 값: {result.registers}")
        else:
            print(f"읽기失败: {result}")

        # 쓰기单个유지 레지스터(주소5,值1234)
        result = client.write_register(주소=5, value=1234, slave=1)
        if not result.is오답():
            print("레지스터쓰기成功")

        # 코일 읽기 (FC01)(주소0,数量8)
        result = client.read_coils(주소=0, count=8, slave=1)
        if not result.is오답():
            print(f"코일 상태: {result.bits}")

finally:
    # 닫기연결
    client.close()

2.3 Modbus RTU클라이언트예제

from pymodbus.client import ModbusSerialClient

# 创建RTU클라이언트
client = ModbusSerialClient(
    method='rtu',          # 프로토콜유형(rtu或ascii)
    port='/dev/ttyUSB0',   # 시리얼 포트设备
    baudrate=9600,         # 통신 속도
    bytesize=8,            # 데이터 비트
    parity='N',            # 패리티(N:无, E:偶, O:奇)
    stopbits=1,            # 정지 비트
    timeout=1              # 超时时间
)

try:
    if client.connect():
        print("成功연결到시리얼 포트设备")

        # 批量입력 레지스터 읽기 (FC04)
        result = client.read_input_registers(주소=0, count=20, slave=1)
        if not result.is오답():
            데이터 = result.registers
            # 处理데이터:假设前10个是온도值(需要除以10)
            temperatures = [value/10.0 for value in 데이터[:10]]
            print(f"온도데이터: {temperatures}")

finally:
    client.close()

2.4 异步클라이언트예제

import asyncio
from pymodbus.client import AsyncModbusTcpClient

async def read_device_데이터():
    # 创建异步클라이언트
    client = AsyncModbusTcpClient(
        host='192.168.1.100',
        port=502
    )

    await client.connect()

    try:
        # 同时읽기多个设备的데이터
        tasks = []
        for slave_id in [1, 2, 3]:
            task = client.read_holding_registers(
                주소=0, 
                count=10, 
                slave=slave_id
            )
            tasks.append(task)

        # 并行执行所有읽기任务
        results = await asyncio.gather(*tasks)

        for i, result in enumerate(results):
            if not result.is오답():
                print(f"设备{i+1}데이터: {result.registers}")

    finally:
        client.close()

# 运行异步函数
asyncio.run(read_device_데이터())

三、minimalmodbus快速上手

3.1 基础使用

import minimalmodbus
import serial

# 创建仪器对象
instrument = minimalmodbus.Instrument(
    port='/dev/ttyUSB0',   # 시리얼 포트设备
    slave주소=1,        # 슬레이브 주소
    mode='rtu'             # 프로토콜모드
)

# 설정시리얼 포트 매개변수
instrument.serial.baudrate = 9600
instrument.serial.bytesize = 8
instrument.serial.parity = serial.PARITY_NONE
instrument.serial.stopbits = 1
instrument.serial.timeout = 1.0  # 秒

# 유지 레지스터 읽기 (FC03)
try:
    # 읽기单个레지스터(주소30001)
    temperature = instrument.read_register(
        register주소=0,  # 레지스터주소(0对应30001)
        functioncode=3,     # 기능 코드3:读유지 레지스터
        signed=True         # 有符号整数
    )
    print(f"온도: {temperature}°C")

    # 읽기多个레지스터
    values = instrument.read_registers(
        register주소=0,
        number_of_registers=5,
        functioncode=3
    )
    print(f"多个레지스터 값: {values}")

    # 단일 레지스터 쓰기 (FC06)
    instrument.write_register(
        register주소=10,
        value=100,
        functioncode=6
    )

except Exception as e:
    print(f"통신错误: {e}")

3.2 高级功能

# 自定义바이트顺序(大端/小端)
instrument.byteorder = minimalmodbus.BYTEORDER_BIG

# 设置디버깅모드(显示原始통신데이터)
instrument.debug = True

# 使用不同的精度和缩放因子
def read_temperature_with_precision():
    # 假设온도레지스터存储的是实际值*10
    raw_value = instrument.read_register(0, 3)
    temperature = raw_value / 10.0
    return temperature

# 批量읽기优化
def batch_read_registers(주소es):
    """批量읽기多个레지스터,优化통신效率"""
    results = {}
    for addr in 주소es:
        try:
            value = instrument.read_register(addr, 3)
            results[addr] = value
        except Exception as e:
            results[addr] = f"错误: {e}"
    return results

四、uModbus现代化개발

4.1 异步服务器예제

import asyncio
from umodbus.server.tcp import RequestHandler, get_server
from umodbus.conf import Modbus데이터Type

# 定义데이터存储
데이터_store = {
    'coils': {0: False, 1: True, 2: False},
    'discrete_inputs': {0: True, 1: False},
    'holding_registers': {
        0: 100,
        1: 200,
        2: 300
    },
    'input_registers': {
        0: 400,
        1: 500
    }
}

async def handle_request(slave_id, function_code, 주소, 수량):
    """处理Modbus请求"""
    if function_code == 1:  # 读코일
        return [데이터_store['coils'].get(addr, False) 
                for addr in range(주소, 주소 + 수량)]

    elif function_code == 3:  # 读유지 레지스터
        return [데이터_store['holding_registers'].get(addr, 0)
                for addr in range(주소, 주소 + 수량)]

    elif function_code == 6:  # 写单个레지스터
        # 在实际적용中,这里会更新데이터存储
        return True

    return None

async def main():
    # 创建TCP服务器
    server = get_server(
        RequestHandler,
        데이터_store=데이터_store,
        request_handler=handle_request
    )

    # 启动服务器
    await server.serve_forever()

# 运行服务器
asyncio.run(main())

五、实际적용案例

5.1 工业온도监控系统

import time
from datetime import datetime
import json
from pymodbus.client import ModbusTcpClient

class TemperatureMonitor:
    def __init__(self, device_configs):
        """
        初始化온도监控系统
        device_configs: 设备설정列表
        """
        self.devices = []
        for config in device_configs:
            client = ModbusTcpClient(
                host=config['ip'],
                port=config.get('port', 502),
                timeout=config.get('timeout', 3)
            )
            self.devices.append({
                'client': client,
                'name': config['name'],
                'slave_id': config.get('slave_id', 1),
                'registers': config['registers']
            })

        self.데이터_history = []

    def read_all_temperatures(self):
        """읽기所有设备的온도데이터"""
        readings = []

        for device in self.devices:
            client = device['client']

            if not client.connect():
                print(f"无法연결到设备: {device['name']}")
                continue

            try:
                device_readings = {}
                for reg_name, reg_config in device['registers'].items():
                    result = client.read_holding_registers(
                        주소=reg_config['주소'],
                        count=reg_config.get('count', 1),
                        slave=device['slave_id']
                    )

                    if not result.is오답():
                        raw_value = result.registers[0]
                        # 적용转换公式(如除以10得到实际온도)
                        actual_value = raw_value / reg_config.get('scale', 1.0)
                        device_readings[reg_name] = {
                            'raw': raw_value,
                            'actual': actual_value,
                            'unit': reg_config.get('unit', '°C')
                        }

                readings.append({
                    'device': device['name'],
                    'timestamp': datetime.now().isoformat(),
                    'readings': device_readings
                })

            except Exception as e:
                print(f"읽기设备{device['name']}时出错: {e}")
            finally:
                client.close()

        # 저장到历史记录
        self.데이터_history.extend(readings)

        # 只保留最近1000条记录
        if len(self.데이터_history) > 1000:
            self.데이터_history = self.데이터_history[-1000:]

        return readings

    def check_alarms(self, readings):
        """检查报警条件"""
        alarms = []

        for device_reading in readings:
            for sensor_name, sensor_데이터 in device_reading['readings'].items():
                value = sensor_데이터['actual']

                # 简单报警逻辑
                if value > 80:  # 高温报警
                    alarms.append({
                        'device': device_reading['device'],
                        'sensor': sensor_name,
                        'value': value,
                        'type': 'high_temperature',
                        'message': f"온도过高: {value}°C",
                        'timestamp': device_reading['timestamp']
                    })
                elif value < 10:  # 低温报警
                    alarms.append({
                        'device': device_reading['device'],
                        'sensor': sensor_name,
                        'value': value,
                        'type': 'low_temperature',
                        'message': f"온도过低: {value}°C",
                        'timestamp': device_reading['timestamp']
                    })

        return alarms

    def export_데이터(self, filename='temperature_데이터.json'):
        """导出데이터到JSON文件"""
        with 열기(filename, 'w', encoding='utf-8') as f:
            json.dump({
                'history': self.데이터_history,
                'export_time': datetime.now().isoformat()
            }, f, ensure_ascii=False, indent=2)

        print(f"데이터已导出到: {filename}")

# 使用예제
if __name__ == "__main__":
    # 设备설정
    device_configs = [
        {
            'name': '锅炉1',
            'ip': '192.168.1.101',
            'registers': {
                'temperature': {'주소': 0, 'scale': 10.0, 'unit': '°C'},
                'pressure': {'주소': 1, 'scale': 100.0, 'unit': 'kPa'}
            }
        },
        {
            'name': '反应釜2',
            'ip': '192.168.1.102',
            'registers': {
                'temperature': {'주소': 0, 'scale': 10.0, 'unit': '°C'},
                'ph_value': {'주소': 1, 'scale': 100.0, 'unit': 'pH'}
            }
        }
    ]

    # 创建监控器
    monitor = TemperatureMonitor(device_configs)

    # 监控循环
    try:
        while True:
            print(f"\n[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 开始읽기데이터...")

            # 읽기데이터
            readings = monitor.read_all_temperatures()

            # 显示데이터
            for reading in readings:
                print(f"\n设备: {reading['device']}")
                for sensor, 데이터 in reading['readings'].items():
                    print(f"  {sensor}: {데이터['actual']} {데이터['unit']}")

            # 检查报警
            alarms = monitor.check_alarms(readings)
            if alarms:
                print("\n⚠️ 报警信息:")
                for alarm in alarms:
                    print(f"  {alarm['message']}")

            # 每5秒읽기一次
            time.sleep(5)

    except KeyboardInterrupt:
        print("\n监控정지")
        # 导出데이터
        monitor.export_데이터()

5.2 데이터可视化界面

```python import tkinter as tk from tkinter import ttk import threading import time from datetime import datetime from pymodbus.client import ModbusTcpClient class ModbusMonitorGUI: def init(self, root): self.root = root self.root.title("Modbus设备监控系统") self.root.geometry("800x600")
    # 设备설정
    self.devices = [
        {'name': 'PLC_1', 'ip': '192.168.1.100', 'port': 502, 'slave_id': 1},
        {'name': 'PLC_2', 'ip': '192.168.1.101', 'port': 502, 'slave_id': 2}
    ]

    # 데이터存储
    self.데이터 = {}
    for device in self.devices:
        self.데이터[device['name']] = {
            'registers': {},
            'last_update': None,
            'status': '离线'
        }

    # 创建界面
    self.setup_ui()

    # 启动监控线程
    self.monitoring = True
    self.monitor_thread = threading.Thread(target=self.monitor_devices)
    self.monitor_thread.daemon = True
    self.monitor_thread.시작()

def setup_ui(self):
    """设置用户界面"""
    # 创建主框架
    main_frame = ttk.Frame(self.root, padding="10")
    main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))

    # 设备状态区域
    status_frame = ttk.LabelFrame(main_frame, text="设备状态", padding="10")
    status_frame.grid(row=0, column=0, sticky=(tk.W, tk.E), pady=(0, 10))

    self.status_labels = {}
    for i, device in enumerate(self.devices):
        label = ttk.Label(status_frame, text=f"{device['name']}: 离线")
        label.grid(row=i, column=0, sticky=tk.W, padx=5, pady=2)
        self.status_labels[device['name']] = label

    # 데이터展示区域
    데이터_frame = ttk.LabelFrame(main_frame, text="实时데이터", padding="10")
    데이터_frame.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 10))

    # 创建Treeview显示데이터
    columns = ('设备', '레지스터', '주소', '值', '时间')
    self.tree = ttk.Treeview(데이터_frame, columns=columns, show='headings', height=15)

    for col in columns:
        self.tree.heading(col, text=col)
        self.tree.column(col, width=100)

    # 추가滚动条
    scrollbar = ttk.Scrollbar(데이터_frame, orient=tk.VERTICAL, command=self.tree.yview)
    self.tree.configure(yscrollcommand=scrollbar.set)

    self.tree.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
    scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S))

    # 控制버튼区域
    button_frame = ttk.Frame(main_frame)
    button_frame.grid(row=2, column=0, sticky=(tk.W, tk.E))

    ttk.Button(button_frame, text="새로고침데이터", command=self.manual_refresh).pack(side=tk.LEFT, padx=5)
    ttk.Button(button_frame, text="导出데이터", command=self.export_데이터).pack(side=tk.LEFT, padx=5)
    ttk.Button(button_frame, text="정지监控", command=self.stop_monitoring).pack(side=tk.LEFT, padx=5)

    # 설정网格权重
    main_frame.columnconfigure(0, weight=1)
    main_frame.rowconfigure(1, weight=1)
    데이터_frame.columnconfigure(0, weight=1)
    데이터_frame.rowconfigure(0, weight=1)

def read_device_데이터(self, device_config):
    """읽기单个设备的데이터"""
    client = ModbusTcpClient(
        host=device_config['ip'],
        port=device_config['port'],
        timeout=3
    )

    try:
        if client.connect():
            # 읽기레지스터0-19
            result = client.read_holding_registers(
                주소=0,
                count=20,
                slave=device_config['slave_id']
            )

            if not result.is오답():
                return {
                    'status': '在线',
                    '데이터': result.registers,
                    'timestamp': datetime.now()
                }
            else:
                return {
                    'status': '읽기错误',
                    '데이터': None,
                    'timestamp': datetime.now()
                }
        else:
            return {
                'status': '연결失败',
                '데이터': None,
                'timestamp': datetime.now()
            }

    except Exception as e:
        return {
            'status': f'异常: {str(e)}',
            '데이터': None,
            'timestamp': datetime.now()
        }
    finally:
        client.close()

def monitor_devices(self):
    """监控设备线程"""
    while self.monitoring:
        for device in self.devices:
            result = self.read_device_데이터(device)

            # 更新데이터存储
            self.데이터[device['name']].update({
                'registers': result['데이터'] or {},
                'last_update': result['timestamp'],
                'status': result['status']
            })

            # 更新UI(需要在主线程中执行)
            self.root.after(0, self.update_ui, device['name'], result)

        # 每2秒更新一次
        time.sleep(2)

def update_ui(self, device_name, result):
    """更新用户界面"""
    # 更新状态标签
    status_text = f"{device_name}: {result['status']}"
    if result['timestamp']:
        status_text += f" ({result['timestamp'].strftime('%H:%M:%S')})"

    self.status_labels[device_name].config(text=status_text)

    # 更新颜色
    if result['status'] == '在线':
        self.status_labels[device_name].config(foreground='green')
    elif '错误' in result['status'] or '失败' in result['status']:
        self.status_labels[device_name].config(foreground='red')
    else:
        self.status_labels[device_name].config(foreground='orange')

    # 更新데이터表格
    if result['데이터']:
        # 清除旧데이터
        for item in self.tree.get_children():
            if self.tree.item(item)['values'][0] == device_name:
                self.tree.delete(item)

        # 추가新데이터
        for i, value in enumerate(result['데이터']):
            self.tree.insert('', 'end', values=(
                device_name,
                f'400{i+1:02d}',
                i,
                value,
                result['timestamp'].strftime('%H:%M:%S') if result['timestamp'] else ''
            ))

def manual_refresh(self):
    """手动새로고침데이터"""
    for device in self.devices:
        result = self.read_device_데이터(device)
        self.update_ui(device['name'], result)

def export_데이터(self):
    """导出데이터到文件"""
    import json
    from datetime import datetime

    export_데이터 = {
        'export_time': datetime.now().isoformat(),
        'devices': {}
    }

    for device_name, device_데이터 in self.데이터.items():
        export_데이터['devices'][device_name] = {
            'status': device_데이터['status'],
            'last_update': device_데이터['last_update'].isoformat() if device_데이터['last_update'] else None,
            'registers': device_데이터['registers']
        }

    filename = f"modbus_데이터_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
    with 열기(filename, 'w', encoding='utf-8') as f:
        json.dump(export_데이터, f, ensure_ascii=False, indent=2)

    # 显示导出成功消息
    from tkinter import messagebox
    messagebox.showinfo("导出成功", f"데이터已导出到: {filename}")

def stop_monitoring(self):
    """정지监控"""
    self.monitoring = False
    self.root.quit()

运行GUI적용

if name == "main": root = tk.Tk() app = ModbusMonitorGUI(root) root.mainloop()
실제 프로젝트에 이 자료를 활용하시겠습니까?

도구 센터에서 프레임 분석, CRC 검증, 장비 디버깅을 진행하거나 요청을 제출하여 선정 및 연동 제안을 받아보세요.

엔지니어 회원

이 글을 실행 가능한 디버깅 자료로 활용하세요

가입하면 고급 프레임 분석, 자료 패키지 다운로드, 코드 예제, 엔지니어링 사례, 우선 기술 지원을 이용할 수 있어 실제 프로젝트 납품에 적합합니다.

고급 도구 무제한 이용
자료 패키지와 코드 패키지
완전한 엔지니어링 사례 라이브러리
우선 기술 지원 채널

답글 작성

이메일 주소는 공개되지 않습니다. 必填项已用 * 标注