Android:BLE(Bluetooth Low Energy)开发

来源:互联网 发布:淘宝卖家充快递单号 编辑:程序博客网 时间:2024/05/01 13:56

Demo下载

demo下载

BLE介绍

最近穿戴设备发展得很火,其大多数所用的技术是BLE(Bluetooth Low Energy)。BLE是蓝牙4.0的核心Profile,BLE前身是NOKIA开发的Wibree技术,主要用于实现移动智能终端与周边配件之间的持续连接,是功耗极低的短距离无线通信技术,并且有效传输距离被提升到了100米以上,同时只需要一颗纽扣电池就可以工作数年之久。
BLE主打功能是快速搜索,快速连接,超低功耗保持连接和传输数据,弱点是数据传输速率低,由于BLE的低功耗特点,因此普遍用于穿戴设备。Android 4.3才开始支持BLE API,所以大家在开发时需要在蓝牙4.0和Android 4.3及其以上的系统。

BLE分类

BLE设备分单模和双模两种,双模简称BR,商标为Bluetooth Smart Ready,单模简称BLE或者LE,商标为Bluetooth Smart。Android是在4.3后才支持BLE,这可以解释不是所有蓝牙手机都支持BLE,而且支持BLE的蓝牙手机一般是双模的。
双模兼容传统蓝牙,可以和传统蓝牙通信,也可以和BLE通信,常用在手机上,android4.3和IOS4.0之后版本都支持BR,也就是双模设备。单模只能和BR和单模的设备通信,不能和传统蓝牙通信,由于功耗低,待机长,所以常用在手环的智能设备上。这可以解释手机上的BLE与手环等设备上的BLE的区别。
不是所有手机都支持BLE,因为BLE不仅仅依靠软件实现,同时需要硬件支持,于是有很多手机不能联接智能手环等设备。Android4.3手机上安装的是双模BR,因此兼容蓝牙3.0之前的技术,既能与BLE设备通信,也能与传统蓝牙通信,比较耗电,能够像传统设备一样高速传输。大部分智能手环使用的单工BLE,不支持传统蓝牙,不能与之联结和通信,低功耗低速率设备。

蓝牙的工作原理

蓝牙通信的主从关系

蓝牙技术规定每一对设备之间进行蓝牙通讯时,必须一个为主角色,另一为从角色,才能进行通信,通信时,必须由主端进行查找,发起配对,建链成功后,双方即可收发数据。理论上,一个蓝牙主端设备,可同时与7个蓝牙从端设备进行通讯。一个具备蓝牙通讯功能的设备,可以在两个角色间切换,平时工作在从模式,等待其它主设备来连接,需要时,转换为主模式,向其它设备发起呼叫。一个蓝牙设备以主模式发起呼叫时,需要知道对方的蓝牙地址,配对密码等信息,配对完成后,可直接发起呼叫。这可以解释为什么有时无法连接蓝牙,有可能是连接的蓝牙设备过多。

蓝牙的呼叫过程

蓝牙主端设备发起呼叫,首先是查找,找出周围处于可被查找的蓝牙设备。主端设备找到从端蓝牙设备后,与从端蓝牙设备进行配对,此时需要输入从端设备的PIN码,也有设备不需要输入PIN码。配对完成后,从端蓝牙设备会记录主端设备的信任信息,此时主端即可向从端设备发起呼叫,已配对的设备在下次呼叫时,不再需要重新配对。已配对的设备,做为从端的蓝牙耳机也可以发起建链请求,但做数据通讯的蓝牙模块一般不发起呼叫。链路建立成功后,主从两端之间即可进行双向的数据或语音通讯。在通信状态下,主端和从端设备都可以发起断链,断开蓝牙链路。

蓝牙一对一的串口数据传输应用

蓝牙数据传输应用中,一对一串口数据通讯是最常见的应用之一,蓝牙设备在出厂前即提前设好两个蓝牙设备之间的配对信息,主端预存有从端设备的PIN码、地址等,两端设备加电即自动建链,透明串口传输,无需外围电路干预。一对一应用中从端设备可以设为两种类型,一是静默状态,即只能与指定的主端通信,不被别的蓝牙设备查找;二是开发状态,既可被指定主端查找,也可以被别的蓝牙设备查找建链。

Android与BLE

关键概念

Generic Attribute Profile (GATT)

通过BLE连接,读写属性类小数据的Profile通用规范。现在所有的BLE应用Profile都是基于GATT的。

Attribute Protocol (ATT)

GATT是基于ATTProtocol的。ATT针对BLE设备做了专门的优化,具体就是在传输过程中使用尽量少的数据。每个属性都有一个唯一的UUID,属性将以characteristics and services的形式传输。

