这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 嵌入式开发 » STM32 » 【STM32WBA55CG开发板】DIY蓝牙控制取暖器——1、基于Pythonb

共4条 1/1 1 跳转至

【STM32WBA55CG开发板】DIY蓝牙控制取暖器——1、基于Pythonbleak的蓝牙桌面温度控制设计

助工
2024-11-11 10:55:02     打赏

【前言】

参与《NUCLEO-WBA55CG开发板》试用中,我是准备DIY一台蓝牙远程控制的取暖器,蓝牙控制可以使用手机的微信小程,也可以使用web控件,我选择了python的bleak库进行设计,结合PyQt5实现GUI的桌面程序。

【界面设计】

使用QtDesigner进行设计,目前想到的功能,主要是展示从WBA55CG上采集到的温湿度进行展示。执行温度的设计,开关机,还有可以扫描到自己的设备,可指定设备进行连接。界面如下:

image.png

【代码设计】

1、首先使用PyUIC把ui文件转化为py文件,转换后实现界面.py文件:

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'wbg55.ui'
#
# Created by: PyQt5 UI code generator 5.15.9
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again.  Do not edit this file unless you know what you are doing.


from PyQt5 import QtCore, QtGui, QtWidgets


class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(800, 600)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.bntConnect = QtWidgets.QPushButton(self.centralwidget)
        self.bntConnect.setGeometry(QtCore.QRect(50, 270, 81, 41))
        font = QtGui.QFont()
        font.setPointSize(16)
        self.bntConnect.setFont(font)
        self.bntConnect.setObjectName("bntConnect")
        self.device_list = QtWidgets.QListWidget(self.centralwidget)
        self.device_list.setGeometry(QtCore.QRect(50, 70, 311, 191))
        self.device_list.setObjectName("device_list")
        self.lcdNumberTemp = QtWidgets.QLCDNumber(self.centralwidget)
        self.lcdNumberTemp.setGeometry(QtCore.QRect(380, 80, 91, 71))
        font = QtGui.QFont()
        font.setPointSize(36)
        self.lcdNumberTemp.setFont(font)
        self.lcdNumberTemp.setObjectName("lcdNumberTemp")
        self.labelLinkState = QtWidgets.QLabel(self.centralwidget)
        self.labelLinkState.setGeometry(QtCore.QRect(40, 370, 291, 31))
        self.labelLinkState.setObjectName("labelLinkState")
        self.pushButtonConnect = QtWidgets.QPushButton(self.centralwidget)
        self.pushButtonConnect.setGeometry(QtCore.QRect(50, 40, 311, 31))
        font = QtGui.QFont()
        font.setPointSize(12)
        self.pushButtonConnect.setFont(font)
        self.pushButtonConnect.setObjectName("pushButtonConnect")
        self.label_2 = QtWidgets.QLabel(self.centralwidget)
        self.label_2.setGeometry(QtCore.QRect(381, 40, 91, 31))
        font = QtGui.QFont()
        font.setPointSize(16)
        self.label_2.setFont(font)
        self.label_2.setObjectName("label_2")
        self.label_3 = QtWidgets.QLabel(self.centralwidget)
        self.label_3.setGeometry(QtCore.QRect(530, 40, 81, 31))
        font = QtGui.QFont()
        font.setPointSize(16)
        self.label_3.setFont(font)
        self.label_3.setObjectName("label_3")
        self.bntADD = QtWidgets.QPushButton(self.centralwidget)
        self.bntADD.setGeometry(QtCore.QRect(530, 230, 75, 41))
        font = QtGui.QFont()
        font.setPointSize(16)
        self.bntADD.setFont(font)
        self.bntADD.setObjectName("bntADD")
        self.bntDec = QtWidgets.QPushButton(self.centralwidget)
        self.bntDec.setGeometry(QtCore.QRect(530, 290, 75, 41))
        font = QtGui.QFont()
        font.setPointSize(16)
        self.bntDec.setFont(font)
        self.bntDec.setObjectName("bntDec")
        self.lcdNumberSet = QtWidgets.QLCDNumber(self.centralwidget)
        self.lcdNumberSet.setGeometry(QtCore.QRect(400, 240, 121, 91))
        font = QtGui.QFont()
        font.setPointSize(36)
        self.lcdNumberSet.setFont(font)
        self.lcdNumberSet.setObjectName("lcdNumberSet")
        self.label_4 = QtWidgets.QLabel(self.centralwidget)
        self.label_4.setGeometry(QtCore.QRect(410, 180, 151, 31))
        font = QtGui.QFont()
        font.setPointSize(16)
        self.label_4.setFont(font)
        self.label_4.setObjectName("label_4")
        self.bntOnOFF = QtWidgets.QPushButton(self.centralwidget)
        self.bntOnOFF.setGeometry(QtCore.QRect(190, 280, 91, 41))
        font = QtGui.QFont()
        font.setPointSize(16)
        self.bntOnOFF.setFont(font)
        self.bntOnOFF.setObjectName("bntOnOFF")
        self.labelData = QtWidgets.QLabel(self.centralwidget)
        self.labelData.setGeometry(QtCore.QRect(40, 320, 401, 31))
        self.labelData.setObjectName("labelData")
        self.lcdNumberHum = QtWidgets.QLCDNumber(self.centralwidget)
        self.lcdNumberHum.setGeometry(QtCore.QRect(520, 80, 81, 71))
        font = QtGui.QFont()
        font.setPointSize(36)
        self.lcdNumberHum.setFont(font)
        self.lcdNumberHum.setObjectName("lcdNumberHum")
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 22))
        self.menubar.setObjectName("menubar")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.bntConnect.setText(_translate("MainWindow", "连接"))
        self.labelLinkState.setText(_translate("MainWindow", "连接状态:"))
        self.pushButtonConnect.setText(_translate("MainWindow", "扫描蓝牙设备"))
        self.label_2.setText(_translate("MainWindow", "室温"))
        self.label_3.setText(_translate("MainWindow", "湿度"))
        self.bntADD.setText(_translate("MainWindow", "增大"))
        self.bntDec.setText(_translate("MainWindow", "减小"))
        self.label_4.setText(_translate("MainWindow", "温度设定"))
        self.bntOnOFF.setText(_translate("MainWindow", "关机"))
        self.labelData.setText(_translate("MainWindow", "接收到数据:"))

