天天看點

QT應用程式設計: 編寫低功耗BLE藍牙調試助手(Android系統APP)

一、環境介紹

QT版本: 5.12.6

編譯環境: win10 64位

目标系統: Android

完整工程源碼下載下傳位址(包含APK檔案):  

https://download.csdn.net/download/xiaolong1126626497/19051696 想學習QT的Android環境搭建看這裡(win10版本): https://blog.csdn.net/xiaolong1126626497/article/details/117254453                                                          (ubuntu版本): https://blog.csdn.net/xiaolong1126626497/article/details/117256660 想學習QT入門到精通程式設計的看這裡: https://blog.csdn.net/xiaolong1126626497/article/details/116485145

二、功能介紹

本軟體是一款BLE藍牙序列槽調試助手,支援正常的發送和接收調試,BLE是低功耗類型藍牙,在智能家居、物聯網領域使用較多。BLE低功耗藍牙一般不能直接使用手機進行連接配接,手機藍牙預設都是工作在傳統藍牙模式,無法直接連接配接BLE低功耗藍牙信号,需要使用專用的APP配置手機藍牙模式,方可進行通信。

本軟體為了友善工程師在開發産品過程中調試BLE藍牙,将藍牙連接配接過程中的資訊全部輸出到螢幕上,友善調試錯誤。  

QT應用程式設計: 編寫低功耗BLE藍牙調試助手(Android系統APP)
QT應用程式設計: 編寫低功耗BLE藍牙調試助手(Android系統APP)
QT應用程式設計: 編寫低功耗BLE藍牙調試助手(Android系統APP)
QT應用程式設計: 編寫低功耗BLE藍牙調試助手(Android系統APP)
QT應用程式設計: 編寫低功耗BLE藍牙調試助手(Android系統APP)
三、核心源碼

#include "mainwindow.h"
#include "ui_mainwindow.h"
 
 
/*
 * 設定QT界面的樣式
*/
void MainWindow::SetStyle(const QString &qssFile) {
    QFile file(qssFile);
    if (file.open(QFile::ReadOnly)) {
        QString qss = QLatin1String(file.readAll());
        qApp->setStyleSheet(qss);
        QString PaletteColor = qss.mid(20,7);
        qApp->setPalette(QPalette(QColor(PaletteColor)));
        file.close();
    }
    else
    {
        qApp->setStyleSheet("");
    }
}
 
 
//#藍牙序列槽服務
//SerialPortServiceClass_UUID = '{00001101-0000-1000-8000-00805F9B34FB}'
//Service UUID 0xFEE0 主服務
 
 
//static const QLatin1String serviceUuid("0000FEE0-0000-1000-8000-00805F9B34FB");
//這個字元串裡面的内容就是序列槽模式的Uuid
 
MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
 
    this->SetStyle(":/qss/blue.css");     //設定樣式表
    this->setWindowTitle("BLE藍牙調試助手"); //設定标題
    this->setWindowIcon(QIcon(":/wbyq.ico")); //設定圖示
 
    /*1. 執行個體化藍牙相關的對象*/
    discoveryAgent = new QBluetoothDeviceDiscoveryAgent();
    localDevice = new QBluetoothLocalDevice();
 
    /*2. 關聯藍牙裝置相關的信号*/
    /*2.1 關聯發現裝置的槽函數,當掃描發現周圍的藍牙裝置時,會發出deviceDiscovered信号*/
    connect(discoveryAgent,
            SIGNAL(deviceDiscovered(QBluetoothDeviceInfo)),
            this,
            SLOT(addBlueToothDevicesToList(QBluetoothDeviceInfo))
            );
 
    /*3. 檢查藍牙的狀态,用于設定按鈕的初始狀态*/
    /*3.1 檢查藍牙是否開啟*/
    if(localDevice->hostMode() == QBluetoothLocalDevice::HostPoweredOff)
    {
            //如果藍牙處于關閉狀态
            ui->pushButton_OpenBluetooth->setEnabled(true);   //打開按鈕
            ui->pushButton_CloseBluetooth->setEnabled(false); //關閉按鈕
 
    }
    else    //如果藍牙處于開啟狀态
    {
            ui->pushButton_OpenBluetooth->setEnabled(false);//打開按鈕
            ui->pushButton_CloseBluetooth->setEnabled(true);//關閉按鈕
            ui->pushButton_BluetoothScan->setEnabled(true); //設定掃描按鈕可用
    }
 
    /*3.2 設定标簽顯示本地藍牙的名稱*/
    QString name_info("本機藍牙:");
    name_info+=localDevice->name();
    ui->label_BluetoothName->setText(name_info);
 
     ui->pushButton_StopScan->setEnabled(false);      //設定停止掃描藍牙的按鈕不可用
 
     ui->plainTextEdit_BluetoothInfiShow->setEnabled(false); //設定不可編輯
 
     m_control=NULL; //初始值
     m_service=NULL;  //初始值
     SendModeSelect=0;
     SendMaxMode=0;
}
 
 
MainWindow::~MainWindow()
{
    delete ui;
    delete discoveryAgent;
    delete localDevice;
}
 
