Android的BLE广播数据包解析---Android系列, 蓝牙技术(含BLE)
一、引言
理解和分析这个数据包结构(这里面也涉及广播间隔时间的设置,设备广播数据间隔设置长了,会影响设备被发现的效率;设置短时,又响应功耗)。
我们所说的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. |
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>; | getServiceUuids() | Returns a list of service UUIDs within the advertisement that are used to identify the bluetooth GATT services. |
五、总结
BLE广播数据包并不复杂,数来数去也有那几个字节,用心地去理解,就可以解析出自已想要的数据。
Android的BLE广播数据包解析---Android系列, 蓝牙技术(含BLE)的更多相关文章
- 蓝牙Beacon广播数据包格式以及解析
目录 1. 获取原始蓝牙广播包 2. 安装WireShark软件 3. 分析Beacon广播包数据 3.1 第一个数据包格式 3.2 第二个数据包格式 3.3 Android程序开发中的蓝牙广播包 4 ...
- 蓝牙BLE: 蓝牙4.0 BLE广播数据解析(转)
BLE 设备工作的第一步就是向外广播数据.广播数据中带有设备相关的信息.本文主要说一下 BLE 的广播中的数据的规范以及广播包的解析. 1. 广播模式 BLE 中有两种角色 Central 和 Per ...
- 基于tcpdump的Android智能移动终端数据包捕获完整解决方案
如何在Android智能手机上捕获数据包? 本文由CSDN-蚍蜉撼青松[主页:http://blog.csdn.net/howeverpf]原创,转载请注明出处! 当前Android系统越来越流行,无 ...
- 一分钟读懂低功耗蓝牙(BLE)连接数据包
一分钟读懂低功耗蓝牙(BLE)连接数据包 1.概述 BLE 连接过程中有三个重要的数据包:SCAN_REQ, SCAN_RSP 和 CONNECT_REQ. SCAN_REQ: 扫描请求,由主设备(M ...
- GPS数据包格式及数据包解析
GPS数据包解析 GPS数据包解析 目的 GPS数据类型及格式 数据格式 数据解释 解析代码 结构体定义 GPRMC解析函数 GPGGA解析函数 测试样例输出 gps数据包格式 gps数据解析 车联网 ...
- 一个C++版的网络数据包解析策略
C++版的网络数据包解析策略(升级版) 一.数据包格式形如下图 二.代码 int ReceiveFromRemoteEndPoint() { int nPackageDataLength = ; ch ...
- 【九度OJ】题目1475:IP数据包解析 解题报告
[九度OJ]题目1475:IP数据包解析 解题报告 标签(空格分隔): 九度OJ http://ac.jobdu.com/problem.php?pid=1475 题目描述: 我们都学习过计算机网络, ...
- Wireshark-过滤器-数据包解析
目录 过滤器 数据包解析 参考 推荐阅读: https://www.cnblogs.com/zwtblog/tag/计算机网络/ 过滤器 显示过滤器 和 捕获过滤器,俩者使用非常类似. 在Wiresh ...
- BLE广播数据的抓包解析
前言: 报文由数据字节组成同时是按比特传输的,这就免不了牵涉到字节序的问题. 对于各个字节的传输,总是从最低位开始传输.如0x80是按00000001发送的,0x01是按10000000发送的. 同时 ...
- BLE 广播数据解析
从上一篇GATT Profile 简介中提到过,BLE 设备工作的第一步就是向外广播数据.广播数据中带有设备相关的信息.本文主要说一下 BLE 的广播中的数据的规范以及广播包的解析. 广播模式 BLE ...
随机推荐
- &&运算提高代码质量
sendGiveWeb: { code: 200, success: true, data: [ { id: "1230", name: "lh" }, { i ...
- IConfigurationSectionHandler 接口的用法
今天终于花了点时间了解一下IConfigurationSectionHandler 接口的用法 ,引以入门.首先建立一 RobsunConfigSectionHandler 专案,代码如下 : nam ...
- WPF内嵌Http协议的Server端
需求:有时后比如WPF,WinForm,Windows服务这些程序可能需要对外提供接口用于第三方服务主动通信,调用推送一些服务或者数据. 想到的部分实现方式: 一.使用Socket/WebSocket ...
- GPTs prompts灵感库:创意无限,专业级创作指南,打造吸睛之作的秘诀
GPTs prompts灵感库:创意无限,专业级创作指南,打造吸睛之作的秘诀 优质prompt展示 1.1 极简翻译 中英文转换 你是一个极简翻译工具,请在对话中遵循以下规则: - Prohibit ...
- Python 实现专属字典生成器
编写一个密码生成工具,这里我们使用弱密码与个性化数组组合形成一个定制字典,例如收集用户的姓名,昵称,QQ号手机号等资源,然后通过Python对搜集到的数据与弱密码进行结合,从而定制出属于某个人的专属密 ...
- Xcode常用环境变量与常见使用场景
在Xcode的工程配置中,与路径相关的都是使用环境变量,这样可以避免使用决定路径时项目移植性差的问题. Xcode常用宏 __FILE__ 当前文件所在目录 __DATE__ 编译日期的字符串,格式为 ...
- 【动态内存】C语言动态内存使用常见错误及其避免方法(初学者避雷)
C语言动态内存使用常见错误及其避免方法(初学者动态内存避雷手册) 求个赞求个赞求个赞求个赞 谢谢 先赞后看好习惯 打字不容易,这都是很用心做的,希望得到支持你 大家的点赞和支持对于我来说是一种非常重要 ...
- 使用DoraCloud构建远程办公桌面云
公司总部在上海.员工分布在各地.部分员工需要远程办公.为了实现远程办公,有几种备选方案. 方案1.在员工的PC上安装向日葵.ToDesk之类的远程工具. 方案2.公司总部提供VPN,员工通过VPN拨号 ...
- Qt processEvents - 解决线程中事件阻塞(如槽函数被阻塞)
百度了一会,发现没太有文字讲这件事情,因此整理成文字记录一下. processEvents介绍 长时间运行的操作可以调用processEvents() 保持应用程序响应能力. void QCoreAp ...
- 【译】.NET 8 网络改进(一)
原文 | Máňa,Natalia Kondratyeva 翻译 | 郑子铭 随着新的 .NET 版本的发布,发布有关网络空间中新的有趣变化的博客文章已成为一种传统.今年,我们希望引入 HTTP 空间 ...