Class
Sealed
public sealed class SerialTransport : SharpMeter.Transport.ITransport
Namespace: SharpMeter.Transport
Serial port transport implementation for PSEM communication.
Inheritance
Implements: SharpMeter.Transport.ITransport
Constructors
| Name | Description |
|---|---|
SerialTransport(TransportOptions options, SerialTransport> logger) |
Initializes a new instance of SerialTransport. |
SerialTransport(TransportOptions options, SerialTransport> logger)
SerialTransport.SerialTransport(TransportOptions options, ILogger<SerialTransport> logger)
Initializes a new instance of SerialTransport.
Parameters
| Name | Type | Description |
|---|---|---|
options | SharpMeter.Transport.TransportOptions | The transport configuration options. |
logger | ILogger<SharpMeter.Transport.SerialTransport> | The logger instance. |
Properties
| Name | Description |
|---|---|
IsConnected |
Gets a value indicating whether the transport connection is open. |
IsConnected
bool SerialTransport.IsConnected { get; }
Gets a value indicating whether the transport connection is open.
Methods
| Name | Description |
|---|---|
ConnectAsync(CancellationToken cancellationToken) |
Opens the transport connection. |
DisconnectAsync(CancellationToken cancellationToken) |
Closes the transport connection. |
DisposeAsync() |
|
ReceiveAsync(Memory<byte> buffer, CancellationToken cancellationToken) |
Receives raw bytes from the transport. |
SendAsync(ReadOnlyMemory<byte> data, CancellationToken cancellationToken) |
Sends raw bytes over the transport. |
SendControlAsync(byte controlByte, CancellationToken cancellationToken) |
Sends a single control byte (ACK, NAK, CAN). |
ConnectAsync(CancellationToken cancellationToken)
ValueTask<Result<bool>> SerialTransport.ConnectAsync(CancellationToken cancellationToken = null)
Opens the transport connection.
Parameters
| Name | Type | Description |
|---|---|---|
cancellationToken | CancellationToken | Cancellation token. |
Returns: A result indicating success or transport error.
DisconnectAsync(CancellationToken cancellationToken)
ValueTask SerialTransport.DisconnectAsync(CancellationToken cancellationToken = null)
Closes the transport connection.
Parameters
| Name | Type | Description |
|---|---|---|
cancellationToken | CancellationToken | Cancellation token. |
ReceiveAsync(Memory buffer, CancellationToken cancellationToken)
ValueTask<Result<int>> SerialTransport.ReceiveAsync(Memory<byte> buffer, CancellationToken cancellationToken = null)
Receives raw bytes from the transport.
Parameters
| Name | Type | Description |
|---|---|---|
buffer | Memory<byte> | The buffer to receive into. |
cancellationToken | CancellationToken | Cancellation token. |
Returns: A result containing the number of bytes received, or a transport error.
SendAsync(ReadOnlyMemory data, CancellationToken cancellationToken)
ValueTask<Result<bool>> SerialTransport.SendAsync(ReadOnlyMemory<byte> data, CancellationToken cancellationToken = null)
Sends raw bytes over the transport.
Parameters
| Name | Type | Description |
|---|---|---|
data | ReadOnlyMemory<byte> | The data to send. |
cancellationToken | CancellationToken | Cancellation token. |
Returns: A result indicating success or transport error.
SendControlAsync(byte controlByte, CancellationToken cancellationToken)
ValueTask SerialTransport.SendControlAsync(byte controlByte, CancellationToken cancellationToken = null)
Sends a single control byte (ACK, NAK, CAN).
Parameters
| Name | Type | Description |
|---|---|---|
controlByte | byte | The control byte to send. |
cancellationToken | CancellationToken | Cancellation token. |
Type Relationships
classDiagram
style SerialTransport fill:#f9f,stroke:#333,stroke-width:2px
SerialTransport ..|> ITransport : implements
View Source
/// <summary>
/// Serial port transport implementation for PSEM communication.
/// </summary>
public sealed partial class SerialTransport : ITransport
{
#region Constructor
/// <summary>
/// Initializes a new instance of <see cref = "SerialTransport"/>.
/// </summary>
/// <param name = "options">The transport configuration options.</param>
/// <param name = "logger">The logger instance.</param>
public SerialTransport(TransportOptions options, ILogger<SerialTransport> logger)
{
_options = options;
_logger = logger;
}
#endregion
#region IAsyncDisposable
/// <inheritdoc/>
public async ValueTask DisposeAsync() => await DisconnectAsync();
#endregion
#region Log
private static partial class Log
{
[LoggerMessage(Level = LogLevel.Information, Message = "Serial port {PortName} opened at {BaudRate} baud")]
public static partial void PortOpened(ILogger logger, string portName, int baudRate);
[LoggerMessage(Level = LogLevel.Information, Message = "Serial port {PortName} closed")]
public static partial void PortClosed(ILogger logger, string portName);
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to open serial port {PortName}")]
public static partial void PortOpenFailed(ILogger logger, Exception ex, string portName);
[LoggerMessage(Level = LogLevel.Debug, Message = "TX [{Length} bytes]: {Data}")]
public static partial void DataSent(ILogger logger, int length, string data);
[LoggerMessage(Level = LogLevel.Debug, Message = "RX [{Length} bytes]: {Data}")]
public static partial void DataReceived(ILogger logger, int length, string data);
[LoggerMessage(Level = LogLevel.Error, Message = "Serial send failed")]
public static partial void SendFailed(ILogger logger, Exception ex);
[LoggerMessage(Level = LogLevel.Error, Message = "Serial receive failed")]
public static partial void ReceiveFailed(ILogger logger, Exception ex);
}
#endregion
#region Fields
private readonly TransportOptions _options;
private readonly ILogger<SerialTransport> _logger;
private SerialPort? _serialPort;
#endregion
#region ITransport Members
/// <inheritdoc/>
public bool IsConnected => _serialPort?.IsOpen ?? false;
/// <inheritdoc/>
public async ValueTask<Result<bool>> ConnectAsync(CancellationToken cancellationToken = default)
{
try
{
if (_serialPort is { IsOpen: true })
await DisconnectAsync(cancellationToken);
_serialPort = new SerialPort
{
PortName = _options.PortName,
BaudRate = _options.BaudRate,
DataBits = _options.DataBits,
Parity = Parity.None,
StopBits = StopBits.One,
ReadTimeout = _options.ReadTimeoutMs,
WriteTimeout = _options.WriteTimeoutMs,
ReadBufferSize = _options.ReceiveBufferSize
};
_serialPort.Open();
_serialPort.DiscardInBuffer();
_serialPort.DiscardOutBuffer();
Log.PortOpened(_logger, _options.PortName, _options.BaudRate);
await Task.CompletedTask;
return true;
}
catch (IOException ex)
{
Log.PortOpenFailed(_logger, ex, _options.PortName);
return PsemError.Transport($"Failed to open {_options.PortName}: {ex.Message}");
}
catch (UnauthorizedAccessException ex)
{
Log.PortOpenFailed(_logger, ex, _options.PortName);
return PsemError.Transport($"Failed to open {_options.PortName}: {ex.Message}");
}
catch (InvalidOperationException ex)
{
Log.PortOpenFailed(_logger, ex, _options.PortName);
return PsemError.Transport($"Failed to open {_options.PortName}: {ex.Message}");
}
}
/// <inheritdoc/>
public ValueTask DisconnectAsync(CancellationToken cancellationToken = default)
{
if (_serialPort is { IsOpen: true })
{
_serialPort.DiscardInBuffer();
_serialPort.DiscardOutBuffer();
_serialPort.Close();
Log.PortClosed(_logger, _options.PortName);
}
_serialPort?.Dispose();
_serialPort = null;
return ValueTask.CompletedTask;
}
/// <inheritdoc/>
public ValueTask<Result<bool>> SendAsync(ReadOnlyMemory<byte> data, CancellationToken cancellationToken = default)
{
if (_serialPort is not { IsOpen: true })
return ValueTask.FromResult<Result<bool>>(PsemError.Transport("Serial port is not open"));
try
{
_serialPort.Write(data.ToArray(), 0, data.Length);
if (_logger.IsEnabled(LogLevel.Debug))
{
var hex = Convert.ToHexString(data.Span);
Log.DataSent(_logger, data.Length, hex);
}
return ValueTask.FromResult<Result<bool>>(true);
}
catch (IOException ex)
{
Log.SendFailed(_logger, ex);
return ValueTask.FromResult<Result<bool>>(PsemError.Transport($"Serial send failed: {ex.Message}"));
}
catch (ObjectDisposedException ex)
{
Log.SendFailed(_logger, ex);
return ValueTask.FromResult<Result<bool>>(PsemError.Transport($"Serial send failed: {ex.Message}"));
}
}
/// <inheritdoc/>
public ValueTask<Result<int>> ReceiveAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
{
if (_serialPort is not { IsOpen: true })
return ValueTask.FromResult<Result<int>>(PsemError.Transport("Serial port is not open"));
try
{
var tempBuffer = new byte[buffer.Length];
var bytesRead = _serialPort.Read(tempBuffer, 0, tempBuffer.Length);
if (bytesRead > 0)
tempBuffer.AsSpan(0, bytesRead).CopyTo(buffer.Span);
if (bytesRead > 0 && _logger.IsEnabled(LogLevel.Debug))
{
var hex = Convert.ToHexString(buffer.Span[..bytesRead]);
Log.DataReceived(_logger, bytesRead, hex);
}
return ValueTask.FromResult<Result<int>>(bytesRead);
}
catch (TimeoutException)
{
return ValueTask.FromResult<Result<int>>(PsemError.Timeout("serial receive"));
}
catch (IOException ex)
{
Log.ReceiveFailed(_logger, ex);
return ValueTask.FromResult<Result<int>>(PsemError.Transport($"Serial receive failed: {ex.Message}"));
}
catch (ObjectDisposedException ex)
{
Log.ReceiveFailed(_logger, ex);
return ValueTask.FromResult<Result<int>>(PsemError.Transport($"Serial receive failed: {ex.Message}"));
}
}
/// <inheritdoc/>
public async ValueTask SendControlAsync(byte controlByte, CancellationToken cancellationToken = default)
{
var buf = new[]
{
controlByte
};
await SendAsync(buf, cancellationToken);
}
#endregion
}