Windows上用QT开发BLE(Bluetooth low energy)程序,及一个坑的填充

因项目需要,涉及到了BLE通信,同时之前有BLE的设备端开发经验,又使用了一段时间的QT开发一些研发工具,如串口通信测试工具,tcp/ip测试工具,以及一些数据图形化工具等,那么就用QT进行上位机的开发BLE的研发工具吧。其中低功耗蓝牙的基础知识本处不细说了。

系统:win11

QT:5.15.2,使用qmake构建

注意:必须使用msvc,如果使用MingGW,会搜索不到设备,以及其它你不想见到的问题

1. 在工程文件 .pro 中添加对bluetooth的支持

QT       += bluetooth

2.首先是扫描

void MainWindow::ble_dev_tool_start_scan(int scan_timeout)
{    
    // Clear the device infomation list before sverytime we start scan
    m_devInfoList.clear();

    //先清空列表
    ui->lw_dev_info->clear();

    // Add the title
    QString devLabel = QString(" rssi         ble address                     device name");
    QListWidgetItem* devItemPtr = new QListWidgetItem(devLabel);
    ui->lw_dev_info->addItem(devItemPtr);

    m_deviceDiscoveryAgentPtr.setLowEnergyDiscoveryTimeout(scan_timeout);

    connect(&m_deviceDiscoveryAgentPtr, &QBluetoothDeviceDiscoveryAgent::deviceDiscovered, this, &MainWindow::ble_dev_tool_device_discovered_slot);
    void (QBluetoothDeviceDiscoveryAgent:: *deviceDiscoveryErrorOccurred)(QBluetoothDeviceDiscoveryAgent::Error) = &QBluetoothDeviceDiscoveryAgent::error;
    connect(&m_deviceDiscoveryAgentPtr, deviceDiscoveryErrorOccurred, this, &MainWindow::ble_dev_tool_device_discovery_error_slot);
    connect(&m_deviceDiscoveryAgentPtr, &QBluetoothDeviceDiscoveryAgent::finished, this, &MainWindow::ble_dev_tool_device_discovery_finished_slot);
    connect(&m_deviceDiscoveryAgentPtr, &QBluetoothDeviceDiscoveryAgent::canceled, this, &MainWindow::ble_dev_tool_device_discovery_finished_slot);

    m_deviceDiscoveryAgentPtr.start(QBluetoothDeviceDiscoveryAgent::LowEnergyMethod);

    // Display the status in the statusbar
    ble_dev_tool_status_bar_update("ble scanning......");
    ui->pb_scan->setText("stop scan");
}

此处要注意的是扫描的时间,setLowEnergyDiscoveryTimeout方法的参数如果为0,则一直扫描直到调用停止扫描的方法,如果不为零,则扫描指定时间之后自动停止。

3.列出扫描到的设备

也就是slot槽函数ble_dev_tool_device_discovered_slot,将扫描到的设备列到一个QListWidget中。

void MainWindow::ble_dev_tool_device_discovered_slot(const QBluetoothDeviceInfo &devInfo)
{
    // If the name is not empty and it is Bluetooth low power, consider adding it
    if(devInfo.coreConfigurations() & QBluetoothDeviceInfo::LowEnergyCoreConfiguration)
    {
        // 先判断是否是想要的设备
        if(devInfo.name().contains(BLE_FILTER_NAME))
        {
            // 获取rssi,地址和名称
            QString label = QString("%1 %2 %3").arg(QString::number(devInfo.rssi()) + "        ", devInfo.address().toString() + "        ", devInfo.name());
            // Search the listwidget to see if the device already exists
            QList<QListWidgetItem *> itemPtrList = ui->lw_dev_info->findItems(label, Qt::MatchExactly);
            // Prevent duplicate addition
            if (itemPtrList.empty()) //If there is no information about this device
            {
                QListWidgetItem* itemPtr = new QListWidgetItem(label);
                ui->lw_dev_info->addItem(itemPtr);//Add it to the listwidget
                m_devInfoList.append(devInfo);    //Add to devece information list
            }
        }
    }
}

此处可以根据 if(devInfo.name().contains(BLE_FILTER_NAME)) 对扫描到的设备进行过滤

4.连接指定设备

在上述设备列表中,双击指定设备的slot槽函数on_lw_dev_info_itemDoubleClicked,可以发起连接操作

