【蓝牙】BLE 数据收发实战指南(手机 App ↔ 嵌入式 Linux/BlueZ)

BLE 数据收发实战指南(手机 App ↔ 嵌入式 Linux/BlueZ)

本文面向在手机 App(Android/iOS)与嵌入式 Linux(BlueZ)之间实现 BLE(Bluetooth Low Energy)数据收发的工程实践,涵盖基础概念、接口映射、两类典型拓扑的完整流程、命令与验证、常见问题与排查,以及上线建议。

1. 基础概念与方向

  • 角色与职责
    • Central(中心):负责扫描、连接、发现服务;常发起读写与订阅通知。
    • Peripheral(外设):暴露 GATT 服务/特征/描述符;常被读写并主动上报通知/指示。
  • GATT/ATT 数据通道
    • 订阅上报:notify/indicate 由外设主动上报;由 CCCD(0x2902)控制。
    • 主动交互:read/write 由中心主动发起;外设读写回调中处理。
  • 关键对象(BlueZ D-Bus)
    • org.bluez.GattService1、org.bluez.GattCharacteristic1、org.bluez.GattDescriptor1
    • 特征属性:UUID(s)、Service(o)、Value(ay)、Notifying(b)、Flags(as)
    • 特征方法:ReadValue(a{sv} → ay)、WriteValue(ay, a{sv})、StartNotify()、StopNotify()
  • CCCD(Client Characteristic Configuration Descriptor)
    • 订阅通知:中心向 CCCD 写入 0x0001(notify)或 0x0002(indicate)。
    • 取消订阅:写入 0x0000。

2. BlueZ 接口与信号格式(Linux 外设)

  • 特征 Value 类型:ay(array of bytes)。
  • 通知事件通过 PropertiesChanged 信号上报,签名:s a{sv} as
    • 正确结构示例:
      • string "org.bluez.GattCharacteristic1"
      • array [ dict entry( string "Value" variant array of bytes [ 12 34 56 78 ] ) ]
      • array [ ]
  • 重要实践:构造 a{sv} 时应直接以 ay 作为 "{sv}" 的值,由 D-Bus 框架封装成单层 variant。不要手动 g_variant_new_variant(ay) 再传入,否则会出现 variant-of-variant(双层封装),导致上位机解析异常。
    • 参考实现路径:vendor/linkric/acs-device-sdk/BluetoothImplementations/BlueZ/src/BlueZLEGattCharacteristic.cpp
    • 方法:emitPropertiesChanged() 将 Value 以 ay 直接放入 "{sv}"。

2.1 从发现到数据收发的流程图(端到端)

详细步骤说明(基础通信过程)

  • 发现与连接

    • 中心启用控制器: btmgmt power on、btmgmt le on
    • 扫描:bluetoothctl → scan on,观察目标设备的广播(名称/服务 UUID/厂商字段)。
    • 连接:bluetoothctl connect <MAC>,建立 LE 连接(可在 btmon 中看到 LE Connection Complete)。
  • 服务与特征发现

    • bluetoothctl → menu gatt → list-attributes 查看服务与特征(GATT/ATT 句柄)。
    • 手机 App(Android/iOS)在连接后自动进行服务发现(BluetoothGatt#discoverServices / CBPeripheral#discoverServices),并定位目标特征与 CCCD(0x2902)。
  • 订阅通知 / 指示

    • 中心写入 CCCD:
      • Android:descriptor.setValue(ENABLE_NOTIFICATION_VALUE/ENABLE_INDICATION_VALUE) → writeDescriptor
      • iOS:peripheral.setNotifyValue(true, for: characteristic)
    • 外设(Linux/BlueZ)在 StartNotify 设定 Notifying=true。随后调用 setValue(...) 会发出 PropertiesChanged,ATT 层表现为 Handle Value Notification/Indication。
  • 数据发送(外设→中心)

    • 外设侧将待发字节流放入特征的 Value(ay) 并触发 PropertiesChanged:
      • 信号结构:s a{sv} as,其中 Value 必须是 variant(ay) 的单层封装。
      • 上层解析期望:array of bytes [...]。
    • 中心在回调中接收:
      • Android:onCharacteristicChanged
      • iOS:didUpdateValueFor
  • 数据写入(中心→外设)

    • 中心构造要写入的 ay 字节流:
      • Android:writeCharacteristic
      • iOS:writeValue(data, type: .withResponse/.withoutResponse)
    • 外设在 WriteValue 中读取参数并执行业务处理;需要返回结果时,结合通知/指示上报:setValue(result_bytes)。
  • 诊断与验证

    • D-Bus 信号:dbus-monitor --system "interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'"
    • 低层抓包:sudo btmon,观察 ATT 读写与通知时序。
    • 常见问题:CCCD 写入失败、Notifying 未更新、variant-of-variant 导致解析异常(务必保证 Value 的 ay 仅单层 variant)。

3. 案例 A:手机 App 为 Central,Linux/BlueZ 为 Peripheral

3.1 Linux 侧(外设)

  • 特征声明包含 Flags:notify 或 indicate。
  • 启用通知:在 StartNotify 中设置 Notifying=true;之后每次调用 setValue(...) 都会触发 PropertiesChanged 信号(通知)。
  • 验证信号:
    • dbus-monitor --system "interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'" | grep -A3 /org/bluez/app/service0/chrc1
    • 低层抓包:sudo btmon 观察 ATT Handle Value Notification/Indication。
  • 快速命令(bluetoothctl):
    • scan on → 发现并 connect <MAC> → menu gatt → 选择特征后 notify on。

3.2 手机 App 侧(中心)

  • Android(BluetoothGatt)
    • 流程:连接→发现服务→查找特征与 CCCD。
    • 订阅示例:
      • gatt.setCharacteristicNotification(characteristic, true)
      • 写入 CCCD:descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);gatt.writeDescriptor(descriptor)。
    • 接收:onCharacteristicChanged 回调中读取字节数组。
  • iOS(CoreBluetooth)
    • 流程:连接→discoverServices / discoverCharacteristics。
    • 订阅:peripheral.setNotifyValue(true, for: characteristic)。
    • 接收:peripheral(_:didUpdateValueFor:error:) 回调读取 characteristic.value。

