【QT】QtBluetooth 低功耗蓝牙BLE 笔记

server/2025/2/25 3:20:06/

前言, 最近发现笔记本的蓝牙可以被qt调用, 然后直接连接蓝牙模块, 不一定非要手机蓝牙app或是另一个蓝牙模块转usb.

1.环境要求

注意,因为电脑环境/版本等原因,你可能会遇到很多文本没提到的问题,不要慌,csdn查一下就好.我也会把配置过程中遇到的典型问题列出来.

  • PC电脑, 系统win10,
  • Qt Creadtor 4.10.0
  • Qt 5.13.1
  • 起初只有电脑只有 MinGw 7.3.0 32/64 要用 QtBluetooth 库必须使用 MSVC2017 32/64 (或其它 MSVC 版本);

2.安装MSVC

参考笔记: QT5.13.1-安装MSVC2017-Windows
参考笔记: 关于已经安装了QT再添加MSVC2017
参考笔记: QT5构建套件检测不到MSVC2017解决方法
下载 Visual Studio Installer 安装 MSVC : Windows 如何仅安装 MSVC 而不安装 Visual Studio
下载 Windows SDK 和模拟器存档 安装 CDB : 为Qt creator安装CDB调试器
关于解决Qt Creator CDB调试卡死这件事

  • 以上是参考笔记, 主要流程就是:

2.1 安装 MSVC 编译器

  • 注意需要使用 Visual Studio Installer , 在单个组件包内搜索 MSVC v140MSVC v141 安装框内的所有东西.注意是所有东西! 有一些笔记会说只需要安装部分即可,但是我没成功,折腾半天后才发现安装全部才可以.

在这里插入图片描述

  • 注意如果这一步安装成功后是可以在 Qt->选项->Kits 界面看到如下内容的, 点击添加也能看到 MSVC 选项.请确保也有如下内容且没有显示黄色感叹号才进行下一步.

在这里插入图片描述

在这里插入图片描述

  • 然后添加特定的编译链参数,确保一致 CC++ 都需要, 32位64位 都需要.
  • 确保参数一样,我也是跟着别人教程做的,这样最后能用,n_n

在这里插入图片描述
在这里插入图片描述

  • 添加完毕就可以在 构建套件(Kit) 中选择了; 就算完成一半了.

在这里插入图片描述

2.2.安装 SDK CDB 调试器

不正确的笔记: qtcreator下断点调试时卡住?
正确的笔记: 下载 Windows SDK 和模拟器存档 安装 CDB : 为Qt creator安装CDB调试器
关于解决Qt Creator CDB调试卡死这件事

  • 大部分教程都是教安装 Windows 10 SDK 版本 2104 (10.0.20348.0). 但是安装后我的工程不能单步调试, 我看有些是建议改电脑的语言格式, 效果不好.会把很多软件搞乱码.
  • 我又看到有人是建议使用 Windows 8.1 SDK 就可以解决问题了.确实如此.
  • 另外有些笔记是建议在 Visual Studio Installer 搜索安装, 我这边尝试没有效果, 最后还是得从网站下载 SDK 安装器.
  • 注意: 最后为了以防万一请到电脑的系统环境中添加路径.Path. 或者后面遇到问题时大多都是路径缺失导致的, 到时候再加.
  • 安装成功后能在Qt界面看到如下内容,

在这里插入图片描述
在这里插入图片描述

  • 至此就算完成环境的构建了.按理只需要设置这3个内容就可以正常使用; Compiler C , Compiler C++ , Debugger .

2.3.切换 MSVC 移植工程

参考笔记: QT编译出错:'rc’不是内部或外部命令,也不是可运行的程序 或批处理文件。
参考笔记: 【问题集锦·亲测有效!!】QT <5.14.2> mingw项目 转 MSVC编译报错 + 通过VS2022编译【一站式解决方案】
参考笔记: Qt报vcvarsall.bat x86_amd: The command “C:\WINDOWS\system32\cmd.exe“ could not be started.

  • MinGW 转为 MSVC 工程时, 大概率会出现编译错误; 因为某些编译规则不一样. 根据报错信息逐一修改即可.

3.了解蓝牙

在蓝牙低功耗(BLE)开发中,服务(Service)、特征(Characteristic)和 描述符(Descriptor) (由DS-R1生成)
Qt开发简易蓝牙调试助手(低功耗蓝牙)
QT开发低功耗蓝牙BLE连接ECB02模块进行数据收发
QT 低功耗蓝牙 PC端
Windows上用QT开发BLE(Bluetooth low energy)程序,及一个坑的填充
QBluetoothSocket
吐槽: 一开始不知道蓝牙区分低功耗蓝牙和经典蓝牙,还有部分是安卓api和linux的,虽然网上资料很多,但是注意区分.

  • 我要做的是用Qt编写一个win7/10/11平台的上位机软件, 使用电脑自带的蓝牙功能搜索蓝牙并
    连接, 进行数据交换.

