使用 Pyside 构建一个串口调试助手

开发环境搭建

1. 编辑工具 vscode 安装,vscode 下载网址。Linux 安装运行以下命令: 

sudo snap install --classic code

2.管理python环境 anaconda 工具 工具安装,Linux Anconda 安装脚本下载anconda地址(清华源),在末尾查找对应服务器的最新版本号。Linux 安装运行以下命令: 

 wget https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/Anaconda3-5.3.1-Linux-x86_64.sh

 安装 anaconda

 bash Anaconda3-5.3.1-Linux-x86_64.sh

使用 anconda 搭建python 的 虚拟运行环境

1, windows 环境下,运行在 "开始菜单下" --> "所有应用" --> "Anaconda3(64-bit)目录下"--> “Anaconda Powershell Prompt”,进入终端界面。

2.设置 Anconda Python pip 为国内的下载源,清华镜像源。

conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/pytorch/
conda config --set show_channel_urls yes
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple

3. 安装对应的python 版本,并创建对应的python 运行环境,我的python 版本 Python 3.11.8。

conda create -n env_name python=3.11  # 创建一个名为env_name,版本为3.11的python虚拟环境

4. 查看主机中存在的虚拟环境

conda env list

5. 激活虚拟环境conda activate env_name

6. 删除虚拟环境

conda remove --name <env_name> --all

 安装 Pyside6, pySerial 库

1. 激活创建的虚拟环境

conda activate env_name

2. 安装 PySide6, pySerial库

pip install PySide6

使用 Pyside6,pySerial 库练习搭建 串口调试助手

from PySide6.QtWidgets import *
from PySide6.QtCore import *
from PySide6.QtGui import *
import serial.tools.list_ports

baud_rates = [115200, 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, 9600, 19200, 38400, 57600]
parity_options = ["None", "Even", "Odd"]
data_bits_options = [8, 7, 6, 5]
stop_bits_options = [1, 1.5, 2]

class SerialThread(QThread):
    received_data = Signal(bytes)

    def __init__(self, serial):
        super().__init__()
        self.serial = serial
        self.running = True

    def run(self):
        while self.running and self.serial.isOpen():
            data = self.serial.read(1024)
            if data:
                self.received_data.emit(data)
               
    def stop(self):
        self.running = False

            
class SerialPort:
    def __init__(self, device, description, manufacturer, hwid):
        self.device = device
        self.description = description
        self.manufacturer = manufacturer
        self.hwid = hwid

class IndicatorLed(QAbstractButton):
    def __init__(self, parent=None):
        super(IndicatorLed, self).__init__(parent)
        self.initUI()

    def initUI(self):
        self.setMinimumSize(24, 24)
        self.setCheckable(True)  # 设置可选中
        self.colorOn = QColor(0, 240, 0)    # 绿色
        self.colorOff = QColor(240, 0, 0)   # 红色

    def mousePressEvent(self, event):
        pass  # 禁用鼠标点击事件

    def paintEvent(self, event):
        painter = QPainter(self)
        painter.setRenderHint(QPainter.Antialiasing, True)
        painter.setPen(Qt.NoPen)

        # 绘制外圈
        outerRadius = min(self.width(), self.height())
        outerRect = QRectF(0, 0, outerRadius, outerRadius)
        gradient = QRadialGradient(outerRect.center(), outerRadius, outerRect.center())
        gradient.setColorAt(0, Qt.white)
        gradient.setColorAt(1, self.colorOn if self.isChecked() else self.colorOff)
        painter.setBrush(gradient)
        painter.drawEllipse(outerRect)

        # 绘制内圈
        innerRadius = outerRadius * 0.6
        innerRect = QRectF((outerRadius - innerRadius) / 2, (outerRadius - innerRadius) / 2, innerRadius, innerRadius)
        painter.setBrush(self.colorOn if self.isChecked() else self.colorOff)
        painter.drawEllipse(innerRect)


