一、BLE(Bluetooth Low Energy)低功耗蓝牙基本知识

Android 4.3(API级别18)引入了以低功耗蓝牙(BLE)为中心角色的内置平台支持,并提供应用程序可用于发现设备,查询服务和传输信息的API

常见用例包括以下内容:

  • 在附近的设备之间传输少量的数据
  • 与Google Beacons等接近传感器进行互动,为用户提供基于当前位置的定制体验。

传统蓝牙可以用于数据量比较大的传输,如语音,音乐,较高数据量传输等,但是比较耗电,低功耗蓝牙这样应用于实时性要求比较高,功耗比较低,但是数据速率比较低的产品,如遥控类的,如鼠标,键盘,遥控鼠标(Air Mouse),传感设备的数据发送,如心跳带,血压计,温度传感器等。

1. GATT 和 ATT

GATT协议

(1)通用属性配置文件 Generic Attribute Profile(GATT):GATT配置文件是通过BLE链接发送和接收被称为“属性”的短小数据的通用规范。目前所有的低能耗应用程序都基于GATT。

  • 蓝牙SIG为低能耗设备定义了许多配置文件。 配置文件是设备如何在特定应用程序中工作的规范。 请注意,设备可以实现多个配置文件。 例如,一个设备可以包含一个心率监测器和一个电池电量检测器。
  • 可以把他看成xml来理解:

    • 每个GATT由完成不同功能的Service组成;
    • 每个Service由不同的Characteristic组成;
    • 每个Characteristic由一个value和一个或者多个Descriptor组成;

Service、Characteristic相当于标签(Service相当于他的类别,Characteristic相当于它的名字),而value才真正的包含数据,Descriptor是对这个value进行的说明和描述,当然我们可以从不同角度来描述和说明,因此可以有多个Descriptor.

1. Service  的UUID: 设备中每一个不同的 Service 都有一个 128 bit 的 UUID 作为这个 Service 的独立标志。蓝牙核心规范制定了两种不同的UUID,一种是基本的UUID,一种是代替基本UUID的16位UUID。所有的蓝牙技术联盟定义UUID共用了一个基本的UUID:0x0000xxxx-0000-1000-8000-00805F9B34FB

2. 每一个 Characteristic 也有一个唯一的 UUID 作为标识符

举一个简单的例子进行说明:

常见的小米手环是一个BLE设备,(假设)它包含三个Service,分别是提供设备信息的Service、提供步数的Service、检测心率的Service;
设备信息的service中包含的characteristic包括厂商信息、硬件信息、版本信息等;

心率Service则包括心率characteristic等,心率characteristic中的value则真正的包含心率的数据,而descriptor则是对该value的描述说明,比如value的单位啊,描述啊,权限啊等。

 

(2)属性协议 Attribute Protocol(ATT):GATT建立在属性协议(ATT)之上。 这也被称为GATT / ATT。 ATT经过优化,可在BLE设备上运行。 为此,它使用尽可能少的字节。 每个属性由一个通用唯一标识符(UUID)唯一标识,该标识符是用于唯一标识信息的字符串ID的标准化128位格式。 ATT传输的属性被格式化为特征和服务。

  • 特性 Characteristic: 特性包含描述特性值的单个值和0-n个描述符。一个特征可以被认为是一个类,类似于一个阶级,是最小的数据逻辑单元。
  • 描述符 Descriptor: 描述符是描述特征值的定义属性。 例如,一个描述符可以指定一个可读的描述,一个特征值的可接受范围,或者一个特征值特有的度量单位。value、descriptor中存储数据的解析由Server的工程师决定,并无规范,双发按照约定开发。
  • 服务 Service: 服务是一个特征的集合。 例如,您可以拥有一个名为“心率监测器”的服务,其中包含“心率测量”等特性。 您可以在bluetooth.org上找到现有基于GATT的配置文件和服务的列表。Service/Characteristic均有一个唯一的UUID标识,UUID既有16位的也有128位的,我们需要了解的是16位的UUID是经过蓝牙组织认证的,是需要购买的,当然也有一些通用的16位UUID。例如Heart Rate服务的UUID就是0X180D,代码中表示为0X00001800-0000-1000-8000-00805f9b34fb,其他位为固定的。而128位的UUID则可以自定义。

在 Android 开发中,建立蓝牙连接后,我们说的通过蓝牙发送数据给外围设备就是往这些 Characteristic 中的 Value 字段写入数据;外围设备发送数据给手机就是监听这些 Charateristic 中的 Value 字段有没有变化,如果发生了变化,手机的 BLE API 就会收到一个监听的回调。 

2. 角色(Roles)和责任(Responsibilities)

