1
This commit is contained in:
31
.gitignore
vendored
Normal file
31
.gitignore
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
migrate_working_dir/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
#.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
|
||||
/pubspec.lock
|
||||
**/doc/api/
|
||||
.dart_tool/
|
||||
build/
|
||||
|
||||
.omc/
|
||||
30
.metadata
Normal file
30
.metadata
Normal file
@@ -0,0 +1,30 @@
|
||||
# This file tracks properties of this Flutter project.
|
||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||
#
|
||||
# This file should be version controlled and should not be manually edited.
|
||||
|
||||
version:
|
||||
revision: "761747bfc538b5af34aa0d3fac380f1bc331ec49"
|
||||
channel: "stable"
|
||||
|
||||
project_type: plugin
|
||||
|
||||
# Tracks metadata for the flutter migrate command
|
||||
migration:
|
||||
platforms:
|
||||
- platform: root
|
||||
create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
|
||||
base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
|
||||
- platform: android
|
||||
create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
|
||||
base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
|
||||
|
||||
# User provided section
|
||||
|
||||
# List of Local paths (relative to this file) that should be
|
||||
# ignored by the migrate tool.
|
||||
#
|
||||
# Files that are not part of the templates will be ignored by default.
|
||||
unmanaged_files:
|
||||
- 'lib/main.dart'
|
||||
- 'ios/Runner.xcodeproj/project.pbxproj'
|
||||
198
.planning/codebase/ARCHITECTURE.md
Normal file
198
.planning/codebase/ARCHITECTURE.md
Normal file
@@ -0,0 +1,198 @@
|
||||
# Architecture
|
||||
|
||||
**Analysis Date:** 2026-04-16
|
||||
|
||||
## Pattern Overview
|
||||
|
||||
**Overall:** Flutter Platform Plugin (Federated Plugin Pattern)
|
||||
|
||||
This is an Android-only Flutter plugin that wraps the WCH CH34X UART library (`CH34XUARTDriver.jar`). It bridges Flutter/Dart code to native Android USB serial communication via `MethodChannel` and `EventChannel`.
|
||||
|
||||
**Key Characteristics:**
|
||||
- Static facade pattern for public API (`Ch34Manager`)
|
||||
- Platform interface pattern with token-based verification (`Ch34Platform`)
|
||||
- MethodChannel for command invocation, EventChannel for streaming callbacks
|
||||
- Single platform implementation (Android only, Kotlin/Java)
|
||||
- Singleton WCH UART manager on the native side
|
||||
|
||||
## Layers
|
||||
|
||||
### Dart Public API Layer
|
||||
- **Purpose:** Static facade providing the consumer-facing API
|
||||
- **Location:** `D:\code\new_git_code\flutter\ch34\lib\src\ch34_manager.dart`
|
||||
- **Contains:** Static methods delegating to `Ch34Platform.instance`
|
||||
- **Depends on:** `Ch34Platform`
|
||||
- **Used by:** Flutter app consuming the plugin
|
||||
|
||||
### Dart Platform Interface Layer
|
||||
- **Purpose:** Abstract interface defining all plugin methods with token verification
|
||||
- **Location:** `D:\code\new_git_code\flutter\ch34\lib\src\ch34_platform_interface.dart`
|
||||
- **Contains:** `abstract class Ch34Platform extends PlatformInterface` with 33 method signatures
|
||||
- **Depends on:** `plugin_platform_interface` package, `ch34_types.dart`
|
||||
- **Used by:** `MethodChannelCh34` for concrete implementation
|
||||
|
||||
### Dart MethodChannel Implementation Layer
|
||||
- **Purpose:** Bridges abstract platform interface to native Android via Flutter's platform channels
|
||||
- **Location:** `D:\code\new_git_code\flutter\ch34\lib\src\ch34_method_channel.dart`
|
||||
- **Contains:** `class MethodChannelCh34 extends Ch34Platform` with actual channel communication
|
||||
- **Channels:**
|
||||
- `MethodChannel('ch34')` - synchronous method calls
|
||||
- `EventChannel('ch34/data')` - serial data stream
|
||||
- `EventChannel('ch34/modem')` - modem status stream
|
||||
- `EventChannel('ch34/usb_state')` - USB hotplug events
|
||||
- **Depends on:** `dart:async`, `flutter/services.dart`
|
||||
- **Used by:** `Ch34Manager` (via `Ch34Platform.instance`)
|
||||
|
||||
### Dart Types Layer
|
||||
- **Purpose:** Shared data types and enums for the plugin
|
||||
- **Location:** `D:\code\new_git_code\flutter\ch34\lib\src\types\ch34_types.dart`
|
||||
- **Contains:**
|
||||
- Enums: `DataBits`, `StopBits`, `Parity`, `GpioDirection`, `GpioValue`, `SerialErrorType`
|
||||
- Data classes: `GpioStatus`, `SerialParameter`, `ModemStatus`, `UsbDeviceInfo`
|
||||
- Exception: `Ch34Exception` (defined in `ch34_method_channel.dart`)
|
||||
|
||||
### Android Native Layer
|
||||
- **Purpose:** Actual WCH UART library interaction, USB device management
|
||||
- **Location:** `D:\code\new_git_code\flutter\ch34\android\src\main\java\com\example\ch34\`
|
||||
- **Contains:**
|
||||
- `Ch34Plugin.java` - Main plugin entry point, MethodCallHandler
|
||||
- `Ch34DataStreamHandler.java` - Data EventChannel stream handler with WCH callback bridging
|
||||
- `Ch34ModemStreamHandler.java` - Modem status EventChannel stream handler
|
||||
- `Ch34UsbStateStreamHandler.java` - USB hotplug EventChannel with BroadcastReceiver
|
||||
- `Ch34TypeConverter.java` - Type conversion between Dart and WCH types
|
||||
- **Depends on:** `CH34XUARTDriver.jar` (bundled in `android/libs/`)
|
||||
- **Used by:** Dart MethodChannel layer
|
||||
|
||||
### Backward Compatibility Layer
|
||||
- **Location:** `D:\code\new_git_code\flutter\ch34\lib\ch34_platform_interface.dart`, `D:\code\new_git_code\flutter\ch34\lib\ch34_method_channel.dart`
|
||||
- **Purpose:** Re-exports from `src/` for consumers using old import paths
|
||||
|
||||
## Data Flow
|
||||
|
||||
### Device Discovery Flow
|
||||
1. Consumer calls `Ch34Manager.enumDevice()`
|
||||
2. Delegates to `Ch34Platform.instance.enumDevice()`
|
||||
3. `MethodChannelCh34.enumDevice()` invokes `methodChannel.invokeMethod('enumDevice')`
|
||||
4. `Ch34Plugin.enumDevice()` calls `WCHUARTManager.enumDevice()` on native side
|
||||
5. Native iterates devices, extracts VID/PID/serialCount/chipType
|
||||
6. Returns `List<Map>` through MethodChannel
|
||||
7. Dart side deserializes into `List<UsbDeviceInfo>`
|
||||
|
||||
### Data Read Flow (Callback Mode)
|
||||
1. Consumer calls `Ch34Manager.registerDataCallback(deviceName, serialNumber, onData)`
|
||||
2. `MethodChannelCh34` subscribes to `EventChannel('ch34/data')`
|
||||
3. Invokes `methodChannel.invokeMethod('registerDataCallback')`
|
||||
4. `Ch34Plugin` registers `Ch34DataStreamHandler.getWchCallback()` with `WCHUARTManager.registerDataCallback()`
|
||||
5. When data arrives, WCH triggers `IDataCallback.onData()`
|
||||
6. `Ch34DataStreamHandler` bridges data to EventChannel
|
||||
7. Dart receives via `StreamSubscription`, invokes user callback
|
||||
|
||||
### Data Write Flow
|
||||
1. Consumer calls `Ch34Manager.writeData(deviceName, serialNumber, data)`
|
||||
2. Delegates through platform interface to `MethodChannelCh34`
|
||||
3. `methodChannel.invokeMethod('writeData', {data: data, ...})`
|
||||
4. `Ch34Plugin.writeData()` calls `WCHUARTManager.writeData(device, serial, data, length, timeout)`
|
||||
5. Returns bytes written count
|
||||
|
||||
### USB Hotplug Event Flow
|
||||
1. `Ch34UsbStateStreamHandler` registers `BroadcastReceiver` for `ACTION_USB_DEVICE_ATTACHED/DETACHED`
|
||||
2. Android system sends broadcast on USB events
|
||||
3. BroadcastReceiver extracts `UsbDevice` and calls `notifyStateChanged()`
|
||||
4. EventChannel sends `{deviceName, connected}` to Dart
|
||||
5. `MethodChannelCh34` invokes `_usbStateCallback`
|
||||
|
||||
### Device Open Flow
|
||||
1. Consumer calls `Ch34Manager.openDevice(deviceName)`
|
||||
2. `Ch34Plugin.openDevice()` attempts `WCHUARTManager.openDevice(device)`
|
||||
3. If `NoPermissionException`, requests permission then retries after 2s delay
|
||||
4. On success, adds to `openedDevices` map and notifies `usbStateStreamHandler`
|
||||
|
||||
## State Management
|
||||
|
||||
**Native State:**
|
||||
- `WCHUARTManager` - Singleton instance, lazy-initialized in `ensureManagerInitialized()`
|
||||
- `openedDevices: Map<String, UsbDevice>` - tracks currently opened devices
|
||||
- Three `EventChannel.StreamHandler` instances - each manages its own `EventSink`
|
||||
|
||||
**Dart State:**
|
||||
- `Ch34Platform._instance` - Singleton platform instance
|
||||
- `MethodChannelCh34` maintains `_dataSubscription`, `_modemSubscription`, `_usbStateSubscription` - StreamSubscriptions for event channels
|
||||
- Callback references: `_dataCallback`, `_modemCallback`, `_usbStateCallback`
|
||||
|
||||
**State Cleanup:**
|
||||
- `Ch34Plugin.close()` calls `closeAllDevices()` to disconnect all and clear map
|
||||
- `Ch34UsbStateStreamHandler.onCancel()` unregisters BroadcastReceiver
|
||||
- `Ch34Manager.close()` triggers platform cleanup and cancels all subscriptions
|
||||
|
||||
## Key Abstractions
|
||||
|
||||
### Platform Interface (`Ch34Platform`)
|
||||
- **Purpose:** Abstract contract that decouples Flutter code from native implementation
|
||||
- **Example:** `D:\code\new_git_code\flutter\ch34\lib\src\ch34_platform_interface.dart`
|
||||
- **Pattern:** Token-based platform interface verification from `plugin_platform_interface` package
|
||||
- **Pattern:**
|
||||
```dart
|
||||
static final Object _token = Object();
|
||||
static Ch34Platform get instance { ... }
|
||||
static void registerDefaultInstance(Ch34Platform instance) {
|
||||
PlatformInterface.verifyToken(instance, _token);
|
||||
_instance = instance;
|
||||
}
|
||||
```
|
||||
|
||||
### Manager Facade (`Ch34Manager`)
|
||||
- **Purpose:** Static facade simplifying API usage for consumers
|
||||
- **Example:** `D:\code\new_git_code\flutter\ch34\lib\src\ch34_manager.dart`
|
||||
- **Pattern:** All static methods, all delegate to `Ch34Platform.instance`
|
||||
- **Pattern:**
|
||||
```dart
|
||||
static Future<List<UsbDeviceInfo>> enumDevice() {
|
||||
return Ch34Platform.instance.enumDevice();
|
||||
}
|
||||
```
|
||||
|
||||
### Type Converter (`Ch34TypeConverter`)
|
||||
- **Purpose:** Bridge between Dart enum indices and WCH native enum values
|
||||
- **Example:** `D:\code\new_git_code\flutter\ch34\android\src\main\java\com\example\ch34\Ch34TypeConverter.java`
|
||||
- **Pattern:** Static utility class with conversion methods for GPIO direction/value, serial error types
|
||||
|
||||
## Entry Points
|
||||
|
||||
### Dart Library Entry
|
||||
- **Location:** `D:\code\new_git_code\flutter\ch34\lib\ch34.dart`
|
||||
- **Triggers:** Consumer imports `package:ch34/ch34.dart`
|
||||
- **Responsibilities:** Exports `Ch34Manager`, `Ch34Platform`, all types, and `Ch34Exception`
|
||||
|
||||
### Plugin Initialization (Android)
|
||||
- **Location:** `D:\code\new_git_code\flutter\ch34\android\src\main\java\com\example\ch34\Ch34Plugin.java`
|
||||
- **Trigger:** Flutter engine attaches plugin via `onAttachedToEngine()`
|
||||
- **Responsibilities:** Creates MethodChannel, 3 EventChannels, sets MethodCallHandler
|
||||
- **Declaration:** `D:\code\new_git_code\flutter\ch34\android\build.gradle` + `pubspec.yaml` declares `package: com.example.ch34`, `pluginClass: Ch34Plugin`
|
||||
|
||||
### MethodChannel Dispatcher
|
||||
- **Location:** `D:\code\new_git_code\flutter\ch34\android\src\main\java\com\example\ch34\Ch34Plugin.java` line 80-187
|
||||
- **Trigger:** Dart invokes `methodChannel.invokeMethod()`
|
||||
- **Responsibilities:** Switch dispatch to 33 method handlers, error handling wrapper
|
||||
|
||||
## Error Handling
|
||||
|
||||
**Strategy:** Native exceptions caught and converted to MethodChannel error responses with error codes; Dart side uses null-coalescing defaults and `Ch34Exception` for explicit failures.
|
||||
|
||||
**Patterns:**
|
||||
- **Native:** Each method handler has try-catch, returns `result.error(ERROR_CODE, message, null)`
|
||||
- Error codes: `ENUM_DEVICE_FAILED`, `OPEN_DEVICE_FAILED`, `PERMISSION_DENIED`, `WRITE_DATA_FAILED`, etc.
|
||||
- **Dart:** Default fallbacks on null results (e.g., `result ?? false`, `result ?? Uint8List(0)`)
|
||||
- **Exception class:** `Ch34Exception` in `D:\code\new_git_code\flutter\ch34\lib\src\ch34_method_channel.dart` for throwing on Dart side
|
||||
- **Logging:** All errors logged via `Log.e(TAG, "...")` on Android native side
|
||||
|
||||
## Cross-Cutting Concerns
|
||||
|
||||
**Logging:** Android `android.util.Log` with per-class TAGs (`Ch34Plugin`, `Ch34DataStream`, `Ch34ModemStream`, `Ch34UsbStateStream`). Dart side uses `flutter/foundation.dart` `debugPrint()` for callback errors.
|
||||
|
||||
**Validation:** Device name resolution via `getDeviceOrThrow()` which searches `openedDevices` map first, then falls back to `manager.enumDevice()` lookup.
|
||||
|
||||
**Authentication/Permission:** USB permission flow handled in `openDevice()` - attempts open, if `NoPermissionException`, calls `manager.requestPermission()` then retries after 2-second delay.
|
||||
|
||||
---
|
||||
|
||||
*Architecture analysis: 2026-04-16*
|
||||
167
.planning/codebase/CONCERNS.md
Normal file
167
.planning/codebase/CONCERNS.md
Normal file
@@ -0,0 +1,167 @@
|
||||
# Codebase Concerns
|
||||
|
||||
**Analysis Date:** 2026-04-16
|
||||
|
||||
## Technical Debt
|
||||
|
||||
### Placeholder Content in LICENSE and CHANGELOG
|
||||
- Issue: `LICENSE` contains "TODO: Add your license here" and `CHANGELOG.md` contains "TODO: Describe initial release." No license or changelog content exists.
|
||||
- Files: `D:\code\new_git_code\flutter\ch34\LICENSE`, `D:\code\new_git_code\flutter\ch34\CHANGELOG.md`
|
||||
- Impact: Legal risk - the package has no defined license. No version history for consumers.
|
||||
- Fix approach: Add an appropriate open-source license (e.g., MIT). Document release history in CHANGELOG.
|
||||
|
||||
### Unimplemented Modem Error Callbacks
|
||||
- Issue: In `registerModemStatusCallback`, the `IModemStatus` interface has three error callbacks (`onOverrunError`, `onParityError`, `onFrameError`) with empty bodies - they silently discard errors.
|
||||
- Files: `D:\code\new_git_code\flutter\ch34\android\src\main\java\com\example\ch34\Ch34Plugin.java` (lines 667-677)
|
||||
- Impact: Serial communication errors are invisible to the Flutter layer. Users cannot detect data corruption.
|
||||
- Fix approach: Forward these errors through the EventChannel or the data callback, or expose a separate error stream.
|
||||
|
||||
### GPIO Status Map Always Returns Hardcoded Values
|
||||
- Issue: `Ch34TypeConverter.gpioStatusToMap()` hardcodes `index` and `value` to 0, regardless of the actual `GPIO_Status` object. Only `enabled` and `direction` are read from the status.
|
||||
- Files: `D:\code\new_git_code\flutter\ch34\android\src\main\java\com\example\ch34\Ch34TypeConverter.java` (lines 28-29)
|
||||
- Impact: Dart side `queryGpioStatus()` and `queryAllGpioStatus()` always return incorrect `index` and `value` fields.
|
||||
- Fix approach: Read `status.getIndex()` and `status.getValue()` from the WCH `GPIO_Status` object.
|
||||
|
||||
## Known Bugs
|
||||
|
||||
### Data Callback Only Supports Single Device Per Serial Number
|
||||
- Issue: `Ch34DataStreamHandler` uses a single `_dataCallback` field. When multiple devices register data callbacks, each new registration overwrites the previous one. Only the last registered device's data is received.
|
||||
- Files: `D:\code\new_git_code\flutter\ch34\lib\src\ch34_method_channel.dart` (line 44), `D:\code\new_git_code\flutter\ch34\android\src\main\java\com\example\ch34\Ch34Plugin.java` (lines 388-407)
|
||||
- Impact: Multi-device scenarios lose data from all but the most recently registered device.
|
||||
- Fix approach: Use a map keyed by `(deviceName, serialNumber)` to manage multiple independent subscriptions.
|
||||
|
||||
### Hardcoded USB Permission Wait Time
|
||||
- Issue: In `openDevice`, when the initial open fails, a `Handler.postDelayed` waits 2000ms and retries. This is a magic number that may be insufficient on slow devices or excessive on fast ones.
|
||||
- Files: `D:\code\new_git_code\flutter\ch34\android\src\main\java\com\example\ch34\Ch34Plugin.java` (line 278)
|
||||
- Impact: Permission may not be granted in time on some devices, leading to false failure.
|
||||
- Fix approach: Use a proper permission result callback or `PendingIntent` instead of a fixed delay.
|
||||
|
||||
### `tryOpenDevice` Swallows Non-Permission Exceptions
|
||||
- Issue: In `tryOpenDevice`, any non-`NoPermissionException` error logs the error but returns `true`, treating it as success.
|
||||
- Files: `D:\code\new_git_code\flutter\ch34\android\src\main\java\com\example\ch34\Ch34Plugin.java` (lines 291-300)
|
||||
- Impact: Failed device opens are reported as successful, leading to downstream errors that are harder to debug.
|
||||
- Fix approach: Return `false` for any exception and let the caller handle the error properly.
|
||||
|
||||
## Security Concerns
|
||||
|
||||
### No Input Validation on Native Method Arguments
|
||||
- Issue: The Java plugin directly casts method call arguments without null checks or type validation (e.g., `call.argument("deviceName")` cast to `String` without null guards).
|
||||
- Files: `D:\code\new_git_code\flutter\ch34\android\src\main\java\com\example\ch34\Ch34Plugin.java` (throughout)
|
||||
- Impact: Malformed or malicious method call arguments from Flutter can cause `ClassCastException` or `NullPointerException` on the Android side.
|
||||
- Fix approach: Add null/type checks before using arguments. Return error results for invalid inputs.
|
||||
|
||||
### `pubspec.lock` Ignored for Library Packages
|
||||
- Issue: `pubspec.lock` is in `.gitignore`, which is correct for library packages. However, `example/pubspec.lock` is not explicitly tracked, meaning the example app's dependency versions are not pinned.
|
||||
- Files: `D:\code\new_git_code\flutter\ch34\.gitignore` (line 26)
|
||||
- Impact: Example app may resolve to different dependency versions on different machines, causing inconsistent test results.
|
||||
- Fix approach: Consider committing `example/pubspec.lock` for reproducible example builds.
|
||||
|
||||
## Performance Bottlenecks
|
||||
|
||||
### Synchronous `enumDevice` in `getDeviceOrThrow`
|
||||
- Issue: `getDeviceOrThrow` calls `manager.enumDevice()` to enumerate all devices just to find one by name. This is called for nearly every method invocation.
|
||||
- Files: `D:\code\new_git_code\flutter\ch34\android\src\main\java\com\example\ch34\Ch34Plugin.java` (lines 772-787)
|
||||
- Impact: USB enumeration is slow and repeated unnecessarily. Every method call (read, write, set parameters) triggers this scan if the device is not in `openedDevices`.
|
||||
- Fix approach: Cache device references when they are discovered, or maintain a device lookup map.
|
||||
|
||||
### Blocking `readData` Without Timeout
|
||||
- Issue: `readData` calls `manager.readData(device, serialNumber)` synchronously with no timeout. If no data arrives, the method blocks indefinitely.
|
||||
- Files: `D:\code\new_git_code\flutter\ch34\android\src\main\java\com\example\ch34\Ch34Plugin.java` (lines 373-385), `D:\code\new_git_code\flutter\ch34\lib\src\ch34_method_channel.dart` (lines 176-185)
|
||||
- Impact: Flutter UI thread could freeze if a `readData` call blocks on a quiet serial line.
|
||||
- Fix approach: Use the async callback-based approach for reading, or enforce a configurable timeout.
|
||||
|
||||
## Fragile Areas
|
||||
|
||||
### Single Global WCHUARTManager Instance
|
||||
- Issue: The entire plugin uses a single `WCHUARTManager` instance stored as a field. It is lazily initialized in `ensureManagerInitialized()` and never reset except on engine detach.
|
||||
- Files: `D:\code\new_git_code\flutter\ch34\android\src\main\java\com\example\ch34\Ch34Plugin.java` (lines 57, 757-770)
|
||||
- Impact: If the WCH library is in a bad state (e.g., after an unhandled error), there is no recovery mechanism. Hot restart during development may cause initialization issues.
|
||||
- Fix approach: Add a `reset()` or `reinitialize()` method for recovery scenarios.
|
||||
|
||||
### EventChannel Single Instance for All Devices
|
||||
- Issue: Each EventChannel (`ch34/data`, `ch34/modem`, `ch34/usb_state`) has a single StreamHandler instance for all devices. The data handler must multiplex all device data through one channel.
|
||||
- Files: `D:\code\new_git_code\flutter\ch34\android\src\main\java\com\example\ch34\Ch34Plugin.java` (lines 66-76), `D:\code\new_git_code\flutter\ch34\android\src\main\java\com\example\ch34\Ch34DataStreamHandler.java`
|
||||
- Impact: Adding device identification to each data event is the responsibility of the consumer. No per-device stream isolation.
|
||||
- Fix approach: Include `deviceName` and `serialNumber` in each data event payload, or create per-device EventChannels.
|
||||
|
||||
### `removeDataCallback` Ignores `serialNumber`
|
||||
- Issue: The Dart API `removeDataCallback` takes only `deviceName`, but `registerDataCallback` takes both `deviceName` and `serialNumber`. If a device has multiple serial ports, removing the callback for one serial number removes all.
|
||||
- Files: `D:\code\new_git_code\flutter\ch34\lib\src\ch34_platform_interface.dart` (line 166), `D:\code\new_git_code\flutter\ch34\lib\src\ch34_manager.dart` (lines 160-162)
|
||||
- Impact: Multi-port devices cannot independently manage callbacks per serial port.
|
||||
- Fix approach: Add `serialNumber` parameter to `removeDataCallback`.
|
||||
|
||||
### Swallowed Exceptions in `enumDevice` Loop
|
||||
- Issue: In `enumDevice`, exceptions during `getSerialCount` or `getChipType` are silently caught and ignored. The device is still reported but with incomplete data.
|
||||
- Files: `D:\code\new_git_code\flutter\ch34\android\src\main\java\com\example\ch34\Ch34Plugin.java` (lines 215)
|
||||
- Impact: Consumers may receive devices with `serialCount = -1` and `chipType = null` with no indication of what went wrong.
|
||||
- Fix approach: At minimum log the exception, or distinguish between "no data available" and "error reading data".
|
||||
|
||||
## Missing Patterns
|
||||
|
||||
### No Structured Error Handling
|
||||
- Issue: The plugin uses `result.error(code, message, null)` with string codes for error reporting. The Dart side never throws typed exceptions (except `Ch34Exception` which is only used for GPIO failures). Most errors silently return `false` or `0`.
|
||||
- Files: `D:\code\new_git_code\flutter\ch34\lib\src\ch34_method_channel.dart` (throughout), `D:\code\new_git_code\flutter\ch34\android\src\main\java\com\example\ch34\Ch34Plugin.java` (throughout)
|
||||
- Impact: Consumers cannot distinguish between different failure modes. Debugging requires enabling debug mode and reading logcat.
|
||||
- Fix approach: Throw `Ch34Exception` with error code and message from all `result.error` responses. Define error code constants.
|
||||
|
||||
### No Lifecycle Management
|
||||
- Issue: The plugin does not handle Android lifecycle events (e.g., app going to background). Active USB connections may be left open or become invalid.
|
||||
- Files: `D:\code\new_git_code\flutter\ch34\android\src\main\java\com\example\ch34\Ch34Plugin.java`
|
||||
- Impact: When the app resumes, existing connections may be in an undefined state.
|
||||
- Fix approach: Implement `ActivityAware` or use `AppLifecycleState` in Dart to pause/resume or clean up connections.
|
||||
|
||||
### No Unit Test Coverage for Core Logic
|
||||
- Issue: The only unit tests cover `getPlatformVersion` and `enumDevice` with stub returns. None of the data conversion, parameter handling, or error paths are tested.
|
||||
- Files: `D:\code\new_git_code\flutter\ch34\test\ch34_test.dart`, `D:\code\new_git_code\flutter\ch34\test\ch34_method_channel_test.dart`, `D:\code\new_git_code\flutter\ch34\android\src\test\java\com\example\ch34\Ch34PluginTest.java`
|
||||
- Impact: Refactoring or bug fixes risk introducing regressions with no automated detection.
|
||||
- Fix approach: Add tests for type conversion, error handling, and the mock platform covering all API methods.
|
||||
|
||||
## Dependencies Risk
|
||||
|
||||
### Bundled JAR Dependency Without Version Pinning
|
||||
- Issue: The WCH `CH34XUARTDriver.jar` is included as a local JAR file in `android/libs/` with no version information in the build file. It is loaded via `flatDir` repository.
|
||||
- Files: `D:\code\new_git_code\flutter\ch34\android\build.gradle` (line 47)
|
||||
- Impact: No easy way to track which version of the WCH library is used. Cannot update to newer versions without manual replacement. No transitive dependency management.
|
||||
- Fix approach: Document the JAR version used. Consider hosting in a Maven repository for proper version management.
|
||||
|
||||
### Android Gradle Plugin Version is Outdated
|
||||
- Issue: The build uses `com.android.tools.build:gradle:7.3.0` which is significantly outdated. Current versions are 8.x.
|
||||
- Files: `D:\code\new_git_code\flutter\ch34\android\build.gradle` (line 11)
|
||||
- Impact: May not be compatible with newer Flutter/Android SDK versions. Missing bug fixes and performance improvements.
|
||||
- Fix approach: Update to AGP 8.x and test thoroughly. Update `compileSdk` and `targetSdk` accordingly.
|
||||
|
||||
### Java 8 Target Compatibility
|
||||
- Issue: The plugin compiles with `JavaVersion.VERSION_1_8` as source and target.
|
||||
- Files: `D:\code\new_git_code\flutter\ch34\android\build.gradle` (lines 35-36)
|
||||
- Impact: Cannot use modern Java language features. May limit compatibility with future Android tooling.
|
||||
- Fix approach: Consider updating to Java 11+ if the minimum SDK and tooling support it.
|
||||
|
||||
## Test Coverage Gaps
|
||||
|
||||
### No Integration Tests for USB Operations
|
||||
- Issue: The single integration test only checks `getPlatformVersion`. No tests for actual USB device operations.
|
||||
- Files: `D:\code\new_git_code\flutter\ch34\example\integration_test\plugin_integration_test.dart`
|
||||
- Risk: USB-specific bugs are only caught through manual testing.
|
||||
- Priority: High
|
||||
|
||||
### No Tests for Type Conversions
|
||||
- Issue: `Ch34TypeConverter` has zero test coverage. The GPIO and error type conversions are not verified.
|
||||
- Files: `D:\code\new_git_code\flutter\ch34\android\src\test\java\com\example\ch34\Ch34PluginTest.java`
|
||||
- Risk: Conversion bugs (like the hardcoded GPIO values) go undetected.
|
||||
- Priority: High
|
||||
|
||||
### No Tests for EventChannel Streams
|
||||
- Issue: No tests verify that data callbacks, modem status, or USB state events flow correctly from native to Dart.
|
||||
- Files: `D:\code\new_git_code\flutter\ch34\test\ch34_method_channel_test.dart`, `D:\code\new_git_code\flutter\ch34\test\ch34_test.dart`
|
||||
- Risk: Stream subscription and cancellation logic may have bugs that only manifest at runtime.
|
||||
- Priority: Medium
|
||||
|
||||
### No Tests for Exception Paths
|
||||
- Issue: All tests use happy-path mock returns. No tests simulate errors, timeouts, or null responses.
|
||||
- Files: `D:\code\new_git_code\flutter\ch34\test\ch34_test.dart`
|
||||
- Risk: Error handling code is untested and may fail silently or crash.
|
||||
- Priority: Medium
|
||||
|
||||
---
|
||||
|
||||
*Concerns audit: 2026-04-16*
|
||||
237
.planning/codebase/CONVENTIONS.md
Normal file
237
.planning/codebase/CONVENTIONS.md
Normal file
@@ -0,0 +1,237 @@
|
||||
# Coding Conventions
|
||||
|
||||
**Analysis Date:** 2026-04-16
|
||||
|
||||
## Overview
|
||||
|
||||
This is a Flutter plugin package (`ch34`) that provides USB-to-serial communication for WCH CH34X series chips. The codebase is written in Dart with Android native code (Java). The plugin follows the standard Flutter plugin federated architecture pattern.
|
||||
|
||||
## Naming Patterns
|
||||
|
||||
**Files:**
|
||||
- Dart files use `snake_case`: `ch34_manager.dart`, `ch34_platform_interface.dart`, `ch34_method_channel.dart`, `ch34_types.dart`
|
||||
- Test files mirror source file names: `ch34_test.dart`, `ch34_method_channel_test.dart`
|
||||
- Java native files use `PascalCase`: `Ch34Plugin.java`, `Ch34UsbStateStreamHandler.java`
|
||||
|
||||
**Classes:**
|
||||
- PascalCase with `Ch34` prefix for public API: `Ch34Manager`, `Ch34Platform`, `MethodChannelCh34`, `Ch34Exception`
|
||||
- Domain models without prefix: `GpioStatus`, `SerialParameter`, `ModemStatus`, `UsbDeviceInfo`
|
||||
- Enum naming: `DataBits`, `StopBits`, `Parity`, `GpioDirection`, `GpioValue`, `SerialErrorType`
|
||||
|
||||
**Functions/Methods:**
|
||||
- camelCase for all methods: `getPlatformVersion()`, `enumDevice()`, `openDevice()`, `setSerialParameter()`
|
||||
- Callback-style methods use descriptive names: `setUsbStateListener()`, `registerDataCallback()`, `registerModemStatusCallback()`
|
||||
- Private methods use underscore prefix: `_cancelDataSubscription()`
|
||||
- Private constructor pattern: `Ch34Manager._()` (static-only class)
|
||||
|
||||
**Variables:**
|
||||
- camelCase: `deviceName`, `serialNumber`, `gpioIndex`, `onStateChanged`
|
||||
- Private fields use underscore prefix: `_dataSubscription`, `_modemCallback`
|
||||
- Constants use const: `const MethodChannel('ch34')`
|
||||
|
||||
**Parameters:**
|
||||
- Named parameters use curly braces for optional: `{int timeout = 0}`
|
||||
- Required named parameters use `required` keyword in data classes
|
||||
|
||||
## Code Style
|
||||
|
||||
**Formatting:**
|
||||
- Uses `flutter_lints` package (`^3.0.0`) with default `flutter.yaml` ruleset
|
||||
- Config: `D:/code/new_git_code/flutter/ch34/analysis_options.yaml`
|
||||
- Example app config: `D:/code/new_git_code/flutter/ch34/example/analysis_options.yaml` (same base, with commented-out custom rules)
|
||||
- No custom lint rules are enabled beyond the default Flutter set
|
||||
- No `.prettierrc` or other formatter config (uses Dart formatter defaults)
|
||||
|
||||
**SDK Constraints:**
|
||||
- SDK: `>=3.4.3 <4.0.0` (`D:/code/new_git_code/flutter/ch34/pubspec.yaml`)
|
||||
- Flutter: `>=3.3.0`
|
||||
|
||||
**Import Organization:**
|
||||
1. Dart SDK imports first: `import 'dart:typed_data';`, `import 'dart:async';`
|
||||
2. Flutter framework imports: `import 'package:flutter/services.dart';`
|
||||
3. Third-party package imports: `import 'package:plugin_platform_interface/plugin_platform_interface.dart';`
|
||||
4. Relative local imports last: `import 'ch34_platform_interface.dart';`, `import 'types/ch34_types.dart';`
|
||||
- Groups separated by blank lines
|
||||
|
||||
**Path Aliases:**
|
||||
- No path aliases detected; all imports use relative paths within the package
|
||||
|
||||
## Patterns
|
||||
|
||||
**Static Manager Pattern:**
|
||||
`D:/code/new_git_code/flutter/ch34/lib/src/ch34_manager.dart` uses a private constructor (`Ch34Manager._()`) with all static methods, delegating to `Ch34Platform.instance`. This provides a clean public API without requiring instantiation.
|
||||
|
||||
```dart
|
||||
class Ch34Manager {
|
||||
Ch34Manager._();
|
||||
|
||||
static Future<String?> getPlatformVersion() {
|
||||
return Ch34Platform.instance.getPlatformVersion();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Platform Interface (Federated Plugin):**
|
||||
`D:/code/new_git_code/flutter/ch34/lib/src/ch34_platform_interface.dart` extends `PlatformInterface` from `plugin_platform_interface`. Uses token-based verification for instance safety:
|
||||
|
||||
```dart
|
||||
abstract class Ch34Platform extends PlatformInterface {
|
||||
Ch34Platform() : super(token: _token);
|
||||
static final Object _token = Object();
|
||||
static Ch34Platform? _instance;
|
||||
|
||||
static Ch34Platform get instance { ... }
|
||||
static set instance(Ch34Platform instance) { ... }
|
||||
}
|
||||
```
|
||||
|
||||
**MethodChannel + EventChannel Pattern:**
|
||||
`D:/code/new_git_code/flutter/ch34/lib/src/ch34_method_channel.dart` uses:
|
||||
- `MethodChannel` for request-response calls: `methodChannel.invokeMethod<bool>('openDevice', {...})`
|
||||
- `EventChannel` for streaming data: `dataEventChannel.receiveBroadcastStream({'deviceName': deviceName}).listen(...)`
|
||||
- `@visibleForTesting` annotation for testable fields (channel names)
|
||||
|
||||
**Data Class Pattern:**
|
||||
`D:/code/new_git_code/flutter/ch34/lib/src/types/ch34_types.dart` uses immutable classes with:
|
||||
- `const` constructors
|
||||
- `fromMap()` factory for deserialization
|
||||
- `toMap()` method for serialization
|
||||
- `toString()` override for debugging
|
||||
- `copyWith()` for mutable updates (e.g., `SerialParameter.copyWith()`)
|
||||
- `==`/`hashCode` override for equality (e.g., `UsbDeviceInfo`)
|
||||
|
||||
```dart
|
||||
class SerialParameter {
|
||||
const SerialParameter({
|
||||
this.baud = 115200,
|
||||
this.dataBits = DataBits.bits8,
|
||||
...
|
||||
});
|
||||
|
||||
factory SerialParameter.fromMap(Map<dynamic, dynamic> map) { ... }
|
||||
Map<String, dynamic> toMap() { ... }
|
||||
SerialParameter copyWith({ ... }) { ... }
|
||||
}
|
||||
```
|
||||
|
||||
**Enum with Associated Values:**
|
||||
Enums use associated integer values with `fromValue()` factory:
|
||||
|
||||
```dart
|
||||
enum DataBits {
|
||||
bits5(5), bits6(6), bits7(7), bits8(8);
|
||||
const DataBits(this.value);
|
||||
final int value;
|
||||
static DataBits fromValue(int value) =>
|
||||
DataBits.values.firstWhere((e) => e.value == value, orElse: () => DataBits.bits8);
|
||||
}
|
||||
```
|
||||
|
||||
**Null-Safe Default Handling:**
|
||||
Method channel results consistently use null-coalescing for safe defaults:
|
||||
```dart
|
||||
return result ?? false; // boolean methods
|
||||
return result ?? 0; // integer methods
|
||||
return result ?? []; // list methods
|
||||
return result ?? Uint8List(0); // byte data
|
||||
```
|
||||
|
||||
**Section Comments:**
|
||||
Code is organized with clear separator comments:
|
||||
```dart
|
||||
/// ==================== 基础方法 ====================
|
||||
/// ==================== 设备枚举与识别 ====================
|
||||
/// ==================== 数据读写 ====================
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
**Custom Exception:**
|
||||
`D:/code/new_git_code/flutter/ch34/lib/src/ch34_method_channel.dart` defines `Ch34Exception`:
|
||||
```dart
|
||||
class Ch34Exception implements Exception {
|
||||
const Ch34Exception(this.message);
|
||||
final String message;
|
||||
@override
|
||||
String toString() => 'Ch34Exception: $message';
|
||||
}
|
||||
```
|
||||
|
||||
**Usage Patterns:**
|
||||
- Platform interface methods document `@throws Ch34Exception` in JSDoc comments for methods that may fail
|
||||
- Actual throwing is limited: only GPIO methods throw `Ch34Exception` when native returns null
|
||||
- Most boolean methods return `false` on failure rather than throwing
|
||||
- Example app uses generic `catch (e)` for error display in UI:
|
||||
```dart
|
||||
} catch (e) {
|
||||
setState(() {
|
||||
_status = '扫描失败: $e';
|
||||
});
|
||||
}
|
||||
```
|
||||
- `StateError` thrown when platform instance not initialized:
|
||||
```dart
|
||||
throw StateError('Ch34Platform.instance has not been initialized...');
|
||||
```
|
||||
|
||||
**Stream Error Handling:**
|
||||
EventChannel streams use `onError` callback with `debugPrint`:
|
||||
```dart
|
||||
onError: (error) {
|
||||
debugPrint('CH34 data callback error: $error');
|
||||
}
|
||||
```
|
||||
|
||||
## Logging
|
||||
|
||||
**Framework:** Flutter's `debugPrint` from `package:flutter/foundation.dart`
|
||||
|
||||
**Patterns:**
|
||||
- Used only in error callbacks within the method channel implementation
|
||||
- Log messages are descriptive with context prefix: `'CH34 USB state error: $error'`
|
||||
- No logging framework beyond `debugPrint`
|
||||
- Debug mode toggle available via `Ch34Manager.setDebug(bool)` (passes to native side)
|
||||
|
||||
## Comments & Documentation
|
||||
|
||||
**API Documentation:**
|
||||
- All public methods have `///` Dart doc comments in Chinese
|
||||
- Consistent param/return documentation using `@param` and `@return`
|
||||
- Exception documentation using `@throws`
|
||||
- Library-level docs at `D:/code/new_git_code/flutter/ch34/lib/ch34.dart` include usage example in code block
|
||||
|
||||
**Inline Comments:**
|
||||
- Chinese section headers for method grouping
|
||||
- Method-level comments explain purpose, parameters, and return values
|
||||
|
||||
**JSDoc/TSDoc Style:**
|
||||
- Uses `///` (not `/** */`) for doc comments
|
||||
- Parameter docs: `/// @param deviceName 设备名称。`
|
||||
- Return docs: `/// @return \`true\` 成功,\`false\` 失败。`
|
||||
|
||||
## Module Design
|
||||
|
||||
**Exports:**
|
||||
- Main entry `D:/code/new_git_code/flutter/ch34/lib/ch34.dart` exports all public types:
|
||||
```dart
|
||||
export 'src/ch34_manager.dart';
|
||||
export 'src/types/ch34_types.dart';
|
||||
export 'src/ch34_platform_interface.dart' show Ch34Platform;
|
||||
export 'src/ch34_method_channel.dart' show Ch34Exception;
|
||||
```
|
||||
- Backward-compatible barrel files at `lib/` root:
|
||||
- `D:/code/new_git_code/flutter/ch34/lib/ch34_platform_interface.dart`
|
||||
- `D:/code/new_git_code/flutter/ch34/lib/ch34_method_channel.dart`
|
||||
|
||||
**Layer Separation:**
|
||||
```
|
||||
lib/ch34.dart -> Public API entry (exports)
|
||||
lib/src/ch34_manager.dart -> Static facade (public-facing)
|
||||
lib/src/ch34_platform_interface.dart -> Abstract interface
|
||||
lib/src/ch34_method_channel.dart -> Concrete implementation
|
||||
lib/src/types/ch34_types.dart -> Data types and enums
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
*Convention analysis: 2026-04-16*
|
||||
95
.planning/codebase/INTEGRATIONS.md
Normal file
95
.planning/codebase/INTEGRATIONS.md
Normal file
@@ -0,0 +1,95 @@
|
||||
# External Integrations
|
||||
|
||||
**Analysis Date:** 2026-04-16
|
||||
|
||||
## Hardware/Device
|
||||
|
||||
**WCH CH34X Series USB-to-Serial Chips:**
|
||||
- **CH340, CH341, CH342, CH343, CH344, CH347** - Standard USB-to-serial converters
|
||||
- **CH9101, CH9102, CH9103, CH9104** - Newer generation USB-to-serial chips
|
||||
- **CH9143** - Multi-port USB-to-serial chip
|
||||
|
||||
**SDK/Driver:**
|
||||
- Library: `CH34XUARTDriver.jar` (local JAR, 140KB)
|
||||
- Location: `android/libs/CH34XUARTDriver.jar`
|
||||
- Package: `cn.wch.uartlib`
|
||||
- Manager: `WCHUARTManager.getInstance()` - Singleton pattern
|
||||
|
||||
**USB Host Protocol:**
|
||||
- Android USB Host API (`android.hardware.usb.UsbManager`)
|
||||
- Feature declaration: `android.hardware.usb.host` (optional, in `android/src/main/AndroidManifest.xml`)
|
||||
- Permission model: Runtime USB permission via `WCHUARTManager.requestPermission()`
|
||||
|
||||
**VID/PID:**
|
||||
- Default VID/PID managed by WCH driver
|
||||
- Custom VID/PID supported via `Ch34Manager.addNewHardware(vid, pid)` - maps to `WCHUARTManager.addNewHardware()`
|
||||
|
||||
## Communication Channels
|
||||
|
||||
**MethodChannel (`'ch34'`):**
|
||||
- Location: `lib/src/ch34_method_channel.dart` (Dart), `android/src/main/java/com/example/ch34/Ch34Plugin.java` (Java)
|
||||
- 33 methods mapped: `enumDevice`, `openDevice`, `requestPermission`, `writeData`, `readData`, `setDtr`, `setRts`, `setBreak`, `enableGpio`, `setGpioVal`, `getGpioVal`, etc.
|
||||
- Error codes: `ENUM_DEVICE_FAILED`, `OPEN_DEVICE_FAILED`, `PERMISSION_DENIED`, `WRITE_DATA_FAILED`, `GPIO_CHECK_FAILED`, etc.
|
||||
|
||||
**EventChannel (`'ch34/data'`):**
|
||||
- Location: `android/src/main/java/com/example/ch34/Ch34DataStreamHandler.java`
|
||||
- Payload: `byte[]` serial data bytes
|
||||
- Registered via `manager.registerDataCallback(device, callback)`
|
||||
|
||||
**EventChannel (`'ch34/modem'`):**
|
||||
- Location: `android/src/main/java/com/example/ch34/Ch34ModemStreamHandler.java`
|
||||
- Payload: Map with `cts`, `dsr`, `ri`, `dcd` boolean values
|
||||
- Registered via `manager.registerModemStatusCallback(device, IModemStatus)`
|
||||
|
||||
**EventChannel (`'ch34/usb_state'`):**
|
||||
- Location: `android/src/main/java/com/example/ch34/Ch34UsbStateStreamHandler.java`
|
||||
- Payload: Map with `deviceName` (String) and `connected` (boolean)
|
||||
- Triggered by Android broadcast receiver (`UsbManager.ACTION_USB_DEVICE_ATTACHED`, `UsbManager.ACTION_USB_DEVICE_DETACHED`)
|
||||
|
||||
## Data Sources
|
||||
|
||||
**Serial Data:**
|
||||
- Read: `Ch34Manager.readData(deviceName, serialNumber)` -> `Uint8List` (blocking)
|
||||
- Stream: `Ch34Manager.registerDataCallback(deviceName, serialNumber, onData)` -> EventChannel push
|
||||
- Write: `Ch34Manager.writeData(deviceName, serialNumber, data)` -> `int` bytes written
|
||||
|
||||
**GPIO Data:**
|
||||
- Query: `Ch34Manager.queryGpioStatus(deviceName, gpioIndex)` -> `GpioStatus`
|
||||
- Query all: `Ch34Manager.queryAllGpioStatus(deviceName)` -> `List<GpioStatus>`
|
||||
- Control: `Ch34Manager.setGpioVal(deviceName, gpioIndex, value)` / `Ch34Manager.getGpioVal(deviceName, gpioIndex)`
|
||||
|
||||
**Modem Status:**
|
||||
- `ModemStatus` class with fields: `cts` (Clear To Send), `dsr` (Data Set Ready), `ri` (Ring Indicator), `dcd` (Data Carrier Detect)
|
||||
- Registered via `Ch34Manager.registerModemStatusCallback(deviceName, onModemStatus)`
|
||||
|
||||
**Error Types:**
|
||||
- `SerialErrorType.framingError` - Framing error
|
||||
- `SerialErrorType.parityError` - Parity error
|
||||
- `SerialErrorType.overrunError` - Overrun error
|
||||
- `SerialErrorType.breakInterrupt` - Break interrupt
|
||||
- Query via `Ch34Manager.querySerialErrorCount(deviceName, serialNumber, errorType)`
|
||||
|
||||
## Authentication
|
||||
|
||||
**USB Device Permission:**
|
||||
- Android runtime USB permission dialog via `WCHUARTManager.requestPermission(context, device)`
|
||||
- No API keys, tokens, or credentials required
|
||||
- Permission flow: `openDevice` -> if no permission, auto-request -> retry after 2000ms delay
|
||||
|
||||
## Third-party Services
|
||||
|
||||
**None.** This is a pure hardware driver plugin with no external API integrations, cloud services, or network dependencies.
|
||||
|
||||
## CI/CD & Deployment
|
||||
|
||||
**Hosting:** Not applicable (Flutter plugin package, published to pub.dev)
|
||||
**CI Pipeline:** Not detected (no `.github/`, `.gitlab-ci.yml`, or `.circleci/` found)
|
||||
|
||||
## Webhooks & Callbacks
|
||||
|
||||
**Incoming:** None
|
||||
**Outgoing:** None
|
||||
|
||||
---
|
||||
|
||||
*Integration audit: 2026-04-16*
|
||||
104
.planning/codebase/STACK.md
Normal file
104
.planning/codebase/STACK.md
Normal file
@@ -0,0 +1,104 @@
|
||||
# Technology Stack
|
||||
|
||||
**Analysis Date:** 2026-04-16
|
||||
|
||||
## Languages
|
||||
|
||||
**Primary:**
|
||||
- **Dart** >=3.4.3 - Flutter plugin Dart-side code (`lib/`, `test/`)
|
||||
- **Java** 8 - Android native plugin implementation (`android/src/main/java/`)
|
||||
|
||||
**Secondary:**
|
||||
- **Kotlin** 1.7.10 - Declared in example app Gradle plugins (`example/android/settings.gradle`) but not actively used in plugin code
|
||||
|
||||
## Runtime
|
||||
|
||||
**Environment:**
|
||||
- **Flutter** >=3.3.0
|
||||
- **Android SDK** - compileSdk 34, minSdk 21
|
||||
|
||||
**Package Manager:**
|
||||
- **pub** (Dart) - Lockfile: `pubspec.lock` present
|
||||
- **Gradle** 7.3.0 - Android build system
|
||||
|
||||
## Frameworks
|
||||
|
||||
**Core:**
|
||||
- **Flutter** >=3.3.0 - Cross-platform plugin framework
|
||||
- **Flutter Plugin API** - `FlutterPlugin`, `MethodCallHandler`, `MethodChannel`, `EventChannel`
|
||||
|
||||
**Testing:**
|
||||
- **flutter_test** (Flutter SDK) - Unit testing framework
|
||||
- **JUnit** 4.13.2 - Android native unit testing
|
||||
- **Mockito** 5.0.0 - Java mocking for Android tests
|
||||
- **integration_test** (Flutter SDK) - Integration testing
|
||||
|
||||
**Build/Dev:**
|
||||
- **flutter_lints** ^3.0.0 - Lint rules
|
||||
- **Gradle** 7.3.0 - Android build automation
|
||||
|
||||
## Key Dependencies
|
||||
|
||||
**Critical:**
|
||||
- **plugin_platform_interface** ^2.0.2 - Federated plugin platform interface pattern (`Ch34Platform` extends `PlatformInterface`)
|
||||
- **CH34XUARTDriver** (local JAR, 140KB) - WCH official UART library, located at `android/libs/CH34XUARTDriver.jar`. Provides `WCHUARTManager`, `ChipType2`, `IDataCallback`, `IModemStatus`, `GPIO_Status` etc.
|
||||
|
||||
**Infrastructure:**
|
||||
- **cupertino_icons** ^1.0.6 - Example app only, Material/Cupertino icon support
|
||||
|
||||
**Android Native Dependencies (WCH SDK):**
|
||||
- `cn.wch.uartlib.WCHUARTManager` - Core manager singleton
|
||||
- `cn.wch.uartlib.chipImpl.type.ChipType2` - Chip type identification
|
||||
- `cn.wch.uartlib.callback.IDataCallback` - Serial data callback interface
|
||||
- `cn.wch.uartlib.callback.IModemStatus` - Modem status callback interface
|
||||
- `cn.wch.uartlib.callback.IUsbStateChange` - USB state change callback
|
||||
- `cn.wch.uartlib.exception.*` - Exception classes (ChipException, NoPermissionException, UartLibException)
|
||||
|
||||
## Configuration
|
||||
|
||||
**Environment:**
|
||||
- `analysis_options.yaml` - Includes `package:flutter_lints/flutter.yaml`
|
||||
- No `.env` or environment variable configuration required
|
||||
- Secrets stored in example `local.properties` (flutter.sdk path)
|
||||
|
||||
**Build:**
|
||||
- `pubspec.yaml` - Main plugin manifest, package: `ch34`, version `1.0.0`
|
||||
- `pubspec.lock` - Dependency lockfile
|
||||
- `android/build.gradle` - Android library build config (namespace: `com.example.ch34`)
|
||||
- `android/settings.gradle` - Project name: `ch34`
|
||||
- `example/android/build.gradle` - Example app root build
|
||||
- `example/android/app/build.gradle` - Example app module (namespace: `com.example.ch34_example`)
|
||||
- `example/android/settings.gradle` - Gradle plugins including dev.flutter.flutter-plugin-loader
|
||||
|
||||
## Platform Requirements
|
||||
|
||||
**Development:**
|
||||
- Flutter SDK >=3.3.0
|
||||
- Dart SDK >=3.4.3
|
||||
- Android SDK compileSdk 34
|
||||
- Java 8+ (sourceCompatibility/targetCompatibility = JavaVersion.VERSION_1_8)
|
||||
|
||||
**Production:**
|
||||
- Android device with USB Host support (`android.hardware.usb.host` feature declared in `android/src/main/AndroidManifest.xml`)
|
||||
- Android minSdk 21 (Android 5.0 Lollipop)
|
||||
- Physical CH34X series USB-to-serial hardware connected via USB OTG
|
||||
|
||||
## Supported Chip Models
|
||||
|
||||
CH340, CH341, CH342, CH343, CH344, CH347, CH9101, CH9102, CH9103, CH9104, CH9143
|
||||
|
||||
## Build System
|
||||
|
||||
**Flutter Plugin:** Standard Flutter plugin structure with Android platform implementation.
|
||||
|
||||
**Communication Pattern:**
|
||||
- `MethodChannel('ch34')` - 33 method calls for device control
|
||||
- `EventChannel('ch34/data')` - Serial data streaming
|
||||
- `EventChannel('ch34/modem')` - Modem status streaming
|
||||
- `EventChannel('ch34/usb_state')` - USB plug/unplug events
|
||||
|
||||
**Architecture Pattern:** Federated plugin with platform interface (`Ch34Platform`), method channel implementation (`MethodChannelCh34`), and manager facade (`Ch34Manager`).
|
||||
|
||||
---
|
||||
|
||||
*Stack analysis: 2026-04-16*
|
||||
217
.planning/codebase/STRUCTURE.md
Normal file
217
.planning/codebase/STRUCTURE.md
Normal file
@@ -0,0 +1,217 @@
|
||||
# Codebase Structure
|
||||
|
||||
**Analysis Date:** 2026-04-16
|
||||
|
||||
## Directory Layout
|
||||
|
||||
```
|
||||
ch34/ # Flutter plugin root
|
||||
├── lib/ # Dart source code
|
||||
│ ├── ch34.dart # Main library entry point (exports)
|
||||
│ ├── ch34_platform_interface.dart # Backward compat: re-exports src/
|
||||
│ ├── ch34_method_channel.dart # Backward compat: re-exports src/
|
||||
│ └── src/ # Core implementation
|
||||
│ ├── ch34_manager.dart # Public static API facade
|
||||
│ ├── ch34_platform_interface.dart # Abstract platform interface
|
||||
│ ├── ch34_method_channel.dart # MethodChannel implementation
|
||||
│ └── types/
|
||||
│ └── ch34_types.dart # All data types and enums
|
||||
├── android/ # Android native code
|
||||
│ ├── build.gradle # Android library build config
|
||||
│ ├── settings.gradle # Gradle settings
|
||||
│ ├── libs/ # Native dependency JARs
|
||||
│ │ └── CH34XUARTDriver.jar # WCH UART library
|
||||
│ └── src/
|
||||
│ ├── main/java/com/example/ch34/
|
||||
│ │ ├── Ch34Plugin.java # Main plugin + MethodCallHandler
|
||||
│ │ ├── Ch34DataStreamHandler.java # Data EventChannel handler
|
||||
│ │ ├── Ch34ModemStreamHandler.java # Modem EventChannel handler
|
||||
│ │ ├── Ch34UsbStateStreamHandler.java # USB hotplug handler
|
||||
│ │ └── Ch34TypeConverter.java # Type conversion utilities
|
||||
│ └── test/java/com/example/ch34/
|
||||
│ └── Ch34PluginTest.java # Unit tests
|
||||
├── example/ # Example Flutter app
|
||||
│ ├── lib/
|
||||
│ │ └── main.dart # Example app with device scan/connect/send
|
||||
│ ├── test/
|
||||
│ │ └── widget_test.dart # Widget test (stub)
|
||||
│ └── integration_test/
|
||||
│ └── plugin_integration_test.dart # Integration test
|
||||
├── test/ # Plugin unit tests
|
||||
│ ├── ch34_test.dart # Platform interface + manager tests
|
||||
│ └── ch34_method_channel_test.dart # MethodChannel mock tests
|
||||
├── docs/
|
||||
│ └── CH34X-api_docs.md # WCH API documentation reference
|
||||
├── pubspec.yaml # Plugin package manifest
|
||||
├── analysis_options.yaml # Dart linting config
|
||||
├── CHANGELOG.md # Version history
|
||||
├── LICENSE # License file
|
||||
├── README.md # Plugin documentation
|
||||
└── .gitignore # Git ignore rules
|
||||
```
|
||||
|
||||
## Directory Purposes
|
||||
|
||||
### `lib/` - Dart Source
|
||||
- **Purpose:** All Dart code for the Flutter plugin
|
||||
- **Key files:**
|
||||
- `lib/ch34.dart` - Primary consumer import: `import 'package:ch34/ch34.dart';`
|
||||
- `lib/src/ch34_manager.dart` - Static facade, all public APIs
|
||||
- `lib/src/ch34_platform_interface.dart` - Abstract interface with 33 methods
|
||||
- `lib/src/ch34_method_channel.dart` - MethodChannel + EventChannel implementation
|
||||
- `lib/src/types/ch34_types.dart` - Shared data models and enums
|
||||
|
||||
### `android/` - Android Native
|
||||
- **Purpose:** Java implementation bridging to WCH CH34X UART library
|
||||
- **Key files:**
|
||||
- `android/src/main/java/com/example/ch34/Ch34Plugin.java` - Plugin entry point (788 lines)
|
||||
- `android/build.gradle` - Library configuration (compileSdk 34, minSdk 21, Java 8)
|
||||
- `android/libs/CH34XUARTDriver.jar` - WCH vendor SDK (binary dependency)
|
||||
|
||||
### `example/` - Sample Application
|
||||
- **Purpose:** Demonstrates plugin usage for development and testing
|
||||
- **Key files:**
|
||||
- `example/lib/main.dart` - Full working example: scan devices, open, set params, send/receive data
|
||||
|
||||
### `test/` - Plugin Tests
|
||||
- **Purpose:** Unit tests for the Dart plugin code
|
||||
- **Contains:** MethodChannel mock tests, platform interface mock implementation
|
||||
|
||||
## Key File Locations
|
||||
|
||||
**Entry Points:**
|
||||
- `lib/ch34.dart`: Main library entry, consumer-facing import
|
||||
- `android/src/main/java/com/example/ch34/Ch34Plugin.java`: Android plugin entry, registered via `pubspec.yaml`
|
||||
|
||||
**Configuration:**
|
||||
- `pubspec.yaml`: Plugin declaration, Android platform registration (`package: com.example.ch34`, `pluginClass: Ch34Plugin`)
|
||||
- `android/build.gradle`: Android library build configuration
|
||||
- `analysis_options.yaml`: Dart linting (extends `flutter_lints`)
|
||||
|
||||
**Core Logic:**
|
||||
- `lib/src/ch34_manager.dart`: Static API facade (all consumer methods)
|
||||
- `lib/src/ch34_platform_interface.dart`: Abstract interface contract
|
||||
- `lib/src/ch34_method_channel.dart`: Channel communication implementation
|
||||
- `android/src/main/java/com/example/ch34/Ch34Plugin.java`: Native bridge logic
|
||||
|
||||
**Types:**
|
||||
- `lib/src/types/ch34_types.dart`: All data classes (`UsbDeviceInfo`, `SerialParameter`, `ModemStatus`, `GpioStatus`) and enums (`DataBits`, `StopBits`, `Parity`, `GpioDirection`, `GpioValue`, `SerialErrorType`)
|
||||
|
||||
**Testing:**
|
||||
- `test/ch34_method_channel_test.dart`: Unit tests with mock platform
|
||||
- `test/ch34_test.dart`: Integration-style tests with real MethodChannel mock
|
||||
- `example/integration_test/plugin_integration_test.dart`: On-device integration test
|
||||
|
||||
## Naming Conventions
|
||||
|
||||
**Files:**
|
||||
- Dart: `snake_case.dart` for all files (e.g., `ch34_manager.dart`, `ch34_types.dart`)
|
||||
- Java: `PascalCase.java` for all classes (e.g., `Ch34Plugin.java`, `Ch34TypeConverter.java`)
|
||||
|
||||
**Dart Classes:**
|
||||
- Public API class: `Ch34Manager` (static methods, facade pattern)
|
||||
- Platform interface: `Ch34Platform` (abstract, extends `PlatformInterface`)
|
||||
- Channel implementation: `MethodChannelCh34` (extends `Ch34Platform`)
|
||||
- Data types: `UsbDeviceInfo`, `SerialParameter`, `ModemStatus`, `GpioStatus`
|
||||
- Enums: `DataBits`, `StopBits`, `Parity`, `GpioDirection`, `GpioValue`, `SerialErrorType`
|
||||
- Exception: `Ch34Exception`
|
||||
|
||||
**Java Classes:**
|
||||
- Main plugin: `Ch34Plugin` (implements `FlutterPlugin`, `MethodCallHandler`)
|
||||
- Stream handlers: `Ch34DataStreamHandler`, `Ch34ModemStreamHandler`, `Ch34UsbStateStreamHandler`
|
||||
- Utility: `Ch34TypeConverter` (static methods only, private constructor)
|
||||
|
||||
**MethodChannel names:**
|
||||
- Method channel: `'ch34'`
|
||||
- Data event channel: `'ch34/data'`
|
||||
- Modem event channel: `'ch34/modem'`
|
||||
- USB state event channel: `'ch34/usb_state'`
|
||||
|
||||
**Method names:** Match Dart method names exactly (e.g., `enumDevice`, `openDevice`, `setSerialParameter`, `writeData`, `readData`)
|
||||
|
||||
## Where to Add New Code
|
||||
|
||||
**New Dart API Method:**
|
||||
1. Add abstract method signature to `lib/src/ch34_platform_interface.dart`
|
||||
2. Add concrete implementation to `lib/src/ch34_method_channel.dart` (using `methodChannel.invokeMethod()`)
|
||||
3. Add static wrapper to `lib/src/ch34_manager.dart` (delegating to `Ch34Platform.instance`)
|
||||
4. Add mock implementation in `test/ch34_method_channel_test.dart` for testing
|
||||
5. Add native handler in `android/src/main/java/com/example/ch34/Ch34Plugin.java`
|
||||
|
||||
**New EventChannel (streaming):**
|
||||
1. Create new `*StreamHandler.java` in `android/src/main/java/com/example/ch34/`
|
||||
2. Register in `Ch34Plugin.onAttachedToEngine()`
|
||||
3. Add `EventChannel` and `StreamSubscription` in `lib/src/ch34_method_channel.dart`
|
||||
4. Add platform interface methods to `Ch34Platform`
|
||||
5. Add static wrappers to `Ch34Manager`
|
||||
|
||||
**New Data Type:**
|
||||
- Add to `lib/src/types/ch34_types.dart`
|
||||
- Add corresponding conversion in `android/src/main/java/com/example/ch34/Ch34TypeConverter.java`
|
||||
|
||||
**New Native Utility:**
|
||||
- Add to `android/src/main/java/com/example/ch34/` as a new Java class or extend existing
|
||||
|
||||
## Module Boundaries
|
||||
|
||||
### Dart Layer
|
||||
```
|
||||
lib/ch34.dart (exports)
|
||||
|
|
||||
v
|
||||
lib/src/ch34_manager.dart (static facade)
|
||||
|
|
||||
v
|
||||
lib/src/ch34_platform_interface.dart (abstract contract)
|
||||
|
|
||||
v
|
||||
lib/src/ch34_method_channel.dart (concrete implementation)
|
||||
|
|
||||
+---> lib/src/types/ch34_types.dart (shared types)
|
||||
```
|
||||
|
||||
### Native Layer
|
||||
```
|
||||
Ch34Plugin.java (MethodCallHandler + 3 EventChannels)
|
||||
|
|
||||
+---> Ch34DataStreamHandler.java (data streaming)
|
||||
+---> Ch34ModemStreamHandler.java (modem status streaming)
|
||||
+---> Ch34UsbStateStreamHandler.java (USB hotplug streaming)
|
||||
+---> Ch34TypeConverter.java (type conversion utilities)
|
||||
|
|
||||
v
|
||||
CH34XUARTDriver.jar (WCH vendor SDK)
|
||||
```
|
||||
|
||||
### Platform Bridge
|
||||
```
|
||||
Dart: MethodChannel('ch34') <---> Java: Ch34Plugin (MethodCallHandler)
|
||||
Dart: EventChannel('ch34/data') <---> Java: Ch34DataStreamHandler
|
||||
Dart: EventChannel('ch34/modem') <---> Java: Ch34ModemStreamHandler
|
||||
Dart: EventChannel('ch34/usb_state') <---> Java: Ch34UsbStateStreamHandler
|
||||
```
|
||||
|
||||
## Special Directories
|
||||
|
||||
**`android/libs/`:**
|
||||
- Purpose: Contains binary JAR dependency (`CH34XUARTDriver.jar`)
|
||||
- Generated: No, committed to repo
|
||||
- Referenced by: `android/build.gradle` via `flatDir` repository
|
||||
|
||||
**`example/`:**
|
||||
- Purpose: Self-contained Flutter app demonstrating plugin usage
|
||||
- Generated: No, committed to repo
|
||||
- Note: The example app package is `ch34_example`
|
||||
|
||||
**`docs/`:**
|
||||
- Purpose: API documentation reference for WCH WCHUARTManager
|
||||
- Contains: `CH34X-api_docs.md` - comprehensive API documentation in Markdown
|
||||
|
||||
**`lib/` root-level files:**
|
||||
- Purpose: Backward compatibility re-exports
|
||||
- `lib/ch34_platform_interface.dart` exports `src/ch34_platform_interface.dart`
|
||||
- `lib/ch34_method_channel.dart` exports `src/ch34_method_channel.dart`
|
||||
|
||||
---
|
||||
|
||||
*Structure analysis: 2026-04-16*
|
||||
232
.planning/codebase/TESTING.md
Normal file
232
.planning/codebase/TESTING.md
Normal file
@@ -0,0 +1,232 @@
|
||||
# Testing Patterns
|
||||
|
||||
**Analysis Date:** 2026-04-16
|
||||
|
||||
## Framework
|
||||
|
||||
**Runner:**
|
||||
- `flutter_test` (Flutter SDK built-in)
|
||||
- `integration_test` (Flutter SDK built-in, for E2E/integration tests)
|
||||
- No explicit `test` package dependency (uses Flutter's bundled test framework)
|
||||
|
||||
**Assertion Library:**
|
||||
- Built-in `flutter_test` matchers (`expect`, `findsOneWidget`, `isEmpty`, `isInstanceOf`)
|
||||
|
||||
**Dependencies:**
|
||||
- `flutter_test` (sdk: flutter) - Unit and widget testing
|
||||
- `flutter_lints` (^3.0.0) - Code analysis
|
||||
- `plugin_platform_interface` (^2.0.2) - Required for mock platform interface mixin
|
||||
|
||||
**Run Commands:**
|
||||
```bash
|
||||
flutter test # Run all unit tests
|
||||
flutter test --coverage # Run tests with coverage
|
||||
flutter test test/ch34_test.dart # Run a specific test file
|
||||
flutter test integration_test/ # Run integration tests (from example/)
|
||||
```
|
||||
|
||||
## Test Structure
|
||||
|
||||
**Test File Organization:**
|
||||
- Plugin tests in `D:/code/new_git_code/flutter/ch34/test/`:
|
||||
- `ch34_test.dart` - Unit tests with mock platform
|
||||
- `ch34_method_channel_test.dart` - MethodChannel-level tests with mock handler
|
||||
- Example app tests in `D:/code/new_git_code/flutter/ch34/example/test/`:
|
||||
- `widget_test.dart` - Widget tests for example app
|
||||
- Integration tests in `D:/code/new_git_code/flutter/ch34/example/integration_test/`:
|
||||
- `plugin_integration_test.dart` - End-to-end plugin tests on real device
|
||||
|
||||
**Naming:**
|
||||
- Test files: `{source_file}_test.dart` pattern
|
||||
- Integration test files: `{feature}_integration_test.dart`
|
||||
|
||||
**Test Entry Pattern:**
|
||||
All tests call framework initialization before running:
|
||||
```dart
|
||||
// Unit tests
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
// Integration tests
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
```
|
||||
|
||||
**Suite Organization:**
|
||||
Unit tests follow a consistent pattern:
|
||||
|
||||
```dart
|
||||
// D:/code/new_git_code/flutter/ch34/test/ch34_test.dart
|
||||
void main() {
|
||||
MethodChannelCh34.registerDefault();
|
||||
final Ch34Platform initialPlatform = Ch34Platform.instance;
|
||||
|
||||
test('$MethodChannelCh34 is the default instance', () {
|
||||
expect(initialPlatform, isInstanceOf<MethodChannelCh34>());
|
||||
});
|
||||
|
||||
test('getPlatformVersion', () async {
|
||||
MockCh34Platform fakePlatform = MockCh34Platform();
|
||||
Ch34Platform.instance = fakePlatform;
|
||||
|
||||
expect(await Ch34Manager.getPlatformVersion(), '42');
|
||||
});
|
||||
|
||||
test('enumDevice returns empty list', () async {
|
||||
MockCh34Platform fakePlatform = MockCh34Platform();
|
||||
Ch34Platform.instance = fakePlatform;
|
||||
|
||||
final devices = await Ch34Manager.enumDevice();
|
||||
expect(devices, isEmpty);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## Mocking
|
||||
|
||||
**Framework:** Manual mock classes (no mockito or mocktail dependency)
|
||||
|
||||
**Platform Mock Pattern:**
|
||||
`D:/code/new_git_code/flutter/ch34/test/ch34_test.dart` uses `MockPlatformInterfaceMixin` for creating test doubles:
|
||||
|
||||
```dart
|
||||
class MockCh34Platform
|
||||
with MockPlatformInterfaceMixin
|
||||
implements Ch34Platform {
|
||||
|
||||
@override
|
||||
Future<String?> getPlatformVersion() => Future.value('42');
|
||||
|
||||
@override
|
||||
Future<List<UsbDeviceInfo>> enumDevice() => Future.value([]);
|
||||
|
||||
// ... all 30+ methods must be overridden
|
||||
}
|
||||
```
|
||||
|
||||
**Mock Usage Pattern:**
|
||||
Mock instances are swapped into the platform interface before each test:
|
||||
```dart
|
||||
MockCh34Platform fakePlatform = MockCh34Platform();
|
||||
Ch34Platform.instance = fakePlatform;
|
||||
|
||||
expect(await Ch34Manager.getPlatformVersion(), '42');
|
||||
```
|
||||
|
||||
**MethodChannel Mock Pattern:**
|
||||
`D:/code/new_git_code/flutter/ch34/test/ch34_method_channel_test.dart` uses `setMockMethodCallHandler` for low-level channel mocking:
|
||||
|
||||
```dart
|
||||
const MethodChannel channel = MethodChannel('ch34');
|
||||
|
||||
setUp(() {
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
||||
.setMockMethodCallHandler(
|
||||
channel,
|
||||
(MethodCall methodCall) async {
|
||||
return '42'; // Returns same value for all method calls
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
||||
.setMockMethodCallHandler(channel, null);
|
||||
});
|
||||
```
|
||||
|
||||
**What to Mock:**
|
||||
- Platform interface layer (`Ch34Platform`) for testing `Ch34Manager` delegation
|
||||
- `MethodChannel` for testing `MethodChannelCh34` channel mapping
|
||||
|
||||
**What NOT to Mock:**
|
||||
- Native Android code (tested via integration tests on real device)
|
||||
- EventChannel streams (not currently mocked in any test)
|
||||
|
||||
## Coverage
|
||||
|
||||
**Requirements:** No coverage target enforced
|
||||
|
||||
**Current State:**
|
||||
- Only 2 unit test assertions beyond boilerplate (`getPlatformVersion`, `enumDevice returns empty list`)
|
||||
- MethodChannel test has only 1 assertion (`getPlatformVersion`)
|
||||
- Integration test has 1 assertion (`getPlatformVersion` on real device)
|
||||
- Most platform methods have zero test coverage (GPIO, serial I/O, modem status, signal control, etc.)
|
||||
|
||||
**View Coverage:**
|
||||
```bash
|
||||
flutter test --coverage
|
||||
# Coverage written to coverage/lcov.info
|
||||
```
|
||||
|
||||
## Test Locations
|
||||
|
||||
**Unit Tests:**
|
||||
- `D:/code/new_git_code/flutter/ch34/test/ch34_test.dart` - Ch34Manager + mock platform
|
||||
- `D:/code/new_git_code/flutter/ch34/test/ch34_method_channel_test.dart` - MethodChannel tests
|
||||
|
||||
**Integration Tests:**
|
||||
- `D:/code/new_git_code/flutter/ch34/example/integration_test/plugin_integration_test.dart` - Real device tests
|
||||
|
||||
**Widget Tests:**
|
||||
- `D:/code/new_git_code/flutter/ch34/example/test/widget_test.dart` - Example app widget test (currently outdated - looks for "Running on:" text that no longer exists)
|
||||
|
||||
## Test Types
|
||||
|
||||
**Unit Tests:**
|
||||
- Scope: Test `Ch34Manager` delegation to mocked platform
|
||||
- Approach: Replace `Ch34Platform.instance` with `MockCh34Platform`
|
||||
- Current coverage: 2 methods tested out of 30+ API methods
|
||||
|
||||
**Integration Tests:**
|
||||
- Framework: `integration_test` package
|
||||
- Approach: Run on real Android device with actual USB hardware
|
||||
- Current coverage: 1 test (`getPlatformVersion`)
|
||||
|
||||
**E2E Tests:**
|
||||
- No dedicated E2E test framework beyond `integration_test`
|
||||
|
||||
## Common Patterns
|
||||
|
||||
**Async Testing:**
|
||||
All platform method tests use `async/await`:
|
||||
```dart
|
||||
test('enumDevice returns empty list', () async {
|
||||
MockCh34Platform fakePlatform = MockCh34Platform();
|
||||
Ch34Platform.instance = fakePlatform;
|
||||
|
||||
final devices = await Ch34Manager.enumDevice();
|
||||
expect(devices, isEmpty);
|
||||
});
|
||||
```
|
||||
|
||||
**Error Testing:**
|
||||
Mock throws exceptions to test error paths:
|
||||
```dart
|
||||
@override
|
||||
Future<GpioStatus> queryGpioStatus(String deviceName, int gpioIndex) =>
|
||||
throw const Ch34Exception('Not supported');
|
||||
```
|
||||
|
||||
**Setup/Teardown Pattern:**
|
||||
```dart
|
||||
setUp(() {
|
||||
// Configure mock method channel handler
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
||||
.setMockMethodCallHandler(channel, handler);
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
// Clean up mock handler
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
||||
.setMockMethodCallHandler(channel, null);
|
||||
});
|
||||
```
|
||||
|
||||
## CI Testing
|
||||
|
||||
**CI Pipeline:** Not detected - no `.github/workflows/`, `.gitlab-ci.yml`, or `azure-pipelines.yml` found.
|
||||
|
||||
**Recommendation:** Tests should be run via `flutter analyze && flutter test` in any CI pipeline.
|
||||
|
||||
---
|
||||
|
||||
*Testing analysis: 2026-04-16*
|
||||
3
CHANGELOG.md
Normal file
3
CHANGELOG.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 0.0.1
|
||||
|
||||
* TODO: Describe initial release.
|
||||
105
CLAUDE.md
Normal file
105
CLAUDE.md
Normal file
@@ -0,0 +1,105 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## 项目概述
|
||||
|
||||
这是一个 Flutter 插件项目,为 WCH CH34X 系列 USB 转串口芯片提供 Flutter 接口支持。支持的芯片型号:CH340/CH341/CH342/CH343/CH344/CH347/CH9101/CH9102/CH9103/CH9104/CH9143。
|
||||
|
||||
## 常用命令
|
||||
|
||||
```bash
|
||||
# 安装依赖
|
||||
flutter pub get
|
||||
|
||||
# 运行所有测试
|
||||
flutter test
|
||||
|
||||
# 运行单个测试文件
|
||||
flutter test test/ch34_test.dart
|
||||
flutter test test/ch34_method_channel_test.dart
|
||||
|
||||
# 代码分析(lint)
|
||||
flutter analyze
|
||||
|
||||
# 格式化代码
|
||||
dart format .
|
||||
|
||||
# 构建示例 APK
|
||||
cd example && flutter build apk
|
||||
|
||||
# 运行示例应用
|
||||
cd example && flutter run
|
||||
```
|
||||
|
||||
## 代码架构
|
||||
|
||||
### 目录结构
|
||||
|
||||
```
|
||||
lib/
|
||||
├── ch34.dart # 主入口,导出所有公共 API
|
||||
├── ch34_method_channel.dart # 顶层导出(向后兼容)
|
||||
├── ch34_platform_interface.dart # 顶层导出(向后兼容)
|
||||
└── src/
|
||||
├── ch34_manager.dart # 公共 API 管理器(静态方法门面)
|
||||
├── ch34_platform_interface.dart # 抽象平台接口定义
|
||||
├── ch34_method_channel.dart # MethodChannel 实现 + 异常类
|
||||
└── types/
|
||||
└── ch34_types.dart # 所有类型和枚举定义
|
||||
```
|
||||
|
||||
### 核心类
|
||||
|
||||
- **`Ch34Manager`** - 公共 API 入口,所有方法为静态,委托给 `Ch34Platform.instance`
|
||||
- **`Ch34Platform`** - 抽象平台接口,定义所有方法签名,继承自 `PlatformInterface`
|
||||
- **`MethodChannelCh34`** - 平台接口的具体实现,通过 MethodChannel 和 EventChannel 与 Android 原生端通信
|
||||
- **`Ch34Exception`** - 插件自定义异常类
|
||||
|
||||
### 通信通道
|
||||
|
||||
| 通道类型 | 名称 | 用途 |
|
||||
|---------|------|------|
|
||||
| MethodChannel | `ch34` | 双向方法调用 |
|
||||
| EventChannel | `ch34/data` | 串口数据推送 |
|
||||
| EventChannel | `ch34/modem` | Modem 状态变化 |
|
||||
| EventChannel | `ch34/usb_state` | USB 插拔状态 |
|
||||
|
||||
### Android 原生端
|
||||
|
||||
```
|
||||
android/src/main/java/com/example/ch34/
|
||||
├── Ch34Plugin.java # Flutter 插件主类,注册 MethodChannel/EventChannel
|
||||
├── Ch34DataStreamHandler.java # 数据 EventChannel 的 StreamHandler
|
||||
├── Ch34ModemStreamHandler.java # Modem EventChannel 的 StreamHandler
|
||||
├── Ch34UsbStateStreamHandler.java # USB 状态 EventChannel 的 StreamHandler
|
||||
└── Ch34TypeConverter.java # Dart/Java 类型转换工具
|
||||
```
|
||||
|
||||
### 数据流
|
||||
|
||||
```
|
||||
Flutter App → Ch34Manager → Ch34Platform.instance → MethodChannelCh34 → MethodChannel → Android (Ch34Plugin)
|
||||
```
|
||||
|
||||
### 类型系统 (`ch34_types.dart`)
|
||||
|
||||
- **枚举**: `DataBits`, `StopBits`, `Parity`, `GpioDirection`, `GpioValue`, `SerialErrorType`
|
||||
- **数据类**: `GpioStatus`, `SerialParameter`, `ChipMasterFrequency`, `ModemStatus`, `UsbDeviceInfo`
|
||||
- 所有数据类提供 `fromMap`/`toMap` 方法用于平台间序列化
|
||||
|
||||
## 测试策略
|
||||
|
||||
- `test/ch34_test.dart` - 使用 Mock 平台实例测试 `Ch34Manager` 接口
|
||||
- `test/ch34_method_channel_test.dart` - 使用 BinaryMessenger 模拟测试 MethodChannel 调用
|
||||
- 测试遵循 flutter plugin 的标准测试模式
|
||||
|
||||
## 示例应用
|
||||
|
||||
`example/` 目录包含完整的示例应用,展示设备扫描、打开、数据发送/接收的基本流程。
|
||||
|
||||
## 用户约束
|
||||
|
||||
- Flutter 项目中禁止使用 `Get.snackbar`,使用 `easyloading` 代替
|
||||
- 所有方法需添加方法级注释
|
||||
- 遵循高内聚低耦合设计原则
|
||||
4
analysis_options.yaml
Normal file
4
analysis_options.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
9
android/.gitignore
vendored
Normal file
9
android/.gitignore
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/workspace.xml
|
||||
/.idea/libraries
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.cxx
|
||||
59
android/build.gradle
Normal file
59
android/build.gradle
Normal file
@@ -0,0 +1,59 @@
|
||||
group = "com.example.ch34"
|
||||
version = "1.0"
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath("com.android.tools.build:gradle:7.3.0")
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
flatDir{
|
||||
dirs project(':ch34').file('libs')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: "com.android.library"
|
||||
|
||||
android {
|
||||
if (project.android.hasProperty("namespace")) {
|
||||
namespace = "com.example.ch34"
|
||||
}
|
||||
|
||||
compileSdk = 34
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 21
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testImplementation("junit:junit:4.13.2")
|
||||
testImplementation("org.mockito:mockito-core:5.0.0")
|
||||
|
||||
implementation(name: 'CH34XUARTDriver',ext: 'jar')
|
||||
}
|
||||
|
||||
testOptions {
|
||||
unitTests.all {
|
||||
testLogging {
|
||||
events "passed", "skipped", "failed", "standardOut", "standardError"
|
||||
outputs.upToDateWhen {false}
|
||||
showStandardStreams = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
android/libs/CH34XUartDriver.jar
Normal file
BIN
android/libs/CH34XUartDriver.jar
Normal file
Binary file not shown.
1
android/settings.gradle
Normal file
1
android/settings.gradle
Normal file
@@ -0,0 +1 @@
|
||||
rootProject.name = 'ch34'
|
||||
7
android/src/main/AndroidManifest.xml
Normal file
7
android/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.example.ch34">
|
||||
|
||||
<!-- USB Host 功能声明 -->
|
||||
<uses-feature android:name="android.hardware.usb.host" android:required="false" />
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,110 @@
|
||||
package com.example.ch34;
|
||||
|
||||
import io.flutter.plugin.common.EventChannel;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
|
||||
import android.hardware.usb.UsbDevice;
|
||||
import cn.wch.uartlib.callback.IDataCallback;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 数据 EventChannel StreamHandler。
|
||||
*
|
||||
* 负责将 WCH IDataCallback 接收到的数据通过 EventChannel 发送给 Flutter 端。
|
||||
*/
|
||||
public class Ch34DataStreamHandler implements EventChannel.StreamHandler {
|
||||
|
||||
private static final String TAG = "Ch34DataStream";
|
||||
|
||||
private EventChannel.EventSink eventSink;
|
||||
private final Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||
|
||||
/** 设备名 -> WCH 回调实例 */
|
||||
private final Map<String, IDataCallback> wchCallbacks = new HashMap<>();
|
||||
|
||||
/** 数据回调接口,用于桥接 WCH 回调到 EventChannel */
|
||||
public interface DataBridge {
|
||||
void onData(int serialNumber, byte[] buffer, int length);
|
||||
}
|
||||
|
||||
/** 设备名 -> 数据桥接回调 */
|
||||
private final Map<String, DataBridge> dataBridges = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public void onListen(Object arguments, EventChannel.EventSink events) {
|
||||
this.eventSink = events;
|
||||
Log.d(TAG, "Data stream listening");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancel(Object arguments) {
|
||||
this.eventSink = null;
|
||||
Log.d(TAG, "Data stream cancelled");
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送数据到 Flutter 端。
|
||||
*
|
||||
* 会在主线程上执行 eventSink.success(),确保线程安全。
|
||||
*
|
||||
* @param data 字节数组数据。
|
||||
*/
|
||||
public void sendData(byte[] data) {
|
||||
mainHandler.post(() -> {
|
||||
EventChannel.EventSink sink = eventSink;
|
||||
if (sink != null) {
|
||||
sink.success(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册设备的数据回调。
|
||||
*
|
||||
* @param device USB 设备。
|
||||
* @param deviceName 设备名称。
|
||||
* @param serialNumber 串口号。
|
||||
* @param bridge 数据桥接回调。
|
||||
*/
|
||||
public void registerCallback(UsbDevice device, String deviceName, int serialNumber, DataBridge bridge) {
|
||||
dataBridges.put(deviceName, bridge);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取设备的 WCH 回调实例。
|
||||
*
|
||||
* @param deviceName 设备名称。
|
||||
* @return WCH IDataCallback 实例。
|
||||
*/
|
||||
public IDataCallback getWchCallback(String deviceName) {
|
||||
if (wchCallbacks.containsKey(deviceName)) {
|
||||
return wchCallbacks.get(deviceName);
|
||||
}
|
||||
|
||||
IDataCallback callback = new IDataCallback() {
|
||||
@Override
|
||||
public void onData(int serialNumber, byte[] buffer, int length) {
|
||||
DataBridge bridge = dataBridges.get(deviceName);
|
||||
if (bridge != null) {
|
||||
bridge.onData(serialNumber, buffer, length);
|
||||
}
|
||||
}
|
||||
};
|
||||
wchCallbacks.put(deviceName, callback);
|
||||
return callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除设备的数据回调。
|
||||
*
|
||||
* @param deviceName 设备名称。
|
||||
*/
|
||||
public void removeCallback(String deviceName) {
|
||||
wchCallbacks.remove(deviceName);
|
||||
dataBridges.remove(deviceName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package com.example.ch34;
|
||||
|
||||
import io.flutter.plugin.common.EventChannel;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Modem 状态 EventChannel StreamHandler。
|
||||
*
|
||||
* 负责将 Modem 状态变化通过 EventChannel 发送给 Flutter 端。
|
||||
*/
|
||||
public class Ch34ModemStreamHandler implements EventChannel.StreamHandler {
|
||||
|
||||
private static final String TAG = "Ch34ModemStream";
|
||||
|
||||
private EventChannel.EventSink eventSink;
|
||||
private final Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||
|
||||
private boolean isListening = false;
|
||||
|
||||
@Override
|
||||
public void onListen(Object arguments, EventChannel.EventSink events) {
|
||||
this.eventSink = events;
|
||||
Log.d(TAG, "Modem stream listening");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancel(Object arguments) {
|
||||
this.eventSink = null;
|
||||
this.isListening = false;
|
||||
Log.d(TAG, "Modem stream cancelled");
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始监听 Modem 状态。
|
||||
*/
|
||||
public void startListening() {
|
||||
this.isListening = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止监听 Modem 状态。
|
||||
*/
|
||||
public void stopListening() {
|
||||
this.isListening = false;
|
||||
this.eventSink = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送 Modem 状态到 Flutter 端。
|
||||
*
|
||||
* 会在主线程上执行 eventSink.success(),确保线程安全。
|
||||
*
|
||||
* @param status 状态 Map,包含 cts/dsr/ri/dcd 布尔值。
|
||||
*/
|
||||
public void sendStatus(Map<String, Object> status) {
|
||||
if (!isListening) return;
|
||||
mainHandler.post(() -> {
|
||||
EventChannel.EventSink sink = eventSink;
|
||||
if (sink != null) {
|
||||
sink.success(status);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
892
android/src/main/java/com/example/ch34/Ch34Plugin.java
Normal file
892
android/src/main/java/com/example/ch34/Ch34Plugin.java
Normal file
@@ -0,0 +1,892 @@
|
||||
package com.example.ch34;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import io.flutter.embedding.engine.plugins.FlutterPlugin;
|
||||
import io.flutter.plugin.common.EventChannel;
|
||||
import io.flutter.plugin.common.MethodCall;
|
||||
import io.flutter.plugin.common.MethodChannel;
|
||||
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
|
||||
import io.flutter.plugin.common.MethodChannel.Result;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.hardware.usb.UsbDevice;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
|
||||
import cn.wch.uartlib.WCHUARTManager;
|
||||
import cn.wch.uartlib.chip.type.ChipType2;
|
||||
import cn.wch.uartlib.callback.IDataCallback;
|
||||
import cn.wch.uartlib.callback.IModemStatus;
|
||||
import cn.wch.uartlib.exception.ChipException;
|
||||
import cn.wch.uartlib.exception.NoPermissionException;
|
||||
import cn.wch.uartlib.exception.UartLibException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* CH34X USB 转串口驱动 Flutter 插件。
|
||||
*
|
||||
* 实现 WCH WCHUARTManager 文档 4.3–4.38 共 36 个公开 API(getInstance/init 由内部自动调用)
|
||||
* 的 MethodChannel 映射,并通过 3 个 EventChannel 提供数据回调、Modem 状态和 USB 插拔事件流。
|
||||
*/
|
||||
public class Ch34Plugin implements FlutterPlugin, MethodCallHandler {
|
||||
|
||||
private static final String TAG = "Ch34Plugin";
|
||||
|
||||
private MethodChannel channel;
|
||||
private Context context;
|
||||
|
||||
private EventChannel dataEventChannel;
|
||||
private EventChannel modemEventChannel;
|
||||
private EventChannel usbStateEventChannel;
|
||||
|
||||
private Ch34DataStreamHandler dataStreamHandler;
|
||||
private Ch34ModemStreamHandler modemStreamHandler;
|
||||
private Ch34UsbStateStreamHandler usbStateStreamHandler;
|
||||
|
||||
/** 已打开的设备映射:deviceName -> UsbDevice */
|
||||
private final Map<String, UsbDevice> openedDevices = new HashMap<>();
|
||||
|
||||
/** 全局 WCHUARTManager 实例 */
|
||||
private WCHUARTManager manager;
|
||||
|
||||
@Override
|
||||
public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
|
||||
channel = new MethodChannel(binding.getBinaryMessenger(), "ch34");
|
||||
channel.setMethodCallHandler(this);
|
||||
|
||||
context = binding.getApplicationContext();
|
||||
|
||||
dataEventChannel = new EventChannel(binding.getBinaryMessenger(), "ch34/data");
|
||||
dataStreamHandler = new Ch34DataStreamHandler();
|
||||
dataEventChannel.setStreamHandler(dataStreamHandler);
|
||||
|
||||
modemEventChannel = new EventChannel(binding.getBinaryMessenger(), "ch34/modem");
|
||||
modemStreamHandler = new Ch34ModemStreamHandler();
|
||||
modemEventChannel.setStreamHandler(modemStreamHandler);
|
||||
|
||||
usbStateEventChannel = new EventChannel(binding.getBinaryMessenger(), "ch34/usb_state");
|
||||
usbStateStreamHandler = new Ch34UsbStateStreamHandler();
|
||||
usbStateEventChannel.setStreamHandler(usbStateStreamHandler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
|
||||
try {
|
||||
switch (call.method) {
|
||||
case "getPlatformVersion":
|
||||
result.success("Android " + android.os.Build.VERSION.RELEASE);
|
||||
break;
|
||||
case "enumDevice":
|
||||
enumDevice(result);
|
||||
break;
|
||||
case "getChipType":
|
||||
getChipType(call, result);
|
||||
break;
|
||||
case "openDevice":
|
||||
openDevice(call, result);
|
||||
break;
|
||||
case "requestPermission":
|
||||
requestPermission(call, result);
|
||||
break;
|
||||
case "getSerialCount":
|
||||
getSerialCount(call, result);
|
||||
break;
|
||||
case "getSerialBaud":
|
||||
getSerialBaud(call, result);
|
||||
break;
|
||||
case "getChipMasterFrequency":
|
||||
getChipMasterFrequency(call, result);
|
||||
break;
|
||||
case "enableSerial":
|
||||
enableSerial(call, result);
|
||||
break;
|
||||
case "setSerialParameter":
|
||||
setSerialParameter(call, result);
|
||||
break;
|
||||
case "writeData":
|
||||
writeData(call, result);
|
||||
break;
|
||||
case "asyncWriteData":
|
||||
asyncWriteData(call, result);
|
||||
break;
|
||||
case "readData":
|
||||
readData(call, result);
|
||||
break;
|
||||
case "readDataWithTimeout":
|
||||
readDataWithTimeout(call, result);
|
||||
break;
|
||||
case "registerDataCallback":
|
||||
registerDataCallback(call, result);
|
||||
break;
|
||||
case "removeDataCallback":
|
||||
removeDataCallback(call, result);
|
||||
break;
|
||||
case "isConnected":
|
||||
isConnected(call, result);
|
||||
break;
|
||||
case "getConnectedDevices":
|
||||
getConnectedDevices(result);
|
||||
break;
|
||||
case "disconnect":
|
||||
disconnect(call, result);
|
||||
break;
|
||||
case "close":
|
||||
close(result);
|
||||
break;
|
||||
case "isSupportGpio":
|
||||
isSupportGpio(call, result);
|
||||
break;
|
||||
case "queryGpioCount":
|
||||
queryGpioCount(call, result);
|
||||
break;
|
||||
case "queryGpioStatus":
|
||||
queryGpioStatus(call, result);
|
||||
break;
|
||||
case "queryAllGpioStatus":
|
||||
queryAllGpioStatus(call, result);
|
||||
break;
|
||||
case "enableGpio":
|
||||
enableGpio(call, result);
|
||||
break;
|
||||
case "setGpioVal":
|
||||
setGpioVal(call, result);
|
||||
break;
|
||||
case "getGpioVal":
|
||||
getGpioVal(call, result);
|
||||
break;
|
||||
case "setDtr":
|
||||
setDtr(call, result);
|
||||
break;
|
||||
case "setRts":
|
||||
setRts(call, result);
|
||||
break;
|
||||
case "setBreak":
|
||||
setBreak(call, result);
|
||||
break;
|
||||
case "registerModemStatusCallback":
|
||||
registerModemStatusCallback(call, result);
|
||||
break;
|
||||
case "removeModemStatusCallback":
|
||||
removeModemStatusCallback(call, result);
|
||||
break;
|
||||
case "querySerialErrorCount":
|
||||
querySerialErrorCount(call, result);
|
||||
break;
|
||||
case "setReadTimeout":
|
||||
setReadTimeout(call, result);
|
||||
break;
|
||||
case "addNewHardware":
|
||||
addNewHardware(call, result);
|
||||
break;
|
||||
case "setDebug":
|
||||
setDebug(call, result);
|
||||
break;
|
||||
case "isDebugMode":
|
||||
isDebugMode(result);
|
||||
break;
|
||||
default:
|
||||
result.notImplemented();
|
||||
break;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Unhandled method call: " + call.method, e);
|
||||
result.error("INTERNAL_ERROR", e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
|
||||
channel.setMethodCallHandler(null);
|
||||
closeAllDevices();
|
||||
if (dataEventChannel != null) dataEventChannel.setStreamHandler(null);
|
||||
if (modemEventChannel != null) modemEventChannel.setStreamHandler(null);
|
||||
if (usbStateEventChannel != null) usbStateEventChannel.setStreamHandler(null);
|
||||
}
|
||||
|
||||
// ==================== 设备枚举与识别 ====================
|
||||
|
||||
private void enumDevice(@NonNull Result result) {
|
||||
ensureManagerInitialized();
|
||||
try {
|
||||
ArrayList<UsbDevice> devices = manager.enumDevice();
|
||||
List<Map<String, Object>> list = new ArrayList<>();
|
||||
for (UsbDevice device : devices) {
|
||||
String deviceName = device.getDeviceName();
|
||||
int serialCount = -1;
|
||||
String chipType = null;
|
||||
try {
|
||||
serialCount = manager.getSerialCount(device);
|
||||
ChipType2 type = manager.getChipType(device);
|
||||
if (type != null) {
|
||||
chipType = type.getDescription();
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("deviceName", deviceName);
|
||||
map.put("productId", device.getProductId());
|
||||
map.put("vendorId", device.getVendorId());
|
||||
map.put("serialCount", serialCount);
|
||||
map.put("chipType", chipType);
|
||||
list.add(map);
|
||||
}
|
||||
result.success(list);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "enumDevice error", e);
|
||||
result.error("ENUM_DEVICE_FAILED", e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
private void getChipType(@NonNull MethodCall call, @NonNull Result result) {
|
||||
ensureManagerInitialized();
|
||||
try {
|
||||
String deviceName = call.argument("deviceName");
|
||||
UsbDevice device = getDeviceOrThrow(deviceName);
|
||||
ChipType2 type = manager.getChipType(device);
|
||||
if (type != null) {
|
||||
result.success(type.getDescription());
|
||||
} else {
|
||||
result.success(null);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "getChipType error", e);
|
||||
result.error("GET_CHIP_TYPE_FAILED", e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 设备打开与权限 ====================
|
||||
|
||||
private void openDevice(@NonNull MethodCall call, @NonNull Result result) {
|
||||
ensureManagerInitialized();
|
||||
try {
|
||||
String deviceName = call.argument("deviceName");
|
||||
UsbDevice device = getDeviceOrThrow(deviceName);
|
||||
|
||||
if (openedDevices.containsKey(deviceName)) {
|
||||
result.success(true);
|
||||
return;
|
||||
}
|
||||
|
||||
boolean opened = tryOpenDevice(device);
|
||||
if (!opened) {
|
||||
manager.requestPermission(context, device);
|
||||
new Handler(Looper.getMainLooper()).postDelayed(() -> {
|
||||
try {
|
||||
boolean retryResult = tryOpenDevice(device);
|
||||
if (retryResult) {
|
||||
openedDevices.put(deviceName, device);
|
||||
usbStateStreamHandler.notifyStateChanged(deviceName, true);
|
||||
result.success(true);
|
||||
} else {
|
||||
result.error("PERMISSION_DENIED", "USB permission not granted", null);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
result.error("OPEN_DEVICE_FAILED", e.getMessage(), null);
|
||||
}
|
||||
}, 2000);
|
||||
return;
|
||||
}
|
||||
|
||||
openedDevices.put(deviceName, device);
|
||||
usbStateStreamHandler.notifyStateChanged(deviceName, true);
|
||||
result.success(true);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "openDevice error", e);
|
||||
result.error("OPEN_DEVICE_FAILED", e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean tryOpenDevice(@NonNull UsbDevice device) {
|
||||
try {
|
||||
manager.openDevice(device);
|
||||
return true;
|
||||
} catch (NoPermissionException e) {
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "tryOpenDevice non-permission error", e);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private void requestPermission(@NonNull MethodCall call, @NonNull Result result) {
|
||||
ensureManagerInitialized();
|
||||
try {
|
||||
String deviceName = call.argument("deviceName");
|
||||
UsbDevice device = getDeviceOrThrow(deviceName);
|
||||
manager.requestPermission(context, device);
|
||||
result.success(true);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "requestPermission error", e);
|
||||
result.error("REQUEST_PERMISSION_FAILED", e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 串口信息 ====================
|
||||
|
||||
private void getSerialCount(@NonNull MethodCall call, @NonNull Result result) {
|
||||
ensureManagerInitialized();
|
||||
try {
|
||||
String deviceName = call.argument("deviceName");
|
||||
UsbDevice device = getDeviceOrThrow(deviceName);
|
||||
result.success(manager.getSerialCount(device));
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "getSerialCount error", e);
|
||||
result.error("GET_SERIAL_COUNT_FAILED", e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
private void getSerialBaud(@NonNull MethodCall call, @NonNull Result result) {
|
||||
ensureManagerInitialized();
|
||||
try {
|
||||
String deviceName = call.argument("deviceName");
|
||||
UsbDevice device = getDeviceOrThrow(deviceName);
|
||||
int serialNumber = call.argument("serialNumber");
|
||||
result.success(manager.getSerialBaud(device, serialNumber));
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "getSerialBaud error", e);
|
||||
result.error("GET_SERIAL_BAUD_FAILED", e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
private void getChipMasterFrequency(@NonNull MethodCall call, @NonNull Result result) {
|
||||
ensureManagerInitialized();
|
||||
try {
|
||||
String deviceName = call.argument("deviceName");
|
||||
UsbDevice device = getDeviceOrThrow(deviceName);
|
||||
cn.wch.uartlib.base.other.ChipMasterFrequency freq =
|
||||
manager.getChipMasterFrequency(device);
|
||||
if (freq != null) {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("frequency", freq.getFrequency());
|
||||
map.put("switchEnable", freq.isSwitchEnable());
|
||||
map.put("coStatus", freq.getCoStatus());
|
||||
result.success(map);
|
||||
} else {
|
||||
result.error("GET_CHIP_FREQ_FAILED", "Failed to get chip master frequency", null);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "getChipMasterFrequency error", e);
|
||||
result.error("GET_CHIP_FREQ_FAILED", e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
private void enableSerial(@NonNull MethodCall call, @NonNull Result result) {
|
||||
ensureManagerInitialized();
|
||||
try {
|
||||
String deviceName = call.argument("deviceName");
|
||||
UsbDevice device = getDeviceOrThrow(deviceName);
|
||||
int serialNumber = call.argument("serialNumber");
|
||||
boolean enable = call.argument("enable");
|
||||
result.success(manager.enableSerial(device, serialNumber, enable));
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "enableSerial error", e);
|
||||
result.error("ENABLE_SERIAL_FAILED", e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 串口参数设置 ====================
|
||||
|
||||
private void setSerialParameter(@NonNull MethodCall call, @NonNull Result result) {
|
||||
ensureManagerInitialized();
|
||||
try {
|
||||
String deviceName = call.argument("deviceName");
|
||||
UsbDevice device = getDeviceOrThrow(deviceName);
|
||||
int serialNumber = call.argument("serialNumber");
|
||||
int baud = call.argument("baud");
|
||||
int dataBits = call.argument("dataBits");
|
||||
int stopBits = call.argument("stopBits");
|
||||
int parity = call.argument("parity");
|
||||
boolean flow = call.argument("hardwareFlowControl");
|
||||
|
||||
boolean success = manager.setSerialParameter(
|
||||
device, serialNumber, baud, dataBits, stopBits, parity, flow);
|
||||
result.success(success);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "setSerialParameter error", e);
|
||||
result.error("SET_SERIAL_PARAM_FAILED", e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 数据读写 ====================
|
||||
|
||||
private void writeData(@NonNull MethodCall call, @NonNull Result result) {
|
||||
ensureManagerInitialized();
|
||||
try {
|
||||
String deviceName = call.argument("deviceName");
|
||||
UsbDevice device = getDeviceOrThrow(deviceName);
|
||||
int serialNumber = call.argument("serialNumber");
|
||||
byte[] data = call.argument("data");
|
||||
int length = data != null ? data.length : 0;
|
||||
int timeout = call.argument("timeout");
|
||||
|
||||
int written = manager.syncWriteData(device, serialNumber, data, length, timeout);
|
||||
result.success(written);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "writeData error", e);
|
||||
result.error("WRITE_DATA_FAILED", e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
private void asyncWriteData(@NonNull MethodCall call, @NonNull Result result) {
|
||||
ensureManagerInitialized();
|
||||
try {
|
||||
String deviceName = call.argument("deviceName");
|
||||
UsbDevice device = getDeviceOrThrow(deviceName);
|
||||
int serialNumber = call.argument("serialNumber");
|
||||
byte[] data = call.argument("data");
|
||||
|
||||
manager.asyncWriteData(device, serialNumber, data);
|
||||
result.success(null);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "asyncWriteData error", e);
|
||||
result.error("ASYNC_WRITE_FAILED", e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
private void readData(@NonNull MethodCall call, @NonNull Result result) {
|
||||
ensureManagerInitialized();
|
||||
try {
|
||||
String deviceName = call.argument("deviceName");
|
||||
UsbDevice device = getDeviceOrThrow(deviceName);
|
||||
int serialNumber = call.argument("serialNumber");
|
||||
|
||||
byte[] data = manager.readData(device, serialNumber);
|
||||
result.success(data);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "readData error", e);
|
||||
result.error("READ_DATA_FAILED", e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
private void readDataWithTimeout(@NonNull MethodCall call, @NonNull Result result) {
|
||||
ensureManagerInitialized();
|
||||
try {
|
||||
String deviceName = call.argument("deviceName");
|
||||
UsbDevice device = getDeviceOrThrow(deviceName);
|
||||
int serialNumber = call.argument("serialNumber");
|
||||
int vTime = call.argument("vTime");
|
||||
int vMin = call.argument("vMin");
|
||||
|
||||
byte[] data = manager.readData(device, serialNumber, vTime, vMin);
|
||||
result.success(data);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "readDataWithTimeout error", e);
|
||||
result.error("READ_DATA_TIMEOUT_FAILED", e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
private void registerDataCallback(@NonNull MethodCall call, @NonNull Result result) {
|
||||
ensureManagerInitialized();
|
||||
try {
|
||||
String deviceName = call.argument("deviceName");
|
||||
UsbDevice device = getDeviceOrThrow(deviceName);
|
||||
int serialNumber = call.argument("serialNumber");
|
||||
|
||||
dataStreamHandler.registerCallback(device, deviceName, serialNumber, (serial, buffer, length) -> {
|
||||
byte[] data = new byte[length];
|
||||
System.arraycopy(buffer, 0, data, 0, length);
|
||||
dataStreamHandler.sendData(data);
|
||||
});
|
||||
|
||||
manager.registerDataCallback(device, dataStreamHandler.getWchCallback(deviceName));
|
||||
result.success(null);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "registerDataCallback error", e);
|
||||
result.error("REGISTER_CALLBACK_FAILED", e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
private void removeDataCallback(@NonNull MethodCall call, @NonNull Result result) {
|
||||
ensureManagerInitialized();
|
||||
try {
|
||||
String deviceName = call.argument("deviceName");
|
||||
UsbDevice device = getDeviceOrThrow(deviceName);
|
||||
|
||||
dataStreamHandler.removeCallback(deviceName);
|
||||
manager.removeDataCallback(device);
|
||||
result.success(null);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "removeDataCallback error", e);
|
||||
result.error("REMOVE_CALLBACK_FAILED", e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 连接状态 ====================
|
||||
|
||||
private void isConnected(@NonNull MethodCall call, @NonNull Result result) {
|
||||
try {
|
||||
String deviceName = call.argument("deviceName");
|
||||
UsbDevice device = openedDevices.get(deviceName);
|
||||
result.success(device != null);
|
||||
} catch (Exception e) {
|
||||
result.success(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void getConnectedDevices(@NonNull Result result) {
|
||||
result.success(new ArrayList<>(openedDevices.keySet()));
|
||||
}
|
||||
|
||||
// ==================== 断开与关闭 ====================
|
||||
|
||||
private void disconnect(@NonNull MethodCall call, @NonNull Result result) {
|
||||
ensureManagerInitialized();
|
||||
try {
|
||||
String deviceName = call.argument("deviceName");
|
||||
UsbDevice device = openedDevices.get(deviceName);
|
||||
if (device != null) {
|
||||
manager.disconnect(device);
|
||||
openedDevices.remove(deviceName);
|
||||
usbStateStreamHandler.notifyStateChanged(deviceName, false);
|
||||
}
|
||||
result.success(null);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "disconnect error", e);
|
||||
result.error("DISCONNECT_FAILED", e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
private void close(@NonNull Result result) {
|
||||
ensureManagerInitialized();
|
||||
try {
|
||||
closeAllDevices();
|
||||
if (context instanceof Application) {
|
||||
manager.close((Application) context);
|
||||
}
|
||||
result.success(null);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "close error", e);
|
||||
result.error("CLOSE_FAILED", e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
private void closeAllDevices() {
|
||||
ensureManagerInitialized();
|
||||
for (Map.Entry<String, UsbDevice> entry : openedDevices.entrySet()) {
|
||||
try {
|
||||
manager.disconnect(entry.getValue());
|
||||
usbStateStreamHandler.notifyStateChanged(entry.getKey(), false);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "disconnect error during closeAll", e);
|
||||
}
|
||||
}
|
||||
openedDevices.clear();
|
||||
}
|
||||
|
||||
// ==================== GPIO 功能 ====================
|
||||
|
||||
private void isSupportGpio(@NonNull MethodCall call, @NonNull Result result) {
|
||||
ensureManagerInitialized();
|
||||
try {
|
||||
String deviceName = call.argument("deviceName");
|
||||
UsbDevice device = getDeviceOrThrow(deviceName);
|
||||
result.success(manager.isSupportGPIOFeature(device));
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "isSupportGpio error", e);
|
||||
result.error("GPIO_CHECK_FAILED", e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
private void queryGpioCount(@NonNull MethodCall call, @NonNull Result result) {
|
||||
ensureManagerInitialized();
|
||||
try {
|
||||
String deviceName = call.argument("deviceName");
|
||||
UsbDevice device = getDeviceOrThrow(deviceName);
|
||||
result.success(manager.queryGPIOCount(device));
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "queryGpioCount error", e);
|
||||
result.error("QUERY_GPIO_COUNT_FAILED", e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
private void queryGpioStatus(@NonNull MethodCall call, @NonNull Result result) {
|
||||
ensureManagerInitialized();
|
||||
try {
|
||||
String deviceName = call.argument("deviceName");
|
||||
UsbDevice device = getDeviceOrThrow(deviceName);
|
||||
int gpioIndex = call.argument("gpioIndex");
|
||||
|
||||
cn.wch.uartlib.base.gpio.GPIO_Status status = manager.queryGPIOStatus(device, gpioIndex);
|
||||
result.success(Ch34TypeConverter.gpioStatusToMap(status, gpioIndex));
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "queryGpioStatus error", e);
|
||||
result.error("QUERY_GPIO_STATUS_FAILED", e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
private void queryAllGpioStatus(@NonNull MethodCall call, @NonNull Result result) {
|
||||
ensureManagerInitialized();
|
||||
try {
|
||||
String deviceName = call.argument("deviceName");
|
||||
UsbDevice device = getDeviceOrThrow(deviceName);
|
||||
|
||||
List<cn.wch.uartlib.base.gpio.GPIO_Status> statuses = manager.queryAllGPIOStatus(device);
|
||||
List<Map<String, Object>> list = new ArrayList<>();
|
||||
for (int i = 0; i < statuses.size(); i++) {
|
||||
list.add(Ch34TypeConverter.gpioStatusToMap(statuses.get(i), i));
|
||||
}
|
||||
result.success(list);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "queryAllGpioStatus error", e);
|
||||
result.error("QUERY_ALL_GPIO_FAILED", e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
private void enableGpio(@NonNull MethodCall call, @NonNull Result result) {
|
||||
ensureManagerInitialized();
|
||||
try {
|
||||
String deviceName = call.argument("deviceName");
|
||||
UsbDevice device = getDeviceOrThrow(deviceName);
|
||||
int gpioIndex = call.argument("gpioIndex");
|
||||
boolean enable = call.argument("enable");
|
||||
int directionIndex = call.argument("direction");
|
||||
|
||||
cn.wch.uartlib.base.gpio.GPIO_DIR dir = Ch34TypeConverter.toGpioDirection(directionIndex);
|
||||
boolean success = manager.enableGPIO(device, gpioIndex, enable, dir);
|
||||
result.success(success);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "enableGpio error", e);
|
||||
result.error("ENABLE_GPIO_FAILED", e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
private void setGpioVal(@NonNull MethodCall call, @NonNull Result result) {
|
||||
ensureManagerInitialized();
|
||||
try {
|
||||
String deviceName = call.argument("deviceName");
|
||||
UsbDevice device = getDeviceOrThrow(deviceName);
|
||||
int gpioIndex = call.argument("gpioIndex");
|
||||
int valueIndex = call.argument("value");
|
||||
|
||||
cn.wch.uartlib.base.gpio.GPIO_VALUE value = Ch34TypeConverter.toGpioValue(valueIndex);
|
||||
boolean success = manager.setGPIOVal(device, gpioIndex, value);
|
||||
result.success(success);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "setGpioVal error", e);
|
||||
result.error("SET_GPIO_VAL_FAILED", e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
private void getGpioVal(@NonNull MethodCall call, @NonNull Result result) {
|
||||
ensureManagerInitialized();
|
||||
try {
|
||||
String deviceName = call.argument("deviceName");
|
||||
UsbDevice device = getDeviceOrThrow(deviceName);
|
||||
int gpioIndex = call.argument("gpioIndex");
|
||||
|
||||
cn.wch.uartlib.base.gpio.GPIO_VALUE value = manager.getGPIOVal(device, gpioIndex);
|
||||
result.success(value == cn.wch.uartlib.base.gpio.GPIO_VALUE.HIGH ? 1 : 0);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "getGpioVal error", e);
|
||||
result.error("GET_GPIO_VAL_FAILED", e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 信号控制 ====================
|
||||
|
||||
private void setDtr(@NonNull MethodCall call, @NonNull Result result) {
|
||||
ensureManagerInitialized();
|
||||
try {
|
||||
String deviceName = call.argument("deviceName");
|
||||
UsbDevice device = getDeviceOrThrow(deviceName);
|
||||
int serialNumber = call.argument("serialNumber");
|
||||
boolean valid = call.argument("valid");
|
||||
|
||||
boolean success = manager.setDTR(device, serialNumber, valid);
|
||||
result.success(success);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "setDtr error", e);
|
||||
result.error("SET_DTR_FAILED", e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
private void setRts(@NonNull MethodCall call, @NonNull Result result) {
|
||||
ensureManagerInitialized();
|
||||
try {
|
||||
String deviceName = call.argument("deviceName");
|
||||
UsbDevice device = getDeviceOrThrow(deviceName);
|
||||
int serialNumber = call.argument("serialNumber");
|
||||
boolean valid = call.argument("valid");
|
||||
|
||||
boolean success = manager.setRTS(device, serialNumber, valid);
|
||||
result.success(success);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "setRts error", e);
|
||||
result.error("SET_RTS_FAILED", e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
private void setBreak(@NonNull MethodCall call, @NonNull Result result) {
|
||||
ensureManagerInitialized();
|
||||
try {
|
||||
String deviceName = call.argument("deviceName");
|
||||
UsbDevice device = getDeviceOrThrow(deviceName);
|
||||
int serialNumber = call.argument("serialNumber");
|
||||
boolean valid = call.argument("valid");
|
||||
|
||||
boolean success = manager.setBreak(device, serialNumber, valid);
|
||||
result.success(success);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "setBreak error", e);
|
||||
result.error("SET_BREAK_FAILED", e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== Modem 状态回调 ====================
|
||||
|
||||
private void registerModemStatusCallback(@NonNull MethodCall call, @NonNull Result result) {
|
||||
ensureManagerInitialized();
|
||||
try {
|
||||
String deviceName = call.argument("deviceName");
|
||||
UsbDevice device = getDeviceOrThrow(deviceName);
|
||||
|
||||
modemStreamHandler.startListening();
|
||||
manager.registerModemStatusCallback(device, new IModemStatus() {
|
||||
@Override
|
||||
public void onStatusChanged(int serialNumber, boolean isDCDRaised,
|
||||
boolean isDSRRaised, boolean isCTSRaised, boolean isRINGRaised) {
|
||||
Log.d(TAG, "Modem onStatusChanged: serial=" + serialNumber
|
||||
+ " dcd=" + isDCDRaised + " dsr=" + isDSRRaised
|
||||
+ " cts=" + isCTSRaised + " ri=" + isRINGRaised);
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("dcd", isDCDRaised);
|
||||
map.put("dsr", isDSRRaised);
|
||||
map.put("cts", isCTSRaised);
|
||||
map.put("ri", isRINGRaised);
|
||||
Log.d(TAG, "Sending modem status to Flutter: " + map);
|
||||
modemStreamHandler.sendStatus(map);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOverrunError(int serialNumber) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onParityError(int serialNumber) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFrameError(int serialNumber) {
|
||||
}
|
||||
});
|
||||
result.success(null);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "registerModemStatusCallback error", e);
|
||||
result.error("REGISTER_MODEM_CALLBACK_FAILED", e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
private void removeModemStatusCallback(@NonNull MethodCall call, @NonNull Result result) {
|
||||
try {
|
||||
modemStreamHandler.stopListening();
|
||||
result.success(null);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "removeModemStatusCallback error", e);
|
||||
result.error("REMOVE_MODEM_CALLBACK_FAILED", e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 错误查询 ====================
|
||||
|
||||
private void querySerialErrorCount(@NonNull MethodCall call, @NonNull Result result) {
|
||||
ensureManagerInitialized();
|
||||
try {
|
||||
String deviceName = call.argument("deviceName");
|
||||
UsbDevice device = getDeviceOrThrow(deviceName);
|
||||
int serialNumber = call.argument("serialNumber");
|
||||
String errorType = call.argument("errorType");
|
||||
|
||||
cn.wch.uartlib.base.error.SerialErrorType nativeType =
|
||||
Ch34TypeConverter.toSerialErrorType(errorType);
|
||||
int count = manager.querySerialErrorCount(device, serialNumber, nativeType);
|
||||
result.success(count);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "querySerialErrorCount error", e);
|
||||
result.error("QUERY_ERROR_COUNT_FAILED", e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 全局配置 ====================
|
||||
|
||||
private void setReadTimeout(@NonNull MethodCall call, @NonNull Result result) {
|
||||
try {
|
||||
int timeout = call.argument("timeout");
|
||||
WCHUARTManager.setReadTimeout(timeout);
|
||||
result.success(null);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "setReadTimeout error", e);
|
||||
result.error("SET_TIMEOUT_FAILED", e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
private void addNewHardware(@NonNull MethodCall call, @NonNull Result result) {
|
||||
try {
|
||||
int vid = call.argument("vid");
|
||||
int pid = call.argument("pid");
|
||||
String chipType = call.argument("chipType");
|
||||
cn.wch.uartlib.chip.type.ChipType2 type =
|
||||
Ch34TypeConverter.toChipType(chipType);
|
||||
WCHUARTManager.addNewHardwareAndChipType(vid, pid, type);
|
||||
result.success(null);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "addNewHardware error", e);
|
||||
result.error("ADD_HARDWARE_FAILED", e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
private void setDebug(@NonNull MethodCall call, @NonNull Result result) {
|
||||
try {
|
||||
boolean enabled = call.argument("enabled");
|
||||
WCHUARTManager.setDebug(enabled);
|
||||
result.success(null);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "setDebug error", e);
|
||||
result.error("SET_DEBUG_FAILED", e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
private void isDebugMode(@NonNull Result result) {
|
||||
result.success(WCHUARTManager.isDebugMode());
|
||||
}
|
||||
|
||||
// ==================== 辅助方法 ====================
|
||||
|
||||
private void ensureManagerInitialized() {
|
||||
if (manager == null) {
|
||||
Application app;
|
||||
if (context instanceof Application) {
|
||||
app = (Application) context;
|
||||
} else {
|
||||
app = (Application) context.getApplicationContext();
|
||||
}
|
||||
manager = WCHUARTManager.getInstance();
|
||||
manager.init(app);
|
||||
usbStateStreamHandler.setManager(manager);
|
||||
usbStateStreamHandler.setContext(context);
|
||||
}
|
||||
}
|
||||
|
||||
private UsbDevice getDeviceOrThrow(String deviceName) {
|
||||
UsbDevice device = openedDevices.get(deviceName);
|
||||
if (device == null) {
|
||||
try {
|
||||
ArrayList<UsbDevice> devices = manager.enumDevice();
|
||||
for (UsbDevice d : devices) {
|
||||
if (d.getDeviceName().equals(deviceName)) {
|
||||
return d;
|
||||
}
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
throw new IllegalStateException("Device not found: " + deviceName);
|
||||
}
|
||||
return device;
|
||||
}
|
||||
}
|
||||
134
android/src/main/java/com/example/ch34/Ch34TypeConverter.java
Normal file
134
android/src/main/java/com/example/ch34/Ch34TypeConverter.java
Normal file
@@ -0,0 +1,134 @@
|
||||
package com.example.ch34;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 类型转换工具类。
|
||||
*
|
||||
* 在 Dart 枚举值和 Java/WCH 类型之间进行转换。
|
||||
*/
|
||||
public class Ch34TypeConverter {
|
||||
|
||||
private Ch34TypeConverter() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 GPIO 状态对象转换为 Map。
|
||||
*
|
||||
* @param status WCH GPIO_Status 对象。
|
||||
* @param gpioIndex GPIO 编号。
|
||||
* @return 包含 index/direction/value/enabled 的 Map。
|
||||
*/
|
||||
public static Map<String, Object> gpioStatusToMap(
|
||||
cn.wch.uartlib.base.gpio.GPIO_Status status, int gpioIndex) {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
if (status != null) {
|
||||
map.put("index", gpioIndex);
|
||||
map.put("enabled", status.isEnabled());
|
||||
map.put("direction", status.getDir() == cn.wch.uartlib.base.gpio.GPIO_DIR.OUT ? 1 : 0);
|
||||
map.put("value", status.getValue() == cn.wch.uartlib.base.gpio.GPIO_VALUE.HIGH ? 1 : 0);
|
||||
} else {
|
||||
map.put("index", gpioIndex);
|
||||
map.put("enabled", false);
|
||||
map.put("direction", 0);
|
||||
map.put("value", 0);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 Dart 端的方向索引转换为 WCH GPIO_DIR 枚举。
|
||||
*
|
||||
* @param index 0 = IN, 1 = OUT。
|
||||
* @return WCH GPIO_DIR 枚举。
|
||||
*/
|
||||
public static cn.wch.uartlib.base.gpio.GPIO_DIR toGpioDirection(int index) {
|
||||
if (index == 1) {
|
||||
return cn.wch.uartlib.base.gpio.GPIO_DIR.OUT;
|
||||
}
|
||||
return cn.wch.uartlib.base.gpio.GPIO_DIR.IN;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 Dart 端的值索引转换为 WCH GPIO_VALUE 枚举。
|
||||
*
|
||||
* @param index 0 = LOW, 1 = HIGH。
|
||||
* @return WCH GPIO_VALUE 枚举。
|
||||
*/
|
||||
public static cn.wch.uartlib.base.gpio.GPIO_VALUE toGpioValue(int index) {
|
||||
if (index == 1) {
|
||||
return cn.wch.uartlib.base.gpio.GPIO_VALUE.HIGH;
|
||||
}
|
||||
return cn.wch.uartlib.base.gpio.GPIO_VALUE.LOW;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将芯片类型字符串转换为 WCH ChipType2 枚举。
|
||||
*
|
||||
* @param chipType 芯片类型描述字符串(如 "CH340"、"CH9102")。
|
||||
* @return WCH ChipType2 枚举。
|
||||
* @throws IllegalArgumentException 当 chipType 为 null 或未知类型时抛出。
|
||||
*/
|
||||
public static cn.wch.uartlib.chip.type.ChipType2 toChipType(String chipType) {
|
||||
if (chipType == null) {
|
||||
throw new IllegalArgumentException("chipType is null");
|
||||
}
|
||||
switch (chipType) {
|
||||
case "CH340":
|
||||
case "CH341":
|
||||
return cn.wch.uartlib.chip.type.ChipType2.CHIP_CH341;
|
||||
case "CH342":
|
||||
return cn.wch.uartlib.chip.type.ChipType2.CHIP_CH342F;
|
||||
case "CH343":
|
||||
return cn.wch.uartlib.chip.type.ChipType2.CHIP_CH343GP;
|
||||
case "CH344":
|
||||
return cn.wch.uartlib.chip.type.ChipType2.CHIP_CH344L;
|
||||
case "CH347":
|
||||
return cn.wch.uartlib.chip.type.ChipType2.CHIP_CH347TF;
|
||||
case "CH9101":
|
||||
return cn.wch.uartlib.chip.type.ChipType2.CHIP_CH9101U;
|
||||
case "CH9102":
|
||||
return cn.wch.uartlib.chip.type.ChipType2.CHIP_CH9102F;
|
||||
case "CH9103":
|
||||
return cn.wch.uartlib.chip.type.ChipType2.CHIP_CH9103M;
|
||||
case "CH9104":
|
||||
return cn.wch.uartlib.chip.type.ChipType2.CHIP_CH9104L;
|
||||
case "CH9143":
|
||||
return cn.wch.uartlib.chip.type.ChipType2.CHIP_CH9143;
|
||||
case "CH9111":
|
||||
return cn.wch.uartlib.chip.type.ChipType2.CHIP_CH9111L_MODE0;
|
||||
case "CH9114":
|
||||
return cn.wch.uartlib.chip.type.ChipType2.CHIP_CH9114L;
|
||||
case "CH339":
|
||||
return cn.wch.uartlib.chip.type.ChipType2.CHIP_CH339W;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown chipType: " + chipType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 Dart 端的错误类型字符串转换为 WCH SerialErrorType 枚举。
|
||||
*
|
||||
* WCH 原生库仅支持 FRAME/PARITY/OVERRUN 三种错误类型。
|
||||
*
|
||||
* @param errorType 错误类型字符串。
|
||||
* @return WCH SerialErrorType 枚举。
|
||||
* @throws IllegalArgumentException 传入的错误类型字符串无法识别时抛出。
|
||||
*/
|
||||
public static cn.wch.uartlib.base.error.SerialErrorType toSerialErrorType(String errorType) {
|
||||
if (errorType == null) {
|
||||
throw new IllegalArgumentException("errorType is null");
|
||||
}
|
||||
switch (errorType) {
|
||||
case "SerialErrorType.FramingError":
|
||||
return cn.wch.uartlib.base.error.SerialErrorType.FRAME;
|
||||
case "SerialErrorType.ParityError":
|
||||
return cn.wch.uartlib.base.error.SerialErrorType.PARITY;
|
||||
case "SerialErrorType.OverrunError":
|
||||
return cn.wch.uartlib.base.error.SerialErrorType.OVERRUN;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown SerialErrorType: " + errorType);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package com.example.ch34;
|
||||
|
||||
import io.flutter.plugin.common.EventChannel;
|
||||
import android.content.Context;
|
||||
import android.hardware.usb.UsbDevice;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
|
||||
import cn.wch.uartlib.WCHUARTManager;
|
||||
import cn.wch.uartlib.callback.IUsbStateChange;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* USB 插拔状态 EventChannel StreamHandler。
|
||||
*
|
||||
* 基于 WCH 库的 {@link IUsbStateChange} 回调接收设备插入/拔出/权限变更事件,
|
||||
* 并通过 EventChannel 通知 Flutter 端。
|
||||
*/
|
||||
public class Ch34UsbStateStreamHandler implements EventChannel.StreamHandler {
|
||||
|
||||
private static final String TAG = "Ch34UsbStateStream";
|
||||
|
||||
private EventChannel.EventSink eventSink;
|
||||
private Context context;
|
||||
private WCHUARTManager manager;
|
||||
private final Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||
|
||||
/** WCH 库 USB 状态回调,监听设备插拔与权限变化 */
|
||||
private final IUsbStateChange wchUsbStateListener = new IUsbStateChange() {
|
||||
@Override
|
||||
public void usbDeviceAttach(UsbDevice device) {
|
||||
if (device != null) {
|
||||
notifyStateChanged(device.getDeviceName(), true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void usbDeviceDetach(UsbDevice device) {
|
||||
if (device != null) {
|
||||
notifyStateChanged(device.getDeviceName(), false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void usbDevicePermission(UsbDevice device, boolean granted) {
|
||||
Log.d(TAG, "USB permission changed: "
|
||||
+ (device != null ? device.getDeviceName() : "null")
|
||||
+ ", granted=" + granted);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 设置 WCH 管理器。
|
||||
*
|
||||
* 在管理器可用后自动注册 USB 状态监听器。
|
||||
*
|
||||
* @param manager WCHUARTManager 实例。
|
||||
*/
|
||||
public void setManager(WCHUARTManager manager) {
|
||||
this.manager = manager;
|
||||
if (manager != null) {
|
||||
try {
|
||||
manager.setUsbStateListener(wchUsbStateListener);
|
||||
Log.d(TAG, "WCH USB state listener registered");
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Failed to register WCH USB state listener", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置上下文。
|
||||
*
|
||||
* @param context Android Context。
|
||||
*/
|
||||
public void setContext(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onListen(Object arguments, EventChannel.EventSink events) {
|
||||
this.eventSink = events;
|
||||
Log.d(TAG, "USB state stream listening");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancel(Object arguments) {
|
||||
this.eventSink = null;
|
||||
Log.d(TAG, "USB state stream cancelled");
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知设备状态变化。
|
||||
*
|
||||
* 会在主线程上执行 eventSink.success(),确保线程安全。
|
||||
*
|
||||
* @param deviceName 设备名称。
|
||||
* @param connected `true` 已连接,`false` 已断开。
|
||||
*/
|
||||
public void notifyStateChanged(String deviceName, boolean connected) {
|
||||
mainHandler.post(() -> {
|
||||
EventChannel.EventSink sink = eventSink;
|
||||
if (sink != null) {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("deviceName", deviceName);
|
||||
map.put("connected", connected);
|
||||
sink.success(map);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
29
android/src/test/java/com/example/ch34/Ch34PluginTest.java
Normal file
29
android/src/test/java/com/example/ch34/Ch34PluginTest.java
Normal file
@@ -0,0 +1,29 @@
|
||||
package com.example.ch34;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import io.flutter.plugin.common.MethodCall;
|
||||
import io.flutter.plugin.common.MethodChannel;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* This demonstrates a simple unit test of the Java portion of this plugin's implementation.
|
||||
*
|
||||
* Once you have built the plugin's example app, you can run these tests from the command
|
||||
* line by running `./gradlew testDebugUnitTest` in the `example/android/` directory, or
|
||||
* you can run them directly from IDEs that support JUnit such as Android Studio.
|
||||
*/
|
||||
|
||||
public class Ch34PluginTest {
|
||||
@Test
|
||||
public void onMethodCall_getPlatformVersion_returnsExpectedValue() {
|
||||
Ch34Plugin plugin = new Ch34Plugin();
|
||||
|
||||
final MethodCall call = new MethodCall("getPlatformVersion", null);
|
||||
MethodChannel.Result mockResult = mock(MethodChannel.Result.class);
|
||||
plugin.onMethodCall(call, mockResult);
|
||||
|
||||
verify(mockResult).success("Android " + android.os.Build.VERSION.RELEASE);
|
||||
}
|
||||
}
|
||||
703
docs/CH34X-api_docs.md
Normal file
703
docs/CH34X-api_docs.md
Normal file
@@ -0,0 +1,703 @@
|
||||
# CH34X 系列芯片串口 Android 程序开发说明
|
||||
|
||||
**版本:1.8**
|
||||
|
||||
https://wch.cn
|
||||
|
||||
---
|
||||
|
||||
## 一、简介
|
||||
|
||||
本文档适用于 CH339 / CH340 / CH341 / CH342 / CH343 / CH344 / CH347 / CH9101 / CH9102 / CH9103 / CH9104 / CH9143 / CH9111 / CH9114 的 USB 转串口芯片开发说明。
|
||||
|
||||
本文档其余部分均以 CH340 系列 USB 转异步串口功能(以下简称 CH34XUART)以及 GPIO 功能,以及 Android 4.4 和更新版本的系统进行说明。在功能基于 USB Host 实现连接,用户需调用相应的 API 实现与 Android 设备进行数据交互。
|
||||
|
||||
Android Host 和 USB Device 的通讯关系参考图:
|
||||
|
||||
```
|
||||
USB HOST USB Device
|
||||
Android (USB HOST/OTG) ─── USB 通讯 ─── 转接芯片 ─── UART ─── PC/MCU/串口设备
|
||||
```
|
||||
|
||||
CH34X 串口转接芯片 Android 接口需要 Android 4.4 及以上版本系统,使用 CH34X 需要 Android 满足条件:
|
||||
|
||||
1. 基于 Android 4.4 及以上版本系统
|
||||
2. Android 设备具备 USB Host(或 OTG)接口
|
||||
|
||||
本文档将着重介绍调用 Android USB Host 与 Device 通讯相关 API 以及相应的操作说明。关于 Android USB Host 协议说明,可以参考 Google 官方文档。
|
||||
|
||||
---
|
||||
|
||||
## 二、Android Host
|
||||
|
||||
本文档所指的程序是基于 Android 4.4 及以上版本系统下编写的。Android 应用程序的动画参考文章 Android Filter.xml 文件的 product id 和 vendor id,基于 CH34X UART 开发的 Android 应用程序主要架构和步骤,如下图:
|
||||
|
||||
```
|
||||
┌──────────────────────────────┐
|
||||
│ Android Host │
|
||||
│ ┌──────────────────────┐ │
|
||||
│ │ User Layout │ │
|
||||
│ └──────────────────────┘ │
|
||||
│ │
|
||||
│ ┌──────────────────────┐ │
|
||||
│ │ CH34XUART │ │
|
||||
│ │ Applications │ │
|
||||
│ └──────────────────────┘ │
|
||||
│ ↕ │
|
||||
│ ┌──────────────────────┐ │
|
||||
│ │ CH34XDriver.jar (lib)│ │
|
||||
│ └──────────────────────┘ │
|
||||
└──────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、软件操作说明
|
||||
|
||||
用户需要在支持 USB Host 功能的 Android 设备上安装测试程序(CH34XUARTDemo.apk),点击打开测试程序,测试程序显示当前 USB 设备的状态。点击"打开"后,如果未连接相应的 USB 芯片的设备,则会显示设备未连接状态;点击"断开",则会显示设备未连接。在连接条件后,按钮显示"已连接"。用户通过串口调试功能,发送数据可以接收返回的数据。
|
||||
|
||||
---
|
||||
|
||||
## 四、函数接口说明
|
||||
|
||||
### 4.1 getInstance
|
||||
|
||||
```java
|
||||
public static WCHUARTManager getInstance()
|
||||
```
|
||||
|
||||
用于获取全局唯一实例。
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| 返回 | 返回全局唯一实例 |
|
||||
|
||||
---
|
||||
|
||||
### 4.2 init
|
||||
|
||||
```java
|
||||
public void init(android.app.Application application)
|
||||
```
|
||||
|
||||
初始化上下文,注册广播接收设备状态变化。
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| 参数 | application - 全局上下文 |
|
||||
|
||||
---
|
||||
|
||||
### 4.3 enumDevice
|
||||
|
||||
```java
|
||||
public java.util.ArrayList<android.hardware.usb.UsbDevice> enumDevice()
|
||||
throws java.lang.Exception
|
||||
```
|
||||
|
||||
枚举当前所有符合要求的 USB 设备。
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| 返回 | android.hardware.usb.UsbDevice 设备列表 |
|
||||
| 抛出 | java.lang.Exception |
|
||||
|
||||
---
|
||||
|
||||
### 4.4 getChipType
|
||||
|
||||
```java
|
||||
public cn.wch.uartlib.chipImpl.type.ChipType2 getChipType(@NonNull
|
||||
android.hardware.usb.UsbDevice usbDevice)
|
||||
```
|
||||
|
||||
获取 usbDevice 的芯片类型。
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| 参数 | usbDevice - USB 设备 |
|
||||
| 返回 | 芯片类型(usbDevice 是 WCH 芯片);否则表示无法识别 USB 设备的芯片类型 |
|
||||
|
||||
---
|
||||
|
||||
### 4.5 openDevice
|
||||
|
||||
```java
|
||||
public boolean openDevice(@NonNull
|
||||
android.hardware.usb.UsbDevice usbDevice)
|
||||
throws cn.wch.uartlib.exception.UartLibException,
|
||||
cn.wch.uartlib.exception.NoPermissionException,
|
||||
cn.wch.uartlib.exception.ChipException
|
||||
```
|
||||
|
||||
打开 USB 设备。
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| 参数 | usbDevice - USB 设备 |
|
||||
| 返回 | true 成功;false 失败 |
|
||||
| 抛出 | cn.wch.uartlib.exception.UartLibException<br>cn.wch.uartlib.exception.NoPermissionException<br>cn.wch.uartlib.exception.ChipException |
|
||||
|
||||
---
|
||||
|
||||
### 4.6 requestPermission
|
||||
|
||||
```java
|
||||
public void requestPermission(@NonNull
|
||||
android.content.Context context,
|
||||
@NonNull android.hardware.usb.UsbDevice usbDevice)
|
||||
throws cn.wch.uartlib.exception.UartLibException
|
||||
```
|
||||
|
||||
请求 USB 设备权限。
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| 参数 | context - 上下文<br>usbDevice - USB 设备 |
|
||||
| 抛出 | cn.wch.uartlib.exception.UartLibException |
|
||||
|
||||
---
|
||||
|
||||
### 4.7 setUsbStateListener
|
||||
|
||||
```java
|
||||
public void setUsbStateListener(@NonNull
|
||||
cn.wch.uartlib.callback.IUsbStateChange usbStateListener)
|
||||
```
|
||||
|
||||
监听设备的状态变化。
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| 参数 | usbStateListener - 设备状态监听回调 |
|
||||
|
||||
---
|
||||
|
||||
### 4.8 getSerialCount
|
||||
|
||||
```java
|
||||
public int getSerialCount(@NonNull
|
||||
android.hardware.usb.UsbDevice usbDevice)
|
||||
```
|
||||
|
||||
获取设备串口数量。
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| 参数 | usbDevice - USB 设备 |
|
||||
| 返回 | 返回串口数量。如果为负数,说明获取芯片类型失败 |
|
||||
|
||||
---
|
||||
|
||||
### 4.9 enableSerial
|
||||
|
||||
```java
|
||||
public boolean enableSerial(@NonNull UsbDevice usbDevice,
|
||||
int serialNumber, boolean enable) throws Exception
|
||||
```
|
||||
|
||||
打开/关闭串口(实际仅对 CH9114 系列有效,其他类型设备无影响)。
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| 参数 | usbDevice - USB 设备<br>serialNumber - 串口号<br>enable - false 关闭;true 打开 |
|
||||
| 返回 | true 设置成功;false 设置失败 |
|
||||
| 抛出 | java.lang.Exception |
|
||||
|
||||
---
|
||||
|
||||
### 4.10 setSerialParameter
|
||||
|
||||
```java
|
||||
public boolean setSerialParameter(@NonNull
|
||||
android.hardware.usb.UsbDevice usbDevice,
|
||||
int serialNumber,
|
||||
int baud,
|
||||
int dataBit,
|
||||
int stopBit,
|
||||
int parityBit,
|
||||
boolean flow)
|
||||
throws java.lang.Exception
|
||||
```
|
||||
|
||||
设置串口参数。
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| 参数 | usbDevice - USB 设备<br>serialNumber - 串口号<br>baud - 波特率<br>dataBit - 数据位 5,6,7,8<br>stopBit - 停止位 1,2<br>parityBit - 校验位 0 NONE; 1 ODD; 2 EVEN; 3 MARK; 4 SPACE<br>flow - true 打开流控;false 关闭 |
|
||||
| 返回 | true 设置成功;false 设置失败 |
|
||||
| 抛出 | java.lang.Exception |
|
||||
|
||||
---
|
||||
|
||||
### 4.11 getSerialBaud
|
||||
|
||||
```java
|
||||
public int getSerialBaud(@NonNull UsbDevice usbDevice, int serialNumber)
|
||||
throws Exception
|
||||
```
|
||||
|
||||
获取串口波特率(实际仅对 CH9114 系列有效,其他类型设备无影响)。
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| 参数 | usbDevice - USB 设备<br>serialNumber - 串口号 |
|
||||
| 返回 | 大于 0:串口波特率;小于 0:出错 |
|
||||
| 抛出 | java.lang.Exception |
|
||||
|
||||
---
|
||||
|
||||
### 4.12 getChipMasterFrequency
|
||||
|
||||
```java
|
||||
public ChipMasterFrequency getChipMasterFrequency(@NonNull UsbDevice
|
||||
usbDevice) throws Exception
|
||||
```
|
||||
|
||||
获取芯片主频(实际仅对 CH9114 系列有效,其他类型设备无影响)。
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| 参数 | usbDevice - USB 设备 |
|
||||
| 返回 | ChipMasterFrequency 对象:<br>- int frequency 频率<br>- Boolean switchEnable 是否允许切换<br>- int CoStatus 当前状态 |
|
||||
| 抛出 | java.lang.Exception |
|
||||
|
||||
---
|
||||
|
||||
### 4.13 syncWriteData
|
||||
|
||||
```java
|
||||
public int syncWriteData(@NonNull UsbDevice usbDevice, int serialNumber,
|
||||
byte[] data, int length, int timeout) throws Exception
|
||||
```
|
||||
|
||||
发送串口数据(同步发送)。
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| 参数 | usbDevice - USB 设备<br>serialNumber - 串口号<br>data - 将要发送的数据<br>length - 将要发送数据的长度<br>timeout - 超时时间 |
|
||||
| 返回 | 发送成功返回数据的长度 |
|
||||
| 抛出 | java.lang.Exception |
|
||||
|
||||
---
|
||||
|
||||
### 4.14 asyncWriteData
|
||||
|
||||
```java
|
||||
public void asyncWriteData(@NonNull UsbDevice usbDevice, int serialNumber,
|
||||
byte[] data) throws Exception
|
||||
```
|
||||
|
||||
发送串口数据(异步发送,将数据加入缓存队列发送,不能返回状态和结果)。
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| 参数 | usbDevice - USB 设备<br>serialNumber - 串口号<br>data - 将要发送的数据 |
|
||||
| 抛出 | java.lang.Exception |
|
||||
|
||||
---
|
||||
|
||||
### 4.15 readData
|
||||
|
||||
```java
|
||||
public byte[] readData(@NonNull
|
||||
android.hardware.usb.UsbDevice usbDevice,
|
||||
int serialNumber)
|
||||
throws java.lang.Exception
|
||||
```
|
||||
|
||||
读取串口数据。
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| 参数 | usbDevice - USB 设备<br>serialNumber - 串口号 |
|
||||
| 返回 | 读取到的数据 |
|
||||
| 抛出 | cn.wch.uartlib.exception.ChipException |
|
||||
|
||||
---
|
||||
|
||||
### 4.16 readData(重载方法)
|
||||
|
||||
```java
|
||||
public byte[] readData(@NonNull
|
||||
android.hardware.usb.UsbDevice usbDevice,
|
||||
int serialNumber,
|
||||
int vTime,
|
||||
int vMin)
|
||||
throws java.lang.Exception
|
||||
```
|
||||
|
||||
读取串口数据。
|
||||
|
||||
**说明:**
|
||||
|
||||
1. 当 vTime>0,vMin>0 时,read 函数将阻塞直到读到最后一个字符时开始计时,超过 vTime 时间后,如果接收到的数据不足 vMin 字节,则返回。否则继续等待直到收到 vMin 个字节或超时。
|
||||
2. 当 vTime>0,vMin=0 时,read 函数立即返回,超时为每个字符等待 vTime 时间。
|
||||
3. 当 vTime=0,vMin>0 时,read 函数一直阻塞,直到读到 vMin 个字节后返回。
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| 参数 | usbDevice - USB 设备<br>serialNumber - 串口号<br>vTime - 等待时间<br>vMin - 最小读取字节数 |
|
||||
| 返回 | 读取到的数据 |
|
||||
| 抛出 | cn.wch.uartlib.exception.ChipException |
|
||||
|
||||
---
|
||||
|
||||
### 4.17 registerDataCallback
|
||||
|
||||
```java
|
||||
public void registerDataCallback (@NonNull
|
||||
android.hardware.usb.UsbDevice usbDevice,
|
||||
cn.wch.uartlib.callback.IDataCallback dataCallback)
|
||||
throws java.lang.Exception
|
||||
```
|
||||
|
||||
注册串口数据回调(此方法可替代 readData 函数,用户通过回调方式接收数据)。注意:如果调用了 registerDataCallback(device,null),等同于 removeDataCallback(device)。
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| 参数 | usbDevice - USB 设备<br>dataCallback - 数据回调 |
|
||||
| 抛出 | java.lang.Exception |
|
||||
|
||||
---
|
||||
|
||||
### 4.18 removeDataCallback
|
||||
|
||||
```java
|
||||
public void removeDataCallback (@NonNull
|
||||
android.hardware.usb.UsbDevice usbDevice)
|
||||
```
|
||||
|
||||
移除串口数据回调。
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| 参数 | usbDevice - USB 设备 |
|
||||
|
||||
---
|
||||
|
||||
### 4.19 isConnected
|
||||
|
||||
```java
|
||||
public boolean isConnected(@NonNull
|
||||
android.hardware.usb.UsbDevice usbDevice)
|
||||
```
|
||||
|
||||
判断 USB 设备是否已经连接。
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| 参数 | usbDevice - USB 设备 |
|
||||
| 返回 | true 已经连接;false 没有打开 |
|
||||
|
||||
---
|
||||
|
||||
### 4.20 getConnectedDevices
|
||||
|
||||
```java
|
||||
public java.util.ArrayList<android.hardware.usb.UsbDevice> getConnectedDevices()
|
||||
```
|
||||
|
||||
获取当前已连接的设备。
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| 返回 | 已经打开的设备列表 |
|
||||
|
||||
---
|
||||
|
||||
### 4.21 disconnect
|
||||
|
||||
```java
|
||||
public void disconnect(@NonNull
|
||||
android.hardware.usb.UsbDevice usbDevice)
|
||||
```
|
||||
|
||||
断开 USB 设备连接。
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| 参数 | usbDevice - USB 设备 |
|
||||
|
||||
---
|
||||
|
||||
### 4.22 close
|
||||
|
||||
```java
|
||||
public void close(@NonNull Context context)
|
||||
```
|
||||
|
||||
释放资源、断开所有连接设备。
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| 参数 | context - 上下文 |
|
||||
|
||||
---
|
||||
|
||||
### 4.23 isSupportGPIOFeature
|
||||
|
||||
```java
|
||||
public boolean isSupportGPIOFeature(UsbDevice device)
|
||||
throws java.lang.Exception
|
||||
```
|
||||
|
||||
检查本接口当前是否支持该设备的 GPIO 特性配置。应当在操作 GPIO 前调用。
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| 参数 | device - USB 设备 |
|
||||
| 返回 | true 支持;false 不支持 |
|
||||
| 抛出 | java.lang.Exception |
|
||||
|
||||
---
|
||||
|
||||
### 4.24 queryGPIOCount
|
||||
|
||||
```java
|
||||
public int queryGPIOCount(UsbDevice device)
|
||||
throws java.lang.Exception
|
||||
```
|
||||
|
||||
查询该 USB 设备的 GPIO 数量。
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| 参数 | device - USB 设备 |
|
||||
| 返回 | GPIO 数量 |
|
||||
| 抛出 | java.lang.Exception |
|
||||
|
||||
---
|
||||
|
||||
### 4.25 queryGPIOStatus
|
||||
|
||||
```java
|
||||
public GPIO_Status queryGPIOStatus(UsbDevice device, int gpioIndex)
|
||||
throws java.lang.Exception
|
||||
```
|
||||
|
||||
查询该 USB 设备的某个 GPIO 状态。
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| 参数 | device - USB 设备<br>gpioIndex - GPIO 序号,从 0 开始 |
|
||||
| 返回 | GPIO 状态 |
|
||||
| 抛出 | java.lang.Exception |
|
||||
|
||||
---
|
||||
|
||||
### 4.26 queryAllGPIOStatus
|
||||
|
||||
```java
|
||||
public List<GPIO_Status> queryAllGPIOStatus(UsbDevice device)
|
||||
throws java.lang.Exception
|
||||
```
|
||||
|
||||
查询该 USB 设备的全部 GPIO 状态。
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| 参数 | device - USB 设备 |
|
||||
| 返回 | 全部 GPIO 状态 |
|
||||
| 抛出 | java.lang.Exception |
|
||||
|
||||
---
|
||||
|
||||
### 4.27 enableGPIO
|
||||
|
||||
```java
|
||||
public boolean enableGPIO(UsbDevice device, int gpioIndex, boolean enable,
|
||||
GPIO_DIR dir)
|
||||
throws java.lang.Exception
|
||||
```
|
||||
|
||||
使能该硬件设备的某个 GPIO。
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| 参数 | device - USB 设备<br>gpioIndex - GPIO 序号<br>enable - true 打开;false 关闭<br>dir - GPIO 方向 |
|
||||
| 抛出 | java.lang.Exception |
|
||||
|
||||
---
|
||||
|
||||
### 4.28 setGPIOVal
|
||||
|
||||
```java
|
||||
public boolean setGPIOVal(UsbDevice device, int gpioIndex, GPIO_VALUE value)
|
||||
throws java.lang.Exception
|
||||
```
|
||||
|
||||
设置该硬件设备的某个 GPIO 的电平值。
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| 参数 | device - USB 设备<br>gpioIndex - GPIO 序号<br>value - GPIO 电平值 |
|
||||
| 返回 | true 操作成功;false 操作失败 |
|
||||
| 抛出 | java.lang.Exception |
|
||||
|
||||
---
|
||||
|
||||
### 4.29 getGPIOVal
|
||||
|
||||
```java
|
||||
public GPIO_VALUE getGPIOVal(UsbDevice device, int gpioIndex)
|
||||
throws java.lang.Exception
|
||||
```
|
||||
|
||||
获取该硬件设备的某个 GPIO 的电平值。
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| 参数 | device - USB 设备<br>gpioIndex - GPIO 序号 |
|
||||
| 返回 | value - GPIO 电平值 |
|
||||
| 抛出 | java.lang.Exception |
|
||||
|
||||
---
|
||||
|
||||
### 4.30 setDTR
|
||||
|
||||
```java
|
||||
public boolean setDTR(@NonNull UsbDevice usbDevice, int serialNumber, boolean valid)
|
||||
throws Exception
|
||||
```
|
||||
|
||||
设置 DTR 信号。
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| 参数 | device - USB 设备<br>serialNumber - 串口号<br>valid - 是否有效(低电平有效) |
|
||||
| 返回 | true 操作成功;false 操作失败 |
|
||||
| 抛出 | java.lang.Exception |
|
||||
|
||||
---
|
||||
|
||||
### 4.31 setRTS
|
||||
|
||||
```java
|
||||
public boolean setRTS(@NonNull UsbDevice usbDevice, int serialNumber, boolean valid)
|
||||
throws Exception
|
||||
```
|
||||
|
||||
设置 RTS 信号。
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| 参数 | device - USB 设备<br>serialNumber - 串口号<br>valid - 是否有效(低电平有效) |
|
||||
| 返回 | true 操作成功;false 操作失败 |
|
||||
| 抛出 | java.lang.Exception |
|
||||
|
||||
---
|
||||
|
||||
### 4.32 setBreak
|
||||
|
||||
```java
|
||||
public boolean setBreak(@NonNull UsbDevice usbDevice, int serialNumber, boolean valid)
|
||||
throws Exception
|
||||
```
|
||||
|
||||
设置 Break 信号。
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| 参数 | device - USB 设备<br>serialNumber - 串口号<br>valid - 是否有效(低电平有效) |
|
||||
| 返回 | true 操作成功;false 操作失败 |
|
||||
| 抛出 | java.lang.Exception |
|
||||
|
||||
---
|
||||
|
||||
### 4.33 registerModemStatusCallback
|
||||
|
||||
```java
|
||||
public void registerModemStatusCallback(@NonNull UsbDevice usbDevice, IModemStatus
|
||||
modemStatus) throws Exception
|
||||
```
|
||||
|
||||
注册 Modem 输入信号状态的回调。
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| 参数 | device - USB 设备<br>modemStatus - 状态回调 |
|
||||
| 抛出 | java.lang.Exception |
|
||||
|
||||
---
|
||||
|
||||
### 4.34 querySerialErrorCount
|
||||
|
||||
```java
|
||||
public int querySerialErrorCount(@NonNull UsbDevice usbDevice, int
|
||||
serialNumber, @NonNull SerialErrorType errorType) throws Exception
|
||||
```
|
||||
|
||||
查询串口错误状态。
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| 参数 | device - USB 设备<br>serialNumber - 串口号<br>errorType - 错误类型 |
|
||||
| 返回 | 该种错误的次数 |
|
||||
| 抛出 | java.lang.Exception |
|
||||
|
||||
---
|
||||
|
||||
### 4.35 setReadTimeout
|
||||
|
||||
```java
|
||||
public static void setReadTimeout(int timeout)
|
||||
```
|
||||
|
||||
设置读超时时间。默认为 0,即采用异步方式读取;设置为非 0 时采用同步方式,超时时间为 BulkTransfer 同步传输的超时时间。全局生效,应在 APP 初始化时设置。
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| 参数 | timeout - 超时时间,单位为 ms |
|
||||
|
||||
---
|
||||
|
||||
### 4.36 addNewHardwareAndChipType
|
||||
|
||||
```java
|
||||
public static void addNewHardwareAndChipType(int vid, int pid, ChipType2 chipType)
|
||||
```
|
||||
|
||||
添加新的硬件 VID/PID 以及芯片类型。
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| 参数 | vid - 硬件 vid<br>pid - 硬件 pid<br>chipType - 芯片类型 |
|
||||
|
||||
---
|
||||
|
||||
### 4.37 setDebug
|
||||
|
||||
```java
|
||||
public static void setDebug(boolean open)
|
||||
```
|
||||
|
||||
设置调试模式打开或者关闭。打开调试模式会打印日志,默认关闭。应在 APP 初始化时设置。
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| 参数 | open - true 打开;false 关闭 |
|
||||
|
||||
---
|
||||
|
||||
### 4.38 isDebugMode
|
||||
|
||||
```java
|
||||
public static boolean isDebugMode()
|
||||
```
|
||||
|
||||
返回当前是否在调试模式,是否打印日志。
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| 返回 | true 处于调试模式;false 不处于调试模式 |
|
||||
|
||||
---
|
||||
|
||||
## 附录
|
||||
|
||||
- **文档版本:** V1.8
|
||||
- **文档名称:** CH34X 系列芯片串口 Android 程序开发说明
|
||||
- **适用芯片型号:** CH339 / CH340 / CH341 / CH342 / CH343 / CH344 / CH347 / CH9101 / CH9102 / CH9103 / CH9104 / CH9143 / CH9111 / CH9114
|
||||
- **系统要求:** Android 4.4 及以上版本
|
||||
- **技术支持网站:** https://wch.cn
|
||||
1265
docs/USAGE_GUIDE.md
Normal file
1265
docs/USAGE_GUIDE.md
Normal file
File diff suppressed because it is too large
Load Diff
43
example/.gitignore
vendored
Normal file
43
example/.gitignore
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
migrate_working_dir/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
#.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
**/doc/api/
|
||||
**/ios/Flutter/.last_build_id
|
||||
.dart_tool/
|
||||
.flutter-plugins
|
||||
.flutter-plugins-dependencies
|
||||
.pub-cache/
|
||||
.pub/
|
||||
/build/
|
||||
|
||||
# Symbolication related
|
||||
app.*.symbols
|
||||
|
||||
# Obfuscation related
|
||||
app.*.map.json
|
||||
|
||||
# Android Studio will place build artifacts here
|
||||
/android/app/debug
|
||||
/android/app/profile
|
||||
/android/app/release
|
||||
16
example/README.md
Normal file
16
example/README.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# ch34_example
|
||||
|
||||
Demonstrates how to use the ch34 plugin.
|
||||
|
||||
## Getting Started
|
||||
|
||||
This project is a starting point for a Flutter application.
|
||||
|
||||
A few resources to get you started if this is your first Flutter project:
|
||||
|
||||
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
|
||||
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
|
||||
|
||||
For help getting started with Flutter development, view the
|
||||
[online documentation](https://docs.flutter.dev/), which offers tutorials,
|
||||
samples, guidance on mobile development, and a full API reference.
|
||||
28
example/analysis_options.yaml
Normal file
28
example/analysis_options.yaml
Normal file
@@ -0,0 +1,28 @@
|
||||
# This file configures the analyzer, which statically analyzes Dart code to
|
||||
# check for errors, warnings, and lints.
|
||||
#
|
||||
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
|
||||
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
|
||||
# invoked from the command line by running `flutter analyze`.
|
||||
|
||||
# The following line activates a set of recommended lints for Flutter apps,
|
||||
# packages, and plugins designed to encourage good coding practices.
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
linter:
|
||||
# The lint rules applied to this project can be customized in the
|
||||
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
||||
# included above or to enable additional rules. A list of all available lints
|
||||
# and their documentation is published at https://dart.dev/lints.
|
||||
#
|
||||
# Instead of disabling a lint rule for the entire project in the
|
||||
# section below, it can also be suppressed for a single line of code
|
||||
# or a specific dart file by using the `// ignore: name_of_lint` and
|
||||
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
||||
# producing the lint.
|
||||
rules:
|
||||
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
13
example/android/.gitignore
vendored
Normal file
13
example/android/.gitignore
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
gradle-wrapper.jar
|
||||
/.gradle
|
||||
/captures/
|
||||
/gradlew
|
||||
/gradlew.bat
|
||||
/local.properties
|
||||
GeneratedPluginRegistrant.java
|
||||
|
||||
# Remember to never publicly share your keystore.
|
||||
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
|
||||
key.properties
|
||||
**/*.keystore
|
||||
**/*.jks
|
||||
58
example/android/app/build.gradle
Normal file
58
example/android/app/build.gradle
Normal file
@@ -0,0 +1,58 @@
|
||||
plugins {
|
||||
id "com.android.application"
|
||||
id "kotlin-android"
|
||||
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
|
||||
id "dev.flutter.flutter-gradle-plugin"
|
||||
}
|
||||
|
||||
def localProperties = new Properties()
|
||||
def localPropertiesFile = rootProject.file("local.properties")
|
||||
if (localPropertiesFile.exists()) {
|
||||
localPropertiesFile.withReader("UTF-8") { reader ->
|
||||
localProperties.load(reader)
|
||||
}
|
||||
}
|
||||
|
||||
def flutterVersionCode = localProperties.getProperty("flutter.versionCode")
|
||||
if (flutterVersionCode == null) {
|
||||
flutterVersionCode = "1"
|
||||
}
|
||||
|
||||
def flutterVersionName = localProperties.getProperty("flutter.versionName")
|
||||
if (flutterVersionName == null) {
|
||||
flutterVersionName = "1.0"
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.example.ch34_example"
|
||||
compileSdk = flutter.compileSdkVersion
|
||||
ndkVersion = flutter.ndkVersion
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId = "com.example.ch34_example"
|
||||
// You can update the following values to match your application needs.
|
||||
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
|
||||
minSdk = flutter.minSdkVersion
|
||||
targetSdk = flutter.targetSdkVersion
|
||||
versionCode = flutterVersionCode.toInteger()
|
||||
versionName = flutterVersionName
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
// TODO: Add your own signing config for the release build.
|
||||
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||
signingConfig = signingConfigs.debug
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flutter {
|
||||
source = "../.."
|
||||
}
|
||||
7
example/android/app/src/debug/AndroidManifest.xml
Normal file
7
example/android/app/src/debug/AndroidManifest.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- The INTERNET permission is required for development. Specifically,
|
||||
the Flutter tool needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</manifest>
|
||||
72
example/android/app/src/main/AndroidManifest.xml
Normal file
72
example/android/app/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,72 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<application
|
||||
android:label="ch34_example"
|
||||
android:name="${applicationName}"
|
||||
android:enabled="true"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTop"
|
||||
android:taskAffinity=""
|
||||
android:theme="@style/LaunchTheme"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
android:hardwareAccelerated="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||
the Android process has started. This theme is visible to the user
|
||||
while the Flutter UI initializes. After that, this theme continues
|
||||
to determine the Window background behind the Flutter UI. -->
|
||||
<meta-data
|
||||
android:name="io.flutter.embedding.android.NormalTheme"
|
||||
android:resource="@style/NormalTheme"
|
||||
/>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
|
||||
<!-- 添加你的 BroadcastReceiver 定义 -->
|
||||
<!-- <receiver-->
|
||||
<!-- android:name="com.example.ch34.MyBroadcastReceiver"-->
|
||||
<!-- android:directBootAware="true"-->
|
||||
<!-- android:enabled="true"-->
|
||||
<!-- android:exported="true"> <!– 根据需要设置为 true 或 false –>-->
|
||||
<!-- <intent-filter>-->
|
||||
<!-- <action android:name="com.example.ch34.intent" />-->
|
||||
<!-- </intent-filter>-->
|
||||
<!-- </receiver>-->
|
||||
|
||||
|
||||
<!-- <receiver-->
|
||||
<!-- android:name="com.example.ch34_example.MyBroadcastReceiver"-->
|
||||
<!-- android:directBootAware="true"-->
|
||||
<!-- android:enabled="true"-->
|
||||
<!-- android:exported="true">-->
|
||||
<!-- <intent-filter>-->
|
||||
<!-- <action android:name="com.example.ch34_example.intent" />-->
|
||||
<!-- </intent-filter>-->
|
||||
<!-- </receiver>-->
|
||||
|
||||
|
||||
|
||||
<!-- Don't delete the meta-data below.
|
||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||
<meta-data
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
</application>
|
||||
<!-- Required to query activities that can process text, see:
|
||||
https://developer.android.com/training/package-visibility and
|
||||
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
|
||||
|
||||
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
||||
<data android:mimeType="text/plain"/>
|
||||
</intent>
|
||||
</queries>
|
||||
</manifest>
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.example.ch34_example;
|
||||
|
||||
import io.flutter.embedding.android.FlutterActivity;
|
||||
|
||||
public class MainActivity extends FlutterActivity {
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="?android:colorBackground" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
</layer-list>
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@android:color/white" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
</layer-list>
|
||||
BIN
example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
BIN
example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 544 B |
BIN
example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
BIN
example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 442 B |
BIN
example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
BIN
example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 721 B |
BIN
example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
BIN
example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.0 KiB |
BIN
example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
BIN
example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
18
example/android/app/src/main/res/values-night/styles.xml
Normal file
18
example/android/app/src/main/res/values-night/styles.xml
Normal file
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
the Flutter engine draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
||||
18
example/android/app/src/main/res/values/styles.xml
Normal file
18
example/android/app/src/main/res/values/styles.xml
Normal file
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
the Flutter engine draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
||||
7
example/android/app/src/profile/AndroidManifest.xml
Normal file
7
example/android/app/src/profile/AndroidManifest.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- The INTERNET permission is required for development. Specifically,
|
||||
the Flutter tool needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</manifest>
|
||||
18
example/android/build.gradle
Normal file
18
example/android/build.gradle
Normal file
@@ -0,0 +1,18 @@
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.buildDir = "../build"
|
||||
subprojects {
|
||||
project.buildDir = "${rootProject.buildDir}/${project.name}"
|
||||
}
|
||||
subprojects {
|
||||
project.evaluationDependsOn(":app")
|
||||
}
|
||||
|
||||
tasks.register("clean", Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
3
example/android/gradle.properties
Normal file
3
example/android/gradle.properties
Normal file
@@ -0,0 +1,3 @@
|
||||
org.gradle.jvmargs=-Xmx4G -XX:+HeapDumpOnOutOfMemoryError
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
5
example/android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
5
example/android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-all.zip
|
||||
25
example/android/settings.gradle
Normal file
25
example/android/settings.gradle
Normal file
@@ -0,0 +1,25 @@
|
||||
pluginManagement {
|
||||
def flutterSdkPath = {
|
||||
def properties = new Properties()
|
||||
file("local.properties").withInputStream { properties.load(it) }
|
||||
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
||||
return flutterSdkPath
|
||||
}()
|
||||
|
||||
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
|
||||
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||
id "com.android.application" version "8.7.0" apply false
|
||||
id "org.jetbrains.kotlin.android" version "2.0.20" apply false
|
||||
}
|
||||
|
||||
include ":app"
|
||||
13
example/integration_test/plugin_integration_test.dart
Normal file
13
example/integration_test/plugin_integration_test.dart
Normal file
@@ -0,0 +1,13 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'package:ch34/ch34.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
testWidgets('getPlatformVersion test', (WidgetTester tester) async {
|
||||
final String? version = await Ch34Manager.getPlatformVersion();
|
||||
expect(version?.isNotEmpty, true);
|
||||
});
|
||||
}
|
||||
189
example/lib/main.dart
Normal file
189
example/lib/main.dart
Normal file
@@ -0,0 +1,189 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:ch34/ch34.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const MyApp());
|
||||
}
|
||||
|
||||
class MyApp extends StatefulWidget {
|
||||
const MyApp({super.key});
|
||||
|
||||
@override
|
||||
State<MyApp> createState() => _MyAppState();
|
||||
}
|
||||
|
||||
class _MyAppState extends State<MyApp> {
|
||||
String _platformVersion = 'Unknown';
|
||||
List<UsbDeviceInfo> _devices = [];
|
||||
String _status = '未连接';
|
||||
String _receivedData = '';
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
initPlatformState();
|
||||
}
|
||||
|
||||
Future<void> initPlatformState() async {
|
||||
String platformVersion;
|
||||
try {
|
||||
platformVersion =
|
||||
await Ch34Manager.getPlatformVersion() ?? 'Unknown platform version';
|
||||
} on PlatformException {
|
||||
platformVersion = 'Failed to get platform version.';
|
||||
}
|
||||
|
||||
if (!mounted) return;
|
||||
|
||||
setState(() {
|
||||
_platformVersion = platformVersion;
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _scanDevices() async {
|
||||
try {
|
||||
final devices = await Ch34Manager.enumDevice();
|
||||
setState(() {
|
||||
_devices = devices;
|
||||
_status = '发现 ${devices.length} 个设备';
|
||||
});
|
||||
} catch (e) {
|
||||
setState(() {
|
||||
_status = '扫描失败: $e';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _openDevice(String deviceName) async {
|
||||
try {
|
||||
final success = await Ch34Manager.openDevice(deviceName);
|
||||
if (success) {
|
||||
await Ch34Manager.setSerialParameter(
|
||||
deviceName,
|
||||
0,
|
||||
const SerialParameter(baud: 115200),
|
||||
);
|
||||
|
||||
await Ch34Manager.registerDataCallback(
|
||||
deviceName,
|
||||
0,
|
||||
(Uint8List data) {
|
||||
final hex = data.map((b) => b.toRadixString(16).padLeft(2, '0')).join(' ');
|
||||
setState(() {
|
||||
_receivedData = '收到: $hex';
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
setState(() {
|
||||
_status = '已连接: $deviceName';
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
_status = '打开失败';
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
setState(() {
|
||||
_status = '连接失败: $e';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _sendData(String deviceName) async {
|
||||
try {
|
||||
final data = Uint8List.fromList([0x01, 0x02, 0x03]);
|
||||
final sent = await Ch34Manager.writeData(deviceName, 0, data);
|
||||
setState(() {
|
||||
_status = '已发送 $sent 字节';
|
||||
});
|
||||
} catch (e) {
|
||||
setState(() {
|
||||
_status = '发送失败: $e';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _disconnect(String deviceName) async {
|
||||
try {
|
||||
Ch34Manager.removeDataCallback(deviceName);
|
||||
await Ch34Manager.disconnect(deviceName);
|
||||
setState(() {
|
||||
_status = '已断开';
|
||||
});
|
||||
} catch (e) {
|
||||
setState(() {
|
||||
_status = '断开失败: $e';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
home: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('CH34X 示例'),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('平台版本: $_platformVersion\n'),
|
||||
Text('状态: $_status\n'),
|
||||
if (_receivedData.isNotEmpty)
|
||||
Text('接收数据: $_receivedData\n'),
|
||||
const SizedBox(height: 16),
|
||||
ElevatedButton(
|
||||
onPressed: _scanDevices,
|
||||
child: const Text('扫描设备'),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
if (_devices.isNotEmpty)
|
||||
ListView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: _devices.length,
|
||||
itemBuilder: (context, index) {
|
||||
final device = _devices[index];
|
||||
return ListTile(
|
||||
title: Text('${device.deviceName}'),
|
||||
subtitle: Text(
|
||||
'VID: 0x${device.vendorId.toRadixString(16).toUpperCase()} '
|
||||
'PID: 0x${device.productId.toRadixString(16).toUpperCase()} '
|
||||
'端口: ${device.serialCount}'),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.power),
|
||||
onPressed: () => _openDevice(device.deviceName),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.send),
|
||||
onPressed: _status.contains('已连接')
|
||||
? () => _sendData(device.deviceName)
|
||||
: null,
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.power_off),
|
||||
onPressed: _status.contains('已连接')
|
||||
? () => _disconnect(device.deviceName)
|
||||
: null,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
283
example/pubspec.lock
Normal file
283
example/pubspec.lock
Normal file
@@ -0,0 +1,283 @@
|
||||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: async
|
||||
sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.13.1"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: boolean_selector
|
||||
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
ch34:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: ".."
|
||||
relative: true
|
||||
source: path
|
||||
version: "1.1.0"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: characters
|
||||
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: clock
|
||||
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: collection
|
||||
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.19.1"
|
||||
cupertino_icons:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: cupertino_icons
|
||||
sha256: "41e005c33bd814be4d3096aff55b1908d419fde52ca656c8c47719ec745873cd"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.0.9"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fake_async
|
||||
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.3.3"
|
||||
file:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file
|
||||
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "7.0.1"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_driver:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_lints:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: flutter_lints
|
||||
sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
fuchsia_remote_debug_protocol:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
integration_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
leak_tracker:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "11.0.2"
|
||||
leak_tracker_flutter_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_flutter_testing
|
||||
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.0.10"
|
||||
leak_tracker_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_testing
|
||||
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
lints:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: lints
|
||||
sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.12.19"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.13.0"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.17.0"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path
|
||||
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: platform
|
||||
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.1.6"
|
||||
plugin_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: plugin_platform_interface
|
||||
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.1.8"
|
||||
process:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: process
|
||||
sha256: c6248e4526673988586e8c00bb22a49210c258dc91df5227d5da9748ecf79744
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "5.0.5"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
source_span:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_span
|
||||
sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.10.2"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stack_trace
|
||||
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.12.1"
|
||||
stream_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_channel
|
||||
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
string_scanner:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: string_scanner
|
||||
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
sync_http:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sync_http
|
||||
sha256: "7f0cd72eca000d2e026bcd6f990b81d0ca06022ef4e32fb257b30d3d1014a961"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.3.1"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: term_glyph
|
||||
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
|
||||
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.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.7.10"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_math
|
||||
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
vm_service:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: "046d3928e16fa4dc46e8350415661755ab759d9fc97fc21b5ab295f71e4f0499"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "15.1.0"
|
||||
webdriver:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: webdriver
|
||||
sha256: "2f3a14ca026957870cfd9c635b83507e0e51d8091568e90129fbf805aba7cade"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
sdks:
|
||||
dart: ">=3.9.0 <4.0.0"
|
||||
flutter: ">=3.18.0-18.0.pre.54"
|
||||
85
example/pubspec.yaml
Normal file
85
example/pubspec.yaml
Normal file
@@ -0,0 +1,85 @@
|
||||
name: ch34_example
|
||||
description: "Demonstrates how to use the ch34 plugin."
|
||||
# The following line prevents the package from being accidentally published to
|
||||
# pub.dev using `flutter pub publish`. This is preferred for private packages.
|
||||
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||
|
||||
environment:
|
||||
sdk: '>=3.4.3 <4.0.0'
|
||||
|
||||
# Dependencies specify other packages that your package needs in order to work.
|
||||
# To automatically upgrade your package dependencies to the latest versions
|
||||
# consider running `flutter pub upgrade --major-versions`. Alternatively,
|
||||
# dependencies can be manually updated by changing the version numbers below to
|
||||
# the latest version available on pub.dev. To see which dependencies have newer
|
||||
# versions available, run `flutter pub outdated`.
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
|
||||
ch34:
|
||||
# When depending on this package from a real application you should use:
|
||||
# ch34: ^x.y.z
|
||||
# See https://dart.dev/tools/pub/dependencies#version-constraints
|
||||
# The example app is bundled with the plugin so we use a path dependency on
|
||||
# the parent directory to use the current plugin's version.
|
||||
path: ../
|
||||
|
||||
# The following adds the Cupertino Icons font to your application.
|
||||
# Use with the CupertinoIcons class for iOS style icons.
|
||||
cupertino_icons: ^1.0.6
|
||||
|
||||
dev_dependencies:
|
||||
integration_test:
|
||||
sdk: flutter
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
||||
# The "flutter_lints" package below contains a set of recommended lints to
|
||||
# encourage good coding practices. The lint set provided by the package is
|
||||
# activated in the `analysis_options.yaml` file located at the root of your
|
||||
# package. See that file for information about deactivating specific lint
|
||||
# rules and activating additional ones.
|
||||
flutter_lints: ^3.0.0
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
# following page: https://dart.dev/tools/pub/pubspec
|
||||
|
||||
# The following section is specific to Flutter packages.
|
||||
flutter:
|
||||
|
||||
# The following line ensures that the Material Icons font is
|
||||
# included with your application, so that you can use the icons in
|
||||
# the material Icons class.
|
||||
uses-material-design: true
|
||||
|
||||
# To add assets to your application, add an assets section, like this:
|
||||
# assets:
|
||||
# - images/a_dot_burr.jpeg
|
||||
# - images/a_dot_ham.jpeg
|
||||
|
||||
# An image asset can refer to one or more resolution-specific "variants", see
|
||||
# https://flutter.dev/assets-and-images/#resolution-aware
|
||||
|
||||
# For details regarding adding assets from package dependencies, see
|
||||
# https://flutter.dev/assets-and-images/#from-packages
|
||||
|
||||
# To add custom fonts to your application, add a fonts section here,
|
||||
# in this "flutter" section. Each entry in this list should have a
|
||||
# "family" key with the font family name, and a "fonts" key with a
|
||||
# list giving the asset and other descriptors for the font. For
|
||||
# example:
|
||||
# fonts:
|
||||
# - family: Schyler
|
||||
# fonts:
|
||||
# - asset: fonts/Schyler-Regular.ttf
|
||||
# - asset: fonts/Schyler-Italic.ttf
|
||||
# style: italic
|
||||
# - family: Trajan Pro
|
||||
# fonts:
|
||||
# - asset: fonts/TrajanPro.ttf
|
||||
# - asset: fonts/TrajanPro_Bold.ttf
|
||||
# weight: 700
|
||||
#
|
||||
# For details regarding fonts from package dependencies,
|
||||
# see https://flutter.dev/custom-fonts/#from-packages
|
||||
27
example/test/widget_test.dart
Normal file
27
example/test/widget_test.dart
Normal file
@@ -0,0 +1,27 @@
|
||||
// This is a basic Flutter widget test.
|
||||
//
|
||||
// To perform an interaction with a widget in your test, use the WidgetTester
|
||||
// utility in the flutter_test package. For example, you can send tap and scroll
|
||||
// gestures. You can also use WidgetTester to find child widgets in the widget
|
||||
// tree, read text, and verify that the values of widget properties are correct.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:ch34_example/main.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('Verify Platform version', (WidgetTester tester) async {
|
||||
// Build our app and trigger a frame.
|
||||
await tester.pumpWidget(const MyApp());
|
||||
|
||||
// Verify that platform version is retrieved.
|
||||
expect(
|
||||
find.byWidgetPredicate(
|
||||
(Widget widget) => widget is Text &&
|
||||
widget.data!.startsWith('Running on:'),
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
});
|
||||
}
|
||||
68
lib/ch34.dart
Normal file
68
lib/ch34.dart
Normal file
@@ -0,0 +1,68 @@
|
||||
/// CH34 Flutter 插件。
|
||||
///
|
||||
/// 用于与 WCH CH34X 系列 USB 转串口芯片进行通信。
|
||||
///
|
||||
/// 支持的芯片:CH340/CH341/CH342/CH343/CH344/CH347/CH9101/CH9102/CH9103/CH9104/CH9143
|
||||
///
|
||||
/// 使用方法:
|
||||
/// ```dart
|
||||
/// import 'package:ch34/ch34.dart';
|
||||
///
|
||||
/// // 初始化插件(必须在首次调用 Ch34Manager 前调用)
|
||||
/// Ch34.ensureInitialized();
|
||||
///
|
||||
/// // 枚举设备
|
||||
/// final devices = await Ch34Manager.enumDevice();
|
||||
///
|
||||
/// // 打开设备
|
||||
/// await Ch34Manager.openDevice(devices.first.deviceName);
|
||||
///
|
||||
/// // 设置参数
|
||||
/// await Ch34Manager.setSerialParameter(
|
||||
/// devices.first.deviceName,
|
||||
/// 0,
|
||||
/// const SerialParameter(baud: 9600),
|
||||
/// );
|
||||
///
|
||||
/// // 发送数据
|
||||
/// await Ch34Manager.writeData(
|
||||
/// devices.first.deviceName,
|
||||
/// 0,
|
||||
/// Uint8List.fromList([0x01, 0x02]),
|
||||
/// );
|
||||
///
|
||||
/// // 注册数据回调
|
||||
/// Ch34Manager.registerDataCallback(
|
||||
/// devices.first.deviceName,
|
||||
/// 0,
|
||||
/// (data) { print('Received: $data'); },
|
||||
/// );
|
||||
/// ```
|
||||
library ch34;
|
||||
|
||||
export 'src/ch34_method_channel.dart' show Ch34Exception;
|
||||
export 'src/ch34_platform_interface.dart' show Ch34Platform;
|
||||
export 'src/types/ch34_types.dart';
|
||||
export 'src/ch34_manager.dart';
|
||||
|
||||
import 'src/ch34_method_channel.dart';
|
||||
|
||||
/// CH34 插件入口类。
|
||||
///
|
||||
/// 提供插件初始化方法,确保平台实例已注册。
|
||||
class Ch34 {
|
||||
Ch34._();
|
||||
|
||||
static bool _initialized = false;
|
||||
|
||||
/// 初始化 CH34 插件。
|
||||
///
|
||||
/// 必须在首次调用 [Ch34Manager] 方法前调用此方法。
|
||||
/// 重复调用不会产生副作用。
|
||||
static void ensureInitialized() {
|
||||
if (!_initialized) {
|
||||
MethodChannelCh34.registerDefault();
|
||||
_initialized = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
5
lib/ch34_method_channel.dart
Normal file
5
lib/ch34_method_channel.dart
Normal file
@@ -0,0 +1,5 @@
|
||||
/// 顶层导出,保持向后兼容。
|
||||
/// 所有实现已迁移到 `src/` 目录下。
|
||||
library ch34_method_channel;
|
||||
|
||||
export 'src/ch34_method_channel.dart';
|
||||
5
lib/ch34_platform_interface.dart
Normal file
5
lib/ch34_platform_interface.dart
Normal file
@@ -0,0 +1,5 @@
|
||||
/// 顶层导出,保持向后兼容。
|
||||
/// 所有实现已迁移到 `src/` 目录下。
|
||||
library ch34_platform_interface;
|
||||
|
||||
export 'src/ch34_platform_interface.dart';
|
||||
491
lib/src/ch34_manager.dart
Normal file
491
lib/src/ch34_manager.dart
Normal file
@@ -0,0 +1,491 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'ch34_platform_interface.dart';
|
||||
import 'types/ch34_types.dart';
|
||||
|
||||
/// CH34X USB 转串口插件的管理器。
|
||||
///
|
||||
/// 提供静态方法访问所有 WCH WCHUARTManager API。
|
||||
/// 所有方法委托给 [Ch34Platform.instance] 实现。
|
||||
class Ch34Manager {
|
||||
Ch34Manager._();
|
||||
|
||||
/// ==================== 基础方法 ====================
|
||||
|
||||
/// 获取平台版本。
|
||||
///
|
||||
/// @return 平台版本字符串。
|
||||
static Future<String?> getPlatformVersion() {
|
||||
return Ch34Platform.instance.getPlatformVersion();
|
||||
}
|
||||
|
||||
/// ==================== 设备枚举与识别 ====================
|
||||
|
||||
/// 枚举当前所有可用的 USB 设备。
|
||||
///
|
||||
/// @return 可用 USB 设备列表。
|
||||
/// @throws Ch34Exception 如果枚举失败。
|
||||
static Future<List<UsbDeviceInfo>> enumDevice() {
|
||||
return Ch34Platform.instance.enumDevice();
|
||||
}
|
||||
|
||||
/// 获取该 UsbDevice 的芯片型号。
|
||||
///
|
||||
/// @param deviceName 设备名称。
|
||||
/// @return 芯片型号字符串,null 表示无法识别。
|
||||
static Future<String?> getChipType(String deviceName) {
|
||||
return Ch34Platform.instance.getChipType(deviceName);
|
||||
}
|
||||
|
||||
/// ==================== 设备打开与权限 ====================
|
||||
|
||||
/// 打开 USB 设备。
|
||||
///
|
||||
/// @param deviceName 设备名称。
|
||||
/// @return `true` 成功,`false` 失败。
|
||||
static Future<bool> openDevice(String deviceName) {
|
||||
return Ch34Platform.instance.openDevice(deviceName);
|
||||
}
|
||||
|
||||
/// 申请 USB 设备的权限。
|
||||
///
|
||||
/// @param deviceName 设备名称。
|
||||
/// @return `true` 已授权,`false` 被拒绝。
|
||||
static Future<bool> requestPermission(String deviceName) {
|
||||
return Ch34Platform.instance.requestPermission(deviceName);
|
||||
}
|
||||
|
||||
/// ==================== USB 状态监听 ====================
|
||||
|
||||
/// 注册 USB 设备插拔状态监听。
|
||||
///
|
||||
/// @param onStateChanged 状态变化回调。
|
||||
static void setUsbStateListener(
|
||||
void Function(String deviceName, bool connected) onStateChanged) {
|
||||
Ch34Platform.instance.setUsbStateListener(onStateChanged);
|
||||
}
|
||||
|
||||
/// 移除 USB 状态监听。
|
||||
static void removeUsbStateListener() {
|
||||
Ch34Platform.instance.removeUsbStateListener();
|
||||
}
|
||||
|
||||
/// ==================== 串口信息 ====================
|
||||
|
||||
/// 获取设备的串口数目。
|
||||
///
|
||||
/// @param deviceName 设备名称。
|
||||
/// @return 串口数目,-1 表示读取芯片型号失败。
|
||||
static Future<int> getSerialCount(String deviceName) {
|
||||
return Ch34Platform.instance.getSerialCount(deviceName);
|
||||
}
|
||||
|
||||
/// 获取串口波特率。
|
||||
///
|
||||
/// 实际仅针对 CH9114 系列有效,其他类型设备无需调用。
|
||||
///
|
||||
/// @param deviceName 设备名称。
|
||||
/// @param serialNumber 串口号。
|
||||
/// @return 大于 0 表示串口波特率,小于 0 表示出错。
|
||||
static Future<int> getSerialBaud(String deviceName, int serialNumber) {
|
||||
return Ch34Platform.instance.getSerialBaud(deviceName, serialNumber);
|
||||
}
|
||||
|
||||
/// 获取芯片主频。
|
||||
///
|
||||
/// 实际仅针对 CH9114 系列有效,其他类型设备无需调用。
|
||||
///
|
||||
/// @param deviceName 设备名称。
|
||||
/// @return 芯片主频信息对象。
|
||||
static Future<ChipMasterFrequency> getChipMasterFrequency(String deviceName) {
|
||||
return Ch34Platform.instance.getChipMasterFrequency(deviceName);
|
||||
}
|
||||
|
||||
/// 打开或关闭串口。
|
||||
///
|
||||
/// 实际仅针对 CH9114 系列有效,其他类型设备无需调用。
|
||||
///
|
||||
/// @param deviceName 设备名称。
|
||||
/// @param serialNumber 串口号。
|
||||
/// @param enable `true` 打开,`false` 关闭。
|
||||
/// @return `true` 设置成功,`false` 设置失败。
|
||||
static Future<bool> enableSerial(
|
||||
String deviceName,
|
||||
int serialNumber,
|
||||
bool enable,
|
||||
) {
|
||||
return Ch34Platform.instance.enableSerial(deviceName, serialNumber, enable);
|
||||
}
|
||||
|
||||
/// ==================== 串口参数设置 ====================
|
||||
|
||||
/// 设置串口参数。
|
||||
///
|
||||
/// @param deviceName 设备名称。
|
||||
/// @param serialNumber 串口号(从 0 开始)。
|
||||
/// @param parameter 串口参数配置。
|
||||
/// @return `true` 设置成功,`false` 设置失败。
|
||||
static Future<bool> setSerialParameter(
|
||||
String deviceName,
|
||||
int serialNumber,
|
||||
SerialParameter parameter,
|
||||
) {
|
||||
return Ch34Platform.instance.setSerialParameter(
|
||||
deviceName,
|
||||
serialNumber,
|
||||
parameter,
|
||||
);
|
||||
}
|
||||
|
||||
/// ==================== 数据读写 ====================
|
||||
|
||||
/// 发送串口数据(同步发送)。
|
||||
///
|
||||
/// @param deviceName 设备名称。
|
||||
/// @param serialNumber 串口号。
|
||||
/// @param data 要发送的数据。
|
||||
/// @param timeout 超时时间(毫秒),0 表示不超时。
|
||||
/// @return 实际发送的字节数。
|
||||
static Future<int> writeData(
|
||||
String deviceName,
|
||||
int serialNumber,
|
||||
Uint8List data, {
|
||||
int timeout = 0,
|
||||
}) {
|
||||
return Ch34Platform.instance.writeData(
|
||||
deviceName,
|
||||
serialNumber,
|
||||
data,
|
||||
timeout: timeout,
|
||||
);
|
||||
}
|
||||
|
||||
/// 发送串口数据(异步发送)。
|
||||
///
|
||||
/// 将数据加入缓存持续发送,不返回状态和结果。
|
||||
///
|
||||
/// @param deviceName 设备名称。
|
||||
/// @param serialNumber 串口号。
|
||||
/// @param data 要发送的数据。
|
||||
static Future<void> asyncWriteData(
|
||||
String deviceName,
|
||||
int serialNumber,
|
||||
Uint8List data,
|
||||
) {
|
||||
return Ch34Platform.instance.asyncWriteData(
|
||||
deviceName,
|
||||
serialNumber,
|
||||
data,
|
||||
);
|
||||
}
|
||||
|
||||
/// 阻塞读取串口数据。
|
||||
///
|
||||
/// @param deviceName 设备名称。
|
||||
/// @param serialNumber 串口号。
|
||||
/// @return 读取到的数据。
|
||||
static Future<Uint8List> readData(
|
||||
String deviceName,
|
||||
int serialNumber,
|
||||
) {
|
||||
return Ch34Platform.instance.readData(deviceName, serialNumber);
|
||||
}
|
||||
|
||||
/// 主动读取串口数据(带超时参数)。
|
||||
///
|
||||
/// 读取行为说明:
|
||||
/// - 当 vTime>0,vMin>0 时:阻塞直到读取到第一个字符后开始计时,
|
||||
/// 时间到或已读够 vMin 个字符则返回。
|
||||
/// - 当 vTime>0,vMin=0 时:读到数据立即返回,否则最多等待 vTime。
|
||||
/// - 当 vTime=0,vMin>0 时:一直阻塞直到读到 vMin 个字符后返回。
|
||||
///
|
||||
/// @param deviceName 设备名称。
|
||||
/// @param serialNumber 串口号。
|
||||
/// @param vTime 等待时间(毫秒)。
|
||||
/// @param vMin 读取的最小字节数。
|
||||
/// @return 读取到的数据。
|
||||
static Future<Uint8List> readDataWithTimeout(
|
||||
String deviceName,
|
||||
int serialNumber,
|
||||
int vTime,
|
||||
int vMin,
|
||||
) {
|
||||
return Ch34Platform.instance.readDataWithTimeout(
|
||||
deviceName,
|
||||
serialNumber,
|
||||
vTime,
|
||||
vMin,
|
||||
);
|
||||
}
|
||||
|
||||
/// 注册串口数据回调。
|
||||
///
|
||||
/// 注册后数据自动推送,不需要主动调用 [readData]。
|
||||
///
|
||||
/// @param deviceName 设备名称。
|
||||
/// @param serialNumber 串口号。
|
||||
/// @param onData 数据接收回调。
|
||||
static Future<void> registerDataCallback(
|
||||
String deviceName,
|
||||
int serialNumber,
|
||||
void Function(Uint8List data) onData,
|
||||
) {
|
||||
return Ch34Platform.instance.registerDataCallback(
|
||||
deviceName,
|
||||
serialNumber,
|
||||
onData,
|
||||
);
|
||||
}
|
||||
|
||||
/// 取消注册串口数据回调。
|
||||
///
|
||||
/// @param deviceName 设备名称。
|
||||
static void removeDataCallback(String deviceName) {
|
||||
Ch34Platform.instance.removeDataCallback(deviceName);
|
||||
}
|
||||
|
||||
/// ==================== 连接状态 ====================
|
||||
|
||||
/// 判断 USB 设备是否已经连接。
|
||||
///
|
||||
/// @param deviceName 设备名称。
|
||||
/// @return `true` 已连接,`false` 未连接。
|
||||
static Future<bool> isConnected(String deviceName) {
|
||||
return Ch34Platform.instance.isConnected(deviceName);
|
||||
}
|
||||
|
||||
/// 获取当前已经打开的设备列表。
|
||||
///
|
||||
/// @return 已打开的设备名称列表。
|
||||
static Future<List<String>> getConnectedDevices() {
|
||||
return Ch34Platform.instance.getConnectedDevices();
|
||||
}
|
||||
|
||||
/// ==================== 断开与关闭 ====================
|
||||
|
||||
/// 断开 USB 设备的连接。
|
||||
///
|
||||
/// @param deviceName 设备名称。
|
||||
static Future<void> disconnect(String deviceName) {
|
||||
return Ch34Platform.instance.disconnect(deviceName);
|
||||
}
|
||||
|
||||
/// 释放资源,关闭所有串口设备。
|
||||
static Future<void> close() {
|
||||
return Ch34Platform.instance.close();
|
||||
}
|
||||
|
||||
/// ==================== GPIO 功能 ====================
|
||||
|
||||
/// 查询设备是否支持 GPIO 功能。
|
||||
///
|
||||
/// @param deviceName 设备名称。
|
||||
/// @return `true` 支持,`false` 不支持。
|
||||
static Future<bool> isSupportGpio(String deviceName) {
|
||||
return Ch34Platform.instance.isSupportGpio(deviceName);
|
||||
}
|
||||
|
||||
/// 查询该 USB 设备的 GPIO 数目。
|
||||
///
|
||||
/// @param deviceName 设备名称。
|
||||
/// @return GPIO 数目。
|
||||
static Future<int> queryGpioCount(String deviceName) {
|
||||
return Ch34Platform.instance.queryGpioCount(deviceName);
|
||||
}
|
||||
|
||||
/// 查询该 USB 设备指定 GPIO 的状态。
|
||||
///
|
||||
/// @param deviceName 设备名称。
|
||||
/// @param gpioIndex GPIO 编号(从 0 开始)。
|
||||
/// @return GPIO 状态。
|
||||
static Future<GpioStatus> queryGpioStatus(
|
||||
String deviceName,
|
||||
int gpioIndex,
|
||||
) {
|
||||
return Ch34Platform.instance.queryGpioStatus(deviceName, gpioIndex);
|
||||
}
|
||||
|
||||
/// 查询该 USB 设备的所有 GPIO 状态。
|
||||
///
|
||||
/// @param deviceName 设备名称。
|
||||
/// @return 全部 GPIO 状态列表。
|
||||
static Future<List<GpioStatus>> queryAllGpioStatus(String deviceName) {
|
||||
return Ch34Platform.instance.queryAllGpioStatus(deviceName);
|
||||
}
|
||||
|
||||
/// 使能指定 GPIO。
|
||||
///
|
||||
/// @param deviceName 设备名称。
|
||||
/// @param gpioIndex GPIO 编号。
|
||||
/// @param enable `true` 使能,`false` 关闭。
|
||||
/// @param direction GPIO 方向。
|
||||
/// @return `true` 使能成功,`false` 使能失败。
|
||||
static Future<bool> enableGpio(
|
||||
String deviceName,
|
||||
int gpioIndex,
|
||||
bool enable,
|
||||
GpioDirection direction,
|
||||
) {
|
||||
return Ch34Platform.instance.enableGpio(
|
||||
deviceName,
|
||||
gpioIndex,
|
||||
enable,
|
||||
direction,
|
||||
);
|
||||
}
|
||||
|
||||
/// 设置指定 GPIO 的电平值。
|
||||
///
|
||||
/// @param deviceName 设备名称。
|
||||
/// @param gpioIndex GPIO 编号。
|
||||
/// @param value GPIO 电平值。
|
||||
/// @return `true` 设置成功,`false` 设置失败。
|
||||
static Future<bool> setGpioVal(
|
||||
String deviceName,
|
||||
int gpioIndex,
|
||||
GpioValue value,
|
||||
) {
|
||||
return Ch34Platform.instance.setGpioVal(
|
||||
deviceName,
|
||||
gpioIndex,
|
||||
value,
|
||||
);
|
||||
}
|
||||
|
||||
/// 获取指定 GPIO 的电平值。
|
||||
///
|
||||
/// @param deviceName 设备名称。
|
||||
/// @param gpioIndex GPIO 编号。
|
||||
/// @return GPIO 电平值。
|
||||
static Future<GpioValue> getGpioVal(
|
||||
String deviceName,
|
||||
int gpioIndex,
|
||||
) {
|
||||
return Ch34Platform.instance.getGpioVal(deviceName, gpioIndex);
|
||||
}
|
||||
|
||||
/// ==================== 信号控制 ====================
|
||||
|
||||
/// 设置 DTR 信号。
|
||||
///
|
||||
/// @param deviceName 设备名称。
|
||||
/// @param serialNumber 串口号。
|
||||
/// @param valid 是否有效(低电平有效)。
|
||||
/// @return `true` 设置成功,`false` 设置失败。
|
||||
static Future<bool> setDtr(
|
||||
String deviceName,
|
||||
int serialNumber,
|
||||
bool valid,
|
||||
) {
|
||||
return Ch34Platform.instance.setDtr(deviceName, serialNumber, valid);
|
||||
}
|
||||
|
||||
/// 设置 RTS 信号。
|
||||
///
|
||||
/// @param deviceName 设备名称。
|
||||
/// @param serialNumber 串口号。
|
||||
/// @param valid 是否有效(低电平有效)。
|
||||
/// @return `true` 设置成功,`false` 设置失败。
|
||||
static Future<bool> setRts(
|
||||
String deviceName,
|
||||
int serialNumber,
|
||||
bool valid,
|
||||
) {
|
||||
return Ch34Platform.instance.setRts(deviceName, serialNumber, valid);
|
||||
}
|
||||
|
||||
/// 设置 Break 信号。
|
||||
///
|
||||
/// @param deviceName 设备名称。
|
||||
/// @param serialNumber 串口号。
|
||||
/// @param valid 是否有效(低电平有效)。
|
||||
/// @return `true` 设置成功,`false` 设置失败。
|
||||
static Future<bool> setBreakSignal(
|
||||
String deviceName,
|
||||
int serialNumber,
|
||||
bool valid,
|
||||
) {
|
||||
return Ch34Platform.instance.setBreakSignal(
|
||||
deviceName,
|
||||
serialNumber,
|
||||
valid,
|
||||
);
|
||||
}
|
||||
|
||||
/// ==================== Modem 状态回调 ====================
|
||||
|
||||
/// 注册 Modem 控制信号状态回调。
|
||||
///
|
||||
/// @param deviceName 设备名称。
|
||||
/// @param onModemStatus Modem 状态变化回调。
|
||||
static Future<void> registerModemStatusCallback(
|
||||
String deviceName,
|
||||
void Function(ModemStatus status) onModemStatus,
|
||||
) {
|
||||
return Ch34Platform.instance.registerModemStatusCallback(
|
||||
deviceName,
|
||||
onModemStatus,
|
||||
);
|
||||
}
|
||||
|
||||
/// 移除 Modem 状态回调。
|
||||
///
|
||||
/// @param deviceName 设备名称。
|
||||
static void removeModemStatusCallback(String deviceName) {
|
||||
Ch34Platform.instance.removeModemStatusCallback(deviceName);
|
||||
}
|
||||
|
||||
/// ==================== 错误查询 ====================
|
||||
|
||||
/// 查询串口错误状态。
|
||||
///
|
||||
/// @param deviceName 设备名称。
|
||||
/// @param serialNumber 串口号。
|
||||
/// @param errorType 错误类型。
|
||||
/// @return 该种错误出现的次数。
|
||||
static Future<int> querySerialErrorCount(
|
||||
String deviceName,
|
||||
int serialNumber,
|
||||
SerialErrorType errorType,
|
||||
) {
|
||||
return Ch34Platform.instance.querySerialErrorCount(
|
||||
deviceName,
|
||||
serialNumber,
|
||||
errorType,
|
||||
);
|
||||
}
|
||||
|
||||
/// ==================== 全局配置 ====================
|
||||
|
||||
/// 设置读取超时时间。
|
||||
///
|
||||
/// 全局有效,应在 APP 初始化时调用。
|
||||
///
|
||||
/// @param timeout 超时时间(毫秒)。
|
||||
static Future<void> setReadTimeout(int timeout) {
|
||||
return Ch34Platform.instance.setReadTimeout(timeout);
|
||||
}
|
||||
|
||||
/// 添加自定义硬件 VID/PID。
|
||||
///
|
||||
/// @param vid 硬件 VID。
|
||||
/// @param pid 硬件 PID。
|
||||
/// @param chipType 芯片类型(必填,如 "CH340"、"CH9102")。
|
||||
static Future<void> addNewHardware(int vid, int pid, String chipType) {
|
||||
return Ch34Platform.instance.addNewHardware(vid, pid, chipType);
|
||||
}
|
||||
|
||||
/// 设置调试模式。
|
||||
///
|
||||
/// @param enabled `true` 开启,`false` 关闭。
|
||||
static Future<void> setDebug(bool enabled) {
|
||||
return Ch34Platform.instance.setDebug(enabled);
|
||||
}
|
||||
|
||||
/// 返回当前是否处于调试模式。
|
||||
///
|
||||
/// @return `true` 处于调试模式,`false` 不处于。
|
||||
static Future<bool> isDebugMode() {
|
||||
return Ch34Platform.instance.isDebugMode();
|
||||
}
|
||||
}
|
||||
589
lib/src/ch34_method_channel.dart
Normal file
589
lib/src/ch34_method_channel.dart
Normal file
@@ -0,0 +1,589 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'ch34_platform_interface.dart';
|
||||
import 'types/ch34_types.dart';
|
||||
|
||||
/// [Ch34Platform] 的 MethodChannel 实现。
|
||||
///
|
||||
/// 将平台接口方法映射到原生端 MethodChannel 调用,
|
||||
/// 使用 EventChannel 处理数据回调、Modem 状态和 USB 插拔事件。
|
||||
class MethodChannelCh34 extends Ch34Platform {
|
||||
/// 注册默认实例。
|
||||
static void registerDefault() {
|
||||
Ch34Platform.instance = MethodChannelCh34._();
|
||||
}
|
||||
|
||||
MethodChannelCh34._();
|
||||
|
||||
/// MethodChannel 名称。
|
||||
@visibleForTesting
|
||||
final methodChannel = const MethodChannel('ch34');
|
||||
|
||||
/// 数据 EventChannel 名称。
|
||||
@visibleForTesting
|
||||
final dataEventChannel = const EventChannel('ch34/data');
|
||||
|
||||
/// Modem 状态 EventChannel 名称。
|
||||
@visibleForTesting
|
||||
final modemEventChannel = const EventChannel('ch34/modem');
|
||||
|
||||
/// USB 状态 EventChannel 名称。
|
||||
@visibleForTesting
|
||||
final usbStateEventChannel = const EventChannel('ch34/usb_state');
|
||||
|
||||
// ==================== 事件流订阅 ====================
|
||||
|
||||
StreamSubscription? _dataSubscription;
|
||||
StreamSubscription? _modemSubscription;
|
||||
StreamSubscription? _usbStateSubscription;
|
||||
|
||||
void Function(String deviceName, bool connected)? _usbStateCallback;
|
||||
void Function(Uint8List data)? _dataCallback;
|
||||
void Function(ModemStatus status)? _modemCallback;
|
||||
|
||||
// ==================== 基础方法 ====================
|
||||
|
||||
@override
|
||||
Future<String?> getPlatformVersion() async {
|
||||
final version =
|
||||
await methodChannel.invokeMethod<String>('getPlatformVersion');
|
||||
return version;
|
||||
}
|
||||
|
||||
// ==================== 设备枚举与识别 ====================
|
||||
|
||||
@override
|
||||
Future<List<UsbDeviceInfo>> enumDevice() async {
|
||||
final result = await methodChannel.invokeMethod<List>('enumDevice');
|
||||
if (result == null) return [];
|
||||
return result
|
||||
.map((e) => UsbDeviceInfo.fromMap(e as Map<dynamic, dynamic>))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String?> getChipType(String deviceName) async {
|
||||
return await methodChannel.invokeMethod<String>(
|
||||
'getChipType',
|
||||
{'deviceName': deviceName},
|
||||
);
|
||||
}
|
||||
|
||||
// ==================== 设备打开与权限 ====================
|
||||
|
||||
@override
|
||||
Future<bool> openDevice(String deviceName) async {
|
||||
final result = await methodChannel.invokeMethod<bool>(
|
||||
'openDevice',
|
||||
{'deviceName': deviceName},
|
||||
);
|
||||
return result ?? false;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> requestPermission(String deviceName) async {
|
||||
final result = await methodChannel.invokeMethod<bool>(
|
||||
'requestPermission',
|
||||
{'deviceName': deviceName},
|
||||
);
|
||||
return result ?? false;
|
||||
}
|
||||
|
||||
// ==================== USB 状态监听 ====================
|
||||
|
||||
@override
|
||||
void setUsbStateListener(
|
||||
void Function(String deviceName, bool connected) onStateChanged) {
|
||||
_usbStateCallback = onStateChanged;
|
||||
_usbStateSubscription?.cancel();
|
||||
_usbStateSubscription = usbStateEventChannel
|
||||
.receiveBroadcastStream()
|
||||
.listen(
|
||||
(event) {
|
||||
final map = event as Map<dynamic, dynamic>;
|
||||
final deviceName = map['deviceName'] as String;
|
||||
final connected = map['connected'] as bool;
|
||||
_usbStateCallback?.call(deviceName, connected);
|
||||
},
|
||||
onError: (error) {
|
||||
debugPrint('CH34 USB state error: $error');
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void removeUsbStateListener() {
|
||||
_usbStateSubscription?.cancel();
|
||||
_usbStateSubscription = null;
|
||||
_usbStateCallback = null;
|
||||
}
|
||||
|
||||
// ==================== 串口信息 ====================
|
||||
|
||||
@override
|
||||
Future<int> getSerialCount(String deviceName) async {
|
||||
final result = await methodChannel.invokeMethod<int>(
|
||||
'getSerialCount',
|
||||
{'deviceName': deviceName},
|
||||
);
|
||||
return result ?? -1;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<int> getSerialBaud(String deviceName, int serialNumber) async {
|
||||
final result = await methodChannel.invokeMethod<int>(
|
||||
'getSerialBaud',
|
||||
{
|
||||
'deviceName': deviceName,
|
||||
'serialNumber': serialNumber,
|
||||
},
|
||||
);
|
||||
return result ?? -1;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ChipMasterFrequency> getChipMasterFrequency(String deviceName) async {
|
||||
final result = await methodChannel.invokeMethod<Map>(
|
||||
'getChipMasterFrequency',
|
||||
{'deviceName': deviceName},
|
||||
);
|
||||
if (result == null) {
|
||||
throw const Ch34Exception('Failed to get chip master frequency');
|
||||
}
|
||||
return ChipMasterFrequency.fromMap(result);
|
||||
}
|
||||
|
||||
// ==================== 串口参数设置 ====================
|
||||
|
||||
@override
|
||||
Future<bool> enableSerial(
|
||||
String deviceName,
|
||||
int serialNumber,
|
||||
bool enable,
|
||||
) async {
|
||||
final result = await methodChannel.invokeMethod<bool>(
|
||||
'enableSerial',
|
||||
{
|
||||
'deviceName': deviceName,
|
||||
'serialNumber': serialNumber,
|
||||
'enable': enable,
|
||||
},
|
||||
);
|
||||
return result ?? false;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> setSerialParameter(
|
||||
String deviceName,
|
||||
int serialNumber,
|
||||
SerialParameter parameter,
|
||||
) async {
|
||||
final result = await methodChannel.invokeMethod<bool>(
|
||||
'setSerialParameter',
|
||||
{
|
||||
'deviceName': deviceName,
|
||||
'serialNumber': serialNumber,
|
||||
...parameter.toMap(),
|
||||
},
|
||||
);
|
||||
return result ?? false;
|
||||
}
|
||||
|
||||
// ==================== 数据读写 ====================
|
||||
|
||||
@override
|
||||
Future<int> writeData(
|
||||
String deviceName,
|
||||
int serialNumber,
|
||||
Uint8List data, {
|
||||
int timeout = 0,
|
||||
}) async {
|
||||
final result = await methodChannel.invokeMethod<int>(
|
||||
'writeData',
|
||||
{
|
||||
'deviceName': deviceName,
|
||||
'serialNumber': serialNumber,
|
||||
'data': data,
|
||||
'timeout': timeout,
|
||||
},
|
||||
);
|
||||
return result ?? 0;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> asyncWriteData(
|
||||
String deviceName,
|
||||
int serialNumber,
|
||||
Uint8List data,
|
||||
) async {
|
||||
await methodChannel.invokeMethod(
|
||||
'asyncWriteData',
|
||||
{
|
||||
'deviceName': deviceName,
|
||||
'serialNumber': serialNumber,
|
||||
'data': data,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Uint8List> readData(String deviceName, int serialNumber) async {
|
||||
final result = await methodChannel.invokeMethod<Uint8List>(
|
||||
'readData',
|
||||
{
|
||||
'deviceName': deviceName,
|
||||
'serialNumber': serialNumber,
|
||||
},
|
||||
);
|
||||
return result ?? Uint8List(0);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Uint8List> readDataWithTimeout(
|
||||
String deviceName,
|
||||
int serialNumber,
|
||||
int vTime,
|
||||
int vMin,
|
||||
) async {
|
||||
final result = await methodChannel.invokeMethod<Uint8List>(
|
||||
'readDataWithTimeout',
|
||||
{
|
||||
'deviceName': deviceName,
|
||||
'serialNumber': serialNumber,
|
||||
'vTime': vTime,
|
||||
'vMin': vMin,
|
||||
},
|
||||
);
|
||||
return result ?? Uint8List(0);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> registerDataCallback(
|
||||
String deviceName,
|
||||
int serialNumber,
|
||||
void Function(Uint8List data) onData,
|
||||
) async {
|
||||
_dataCallback = onData;
|
||||
await _cancelDataSubscription();
|
||||
_dataSubscription = dataEventChannel
|
||||
.receiveBroadcastStream({'deviceName': deviceName})
|
||||
.listen(
|
||||
(event) {
|
||||
if (event is Uint8List) {
|
||||
_dataCallback?.call(event);
|
||||
}
|
||||
},
|
||||
onError: (error) {
|
||||
debugPrint('CH34 data callback error: $error');
|
||||
},
|
||||
);
|
||||
await methodChannel.invokeMethod('registerDataCallback', {
|
||||
'deviceName': deviceName,
|
||||
'serialNumber': serialNumber,
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void removeDataCallback(String deviceName) {
|
||||
_cancelDataSubscription();
|
||||
methodChannel.invokeMethod('removeDataCallback', {
|
||||
'deviceName': deviceName,
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _cancelDataSubscription() async {
|
||||
await _dataSubscription?.cancel();
|
||||
_dataSubscription = null;
|
||||
_dataCallback = null;
|
||||
}
|
||||
|
||||
// ==================== 连接状态 ====================
|
||||
|
||||
@override
|
||||
Future<bool> isConnected(String deviceName) async {
|
||||
final result = await methodChannel.invokeMethod<bool>(
|
||||
'isConnected',
|
||||
{'deviceName': deviceName},
|
||||
);
|
||||
return result ?? false;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<String>> getConnectedDevices() async {
|
||||
final result = await methodChannel.invokeMethod<List>(
|
||||
'getConnectedDevices',
|
||||
);
|
||||
if (result == null) return [];
|
||||
return result.map((e) => e.toString()).toList();
|
||||
}
|
||||
|
||||
// ==================== 断开与关闭 ====================
|
||||
|
||||
@override
|
||||
Future<void> disconnect(String deviceName) async {
|
||||
await methodChannel.invokeMethod(
|
||||
'disconnect',
|
||||
{'deviceName': deviceName},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
// 清理所有订阅
|
||||
await _cancelDataSubscription();
|
||||
await _modemSubscription?.cancel();
|
||||
_modemSubscription = null;
|
||||
_modemCallback = null;
|
||||
_usbStateSubscription?.cancel();
|
||||
_usbStateSubscription = null;
|
||||
_usbStateCallback = null;
|
||||
|
||||
await methodChannel.invokeMethod('close');
|
||||
}
|
||||
|
||||
// ==================== GPIO 功能 ====================
|
||||
|
||||
@override
|
||||
Future<bool> isSupportGpio(String deviceName) async {
|
||||
final result = await methodChannel.invokeMethod<bool>(
|
||||
'isSupportGpio',
|
||||
{'deviceName': deviceName},
|
||||
);
|
||||
return result ?? false;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<int> queryGpioCount(String deviceName) async {
|
||||
final result = await methodChannel.invokeMethod<int>(
|
||||
'queryGpioCount',
|
||||
{'deviceName': deviceName},
|
||||
);
|
||||
return result ?? 0;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<GpioStatus> queryGpioStatus(String deviceName, int gpioIndex) async {
|
||||
final result = await methodChannel.invokeMethod<Map>(
|
||||
'queryGpioStatus',
|
||||
{
|
||||
'deviceName': deviceName,
|
||||
'gpioIndex': gpioIndex,
|
||||
},
|
||||
);
|
||||
if (result == null) {
|
||||
throw const Ch34Exception('Failed to query GPIO status');
|
||||
}
|
||||
return GpioStatus.fromMap(result);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<GpioStatus>> queryAllGpioStatus(String deviceName) async {
|
||||
final result = await methodChannel.invokeMethod<List>(
|
||||
'queryAllGpioStatus',
|
||||
{'deviceName': deviceName},
|
||||
);
|
||||
if (result == null) return [];
|
||||
return result
|
||||
.map((e) => GpioStatus.fromMap(e as Map<dynamic, dynamic>))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> enableGpio(
|
||||
String deviceName,
|
||||
int gpioIndex,
|
||||
bool enable,
|
||||
GpioDirection direction,
|
||||
) async {
|
||||
final result = await methodChannel.invokeMethod<bool>(
|
||||
'enableGpio',
|
||||
{
|
||||
'deviceName': deviceName,
|
||||
'gpioIndex': gpioIndex,
|
||||
'enable': enable,
|
||||
'direction': direction.index,
|
||||
},
|
||||
);
|
||||
return result ?? false;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> setGpioVal(
|
||||
String deviceName,
|
||||
int gpioIndex,
|
||||
GpioValue value,
|
||||
) async {
|
||||
final result = await methodChannel.invokeMethod<bool>(
|
||||
'setGpioVal',
|
||||
{
|
||||
'deviceName': deviceName,
|
||||
'gpioIndex': gpioIndex,
|
||||
'value': value.index,
|
||||
},
|
||||
);
|
||||
return result ?? false;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<GpioValue> getGpioVal(String deviceName, int gpioIndex) async {
|
||||
final result = await methodChannel.invokeMethod<int>(
|
||||
'getGpioVal',
|
||||
{
|
||||
'deviceName': deviceName,
|
||||
'gpioIndex': gpioIndex,
|
||||
},
|
||||
);
|
||||
if (result == null) {
|
||||
throw const Ch34Exception('Failed to get GPIO value');
|
||||
}
|
||||
return GpioValue.values[result];
|
||||
}
|
||||
|
||||
// ==================== 信号控制 ====================
|
||||
|
||||
@override
|
||||
Future<bool> setDtr(String deviceName, int serialNumber, bool valid) async {
|
||||
final result = await methodChannel.invokeMethod<bool>(
|
||||
'setDtr',
|
||||
{
|
||||
'deviceName': deviceName,
|
||||
'serialNumber': serialNumber,
|
||||
'valid': valid,
|
||||
},
|
||||
);
|
||||
return result ?? false;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> setRts(String deviceName, int serialNumber, bool valid) async {
|
||||
final result = await methodChannel.invokeMethod<bool>(
|
||||
'setRts',
|
||||
{
|
||||
'deviceName': deviceName,
|
||||
'serialNumber': serialNumber,
|
||||
'valid': valid,
|
||||
},
|
||||
);
|
||||
return result ?? false;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> setBreakSignal(
|
||||
String deviceName, int serialNumber, bool valid) async {
|
||||
final result = await methodChannel.invokeMethod<bool>(
|
||||
'setBreak',
|
||||
{
|
||||
'deviceName': deviceName,
|
||||
'serialNumber': serialNumber,
|
||||
'valid': valid,
|
||||
},
|
||||
);
|
||||
return result ?? false;
|
||||
}
|
||||
|
||||
// ==================== Modem 状态回调 ====================
|
||||
|
||||
@override
|
||||
Future<void> registerModemStatusCallback(
|
||||
String deviceName,
|
||||
void Function(ModemStatus status) onModemStatus,
|
||||
) async {
|
||||
_modemCallback = onModemStatus;
|
||||
await _modemSubscription?.cancel();
|
||||
_modemSubscription = modemEventChannel
|
||||
.receiveBroadcastStream({'deviceName': deviceName})
|
||||
.listen(
|
||||
(event) {
|
||||
final map = event as Map<dynamic, dynamic>;
|
||||
debugPrint('CH34 modem event received: $map');
|
||||
_modemCallback?.call(ModemStatus.fromMap(map));
|
||||
},
|
||||
onError: (error) {
|
||||
debugPrint('CH34 modem callback error: $error');
|
||||
},
|
||||
);
|
||||
await methodChannel.invokeMethod('registerModemStatusCallback', {
|
||||
'deviceName': deviceName,
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void removeModemStatusCallback(String deviceName) {
|
||||
_modemSubscription?.cancel();
|
||||
_modemSubscription = null;
|
||||
_modemCallback = null;
|
||||
methodChannel.invokeMethod('removeModemStatusCallback', {
|
||||
'deviceName': deviceName,
|
||||
});
|
||||
}
|
||||
|
||||
// ==================== 错误查询 ====================
|
||||
|
||||
@override
|
||||
Future<int> querySerialErrorCount(
|
||||
String deviceName,
|
||||
int serialNumber,
|
||||
SerialErrorType errorType,
|
||||
) async {
|
||||
final result = await methodChannel.invokeMethod<int>(
|
||||
'querySerialErrorCount',
|
||||
{
|
||||
'deviceName': deviceName,
|
||||
'serialNumber': serialNumber,
|
||||
'errorType': errorType.toNativeString(),
|
||||
},
|
||||
);
|
||||
return result ?? 0;
|
||||
}
|
||||
|
||||
// ==================== 全局配置 ====================
|
||||
|
||||
@override
|
||||
Future<void> setReadTimeout(int timeout) async {
|
||||
await methodChannel.invokeMethod(
|
||||
'setReadTimeout',
|
||||
{'timeout': timeout},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> addNewHardware(int vid, int pid, String chipType) async {
|
||||
await methodChannel.invokeMethod(
|
||||
'addNewHardware',
|
||||
{
|
||||
'vid': vid,
|
||||
'pid': pid,
|
||||
'chipType': chipType,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> setDebug(bool enabled) async {
|
||||
await methodChannel.invokeMethod(
|
||||
'setDebug',
|
||||
{'enabled': enabled},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> isDebugMode() async {
|
||||
final result =
|
||||
await methodChannel.invokeMethod<bool>('isDebugMode');
|
||||
return result ?? false;
|
||||
}
|
||||
}
|
||||
|
||||
/// CH34 插件自定义异常。
|
||||
class Ch34Exception implements Exception {
|
||||
const Ch34Exception(this.message);
|
||||
|
||||
/// 异常描述。
|
||||
final String message;
|
||||
|
||||
@override
|
||||
String toString() => 'Ch34Exception: $message';
|
||||
}
|
||||
418
lib/src/ch34_platform_interface.dart
Normal file
418
lib/src/ch34_platform_interface.dart
Normal file
@@ -0,0 +1,418 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
|
||||
|
||||
import 'types/ch34_types.dart';
|
||||
|
||||
/// CH34X USB 转串口插件的抽象平台接口。
|
||||
///
|
||||
/// 定义所有 WCH WCHUARTManager API 的方法签名。
|
||||
/// 具体实现由 `MethodChannelCh34` 提供。
|
||||
abstract class Ch34Platform extends PlatformInterface {
|
||||
/// Constructs a Ch34Platform.
|
||||
Ch34Platform() : super(token: _token);
|
||||
|
||||
static final Object _token = Object();
|
||||
|
||||
static Ch34Platform? _instance;
|
||||
|
||||
/// The default instance of [Ch34Platform] to use.
|
||||
///
|
||||
/// Defaults to [MethodChannelCh34].
|
||||
/// 注意: 需要在应用初始化时调用 [Ch34Platform.registerDefaultInstance] 注册默认实例。
|
||||
static Ch34Platform get instance {
|
||||
if (_instance == null) {
|
||||
throw StateError(
|
||||
'Ch34Platform.instance has not been initialized. '
|
||||
'Ensure the plugin is properly registered.',
|
||||
);
|
||||
}
|
||||
return _instance!;
|
||||
}
|
||||
|
||||
/// 注册默认平台实例。
|
||||
///
|
||||
/// 通常在插件初始化时由 [MethodChannelCh34] 调用。
|
||||
static void registerDefaultInstance(Ch34Platform instance) {
|
||||
PlatformInterface.verifyToken(instance, _token);
|
||||
_instance = instance;
|
||||
}
|
||||
|
||||
/// Platform-specific implementations should set this with their own
|
||||
/// platform-specific class that extends [Ch34Platform] when they register
|
||||
/// themselves.
|
||||
static set instance(Ch34Platform instance) {
|
||||
PlatformInterface.verifyToken(instance, _token);
|
||||
_instance = instance;
|
||||
}
|
||||
|
||||
/// ==================== 基础方法 ====================
|
||||
|
||||
/// 获取平台版本。
|
||||
///
|
||||
/// @return 平台版本字符串。
|
||||
Future<String?> getPlatformVersion();
|
||||
|
||||
/// ==================== 设备枚举与识别 ====================
|
||||
|
||||
/// 枚举当前所有可用的 USB 设备。
|
||||
///
|
||||
/// 返回设备信息列表,包含 VID/PID、串口数量等。
|
||||
///
|
||||
/// @return 可用 USB 设备列表。
|
||||
/// @throws Ch34Exception 如果枚举失败。
|
||||
Future<List<UsbDeviceInfo>> enumDevice();
|
||||
|
||||
/// 获取该 UsbDevice 的芯片型号。
|
||||
///
|
||||
/// @param deviceName 设备名称(由 [enumDevice] 返回的 deviceName)。
|
||||
/// @return 芯片型号字符串。如果为 null,表示无法识别。
|
||||
Future<String?> getChipType(String deviceName);
|
||||
|
||||
/// ==================== 设备打开与权限 ====================
|
||||
|
||||
/// 打开 USB 设备。
|
||||
///
|
||||
/// 打开设备后可以进行串口通信。
|
||||
///
|
||||
/// @param deviceName 设备名称。
|
||||
/// @return `true` 成功,`false` 失败。
|
||||
Future<bool> openDevice(String deviceName);
|
||||
|
||||
/// 申请 USB 设备的权限。
|
||||
///
|
||||
/// @param deviceName 设备名称。
|
||||
/// @return `true` 已授权,`false` 被拒绝。
|
||||
Future<bool> requestPermission(String deviceName);
|
||||
|
||||
/// ==================== USB 状态监听 ====================
|
||||
|
||||
/// 注册 USB 设备插拔状态监听。
|
||||
///
|
||||
/// 通过 EventChannel 监听设备的连接和断开事件。
|
||||
///
|
||||
/// @param onStateChanged 状态变化回调,参数为设备名称和是否已连接。
|
||||
void setUsbStateListener(void Function(String deviceName, bool connected) onStateChanged);
|
||||
|
||||
/// 移除 USB 状态监听。
|
||||
void removeUsbStateListener();
|
||||
|
||||
/// ==================== 串口信息 ====================
|
||||
|
||||
/// 获取设备的串口数目。
|
||||
///
|
||||
/// @param deviceName 设备名称。
|
||||
/// @return 串口数目;如果为 -1,说明读取芯片型号失败。
|
||||
Future<int> getSerialCount(String deviceName);
|
||||
|
||||
/// 获取串口波特率。
|
||||
///
|
||||
/// 实际仅针对 CH9114 系列有效,其他类型设备无需调用。
|
||||
///
|
||||
/// @param deviceName 设备名称。
|
||||
/// @param serialNumber 串口号。
|
||||
/// @return 大于 0 表示串口波特率,小于 0 表示出错。
|
||||
Future<int> getSerialBaud(String deviceName, int serialNumber);
|
||||
|
||||
/// 获取芯片主频。
|
||||
///
|
||||
/// 实际仅针对 CH9114 系列有效,其他类型设备无需调用。
|
||||
///
|
||||
/// @param deviceName 设备名称。
|
||||
/// @return 芯片主频信息对象。
|
||||
Future<ChipMasterFrequency> getChipMasterFrequency(String deviceName);
|
||||
|
||||
/// 打开或关闭串口。
|
||||
///
|
||||
/// 实际仅针对 CH9114 系列有效,其他类型设备无需调用。
|
||||
///
|
||||
/// @param deviceName 设备名称。
|
||||
/// @param serialNumber 串口号。
|
||||
/// @param enable `true` 打开,`false` 关闭。
|
||||
/// @return `true` 设置成功,`false` 设置失败。
|
||||
/// @throws Ch34Exception 如果操作失败。
|
||||
Future<bool> enableSerial(String deviceName, int serialNumber, bool enable);
|
||||
|
||||
/// ==================== 串口参数设置 ====================
|
||||
|
||||
/// 设置串口参数。
|
||||
///
|
||||
/// @param deviceName 设备名称。
|
||||
/// @param serialNumber 串口号(从 0 开始)。
|
||||
/// @param parameter 串口参数配置。
|
||||
/// @return `true` 设置成功,`false` 设置失败。
|
||||
/// @throws Ch34Exception 如果参数无效或设备未打开。
|
||||
Future<bool> setSerialParameter(
|
||||
String deviceName,
|
||||
int serialNumber,
|
||||
SerialParameter parameter,
|
||||
);
|
||||
|
||||
/// ==================== 数据读写 ====================
|
||||
|
||||
/// 发送串口数据(同步发送)。
|
||||
///
|
||||
/// @param deviceName 设备名称。
|
||||
/// @param serialNumber 串口号。
|
||||
/// @param data 要发送的数据。
|
||||
/// @param timeout 超时时间(毫秒),0 表示不超时。
|
||||
/// @return 实际发送的字节数。
|
||||
/// @throws Ch34Exception 如果发送失败。
|
||||
Future<int> writeData(
|
||||
String deviceName,
|
||||
int serialNumber,
|
||||
Uint8List data, {
|
||||
int timeout = 0,
|
||||
});
|
||||
|
||||
/// 发送串口数据(异步发送)。
|
||||
///
|
||||
/// 将数据加入缓存持续发送,不返回状态和结果。
|
||||
///
|
||||
/// @param deviceName 设备名称。
|
||||
/// @param serialNumber 串口号。
|
||||
/// @param data 要发送的数据。
|
||||
/// @throws Ch34Exception 如果发送失败。
|
||||
Future<void> asyncWriteData(
|
||||
String deviceName,
|
||||
int serialNumber,
|
||||
Uint8List data,
|
||||
);
|
||||
|
||||
/// 阻塞读取串口数据。
|
||||
///
|
||||
/// @param deviceName 设备名称。
|
||||
/// @param serialNumber 串口号。
|
||||
/// @return 读取到的数据。
|
||||
/// @throws Ch34Exception 如果读取失败。
|
||||
Future<Uint8List> readData(String deviceName, int serialNumber);
|
||||
|
||||
/// 主动读取串口数据(带超时参数)。
|
||||
///
|
||||
/// 读取行为说明:
|
||||
/// - 当 vTime>0,vMin>0 时:读取将保持阻塞直到读取到第一个字符,
|
||||
/// 读到了第一个字符之后开始计时,此后若时间到了 vTime 或者时间未到
|
||||
/// 但已读够了 vMin 个字符则会返回。
|
||||
/// - 当 vTime>0,vMin=0 时:读取读到数据则立即返回,否则将为每个字符
|
||||
/// 最多等待 vTime 时间。
|
||||
/// - 当 vTime=0,vMin>0 时:读取一直阻塞,直到读到 vMin 个字符后立即返回。
|
||||
///
|
||||
/// @param deviceName 设备名称。
|
||||
/// @param serialNumber 串口号。
|
||||
/// @param vTime 等待时间(毫秒)。
|
||||
/// @param vMin 读取的最小字节数。
|
||||
/// @return 读取到的数据。
|
||||
/// @throws Ch34Exception 如果读取失败。
|
||||
Future<Uint8List> readDataWithTimeout(
|
||||
String deviceName,
|
||||
int serialNumber,
|
||||
int vTime,
|
||||
int vMin,
|
||||
);
|
||||
|
||||
/// 注册串口数据回调。
|
||||
///
|
||||
/// 注册后数据会通过 EventChannel 自动推送,不需要主动调用 [readData]。
|
||||
///
|
||||
/// @param deviceName 设备名称。
|
||||
/// @param serialNumber 串口号。
|
||||
/// @param onData 数据接收回调。
|
||||
/// @throws Ch34Exception 如果注册失败。
|
||||
Future<void> registerDataCallback(
|
||||
String deviceName,
|
||||
int serialNumber,
|
||||
void Function(Uint8List data) onData,
|
||||
);
|
||||
|
||||
/// 取消注册串口数据回调。
|
||||
///
|
||||
/// @param deviceName 设备名称。
|
||||
void removeDataCallback(String deviceName);
|
||||
|
||||
/// ==================== 连接状态 ====================
|
||||
|
||||
/// 判断 USB 设备是否已经连接。
|
||||
///
|
||||
/// @param deviceName 设备名称。
|
||||
/// @return `true` 已连接,`false` 未连接。
|
||||
Future<bool> isConnected(String deviceName);
|
||||
|
||||
/// 获取当前已经打开的设备列表。
|
||||
///
|
||||
/// @return 已打开的设备名称列表。
|
||||
Future<List<String>> getConnectedDevices();
|
||||
|
||||
/// ==================== 断开与关闭 ====================
|
||||
|
||||
/// 断开 USB 设备的连接。
|
||||
///
|
||||
/// @param deviceName 设备名称。
|
||||
Future<void> disconnect(String deviceName);
|
||||
|
||||
/// 释放资源,关闭所有的串口设备。
|
||||
Future<void> close();
|
||||
|
||||
/// ==================== GPIO 功能 ====================
|
||||
|
||||
/// 查询设备是否支持 GPIO 功能。
|
||||
///
|
||||
/// 应该在操作 GPIO 前调用。
|
||||
///
|
||||
/// @param deviceName 设备名称。
|
||||
/// @return `true` 支持,`false` 不支持。
|
||||
/// @throws Ch34Exception 如果查询失败。
|
||||
Future<bool> isSupportGpio(String deviceName);
|
||||
|
||||
/// 查询该 USB 设备的 GPIO 数目。
|
||||
///
|
||||
/// @param deviceName 设备名称。
|
||||
/// @return GPIO 数目。
|
||||
/// @throws Ch34Exception 如果查询失败。
|
||||
Future<int> queryGpioCount(String deviceName);
|
||||
|
||||
/// 查询该 USB 设备指定 GPIO 的状态。
|
||||
///
|
||||
/// @param deviceName 设备名称。
|
||||
/// @param gpioIndex GPIO 编号(从 0 开始)。
|
||||
/// @return GPIO 状态。
|
||||
/// @throws Ch34Exception 如果查询失败。
|
||||
Future<GpioStatus> queryGpioStatus(String deviceName, int gpioIndex);
|
||||
|
||||
/// 查询该 USB 设备的所有 GPIO 状态。
|
||||
///
|
||||
/// @param deviceName 设备名称。
|
||||
/// @return 全部 GPIO 状态列表。
|
||||
/// @throws Ch34Exception 如果查询失败。
|
||||
Future<List<GpioStatus>> queryAllGpioStatus(String deviceName);
|
||||
|
||||
/// 使能指定 GPIO。
|
||||
///
|
||||
/// @param deviceName 设备名称。
|
||||
/// @param gpioIndex GPIO 编号。
|
||||
/// @param enable `true` 使能,`false` 关闭。
|
||||
/// @param direction GPIO 方向。
|
||||
/// @return `true` 使能成功,`false` 使能失败。
|
||||
/// @throws Ch34Exception 如果操作失败。
|
||||
Future<bool> enableGpio(
|
||||
String deviceName,
|
||||
int gpioIndex,
|
||||
bool enable,
|
||||
GpioDirection direction,
|
||||
);
|
||||
|
||||
/// 设置指定 GPIO 的电平值。
|
||||
///
|
||||
/// @param deviceName 设备名称。
|
||||
/// @param gpioIndex GPIO 编号。
|
||||
/// @param value GPIO 电平值。
|
||||
/// @return `true` 设置成功,`false` 设置失败。
|
||||
/// @throws Ch34Exception 如果操作失败。
|
||||
Future<bool> setGpioVal(
|
||||
String deviceName,
|
||||
int gpioIndex,
|
||||
GpioValue value,
|
||||
);
|
||||
|
||||
/// 获取指定 GPIO 的电平值。
|
||||
///
|
||||
/// @param deviceName 设备名称。
|
||||
/// @param gpioIndex GPIO 编号。
|
||||
/// @return GPIO 电平值。
|
||||
/// @throws Ch34Exception 如果操作失败。
|
||||
Future<GpioValue> getGpioVal(String deviceName, int gpioIndex);
|
||||
|
||||
/// ==================== 信号控制 ====================
|
||||
|
||||
/// 设置 DTR 信号。
|
||||
///
|
||||
/// @param deviceName 设备名称。
|
||||
/// @param serialNumber 串口号。
|
||||
/// @param valid 是否有效(低电平有效)。
|
||||
/// @return `true` 设置成功,`false` 设置失败。
|
||||
/// @throws Ch34Exception 如果操作失败。
|
||||
Future<bool> setDtr(String deviceName, int serialNumber, bool valid);
|
||||
|
||||
/// 设置 RTS 信号。
|
||||
///
|
||||
/// @param deviceName 设备名称。
|
||||
/// @param serialNumber 串口号。
|
||||
/// @param valid 是否有效(低电平有效)。
|
||||
/// @return `true` 设置成功,`false` 设置失败。
|
||||
/// @throws Ch34Exception 如果操作失败。
|
||||
Future<bool> setRts(String deviceName, int serialNumber, bool valid);
|
||||
|
||||
/// 设置 Break 信号。
|
||||
///
|
||||
/// @param deviceName 设备名称。
|
||||
/// @param serialNumber 串口号。
|
||||
/// @param valid 是否有效(低电平有效)。
|
||||
/// @return `true` 设置成功,`false` 设置失败。
|
||||
/// @throws Ch34Exception 如果操作失败。
|
||||
Future<bool> setBreakSignal(String deviceName, int serialNumber, bool valid);
|
||||
|
||||
/// ==================== Modem 状态回调 ====================
|
||||
|
||||
/// 注册 Modem 控制信号状态回调。
|
||||
///
|
||||
/// @param deviceName 设备名称。
|
||||
/// @param onModemStatus Modem 状态变化回调。
|
||||
/// @throws Ch34Exception 如果注册失败。
|
||||
Future<void> registerModemStatusCallback(
|
||||
String deviceName,
|
||||
void Function(ModemStatus status) onModemStatus,
|
||||
);
|
||||
|
||||
/// 移除 Modem 状态回调。
|
||||
///
|
||||
/// @param deviceName 设备名称。
|
||||
void removeModemStatusCallback(String deviceName);
|
||||
|
||||
/// ==================== 错误查询 ====================
|
||||
|
||||
/// 查询串口错误状态。
|
||||
///
|
||||
/// @param deviceName 设备名称。
|
||||
/// @param serialNumber 串口号。
|
||||
/// @param errorType 错误类型。
|
||||
/// @return 该种错误出现的次数。
|
||||
/// @throws Ch34Exception 如果查询失败。
|
||||
Future<int> querySerialErrorCount(
|
||||
String deviceName,
|
||||
int serialNumber,
|
||||
SerialErrorType errorType,
|
||||
);
|
||||
|
||||
/// ==================== 全局配置 ====================
|
||||
|
||||
/// 设置读取超时时间。
|
||||
///
|
||||
/// 默认为 0,使用 USBRequest 异步读取;
|
||||
/// 如果不为 0,使用同步传输,超时单位为毫秒。
|
||||
/// 全局有效,应在 APP 初始化时调用。
|
||||
///
|
||||
/// @param timeout 超时时间(毫秒)。
|
||||
Future<void> setReadTimeout(int timeout);
|
||||
|
||||
/// 添加自定义硬件 VID/PID 及芯片类型。
|
||||
///
|
||||
/// 当用户修改了硬件设备的 VID 和 PID 后,需要将修改后的值添加到库中。
|
||||
///
|
||||
/// @param vid 硬件 VID。
|
||||
/// @param pid 硬件 PID。
|
||||
/// @param chipType 芯片类型(必填,如 "CH340"、"CH9102")。
|
||||
Future<void> addNewHardware(int vid, int pid, String chipType);
|
||||
|
||||
/// 设置调试模式。
|
||||
///
|
||||
/// 开启调试模式会打印日志,默认关闭。
|
||||
/// 应在 APP 初始化时调用。
|
||||
///
|
||||
/// @param enabled `true` 开启,`false` 关闭。
|
||||
Future<void> setDebug(bool enabled);
|
||||
|
||||
/// 返回当前是否处于调试模式。
|
||||
///
|
||||
/// @return `true` 处于调试模式,`false` 不处于。
|
||||
Future<bool> isDebugMode();
|
||||
}
|
||||
359
lib/src/types/ch34_types.dart
Normal file
359
lib/src/types/ch34_types.dart
Normal file
@@ -0,0 +1,359 @@
|
||||
/// CH34X 插件的所有类型和枚举定义。
|
||||
///
|
||||
/// 对应 WCH WCHUARTManager API 文档中的参数类型。
|
||||
library ch34_types;
|
||||
|
||||
/// 数据位
|
||||
enum DataBits {
|
||||
bits5(5),
|
||||
bits6(6),
|
||||
bits7(7),
|
||||
bits8(8);
|
||||
|
||||
const DataBits(this.value);
|
||||
final int value;
|
||||
|
||||
static DataBits fromValue(int value) {
|
||||
return DataBits.values.firstWhere(
|
||||
(e) => e.value == value,
|
||||
orElse: () => DataBits.bits8,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 停止位
|
||||
enum StopBits {
|
||||
one(1),
|
||||
two(2);
|
||||
|
||||
const StopBits(this.value);
|
||||
final int value;
|
||||
|
||||
static StopBits fromValue(int value) {
|
||||
return StopBits.values.firstWhere(
|
||||
(e) => e.value == value,
|
||||
orElse: () => StopBits.one,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 校验位
|
||||
enum Parity {
|
||||
none(0),
|
||||
odd(1),
|
||||
even(2),
|
||||
mark(3),
|
||||
space(4);
|
||||
|
||||
const Parity(this.value);
|
||||
final int value;
|
||||
|
||||
static Parity fromValue(int value) {
|
||||
return Parity.values.firstWhere(
|
||||
(e) => e.value == value,
|
||||
orElse: () => Parity.none,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// GPIO 方向
|
||||
enum GpioDirection {
|
||||
inDir,
|
||||
outDir;
|
||||
}
|
||||
|
||||
/// GPIO 电平值
|
||||
enum GpioValue {
|
||||
low,
|
||||
high;
|
||||
}
|
||||
|
||||
/// GPIO 状态
|
||||
class GpioStatus {
|
||||
const GpioStatus({
|
||||
required this.index,
|
||||
required this.direction,
|
||||
required this.value,
|
||||
required this.enabled,
|
||||
});
|
||||
|
||||
factory GpioStatus.fromMap(Map<dynamic, dynamic> map) {
|
||||
return GpioStatus(
|
||||
index: map['index'] as int,
|
||||
direction:
|
||||
GpioDirection.values[map['direction'] as int? ?? 0],
|
||||
value: GpioValue.values[map['value'] as int? ?? 0],
|
||||
enabled: map['enabled'] as bool? ?? false,
|
||||
);
|
||||
}
|
||||
|
||||
/// GPIO 编号(从 0 开始)
|
||||
final int index;
|
||||
|
||||
/// 方向
|
||||
final GpioDirection direction;
|
||||
|
||||
/// 电平值
|
||||
final GpioValue value;
|
||||
|
||||
/// 是否已使能
|
||||
final bool enabled;
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
'index': index,
|
||||
'direction': direction.index,
|
||||
'value': value.index,
|
||||
'enabled': enabled,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'GpioStatus(index: $index, direction: $direction, '
|
||||
'value: $value, enabled: $enabled)';
|
||||
}
|
||||
}
|
||||
|
||||
/// 串口参数
|
||||
class SerialParameter {
|
||||
const SerialParameter({
|
||||
this.baud = 115200,
|
||||
this.dataBits = DataBits.bits8,
|
||||
this.stopBits = StopBits.one,
|
||||
this.parity = Parity.none,
|
||||
this.hardwareFlowControl = false,
|
||||
});
|
||||
|
||||
factory SerialParameter.fromMap(Map<dynamic, dynamic> map) {
|
||||
return SerialParameter(
|
||||
baud: map['baud'] as int? ?? 115200,
|
||||
dataBits: DataBits.fromValue(map['dataBits'] as int? ?? 8),
|
||||
stopBits: StopBits.fromValue(map['stopBits'] as int? ?? 1),
|
||||
parity: Parity.fromValue(map['parity'] as int? ?? 0),
|
||||
hardwareFlowControl: map['hardwareFlowControl'] as bool? ?? false,
|
||||
);
|
||||
}
|
||||
|
||||
/// 波特率
|
||||
final int baud;
|
||||
|
||||
/// 数据位
|
||||
final DataBits dataBits;
|
||||
|
||||
/// 停止位
|
||||
final StopBits stopBits;
|
||||
|
||||
/// 校验位
|
||||
final Parity parity;
|
||||
|
||||
/// 硬件流控
|
||||
final bool hardwareFlowControl;
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
'baud': baud,
|
||||
'dataBits': dataBits.value,
|
||||
'stopBits': stopBits.value,
|
||||
'parity': parity.value,
|
||||
'hardwareFlowControl': hardwareFlowControl,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SerialParameter(baud: $baud, dataBits: $dataBits, '
|
||||
'stopBits: $stopBits, parity: $parity, '
|
||||
'hardwareFlowControl: $hardwareFlowControl)';
|
||||
}
|
||||
|
||||
SerialParameter copyWith({
|
||||
int? baud,
|
||||
DataBits? dataBits,
|
||||
StopBits? stopBits,
|
||||
Parity? parity,
|
||||
bool? hardwareFlowControl,
|
||||
}) {
|
||||
return SerialParameter(
|
||||
baud: baud ?? this.baud,
|
||||
dataBits: dataBits ?? this.dataBits,
|
||||
stopBits: stopBits ?? this.stopBits,
|
||||
parity: parity ?? this.parity,
|
||||
hardwareFlowControl:
|
||||
hardwareFlowControl ?? this.hardwareFlowControl,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 芯片主频信息
|
||||
///
|
||||
/// 对应 WCH API 4.12 节 `getChipMasterFrequency` 返回值。
|
||||
class ChipMasterFrequency {
|
||||
const ChipMasterFrequency({
|
||||
required this.frequency,
|
||||
required this.switchEnable,
|
||||
required this.coStatus,
|
||||
});
|
||||
|
||||
factory ChipMasterFrequency.fromMap(Map<dynamic, dynamic> map) {
|
||||
return ChipMasterFrequency(
|
||||
frequency: map['frequency'] as int? ?? 0,
|
||||
switchEnable: map['switchEnable'] as bool? ?? false,
|
||||
coStatus: map['coStatus'] as int? ?? 0,
|
||||
);
|
||||
}
|
||||
|
||||
/// 芯片主频(Hz)
|
||||
final int frequency;
|
||||
|
||||
/// 是否允许切换主频
|
||||
final bool switchEnable;
|
||||
|
||||
/// 晶振状态
|
||||
final int coStatus;
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
'frequency': frequency,
|
||||
'switchEnable': switchEnable,
|
||||
'coStatus': coStatus,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ChipMasterFrequency(frequency: $frequency Hz, '
|
||||
'switchEnable: $switchEnable, coStatus: $coStatus)';
|
||||
}
|
||||
}
|
||||
|
||||
/// Modem 状态
|
||||
class ModemStatus {
|
||||
const ModemStatus({
|
||||
this.cts = false,
|
||||
this.dsr = false,
|
||||
this.ri = false,
|
||||
this.dcd = false,
|
||||
});
|
||||
|
||||
factory ModemStatus.fromMap(Map<dynamic, dynamic> map) {
|
||||
return ModemStatus(
|
||||
cts: map['cts'] as bool? ?? false,
|
||||
dsr: map['dsr'] as bool? ?? false,
|
||||
ri: map['ri'] as bool? ?? false,
|
||||
dcd: map['dcd'] as bool? ?? false,
|
||||
);
|
||||
}
|
||||
|
||||
/// Clear To Send
|
||||
final bool cts;
|
||||
|
||||
/// Data Set Ready
|
||||
final bool dsr;
|
||||
|
||||
/// Ring Indicator
|
||||
final bool ri;
|
||||
|
||||
/// Data Carrier Detect
|
||||
final bool dcd;
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
'cts': cts,
|
||||
'dsr': dsr,
|
||||
'ri': ri,
|
||||
'dcd': dcd,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ModemStatus(CTS: $cts, DSR: $dsr, RI: $ri, DCD: $dcd)';
|
||||
}
|
||||
}
|
||||
|
||||
/// 串口错误类型
|
||||
///
|
||||
/// 对应 WCH 原生库的 `SerialErrorType` 枚举(仅包含 FRAME/PARITY/OVERRUN 三种)。
|
||||
enum SerialErrorType {
|
||||
framingError,
|
||||
parityError,
|
||||
overrunError;
|
||||
|
||||
/// 转换为原生端字符串标识。
|
||||
String toNativeString() {
|
||||
switch (this) {
|
||||
case SerialErrorType.framingError:
|
||||
return 'SerialErrorType.FramingError';
|
||||
case SerialErrorType.parityError:
|
||||
return 'SerialErrorType.ParityError';
|
||||
case SerialErrorType.overrunError:
|
||||
return 'SerialErrorType.OverrunError';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// USB 设备信息
|
||||
class UsbDeviceInfo {
|
||||
const UsbDeviceInfo({
|
||||
required this.deviceName,
|
||||
required this.productId,
|
||||
required this.vendorId,
|
||||
required this.serialCount,
|
||||
this.chipType,
|
||||
});
|
||||
|
||||
factory UsbDeviceInfo.fromMap(Map<dynamic, dynamic> map) {
|
||||
return UsbDeviceInfo(
|
||||
deviceName: map['deviceName'] as String,
|
||||
productId: map['productId'] as int,
|
||||
vendorId: map['vendorId'] as int,
|
||||
serialCount: map['serialCount'] as int? ?? 1,
|
||||
chipType: map['chipType'] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
/// 设备名称
|
||||
final String deviceName;
|
||||
|
||||
/// 产品 ID
|
||||
final int productId;
|
||||
|
||||
/// 厂商 ID
|
||||
final int vendorId;
|
||||
|
||||
/// 串口数量
|
||||
final int serialCount;
|
||||
|
||||
/// 芯片型号(如果识别)
|
||||
final String? chipType;
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
'deviceName': deviceName,
|
||||
'productId': productId,
|
||||
'vendorId': vendorId,
|
||||
'serialCount': serialCount,
|
||||
'chipType': chipType,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'UsbDeviceInfo(name: $deviceName, VID: 0x${vendorId.toRadixString(16).toUpperCase().padLeft(4, '0')}, '
|
||||
'PID: 0x${productId.toRadixString(16).toUpperCase().padLeft(4, '0')}, '
|
||||
'ports: $serialCount, chip: $chipType)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
return other is UsbDeviceInfo &&
|
||||
other.deviceName == deviceName &&
|
||||
other.productId == productId &&
|
||||
other.vendorId == vendorId;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(deviceName, productId, vendorId);
|
||||
}
|
||||
25
pubspec.yaml
Normal file
25
pubspec.yaml
Normal file
@@ -0,0 +1,25 @@
|
||||
name: ch34
|
||||
description: "CH34X"
|
||||
version: 1.1.0
|
||||
homepage:
|
||||
|
||||
environment:
|
||||
sdk: '>=3.4.3 <4.0.0'
|
||||
flutter: '>=3.3.0'
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
plugin_platform_interface: ^2.0.2
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
flutter_lints: ^3.0.0
|
||||
|
||||
flutter:
|
||||
plugin:
|
||||
platforms:
|
||||
android:
|
||||
package: com.example.ch34
|
||||
pluginClass: Ch34Plugin
|
||||
31
test/ch34_method_channel_test.dart
Normal file
31
test/ch34_method_channel_test.dart
Normal file
@@ -0,0 +1,31 @@
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:ch34/src/ch34_method_channel.dart';
|
||||
import 'package:ch34/src/ch34_platform_interface.dart';
|
||||
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
MethodChannelCh34.registerDefault();
|
||||
MethodChannelCh34 platform = Ch34Platform.instance as MethodChannelCh34;
|
||||
const MethodChannel channel = MethodChannel('ch34');
|
||||
|
||||
setUp(() {
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
||||
.setMockMethodCallHandler(
|
||||
channel,
|
||||
(MethodCall methodCall) async {
|
||||
return '42';
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
||||
.setMockMethodCallHandler(channel, null);
|
||||
});
|
||||
|
||||
test('getPlatformVersion', () async {
|
||||
expect(await platform.getPlatformVersion(), '42');
|
||||
});
|
||||
}
|
||||
205
test/ch34_test.dart
Normal file
205
test/ch34_test.dart
Normal file
@@ -0,0 +1,205 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:ch34/ch34.dart';
|
||||
import 'package:ch34/src/ch34_method_channel.dart';
|
||||
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
|
||||
|
||||
class MockCh34Platform
|
||||
with MockPlatformInterfaceMixin
|
||||
implements Ch34Platform {
|
||||
|
||||
@override
|
||||
Future<String?> getPlatformVersion() => Future.value('42');
|
||||
|
||||
@override
|
||||
Future<List<UsbDeviceInfo>> enumDevice() => Future.value([]);
|
||||
|
||||
@override
|
||||
Future<String?> getChipType(String deviceName) => Future.value('CH340');
|
||||
|
||||
@override
|
||||
Future<bool> openDevice(String deviceName) => Future.value(true);
|
||||
|
||||
@override
|
||||
Future<bool> requestPermission(String deviceName) => Future.value(true);
|
||||
|
||||
@override
|
||||
void setUsbStateListener(void Function(String, bool) onStateChanged) {}
|
||||
|
||||
@override
|
||||
void removeUsbStateListener() {}
|
||||
|
||||
@override
|
||||
Future<int> getSerialCount(String deviceName) => Future.value(1);
|
||||
|
||||
@override
|
||||
Future<int> getSerialBaud(String deviceName, int serialNumber) =>
|
||||
Future.value(115200);
|
||||
|
||||
@override
|
||||
Future<ChipMasterFrequency> getChipMasterFrequency(String deviceName) =>
|
||||
Future.value(const ChipMasterFrequency(
|
||||
frequency: 12000000,
|
||||
switchEnable: false,
|
||||
coStatus: 0,
|
||||
));
|
||||
|
||||
@override
|
||||
Future<bool> enableSerial(String deviceName, int serialNumber, bool enable) =>
|
||||
Future.value(true);
|
||||
|
||||
@override
|
||||
Future<bool> setSerialParameter(
|
||||
String deviceName,
|
||||
int serialNumber,
|
||||
SerialParameter parameter,
|
||||
) => Future.value(true);
|
||||
|
||||
@override
|
||||
Future<int> writeData(
|
||||
String deviceName,
|
||||
int serialNumber,
|
||||
Uint8List data, {
|
||||
int timeout = 0,
|
||||
}) => Future.value(data.length);
|
||||
|
||||
@override
|
||||
Future<void> asyncWriteData(
|
||||
String deviceName,
|
||||
int serialNumber,
|
||||
Uint8List data,
|
||||
) => Future.value();
|
||||
|
||||
@override
|
||||
Future<Uint8List> readData(String deviceName, int serialNumber) =>
|
||||
Future.value(Uint8List(0));
|
||||
|
||||
@override
|
||||
Future<Uint8List> readDataWithTimeout(
|
||||
String deviceName,
|
||||
int serialNumber,
|
||||
int vTime,
|
||||
int vMin,
|
||||
) => Future.value(Uint8List(0));
|
||||
|
||||
@override
|
||||
Future<void> registerDataCallback(
|
||||
String deviceName,
|
||||
int serialNumber,
|
||||
void Function(Uint8List) onData,
|
||||
) => Future.value();
|
||||
|
||||
@override
|
||||
void removeDataCallback(String deviceName) {}
|
||||
|
||||
@override
|
||||
Future<bool> isConnected(String deviceName) => Future.value(true);
|
||||
|
||||
@override
|
||||
Future<List<String>> getConnectedDevices() => Future.value([]);
|
||||
|
||||
@override
|
||||
Future<void> disconnect(String deviceName) => Future.value();
|
||||
|
||||
@override
|
||||
Future<void> close() => Future.value();
|
||||
|
||||
@override
|
||||
Future<bool> isSupportGpio(String deviceName) => Future.value(false);
|
||||
|
||||
@override
|
||||
Future<int> queryGpioCount(String deviceName) => Future.value(0);
|
||||
|
||||
@override
|
||||
Future<GpioStatus> queryGpioStatus(String deviceName, int gpioIndex) =>
|
||||
throw const Ch34Exception('Not supported');
|
||||
|
||||
@override
|
||||
Future<List<GpioStatus>> queryAllGpioStatus(String deviceName) =>
|
||||
Future.value([]);
|
||||
|
||||
@override
|
||||
Future<bool> enableGpio(
|
||||
String deviceName,
|
||||
int gpioIndex,
|
||||
bool enable,
|
||||
GpioDirection direction,
|
||||
) => Future.value(false);
|
||||
|
||||
@override
|
||||
Future<bool> setGpioVal(
|
||||
String deviceName,
|
||||
int gpioIndex,
|
||||
GpioValue value,
|
||||
) => Future.value(false);
|
||||
|
||||
@override
|
||||
Future<GpioValue> getGpioVal(String deviceName, int gpioIndex) =>
|
||||
Future.value(GpioValue.low);
|
||||
|
||||
@override
|
||||
Future<bool> setDtr(String deviceName, int serialNumber, bool valid) =>
|
||||
Future.value(true);
|
||||
|
||||
@override
|
||||
Future<bool> setRts(String deviceName, int serialNumber, bool valid) =>
|
||||
Future.value(true);
|
||||
|
||||
@override
|
||||
Future<bool> setBreakSignal(
|
||||
String deviceName, int serialNumber, bool valid) =>
|
||||
Future.value(true);
|
||||
|
||||
@override
|
||||
Future<void> registerModemStatusCallback(
|
||||
String deviceName,
|
||||
void Function(ModemStatus) onModemStatus,
|
||||
) => Future.value();
|
||||
|
||||
@override
|
||||
void removeModemStatusCallback(String deviceName) {}
|
||||
|
||||
@override
|
||||
Future<int> querySerialErrorCount(
|
||||
String deviceName,
|
||||
int serialNumber,
|
||||
SerialErrorType errorType,
|
||||
) => Future.value(0);
|
||||
|
||||
@override
|
||||
Future<void> setReadTimeout(int timeout) => Future.value();
|
||||
|
||||
@override
|
||||
Future<void> addNewHardware(int vid, int pid, String chipType) => Future.value();
|
||||
|
||||
@override
|
||||
Future<void> setDebug(bool enabled) => Future.value();
|
||||
|
||||
@override
|
||||
Future<bool> isDebugMode() => Future.value(false);
|
||||
}
|
||||
|
||||
void main() {
|
||||
MethodChannelCh34.registerDefault();
|
||||
final Ch34Platform initialPlatform = Ch34Platform.instance;
|
||||
|
||||
test('$MethodChannelCh34 is the default instance', () {
|
||||
expect(initialPlatform, isInstanceOf<MethodChannelCh34>());
|
||||
});
|
||||
|
||||
test('getPlatformVersion', () async {
|
||||
MockCh34Platform fakePlatform = MockCh34Platform();
|
||||
Ch34Platform.instance = fakePlatform;
|
||||
|
||||
expect(await Ch34Manager.getPlatformVersion(), '42');
|
||||
});
|
||||
|
||||
test('enumDevice returns empty list', () async {
|
||||
MockCh34Platform fakePlatform = MockCh34Platform();
|
||||
Ch34Platform.instance = fakePlatform;
|
||||
|
||||
final devices = await Ch34Manager.enumDevice();
|
||||
expect(devices, isEmpty);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user