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
17 changes: 14 additions & 3 deletions MainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -335,14 +335,15 @@
<Grid x:Name="CardContent">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="5"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="56"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- Static protection-relay fascia with a dedicated status label below it. -->
<StackPanel Grid.Row="0" Grid.RowSpan="2" Grid.Column="0" Width="60"
<StackPanel Grid.Row="0" Grid.RowSpan="3" Grid.Column="0" Width="60"
Margin="-2,0,-2,0" VerticalAlignment="Center"
HorizontalAlignment="Center" Panel.ZIndex="2">
<Viewbox Width="50" Height="50" Stretch="Uniform"
Expand Down Expand Up @@ -378,8 +379,18 @@
</TextBlock.ContextMenu>
</TextBlock>
</StackPanel>
<!-- Permanently reserved slot: Browser Read never inserts a row or masks the IEDCard.
This indicator tracks the existing association-bound progress, without
changing MMS work or reporting. Zero opacity preserves geometry. -->
<ProgressBar x:Name="BrowserReadCardLine" Grid.Row="1" Grid.Column="1"
Height="2.5" Margin="4,1,12,1"
HorizontalAlignment="Stretch" VerticalAlignment="Center"
Minimum="0" Maximum="100"
Value="{Binding DiscoveryProgressPercent, Mode=OneWay}"
Style="{StaticResource DiscoveryProgressBar}" Opacity="0"
IsHitTestVisible="False"/>
<!-- Adaptive five-slot action bar. It spans the card and reserves the relay-icon area so Play/Stop never clip. -->
<Grid Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" Margin="62,4,1,0" Panel.ZIndex="10">
<Grid Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" Margin="62,4,1,0" Panel.ZIndex="10">
<UniformGrid Rows="1" Columns="5" HorizontalAlignment="Stretch">
<Button Style="{StaticResource IedIconButton}" Width="27" Height="27" Margin="0" Tag="{Binding}" Click="IedPlayAction_Click"
IsEnabled="{Binding CanPlayAction}" ToolTip="{Binding PlayToolTip}">
Expand Down Expand Up @@ -481,7 +492,7 @@
<Setter TargetName="BusyOverlay" Property="Visibility" Value="Visible"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsBrowserReading}" Value="True">
<Setter TargetName="BusyOverlay" Property="Visibility" Value="Visible"/>
<Setter TargetName="BrowserReadCardLine" Property="Opacity" Value="1"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
Expand Down
2 changes: 1 addition & 1 deletion Models/IedBrowserReadPlan.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public static IedBrowserReadSelectionKind SelectionKind(IedBrowserTreeRow? page)
};

private static readonly HashSet<string> ReadableFc = new(
["ST", "MX", "CF", "DC", "SP", "SG", "SE", "EX"], StringComparer.OrdinalIgnoreCase);
["ST", "MX", "CF", "DC", "SP", "SG", "SE", "EX", "SV", "OR", "BL"], StringComparer.OrdinalIgnoreCase);

/// <summary>
/// FC CO denotes command parameters, not scalar MMS Read targets.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
using AR.Iec61850.Discovery;
using ArIED61850Tester.Models;

namespace ARSAS.Tests;

public sealed class P0FSubstitutionOperateBlockingReadPlanTests
{
private static LiveIedModelDiscoveryDocument BuildModel()
{
var root = "SIEBCUCTRL/XCBR1.Pos";
var attributes = new[]
{
("subEna", "SV"), ("subVal", "SV"), ("subQ", "SV"), ("subID", "SV"),
("opRcvd", "OR"), ("opOk", "OR"), ("tOpOk", "OR"),
("blkEna", "BL"), ("stVal", "ST"), ("ctlVal", "CO")
}.Select(x => new LiveIedDataAttributeModel
{
ObjectReference = root,
AttributePath = "Pos." + x.Item1,
MmsReference = "SIEBCUCTRL/XCBR1$" + x.Item2 + "$Pos$" + x.Item1,
FunctionalConstraint = x.Item2,
MmsType = "Boolean",
TypeSource = "LiveTypeSpecification"
}).ToArray();
return new LiveIedModelDiscoveryDocument
{
LogicalDevices = [new LiveIedLogicalDeviceModel
{
LogicalNodes = [new LiveIedLogicalNodeModel
{
DataObjects = [new LiveIedDataObjectModel
{
Name = "Pos", Reference = root, InferredCdc = "DPC",
Attributes = attributes
}]
}]
}]
};
}

[Fact]
public void ExplicitReadAllIncludesVerifiedSvOrBlButNeverControl()
{
var targets = IedBrowserReadPlan.Build(BuildModel(), null, readAll: true);
Assert.Equal(9, targets.Count);
foreach (var fc in new[] { "SV", "OR", "BL", "ST" })
Assert.Contains(targets, t => t.FunctionalConstraint == fc);
Assert.DoesNotContain(targets, t => t.FunctionalConstraint == "CO");
Assert.Contains(targets, t => t.Reference.EndsWith("$SV$Pos$subEna", StringComparison.Ordinal));
Assert.Contains(targets, t => t.Reference.EndsWith("$SV$Pos$subVal", StringComparison.Ordinal));
Assert.Contains(targets, t => t.Reference.EndsWith("$OR$Pos$opRcvd", StringComparison.Ordinal));
Assert.Contains(targets, t => t.Reference.EndsWith("$BL$Pos$blkEna", StringComparison.Ordinal));
}

[Fact]
public void NoLiveModelMeansNoReadTargets()
{
Assert.Empty(IedBrowserReadPlan.Build(null, null, readAll: true));
Assert.Equal("Control only", IedBrowserReadPlan.UnobservedValue("CO"));
Assert.Equal("Not read", IedBrowserReadPlan.UnobservedValue("SV"));
}

[Fact]
public void InlineReadProgressHasFixedCardSlotAndDoesNotInvokeBusyOverlay()
{
var directory = new DirectoryInfo(AppContext.BaseDirectory);
string xaml;
while (true)
{
var path = Path.Combine(directory.FullName, "MainWindow.xaml");
if (File.Exists(path)) { xaml = File.ReadAllText(path); break; }
directory = directory.Parent ?? throw new FileNotFoundException("MainWindow.xaml");
}
Assert.Contains("<RowDefinition Height=\"5\"/>", xaml);
Assert.Contains("x:Name=\"BrowserReadCardLine\" Grid.Row=\"1\" Grid.Column=\"1\"", xaml);
Assert.Contains("<Setter TargetName=\"BrowserReadCardLine\" Property=\"Opacity\" Value=\"1\"/>", xaml);
Assert.DoesNotContain("<Setter TargetName=\"BusyOverlay\" Property=\"Visibility\" Value=\"Visible\"/>\n </DataTrigger>\n </DataTemplate.Triggers>", xaml);
}
}
Loading