diff --git a/ACP.sln b/ACP.sln
index 34931f0..b55c9d9 100644
--- a/ACP.sln
+++ b/ACP.sln
@@ -45,6 +45,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ACP", "ACP\ACP.csproj", "{C
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TSMasterCAN", "TSMasterCAN\TSMasterCAN.csproj", "{B45DC94C-2CBB-4119-8787-B52A9E7EC58B}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerAdaptabilityModule", "PowerAdaptabilityModule\PowerAdaptabilityModule.csproj", "{CCA6AA94-6105-4C0B-BCA9-D0712B1A7B89}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -131,6 +133,10 @@ Global
{B45DC94C-2CBB-4119-8787-B52A9E7EC58B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B45DC94C-2CBB-4119-8787-B52A9E7EC58B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B45DC94C-2CBB-4119-8787-B52A9E7EC58B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CCA6AA94-6105-4C0B-BCA9-D0712B1A7B89}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CCA6AA94-6105-4C0B-BCA9-D0712B1A7B89}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CCA6AA94-6105-4C0B-BCA9-D0712B1A7B89}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CCA6AA94-6105-4C0B-BCA9-D0712B1A7B89}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -146,5 +152,6 @@ Global
{AF2533DD-599D-49D7-942B-B0C26534D15F} = {1E92D601-68FC-45F6-8251-BD0F39226E5B}
{84006890-9288-44C4-9D17-FA88F3FDECFA} = {1E92D601-68FC-45F6-8251-BD0F39226E5B}
{8923122F-F7D3-4AE7-9E86-9E78F1296A11} = {1E92D601-68FC-45F6-8251-BD0F39226E5B}
+ {CCA6AA94-6105-4C0B-BCA9-D0712B1A7B89} = {1E92D601-68FC-45F6-8251-BD0F39226E5B}
EndGlobalSection
EndGlobal
diff --git a/ACP/ACP.csproj b/ACP/ACP.csproj
index f159891..b8151fe 100644
--- a/ACP/ACP.csproj
+++ b/ACP/ACP.csproj
@@ -28,6 +28,7 @@
+
diff --git a/ACP/ViewModels/ShellViewModel.cs b/ACP/ViewModels/ShellViewModel.cs
index 4746303..00455f3 100644
--- a/ACP/ViewModels/ShellViewModel.cs
+++ b/ACP/ViewModels/ShellViewModel.cs
@@ -902,6 +902,10 @@ namespace ACP.ViewModels
if (_globalInfo.CurrentScope != "default") return;
_regionManager.RequestNavigate("ShellViewManager", "ExportView");
break;
+ case "电源适应性测试":
+ if (_globalInfo.CurrentScope != "default") return;
+ _regionManager.RequestNavigate("ShellViewManager", "PowerAdaptabilityView");
+ break;
}
}
diff --git a/ACP/Views/ShellView.xaml b/ACP/Views/ShellView.xaml
index 3b66b38..736a740 100644
--- a/ACP/Views/ShellView.xaml
+++ b/ACP/Views/ShellView.xaml
@@ -98,6 +98,11 @@
CommandParameter="{Binding Content, RelativeSource={RelativeSource Self}}"
Style="{StaticResource MaterialDesignFlatButton}"
Margin="8" />
+
diff --git a/DeviceCommand/Devices/IOBoard.cs b/DeviceCommand/Devices/IOBoard.cs
index 1a730c3..620616f 100644
--- a/DeviceCommand/Devices/IOBoard.cs
+++ b/DeviceCommand/Devices/IOBoard.cs
@@ -35,6 +35,7 @@ namespace DeviceCommand.Devices
/// 开关状态(true: 吸合/开启, false: 断开/关闭)
public async Task 写输出开关(byte 站号, ushort 开始地址, bool data)
{
+ 确保已连接();
await Modbus.WriteSingleCoilAsync(站号, 开始地址, data);
}
@@ -47,6 +48,7 @@ namespace DeviceCommand.Devices
/// 要写入的开关状态数组,按顺序从开始地址写入
public async Task 批量写输出开关(byte 站号, ushort 开始地址, bool[] datas)
{
+ 确保已连接();
var 批量写入 = await Modbus.ReadCoilsAsync(1, 0, 16);
for (int i = 0; i < datas.Length; i++)
{
@@ -65,6 +67,7 @@ namespace DeviceCommand.Devices
/// 线圈地址(0 起始)
public async Task 读输出开关(byte 站号, ushort 开始地址)
{
+ 确保已连接();
await Modbus.ReadCoilsAsync(站号, 开始地址, 1);
}
@@ -77,7 +80,20 @@ namespace DeviceCommand.Devices
/// 线圈状态数组,true 表示吸合/开启
public async Task 批量读输出开关(byte 站号, ushort 开始地址,ushort 数量)
{
+ 确保已连接();
return await Modbus.ReadCoilsAsync(站号, 开始地址, 数量);
}
+
+ ///
+ /// 确认 Modbus 主站已建立(设备连接成功)。
+ /// 未连接时抛出带明确提示的异常,替代对 null 主站的 NullReferenceException,
+ /// 供上层(测试步骤 / 报警蜂鸣器)捕获并记录。
+ ///
+ /// Modbus 主站尚未建立时抛出
+ private void 确保已连接()
+ {
+ if (Modbus == null)
+ throw new InvalidOperationException("IO 板卡尚未连接(Modbus 主站未建立),无法执行输出控制操作,请确认设备连接状态。");
+ }
}
}
diff --git a/MainModule/ViewModels/AutomatedTestingViewModel.cs b/MainModule/ViewModels/AutomatedTestingViewModel.cs
index 89e23e0..62c448b 100644
--- a/MainModule/ViewModels/AutomatedTestingViewModel.cs
+++ b/MainModule/ViewModels/AutomatedTestingViewModel.cs
@@ -24,6 +24,7 @@ namespace MainModule.ViewModels
private bool IsInitialized =false;
private bool _isRunningErrorSteps = false;
private SubscriptionToken? _alarmToken;
+ private SubscriptionToken? _silenceToken;
#endregion
#region 属性
@@ -87,7 +88,7 @@ namespace MainModule.ViewModels
RefreshCommand = new DelegateCommand(OnRefresh);
BackToProtocolCommand = new DelegateCommand(OnBackToProtocol);
LoadedCommand = new AsyncDelegateCommand(OnLoad);
- _eventAggregator.GetEvent().Subscribe(async (scope) => await SilenceBuzzer(scope));
+ _silenceToken = _eventAggregator.GetEvent().Subscribe(async (scope) => await SilenceBuzzer(scope));
_alarmToken = _eventAggregator.GetEvent().Subscribe(OnAlarmTriggered);
}
@@ -95,21 +96,29 @@ namespace MainModule.ViewModels
{
if (_deviceManager.IOGroup == null) return;
- if (scope == "default")
+ try
{
- for(int i = 1; i <= 8; i++)
+ if (scope == "default")
{
- await _deviceManager.IOGroup.执行台架动作Async(i, 功能动作.蜂鸣器报警, false);
+ for(int i = 1; i <= 8; i++)
+ {
+ await _deviceManager.IOGroup.执行台架动作Async(i, 功能动作.蜂鸣器报警, false);
+ }
+ }
+ else if (scope == TestStatus)
+ {
+ int 台架号 = _systemConfig.SharedParameterList
+ .FirstOrDefault(x => x.ParameterName == "台架序号")?.Value ?? 0;
+ if (台架号 >= 1 && 台架号 <= 8)
+ {
+ await _deviceManager.IOGroup.执行台架动作Async(台架号, 功能动作.蜂鸣器报警, false);
+ }
}
}
- else if (scope == TestStatus)
+ catch (Exception ex)
{
- int 台架号 = _systemConfig.SharedParameterList
- .FirstOrDefault(x => x.ParameterName == "台架序号")?.Value ?? 0;
- if (台架号 >= 1 && 台架号 <= 8)
- {
- await _deviceManager.IOGroup.执行台架动作Async(台架号, 功能动作.蜂鸣器报警, false);
- }
+ // 消音为尽力而为动作:IO 板卡未连接或通信失败时仅记录日志,避免切换页面/点击消音时崩溃
+ LoggerHelper.Error($"[{scope}] 蜂鸣器消音失败:{ex.Message}");
}
}
@@ -127,9 +136,17 @@ namespace MainModule.ViewModels
.FirstOrDefault(x => x.ParameterName == "台架序号")?.Value ?? 0;
if (台架号 < 1 || 台架号 > 8) return;
- // 上下限 / 上下极限 均触发蜂鸣器报警
- await _deviceManager.IOGroup.执行台架动作Async(台架号, 功能动作.蜂鸣器报警, true);
- LoggerHelper.WarnWithNotify(args.Scope, $"[{args.Scope}] 报警触发: {args.Status} ({args.Fingerprint}),蜂鸣器已开启。");
+ try
+ {
+ // 上下限 / 上下极限 均触发蜂鸣器报警
+ await _deviceManager.IOGroup.执行台架动作Async(台架号, 功能动作.蜂鸣器报警, true);
+ LoggerHelper.WarnWithNotify(args.Scope, $"[{args.Scope}] 报警触发: {args.Status} ({args.Fingerprint}),蜂鸣器已开启。");
+ }
+ catch (Exception ex)
+ {
+ // async void 内绝不能抛出异常:蜂鸣器为尽力而为动作,IO 板卡未连接/通信失败时仅记录日志,避免程序崩溃
+ LoggerHelper.Error($"[{args.Scope}] 蜂鸣器报警开启失败:{ex.Message}");
+ }
// 上下极限额外触发异常流程
if (args.Status == "超上极限" || args.Status == "超下极限")
@@ -205,7 +222,8 @@ namespace MainModule.ViewModels
{
if (_alarmToken != null)
_eventAggregator.GetEvent().Unsubscribe(_alarmToken);
- _eventAggregator.GetEvent().Unsubscribe(async (scope) => await SilenceBuzzer(scope));
+ if (_silenceToken != null)
+ _eventAggregator.GetEvent().Unsubscribe(_silenceToken);
// 2. 显式释放硬件资源(防止端口占用/死锁)
if (_deviceManager is IDisposable disposableDevice)
{
diff --git a/MonitorModule/ViewModels/Dialogs/ValueLimitViewModel.cs b/MonitorModule/ViewModels/Dialogs/ValueLimitViewModel.cs
index 1784202..d3520fa 100644
--- a/MonitorModule/ViewModels/Dialogs/ValueLimitViewModel.cs
+++ b/MonitorModule/ViewModels/Dialogs/ValueLimitViewModel.cs
@@ -157,7 +157,9 @@ namespace MonitorModule.ViewModels.Dialogs
.Where(m =>
{
if (m.GetCustomAttribute() == null) return false;
- if (m.ReturnType != typeof(Task)) return false;
+ // 同时支持 Task 和 Task 返回类型
+ var rt = m.ReturnType;
+ if (rt != typeof(Task) && rt != typeof(Task)) return false;
var parms = m.GetParameters();
return parms.Length == 0 ||
(parms.Length == 1 && parms[0].ParameterType == typeof(CancellationToken));
diff --git a/MonitorModule/ViewModels/MonitorViewModel.cs b/MonitorModule/ViewModels/MonitorViewModel.cs
index cb06ebc..410b664 100644
--- a/MonitorModule/ViewModels/MonitorViewModel.cs
+++ b/MonitorModule/ViewModels/MonitorViewModel.cs
@@ -438,7 +438,9 @@ namespace MonitorModule.ViewModels
.Where(m =>
{
if (m.GetCustomAttribute() == null) return false;
- if (m.ReturnType != typeof(Task)) return false;
+ // 同时支持 Task 和 Task 返回类型
+ var rt = m.ReturnType;
+ if (rt != typeof(Task) && rt != typeof(Task)) return false;
var parms = m.GetParameters();
return parms.Length == 0 ||
(parms.Length == 1 && parms[0].ParameterType == typeof(CancellationToken));
diff --git a/MonitorModule/ViewModels/RecordViewModel.cs b/MonitorModule/ViewModels/RecordViewModel.cs
index bfb575f..358b7cc 100644
--- a/MonitorModule/ViewModels/RecordViewModel.cs
+++ b/MonitorModule/ViewModels/RecordViewModel.cs
@@ -233,7 +233,9 @@ namespace MonitorModule.ViewModels
.Where(m =>
{
if (m.GetCustomAttribute() == null) return false;
- if (m.ReturnType != typeof(Task)) return false;
+ // 同时支持 Task 和 Task 返回类型
+ var rt = m.ReturnType;
+ if (rt != typeof(Task) && rt != typeof(Task)) return false;
var parms = m.GetParameters();
return parms.Length == 0 ||
(parms.Length == 1 && parms[0].ParameterType == typeof(CancellationToken));
diff --git a/PowerAdaptabilityModule/PowerAdaptabilityModule.cs b/PowerAdaptabilityModule/PowerAdaptabilityModule.cs
new file mode 100644
index 0000000..c0982fc
--- /dev/null
+++ b/PowerAdaptabilityModule/PowerAdaptabilityModule.cs
@@ -0,0 +1,20 @@
+using PowerAdaptabilityModule.Views;
+
+namespace PowerAdaptabilityModule
+{
+ ///
+ /// 电源适应性测试模块:Chroma61800 交流源三相输出控制、LIST 模式设置与三相数据实时波形展示。
+ /// 通过左侧菜单“电源适应性测试”导航进入(仅 default 作用域可用)。
+ ///
+ public class PowerAdaptabilityModule : IModule
+ {
+ public void OnInitialized(IContainerProvider containerProvider)
+ {
+ }
+
+ public void RegisterTypes(IContainerRegistry containerRegistry)
+ {
+ containerRegistry.RegisterForNavigation("PowerAdaptabilityView");
+ }
+ }
+}
diff --git a/PowerAdaptabilityModule/PowerAdaptabilityModule.csproj b/PowerAdaptabilityModule/PowerAdaptabilityModule.csproj
new file mode 100644
index 0000000..ea0cff6
--- /dev/null
+++ b/PowerAdaptabilityModule/PowerAdaptabilityModule.csproj
@@ -0,0 +1,18 @@
+
+
+
+ net8.0-windows
+ enable
+ true
+ enable
+
+
+
+
+
+
+
+
+
+
+
diff --git a/PowerAdaptabilityModule/ViewModels/PowerAdaptabilityViewModel.cs b/PowerAdaptabilityModule/ViewModels/PowerAdaptabilityViewModel.cs
new file mode 100644
index 0000000..9990695
--- /dev/null
+++ b/PowerAdaptabilityModule/ViewModels/PowerAdaptabilityViewModel.cs
@@ -0,0 +1,637 @@
+using DeviceCommand.Devices;
+using OxyPlot;
+using OxyPlot.Axes;
+using OxyPlot.Legends;
+using OxyPlot.Series;
+using Prism.Commands;
+using Prism.Ioc;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Input;
+using UIShare.GlobalVariable;
+using UIShare.ViewModelBase;
+
+namespace PowerAdaptabilityModule.ViewModels
+{
+ ///
+ /// 电源适应性测试 ViewModel(Chroma61800 交流源一拖三):
+ /// 左侧为设备命令面板(三相输出设置 + LIST 模式),右侧为 A/B/C 三相实时波形图表。
+ ///
+ public class PowerAdaptabilityViewModel : NavigateViewModelBase, IDisposable
+ {
+ #region 常量
+ /// 采样周期(毫秒)
+ private const int SampleIntervalMs = 1000;
+
+ /// 合成正弦波的分段数(0~360°,每 2° 一个点,共 181 点)
+ private const int SinePoints = 180;
+
+ /// 三相相位偏移(°):A=0,B=-120,C=+120
+ private static readonly double[] PhaseOffsetDeg = { 0, -120, 120 };
+
+ /// 三相曲线配色:A 红、B 绿、C 蓝(三张图统一,便于对照)
+ private static readonly OxyColor[] PhaseColors =
+ { OxyColor.FromRgb(0xE5, 0x39, 0x35), OxyColor.FromRgb(0x43, 0xA0, 0x47), OxyColor.FromRgb(0x1E, 0x88, 0xE5) };
+ #endregion
+
+ #region 私有字段
+ private readonly DeviceManager _dm;
+ private readonly GlobalInfo _globalInfo;
+ private Chroma61800? _dev;
+ private CancellationTokenSource? _cmdCts;
+ private CancellationTokenSource? _sampleCts;
+
+ // 三相正弦曲线:按物理量分组(电压 / 电流 / 功率),每组含 A/B/C 三相
+ private readonly LineSeries _voltA, _voltB, _voltC;
+ private readonly LineSeries _currA, _currB, _currC;
+ private readonly LineSeries _powA, _powB, _powC;
+ #endregion
+
+ #region 设备状态属性
+ private string _deviceName = "Chroma61800";
+ public string DeviceName { get => _deviceName; set => SetProperty(ref _deviceName, value); }
+
+ private bool _isConnected;
+ public bool IsConnected { get => _isConnected; set => SetProperty(ref _isConnected, value); }
+
+ private bool _isBusy;
+ public bool IsBusy { get => _isBusy; set => SetProperty(ref _isBusy, value); }
+
+ private bool _isSampling;
+ /// 是否正在采集波形数据
+ public bool IsSampling { get => _isSampling; set => SetProperty(ref _isSampling, value); }
+ #endregion
+
+ #region 三相图表(按物理量分图:电压 / 电流 / 功率,每图叠加 A/B/C 三相正弦)
+ /// 三相电压波形图表
+ public PlotModel VoltagePlot { get; }
+
+ /// 三相电流波形图表
+ public PlotModel CurrentPlot { get; }
+
+ /// 三相功率波形图表
+ public PlotModel PowerPlot { get; }
+ #endregion
+
+ #region 实时测量值
+ private double _voltageA; public double VoltageA { get => _voltageA; set => SetProperty(ref _voltageA, value); }
+ private double _currentA; public double CurrentA { get => _currentA; set => SetProperty(ref _currentA, value); }
+ private double _voltageB; public double VoltageB { get => _voltageB; set => SetProperty(ref _voltageB, value); }
+ private double _currentB; public double CurrentB { get => _currentB; set => SetProperty(ref _currentB, value); }
+ private double _voltageC; public double VoltageC { get => _voltageC; set => SetProperty(ref _voltageC, value); }
+ private double _currentC; public double CurrentC { get => _currentC; set => SetProperty(ref _currentC, value); }
+
+ private double _frequencyNow;
+ /// 当前输出频率 (Hz)
+ public double FrequencyNow { get => _frequencyNow; set => SetProperty(ref _frequencyNow, value); }
+
+ private double _powerNow;
+ /// 当前真实输出功率 (W)
+ public double PowerNow { get => _powerNow; set => SetProperty(ref _powerNow, value); }
+ #endregion
+
+ #region 输入参数(三相输出设置)
+ private double _voltage = 220;
+ /// 待设置的交流电压 (V)
+ public double Voltage { get => _voltage; set => SetProperty(ref _voltage, value); }
+
+ private double _frequency = 50;
+ /// 待设置的输出频率 (Hz)
+ public double Frequency { get => _frequency; set => SetProperty(ref _frequency, value); }
+
+ private string _waveform = "SINE";
+ /// 波形选择
+ public string Waveform { get => _waveform; set => SetProperty(ref _waveform, value); }
+
+ private string _couplingMode = "AC";
+ /// 输出耦合模式 (AC / DC / ACDC)
+ public string CouplingMode { get => _couplingMode; set => SetProperty(ref _couplingMode, value); }
+
+ private string _threePhaseRelation = "BALANCE";
+ /// 三相关系 (INDEPEND / SAMEFREQ / BALANCE)
+ public string ThreePhaseRelation { get => _threePhaseRelation; set => SetProperty(ref _threePhaseRelation, value); }
+ #endregion
+
+ #region 输入参数(LIST 模式)
+ private string _listCoupling = "NONE";
+ /// 列表耦合模态 (ALL / NONE)
+ public string ListCoupling { get => _listCoupling; set => SetProperty(ref _listCoupling, value); }
+
+ private string _listTrigger = "AUTO";
+ /// 列表触发形态 (AUTO / MANUAL / EXCITE)
+ public string ListTrigger { get => _listTrigger; set => SetProperty(ref _listTrigger, value); }
+
+ private int _listCount = 1;
+ /// 列表执行次数 (0 ~ 65535)
+ public int ListCount { get => _listCount; set => SetProperty(ref _listCount, value); }
+
+ private string _listTimeBase = "TIME";
+ /// 列表时间基础 (TIME / CYCLE)
+ public string ListTimeBase { get => _listTimeBase; set => SetProperty(ref _listTimeBase, value); }
+
+ private string _listVoltAcStartText = "";
+ /// AC 起始电压序列(逗号分隔,如 0,50,100,150)
+ public string ListVoltAcStartText { get => _listVoltAcStartText; set => SetProperty(ref _listVoltAcStartText, value); }
+
+ private string _listVoltAcEndText = "";
+ /// AC 结束电压序列(逗号分隔)
+ public string ListVoltAcEndText { get => _listVoltAcEndText; set => SetProperty(ref _listVoltAcEndText, value); }
+
+ private string _listFreqStartText = "";
+ /// 起始频率序列(逗号分隔,如 50,60,50)
+ public string ListFreqStartText { get => _listFreqStartText; set => SetProperty(ref _listFreqStartText, value); }
+
+ private string _listFreqEndText = "";
+ /// 结束频率序列(逗号分隔)
+ public string ListFreqEndText { get => _listFreqEndText; set => SetProperty(ref _listFreqEndText, value); }
+
+ private string _listDwellText = "";
+ /// 静止时间序列(ms,逗号分隔,如 500,500,500)
+ public string ListDwellText { get => _listDwellText; set => SetProperty(ref _listDwellText, value); }
+
+ private string _listDegrText = "";
+ /// 相位角度序列(°,逗号分隔)
+ public string ListDegrText { get => _listDegrText; set => SetProperty(ref _listDegrText, value); }
+
+ private string _listShapeText = "";
+ /// 波形缓冲顺序(A/B,逗号分隔,如 A,B,A)
+ public string ListShapeText { get => _listShapeText; set => SetProperty(ref _listShapeText, value); }
+ #endregion
+
+ #region 下拉选项
+ public IReadOnlyList WaveformOptions { get; } = new[] { "SINE", "SQUA", "CSIN" };
+ public IReadOnlyList CouplingOptions { get; } = new[] { "AC", "DC", "ACDC" };
+ public IReadOnlyList RelationOptions { get; } = new[] { "INDEPEND", "SAMEFREQ", "BALANCE" };
+ public IReadOnlyList ListCouplingOptions { get; } = new[] { "ALL", "NONE" };
+ public IReadOnlyList ListTriggerOptions { get; } = new[] { "AUTO", "MANUAL", "EXCITE" };
+ public IReadOnlyList ListTimeBaseOptions { get; } = new[] { "TIME", "CYCLE" };
+ #endregion
+
+ #region 日志
+ private string _responseLog = "";
+ /// 操作与采样日志
+ public string ResponseLog { get => _responseLog; set => SetProperty(ref _responseLog, value); }
+ #endregion
+
+ #region 命令
+ public ICommand QueryIdnCommand { get; }
+ public ICommand ResetCommand { get; }
+ public ICommand OutputOnCommand { get; }
+ public ICommand OutputOffCommand { get; }
+ public ICommand RelayOnCommand { get; }
+ public ICommand RelayOffCommand { get; }
+ public ICommand ApplyCouplingCommand { get; }
+ public ICommand SetThreePhaseModeCommand { get; }
+ public ICommand SetSinglePhaseModeCommand { get; }
+ public ICommand ApplyRelationCommand { get; }
+ public ICommand SetVoltageCommand { get; }
+ public ICommand SetFrequencyCommand { get; }
+ public ICommand SetWaveformCommand { get; }
+ public ICommand SetFixedModeCommand { get; }
+ public ICommand EnterListModeCommand { get; }
+ public ICommand ApplyListBasicCommand { get; }
+ public ICommand ApplyListSequencesCommand { get; }
+ public ICommand ListStartCommand { get; }
+ public ICommand ListStopCommand { get; }
+ public ICommand StartSamplingCommand { get; }
+ public ICommand StopSamplingCommand { get; }
+ public ICommand ClearChartsCommand { get; }
+ #endregion
+
+ public PowerAdaptabilityViewModel(IContainerProvider containerProvider) : base(containerProvider)
+ {
+ _dm = containerProvider.Resolve();
+ _globalInfo = containerProvider.Resolve();
+
+ // 初始化三相图表(按物理量分图,每图叠加 A/B/C 三相正弦)
+ VoltagePlot = BuildSinePlot("三相电压", "V");
+ CurrentPlot = BuildSinePlot("三相电流", "A");
+ PowerPlot = BuildSinePlot("三相功率", "W");
+ (_voltA, _voltB, _voltC) = AddThreePhaseSeries(VoltagePlot);
+ (_currA, _currB, _currC) = AddThreePhaseSeries(CurrentPlot);
+ (_powA, _powB, _powC) = AddThreePhaseSeries(PowerPlot);
+
+ #region 命令初始化
+ QueryIdnCommand = new DelegateCommand(async () =>
+ await Exec(async () => Log("IDN: " + await _dev!.查询设备信息Async(Ct()))));
+
+ ResetCommand = new DelegateCommand(async () =>
+ await Exec(async () => { await _dev!.复位Async(Ct()); Log("设备已复位"); }));
+
+ OutputOnCommand = new DelegateCommand(async () =>
+ await Exec(async () => { await _dev!.设置输出开关Async(true, Ct()); Log("输出已开启"); }));
+
+ OutputOffCommand = new DelegateCommand(async () =>
+ await Exec(async () => { await _dev!.设置输出开关Async(false, Ct()); Log("输出已关闭"); }));
+
+ RelayOnCommand = new DelegateCommand(async () =>
+ await Exec(async () => { await _dev!.设置输出继电器Async(true, Ct()); Log("输出继电器已开启"); }));
+
+ RelayOffCommand = new DelegateCommand(async () =>
+ await Exec(async () => { await _dev!.设置输出继电器Async(false, Ct()); Log("输出继电器已关闭"); }));
+
+ ApplyCouplingCommand = new DelegateCommand(async () =>
+ await Exec(async () => { await _dev!.设置耦合模式Async(CouplingMode, Ct()); Log($"耦合模式={CouplingMode}"); }));
+
+ SetThreePhaseModeCommand = new DelegateCommand(async () =>
+ await Exec(async () => { await _dev!.设置单三相模式Async(true, Ct()); Log("已切换为三相模式"); }));
+
+ SetSinglePhaseModeCommand = new DelegateCommand(async () =>
+ await Exec(async () => { await _dev!.设置单三相模式Async(false, Ct()); Log("已切换为单相模式"); }));
+
+ ApplyRelationCommand = new DelegateCommand(async () =>
+ await Exec(async () => { await _dev!.设置三相关系Async(ThreePhaseRelation, Ct()); Log($"三相关系={ThreePhaseRelation}"); }));
+
+ SetVoltageCommand = new DelegateCommand(async () =>
+ await Exec(async () => { await _dev!.设置交流电压Async(Voltage, Ct()); Log($"交流电压={Voltage} V"); }));
+
+ SetFrequencyCommand = new DelegateCommand(async () =>
+ await Exec(async () => { await _dev!.设置频率Async(Frequency, Ct()); Log($"输出频率={Frequency} Hz"); }));
+
+ SetWaveformCommand = new DelegateCommand(async () =>
+ await Exec(async () => { await _dev!.设置波形Async(Waveform, Ct()); Log($"波形={Waveform}"); }));
+
+ SetFixedModeCommand = new DelegateCommand(async () =>
+ await Exec(async () => { await _dev!.设置操作模态Async("FIXED", Ct()); Log("已切换为 FIXED 固定模式"); }));
+
+ EnterListModeCommand = new DelegateCommand(async () =>
+ await Exec(async () => { await _dev!.设置操作模态Async("LIST", Ct()); Log("已切换为 LIST 模式"); }));
+
+ ApplyListBasicCommand = new DelegateCommand(async () => await Exec(async () =>
+ {
+ await _dev!.设置列表耦合模态Async(ListCoupling, Ct());
+ await _dev!.设置列表触发形态Async(ListTrigger, Ct());
+ await _dev!.设置列表执行次数Async(ListCount, Ct());
+ await _dev!.设置列表时间基础Async(ListTimeBase, Ct());
+ Log($"LIST 基础配置:耦合={ListCoupling} 触发={ListTrigger} 次数={ListCount} 时基={ListTimeBase}");
+ }));
+
+ ApplyListSequencesCommand = new DelegateCommand(async () => await Exec(async () =>
+ {
+ int applied = 0;
+ var vAcStart = ParseDoubles(ListVoltAcStartText);
+ if (vAcStart.Length > 0) { await _dev!.设置列表AC起始电压顺序Async(vAcStart, Ct()); applied++; }
+ var vAcEnd = ParseDoubles(ListVoltAcEndText);
+ if (vAcEnd.Length > 0) { await _dev!.设置列表AC结束电压顺序Async(vAcEnd, Ct()); applied++; }
+ var fStart = ParseDoubles(ListFreqStartText);
+ if (fStart.Length > 0) { await _dev!.设置列表起始频率顺序Async(fStart, Ct()); applied++; }
+ var fEnd = ParseDoubles(ListFreqEndText);
+ if (fEnd.Length > 0) { await _dev!.设置列表结束频率顺序Async(fEnd, Ct()); applied++; }
+ var dwell = ParseDoubles(ListDwellText);
+ if (dwell.Length > 0) { await _dev!.设置列表静止时间顺序Async(dwell, Ct()); applied++; }
+ var degr = ParseDoubles(ListDegrText);
+ if (degr.Length > 0) { await _dev!.设置列表相位角度顺序Async(degr, Ct()); applied++; }
+ var shapes = ParseShapes(ListShapeText);
+ if (shapes.Length > 0) { await _dev!.设置列表波形顺序Async(shapes, Ct()); applied++; }
+ Log(applied > 0 ? $"已下发 {applied} 组列表序列" : "没有可下发的列表序列(请先填写序列文本框)");
+ }));
+
+ ListStartCommand = new DelegateCommand(async () => await Exec(async () =>
+ {
+ await _dev!.设置操作模态Async("LIST", Ct());
+ await _dev!.触发列表执行Async(true, Ct());
+ Log("LIST 已开始执行");
+ }));
+
+ ListStopCommand = new DelegateCommand(async () =>
+ await Exec(async () => { await _dev!.触发列表执行Async(false, Ct()); Log("LIST 已停止执行"); }));
+
+ StartSamplingCommand = new DelegateCommand(StartSampling);
+ StopSamplingCommand = new DelegateCommand(StopSampling);
+ ClearChartsCommand = new DelegateCommand(ClearCharts);
+ #endregion
+
+ Initialize();
+ }
+
+ #region 初始化 / 导航
+ ///
+ /// 查找 Chroma61800 设备实例:优先当前作用域 DeviceManager,回退全局 HardwarePool(跨作用域共享实例)。
+ ///
+ private void Initialize()
+ {
+ Chroma61800? found = null;
+ string? fn = null;
+
+ foreach (var kv in _dm.DeviceMap)
+ {
+ if (kv.Value is Chroma61800 it) { found = it; fn = kv.Key; break; }
+ }
+
+ if (found == null)
+ {
+ foreach (var kv in _globalInfo.HardwarePool)
+ {
+ if (kv.Value.IsValueCreated && kv.Value.Value is Chroma61800 c) { found = c; fn = kv.Key; break; }
+ }
+ }
+
+ _dev = found;
+ DeviceName = fn ?? "Chroma61800 (未找到)";
+ IsConnected = _dev?.IsConnected ?? false;
+
+ Log(found != null
+ ? $"已关联设备 [{DeviceName}],连接:{(IsConnected ? "已连接" : "未连接")}"
+ : "未找到 Chroma61800 设备,请先在设置界面添加并启用该设备");
+ }
+
+ public override void OnNavigatedTo(NavigationContext navigationContext)
+ {
+ Initialize();
+ }
+
+ public override void OnNavigatedFrom(NavigationContext navigationContext)
+ {
+ // 离开页面时停止采样,避免后台持续占用设备通信
+ StopSampling();
+ }
+ #endregion
+
+ #region 数据采集
+ private void StartSampling()
+ {
+ if (_dev == null) { Log("未关联 Chroma61800 设备,无法采集"); return; }
+ if (IsSampling) return;
+
+ IsSampling = true;
+ _sampleCts = new CancellationTokenSource();
+ var token = _sampleCts.Token;
+ _ = Task.Run(() => SamplingLoopAsync(token), token);
+ Log($"开始采集(周期 {SampleIntervalMs} ms)");
+ }
+
+ private void StopSampling()
+ {
+ if (!IsSampling) return;
+ IsSampling = false;
+ try { _sampleCts?.Cancel(); } catch { }
+ _sampleCts?.Dispose();
+ _sampleCts = null;
+ Log("停止采集");
+ }
+
+ ///
+ /// 采样循环:逐相读取交流电压/电流,合成波形;并刷新频率与功率实时值。
+ /// 说明:Chroma61800 类现有测量方法面向"活动相位",三相逐一调用读取;
+ /// 如设备需要按相切换后读取,可在每相读取前补充相位选择命令。
+ ///
+ private async Task SamplingLoopAsync(CancellationToken token)
+ {
+ while (!token.IsCancellationRequested)
+ {
+ try
+ {
+ if (_dev != null && _dev.IsConnected)
+ {
+ // A 相
+ double vA = await _dev.读取交流电压Async(token);
+ double iA = await _dev.读取交流电流Async(token);
+
+ // B 相
+ double vB = await _dev.读取交流电压Async(token);
+ double iB = await _dev.读取交流电流Async(token);
+
+ // C 相
+ double vC = await _dev.读取交流电压Async(token);
+ double iC = await _dev.读取交流电流Async(token);
+
+ // 设备级:频率与功率
+ double freq = await _dev.读取频率Async(token);
+ double power = await _dev.读取真实功率Async(token);
+
+ var dispatcher = Application.Current?.Dispatcher;
+ if (dispatcher != null)
+ {
+ _ = dispatcher.BeginInvoke(new Action(() =>
+ {
+ RegenerateWaveforms(vA, iA, vB, iB, vC, iC, freq, power);
+ IsConnected = _dev.IsConnected;
+ }));
+ }
+ }
+ }
+ catch (OperationCanceledException) { break; }
+ catch (Exception ex)
+ {
+ Log($"采样异常:{ex.Message}");
+ }
+
+ try { await Task.Delay(SampleIntervalMs, token); }
+ catch (OperationCanceledException) { break; }
+ }
+ }
+
+ ///
+ /// 根据实测 RMS 值合成三相正弦波并刷新三张图表(UI 线程调用)。
+ /// 横轴为相位角 0~360°,纵轴对称显示正负半周;电压/电流峰值 = √2 × RMS,
+ /// 三相相位互差 120°(A=0,B=-120,C=+120)。设备仅提供 RMS 标量,故为示意波形。
+ ///
+ private void RegenerateWaveforms(double vA, double iA, double vB, double iB,
+ double vC, double iC, double freq, double power)
+ {
+ // 电压三相
+ RegenerateSine(_voltA, vA, PhaseOffsetDeg[0]);
+ RegenerateSine(_voltB, vB, PhaseOffsetDeg[1]);
+ RegenerateSine(_voltC, vC, PhaseOffsetDeg[2]);
+ // 电流三相
+ RegenerateSine(_currA, iA, PhaseOffsetDeg[0]);
+ RegenerateSine(_currB, iB, PhaseOffsetDeg[1]);
+ RegenerateSine(_currC, iC, PhaseOffsetDeg[2]);
+ // 功率三相(设备仅提供总有功,平衡三相每相 ≈ 总功率 / 3,按正弦示意呈现正负半周)
+ double pPhase = power / 3.0;
+ RegenerateSine(_powA, pPhase, PhaseOffsetDeg[0]);
+ RegenerateSine(_powB, pPhase, PhaseOffsetDeg[1]);
+ RegenerateSine(_powC, pPhase, PhaseOffsetDeg[2]);
+
+ // 实时数值
+ VoltageA = vA; CurrentA = iA;
+ VoltageB = vB; CurrentB = iB;
+ VoltageC = vC; CurrentC = iC;
+ FrequencyNow = freq; PowerNow = power;
+
+ // 纵轴对称范围(取该图三相峰值最大值并留 15% 余量,确保正负半周完整可见)
+ SetSymmetricAxis(VoltagePlot, vA, vB, vC);
+ SetSymmetricAxis(CurrentPlot, iA, iB, iC);
+ SetSymmetricAxis(PowerPlot, pPhase);
+
+ VoltagePlot.InvalidatePlot(true);
+ CurrentPlot.InvalidatePlot(true);
+ PowerPlot.InvalidatePlot(true);
+ }
+
+ /// 按 RMS 幅值与相位偏移,生成 0~360° 一个完整周期的正弦曲线点(峰值 = √2 × RMS)。
+ private static void RegenerateSine(LineSeries series, double rms, double phaseDeg)
+ {
+ series.Points.Clear();
+ double peak = Math.Sqrt(2) * rms;
+ double phaseRad = phaseDeg * Math.PI / 180.0;
+ for (int i = 0; i <= SinePoints; i++)
+ {
+ double deg = 360.0 * i / SinePoints;
+ double rad = deg * Math.PI / 180.0 + phaseRad;
+ series.Points.Add(new DataPoint(deg, peak * Math.Sin(rad)));
+ }
+ }
+
+ /// 设置纵轴对称范围(-peak ~ +peak);无输出时回退到 ±1 避免坐标轴退化。
+ private static void SetSymmetricAxis(PlotModel plot, params double[] rmsValues)
+ {
+ double maxRms = rmsValues.Max();
+ double peak = Math.Sqrt(2) * maxRms * 1.15;
+ if (peak <= 0) peak = 1;
+ if (plot.Axes.OfType().FirstOrDefault() is { } axis)
+ {
+ axis.Minimum = -peak;
+ axis.Maximum = peak;
+ }
+ }
+
+ private void ClearCharts()
+ {
+ foreach (var s in new[] { _voltA, _voltB, _voltC, _currA, _currB, _currC, _powA, _powB, _powC })
+ {
+ s.Points.Clear();
+ }
+ VoltagePlot.InvalidatePlot(true);
+ CurrentPlot.InvalidatePlot(true);
+ PowerPlot.InvalidatePlot(true);
+ Log("已清空波形数据");
+ }
+ #endregion
+
+ #region 图表构建
+ ///
+ /// 构建正弦波图表:横轴为相位角 0~360°,纵轴为对应物理量(对称显示正负半周),含图例。
+ ///
+ /// 图表标题(如“三相电压”)
+ /// 纵轴单位(V / A / W)
+ private static PlotModel BuildSinePlot(string title, string unit)
+ {
+ var pm = new PlotModel
+ {
+ Title = $"{title} ({unit})",
+ PlotAreaBorderColor = OxyColors.LightGray,
+ Background = OxyColors.White
+ };
+ pm.Axes.Add(new LinearAxis
+ {
+ Position = AxisPosition.Bottom,
+ Title = "相位角 (°)",
+ Minimum = 0,
+ Maximum = 360,
+ MajorStep = 45,
+ MinorStep = 15,
+ StringFormat = "0",
+ MajorGridlineStyle = LineStyle.Dot,
+ MinorGridlineStyle = LineStyle.None
+ });
+ pm.Axes.Add(new LinearAxis
+ {
+ Position = AxisPosition.Left,
+ Title = unit,
+ Minimum = -1,
+ Maximum = 1,
+ MajorGridlineStyle = LineStyle.Dot,
+ MinorGridlineStyle = LineStyle.None
+ });
+ pm.Legends.Add(new Legend
+ {
+ LegendPosition = LegendPosition.RightTop,
+ LegendBackground = OxyColor.FromAColor(200, OxyColors.White),
+ LegendBorder = OxyColors.LightGray
+ });
+ return pm;
+ }
+
+ /// 向图表添加 A/B/C 三相正弦曲线(红/绿/蓝,与三张图配色一致)。
+ private static (LineSeries A, LineSeries B, LineSeries C) AddThreePhaseSeries(PlotModel plot)
+ {
+ var a = NewPhaseSeries("A 相", PhaseColors[0]);
+ var b = NewPhaseSeries("B 相", PhaseColors[1]);
+ var c = NewPhaseSeries("C 相", PhaseColors[2]);
+ plot.Series.Add(a);
+ plot.Series.Add(b);
+ plot.Series.Add(c);
+ return (a, b, c);
+ }
+
+ /// 创建一条相曲线(无标记点、平滑线条)。
+ private static LineSeries NewPhaseSeries(string title, OxyColor color) => new LineSeries
+ {
+ Title = title,
+ Color = color,
+ StrokeThickness = 1.8,
+ MarkerType = MarkerType.None
+ };
+ #endregion
+
+ #region 辅助
+ /// 创建 10 秒超时的取消令牌(命令用)。
+ private CancellationToken Ct() => (_cmdCts = new CancellationTokenSource(TimeSpan.FromSeconds(10))).Token;
+
+ /// 统一的设备命令执行包装:防重入、异常捕获与日志。
+ private async Task Exec(Func action)
+ {
+ if (_dev == null) { Log("错误:未找到 Chroma61800 设备实例,请检查设备配置"); return; }
+ if (IsBusy) return;
+
+ IsBusy = true;
+ try
+ {
+ await action();
+ IsConnected = _dev.IsConnected;
+ }
+ catch (OperationCanceledException) { Log("命令超时或已取消"); }
+ catch (Exception ex) { Log($"错误:{ex.Message}"); }
+ finally { IsBusy = false; }
+ }
+
+ /// 线程安全的日志输出(自动切回 UI 线程,保留最近内容)。
+ private void Log(string message)
+ {
+ var dispatcher = Application.Current?.Dispatcher;
+ if (dispatcher != null && !dispatcher.CheckAccess())
+ {
+ _ = dispatcher.BeginInvoke(new Action(() => Log(message)));
+ return;
+ }
+
+ var line = $"[{DateTime.Now:HH:mm:ss}] {message}";
+ ResponseLog = ResponseLog.Length > 6000
+ ? line + "\n" + ResponseLog[..4000]
+ : line + "\n" + ResponseLog;
+ }
+
+ /// 解析逗号/空格/分号分隔的数值序列。
+ private static double[] ParseDoubles(string text)
+ {
+ if (string.IsNullOrWhiteSpace(text)) return Array.Empty();
+ return text.Split(new[] { ',', ',', ' ', '\t', ';' }, StringSplitOptions.RemoveEmptyEntries)
+ .Select(s => double.TryParse(s.Trim(), out double v) ? (double?)v : null)
+ .Where(v => v.HasValue)
+ .Select(v => v!.Value)
+ .ToArray();
+ }
+
+ /// 解析波形缓冲顺序(A/B)序列。
+ private static string[] ParseShapes(string text)
+ {
+ if (string.IsNullOrWhiteSpace(text)) return Array.Empty();
+ return text.Split(new[] { ',', ',', ' ', '\t', ';' }, StringSplitOptions.RemoveEmptyEntries)
+ .Select(s => s.Trim().ToUpperInvariant())
+ .ToArray();
+ }
+ #endregion
+
+ public void Dispose()
+ {
+ StopSampling();
+ try { _cmdCts?.Cancel(); } catch { }
+ _cmdCts?.Dispose();
+ }
+ }
+}
diff --git a/PowerAdaptabilityModule/Views/PowerAdaptabilityView.xaml b/PowerAdaptabilityModule/Views/PowerAdaptabilityView.xaml
new file mode 100644
index 0000000..6400633
--- /dev/null
+++ b/PowerAdaptabilityModule/Views/PowerAdaptabilityView.xaml
@@ -0,0 +1,410 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/PowerAdaptabilityModule/Views/PowerAdaptabilityView.xaml.cs b/PowerAdaptabilityModule/Views/PowerAdaptabilityView.xaml.cs
new file mode 100644
index 0000000..8308cf5
--- /dev/null
+++ b/PowerAdaptabilityModule/Views/PowerAdaptabilityView.xaml.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Navigation;
+using System.Windows.Shapes;
+
+namespace PowerAdaptabilityModule.Views
+{
+ ///
+ /// PowerAdaptabilityView.xaml 的交互逻辑
+ ///
+ public partial class PowerAdaptabilityView : UserControl
+ {
+ public PowerAdaptabilityView()
+ {
+ InitializeComponent();
+ }
+ }
+}
diff --git a/UIShare/GlobalVariable/HardwareDataBroadcaster.cs b/UIShare/GlobalVariable/HardwareDataBroadcaster.cs
index 2380403..12a836c 100644
--- a/UIShare/GlobalVariable/HardwareDataBroadcaster.cs
+++ b/UIShare/GlobalVariable/HardwareDataBroadcaster.cs
@@ -33,12 +33,12 @@ namespace UIShare.GlobalVariable
///
/// 已注册的采样项:(Fingerprint, MethodName, 编译后的委托, DeviceInstance)
- /// 委托签名统一为 Func<object, CancellationToken, Task<string>>
+ /// 委托签名统一为 Func<object, CancellationToken, Task<double>>
/// - 第一个参数 = 设备实例(open delegate 风格,编译时已做类型转换)
/// - 第二个参数 = CancellationToken(无参方法会忽略它)
- /// - 返回值 = Task<string>
+ /// - 返回值 = Task<double>(Task<string> 方法自动 TryParse 为 double)
///
- private readonly List<(string Fingerprint, string MethodName, Func