Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion MainWindow.SclQuickActions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ internal async Task OpenIedWorkspaceActionsAsync(Iec61850MonitorDevice device)
ApplyStaticDataSetSelection(device);
if (device.SelectedLiveSignalCount == 0)
{
SetStatus($"{device.Name}: Static DataSet selected, but no report-authoritative process leaf is available to monitor.");
SetStatus($"{device.Name}: Static DataSet selected, but no DataSet-authoritative process signal is available to monitor.");
return;
}

Expand Down
13 changes: 7 additions & 6 deletions MainWindow.SharedSclWorkspace.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,14 @@ private enum SclSignalSelectionMode

private void ApplyStaticDataSetSelection(Iec61850MonitorDevice device)
{
// Static DataSet is a protocol-authority mode, not a request to monitor the
// whole IED and then opportunistically prefer reports. First make sure every
// ARIEC-owned DataSet member is present in the workspace, then select only
// runtime signals that carry an explicit static DataSet identity.
// Static DataSet remains the source of truth for WHAT is monitored. Acquisition
// prefers the IED's configured BRCB/URCB, while bounded MMS reads remain available
// for the initial image and for DataSet members whose report mapping/projection is
// incomplete. Dynamic DataSet writes stay disabled so monitoring never rewrites the
// IED configuration merely to obtain complete readable process values.
var merge = Iec61850DataSetSignalInventoryService.EnsureMandatorySignals(device);
RegisterRecoveredDataSetSignals(device, merge);
Iec61850MonitoringModeRegistry.UseStaticDataSetReportOnly(device);
Iec61850MonitoringModeRegistry.UseStaticDataSetWithMmsFallback(device);

device.BeginBulkSignalSelection();
try
Expand All @@ -64,7 +65,7 @@ private void ApplyStaticDataSetSelection(Iec61850MonitorDevice device)
AddLog(
"INFO",
device.Name,
$"Static DataSet report-only authority selected: {device.SelectedLiveSignalCount} runtime DataSet signal(s); cyclic MMS process polling and dynamic DataSet writes are disabled for this monitoring mode.");
$"Static DataSet authority selected: {device.SelectedLiveSignalCount} runtime DataSet signal(s); configured RCB reporting is preferred, bounded MMS read fallback remains available for initial/uncovered values, and dynamic DataSet writes are disabled.");
}

