1. 程式人生 > >Android藍芽開發(二) BLE4.0低功耗藍芽

Android藍芽開發(二) BLE4.0低功耗藍芽

一、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描述

五、角色和職責

1.中央與周邊: 中央裝置,可以進行掃描和搜尋周邊裝置發出的廣播。  而周邊裝置,可以發出裝置廣播。
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的   目前我的解決方法是,通過資料拆分成多份,分多次傳輸的。