feat(android): 添加 CH934X USB 转串口芯片支持
- 在 AndroidManifest.xml 中添加 USB Host 权限声明 - 集成 CH934X Android SDK 并通过反射调用原生功能 - 实现设备查找、串口读写、GPIO 和 Modem 控制功能 - 添加异常回调机制处理设备拔出等情况 - 提供 Stream 数据流支持实时串口数据监听 - 完善单元测试覆盖所有核心功能模块
This commit is contained in:
@@ -1,19 +1,183 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'ch934x_serial_platform_interface.dart';
|
||||
import 'src/models/models.dart';
|
||||
|
||||
/// An implementation of [Ch934xSerialPlatform] that uses method channels.
|
||||
/// MethodChannel 实现的 [Ch934xSerialPlatform]。
|
||||
///
|
||||
/// 在 Android 端通过同一 MethodChannel 与原生层通信,
|
||||
/// 命名规则:方法名使用下划线小写,字段名使用驼峰式以便
|
||||
/// 直接对应 Java 侧 Map 的 key。
|
||||
class MethodChannelCh934xSerial extends Ch934xSerialPlatform {
|
||||
/// The method channel used to interact with the native platform.
|
||||
/// 测试时可被替换的 MethodChannel。
|
||||
@visibleForTesting
|
||||
final methodChannel = const MethodChannel('ch934x_serial');
|
||||
final MethodChannel methodChannel =
|
||||
const MethodChannel('ch934x_serial');
|
||||
|
||||
/// 通知 Dart 侧的异常事件流,用于支持 `setExceptionCallback`。
|
||||
final StreamController<Ch934xException> _exceptionController =
|
||||
StreamController<Ch934xException>.broadcast();
|
||||
|
||||
@override
|
||||
Future<String?> getPlatformVersion() async {
|
||||
final version = await methodChannel.invokeMethod<String>(
|
||||
'getPlatformVersion',
|
||||
Future<List<Ch934xDeviceInfo>> getDeviceList() async {
|
||||
final raw = await methodChannel.invokeMethod<List<dynamic>>('getDeviceList');
|
||||
if (raw == null) {
|
||||
return const <Ch934xDeviceInfo>[];
|
||||
}
|
||||
return raw
|
||||
.whereType<Map>()
|
||||
.map((e) => Ch934xDeviceInfo.fromMap(Map<dynamic, dynamic>.from(e)))
|
||||
.toList(growable: false);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String?> getSerialNumber(int deviceId) {
|
||||
return methodChannel.invokeMethod<String>(
|
||||
'getSerialNumber',
|
||||
<String, Object>{'deviceId': deviceId},
|
||||
);
|
||||
return version;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<int> getDeviceType(int deviceId) async {
|
||||
final result = await methodChannel.invokeMethod<int>(
|
||||
'getDeviceType',
|
||||
<String, Object>{'deviceId': deviceId},
|
||||
);
|
||||
return result ?? Ch934xDeviceType.unknown;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<Ch934xSerialPortInfo>> getSerialPortList(
|
||||
int deviceId, {
|
||||
required int interfaceNumber,
|
||||
}) async {
|
||||
final raw = await methodChannel.invokeMethod<List<dynamic>>(
|
||||
'getSerialPortList',
|
||||
<String, Object>{
|
||||
'deviceId': deviceId,
|
||||
'interfaceNumber': interfaceNumber,
|
||||
},
|
||||
);
|
||||
if (raw == null) {
|
||||
return const <Ch934xSerialPortInfo>[];
|
||||
}
|
||||
return raw
|
||||
.whereType<Map>()
|
||||
.map((e) => Ch934xSerialPortInfo.fromMap(Map<dynamic, dynamic>.from(e)))
|
||||
.toList(growable: false);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> openPort(Ch934xPortTarget target) async {
|
||||
final result = await methodChannel.invokeMethod<bool>(
|
||||
'openPort',
|
||||
target.toMap(),
|
||||
);
|
||||
return result ?? false;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> closePort() async {
|
||||
final result = await methodChannel.invokeMethod<bool>('closePort');
|
||||
return result ?? false;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Uint8List> read(int length) async {
|
||||
if (length <= 0) {
|
||||
return Uint8List(0);
|
||||
}
|
||||
final raw = await methodChannel.invokeMethod<Uint8List>(
|
||||
'read',
|
||||
<String, Object>{'length': length},
|
||||
);
|
||||
return raw ?? Uint8List(0);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<int> write(Uint8List data) async {
|
||||
if (data.isEmpty) {
|
||||
return 0;
|
||||
}
|
||||
final result = await methodChannel.invokeMethod<int>(
|
||||
'write',
|
||||
<String, Object>{'data': data},
|
||||
);
|
||||
return result ?? 0;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> setGpioOutput({
|
||||
required int gpioNumber,
|
||||
required int level,
|
||||
}) async {
|
||||
final result = await methodChannel.invokeMethod<bool>(
|
||||
'setGpioOutput',
|
||||
<String, Object>{
|
||||
'gpioNumber': gpioNumber,
|
||||
'level': level,
|
||||
},
|
||||
);
|
||||
return result ?? false;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<int> getGpioInput(int gpioNumber) async {
|
||||
final result = await methodChannel.invokeMethod<int>(
|
||||
'getGpioInput',
|
||||
<String, Object>{'gpioNumber': gpioNumber},
|
||||
);
|
||||
return result ?? -1;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> setModemControl({required int dtr, required int rts}) async {
|
||||
final result = await methodChannel.invokeMethod<bool>(
|
||||
'setModemControl',
|
||||
<String, Object>{
|
||||
'dtr': dtr,
|
||||
'rts': rts,
|
||||
},
|
||||
);
|
||||
return result ?? false;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<int> getModemStatus() async {
|
||||
final result = await methodChannel.invokeMethod<int>('getModemStatus');
|
||||
return result ?? 0;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> setExceptionCallback(
|
||||
void Function(Ch934xException exception) onException,
|
||||
) async {
|
||||
_exceptionController.stream.listen(onException);
|
||||
await methodChannel.invokeMethod<void>('setExceptionCallback');
|
||||
}
|
||||
|
||||
/// 由原生层主动调用的入口,对应文档 7.3.1 中的 `onException` 回调。
|
||||
///
|
||||
/// 必须在原生层将 `MethodChannel` 的 `setMethodCallHandler` 调通后
|
||||
/// 才会被触发;此方法在测试中也可直接调用,用于模拟异常事件。
|
||||
@visibleForTesting
|
||||
void dispatchException({
|
||||
required int type,
|
||||
String? message,
|
||||
String? cause,
|
||||
}) {
|
||||
_exceptionController.add(
|
||||
Ch934xException(type: type, message: message, cause: cause),
|
||||
);
|
||||
}
|
||||
|
||||
/// 释放内部资源,通常仅在测试或热重载场景下使用。
|
||||
@visibleForTesting
|
||||
void dispose() {
|
||||
_exceptionController.close();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user