背景

在学习BLE的过程中,积累了一些心得的DEMO,放到Github,形成本文。感兴趣的同学可以下载到源代码。

github: https://github.com/vir56k/bluetoothDemo

什么是BLE(低功耗蓝牙)

BLE(Bluetooth Low Energy,低功耗蓝牙)是对传统蓝牙BR/EDR技术的补充。

尽管BLE和传统蓝牙都称之为蓝牙标准,且共享射频,但是,BLE是一个完全不一样的技术。

BLE不具备和传统蓝牙BR/EDR的兼容性。它是专为小数据率、离散传输的应用而设计的。

通信距离上也有改变,传统蓝牙的传输距离几十米到几百米不等,BLE则规定为100米。

低功耗蓝牙特点

*功耗低

*连接更快,无需配对

*异步通讯

常见两种蓝牙模式

*普通蓝牙连接(2.0)

*BLE(蓝牙4.0)

关键术语和概念

*Generic Attribute Profile(GATT)—GATT配置文件是一个通用规范,用于在BLE链路上发送和接收被称为“属性”的数据块。目前所有的BLE应用都基于GATT。 蓝牙SIG规定了许多低功耗设备的配置文件。配置文件是设备如何在特定的应用程序中工作的规格说明。注意一个设备可以实现多个配置文件。例如,一个设备可能包括心率监测仪和电量检测。

*Attribute Protocol(ATT)—GATT在ATT协议基础上建立,也被称为GATT/ATT。ATT对在BLE设备上运行进行了优化,为此,它使用了尽可能少的字节。每个属性通过一个唯一的的统一标识符(UUID)来标识,每个String类型UUID使用128 bit标准格式。属性通过ATT被格式化为characteristics和services。

*Characteristic 一个characteristic包括一个单一变量和0-n个用来描述characteristic变量的descriptor,characteristic可以被认为是一个类型,类似于类。

*Descriptor Descriptor用来描述characteristic变量的属性。例如,一个descriptor可以规定一个可读的描述,或者一个characteristic变量可接受的范围,或者一个characteristic变量特定的测量单位。

*Service service是characteristic的集合。例如,你可能有一个叫“Heart Rate Monitor(心率监测仪)”的service,它包括了很多characteristics,如“heart rate measurement(心率测量)”等。你可以在bluetooth.org 找到一个目前支持的基于GATT的配置文件和服务列表。

角色和责任

以下是Android设备与BLE设备交互时的角色和责任:

*中央 VS 外围设备。 适用于BLE连接本身。中央设备扫描,寻找广播;外围设备发出广播。

*GATT 服务端 VS GATT 客户端。决定了两个设备在建立连接后如何互相交流。

为了方便理解,想象你有一个Android手机和一个用于活动跟踪BLE设备,手机支持中央角色,活动跟踪器支持外围(为了建立BLE连接你需要注意两件事,只支持外围设备的两方或者只支持中央设备的两方不能互相通信)。

当手机和运动追踪器建立连接后,他们开始向另一方传输GATT数据。哪一方作为服务器取决于他们传输数据的种类。例如,如果运动追踪器想向手机报告传感器数据,运动追踪器是服务端。如果运动追踪器更新来自手机的数据,手机会作为服务端。

在这份文档的例子中,android app(运行在android设备上)作为GATT客户端。app从gatt服务端获得数据,gatt服务端即支持Heart Rate Profile(心率配置)的BLE心率监测仪。但是你可以自己设计android app去扮演GATT服务端角色

设备对BLE的支持

分为两种情况

* 目标设备是否支持BLE

* Android手机是否支持BLE

目标设备是否支持要看具体目标设备的情况,请参考硬件提供商和说明书。

一般情况下Android4.3以后的手机具有蓝牙模块的话都会支持BLE,具体可以再代码中判断。

