Dreamine.Communication.Serial 1.0.2
Dreamine.Communication.Serial 통신 기능과 관련 API를 제공합니다.
로딩중...
검색중...
일치하는것 없음
SerialPortTransport.cs
이 파일의 문서화 페이지로 가기
1using System.IO;
2using System.IO.Ports;
3using Dreamine.Communication.Abstractions.Enums;
4using Dreamine.Communication.Abstractions.Interfaces;
5using Dreamine.Communication.Abstractions.Models;
6using Dreamine.Communication.Core.Framing;
7using Dreamine.Communication.Core.Protocols;
9
11
20public sealed class SerialPortTransport : IMessageTransport
21{
39 private readonly IMessageProtocolAdapter _protocolAdapter;
48 private readonly IMessageFrameCodec _frameCodec;
49
58 private SerialPort? _serialPort;
67 private CancellationTokenSource? _receiveLoopCts;
76 private Task? _receiveLoopTask;
85 private int _state = (int)ConnectionState.Disconnected;
86
104 : this(
105 options,
106 new DreamineEnvelopeProtocolAdapter(),
107 new LengthPrefixedMessageFrameCodec())
108 {
109 }
110
153 IMessageProtocolAdapter protocolAdapter,
154 IMessageFrameCodec frameCodec)
155 {
156 _options = options ?? throw new ArgumentNullException(nameof(options));
157 _protocolAdapter = protocolAdapter ?? throw new ArgumentNullException(nameof(protocolAdapter));
158 _frameCodec = frameCodec ?? throw new ArgumentNullException(nameof(frameCodec));
159
161 }
162
171 public ConnectionState State => (ConnectionState)Volatile.Read(ref _state);
172
181 public TransportKind Kind => TransportKind.Serial;
182
191 public event EventHandler<MessageEnvelope>? MessageReceived;
192
217 public Task ConnectAsync(CancellationToken cancellationToken = default)
218 {
219 if (State is ConnectionState.Connected or ConnectionState.Connecting)
220 {
221 return Task.CompletedTask;
222 }
223
224 cancellationToken.ThrowIfCancellationRequested();
225
226 SetState(ConnectionState.Connecting);
227
228 try
229 {
230 _serialPort = new SerialPort(
231 _options.PortName,
232 _options.BaudRate,
233 _options.Parity,
234 _options.DataBits,
235 _options.StopBits)
236 {
237 Handshake = _options.Handshake,
238 ReadTimeout = _options.ReadTimeoutMs,
239 WriteTimeout = _options.WriteTimeoutMs,
240 ReadBufferSize = _options.ReadBufferSize,
241 WriteBufferSize = _options.WriteBufferSize
242 };
243
244 _serialPort.Open();
245
246 SetState(ConnectionState.Connected);
247
248 _receiveLoopCts = new CancellationTokenSource();
249 _receiveLoopTask = Task.Run(
251 _receiveLoopCts.Token);
252
253 return Task.CompletedTask;
254 }
255 catch
256 {
257 SetState(ConnectionState.Faulted);
258
259 _serialPort?.Dispose();
260 _serialPort = null;
261
262 throw;
263 }
264 }
265
290 public async Task DisconnectAsync(CancellationToken cancellationToken = default)
291 {
292 if (State == ConnectionState.Disconnected)
293 {
294 return;
295 }
296
297 SetState(ConnectionState.Disconnecting);
298
299 _receiveLoopCts?.Cancel();
300
301 if (_serialPort is not null)
302 {
303 try
304 {
305 if (_serialPort.IsOpen)
306 {
307 _serialPort.Close();
308 }
309 }
310 finally
311 {
312 _serialPort.Dispose();
313 }
314 }
315
316 if (_receiveLoopTask is not null)
317 {
318 try
319 {
320 await _receiveLoopTask.ConfigureAwait(false);
321 }
322 catch (OperationCanceledException)
323 {
324 }
325 catch (ObjectDisposedException)
326 {
327 }
328 catch (InvalidOperationException)
329 {
330 }
331 catch (IOException)
332 {
333 }
334 }
335
336 _receiveLoopCts?.Dispose();
337 _receiveLoopCts = null;
338 _receiveLoopTask = null;
339 _serialPort = null;
340
341 cancellationToken.ThrowIfCancellationRequested();
342
343 SetState(ConnectionState.Disconnected);
344 }
345
386 public async Task SendAsync(
387 MessageEnvelope message,
388 CancellationToken cancellationToken = default)
389 {
390 ArgumentNullException.ThrowIfNull(message);
391
392 if (_serialPort is null ||
393 !_serialPort.IsOpen ||
394 State != ConnectionState.Connected)
395 {
396 throw new InvalidOperationException("Serial port is not connected.");
397 }
398
399 try
400 {
401 var payload = _protocolAdapter.Encode(message);
402
403 await _frameCodec.WriteFrameAsync(
404 _serialPort.BaseStream,
405 payload,
406 cancellationToken)
407 .ConfigureAwait(false);
408 }
409 catch
410 {
411 SetState(ConnectionState.Faulted);
413
414 throw;
415 }
416 }
417
434 public async ValueTask DisposeAsync()
435 {
436 await DisconnectAsync().ConfigureAwait(false);
437 }
438
463 private async Task ReceiveLoopAsync(CancellationToken cancellationToken)
464 {
465 if (_serialPort is null)
466 {
467 return;
468 }
469
470 try
471 {
472 while (!cancellationToken.IsCancellationRequested &&
473 State == ConnectionState.Connected)
474 {
475 var payload = await _frameCodec.ReadFrameAsync(
476 _serialPort.BaseStream,
477 cancellationToken)
478 .ConfigureAwait(false);
479
480 if (payload is null)
481 {
482 break;
483 }
484
485 var message = _protocolAdapter.Decode(payload);
486 MessageReceived?.Invoke(this, message);
487 }
488
489 if (!cancellationToken.IsCancellationRequested &&
490 State == ConnectionState.Connected)
491 {
492 SetState(ConnectionState.Disconnected);
493 }
494 }
495 catch (OperationCanceledException)
496 {
497 }
498 catch (ObjectDisposedException)
499 {
500 }
501 catch (InvalidOperationException)
502 {
503 if (!cancellationToken.IsCancellationRequested)
504 {
505 SetState(ConnectionState.Faulted);
506 }
507 }
508 catch (IOException)
509 {
510 if (!cancellationToken.IsCancellationRequested)
511 {
512 SetState(ConnectionState.Faulted);
513 }
514 }
515 catch
516 {
517 if (!cancellationToken.IsCancellationRequested)
518 {
519 SetState(ConnectionState.Faulted);
521 }
522 }
523 }
524
533 private void CleanupSerialPort()
534 {
535 try
536 {
537 if (_serialPort is not null && _serialPort.IsOpen)
538 {
539 _serialPort.Close();
540 }
541 }
542 catch
543 {
544 }
545
546 try
547 {
548 _serialPort?.Dispose();
549 }
550 catch
551 {
552 }
553
554 _serialPort = null;
555 }
556
573 private void SetState(ConnectionState state)
574 {
575 Interlocked.Exchange(ref _state, (int)state);
576 }
577
610 private static void ValidateOptions(SerialPortTransportOptions options)
611 {
612 ArgumentException.ThrowIfNullOrWhiteSpace(options.PortName);
613
614 if (options.BaudRate <= 0)
615 {
616 throw new ArgumentOutOfRangeException(nameof(options.BaudRate));
617 }
618
619 if (options.DataBits is < 5 or > 8)
620 {
621 throw new ArgumentOutOfRangeException(nameof(options.DataBits));
622 }
623
624 if (options.ReadTimeoutMs < System.IO.Ports.SerialPort.InfiniteTimeout)
625 {
626 throw new ArgumentOutOfRangeException(nameof(options.ReadTimeoutMs));
627 }
628
629 if (options.WriteTimeoutMs < System.IO.Ports.SerialPort.InfiniteTimeout)
630 {
631 throw new ArgumentOutOfRangeException(nameof(options.WriteTimeoutMs));
632 }
633
634 if (options.ReadBufferSize <= 0)
635 {
636 throw new ArgumentOutOfRangeException(nameof(options.ReadBufferSize));
637 }
638
639 if (options.WriteBufferSize <= 0)
640 {
641 throw new ArgumentOutOfRangeException(nameof(options.WriteBufferSize));
642 }
643 }
644}
async Task ReceiveLoopAsync(CancellationToken cancellationToken)
Task ConnectAsync(CancellationToken cancellationToken=default)
async Task DisconnectAsync(CancellationToken cancellationToken=default)
SerialPortTransport(SerialPortTransportOptions options, IMessageProtocolAdapter protocolAdapter, IMessageFrameCodec frameCodec)
static void ValidateOptions(SerialPortTransportOptions options)
async Task SendAsync(MessageEnvelope message, CancellationToken cancellationToken=default)