flutter_blue: working around a misbehaving BLE device and stream persistence

4,963 views
Skip to first unread message

Brenden Smerbeck

unread,
Jul 29, 2020, 12:33:00 PM7/29/20
to Flutter Development (flutter-dev)
SO post link containing code and more detailed notes: https://stackoverflow.com/questions/63138321/flutter-blue-set-notification-and-read-characteristic-errors-on-android-ble-de

Long story short, I've been trying to fix some bugs in an app I've developed to communicate with a BLE power bank. On iOS it's working alright, but there's fatal exceptions on Android. Part of this is due to some misbehavior on the device's part - trying to figure a way around it.

Basically, the device is a bit finicky, and connecting to it goes something like:
  • initiate connect
  • device connects
  • (begin service discovery, etc..)
  • device disconnects
  • initiate second connect
  • device connects
  • everything goes perfectly
BUT, all of these operations are part of a stream listener of the device state and the discovery methods and setNotify() methods are async. 

More importantly, on the second time of trying to connect, there seems to be some duplicate firing. discoverServices() fires twice, setting the notification fires twice. It's as if there's a duplicate instance created and both try to work.

Notification error is:
PlatformException(set_notification_error, error when writing the descriptor, null) on setNotifyValue

Relevant github issue: https://github.com/pauldemarco/flutter_blue/issues/295

What I'm trying to solve:

1. Workaround for when device connects immediately after connecting

This is obviously device-specific, but I've been bashing my head in looking for a solution. At first, I thought (okay, we'll try to go through discovery/setting notifications on the second connection attempt). The problem there is that, if someone else initiates the first connection (or in general it has failed the connection once before), the first try of the app succeeds and the connection is maintained. This means I can't be sure it happens every other connection attempt. 

If I could somehow detect that is was the failed connection, I could hold off on performing the operations that cause the program to throw exceptions. I'll paste the code block for reference:

void connect(String deviceId) async {
   
var dev = devices[deviceId];
   
if (connectedDevices[deviceId] != null) return;
    await dev
.device.connect(autoConnect: false);

    dev
.connection = dev.device.state.listen((state) async {
      dev
.deviceState = state;
      notifyListeners
();
     
if (state == BluetoothDeviceState.disconnected) {
        connectedDevices
[dev.id] = null;
        await dev
.dispose();
        notifyListeners
();
     
}

     
if (state == BluetoothDeviceState.connected) {
        dev
.services = await dev.device.discoverServices();
       
for (BluetoothService service in dev.services) {
         
// set services based on uuid
       
}
       
for (BluetoothCharacteristic characteristic
           
in dev.deviceInfoService.characteristics) {
         
// set characteristics from services
       
}
       
for (BluetoothCharacteristic characteristic
           
in dev.notificationService.characteristics) {
         
switch (characteristic.uuid.toString()) {
           
case notificationServiceCharacteristic:
              dev
.notificationServiceCharacteristic = characteristic;
             
if (!dev.notificationServiceCharacteristic.isNotifying) {
                await dev
.notificationServiceCharacteristic
                   
.setNotifyValue(true);          // this is where the error is thrown. I've tried checking call count for the even number attempts to no avail
                dev
.valueChangedSubscription = dev
                   
.notificationServiceCharacteristic.value
                   
.listen((value) {
                  _onValuesChanged
(dev, value);
                  notifyListeners
();
               
});
                connectedDevices
[dev.id] = dev;
             
}
             
break;
           
case writeCharacteristic:
              dev
.writeCharacteristic = characteristic;
             
break;
           
default:
             
break;
         
}
       
}
        notifyListeners
(); //using scopedModel for handling state
        await readServiceCharacteristics
(dev);
     
}
   
});
 
}

2. Solving the case of duplicated calls

This seems a bit tougher, as there's currently work being done by the author himself to hopefully solve it: https://github.com/pauldemarco/flutter_blue/issues/525

However, I'm not just getting duplicated notifications - everything is being duplicated on occasion. Services discovered twice, notifications set twice, reading characteristics twice, etc. I thought the await would help, but it's not getting me any closer. This isn't a major issue, but after each successful reconnection the number of notifications per second per devices increases by 3. Given enough time, the app is crippled. 


I've pinged the Flutter dev discord a few times, but it seems like there aren't a lot of developers working with bluetooth. Any suggestions would be greatly appreciated. Also feel free to contact me directly on discord at pandemic#0001

Thanks,

Brenden






Souvik Dutta

unread,
Jul 29, 2020, 9:24:00 PM7/29/20
to Brenden Smerbeck, Flutter Development (flutter-dev)
<note: The below points will only work if a failed connection returns an exception>

For the first problem you could try to wrap your connection methods in a try catch block. That way if the connection fails you will have some solid information on why the connection failed. 

On the second issue- is there a way to know if a connection has been previously made? That way you could first check if an ongoing connection exists, if it does then do nothing, else if it doesn't then start a new connection.

If this type of a method doesn't exist i.e. if there is an ongoing connection or not then you might try using a variable that hold either true or false. Let it initially hold false. Now if the connection is established successfully that is the try bolck works well then you can change this variable to false. This way before executing the whole try and catch block you will be able to check if the variable is true or false and take necessary steps. Hope this helps.

Souvik flutter dev

--
You received this message because you are subscribed to the Google Groups "Flutter Development (flutter-dev)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to flutter-dev...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/flutter-dev/9cea580b-906e-43f2-bc12-82ef73c0857do%40googlegroups.com.
Reply all
Reply to author
Forward
0 new messages