Android藍芽開發(二) BLE4.0低功耗藍芽
阿新 • • 發佈:2019-02-15
一、BLE4.0低功耗藍芽
Bluetooth Low Energy,藍芽低功耗,是從藍芽4.0開始支援的技術。相較傳統藍芽,傳輸速度更快、覆蓋範圍廣、安全性高、延時短、耗電低等特點。
二、關鍵術語
1.GATT(通用屬性配置):通用屬性配置檔案,用於ble鏈路上傳送和接收“屬性”的資料塊。目前所有的ble應用都是基於GATT的,一個裝置可以實現多個配置檔案。2.ATT(屬性協議):GATT是構建於ATT上面的,每一個屬性都是由唯一標識碼(UUID)來唯一確定。
ble互動的橋樑是Service、Characteristic、Descriptor 三者都是由UUID作為唯一識別符號
3.Characteristic(特徵):一個特徵包含一個單一的值和0-n個描述符(Descriptor),描述符描述用於特徵的值。一個特質可以被認為是一個數據型別,或一個類。
4.Descriptor(描述符):對Characteristic的描述,如範圍、單位等。
5.Service(服務):服務是特徵的集合。可以包含多個Characteristic。一個ble終端可以包含多個Service,一個Characteristic可以包含一個Value和多個Descriptor。
三、許可權申請
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
如果需要宣告應用僅對低功耗藍芽有效,還需要在app的manifest中宣告
<uses-feature android:name="android.hardware.bluetooth_le"
android:required="true" />
四、相關類
藍芽4.0API的相關類在Framework的 frameworks/base/core/java/android/bluetooth/ 中其中主要的類有:1.BluetoothGatt:中央裝置使用的類,處理資料
2.BluetoothGattCallback: 中央裝置回撥
3.BluetoothGattServer:周邊裝置提供資料
4.BluetoothGattServerCallback:周邊裝置的回撥
5.BluetoothGattService:Gatt服務
6.BluetoothGattCharacteristic:Gatt特性
7.BuletoothGattDecriptor:Gatt描述
五、角色和職責
2.GATT伺服器與GATT客戶端: 這兩個 決定了建立連線後的通訊方式。
六、配置BLE
在使用BLE之前,我們需要驗證裝置是否支援BLE4.0,如果支援,則需要驗證藍芽是否開啟。而這些操作,都是使用BluetoothAdapter.1.獲取BluetoothAdapter
BluetoothAdapter代表裝置自身的藍芽介面卡,整個系統,只會有一個該介面卡。我們需要通過獲取系統服務來獲取BluetoothAdapter。
Android 4.3(API 18)以後,才支援BluetoothManager
// Initializes Bluetooth adapter. final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); mBluetoothAdapter = bluetoothManager.getAdapter();
2.使用藍芽
我們可以通過 BluetoothAdapter 的isEnable()方法來判斷藍芽是否開啟。如果沒開啟,我們需要提醒使用者開啟藍芽,又或者,直接呼叫enable()方法來開啟藍芽。
if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
七、中央掃描周邊BLE裝置
1.掃描裝置:
//搜尋附近所有的外圍裝置
mBluetoothAdapter.startLeScan(mLeScanCallback);
//搜尋某些uuid的外圍裝置。 可指定uuid
mBluetoothAdapter.startLeScan(uuid[] ,mLeScanCallback);
停止掃描
mBluetoothAdapter.stopLeScan(mLeScanCallback);
2.掃描結果回撥:
mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
}
}
其中,返回的 device:搜尋到的ble裝置 rssi:訊號強度 scanRecord:遠端裝置廣告記錄的內容(藍芽名稱)
八、傳送連結請求,獲取中央裝置
根據第七步搜尋到的外圍裝置,我們需要去連結它。連結,指的是連結到GATT伺服器裝置,
此處我們需要傳進去三個引數:
1.context
2.false:直接立即連結 true:等待遠端裝置可用時自動連結
3.藍芽連結回撥 其中包括:連結狀態改變、characteristic的read、write、change、和MTU change的監聽
// 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) {
String intentAction;
//收到裝置notify值 (裝置上報值) 連結狀態改變回調方法,此處處理連結成功
if (newState == BluetoothProfile.STATE_CONNECTED) {
intentAction = ACTION_GATT_CONNECTED;
mConnectionState = STATE_CONNECTED;
broadcastUpdate(intentAction);
Log.i(TAG, "Connected to GATT server.");
// Attempts to discover services after successful connection.
Log.i(TAG, "Attempting to start service discovery:" +
mBluetoothGatt.discoverServices());
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
intentAction = ACTION_GATT_DISCONNECTED;
mConnectionState = STATE_DISCONNECTED;
Log.i(TAG, "Disconnected from GATT server.");
broadcastUpdate(intentAction);
}
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) { //連結成功後,從下面獲取service列表
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){
if(MTU>20){
boolean ret = mBluetoothGatt.requestMtu(MTU);
Log.d("BLE","requestMTU "+MTU+" ret="+ret);
}
}
broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED);
} else {
Log.w(TAG, "onServicesDiscovered received: " + status);
System.out.println("onServicesDiscovered received: " + status);
}
}
@Override
public void onCharacteristicRead(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic,
int status) {
//讀取從周邊裝置傳遞過來的資料值,在這裡讀資料
if (status == BluetoothGatt.GATT_SUCCESS) {
broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
}
}
@Override
public void onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) { //特徵狀態改變
broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
}
@Override
public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
super.onMtuChanged(gatt, mtu, status);
if (status == BluetoothGatt.GATT_SUCCESS) {
Log.e(TAG, "onMtuChanged: "+mtu);
//local var to record MTU size
}
}
};
1.通過onServicesDiscovered()成功回撥獲取的BluetoothGatt 我們可以呼叫gatt的getServices()方法,來獲取List<BluetoothGattService>集合。2.從集合中找到我們需要的service後,可以呼叫該service中的getCharacteristics()方法,來獲取List<Characteristic> 集合。
3.再從指定的Characteristic中,我們可以通過getDescriptor()方法來獲取該特徵所包含的descriptor
以上的BluetoothGattService、BluetoothGattCharacteristic、BluetoothGattDescriptor。我們都可以通過其getUuid()方法,來獲取其對應的Uuid,從而判斷是否是自己需要的。
九、中央裝置寫入和接收資料
1.寫入特徵值寫入特徵值,首先我們的特徵值屬性,滿足BluetoothGattCharacteristic.PROPERTY_WRITE或BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE,如果其property都不包含這兩個,寫特徵值writeCharacteristic()函式直接返回false,什麼都不做處理。
其次此characteristic許可權應滿足BluetoothGattCharacteristic.PERMISSION_WRITE,否則onCharacteristicWrite()回撥收到GATT_WRITE_NOT_PERMITTED迴應。
characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
characteristic.setValue(string);
boolean isSuccess = mBluetoothLeService.writeCharacteristic(characteristic);
如上程式碼,在寫入特徵之前,我們可以設定寫入的型別,寫入型別有三種
WRITE_TYPE_DEFAULT 預設型別,需要外圍裝置的確認,也就是需要外圍裝置的迴應,這樣才能繼續傳送寫。
WRITE_TYPE_NO_RESPONSE 設定該型別不需要外圍裝置的迴應,可以繼續寫資料。加快傳輸速率。
WRITE_TYPE_SIGNED 寫特徵攜帶認證簽名
當外圍裝置收到中央寫特徵值的請求,會回撥 onCharacteristicWriteRequest。
如果此次請求需要回應,則外圍裝置迴應 mGattServer.sendResponse
中央裝置收到響應,回撥onCharacteristicWrite(BluetoothGatt gatt,BluetoothGattCharacteristic characteristic, int status)
2.讀取特徵值
讀取特徵值,需要呼叫BluetoothLeService的readCharacteristic(characteristic)方法。 讀特徵,也需要特徵具有相應的許可權和屬性。
(1)該特徵屬性必須包含PROPERTY_READ,否則返回false.
(2)該特徵屬性必須滿足BluetoothGattCharacteristic.PERMISSION_READ許可權,否則onCharacteristicRead()回撥收到GATT_READ_NOT_PERMITTED迴應。
外圍裝置接收到中央裝置讀特徵值請求時,則會呼叫onCharacteristicReadRequest()函式回撥。
外圍裝置迴應此請求,則呼叫sendResponse(BluetoothDevice device, int requestId, int status, int offset, byte[] value)。
而中央裝置收到外圍裝置迴應時,則會呼叫onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) 回撥。
3.訂閱
我們可以通過BluetoothLeService的setCharacteristicNotification(characteristic, true);(後面的第二個引數,true表示訂閱 false表示取消訂閱)方法來指定一個Characteristic特徵。當該特徵發生變化時,會回撥onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) 方法,通過引數characteristic,可獲得getValue獲得其中的內容。
注意:如果該特徵的屬性沒有設定value為:descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); 則收不到訂閱資訊。
十、外圍裝置的設定
1.獲取開啟外圍裝置
mGattServer = mBluetoothManager.openGattServer(mContext, callback);
//其中callback是一個MyGattServerCallback(繼承了BluetoothGattServerCallback)物件。
2.初始化特徵
其中我們可以看到上面第八步所介紹的,外圍裝置的特徵值的寫入和讀取的許可權設定。
BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(
UUID.fromString("0000bbb1-0000-1000-8000-00805f9b34fb"),
BluetoothGattCharacteristic.PROPERTY_NOTIFY +BluetoothGattCharacteristic.PROPERTY_WRITE +BluetoothGattCharacteristic.PROPERTY_READ ,
BluetoothGattCharacteristic.PERMISSION_WRITE+BluetoothGattCharacteristic.PERMISSION_READ );
3.設定特徵屬性
第二行程式碼,是第九步介紹的,訂閱步驟中所需要的特徵屬性值的設定。
BluetoothGattDescriptor descriptor = new BluetoothGattDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"), BluetoothGattDescriptor.PERMISSION_WRITE);
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
characteristic.addDescriptor(descriptor);
4.設定服務
第二個引數為service type,
SERVICE_TYPE_PRIMARY 基礎服務、主要服務。
SERVICE_TYPE_SECONDARY 輔助服務(由初級服務包含在內)。
BluetoothGattService 類中方法
addService(bluetoothGattService),將輔助服務新增到主要服務中。
getIncludeedServices() 獲取包含的服務列表。
getType() 獲取服務的type。
getUuid() 獲取服務的UUID。
final BluetoothGattService service = new BluetoothGattService(UUID.fromString("0000bbb0-0000-1000-8000-00805f9b34fb"),
BluetoothGattService.SERVICE_TYPE_PRIMARY);
service.addCharacteristic(characteristic);
5.新增服務
boolean isSuccess = gattServer.addService(service);
LogUtils.e(TAG," 新增service2:"+isSuccess );
6.開啟廣播
如何需要讓其他中央裝置搜尋到我們的周邊裝置呢? 這裡我們需要開啟廣播
mGattServer.startAdvertising();//開始廣播
mGattServer.stopAdvertising();//停止廣播
首先我們需要判斷裝置是否支援廣播的開啟
private void startService() {
//判斷你的裝置到底支援不支援BLE Peripheral,不支援則返回空
mBluetoothLeAdvertiser = bluetoothAdapter.getBluetoothLeAdvertiser();
Log.e(TAG,"mBluetoothLeAdvertiser"+mBluetoothLeAdvertiser);
if(mBluetoothLeAdvertiser == null){
return;
}
startAdvertising(); //初始化BLE藍芽廣播
}
public void startAdvertising() {
byte[] broadcastData = {0x34, 0x56};
String bleName = "小郎";
byte[] broadcastData = bleName.getBytes();
//廣播設定引數,廣播資料,還有一個是Callback
mBluetoothLeAdvertiser.startAdvertising(createAdvSettings(true, 0), createAdvertiseData(broadcastData), mAdvertiseCallback);
}
上面的開啟廣播中 有三個引數
(1)廣播的基本設定
public AdvertiseSettings createAdvSettings(boolean connectable, int timeoutMillis) {
AdvertiseSettings.Builder mSettingsbuilder = new AdvertiseSettings.Builder();
mSettingsbuilder.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY);
mSettingsbuilder.setConnectable(connectable);
mSettingsbuilder.setTimeout(timeoutMillis);
AdvertiseSettings mAdvertiseSettings = mSettingsbuilder.build();
return mAdvertiseSettings;
}
(2)設定廣播攜帶的引數
public AdvertiseData createAdvertiseData(byte[] data) {
AdvertiseData.Builder mDataBuilder = new AdvertiseData.Builder();
mDataBuilder.addManufacturerData(0x01AC, data);
mDataBuilder.addServiceUuid(ParcelUuid.fromString(uid));
mDataBuilder.setIncludeDeviceName(true); //設定是否攜帶裝置名稱
AdvertiseData mAdvertiseData = mDataBuilder.build();
return mAdvertiseData;
}
(3)廣播開啟回撥此處,我是在廣播開啟成功後,再初始化周邊裝置的
private AdvertiseCallback mAdvertiseCallback = new AdvertiseCallback() {
@Override
public void onStartSuccess(AdvertiseSettings settingsInEffect) {
super.onStartSuccess(settingsInEffect);
LogUtils.e(TAG, "開啟廣播成功");
ToastUtils.showToast(BLEConnectService.this, "開啟廣播成功", 2000);
initGattServer(); //初始化GATT服務
}
@Override
public void onStartFailure(int errorCode) {
super.onStartFailure(errorCode);
ToastUtils.showToast(BLEConnectService.this, "開啟廣播失敗 errorCode:" + errorCode, 2000);
}
};
其中第十步驟的第一小步 開啟外圍裝置的回撥方法 在第九步有介紹 此處就不再繼續解釋了
十一、開發中的注意事項 (有其他的歡迎補充)
1.中央和外圍裝置傳輸的資料 有20個位元組長度的限制。 在5.0之後 我們可以通過中央裝置 設定MTU值,來增大傳輸長度
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){
if(MTU>20){
boolean ret = mBluetoothGatt.requestMtu(MTU);
Log.d("BLE","requestMTU "+MTU+" ret="+ret);
}
}
次程式碼可寫入到onServicesDiscovered成功的回撥中。 當我們設定MTU長度成功時,中央和外圍裝置都會回撥onMtuChanged()。
如果我們的手機不是5.0的 目前我的解決方法是,通過資料拆分成多份,分多次傳輸的。