Bluetooth low energy基础

/ 1评 / 0

概述

在Android 4.3 (API level 18)中,Android为我们提供了低功耗蓝牙的支持,最近很火的可穿戴设备的主要通信方式就是低功耗蓝牙。与传统蓝牙相比,其最主要的目的就是更低的功耗用于支持用户使用可穿戴设备,本篇博客主要介绍关于其的基础。

相关术语

Attribute Protocol (ATT):全称 attribute protocol,中文名“属性协议”。它是 BLE 通信的基础。ATT 把数据封装,向外暴露为“属性”,提供“属性”的为服务端,获取“属性”的为客户端。ATT 是专门为低功耗蓝牙设计的,结构非常简单,数据长度很短。

Generic Attribute Profile (GATT):全称 Generic Attribute Profile, 中文名“通用属性配置文件”。它是在ATT 的基础上,对 ATT 进行的进一步逻辑封装,定义数据的交互方式和含义。GATT是我们做 BLE 开发的时候直接接触的概念。

有关上面两个协议可以参看:GATT Profile 简介

Characteristic: 它定义了数值和操作,包含一个Characteristic声明、Characteristic属性、值、值的描述(Optional)。通常我们讲的 BLE 通信,其实就是对 Characteristic 的读写或者订阅通知。比如在实际操作过程中,我对某一个Characteristic进行读,就是获取这个Characteristic的value。

Descriptor:用于描述Characteristic的value。

Service:Service是一系列的Characteristic的集合。Service 是把数据分成一个个的独立逻辑项,它包含一个或者多个 Characteristic。每个 Service 有一个 UUID 唯一标识。 UUID 有 16 bit 的,或者 128 bit 的。16 bit 的 UUID 是官方通过认证的,需要花钱购买,128 bit 是自定义的,这个就可以自己随便设置。

基于GATT连接的方式

外设通过广播自己来让中心设备发现自己,并建立 GATT 连接,从而进行更多的数据交换。这里有且仅有两个角色,发起连接的一方,叫做中心设备—Central,被连接的设备,叫做外设—Peripheral。

GATT 连接需要特别注意的是:GATT 连接是独占的。也就是一个 BLE 外设同时只能被一个中心设备连接。一旦外设被连接,它就会马上停止广播,这样它就对其他设备不可见了。当设备断开,它又开始广播。中心设备和外设需要双向通信的话,唯一的方式就是建立 GATT 连接。

GATT 通信的双方是 C/S 关系。外设作为 GATT 服务端(Server),它维持了 ATT 的查找表以及 service 和 characteristic 的定义。中心设备是 GATT 客户端(Client),它向 Server 发起请求。需要注意的是,所有的通信事件,都是由客户端发起,并且接收服务端的响应。

基础使用

定义权限:

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

如果需要扫描BLE设备。那么还需要拥有定位权限。注:6.0以上需要动态申请定位权限

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
或者
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

检查设备是否支持BLE设备(并不是检查是否支持蓝牙)

if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
    Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();
    finish();
}

BluetoothAdapter : 系统蓝牙适配器,通过这个适配器,我们可以使用系统蓝牙

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

我们还可以使用BluetoothAdapter的静态方法

BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

常用方法

//bluetoothAdapter为null,说明设备不支持蓝牙
BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
//蓝牙是否可用
bluetoothAdapter.isEnabled();
//开启蓝牙,不推荐使用,因为无法知道用户是否拒绝
bluetoothAdapter.enable();
//关闭蓝牙
bluetoothAdapter.disable();

开启蓝牙:通过下面这种方法,设备会弹出选择框让用户选择是否开启蓝牙,然后我们在onActivityResult回调中即可获知是否开启。

if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
    Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}

蓝牙广播

我们也可以监听BluetoothAdapter.ACTION_STATE_CHANGED广播,可以获取到蓝牙状态改变。

IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
registerReceiver(mBroadcastReceiver, filter);
private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        //当前状态
        int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
        //上一状态
        int statePre = intent.getIntExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, -1);
        String msg = null;
        switch (state) {
            case BluetoothAdapter.STATE_TURNING_ON:
                msg = "turning on";
                break;
            case BluetoothAdapter.STATE_ON:
                msg = "on";
                break;
            case BluetoothAdapter.STATE_TURNING_OFF:
                msg = "turning off";
                break;
            case BluetoothAdapter.STATE_OFF:
                msg = "off";
                break;
        }
        Log.i(TAG, "onReceive: " + msg);
    }
};

扫描BLE设备

我们可以使用adapter.startLeScan来进行蓝牙扫描,由于扫描蓝牙操作很耗电,所以当我们扫描到需要的设备时,停止扫描,扫描时设置一个超时时间。

/**
 * 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);
        }
        ...
    }
...
}
private LeDeviceListAdapter mLeDeviceListAdapter;
...
// Device scan callback.
private BluetoothAdapter.LeScanCallback mLeScanCallback =
        new BluetoothAdapter.LeScanCallback() {
    @Override
    public void onLeScan(final BluetoothDevice device, int rssi,
            byte[] scanRecord) {
   }
};

说明:上面的回调中,rssi为信号强度指示,最大为0,一般都是负数,比如-50比-70信号要强。

连接BLE设备

在上面扫描的回调中我们能获取到BluetoothDevice,然后我们使用列表展示给用户,当用户选择时,使用下面的方法进行连接,主要关注第三个回调参数。因为我们的主要操作均在回调中完成。

//获取蓝牙名
device.getName();
//获取硬件名
device.getAddress();
//获取绑定状态
device.getBondState();
mBluetoothGatt = device.connectGatt(this, false, mGattCallback);

注意:connectGatt方法有一个重载,可以设置通道,所以需要特殊判断版本,参看:FastBlestackoverflow

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    return device.connectGatt(context, autoConnect, bluetoothGattCallback, TRANSPORT_LE);
} else {
    return device.connectGatt(context, autoConnect, bluetoothGattCallback);
}

在下面的回调中我们就能获取到BluetoothGatt对象,这个对象则表示蓝牙设备之间的"连接",我们可以通过gatt.writeCharacteristic()或gatt.readCharacteristic()等方法去进行通信。

public BluetoothGattCallback getCallback() {
    return new BluetoothGattCallback() {
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            super.onConnectionStateChange(gatt, status, newState);
            //已连接
            if (newState == BluetoothProfile.STATE_CONNECTED) {
                //启动发现服务
                gatt.discoverServices();
            }
            //断开连接
            else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
            }
        }

        //服务被发现
        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            super.onServicesDiscovered(gatt, status);
        }


        //Characteristic读取回调
        @Override
        public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            super.onCharacteristicRead(gatt, characteristic, status);
        }

        @Override
        public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            super.onCharacteristicWrite(gatt, characteristic, status);
        }

        //Characteristic改变回调
        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
            super.onCharacteristicChanged(gatt, characteristic);
        }

        @Override
        public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
            super.onDescriptorWrite(gatt, descriptor, status);
        }
    };
}

获取GATT notifications

对于我们的app来说,由设备主动通知app信息发生改变是必须的,我们可以使用setCharacteristicNotification来打开Characteristic的通知,并且将描述设置为打开。代码如下

private BluetoothGatt mBluetoothGatt;
BluetoothGattCharacteristic characteristic;
boolean enabled;
...
mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);
...
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
        UUID.fromString(descriptor_id));
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
mBluetoothGatt.writeDescriptor(descriptor);

当一个通知到来的时候,onCharacteristicChanged会被回调。

关闭连接

public void close() {
    if (mBluetoothGatt == null) {
        return;
    }
    mBluetoothGatt.close();
    mBluetoothGatt = null;
}

参考链接:

Android BLE开发详解和FastBle源码解析

BLE guide

  1. 老黑说道:

    老哥666

发表评论

您的电子邮箱地址不会被公开。