void MainWindow::on_lw_dev_info_itemDoubleClicked(QListWidgetItem *item)
{
    if(ui->lw_dev_info->currentRow() == 0)
    {
        return;
    }

    // 正在扫描的话要先停止
    if(scan_flag == true)
    {
        ble_dev_tool_stop_scan();
    }

    //设备被双击后需要先清空Uuid List,只保存当前选中设备的服务Uuid
    m_uuidList.clear();

    //先清空列表
    ui->lw_dev_info_service->clear();

    //创建蓝牙控制器; currentRow()-1是因为自己手动添加了一行标题
    m_bleControllerPtr = QLowEnergyController::createCentral(m_devInfoList.at(ui->lw_dev_info->currentRow() - 1)); //central相当于是主机
    if(m_bleControllerPtr == NULL)
    {
        QMessageBox::warning(this,"警告","创建控制器失败!");
    }
    else
    {
        //bleController的槽函数
        connect(m_bleControllerPtr, &QLowEnergyController::connected, this, &MainWindow::ble_dev_tool_device_connected_slot); //设备连接成功
        void (QLowEnergyController:: *bleDeviceConnectionErrorOccurred)(QLowEnergyController::Error) = &QLowEnergyController::error;//有重载
        connect(m_bleControllerPtr, bleDeviceConnectionErrorOccurred, this, &MainWindow::ble_dev_tool_device_connection_error_slot); //设备连接出现错误
        connect(m_bleControllerPtr, &QLowEnergyController::disconnected, this, &MainWindow::ble_dev_tool_device_disconnected_slot); //设备断开链接
        connect(m_bleControllerPtr, &QLowEnergyController::serviceDiscovered, this, &MainWindow::ble_dev_tool_service_discovered_slot); //发现一个服务
        connect(m_bleControllerPtr, &QLowEnergyController::discoveryFinished, this, &MainWindow::ble_dev_tool_service_discovery_finished_slot); //服务发现结束

        //创建后控制器中对应的设备就是我们在列表中选中的设备
        ble_dev_tool_status_bar_update("Connecting......");

        QTimer::singleShot(BLE_SLOT_DELAY_CONN_mS, this, [this](){
            ble_dev_tool_device_connecte();
        });
    }
}

此处做了一些简单的处理,如正在扫描则将扫描停止

并做了一个小延时再发起连接

void MainWindow::ble_dev_tool_device_connecte(void)
{
    m_bleControllerPtr->connectToDevice();
}

5.发现服务

发现服务需要在连接成功之后进行,连接成功有一个slot槽函数ble_dev_tool_device_connected_slot

void MainWindow::ble_dev_tool_device_connected_slot()
{
    ble_dev_tool_status_bar_update("Connected!");

    QTimer::singleShot(BLE_SLOT_DELAY_DISC_SERVICE_mS, this, [this](){
        ble_dev_tool_discover_service();
    });
}

在该函数中做了个延时之后发起服务发现过程

void MainWindow::ble_dev_tool_discover_service(void)
{
    m_bleControllerPtr->discoverServices();
}

6.对搜索到的服务进行处理,即slot槽函数ble_dev_tool_service_discovered_slot

void MainWindow::ble_dev_tool_service_discovered_slot(QBluetoothUuid serviceUuid)
{
    //将搜索到的服务的Uuid存到一个label中
    QString label = QString("%1").arg(serviceUuid.toString());
    //在listwidget中搜索是否已经有这个服务
    QList<QListWidgetItem *> itemPtrList = ui->lw_dev_info_service->findItems(label, Qt::MatchExactly);
    //防止重复
    if (itemPtrList.empty()) //如果listwidget中没有搜索到的这个服务
    {
        QListWidgetItem* itemPtr = new QListWidgetItem(label);
        ui->lw_dev_info_service->addItem(itemPtr);//将设备的信息添加到listwidget中
        m_uuidList.append(serviceUuid); //设备信息添加到自己的列表中

        if (label == BLE_FILTER_SERVICE)
        {
            service_find_flag = true;


            ble_dev_tool_serv_index = m_uuidList.count();
        }
    }
}

7.对服务server发现之后就需要对characteristic进行detail获取

需要在服务发现完成slot槽函数ble_dev_tool_service_discovery_finished_slot中进行

void MainWindow::ble_dev_tool_service_discovery_finished_slot()
{
    ui->te_ble_log->append("服务发现完成");

    if(service_find_flag == true)
    {
        QTimer::singleShot(BLE_SLOT_DELAY_BEFOR_GET_DETAIL_mS, this, [this](){
            ble_dev_tool_update_notify_get_details();
        });
    }
}