void MainWindow::on_pushButton_OpenBluetooth_clicked()
{
    /*請求打開藍牙裝置*/
    localDevice->powerOn();
    ui->pushButton_OpenBluetooth->setEnabled(false);//打開按鈕
    ui->pushButton_CloseBluetooth->setEnabled(true);//關閉按鈕
    ui->pushButton_BluetoothScan->setEnabled(true); //設定掃描按鈕可用
 
}
 
void MainWindow::on_pushButton_CloseBluetooth_clicked()
{
    /*關閉藍牙裝置*/
    localDevice->setHostMode(QBluetoothLocalDevice::HostPoweredOff);
    ui->pushButton_OpenBluetooth->setEnabled(true);//打開按鈕
    ui->pushButton_CloseBluetooth->setEnabled(false);//關閉按鈕
    ui->pushButton_BluetoothScan->setEnabled(false); //設定掃描按鈕不可用
}
 
 
void MainWindow::on_pushButton_BluetoothScan_clicked()
{
     /*開始掃描周圍的藍牙裝置*/
    discoveryAgent->start();
    ui->comboBox_BluetoothDevice->clear(); //清除條目
    ui->pushButton_BluetoothScan->setEnabled(false); //設定掃描按鈕不可用
    ui->pushButton_StopScan->setEnabled(true);     //設定停止掃描按鈕可用
}
 
void MainWindow::on_pushButton_StopScan_clicked()
{
    /*停止掃描周圍的藍牙裝置*/
    discoveryAgent->stop();
    ui->pushButton_StopScan->setEnabled(false);     //設定停止掃描按鈕不可用
    ui->pushButton_BluetoothScan->setEnabled(true); //設定掃描按鈕可用
}
 
 
/*當掃描到周圍的裝置時會調用目前的槽函數*/
void MainWindow::addBlueToothDevicesToList(const QBluetoothDeviceInfo &info)
{
   // QString label = QString("%1 %2").arg(info.name()).arg(info.address().toString());
    QString label = QString("%1 %2").arg(info.address().toString()).arg(info.name());
    ui->comboBox_BluetoothDevice->addItem(label); //添加字元串到comboBox上
}
 
 
/*
在說藍牙裝置連接配接之前,不得不提一個非常重要的概念,就是藍牙的Uuid,引用一下百度的:
在藍牙中,每個服務和服務屬性都唯一地由"全球唯一辨別符" (UUID)來校驗。
正如它的名字所暗示的,每一個這樣的辨別符都要在時空上保證唯一。
UUID類可表現為短整形(16或32位)和長整形(128位)UUID。
他提供了分别利用String和16位或32位數值來建立類的構造函數,提供了一個可以比較兩個UUID(如果兩個都是128位)的方法,還有一個可以轉換一個UUID為一個字元串的方法。
UUID執行個體是不可改變的(immutable),隻有被UUID标示的服務可以被發現。
在Linux下你用一個指令uuidgen -t可以生成一個UUID值;
在Windows下則執行指令uuidgen 。UUID看起來就像如下的這個形式:2d266186-01fb-47c2-8d9f-10b8ec891363。當使用生成的UUID去建立一個UUID對象,你可以去掉連字元。
*/
 