class MainWindow(QMainWindow):
    received_data = Signal(bytes)

    def __init__(self):
        super().__init__()
        self.setWindowTitle("串口调试助手")

        # 定义成员变量用于存储用户选择的串口参数
        self.selected_port = "COM1"
        self.selected_baud_rate = 9600  # 默认波特率
        self.selected_parity = 'N'      # 默认奇偶校验位
        self.selected_data_bits = 8     # 默认数据位
        self.selected_stop_bits = 1     # 默认停止位

        # 创建串口对象
        self.serial = None
        self.serial_thread = None
        
        # 创建文本
        self.text_display = QTextEdit()
        self.text_display.setReadOnly(True)
        self.text_display.setStyleSheet("background-color: black; color: white;")  # 设置样式表

        # 创建文本输入框
        self.text_input = QTextEdit()
        self.text_input.setMinimumHeight(50)    # 设置最小高度
        self.text_input.setMaximumHeight(100)   # 设置最大高度

        # 创建发送和清除按钮
        send_button = QPushButton("发送")
        clear_button = QPushButton("清除")
        send_button.setMinimumHeight(45)        # 设置按钮高度
        clear_button.setMinimumHeight(45)       # 设置按钮高度

        # 创建下拉串口选择框
        self.serial_combo_box = QComboBox()
        self.buad_combo_box = QComboBox()
        self.parity_combo_box = QComboBox()
        self.data_bits_combo_box = QComboBox()
        self.stop_bits_combo_box = QComboBox()

        # 遍历波特率列表并添加到下拉选择框的选项中
        for baud_rate in baud_rates:
            self.buad_combo_box.addItem(str(baud_rate))

        # 添加奇偶校验位选项
        self.parity_combo_box.addItems(parity_options)

        # 添加数据位选项
        self.data_bits_combo_box.addItems([str(bits) for bits in data_bits_options])

        # 添加停止位选项
        self.stop_bits_combo_box.addItems([str(bits) for bits in stop_bits_options])

        # 创建下拉选择框水平布局
        combox_layout = QHBoxLayout()
        combox_layout.addWidget(send_button)
        combox_layout.addWidget(clear_button)

        # 创建按钮布局
        button_layout = QVBoxLayout()
        button_layout.addWidget(send_button)
        button_layout.addWidget(clear_button)

        # 创建文本输入框和按钮的水平布局
        input_layout = QHBoxLayout()
        input_layout.addWidget(self.text_input)
        input_layout.addLayout(button_layout)

        # 将文本显示框放入滚动区域
        scroll_area = QScrollArea()
        scroll_area.setWidgetResizable(True)
        scroll_area.setWidget(self.text_display)

        # 创建串口操作 水平布局
        serial_operation_layout = QHBoxLayout()
        serial_operation_label = QLabel("串口操作: ")
        self.led = IndicatorLed()
        self.led.setChecked(False)  # 设置为灭
        self.connect_button = QPushButton("Connect")

        serial_operation_layout.addWidget(serial_operation_label)
        serial_operation_layout.addWidget(self.connect_button)
        serial_operation_layout.addWidget(self.led)

        # 创建主布局
        layout = QHBoxLayout()

        # 创建左侧布局
        left_layout = QVBoxLayout()

        # 创建右侧布局
        right_layout = QVBoxLayout()

        # 创建串口选择框布局
        serial_layout = QHBoxLayout()
        serial_label = QLabel("串口选择:")
        serial_layout.addWidget(serial_label)
        serial_layout.addWidget(self.serial_combo_box)

        # 创建波特率选择框布局
        buad_layout = QHBoxLayout()
        buad_label = QLabel("波特率选择:")
        buad_layout.addWidget(buad_label)
        buad_layout.addWidget(self.buad_combo_box)

        # 创建奇偶校验位选择框布局
        parity_layout = QHBoxLayout()
        parity_label = QLabel("奇偶校验位:")
        parity_layout.addWidget(parity_label)
        parity_layout.addWidget(self.parity_combo_box)

        # 创建数据位选择框布局
        data_bits_layout = QHBoxLayout()
        data_bits_label = QLabel("数据位:")
        data_bits_layout.addWidget(data_bits_label)
        data_bits_layout.addWidget(self.data_bits_combo_box)

        # 创建停止位选择框布局
        stop_bits_layout = QHBoxLayout()
        stop_bits_label = QLabel("停止位:")
        stop_bits_layout.addWidget(stop_bits_label)
        stop_bits_layout.addWidget(self.stop_bits_combo_box)

        left_layout = QVBoxLayout()
        left_layout.setSpacing(10)  # 设置固定的垂直间距为10像素

        left_layout.addLayout(serial_layout)
        left_layout.addLayout(buad_layout)

        left_layout.addLayout(parity_layout)
        left_layout.addLayout(data_bits_layout)
        left_layout.addLayout(stop_bits_layout)
        left_layout.addLayout(serial_operation_layout)
        left_layout.addStretch(1)  # 添加一个伸缩项,占据剩余空间

        # 创建一个QFrame对象
        frame = QFrame()
        frame.setFrameShape(QFrame.Panel)  # 设置边框形状为Panel
        frame.setFrameShadow(QFrame.Raised)  # 设置边框阴影效果为Raised
        frame.setStyleSheet("border: 2px solid blue;")  # 设置边框样式为蓝色实线边框

        # 将QVBoxLayout添加到QFrame中
        frame.setLayout(left_layout)

        right_layout.addWidget(scroll_area)
        right_layout.addLayout(input_layout)

        layout.addWidget(frame)
        layout.addLayout(right_layout)

        # 创建主部件并设置布局
        main_widget = QWidget()
        main_widget.setLayout(layout)
        self.setCentralWidget(main_widget)

        # 连接发送按钮的clicked信号到槽函数
        send_button.clicked.connect(self.on_send_button_clicked)

        # 连接清除按钮的clicked信号到槽函数
        clear_button.clicked.connect(self.on_clear_button_clicked)

        # 串口连接操作按钮
        self.connect_button.clicked.connect(self.handleConnectButton)

        # 创建定时器
        self.timer = QTimer()
        self.timer.setInterval(1000)  # 设置定时器的时间间隔为1秒
        self.timer.timeout.connect(self.update_serial_ports)  # 连接定时器的timeout信号到更新串口列表的函数
        self.timer.start()  # 启动定时器

        # 更新串口列表和下拉选择框
        self.update_serial_ports()
         
    # 发送按钮点击时触发的槽函数
    def on_send_button_clicked(self):
        text = self.text_input.toPlainText()  # 获取文本输入框的内容

        # 获取当前时间并格式化为字符串
        current_time = QDateTime.currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz")

        # 添加时间前缀
        text_with_time = f"[{current_time}] {text}"

        # 设置文本颜色为微蓝色
        color = QColor(0, 0, 255)  # 微蓝色
        self.text_display.setTextColor(color)

        # 在文本显示框中添加带有时间前缀的文本
        self.text_display.append(text_with_time)

        # 将光标移动到文本末尾
        cursor = self.text_display.textCursor()
        cursor.movePosition(QTextCursor.End)
        self.text_display.setTextCursor(cursor)

    # 清除按钮点击时触发的槽函数
    def on_clear_button_clicked(self):
        self.text_display.clear()  # 清空文本显示框的内容

    # 更新串口列表和下拉选择框
    def update_serial_ports(self):
        # 获取电脑上所有的串口列表
        ports = serial.tools.list_ports.comports()

        # 获取当前选中的串口
        current_port = self.serial_combo_box.currentText()

        # 清空下拉选择框的选项
        self.serial_combo_box.clear()

        # 遍历串口列表并更新下拉选择框的选项
        for port in ports:
            device = port.device
            self.serial_combo_box.addItem(device)

        # 恢复之前选中的串口
        if current_port:
            index = self.serial_combo_box.findText(current_port)
            if index != -1:
                self.serial_combo_box.setCurrentIndex(index)

    def start_listening(self):
        if self.serial is not None and self.serial.isOpen():
            self.serial_thread = SerialThread(self.serial)
            self.serial_thread.received_data.connect(self.handle_received_data)
            self.serial_thread.start()

    def closeEvent(self, event):
        if self.serial_thread is not None and self.serial_thread.isRunning():
            self.serial_thread.stop()
            self.serial_thread.wait()
        event.accept()

    def handleConnectButton(self):
        self.selected_port = self.serial_combo_box.currentText()

        self.selected_baud_rate = int(self.buad_combo_box.currentText())

        pos = self.parity_combo_box.currentIndex()
        if pos == 0:
            self.selected_parity = 'N'  # 默认无校验位
        elif pos == 1:
            self.selected_parity = 'E'  # 默认奇校验位
        elif pos == 2:
            self.selected_parity = 'O'  # 默认奇校验位

        self.selected_data_bits = int(self.data_bits_combo_box.currentText())
        self.selected_stop_bits = float(self.stop_bits_combo_box.currentText())

        if self.connect_button.text() == "Connect":
            try:
                # 创建串口连接
                self.serial = serial.Serial(
                    port=self.selected_port,
                    baudrate=self.selected_baud_rate,
                    # parity=self.selected_parity,
                    # bytesize=self.selected_data_bits,
                    # stopbits=self.selected_stop_bits,
                    timeout=0.5,
                    # xonxoff=0,  # enable software flow control
                    # rtscts=0,  # enable RTS/CTS flow control
                )
                print(self.serial)

                # 启动监听线程
                self.start_listening()

                # 设置连接按钮和指示灯的状态
                self.connect_button.setText("Disconnect")
                self.led.setChecked(True)
            except serial.SerialException as e:
                self.serial.close()
                QMessageBox.warning(self, "警告", f"串口连接失败:{str(e)}")
        else:
            # 关闭串口连接
            self.serial.close()

            # 设置连接按钮和指示灯的状态
            self.connect_button.setText("Connect")
            self.led.setChecked(False)

    def handle_received_data(self, data):
        # 获取当前时间并格式化为字符串
        current_time = QDateTime.currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz")

        # 添加时间前缀
        text_with_time = f"[{current_time}] {data}"
        
        # 处理接收到的数据,例如显示在文本显示框中
        self.text_display.append(text_with_time.decode('utf-8'))


if __name__ == '__main__':
    # 创建应用程序并运行
    app = QApplication([])

    # 获取主屏幕
    primary_screen = app.primaryScreen()

    # 获取显示屏的大小
    screen_geometry = primary_screen.geometry()
    screen_width = screen_geometry.width()
    screen_height = screen_geometry.height()

    window = MainWindow()

    # 设置窗口的默认大小为宽度为2/3,高度为2/3
    window.resize(screen_width * 2 / 3, screen_height * 2 / 3)

    window.show()
    app.exec()

代码问题-待解决

1. 在接收带有颜色字节的串口数据时,颜色字节不能在文本框正常生效,会打印在文本框内看起来像乱码,如下:

[0;32m 表示设置文本颜色为绿色。

2.本文只是记录,练习时所遇到的问题。

  • 6
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值