2、在main.py中,我们首先要安排PyQt5、并安装bleak蓝牙库:

image.png


具体的安装方法,大家可以自行百度。

3、主要代码介绍:

第一是蓝牙扫描,我当创建了一个类BluetoothScanner用于扫描蓝牙设备,扫描结束后,使用pyqtSignal异步发送给主函数,用于界面显示。

class BluetoothScanner(QThread):
    device_found_signal = pyqtSignal(dict)

    def __init__(self, parent=None):
        super().__init__(parent)

    def run(self):
        asyncio.run(self.scan())

    async def scan(self):
        devices = await BleakScanner.discover()
        for device in devices:
            self.device_found_signal.emit({'name': device.name, 'address': device.address})
            print({'name': device.name, 'address': device.address})

第二是,处理与蓝牙设备的连接、数据接收与发送:其代码如下:

class BluetoothReceiver(QThread):
    data_received_signal = pyqtSignal(dict)
    connection_status_signal = pyqtSignal(str)
    def __init__(self, device_address):
        super().__init__()
        print("init")
        self.device_address = device_address
        self.client = None
        self.receive_task = None
        self.connected = False
        print("init end address:" + str(self.device_address))

    def run(self):
        print("recive run")
        asyncio.run(self.connect())


    async def connect(self):
        try:
            # 基于MAC地址查找设备
            device = await BleakScanner.find_device_by_address(
                self.device_address, cb=dict(use_bdaddr=False)  # use_bdaddr判断是否是MOC系统
            )
            if device is None:
                print("could not find device with address '%s'",self.device_address)
                return
                # 事件定义

            self.client = BleakClient(self.device_address,disconnected_callback=self.disconnect)
            await self.client.connect()
            await self.client.start_notify(par_notification_characteristic,self.receive_data)
            self.connected = True
            self.connection_status_signal.emit("已连接")
            while True:
                await self.client.write_gatt_char(par_write_characteristic, send_str)
                await asyncio.sleep(1.0)  # 每休眠1秒发送一次
                await self.client.write_gatt_char(par_write_characteristic, send_str1)
                await asyncio.sleep(1.0)
            # self.receive_task = asyncio.create_task(self.receive_data())
        except Exception as e:
            self.connection_status_signal.emit(f"连接失败: {e}")
    async def receive_data(self,characteristic, data):
        try:
            Temp = data[0]*10 + data[1]
            Hum = data[2]*10 + data[3]
            self.data_received_signal.emit({'Temp': Temp, 'Hum': Hum})
        except Exception as e:
            self.connection_status_signal.emit(f"接收数据出错: {e}")

    async def disconnect(self):
        if self.client:
            await self.client.disconnect()
            self.connected = False
            self.connection_status_signal.emit("已断开连接")

