低功耗蓝牙原理与实现
最近公司提出一个需求:如果当前安卓手机支持BLE(Bluetooth Low Energy 低功耗蓝牙),则需要将当前的手机和车机通过蓝牙进行互联,并实现通信。由于之前没有接触过BLE相关的内容,实现的过程中遇到了许多困难,但也因此学到了不少的知识,因此想写下这篇文章与大家分享。文章主要包括以下几个内容:
- BLE原理详解
- BLE相关的几个概念详解
- BLE相关的几个类详解
- BLE实现的前提条件
- BLE使用的一般流程
- BLE使用过程中遇到的一些坑
- 参考资料
原理详解
- 蓝牙通信的主从关系
蓝牙技术规定每一对设备之间进行蓝牙通讯时,必须一个为主角色,另一为从角色,才能进行通信,通信时,必须由主端进行查找,发起配对,建链成功后,双方即可收发数据。理论上,一个蓝牙主端设备,可同时与7个蓝牙从端设备进行通讯。一个具备蓝牙通讯功能的设备,可以在两个角色间切换,平时工作在从模式,等待其它主设备来连接,需要时,转换为主模式,向其它设备发起呼叫。一个蓝牙设备以主模式发起呼叫时,需要知道对方的蓝牙地址,配对密码等信息,配对完成后,可直接发起呼叫。这可以解释为什么有时无法连接蓝牙,有可能是连接的蓝牙设备过多。
- 蓝牙的呼叫过程
蓝牙主端设备发起呼叫,首先是查找,找出周围处于可被查找的蓝牙设备。主端设备找到从端蓝牙设备后,与从端蓝牙设备进行配对,此时需要输入从端设备的PIN码,也有设备不需要输入PIN码。配对完成后,从端蓝牙设备会记录主端设备的信任信息,此时主端即可向从端设备发起呼叫,已配对的设备在下次呼叫时,不再需要重新配对。已配对的设备,做为从端的蓝牙耳机也可以发起建链请求,但做数据通讯的蓝牙模块一般不发起呼叫。链路建立成功后,主从两端之间即可进行双向的数据或语音通讯。在通信状态下,主端和从端设备都可以发起断链,断开蓝牙链路。
- 蓝牙一对一的串口数据传输应用
蓝牙数据传输应用中,一对一串口数据通讯是最常见的应用之一,蓝牙设备在出厂前即提前设好两个蓝牙设备之间的配对信息,主端预存有从端设备的PIN码、地址等,两端设备加电即自动建链,透明串口传输,无需外围电路干预。一对一应用中从端设备可以设为两种类型,一是静默状态,即只能与指定的主端通信,不被别的蓝牙设备查找;二是开发状态,既可被指定主端查找,也可以被别的蓝牙设备查找建链。
低功耗蓝牙相关的几个概念详解
- BLE 全称为Bluetooth Low Energy,即低功耗蓝牙
- GATT 全称为Generic Attribute Profile,即通用属性协议
通过BLE连接,读写属性类小数据的Profile通用规范。现在所有的BLE应用Profile都是基于GATT的。GATT包含若干个Profile,一个Profile包含若干个Services,一个Service包含若干个Characteristics,一个Characteristic包含Properties字段和若干个Descriptor(可选)。
GATT调用下层的ATT,ATT的attirbute在GATT中表现为Characteristic。有关GATT相关的东西,这篇博客写的很清楚,有兴趣的朋友可以看看。Generic Attribute Profile (GATT) 通用属性协议 - ATT 全称Attribute Protocol
GATT是基于ATT Protocol的。ATT针对BLE设备做了专门的优化,具体就是在传输过程中使用尽量少的数据。每个属性都有一个唯一的UUID,属性将以characteristics and services的形式传输。 - UUID 全称Universally Unique Identifier,即通用唯一识别码
在BLE中,所有的Service,Service包含的Characteristic,以及Characteristic包含的Descriptor都是通过UUID进行标识的。 - Service
Characteristic的集合。例如一个service叫做“Heart Rate Monitor”,它可能包含多个Characteristics,其中可能包含一个叫做“heart ratemeasurement”的Characteristic。一个手环可能。 - Characteristic
Characteristic可以理解为一个数据类型,它包括一个value和0至多个对次value的描述(Descriptor)。 - Descriptor
对Characteristic的描述,例如范围、计量单位等。
低功耗蓝牙相关的几个类详解
- BluetoothManager。通过Context.getSystemService(Context.BLUETOOTH_SERVICE)获得,主要是用于获取BluetoothAdapter实例。
- BluetoothAdapter。BLE的主要操作都是通过BluetoothAdapter来实现的,比如判断当前设备的BLE是否可用,是否打开,以及扫描,停止扫描,获取远程设备等。
- BluetoothDevice。代表设备上发现的BLE设备,相当于一个Bean,里面包含BLE设备的许多信息,比如蓝牙的名称,蓝牙的地址,同时也是通过它进行连接操作的,同时返回一个BluetoothGatt对象等等。
- BluetoothGatt。包含一些 Bluetooth GATT Profile公用的API,比如发现服务,读数据,写数据,判断设备是否在连接状态,断开连接等等。
- BluetoothGattService。代表一个Bluetooth GATT Service
- BluetoothGattCharacteristic。Represents a Bluetooth GATT Characteristic
- BluetoothGattDescriptor。代表一个Bluetooth GATT Descriptor
实现的前提条件
首先当前的设备必须要支持BLE,且Android系统必须在4.3或以上。和经典蓝牙一样,应用使用蓝牙,需要声明BLUETOOTH权限,如果需要扫描设备或者操作蓝牙设置,则还需要BLUETOOTH_ADMIN权限:
除了蓝牙权限外,如果需要BLE feature则还需要声明uses-feature:按时required为true时,则应用只能在支持BLE的Android设备上安装运行;required为false时,Android设备均可正常安装运行,需要在代码运行时判断设备是否支持BLE feature:
if(!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)){
Toast.makeText(this, R.string.ble_not_supported,Toast.LENGTH_SHORT).show();
finish();
}
低功耗蓝牙使用的一般流程
- 在AndroidManifest文件中添加相关的权限和相关的特征;
- 检查当前设备是否支持BLE,并且蓝牙是否打开;
- 扫描设备;
- 连接指定设备;
- 对指定设备进行读写等操作;
- 断开连接;
- 关闭蓝牙
使用过程中遇到的一些坑
- 扫描到蓝牙后,除了BluetoothDevice里面发现的名称和地址之外,没有获取设备里包含的更多信息:在BluetoothAdapter.LeScanCallback中的onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord)回调函数,第三个参数scanRecord中包含了更多远程设备的信息。
- 写数据收不到反馈信息(onCharacteristicChanged()函数就是不回调):首先要确保打开了对应的通知,其次要确保相应的Descriptor执行了类似以下的操作:
if (UUID_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())) {
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);
mBluetoothGatt.writeDescriptor(descriptor);
}
- 写数据写两次才能收到反馈信息:这个问题真的是非常奇怪,就是明明用正确的方式打开了通知,可是就是收不到数据,但是写两次就可以收到数据。后来的解决方案是:在打开通知以后,延迟1秒进行写操作,就成功了。想必应该是打开通知也是需要时间的,必须在通知打开完成之后,进行写操作才能收到对象的信息。
- 发现服务后,在ExpandableListView中无法显示数据:解决方案也是通过Handler延迟0.5S左右就可以正常显示了。
参考资料
- BLE简介和Android BLE编程
- Generic Attribute Profile (GATT) 通用属性协议
- android ble 的各种坑
- Android 蓝牙4.0 ble 官方 demo