Android的BLE广播数据包解析



一、引言

最近一个项目,需要在广播数据包中解析出设备厂家代码来区分不同的设备类型,一下子还是有点愣了,原先的开发中,并不会去在意这个广播数据包,通常只取Device、MAC等。为此,特意花了时间去理解和分析这个数据包结构(这里面也涉及广播间隔时间的设置,设备广播数据间隔设置长了,会影响设备被发现的效率;设置短时,又响应功耗)。

我们所说的BLE设备,其实是有区分有两种角色 Central 和 Peripheral,也就是中心设备和外围设备。中心设备可以主动连接外围设备,外围设备发送广播或者被中心设备连接。外围通过广播被中心设备发现,广播中带有外围设备自身的相关信息。在日常APP开发中,手机端的BLE一般都是充当中心设备的。

广播包有两种:广播包(Advertising Data)和响应包(Scan Response),其中广播包是每个设备必须广播的,而响应包是可选的。

二、广播的类型

广播的类型一般分为四种,有:

2.1 可连接的非定向广播(Connectable Undirected Event Type)

这种是用途最广的广播类型,包括广播数据和扫描响应数据,它表示当前设备可以接受其他任何设备的连接请求。进行通用广播的设备能够被扫描设备扫描到,或者在接收到连接请求时作为从设备进入一个连接。通用广播可以在没有连接的情况下发出,换句话说,没有主从设备之分。

2.2 可连接的定向广播(Connectable Directed Event Type)

定向广播类型是为了尽可能快的建立连接。这种报文包含两个地址:广播者的地址和发起者的地址。发起者收到发给自己的定向广播报文之后,可以立即发送连接请求作为回应。定向广播类型有特殊的时序要求。完整的广播事件必须每3.75ms重复一次。这一要求使得扫描设备只需扫描3.75ms便可以收到定向广播设备的消息。当然,如此快的发送会让报文充斥着广播信道,进而导致该区域内的其他设备无法进行广播。因此,定向广播不可以持续1.28s以上的时间。如果主机没有主动要求停止,或者连接没有建立,控制器都会自动停止广播。一旦到了1.28s,主机便只能使用间隔长得多的可连接非定向广播让其他设备来连接。

当使用定向广播时,设备不能被主动扫描。此外,定向广播报文的净荷中也不能带有其他附加数据。该净荷只能包含两个必须的地址。

2.3 不可连接的非定向广播(Non-connectable Undirected Event Type)

仅仅发送广播数据,而不想被扫描或者连接。这也是唯一可用于只有发射机而没有接收机设备的广播类型。不可连接广播设备不会进入连接态,因此,它只能根据主机的要求在广播态和就绪态之间切换。

2.4 可扫描的非定向广播(Scannable Undirected Event Type)

又称可发现广播,这种广播不能用于发起连接,但允许其他设备扫描该广播设备。这意味着该设备可以被发现,既可以发送广播数据,也可以响应扫描发送扫描回应数据,但不能建立连接。这是一种适用于广播数据的广播形式,动态数据可以包含于广播数据之中,而静态数据可以包含于扫描响应数据之中。

注意:所谓的定向和非定向针对的是广播的对象,如果是针对特定的对象进行广播(在广播包PDU中会包含目标对象的MAC)就是定向广播,反之就是非定向。可连接和不可连接是指是否接受连接请求,如果是不可连接的广播类型,它将不回应连接请求。可扫描广播类型是指回应扫描请求。

三、广播数据格式

每个包都是 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 包括如下类型:

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,剩下的是厂商自己按照需求添加,里面的数据内容自己定义。

主要的也就是这些了,更多的可参考SIG官网。

四、广播数据解析

4.1 旧接口(API 21 之前)

在API21之前,Android蓝牙扫描可以使用 BluetoothAdapter的startLeScan来发起扫描。基本用法如下:
BluetoothAdapter:

    //不带过滤搜索
    startLeScan(BluetoothAdapter.LeScanCallback callback)
    //带UUID过滤搜索
    startLeScan(UUID[] serviceUuids, BluetoothAdapter.LeScanCallback callback)

    BluetoothAdapter.LeScanCallback mLeScanCallback =  new BluetoothAdapter.LeScanCallback() {
        @Override
        public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
            // 解析广播数据
            parseScanRecodeData(scanRecord);
        }
    };

当扫描到设备以后,就会回调 onLeScan(...),这里的参数 scanRecord 就是广播数据,同时包含广播数据和扫描相应数据(如果有的话),所以长度一般就是 62 字节。搜索结果如下:

整个广播数据就在这个scanRecord中,所以需要对scanRecord进行字节解析

public static ParsedAd parseScanRecodeData(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;
}

4.2 新接口

在Android 5.0 (API 21)及之后使用新接口扫描BLE设备,也提供了一些广播数据包的解析接口。新和扫描方法使用的是BluetoothLeScanner,封装成了一个新的类,BluetoothLeScanner还有另一个扫描方法,这个方法就是多了一个扫描过滤 和 扫描设置参数:

    BluetoothLeScanner bluetoothLeScanner=bluetoothAdapter.getBluetoothLeScanner();
    bluetoothLeScanner.startScan(mLeScanCallback);
    //扫描过滤 和 扫描设置参数:
    bluetoothLeScanner.startScan(List<ScanFilter> filters, ScanSettings settings, mLeScanCallback)

    ScanCallback mLeScanCallback = new ScanCallback() {
        @Override
        public void onScanResult(int callbackType, ScanResult result) {
            Log.e(TAG, "ScanResult:" + scanResult.toString());
            ![](http://www.xmamiga.com/wp-content/uploads/2018/10/Android-BLE-Scaner.png)byte[] scanData=result.getScanRecord().getBytes();
            //把byte数组转成16进制字符串,方便查看
            //Log.e("TAG","onScanResult :"+utils.Bytes2HexString(scanData));//与旧接口一致
            Log.e(TAG, "newdevice:" + device.getAddress() + "  " + device.getName());
            Log.e(TAG, "scanRecord:" + scanResult.getScanRecord().toString());
            Log.e(TAG, "scanRecord getManufacturerSpecificData:" + scanResult.getScanRecord().getManufacturerSpecificData().toString());
        }
    };

搜索结果如下:



手机端运行Log:

涉及接口有:
ScanResult接口

Type Public methods ins
int getAdvertisingSid() Returns the advertising set id.
BluetoothDevice getDevice() Returns the remote Bluetooth device identified by the Bluetooth device address.
int getPeriodicAdvertisingInterval() Returns the periodic advertising interval in units of 1.25ms.
int getRssi() Returns the received signal strength in dBm.
ScanRecord getScanRecord() Returns the scan record, which is a combination of advertisement and scan response.

ScanRecord接口

Type Public methods ins
int getAdvertiseFlags() Returns the advertising flags indicating the discoverable mode and capability of the device.
byte[] getBytes() Returns raw bytes of scan record.
String getDeviceName() Returns the local name of the BLE device.
SparseArray<byte[]> getManufacturerSpecificData() Returns a sparse array of manufacturer identifier and its corresponding manufacturer specific data.
byte[] getManufacturerSpecificData(int manufacturerId) Returns the manufacturer specific data associated with the manufacturer id.
byte[] getServiceData(ParcelUuid serviceDataUuid) Returns the service data byte array associated with the serviceUuid.
List≶ParcelUuid&gt; getServiceUuids() Returns a list of service UUIDs within the advertisement that are used to identify the bluetooth GATT services.

五、总结

BLE广播数据包并不复杂,数来数去也有那几个字节,用心地去理解,就可以解析出自已想要的数据。


参考