Android Things 教程上:与低功耗蓝牙 BLE 设备进行通讯 —— Raspberry Pi 部分

Android Things 支持蓝牙和蓝牙低功耗 API。在这篇博文中,我们将使用 Bluetooth LE API 在服务器(Android Things 开发板)和客户端(手机/手表上的 Android 应用程序)之间进行通信。

我们将为我们的真棒(awesomeness)建立一个计数器:每当你感觉真棒(awesomeness,无论什么原因),按一下你的移动设备上的按钮。 一只幸运的猫会移动它的爪子,并增加你的真棒(awesomeness)计数器。

理解低功耗蓝牙 BLE

BLE基于一个名为 “通用属性概要文件”(General ATTribute profile,GATT)的规范,该规范定义了如何在服务器和客户端之间传输和接收被称为 “属性” (attributes)的短小数据。

它提到了“概况”(profiles),“服务”(services),“特征” (characteristics) 和 “描述” (descriptors)等概念。

一个配置文件是(一或多个)服务的集合。每个服务(可以被认为是一个行为)可以包含一个或多个封装数据的特征(characteristics)。

GATT 结构图

为了确保互操作性,Bluetooth SIG(特殊兴趣小组)已经预定义了多个配置文件和服务。想象一下,我们要创建我们自己的键盘设备。 为确保兼容性,我们将不得不通过 GATT 配置文件遵循 HID(人机界面设备):

HID 设备

配置文件主要是一个规范,告诉我们我们将不得不实施哪些服务。要创建我们的自定义键盘,我们将必须实施 3 个强制性服务(HID、电池、设备信息)以及可选的扫描参数服务。

如果我们看过电池服务,它暴露了设备内的电池状态,我们可以看到它嵌入了一个名为电池电量的单一的强制只读特性。这个特性封装了一个介于 0 和 100 之间的 int 值,代表了设备电池的百分比。它也有一个可选的 “通知” (Notify)属性,这意味着客户可以订阅它,当值改变时自动通知。

电池级别

在 Android 上开始使用 BLE

官方文档 是在 Android 上开始使用蓝牙低功耗的最佳方式。

Google 还提供了 2 个示例项目:

部署这两个项目之后,您将能够扫描 Android Things GATT 服务:

示例 BLE

服务和特征由 UUID 唯一标识。这里,Raspberry Pi 3 公开了 3 个服务:通用属性(0x1801),通用访问(0x1800)和当前时间服务(0x1805)。后者有两个特征:当前时间(0x2A2B)和本地时间信息(0x2A0F)

关于自定义GATT服务/特性, 虽然按照蓝牙技术联盟的定义实施服务是推荐的方式,但也可以创建自己的专有服务(我们将在今天这样做)。在某些情况下,这可能是首选的解决方案,但是您将不具有互操作性的好处。

您应该为您的非标准服务和特性使用 128 位随机 UUID。短的 16 位 UUID 仅用于由蓝牙标准定义的服务/特性。

创建服务器

现在我们已经熟悉 BLE 关键概念,我们可以开始实施我们的 GATT 服务器。

我们的 Android Things 项目将公开一个具有两个特征的服务:

  • AwesomenessCounter:一个只读的,可通知的属性,表示你到目前为止真棒(Awesomeness)的次数
  • AwesomenessInteractor:当一个客户端为这个特性写一个值时,该设备应该移动猫的爪子并且增加真棒(Awesomeness)计数器。

下面的代码受到 sample-bluetooth-le-gattserver 的启发。如果你需要创建一个 GATT 服务器,你可以使用这个项目作为参考,或者按照官方文档。

常量

我们将定义以下常量

SERVICE_UUID = UUID.fromString("795090c7-420d-4048-a24e-18e60180e23c");
CHARACTERISTIC_COUNTER_UUID = UUID.fromString("31517c58-66bf-470c-b662-e352a6c80cba");
CHARACTERISTIC_INTERACTOR_UUID = UUID.fromString("0b89d2d4-0ea6-4141-86bb-0c5fb91ab14a");

DESCRIPTOR_CONFIG_UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");

如前所述,服务和特征由 UUID 唯一标识。由于我们没有实施标准服务,我们使用随机生成的值。

另外请注意 DESCRIPTOR_CONFIG_UUID 常量:每个特征都有价。如果我们以“电池电量”特性为例,它保持从 0 到 100 的值。特性也可以包含一些描述符。描述符定义元数据,如描述和展示信息。

