Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
115 commits
Select commit Hold shift + click to select a range
c4abd9c
P10.9L: restore command catalog and exact read-only live ctlModel dia…
masarray Oct 12, 2026
5564d01
P10.9L: qualify exact engine probe CI SHA and preserve immutable ance…
masarray Oct 12, 2026
7d14678
P10.9L: align fail-closed regression oracles and exact engine SHA adm…
masarray Oct 12, 2026
875eeb4
P10.9M: decouple control action presentation from live write authoriz…
masarray Oct 12, 2026
77bc3a7
P10.9M: assert FAT command authorization contract instead of obsolete…
masarray Oct 12, 2026
5b80c1b
P10.9N-P: preserve live verified CDC across dataset projection and di…
masarray Oct 12, 2026
5cb04a1
P10.9P: invalidate control CDC and operate readiness on automatic MMS…
masarray Oct 12, 2026
4d74531
P10.9Q: confirm directional controls, fit dock viewport and add expli…
masarray Oct 12, 2026
d558284
P10.9Q: bind commissioning through exact WPF-safe runtime signature a…
masarray Oct 12, 2026
667729d
P10.9Q: preserve full P10 generic-control provenance and fail-closed …
masarray Oct 12, 2026
60d0bf4
P10.9R: restore Live Values layout, compact command controls, replace…
masarray Oct 12, 2026
0edfc7d
P10.9R: allow explicit status-only model 0 end-to-end in commissionin…
masarray Oct 12, 2026
f4e0d97
P10.9R: correct command row actual rendered height including cell pad…
masarray Oct 12, 2026
b198796
P10.9R: keep engine lock notes vendor-neutral under clean-room source…
masarray Oct 12, 2026
abe7e2a
P10.9R: remove duplicate compact Model button MinHeight XAML attribute
masarray Oct 12, 2026
175f0f3
P10.9S: correct ctlModel dispatch, refresh descriptor and fix Dock bu…
masarray Oct 12, 2026
acdef06
P10.9S: serialize descriptor refresh with control writes and revoke s…
masarray Oct 12, 2026
1446696
P10.9S: route ctlModel Write through real UI facade and per-device se…
masarray Oct 12, 2026
079638f
P10.9T: make Open/Close inspect-on-click, remove intrusive tooltips, …
masarray Oct 12, 2026
357f383
P10.9T: update Dock regression expectations for concise hover-free co…
masarray Oct 12, 2026
7091ca2
P10.9U: pin typed standard Pos status recovery for truncated ZIV Name…
masarray Oct 12, 2026
4071cac
P10.9U: pin engine with successful typed Pos status regression CI
masarray Oct 12, 2026
c117d93
test(control): end-to-end consumer typed BSC recovery across refresh …
masarray Oct 13, 2026
4d118af
build(control): pin CI-verified P10.9V ARIEC typed BSC resolver
masarray Oct 13, 2026
d9cd6d7
ci(control): admit only exact CI-qualified P10.9V pin with full U lin…
masarray Oct 13, 2026
ec4dff9
test(provenance): retain immutable U ancestry while checking exact V pin
masarray Oct 13, 2026
e585b3b
test(provenance): retain immutable U ancestry while checking exact V pin
masarray Oct 13, 2026
bc387f2
feat(control): classify operator toast by explicit AddCause and feedb…
masarray Oct 13, 2026
4e979bc
ui(control): center compact nonblocking semantic command toast
masarray Oct 13, 2026
1775f54
feat(control): show centered evidence-graded toast for all final outc…
masarray Oct 13, 2026
6acc730
ux(control): surface pre-wire validation blocks without claiming IED …
masarray Oct 13, 2026
fac5dd6
test(control): cover position, tap, interlock, auth, timeout and cent…
masarray Oct 13, 2026
0626fda
test(control): assert all-result toast while retaining failure-only d…
masarray Oct 13, 2026
40b718e
test(control): assert precise AddCause toast titles and provenance
masarray Oct 13, 2026
ce24bd5
ui(control): move concise error-only toast into center of Command Dock
masarray Oct 13, 2026
0ba99d0
fix(control): silence successful commands and render compact dock-loc…
masarray Oct 13, 2026
8aaf446
test(control): lock toast inside Command Dock with no secondary caption
masarray Oct 13, 2026
00d2a8b
test(control): failure-only visible toast guards success and already-…
masarray Oct 13, 2026
c42e7b3
test(control): assert dock toast alignment independent of XAML wrapping
masarray Oct 13, 2026
9b0f9f0
polish(control): keep IED failure toast labels concise and evidence-safe
masarray Oct 13, 2026
fe72db9
test: align compact toast label
masarray Oct 13, 2026
17d38b1
test(control): make failure-only toast gate assertion CRLF-safe
masarray Oct 13, 2026
8022da4
ui(control): unify directional Raise/Lower palette and pending confir…
masarray Oct 13, 2026
89e9bcf
ui(control): give both Raise and Lower colored primary-level action b…
masarray Oct 13, 2026
60e63b0
test(control): lock Raise/Lower palette and action-matched confirmations
masarray Oct 13, 2026
7f3e246
feat(events): classify observed IEC report reasons without inferring …
masarray Oct 13, 2026
2da9402
feat(events): expose traceable event cause and report channel in SOE …
masarray Oct 13, 2026
3b5b159
ui(events): show IEC report cause and buffer-capable stream separately
masarray Oct 13, 2026
e790e59
feat(events): export event cause, report channel, and raw reason to CSV
masarray Oct 13, 2026
e8f4be9
test(events): validate report reasons, BRCB display, no fabricated bu…
masarray Oct 13, 2026
2f6f8e7
test(events): preserve existing SOE subtitle contract while extending…
masarray Oct 13, 2026
cfa6509
ux(events): short evidence-backed SOE labels and actual report ID fal…
masarray Oct 13, 2026
e6a2d88
model(events): retain decoded ReportId and RCB identity with compact …
masarray Oct 13, 2026
b23c6cf
runtime(events): preserve real report header ID and matched RCB per S…
masarray Oct 13, 2026
7f66949
ux(events): compact event grid and show report acquisition identity w…
masarray Oct 13, 2026
834e60f
export(events): keep brief operator columns with full report provenan…
masarray Oct 13, 2026
d24338f
test(events): cover concise SOE, real report ID, RCB fallback and saf…
masarray Oct 13, 2026
0c7bc34
fix(p0): marshal control projection reset to WPF dispatcher and alway…
masarray Oct 13, 2026
4778fd6
model(ui): expose offline state on retained signal rows
masarray Oct 13, 2026
16b819a
fix(p0): one-click Stop disconnect, retain muted offline signals, saf…
masarray Oct 13, 2026
b6aed6f
ux(p0): mute cached offline values and remove center Start Monitor/Ed…
masarray Oct 13, 2026
6e8ced0
fix(p0): enable Stop during connecting, reconnecting and monitoring
masarray Oct 13, 2026
fc1d3eb
fix(p0): preemptive Stop while busy and restore offline signal rows a…
masarray Oct 13, 2026
37ab88f
test(p0): reproduce stop thread fault guards and offline Play reconne…
masarray Oct 13, 2026
9b14959
fix(p0): lock Play/Stop during disconnect, disable Stop once offline
masarray Oct 13, 2026
6d3b189
fix(p0): make repeated stop click a no-op while disconnecting
masarray Oct 13, 2026
2993355
fix(p0): coalesce repeated Stop operations per IED and fence associat…
masarray Oct 13, 2026
d03048b
test(p0): enforce dispatcher-safe authority revocation before automat…
masarray Oct 13, 2026
ba123b3
test(p0): actual repeated-stop idempotence and button single-flight r…
masarray Oct 13, 2026
15274ad
fix(p0): clear stale control inspection busy state on stopped associa…
masarray Oct 13, 2026
b3bfde9
fix(p0): clear stale control inspection busy state on stopped associa…
masarray Oct 13, 2026
3ab2844
safety(p0): Model never auto-connects offline; summary counts only li…
masarray Oct 13, 2026
8918dc1
ux(p0): lock Control Model editor while IED association is offline
masarray Oct 13, 2026
f7e953f
test(p0): guard against offline control editor hidden reconnect
masarray Oct 13, 2026
b4ca9e2
test(p0): verify preemptive stop semantics instead of comment wording
masarray Oct 13, 2026
1a9d93e
fix(events): identify actual routing RCB, never confuse IEC RptID wit…
masarray Oct 13, 2026
d8d48a7
model(ui): keep verified last reporting RCB per live point and update…
masarray Oct 13, 2026
5e70969
runtime(events): propagate last actual routed RCB to Explorer snapsho…
masarray Oct 13, 2026
775d05e
ux(events): apply actual report-source RCB only on routed snapshot
masarray Oct 13, 2026
26fb864
ux(events): bind both Explorer and Live Monitor acquisition to actual…
masarray Oct 13, 2026
d0604a8
test(events): regress ZIV Rpt_0 vs brcb01, all Acquisition grids and …
masarray Oct 13, 2026
877d17c
fix(events): preserve verified per-point RCB across coalesced status …
masarray Oct 13, 2026
b173b63
ux(events): retain verified RCB identity after non-value status snaps…
masarray Oct 13, 2026
b21ffd5
test(events): validate verified RCB survives UI status batching
masarray Oct 13, 2026
1929956
ux(events): make computed RCB Acquisition display read-only in all grids
masarray Oct 13, 2026
3484b41
fix(build): remove duplicate IsReadOnly attribute in Event Log XAML
masarray Oct 13, 2026
64db0ca
fix(rcb): accumulate every live routed RCB per point, not last-only
masarray Oct 13, 2026
3efeda9
model(rcb): immutable per-point multi-RCB identity for Explorer, Live…
masarray Oct 13, 2026
2b8386f
policy(rcb): display deterministic comma-separated actual RCB names, …
masarray Oct 13, 2026
2575c82
runtime(rcb): preserve exact event-emitting RCB plus observed multi-R…
masarray Oct 13, 2026
cda76bc
ux(rcb): bind every proven per-signal RCB, not only the last report
masarray Oct 13, 2026
b434fb4
test(rcb): multiple concurrent report sources, dedup, ordering and ex…
masarray Oct 13, 2026
5c1054c
ux(rcb): compact multi-RCB Acquisition cells with full-list tooltip
masarray Oct 13, 2026
5bfe557
fix(rcb): revoke prior association's RCB sources before automatic rec…
masarray Oct 13, 2026
548c530
test(rcb): assert multi-source UI projection instead of removed last-…
masarray Oct 13, 2026
99b76ed
fix(P10.9): verified ctlModel write closes dialog; show inline failur…
masarray Oct 13, 2026
8204378
feat(P10.9): add source-accurate Report ID and read-only RCB inspecto…
masarray Oct 13, 2026
2333641
fix(P10.9): compare engine ctlModel and consumer enum using exact IEC…
masarray Oct 13, 2026
768c8fa
fix(P10.9): bind Report ID template click to inspector window instance
masarray Oct 13, 2026
c1eff5d
fix(P10.9): require accepted CF write; hide offline RCB state and sho…
masarray Oct 13, 2026
5db42fd
fix(P10.9): operator-friendly SOE categories and auto-hide provenance…
masarray Oct 13, 2026
eaafd44
feat(P10.9): add native IED Browser workspace with RCB, DataSets, mod…
masarray Oct 13, 2026
b080f4b
feat(P10.9): lifecycle-safe IED Browser model views and explicit read…
masarray Oct 13, 2026
9b5aa9f
test(P10.9): lock eight-tab navigation and truthful read-only IED Bro…
masarray Oct 13, 2026
fe3d41e
test(P10.9): align navigation dimensions and reject actual MMS write …
masarray Oct 13, 2026
e568e69
release(P10.9): restore proven RCB exporter and seven-tab shell; reta…
masarray Oct 13, 2026
dd36d98
fix(release): decouple SOE drawer from deferred RCB inspector model
masarray Oct 13, 2026
b040c54
fix(release): restore trusted SCL identity authority under original p…
masarray Oct 13, 2026
d1a2184
fix(release): restore original production RCB dialog for mouse, keybo…
masarray Oct 13, 2026
8a8b466
test(release): assert original RCB production route and keep experime…
masarray Oct 13, 2026
1c87e43
test(release): pin original RCB dialog across click and preview input…
masarray Oct 13, 2026
98edd5a
release(v1.6.41): show actual assembly version in native title bar an…
masarray Oct 13, 2026
ba647a6
test(v1.6.41): correct release-manifest JSON literal in title-version…
masarray Oct 13, 2026
d5eb693
ci(release): run Ubuntu release-ready fixtures with POSIX paths and r…
masarray Oct 13, 2026
6285867
ci(release): normalize Linux PowerShell Python script and source-dir …
masarray Oct 13, 2026
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
12 changes: 6 additions & 6 deletions .github/workflows/release-windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,11 @@ jobs:
python-version: '3.12'

