从上一篇GATT Profile 简介中提到过,BLE 设备工作的第一步就是向外广播数据。广播数据中带有设备相关的信息。本文主要说一下 BLE 的广播中的数据的规范以及广播包的解析。 
 
广播模式 
 
BLE 中有两种角色 Central 和 Peripheral ,也就是中心设备和外围设备。中心设备可以主动连接外围设备,外围设备发送广播或者被中心设备连接。外围通过广播被中心设备发现,广播中带有外围设备自身的相关信息。 
 
广播包有两种: 广播包 (Advertising Data)和 响应包 (Scan Response),其中广播包是每个设备必须广播的,而响应包是可选的。 数据包的格式如下图所示(图片来自官方 Spec): 每个包都是 31 字节,数据包中分为有效数据(significant)和无效数据(non-significant)两部分。 
 
有效数据部分 :包含若干个广播数据单元,称为 AD Structure 。如图中所示,AD Structure 的组成是:第一个字节是长度值 Len ,表示接下来的 Len 个字节是数据部分。数据部分的第一个字节表示数据的类型 AD Type ,剩下的 Len - 1 个字节是真正的数据 AD data 。其中 AD type 非常关键,决定了 AD Data 的数据代表的是什么和怎么解析,这个在后面会详细讲; 
无效数据部分 :因为广播包的长度必须是 31 个 byte,如果有效数据部分不到 31 自己,剩下的就用 0 补全。这部分的数据是无效的,解释的时候,忽略即可。 
广播数据格式 
 
所有的 AD type 的定义在文档 Core Specification Supplement 中。 AD Type 包括如下类型: 
 
Flags: TYPE = 0x01。这个数据用来标识设备 LE 物理连接的功能。DATA 是 0 到多个字节的 Flag 值,每个 bit 上用 0 或者 1 来表示是否为 True。如果有任何一个 bit 不为 0,并且广播包是可连接的,就必须包含此数据。各 bit 的定义如下: 
 
bit 0: LE 有限发现模式 
bit 1: LE 普通发现模式 
bit 2: 不支持 BR/EDR 
bit 3: 对 Same Device Capable(Controller) 同时支持 BLE 和 BR/EDR 
bit 4: 对 Same Device Capable(Host) 同时支持 BLE 和 BR/EDR 
bit 5..7: 预留 
Service UUID: 广播数据中一般都会把设备支持的 GATT Service 广播出来,用来告诉外面本设备所支持的 Service。有三种类型的 UUID:16 bit, 32bit, 128 bit。广播中,每种类型类型有有两个类别:完整和非完整的。这样就共有 6 种 AD Type。 
 
非完整的 16 bit UUID 列表: TYPE = 0x02; 
完整的 16 bit UUID 列表: TYPE = 0x03; 
非完整的 32 bit UUID 列表: TYPE = 0x04; 
完整的 32 bit UUID 列表: TYPE = 0x05; 
非完整的 128 bit UUID 列表: TYPE = 0x06; 
完整的 128 bit UUID 列表: TYPE = 0x07; 
Local Name: 设备名字,DATA 是名字的字符串。 Local Name 可以是设备的全名,也可以是设备名字的缩写,其中缩写必须是全名的前面的若干字符。 
 
设备全名: TYPE = 0x08 
设备简称: TYPE = 0x09 
TX Power Level: TYPE = 0x0A,表示设备发送广播包的信号强度。DATA 部分是一个字节,表示 -127 到 + 127 dBm。 
 
带外安全管理(Security Manager Out of Band):TYPE = 0x11。DATA 也是 Flag,每个 bit 表示一个功能: 
 
bit 0: OOB Flag,0 表示没有 OOB 数据,1 表示有 
bit 1: 支持 LE 
bit 2: 对 Same Device Capable(Host) 同时支持 BLE 和 BR/EDR 
bit 3: 地址类型,0 表示公开地址,1 表示随机地址 
外设(Slave)连接间隔范围:TYPE = 0x12。数据中定义了 Slave 最大和最小连接间隔,数据包含 4 个字节: 
 
前 2 字节:定义最小连接间隔,取值范围:0x0006 ~ 0x0C80,而 0xFFFF 表示未定义; 
后 2 字节:定义最大连接间隔,同上,不过需要保证最大连接间隔大于或者等于最小连接间隔。 
服务搜寻:外围设备可以要请中心设备提供相应的 Service。其数据定义和前面的 Service UUID 类似: 
 
16 bit UUID 列表: TYPE = 0x14 
32 bit UUID 列表: TYPE = 0x?? 
128 bit UUID 列表: TYPE = 0x15 
Service Data: Service 对应的数据。 
 
