安全功能各种修改、细节调整
This commit is contained in:
parent
61819a3139
commit
a5a36d466f
117
Docs/警灯代码.html
Normal file
117
Docs/警灯代码.html
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<title>警示灯图标预览 v2</title>
|
||||||
|
<style>
|
||||||
|
body{font-family:Segoe UI,Microsoft YaHei,sans-serif;background:#f5f5f5;margin:28px;color:#222}
|
||||||
|
h2{margin:0 0 8px;font-size:20px} p{margin:0 0 22px;color:#555;font-size:14px}
|
||||||
|
.row{display:flex;gap:24px;align-items:flex-start;flex-wrap:wrap}
|
||||||
|
.card{background:white;border:1px solid #ddd;border-radius:12px;padding:18px;width:180px;text-align:center;box-shadow:0 2px 10px #0001}
|
||||||
|
.stage{height:100px;display:flex;align-items:center;justify-content:center}
|
||||||
|
.small .stage{height:58px}
|
||||||
|
.label{margin-top:10px;font-size:14px;color:#555}
|
||||||
|
svg.big{width:82px;height:82px} svg.smallIcon{width:34px;height:34px}
|
||||||
|
|
||||||
|
.inactive .glassMain{fill:url(#grayGlass)}
|
||||||
|
.inactive .glassCap{fill:url(#grayCap)}
|
||||||
|
.inactive .rib{stroke:#555}
|
||||||
|
.inactive .baseMain{fill:url(#grayBase)}
|
||||||
|
.inactive .redOnly{opacity:0}
|
||||||
|
|
||||||
|
.active .blinkLayer{animation:hardBlink .72s steps(2,end) infinite}
|
||||||
|
.active .sweep{animation:sweepMove .95s linear infinite}
|
||||||
|
.active .rim{animation:rimBlink .72s steps(2,end) infinite}
|
||||||
|
.active .baseFlash{animation:baseFlash .72s steps(2,end) infinite}
|
||||||
|
|
||||||
|
@keyframes hardBlink{0%,44%{opacity:1}45%,100%{opacity:.42}}
|
||||||
|
@keyframes rimBlink{0%,44%{opacity:1;stroke-width:2.3}45%,100%{opacity:.35;stroke-width:1.2}}
|
||||||
|
@keyframes baseFlash{0%,44%{opacity:1}45%,100%{opacity:.15}}
|
||||||
|
@keyframes sweepMove{from{transform:translateX(-24px)}to{transform:translateX(30px)}}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h2>警示灯图标预览 v2</h2>
|
||||||
|
<p>去掉外部红色光晕,改为灯罩本体强闪 + 内部扫光。小尺寸下动态更明显。</p>
|
||||||
|
<div class="row">
|
||||||
|
<div class="card inactive">
|
||||||
|
<div class="stage">
|
||||||
|
<svg class="big" viewBox="0 0 64 64" role="img" aria-label="inactive warning light">
|
||||||
|
<defs>
|
||||||
|
<radialGradient id="redGlass" cx="34%" cy="16%" r="78%"><stop offset="0" stop-color="#ff8a8a"/><stop offset=".36" stop-color="#df0010"/><stop offset="1" stop-color="#720006"/></radialGradient>
|
||||||
|
<radialGradient id="redCap" cx="38%" cy="12%" r="85%"><stop offset="0" stop-color="#ffb0b0"/><stop offset=".5" stop-color="#f00012"/><stop offset="1" stop-color="#8a0008"/></radialGradient>
|
||||||
|
<radialGradient id="grayGlass" cx="34%" cy="16%" r="78%"><stop offset="0" stop-color="#dedede"/><stop offset=".36" stop-color="#969696"/><stop offset="1" stop-color="#4b4b4b"/></radialGradient>
|
||||||
|
<radialGradient id="grayCap" cx="38%" cy="12%" r="85%"><stop offset="0" stop-color="#f0f0f0"/><stop offset=".5" stop-color="#aaa"/><stop offset="1" stop-color="#5d5d5d"/></radialGradient>
|
||||||
|
<linearGradient id="base" x1="0" x2="0" y1="0" y2="1"><stop offset="0" stop-color="#3b3b3b"/><stop offset=".45" stop-color="#101010"/><stop offset="1" stop-color="#303030"/></linearGradient>
|
||||||
|
<linearGradient id="grayBase" x1="0" x2="0" y1="0" y2="1"><stop offset="0" stop-color="#666"/><stop offset=".45" stop-color="#303030"/><stop offset="1" stop-color="#565656"/></linearGradient>
|
||||||
|
<clipPath id="clipGlass"><path d="M14 44V23C14 10.5 22.1 4 32 4s18 6.5 18 19v21z"/></clipPath>
|
||||||
|
</defs>
|
||||||
|
<path class="glassMain" d="M14 44V23C14 10.5 22.1 4 32 4s18 6.5 18 19v21z" fill="url(#redGlass)"/>
|
||||||
|
<path class="glassCap" d="M15 26C16 11.8 23.8 5 32 5s16 6.8 17 21c-8-3.1-25.9-3.1-34 0z" fill="url(#redCap)" opacity=".95"/>
|
||||||
|
<g opacity=".38" clip-path="url(#clipGlass)"><path class="rib" d="M18 22v23M22 18v27M26 15v30M30 14v31M34 14v31M38 15v30M42 18v27M46 22v23" stroke="#880007" stroke-width="1"/></g>
|
||||||
|
<path d="M25 13l7-3 3 6-8 3zM38 11c4 1 6 3 8 7l-5 2c-1-3-3-4-6-5z" fill="#fff" opacity=".72"/>
|
||||||
|
<ellipse cx="32" cy="44" rx="22" ry="5" fill="#1a0000" opacity=".48"/>
|
||||||
|
<path class="redOnly baseFlash" d="M13 42h38v2H13z" fill="#ff0014"/>
|
||||||
|
<path d="M11 43h42v6H11z" fill="#240004" opacity=".88"/>
|
||||||
|
<path class="baseMain" d="M9 48h46v8c0 3-3 5-6 5H15c-3 0-6-2-6-5z" fill="url(#base)"/>
|
||||||
|
<path d="M13 50h38" stroke="#777" stroke-width="1" opacity=".45"/>
|
||||||
|
<path d="M42 56v4M47 55v4M52 54v3" stroke="#050505" stroke-width="2" stroke-linecap="round"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="label">未激活:灰色静态</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card active">
|
||||||
|
<div class="stage">
|
||||||
|
<svg class="big" viewBox="0 0 64 64" role="img" aria-label="active warning light">
|
||||||
|
<defs>
|
||||||
|
<radialGradient id="redGlass2" cx="34%" cy="16%" r="78%"><stop offset="0" stop-color="#ff8a8a"/><stop offset=".36" stop-color="#df0010"/><stop offset="1" stop-color="#720006"/></radialGradient>
|
||||||
|
<radialGradient id="redCap2" cx="38%" cy="12%" r="85%"><stop offset="0" stop-color="#ffb0b0"/><stop offset=".5" stop-color="#f00012"/><stop offset="1" stop-color="#8a0008"/></radialGradient>
|
||||||
|
<linearGradient id="base2" x1="0" x2="0" y1="0" y2="1"><stop offset="0" stop-color="#3b3b3b"/><stop offset=".45" stop-color="#101010"/><stop offset="1" stop-color="#303030"/></linearGradient>
|
||||||
|
<clipPath id="clipGlass2"><path d="M14 44V23C14 10.5 22.1 4 32 4s18 6.5 18 19v21z"/></clipPath>
|
||||||
|
</defs>
|
||||||
|
<g class="blinkLayer">
|
||||||
|
<path d="M14 44V23C14 10.5 22.1 4 32 4s18 6.5 18 19v21z" fill="url(#redGlass2)"/>
|
||||||
|
<path d="M15 26C16 11.8 23.8 5 32 5s16 6.8 17 21c-8-3.1-25.9-3.1-34 0z" fill="url(#redCap2)" opacity=".95"/>
|
||||||
|
</g>
|
||||||
|
<path class="rim" d="M14 44V23C14 10.5 22.1 4 32 4s18 6.5 18 19v21z" fill="none" stroke="#ff2635" stroke-width="2"/>
|
||||||
|
<g opacity=".38" clip-path="url(#clipGlass2)"><path d="M18 22v23M22 18v27M26 15v30M30 14v31M34 14v31M38 15v30M42 18v27M46 22v23" stroke="#650005" stroke-width="1"/></g>
|
||||||
|
<g clip-path="url(#clipGlass2)">
|
||||||
|
<path class="sweep" d="M17 6h7l-11 42h-7z" fill="#fff" opacity=".62"/>
|
||||||
|
</g>
|
||||||
|
<path d="M25 13l7-3 3 6-8 3zM38 11c4 1 6 3 8 7l-5 2c-1-3-3-4-6-5z" fill="#fff" opacity=".78"/>
|
||||||
|
<ellipse cx="32" cy="44" rx="22" ry="5" fill="#1a0000" opacity=".48"/>
|
||||||
|
<path class="baseFlash" d="M13 42h38v2H13z" fill="#ff0014"/>
|
||||||
|
<path d="M11 43h42v6H11z" fill="#240004" opacity=".88"/>
|
||||||
|
<path d="M9 48h46v8c0 3-3 5-6 5H15c-3 0-6-2-6-5z" fill="url(#base2)"/>
|
||||||
|
<path d="M13 50h38" stroke="#777" stroke-width="1" opacity=".45"/>
|
||||||
|
<path d="M42 56v4M47 55v4M52 54v3" stroke="#050505" stroke-width="2" stroke-linecap="round"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="label">激活:本体强闪 + 扫光</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card active small">
|
||||||
|
<div class="stage">
|
||||||
|
<svg class="smallIcon" viewBox="0 0 64 64" role="img" aria-label="small active warning light">
|
||||||
|
<defs>
|
||||||
|
<radialGradient id="redGlass3" cx="34%" cy="16%" r="78%"><stop offset="0" stop-color="#ff8a8a"/><stop offset=".36" stop-color="#df0010"/><stop offset="1" stop-color="#720006"/></radialGradient>
|
||||||
|
<linearGradient id="base3" x1="0" x2="0" y1="0" y2="1"><stop offset="0" stop-color="#3b3b3b"/><stop offset=".45" stop-color="#101010"/><stop offset="1" stop-color="#303030"/></linearGradient>
|
||||||
|
<clipPath id="clipGlass3"><path d="M14 44V23C14 10.5 22.1 4 32 4s18 6.5 18 19v21z"/></clipPath>
|
||||||
|
</defs>
|
||||||
|
<path class="blinkLayer" d="M14 44V23C14 10.5 22.1 4 32 4s18 6.5 18 19v21z" fill="url(#redGlass3)"/>
|
||||||
|
<path class="rim" d="M14 44V23C14 10.5 22.1 4 32 4s18 6.5 18 19v21z" fill="none" stroke="#ff2635" stroke-width="2"/>
|
||||||
|
<g clip-path="url(#clipGlass3)"><path class="sweep" d="M17 6h8l-11 42h-8z" fill="#fff" opacity=".7"/></g>
|
||||||
|
<path d="M25 13l7-3 3 6-8 3zM38 11c4 1 6 3 8 7l-5 2c-1-3-3-4-6-5z" fill="#fff" opacity=".78"/>
|
||||||
|
<ellipse cx="32" cy="44" rx="22" ry="5" fill="#1a0000" opacity=".48"/>
|
||||||
|
<path class="baseFlash" d="M13 42h38v2H13z" fill="#ff0014"/>
|
||||||
|
<path d="M11 43h42v6H11z" fill="#240004" opacity=".88"/>
|
||||||
|
<path d="M9 48h46v8c0 3-3 5-6 5H15c-3 0-6-2-6-5z" fill="url(#base3)"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="label">34px 小图标预览</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -93,9 +93,9 @@ public sealed class MockSspc : MockDeviceBase, ISspc
|
|||||||
return Task.FromResult(new ChannelMeasurement
|
return Task.FromResult(new ChannelMeasurement
|
||||||
{
|
{
|
||||||
PhysicalSlot = physicalSlot,
|
PhysicalSlot = physicalSlot,
|
||||||
Voltage = on ? 28.0 : 0.0,
|
Voltage = 28.0,
|
||||||
Current = on ? 1.0 : 0.0,
|
Current = on ? 1.0 : 0.0,
|
||||||
VoltageRaw = on ? (ushort)2800 : (ushort)0,
|
VoltageRaw = 2800,
|
||||||
CurrentRaw = on ? (short)167 : (short)0,
|
CurrentRaw = on ? (short)167 : (short)0,
|
||||||
Timestamp = DateTimeOffset.Now
|
Timestamp = DateTimeOffset.Now
|
||||||
});
|
});
|
||||||
|
|||||||
@ -9,6 +9,9 @@ public partial class ChannelResult : ObservableObject
|
|||||||
[ObservableProperty] private double _voltageOn;
|
[ObservableProperty] private double _voltageOn;
|
||||||
[ObservableProperty] private double _currentOn;
|
[ObservableProperty] private double _currentOn;
|
||||||
[ObservableProperty] private double _voltageOff;
|
[ObservableProperty] private double _voltageOff;
|
||||||
|
[ObservableProperty] private double _currentOff;
|
||||||
|
[ObservableProperty] private double _displayVoltage;
|
||||||
|
[ObservableProperty] private double _displayCurrent;
|
||||||
[ObservableProperty] private TestStatus _status = TestStatus.Untested;
|
[ObservableProperty] private TestStatus _status = TestStatus.Untested;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,6 +155,8 @@ public static class ProtectTestPlan
|
|||||||
row.Trigger = item.Trigger;
|
row.Trigger = item.Trigger;
|
||||||
if (string.IsNullOrWhiteSpace(row.JudgeMode))
|
if (string.IsNullOrWhiteSpace(row.JudgeMode))
|
||||||
row.JudgeMode = ManualJudgeMode;
|
row.JudgeMode = ManualJudgeMode;
|
||||||
|
if (row.Status == TestStatus.Measured)
|
||||||
|
row.Status = TestStatus.Untested;
|
||||||
|
|
||||||
project.ProtectResults.Add(row);
|
project.ProtectResults.Add(row);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,7 +25,7 @@ public sealed class ReportSelection
|
|||||||
public bool Curve { get; set; } = true;
|
public bool Curve { get; set; } = true;
|
||||||
public bool Power { get; set; } = true;
|
public bool Power { get; set; } = true;
|
||||||
public bool Protect { get; set; } = true;
|
public bool Protect { get; set; } = true;
|
||||||
public string FileName { get; set; } = "report_{yyyyMMdd_HHmmss}.pdf";
|
public string FileName { get; set; } = "{ProjectName}_{yyyyMMdd_HHmmss}.pdf";
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>四个测试页“测试说明”手动记录内容。</summary>
|
/// <summary>四个测试页“测试说明”手动记录内容。</summary>
|
||||||
|
|||||||
@ -349,12 +349,12 @@ public sealed class ReportGenerator
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void BasicTable(IContainer container, Project p) =>
|
private static void BasicTable(IContainer container, Project p) =>
|
||||||
DataTable(container, new[] { 0.65f, 1f, 1f, 1.15f, 0.8f },
|
DataTable(container, new[] { 0.65f, 1f, 1f, 1.15f, 1.15f, 0.8f },
|
||||||
new[] { "通道", "导通电压\nV", "导通电流\nA", "关后电压\nV", "判定" },
|
new[] { "通道", "导通电压\nV", "导通电流\nA", "关后电压\nV", "关后电流\nA", "判定" },
|
||||||
p.BasicResults.Select(r => new ReportRow(r.Status, new[]
|
p.BasicResults.Select(r => new ReportRow(r.Status, new[]
|
||||||
{
|
{
|
||||||
$"CH{r.Channel:00}", Format(r.VoltageOn, "F2"), Format(r.CurrentOn, "F3"),
|
$"CH{r.Channel:00}", Format(r.VoltageOn, "F2"), Format(r.CurrentOn, "F3"),
|
||||||
Format(r.VoltageOff, "F2"), StatusText(r.Status)
|
Format(r.VoltageOff, "F2"), Format(r.CurrentOff, "F3"), StatusText(r.Status)
|
||||||
})));
|
})));
|
||||||
|
|
||||||
private static void CurveTable(IContainer container, Project p) =>
|
private static void CurveTable(IContainer container, Project p) =>
|
||||||
@ -386,7 +386,7 @@ public sealed class ReportGenerator
|
|||||||
ValueOrDash(r.Trigger),
|
ValueOrDash(r.Trigger),
|
||||||
ValueOrDash(r.MeasuredValue),
|
ValueOrDash(r.MeasuredValue),
|
||||||
ValueOrDash(r.JudgeMode),
|
ValueOrDash(r.JudgeMode),
|
||||||
StatusText(r.Status)
|
ProtectStatusText(r.Status)
|
||||||
})));
|
})));
|
||||||
|
|
||||||
private static void DataTable(IContainer container, IReadOnlyList<float> widths,
|
private static void DataTable(IContainer container, IReadOnlyList<float> widths,
|
||||||
@ -501,6 +501,13 @@ public sealed class ReportGenerator
|
|||||||
TestStatus.Running => "测试中", TestStatus.Measured => "已测",
|
TestStatus.Running => "测试中", TestStatus.Measured => "已测",
|
||||||
_ => "未测试"
|
_ => "未测试"
|
||||||
};
|
};
|
||||||
|
private static string ProtectStatusText(TestStatus status) => status switch
|
||||||
|
{
|
||||||
|
TestStatus.Pass => "合格",
|
||||||
|
TestStatus.Fail => "不合格",
|
||||||
|
TestStatus.Running => "测试中",
|
||||||
|
_ => "未判定"
|
||||||
|
};
|
||||||
private static string StatusColor(TestStatus status) => status switch
|
private static string StatusColor(TestStatus status) => status switch
|
||||||
{
|
{
|
||||||
TestStatus.Pass => Success, TestStatus.Fail => Danger,
|
TestStatus.Pass => Success, TestStatus.Fail => Danger,
|
||||||
|
|||||||
@ -13,7 +13,7 @@ public enum ReportOutputFormat
|
|||||||
|
|
||||||
public static class ReportFileHelper
|
public static class ReportFileHelper
|
||||||
{
|
{
|
||||||
public const string DefaultTemplate = "report_{yyyyMMdd_HHmmss}.pdf";
|
public const string DefaultTemplate = "{ProjectName}_{yyyyMMdd_HHmmss}.pdf";
|
||||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||||
{
|
{
|
||||||
WriteIndented = true,
|
WriteIndented = true,
|
||||||
|
|||||||
@ -189,15 +189,16 @@ internal sealed class LegacyReportGenerator
|
|||||||
{
|
{
|
||||||
t.ColumnsDefinition(cd =>
|
t.ColumnsDefinition(cd =>
|
||||||
{
|
{
|
||||||
cd.ConstantColumn(60); cd.RelativeColumn(); cd.RelativeColumn(); cd.RelativeColumn(); cd.ConstantColumn(70);
|
cd.ConstantColumn(60); cd.RelativeColumn(); cd.RelativeColumn(); cd.RelativeColumn(); cd.RelativeColumn(); cd.ConstantColumn(70);
|
||||||
});
|
});
|
||||||
HeaderRow(t, "通道", "电压(V)", "电流(A)", "关后电压(V)", "结果");
|
HeaderRow(t, "通道", "开电压(V)", "开电流(A)", "关电压(V)", "关电流(A)", "结果");
|
||||||
foreach (var r in p.BasicResults)
|
foreach (var r in p.BasicResults)
|
||||||
{
|
{
|
||||||
t.Cell().Padding(3).Text($"CH{r.Channel}");
|
t.Cell().Padding(3).Text($"CH{r.Channel}");
|
||||||
t.Cell().Padding(3).Text(r.VoltageOn.ToString("F2"));
|
t.Cell().Padding(3).Text(r.VoltageOn.ToString("F2"));
|
||||||
t.Cell().Padding(3).Text(r.CurrentOn.ToString("F3"));
|
t.Cell().Padding(3).Text(r.CurrentOn.ToString("F3"));
|
||||||
t.Cell().Padding(3).Text(r.VoltageOff.ToString("F2"));
|
t.Cell().Padding(3).Text(r.VoltageOff.ToString("F2"));
|
||||||
|
t.Cell().Padding(3).Text(r.CurrentOff.ToString("F3"));
|
||||||
t.Cell().Padding(3).Text(StatusText(r.Status)).FontColor(StatusColor(r.Status));
|
t.Cell().Padding(3).Text(StatusText(r.Status)).FontColor(StatusColor(r.Status));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -271,7 +272,7 @@ internal sealed class LegacyReportGenerator
|
|||||||
t.Cell().Padding(3).Text(r.Trigger);
|
t.Cell().Padding(3).Text(r.Trigger);
|
||||||
t.Cell().Padding(3).Text(r.MeasuredValue);
|
t.Cell().Padding(3).Text(r.MeasuredValue);
|
||||||
t.Cell().Padding(3).Text(r.JudgeMode);
|
t.Cell().Padding(3).Text(r.JudgeMode);
|
||||||
t.Cell().Padding(3).Text(StatusText(r.Status)).FontColor(StatusColor(r.Status));
|
t.Cell().Padding(3).Text(ProtectStatusText(r.Status)).FontColor(StatusColor(r.Status));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -292,6 +293,13 @@ internal sealed class LegacyReportGenerator
|
|||||||
TestStatus.Measured => "已测",
|
TestStatus.Measured => "已测",
|
||||||
_ => "未测试"
|
_ => "未测试"
|
||||||
};
|
};
|
||||||
|
private static string ProtectStatusText(TestStatus s) => s switch
|
||||||
|
{
|
||||||
|
TestStatus.Pass => "合格",
|
||||||
|
TestStatus.Fail => "不合格",
|
||||||
|
TestStatus.Running => "测试中",
|
||||||
|
_ => "未判定"
|
||||||
|
};
|
||||||
|
|
||||||
private static string StatusColor(TestStatus s) => s switch
|
private static string StatusColor(TestStatus s) => s switch
|
||||||
{
|
{
|
||||||
|
|||||||
@ -118,11 +118,11 @@ public sealed class WordReportGenerator
|
|||||||
Section(body, section++, "基础测试", "BASIC FUNCTION TEST");
|
Section(body, section++, "基础测试", "BASIC FUNCTION TEST");
|
||||||
if (p.BasicResults.Count > 0)
|
if (p.BasicResults.Count > 0)
|
||||||
body.Append(ResultTable(
|
body.Append(ResultTable(
|
||||||
new[] { "通道", "导通电压 V", "导通电流 A", "关后电压 V", "判定" },
|
new[] { "通道", "导通电压 V", "导通电流 A", "关后电压 V", "关后电流 A", "判定" },
|
||||||
p.BasicResults.Select(r => new[]
|
p.BasicResults.Select(r => new[]
|
||||||
{
|
{
|
||||||
$"CH{r.Channel:00}", Format(r.VoltageOn, "F2"), Format(r.CurrentOn, "F3"),
|
$"CH{r.Channel:00}", Format(r.VoltageOn, "F2"), Format(r.CurrentOn, "F3"),
|
||||||
Format(r.VoltageOff, "F2"), StatusText(r.Status)
|
Format(r.VoltageOff, "F2"), Format(r.CurrentOff, "F3"), StatusText(r.Status)
|
||||||
})));
|
})));
|
||||||
ManualNote(body, p.TestFunction?.Basic);
|
ManualNote(body, p.TestFunction?.Basic);
|
||||||
}
|
}
|
||||||
@ -174,7 +174,7 @@ public sealed class WordReportGenerator
|
|||||||
ValueOrDash(r.Trigger),
|
ValueOrDash(r.Trigger),
|
||||||
ValueOrDash(r.MeasuredValue),
|
ValueOrDash(r.MeasuredValue),
|
||||||
ValueOrDash(r.JudgeMode),
|
ValueOrDash(r.JudgeMode),
|
||||||
StatusText(r.Status)
|
ProtectStatusText(r.Status)
|
||||||
})));
|
})));
|
||||||
ManualNote(body, p.TestFunction?.Protect);
|
ManualNote(body, p.TestFunction?.Protect);
|
||||||
}
|
}
|
||||||
@ -417,6 +417,13 @@ public sealed class WordReportGenerator
|
|||||||
TestStatus.Measured => "已测",
|
TestStatus.Measured => "已测",
|
||||||
_ => "未测试"
|
_ => "未测试"
|
||||||
};
|
};
|
||||||
|
private static string ProtectStatusText(TestStatus status) => status switch
|
||||||
|
{
|
||||||
|
TestStatus.Pass => "合格",
|
||||||
|
TestStatus.Fail => "不合格",
|
||||||
|
TestStatus.Running => "测试中",
|
||||||
|
_ => "未判定"
|
||||||
|
};
|
||||||
|
|
||||||
private static string StatusColor(TestStatus status) => status switch
|
private static string StatusColor(TestStatus status) => status switch
|
||||||
{
|
{
|
||||||
|
|||||||
@ -26,6 +26,9 @@ public sealed class BasicTest : ITestModule
|
|||||||
var on = await ctx.Sspc.ReadMeasurementAsync(ch, ct).ConfigureAwait(false);
|
var on = await ctx.Sspc.ReadMeasurementAsync(ch, ct).ConfigureAwait(false);
|
||||||
row.VoltageOn = on.Voltage;
|
row.VoltageOn = on.Voltage;
|
||||||
row.CurrentOn = Math.Abs(on.Current);
|
row.CurrentOn = Math.Abs(on.Current);
|
||||||
|
row.DisplayVoltage = row.VoltageOn;
|
||||||
|
row.DisplayCurrent = row.CurrentOn;
|
||||||
|
bool onOk = BasicTestCriteria.IsOnState(row.VoltageOn, row.CurrentOn);
|
||||||
|
|
||||||
await ctx.Sspc.TurnOffAsync(ch, ct).ConfigureAwait(false);
|
await ctx.Sspc.TurnOffAsync(ch, ct).ConfigureAwait(false);
|
||||||
await Task.Delay(BasicTestCriteria.SwitchSettleDelayMs, ct).ConfigureAwait(false);
|
await Task.Delay(BasicTestCriteria.SwitchSettleDelayMs, ct).ConfigureAwait(false);
|
||||||
@ -33,7 +36,10 @@ public sealed class BasicTest : ITestModule
|
|||||||
.WaitForStableOffAsync(ctx.Sspc, ch, ct)
|
.WaitForStableOffAsync(ctx.Sspc, ch, ct)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
row.VoltageOff = offResult.Measurement.Voltage;
|
row.VoltageOff = offResult.Measurement.Voltage;
|
||||||
row.Status = BasicTestCriteria.IsOn(row.VoltageOn) && offResult.IsStableOff
|
row.CurrentOff = Math.Abs(offResult.Measurement.Current);
|
||||||
|
row.DisplayVoltage = row.VoltageOff;
|
||||||
|
row.DisplayCurrent = row.CurrentOff;
|
||||||
|
row.Status = onOk && offResult.IsStableOff
|
||||||
? TestStatus.Pass : TestStatus.Fail;
|
? TestStatus.Pass : TestStatus.Fail;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
@ -8,6 +8,7 @@ public static class BasicTestCriteria
|
|||||||
{
|
{
|
||||||
public const double OnVoltageThreshold = 1.0;
|
public const double OnVoltageThreshold = 1.0;
|
||||||
public const double OffVoltageThreshold = 0.5;
|
public const double OffVoltageThreshold = 0.5;
|
||||||
|
public const double CurrentPresentThreshold = 0.001;
|
||||||
public const int SwitchSettleDelayMs = 1_000;
|
public const int SwitchSettleDelayMs = 1_000;
|
||||||
public const int OffSettleTimeoutMs = 2_000;
|
public const int OffSettleTimeoutMs = 2_000;
|
||||||
public const int OffInitialDelayMs = 100;
|
public const int OffInitialDelayMs = 100;
|
||||||
@ -18,8 +19,18 @@ public static class BasicTestCriteria
|
|||||||
|
|
||||||
public static bool IsOff(double voltage) => Math.Abs(voltage) < OffVoltageThreshold;
|
public static bool IsOff(double voltage) => Math.Abs(voltage) < OffVoltageThreshold;
|
||||||
|
|
||||||
|
public static bool HasVoltageData(double voltage) => voltage > OnVoltageThreshold;
|
||||||
|
|
||||||
|
public static bool HasCurrentData(double current) => Math.Abs(current) > CurrentPresentThreshold;
|
||||||
|
|
||||||
|
public static bool IsOnState(double voltage, double current) =>
|
||||||
|
HasVoltageData(voltage) && HasCurrentData(current);
|
||||||
|
|
||||||
|
public static bool IsOffState(double voltage, double current) =>
|
||||||
|
HasVoltageData(voltage) && !HasCurrentData(current);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 等待关断电压稳定。连续多次低于阈值才确认关断,避免旧帧和瞬时噪声造成误判。
|
/// 等待关状态稳定。连续多次满足“电压有数据、电流无数据”才确认关断,避免旧帧和瞬时噪声造成误判。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static async Task<OffStateResult> WaitForStableOffAsync(
|
public static async Task<OffStateResult> WaitForStableOffAsync(
|
||||||
ISspc sspc,
|
ISspc sspc,
|
||||||
@ -35,7 +46,7 @@ public static class BasicTestCriteria
|
|||||||
while (stopwatch.ElapsedMilliseconds < OffSettleTimeoutMs)
|
while (stopwatch.ElapsedMilliseconds < OffSettleTimeoutMs)
|
||||||
{
|
{
|
||||||
latest = await sspc.ReadMeasurementAsync(channel, ct).ConfigureAwait(false);
|
latest = await sspc.ReadMeasurementAsync(channel, ct).ConfigureAwait(false);
|
||||||
stableSamples = IsOff(latest.Voltage) ? stableSamples + 1 : 0;
|
stableSamples = IsOffState(latest.Voltage, latest.Current) ? stableSamples + 1 : 0;
|
||||||
|
|
||||||
if (stableSamples >= RequiredStableOffSamples)
|
if (stableSamples >= RequiredStableOffSamples)
|
||||||
return new OffStateResult(true, latest, stopwatch.Elapsed);
|
return new OffStateResult(true, latest, stopwatch.Elapsed);
|
||||||
|
|||||||
@ -22,6 +22,9 @@ public sealed class PowerTest : ITestModule
|
|||||||
{
|
{
|
||||||
if (!_power.IsConnected) await _power.ConnectAsync(ct).ConfigureAwait(false);
|
if (!_power.IsConnected) await _power.ConnectAsync(ct).ConfigureAwait(false);
|
||||||
if (!ctx.Load.IsConnected) await ctx.Load.ConnectAsync(ct).ConfigureAwait(false);
|
if (!ctx.Load.IsConnected) await ctx.Load.ConnectAsync(ct).ConfigureAwait(false);
|
||||||
|
await _power.OutputAsync(true, ct).ConfigureAwait(false);
|
||||||
|
await ctx.Load.InputAsync(true, ct).ConfigureAwait(false);
|
||||||
|
|
||||||
int n = ctx.Sspc.ChannelCount;
|
int n = ctx.Sspc.ChannelCount;
|
||||||
ctx.Project.PowerResults.Clear();
|
ctx.Project.PowerResults.Clear();
|
||||||
|
|
||||||
|
|||||||
@ -24,7 +24,7 @@ public sealed class ProtectTest : ITestModule
|
|||||||
|
|
||||||
await Task.Delay(150, ct).ConfigureAwait(false);
|
await Task.Delay(150, ct).ConfigureAwait(false);
|
||||||
if (!preserveManualJudgement && row.Status == TestStatus.Running)
|
if (!preserveManualJudgement && row.Status == TestStatus.Running)
|
||||||
row.Status = TestStatus.Measured;
|
row.Status = TestStatus.Untested;
|
||||||
progress.Report(new TestProgress { ModuleName = Name, Step = i + 1, TotalSteps = ctx.Project.ProtectResults.Count, StepStatus = row.Status });
|
progress.Report(new TestProgress { ModuleName = Name, Step = i + 1, TotalSteps = ctx.Project.ProtectResults.Count, StepStatus = row.Status });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,4 +22,23 @@ public sealed class BasicTestCriteriaTests
|
|||||||
{
|
{
|
||||||
Assert.Equal(expected, BasicTestCriteria.IsOff(voltage));
|
Assert.Equal(expected, BasicTestCriteria.IsOff(voltage));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(28.0, 1.0, true)]
|
||||||
|
[InlineData(28.0, 0.0, false)]
|
||||||
|
[InlineData(0.0, 1.0, false)]
|
||||||
|
public void IsOnState_RequiresVoltageAndCurrentData(double voltage, double current, bool expected)
|
||||||
|
{
|
||||||
|
Assert.Equal(expected, BasicTestCriteria.IsOnState(voltage, current));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(28.0, 0.0, true)]
|
||||||
|
[InlineData(28.0, 0.0005, true)]
|
||||||
|
[InlineData(28.0, 0.01, false)]
|
||||||
|
[InlineData(0.0, 0.0, false)]
|
||||||
|
public void IsOffState_RequiresVoltageDataAndNoCurrentData(double voltage, double current, bool expected)
|
||||||
|
{
|
||||||
|
Assert.Equal(expected, BasicTestCriteria.IsOffState(voltage, current));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,7 +29,8 @@ public sealed class InterfacePowerTests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
var test = new PowerTest(new FixedPowerSupply()) { SamplesPerChannel = 1, IntervalMs = 0 };
|
var power = new FixedPowerSupply();
|
||||||
|
var test = new PowerTest(power) { SamplesPerChannel = 1, IntervalMs = 0 };
|
||||||
var context = new TestContext(project, sspc, forbiddenPower, load, daq);
|
var context = new TestContext(project, sspc, forbiddenPower, load, daq);
|
||||||
|
|
||||||
await test.RunAsync(context, new Progress<TestProgress>(), CancellationToken.None);
|
await test.RunAsync(context, new Progress<TestProgress>(), CancellationToken.None);
|
||||||
@ -48,6 +49,8 @@ public sealed class InterfacePowerTests
|
|||||||
});
|
});
|
||||||
Assert.Equal(0, sspc.TurnOffCount);
|
Assert.Equal(0, sspc.TurnOffCount);
|
||||||
Assert.Equal(0, sspc.ReadMeasurementCount);
|
Assert.Equal(0, sspc.ReadMeasurementCount);
|
||||||
|
Assert.Equal(1, power.OutputOnCount);
|
||||||
|
Assert.Equal(1, load.InputOnCount);
|
||||||
Assert.Equal(24, load.ReadPowerCount);
|
Assert.Equal(24, load.ReadPowerCount);
|
||||||
Assert.Equal(0, daq.ReadInstantCount);
|
Assert.Equal(0, daq.ReadInstantCount);
|
||||||
}
|
}
|
||||||
@ -127,6 +130,7 @@ public sealed class InterfacePowerTests
|
|||||||
private sealed class MeasuringLoad : ILoad
|
private sealed class MeasuringLoad : ILoad
|
||||||
{
|
{
|
||||||
public int ReadPowerCount { get; private set; }
|
public int ReadPowerCount { get; private set; }
|
||||||
|
public int InputOnCount { get; private set; }
|
||||||
public bool IsConnected => true;
|
public bool IsConnected => true;
|
||||||
public string DisplayName => "it8702p";
|
public string DisplayName => "it8702p";
|
||||||
public event EventHandler<bool>? ConnectionChanged;
|
public event EventHandler<bool>? ConnectionChanged;
|
||||||
@ -134,7 +138,11 @@ public sealed class InterfacePowerTests
|
|||||||
public Task DisconnectAsync() => Task.CompletedTask;
|
public Task DisconnectAsync() => Task.CompletedTask;
|
||||||
public Task SetModeAsync(LoadMode mode, CancellationToken ct = default) => throw new InvalidOperationException();
|
public Task SetModeAsync(LoadMode mode, CancellationToken ct = default) => throw new InvalidOperationException();
|
||||||
public Task SetValueAsync(double value, CancellationToken ct = default) => throw new InvalidOperationException();
|
public Task SetValueAsync(double value, CancellationToken ct = default) => throw new InvalidOperationException();
|
||||||
public Task InputAsync(bool on, CancellationToken ct = default) => throw new InvalidOperationException();
|
public Task InputAsync(bool on, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
if (on) InputOnCount++;
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
public Task<(double volts, double amps)> ReadAsync(CancellationToken ct = default) => Task.FromResult((28.0, 1.0));
|
public Task<(double volts, double amps)> ReadAsync(CancellationToken ct = default) => Task.FromResult((28.0, 1.0));
|
||||||
public Task<PowerReading> ReadPowerAsync(CancellationToken ct = default)
|
public Task<PowerReading> ReadPowerAsync(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
@ -146,6 +154,7 @@ public sealed class InterfacePowerTests
|
|||||||
|
|
||||||
private sealed class FixedPowerSupply : IPowerSupply
|
private sealed class FixedPowerSupply : IPowerSupply
|
||||||
{
|
{
|
||||||
|
public int OutputOnCount { get; private set; }
|
||||||
public bool IsConnected => true;
|
public bool IsConnected => true;
|
||||||
public string DisplayName => "fixed power";
|
public string DisplayName => "fixed power";
|
||||||
public event EventHandler<bool>? ConnectionChanged;
|
public event EventHandler<bool>? ConnectionChanged;
|
||||||
@ -153,7 +162,11 @@ public sealed class InterfacePowerTests
|
|||||||
public Task DisconnectAsync() => Task.CompletedTask;
|
public Task DisconnectAsync() => Task.CompletedTask;
|
||||||
public Task SetVoltageAsync(double volts, CancellationToken ct = default) => throw new InvalidOperationException();
|
public Task SetVoltageAsync(double volts, CancellationToken ct = default) => throw new InvalidOperationException();
|
||||||
public Task SetCurrentLimitAsync(double amps, CancellationToken ct = default) => throw new InvalidOperationException();
|
public Task SetCurrentLimitAsync(double amps, CancellationToken ct = default) => throw new InvalidOperationException();
|
||||||
public Task OutputAsync(bool on, CancellationToken ct = default) => throw new InvalidOperationException();
|
public Task OutputAsync(bool on, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
if (on) OutputOnCount++;
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
public Task<(double volts, double amps)> ReadAsync(CancellationToken ct = default) => Task.FromResult((30.0, 1.0));
|
public Task<(double volts, double amps)> ReadAsync(CancellationToken ct = default) => Task.FromResult((30.0, 1.0));
|
||||||
public Task<string> QueryIdentityAsync(CancellationToken ct = default) => Task.FromResult("fixed");
|
public Task<string> QueryIdentityAsync(CancellationToken ct = default) => Task.FromResult("fixed");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,7 +29,7 @@ public sealed class ProtectTestTests
|
|||||||
row => AssertProtection(row, 4, "过压", "≥32V", "28.193 V"),
|
row => AssertProtection(row, 4, "过压", "≥32V", "28.193 V"),
|
||||||
row => AssertProtection(row, 5, "欠压", "≤22V", "28.193 V"),
|
row => AssertProtection(row, 5, "欠压", "≤22V", "28.193 V"),
|
||||||
row => AssertProtection(row, 6, "限流", "≥1.2A", "1.049 A"));
|
row => AssertProtection(row, 6, "限流", "≥1.2A", "1.049 A"));
|
||||||
Assert.All(project.ProtectResults, row => Assert.Equal(TestStatus.Measured, row.Status));
|
Assert.All(project.ProtectResults, row => Assert.Equal(TestStatus.Untested, row.Status));
|
||||||
Assert.Contains(progress, x => x.ModuleName == ProtectTest.ModuleName);
|
Assert.Contains(progress, x => x.ModuleName == ProtectTest.ModuleName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -57,11 +57,11 @@ public sealed class ReportFileHelperTests
|
|||||||
[Fact]
|
[Fact]
|
||||||
public void ResolveFileName_UsesFallbackForBlankTemplate()
|
public void ResolveFileName_UsesFallbackForBlankTemplate()
|
||||||
{
|
{
|
||||||
var project = new Project { Report = { FileName = " " } };
|
var project = new Project { Name = "ProjectA", Report = { FileName = " " } };
|
||||||
|
|
||||||
string fileName = ReportFileHelper.ResolveFileName(project, new DateTime(2026, 6, 28, 1, 2, 3));
|
string fileName = ReportFileHelper.ResolveFileName(project, new DateTime(2026, 6, 28, 1, 2, 3));
|
||||||
|
|
||||||
Assert.Equal("report_20260628_010203.pdf", fileName);
|
Assert.Equal("ProjectA_20260628_010203.pdf", fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
|
|||||||
@ -59,7 +59,8 @@ public sealed class ReportLayoutTests
|
|||||||
Channel = channel,
|
Channel = channel,
|
||||||
VoltageOn = failed ? 0.12 : 28.01,
|
VoltageOn = failed ? 0.12 : 28.01,
|
||||||
CurrentOn = failed ? 0 : 1.002,
|
CurrentOn = failed ? 0 : 1.002,
|
||||||
VoltageOff = 0.01,
|
VoltageOff = 28.01,
|
||||||
|
CurrentOff = failed ? 0.05 : 0,
|
||||||
Status = failed ? TestStatus.Fail : TestStatus.Pass
|
Status = failed ? TestStatus.Fail : TestStatus.Pass
|
||||||
});
|
});
|
||||||
project.CurveResults.Add(new CurveResultRow
|
project.CurveResults.Add(new CurveResultRow
|
||||||
|
|||||||
@ -31,6 +31,7 @@ public sealed class WordReportGeneratorTests
|
|||||||
Assert.Contains("基础测试", text);
|
Assert.Contains("基础测试", text);
|
||||||
Assert.Contains("功率损耗", text);
|
Assert.Contains("功率损耗", text);
|
||||||
Assert.Contains("PROTECTION FUNCTION", text);
|
Assert.Contains("PROTECTION FUNCTION", text);
|
||||||
|
Assert.Contains("未判定", text);
|
||||||
|
|
||||||
var fontNames = document.MainDocumentPart.Document.Descendants<RunFonts>()
|
var fontNames = document.MainDocumentPart.Document.Descendants<RunFonts>()
|
||||||
.SelectMany(x => new[] { x.Ascii?.Value, x.HighAnsi?.Value, x.EastAsia?.Value })
|
.SelectMany(x => new[] { x.Ascii?.Value, x.HighAnsi?.Value, x.EastAsia?.Value })
|
||||||
@ -88,7 +89,8 @@ public sealed class WordReportGeneratorTests
|
|||||||
Channel = 1,
|
Channel = 1,
|
||||||
VoltageOn = 28.0,
|
VoltageOn = 28.0,
|
||||||
CurrentOn = 1.0,
|
CurrentOn = 1.0,
|
||||||
VoltageOff = 0.01,
|
VoltageOff = 28.0,
|
||||||
|
CurrentOff = 0.0,
|
||||||
Status = TestStatus.Pass
|
Status = TestStatus.Pass
|
||||||
});
|
});
|
||||||
project.CurveResults.Add(new CurveResultRow
|
project.CurveResults.Add(new CurveResultRow
|
||||||
@ -115,7 +117,7 @@ public sealed class WordReportGeneratorTests
|
|||||||
ProtectType = "短路",
|
ProtectType = "短路",
|
||||||
Trigger = "输出短路",
|
Trigger = "输出短路",
|
||||||
TripTimeMs = 2.1,
|
TripTimeMs = 2.1,
|
||||||
Status = TestStatus.Pass
|
Status = TestStatus.Untested
|
||||||
});
|
});
|
||||||
return project;
|
return project;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,16 @@
|
|||||||
<UserControl x:Class="SSPCTester.UI.Controls.CurvePlotControl"
|
<UserControl x:Class="SSPCTester.UI.Controls.CurvePlotControl"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:sp="clr-namespace:ScottPlot.WPF;assembly=ScottPlot.WPF"
|
|
||||||
MinHeight="120"
|
MinHeight="120"
|
||||||
Background="{StaticResource WaveBgBrush}">
|
Background="{StaticResource WaveBgBrush}">
|
||||||
<Border BorderBrush="{StaticResource BorderSubtleBrush}" BorderThickness="1" CornerRadius="{StaticResource RadiusMd}">
|
<Border BorderBrush="{StaticResource BorderSubtleBrush}" BorderThickness="1" CornerRadius="{StaticResource RadiusMd}">
|
||||||
<sp:WpfPlot x:Name="Plot" />
|
<Grid x:Name="RootContainer">
|
||||||
|
<Border x:Name="DesignPlaceholder" Visibility="Collapsed" Background="{StaticResource WaveBgBrush}">
|
||||||
|
<TextBlock Text="曲线图设计时占位"
|
||||||
|
Foreground="{StaticResource TextMutedBrush}"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|||||||
@ -1,16 +1,27 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using ScottPlot;
|
using ScottPlot;
|
||||||
|
using ScottPlot.WPF;
|
||||||
using SSPCTester.Devices.Interfaces;
|
using SSPCTester.Devices.Interfaces;
|
||||||
|
|
||||||
namespace SSPCTester.UI.Controls;
|
namespace SSPCTester.UI.Controls;
|
||||||
|
|
||||||
public partial class CurvePlotControl : UserControl
|
public partial class CurvePlotControl : UserControl
|
||||||
{
|
{
|
||||||
|
private readonly bool _isDesignMode;
|
||||||
|
private WpfPlot? _plot;
|
||||||
|
|
||||||
public CurvePlotControl()
|
public CurvePlotControl()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
_isDesignMode = DesignerProperties.GetIsInDesignMode(this);
|
||||||
|
if (_isDesignMode)
|
||||||
|
{
|
||||||
|
DesignPlaceholder.Visibility = Visibility.Visible;
|
||||||
|
return;
|
||||||
|
}
|
||||||
Loaded += (_, _) => InitPlot();
|
Loaded += (_, _) => InitPlot();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,30 +53,39 @@ public partial class CurvePlotControl : UserControl
|
|||||||
|
|
||||||
private void InitPlot()
|
private void InitPlot()
|
||||||
{
|
{
|
||||||
Plot.Plot.Title("");
|
_plot ??= CreateRuntimePlot();
|
||||||
Plot.Plot.XLabel("t (ms)");
|
_plot.Plot.Title("");
|
||||||
Plot.Plot.YLabel("V");
|
_plot.Plot.XLabel("t (ms)");
|
||||||
Plot.Plot.Axes.AntiAlias(true);
|
_plot.Plot.YLabel("V");
|
||||||
Plot.Plot.FigureBackground.Color = ScottPlot.Color.FromHex("#FBFCFD");
|
_plot.Plot.Axes.AntiAlias(true);
|
||||||
Plot.Plot.DataBackground.Color = ScottPlot.Color.FromHex("#FBFCFD");
|
_plot.Plot.FigureBackground.Color = ScottPlot.Color.FromHex("#FBFCFD");
|
||||||
Plot.Plot.Grid.MajorLineColor = ScottPlot.Color.FromHex("#DFE4EA");
|
_plot.Plot.DataBackground.Color = ScottPlot.Color.FromHex("#FBFCFD");
|
||||||
|
_plot.Plot.Grid.MajorLineColor = ScottPlot.Color.FromHex("#DFE4EA");
|
||||||
Render();
|
Render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private WpfPlot CreateRuntimePlot()
|
||||||
|
{
|
||||||
|
var plot = new WpfPlot();
|
||||||
|
RootContainer.Children.Clear();
|
||||||
|
RootContainer.Children.Add(plot);
|
||||||
|
return plot;
|
||||||
|
}
|
||||||
|
|
||||||
private void Render()
|
private void Render()
|
||||||
{
|
{
|
||||||
if (!IsLoaded || Plot == null) return;
|
if (_isDesignMode || !IsLoaded || _plot == null) return;
|
||||||
Plot.Plot.Clear();
|
_plot.Plot.Clear();
|
||||||
var c = Curve;
|
var c = Curve;
|
||||||
if (c == null || c.Voltage.Length == 0)
|
if (c == null || c.Voltage.Length == 0)
|
||||||
{
|
{
|
||||||
Plot.Refresh();
|
_plot.Refresh();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var points = BuildViewPoints(c);
|
var points = BuildViewPoints(c);
|
||||||
if (points.Count == 0)
|
if (points.Count == 0)
|
||||||
{
|
{
|
||||||
Plot.Refresh();
|
_plot.Refresh();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,31 +98,31 @@ public partial class CurvePlotControl : UserControl
|
|||||||
ys[i] = points[i].Voltage;
|
ys[i] = points[i].Voltage;
|
||||||
}
|
}
|
||||||
var color = IsRising ? "#1A73C4" : "#D23B3B";
|
var color = IsRising ? "#1A73C4" : "#D23B3B";
|
||||||
var sig = Plot.Plot.Add.ScatterLine(xs, ys, ScottPlot.Color.FromHex(color));
|
var sig = _plot.Plot.Add.ScatterLine(xs, ys, ScottPlot.Color.FromHex(color));
|
||||||
sig.LineWidth = 2;
|
sig.LineWidth = 2;
|
||||||
sig.MarkerSize = 0;
|
sig.MarkerSize = 0;
|
||||||
|
|
||||||
// 阈值线
|
// 阈值线
|
||||||
double threshold = IsRising ? VMax * 0.9 : VMax * 0.1;
|
double threshold = IsRising ? VMax * 0.9 : VMax * 0.1;
|
||||||
var hLine = Plot.Plot.Add.HorizontalLine(threshold);
|
var hLine = _plot.Plot.Add.HorizontalLine(threshold);
|
||||||
hLine.Color = ScottPlot.Color.FromHex("#98A1AD");
|
hLine.Color = ScottPlot.Color.FromHex("#98A1AD");
|
||||||
hLine.LinePattern = ScottPlot.LinePattern.Dashed;
|
hLine.LinePattern = ScottPlot.LinePattern.Dashed;
|
||||||
hLine.LineWidth = 1;
|
hLine.LineWidth = 1;
|
||||||
var label = Plot.Plot.Add.Text($"{(IsRising ? 90 : 10)}%", xs[0], threshold);
|
var label = _plot.Plot.Add.Text($"{(IsRising ? 90 : 10)}%", xs[0], threshold);
|
||||||
label.LabelFontColor = ScottPlot.Color.FromHex("#98A1AD");
|
label.LabelFontColor = ScottPlot.Color.FromHex("#98A1AD");
|
||||||
label.LabelFontSize = 10;
|
label.LabelFontSize = 10;
|
||||||
|
|
||||||
// 触发线 t=0
|
// 触发线 t=0
|
||||||
if (!HasViewWindow(out double start, out double end) || (start <= 0 && end >= 0))
|
if (!HasViewWindow(out double start, out double end) || (start <= 0 && end >= 0))
|
||||||
{
|
{
|
||||||
var vLine = Plot.Plot.Add.VerticalLine(0);
|
var vLine = _plot.Plot.Add.VerticalLine(0);
|
||||||
vLine.Color = ScottPlot.Color.FromHex("#98A1AD");
|
vLine.Color = ScottPlot.Color.FromHex("#98A1AD");
|
||||||
vLine.LinePattern = ScottPlot.LinePattern.Dotted;
|
vLine.LinePattern = ScottPlot.LinePattern.Dotted;
|
||||||
vLine.LineWidth = 1;
|
vLine.LineWidth = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
Plot.Plot.Axes.AutoScale();
|
_plot.Plot.Axes.AutoScale();
|
||||||
Plot.Refresh();
|
_plot.Refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<(double TimeMs, double Voltage)> BuildViewPoints(CurveData curve)
|
private List<(double TimeMs, double Voltage)> BuildViewPoints(CurveData curve)
|
||||||
@ -131,6 +151,9 @@ public partial class CurvePlotControl : UserControl
|
|||||||
/// <summary>导出为 PNG 字节。</summary>
|
/// <summary>导出为 PNG 字节。</summary>
|
||||||
public byte[] ExportPng(int width = 800, int height = 300)
|
public byte[] ExportPng(int width = 800, int height = 300)
|
||||||
{
|
{
|
||||||
return Plot.Plot.GetImageBytes(width, height, ImageFormat.Png);
|
if (_plot == null)
|
||||||
|
return Array.Empty<byte>();
|
||||||
|
|
||||||
|
return _plot.Plot.GetImageBytes(width, height, ImageFormat.Png);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -57,7 +57,7 @@ public sealed class StatusToTextConverter : IValueConverter
|
|||||||
TestStatus.Fail => "不合格",
|
TestStatus.Fail => "不合格",
|
||||||
TestStatus.Running => "测试中",
|
TestStatus.Running => "测试中",
|
||||||
TestStatus.Measured => "已测",
|
TestStatus.Measured => "已测",
|
||||||
_ => "未测试"
|
_ => "未判定"
|
||||||
} : "—";
|
} : "—";
|
||||||
public object ConvertBack(object value, Type targetType, object? parameter, CultureInfo culture) => Binding.DoNothing;
|
public object ConvertBack(object value, Type targetType, object? parameter, CultureInfo culture) => Binding.DoNothing;
|
||||||
}
|
}
|
||||||
@ -75,6 +75,22 @@ public sealed class BoolToVisConverter : IValueConverter
|
|||||||
=> value is Visibility v && v == Visibility.Visible;
|
=> value is Visibility v && v == Visibility.Visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>bool 取反;用于 IsEnabled 这类布尔属性。</summary>
|
||||||
|
public sealed class InverseBoolConverter : IValueConverter
|
||||||
|
{
|
||||||
|
public object Convert(object value, Type targetType, object? parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
bool b = value is bool x && x;
|
||||||
|
return !b;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object ConvertBack(object value, Type targetType, object? parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
bool b = value is bool x && x;
|
||||||
|
return !b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>null → Collapsed,非 null → Visible(参数 "Invert" 取反)。</summary>
|
/// <summary>null → Collapsed,非 null → Visible(参数 "Invert" 取反)。</summary>
|
||||||
public sealed class NullToVisConverter : IValueConverter
|
public sealed class NullToVisConverter : IValueConverter
|
||||||
{
|
{
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
<cnv:StatusToSoftBrushConverter x:Key="StatusToSoftBrush" />
|
<cnv:StatusToSoftBrushConverter x:Key="StatusToSoftBrush" />
|
||||||
<cnv:StatusToTextConverter x:Key="StatusToText" />
|
<cnv:StatusToTextConverter x:Key="StatusToText" />
|
||||||
<cnv:BoolToVisConverter x:Key="BoolToVis" />
|
<cnv:BoolToVisConverter x:Key="BoolToVis" />
|
||||||
|
<cnv:InverseBoolConverter x:Key="InverseBoolConverter" />
|
||||||
<cnv:NullToVisConverter x:Key="NullToVis" />
|
<cnv:NullToVisConverter x:Key="NullToVis" />
|
||||||
<cnv:LogLevelToBrushConverter x:Key="LogLevelToBrush" />
|
<cnv:LogLevelToBrushConverter x:Key="LogLevelToBrush" />
|
||||||
<cnv:NumberOrDashConverter x:Key="NumberOrDash" />
|
<cnv:NumberOrDashConverter x:Key="NumberOrDash" />
|
||||||
|
|||||||
@ -16,6 +16,7 @@ public sealed class SafetyMonitorService : IDisposable
|
|||||||
private readonly SemaphoreSlim _gate = new(1, 1);
|
private readonly SemaphoreSlim _gate = new(1, 1);
|
||||||
private Timer? _timer;
|
private Timer? _timer;
|
||||||
private bool _handlingEmergency;
|
private bool _handlingEmergency;
|
||||||
|
private bool _softwareEmergencyActive;
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
|
|
||||||
public SafetyMonitorService(
|
public SafetyMonitorService(
|
||||||
@ -56,18 +57,66 @@ public sealed class SafetyMonitorService : IDisposable
|
|||||||
SetStatus(false, false, "设备正常", "正在监控 PCI2312 急停输入");
|
SetStatus(false, false, "设备正常", "正在监控 PCI2312 急停输入");
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ResetAsync(CancellationToken ct = default)
|
public async Task TriggerSoftwareEmergencyAsync(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await _gate.WaitAsync(ct).ConfigureAwait(false);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_softwareEmergencyActive = true;
|
||||||
|
_handlingEmergency = true;
|
||||||
|
SetStatus(true, IsAlarmOutputOn, "软件急停已触发", "正在执行软件急停安全关断");
|
||||||
|
_log.Add(UiLogLevel.Error, "软件触发报警急停,开始执行报警灯、电源、负载关断。");
|
||||||
|
|
||||||
|
await EnsureSafetyDioConnectedAsync(ct).ConfigureAwait(false);
|
||||||
|
await SetAlarmOutputAsync(true, ct).ConfigureAwait(false);
|
||||||
|
await TryShutdownPowerAsync().ConfigureAwait(false);
|
||||||
|
await TryShutdownLoadAsync().ConfigureAwait(false);
|
||||||
|
SetStatus(true, true, "软件急停已触发", "报警灯已接通,电源输出和负载输入已发送关闭指令");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_softwareEmergencyActive = false;
|
||||||
|
_handlingEmergency = false;
|
||||||
|
_logger.LogError(ex, "Software emergency shutdown failed.");
|
||||||
|
_log.Add(UiLogLevel.Error, $"软件急停执行异常:{ex.Message}");
|
||||||
|
SetStatus(true, IsAlarmOutputOn, "设备故障", ex.Message);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_gate.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<SafetyResetResult> ResetAsync(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await _gate.WaitAsync(ct).ConfigureAwait(false);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return await ResetCoreAsync(ct).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_gate.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<SafetyResetResult> ResetCoreAsync(CancellationToken ct)
|
||||||
{
|
{
|
||||||
var options = _options.Pci2312;
|
var options = _options.Pci2312;
|
||||||
|
await EnsureSafetyDioConnectedAsync(ct).ConfigureAwait(false);
|
||||||
if (await IsEmergencyInputActiveAsync(options, ct).ConfigureAwait(false))
|
if (await IsEmergencyInputActiveAsync(options, ct).ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
SetStatus(true, IsAlarmOutputOn, "急停已触发", "急停输入仍处于接通状态,无法复位报警灯");
|
const string message = "硬件急停按钮仍处于触发状态,无法执行状态重置。请先旋转或释放设备上的急停按钮,确认急停输入复位后再重试。";
|
||||||
return;
|
SetStatus(true, IsAlarmOutputOn, "急停已触发", message);
|
||||||
|
return new SafetyResetResult(false, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
await SetAlarmOutputAsync(false, ct).ConfigureAwait(false);
|
await SetAlarmOutputAsync(false, ct).ConfigureAwait(false);
|
||||||
SetStatus(false, false, "设备正常", "急停已复位,正在监控 PCI2312 输入");
|
_softwareEmergencyActive = false;
|
||||||
_handlingEmergency = false;
|
_handlingEmergency = false;
|
||||||
|
SetStatus(false, false, "设备正常", "急停状态已重置,报警灯已关闭,正在监控 PCI2312 输入");
|
||||||
|
return new SafetyResetResult(true, "急停状态已重置。");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnTimer(object? state)
|
private void OnTimer(object? state)
|
||||||
@ -92,11 +141,13 @@ public sealed class SafetyMonitorService : IDisposable
|
|||||||
{
|
{
|
||||||
await HandleEmergencyAsync().ConfigureAwait(false);
|
await HandleEmergencyAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
else if (_softwareEmergencyActive)
|
||||||
|
{
|
||||||
|
SetStatus(true, IsAlarmOutputOn, "软件急停已触发", "等待状态重置,电源和负载不会自动恢复");
|
||||||
|
}
|
||||||
else if (IsEmergencyActive || IsAlarmOutputOn)
|
else if (IsEmergencyActive || IsAlarmOutputOn)
|
||||||
{
|
{
|
||||||
await SetAlarmOutputAsync(false, CancellationToken.None).ConfigureAwait(false);
|
SetStatus(true, IsAlarmOutputOn, "急停已触发", $"DI{options.EmergencyInputChannel} 已复位,请点击状态重置关闭报警灯");
|
||||||
_handlingEmergency = false;
|
|
||||||
SetStatus(false, false, "设备正常", $"DI{options.EmergencyInputChannel} 已复位,报警灯已关闭");
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -185,6 +236,16 @@ public sealed class SafetyMonitorService : IDisposable
|
|||||||
IsAlarmOutputOn = on;
|
IsAlarmOutputOn = on;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task EnsureSafetyDioConnectedAsync(CancellationToken ct)
|
||||||
|
{
|
||||||
|
var options = _options.Pci2312;
|
||||||
|
if (!options.Enabled)
|
||||||
|
throw new InvalidOperationException("PCI2312 安全输入输出未启用,无法控制报警灯。");
|
||||||
|
|
||||||
|
if (!_safetyDio.IsConnected)
|
||||||
|
await _safetyDio.ConnectAsync(ct).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
private void SetStatus(bool emergencyActive, bool alarmOutputOn, string statusText, string detailText)
|
private void SetStatus(bool emergencyActive, bool alarmOutputOn, string statusText, string detailText)
|
||||||
{
|
{
|
||||||
IsEmergencyActive = emergencyActive;
|
IsEmergencyActive = emergencyActive;
|
||||||
@ -201,3 +262,5 @@ public sealed class SafetyMonitorService : IDisposable
|
|||||||
_gate.Dispose();
|
_gate.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public sealed record SafetyResetResult(bool Success, string Message);
|
||||||
|
|||||||
@ -87,8 +87,13 @@ public partial class BasicTestViewModel : ObservableObject
|
|||||||
var measurement = await _sspc.ReadMeasurementAsync(row.Channel);
|
var measurement = await _sspc.ReadMeasurementAsync(row.Channel);
|
||||||
row.VoltageOn = measurement.Voltage;
|
row.VoltageOn = measurement.Voltage;
|
||||||
row.CurrentOn = Math.Abs(measurement.Current);
|
row.CurrentOn = Math.Abs(measurement.Current);
|
||||||
|
row.DisplayVoltage = row.VoltageOn;
|
||||||
|
row.DisplayCurrent = row.CurrentOn;
|
||||||
|
bool onOk = BasicTestCriteria.IsOnState(row.VoltageOn, row.CurrentOn);
|
||||||
|
row.Status = onOk ? TestStatus.Running : TestStatus.Fail;
|
||||||
StartPolling(row.Channel);
|
StartPolling(row.Channel);
|
||||||
_log.Add(UiLogLevel.Info, $"CH{row.Channel} 开 -> V={row.VoltageOn:F2}V I={row.CurrentOn:F3}A");
|
_log.Add(onOk ? UiLogLevel.Info : UiLogLevel.Error,
|
||||||
|
$"CH{row.Channel} 开 -> V={row.VoltageOn:F2}V I={row.CurrentOn:F3}A {(onOk ? "开状态合格" : "开状态不合格")}");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -112,12 +117,15 @@ public partial class BasicTestViewModel : ObservableObject
|
|||||||
await Task.Delay(SwitchSettleDelayMs);
|
await Task.Delay(SwitchSettleDelayMs);
|
||||||
var offResult = await BasicTestCriteria.WaitForStableOffAsync(_sspc, row.Channel);
|
var offResult = await BasicTestCriteria.WaitForStableOffAsync(_sspc, row.Channel);
|
||||||
row.VoltageOff = offResult.Measurement.Voltage;
|
row.VoltageOff = offResult.Measurement.Voltage;
|
||||||
bool onOk = BasicTestCriteria.IsOn(row.VoltageOn);
|
row.CurrentOff = Math.Abs(offResult.Measurement.Current);
|
||||||
|
row.DisplayVoltage = row.VoltageOff;
|
||||||
|
row.DisplayCurrent = row.CurrentOff;
|
||||||
|
bool onOk = BasicTestCriteria.IsOnState(row.VoltageOn, row.CurrentOn);
|
||||||
row.Status = (onOk && offResult.IsStableOff) ? TestStatus.Pass : TestStatus.Fail;
|
row.Status = (onOk && offResult.IsStableOff) ? TestStatus.Pass : TestStatus.Fail;
|
||||||
string verdict = row.Status == TestStatus.Pass ? "合格" : "不合格";
|
string verdict = row.Status == TestStatus.Pass ? "合格" : "不合格";
|
||||||
string offState = offResult.IsStableOff ? "已稳定关断" : "关断超时";
|
string offState = offResult.IsStableOff ? "已稳定关断" : "关断超时";
|
||||||
_log.Add(row.Status == TestStatus.Pass ? UiLogLevel.Success : UiLogLevel.Error,
|
_log.Add(row.Status == TestStatus.Pass ? UiLogLevel.Success : UiLogLevel.Error,
|
||||||
$"CH{row.Channel} 关 -> V={row.VoltageOff:F2}V {offState} {verdict}");
|
$"CH{row.Channel} 关 -> V={row.VoltageOff:F2}V I={row.CurrentOff:F3}A {offState} {verdict}");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -191,24 +199,37 @@ public partial class BasicTestViewModel : ObservableObject
|
|||||||
if (!_sspc.IsConnected) await _sspc.ConnectAsync(ct);
|
if (!_sspc.IsConnected) await _sspc.ConnectAsync(ct);
|
||||||
|
|
||||||
row.Status = TestStatus.Running;
|
row.Status = TestStatus.Running;
|
||||||
|
_log.Add(UiLogLevel.Info, $"CH{row.Channel} 基础测试开始:执行开状态测试。");
|
||||||
await _sspc.TurnOnAsync(row.Channel, ct);
|
await _sspc.TurnOnAsync(row.Channel, ct);
|
||||||
await Task.Delay(SwitchSettleDelayMs, ct);
|
await Task.Delay(SwitchSettleDelayMs, ct);
|
||||||
|
|
||||||
var measurement = await _sspc.ReadMeasurementAsync(row.Channel, ct);
|
var measurement = await _sspc.ReadMeasurementAsync(row.Channel, ct);
|
||||||
row.VoltageOn = measurement.Voltage;
|
row.VoltageOn = measurement.Voltage;
|
||||||
row.CurrentOn = Math.Abs(measurement.Current);
|
row.CurrentOn = Math.Abs(measurement.Current);
|
||||||
|
row.DisplayVoltage = row.VoltageOn;
|
||||||
|
row.DisplayCurrent = row.CurrentOn;
|
||||||
|
bool onOk = BasicTestCriteria.IsOnState(row.VoltageOn, row.CurrentOn);
|
||||||
|
_log.Add(onOk ? UiLogLevel.Info : UiLogLevel.Error,
|
||||||
|
$"CH{row.Channel} 开 -> V={row.VoltageOn:F2}V I={row.CurrentOn:F3}A {(onOk ? "开状态合格" : "开状态不合格")}");
|
||||||
StartPolling(row.Channel);
|
StartPolling(row.Channel);
|
||||||
|
|
||||||
ct.ThrowIfCancellationRequested();
|
ct.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
await StopPollingAsync(row.Channel);
|
await StopPollingAsync(row.Channel);
|
||||||
|
_log.Add(UiLogLevel.Info, $"CH{row.Channel} 执行关状态测试。");
|
||||||
await _sspc.TurnOffAsync(row.Channel, ct);
|
await _sspc.TurnOffAsync(row.Channel, ct);
|
||||||
await Task.Delay(SwitchSettleDelayMs, ct);
|
await Task.Delay(SwitchSettleDelayMs, ct);
|
||||||
|
|
||||||
var offResult = await BasicTestCriteria.WaitForStableOffAsync(_sspc, row.Channel, ct);
|
var offResult = await BasicTestCriteria.WaitForStableOffAsync(_sspc, row.Channel, ct);
|
||||||
row.VoltageOff = offResult.Measurement.Voltage;
|
row.VoltageOff = offResult.Measurement.Voltage;
|
||||||
bool onOk = BasicTestCriteria.IsOn(row.VoltageOn);
|
row.CurrentOff = Math.Abs(offResult.Measurement.Current);
|
||||||
|
row.DisplayVoltage = row.VoltageOff;
|
||||||
|
row.DisplayCurrent = row.CurrentOff;
|
||||||
row.Status = (onOk && offResult.IsStableOff) ? TestStatus.Pass : TestStatus.Fail;
|
row.Status = (onOk && offResult.IsStableOff) ? TestStatus.Pass : TestStatus.Fail;
|
||||||
|
string verdict = row.Status == TestStatus.Pass ? "合格" : "不合格";
|
||||||
|
string offState = offResult.IsStableOff ? "关状态合格" : "关状态不合格";
|
||||||
|
_log.Add(row.Status == TestStatus.Pass ? UiLogLevel.Success : UiLogLevel.Error,
|
||||||
|
$"CH{row.Channel} 关 -> V={row.VoltageOff:F2}V I={row.CurrentOff:F3}A {offState} {verdict}");
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
@ -216,7 +237,8 @@ public partial class BasicTestViewModel : ObservableObject
|
|||||||
{
|
{
|
||||||
foreach (var r in Rows)
|
foreach (var r in Rows)
|
||||||
{
|
{
|
||||||
r.VoltageOn = 0; r.CurrentOn = 0; r.VoltageOff = 0;
|
r.VoltageOn = 0; r.CurrentOn = 0; r.VoltageOff = 0; r.CurrentOff = 0;
|
||||||
|
r.DisplayVoltage = 0; r.DisplayCurrent = 0;
|
||||||
r.Status = TestStatus.Untested;
|
r.Status = TestStatus.Untested;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -302,6 +324,8 @@ public partial class BasicTestViewModel : ObservableObject
|
|||||||
|
|
||||||
row.VoltageOn = measurement.Voltage;
|
row.VoltageOn = measurement.Voltage;
|
||||||
row.CurrentOn = Math.Abs(measurement.Current);
|
row.CurrentOn = Math.Abs(measurement.Current);
|
||||||
|
row.DisplayVoltage = row.VoltageOn;
|
||||||
|
row.DisplayCurrent = row.CurrentOn;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -82,6 +82,13 @@ public partial class CurveTestViewModel : ObservableObject
|
|||||||
_ = LoadSelectedChannelCurvesAsync();
|
_ = LoadSelectedChannelCurvesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
partial void OnSelectedRowChanged(CurveResultRow? value)
|
||||||
|
{
|
||||||
|
if (_syncingViewWindow || value == null) return;
|
||||||
|
if (SelectedChannel != value.Channel)
|
||||||
|
SelectedChannel = value.Channel;
|
||||||
|
}
|
||||||
|
|
||||||
partial void OnRisingViewStartMsChanged(double? value)
|
partial void OnRisingViewStartMsChanged(double? value)
|
||||||
{
|
{
|
||||||
if (_syncingViewWindow || SelectedRow == null) return;
|
if (_syncingViewWindow || SelectedRow == null) return;
|
||||||
@ -117,10 +124,10 @@ public partial class CurveTestViewModel : ObservableObject
|
|||||||
{
|
{
|
||||||
SelectedRow = Rows.FirstOrDefault(x => x.Channel == SelectedChannel)
|
SelectedRow = Rows.FirstOrDefault(x => x.Channel == SelectedChannel)
|
||||||
?? Project?.CurveResults.FirstOrDefault(x => x.Channel == SelectedChannel);
|
?? Project?.CurveResults.FirstOrDefault(x => x.Channel == SelectedChannel);
|
||||||
RisingViewStartMs = SelectedRow?.RisingViewStartMs ?? SelectedRow?.ViewStartMs;
|
RisingViewStartMs = SelectedRow?.RisingViewStartMs;
|
||||||
RisingViewEndMs = SelectedRow?.RisingViewEndMs ?? SelectedRow?.ViewEndMs;
|
RisingViewEndMs = SelectedRow?.RisingViewEndMs;
|
||||||
FallingViewStartMs = SelectedRow?.FallingViewStartMs ?? SelectedRow?.ViewStartMs;
|
FallingViewStartMs = SelectedRow?.FallingViewStartMs;
|
||||||
FallingViewEndMs = SelectedRow?.FallingViewEndMs ?? SelectedRow?.ViewEndMs;
|
FallingViewEndMs = SelectedRow?.FallingViewEndMs;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@ -228,9 +235,9 @@ public partial class CurveTestViewModel : ObservableObject
|
|||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private void OpenPopup()
|
private void OpenPopup(string? curveKind)
|
||||||
{
|
{
|
||||||
var win = new Views.CurvePopupWindow(this) { Owner = System.Windows.Application.Current?.MainWindow };
|
var win = new Views.CurvePopupWindow(this, curveKind) { Owner = System.Windows.Application.Current?.MainWindow };
|
||||||
win.Show();
|
win.Show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -30,8 +30,6 @@ public partial class MainViewModel : ObservableObject
|
|||||||
private readonly UiLogSink _logSink;
|
private readonly UiLogSink _logSink;
|
||||||
private readonly DeviceOptions _deviceOptions;
|
private readonly DeviceOptions _deviceOptions;
|
||||||
private readonly DispatcherTimer _deviceMonitorTimer;
|
private readonly DispatcherTimer _deviceMonitorTimer;
|
||||||
private readonly DispatcherTimer _safetyReadingsTimer;
|
|
||||||
private bool _safetyReadingsBusy;
|
|
||||||
private object? _viewBeforeSettings;
|
private object? _viewBeforeSettings;
|
||||||
|
|
||||||
public MainViewModel(
|
public MainViewModel(
|
||||||
@ -54,8 +52,6 @@ public partial class MainViewModel : ObservableObject
|
|||||||
_deviceMonitorTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };
|
_deviceMonitorTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };
|
||||||
_deviceMonitorTimer.Tick += (_, _) => RefreshDeviceStatus();
|
_deviceMonitorTimer.Tick += (_, _) => RefreshDeviceStatus();
|
||||||
_deviceMonitorTimer.Start();
|
_deviceMonitorTimer.Start();
|
||||||
_safetyReadingsTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };
|
|
||||||
_safetyReadingsTimer.Tick += async (_, _) => await RefreshSafetyReadingsAsync().ConfigureAwait(true);
|
|
||||||
|
|
||||||
AllProjects = new ObservableCollection<Project>();
|
AllProjects = new ObservableCollection<Project>();
|
||||||
TreeRoots = new ObservableCollection<ProjectNode>();
|
TreeRoots = new ObservableCollection<ProjectNode>();
|
||||||
@ -81,8 +77,8 @@ public partial class MainViewModel : ObservableObject
|
|||||||
[ObservableProperty] private object? _currentView;
|
[ObservableProperty] private object? _currentView;
|
||||||
[ObservableProperty] private bool _devicesConnected;
|
[ObservableProperty] private bool _devicesConnected;
|
||||||
[ObservableProperty] private bool _devicePanelOpen;
|
[ObservableProperty] private bool _devicePanelOpen;
|
||||||
[ObservableProperty] private bool _safetyPanelOpen;
|
|
||||||
[ObservableProperty] private bool _deviceOperationBusy;
|
[ObservableProperty] private bool _deviceOperationBusy;
|
||||||
|
[ObservableProperty] private bool _emergencyOperationBusy;
|
||||||
[ObservableProperty] private string _deviceSummary = "设备状态检查中";
|
[ObservableProperty] private string _deviceSummary = "设备状态检查中";
|
||||||
[ObservableProperty] private string _sspcConnectionStatus = "检查中";
|
[ObservableProperty] private string _sspcConnectionStatus = "检查中";
|
||||||
[ObservableProperty] private string _powerConnectionStatus = "检查中";
|
[ObservableProperty] private string _powerConnectionStatus = "检查中";
|
||||||
@ -91,26 +87,13 @@ public partial class MainViewModel : ObservableObject
|
|||||||
[ObservableProperty] private string _pci2312ConnectionStatus = "检查中";
|
[ObservableProperty] private string _pci2312ConnectionStatus = "检查中";
|
||||||
[ObservableProperty] private string _safetyStatusText = "设备故障";
|
[ObservableProperty] private string _safetyStatusText = "设备故障";
|
||||||
[ObservableProperty] private string _safetyDetailText = "设备安全监控未启动";
|
[ObservableProperty] private string _safetyDetailText = "设备安全监控未启动";
|
||||||
[ObservableProperty] private string _safetyPowerCurrentText = "--";
|
|
||||||
[ObservableProperty] private string _safetyLoadCurrentText = "--";
|
|
||||||
[ObservableProperty] private bool _alarmLampActive;
|
[ObservableProperty] private bool _alarmLampActive;
|
||||||
|
[ObservableProperty] private string _emergencyButtonText = "报警急停(状态重置)";
|
||||||
[ObservableProperty] private string _testingMessage = "";
|
[ObservableProperty] private string _testingMessage = "";
|
||||||
[ObservableProperty] private TestSessionViewModel? _currentSession;
|
[ObservableProperty] private TestSessionViewModel? _currentSession;
|
||||||
|
|
||||||
partial void OnStateChanged(AppState value) => RebuildTree();
|
partial void OnStateChanged(AppState value) => RebuildTree();
|
||||||
partial void OnCurrentProjectChanged(Project? value) => RebuildTree();
|
partial void OnCurrentProjectChanged(Project? value) => RebuildTree();
|
||||||
partial void OnSafetyPanelOpenChanged(bool value)
|
|
||||||
{
|
|
||||||
if (value)
|
|
||||||
{
|
|
||||||
_safetyReadingsTimer.Start();
|
|
||||||
_ = RefreshSafetyReadingsAsync();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_safetyReadingsTimer.Stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task ConnectConfiguredDevicesAsync()
|
private async Task ConnectConfiguredDevicesAsync()
|
||||||
{
|
{
|
||||||
@ -180,55 +163,11 @@ public partial class MainViewModel : ObservableObject
|
|||||||
{
|
{
|
||||||
SafetyStatusText = _safetyMonitor.StatusText;
|
SafetyStatusText = _safetyMonitor.StatusText;
|
||||||
SafetyDetailText = _safetyMonitor.DetailText;
|
SafetyDetailText = _safetyMonitor.DetailText;
|
||||||
AlarmLampActive = _safetyMonitor.IsEmergencyActive || _safetyMonitor.IsAlarmOutputOn;
|
AlarmLampActive = _safetyMonitor.IsAlarmOutputOn;
|
||||||
|
EmergencyButtonText = AlarmLampActive ? "状态重置" : "报警急停";
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task RefreshSafetyReadingsAsync()
|
|
||||||
{
|
|
||||||
if (_safetyReadingsBusy) return;
|
|
||||||
_safetyReadingsBusy = true;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
SafetyPowerCurrentText = await ReadPowerCurrentTextAsync().ConfigureAwait(true);
|
|
||||||
SafetyLoadCurrentText = await ReadLoadCurrentTextAsync().ConfigureAwait(true);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_safetyReadingsBusy = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<string> ReadPowerCurrentTextAsync()
|
|
||||||
{
|
|
||||||
if (!_power.IsConnected) return "未连接";
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var (_, amps) = await _power.ReadAsync().ConfigureAwait(true);
|
|
||||||
return $"{amps:F3} A";
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogDebug(ex, "Read UDP5080 current for safety panel failed.");
|
|
||||||
return "读取失败";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<string> ReadLoadCurrentTextAsync()
|
|
||||||
{
|
|
||||||
if (!_load.IsConnected) return "未连接";
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var (_, amps) = await _load.ReadAsync().ConfigureAwait(true);
|
|
||||||
return $"{amps:F3} A";
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogDebug(ex, "Read IT8702P current for safety panel failed.");
|
|
||||||
return "读取失败";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string DeviceStatusText(IDeviceConnection device) =>
|
private static string DeviceStatusText(IDeviceConnection device) =>
|
||||||
device.IsConnected ? "已连接" : "未连接";
|
device.IsConnected ? "已连接" : "未连接";
|
||||||
|
|
||||||
@ -240,18 +179,51 @@ public partial class MainViewModel : ObservableObject
|
|||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private void ToggleSafetyPanel()
|
private async Task ToggleEmergencyAlarm()
|
||||||
{
|
{
|
||||||
|
if (EmergencyOperationBusy) return;
|
||||||
|
EmergencyOperationBusy = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (AlarmLampActive)
|
||||||
|
{
|
||||||
|
var result = await _safetyMonitor.ResetAsync().ConfigureAwait(false);
|
||||||
RefreshSafetyStatus();
|
RefreshSafetyStatus();
|
||||||
SafetyPanelOpen = !SafetyPanelOpen;
|
if (!result.Success)
|
||||||
|
{
|
||||||
|
Application.Current?.Dispatcher.Invoke(() =>
|
||||||
|
MessageBox.Show(
|
||||||
|
Application.Current?.MainWindow,
|
||||||
|
result.Message,
|
||||||
|
"状态重置受阻",
|
||||||
|
MessageBoxButton.OK,
|
||||||
|
MessageBoxImage.Warning));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
[RelayCommand]
|
else
|
||||||
private async Task ResetSafetyAlarm()
|
|
||||||
{
|
{
|
||||||
await _safetyMonitor.ResetAsync().ConfigureAwait(false);
|
await _safetyMonitor.TriggerSoftwareEmergencyAsync().ConfigureAwait(false);
|
||||||
RefreshSafetyStatus();
|
RefreshSafetyStatus();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Toggle emergency alarm failed.");
|
||||||
|
_logSink.Add(UiLogLevel.Error, $"报警急停操作失败:{ex.Message}");
|
||||||
|
Application.Current?.Dispatcher.Invoke(() =>
|
||||||
|
MessageBox.Show(
|
||||||
|
Application.Current?.MainWindow,
|
||||||
|
$"报警急停操作失败:{ex.Message}",
|
||||||
|
"报警急停",
|
||||||
|
MessageBoxButton.OK,
|
||||||
|
MessageBoxImage.Error));
|
||||||
|
RefreshSafetyStatus();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
EmergencyOperationBusy = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private async Task ConnectAllDevices()
|
private async Task ConnectAllDevices()
|
||||||
@ -427,8 +399,7 @@ public partial class MainViewModel : ObservableObject
|
|||||||
{
|
{
|
||||||
CurrentProject = p;
|
CurrentProject = p;
|
||||||
State = AppState.Testing;
|
State = AppState.Testing;
|
||||||
TestingMessage = $"测试进行中:{p.Name}";
|
TestingMessage = $"测试页面:{p.Name}";
|
||||||
|
|
||||||
var session = new TestSessionViewModel(_sp, p, _sspc, _power, _load, _daq, _logSink);
|
var session = new TestSessionViewModel(_sp, p, _sspc, _power, _load, _daq, _logSink);
|
||||||
CurrentSession = session;
|
CurrentSession = session;
|
||||||
|
|
||||||
@ -439,30 +410,31 @@ public partial class MainViewModel : ObservableObject
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await session.RunAllAsync();
|
await _store.SaveAsync(p);
|
||||||
_logSink.Add(UiLogLevel.Success, "全部测试完成。");
|
_logSink.Add(UiLogLevel.Info, "已进入测试页面,请在各测试页手动执行当前项目。");
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
_logSink.Add(UiLogLevel.Warning, "测试已中止。");
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Test failed.");
|
_logger.LogError(ex, "Failed to enter test view.");
|
||||||
_logSink.Add(UiLogLevel.Error, "测试出错:" + ex.Message);
|
_logSink.Add(UiLogLevel.Error, "进入测试页面出错:" + ex.Message);
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
await _store.SaveAsync(p);
|
|
||||||
State = AppState.Browsing;
|
|
||||||
CurrentSession = null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private void AbortTest()
|
private async Task AbortTest()
|
||||||
{
|
{
|
||||||
CurrentSession?.Cancel();
|
CurrentSession?.Cancel();
|
||||||
|
if (CurrentView is TestHostViewModel host && host.Basic.IsBatchRunning)
|
||||||
|
await host.Basic.CancelRunAllCommand.ExecuteAsync(null);
|
||||||
|
|
||||||
|
if (CurrentProject != null)
|
||||||
|
await _store.SaveAsync(CurrentProject);
|
||||||
|
|
||||||
|
CurrentSession = null;
|
||||||
|
TestingMessage = string.Empty;
|
||||||
|
State = AppState.Browsing;
|
||||||
|
if (CurrentProject != null)
|
||||||
|
OpenProjectInfo(CurrentProject);
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
|
|||||||
@ -131,6 +131,9 @@ public partial class PowerTestViewModel : ObservableObject
|
|||||||
throw new InvalidOperationException($"IT8702P connection failed: {ex.Message}", ex);
|
throw new InvalidOperationException($"IT8702P connection failed: {ex.Message}", ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await _power.OutputAsync(true);
|
||||||
|
await _load.InputAsync(true);
|
||||||
|
|
||||||
double sVin = 0, sIin = 0, sVout = 0, sIout = 0, sPout = 0;
|
double sVin = 0, sIin = 0, sVout = 0, sIout = 0, sPout = 0;
|
||||||
for (int s = 0; s < SamplesPerChannel; s++)
|
for (int s = 0; s < SamplesPerChannel; s++)
|
||||||
{
|
{
|
||||||
@ -165,7 +168,7 @@ public partial class PowerTestViewModel : ObservableObject
|
|||||||
row.MeasuredPowerOut = sPout / SamplesPerChannel;
|
row.MeasuredPowerOut = sPout / SamplesPerChannel;
|
||||||
row.Status = TestStatus.Measured;
|
row.Status = TestStatus.Measured;
|
||||||
Current = row;
|
Current = row;
|
||||||
_log.Add(UiLogLevel.Success, $"CH{ch} 功率损耗 {row.PowerLossW:F3} W,效率 {row.EfficiencyPct:F3}%");
|
_log.Add(UiLogLevel.Success, $"CH{ch} 功率损耗 {row.PowerLossW:F3} W,效率 {row.EfficiencyPct:F1}%");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -61,8 +61,8 @@ public partial class ProtectViewModel : ObservableObject
|
|||||||
"过压" or "欠压" => $"{volts:F3} V",
|
"过压" or "欠压" => $"{volts:F3} V",
|
||||||
_ => $"{amps:F3} A"
|
_ => $"{amps:F3} A"
|
||||||
};
|
};
|
||||||
if (row.Status is TestStatus.Untested or TestStatus.Running)
|
if (row.Status == TestStatus.Running)
|
||||||
row.Status = TestStatus.Measured;
|
row.Status = TestStatus.Untested;
|
||||||
_log.Add(UiLogLevel.Success, $"保护功能 {row.ProtectType} 实测值:{row.MeasuredValue}");
|
_log.Add(UiLogLevel.Success, $"保护功能 {row.ProtectType} 实测值:{row.MeasuredValue}");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
@ -60,7 +60,8 @@ public partial class ReportViewModel : ObservableObject
|
|||||||
{
|
{
|
||||||
Project = p;
|
Project = p;
|
||||||
if (string.IsNullOrWhiteSpace(p.Report.FileName) ||
|
if (string.IsNullOrWhiteSpace(p.Report.FileName) ||
|
||||||
string.Equals(p.Report.FileName, "report.pdf", StringComparison.OrdinalIgnoreCase))
|
string.Equals(p.Report.FileName, "report.pdf", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
string.Equals(p.Report.FileName, "report_{yyyyMMdd_HHmmss}.pdf", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
p.Report.FileName = ReportFileHelper.DefaultTemplate;
|
p.Report.FileName = ReportFileHelper.DefaultTemplate;
|
||||||
}
|
}
|
||||||
@ -318,15 +319,15 @@ public partial class ReportViewModel : ObservableObject
|
|||||||
: CurveImageRenderer.RenderRisingChannel(
|
: CurveImageRenderer.RenderRisingChannel(
|
||||||
row.Channel,
|
row.Channel,
|
||||||
rising,
|
rising,
|
||||||
row.RisingViewStartMs ?? row.ViewStartMs,
|
row.RisingViewStartMs,
|
||||||
row.RisingViewEndMs ?? row.ViewEndMs),
|
row.RisingViewEndMs),
|
||||||
falling == null
|
falling == null
|
||||||
? null
|
? null
|
||||||
: CurveImageRenderer.RenderFallingChannel(
|
: CurveImageRenderer.RenderFallingChannel(
|
||||||
row.Channel,
|
row.Channel,
|
||||||
falling,
|
falling,
|
||||||
row.FallingViewStartMs ?? row.ViewStartMs,
|
row.FallingViewStartMs,
|
||||||
row.FallingViewEndMs ?? row.ViewEndMs));
|
row.FallingViewEndMs));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -87,7 +87,7 @@
|
|||||||
</DataGridTextColumn>
|
</DataGridTextColumn>
|
||||||
<DataGridTextColumn Header="电压(V)" Width="*">
|
<DataGridTextColumn Header="电压(V)" Width="*">
|
||||||
<DataGridTextColumn.Binding>
|
<DataGridTextColumn.Binding>
|
||||||
<Binding Path="VoltageOn" Converter="{StaticResource NumberOrDash}" ConverterParameter="F2" />
|
<Binding Path="DisplayVoltage" Converter="{StaticResource NumberOrDash}" ConverterParameter="F2" />
|
||||||
</DataGridTextColumn.Binding>
|
</DataGridTextColumn.Binding>
|
||||||
<DataGridTextColumn.ElementStyle>
|
<DataGridTextColumn.ElementStyle>
|
||||||
<Style TargetType="TextBlock" BasedOn="{StaticResource MonoText}" />
|
<Style TargetType="TextBlock" BasedOn="{StaticResource MonoText}" />
|
||||||
@ -95,7 +95,7 @@
|
|||||||
</DataGridTextColumn>
|
</DataGridTextColumn>
|
||||||
<DataGridTextColumn Header="电流(A)" Width="*">
|
<DataGridTextColumn Header="电流(A)" Width="*">
|
||||||
<DataGridTextColumn.Binding>
|
<DataGridTextColumn.Binding>
|
||||||
<Binding Path="CurrentOn" Converter="{StaticResource NumberOrDash}" ConverterParameter="F3" />
|
<Binding Path="DisplayCurrent" Converter="{StaticResource NumberOrDash}" ConverterParameter="F3" />
|
||||||
</DataGridTextColumn.Binding>
|
</DataGridTextColumn.Binding>
|
||||||
<DataGridTextColumn.ElementStyle>
|
<DataGridTextColumn.ElementStyle>
|
||||||
<Style TargetType="TextBlock" BasedOn="{StaticResource MonoText}" />
|
<Style TargetType="TextBlock" BasedOn="{StaticResource MonoText}" />
|
||||||
|
|||||||
@ -3,20 +3,20 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:ctl="clr-namespace:SSPCTester.UI.Controls"
|
xmlns:ctl="clr-namespace:SSPCTester.UI.Controls"
|
||||||
xmlns:vm="clr-namespace:SSPCTester.UI.ViewModels"
|
xmlns:vm="clr-namespace:SSPCTester.UI.ViewModels"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
Title="曲线查看器"
|
Title="曲线查看器"
|
||||||
Width="1100" Height="720"
|
Width="1100" Height="720"
|
||||||
MinWidth="600" MinHeight="400"
|
MinWidth="600" MinHeight="400"
|
||||||
WindowStartupLocation="CenterOwner"
|
WindowStartupLocation="CenterOwner"
|
||||||
|
WindowState="Maximized"
|
||||||
Background="{StaticResource BgAppBrush}"
|
Background="{StaticResource BgAppBrush}"
|
||||||
d:DataContext="{d:DesignInstance Type=vm:CurveTestViewModel}"
|
d:DataContext="{d:DesignInstance Type=vm:CurveTestViewModel}"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
|
||||||
mc:Ignorable="d">
|
mc:Ignorable="d">
|
||||||
<Grid Margin="16">
|
<Grid Margin="16">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition Height="*" />
|
<RowDefinition Height="*" />
|
||||||
<RowDefinition Height="*" />
|
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
<Grid Margin="0,0,0,12">
|
<Grid Margin="0,0,0,12">
|
||||||
@ -31,64 +31,26 @@
|
|||||||
<ColumnDefinition Width="Auto" />
|
<ColumnDefinition Width="Auto" />
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<TextBlock VerticalAlignment="Center" FontSize="{StaticResource FsH1}" FontWeight="Bold">
|
<TextBlock VerticalAlignment="Center" FontSize="{StaticResource FsH1}" FontWeight="Bold">
|
||||||
<Run Text="曲线查看器 " />
|
<Run x:Name="TitleRun" Text="曲线查看器 " />
|
||||||
<Run Text="CH" Foreground="{StaticResource TextSecondaryBrush}" />
|
<Run Text="CH" Foreground="{StaticResource TextSecondaryBrush}" />
|
||||||
<Run Text="{Binding SelectedChannel, Mode=OneWay}" Foreground="{StaticResource PrimaryBrush}" />
|
<Run Text="{Binding SelectedChannel, Mode=OneWay}" Foreground="{StaticResource PrimaryBrush}" />
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
<ComboBox Grid.Column="1" Width="120" Margin="0,0,8,0"
|
<ComboBox Grid.Column="1" Width="120" Margin="0,0,8,0"
|
||||||
ItemsSource="{Binding Channels}" SelectedItem="{Binding SelectedChannel}" />
|
ItemsSource="{Binding Channels}" SelectedItem="{Binding SelectedChannel}" />
|
||||||
<TextBlock Grid.Column="2" Text="起点" VerticalAlignment="Center" Margin="0,0,4,0" />
|
<TextBlock Grid.Column="2" Text="起点" VerticalAlignment="Center" Margin="0,0,4,0" />
|
||||||
<TextBox Grid.Column="3" Width="64" Height="26" Margin="0,0,8,0" Padding="4,0"
|
<TextBox x:Name="StartTextBox" Grid.Column="3" Width="64" Height="26" Margin="0,0,8,0" Padding="4,0"
|
||||||
Text="{Binding RisingViewStartMs, UpdateSourceTrigger=PropertyChanged, TargetNullValue=''}"
|
|
||||||
FontFamily="{StaticResource FontMono}" />
|
FontFamily="{StaticResource FontMono}" />
|
||||||
<TextBlock Grid.Column="4" Text="终点" VerticalAlignment="Center" Margin="0,0,4,0" />
|
<TextBlock Grid.Column="4" Text="终点" VerticalAlignment="Center" Margin="0,0,4,0" />
|
||||||
<TextBox Grid.Column="5" Width="64" Height="26" Margin="0,0,8,0" Padding="4,0"
|
<TextBox x:Name="EndTextBox" Grid.Column="5" Width="64" Height="26" Margin="0,0,8,0" Padding="4,0"
|
||||||
Text="{Binding RisingViewEndMs, UpdateSourceTrigger=PropertyChanged, TargetNullValue=''}"
|
|
||||||
FontFamily="{StaticResource FontMono}" />
|
FontFamily="{StaticResource FontMono}" />
|
||||||
<Button Grid.Column="6" Content="默认" Width="54" Height="26" Margin="0,0,8,0" Padding="0"
|
<Button x:Name="ResetButton" Grid.Column="6" Content="默认" Width="54" Height="26" Margin="0,0,8,0" Padding="0" />
|
||||||
Command="{Binding ResetRisingCurveViewCommand}" />
|
|
||||||
<Button Grid.Column="7" Content="关闭" Width="80" IsCancel="True" Click="Close_Click" />
|
<Button Grid.Column="7" Content="关闭" Width="80" IsCancel="True" Click="Close_Click" />
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Border Grid.Row="1" Style="{StaticResource Card}" Margin="0,0,0,6">
|
<Border Grid.Row="1" Style="{StaticResource Card}">
|
||||||
<DockPanel>
|
<DockPanel>
|
||||||
<TextBlock DockPanel.Dock="Top" Style="{StaticResource CardTitle}" Text="开启曲线(电压上升,90% 阈值)" />
|
<TextBlock x:Name="CurveTitleText" DockPanel.Dock="Top" Style="{StaticResource CardTitle}" />
|
||||||
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,0,0,6">
|
<ctl:CurvePlotControl x:Name="CurvePlot" VMax="{Binding VMax}" />
|
||||||
<TextBlock Text="璧风偣" VerticalAlignment="Center" Margin="0,0,4,0" />
|
|
||||||
<TextBox Width="64" Height="26" Margin="0,0,8,0" Padding="4,0"
|
|
||||||
Text="{Binding RisingViewStartMs, UpdateSourceTrigger=PropertyChanged, TargetNullValue=''}"
|
|
||||||
FontFamily="{StaticResource FontMono}" />
|
|
||||||
<TextBlock Text="缁堢偣" VerticalAlignment="Center" Margin="0,0,4,0" />
|
|
||||||
<TextBox Width="64" Height="26" Margin="0,0,8,0" Padding="4,0"
|
|
||||||
Text="{Binding RisingViewEndMs, UpdateSourceTrigger=PropertyChanged, TargetNullValue=''}"
|
|
||||||
FontFamily="{StaticResource FontMono}" />
|
|
||||||
<Button Content="榛樿" Width="54" Height="26" Margin="0,0,8,0" Padding="0"
|
|
||||||
Command="{Binding ResetRisingCurveViewCommand}" />
|
|
||||||
</StackPanel>
|
|
||||||
<ctl:CurvePlotControl Curve="{Binding RisingCurve}" IsRising="True" VMax="{Binding VMax}"
|
|
||||||
ViewStartMs="{Binding RisingViewStartMs}"
|
|
||||||
ViewEndMs="{Binding RisingViewEndMs}" />
|
|
||||||
</DockPanel>
|
|
||||||
</Border>
|
|
||||||
|
|
||||||
<Border Grid.Row="2" Style="{StaticResource Card}" Margin="0,6,0,0">
|
|
||||||
<DockPanel>
|
|
||||||
<TextBlock DockPanel.Dock="Top" Style="{StaticResource CardTitle}" Text="关断曲线(电压下降,10% 阈值)" />
|
|
||||||
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,0,0,6">
|
|
||||||
<TextBlock Text="璧风偣" VerticalAlignment="Center" Margin="0,0,4,0" />
|
|
||||||
<TextBox Width="64" Height="26" Margin="0,0,8,0" Padding="4,0"
|
|
||||||
Text="{Binding FallingViewStartMs, UpdateSourceTrigger=PropertyChanged, TargetNullValue=''}"
|
|
||||||
FontFamily="{StaticResource FontMono}" />
|
|
||||||
<TextBlock Text="缁堢偣" VerticalAlignment="Center" Margin="0,0,4,0" />
|
|
||||||
<TextBox Width="64" Height="26" Margin="0,0,8,0" Padding="4,0"
|
|
||||||
Text="{Binding FallingViewEndMs, UpdateSourceTrigger=PropertyChanged, TargetNullValue=''}"
|
|
||||||
FontFamily="{StaticResource FontMono}" />
|
|
||||||
<Button Content="榛樿" Width="54" Height="26" Margin="0,0,8,0" Padding="0"
|
|
||||||
Command="{Binding ResetFallingCurveViewCommand}" />
|
|
||||||
</StackPanel>
|
|
||||||
<ctl:CurvePlotControl Curve="{Binding FallingCurve}" IsRising="False" VMax="{Binding VMax}"
|
|
||||||
ViewStartMs="{Binding FallingViewStartMs}"
|
|
||||||
ViewEndMs="{Binding FallingViewEndMs}" />
|
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
</Border>
|
</Border>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
@ -1,14 +1,50 @@
|
|||||||
using System.Windows;
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Data;
|
||||||
|
using SSPCTester.UI.Controls;
|
||||||
using SSPCTester.UI.ViewModels;
|
using SSPCTester.UI.ViewModels;
|
||||||
|
|
||||||
namespace SSPCTester.UI.Views;
|
namespace SSPCTester.UI.Views;
|
||||||
|
|
||||||
public partial class CurvePopupWindow : Window
|
public partial class CurvePopupWindow : Window
|
||||||
{
|
{
|
||||||
public CurvePopupWindow(CurveTestViewModel vm)
|
public CurvePopupWindow()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public CurvePopupWindow(CurveTestViewModel vm, string? curveKind)
|
||||||
|
: this()
|
||||||
|
{
|
||||||
DataContext = vm;
|
DataContext = vm;
|
||||||
|
ConfigureCurve(curveKind);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ConfigureCurve(string? curveKind)
|
||||||
|
{
|
||||||
|
bool isFalling = string.Equals(curveKind, "Falling", StringComparison.OrdinalIgnoreCase);
|
||||||
|
string title = isFalling ? "关断曲线(电压下降,10% 阈值)" : "开启曲线(电压上升,90% 阈值)";
|
||||||
|
|
||||||
|
Title = title;
|
||||||
|
TitleRun.Text = $"{title} ";
|
||||||
|
CurveTitleText.Text = title;
|
||||||
|
CurvePlot.IsRising = !isFalling;
|
||||||
|
|
||||||
|
CurvePlot.SetBinding(CurvePlotControl.CurveProperty, new Binding(isFalling ? "FallingCurve" : "RisingCurve"));
|
||||||
|
CurvePlot.SetBinding(CurvePlotControl.ViewStartMsProperty, new Binding(isFalling ? "FallingViewStartMs" : "RisingViewStartMs"));
|
||||||
|
CurvePlot.SetBinding(CurvePlotControl.ViewEndMsProperty, new Binding(isFalling ? "FallingViewEndMs" : "RisingViewEndMs"));
|
||||||
|
|
||||||
|
StartTextBox.SetBinding(TextBox.TextProperty, new Binding(isFalling ? "FallingViewStartMs" : "RisingViewStartMs")
|
||||||
|
{
|
||||||
|
UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
|
||||||
|
TargetNullValue = ""
|
||||||
|
});
|
||||||
|
EndTextBox.SetBinding(TextBox.TextProperty, new Binding(isFalling ? "FallingViewEndMs" : "RisingViewEndMs")
|
||||||
|
{
|
||||||
|
UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
|
||||||
|
TargetNullValue = ""
|
||||||
|
});
|
||||||
|
ResetButton.SetBinding(Button.CommandProperty, new Binding(isFalling ? "ResetFallingCurveViewCommand" : "ResetRisingCurveViewCommand"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Close_Click(object sender, RoutedEventArgs e) => Close();
|
private void Close_Click(object sender, RoutedEventArgs e) => Close();
|
||||||
|
|||||||
@ -54,12 +54,10 @@
|
|||||||
</Grid>
|
</Grid>
|
||||||
<Button Style="{StaticResource PrimaryButton}" Content="采集开启曲线" Margin="0,16,0,0"
|
<Button Style="{StaticResource PrimaryButton}" Content="采集开启曲线" Margin="0,16,0,0"
|
||||||
Command="{Binding CaptureRisingCommand}"
|
Command="{Binding CaptureRisingCommand}"
|
||||||
IsEnabled="{Binding Busy, Converter={StaticResource BoolToVis}, ConverterParameter=Invert}" />
|
IsEnabled="{Binding Busy, Converter={StaticResource InverseBoolConverter}}" />
|
||||||
<Button Style="{StaticResource PrimaryButton}" Content="采集关断曲线" Margin="0,8,0,0"
|
<Button Style="{StaticResource PrimaryButton}" Content="采集关断曲线" Margin="0,8,0,0"
|
||||||
Command="{Binding CaptureFallingCommand}"
|
Command="{Binding CaptureFallingCommand}"
|
||||||
IsEnabled="{Binding Busy, Converter={StaticResource BoolToVis}, ConverterParameter=Invert}" />
|
IsEnabled="{Binding Busy, Converter={StaticResource InverseBoolConverter}}" />
|
||||||
<Button Content="在新窗口查看曲线" Margin="0,8,0,0"
|
|
||||||
Command="{Binding OpenPopupCommand}" />
|
|
||||||
<TextBlock Margin="0,12,0,0" TextWrapping="Wrap"
|
<TextBlock Margin="0,12,0,0" TextWrapping="Wrap"
|
||||||
Foreground="{StaticResource TextMutedBrush}"
|
Foreground="{StaticResource TextMutedBrush}"
|
||||||
Text="开启/关断时间以软件发送 Modbus 命令前的标记为基准,包含串口通信延迟,不等同于硬件同步触发精度。" />
|
Text="开启/关断时间以软件发送 Modbus 命令前的标记为基准,包含串口通信延迟,不等同于硬件同步触发精度。" />
|
||||||
@ -92,7 +90,8 @@
|
|||||||
<Button Content="默认" Width="54" Height="26" Padding="0" Margin="0,0,8,0"
|
<Button Content="默认" Width="54" Height="26" Padding="0" Margin="0,0,8,0"
|
||||||
Command="{Binding ResetRisingCurveViewCommand}" />
|
Command="{Binding ResetRisingCurveViewCommand}" />
|
||||||
<Button Content="放大" Width="64" Height="26" Padding="0"
|
<Button Content="放大" Width="64" Height="26" Padding="0"
|
||||||
Command="{Binding OpenPopupCommand}" />
|
Command="{Binding OpenPopupCommand}"
|
||||||
|
CommandParameter="Rising" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
<ctl:CurvePlotControl Curve="{Binding RisingCurve}" IsRising="True" VMax="{Binding VMax}"
|
<ctl:CurvePlotControl Curve="{Binding RisingCurve}" IsRising="True" VMax="{Binding VMax}"
|
||||||
@ -120,7 +119,8 @@
|
|||||||
<Button Content="默认" Width="54" Height="26" Padding="0" Margin="0,0,8,0"
|
<Button Content="默认" Width="54" Height="26" Padding="0" Margin="0,0,8,0"
|
||||||
Command="{Binding ResetFallingCurveViewCommand}" />
|
Command="{Binding ResetFallingCurveViewCommand}" />
|
||||||
<Button Content="放大" Width="64" Height="26" Padding="0"
|
<Button Content="放大" Width="64" Height="26" Padding="0"
|
||||||
Command="{Binding OpenPopupCommand}" />
|
Command="{Binding OpenPopupCommand}"
|
||||||
|
CommandParameter="Falling" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
<ctl:CurvePlotControl Curve="{Binding FallingCurve}" IsRising="False" VMax="{Binding VMax}"
|
<ctl:CurvePlotControl Curve="{Binding FallingCurve}" IsRising="False" VMax="{Binding VMax}"
|
||||||
@ -141,7 +141,8 @@
|
|||||||
<Border Style="{StaticResource Card}" Margin="0,0,12,0">
|
<Border Style="{StaticResource Card}" Margin="0,0,12,0">
|
||||||
<DockPanel>
|
<DockPanel>
|
||||||
<TextBlock DockPanel.Dock="Top" Style="{StaticResource CardTitle}" Text="各通道时间参数" />
|
<TextBlock DockPanel.Dock="Top" Style="{StaticResource CardTitle}" Text="各通道时间参数" />
|
||||||
<DataGrid ItemsSource="{Binding Rows}">
|
<DataGrid ItemsSource="{Binding Rows}"
|
||||||
|
SelectedItem="{Binding SelectedRow, Mode=TwoWay}">
|
||||||
<DataGrid.Columns>
|
<DataGrid.Columns>
|
||||||
<DataGridTextColumn Header="通道" Width="70">
|
<DataGridTextColumn Header="通道" Width="70">
|
||||||
<DataGridTextColumn.Binding>
|
<DataGridTextColumn.Binding>
|
||||||
|
|||||||
@ -37,58 +37,152 @@
|
|||||||
FontSize="{StaticResource FsTitle}" FontWeight="Bold" />
|
FontSize="{StaticResource FsTitle}" FontWeight="Bold" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel Orientation="Horizontal" Grid.Column="2" VerticalAlignment="Center">
|
<StackPanel Orientation="Horizontal" Grid.Column="2" VerticalAlignment="Center">
|
||||||
<Grid Margin="0,0,8,0">
|
<Viewbox Width="26" Height="26" Margin="0,0,6,0"
|
||||||
<Button x:Name="SafetyStatusButton" Width="34" Height="30" Padding="0"
|
ToolTip="{Binding SafetyDetailText}">
|
||||||
Command="{Binding ToggleSafetyPanelCommand}"
|
<Canvas Width="64" Height="64">
|
||||||
ToolTip="{Binding SafetyStatusText}"
|
<Canvas.Resources>
|
||||||
Background="Transparent" BorderBrush="{StaticResource BorderBrush}">
|
<RadialGradientBrush x:Key="AlarmRedGlassBrush" Center="0.34,0.16" GradientOrigin="0.34,0.16" RadiusX="0.78" RadiusY="0.78">
|
||||||
<Grid>
|
<GradientStop Color="#FFFF8A8A" Offset="0" />
|
||||||
<Ellipse Width="18" Height="18" Stroke="{StaticResource BorderBrush}" StrokeThickness="1">
|
<GradientStop Color="#FFDF0010" Offset="0.36" />
|
||||||
<Ellipse.Style>
|
<GradientStop Color="#FF720006" Offset="1" />
|
||||||
<Style TargetType="Ellipse">
|
</RadialGradientBrush>
|
||||||
<Setter Property="Fill" Value="{StaticResource SuccessBrush}" />
|
<RadialGradientBrush x:Key="AlarmGrayGlassBrush" Center="0.34,0.16" GradientOrigin="0.34,0.16" RadiusX="0.78" RadiusY="0.78">
|
||||||
|
<GradientStop Color="#FFDEDEDE" Offset="0" />
|
||||||
|
<GradientStop Color="#FF969696" Offset="0.36" />
|
||||||
|
<GradientStop Color="#FF4B4B4B" Offset="1" />
|
||||||
|
</RadialGradientBrush>
|
||||||
|
<LinearGradientBrush x:Key="AlarmBaseBrush" StartPoint="0,0" EndPoint="0,1">
|
||||||
|
<GradientStop Color="#FF3B3B3B" Offset="0" />
|
||||||
|
<GradientStop Color="#FF101010" Offset="0.45" />
|
||||||
|
<GradientStop Color="#FF303030" Offset="1" />
|
||||||
|
</LinearGradientBrush>
|
||||||
|
</Canvas.Resources>
|
||||||
|
<Path x:Name="AlarmGlass" Data="M14 44V23C14 10.5 22.1 4 32 4s18 6.5 18 19v21z">
|
||||||
|
<Path.Style>
|
||||||
|
<Style TargetType="Path">
|
||||||
|
<Setter Property="Fill" Value="{StaticResource AlarmGrayGlassBrush}" />
|
||||||
<Style.Triggers>
|
<Style.Triggers>
|
||||||
<DataTrigger Binding="{Binding AlarmLampActive}" Value="True">
|
<DataTrigger Binding="{Binding AlarmLampActive}" Value="True">
|
||||||
<Setter Property="Fill" Value="{StaticResource DangerBrush}" />
|
<Setter Property="Fill" Value="{StaticResource AlarmRedGlassBrush}" />
|
||||||
|
<DataTrigger.EnterActions>
|
||||||
|
<BeginStoryboard x:Name="AlarmGlassBlink">
|
||||||
|
<Storyboard>
|
||||||
|
<DoubleAnimation Storyboard.TargetProperty="Opacity"
|
||||||
|
From="1" To=".42" Duration="0:0:0.36" AutoReverse="True" RepeatBehavior="Forever" />
|
||||||
|
</Storyboard>
|
||||||
|
</BeginStoryboard>
|
||||||
|
</DataTrigger.EnterActions>
|
||||||
|
<DataTrigger.ExitActions>
|
||||||
|
<StopStoryboard BeginStoryboardName="AlarmGlassBlink" />
|
||||||
|
</DataTrigger.ExitActions>
|
||||||
</DataTrigger>
|
</DataTrigger>
|
||||||
</Style.Triggers>
|
</Style.Triggers>
|
||||||
</Style>
|
</Style>
|
||||||
</Ellipse.Style>
|
</Path.Style>
|
||||||
</Ellipse>
|
</Path>
|
||||||
<TextBlock Text="!" Foreground="White" FontSize="12" FontWeight="Bold"
|
<Path x:Name="AlarmRim" Data="M14 44V23C14 10.5 22.1 4 32 4s18 6.5 18 19v21z"
|
||||||
HorizontalAlignment="Center" VerticalAlignment="Center" />
|
Fill="Transparent">
|
||||||
</Grid>
|
<Path.Style>
|
||||||
|
<Style TargetType="Path">
|
||||||
|
<Setter Property="Stroke" Value="#FF555555" />
|
||||||
|
<Setter Property="StrokeThickness" Value="1.4" />
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding AlarmLampActive}" Value="True">
|
||||||
|
<Setter Property="Stroke" Value="#FFFF2635" />
|
||||||
|
<Setter Property="StrokeThickness" Value="2.2" />
|
||||||
|
<DataTrigger.EnterActions>
|
||||||
|
<BeginStoryboard x:Name="AlarmRimBlink">
|
||||||
|
<Storyboard>
|
||||||
|
<DoubleAnimation Storyboard.TargetProperty="Opacity"
|
||||||
|
From="1" To=".35" Duration="0:0:0.36" AutoReverse="True" RepeatBehavior="Forever" />
|
||||||
|
</Storyboard>
|
||||||
|
</BeginStoryboard>
|
||||||
|
</DataTrigger.EnterActions>
|
||||||
|
<DataTrigger.ExitActions>
|
||||||
|
<StopStoryboard BeginStoryboardName="AlarmRimBlink" />
|
||||||
|
</DataTrigger.ExitActions>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</Path.Style>
|
||||||
|
</Path>
|
||||||
|
<Path x:Name="AlarmSweep" Data="M17 6h8l-11 42h-8z"
|
||||||
|
Fill="White">
|
||||||
|
<Path.RenderTransform>
|
||||||
|
<TranslateTransform />
|
||||||
|
</Path.RenderTransform>
|
||||||
|
<Path.Style>
|
||||||
|
<Style TargetType="Path">
|
||||||
|
<Setter Property="Opacity" Value="0" />
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding AlarmLampActive}" Value="True">
|
||||||
|
<Setter Property="Opacity" Value=".65" />
|
||||||
|
<DataTrigger.EnterActions>
|
||||||
|
<BeginStoryboard x:Name="AlarmSweepMove">
|
||||||
|
<Storyboard>
|
||||||
|
<DoubleAnimation Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.X)"
|
||||||
|
From="-24" To="30" Duration="0:0:0.95" RepeatBehavior="Forever" />
|
||||||
|
</Storyboard>
|
||||||
|
</BeginStoryboard>
|
||||||
|
</DataTrigger.EnterActions>
|
||||||
|
<DataTrigger.ExitActions>
|
||||||
|
<StopStoryboard BeginStoryboardName="AlarmSweepMove" />
|
||||||
|
</DataTrigger.ExitActions>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</Path.Style>
|
||||||
|
</Path>
|
||||||
|
<Path Data="M25 13l7-3 3 6-8 3zM38 11c4 1 6 3 8 7l-5 2c-1-3-3-4-6-5z"
|
||||||
|
Fill="White" Opacity=".72" />
|
||||||
|
<Ellipse Canvas.Left="10" Canvas.Top="39" Width="44" Height="10" Fill="#771A0000" />
|
||||||
|
<Rectangle x:Name="AlarmBaseFlash" Canvas.Left="13" Canvas.Top="42" Width="38" Height="2"
|
||||||
|
Fill="#FFFF0014">
|
||||||
|
<Rectangle.Style>
|
||||||
|
<Style TargetType="Rectangle">
|
||||||
|
<Setter Property="Opacity" Value="0" />
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding AlarmLampActive}" Value="True">
|
||||||
|
<Setter Property="Opacity" Value="1" />
|
||||||
|
<DataTrigger.EnterActions>
|
||||||
|
<BeginStoryboard x:Name="AlarmBaseBlink">
|
||||||
|
<Storyboard>
|
||||||
|
<DoubleAnimation Storyboard.TargetProperty="Opacity"
|
||||||
|
From="1" To=".15" Duration="0:0:0.36" AutoReverse="True" RepeatBehavior="Forever" />
|
||||||
|
</Storyboard>
|
||||||
|
</BeginStoryboard>
|
||||||
|
</DataTrigger.EnterActions>
|
||||||
|
<DataTrigger.ExitActions>
|
||||||
|
<StopStoryboard BeginStoryboardName="AlarmBaseBlink" />
|
||||||
|
</DataTrigger.ExitActions>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</Rectangle.Style>
|
||||||
|
</Rectangle>
|
||||||
|
<Path Data="M11 43h42v6H11z" Fill="#DD240004" />
|
||||||
|
<Path Data="M9 48h46v8c0 3-3 5-6 5H15c-3 0-6-2-6-5z"
|
||||||
|
Fill="{StaticResource AlarmBaseBrush}" />
|
||||||
|
<Path Data="M13 50h38" Stroke="#777777" StrokeThickness="1" Opacity=".45" />
|
||||||
|
<Path Data="M42 56v4M47 55v4M52 54v3" Stroke="#050505" StrokeThickness="2" StrokeStartLineCap="Round" StrokeEndLineCap="Round" />
|
||||||
|
</Canvas>
|
||||||
|
</Viewbox>
|
||||||
|
<Button Margin="0,0,8,0" Padding="12,4"
|
||||||
|
Command="{Binding ToggleEmergencyAlarmCommand}"
|
||||||
|
IsEnabled="{Binding EmergencyOperationBusy, Converter={StaticResource InverseBoolConverter}}"
|
||||||
|
ToolTip="{Binding SafetyDetailText}">
|
||||||
|
<Button.Style>
|
||||||
|
<Style TargetType="Button" BasedOn="{StaticResource DangerButton}">
|
||||||
|
<Setter Property="Content" Value="{Binding EmergencyButtonText}" />
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding AlarmLampActive}" Value="False">
|
||||||
|
<Setter Property="Background" Value="{StaticResource WarningBrush}" />
|
||||||
|
<Setter Property="Foreground" Value="White" />
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</Button.Style>
|
||||||
</Button>
|
</Button>
|
||||||
<Popup IsOpen="{Binding SafetyPanelOpen}" PlacementTarget="{Binding ElementName=SafetyStatusButton}"
|
|
||||||
Placement="Bottom" StaysOpen="False" AllowsTransparency="True">
|
|
||||||
<Border Width="300" Margin="0,8,0,0" Padding="16"
|
|
||||||
Background="{StaticResource BgSurfaceBrush}" BorderBrush="{StaticResource BorderBrush}"
|
|
||||||
BorderThickness="1" CornerRadius="{StaticResource RadiusMd}">
|
|
||||||
<StackPanel>
|
|
||||||
<TextBlock Text="设备安全" FontWeight="Bold" FontSize="{StaticResource FsH2}" Margin="0,0,0,12" />
|
|
||||||
<Grid Margin="0,3">
|
|
||||||
<Grid.ColumnDefinitions><ColumnDefinition Width="*" /><ColumnDefinition Width="Auto" /></Grid.ColumnDefinitions>
|
|
||||||
<TextBlock Text="PCI2312" /><TextBlock Grid.Column="1" Text="{Binding Pci2312ConnectionStatus}" />
|
|
||||||
</Grid>
|
|
||||||
<Grid Margin="0,3">
|
|
||||||
<Grid.ColumnDefinitions><ColumnDefinition Width="*" /><ColumnDefinition Width="Auto" /></Grid.ColumnDefinitions>
|
|
||||||
<TextBlock Text="设备状态" /><TextBlock Grid.Column="1" Text="{Binding SafetyStatusText}" />
|
|
||||||
</Grid>
|
|
||||||
<Grid Margin="0,3">
|
|
||||||
<Grid.ColumnDefinitions><ColumnDefinition Width="*" /><ColumnDefinition Width="Auto" /></Grid.ColumnDefinitions>
|
|
||||||
<TextBlock Text="电源电流" /><TextBlock Grid.Column="1" Text="{Binding SafetyPowerCurrentText}" FontFamily="{StaticResource FontMono}" />
|
|
||||||
</Grid>
|
|
||||||
<Grid Margin="0,3">
|
|
||||||
<Grid.ColumnDefinitions><ColumnDefinition Width="*" /><ColumnDefinition Width="Auto" /></Grid.ColumnDefinitions>
|
|
||||||
<TextBlock Text="负载电流" /><TextBlock Grid.Column="1" Text="{Binding SafetyLoadCurrentText}" FontFamily="{StaticResource FontMono}" />
|
|
||||||
</Grid>
|
|
||||||
<TextBlock Text="{Binding SafetyDetailText}" TextWrapping="Wrap"
|
|
||||||
Foreground="{StaticResource TextSecondaryBrush}" Margin="0,8,0,12" />
|
|
||||||
<Button Content="复位报警灯" Command="{Binding ResetSafetyAlarmCommand}" HorizontalAlignment="Stretch" />
|
|
||||||
</StackPanel>
|
|
||||||
</Border>
|
|
||||||
</Popup>
|
|
||||||
</Grid>
|
|
||||||
<Grid>
|
<Grid>
|
||||||
<Button x:Name="DeviceStatusButton" Padding="10,4" BorderThickness="0"
|
<Button x:Name="DeviceStatusButton" Padding="10,4" BorderThickness="0"
|
||||||
Background="{Binding DevicesConnected, Converter={StaticResource StatusToSoftBrush}, ConverterParameter=Pass}"
|
Background="{Binding DevicesConnected, Converter={StaticResource StatusToSoftBrush}, ConverterParameter=Pass}"
|
||||||
@ -124,7 +218,7 @@
|
|||||||
</Grid>
|
</Grid>
|
||||||
<Grid Margin="0,3,0,12">
|
<Grid Margin="0,3,0,12">
|
||||||
<Grid.ColumnDefinitions><ColumnDefinition Width="*" /><ColumnDefinition Width="Auto" /></Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions><ColumnDefinition Width="*" /><ColumnDefinition Width="Auto" /></Grid.ColumnDefinitions>
|
||||||
<TextBlock Text="PCI2312 安全输入输出" /><TextBlock Grid.Column="1" Text="{Binding Pci2312ConnectionStatus}" />
|
<TextBlock Text="报警/急停" /><TextBlock Grid.Column="1" Text="{Binding Pci2312ConnectionStatus}" />
|
||||||
</Grid>
|
</Grid>
|
||||||
<WrapPanel>
|
<WrapPanel>
|
||||||
<Button Content="连接全部" Command="{Binding ConnectAllDevicesCommand}" Margin="0,0,8,8" />
|
<Button Content="连接全部" Command="{Binding ConnectAllDevicesCommand}" Margin="0,0,8,8" />
|
||||||
@ -225,7 +319,7 @@
|
|||||||
<TextBlock Grid.Column="1" Margin="10,0,0,0" VerticalAlignment="Center"
|
<TextBlock Grid.Column="1" Margin="10,0,0,0" VerticalAlignment="Center"
|
||||||
FontWeight="SemiBold" Foreground="{StaticResource TestingBarTextBrush}"
|
FontWeight="SemiBold" Foreground="{StaticResource TestingBarTextBrush}"
|
||||||
Text="{Binding TestingMessage}" />
|
Text="{Binding TestingMessage}" />
|
||||||
<Button Grid.Column="2" Style="{StaticResource DangerButton}" Content="中止测试"
|
<Button Grid.Column="2" Style="{StaticResource DangerButton}" Content="退出测试"
|
||||||
Command="{Binding AbortTestCommand}" />
|
Command="{Binding AbortTestCommand}" />
|
||||||
</Grid>
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
|
|||||||
@ -1,24 +1,31 @@
|
|||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
|
using System.ComponentModel;
|
||||||
using SSPCTester.UI.ViewModels;
|
using SSPCTester.UI.ViewModels;
|
||||||
|
|
||||||
namespace SSPCTester.UI.Views;
|
namespace SSPCTester.UI.Views;
|
||||||
|
|
||||||
public partial class MainWindow : Window
|
public partial class MainWindow : Window
|
||||||
{
|
{
|
||||||
private readonly MainViewModel _vm;
|
private readonly MainViewModel? _vm;
|
||||||
|
|
||||||
public MainWindow(MainViewModel vm)
|
public MainWindow()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MainWindow(MainViewModel vm) : this()
|
||||||
|
{
|
||||||
_vm = vm;
|
_vm = vm;
|
||||||
DataContext = vm;
|
DataContext = vm;
|
||||||
|
if (!DesignerProperties.GetIsInDesignMode(this))
|
||||||
Loaded += (_, _) => _vm.Initialize();
|
Loaded += (_, _) => _vm.Initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ProjectTree_MouseDoubleClick(object sender, MouseButtonEventArgs e)
|
private void ProjectTree_MouseDoubleClick(object sender, MouseButtonEventArgs e)
|
||||||
{
|
{
|
||||||
|
if (_vm == null) return;
|
||||||
if (sender is TreeView tv && tv.SelectedItem is ProjectNode node)
|
if (sender is TreeView tv && tv.SelectedItem is ProjectNode node)
|
||||||
{
|
{
|
||||||
_vm.HandleNodeActivated(node);
|
_vm.HandleNodeActivated(node);
|
||||||
|
|||||||
@ -30,7 +30,7 @@
|
|||||||
<TextBox Text="{Binding SamplesPerChannel}" Margin="0,0,0,12" FontFamily="{StaticResource FontMono}" />
|
<TextBox Text="{Binding SamplesPerChannel}" Margin="0,0,0,12" FontFamily="{StaticResource FontMono}" />
|
||||||
<TextBlock Style="{StaticResource FieldLabel}" Text="采集间隔 (ms)" />
|
<TextBlock Style="{StaticResource FieldLabel}" Text="采集间隔 (ms)" />
|
||||||
<TextBox Text="{Binding IntervalMs}" Margin="0,0,0,12" FontFamily="{StaticResource FontMono}" />
|
<TextBox Text="{Binding IntervalMs}" Margin="0,0,0,12" FontFamily="{StaticResource FontMono}" />
|
||||||
<UniformGrid Columns="2" Margin="0,16,0,0">
|
<UniformGrid Columns="2" Margin="0,0,0,0">
|
||||||
<Button Content="开启当前通道" Margin="0,0,6,0"
|
<Button Content="开启当前通道" Margin="0,0,6,0"
|
||||||
Command="{Binding TurnOnCurrentChannelCommand}"
|
Command="{Binding TurnOnCurrentChannelCommand}"
|
||||||
IsEnabled="{Binding CanOperate}" />
|
IsEnabled="{Binding CanOperate}" />
|
||||||
@ -38,7 +38,7 @@
|
|||||||
Command="{Binding TurnOffCurrentChannelCommand}"
|
Command="{Binding TurnOffCurrentChannelCommand}"
|
||||||
IsEnabled="{Binding CanOperate}" />
|
IsEnabled="{Binding CanOperate}" />
|
||||||
</UniformGrid>
|
</UniformGrid>
|
||||||
<Button Style="{StaticResource PrimaryButton}" Content="测量当前通道" Margin="0,16,0,0"
|
<Button Style="{StaticResource PrimaryButton}" Content="测量当前通道" Margin="0,10,0,0"
|
||||||
Command="{Binding MeasureCurrentCommand}"
|
Command="{Binding MeasureCurrentCommand}"
|
||||||
IsEnabled="{Binding CanOperate}" />
|
IsEnabled="{Binding CanOperate}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
@ -161,7 +161,7 @@
|
|||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<TextBlock Text="效率" VerticalAlignment="Center" Foreground="{StaticResource TextSecondaryBrush}" />
|
<TextBlock Text="效率" VerticalAlignment="Center" Foreground="{StaticResource TextSecondaryBrush}" />
|
||||||
<TextBlock Grid.Column="1" HorizontalAlignment="Right" VerticalAlignment="Center" Foreground="{StaticResource PrimaryBrush}" FontFamily="{StaticResource FontMono}" FontSize="20" FontWeight="Bold"
|
<TextBlock Grid.Column="1" HorizontalAlignment="Right" VerticalAlignment="Center" Foreground="{StaticResource PrimaryBrush}" FontFamily="{StaticResource FontMono}" FontSize="20" FontWeight="Bold"
|
||||||
Text="{Binding Current.EfficiencyPct, Converter={StaticResource NumberOrDash}, ConverterParameter=F3}" />
|
Text="{Binding Current.EfficiencyPct, Converter={StaticResource NumberOrDash}, ConverterParameter=F1}" />
|
||||||
<TextBlock Grid.Column="2" Text="%" Margin="6,0,0,0" VerticalAlignment="Center" Foreground="{StaticResource TextSecondaryBrush}" />
|
<TextBlock Grid.Column="2" Text="%" Margin="6,0,0,0" VerticalAlignment="Center" Foreground="{StaticResource TextSecondaryBrush}" />
|
||||||
</Grid>
|
</Grid>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
@ -236,7 +236,7 @@
|
|||||||
</DataGridTextColumn>
|
</DataGridTextColumn>
|
||||||
<DataGridTextColumn Header="效率(%)" Width="*">
|
<DataGridTextColumn Header="效率(%)" Width="*">
|
||||||
<DataGridTextColumn.Binding>
|
<DataGridTextColumn.Binding>
|
||||||
<Binding Path="EfficiencyPct" Converter="{StaticResource NumberOrDash}" ConverterParameter="F3" />
|
<Binding Path="EfficiencyPct" Converter="{StaticResource NumberOrDash}" ConverterParameter="F1" />
|
||||||
</DataGridTextColumn.Binding>
|
</DataGridTextColumn.Binding>
|
||||||
<DataGridTextColumn.ElementStyle>
|
<DataGridTextColumn.ElementStyle>
|
||||||
<Style TargetType="TextBlock" BasedOn="{StaticResource MonoText}" />
|
<Style TargetType="TextBlock" BasedOn="{StaticResource MonoText}" />
|
||||||
|
|||||||
@ -90,7 +90,6 @@
|
|||||||
MinWidth="90">
|
MinWidth="90">
|
||||||
<ComboBoxItem Content="合格" Tag="{x:Static model:TestStatus.Pass}" />
|
<ComboBoxItem Content="合格" Tag="{x:Static model:TestStatus.Pass}" />
|
||||||
<ComboBoxItem Content="不合格" Tag="{x:Static model:TestStatus.Fail}" />
|
<ComboBoxItem Content="不合格" Tag="{x:Static model:TestStatus.Fail}" />
|
||||||
<ComboBoxItem Content="已测量" Tag="{x:Static model:TestStatus.Measured}" />
|
|
||||||
<ComboBoxItem Content="未判定" Tag="{x:Static model:TestStatus.Untested}" />
|
<ComboBoxItem Content="未判定" Tag="{x:Static model:TestStatus.Untested}" />
|
||||||
</ComboBox>
|
</ComboBox>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
|
|||||||
@ -9,8 +9,8 @@
|
|||||||
<Grid Margin="10">
|
<Grid Margin="10">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="34" />
|
<RowDefinition Height="34" />
|
||||||
<RowDefinition Height="218" />
|
<RowDefinition Height="252" />
|
||||||
<RowDefinition Height="354" />
|
<RowDefinition Height="320" />
|
||||||
<RowDefinition Height="90" />
|
<RowDefinition Height="90" />
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
@ -28,15 +28,15 @@
|
|||||||
<Grid>
|
<Grid>
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition Height="30" />
|
<RowDefinition Height="36" />
|
||||||
<RowDefinition Height="30" />
|
<RowDefinition Height="36" />
|
||||||
<RowDefinition Height="30" />
|
<RowDefinition Height="36" />
|
||||||
<RowDefinition Height="30" />
|
<RowDefinition Height="36" />
|
||||||
<RowDefinition Height="30" />
|
<RowDefinition Height="38" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
<TextBlock Grid.Row="0" Style="{StaticResource CardTitle}" Margin="0,0,0,6" Text="设备连接配置" />
|
<TextBlock Grid.Row="0" Style="{StaticResource CardTitle}" Margin="0,0,0,6" Text="设备连接配置" />
|
||||||
|
|
||||||
<Grid Grid.Row="1" Margin="0,0,0,3">
|
<Grid Grid.Row="1" Margin="0,0,0,5">
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="120" />
|
<ColumnDefinition Width="120" />
|
||||||
<ColumnDefinition Width="150" />
|
<ColumnDefinition Width="150" />
|
||||||
@ -57,7 +57,7 @@
|
|||||||
<TextBlock Grid.Column="7" Margin="10,0,0,0" Text="{Binding SspcStatus}" VerticalAlignment="Center" />
|
<TextBlock Grid.Column="7" Margin="10,0,0,0" Text="{Binding SspcStatus}" VerticalAlignment="Center" />
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Grid Grid.Row="2" Margin="0,0,0,3">
|
<Grid Grid.Row="2" Margin="0,0,0,5">
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="120" />
|
<ColumnDefinition Width="120" />
|
||||||
<ColumnDefinition Width="220" />
|
<ColumnDefinition Width="220" />
|
||||||
@ -76,7 +76,7 @@
|
|||||||
<TextBlock Grid.Column="6" Margin="10,0,0,0" Text="{Binding PowerStatus}" VerticalAlignment="Center" />
|
<TextBlock Grid.Column="6" Margin="10,0,0,0" Text="{Binding PowerStatus}" VerticalAlignment="Center" />
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Grid Grid.Row="3" Margin="0,0,0,3">
|
<Grid Grid.Row="3" Margin="0,0,0,5">
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="120" />
|
<ColumnDefinition Width="120" />
|
||||||
<ColumnDefinition Width="220" />
|
<ColumnDefinition Width="220" />
|
||||||
@ -95,7 +95,7 @@
|
|||||||
<TextBlock Grid.Column="6" Margin="10,0,0,0" Text="{Binding LoadStatus}" VerticalAlignment="Center" />
|
<TextBlock Grid.Column="6" Margin="10,0,0,0" Text="{Binding LoadStatus}" VerticalAlignment="Center" />
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Grid Grid.Row="4">
|
<Grid Grid.Row="4" Margin="0,0,0,5">
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="120" />
|
<ColumnDefinition Width="120" />
|
||||||
<ColumnDefinition Width="260" />
|
<ColumnDefinition Width="260" />
|
||||||
@ -116,37 +116,53 @@
|
|||||||
<TextBlock Grid.Column="7" Margin="10,0,0,0" Text="{Binding DaqStatus}" VerticalAlignment="Center" />
|
<TextBlock Grid.Column="7" Margin="10,0,0,0" Text="{Binding DaqStatus}" VerticalAlignment="Center" />
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Grid Grid.Row="5">
|
<Grid Grid.Row="5" Margin="0,0,74,0">
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="90" />
|
<ColumnDefinition Width="90" />
|
||||||
<ColumnDefinition Width="48" />
|
<ColumnDefinition Width="48" />
|
||||||
<ColumnDefinition Width="60" />
|
<ColumnDefinition Width="60" />
|
||||||
<ColumnDefinition Width="58" />
|
<ColumnDefinition Width="239" />
|
||||||
<ColumnDefinition Width="80" />
|
<ColumnDefinition Width="237" />
|
||||||
<ColumnDefinition Width="85" />
|
<ColumnDefinition Width="67" />
|
||||||
<ColumnDefinition Width="58" />
|
<ColumnDefinition Width="66" />
|
||||||
<ColumnDefinition Width="80" />
|
<ColumnDefinition Width="71" />
|
||||||
<ColumnDefinition Width="85" />
|
<ColumnDefinition Width="93" />
|
||||||
<ColumnDefinition Width="70" />
|
<ColumnDefinition />
|
||||||
<ColumnDefinition Width="70" />
|
</Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="70" />
|
<TextBlock FontWeight="SemiBold" VerticalAlignment="Center" Height="18" ><Run Language="zh-cn" Text="报警、急停"/></TextBlock>
|
||||||
<ColumnDefinition Width="70" />
|
<TextBlock Grid.Column="1" Text="设备ID" Style="{StaticResource FieldLabel}" Margin="8,0,4,0" Height="16" />
|
||||||
|
<TextBox Grid.Column="2" Text="{Binding Devices.Pci2312.DeviceId, UpdateSourceTrigger=PropertyChanged}" FontFamily="{StaticResource FontMono}" TextChanged="DraftChanged" Margin="0,4,0,4" />
|
||||||
|
<Border Grid.Column="3" Margin="8,0,4,0" Padding="8,0"
|
||||||
|
Background="{StaticResource NeutralSoftBrush}" CornerRadius="{StaticResource RadiusSm}">
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="40" />
|
||||||
|
<ColumnDefinition Width="74" />
|
||||||
<ColumnDefinition Width="*" />
|
<ColumnDefinition Width="*" />
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<TextBlock Text="PCI2312" FontWeight="SemiBold" VerticalAlignment="Center" />
|
<TextBlock Text="急停" FontWeight="SemiBold" VerticalAlignment="Center" />
|
||||||
<TextBlock Grid.Column="1" Text="设备ID" Style="{StaticResource FieldLabel}" Margin="8,0,4,0" />
|
<ComboBox Grid.Column="1" ItemsSource="{Binding Pci2312Channels}" SelectedItem="{Binding Devices.Pci2312.EmergencyInputChannel}" SelectionChanged="DraftChanged" />
|
||||||
<TextBox Grid.Column="2" Text="{Binding Devices.Pci2312.DeviceId, UpdateSourceTrigger=PropertyChanged}" FontFamily="{StaticResource FontMono}" TextChanged="DraftChanged" />
|
<CheckBox Grid.Column="2" Margin="8,0,0,0" Content="DI高有效" IsChecked="{Binding Devices.Pci2312.EmergencyInputActiveHigh}" Click="DraftChanged" />
|
||||||
<TextBlock Grid.Column="3" Text="急停DI" Style="{StaticResource FieldLabel}" Margin="8,0,4,0" />
|
</Grid>
|
||||||
<ComboBox Grid.Column="4" ItemsSource="{Binding Pci2312Channels}" SelectedItem="{Binding Devices.Pci2312.EmergencyInputChannel}" SelectionChanged="DraftChanged" />
|
</Border>
|
||||||
<CheckBox Grid.Column="5" Margin="10,0,0,0" Content="DI高有效" IsChecked="{Binding Devices.Pci2312.EmergencyInputActiveHigh}" Click="DraftChanged" />
|
<Border Grid.Column="4" Margin="4,0,4,0" Padding="8,0"
|
||||||
<TextBlock Grid.Column="6" Text="报警DO" Style="{StaticResource FieldLabel}" Margin="8,0,4,0" />
|
Background="{StaticResource NeutralSoftBrush}" CornerRadius="{StaticResource RadiusSm}">
|
||||||
<ComboBox Grid.Column="7" ItemsSource="{Binding Pci2312Channels}" SelectedItem="{Binding Devices.Pci2312.AlarmOutputChannel}" SelectionChanged="DraftChanged" />
|
<Grid>
|
||||||
<CheckBox Grid.Column="8" Margin="10,0,65,0" Content="DO高有效" IsChecked="{Binding Devices.Pci2312.AlarmOutputActiveHigh}" Click="DraftChanged" Grid.ColumnSpan="2" />
|
<Grid.ColumnDefinitions>
|
||||||
<CheckBox Grid.Column="9" Margin="10,0,0,0" Content="启用" IsChecked="{Binding Devices.Pci2312.Enabled}" Click="DraftChanged" />
|
<ColumnDefinition Width="40" />
|
||||||
<CheckBox Grid.Column="10" Margin="10,0,0,0" Content="Mock" IsChecked="{Binding Devices.Pci2312.UseMock}" Click="DraftChanged" />
|
<ColumnDefinition Width="74" />
|
||||||
<CheckBox Grid.Column="11" Margin="10,0,0,0" Content="自连" IsChecked="{Binding Devices.Pci2312.AutoConnect}" Click="DraftChanged" />
|
<ColumnDefinition Width="*" />
|
||||||
<Button Grid.Column="12" Margin="8,0,0,0" Content="测试" Command="{Binding TestPci2312Command}" />
|
</Grid.ColumnDefinitions>
|
||||||
<TextBlock Grid.Column="13" Margin="10,0,0,0" Text="{Binding Pci2312Status}" VerticalAlignment="Center" />
|
<TextBlock Text="报警" FontWeight="SemiBold" VerticalAlignment="Center" />
|
||||||
|
<ComboBox Grid.Column="1" ItemsSource="{Binding Pci2312Channels}" SelectedItem="{Binding Devices.Pci2312.AlarmOutputChannel}" SelectionChanged="DraftChanged" />
|
||||||
|
<CheckBox Grid.Column="2" Margin="8,0,0,0" Content="DO高有效" IsChecked="{Binding Devices.Pci2312.AlarmOutputActiveHigh}" Click="DraftChanged" />
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
<CheckBox Grid.Column="5" Margin="10,0,0,0" Content="启用" IsChecked="{Binding Devices.Pci2312.Enabled}" Click="DraftChanged" />
|
||||||
|
<CheckBox Grid.Column="6" Margin="10,0,0,0" Content="Mock" IsChecked="{Binding Devices.Pci2312.UseMock}" Click="DraftChanged" />
|
||||||
|
<CheckBox Grid.Column="7" Margin="10,0,0,0" Content="自连" IsChecked="{Binding Devices.Pci2312.AutoConnect}" Click="DraftChanged" />
|
||||||
|
<Button Grid.Column="8" Margin="8,4,0,4" Content="测试" Command="{Binding TestPci2312Command}" />
|
||||||
|
<TextBlock Grid.Column="9" Margin="10,0,0,0" Text="{Binding Pci2312Status}" VerticalAlignment="Center" Height="18" />
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
|
|||||||
BIN
SSPCTester.zip
Normal file
BIN
SSPCTester.zip
Normal file
Binary file not shown.
22
publish.ps1
22
publish.ps1
@ -34,6 +34,28 @@ param(
|
|||||||
)
|
)
|
||||||
|
|
||||||
$ErrorActionPreference = 'Stop'
|
$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
|
function Initialize-ConsoleUtf8 {
|
||||||
|
try {
|
||||||
|
if ($IsWindows) {
|
||||||
|
& chcp.com 65001 > $null
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
Write-Warning "无法切换到 UTF-8 代码页,将继续使用当前代码页。"
|
||||||
|
}
|
||||||
|
|
||||||
|
$utf8NoBom = New-Object System.Text.UTF8Encoding($false)
|
||||||
|
[Console]::InputEncoding = $utf8NoBom
|
||||||
|
[Console]::OutputEncoding = $utf8NoBom
|
||||||
|
Set-Variable -Name OutputEncoding -Value $utf8NoBom -Scope Global
|
||||||
|
|
||||||
|
if ([string]::IsNullOrWhiteSpace($env:DOTNET_CLI_UI_LANGUAGE)) {
|
||||||
|
$env:DOTNET_CLI_UI_LANGUAGE = 'zh-Hans'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Initialize-ConsoleUtf8
|
||||||
|
|
||||||
$root = $PSScriptRoot
|
$root = $PSScriptRoot
|
||||||
Set-Location $root
|
Set-Location $root
|
||||||
|
|
||||||
|
|||||||
22
run.ps1
22
run.ps1
@ -19,6 +19,28 @@ param(
|
|||||||
)
|
)
|
||||||
|
|
||||||
$ErrorActionPreference = 'Stop'
|
$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
|
function Initialize-ConsoleUtf8 {
|
||||||
|
try {
|
||||||
|
if ($IsWindows) {
|
||||||
|
& chcp.com 65001 > $null
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
Write-Warning "无法切换到 UTF-8 代码页,将继续使用当前代码页。"
|
||||||
|
}
|
||||||
|
|
||||||
|
$utf8NoBom = New-Object System.Text.UTF8Encoding($false)
|
||||||
|
[Console]::InputEncoding = $utf8NoBom
|
||||||
|
[Console]::OutputEncoding = $utf8NoBom
|
||||||
|
Set-Variable -Name OutputEncoding -Value $utf8NoBom -Scope Global
|
||||||
|
|
||||||
|
if ([string]::IsNullOrWhiteSpace($env:DOTNET_CLI_UI_LANGUAGE)) {
|
||||||
|
$env:DOTNET_CLI_UI_LANGUAGE = 'zh-Hans'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Initialize-ConsoleUtf8
|
||||||
|
|
||||||
$root = $PSScriptRoot
|
$root = $PSScriptRoot
|
||||||
Set-Location $root
|
Set-Location $root
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user