Coder Social home page Coder Social logo

Comments (10)

trueb2 avatar trueb2 commented on July 26, 2024

sounds like you failed pairing

/// Retrieves the value of the characteristic
  Future<List<int>> read() async {
    var request = protos.ReadCharacteristicRequest.create()
      ..remoteId = deviceId.toString()
      ..characteristicUuid = uuid.toString()
      ..serviceUuid = serviceUuid.toString();
    FlutterBlue.instance._log(LogLevel.info,
        'remoteId: ${deviceId.toString()} characteristicUuid: ${uuid.toString()} serviceUuid: ${serviceUuid.toString()}');

    await FlutterBlue.instance._channel
        .invokeMethod('readCharacteristic', request.writeToBuffer());

    return FlutterBlue.instance._methodStream
        .where((m) => m.method == "ReadCharacteristicResponse")
        .map((m) => m.arguments)
        .map((buffer) =>
            new protos.ReadCharacteristicResponse.fromBuffer(buffer))
        .where((p) =>
            (p.remoteId == request.remoteId) &&
            (p.characteristic.uuid == request.characteristicUuid) &&
            (p.characteristic.serviceUuid == request.serviceUuid))
        .map((p) => p.characteristic.value)
        .first
        .then((d) {
      _value.add(d);
      return d;
    });
  }

There is no timeout on the read, so it is requested but never responded.

from flutter_blue.

trueb2 avatar trueb2 commented on July 26, 2024

Seems like these calls could have a built in timeout or a chained timeout: https://stackoverflow.com/questions/52672137/await-future-for-a-specific-time

You can't cancel a future, so accumulating permanently pending futures looks like a memory leak that we may want to avoid.
https://dart.academy/how_cancel_future/

These reads, writes, and discovers could have some stream subscription/cancel changes and still fulfill the future, but it would probably be easier to allow the small leak with something like the future timeout interface in that stack overflow link.

// might leak a future whenever the connection times out in the middle of a read/write
final value = await char.read().timeout(const Duration(seconds: 5), () => null);

from flutter_blue.

pepe19000 avatar pepe19000 commented on July 26, 2024

I have built a timeout on future, but i think it is not the best solution because:
if i set the duration to low, user doesn't has enough time to write the PIN on the dialog
if i set the duration to high, user has enough time in PIN dialog, but in case of cancel PIN dialog, user has to wait to end of duration.

If PIN dialog is canceled or get a wrong PIN i display an alert dialog.

I hope you understand my cases

from flutter_blue.

trueb2 avatar trueb2 commented on July 26, 2024

I have built a timeout on future, but i think it is not the best solution because: if i set the duration to low, user doesn't has enough time to write the PIN on the dialog if i set the duration to high, user has enough time in PIN dialog, but in case of cancel PIN dialog, user has to wait to end of duration.

If PIN dialog is canceled or get a wrong PIN i display an alert dialog.

I hope you understand my cases

Are you await'ing the future on the main event loop or chaining futures like promises?

from flutter_blue.

trueb2 avatar trueb2 commented on July 26, 2024

Related: https://stackoverflow.com/questions/27836416/corebluetooth-pairing-feedback-callback/54644299#54644299

This question recommends attempting a write and checking for an error like 'Authentication is insufficient'. In my experience, there are other mechanisms for initiating pairing through the iOS system Bluetooth menu depending on the services advertised by the peripheral.

Initiating and checking pairing state is tricky for iOS.

from flutter_blue.

pepe19000 avatar pepe19000 commented on July 26, 2024

I'm awaiting the future on the main event as: await characteristic.write([1, 3, 1, 2, 0]).timeout(....)

If timeout is not specified on the future and bluetooth authenticate run to error, future (characteristic.write([....])) never returns.
Currently I protect this problem with the timeout method, but in my opinion it is not the best solution because it cause worse user experience

from flutter_blue.

trueb2 avatar trueb2 commented on July 26, 2024

That sounds like you are causing the blocking behavior. Aside from changing the control flow to use .then, I think it is just up to you to write better client code.

You could debug calls that are made in Obj-C. You can see that the peripheral's didWriteValueForCharacteristic callback sets a success flag if there is no error. With what is currently there, you could make a first-time write attempt and assume that failure is due to an authentication error. If you want more robust certainty, then you would have to change the proto for WriteCharacteristicResponse to have a success message or code.

- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
  NSLog(@"didUpdateValueForCharacteristic %@", [peripheral.identifier UUIDString]);
  ProtosReadCharacteristicResponse *result = [[ProtosReadCharacteristicResponse alloc] init];
  [result setRemoteId:[peripheral.identifier UUIDString]];
  [result setCharacteristic:[self toCharacteristicProto:peripheral characteristic:characteristic]];
  [_channel invokeMethod:@"ReadCharacteristicResponse" arguments:[self toFlutterData:result]];

  // on iOS, this method also handles notification values
  ProtosOnCharacteristicChanged *onChangedResult = [[ProtosOnCharacteristicChanged alloc] init];
  [onChangedResult setRemoteId:[peripheral.identifier UUIDString]];
  [onChangedResult setCharacteristic:[self toCharacteristicProto:peripheral characteristic:characteristic]];
  [_channel invokeMethod:@"OnCharacteristicChanged" arguments:[self toFlutterData:onChangedResult]];
}

- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
  NSLog(@"didWriteValueForCharacteristic");
  ProtosWriteCharacteristicRequest *request = [[ProtosWriteCharacteristicRequest alloc] init];
  [request setRemoteId:[peripheral.identifier UUIDString]];
  [request setCharacteristicUuid:[characteristic.UUID fullUUIDString]];
  [request setServiceUuid:[characteristic.service.UUID fullUUIDString]];
  ProtosWriteCharacteristicResponse *result = [[ProtosWriteCharacteristicResponse alloc] init];
  [result setRequest:request];
  [result setSuccess:(error == nil)];
  [_channel invokeMethod:@"WriteCharacteristicResponse" arguments:[self toFlutterData:result]];
}

From what is there, you should already be able to catch an exception when the write fails

  Future<Null> write(List<int> value, {bool withoutResponse = false}) async {
    final type = withoutResponse
        ? CharacteristicWriteType.withoutResponse
        : CharacteristicWriteType.withResponse;

    var request = protos.WriteCharacteristicRequest.create()
      ..remoteId = deviceId.toString()
      ..characteristicUuid = uuid.toString()
      ..serviceUuid = serviceUuid.toString()
      ..writeType =
          protos.WriteCharacteristicRequest_WriteType.valueOf(type.index)!
      ..value = value;

    var result = await FlutterBlue.instance._channel
        .invokeMethod('writeCharacteristic', request.writeToBuffer());

    if (type == CharacteristicWriteType.withoutResponse) {
      return result;
    }

    return FlutterBlue.instance._methodStream
        .where((m) => m.method == "WriteCharacteristicResponse")
        .map((m) => m.arguments)
        .map((buffer) =>
            new protos.WriteCharacteristicResponse.fromBuffer(buffer))
        .where((p) =>
            (p.request.remoteId == request.remoteId) &&
            (p.request.characteristicUuid == request.characteristicUuid) &&
            (p.request.serviceUuid == request.serviceUuid))
        .first
        .then((w) => w.success)
        .then((success) => (!success)
            ? throw new Exception('Failed to write the characteristic')
            : null)
        .then((_) => null);
  }

from flutter_blue.

pepe19000 avatar pepe19000 commented on July 26, 2024

That sounds like you are causing the blocking behavior. Aside from changing the control flow to use .then, I think it is just up to you to write better client code.

You could debug calls that are made in Obj-C. You can see that the peripheral's didWriteValueForCharacteristic callback sets a success flag if there is no error. With what is currently there, you could make a first-time write attempt and assume that failure is due to an authentication error. If you want more robust certainty, then you would have to change the proto for WriteCharacteristicResponse to have a success message or code.

- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
  NSLog(@"didUpdateValueForCharacteristic %@", [peripheral.identifier UUIDString]);
  ProtosReadCharacteristicResponse *result = [[ProtosReadCharacteristicResponse alloc] init];
  [result setRemoteId:[peripheral.identifier UUIDString]];
  [result setCharacteristic:[self toCharacteristicProto:peripheral characteristic:characteristic]];
  [_channel invokeMethod:@"ReadCharacteristicResponse" arguments:[self toFlutterData:result]];

  // on iOS, this method also handles notification values
  ProtosOnCharacteristicChanged *onChangedResult = [[ProtosOnCharacteristicChanged alloc] init];
  [onChangedResult setRemoteId:[peripheral.identifier UUIDString]];
  [onChangedResult setCharacteristic:[self toCharacteristicProto:peripheral characteristic:characteristic]];
  [_channel invokeMethod:@"OnCharacteristicChanged" arguments:[self toFlutterData:onChangedResult]];
}

- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
  NSLog(@"didWriteValueForCharacteristic");
  ProtosWriteCharacteristicRequest *request = [[ProtosWriteCharacteristicRequest alloc] init];
  [request setRemoteId:[peripheral.identifier UUIDString]];
  [request setCharacteristicUuid:[characteristic.UUID fullUUIDString]];
  [request setServiceUuid:[characteristic.service.UUID fullUUIDString]];
  ProtosWriteCharacteristicResponse *result = [[ProtosWriteCharacteristicResponse alloc] init];
  [result setRequest:request];
  [result setSuccess:(error == nil)];
  [_channel invokeMethod:@"WriteCharacteristicResponse" arguments:[self toFlutterData:result]];
}

From what is there, you should already be able to catch an exception when the write fails

  Future<Null> write(List<int> value, {bool withoutResponse = false}) async {
    final type = withoutResponse
        ? CharacteristicWriteType.withoutResponse
        : CharacteristicWriteType.withResponse;

    var request = protos.WriteCharacteristicRequest.create()
      ..remoteId = deviceId.toString()
      ..characteristicUuid = uuid.toString()
      ..serviceUuid = serviceUuid.toString()
      ..writeType =
          protos.WriteCharacteristicRequest_WriteType.valueOf(type.index)!
      ..value = value;

    var result = await FlutterBlue.instance._channel
        .invokeMethod('writeCharacteristic', request.writeToBuffer());

    if (type == CharacteristicWriteType.withoutResponse) {
      return result;
    }

    return FlutterBlue.instance._methodStream
        .where((m) => m.method == "WriteCharacteristicResponse")
        .map((m) => m.arguments)
        .map((buffer) =>
            new protos.WriteCharacteristicResponse.fromBuffer(buffer))
        .where((p) =>
            (p.request.remoteId == request.remoteId) &&
            (p.request.characteristicUuid == request.characteristicUuid) &&
            (p.request.serviceUuid == request.serviceUuid))
        .first
        .then((w) => w.success)
        .then((success) => (!success)
            ? throw new Exception('Failed to write the characteristic')
            : null)
        .then((_) => null);
  }

Thank you your answer but i think we went to wrong direction :) We are in a package's issue topic and i don't think i sould go deeper than my own code.
I think if a write or read a future characteristic it always have to return with a result. I always need a result.
If i use await or Future.then the result will same: I cannot go further in the code to take further actions.

My only chance to give feedback to user about errors if i use timeout.

from flutter_blue.

trueb2 avatar trueb2 commented on July 26, 2024

Yes, the existing code requires a deeper dive than a pleasant try-catch on an await call. I was mostly pointing out that if you inspect the code that actually runs for the package, you can use this package but need to do you own safe-guards when off the happy path of execution. It looks the same to me for flutter_reactive_ble.

Your issue touches on the main architectural flaw of this project. There is no short circuiting logic between Obj-C and FlutterMethodChannel on erroneous conditions. I went deeper than your own code to show that this is the code that people are trying to use when they use this package. This is what they should expect. Realistically, I don't see any fixes coming aside from adding your own timeouts. If someone wants to PR the proper pattern to use, I think we could all greatly benefit.

From this example, we are awaiting a method stream for a Response message on a method event channel in most of these calls. Aside from authentication failure, there are a number of other conditions that could trigger unpleasant experiences in BLE handling code, i.e., long connection intervals, noisy RF environments, OS exceptions, etc. All of those code paths share a similar downfall to what you have demonstrated here.

There are no Response methods emitted on connection state change or error. There is no short circuiting any of these streams via other futures. If we were to add fixes to the library to address these, then the method streams would need an event driven handling of events and timeouts. All of the calling code would the need the appropriate try catch logic to be hardened and documented as well. I find this is quite difficult to configure for integration testing, so long term stability is difficult/expensive.

Breaking design should go in a separate issue.

from flutter_blue.

pepe19000 avatar pepe19000 commented on July 26, 2024

Thanks for your answer. So what I can do is wait patiently maybe someone fix this problem :)

from flutter_blue.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.