android BLE Peripheral 手機模擬裝置發出BLE廣播 BluetoothLeAdvertiser

weixin_33686714發表於2018-04-02

android 從4.3系統開始可以連線BLE裝置,這個大家都知道了。iOS是從7.0版本開始支援BLE。

android 進入5.0時代時,開放了一個新功能,手機可以模擬裝置發出BLE廣播, 這個新功能其實是 對標於 iOS系統的手機模擬iBeacon裝置。


先介紹一下BLE的廣播, BLE裝置之所以能被手機掃描到,是因為 BLE裝置一直在每隔 一段時間廣播一次,這個廣播裡面包含很多資料。

手機掃描BLE裝置程式碼如下:

        public void startScan(){
	        bluetoothAdapter.startLeScan(leScanCallback);
        }

	public void stopScan(){
		bluetoothAdapter.stopLeScan(leScanCallback);
	}
	private LeScanCallback leScanCallback=new LeScanCallback() {
		
		@Override
		public void onLeScan(BluetoothDevice bluetoothdeivce, int rssi, byte[] scandata) {
			//把byte陣列轉成16進位制字串,方便檢視
			Log.e("TAG","scandata:"+ CYUtils.Bytes2HexString(scandata));
		}
	};

ok,這段程式碼大家在做連線BLE裝置進行通訊的時候,已經很熟悉了。其中的 byte陣列 scandata就是 BLE裝置的廣播資料。


那麼接下來,我們開始使用 手機1 模擬成BLE裝置來傳送廣播,然後用手機2 來進行掃描檢視廣播資料 scandata

首先獲取 BluetoothAdapter, 熟悉的程式碼:

BluetoothManager bluetoothManager = (BluetoothManager)getSystemService(Context.BLUETOOTH_SERVICE);

bluetoothAdapter = bluetoothManager.getAdapter();


進行廣播的時候需要用到BluetoothLeAdvertiser,進行例項化:


mBluetoothLeAdvertiser = bluetoothAdapter.getBluetoothLeAdvertiser();

例項化好之後就可以進行廣播資料了,開啟廣播方法是:



BluetoothLeAdvertiser: 
public void startAdvertising(AdvertiseSettings settings,
            AdvertiseData advertiseData, final AdvertiseCallback callback)

其中, AdvertiseSettings 是廣播的一些設定,比如,廣播間隔,是否可以連線等等; AdvertiseData 就是廣播資料了, AdvertiseCallback是廣播回撥,會告訴你廣播成功還是失敗。


先給一段完整廣播程式碼如下:


    public void startAction(View v){
        byte[] broadcastData ={0x34,0x56};
        mBluetoothLeAdvertiser.startAdvertising(createAdvSettings(true, 0), createAdvertiseData(broadcastData), mAdvertiseCallback);
    }
    public void stopAction(View v) {
        mBluetoothLeAdvertiser.stopAdvertising(mAdvertiseCallback);
    }

    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;
    }

    public AdvertiseData createAdvertiseData(byte[] data) {
        AdvertiseData.Builder mDataBuilder = new AdvertiseData.Builder();
        mDataBuilder.addManufacturerData(0x01AC, data);
        AdvertiseData mAdvertiseData = mDataBuilder.build();
        return mAdvertiseData;
    }
    private AdvertiseCallback mAdvertiseCallback = new AdvertiseCallback() {
        @Override
        public void onStartSuccess(AdvertiseSettings settingsInEffect) {
            super.onStartSuccess(settingsInEffect);

            ToastUtils.showToast(MainActivity.this, "開啟廣播成功", 2000);
        }

        @Override
        public void onStartFailure(int errorCode) {
            super.onStartFailure(errorCode);
            ToastUtils.showToast(MainActivity.this, "開啟廣播失敗 errorCode:" + errorCode, 2000);
        }
    };


其中,廣播資料broadcastData 我暫時直接先定死為2個位元組 0x3456,同樣在createAdvertiseData裡面

也有定死的資料 0x01AC . 開啟成功之後

我們使用手機2 來掃描看下廣播的資料是什麼:


