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.