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

SerialTransport(TransportOptions options, SerialTransport> logger)

SerialTransport.SerialTransport(TransportOptions options, ILogger<SerialTransport> logger)

Initializes a new instance of SerialTransport.

Parameters

NameTypeDescription
optionsSharpMeter.Transport.TransportOptionsThe transport configuration options.
loggerILogger<SharpMeter.Transport.SerialTransport>The logger instance.

Properties

NameDescription
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

ConnectAsync(CancellationToken cancellationToken)

ValueTask<Result<bool>> SerialTransport.ConnectAsync(CancellationToken cancellationToken = null)

Opens the transport connection.

Parameters

NameTypeDescription
cancellationTokenCancellationTokenCancellation token.

Returns: A result indicating success or transport error.

DisconnectAsync(CancellationToken cancellationToken)

ValueTask SerialTransport.DisconnectAsync(CancellationToken cancellationToken = null)

Closes the transport connection.

Parameters

NameTypeDescription
cancellationTokenCancellationTokenCancellation token.

ReceiveAsync(Memory buffer, CancellationToken cancellationToken)

ValueTask<Result<int>> SerialTransport.ReceiveAsync(Memory<byte> buffer, CancellationToken cancellationToken = null)

Receives raw bytes from the transport.

Parameters

NameTypeDescription
bufferMemory<byte>The buffer to receive into.
cancellationTokenCancellationTokenCancellation 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

NameTypeDescription
dataReadOnlyMemory<byte>The data to send.
cancellationTokenCancellationTokenCancellation 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

NameTypeDescription
controlBytebyteThe control byte to send.
cancellationTokenCancellationTokenCancellation 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
}
Was this page helpful?