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

转载自:

https://blog.csdn.net/fu908323236/article/details/76208997

这篇博客主要讲解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设备相互通信的大致流程
扫描并与指定的BLE设备进行连接。
连接成功就能拿到设备的GATT、Service、Characteristic、Descriptor这几个关键的东西(其实能拿到很多东西),并且每个Service、Characteristic都有自己唯一的UUID。
开启数据通道,这里的一条数据通道就相当于一个Characteristic,而具体开启哪一个或哪几个,需要根据BLE设备的工程师给的协议文档,如果工程师给的文档很坑的话,就要自己调试,判断等。
手机就可以向BLE设备发送数据或接受BLE向手机发送的数据。
一般BLE设备向手机发送的数据都16进制的数据报,类似于IP数据报,需要自己解析,具体的解析规则BLE设备的工程师都会给一份协议文档的,看着解析就行了,到这里就完成了与BLE设备的通信了。
下面就开始具体的一步一步实现了。

一、扫描BLE设备并对指定设备进行连接
首先别忘了在AndroidManifest.xml中声明蓝牙权限。

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

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

扫描二维码关注公众号,回复: 4478824 查看本文章

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,记录下来,开数据通道的时候也需要用到。 

è¿éåå¾çæè¿°
//发现服务的回调
@Override
public 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是自己写的方法。
@Override
public 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开发一些初步理解,如有错误的地方,还望指正。

猜你喜欢

转载自blog.csdn.net/parasoft/article/details/84852319