在这里插入图片描述

  • 蓝牙貌似分为经典蓝牙和低功耗蓝牙,我需要连接的是低功耗蓝牙 BLE . 代码也是针对 BLE 才有效的.
  • 在开始写代码之前, 还需要了解蓝牙的连接流程:
  1. 确定支持蓝牙功能, 并搜索蓝牙设备 (BLE);
  2. 连接蓝牙, 连接成功后发现支持的服务 (Service);
  3. 选择服务, 发现支持的特征 (Characteristic);
  4. 根据需要修改特征的描述符 (Descriptor);

4.创建蓝牙类

  • 方便打包调用, 第一件事就是创建 .c .h , 然后在 .h 头文件内一次性定义类的内容, 方便写 .c .
#ifndef BLUETOOTHPORT_H
#define BLUETOOTHPORT_H#include <QObject>
#include <QString>
#include <QDebug>#include <QLowEnergyController> //蓝牙控制器对象
#include <QLowEnergyService>
#include <QLowEnergyCharacteristic>
#include <QLowEnergyConnectionParameters>#include <QtBluetooth/qtbluetoothglobal.h>
#include <QtBluetooth/qbluetoothlocaldevice.h>
#include <QtBluetooth/qbluetoothsocket.h>
#include <QtBluetooth/qbluetoothservicediscoveryagent.h>
#include <QtBluetooth/qbluetoothaddress.h>// 类定义
class BluetoothPort : public QObject
{Q_OBJECT // 添加 Q_OBJECT 宏, 如果类中有自定义信号signals就必须要有, 添加后要重新构建编译
public : // 公开的 类里面 和 类外面可以直接访问BluetoothPort(QObject *parent = nullptr); // 构造函数~BluetoothPort(void); // 析构函数bool deviceSupport(void); // 判断蓝牙是否可用void deviceDiscovery(int ms_timeout = 15000); // 开始搜索蓝牙QList<QList<QString>> deviceDiscoveryStop(void); // 停止搜索蓝牙QList<QString> deviceConnect(const QString &deviceAddStr, int ms_timeout = 5000); // 连接低功耗蓝牙对象 并 搜索支持的服务(Service)UUIDvoid deviceDisconnect(void); // 断开连接低功耗蓝牙对象bool deviceState(void); // 获取控制器对象状态QList<QList<QString>> serviceConnect(QString serviceUUIDStr, int ms_timeout = 5000); // 连接服务(Service)UUID并搜索支持的特征(Characteristic)UUIDvoid serviceDisconnect(void); // 断开服务对象bool serviceState(void); // 获取服务对象状态QByteArray rwCharacteristics(QByteArray arr = QByteArray(), // 修改写入特征值 -> 内含 更新读写 特征值(Characteristic) 和 读写 描述符(Descriptor)int ms_timeout = 0, // 等待读取特征值QString readCharacteristicStr = "", // 指定读取特征值QString writeCharacteristicStr = ""); // 指定写入特征值private: // 私有的 只允许在类里面访问bool qDebug_ok; // 打印内容QBluetoothLocalDevice *localDevice; // 对本地设备进行操作,比如进行设备的打开,设备的关闭等等QBluetoothDeviceDiscoveryAgent *discoveryAgent; // 用来对周围蓝牙进行搜寻QLowEnergyController *lowEnergyController; // 低功耗蓝牙控制器对象QLowEnergyService *lowEnergyService; //低功耗蓝牙服务对象QList<QBluetoothDeviceInfo> listDeviceInfo; // 蓝牙对象列表QList<QBluetoothUuid> listServicesUuid; // 服务(Service)UUID列表QList<QLowEnergyCharacteristic> listLowEnergyCharacteristicRead; // 特征(Characteristic)UUID列表QList<QLowEnergyCharacteristic> listLowEnergyCharacteristicWrite; // 特征(Characteristic)UUID列表QByteArray readArray; // 读取数组QString lowEnergyCharacteristicRead; // 指定读取 特征(Characteristic)UUIDprotected: // 保护的 只允许在本类 和 子类中访问signals:void deviceDiscovered(QString name, QString address); // 发现设备时产生的信号
};// 全局变量声明
extern BluetoothPort *bluetooth_pc;#endif // BLUETOOTHPORT_H

5.构造函数和析构函数

  • 使用指针的方式创建实例, 供其他地方调用.
  • 构造函数中需要初始化几个变量指针, localDevicediscoveryAgent是不需要变更的,所以一开始就创建.lowEnergyControllerlowEnergyService是根据所选蓝牙和服务创建的,初始状态要给空指针.否则后面程序删除指针时会卡死.
  • 析构函数貌似不写也没关系,会指针自己删除.