//發送資料
void MainWindow::on_pushButton_SendData_clicked()
{
    QString text=ui->lineEdit_SendData->text();
    QByteArray array=text.toLocal8Bit();
    /*寫入newValue作為特性的值。
     如果操作成功,将發射characteristicWritten()信号;
    低功耗裝置: 每次最多寫20個位元組
    */
      m_service->writeCharacteristic(m_writeCharacteristic[SendModeSelect],array, m_writeMode);
}
 
 
//清空收到的資料
void MainWindow::on_pushButton_Clear_clicked()
{
    ui->plainTextEdit_BluetoothInfiShow->setPlainText("");
}
 
//連接配接藍牙
void MainWindow::on_pushButton_ConnectDev_clicked()
{
    QString text = ui->comboBox_BluetoothDevice->currentText();
    int index = text.indexOf(' ');
    if(index == -1) return;
 
    QBluetoothAddress address(text.left(index));
 
    QString connect_device="開始連接配接藍牙裝置:\n";
    connect_device+=ui->comboBox_BluetoothDevice->currentText();
    QMessageBox::information(this,tr("連接配接提示"),connect_device);
 
    /*低功耗藍牙裝置*/
    if(m_control!=NULL)
    {
        m_control->disconnectFromDevice(); //斷開遠端裝置
        delete m_control;
        m_control = NULL;
    }
 
    ui->comboBox_UUID->clear();         //清除顯示UUID服務的清單框
    QList<QBluetoothDeviceInfo> info_list=discoveryAgent->discoveredDevices(); //得到掃描的所有裝置資訊
    for(int i=0;i<info_list.count();i++)
    {
        if(info_list.at(i).address().toString()==text.left(index))
        {
             remoteDevice=info_list.at(i);
             ui->plainTextEdit_BluetoothInfiShow->insertPlainText("連接配接裝置:");
             ui->plainTextEdit_BluetoothInfiShow->insertPlainText(remoteDevice.name());
             ui->plainTextEdit_BluetoothInfiShow->insertPlainText("\n");
             ui->plainTextEdit_BluetoothInfiShow->insertPlainText(remoteDevice.address().toString());
             ui->plainTextEdit_BluetoothInfiShow->insertPlainText("\n");
            break;
        }
   }
 
    //建立中央角色裝置
    m_control = new QLowEnergyController(remoteDevice, this);
    //m_control=QLowEnergyController::createCentral(remoteDevice,this);
    if(m_control==0)
    {
        ui->plainTextEdit_BluetoothInfiShow->insertPlainText("建立中央角色裝置失敗!\n");
    }
    else
    {
        ui->plainTextEdit_BluetoothInfiShow->insertPlainText("建立中央角色裝置成功!\n");
    }
    //每次發現新的服務就會發送此信号
    connect(m_control, SIGNAL(serviceDiscovered(QBluetoothUuid)),this, SLOT(BlueServiceDiscovered(QBluetoothUuid)));
 
    //正在運作的服務發現完成時發出此信号。
    connect(m_control, SIGNAL(discoveryFinished()),this, SLOT(BlueServiceScanDone()));
 
    //當控制器成功連接配接到遠端Low Energy裝置時,會發出此信号。
     connect(m_control, SIGNAL(connected()),this, SLOT(BlueDeviceConnected()));
 
    //當控制器從遠端低功耗裝置斷開時發出此信号。
     connect(m_control, SIGNAL(disconnected()),this, SLOT(BlueDeviceDisconnected()));
 
     //該信号在發生錯誤時發出。
     connect(m_control, static_cast<void(QLowEnergyController::*)(QLowEnergyController::Error)>(&QLowEnergyController::error),
           [=](QLowEnergyController::Error error){
 
         if(error==QLowEnergyController::NoError)
         {
             ui->plainTextEdit_BluetoothInfiShow->insertPlainText("沒有發生錯誤\n");
         }
         else if(error==QLowEnergyController::UnknownError)
         {
             ui->plainTextEdit_BluetoothInfiShow->insertPlainText("出現未知錯誤。\n");
         }
          else if(error==QLowEnergyController::UnknownRemoteDeviceError)
         {
             ui->plainTextEdit_BluetoothInfiShow->insertPlainText("無法找到傳遞給此類構造函數的遠端Bluetooth Low Energy裝置。\n");
         }
 
         else if(error==QLowEnergyController::NetworkError)
         {
             ui->plainTextEdit_BluetoothInfiShow->insertPlainText("嘗試讀取或寫入遠端裝置失敗\n");
         }
 
          else if(error==QLowEnergyController::InvalidBluetoothAdapterError)
         {
             ui->plainTextEdit_BluetoothInfiShow->insertPlainText("傳遞給此類構造函數的本地藍牙裝置無法找到,或者沒有本地藍牙裝置\n");
         }
 
          else if(error==QLowEnergyController::InvalidBluetoothAdapterError)
         {
             ui->plainTextEdit_BluetoothInfiShow->insertPlainText("嘗試連接配接到遠端裝置失敗。\n");
         }
         else
          ui->plainTextEdit_BluetoothInfiShow->insertPlainText("*****未知錯誤!******\n");
     });
 
     //連接配接到遠端藍牙低功耗裝置。
      m_control->connectToDevice();
}
 
 
 
