前言, 最近发现笔记本的蓝牙可以被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 v140
和MSVC v141
安装框内的所有东西.注意是所有东西! 有一些笔记会说只需要安装部分即可,但是我没成功,折腾半天后才发现安装全部才可以.
- 注意如果这一步安装成功后是可以在
Qt->选项->Kits
界面看到如下内容的, 点击添加也能看到MSVC
选项.请确保也有如下内容且没有显示黄色感叹号才进行下一步.
- 然后添加特定的编译链参数,确保一致
C
和C++
都需要,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
才有效的. - 在开始写代码之前, 还需要了解蓝牙的连接流程:
- 确定支持蓝牙功能, 并搜索蓝牙
设备 (BLE)
; - 连接蓝牙, 连接成功后发现支持的
服务 (Service)
; - 选择服务, 发现支持的
特征 (Characteristic)
; - 根据需要修改特征的
描述符 (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)UUID
void deviceDisconnect(void); // 断开连接低功耗蓝牙对象
bool deviceState(void); // 获取控制器对象状态
QList<QList<QString>> serviceConnect(QString serviceUUIDStr, int ms_timeout = 5000); // 连接服务(Service)UUID并搜索支持的特征(Characteristic)UUID
void 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)UUID
protected: // 保护的 只允许在本类 和 子类中访问
signals:
void deviceDiscovered(QString name, QString address); // 发现设备时产生的信号
};
// 全局变量声明
extern BluetoothPort *bluetooth_pc;
#endif // BLUETOOTHPORT_H
5.构造函数和析构函数
- 使用指针的方式创建实例, 供其他地方调用.
- 构造函数中需要初始化几个变量指针,
localDevice
和discoveryAgent
是不需要变更的,所以一开始就创建.lowEnergyController
和lowEnergyService
是根据所选蓝牙和服务创建的,初始状态要给空指针.否则后面程序删除指针时会卡死. - 析构函数貌似不写也没关系,会指针自己删除.
#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(); // 如果长度没变就是没收到
else
break;
}
return readArray;
}
9. 总结
我手上有3款蓝牙,它们的服务和特征值居然都不一样,而且有其中一个一直不能写入特征值和描述符.好巧不巧的是我一开始就是拿着这个不能用的蓝牙测试,导致我一度怀疑人生,代码怎么都跑不通.后来换了两个蓝牙后就成功了.不过我用手机小程序和手机app都是能连上的,所以可能程序还是有点问题.目前代码至少是能用了,
- 暂时告一段落了.