Qt for Android 使用BLE串口藍牙發送數據

最近搞畢設,買了個串口BLE藍牙想配合Qt for Android做一個自制的遙控器,參考了很多qt藍牙的文章,最後弄成apk發現連接不了我的藍牙,折騰了好久發現Qt官方的藍牙套接字只適用於SPP傳輸的藍牙,而BLE藍牙則有專門實現代碼,說實話比藍牙套接字複雜了好幾百倍,而且官方的示例工程是一些有特殊功能的藍牙,不太能移植到單單發送數據的串口藍牙。由於我對藍牙研究也不是很深,參考了很多大佬的博客,發現了一個比較好,功能相近的項目,下面是鏈接

Qt for Androidble藍牙串口助手

但是進去後界面排版有點不好看,也沒有源碼可以提供,並且我只是要一個發送數據的功能,所以經過改造,實現了可以發送數據的功能了,下面貼下關鍵步驟的代碼

下面是構造函數執行的操作,首先是建立一個搜索設備的服務類,設置好各種信號槽連接,然後開始搜索設備。

    //搜索設備
    m_deviceDiscoveryAgent = new QBluetoothDeviceDiscoveryAgent(this);
    m_deviceDiscoveryAgent->setLowEnergyDiscoveryTimeout(20000);	//設置超時時間
    connect(m_deviceDiscoveryAgent,SIGNAL(deviceDiscovered(QBluetoothDeviceInfo)), this,
            SLOT(addDevice(QBluetoothDeviceInfo)));//搜索到設備操作
    connect(m_deviceDiscoveryAgent, SIGNAL(finished()), this, SLOT(scanFinished()));//搜索結束
    connect(this, SIGNAL(returnAddress(QBluetoothDeviceInfo)), this, SLOT(createCtl(QBluetoothDeviceInfo)));				//開始連接目標設備
    connect(ui->bluemsg,SIGNAL(cursorPositionChanged()),this,SLOT(autoScroll()));	//信息一直在最下面顯示

    m_deviceDiscoveryAgent->start(QBluetoothDeviceDiscoveryAgent::LowEnergyMethod);	//開始進行搜索
    ui->blue_search->setEnabled(false);

當搜索到設備後會進入下列槽函數,這裏對目標設備進行了過濾,去除了非BLE的設備,然後添加進ListWidget的列表中

void MainWindow::addDevice(const QBluetoothDeviceInfo &info)
{
    if (info.coreConfigurations() & QBluetoothDeviceInfo::LowEnergyCoreConfiguration)
    {//判斷是否是BLE設備
        QString label = QString("%1 %2").arg(info.address().toString()).arg(info.name());//按順序顯示地址和設備名稱
        QList<QListWidgetItem *> items = ui->bluelist->findItems(label, Qt::MatchExactly);//檢查設備是否已存在,避免重複添加
        if (items.empty())
        {//不存在則添加至設備列表
            QListWidgetItem *item = new QListWidgetItem(label);
            ui->bluelist->addItem(item);
            m_devices.append(info);
        }
    }
}

當雙擊列表中的設備後,就會將設備信息傳到另一個函數中(建立中心設備的函數),準備開始連接

void MainWindow::on_blue_connect_clicked(bool checked)
{
    if(ui->bluelist->currentItem()->text().isEmpty()){}//確認選取有效
    else
    {
        QString bltAddress = ui->bluelist->currentItem()->text().left(17);//獲取選擇的地址
        for (int i = 0; i<m_devices.count(); i++)
        {
            if(m_devices.at(i).address().toString().left(17) == bltAddress)//地址對比
            {
                QBluetoothDeviceInfo choosenDevice = m_devices.at(i);
                emit returnAddress(choosenDevice);//發送設備信息
                m_deviceDiscoveryAgent->stop();//停止搜索服務
                break;
            }
        }
    }
}

獲取到設備信息後,建立BLE中心設備系統類,並先配置好有關下面操作的信號槽:

  • 搜索外圍設備服務
  • 通過UUID建立對應服務
  • 配置相應的服務
  • 出錯提示
  • 成功提示

然後開始對對應的外圍設備進行連接申請

