fix(mtconnect): review rulings for CONDITION alarms (#626)
- Unavailable keeps an ACTIVE condition active: quality-only BadNoCommunication; on data return same statement => quality-only Good, Normal => Clear, different => Retrigger. - A changed alarm reference set (alarm-bearing delta = full rebuild) clears the whole last-told record so every active condition is re-asserted; the same set keeps it. - Quality-only events go through the host's AlarmQualityUpdate path, never a projected snapshot (which could ack a retained unacked condition or rewrite message/severity). - Deterministic tie-break between equal-level conditions (ordinal nativeCode, then type). - Dedup key is level+nativeCode+nativeSeverity+qualifier; a text-only change updates nothing. Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
@@ -1789,6 +1789,25 @@ public sealed class DriverHostActor : ReceiveActor, IWithTimers, IWithUnboundedS
|
||||
_localNode, msg.DriverInstanceId, msg.Args.ConditionId);
|
||||
return;
|
||||
}
|
||||
|
||||
// #626 — a QUALITY-ONLY annotation (an MTConnect condition gone Unavailable, or back) is not an alarm
|
||||
// transition. It rides the SAME quality-only node path the #477 connectivity annotation uses
|
||||
// (AlarmQualityUpdate → WriteAlarmQuality, which touches Quality and nothing else), never a projected
|
||||
// snapshot: the projector's prior state is cleared on every apply, so a projected "keep the state"
|
||||
// after a deploy would default to inactive+acked and silently acknowledge a retained, unacked
|
||||
// condition — and would rewrite its message and severity. Ungated by role (a Secondary keeps quality
|
||||
// warm, as for connectivity) and no alerts row.
|
||||
if (msg.Args is { IsQualityOnly: true, QualityStatusCode: { } quality })
|
||||
{
|
||||
foreach (var n in nodeIds)
|
||||
{
|
||||
_opcUaPublishActor.Tell(new ZB.MOM.WW.OtOpcUa.Runtime.OpcUa.OpcUaPublishActor.AlarmQualityUpdate(
|
||||
n.NodeId, quality, msg.Args.SourceTimestampUtc, n.Realm));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// PRIMARY GATE (archreview 03/S4) — decided ONCE for this transition: the OPC UA condition write
|
||||
// below stays UNGATED (a Secondary keeps its address space warm for failover), but only the Primary
|
||||
// publishes the single cluster-wide alerts copy. Denied on a Secondary/Detached, OR on a multi-driver
|
||||
@@ -1811,11 +1830,8 @@ public sealed class DriverHostActor : ReceiveActor, IWithTimers, IWithUnboundedS
|
||||
_opcUaPublishActor.Tell(new ZB.MOM.WW.OtOpcUa.Runtime.OpcUa.OpcUaPublishActor.AlarmStateUpdate(
|
||||
nodeId, snapshot, msg.Args.SourceTimestampUtc, n.Realm));
|
||||
|
||||
// Only the Primary publishes the cluster-wide alerts transition (see the gate above). A quality-only
|
||||
// annotation (#626: e.g. an MTConnect condition gone Unavailable while already Normal) is not an
|
||||
// alarm transition at all — the condition write above carries it; an alerts row would read as
|
||||
// "Activated" for something that never activated.
|
||||
if (!serviceAlertsAsPrimary || msg.Args.IsQualityOnly) continue;
|
||||
// Only the Primary publishes the cluster-wide alerts transition (see the gate above).
|
||||
if (!serviceAlertsAsPrimary) continue;
|
||||
|
||||
var meta = _alarmMetaByNodeId.TryGetValue(nodeId, out var m)
|
||||
? m : (EquipmentId: nodeId, Name: nodeId, AlarmType: "AlarmCondition", HistorizeToAveva: (bool?)null,
|
||||
|
||||
Reference in New Issue
Block a user