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