Refactor USB handling to improve connection management and error cleanup

chore/offband-rebrand
just_stuff_tm 5 months ago committed by just-stuff-tm
parent f462815775
commit 98f7c3b088

@ -21,6 +21,8 @@ import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel
import java.util.Locale import java.util.Locale
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
class MainActivity : FlutterActivity() { class MainActivity : FlutterActivity() {
private val usbMethodChannelName = "meshcore_open/android_usb_serial" private val usbMethodChannelName = "meshcore_open/android_usb_serial"
@ -29,6 +31,7 @@ class MainActivity : FlutterActivity() {
private lateinit var usbManager: UsbManager private lateinit var usbManager: UsbManager
private val mainHandler = Handler(Looper.getMainLooper()) private val mainHandler = Handler(Looper.getMainLooper())
private val usbIoExecutor: ExecutorService = Executors.newSingleThreadExecutor()
private var eventSink: EventChannel.EventSink? = null private var eventSink: EventChannel.EventSink? = null
private var usbConnection: UsbDeviceConnection? = null private var usbConnection: UsbDeviceConnection? = null
@ -112,6 +115,7 @@ class MainActivity : FlutterActivity() {
override fun onDestroy() { override fun onDestroy() {
closeUsbConnection() closeUsbConnection()
usbIoExecutor.shutdownNow()
unregisterReceiver(permissionReceiver) unregisterReceiver(permissionReceiver)
super.onDestroy() super.onDestroy()
} }
@ -191,13 +195,19 @@ class MainActivity : FlutterActivity() {
return return
} }
usbIoExecutor.execute {
try { try {
port.write(data, 1000) port.write(data, 1000)
mainHandler.post {
result.success(null) result.success(null)
}
} catch (error: Exception) { } catch (error: Exception) {
mainHandler.post {
result.error("usb_write_failed", error.message, null) result.error("usb_write_failed", error.message, null)
} }
} }
}
}
private fun findUsbDevice(portName: String): UsbDevice? { private fun findUsbDevice(portName: String): UsbDevice? {
return usbManager.deviceList.values.firstOrNull { it.deviceName == portName } return usbManager.deviceList.values.firstOrNull { it.deviceName == portName }

@ -981,7 +981,6 @@ class MeshCoreConnector extends ChangeNotifier {
_selfInfoRetryTimer = null; _selfInfoRetryTimer = null;
_hasReceivedDeviceInfo = false; _hasReceivedDeviceInfo = false;
_pendingInitialChannelSync = false; _pendingInitialChannelSync = false;
_hasReceivedDeviceInfo = false;
} }
bool get _shouldAutoReconnect => bool get _shouldAutoReconnect =>

@ -27,6 +27,7 @@ class UsbSerialDecodedPacket {
class UsbSerialFrameDecoder { class UsbSerialFrameDecoder {
final List<int> _rxBuffer = <int>[]; final List<int> _rxBuffer = <int>[];
int _startIndex = 0;
List<UsbSerialDecodedPacket> ingest(Uint8List bytes) { List<UsbSerialDecodedPacket> ingest(Uint8List bytes) {
if (bytes.isEmpty) { if (bytes.isEmpty) {
@ -37,34 +38,56 @@ class UsbSerialFrameDecoder {
final packets = <UsbSerialDecodedPacket>[]; final packets = <UsbSerialDecodedPacket>[];
while (true) { while (true) {
if (_rxBuffer.isEmpty) { if (_startIndex >= _rxBuffer.length) {
_rxBuffer.clear();
_startIndex = 0;
return packets; return packets;
} }
if (_rxBuffer.first != usbSerialRxFrameStart && if (_rxBuffer[_startIndex] != usbSerialRxFrameStart &&
_rxBuffer.first != usbSerialTxFrameStart) { _rxBuffer[_startIndex] != usbSerialTxFrameStart) {
_rxBuffer.removeAt(0); _startIndex++;
_compactBufferIfNeeded();
continue; continue;
} }
if (_rxBuffer.length < usbSerialHeaderLength) { final availableLength = _rxBuffer.length - _startIndex;
if (availableLength < usbSerialHeaderLength) {
_compactBufferIfNeeded(force: true);
return packets; return packets;
} }
final payloadLength = _rxBuffer[1] | (_rxBuffer[2] << 8); final payloadLength =
_rxBuffer[_startIndex + 1] | (_rxBuffer[_startIndex + 2] << 8);
final packetLength = usbSerialHeaderLength + payloadLength; final packetLength = usbSerialHeaderLength + payloadLength;
if (_rxBuffer.length < packetLength) { if (availableLength < packetLength) {
_compactBufferIfNeeded(force: true);
return packets; return packets;
} }
final frameStart = _rxBuffer.first; final frameStart = _rxBuffer[_startIndex];
final payload = Uint8List.fromList( final payload = Uint8List.fromList(
_rxBuffer.sublist(usbSerialHeaderLength, packetLength), _rxBuffer.sublist(
_startIndex + usbSerialHeaderLength,
_startIndex + packetLength,
),
); );
_rxBuffer.removeRange(0, packetLength); _startIndex += packetLength;
_compactBufferIfNeeded();
packets.add( packets.add(
UsbSerialDecodedPacket(frameStart: frameStart, payload: payload), 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;
}
} }

@ -88,8 +88,10 @@ class UsbSerialService {
debugPrint('USB serial opened port=$_connectedPortName via Web Serial'); debugPrint('USB serial opened port=$_connectedPortName via Web Serial');
} catch (error) { } catch (error) {
await _cleanupFailedConnect();
_status = UsbSerialStatus.disconnected; _status = UsbSerialStatus.disconnected;
_connectedPortName = null; _connectedPortName = null;
_connectedPortKey = null;
rethrow; rethrow;
} }
} }
@ -205,6 +207,37 @@ class UsbSerialService {
return port.callMethod<JSPromise<JSAny?>>('open'.toJS, options).toDart; return port.callMethod<JSPromise<JSAny?>>('open'.toJS, options).toDart;
} }
Future<void> _cleanupFailedConnect() async {
final reader = _reader;
final writer = _writer;
final port = _port;
_reader = null;
_writer = null;
_port = null;
if (reader != null) {
try {
await reader.callMethod<JSPromise<JSAny?>>('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<JSPromise<JSAny?>>('close'.toJS).toDart;
} catch (_) {
// Ignore cleanup errors after a failed connect.
}
}
}
JSObject? _getReader(JSObject port) { JSObject? _getReader(JSObject port) {
final readable = port.getProperty<JSAny?>('readable'.toJS); final readable = port.getProperty<JSAny?>('readable'.toJS);
if (readable == null) { if (readable == null) {

Loading…
Cancel
Save

Powered by TurnKey Linux.