做了个延时触发

void MainWindow::ble_dev_tool_update_notify_get_details(void)
{
    QBluetoothUuid serviceUuid = m_uuidList.at(ble_dev_tool_serv_index - 1); //当前选中的服务的Uuid

    //创建服务
    m_bleServicePtr = m_bleControllerPtr->createServiceObject(QBluetoothUuid(serviceUuid), this);

    //判断创建服务是否出现错误
    if(m_bleServicePtr == NULL)
    {
        QMessageBox::warning(this, "警告", "创建服务失败!");
    }
    else //创建服务成功;创建服务就相当于连接上了,执行完ServiceStateChangedSlot之后就可以正常通信了
    {
        ui->te_ble_log->append("开始获取服务细节并使能notify");

        //监听服务状态变化
        connect(m_bleServicePtr, &QLowEnergyService::stateChanged, this, &MainWindow::ble_dev_tool_service_state_changed_slot);
        //服务的characteristic变化,有数据传来
        connect(m_bleServicePtr, &QLowEnergyService::characteristicChanged, this, &MainWindow::ble_dev_tool_service_characteristic_changed_slot);
        //错误处理
        void (QLowEnergyService:: *bleServiceErrorOccurred)(QLowEnergyService::ServiceError) = &QLowEnergyService::error;//有重载
        connect(m_bleServicePtr, bleServiceErrorOccurred, this, &MainWindow::ble_dev_tool_service_error_occurred_slot);
        //描述符成功被写
        connect(m_bleServicePtr, &QLowEnergyService::descriptorWritten, this, &MainWindow::ble_dev_tool_service_descriptor_written_slot);

        //触发服务详情发现函数
        QTimer::singleShot(BLE_SLOT_DELAY_GET_DETAIL_mS, this, [this](){
            ble_dev_tool_discover_service_details();
        });
    }
}

函数

void MainWindow::ble_dev_tool_discover_service_details(void)
{
    m_bleServicePtr->discoverDetails();
}

8.使能具有notify权限的characteristic

需要在slot槽函数ble_dev_tool_service_state_changed_slot中进行

void MainWindow::ble_dev_tool_service_state_changed_slot(QLowEnergyService::ServiceState state) //服务状态改变
{
    QLowEnergyCharacteristic m_bleCharacteristic;
    //发现服务
    if(state == QLowEnergyService::ServiceDiscovered)
    {
        QList<QLowEnergyCharacteristic> list = m_bleServicePtr->characteristics();
        for(int i = 0; i < list.count(); i++)
        {
            //当前位置的bleCharacteritic
            m_bleCharacteristic = list.at(i);

            //如果当前characteristic有效
            if(m_bleCharacteristic.isValid())
            {
                //描述符定义特征如何由特定客户端配置
                m_notify_descriptor = m_bleCharacteristic.descriptor(QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration);

                //如果descriptor有效
                if(m_notify_descriptor.isValid())
                {
                    m_bleServicePtr->writeDescriptor(m_notify_descriptor, QByteArray::fromHex("0100"));
                }

                if (m_bleCharacteristic.properties() & QLowEnergyCharacteristic::WriteNoResponse || m_bleCharacteristic.properties() & QLowEnergyCharacteristic::Write)
                {
                    m_write_characteristic = m_bleCharacteristic;
                }
            }
        }
    }
}

9.notify数据接收

从机peripheral向主机central发送数据是通过notify方法,上述已经connect相应的signal信号,下面是具体处理

void MainWindow::ble_dev_tool_service_characteristic_changed_slot(QLowEnergyCharacteristic characteristic, QByteArray value)
{
    ui->te_ble_log->append(QString(value));

    qDebug() <<"receive notification data from peripheral";
}

10.发送数据

主机向从机发送数据是通过write进行的

oid MainWindow::ble_dev_tool_send_data(QByteArray value)
{
    if(value.count() > 0)
    {
        m_bleServicePtr->writeCharacteristic(m_write_characteristic, value, QLowEnergyService::WriteWithResponse);
    }
}

11.关于坑

由于BLE的一些方法不能直接在slot中操作,会造成运行时奔溃,并报错:Could not await service operation (A method was called at an unexpected time.因此,需要在slot中做一些延时,延时方法使用QTimer的singleShot,也就是上面几个函数中使用的 QTimer::singleShot(),这点很重要!!!

有需要可联系交流

  • 15
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 22
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 22
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Tristan Tsai

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值