This commit is contained in:
2026-04-21 12:57:33 +08:00
commit c000eb12f8
64 changed files with 7970 additions and 0 deletions

9
android/.gitignore vendored Normal file
View 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
View 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
}
}
}
}

Binary file not shown.

1
android/settings.gradle Normal file
View File

@@ -0,0 +1 @@
rootProject.name = 'ch34'

View 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>

View File

@@ -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);
}
}

View File

@@ -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);
}
});
}
}

View 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.34.38 共 36 个公开 APIgetInstance/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;
}
}

View 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);
}
}
}

View File

@@ -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);
}
});
}
}

View 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);
}
}