Add UI features, alarm ack, historian UTC fix, and Client.UI documentation

Major changes across the client stack:
- Settings persistence (connection, subscriptions, alarm source)
- Deferred OPC UA SDK init for instant startup
- Array/status code formatting, write value popup, alarm acknowledgment
- Severity-colored alarm rows, condition dedup on server side
- DateTimeRangePicker control with preset buttons and UTC text input
- Historian queries use wwTimezone=UTC and OPCQuality column
- Recursive subscribe from tree, multi-select remove
- Connection panel with expander, folder chooser for cert path
- Dynamic tab headers showing subscription/alarm counts
- Client.UI.md documentation with headless-rendered screenshots

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-03-31 20:46:45 -04:00
parent 8fae2cb790
commit 188cbf7d24
53 changed files with 2652 additions and 189 deletions

View File

@@ -1100,6 +1100,11 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.OpcUa
/// Gets or sets the Galaxy tag reference for the acknowledge message that triggers acknowledgment.
/// </summary>
public string AckMsgTagReference { get; set; } = "";
/// <summary>
/// Gets or sets the most recent acknowledged state so duplicate transitions are not reissued.
/// </summary>
public bool? LastAcked { get; set; }
}
#region Read/Write Handlers
@@ -1726,13 +1731,15 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.OpcUa
alarmInfo = null;
}
// Check for Acked transitions
// Check for Acked transitions — skip if state hasn't changed
if (_alarmAckedTags.TryGetValue(address, out ackedAlarmInfo))
{
newAcked = vtq.Value is true || vtq.Value is 1 ||
(vtq.Value is int ackedIntVal && ackedIntVal != 0);
pendingAckedEvents.Add((ackedAlarmInfo, newAcked));
ackedAlarmInfo = null; // handled
if (ackedAlarmInfo.LastAcked.HasValue && newAcked == ackedAlarmInfo.LastAcked.Value)
ackedAlarmInfo = null; // No transition → skip
else
pendingAckedEvents.Add((ackedAlarmInfo, newAcked));
}
}
@@ -1818,6 +1825,12 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.OpcUa
// Apply Acked state changes
foreach (var (info, acked) in pendingAckedEvents)
{
// Double-check dedup under lock
if (info.LastAcked.HasValue && acked == info.LastAcked.Value)
continue;
info.LastAcked = acked;
var condition = info.ConditionNode;
if (condition == null) continue;