Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

characteristic for Notification (subscribeToCharacteristic) not working on iOS #95

Closed
andrewknockin opened this issue Aug 5, 2020 · 4 comments
Labels
iOS iOS-specific ticket

Comments

@andrewknockin
Copy link

I have flutter code (laone) using flutter_reactive_ble v2.4.0 which works for Android 9 but raises an exception for iOS. The code allows the selection of a single device from a list of found devices, then when Connect is clicked for that device, it should connect and send some initial data requests, which are then received using a subscribeToCharacteristic method and processed.
On iOS, the exception seems to be due to the subscribeToCharacteristic failing because no trace of the incoming replies is found.
Since the flutter code works on Android, and I can communicate with the device using the iOS app Blue Chat, which also uses a characteristic subscription, the remote server responses seem valid. In the server log, I can see the initial data requests (10 of them) being received and replied to, so the iOS connection is made and the transmit aspect of flutter_reactive_ble is working on iOS.
I have tried with the rxUuid in both long form and 32 bit short form, as suggested in #76.

The connection code is:

static Uuid myServiceId = Uuid.parse('6e400001-b5a3-f393-e0a9-e50e24dcca9e');
static List<Uuid> myServices = [myServiceId];
static Uuid txUuid = Uuid.parse('6e400002');
static Uuid rxUuid = Uuid.parse('6e400003');