#include "bluetoothport.h"#include <QTime>
#include <QTimer>#include <QCoreApplication>
#include <QElapsedTimer>BluetoothPort *bluetooth_pc = new BluetoothPort();
#define BLE_SLOT_DELAY_CONN_MS (500) // 局部宏定义, 限定延时启动部分蓝牙功能// ====================================================================================== //// 构造函数
BluetoothPort::BluetoothPort(QObject *parent) : QObject(parent)
{localDevice = new QBluetoothLocalDevice(); // 本地操作discoveryAgent = new QBluetoothDeviceDiscoveryAgent(); // 用来发现扫描周围设备lowEnergyController = nullptr; // 用来连接蓝牙lowEnergyService = nullptr; // 创建服务qDebug_ok = false;if (qDebug_ok) qDebug() << "执行 BluetoothPort 构造函数";
}// 析构函数
BluetoothPort::~BluetoothPort(void)
{// 顺序不能乱, 必须是先控制器对象再是服务对象if (lowEnergyController)delete lowEnergyController; // 低功耗蓝牙控制器对象if (lowEnergyService)delete lowEnergyService; //低功耗蓝牙服务对象// lowEnergyController = nullptr;// lowEnergyService = nullptr;listDeviceInfo.clear(); // 蓝牙对象列表listServicesUuid.clear(); // 服务(Service)UUID列表listLowEnergyCharacteristicRead.clear(); // 特征(Characteristic)UUID列表listLowEnergyCharacteristicWrite.clear(); // 特征(Characteristic)UUID列表readArray.clear(); // 读取数组if (qDebug_ok) qDebug() << "执行 BluetoothPort 析构函数";
}

5.搜索&连接蓝牙

  • 判断蓝牙是否可用的代码貌似没有用, 即使电脑没有打开蓝牙依旧会判断通过执行代码,并且会弹出报错窗口.
  • 搜索蓝牙的代码貌似只是查询电脑已经找到的蓝牙. 比如:在win11的电脑上搜索很慢甚至没有,可以主动点击win11的蓝牙搜索,这时程序才陆续查找到设备.
  • 注意找到蓝牙后需要筛选, 排除非BLE的蓝牙和没有名字的蓝牙;发现蓝牙后会调用信号deviceDiscovered,供外部使用.
  • 因为搜索蓝牙是一个比较长时间的过程, 所以还打包了一个手动停止搜索的函数.同时一次性返回搜索到的蓝牙名字和地址的字符串列表.