- name: Run offline release-ready and installer-verifier fixtures
shell: bash
run: |
python .\ArIED61850Tester\scripts\test-ci-release-ready.py
if ($LASTEXITCODE -ne 0) { throw 'Release-ready metadata gate fixtures failed.' }
python .\ArIED61850Tester\scripts\test-ci-installer-reuse.py
if ($LASTEXITCODE -ne 0) { throw 'Validated-installer verifier fixtures failed.' }
set -euo pipefail
python ./ArIED61850Tester/scripts/test-ci-release-ready.py
python ./ArIED61850Tester/scripts/test-ci-installer-reuse.py

- name: Wait for exact immutable release dependencies
id: ready
Expand All @@ -76,9 +76,9 @@ jobs:
GITHUB_TOKEN: ${{ github.token }}
run: |
$proofPath = Join-Path $env:RUNNER_TEMP 'arsas-release-ready.json'
python .\ArIED61850Tester\scripts\wait-ci-release-ready.py `
python ./ArIED61850Tester/scripts/wait-ci-release-ready.py `
--repository $env:GITHUB_REPOSITORY `
--source-dir .\ArIED61850Tester `
--source-dir ./ArIED61850Tester `
--source-sha $env:GITHUB_SHA `
--ref-type $env:GITHUB_REF_TYPE `
--ref-name $env:GITHUB_REF_NAME `
Expand Down
2 changes: 1 addition & 1 deletion .release/windows.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "1.6.40",
"version": "1.6.41",
"channel": "stable",
"platform": "windows-x64",
"primaryAsset": "ARSAS-Windows-x64-Setup.exe",
Expand Down
55 changes: 46 additions & 9 deletions App.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -476,9 +476,9 @@
<Style x:Key="CommandActionButtonBase" TargetType="Button">
<Setter Property="Foreground" Value="White"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Padding" Value="15,7"/>
<Setter Property="MinWidth" Value="66"/>
<Setter Property="MinHeight" Value="33"/>
<Setter Property="Padding" Value="10,3"/>
<Setter Property="MinWidth" Value="58"/>
<Setter Property="MinHeight" Value="29"/>
<Setter Property="FontWeight" Value="SemiBold"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="SnapsToDevicePixels" Value="True"/>
Expand All @@ -490,15 +490,12 @@
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="11">
<Border.Effect>
<DropShadowEffect BlurRadius="11" ShadowDepth="3" Opacity="0.22" Color="#31445F"/>
</Border.Effect>
CornerRadius="9">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"
Margin="{TemplateBinding Padding}"
TextElement.Foreground="{TemplateBinding Foreground}"/>
</Border>
<Border x:Name="InteractionSurface" Background="Transparent" CornerRadius="11"
<Border x:Name="InteractionSurface" Background="Transparent" CornerRadius="9"
BorderBrush="Transparent" BorderThickness="1" IsHitTestVisible="False"/>
</Grid>
<ControlTemplate.Triggers>
Expand All @@ -524,6 +521,36 @@
</Setter>
</Style>

