diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f58d67..5fac148 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.0.1 + +- `write` 新增可选 `serialPortIndex` 参数;传入时直接写入指定端口且不修改当前活跃端口,省略时保持原有活跃端口行为。 + ## 1.0.0 - 重构插件代码,完整对接 CH934X Android SDK 文档中定义的全部接口: diff --git a/android/src/main/java/com/xiarui/ch934x_serial/Ch934xSerialPlugin.java b/android/src/main/java/com/xiarui/ch934x_serial/Ch934xSerialPlugin.java index 439024f..3728831 100644 --- a/android/src/main/java/com/xiarui/ch934x_serial/Ch934xSerialPlugin.java +++ b/android/src/main/java/com/xiarui/ch934x_serial/Ch934xSerialPlugin.java @@ -584,9 +584,14 @@ public class Ch934xSerialPlugin implements FlutterPlugin, MethodCallHandler, Act if (data == null || data.length == 0) { return 0; } + Integer portIndex = call.argument("serialPortIndex"); + int idx = portIndex != null ? portIndex : activeSerialNumber; + if (idx < 0) { + return 0; + } try { return CH934XManager.getInstance().writeData( - activeDevice, activeSerialNumber, data, data.length, 2000); + activeDevice, idx, data, data.length, 2000); } catch (ChipException e) { return 0; } diff --git a/docs/CH934X_Plugin_使用说明.md b/docs/CH934X_Plugin_使用说明.md index f360c29..35d8618 100644 --- a/docs/CH934X_Plugin_使用说明.md +++ b/docs/CH934X_Plugin_使用说明.md @@ -161,11 +161,14 @@ await plugin.closePort(); ### 3.3 串口读写 ```dart -// 写入 +// 写入当前活跃端口(向后兼容) final bytes = Uint8List.fromList([0x01, 0x02, 0x03]); final written = await plugin.write(bytes); debugPrint('写入字节数: $written'); +// 写入指定端口,不改变当前活跃端口;多端口客户端应使用此形式。 +final portWritten = await plugin.write(bytes, serialPortIndex: 2); + // 读取(单次) final recv = await plugin.read(1024); if (recv.isEmpty) debugPrint('暂无数据'); @@ -182,13 +185,13 @@ await sub.cancel(); - `length <= 0` 时**不会调用原生层**,直接返回空数组。 - SDK 的 `readData` 会返回内部缓冲的所有数据,插件按 `min(data.length, length)` 截断后回传。 -- `write(data)` 返回实际写入字节数,失败时为 0。 +- `write(data, serialPortIndex: n)` 返回实际写入字节数;传入端口时直接写入该端口且不改变当前活跃端口,失败时为 0。省略 `serialPortIndex` 时保持原有的当前活跃端口行为。 - `dataStream` 默认 `chunkSize = 1024`、`interval = 20ms`,内部 持续以 [interval] 为周期反复调用 `read`;当底层无数据时返回 空缓冲区,消费者可据此判定。`chunkSize <= 0` 会抛 `ArgumentError`。 - 业务方在 widget dispose 时记得 `cancel` 订阅并 `closePort`。 -- `read`/`write` 都针对**当前活跃串口**;切换串口用 `setActivePort`。 +- `read` 默认针对当前活跃串口,也可传入显式端口;`write` 省略 `serialPortIndex` 时针对当前活跃串口,传入时直接写入指定端口。多端口并发客户端应使用显式端口写入,避免通过 `setActivePort` 共享切换端口。 ### 3.4 GPIO 接口 diff --git a/example/pubspec.lock b/example/pubspec.lock index 190501a..f4a6c1a 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -6,7 +6,7 @@ packages: description: name: async sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.13.1" boolean_selector: @@ -14,7 +14,7 @@ packages: description: name: boolean_selector sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.2" ch934x_serial: @@ -23,13 +23,13 @@ packages: path: ".." relative: true source: path - version: "1.0.0" + version: "1.0.1" characters: dependency: transitive description: name: characters sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.4.1" clock: @@ -37,7 +37,7 @@ packages: description: name: clock sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.2" collection: @@ -45,7 +45,7 @@ packages: description: name: collection sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.19.1" cupertino_icons: @@ -53,7 +53,7 @@ packages: description: name: cupertino_icons sha256: "41e005c33bd814be4d3096aff55b1908d419fde52ca656c8c47719ec745873cd" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.9" fake_async: @@ -61,7 +61,7 @@ packages: description: name: fake_async sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.3.3" file: @@ -69,7 +69,7 @@ packages: description: name: file sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "7.0.1" flutter: @@ -87,7 +87,7 @@ packages: description: name: flutter_lints sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "6.0.0" flutter_test: @@ -110,7 +110,7 @@ packages: description: name: leak_tracker sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "11.0.2" leak_tracker_flutter_testing: @@ -118,7 +118,7 @@ packages: description: name: leak_tracker_flutter_testing sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.10" leak_tracker_testing: @@ -126,7 +126,7 @@ packages: description: name: leak_tracker_testing sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.2" lints: @@ -134,7 +134,7 @@ packages: description: name: lints sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "6.1.0" matcher: @@ -142,7 +142,7 @@ packages: description: name: matcher sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.12.19" material_color_utilities: @@ -150,23 +150,23 @@ packages: description: name: material_color_utilities sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.13.0" meta: dependency: transitive description: name: meta - sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" - url: "https://pub.dev" + sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349" + url: "https://pub.flutter-io.cn" source: hosted - version: "1.17.0" + version: "1.18.0" path: dependency: transitive description: name: path sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.9.1" platform: @@ -174,7 +174,7 @@ packages: description: name: platform sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.1.6" plugin_platform_interface: @@ -182,7 +182,7 @@ packages: description: name: plugin_platform_interface sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.8" process: @@ -190,7 +190,7 @@ packages: description: name: process sha256: c6248e4526673988586e8c00bb22a49210c258dc91df5227d5da9748ecf79744 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "5.0.5" sky_engine: @@ -203,7 +203,7 @@ packages: description: name: source_span sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.10.2" stack_trace: @@ -211,7 +211,7 @@ packages: description: name: stack_trace sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.12.1" stream_channel: @@ -219,7 +219,7 @@ packages: description: name: stream_channel sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.4" string_scanner: @@ -227,7 +227,7 @@ packages: description: name: string_scanner sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.4.1" sync_http: @@ -235,7 +235,7 @@ packages: description: name: sync_http sha256: "7f0cd72eca000d2e026bcd6f990b81d0ca06022ef4e32fb257b30d3d1014a961" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.3.1" term_glyph: @@ -243,23 +243,23 @@ packages: description: name: term_glyph sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.2.2" test_api: dependency: transitive description: name: test_api - sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" - url: "https://pub.dev" + sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e" + url: "https://pub.flutter-io.cn" source: hosted - version: "0.7.10" + version: "0.7.11" vector_math: dependency: transitive description: name: vector_math sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.2.0" vm_service: @@ -267,7 +267,7 @@ packages: description: name: vm_service sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "15.2.0" webdriver: @@ -275,7 +275,7 @@ packages: description: name: webdriver sha256: "2f3a14ca026957870cfd9c635b83507e0e51d8091568e90129fbf805aba7cade" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.1.0" sdks: diff --git a/lib/ch934x_serial.dart b/lib/ch934x_serial.dart index 65cf7f7..99547cd 100644 --- a/lib/ch934x_serial.dart +++ b/lib/ch934x_serial.dart @@ -29,16 +29,14 @@ class Ch934xSerial { // --------------------------------------------------------------------------- /// 获取所有已连接的 CH934X 设备信息。 - Future> getDeviceList() => - _platform.getDeviceList(); + Future> getDeviceList() => _platform.getDeviceList(); /// 获取指定设备序列号;非 CH934X 设备时返回 null。 Future getSerialNumber(int deviceId) => _platform.getSerialNumber(deviceId); /// 获取指定设备类型,取值见 [Ch934xDeviceType]。 - Future getDeviceType(int deviceId) => - _platform.getDeviceType(deviceId); + Future getDeviceType(int deviceId) => _platform.getDeviceType(deviceId); /// 获取指定设备的串口列表。 Future> getSerialPortList( @@ -85,7 +83,11 @@ class Ch934xSerial { Future read(int length) => _platform.read(length); /// 写入数据,返回实际写入的字节数。 - Future write(Uint8List data) => _platform.write(data); + /// + /// [serialPortIndex] 为空时沿用当前活跃串口;传入时直接写入指定端口, + /// 不会改变当前活跃串口。 + Future write(Uint8List data, {int? serialPortIndex}) => + _platform.write(data, serialPortIndex: serialPortIndex); /// 构造一个持续从串口拉取数据的 `Stream`。 /// @@ -180,12 +182,10 @@ class Ch934xSerial { Future getCurrentMode() => _platform.getCurrentMode(); /// 获取 GPIO 数量。 - Future getGpiocount(int deviceId) => - _platform.getGpiocount(deviceId); + Future getGpiocount(int deviceId) => _platform.getGpiocount(deviceId); /// 获取 GPIO 组数。 - Future getGpiogroup(int deviceId) => - _platform.getGpiogroup(deviceId); + Future getGpiogroup(int deviceId) => _platform.getGpiogroup(deviceId); /// 使能 GPIO。 Future enableGpio({ @@ -222,12 +222,10 @@ class Ch934xSerial { ); /// 查询设备是否已打开。 - Future isConnected(int deviceId) => - _platform.isConnected(deviceId); + Future isConnected(int deviceId) => _platform.isConnected(deviceId); /// 获取当前已打开的设备 ID 列表。 - Future> getConnectedDevices() => - _platform.getConnectedDevices(); + Future> getConnectedDevices() => _platform.getConnectedDevices(); /// 配置当前活跃串口的参数。 Future setSerialParameter({ diff --git a/lib/ch934x_serial_method_channel.dart b/lib/ch934x_serial_method_channel.dart index b963bbf..9a2fdf9 100644 --- a/lib/ch934x_serial_method_channel.dart +++ b/lib/ch934x_serial_method_channel.dart @@ -14,8 +14,7 @@ import 'src/models/models.dart'; class MethodChannelCh934xSerial extends Ch934xSerialPlatform { /// 测试时可被替换的 MethodChannel。 @visibleForTesting - final MethodChannel methodChannel = - const MethodChannel('ch934x_serial'); + final MethodChannel methodChannel = const MethodChannel('ch934x_serial'); /// 通知 Dart 侧的异常事件流,用于支持 `setExceptionCallback`。 final StreamController _exceptionController = @@ -39,7 +38,8 @@ class MethodChannelCh934xSerial extends Ch934xSerialPlatform { @override Future> getDeviceList() async { - final raw = await methodChannel.invokeMethod>('getDeviceList'); + final raw = + await methodChannel.invokeMethod>('getDeviceList'); if (raw == null) { return const []; } @@ -137,14 +137,15 @@ class MethodChannelCh934xSerial extends Ch934xSerialPlatform { } @override - Future write(Uint8List data) async { - if (data.isEmpty) { + Future write(Uint8List data, {int? serialPortIndex}) async { + if (data.isEmpty || (serialPortIndex != null && serialPortIndex < 0)) { return 0; } - final result = await methodChannel.invokeMethod( - 'write', - {'data': data}, - ); + final args = {'data': data}; + if (serialPortIndex != null) { + args['serialPortIndex'] = serialPortIndex; + } + final result = await methodChannel.invokeMethod('write', args); return result ?? 0; } @@ -225,7 +226,8 @@ class MethodChannelCh934xSerial extends Ch934xSerialPlatform { @override Future getCurrentMode() { - return methodChannel.invokeMethod('getCurrentMode') + return methodChannel + .invokeMethod('getCurrentMode') .then((v) => v ?? -1); } @@ -301,7 +303,8 @@ class MethodChannelCh934xSerial extends Ch934xSerialPlatform { @override Future> getConnectedDevices() async { - final raw = await methodChannel.invokeMethod>('getConnectedDevices'); + final raw = + await methodChannel.invokeMethod>('getConnectedDevices'); if (raw == null) return const []; return raw.whereType().toList(growable: false); } diff --git a/lib/ch934x_serial_platform_interface.dart b/lib/ch934x_serial_platform_interface.dart index ba0715d..e8b0896 100644 --- a/lib/ch934x_serial_platform_interface.dart +++ b/lib/ch934x_serial_platform_interface.dart @@ -79,7 +79,9 @@ abstract class Ch934xSerialPlatform extends PlatformInterface { Future read(int length, {int? serialPortIndex}); /// 向串口写入数据(对应 6.2.1 `UsbSerial.write`)。 - Future write(Uint8List data); + /// + /// [serialPortIndex] 为空时使用当前活跃端口;传入时直接写入指定端口。 + Future write(Uint8List data, {int? serialPortIndex}); // --------------------------------------------------------------------------- // GPIO diff --git a/pubspec.yaml b/pubspec.yaml index f94ee49..6ce59f0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: ch934x_serial description: "Flutter 插件,封装南京沁恒 CH934X 系列 USB 转串口芯片的 Android SDK,提供设备查找、串口读写、GPIO 与 Modem 控制等能力。" -version: 1.0.0 +version: 1.0.1 homepage: https://example.com/ch934x_serial environment: diff --git a/test/ch934x_serial_method_channel_test.dart b/test/ch934x_serial_method_channel_test.dart index 0d763a9..e74b841 100644 --- a/test/ch934x_serial_method_channel_test.dart +++ b/test/ch934x_serial_method_channel_test.dart @@ -69,13 +69,39 @@ void main() { test('write 将数据写入原生层并回传字节数', () async { mockCall((call) async { expect(call.method, 'write'); - expect((call.arguments as Map)['data'], isA()); + expect(call.arguments, { + 'data': Uint8List.fromList([9, 8, 7]), + }); return 3; }); final written = await platform.write(Uint8List.fromList([9, 8, 7])); expect(written, 3); }); + test('write 透传显式串口索引', () async { + mockCall((call) async { + expect(call.method, 'write'); + expect(call.arguments, { + 'data': Uint8List.fromList([9, 8, 7]), + 'serialPortIndex': 2, + }); + return 3; + }); + final written = await platform.write( + Uint8List.fromList([9, 8, 7]), + serialPortIndex: 2, + ); + expect(written, 3); + }); + + test('write 接收负串口索引直接返回零', () async { + final written = await platform.write( + Uint8List.fromList([9]), + serialPortIndex: -1, + ); + expect(written, 0); + }); + test('getModemStatus 默认回退到 0', () async { mockCall((_) async => null); expect(await platform.getModemStatus(), 0); diff --git a/test/ch934x_serial_test.dart b/test/ch934x_serial_test.dart index caf98c4..8d37f44 100644 --- a/test/ch934x_serial_test.dart +++ b/test/ch934x_serial_test.dart @@ -45,6 +45,7 @@ class _MockCh934xSerialPlatform extends Ch934xSerialPlatform bool closeResult; Uint8List? readBytes; int writeResult; + int? lastWritePort; bool gpioOutputResult; int gpioInput; bool modemControlResult; @@ -63,8 +64,7 @@ class _MockCh934xSerialPlatform extends Ch934xSerialPlatform Future> getSerialPortList( int deviceId, { required int interfaceNumber, - }) async => - ports; + }) async => ports; @override Future openPort(Ch934xPortTarget target) async => openResult; @@ -79,17 +79,20 @@ class _MockCh934xSerialPlatform extends Ch934xSerialPlatform Future requestUsbPermission(int deviceId) async => true; @override - Future read(int length, {int? serialPortIndex}) async => readBytes ?? Uint8List(0); + Future read(int length, {int? serialPortIndex}) async => + readBytes ?? Uint8List(0); @override - Future write(Uint8List data) async => writeResult; + Future write(Uint8List data, {int? serialPortIndex}) async { + lastWritePort = serialPortIndex; + return writeResult; + } @override Future setGpioOutput({ required int gpioNumber, required int level, - }) async => - gpioOutputResult; + }) async => gpioOutputResult; @override Future getGpioInput(int gpioNumber) async => gpioInput; @@ -105,13 +108,19 @@ class _MockCh934xSerialPlatform extends Ch934xSerialPlatform Future setExceptionCallback( void Function(Ch934xException exception) onException, ) async {} + + @override + dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); } void main() { TestWidgetsFlutterBinding.ensureInitialized(); test('默认平台实现是 MethodChannelCh934xSerial', () { - expect(Ch934xSerialPlatform.instance, isInstanceOf()); + expect( + Ch934xSerialPlatform.instance, + isInstanceOf(), + ); }); test('Ch934xSerial 委托 platform 调用 getDeviceList', () async { @@ -131,6 +140,20 @@ void main() { expect(list.single.serialNumber, 'SN-1'); }); + test('Ch934xSerial write 透传可选串口索引', () async { + final mock = _MockCh934xSerialPlatform(writeResult: 3); + final plugin = Ch934xSerial.withPlatform(mock); + + expect(await plugin.write(Uint8List.fromList([1, 2, 3])), 3); + expect(mock.lastWritePort, isNull); + + expect( + await plugin.write(Uint8List.fromList([1, 2, 3]), serialPortIndex: 4), + 3, + ); + expect(mock.lastWritePort, 4); + }); + test('ModemStatus.isSet 按位与工作', () { const status = ModemStatus.cts | ModemStatus.dcd; expect(ModemStatus.isSet(status, ModemStatus.cts), isTrue);