Auto: focas-f2d — PMC range coalescing

Closes #266
This commit is contained in:
Joseph Doherty
2026-04-25 20:02:10 -04:00
parent 9ebe5bd523
commit 4d3ee47235
7 changed files with 836 additions and 0 deletions

View File

@@ -184,6 +184,45 @@ public interface IFocasClient : IDisposable
/// </summary>
Task SetPathAsync(int pathId, CancellationToken cancellationToken)
=> Task.CompletedTask;
/// <summary>
/// Read a contiguous range of PMC bytes in a single wire call (FOCAS
/// <c>pmc_rdpmcrng</c> with byte data type) for the given <paramref name="letter"/>
/// (<c>R</c>, <c>D</c>, <c>X</c>, etc.) starting at <paramref name="startByte"/> and
/// spanning <paramref name="byteCount"/> bytes. Returned tuple has the byte buffer
/// (length <paramref name="byteCount"/> on success) + the OPC UA status mapped through
/// <see cref="FocasStatusMapper"/>. Used by <see cref="FocasDriver"/> to coalesce
/// same-letter/same-path PMC reads in a batch into one round trip per range
/// (issue #266 — see <see cref="Wire.FocasPmcCoalescer"/>).
/// <para>
/// Default falls back to per-byte <see cref="ReadAsync(FocasAddress, FocasDataType, CancellationToken)"/>
/// calls so transport variants that haven't extended their wire surface still work
/// correctly — they just won't see the round-trip reduction. The fallback short-circuits
/// on the first non-Good status so a partial buffer isn't returned with a Good code.
/// </para>
/// </summary>
async Task<(byte[]? buffer, uint status)> ReadPmcRangeAsync(
string letter, int pathId, int startByte, int byteCount, CancellationToken cancellationToken)
{
if (byteCount <= 0) return (Array.Empty<byte>(), FocasStatusMapper.Good);
var buf = new byte[byteCount];
for (var i = 0; i < byteCount; i++)
{
cancellationToken.ThrowIfCancellationRequested();
var addr = new FocasAddress(FocasAreaKind.Pmc, letter, startByte + i, BitIndex: null, PathId: pathId);
var (value, status) = await ReadAsync(addr, FocasDataType.Byte, cancellationToken).ConfigureAwait(false);
if (status != FocasStatusMapper.Good) return (null, status);
buf[i] = value switch
{
sbyte s => unchecked((byte)s),
byte b => b,
int n => unchecked((byte)n),
short s => unchecked((byte)s),
_ => 0,
};
}
return (buf, FocasStatusMapper.Good);
}
}
/// <summary>