「Python」结合PyQt5和pyserial实现串口助手

2023-10-16 04:59

本文主要是介绍「Python」结合PyQt5和pyserial实现串口助手,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

「Python」结合PyQt5和PySerial实现串口助手

一、概要

1.主要模块介绍

PyQt5

PyQt5是一个用于创建 GUI应用程序的跨平台的工具包,它将 Python编程语言和Qt库成功融合在一起(Qt库是目前最强大的GUI库之一)。PyQt5可以运行在所有主流的操作系统上,包括UNIX、Windows和Mac OS。
特点:

  • 基于高性能的Qt的GUI控件集。
  • 能够跨平台运行在Windows、Linux和Mac OS等系统上。
  • 使用信号/槽(signal/slot)机制进行通信。
  • 对Qt库的完全封装。
  • 可以使用Qt成熟的IDE(如Qt Designer)进行图形界面设计,并自动生成可执行的Python代码。
  • 提供了一整套种类繁多的窗口控件。

pyqt5的类别分为几个模块,包括以下:
将会详细列出本案例所用到的模块和组件,下同

  • QtCore(包含了核心的非GUI功能。此模块用于处理时间、文件和目录、各种数据类型、流、URL、MIME类型、线程或进程。)
    • QDateTime(日期操作)、QTimer(定时器)、QRegExp(正则表达式类)
  • QtGui(包含类窗口系统集成、事件处理、二维图形、基本成像、字体和文本)
    • QIcon(图像)、QRegExpValidator(内置正则表达式检验器)、QColor(颜色)、QTextCursor(操作指针)
  • QtWidgets(包含创造经典桌面风格的用户界面提供了一套UI元素的类)
    • QWidget(基础窗口控件)、QApplication(控制流和主要设置)、QDesktopWidget(提供屏幕信息的访问)、QMessageBox(弹出式对话框)、QFileDialog(文件目录对话框)
    • QGroupBox(组合框)、QGridLayout(表格布局)、QVBoxLayout(垂直布局)、QHBoxLayout(水平布局)、QFormLayout(表单布局)
    • QLabel(标签)、QPushButton(按钮)、QComboBox(下拉列表)、QCheckBox(复选框)、QTextEdit(多行文本框)、QLineEdit(单行文本框)、QTextBrowser(富文本浏览器)
  • QtMultimedia、QtBluetooth、QtNetwork、QtPositioning、Enginio、QtWebSockets、QtWebKit、QtWebKitWidgets、QtXml、QtSvg、QtSql、QtTest

一些组件的基本用法
QLabel(标签)

方法名用途
setText()设置QLabel的文本内容
text()获得QLabel的文本内容

QPushButton(按钮)

方法名用途
setIcon()设置按钮上的图标
setEnabled()设置按钮是否可用 False不可用 点击不会发射信号
setText()设置按钮的显示文本
text()返回按钮的显示文本

QComboBox(下拉列表)

方法名用途
addItem()添加一个下拉选项
addItems()从列表中添加下拉选项
clear()删除下拉列表中所有的选项
currentText()返回选中选项的文本
currentIndex()返回选中项的索引
setCurrentText()设置当前选择的文本

QCheckBox(复选框)

方法用途
setChecked()设置复选框的状态,设置为True表示选中,False表示取消选中的复选框
setText()设置复选框的显示文本
text()返回复选框的显示文本
isChecked()检查复选框是否被选中

QLineEdit(单行文本框)

方法名用途
setText()设置文本框的文本内容
text()获得文本框的文本内容
clear()清除文本框内容

QTextEdit(多行文本框)

方法名用途
setPlainText()设置多行文本框的文本内容
toPlainText()返回文本框的文本内容
clear()清除文本框内容

QTextBrowser(富文本浏览器)

方法名用途
append()追加文本,添加新行
insertPlainText()添加什么就显示什么

QMessageBox(弹出式对话框)

方法名用途
information(QWidget parent, title, text, buttons, defaultButton)弹出消息对话框:QWidget parent指定的父窗口控件;title对话框标题;text度花开文本;buttons多个标准按钮,默认为OK按钮;defaultButton默认选中的标准按钮,默认是第一个标准按钮
question(QWidget parent, title, text, buttons, defaultButton)弹出问答对话框
warning(QWidget parent, title, text, buttons, defaultButton)弹出警告对话框
critical(QWidget parent, title, text, buttons, defaultButton)弹出严重错误对话框
about(QWidget parent, title, text, buttons, defaultButton)弹出关于对话框
setTitle()设置标题
setText()
setIcon()设置弹出对话框的图片

QTimer(定时器)

方法名用途
start(milliseconds)启动或重新启动定时器,时间间隔为毫秒,如果定时器已经运行,他将停止并重新启动,如果singleSlot信号为真,定时器仅被激活一次
stop()停止定时器

QFileDialog(文件目录对话框)

方法名用途
getOpenFileName()返回用户所选择文件的名称,并打开该文件
getSaveFileName()使用用户选择的文件名保存文件
setFileMode()可以选择的文件类型,枚举常量是:
QFileDialog.AnyFile:任何文件
QFileDialog.ExistingFile:已存在的文件
QFileDialog.Directory:文件目录
QFileDialog.ExistingFiles:已经存在的多个文件
setFilter()设置过滤器,只显示过滤器允许的文件类型

信号与槽
PyQt5有一个独一无二的信号和槽机制来处理事件。信号和槽用于对象之间的通信。当指定事件发生,一个事件信号会被发射。槽可以被任何Python脚本调用。当和槽连接的信号被发射时,槽会被调用。
PyQt中所有继承自QWidget的控件(这些都是QObject的子对象)都支持信号与槽机制。当信号发射时,连接的槽函数将会自动执行。在PyQt 5中信号与槽通过object.signal.connect()方法连接。
PyQt的窗口控件类中有很多内置信号,开发者也可以添加自定义信号。信号与槽具有如下特点。

  • 一个信号可以连接多个槽。
  • 一个信号可以连接另一个信号。
  • 信号参数可以是任何Python类型。
  • 一个槽可以监听多个信号。
  • 信号与槽的连接方式可以是同步连接,也可以是异步连接。
  • 信号与槽的连接可能会跨线程。
  • 信号可能会断开。

PySerial

PySerial模块封装了对串口的访问。
特性:

  • 为多平台提供了统一的接口。
  • 通过python属性访问串口设置。
  • 支持不同的字节大小、停止位、校验位和流控设置。
  • 可以有或者没有接收超时。
  • 类似文件的API,例如read和write,也支持readline等。

