Skip to content
Draft
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
107 changes: 107 additions & 0 deletions DynamicReportQualificationResultWindow.G25A2.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
using System.Text;
using ArIED61850Tester.Services;

namespace ArIED61850Tester;

internal partial class DynamicReportQualificationResultWindow
{
internal DynamicReportQualificationResultWindow(DynamicReportStimulusEligibilityDiscoveryResult result)
{
ArgumentNullException.ThrowIfNull(result);
InitializeComponent();

Title = "G2.5-A2 Stimulus Eligibility Discovery Evidence";
HeaderText.Text = "G2.5-A2 Stimulus Eligibility Discovery";
SummaryText.Text = result.Summary;
StateText.Text = result.IsSuccess
? "Stimulus Candidate Proven"
: result.IsBlocked
? "Blocked"
: "No Candidate Transition Proven";
EvidenceTextBox.Text = BuildG25A2Evidence(result);

if (result.IsSuccess)
SetPassBadge();
}

private static string BuildG25A2Evidence(DynamicReportStimulusEligibilityDiscoveryResult result)
{
var builder = new StringBuilder();
builder.AppendLine("ARSAS G2.5-A2 READ-ONLY STIMULUS ELIGIBILITY DISCOVERY EVIDENCE");
builder.AppendLine(new string('=', 84));
builder.AppendLine($"Result: {result.Summary}");
builder.AppendLine($"Blocked: {result.IsBlocked}");
builder.AppendLine($"G2.5-A2 success: {result.IsSuccess}");
builder.AppendLine($"Stimulus eligibility proven: {result.StimulusEligibilityProven}");

if (result.Identity is not null)
{
builder.AppendLine();
builder.AppendLine("IED IDENTITY");
builder.AppendLine($"Stable identity: {result.Identity.StableIdentityKey}");
builder.AppendLine($"Model fingerprint: {result.Identity.ModelFingerprint}");
builder.AppendLine($"Model: {result.Identity.Model}");
builder.AppendLine($"Firmware: {result.Identity.FirmwareRevision}");
builder.AppendLine($"Profile revision: {result.Identity.ProfileRevision}");
}

builder.AppendLine();
builder.AppendLine("READ-ONLY SAMPLING RESULT");
builder.AppendLine($"Input profile state: {result.InputProfile?.State.ToString() ?? "-"}");
builder.AppendLine($"Baseline captured: {result.BaselineCaptured}");
builder.AppendLine($"Association healthy: {result.AssociationHealthy}");
builder.AppendLine($"Candidate count: {result.CandidateCount}");
builder.AppendLine($"Fast-lane count: {result.FastLaneCount}");
builder.AppendLine($"Sample cycles: {result.SampleCycles}");
builder.AppendLine($"Read failures: {result.ReadFailures}");

builder.AppendLine();
builder.AppendLine("ELIGIBLE CANDIDATES");
if (result.EligibleCandidates.Count == 0)
{
builder.AppendLine(" none");
}
else
{
foreach (var candidate in result.EligibleCandidates)
{
builder.AppendLine($" #{candidate.Rank} {candidate.Reference}");
builder.AppendLine($" MMS: {candidate.MmsReference}");
builder.AppendLine($" Kind: {candidate.Kind}");
builder.AppendLine($" Baseline -> final: {candidate.BaselineValue} -> {candidate.FinalValue}");
builder.AppendLine($" Transitions: {candidate.TransitionCount}");
builder.AppendLine($" Observed active/pulse duration ms: {FormatDuration(candidate.ObservedActiveMilliseconds)}");
builder.AppendLine($" Fast lane: {candidate.FastLane}");
builder.AppendLine($" Score: {candidate.Score}");
builder.AppendLine($" Selection reason: {candidate.SelectionReason}");
foreach (var transition in candidate.Transitions)
builder.AppendLine($" {transition.ObservedAtUtc:O} | {transition.BeforeValue} -> {transition.AfterValue}");
}
}

builder.AppendLine();
builder.AppendLine("ALL OBSERVED CANDIDATES");
foreach (var candidate in result.Observations)
{
builder.AppendLine($" {(candidate.FastLane ? "FAST" : "SECONDARY"),-9} | score={candidate.Score,4} | transitions={candidate.TransitionCount,2} | {candidate.Kind,-20} | {candidate.Reference} | {candidate.BaselineValue} -> {candidate.FinalValue}");
}

builder.AppendLine();
builder.AppendLine("WIRE / DIAGNOSTIC EVIDENCE");
foreach (var line in result.EvidenceLines)
builder.AppendLine(line);

builder.AppendLine();
builder.AppendLine("SAFETY STATE");
builder.AppendLine("G2.5-A2 is read-only: it does not read/write RCB attributes, enable RptEna, send GI, or mutate any DataSet.");
builder.AppendLine("G2.5-A2 does not alter the persisted InformationReportProven profile.");
builder.AppendLine("G2.5-A2 PASS only identifies a physical stimulus-responsive MMS candidate; it does NOT prove dchg reporting.");
builder.AppendLine("Production automatic dynamic reporting remains OFF.");
return builder.ToString();
}

private static string FormatDuration(double? milliseconds)
=> milliseconds.HasValue
? milliseconds.Value.ToString("0.0", System.Globalization.CultureInfo.InvariantCulture)
: "-";
}
31 changes: 30 additions & 1 deletion DynamicReportQualificationUiBehavior.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ private static async void OnPreviewKeyDown(object sender, KeyEventArgs e)
{
if (sender is not MainWindow window ||
Keyboard.Modifiers != (ModifierKeys.Control | ModifierKeys.Shift) ||
(e.Key != Key.Q && e.Key != Key.R && e.Key != Key.T && e.Key != Key.O && e.Key != Key.C && e.Key != Key.D))
(e.Key != Key.Q && e.Key != Key.R && e.Key != Key.T && e.Key != Key.O && e.Key != Key.C && e.Key != Key.D && e.Key != Key.E))
return;