// 判断蓝牙是否可用
bool BluetoothPort::deviceSupport(void)
{// 保护措施if (!localDevice) {if (qDebug_ok) qDebug() << "localDevice == nullptr";return false;}// 清除所有信号disconnect(localDevice, nullptr, nullptr, nullptr);// 创建并连接到其信号connect(localDevice, &QBluetoothLocalDevice::deviceConnected, this, [this](const QBluetoothAddress &address){if (qDebug_ok) qDebug() << "localDevice:deviceConnected:当本地设备与具有地址的远程设备建立连接时:" << address; });connect(localDevice, &QBluetoothLocalDevice::deviceDisconnected, this, [this](const QBluetoothAddress &address){if (qDebug_ok) qDebug() << "localDevice:deviceDisconnected:当本地设备与具有地址的远程蓝牙设备断开连接时:" << address; });connect(localDevice, &QBluetoothLocalDevice::error, this, [this](QBluetoothLocalDevice::Error error){if (qDebug_ok) qDebug() << "localDevice:error:配对时出现异常错误时发出信号:" << error; });connect(localDevice, &QBluetoothLocalDevice::hostModeStateChanged, this, [this](QBluetoothLocalDevice::HostMode state){if (qDebug_ok) qDebug() << "localDevice:hostModeStateChanged:主机的状态已转换为不同的HostMode:" << state; });connect(localDevice, &QBluetoothLocalDevice::pairingDisplayConfirmation, this, [this](const QBluetoothAddress &address, QString pin){if (qDebug_ok) qDebug() << "localDevice:pairingDisplayConfirmation:在收到 pairingDisplayConfirmation() 后调用:" << address << pin; });connect(localDevice, &QBluetoothLocalDevice::pairingDisplayPinCode, this, [this](const QBluetoothAddress &address, QString pin){if (qDebug_ok) qDebug() << "localDevice:pairingDisplayPinCode:通过调用 requestPairing() 进行配对请求时发出:" << address << pin; });connect(localDevice, &QBluetoothLocalDevice::pairingFinished, this, [this](const QBluetoothAddress &address, QBluetoothLocalDevice::Pairing pairing){if (qDebug_ok) qDebug() << "localDevice:pairingFinished:已完成与地址的配对或取消配对:" << address << pairing; });// 如果 QBluetoothLocalDevice 表示可用的本地蓝牙设备, 则返回true, 否则返回false,if (!(localDevice->isValid())) {if (qDebug_ok) qDebug() << "蓝牙在此设备上不可用";return false; // 返回失败}// 尝试打开蓝牙localDevice->powerOn();// 读取本地设备名称 (可能为空或未设置)if (localDevice->name().size() != 0) {if (qDebug_ok) qDebug() << "本地设备名称:" << localDevice->name();}// 尝试将设备设置为可发现模式 // 可选地,连接到主机ModeChanged信号,以便在模式更改时收到通知if (localDevice->hostMode() != QBluetoothLocalDevice::HostDiscoverable) {localDevice->setHostMode(QBluetoothLocalDevice::HostDiscoverable);if (qDebug_ok) qDebug() << "设备处于可发现模式";}// 获取已连接的设备QList<QBluetoothAddress> remotes = localDevice->connectedDevices();if (remotes.size()) {if (qDebug_ok) qDebug() << "已连接设备: " << remotes.size();for (const QBluetoothAddress &address : remotes) {if (qDebug_ok) qDebug() << " - " << address.toString();}}if (qDebug_ok) qDebug() << "蓝牙在此设备上已开启";return true; // 返回成功
}// 开始搜索蓝牙
void BluetoothPort::deviceDiscovery(int ms_timeout)
{// 保护措施if (!discoveryAgent) {if (qDebug_ok) qDebug() << "discoveryAgent == nullptr";return;}// 停止发现discoveryAgent->stop();// 清空列表listDeviceInfo.clear();// 关闭所有信号disconnect(discoveryAgent, nullptr, nullptr, nullptr);// 创建并连接到其信号connect(discoveryAgent, &QBluetoothDeviceDiscoveryAgent::deviceDiscovered, this, [this](const QBluetoothDeviceInfo &info){if (qDebug_ok) qDebug() << "discoveryAgent:deviceDiscovered:找到新设备:" << info.name()<< "; 地址:" << info.address().toString()<< "; 设备类型:" << info.coreConfigurations();if (info.name().toUpper().contains(info.address().toString()) == false) {listDeviceInfo.append(info); // 只获取有效ID的设备emit deviceDiscovered(info.name(), info.address().toString()); // 发送信号量}});connect(discoveryAgent, &QBluetoothDeviceDiscoveryAgent::deviceUpdated, this, [this](const QBluetoothDeviceInfo &info, QBluetoothDeviceInfo::Fields updatedFields){if (qDebug_ok) qDebug() << "discoveryAgent:deviceUpdated:信息已更新:" << info.name()<< "; 地址:" << info.address().toString()<< "; 更新数据:" << updatedFields<< "; rssi:" << info.rssi()<< "; 制造商数据:" << info.manufacturerData();});connect(discoveryAgent, QOverload<QBluetoothDeviceDiscoveryAgent::Error>::of(&QBluetoothDeviceDiscoveryAgent::error), [this](QBluetoothDeviceDiscoveryAgent::Error error){if (qDebug_ok) qDebug() << "discoveryAgent:error:发生错误:" << error; });connect(discoveryAgent, &QBluetoothDeviceDiscoveryAgent::finished, this, [this](void){if (qDebug_ok) qDebug() << "discoveryAgent:finished:蓝牙设备发现完成"; });connect(discoveryAgent, &QBluetoothDeviceDiscoveryAgent::canceled, this, [this](void){if (qDebug_ok) qDebug() << "discoveryAgent:QBluetoothDeviceDiscoveryAgent:中止设备发现"; });// 设置低功耗设备发现超时时间if (qDebug_ok) qDebug() << "开始搜索蓝牙, 超时" << ms_timeout << "秒.";discoveryAgent->setLowEnergyDiscoveryTimeout(ms_timeout); // 将蓝牙低功耗设备搜索的最大搜索时间设置为超时 (毫秒). 如果超时为0, 则查找将运行,  直到调用 stop()// 开始发现低功耗蓝牙设备discoveryAgent->start(QBluetoothDeviceDiscoveryAgent::LowEnergyMethod); // 仅搜索低功耗蓝牙设备 QBluetoothDeviceDiscoveryAgent::DiscoveryMethods methods
}// 停止搜索蓝牙
QList<QList<QString>> BluetoothPort::deviceDiscoveryStop(void)
{discoveryAgent->stop();QList<QList<QString>> ret;for (QBluetoothDeviceInfo &info : listDeviceInfo){QList<QString> temp;temp.append(info.name());temp.append(info.address().toString());ret.append(temp);}return ret;
}

6.连接蓝牙 并 发现服务

  • 因为连接蓝牙和发现服务是一个比较快的过程,所以打包在一起并且使用非阻塞的等待延时,最后成功的话只需要返回包含服务UUID的字符串列表即可.
  • 注意,部分环境可能会出现连接蓝牙失败或搜索服务失败的情况, 根据别人分享的经验, 是不能太快调用部分函数,可以另外打包一个函数,分两次先后调用, 或是使用 QTimer::singleShot 延时调用.可以自行修改宏定义 BLE_SLOT_DELAY_CONN_MS 尝试0或是500看看.
