Android BLE蓝牙4.0开发—Android手机与BLE终端通信

来源:互联网 发布:windows 注册服务 编辑:程序博客网 时间:2024/05/14 17:43

这篇博客主要讲解AndroidBLE蓝牙4.0的基本概念,以及基础用法。
BLE 即 Bluetooth Low Energy,蓝牙低功耗技术,是蓝牙4.0引入的新技术,在安卓4.3(API 18)以上为BLE的核心功能提供平台支持和API。与传统的蓝牙相比,BLE更显著的特点是低功耗,所以现在越来越多的智能设备使用了BLE,比如满大街的智能手环,还有体重秤、血压计、心电计等很多BLE设备都使用了BLE与终端设备进行通信。

关键概念和术语


  • Generic Attribute Profile(GATT):GATT配置文件是一个通用规范,用于在BLE链路上发送和接收被称为“属性”的数据块。目前所有的BLE应用都基于GATT。 蓝牙SIG规定了许多低功耗设备的配置文件。配置文件是设备如何在特定的应用程序中工作的规格说明。注意一个设备可以实现多个配置文件。例如,一个设备可能包括心率监测仪和电量检测。
  • Service:service是characteristic的集合。例如,你可能有一个叫“Heart Rate Monitor(心率监测仪)”的service,它包括了很多characteristics,如“heart rate measurement(心率测量)”等。你可以在bluetooth.org 找到一个目前支持的基于GATT的配置文件和服务列表。
  • Characteristic:一个characteristic包括一个单一变量和0-n个用来描述characteristic变量的descriptor,characteristic可以被认为是一个类型,类似于类。
  • Descriptor:Descriptor用来描述characteristic变量的属性。例如,一个descriptor可以规定一个可读的描述,或者一个characteristic变量可接受的范围,或者一个characteristic变量特定的测量单位。

他们之间的关系是一个BLE终端可以包含多个Service, 一个Service可以包含多个Characteristic,一个Characteristic包含一个value和多个Descriptor,一个Descriptor包含一个Value。Characteristic是比较重要的,是手机与BLE终端交换数据的关键,读取设置数据等操作都是操作Characteristic的相关属性。

这里我打个断点,在调试模式下给你们看看里面具体的结构,先大致的感受一下,不感兴趣的直接往下拉吧。
这里写图片描述

这是连接了一个BLE设备后获取的,可以看出,我这个BLE设备里有5个Service,第一个Service里又有5个Characteristic。

这里写图片描述

这幅图是把第一个Service里的Characteristic展开后的结构,可以看到,这个Characteristic没有Descriptor。通过这两幅图可以看出每个Service和Characteristic都有他自己的UUID。

与BLE设备相互通信的大致流程


  1. 扫描并与指定的BLE设备进行连接。
  2. 连接成功就能拿到设备的GATT、Service、Characteristic、Descriptor这几个关键的东西(其实能拿到很多东西),并且每个Service、Characteristic都有自己唯一的UUID。
  3. 开启数据通道,这里的一条数据通道就相当于一个Characteristic,而具体开启哪一个或哪几个,需要根据BLE设备的工程师给的协议文档,如果工程师给的文档很坑的话,就要自己调试,判断等。
  4. 手机就可以向BLE设备发送数据或接受BLE向手机发送的数据。
  5. 一般BLE设备向手机发送的数据都16进制的数据报,类似于IP数据报,需要自己解析,具体的解析规则BLE设备的工程师都会给一份协议文档的,看着解析就行了,到这里就完成了与BLE设备的通信了。

下面就开始具体的一步一步实现了。

一、扫描BLE设备并对指定设备进行连接

首先别忘了在AndroidManifest.xml中声明蓝牙权限。

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

一般在程序开始的时候先要判断手机是否支持BLE,不支持的话赶快换手机吧。

if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {    Log.i(TAG, "支持BLE");} else {    Log.i(TAG, "不支持BLE");}

手机支持BLE的话,就可以继续往下走了。
接着我们需要用到蓝牙适配器,然后判断蓝牙是否开启,没开启就会提示开启。

//通过系统服务获取蓝牙管理者BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);//获取蓝牙适配器BluetoothAdapter mBluetoothAdapter = bluetoothManager.getAdapter();if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {    System.out.println("蓝牙没有开启");    //请求开启蓝牙    Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);    startActivityForResult(enableBtIntent, 1);}

提示开蓝牙
这里写图片描述

