300字范文,内容丰富有趣,生活中的好帮手!
300字范文 > Android 低功耗蓝牙BLE连接通信

Android 低功耗蓝牙BLE连接通信

时间:2018-10-05 02:45:02

相关推荐

Android 低功耗蓝牙BLE连接通信

目录

简介

蓝牙 4.0

BLE与蓝牙4.0的区别

BLE的特点

主要特性

技术细节

BLE的应用

BLE的体系结构

BLE设备链路层状态

就绪态

广播态

扫描态

发起态

连接状态

通信基本过程

两种方式向外广播数据

广播参数

扫描事件

GATT结构

通用属性规范的常见基本操作

BLE开发中主要的类和其作用:

蓝牙权限

获取蓝牙适配器

扫描设备

获取设备

连接设备

发现所有首要服务

发现服务失败

发现服务的所有特征

发现所有特征描述符

读取特征值

写入特征值

监听外设特征值改变

断开连接

简介

蓝牙(Bluetooth®):是一种无线技术标准,可实现固定设备、移动设备和楼宇个人域网之间的短距离数据交换(使用2.4—2.485GHz的ISM波段的UHF无线电波)。蓝牙技术最初由电信巨头爱立信公司于1994年创制,当时是作为RS232数据线的替代方案。蓝牙可连接多个设备,克服了数据同步的难题。

蓝牙 4.0

蓝牙技术联盟于6月30日正式推出蓝牙核心规格4.0 。它包括经典蓝牙、高速蓝牙和蓝牙低功耗协议。

低功耗蓝牙,是蓝牙4.0版本的一个子集,简称BLE。它有着全新的协议栈,可快速建立简单的链接。它主要面向对功耗需求极低、用纽扣电池供电的应用。芯片设计可有两种:双模、单模。

单模情况下,只能执行低功耗的协议栈。意法半导体、笙科电子、CSR、北欧半导体和德州仪器已经发布了单模蓝牙低功耗解决方案。

双模情况下,Bluetooth Smart功能整合入既有的经典蓝牙控制器。截至3月,高通创锐讯、CSR、博通和德州仪器已宣布发表符合此标准的芯片。

BLE与蓝牙4.0的区别

蓝牙4.0实际是个三位一体的蓝牙技术,它将三种规格合而为一,分别是传统蓝牙、低功耗蓝牙和高速蓝牙技术,这三个规格可以组合或者单独使用,分为以下三类:

单模蓝牙只支持低功耗蓝牙设备。双模蓝牙即支持经典蓝牙设备,又支持低功耗蓝牙设备。经典蓝牙仅支持经典蓝牙的设备。

BLE的特点

主要特性

超低的峰值、平均和待机模式功耗;使用标准纽扣电池可运行一年乃至数年;低成本;不同厂商设备交互性;完全向下兼容;低延迟

技术细节

速度:支持1Mbps数据传输率下的超短数据包,最少8个八组位,最多27个跳频:使用所有蓝牙规范版本通用的自适应跳频,最大程度地减少和其他2.4GHz ISM频段无线技术的串扰。(调频制式GFSK)主控制:更加智能,可以休眠更长时间,只在需要执行动作的时候才唤醒。延迟:最短可在3毫秒内完成连接设置并开始传输数据。(传统50ms)范围:提高调制指数,最大范围可超过100米(根据不同应用领域, 距离不同)。健壮性:所有数据包都使用24-bitCRC校验,确保最大程度抵御干扰安全:使用AES-128 CCM加密算法进行数据包加密和认证。

BLE的应用

BLE的应用(未来五年将有数十亿的设备需求量)

2.4G蓝牙低功耗系统消费类电子产品移动电话外围扩展设备运动和休闲设备健康医疗用品(血压计、体温计……)汽车电子设备人机接口设备(鼠标、键盘、遥控器……)

BLE的体系结构

低功耗蓝牙(BLE)连接都是建立在 GATT (Generic Attribute Profile) 协议之上。GATT 是一个在蓝牙连接之上的发送和接收很短的数据段的通用规范,这些很短的数据段被称为属性(Attribute)。

详细介绍 GATT 之前,需要了解 GAP(Generic Access Profile),它在用来控制设备连接和广播。GAP 使你的设备被其他设备可见,并决定了你的设备是否可以或者怎样与其他设备进行交互。例如 计步器、手环、苹果手表就等设备就可以与中心设备连接。

GAP 给设备定义了若干角色,其中主要的两个是:外围设备(Peripheral)和中心设备(Central)。(主从设备)

