3. 广播数据包格式:AD Structure、AD Type 定义、完整/缩短本地名称、TX Power Level、Service UUID

好,咱们今天聊聊广播包里的数据到底是怎么组织的。说白了,BLE 设备在广播的时候,不是随便扔一堆字节出去,而是有一套严格的格式。我刚开始啃蓝牙协议栈的时候,就被这堆 AD Structure 搞得有点晕,后来调了几个实际项目,才摸清楚门道。

3.1 AD Structure 基本结构

每个广播数据单元,都遵循一个简单的套路:Length + AD Type + AD Data。你想想看,这不就是典型的 TLV(Type-Length-Value)结构嘛。

  • Length:1 字节,表示后面 AD Type 和 AD Data 的总长度(不包含自身)。
  • AD Type:1 字节,告诉接收方这段数据是什么类型。
  • AD Data:N 字节,具体的数据内容。

举个例子,一个完整的广播包可能包含多个这样的 AD Structure,它们一个接一个地排列在广播数据区里。我记得有一次调试一个 Beacon 设备,发现手机扫描不到,最后定位到是 AD Structure 的 Length 字段算错了,多写了一个字节,导致整个包解析失败。嗯,这种坑踩过一次就记住了。

核心要点:广播数据区最多 31 字节,所有 AD Structure 的总长度不能超过这个限制。如果数据太多,就得考虑用 Scan Response 来补充。

3.2 AD Type 定义

蓝牙 SIG 定义了一大堆 AD Type,每个都有固定的编号。咱们做开发,常用的其实就那么几个。我整理了一张表,方便你对照着看。

AD Type 值 名称 用途
0x01 Flags 广播模式、发现模式等标志位
0x02 / 0x03 Incomplete / Complete Service UUID (16-bit) 16 位服务 UUID 列表
0x04 / 0x05 Incomplete / Complete Service UUID (32-bit) 32 位服务 UUID 列表
0x06 / 0x07 Incomplete / Complete Service UUID (128-bit) 128 位服务 UUID 列表
0x08 / 0x09 Shortened / Complete Local Name 设备本地名称
0x0A TX Power Level 发射功率电平
0xFF Manufacturer Specific Data 厂商自定义数据

这里有个细节要注意:Flags(0x01)通常是广播包里的第一个 AD Structure。为什么?因为接收方需要先知道这个设备支持什么模式,才能决定后续怎么处理。我个人习惯,只要做广播,一定会带上 Flags,哪怕只是最简单的可发现模式。

3.3 完整本地名称 vs 缩短本地名称

这两个 AD Type 很容易搞混。我简单说一下区别:

  • Complete Local Name (0x09):完整的设备名称,长度不限。但如果名称太长,超过了广播包剩余空间,就只能用缩短版。
  • Shortened Local Name (0x08):名称被截断,只保留前 N 个字节。接收方看到这个类型,就知道名称不完整。

你可能会问:为什么不用完整的?原因很简单——广播包只有 31 字节。如果你既要放 Service UUID,又要放 TX Power Level,还要放厂商数据,留给名称的空间可能就只剩几个字节了。这时候,缩短名称就成了唯一的选择。

我的建议:如果设备名称超过 8 个字符,优先考虑用缩短名称,把宝贵的字节留给更重要的数据。我曾经在一个项目中,为了把完整的 20 字节名称塞进去,挤掉了 Service UUID,结果手机端无法识别设备类型,折腾了两天才发现是这个问题。

3.4 TX Power Level

TX Power Level(0x0A)表示设备的发射功率,单位是 dBm。这个值有什么用?最典型的场景就是距离估算。

接收方拿到这个值,再结合自己测到的 RSSI,就能大致估算出距离。公式很简单:

距离 ≈ 10 ^ ((TX Power - RSSI) / (10 * n))

其中 n 是环境衰减因子,一般取 2~4。当然,这个估算很粗糙,受环境影响很大。我在做室内定位项目时试过,空旷环境下误差在 1~2 米,但到了有遮挡的仓库里,误差能到 5 米以上。

注意:TX Power Level 的值是在 0 米处测量的参考值,不是设备当前的实际发射功率。不同设备、不同天线设计,这个值都不一样。如果你要做距离估算,最好在产线上校准一下每个设备的 TX Power。

3.5 Service UUID

Service UUID 是广播包里最重要的信息之一。它告诉接收方:我这个设备支持哪些服务。比如,一个心率监测器会广播 Heart Rate Service 的 UUID(0x180D),手机扫描到就知道这是个健康设备。

UUID 有三种长度:

  • 16 位 UUID:由蓝牙 SIG 定义的标准服务,比如 0x1800(Generic Access)、0x1801(Generic Attribute)。
  • 32 位 UUID:用于自定义服务,但不太常见。
  • 128 位 UUID:完全自定义的服务,格式如 0000XXXX-0000-1000-8000-00805F9B34FB。

广播包里可以放多个 UUID,但要注意空间限制。我一般会优先放 16 位的标准 UUID,因为省空间。如果必须用 128 位 UUID,那就只能放一两个,剩下的靠 Scan Response 补充。

这里有个小技巧:如果你用 16 位 UUID,AD Type 用 0x02(不完整列表)或 0x03(完整列表)。不完整列表的意思是,设备还有更多 UUID 没放进来,接收方需要进一步连接才能获取完整列表。完整列表则表示所有 UUID 都在这里了。

3.6 知识结构图

下面这张图,帮你把广播数据包的层次结构理清楚。

广播数据包结构 广播数据包 (31 字节) AD Structure 1 Length (1 字节) AD Type (1 字节) AD Data (N 字节) AD Type 示例:0x09 (Complete Local Name) Flags (0x01) Service UUID (0x02-0x07) Local Name (0x08/0x09) TX Power (0x0A) 注意:多个 AD Structure 依次排列,总长度不超过 31 字节 如果数据超长,可以使用 Scan Response 扩展

3.7 实战中的避坑指南

最后,分享几个我在项目中踩过的坑:

  • Length 字段别算错:Length 只包含 AD Type 和 AD Data,不包含自身。我见过有人把整个 AD Structure 的长度都算进去,结果解析器直接报错。
  • Flags 尽量放第一个:虽然协议没强制,但很多蓝牙协议栈会默认第一个 AD Structure 是 Flags。如果你不放,某些手机可能无法正确识别设备。
  • 缩短名称要加后缀:如果用缩短名称,最好在名称末尾加个省略号或特殊标记,让接收方知道名称不完整。不过这个不是强制的,看具体需求。
  • TX Power 要校准:不同设备的射频性能不一样,TX Power 值最好在产线上逐台校准。我曾经偷懒用了统一值,结果距离估算偏差很大,被客户投诉了。