android BLE系列:

android蓝牙BLE(一) —— 扫描

android蓝牙BLE(二) —— 通信

android蓝牙BLE(三) —— 广播

手机作为外设

        在蓝牙开发中,有些情况是不需要连接的,只要外设广播自己的数据即可,例如苹果的ibeacon。自Android 5.0更新蓝牙API后,手机可以作为外设广播数据。

广播包有两种:

  • 广播包(Advertising Data)
  • 响应包(Scan Response)

        其中广播包是每个外设都必须广播的,而响应包是可选的。每个广播包的长度必须是31个字节,如果不到31个字节 ,则剩下的全用0填充。 补全。这部分的数据是无效的

广播数据单元

        广播包中包含若干个广播数据单元,广播数据单元也称为 AD Structure。

广播数据单元 = 长度值Length + AD type + AD Data。

长度值Length只占一个字节,并且位于广播数据单元的第一个字节

概念的东西有些抽象,先看看下面的广播报文:

        0x代表这串字符串是十六进制的字符串。两位十六进制数代表一个字节。因为两个字符组成的十六进制字符串最大为FF,即255,而Java中byte类型的取值范围是-128到127,刚好可以表示一个255的大小。所以两个十六进制的字符串表示一个字节。

        继续查看报文内容,开始读取第一个广播数据单元。读取第一个字节:0x07,转换为十进制就是7,即表示后面的7个字节是这个广播数据单元的数据内容。超过这7个字节的数据内容后,表示是一个新的广播数据单元。

        而第二个广播数据单元,第一个字节的值是0x16,转换为十进制就是22,表示后面22个字节为第二个广播数据单元。

        在广播数据单元的数据部分中,第一个字节代表数据类型(AD type),决定数据部分表示的是什么数据。(即广播数据单元第二个字节为AD type)

AD Type的类型如下:

  • Flags:TYPE = 0x01。用来标识设备LE物理连接。

        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时,表示支持该功能。 例:

  • Service UUID。广播数据中可以将设备支持的GATT Service的UUID广播出来,来告知中心设备其支持的Service。
    对于不同bit的UUID,其对应的类型也有不同:

        非完整的16bit UUID: TYPE = 0x02;

        完整的16bit UUID 列表: TYPE = 0x03;

        非完整的32bit UUID 列表: TYPE = 0x04;

        完整的32bit UUID 列表: TYPE = 0x05;

        非完整的128bit UUID 列表: TYPE = 0x06;

        完整的128bit UUID: TYPE = 0x07;

  • TX Power Level: TYPE = 0x0A,表示设备发送广播包的信号强度。 数值范围:±127 dBm。
  • 设备名字,DATA 是名字的字符串,可以是设备的全名,也可以是设备名字的缩写。

        缩写的设备名称: TYPE = 0x08

        完整的设备名称: TYPE = 0x09

  • Service Data: Service 对应的数据。

        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 的数据;

  • 厂商自定义数据: TYPE = 0xFF。 厂商数据中,前两个字节表示厂商ID,剩下的是厂商自定义的数据。

BLE广播

蓝牙广播的数据格式大致讲了一下,有助于下面的广播操作的理解。

先自定义UUID:

//UUIDpublic 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种模式:

        1、在均衡电源模式下执行蓝牙LE广播:AdvertiseSettings#ADVERTISE_MODE_BALANCED
        2、在低延迟,高功率模式下执行蓝牙LE广播: AdvertiseSettings#ADVERTISE_MODE_LOW_LATENCY
        3、在低功耗模式下执行蓝牙LE广播:AdvertiseSettings#ADVERTISE_MODE_LOW_POWER

(2)、通过AdvertiseSettings.Builder#setAdvertiseMode() 设置广播发射功率。共有4种功率模式:

        1、使用高TX功率级别进行广播:AdvertiseSettings#ADVERTISE_TX_POWER_HIGH
        2、使用低TX功率级别进行广播:AdvertiseSettings#ADVERTISE_TX_POWER_LOW
        3、使用中等TX功率级别进行广播:AdvertiseSettings#ADVERTISE_TX_POWER_MEDIUM
        4、使用最低传输(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#setIncludeDeviceName()方法,可以设置广播包中是否包含蓝牙的发射功率。

(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);    }}复制代码

初始化完毕上面的对象后,就可以进行广播:

