android藍芽BLE(三) —— 廣播

大棋發表於2019-05-22

android BLE系列:

android藍芽BLE(一) —— 掃描

android藍芽BLE(二) —— 通訊

android藍芽BLE(三) —— 廣播

手機作為外設

        在藍芽開發中,有些情況是不需要連線的,只要外設廣播自己的資料即可,例如蘋果的ibeacon。自Android 5.0更新藍芽API後,手機可以作為外設廣播資料。

廣播包有兩種:

  • 廣播包(Advertising Data)
  • 響應包(Scan Response)

        其中廣播包是每個外設都必須廣播的,而響應包是可選的。每個廣播包的長度必須是31個位元組,如果不到31個位元組 ,則剩下的全用0填充。 補全。這部分的資料是無效的

android藍芽BLE(三) —— 廣播

廣播資料單元

        廣播包中包含若干個廣播資料單元,廣播資料單元也稱為 AD Structure。

廣播資料單元 = 長度值Length + AD type + AD Data。

長度值Length只佔一個位元組,並且位於廣播資料單元的第一個位元組

概念的東西有些抽象,先看看下面的廣播報文:

android藍芽BLE(三) —— 廣播

        0x代表這串字串是十六進位制的字串。兩位十六進位制數代表一個位元組。因為兩個字元組成的十六進位制字串最大為FF,即255,而Java中byte型別的取值範圍是-128到127,剛好可以表示一個255的大小。所以兩個十六進位制的字串表示一個位元組。

        繼續檢視報文內容,開始讀取第一個廣播資料單元。讀取第一個位元組:0x07,轉換為十進位制就是7,即表示後面的7個位元組是這個廣播資料單元的資料內容。超過這7個位元組的資料內容後,表示是一個新的廣播資料單元。

        而第二個廣播資料單元,第一個位元組的值是0x16,轉換為十進位制就是22,表示後面22個位元組為第二個廣播資料單元。

        在廣播資料單元的資料部分中,第一個位元組代表資料型別(AD type),決定資料部分表示的是什麼資料。(即廣播資料單元第二個位元組為AD type)

android藍芽BLE(三) —— 廣播

AD Type的型別如下:

  • Flags:TYPE = 0x01。用來標識裝置LE物理連線。

        bit 0: LE 有限發現模式
        bit 1: LE 普通發現模式
        bit 2: 不支援 BR/EDR
        bit 3: 對 Same Device Capable(Controller) 同時支援 BLE 和 BR/EDR
        bit 4: 對 Same Device Capable(Host) 同時支援 BLE 和 BR/EDR
        bit 5..7: 預留

        這bit 1~7分別代表著傳送該廣播的藍芽晶片的物理連線狀態。當bit的值為1時,表示支援該功能。 例:

android藍芽BLE(三) —— 廣播

  • Service UUID。廣播資料中可以將裝置支援的GATT Service的UUID廣播出來,來告知中心裝置其支援的Service。
    對於不同bit的UUID,其對應的型別也有不同:

        非完整的16bit UUID: TYPE = 0x02;

        完整的16bit UUID 列表: TYPE = 0x03;

        非完整的32bit UUID 列表: TYPE = 0x04;

        完整的32bit UUID 列表: TYPE = 0x05;

        非完整的128bit UUID 列表: TYPE = 0x06;

        完整的128bit UUID: TYPE = 0x07;

  • TX Power Level: TYPE = 0x0A,表示裝置傳送廣播包的訊號強度。 數值範圍:±127 dBm。
  • 裝置名字,DATA 是名字的字串,可以是裝置的全名,也可以是裝置名字的縮寫。

        縮寫的裝置名稱: TYPE = 0x08

        完整的裝置名稱: TYPE = 0x09

  • Service Data: Service 對應的資料。

        16 bit UUID Service: TYPE = 0x16, 前 2 位元組是 UUID,後面是 Service 的資料;

        32 bit UUID Service: TYPE = 0x20, 前 4 位元組是 UUID,後面是 Service 的資料;

        128 bit UUID Service: TYPE = 0x21, 前 16 位元組是 UUID,後面是 Service 的資料;

  • 廠商自定義資料: TYPE = 0xFF。 廠商資料中,前兩個位元組表示廠商ID,剩下的是廠商自定義的資料。

BLE廣播

藍芽廣播的資料格式大致講了一下,有助於下面的廣播操作的理解。

