一、BLE知识
1.关于BLE
BLE(Bluetooth low energy)蓝牙低功耗是蓝牙4.0新增的子规范,其具有低能耗、低成本、低延迟、传输距离长等特点,自安卓4.3(api level 18)起,安卓开始支持BLE。BLE与经典蓝牙对比如下图(图片来自网络):
2.BLE设备间交互方式
- 完全基于广播
设备间无需连接而只需外设广播数据即可。该方式主要是让外设把自己的信息发送给多个中心设备。使用该方式的应用叫作Beacon。该方式下有Broadcaster(广播者)和Observer(Scanner扫描者)两种角色。使用该方式最典型的应用就是苹果的 iBeacon,可实现广告推送和室内定位 - 基于GATT连接
设备连接后通过GATT(通用配置文件,下文有详细介绍)来进行通信
GATT 连接是独占的。即一个 外设同时只能被一个中心设备连接。一旦外设被连接,它就会马上停止广播,这样它就对其他设备不可见了。当连接断开,它又开始广播
3.BLE中角色与职责
- Central vs. Peripheral:中心设备-外围设备,中心设备和外围设备的概念针对的是BLE连接本身,中心设备负责扫描广播,而外围设备负责发出广播。
注意:
1.只有一方支持Central另一方支持Peripheral才能通信
2.Android 4.3开始支持BLE,但只支持作为中心设备(Central)模式,
Android 5.0开始两种模式都支持
- GATT server vs. GATT client:这两种角色取决于BLE连接成功后,两个设备间通信的方式。若A需从B请求数据,则A是gatt client,B是gatt server;反之A是gatt server,B是gatt client。通俗来讲就是产生数据的是gatt server,访问数据的设备是gatt client,一个设备既可以是gatt server也可以是gatt client
4.关于BLE广播
- 广播间隔(Advertising interval)
外围设备会设定一个特定的广播间隔Advertising interval来发送广播数据,每当经过广播间隔时,外设将重新发送广播数据包。广播间隔越长越省电,但也不太容易扫描到。 - 广播数据
外围设备向外广播数据包:Advertising Data Payload(广播数据) + Scan Response Data Payload(扫描回复),广播数据和扫描回复的数据长度分别可达 31 byte。Advertising Data Payload是强制性的,因为外围设备必需不停向外广播而让其他中心设备感知其存在,而Scan Response Data Payload(扫描回复)是可选的,中心设备可向外围设备请求扫描回复(设备设计者可将更多设备信息如设备名字符串等放置于此)。因此,在含扫描回复的情况下,广播数据包最大可为62byte。自Android 5.0起,系统提供了ScanRecord类来帮助解析设备接收到的BLE广播数据包,可直接通过该类获取有意义的数据如设备连接属性(标识设备支持 的 BLE 模式)、设备名、设备包含的关键 GATT service或 Service data、厂商自定义 数据等。
5.BLE的链接参数
在讲链接参数之前,需要先了解连接事件Connection Events
1)Conection Events(连接事件)
蓝牙设备建立连接后,所有信息的交换都是通过Connetion Events进行的,主设备在Connection Events开始起发送数据包,从属设备在Connection Events期间回复。Connection Events是周期性出现的且时间间隔很短,在一个Connection Events中,器件最大电流为十几mA,平均电流1uA,这便是BLE功耗较低的原因。Connection Events如下图(图片来自网络)
2)链接参数
在两个蓝牙设备建立连接进入连接状态前,蓝牙设备需要设置一系列的链接参数,链接参数是在中心设备向外围设备发起连接请求时传递的:
Connection Interval
两次connection event的时间间隔称Conection Interval,Connection Inetrval为1.25ms的倍数,数值位于6(7.5ms)至3200(4.0s)之间。较长的Connection Interval更节省功耗,但数据相对无法做到及时传输,而较短的Connection Interval 可让数据传输更及时,但由于设备频繁连接,设备功耗较大。由于蓝牙采用了快跳频技术,设备每隔一段时间后都会在一个新频道上进行数据传输,每次数据传输都称为 一次connection event,即使无数据传输,设备也会connection event上交换LL层数据以保持两设备的连接状态Slave Latency
Slave Latency代表外围设备最多可以忽略的connection events数目,数值可以位于0~499之间,但最大值必须保证 effective connectioninterval不得大于16.0s,为外围设备设置该参数可使外围设备在无数据传输时灵活地跳过connection events保持睡眠状态以节省更多的功耗。有效连接间隔计算公式如下:
Effective Connectioninterval = ConnectionInterval * ( 1 + SlaveLatency)
Supervision Timeout
Supervision Timeout表示两次成功完成connection events的最大时间间隔。若时间超过Supervision Timeout时两设备仍无成功完成的connection event,设备将认为链接丢失并回归未连接状态。Supervision Timeout为10ms的倍数,其值在10(100ms)至3200(32.0s);Supervision Timeout 必须大于effective connectioninterval
当外围设备认为中心设备请求的链接参数不合适时,外围设备可在连接期间发送一个Connetion Parameter UpdateRequest 请求中心设备更改链接参数。当中心设备收到请求时可接受或拒绝外围设备的链接参数更新请求
在Android中未提供修改链接参数具体值的api,只提供了一个修改连接优先级的方法BluetoothGatt#requestConnectionPriority(int connectionPriority),该方法参数为以下三者之一CONNECTION_PRIORITY_BALANCED (默认值)、CONNECTION_PRIORITY_HIGH、CONNECTION_PRIORITY_LOW_POWER,该方法不同的参数对应有不同的链接参数,具体使用参见方法说明
6.GATT及相关概念
- GATT: Generic Attribute Profile(通用属性配置文件),GATT是一种用于收发短片段数据(属性)的通用规范,其被用于在BLE连接后数据的收发,当前所有的BLE应用配置文件都是基于GATT
对于BLE,蓝牙SIG组织(Special Interest Group)已定义并采纳了很多基于GATT的配置文件。配置文件(Profile)是一种规定设备在一个特定应用中如何工作的规范,当然,一个设备可实现多种配置文件,比如一台设备可包含心率监测与电量监控等
- ATT: Attribute Profile,属性协议,GATT就建立在ATT之上。通常二者合称为GATT/ATT。ATT使用尽可能少的字节且每一个属性都被UUID唯一标志
- Chracteristic:特征,可理解成一种类型,BLE设备间通信主要就使用它。Chracteristic包含一个值(Value)、0-n个描述符(Descriptor)、一系列属性(Property,表明该特征所支持的操作)、一些列与安全相关的Permission等。
- Descriptor:描述符,描述特征值(Chracteristic中的value)的定义属性。比如特征值的范围、单位等
- Service:服务,包含一系列Chracteristic。比如心率服务,包含心跳检测特征一个profile可包含n个服务
GATT层级图如下(图片来自蓝牙官网)
7.关于BLE的MTU
MTU(Maximum Transmission Unit): 即数据的最大传输单元。具体是指一个Chracteristic一次性可传输的数据大小。
蓝牙核心规范(core spec)中定义了ATT的默认MTU为23byte,除去ATT的opcode一个字节以及ATT的handle2个字节之后,剩下的20个字节便是留给GATT的了。由于ATT的最大长度为512byte,故一般认为MTU的最大长度为512个byte。
注:core spec规定每一个设备都必须支持MTU为23。
二、安卓BLE通信开发
用系统api开发参见安卓BLE开发官方文档
这里介绍使用第三方库EasyBle快速开发BLE
1.Gradle依赖
在项目根gradle中添加
allprojects {
repositories {
maven { url 'https://jitpack.io' }
}
}
在具体module的gradle中添加
dependencies {
implementation 'com.github.Ficat:EasyBle:v2.0.1'
}
2.使用
1)判断设备是否支持BLE并打开蓝牙
//是否支持BLE
BleManager.supportBle(context);
//蓝牙是否打开
BleManager.isBluetoothOn();
//若蓝牙未打开则首先使用该方法请求用户打开蓝牙,需在传入的activity的onActivityResult中处理请求结果
BleManager.enableBluetooth(activity,requestCode);
2)获取BleManager并初始化
//scan/connection不是必须的,若不设置,那么扫描或连接就
//会使用默认参数
BleManager.ScanOptions scanOptions = BleManager.ScanOptions
.newInstance()
.scanPeriod(10000)
.scanDeviceName(null);
BleManager.ConnectOptions connectOptions = BleManager.ConnectOptions
.newInstance()
.connectTimeout(12000);
BleManager manager = BleManager
.getInstance()
.setScanOptions(scanOptions)//非必须设置项
.setConnectionOptions(connectOptions)
.setLog(true, "TAG")
.init(this.getApplication());//这里需要Context,但注意不要传Activity
3)扫描
安卓版本不小于6.0的,扫描必须要有定位权限,若版本为Android10及以上,则需精确定位权限(即Manifest.permission.ACCESS_FINE_LOCATION)
bleManager.startScan(new BleScanCallback() {
@Override
public void onLeScan(BleDevice device, int rssi, byte[] scanRecord) {
String name = device.name;
String address = device.address;
}
@Override
public void onStart(boolean startScanSuccess, String info) {
if (startScanSuccess) {
//开始扫描成功
} else {
//未能成功开始扫描,可通过info查看详情
String failReason = info;
}
}
@Override
public void onFinish() {
}
});
//使用指定扫描参数扫描
bleManager.startScan(scanOptions, bleScanCallback);
当需要结束扫描时用以下方法结束扫描,建议在扫描到目标设备后停止扫描
bleManager.stopScan();
4)连接
BleConnectCallback bleConnectCallback = new BleConnectCallback() {
@Override
public void onStart(boolean startConnectSuccess, String info, BleDevice device) {
if (startConnectSuccess) {
//开始连接
} else {
//未能成功开始连接,可通过info查看详情
String failReason = info;
}
}
@Override
public void onFailure(int failCode, String info, BleDevice device) {
if(failCode == BleConnectCallback.FAIL_CONNECT_TIMEOUT){
//连接超时
}else{
//其他原因导致的连接失败
}
}
@Override
public void onConnected(BleDevice device) {
}
@Override
public void onDisconnected(String info, int status, BleDevice device) {
}
};
bleManager.connect(bleDevice, bleConnectCallback);
//使用指定连接选项参数进行连接
bleManager.connect(bleDevice, connectOptions, bleConnectCallback);
//使用mac地址连接
bleManager.connect(address, bleConnectCallback);
bleManager.connect(address, connectOptions, bleConnectCallback);
当需要断开与设备的连接时可使用以下任一方法断开设备连接
//断开与指定设备的连接
bleManager.disconnect(bleDevice);
//传入目标的mac地址断开与该设备的连接
bleManager.disconnect(address);
//断开所有已连接设备
bleManager.disconnectAll();
5)设置通知(notify或indicate)
notify和indicate都使用以下方法
bleManager.notify(bleDevice, serviceUuid, notifyUuid, new BleNotifyCallback() {
@Override
public void onCharacteristicChanged(byte[] data, BleDevice device) {
}
@Override
public void onNotifySuccess(String notifySuccessUuid, BleDevice device) {
}
@Override
public void onFailure(int failCode, String info, BleDevice device) {
switch (failCode) {
case BleCallback.FAIL_DISCONNECTED://连接断开
break;
case BleCallback.FAIL_OTHER://其他原因
break;
default:
break;
}
}
});
当需要取消notify或indicate时调用以下方法
bleManager.cancelNotify(bleDevice, notifyUuid);
6)写入特征数据
bleManager.write(bleDevice, serviceUuid, writeUuid, data, new BleWriteCallback() {
@Override
public void onWriteSuccess(byte[] data, BleDevice device) {
}
@Override
public void onFailure(int failCode, String info, BleDevice device) {
}
});
如果一次性写入的数据长度大于MTU即最大传输单元(默认是20字节),则可以使用下列方法进行分批写入
bleManager.writeByBatch(bleDevice, serviceUuid, writeUuid, data, lengthPerPackage, new BleWriteByBatchCallback() {
@Override
public void writeByBatchSuccess(byte[] data, BleDevice device) {
}
@Override
public void onFailure(int failCode, String info, BleDevice device) {
}
});
7)Destroy
当结束BLE通信时不要忘了调用destroy
bleManager.destroy();
其他api
Method | Description | |
---|---|---|
read(BleDevice bleDevice, String serviceUuid, String readUuid, BleReadCallback bleReadCallback) | 读取characteristic数据 | |
readRssi(BleDevice device, BleRssiCallback callback) | 读取设备信号强度 | |
setMtu(BleDevice device, int mtu, BleMtuCallback callback) | 设置MTU (Maximum Transmission Unit,即最大传输单元) | |
isAddressValid(String address) | 是否为合法的mac地址 | |
isScanning() | 是否正在扫描 | |
isConnected(String address) | 是否已连接到指定mac的设备 | |
isConnecting(String address) | 是否正在与指定设备进行连接 | |
getConnectedDevices() | 获取已连接设备列表 | |
getDeviceServices(BleDevice device); getDeviceServices(String address) |
获取已连接设备所支持的服务/特征信息,注意若未连接则返回null,该方法得到一个Map ServiceInfo: 服务信息如uuid. CharacteristicInfo: 特征信息如uuid、property(readable,writable,notify,indicative)等. |
|
supportBle(Context context) | 设备是否支持BLE | |
isBluetoothOn() | 蓝牙是否已打开 | |
enableBluetooth(Activity activity, int requestCode) | 打开蓝牙,该方法会显示一个dialog请求用户打开,因此打开与否需从Activity#onActivityResult()获取结果 | |
toggleBluetooth(boolean enable) | 打开或关闭蓝牙,有些设备仍会显示请求dialog,但与enableBluetooth()不一样的是该方法调用后不会立刻获取到打开/关闭的结果 | |
getScanOptions() | 获取默认或您已设置过的扫描配置信息 | |
getConnectOptions() | 获取默认或您已设置过的连接配置信息 | |
getBluetoothGatt(String address) | 获取指定设备的BluetoothGatt,注意若尚未与指定设备建立连接,则返回null |
本文参考:
1.https://www.bluetooth.com/specifications/gatt
2.https://blog.csdn.net/tracy_luffy/article/details/4166263