Files
ch934x_serial/lib/ch934x_serial.dart
huangsheng 7fe87f8d7c feat(ch934x_serial): 添加串口写入功能的端口索引参数
- write方法新增可选的serialPortIndex参数,支持直接写入指定端口
- 传入端口索引时不会改变当前活跃端口,保持原有活跃端口行为
- 添加对负数端口索引的验证处理,直接返回零避免异常
- 更新文档说明多端口并发客户端应使用显式端口写入方式
- 增加相关单元测试覆盖新参数的各种使用场景
- 同步更新Android原生层实现以支持端口索引传递
2026-07-13 20:51:27 +08:00

246 lines
8.5 KiB
Dart

export 'src/models/models.dart';
import 'dart:async';
import 'dart:typed_data';
import 'ch934x_serial_method_channel.dart';
import 'ch934x_serial_platform_interface.dart';
import 'src/models/models.dart';
/// CH934X 插件的对外门面类。
///
/// 内部委托 [Ch934xSerialPlatform] 真正执行平台调用,
/// 在 Android 上默认走 [MethodChannelCh934xSerial]。
///
/// 命名/语义与官方 Android SDK 文档保持一致;若希望
/// 监听串口数据流,可使用 [dataStream] 配合 [read]。
class Ch934xSerial {
/// 使用默认平台实现构造。
Ch934xSerial() : _platform = Ch934xSerialPlatform.instance;
/// 注入自定义平台实现,常用于单元测试。
Ch934xSerial.withPlatform(Ch934xSerialPlatform platform)
: _platform = platform;
final Ch934xSerialPlatform _platform;
// ---------------------------------------------------------------------------
// 设备查找
// ---------------------------------------------------------------------------
/// 获取所有已连接的 CH934X 设备信息。
Future<List<Ch934xDeviceInfo>> getDeviceList() => _platform.getDeviceList();
/// 获取指定设备序列号;非 CH934X 设备时返回 null。
Future<String?> getSerialNumber(int deviceId) =>
_platform.getSerialNumber(deviceId);
/// 获取指定设备类型,取值见 [Ch934xDeviceType]。
Future<int> getDeviceType(int deviceId) => _platform.getDeviceType(deviceId);
/// 获取指定设备的串口列表。
Future<List<Ch934xSerialPortInfo>> getSerialPortList(
int deviceId, {
required int interfaceNumber,
}) =>
_platform.getSerialPortList(
deviceId,
interfaceNumber: interfaceNumber,
);
// ---------------------------------------------------------------------------
// 设备打开 / 关闭
// ---------------------------------------------------------------------------
/// 打开指定串口。
Future<bool> openPort(Ch934xPortTarget target) => _platform.openPort(target);
/// 关闭当前会话最近一次打开的串口。
Future<bool> closePort() => _platform.closePort();
/// 切换当前活跃串口(同一 UsbDevice 内部的不同串口)。
///
/// CH934X 设备一次 `openPort` 之后所有串口已连接,后续
/// 读写只需切换 `serialPortIndex`,无需重新 open。
Future<bool> setActivePort(int serialPortIndex) =>
_platform.setActivePort(serialPortIndex);
/// 主动申请指定设备的 USB 权限。
///
/// 正常情况下 `openPort` 内部会自动申请,本方法用于业务
/// 方希望提前引导用户授权的场景,例如在主界面"刷新设备
/// 列表"之后立刻弹一次授权请求。
Future<bool> requestUsbPermission(int deviceId) =>
_platform.requestUsbPermission(deviceId);
// ---------------------------------------------------------------------------
// 串口读写
// ---------------------------------------------------------------------------
/// 阻塞式读取,直到拿到 [length] 字节或缓冲区被填满。
///
/// 返回值为实际读到的字节;若底层无数据或读取失败,返回空。
Future<Uint8List> read(int length) => _platform.read(length);
/// 写入数据,返回实际写入的字节数。
///
/// [serialPortIndex] 为空时沿用当前活跃串口;传入时直接写入指定端口,
/// 不会改变当前活跃串口。
Future<int> write(Uint8List data, {int? serialPortIndex}) =>
_platform.write(data, serialPortIndex: serialPortIndex);
/// 构造一个持续从串口拉取数据的 `Stream<Uint8List>`。
///
/// 内部以 [interval] 为周期反复调用 [read];当底层无数据
/// 时返回空缓冲区,消费者可据此判定是否需要结束订阅。
Stream<Uint8List> dataStream({
int chunkSize = 1024,
Duration interval = const Duration(milliseconds: 20),
}) async* {
if (chunkSize <= 0) {
throw ArgumentError.value(chunkSize, 'chunkSize', '必须大于 0');
}
while (true) {
final chunk = await _platform.read(chunkSize);
if (chunk.isNotEmpty) {
yield chunk;
}
await Future<void>.delayed(interval);
}
}
/// 多端口并发轮询流:每周期轮询 8 个端口各一次,返回 (portIndex, data) 元组。
///
/// 各端口数据互不干扰,消费者端需要用每个端口独立的缓冲区拆分 \r\n 行。
/// 不传 [ports] 时默认轮询 0..7 全部端口。
Stream<(int, Uint8List)> portDataStream({
int chunkSize = 1024,
Duration interval = const Duration(milliseconds: 25),
List<int> ports = const [0, 1, 2, 3, 4, 5, 6, 7],
}) async* {
if (chunkSize <= 0) {
throw ArgumentError.value(chunkSize, 'chunkSize', '必须大于 0');
}
while (true) {
for (final port in ports) {
final chunk = await _platform.read(chunkSize, serialPortIndex: port);
if (chunk.isNotEmpty) {
yield (port, chunk);
}
}
await Future<void>.delayed(interval);
}
}
// ---------------------------------------------------------------------------
// GPIO
// ---------------------------------------------------------------------------
/// 设置 GPIO 输出电平(0 或 1)。
Future<bool> setGpioOutput({required int gpioNumber, required int level}) =>
_platform.setGpioOutput(gpioNumber: gpioNumber, level: level);
/// 读取 GPIO 输入电平;负值表示读取失败。
Future<int> getGpioInput(int gpioNumber) =>
_platform.getGpioInput(gpioNumber);
// ---------------------------------------------------------------------------
// Modem
// ---------------------------------------------------------------------------
/// 设置 DTR / RTS 信号。
Future<bool> setModemControl({required int dtr, required int rts}) =>
_platform.setModemControl(dtr: dtr, rts: rts);
/// 获取 Modem 状态位,可通过 [ModemStatus] 工具类解析。
Future<int> getModemStatus() => _platform.getModemStatus();
// ---------------------------------------------------------------------------
// 异常回调
// ---------------------------------------------------------------------------
/// 注册异常回调(例如设备拔出)。
///
/// 返回一个 [StreamSubscription],可在外层 dispose 时取消。
Future<StreamSubscription<Ch934xException>> setExceptionCallback(
void Function(Ch934xException exception) onException,
) async {
final controller = StreamController<Ch934xException>();
final subscription = controller.stream.listen(onException);
await _platform.setExceptionCallback(controller.add);
return subscription;
}
// ---------------------------------------------------------------------------
// 新增接口(文档补充的完整 SDK API)
// ---------------------------------------------------------------------------
/// 设置 Break 信号。
Future<bool> setBreak(bool valid) => _platform.setBreak(valid);
/// 获取当前串口模式。
Future<int> getCurrentMode() => _platform.getCurrentMode();
/// 获取 GPIO 数量。
Future<int> getGpiocount(int deviceId) => _platform.getGpiocount(deviceId);
/// 获取 GPIO 组数。
Future<int> getGpiogroup(int deviceId) => _platform.getGpiogroup(deviceId);
/// 使能 GPIO。
Future<bool> enableGpio({
required int chipType,
required int gpioGroup,
required int enable,
}) =>
_platform.enableGpio(
chipType: chipType,
gpioGroup: gpioGroup,
enable: enable,
);
/// 设置 GPIO 方向。
Future<bool> setGpioDir({
required int gpioGroup,
required int gpioNumber,
required int dir,
}) =>
_platform.setGpioDir(
gpioGroup: gpioGroup,
gpioNumber: gpioNumber,
dir: dir,
);
/// 从缓存查询 GPIO 方向。
Future<int> queryGpioDirFromCache({
required int gpioGroup,
required int gpioNumber,
}) =>
_platform.queryGpioDirFromCache(
gpioGroup: gpioGroup,
gpioNumber: gpioNumber,
);
/// 查询设备是否已打开。
Future<bool> isConnected(int deviceId) => _platform.isConnected(deviceId);
/// 获取当前已打开的设备 ID 列表。
Future<List<int>> getConnectedDevices() => _platform.getConnectedDevices();
/// 配置当前活跃串口的参数。
Future<bool> setSerialParameter({
required int baud,
int dataBit = 8,
int stopBit = 1,
int parityBit = 0,
bool flow = false,
}) =>
_platform.setSerialParameter(
baud: baud,
dataBit: dataBit,
stopBit: stopBit,
parityBit: parityBit,
flow: flow,
);
}