chipweinberger / flutter_blue_plus

Flutter plugin for connecting and communicationg with Bluetooth Low Energy devices, on Android, iOS, macOS
Other
788 stars 478 forks source link

[Bug]: Certain Bluetooth devices crash with GATT_INSUFFICIENT_RESOURCES on Android because of iOS compatibility code #955

Closed MrCsabaToth closed 3 months ago

MrCsabaToth commented 3 months ago

FlutterBluePlus Version

1.31.15

Flutter Version

3.4.0

What OS?

Android

OS Version

Android 14

Bluetooth Module

N/A

What is your issue?

Recently a YOSUDA Rower machine cannot successfully complete the discoverServices call of BluetoothDevice because the iOS compatibility setNotify (for 0x1801 0x2A05) errors out with an error code 17 GATT_INSUFFICIENT_RESOURCES.

I don't own that machine so I cannot reproduce this in my developer environment, however the user was nice enough to inspect the machine with nRF Connect and it's quite surprising, that nRF connect does not list 0x2A05 under the 0x1801:

354927633-c68e3336-2571-436d-93fa-1cd1c7ff0348

I also see that at least one other person ran into the same crash recently: https://stackoverflow.com/questions/78819399/flutter-blue-plus-how-to-handle-gatt-insufficient-resources

Since this setNotify is there only for iOS compatibility, I propose to guard it with a try-catch so these outlier devices could survive the discoverServices. I see that the code checks if that charactersitic exists and the proper type, but as nRF shows it's not really operable. In case of these outlier devices the users will survive and live without the feature whatever that iOS compatibility would provide.

Also note that even though the user uses FBP 1.31.15, I don't believe it's specific to that version. But I can be wrong. This could be also emerging now that Android 14 gains coverage.

Logs

FlutterBluePlusException | setNotifyValue | android-code: 17 | GATT_INSUFFICIENT_RESOURCES; #0      BluetoothCharacteristic.setNotifyValue (package:flutter_blue_plus/src/bluetooth_characteristic.dart:280)
<asynchronous suspension>
#1      BluetoothDevice.discoverServices (package:flutter_blue_plus/src/bluetooth_device.dart:273)
<asynchronous suspension>
#2      DeviceBase.discover (package:track_my_indoor_exercise/devices/gadgets/device_base.dart:213)
<asynchronous suspension>
#3      FitnessEquipment.discover (package:track_my_indoor_exercise/devices/gadgets/fitness_equipment.dart:668)
<asynchronous suspension>
#4      FitnessEquipment.connectOnDemand (package:track_my_indoor_exercise/devices/gadgets/fitness_equipment.dart:514)
<asynchronous suspension>
#5      FindDevicesState.goToRecording (package:track_my_indoor_exercise/ui/find_devices.dart:628)
<asynchronous suspension>
#6      FindDevicesState.build.<anonymous closure>.<anonymous closure>.<anonymous closure> (package:track_my_indoor_exercise/ui/find_devices.dart:995)
<asynchronous suspension>
MrCsabaToth commented 3 months ago

Note that any device I have in my environment (even if it's just a BLE tracker) shows 0x2A05, so I think the bug is correlated nRF Connect not showing that. I could be wrong though.

MangoScango commented 3 months ago

Happy to help debug this, it's my rower.

chipweinberger commented 3 months ago

what do you mean ios compatibility? what code are you referring to

chipweinberger commented 3 months ago

ah i get it now. you are referring to this feature

/// Discover services, characteristics, and descriptors of the remote device
  ///   - [subscribeToServicesChanged] Android Only: If true, after discovering services we will subscribe
  ///     to the Services Changed Characteristic (0x2A05) used for the `device.onServicesReset` stream.
  ///     Note: this behavior happens automatically on iOS and cannot be disabled
  Future<List<BluetoothService>> discoverServices({bool subscribeToServicesChanged = true, int timeout = 15}) async {

just set subscribeToServicesChanged to false.

this feature is there so that onServicesReset works by default, just like on iOS.

if you don't use onServicesReset, you can set subscribeToServicesChanged to false without worry.

if you DO use onServicesReset, you can still set subscribeToServicesChanged to false, but you'll need to call setNotifyValue on 2A05 yourself manually. But at least this way you can handle the error separately from discoverServices.

perhaps you can open a PR to improve the docs for subscribeToServicesChanged

chipweinberger commented 3 months ago

if we wanted to improve this feature,

we would add a "onListen" handler to onServicesReset, and we would only subscribe to 2a05 if the user is listening to the onServicesReset stream

feel free to open a pr that does this behavior ^^

but for your suggestion - i don't want to ignore errors because then the users will not have a way to be informed about failures.

MrCsabaToth commented 3 months ago

No need for any complication, I was too tired late night not seeing that boolean parameter!