Android蓝牙开发系列文章-玩转BLE开发(二)

      本文是BLE开发第二篇,阅读第一篇,请点击《Android蓝牙开发系列文章-玩转BLE开发(一)》。本文主要讲解如何利用BLE实现IBeacon技术,并写个小demo实现该功能。

目录

1.IBeacon是个啥

2.如何实现Advertiser

     2.1申请必要的权限

2.2设置广播格式

2.3设置广播数据

2.4设置扫描响应数据

       2.5发起广播

3.Demo验证 

3.1验证是否可以被搜索到

  3.2利用BLE蓝牙开发助手验证

4.总结


看完上面第一段,部分同学可能有疑问了,不是讲解BLE嘛?怎么扯上了IBeacon?你要nong(注意,请跟着小岳岳一起读:nong)啥来?

       听我慢慢讲来。回答这个问题也很简单,目的是为了各大家呈现出各种BLE角色。

       看过我的《Android蓝牙开发系列文章-玩转BLE开发(一)》的同学可能会认为BLE中就两种角色嘛:client和server,server端先发广播,client端发起连接,然后进行gatt通信就好了啊。

       除了上面的client和server,其实还有两个角色。一种是只发广播包,也不支持连接的设备。还有一种是只接受广播包,也不对设备发起连接的设备。这好比一个“造谣者”一直散布谣言,对于别人的“审问”充耳不闻,另外一个角色只顾着收听别人说的话,不会去打断别人,也不去辨别是否是谣言。

      总结一下,BLE中有四种角色:

  • Advertiser:周期性向周围设备发送广播,不支持连接;
  • Observer:搜索周围设备,监听广播数据包;
  • Centeral:发起设备扫描并发起设备连接,在连接成功后会变成master;
  • Peripheral:发送广播数据包并支持连接,在连接成功后会变成slave。

       

    好了,本文可以正式开始了。

1.IBeacon是个啥

IBeacon是由苹果公司推出的一项技术,目的是为了弥补GPS无法覆盖室内定位的这种场景。通常,我们说的蓝牙定位利用的信号强度与距离有关(距离越远,接收者接收到的信号强度越小)来实现的。

在蓝牙SPC V5.1版本提出了基于出发角和到达角的厘米级定位。如果对无线定位技术感兴趣,可以自行百度学习一下,这里就不深入了。

我们看一下IBeacon广播包的结构,如下图所示。IBeacon广播包由30字节固定长度的前缀最长31个字节的有效负载构成。

看一下IBeacon的前缀的结构:

字段

长度(字节)

含义
IBeacon Prefix 9 该部分是固定的值,值为:02 01 06 1A FF 4C 00 02 15
Proximity UUID 16 用于区分不同厂商生存的IBeacon设备
Major 2 用于区分不同群组,例如不同的商店里IBeacon设备的Major值不同
Minor 2 用于区分群组内的不同IBeacon设备,例如同一家商店里的不同位置的IBeacon设备
Tx Power 1 表示发射功率,用于计算IBeacon设备与接受者之间的距离

腾讯公司利用IBeacon实现了微信摇一摇的功能,这一功能在O2O领域得到广泛应用。

2.如何实现Advertiser

     2.1申请必要的权限

AndroidManifest.xml中添加:

   <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-feature android:name="android.hardware.bluetooth_le"
        android:required="true" />

动态申请位置权限:

    private  void requestPermissions() {
        if (Build.VERSION.SDK_INT < 23){return;}
        //判断是否有权限
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            //请求权限
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, 1);
        }

        if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            //请求权限
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 1);
        }
    }

2.2设置广播格式

       //设置广播
        AdvertiseSettings settings = new AdvertiseSettings.Builder()
                .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY) //设置广播模式
                .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH) //设置发送功率
                .setConnectable(false) //能否连接,广播分为可连接广播和不可连接广播
                .build();

在这里,我们设置了广播模式为低延迟模式,除此模式外,还有均衡模式低功耗模式,低延迟模式下更容易被其他设备扫描到。

设置发射功率为高,除此之后,发射功率还有极低、低、中等三种。

​     /**
     * Advertise using the lowest transmission (TX) power level. Low transmission power can be used
     * to restrict the visibility range of advertising packets.
     */
    public static final int ADVERTISE_TX_POWER_ULTRA_LOW = 0;

    /**
     * Advertise using low TX power level.
     */
    public static final int ADVERTISE_TX_POWER_LOW = 1;

    /**
     * Advertise using medium TX power level.
     */
    public static final int ADVERTISE_TX_POWER_MEDIUM = 2;