E/TAG: scandata:02011A05FFAC0134560000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

log列印出來的scandata 有效資料是 02011A05FFAC013456 .  給大家解釋一下這個資料的意思

為了看清楚,我分段如下: 02011A  05  FF AC01 3456   (注,這裡的都是16進位制數字)

02011A這3個位元組,02表示後面一段資料長度為2位元組,01表示資料型別是flag ,1A就是flag的資料了

05 表示後面的一段資料長度為 5個位元組, FF一個位元組,AC01 兩個位元組,3456兩個位元組,加起來一共5個位元組,老鐵沒毛病

FF,是一個資料型別,這是我們通過程式碼mDataBuilder.addManufacturerData(0x01AC, data); 新增廣播資料時候設定的

ManufacturerData 是指裝置廠商自定義資料,FF 就是代表下面的資料實體是廠商資料.

第一個引數0x01AC,是廠商id,id長度為2個位元組,不足2個位元組系統會補0,可以看到log列印出來的是 AC01,順序是倒過來的,這點要注意!

如果我程式碼是這樣寫的 :


mDataBuilder.addManufacturerData(0xAC, data); //只寫了一個位元組的id

那麼使用手機2 掃描出的scandata是:

 E/TAG: scandata:02011A05FFAC0034560000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

可以看到,AC後面系統自動補了00


廣播資料出除了可以新增ManufacturerData,還可以新增ServerUUID, 程式碼如下:

 

public AdvertiseData createAdvertiseData(byte[] data) {
        AdvertiseData.Builder mDataBuilder = new AdvertiseData.Builder();
        mDataBuilder.addManufacturerData(0x01AC, data);
        mDataBuilder.addServiceUuid( ParcelUuid.fromString("0000ae8f-0000-1000-8000-00805f9b34fb"));
        AdvertiseData mAdvertiseData = mDataBuilder.build();
        return mAdvertiseData;
    }

程式碼新增了一個 AE8F的 server uuid, 使用手機2 掃描的scandata 如下:


 

E/TAG: scandata:02011A05FFAC01345603038FAE00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

直接看03038FAE 這一段,第一個03 表示後面的一段資料長度為3個位元組  第二個03 表示這個資料型別是 server uuid型別,uuid的資料就是8FAE,順序是倒過來的!

有人會問: 如果 我把addServiceUuid程式碼放在 addManufacturerData 前面,掃描的資料順序是什麼樣的呢?

答案 還是:


E/TAG: scandata:02011A05FFAC01345603038FAE00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
可以新增多個 server uuid嗎? 可以,程式碼如下:
    public AdvertiseData createAdvertiseData(byte[] data) {
        AdvertiseData.Builder mDataBuilder = new AdvertiseData.Builder();
        mDataBuilder.addServiceUuid( ParcelUuid.fromString("0000ae8f-0000-1000-8000-00805f9b34fb"));
        mDataBuilder.addServiceUuid( ParcelUuid.fromString("0000ffe1-0000-1000-8000-00805f9b34fb"));
        mDataBuilder.addManufacturerData(0x01AC, data);
        AdvertiseData mAdvertiseData = mDataBuilder.build();
        return mAdvertiseData;
    }

掃描的結果是:



E/TAG: scandata:02011A05FFAC01345605038FAEE1FF0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

直接看0503 8FAE E1FF 這一段, 05 表示後面的一段資料長度是5個位元組,03表示資料型別是 server uuid, 8FAE是第一個uuid, E1FF是第二個uuid


這個ServerUUID 有什麼用呢? 

不知大家在掃描BLE裝置的時候,有沒有注意到這個方法:

BluetoothAdapter
public boolean startLeScan(final UUID[] serviceUuids, final LeScanCallback callback)



