Android BLE 开发小结

当对方给你丢过来一份协议

当嵌入式同事发来一篇讲蓝牙协议的博文时,蓝牙协议分析——BLE连接有关的技术分析,站在一个Android应用程序猿的角度来看,基本上是懵圈的。如果仔细研究一下,再回忆回忆当初大学里面自动化、嵌入式、网络通信协议之类的基础知识,其实还是能看懂一二的。但问题在于,在android平台上,这些东西该怎么操作,对于第一次接触android BLE的菜鸟来说,这是个伤脑筋的问题。与此同时,嵌入式的大神又丢过来一份我们自己产品的私有蓝牙通信协议,于是事情变得比较紧迫了。

了解概念

在应用开发的层面上,能够很清晰的知道的是,很多基础的东西framework都封装好了,大部分时间我们只是在调用系统提供的API。所以就android而言,要去详读的是它的BLE部分的开发者引导——Bluetooth Low Energy,找到一篇译文,也是转载的,找不到出处,由于格式看起来不是很友好,就自己重新捋了一遍——[译][转]Android BLE(低功耗蓝牙)开发API引导译文。通过这篇专业的引导文章,可以了解到一些必要的基本概念,帮助很大。

  • 谷歌在4.3以后才提供了BLE的API,兼容性问题要提前考虑;
  • BLE通信协议GATT/ATT;
  • services/characteristic/descriptor;
  • 中央设备/外围设备的概念(一说android sdk5.0以后才支持两种身份);
  • 权限问题——引申到6.0以上的权限动态申请;
  • API功能类,BluetoothAdapter/BluetoothDevice/BluetoothGattCallback等;

API熟悉

有了一定的基础概念以后,下面就是要去寻找DEMO。那么最基础、最权威的就应该是谷歌提供的sample了——android-BluetoothLeGatt。跑通DEMO的意义在于熟悉整个BLE的控制流程、API的使用方式。调试的话,IOS有个模拟蓝牙设备的应用LightBlue,实际测试下使用Android BluetoothLeGatt DEMO APK来链接IOS的LightBlue模拟设备经常断线,效果不是很好,暂时调试一下主要流程还是勉强能用。

对接到需求

协议协议,就是大家都认可的一套的规则;那么通信协议就是大家都认可的一套数据交换的规则,蓝牙协议是协议、http/tcp也是,我们日常使用的文字语言也能算是个通信协议。从一般的设备间数据通信大体可以预计到应该有这么几个步骤,发现设备->设备间校验身份->通信(上报和下发),Android平台把基础的数据交换都封装成API接口了,从第一步开始。在上一步DEMO中可以看到,只一个函数就可以扫描到周围的BLE设备了。

mBluetoothAdapter.startLeScan(mLeScanCallback);
// Device scan callback.
private BluetoothAdapter.LeScanCallback mLeScanCallback =
        new BluetoothAdapter.LeScanCallback() {
    @Override
    public void onLeScan(final BluetoothDevice device, int rssi,
            byte[] scanRecord) {
       //do something
   }
};

设备识别

我们又不是黑客,所以只对自己的设备感兴趣,连上别人的设备没有意义。通过对BLE协议的种种了解,知道外围蓝牙设备是通过不断发送广播来实现跟中心设备的交互对接的。在广播一般会附带一段初始数据,在被中央设备扫描到之后会发送一段扫描应答数据,两段数据是有长度限制的。而我们识别自己设备的实现就是在这几十个字节中做文章。

那么在android API中,广播数据在哪里获取呢?见楼上的回调函数:

onLeScan

void onLeScan (BluetoothDevice device,
int rssi,
byte[] scanRecord)

Callback reporting an LE device found during a device scan initiated by the startLeScan(BluetoothAdapter.LeScanCallback) function.

Parameters
device BluetoothDevice: Identifies the remote device
rssi int: The RSSI value for the remote device as reported by the Bluetooth hardware. 0 if no RSSI value is available.
scanRecord byte: The content of the advertisement record offered by the remote device.

