0x00 低功耗藍牙(BLE)
上一篇簡單介紹了傳統(tǒng)藍牙設備的使用,當你沿著上一篇的思路去連接某些藍牙設備的時候,你會發(fā)現總是不成功。沒錯,我們還有另外一種藍牙沒有講:低功耗藍牙(BLE: Bluetooth Low Energy)。
這并不是一種新的東西,它只是藍牙協(xié)議中的一個新的版本。之前提到的多是藍牙2.0/2.1,這里的低功耗藍牙(BLE)主要是指藍牙4.0/4.1/4.2。在我們的日常生活中這種藍牙越來越常見,比如各種手環(huán)、體脂秤、智能設備、便攜藍牙設備等等。
更多關于藍牙的介紹可以參考這里
0x01 藍牙協(xié)議棧
和學習 TCP/IP 一樣,如果了解BLE的議棧對我們掌握藍牙會有很大的幫助。
-
物理 (PHY) 層
通過藍牙通信信道控制 2.4Ghz 射頻的傳輸/接收。BR/EDR 提供的信道較多但帶寬較窄,而 LE 使用的信道較少但帶寬較寬。
-
鏈路層
定義數據包結構/信道、發(fā)現/連接程序以及發(fā)送/接收數據。
-
直接測試模式
允許測試人員向 PHY 層發(fā)出指令以傳輸或接收給定數據包序列,通過 HCI 或 2 線 UART 接口提交命令。
-
主機控制器接口 (HCI)
藍牙控制器子系統(tǒng)(底部三層)和藍牙主機之間的可選標準接口。
-
邏輯鏈路控制和適配協(xié)議 (L2CAP) 層
基于數據包的協(xié)議,可將數據包傳輸至 HCI 或直接傳輸到無主機系統(tǒng)中的鏈路管理器。支持更高級別的協(xié)議多路復用、數據包分割和重組,以及將服務質量信息傳輸到更高層。
-
屬性協(xié)議 (ATT)
在建立連接之后定義數據交換客戶端/服務器協(xié)議。使用通用屬性配置文件 (GATT) 將屬性分類為有意義的服務。ATT 主要用于 LE 部署,偶爾也會用于 BR/EDR 部署。
-
安全管理器
定義管理藍牙設備之間配對完整性、身份驗證以及加密的協(xié)議和操作,提供安全功能工具箱,其他組件可利用該工具箱支持不同應用所需的各種安全級別。
-
通用屬性配置文件 (GATT)
使用屬性協(xié)議,GATT 對封裝設備組件性能的服務進行分組,并描述基于 GATT 功能的用例、角色和一般性能。其服務框架定義服務規(guī)程和格式及其特性,其中包括發(fā)現、讀取、寫入、通知以及指示特性以及配置特性廣播。GATT 僅用于藍牙 LE 部署。 詳細了解 GATT 信息。
通用訪問配置文件(GAP)
可與藍牙 LE 部署中的 GATT 配合使用,以定義與發(fā)現藍牙設備和共享信息相關的規(guī)程和角色,以及連接藍牙設備的鏈路管理內容。
0x02 Android系統(tǒng)中的BLE
Android系統(tǒng)中對于低功耗藍牙我們需要關心以下幾點:
- Android 4.3(18)開始支持BLE
- Android 5.0(21)之前手機只可以作為中心設備(Central mode)使用,5.0之后可以作為外設(Peripheral mode)使用
除了以上信息,我們在對協(xié)議中的幾個概念做個介紹,這涉及到之后的開發(fā):
-
Attribute Protocol (ATT)
屬性協(xié)議,對應
BluetoothGattService
-
Generic Attribute Profile (GATT)
通用屬性配置文件,對應Android中的
BluetoothGatt
-
Characteristic
BluetoothGattCharacteristic
-
Descriptor
BluetoothGattDescriptor
-
Service
BluetoothGattService
0x03 Android系統(tǒng)中的BLE操作流程
和之前的普通藍牙相同,我們需要先檢測設備的藍牙是否可用,然后掃描周圍的藍牙設備,然后連接。這里可以看到,BLE的操作并不需要配對。如需要在設備必須支持低功耗藍牙,則還需要加上這句:
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
-
藍牙掃描
private boolean mScanning; private void scanLeDevice(final boolean enable) { if (enable) { mHandler.postDelayed(new Runnable() { @Override public void run() { mScanning = false; mBluetoothAdapter.stopLeScan(mLeScanCallback); } }, SCAN_PERIOD); mScanning = true; mBluetoothAdapter.startLeScan(mLeScanCallback); } else { mScanning = false; mBluetoothAdapter.stopLeScan(mLeScanCallback); } } // Device scan callback. private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() { @Override public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) { String format = "Name:%s, Mac:%s, Type:%s"; String msg = String.format(format, device.getName(), device.getAddress(), device.getType()); print(msg); } };
掃描完成之后,我們會拿到藍牙的設備信息,然后就可以進行連接了。
-
連接藍牙
@Override public int onStartCommand(Intent intent, int flags, int startId) { if (null != intent) { mMAC = intent.getStringExtra(KEY_MAC); } if (TextUtils.isEmpty(mMAC)) { mMAC = MAC_BIKE; } mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); mBluetoothAdapter = mBluetoothManager.getAdapter(); if (null == mBluetoothAdapter) { stopSelf(); return START_NOT_STICKY; } BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(mMAC); if (null == device) { stopSelf(); return START_NOT_STICKY; } closeConnect(); mBluetoothGatt = device.connectGatt(this, false, mCallBack); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { //5.0設置的傳輸最大空間 mBluetoothGatt.requestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_HIGH); mBluetoothGatt.requestMtu(84); } print("Gatt connect"); return START_STICKY; }
-
連接藍牙
在連接的CallBack中如果我們檢測到連接成功,才可以請求藍牙提供的服務,這里先檢測連接的狀態(tài):
private final BluetoothGattCallback mCallBack = new BluetoothGattCallback() { @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { super.onConnectionStateChange(gatt, status, newState); print(String.format("status:%d, newState:%d", status, newState)); if (status != BluetoothGatt.GATT_SUCCESS) { closeConnect(); } switch (newState) { case BluetoothProfile.STATE_CONNECTED: print("連接GATT服務成功,開始發(fā)現服務..."); gatt.discoverServices(); break; case BluetoothProfile.STATE_DISCONNECTED: print("斷開GATT服務,Bye"); closeConnect(); break; default: break; } } ... };
-
發(fā)現服務
private final BluetoothGattCallback mCallBack = new BluetoothGattCallback() { @Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { super.onServicesDiscovered(gatt, status); print("發(fā)現服務:" + status); if (BluetoothGatt.GATT_SUCCESS == status) { List<BluetoothGattService> gattServices = gatt.getServices(); if (null == gattServices || gattServices.size() == 0) { return; } for (BluetoothGattService gattService : gattServices) { String serviceUUID = gattService.getUuid().toString(); print("UUID GATT:" + serviceUUID); List<BluetoothGattCharacteristic> characteristics = gattService.getCharacteristics(); for (BluetoothGattCharacteristic characteristic : characteristics) { String uuid = characteristic.getUuid().toString(); print("UUID Cha:" + uuid); print("UUID Status:" + getProperties(characteristic)); if (UUID_RECEIVE.toString().equalsIgnoreCase(uuid)) { mBluetoothGatt.setCharacteristicNotification(characteristic, true); print("開始監(jiān)聽:" + uuid); } } } } } ... }
-
使用服務
...
0x04 還需要了解的一些細節(jié)
- UUID
- 判斷服務的屬性
0x05 可能會遇到的坑
手上的兩臺魅族設備,連接一個客戶提供的藍牙模塊,死活連不上,其他手機連接正常。這兩部設備連接淘寶上買的一個藍牙模塊正常。
0xFF 參考
- https://developer.android.com/guide/topics/connectivity/bluetooth-le.html
- https://www.bluetooth.com/zh-cn/specifications/bluetooth-core-specification
- https://zh.wikipedia.org/wiki/%E8%97%8D%E7%89%99#.E8.97.8D.E7.89.994.0
- https://race604.com/android-ble-in-action/
- http://blog.csdn.net/qinxiandiqi/article/details/40741269
- http://www.lxweimin.com/p/8690dbafe849
- https://www.bluetooth.com/zh-cn/specifications/adopted-specifications
- https://www.bluetooth.org/DocMan/handlers/DownloadDoc.ashx?doc_id=286439