GATT 描述符的一些示例:

  • 特征用户描述 Characteristic User Description(0x2901):提供特征值的文本用户描述。
  • 有效范围 Valid Range(0x2906):定义特征的范围。
  • 客户端特性配置 Client Characteristic Configuration(0x2902):定义特性如何由特定客户端配置。

如果一个客户想订阅一个特性,所以当一个值发生变化的时候它可以被自动地通知,它应该在 “客户特性配置” (Client Characteristic Configuration)描述符上执行一个写操作来通知它的意图。

在这里,我们定义一个 DESCRIPTOR_CONFIG_UUID,以便客户端可以订阅 CHARACTERISTIC_COUNTER_UUID 的值。

AndroidManifest.xml

首先,我们声明 BLUETOOTHBLUETOOTH_ADMIN 权限。BLUETOOTH_ADMIN 需要启动发现,或自动启用设备上的蓝牙。

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

我们还指定我们的应用程序需要使用 bluetooth_le 功能。如果低功耗蓝牙是您的应用程序的可选功能,请将此功能设置为 android:required="false

开始广播(advertising)

当 Android Things 程序启动时,它应该开始广播(advertising),以便其他设备可以看到它公开哪些 BLE 服务,并可以连接到它。

// The BluetoothAdapter is required for any and all Bluetooth activity.
mBluetoothManager = (BluetoothManager) getSystemService(BLUETOOTH_SERVICE);
BluetoothAdapter bluetoothAdapter = mBluetoothManager.getAdapter();

// Some advertising settings. We don't set an advertising timeout
// since our device is always connected to AC power.
AdvertiseSettings settings = new AdvertiseSettings.Builder()
        .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_BALANCED)
        .setConnectable(true)
        .setTimeout(0)
        .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM)
        .build();

// Defines which service to advertise.
AdvertiseData data = new AdvertiseData.Builder()
        .setIncludeDeviceName(true)
        .setIncludeTxPowerLevel(false)
        .addServiceUuid(new ParcelUuid(SERVICE_ID))
        .build();

// Starts advertising.
mBluetoothLeAdvertiser = bluetoothAdapter.getBluetoothLeAdvertiser();
mBluetoothLeAdvertiser.startAdvertising(settings, data, mAdvertiseCallback);

广播是电池密集型的。在这里,我们的设备总是连接到交流电源,所以它会连续广播。如果它使用电池供电,一个好主意是添加一个超时和一个物理按钮来开始广播过程。 此外,您需要在客户端连接后停止广播。

startAdvertising 方法需要一个 AdvertiseCallback 实例,定义如下:

private AdvertiseCallback mAdvertiseCallback = new AdvertiseCallback() {
    @Override
    public void onStartSuccess(AdvertiseSettings settingsInEffect) {
        Log.i(TAG, "LE Advertise Started.");
    }

    @Override
    public void onStartFailure(int errorCode) {
        Log.w(TAG, "LE Advertise Failed: " + errorCode);
    }
};

创建 GATT 服务

我们必须以编程的方式定义我们的 GATT 服务。请记住,我们的服务应该包含两个特点:

  • 计数器(只读,通过配置描述符支持订阅)
  • 交互器(只写)
private BluetoothGattService createService() {
  BluetoothGattService service = new BluetoothGattService(SERVICE_UUID, SERVICE_TYPE_PRIMARY);

  // Counter characteristic (read-only, supports subscriptions)
  BluetoothGattCharacteristic counter = new BluetoothGattCharacteristic(CHARACTERISTIC_COUNTER_UUID, PROPERTY_READ | PROPERTY_NOTIFY, PERMISSION_READ);
  BluetoothGattDescriptor counterConfig = new BluetoothGattDescriptor(DESCRIPTOR_CONFIG_UUID, PERMISSION_READ | PERMISSION_WRITE);
  counter.addDescriptor(counterConfig);

  // Interactor characteristic
  BluetoothGattCharacteristic interactor = new BluetoothGattCharacteristic(CHARACTERISTIC_INTERACTOR_UUID, PROPERTY_WRITE_NO_RESPONSE, PERMISSION_WRITE);

  service.addCharacteristic(counter);
  service.addCharacteristic(interactor);
  return service;
}

启动服务器