蓝牙开启了就可以开始搜索设备了。
这里的mBluetoothAdapter就是我们前面拿到的那个,然后调用startLeScan()方法搜索设备(只能搜索到BLE设备,蓝牙2.0的不行),并且需要传给它一个LeScanCallback回调,那么我们就实现一个传给它。

mBluetoothAdapter.startLeScan(mLeScanCallback); //开始搜索BLE设备private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {        //当搜索到一个设备,这里就会回调,注意这里回调到的是子线程。        @Override        public void onLeScan(final BluetoothDevice device, final int rssi, byte[] scanRecord) {            //在这里可以把搜索到的设备保存起来0            //device.getName();获取蓝牙设备名字            //device.getAddress();获取蓝牙设备mac地址。            //这里的rssi即信号强度,即手机与设备之间的信号强度。            //注意,这里不是搜索到1个设备后就只回调一次这个设备,可能过个几秒又搜索到了这个设备,还会回调的            //所以,这里可以实时刷新设备的信号强度rssi,但是保存的时候就只保存一次就行了。            if (!bluetoothDeviceList.contains(device)) {//保存设备                bluetoothDeviceList.add(device);                Log.i(TAG, device.getName() + "");                Log.i(TAG, device.getAddress() + "");                Log.i(TAG, "信号:" + rssi);            }        }    };

搜索到了BLE设备,并且也保存了。就可以通过调用device.connectGatt()来进行连接,这个方法有三个参数,第一个context不用说了,第二个是boolean类型的,表示是否自动连接,第三个参数又是一个回调,这个回调比较重要,后续很多操作都跟这个回到有关,这里为了方便看我就直接匿名实现了。在onConnectionStateChange这个回调中,我们会收到3个参数,gatt,就是上面的调试图中的那个gatt,这里我们可以保存下来也可以不保存,因为下面其他的回调方法中都会有这个gatt,都是同一个,会经常用到。status表示相应的连接或断开操作是否完成,而不是指连接状态,newStatus表示的是设备的新状态,这里我们可以通过newState来判断是否连接成功。如果连接成功再进行后续的操作。当然也可以通过newState判断设备是否断开。

