Files
ACP/DeviceCommand/Base/Serial_Port.cs
2026-07-29 13:12:27 +08:00

227 lines
7.8 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Model.Models;
using System;
using System.IO;
using System.IO.Ports;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace DeviceCommand.Base
{
public class Serial_Port : ISerialPort, IDisposable
{
public string PortName { get; set; } = "COM1";
public int BaudRate { get; set; } = 9600;
public int DataBits { get; set; } = 8;
public StopBits StopBits { get; set; } = StopBits.One;
public Parity Parity { get; set; } = Parity.None;
public int ReadTimeout { get; set; } = 3000;
public int WriteTimeout { get; set; } = 3000;
private SerialPort _serialPort;
public bool IsConnected => _serialPort?.IsOpen ?? false;
protected readonly SemaphoreSlim commLock = new(1, 1);
public Serial_Port()
{
_serialPort = new SerialPort();
}
/// <summary>
/// 通过 <see cref="SerialPortConfig"/> 一次性配置串口通信参数。
/// </summary>
public Serial_Port(SerialPortConfig config) : this()
{
if (config == null) return;
ConfigureDevice(config.PortName, config.BaudRate, config.DataBits, config.StopBits, config.Parity, config.ReadTimeout, config.WriteTimeout);
}
public void ConfigureDevice(string portName, int baudRate, int dataBits = 8, StopBits stopBits = StopBits.One, Parity parity = Parity.None, int readTimeout = 3000, int writeTimeout = 3000)
{
PortName = portName;
BaudRate = baudRate;
DataBits = dataBits;
StopBits = stopBits;
Parity = parity;
ReadTimeout = readTimeout;
WriteTimeout = writeTimeout;
}
public virtual async Task<bool> ConnectAsync(CancellationToken ct = default)
{
await commLock.WaitAsync(ct);
try
{
if (_serialPort.IsOpen) _serialPort.Close();
_serialPort.PortName = PortName;
_serialPort.BaudRate = BaudRate;
_serialPort.DataBits = DataBits;
_serialPort.StopBits = StopBits;
_serialPort.Parity = Parity;
// 允许底层保留默认设置,控制权交给外层 WaitAsync
_serialPort.ReadTimeout = SerialPort.InfiniteTimeout;
_serialPort.WriteTimeout = SerialPort.InfiniteTimeout;
_serialPort.Open();
return true;
}
finally
{
commLock.Release();
}
}
public virtual void Close()
{
if (_serialPort?.IsOpen == true) _serialPort.Close();
}
private async Task LoglessSendAsync(string data, CancellationToken ct)
{
if (!_serialPort.IsOpen) throw new InvalidOperationException("串口未打开。");
byte[] bytes = Encoding.UTF8.GetBytes(data);
try
{
if (WriteTimeout > 0)
{
// 核心修改:使用 WaitAsync 精准控制写入超时
await _serialPort.BaseStream.WriteAsync(bytes, 0, bytes.Length, ct)
.WaitAsync(TimeSpan.FromMilliseconds(WriteTimeout), ct)
.ConfigureAwait(false);
}
else
{
await _serialPort.BaseStream.WriteAsync(bytes, 0, bytes.Length, ct).ConfigureAwait(false);
}
}
catch (TimeoutException ex)
{
throw new TimeoutException($"串口写入数据超时({WriteTimeout} ms", ex);
}
}
public async Task SendAsync(string data, CancellationToken ct = default)
{
await commLock.WaitAsync(ct);
try
{
await LoglessSendAsync(data, ct);
}
finally
{
commLock.Release();
}
}
private async Task<string> LoglessReadAsync(string delimiter, CancellationToken ct)
{
if (!_serialPort.IsOpen) throw new InvalidOperationException("串口未打开。");
delimiter ??= "\n";
var sb = new StringBuilder();
byte[] buffer = new byte[1024];
try
{
while (true)
{
ct.ThrowIfCancellationRequested();
int bytesRead;
if (ReadTimeout > 0)
{
// 核心修改:使用 WaitAsync 精准控制单次异步读取超时
bytesRead = await _serialPort.BaseStream.ReadAsync(buffer, 0, buffer.Length, ct)
.WaitAsync(TimeSpan.FromMilliseconds(ReadTimeout), ct)
.ConfigureAwait(false);
}
else
{
bytesRead = await _serialPort.BaseStream.ReadAsync(buffer, 0, buffer.Length, ct).ConfigureAwait(false);
}
if (bytesRead == 0) continue;
sb.Append(Encoding.UTF8.GetString(buffer, 0, bytesRead));
string currentText = sb.ToString();
int index = currentText.IndexOf(delimiter, StringComparison.Ordinal);
if (index >= 0)
{
return currentText.Substring(0, index).Trim();
}
}
}
catch (TimeoutException ex)
{
// 关键防污染策略串口发生命令错、超时不响应时WaitAsync 会立功捕获异常
// 我们直接清空串口驱动内部的输入缓冲区,把残余垃圾数据物理抹除,确保下一条指令安全、不掉线
ClearHardwareBuffer();
throw new TimeoutException($"串口读取超时(未收到结束符 '{delimiter}'),等待时间:{ReadTimeout} ms", ex);
}
}
public async Task<string> ReadAsync(string delimiter = "\n", CancellationToken ct = default)
{
await commLock.WaitAsync(ct);
try
{
return await LoglessReadAsync(delimiter, ct);
}
finally
{
commLock.Release();
}
}
public async Task<string> WriteReadAsync(string command, string delimiter = "\n", CancellationToken ct = default)
{
await commLock.WaitAsync(ct);
try
{
// 发送新命令前先冲洗一下缓冲区,双重保险
ClearHardwareBuffer();
await LoglessSendAsync(command, ct);
return await LoglessReadAsync(delimiter, ct);
}
finally
{
commLock.Release();
}
}
/// <summary>
/// 物理强制清空串口驱动层及软件层的缓冲区
/// </summary>
private void ClearHardwareBuffer()
{
try
{
if (_serialPort?.IsOpen == true)
{
_serialPort.DiscardInBuffer(); // 清空硬件接收缓冲区
_serialPort.DiscardOutBuffer(); // 清空硬件发送缓冲区
}
}
catch
{
// 忽略清理异常
}
}
public void Dispose()
{
if (_serialPort != null)
{
if (_serialPort.IsOpen) _serialPort.Close();
_serialPort.Dispose();
}
commLock?.Dispose();
}
}
}