为了在app中使用蓝牙功能,必须声明蓝牙权限BLUETOOTH。利用这个权限去执行蓝牙通信,例如请求连接、接受连接、和传输数据。

如果想让你的app启动设备发现或操纵蓝牙设置,必须声明BLUETOOTH_ADMIN权限。注意:如果你使用BLUETOOTH_ADMIN权限,你也必须声明BLUETOOTH权限。

在你的app manifest文件中声明蓝牙权限。

  1. <uses-permission android:name="android.permission.BLUETOOTH"/>
  2. <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>

如果想声明你的app只为具有BLE的设备提供,在manifest文件中包括:

  1. <uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>

代码中判断手机是否支持BLE特性:

  1. // 使用此检查确定BLE是否支持在设备上,然后你可以有选择性禁用BLE相关的功能
  2. if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
  3. Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();
  4. finish();
  5. }

在Android中使用BLE

1.获取 BluetoothAdapter

所有的蓝牙活动都需要蓝牙适配器。BluetoothAdapter代表设备本身的蓝牙适配器(蓝牙无线)。整个系统只有一个蓝牙适配器,而且你的app使用它与系统交互。

  1. //使用getSystemService()返回BluetoothManager,然后将其用于获取适配器的一个实例。
  2. // 初始化蓝牙适配器
  3. final BluetoothManager bluetoothManager =
  4. (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
  5. mBluetoothAdapter = bluetoothManager.getAdapter();

2.开启蓝牙

调用isEnabled())去检测蓝牙当前是否开启。如果该方法返回false,蓝牙被禁用。下面的代码检查蓝牙是否开启,如果没有开启,将显示错误提示用户去设置开启蓝牙

  1. // 确保蓝牙在设备上可以开启
  2. if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
  3. Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
  4. startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
  5. }

3.搜索蓝牙设备

为了发现BLE设备,使用startLeScan())方法。这个方法需要一个参数BluetoothAdapter.LeScanCallback。你必须实现它的回调函数,那就是返回的扫描结果。因为扫描非常消耗电量,你应当遵守以下准则:

*只要找到所需的设备,停止扫描。

*不要在循环里扫描,并且对扫描设置时间限制。以前可用的设备可能已经移出范围,继续扫描消耗电池电量。

  1. public void cancelDiscovery() {
  2. if(isDiscovering) {
  3. isDiscovering = false;
  4. mBluetoothAdapter.stopLeScan(mLeScanCallback);
  5. }
  6. }
  7. public boolean isDiscovering() {
  8. return isDiscovering;
  9. }
  10. public void startDiscovery() {
  11. mBluetoothAdapter.startLeScan(mLeScanCallback);
  12. isDiscovering = true;
  13. mHandler.postDelayed(new Runnable() {
  14. @Override
  15. public void run() {
  16. cancelDiscovery();
  17. if (getCallback() != null)
  18. getCallback().onDiscoveryComplete();
  19. }
  20. }, SCAN_PERIOD);
  21. }
  22. private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
  23. @Override
  24. public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
  25. if (getCallback() != null)
  26. getCallback().onDeviceFound(device);
  27. }
  28. };

GATT连接

搜索结束后,我们可得到一个搜索结果 BluetoothDevice ,它表示搜到的蓝牙设备

1.调用

  1. mBluetoothGatt = device.connectGatt(this, false, mGattCallback);

可以建立一个GATT连接,它需要一个 回调mGattCallback 参数。

2.在回调方法的 onConnectionStateChange 中,我们可以通过 status 判断是否GATT连接成功

3.在GATT连接建立成功后,我们调用 mBluetoothGatt.discoverServices() 方法 发现GATT服务。