<!-- One semantic palette for tap direction and the corresponding confirmation. -->
<SolidColorBrush x:Key="CommandRaiseFill" Color="#526AE8"/>
<SolidColorBrush x:Key="CommandRaiseStroke" Color="#4058C5"/>
<SolidColorBrush x:Key="CommandLowerFill" Color="#E9B451"/>
<SolidColorBrush x:Key="CommandLowerStroke" Color="#C28C2C"/>
<SolidColorBrush x:Key="CommandLowerInk" Color="#4A3108"/>

<Style x:Key="CommandRaiseButton" TargetType="Button" BasedOn="{StaticResource CommandActionButtonBase}">
<Setter Property="Background" Value="{StaticResource CommandRaiseFill}"/>
<Setter Property="BorderBrush" Value="{StaticResource CommandRaiseStroke}"/>
<Setter Property="MinWidth" Value="56"/>
</Style>
<Style x:Key="CommandLowerButton" TargetType="Button" BasedOn="{StaticResource CommandActionButtonBase}">
<Setter Property="Background" Value="{StaticResource CommandLowerFill}"/>
<Setter Property="BorderBrush" Value="{StaticResource CommandLowerStroke}"/>
<Setter Property="Foreground" Value="{StaticResource CommandLowerInk}"/>
<Setter Property="MinWidth" Value="56"/>
</Style>