先自定義UUID:

//UUID
public static UUID UUID_SERVICE = UUID.fromString("0000fff7-0000-1000-8000-00805f9b34fb");
複製程式碼

        開啟廣播一般需要3~4物件:廣播設定(AdvertiseSettings)、廣播包(AdvertiseData)、掃描包(可選)、廣播回撥(AdvertiseCallback)。

先看看廣播設定(AdvertiseSettings)如何定義:

//初始化廣播設定
mAdvertiseSettings = new AdvertiseSettings.Builder()
        //設定廣播模式,以控制廣播的功率和延遲。
        .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_POWER)
        //發射功率級別
        .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH)
        //不得超過180000毫秒。值為0將禁用時間限制。
        .setTimeout(3000)
        //設定是否可以連線
        .setConnectable(false)
        .build();
複製程式碼

(1)、通過AdvertiseSettings.Builder#setAdvertiseMode() 設定廣播模式。其中有3種模式:

        1、在均衡電源模式下執行藍芽LE廣播:AdvertiseSettings#ADVERTISE_MODE_BALANCED
        2、在低延遲,高功率模式下執行藍芽LE廣播: AdvertiseSettings#ADVERTISE_MODE_LOW_LATENCY
        3、在低功耗模式下執行藍芽LE廣播:AdvertiseSettings#ADVERTISE_MODE_LOW_POWER

(2)、通過AdvertiseSettings.Builder#setAdvertiseMode() 設定廣播發射功率。共有4種功率模式:

        1、使用高TX功率級別進行廣播:AdvertiseSettings#ADVERTISE_TX_POWER_HIGH
        2、使用低TX功率級別進行廣播:AdvertiseSettings#ADVERTISE_TX_POWER_LOW
        3、使用中等TX功率級別進行廣播:AdvertiseSettings#ADVERTISE_TX_POWER_MEDIUM
        4、使用最低傳輸(TX)功率級別進行廣播:AdvertiseSettings#ADVERTISE_TX_POWER_ULTRA_LOW

(3)、通過AdvertiseSettings.Builder#setTimeout()設定持續廣播的時間,單位為毫秒。最多180000毫秒。當值為0則無時間限制,持續廣播,除非呼叫BluetoothLeAdvertiser#stopAdvertising()。

(4)、通過AdvertiseSettings.Builder#setConnectable()設定該廣播是否可以連線的。

之前說過,外設必須廣播廣播包,掃描包是可選。但新增掃描包也意味著廣播更多得資料,即可廣播62個位元組。

//初始化廣播包
mAdvertiseData = new AdvertiseData.Builder()
        //設定廣播裝置名稱
        .setIncludeDeviceName(true)
        //設定發射功率級別
        .setIncludeDeviceName(true)
        .build();
        
//初始化掃描響應包
mScanResponseData = new AdvertiseData.Builder()
        //隱藏廣播裝置名稱
        .setIncludeDeviceName(false)
        //隱藏發射功率級別
        .setIncludeDeviceName(false)
        //設定廣播的服務UUID
        .addServiceUuid(new ParcelUuid(UUID_SERVICE))
        //設定廠商資料
        .addManufacturerData(0x11,hexStrToByte(mData))
        .build();
複製程式碼

可見無論是廣播包還是掃描包,其廣播的內容都是用AdvertiseData類封裝的。

(1)、AdvertiseData.Builder#setIncludeDeviceName()方法,可以設定廣播包中是否包含藍芽的名稱。

(2)、AdvertiseData.Builder#setIncludeDeviceName()方法,可以設定廣播包中是否包含藍芽的發射功率。

(3)、AdvertiseData.Builder#addServiceUuid(ParcelUuid)方法,可以設定特定的UUID在廣播包中。

(4)、AdvertiseData.Builder#addServiceData(ParcelUuid,byte[])方法,可以設定特定的UUID和其資料在廣播包中。

(5)、AdvertiseData.Builder#addManufacturerData(int,byte[])方法,可以設定特定廠商Id和其資料在廣播包中。

        從AdvertiseData.Builder的設定中可以看出,如果一個外設需要在不連線的情況下對外廣播資料,其資料可以儲存在UUID對應的資料中,也可以儲存在廠商資料中。但由於廠商ID是需要由Bluetooth SIG進行分配的,廠商間一般都將資料設定在廠商資料。

另外可以通過BluetoothAdapter#setName()設定廣播的名稱