如果搜到服务将会触发onServicesDiscovered回调

  1. // Implements callback methods for GATT events that the app cares about. For example,
  2. // connection change and services discovered.
  3. private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
  4. @Override
  5. public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
  6. if (newState == BluetoothProfile.STATE_CONNECTED) {
  7. Log.e(TAG, "Connected to GATT server.");
  8. // Attempts to discover services after successful connection.
  9. Log.e(TAG, "Attempting to start service discovery:" +
  10. mBluetoothGatt.discoverServices());
  11. } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
  12. Log.e(TAG, "Disconnected from GATT server.");
  13. setState(ConnectionState.STATE_NONE);
  14. if (getConnectionCallback() != null)
  15. getConnectionCallback().onConnectionLost();
  16. }
  17. }
  18. @Override
  19. public void onServicesDiscovered(BluetoothGatt gatt, int status) {
  20. if (status == BluetoothGatt.GATT_SUCCESS) {
  21. Log.e(TAG, "onServicesDiscovered received: SUCCESS");
  22. initCharacteristic();
  23. try {
  24. Thread.sleep(200);//延迟发送,否则第一次消息会不成功
  25. } catch (InterruptedException e) {
  26. e.printStackTrace();
  27. }
  28. if (getConnectionCallback() != null)
  29. getConnectionCallback().onConnected(mBluetoothDevice.getName());
  30. setState(ConnectionState.STATE_CONNECTED);
  31. } else {
  32. Log.e(TAG, "onServicesDiscovered error falure " + status);
  33. setState(ConnectionState.STATE_NONE);
  34. if (getConnectionCallback() != null)
  35. getConnectionCallback().onConnectionLost();
  36. }
  37. }
  38. @Override
  39. public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
  40. super.onCharacteristicWrite(gatt, characteristic, status);
  41. Log.e(TAG, "onCharacteristicWrite status: " + status);
  42. }
  43. @Override
  44. public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
  45. super.onDescriptorWrite(gatt, descriptor, status);
  46. Log.e(TAG, "onDescriptorWrite status: " + status);
  47. }
  48. @Override
  49. public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
  50. super.onDescriptorRead(gatt, descriptor, status);
  51. Log.e(TAG, "onDescriptorRead status: " + status);
  52. }
  53. @Override
  54. public void onCharacteristicRead(BluetoothGatt gatt,
  55. BluetoothGattCharacteristic characteristic,
  56. int status) {
  57. Log.e(TAG, "onCharacteristicRead status: " + status);
  58. }
  59. @Override
  60. public void onCharacteristicChanged(BluetoothGatt gatt,
  61. BluetoothGattCharacteristic characteristic) {
  62. Log.e(TAG, "onCharacteristicChanged characteristic: " + characteristic);
  63. readCharacteristic(characteristic);
  64. }
  65. };

发现服务 (触发onServicesDiscovered)

在发现服务后,会触发 GATT回调的onServicesDiscovered 方法,我们需要在这里初始化我们的操作,包括:

1 查看服务。或者便利查找指定的(和目标硬件UUID符合的)服务。

2 获得指定服务的特征 characteristic1

3 订阅“特征”发生变化的通知”

  1. public void initCharacteristic() {
  2. if (mBluetoothGatt == null) throw new NullPointerException();
  3. List<BluetoothGattService> services = mBluetoothGatt.getServices();
  4. Log.e(TAG, services.toString());
  5. BluetoothGattService service = mBluetoothGatt.getService(uuidServer);
  6. characteristic1 = service.getCharacteristic(uuidChar1);
  7. characteristic2 = service.getCharacteristic(uuidChar2);
  8. final String uuid = "00002902-0000-1000-8000-00805f9b34fb";
  9. if (mBluetoothGatt == null) throw new NullPointerException();
  10. mBluetoothGatt.setCharacteristicNotification(characteristic1, true);
  11. BluetoothGattDescriptor descriptor = characteristic1.getDescriptor(UUID.fromString(uuid));
  12. descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
  13. mBluetoothGatt.writeDescriptor(descriptor);
  14. }

订阅“特征”发生变化的通知”

调用 mBluetoothGatt.setCharacteristicNotification() 方法,传入一个特征 characteristic 对象。

