第27章 Qt Bluetooth与NFC:让设备“握手”的艺术

说实话,蓝牙和NFC这两个技术,在移动端开发里已经不算新鲜了。但放到Qt桌面应用里,很多人第一反应是“有必要吗?”——我刚开始也这么想。直到有一次,客户要求用PC端软件直接读取工业设备上的蓝牙传感器数据,我才意识到,Qt在这块的生态其实比想象中成熟得多。

这一章,我们就来聊聊Qt中蓝牙和NFC的那些事。我会从最基础的设备发现讲起,再到低功耗蓝牙(BLE)的通信,最后说说NFC标签的读写。嗯,内容不少,但都是实战中能直接用的。

27.1 蓝牙设备发现:让应用“看见”周围

蓝牙设备发现,说白了就是让你的应用能扫描到附近的蓝牙设备。Qt提供了QBluetoothDeviceDiscoveryAgent这个类,用起来非常直接。

核心流程:

  • 创建QBluetoothDeviceDiscoveryAgent实例
  • 连接deviceDiscovered信号,每发现一个设备就会触发
  • 调用start()开始扫描
  • 扫描结束后,finished信号会告诉你“好了,就这些了”

我在项目中遇到过一个问题:扫描到的设备列表里,有些设备明明就在旁边,但信号强度(RSSI)却很低。后来发现是设备处于深度休眠模式,需要先唤醒才能正常通信。这个坑,后面会细说。

// 蓝牙设备发现示例
QBluetoothDeviceDiscoveryAgent *agent = new QBluetoothDeviceDiscoveryAgent(this);
connect(agent, &QBluetoothDeviceDiscoveryAgent::deviceDiscovered,
        [](const QBluetoothDeviceInfo &info) {
    qDebug() << "发现设备:" << info.name() << "地址:" << info.address().toString();
});
agent->start();

小提示:扫描过程中,如果用户关闭了蓝牙适配器,error信号会触发。记得处理这个情况,不然应用会卡住。我习惯在error信号里弹个提示框,告诉用户“请打开蓝牙”。

27.2 蓝牙通信:经典SPP与RFCOMM

经典蓝牙通信,最常用的就是串口协议(SPP)和RFCOMM。Qt里用QBluetoothSocket来实现,用法和TCP Socket非常像。

你想想看,如果你要连接一个蓝牙串口模块(比如HC-05),流程就是:

  1. QBluetoothSocket指定RFCOMM协议
  2. 连接目标设备的地址和端口
  3. 连接成功后,通过write()read()收发数据
// 蓝牙Socket通信示例
QBluetoothSocket *socket = new QBluetoothSocket(QBluetoothServiceInfo::RfcommProtocol, this);
socket->connectToService(QBluetoothAddress("00:11:22:33:44:55"), 1);
connect(socket, &QBluetoothSocket::readyRead, [socket]() {
    QByteArray data = socket->readAll();
    qDebug() << "收到数据:" << data;
});

注意:经典蓝牙通信在Android和iOS上限制较多,很多移动平台只允许BLE。如果你做的是跨平台应用,建议优先考虑低功耗蓝牙。

27.3 低功耗蓝牙(BLE):省电才是王道

BLE和经典蓝牙最大的区别是什么?说白了,就是省电。BLE设备可以靠一颗纽扣电池跑好几年。Qt里用QLowEnergyControllerQLowEnergyService来操作。

我记得第一次用BLE时,被“Service”和“Characteristic”这两个概念绕晕了。简单理解:

  • Service:设备提供的功能模块,比如“电池服务”、“心率服务”
  • Characteristic:服务里的具体数据点,比如“电池电量百分比”、“心率值”

每个Characteristic都有UUID,通过UUID就能读写对应的数据。

// BLE连接与数据读取示例
QLowEnergyController *controller = QLowEnergyController::createCentral(deviceInfo);
controller->connectToDevice();
connect(controller, &QLowEnergyController::connected, [controller]() {
    controller->discoverServices();
});
connect(controller, &QLowEnergyController::serviceDiscovered,
        [controller](const QBluetoothUuid &uuid) {
    QLowEnergyService *service = controller->createServiceObject(uuid);
    service->discoverDetails();
    connect(service, &QLowEnergyService::characteristicChanged,
            [](const QLowEnergyCharacteristic &c, const QByteArray &value) {
        qDebug() << "特征值变化:" << value.toHex();
    });
});

