Android使用BLE(低功耗藍芽,Bluetooth Low Energy)
背景
在學習BLE的過程中,積累了一些心得的DEMO,放到Github,形成本文。感興趣的同學可以下載到原始碼。
github:https://github.com/vir56k/bluetoothDemo
什麼是BLE(低功耗藍芽)
BLE(Bluetooth Low Energy,低功耗藍芽)是對傳統藍芽BR/EDR技術的補充。
儘管BLE和傳統藍芽都稱之為藍芽標準,且共享射頻,但是,BLE是一個完全不一樣的技術。
BLE不具備和傳統藍芽BR/EDR的相容性。它是專為小資料率、離散傳輸的應用而設計的。
通訊距離上也有改變,傳統藍芽的傳輸距離幾十米到幾百米不等,BLE則規定為100米。
低功耗藍芽特點
*功耗低
*連線更快,無需配對
*非同步通訊
常見兩種藍芽模式
*普通藍芽連線(2.0)
*BLE(藍芽4.0)
關鍵術語和概念
*Generic Attribute Profile(GATT)—GATT配置檔案是一個通用規範,用於在BLE鏈路上傳送和接收被稱為“屬性”的資料塊。目前所有的BLE應用都基於GATT。 藍芽SIG規定了許多低功耗裝置的配置檔案。配置檔案是裝置如何在特定的應用程式中工作的規格說明。注意一個裝置可以實現多個配置檔案。例如,一個裝置可能包括心率監測儀和電量檢測。
*Attribute Protocol(ATT)—GATT在ATT協議基礎上建立,也被稱為GATT/ATT。ATT對在BLE裝置上執行進行了優化,為此,它使用了儘可能少的位元組。每個屬性通過一個唯一的的統一識別符號(UUID)來標識,每個String型別UUID使用128 bit標準格式。屬性通過ATT被格式化為characteristics和services。
*Characteristic 一個characteristic包括一個單一變數和0-n個用來描述characteristic變數的descriptor,characteristic可以被認為是一個型別,類似於類。
*Descriptor Descriptor用來描述characteristic變數的屬性。例如,一個descriptor可以規定一個可讀的描述,或者一個characteristic變數可接受的範圍,或者一個characteristic變數特定的測量單位。
*Service service是characteristic的集合。例如,你可能有一個叫“Heart Rate Monitor(心率監測儀)”的service,它包括了很多characteristics,如“heart rate measurement(心率測量)”等。你可以在bluetooth.org 找到一個目前支援的基於GATT的配置檔案和服務列表。
角色和責任
以下是Android裝置與BLE裝置互動時的角色和責任:
*中央 VS 外圍裝置。 適用於BLE連線本身。中央裝置掃描,尋找廣播;外圍裝置發出廣播。
*GATT 服務端 VS GATT 客戶端。決定了兩個裝置在建立連線後如何互相交流。
為了方便理解,想象你有一個Android手機和一個用於活動跟蹤BLE裝置,手機支援中央角色,活動跟蹤器支援外圍(為了建立BLE連線你需要注意兩件事,只支援外圍裝置的兩方或者只支援中央裝置的兩方不能互相通訊)。
當手機和運動追蹤器建立連線後,他們開始向另一方傳輸GATT資料。哪一方作為伺服器取決於他們傳輸資料的種類。例如,如果運動追蹤器想向手機報告感測器資料,運動追蹤器是服務端。如果運動追蹤器更新來自手機的資料,手機會作為服務端。
在這份文件的例子中,android app(執行在android裝置上)作為GATT客戶端。app從gatt服務端獲得資料,gatt服務端即支援Heart Rate Profile(心率配置)的BLE心率監測儀。但是你可以自己設計android app去扮演GATT服務端角色
裝置對BLE的支援
分為兩種情況
* 目標裝置是否支援BLE
* Android手機是否支援BLE
目標裝置是否支援要看具體目標裝置的情況,請參考硬體提供商和說明書。
一般情況下Android4.3以後的手機具有藍芽模組的話都會支援BLE,具體可以再程式碼中判斷。
為了在app中使用藍芽功能,必須宣告藍芽許可權BLUETOOTH。利用這個許可權去執行藍芽通訊,例如請求連線、接受連線、和傳輸資料。
如果想讓你的app啟動裝置發現或操縱藍芽設定,必須宣告BLUETOOTH_ADMIN許可權。注意:如果你使用BLUETOOTH_ADMIN許可權,你也必須宣告BLUETOOTH許可權。
在你的app manifest檔案中宣告藍芽許可權。
<uses-permission android:name="android.permission.BLUETOOTH"/> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
如果想宣告你的app只為具有BLE的裝置提供,在manifest檔案中包括:
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
程式碼中判斷手機是否支援BLE特性:
// 使用此檢查確定BLE是否支援在裝置上,然後你可以有選擇性禁用BLE相關的功能 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) { Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show(); finish(); }
在Android中使用BLE
1.獲取 BluetoothAdapter
所有的藍芽活動都需要藍芽介面卡。BluetoothAdapter代表裝置本身的藍芽介面卡(藍芽無線)。整個系統只有一個藍芽介面卡,而且你的app使用它與系統互動。
//使用getSystemService()返回BluetoothManager,然後將其用於獲取介面卡的一個例項。 // 初始化藍芽介面卡 final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); mBluetoothAdapter = bluetoothManager.getAdapter();
2.開啟藍芽
呼叫isEnabled())去檢測藍芽當前是否開啟。如果該方法返回false,藍芽被禁用。下面的程式碼檢查藍芽是否開啟,如果沒有開啟,將顯示錯誤提示使用者去設定開啟藍芽
// 確保藍芽在裝置上可以開啟 if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) { Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT); }
3.搜尋藍芽裝置
為了發現BLE裝置,使用startLeScan())方法。這個方法需要一個引數BluetoothAdapter.LeScanCallback。你必須實現它的回撥函式,那就是返回的掃描結果。因為掃描非常消耗電量,你應當遵守以下準則:
*只要找到所需的裝置,停止掃描。
*不要在迴圈裡掃描,並且對掃描設定時間限制。以前可用的裝置可能已經移出範圍,繼續掃描消耗電池電量。
public void cancelDiscovery() { if(isDiscovering) { isDiscovering = false; mBluetoothAdapter.stopLeScan(mLeScanCallback); } } public boolean isDiscovering() { return isDiscovering; } public void startDiscovery() { mBluetoothAdapter.startLeScan(mLeScanCallback); isDiscovering = true; mHandler.postDelayed(new Runnable() { @Override public void run() { cancelDiscovery(); if (getCallback() != null) getCallback().onDiscoveryComplete(); } }, SCAN_PERIOD); } private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() { @Override public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) { if (getCallback() != null) getCallback().onDeviceFound(device); } };
GATT連線
搜尋結束後,我們可得到一個搜尋結果 BluetoothDevice ,它表示搜到的藍芽裝置
1.呼叫
mBluetoothGatt = device.connectGatt(this, false, mGattCallback);
可以建立一個GATT連線,它需要一個 回撥mGattCallback 引數。
2.在回撥方法的 onConnectionStateChange 中,我們可以通過 status 判斷是否GATT連線成功
3.在GATT連線建立成功後,我們呼叫 mBluetoothGatt.discoverServices() 方法 發現GATT服務。
如果搜到服務將會觸發onServicesDiscovered回撥
// Implements callback methods for GATT events that the app cares about.For example, // connection change and services discovered. private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { if (newState == BluetoothProfile.STATE_CONNECTED) { Log.e(TAG, "Connected to GATT server."); // Attempts to discover services after successful connection. Log.e(TAG, "Attempting to start service discovery:" + mBluetoothGatt.discoverServices()); } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { Log.e(TAG, "Disconnected from GATT server."); setState(ConnectionState.STATE_NONE); if (getConnectionCallback() != null) getConnectionCallback().onConnectionLost(); } } @Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { if (status == BluetoothGatt.GATT_SUCCESS) { Log.e(TAG, "onServicesDiscovered received:SUCCESS"); initCharacteristic(); try { Thread.sleep(200);//延遲傳送,否則第一次訊息會不成功 } catch (InterruptedException e) { e.printStackTrace(); } if (getConnectionCallback() != null) getConnectionCallback().onConnected(mBluetoothDevice.getName()); setState(ConnectionState.STATE_CONNECTED); } else { Log.e(TAG, "onServicesDiscovered error falure " + status); setState(ConnectionState.STATE_NONE); if (getConnectionCallback() != null) getConnectionCallback().onConnectionLost(); } } @Override public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { super.onCharacteristicWrite(gatt, characteristic, status); Log.e(TAG, "onCharacteristicWrite status: " + status); } @Override public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { super.onDescriptorWrite(gatt, descriptor, status); Log.e(TAG, "onDescriptorWrite status: " + status); } @Override public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { super.onDescriptorRead(gatt, descriptor, status); Log.e(TAG, "onDescriptorRead status: " + status); } @Override public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { Log.e(TAG, "onCharacteristicRead status: " + status); } @Override public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { Log.e(TAG, "onCharacteristicChanged characteristic: " + characteristic); readCharacteristic(characteristic); } };
發現服務 (觸發onServicesDiscovered)
在發現服務後,會觸發 GATT回撥的onServicesDiscovered 方法,我們需要在這裡初始化我們的操作,包括:
1 檢視服務。或者便利查詢指定的(和目標硬體UUID符合的)服務。
2 獲得指定服務的特徵 characteristic1
3 訂閱“特徵”發生變化的通知”
public void initCharacteristic() { if (mBluetoothGatt == null) throw new NullPointerException(); List<BluetoothGattService> services = mBluetoothGatt.getServices(); Log.e(TAG, services.toString()); BluetoothGattService service = mBluetoothGatt.getService(uuidServer); characteristic1 = service.getCharacteristic(uuidChar1); characteristic2 = service.getCharacteristic(uuidChar2); final String uuid = "00002902-0000-1000-8000-00805f9b34fb"; if (mBluetoothGatt == null) throw new NullPointerException(); mBluetoothGatt.setCharacteristicNotification(characteristic1, true); BluetoothGattDescriptor descriptor = characteristic1.getDescriptor(UUID.fromString(uuid)); descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); mBluetoothGatt.writeDescriptor(descriptor); }
訂閱“特徵”發生變化的通知”
呼叫mBluetoothGatt.setCharacteristicNotification()方法,傳入一個特徵 characteristic 物件。
當這個特徵裡的資料發生變化(接收到資料了),會觸發 回撥方法的onCharacteristicChanged 方法。我們在這個回撥方法中讀取資料。
final String uuid = "00002902-0000-1000-8000-00805f9b34fb"; if (mBluetoothGatt == null) throw new NullPointerException(); mBluetoothGatt.setCharacteristicNotification(characteristic1, true); BluetoothGattDescriptor descriptor = characteristic1.getDescriptor(UUID.fromString(uuid)); descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); mBluetoothGatt.writeDescriptor(descriptor);
讀取資料
GATT的回撥中有onCharacteristicChanged 方法,我們在這裡可以獲得接收的資料
@Override public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { Log.e(TAG, "onCharacteristicChanged characteristic: " + characteristic); readCharacteristic(characteristic); }
呼叫 characteristic.getValue() 方法,獲得位元組
public void readCharacteristic(BluetoothGattCharacteristic characteristic) { if (mBluetoothAdapter == null || mBluetoothGatt == null) { Log.e(TAG, "BluetoothAdapter not initialized"); return; } mBluetoothGatt.readCharacteristic(characteristic); byte[] bytes = characteristic.getValue(); String str = new String(bytes); Log.e(TAG, "## readCharacteristic, 讀取到: " + str); if (getConnectionCallback() != null) getConnectionCallback().onReadMessage(bytes); }
寫入資料
寫入資料時,我們需要先獲得特徵,特徵存在於服務內,一般在發現服務的 onServicesDiscovered 時,查詢到特徵物件。
public void write(byte[] cmd) { Log.e(TAG, "write:" + new String(cmd)); synchronized (LOCK) { characteristic2.setValue(cmd); characteristic2.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT); mBluetoothGatt.writeCharacteristic(characteristic2); if (getConnectionCallback() != null) getConnectionCallback().onWriteMessage(cmd); } }
關閉藍芽連線
public void close() {
if (mBluetoothGatt == null) {
return;
}
mBluetoothGatt.close();
mBluetoothGatt = null;
}
參考
https://developer.android.com/guide/topics/connectivity/bluetooth-le.html