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

在一篇文章《Android Things 教程:与低功耗蓝牙 BLE 设备进行通讯 —— Raspberry Pi 部分》 中,我们介绍了如何在 Android Things 和 Raspberry Pi 上创建 BLE 服务。在这篇文章中,我们将介绍如何编写低功耗蓝牙的客户端应用,以便于和我们的 Raspberry Pi 部分进行通讯。

我们测试了我们的 Android Things 应用程序,它的效果很好。现在,我们必须创建一个移动客户端,它可以连接到广播设备,并对特征进行读/写。

创建客户端

扫描 BLE 设备

当 Android 客户端应用程序启动时,首先应该扫描可用的 BLE 设备。

扫描 BLE 设备可能相当复杂,因为 SDK 21 中的初始 API(SDK 18)已更改,并且在 SDK 23 中进行了扩展(来源:开发 BLE Android 应用程序时要记住的内容)。 为了简化,我们将使用统一的第三方兼容库:Android BLE Scanner Compat 库。

BluetoothLeScannerCompat scanner = BluetoothLeScannerCompat.getScanner();

// We want to receive a list of found devices every second
ScanSettings settings = new ScanSettings.Builder()
  .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
  .setReportDelay(1000)
  .build();

// We only want to scan for devices advertising our custom service  
ScanFilter scanFilter = new ScanFilter.Builder()
  .setServiceUuid(new ParcelUuid(SERVICE_UUID)).build();
scanner.startScan(Arrays.asList(scanFilter), settings, mScanCallback);

再次提一下,扫描是电池密集型的。因此我们应该创建一个处理程序,在几秒钟后(例如 10 秒)停止扫描,并在找到所需设备后立即停止扫描。

startScan 方法采用下面实现的 ScanCallback

private final ScanCallback mScanCallback = new ScanCallback() {
  @Override
  public void onScanResult(int callbackType, ScanResult result) {
    // We scan with report delay > 0. This will never be called.
  }

  @Override
  public void onBatchScanResults(List<scanresult> results) {
    if (!results.isEmpty()) {
      ScanResult result = results.get(0);
      BluetoothDevice device = result.getDevice();
      String deviceAddress = device.getAddress();
      // Device detected, we can automatically connect to it and stop the scan
    }
  }

  @Override
  public void onScanFailed(int errorCode) {
    // Scan error
  }
};

onBatchScanResults 将定期返回检测到的设备列表,具体取决于您之前指定的 setReportDelay 值。您可以将这些设备添加到列表中,以便用户可以选择要连接的设备。 在我们的情况下,我们知道只有一个设备广播我们的自定义服务,所以一旦检测到,我们会自动连接到它。

连接 GATT 服务器

要连接到GATT服务器,请使用设备地址获取 BluetoothDevice 的实例,然后调用 connectGatt 方法:

BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(deviceAddress);
mGatt = device.connectGatt(mContext, false, mGattCallback);

这个方法需要一个 BluetoothGattCallback,与我们前面看到的 BluetoothGattServerCallback 非常相似,当特征/描述符已经被读/写时,包含回调方法。

  • 警告 - 回调地狱传入 - 警告 -

做好准备!你会看到一连串的回调。每个操作都有一个关联的回调。我们不能执行两个蓝牙操作,例如在同一时间两个写操作。我们将不得不等待一个完成,然后才能开始下一个。

发现服务

当 GATT 连接成功时,onConnectionStateChange 将被调用。

设备连接成功后,您可以从这里开始发现服务:

@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
  if (newState == BluetoothProfile.STATE_CONNECTED) {
    Log.i(TAG, "Connected to GATT client. Attempting to start service discovery");
    gatt.discoverServices();
  } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
    Log.i(TAG, "Disconnected from GATT client");
  }
}

启用计数器特性的通知

然后,discoverServices 方法将调用您必须覆写的 onServicesDiscovered 方法。我们的客户端希望得到每个 CHARACTERISTIC_COUNTER_UUID 更改的通知,所以这是开始写入描述符的正确位置:

@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
  if (status != BluetoothGatt.GATT_SUCCESS) {
    // Handle the error
    return;
  }

  // Get the counter characteristic
  BluetoothGattCharacteristic characteristic = gatt
    .getService(SERVICE_UUID)
    .getCharacteristic(CHARACTERISTIC_COUNTER_UUID);

  // Enable notifications for this characteristic locally
  gatt.setCharacteristicNotification(characteristic, true);

  // Write on the config descriptor to be notified when the value changes
  BluetoothGattDescriptor descriptor =
    characteristic.getDescriptor(DESCRIPTOR_CONFIG_UUID);
  descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
  gatt.writeDescriptor(descriptor);
}