//獲取藍芽設配器
BluetoothManager bluetoothManager = (BluetoothManager)
        getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();
//設定裝置藍芽名稱
mBluetoothAdapter.setName("daqi");
複製程式碼

先看一個例子,我們分別在廣播包掃描包中設定AdvertiseData.Builder的每一種廣播報文引數,得到一下報文內容:

android藍芽BLE(三) —— 廣播

(1)、Type = 0x01 表示裝置LE物理連線。
(2)、Type = 0x09 表示裝置的全名
(3)、Type = 0x03 表示完整的16bit UUId。其值為0xFFF7。
(4)、Type = 0xFF 表示廠商資料。前兩個位元組表示廠商ID,即廠商ID為0x11。後面的為廠商資料,具體由使用者自行定義。
(5)、Type = 0x16 表示16 bit UUID的資料,所以前兩個位元組為UUID,即UUID為0xF117,後續為UUID對應的資料,具體由使用者自行定義。

最後繼承AdvertiseCallback自定義廣播回撥。

private class daqiAdvertiseCallback extends AdvertiseCallback {
    //開啟廣播成功回撥
    @Override
    public void onStartSuccess(AdvertiseSettings settingsInEffect){
        super.onStartSuccess(settingsInEffect);
        Log.d("daqi","開啟服務成功");
    }

    //無法啟動廣播回撥。
    @Override
    public void onStartFailure(int errorCode) {
        super.onStartFailure(errorCode);
        Log.d("daqi","開啟服務失敗,失敗碼 = " + errorCode);
    }
}
複製程式碼

初始化完畢上面的物件後,就可以進行廣播:

//如果晶片組支援多廣播,則返回true
boolean result1 = mBluetoothAdapter.isMultipleAdvertisementSupported();
if (result1){
    //獲取BLE廣播的操作物件。
    //如果藍芽關閉或此裝置不支援藍芽LE廣播,則返回null。
    mBluetoothLeAdvertiser = mBluetoothAdapter.getBluetoothLeAdvertiser();
    if (mBluetoothLeAdvertiser != null){
        //開啟廣播
        mBluetoothLeAdvertiser.startAdvertising(mAdvertiseSettings,
                mAdvertiseData, mScanResponseData, mAdvertiseCallback);
    }else {
        Log.d("daqi","手機藍芽未開啟");
    }
}else {
    Log.d("daqi","該手機晶片不支援廣播");
}
複製程式碼

        廣播主要是通過BluetoothLeAdvertiser#startAdvertising()方法實現,但在之前需要先獲取BluetoothLeAdvertiser物件。

BluetoothLeAdvertiser物件存在兩個情況獲取為Null:

        1、手機藍芽模組不支援BLE廣播
        2、藍芽未開啟

        所以在呼叫BluetoothAdapter#getBluetoothLeAdvertiser()前,需要先呼叫BluetoothAdapter#isMultipleAdvertisementSupported()檢查手機是否支援傳送BLE廣播。

        與廣播成對出現就是BluetoothLeAdvertiser.stopAdvertising()停止廣播了,傳入開啟廣播時傳遞的廣播回撥物件,即可關閉廣播:

mBluetoothLeAdvertiser.stopAdvertising(mAdvertiseCallback)
複製程式碼

啟動GATT Service 和 Characteristic

        雖然通過廣播告知外邊自身擁有這些Service,但手機自身並沒有初始化Gattd的Service。導致外部的中心裝置連線手機後,並不能找到對應的GATT Service 和 獲取對應的資料。

BluetoothGattService service = new BluetoothGattService(UUID_SERVICE,
                                BluetoothGattService.SERVICE_TYPE_PRIMARY);
複製程式碼

建立BluetoothGattService時,傳入兩個引數:UUID和Service型別。

Service型別有兩個級別:

  • BluetoothGattService#SERVICE_TYPE_PRIMARY 主服務
  • BluetoothGattService#SERVICE_TYPE_PRIMARY 次要服務(存在於主服務中的服務)

        我們都知道Gatt中,Service的下一級是Characteristic,Characteristic是最小的通訊單元,通過對Characteristic進行讀寫操作來進行通訊。

