feat(opcua): IOpcUaAddressSpaceSink.WriteStatuses — status-only batch write retaining the node value (03/S15)
This commit is contained in:
@@ -32,6 +32,14 @@ public sealed class DeferredAddressSpaceSink : IOpcUaAddressSpaceSink, ISurgical
|
||||
// (actors inject THIS wrapper, not the inner SdkAddressSpaceSink) — same trap as WireAlarmNotifiers above.
|
||||
public void WriteValues(IReadOnlyList<ValueWrite> values) => _inner.WriteValues(values);
|
||||
|
||||
/// <inheritdoc />
|
||||
// Forward the status-only batch (03/S15). Unforwarded, the interface's DEFAULT runs here instead —
|
||||
// and that default writes a NULL value, so a comms-loss annotation would erase the last known value
|
||||
// of every node on the driver rather than retaining it, on every driver-role host (actors inject
|
||||
// THIS wrapper, not the inner SdkAddressSpaceSink). Same trap as WriteValues / WireAlarmNotifiers
|
||||
// above, except this one degrades to wrong data rather than to slow.
|
||||
public void WriteStatuses(IReadOnlyList<StatusWrite> statuses) => _inner.WriteStatuses(statuses);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void WriteAlarmCondition(string alarmNodeId, AlarmConditionSnapshot state, DateTime sourceTimestampUtc, AddressSpaceRealm realm)
|
||||
=> _inner.WriteAlarmCondition(alarmNodeId, state, sourceTimestampUtc, realm);
|
||||
|
||||
@@ -42,6 +42,31 @@ public interface IOpcUaAddressSpaceSink
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Batched <b>status-only</b> write: each node keeps its CURRENT value and only its
|
||||
/// StatusCode + SourceTimestamp change. This is the comms-loss annotation for value variables
|
||||
/// (archreview 03/S15) — a driver that has stopped communicating leaves the node holding its last
|
||||
/// known value (the Part 8 convention SCADA clients expect) under a
|
||||
/// <see cref="OpcUaStatus.BadNoCommunication"/> status, rather than nulling it.
|
||||
/// <para>A sink that HOLDS the node's value must retain it — the SDK-backed sink does, because the
|
||||
/// node itself is where the value lives. This DEFAULT implementation cannot: the host holds no value
|
||||
/// cache (v3's single-source design), so it degrades to <see cref="WriteValue"/> with a null value,
|
||||
/// which is the honest statement for a value-less sink and keeps every fake compiling unchanged.</para>
|
||||
/// <para>Same per-item isolation contract as <see cref="WriteValues"/>. An unknown or
|
||||
/// unmaterialised NodeId is a <b>no-op, never a create</b>: a status carries nothing to create a node
|
||||
/// from, and the caller's index can legitimately name a node a surgical remove has just dropped.</para></summary>
|
||||
/// <param name="statuses">The status writes to apply, in order.</param>
|
||||
/// <exception cref="ValueWriteBatchException">A batching implementation throws this when some — but not
|
||||
/// necessarily all — items failed; every other item has been applied.</exception>
|
||||
void WriteStatuses(IReadOnlyList<StatusWrite> statuses)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(statuses);
|
||||
for (var i = 0; i < statuses.Count; i++)
|
||||
{
|
||||
var s = statuses[i];
|
||||
WriteValue(s.NodeId, null, s.StatusCode, s.SourceTimestampUtc, s.Realm);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Write an alarm-condition's full Part 9 state. When a real condition node has been
|
||||
/// materialised for <paramref name="alarmNodeId"/> via <see cref="MaterialiseAlarmCondition"/>,
|
||||
/// this projects the whole <see cref="AlarmConditionSnapshot"/>
|
||||
@@ -190,6 +215,16 @@ public interface IOpcUaAddressSpaceSink
|
||||
public readonly record struct ValueWrite(
|
||||
string NodeId, object? Value, uint StatusCode, DateTime SourceTimestampUtc, AddressSpaceRealm Realm);
|
||||
|
||||
/// <summary>One status-only write within a <see cref="IOpcUaAddressSpaceSink.WriteStatuses"/> batch: the
|
||||
/// node's current VALUE is retained and only <paramref name="StatusCode"/> +
|
||||
/// <paramref name="SourceTimestampUtc"/> change.</summary>
|
||||
/// <param name="NodeId">The OPC UA node id of the variable, within <paramref name="Realm"/>.</param>
|
||||
/// <param name="StatusCode">The full status code, sub-code included, passed to the SDK verbatim (#519).</param>
|
||||
/// <param name="SourceTimestampUtc">The source timestamp in UTC.</param>
|
||||
/// <param name="Realm">Which of the two v3 namespaces <paramref name="NodeId"/> lives in.</param>
|
||||
public readonly record struct StatusWrite(
|
||||
string NodeId, uint StatusCode, DateTime SourceTimestampUtc, AddressSpaceRealm Realm);
|
||||
|
||||
/// <summary>Aggregate outcome of a partially-failed <see cref="IOpcUaAddressSpaceSink.WriteValues"/> batch:
|
||||
/// every non-failing item HAS been applied; this carries what the caller needs to log and meter honestly.</summary>
|
||||
/// <remarks>A batched sink must NOT abandon the tail of a batch when one item throws: the poll engine
|
||||
|
||||
Reference in New Issue
Block a user