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 知识结构图
下面这张图,帮你把广播数据包的层次结构理清楚。
3.7 实战中的避坑指南
最后,分享几个我在项目中踩过的坑:
- Length 字段别算错:Length 只包含 AD Type 和 AD Data,不包含自身。我见过有人把整个 AD Structure 的长度都算进去,结果解析器直接报错。
- Flags 尽量放第一个:虽然协议没强制,但很多蓝牙协议栈会默认第一个 AD Structure 是 Flags。如果你不放,某些手机可能无法正确识别设备。
- 缩短名称要加后缀:如果用缩短名称,最好在名称末尾加个省略号或特殊标记,让接收方知道名称不完整。不过这个不是强制的,看具体需求。
- TX Power 要校准:不同设备的射频性能不一样,TX Power 值最好在产线上逐台校准。我曾经偷懒用了统一值,结果距离估算偏差很大,被客户投诉了。