db751d12a5
Part 9 ConditionType.Quality was never assigned; default(StatusCode)==Good so every native + scripted condition reported Good unconditionally — a comms-lost device still showed a healthy, inactive, Good condition (a wrong-VALUE bug, distinct from the null-value #473/#475). Clients (and HMIs bucketing on IsGood) could not tell "genuinely inactive" from "lost contact". Layer 1 — make Quality a real, plumbed field: - AlarmConditionSnapshot gains OpcUaQuality Quality (default Good). - MaterialiseAlarmCondition sets it (native BadWaitingForInitialData, scripted Good). - WriteAlarmCondition projects snapshot.Quality; the delta-gate gains a Quality member so a quality-bucket change fires a Part 9 event. Layer 2 — drive native quality from driver connectivity (a comms-lost driver emits no alarm transitions, and an alarm-bearing raw tag has no value variable, so quality can't come from either existing channel): - DriverInstanceActor Tells parent ConnectivityChanged on Connected/Reconnecting. - DriverHostActor fans it to every native condition the driver owns as OpcUaPublishActor.AlarmQualityUpdate (Good on connect, Bad on disconnect). - New dedicated IOpcUaAddressSpaceSink.WriteAlarmQuality sets ONLY Quality and fires only on a bucket change — never touches Active/Acked/Retain (an active alarm that loses comms stays active). Not a full-snapshot re-projection, so it can't clobber severity/message and works for a never-fired condition. Forwarded through DeferredAddressSpaceSink (F10b trap; auto-verified by the reflection forwarding guard). Ungated by redundancy role; no /alerts row. Scripted conditions stay Good; worst-of-input quality deferred to #478 (Layer 3). Tests: node-level (materialise/project/no-clobber/unknown-node no-op), NativeAlarmProjector, DriverInstanceActor connectivity emission, DriverHostActor fan-out, OpcUaPublishActor routing, and the wire-level guard (Condition_event_Quality_tracks_source_connectivity_on_the_wire) — RED-verified against a simulated pre-fix always-Good server. Existing DriverInstanceActor parent probes ignore the new ConnectivityChanged. Docs: docs/AlarmTracking.md §"Condition source-data Quality (#477)"; design doc docs/plans/2026-07-17-alarm-condition-quality-477-design.md.
80 lines
4.5 KiB
C#
80 lines
4.5 KiB
C#
using ZB.MOM.WW.OtOpcUa.Commons.OpcUa;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.OpcUaServer;
|
|
|
|
/// <summary>
|
|
/// Production <see cref="IOpcUaAddressSpaceSink"/> binding for v2 — bridges
|
|
/// OpcUaPublishActor's writes to the SDK address space owned by
|
|
/// <see cref="OtOpcUaNodeManager"/>. The host wires this in once the StandardServer has
|
|
/// been started (so the node manager exists).
|
|
/// </summary>
|
|
public sealed class SdkAddressSpaceSink : IOpcUaAddressSpaceSink, ISurgicalAddressSpaceSink
|
|
{
|
|
private readonly OtOpcUaNodeManager _nodeManager;
|
|
|
|
/// <summary>Initializes a new instance of the SdkAddressSpaceSink class.</summary>
|
|
/// <param name="nodeManager">The OPC UA node manager.</param>
|
|
public SdkAddressSpaceSink(OtOpcUaNodeManager nodeManager)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(nodeManager);
|
|
_nodeManager = nodeManager;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public void WriteValue(string nodeId, object? value, OpcUaQuality quality, DateTime sourceTimestampUtc, AddressSpaceRealm realm)
|
|
=> _nodeManager.WriteValue(nodeId, value, quality, sourceTimestampUtc, realm);
|
|
|
|
/// <inheritdoc />
|
|
public void WriteAlarmCondition(string alarmNodeId, AlarmConditionSnapshot state, DateTime sourceTimestampUtc, AddressSpaceRealm realm)
|
|
=> _nodeManager.WriteAlarmCondition(alarmNodeId, state, sourceTimestampUtc, realm);
|
|
|
|
public void WriteAlarmQuality(string alarmNodeId, OpcUaQuality quality, DateTime sourceTimestampUtc, AddressSpaceRealm realm)
|
|
=> _nodeManager.WriteAlarmQuality(alarmNodeId, quality, sourceTimestampUtc, realm);
|
|
|
|
/// <inheritdoc />
|
|
public void MaterialiseAlarmCondition(string alarmNodeId, string equipmentNodeId, string displayName, string alarmType, int severity, AddressSpaceRealm realm, bool isNative = false)
|
|
=> _nodeManager.MaterialiseAlarmCondition(alarmNodeId, equipmentNodeId, displayName, alarmType, severity, realm, isNative);
|
|
|
|
/// <inheritdoc />
|
|
public void WireAlarmNotifiers(string alarmNodeId, AddressSpaceRealm alarmRealm, IReadOnlyList<string> notifierFolderNodeIds, AddressSpaceRealm notifierFolderRealm)
|
|
=> _nodeManager.WireAlarmNotifiers(alarmNodeId, alarmRealm, notifierFolderNodeIds, notifierFolderRealm);
|
|
|
|
/// <inheritdoc />
|
|
public void EnsureFolder(string folderNodeId, string? parentNodeId, string displayName, AddressSpaceRealm realm)
|
|
=> _nodeManager.EnsureFolder(folderNodeId, parentNodeId, displayName, realm);
|
|
|
|
/// <inheritdoc />
|
|
public void EnsureVariable(string variableNodeId, string? parentFolderNodeId, string displayName, string dataType, bool writable, AddressSpaceRealm realm, string? historianTagname = null, bool isArray = false, uint? arrayLength = null)
|
|
=> _nodeManager.EnsureVariable(variableNodeId, parentFolderNodeId, displayName, dataType, writable, realm, historianTagname, isArray, arrayLength);
|
|
|
|
/// <inheritdoc />
|
|
public bool UpdateTagAttributes(string variableNodeId, bool writable, string? historianTagname, string dataType, bool isArray, uint? arrayLength, AddressSpaceRealm realm)
|
|
=> _nodeManager.UpdateTagAttributes(variableNodeId, writable, historianTagname, dataType, isArray, arrayLength, realm);
|
|
|
|
/// <inheritdoc />
|
|
public bool UpdateFolderDisplayName(string folderNodeId, string displayName, AddressSpaceRealm realm)
|
|
=> _nodeManager.UpdateFolderDisplayName(folderNodeId, displayName, realm);
|
|
|
|
/// <inheritdoc />
|
|
public bool RemoveVariableNode(string variableNodeId, AddressSpaceRealm realm)
|
|
=> _nodeManager.RemoveVariableNode(variableNodeId, realm);
|
|
|
|
/// <inheritdoc />
|
|
public bool RemoveAlarmConditionNode(string alarmNodeId, AddressSpaceRealm realm)
|
|
=> _nodeManager.RemoveAlarmConditionNode(alarmNodeId, realm);
|
|
|
|
/// <inheritdoc />
|
|
public bool RemoveEquipmentSubtree(string equipmentNodeId, AddressSpaceRealm realm)
|
|
=> _nodeManager.RemoveEquipmentSubtree(equipmentNodeId, realm);
|
|
|
|
/// <inheritdoc />
|
|
public void RebuildAddressSpace() => _nodeManager.RebuildAddressSpace();
|
|
|
|
/// <inheritdoc />
|
|
public void RaiseNodesAddedModelChange(string affectedNodeId, AddressSpaceRealm realm) => _nodeManager.RaiseNodesAddedModelChange(affectedNodeId, realm);
|
|
|
|
/// <inheritdoc />
|
|
public void AddReference(string sourceNodeId, AddressSpaceRealm sourceRealm, string targetNodeId, AddressSpaceRealm targetRealm, string referenceType = "Organizes")
|
|
=> _nodeManager.AddReference(sourceNodeId, sourceRealm, targetNodeId, targetRealm, referenceType);
|
|
}
|