// 连接低功耗蓝牙对象并搜索支持的服务(Service)UUID
QList<QString> BluetoothPort::deviceConnect(const QString &deviceAddStr, int ms_timeout)
{QList<QString> ret;// 根据地址码获取蓝牙句柄const QBluetoothDeviceInfo *deviceInfo = nullptr;for (int i=0; i<listDeviceInfo.size(); i++) {if (listDeviceInfo.at(i).address().toString() == deviceAddStr) {deviceInfo = &listDeviceInfo.at(i); // 遍历储存的内容查找符合的内容break;}}if (deviceInfo == nullptr) // 确保有效return ret;if (deviceInfo->coreConfigurations() != QBluetoothDeviceInfo::LowEnergyCoreConfiguration) // 确保低功耗设备return ret;// 停止发现discoveryAgent->stop();// 清空列表listServicesUuid.clear();// 删除上一个指针if (lowEnergyController)delete lowEnergyController;// 创建低功耗蓝牙控制器对象lowEnergyController = QLowEnergyController::createCentral(deviceInfo[0]);if (lowEnergyController == nullptr) {if (qDebug_ok) qDebug() << ("创建 lowEnergyController 失败");return ret; // 返回失败}// 关闭所有信号disconnect(lowEnergyController, nullptr, nullptr, nullptr);// 创建并连接到其信号connect(lowEnergyController, &QLowEnergyController::serviceDiscovered, this, [this](const QBluetoothUuid &newService){listServicesUuid.append(newService); // 保存找到的服务if (qDebug_ok) qDebug() << "lowEnergyController:serviceDiscovered:发现新服务:" << newService.toString();});connect(lowEnergyController, &QLowEnergyController::connected, this, [this](void){QTimer::singleShot(BLE_SLOT_DELAY_CONN_MS, this, [this](void) {if (qDebug_ok) qDebug() << "lowEnergyController:connected:延迟后开始搜索";lowEnergyController->discoverServices(); // 成功连接到设备后开始搜索服务});if (qDebug_ok) qDebug() << "lowEnergyController:connected:成功连接";});connect(lowEnergyController, &QLowEnergyController::disconnected, this, [this](void){// 出现断开后程序奔溃的情况,直接清空所有内容if (lowEnergyController)disconnect(lowEnergyController, nullptr, nullptr, nullptr);if (lowEnergyService)disconnect(lowEnergyService, nullptr, nullptr, nullptr);if (qDebug_ok) qDebug() << "lowEnergyController:disconnected:控制器与远程低功耗设备断开连接";});connect(lowEnergyController, &QLowEnergyController::connectionUpdated, this, [this](const QLowEnergyConnectionParameters &newParameters){if (qDebug_ok) qDebug() << "lowEnergyController:connectionUpdated:连接参数发生变化:" << newParameters.latency(); });connect(lowEnergyController, &QLowEnergyController::discoveryFinished, this, [this](void){if (qDebug_ok) qDebug() << "lowEnergyController:discoveryFinished:正在运行的服务发现完成"; });connect(lowEnergyController, QOverload<QLowEnergyController::Error>::of(&QLowEnergyController::error), [this](QLowEnergyController::Error newError){if (qDebug_ok) qDebug() << "lowEnergyController:error:发生错误:" << newError; });connect(lowEnergyController, &QLowEnergyController::stateChanged, this, [this](QLowEnergyController::ControllerState state) {if (qDebug_ok) qDebug() << "lowEnergyController:stateChanged:控制器的状态改变:" << state; });// 连接到远程低功耗蓝牙设备if (lowEnergyController->state() != QLowEnergyController::UnconnectedState) {if (qDebug_ok) qDebug() << "已占用,不可连接到远程低功耗蓝牙设备";return ret;}QTimer::singleShot(BLE_SLOT_DELAY_CONN_MS, this, [this](void) {if (qDebug_ok) qDebug() << "开始连接 (设备) 并扫描 (服务)"; // 经过一定延时后才执行lowEnergyController->connectToDevice(); // 连接到远程低功耗蓝牙设备 // 未连接时才有效});ms_timeout += BLE_SLOT_DELAY_CONN_MS*2; // 累加上// 等待扫描完成, 不为空才等待if (ms_timeout != 0) {QElapsedTimer timer;timer.start();while (timer.elapsed() < ms_timeout) {QCoreApplication::processEvents(QEventLoop::AllEvents, 50); // 它允许事件循环在调用它的位置暂时接管,处理所有挂起的事件,然后立即返回//QThread::yieldCurrentThread(); // 可以使用此函数来让出CPU时间片,但不推荐在长时间阻塞循环中使用if (deviceState() == true) {for (QBluetoothUuid &uuid : listServicesUuid) {ret.append(uuid.toString());}return ret; // 连接成功才执行}}}deviceDisconnect(); // 上面没有成功, 这里就删除对象return ret; // 返回失败
}// 断开连接低功耗蓝牙对象
void BluetoothPort::deviceDisconnect(void)
{// 删除上一个指针if (lowEnergyController) {lowEnergyController->disconnectFromDevice(); // 不断开直接删除貌似也没啥问题?disconnect(lowEnergyController, nullptr, nullptr, nullptr);delete lowEnergyController;lowEnergyController = nullptr;}
}// 获取控制器对象状态
bool BluetoothPort::deviceState(void)
{if (lowEnergyController == nullptr)return false;if (lowEnergyController->state() != QLowEnergyController::DiscoveredState) // 连接成功才执行return false;return true;
}

