feat(android): 添加 CH934X USB 转串口芯片支持
- 在 AndroidManifest.xml 中添加 USB Host 权限声明 - 集成 CH934X Android SDK 并通过反射调用原生功能 - 实现设备查找、串口读写、GPIO 和 Modem 控制功能 - 添加异常回调机制处理设备拔出等情况 - 提供 Stream 数据流支持实时串口数据监听 - 完善单元测试覆盖所有核心功能模块
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
import 'ch934x_device_type.dart';
|
||||
|
||||
/// 单个 CH934X 设备所挂载串口的信息。
|
||||
///
|
||||
/// 对应 Android 端 `UsbHelper.getCH934XSerialPortList` 返
|
||||
/// 回的 `UsbSerial` 数组中每个元素的 Dart 描述,通常足以
|
||||
/// 用来调用 `UsbSerial.init` 打开对应的串口通道。
|
||||
class Ch934xSerialPortInfo {
|
||||
const Ch934xSerialPortInfo({
|
||||
required this.portIndex,
|
||||
this.devicePath,
|
||||
this.driverName,
|
||||
});
|
||||
|
||||
/// 串口在所属设备上的索引(从 0 开始)。
|
||||
final int portIndex;
|
||||
|
||||
/// 底层串口节点路径(若原生层提供)。
|
||||
final String? devicePath;
|
||||
|
||||
/// 驱动或端口名(若原生层提供)。
|
||||
final String? driverName;
|
||||
|
||||
/// 从原生层返回的 Map 还原对象,字段缺失时使用安全默认值。
|
||||
factory Ch934xSerialPortInfo.fromMap(Map<dynamic, dynamic> map) {
|
||||
final indexValue = map['portIndex'] ?? map['serialPortIndex'];
|
||||
return Ch934xSerialPortInfo(
|
||||
portIndex: indexValue is int ? indexValue : 0,
|
||||
devicePath: map['devicePath'] as String?,
|
||||
driverName: map['driverName'] as String?,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 文档 4.1.4 中 `UsbHelper.getCH934XDeviceList` 返回的设备信息。
|
||||
///
|
||||
/// 字段命名遵循 Java 端的驼峰式命名,通过 `fromMap` 解析原生层结果。
|
||||
class Ch934xDeviceInfo {
|
||||
const Ch934xDeviceInfo({
|
||||
required this.deviceId,
|
||||
required this.vendorId,
|
||||
required this.productId,
|
||||
required this.deviceType,
|
||||
this.serialNumber,
|
||||
this.productName,
|
||||
this.manufacturerName,
|
||||
this.interfaceCount = 0,
|
||||
this.serialPorts = const <Ch934xSerialPortInfo>[],
|
||||
});
|
||||
|
||||
/// Android `UsbDevice.getDeviceId()`。
|
||||
final int deviceId;
|
||||
|
||||
/// USB 厂商 ID。
|
||||
final int vendorId;
|
||||
|
||||
/// USB 产品 ID。
|
||||
final int productId;
|
||||
|
||||
/// 通过 [Ch934xDeviceType] 中的常量值标识。
|
||||
final int deviceType;
|
||||
|
||||
/// 通过 `UsbHelper.CH934XSerialNum` 获取的序列号;非 CH934X 设备时为 null。
|
||||
final String? serialNumber;
|
||||
|
||||
/// 设备产品名(若原生层提供)。
|
||||
final String? productName;
|
||||
|
||||
/// 设备厂商名(若原生层提供)。
|
||||
final String? manufacturerName;
|
||||
|
||||
/// 该设备暴露的 USB 接口数量。
|
||||
final int interfaceCount;
|
||||
|
||||
/// 关联的串口列表,部分设备可能为空。
|
||||
final List<Ch934xSerialPortInfo> serialPorts;
|
||||
|
||||
/// 判断当前设备是否被原生层识别为 CH934X 系列。
|
||||
bool get isCh934x => deviceType >= Ch934xDeviceType.ch9344 &&
|
||||
deviceType <= Ch934xDeviceType.ch934xOther;
|
||||
|
||||
/// 从原生层返回值反序列化,容错处理缺失字段。
|
||||
factory Ch934xDeviceInfo.fromMap(Map<dynamic, dynamic> map) {
|
||||
final rawPorts = map['serialPorts'];
|
||||
final ports = <Ch934xSerialPortInfo>[];
|
||||
if (rawPorts is List) {
|
||||
for (final entry in rawPorts) {
|
||||
if (entry is Map) {
|
||||
ports.add(
|
||||
Ch934xSerialPortInfo.fromMap(Map<dynamic, dynamic>.from(entry)),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
return Ch934xDeviceInfo(
|
||||
deviceId: (map['deviceId'] as int?) ?? 0,
|
||||
vendorId: (map['vendorId'] as int?) ?? 0,
|
||||
productId: (map['productId'] as int?) ?? 0,
|
||||
deviceType: (map['deviceType'] as int?) ?? Ch934xDeviceType.unknown,
|
||||
serialNumber: map['serialNumber'] as String?,
|
||||
productName: map['productName'] as String?,
|
||||
manufacturerName: map['manufacturerName'] as String?,
|
||||
interfaceCount: (map['interfaceCount'] as int?) ?? 0,
|
||||
serialPorts: ports,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/// CH934X 设备类型常量。
|
||||
///
|
||||
/// 来自文档 4.1.2 `UsbHelper.CH934XDeviceType` 的返回值,描述
|
||||
/// 枚举到的 USB 设备属于沁恒 CH934X 家族中的哪一颗芯片。
|
||||
class Ch934xDeviceType {
|
||||
const Ch934xDeviceType._();
|
||||
|
||||
/// CH9344 芯片。
|
||||
static const int ch9344 = 0;
|
||||
|
||||
/// CH9344L 芯片。
|
||||
static const int ch9344L = 1;
|
||||
|
||||
/// CH9350 芯片。
|
||||
static const int ch9350 = 2;
|
||||
|
||||
/// CH9348Q 芯片。
|
||||
static const int ch9348Q = 3;
|
||||
|
||||
/// CH9342 芯片。
|
||||
static const int ch9342 = 4;
|
||||
|
||||
/// 其他 CH934X 设备。
|
||||
static const int ch934xOther = 5;
|
||||
|
||||
/// 未知或非 CH934X 设备。
|
||||
static const int unknown = -1;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/// 设备拔出等异常事件类型常量。
|
||||
///
|
||||
/// 透传自 Android 端 `UsbSerial.ExceptionCallback.onException`
|
||||
/// 的 `type` 参数,插件使用者可根据此值进行不同处理。
|
||||
class Ch934xExceptionType {
|
||||
const Ch934xExceptionType._();
|
||||
|
||||
/// 未知异常。
|
||||
static const int unknown = 0;
|
||||
|
||||
/// 设备被拔出。
|
||||
static const int deviceDetached = 1;
|
||||
|
||||
/// 读写过程中发生 IO 错误。
|
||||
static const int ioError = 2;
|
||||
|
||||
/// 原生 SDK 主动抛出的其他异常。
|
||||
static const int sdk = 3;
|
||||
}
|
||||
|
||||
/// `setExceptionCallback` 回调中的载荷,描述一次异常事件。
|
||||
class Ch934xException {
|
||||
const Ch934xException({required this.type, this.message, this.cause});
|
||||
|
||||
/// 异常类型,取值见 [Ch934xExceptionType] 常量。
|
||||
final int type;
|
||||
|
||||
/// 异常的文本描述(若原生层提供)。
|
||||
final String? message;
|
||||
|
||||
/// 底层异常类名(若原生层提供)。
|
||||
final String? cause;
|
||||
|
||||
/// 便于在日志/UI 中显示的描述,自动将类型转换为常量名。
|
||||
@override
|
||||
String toString() {
|
||||
final typeName = switch (type) {
|
||||
Ch934xExceptionType.deviceDetached => 'deviceDetached',
|
||||
Ch934xExceptionType.ioError => 'ioError',
|
||||
Ch934xExceptionType.sdk => 'sdk',
|
||||
_ => 'unknown',
|
||||
};
|
||||
final buffer = StringBuffer('Ch934xException(type: $typeName');
|
||||
if (message != null && message!.isNotEmpty) {
|
||||
buffer.write(', message: $message');
|
||||
}
|
||||
if (cause != null && cause!.isNotEmpty) {
|
||||
buffer.write(', cause: $cause');
|
||||
}
|
||||
buffer.write(')');
|
||||
return buffer.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/// 用于打开 CH934X 串口的目标描述,封装 init 接口需要的全部参数。
|
||||
///
|
||||
/// 取代文档示例代码中散落的 `device`、`interfaceNum`、
|
||||
/// `serialPortIndex`,便于在异步链中安全传递。
|
||||
class Ch934xPortTarget {
|
||||
const Ch934xPortTarget({
|
||||
required this.deviceId,
|
||||
required this.interfaceNumber,
|
||||
required this.serialPortIndex,
|
||||
});
|
||||
|
||||
/// Android `UsbDevice.getDeviceId()`。
|
||||
final int deviceId;
|
||||
|
||||
/// CH934X 设备的接口号(文档 4.1.3 中的 `interfaceNum`)。
|
||||
final int interfaceNumber;
|
||||
|
||||
/// 串口索引(文档 5.1.1 中的 `serialPortIndex`)。
|
||||
final int serialPortIndex;
|
||||
|
||||
/// 等价的可序列化 Map,用于在 MethodChannel 中传递。
|
||||
Map<String, Object> toMap() => <String, Object>{
|
||||
'deviceId': deviceId,
|
||||
'interfaceNumber': interfaceNumber,
|
||||
'serialPortIndex': serialPortIndex,
|
||||
};
|
||||
|
||||
/// 从 MethodChannel 回传的 Map 还原对象,字段缺失时回退到 0。
|
||||
factory Ch934xPortTarget.fromMap(Map<dynamic, dynamic> map) {
|
||||
return Ch934xPortTarget(
|
||||
deviceId: (map['deviceId'] as int?) ?? 0,
|
||||
interfaceNumber: (map['interfaceNumber'] as int?) ?? 0,
|
||||
serialPortIndex: (map['serialPortIndex'] as int?) ?? 0,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export 'ch934x_device_info.dart';
|
||||
export 'ch934x_device_type.dart';
|
||||
export 'ch934x_exception.dart';
|
||||
export 'ch934x_port_target.dart';
|
||||
export 'modem_status.dart';
|
||||
@@ -0,0 +1,21 @@
|
||||
/// Modem 状态位掩码常量,对应文档 7.2.2 中 `getModemStatus` 的返回值。
|
||||
///
|
||||
/// 可与 `getModemStatus()` 返回值按位与来判定具体引脚电平。
|
||||
class ModemStatus {
|
||||
const ModemStatus._();
|
||||
|
||||
/// CTS 状态位,值为 0x01。
|
||||
static const int cts = 0x01;
|
||||
|
||||
/// DSR 状态位,值为 0x02。
|
||||
static const int dsr = 0x02;
|
||||
|
||||
/// RI 状态位,值为 0x04。
|
||||
static const int ri = 0x04;
|
||||
|
||||
/// DCD 状态位,值为 0x08。
|
||||
static const int dcd = 0x08;
|
||||
|
||||
/// 读取指定状态位是否为高电平。
|
||||
static bool isSet(int status, int mask) => (status & mask) != 0;
|
||||
}
|
||||
Reference in New Issue
Block a user