16 bit UUID Service: TYPE = 0x16, 前 2 字节是 UUID,后面是 Service 的数据; 
32 bit UUID Service: TYPE = 0x??, 前 4 字节是 UUID,后面是 Service 的数据; 
128 bit UUID Service: TYPE = 0x??, 前 16 字节是 UUID,后面是 Service 的数据; 
公开目标地址:TYPE = 0x17,表示希望这个广播包被指定的目标设备处理,此设备绑定了公开地址,DATA 是目标地址列表,每个地址 6 字节。 
 
随机目标地址:TYPE = 0x18,定义和前一个类似,表示希望这个广播包被指定的目标设备处理,此设备绑定了随机地址,DATA 是目标地址列表,每个地址 6 字节。 
 
Appearance:TYPE = 0x19,DATA 是表示了设备的外观。 
 
厂商自定义数据: TYPE = 0xFF,厂商自定义的数据中,前两个字节表示厂商 ID,剩下的是厂商自己按照需求添加,里面的数据内容自己定义。 
 
还有一些其他的数据,我这里就不一一列举了,有需要的可以从这个文档查阅 Core Specification Supplement 。 
 
广播数据解析 
 
在 Android 可以使用 BluetoothAdapter 来发起扫描。基本用法如下: 
 
BluetoothAdapter.LeScanCallback mLeScanCallback =   
        new BluetoothAdapter.LeScanCallback() { 
                @Override 
                public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) { 
                        // 解析广播数据 
                        parseAdvData(scanRecord); 
                } 
        }; 
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();   
// 开始扫描设备 
mBluetoothAdapter.startLeScan(mLeScanCallback);   
... 
// 停止扫描设备 
mBluetoothAdapter.stopLeScan(mLeScanCallback);   
 
当扫描到设备以后,就会回调 onLeScan(...) ,这里的参数 scanRecord 就是广播数据,这里同时包含 广播数据 和 扫描相应数据 (如果有的话),所以长度一般就是 62 字节。 
 
根据上一节的广播数据格式的说明,可以实现解析广播数据函数 parseAdvData(scanRecord); ,下面的代码实现了解析几个我关心的数据: 
 
public static ParsedAd parseData(byte[] adv_data) {   
        ParsedAd parsedAd = new ParsedAd(); 
        ByteBuffer buffer = ByteBuffer.wrap(adv_data).order(ByteOrder.LITTLE_ENDIAN); 
        while (buffer.remaining() > 2) { 
                byte length = buffer.get(); 
                if (length == 0) 
                        break; 
                byte type = buffer.get(); 
                length -= 1; 
                switch (type) { 
                        case 0x01: // Flags 
                                parsedAd.flags = buffer.get(); 
                                length--; 
                                break; 
                        case 0x02: // Partial list of 16-bit UUIDs 
                        case 0x03: // Complete list of 16-bit UUIDs 
                        case 0x14: // List of 16-bit Service Solicitation UUIDs 
                                while (length >= 2) { 
                                        parsedAd.uuids.add(UUID.fromString(String.format( 
                                                        "%08x-0000-1000-8000-00805f9b34fb", buffer.getShort()))); 
                                        length -= 2; 
                                } 
                                break; 
                        case 0x04: // Partial list of 32 bit service UUIDs 
                        case 0x05: // Complete list of 32 bit service UUIDs 
                                while (length >= 4) { 
                                        parsedAd.uuids.add(UUID.fromString(String.format( 
                                                        "%08x-0000-1000-8000-00805f9b34fb", buffer.getInt()))); 
                                        length -= 4; 
                                } 
                                break; 
                        case 0x06: // Partial list of 128-bit UUIDs 
                        case 0x07: // Complete list of 128-bit UUIDs 
                        case 0x15: // List of 128-bit Service Solicitation UUIDs 
                                while (length >= 16) { 
                                        long lsb = buffer.getLong(); 
                                        long msb = buffer.getLong(); 
                                        parsedAd.uuids.add(new UUID(msb, lsb)); 
                                        length -= 16; 
                                } 
                                break; 
                        case 0x08: // Short local device name 
                        case 0x09: // Complete local device name 
                                byte sb[] = new byte[length]; 
                                buffer.get(sb, 0, length); 
                                length = 0; 
                                parsedAd.localName = new String(sb).trim(); 
                                break;                                 
                        case (byte) 0xFF: // Manufacturer Specific Data 
                                parsedAd.manufacturer = buffer.getShort(); 
                                length -= 2; 
                                break; 
                        default: // skip 
                                break; 
                } 
                if (length > 0) { 
                        buffer.position(buffer.position() + length); 
                } 
        } 
        return parsedAd; 
} 
 
其中 ParsedAd 是自定义的简单 Java 对象,用来保存解析后的数据。这里只是解析了我关心的数据,你也可以根据前面的说明,解析更多的内容。 |