<Style x:Key="CommandDockPrimaryButton" TargetType="Button" BasedOn="{StaticResource CommandActionButtonBase}">
<Setter Property="Background" Value="{StaticResource AccentGradient}"/>
<Setter Property="BorderBrush" Value="#4B83DF"/>
<Setter Property="MinWidth" Value="56"/>
</Style>
<Style x:Key="CommandDockSoftButton" TargetType="Button" BasedOn="{StaticResource CommandActionButtonBase}">
<Setter Property="Foreground" Value="{StaticResource Ink}"/>
<Setter Property="Background" Value="#F7F9FC"/>
<Setter Property="BorderBrush" Value="#CDD8E5"/>
<Setter Property="MinWidth" Value="56"/>
</Style>
<Style x:Key="CommandOpenButton" TargetType="Button" BasedOn="{StaticResource CommandActionButtonBase}">
<Setter Property="Background" Value="{StaticResource CommandOpenGradient}"/>
<Setter Property="BorderBrush" Value="#69D2A1"/>
Expand All @@ -543,6 +570,16 @@
<Setter Property="Background" Value="{StaticResource CommandCloseGradient}"/>
<Setter Property="BorderBrush" Value="#FF9A9A"/>
</DataTrigger>
<DataTrigger Binding="{Binding ControlPendingAction}" Value="Raise">
<Setter Property="Background" Value="{StaticResource CommandRaiseFill}"/>
<Setter Property="BorderBrush" Value="{StaticResource CommandRaiseStroke}"/>
<Setter Property="Foreground" Value="White"/>
</DataTrigger>
<DataTrigger Binding="{Binding ControlPendingAction}" Value="Lower">
<Setter Property="Background" Value="{StaticResource CommandLowerFill}"/>
<Setter Property="BorderBrush" Value="{StaticResource CommandLowerStroke}"/>
<Setter Property="Foreground" Value="{StaticResource CommandLowerInk}"/>
</DataTrigger>
</Style.Triggers>
</Style>