這個方法也可以用來掃描BLE裝置,但是多了一個引數, UUID陣列, 這個掃描方法是用來過濾BLE裝置用的,比如 你公司開發一個 藍芽防丟器APP,你使用 startLeScan(callback)這個方法掃描的話,你會發現你掃描到周圍的所有的BLE裝置,同事戴的小米手環可能也被你掃描到,這樣讓使用者來選擇裝置進行連線的話可能就比較迷糊,startLeScan(serviceUuids,callback) 這個方法在掃描的時候會過濾廣播裡的資料,只有符合的BLE裝置才會被掃描回撥。 所以,你們公司的藍芽防丟器裝置可以在廣播欄位里加入特定的server uuid, app掃描的時候可以過濾其他裝置。

我們來實現一下這個功能, 修改 手機2 的掃描程式碼:


        public void startScan(){
                UUID[] serviceUuids = new UUID[] { UUID	.fromString("0000ae8f-0000-1000-8000-00805f9b34fb") };
		bluetoothAdapter.startLeScan(serviceUuids, leScanCallback);
	}
	public void stopScan(){
		bluetoothAdapter.stopLeScan(leScanCallback);
	}
	private LeScanCallback leScanCallback=new LeScanCallback() {
		
		@Override
		public void onLeScan(BluetoothDevice bluetoothdeivce, int rssi, byte[] scandata) {
			//把byte陣列轉成16進位制字串,方便檢視
			Log.e("TAG","scandata:"+ CYUtils.Bytes2HexString(scandata));
		}
	};

掃描結果是這樣的:



05-23 16:13:30.522 10197-10197/jiqi.blescandemo E/TAG: scandata:02011A05FFAC01345605038FAEE1FF0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
05-23 16:13:30.625 10197-10197/jiqi.blescandemo E/TAG: scandata:02011A05FFAC01345605038FAEE1FF0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
05-23 16:13:30.735 10197-10197/jiqi.blescandemo E/TAG: scandata:02011A05FFAC01345605038FAEE1FF0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
05-23 16:13:30.847 10197-10197/jiqi.blescandemo E/TAG: scandata:02011A05FFAC01345605038FAEE1FF0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
05-23 16:13:30.955 10197-10197/jiqi.blescandemo E/TAG: scandata:02011A05FFAC01345605038FAEE1FF0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
05-23 16:13:31.061 10197-10197/jiqi.blescandemo E/TAG: scandata:02011A05FFAC01345605038FAEE1FF0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
05-23 16:13:31.192 10197-10197/jiqi.blescandemo E/TAG: scandata:02011A05FFAC01345605038FAEE1FF0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
05-23 16:13:31.283 10197-10197/jiqi.blescandemo E/TAG: scandata:02011A05FFAC01345605038FAEE1FF0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
05-23 16:13:31.369 10197-10197/jiqi.blescandemo E/TAG: scandata:02011A05FFAC01345605038FAEE1FF0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
05-23 16:13:31.480 10197-10197/jiqi.blescandemo E/TAG: scandata:02011A05FFAC01345605038FAEE1FF0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

可以發現,掃描結果裡面只會出現擁有  AE8F 這個uuid的 BLE裝置,搜尋不到其他裝置

注意:部分手機使用startLeScan(serviceUuids,callback)這個方法過濾裝置 會掃描不到裝置,即使這個裝置UUID符合過濾條件,我歸結為手機/系統問題,如三星手機


這樣,我們知道,廣播資料可以新增ManufacturerData,還可以新增ServerUUID, 還有嗎? 有,程式碼如下:

    public AdvertiseData createAdvertiseData(byte[] data) {
        AdvertiseData.Builder mDataBuilder = new AdvertiseData.Builder();
        mDataBuilder.addServiceUuid( ParcelUuid.fromString("0000ae8f-0000-1000-8000-00805f9b34fb"));
        mDataBuilder.addServiceUuid( ParcelUuid.fromString("0000ffe1-0000-1000-8000-00805f9b34fb"));
        mDataBuilder.addServiceData( ParcelUuid.fromString("0000ae8f-0000-1000-8000-00805f9b34fb"),new byte[]{0x64,0x12});
        mDataBuilder.addManufacturerData(0x01AC, data);
        AdvertiseData mAdvertiseData = mDataBuilder.build();
        return mAdvertiseData;
    }


掃描結果如下:

 

