flutter_reactive_ble icon indicating copy to clipboard operation
flutter_reactive_ble copied to clipboard

Issue with subscribeToCharacteristic on iOS

Open brucewampler opened this issue 3 years ago • 12 comments

Describe the bug My app needs to subscribeToCharacteristic to two different characteristics on the same service.

This works just fine on Android, but fails on iOS

To Reproduce Steps to reproduce the behavior:

  1. Subscribe to two different characteristics of same service.
  2. Issue device dependent writeCharacteristicWithResponse to the device which tells it to send the relevant data for the two charadcteristics
  3. Do this on Android - works fine. Do this on iOS will send the data from the first characteristic to both subscriptions, does not send the characteristics from the 2nd set to the correct subscription.

If I reverse the order of the subscription/and reading the sent characteristic data, the symptoms reverse.

Expected behavior The data sent from different characteristics should go to the proper subscription listener.

Smartphone / tablet

Fails on

  • Device: [iPhone 13 Pro]
  • OS: [e.g. iOS 15, Android 10]
  • Package version: [current version]

Peripheral device

  • In house device that has two sets of characteristics that are read/written by our app.

Additional context These operation are not done until after the device is connected

brucewampler avatar Mar 01 '22 23:03 brucewampler

There is a difference in the way that the Android and iOS versions behave, and it does seem to be a timing issue.

I have found a workaround that seems acceptable.

In my use case, after subscribing to a characteristic, the app then issues a command to the device that tells it to send a sequence of notifications to the app. Apparently the bug involves the iOS Reactive BLE code not being able to switch to the other listener if the notifications are piling up.

So my fix was to wait until all the initial notifications from one of the subscribes had been issues, and only then issue the command to send the notifications for the second set of subscribed notifications.

I suppose a timing problem like this will be a fairly rare use case, and also pretty hard for anyone else to duplicate. But I hope that this workaround may help someone else if that have issues with multiple subscription listeners.

brucewampler avatar Mar 02 '22 16:03 brucewampler

Thanks for sharing this information may actually be very valuable . @werediver I know from Android that parallel BLE operations are a bad practice and can yield into weird behavior. What are your observations on iOS since my knowledge is too basic to give a good judgement. If so I think the practice above can be very useful to highlight a bit more

remonh87 avatar Mar 06 '22 16:03 remonh87

I know from Android that parallel BLE operations are a bad practice and can yield into weird behavior.

I'd rather call them "concurrent", not parallel. True for Android.

What are your observations on iOS

iOS is excellent at handling concurrent BLE operations, no problems whatsoever (at the native API level). But for a cross-platform application one may prefer to stick to a safer approach that reliably works on both platforms.

Philips Hue Bluetooth app is currently at flutter_reactive_ble 5.0.0. It's using quite a number of subscriptions. That doesn't guarantee there is no bugs in the library, but it does guarantee it is possible to work with multiple subscriptions per device, with multiple devices simultaneously.

Something that I'd recommend to check and accept as a rule of thumb: await on every BLE API call that returns a future (especially in a sequence of calls), unless you have a good reason to do otherwise, understand implications, and have tested that code on multiple Android devices and an iOS device.

werediver avatar Mar 07 '22 13:03 werediver

I can confirm this behaviour. I have tested this with the Cycling Power Service subscrbing to characeristics 2A64 and 2A63.

The issue can be easily repliacted by using the free app lightblue and creating a a virtual peripheral device which emulates a cycling power meter.

image

@brucewampler Can you post some code of workaround. I seem not be able to get a workaround working.

void connectSensor(List<Function> unpackData) async {
    _deviceConnection.cancel();
    // Let's listen to our connection so we can make updates on a state change
    _deviceConnectionStream = flutterReactiveBle.connectToAdvertisingDevice(
        id: _discoveredDevice.id,
        prescanDuration: const Duration(seconds: 1),
        withServices: [bleService]).listen((event) {
      switch (event.connectionState) {
        // Successfull connection
        case DeviceConnectionState.connected:
          {
            // Loop through the list of characteristics to subscribe to
            int i = 0;
            for (var element in bleCharaceristics) {
              print("Subscribing to Characteristic $element");
              // Define Characteristic and subscribe to it
              QualifiedCharacteristic deviceMeasurementCharacteristic =
                  QualifiedCharacteristic(
                      serviceId: bleService,
                      characteristicId: element,
                      deviceId: event.deviceId);
              Stream<List<int>> sensorReceivedDataStream = flutterReactiveBle
                  .subscribeToCharacteristic(deviceMeasurementCharacteristic);
              // Add handler for onData received event
              _deviceReceivedDataStreamSubscriptions
                  .add(sensorReceivedDataStream.listen((data) {
                print(data);
                unpackData[i](data);
                i = i++;
              }, onError: (dynamic error) {
                //_logTexts = "${_logTexts}Error:$error$id\n";
              }));
            }
            _connected = true;
            _scan = false;
            break;
          }
        // Can add various state state updates on disconnect
        case DeviceConnectionState.disconnected:
          {
            _connected = false;
            break;
          }
        default:
          _connected = false;
          break;
      }
    });
 }