connectDevice(DiscoveredDevice device) {
     _ble.connectToAdvertisingDevice(
          id: device.id,
          withServices: myServices,
          prescanDuration: const Duration(seconds: 1),
          servicesWithCharacteristicsToDiscover: {myServiceId: [txUuid, rxUuid]},
          connectionTimeout: const Duration(seconds:  2),
        ).listen((connectionState) {
          // Handle connection state updates
        }, onError: (dynamic error) {
          // Handle a possible error
        });
    rxCharacteristic = QualifiedCharacteristic(
        serviceId: myServiceId,
        characteristicId: rxUuid,
        deviceId: device.id);
    rxSubscription = _ble.subscribeToCharacteristic(rxCharacteristic).listen((data) {
        onDataReceived(data);
    };
    txCharacteristic = QualifiedCharacteristic(
        serviceId: myServiceId, characteristicId: txUuid, deviceId: device.id);
    sendInitialCommands();
    connectedDevice = device;
  }

For the working Android code, the console shows:

I/flutter (17626): connecting to 
I/flutter (17626): 0
I/flutter (17626): done
I/flutter (17626): disconnected device
I/flutter (17626): connecting to Zoom
I/flutter (17626): ====sendInitialCommands
I/flutter (17626): ====InitialCommands==Sent
D/BluetoothGatt(17626): connect() - device: A4:CF:12:78:10:3E, auto: true
D/BluetoothGatt(17626): registerApp()
D/BluetoothGatt(17626): registerApp() - UUID=1998bf31-c89d-4a37-b12e-f690c8d0334f
D/BluetoothGatt(17626): onClientRegistered() - status=0 clientIf=7
D/BluetoothGatt(17626): onClientConnectionState() - status=0 clientIf=7 device=A4:CF:12:78:10:3E
D/BluetoothGatt(17626): discoverServices() - device: A4:CF:12:78:10:3E
D/BluetoothGatt(17626): onConnectionUpdated() - Device=A4:CF:12:78:10:3E interval=6 latency=0 timeout=500 status=0
D/BluetoothGatt(17626): onSearchComplete() = Device=A4:CF:12:78:10:3E Status=0
D/BluetoothGatt(17626): onConnectionUpdated() - Device=A4:CF:12:78:10:3E interval=30 latency=0 timeout=500 status=0
D/BluetoothGatt(17626): setCharacteristicNotification() - uuid: 6e400003-b5a3-f393-e0a9-e50e24dcca9e enable: true
I/flutter (17626): Received: [y 10]
I/flutter (17626): Received: [x 8]
I/flutter (17626): Received: [f 2.0.0-lc-e58]
I/flutter (17626): ** ZoomController.onDevicePropertyChange **
I/flutter (17626): Setter for firmwareVersion invoked
I/flutter (17626): ** ZoomController.onDevicePropertyChange **
I/flutter (17626): Received firmware version: 2.0.0-lc-e58
I/flutter (17626): Received: [h 160]

where the I/flutter lines are debug messages and the lower set show the responses being received from the first 3 of the 10 commands, and then processed, with the final line showing the 4th response being received.
The iOS log shows the same first 7 log lines and then the exception. I do not recognise the Uuid it claims is unknown in the final log line.

flutter: connecting to
flutter: 0
flutter: done
flutter: disconnected device
flutter: connecting to Zoom
flutter: ====sendInitialCommands
flutter: ====InitialCommands==Sent
flutter: RX error: PlatformException(flutter_reactive_ble.Central.(unknown context at $100d47ef0).Failure:1, The operation couldn’t be completed. (flutter_reactive_ble.Central.(unknown context at $100d47ef0).Failure error 1.), {})
[VERBOSE-2:ui_dart_state.cc(157)] Unhandled Exception: Exception: GenericFailure<WriteCharacteristicFailure>(code: WriteCharacteristicFailure.unknown, message: "A peripheral 0E76782B-8B93-89F0-B597-8F2963FAE171 is unknown (make sure it has been discovered)")
#0      Result.dematerialize.<anonymous closure> (package:flutter_reactive_ble/src/model/result.dart:22:13)
#1      Result.iif (package:flutter_reactive_ble/src/model/result.dart:34:21)
#2      Result.dematerialize (package:flutter_reactive_ble/src/model/result.dart:15:28)
#3      ConnectedDeviceOperation.writeCharacteristicWithResponse.<anonymous closure> (package:flutter_reactive_ble/src/connected_device_operation.dart:34:39)
#4      _rootRunUnary (dart:async/zone.dart:1192:38)
#5      _CustomZone.runUnary (dart:async/zone.dart:1085:19)
#6      _FutureListener.handleValue (dart:async/future_impl.dart:141:18)
#7      Future._propagateToListeners.handleValueCallback (dart:async/future_impl.dart:682:45)
#8      Future._propagateToListeners (dart:async/future_impl.dart:711:32)
#9      Future._completeWithValue (dart:async/future_impl.dart:526:5)
#10     _AsyncAwaitCompleter.complete (dart:async-patch/async_patch.dart:36:15)
#11     _completeOnAsyncReturn (dart:async-patch/async_patch.dart:298:13)
#12     MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart)
<asynchronous suspension>
#13     MethodChannel.invokeMethod (package:flutter/src/services/platform_channel.dart:329:12)
#14     PluginController.writeCharacteristicWithResponse (package:flutter_reactive_ble/src/plugin_controller.dart:192:10)
#15     ConnectedDeviceOperation.writeCharacteristicWithResponse (package:flutter_reactive_ble/src/connected_device_operation.dart:33:12)
#16     FlutterReactiveBle.writeCharacteristicWithResponse (package:flutter_reactive_ble/src/reactive_ble.dart:170:37)
<asynchronous suspension>
#17     ZoomController.sendString (package:laone/controllers/ZoomController.dart:520:12)
#18     ZoomController.sendCmdGetSwitchQty (package:laone/controllers/ZoomController.dart:343:11)
#19     ZoomController.sendInitialCommands (package:laone/controllers/ZoomController.dart:476:5)
#20     ZoomController.configureDeviceSettings (package:laone/controllers/ZoomController.dart:468:10)
#21     ZoomController.connectDevice.<anonymous closure> (package:laone/controllers/ZoomController.dart:437:9)
#22     new Future.delayed.<anonymous closure> (dart:async/future.dart:318:39)
#23     _rootRun (dart:async/zone.dart:1180:38)
#24     _CustomZone.run (dart:async/zone.dart:1077:19)
#25     _CustomZone.runGuarded (dart:async/zone.dart:979:7)
#26     _CustomZone.bindCallbackGuarded.<anonymous closure> (dart:async/zone.dart:1019:23)
#27     _rootRun (dart:async/zone.dart:1184:13)
#28     _CustomZone.run (dart:async/zone.dart:1077:19)
#29     _CustomZone.bindCallback.<anonymous closure> (dart:async/zone.dart:1003:23)
#30     _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:168:12)
[VERBOSE-2:ui_dart_state.cc(157)] Unhandled Exception: Exception: GenericFailure<WriteCharacteristicFailure>(code: WriteCharacteristicFailure.unknown, message: "A peripheral 0E76782B-8B93-89F0-B597-8F2963FAE171 is unknown (make sure it has been discovered)")
@werediver werediver added the iOS iOS-specific ticket label Aug 6, 2020
@werediver
Copy link
Collaborator

Hi, @andrewknockin. Your ticket is pretty informative, but you could use the standard issue template to resolve some typical questions immediately.

Unfortunately, your connectDevice(·) method does not look correct: establishing a BLE connection to a device takes time (everything in BLE takes time, plus any interaction with the platform side of a Flutter plug-in is inherently asynchronous) and you are starting interacting with the device before the connection is established.

You should only start interacting with a device (subscribing to, reading from, writing to characteristics) after the stream returned by one of the functions establishing a connection reports "connected" status.

Apart from that, you may want to use connectToAdvertisingDevice(⋯) on Android only. On iOS it's just inefficient (see the doc-comments / docs). Your prescan duration (currently 1 second) appears to be pretty short. I guess, anything below somewhere about 2–3 seconds is on the short side (@remonh87 could you comment on the reasonable prescan duration and connection timeout values for Android?).

@remonh87
Copy link
Contributor

remonh87 commented Aug 6, 2020

@andrewknockin I agree with the remarks of @werediver .Some additions: I would advise to scan for around at least 2 seconds on Android since we re using an aggressive way of scanning this should be sufficient. If the user is in an area with a lot of bluetooth devices (e.g. lights, headset and laptops) it can take a bit longer before the device is discovered, that's why 1 second is not always sufficient. I also would increase the connection timeout to let's say 8 seconds. Some Android devices take some time to connect but feel free to use your own judgement there.

@andrewknockin
Copy link
Author

Thanks for the advice @werediver and @remonh87.
The short answer to my problem was that I was using _ble.writeCharacteristicWithoutResponse() which works for Android but fails for iOS. I changed to _ble.writeCharacteristicWithResponse() and that moved me forward, but I now have another issue which I'll put in a new thread.

I also extended the discovery times, and I have distinguished between Android and iOS using:
'''
if (Platform.isAndroid) { // uses dart.io
_ble.connectToAdvertisingDevice(
id: device.id,
withServices: myServices,
prescanDuration: const Duration(seconds: 5),
).listen((connectionStateUpdate) {
// Handle connection state updates
}, onError: (dynamic error) {
print("connectToAdvertisingDevice error: " + error.toString());
});
} else {
_ble.connectToDevice(
id: device.id,
servicesWithCharacteristicsToDiscover: {myService: [txUuid, rxUuid] },
connectionTimeout: const Duration(seconds: 5),
);
}
'''

@werediver
Copy link
Collaborator

werediver commented Aug 11, 2020

@andrewknockin writeCharacteristicWithoutResponse() should work on iOS very well, but with some minor differences between iOS and Android. You may notice the differences, if you are running many writes rapidly (tens to hundreds and more). In short, it's tricky to make such a streaming-like transfer using write-without-response work on both platform.

For relatively rare writes there must be no problems or differences.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
iOS iOS-specific ticket
Projects
None yet
Development

No branches or pull requests

3 participants