外围设备(从机):这一般就是非常小或者简单的低功耗设备,用来提供数据,并连接到一个更加相对强大的中心设备。例如小米手环。

中心设备(主机):中心设备相对比较强大,用来连接其他外围设备。通常是手机、平板等。IOS7.0和Android 4.3以后支持。

BLE设备链路层状态

链路层定义设备处于状态机中五种状态的一种:

就绪态

上电后,链路层进入并保持就绪态,直到接到主机的命令。从就绪态可进入广播态、扫描态或发起态。从其他任意状态也可以进入就绪态。就绪态是链路层状态机的中心状态。

广播态

处于广播态的链路层可以发送广播报文,也可以发送扫描响应,用以回应主动扫描设备。可被发现或者可被连接的设备需要处于广播态。想向一定区域内其他设备广播数据的设备也需要处于广播态。

广播态的设备停止广播后可进入就绪态。在收到发起者的连接请求之后,广播态的设备也可以进入连接态。

扫描态

扫描态可监听那些设备正在广播。扫描态有两个状态:被动扫描和主动扫描。被动扫描进接收广播报文。主动扫描则发送扫描请求给广播态设备、并获取附加的扫描响应数据。扫描态的设备只能进入就绪态,转换条件就是停止扫描。

发起态

为了发起连接,链路层需要处于发起态,如下图所示。处于发起态的发起者,侦听自己想要连接的设备,如果收到了来自该设备的广播报文,链路层会向其发起连接请求并进入连接态,并假设广播者也进入了连接态。如果发起者不在试图发起连接,也可以进入就绪态。

连接状态

从广播态或发起态都可进入连接态,如下图所示。连接态有两种子状态:主或从,也可以说是两种身份。主设备只能从发起态进入连接态,而从设备只能从广播态进入连接态。从设备不断向主设备进行广播,主设备则发起连接,这样双方都进入了连接态。进入连接态后,主设备必须定期向从设备发送报文,从设备只能通过回复这些报文才能发送自己的报文。

通信基本过程

在主机设备(如:手机)跟从机设备(如:BLE 模块)建立连接之前,从机设备需要先进行广播。即从机设备会设定一个广播间隔,每个广播间隔中,它会重新发送自己的广播数据。广播间隔越长,越省电,同时也不太容易扫描到。

从机设备不断发送广播信号给主机设备,如果主机设备不开启扫描窗口,主机设备是收不到从机设备的广播的。当主机设备开启扫描发现从机设备的广播信号时就可以请求建立连接,连接成功后就可以发送数据报文。

两种方式向外广播数据

在 GAP 中外围设备通过两种方式向外广播数据: Advertising Data Payload(广播数据)和 Scan Response Data Payload(扫描回复),每种数据最长可以包含 31 byte。这里广播数据是必需的,因为外设必需不停的向外广播,让中心设备知道它的存在。扫描回复是可选的,中心设备可以向外设请求扫描回复,这里包含一些设备额外的信息,例如设备的名字、Server UUID、厂商定义数据等。

广播参数

设备每次广播时,会在3个广播信道上发送相同的报文。称为一个广播事件。

广播间隔,2次广播事件之间的时间。称为广播事件间隔。广播间隔的取值范围20ms~10.28s。

链路层会在每两次广播事件期间产生一个随机广播延时时间(0~10ms,避免数据碰撞)凡是在广播信道传输的都是广播报文。

扫描事件

每次扫描设备打开接收器去监听广播设备,称为一个扫描事件。

扫描事件交替地发生在三个特定的广播信道:37、38、39

扫描的两个参数:1、扫描间隔:扫描设备的扫描频率;2、扫描窗口:每次扫描事件的持续时间。

GATT结构

GATT 事务是建立在嵌套的Profiles, Services 和 Characteristics之上的的,如下图所示:

Profile:是蓝牙的一个很重要特性,Profile定义了设备如何实现一种连接或者应用,你可以把Profile理解为连接层或者应用层协议。是一种规范,一个标准的通信协议,它存在于从机中。一个ble蓝牙设备有包括多个Profile。

Service:包含一个或者多个 Characteristic。每个 Service 有一个 UUID 唯一标识(通过服务的UUID找到对应的Service)。

Characteristic: 是最小的逻辑数据单元。一个Characteristic包括一个value变量和多个用来描述characteristic变量的Descriptor。与 Service 类似,每个 Characteristic 用 16 bit 或者 128 bit 的 UUID 唯一标识(通过Descriptor的uuid找到对应的Descriptor)。

通用属性规范的常见基本操作

