Qt開發(fā)上位機(jī)建立BLE通訊
最近在做一個(gè)具有低功耗藍(lán)牙
BLE
通訊功能的Windows上位機(jī)軟件,在網(wǎng)上學(xué)習(xí)了許多BLE
相關(guān)的知識(shí)、看了許多相關(guān)博客并參考了官方例程后總結(jié)出了使用Qt建立BLE
通訊的步驟,附帶相關(guān)源碼,分享給網(wǎng)友
開發(fā)環(huán)境
我使用的Qt
版本是5.15,使用的CMake
構(gòu)建項(xiàng)目。
整體開發(fā)使用的IDE
是Qt Creator
,采用的方式是基于widgets
的ui
設(shè)計(jì)界面、C++寫邏輯的方式。
編譯使用的是Desktop Qt 5.15.2 MINGW 64-bit
CMake配置
BLE
低功耗藍(lán)牙通訊需要用到Qt
的藍(lán)牙模塊,需要添加Bluetooth
模塊:
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core Widgets Bluetooth)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core Widgets Bluetooth)
在add_executable
之后設(shè)置target_link_libraries
:
target_link_libraries(bluetooth_serial_host_computer PRIVATE
Qt${QT_VERSION_MAJOR}::Core
Qt${QT_VERSION_MAJOR}::Widgets
Qt${QT_VERSION_MAJOR}::Bluetooth)
包含頭文件
#include <QBluetoothDeviceDiscoveryAgent> //發(fā)現(xiàn)設(shè)備
#include <QBluetoothUuid> //藍(lán)牙uuid
#include <QBluetoothDeviceInfo> //設(shè)備信息
#include <QLowEnergyController> //ble controller
#include <QLowEnergyDescriptor> //ble 描述符
#include <QLowEnergyService> //ble 服務(wù)
#include <QLowEnergyCharacteristic> //ble特性
建立BLE通訊的步驟
建立BLE
通訊的大體步驟可參考下圖:
接下來(lái),我們將每個(gè)步驟進(jìn)行詳細(xì)講解。
搜索藍(lán)牙設(shè)備Device并連接
搜索藍(lán)牙設(shè)備用到了BluetoothDeviceDiscoveryAgent
,我們先在構(gòu)造函數(shù)中創(chuàng)建對(duì)象并連接上信號(hào)與槽:
m_deviceDiscoveryAgentPtr = new QBluetoothDeviceDiscoveryAgent(this); //創(chuàng)建對(duì)象
connect(m_deviceDiscoveryAgentPtr, &QBluetoothDeviceDiscoveryAgent::deviceDiscovered, this, &MainWindow::deviceDiscoveredSlot); //發(fā)現(xiàn)了一個(gè)設(shè)備
void (QBluetoothDeviceDiscoveryAgent:: *deviceDiscoveryErrorOccurred)(QBluetoothDeviceDiscoveryAgent::Error) = &QBluetoothDeviceDiscoveryAgent::error;//有重載
connect(m_deviceDiscoveryAgentPtr, deviceDiscoveryErrorOccurred, this, &MainWindow::deviceDiscoveryErrorOccurredSlot); //設(shè)備發(fā)現(xiàn)出現(xiàn)錯(cuò)誤
connect(m_deviceDiscoveryAgentPtr, &QBluetoothDeviceDiscoveryAgent::finished, this, &MainWindow::deviceDiscoveryFinishedSlot); //設(shè)備發(fā)現(xiàn)結(jié)束
開始搜索BLE
設(shè)備:
void MainWindow::on_searchButton_clicked() //點(diǎn)擊搜索按鈕
{
//設(shè)備列表中只保存本次搜索得到的設(shè)備信息;每次搜索開始時(shí)先清空列表,再在槽函數(shù)中添加本次搜索到的設(shè)備信息
m_devInfoList.clear();
//如果已經(jīng)搜索過一次,列表中item數(shù)量就可能大于1;再次搜索需要先清空列表,將標(biāo)題重新添加進(jìn)去
if(ui->devInfoListWidget->count() > 1)
{
ui->devInfoListWidget->clear(); //先清空列表
//再重新添加標(biāo)題
QString devLabel = QString("地址 設(shè)備名稱");
QListWidgetItem* devItemPtr = new QListWidgetItem(devLabel);
ui->devInfoListWidget->addItem(devItemPtr);
}
m_deviceDiscoveryAgentPtr->start(QBluetoothDeviceDiscoveryAgent::LowEnergyMethod); //以LowEnergyMethod進(jìn)行搜索,搜索低功耗藍(lán)牙
//statusBar顯示提示信息.
ui->statusbar->showMessage("設(shè)備搜索中......"); //timeout為默認(rèn)值 0 時(shí),信息一直顯示,直到被覆蓋
ui->searchButton->setEnabled(false);//搜索過程中,search按鈕不可點(diǎn)擊
}
devInfoListWidget
是我自己在ui
中添加的一個(gè)列表組件,用來(lái)顯示搜索到的設(shè)備信息:
發(fā)現(xiàn)一個(gè)設(shè)備的槽函數(shù):
void MainWindow::deviceDiscoveredSlot(const QBluetoothDeviceInfo &devInfo)
{
//名稱不為空且是低功耗藍(lán)牙,則考慮加進(jìn)去
if(devInfo.coreConfigurations() & QBluetoothDeviceInfo::LowEnergyCoreConfiguration)
{
//將搜索到的設(shè)備的address與name存到一個(gè)label中
QString label = QString("%1 %2").arg(devInfo.address().toString(), devInfo.name());
//在listwidget中搜索是否已經(jīng)有這個(gè)設(shè)備
QList<QListWidgetItem *> itemPtrList = ui->devInfoListWidget->findItems(label, Qt::MatchExactly);
//防止重復(fù)
if (itemPtrList.empty()) //如果listwidget中沒有搜索到的這個(gè)設(shè)備
{
QListWidgetItem* itemPtr = new QListWidgetItem(label);
ui->devInfoListWidget->addItem(itemPtr);//將設(shè)備的信息添加到listwidget中
m_devInfoList.append(devInfo); //設(shè)備信息添加到自己的列表中
}
}
}
設(shè)備發(fā)現(xiàn)出現(xiàn)錯(cuò)誤的槽函數(shù):
void MainWindow::deviceDiscoveryErrorOccurredSlot(QBluetoothDeviceDiscoveryAgent::Error error)
{
QMessageBox::warning(this, "警告", "搜索藍(lán)牙設(shè)備發(fā)生錯(cuò)誤,請(qǐng)檢查藍(lán)牙是否開啟!");
}
設(shè)備發(fā)現(xiàn)結(jié)束的槽函數(shù):
void MainWindow::deviceDiscoveryFinishedSlot()
{
//statusBar顯示提示信息
ui->statusbar->showMessage("設(shè)備搜索完成,請(qǐng)雙擊設(shè)備進(jìn)行連接!");
ui->searchButton->setEnabled(true);//搜索完成后,可以再次點(diǎn)擊搜索按鈕
}
接下來(lái)雙擊設(shè)備列表中的設(shè)備進(jìn)行連接:
void MainWindow::on_devInfoListWidget_itemDoubleClicked(QListWidgetItem *itemPtr)
{
//設(shè)備被雙擊后需要先清空Uuid List,只保存當(dāng)前選中設(shè)備的服務(wù)Uuid
m_uuidList.clear();
//如果service List中已經(jīng)有了搜索出的服務(wù)
if(ui->serviceInfoListWidget->count() > 1)
{
ui->serviceInfoListWidget->clear(); //先清空列表
//再重新添加標(biāo)題
QString serviceLabel = QString("服務(wù)Uuid");
QListWidgetItem* serviceItemPtr = new QListWidgetItem(serviceLabel);
ui->serviceInfoListWidget->addItem(serviceItemPtr);
}
//創(chuàng)建藍(lán)牙控制器; currentRow()-1是因?yàn)樽约菏謩?dòng)添加了一行標(biāo)題
m_bleControllerPtr = QLowEnergyController::createCentral(m_devInfoList.at(ui->devInfoListWidget->currentRow() - 1)); //central相當(dāng)于是主機(jī)
//bleController的槽函數(shù)
connect(m_bleControllerPtr, &QLowEnergyController::connected, this, &MainWindow::bleDeviceConnectedSlot); //設(shè)備連接成功
void (QLowEnergyController:: *bleDeviceConnectionErrorOccurred)(QLowEnergyController::Error) = &QLowEnergyController::error;//有重載
connect(m_bleControllerPtr, bleDeviceConnectionErrorOccurred, this, &MainWindow::bleDeviceConnectionErrorOccurredSlot); //設(shè)備連接出現(xiàn)錯(cuò)誤
connect(m_bleControllerPtr, &QLowEnergyController::serviceDiscovered, this, &MainWindow::bleServiceDiscoveredSlot); //發(fā)現(xiàn)一個(gè)服務(wù)
connect(m_bleControllerPtr, &QLowEnergyController::discoveryFinished, this, &MainWindow::bleServiceDiscoveryFinishedSlot); //服務(wù)發(fā)現(xiàn)結(jié)束
//創(chuàng)建后控制器中對(duì)應(yīng)的設(shè)備就是我們?cè)诹斜碇羞x中的設(shè)備
ui->statusbar->showMessage("正在連接設(shè)備......");
m_bleControllerPtr->connectToDevice(); //連接設(shè)備
}
設(shè)備連接出現(xiàn)錯(cuò)誤的槽函數(shù):
void MainWindow::bleDeviceConnectionErrorOccurredSlot(QLowEnergyController::Error error)
{
QMessageBox::warning(this, "警告", "連接低功耗藍(lán)牙設(shè)備發(fā)生錯(cuò)誤!");
}
搜索藍(lán)牙設(shè)備并連接的大致步驟如上。
搜索設(shè)備Device的服務(wù)Service
在設(shè)備連接成功的槽函數(shù)中搜索服務(wù):
void MainWindow::bleDeviceConnectedSlot()
{
ui->statusbar->showMessage("低功耗藍(lán)牙設(shè)備連接成功!");
m_bleControllerPtr->discoverServices(); //開始搜索服務(wù)
}
發(fā)現(xiàn)一個(gè)服務(wù)的槽函數(shù):
void MainWindow::bleServiceDiscoveredSlot(QBluetoothUuid serviceUuid)
{
QLowEnergyService* servicePtr = m_bleControllerPtr->createServiceObject(serviceUuid); //創(chuàng)建服務(wù)對(duì)象
//將搜索到的服務(wù)的Uuid存到一個(gè)label中
QString label = QString("%1").arg(serviceUuid.toString());
//在listwidget中搜索是否已經(jīng)有這個(gè)服務(wù)
QList<QListWidgetItem *> itemPtrList = ui->serviceInfoListWidget->findItems(label, Qt::MatchExactly);
//防止重復(fù)
if (itemPtrList.empty()) //如果listwidget中沒有搜索到的這個(gè)服務(wù)
{
QListWidgetItem* itemPtr = new QListWidgetItem(label);
ui->serviceInfoListWidget->addItem(itemPtr);//將設(shè)備的信息添加到listwidget中
m_uuidList.append(serviceUuid); //設(shè)備信息添加到自己的列表中
}
}
serviceInfoListWidget是我自己在ui中添加的一個(gè)列表組件,用來(lái)顯示服務(wù)的uuid:
服務(wù)搜索結(jié)束的槽函數(shù):
void MainWindow::bleServiceDiscoveryFinishedSlot()
{
ui->statusbar->showMessage("服務(wù)搜索結(jié)束,請(qǐng)雙擊服務(wù)進(jìn)行監(jiān)聽");
}
選擇服務(wù)的特性characteristic,寫描述符,建立通訊
雙擊服務(wù)列表,進(jìn)行相應(yīng)操作:
void MainWindow::on_serviceInfoListWidget_itemDoubleClicked(QListWidgetItem *itemPtr) //雙擊服務(wù)
{
QBluetoothUuid serviceUuid = m_uuidList.at(ui->serviceInfoListWidget->currentRow() - 1); //當(dāng)前選中的服務(wù)的Uuid
//創(chuàng)建服務(wù)
m_bleServicePtr = m_bleControllerPtr->createServiceObject(QBluetoothUuid(serviceUuid), this);
//判斷創(chuàng)建服務(wù)是否出現(xiàn)錯(cuò)誤
if(m_bleServicePtr == NULL)
{
QMessageBox::warning(this,"警告","創(chuàng)建服務(wù)失敗!");
}
else //創(chuàng)建服務(wù)成功;創(chuàng)建服務(wù)就相當(dāng)于連接上了,執(zhí)行完ServiceStateChangedSlot之后就可以正常通信了
{
//搜索之前先清空,只保存當(dāng)前服務(wù)的特性
m_characteristicSelectionDialog->clearCharacteristic();
//特性選擇對(duì)話框出現(xiàn)
m_characteristicSelectionDialog->show();
//監(jiān)聽服務(wù)狀態(tài)變化
connect(m_bleServicePtr, &QLowEnergyService::stateChanged, this, &MainWindow::bleServiceStateChangedSlot);
//服務(wù)的characteristic變化,有數(shù)據(jù)傳來(lái)
connect(m_bleServicePtr, &QLowEnergyService::characteristicChanged, this, &MainWindow::bleServiceCharacteristicChangedSlot);
//錯(cuò)誤處理
void (QLowEnergyService:: *bleServiceErrorOccurred)(QLowEnergyService::ServiceError) = &QLowEnergyService::error;//有重載
connect(m_bleServicePtr, bleServiceErrorOccurred, this, &MainWindow::bleServiceErrorOccurredSlot);
//描述符成功被寫
connect(m_bleServicePtr, &QLowEnergyService::descriptorWritten, this, &MainWindow::bleServiceDescriptorWrittenSlot);
//觸發(fā)服務(wù)詳情發(fā)現(xiàn)函數(shù)
m_bleServicePtr->discoverDetails();
}
}
服務(wù)狀態(tài)變化的槽函數(shù):
void MainWindow::bleServiceStateChangedSlot(QLowEnergyService::ServiceState state) //服務(wù)狀態(tài)改變
{
//發(fā)現(xiàn)服務(wù)
if(m_bleServicePtr->state() == QLowEnergyService::ServiceDiscovered)
{
QList<QLowEnergyCharacteristic> list = m_bleServicePtr->characteristics();
for(int i = 0; i < list.count(); i++)
{
//當(dāng)前位置的bleCharacteritic
m_bleCharacteristic = list.at(i);
//如果當(dāng)前characteristic有效
if(m_bleCharacteristic.isValid())
{
//將有效特性添加到列表中
m_characteristicSelectionDialog->addCharacteristic(m_bleCharacteristic);
//描述符定義特征如何由特定客戶端配置
QLowEnergyDescriptor descriptor = m_bleCharacteristic.descriptor(QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration);
//如果descriptor有效
if(descriptor.isValid())
{
m_bleServicePtr->writeDescriptor(descriptor, QByteArray::fromHex("FEE1"));
}
}
}
}
}
當(dāng)描述符descriptor
被正確地寫入值之后,BLE
通訊就成功建立。
特性發(fā)生變化的槽函數(shù),處理接收數(shù)據(jù):
void MainWindow::bleServiceCharacteristicChangedSlot(QLowEnergyCharacteristic characteristic, QByteArray value)
{
if(m_isClose == false) //數(shù)據(jù)接收框在打開的狀態(tài),顯示接收的數(shù)據(jù)
{
//直接將這個(gè)ByteArray放進(jìn)去的話中文會(huì)顯示亂碼
QTextCodec* textCodePtr = QTextCodec::codecForName("GBK");
QString str = textCodePtr->toUnicode(value);
ui->btDataRevTextBrowser->insertPlainText(str); //不自動(dòng)換行
}
if(ui->btRealtimeCurveCheckBox->checkState() == Qt::Checked) //勾選實(shí)時(shí)曲線,使能
{
qDebug() << value.toDouble();
ui->btRealtimeCurveWidget->dataReceived(value.toDouble());
}
}
發(fā)送數(shù)據(jù)(發(fā)送數(shù)據(jù)需要使用具有write或者writeNoResponse權(quán)限的特性):
QString text = ui->btSendDataTextEdit->toPlainText(); //當(dāng)前輸入框中的數(shù)據(jù)
if(text.indexOf("\n") != -1)
{
text.replace("\n","\r\n"); //更換為有效回車
}
m_bleServicePtr->writeCharacteristic(m_characteristicSelectionDialog->getWriteCharacteristic(), text.toUtf8(), QLowEnergyService::WriteWithResponse); //發(fā)送數(shù)據(jù)
BLE
服務(wù)發(fā)生錯(cuò)誤的槽函數(shù):文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-472912.html
void MainWindow::bleServiceErrorOccurredSlot(QLowEnergyService::ServiceError error) //低功耗藍(lán)牙服務(wù)產(chǎn)生錯(cuò)誤
{
if(QLowEnergyService::NoError == error)
{
qDebug() <<"沒有發(fā)生錯(cuò)誤。";
}
if(QLowEnergyService::OperationError== error)
{
QMessageBox::warning(this, "錯(cuò)誤", "當(dāng)服務(wù)沒有準(zhǔn)備好時(shí)嘗試進(jìn)行操作!");
}
if(QLowEnergyService::CharacteristicReadError== error)
{
QMessageBox::warning(this, "錯(cuò)誤", "嘗試讀取特征值失敗!");
}
if(QLowEnergyService::CharacteristicWriteError== error)
{
QMessageBox::warning(this, "錯(cuò)誤", "嘗試為特性寫入新值失敗!");
}
if(QLowEnergyService::DescriptorReadError== error)
{
QMessageBox::warning(this, "錯(cuò)誤", "嘗試讀取描述符值失敗!");
}
if(QLowEnergyService::DescriptorWriteError== error)
{
QMessageBox::warning(this, "錯(cuò)誤", "嘗試向描述符寫入新值失敗!");
}
if(QLowEnergyService::UnknownError== error)
{
QMessageBox::warning(this, "錯(cuò)誤", "與服務(wù)交互時(shí)發(fā)生未知錯(cuò)誤!");
}
}
DescriptorReadError== error)
{
QMessageBox::warning(this, "錯(cuò)誤", "嘗試讀取描述符值失敗!");
}
if(QLowEnergyService::DescriptorWriteError== error)
{
QMessageBox::warning(this, "錯(cuò)誤", "嘗試向描述符寫入新值失敗!");
}
if(QLowEnergyService::UnknownError== error)
{
QMessageBox::warning(this, "錯(cuò)誤", "與服務(wù)交互時(shí)發(fā)生未知錯(cuò)誤!");
}
}
以上即為使用Qt建立BLE通訊的基本步驟。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-472912.html
到了這里,關(guān)于Qt開發(fā)上位機(jī)建立BLE通訊的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!