pyqt之实现一个串口工具


import sys
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
                             QLabel, QComboBox, QPushButton, QTextEdit, QLineEdit, QSpinBox)
from PyQt5.QtCore import QIODevice, Qt, QTimer
from PyQt5.QtSerialPort import QSerialPort, QSerialPortInfo


class SerialTool(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("PyQt5 串口工具")
        self.resize(800, 600)

        # 串口对象
        self.serial = QSerialPort()

        # 创建UI
        self.init_ui()

        # 定时刷新可用串口
        self.timer = QTimer(self)
        self.timer.timeout.connect(self.refresh_ports)
        self.timer.start(1000)  # 1秒刷新一次

        # 连接信号槽
        self.connect_slots()

        # 初始化可用串口
        self.refresh_ports()

    def init_ui(self):
        # 主窗口布局
        main_widget = QWidget()
        main_layout = QVBoxLayout()
        main_widget.setLayout(main_layout)
        self.setCentralWidget(main_widget)

        # 串口配置区域
        config_layout = QHBoxLayout()

        # 串口选择
        self.port_label = QLabel("串口:")
        self.port_combo = QComboBox()

        # 波特率选择
        self.baud_label = QLabel("波特率:")
        self.baud_combo = QComboBox()
        self.baud_combo.addItems(["9600", "19200", "38400", "57600", "115200"])
        self.baud_combo.setCurrentText("115200")

        # 数据位
        self.data_bits_label = QLabel("数据位:")
        self.data_bits_combo = QComboBox()
        self.data_bits_combo.addItems(["5", "6", "7", "8"])
        self.data_bits_combo.setCurrentText("8")

        # 停止位
        self.stop_bits_label = QLabel("停止位:")
        self.stop_bits_combo = QComboBox()
        self.stop_bits_combo.addItems(["1", "1.5", "2"])

        # 校验位
        self.parity_label = QLabel("校验位:")
        self.parity_combo = QComboBox()
        self.parity_combo.addItems(["无", "奇校验", "偶校验", "标记", "空格"])

        # 打开/关闭按钮
        self.open_btn = QPushButton("打开串口")

        # 添加到配置布局
        config_layout.addWidget(self.port_label)
        config_layout.addWidget(self.port_combo)
        config_layout.addWidget(self.baud_label)
        config_layout.addWidget(self.baud_combo)
        config_layout.addWidget(self.data_bits_label)
        config_layout.addWidget(self.data_bits_combo)
        config_layout.addWidget(self.stop_bits_label)
        config_layout.addWidget(self.stop_bits_combo)
        config_layout.addWidget(self.parity_label)
        config_layout.addWidget(self.parity_combo)
        config_layout.addWidget(self.open_btn)

        # 数据收发区域
        data_layout = QHBoxLayout()

        # 接收区
        receive_group = QVBoxLayout()
        self.receive_label = QLabel("接收数据:")
        self.receive_text = QTextEdit()
        self.receive_text.setReadOnly(True)
        self.clear_receive_btn = QPushButton("清空接收")
        receive_group.addWidget(self.receive_label)
        receive_group.addWidget(self.receive_text)
        receive_group.addWidget(self.clear_receive_btn)

        # 发送区
        send_group = QVBoxLayout()
        self.send_label = QLabel("发送数据:")
        self.send_text = QTextEdit()
        self.send_btn = QPushButton("发送")
        self.clear_send_btn = QPushButton("清空发送")
        send_group.addWidget(self.send_label)
        send_group.addWidget(self.send_text)
        send_group.addWidget(self.send_btn)
        send_group.addWidget(self.clear_send_btn)

        data_layout.addLayout(receive_group, stretch=1)
        data_layout.addLayout(send_group, stretch=1)

        # 状态栏
        self.status_bar = self.statusBar()
        self.status_label = QLabel("就绪")
        self.status_bar.addWidget(self.status_label)

        # 添加到主布局
        main_layout.addLayout(config_layout)
        main_layout.addLayout(data_layout)

    def connect_slots(self):
        """连接信号槽"""
        self.open_btn.clicked.connect(self.toggle_serial)
        self.send_btn.clicked.connect(self.send_data)
        self.clear_receive_btn.clicked.connect(self.receive_text.clear)
        self.clear_send_btn.clicked.connect(self.send_text.clear)
        self.serial.readyRead.connect(self.read_data)

    def refresh_ports(self):
        """刷新可用串口列表"""
        current_port = self.port_combo.currentText()
        self.port_combo.clear()

        ports = QSerialPortInfo.availablePorts()
        for port in ports:
            self.port_combo.addItem(port.portName())

        # 保持之前选择的串口
        if current_port in [self.port_combo.itemText(i) for i in range(self.port_combo.count())]:
            self.port_combo.setCurrentText(current_port)

    def toggle_serial(self):
        """打开/关闭串口"""
        if self.serial.isOpen():
            self.close_serial()
        else:
            self.open_serial()

    def open_serial(self):
        """打开串口"""
        port_name = self.port_combo.currentText()
        if not port_name:
            self.status_label.setText("错误: 请选择串口")
            return

        # 配置串口参数
        self.serial.setPortName(port_name)
        self.serial.setBaudRate(int(self.baud_combo.currentText()))

        # 数据位
        data_bits = {
            "5": QSerialPort.Data5,
            "6": QSerialPort.Data6,
            "7": QSerialPort.Data7,
            "8": QSerialPort.Data8
        }
        self.serial.setDataBits(data_bits[self.data_bits_combo.currentText()])

        # 停止位
        stop_bits = {
            "1": QSerialPort.OneStop,
            "1.5": QSerialPort.OneAndHalfStop,
            "2": QSerialPort.TwoStop
        }
        self.serial.setStopBits(stop_bits[self.stop_bits_combo.currentText()])

        # 校验位
        parity = {
            "无": QSerialPort.NoParity,
            "奇校验": QSerialPort.OddParity,
            "偶校验": QSerialPort.EvenParity,
            "标记": QSerialPort.MarkParity,
            "空格": QSerialPort.SpaceParity
        }
        self.serial.setParity(parity[self.parity_combo.currentText()])

        # 打开串口
        if self.serial.open(QIODevice.ReadWrite):
            self.open_btn.setText("关闭串口")
            self.status_label.setText(f"已连接 {port_name}")
            self.port_combo.setEnabled(False)
            self.baud_combo.setEnabled(False)
            self.data_bits_combo.setEnabled(False)
            self.stop_bits_combo.setEnabled(False)
            self.parity_combo.setEnabled(False)
        else:
            self.status_label.setText(f"错误: 无法打开串口 {port_name}")

    def close_serial(self):
        """关闭串口"""
        if self.serial.isOpen():
            self.serial.close()
            self.open_btn.setText("打开串口")
            self.status_label.setText("串口已关闭")
            self.port_combo.setEnabled(True)
            self.baud_combo.setEnabled(True)
            self.data_bits_combo.setEnabled(True)
            self.stop_bits_combo.setEnabled(True)
            self.parity_combo.setEnabled(True)

    def read_data(self):
        """读取串口数据"""
        if self.serial.bytesAvailable():
            data = self.serial.readAll()
            try:
                text = data.data().decode('utf-8')
                self.receive_text.moveCursor(Qt.TextCursor.End)
                self.receive_text.insertPlainText(text)
            except UnicodeDecodeError:
                # 如果不是UTF-8文本,显示16进制
                hex_data = data.toHex().data().decode()
                self.receive_text.moveCursor(Qt.TextCursor.End)
                self.receive_text.insertPlainText(f"[HEX] {hex_data} ")

    def send_data(self):
        """发送数据"""
        if not self.serial.isOpen():
            self.status_label.setText("错误: 串口未打开")
            return

        text = self.send_text.toPlainText()
        if not text:
            return

        # 发送文本数据
        if self.serial.write(text.encode('utf-8')) == -1:
            self.status_label.setText("错误: 发送失败")
        else:
            self.status_label.setText("数据已发送")

    def closeEvent(self, event):
        """关闭窗口时确保串口关闭"""
        if self.serial.isOpen():
            self.serial.close()
        event.accept()


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = SerialTool()
    window.show()
    sys.exit(app.exec_())
posted @ 2025-08-17 17:44  我不是萧海哇~~~  阅读(20)  评论(0)    收藏  举报