Comments (10)
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.
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.
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.
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.
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.
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.
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.
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.
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.
Thanks for your answer. So what I can do is wait patiently maybe someone fix this problem :)
from flutter_blue.
Related Issues (20)
- Invalid Data safety section - Usage of ACCESS_FINE_LOCATION HOT 1
- Minimum iOS Deployment target is 10.0 HOT 1
- CentralManager Init: Background Usage and API MISUSE HOT 22
- java.lang.ClassCastException on device.connect() - version 0.13.0 specific HOT 2
- Can you Add this fork to Pub.dev HOT 2
- Crash while scanning... HOT 8
- Add getUuids() HOT 3
- With Android 12 not find all bluetooth devices
- Device connect and disconnec
- iOS BluetoothState.off initially even if bluetooth is on for version > 0.9.0 HOT 2
- BluetoothDescriptor isNotifying throws error: out of range
- Example Android Build Fails with :flutter_blue:compileReleaseJavaWithJavac HOT 1
- deprecated API
- Notification HOT 2
- pubspec setup in README.md
- The ios name is incorrect HOT 1
- Example app is not working on macos
- IOS The device cannot be detected during the first ios scan HOT 1
- Cant find phones on scan result
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from flutter_blue.