Files
ch934x_serial/lib/ch934x_serial_method_channel.dart
T
huangsheng 7fe87f8d7c feat(ch934x_serial): 添加串口写入功能的端口索引参数
- write方法新增可选的serialPortIndex参数,支持直接写入指定端口
- 传入端口索引时不会改变当前活跃端口,保持原有活跃端口行为
- 添加对负数端口索引的验证处理,直接返回零避免异常
- 更新文档说明多端口并发客户端应使用显式端口写入方式
- 增加相关单元测试覆盖新参数的各种使用场景
- 同步更新Android原生层实现以支持端口索引传递
2026-07-13 20:51:27 +08:00

338 lines
8.9 KiB
Dart

import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'ch934x_serial_platform_interface.dart';
import 'src/models/models.dart';
/// MethodChannel 实现的 [Ch934xSerialPlatform]。
///
/// 在 Android 端通过同一 MethodChannel 与原生层通信,
/// 命名规则:方法名使用下划线小写,字段名使用驼峰式以便
/// 直接对应 Java 侧 Map 的 key。
class MethodChannelCh934xSerial extends Ch934xSerialPlatform {
/// 测试时可被替换的 MethodChannel。
@visibleForTesting
final MethodChannel methodChannel = const MethodChannel('ch934x_serial');
/// 通知 Dart 侧的异常事件流,用于支持 `setExceptionCallback`。
final StreamController<Ch934xException> _exceptionController =
StreamController<Ch934xException>.broadcast();
/// 接收来自原生层主动调用的 `onException` 消息。
Future<void> _onNativeMethodCall(MethodCall call) async {
if (call.method == 'onException') {
final args = call.arguments;
if (args is Map) {
_exceptionController.add(
Ch934xException(
type: (args['type'] as int?) ?? Ch934xExceptionType.unknown,
message: args['message'] as String?,
cause: args['cause'] as String?,
),
);
}
}
}
@override
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},
);
}
@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<bool> setActivePort(int serialPortIndex) async {
if (serialPortIndex < 0) {
return false;
}
final result = await methodChannel.invokeMethod<bool>(
'setActivePort',
<String, Object>{'serialPortIndex': serialPortIndex},
);
return result ?? false;
}
@override
Future<bool> requestUsbPermission(int deviceId) async {
final result = await methodChannel.invokeMethod<bool>(
'requestUsbPermission',
<String, Object>{'deviceId': deviceId},
);
return result ?? false;
}
@override
Future<Uint8List> read(int length, {int? serialPortIndex}) async {
if (length <= 0) {
return Uint8List(0);
}
final args = <String, Object>{'length': length};
if (serialPortIndex != null) {
args['serialPortIndex'] = serialPortIndex;
}
final raw = await methodChannel.invokeMethod<Uint8List>('read', args);
return raw ?? Uint8List(0);
}
@override
Future<int> write(Uint8List data, {int? serialPortIndex}) async {
if (data.isEmpty || (serialPortIndex != null && serialPortIndex < 0)) {
return 0;
}
final args = <String, Object>{'data': data};
if (serialPortIndex != null) {
args['serialPortIndex'] = serialPortIndex;
}
final result = await methodChannel.invokeMethod<int>('write', args);
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 {
// 注册接收原生层主动推送的 `onException` 事件。
methodChannel.setMethodCallHandler(_onNativeMethodCall);
_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),
);
}
@override
Future<bool> setBreak(bool valid) {
return methodChannel.invokeMethod<bool>(
'setBreak',
<String, Object>{'valid': valid},
).then((v) => v ?? false);
}
@override
Future<int> getCurrentMode() {
return methodChannel
.invokeMethod<int>('getCurrentMode')
.then((v) => v ?? -1);
}
@override
Future<int> getGpiocount(int deviceId) {
return methodChannel.invokeMethod<int>(
'getGPIOCount',
<String, Object>{'deviceId': deviceId},
).then((v) => v ?? -1);
}
@override
Future<int> getGpiogroup(int deviceId) {
return methodChannel.invokeMethod<int>(
'getGPIOGroup',
<String, Object>{'deviceId': deviceId},
).then((v) => v ?? -1);
}
@override
Future<bool> enableGpio({
required int chipType,
required int gpioGroup,
required int enable,
}) {
return methodChannel.invokeMethod<bool>(
'enableGPIO',
<String, Object>{
'chipType': chipType,
'gpioGroup': gpioGroup,
'enable': enable,
},
).then((v) => v ?? false);
}
@override
Future<bool> setGpioDir({
required int gpioGroup,
required int gpioNumber,
required int dir,
}) {
return methodChannel.invokeMethod<bool>(
'setGPIODir',
<String, Object>{
'gpioGroup': gpioGroup,
'gpioNumber': gpioNumber,
'dir': dir,
},
).then((v) => v ?? false);
}
@override
Future<int> queryGpioDirFromCache({
required int gpioGroup,
required int gpioNumber,
}) {
return methodChannel.invokeMethod<int>(
'queryGPIODirFromCache',
<String, Object>{
'gpioGroup': gpioGroup,
'gpioNumber': gpioNumber,
},
).then((v) => v ?? -1);
}
@override
Future<bool> isConnected(int deviceId) {
return methodChannel.invokeMethod<bool>(
'isConnected',
<String, Object>{'deviceId': deviceId},
).then((v) => v ?? false);
}
@override
Future<List<int>> getConnectedDevices() async {
final raw =
await methodChannel.invokeMethod<List<dynamic>>('getConnectedDevices');
if (raw == null) return const <int>[];
return raw.whereType<int>().toList(growable: false);
}
@override
Future<bool> setSerialParameter({
required int baud,
int dataBit = 8,
int stopBit = 1,
int parityBit = 0,
bool flow = false,
}) {
return methodChannel.invokeMethod<bool>(
'setSerialParameter',
<String, Object>{
'baud': baud,
'dataBit': dataBit,
'stopBit': stopBit,
'parityBit': parityBit,
'flow': flow,
},
).then((v) => v ?? false);
}
/// 释放内部资源,通常仅在测试或热重载场景下使用。
@visibleForTesting
void dispose() {
_exceptionController.close();
}
}