然后,我们用 openGattServer 方法启动低功耗蓝牙服务器。

mGattServer = mBluetoothManager.openGattServer(mContext, mGattServerCallback);
mGattServer.addService(createService());

此方法采用 BluetoothGattServerCallback 实例,该实例包含在读取或写入特征/描述符时实现的回调。

返回计数器值

当 GATT 客户端读取 CHARACTERISTIC_COUNTER_UUID 时,我们应该返回计数器的值。为此,我们重写我们的 BluetoothGattServerCallbackonCharacteristicReadRequest 方法,如果在计数器特性上有读取请求,则返回 currentCounterValue

@Override
public void onCharacteristicReadRequest(BluetoothDevice device,
    int requestId, int offset, BluetoothGattCharacteristic characteristic) {
  if (CHARACTERISTIC_COUNTER_UUID.equals(characteristic.getUuid())) {
    byte[] value = Ints.toByteArray(currentCounterValue);
    mGattServer.sendResponse(device, requestId, GATT_SUCCESS, 0, value);
  }
}

添加计数器

当 GATT 客户端写入 CHARACTERISTIC_INTERACTOR_UUID时,我们应该增加计数器的值。为此,我们可以覆写 onCharacteristicWriteRequest 方法:

@Override
public void onCharacteristicWriteRequest(BluetoothDevice device,
    int requestId, BluetoothGattCharacteristic characteristic,
    boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
  if (CHARACTERISTIC_INTERACTOR_UUID.equals(characteristic.getUuid())) {
    currentCounterValue++;
    notifyRegisteredDevices();
  }
}

请注意 notifyRegisteredDevices 被调用了。

由于计数器值已经改变,我们应该通知设备。 稍后我们将看到实现,但首先,我们来处理订阅。

处理通知

如果客户想要得到计数器特征值的任何变化的通知,就应该把它的意图写在一个配置描述符上。

我们重写 onDescriptorWriteRequest,并在名为 mRegisteredDevices 的私有列表中保留蓝牙设备的引用:

@Override
public void onDescriptorWriteRequest(BluetoothDevice device,
    int requestId, BluetoothGattDescriptor descriptor,
    boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
  if (DESCRIPTOR_CONFIG_UUID.equals(descriptor.getUuid())) {
    if (Arrays.equals(ENABLE_NOTIFICATION_VALUE, value)) {
      mRegisteredDevices.add(device);
    } else if (Arrays.equals(DISABLE_NOTIFICATION_VALUE, value)) {
      mRegisteredDevices.remove(device);
    }

    if (responseNeeded) {
      mGattServer.sendResponse(device, requestId, GATT_SUCCESS, 0, null);
    }
  }
}

现在,我们可以创建我们的 notifyRegisteredDevices 方法,为每个订阅的设备简单地调用 notifyCharacteristicChanged

private void notifyRegisteredDevices() {
  BluetoothGattCharacteristic characteristic = mGattServer
    .getService(SERVICE_UUID)
    .getCharacteristic(CHARACTERISTIC_COUNTER_UUID);

  for (BluetoothDevice device : mRegisteredDevices) {
    byte[] value = Ints.toByteArray(currentCounterValue);
    counterCharacteristic.setValue(value);
    mGattServer.notifyCharacteristicChanged(device, characteristic, false);
  }
}

测试 GATT 服务器

我们已经完成了我们的 GATT 服务器的编写。在开始创建客户端之前,我们将首先测试服务器,以确保我们已经正确实施了所有功能。

要测试低功耗蓝牙设备,您可以使用 nRF Connect for Mobile 应用程序。此应用程序允许您扫描蓝牙低功耗设备,并让您读取、写入、订阅特征。

启动应用程序后,我们可以看到 RPI3 是广播。 一旦我们连接到它,我们可以看到我们的自定义服务(UUID = 7950 ...)

测试 RPI 广播

使用这个应用程序,我们可以浏览给定服务的所有特征。

我们可以点击阅读按钮(蓝色)来读取计数器特性的值(这里的值等于0x2A [42])。当计数器特性发生变化时,我们也可以得到通知,写在交互器特性上,并看到自动递增的值。

计数

英语原文链接:http://nilhcem.com/android-things/bluetooth-low-energy

尚未评分
您的评分将帮助我们做出更好的玩法

观光\评论区

Copyright © 2017 玩点什么. All Rights Reserved.