E/TAG: scandata:02011A05FFAC01345605038FAEE1FF05168FAE64120000000000000000000000000000000000000000000000000000000000000000000000000000000000

直接看05168FAE6412 這一段,05依然表示下面一段資料長度為5個位元組,16表示資料型別為 server data, 8FAE表示這個資料的uuid是AE8F, 6412就是資料本體了.

那麼這個 server data能做什麼呢?比如有這樣一個 產品:溫度計,溫度計硬體在廣播欄位裡的server data裡面加入它測量的溫度,這樣APP可以不連線溫度計裝置 只通過掃描就知道溫度了,是不是很方便.

以下有幾個坑請大家注意一下:

  情況1:

 

    public AdvertiseData createAdvertiseData(byte[] data) {
        AdvertiseData.Builder mDataBuilder = new AdvertiseData.Builder();
       // mDataBuilder.addServiceUuid( ParcelUuid.fromString("0000ae8f-0000-1000-8000-00805f9b34fb"));
        mDataBuilder.addServiceUuid( ParcelUuid.fromString("0000ffe1-0000-1000-8000-00805f9b34fb"));
        mDataBuilder.addServiceData( ParcelUuid.fromString("0000ae8f-0000-1000-8000-00805f9b34fb"),new byte[]{0x64,0x12});
        mDataBuilder.addManufacturerData(0x01AC, data);
        AdvertiseData mAdvertiseData = mDataBuilder.build();
        return mAdvertiseData;
    }


     我註釋了一句程式碼,廣播欄位裡我沒有 新增 ae8f這個 uuid,而直接新增了 ae8f的data 為 0x6412,那麼掃描結果如何?

     使用startLeScan(serviceUuids,callback)過濾 ae8f這個uuid,沒有掃描結果;

     使用startLeScan(callback),掃描結果如下:

E/TAG: scandata:02011A05FFAC0134560303E1FF05168FAE641200000000000000000000000000000000000000000000000000000000000000000000000000000000000000


     可以看到是有 ae8f對應的資料 6412,但是server uuid裡面是沒有 ae8f的.

  情況2:

     程式碼順序1:

        mDataBuilder.addServiceUuid( ParcelUuid.fromString("0000ae8f-0000-1000-8000-00805f9b34fb"));
        mDataBuilder.addServiceUuid( ParcelUuid.fromString("0000ffe1-0000-1000-8000-00805f9b34fb"));
        mDataBuilder.addServiceData( ParcelUuid.fromString("0000ffe1-0000-1000-8000-00805f9b34fb"),new byte[]{0x22,0x44});
        mDataBuilder.addServiceData( ParcelUuid.fromString("0000ae8f-0000-1000-8000-00805f9b34fb"),new byte[]{0x54,0x12});

//掃描結果
E/TAG: scandata:02011A05FFAC01345605038FAEE1FF05168FAE54120000000000000000000000000000000000000000000000000000000000000000000000000000000000


    程式碼順序2:

 

        mDataBuilder.addServiceUuid( ParcelUuid.fromString("0000ae8f-0000-1000-8000-00805f9b34fb"));
        mDataBuilder.addServiceUuid( ParcelUuid.fromString("0000ffe1-0000-1000-8000-00805f9b34fb"));
        mDataBuilder.addServiceData( ParcelUuid.fromString("0000ae8f-0000-1000-8000-00805f9b34fb"),new byte[]{0x54,0x11});
        mDataBuilder.addServiceData( ParcelUuid.fromString("0000ffe1-0000-1000-8000-00805f9b34fb"),new byte[]{0x22,0x43});

//掃描結果
E/TAG: scandata:02011A05FFAC01345605038FAEE1FF05168FAE54110000000000000000000000000000000000000000000000000000000000000000000000000000000000


    程式碼順序3:

           

        mDataBuilder.addServiceUuid( ParcelUuid.fromString("0000ffe1-0000-1000-8000-00805f9b34fb"));
        mDataBuilder.addServiceUuid( ParcelUuid.fromString("0000ae8f-0000-1000-8000-00805f9b34fb"));
        mDataBuilder.addServiceData( ParcelUuid.fromString("0000ae8f-0000-1000-8000-00805f9b34fb"),new byte[]{0x54,0x1A});
        mDataBuilder.addServiceData( ParcelUuid.fromString("0000ffe1-0000-1000-8000-00805f9b34fb"),new byte[]{0x22,0x47});

