本文是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中有四种角色:
好了,本文可以正式开始了。
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领域得到广泛应用。
AndroidManifest.xml中添加:
动态申请位置权限:
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);
}
}
//设置广播
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功能,所以不用设置可连接。下一小节中,我们会用微信小程序测试一下,我们这样设备后的设备是否可以连接成功,预期是不能的。
//设置广播内容
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;
}
在扫描端搜索到我们的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
首先,先拿到一个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);
}
};
完成上一小节描述的步骤,我们其实已经完成demo的编码。怎么来验证我们的功能呢?我们可以分两个步骤来完成,
(1)利用《Android蓝牙开发系列文章-扫不到蓝牙设备,你的姿势对了吗?》中的demo看是否可以正常扫描到我的Observer,广播数据是否可以正常搜索到。
(2)利用BLE蓝牙开发助手测试是否可以正常搜索到Advertiser,是否可以正常建立gatt连接。
修改搜索支持"10000000-0000-1000-8000-00805f9b34fb"UUID的目标设备。
private void startBleScan2() {
// mBluetoothLeScanner.startScan(mScanCallback);
//过滤条件
List 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,这一点,我还没搞懂,如果你知道原因,可以后台告诉我一下哈,十分感谢。
实验结果是可以正常搜索到,但是连接不成功,gatt连接肯定也建不了。
本文讲解了BLE开发中常见的四种角色以及它们之间的区别,编码实现了Advertiser功能,即IBeacon功能。
持续关注本博客内容,请扫描关注个人微信公众号:万物互联技术~