读取计数器的值

现在,我们要读取计数器值。 我们不能在 onServicesDiscovered 方法中完成,因为已经有一个描述符的挂起写操作。所以,我们将重写 onDescriptorWrite,并在描述符写完后开始读取我们的特性:

@Override
public void onDescriptorWrite(BluetoothGatt gatt,
    BluetoothGattDescriptor descriptor, int status) {
  if (DESCRIPTOR_CONFIG_UUID.equals(descriptor.getUuid())) {
    BluetoothGattCharacteristic characteristic = gatt
      .getService(SERVICE_UUID)
      .getCharacteristic(CHARACTERISTIC_COUNTER_UUID);
    gatt.readCharacteristic(characteristic);
  }
}

当计数器值被提取时更新UI

读取特征时,将调用 onCharacteristicRead 方法。我们可以在这里收到特征值,并更新 UI:

@Override
public void onCharacteristicRead(BluetoothGatt gatt,
    BluetoothGattCharacteristic characteristic, int status) {
  readCounterCharacteristic(characteristic);
}

private void readCounterCharacteristic(BluetoothGattCharacteristic
    characteristic) {
  if (CHARACTERISTIC_COUNTER_UUID.equals(characteristic.getUuid())) {
    byte[] data = characteristic.getValue();
    int value = Ints.fromByteArray(data);
    // Update UI
  }
}

当值改变时得到通知

最后,由于我们现在通知特征值自动变化时,我们可以重写 onCharacteristicChanged 再次读取计数器值并更新 UI

@Override
public void onCharacteristicChanged(BluetoothGatt gatt,
    BluetoothGattCharacteristic characteristic) {
  readCounterCharacteristic(characteristic);
}

而且...我们完成了!

那么,不是完整的...我们已经完成了 BLE 部分,但是还没有物理计数器,也没有幸运的猫。

增加一些乐趣(也就是创建物理对象)

在这一刻,如果我告诉你,我们写的所有东西只有当客户端发送一个写请求时才有一个自动递增的变量,那么你可能会这样说:“所有这些什么都没有看到!”。感谢 Android Things,我们会添加一些乐趣,并围绕这个软件创建一个物理对象。

一个物理计数器设备

这里使用的显示器是 “MAX7219 8位模块”。

MAX7219

顾名思义,它使用 MAX7219 来控制段显示。

在之前的文章中,我们已经提到 MAX7219 控制 8x8 LED 矩阵。好的,您可以使用相同的驱动程序(LED控制库)通过 SPI 与此设备进行交互。

例如:显示 “0042”:

LedControl ledControl = new LedControl(SPI_NAME);
ledControl.setDigit(3, 0, false);
ledControl.setDigit(2, 0, false);
ledControl.setDigit(1, 4, false);
ledControl.setDigit(0, 2, false);

一只幸运猫

我们将使用一只幸运的猫。

要移动猫的爪子,我们只需要将爪子连接到伺服电机(在这里,使用的是橡皮筋)。

PWM

然后,当计数器值改变时,我们移动伺服器:

Servo servo = new Servo(PWM_NAME);
servo.setPulseDurationRange(1, 2);
servo.setAngleRange(-90, 90);
servo.setEnabled(true);

servo.setAngle(servo.getMaximumAngle());
Thread.sleep(1000);
servo.setAngle(servo.getMinimumAngle());

Android Wear 应用

这不是最初计划的,但是因为它使用完全相同的 API,所以只需要 10 分钟就可以将 Android(电话)客户端,移植到 Android Wear 应用程序中(我的意思是复制/粘贴)。

结论

首先,通过蓝牙进行通信似乎相当复杂。您需要编写大量的代码,才能通过蓝牙低功耗进行通信,但是一旦您了解了它的工作原理,就会发现它确实非常冗长,但仍然简单明了,与 Bluetooth LE 规范完全一致。

为了简化文章,我没有处理边界情况(如检查返回值,如果尚未启用蓝牙,启用等)。

你可以在这里找到一个完整的实现:github.com/Nilhcem/blefun-androidthings

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

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

观光\评论区

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