private void ClearSharedSignalSelection(Iec61850MonitorDevice device)
Expand Down
2 changes: 1 addition & 1 deletion SclSignalSelectionModeWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="0" Style="{StaticResource PrimaryButton}" Padding="14,11"
Click="MonitorStaticDataSet_Click" ToolTip="Select authoritative static DataSet members and start online monitoring">
Click="MonitorStaticDataSet_Click" ToolTip="Select authoritative static DataSet members. Configured BRCB/URCB reporting is preferred; bounded MMS reads keep initial or uncovered values readable without creating dynamic DataSets.">
<StackPanel>
<TextBlock Text="Static DataSet" FontWeight="SemiBold" HorizontalAlignment="Center"/>
<TextBlock Text="Select + monitor" FontSize="10.5" Opacity="0.82" HorizontalAlignment="Center"/>
Expand Down
61 changes: 51 additions & 10 deletions Services/Iec61850MonitoringModeRegistry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,32 +11,61 @@ public static class Iec61850MonitoringModeRegistry
{
private sealed class DeviceModeState
{
public bool StaticDataSetReportOnly { get; set; }
public bool StaticDataSetMode { get; set; }
public bool StrictReportOnly { get; set; }
public bool PreviousDynamicDataSetWrites { get; set; }
public bool HasPreviousDynamicDataSetWrites { get; set; }
}

private static readonly ConditionalWeakTable<Iec61850MonitorDevice, DeviceModeState> States = new();

public static bool IsStaticDataSetMode(Iec61850MonitorDevice device)
{
ArgumentNullException.ThrowIfNull(device);
return States.TryGetValue(device, out var state) && state.StaticDataSetMode;
}

public static bool IsStaticDataSetReportOnly(Iec61850MonitorDevice device)
{
ArgumentNullException.ThrowIfNull(device);
return States.TryGetValue(device, out var state) && state.StaticDataSetReportOnly;
return States.TryGetValue(device, out var state) &&
state.StaticDataSetMode &&
state.StrictReportOnly;
}

public static void UseStaticDataSetReportOnly(Iec61850MonitorDevice device)
/// <summary>
/// Selects the Static DataSet workflow while retaining bounded MMS reads for the
/// initial live image and for DataSet members that cannot be safely supplied by a
/// configured report. Dynamic DataSet writes remain disabled, so the IED's existing
/// engineering configuration is never modified merely to obtain readable values.
/// </summary>
public static void UseStaticDataSetWithMmsFallback(Iec61850MonitorDevice device)
{
ArgumentNullException.ThrowIfNull(device);
var state = States.GetOrCreateValue(device);
lock (state)
{
if (!state.StaticDataSetReportOnly)
{
state.PreviousDynamicDataSetWrites = device.AllowDynamicDataSetWrites;
state.HasPreviousDynamicDataSetWrites = true;
}
CapturePreviousDynamicWriteSetting(device, state);
state.StaticDataSetMode = true;
state.StrictReportOnly = false;
device.AllowDynamicDataSetWrites = false;
}
}

state.StaticDataSetReportOnly = true;
/// <summary>
/// Strict report-only variant retained for explicit engineering/diagnostic use.
/// Process values that cannot be delivered through configured static reporting are
/// intentionally left unavailable in this mode.
/// </summary>
public static void UseStaticDataSetReportOnly(Iec61850MonitorDevice device)
{
ArgumentNullException.ThrowIfNull(device);
var state = States.GetOrCreateValue(device);
lock (state)
{
CapturePreviousDynamicWriteSetting(device, state);
state.StaticDataSetMode = true;
state.StrictReportOnly = true;
device.AllowDynamicDataSetWrites = false;
}
}
Expand All @@ -47,12 +76,24 @@ public static void UseHybrid(Iec61850MonitorDevice device)
var state = States.GetOrCreateValue(device);
lock (state)
{
state.StaticDataSetReportOnly = false;
state.StaticDataSetMode = false;
state.StrictReportOnly = false;
if (state.HasPreviousDynamicDataSetWrites)
{
device.AllowDynamicDataSetWrites = state.PreviousDynamicDataSetWrites;
state.HasPreviousDynamicDataSetWrites = false;
}
}
}

private static void CapturePreviousDynamicWriteSetting(
Iec61850MonitorDevice device,
DeviceModeState state)
{
if (state.StaticDataSetMode || state.HasPreviousDynamicDataSetWrites)
return;

state.PreviousDynamicDataSetWrites = device.AllowDynamicDataSetWrites;
state.HasPreviousDynamicDataSetWrites = true;
}
}
49 changes: 44 additions & 5 deletions tests/ARSAS.Tests/StaticDataSetReportOnlyModeRegressionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,35 @@ namespace ARSAS.Tests;
public sealed class StaticDataSetReportOnlyModeRegressionTests
{
[Fact]
public void StaticMode_DisablesDynamicWrites_AndManualModeRestoresPriorValue()
public void StaticDataSetWithMmsFallback_DisablesDynamicWrites_AndManualModeRestoresPriorValue()
{
var device = new Iec61850MonitorDevice { AllowDynamicDataSetWrites = true };

Iec61850MonitoringModeRegistry.UseStaticDataSetWithMmsFallback(device);

Assert.True(Iec61850MonitoringModeRegistry.IsStaticDataSetMode(device));
Assert.False(Iec61850MonitoringModeRegistry.IsStaticDataSetReportOnly(device));
Assert.False(device.AllowDynamicDataSetWrites);

Iec61850MonitoringModeRegistry.UseHybrid(device);

Assert.False(Iec61850MonitoringModeRegistry.IsStaticDataSetMode(device));
Assert.False(Iec61850MonitoringModeRegistry.IsStaticDataSetReportOnly(device));
Assert.True(device.AllowDynamicDataSetWrites);
}

[Fact]
public void StrictStaticDataSetReportOnly_RemainsAvailableForExplicitDiagnosticUse()
{
var device = new Iec61850MonitorDevice { AllowDynamicDataSetWrites = true };

Iec61850MonitoringModeRegistry.UseStaticDataSetReportOnly(device);

Assert.True(Iec61850MonitoringModeRegistry.IsStaticDataSetMode(device));
Assert.True(Iec61850MonitoringModeRegistry.IsStaticDataSetReportOnly(device));
Assert.False(device.AllowDynamicDataSetWrites);

Iec61850MonitoringModeRegistry.UseHybrid(device);
Assert.False(Iec61850MonitoringModeRegistry.IsStaticDataSetReportOnly(device));
Assert.True(device.AllowDynamicDataSetWrites);
}

Expand All @@ -30,7 +51,23 @@ public void StaticSelection_AllowsOnlyRuntimeSignalsWithExplicitDataSetAuthority
}