7.选择服务 并 发现特征

  • 服务不同设备, 没有断开的api调用, 而且除非重新连接设备, 否则服务的特征只能发现一次, 之后即使删除服务指针重新初始化, 也不会第二次触发 stateChanged .
  • 所以推断服务对象应该用一个列表存储,但有点麻烦, 为了保持和上面的一致性, 我还是写了断开服务的函数, 不过是没有效果的.各位可以自己尝试一下, 就能理解了.
// 连接服务(Service)UUID并搜索支持的特征(Characteristic)UUID
QList<QList<QString>> BluetoothPort::serviceConnect(QString serviceUUIDStr, int ms_timeout)
{QList<QList<QString>> ret;// 遍历储存的内容查找符合的内容const QBluetoothUuid *serviceUUID = nullptr;for (int i=0; i<listServicesUuid.size(); i++) {if (listServicesUuid.at(i).toString() == serviceUUIDStr) {serviceUUID = &listServicesUuid.at(i);break;}}// 保护措施if (serviceUUID == nullptr)return ret;if (deviceState() == false)return ret;// 清空列表, 下面从新添加readArray.clear();lowEnergyCharacteristicRead = "";listLowEnergyCharacteristicRead.clear();listLowEnergyCharacteristicWrite.clear();// 删除上一个指针if (lowEnergyService)delete lowEnergyService;//创建服务lowEnergyService = lowEnergyController->createServiceObject(serviceUUID[0], this); // 扫描完uuid后才有效if (lowEnergyService == nullptr) {if (qDebug_ok) qDebug() << ("创建 lowEnergyService 失败");return ret; // 返回失败}// 关闭所有信号disconnect(lowEnergyService, nullptr, nullptr, nullptr);// 创建并连接到其信号connect(lowEnergyService, &QLowEnergyService::characteristicChanged, this, [this](const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue){if (qDebug_ok) qDebug() << "lowEnergyService:characteristicChanged:BLE设备:" << characteristic.uuid().toString()<< "; 类型:" << characteristic.properties()<< "; 特性值发生变化:" << newValue.toHex();if (characteristic.uuid().toString() == lowEnergyCharacteristicRead) // 符合特指值, 累加数据readArray.append(newValue);});connect(lowEnergyService, &QLowEnergyService::characteristicRead, this, [this](const QLowEnergyCharacteristic &characteristic, const QByteArray &value){if (qDebug_ok) qDebug() << "lowEnergyService:characteristicRead:BLE设备:" << characteristic.uuid().toString()<< "; 类型:" << characteristic.properties()<< "; 特性值读取到的值:" << value.toHex();});connect(lowEnergyService, &QLowEnergyService::characteristicWritten, this, [this](const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue){if (qDebug_ok) qDebug() << "lowEnergyService:characteristicWritten:BLE设备:" << characteristic.uuid().toString()<< "; 类型:" << characteristic.properties()<< "; 特性值成功写入值:" << newValue.toHex();});connect(lowEnergyService, &QLowEnergyService::descriptorRead, this, [this](const QLowEnergyDescriptor &descriptor, const QByteArray &value){if (qDebug_ok) qDebug() << "lowEnergyService:descriptorRead:BLE设备:" << descriptor.name()<< "; 描述符成功返回其值:" << value.toHex();});connect(lowEnergyService, &QLowEnergyService::descriptorWritten, this, [this](const QLowEnergyDescriptor &descriptor, const QByteArray &newValue){if (qDebug_ok) qDebug() << "lowEnergyService:descriptorWritten:BLE设备:" << descriptor.name()<< "; 描述符成功写入值:" <<  newValue.toHex();});connect(lowEnergyService, QOverload<QLowEnergyService::ServiceError>::of(&QLowEnergyService::error), [this](QLowEnergyService::ServiceError newError){if (qDebug_ok) qDebug() << "lowEnergyService:error:发生错误:" << newError; });connect(lowEnergyService, &QLowEnergyService::stateChanged, this, [this](QLowEnergyService::ServiceState newState){if (qDebug_ok) qDebug() << "lowEnergyService:stateChanged:服务的状态发生变化:" << newState;if (serviceState() == true) {QTimer::singleShot(BLE_SLOT_DELAY_CONN_MS, this, [this](void) {if (qDebug_ok) qDebug() << "lowEnergyService:stateChanged:稍后延迟后查询:" << rwCharacteristics();});}});// 启动服务, 特征和服务所包含的描述符的发现. 发现过程通过 stateChanged() 信号指示.QTimer::singleShot(BLE_SLOT_DELAY_CONN_MS, this, [this](void) {if (qDebug_ok) qDebug() << ("开始连接 (服务) 并扫描 (特征)");lowEnergyService->discoverDetails();});ms_timeout += BLE_SLOT_DELAY_CONN_MS*2; // 累加上延迟的时间// 等待扫描完成, 不为空才等待if (ms_timeout != 0) {QElapsedTimer timer;timer.start();while (timer.elapsed() < ms_timeout) {QCoreApplication::processEvents(QEventLoop::AllEvents, 50); // 它允许事件循环在调用它的位置暂时接管,处理所有挂起的事件,然后立即返回//QThread::yieldCurrentThread(); // 可以使用此函数来让出CPU时间片,但不推荐在长时间阻塞循环中使用if (serviceState() == true && // 没有扫描完成标记位, 为了不误判, 需要确保列表不为空(listLowEnergyCharacteristicRead.size() != 0 ||listLowEnergyCharacteristicWrite.size() != 0)) {QList<QString> tempRead;QList<QString> tempWrite;for (QLowEnergyCharacteristic &ch : listLowEnergyCharacteristicRead)tempRead.append(ch.uuid().toString());for (QLowEnergyCharacteristic &ch : listLowEnergyCharacteristicWrite)tempWrite.append(ch.uuid().toString());ret.append(tempRead);ret.append(tempWrite);return ret; // 连接成功才执行}}}serviceDisconnect(); // 上面没有成功, 这里就删除对象return ret; // 返回失败
}// 断开服务对象
void BluetoothPort::serviceDisconnect(void)
{// 已知问题: 蓝牙中 服务貌似是可以共存的, 而且服务对象创建并发现特征后, 即使删除对象也没法再次发现特征.// 意味着正确的使用方法, 应该是连接蓝牙, 成功后就一次性获取所有服务和包含的所有特征. 然后再挑选// 删除上一个指针if (lowEnergyService) {disconnect(lowEnergyService, nullptr, nullptr, nullptr);delete lowEnergyService;lowEnergyService = nullptr;}
}// 获取服务对象状态
bool BluetoothPort::serviceState(void)
{if (lowEnergyService == nullptr)return false;if (lowEnergyService->state() != QLowEnergyService::ServiceDiscovered) // 连接成功才执行return false;return true;
}

