diff --git a/docs/AlarmClientDiscovery.md b/docs/AlarmClientDiscovery.md index 1d08632..5eee686 100644 --- a/docs/AlarmClientDiscovery.md +++ b/docs/AlarmClientDiscovery.md @@ -813,9 +813,10 @@ calling `IAlarmWatchListResolver.GetAlarmAttributesAsync`. The resolver merges: The resolved list is a set of `AlarmSubtagTarget` messages sent to the worker inside `SubscribeAlarmsCommand.watch_list`. Each target carries the composed -MXAccess item addresses for the `.active`, `.acked`, ack-comment, and priority -subtags. The gateway re-runs discovery on its reconcile cadence and pushes an -updated watch-list when the model changes. +MXAccess item addresses for the `InAlarm`, `Acked`, `AckMsg`, and `Priority` +subtags (confirmed AVEVA `AlarmExtension` field names, verified against the live +ZB Galaxy `attribute_definition` rows). The gateway re-runs discovery on its +reconcile cadence and pushes an updated watch-list when the model changes. ### Subtag advise and `LmxSubtagAlarmSource` @@ -836,22 +837,22 @@ emits `MxAlarmTransitionEvent` records on change: | Subtag change | Emitted transition | Notes | |---|---|---| -| `active` false → true | Raise (`UNACK_ALM`) | `original_raise_timestamp` = first observed active time for this episode | -| `acked` false → true, while `active` | Acknowledge (`ACK_ALM`) | `AckedDuringEpisode` latch set | -| `active` true → false | Clear | `AckRtn` if `AckedDuringEpisode` is set, else `UnackRtn` | -| `acked` true → false, while `active` | (none) | Latch is NOT cleared; the episode retains its acknowledged status at clear | +| `InAlarm` false → true | Raise (`UNACK_ALM`) | `original_raise_timestamp` = first observed active time for this episode | +| `Acked` false → true, while `InAlarm` | Acknowledge (`ACK_ALM`) | `AckedDuringEpisode` latch set | +| `InAlarm` true → false | Clear | `AckRtn` if `AckedDuringEpisode` is set, else `UnackRtn` | +| `Acked` true → false, while `InAlarm` | (none) | Latch is NOT cleared; the episode retains its acknowledged status at clear | The `AckedDuringEpisode` latch addresses out-of-order subtag delivery: -MXAccess does not guarantee the `acked = false` update arrives before the -`active = false` update. The latch ensures a clear always emits `ACK_RTN` +MXAccess does not guarantee the `Acked = false` update arrives before the +`InAlarm = false` update. The latch ensures a clear always emits `ACK_RTN` when the alarm was acknowledged at any point during the active episode. `SnapshotActive()` returns one `MxAlarmSnapshotRecord` per currently-active alarm. State mapping: -- `active && !acked` → `UNACK_ALM` -- `active && acked` → `ACK_ALM` -- `!active` → not included in the snapshot +- `InAlarm && !Acked` → `UNACK_ALM` +- `InAlarm && Acked` → `ACK_ALM` +- `!InAlarm` → not included in the snapshot ### Synthetic GUID @@ -870,7 +871,9 @@ AVEVA-internal GUID. - **Alarm-manager mode:** `AlarmAckByName` on `wwAlarmConsumerClass` (unchanged). - **Subtag mode:** `SubtagAlarmConsumer.AcknowledgeByName` resolves the watch-list entry's `ack_comment_subtag` and issues a `Write(comment)` on - the STA via `LmxSubtagAlarmSource`. The write performs the ack in AVEVA. + the STA via `LmxSubtagAlarmSource`. Writing the `AckMsg` subtag performs + the acknowledge in AVEVA (`AckMsg` is the confirmed `AlarmExtension` ack-comment + write target). If the alarm has no writable ack-comment subtag (`AckComment` config key is empty, or the entry's `ack_comment_subtag` field is empty), the ack call diff --git a/docs/GatewayConfiguration.md b/docs/GatewayConfiguration.md index e65ce68..4778e58 100644 --- a/docs/GatewayConfiguration.md +++ b/docs/GatewayConfiguration.md @@ -245,10 +245,10 @@ native wnwrap alarm-manager provider and the subtag-monitoring fallback. | `MxGateway:Alarms:Fallback:Discovery:Area` | _(empty)_ | Galaxy area to scope the Repository query to. Falls back to `MxGateway:Alarms:DefaultArea` when empty. Ignored when `UseGalaxyRepository` is `false`. | | `MxGateway:Alarms:Fallback:Discovery:IncludeAttributes` | _(empty)_ | Explicit MXAccess attribute paths to add to the subtag watch-list, supplementing (or replacing, when `UseGalaxyRepository` is `false`) the Repository-derived list. | | `MxGateway:Alarms:Fallback:Discovery:ExcludeAttributes` | _(empty)_ | Attribute paths to remove from the Repository-derived watch-list. Ignored when `UseGalaxyRepository` is `false`. | -| `MxGateway:Alarms:Fallback:Subtags:Active` | `active` | Subtag name for the in-alarm boolean. | -| `MxGateway:Alarms:Fallback:Subtags:Acked` | `acked` | Subtag name for the acknowledged boolean. | -| `MxGateway:Alarms:Fallback:Subtags:AckComment` | _(empty)_ | Subtag name for the acknowledgement comment attribute. When empty, writing an ack comment in subtag mode is disabled. Must be verified against the live MXAccess attribute set before use. | -| `MxGateway:Alarms:Fallback:Subtags:Priority` | `priority` | Subtag name for the alarm priority / severity value. | +| `MxGateway:Alarms:Fallback:Subtags:Active` | `InAlarm` | Subtag name for the in-alarm boolean. Confirmed AVEVA `AlarmExtension` field name. | +| `MxGateway:Alarms:Fallback:Subtags:Acked` | `Acked` | Subtag name for the acknowledged boolean. Confirmed AVEVA `AlarmExtension` field name. | +| `MxGateway:Alarms:Fallback:Subtags:AckComment` | `AckMsg` | Subtag name for the acknowledgement comment write target. Writing this subtag performs the acknowledge in AVEVA. Confirmed AVEVA `AlarmExtension` field name. When empty, the ack-comment write path is disabled. | +| `MxGateway:Alarms:Fallback:Subtags:Priority` | `Priority` | Subtag name for the alarm priority / severity value. Confirmed AVEVA `AlarmExtension` field name. | Validation rules: @@ -281,10 +281,10 @@ Full example with non-default fallback settings: "ExcludeAttributes": [] }, "Subtags": { - "Active": "active", - "Acked": "acked", - "AckComment": "", - "Priority": "priority" + "Active": "InAlarm", + "Acked": "Acked", + "AckComment": "AckMsg", + "Priority": "Priority" } } } @@ -292,10 +292,11 @@ Full example with non-default fallback settings: } ``` -The exact AVEVA subtag names for `Active`, `Acked`, `AckComment`, and -`Priority` are not hard-coded. The `Subtags` block exists so names can be -confirmed against the live MXAccess attribute set and configured without a -code change. See `docs/AlarmClientDiscovery.md` for the synthesis rules that +The defaults (`InAlarm`/`Acked`/`AckMsg`/`Priority`) are the confirmed AVEVA +`AlarmExtension` primitive field names, verified by querying the live ZB Galaxy +`attribute_definition` rows. The `Subtags` block exists so names can be +overridden without a code change if a site's alarm template uses different +attribute names. See `docs/AlarmClientDiscovery.md` for the synthesis rules that depend on these names. ## Host Endpoints and Transport Security (Kestrel) diff --git a/src/ZB.MOM.WW.MxGateway.Server/Alarms/AlarmWatchListResolver.cs b/src/ZB.MOM.WW.MxGateway.Server/Alarms/AlarmWatchListResolver.cs index 8b5d581..c7e4923 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Alarms/AlarmWatchListResolver.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Alarms/AlarmWatchListResolver.cs @@ -17,8 +17,8 @@ namespace ZB.MOM.WW.MxGateway.Server.Alarms; public sealed class AlarmWatchListResolver : IAlarmWatchListResolver { private const string ProviderLiteral = "Galaxy"; - private const string DefaultActiveSubtag = "active"; - private const string DefaultAckedSubtag = "acked"; + private const string DefaultActiveSubtag = "InAlarm"; + private const string DefaultAckedSubtag = "Acked"; private readonly IGalaxyRepository _repository; private readonly ILogger _logger; diff --git a/src/ZB.MOM.WW.MxGateway.Server/Configuration/AlarmFallbackOptions.cs b/src/ZB.MOM.WW.MxGateway.Server/Configuration/AlarmFallbackOptions.cs index 8760764..afd3f63 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Configuration/AlarmFallbackOptions.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Configuration/AlarmFallbackOptions.cs @@ -98,28 +98,34 @@ public sealed class AlarmDiscoveryOptions /// in subtag-polling fallback mode. Names are matched against MXAccess item /// handles; validation against the live MXAccess attribute list occurs at /// runtime, not at startup. +/// Defaults are the confirmed AVEVA AlarmExtension primitive field names, +/// verified against the live ZB Galaxy attribute_definition rows. /// public sealed class AlarmSubtagNameOptions { /// - /// Subtag name for the active-alarm flag. Default active. + /// Subtag name for the in-alarm boolean. Confirmed AVEVA AlarmExtension + /// field name. Default InAlarm. /// - public string Active { get; init; } = "active"; + public string Active { get; init; } = "InAlarm"; /// - /// Subtag name for the acknowledged flag. Default acked. + /// Subtag name for the acknowledged boolean. Confirmed AVEVA AlarmExtension + /// field name. Default Acked. /// - public string Acked { get; init; } = "acked"; + public string Acked { get; init; } = "Acked"; /// - /// Optional subtag name for the acknowledgement comment field. - /// When empty the feature is disabled. Verified against MXAccess at - /// runtime before use. Default empty. + /// Subtag name for the acknowledgement comment write target. Writing this subtag + /// performs the acknowledge in AVEVA. Confirmed AVEVA AlarmExtension + /// field name. When empty the ack-comment write path is disabled. + /// Default AckMsg. /// - public string AckComment { get; init; } = string.Empty; + public string AckComment { get; init; } = "AckMsg"; /// - /// Subtag name for the alarm priority. Default priority. + /// Subtag name for the alarm priority / severity. Confirmed AVEVA + /// AlarmExtension field name. Default Priority. /// - public string Priority { get; init; } = "priority"; + public string Priority { get; init; } = "Priority"; } diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Alarms/AlarmWatchListResolverTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Alarms/AlarmWatchListResolverTests.cs index d855ab4..e9e1239 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Alarms/AlarmWatchListResolverTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Alarms/AlarmWatchListResolverTests.cs @@ -58,7 +58,7 @@ public sealed class AlarmWatchListResolverTests Assert.Equal( new[] { "Tank01.Level.HiHi", "Pump01.Fault", "Valve03.Position.Lo" }, - result.Select(t => t.ActiveSubtag.Replace(".active", string.Empty, StringComparison.Ordinal))); + result.Select(t => t.ActiveSubtag.Replace(".InAlarm", string.Empty, StringComparison.Ordinal))); // De-dup preserved first (GR) occurrence; exclude removed Tank02. Assert.Equal(3, result.Count); } @@ -99,13 +99,13 @@ public sealed class AlarmWatchListResolverTests AlarmWatchListResolver resolver = CreateResolver(repo); - // Default Priority is "priority"; force it empty alongside empty AckComment. + // Default Priority is "Priority"; force it empty alongside empty AckComment. IReadOnlyList result = await resolver.ResolveAsync(Options( subtags: new AlarmSubtagNameOptions { Priority = string.Empty, AckComment = string.Empty })); AlarmSubtagTarget target = Assert.Single(result); - Assert.Equal("Tank01.Level.HiHi.active", target.ActiveSubtag); - Assert.Equal("Tank01.Level.HiHi.acked", target.AckedSubtag); + Assert.Equal("Tank01.Level.HiHi.InAlarm", target.ActiveSubtag); + Assert.Equal("Tank01.Level.HiHi.Acked", target.AckedSubtag); Assert.Equal(string.Empty, target.PrioritySubtag); Assert.Equal(string.Empty, target.AckCommentSubtag); } diff --git a/src/ZB.MOM.WW.MxGateway.Worker.Tests/Probes/AlarmSubtagLiveSmokeTests.cs b/src/ZB.MOM.WW.MxGateway.Worker.Tests/Probes/AlarmSubtagLiveSmokeTests.cs index b01e465..9fc9d88 100644 --- a/src/ZB.MOM.WW.MxGateway.Worker.Tests/Probes/AlarmSubtagLiveSmokeTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Worker.Tests/Probes/AlarmSubtagLiveSmokeTests.cs @@ -1,14 +1,17 @@ // AlarmSubtagLiveSmokeTests.cs // -// Validates the open design item from Task 17: exact subtag names and the -// canonical AlarmSubtagTarget reference shape for a known Galaxy alarm. -// -// This test exercises the full subtag-fallback pipeline end-to-end against -// a real Galaxy + MXAccess install: +// Validates the subtag-fallback pipeline against a real Galaxy + MXAccess install: // LmxSubtagAlarmSource (own LMXProxyServerClass) -> // SubtagAlarmConsumer (state machine + AcknowledgeByName write) -> // synthesized MxAlarmTransitionEvent (Raise / Clear, Degraded=true, SyntheticGuid) // +// FIELD NAMES CONFIRMED: InAlarm/Acked/AckMsg/Priority are the confirmed AVEVA +// AlarmExtension primitive field names, verified by querying the live ZB Galaxy +// attribute_definition rows. The remaining open item for live validation is +// confirming the runtime item reference path — i.e. that +// "..InAlarm" is the correct MXAccess path with no +// intermediate alarm-condition segment. +// // HOW TO RUN: // 1. On the dev rig with AVEVA System Platform installed and Galaxy running: // $env:MXGATEWAY_RUN_LIVE_MXACCESS_TESTS = "1" @@ -16,12 +19,6 @@ // 3. Run with an alarm flip script (same one used by AlarmsLiveSmokeTests) // so that TestMachine_001.TestAlarm001 toggles its Active/Acked subtags // on a ~10 s cadence. -// 4. VERIFY the placeholder subtag names (PLACEHOLDER_* constants below) -// against the live Galaxy before flipping Skip -- see VERIFY comment block. -// -// OPEN ITEM (Task 17): the exact subtag address strings are not yet confirmed -// from a live Galaxy or from C:\Users\dohertj2\Desktop\mxaccess -// docs/MXAccess-Public-API.md. Flip this test only after verifying them. // // net48/x86 constraints: // - No init-only properties, records, index/range operators, C# 8+ pattern @@ -43,34 +40,31 @@ namespace ZB.MOM.WW.MxGateway.Worker.Tests.Probes; /// /// Live dev-rig smoke test for the subtag-fallback alarm pipeline. -/// Validates the open design item from Task 17: confirms that -/// wired to a real +/// Confirms that wired to a real /// LMXProxyServerClass + can /// synthesize records from Galaxy -/// alarm subtags, and that -/// writes the ack-comment subtag successfully. +/// alarm subtags using the confirmed AVEVA AlarmExtension field +/// names (InAlarm/Acked/AckMsg/Priority), and that +/// writes the +/// ack-comment subtag (AckMsg) successfully. /// /// Skip-gated; flip Skip=null on the dev rig with the alarm flip -/// script running. VERIFY the PLACEHOLDER_* subtag names before running. +/// script running. The remaining live-validation item is confirming that +/// the runtime MXAccess item reference path requires no intermediate +/// alarm-condition segment (i.e. <Object>.<AlarmAttr>.InAlarm +/// resolves as-is). /// public sealed class AlarmSubtagLiveSmokeTests { // ------------------------------------------------------------------------- - // PLACEHOLDER references — VERIFY before flipping Skip. + // Subtag addresses for TestMachine_001.TestAlarm001. // - // These values are the best-guess subtag addresses for TestMachine_001.TestAlarm001 - // as used by the existing AlarmsLiveSmokeTests (which watches the same Galaxy - // provider). The exact attribute suffix strings (.InAlarm, .Acked, - // .AckComment, .Priority) MUST be confirmed against: - // - C:\Users\dohertj2\Desktop\mxaccess\docs\MXAccess-Public-API.md - // - A live Galaxy attribute browse or MXTraceHarness capture - // before this test is flipped on. The subtag address format is - // . - // where the alarm attribute names are Galaxy-attribute names surfaced - // through MXAccess. Known AVEVA attribute names are .InAlarm (bool), - // .Acked (bool), .Priority (int), and a writable comment attribute whose - // exact name varies by alarm template (commonly .AlarmComment or - // .AckComment). + // Field names (InAlarm/Acked/AckMsg/Priority) are CONFIRMED against the live + // ZB Galaxy AlarmExtension primitive attribute_definition rows. The subtag + // address format is .. The remaining + // live-validation item is confirming that the MXAccess runtime item + // reference resolves without an intermediate alarm-condition segment + // (i.e. "..InAlarm" resolves as-is). // ------------------------------------------------------------------------- /// The Galaxy provider expression used by the existing live smoke tests. @@ -83,20 +77,17 @@ public sealed class AlarmSubtagLiveSmokeTests /// The test alarm's tag name as the dispatcher composes it (Group.TagName). private const string AlarmTagName = "TestMachine_001.TestAlarm001"; - // PLACEHOLDER — VERIFY against MXAccess-Public-API.md and live Galaxy. - // Typical InProcess Platform alarm active subtag: ".InAlarm" + // Confirmed AVEVA AlarmExtension field name (in-alarm boolean). private const string PlaceholderActiveSubtag = "TestMachine_001.TestAlarm001.InAlarm"; - // PLACEHOLDER — VERIFY. Typical: ".Acked" + // Confirmed AVEVA AlarmExtension field name (acknowledged boolean). private const string PlaceholderAckedSubtag = "TestMachine_001.TestAlarm001.Acked"; - // PLACEHOLDER — VERIFY. The writable ack-comment attribute varies by alarm - // template; common names are .AlarmComment, .AckComment, or .OperComment. - // Use a live Galaxy attribute browse (InTouch AlarmDB or MXTraceHarness - // GetItemList) to confirm before flipping. - private const string PlaceholderAckCommentSubtag = "TestMachine_001.TestAlarm001.AlarmComment"; + // Confirmed AVEVA AlarmExtension field name (ack-comment write target). + // Writing this subtag performs the acknowledge in AVEVA. + private const string PlaceholderAckCommentSubtag = "TestMachine_001.TestAlarm001.AckMsg"; - // PLACEHOLDER — VERIFY. Typically: ".Priority" + // Confirmed AVEVA AlarmExtension field name (alarm priority/severity). private const string PlaceholderPrioritySubtag = "TestMachine_001.TestAlarm001.Priority"; // ------------------------------------------------------------------------- @@ -125,7 +116,7 @@ public sealed class AlarmSubtagLiveSmokeTests /// the Degraded flag and synthetic GUID are stamped, then /// AcknowledgeByName and verifies the ack-comment write returns 0. /// - [Fact(Skip = "Live dev-rig smoke test — flip Skip=null with AVEVA + an alarm flip script running. Subtag fallback path. VERIFY PLACEHOLDER_* subtag names before enabling.")] + [Fact(Skip = "Live dev-rig smoke test — flip Skip=null with AVEVA + an alarm flip script running. Subtag fallback path. Field names confirmed (InAlarm/Acked/AckMsg/Priority); live-validate runtime path resolves without intermediate alarm-condition segment.")] public void SubtagFallback_FullPipelineRoundTrip_SynthesizesRaiseAndAcknowledges() { Exception? threadException = null; @@ -165,9 +156,10 @@ public sealed class AlarmSubtagLiveSmokeTests Log(string.Format("RaiseWaitTimeout={0}s ClearWaitTimeout={1}s", RaiseWaitTimeout.TotalSeconds, ClearWaitTimeout.TotalSeconds)); - // Build target with PLACEHOLDER subtag names. - // VERIFY these against MXAccess-Public-API.md and a live Galaxy - // attribute browse before flipping Skip=null. + // Build target with confirmed AVEVA AlarmExtension subtag names. + // InAlarm/Acked/AckMsg/Priority confirmed against live ZB Galaxy + // attribute_definition rows. Remaining live-validation: runtime path resolves + // without an intermediate alarm-condition segment. AlarmSubtagTarget target = new AlarmSubtagTarget { AlarmFullReference = AlarmFullReference,