import 'dart:async'; import 'dart:typed_data'; import 'package:usb_serial/usb_serial.dart'; import '../models/serial_config.dart'; import 'device_log.dart'; /// 串口连接状态 enum SerialConnectionState { disconnected, connecting, connected, error } /// 串口服务 /// /// 封装 [UsbSerial] 库,对外暴露稳定的连接/读写接口, /// 屏蔽底层 USB 设备的插拔、权限请求、参数配置等细节。 class SerialPortService { UsbPort? _port; UsbDevice? _device; StreamSubscription? _readSub; final _connectionCtrl = StreamController.broadcast(); final _dataCtrl = StreamController.broadcast(); final _errorCtrl = StreamController.broadcast(); SerialConnectionState _state = SerialConnectionState.disconnected; String? _lastError; /// 当前连接状态 SerialConnectionState get state => _state; /// 最后一次错误信息 String? get lastError => _lastError; /// 是否已连接 bool get isConnected => _state == SerialConnectionState.connected; /// 当前连接设备(仅在已连接时可用) UsbDevice? get currentDevice => _device; /// 连接状态变更流 Stream get connectionStateChanges => _connectionCtrl.stream; /// 接收到的数据流 Stream get onData => _dataCtrl.stream; /// 错误信息流 Stream get onError => _errorCtrl.stream; /// 列出当前连接的 USB 串口设备 Future> listDevices() async { return await UsbSerial.listDevices(); } /// 设备名(vendor/product 拼接) static String deviceLabel(UsbDevice d) { final v = _hex(d.vid); final p = _hex(d.pid); final name = d.productName ?? d.manufacturerName ?? 'USB Serial'; return '$name [$v:$p]'; } static String _hex(int? v) => (v ?? 0).toRadixString(16).padLeft(4, '0').toUpperCase(); /// 打开指定设备并按 [config] 配置参数 Future connect(UsbDevice device, SerialConfig config) async { if (_state == SerialConnectionState.connected || _state == SerialConnectionState.connecting) { await disconnect(); } DeviceLog.info('连接串口设备: ${deviceLabel(device)}, ' 'baud=${config.baudRate} dataBits=${config.dataBits} ' 'stopBits=${config.stopBits} parity=${config.parity.name}'); _setState(SerialConnectionState.connecting); try { // UsbSerial.create 内部会触发 Android USB 权限弹窗 final port = await device.create(); if (port == null) { _fail('创建设备端口失败(可能未授予 USB 权限)'); return false; } final opened = await port.open(); if (!opened) { await port.close(); _fail('打开端口失败'); return false; } await _applyConfig(port, config); _port = port; _device = device; _subscribeStream(port); _setState(SerialConnectionState.connected); DeviceLog.info('串口已连接: ${deviceLabel(device)}'); return true; } catch (e) { _fail('连接异常: $e'); return false; } } /// 应用串口参数 Future _applyConfig(UsbPort port, SerialConfig config) async { await port.setPortParameters( config.baudRate, config.dataBits, config.stopBits, _parityToCode(config.parity), ); await port.setFlowControl(_flowControlToCode(config.flowControl)); } /// 断开当前连接 Future disconnect() async { if (_state == SerialConnectionState.disconnected) return; await _readSub?.cancel(); _readSub = null; try { await _port?.close(); } catch (_) {} _port = null; _device = null; _setState(SerialConnectionState.disconnected); DeviceLog.info('串口已断开'); } /// 写入数据,返回成功写入的字节数(设备库本身不返回字节数,连接失败时返回 0) Future write(Uint8List data) async { final port = _port; if (port == null || !isConnected) { _emitError('串口未连接,写入失败'); return 0; } try { await port.write(data); DeviceLog.info('串口 TX: ${data.length} 字节'); return data.length; } catch (e) { _emitError('写入异常: $e', error: e); return 0; } } /// 释放资源(应用退出时调用) Future dispose() async { await disconnect(); await _connectionCtrl.close(); await _dataCtrl.close(); await _errorCtrl.close(); } void _subscribeStream(UsbPort port) { _readSub?.cancel(); final stream = port.inputStream; if (stream == null) { _emitError('当前端口无输入流'); return; } _readSub = stream.listen( (data) { if (data.isNotEmpty) { DeviceLog.info('串口 RX: ${data.length} 字节'); _dataCtrl.add(data); } }, onError: (Object e) { _emitError('读取异常: $e', error: e); }, onDone: () { DeviceLog.info('串口输入流关闭'); _setState(SerialConnectionState.disconnected); }, cancelOnError: false, ); } void _setState(SerialConnectionState s) { _state = s; if (s != SerialConnectionState.error) { _lastError = null; } if (!_connectionCtrl.isClosed) { _connectionCtrl.add(s); } } void _fail(String message) { _lastError = message; _setState(SerialConnectionState.error); _emitError(message); } void _emitError(String message, {Object? error}) { _lastError = message; if (error != null) { DeviceLog.severe(message, error: error); } else { DeviceLog.warn(message); } if (!_errorCtrl.isClosed) { _errorCtrl.add(message); } } static int _parityToCode(SerialParity p) { return switch (p) { SerialParity.none => UsbPort.PARITY_NONE, SerialParity.odd => UsbPort.PARITY_ODD, SerialParity.even => UsbPort.PARITY_EVEN, SerialParity.mark => UsbPort.PARITY_MARK, SerialParity.space => UsbPort.PARITY_SPACE, }; } static int _flowControlToCode(SerialFlowControl f) { return switch (f) { SerialFlowControl.none => UsbPort.FLOW_CONTROL_OFF, SerialFlowControl.rtsCts => UsbPort.FLOW_CONTROL_RTS_CTS, SerialFlowControl.xonXoff => UsbPort.FLOW_CONTROL_XON_XOFF, SerialFlowControl.dtrDsr => UsbPort.FLOW_CONTROL_DSR_DTR, }; } }