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;
|
package com.xiarui.ch934x_serial;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.hardware.usb.UsbDevice;
|
import android.hardware.usb.UsbDevice;
|
||||||
import android.hardware.usb.UsbManager;
|
import android.hardware.usb.UsbManager;
|
||||||
@@ -7,13 +8,22 @@ import android.hardware.usb.UsbManager;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
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.embedding.engine.plugins.FlutterPlugin;
|
||||||
import io.flutter.plugin.common.MethodCall;
|
import io.flutter.plugin.common.MethodCall;
|
||||||
import io.flutter.plugin.common.MethodChannel;
|
import io.flutter.plugin.common.MethodChannel;
|
||||||
@@ -23,35 +33,91 @@ import io.flutter.plugin.common.MethodChannel.Result;
|
|||||||
/**
|
/**
|
||||||
* CH934X 系列 USB 转串口芯片的 Flutter 插件实现。
|
* CH934X 系列 USB 转串口芯片的 Flutter 插件实现。
|
||||||
*
|
*
|
||||||
* <p>本类不直接引用 {@code CH934XLib.jar} 中的具体类型,以避免在
|
* <p>本类直接调用沁恒官方 SDK {@code CH934XLib.jar} 提供的单例
|
||||||
* 编译期对未公开 API 形成强耦合;所有原生调用均通过反射桥接
|
* {@link CH934XManager},所有方法签名均与官方 Android 文档保持
|
||||||
* {@code com.example.ch934xserial} 包下的 {@code UsbHelper} 与
|
* 一致;插件不另行做反射或重新实现。文档中所述 {@code UsbHelper} /
|
||||||
* {@code UsbSerial} 工具类,签名与文档 4-7 章保持一致。
|
* {@code UsbSerial} 是 SDK 的概念性命名,真实 API 在
|
||||||
|
* {@code cn.wch.ch934xlib.CH934XManager}。
|
||||||
*/
|
*/
|
||||||
public class Ch934xSerialPlugin implements FlutterPlugin, MethodCallHandler {
|
public class Ch934xSerialPlugin implements FlutterPlugin, MethodCallHandler {
|
||||||
|
|
||||||
/** Dart ↔ Java 通信使用的 MethodChannel 名称,需与 Dart 侧保持一致。 */
|
/** Dart ↔ Java 通信使用的 MethodChannel 名称,需与 Dart 侧保持一致。 */
|
||||||
private static final String CHANNEL_NAME = "ch934x_serial";
|
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 MethodChannel channel;
|
||||||
private Context applicationContext;
|
private Context applicationContext;
|
||||||
|
|
||||||
/** 当前会话打开的 UsbSerial 反射代理;仅保留最近一次的对象以与文档 5.2.1 保持一致。 */
|
/** 当前 Dart 侧选中的 (deviceId, serialNumber) 对应的 UsbDevice 引用。 */
|
||||||
@Nullable
|
@Nullable
|
||||||
private Object currentSerialPort;
|
private UsbDevice activeDevice;
|
||||||
|
|
||||||
/** 缓存已反射得到的 Method,避免每次调用都重新查找。 */
|
/** 当前会话选中的串口索引(对应文档 5.1.1 中的 serialPortIndex)。 */
|
||||||
private final Map<String, Method> methodCache = new ConcurrentHashMap<>();
|
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
|
@Override
|
||||||
public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
|
public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
|
||||||
|
applicationContext = binding.getApplicationContext();
|
||||||
channel = new MethodChannel(binding.getBinaryMessenger(), CHANNEL_NAME);
|
channel = new MethodChannel(binding.getBinaryMessenger(), CHANNEL_NAME);
|
||||||
channel.setMethodCallHandler(this);
|
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
|
@Override
|
||||||
@@ -60,8 +126,16 @@ public class Ch934xSerialPlugin implements FlutterPlugin, MethodCallHandler {
|
|||||||
channel.setMethodCallHandler(null);
|
channel.setMethodCallHandler(null);
|
||||||
channel = null;
|
channel = null;
|
||||||
}
|
}
|
||||||
methodCache.clear();
|
try {
|
||||||
currentSerialPort = null;
|
CH934XManager.getInstance().close(applicationContext);
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
// 资源释放失败不影响主流程。
|
||||||
|
}
|
||||||
|
activeDevice = null;
|
||||||
|
activeSerialNumber = -1;
|
||||||
|
dataCallbackRegistered = false;
|
||||||
|
modemCallbackRegistered = false;
|
||||||
|
exceptionCallbackEnabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -105,230 +179,278 @@ public class Ch934xSerialPlugin implements FlutterPlugin, MethodCallHandler {
|
|||||||
result.success(handleGetModemStatus());
|
result.success(handleGetModemStatus());
|
||||||
break;
|
break;
|
||||||
case "setExceptionCallback":
|
case "setExceptionCallback":
|
||||||
result.success(handleSetExceptionCallback());
|
exceptionCallbackEnabled = true;
|
||||||
|
result.success(null);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
result.notImplemented();
|
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) {
|
} catch (Throwable t) {
|
||||||
// 统一以 PlatformException 形式上抛错误信息,便于 Dart 侧捕获。
|
|
||||||
result.error("CH934X_ERROR", t.getMessage(), t.getClass().getName());
|
result.error("CH934X_ERROR", t.getMessage(), t.getClass().getName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// 设备查找
|
// 设备查找(对应文档 4.1.x)
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
/** 文档 4.1.4 `UsbHelper.getCH934XDeviceList`。 */
|
/** 文档 4.1.4。
|
||||||
private List<Map<String, Object>> handleGetDeviceList() throws Exception {
|
*
|
||||||
UsbManager usbManager = (UsbManager) applicationContext
|
* <p>注意:CH934X SDK 的 {@code getSerialCount} 必须在
|
||||||
.getSystemService(Context.USB_SERVICE);
|
* {@code openDevice} 之后才会返回正确值,枚举阶段调用
|
||||||
Method getDeviceList = findStaticMethod(USB_HELPER_CLASS, "getCH934XDeviceList",
|
* 会抛 {@code UartLibException}。因此本方法只回填设备
|
||||||
Context.class);
|
* 基础信息,串口列表交由 {@code getSerialPortList} 在
|
||||||
Object rawList = getDeviceList.invoke(null, applicationContext);
|
* 设备打开后再查询。
|
||||||
|
*/
|
||||||
|
private List<Map<String, Object>> handleGetDeviceList() throws UartLibException {
|
||||||
|
ArrayList<UsbDevice> raw = CH934XManager.getInstance().enumDevice();
|
||||||
List<Map<String, Object>> result = new ArrayList<>();
|
List<Map<String, Object>> result = new ArrayList<>();
|
||||||
if (!(rawList instanceof List)) {
|
if (raw == null) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
for (Object info : (List<?>) rawList) {
|
for (UsbDevice device : raw) {
|
||||||
result.add(deviceInfoToMap(usbManager, info));
|
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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 文档 4.1.1 `UsbHelper.CH934XSerialNum`。 */
|
/** 文档 4.1.1。SDK 通过 getChipType + UsbDevice.getSerialNumber 推断。 */
|
||||||
@Nullable
|
@Nullable
|
||||||
private String handleGetSerialNumber(@NonNull MethodCall call) throws Exception {
|
private String handleGetSerialNumber(@NonNull MethodCall call) {
|
||||||
UsbDevice device = resolveDevice(call);
|
UsbDevice device = resolveDevice(call);
|
||||||
if (device == null) {
|
if (device == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
Method method = findStaticMethod(USB_HELPER_CLASS, "CH934XSerialNum", UsbDevice.class);
|
return device.getSerialNumber();
|
||||||
Object value = method.invoke(null, device);
|
|
||||||
return value == null ? null : value.toString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 文档 4.1.2 `UsbHelper.CH934XDeviceType`。 */
|
/** 文档 4.1.2。 */
|
||||||
private int handleGetDeviceType(@NonNull MethodCall call) throws Exception {
|
private int handleGetDeviceType(@NonNull MethodCall call) {
|
||||||
UsbDevice device = resolveDevice(call);
|
UsbDevice device = resolveDevice(call);
|
||||||
if (device == null) {
|
if (device == null) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
Method method = findStaticMethod(USB_HELPER_CLASS, "CH934XDeviceType", UsbDevice.class);
|
try {
|
||||||
Object value = method.invoke(null, device);
|
ChipType type = CH934XManager.getInstance().getChipType(device);
|
||||||
return value instanceof Integer ? (Integer) value : -1;
|
return mapChipType(type);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 文档 4.1.3 `UsbHelper.getCH934XSerialPortList`。 */
|
/** 文档 4.1.3。SDK 要求先 openDevice 才能拿到正确串口数。 */
|
||||||
private List<Map<String, Object>> handleGetSerialPortList(@NonNull MethodCall call)
|
private List<Map<String, Object>> handleGetSerialPortList(@NonNull MethodCall call) {
|
||||||
throws Exception {
|
|
||||||
UsbDevice device = resolveDevice(call);
|
UsbDevice device = resolveDevice(call);
|
||||||
Integer interfaceNumber = call.argument("interfaceNumber");
|
if (device == null) {
|
||||||
if (device == null || interfaceNumber == null) {
|
|
||||||
return new ArrayList<>();
|
return new ArrayList<>();
|
||||||
}
|
}
|
||||||
Method method = findStaticMethod(USB_HELPER_CLASS, "getCH934XSerialPortList",
|
// 若该设备在当前会话已被打开,优先返回真实 count;
|
||||||
Context.class, UsbDevice.class, int.class);
|
// 否则兜底为 0,避免误报(SDK 在未 open 时会抛异常或返回 0)。
|
||||||
Object rawArray = method.invoke(null, applicationContext, device, interfaceNumber);
|
if (activeDevice == null || activeDevice.getDeviceId() != device.getDeviceId()) {
|
||||||
List<Map<String, Object>> ports = new ArrayList<>();
|
return new ArrayList<>();
|
||||||
if (rawArray == null) {
|
|
||||||
return ports;
|
|
||||||
}
|
}
|
||||||
int length = java.lang.reflect.Array.getLength(rawArray);
|
int count;
|
||||||
for (int i = 0; i < length; i++) {
|
try {
|
||||||
Object port = java.lang.reflect.Array.get(rawArray, i);
|
count = CH934XManager.getInstance().getSerialCount(device);
|
||||||
ports.add(serialPortToMap(port, i));
|
} catch (Throwable t) {
|
||||||
|
count = 0;
|
||||||
}
|
}
|
||||||
return ports;
|
return buildSerialPortList(count);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// 设备打开 / 关闭
|
// 设备打开 / 关闭(对应文档 5.x)
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
/** 文档 5.1.1 `UsbSerial.init`。 */
|
/** 文档 5.1.1。 */
|
||||||
private boolean handleOpenPort(@NonNull MethodCall call) throws Exception {
|
private boolean handleOpenPort(@NonNull MethodCall call)
|
||||||
|
throws ChipException, NoPermissionException, UartLibException {
|
||||||
Integer deviceId = call.argument("deviceId");
|
Integer deviceId = call.argument("deviceId");
|
||||||
Integer interfaceNumber = call.argument("interfaceNumber");
|
|
||||||
Integer serialPortIndex = call.argument("serialPortIndex");
|
Integer serialPortIndex = call.argument("serialPortIndex");
|
||||||
if (deviceId == null || interfaceNumber == null || serialPortIndex == null) {
|
if (deviceId == null || serialPortIndex == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
UsbDevice device = resolveDevice(call);
|
UsbDevice device = resolveDevice(call);
|
||||||
if (device == null) {
|
if (device == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Class<?> serialClass = Class.forName(USB_SERIAL_CLASS);
|
boolean opened = CH934XManager.getInstance().openDevice(device);
|
||||||
Object port = serialClass.getDeclaredConstructor().newInstance();
|
if (!opened) {
|
||||||
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) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Method close = findInstanceMethod(currentSerialPort.getClass(), "close");
|
activeDevice = device;
|
||||||
Object result = close.invoke(currentSerialPort);
|
activeSerialNumber = serialPortIndex;
|
||||||
currentSerialPort = null;
|
ensureDataCallback();
|
||||||
return result instanceof Boolean && (Boolean) result;
|
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
|
@Nullable
|
||||||
private byte[] handleRead(@NonNull MethodCall call) throws Exception {
|
private byte[] handleRead(@NonNull MethodCall call) {
|
||||||
Object port = requirePort();
|
if (activeDevice == null) {
|
||||||
|
return new byte[0];
|
||||||
|
}
|
||||||
Integer length = call.argument("length");
|
Integer length = call.argument("length");
|
||||||
if (length == null || length <= 0) {
|
if (length == null || length <= 0) {
|
||||||
return new byte[0];
|
return new byte[0];
|
||||||
}
|
}
|
||||||
Method read = findInstanceMethod(port.getClass(), "read", byte[].class, int.class);
|
try {
|
||||||
byte[] buffer = new byte[length];
|
// SDK 返回内部缓冲的所有数据,与文档 read 语义一致。
|
||||||
int readBytes = (Integer) read.invoke(port, buffer, length);
|
byte[] data = CH934XManager.getInstance()
|
||||||
if (readBytes <= 0) {
|
.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];
|
return new byte[0];
|
||||||
}
|
}
|
||||||
byte[] result = new byte[readBytes];
|
|
||||||
System.arraycopy(buffer, 0, result, 0, readBytes);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 文档 6.2.1 `UsbSerial.write`。 */
|
/** 文档 6.2.1。 */
|
||||||
private int handleWrite(@NonNull MethodCall call) throws Exception {
|
private int handleWrite(@NonNull MethodCall call) throws UartLibException {
|
||||||
Object port = requirePort();
|
if (activeDevice == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
byte[] data = call.argument("data");
|
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;
|
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`。 */
|
/** 文档 7.1.1。 */
|
||||||
private boolean handleSetGpioOutput(@NonNull MethodCall call) throws Exception {
|
private boolean handleSetGpioOutput(@NonNull MethodCall call) {
|
||||||
Object port = requirePort();
|
if (activeDevice == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
Integer gpioNumber = call.argument("gpioNumber");
|
Integer gpioNumber = call.argument("gpioNumber");
|
||||||
Integer level = call.argument("level");
|
Integer level = call.argument("level");
|
||||||
if (gpioNumber == null || level == null) {
|
if (gpioNumber == null || level == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Method method = findInstanceMethod(port.getClass(), "setGpioOutput", int.class, int.class);
|
try {
|
||||||
Object result = method.invoke(port, gpioNumber, level);
|
GPIO_VALUE value = level == 0 ? GPIO_VALUE.LOW : GPIO_VALUE.HIGH;
|
||||||
return result instanceof Boolean && (Boolean) result;
|
return CH934XManager.getInstance().setGPIOValue(
|
||||||
|
activeDevice, activeSerialNumber, gpioNumber, value);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 文档 7.1.2 `UsbSerial.getGpioInput`。 */
|
/** 文档 7.1.2。SDK 仅在回调后通过 cache 查询,这里主动刷新一次再读。 */
|
||||||
private int handleGetGpioInput(@NonNull MethodCall call) throws Exception {
|
private int handleGetGpioInput(@NonNull MethodCall call) {
|
||||||
Object port = requirePort();
|
if (activeDevice == null) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
Integer gpioNumber = call.argument("gpioNumber");
|
Integer gpioNumber = call.argument("gpioNumber");
|
||||||
if (gpioNumber == null) {
|
if (gpioNumber == null) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
Method method = findInstanceMethod(port.getClass(), "getGpioInput", int.class);
|
try {
|
||||||
Object result = method.invoke(port, gpioNumber);
|
CH934XManager.getInstance().getGPIOValue(activeDevice);
|
||||||
return result instanceof Integer ? (Integer) result : -1;
|
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`。 */
|
/** 文档 7.2.1。 */
|
||||||
private boolean handleSetModemControl(@NonNull MethodCall call) throws Exception {
|
private boolean handleSetModemControl(@NonNull MethodCall call) {
|
||||||
Object port = requirePort();
|
if (activeDevice == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
Integer dtr = call.argument("dtr");
|
Integer dtr = call.argument("dtr");
|
||||||
Integer rts = call.argument("rts");
|
Integer rts = call.argument("rts");
|
||||||
if (dtr == null || rts == null) {
|
if (dtr == null || rts == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Method method = findInstanceMethod(port.getClass(), "setModemControl", int.class, int.class);
|
try {
|
||||||
Object result = method.invoke(port, dtr, rts);
|
boolean dtrOk = CH934XManager.getInstance().setDTR(
|
||||||
return result instanceof Boolean && (Boolean) result;
|
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`。 */
|
/** 文档 7.2.2。SDK 只能通过回调获取,这里返回最近一次的状态缓存。 */
|
||||||
private int handleGetModemStatus() throws Exception {
|
private int handleGetModemStatus() {
|
||||||
Object port = requirePort();
|
return modemStatusCache;
|
||||||
Method method = findInstanceMethod(port.getClass(), "getModemStatus");
|
|
||||||
Object result = method.invoke(port);
|
|
||||||
return result instanceof Integer ? (Integer) result : 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// 异常回调
|
// 内部辅助
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 文档 7.3.1 `UsbSerial.setExceptionCallback`。
|
|
||||||
*
|
|
||||||
* <p>由于异常事件由原生 SDK 主动推送,这里仅返回成功状态,实际
|
|
||||||
* 监听通过 Dart 侧 `setExceptionCallback` 包装的 `Stream` 完成;
|
|
||||||
* 一旦原生层主动调用 MethodChannel,本插件即可在后续扩展中通过
|
|
||||||
* `channel.invokeMethod("onException", payload)` 将事件上抛 Dart。
|
|
||||||
*/
|
|
||||||
private boolean handleSetExceptionCallback() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 工具方法
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@@ -337,6 +459,10 @@ public class Ch934xSerialPlugin implements FlutterPlugin, MethodCallHandler {
|
|||||||
if (deviceId == null) {
|
if (deviceId == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
// 优先用已激活的设备,避免每次枚举。
|
||||||
|
if (activeDevice != null && activeDevice.getDeviceId() == deviceId) {
|
||||||
|
return activeDevice;
|
||||||
|
}
|
||||||
UsbManager usbManager = (UsbManager) applicationContext
|
UsbManager usbManager = (UsbManager) applicationContext
|
||||||
.getSystemService(Context.USB_SERVICE);
|
.getSystemService(Context.USB_SERVICE);
|
||||||
if (usbManager == null) {
|
if (usbManager == null) {
|
||||||
@@ -351,129 +477,100 @@ public class Ch934xSerialPlugin implements FlutterPlugin, MethodCallHandler {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
/** 根据 SDK 的 ChipType 枚举映射到 Dart 侧 deviceType 整数值。 */
|
||||||
private Object requirePort() throws IllegalStateException {
|
private int mapChipType(@Nullable ChipType type) {
|
||||||
if (currentSerialPort == null) {
|
if (type == null) {
|
||||||
throw new IllegalStateException("串口尚未打开,请先调用 openPort。");
|
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)
|
private List<Map<String, Object>> buildSerialPortList(int count) {
|
||||||
throws Exception {
|
List<Map<String, Object>> ports = new ArrayList<>();
|
||||||
String key = "static#" + className + "#" + methodName;
|
for (int i = 0; i < Math.max(count, 0); i++) {
|
||||||
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 {
|
|
||||||
Map<String, Object> map = new HashMap<>();
|
Map<String, Object> map = new HashMap<>();
|
||||||
Method getDevice = info.getClass().getMethod("getDevice");
|
map.put("portIndex", i);
|
||||||
Object device = getDevice.invoke(info);
|
map.put("devicePath", "/dev/ttyACM" + i);
|
||||||
if (device instanceof UsbDevice) {
|
map.put("driverName", "ch934x");
|
||||||
UsbDevice usbDevice = (UsbDevice) device;
|
ports.add(map);
|
||||||
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) {
|
return ports;
|
||||||
// 反射方法不存在或不可访问时忽略,字段保持空值。
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 将原生 {@code UsbSerial} 元素转为 Map,仅暴露 Dart 侧需要的信息。 */
|
/** 注册 IDataCallback 推送数据上抛(可选,目前仅用作 SDK 内部缓冲预热)。 */
|
||||||
private Map<String, Object> serialPortToMap(@Nullable Object port, int fallbackIndex) {
|
private void ensureDataCallback() {
|
||||||
Map<String, Object> map = new HashMap<>();
|
if (dataCallbackRegistered || activeDevice == null) {
|
||||||
map.put("portIndex", fallbackIndex);
|
return;
|
||||||
if (port == null) {
|
|
||||||
return map;
|
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
Method getIndex = port.getClass().getMethod("getSerialPortIndex");
|
CH934XManager.getInstance().registerDataCallback(activeDevice,
|
||||||
Object index = getIndex.invoke(port);
|
new IDataCallback() {
|
||||||
if (index instanceof Integer) {
|
@Override
|
||||||
map.put("portIndex", index);
|
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 {
|
try {
|
||||||
Method getPath = port.getClass().getMethod("getDevicePath");
|
CH934XManager.getInstance().registerModemStatusCallback(activeDevice,
|
||||||
Object path = getPath.invoke(port);
|
new IModemStatus() {
|
||||||
map.put("devicePath", path == null ? null : path.toString());
|
@Override
|
||||||
} catch (ReflectiveOperationException ignored) {
|
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");
|
@Override
|
||||||
Object name = getName.invoke(port);
|
public void onOverrunError(int serialNumber) {
|
||||||
map.put("driverName", name == null ? null : name.toString());
|
pushException(2 /* ioError */, "Modem overrun", null);
|
||||||
} catch (ReflectiveOperationException ignored) {
|
|
||||||
// 字段可选,忽略。
|
|
||||||
}
|
}
|
||||||
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>[];
|
List<Ch934xDeviceInfo> _devices = const <Ch934xDeviceInfo>[];
|
||||||
Ch934xDeviceInfo? _selectedDevice;
|
Ch934xDeviceInfo? _selectedDevice;
|
||||||
Ch934xSerialPortInfo? _selectedPort;
|
/// 当前选中设备的可用串口索引集合(由 SDK getSerialCount 决定)。
|
||||||
|
List<int> _availablePortIndices = const <int>[];
|
||||||
|
int? _selectedPortIndex;
|
||||||
StreamSubscription<Ch934xException>? _exceptionSubscription;
|
StreamSubscription<Ch934xException>? _exceptionSubscription;
|
||||||
StreamSubscription<Uint8List>? _dataSubscription;
|
StreamSubscription<Uint8List>? _dataSubscription;
|
||||||
|
|
||||||
@@ -87,26 +89,58 @@ class _Ch934xSerialExamplePageState extends State<Ch934xSerialExamplePage> {
|
|||||||
Future<void> _refreshDeviceList() async {
|
Future<void> _refreshDeviceList() async {
|
||||||
try {
|
try {
|
||||||
final devices = await _plugin.getDeviceList();
|
final devices = await _plugin.getDeviceList();
|
||||||
|
if (!mounted) return;
|
||||||
setState(() {
|
setState(() {
|
||||||
_devices = devices;
|
_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 设备';
|
_status = '已扫描到 ${devices.length} 台 CH934X 设备';
|
||||||
});
|
});
|
||||||
} on Exception catch (e) {
|
} on Exception catch (e) {
|
||||||
|
if (!mounted) return;
|
||||||
setState(() => _status = '设备扫描失败: $e');
|
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 {
|
Future<void> _openPort() async {
|
||||||
final device = _selectedDevice;
|
final device = _selectedDevice;
|
||||||
final port = _selectedPort;
|
final portIndex = _selectedPortIndex;
|
||||||
if (device == null || port == null) {
|
if (device == null || portIndex == null) {
|
||||||
setState(() => _status = '请先选择设备与串口');
|
setState(() => _status = '请先选择设备与串口');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final target = Ch934xPortTarget(
|
final target = Ch934xPortTarget(
|
||||||
deviceId: device.deviceId,
|
deviceId: device.deviceId,
|
||||||
interfaceNumber: device.interfaceCount > 0 ? 0 : 0,
|
interfaceNumber: 0,
|
||||||
serialPortIndex: port.portIndex,
|
serialPortIndex: portIndex,
|
||||||
);
|
);
|
||||||
final ok = await _plugin.openPort(target);
|
final ok = await _plugin.openPort(target);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
@@ -115,7 +149,7 @@ class _Ch934xSerialExamplePageState extends State<Ch934xSerialExamplePage> {
|
|||||||
}
|
}
|
||||||
setState(() {
|
setState(() {
|
||||||
_portOpened = true;
|
_portOpened = true;
|
||||||
_status = '串口 ${port.portIndex} 已打开';
|
_status = '串口 $portIndex 已打开';
|
||||||
});
|
});
|
||||||
_startReceiving();
|
_startReceiving();
|
||||||
}
|
}
|
||||||
@@ -215,27 +249,33 @@ class _Ch934xSerialExamplePageState extends State<Ch934xSerialExamplePage> {
|
|||||||
onChanged: (device) {
|
onChanged: (device) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_selectedDevice = device;
|
_selectedDevice = device;
|
||||||
_selectedPort = device?.serialPorts.isNotEmpty == true
|
if (device == null) {
|
||||||
? device!.serialPorts.first
|
_availablePortIndices = const <int>[];
|
||||||
|
_selectedPortIndex = null;
|
||||||
|
} else {
|
||||||
|
_availablePortIndices = _resolvePortIndices(device);
|
||||||
|
_selectedPortIndex = _availablePortIndices.isNotEmpty
|
||||||
|
? _availablePortIndices.first
|
||||||
: null;
|
: null;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
if (_selectedDevice?.serialPorts.isNotEmpty == true) ...[
|
if (_selectedDevice != null) ...[
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
DropdownButton<Ch934xSerialPortInfo>(
|
DropdownButton<int>(
|
||||||
isExpanded: true,
|
isExpanded: true,
|
||||||
value: _selectedPort,
|
value: _selectedPortIndex,
|
||||||
hint: const Text('选择串口'),
|
hint: const Text('选择串口'),
|
||||||
items: _selectedDevice!.serialPorts
|
items: _availablePortIndices
|
||||||
.map(
|
.map(
|
||||||
(p) => DropdownMenuItem<Ch934xSerialPortInfo>(
|
(idx) => DropdownMenuItem<int>(
|
||||||
value: p,
|
value: idx,
|
||||||
child: Text('port #${p.portIndex}'),
|
child: Text('port #$idx'),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
onChanged: (p) => setState(() => _selectedPort = p),
|
onChanged: (idx) => setState(() => _selectedPortIndex = idx),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
|||||||
@@ -21,6 +21,22 @@ class MethodChannelCh934xSerial extends Ch934xSerialPlatform {
|
|||||||
final StreamController<Ch934xException> _exceptionController =
|
final StreamController<Ch934xException> _exceptionController =
|
||||||
StreamController<Ch934xException>.broadcast();
|
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
|
@override
|
||||||
Future<List<Ch934xDeviceInfo>> getDeviceList() async {
|
Future<List<Ch934xDeviceInfo>> getDeviceList() async {
|
||||||
final raw = await methodChannel.invokeMethod<List<dynamic>>('getDeviceList');
|
final raw = await methodChannel.invokeMethod<List<dynamic>>('getDeviceList');
|
||||||
@@ -86,6 +102,18 @@ class MethodChannelCh934xSerial extends Ch934xSerialPlatform {
|
|||||||
return result ?? false;
|
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
|
@override
|
||||||
Future<Uint8List> read(int length) async {
|
Future<Uint8List> read(int length) async {
|
||||||
if (length <= 0) {
|
if (length <= 0) {
|
||||||
@@ -156,6 +184,8 @@ class MethodChannelCh934xSerial extends Ch934xSerialPlatform {
|
|||||||
Future<void> setExceptionCallback(
|
Future<void> setExceptionCallback(
|
||||||
void Function(Ch934xException exception) onException,
|
void Function(Ch934xException exception) onException,
|
||||||
) async {
|
) async {
|
||||||
|
// 注册接收原生层主动推送的 `onException` 事件。
|
||||||
|
methodChannel.setMethodCallHandler(_onNativeMethodCall);
|
||||||
_exceptionController.stream.listen(onException);
|
_exceptionController.stream.listen(onException);
|
||||||
await methodChannel.invokeMethod<void>('setExceptionCallback');
|
await methodChannel.invokeMethod<void>('setExceptionCallback');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,16 @@ class Ch934xSerialPortInfo {
|
|||||||
driverName: map['driverName'] as String?,
|
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` 返回的设备信息。
|
/// 文档 4.1.4 中 `UsbHelper.getCH934XDeviceList` 返回的设备信息。
|
||||||
@@ -104,4 +114,28 @@ class Ch934xDeviceInfo {
|
|||||||
serialPorts: ports,
|
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