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

news/2024/11/13 4:15:52/

因项目需要,涉及到了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 scanm_devInfoList.clear();//先清空列表ui->lw_dev_info->clear();// Add the titleQString 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 statusbarble_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 itif(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 existsQList<QListWidgetItem *> itemPtrList = ui->lw_dev_info->findItems(label, Qt::MatchExactly);// Prevent duplicate additionif (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 listwidgetm_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,只保存当前选中设备的服务Uuidm_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++){//当前位置的bleCharacteriticm_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(),这点很重要!!!


http://www.ppmy.cn/news/99735.html

相关文章

旧键盘打字 两数之和

&#x1f495;"不要因为别人的成功而感到沮丧&#xff0c;你的时机会来&#xff0c;只要你继续努力、坚持不懈。"&#x1f495; &#x1f43c;作者:不能再留遗憾了&#x1f43c; &#x1f386;专栏:Java学习&#x1f386; &#x1f697;本文章主要内容:使用哈希表的思…

关系类算法函数

关系类算法函数 equal 比较算法include &#xff08;是否包含&#xff09;lexicographical_compare(比较序列)最大值&#xff08;max_element&#xff09;最小值&#xff08;min_element&#xff09;mismatch(找到第一个不同位置的) equal 比较算法 includes 是不是包含 lexico…

C++爱好者的自我修养(15):数据类型转换(千字总结)

数据类型转换 1.引言2.C Primer介绍的转换规则2.1 初始化和赋值进行的转换2.2 以{}方式初始化时进行的转换&#xff08;C11&#xff09;2.3 表达式中的转换2.4 传递参数时的转换2.5 强制类型转换 3.强制类型转换运算符&#xff08;来自GPT-3.5-turbo&#xff09;3.1 static_cas…

LockSupport

LockSupport是一个编程工具类&#xff0c;主要是为了阻塞和唤醒线程用的。使用它我们可以实现很多功能&#xff0c;今天主要就是对这个工具类的讲解&#xff0c;希望对你有帮助&#xff1a; 一、LockSupport简介 1、LockSupport是什么 刚刚开头提到过&#xff0c;LockSuppor…

115.删除有序数组中的重复项 removeDuplicatesFromSortedArray

文章目录 题目描述解题思路代码详解运行截图 题目描述 题目链接 给你一个 升序排列 的数组 nums &#xff0c;请你 原地 删除重复出现的元素&#xff0c;使每个元素 只出现一次 &#xff0c;返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元…

Postman新手教程

博主简介&#xff1a;想进大厂的打工人博主主页&#xff1a;xyk:所属专栏: JavaEE初阶 目录 文章目录 一、Postman背景介绍 二、Postman下载地址 三、Postman简单使用 一、Postman背景介绍 Postman是Chrome插件类产品中的代表产品之一&#xff0c;这款网页调试工具不仅可以调…

数据结构——二叉树

思维导图&#xff1a; 目录 一&#xff0c;什么是二叉树 二 &#xff0c;二叉树的构建 2.1二叉树的内部结构 2.2手撕二叉树 2.3求二叉树的节点个数 2.3计算二叉树的叶子节点 2.4二叉树的高度 2.5计算第K层有多少个节点 2.6 二叉树的打印 2.6寻找二叉树的节点值 2.7二叉…

嵌入式C语言关键字(const、static、volitatile)

const关键字 C语言中const修饰通常是用来声明常量&#xff0c;并声明常量的值不能修改。当涉及指针变量时情况就会变得更加有趣&#xff0c;需要特别注意。因为有两样东西都有可能成为常量—一种是用来限定指向的空间的值不可修改&#xff1b;另一种是限定指针不可修改。下面是…