alarms: use confirmed AVEVA AlarmExtension subtag names (InAlarm/Acked/AckMsg/Priority)
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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<AlarmWatchListResolver> _logger;
|
||||
|
||||
@@ -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 <c>AlarmExtension</c> primitive field names,
|
||||
/// verified against the live ZB Galaxy <c>attribute_definition</c> rows.
|
||||
/// </summary>
|
||||
public sealed class AlarmSubtagNameOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Subtag name for the active-alarm flag. Default <c>active</c>.
|
||||
/// Subtag name for the in-alarm boolean. Confirmed AVEVA <c>AlarmExtension</c>
|
||||
/// field name. Default <c>InAlarm</c>.
|
||||
/// </summary>
|
||||
public string Active { get; init; } = "active";
|
||||
public string Active { get; init; } = "InAlarm";
|
||||
|
||||
/// <summary>
|
||||
/// Subtag name for the acknowledged flag. Default <c>acked</c>.
|
||||
/// Subtag name for the acknowledged boolean. Confirmed AVEVA <c>AlarmExtension</c>
|
||||
/// field name. Default <c>Acked</c>.
|
||||
/// </summary>
|
||||
public string Acked { get; init; } = "acked";
|
||||
public string Acked { get; init; } = "Acked";
|
||||
|
||||
/// <summary>
|
||||
/// 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 <c>AlarmExtension</c>
|
||||
/// field name. When empty the ack-comment write path is disabled.
|
||||
/// Default <c>AckMsg</c>.
|
||||
/// </summary>
|
||||
public string AckComment { get; init; } = string.Empty;
|
||||
public string AckComment { get; init; } = "AckMsg";
|
||||
|
||||
/// <summary>
|
||||
/// Subtag name for the alarm priority. Default <c>priority</c>.
|
||||
/// Subtag name for the alarm priority / severity. Confirmed AVEVA
|
||||
/// <c>AlarmExtension</c> field name. Default <c>Priority</c>.
|
||||
/// </summary>
|
||||
public string Priority { get; init; } = "priority";
|
||||
public string Priority { get; init; } = "Priority";
|
||||
}
|
||||
|
||||
@@ -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<AlarmSubtagTarget> 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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
// "<Object>.<AlarmAttr>.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;
|
||||
|
||||
/// <summary>
|
||||
/// Live dev-rig smoke test for the subtag-fallback alarm pipeline.
|
||||
/// Validates the open design item from Task 17: confirms that
|
||||
/// <see cref="LmxSubtagAlarmSource"/> wired to a real
|
||||
/// Confirms that <see cref="LmxSubtagAlarmSource"/> wired to a real
|
||||
/// <c>LMXProxyServerClass</c> + <see cref="SubtagAlarmConsumer"/> can
|
||||
/// synthesize <see cref="MxAlarmTransitionEvent"/> records from Galaxy
|
||||
/// alarm subtags, and that <see cref="SubtagAlarmConsumer.AcknowledgeByName"/>
|
||||
/// writes the ack-comment subtag successfully.
|
||||
/// alarm subtags using the confirmed AVEVA <c>AlarmExtension</c> field
|
||||
/// names (InAlarm/Acked/AckMsg/Priority), and that
|
||||
/// <see cref="SubtagAlarmConsumer.AcknowledgeByName"/> writes the
|
||||
/// ack-comment subtag (AckMsg) successfully.
|
||||
///
|
||||
/// Skip-gated; flip <c>Skip=null</c> 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. <c><Object>.<AlarmAttr>.InAlarm</c>
|
||||
/// resolves as-is).
|
||||
/// </summary>
|
||||
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
|
||||
// <ObjectTagName>.<AlarmAttributeName>
|
||||
// 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 <ObjectTagName>.<AlarmAttributeName>. The remaining
|
||||
// live-validation item is confirming that the MXAccess runtime item
|
||||
// reference resolves without an intermediate alarm-condition segment
|
||||
// (i.e. "<Object>.<AlarmAttr>.InAlarm" resolves as-is).
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>The Galaxy provider expression used by the existing live smoke tests.</summary>
|
||||
@@ -83,20 +77,17 @@ public sealed class AlarmSubtagLiveSmokeTests
|
||||
/// <summary>The test alarm's tag name as the dispatcher composes it (Group.TagName).</summary>
|
||||
private const string AlarmTagName = "TestMachine_001.TestAlarm001";
|
||||
|
||||
// PLACEHOLDER — VERIFY against MXAccess-Public-API.md and live Galaxy.
|
||||
// Typical InProcess Platform alarm active subtag: "<TagName>.InAlarm"
|
||||
// Confirmed AVEVA AlarmExtension field name (in-alarm boolean).
|
||||
private const string PlaceholderActiveSubtag = "TestMachine_001.TestAlarm001.InAlarm";
|
||||
|
||||
// PLACEHOLDER — VERIFY. Typical: "<TagName>.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: "<TagName>.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.
|
||||
/// </summary>
|
||||
[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,
|
||||
|
||||
Reference in New Issue
Block a user