蓝牙低功耗扫描器

一个用于浏览蓝牙低功耗外围设备内容的程序。该示例演示了使用所有 Qt 蓝牙 低功耗类的使用。

蓝牙低功耗扫描器示例展示了如何使用 Qt 蓝牙 API 开发蓝牙低功耗应用。该应用涵盖了对低功耗设备的扫描、扫描它们的服务和读取服务特性和描述符。

此示例介绍了以下 Qt 类

此示例可以与任何任意蓝牙低功耗外围设备一起使用。它创建了一个所有服务和特性及描述符的快照,并将它们展示给用户。因此应用程序提供了轻松浏览外围设备提供的内容的便捷方式。

运行示例

要从 Qt Creator 运行示例,打开 欢迎 模式并从 示例 中选择示例。有关更多信息,请访问 构建和运行示例

请求使用蓝牙的权限

在某个平台上,需要明确授权使用蓝牙的权限。示例使用 BluetoothPermission QML 对象来检查和请求所需的权限。

BluetoothPermission {
    id: permission
    communicationModes: BluetoothPermission.Access
    onStatusChanged: {
        if (permission.status === Qt.PermissionStatus.Denied)
            Device.update = "Bluetooth permission required"
        else if (permission.status === Qt.PermissionStatus.Granted)
            devicesPage.toggleDiscovery()
    }
}

当用户尝试启动设备发现且权限状态为 Undetermined 时,将触发权限请求对话框。

onButtonClick: {
    if (permission.status === Qt.PermissionStatus.Undetermined)
        permission.request()
    else if (permission.status === Qt.PermissionStatus.Granted)
        devicesPage.toggleDiscovery()
}

如果用户授权了权限,则启动设备发现。否则,应用程序将无法使用。

扫描设备

第一步是找到所有外围设备。可以使用 QBluetoothDeviceDiscoveryAgent 类找到设备。通过调用 start() 开始发现过程。每个新设备都通过 deviceDiscovered() 信号进行广播

discoveryAgent = new QBluetoothDeviceDiscoveryAgent(this);
discoveryAgent->setLowEnergyDiscoveryTimeout(25000);
connect(discoveryAgent, &QBluetoothDeviceDiscoveryAgent::deviceDiscovered,
        this, &Device::addDevice);
connect(discoveryAgent, &QBluetoothDeviceDiscoveryAgent::errorOccurred,
        this, &Device::deviceScanError);
connect(discoveryAgent, &QBluetoothDeviceDiscoveryAgent::finished,
        this, &Device::deviceScanFinished);
connect(discoveryAgent, &QBluetoothDeviceDiscoveryAgent::canceled,
        this, &Device::deviceScanFinished);
discoveryAgent->start(QBluetoothDeviceDiscoveryAgent::LowEnergyMethod);

下面的 addDevice() 槽在发现新设备时触发。它筛选出所有具有 QBluetoothDeviceInfo::LowEnergyCoreConfiguration 标志的设备,并将它们添加到用户看到的列表中。由于对同一设备可能发现更多详细信息,因此 deviceDiscovered() 信号可能多次发射,这里我们匹配这些设备发现,以便用户只看到单个设备

void Device::addDevice(const QBluetoothDeviceInfo &info)
{
    if (info.coreConfigurations() & QBluetoothDeviceInfo::LowEnergyCoreConfiguration) {
        auto devInfo = new DeviceInfo(info);
        auto it = std::find_if(devices.begin(), devices.end(),
                               [devInfo](DeviceInfo *dev) {
                                   return devInfo->getAddress() == dev->getAddress();
                               });
        if (it == devices.end()) {
            devices.append(devInfo);
        } else {
            auto oldDev = *it;
            *it = devInfo;
            delete oldDev;
        }
        emit devicesUpdated();
    }
}

设备列表可能看起来像下面的图片。

注意:远程设备主动宣布其存在是先决条件。

连接到服务

