【蓝牙】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。
  • 为数据帧定义清晰的协议文档与测试用例,确保两端编码一致。
相关推荐
q***47437 分钟前
Windows 和 Linux 系统下,如何查看 Redis 的版本号?
linux·windows·redis
代码对我眨眼睛15 分钟前
Ubuntu 系统 NVIDIA 显卡驱动自动化安装全流程
linux·ubuntu·自动化
xiong2learning15 分钟前
Linux虚拟机无法使用u盘的一种可能-- 重新下载open-vm-tools + open-vm-tools-desktop解决
linux
LCG元17 分钟前
实战:一次完整的网站故障排查记录(从用户访问到数据库)
linux
xuyanqiangCode22 分钟前
Ubuntu二进制安装Apache Doris(2.1版本)
linux·ubuntu·apache
Yue丶越25 分钟前
【Python】基础语法入门(四)
linux·开发语言·python
木童66229 分钟前
Nginx 深度解析:反向代理与负载均衡、后端Tomcat
linux·运维·nginx
赖small强1 小时前
【Linux 网络基础】网络通信中的组播与广播:基础概念、原理、抓包与应用
linux·网络·broadcast·组播·广播·multicast
陌路201 小时前
Linux是如何收发网络包的?
linux·网络
带鱼吃猫2 小时前
Linux系统:策略模式实现自定义日志功能
linux·c++