Expand Down Expand Up @@ -1010,7 +1047,7 @@
</Style>

<Style x:Key="CommandDataGridCell" TargetType="DataGridCell" BasedOn="{StaticResource {x:Type DataGridCell}}">
<Setter Property="Padding" Value="10,4"/>
<Setter Property="Padding" Value="8,2"/>
<Setter Property="BorderBrush" Value="#E5ECF5"/>
<Setter Property="BorderThickness" Value="0,0,0,1"/>
</Style>
Expand Down
6 changes: 3 additions & 3 deletions ArIED61850Tester.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@
<Product>ARSAS</Product>
<AssemblyTitle>ARSAS - IEC 61850 Engineering Workstation</AssemblyTitle>
<Description>Open-source Windows IEC 61850 engineering workstation for MMS model discovery, reporting, independent multi-IED monitoring, GOOSE subscription, fault-record file transfer, Sampled Values engineering and evidence export, SCL workflows, diagnostics, sequence of events, and guarded control validation.</Description>
<Version>1.6.40</Version>
<AssemblyVersion>1.6.40.0</AssemblyVersion>
<FileVersion>1.6.40.0</FileVersion>
<Version>1.6.41</Version>
<AssemblyVersion>1.6.41.0</AssemblyVersion>
<FileVersion>1.6.41.0</FileVersion>
<PackageProjectUrl>https://github.com/masarray/arsas</PackageProjectUrl>
<RepositoryUrl>https://github.com/masarray/arsas</RepositoryUrl>
<RepositoryType>git</RepositoryType>
Expand Down
53 changes: 53 additions & 0 deletions ControlModelWriteWindow.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<Window x:Class="ArIED61850Tester.ControlModelWriteWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Write Data to IED" Width="510" Height="290" MinWidth="490"
ResizeMode="NoResize" WindowStartupLocation="CenterOwner"
Background="#F4F7FB" FontFamily="{StaticResource AppFontFamily}"
Icon="Assets/app-icon.ico" Loaded="Window_Loaded" Closing="Window_Closing">
<Grid Margin="16">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock x:Name="ReferenceLabel" Grid.Row="0" FontSize="11.5"
Foreground="{StaticResource Ink}" FontFamily="{StaticResource AppCodeFontFamily}"
TextTrimming="CharacterEllipsis" Margin="0,0,0,12"/>
<Border Grid.Row="1" BorderThickness="1" BorderBrush="{StaticResource Line}"
CornerRadius="8" Background="#F8FAFC" Padding="10,12">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="105"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="ctlModel [CF]" VerticalAlignment="Center"
Foreground="{StaticResource Ink}" FontSize="12"/>
<ComboBox x:Name="ModelPicker" Grid.Column="1" Height="31"
ItemsSource="{Binding Choices}" DisplayMemberPath="Label"
SelectionChanged="ModelPicker_SelectionChanged" IsEnabled="False"/>
<Button x:Name="ReadButton" Grid.Column="2" Content="Read" Click="Read_Click"
Style="{StaticResource SoftButton}" Height="29" MinHeight="29"
Padding="10,3" Margin="8,0,0,0"/>
</Grid>
</Border>
<StackPanel Grid.Row="2" Margin="2,10,0,5">
<TextBlock x:Name="StatusLabel" FontSize="11" TextWrapping="Wrap"
Foreground="{StaticResource Muted}" Text="Reading ctlModel from IED…"/>
<TextBox x:Name="FailureDetails" Visibility="Collapsed" Margin="0,7,0,0"
IsReadOnly="True" IsTabStop="False" TextWrapping="Wrap"
VerticalScrollBarVisibility="Auto" MaxHeight="73" MinHeight="48"
FontSize="11" Padding="8,6" BorderThickness="1" BorderBrush="#E7C4C4"
Background="#FFF8F8" Foreground="#923C3C"/>
</StackPanel>
<StackPanel Grid.Row="3" Orientation="Horizontal" HorizontalAlignment="Right">
<Button x:Name="WriteButton" Content="Write" Click="Write_Click"
Style="{StaticResource PrimaryButton}" Width="93" Height="31"
MinHeight="31" Padding="10,4" IsEnabled="False" Margin="0,0,8,0"/>
<Button Content="Cancel" Click="Close_Click" Style="{StaticResource SoftButton}"
Width="85" Height="31" MinHeight="31" Padding="10,4"/>
</StackPanel>
</Grid>
</Window>
176 changes: 176 additions & 0 deletions ControlModelWriteWindow.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using ArIED61850Tester.Models;
using ArIED61850Tester.Services;
using AR.Iec61850.Control;