ElreboCM avatar Sep 25 '22 07:09 ElreboCM

On Sep 25, 2022, at 1:48 AM, ElreboCM @.***> wrote:

I can confirm this behaviour. I have tested this with the Cycling Power Service https://www.bluetooth.com/specifications/specs/cycling-power-service-1-1/ subscrbing to characeristics 2A64 and 2A63.

@brucewampler https://github.com/brucewampler Can you post some code of workaround. I seem not be able to get a workaround working.

— Reply to this email directly, view it on GitHub https://github.com/PhilipsHue/flutter_reactive_ble/issues/526#issuecomment-1257141027, or unsubscribe https://github.com/notifications/unsubscribe-auth/AWGWAOGTA2IDST6YUJHYX6DV777UHANCNFSM5PVOT6ZA. You are receiving this because you were mentioned.

I can't help much more. My earlier reply tried to convey my solution. But looking at my code now, it is difficult to provide actual code.

My situation was a bit bluetooth device dependent. The device itself can send two different sets of data at startup up which depend on its hardware state.

So, what my code does is:

  1. Wait for connection to device.
  2. Subscribe to the more "constant" type of data (like serial number).
  3. Set up a listener and read all that data.
  4. When the first set of data has been received, then subscribe to second set of data.
  5. Only now set up the listener for that second set of data, and read it all.

And all of this interacts with the actual app screens that display those values.


Bruce Wampler, Ph.D.

Software developer Creator of first spelling checker for a PC Creator of Grammatik(tm), first true grammar checker e-mail: bw at brucewampler.com

brucewampler avatar Sep 28 '22 17:09 brucewampler

Thanks for the reply. As you mention, this is device dependent. It won´t help in my case.

Other people have also identfied this bug: #598.

ElreboCM avatar Sep 29 '22 10:09 ElreboCM

I have started to investigate the issue a bit more.

While debugging the iOS / swift part of the library, I can confirm that the notification is successfully setup. Within the iOS part, the notification for both characteristics shows up. Somehow, the wrong handler seems to be called on the flutter side of the plugin.

ElreboCM avatar Oct 23 '22 19:10 ElreboCM

If I understand correctly, this method in Central.swift is being called when the characteristic is updated:

    onCharacteristicValueUpdate: papply(weak: self) { central, characteristic, error in
                onCharacteristicValueUpdate(central, QualifiedCharacteristic(characteristic), characteristic.value, error)
            }

Unfortunately, there is no documentation on what papply does. It would be great if some of the main contributors could comment. @remonh87 Can you help here?

ElreboCM avatar Oct 23 '22 20:10 ElreboCM

I have done some more research on this. It could be a deeper issue within flutter event channel and iOS. See this thread on Stack overflow.

From what I can tell only one is setup for different characteristic subscriptions.

`ReactiveBleMobilePlatform create() { const _bleMethodChannel = MethodChannel("flutter_reactive_ble_method");

const connectedDeviceChannel =
    EventChannel("flutter_reactive_ble_connected_device");
const charEventChannel = EventChannel("flutter_reactive_ble_char_update");
const scanEventChannel = EventChannel("flutter_reactive_ble_scan");
const bleStatusChannel = EventChannel("flutter_reactive_ble_status");

return ReactiveBleMobilePlatform(
  protobufConverter: const ProtobufConverterImpl(),
  argsToProtobufConverter: const ArgsToProtobufConverterImpl(),
  bleMethodChannel: _bleMethodChannel,
  connectedDeviceChannel:
      connectedDeviceChannel.receiveBroadcastStream().cast<List<int>>(),
  charUpdateChannel:
      charEventChannel.receiveBroadcastStream().cast<List<int>>(),
  bleDeviceScanChannel:
      scanEventChannel.receiveBroadcastStream().cast<List<int>>(),
  bleStatusChannel:
      bleStatusChannel.receiveBroadcastStream().cast<List<int>>(),
);

} `

The observed behaviour is similar to what is observed on the stack overflow thread: it works fine on android but on iOS the first handler is called.

If I am correct this would either mean fixing the bug within the flutter event channel or adding separate channels for each characteristic.

ElreboCM avatar Oct 24 '22 20:10 ElreboCM

Rethinking again, it looks like the described behavior from the stack overflow thread is different after all. The flutter_ble_reactive library only uses a single stream.

ElreboCM avatar Oct 25 '22 07:10 ElreboCM

is this fixed as of 5.0.2? it seems like a related fix was made in that version (https://github.com/PhilipsHue/flutter_reactive_ble/issues/414#issuecomment-966048908)

MoralCode avatar Nov 16 '22 10:11 MoralCode

@MoralCode No. Release 5.0.2 is from November 2021. That is one year old... I have since moved back to flutter blue (plus) as it is working there. However, that library has other issues (e.g., logging at the native level that is really annoying but can be removed by changing the native part accordingly).

ElreboCM avatar Nov 16 '22 18:11 ElreboCM