蓝牙的信息是通过特征值(characteristics)和服务(services)传输的。进行通信的时候,主要是通过 Characteristic,可以从 Characteristic 读取数据,也可以往 Characteristic 写数据,从而实现双向的通信。

BLE开发中主要的类和其作用:

BluetoothDeivce:蓝牙设备,代表一个具体的蓝牙外设。BluetoothGatt:通用属性协议,定义了BLE通讯的基本规则和操作。BluetoothGattCallback:GATT通信回调类,用于回调的各种状态和结果。BluetoothGattService:服务,由零或多个特征组构成。BluetoothGattCharacteristic:特征,里面包含了一组或多组数据,是GATT通信中的最小数据单元。BluetoothGattDescriptor:特征描述符,对特征的额外描述,包括但不仅限于特征的单位,属性等。

蓝牙权限

android.permission.BLUETOOTH: 允许程序连接到已配对的蓝牙设备, 请求连接/接收连接/传输数据需要改权限, 主要用于对配对后进行操作;

android.permission.BLUETOOTH_ADMIN:允许程序发现和配对蓝牙设备, 该权限用来管理蓝牙设备, 有了这个权限, 应用才能使用本机的蓝牙设备, 主要用于对配对前的操作;

优先级 : BLUETOOTH权限是BLUETOOTH_ADMIN权限的前提, 如果没有BLUETOOTH权限, 就不能使用BLUETOOTH_ADMIN权限;

<uses-permission android:name="android.permission.BLUETOOTH" /><uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /><!-- LE Beacons位置相关权限 --><!-- Android 6.0 需要以下获取到定位权限。否则会报运行时异常 --><uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /><uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

获取蓝牙适配器

BluetoothAdapter 代表了移动设备的本地的蓝牙适配器, 通过该蓝牙适配器可以对蓝牙进行基本操作。例如,startLeScan() 开始扫描;stopLeScan() 停止扫描等。

// 判断手机硬件是否支持蓝牙if (!context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {Log.i(TAG, "手机硬件不支持蓝牙");return;}// 获取手机本地的蓝牙适配器final BluetoothManager bluetoothManager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);mBluetoothAdapter = bluetoothManager.getAdapter();// 打开蓝牙权限if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {Log.i(TAG, "蓝牙未打开");return;}

扫描设备

在 Android 4.3和 Android 4.4进行蓝牙扫描,可使用BluetoothAdapter.startLeScan(BluetoothAdapter.LeScanCallback)进行蓝牙扫描。

//开始扫描mBluetoothAdapter.startLeScan(mLeScanCallback);//停止扫描mBluetoothAdapter.stopLeScan(mLeScanCallback);//扫描回调private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {/*** 扫描回调* @param device 扫描到的设备实例,可从实例中获取到相应的信息。* @param rssi 可理解成设备的信号值。该数值是一个负数,越大则信号越强。如果没有可用的RSSI值是0。* @param scanRecord 远程设备提供的广播数据的内容。*/@Overridepublic void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {// 通过device信息获取BLE MAC地址用于设备进行连接}};

在 Android 5.0 之后的版本(包括 5.0)建议使用新的Api进行蓝牙扫描

BluetoothLeScanner.startScan(ScanCallback)

BluetoothLeScanner.startScan(List<ScanFilter>, ScanSettings, ScanCallback)。

//获取 5.0 的扫描类实例mBLEScanner = mBluetoothAdapter.getBluetoothLeScanner();//开始扫描//可设置过滤条件,在第一个参数传入,但一般不设置过滤。mBLEScanner.startScan(null, mScanSettings, mScanCallback);//停止扫描mBLEScanner.stopScan(mScanCallback);//扫描回调private ScanCallback mScanCallback = new ScanCallback() {/*** 当一个蓝牙ble广播被发现时回调* @param callbackType 扫描类型有开始扫描时传入的ScanSettings相关* @param result 对扫描到的设备进行操作。如:获取设备信息。*/@Overridepublic void onScanResult(int callbackType, ScanResult result) {super.onScanResult(callbackType, result);}/*** 批量返回扫描结果* @param results 以前扫描到的扫描结果列表。*/@Overridepublic void onBatchScanResults(List<ScanResult> results) {super.onBatchScanResults(results);}/*** 当扫描不能开启时回调* @param errorCode 扫描太频繁会返回ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED,* 表示app无法注册,无法开始扫描。*/@Overridepublic void onScanFailed(int errorCode) {super.onScanFailed(errorCode);}};

获取设备

根据扫描到的 BLE 设备 MAC 地址获取到BluetoothDeivce用于连接

