flutter_blue icon indicating copy to clipboard operation
flutter_blue copied to clipboard

Error trying to write to chracteristc on 0.6.0+2

Open shinayser opened this issue 6 years ago • 38 comments

The package I am trying to send is 20 bytes long on Android:

/flutter (21603): [ERROR:flutter/lib/ui/ui_dart_state.cc(148)] Unhandled Exception: PlatformException(write_characteristic_error, writeCharacteristic failed, null)
E/flutter (21603): #0      StandardMethodCodec.decodeEnvelope (package:flutter/src/services/message_codecs.dart:564:7)
E/flutter (21603): #1      MethodChannel.invokeMethod (package:flutter/src/services/platform_channel.dart:316:33)
E/flutter (21603): <asynchronous suspension>
E/flutter (21603): #2      BluetoothCharacteristic.write (package:flutter_blue/src/bluetooth_characteristic.dart:123:10)
E/flutter (21603): <asynchronous suspension>

On iOS the error is a little different:

[VERBOSE-2:ui_dart_state.cc(148)] Unhandled Exception: Exception: Failed to write the characteristic
#0      BluetoothCharacteristic.write.<anonymous closure> (package:flutter_blue/src/bluetooth_characteristic.dart:142:15)
#1      _rootRunUnary (dart:async/zone.dart:1132:38)
#2      _CustomZone.runUnary (dart:async/zone.dart:1029:19)
#3      _FutureListener.handleValue (dart:async/future_impl.dart:126:18)
#4      Future._propagateToListeners.handleValueCallback (dart:async/future_impl.dart:639:45)

This is my sendPackage method:

_sendPackage(BLEConnectionService service, List<int> package) async {
  if (package.length <= 20) {
    print("Sending small package");
    _characteristic.write(package);
  } else {
    print("Sending chunked package of ${package.length} bytes");

    int chunk = 0;
    int nextRemaining = package.length;
    List<int> toSend;

    while (nextRemaining > 0) {
      toSend = package.sublist(chunk, chunk + min(20, nextRemaining));
      print("Enviando chunk $toSend");
      _characteristic.write(toSend);
      await delay(20);
      nextRemaining -= 20;
      chunk += 20;
    }
  }
}

I am using flutter_blue: ^0.6.0+2 but the code worked fine with my previous version (which was 0.4.1)

There is any workaround for this?

shinayser avatar Aug 01 '19 19:08 shinayser

And where is your code at?

ghost avatar Aug 07 '19 01:08 ghost

I have the same problem and I am really interested in the solution too! I do await bluetoothCharacteristic.write(utf8.encode(command)), withoutResponse: true ); and the error occurs after some writings. And in this way it is every time
await bluetoothCharacteristic.write(utf8.encode(command)));

chronocurl avatar Aug 07 '19 14:08 chronocurl

And where is your code at?

I've updated the question.

shinayser avatar Aug 07 '19 16:08 shinayser

I did a test and I pass 1 or 2 characters to the write command and in my case it does not seem to be the size that causes the error. The bluetooth device returns information during writing, could a collision between messages explain the error?

chronocurl avatar Aug 07 '19 18:08 chronocurl

I did a test and I pass 1 or 2 characters to the write command and in my case it does not seem to be the size that causes the error. The bluetooth device returns information during writing, could a collision between messages explain the error?

I am chunking the packages because our hardware is the limiting part here. He can't hadle packages more than 20 bytes. But doesn't matter how much bytes we send to the hardware, it always gives the error above.

Again, in the version 0.4.1 everything was working fine. @pauldemarco Could you give us some light?

shinayser avatar Aug 07 '19 18:08 shinayser

Did you try the write with the withoutResponse : true. In my case it works for some write and it crashes after.

chronocurl avatar Aug 07 '19 18:08 chronocurl

Seems like the write calls are overlapping to the underlying platform. Please make sure to ‘await’ any calls to the plugin, and the outer functions as well. Example)

await  _characteristic.write(X);

...

await sendPackage(service, package);

pauldemarco avatar Aug 07 '19 19:08 pauldemarco

In my case I already have await before the write instruction and I still have the error. I notice that in my Characteristic listener I receive the writes that I send and also the returns of the bluetooth device. Is it possible that there is a problem when I send a message and return a message at the same time?

chronocurl avatar Aug 07 '19 20:08 chronocurl

Awaiting with delay is not the same as awaiting the actual function call. Please post your code, including the outer function calls.

pauldemarco avatar Aug 07 '19 20:08 pauldemarco

I just set "withoutResponse" to true and now I am able to send packages to the hardware. But, now I am receiving duplicate characteristic updates, like https://github.com/pauldemarco/flutter_blue/issues/317.

