diff --git a/MainWindow.IedBrowser.RcbFocusedRead.cs b/MainWindow.IedBrowser.RcbFocusedRead.cs index db79dbc64..a60177322 100644 --- a/MainWindow.IedBrowser.RcbFocusedRead.cs +++ b/MainWindow.IedBrowser.RcbFocusedRead.cs @@ -64,7 +64,8 @@ await _runtime.ReadBrowserValuesAsync( device.LiveValues.PublishForAssociation(association, result.Target.Reference, result.Target.FunctionalConstraint, scalar, result.Quality, result.DeviceTimestamp, "MMS Read", - result.UtcTimeEvidence, result.QualityEvidence); + result.UtcTimeEvidence, result.QualityEvidence, + typedBoolean: result.TypedBoolean); }); _iedBrowserLastLiveVersion = -1; RefreshIedBrowserVisibleValues(); diff --git a/MainWindow.IedBrowser.cs b/MainWindow.IedBrowser.cs index 1af8c3598..c1d3a1d49 100644 --- a/MainWindow.IedBrowser.cs +++ b/MainWindow.IedBrowser.cs @@ -30,6 +30,10 @@ public partial class MainWindow private bool? _iedBrowserRenderedConnected; private bool _iedBrowserForceRefresh; private Iec61850MonitorDevice? _iedBrowserCachedDevice; + private long _iedBrowserCachedAssociation = -1; + // UI freshness fence only: a monitor/RCB state transition invalidates + // previous RptEna display proof without modifying the live-value ledger. + private DateTime _iedBrowserRcbEnabledMinReadUtc = DateTime.MinValue; private LiveIedModelDiscoveryDocument? _iedBrowserCachedModel; private IedBrowserSchemaIndex? _iedBrowserValueSchemaIndex; // Immutable EnumType declarations, never a second live-value store. @@ -570,7 +574,8 @@ void DrainReadResults() readAssociation, result.Target.Reference, result.Target.FunctionalConstraint, safeValue, result.Quality, result.DeviceTimestamp, "MMS Read", - result.UtcTimeEvidence, result.QualityEvidence); + result.UtcTimeEvidence, result.QualityEvidence, + typedBoolean: result.TypedBoolean); if (!accepted) return; _iedBrowserReadFailureEvidence?.ClearFailure(result.Target); successes++; @@ -916,6 +921,36 @@ private bool TryGetBrowserReadFailure( return false; } + private void RefreshIedBrowserRcbEnabledIndicators(Iec61850MonitorDevice device) + { + // One model identity index per coalesced flush; no full-IED MMS probe, + // duplicate live state store or per-RCB model inventory scan. + var targets = IedBrowserRcbEnabledPresentation.VerifiedTargets( + device.LiveDiscoveryModel); + var connected = device.IsConnected && !device.LiveValues.IsOffline; + + // Right list objects can be visible while left LD/LN folders stay lazy. + foreach (var detail in _iedBrowserDetails.Rows) + { + if (detail.NavigateTo is not IedBrowserTreeRow node || + node.Payload is not RcbExportRow rcb) + continue; + var verified = IedBrowserRcbEnabledPresentation.TryVerifiedTrue( + device.LiveValues, rcb, targets, connected, out var tooltip, + _iedBrowserRcbEnabledMinReadUtc); + if (node.SetRcbEnabled(verified, tooltip)) + detail.RefreshRcbUsage(); + } + foreach (var node in _iedBrowserTree.Rows) + { + if (node.Payload is not RcbExportRow rcb) continue; + var verified = IedBrowserRcbEnabledPresentation.TryVerifiedTrue( + device.LiveValues, rcb, targets, connected, out var tooltip, + _iedBrowserRcbEnabledMinReadUtc); + node.SetRcbEnabled(verified, tooltip); + } + } + private void RefreshIedBrowserVisibleValues() { if (MainTabs.SelectedIndex != IedBrowserWorkspaceIndex || @@ -933,12 +968,19 @@ private void RefreshIedBrowserVisibleValues() : 0; if (usageChanges > 0) { + // Runtime ownership changes are not fresh Boolean read-back. + _iedBrowserRcbEnabledMinReadUtc = DateTime.UtcNow; foreach (var treeRow in _iedBrowserTree.Rows) treeRow.RefreshRcbUsage(); foreach (var detailRow in _iedBrowserDetails.Rows) detailRow.RefreshRcbUsage(); } var version = device.LiveValues.Version; + // RptEna is current-association MMS Read evidence, never RCB occupancy. + // Update only current virtual rows when source authority changes. + if (usageChanges > 0 || _iedBrowserLastLiveVersion != version || + !ReferenceEquals(_iedBrowserLastCfCanonicalModel, device.LiveCanonicalModel)) + RefreshIedBrowserRcbEnabledIndicators(device); if (_iedBrowserLastLiveVersion == version && ReferenceEquals(_iedBrowserLastCfCanonicalModel, device.LiveCanonicalModel)) { @@ -1699,6 +1741,7 @@ private void ObserveIedBrowserDevice(Iec61850MonitorDevice? device) _iedBrowserFilesLoading = false; _iedBrowserFileCatalog = null; _iedBrowserObservedDevice = device; + _iedBrowserRcbEnabledMinReadUtc = DateTime.MinValue; _iedBrowserSnapshot = null; _iedBrowserSnapshotLease = null; _iedBrowserFileOwner = device; @@ -1724,7 +1767,10 @@ or nameof(Iec61850MonitorDevice.IsMonitoring))) if (!ReferenceEquals(sender, _iedBrowserObservedDevice)) return; if (e.PropertyName is nameof(Iec61850MonitorDevice.IsMonitoring)) + { _iedBrowserForceRefresh = true; + _iedBrowserRcbEnabledMinReadUtc = DateTime.UtcNow; + } // A disconnected/replaced association can never supply "live" inspector values. if (_iedBrowserObservedDevice?.IsConnected != true) @@ -1871,6 +1917,7 @@ private void RefreshIedBrowser() if (device == null) { _iedBrowserCachedDevice = null; + _iedBrowserCachedAssociation = -1; _iedBrowserRenderedPage = null; _iedBrowserRenderedConnected = null; _iedBrowserNavigation.SelectFromTree(null); @@ -1913,6 +1960,7 @@ private void RefreshIedBrowser() var changed = _iedBrowserForceRefresh || !ReferenceEquals(device, _iedBrowserCachedDevice) || + device.LiveValues.Association != _iedBrowserCachedAssociation || !ReferenceEquals(model, _iedBrowserCachedModel) || !ReferenceEquals(_iedBrowserSnapshot, _iedBrowserCachedSnapshot) || !string.Equals(sourcePath, _iedBrowserCachedSourcePath, StringComparison.Ordinal) || @@ -1920,7 +1968,9 @@ private void RefreshIedBrowser() if (changed) { _iedBrowserForceRefresh = false; - var sameDevice = ReferenceEquals(device, _iedBrowserCachedDevice); + var sameDevice = ReferenceEquals(device, _iedBrowserCachedDevice) && + device.LiveValues.Association == _iedBrowserCachedAssociation && + ReferenceEquals(model, _iedBrowserCachedModel); var priorKey = sameDevice ? (IedBrowserTreeGrid.SelectedItem as IedBrowserTreeRow)?.Key : null; @@ -1954,6 +2004,7 @@ private void RefreshIedBrowser() device.IsConnected && device.IsMonitoring); _iedBrowserCachedRcbRows = rcbs; _iedBrowserCachedDevice = device; + _iedBrowserCachedAssociation = device.LiveValues.Association; _iedBrowserCachedModel = model; _iedBrowserCachedSnapshot = _iedBrowserSnapshot; _iedBrowserCachedSourcePath = sourcePath; @@ -1963,23 +2014,9 @@ private void RefreshIedBrowser() _iedBrowserRemoteFiles, EffectiveSclIedName(device), device.SclWorkspace?.DesignModel); _iedBrowserTree.Reset(roots, expandedKeys); - // Reports is the familiar entry point; selecting the category shows its - // full RCB list on the right. Preserve explicit expansion on same-IED refresh. - if (!sameDevice) - { - var reports = roots.FirstOrDefault(r => r.Key == "category/reports"); - if (reports is not null) - { - _iedBrowserTree.Expand(reports); - var ld = reports.GetChildren().FirstOrDefault(); - if (ld is not null) - { - _iedBrowserTree.Expand(ld); - var ln = ld.GetChildren().FirstOrDefault(); - if (ln is not null) _iedBrowserTree.Expand(ln); - } - } - } + // Fresh IED/association/model epoch: all six Objects categories + // remain collapsed. Selecting Reports fills the right pane + // without materializing left LD/LN/RCB descendants. IedBrowserTreeGrid.ItemsSource = _iedBrowserTree.Rows; var selected = priorKey == null ? null : _iedBrowserTree.FindVisible(priorKey); diff --git a/MainWindow.xaml b/MainWindow.xaml index 26f881fe5..cb5a4ab52 100644 --- a/MainWindow.xaml +++ b/MainWindow.xaml @@ -11,6 +11,8 @@ TextOptions.TextHintingMode="Fixed" Icon="Assets/app-icon.ico" Closing="Window_Closing" PreviewKeyDown="MainWindow_PreviewKeyDown"> + + M380-320h200q25 0 42.5-17.5T640-380v-120q0-25-17.5-42.5T580-560v-60q0-42-29-71t-71-29q-42 0-71 29t-29 71h60q0-17 11.5-28.5T480-660q17 0 28.5 11.5T520-620v60H380q-25 0-42.5 17.5T320-500v120q0 25 17.5 42.5T380-320ZM480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T197-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z