android蓝牙BLE(一) —— 扫描android
在蓝牙开发中,有些状况是不须要链接的,只要外设广播本身的数据便可,例如苹果的ibeacon。自Android 5.0更新蓝牙API后,手机能够做为外设广播数据。post
广播包有两种:测试
其中广播包是每一个外设都必须广播的,而响应包是可选的。每一个广播包的长度必须是31个字节,若是不到31个字节 ,则剩下的全用0填充。 补全。这部分的数据是无效的 ui
广播包中包含若干个广播数据单元,广播数据单元也称为 AD Structure。this
广播数据单元 = 长度值Length + AD type + AD Data。spa
长度值Length只占一个字节,而且位于广播数据单元的第一个字节。code
概念的东西有些抽象,先看看下面的广播报文:
0x表明这串字符串是十六进制的字符串。两位十六进制数表明一个字节。由于两个字符组成的十六进制字符串最大为FF,即255,而Java中byte类型的取值范围是-128到127,恰好能够表示一个255的大小。因此两个十六进制的字符串表示一个字节。
继续查看报文内容,开始读取第一个广播数据单元。读取第一个字节:0x07,转换为十进制就是7,即表示后面的7个字节是这个广播数据单元的数据内容。超过这7个字节的数据内容后,表示是一个新的广播数据单元。
而第二个广播数据单元,第一个字节的值是0x16,转换为十进制就是22,表示后面22个字节为第二个广播数据单元。
在广播数据单元的数据部分中,第一个字节表明数据类型(AD type),决定数据部分表示的是什么数据。(即广播数据单元第二个字节为AD type)
AD Type的类型以下:
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: 预留
这bit 1~7分别表明着发送该广播的蓝牙芯片的物理链接状态。当bit的值为1时,表示支持该功能。 例:
非完整的16bit UUID: TYPE = 0x02;
完整的16bit UUID 列表: TYPE = 0x03;
非完整的32bit UUID 列表: TYPE = 0x04;
完整的32bit UUID 列表: TYPE = 0x05;
非完整的128bit UUID 列表: TYPE = 0x06;
完整的128bit UUID: TYPE = 0x07;
缩写的设备名称: TYPE = 0x08
完整的设备名称: TYPE = 0x09
16 bit UUID Service: TYPE = 0x16, 前 2 字节是 UUID,后面是 Service 的数据;
32 bit UUID Service: TYPE = 0x20, 前 4 字节是 UUID,后面是 Service 的数据;
128 bit UUID Service: TYPE = 0x21, 前 16 字节是 UUID,后面是 Service 的数据;
蓝牙广播的数据格式大体讲了一下,有助于下面的广播操做的理解。
先自定义UUID:
//UUID
public static UUID UUID_SERVICE = UUID.fromString("0000fff7-0000-1000-8000-00805f9b34fb");
复制代码
开启广播通常须要3~4对象:广播设置(AdvertiseSettings)、广播包(AdvertiseData)、扫描包(可选)、广播回调(AdvertiseCallback)。
先看看广播设置(AdvertiseSettings)如何定义:
//初始化广播设置
mAdvertiseSettings = new AdvertiseSettings.Builder()
//设置广播模式,以控制广播的功率和延迟。
.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_POWER)
//发射功率级别
.setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH)
//不得超过180000毫秒。值为0将禁用时间限制。
.setTimeout(3000)
//设置是否能够链接
.setConnectable(false)
.build();
复制代码
(1)、经过AdvertiseSettings.Builder#setAdvertiseMode() 设置广播模式。其中有3种模式:
一、在均衡电源模式下执行蓝牙LE广播:AdvertiseSettings#ADVERTISE_MODE_BALANCED
二、在低延迟,高功率模式下执行蓝牙LE广播: AdvertiseSettings#ADVERTISE_MODE_LOW_LATENCY
三、在低功耗模式下执行蓝牙LE广播:AdvertiseSettings#ADVERTISE_MODE_LOW_POWER
(2)、经过AdvertiseSettings.Builder#setAdvertiseMode() 设置广播发射功率。共有4种功率模式:
一、使用高TX功率级别进行广播:AdvertiseSettings#ADVERTISE_TX_POWER_HIGH
二、使用低TX功率级别进行广播:AdvertiseSettings#ADVERTISE_TX_POWER_LOW
三、使用中等TX功率级别进行广播:AdvertiseSettings#ADVERTISE_TX_POWER_MEDIUM
四、使用最低传输(TX)功率级别进行广播:AdvertiseSettings#ADVERTISE_TX_POWER_ULTRA_LOW
(3)、经过AdvertiseSettings.Builder#setTimeout()设置持续广播的时间,单位为毫秒。最多180000毫秒。当值为0则无时间限制,持续广播,除非调用BluetoothLeAdvertiser#stopAdvertising()。
(4)、经过AdvertiseSettings.Builder#setConnectable()设置该广播是否能够链接的。
以前说过,外设必须广播广播包,扫描包是可选。但添加扫描包也意味着广播更多得数据,便可广播62个字节。
//初始化广播包
mAdvertiseData = new AdvertiseData.Builder()
//设置广播设备名称
.setIncludeDeviceName(true)
//设置发射功率级别
.setIncludeDeviceName(true)
.build();
//初始化扫描响应包
mScanResponseData = new AdvertiseData.Builder()
//隐藏广播设备名称
.setIncludeDeviceName(false)
//隐藏发射功率级别
.setIncludeDeviceName(false)
//设置广播的服务UUID
.addServiceUuid(new ParcelUuid(UUID_SERVICE))
//设置厂商数据
.addManufacturerData(0x11,hexStrToByte(mData))
.build();
复制代码
可见不管是广播包仍是扫描包,其广播的内容都是用AdvertiseData类封装的。
(1)、AdvertiseData.Builder#setIncludeDeviceName()方法,能够设置广播包中是否包含蓝牙的名称。
(2)、AdvertiseData.Builder#setIncludeTxPowerLevel()方法,能够设置广播包中是否包含蓝牙的发射功率。
(3)、AdvertiseData.Builder#addServiceUuid(ParcelUuid)方法,能够设置特定的UUID在广播包中。
(4)、AdvertiseData.Builder#addServiceData(ParcelUuid,byte[])方法,能够设置特定的UUID和其数据在广播包中。
(5)、AdvertiseData.Builder#addManufacturerData(int,byte[])方法,能够设置特定厂商Id和其数据在广播包中。
从AdvertiseData.Builder的设置中能够看出,若是一个外设须要在不链接的状况下对外广播数据,其数据能够存储在UUID对应的数据中,也能够存储在厂商数据中。但因为厂商ID是须要由Bluetooth SIG进行分配的,厂商间通常都将数据设置在厂商数据。
另外能够经过BluetoothAdapter#setName()设置广播的名称
//获取蓝牙设配器
BluetoothManager bluetoothManager = (BluetoothManager)
getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();
//设置设备蓝牙名称
mBluetoothAdapter.setName("daqi");
复制代码
先看一个例子,咱们分别在广播包和扫描包中设置AdvertiseData.Builder的每一种广播报文参数,获得一下报文内容:
(1)、Type = 0x01 表示设备LE物理链接。
(2)、Type = 0x09 表示设备的全名
(3)、Type = 0x03 表示完整的16bit UUId。其值为0xFFF7。
(4)、Type = 0xFF 表示厂商数据。前两个字节表示厂商ID,即厂商ID为0x11。后面的为厂商数据,具体由用户自行定义。
(5)、Type = 0x16 表示16 bit UUID的数据,因此前两个字节为UUID,即UUID为0xF117,后续为UUID对应的数据,具体由用户自行定义。
最后继承AdvertiseCallback自定义广播回调。
private class daqiAdvertiseCallback extends AdvertiseCallback {
//开启广播成功回调
@Override
public void onStartSuccess(AdvertiseSettings settingsInEffect){
super.onStartSuccess(settingsInEffect);
Log.d("daqi","开启服务成功");
}
//没法启动广播回调。
@Override
public void onStartFailure(int errorCode) {
super.onStartFailure(errorCode);
Log.d("daqi","开启服务失败,失败码 = " + errorCode);
}
}
复制代码
初始化完毕上面的对象后,就能够进行广播:
//获取BLE广播的操做对象。
//若是蓝牙关闭或此设备不支持蓝牙LE广播,则返回null。
mBluetoothLeAdvertiser = mBluetoothAdapter.getBluetoothLeAdvertiser();
//mBluetoothLeAdvertiser不为空,且蓝牙已开打
if(mBluetoothAdapter.isEnabled()){
if (mBluetoothLeAdvertiser != null){
//开启广播
mBluetoothLeAdvertiser.startAdvertising(mAdvertiseSettings,
mAdvertiseData, mScanResponseData, mAdvertiseCallback);
}else {
Log.d("daqi","该手机不支持ble广播");
}
}else{
Log.d("daqi","手机蓝牙未开启");
}
复制代码
广播主要是经过BluetoothLeAdvertiser#startAdvertising()方法实现,但在以前须要先获取BluetoothLeAdvertiser对象。
BluetoothLeAdvertiser对象存在两个状况获取为Null:
一、手机蓝牙模块不支持BLE广播
二、蓝牙未开启
因此在调用BluetoothAdapter#getBluetoothLeAdvertiser()前,须要先调用判断蓝牙已开启,并判断在BluetoothAdapter中获取的BluetoothLeAdvertiser是否为空(测试过某些华为手机mBluetoothAdapter.isMultipleAdvertisementSupported()为false,可是能发送ble广播)。
与广播成对出现就是BluetoothLeAdvertiser.stopAdvertising()中止广播了,传入开启广播时传递的广播回调对象,便可关闭广播:
mBluetoothLeAdvertiser.stopAdvertising(mAdvertiseCallback)
复制代码
虽然经过广播告知外边自身拥有这些Service,但手机自身并无初始化Gattd的Service。致使外部的中心设备链接手机后,并不能找到对应的GATT Service 和 获取对应的数据。
BluetoothGattService service = new BluetoothGattService(UUID_SERVICE,
BluetoothGattService.SERVICE_TYPE_PRIMARY);
复制代码
建立BluetoothGattService时,传入两个参数:UUID和Service类型。
Service类型有两个级别:
咱们都知道Gatt中,Service的下一级是Characteristic,Characteristic是最小的通讯单元,经过对Characteristic进行读写操做来进行通讯。
//初始化特征值
mGattCharacteristic = new BluetoothGattCharacteristic(UUID_CHARACTERISTIC,
BluetoothGattCharacteristic.PROPERTY_WRITE|
BluetoothGattCharacteristic.PROPERTY_NOTIFY|
BluetoothGattCharacteristic.PROPERTY_READ,
BluetoothGattCharacteristic.PERMISSION_WRITE|
BluetoothGattCharacteristic.PERMISSION_READ);
复制代码
建立BluetoothGattCharacteristic时,传入三两个参数:UUID、特征属性 和 权限属性。
特征属性表示该BluetoothGattCharacteristic拥有什么功能,即能对BluetoothGattCharacteristic进行什么操做。其中主要有3种:
权限属性用于配置该特征值所具备的功能。主要两种:
当特征值只有读权限时,调用BluetoothGatt#writeCharacteristic()对特征值进行修改时,将返回false,没法写入。并不会触发BluetoothGattCallback#onCharacteristicWrite()回调。
当特征值只有写权限时,调用BluetoothGatt#readCharacteristic()对特征值进行读取时,将返回false,没法写入。并不会触发BluetoothGattCallback#onCharacteristicRead()回调。
Characteristic下还有Descriptor,初始化BluetoothGattDescriptor时传入:Descriptor UUID 和 权限属性
//初始化描述
mGattDescriptor = new BluetoothGattDescriptor(UUID_DESCRIPTOR,BluetoothGattDescriptor.PERMISSION_WRITE);
复制代码
为Service添addCharacteristic,为Characteristic添加Service:
//Service添加特征值
mGattService.addCharacteristic(mGattCharacteristic);
mGattService.addCharacteristic(mGattReadCharacteristic);
//特征值添加描述
mGattCharacteristic.addDescriptor(mGattDescriptor);
复制代码
经过蓝牙管理器mBluetoothManager获取Gatt Server,用来添加Gatt Service。添加完Gatt Service后,外部中心设备链接手机时,将能获取到对应的GATT Service 和 获取对应的数据
//初始化GattServer回调
mBluetoothGattServerCallback = new daqiBluetoothGattServerCallback();
if (mBluetoothManager != null)
mBluetoothGattServer = mBluetoothManager.openGattServer(this, mBluetoothGattServerCallback);
boolean result = mBluetoothGattServer.addService(mGattService);
if (result){
Toast.makeText(daqiActivity.this,"添加服务成功",Toast.LENGTH_SHORT).show();
}else {
Toast.makeText(daqiActivity.this,"添加服务失败",Toast.LENGTH_SHORT).show();
}
复制代码
定义Gatt Server回调。当中心设备链接该手机外设、修改特征值、读取特征值等状况时,会获得相应状况的回调。
private class daqiBluetoothGattServerCallback extends BluetoothGattServerCallback{
//设备链接/断开链接回调
@Override
public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
super.onConnectionStateChange(device, status, newState);
}
//添加本地服务回调
@Override
public void onServiceAdded(int status, BluetoothGattService service) {
super.onServiceAdded(status, service);
}
//特征值读取回调
@Override
public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) {
super.onCharacteristicReadRequest(device, requestId, offset, characteristic);
}
//特征值写入回调
@Override
public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
super.onCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, responseNeeded, offset, value);
}
//描述读取回调
@Override
public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattDescriptor descriptor) {
super.onDescriptorReadRequest(device, requestId, offset, descriptor);
}
//描述写入回调
@Override
public void onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
super.onDescriptorWriteRequest(device, requestId, descriptor, preparedWrite, responseNeeded, offset, value);
}
}
复制代码
最后开启广播后,用nRF链接后看到的特征值信息以下图所示:(加多了一个只能都的特征值)