Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 152 additions & 0 deletions src/Drivers/Transport/SerialPortAdapter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
using System;
using System.IO.Ports;

namespace Elatec.NET.System
{
/// <summary>
/// Provides an abstraction layer around <see cref="SerialPort"/> for testing.
/// </summary>
internal interface ISerialPortAdapter : IDisposable
{
string PortName { get; set; }

int BaudRate { get; set; }

int DataBits { get; set; }

StopBits StopBits { get; set; }

Parity Parity { get; set; }

string NewLine { get; set; }

int ReadTimeout { get; set; }

int WriteTimeout { get; set; }

bool IsOpen { get; }

event SerialErrorReceivedEventHandler ErrorReceived;

void Open();

void Close();

void DiscardInBuffer();

void DiscardOutBuffer();

string ReadLine();

void WriteLine(string data);
}

internal sealed class SerialPortAdapter : ISerialPortAdapter
{
private readonly SerialPort _serialPort;

public SerialPortAdapter(string portName)
{
_serialPort = new SerialPort
{
PortName = portName,
BaudRate = 9600,
DataBits = 8,
StopBits = StopBits.One,
Parity = Parity.None,
NewLine = "\r"
};
}

public string PortName
{
get => _serialPort.PortName;
set => _serialPort.PortName = value;
}

public int BaudRate
{
get => _serialPort.BaudRate;
set => _serialPort.BaudRate = value;
}

public int DataBits
{
get => _serialPort.DataBits;
set => _serialPort.DataBits = value;
}

public StopBits StopBits
{
get => _serialPort.StopBits;
set => _serialPort.StopBits = value;
}

public Parity Parity
{
get => _serialPort.Parity;
set => _serialPort.Parity = value;
}

public string NewLine
{
get => _serialPort.NewLine;
set => _serialPort.NewLine = value;
}

public int ReadTimeout
{
get => _serialPort.ReadTimeout;
set => _serialPort.ReadTimeout = value;
}

public int WriteTimeout
{
get => _serialPort.WriteTimeout;
set => _serialPort.WriteTimeout = value;
}

public bool IsOpen => _serialPort.IsOpen;

public event SerialErrorReceivedEventHandler ErrorReceived
{
add => _serialPort.ErrorReceived += value;
remove => _serialPort.ErrorReceived -= value;
}

public void Open()
{
_serialPort.Open();
}

public void Close()
{
_serialPort.Close();
}

public void DiscardInBuffer()
{
_serialPort.DiscardInBuffer();
}

public void DiscardOutBuffer()
{
_serialPort.DiscardOutBuffer();
}

public string ReadLine()
{
return _serialPort.ReadLine();
}

public void WriteLine(string data)
{
_serialPort.WriteLine(data);
}

public void Dispose()
{
_serialPort.Dispose();
}
}
}
102 changes: 80 additions & 22 deletions src/Drivers/Transport/SerialPortTransport.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.IO;
using System.IO.Ports;
using System.Threading.Tasks;
using System.Threading;
using Elatec.NET.Interfaces;

namespace Elatec.NET.System
Expand All @@ -11,29 +12,27 @@ namespace Elatec.NET.System
/// </summary>
public class SerialPortTransport : IReaderTransport
{
private readonly SerialPort _serialPort;
private readonly ISerialPortAdapter _serialPort;
private readonly SemaphoreSlim _connectionLock = new SemaphoreSlim(1, 1);
private bool _disposed;

/// <summary>
/// Initializes a new instance of the <see cref="SerialPortTransport"/> class for the given port.
/// </summary>
/// <param name="portName">Name of the serial port used to reach the reader.</param>
public SerialPortTransport(string portName)
: this(CreateConfiguredPort(portName))
{
if (string.IsNullOrWhiteSpace(portName))
{
throw new ArgumentException("Port name must be provided.", nameof(portName));
}
}

_serialPort = new SerialPort
internal SerialPortTransport(ISerialPortAdapter serialPort)
{
if (serialPort == null)
{
PortName = portName,
BaudRate = 9600,
DataBits = 8,
StopBits = StopBits.One,
Parity = Parity.None,
NewLine = "\r"
};
throw new ArgumentNullException(nameof(serialPort));
}

_serialPort = serialPort;
_serialPort.ErrorReceived += OnErrorReceived;
}

Expand Down Expand Up @@ -63,69 +62,128 @@ public int WriteTimeout
/// <inheritdoc />
public async Task ConnectAsync()
{
await Task.Run(() =>
ThrowIfDisposed();

await _connectionLock.WaitAsync().ConfigureAwait(false);
try
{
ThrowIfDisposed();
if (!_serialPort.IsOpen)
{
_serialPort.Open();
}
}).ConfigureAwait(false);
}
finally
{
_connectionLock.Release();
}
}

/// <inheritdoc />
public async Task DisconnectAsync()
{
await Task.Run(() =>
ThrowIfDisposed();

await _connectionLock.WaitAsync().ConfigureAwait(false);
try
{
if (_serialPort.IsOpen)
{
_serialPort.DiscardInBuffer();
_serialPort.DiscardOutBuffer();
_serialPort.Close();
}
}).ConfigureAwait(false);
}
finally
{
_connectionLock.Release();
}
}

/// <inheritdoc />
public void DiscardInBuffer()
{
ThrowIfDisposed();
_serialPort.DiscardInBuffer();
}

/// <inheritdoc />
public void DiscardOutBuffer()
{
ThrowIfDisposed();
_serialPort.DiscardOutBuffer();
}

/// <inheritdoc />
public async Task<string> ReadLineAsync()
{
ThrowIfDisposed();
return await Task.Run(() => _serialPort.ReadLine()).ConfigureAwait(false);
}

/// <inheritdoc />
public async Task WriteLineAsync(string data)
{
ThrowIfDisposed();
await Task.Run(() => _serialPort.WriteLine(data)).ConfigureAwait(false);
}

/// <inheritdoc />
public void Dispose()
{
_serialPort.ErrorReceived -= OnErrorReceived;

if (_serialPort.IsOpen)
if (_disposed)
{
_serialPort.Close();
return;
}

_serialPort.Dispose();
_connectionLock.Wait();
try
{
if (_disposed)
{
return;
}

_disposed = true;
_serialPort.ErrorReceived -= OnErrorReceived;

if (_serialPort.IsOpen)
{
_serialPort.DiscardInBuffer();
_serialPort.DiscardOutBuffer();
_serialPort.Close();
}

_serialPort.Dispose();
}
finally
{
_connectionLock.Release();
_connectionLock.Dispose();
}
}

private void OnErrorReceived(object sender, SerialErrorReceivedEventArgs e)
{
ErrorReceived?.Invoke(this, new IOException($"Serial port error: {e.EventType}"));
}

private static ISerialPortAdapter CreateConfiguredPort(string portName)
{
if (string.IsNullOrWhiteSpace(portName))
{
throw new ArgumentException("Port name must be provided.", nameof(portName));
}

return new SerialPortAdapter(portName);
}

private void ThrowIfDisposed()
{
if (_disposed)
{
throw new ObjectDisposedException(nameof(SerialPortTransport));
}
}
}
}
Loading
Loading