feat(commons): HandshakePhase + HandshakeResult for the reset-aware handshake (#36)

Claude-Session: https://claude.ai/code/session_01XPsuy2ZZ5Gbyfpt8PrK5BV
This commit is contained in:
Joseph Doherty
2026-09-10 15:18:10 -04:00
parent 9bb373c4a1
commit e5c536c6b7
3 changed files with 74 additions and 0 deletions
@@ -0,0 +1,47 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Types;
/// <summary>
/// Which leg of a reset-aware <c>Attributes.WriteBatchAndWaitAsync</c> handshake a
/// <see cref="HandshakeResult"/> describes (Gitea #36, design 2026-09-10 §4).
/// </summary>
public enum HandshakePhase
{
/// <summary>The reset write + reset-echo wait that precedes the batch and trigger.</summary>
Reset,
/// <summary>The batch + trigger write and the response wait that follow an observed reset echo.</summary>
Response,
}
/// <summary>
/// Result of the reset-aware <c>Attributes.WriteBatchAndWaitAsync</c> overload (Gitea #36):
/// like <see cref="WaitResult"/>, plus the <see cref="Phase"/> the outcome belongs to so a
/// script can report "timed out waiting for the reset echo" separately from "timed out
/// waiting for the device to complete". A reset that is never echoed means the batch and
/// trigger were NEVER written.
/// </summary>
/// <param name="Matched">
/// <see langword="true"/> when the response attribute reached its target within the timeout
/// (which implies the reset echo was observed first).
/// </param>
/// <param name="Value">The matched value of the phase's attribute; <see langword="null"/> on timeout / error.</param>
/// <param name="Quality">The attribute quality at match time; <see langword="null"/> on the non-match paths.</param>
/// <param name="TimedOut"><see langword="true"/> when the phase's wait timed out before a match.</param>
/// <param name="Phase">The leg this result describes — <see cref="HandshakePhase.Reset"/> only when that leg failed.</param>
/// <param name="ErrorMessage">Why the phase did not match, when known; <see langword="null"/> on success.</param>
public readonly record struct HandshakeResult(
bool Matched,
object? Value,
string? Quality,
bool TimedOut,
HandshakePhase Phase,
string? ErrorMessage)
{
/// <summary>
/// <see langword="true"/> when the reset leg did not match (timeout or error), i.e. the
/// device never echoed the reset value and nothing else was written. Provided so a
/// script can branch without spelling the <see cref="HandshakePhase"/> type name, which
/// is not in the script imports.
/// </summary>
public bool ResetFailed => !Matched && Phase == HandshakePhase.Reset;
}