BluetoothManager bluetoothManager = (BluetoothManager)context.getSystemService(Context.BLUETOOTH_SERVICE);mBluetoothAdapter = bluetoothManager.getAdapter();//获取蓝牙设备对象进行连接mBluetoothDevice = mBluetoothAdapter.getRemoteDevice(macAddressStr)

连接设备

// 调用device中的connectGatt连接到远程设备mBluetoothGatt = mBluetoothDevice.connectGatt(mContext, false, mGattCallback);// 连接远程设备的回调函数private final static BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {/*** 连接状态改变*/@Overridepublic void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {if (newState == BluetoothProfile.STATE_CONNECTED) { //连接成功} else if (newState == BluetoothProfile.STATE_DISCONNECTED) { //连接失败} else if (newState == 133) { // 133错误,需要close// 错误代码:// 8 : 设备超出范围// 22 :表示本地设备终止了连接// 133 :连接超时或未找到设备。gatt.close();}}/*** 重写onServicesDiscovered,发现蓝牙服务*/@Overridepublic void onServicesDiscovered(BluetoothGatt gatt, int status) {//发现到服务if (status == BluetoothGatt.GATT_SUCCESS) {}}/*** 特征值的读写*/@Overridepublic void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {// TODO Auto-generated method stub}/*** 特征值的改变*/@Overridepublic void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {// TODO Auto-generated method stub}/*** 特征值的写*/@Overridepublic void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {// TODO Auto-generated method stub}/*** 读描述值*/@Overridepublic void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {// TODO Auto-generated method stub}/*** 写描述值*/@Overridepublic void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {// TODO Auto-generated method stub}/*** 读写蓝牙信号值*/@Overridepublic void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {// TODO Auto-generated method stub}/*** 调用一个可靠写事务已经完成。*/@Overridepublic void onReliableWriteCompleted(BluetoothGatt gatt, int status) {// TODO Auto-generated method stub}};

发现所有首要服务

当发现服务成功后,会触发BluetoothGattCallback#onServicesDiscovered()回调

//定义需要进行通信的ServiceUUIDprivate UUID mServiceUUID = UUID.fromString("0000xxxx-0000-1000-8000-00805f9b34fb");//定义蓝牙Gatt回调private final static BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {//服务发现回调@Overridepublic void onServicesDiscovered(BluetoothGatt gatt, int status) {super.onServicesDiscovered(gatt, status);if (status == BluetoothGatt.GATT_SUCCESS) {mHandler.post(() ->//获取指定uuid的serviceBluetoothGattService gattService = mBluetoothGatt.getService(mServiceUUID);//获取发现的所有服务//List<BluetoothGattService> gattServices = mBluetoothGatt.getServices();//获取到特定的服务不为空if(gattService != null){}else{//获取特定服务失败});}}}

发现服务失败

​发现服务时,会存在发现不了特定服务的情况。或者说,整个BluetoothGatt对象中的服务列表为空。

BluetoothGatt类中存在一个隐藏的方法refresh(),用于刷新Gatt的服务列表。当发现不了服务时,可以通过反射去调用该方法,再发现一遍服务。

发现服务的所有特征

//蓝牙4.0的UUID,其中0000ffe1-0000-1000-8000-00805f9b34fb是广州汇承信息科技有限公司08蓝牙模块的UUIDpublic static String HEART_RATE_MEASUREMENT = "0000fff6-0000-1000-8000-00805f9b34fb";//定义蓝牙Gatt回调类private final static BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {//服务发现回调@Overridepublic void onServicesDiscovered(BluetoothGatt gatt, int status) {super.onServicesDiscovered(gatt, status);if (status == BluetoothGatt.GATT_SUCCESS) { //发现到服务mHandler.post(() ->//获取发现的所有服务List<BluetoothGattService> gattServices = mBluetoothGatt.getServices();//从当前循环所指向的服务中读取特征值列表List<BluetoothGattCharacteristic> gattCharacteristics = gattServices.getCharacteristics();for (final BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) {if (gattCharacteristic.getUuid().toString().equals(HEART_RATE_MEASUREMENT)) {Log.i(TAG, "FOUND UUID:" + gattCharacteristic.getUuid().toString());// 接受Characteristic被写的通知,收到蓝牙模块的数据后会触发onCharacteristicWrite()setCharacteristicNotification(gattCharacteristic, true);}});}}}

发现所有特征描述符

