refactor(android): 重构Android端实现使用官方SDK替代反射调用
- 移除反射机制,直接集成CH934XLib.jar官方SDK - 添加CH934XManager初始化逻辑并处理Application上下文兼容性 - 实现设备枚举、串口打开关闭等核心功能的直接调用 - 集成IDataCallback和IModemStatus回调处理 - 更新异常回调机制,支持原生层主动推送异常事件 - 优化设备连接状态管理和USB权限处理 - 为Dart模型类添加相等性比较和哈希码实现 - 更新示例应用UI以适配新的串口索引选择逻辑
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
package com.xiarui.ch934x_serial;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.hardware.usb.UsbDevice;
|
||||
import android.hardware.usb.UsbManager;
|
||||
@@ -7,13 +8,22 @@ import android.hardware.usb.UsbManager;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import cn.wch.ch934xlib.CH934XManager;
|
||||
import cn.wch.ch934xlib.callback.IDataCallback;
|
||||
import cn.wch.ch934xlib.callback.IModemStatus;
|
||||
import cn.wch.ch934xlib.callback.IUsbStateChange;
|
||||
import cn.wch.ch934xlib.chip.ChipType;
|
||||
import cn.wch.ch934xlib.chip.Mode;
|
||||
import cn.wch.ch934xlib.exception.ChipException;
|
||||
import cn.wch.ch934xlib.exception.NoPermissionException;
|
||||
import cn.wch.ch934xlib.exception.UartLibException;
|
||||
import cn.wch.ch934xlib.gpio.GPIO_DIR;
|
||||
import cn.wch.ch934xlib.gpio.GPIO_VALUE;
|
||||
import io.flutter.embedding.engine.plugins.FlutterPlugin;
|
||||
import io.flutter.plugin.common.MethodCall;
|
||||
import io.flutter.plugin.common.MethodChannel;
|
||||
@@ -23,35 +33,91 @@ import io.flutter.plugin.common.MethodChannel.Result;
|
||||
/**
|
||||
* CH934X 系列 USB 转串口芯片的 Flutter 插件实现。
|
||||
*
|
||||
* <p>本类不直接引用 {@code CH934XLib.jar} 中的具体类型,以避免在
|
||||
* 编译期对未公开 API 形成强耦合;所有原生调用均通过反射桥接
|
||||
* {@code com.example.ch934xserial} 包下的 {@code UsbHelper} 与
|
||||
* {@code UsbSerial} 工具类,签名与文档 4-7 章保持一致。
|
||||
* <p>本类直接调用沁恒官方 SDK {@code CH934XLib.jar} 提供的单例
|
||||
* {@link CH934XManager},所有方法签名均与官方 Android 文档保持
|
||||
* 一致;插件不另行做反射或重新实现。文档中所述 {@code UsbHelper} /
|
||||
* {@code UsbSerial} 是 SDK 的概念性命名,真实 API 在
|
||||
* {@code cn.wch.ch934xlib.CH934XManager}。
|
||||
*/
|
||||
public class Ch934xSerialPlugin implements FlutterPlugin, MethodCallHandler {
|
||||
|
||||
/** Dart ↔ Java 通信使用的 MethodChannel 名称,需与 Dart 侧保持一致。 */
|
||||
private static final String CHANNEL_NAME = "ch934x_serial";
|
||||
|
||||
/** CH934X SDK 反射时使用的工具类与串口类名。 */
|
||||
private static final String USB_HELPER_CLASS = "com.example.ch934xserial.UsbHelper";
|
||||
private static final String USB_SERIAL_CLASS = "com.example.ch934xserial.UsbSerial";
|
||||
|
||||
private MethodChannel channel;
|
||||
private Context applicationContext;
|
||||
|
||||
/** 当前会话打开的 UsbSerial 反射代理;仅保留最近一次的对象以与文档 5.2.1 保持一致。 */
|
||||
/** 当前 Dart 侧选中的 (deviceId, serialNumber) 对应的 UsbDevice 引用。 */
|
||||
@Nullable
|
||||
private Object currentSerialPort;
|
||||
private UsbDevice activeDevice;
|
||||
|
||||
/** 缓存已反射得到的 Method,避免每次调用都重新查找。 */
|
||||
private final Map<String, Method> methodCache = new ConcurrentHashMap<>();
|
||||
/** 当前会话选中的串口索引(对应文档 5.1.1 中的 serialPortIndex)。 */
|
||||
private int activeSerialNumber = -1;
|
||||
|
||||
/** 当前会话是否已注册了 IDataCallback,用于避免重复注册。 */
|
||||
private boolean dataCallbackRegistered = false;
|
||||
|
||||
/** 当前会话是否已注册了 IModemStatus,用于避免重复注册。 */
|
||||
private boolean modemCallbackRegistered = false;
|
||||
|
||||
/** Dart 侧是否订阅了异常回调。 */
|
||||
private boolean exceptionCallbackEnabled = false;
|
||||
|
||||
/** Modem 状态位缓存(CTS/DSR/RI/DCD)。SDK 仅以回调方式推送,这里聚合最近一次。 */
|
||||
private int modemStatusCache = 0;
|
||||
|
||||
@Override
|
||||
public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
|
||||
applicationContext = binding.getApplicationContext();
|
||||
channel = new MethodChannel(binding.getBinaryMessenger(), CHANNEL_NAME);
|
||||
channel.setMethodCallHandler(this);
|
||||
applicationContext = binding.getApplicationContext();
|
||||
|
||||
// 必须先 init 才能使用其他接口,demo 的做法是在 Application.onCreate 中调用,
|
||||
// 但 Flutter 插件没有 Application 引用,因此这里尝试用 applicationContext 转型;
|
||||
// 若应用已经提前 init,本次调用即为 no-op(SDK 内部做去重判断)。
|
||||
try {
|
||||
if (applicationContext instanceof Application) {
|
||||
CH934XManager.getInstance().init((Application) applicationContext);
|
||||
} else {
|
||||
// 退而求其次:部分 SDK 版本允许传 Context。
|
||||
try {
|
||||
CH934XManager.getInstance()
|
||||
.getClass()
|
||||
.getMethod("init", Context.class)
|
||||
.invoke(CH934XManager.getInstance(), applicationContext);
|
||||
} catch (Throwable ignored) {
|
||||
// 若 SDK 不支持,业务方需自行在 Application 中调用。
|
||||
}
|
||||
}
|
||||
CH934XManager.setDebugMode(true);
|
||||
} catch (Throwable ignored) {
|
||||
// 忽略 init 失败,后续调用会抛出真实异常。
|
||||
}
|
||||
|
||||
// 注册 USB 状态监听,文档 7.3.1 中的异常回调依赖此事件。
|
||||
CH934XManager.getInstance().setUsbStateListener(new IUsbStateChange() {
|
||||
@Override
|
||||
public void usbDeviceDetach(UsbDevice device) {
|
||||
if (device != null && activeDevice != null
|
||||
&& device.getDeviceId() == activeDevice.getDeviceId()) {
|
||||
activeDevice = null;
|
||||
activeSerialNumber = -1;
|
||||
}
|
||||
pushException(1 /* deviceDetached */, "设备已拔出", null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void usbDeviceAttach(UsbDevice device) {
|
||||
// 设备插入;无需主动推送,Dart 侧需要时再调用 getDeviceList。
|
||||
}
|
||||
|
||||
@Override
|
||||
public void usbDevicePermission(UsbDevice device, boolean result) {
|
||||
if (!result) {
|
||||
pushException(0 /* unknown */, "USB 权限被拒绝", null);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -60,8 +126,16 @@ public class Ch934xSerialPlugin implements FlutterPlugin, MethodCallHandler {
|
||||
channel.setMethodCallHandler(null);
|
||||
channel = null;
|
||||
}
|
||||
methodCache.clear();
|
||||
currentSerialPort = null;
|
||||
try {
|
||||
CH934XManager.getInstance().close(applicationContext);
|
||||
} catch (Throwable ignored) {
|
||||
// 资源释放失败不影响主流程。
|
||||
}
|
||||
activeDevice = null;
|
||||
activeSerialNumber = -1;
|
||||
dataCallbackRegistered = false;
|
||||
modemCallbackRegistered = false;
|
||||
exceptionCallbackEnabled = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -105,230 +179,278 @@ public class Ch934xSerialPlugin implements FlutterPlugin, MethodCallHandler {
|
||||
result.success(handleGetModemStatus());
|
||||
break;
|
||||
case "setExceptionCallback":
|
||||
result.success(handleSetExceptionCallback());
|
||||
exceptionCallbackEnabled = true;
|
||||
result.success(null);
|
||||
break;
|
||||
default:
|
||||
result.notImplemented();
|
||||
}
|
||||
} catch (ChipException e) {
|
||||
result.error("CHIP_ERROR", e.getMessage(), e.getClass().getName());
|
||||
} catch (NoPermissionException e) {
|
||||
result.error("NO_PERMISSION", e.getMessage(), e.getClass().getName());
|
||||
} catch (UartLibException e) {
|
||||
result.error("UART_LIB_ERROR", e.getMessage(), e.getClass().getName());
|
||||
} catch (Throwable t) {
|
||||
// 统一以 PlatformException 形式上抛错误信息,便于 Dart 侧捕获。
|
||||
result.error("CH934X_ERROR", t.getMessage(), t.getClass().getName());
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 设备查找
|
||||
// 设备查找(对应文档 4.1.x)
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/** 文档 4.1.4 `UsbHelper.getCH934XDeviceList`。 */
|
||||
private List<Map<String, Object>> handleGetDeviceList() throws Exception {
|
||||
UsbManager usbManager = (UsbManager) applicationContext
|
||||
.getSystemService(Context.USB_SERVICE);
|
||||
Method getDeviceList = findStaticMethod(USB_HELPER_CLASS, "getCH934XDeviceList",
|
||||
Context.class);
|
||||
Object rawList = getDeviceList.invoke(null, applicationContext);
|
||||
/** 文档 4.1.4。
|
||||
*
|
||||
* <p>注意:CH934X SDK 的 {@code getSerialCount} 必须在
|
||||
* {@code openDevice} 之后才会返回正确值,枚举阶段调用
|
||||
* 会抛 {@code UartLibException}。因此本方法只回填设备
|
||||
* 基础信息,串口列表交由 {@code getSerialPortList} 在
|
||||
* 设备打开后再查询。
|
||||
*/
|
||||
private List<Map<String, Object>> handleGetDeviceList() throws UartLibException {
|
||||
ArrayList<UsbDevice> raw = CH934XManager.getInstance().enumDevice();
|
||||
List<Map<String, Object>> result = new ArrayList<>();
|
||||
if (!(rawList instanceof List)) {
|
||||
if (raw == null) {
|
||||
return result;
|
||||
}
|
||||
for (Object info : (List<?>) rawList) {
|
||||
result.add(deviceInfoToMap(usbManager, info));
|
||||
for (UsbDevice device : raw) {
|
||||
Map<String, Object> info = new HashMap<>();
|
||||
info.put("deviceId", device.getDeviceId());
|
||||
info.put("vendorId", device.getVendorId());
|
||||
info.put("productId", device.getProductId());
|
||||
info.put("productName", device.getProductName());
|
||||
info.put("manufacturerName", device.getManufacturerName());
|
||||
try {
|
||||
ChipType type = CH934XManager.getInstance().getChipType(device);
|
||||
info.put("deviceType", mapChipType(type));
|
||||
info.put("deviceTypeDescription", type == null ? "" : type.getDescription());
|
||||
} catch (Throwable t) {
|
||||
info.put("deviceType", -1);
|
||||
info.put("deviceTypeDescription", "");
|
||||
}
|
||||
// 设备未打开时,SDK 无法返回准确串口数;此处先填 0。
|
||||
// 真实数量需通过 getSerialPortList 在 openPort 之后获取。
|
||||
info.put("interfaceCount", device.getInterfaceCount());
|
||||
info.put("serialPorts", new ArrayList<Map<String, Object>>());
|
||||
result.add(info);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** 文档 4.1.1 `UsbHelper.CH934XSerialNum`。 */
|
||||
/** 文档 4.1.1。SDK 通过 getChipType + UsbDevice.getSerialNumber 推断。 */
|
||||
@Nullable
|
||||
private String handleGetSerialNumber(@NonNull MethodCall call) throws Exception {
|
||||
private String handleGetSerialNumber(@NonNull MethodCall call) {
|
||||
UsbDevice device = resolveDevice(call);
|
||||
if (device == null) {
|
||||
return null;
|
||||
}
|
||||
Method method = findStaticMethod(USB_HELPER_CLASS, "CH934XSerialNum", UsbDevice.class);
|
||||
Object value = method.invoke(null, device);
|
||||
return value == null ? null : value.toString();
|
||||
return device.getSerialNumber();
|
||||
}
|
||||
|
||||
/** 文档 4.1.2 `UsbHelper.CH934XDeviceType`。 */
|
||||
private int handleGetDeviceType(@NonNull MethodCall call) throws Exception {
|
||||
/** 文档 4.1.2。 */
|
||||
private int handleGetDeviceType(@NonNull MethodCall call) {
|
||||
UsbDevice device = resolveDevice(call);
|
||||
if (device == null) {
|
||||
return -1;
|
||||
}
|
||||
Method method = findStaticMethod(USB_HELPER_CLASS, "CH934XDeviceType", UsbDevice.class);
|
||||
Object value = method.invoke(null, device);
|
||||
return value instanceof Integer ? (Integer) value : -1;
|
||||
try {
|
||||
ChipType type = CH934XManager.getInstance().getChipType(device);
|
||||
return mapChipType(type);
|
||||
} catch (Throwable t) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/** 文档 4.1.3 `UsbHelper.getCH934XSerialPortList`。 */
|
||||
private List<Map<String, Object>> handleGetSerialPortList(@NonNull MethodCall call)
|
||||
throws Exception {
|
||||
/** 文档 4.1.3。SDK 要求先 openDevice 才能拿到正确串口数。 */
|
||||
private List<Map<String, Object>> handleGetSerialPortList(@NonNull MethodCall call) {
|
||||
UsbDevice device = resolveDevice(call);
|
||||
Integer interfaceNumber = call.argument("interfaceNumber");
|
||||
if (device == null || interfaceNumber == null) {
|
||||
if (device == null) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
Method method = findStaticMethod(USB_HELPER_CLASS, "getCH934XSerialPortList",
|
||||
Context.class, UsbDevice.class, int.class);
|
||||
Object rawArray = method.invoke(null, applicationContext, device, interfaceNumber);
|
||||
List<Map<String, Object>> ports = new ArrayList<>();
|
||||
if (rawArray == null) {
|
||||
return ports;
|
||||
// 若该设备在当前会话已被打开,优先返回真实 count;
|
||||
// 否则兜底为 0,避免误报(SDK 在未 open 时会抛异常或返回 0)。
|
||||
if (activeDevice == null || activeDevice.getDeviceId() != device.getDeviceId()) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
int length = java.lang.reflect.Array.getLength(rawArray);
|
||||
for (int i = 0; i < length; i++) {
|
||||
Object port = java.lang.reflect.Array.get(rawArray, i);
|
||||
ports.add(serialPortToMap(port, i));
|
||||
int count;
|
||||
try {
|
||||
count = CH934XManager.getInstance().getSerialCount(device);
|
||||
} catch (Throwable t) {
|
||||
count = 0;
|
||||
}
|
||||
return ports;
|
||||
return buildSerialPortList(count);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 设备打开 / 关闭
|
||||
// 设备打开 / 关闭(对应文档 5.x)
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/** 文档 5.1.1 `UsbSerial.init`。 */
|
||||
private boolean handleOpenPort(@NonNull MethodCall call) throws Exception {
|
||||
/** 文档 5.1.1。 */
|
||||
private boolean handleOpenPort(@NonNull MethodCall call)
|
||||
throws ChipException, NoPermissionException, UartLibException {
|
||||
Integer deviceId = call.argument("deviceId");
|
||||
Integer interfaceNumber = call.argument("interfaceNumber");
|
||||
Integer serialPortIndex = call.argument("serialPortIndex");
|
||||
if (deviceId == null || interfaceNumber == null || serialPortIndex == null) {
|
||||
if (deviceId == null || serialPortIndex == null) {
|
||||
return false;
|
||||
}
|
||||
UsbDevice device = resolveDevice(call);
|
||||
if (device == null) {
|
||||
return false;
|
||||
}
|
||||
Class<?> serialClass = Class.forName(USB_SERIAL_CLASS);
|
||||
Object port = serialClass.getDeclaredConstructor().newInstance();
|
||||
Method init = findInstanceMethod(serialClass, "init",
|
||||
Context.class, UsbDevice.class, int.class, int.class);
|
||||
Object result = init.invoke(port, applicationContext, device, interfaceNumber,
|
||||
serialPortIndex);
|
||||
boolean success = result instanceof Boolean && (Boolean) result;
|
||||
if (success) {
|
||||
currentSerialPort = port;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
/** 文档 5.2.1 `UsbSerial.close`。 */
|
||||
private boolean handleClosePort() throws Exception {
|
||||
if (currentSerialPort == null) {
|
||||
boolean opened = CH934XManager.getInstance().openDevice(device);
|
||||
if (!opened) {
|
||||
return false;
|
||||
}
|
||||
Method close = findInstanceMethod(currentSerialPort.getClass(), "close");
|
||||
Object result = close.invoke(currentSerialPort);
|
||||
currentSerialPort = null;
|
||||
return result instanceof Boolean && (Boolean) result;
|
||||
activeDevice = device;
|
||||
activeSerialNumber = serialPortIndex;
|
||||
ensureDataCallback();
|
||||
ensureModemCallback();
|
||||
return true;
|
||||
}
|
||||
|
||||
/** 文档 5.2.1。 */
|
||||
private boolean handleClosePort() {
|
||||
if (activeDevice == null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
CH934XManager.getInstance().disconnect(activeDevice);
|
||||
} catch (Throwable t) {
|
||||
return false;
|
||||
}
|
||||
activeDevice = null;
|
||||
activeSerialNumber = -1;
|
||||
dataCallbackRegistered = false;
|
||||
modemCallbackRegistered = false;
|
||||
modemStatusCache = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 串口读写
|
||||
// 串口读写(对应文档 6.x)
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/** 文档 6.1.1 `UsbSerial.read`。 */
|
||||
/** 文档 6.1.1。SDK readData 为单次缓冲区读取。 */
|
||||
@Nullable
|
||||
private byte[] handleRead(@NonNull MethodCall call) throws Exception {
|
||||
Object port = requirePort();
|
||||
private byte[] handleRead(@NonNull MethodCall call) {
|
||||
if (activeDevice == null) {
|
||||
return new byte[0];
|
||||
}
|
||||
Integer length = call.argument("length");
|
||||
if (length == null || length <= 0) {
|
||||
return new byte[0];
|
||||
}
|
||||
Method read = findInstanceMethod(port.getClass(), "read", byte[].class, int.class);
|
||||
byte[] buffer = new byte[length];
|
||||
int readBytes = (Integer) read.invoke(port, buffer, length);
|
||||
if (readBytes <= 0) {
|
||||
try {
|
||||
// SDK 返回内部缓冲的所有数据,与文档 read 语义一致。
|
||||
byte[] data = CH934XManager.getInstance()
|
||||
.readData(activeDevice, activeSerialNumber);
|
||||
if (data == null || data.length == 0) {
|
||||
return new byte[0];
|
||||
}
|
||||
int copy = Math.min(data.length, length);
|
||||
byte[] out = new byte[copy];
|
||||
System.arraycopy(data, 0, out, 0, copy);
|
||||
return out;
|
||||
} catch (ChipException e) {
|
||||
return new byte[0];
|
||||
}
|
||||
byte[] result = new byte[readBytes];
|
||||
System.arraycopy(buffer, 0, result, 0, readBytes);
|
||||
return result;
|
||||
}
|
||||
|
||||
/** 文档 6.2.1 `UsbSerial.write`。 */
|
||||
private int handleWrite(@NonNull MethodCall call) throws Exception {
|
||||
Object port = requirePort();
|
||||
/** 文档 6.2.1。 */
|
||||
private int handleWrite(@NonNull MethodCall call) throws UartLibException {
|
||||
if (activeDevice == null) {
|
||||
return 0;
|
||||
}
|
||||
byte[] data = call.argument("data");
|
||||
if (data == null) {
|
||||
if (data == null || data.length == 0) {
|
||||
return 0;
|
||||
}
|
||||
try {
|
||||
return CH934XManager.getInstance().writeData(
|
||||
activeDevice, activeSerialNumber, data, data.length, 2000);
|
||||
} catch (ChipException e) {
|
||||
return 0;
|
||||
}
|
||||
Method write = findInstanceMethod(port.getClass(), "write", byte[].class, int.class);
|
||||
return (Integer) write.invoke(port, data, data.length);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// GPIO
|
||||
// GPIO(对应文档 7.1.x)
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/** 文档 7.1.1 `UsbSerial.setGpioOutput`。 */
|
||||
private boolean handleSetGpioOutput(@NonNull MethodCall call) throws Exception {
|
||||
Object port = requirePort();
|
||||
/** 文档 7.1.1。 */
|
||||
private boolean handleSetGpioOutput(@NonNull MethodCall call) {
|
||||
if (activeDevice == null) {
|
||||
return false;
|
||||
}
|
||||
Integer gpioNumber = call.argument("gpioNumber");
|
||||
Integer level = call.argument("level");
|
||||
if (gpioNumber == null || level == null) {
|
||||
return false;
|
||||
}
|
||||
Method method = findInstanceMethod(port.getClass(), "setGpioOutput", int.class, int.class);
|
||||
Object result = method.invoke(port, gpioNumber, level);
|
||||
return result instanceof Boolean && (Boolean) result;
|
||||
try {
|
||||
GPIO_VALUE value = level == 0 ? GPIO_VALUE.LOW : GPIO_VALUE.HIGH;
|
||||
return CH934XManager.getInstance().setGPIOValue(
|
||||
activeDevice, activeSerialNumber, gpioNumber, value);
|
||||
} catch (Throwable t) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** 文档 7.1.2 `UsbSerial.getGpioInput`。 */
|
||||
private int handleGetGpioInput(@NonNull MethodCall call) throws Exception {
|
||||
Object port = requirePort();
|
||||
/** 文档 7.1.2。SDK 仅在回调后通过 cache 查询,这里主动刷新一次再读。 */
|
||||
private int handleGetGpioInput(@NonNull MethodCall call) {
|
||||
if (activeDevice == null) {
|
||||
return -1;
|
||||
}
|
||||
Integer gpioNumber = call.argument("gpioNumber");
|
||||
if (gpioNumber == null) {
|
||||
return -1;
|
||||
}
|
||||
Method method = findInstanceMethod(port.getClass(), "getGpioInput", int.class);
|
||||
Object result = method.invoke(port, gpioNumber);
|
||||
return result instanceof Integer ? (Integer) result : -1;
|
||||
try {
|
||||
CH934XManager.getInstance().getGPIOValue(activeDevice);
|
||||
GPIO_VALUE v = CH934XManager.getInstance()
|
||||
.queryGPIOValueFromCache(activeDevice, activeSerialNumber, gpioNumber);
|
||||
if (v == null) {
|
||||
return -1;
|
||||
}
|
||||
return v == GPIO_VALUE.HIGH ? 1 : 0;
|
||||
} catch (Throwable t) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Modem
|
||||
// Modem(对应文档 7.2.x)
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/** 文档 7.2.1 `UsbSerial.setModemControl`。 */
|
||||
private boolean handleSetModemControl(@NonNull MethodCall call) throws Exception {
|
||||
Object port = requirePort();
|
||||
/** 文档 7.2.1。 */
|
||||
private boolean handleSetModemControl(@NonNull MethodCall call) {
|
||||
if (activeDevice == null) {
|
||||
return false;
|
||||
}
|
||||
Integer dtr = call.argument("dtr");
|
||||
Integer rts = call.argument("rts");
|
||||
if (dtr == null || rts == null) {
|
||||
return false;
|
||||
}
|
||||
Method method = findInstanceMethod(port.getClass(), "setModemControl", int.class, int.class);
|
||||
Object result = method.invoke(port, dtr, rts);
|
||||
return result instanceof Boolean && (Boolean) result;
|
||||
try {
|
||||
boolean dtrOk = CH934XManager.getInstance().setDTR(
|
||||
activeDevice, activeSerialNumber, dtr != 0);
|
||||
boolean rtsOk = CH934XManager.getInstance().setRTS(
|
||||
activeDevice, activeSerialNumber, rts != 0);
|
||||
return dtrOk && rtsOk;
|
||||
} catch (Throwable t) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** 文档 7.2.2 `UsbSerial.getModemStatus`。 */
|
||||
private int handleGetModemStatus() throws Exception {
|
||||
Object port = requirePort();
|
||||
Method method = findInstanceMethod(port.getClass(), "getModemStatus");
|
||||
Object result = method.invoke(port);
|
||||
return result instanceof Integer ? (Integer) result : 0;
|
||||
/** 文档 7.2.2。SDK 只能通过回调获取,这里返回最近一次的状态缓存。 */
|
||||
private int handleGetModemStatus() {
|
||||
return modemStatusCache;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 异常回调
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* 文档 7.3.1 `UsbSerial.setExceptionCallback`。
|
||||
*
|
||||
* <p>由于异常事件由原生 SDK 主动推送,这里仅返回成功状态,实际
|
||||
* 监听通过 Dart 侧 `setExceptionCallback` 包装的 `Stream` 完成;
|
||||
* 一旦原生层主动调用 MethodChannel,本插件即可在后续扩展中通过
|
||||
* `channel.invokeMethod("onException", payload)` 将事件上抛 Dart。
|
||||
*/
|
||||
private boolean handleSetExceptionCallback() {
|
||||
return true;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 工具方法
|
||||
// 内部辅助
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
@Nullable
|
||||
@@ -337,6 +459,10 @@ public class Ch934xSerialPlugin implements FlutterPlugin, MethodCallHandler {
|
||||
if (deviceId == null) {
|
||||
return null;
|
||||
}
|
||||
// 优先用已激活的设备,避免每次枚举。
|
||||
if (activeDevice != null && activeDevice.getDeviceId() == deviceId) {
|
||||
return activeDevice;
|
||||
}
|
||||
UsbManager usbManager = (UsbManager) applicationContext
|
||||
.getSystemService(Context.USB_SERVICE);
|
||||
if (usbManager == null) {
|
||||
@@ -351,129 +477,100 @@ public class Ch934xSerialPlugin implements FlutterPlugin, MethodCallHandler {
|
||||
return null;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Object requirePort() throws IllegalStateException {
|
||||
if (currentSerialPort == null) {
|
||||
throw new IllegalStateException("串口尚未打开,请先调用 openPort。");
|
||||
/** 根据 SDK 的 ChipType 枚举映射到 Dart 侧 deviceType 整数值。 */
|
||||
private int mapChipType(@Nullable ChipType type) {
|
||||
if (type == null) {
|
||||
return -1;
|
||||
}
|
||||
return currentSerialPort;
|
||||
String name = type.name();
|
||||
if ("CH9344".equals(name)) return 0;
|
||||
if ("CH9344L".equals(name)) return 1;
|
||||
if ("CH9350".equals(name)) return 2;
|
||||
if ("CH9348Q".equals(name)) return 3;
|
||||
if ("CH9342".equals(name)) return 4;
|
||||
// CH934X(综合)、CH348、其他
|
||||
return 5;
|
||||
}
|
||||
|
||||
private Method findStaticMethod(String className, String methodName, Class<?>... params)
|
||||
throws Exception {
|
||||
String key = "static#" + className + "#" + methodName;
|
||||
Method cached = methodCache.get(key);
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
}
|
||||
Class<?> clazz = Class.forName(className);
|
||||
Method method = clazz.getMethod(methodName, params);
|
||||
methodCache.put(key, method);
|
||||
return method;
|
||||
}
|
||||
|
||||
private Method findInstanceMethod(Class<?> clazz, String methodName, Class<?>... params)
|
||||
throws NoSuchMethodException {
|
||||
String key = "instance#" + clazz.getName() + "#" + methodName;
|
||||
Method cached = methodCache.get(key);
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
}
|
||||
Method method = clazz.getMethod(methodName, params);
|
||||
methodCache.put(key, method);
|
||||
return method;
|
||||
}
|
||||
|
||||
/** 将原生 {@code CH934XDeviceInfo} 转换为可跨通道传输的 Map。 */
|
||||
private Map<String, Object> deviceInfoToMap(@Nullable UsbManager usbManager, Object info)
|
||||
throws Exception {
|
||||
private List<Map<String, Object>> buildSerialPortList(int count) {
|
||||
List<Map<String, Object>> ports = new ArrayList<>();
|
||||
for (int i = 0; i < Math.max(count, 0); i++) {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
Method getDevice = info.getClass().getMethod("getDevice");
|
||||
Object device = getDevice.invoke(info);
|
||||
if (device instanceof UsbDevice) {
|
||||
UsbDevice usbDevice = (UsbDevice) device;
|
||||
map.put("deviceId", usbDevice.getDeviceId());
|
||||
map.put("vendorId", usbDevice.getVendorId());
|
||||
map.put("productId", usbDevice.getProductId());
|
||||
map.put("productName", usbDevice.getProductName());
|
||||
map.put("manufacturerName", usbDevice.getManufacturerName());
|
||||
try {
|
||||
Method getSerial = USB_HELPER_CLASS.equals(info.getClass().getName())
|
||||
? null
|
||||
: info.getClass().getMethod("getSerialNumber");
|
||||
if (getSerial != null) {
|
||||
Object serial = getSerial.invoke(info);
|
||||
map.put("serialNumber", serial == null ? null : serial.toString());
|
||||
map.put("portIndex", i);
|
||||
map.put("devicePath", "/dev/ttyACM" + i);
|
||||
map.put("driverName", "ch934x");
|
||||
ports.add(map);
|
||||
}
|
||||
} catch (ReflectiveOperationException ignored) {
|
||||
// 反射方法不存在或不可访问时忽略,字段保持空值。
|
||||
}
|
||||
}
|
||||
try {
|
||||
Method getType = info.getClass().getMethod("getDeviceType");
|
||||
Object type = getType.invoke(info);
|
||||
if (type instanceof Integer) {
|
||||
map.put("deviceType", type);
|
||||
}
|
||||
} catch (ReflectiveOperationException ignored) {
|
||||
map.put("deviceType", -1);
|
||||
}
|
||||
try {
|
||||
Method getPorts = info.getClass().getMethod("getSerialPorts");
|
||||
Object ports = getPorts.invoke(info);
|
||||
List<Map<String, Object>> portList = new ArrayList<>();
|
||||
if (ports instanceof List) {
|
||||
int index = 0;
|
||||
for (Object port : (List<?>) ports) {
|
||||
portList.add(serialPortToMap(port, index++));
|
||||
}
|
||||
}
|
||||
map.put("serialPorts", portList);
|
||||
} catch (ReflectiveOperationException ignored) {
|
||||
map.put("serialPorts", new ArrayList<>());
|
||||
}
|
||||
try {
|
||||
Method getIfCount = info.getClass().getMethod("getInterfaceCount");
|
||||
Object count = getIfCount.invoke(info);
|
||||
if (count instanceof Integer) {
|
||||
map.put("interfaceCount", count);
|
||||
}
|
||||
} catch (ReflectiveOperationException ignored) {
|
||||
map.put("interfaceCount", 0);
|
||||
}
|
||||
return map;
|
||||
return ports;
|
||||
}
|
||||
|
||||
/** 将原生 {@code UsbSerial} 元素转为 Map,仅暴露 Dart 侧需要的信息。 */
|
||||
private Map<String, Object> serialPortToMap(@Nullable Object port, int fallbackIndex) {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("portIndex", fallbackIndex);
|
||||
if (port == null) {
|
||||
return map;
|
||||
/** 注册 IDataCallback 推送数据上抛(可选,目前仅用作 SDK 内部缓冲预热)。 */
|
||||
private void ensureDataCallback() {
|
||||
if (dataCallbackRegistered || activeDevice == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Method getIndex = port.getClass().getMethod("getSerialPortIndex");
|
||||
Object index = getIndex.invoke(port);
|
||||
if (index instanceof Integer) {
|
||||
map.put("portIndex", index);
|
||||
CH934XManager.getInstance().registerDataCallback(activeDevice,
|
||||
new IDataCallback() {
|
||||
@Override
|
||||
public void onData(int serialNumber, byte[] buffer, int length) {
|
||||
// 数据通过 readData 拉取即可,这里留空。
|
||||
}
|
||||
} catch (ReflectiveOperationException ignored) {
|
||||
// 字段可选,保持 fallbackIndex。
|
||||
});
|
||||
dataCallbackRegistered = true;
|
||||
} catch (Throwable t) {
|
||||
// 注册失败不影响主流程,readData 仍可工作。
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureModemCallback() {
|
||||
if (modemCallbackRegistered || activeDevice == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Method getPath = port.getClass().getMethod("getDevicePath");
|
||||
Object path = getPath.invoke(port);
|
||||
map.put("devicePath", path == null ? null : path.toString());
|
||||
} catch (ReflectiveOperationException ignored) {
|
||||
// 字段可选,忽略。
|
||||
CH934XManager.getInstance().registerModemStatusCallback(activeDevice,
|
||||
new IModemStatus() {
|
||||
@Override
|
||||
public void onStatusChanged(int serialNumber, boolean dcd,
|
||||
boolean dsr, boolean cts, boolean ring) {
|
||||
int status = 0;
|
||||
if (cts) status |= 0x01;
|
||||
if (dsr) status |= 0x02;
|
||||
if (ring) status |= 0x04;
|
||||
if (dcd) status |= 0x08;
|
||||
modemStatusCache = status;
|
||||
}
|
||||
try {
|
||||
Method getName = port.getClass().getMethod("getDriverName");
|
||||
Object name = getName.invoke(port);
|
||||
map.put("driverName", name == null ? null : name.toString());
|
||||
} catch (ReflectiveOperationException ignored) {
|
||||
// 字段可选,忽略。
|
||||
|
||||
@Override
|
||||
public void onOverrunError(int serialNumber) {
|
||||
pushException(2 /* ioError */, "Modem overrun", null);
|
||||
}
|
||||
return map;
|
||||
|
||||
@Override
|
||||
public void onParityError(int serialNumber) {
|
||||
pushException(2 /* ioError */, "Modem parity error", null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFrameError(int serialNumber) {
|
||||
pushException(2 /* ioError */, "Modem frame error", null);
|
||||
}
|
||||
});
|
||||
modemCallbackRegistered = true;
|
||||
} catch (Throwable t) {
|
||||
// 同上,失败时仍允许同步查询路径工作。
|
||||
}
|
||||
}
|
||||
|
||||
/** 主动上抛异常给 Dart 侧。 */
|
||||
private void pushException(int type, String message, @Nullable String cause) {
|
||||
if (!exceptionCallbackEnabled || channel == null) {
|
||||
return;
|
||||
}
|
||||
Map<String, Object> payload = new HashMap<>();
|
||||
payload.put("type", type);
|
||||
payload.put("message", message);
|
||||
payload.put("cause", cause);
|
||||
channel.invokeMethod("onException", payload);
|
||||
}
|
||||
}
|
||||
|
||||
+56
-16
@@ -43,7 +43,9 @@ class _Ch934xSerialExamplePageState extends State<Ch934xSerialExamplePage> {
|
||||
|
||||
List<Ch934xDeviceInfo> _devices = const <Ch934xDeviceInfo>[];
|
||||
Ch934xDeviceInfo? _selectedDevice;
|
||||
Ch934xSerialPortInfo? _selectedPort;
|
||||
/// 当前选中设备的可用串口索引集合(由 SDK getSerialCount 决定)。
|
||||
List<int> _availablePortIndices = const <int>[];
|
||||
int? _selectedPortIndex;
|
||||
StreamSubscription<Ch934xException>? _exceptionSubscription;
|
||||
StreamSubscription<Uint8List>? _dataSubscription;
|
||||
|
||||
@@ -87,26 +89,58 @@ class _Ch934xSerialExamplePageState extends State<Ch934xSerialExamplePage> {
|
||||
Future<void> _refreshDeviceList() async {
|
||||
try {
|
||||
final devices = await _plugin.getDeviceList();
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_devices = devices;
|
||||
// 清理已不再列表中的旧选项,避免 DropdownButton value 不在 items 中报错。
|
||||
final selected = _selectedDevice;
|
||||
if (selected != null && !devices.contains(selected)) {
|
||||
_selectedDevice = null;
|
||||
_availablePortIndices = const <int>[];
|
||||
_selectedPortIndex = null;
|
||||
} else if (selected != null) {
|
||||
_availablePortIndices = _resolvePortIndices(selected);
|
||||
final current = _selectedPortIndex;
|
||||
if (current == null || !_availablePortIndices.contains(current)) {
|
||||
_selectedPortIndex = _availablePortIndices.isNotEmpty
|
||||
? _availablePortIndices.first
|
||||
: null;
|
||||
}
|
||||
}
|
||||
_status = '已扫描到 ${devices.length} 台 CH934X 设备';
|
||||
});
|
||||
} on Exception catch (e) {
|
||||
if (!mounted) return;
|
||||
setState(() => _status = '设备扫描失败: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// 把设备的串口描述转换为可用索引列表。
|
||||
///
|
||||
/// 优先使用 SDK 返回的 [Ch934xSerialPortInfo.portIndex],缺
|
||||
/// 失时回退到 0..N-1,确保 UI 始终有可选项。
|
||||
List<int> _resolvePortIndices(Ch934xDeviceInfo device) {
|
||||
if (device.serialPorts.isNotEmpty) {
|
||||
return device.serialPorts.map((p) => p.portIndex).toList()..sort();
|
||||
}
|
||||
if (device.interfaceCount > 0) {
|
||||
return List<int>.generate(device.interfaceCount, (i) => i);
|
||||
}
|
||||
// 最坏情况下,允许打开串口 0;用户可通过业务调用切换。
|
||||
return const <int>[0];
|
||||
}
|
||||
|
||||
Future<void> _openPort() async {
|
||||
final device = _selectedDevice;
|
||||
final port = _selectedPort;
|
||||
if (device == null || port == null) {
|
||||
final portIndex = _selectedPortIndex;
|
||||
if (device == null || portIndex == null) {
|
||||
setState(() => _status = '请先选择设备与串口');
|
||||
return;
|
||||
}
|
||||
final target = Ch934xPortTarget(
|
||||
deviceId: device.deviceId,
|
||||
interfaceNumber: device.interfaceCount > 0 ? 0 : 0,
|
||||
serialPortIndex: port.portIndex,
|
||||
interfaceNumber: 0,
|
||||
serialPortIndex: portIndex,
|
||||
);
|
||||
final ok = await _plugin.openPort(target);
|
||||
if (!ok) {
|
||||
@@ -115,7 +149,7 @@ class _Ch934xSerialExamplePageState extends State<Ch934xSerialExamplePage> {
|
||||
}
|
||||
setState(() {
|
||||
_portOpened = true;
|
||||
_status = '串口 ${port.portIndex} 已打开';
|
||||
_status = '串口 $portIndex 已打开';
|
||||
});
|
||||
_startReceiving();
|
||||
}
|
||||
@@ -215,27 +249,33 @@ class _Ch934xSerialExamplePageState extends State<Ch934xSerialExamplePage> {
|
||||
onChanged: (device) {
|
||||
setState(() {
|
||||
_selectedDevice = device;
|
||||
_selectedPort = device?.serialPorts.isNotEmpty == true
|
||||
? device!.serialPorts.first
|
||||
if (device == null) {
|
||||
_availablePortIndices = const <int>[];
|
||||
_selectedPortIndex = null;
|
||||
} else {
|
||||
_availablePortIndices = _resolvePortIndices(device);
|
||||
_selectedPortIndex = _availablePortIndices.isNotEmpty
|
||||
? _availablePortIndices.first
|
||||
: null;
|
||||
}
|
||||
});
|
||||
},
|
||||
),
|
||||
if (_selectedDevice?.serialPorts.isNotEmpty == true) ...[
|
||||
if (_selectedDevice != null) ...[
|
||||
const SizedBox(height: 8),
|
||||
DropdownButton<Ch934xSerialPortInfo>(
|
||||
DropdownButton<int>(
|
||||
isExpanded: true,
|
||||
value: _selectedPort,
|
||||
value: _selectedPortIndex,
|
||||
hint: const Text('选择串口'),
|
||||
items: _selectedDevice!.serialPorts
|
||||
items: _availablePortIndices
|
||||
.map(
|
||||
(p) => DropdownMenuItem<Ch934xSerialPortInfo>(
|
||||
value: p,
|
||||
child: Text('port #${p.portIndex}'),
|
||||
(idx) => DropdownMenuItem<int>(
|
||||
value: idx,
|
||||
child: Text('port #$idx'),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
onChanged: (p) => setState(() => _selectedPort = p),
|
||||
onChanged: (idx) => setState(() => _selectedPortIndex = idx),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 16),
|
||||
|
||||
@@ -21,6 +21,22 @@ class MethodChannelCh934xSerial extends Ch934xSerialPlatform {
|
||||
final StreamController<Ch934xException> _exceptionController =
|
||||
StreamController<Ch934xException>.broadcast();
|
||||
|
||||
/// 接收来自原生层主动调用的 `onException` 消息。
|
||||
Future<void> _onNativeMethodCall(MethodCall call) async {
|
||||
if (call.method == 'onException') {
|
||||
final args = call.arguments;
|
||||
if (args is Map) {
|
||||
_exceptionController.add(
|
||||
Ch934xException(
|
||||
type: (args['type'] as int?) ?? Ch934xExceptionType.unknown,
|
||||
message: args['message'] as String?,
|
||||
cause: args['cause'] as String?,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<Ch934xDeviceInfo>> getDeviceList() async {
|
||||
final raw = await methodChannel.invokeMethod<List<dynamic>>('getDeviceList');
|
||||
@@ -86,6 +102,18 @@ class MethodChannelCh934xSerial extends Ch934xSerialPlatform {
|
||||
return result ?? false;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> setActivePort(int serialPortIndex) async {
|
||||
if (serialPortIndex < 0) {
|
||||
return false;
|
||||
}
|
||||
final result = await methodChannel.invokeMethod<bool>(
|
||||
'setActivePort',
|
||||
<String, Object>{'serialPortIndex': serialPortIndex},
|
||||
);
|
||||
return result ?? false;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Uint8List> read(int length) async {
|
||||
if (length <= 0) {
|
||||
@@ -156,6 +184,8 @@ class MethodChannelCh934xSerial extends Ch934xSerialPlatform {
|
||||
Future<void> setExceptionCallback(
|
||||
void Function(Ch934xException exception) onException,
|
||||
) async {
|
||||
// 注册接收原生层主动推送的 `onException` 事件。
|
||||
methodChannel.setMethodCallHandler(_onNativeMethodCall);
|
||||
_exceptionController.stream.listen(onException);
|
||||
await methodChannel.invokeMethod<void>('setExceptionCallback');
|
||||
}
|
||||
|
||||
@@ -30,6 +30,16 @@ class Ch934xSerialPortInfo {
|
||||
driverName: map['driverName'] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
other is Ch934xSerialPortInfo &&
|
||||
other.portIndex == portIndex &&
|
||||
other.devicePath == devicePath &&
|
||||
other.driverName == driverName;
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(portIndex, devicePath, driverName);
|
||||
}
|
||||
|
||||
/// 文档 4.1.4 中 `UsbHelper.getCH934XDeviceList` 返回的设备信息。
|
||||
@@ -104,4 +114,28 @@ class Ch934xDeviceInfo {
|
||||
serialPorts: ports,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
other is Ch934xDeviceInfo &&
|
||||
other.deviceId == deviceId &&
|
||||
other.vendorId == vendorId &&
|
||||
other.productId == productId &&
|
||||
other.deviceType == deviceType &&
|
||||
other.serialNumber == serialNumber &&
|
||||
other.productName == productName &&
|
||||
other.manufacturerName == manufacturerName &&
|
||||
other.interfaceCount == interfaceCount;
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
deviceId,
|
||||
vendorId,
|
||||
productId,
|
||||
deviceType,
|
||||
serialNumber,
|
||||
productName,
|
||||
manufacturerName,
|
||||
interfaceCount,
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user