在此个类中,首先基于Mac地址来查找设备,查找到后使用start_notfy来创建异步连接,实现对服务的数据异步接收的recive_data回调函数的注册,如果接收到数据进行数据组装通过emit发送给GUI进行数据展示。

同时每隔1秒向服务端发送LED的开关指令,确保长连接。

3、GUI主程序:

class MyMainForm(QMainWindow, Ui_MainWindow):

    def __init__(self, parent=None):
        super(MyMainForm, self).__init__(parent)
        self.setupUi(self)
        self.pushButtonConnect.clicked.connect(self.start_async_task)
        self.bntConnect.clicked.connect(self.connect_device)

    def start_async_task(self):
        self.device_list.clear()
        selected_item = self.device_list.currentItem()
        self.labelLinkState.setText("正在执行扫描...")
        async_thread = BluetoothScanner(self)
        async_thread.finished.connect(self.async_task_finished)
        async_thread.device_found_signal.connect(self.add_device_to_list)
        async_thread.start()
    def async_task_finished(self):
        self.labelLinkState.setText("扫描完成")

    def add_device_to_list(self, device_info):
        self.device_list.addItem(f"{device_info['name']} - {device_info['address']}")

    def connect_device(self):
        selected_item = self.device_list.currentItem()
        if selected_item:
            device_address = selected_item.text().split(' - ')[1]
            self.thread1 = BluetoothReceiver(device_address)
            self.thread1.finished.connect(self.async_task_finished)
            self.thread1.connection_status_signal.connect(self.update_status_label)
            self.thread1.data_received_signal.connect(self.update_data_label)
            self.thread1.start()
            print(5)

    def update_data_label(self, data):
        try:
            self.labelData.setText(f"接收数据: {data}")
            self.lcdNumberTemp.display(data['Temp'])
            self.lcdNumberHum.display(data['Hum'])
        except Exception as e:
            print("接收数据解释错误:", str(e))
    def update_status_label(self, status):
        self.labelLinkState.setText(f"状态: {status}")


if __name__ == "__main__":
    # 固定的,PyQt5程序都需要QApplication对象。sys.argv是命令行参数列表,确保程序可以双击运行
    app = QApplication(sys.argv)
    # 初始化
    myWin = MyMainForm()
    # 将窗口控件显示在屏幕上
    myWin.show()
    # 程序运行,sys.exit方法确保程序完整退出。
    sys.exit(app.exec_())

在GUI类中,对应按键件事,创建对应的任务,并注册信号接收回调函数,对数据进行解析、展示。

【实现效果】

image.png

运行桌面程序后,首先点击扫描,我们的P2S服务会出现在列表框中,选中该行后按连接,在下面的状态提示中会提示连接,室温、湿度的LCD会实现显示开发板的温湿度,同时开发板也会接收到界面发出的指令,第隔一秒闪烁一次。

【总结】

PyQt5+bleak库,可以快速的创建桌面蓝牙应用程序,相关学习资料好找,python也提供强大的跨平台支持。




关键词: STM32WBA55CG          温度控制     Python    

专家
2024-11-11 13:56:28     打赏
2楼

学习下Qt编程


院士
2024-11-13 06:14:05     打赏
3楼

谢谢楼主分享~!


高工
2024-11-13 12:39:32     打赏
4楼

谢谢分享


共4条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]