List<BluetoothGattDescriptor> descriptors = gattCharacteristic.getDescriptors();for (BluetoothGattDescriptor descriptor : descriptors) {Log.i(TAG, "---descriptor UUID:" + descriptor.getUuid());// 获取特征值的描述if (mBluetoothLeService != null) {mBluetoothLeService.getCharacteristicDescriptor(descriptor);}}

读取特征值

当成功读取特征值时,会触发BluetoothGattCallback#onCharacteristicRead()回调。

//读取特征值,传入要读取的特征值mBluetoothGatt.readCharacteristic(characteristic);//当成功读取特征值时,回调该函数@Overridepublic void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {super.onCharacteristicRead(gatt, characteristic, status);if (status == BluetoothGatt.GATT_SUCCESS) {//获取读取到的特征值byte[] sucString = characteristic.getValue();String string = new String(sucString);}}

写入特征值

当成功写入特征值到外设时,会触发BluetoothGattCallback#onCharacteristicWrite()回调。

//给特征值设置传输的字数数组characteristic.setValue(buff);//写入特征值mBluetoothGatt.writeCharacteristic(characteristic);//当成功写入特征值,回调该函数@Overridepublic void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {super.onCharacteristicWrite(gatt, characteristic, status);if (status == BluetoothGatt.GATT_SUCCESS) {//获取写入到外设的特征值characteristic.getValue()}}

监听外设特征值改变

无论是对外设写入新值,还是读取外设特定Characteristic的值,其实都只是单方通信。如果需要双向通信,可以在BluetoothGattCallback#onServicesDiscovered中对某个特征值设置监听(前提是该Characteristic具有NOTIFY属性)

public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic, boolean enabled) {if (mBluetoothAdapter == null || mBluetoothGatt == null) {Log.w(TAG, "BluetoothAdapter not initialized");return;}mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);//开启特征的描述符,一般使用的是00002902-0000-1000-8000-00805f9b34fb//不要在设置接收通知时开启readCharacteristic。会设置描述符失败的。一直返回false,你永远收不到通知BluetoothGattDescriptor clientConfig = characteristic.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));if (enabled) {clientConfigc.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);} else {clientConfig.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);}mBluetoothGatt.writeDescriptor(clientConfig);}

当写入完特征值后,外设修改自己的特征值进行回复时,手机端会触发BluetoothGattCallback#onCharacteristicChanged()方法,获取到外设回复的值,从而实现双向通信。

@Overridepublic void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {if (status == BluetoothGatt.GATT_SUCCESS) {//获取外设修改的特征值String value = characteristic.getValue()}}

读取特征的描述符

当成功读取特征的描述符时,会触发BluetoothGattCallback#onDescriptorRead()回调。

//得到特征值下的描述值public void getCharacteristicDescriptor(BluetoothGattDescriptor descriptor) {if (mBluetoothAdapter == null || mBluetoothGatt == null) {Log.w(TAG, "BluetoothAdapter not initialized");return;}mBluetoothGatt.readDescriptor(descriptor);}//读描述值回调@Overridepublic void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {// TODO Auto-generated method stubsuper.onDescriptorRead(gatt, descriptor, status);Log.w(TAG, "----onDescriptorRead status: " + status);byte[] desc = descriptor.getValue();if (desc != null) {Log.w(TAG, "----onDescriptorRead value: " + new String(desc));}}

断开连接

断开连接的操作分为两步:

mBluetoothGatt.disconnect();mBluetoothGatt.close();

调用disconnect()后,会触发手机会触发BluetoothGattCallback#onConnectionStateChange()的回调,回调断开连接信息,newState = BluetoothProfile.STATE_DISCONNECTED。但调用完disconnect()紧接着马上调用close(),会终止BluetoothGattCallback#onConnectionStateChange()的回调。可以看情况将两个进行拆分调用,来实现断开连接,但必须两个方法都调用。

//关闭所有蓝牙连接public void close() {if (mBluetoothGatt == null) {return;}int state = mBluetoothManager.getConnectionState(mBluetoothGatt.getDevice(), BluetoothProfile.GATT);Log.i(TAG, "address:" + mBluetoothGatt.getDevice().getAddress() + ",state:" + state);if (state == BluetoothProfile.STATE_CONNECTED) {mBluetoothGatt.disconnect();try {Thread.sleep(20);} catch (InterruptedException e) {e.printStackTrace();}}mBluetoothGatt.close();mBluetoothGatt = null;}

//连接状态改变回调@Overridepublic void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {if (newState == BluetoothProfile.STATE_CONNECTED) { //连接成功} else if (newState == BluetoothProfile.STATE_DISCONNECTED) { //连接失败或断开连接// 关闭gattgatt.close();} else if (newState == 133) {// 错误代码:// 8 : 设备超出范围// 22 :表示本地设备终止了连接// 133 :连接超时或未找到设备。gatt.close();}}

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。