using Common.Attributes; using System; using System.Collections.Concurrent; using System.IO.Ports; using System.Text; using System.Threading; using System.Threading.Tasks; namespace DeviceCommand.Flexible { /// /// 灵活型串口通信类(免实例化,每次调用临时创建串口连接)。 /// 支持只发送指令、发送并读取应答两种最常用操作, /// 内部使用按串口名称粒度的通信锁保证同一串口同一时刻只有一个事务在执行, /// 不同串口之间互不阻塞,适用于偶发性、无需保持长连接的串口设备通信场景(如示波器、电源的 SCPI 指令)。 /// [ADPCommand] public static class FSerialPort { // 按串口名称粒度的通信锁:同一串口同一时刻只有一个事务在执行,不同串口互不阻塞 private static readonly ConcurrentDictionary _commLocks = new(); /// 获取指定串口的通信锁(不存在则自动创建) private static SemaphoreSlim GetLock(string portName) => _commLocks.GetOrAdd(portName, _ => new SemaphoreSlim(1, 1)); /// /// 创建串口实例并配置 UTF8 编码与超时参数。 /// private static SerialPort CreatePort(string portName, int baudRate, Parity parity, int dataBits, StopBits stopBits, int sendTimeout,int receiveTimeout) { return new SerialPort(portName, baudRate, parity, dataBits, stopBits) { Encoding = Encoding.UTF8, WriteTimeout = sendTimeout > 0 ? sendTimeout : SerialPort.InfiniteTimeout, ReadTimeout = receiveTimeout > 0 ? receiveTimeout : SerialPort.InfiniteTimeout }; } #region 最常用:只发送字符串 /// /// 向串口发送一条字符串指令(只发送,不等待应答)。 /// /// 串口名称,如 "COM1" /// 波特率,如 9600、115200 /// 校验位 /// 数据位,常用 8 /// 停止位 /// 发送超时时间(毫秒) /// 接收超时时间(毫秒) /// 要发送的指令字符串 /// 异步取消令牌 public static async Task 发送指令(string portName,int baudRate,Parity parity,int dataBits,StopBits stopBits,int sendTimeout,int receiveTimeout,string command,CancellationToken ct = default) { await GetLock(portName).WaitAsync(ct); try { using var port = CreatePort( portName, baudRate, parity, dataBits, stopBits, sendTimeout, receiveTimeout); port.Open(); using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct); if (sendTimeout > 0) cts.CancelAfter(sendTimeout); var bytes = port.Encoding.GetBytes(command); await port.BaseStream.WriteAsync(bytes, 0, bytes.Length, cts.Token); await port.BaseStream.FlushAsync(cts.Token); } finally { GetLock(portName).Release(); } } #endregion #region 最常用:发送字符串并读取字符串 /// /// 向串口发送一条字符串指令并读取应答,读取到结束符为止(应答不含结束符)。 /// /// 串口名称,如 "COM1" /// 波特率,如 9600、115200 /// 校验位 /// 数据位,常用 8 /// 停止位 /// 发送超时时间(毫秒) /// 接收超时时间(毫秒),超时未收到结束符抛出超时异常 /// 要发送的指令字符串 /// 应答结束符,默认换行符 "\n" /// 异步取消令牌 /// 去除结束符并去除首尾空白后的应答字符串 public static async Task 发送并读取应答(string portName,int baudRate, Parity parity, int dataBits, StopBits stopBits,int sendTimeout, int receiveTimeout,string command,string delimiter = "\n", CancellationToken ct = default) { await GetLock(portName).WaitAsync(ct); try { using var port = CreatePort( portName, baudRate, parity, dataBits, stopBits, sendTimeout, receiveTimeout); port.Open(); // Send var sendBytes = port.Encoding.GetBytes(command); await port.BaseStream.WriteAsync(sendBytes, 0, sendBytes.Length, ct); await port.BaseStream.FlushAsync(ct); // Read delimiter ??= "\n"; var sb = new StringBuilder(); byte[] buffer = new byte[256]; using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct); if (receiveTimeout > 0) cts.CancelAfter(receiveTimeout); while (!cts.Token.IsCancellationRequested) { int read = await port.BaseStream.ReadAsync( buffer, 0, buffer.Length, cts.Token); if (read == 0) break; sb.Append(port.Encoding.GetString(buffer, 0, read)); int index = sb.ToString().IndexOf(delimiter, StringComparison.Ordinal); if (index >= 0) return sb.ToString(0, index).Trim(); } throw new TimeoutException("串口读取超时"); } finally { GetLock(portName).Release(); } } #endregion } }