精通实时数据:用PySide6、QML和PySerial打造ESP32可视化通信QT界面📊

本喵又来写不一样的精品小教程啦

目录

安装Python和相关库 🐍

在我们开始之前,本喵默认你已经安装了python,安装pyserial指令如下~终端输入

pip install pyserial
pip install PySide6

安装我们需要的库

QML简介 🌟

QML是图形编辑用的语言,相当于将Qt编程变成html那么简单的格式,最后将qml文件由pyside库调用,咱们酷酷的界面就出来啦~

设置ESP32开发环境 🔧

在本喵的教程:
🌟【一站式教程】精通ESP32:使用VSCode与PlatformIO构建FreeRTOS项目、WIFI网页智能灯控系统、蓝牙、有趣的小功能 — 从轻松入门到项目实战
中写的很详细啦~

PySerial初探 🔍

PySerial提供了与串口设备通讯的接口。它允许您在Python脚本中打开、读取、写入和关闭串口。

从ESP32读取数据 📈

接下来,我们将编写一个简单的Python脚本来从ESP32通过串口读取数据。请确保您的ESP32已通过USB连接到电脑,并且您知道它的串口名称(例如,Windows上可能是COM3,Linux或Mac上可能是/dev/ttyUSB0)。

创建一个名为read_serial.py的新Python文件,并添加以下代码:

import serial
import time

# 替换以下串口名称与ESP32连接的实际串口名称
# 例如,Windows上可能是 COM3, Linux或Mac上可能是 /dev/ttyUSB0
SERIAL_PORT = 'COM3'
BAUD_RATE = 115200  # 根据ESP32程序设置的波特率调整

def main():
    # 初始化串口连接
    ser = serial.Serial(SERIAL_PORT, BAUD_RATE, timeout=1)
    time.sleep(2)  # 等待串口初始化

    try:
        print("开始从ESP32读取数据...")
        while True:
            if ser.in_waiting > 0:
                # 从串口读取一行数据
                line = ser.readline().decode('utf-8').rstrip()
                print(f"接收到数据: {line}")
    except KeyboardInterrupt:
        print("程序被手动中断")
    finally:
        ser.close()  # 确保串口连接被正确关闭
        print("串口连接已关闭")

if __name__ == "__main__":
    main()

运行脚本:

python read_serial.py

运行结果:
在这里插入图片描述

为了完成“构建数据可视化界面”这一部分的内容,我们将创建一个使用PySide6和QML的示例,以实现从ESP32通过串口接收的数据的动态可视化。

##构建数据可视化界面 📊

文件结构 🖌️

在这里插入图片描述
本喵的文件结构如上,创建这个

集成QML与Python 🧩

接下来,我们将使用PySide6在Python脚本中加载这个QML界面,并设置一个简单的框架来从串口读取数据并更新界面。

main.py:

import sys
from PySide6.QtCore import QCoreApplication, QObject, QUrl, Signal, Slot
from PySide6.QtGui import QGuiApplication
from PySide6.QtQml import QQmlApplicationEngine
import serial
import threading

# 初始化串口连接
SERIAL_PORT = 'COM3'  # 更改为您的ESP32串口号
BAUD_RATE = 9600
ser = serial.Serial(SERIAL_PORT, BAUD_RATE, timeout=0.1)

class Backend(QObject):
    dataReceived = Signal(str)  # 创建一个信号

    def __init__(self):
        super().__init__()
        self.serial_thread = threading.Thread(target=self.read_from_port)
        self.serial_thread.start()

    def read_from_port(self):
        while True:
            if ser.in_waiting > 0:
                data = ser.readline().decode('utf-8').strip()
                self.dataReceived.emit(data)  # 发送信号

if __name__ == "__main__":
    app = QGuiApplication(sys.argv)
    engine = QQmlApplicationEngine()

    backend = Backend()
    engine.rootContext().setContextProperty("backend", backend)

    engine.load(QUrl("main.qml"))

    if not engine.rootObjects():
        sys.exit(-1)

    sys.exit(app.exec())

main.qml:

import QtQuick 6.5
import QtQuick.Controls 6.5
import Qtcharts
ApplicationWindow {
    title: "ESP32 数据可视化"
    width: 600
    height: 400
    visible: true

    ChartView {
        id: chart
        title: "实时数据"
        anchors.fill: parent
        antialiasing: true

        LineSeries {
            id: dataSeries
            name: "传感器数据"
        }
    }

    Connections {
        target: backend
        onDataReceived: function(data) {  
            var newData = JSON.parse(data); // 假设数据以JSON格式传入,需要解析
            dataSeries.append(newData.timestamp, newData.value); // 假设数据有时间戳和值
        }
    }
}

上述Python代码创建了一个后端类,该类在一个单独的线程中不断读取串口数据。当从ESP32接收到数据时,通过信号dataReceived将数据发送出去。

QML文件中,Connections组件用于连接我们的后端(即Python脚本中的Backend类)和QML界面。每当dataReceived信号被触发时,onDataReceived处理函数就会更新文本控件中的文本,显示从串口接收到的最新数据。

动态展示数据 🌈

运行效果~

在这里插入图片描述

动态展示图表📈在这里插入图片描述

先上效果~~~
将main.py和main.qml更新为以下样式
main.qml:

import QtQuick 6.5
import QtQuick.Controls 6.5
import QtCharts 2.15

ApplicationWindow {
    visible: true
    width: 640
    height: 480
    title: "ESP32 随机数据可视化"

    ChartView {
        id: chart
        anchors.fill: parent
        antialiasing: true

        DateTimeAxis {
            id: axisX
            format: "hh:mm:ss"
            tickCount: 5 // 根据需要调整
            min: new Date()
            max: new Date(new Date().getTime() + 20 * 1000) // 设置为当前时间加20秒
        }

        ValueAxis {
            id: axisY
            min: 0
            max: 100
        }

        LineSeries {
            id: lineSeries
            name: "实时数据"
            axisX: axisX
            axisY: axisY
        }
    }

    Timer {
        interval: 1000 // 1秒
        repeat: true
        running: true
        onTriggered: {
            let now = new Date();
            lineSeries.append(now.getTime(), data); // 使用当前时间戳作为X轴
            if (lineSeries.count > 20) { // 保持最近20个数据点
                let firstItemTime = lineSeries.at(0).x; // 获取第一个数据点的时间
                axisX.min = new Date(now.getTime() - 20 * 1000);
                axisX.max = now; // 确保当前点在范围内
                lineSeries.remove(0); // 移除最旧的数据点以保持图表清晰
            }
        }
    }
    // 这里假设'backend'是已经定义并能够接收数据的组件
    // 如果需要从外部设备接收数据,您需要定义这个组件并确保它可以触发'onDataReceived'事件
    Connections {
        target: backend
        onDataReceived: function(data) {
            console.log("接收到的数据: " + data);
            lineSeries.append(new Date().getTime(), parseInt(data));
            // 这里不需要手动调整X轴,因为已经在Timer中处理
        }
    }
}

main.py:

import sys
import serial
import threading
from PySide6.QtCore import QObject, Signal
from PySide6.QtWidgets import QApplication
from PySide6.QtQml import QQmlApplicationEngine

SERIAL_PORT = 'COM14'  # 更改为您的ESP32串口号
BAUD_RATE = 115200

class Backend(QObject):
    dataReceived = Signal(int)  
    
    def __init__(self, serial_port, baud_rate):
        super().__init__()
        self.ser = serial.Serial(serial_port, baud_rate, timeout=1)
        self.serial_thread = threading.Thread(target=self.read_from_port)
        self.serial_thread.daemon = True
        self.serial_thread.start()
    def read_from_port(self):
        while True:
            if self.ser.in_waiting > 0:
                data = self.ser.readline().decode('utf-8').strip()
                if data.isdigit():
                    self.dataReceived.emit(int(data))

if __name__ == "__main__":
    app = QApplication(sys.argv)
    engine = QQmlApplicationEngine()
    backend = Backend(SERIAL_PORT, BAUD_RATE)
    engine.rootContext().setContextProperty("backend", backend)
    engine.load("main.qml")
    if not engine.rootObjects():
        sys.exit(-1)
    sys.exit(app.exec())
Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