From 98f7c3b088d9daae2a9ca8b7595665388f280726 Mon Sep 17 00:00:00 2001 From: just_stuff_tm <133525672+just-stuff-tm@users.noreply.github.com> Date: Mon, 2 Mar 2026 01:24:33 -0500 Subject: [PATCH] Refactor USB handling to improve connection management and error cleanup --- .../meshcore/meshcore_open/MainActivity.kt | 20 ++++++--- lib/connector/meshcore_connector.dart | 1 - lib/services/usb_serial_frame_codec.dart | 43 ++++++++++++++----- lib/services/usb_serial_service_web.dart | 33 ++++++++++++++ 4 files changed, 81 insertions(+), 16 deletions(-) diff --git a/android/app/src/main/kotlin/com/meshcore/meshcore_open/MainActivity.kt b/android/app/src/main/kotlin/com/meshcore/meshcore_open/MainActivity.kt index b327b06c..11bca61f 100644 --- a/android/app/src/main/kotlin/com/meshcore/meshcore_open/MainActivity.kt +++ b/android/app/src/main/kotlin/com/meshcore/meshcore_open/MainActivity.kt @@ -21,6 +21,8 @@ import io.flutter.plugin.common.EventChannel import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel import java.util.Locale +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors class MainActivity : FlutterActivity() { private val usbMethodChannelName = "meshcore_open/android_usb_serial" @@ -29,6 +31,7 @@ class MainActivity : FlutterActivity() { private lateinit var usbManager: UsbManager private val mainHandler = Handler(Looper.getMainLooper()) + private val usbIoExecutor: ExecutorService = Executors.newSingleThreadExecutor() private var eventSink: EventChannel.EventSink? = null private var usbConnection: UsbDeviceConnection? = null @@ -112,6 +115,7 @@ class MainActivity : FlutterActivity() { override fun onDestroy() { closeUsbConnection() + usbIoExecutor.shutdownNow() unregisterReceiver(permissionReceiver) super.onDestroy() } @@ -191,11 +195,17 @@ class MainActivity : FlutterActivity() { return } - try { - port.write(data, 1000) - result.success(null) - } catch (error: Exception) { - result.error("usb_write_failed", error.message, null) + usbIoExecutor.execute { + try { + port.write(data, 1000) + mainHandler.post { + result.success(null) + } + } catch (error: Exception) { + mainHandler.post { + result.error("usb_write_failed", error.message, null) + } + } } } diff --git a/lib/connector/meshcore_connector.dart b/lib/connector/meshcore_connector.dart index 4b5fe4f5..392be9a8 100644 --- a/lib/connector/meshcore_connector.dart +++ b/lib/connector/meshcore_connector.dart @@ -981,7 +981,6 @@ class MeshCoreConnector extends ChangeNotifier { _selfInfoRetryTimer = null; _hasReceivedDeviceInfo = false; _pendingInitialChannelSync = false; - _hasReceivedDeviceInfo = false; } bool get _shouldAutoReconnect => diff --git a/lib/services/usb_serial_frame_codec.dart b/lib/services/usb_serial_frame_codec.dart index ee4a17c4..f2ddbb64 100644 --- a/lib/services/usb_serial_frame_codec.dart +++ b/lib/services/usb_serial_frame_codec.dart @@ -27,6 +27,7 @@ class UsbSerialDecodedPacket { class UsbSerialFrameDecoder { final List _rxBuffer = []; + int _startIndex = 0; List ingest(Uint8List bytes) { if (bytes.isEmpty) { @@ -37,34 +38,56 @@ class UsbSerialFrameDecoder { final packets = []; while (true) { - if (_rxBuffer.isEmpty) { + if (_startIndex >= _rxBuffer.length) { + _rxBuffer.clear(); + _startIndex = 0; return packets; } - if (_rxBuffer.first != usbSerialRxFrameStart && - _rxBuffer.first != usbSerialTxFrameStart) { - _rxBuffer.removeAt(0); + if (_rxBuffer[_startIndex] != usbSerialRxFrameStart && + _rxBuffer[_startIndex] != usbSerialTxFrameStart) { + _startIndex++; + _compactBufferIfNeeded(); continue; } - if (_rxBuffer.length < usbSerialHeaderLength) { + final availableLength = _rxBuffer.length - _startIndex; + if (availableLength < usbSerialHeaderLength) { + _compactBufferIfNeeded(force: true); return packets; } - final payloadLength = _rxBuffer[1] | (_rxBuffer[2] << 8); + final payloadLength = + _rxBuffer[_startIndex + 1] | (_rxBuffer[_startIndex + 2] << 8); final packetLength = usbSerialHeaderLength + payloadLength; - if (_rxBuffer.length < packetLength) { + if (availableLength < packetLength) { + _compactBufferIfNeeded(force: true); return packets; } - final frameStart = _rxBuffer.first; + final frameStart = _rxBuffer[_startIndex]; final payload = Uint8List.fromList( - _rxBuffer.sublist(usbSerialHeaderLength, packetLength), + _rxBuffer.sublist( + _startIndex + usbSerialHeaderLength, + _startIndex + packetLength, + ), ); - _rxBuffer.removeRange(0, packetLength); + _startIndex += packetLength; + _compactBufferIfNeeded(); packets.add( UsbSerialDecodedPacket(frameStart: frameStart, payload: payload), ); } } + + void _compactBufferIfNeeded({bool force = false}) { + if (_startIndex == 0) { + return; + } + if (!force && _startIndex < 1024 && _startIndex < (_rxBuffer.length ~/ 2)) { + return; + } + _rxBuffer.removeRange(0, _startIndex); + _startIndex = 0; + } } diff --git a/lib/services/usb_serial_service_web.dart b/lib/services/usb_serial_service_web.dart index 67844dfe..1f0fcb9a 100644 --- a/lib/services/usb_serial_service_web.dart +++ b/lib/services/usb_serial_service_web.dart @@ -88,8 +88,10 @@ class UsbSerialService { debugPrint('USB serial opened port=$_connectedPortName via Web Serial'); } catch (error) { + await _cleanupFailedConnect(); _status = UsbSerialStatus.disconnected; _connectedPortName = null; + _connectedPortKey = null; rethrow; } } @@ -205,6 +207,37 @@ class UsbSerialService { return port.callMethod>('open'.toJS, options).toDart; } + Future _cleanupFailedConnect() async { + final reader = _reader; + final writer = _writer; + final port = _port; + + _reader = null; + _writer = null; + _port = null; + + if (reader != null) { + try { + await reader.callMethod>('cancel'.toJS).toDart; + } catch (_) { + // Ignore cleanup errors after a failed connect. + } + _releaseLock(reader); + } + + if (writer != null) { + _releaseLock(writer); + } + + if (port != null) { + try { + await port.callMethod>('close'.toJS).toDart; + } catch (_) { + // Ignore cleanup errors after a failed connect. + } + } + } + JSObject? _getReader(JSObject port) { final readable = port.getProperty('readable'.toJS); if (readable == null) {