namespace ArIED61850Tester;

/// <summary>
/// One CF value editor. Read → select → Write → exact readback.
/// No typed phrases, extra checkboxes, command-service Operate, or retries.
/// </summary>
public partial class ControlModelWriteWindow : Window
{
public sealed record ModelChoice(Iec61850ControlModelKind Model, string Label);
private readonly Iec61850MonitorRuntime _runtime;
private readonly Iec61850MonitorDevice _device;
private readonly SignalDefinition _signal;
private readonly CancellationTokenSource _cancellation = new();
private bool _busy;
private bool _liveModelReady;
private Iec61850ControlModelKind _liveModel = Iec61850ControlModelKind.Unknown;

public ObservableCollection<ModelChoice> Choices { get; } = new()
{
new(Iec61850ControlModelKind.StatusOnly, "0 [status-only]"),
new(Iec61850ControlModelKind.DirectNormal, "1 [direct-with-normal-security]"),
new(Iec61850ControlModelKind.SboNormal, "2 [sbo-with-normal-security]"),
new(Iec61850ControlModelKind.DirectEnhanced, "3 [direct-with-enhanced-security]"),
new(Iec61850ControlModelKind.SboEnhanced, "4 [sbo-with-enhanced-security]")
};

public ControlModelWriteWindow(
Iec61850MonitorRuntime runtime, Iec61850MonitorDevice device, SignalDefinition signal)
{
_runtime = runtime ?? throw new ArgumentNullException(nameof(runtime));
_device = device ?? throw new ArgumentNullException(nameof(device));
_signal = signal ?? throw new ArgumentNullException(nameof(signal));
InitializeComponent();
DataContext = this;
ReferenceLabel.Text = _signal.ObjectReference + ".ctlModel";
}

private async void Window_Loaded(object sender, RoutedEventArgs e)
=> await ReadModelAsync();

private void ModelPicker_SelectionChanged(object sender, SelectionChangedEventArgs e)
=> UpdateWriteButton();

private void UpdateWriteButton()
{
ReadButton.IsEnabled = !_busy;
WriteButton.IsEnabled = !_busy && _device.IsConnected && _liveModelReady &&
ModelPicker.SelectedItem is ModelChoice choice && choice.Model != _liveModel;
}

private async Task ReadModelAsync()
{
_busy = true;
_liveModelReady = false;
ModelPicker.IsEnabled = false;
UpdateWriteButton();
StatusLabel.Text = "Reading current ctlModel…";
FailureDetails.Visibility = Visibility.Collapsed;
FailureDetails.Text = string.Empty;
try
{
var capabilities = await _runtime.InspectControlAsync(
_device.DeviceId, _signal, _cancellation.Token);
_liveModel = capabilities.ControlModel;
_liveModelReady = _liveModel != Iec61850ControlModelKind.Unknown &&
capabilities.EngineControlServiceAvailable;
if (_liveModelReady)
{
ModelPicker.SelectedItem = Choices.FirstOrDefault(x => x.Model == _liveModel);
ModelPicker.IsEnabled = true;
StatusLabel.Text = $"Current: {(int)_liveModel} · {capabilities.ControlModelText}";
}
else
{
StatusLabel.Text = "ctlModel could not be read. Write disabled; check Diagnostics.";
}
}
catch (OperationCanceledException)
{
StatusLabel.Text = "Read cancelled.";
}
catch (Exception ex)
{
StatusLabel.Text = $"Read failed: {ex.Message}";
}
finally
{
_busy = false;
UpdateWriteButton();
}
}

private async void Read_Click(object sender, RoutedEventArgs e)
{
if (!_busy) await ReadModelAsync();
}

private async void Write_Click(object sender, RoutedEventArgs e)
{
if (_busy || !_liveModelReady || !_device.IsConnected ||
ModelPicker.SelectedItem is not ModelChoice selected || selected.Model == _liveModel)
return;
// Exact expected value is confirmed again by ARIEC immediately before
// the one permitted CF Write. No automatic mode trials.
var expected = _liveModel;
_busy = true;
_liveModelReady = false;
ModelPicker.IsEnabled = false;
UpdateWriteButton();
StatusLabel.Text = "Writing ctlModel and checking readback…";
try
{
var result = await ConfigureExactModelAsync(expected, selected.Model, _cancellation.Token);
if (!result.WriteAccepted || !result.ReadbackVerified ||
(int)result.AfterModel != (int)selected.Model)
{
ShowWriteFailure(result.Detail, (int)selected.Model, (int)result.AfterModel, result.ReadbackVerified);
return;
}

// The engine has completed the exact CF write and authoritative MMS readback.
// Do not issue a second inspection or keep the commissioning dialog open.
if (!_cancellation.IsCancellationRequested && IsLoaded)
DialogResult = true;
}
catch (OperationCanceledException)
{
ShowWriteFailure("Connection interrupted or operation cancelled. Read actual ctlModel before retrying.");
}
catch (Exception ex)
{
ShowWriteFailure($"Write not verified: {ex.Message}. Read actual ctlModel.");
}
finally
{
_busy = false;
UpdateWriteButton();
}
}

private void ShowWriteFailure(string? detail,
int? expected = null,
int? actual = null, bool readbackVerified = false)
{
if (_cancellation.IsCancellationRequested || !IsLoaded)
return;

var reason = string.IsNullOrWhiteSpace(detail)
? "IED did not provide a verified write result."
: detail.Trim();
if (readbackVerified && expected.HasValue && actual.HasValue && expected != actual)
reason = $"Readback mismatch • Expected {expected.Value}, actual {actual.Value}. {reason}";

FailureDetails.Text = reason;
FailureDetails.Visibility = Visibility.Visible;
StatusLabel.Text = "Write not verified • no further write will be sent automatically.";
}

// Compile-time transport bridge; no reflection or ad-hoc MMS Write.
private Task<Iec61850ControlModelConfigurationResult> ConfigureExactModelAsync(
Iec61850ControlModelKind expected, Iec61850ControlModelKind selected,
CancellationToken cancellationToken)
=> Iec61850ControlModelWriteDispatcher.ConfigureAsync(
_runtime, _device.DeviceId, _signal, expected, selected, cancellationToken);

private void Close_Click(object sender, RoutedEventArgs e) => Close();
private void Window_Closing(object? sender, CancelEventArgs e) => _cancellation.Cancel();
}
8 changes: 4 additions & 4 deletions Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<Project>
<PropertyGroup>
<Version>1.6.40</Version>
<AssemblyVersion>1.6.40.0</AssemblyVersion>
<FileVersion>1.6.40.0</FileVersion>
<InformationalVersion>1.6.40</InformationalVersion>
<Version>1.6.41</Version>
<AssemblyVersion>1.6.41.0</AssemblyVersion>
<FileVersion>1.6.41.0</FileVersion>
<InformationalVersion>1.6.41</InformationalVersion>
</PropertyGroup>
</Project>
Loading
Loading