flutter_blue icon indicating copy to clipboard operation
flutter_blue copied to clipboard

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

Open rma6 opened this issue 5 years ago • 18 comments

I'm trying to turn on notifications for a characteristic, but every time that I run characteristic(true) I get this error PlatformException(set_notification_error, error when writing the descriptor, null). Here's a piece of my code:

BluetoothCharacteristic ss;
List<BluetoothService> services = await connectedDevice.discoverServices();
        services.forEach((service) {
          if(service.uuid.toString().compareTo("0d3448ae-12ec-4eee-8adf-e4330cfcfd5d") == 0)
          {
            service.characteristics.forEach((c) {
              if(c.uuid.toString().compareTo("31b3a584-9c79-438d-8b41-c033a9089e8b") == 0)
              {
                ss = c;
              }
            });
          }
        });
await ss.setNotifyValue(true);
ss.value.listen((value) {
_status = value.elementAt(0);
});

rma6 avatar Jul 03 '19 05:07 rma6

I am experiencing this too, but only with a characteristic that is not readable (flutter_blue 0.6.0+1). (Tool is called LightBlue) Here's what I mean: I can setNotifyValue to true here, without a problem: read-subscribe But here, I am getting exactly the same error as @rma6. subscribe

It seems like a bug.

Here some code I am using:

    // services come from discoverServices
   // this is working
    var stateCharacteristic = _services
        .singleWhere((service) => service.uuid == STATE_SERVICE)
        .characteristics
        .singleWhere((c) => c.uuid == STATE_RESPONSE)
      ..setNotifyValue(true);
   // this isn't working => see screenshots above
    var infoCharacteristic = _services
        .singleWhere((service) => service.uuid == INFO_SERVICE)
        .characteristics
        .singleWhere((c) => c.uuid == INFO_RESPONSE)
      ..setNotifyValue(true);

Since I am calling those right after eachother I thought that this might be a problem (since I am not respecting the return of the future yet), but I turned them around and tried respecting Future and even let it run without the first one. Same result, the lower one doesn't subscribe.

andretietz avatar Jul 16 '19 19:07 andretietz

@rma6 most probably await ss.setNotifyValue(true); runs before you finish iterating through the services/characteristics. If you run this it would work:

BluetoothCharacteristic ss;
List<BluetoothService> services = await connectedDevice.discoverServices();
        services.forEach((service) {
          if(service.uuid.toString().compareTo("0d3448ae-12ec-4eee-8adf-e4330cfcfd5d") == 0)
          {
            service.characteristics.forEach((c) {
              if(c.uuid.toString().compareTo("31b3a584-9c79-438d-8b41-c033a9089e8b") == 0)
              {
                ss = c;
                await ss.setNotifyValue(true);
                ss.value.listen((value) {
                _status = value.elementAt(0);
              }
            });
          }
        });

});

aytunch avatar Nov 05 '19 20:11 aytunch

+1

itsgk92 avatar May 13 '20 17:05 itsgk92

+1

boskokg avatar May 13 '20 17:05 boskokg

@aytunch I think that your code example is wrong. You should not call await inside forEach method

boskokg avatar May 13 '20 17:05 boskokg

The issue is solved if we have a delay between consecutive setNotifyValue.

itsgk92 avatar May 13 '20 18:05 itsgk92

The issue is solved if we have a delay between consecutive setNotifyValue.

could you paste your code, i have the same problem, thank you

jiyarong avatar Jun 13 '20 08:06 jiyarong

@aytunch The snippet you provided did not solve the problem

saramonteiro avatar Jun 26 '20 12:06 saramonteiro

In the example above, await inside a forEach doesn't work. Separate discovery of service/characteristics and setNotifyValue call rather than making it in a single loop.

For example

accelerometerCharacteristic = _getAccelerometerCharacteristic(sensorService);
    gyroscopeCharacteristic = _getGyroscopeCharacteristic(sensorService);