Characteristic

Characteristic可以理解为一个数据类型,它包括一个value和0至多个对次value的描述(Descriptor)。

Descriptor

对Characteristic的描述,例如范围、计量单位等。

Service

Characteristic的集合。例如一个service叫做“Heart Rate Monitor”,它可能包含多个Characteristics,其中可能包含一个叫做“heart ratemeasurement”的Characteristic。

角色和职责

Android设备与BLE设备交互有两组角色:

中心设备和外围设备(Central vs. peripheral);

GATT server vs. GATT client.

Central vs. peripheral:

中心设备和外围设备的概念针对的是BLE连接本身。Central角色负责scan advertisement。而peripheral角色负责make advertisement。

GATT server vs. GATT client:

这两种角色取决于BLE连接成功后,两个设备间通信的方式。

举例说明:

现 有一个活动追踪的BLE设备和一个支持BLE的Android设备。Android设备支持Central角色,而BLE设备支持peripheral角色。创建一个BLE连接需要这两个角色都存在,都仅支持Central角色或者都仅支持peripheral角色则无法建立连接。

当 连接建立后,它们之间就需要传输GATT数据。谁做server,谁做client,则取决于具体数据传输的情况。例如,如果活动追踪的BLE设备需要向 Android设备传输sensor数据,则活动追踪器自然成为了server端;而如果活动追踪器需要从Android设备获取更新信息,则 Android设备作为server端可能更合适。

权限及feature

和经典蓝牙一样,应用使用蓝牙,需要声明BLUETOOTH权限,如果需要扫描设备或者操作蓝牙设置,则还需要BLUETOOTH_ADMIN权限:

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

除了蓝牙权限外,如果需要BLE feature则还需要声明uses-feature:

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

按时required为true时,则应用只能在支持BLE的Android设备上安装运行;required为false时,Android设备均可正常安装运行,需要在代码运行时判断设备是否支持BLE feature:

// Use this check to determine whether BLE is supportedon the device. Then// you can selectively disable BLE-related features.if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)){Toast.makeText(this, R.string.ble_not_supported,Toast.LENGTH_SHORT).show();finish();}

启动蓝牙

在使用蓝牙BLE之前,需要确认Android设备是否支持BLE feature(required为false时),另外要需要确认蓝牙是否打开。

如果发现不支持BLE,则不能使用BLE相关的功能。如果支持BLE,但是蓝牙没打开,则需要打开蓝牙。

打开蓝牙的步骤:

1.获取BluetoothAdapter

BluetoothAdapter是Android系统中所有蓝牙操作都需要的,它对应本地Android设备的蓝牙模块,在整个系统中BluetoothAdapter是单例的。当你获取到它的示例之后,就能进行相关的蓝牙操作了。

获取BluetoothAdapter代码示例如下:

// Initializes Bluetooth adapter.final BluetoothManager bluetoothManager =(BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);mBluetoothAdapter = bluetoothManager.getAdapter();

注:这里通过getSystemService获取BluetoothManager,再通过BluetoothManager获取BluetoothAdapter。BluetoothManager在Android4.3以上支持(API level 18)。

2.判断是否支持蓝牙,并打开蓝牙

获取到BluetoothAdapter之后,还需要判断是否支持蓝牙,以及蓝牙是否打开。

如果没打开,需要让用户打开蓝牙:

private BluetoothAdapter mBluetoothAdapter;...// Ensures Bluetooth is available on the device and it is enabled. If not,// displays a dialog requesting user permission to enable Bluetooth.if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {Intent enableBtIntent = newIntent(BluetoothAdapter.ACTION_REQUEST_ENABLE);startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);}

搜索BLE设备

通过调用BluetoothAdapter的 startLeScan() 搜索BLE设备。调用此方法时需要传入 BluetoothAdapter.LeScanCallback 参数。

因此你需要实现 BluetoothAdapter.LeScanCallback 接口,BLE设备的搜索结果将通过这个callback返回。

由于搜索需要尽量减少功耗,因此在实际使用时需要注意:

1、当找到对应的设备后,立即停止扫描;

2、不要循环搜索设备,为每次搜索设置适合的时间限制。避免设备不在可用范围的时候持续不停扫描,消耗电量。

搜索的示例代码如下:

/** * Activity for scanning and displaying available BLE devices. */public class DeviceScanActivity extends ListActivity {  private BluetoothAdapter mBluetoothAdapter;  private boolean mScanning;  private Handler mHandler;  // Stops scanning after 10 seconds.  private static final long SCAN_PERIOD = 10000;  ...  private void scanLeDevice(final boolean enable) {    if (enable) {      // Stops scanning after a pre-defined scan period.      mHandler.postDelayed(new Runnable() {        @Override        public void run() {          mScanning = false;          mBluetoothAdapter.stopLeScan(mLeScanCallback);        }      }, SCAN_PERIOD);      mScanning = true;      mBluetoothAdapter.startLeScan(mLeScanCallback);    } else {      mScanning = false;      mBluetoothAdapter.stopLeScan(mLeScanCallback);    }  }}

如果你只需要搜索指定UUID的外设,你可以调用 startLeScan(UUID[], BluetoothAdapter.LeScanCallback) 方法。

其中UUID数组指定你的应用程序所支持的GATT Services的UUID。

BluetoothAdapter.LeScanCallback 的实现示例如下:

private LeDeviceListAdapter mLeDeviceListAdapter;...// Device scan callback.private BluetoothAdapter.LeScanCallback mLeScanCallback =  new BluetoothAdapter.LeScanCallback() {    @Override    public void onLeScan(final BluetoothDevice device, int rssi,      byte[] scanRecord) {  runOnUiThread(new Runnable() {     @Override     public void run() {         mLeDeviceListAdapter.addDevice(device);         mLeDeviceListAdapter.notifyDataSetChanged();     }       });   }};

注意:搜索时,你只能搜索传统蓝牙设备或者BLE设备,两者完全独立,不可同时被搜索。

连接GATTServer

两个设备通过BLE通信,首先需要建立GATT连接。这里我们讲的是Android设备作为client端,连接GATT Server。

连接GATT Server,你需要调用BluetoothDevice的 connectGatt() 方法。此函数带三个参数:Context、autoConnect(boolean)和 BluetoothGattCallback 对象。调用示例:

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

函数成功,返回 BluetoothGatt 对象,它是GATT profile的封装。通过这个对象,我们就能进行GATT Client端的相关操作。 BluetoothGattCallback 用于传递一些连接状态及结果。

BluetoothGatt常规用到的几个操作示例:

connect() :连接远程设备。

discoverServices() : 搜索连接设备所支持的service。

disconnect():断开与远程设备的GATT连接。

close():关闭GATTClient端。

readCharacteristic(characteristic) :读取指定的characteristic。

setCharacteristicNotification(characteristic, enabled):设置当指定characteristic值变化时,发出通知。

getServices() :获取远程设备所支持的services。

等等。

注意:

1.某些函数调用之间存在先后关系。例如首先需要connect上才能discoverServices。

2.一些函数调用是异步的,需要得到的值不会立即返回,而会在BluetoothGattCallback的回调函数中返回。例如 discoverServices与onServicesDiscovered回调,readCharacteristic与 onCharacteristicRead回调,setCharacteristicNotification与onCharacteristicChanged回调等。

通讯

这一点与之前形式上完全不一样了
BLE分为三部分Service、Characteristic、Descriptor,这三部分都由UUID作为唯一标示符。一个蓝牙4.0的终端可以包含多个Service,一个Service可以包含多个Characteristic,一个Characteristic包含一个Value和多个Descriptor,一个Descriptor包含一个Value。

在连接上某个终端后,可以将其每个结点的UUID全部打印出来,但每个结点不是都能读写。

一般来说,Characteristic是手机与BLE终端交换数据的关键,Characteristic有较多的跟权限相关的字段,例如PERMISSION和PROPERTY,而其中最常用的是PROPERTY,本文所用的BLE蓝牙模块竟然没有标准的Characteristic的PERMISSION。Characteristic的PROPERTY可以通过位运算符组合来设置读写属性,例如READ|WRITE、READ|WRITE_NO_RESPONSE|NOTIFY,因此读取PROPERTY后要分解成所用的组合

我是这么去和ble终端通信的:

得到某个service的对象

BluetoothGattService linkLossService =mBluetoothGatt                                   .getService(UUID.fromString("49535343-fe7d-4ae5-8fa9-9fafd205e455"));

一般说来,ble设备都带有几个标准的服务,其UUID已经定义好了,这些结点里的值只能读了,因为我一个一个试过了,终于找到了我的设备里可以读写的服务,其中49535343-fe7d-4ae5-8fa9-9fafd205e455就是对应这个服务的

获取此服务结点下的某个Characteristic对象

BluetoothGattCharacteristic alertLevel =linkLossService.getCharacteristic(UUID.fromString("49535343-8841-43f4-a8d4-ecbe34729bb3"));

一般供应商会给出多个Characteristic,你需要找到到底哪个才是让你去写的,怎么找需要看对应的终端的一些开发文档之类的,在这里我经过测试已经找到我要的了

设置要写的值

alertLevel.setValue(values_on);

这里的values_on是一个byte数组

status = mBluetoothGatt.writeCharacteristic(alertLevel);

status如果为true,表示写操作已经成功执行,BluetoothGattCallback抽象类的一个方法会被执行,如果刚好你又重写了这个方法,就可以打印一些消息了

public void onCharacteristicWrite(BluetoothGatt gatt,           BluetoothGattCharacteristiccharacteristic, int status)

读某个Characteris

public void readCharacteristic(BluetoothGattCharacteristiccharacteristic) {        if (mBluetoothAdapter == null || mBluetoothGatt == null) {            Log.w(TAG, "BluetoothAdapter not initialized");            return;        }        mBluetoothGatt.readCharacteristic(characteristic);}

如果成功,数据会在下面的方法回调中传进来

public voidonCharacteristicRead(BluetoothGatt gatt,                                        BluetoothGattCharacteristic characteristic,                                         int status)

当终端有数据要传过来的时候,表面上正常的话,手机这边下面的方法会

public voidonCharacteristicRead(BluetoothGatt gatt,                                    BluetoothGattCharacteristic characteristic,                                         intstatus)

是可以控制的,设置descriptor的value不同,可以控制这个重写的方法是否会被调用,没有测试其他的设备,感觉这个应该是会对应不同的设备,具体设置的地方会有不同,在我这边是这么操作的:

public void enableNotification(boolean b){    if(b)    {               BluetoothGattService service =mBluetoothGatt                             .getService(UUID.fromString("49535343-fe7d-4ae5-8fa9-9fafd205e455"));             BluetoothGattCharacteristicale =service.getCharacteristic(UUID.fromString("49535343-1E4D-4BD9-BA61-23C647249616"));             booleanset = mBluetoothGatt.setCharacteristicNotification(ale, true);             Log.d(TAG," setnotification = " + set);             BluetoothGattDescriptordsc =ale.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));             byte[]bytes = {0x01,0x00};        dsc.setValue(bytes);        boolean success =mBluetoothGatt.writeDescriptor(dsc);        Log.d(TAG, "writing enabledescriptor:" + success);    }    else    {               BluetoothGattService service =mBluetoothGatt                             .getService(UUID.fromString("49535343-fe7d-4ae5-8fa9-9fafd205e455"));             BluetoothGattCharacteristicale =service.getCharacteristic(UUID.fromString("49535343-1E4D-4BD9-BA61-23C647249616"));             booleanset = mBluetoothGatt.setCharacteristicNotification(ale, false);             Log.d(TAG," setnotification = " + set);             BluetoothGattDescriptordsc =ale.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));             byte[]bytes = {0x00, 0x00};        dsc.setValue(bytes);        boolean success =mBluetoothGatt.writeDescriptor(dsc);        Log.d(TAG, "writing enabledescriptor:" + success);    } }

