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

namespace ArIED61850Tester;

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

Title = "G2.5-A Spontaneous dchg Proof Evidence";
HeaderText.Text = "G2.5-A Spontaneous dchg Proof";
SummaryText.Text = result.Summary;
StateText.Text = result.IsSuccess
? "Spontaneous dchg Proven"
: result.IsBlocked
? "Blocked"
: "dchg proof not proven";
EvidenceTextBox.Text = BuildG25AEvidence(result);

if (result.IsSuccess)
SetPassBadge();
}

private static string BuildG25AEvidence(DynamicReportSpontaneousDataChangeCommissioningResult result)
{
var builder = new StringBuilder();
builder.AppendLine("ARSAS G2.5-A SPONTANEOUS DCHG INFORMATIONREPORT EVIDENCE");
builder.AppendLine(new string('=', 76));
builder.AppendLine($"Result: {result.Summary}");
builder.AppendLine($"Blocked: {result.IsBlocked}");
builder.AppendLine($"G2.5-A success: {result.IsSuccess}");

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("G2.5-A TARGET");
builder.AppendLine($"Input profile state: {result.InputProfile?.State.ToString() ?? "-"}");
builder.AppendLine($"URCB: {G25ATextOrDash(result.RcbReference)}");
builder.AppendLine($"Temporary DataSet: {G25ATextOrDash(result.DataSetReference)}");
builder.AppendLine($"RptID: {G25ATextOrDash(result.ReportId)}");
builder.AppendLine($"Qualified members: {result.MemberReferences.Count}");
foreach (var member in result.MemberReferences)
builder.AppendLine(" " + member);

builder.AppendLine();
builder.AppendLine("TRIGGER CONTRACT");
builder.AppendLine("TrgOps temporary: dchg ONLY");
builder.AppendLine("Canonical TrgOps raw target: 0240");
builder.AppendLine("OptFlds temporary: reason-for-inclusion + data-set-name");
builder.AppendLine("Canonical OptFlds raw target: 061800");
builder.AppendLine("GI requested: False");
builder.AppendLine("Integrity/qchg/dupd requested: False");

builder.AppendLine();
builder.AppendLine("PROOF RESULT");
builder.AppendLine($"Activation proven: {result.ActivationProven}");
builder.AppendLine($"Spontaneous data-change proven: {result.SpontaneousDataChangeProven}");
builder.AppendLine($"Association healthy after report: {result.AssociationHealthyAfterReport}");
builder.AppendLine($"Included indexes: [{string.Join(",", result.IncludedIndexes)}]");
builder.AppendLine($"Reasons: [{string.Join(",", result.Reasons)}]");
builder.AppendLine("Included members:");
foreach (var member in result.IncludedMemberReferences)
builder.AppendLine(" " + member);

builder.AppendLine();
builder.AppendLine("CLEANUP CLOSURE");
builder.AppendLine($"Monitor cleanup: {result.MonitorCleanupSucceeded}");
builder.AppendLine($"Proof-field restore: {result.ProofFieldRestoreSucceeded}");
builder.AppendLine($"Fresh-association cleanup closure: {result.FreshCleanupClosureSucceeded}");

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

builder.AppendLine();
builder.AppendLine("SAFETY STATE");
builder.AppendLine("G2.5-A sends NO GI and accepts only an actual spontaneous data-change report as proof.");
builder.AppendLine("G2.5-A does not alter the persisted InformationReportProven profile.");
builder.AppendLine("G2.5-A PASS != ProductionEligible.");
builder.AppendLine("Production automatic dynamic reporting remains OFF until later G2.5/G2.6 gates pass.");
return builder.ToString();
}

private static string G25ATextOrDash(string? value)
=> string.IsNullOrWhiteSpace(value) ? "-" : value.Trim();
}
82 changes: 82 additions & 0 deletions DynamicReportQualificationResultWindow.G25A1.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
using System.Text;
using ArIED61850Tester.Services;

namespace ArIED61850Tester;

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

Title = "G2.5-A1 Stimulus Witness Evidence";
HeaderText.Text = "G2.5-A1 Stimulus Witness + dchg Correlation";
SummaryText.Text = result.Summary;
StateText.Text = result.IsSuccess
? "Stimulus/report correlation Proven"
: result.StimulusWitnessProven
? "Stimulus proven; report correlation not proven"
: "Stimulus witness not proven";
EvidenceTextBox.Text = BuildG25A1Evidence(result);

if (result.IsSuccess)
SetPassBadge();
}