Android设备与BLE设备交互时适用的角色和职责:

  • 中央(Central) 与 周边(Peripheral)。这适用于BLE连接本身。处于中心角色的设备扫描,寻找广告,并且在外围角色中的设备进行广告。
  • GATT服务器 与 GATT客户端。这决定了两台设备在建立连接后如何相互通话。

 

为了理解这个区别,假设你有一个Android手机和一个BLE设备的活动追踪器。 手机支持中心角色; 活动跟踪器支持外设角色(建立一个BLE连接,你需要每两个事物中只有一个支持外围设备的人不能彼此交谈,也不能只支持两个事物)。

一旦手机和活动追踪器建立了连接,他们就开始将GATT元数据转移到另一个。根据他们传输的数据的种类,其中一个或另一个可能充当服务器。例如,如果活动跟踪器想要将传感器数据报告给电话,则活动跟踪器可以充当服务器。如果活动跟踪器想要从手机接收更新,那么手机作为服务器可能是有意义的。

在本文档中使用的示例中,Android应用程序(在Android设备上运行)是GATT客户端。该应用程序从GATT服务器获取数据,GATT服务器是支持心率档案的BLE心率监测器。但你也可以设计你的Android应用程序来扮演GATT服务器的角色。

二、安卓BLE开发流程

首先要判断当前的Android设备是否支持蓝牙,如果支持则再判断当前蓝牙是否处于开启状态,如果未开启则发送广播通知系统开启蓝牙,蓝牙开启后开始搜索周围的蓝牙设备,注意搜索一定要设置超时处理,搜索到指定蓝牙设备后停止搜索任务。

此时可以以列表的形式供用户选择需要连接的设备,或者内部自动连接特定的设备,连接成功后,搜索此蓝牙设备提供的服务(特性、描述符的集合),搜索完成后设置一些对应的参数,即可与蓝牙设备进行通信了

1. 相关的API

我们在开发过程中需要用到的一些API:

  • 1.BluetoothAdapter

本地蓝牙适配器,用于一些蓝牙的基本操作,比如判断蓝牙是否开启、搜索蓝牙设备等。

  • 2.BluetoothDevice

蓝牙设备对象,包含一些蓝牙设备的属性,比如设备名称、mac地址等。

  • 3.BluetoothProfile

一个通用的蓝牙规范,设备之间按照这个规范来收发数据。

  • 4.BluetoothGatt

蓝牙通用属性协议,定义了BLE通讯的基本规则,是BluetoothProfile的实现类,Gatt是Generic Attribute Profile的缩写,用于连接设备、搜索服务等操作。

  • 5.BluetoothGattCallback

蓝牙设备连接成功后,用于回调一些操作的结果,必须连接成功后才会回调

  • 6.BluetoothGattService

蓝牙设备提供的服务,是蓝牙设备特征的集合。

  • 7.BluetoothGattCharacteristic

蓝牙设备特征,是构建GATT服务的基本数据单元。

  • 8.BluetoothGattDescriptor

蓝牙设备特征描述符,是对特征的额外描述。

我们可以将上述几个类的关系比喻为:BluetoothDevice为学校,BluetoothGatt为学校到达某一个班级的通道,BluetoothCattService为学校的某一个班级,BluetoothCattCharacteristic为班级中的某一个学生。那么蓝牙连接通信的过程就是这样,BluetoothAdapter先找到学校(就是连接目的设备),再通过通道找到目标班级,最后从班级中找到目标学生,这个学生就是我们设备之间通信的中介,很重要,学校有唯一的MAC地址,班级有唯一的serviceUUID,学生有唯一的charactersticUUID(相当于学号),所以就是在一所学校找一个学生的问题。

2. 操作流程

(1)打开所需要的权限

在 Android 6.0 及以上,还需要打开位置权限。如果应用没有位置权限,蓝牙扫描功能不可用,即扫描不到BLE设备,但其它蓝牙操作例如连接蓝牙设备和写入数据不受影响。

<!-- 通用蓝牙权限 -->
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>

<!-- 位置权限 -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>

(2)获取 BluetoothAdapter

BluetoothAdapter是任何和所有的蓝牙活动所必需的。 BluetoothAdapter代表设备自己的蓝牙适配器(蓝牙无线电)。整个系统有一个蓝牙适配器,您的应用程序可以使用这个对象与它进行交互。下面的代码展示了如何获取适配器。请注意,此方法使用getSystemService()返回 BluetoothManager 的实例,然后用于获取适配器。 Android 4.3(API Level 18)介绍了BluetoothManager:

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

(3)启动蓝牙

调用isEnabled()来检查当前是否启用了蓝牙。如果此方法返回false,则蓝牙被禁用。

以下片段检查是否启用了蓝牙。如果不是,该片段会显示一个错误,提示用户转到设置以启用蓝牙:

// 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 = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}

注意:
传递给 startActivityForResult(android.content.Intent,int)的REQUEST_ENABLE_BT常量是系统在onActivityResult(int,int,android.content)中返回给您的本地定义的整数(它必须大于0))实现作为requestCode参数。