总结

网上的一些资料大都以上面的命名来标识自己的文档,有必要解释一下,应该分开来看这个命题:

android指的安装的4.3及以上版本的android系统的设备

4.0蓝牙指的蓝牙芯片使用4.0协议的设备

这种开发的一种标准用处是:用4.3以上android版本的手机,与4.0蓝牙穿戴式设备进行通信

按网上的一种中央与周边的说法,手机就是中央,4.0蓝牙设备就中周边

如果要开发4.0蓝牙,应该知道4.0蓝牙具有高速、低功耗的优点,这些优点对手机的提升不大,但对其他一些终端设备的提升就比较大。

有意思的是,android对与4.0蓝牙通信的封装,不需要本身设备的蓝牙芯片是4.0协议的蓝牙芯片

于是android 蓝牙4.0开发的这么一个“大环境”下的真实情景就是:一个没有必要拥有蓝牙4.0协议的蓝牙芯片的android4.3以上系统的手机,与一些4.0蓝牙协议的蓝牙芯片设备终端的故事

以上是一些事实,以下是一些猜想

1、 蓝牙4.0与之前版本协议之间可以通讯,说明:4.0蓝牙协议并不是修改的无线波的调制与解调,而是修改的数据的组成

2、 对蓝牙4.0协议的支持,是由google提出的,而不是各个手机厂商提出的,说明:android系统在软件上可以一致对待不同的蓝牙芯片,不同的蓝牙芯片对同一段数据的调制解调结果是一样的,于是在这段数据通过串口传到手机主控的时候,也是一样的,在这个环境里,蓝牙芯片只是一个调制解调器,android封装了对数据全部的处理。

参考

http://blog.csdn.net/hellogv/article/details/24267685
http://www.blogjava.net/zh-weir/archive/2013/12/09/407373.html
http://blog.csdn.net/mov2012/article/details/16368441

0 0