【蓝牙】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.GattService1org.bluez.GattCharacteristic1org.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()Valueay 直接放入 "{sv}"

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

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

  • 发现与连接

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

    • bluetoothctlmenu gattlist-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 导致解析异常(务必保证 Valueay 仅单层 variant)。

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

3.1 Linux 侧(外设)

  • 特征声明包含 Flagsnotifyindicate
  • 启用通知:在 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)
      • 写入 CCCDdescriptor.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 观察 PropertiesChangedbtmon 观察 ATT 写入与后续通知时序。

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

5.1 Linux 侧(中心)

  • bluetoothctl 快速验证:
    • scan onconnect <MAC>menu gattlist-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/indicatewrite
  • 快速验证可用 nRF Connect/LightBlue 等通用 App。

6. 验证与诊断清单

  • 控制器启用:btmgmt power onbtmgmt le on
  • 扫描与连接:bluetoothctlscan onconnect <MAC>
  • 订阅通知:menu gatt → 选择特征 → notify on
  • 读写测试:readwrite <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. 上线建议

  • 建立"金路径"脚本:用 bluetoothctlbtmon 固化验证步骤,更新后快速回归。
  • 手机侧先用通用 App(nRF Connect/LightBlue)快速验证,再集成到自研 App。
  • 为数据帧定义清晰的协议文档与测试用例,确保两端编码一致。
相关推荐
FoldWinCard6 小时前
D5 Linux 网络及端口命令
linux·运维·服务器
零涂毕业设计8 小时前
毕业设计模块开发-OLED显示屏(IIC协议0.96寸)STM32 ESP32 FPGA Linux驱动免费分享
linux·stm32·单片机·嵌入式·esp32·fpga·oled
tedcloud1238 小时前
OmniRoute怎么部署?开源AI模型路由平台Linux部署教程
linux·服务器·人工智能·开源·音视频
霸道流氓气质8 小时前
KMS 密钥管理服务(Key Management Service)原理与实践
linux·服务器·数据库
SakitamaX8 小时前
LVS(Linux Virtual Server)概念详解
linux·运维·lvs
小张同学a.8 小时前
Linux系统管理
linux·运维
阿龙的工作记录9 小时前
深入理解Linux设备驱动模型
linux·嵌入式硬件·工作记录
代码村新手9 小时前
Linux的基本指令
linux·运维·服务器
hkj88089 小时前
Ubuntu 切换java版本
java·linux·ubuntu
撩得Android一次心动11 小时前
Linux编程笔记3【个人用】
linux·笔记