Skip to content

Commit 65c76b3

Browse files
committed
Implemented retry mechanism for serial connection
Implemented AwaitableTransport interface for serial transport
1 parent 6db7da6 commit 65c76b3

File tree

4 files changed

+149
-31
lines changed

4 files changed

+149
-31
lines changed

MIN.SerialPort/MINSerialTransport.cs

+91-17
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Diagnostics;
23
using System.IO;
34
using System.IO.Ports;
45
using System.Threading;
@@ -10,10 +11,16 @@ namespace MIN.SerialPort
1011
/// Implements the MIN transport for a SerialPort connection.
1112
/// </summary>
1213
// ReSharper disable once UnusedMember.Global - public API
13-
public class MINSerialTransport : IMINTransport
14+
public class MINSerialTransport : IMINAwaitableTransport
1415
{
15-
private readonly System.IO.Ports.SerialPort serialPort;
16+
private System.IO.Ports.SerialPort serialPort;
17+
private readonly Func<System.IO.Ports.SerialPort> serialPortFactory;
18+
private readonly ManualResetEventSlim dataAvailableEvent = new ManualResetEventSlim();
1619

20+
/// <inheritdoc />
21+
public event EventHandler OnDisconnected;
22+
23+
1724
/// <summary>
1825
/// Initializes a new instance of the MIN serial transport.
1926
/// </summary>
@@ -27,7 +34,7 @@ public class MINSerialTransport : IMINTransport
2734
public MINSerialTransport(string portName, int baudRate, Parity parity = Parity.None, int dataBits = 8, StopBits stopBits = StopBits.One,
2835
Handshake handshake = Handshake.None, bool dtrEnable = false)
2936
{
30-
serialPort = new System.IO.Ports.SerialPort(portName, baudRate, parity, dataBits, stopBits)
37+
serialPortFactory = () => new System.IO.Ports.SerialPort(portName, baudRate, parity, dataBits, stopBits)
3138
{
3239
Handshake = handshake,
3340
DtrEnable = dtrEnable
@@ -40,7 +47,7 @@ public void Dispose()
4047
{
4148
try
4249
{
43-
serialPort.Dispose();
50+
serialPort?.Dispose();
4451
}
4552
catch
4653
{
@@ -52,46 +59,113 @@ public void Dispose()
5259
/// <inheritdoc />
5360
public void Connect(CancellationToken cancellationToken)
5461
{
55-
// TODO (must have - port from old source) retry
56-
serialPort.Open();
62+
dataAvailableEvent.Reset();
5763

58-
// TODO (must have - port from old source) detect disconnects and report back
64+
65+
while ((serialPort == null || !serialPort.IsOpen) && !cancellationToken.IsCancellationRequested)
66+
{
67+
try
68+
{
69+
if (serialPort == null)
70+
serialPort = serialPortFactory();
71+
72+
Debug.Assert(serialPort != null);
73+
serialPort.Open();
74+
75+
serialPort.DataReceived += (sender, args) =>
76+
{
77+
dataAvailableEvent.Set();
78+
};
79+
80+
serialPort.ErrorReceived += (sender, args) =>
81+
{
82+
Disconnect();
83+
};
84+
}
85+
catch
86+
{
87+
Thread.Sleep(500);
88+
}
89+
}
90+
}
91+
92+
93+
private void Disconnect()
94+
{
95+
try
96+
{
97+
serialPort?.Dispose();
98+
}
99+
catch
100+
{
101+
// Ignored
102+
}
103+
104+
serialPort = null;
105+
dataAvailableEvent.Reset();
106+
107+
OnDisconnected?.Invoke(this, EventArgs.Empty);
59108
}
60109

61110

62111
/// <inheritdoc />
63112
public void Reset()
64113
{
65-
if (serialPort.IsOpen)
114+
if (serialPort != null && serialPort.IsOpen)
66115
serialPort.DiscardInBuffer();
67116
}
68117

69118

70119
/// <inheritdoc />
71120
public void Write(byte[] data, CancellationToken cancellationToken)
72121
{
73-
if (!serialPort.IsOpen)
122+
if (serialPort == null || !serialPort.IsOpen)
74123
return;
75-
76-
serialPort.Write(data, 0, data.Length);
124+
125+
try
126+
{
127+
serialPort.Write(data, 0, data.Length);
128+
}
129+
catch
130+
{
131+
Disconnect();
132+
}
77133
}
78134

79135

80136
/// <inheritdoc />
81137
public byte[] ReadAll()
82138
{
83-
if (!serialPort.IsOpen)
139+
if (serialPort == null || !serialPort.IsOpen)
84140
return Array.Empty<byte>();
85-
141+
86142
var bytesToRead = serialPort.BytesToRead;
87143
if (bytesToRead == 0)
88144
return Array.Empty<byte>();
89145

90-
var data = new byte[bytesToRead];
91-
if (serialPort.Read(data, 0, bytesToRead) < bytesToRead)
92-
throw new IOException("SerialPort lied about the available BytesToRead");
146+
try
147+
{
148+
var data = new byte[bytesToRead];
149+
if (serialPort.Read(data, 0, bytesToRead) < bytesToRead)
150+
throw new IOException("SerialPort lied about the available BytesToRead");
151+
152+
if (serialPort.BytesToRead == 0)
153+
dataAvailableEvent.Reset();
154+
155+
return data;
156+
}
157+
catch
158+
{
159+
Disconnect();
160+
return Array.Empty<byte>();
161+
}
162+
}
163+
93164

94-
return data;
165+
/// <inheritdoc />
166+
public WaitHandle DataAvailable()
167+
{
168+
return dataAvailableEvent.WaitHandle;
95169
}
96170
}
97171
}

MIN/Abstractions/IMINProtocol.cs

+7-2
Original file line numberDiff line numberDiff line change
@@ -112,9 +112,14 @@ public interface IMINProtocol : IDisposable
112112
/// An event which is called when an incoming frame arrives.
113113
/// </summary>
114114
event MINFrameEventHandler OnFrame;
115+
116+
/// <summary>
117+
/// An event which is called when a reset has been requested by the target.
118+
/// </summary>
119+
event MINConnectionStateEventHandler OnReset;
115120
}
116-
117-
121+
122+
118123
/// <summary>
119124
/// Statistics on the MIN protocol since the last start.
120125
/// </summary>

MIN/Abstractions/IMINTransport.cs

+5
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ public interface IMINTransport : IDisposable
3636
/// </summary>
3737
/// <returns>The raw data available</returns>
3838
byte[] ReadAll();
39+
40+
/// <summary>
41+
/// Event which is raised when the transport disconnects.
42+
/// </summary>
43+
event EventHandler OnDisconnected;
3944
}
4045

4146

MIN/MINProtocol.cs

+46-12
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,10 @@ public Task Reset()
186186
/// <inheritdoc />
187187
public event MINFrameEventHandler OnFrame;
188188

189-
189+
/// <inheritdoc />
190+
public event MINConnectionStateEventHandler OnReset;
191+
192+
190193
// ReSharper disable once SuggestBaseTypeForParameter - I like the explicitness
191194
private static void ValidateFrame(byte id, byte[] payload)
192195
{
@@ -246,15 +249,30 @@ private void RunWorker(CancellationTokenSource cancellationTokenSource)
246249
var waitHandlesArray = waitHandles.ToArray();
247250

248251

249-
// TODO (must have) connect transport, handle OnDisconnect
250-
transport.Connect(cancellationTokenSource.Token);
251-
OnConnected?.Invoke(this, EventArgs.Empty);
252+
var transportConnected = false;
253+
transport.OnDisconnected += (sender, args) =>
254+
{
255+
OnDisconnected?.Invoke(this, EventArgs.Empty);
256+
transportConnected = false;
257+
};
252258

253259

254260
while (!cancellationTokenSource.Token.IsCancellationRequested)
255261
{
256262
try
257263
{
264+
if (!transportConnected)
265+
{
266+
transport.Connect(cancellationTokenSource.Token);
267+
if (cancellationTokenSource.Token.IsCancellationRequested)
268+
break;
269+
270+
OnConnected?.Invoke(this, EventArgs.Empty);
271+
transportConnected = true;
272+
}
273+
274+
275+
258276
DateTime now;
259277
bool remoteActive;
260278

@@ -281,6 +299,9 @@ private void RunWorker(CancellationTokenSource cancellationTokenSource)
281299
}
282300
}
283301

302+
if (cancellationTokenSource.Token.IsCancellationRequested)
303+
break;
304+
284305
var incomingData = transport.ReadAll();
285306
if (incomingData.Length > 0)
286307
{
@@ -289,20 +310,30 @@ private void RunWorker(CancellationTokenSource cancellationTokenSource)
289310
}
290311

291312

292-
var queuedFrame = GetNextQueuedFrame();
293-
if (queuedFrame != null)
313+
if (cancellationTokenSource.Token.IsCancellationRequested)
314+
break;
315+
316+
if (transportConnected)
294317
{
295-
if (ProcessQueuedFrame(queuedFrame, cancellationTokenSource.Token))
296-
FrameSent(queuedFrame);
318+
var queuedFrame = GetNextQueuedFrame();
319+
if (queuedFrame != null)
320+
{
321+
if (ProcessQueuedFrame(queuedFrame, cancellationTokenSource.Token))
322+
FrameSent(queuedFrame);
297323

298-
yield = false;
324+
yield = false;
325+
}
299326
}
327+
else
328+
yield = false;
300329

330+
if (cancellationTokenSource.Token.IsCancellationRequested)
331+
break;
301332

302333
now = timeProvider.Now();
303334
remoteActive = now - lastReceivedFrame < IdleTimeout;
304335

305-
if (now - lastAck > AckRetransmitTimeout && remoteActive)
336+
if (now - lastAck > AckRetransmitTimeout && remoteActive && transportConnected)
306337
SendAck(cancellationTokenSource.Token);
307338
}
308339
catch (OperationCanceledException)
@@ -311,7 +342,11 @@ private void RunWorker(CancellationTokenSource cancellationTokenSource)
311342
}
312343

313344

345+
346+
if (cancellationTokenSource.Token.IsCancellationRequested)
347+
break;
314348

349+
315350
// If any frames were processed, spin once more to check again
316351
if (!yield)
317352
continue;
@@ -622,8 +657,7 @@ private void ProcessReceivedResetFrame()
622657
}
623658

624659
InternalReset(true);
625-
626-
// TODO (nice to have) raise OnReset event?
660+
OnReset?.Invoke(this, EventArgs.Empty);
627661
}
628662

629663

0 commit comments

Comments
 (0)