//每次發現新的服務,就會調用該槽函數
void MainWindow::BlueServiceDiscovered(const QBluetoothUuid &gatt)
{
     ui->comboBox_UUID->addItem(gatt.toString()); //添加字元串到comboBox上
     ui->plainTextEdit_BluetoothInfiShow->insertPlainText("\n");
     ui->plainTextEdit_BluetoothInfiShow->insertPlainText(gatt.toString());
}
 
 
//幫助提示
void MainWindow::on_pushButton_help_clicked()
{
    QMessageBox::information(this,tr("幫助提示"),"本軟體用于BLE4.0藍牙調試\n"
                                             "不支援HC-05系列2.0版本藍牙\n"
                                             "如果第一次打開軟體界面"
                                             "不适應螢幕關閉應用重新打開即可\n"
                                             "軟體作者:DS小龍哥\n"
                                             "BUG回報:[email protected]");
}
 
 
//預設指定UUID服務
static const QLatin1String serviceUuid("{0000FEE0-0000-1000-8000-00805F9B34FB}");
 
//正在運作的服務發現完成時發出此信号。
void MainWindow::BlueServiceScanDone()
{
   // ui->plainTextEdit_BluetoothInfiShow->insertPlainText("正在運作的服務發現完成\n");
 
//    QMessageBox::information(this,tr("幫助提示"),"服務發現完成\n"
//                                             "請選擇上方清單中的服務\n"
//                                             "進行連接配接BLE低功耗藍牙裝置\n");
    /*判斷之前有沒有連接配接過*/
    if(m_service!=NULL)
    {
        delete m_service;
        m_service=NULL;
    }
 
    ui->plainTextEdit_BluetoothInfiShow->insertPlainText("\n選中的服務:");
    ui->plainTextEdit_BluetoothInfiShow->insertPlainText(serviceUuid);
    ui->plainTextEdit_BluetoothInfiShow->insertPlainText("\n");
 
    /*與裝置之間建立服務*/
    m_service=m_control->createServiceObject(QBluetoothUuid(serviceUuid),this);
    if(m_service==NULL)
    {
        ui->plainTextEdit_BluetoothInfiShow->insertPlainText("服務建立失敗!\n");
        return;
    }
    else
    {
        ui->plainTextEdit_BluetoothInfiShow->insertPlainText("服務建立成功!\n");
    }
    /*服務狀态改變時發出此信号。newState也可以通過state()。*/
    connect(m_service, SIGNAL(stateChanged(QLowEnergyService::ServiceState)),
                this, SLOT(BleServiceServiceStateChanged(QLowEnergyService::ServiceState)));
 
    /*特性值由事件改變時發出此信号在外設上。 newValue參數包含更新後的值特性*/
    connect(m_service, SIGNAL(characteristicChanged(QLowEnergyCharacteristic,QByteArray)),
                this, SLOT(BleServiceCharacteristicChanged(QLowEnergyCharacteristic,QByteArray)));
 
    /*當特征讀取請求成功傳回其值時,發出此信号。*/
     connect(m_service, SIGNAL(characteristicRead(QLowEnergyCharacteristic,QByteArray)),
                this, SLOT(BleServiceCharacteristicRead(QLowEnergyCharacteristic,QByteArray)));
 
    /*當特性值成功更改為newValue時,會發出此信号。*/
    connect(m_service, SIGNAL(characteristicWritten(QLowEnergyCharacteristic,QByteArray)),
                this, SLOT(BleServiceCharacteristicWrite(QLowEnergyCharacteristic,QByteArray)));
 
    /*錯誤信号*/
    connect(m_service, static_cast<void(QLowEnergyService::*)(QLowEnergyService::ServiceError)>(&QLowEnergyService::error),
         [=](QLowEnergyService::ServiceError newErrorr)
    {
        if(QLowEnergyService::NoError == newErrorr)
        {
             ui->plainTextEdit_BluetoothInfiShow->insertPlainText("沒有發生錯誤。\n");
        }
        if(QLowEnergyService::OperationError==newErrorr)
        {
             ui->plainTextEdit_BluetoothInfiShow->insertPlainText("錯誤: 當服務沒有準備好時嘗試進行操作!\n");
        }
        if(QLowEnergyService::CharacteristicReadError==newErrorr)
        {
             ui->plainTextEdit_BluetoothInfiShow->insertPlainText("嘗試讀取特征值失敗!\n");
        }
        if(QLowEnergyService::CharacteristicWriteError==newErrorr)
        {
             ui->plainTextEdit_BluetoothInfiShow->insertPlainText("嘗試為特性寫入新值失敗!\n");
        }
        if(QLowEnergyService::DescriptorReadError==newErrorr)
        {
             ui->plainTextEdit_BluetoothInfiShow->insertPlainText("嘗試讀取描述符值失敗!\n");
        }
        if(QLowEnergyService::DescriptorWriteError==newErrorr)
        {
             ui->plainTextEdit_BluetoothInfiShow->insertPlainText(" 嘗試向描述符寫入新值失敗!\n");
        }
        if(QLowEnergyService::UnknownError==newErrorr)
        {
             ui->plainTextEdit_BluetoothInfiShow->insertPlainText("與服務互動時發生未知錯誤!\n");
        }
    });
 
    if(m_service->state() == QLowEnergyService::DiscoveryRequired)
    {
        m_service->discoverDetails(); //啟動服務發現掃描
    }
    else
        searchCharacteristic();
}
 
 
//搜尋特性
void MainWindow::searchCharacteristic()
{
    if(m_service)
    {
        QList<QLowEnergyCharacteristic> list=m_service->characteristics();
        qDebug()<<"list.count()="<<list.count();
        //characteristics 擷取詳細特性
 
        SendMaxMode=list.count();  //設定模式選擇上限
        for(int i=0;i<list.count();i++)
        {
            QLowEnergyCharacteristic c=list.at(i);
 
            /*如果QLowEnergyCharacteristic對象有效,則傳回true,否則傳回false*/
            if(c.isValid())
            {
//                傳回特征的屬性。
//                這些屬性定義了特征的通路權限。
               if(c.properties() & QLowEnergyCharacteristic::WriteNoResponse || c.properties() & QLowEnergyCharacteristic::Write)
               // if(c.properties() & QLowEnergyCharacteristic::Write)
                {
                    ui->plainTextEdit_BluetoothInfiShow->insertPlainText("具有寫權限!\n");
                    m_writeCharacteristic[i] = c;  //儲存寫權限特性
                    if(c.properties() & QLowEnergyCharacteristic::WriteNoResponse)
//                        如果使用此模式寫入特性,則遠端外設不應發送寫入确認。
//                        無法确定操作的成功,并且有效負載不得超過20個位元組。
//                        一個特性必須設定QLowEnergyCharacteristic :: WriteNoResponse屬性來支援這種寫模式。
//                         它的優點是更快的寫入操作,因為它可能發生在其他裝置互動之間。
                        m_writeMode = QLowEnergyService::WriteWithoutResponse;
                    else
                        m_writeMode = QLowEnergyService::WriteWithResponse;
                    //如果使用此模式寫入特性,則外設應發送寫入确認。
                    //如果操作成功,則通過characteristicWritten()信号發出确認。
                    //否則,發出CharacteristicWriteError。
                    //一個特性必須設定QLowEnergyCharacteristic :: Write屬性來支援這種寫模式。
 
                }
                if(c.properties() & QLowEnergyCharacteristic::Read)
                {
                    m_readCharacteristic = c; //儲存讀權限特性
                }
 
                //描述符定義特征如何由特定用戶端配置。
                m_notificationDesc = c.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration);
 
                //值為真
                if(m_notificationDesc.isValid())
                {
                    //寫描述符
                    m_service->writeDescriptor(m_notificationDesc, QByteArray::fromHex("0100"));
                  //   m_service->writeDescriptor(m_notificationDesc, QByteArray::fromHex("FEE1"));
                    ui->plainTextEdit_BluetoothInfiShow->insertPlainText("寫描述符!\n");
                }
            }
        }
    }
}
 
 
//當控制器成功連接配接到遠端Low Energy裝置時,會發出此信号。
void MainWindow::BlueDeviceConnected()
{
    ui->plainTextEdit_BluetoothInfiShow->insertPlainText("成功連接配接裝置!\n");
 
    //啟動發現服務Services
    m_control->discoverServices();
 
//    QList<QBluetoothUuid> uuid_list=m_control->services(); //擷取已經查找成功的服務
//    for(int i=0;i<uuid_list.count();i++)
//    {
//        ui->comboBox_UUID->addItem(uuid_list.at(i).toString()); //添加字元串到comboBox上
//    }
//    if(uuid_list.count()<=0)
//    {
//        ui->plainTextEdit_BluetoothInfiShow->insertPlainText("沒有查找到UUID服務!\n");
//    }
}
 
 
//當控制器從遠端低功耗裝置斷開時發出此信号。
void MainWindow::BlueDeviceDisconnected()
{
    ui->plainTextEdit_BluetoothInfiShow->insertPlainText("成功斷開!\n");
}
 