void MainWindow::createCtl(QBluetoothDeviceInfo info)
{
    m_control = QLowEnergyController::createCentral(info, this);
    connect(m_control, &QLowEnergyController::serviceDiscovered,
            this,&MainWindow::serviceDiscovered);	//發現了目標設備後觸發的操作

    connect(m_control, &QLowEnergyController::discoveryFinished,
            this, &MainWindow::serviceScanDone);	//配置服務

    connect(m_control, static_cast<void (QLowEnergyController::*)(QLowEnergyController::Error)>(&QLowEnergyController::error),
            this, [this](QLowEnergyController::Error error) {
        Q_UNUSED(error);
        ui->bluemsg->append("Cannot connect to remote device.");	//錯誤連接
        });

    connect(m_control, &QLowEnergyController::connected, this, [this]() {
        ui->bluemsg->append("Controller connected. Search services...\n");	//成功連接觸發的槽函數
        m_control->discoverServices();
        isconnected=true;
    });

    connect(m_control, &QLowEnergyController::disconnected, this, [this]() {
        ui->bluemsg->append("LowEnergy controller disconnected");	//錯誤連接
    });

    //connect
    ui->bluemsg->append("start to connect\n");

    m_control->connectToDevice();			//開始連接目標設備
}

第一步:開始搜索外圍設備的UUID。第一次如果不知道外圍設備有什麼UUID就像下面這樣寫,然後下一次將其中一個UUID複製下來用就行了。

void MainWindow::serviceDiscovered(const QBluetoothUuid &gatt)
{
        ui->bluemsg->insertPlainText(QString("%1").arg(gatt.toString()));
        m_foundHeartRateService = true;
}

第二步:通過UUID建立服務,並配置讀寫特性就緒、狀態變化、特性變化的信號槽。如果UUID沒有問題,就會進入searchCharacteristic函數進行篩選特性。

void MainWindow::serviceScanDone()
{
    //setInfo("Service scan done.");
    ui->bluemsg->append("Service scan done.");

    m_service = m_control->createServiceObject(QBluetoothUuid(serviceUuid),
                                                       this);//這裏的serviceUuid就是自己找的,可以通過上面方法找
    if(m_service)
    {
        ui->bluemsg->append("服務建立成功\n");

        m_service->discoverDetails();
    }
    else
    {
        ui->bluemsg->append("Service not found");
        return;
    }
    connect(m_service, &QLowEnergyService::stateChanged, this,
            &MainWindow::serviceStateChanged);
    connect(m_service, &QLowEnergyService::characteristicChanged, this,
            &MainWindow::BleServiceCharacteristicChanged);
    connect(m_service, &QLowEnergyService::characteristicRead, this,
            &MainWindow::BleServiceCharacteristicRead);
    connect(m_service, SIGNAL(characteristicWritten(QLowEnergyCharacteristic,QByteArray)),
            this, SLOT(BleServiceCharacteristicWrite(QLowEnergyCharacteristic,QByteArray)));

    if(m_service->state()==QLowEnergyService::DiscoveryRequired)
    {
        m_service->discoverDetails();
    }
    else
    {
        searchCharacteristic();
    }
}

這裏可以判斷服務的獲取狀態來驗證服務是否成功建立,進入不了那麼大概率是UUID配置錯了。

void MainWindow::serviceStateChanged(QLowEnergyService::ServiceState s)
{
    if(s == QLowEnergyService::ServiceDiscovered)
    {
        ui->bluemsg->append("服務已同步\n");
        searchCharacteristic();
    }

}

下面是篩選特性,讀特性沒什麼講究,只要UUID是正確的就可以。但是寫特性要注意,BLE中分爲迴響寫特性和無迴響寫特性,迴響寫特性就是寫一次必須要等到外圍設備發回來一次才能寫,如果你的寫模式兩種都要,那麼可以用個容器保存,不然過濾掉迴響寫特性就可以了。

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->bluemsg->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->bluemsg->insertPlainText("寫描述符!\n");
                    }
                }
            }
    }
}

下面分別是發送數據的函數和發送成功的函數,通過向寫特性寫入數據即可發送數據,發送成功後就會進入對應的槽函數。

void MainWindow::SendMsg(QString text)
{
    QByteArray array=text.toLocal8Bit();

    m_service->writeCharacteristic(m_writeCharacteristic[SendModeSelect],array, m_writeMode);
}

void MainWindow::BleServiceCharacteristicWrite(const QLowEnergyCharacteristic &c, const QByteArray &value)
{
    ui->bluemsg->append(QString("指令%1發送成功").arg(QString(value)));
}

當有數據發送過來的時候,我們就可以通過讀特性的槽函數來獲取數據。

在我另外的一個藍牙小車遙控器也用相關代碼實現了,可以在那個博客獲取到源碼。

下面是成功連接的圖片
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章