feat(android): 添加 CH934X USB 转串口芯片支持

- 在 AndroidManifest.xml 中添加 USB Host 权限声明
- 集成 CH934X Android SDK 并通过反射调用原生功能
- 实现设备查找、串口读写、GPIO 和 Modem 控制功能
- 添加异常回调机制处理设备拔出等情况
- 提供 Stream 数据流支持实时串口数据监听
- 完善单元测试覆盖所有核心功能模块
This commit is contained in:
Developer
2026-07-06 16:06:54 +08:00
parent 6efaf5671f
commit e0d1a1775d
26 changed files with 2055 additions and 184 deletions
+107
View File
@@ -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,
);
}
}
+28
View File
@@ -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;
}
+53
View File
@@ -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();
}
}
+36
View File
@@ -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,
);
}
}
+5
View File
@@ -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';
+21
View File
@@ -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;
}