diff --git a/SSPCTester.Logic/Models/Results.cs b/SSPCTester.Logic/Models/Results.cs
index fdddf24..46d4443 100644
--- a/SSPCTester.Logic/Models/Results.cs
+++ b/SSPCTester.Logic/Models/Results.cs
@@ -97,6 +97,12 @@ public partial class ProtectRow : ObservableObject
[ObservableProperty] private double _tripTimeMs;
[ObservableProperty] private TestStatus _status = TestStatus.Untested;
+ public bool CanMeasureFromPower =>
+ ProtectType is "跳闸" or "过压" or "欠压" or "限流";
+
+ partial void OnProtectTypeChanged(string value) =>
+ OnPropertyChanged(nameof(CanMeasureFromPower));
+
public static string BuildMeasuredValue(string protectType, double? inputVoltage, double? inputCurrent) => protectType switch
{
"跳闸" when inputCurrent.HasValue => $"{inputCurrent.Value:F3} A",
@@ -105,3 +111,63 @@ public partial class ProtectRow : ObservableObject
_ => "----"
};
}
+
+/// 保护功能固定测试项与结果初始化逻辑。
+public static class ProtectTestPlan
+{
+ public const string ManualJudgeMode = "人工判别";
+
+ public static readonly IReadOnlyList<(string ProtectType, string Trigger)> Items = new[]
+ {
+ ("跳闸", "1.5×额定电流"),
+ ("短路", "输出短路"),
+ ("过温", "≥85℃"),
+ ("过压", "≥32V"),
+ ("欠压", "≤22V"),
+ ("限流", "≥1.2A"),
+ };
+
+ public static void EnsureRows(Project project)
+ {
+ var existing = project.ProtectResults
+ .GroupBy(x => x.ProtectType)
+ .ToDictionary(x => x.Key, x => x.First());
+
+ project.ProtectResults.Clear();
+ for (int i = 0; i < Items.Count; i++)
+ {
+ var item = Items[i];
+ if (!existing.TryGetValue(item.ProtectType, out var row))
+ {
+ row = new ProtectRow
+ {
+ ProtectType = item.ProtectType,
+ Status = TestStatus.Untested
+ };
+ }
+
+ row.Index = i + 1;
+ row.ProtectType = item.ProtectType;
+ if (string.IsNullOrWhiteSpace(row.Trigger))
+ row.Trigger = item.Trigger;
+ if (string.IsNullOrWhiteSpace(row.JudgeMode))
+ row.JudgeMode = ManualJudgeMode;
+
+ project.ProtectResults.Add(row);
+ }
+
+ RefreshMeasuredValues(project, overwriteExisting: false);
+ }
+
+ public static void RefreshMeasuredValues(Project project, bool overwriteExisting = true)
+ {
+ var latestPower = project.PowerResults.LastOrDefault();
+ foreach (var row in project.ProtectResults)
+ {
+ if (!overwriteExisting && !string.IsNullOrWhiteSpace(row.MeasuredValue) && row.MeasuredValue != "----")
+ continue;
+
+ row.MeasuredValue = ProtectRow.BuildMeasuredValue(row.ProtectType, latestPower?.Vin, latestPower?.Iin);
+ }
+ }
+}
diff --git a/SSPCTester.Logic/Report/ReportGenerator.cs b/SSPCTester.Logic/Report/ReportGenerator.cs
index 8817d41..c7ecba3 100644
--- a/SSPCTester.Logic/Report/ReportGenerator.cs
+++ b/SSPCTester.Logic/Report/ReportGenerator.cs
@@ -98,7 +98,7 @@ internal sealed class LegacyReportGenerator
if (sel.Protect && (p.ProtectResults.Count > 0 || HasManualNote(p.TestFunction?.Protect)))
{
- Section(col, "7. 保护功能(框架占位)");
+ Section(col, "7. 保护功能");
if (p.ProtectResults.Count > 0) col.Item().Element(e => ProtectTable(e, p));
ManualNote(col, p.TestFunction?.Protect);
}
diff --git a/SSPCTester.Logic/Testing/ProtectTest.cs b/SSPCTester.Logic/Testing/ProtectTest.cs
index 624c67a..e93b037 100644
--- a/SSPCTester.Logic/Testing/ProtectTest.cs
+++ b/SSPCTester.Logic/Testing/ProtectTest.cs
@@ -2,47 +2,30 @@ using SSPCTester.Logic.Models;
namespace SSPCTester.Logic.Testing;
-///
-/// 保护功能测试:当前阶段仅生成框架占位结果,不真正触发故障。
-///
+/// 保护功能测试:刷新固定保护测试项的实测值与测量状态。
public sealed class ProtectTest : ITestModule
{
- public string Name => "保护功能";
-
- private static readonly (string Type, string Trigger)[] _items = new[]
- {
- ("跳闸", "1.5×额定电流"),
- ("短路", "输出短路"),
- ("过温", "≥85℃"),
- ("过压", "≥32V"),
- ("欠压", "≤22V"),
- ("限流", "≥1.2A"),
- };
+ public const string ModuleName = "保护功能";
+ public string Name => ModuleName;
public async Task RunAsync(TestContext ctx, IProgress progress, CancellationToken ct)
{
- ctx.Project.ProtectResults.Clear();
- var latestPower = ctx.Project.PowerResults.LastOrDefault();
- for (int i = 0; i < _items.Length; i++)
+ ProtectTestPlan.EnsureRows(ctx.Project);
+
+ for (int i = 0; i < ctx.Project.ProtectResults.Count; i++)
{
ct.ThrowIfCancellationRequested();
- var row = new ProtectRow
- {
- Index = i + 1,
- ProtectType = _items[i].Type,
- Trigger = _items[i].Trigger,
- MeasuredValue = ProtectRow.BuildMeasuredValue(_items[i].Type, latestPower?.Vin, latestPower?.Iin),
- JudgeMode = "人工判别",
- Status = TestStatus.Running
- };
- ctx.Project.ProtectResults.Add(row);
- progress.Report(new TestProgress { ModuleName = Name, Step = i, TotalSteps = _items.Length, Message = $"框架占位:{row.ProtectType}" });
+ var row = ctx.Project.ProtectResults[i];
+ bool preserveManualJudgement = row.Status is TestStatus.Pass or TestStatus.Fail;
+ if (!preserveManualJudgement)
+ row.Status = TestStatus.Running;
+ ProtectTestPlan.RefreshMeasuredValues(ctx.Project, overwriteExisting: false);
+ progress.Report(new TestProgress { ModuleName = Name, Step = i, TotalSteps = ctx.Project.ProtectResults.Count, Message = $"保护项测量:{row.ProtectType}" });
await Task.Delay(150, ct).ConfigureAwait(false);
- // 框架占位:固定演示数据
- row.TripTimeMs = 2.0 + i * 0.3;
- row.Status = TestStatus.Pass;
- progress.Report(new TestProgress { ModuleName = Name, Step = i + 1, TotalSteps = _items.Length, StepStatus = row.Status });
+ if (!preserveManualJudgement && row.Status == TestStatus.Running)
+ row.Status = TestStatus.Measured;
+ progress.Report(new TestProgress { ModuleName = Name, Step = i + 1, TotalSteps = ctx.Project.ProtectResults.Count, StepStatus = row.Status });
}
}
}
diff --git a/SSPCTester.Tests/ProtectTestTests.cs b/SSPCTester.Tests/ProtectTestTests.cs
new file mode 100644
index 0000000..3bf2810
--- /dev/null
+++ b/SSPCTester.Tests/ProtectTestTests.cs
@@ -0,0 +1,72 @@
+using SSPCTester.Logic.Models;
+using SSPCTester.Logic.Testing;
+
+namespace SSPCTester.Tests;
+
+public sealed class ProtectTestTests
+{
+ [Fact]
+ public async Task ProtectTest_EnsuresSixProtectionRowsAndRefreshesMeasuredValues()
+ {
+ var project = new Project();
+ project.PowerResults.Add(new PowerLossRow
+ {
+ Vin = 28.193,
+ Iin = 1.049,
+ Status = TestStatus.Pass
+ });
+
+ var progress = new List();
+ var context = new TestContext(project, null!, null!, null!, null!);
+
+ await new ProtectTest().RunAsync(context, new InlineProgress(progress.Add), CancellationToken.None);
+
+ Assert.Equal(6, project.ProtectResults.Count);
+ Assert.Collection(project.ProtectResults,
+ row => AssertProtection(row, 1, "跳闸", "1.5×额定电流", "1.049 A"),
+ row => AssertProtection(row, 2, "短路", "输出短路", "----"),
+ row => AssertProtection(row, 3, "过温", "≥85℃", "----"),
+ row => AssertProtection(row, 4, "过压", "≥32V", "28.193 V"),
+ row => AssertProtection(row, 5, "欠压", "≤22V", "28.193 V"),
+ row => AssertProtection(row, 6, "限流", "≥1.2A", "1.049 A"));
+ Assert.All(project.ProtectResults, row => Assert.Equal(TestStatus.Measured, row.Status));
+ Assert.Contains(progress, x => x.ModuleName == ProtectTest.ModuleName);
+ }
+
+ [Fact]
+ public async Task ProtectTest_PreservesEditedTriggerAndMeasuredValue()
+ {
+ var project = new Project();
+ project.ProtectResults.Add(new ProtectRow
+ {
+ ProtectType = "跳闸",
+ Trigger = "用户触发条件",
+ MeasuredValue = "1.234 A",
+ JudgeMode = ProtectTestPlan.ManualJudgeMode,
+ Status = TestStatus.Pass
+ });
+
+ var context = new TestContext(project, null!, null!, null!, null!);
+
+ await new ProtectTest().RunAsync(context, new InlineProgress(_ => { }), CancellationToken.None);
+
+ var trip = project.ProtectResults.Single(x => x.ProtectType == "跳闸");
+ Assert.Equal("用户触发条件", trip.Trigger);
+ Assert.Equal("1.234 A", trip.MeasuredValue);
+ Assert.Equal(TestStatus.Pass, trip.Status);
+ }
+
+ private static void AssertProtection(ProtectRow row, int index, string type, string trigger, string measured)
+ {
+ Assert.Equal(index, row.Index);
+ Assert.Equal(type, row.ProtectType);
+ Assert.Equal(trigger, row.Trigger);
+ Assert.Equal(measured, row.MeasuredValue);
+ Assert.Equal(ProtectTestPlan.ManualJudgeMode, row.JudgeMode);
+ }
+
+ private sealed class InlineProgress(Action handler) : IProgress
+ {
+ public void Report(TestProgress value) => handler(value);
+ }
+}
diff --git a/SSPCTester.Tests/WordReportGeneratorTests.cs b/SSPCTester.Tests/WordReportGeneratorTests.cs
index 49ed0c4..db005b0 100644
--- a/SSPCTester.Tests/WordReportGeneratorTests.cs
+++ b/SSPCTester.Tests/WordReportGeneratorTests.cs
@@ -30,6 +30,7 @@ public sealed class WordReportGeneratorTests
Assert.Contains("ProjectA", text);
Assert.Contains("基础测试", text);
Assert.Contains("功率损耗", text);
+ Assert.Contains("PROTECTION FUNCTION", text);
var fontNames = document.MainDocumentPart.Document.Descendants()
.SelectMany(x => new[] { x.Ascii?.Value, x.HighAnsi?.Value, x.EastAsia?.Value })
diff --git a/SSPCTester.UI/ViewModels/ProtectViewModel.cs b/SSPCTester.UI/ViewModels/ProtectViewModel.cs
index 1a308f1..5992192 100644
--- a/SSPCTester.UI/ViewModels/ProtectViewModel.cs
+++ b/SSPCTester.UI/ViewModels/ProtectViewModel.cs
@@ -1,87 +1,73 @@
using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
+using SSPCTester.Devices.Interfaces;
using SSPCTester.Logic.Models;
using SSPCTester.UI.Services;
namespace SSPCTester.UI.ViewModels;
-/// 保护功能页 VM(框架占位)。
+/// 保护功能页 VM。
public partial class ProtectViewModel : ObservableObject
{
+ private readonly IPowerSupply _power;
private readonly UiLogSink _log;
+ private TestSessionViewModel? _session;
public ObservableCollection Rows { get; } = new();
[ObservableProperty] private Project? _project;
- [ObservableProperty] private bool _enableTrip = true;
- [ObservableProperty] private bool _enableShort = true;
- [ObservableProperty] private bool _enableOverTemp = true;
- [ObservableProperty] private bool _enableOverVolt = true;
- [ObservableProperty] private bool _enableUnderVolt = true;
- [ObservableProperty] private bool _enableLimit = true;
- [ObservableProperty] private double _overVoltThreshold = 32;
- [ObservableProperty] private double _underVoltThreshold = 22;
- [ObservableProperty] private double _overCurrentThreshold = 1.2;
- [ObservableProperty] private double _overTempThreshold = 85;
-
- public ProtectViewModel(UiLogSink log) { _log = log; }
+ public ProtectViewModel(IPowerSupply power, UiLogSink log)
+ {
+ _power = power;
+ _log = log;
+ }
public void SetProject(Project p)
{
Project = p;
+ ProtectTestPlan.EnsureRows(p);
SyncRowsFromProject();
}
+ public void AttachSession(TestSessionViewModel session)
+ {
+ if (_session != null)
+ _session.ProtectResultsChanged -= SyncRowsFromProject;
+
+ _session = session;
+ _session.ProtectResultsChanged += SyncRowsFromProject;
+ }
+
private void SyncRowsFromProject()
{
if (Project == null) return;
- var latestPower = Project.PowerResults.LastOrDefault();
- for (int i = 0; i < Project.ProtectResults.Count; i++)
- {
- var row = Project.ProtectResults[i];
- row.Index = i + 1;
- row.MeasuredValue = ProtectRow.BuildMeasuredValue(row.ProtectType, latestPower?.Vin, latestPower?.Iin);
- if (string.IsNullOrWhiteSpace(row.JudgeMode))
- row.JudgeMode = "人工判别";
- }
-
+ ProtectTestPlan.RefreshMeasuredValues(Project, overwriteExisting: false);
Rows.Clear();
foreach (var row in Project.ProtectResults) Rows.Add(row);
}
[RelayCommand]
- private void RunDemo()
+ private async Task MeasurePowerAsync(ProtectRow? row)
{
- if (Project == null) return;
- Project.ProtectResults.Clear();
- Rows.Clear();
- var latestPower = Project.PowerResults.LastOrDefault();
- var items = new (string, string)[]
+ if (row == null || !row.CanMeasureFromPower) return;
+
+ try
{
- ("跳闸", "1.5x额定电流"),
- ("短路", "输出短路"),
- ("过温", $">={OverTempThreshold} ℃"),
- ("过压", $">={OverVoltThreshold} V"),
- ("欠压", $"<={UnderVoltThreshold} V"),
- ("限流", $">={OverCurrentThreshold} A"),
- };
- for (int i = 0; i < items.Length; i++)
- {
- var r = new ProtectRow
+ var (volts, amps) = await _power.ReadAsync();
+ row.MeasuredValue = row.ProtectType switch
{
- Index = i + 1,
- ProtectType = items[i].Item1,
- Trigger = items[i].Item2,
- MeasuredValue = ProtectRow.BuildMeasuredValue(items[i].Item1, latestPower?.Vin, latestPower?.Iin),
- JudgeMode = "人工判别",
- TripTimeMs = 2 + i * 0.3,
- Status = TestStatus.Pass
+ "过压" or "欠压" => $"{volts:F3} V",
+ _ => $"{amps:F3} A"
};
- Project.ProtectResults.Add(r);
- Rows.Add(r);
+ if (row.Status is TestStatus.Untested or TestStatus.Running)
+ row.Status = TestStatus.Measured;
+ _log.Add(UiLogLevel.Success, $"保护功能 {row.ProtectType} 实测值:{row.MeasuredValue}");
+ }
+ catch (Exception ex)
+ {
+ _log.Add(UiLogLevel.Error, $"读取电源失败:{ex.Message}");
}
- _log.Add(UiLogLevel.Warning, "保护功能模块仅生成框架演示数据。");
}
}
diff --git a/SSPCTester.UI/ViewModels/TestHostViewModel.cs b/SSPCTester.UI/ViewModels/TestHostViewModel.cs
index d63c424..bdfad59 100644
--- a/SSPCTester.UI/ViewModels/TestHostViewModel.cs
+++ b/SSPCTester.UI/ViewModels/TestHostViewModel.cs
@@ -32,5 +32,6 @@ public partial class TestHostViewModel : ObservableObject
{
Session = s;
Curve.AttachSession(s);
+ Protect.AttachSession(s);
}
}
diff --git a/SSPCTester.UI/ViewModels/TestSessionViewModel.cs b/SSPCTester.UI/ViewModels/TestSessionViewModel.cs
index 777235b..7af757b 100644
--- a/SSPCTester.UI/ViewModels/TestSessionViewModel.cs
+++ b/SSPCTester.UI/ViewModels/TestSessionViewModel.cs
@@ -24,6 +24,7 @@ public sealed class TestSessionViewModel
private readonly CancellationTokenSource _cts = new();
public event Action? CurveReady;
+ public event Action? ProtectResultsChanged;
public TestSessionViewModel(IServiceProvider sp, Project project,
ISspc sspc, IPowerSupply power, ILoad load, IDaq daq, UiLogSink logSink)
@@ -50,6 +51,9 @@ public sealed class TestSessionViewModel
IProgress progress = new Progress(p =>
{
+ if (p.ModuleName == ProtectTest.ModuleName)
+ ProtectResultsChanged?.Invoke();
+
if (!string.IsNullOrEmpty(p.Message))
{
var lvl = p.StepStatus switch
diff --git a/SSPCTester.UI/Views/ProtectView.xaml b/SSPCTester.UI/Views/ProtectView.xaml
index 067a31f..4307b16 100644
--- a/SSPCTester.UI/Views/ProtectView.xaml
+++ b/SSPCTester.UI/Views/ProtectView.xaml
@@ -27,15 +27,49 @@
-
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -56,6 +90,8 @@
MinWidth="90">
+
+
@@ -72,7 +108,7 @@
-
+