if(accelerometerCharacteristic` == null || gyroscopeCharacteristic == null){

    return false;
  }else {
    bool accelerometerSubscribed;
    bool gyroscopeSubsribed;

    if (!accelerometerCharacteristic.isNotifying) {
      accelerometerSubscribed =
      await accelerometerCharacteristic.setNotifyValue(true);
    }

    await Future.delayed(const Duration(milliseconds: 500));

    if (!gyroscopeCharacteristic.isNotifying) {
      gyroscopeSubsribed = await gyroscopeCharacteristic.setNotifyValue(true);
    }

itsgk92 avatar Jun 26 '20 13:06 itsgk92

@itsgk92 I did like this, and it continues throwing the exception:

` Future<void> _connect (BluetoothDevice dev) async {

String envSensServiceUUID = '0000181a-0000-1000-8000-00805f9b34fb';
String tempCharUUID = '00002a6e-0000-1000-8000-00805f9b34fb';
List<int> tempValue;
List<BluetoothCharacteristic> characteristics;
BluetoothCharacteristic c, MyChar;
BluetoothService s, Myservice;
bool tempCharSubscribed = false;
await dev.connect();
print("CONNECTED");

List<BluetoothService> services = await dev.discoverServices();

for (s in services) {
  if (s.uuid.toString() == envSensServiceUUID) {
    Myservice = s;
  }
}
for (c in Myservice.characteristics) {
  if (c.uuid.toString() == tempCharUUID) {
    MyChar = c;
  }
}
  tempCharSubscribed = await MyChar.setNotifyValue(true);

} `

saramonteiro avatar Jun 26 '20 19:06 saramonteiro

For me a synchronized call to setNotifyValue sufficed. I used synchronized package to achive that:

await _lock.synchronized(() async {
        await characteristic.setNotifyValue(true);
      },
    );

Also funny thing is that the exception only happend on Android.

maqoo avatar Aug 13 '20 10:08 maqoo

I had encountered the same issue. Here's my solution to solved:

  1. Use for in instead of forEach;
  2. Add await keyword before method .setNotifyValue;
  3. Add a Delayed moment between any actions;

Here's my codes:

List<BluetoothService> services = await currentDevice.result.device.discoverServices();
    for (BluetoothService s in services) {
      // services.forEach((s) async {
      if (s.uuid == DATA_SERVICE_UDID) {
        BLELog('>>> Discover DATA_SERVICE_UDID');
        // await s.characteristics.forEach((c) async {
        for (BluetoothCharacteristic c in s.characteristics) {
          if (c.uuid == CMD_WRITE_UDID) {
            BLELog('>>> >>> Discover CMD_WRITE_UDID');
            _CMD_WRITE_CHAR = c;
          } else if (c.uuid == CMD_NOTIFY_UDID) {
            BLELog('>>> >>> Discover CMD_NOTIFY_UDID');
            _CMD_NOTIFY_CHAR = c;
            await _CMD_NOTIFY_CHAR.setNotifyValue(true);
            _subscriptions.add(_CMD_NOTIFY_CHAR.value.listen(_didRecvCommandRespond));
            await Future.delayed(CommandDelayDuration);
            BLELog('>>> >>> Delayed completed');
          } else if (c.uuid == STATE_NOTIFY_UDID) {
            BLELog('>>> >>> Discover STATE_NOTIFY_UDID');
            _STATE_NOTIFY_CHAR = c;
            await _STATE_NOTIFY_CHAR.setNotifyValue(true);
            _subscriptions.add(_STATE_NOTIFY_CHAR.value.listen(_didRecvStateRespond));
            await Future.delayed(CommandDelayDuration);
            BLELog('>>> >>> Delayed completed');
          }
          else {
            BLELog('>>> >>> Discover Unknow Char ${c.uuid}');
          }
          // });
        }
      } else {
        BLELog('>>> Discover Unknow Service : ${s.uuid}');
      }
      // });
    }

GrayLand119 avatar Jan 05 '21 07:01 GrayLand119

I think this issue can be closed because its working as expected in current version 0.7.3 as long as you await calls to setNotifyValue(true) AND as per the previous comment, the key is to not use async forEach()!

A very good explaination as to why async forEach() causes a problem is documented in this SO question.

So for example I used await Future.forEach() instead.

maks avatar Feb 08 '21 01:02 maks

Hi guys i face this for two days this is my solution it work for me i hope it will solve ur problem i think we need await every function return future

discoverService(List<BluetoothDevice> device){ device.forEach((d) async { List<BluetoothService> ser = await d.discoverServices(); print("this is llist of servieces"); for(BluetoothService service in ser){ for(BluetoothCharacteristic chr in service.characteristics){ if(chr.properties.notify){ await chr.setNotifyValue(true); print("this notify bit now after setNotifiction true ${chr.isNotifying}"); chr.value.listen((event) { print("this is last value of chr = ${event.toString()}"); }); await Future.delayed(Duration(seconds: 3));

        print("${chr.uuid}");
      }
    }
  }
  print("this is llist of servieces");
});}

jimykhan avatar Feb 20 '21 12:02 jimykhan

For me a synchronized call to setNotifyValue sufficed. I used synchronized package to achive that:

await _lock.synchronized(() async {
        await characteristic.setNotifyValue(true);
      },
    );

Also funny thing is that the exception only happend on Android.

It's works!

yuriboeira11tx avatar May 17 '21 14:05 yuriboeira11tx

The fact that adding a synchronized call suggests that you were making a second setNotifyValue( true ) call before the first one had completed. Note that the conditional !gyroscopeCharacteristic.isNotifying() doesn't prevent multiple simultaneous asynchronous calls since the second one starts before the first has completed.

In @saramonteiro 's example, this should work:

bool subscribeAlreadyCalled = false;

Future<void> _connect (BluetoothDevice dev) async {
	
	String envSensServiceUUID = '0000181a-0000-1000-8000-00805f9b34fb';
	String tempCharUUID = '00002a6e-0000-1000-8000-00805f9b34fb';
	List<int> tempValue;
	List<BluetoothCharacteristic> characteristics;
	BluetoothCharacteristic c, MyChar;
	BluetoothService s, Myservice;
	bool tempCharSubscribed = false;
	await dev.connect();
	print("CONNECTED");
	
	List<BluetoothService> services = await dev.discoverServices();
	
	for (s in services) {
	  if (s.uuid.toString() == envSensServiceUUID) {
	    Myservice = s;
	  }
	}
	for (c in Myservice.characteristics) {
	  if (c.uuid.toString() == tempCharUUID) {
	    MyChar = c;
	  }
	}
	
	if (!subscribeAlreadyCalled) {
	  subscribeAlreadyCalled = true;
	  tempCharSubscribed = await MyChar.setNotifyValue(true);
	}
}

stefan-sherwood avatar May 23 '23 23:05 stefan-sherwood

@rma6 most probably await ss.setNotifyValue(true); runs before you finish iterating through the services/characteristics. If you run this it would work:

BluetoothCharacteristic ss;
List<BluetoothService> services = await connectedDevice.discoverServices();
        services.forEach((service) {
          if(service.uuid.toString().compareTo("0d3448ae-12ec-4eee-8adf-e4330cfcfd5d") == 0)
          {
            service.characteristics.forEach((c) {
              if(c.uuid.toString().compareTo("31b3a584-9c79-438d-8b41-c033a9089e8b") == 0)
              {
                ss = c;
                await ss.setNotifyValue(true);
                ss.value.listen((value) {
                _status = value.elementAt(0);
              }
            });
          }
        });

});

This helped me 👍 Thank you

For anyone looking into this in the future, I decided to switch this library out (3 years out of date now) for a fork of this one, called flutter blue plus.

It'll take some rewriting in your code to get set up with, but this library is much more reliable than the latest release of the original Flutter Blue library.

MO-Lewis avatar Mar 21 '24 11:03 MO-Lewis