device.connectGatt(MainActivity.this, false, new BluetoothGattCallback() {    //连接状态改变时回调    @Override    public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {        if (newState == BluetoothProfile.STATE_CONNECTED) {            Log.i(TAG, "连接成功");        } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {            Log.i(TAG, "连接已断开");        }    }    //发现服务的回调    @Override    public void onServicesDiscovered(BluetoothGatt gatt, int status) {    }    //写操作的回调    @Override    public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {    }    //数据返回的回调    @Override    public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {    }    //写入描述符后的回调    @Override    public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {});

二、扫描服务

扫描服务一般在连接成功后就开始扫描,调用gatt.discoverServices()方法就可以了。

    //连接状态改变时回调    @Override    public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {        if (newState == BluetoothProfile.STATE_CONNECTED) {            Log.i(TAG, "连接成功");            gatt.discoverServices(); //扫描服务        } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {            Log.i(TAG, "连接已断开");        }    }

当扫描到服务后或者发现了服务,就会触发onServicesDiscovered这个回调,收到2个参数,gatt还是那个,status也还是表示相应的连接或断开操作是否完成。

    //发现服务的回调    @Override    public void onServicesDiscovered(BluetoothGatt gatt, int status) {        if (status == BluetoothGatt.GATT_SUCCESS) {            Log.i(TAG, "发现服务成功");        }    }

三、开启数据通道(Characteristic)

有了gatt,也成功发现了服务(Service),接下来就是开启数据通道,因为只有通道开启了,才能进行通信。
开启数据通道还是在发现服务的这个回调里。这里要看工程师给的文档怎么样了。我这就先用文档中有UUID进行讲解。
这里写图片描述
可以看到,有一个Services的UUID,一个可写的通道(Characteristic),和4个Notify就是收数据的通道(Characteristic)。虽然UUID没写全,但是我们可以根据这个信息,在调试模式里就能找到,如下图。
这里写图片描述
可以看到这个设备里有4个Service,但是看文挡,我们只需要UUID是ba11f08c-5f14开头的这个Service,然后这里面又有5个Charactristic,刚好是文档中的,我们全都记录下来,定义成常量。还有每一个Charactristic下都有一个Descriptor,这个是描述符也有对应的UUID,记录下来,开数据通道的时候也需要用到。
这里写图片描述

//发现服务的回调@Overridepublic void onServicesDiscovered(BluetoothGatt gatt, int status) {    if (status == BluetoothGatt.GATT_SUCCESS) {        Log.i(TAG, "onServicesDiscovered: 成功");    }    /下面是通过UUID拿到对应的service和characteristic,并把characteristic声明成全局的    BluetoothGattService service = gatt.getService(UUID.fromString(UUID_SERVICE));    characteristicWrite = service.getCharacteristic(UUID.fromString(UUID_CHARACTER_WRITE));    characteristic01 = service.getCharacteristic(UUID.fromString(UUID_CHARACTER_NOTIFY1));    characteristic02 = service.getCharacteristic(UUID.fromString(UUID_CHARACTER_NOTIFY2));    characteristic03 = service.getCharacteristic(UUID.fromString(UUID_CHARACTER_NOTIFY3));    characteristic04 = service.getCharacteristic(UUID.fromString(UUID_CHARACTER_NOTIFY4));    setNotification(gatt, characteristic01, true);  //先开启1号通道}//成功写入描述符回调,这里就实现按顺序数据通道,setNotification是自己写的方法。@Overridepublic void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {    Log.i(TAG, "onDescriptorWrite: 回调");    BluetoothGattCharacteristic characteristic = descriptor.getCharacteristic();    String uuid = characteristic.getUuid().toString();    if (uuid.equals(UUID_CHARACTER_NOTIFY1)) {        setNotification(gatt, characteristic02, true);    } else if (uuid.equals(UUID_CHARACTER_NOTIFY2)) {        setNotification(gatt, characteristic03, true);    } else if (uuid.equals(UUID_CHARACTER_NOTIFY3)) {        setNotification(gatt, characteristic04, true);    } else if (uuid.equals(UUID_CHARACTER_NOTIFY4)) {        //给设备发数据,这里是因为我的设备连接的时候需要连接确认。这里的AA5504B10000B5就是指令        characteristicWrite.setValue(getHexBytes("AA5504B10000B5"));        gatt.writeCharacteristic(characteristicWrite);    }}private void setNotification(BluetoothGatt mGatt, BluetoothGattCharacteristic mCharacteristic, boolean mEnable) {    if (mCharacteristic == null) {        return;    }    //设置为Notifiy,并写入描述符    mGatt.setCharacteristicNotification(mCharacteristic, mEnable);      BluetoothGattDescriptor descriptor = mCharacteristic.getDescriptor(UUID.fromString(CLIENT_CHARACTERISTIC_CONFIG));    descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);    mGatt.writeDescriptor(descriptor);}

四、接收数据

当设备有数据发送给手机的话,就会触发onCharacteristicChanged这个回调,这个回调能被触发,那手机与BLE设备的通信基本就算完成了。通过characteristic.getValue()来获取设备发来的数据,是字节数组,
一次回调最多能发来20个字节,所以要是数据很长的话,就会触发多次回调,就要把每次回调回来的数据保存下来,最后再解析,解析的话根据协议文档来就行了,因为不同的设备解析的规则是不一样的。

    //数据返回的回调    @Override    public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {        Log.i(TAG, "onCharacteristicChanged: 回调");        byte[] bytes = characteristic.getValue();   //获取设备发来的数据        Log.i(TAG, "value=" + byte2hex(bytes));   //byte2hex()是把字节数组转换成16进制的字符串,方便看    }

这里写图片描述
一般发来的就是这样的数据,根据协议文档解析

五、发送数据

给设备发送数据,就是发送指令,文档上会写可以给设备发送指令,也就是功能,然后还会说明指令格式等等,指令最终也还是字节数组。
如下代码,就是一条指令

    private byte[] getAllDataOrder() {        Log.i(TAG, "获取全部数据指令");        byte[] data = new byte[7];        data[0] = (byte) 0x93;        data[1] = (byte) 0x8e;        data[2] = (byte) 0x04;        data[3] = (byte) 0x00;        data[4] = (byte) 0x08;        data[5] = (byte) 0x05;        data[6] = (byte) 0x11;        return data;    }

然后调用下面这2个方法写入指令,设备成功收到的话会触发onCharacteristicChanged回调,并收到数据。这里的characteristicWrite是我们前面拿到的那个。

    characteristicWrite.setValue(getAllDataOrder());    gatt.writeCharacteristic(characteristicWrite);

以上就是本人对安卓BLE开发一些初步理解,如有错误的地方,还望指正。