BLE订阅多特征

之前记录过很多Android和iOS的BLE的开发。不过他们都有一个共性,就是上位机都只订阅了一个特征值。

Context

这次,我们需要订阅两个特征了。

iOS

iOS是真的方便,直接订阅了就好了。

- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(nullable NSError *)error {
NSArray *chs = service.characteristics;
for (CBCharacteristic *ch in chs) {
    if ([ch.UUID.UUIDString isEqualToString:kCharacteristicControlPoint]) {
        ControlPointCharacteristic = ch;
        // 订阅它!
        [peripheral setNotifyValue:YES forCharacteristic:ControlPointCharacteristic];
    } else if ([ch.UUID.UUIDString isEqualToString:kCharacteristicDataReceiver]) {
        DataReceiverCharacteristic = ch;
    } else if ([ch.UUID.UUIDString isEqualToString:kCharacteristicDataSender]) {
        DataSenderCharacteristic = ch;
        // 订阅它!
        [peripheral setNotifyValue:YES forCharacteristic:DataSenderCharacteristic];
    }
    if (ControlPointCharacteristic != nil && DataSenderCharacteristic != nil && DataReceiverCharacteristic != nil) {
        //All set up
    }
}

我查到一个需要订阅的characteristic,直接执行订阅就可以了。非常的简单,没什么好说的~

Android

我以为,Android大概也是这么简单的吧~

@Override
    public void onServicesDiscovered(BluetoothGatt gatt, int status) {
        super.onServicesDiscovered(gatt, status);
BluetoothGattService service = gatt.getService(UUID.fromString(ServiceUUID));
    if (service != null) {
        fit_control = service.getCharacteristic(UUID.fromString(ControlPointCharacteristic));
        fit_receiver = service.getCharacteristic(UUID.fromString(DataReceiverCharacteristic));
        fit_sender = service.getCharacteristic(UUID.fromString(DataSenderCharacteristic));
        if (fit_control != null && fit_receiver != null && fit_sender != null) {
            // 订阅它!
            gatt.setCharacteristicNotification(fit_control, true);
            // 订阅它!
            gatt.setCharacteristicNotification(fit_sender, true);
        }
    }
}

结果,死都收不到fit_sender的数据,我甚至以为是硬件的问题了~

后来我想到了,N久以前看到过一个帖子,说是Android BLE开发的一些坑,里面提到了android的蓝牙底层是串行的,没有队列。一次只能做一件事情,多的事情,会被忽略。

所以我得等到一次订阅成功之后,再订阅下一个特征。然后我看到

gatt.setCharacteristicNotification()

这个方法是有返回值的,而且还是个boolean,说不定是成功或者失败的意思。官方的解释是true, if the requested notification status was set successfully,貌似正合我意。

看了一下他的实现源码,在这里有关registerForNotification()没有办法继续反编译了

try {
    mService.registerForNotification(mClientIf, device.getAddress(),
        characteristic.getInstanceId(), enable);
} catch (RemoteException e) {
    Log.e(TAG,"",e);
    return false;
}

所以我也不知道这个返回值有没有卵用~

可是仔细想了一下,这个方法既然不是同步的(执行在这里并不会卡住一段时间去订阅这个Characteristic)那就说明这个返回值,可能并没有乱用,说不定他只是去判断一下这个characteristic是否拥有notify的权限而已。

而且即便获取到了这个返回的boolean值,我又能怎么做呢?写个while(1)?

How does it subscribe

或许,我需要的,是一个回调方法吧~

那么,蓝牙到底是怎么做订阅这个动作的呢?

肯定不是本地订阅一下就完事儿了的,肯定会让remote知道,有个client订阅了自己!

所以,一定是要写入数据的!

于是,在Google很偏僻的文档里面,找到了如下的代码段:

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

是啊,除了本地要开始监听之外,还要写一个描述,告诉BLE设备,我开始订阅了,请开启Notification。

而,writeDescriptor()是由回调的!

public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status);

只要在回调函数里面再去订阅下一个characteristic,再在回调函数里面再再再去订阅下下个characteristic……就可以实现订阅很多很多很多的characteristic了!

Key

@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
    super.onServicesDiscovered(gatt, status);        ...
    gatt.setCharacteristicNotification(fit_control, true);
    List<BluetoothGattDescriptor> descriptorList = fit_control.getDescriptors();
    if (descriptorList != null && descriptorList.size() > 0) {
        for (BluetoothGattDescriptor descriptor : descriptorList) {
            descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
            gatt.writeDescriptor(descriptor);
        }
    }    
 }

 @Override
 public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
        super.onDescriptorWrite(gatt, descriptor, status);
        // 订阅下一个
 }

End

其实,还是蛮折腾的~以前没遇到过这个问题,而且android官方例程里面也没有说这个问题,所以还是花了很多时间。赶紧记录一下,免得以后再次遇到这样的坑儿。

不要以为这个例子就说明了Android比iOS开发难。明天我就写一篇,iOS上花了我差不多一天时间,而Android上半个小时就搞定的东西!