-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathIoListTestingWindow.P0RelayBenchHotPath.cs
More file actions
306 lines (271 loc) · 11.7 KB
/
Copy pathIoListTestingWindow.P0RelayBenchHotPath.cs
File metadata and controls
306 lines (271 loc) · 11.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
using System.Diagnostics;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Threading;
using ArIED61850Tester.Models;
using ArIED61850Tester.Models.IoTesting;
using ArIED61850Tester.Services.IoTesting;
namespace ArIED61850Tester;
/// <summary>
/// Relay-bench hot-path guard. This class handler runs before ordinary Button.Click handlers
/// and owns only the FAT operations that must never enter the legacy synchronous lifecycle.
/// It keeps an already-running Engineering acquisition session untouched and treats FAT as
/// a lightweight evidence consumer.
/// </summary>
public partial class IoListTestingWindow
{
private static readonly bool P0RelayBenchButtonGuardRegistered = RegisterP0RelayBenchButtonGuard();
private bool _p0HotPathStartRunning;
private bool _p0HotPathResumeRunning;
private bool _p0HotPathStopRunning;
private bool _p0HotPathCommandRunning;
private static bool RegisterP0RelayBenchButtonGuard()
{
EventManager.RegisterClassHandler(
typeof(Button),
Button.ClickEvent,
new RoutedEventHandler(P0RelayBenchButton_Click),
handledEventsToo: true);
return true;
}
private static void P0RelayBenchButton_Click(object sender, RoutedEventArgs e)
{
if (sender is not Button button || FindOwningFatWindow(button) is not IoListTestingWindow window)
return;
var text = ButtonText(button);
if (IsFastStartLabel(text) &&
window.SelectedIed?.IsLiveMonitoring == true &&
window.Session.CanStart)
{
e.Handled = true;
_ = window.StartFatFromSharedLiveSessionAsync(button);
return;
}
if (text.Equals("Resume", StringComparison.OrdinalIgnoreCase) && window.Session.CanResume)
{
e.Handled = true;
_ = window.ResumeFatWithoutBlockingDispatcherAsync(button);
return;
}
if (text.Equals("Stop", StringComparison.OrdinalIgnoreCase) && window.Session.CanStop)
{
e.Handled = true;
_ = window.StopFatWithoutBlockingDispatcherAsync(button);
return;
}
// FAT position Confirm buttons inherit SignalDefinition as DataContext from the row.
// Intercept only those buttons; Engineering command controls are untouched.
if (text.Equals("Confirm", StringComparison.OrdinalIgnoreCase) &&
button.DataContext is SignalDefinition signal &&
signal.IsPositionControl &&
signal.ControlConfirmationPending)
{
e.Handled = true;
_ = window.ConfirmFatPositionWithoutUiBlockAsync(button, signal);
}
}
private async Task StartFatFromSharedLiveSessionAsync(Button button)
{
if (_p0HotPathStartRunning)
return;
var ied = SelectedIed;
if (ied == null)
return;
_p0HotPathStartRunning = true;
var originalOpacity = button.Opacity;
button.IsHitTestVisible = false;
button.Opacity = 0.68;
await Dispatcher.Yield(DispatcherPriority.Render);
var stopwatch = Stopwatch.StartNew();
try
{
var preflight = IoTestSessionPreflight.Validate(ied);
if (!preflight.Succeeded)
{
ShowActionResult(preflight, "FAT session scope is not ready");
return;
}
// Critical fast path: Engineering is already connected + monitoring. Do NOT run
// the legacy live-preparation routine again, do NOT reconcile/reselect/restart
// reports, and do NOT touch the acquisition cadence. FAT only arms evidence on
// the live rows already proven by the shared process image.
var requested = ied.TestPoints
.Where(point =>
point.WorkspaceSelected &&
point.IsIncludedInFat &&
point.TestEnabled &&
point.ImportReady)
.ToList();
var live = requested
.Where(point => point.LiveBindingState == IoTestLiveBindingState.LivePointReady)
.ToList();
if (live.Count == 0)
{
var failure = IoTestSessionActionResult.Failure(
$"{ied.IedName} is monitoring, but none of the {requested.Count} selected FAT row(s) has a proven live point.");
ShowActionResult(failure, "FAT evidence session could not start");
return;
}
var result = Session.Start(ied, live);
ShowActionResult(result, "FAT evidence session could not start");
if (result.Succeeded)
{
var waiting = requested.Count - live.Count;
PreparationStatusText = waiting == 0
? $"{ied.IedName} FAT active · attached directly to shared Engineering live data · {live.Count} row(s) armed"
: $"{ied.IedName} FAT active · {live.Count}/{requested.Count} proven live row(s) armed · {waiting} waiting for binding";
Storage?.ScheduleSave();
}
else
{
PreparationStatusText = result.Message;
}
RaiseStatusProperties();
RaiseSelectedIedContextProperties();
Trace.WriteLine(
$"[IO FAT P0] shared-live Start/Continue completed in {stopwatch.ElapsedMilliseconds} ms; " +
$"ied={ied.IedName}; requested={requested.Count}; live={live.Count}; succeeded={result.Succeeded}.");
}
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or InvalidOperationException or ArgumentException)
{
Trace.WriteLine($"[IO FAT P0] shared-live Start/Continue failed after {stopwatch.ElapsedMilliseconds} ms: {ex}");
MessageBox.Show(this, ex.Message, "FAT session could not start", MessageBoxButton.OK, MessageBoxImage.Error);
}
finally
{
button.Opacity = originalOpacity;
button.IsHitTestVisible = true;
_p0HotPathStartRunning = false;
RaiseSelectedIedContextProperties();
}
}
private async Task ResumeFatWithoutBlockingDispatcherAsync(Button button)
{
if (_p0HotPathResumeRunning)
return;
_p0HotPathResumeRunning = true;
var originalOpacity = button.Opacity;
button.IsHitTestVisible = false;
button.Opacity = 0.68;
await Dispatcher.Yield(DispatcherPriority.Render);
var stopwatch = Stopwatch.StartNew();
try
{
var result = Session.Resume();
ShowActionResult(result, "FAT session could not resume");
if (result.Succeeded)
Storage?.ScheduleSave();
RaiseStatusProperties();
RaiseSelectedIedContextProperties();
Trace.WriteLine($"[IO FAT P0] Resume completed in {stopwatch.ElapsedMilliseconds} ms; succeeded={result.Succeeded}.");
}
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or InvalidOperationException)
{
MessageBox.Show(this, ex.Message, "FAT session could not resume", MessageBoxButton.OK, MessageBoxImage.Error);
}
finally
{
button.Opacity = originalOpacity;
button.IsHitTestVisible = true;
_p0HotPathResumeRunning = false;
}
}
private async Task StopFatWithoutBlockingDispatcherAsync(Button button)
{
if (_p0HotPathStopRunning)
return;
_p0HotPathStopRunning = true;
var originalOpacity = button.Opacity;
button.IsHitTestVisible = false;
button.Opacity = 0.68;
await Dispatcher.Yield(DispatcherPriority.Render);
var stopwatch = Stopwatch.StartNew();
try
{
IoTestSessionActionResult result;
using (IoTestEvidenceJournal.BeginDeferredSealScope())
result = Session.Stop();
ShowActionResult(result, "FAT session could not stop");
if (result.Succeeded)
{
// Queue drain, durable disk barrier and full hash-chain verification never
// run on WPF Dispatcher. Engineering/FAT remain repaintable while sealing.
await IoTestEvidenceJournal.AwaitDeferredSealsAsync();
if (Storage != null)
await Task.Run(Storage.SaveNow);
}
RaiseStatusProperties();
RaiseSelectedIedContextProperties();
Trace.WriteLine($"[IO FAT P0] Stop completed in {stopwatch.ElapsedMilliseconds} ms; succeeded={result.Succeeded}.");
}
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or InvalidOperationException)
{
Trace.WriteLine($"[IO FAT P0] Stop failed after {stopwatch.ElapsedMilliseconds} ms: {ex}");
MessageBox.Show(this, ex.Message, "FAT evidence could not be sealed", MessageBoxButton.OK, MessageBoxImage.Error);
}
finally
{
button.Opacity = originalOpacity;
button.IsHitTestVisible = true;
_p0HotPathStopRunning = false;
RaiseSelectedIedContextProperties();
}
}
private async Task ConfirmFatPositionWithoutUiBlockAsync(Button button, SignalDefinition signal)
{
if (_p0HotPathCommandRunning || Owner is not MainWindow engineeringWindow)
return;
if (!signal.TryClaimControlConfirmation(out var claim, out var rejectionReason) || claim == null)
{
signal.ControlLastResult = $"Command rejected: {rejectionReason}.";
return;
}
_p0HotPathCommandRunning = true;
var originalOpacity = button.Opacity;
button.IsHitTestVisible = false;
button.Opacity = 0.68;
RefreshFatCommandActions(signal);
await Dispatcher.Yield(DispatcherPriority.Render);
try
{
await engineeringWindow.ExecuteIoFatControlClaimAsync(signal, claim);
// Command-service feedback is not allowed to overwrite the shared Engineering
// process image. Re-project the newest actual status point after command release,
// then once more after the CSWI stability guard has had time to publish.
engineeringWindow.ReconcileIoFatCommandValueFromSharedProcessImage(signal);
await Task.Delay(450);
engineeringWindow.ReconcileIoFatCommandValueFromSharedProcessImage(signal);
}
finally
{
RefreshFatCommandActions(signal);
button.Opacity = originalOpacity;
button.IsHitTestVisible = true;
_p0HotPathCommandRunning = false;
}
}
private static bool IsFastStartLabel(string text)
=> text.Equals("Start FAT", StringComparison.OrdinalIgnoreCase) ||
text.Equals("Continue FAT", StringComparison.OrdinalIgnoreCase) ||
text.Equals("Retest FAT", StringComparison.OrdinalIgnoreCase);
private static string ButtonText(Button button)
=> button.Content switch
{
string text => text.Trim(),
TextBlock textBlock => textBlock.Text?.Trim() ?? string.Empty,
_ => button.Content?.ToString()?.Trim() ?? string.Empty
};
private static IoListTestingWindow? FindOwningFatWindow(DependencyObject start)
{
DependencyObject? current = start;
while (current != null)
{
if (current is IoListTestingWindow window)
return window;
current = VisualTreeHelper.GetParent(current) ?? LogicalTreeHelper.GetParent(current);
}
return null;
}
}