当这个特征里的数据发生变化(接收到数据了),会触发 回调方法的 onCharacteristicChanged 方法。我们在这个回调方法中读取数据。

  1. final String uuid = "00002902-0000-1000-8000-00805f9b34fb";
  2. if (mBluetoothGatt == null) throw new NullPointerException();
  3. mBluetoothGatt.setCharacteristicNotification(characteristic1, true);
  4. BluetoothGattDescriptor descriptor = characteristic1.getDescriptor(UUID.fromString(uuid));
  5. descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
  6. mBluetoothGatt.writeDescriptor(descriptor);

读取数据

GATT的回调中有 onCharacteristicChanged 方法,我们在这里可以获得接收的数据

  1. @Override
  2. public void onCharacteristicChanged(BluetoothGatt gatt,
  3. BluetoothGattCharacteristic characteristic) {
  4. Log.e(TAG, "onCharacteristicChanged characteristic: " + characteristic);
  5. readCharacteristic(characteristic);
  6. }

调用 characteristic.getValue() 方法,获得字节

  1. public void readCharacteristic(BluetoothGattCharacteristic characteristic) {
  2. if (mBluetoothAdapter == null || mBluetoothGatt == null) {
  3. Log.e(TAG, "BluetoothAdapter not initialized");
  4. return;
  5. }
  6. mBluetoothGatt.readCharacteristic(characteristic);
  7. byte[] bytes = characteristic.getValue();
  8. String str = new String(bytes);
  9. Log.e(TAG, "## readCharacteristic, 读取到: " + str);
  10. if (getConnectionCallback() != null)
  11. getConnectionCallback().onReadMessage(bytes);
  12. }

写入数据

写入数据时,我们需要先获得特征,特征存在于服务内,一般在发现服务的 onServicesDiscovered 时,查找到特征对象。

  1. public void write(byte[] cmd) {
  2. Log.e(TAG, "write:" + new String(cmd));
  3. synchronized (LOCK) {
  4. characteristic2.setValue(cmd);
  5. characteristic2.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
  6. mBluetoothGatt.writeCharacteristic(characteristic2);
  7. if (getConnectionCallback() != null)
  8. getConnectionCallback().onWriteMessage(cmd);
  9. }
  10. }

关闭蓝牙连接

public void close() {

if (mBluetoothGatt == null) {

return;

}

mBluetoothGatt.close();

mBluetoothGatt = null;

}

参考

https://developer.android.com/guide/topics/connectivity/bluetooth-le.html