初始化及对象常用方法

ser = serial.Serial("com1",9600,timeout=0.5)   winsows系统使用com1口连接串行口
ser.isOpen()   查看端口是否被打开。
ser.open()   打开端口‘。
ser.close()   关闭端口。
ser.read()   从端口读字节数据。默认1个字节。
ser.read_all()   从端口接收全部数据。
ser.write("hello")   向端口写数据。
ser.readline()   读一行数据。
ser.readlines()   读多行数据。
in_waiting()   返回接收缓存中的字节数。

configparser

ConfigParser是python中一个用来读取配置文件的模块。该模块适用于配置文件的格式与windows ini文件类似,可以包含一个或多个节(section),每个节可以有多个参数(键=值)。
配置文件一般格式:

[db]
db_host = 127.0.0.1
db_port = 69
db_user = root
db_pass = root
host_port = 69[concurrent]
thread = 10
processor = 20括号“[ ]”内包含的为section。紧接着section 为类似于key-value的options的配置内容。

配置文件的一般使用:

使用ConfigParser 首选需要初始化实例,并读取配置文件:
import configparser
config = configparser.ConfigParser()1、获取指定section下指定option的值
config.read("ini", encoding="utf-8")
r = config.get("db", "db_host")
print(r)  # 运行结果 127.0.0.12、添加section 和 option
if not config.has_section("default"):  # 检查是否存在sectionconfig.add_section("default")
if not config.has_option("default", "db_host"):  # 检查是否存在该optionconfig.set("default", "db_host", "1.1.1.1")3、写入文件
config.write(open("ini", "w"))

2.主要功能规划区分

串口助手主要有五个功能区域,串口设置、串口状态、单条发送、多条发送、接收区。

  • 串口设置:主要用于刷新/检测串口,显示串口名称,选择串口,设置串口的波特率、数据位、校验位、停止位等,打开和关闭串口,以及设置接收窗口的文字及背景颜色。
  • 串口状态:显示当前串口已接收和已发送的字节数,实时显示当前时间,以及串口助手版本号及作者姓名。
  • 单条发送:单条发送数据,单条循环发送,清除窗口及保存窗口,设置是否以HEX进行接收和发送。
  • 多条发送:多条发送数据,多条循环发送,清除接收窗口。
  • 接收区:实时动态显示接收到的数据。

其余具体的设计参照下图:
在这里插入图片描述

3.原型图设计

利用任意原型图设计网站设计原型图,可以根据自己的喜好调整。
在这里简单设计了一下串口助手大致的布局,大小等,方便一会儿创建界面。
在这里插入图片描述

二、界面实现

1.总体布局

在这里插入图片描述

通过设计原型图,大致将串口助手的五大功能区划分为了五个组合框/分组框GroupBox,组合框/分组框的好处在于每个分组框都自带标题和边框,方便区域划分,可以在每个组合框中设置想要的布局结构,各个组合框内部的分组结构互不影响,在一定程度上简化了整个界面的布局。
每个组合框按照表格布局进行排列。首先创建一个用于编写界面的类SerialUi类继承QWidget,在init方法中会调用unit_ui方法来进行初始化UI,在unit_ui方法中对整个窗口进行表格布局,设置窗口大小、图标、名称和位置:

在表格布局中要按照位置关系将每个组合框添加进去
set_serial_setting_groupbox()、set_serial_state_groupbox()、set_receive_groupbox()、set_mul_sent_groupbpx()、set_single_sent_groupbox()都是稍后会写的方法,均会返回一个QGroupBox对象

class SerialUi(QWidget):def __init__(self):super().__init__()# 初始化UIself.unit_ui()# 初始化UIdef unit_ui(self):grid_layout = QGridLayout()  # 设置总体布局为表格布局 2行3列# addWidget用于添加组件grid_layout.addWidget(self.set_serial_setting_groupbox(), 0, 0)grid_layout.addWidget(self.set_serial_state_groupbox(), 1, 0)grid_layout.addWidget(self.set_receive_groupbox(), 0, 1)grid_layout.addWidget(self.set_mul_sent_groupbpx(), 0, 2)grid_layout.addWidget(self.set_single_sent_groupbox(), 1, 1, 1, 2)# 设置布局为grid_layoutself.setLayout(grid_layout)# resize()方法调整窗口的大小self.resize(760, 450)# 设置窗口的图标self.setWindowIcon(QIcon('title_icon.png'))# 将窗口显示到中心self.center()# 设置窗口名self.setWindowTitle('串口助手')# 显示self.show()

将窗口显示到屏幕中间的方法:

# 控制窗口显示在屏幕中心的方法
def center(self):# 获得窗口qr = self.frameGeometry()# 获得屏幕中心点cp = QDesktopWidget().availableGeometry().center()# 显示到屏幕中心qr.moveCenter(cp)self.move(qr.topLeft())

2.串口设置区

在串口设置的界面实现中,根据需要,每一个下拉菜单或者按钮都有一个对应的标签,鉴于这种情况,可以使用表单布局(QFormLayout),QFormLayout是以表单的形式管理界面组件,其中表单中的标签和组件是相对应的关系:
在这里插入图片描述

首先创建串口设置区的分组框serial_setting_gb,并且将其中的布局管理器设置为表单布局:

# 串口设置 区
def set_serial_setting_groupbox(self):# 设置一个 串口设置 分组框serial_setting_gb = QGroupBox('串口设置')# 创建 串口设置 分组框内的布局管理器serial_setting_formlayout = QFormLayout()

创建“检测串口”按钮,addRow是添加表单项的方法,第一个参数为标签Label,第二个参数是组件Field:

# 检测串口 按钮
self.sset_btn_detect = QPushButton('检测串口')
serial_setting_formlayout.addRow('串口选择:', self.sset_btn_detect)

创建后“串口选择”标签,对应“检测串口”组件:

在这里插入图片描述

创建串口下拉菜单,将其添加到表单布局的第二行,没有标签则省略第一个参数,直接添加组件即可:

# 选择串口 下拉菜单
self.sset_cb_choose = QComboBox(serial_setting_gb)
# 添加一个下拉列表 由于没有标签 直接省略即可
serial_setting_formlayout.addRow(self.sset_cb_choose)

创建完成后:
在这里插入图片描述
创建“波特率”、“数据位”、“校验位”、“停止位”、“窗口配色”下拉菜单及对应的标签:

# 波特率 下拉菜单
self.sset_cb_baud = QComboBox(serial_setting_gb)
self.sset_cb_baud.addItems(['100', '300', '600', '1200', '2400', '4800', '9600', '14400', '19200',
'38400', '56000', '57600', '115200', '128000', '256000'])
serial_setting_formlayout.addRow('波特率:', self.sset_cb_baud)# 数据位 下拉菜单
self.sset_cb_data = QComboBox(serial_setting_gb)
self.sset_cb_data.addItems(['8', '7', '6', '5'])
serial_setting_formlayout.addRow('数据位:', self.sset_cb_data)# 校验位 下拉菜单
self.sset_cb_parity = QComboBox(serial_setting_gb)
self.sset_cb_parity.addItems(['N', 'E', 'O'])  # 校验位N-无校验,E-偶校验,O-奇校验
serial_setting_formlayout.addRow('校验位:', self.sset_cb_parity)# 停止位 下拉菜单
self.sset_cb_stop = QComboBox(serial_setting_gb)
self.sset_cb_stop.addItems(['1', '1.5', '2'])
serial_setting_formlayout.addRow('停止位:', self.sset_cb_stop)# 窗口配色 下拉菜单
self.sset_cb_color = QComboBox(serial_setting_gb)
self.sset_cb_color.addItems(['whiteblack', 'blackwhite', 'blackgreen'])
serial_setting_formlayout.addRow('窗口配色:', self.sset_cb_color)

创建后:
在这里插入图片描述

最后创建打开/关闭串口的按钮:

# 打开串口 按钮
self.sset_btn_open = QPushButton('打开串口')
self.sset_btn_open.setIcon(QIcon('open_button.png'))
self.sset_btn_open.setEnabled(False)
serial_setting_formlayout.addRow(self.sset_btn_open)

在这里插入图片描述

最后调整表单布局里每项之间的间距,将组合框serial_setting_gb的布局设置为表单布局serial_setting_formlayout:

serial_setting_formlayout.setSpacing(11)serial_setting_gb.setLayout(serial_setting_formlayout)

最终串口设置组合框的效果如下:

在这里插入图片描述

3.串口状态区

串口状态区和之前的串口设置区类似,首先创建“串口状态”组合框,并设置布局为表单布局:
在这里插入图片描述

# 串口状态区
def set_serial_state_groupbox(self):self.serial_state_gb = QGroupBox('串口状态', self)serial_state_formlayout = QFormLayout()

创建“已发送”和“已接收”标签,初始显示值均为0:

# 已发送 标签
self.sent_count_num = 0
self.ssta_lb_sent = QLabel(str(self.sent_count_num))
serial_state_formlayout.addRow('已发送:', self.ssta_lb_sent)# 已接收 标签
self.receive_count_num = 0
self.ssta_lb_receive = QLabel(str(self.receive_count_num))
serial_state_formlayout.addRow('已接收:', self.ssta_lb_receive)

在这里插入图片描述

添加当前时间标签,这里创建了一个timer定时器对象,用于动态显示时间,将它的timeout()信号连接到showtime函数上,然后调用其start()函数开启定时器:

# 当前时间 标签
self.ssta_lb_timer = QLabel(self)
timer = QTimer(self)
timer.timeout.connect(self.showtime)
timer.start()
serial_state_formlayout.addRow(self.ssta_lb_timer)

在showtime函数中,获取当前的系统时间并转化为适当的格式的字符串,将当前时间的标签设置为该字符串:

def showtime(self):time_display = QDateTime.currentDateTime().toString('yyyy-MM-dd hh:mm:ss dddd')self.ssta_lb_timer.setText(time_display)

在这里插入图片描述

添加版本及作者标签:

# 版本标签
ssta_lb_version = QLabel('version:V1.0.0')
serial_state_formlayout.addRow(ssta_lb_version)
ssta_lb_coder = QLabel('author:wong')
serial_state_formlayout.addRow(ssta_lb_coder)

最后设置行间距和布局:

serial_state_formlayout.setSpacing(13)
self.serial_state_gb.setLayout(serial_state_formlayout)

在这里插入图片描述

4.接收区

接收区的布局较之前的布局较为简单,只需使用垂直布局,在接收区组合框中添加一个富文本浏览器QTextBrowser即可:
在这里插入图片描述

# 接收区
def set_receive_groupbox(self):# 设置一个接收区分组框receive_gb = QGroupBox('接收区', self)# 添加显示接收日志的文本框self.receive_log_view = QTextBrowser(receive_gb)# 在这里设置了最小宽度 防止接收窗口因拉动伸缩而变换的过小self.receive_log_view.setMinimumWidth(350)  self.receive_log_view.append('Hello,欢迎使用串口助手!\n')# 设置布局并添加文本框vbox = QVBoxLayout()vbox.addWidget(self.receive_log_view)# 设置接收区分组框的布局receive_gb.setLayout(vbox)return receive_gb

在这里插入图片描述

5.单条发送区

单条发送区的结构较为复杂,在组合框中总体布局为水平布局,要在其中嵌套一个垂直布局和一个表格布局,垂直布局中添加单条发送文本框,表格布局中添加其余组件,之后将两种布局嵌套进入水平布局中:
在这里插入图片描述

# 单条发送区
def set_single_sent_groupbox(self):single_sent_gb = QGroupBox('单条发送', self)sins_overall_hlayout = QHBoxLayout(single_sent_gb)vlayout_1 = QVBoxLayout()glayout_1 = QGridLayout()

创建单条发送文本框,并添加到垂直布局中:

# 单条命令 文本框
self.sins_te_send = QTextEdit()
self.sins_te_send.setMinimumWidth(350)
vlayout_1.addWidget(self.sins_te_send)

创建循环发送复选框及输入文本框、HEX发送复选框、HEX接收复选框、清除发送、保存窗口、发送按钮,都添加在表格布局中:

# 循环发送 HEX发送 HEX接收 复选框
self.sins_cb_loop_send = QCheckBox('循环发送')
self.sins_le_loop_text = QLineEdit('1000')
reg = QRegExp('^(?!0)(?:[0-9]{1,6}|1000000)$')  # ^(?!0)(?:[0-9]{1,4}|10000)$
reg_validator = QRegExpValidator(reg)
self.sins_le_loop_text.setValidator(reg_validator)
sins_lb_loop_label = QLabel('ms/次')
self.sins_cb_hex_receive = QCheckBox('HEX接收')
self.sins_cb_hex_send = QCheckBox('HEX发送')glayout_1.addWidget(self.sins_cb_loop_send, 0, 0)
glayout_1.addWidget(self.sins_le_loop_text, 0, 1)
glayout_1.addWidget(sins_lb_loop_label, 0, 2)
glayout_1.addWidget(self.sins_cb_hex_receive, 1, 0)
glayout_1.addWidget(self.sins_cb_hex_send, 2, 0)# 保存窗口 清除发送 发送 按钮
self.sins_btn_save = QPushButton('保存窗口')
self.sins_btn_clear = QPushButton('清除发送')
self.sins_btn_send = QPushButton('发送')
glayout_1.addWidget(self.sins_btn_save, 1, 1, 1, 2)
glayout_1.addWidget(self.sins_btn_clear, 2, 1, 1, 2)
glayout_1.addWidget(self.sins_btn_send, 3, 0, 1, 3)

其中,在循环时间输入文本框中,使用了一个校验器来对输入的文字进行校验,文本框中可输入的有效时间范围为1-1000000的数字,如果不符合输入的范围,例如输入了汉字、字母、特殊符号等,则不显示,或者输入了超出范围的数字,例如0、负数、大于1000000的数字,也不予显示。校验用的是正则表达式:

reg = QRegExp('^(?!0)(?:[0-9]{1,6}|1000000)$')  # 创建一个正则表达式
reg_validator = QRegExpValidator(reg)  # QRegExpValidator类用于根据正则表达式检查字符串,创建一个根据reg正则表达式检查字符串的对象reg_validator
self.sins_le_loop_text.setValidator(reg_validator)  # 将文本框的输入限制设置为reg_validator校验器来进行校验# 其中正则表达式解释如下
^(?!0)(?:[0-9]{1,6}|1000000)$
^   # 匹配字符串的开始
(?!0)  # 匹配不是0开头的任意数字  零宽度负预测先行断言(?!exp),断言此位置的后面不能匹配表达式exp
(?:[0-9]{1,6}|1000000)   # (?:exp) 匹配exp表达式但不获取结果   匹配 0-999999 或者 1000000 这些数字
[0-9]{1,6} 0-9  # 这几个数字可以出现至少1次 至多6次 
|  # 或者
$  # 匹配字符串的结束

最后将垂直布局和表格布局添加到水平布局中,再将组合框的布局设置为水平布局:

sins_overall_hlayout.addLayout(vlayout_1)
sins_overall_hlayout.addLayout(glayout_1)
single_sent_gb.setLayout(sins_overall_hlayout)

在这里插入图片描述

6.多条发送区

多条发送中的布局也较为复杂,多条发送组合框总体的布局方式为垂直布局,但是在其中要嵌套两个表格布局,第一个表格布局中放置“清除接收”按钮和“多条循环”复选框、输入文本框和标签,第二个表格布局中放置7组由复选框、单行文本框、按钮组成的多条发送界面:

在这里插入图片描述

# 多条发送区
def set_mul_sent_groupbpx(self):mul_send_gb = QGroupBox('多条发送', self)mul_send_vlayout = QVBoxLayout()mul_send_gridlayout1 = QGridLayout()mul_send_gridlayout2 = QGridLayout()

在第一个表格布局中添加“清除接收”和“多条循环”复选框及单行文本框和“ms”标签,其中时间单行输入文本框的输入限制校验器的设置同上:

# 清除接收 按钮
self.muls_btn_clear = QPushButton('清除接收')
mul_send_gridlayout1.addWidget(self.muls_btn_clear, 0, 0, 1, 3)# 多条循环发送
self.mul_cb_loop_send = QCheckBox('多条循环')
mul_send_gridlayout1.addWidget(self.mul_cb_loop_send, 1, 0)
self.mul_le_loop_text = QLineEdit('1000')
reg = QRegExp('^(?!0)(?:[0-9]{1,6}|1000000)$')
reg_validator = QRegExpValidator(reg)
self.mul_le_loop_text.setValidator(reg_validator)
mul_send_gridlayout1.addWidget(self.mul_le_loop_text, 1, 1)
self.mul_lb_loop_lable = QLabel('ms/次')
mul_send_gridlayout1.addWidget(self.mul_lb_loop_lable, 1, 2)

在第二个表格布局中为了使代码简洁,利用两组for循环添加复选框、文本框和按钮,由于后续的使用需要,给每个复选框、文本框和按钮添加唯一的对象名称objectName,在之后功能实现的过程中如果需要利用这7组组件,则可以直接用findChild通过objectName来查找:

# 多条发送 区域self.mul_btn_list = []for i in range(1, 8):for j in range(3):if j == 0:self.checkbox = QCheckBox()self.checkbox.setObjectName('mul_cb_{}'.format(i))mul_send_gridlayout2.addWidget(self.checkbox, i, j)elif j == 1:self.textedit = QLineEdit()self.textedit.setObjectName('mul_le_{}'.format(i))mul_send_gridlayout2.addWidget(self.textedit, i, j)else:self.button = QPushButton(str(i))self.button.setFixedSize(25, 22)self.button.setObjectName('mul_btn_{}'.format(i))self.mul_btn_list.append(self.button.objectName())mul_send_gridlayout2.addWidget(self.button, i, j)

最后将两组表格布局嵌套进垂直布局中:

mul_send_vlayout.addLayout(mul_send_gridlayout1)
mul_send_vlayout.addLayout(mul_send_gridlayout2)
mul_send_gb.setLayout(mul_send_vlayout)
mul_send_gb.setFixedWidth(180)

在这里插入图片描述

最终完成效果:

在这里插入图片描述

三、功能实现

首先创建一个SerialAssistant类继承SerialUi,在这个类里实现串口助手所需的功能,在init方法里初始化串口通信的serial对象,初始化串口配置文件以及给各个信号绑定的槽函数,由于显示的调用了父类的init方法,所以在实例化的时候会自动生成UI界面,不用再次调用生成函数:

class SerialAssistant(SerialUi):def __init__(self):super().__init__()# 初始化serial对象 用于串口通信self.ser = serial.Serial()# 初始化串口配置文件self.serial_cfg()# 初始化串口 绑定槽self.unit_serial()

检测串口

首先在unit_serial()方法中给串口检测按钮点击clicked信号绑定槽函数port_detect,当点击按钮时就会调用port_detect方法:

# 初始化串口 给各个信号绑定槽
def unit_serial(self):# 串口检测按钮self.sset_btn_detect.clicked.connect(self.port_detect)

在port_detect方法中,调用serial.tools.list_ports.comports()接口可以返回计算机上所有的port口信息,将其储存在列表中并显示在下拉列表中。

返回的列表信息是ListPortInfo类的列表,存储类似于 [ [ COM1, xxxx ], [ COM2, yyyy ] ] 利用for循环读取每个串口的COM口数和串口名称,将其添加到下拉列表中,如果读取到的列表长度为0,则显示无串口:

# 串口检测
def port_detect(self):# 检测所有存在的串口 将信息存在字典中self.port_dict = {}# serial.tools.list_ports.comports()返回计算机上所有的port口信息# 将其存在列表中port_list = list(serial.tools.list_ports.comports())  # 清除下拉列表中已有的选项self.sset_cb_choose.clear()for port in port_list:# 添加到字典里self.port_dict["%s" % port[0]] = "%s" % port[1]# 添加到下拉列表选项self.sset_cb_choose.addItem(port[0] + ':' + port[1])if len(self.port_dict) == 0:self.sset_cb_choose.addItem('无串口')self.sset_btn_open.setEnabled(True)

检测串口-无串口
在这里插入图片描述
检测串口-有串口
在这里插入图片描述

打开/关闭串口

首先在unit_serial()方法中给串口打开/关闭按钮点击clicked信号绑定槽函数port_open_close,当点击按钮时就会调用port_open_close方法:

# 初始化串口 给各个信号绑定槽
def unit_serial(self):# 打开/关闭串口 按钮self.sset_btn_open.clicked.connect(self.port_open_close)

在port_open_close方法中,对按钮文字和是否有串口进行判断,如果打开串口无异常且串口状态是打开,则改变按钮的文字和图标,将串口的波特率、校验位、数据位、停止位下拉列表设置为不可修改:

# 打开/关闭 串口def port_open_close(self):# 按打开串口按钮 且 串口字典里有值 if (self.sset_btn_open.text() == '打开串口') and self.port_dict:self.ser.port = self.get_port_name()  # 设置端口self.ser.baudrate = int(self.sset_cb_baud.currentText())  # 波特率self.ser.bytesize = int(self.sset_cb_data.currentText())  # 数据位self.ser.parity = self.sset_cb_parity.currentText()  # 校验位self.ser.stopbits = int(self.sset_cb_stop.currentText())  # 停止位# 捕获 串口无法打开的异常try:self.ser.open()except serial.SerialException:QMessageBox.critical(self, 'Open Port Error', '此串口不能正常打开!')return None# 打开串口接收定时器 周期为2msself.serial_receive_timer.start(2)# 判断 串口的打开状态if self.ser.isOpen():self.sset_btn_open.setText('关闭串口')self.sset_btn_open.setIcon(QIcon('close_button.png'))self.serial_state_gb.setTitle('串口状态(已开启)')self.set_setting_enable(False)# 按打开串口按钮 但 没有读取到串口elif (self.sset_btn_open.text() == '打开串口') and (self.sset_cb_choose.currentText() == '无串口'):QMessageBox.warning(self, 'Open Port Warning', '没有可打开的串口!')return None# 点击关闭串口按钮elif self.sset_btn_open.text() == '关闭串口':# 停止定时器self.serial_receive_timer.stop()try:self.ser.close()except:QMessageBox.critical(self, 'Open Port Error', '此串口不能正常关闭!')return Noneself.sset_btn_open.setText('打开串口')self.sset_btn_open.setIcon(QIcon('open_button.png'))self.serial_state_gb.setTitle('串口状态')self.set_setting_enable(True)# 更改已发送和已接收标签self.sent_count_num = 0self.ssta_lb_sent.setText(str(self.sent_count_num))self.receive_count_num = 0self.ssta_lb_receive.setText(str(self.receive_count_num))

获取端口号的方法:

# 获取端口号(串口选择界面想显示完全 但打开串口只需要串口号COMX)
def get_port_name(self):full_name = self.sset_cb_choose.currentText()# rfind会找到:的位置com_name = full_name[0:full_name.rfind(':')]return com_name

使能的设置串口设置的可用与禁用:

# 设置 串口设置区 可用与禁用
def set_setting_enable(self, enable):self.sset_cb_choose.setEnabled(enable)self.sset_cb_baud.setEnabled(enable)self.sset_cb_data.setEnabled(enable)self.sset_cb_parity.setEnabled(enable)self.sset_cb_stop.setEnabled(enable)

打开串口-有串口
在这里插入图片描述

打开串口-无串口
在这里插入图片描述

改变窗口颜色

根据窗口配色下拉列表中选择的选项的不同,改变窗口配色,在unit_serial()方法中给根据下拉列表文字改变的信号绑定槽函数change_color,当点选择不同的下拉列表选项时就会调用change_color方法:

def unit_serial(self):# 更改窗口颜色下拉菜单self.sset_cb_color.currentTextChanged.connect(self.change_color)

在change_color方法中通过if判断和设置styleSheet即可更改窗口颜色:
使用setStyleSheet可以设置图形界面的外观,setStyleSheet("QPushButton{color:red}") 设定前景颜色,就是字体颜色,setStyleSheet("QPushButton{background-color:yellow}")设定背景颜色为红色,setStyleSheet("QPushButton{color:rgb(255,255,255);background-color:black}")同时设定字体和背景颜色

# 改变窗口颜色
def change_color(self):if self.sset_cb_color.currentText() == 'whiteblack':self.receive_log_view.setStyleSheet("QTextEdit {color:black;background-color:white}")elif self.sset_cb_color.currentText() == 'blackwhite':self.receive_log_view.setStyleSheet("QTextEdit {color:white;background-color:black}")elif self.sset_cb_color.currentText() == 'blackgreen':self.receive_log_view.setStyleSheet("QTextEdit {color:rgb(0,255,0);background-color:black}")

改变窗口颜色
在这里插入图片描述

发送

分析发送的基本实现代码可知,单条发送和多条发送在基础实现上是差不多的,为了提高代码复用率,先定一个公用的发送函数,根据是否勾选了HEX发送来判断是否要转换为16进制发送:

# 发送def send_text(self, send_string):if self.ser.isOpen():# 非空字符串if send_string != '':# 如果勾选了HEX发送 则以HEX发送 String到Int再到Byteif self.sins_cb_hex_send.isChecked():# 移除头尾的空格或换行符send_string = send_string.strip()sent_list = []while send_string != '':# 检查是否是16进制 如果不是则抛出异常try:# 将send_string前两个字符以16进制解析成整数num = int(send_string[0:2], 16)except ValueError:QMessageBox.critical(self, 'Wrong Data', '请输入十六进制数据,以空格分开!')self.sins_cb_hex_send.setChecked(False)return Noneelse:send_string = send_string[2:].strip()# 将需要发送的字符串保存在sent_list里sent_list.append(num)# 转化为bytesingle_sent_string = bytes(sent_list)# 否则ASCII发送else:single_sent_string = (send_string + '\r\n').encode('utf-8')# 获得发送字节数sent_num = self.ser.write(single_sent_string)self.sent_count_num += sent_numself.ssta_lb_sent.setText(str(self.sent_count_num))else:QMessageBox.warning(self, 'Port Warning', '没有可用的串口,请先打开串口!')return None

单条发送

单条发送要通过点击“发送”按钮来触发,在unit_serial()方法中给“发送”按钮的点击信号绑定槽函数single_send,当点选择不同的下拉列表选项时就会调用single_send方法:

def unit_serial(self):# 单行发送数据 按钮self.sins_btn_send.clicked.connect(self.single_send)

单条发送就是从单条发送文本框中获取到需要发送的文本后调用send_text方法:

# 单条发送def single_send(self):# 获取已输入的字符串single_sent_string = self.sins_te_send.toPlainText()self.send_text(single_sent_string)

单条发送-ASCII
在这里插入图片描述

单条发送-HEX
在这里插入图片描述

多条发送

多条发送要根据所点击命令对应的按钮来触发,在unit_serial方法中根据唯一的objectname来循环给每个多条发送按钮绑定对应的槽函数multi_send_general:

def unit_serial(self):# 多行发送数据 按钮for i in range(1, 8):self.child_button = self.findChild(QPushButton, 'mul_btn_{}'.format(i))self.child_button.clicked.connect(self.multi_send_general)

在multi_send_general中首先要找到发送点击信号的发送者(Sender),判断是哪个按钮发送的信号,然后再获取按钮对应的文本框中的文本,之后调用send_text方法:

# 多行发送————普通 涉及sender 点击数字按钮发送对应的命令
def multi_send_general(self):# 获取到发送信号的按钮序号sent = self.sender().text()# 找到对应的文本框mul_te_sent = self.findChild(QLineEdit, 'mul_le_{}'.format(sent))# 获取内容multi_sent_string = mul_te_sent.text()self.send_text(multi_sent_string)

多条发送
在这里插入图片描述

接收数据

接收数据需要定时刷新串口接收缓存区,所以需要定义一个定时器,在打开串口后定时刷新self.serial_receive_timer.start(2),timeout代表计时结束后调用data_receive,也就是每2ms调用一次data_receive:

def unit_serial(self):# 定时器接受数据self.serial_receive_timer = QTimer(self)self.serial_receive_timer.timeout.connect(self.data_receive)

接收函数中获取接收缓存中的字节数后根据是否勾选HEX接收来判断如何显示接收数据:

# 接收数据def data_receive(self):try:# inWaiting():返回接收缓存中的字节数num = self.ser.inWaiting()except:passelse:# 接收缓存中有数据if num > 0:# 读取所有的字节数data = self.ser.read(num)receive_num = len(data)# HEX显示if self.sins_cb_hex_receive.isChecked():receive_string = ''for i in range(0, len(data)):# {:X}16进制标准输出形式 02是2位对齐 左补0形式receive_string = receive_string + '{:02X}'.format(data[i]) + ' 'self.receive_log_view.append(receive_string)# 让滚动条随着接收一起移动self.receive_log_view.moveCursor(QTextCursor.End)else:self.receive_log_view.insertPlainText(data.decode('utf-8'))self.receive_log_view.moveCursor(QTextCursor.End)# 更新已接收字节数self.receive_count_num += receive_numself.ssta_lb_receive.setText(str(self.receive_count_num))else:pass

循环发送

单条循环发送

单条循环发送需要创建一个定时器来充当循环的时钟,由定时器控制循环时长,根据循环发送复选框是否被勾选的状态信号链接到single_loop_send方法:

def unit_serial(self):# 循环发送数据——单条self.loop_single_send_timer = QTimer()self.loop_single_send_timer.timeout.connect(self.single_send)self.sins_cb_loop_send.stateChanged.connect(self.single_loop_send)

当单条循环复选框的勾选状态改变之后,调用single_loop_send方法后,进行相应的判断“单条输入文本框是否有内容”→“串口是否处于打开状态”→“如果单条循环复选框被勾选”→“打开定时器”:

# 循环发送——单条def single_loop_send(self):# 单挑输入框中有内容if self.sins_te_send.toPlainText():# 串口打开if self.ser.isOpen():# 循环发送复选框被勾选if self.sins_cb_loop_send.isChecked():# 循环时间文本框中输入了内容if self.sins_le_loop_text.text():# 打开定时器 按输入的数字计时self.loop_single_send_timer.start(int(self.sins_le_loop_text.text()))# 将循环时间文本框和打开串口按钮设为不可选中self.sins_le_loop_text.setEnabled(False)self.sset_btn_open.setEnabled(False)else:QMessageBox.warning(self, 'Value Error', '请输入1-1000000的值!')self.sins_le_loop_text.setText('1000')self.sins_cb_loop_send.setChecked(False)else:# 停止定时器self.loop_single_send_timer.stop()self.sins_le_loop_text.setEnabled(True)self.sset_btn_open.setEnabled(True)else:QMessageBox.warning(self, 'Port Warning', '没有可用的串口,请先打开串口!')self.sins_cb_loop_send.setChecked(False)

打开定时器后,定时器计时结束,会调用单行发送的方法single_send将数据发送出去:

# 单行发送
def single_send(self):# 获取已输入的字符串single_sent_string = self.sins_te_send.toPlainText()self.send_text(single_sent_string)

单条循环发送
在这里插入图片描述

多条循环发送(功能尚不完善)