//目前選中的服務
void MainWindow::on_comboBox_UUID_currentIndexChanged(const QString &arg1)
{
 
}
 
//服務狀态改變時發出此信号
void MainWindow::BleServiceServiceStateChanged(QLowEnergyService::ServiceState s)
{
    //ui->plainTextEdit_BluetoothInfiShow->insertPlainText("服務狀态改變時發出此信号!\n");
    if(s == QLowEnergyService::ServiceDiscovered)  //所有細節都已同步
    {
        ui->plainTextEdit_BluetoothInfiShow->insertPlainText("所有細節都已發現!\n");
        searchCharacteristic();
    }
}
 
//讀取到資料
void MainWindow::BleServiceCharacteristicChanged(const QLowEnergyCharacteristic &c,
                                                 const QByteArray &value)
{
   // ui->plainTextEdit_BluetoothInfiShow->insertPlainText("特性值由事件改變時發出此信号在外設上!\n");
    ui->plainTextEdit_BluetoothInfiShow->insertPlainText(QString(value));
    //移動滾動條到底部
    QScrollBar *scrollbar = ui->plainTextEdit_BluetoothInfiShow->verticalScrollBar();
    if(scrollbar)
    {
        scrollbar->setSliderPosition(scrollbar->maximum());
    }
}
 
void MainWindow::BleServiceCharacteristicRead(const QLowEnergyCharacteristic &c,
                                              const QByteArray &value)
{
  //  ui->plainTextEdit_BluetoothInfiShow->insertPlainText("當特征讀取請求成功傳回其值時\n");
    //ui->plainTextEdit_BluetoothInfiShow->insertPlainText(QString(value));
}
 
void MainWindow::BleServiceCharacteristicWrite(const QLowEnergyCharacteristic &c,
                                               const QByteArray &value)
{
    //ui->plainTextEdit_BluetoothInfiShow->insertPlainText("當特性值成功更改為newValue時!\n");
   ui->plainTextEdit_BluetoothInfiShow->insertPlainText(QString(value));
}
 
 
//發送模式
void MainWindow::on_pushButton_SendMode_clicked()
{
    bool ok;
    int data = QInputDialog::getInt(this, tr("擷取輸入模式"),tr("選擇模式:"), 0, 0,SendMaxMode,1, &ok);
    if(ok)
    {
        SendModeSelect=data;
    }
}
       

繼續閱讀