Refactor USB connection handling to use scheduled closure and improve error management in USB services

This commit is contained in:
just_stuff_tm
2026-03-02 04:11:52 -05:00
committed by just-stuff-tm
parent 4b24506310
commit dcad5c586d
4 changed files with 81 additions and 24 deletions
@@ -105,11 +105,8 @@ class MainActivity : FlutterActivity() {
"connect" -> handleUsbConnect(call, result) "connect" -> handleUsbConnect(call, result)
"write" -> handleUsbWrite(call, result) "write" -> handleUsbWrite(call, result)
"disconnect" -> { "disconnect" -> {
usbIoExecutor.execute { scheduleCloseUsbConnection {
closeUsbConnection() result.success(null)
mainHandler.post {
result.success(null)
}
} }
} }
else -> result.notImplemented() else -> result.notImplemented()
@@ -315,7 +312,7 @@ class MainActivity : FlutterActivity() {
null, null,
) )
} }
closeUsbConnection() scheduleCloseUsbConnection()
} }
}, },
).also { manager -> ).also { manager ->
@@ -338,6 +335,16 @@ class MainActivity : FlutterActivity() {
return driver.ports.firstOrNull() return driver.ports.firstOrNull()
} }
private fun scheduleCloseUsbConnection(onComplete: (() -> Unit)? = null) {
usbIoExecutor.execute {
closeUsbConnection()
if (onComplete != null) {
mainHandler.post(onComplete)
}
}
}
@Synchronized
private fun closeUsbConnection() { private fun closeUsbConnection() {
try { try {
ioManager?.stop() ioManager?.stop()
@@ -381,12 +388,13 @@ class MainActivity : FlutterActivity() {
} }
if (connectedDeviceName == detachedName) { if (connectedDeviceName == detachedName) {
closeUsbConnection() scheduleCloseUsbConnection {
eventSink?.error( eventSink?.error(
"usb_device_detached", "usb_device_detached",
"USB device was disconnected", "USB device was disconnected",
null, null,
) )
}
} }
} }
+10 -1
View File
@@ -1266,14 +1266,23 @@ class MeshCoreConnector extends ChangeNotifier {
_selfInfoRetryTimer?.cancel(); _selfInfoRetryTimer?.cancel();
if (PlatformInfo.isWeb && if (PlatformInfo.isWeb &&
_activeTransport == MeshCoreTransportType.bluetooth) { _activeTransport == MeshCoreTransportType.bluetooth) {
_selfInfoRetryTimer = Timer(const Duration(seconds: 10), () { var attempts = 0;
const maxAttempts = 3;
_selfInfoRetryTimer = Timer.periodic(const Duration(seconds: 10), (
timer,
) {
if (!isConnected || !_awaitingSelfInfo) { if (!isConnected || !_awaitingSelfInfo) {
timer.cancel();
return; return;
} }
if (_isLoadingContacts || _isSyncingChannels || _channelSyncInFlight) { if (_isLoadingContacts || _isSyncingChannels || _channelSyncInFlight) {
return; return;
} }
attempts += 1;
unawaited(sendFrame(buildAppStartFrame())); unawaited(sendFrame(buildAppStartFrame()));
if (attempts >= maxAttempts) {
timer.cancel();
}
}); });
return; return;
} }
+26 -6
View File
@@ -186,8 +186,7 @@ class UsbSerialService {
} }
void dispose() { void dispose() {
unawaited(disconnect()); unawaited(disconnect().whenComplete(_closeFrameController));
unawaited(_frameController.close());
} }
void _handleSerialData(FlSerialEventArgs event) { void _handleSerialData(FlSerialEventArgs event) {
@@ -197,7 +196,7 @@ class UsbSerialService {
_ingestRawBytes(Uint8List.fromList(bytes)); _ingestRawBytes(Uint8List.fromList(bytes));
} }
} catch (error, stack) { } catch (error, stack) {
_frameController.addError(error, stack); _addFrameError(error, stack);
} }
} }
@@ -210,13 +209,13 @@ class UsbSerialService {
_ingestRawBytes(data.buffer.asUint8List()); _ingestRawBytes(data.buffer.asUint8List());
return; return;
} }
_frameController.addError( _addFrameError(
StateError('Unexpected Android USB event payload: ${data.runtimeType}'), StateError('Unexpected Android USB event payload: ${data.runtimeType}'),
); );
} }
void _handleSerialError(Object error, [StackTrace? stackTrace]) { void _handleSerialError(Object error, [StackTrace? stackTrace]) {
_frameController.addError(error, stackTrace); _addFrameError(error, stackTrace);
} }
void _handleSerialDone() { void _handleSerialDone() {
@@ -231,10 +230,31 @@ class UsbSerialService {
); );
continue; continue;
} }
_frameController.add(packet.payload); _addFrame(packet.payload);
} }
} }
void _addFrame(Uint8List payload) {
if (_frameController.isClosed) {
return;
}
_frameController.add(payload);
}
void _addFrameError(Object error, [StackTrace? stackTrace]) {
if (_frameController.isClosed) {
return;
}
_frameController.addError(error, stackTrace);
}
Future<void> _closeFrameController() async {
if (_frameController.isClosed) {
return;
}
await _frameController.close();
}
void _logFrameSummary(String prefix, Uint8List bytes) { void _logFrameSummary(String prefix, Uint8List bytes) {
if (bytes.isEmpty) { if (bytes.isEmpty) {
debugPrint('$prefix len=0'); debugPrint('$prefix len=0');
+25 -5
View File
@@ -160,8 +160,7 @@ class UsbSerialService {
} }
void dispose() { void dispose() {
unawaited(disconnect()); unawaited(disconnect().whenComplete(_closeFrameController));
unawaited(_frameController.close());
} }
Future<List<JSObject>> _getAuthorizedPorts() async { Future<List<JSObject>> _getAuthorizedPorts() async {
@@ -285,12 +284,12 @@ class UsbSerialService {
} }
} catch (error, stackTrace) { } catch (error, stackTrace) {
if (_status == UsbSerialStatus.connected) { if (_status == UsbSerialStatus.connected) {
_frameController.addError(error, stackTrace); _addFrameError(error, stackTrace);
} }
} finally { } finally {
_releaseLock(reader); _releaseLock(reader);
if (_status == UsbSerialStatus.connected && identical(reader, _reader)) { if (_status == UsbSerialStatus.connected && identical(reader, _reader)) {
_frameController.addError(StateError('USB serial connection closed')); _addFrameError(StateError('USB serial connection closed'));
} }
} }
} }
@@ -402,10 +401,31 @@ class UsbSerialService {
); );
continue; continue;
} }
_frameController.add(packet.payload); _addFrame(packet.payload);
} }
} }
void _addFrame(Uint8List payload) {
if (_frameController.isClosed) {
return;
}
_frameController.add(payload);
}
void _addFrameError(Object error, [StackTrace? stackTrace]) {
if (_frameController.isClosed) {
return;
}
_frameController.addError(error, stackTrace);
}
Future<void> _closeFrameController() async {
if (_frameController.isClosed) {
return;
}
await _frameController.close();
}
void _logFrameSummary(String prefix, Uint8List bytes) { void _logFrameSummary(String prefix, Uint8List bytes) {
if (bytes.isEmpty) { if (bytes.isEmpty) {
debugPrint('$prefix len=0'); debugPrint('$prefix len=0');