diff --git a/android/src/main/java/com/xiarui/ch934x_serial/Ch934xSerialPlugin.java b/android/src/main/java/com/xiarui/ch934x_serial/Ch934xSerialPlugin.java index 8e21c56..d1a7d91 100644 --- a/android/src/main/java/com/xiarui/ch934x_serial/Ch934xSerialPlugin.java +++ b/android/src/main/java/com/xiarui/ch934x_serial/Ch934xSerialPlugin.java @@ -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 插件实现。 * - *

本类不直接引用 {@code CH934XLib.jar} 中的具体类型,以避免在 - * 编译期对未公开 API 形成强耦合;所有原生调用均通过反射桥接 - * {@code com.example.ch934xserial} 包下的 {@code UsbHelper} 与 - * {@code UsbSerial} 工具类,签名与文档 4-7 章保持一致。 + *

本类直接调用沁恒官方 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 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> 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。 + * + *

注意:CH934X SDK 的 {@code getSerialCount} 必须在 + * {@code openDevice} 之后才会返回正确值,枚举阶段调用 + * 会抛 {@code UartLibException}。因此本方法只回填设备 + * 基础信息,串口列表交由 {@code getSerialPortList} 在 + * 设备打开后再查询。 + */ + private List> handleGetDeviceList() throws UartLibException { + ArrayList raw = CH934XManager.getInstance().enumDevice(); List> 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 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>()); + 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> handleGetSerialPortList(@NonNull MethodCall call) - throws Exception { + /** 文档 4.1.3。SDK 要求先 openDevice 才能拿到正确串口数。 */ + private List> 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> 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`。 - * - *

由于异常事件由原生 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; + private List> buildSerialPortList(int count) { + List> ports = new ArrayList<>(); + for (int i = 0; i < Math.max(count, 0); i++) { + Map map = new HashMap<>(); + map.put("portIndex", i); + map.put("devicePath", "/dev/ttyACM" + i); + map.put("driverName", "ch934x"); + ports.add(map); } - Class clazz = Class.forName(className); - Method method = clazz.getMethod(methodName, params); - methodCache.put(key, method); - return method; + return ports; } - 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; + /** 注册 IDataCallback 推送数据上抛(可选,目前仅用作 SDK 内部缓冲预热)。 */ + private void ensureDataCallback() { + if (dataCallbackRegistered || activeDevice == null) { + return; + } + try { + CH934XManager.getInstance().registerDataCallback(activeDevice, + new IDataCallback() { + @Override + public void onData(int serialNumber, byte[] buffer, int length) { + // 数据通过 readData 拉取即可,这里留空。 + } + }); + dataCallbackRegistered = true; + } catch (Throwable t) { + // 注册失败不影响主流程,readData 仍可工作。 } - Method method = clazz.getMethod(methodName, params); - methodCache.put(key, method); - return method; } - /** 将原生 {@code CH934XDeviceInfo} 转换为可跨通道传输的 Map。 */ - private Map deviceInfoToMap(@Nullable UsbManager usbManager, Object info) - throws Exception { - Map 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()); - } - } catch (ReflectiveOperationException ignored) { - // 反射方法不存在或不可访问时忽略,字段保持空值。 - } + private void ensureModemCallback() { + if (modemCallbackRegistered || activeDevice == null) { + return; } 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); + 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; + } + + @Override + public void onOverrunError(int serialNumber) { + pushException(2 /* ioError */, "Modem overrun", null); + } + + @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) { + // 同上,失败时仍允许同步查询路径工作。 } - try { - Method getPorts = info.getClass().getMethod("getSerialPorts"); - Object ports = getPorts.invoke(info); - List> 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; } - /** 将原生 {@code UsbSerial} 元素转为 Map,仅暴露 Dart 侧需要的信息。 */ - private Map serialPortToMap(@Nullable Object port, int fallbackIndex) { - Map map = new HashMap<>(); - map.put("portIndex", fallbackIndex); - if (port == null) { - return map; + /** 主动上抛异常给 Dart 侧。 */ + private void pushException(int type, String message, @Nullable String cause) { + if (!exceptionCallbackEnabled || channel == null) { + return; } - try { - Method getIndex = port.getClass().getMethod("getSerialPortIndex"); - Object index = getIndex.invoke(port); - if (index instanceof Integer) { - map.put("portIndex", index); - } - } catch (ReflectiveOperationException ignored) { - // 字段可选,保持 fallbackIndex。 - } - try { - Method getPath = port.getClass().getMethod("getDevicePath"); - Object path = getPath.invoke(port); - map.put("devicePath", path == null ? null : path.toString()); - } catch (ReflectiveOperationException ignored) { - // 字段可选,忽略。 - } - try { - Method getName = port.getClass().getMethod("getDriverName"); - Object name = getName.invoke(port); - map.put("driverName", name == null ? null : name.toString()); - } catch (ReflectiveOperationException ignored) { - // 字段可选,忽略。 - } - return map; + Map payload = new HashMap<>(); + payload.put("type", type); + payload.put("message", message); + payload.put("cause", cause); + channel.invokeMethod("onException", payload); } } diff --git a/example/lib/main.dart b/example/lib/main.dart index c541086..990cddb 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -43,7 +43,9 @@ class _Ch934xSerialExamplePageState extends State { List _devices = const []; Ch934xDeviceInfo? _selectedDevice; - Ch934xSerialPortInfo? _selectedPort; + /// 当前选中设备的可用串口索引集合(由 SDK getSerialCount 决定)。 + List _availablePortIndices = const []; + int? _selectedPortIndex; StreamSubscription? _exceptionSubscription; StreamSubscription? _dataSubscription; @@ -87,26 +89,58 @@ class _Ch934xSerialExamplePageState extends State { Future _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 []; + _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 _resolvePortIndices(Ch934xDeviceInfo device) { + if (device.serialPorts.isNotEmpty) { + return device.serialPorts.map((p) => p.portIndex).toList()..sort(); + } + if (device.interfaceCount > 0) { + return List.generate(device.interfaceCount, (i) => i); + } + // 最坏情况下,允许打开串口 0;用户可通过业务调用切换。 + return const [0]; + } + Future _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 { } setState(() { _portOpened = true; - _status = '串口 ${port.portIndex} 已打开'; + _status = '串口 $portIndex 已打开'; }); _startReceiving(); } @@ -215,27 +249,33 @@ class _Ch934xSerialExamplePageState extends State { onChanged: (device) { setState(() { _selectedDevice = device; - _selectedPort = device?.serialPorts.isNotEmpty == true - ? device!.serialPorts.first - : null; + if (device == null) { + _availablePortIndices = const []; + _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( + DropdownButton( isExpanded: true, - value: _selectedPort, + value: _selectedPortIndex, hint: const Text('选择串口'), - items: _selectedDevice!.serialPorts + items: _availablePortIndices .map( - (p) => DropdownMenuItem( - value: p, - child: Text('port #${p.portIndex}'), + (idx) => DropdownMenuItem( + value: idx, + child: Text('port #$idx'), ), ) .toList(), - onChanged: (p) => setState(() => _selectedPort = p), + onChanged: (idx) => setState(() => _selectedPortIndex = idx), ), ], const SizedBox(height: 16), diff --git a/lib/ch934x_serial_method_channel.dart b/lib/ch934x_serial_method_channel.dart index 73333a7..d9d5eee 100644 --- a/lib/ch934x_serial_method_channel.dart +++ b/lib/ch934x_serial_method_channel.dart @@ -21,6 +21,22 @@ class MethodChannelCh934xSerial extends Ch934xSerialPlatform { final StreamController _exceptionController = StreamController.broadcast(); + /// 接收来自原生层主动调用的 `onException` 消息。 + Future _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> getDeviceList() async { final raw = await methodChannel.invokeMethod>('getDeviceList'); @@ -86,6 +102,18 @@ class MethodChannelCh934xSerial extends Ch934xSerialPlatform { return result ?? false; } + @override + Future setActivePort(int serialPortIndex) async { + if (serialPortIndex < 0) { + return false; + } + final result = await methodChannel.invokeMethod( + 'setActivePort', + {'serialPortIndex': serialPortIndex}, + ); + return result ?? false; + } + @override Future read(int length) async { if (length <= 0) { @@ -156,6 +184,8 @@ class MethodChannelCh934xSerial extends Ch934xSerialPlatform { Future setExceptionCallback( void Function(Ch934xException exception) onException, ) async { + // 注册接收原生层主动推送的 `onException` 事件。 + methodChannel.setMethodCallHandler(_onNativeMethodCall); _exceptionController.stream.listen(onException); await methodChannel.invokeMethod('setExceptionCallback'); } diff --git a/lib/src/models/ch934x_device_info.dart b/lib/src/models/ch934x_device_info.dart index 5504892..5859e4b 100644 --- a/lib/src/models/ch934x_device_info.dart +++ b/lib/src/models/ch934x_device_info.dart @@ -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, + ); }