多条循环发送与单纯的多条发送不一样,多条发送只需要判断发送信号的按钮是哪个,然后调用multi_send_general即可,但是多条循环发送需要根据是否勾选多条循环复选框,是否勾选多条发送输入框前的复选框,勾选的多条发送输入框是否由内容来判断能否实施发送操作。首先在unit_serial中给信号绑定槽函数,这里的逻辑可以理解为,多条循环复选框的勾选状态改变后将会调用mul_loop_send方法,在mul_loop_send方法中当判断的先决条件都成立时则打开start定时器loop_mul_sent_timer,定时器multi_send_special计时结束timeout后会调用multi_send_special方法发送多条数据,每当定时器multi_send_special计时结束后都会调用multi_send_special方法,以此来形成多条循环:

def unit_serial(self):# 循环发送数据——多条self.loop_mul_sent_timer = QTimer()self.loop_mul_sent_timer.timeout.connect(self.multi_send_special)self.mul_cb_loop_send.stateChanged.connect(self.mul_loop_send)

当勾选/取消勾选了多条输入复选框后,会进入mul_loop_send方法,在其中进行判断,然后打开定时器,定时时间与多条循环文本框中输入的一致:

	# 循环发送——多条def mul_loop_send(self):# 串口状态打开if self.ser.isOpen():# 多条循环复选框被勾选if self.mul_cb_loop_send.isChecked():# 多条循环时间输入框由内容if self.mul_le_loop_text.text():# 打开定时器self.loop_mul_sent_timer.start(int(self.mul_le_loop_text.text()))self.mul_le_loop_text.setEnabled(False)self.sset_btn_open.setEnabled(False)else:QMessageBox.warning(self, 'Value Error', '请输入1-1000000的值!')self.mul_le_loop_text.setText('1000')self.mul_cb_loop_send.setChecked(False)else:self.loop_mul_sent_timer.stop()self.mul_le_loop_text.setEnabled(True)self.sset_btn_open.setEnabled(True)else:QMessageBox.warning(self, 'Port Warning', '1没有可用的串口,请先打开串口!')self.mul_cb_loop_send.setChecked(False)

定时器loop_mul_sent_timer计时结束timeout后会调用multi_send_special方法,在其中获取到发送命令列表后利用send_text进行发送:

# 多行发送————特殊 不用点击按钮 根据勾选发送def multi_send_special(self):# 将待发送列表中的每一条循环发送出去self.send_list = self.get_mul_send_list()for string in self.send_list:self.send_text(string)

其中get_mul_send_list方法用于获取一个多条发送命令的列表,里面包含了所有被勾选的命令:

# 获取一个多条发送列表 里面包含所有打钩的命令
def get_mul_send_list(self):self.mul_send_list = []for i in range(1, 8):mul_te_sent = self.findChild(QLineEdit, 'mul_le_{}'.format(i))multi_sent_string = mul_te_sent.text()mul_cb_check = self.findChild(QCheckBox, 'mul_cb_{}'.format(i))if multi_sent_string and mul_cb_check.isChecked():self.mul_send_list.append(multi_sent_string)return self.mul_send_list

多条循环
在这里插入图片描述
缺陷
目前多条循环发送,只能一次性同时发送多条,不能定时发送一条后再发送一条,这里仍需改进。

保存窗口

保存窗口用到了QFileDialog.getSaveFileName,这个方法不会帮你创建文件,只返回一个元组。元组第一项为你的文件路径(包括你给的文件名),第二项为该文件的类型。

# 保存窗口
def save_receive_to_file(self):file_name = QFileDialog.getSaveFileName(self, '保存窗口为txt文件','SaveWindow' +time.strftime('%Y_%m_%d_%H-%M-%S', time.localtime()) + '.txt')if file_name[1]:with open(file_name[0], 'w') as file:my_text = self.receive_log_view.toPlainText()file.write(my_text)else:pass

保存窗口

在这里插入图片描述

清除

清除接收

当“清除接收”按钮被点击之后,执行清除操作:

def unit_serial(self):# 清除接收按钮self.muls_btn_clear.clicked.connect(self.clear_receive)

清除窗口就是直接将接收文本框中的文本设置为空:

# 清除接收
def clear_receive(self):self.receive_log_view.setText('')

清除接收
在这里插入图片描述

清除发送

当“清除发送”按钮被点击之后,执行清除操作:

def unit_serial(self):# 清除发送按钮self.sins_btn_clear.clicked.connect(self.clear_send)

清除发送就是直接将发送文本框中的文本设置为空:

# 清除发送
def clear_send(self):self.sins_te_send.setText('')

清除发送
在这里插入图片描述

读写配置文件

有效利用配置文件可以帮助我们记忆串口设置和输入的命令,减少重复工作,提高串口助手的效率。

每次使用配置文件都要在SerialAssistant类中的init方法中对串口配置进行初始化,包括初始化一个用于保存串口配置信息的字典,一个用于保存多条命令的字典,一个用于保存单条命令的字典,获取当前的目录用于保存配置文件,以及配置文件所在的目录名和配置文件的名称,之后要初始化一个configparser对象用于后续的配置操作:

# 初始化串口配置 定义串口设置信息 保存和读取def serial_cfg(self):self.cfg_serial_dic = {}  # 用于保存串口设置信息的字典self.cfg_command_dic = {}self.cfg_single_dic = {}self.current_path = os.path.dirname(os.path.realpath(__file__))  # 当前目录self.cfg_path = ''  # 配置文件的路径self.cfg_dir = 'settings'  # 配置文件目录self.cfg_file_name = 'cfg.ini'  # 配置文件名self.conf_parse = configparser.ConfigParser()  # 配置文件解析ConfigParser对象# 读取串口配置self.read_cfg()# 将读取到的串口配置信息显示到界面上self.display_cfg()

