参与《NUCLEO-WBA55CG开发板》试用中,我是准备DIY一台蓝牙远程控制的取暖器,蓝牙控制可以使用手机的微信小程,也可以使用web控件,我选择了python的bleak库进行设计,结合PyQt5实现GUI的桌面程序。
【界面设计】
使用QtDesigner进行设计,目前想到的功能,主要是展示从WBA55CG上采集到的温湿度进行展示。执行温度的设计,开关机,还有可以扫描到自己的设备,可指定设备进行连接。界面如下:
【代码设计】
1、首先使用PyUIC把ui文件转化为py文件,转换后实现界面.py文件:
view plaincopy to clipboardprint?
# -*- 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蓝牙库:
具体的安装方法,大家可以自行百度。
3、主要代码介绍:
第一是蓝牙扫描,我当创建了一个类BluetoothScanner用于扫描蓝牙设备,扫描结束后,使用pyqtSignal异步发送给主函数,用于界面显示。
view plaincopy to clipboardprint?
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})
第二是,处理与蓝牙设备的连接、数据接收与发送:其代码如下:
view plaincopy to clipboardprint?
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主程序:
view plaincopy to clipboardprint?
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类中,对应按键件事,创建对应的任务,并注册信号接收回调函数,对数据进行解析、展示。
【实现效果】
运行桌面程序后,首先点击扫描,我们的P2S服务会出现在列表框中,选中该行后按连接,在下面的状态提示中会提示连接,室温、湿度的LCD会实现显示开发板的温湿度,同时开发板也会接收到界面发出的指令,第隔一秒闪烁一次。