用户从列表中选择设备后,应用程序连接到设备并扫描所有服务。使用 QLowEnergyController 类连接到设备。当收到 QLowEnergyController::connectToDevice() 函数触发的连接过程或出现错误时,该过程持续到收到 QLowEnergyController::connected() 信号为止

if (!controller) {
    // Connecting signals and slots for connecting to LE services.
    controller = QLowEnergyController::createCentral(currentDevice.getDevice(), this);
    connect(controller, &QLowEnergyController::connected,
            this, &Device::deviceConnected);
    connect(controller, &QLowEnergyController::errorOccurred, this, &Device::errorReceived);
    connect(controller, &QLowEnergyController::disconnected,
            this, &Device::deviceDisconnected);
    connect(controller, &QLowEnergyController::serviceDiscovered,
            this, &Device::addLowEnergyService);
    connect(controller, &QLowEnergyController::discoveryFinished,
            this, &Device::serviceScanDone);
}

if (isRandomAddress())
    controller->setRemoteAddressType(QLowEnergyController::RandomAddress);
else
    controller->setRemoteAddressType(QLowEnergyController::PublicAddress);
controller->connectToDevice();

connected() 信号触发的槽立即调用 QLowEnergyController::discoverServices() 以在连接的外围设备上启动服务发现。

controller->discoverServices();

结果列表将呈现给用户。以下图片显示了选择 SensorTag 设备时的结果。视图列出服务的名称、是基本服务还是次要服务和确定服务类型的 UUID。

一选择服务,就创建与其相关的 QLowEnergyService 实例以允许与其交互

QLowEnergyService *service = controller->createServiceObject(serviceUuid);
if (!service) {
    qWarning() << "Cannot create service for uuid";
    return;
}

服务对象提供必要的信号和函数来发现服务详情、读取和写入特性和描述符,以及接收数据更改通知。更改通知可以由写入值或由于设备上的内部逻辑可能引发的对设备的更新所触发。在初始详细信息搜索期间,服务的状态从 RemoteService 转换到 RemoteServiceDiscovering,最终结束于 RemoteServiceDiscovered

connect(service, &QLowEnergyService::stateChanged,
        this, &Device::serviceDetailsDiscovered);
service->discoverDetails();
setUpdate(u"Back\n(Discovering details...)"_s);

读取服务数据

选择服务后,会显示服务详情。每个特性都列出其名称、UUID、值、句柄和属性。

可以通过 QLowEnergyService::characteristics() 获取服务特性,然后通过 QLowEnergyCharacteristic::descriptors() 获取每个描述符。

const QList<QLowEnergyCharacteristic> chars = service->characteristics();
for (const QLowEnergyCharacteristic &ch : chars) {
    auto cInfo = new CharacteristicInfo(ch);
    m_characteristics.append(cInfo);
}

尽管示例应用程序不显示描述符,但它使用描述符来确定无法根据其 UUID 识别的单独特性的名称。获取名称的第二种方式是存在 QBluetoothUuid::DescriptorType::CharacteristicUserDescription 类型的描述符。下面的代码演示了如何实现这一点

QString name = m_characteristic.name();
if (!name.isEmpty())
    return name;

// find descriptor with CharacteristicUserDescription
const QList<QLowEnergyDescriptor> descriptors = m_characteristic.descriptors();
for (const QLowEnergyDescriptor &descriptor : descriptors) {
    if (descriptor.type() == QBluetoothUuid::DescriptorType::CharacteristicUserDescription) {
        name = descriptor.value();
        break;
    }
}

示例项目 @ code.qt.io

© 2024 Qt 公司有限公司。此处包含的文档贡献是各自所有者的版权所有。此处提供的文档是根据自由软件基金会发布的 GNU 自由文档许可证版本 1.3 的条款许可的。Qt 和相关标志是芬兰和/或世界其他地区的 Qt 公司商标。所有其他商标均为其各自所有者所有。