(4)查找蓝牙设备

要查找BLE设备,请使用startLeScan()方法。 此方法将BluetoothAdapter.LeScanCallback作为参数。 您必须实现此BluetoothAdapter.LeScanCallback,因为这是如何返回扫描结果。 由于扫描耗电量大,您应遵守以下准则:

  • 一旦找到所需的设备,请停止扫描
  • 切勿扫描循环,并在扫描上设置时间限制。以前可用的设备可能已移出范围,并继续扫描电池电量。

以下片段显示了如何启动停止扫描:

/**
 * 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);
        }
        ...
    }
...
}

如果只想扫描特定类型的外设,则改为调用startLeScan(UUID [],BluetoothAdapter.LeScanCallback),提供指定您的应用程序支持的GATT服务的UUID对象数组。

以下是BluetoothAdapter.LeScanCallback的一个实现,它是用于传递BLE扫描结果的接口:

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();
           }
       });
   }
};

(5)读取设备BLE信息

一旦你的Android应用程序连接到GATT服务器并发现服务,它就可以在支持的地方读取和写入属性。例如,这个代码片段遍历服务器的服务和特性,并在UI中显示它们:

public class DeviceControlActivity extends Activity {
    ...
    // (演示)Demonstrates how to (遍历)iterate through the supported GATT
    // Services/Characteristics.
    // In this sample, we (填充)populate the data structure that is bound to the
    // ExpandableListView on the UI.
    private void displayGattServices(List<BluetoothGattService> gattServices) {
        if (gattServices == null) return;
        String uuid = null;
        String unknownServiceString = getResources().
                getString(R.string.unknown_service);
        String unknownCharaString = getResources().
                getString(R.string.unknown_characteristic);
        ArrayList<HashMap<String, String>> gattServiceData =
                new ArrayList<HashMap<String, String>>();
        ArrayList<ArrayList<HashMap<String, String>>> gattCharacteristicData
                = new ArrayList<ArrayList<HashMap<String, String>>>();
        mGattCharacteristics =
                new ArrayList<ArrayList<BluetoothGattCharacteristic>>();

        // Loops through available GATT Services.
        for (BluetoothGattService gattService : gattServices) {
            HashMap<String, String> currentServiceData =
                    new HashMap<String, String>();
            uuid = gattService.getUuid().toString();
            currentServiceData.put(
                    LIST_NAME, SampleGattAttributes.
                            lookup(uuid, unknownServiceString));
            currentServiceData.put(LIST_UUID, uuid);
            gattServiceData.add(currentServiceData);

            ArrayList<HashMap<String, String>> gattCharacteristicGroupData =
                    new ArrayList<HashMap<String, String>>();
            List<BluetoothGattCharacteristic> gattCharacteristics =
                    gattService.getCharacteristics();
            ArrayList<BluetoothGattCharacteristic> charas =
                    new ArrayList<BluetoothGattCharacteristic>();
           // Loops through available Characteristics.
            for (BluetoothGattCharacteristic gattCharacteristic :
                    gattCharacteristics) {
                charas.add(gattCharacteristic);
                HashMap<String, String> currentCharaData =
                        new HashMap<String, String>();
                uuid = gattCharacteristic.getUuid().toString();
                currentCharaData.put(
                        LIST_NAME, SampleGattAttributes.lookup(uuid,
                                unknownCharaString));
                currentCharaData.put(LIST_UUID, uuid);
                gattCharacteristicGroupData.add(currentCharaData);
            }
            mGattCharacteristics.add(charas);
            gattCharacteristicData.add(gattCharacteristicGroupData);
         }
    ...
    }
...
}

(6)接受GATT通知

BLE应用程序在设备上发生特定特征变化时要求收到通知是很常见的。这段代码展示了如何使用setCharacteristicNotification()方法为特性设置通知

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

一旦为特征启用了通知,如果特性在远程设备上发生变化,则会触发onCharacteristicChanged()回调:

@Override
// Characteristic notification
public void onCharacteristicChanged(BluetoothGatt gatt,
        BluetoothGattCharacteristic characteristic) {
    broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
}

(7)关闭蓝牙

一旦您的应用程序使用BLE设备,应该调用close(),以便系统可以正确释放资源:

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

 

项目推荐:

2000多G的计算机各行业电子资源分享(持续更新)

2020年微信小程序全栈项目之喵喵交友【附课件和源码】

Spring Boot开发小而美的个人博客【附课件和源码】

Java微服务实战296集大型视频-谷粒商城【附代码和课件】

Java开发微服务畅购商城实战【全357集大项目】-附代码和课件

最全最详细数据结构与算法视频-【附课件和源码】

在这里插入图片描述

 

 

 

 

 

 

 

 

 

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