[Fact]
public void RuntimeContract_StaticDataSetMode_DoesNotScheduleCyclicMmsProcessPolling()
public void ReadCompatibleStaticMode_FlowsIntoExistingBoundedMmsScheduler()
{
var source = File.ReadAllText(FindRepoFile("Services/Iec61850MonitorRuntime.cs"));
Assert.Contains(
"var staticDataSetReportOnly = Iec61850MonitoringModeRegistry.IsStaticDataSetReportOnly(device);",
source,
StringComparison.Ordinal);
Assert.Contains(
"NextPollUtc = staticDataSetReportOnly ? DateTime.MaxValue : DateTime.UtcNow",
source,
StringComparison.Ordinal);
Assert.Contains("ResetPollQueue(session);", source, StringComparison.Ordinal);
Assert.Contains("MMS live start", source, StringComparison.Ordinal);
}

[Fact]
public void StrictRuntimeContract_StaticDataSetReportOnlyMode_DoesNotScheduleCyclicMmsProcessPolling()
{
var source = File.ReadAllText(FindRepoFile("Services/Iec61850MonitorRuntime.cs"));
Assert.Contains("Static DataSet acquisition ready", source, StringComparison.Ordinal);
Expand All @@ -40,12 +77,14 @@ public void RuntimeContract_StaticDataSetMode_DoesNotScheduleCyclicMmsProcessPol
}

[Fact]
public void SharedSclStaticSelection_UsesDatasetAuthorityPolicy_NotSelectEverything()
public void SharedSclStaticSelection_UsesDatasetAuthorityAndReadableMmsFallback_NotStrictReportOnly()
{
var source = File.ReadAllText(FindRepoFile("MainWindow.SharedSclWorkspace.cs"));
Assert.Contains("Iec61850DataSetSignalInventoryService.EnsureMandatorySignals(device)", source, StringComparison.Ordinal);
Assert.Contains("Iec61850MonitoringModeRegistry.UseStaticDataSetReportOnly(device)", source, StringComparison.Ordinal);
Assert.Contains("Iec61850MonitoringModeRegistry.UseStaticDataSetWithMmsFallback(device)", source, StringComparison.Ordinal);
Assert.Contains("Iec61850StaticDataSetSelectionPolicy.IsEligible(signal)", source, StringComparison.Ordinal);
Assert.Contains("bounded MMS read fallback remains available", source, StringComparison.Ordinal);
Assert.DoesNotContain("Iec61850MonitoringModeRegistry.UseStaticDataSetReportOnly(device)", source, StringComparison.Ordinal);
Assert.DoesNotContain("signal.IsSelected = !string.IsNullOrWhiteSpace(signal.DataSetReference)", source, StringComparison.Ordinal);
}

Expand Down
Loading