呐,就在scanRecord参数中。安卓中会把两段广播数据合在一起回调给上层,即scanRecord字节数组包含了蓝牙设备的广播数据和广播应答数据,听说IOS也是如此。我们可以规定某几个字节作为解析标识,来识别我们的设备,这本身就是私有协议的一部分。

连接设备

通过扫描接口,如果顺利的话,我们可以获取到BluetoothDevice。那么下一步就是尝试连接设备,并且获取到设备中的services/characteristic/descriptor信息,才能进行下一步的数据交换。

我们需要从BluetoothDevice中,获取BluetoothGatt来进行连接、发现设备数据。

Public API for the Bluetooth GATT Profile.

This class provides Bluetooth GATT functionality to enable communication with Bluetooth Smart or Smart Ready devices.

To connect to a remote peripheral device, create a BluetoothGattCallback and call connectGatt(Context, boolean, BluetoothGattCallback) to get a instance of this class. GATT capable devices can be discovered using the Bluetooth device discovery or BLE scan process.

API上说的很清楚,通过BluetoothDevice调用connectGatt,通过BluetoothGattCallback获取连接状态。BluetoothGattCallback是个抽象类,通过onConnectionStateChange接口可以判断设备的连接状态。状态连接成功之后,就可以继续调用BluetoothGatt的discoverServices()来获取设备内部的配置情况,这些在开发引导、谷歌Samlpe中都可以看到。值得注意的是,通过BluetoothGatt来discover、获取service等操作必须是在正确的状态下来调用才能获取到正确的结果,这不难理解,连接设备、获取数据这些都是异步操作的,所以获取数据时需要遵守时序规则。正如discoverServices的函数的API描述所言:

discoverServices

boolean discoverServices ()
Discovers services offered by a remote device as well as their characteristics and descriptors.

This is an asynchronous operation. Once service discovery is completed, the onServicesDiscovered(BluetoothGatt, int) callback is triggered. If the discovery was successful, the remote services can be retrieved using the getServices() function.

在我目前的理解下,到这一步为止都完全是公用的蓝牙协议。从扫描发现到尝试连接再到获取设备的配置信息,对于同一个设备大家调用同样的接口都能获取到同样的设备配置,也就是说,蓝牙设备在未连接的状态下,任何一个安卓应用都可以扫描到、连接并且看到它的内部配置。你可以看到、但未必能控制,否则蓝牙门锁之类的产品就是玩笑般的存在了,这时嵌入式的同事丢过来的私有通讯协议就派上用场了。

协议对接

蓝牙的协议解决了蓝牙设备之间的基础通信问题,那么不同厂商的不同设备会在此基础之上,设立自己的数据解析协议。那么对于菜鸟而言,首先要解决的问题是,要弄清楚基础通信是如何实现的。其实还是接到上一步,discoverServices成功之后,可以拿到蓝牙设备的services/characteristic/descriptor配置信息,我们同蓝牙设备进行数据交换,实际上就是对它的这些设备配置进行读写,通过各路回调完成交换流程。简单的举例,可以通过对characteristic进行读/写操作,实现对蓝牙设备的读/写数据,前提是该characteristic具有读写操作权限;同时。蓝牙设备具有NOTIFY特性的characteristic会通过回调接口BluetoothGattCallback的:

onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic)
Callback triggered as a result of a remote characteristic notification.

接口通知手机端数据发生变化。这样就简单的完成了两端相互的数据交换。

而私有协议即在这些基础的连接/通信基础上的一些私有数据交互约定。由于蓝牙数据发送的字节数是有限的,所以免不了的就是发送与接收都要有拆包/组包的策略实现。到这里就是八仙过海各显神通了。

其他

github上有一些BLE操作的开源库,本质上也是对系统API的重新封装。不过它们应当是针对API的调用特点有针对性的做了一些调用策略和回调接口,让整个调用流程更加顺畅,跳一些坑儿;并且还会使接口调用更人性化,使起来顺手。但是查一些根本问题还是要对API有一些了解——最好是对蓝牙协议本身有所涉猎。

一些有价值的参考:

感谢您赏个荷包蛋~