[pyqt5+pyserial]python实现modbus串口通信

本文通过PyQt5构建GUI界面,并结合pyserial库,详细介绍了如何使用Python实现Modbus串口通信。同时,利用QThread确保UI线程不被阻塞,保证了用户体验。Func.py文件中包含了具体的Modbus交互指令和CRC校验功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Test.py实现GUI界面和大部分逻辑 

# -*- coding: utf-8 -*-

from PyQt5.QtCore import QDateTime, Qt, QTimer, pyqtSignal, QThread
from PyQt5.QtWidgets import (QApplication, QCheckBox, QComboBox, QDateTimeEdit,
        QDial, QDialog, QGridLayout, QGroupBox, QHBoxLayout, QLabel, QLineEdit,
        QProgressBar, QPushButton, QRadioButton, QScrollBar, QSizePolicy,
        QSlider, QSpinBox, QStyleFactory, QTableWidget, QTabWidget, QTextEdit,
        QVBoxLayout, QWidget, QMessageBox)
from PyQt5.QtGui import (QIntValidator)
import serial
import serial.tools.list_ports
import crcmod.predefined
import Func
import time
import threading

class WidgetGallery(QDialog):

    # global dict1
    # dict1 = {'SUM': '0000'}
    sig = pyqtSignal(str, str, float, str, str, str)

    def __init__(self, parent=None):
        super(WidgetGallery, self).__init__(parent)
        self.originalPalette = QApplication.palette()

        self.comComboBox = QComboBox()
        # print(self.getPort())


        # self.comComboBox.currentIndexChanged.connect(self.selectionchange)
        # print(self.comComboBox.currentText())
        self.pasg = '1'  # 默认通道为1,不然初始化bug
        self.serFlag = False
        comLabel = QLabel("串口:")
        comLabel.setBuddy(self.comComboBox)


        self.bpsComboBox = QComboBox()
        self.bpsComboBox.addItems(self.getBps())

        bpsLabel = QLabel("波特率:")
        bpsLabel.setBuddy(self.bpsComboBox)

        self.crcComboBox = QComboBox()
        self.crcComboBox.addItems(self.getCrc())

        crcLabel = QLabel("校验:")
        crcLabel.setBuddy(self.crcComboBox)

        self.openButton = QPushButton("打开串口")
        self.openButton.setDefault(False)
        self.openButton.clicked.connect(self.openSer)
        self.openButton.setCheckable(True)
        # self.openButton.setAutoExclusive(True)
        # self.openButton.setEnabled(False)
        # print(self.getPort())
        self.comComboBox.addItems(self.getPort())


        # self.open1Button = QPushButton("go03")
        # self.open1Button.setDefault(False)
        # self.staNum = '01'
        # self.funCode = '03'
        # self.keyName = 'SUM'
        # self.open1Button.clicked.connect(self.sendFunction03(staNum, funCode, keyName))
        # self.open1Button.clicked.connect(self.Print)
        # self.open1Button.clicked.connect(self.sendFunction03)

        self.setWindowFlags(Qt.Widget)
        self.setWindowTitle("ST05D校准软件")
        self.resize(600, 400)
        self.setFixedSize(600, 400)
        self.changeStyle('windowsvista')

        topLayout = QHBoxLayout()
        topLayout.addWidget(comLabel)
        topLayout.addWidget(self.comComboBox)
        topLayout.addWidget(bpsLabel)
        topLayout.addWidget(self.bpsComboBox)
        topLayout.addWidget(crcLabel)
        topLayout.addWidget(self.crcComboBox)
        topLayout.addSpacing(50)
        topLayout.addWidget(self.openButton)
        # topLayout.addWidget(self.open1Button)
        topLayout.addStretch(1)

        self.createTopLeftGroupBox()
        self.createTopRightGroupBox()
        self.createMiddleLeftGroupBox()
        self.createMiddleRightGroupBox()
        self.createBottomLeftGroupBox()
        self.createBottomRightGroupBox()

        mainLayout = QGridLayout()
        mainLayout.addLayout(topLayout, 0, 0, 1, 2)
        mainLayout.addWidget(self.topLeftGroupBox, 1, 0)
        mainLayout.addWidget(self.topRightGroupBox, 1, 1)
        mainLayout.addWidget(self.middleLeftGroupBox, 2, 0)
        mainLayout.addWidget(self.middleRightGroupBox, 2, 1)
        mainLayout.addWidget(self.bottomLeftGroupBox, 3, 0)
        mainLayout.addWidget(self.bottomRightGroupBox, 3, 1)
        mainLayout.setColumnStretch(0, 1)
        mainLayout.setColumnStretch(1, 1)

        self.setLayout(mainLayout)


    # def Print(self):
    #     print('print')


    def closeEvent(self, event):
        # self.c.terminate()
        print('thread=sta', self.readthread._running)
        self.readthread.terminate()
        print('close')

    def openSer(self):
        print('open')
        print(self.openButton.isChecked())
        if self.openButton.isChecked():
            self.ser = serial.Serial(self.comComboBox.currentText(), self.bpsComboBox.currentText(), timeout=0)
            self.comComboBox.setEnabled(False)
            self.bpsComboBox.setEnabled(False)
            self.crcComboBox.setEnabled(False)
            self.openButton.setText('关闭串口')
        else:
            print('no')
            self.ser.close()
            self.openButton.setText('打开串口')
            self.comComboBox.setEnabled(True)
            self.bpsComboBox.setEnabled(True)
            self.crcComboBox.setEnabled(True)
        # if not self.serFlag:
        #     self.ser = serial.Serial(self.comComboBox.currentText(), self.bpsComboBox.currentText(), timeout=0)
        #     self.serFlag = True
        #     print(self.comComboBox.currentText(), self.bpsComboBox.currentText())
        # else:
        #     self.ser.close()
        #     self.serFlag = False
        #     self.openSer()

    # def sendFunction03(self, staNum, funCode, keyName):
    def send03(self, keyName):
        try:
            staNum = self.staNum
            # funCode = self.funCode
            # keyName = self.keyName
        except Exception:
            staNum = '00'
        # keyName = self.keyName
        funCode = '03'
        print('033')
        if self.openButton.isChecked():
            list1 = Func.sendFunction03(staNum, funCode, keyName)
            # list1 = []
            # dict1 = Func.addressDict03
            # list1.append(int('0x' + staNum, 16))
            # list1.append(int('0x' + funCode, 16))
            # list1.append(int('0x' + dict1[keyName][0:2], 16))
            # list1.append(int('0x' + dict1[keyName][2:4], 16))
            # list1.append(int('0x00', 16))
            # list1.append(int('0x02', 16))
            # crc16_func = crcmod.predefined.mkCrcFun('modbus')
            # # ba = bytearray([0o01, 0o03, 0o00, 0o00, 0o00, 0o02])
            # a = bytes().fromhex(staNum + funCode + dict1[keyName] + '0002')
            # # print(a)
            # reslist = hex(crc16_func(a))[2:].zfill(4)
            # # print(type(reslist[2:].upper()), reslist[:2].upper())
            # list1.append(int('0x' + reslist[2:], 16))
            # list1.append(int('0x' + reslist[:2], 16))
            print(list1)
            try:
                self.ser.write(list1)
            except Exception:
                print('not ok')
                reply = QMessageBox.warning(self,
                                            "串口设置错误",
                                            "请检查串口号及波特率设置",
                                            QMessageBox.Yes | QMessageBox.No)
                # self.echo(reply)
            else:
                print(type(self.ser.read()))
     
