using System; using System.Collections.Concurrent; using System.ComponentModel; using System.IO.Ports; using System.Text; using System.Threading.Tasks; using Newtonsoft.Json; using TeamAAS.Communication.Attributes; using TeamAAS.Communication.Base; using TeamAAS.Communication.Interfaces; using TeamAAS.Communication.Enums; namespace TeamAAS.Communication.Devices { [Communication("串口", "基础通讯", "RS232/RS485 串口通讯")] public class SerialCommunication : BindableCommunicationBase { [JsonIgnore] private SerialPort _serial; private readonly ConcurrentQueue _received = new ConcurrentQueue(); [JsonIgnore] private readonly StringBuilder _receiveBuffer = new StringBuilder(); [JsonIgnore] private System.Threading.Timer _reconnectTimer; [JsonIgnore] private volatile bool _hasConnectedOnce; [JsonIgnore] private volatile bool _manualDisconnect; [JsonIgnore] private volatile bool _isReconnecting; private string _portName = "COM1"; [Category("II.客户端配置"), DisplayName("1.端口名称"), Description("串口名称,如 COM1")] [TypeConverter(typeof(ComPortListConverter))] public string PortName { get { return _portName; } set { if (SetProperty(ref _portName, value)) Notify(nameof(EndpointUrl)); } } private BaudRates _baudRate = BaudRates.BR_9600; [Category("II.客户端配置"), DisplayName("2.波特率"), Description("串口波特率")] public BaudRates BaudRate { get { return _baudRate; } set { if (SetProperty(ref _baudRate, value)) Notify(nameof(EndpointUrl)); } } private Parity _parity = Parity.None; [Category("II.客户端配置"), DisplayName("3.校验位"), Description("串口校验位")] public Parity Parity { get { return _parity; } set { SetProperty(ref _parity, value); } } private StopBits _stopBits = StopBits.One; [Category("II.客户端配置"), DisplayName("4.停止位"), Description("串口停止位")] public StopBits StopBits { get { return _stopBits; } set { SetProperty(ref _stopBits, value); } } private int _dataBits = 8; [Category("II.客户端配置"), DisplayName("5.数据位"), Description("串口数据位")] public int DataBits { get { return _dataBits; } set { SetProperty(ref _dataBits, value); } } private Terminator _terminator = Terminator.None; [Category("III.数据格式"), DisplayName("1.结束符"), Description("发送时自动附加、接收时自动去除的结束符")] public Terminator Terminator { get { return _terminator; } set { SetProperty(ref _terminator, value); } } private DataEncoding _dataEncoding = DataEncoding.Default; [Category("III.数据格式"), DisplayName("2.编码格式"), Description("收发数据的编码格式")] public DataEncoding DataEncoding { get { return _dataEncoding; } set { SetProperty(ref _dataEncoding, value); } } [Browsable(false)] public override string EndpointUrl { get { return $"serial://{PortName}?baud={BaudRate}"; } set { if (!string.IsNullOrWhiteSpace(value) && value.StartsWith("serial://")) { var uri = new Uri(value); PortName = uri.Host; } } } [JsonIgnore, Browsable(false)] public override bool IsConnected => (_serial != null && _serial.IsOpen); public override event Action ConnectChangedEvent; public override event Action DataReceivedEvent; public override void Connect() { Disconnect(); _serial = new SerialPort(PortName, (int)BaudRate, Parity, DataBits, StopBits); _serial.Encoding = GetEncoding(); _serial.DataReceived += Serial_DataReceived; _serial.Open(); _manualDisconnect = false; _hasConnectedOnce = true; StopReconnectTimer(); ConnectChangedEvent?.Invoke(this, true); Notify(nameof(IsConnected)); } public override Task ConnectAsync() { return Task.Run(() => Connect()); } public override void Disconnect() { StopReconnectTimer(); _manualDisconnect = true; if (_serial != null) { try { _serial.Close(); } catch { } _serial.Dispose(); _serial = null; } ConnectChangedEvent?.Invoke(this, false); Notify(nameof(IsConnected)); } public override void Dispose() { Disconnect(); } private void Serial_DataReceived(object sender, SerialDataReceivedEventArgs e) { try { var sp = sender as SerialPort; if (sp == null) return; var text = sp.ReadExisting(); if (!string.IsNullOrEmpty(text)) { _receiveBuffer.Append(text); FlushReceivedMessages(_receiveBuffer); } } catch { if (_hasConnectedOnce && !_manualDisconnect) StartAutoReconnect(); } } /// /// 获取编码 /// public Encoding GetEncoding() { switch (DataEncoding) { case DataEncoding.ASCII: return Encoding.ASCII; case DataEncoding.UTF7: return Encoding.UTF7; case DataEncoding.UTF8: return Encoding.UTF8; case DataEncoding.UTF32: return Encoding.UTF32; case DataEncoding.Unicode: return Encoding.Unicode; case DataEncoding.BigEndianUnicode: return Encoding.BigEndianUnicode; case DataEncoding.GB2312: return Encoding.GetEncoding("gb2312"); default: return Encoding.Default; } } /// /// 获取结束符字符串 /// public string GetTerminatorString() { switch (Terminator) { case Terminator.CR: return "\r"; case Terminator.LF: return "\n"; case Terminator.CRLF: return "\r\n"; case Terminator.None: default: return string.Empty; } } /// /// 去除数据尾部结束符 /// public string TrimTerminator(string text) { if (string.IsNullOrEmpty(text)) return text; var term = GetTerminatorString(); if (string.IsNullOrEmpty(term)) return text; return text.EndsWith(term) ? text.Substring(0, text.Length - term.Length) : text; } /// /// 按结束符分帧:收到完整结束符才触发接收事件;无结束符时直接触发 /// private void FlushReceivedMessages(StringBuilder receiveBuffer) { var term = GetTerminatorString(); if (string.IsNullOrEmpty(term)) { if (receiveBuffer.Length > 0) { var text = receiveBuffer.ToString(); receiveBuffer.Clear(); _received.Enqueue(text); DataReceivedEvent?.Invoke(this, text); } return; } string content = receiveBuffer.ToString(); int idx; while ((idx = content.IndexOf(term, StringComparison.Ordinal)) >= 0) { var msg = content.Substring(0, idx); content = content.Substring(idx + term.Length); _received.Enqueue(msg); DataReceivedEvent?.Invoke(this, msg); } receiveBuffer.Clear(); receiveBuffer.Append(content); } /// /// 自动重连:初始化连接成功过,掉线后每5秒尝试连接一次 /// private void StartAutoReconnect() { if (_reconnectTimer != null) return; if (_serial != null) { try { _serial.Close(); } catch { } _serial.Dispose(); _serial = null; } ConnectChangedEvent?.Invoke(this, false); Notify(nameof(IsConnected)); _reconnectTimer = new System.Threading.Timer(ReconnectTimer_Elapsed, null, TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(5)); } private void ReconnectTimer_Elapsed(object state) { if (_manualDisconnect) { StopReconnectTimer(); return; } if (_isReconnecting) return; _isReconnecting = true; try { if (_serial != null) { try { _serial.Close(); } catch { } _serial.Dispose(); _serial = null; } var serial = new SerialPort(PortName, (int)BaudRate, Parity, DataBits, StopBits); serial.Encoding = GetEncoding(); serial.DataReceived += Serial_DataReceived; try { serial.Open(); _serial = serial; StopReconnectTimer(); ConnectChangedEvent?.Invoke(this, true); Notify(nameof(IsConnected)); } catch { try { serial.Dispose(); } catch { } } } finally { _isReconnecting = false; } } private void StopReconnectTimer() { var t = _reconnectTimer; _reconnectTimer = null; if (t != null) { try { t.Dispose(); } catch { } } } public void Send(string text) { if (_serial == null || !_serial.IsOpen) throw new InvalidOperationException("串口未连接。"); _serial.Write((text ?? string.Empty) + GetTerminatorString()); } public override object ReadValue(string address) { _received.TryDequeue(out var text); return text; } public override Task ReadValueAsync(string address) { return Task.FromResult(ReadValue(address)); } public override void WriteValue(string address, object value) { Send(value?.ToString() ?? string.Empty); } public override Task WriteValueAsync(string address, object value) { Send(value?.ToString() ?? string.Empty); return Task.CompletedTask; } } }