Files
kuaishai2/lib/features/device/services/serial_port_service.dart

226 lines
6.3 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<Uint8List>? _readSub;
final _connectionCtrl = StreamController<SerialConnectionState>.broadcast();
final _dataCtrl = StreamController<Uint8List>.broadcast();
final _errorCtrl = StreamController<String>.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<SerialConnectionState> get connectionStateChanges =>
_connectionCtrl.stream;
/// 接收到的数据流
Stream<Uint8List> get onData => _dataCtrl.stream;
/// 错误信息流
Stream<String> get onError => _errorCtrl.stream;
/// 列出当前连接的 USB 串口设备
Future<List<UsbDevice>> 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<bool> 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<void> _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<void> 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<int> 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<void> 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,
};
}
}