广播数据结构
BLE 外设开始工作时,会发出广播让中心设备发现,广播的数据包中带有外设的相关信息,比如设备的MAC地址,名字,Service 列表的 UUID 等等。
广播包的数据结构都是固定的,按照蓝牙相关规范规定好的。
每个包都是 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 类型
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,剩下的是厂商自己按照需求添加,里面的数据内容自己定义。
这里列举了部分常见的数据,还有一些其他的数据。
广播数据解析
在Android中,扫描蓝牙设备有两套api,一套是Android4.3上面的,扫描的广播数据需要自己解析;另一套是Android5.0上面的,扫描的广播数据,系统会帮我们解析好。
下面是Android4.3上面的开启扫描代码:
//扫描结果回调
val leScanCallback = object : BluetoothAdapter.LeScanCallback {
override fun onLeScan(device: BluetoothDevice, rssi: Int, scanRecord: ByteArray) {
//回调在子线程
ParseBluetoothAdData.parse(scanRecord)//解析广播数据
}
}
val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
if (bluetoothAdapter.isEnabled) {//蓝牙为打开状态
bluetoothAdapter.startLeScan(leScanCallback)//开始扫描,传入回调接口
}
下面是解析广播的代码,这里只解析了UUID,厂商数据和设备名字:
object ParseBluetoothAdData {
fun parse(bytes: ByteArray): AdData {
val baseUuid = "%08X-0000-1000-8000-00805F9B34FB"
val uuidS = ArrayList<UUID>()
var manufacturerByte: ByteArray? = null
var name: String? = null
val buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN)
while (buffer.remaining() > 2) {
var length = buffer.get().toInt() and 0xff
if (length == 0) {
break
}
val type = buffer.get().toInt() and 0xff
length--
when (type) {
0x01 -> {
buffer.position(buffer.position() + length)
}
0x02, 0x03, 0x14 -> {
while (length >= 2) {
uuidS.add(UUID.fromString(String.format(baseUuid, buffer.short)))
length -= 2
}
}
0x04, 0x05 -> {
while (length >= 4) {
uuidS.add(UUID.fromString(String.format(baseUuid, buffer.int)))
length -= 4
}
}
0x06, 0x07, 0x15 -> {
while (length >= 16) {
val lsb = buffer.long
val msb = buffer.long
uuidS.add(UUID(msb, lsb))
length -= 16
}
}
0xff -> {//厂商自定义数据
manufacturerByte = ByteArray(length)
buffer.get(manufacturerByte, 0, length)
}
0x09, 0x08 -> {//全名,简短名
val byteArray = ByteArray(length)
buffer.get(byteArray)
name = String(byteArray)
}
else -> {
buffer.position(buffer.position() + length)
}
}
}
return AdData(uuidS, manufacturerByte, name)
}
class AdData(val UUIDs: ArrayList<UUID>, val manufacturerByte: ByteArray?, val name: String?)
}