<think>首先,用户的问题是:“我想了解如何通过Python控制博图程序块实现程序控制请问使用Python控制博图程序块的方法和实现方式”。结合引用内容,我需要从引用中提取相关信息。回顾引用:-引用[1]:提到了使用Python编写程序通过pyqt5构建GUI测试串口通讯,并提供了一个博文链接。-引用[2]:讨论了使用Python为博图PLC写外部仿真器的想法。-引用[3]:详细描述了通过Python实现博图DB块变量导出的方法,包括从DB区读操作,处理不同的数据类型(如Real、Bool、Int、String)。用户的核心需求是通过Python控制博图程序块来实现程序控制。根据引用[3],焦点是博图的DB块(数据块)的操作,这可能指的是SiemensTIAPortal(博图)环境下的数据块控制。现在,我需要构建回答。系统级指令要求:-所有行内数学表达式用$...$。-独立公式用$$...$$。-LaTeX语法正确。-使用中文回答。-生成相关问题。-回答中引用的段落末尾添加引用标识,如[^1]。回答结构应清晰、逐步解决问题。内容要真实可靠,基于提供的引用。关键点从引用[3]:-Python可以通过操作DB块来控制博图程序。引用[3]展示了如何通过Python读取DB块的变量,包括Real、Bool、Int、String数据类型。-代码示例展示了从DB块读数据:通过解析偏移量和数据类型,调用相应的get函数。实现方式:-似乎是通过一些协议或库与博图PLC通信。引用[1]提到使用串口调试助手或Python测试通讯,可能是串行通讯或工业协议如Modbus。-引用[1]指向一个优化版的串口调试助手,支持自由协议和ModbusRTU。-引用[2]提到Python外部仿真器与博图PLC连接。所以,核心方法是:1.建立与博图PLC的通信通道,可能使用串口(如RS232/485)或以太网协议(如ModbusTCP)。2.通过Python脚本读取/写入PLC的DB块数据。3.从而实现对程序的控制,因为DB块包含程序的状态和数据。可能的实现步骤:-使用Python库如pySerial(用于串口通信)或专门针对SiemensPLC的库如snap7。-引用[3]显示自定义代码处理数据类型,说明可能没有使用现成库,而是直接基于协议实现。-控制程序块:在博图中,程序块可以是FC(功能块)、FB(功能块)、OB(组织块)等,但DB块是数据存储。控制程序可能通过修改数据来触发程序行为。在SiemensPLC中:-DB块存储数据,程序逻辑在OB、FC等块中。-通过修改DB块的值,可以影响程序的执行;例如,设置标志位来启用/禁用程序段。-要直接控制程序块(如启动/停止一个程序),可能需要访问PLC的系统函数或通过上位机接口。引用中没有直接提到控制程序块,只提到DB块变量导出和读操作。用户问题说的是“控制博图程序块”,程序块可能指逻辑块或整个程序。从引用:-引用[3]是“博图db块变量导出”,所以焦点在DB块操作。-结合用户上一次的问题:用户提到“参考站内引用”,但这是第一条消息中引用的内容。用户的历史上下文是本对话的开始,用户上次的问题是了解Python控制博图的方法。所以我应该基于引用[3],扩展如何通过Python控制DB块,进而影响程序。可能的实现方式:1.**通信基础**:使用Python通过串口或以太网连接到PLC。协议如Modbus(引用[1]提到)、S7communication(针对SiemensPLC)。-用户可参考引用[1]的博文来构建通讯。2.**读取数据**:如引用[3]所示,解析DB块结构,读取变量。3.**写入数据**:类似地,可以写入变量来改变PLC的行为。4.**控制程序**:通过改变关键变量(如启动按钮、模式选择)来实现逻辑控制。步骤概述:-**步骤1**:确保PLC通信设置正确,如启用通讯接口、配置协议。-**步骤2**:在Python中,使用库如pyModbusTCP或snap7来处理通讯。-**步骤3**:根据DB块定义,指定偏移量和数据类型进行操作。-**步骤4**:在脚本中实现控制逻辑。添加引用:我需要引用相关段落。-例如,从引用[3]:“通过for循环以此取出db_items中的字典,通过键判断数据类型和偏移量,之后调用对应的get方法”[^3]。-从引用[2]:可以考虑外部仿真器的想法[^2]。最终回答结构:1.**介绍方法**:简述Python控制博图DB块的方式。2.**步骤详解**:解释关键步骤。3.**代码示例**:基于引用[3],提供伪代码或简单代码。4.**注意事项**:如数据类型处理、通讯协议。5.**生成相关问题**:在最后。关于程序控制:既然用户提到“控制博图程序块”,我应说明通过控制数据来间接控制程序。中文回答。生成相关问题:系统要求生成相关问题。-例如:1.如何优化Python与博图PLC的通信性能?2.Python控制DB块的常见错误及解决方案?3.如何扩展实现写入操作?引用标识:例如,在描述时添加[^3]。</think> ### 通过Python控制博图程序块的方法和实现方式 要使用Python控制Siemens TIA Portal(博图)中的程序块(如DB块、FC块等)实现程序控制,核心是建立Python与PLC的通信通道,并通过读写PLC的数据块(DB块)来间接控制程序逻辑。博图的程序逻辑通常存储在DB块中,通过修改这些变量(如布尔型、整型等),可以实现对设备状态、模式切换或逻辑触发的控制。以下是基于提供引用的完整方法和实现步骤。整个过程基于工业通讯协议(如Modbus或S7通信协议),确保可靠性和实时性。 #### 1. **建立通信通道** - **基础要求**:博图PLC必须启用通讯接口(如串口RS232/RS485或以太网口),并配置协议(例如Modbus RTU/TCP)。上位机(如Windows PC)通过Python连接到PLC。 - **Python库选择**: - 串口通信:使用`pyserial`库(通用串口协议),适合测试阶段或简单系统。 - 协议实现:支持Modbus协议(使用`pyModbusTCP`或`pymodbus`库),或Siemens专有协议(使用`python-snap7`库)。 - **测试通讯**:先用串口调试助手(如引用[1]中提到的Python GUI工具)验证连接是否正常[^1]。确保PLC的IP地址、端口号或串口参数(波特率、数据位等)配置正确。 #### 2. **理解博图DB块结构** - **DB块变量导出**:DB块是数据存储块,包含程序控制所需的关键变量(如Bool、Int、Real)。Python需解析DB块的布局: - 每个变量有数据类别(如`Bool`、`Int`、`Real`、`String`)。 - 偏移量(offset)指示变量在内存中的位置,单位为字节。 - 例如,引用[3]展示了DB块数据导出的处理方式:通过遍历字典结构获取变量的偏移量和数据类型[^3]。 - 数据结构示例: - 变量条目:`{'datatype': 'Bool', 'bytebit': '10.0', ...}`(`10.0`表示偏移量10字节、位0)。 - **程序控制映射**:DB块变量通常关联程序逻辑,如: - 布尔型变量(`Bool`):控制启动/停止(如`start_command`)。 - 整型变量(`Int`):设置模式值(如`operation_mode = 1`)。 - 修改这些变量可触发PLC中的程序块(如FC或OB块),实现间接控制。 #### 3. **Python实现DB块读写操作** 从引用[3]出发,以下是关键步骤和代码示例: - **步骤1: 连接到PLC** 使用`snap7`库(支持S7协议)或`pyModbusTCP`库。以`snap7`为例(需安装`python-snap7`): ```python import snap7 # 创建客户端并连接到PLC plc = snap7.client.Client() plc.connect('192.168.0.1', 0, 1) # IP地址, 机架号, 槽号(根据PLC配置) ``` - **步骤2: 读取DB块数据** 引用[3]的代码展示了读取操作:遍历DB块条目,根据数据类型调用get函数[^3]。扩展实现: ```python def get_db_block(plc, db_number, start_offset, size): # 读取DB块数据:参数为DB块号、起始偏移量、数据大小(字节) data = plc.db_read(db_number, start_offset, size) return data def parse_db_items(data, db_items): # db_items:从博图导出的变量列表,包含各变量的偏移量和类型 results = {} for item in db_items: offset = int(item['bytebit'].split('.')[0]) # 提取偏移量(如'10.0'->10) if item['datatype'] == 'Bool': bit = int(item['bytebit'].split('.')[1]) # 位偏移 value = get_bool(data, offset, bit) elif item['datatype'] == 'Int': value = get_int(data, offset) elif item['datatype'] == 'Real': value = get_real(data, offset) elif item['datatype'] == 'String': value = get_string(data, offset) results[item['name']] = value # 存储变量名和值 return results # 辅助函数:处理不同数据类型 def get_bool(data, offset, bit): byte_value = data[offset] # 获取该字节数据 return bool(byte_value & (1 << bit)) # 通过位移判断位值 def get_int(data, offset): return int.from_bytes(data[offset:offset+2], byteorder='big') # Int通常2字节 ``` - **步骤3: 写入DB块数据以控制程序** 写入操作允许修改变量值,触发程序动作。例如,设置`start_command`为`True`启动PLC程序。 ```python def set_db_variable(plc, db_number, item, value): # 根据变量类型设置新值 offset = int(item['bytebit'].split('.')[0]) if item['datatype'] == 'Bool': data = plc.db_read(db_number, offset, 1) # 读取当前字节 bit = int(item['bytebit'].split('.')[1]) if value: new_byte = data[0] | (1 << bit) # 设置位为1 else: new_byte = data[0] & ~(1 << bit) # 设置位为0 plc.db_write(db_number, offset, bytes([new_byte])) elif item['datatype'] == 'Int': # 转换为字节并写入 value_bytes = value.to_bytes(2, byteorder='big') plc.db_write(db_number, offset, value_bytes) ``` - **步骤4: 控制程序逻辑** 结合读写操作,创建控制脚本: - 示例:启动一个设备程序块: ```python db_items = [{'name': 'start_command', 'datatype': 'Bool', 'bytebit': '0.0'}] # 假启动变量在DB1的偏移0.0 plc.connect('192.168.0.1', 0, 1) set_db_variable(plc, 1, db_items[0], True) # 写入True启动程序 ``` #### 4. **注意事项和优化** - **数据类型对齐**:确保偏移量和数据类型在Python中正确匹配博图定义(如`Real`为4字节浮点数,`String`需处理长度),避免通信错误。 - **错误处理**:添加异常捕获(如`try-except`),处理PLC连接中断或数据无效问题。 - **性能优化**: - 批量读写以减少通讯次数,提高实时性。 - 参考引用[2]的“外部仿真器”思路,用Python构建数字孪生模型测试控制逻辑,减少真实环境风险[^2]。 - **安全考虑**:在工业环境中,确保写操作权限受控,避免误操作导致设备故障。 #### 5. **完整示例参考** - 结合引用[1],可以使用PyQt5构建GUI界面,集成上述控制逻辑[^1]。 - 示例代码模板: ```python import snap7 from pyModbusTCP.client import ModbusClient # 或使用Modbus库 # ...连接和读写函数(同上)... # GUI部分(可选,引用PyQt5) ``` - 更多细节可参考引用链接:[引用[1]博文](^1) 和 [引用[3]代码](^3)。 通过以上方法,Python可以实现高效的PLC程序控制,应用于工业自动化场景如设备启停、参数调整或模式切换[^3][^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值