zoukankan      html  css  js  c++  java
  • android BLE Peripheral 手机模拟设备发出BLE广播 BluetoothLeAdvertiser

    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扫描打印的源码就不放出了,很简单。

  • 相关阅读:
    Constants and Variables
    随想
    C#基础篇之语言和框架介绍
    Python基础19 实例方法 类方法 静态方法 私有变量 私有方法 属性
    Python基础18 实例变量 类变量 构造方法
    Python基础17 嵌套函数 函数类型和Lambda表达式 三大基础函数 filter() map() reduce()
    Python基础16 函数返回值 作用区域 生成器
    Python基础11 List插入,删除,替换和其他常用方法 insert() remove() pop() reverse() copy() clear() index() count()
    Python基础15 函数的定义 使用关键字参数调用 参数默认值 可变参数
    Python基础14 字典的创建修改访问和遍历 popitem() keys() values() items()
  • 原文地址:https://www.cnblogs.com/Free-Thinker/p/8695999.html
Copyright © 2011-2022 走看看