Android使用BLE(低功耗蓝牙,Bluetooth Low Energy)的更多相关文章

  1. Overview and Evaluation of Bluetooth Low Energy: An Emerging Low-Power Wireless Technology

    转自:http://www.mdpi.com/1424-8220/12/9/11734/htm Sensors 2012, 12(9), 11734-11753; doi:10.3390/s12091 ...

  2. How to Implement Bluetooth Low Energy (BLE) in Ice Cream Sandwich

    ShareThis - By Vikas Verma Bluetooth low energy (BLE) is a feature of Bluetooth 4.0 wireless radio t ...

  3. 基于蓝牙4.0(Bluetooth Low Energy)胎压监测方案设计

    基于一种新的蓝牙技术——蓝牙4.0(Bluetooth Low Energy)新型的胎压监测系统(TPMS)的设计方案.鉴于蓝牙4.0(Bluetooth Low Energy)的低成本.低功耗.高稳 ...

  4. Bluefruit LE Sniffer - Bluetooth Low Energy (BLE 4.0) - nRF51822 驱动安装及使用

    BLE Sniffer https://www.adafruit.com/product/2269 Bluefruit LE Sniffer - Bluetooth Low Energy (BLE 4 ...

  5. Bluetooth Low Energy 嗅探

    Bluetooth Low Energy 嗅探 路人甲 · 2015/10/16 10:52 0x00 前言 如果你打开这篇文章时期望看到一些新的东西,那么很抱歉这篇文章不是你在找的那篇文章.因为严格 ...

  6. Bluetooth Low Energy介绍

    目录 1. 介绍 2. 协议栈 3. 实现方案 3.1 硬件实现方案 3.2 软件实现方案 1. 介绍 Bluetooth low energy,也称BLE(低功耗蓝牙),在4.0规范中提出 BLE分 ...

  7. Bluetooth Low Energy 介绍

    1.简介 BLE(Bluetooth Low Energy,低功耗蓝牙)是对传统蓝牙BR/EDR技术的补充.尽管BLE和传统蓝牙都称之为蓝牙标准,且共享射频,但是,BLE是一个完全不一样的技术.BLE ...

  8. Bluetooth® Low Energy Beacons

    Bluetooth® Low Energy Beacons ABSTRACT (abstract ) 1.This application report presents the concept of ...

  9. BLE——低功耗蓝牙(Bluetooth Low Energy)

    1.简介 以下蓝牙协议特指低功耗蓝牙协议. 蓝牙协议是由SIG制定并维护的通信协议,蓝牙协议栈是蓝牙协议的具体实现. 各厂商都根据蓝牙协议实现了自己的一套函数库——蓝牙协议栈,所以不同厂商的蓝牙协议栈 ...

随机推荐

  1. SQLSERVER性能监控级别步骤

    SQLSERVER性能监控级别步骤 下面先用一幅图描述一下有哪些步骤和顺序 1.识别瓶颈 识别瓶颈的原因包括多个方面,例如,资源不足,需要添加或升级硬件: 工作负荷在同类资源之间分布不均匀,例如,一个 ...

  2. Centos6.5 下安装PostgreSQL9.4数据库

    一.安装PostgreSQL源 CentOS 6.x 32bit rpm -Uvh http://yum.postgresql.org/9.4/redhat/rhel-6-i386/pgdg-cent ...

  3. ARCGIS FOR JAVASCRIPT API 出现multipleDefine问题

    问题: Error {src: "dojoLoader", info: Object, stack: (...), message: "multipleDefine&qu ...

  4. 真实世界:使用WCF扩展在方法调用前初始化环境

    OperationInvoker 介绍 OperationInvoker 是 WCF 运行时模型中在调用最终用户代码前的最后一个扩展点,OperationInvoker 负责最终调用 Service ...

  5. Xperf Basics: Recording a Trace(转)

    http://randomascii.wordpress.com/2011/08/18/xperf-basics-recording-a-trace/   This post is obsolete ...

  6. UnicodeDecodeError while using json.dumps()

    UnicodeDecodeError 系统.文件.vim全部设置为utf-8 export LANG=zh_CN.UTF8 # coding:utf-8 json.dumps(content, ens ...

  7. [ACM_动态规划] POJ 1050 To the Max ( 动态规划 二维 最大连续和 最大子矩阵)

    Description Given a two-dimensional array of positive and negative integers, a sub-rectangle is any ...

  8. Linux:常用shell快捷键

    按键 作用 Ctrl+d 键盘输入结束或退出终端 Ctrl+s 暂定当前程序,暂停后按下任意键恢复运行 Ctrl+z 将当前程序放到后台运行,恢复到前台为命令fg Ctrl+a 将光标移至输入行头,相 ...

  9. 每天一个linux命令(5):rm 命令

    昨天学习了创建文件和目录的命令mkdir ,今天学习一下linux中删除文件和目录的命令: rm命令.rm是常用的命令,该命令的功能为删除一个目录中的一个或多个文件或目录,它也可以将某个目录及其下的所 ...

  10. python基于LeanCloud的短信验证

    python基于LeanCloud的短信验证 1. 获取LeanCloud的Id.Key 2. 安装Flask框架和Requests库 pip install flask pip install re ...