Arrange code structure. Introduce dynamic scheduler and multi leader.
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
namespace EM.LowLevel
|
||||
{
|
||||
/// <summary>
|
||||
/// Marker interface for command types that can be built into bytes by an ICommandBuilder.
|
||||
/// Implement this on record/struct command shapes.
|
||||
/// </summary>
|
||||
public interface ICommand { }
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
namespace EM.LowLevel
|
||||
{
|
||||
/// <summary>
|
||||
/// Builds device/peripheral commands from C#-friendly types (enums, records, structs, etc.).
|
||||
/// </summary>
|
||||
public interface ICommandBuilder
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Build a command payload (already framed or raw depending on implementation contract) from a
|
||||
/// strongly-typed command description.
|
||||
/// </summary>
|
||||
/// <typeparam name="TCommand">
|
||||
/// Command type (e.g., an enum or a record/struct)
|
||||
/// </typeparam>
|
||||
/// <param name="command">
|
||||
/// Command value to build
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// Command bytes
|
||||
/// </returns>
|
||||
ReadOnlyMemory<byte> Build<TCommand>(TCommand command) where TCommand: notnull;
|
||||
|
||||
/// <summary>
|
||||
/// Try-build variant to avoid throwing and allow buffer reuse strategies.
|
||||
/// </summary>
|
||||
bool TryBuild<TCommand>(TCommand command, out ReadOnlyMemory<byte> bytes) where TCommand: notnull;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
namespace EM.LowLevel
|
||||
{
|
||||
/// <summary>
|
||||
/// Decodes device-specific binary responses into C#-friendly models.
|
||||
/// </summary>
|
||||
public interface IDataDecoder
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Decode a response of a known type into a model.
|
||||
/// </summary>
|
||||
/// <typeparam name="TModel">
|
||||
/// Target model type
|
||||
/// </typeparam>
|
||||
/// <param name="payload">
|
||||
/// Raw or deframed payload depending on transport contract
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// Decoded model
|
||||
/// </returns>
|
||||
TModel Decode<TModel>(ReadOnlySpan<byte> payload);
|
||||
|
||||
/// <summary>
|
||||
/// Try-decode variant to avoid throwing on partial/bad frames.
|
||||
/// </summary>
|
||||
bool TryDecode<TModel>(ReadOnlySpan<byte> payload, out TModel model);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EM.LowLevel
|
||||
{
|
||||
/// <summary>
|
||||
/// Generic high-level controller that composes a PortStream, an ICommandBuilder and an
|
||||
/// IDataDecoder to provide a complete peripheral control workflow.
|
||||
/// </summary>
|
||||
/// <typeparam name="TPort">
|
||||
/// Concrete transport deriving from PortStream
|
||||
/// </typeparam>
|
||||
/// <typeparam name="TBuilder">
|
||||
/// Concrete command builder
|
||||
/// </typeparam>
|
||||
/// <typeparam name="TDecoder">
|
||||
/// Concrete data decoder
|
||||
/// </typeparam>
|
||||
public interface IPortController<TPort, TBuilder, TDecoder> where TPort: PortStream where TBuilder: ICommandBuilder
|
||||
where TDecoder: IDataDecoder
|
||||
{
|
||||
|
||||
TBuilder CommandBuilder { get; }
|
||||
|
||||
TDecoder Decoder { get; }
|
||||
|
||||
TPort Port { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Close the underlying port.
|
||||
/// </summary>
|
||||
void Close();
|
||||
|
||||
/// <summary>
|
||||
/// Ensure the underlying port is opened and ready (delegates to Port.OpenAsync).
|
||||
/// </summary>
|
||||
ValueTask OpenAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Build a command, send it via the port and decode the response into a model.
|
||||
/// </summary>
|
||||
ValueTask<TResponse> RoundTripAsync<TCommand, TResponse>(
|
||||
TCommand command,
|
||||
CancellationToken cancellationToken = default)
|
||||
where TCommand: notnull;
|
||||
|
||||
/// <summary>
|
||||
/// Build a command and send it without expecting a typed response.
|
||||
/// </summary>
|
||||
ValueTask WriteAsync<TCommand>(TCommand command, CancellationToken cancellationToken = default)
|
||||
where TCommand: notnull;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace EM.LowLevel
|
||||
{
|
||||
/// <summary>
|
||||
/// Marker interface for decoded response models returned by IDataDecoder.
|
||||
/// </summary>
|
||||
public interface IResponse { }
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EM.LowLevel
|
||||
{
|
||||
/// <summary>
|
||||
/// High-level transport abstraction for device communication. Derives from Stream and
|
||||
/// encapsulates initialization/handshake and frame (header/tail/checksum) handling as needed
|
||||
/// by the device. Implementations may wrap SerialPort, Sockets, USB, etc.
|
||||
/// </summary>
|
||||
public abstract class PortStream : Stream
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Optional bytes available hint if the transport can provide it.
|
||||
/// </summary>
|
||||
public virtual long? BytesAvailable => null;
|
||||
|
||||
/// <summary>
|
||||
/// Close the underlying transport and release resources.
|
||||
/// </summary>
|
||||
public abstract override void Close();
|
||||
|
||||
/// <summary>
|
||||
/// Ensure the port is opened and ready. Idempotent. May perform device-specific
|
||||
/// handshakes.
|
||||
/// </summary>
|
||||
public abstract ValueTask OpenAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
// Concrete transports must implement Stream's abstract members.
|
||||
// Seek-related members are typically not supported and should throw NotSupportedException.
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user