//初始化特徵值
mGattCharacteristic = new BluetoothGattCharacteristic(UUID_CHARACTERISTIC,
        BluetoothGattCharacteristic.PROPERTY_WRITE|
                BluetoothGattCharacteristic.PROPERTY_NOTIFY|
                BluetoothGattCharacteristic.PROPERTY_READ,
        BluetoothGattCharacteristic.PERMISSION_WRITE|
                BluetoothGattCharacteristic.PERMISSION_READ);
複製程式碼

建立BluetoothGattCharacteristic時,傳入三兩個引數:UUID、特徵屬性 和 許可權屬性。

特徵屬性表示該BluetoothGattCharacteristic擁有什麼功能,即能對BluetoothGattCharacteristic進行什麼操作。其中主要有3種:

  • BluetoothGattCharacteristic#PROPERTY_WRITE 表示特徵支援通知
  • BluetoothGattCharacteristic#PROPERTY_READ 表示特徵支援讀
  • BluetoothGattCharacteristic#PROPERTY_NOTIFY 表示特徵支援寫

許可權屬性用於配置該特徵值所具有的功能。主要兩種:

  • BluetoothGattCharacteristic#PERMISSION_WRITE 特徵寫許可權
  • BluetoothGattCharacteristic#PERMISSION_READ 特徵讀許可權

        當特徵值只有讀許可權時,呼叫BluetoothGatt#writeCharacteristic()對特徵值進行修改時,將返回false,無法寫入。並不會觸發BluetoothGattCallback#onCharacteristicWrite()回撥。

        當特徵值只有寫許可權時,呼叫BluetoothGatt#readCharacteristic()對特徵值進行讀取時,將返回false,無法寫入。並不會觸發BluetoothGattCallback#onCharacteristicRead()回撥。

        Characteristic下還有Descriptor,初始化BluetoothGattDescriptor時傳入:Descriptor UUID 和 許可權屬性

//初始化描述
mGattDescriptor = new BluetoothGattDescriptor(UUID_DESCRIPTOR,BluetoothGattCharacteristic.PERMISSION_WRITE);
複製程式碼

為Service添addCharacteristic,為Characteristic新增Service:

//Service新增特徵值
mGattService.addCharacteristic(mGattCharacteristic);
mGattService.addCharacteristic(mGattReadCharacteristic);
//特徵值新增描述
mGattCharacteristic.addDescriptor(mGattDescriptor);
複製程式碼

        通過藍芽管理器mBluetoothManager獲取Gatt Server,用來新增Gatt Service。新增完Gatt Service後,外部中心裝置連線手機時,將能獲取到對應的GATT Service 和 獲取對應的資料

//初始化GattServer回撥
mBluetoothGattServerCallback = new daqiBluetoothGattServerCallback();

if (mBluetoothManager != null)
    mBluetoothGattServer = mBluetoothManager.openGattServer(this, mBluetoothGattServerCallback);
boolean result = mBluetoothGattServer.addService(mGattService);
if (result){
    Toast.makeText(daqiActivity.this,"新增服務成功",Toast.LENGTH_SHORT).show();
}else {
    Toast.makeText(daqiActivity.this,"新增服務失敗",Toast.LENGTH_SHORT).show();
}
複製程式碼

        定義Gatt Server回撥。當中心裝置連線該手機外設、修改特徵值、讀取特徵值等情況時,會得到相應情況的回撥。

private class daqiBluetoothGattServerCallback extends BluetoothGattServerCallback{

    //裝置連線/斷開連線回撥
    @Override
    public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
        super.onConnectionStateChange(device, status, newState);
    }

    //新增本地服務回撥
    @Override
    public void onServiceAdded(int status, BluetoothGattService service) {
        super.onServiceAdded(status, service);
    }
    
    //特徵值讀取回撥
    @Override
    public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) {
        super.onCharacteristicReadRequest(device, requestId, offset, characteristic);
    }
    
    //特徵值寫入回撥
    @Override
    public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
        super.onCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, responseNeeded, offset, value);
    }
    
    //描述讀取回撥
    @Override
    public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattDescriptor descriptor) {
        super.onDescriptorReadRequest(device, requestId, offset, descriptor);
    }
    
    //描述寫入回撥
    @Override
    public void onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
        super.onDescriptorWriteRequest(device, requestId, descriptor, preparedWrite, responseNeeded, offset, value);
    }
}
複製程式碼

最後開啟廣播後,用nRF連線後看到的特徵值資訊如下圖所示:(加多了一個只能都的特徵值)

android藍芽BLE(三) —— 廣播

相關文章