8.读写数据

  • 发现特征后就可以直接写描述符了.读取数据其实就是读取特征值的变化, 特征值的变化会产生信号被捕捉到.前提是需要写入描述符打开通知功能才会产生信号.
  • 写入数据其实也是往可写特征值内写数据.
  • 不同蓝牙的服务,特征值,描述符可能不一样!!!
// 内含 更新读写 特征值(Characteristic) 和 读写 描述符(Descriptor)
QByteArray BluetoothPort::rwCharacteristics(QByteArray writeArray, int ms_timeout,QString readCharacteristicStr, QString writeCharacteristicStr)
{readArray.clear();lowEnergyCharacteristicRead = "";// 保护措施if (deviceState() == false)return readArray;// 前提条件:服务详情已经被发现if (serviceState() == false)return readArray;listLowEnergyCharacteristicRead.clear(); // 清空列表, 下面从新添加listLowEnergyCharacteristicWrite.clear();// 更新特征值列表QList<QLowEnergyCharacteristic> chars = lowEnergyService->characteristics();for (const QLowEnergyCharacteristic &ch : chars) {if (qDebug_ok) qDebug() << "特性类型:" << ch.properties() << "; uuid:" <<ch.uuid().toString();if (ch.isValid()) {if (ch.properties() & QLowEnergyCharacteristic::Notify) {QLowEnergyDescriptor cccd = ch.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration);if (cccd.isValid()) {listLowEnergyCharacteristicRead.append(ch); // 保存可读特征值lowEnergyService->writeDescriptor(cccd, QByteArray::fromHex("0100")); // 修改描述符 打开通知}}if (ch.properties() & QLowEnergyCharacteristic::WriteNoResponse ||  ch.properties() & QLowEnergyCharacteristic::Write) {listLowEnergyCharacteristicWrite.append(ch); // 保存可写特征值}if (ch.properties() & QLowEnergyCharacteristic::Read) {lowEnergyService->readCharacteristic(ch); // 读取特性值 // 还没遇到过}}}// 写入特征值 // 不为空才写入if (writeArray.size() != 0 && writeCharacteristicStr != "") {for (const QLowEnergyCharacteristic &ch : listLowEnergyCharacteristicWrite) {if (ch.uuid().toString() == writeCharacteristicStr) {QLowEnergyService::WriteMode mode = (ch.properties() & QLowEnergyCharacteristic::WriteNoResponse) ? \(QLowEnergyService::WriteWithoutResponse) : (QLowEnergyService::WriteWithResponse); // 判断写入类型 // 符合才执行lowEnergyService->writeCharacteristic(ch, writeArray, mode); // 写入特性值break;}}}// 读取特征值 // 不为空才等待lowEnergyCharacteristicRead = readCharacteristicStr; // 拷贝 uuid 字符串QElapsedTimer timer;int readArrayLen = 0;while (ms_timeout != 0 && readCharacteristicStr != "") {timer.start(); // 注意, 这里的超时时间指两次数据收到的间隔, 不是总时间while (timer.elapsed() < ms_timeout) {QCoreApplication::processEvents(QEventLoop::AllEvents, 50); // 它允许事件循环在调用它的位置暂时接管,处理所有挂起的事件,然后立即返回//QThread::yieldCurrentThread(); // 可以使用此函数来让出CPU时间片,但不推荐在长时间阻塞循环中使用}if (readArrayLen != readArray.size())readArrayLen = readArray.size(); // 如果长度没变就是没收到elsebreak;}return readArray;
}

