06.ESP8266作为web服务器,用PyQt5编写上位机进行通讯

  • 此系列文章源于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,继承于QWidgetUi_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

如果觉得文章有帮助,请点赞收藏关注

  • 33
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
.action_dim * self.num_agents)) critic_loss = F.mse_loss(q_expected, q_target) self.critic_optimizer.zero_grad() critic_loss.backward() nn.utils.clip_grad_norm_(self.critic_local.parameters(), 1) self.critic_optimizer.step好的,这是一个很有趣的问题。在使用PyQt5开发上位机应用程序时,可以使用() # 更新 Actor 网络 actions_pred = [] for i in range(self.num_agents): actions_pred.append(self.actorPython的Socket模块与ESP32进行无线通信。具体步骤如下: 1. ESP32需要连接到一个Wi-Fi网络,这可以通过编写适当的代码来实现。 2. 在PyQt5应用程序中,使用_local[i](states[:, i])) actions_pred = torch.cat(actions_pred, dim=1) actor_loss = -self.critic_localSocket模块创建一个TCP客户端套接字。可以使用ESP32的IP地址和端口号作为连接参数。(states.view(-1, self.state_dim * self.num_agents), actions_pred).mean() self.actor_optimizer[0].zero_grad() 例如: ``` import socket HOST = '192.168.1.100' # ESP32的IP地址 PORT = actor_loss.backward() nn.utils.clip_grad_norm_(self.actor_local[0].parameters(), 1) self.actor_optimizer[05000 # ESP32使用的端口号 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((HOST,].step() # 软更新 Critic 和 Actor 网络 self.soft_update(self.critic_local, self.critic_target, PORT)) ``` 3. 定义一个函数来发送数据到ESP32。这可以通过调用Socket对象的`send()`方法 TAU) self.soft_update(self.actor_local, self.actor_target, TAU) def soft_update(self, local_model, target来实现。例如: ``` def send_data(data): s.send(data.encode()) ``` 4. 同样定义一个函数来_model, tau): for local_param, target_param in zip(local_model.parameters(), target_model.parameters()): target_param.data.copy_(tau接收ESP32发送的数据。可以使用Socket对象的`recv()`方法来实现。例如: ``` def receive_data(): * local_param.data + (1 - tau) * target_param.data) ``` 最后,需要定义训练函数和测试函数: data = s.recv(1024) return data.decode() ``` 5. 在PyQt5应用程序中,可以使用```python def train(n_episodes=2000, max_t=1000, print_every=100): scores_deque = deque(max信号槽机制来处理发送和接收数据的操作。例如: ``` from PyQt5.QtCore import QObject, pyqtlen=print_every) scores = [] for i_episode in range(1, n_episodes + 1): env_info = envSignal class Communication(QObject): data_received = pyqtSignal(str) def __init__(self, parent=None): super().__init.reset(train_mode=True)[brain_name] state = env_info.vector_observations agent.noise.reset() score = np.zeros__(parent) def send_data(self, data): s.send(data.encode()) def receive_data(self): data = s.recv((num_agents) for t in range(max_t): action = agent.act(state) env_info = env.step(action)[brain_name1024) self.data_received.emit(data.decode()) ``` 这里我们定义了一个名为Communication的QObject子类,其中包] next_state = env_info.vector_observations reward = env_info.rewards done = env_info.local_done 含了两个函数send_data()和receive_data(),以及一个data_received信号。当ESP32发送数据到上位 agent.step(state, action, reward, next_state, done) state = next_state score += reward if np.any机时,我们可以通过data_received信号触发槽函数来处理接收到的数据。 以上就是一个简单的(done): break scores_deque.append(np.max(score)) scores.append(np.max(score)) print('\rEpisode {}\tAverage Score: {:.2f}'.format(i_episode, np.mean(scores_deque)), end="") if i_episode % print_every == 0PyQt5上位机与ESP32进行无线通信的步骤,希望能对你有所帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值