2
votes

I'm developing a ble-based native local multiplayer plugin for Unity (for both Android and iOS). I use a single service, with a single characteristic with rw permissions. I've managed to make Android<->Android and iOS<->iOS work all right, but I'm having a rough time trying to make Android<->iOS work. Specifically, it's the 'iOS as Peripheral, Android as Central' combination the one that keeps me up at night. After many hours of fiddling, testing, googling and trying, I have very much pinned down the problem to this:

From the Android side, if I don't subscribe to the characteristic, a call to BluetoothGatt#writeCharacteristic(characteristic), like this:

String str = "the data";

xferCharacteristic.setValue(str.getBytes("UTF-8"));
mGatt.writeCharacteristic(xferCharacteristic);

will return 'true' and succeed, and the peripheralManager:didReceiveWriteRequests: callback will be called on the iOS side where I can manipulate the precious received data as I see fit. So far so good. But, if I try to update a characteristic from the iOS end, the Android central won't get notified (the callback BluetoothGattCallback#onCharacteristicChanged should be called, but it isn't), since it did not subscribe to the characteristic.

If I make the Android central subscribe to the characteristic offered by the iOS peripheral, by means of this section of code:

First, connect to the iOS peripheral with

public void onScanResult(int callbackType, ScanResult result) {

        BluetoothDevice btDevice = result.getDevice();
        mGatt = device.connectGatt(appContext, false, mGattCallback);
        ...

with mGattCallback an instance of BLEGattCallback which will handle the onServicesDiscovered callback:

public class BLEGattCallback extends BluetoothGattCallback {

private static final UUID CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");

public void onServicesDiscovered(BluetoothGatt gatt, int status) {

    List<BluetoothGattService> services = gatt.getServices();

    for(BluetoothGattService s : services) { // foreach service...

        if(UUID.fromString(MyServiceUUID).equals(s.getUuid())) { // just the one I want...

            List<BluetoothGattCharacteristic> characteristics = s.getCharacteristics();
            for(BluetoothGattCharacteristic c : characteristics) { // foreach characteristic...

                if(c.getUuid().toString().equals(BLEManager.FGUUIDXferQueueString)) { // just the char. I want...

                    c.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);

                    for (BluetoothGattDescriptor descriptor : c.getDescriptors()) {

                        if(descriptor.getUuid().equals(CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID)) {

                            descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
                            gatt.writeDescriptor(descriptor);

                        }

                    }

                    gatt.setCharacteristicNotification(c, true);

                }
            }
        }
    }
}

This makes the Android central correctly subscribe for the characteristic (the callback method peripheralManager:central:didSubscribeToCharacteristic: is called on the iOS peripheral), BUT, if i do this, the call to mGatt.writeCharacteristic(xferCharacteristic) will return 'false' and no data will be written to the peripheral, so it's a kind of can-only-write or can-only-notify-update situation.

I have unsuccessfully tried to find out the meaning of writeCharacteristic returning 'false', to no avail (seriously, an error code would help a lot).

I've tried a lot of different combinations, values, etc... but, bottom line: as soon as I call gatt.writeDescriptor subsequent calls to writeCharacteristic will fail, and if I don't call gatt.writeDescriptor the android central won't subscribe.

I'm pretty much stuck here. Any help appreciated. Thanks a lot.

2

2 Answers

2
votes

Classic issue. You must wait for the operation to complete before you can issue another one. See Android BLE BluetoothGatt.writeDescriptor() return sometimes false.

0
votes

Thanks to the received hint, this issue has been solved. These are the changes I made to the code:

The Android client must wait for the writeDescriptor(...) request to finish before issuing a writeCharacteristic(...) command. For that, I had to @Override the method onDescriptorWrite on my BLEGattCallback class, which will be called when the writeDescriptor operation completes. I moved my first writeCharacteristic(...) call here, and now the information is sent to the iOS endpoint (the rest must be flow-controlled). So I'm very happy.