private static string BuildG25A1Evidence(DynamicReportStimulusWitnessCommissioningResult result)
{
var core = result.CoreResult;
var witness = result.Witness;
var builder = new StringBuilder();
builder.AppendLine("ARSAS G2.5-A1 STIMULUS WITNESS + SPONTANEOUS DCHG CORRELATION EVIDENCE");
builder.AppendLine(new string('=', 88));
builder.AppendLine($"Result: {result.Summary}");
builder.AppendLine($"G2.5-A1 success: {result.IsSuccess}");
builder.AppendLine($"Stimulus witness proven: {result.StimulusWitnessProven}");
builder.AppendLine($"Report correlation proven: {result.ReportCorrelationProven}");
builder.AppendLine($"Correlated indexes: [{string.Join(",", result.CorrelatedIndexes)}]");

builder.AppendLine();
builder.AppendLine("CORE G2.5-A REPORT PATH");
builder.AppendLine($"Activation proven: {core.ActivationProven}");
builder.AppendLine($"Spontaneous data-change proven: {core.SpontaneousDataChangeProven}");
builder.AppendLine($"Report association healthy: {core.AssociationHealthyAfterReport}");
builder.AppendLine($"Report included indexes: [{string.Join(",", core.IncludedIndexes)}]");
builder.AppendLine($"Report reasons: [{string.Join(",", core.Reasons)}]");
builder.AppendLine($"Monitor cleanup: {core.MonitorCleanupSucceeded}");
builder.AppendLine($"Proof-field restore: {core.ProofFieldRestoreSucceeded}");
builder.AppendLine($"Fresh cleanup closure: {core.FreshCleanupClosureSucceeded}");

builder.AppendLine();
builder.AppendLine("INDEPENDENT READ-ONLY STIMULUS WITNESS");
builder.AppendLine($"ARMED observed: {witness.ArmedObserved}");
builder.AppendLine($"Baseline captured: {witness.BaselineCaptured}");
builder.AppendLine($"Qualified-member change observed: {witness.ChangeObserved}");
builder.AppendLine($"Witness association healthy: {witness.AssociationHealthy}");
builder.AppendLine($"Sample cycles: {witness.SampleCycles}");
builder.AppendLine($"Read failures: {witness.ReadFailures}");
if (witness.BaselineValues.Count > 0)
{
builder.AppendLine("Baseline values:");
for (var index = 0; index < witness.BaselineValues.Count && index < core.MemberReferences.Count; index++)
builder.AppendLine($" [{index}] {core.MemberReferences[index]} = {witness.BaselineValues[index]}");
}
builder.AppendLine("Witnessed transitions:");
foreach (var transition in witness.Transitions)
builder.AppendLine($" [{transition.Index}] {transition.MemberReference}: {transition.BeforeValue} -> {transition.AfterValue} @ {transition.ObservedAtUtc:O}");

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

builder.AppendLine();
builder.AppendLine("SAFETY STATE");
builder.AppendLine("The G2.5-A1 witness association is read-only and does not read/write RCB attributes or mutate DataSets.");
builder.AppendLine("The core G2.5-A path still sends NO GI and remains one-URCB commissioning only.");
builder.AppendLine("The persisted InformationReportProven profile is not advanced by this gate.");
builder.AppendLine("Production automatic dynamic reporting remains OFF.");
return builder.ToString();
}
}
85 changes: 40 additions & 45 deletions 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.Q && e.Key != Key.R && e.Key != Key.T && e.Key != Key.O && e.Key != Key.C && e.Key != Key.D))
return;

e.Handled = true;
Expand Down Expand Up @@ -64,6 +64,8 @@ private static async void OnPreviewKeyDown(object sender, KeyEventArgs e)
await RunP1OptionalFieldsProbeAsync(window, device);
else if (e.Key == Key.C)
await RunG24CleanupClosureAsync(window, device);
else if (e.Key == Key.D)
await RunG25ASpontaneousDataChangeAsync(window, device);
else
await RunG24Async(window, device);
}
Expand Down Expand Up @@ -102,16 +104,9 @@ private static async Task RunG23Async(MainWindow window, Models.Iec61850MonitorD

window.LastStatusText = $"G2.3 qualification: opening isolated auxiliary MMS association to {device.Name}…";
var service = new DynamicReportQualificationCommissioningService();
var result = await service.RunAsync(
device,
device.Signals.ToArray(),
CancellationToken.None);

var result = await service.RunAsync(device, device.Signals.ToArray(), CancellationToken.None);
window.LastStatusText = result.Summary;
var evidenceWindow = new DynamicReportQualificationResultWindow(result)
{
Owner = window
};
var evidenceWindow = new DynamicReportQualificationResultWindow(result) { Owner = window };
evidenceWindow.ShowDialog();
}

Expand All @@ -134,16 +129,9 @@ private static async Task RunP0TriggerProbeAsync(MainWindow window, Models.Iec61

window.LastStatusText = $"P0: opening isolated auxiliary MMS association to {device.Name} for one-URCB TrgOps micro-probe…";
var service = new DynamicReportTriggerOptionsProbeCommissioningService();
var result = await service.RunAsync(
device,
device.Signals.ToArray(),
CancellationToken.None);

var result = await service.RunAsync(device, device.Signals.ToArray(), CancellationToken.None);
window.LastStatusText = result.Summary;
var evidenceWindow = new DynamicReportQualificationResultWindow(result)
{
Owner = window
};
var evidenceWindow = new DynamicReportQualificationResultWindow(result) { Owner = window };
evidenceWindow.ShowDialog();
}