//如果芯片组支持多广播,则返回trueboolean result1 = mBluetoothAdapter.isMultipleAdvertisementSupported();if (result1){    //获取BLE广播的操作对象。    //如果蓝牙关闭或此设备不支持蓝牙LE广播,则返回null。    mBluetoothLeAdvertiser = mBluetoothAdapter.getBluetoothLeAdvertiser();    if (mBluetoothLeAdvertiser != null){        //开启广播        mBluetoothLeAdvertiser.startAdvertising(mAdvertiseSettings,                mAdvertiseData, mScanResponseData, mAdvertiseCallback);    }else {        Log.d("daqi","手机蓝牙未开启");    }}else {    Log.d("daqi","该手机芯片不支持广播");}复制代码

        广播主要是通过BluetoothLeAdvertiser#startAdvertising()方法实现,但在之前需要先获取BluetoothLeAdvertiser对象。

BluetoothLeAdvertiser对象存在两个情况获取为Null:

        1、手机蓝牙模块不支持BLE广播
        2、蓝牙未开启

        所以在调用BluetoothAdapter#getBluetoothLeAdvertiser()前,需要先调用BluetoothAdapter#isMultipleAdvertisementSupported()检查手机是否支持发送BLE广播。

        与广播成对出现就是BluetoothLeAdvertiser.stopAdvertising()停止广播了,传入开启广播时传递的广播回调对象,即可关闭广播:

mBluetoothLeAdvertiser.stopAdvertising(mAdvertiseCallback)复制代码

启动GATT Service 和 Characteristic

        虽然通过广播告知外边自身拥有这些Service,但手机自身并没有初始化Gattd的Service。导致外部的中心设备连接手机后,并不能找到对应的GATT Service 和 获取对应的数据。

BluetoothGattService service = new BluetoothGattService(UUID_SERVICE,                                BluetoothGattService.SERVICE_TYPE_PRIMARY);复制代码

创建BluetoothGattService时,传入两个参数:UUID和Service类型。

Service类型有两个级别:

  • BluetoothGattService#SERVICE_TYPE_PRIMARY 主服务
  • BluetoothGattService#SERVICE_TYPE_PRIMARY 次要服务(存在于主服务中的服务)

        我们都知道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种:

  • BluetoothGattCharacteristic#PROPERTY_WRITE 表示特征支持通知
  • BluetoothGattCharacteristic#PROPERTY_READ 表示特征支持读
  • BluetoothGattCharacteristic#PROPERTY_NOTIFY 表示特征支持写

权限属性用于配置该特征值所具有的功能。主要两种:

  • BluetoothGattCharacteristic#PERMISSION_WRITE 特征写权限
  • BluetoothGattCharacteristic#PERMISSION_READ 特征读权限

        当特征值只有读权限时,调用BluetoothGatt#writeCharacteristic()对特征值进行修改时,将返回false,无法写入。并不会触发BluetoothGattCallback#onCharacteristicWrite()回调。

        当特征值只有写权限时,调用BluetoothGatt#readCharacteristic()对特征值进行读取时,将返回false,无法写入。并不会触发BluetoothGattCallback#onCharacteristicRead()回调。

        Characteristic下还有Descriptor,初始化BluetoothGattDescriptor时传入:Descriptor UUID 和 权限属性

//初始化描述mGattDescriptor = new BluetoothGattDescriptor(UUID_DESCRIPTOR,BluetoothGattCharacteristic.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连接后看到的特征值信息如下图所示:(加多了一个只能都的特征值)

转载于:https://juejin.im/post/5cdbd083e51d453ce606dbd0

更多相关文章

  1. 总结Content Provider的使用
  2. 快速上手Android数据库操作
  3. Intent机制详解
  4. Android(安卓)音频框架概述(一)之 AudioTrack
  5. Android蓝牙开发
  6. Android(安卓)的视频编码 H263 MP4V H264
  7. android左右滑动加载分页以及动态加载数据
  8. Android底层开发之旅—蓝牙系统分析
  9. mybatisplus的坑 insert标签insert into select无参数问题的解决

随机推荐

  1. Android(安卓)同时setTag两次保存多种值
  2. Kotlin For Android介绍
  3. 关于 Android(安卓)Drawable Resource学
  4. Android(安卓)进程间通讯之通过Intent+bu
  5. Service通过Broadcast更新UI
  6. Android(安卓)Path类
  7. Android快速开发框架ZBLibrary源码分享
  8. [Android]资源存储方法
  9. AIDL 消息通信
  10. Delphi xe6 android Popup控件的使用