读取串口配置的方法read_cfg是要查看配置文件和所需的section是否存在,如果不存在则需要新建目录setting和目录中的文件cfg.ini,如果文件cfg.ini已存在但其中所需要的的section不存在则需要新建section:

	# 读取串口配置————配置文件和section是否存在def read_cfg(self):self.cfg_path = os.path.join(self.current_path, self.cfg_dir, self.cfg_file_name)  # 获取配置文件路径 join用于连接两个或更多的路径# 判断读取配置文件是否正常 如果读取文件正常if self.conf_parse.read(self.cfg_path):# 判断读取section是否正常try:# 获取serial_setting section  返回一个配置字典serial_items = self.conf_parse.items('serial_setting')self.cfg_serial_dic = dict(serial_items)print(self.cfg_serial_dic)# 如果没有找到sectionexcept configparser.NoSectionError:self.conf_parse.add_section('serial_setting')  # 添加sectionself.conf_parse.write(open(self.cfg_path, 'w'))  # 保存到配置文件try:command_items = self.conf_parse.items('mul_sent_command')self.cfg_command_dic = dict(command_items)print(self.cfg_command_dic)except configparser.NoSectionError:self.conf_parse.add_section('mul_sent_command')self.conf_parse.write(open(self.cfg_path, 'w'))try:command_items = self.conf_parse.items('single_sent_command')self.cfg_single_dic = dict(command_items)print(self.cfg_single_dic)except configparser.NoSectionError:self.conf_parse.add_section('single_sent_command')self.conf_parse.write(open(self.cfg_path, 'w'))# 读取文件异常else:# 判断setting目录是否存在 不存在的话新建目录if not os.path.exists(os.path.join(self.current_path, self.cfg_dir)):os.mkdir(os.path.join(self.current_path, self.cfg_dir))self.conf_parse.add_section('serial_setting')  # 添加sectionself.conf_parse.add_section('mul_sent_command')self.conf_parse.add_section('single_sent_command')self.conf_parse.write(open(self.cfg_path, 'w'))  # 保存到配置文件

保存串口配置信息的方法save_cfg将每个需要保存的信息保存在配置文件中,供下一次启动时读取,每次在退出串口助手是调用保存方法,保存此次串口助手的配置:

	# 保存串口配置信息def save_cfg(self):# 保存每一项到配置文件self.conf_parse.set('serial_setting', 'baudrate', str(self.ser.baudrate))self.conf_parse.set('serial_setting', 'data', str(self.ser.bytesize))self.conf_parse.set('serial_setting', 'stopbits', str(self.ser.stopbits))self.conf_parse.set('serial_setting', 'parity', self.ser.parity)for i in range(1, 8):self.conf_parse.set('mul_sent_command', 'command_{}'.format(i),self.findChild(QLineEdit, 'mul_le_{}'.format(i)).text())self.conf_parse.set('single_sent_command', 'command', self.sins_te_send.toPlainText())self.conf_parse.write(open(self.cfg_path, 'w'))

每次启动串口助手时,需要读取串口配置信息然后将其显示在串口助手中对应的位置上:

	# 将读取到的串口配置信息显示到界面上def display_cfg(self):self.sset_cb_baud.setCurrentText(self.conf_parse.get('serial_setting', 'baudrate'))self.sset_cb_data.setCurrentText(self.conf_parse.get('serial_setting', 'data'))self.sset_cb_stop.setCurrentText(self.conf_parse.get('serial_setting', 'stopbits'))self.sset_cb_parity.setCurrentText(self.conf_parse.get('serial_setting', 'parity'))for i in range(1, 8):command = self.conf_parse.get('mul_sent_command', 'command_{}'.format(i))if command:self.findChild(QLineEdit, 'mul_le_{}'.format(i)).setText(command)self.sins_te_send.setText(self.conf_parse.get('single_sent_command', 'command'))

配置文件格式
在这里插入图片描述
结合配置文件启动程序
在这里插入图片描述

关闭窗口

关闭窗口需要重写closeEvent方法,在点击窗口右上角的×以后,发出询问对话框,询问是否确认退出,一旦选择是则保存当前串口的配置后退出串口助手,否则不做任何操作:

def closeEvent(self, event):reply = QMessageBox.question(self, 'Message', "确定要退出吗?",QMessageBox.Yes | QMessageBox.No, QMessageBox.No)# 判断返回值,如果点击的是Yes按钮,我们就关闭组件和应用,否则就忽略关闭事件if reply == QMessageBox.Yes:self.save_cfg()event.accept()else:event.ignore()

关闭窗口
在这里插入图片描述

main函数

在main函数中实例化应用程序和对象即可运行串口助手:

if __name__ == '__main__':# 每一PyQt5应用程序必须创建一个应用程序对象app = QApplication(sys.argv)# 创建一个SerialAssistant对象su = SerialAssistant()# 系统exit()方法确保应用程序干净的退出# exec_()方法的作用是“进入程序的主循环直到exit()被调用”,# 如果没有这个方法,运行的时候窗口会闪退sys.exit(app.exec_())

四、其他

依赖库版本

python——3.8.3
PyQt5——5.15.0
pyserial——3.5
configparser——5.0.1
以上模块除python外都可以通过pip install 模块名来下载

目录结构

在这里插入图片描述

这篇关于「Python」结合PyQt5和pyserial实现串口助手的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/219209

相关文章

python: 多模块(.py)中全局变量的导入

文章目录 global关键字可变类型和不可变类型数据的内存地址单模块(单个py文件)的全局变量示例总结 多模块(多个py文件)的全局变量from x import x导入全局变量示例 import x导入全局变量示例 总结 global关键字 global 的作用范围是模块(.py)级别: 当你在一个模块(文件)中使用 global 声明变量时,这个变量只在该模块的全局命名空

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

【Python编程】Linux创建虚拟环境并配置与notebook相连接

1.创建 使用 venv 创建虚拟环境。例如,在当前目录下创建一个名为 myenv 的虚拟环境: python3 -m venv myenv 2.激活 激活虚拟环境使其成为当前终端会话的活动环境。运行: source myenv/bin/activate 3.与notebook连接 在虚拟环境中,使用 pip 安装 Jupyter 和 ipykernel: pip instal

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount

【机器学习】高斯过程的基本概念和应用领域以及在python中的实例

引言 高斯过程(Gaussian Process,简称GP)是一种概率模型,用于描述一组随机变量的联合概率分布,其中任何一个有限维度的子集都具有高斯分布 文章目录 引言一、高斯过程1.1 基本定义1.1.1 随机过程1.1.2 高斯分布 1.2 高斯过程的特性1.2.1 联合高斯性1.2.2 均值函数1.2.3 协方差函数(或核函数) 1.3 核函数1.4 高斯过程回归(Gauss

Kubernetes PodSecurityPolicy:PSP能实现的5种主要安全策略

Kubernetes PodSecurityPolicy:PSP能实现的5种主要安全策略 1. 特权模式限制2. 宿主机资源隔离3. 用户和组管理4. 权限提升控制5. SELinux配置 💖The Begin💖点点关注,收藏不迷路💖 Kubernetes的PodSecurityPolicy(PSP)是一个关键的安全特性,它在Pod创建之前实施安全策略,确保P