- 此系列文章源于SYSU 2024电信学院通信工程专业工训课的训练题目,同时也是2024电子设计校内赛的题目。题目的要求描述如下:
无线手持二维码识别器是一个通过图像识别技术,对二维码图片进行识别,并解析出二维码的内容数据,并通过无线传输给手机或电脑的一种设备,通过这个设备,可以快速对物品进行扫描并在电脑端进行归档。要求能够对二维码图片进行扫描,二维码可自行生成,源信息包含字母和数字,能够支持将扫描数据上传到电脑,并在电脑端设计上位机进行数据显示,能够支持识别特殊二维码时,进行报警鸣叫处理,同时要使用3D建模软件对识别器进行建模并制作,大小符合手持。- 项目使用的主控为立创梁山派GD32F470ZGT6,项目源码存放地址为https://github.com/liangbm3/GD32_QR_Decoder
- 此系列文章更多的是对开发过程的记录和思路的呈现,文章分则可以作为教程实现单个功能,合则可以作为复现整个项目的教程。
- 文章的撰写部分参考了立创官方文档
- 文章未经作者许可,不得转载。
文章目录
前言
为了将数据传到上位机,可以用ESP01S模块开启热点,并建立web服务,然后电脑端通过连接热点,访问相应的地址即可获得数据。
一、PyQt5
1.简介
Qt是一组C++库和开发工具,包括平台 图形用户界面、网络、线程、 正则表达式、SQL 数据库、SVG、OpenGL、XML、用户和应用程序 设置、定位和定位服务、短程通信(NFC 和蓝牙)、网页浏览、3D 动画、图表、3D 数据可视化和 与应用商店对接。PyQt5 实现了 1000 多个这样的类作为 Python 模块集。
PyQt5 包括 PyQt5 本身和许多与 Qt 相对应的附加组件 其他库。每个都作为源分发 (sdist) 提供,并且 适用于 Windows、Linux 和 macOS 的二进制轮。
PyQt5 支持 Windows、Linux、UNIX、Android、macOS 和 iOS 平台,并且 需要 Python v3.5 或更高版本。(PyQt5 也应该针对 Python v2.7 构建 以及使用旧版 configure.py 构建脚本的早期版本的 Python v3,但不受支持。
2.安装流程
1.创建虚拟环境
conda create -n pyqt python=3.9
2.使用pip安装
pip install PyQt5
3.安装 PyQt5-tools
pip install PyQt5-tools
4.根据自己安装的位置添加如下环境变量
D:\program\anaconda3\envs\pyqt\Lib\site-packages\qt5_applications\Qt\bin
D:\program\anaconda3\envs\pyqt\Lib\site-packages\pyqt5_tools
5.输入designer.exe
,若出现如图界面,说明环境安装成功
二、下位机的编写
1.导入相应的库
#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
2.定义AP的SSID和密码,定义一个字符串来接收串口传来的数据,和定义一个布尔类型的变量来反映传输状态,定义一个变量来存储连接的设备数目
const char *ssid = "esp01s"; // AP的SSID(WiFi名字)
const char *password = "12345678"; // AP的密码
String inputString = ""; // 定义一个字符串来接收串口传来的数据
boolean stringComplete = false; // 字符串传输完成的标志位
int Station_NUM = 0; // 连接的设备数目
IPAddress IP; // ip地址
3.开启ESP8266web服务
ESP8266WebServer esp8266_server(80);
4.编写初始化函数,这里先初始化串口,然后设置ESP8266工作在AP模式,设置AP的名称和密码,然后通过传输出IP的IP地址。开启web服务器,定义根路由,设置能够接收的字符串长度。
void setup()
{
Serial.begin(9600);
Serial.println("serial ok");
WiFi.mode(WIFI_AP); // 设置为AP模式
WiFi.softAP(ssid, password); // 配置AP信息,并开启AP
IP = WiFi.softAPIP(); // 用变量IP接收AP当前的IP地址
Serial.println(IP); // 打印输出IP的IP地址
esp8266_server.begin(); // 开启web服务器
esp8266_server.on("/", handleRoot); // 访问根路由即调用 handlerRoot 函数处理
esp8266_server.onNotFound(handleNotFound);
Serial.println("HTTP esp8266_server started"); // 告知用户ESP8266网络服务功能已经启动
inputString.reserve(200);
}
5.编写循环函数,这里不断检测连接AP的数量,如果有变化则通过串口传出这个变化,并且不断轮询处理对服务器的请求,处理字符串的接收。
void loop()
{
int gotoAP = WiFi.softAPgetStationNum(); // 获取当前连接到AP的设备数量
if (gotoAP != Station_NUM)
{
Station_NUM = gotoAP;
Serial.println("ap");
Serial.println(gotoAP);
}
esp8266_server.handleClient(); // 处理请求
if (stringComplete)
{
Serial.println(inputString);
// 清除字符串
inputString = "";
stringComplete = false;
}
serialEvent();
}
6.编写根路由处理函数,当访问根路由时,自动调用该函数,将MUC传来的字符串作为响应返回给客户端
void handleRoot()
{
esp8266_server.send(200, "text/plain", inputString); // 状态码 头报文 正文
}
7.编写404处理函数,当请求不存在的地址时,自动调用该函数
void handleNotFound()
{ // 当浏览器请求的网络资源无法在服务器找到时,
esp8266_server.send(404, "text/plain", "404: Not found"); // NodeMCU将调用此函数。
}
8.编写串口事件处理函数
void serialEvent()
{
while (Serial.available() > 0)
{
// get the new byte:
char inChar = Serial.read();
// add it to the inputString:
inputString += inChar;
// if the incoming character is a newline, set a flag
// so the main loop can do something about it:
if (inChar == '\n')
{
stringComplete = true;
}
}
}
三、上位机的编写
在这里上位机是作为客户端,ESP8266是作为服务器的,因此要先连接ESP8266的热点,然后通过python的requests库,请求IP地址的根地址,即可获得需要的字符串。
1.简单上位机的实现
这里为了方便,ESP01S没有连接GD32,因此初始化字符串为QRCode data
String inputString = "QRCode data"
编译烧录运行,串口输出如下
可知IP地址为192.168.4.1
编写上位机,代码如下
import requests
import time
url="http://192.168.4.1:80"
while True:
response=requests.get(url=url)
print(response.text)
time.sleep(0.5)
电脑连接ESP8266的热点后,运行该py文件,输出结果如下
说明传输成功,至此,我们传输的基本逻辑已经实现,现在只需要使用PyQt5编写UI界面即可
2.用PyQt5实现上位机
(1)绘制UI界面
1.命令行输入designer.exe
打开UI编辑器,新建Widget部件,如图
2.设置UI界面,如图
3.保存UI文件到工程文件夹,然后输入如下命令生成py文件
pyuic5.exe .\main_ui.ui -o .\main_ui.py
(2)编写处理函数
新建main.py
文件,作为主函数
新建一个类MainWindow
,继承于QWidget
和Ui_Form
,其中self.setupUi(self)
是刚刚生成的mian_ui.py
文件中Ui_Form
类的方法,用来绘制UI界面
class MainWindow(QWidget,Ui_Form):
def __init__(self,parent=None):
super(MainWindow,self).__init__(parent)
self.setupUi(self)
self.initUi()
self.initUi()
的定义如下
def initUi(self):
self.track_state=False
self.connect_state=False
self.track_button.clicked.connect(self.track_button_operation)
self.connect_button.clicked.connect(self.connect_button_operation)
self.thread = TextOutputThread()
self.thread.newText.connect(self.updateText)
self.thread.start()
# 创建一个定时器,每隔一定时间触发一次timeout信号
self.timer = QTimer(self)
self.timer2=QTimer(self)
self.timer2.timeout.connect(self.refresh)
self.timer.timeout.connect(self.updateData)
# 设置定时器触发间隔为1000毫秒(1秒)
self.timer.start(1000)
在该初始化方法中,定义了一个用来说明是否正在接收数据的状态机,和一个是否连接的状态机,并将两个按钮的点击信号分别连接到相应的槽函数,其中TextOutputThread
是我们自定义的类,它的定义如下
class TextOutputThread(QThread):
newText = pyqtSignal(str)
def __init__(self, parent=None):
super(TextOutputThread, self).__init__(parent)
它继承自QThread
。然后在主窗口类MainWindow
中,我们连接了newText
信号到updateText
槽函数,每当接收到新文本时,就会调用updateText
方法将文本追加到QTextBrowser中。updateText
方法定义如下
def updateText(self, newText):
self.textBrowser.append(newText)
同时在self.initUi()
中,我们还定义了两个定时器,第一个是用来定时刷新页面数据,第二个是定时向服务器发请求来更新数据。
track_button
的槽函数track_button_operation
定义如下
def track_button_operation(self):
if self.track_state:
self.track_state=False
self.track_button.setText('开始收集')
else:
self.track_state=True
self.track_button.setText('停止收集')
在点击按钮时,可以开始或停止收集
connect_button
的槽函数connect_button_operation
定义如下
def connect_button_operation(self):
self.get_data()
if self.connect_state:
self.thread.newText.emit("已成功连接!")
else:
self.thread.newText.emit("连接失败!")
其中get_data()
定义如下
def get_data(self):
url="http://192.168.4.1:80"
try:
response=requests.get(url=url,timeout=0.5)
if response.status_code==200:
self.connect_state=True
if self.track_state:
self.thread.newText.emit(response.text)
else:
self.connect_state=False
except requests.Timeout:
self.connect_state=False
每次点击都尝试向服务器发请求,如果得到响应,说明连接成功
两个定时器的槽函数分别如下
定时器1
def updateData(self):
if self.connect_state:
if not self.timer2.isActive():
self.timer2.start(200)
self.lineEdit.setText("已连接二维码扫描器")
else:
if self.timer2.isActive():
self.timer2.stop()
self.lineEdit.setText("未连接二维码扫描器")
if self.track_state:
self.lineEdit_2.setText("正在收集返回数据")
else:
self.lineEdit_2.setText("未在收集返回数据")
定时器2
def refresh(self):
self.get_data()
(3)使用pyinstaller进行程序打包
1.安装pyinstaller
pip install pyinstaller
2.常用参数介绍
常用参数 含义
-i 或 -icon 生成icon
-F 创建一个绑定的可执行文件
-w 使用窗口,无控制台
-C 使用控制台,无窗口
-D 创建一个包含可执行文件的单文件夹包(默认情况下)
-n 文件名
3.输入以下命令进行打包
pyinstaller -F -w main.py -p main_ui.py
4.打包后的结果如图
(4)进行测试
结果如图
本次代码在我的二维码识别项目的qt_and_esp01s分支下,地址:https://github.com/liangbm3/GD32_QR_Decoder/releases/tag/QT_and_ESP01S
如果觉得文章有帮助,请点赞收藏关注