Subscribing to BLE notifications with flutter_reactive_ble throws error? - swift

I'm pretty new to Dart and know nothing about Swift. Trying to use the flutter_reactive_ble package to manage the BT side of an app I'm building. So far things are going smooth up to the point where attempting to enable notifications fails and crashes the app.
The problem occurs when trying to call subscribeToCharacteristic method like this:
class MeshProxyRx extends ReactiveState<Uint8List> {
MeshProxyRx(this._ble);
final FlutterReactiveBle _ble;
final _stateStreamController = StreamController<Uint8List>.broadcast();
StreamSubscription _meshProxyDataOut;
Uint8List rxDataBuffer;
#override
Stream<Uint8List> get state => _stateStreamController.stream;
startNotification(_proxyDevice) {
print('Notification Start');
final characteristic = QualifiedCharacteristic(
serviceId: serviceUuid,
characteristicId: characteristicUuid,
deviceId: _proxyDevice);
_meshProxyDataOut =
_ble.subscribeToCharacteristic(characteristic).listen((data) {
rxDataBuffer.addAll(data);
_stateStreamController.add(rxDataBuffer);
// code to handle incoming data
// ProxyNodeNotify(nodeAddress: [0x18, 0x28]);
print(data);
}, onError: (dynamic error) {
// code to handle errors
print('Start Notification returns Error: $error');
});
}
Future<void> closeStream() async {
await _stateStreamController.close();
}
Future<void> closeSubscription() async {
_meshProxyDataOut?.cancel();
}
}
The error that throws is:
flutter: REACTIVE_BLE: Start connecting to device with arguments (deviceId: 5523499D-8846-A794-F350-1B5E258859F3, servicesWithCharacteristicsToDiscover: null, timeout: null)
flutter: REACTIVE_BLE: Received ConnectionStateUpdate(deviceId: 5523499D-8846-A794-F350-1B5E258859F3, connectionState: DeviceConnectionState.connected, failure: null)
flutter: Notification Start
flutter: REACTIVE_BLE: Start subscribing to notifications for QualifiedCharacteristic(characteristicId: 2ade, serviceId: 1828, deviceId: 5523499D-8846-A794-F350-1B5E258859F3)
Assertion failed: file flutter_reactive_ble/PluginController.swift, line 103
Assertion failed: file flutter_reactive_ble/PluginController.swift, line 103
* thread #1, queue = 'com.apple.main-thread', stop reason = Assertion failed
frame #0: 0x00000001a9cb8e08 libswiftCore.dylib`_swift_runtime_on_report
libswiftCore.dylib`_swift_runtime_on_report:
-> 0x1a9cb8e08 <+0>: ret
libswiftCore.dylib`_swift_reportToDebugger:
0x1a9cb8e0c <+0>: b 0x1a9cb8e08 ; _swift_runtime_on_report
libswiftCore.dylib`_swift_shouldReportFatalErrorsToDebugger:
0x1a9cb8e10 <+0>: adrp x8, 346593
0x1a9cb8e14 <+4>: ldrb w0, [x8, #0x7c8]
Target 0: (Runner) stopped.
Lost connection to device.
For reference the assert that fails is the guard let sink = conntext.characteristicValueUpdateSink line in the swift code block:
onCharacteristicValueUpdate: papply(weak: self) {
context, central, characteristic, value, error in
guard let sink = context.characteristicValueUpdateSink
else { assert(false); return }
let message = CharacteristicValueInfo.with {
$0.characteristic = CharacteristicAddress.with {
$0.characteristicUuid = Uuid.with { $0.data = characteristic.id.data }
$0.serviceUuid = Uuid.with { $0.data = characteristic.serviceID.data }
$0.deviceID = characteristic.peripheralID.uuidString
}
if let value = value {
$0.value = value
}
if let error = error {
$0.failure = GenericFailure.with {
$0.code = Int32(CharacteristicValueUpdateFailure.unknown.rawValue)
$0.message = "\(error)"
}
}
}
sink.add(.success(message))
}
The assert is doing what it is supposed to do but I cannot figure out what I'm not doing right in the way I set up and make the call to subscribeToCharacteristic. I suspect it may be the way the Stream is set up and not getting passed down properly to the swift code since the assert seems to be guarding against a null Sink? But I really don't have enough experience.
Help is greatly appreciated!!

Related

Flutter: Failed assertion: 'file.absolute.existsSync()': is not true

In my app, a user can send a file to others in a group chat. First, the user records some audio using their mic. The file is then touched up using FFMPEG. Then, the file is uploaded to Firebase Cloud Storage and if this is successful, a record is written in Firebase Realtime Database.
I'm getting the error below when the user records a long audio file and then presses submit. It almost seems as though FFMPEG hasn't finished processing the file...but I thought I used my async/await correctly to make sure that this processing is finished before moving on?
##MyAppFile## saveMyAppFileToCloudStorage Error: 'package:firebase_storage/src/reference.dart': Failed assertion: line 127 pos 12: 'file.absolute.existsSync()': is not true.
Psuedo-code:
User records audio
Audio file is processed using FFMPEG and the new processed file is created on the user's phone
User hits submit, uploading the file to Cloud Storage and, if successful, writing a record to Realtime Database
Order of Functions After User Hits Submit:
msgInput.dart -> sendMyAppFile()
msgInput.dart -> prepareMyAppFileForSending()
msgInput.dart -> runFFMPEGHighLow()
message_dao.dart -> sendMyAppFile()
message_dao.dart -> saveMyAppFileToCloudStorage() //ERROR COMES FROM THIS FUNCTION
The Code:
//msgInput.dart
Future<void> sendMyAppFile() async {
if (sendableMyAppFileExists == 1) {
final MyAppFileReadyToBeSent = await prepareMyAppFileForSending();
if (MyAppFileReadyToBeSent == '1') {
messageDao.sendMyAppFile(MyAppFile, filepath, filename);
} else {
}
}
setState(() {
sendableMyAppFileExists = 0;
});
}
Future<String> prepareMyAppFileForSending() async {
if (sendableMyAppFileExists == 1) {
if (recordedMyAppFileFilterID == '1') {
await runFFMPEGHighLow('1');
return '1';
}
if (recordedMyAppFileFilterID == '2') {
await runFFMPEGHighLow('2');
return '1';
}
}
return '0';
}
Future<void> runFFMPEGHighLow(String filterID) async {
if (filterID != '1' && filterID != '2') {
return;
}
if (sendableMyAppFileExists == 1) {
if (filterID == '1') {
await FFmpegKit.executeAsync(/*...parms...*/);
setState(() {
currentMyAppFileFilename = currentMyAppFileFilename + '1.mp3';
});
}
if (filterID == '2') {
await FFmpegKit.executeAsync(/*...parms...*/);
setState(() {
currentMyAppFileFilename = currentMyAppFileFilename + '2.mp3';
});
}
}
}
//message_dao.dart
void sendMyAppFile(ChatData MyAppFile, String filepath, String filename) {
saveMyAppFileToCloudStorage(filepath, filename).then((value) {
if (value == true) {
saveMyAppFileToRTDB(MyAppFile);
}
});
}
Future<bool> saveMyAppFileToCloudStorage(String filepath, String filename) async {
//filepath: /data/user/0/com.example.MyApp/app_flutter/MyApp/MyAppAudioFiles/MyAppFiles/2d7af6ae-6361-4be5-8209-8498dd17d77d1.mp3
//filename: 2d7af6ae-6361-4be5-8209-8498dd17d77d1.mp3
_firebaseStoragePath = MyAppFileStorageDir + filename;
File file = File(filepath);
try {
await _firebaseStorage
.ref(_firebaseStoragePath)
.putFile(file);
return true;
} catch (e) {
print('##MyAppFile## saveMyAppFileToCloudStorage Error: ' + e.toString()); //ERROR COMES FROM THIS LINE
return false;
}
return true;
}
I assume you're using the package ffmpeg_kit_flutter.
First, why it's not working: execute and executeAsync return FFmpegSession objects. The run of FFmpeg doesn't need to be finished for these methods to complete. In fact, the returned session object has methods like getState to monitor whether the run of FFmpeg has completed.
A good way to fix this: The documentation for executeAsync has a hint for what to do here.
Note that this method returns immediately and does not wait the execution to complete. You must use an FFmpegSessionCompleteCallback if you want to be notified about the result.
You can set a completion callback by passing a function to executeAsync. Here's the full function signature from the docs:
Future<FFmpegSession> executeAsync(
String command,
[FFmpegSessionCompleteCallback? completeCallback = null,
LogCallback? logCallback = null,
StatisticsCallback? statisticsCallback = null]
)
FFmpegSessionCompleteCallback is just a function that accepts an FFmpegSession and returns nothing. You can provide your own.
void someCompletionFunction() {
setState(() {
currentMyAppFileFilename = currentMyAppFileFilename + '1.mp3';
});
}
await FFmpegKit.executeAsync(/*...parms...*/, someCompletionFunction);
Future vs callback: If you prefer to use Futures and async-await instead of callbacks, you'll need to create your own Future and update it in the callback. See Dart, how to create a future to return in your own functions? for an example.

flutter_libserialport write OK but read unhandled exception

I'm trying to implement a simple at cmd application.
I modified the flutter_libserialport example.
https://pub.dev/packages/flutter_libserialport
simply,
replace the floatButton action to my own reTest() function
floatingActionButton: FloatingActionButton(
child: Icon(Icons.refresh),
// onPressed: initPorts,
onPressed: rwTest,
),
and my rwTest below,
Future<void> rwTest() async {
for (var p in availablePorts) {
if (p == 'COM115') {
print(p);
List<int> d = [65, 84, 13];
Uint8List bytes = Uint8List.fromList(d);
SerialPort port = SerialPort(p);
SerialPortReader reader = SerialPortReader(port, timeout: 10000);
try {
port.openReadWrite();
print(port.write(bytes));
await reader.stream.listen((data) {
print('received : $data');
});
port.close();
} on SerialPortError catch (_, err) {
if (port.isOpen) {
port.close();
print('serial port error');
}
}
}
}
}
my device is shown as COM115 so I put the fixed value.
and the "write" operation was success
but when I use "reader.stream.listen()"
SerialPortError occurs as below
flutter: COM115
flutter: 3
[ERROR:flutter/lib/ui/ui_dart_state.cc(209)] Unhandled Exception: SerialPortError: ÀÛ¾÷À» ¿Ï·áÇß½À´Ï´Ù., errno = 0
I guess the usage of "listen" was wrong, but I don't know how to fix it.
anyone can help me to fix it?
You just need to remove the port.close() in try{} ... and I think you should also set the timeout = 1,
Also, it is better to set a configuration before using the port
Note: you can just access the port through name, without looping over all the available ports.
Future<void> rwTest() async {
List<int> d = [65, 84, 13];
Uint8List bytes = Uint8List.fromList(d);
SerialPort port = SerialPort('COM115');
// configuration
final configu = SerialPortConfig();
configu.baudRate = 9600;
configu.bits = 8;
configu.parity = 0;
port.config = configu;
SerialPortReader reader = SerialPortReader(port, timeout: 10);
try {
port.openReadWrite();
print(port.write(bytes));
await reader.stream.listen((data) {
print('received : $data');
});
} on SerialPortError catch (_, err) {
if (port.isOpen) {
port.close();
print('serial port error');
}
}
}
The serial port need to open as far as the communication is active to send and receive data
Remove the port close method call
try {
port.openReadWrite();
print(port.write(bytes));
await reader.stream.listen((data) {
print('received : $data');
});
//port.close(); --> remove this line
}
On exit close the reader stream and then close the port

Flutter flutter_blue label printer issue

I am new in flutter.
I am building an app to print on the ble label printer via Bluetooth.
I use flutter_blue library. I had successfully connected to the device and found the target characteristic. My problem is that when I call write() function of the characteristic printer show on display “waiting for data” and nothing is printed.
Could you please help me with the data format I need to put into the method?
I use utf8 encoded string as data.
Future<void> _print() async {
var serviceUuid = '0000ff00-0000-1000-8000-00805f9b34fb';
var characteristicsUuid = '0000ff02-0000-1000-8000-00805f9b34fb';
_services.forEach((service) {
if(service.uuid.toString().toLowerCase() == serviceUuid) {
printService = service;
for (var c in printService!.characteristics) {
if(c.uuid.toString().toLowerCase() == characteristicsUuid) {
var ZPL_TEST_LABEL = 'Hello WeChat!\r\n';
printCharacteristic = c;
writeData(printCharacteristic, ZPL_TEST_LABEL);
}
}
}});
}
Future<void> writeData(characteristic, data) async{
if (characteristic == null) return;
try {
var utf8Encoded = utf8.encode(data);
debugPrint(data.toString());
var result = await characteristic.write(utf8Encoded);
debugPrint(result.toString());
} catch (e) {
debugPrint(e.toString());
}
}
My first step is to print at least a string, could you pls help me?

How can I get multiple messages from dart isolate?

How can I get multiple messages from dart isolate?
I'm trying to create an excel file and want to do some operation on that file in an isolate. Before doing an operation on that file, I want to return an message to main isolate, that excel file is created.
Here is function goes in isolate :
foo(String filePath){
// create excel file
var bytes = File(filePath).readAsBytesSync();
var excel = Excel.decodeBytes(bytes);
//HERE I WANT TO SEND THE MESSAGE THAT CREATING EXCEL FILE IS DONE
// some operatoin on excel file
var result = doSomeOperation(excel);
return result;
}
Main isolate code :
var result = await compute(foo, filePath);
What should I do to get creating file message before the actual result comes?
For excel, I'm using excel: ^2.0.0-null-safety-3 package.
Compute only returns one result. If you want to pass multiple 'events' back to the main isolate then you need to use the full Isolate logic (with sendPort and receivePort).
For example, the following code runs in an isolate, and downloads a file while emitting float values to represent progress, potentially a String to indicate log messages and then a bool to indicate success or failure upon completion.
Future<void> isolateDownload(
DownloadRequest request) async {
final sendPort = request.sendPort;
if (sendPort != null) {
var success = false;
var errorMessage = '';
var url = Uri.parse('a_url_based_on_request');
IOSink? out;
try {
http.StreamedResponse response =
await http.Client().send(http.Request('GET', url));
if (response.statusCode == 200) {
var filePath =
join(request.destinationDirPath, '${request.fileName}.ZIP');
var contentLength = response.contentLength;
var bytesLoadedUpdateInterval = (contentLength ?? 0) / 50;
var bytesLoaded = 0;
var bytesLoadedAtLastUpdate = 0;
out = File(filePath).openWrite();
await response.stream.forEach((chunk) {
out?.add(chunk);
bytesLoaded += chunk.length;
// update if enough bytes have passed since last update
if (contentLength != null &&
bytesLoaded - bytesLoadedAtLastUpdate >
bytesLoadedUpdateInterval) {
sendPort.send(bytesLoaded / contentLength);
bytesLoadedAtLastUpdate = bytesLoaded;
}
});
success = true;
if (contentLength != null) {
sendPort.send(1.0); // send 100% downloaded message
}
} else {
errorMessage =
'Download of ${request.fileName} '
'received response ${response.statusCode} - ${response.reasonPhrase}';
}
} catch (e) {
errorMessage = 'Download of ${request.chartType}:${request.chartName} '
'received error $e';
} finally {
await out?.flush();
await out?.close();
if (errorMessage.isNotEmpty) {
sendPort.send(errorMessage);
}
sendPort.send(success);
}
}
}
The code that spawns the isolate then simply checks for the type of the message passed to it to determine the action.
Future<bool> _downloadInBackground(
DownloadRequest request) async {
var receivePort = ReceivePort();
request.sendPort = receivePort.sendPort;
var isDone = Completer();
var success = false;
receivePort.listen((message) {
if (message is double) {
showUpdate(message);
}
if (message is String) {
log.fine(message); // log error messages
}
if (message is bool) {
success = message; // end with success or failure
receivePort.close();
}
}, onDone: () => isDone.complete()); // wraps up
await Isolate.spawn(isolateDownload, request);
await isDone.future;
return success;
}

alternative to using 'await' with lazy_static! macro in rust?

I want to use Async MongoDB in a project.
I don't want to pass around the client because it would need to go around multiple tasks and threads. So I kept a static client using lazy_static. However, I can't use await in the initialization block.
What can I do to work around this?
Suggestions for doing it without lazy_static are also welcome.
use std::env;
use futures::stream::StreamExt;
use mongodb::{
bson::{doc, Bson},
options::ClientOptions,
Client,
};
lazy_static! {
static ref MONGO: Option<Client> = {
if let Ok(token) = env::var("MONGO_AUTH") {
if let Ok(client_options) = ClientOptions::parse(&token).await
^^^^^
{
if let Ok(client) = Client::with_options(client_options) {
return Some(client);
}
}
}
return None;
};
}
I went with this approach based on someone's suggestion in rust forums.
static MONGO: OnceCell<Client> = OnceCell::new();
static MONGO_INITIALIZED: OnceCell<tokio::sync::Mutex<bool>> = OnceCell::new();
pub async fn get_mongo() -> Option<&'static Client> {
// this is racy, but that's OK: it's just a fast case
let client_option = MONGO.get();
if let Some(_) = client_option {
return client_option;
}
// it hasn't been initialized yet, so let's grab the lock & try to
// initialize it
let initializing_mutex = MONGO_INITIALIZED.get_or_init(|| tokio::sync::Mutex::new(false));
// this will wait if another task is currently initializing the client
let mut initialized = initializing_mutex.lock().await;
// if initialized is true, then someone else initialized it while we waited,
// and we can just skip this part.
if !*initialized {
// no one else has initialized it yet, so
if let Ok(token) = env::var("MONGO_AUTH") {
if let Ok(client_options) = ClientOptions::parse(&token).await {
if let Ok(client) = Client::with_options(client_options) {
if let Ok(_) = MONGO.set(client) {
*initialized = true;
}
}
}
}
}
drop(initialized);
MONGO.get()
}
However I can't use await in the initialization block.
You can skirt this with futures::executor::block_on
use once_cell::sync::Lazy;
// ...
static PGCLIENT: Lazy<Client> = Lazy::new(|| {
let client: Client = futures::executor::block_on(async {
let (client, connection) = tokio_postgres::connect(
"postgres:///?user=ecarroll&port=5432&host=/run/postgresql",
NoTls,
)
.await
.unwrap();
tokio::spawn(async move {
if let Err(e) = connection.await {
eprintln!("connection error: {}", e);
}
});
client
});
client
});
What we have is a non-async closure blocking in a single thread until the resolution of the future.
Create a new runtime from tokio::runtime::Runtime and use block_on to block the current thread until completion.
// database.rs
use tokio::runtime::Runtime;
use mongodb::Client;
pub fn connect_sync() -> Client {
Runtime::new().unwrap().block_on(async {
Client::with_uri_str("mongodb://localhost:27017").await.unwrap()
})
}
// main.rs
mod database;
lazy_static! {
static ref CLIENT: mongodb::Client = database::connect_sync();
}
#[actix_web::main]
async fn main() {
let collection = &CLIENT.database("db_name").collection("coll_name");
// ...
}
Use the async_once crate.
use async_once::AsyncOnce;
use lazy_static::lazy_static;
use mongodb::Client;
lazy_static! {
static ref CLIENT: AsyncOnce<Client> = AsyncOnce::new(async {
Client::with_uri_str(std::env::var("MONGO_URL").expect("MONGO_URL not set"))
.await
.unwrap()
});
}
then
CLIENT.get().await;