Expand All @@ -166,16 +154,9 @@ private static async Task RunP1OptionalFieldsProbeAsync(MainWindow window, Model

window.LastStatusText = $"P1: opening isolated auxiliary MMS association to {device.Name} for one-URCB OptFlds micro-probe…";
var service = new DynamicReportOptionalFieldsProbeCommissioningService();
var result = await service.RunAsync(
device,
device.Signals.ToArray(),
CancellationToken.None);

var result = await service.RunAsync(device, device.Signals.ToArray(), CancellationToken.None);
window.LastStatusText = result.Summary;
var evidenceWindow = new DynamicReportQualificationResultWindow(result)
{
Owner = window
};
var evidenceWindow = new DynamicReportQualificationResultWindow(result) { Owner = window };
evidenceWindow.ShowDialog();
}

Expand All @@ -198,16 +179,37 @@ private static async Task RunG24CleanupClosureAsync(MainWindow window, Models.Ie

window.LastStatusText = $"G2.4-C: opening fresh read-only auxiliary MMS association to {device.Name}…";
var service = new DynamicReportCleanupClosureCommissioningService();
var result = await service.RunAsync(
device,
device.Signals.ToArray(),
CancellationToken.None);
var result = await service.RunAsync(device, device.Signals.ToArray(), 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(
window,
$"Run G2.5-A1 spontaneous dchg + independent stimulus witness for {device.Name} ({device.EndpointText})?\n\n" +
"ACTIVE COMMISSIONING — ONE URCB / NO GI + READ-ONLY WITNESS\n\n" +
"The core path is unchanged G2.5-A: exact G2.4-proven URCB + 8-member set, temporary TrgOps=dchg ONLY (0240), reason-for-inclusion + data-set-name (061800), one temporary DataSet, RptEna=true, NO GI, and strict spontaneous data-change report validation.\n\n" +
"G2.5-A1 adds a SECOND MMS association that is READ ONLY. It resolves and samples only the same 8 proven process/status members. It does NOT read/write RCB attributes, does NOT Define/Delete a DataSet, and does NOT send GI.\n\n" +
"IMPORTANT: the status may briefly show 'G2.5-A ARMED — NO GI'. DO NOT stimulate on that message. WAIT until the status changes to 'G2.5-A1 WITNESS READY'. Only then cause ONE normal safe physical/process status change affecting one of the 8 proven points. Do NOT manually edit any RCB or DataSet.\n\n" +
"The witness records baseline -> changed values and DataSet indexes. If no report arrives, evidence distinguishes 'stimulus did not touch the envelope' from 'qualified member changed but no dchg report arrived'. If a report arrives, G2.5-A1 correlates the witnessed changed index with the report included index.\n\n" +
"The existing G2.5-A cleanup/restore/fresh-association closure remains mandatory. The persisted InformationReportProven profile is NOT modified and production dynamic reporting remains OFF.\n\n" +
"Continue?",
"G2.5-A1 Stimulus Witness",
MessageBoxButton.YesNo,
MessageBoxImage.Warning,
MessageBoxResult.No);
if (answer != MessageBoxResult.Yes)
return;

window.LastStatusText = $"G2.5-A1: preparing dchg-only report path plus independent read-only witness for {device.Name}…";
var progress = new Progress<string>(text => window.LastStatusText = text);
var service = new DynamicReportStimulusWitnessCommissioningService();
var result = await service.RunAsync(device, device.Signals.ToArray(), progress, CancellationToken.None);
window.LastStatusText = result.Summary;
var evidenceWindow = new DynamicReportQualificationResultWindow(result)
{
Owner = window
};
var evidenceWindow = new DynamicReportQualificationResultWindow(result) { Owner = window };
evidenceWindow.ShowDialog();
}

Expand All @@ -231,16 +233,9 @@ private static async Task RunG24Async(MainWindow window, Models.Iec61850MonitorD

window.LastStatusText = $"G2.4: opening isolated auxiliary MMS association to {device.Name} for transactional one-URCB proof…";
var service = new DynamicReportActivationCommissioningServiceV2();
var result = await service.RunAsync(
device,
device.Signals.ToArray(),
CancellationToken.None);

var result = await service.RunAsync(device, device.Signals.ToArray(), CancellationToken.None);
window.LastStatusText = result.Summary;
var evidenceWindow = new DynamicReportQualificationResultWindow(result)
{
Owner = window
};
var evidenceWindow = new DynamicReportQualificationResultWindow(result) { Owner = window };
evidenceWindow.ShowDialog();
}
}
Loading
Loading