@pauldemarco The API design got awesome BTW, congratulations! Just need to fx those small issues and it will be AWESOME.

shinayser avatar Aug 07 '19 20:08 shinayser

I just set "withoutResponse" to true and now I am able to send packages to the hardware. But, now I am receiving duplicate characteristic updates, like #317.

Just be careful with this, when the documentation is still correct in this case, writing without response will NOT guarantee a successful write/send. Which means, you could still drop some packages you are sending out, but you will not notice it, when writing without response.

khainke avatar Aug 07 '19 20:08 khainke

I just set "withoutResponse" to true and now I am able to send packages to the hardware. But, now I am receiving duplicate characteristic updates, like #317.

Just be careful with this, when the documentation is still correct in this case, writing without response will NOT guarantee a successful write/send. Which means, you could still drop some packages you are sending out, but you will not notice it, when writing without response.

I understand, but when sending with response, the error mentioned on top happens =(

shinayser avatar Aug 07 '19 21:08 shinayser

Here the code to write : @override void sendMessageToDevice(String command) async {

if (this.isConnected) {
  await bluetoothCharacteristic.write(utf8.encode(command), withoutResponse: true );
}

}

@override void calibration() { sendMessageToDevice("I"); }

If necessary the code to set the characteristics listener : void _setServiceCharistic() async{

if (device==null) return;

device.discoverServices().then((s) {
  services = s;
  for(var service in services) {
    if (service.uuid == Guid("0000ffe0-0000-1000-8000-00805F9B34FB")) {
      for (var c in service.characteristics) {
        if (c.uuid == Guid("0000ffe1-0000-1000-8000-00805F9B34FB")) {
          bluetoothCharacteristic=c;
          bluetoothCharacteristic.setNotifyValue(true);
          Future.delayed(const Duration(milliseconds: 100), () {});
          inConnectionStatus.add(ConnectionMessage(deviceState: deviceState, macAdresse: device.id.id));

          valueChangedSubscriptions = bluetoothCharacteristic.value.listen((d){
            ...
          });
        }
      }
    }
  }
});

}

chronocurl avatar Aug 07 '19 21:08 chronocurl

@shinayser If it's working without response, that's fine. But if you ever feel the need to write with response, you could still try what paul suggested and guarantee a single write at a time by awaiting the write calls, awaiting the outer function (sendPackage) calls and maybe adding a mutex or something like this.

khainke avatar Aug 08 '19 06:08 khainke

Currently I send only 1 write at a time to my device, there is no concurrency at this point. The first situation is that everything is fine when the device does not return anything. The second is that when a write is done and a return is expected the error will occur after a few iterations. I suppose there might be a problem when the send comes in at the same time as the receive, but I'm not sure. I see how to block the writes, but how to block the return of the device so that it does not conflict with the writes?

chronocurl avatar Aug 08 '19 12:08 chronocurl

I just set "withoutResponse" to true and now I am able to send packages to the hardware. But, now I am receiving duplicate characteristic updates, like #317.

@pauldemarco The API design got awesome BTW, congratulations! Just need to fx those small issues and it will be AWESOME.

You're getting duplicate characteristic updates? So on the device side that is broadcasting bluetooth you're getting duplicate writes? To elaborate... your write characteristic is being called multiple times?

ghost avatar Aug 08 '19 16:08 ghost

The characterist.value is being called twice:

 charac.value.listen((values) {
         //This is getting called twice
});

shinayser avatar Aug 08 '19 17:08 shinayser

I did several tests today and I confirm that there is a real problem when a write message is sent along with a message from the bluetooth device.

write ---> crash * <------- BT Device

  • PlatformException (write_characteristic_error, writeCharacteristic failed, null)

If the write and read messages are sent sequentially there is no problem. Is there a way around the problem or is there a possible fix for the API?

Another question, is it normal that the bluetoothCharacteristic.value.listen recovers as much the messages coming from the write and the messages coming from bluetooth device? Normally we should not only receive messages from the bluetooth device?

chronocurl avatar Aug 08 '19 18:08 chronocurl

Another question, is it normal that the bluetoothCharacteristic.value.listen recovers as much the messages coming from the write and the messages coming from bluetooth device? Normally we should not only receive messages from the bluetooth device?

See issue #311

khainke avatar Aug 08 '19 20:08 khainke

I am not able to write even after setting withoutResponse to true.

  write(List<int> data) async {
    BluetoothDevice device = locator<BluetoothConnectionService>().device;
    if (device != null) {
      List<BluetoothService> services = await device.discoverServices();
      print(await services[0].characteristics[1]
          .write([0, 0], withoutResponse: true));
    }
  }

Gives me the same error as mentioned in the first comment.

Prathik-Jain avatar Aug 26 '19 12:08 Prathik-Jain

I've write characteristic error also in the plugin example. Is there a release in witch is it working?

z8a avatar Nov 17 '19 22:11 z8a

@z8a I had similar issue, can you please make sure if the characteristic you are writing to is not read-only?

Prathik-Jain avatar Nov 18 '19 13:11 Prathik-Jain

@Prathik-Jain I checked writing with an HM-10 device and it worked. Then I changed the type of my device characteristic from 'Write without response' to 'Write' and it's working.

z8a avatar Nov 27 '19 21:11 z8a

Hi, @z8a , how did you set device characteristics from 'Write without response' to 'Write' on HM-10 ? I have the same problem with flutter blue 0.7.2 on Android device. On IOS it works fine...

Mavrap avatar Jun 14 '20 12:06 Mavrap

I had similar issues with certain BLE devices but I recently found that the root cause of these issues are just the device latency.

I mean, we should have some delay like await Future.delay(Duration(milliseconds: 100) just before such write calls.

Of course, with certain Android/iOS devices, we don't need them but they don't harm anything.

espresso3389 avatar Jun 14 '20 12:06 espresso3389

Thanks but I have already try the delay without success. I don't what I can do!!! my app is finished and deployed on AppStore, just the write function don't works on Android...

Mavrap avatar Jun 14 '20 13:06 Mavrap

Furthermore, i have a function that writes only data without any exchange before when I click on a button, the pb is the same...

Mavrap avatar Jun 14 '20 13:06 Mavrap

I ended up with a retry loop on the write:

import 'package:quiver/iterables.dart'; // for "partition" function

...

int mtu = await device.mtu.first; // default 20 on Android?

partition(bytes, mtu).forEach((bytes) async {
  log.info('Sending chunk of ${bytes.length}: $bytes');

  var retry = 0;
  do {
    try {
      await characteristicWrite.write(bytes, withoutResponse: true);
      break;
    } on PlatformException {
      await Future.delayed(Duration(milliseconds: 100));
    }
    // 3 retries to have a failsafe and avoid potential infinite loop
    // TODO: Check if we cannot have a 'real/decent' max retry...
  } while (retry < 3);
});

rdehouss avatar Jul 01 '20 13:07 rdehouss

I changed a couple things to get mine to work --

  1. set minSdkVersion to 21 in build.gradle
  2. targetSdkVersion 28
  3. Add
    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>

to debug AND main Android.manifest.xml

  1. Change all forEach statements (run asynchronously which causes overlap) to synchronous for loops. For example:
          List<BluetoothService> services = await r.device.discoverServices();
          print('got services');
          for (int j = 0; j < services.length; j++) {
           }

from

       services.forEach

My full code:

void doBluetooth() async {
    batteryLevels = "";
    FlutterBlue flutterBlue = FlutterBlue.instance;
    print('got flutter blue instance');
    updateAlreadyConnectedDevices();
    await flutterBlue.startScan(timeout: Duration(seconds: 1));
    print('started scan');
    flutterBlue.scanResults.listen((results) async {
      print('got results');
      for (ScanResult r in results) {
        if (r.advertisementData.serviceUuids.contains("1811") ||
            r.advertisementData.serviceUuids
                .contains("00001811-0000-1000-8000-00805f9b34fb")) {
          print('connecting to device that contained our service');
          await r.device.connect();
          print('connected looking for services');
          List<BluetoothService> services = await r.device.discoverServices();
          print('got services');
          for (int j = 0; j < services.length; j++) {
            var service = services[j];
            print(service.uuid);
            if (service.uuid == Guid("00001811-0000-1000-8000-00805F9B34FB")) {
              print('matched our uuid -- going through chars');
              for (int i = 0; i < service.characteristics.length; i++) {
                var characteristic = service.characteristics[i];
                print(characteristic.uuid);
                if (characteristic.uuid ==
                    Guid("00002A1F-0000-1000-8000-00805F9B34FB")) {
                  print('matched our char');
                  if (locksOn) {
                    print('locks is on writing 1');
                    await characteristic.write([1], withoutResponse: true);
                  } else {
                    print('locks not on writing 0');
                    await characteristic.write([0], withoutResponse: true);
                  }
                }
              }
            }
          }
        }
      }
    });
  }

SethKitchen avatar Oct 12 '20 02:10 SethKitchen

  1. Change all forEach statements (run asynchronously which causes overlap) to synchronous for loops. For example:
          List<BluetoothService> services = await r.device.discoverServices();
          print('got services');
          for (int j = 0; j < services.length; j++) {
           }

from

       services.forEach

@SethKitchen Changing the forEach statements worked for me. Thank you

brendabullorini avatar Nov 11 '20 19:11 brendabullorini