通过setConnectable(false)来设置广播为不可连接。因为我们这里是仅仅做个Advertiser功能,所以不用设置可连接。下一小节中,我们会用微信小程序测试一下,我们这样设备后的设备是否可以连接成功,预期是不能的。

2.3设置广播数据

//设置广播内容
        AdvertiseData advertiseData = new AdvertiseData.Builder()
                .setIncludeDeviceName(true) //包含蓝牙名称
                .setIncludeTxPowerLevel(true) //包含发射功率级别
                .addManufacturerData(1, new byte[]{1, 2, 3}) //设备厂商数据,可以作为设备过滤条件
                .build();

设置广播数据中包含本地蓝牙设备的名称。

设置广播数据中包含发射功率等级,发射功率等级就是上面提到的:极低中等四个等级。

设备厂商数据,我们这里设置为“1,2,3”,这个字段可以作为扫描端(即client 端)进行设备过滤或者传递信息的字段。

除此之外,我们在广播数据中添加一个service,并且将该service绑定一定的数据。使用的方法如下:

 /**
         * Add a service UUID to advertise data.
         *
         * @param serviceUuid A service UUID to be advertised.
         * @throws IllegalArgumentException If the {@code serviceUuids} are null.
         */
        public Builder addServiceUuid(ParcelUuid serviceUuid) {
            if (serviceUuid == null) {
                throw new IllegalArgumentException("serivceUuids are null");
            }
            mServiceUuids.add(serviceUuid);
            return this;
        }
        /**
         * Add service data to advertise data.
         *
         * @param serviceDataUuid 16-bit UUID of the service the data is associated with
         * @param serviceData Service data
         * @throws IllegalArgumentException If the {@code serviceDataUuid} or {@code serviceData} is
         * empty.
         */
        public Builder addServiceData(ParcelUuid serviceDataUuid, byte[] serviceData) {
            if (serviceDataUuid == null || serviceData == null) {
                throw new IllegalArgumentException(
                        "serviceDataUuid or serviceDataUuid is null");
            }
            mServiceData.put(serviceDataUuid, serviceData);
            return this;
        }

2.4设置扫描响应数据

在扫描端搜索到我们的Advertiser后,会对Advertiser发起主动扫描请求,这个时候,我们可以给出一个响应来。如何进行这个响应数据的设置呢?可以通过如下的方式:

       //设置扫描响应数据,作为客户端扫描(连接前)的响应
        AdvertiseData scanResponse = new AdvertiseData.Builder()
                .addManufacturerData(2, new byte[]{4, 5, 6}) //设备厂商数据,自定义
                .addServiceUuid(new ParcelUuid(TEMPERATURE_SERVICE_UUID)) //服务UUID
                .build();
我们添加了一个自定义的service,其UUID为:
public static final UUID TEMPERATURE_SERVICE_UUID = UUID.fromString("10000000-0000-1000-8000-00805f9b34fb"); //service uuid
 

  
2.5发起广播

首先,先拿到一个BluetoothLeAdvertiser对象,然后,调用startAdvertiser()方法,将我们上面设置的广播设置、广播数据、扫描响应设置进来。

 mBluetoothLeAdvertiser = mBluetoothAdapter.getBluetoothLeAdvertiser();
        Log.d(TAG, "mBluetoothLeAdvertiser = " + mBluetoothLeAdvertiser);
        //发起广播
        mBluetoothLeAdvertiser.startAdvertising(settings, advertiseData, scanResponse, mAdvertiseCallback);
     startAdvertising()方法的第4个参数是个回调,通过该回调我们可以知道我们的广播的是否正常发起了。
    // BLE广播Callback
    private AdvertiseCallback mAdvertiseCallback = new AdvertiseCallback() {
        @Override
        public void onStartSuccess(AdvertiseSettings settingsInEffect) {
            Log.d(TAG, "BLE advertise start succeed");
        }

        @Override
        public void onStartFailure(int errorCode) {
            Log.d(TAG, "BLE advertise start failed, errorCode = " + errorCode);
        }
    };
 

3.Demo验证 

完成上一小节描述的步骤,我们其实已经完成demo的编码。怎么来验证我们的功能呢?我们可以分两个步骤来完成,

 (1)利用《Android蓝牙开发系列文章-扫不到蓝牙设备,你的姿势对了吗?》中的demo看是否可以正常扫描到我的Observer,广播数据是否可以正常搜索到。

(2)利用BLE蓝牙开发助手测试是否可以正常搜索到Advertiser,是否可以正常建立gatt连接。

3.1验证是否可以被搜索到