//掃描結果
E/TAG: scandata:02011A05FFAC0134560503E1FF8FAE05168FAE541A0000000000000000000000000000000000000000000000000000000000000000000000000000000000


    程式碼順序4:

    

        mDataBuilder.addServiceUuid( ParcelUuid.fromString("0000ffe1-0000-1000-8000-00805f9b34fb"));
        mDataBuilder.addServiceUuid( ParcelUuid.fromString("0000ae8f-0000-1000-8000-00805f9b34fb"));
        mDataBuilder.addServiceData( ParcelUuid.fromString("0000ffe1-0000-1000-8000-00805f9b34fb"),new byte[]{0x22,0x48});
        mDataBuilder.addServiceData( ParcelUuid.fromString("0000ae8f-0000-1000-8000-00805f9b34fb"),new byte[]{0x54,0x1B});

//掃描結果
E/TAG: scandata:02011A05FFAC0134560503E1FF8FAE05168FAE541B0000000000000000000000000000000000000000000000000000000000000000000000000000000000


    情況2總結:從上面4個程式碼順序的結果來看,總是掃描到 ae8f這個uuid對應的資料,沒有第二個 server data,但是為什麼每次都是ae8f?我TM也不知道!!


AdvertiseData介紹完畢,下面再稍微介紹一下 AdvertiseSettings

        AdvertiseSettings.Builder mSettingsbuilder = new AdvertiseSettings.Builder();
        mSettingsbuilder.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY);
        mSettingsbuilder.setConnectable(connectable);
        mSettingsbuilder.setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH);
        mSettingsbuilder.setTimeout(0);
        AdvertiseSettings mAdvertiseSettings = mSettingsbuilder.build();


 setAdvertiseMode(int advertiseMode)
         設定廣播的模式,低功耗,平衡和低延遲三種模式;
         對應  AdvertiseSettings.ADVERTISE_MODE_LOW_POWER  ,ADVERTISE_MODE_BALANCED ,ADVERTISE_MODE_LOW_LATENCY
         從左右到右,廣播的間隔會越來越短 


 setConnectable(boolean connectable)
          設定是否可以連線。
          廣播分為可連線廣播和不可連線廣播,一般不可連線廣播應用在iBeacon裝置上,這樣APP無法連線上iBeacon裝置


 setTimeout(int timeoutMillis)
          設定廣播的最長時間,最大值為常量AdvertiseSettings.LIMITED_ADVERTISING_MAX_MILLIS = 180 * 1000;  180秒
          設為0時,代表無時間限制會一直廣播
 setTxPowerLevel(int txPowerLevel)
          設定廣播的訊號強度
          常量有AdvertiseSettings.ADVERTISE_TX_POWER_ULTRA_LOW,ADVERTISE_TX_POWER_LOW,ADVERTISE_TX_POWER_MEDIUM,ADVERTISE_TX_POWER_HIGH 
          從左到右分別表示強度越來越強. 
          舉例:當設定為ADVERTISE_TX_POWER_ULTRA_LOW時,
          手機1和手機2放在一起,手機2掃描到的rssi訊號強度為-56左右,
          當設定為ADVERTISE_TX_POWER_HIGH  時, 掃描到的訊號強度為-33左右,
          訊號強度越大,表示手機和裝置靠的越近



     好了,關於BluetoothLeAdvertiser 的用法介紹完畢!!!!


可能有人會說,bluetoothAdapter.startLeScan(leScanCallback); 這個方法過時了怎麼辦,那可以看一下我的另一篇文章

android BLE 掃描BLE裝置 BluetoothLeScanner


原始碼附件:

模擬BLE廣播原始碼:http://pan.baidu.com/s/1bptOQyb

手機2掃描列印的原始碼就不放出了,很簡單。

相關文章