e.Handled = true;
Expand Down Expand Up @@ -66,6 +66,8 @@ private static async void OnPreviewKeyDown(object sender, KeyEventArgs e)
await RunG24CleanupClosureAsync(window, device);
else if (e.Key == Key.D)
await RunG25ASpontaneousDataChangeAsync(window, device);
else if (e.Key == Key.E)
await RunG25A2StimulusEligibilityAsync(window, device);
else
await RunG24Async(window, device);
}
Expand Down Expand Up @@ -185,6 +187,33 @@ private static async Task RunG24CleanupClosureAsync(MainWindow window, Models.Ie
evidenceWindow.ShowDialog();
}

private static async Task RunG25A2StimulusEligibilityAsync(MainWindow window, Models.Iec61850MonitorDevice device)
{
var answer = MessageBox.Show(
window,
$"Run G2.5-A2 read-only stimulus eligibility discovery for {device.Name} ({device.EndpointText})?\n\n" +
"READ-ONLY PHYSICAL DIAGNOSTIC — NO RCB / NO DATASET / NO GI\n\n" +
"ARSAS will open ONE isolated read-only MMS association, discover and rank a bounded set of ST/stVal status candidates, and capture their baseline values. Priority is given to the live ControlStatusReference, XCBR/CSWI/XSWI Pos.stVal, breaker command-received status, and related GGIO status points.\n\n" +
"IMPORTANT: do NOT operate anything while discovery/baseline is running. WAIT until the status explicitly shows 'G2.5-A2 READY — READ ONLY'. Only then perform ONE normal safe OPEN/CLOSE or equivalent physical stimulus. Do not repeat the stimulus.\n\n" +
"A2 samples a fast lane of at most 8 candidates plus a bounded secondary lane. After the first observed transition it samples for a short settle period to classify the candidate as persistent/latched or momentary/pulse-like.\n\n" +
"This A2 action performs ZERO RCB/DataSet mutation: no RptEna, Resv, DatSet, TrgOps, OptFlds, GI, Define/DeleteNamedVariableList, report monitor, profile save, or ProductionEligible change. A2 PASS only identifies which MMS point physically responds; it does NOT prove dchg reporting.\n\n" +
"Continue?",
"G2.5-A2 Stimulus Eligibility Discovery",
MessageBoxButton.YesNo,
MessageBoxImage.Information,
MessageBoxResult.No);
if (answer != MessageBoxResult.Yes)
return;

window.LastStatusText = $"G2.5-A2: discovering bounded read-only stimulus candidates on {device.Name}; DO NOT stimulate yet…";
var progress = new Progress<string>(text => window.LastStatusText = text);
var service = new DynamicReportStimulusEligibilityDiscoveryService();
var result = await service.RunAsync(device, device.Signals.ToArray(), progress, CancellationToken.None);
window.LastStatusText = result.Summary;
var evidenceWindow = new DynamicReportQualificationResultWindow(result) { Owner = window };
evidenceWindow.ShowDialog();
}

private static async Task RunG25ASpontaneousDataChangeAsync(MainWindow window, Models.Iec61850MonitorDevice device)
{
var answer = MessageBox.Show(
Expand Down
Loading
Loading