修改搜索支持"10000000-0000-1000-8000-00805f9b34fb"UUID的目标设备。

    private void startBleScan2() {
//      mBluetoothLeScanner.startScan(mScanCallback);

      //过滤条件
      List<ScanFilter> bleScanFilters = new ArrayList<>();
//      ScanFilter filter = new ScanFilter.Builder().setDeviceAddress("08:7C:BE:48:65:AD").setServiceUuid(ParcelUuid.fromString("0000fee7-0000-1000-8000-00805f9b34fb")).build();
      ScanFilter filter = new ScanFilter.Builder().setServiceUuid(ParcelUuid.fromString("10000000-0000-1000-8000-00805f9b34fb")).build();
      bleScanFilters.add(filter);
      //扫描设置
      ScanSettings scanSetting = new ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES).setMatchMode(ScanSettings.MATCH_MODE_STICKY).build();
      mBluetoothLeScanner.startScan(bleScanFilters, scanSetting, mScanCallback);
  }

先启动本文Peripheral Demo,log输出如下,可以看到在注册service成功后,会收到onServiceAdded()的回调。发起广播成功后,会收到onStartSuccess()的回调。

01-01 22:10:35.459 10050 10050 D MainActivity: mBluetoothAdapter = android.bluetooth.BluetoothAdapter@26658fe
01-01 22:10:35.461 10050 10050 D MainActivity: mBluetoothLeAdvertiser = android.bluetooth.le.BluetoothLeAdvertiser@725785f
01-01 22:10:35.490 10050 10081 D MainActivity: onServiceAdded, status = 0, service, uuid = 10000000-0000-1000-8000-00805f9b34fb
01-01 22:10:35.571 10050 10050 D MainActivity: BLE advertise start succeed

启动修改后的Observer Demo,log输出如下,

04-02 22:10:56.862: D/MainActivity(6274): onScanResult, result = ScanResult{mDevice=EC:9C:32:1D:F6:44, mScanRecord=ScanRecord [mAdvertiseFlags=-1, mServiceUuids=[10000000-0000-1000-8000-00805f9b34fb], mManufacturerSpecificData={1=[1, 2, 3]2=[1, 2, 3]}, mServiceData={}, mTxPowerLevel=1, mDeviceName=万物互联技术], mRssi=-71, mTimestampNanos=256412745559609}
04-02 22:10:57.927: D/MainActivity(6274): onScanResult, result = ScanResult{mDevice=EC:9C:32:1D:F6:44, mScanRecord=ScanRecord [mAdvertiseFlags=-1, mServiceUuids=[10000000-0000-1000-8000-00805f9b34fb], mManufacturerSpecificData={1=[1, 2, 3]2=[1, 2, 3]}, mServiceData={}, mTxPowerLevel=1, mDeviceName=万物互联技术], mRssi=-72, mTimestampNanos=256412810702942}
04-02 22:10:58.960: D/MainActivity(6274): onScanResult, result = ScanResult{mDevice=EC:9C:32:1D:F6:44, mScanRecord=ScanRecord [mAdvertiseFlags=-1, mServiceUuids=[10000000-0000-1000-8000-00805f9b34fb], mManufacturerSpecificData={1=[1, 2, 3]2=[1, 2, 3]}, mServiceData={}, mTxPowerLevel=1, mDeviceName=万物互联技术], mRssi=-71, mTimestampNanos=256412844336692}
04-02 22:10:59.996: D/MainActivity(6274): onScanResult, result = ScanResult{mDevice=EC:9C:32:1D:F6:44, mScanRecord=ScanRecord [mAdvertiseFlags=-1, mServiceUuids=[10000000-0000-1000-8000-00805f9b34fb], mManufacturerSpecificData={1=[1, 2, 3]2=[1, 2, 3]}, mServiceData={}, mTxPowerLevel=1, mDeviceName=万物互联技术], mRssi=-72, mTimestampNanos=256412879636067}

可以看到我们成功的搜索到了目标设备,且设置的UUID、ManufacturerSpecificData都搜索出来。但是mTxPowerLevel却是1,我们期望值是3,这一点,我还没搞懂,如果你知道原因,可以后台告诉我一下哈,十分感谢。

  3.2利用BLE蓝牙开发助手验证

  实验结果是可以正常搜索到,但是连接不成功,gatt连接肯定也建不了。

 

4.总结

本文讲解了BLE开发中常见的四种角色以及它们之间的区别,编码实现了Advertiser功能,即IBeacon功能。

持续关注本博客内容,请扫描关注个人微信公众号:万物互联技术~

发布了37 篇原创文章 · 获赞 22 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/Atlas12345/article/details/105280126