9. 总结

我手上有3款蓝牙,它们的服务和特征值居然都不一样,而且有其中一个一直不能写入特征值和描述符.好巧不巧的是我一开始就是拿着这个不能用的蓝牙测试,导致我一度怀疑人生,代码怎么都跑不通.后来换了两个蓝牙后就成功了.不过我用手机小程序和手机app都是能连上的,所以可能程序还是有点问题.目前代码至少是能用了,

  • 暂时告一段落了.

http://www.ppmy.cn/server/170464.html

相关文章

探索火山引擎 DeepSeek-R1 满血版:流畅、高效的 AI 开发体验

方舟大模型体验中心全新上线&#xff0c;免登录体验满血联网版Deep Seek R1 模型及豆包最新版模型》https://www.volcengine.com/experience/ark?utm_term202502dsinvite&acDSASUQY5&rcWY1FIKKD 大家好&#xff01;最近我有幸试用了火山引擎推出的 DeepSeek-R1 满血版…

国产编辑器EverEdit - 文本编辑器的关键特性:文件变更实时监视,多头编辑不掉坑

1 监视文件变更 1.1 应用场景 某些时候&#xff0c;用户会使用多个编辑器打开同一个文件&#xff0c;如果在A编辑器修改保存&#xff0c;但是B编辑器没有重新打开&#xff0c;直接在B编辑器修改再保存&#xff0c;则可能造成在A编辑器中修改的内容丢失&#xff0c;因此&#x…

pytorch入门级项目--基于卷积神经网络的数字识别

文章目录 前言1.数据集的介绍2.数据集的准备3.数据集的加载4.自定义网络模型4.1卷积操作4.2池化操作4.3模型搭建 5.模型训练5.1选择损失函数和优化器5.2训练 6.模型的保存7.模型的验证结语 前言 本篇博客主要针对pytorch入门级的教程&#xff0c;实现了一个基于卷积神经网络&a…

《Mycat核心技术》第17章:实现MySQL的读写分离

作者&#xff1a;冰河 星球&#xff1a;http://m6z.cn/6aeFbs 博客&#xff1a;https://binghe.gitcode.host 文章汇总&#xff1a;https://binghe.gitcode.host/md/all/all.html 星球项目地址&#xff1a;https://binghe.gitcode.host/md/zsxq/introduce.html 沉淀&#xff0c…

Qt开发④Qt常用控件_上_QWdget属性+按钮类控件

目录 1. 控件概述和发展 2. QWidget 核心属性 2.1 核心属性概览 2.2 enabled 是否可用 2.3 geometry 位置尺寸 2.4 windowTitle 标题 2.5 windowIcon 图标 2.6 windowOpacity 不透明度 2.7 cursor 光标 2.8 font 字体 2.9 toolTip 鼠标悬停提示 2.10 focusPolicy 焦…

1688代采下单API接口使用指南:实现商品采集与自动化下单

在电商领域&#xff0c;1688平台作为阿里巴巴旗下的批发采购平台&#xff0c;为众多商家提供了丰富的货源选择。为了提升采购效率&#xff0c;许多商家选择通过API接口实现商品采集与自动化下单。本文将详细介绍如何使用1688代采下单API接口&#xff0c;帮助开发者快速上手并实…

c++入门-------命名空间、缺省参数、函数重载

C系列 文章目录 C系列前言一、命名空间二、缺省参数2.1、缺省参数概念2.2、 缺省参数分类2.2.1、全缺省参数2.2.2、半缺省参数 2.3、缺省参数的特点 三、函数重载3.1、函数重载概念3.2、构成函数重载的条件3.2.1、参数类型不同3.2.2、参数个数不同3.2.3、参数类型顺序不同 前言…

PTA:使用指针方式求一个给定的m×n矩阵各行元素之和

本题要求编写程序&#xff0c;使用指针方式求一个给定的mn矩阵各行元素之和。&#xff08;例如&#xff1a;scanf("%d", *(matrix i) j); // 使用指针方式访问二维数组元素&#xff09; 输入格式: 输入第一行给出两个正整数m和n&#xff08;1<m<6, 1<n&…