3.3 数据帧约定(建议)

  • 在字节流首部编码协议版本与命令字,便于扩展与兼容:
    • 示例:[magic=0x5AA5][ver=0x11][cmd=0x01][len=...][payload][CRC]

4. 案例 B:手机 App 写入命令(Central→Peripheral),Linux 回传结果

4.1 手机侧(写入)

  • Android:characteristic.setValue(bytes) → gatt.writeCharacteristic(characteristic);是否需要 withResponse 视特征 Flags 而定。
  • iOS:peripheral.writeValue(data, for: characteristic, type: .withResponse/.withoutResponse)。

4.2 Linux 侧(接收与反馈)

  • 在特征的 WriteValue 中读取 ay 参数,进行业务处理。
  • 需要反馈时:在处理完成后 setValue(result_bytes) 并确保已订阅通知(Notifying=true),中心即可收到结果通知。

4.3 验证

  • dbus-monitor 观察 PropertiesChanged;btmon 观察 ATT 写入与后续通知时序。

5. 案例 C:Linux 为 Central(BlueZ + 工具/bleak),手机/设备为 Peripheral

5.1 Linux 侧(中心)

  • bluetoothctl 快速验证:
    • scan on → connect <MAC> → menu gatt → list-attributes
    • 选择目标特征后:notify on 订阅、read/write <hex bytes> 主动交互。
  • Python bleak(如需自动化):
    • 订阅:await client.start_notify(char_uuid, callback)
    • 写入:await client.write_gatt_char(char_uuid, data)

5.2 手机 App(外设)

  • 暴露自定义 GATT 服务与特征,按需支持 notify/indicate 与 write。
  • 快速验证可用 nRF Connect/LightBlue 等通用 App。

6. 验证与诊断清单

  • 控制器启用:btmgmt power on、btmgmt le on
  • 扫描与连接:bluetoothctl → scan on、connect <MAC>
  • 订阅通知:menu gatt → 选择特征 → notify on
  • 读写测试:read、write <hex bytes>
  • D-Bus 信号监控:dbus-monitor --system "interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'"
  • 低层抓包:sudo btmon

7. 常见问题与排查

  • 无法订阅通知
    • 特征 Flags 未包含 notify/indicate;CCCD 写入失败;外设未维护 Notifying 状态。
  • 收到空数据或长度异常
    • getValue() 返回空数组;写入处理未更新 m_value;两端编码约定不一致。
  • variant-of-variant 解析异常
    • 构造 a{sv} 时应直接传入 ay,让框架封装单层 variant;不要手动 g_variant_new_variant(ay) 再加入 "{sv}"。
  • 连接稳定性与功耗
    • 广播与连接参数(interval/latency/supervision timeout)需与手机端适配;高频上报场景建议限流与降采样。

8. 上线建议

  • 建立"金路径"脚本:用 bluetoothctl 与 btmon 固化验证步骤,更新后快速回归。
  • 手机侧先用通用 App(nRF Connect/LightBlue)快速验证,再集成到自研 App。
  • 为数据帧定义清晰的协议文档与测试用例,确保两端编码一致。
相关推荐
Lhan.zzZ几秒前
Linux + Qt/QML:解决 QtQuick.VirtualKeyboard.Plugins 模块加载失败与环境变量配置
linux·数据库·qt
小毛驴8505 分钟前
mvn help:effective-settings 与 mvn help:effective-pom
java·linux·前端
半杯咖啡半行码19 分钟前
Linux字符设备驱动实战:ioctl接口与GPIO操作指南
linux·开发语言
Zenova EdgeOS20 分钟前
Linux Nginx 反向代理实战:工业边缘 API 接入与安全加固
linux·nginx·安全·边缘计算·工业网关
小白鸽i35 分钟前
【Linux】已安装的软件如何重新构建deb安装包?
linux·运维·服务器
其实防守也摸鱼1 小时前
CTF Web 6 题全解 Writeup
linux·sql·学习·算法·安全·ctf·xss
qq_401700411 小时前
CAN 开源库大全(嵌入式/上位机,适配STM32、Linux、Qt,附带Github地址)
linux·stm32·开源
AAA代码批发商2 小时前
DAYS 56【Linux 驱动深度】SPI 子系统驱动开发全指南:从架构、核心数据结构到设备驱动实战
linux·驱动开发·架构·内核开发·spi 子系统
听雨笑2 小时前
终端窗口关掉了,正在跑的活会不会死:三种起法各自的下场
linux
程序员-Benothing2 小时前
Linux CPU性能分析:top、mpstat、pidstat、perf实战
linux·运维·服务器