一个设备,三种指令 Cmd_Req、Cmd_Res、Cmd_Reprot。发送Cmd_Req设备返回Cmd_Res表示成功,之后人为可以对设备执行特定操作,当执行特定操作时设备主动上报Cmd_Reprot。
Cmd_Res是Write Cmd_Req之后主动读的,Cmd_Reprot能通过DataReceived被动接收的。
在运行时,会发生发送 Cmd_Req 后读到 Cmd_Reprot以及DataReceived时读到Cmd_Res的情况,要如何解决这个问题呢?还是说我这方式本身就是错误的。
主要代码是这样的
//CMD_REPORT,CMD_RES,CMD_REQ 为方便写使用代码定义的三个readonly bool isSend = false; private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e) { if(isSend) return; byte[] Cmd_Report = new byte[serialPort.BytesToRead]; serialPort.Read(Cmd_Report, 0, serialPort.BytesToRead); //实际运行时,Cmd_Report的内容有可能CMD_RES相同 //DataEqual是判定两个BYTE[]的长度及其中的数据是相同 if (DataEqual(Cmd_Report, CMD_REPORT)) { //do something result } } public void DoWork() { //do something byte[] Cmd_Req = new byte[10]; byte[] Cmd_Res = new byte[CMD_RES.Length]; isSend = true; serialPort.Write(Cmd_Req, 0, Cmd_Req.Length ); stopWatch.Start(); while(serialPort.BytesToRead < CMD_RES.Length && stopWatch.ElapsedMilliseconds< 1000) System.Threading.Thread.Sleep(50); int resCount = 0; if (serialPort.BytesToRead < CMD_RES.Length) resCount = serialPort.Read(Cmd_Res, 0, serialPort.BytesToRead); else resCount = serialPort.Read(Cmd_Res, 0, CMD_RES.Length); isSend = false; //实际运行时,Cmd_Res的内容有可能和CMD_REPORT相同 if (DataEqual(Cmd_Res, CMD_RES)) { //do something AA } else { //do something BB } }
不管多么简单,通讯首先要分包处理(接收方会设定超时机制),你这状态量完全是多余的 —— 除非你玩控制线,不过对于单片机,不少板子是未实现除235之外的接口。
谢谢,通过Stackoverflow一个兄弟的启发,我自己解决了。
自己解决了把在Stackoverflow上的回答搬过来。
Inspired by the code of JohnEphraimTugado, I thought of the solution and passed the test in the application scenario.
1 using System; 2 using System.Collections.Generic; 3 using System.IO.Ports; 4 using System.Linq; 5 using System.Text; 6 using System.Threading; 7 8 public class SerialPortHandler 9 { 10 public delegate void OnReportHandler(byte[] data); 11 public delegate void OnReadExceptionHander(Exception error); 12 public delegate void OnHandlingExceptionHandler(Exception error); 13 14 public SerialPortHandler(string portName, Predicate<byte[]> reportPredicate, Func<Queue<byte>, byte[]> dequeueFunc) 15 : this(reportPredicate, dequeueFunc) 16 { 17 this._serialPort = new SerialPort(portName); 18 } 19 20 public SerialPortHandler(string portName, int baudRate, Predicate<byte[]> reportPredicate, Func<Queue<byte>, byte[]> dequeueFunc) 21 : this(reportPredicate, dequeueFunc) 22 { 23 this._serialPort = new SerialPort(portName, baudRate); 24 } 25 26 public SerialPortHandler(string portName, int baudRate, Parity parity, int dataBits, StopBits stopBits, Predicate<byte[]> reportPredicate, Func<Queue<byte>, byte[]> dequeueFunc) 27 : this(reportPredicate, dequeueFunc) 28 { 29 this._serialPort = new SerialPort(portName, baudRate, parity, dataBits, stopBits); 30 } 31 32 private SerialPortHandler(Predicate<byte[]> reportPredicate, Func<Queue<byte>, byte[]> dequeueFunc) 33 { 34 _thrdRead = new Thread(new ThreadStart(Read)); 35 _thrdHandle = new Thread(new ThreadStart(DataHandling)); 36 _isRun = false; 37 _quCmdRespone = new Queue<byte[]>(); 38 _quReceiveBuff = new Queue<byte>(); 39 _cmdResponseReset = new AutoResetEvent(false); 40 _reportPredicate = reportPredicate; 41 _dequeueFunc = dequeueFunc; 42 } 43 44 SerialPort _serialPort; 45 Thread _thrdRead; 46 Thread _thrdHandle; 47 bool _isRun; 48 /// <summary> 49 /// Save all data read from the serial port 50 /// </summary> 51 Queue<byte> _quReceiveBuff; 52 /// <summary> 53 /// Save the response of the last command 54 /// </summary> 55 Queue<byte[]> _quCmdRespone; 56 AutoResetEvent _cmdResponseReset; 57 bool _isSending; 58 /// <summary> 59 /// A method to determine whether a byte[] is a spontaneous report of a serial port 60 /// </summary> 61 Predicate<byte[]> _reportPredicate; 62 /// <summary> 63 /// Dequeuing a command from the received data queue method 64 /// </summary> 65 Func<Queue<byte>, byte[]> _dequeueFunc; 66 67 /// <summary> 68 /// Called when the serial interface is actively reporting data. 69 /// </summary> 70 public event OnReportHandler OnReport; 71 public event OnReadExceptionHander OnReadException; 72 public event OnHandlingExceptionHandler OnHandlingException; 73 74 public bool IsOpen 75 { 76 get { return this._serialPort == null ? false : this._serialPort.IsOpen; } 77 } 78 79 /// <summary> 80 /// Read data from serial port. 81 /// </summary> 82 private void Read() 83 { 84 while (_isRun) 85 { 86 try 87 { 88 if (this._serialPort == null || !this._serialPort.IsOpen || this._serialPort.BytesToRead == 0) 89 { 90 SpinWait.SpinUntil(() => this._serialPort != null && this._serialPort.IsOpen && this._serialPort.BytesToRead > 0, 10); 91 continue; 92 } 93 byte[] data = new byte[this._serialPort.BytesToRead]; 94 this._serialPort.Read(data, 0, data.Length); 95 Array.ForEach(data, b => _quReceiveBuff.Enqueue(b)); 96 } 97 catch (InvalidOperationException) 98 { 99 if (!_isRun || this._serialPort ==null) 100 return; 101 else 102 this._serialPort.Open(); 103 } 104 catch (Exception ex) 105 { 106 this.OnReadException?.BeginInvoke(new Exception(string.Format("An error occurred in the reading processing: {0}", ex.Message), ex), null, null); 107 } 108 } 109 } 110 111 /// <summary> 112 /// Data processing 113 /// </summary> 114 private void DataHandling() 115 { 116 while (_isRun) 117 { 118 try 119 { 120 if (_quReceiveBuff.Count == 0) 121 { 122 SpinWait.SpinUntil(() => _quReceiveBuff.Count > 0, 10); 123 continue; 124 } 125 byte[] data = _dequeueFunc(_quReceiveBuff); 126 if (data == null || data.Length == 0) 127 { 128 SpinWait.SpinUntil(() => false, 10); 129 continue; 130 } 131 132 if (_reportPredicate(data)) 133 OnReport?.BeginInvoke(data, null, null); //If the data is spontaneously reported by the serial port, the OnReport event is called 134 else 135 { //If the command response returned by the serial port, join the command response queue 136 if (_quCmdRespone.Count > 0) 137 _quCmdRespone.Clear(); //The queue is cleared to ensure that if a command timed out does not affect subsequent command results 138 139 _quCmdRespone.Enqueue(data); 140 _cmdResponseReset.Set(); 141 } 142 } 143 catch (Exception ex) 144 { 145 this.OnHandlingException?.BeginInvoke(new Exception(string.Format("An error occurred in the data processing: {0}", ex.Message), ex), null, null); 146 } 147 } 148 } 149 150 /// <summary> 151 /// Read the response of the last command. 152 /// </summary> 153 /// <param name="timeOut"></param> 154 /// <returns></returns> 155 private byte[] ReadCommandResponse(int timeOut) 156 { 157 byte[] buffer = null; 158 if (_cmdResponseReset.WaitOne(timeOut, false)) 159 buffer = _quCmdRespone.Dequeue(); 160 return buffer; 161 } 162 163 /// <summary> 164 /// Send a command 165 /// </summary> 166 /// <param name="sendData">command buff</param> 167 /// <param name="receiveData">REF: response of command</param> 168 /// <param name="timeout">timeout(millseconds)</param> 169 /// <returns>count of response, -1: failure, -2: port is busy</returns> 170 public int SendCommand(byte[] sendData, ref byte[] receiveData, int timeout) 171 { 172 if (_isSending) 173 return -2; 174 if (this._serialPort.IsOpen) 175 { 176 try 177 { 178 _isSending = true; 179 this._serialPort.Write(sendData, 0, sendData.Length); 180 int ret = 0; 181 receiveData = ReadCommandResponse(timeout); 182 ret = receiveData == null ? -1 : receiveData.Length; 183 return ret; 184 } 185 catch (Exception ex) 186 { 187 throw new Exception(string.Format("Send command is failure:{0}", ex.Message), ex); 188 } 189 finally 190 { 191 _isSending = false; 192 } 193 } 194 return -1; 195 } 196 197 public bool Open() 198 { 199 200 if (this._serialPort == null || this._serialPort.IsOpen) 201 return false; 202 this._serialPort.Open(); 203 _isRun = true; 204 _thrdRead.Start(); 205 _thrdHandle.Start(); 206 return true; 207 } 208 209 public bool Close() 210 { 211 _isRun = false; 212 if (_thrdHandle.IsAlive) 213 _thrdHandle.Join(); 214 if (_thrdRead.IsAlive) 215 _thrdRead.Join(); 216 if (this._serialPort == null) 217 return false; 218 if (this._serialPort.IsOpen) 219 this._serialPort.Close(); 220 return true; 221 } 222 }
Usage
1 SerialPortHandler spHandler; 2 public void Init() 3 { 4 SerialPortHandler spHandler = new SerialPortHandler("COM1", IsReport, DequeueResponse); 5 spHandler.OnReport += SpHandler_OnReport; 6 } 7 8 bool IsReport(byte[] data) 9 { 10 //Determines whether the command is Cmd_Reprot 11 return true; 12 } 13 14 byte[] DequeueResponse(Queue<byte> quReceive) 15 { 16 byte[] response = null; 17 //Dequeuing a valid response based protocol rules 18 return response; 19 } 20 21 private void SpHandler_OnReport(byte[] data) 22 { 23 if (DataEqual(Cmd_Report, CMD_REPORT)) 24 { 25 //do something X about data then save data 26 } 27 } 28 29 public void DoWork() 30 { 31 //do something 32 byte[] Cmd_Req = null; 33 byte[] Cmd_Res = new byte[CMD_RES.Length]; 34 int ret = spHandler.SendCommand(Cmd_Req, Cmd_Req, 1000); 35 36 if (ret > 0 && DataEqual(Cmd_Res, CMD_RES)) 37 { 38 //create data, do something A about data 39 } 40 else 41 { 42 //do something B 43 } 44 }
我把它发布的nuget了,有需要可以拿来用
1 Install-Package SerialHandler -Version 0.0.1-beta3