安卓BLE知识及EasyBle的使用

一、BLE知识

1.关于BLE

BLE(Bluetooth low energy)蓝牙低功耗是蓝牙4.0新增的子规范,其具有低能耗、低成本、低延迟、传输距离长等特点,自安卓4.3(api level 18)起,安卓开始支持BLE。BLE与经典蓝牙对比如下图(图片来自网络):


BLE和经典蓝牙对比.png

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如下图(图片来自网络)

ConnectionEvent.jpg

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层级图如下(图片来自蓝牙官网)

GATT Profile Hierarchy.png

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, List<CharacteristicInfo>>
ServiceInfo: 服务信息如uuid.
CharacteristicInfo: 特征信息如uuidproperty(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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容

  • 相关概念 BRBasic Rate,早期的传统蓝牙技术 V1.1, V1.2 版本,传输速率为748~810kb/...
    七零八落问号阅读 9,293评论 2 26
  • 因为自己的项目中有用到了蓝牙相关的功能,所以之前也断断续续地针对蓝牙通信尤其是BLE通信进行了一番探索,整理出了一...
    陈利健阅读 113,500评论 172 294
  • 蓝牙 蓝牙的波段为2400-2483.5MHz(包括防护频带)。这是全球范围内无需取得执照(但定不是无管制的)的工...
    苏永茂阅读 6,103评论 0 11
  • Key Terms And Concepts 关键术语和概念 Here is a summary of key B...
    Jaesoon阅读 2,426评论 0 5
  • 背景 蓝牙历史说到蓝牙,就不得不说下蓝牙技术联盟(Bluetooth SIG),它负责蓝牙规范制定和推广的国际组织...
    徐正峰阅读 12,224评论 6 33