避坑指南:我曾经在读取BLE设备数据时,发现characteristicChanged信号死活不触发。折腾了半天,才发现需要先启用Notification(通知)。具体做法是:

QLowEnergyDescriptor notificationDesc = characteristic.descriptor(
    QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration);
service->writeDescriptor(notificationDesc, QByteArray::fromHex("0100"));

嗯,这个“0100”就是启用通知的指令。不写这一行,设备不会主动发数据给你。

27.4 NFC标签读写:近场通信的“碰一碰”

NFC(近场通信)在Qt里主要通过QNdefMessageQNearFieldManager来实现。NFC标签本质上是一个存储空间,你可以往里面写数据,也可以读出来。

常见的NFC标签类型有:

标签类型 存储容量 典型用途
NTAG213 144字节 名片、URL跳转
NTAG215 504字节 游戏角色、产品认证
NTAG216 888字节 更复杂的数据存储

读写NFC标签的流程:

  1. 创建QNearFieldManager实例
  2. 调用startDetection()开始检测NFC标签
  3. 检测到标签后,通过QNdefMessage解析或写入数据
// NFC标签读取示例
QNearFieldManager *manager = new QNearFieldManager(this);
connect(manager, &QNearFieldManager::tagDetected, [](QNearFieldTarget *target) {
    connect(target, &QNearFieldTarget::ndefMessageRead,
            [](const QNdefMessage &msg) {
        for (const QNdefRecord &record : msg) {
            qDebug() << "记录类型:" << record.type() << "数据:" << record.payload();
        }
    });
    target->readNdefMessages();
});
manager->startDetection();

个人经验:写NFC标签时,一定要先检查标签是否可写。有些标签出厂时被写保护了,强行写入会失败。我习惯在写入前先读一下标签的“能力容器”字节,确认可写后再操作。

27.5 近场通信的“碰一碰”场景

NFC最经典的场景就是“碰一碰”交互。比如,你的应用可以这样设计:

  • 用户把手机靠近NFC标签
  • 应用自动读取标签内容
  • 根据内容执行相应操作(打开网页、启动功能、验证身份等)

我在一个门禁系统项目里用过这个方案。每个门禁点放一个NFC标签,用户用手机碰一下,应用读取标签ID,然后通过蓝牙把ID发送到服务器验证。整个过程不到1秒,体验非常好。

核心逻辑图:下面这张图展示了蓝牙与NFC在Qt应用中的整体协作流程。

Qt蓝牙与NFC协作流程图 Qt应用 蓝牙模块 NFC模块 设备发现 SPP/RFCOMM通信 BLE低功耗通信 标签检测 NDEF读写 近场交互 应用层通过蓝牙和NFC模块与外部设备交互,实现数据采集与控制

27.6 实战中的坑与经验

做蓝牙和NFC开发,有几个坑是绕不开的。我在这里列一下,希望能帮你少走弯路:

  • 权限问题:Android 6.0以上需要动态申请位置权限,iOS需要Info.plist里声明蓝牙用途。Qt虽然封装了跨平台API,但权限还得自己处理。
  • 设备兼容性:不同厂商的蓝牙芯片,对BLE协议的支持程度不一样。我遇到过某款国产设备,必须用特定的MTU(最大传输单元)才能通信,否则数据会丢包。
  • NFC标签的物理限制:金属表面会屏蔽NFC信号,标签贴在金属上基本读不到。解决方案是使用“抗金属标签”,或者在标签和金属之间加一层铁氧体。
  • 调试工具:强烈推荐用nRF Connect(手机端)和LightBlue(桌面端)来调试BLE设备。它们能帮你看到设备的所有Service和Characteristic,省去很多猜测的时间。

重要提醒:NFC标签的写入次数是有限的(通常10万次左右)。如果你做的是频繁写入的应用(比如打卡系统),建议定期检查标签的剩余寿命,或者使用更耐用的标签类型。

好了,这一章的内容就到这里。蓝牙和NFC的组合,能让你的Qt应用真正“活”起来,和物理世界产生交互。下一章我们会聊到Qt Multimedia,也就是音频和视频的处理——嗯,那又是另一个有趣的话题了。


公众号:蓝海资料掘金营,微信deep3321