因项目需要,涉及到了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(),这点很重要!!!