2026-07-01 20:04:27 +08:00
|
|
|
|
using System.Collections.ObjectModel;
|
|
|
|
|
|
using System.IO;
|
|
|
|
|
|
using System.IO.Ports;
|
|
|
|
|
|
using System.Text.Json;
|
|
|
|
|
|
using System.Windows;
|
|
|
|
|
|
using CommunityToolkit.Mvvm.ComponentModel;
|
|
|
|
|
|
using CommunityToolkit.Mvvm.Input;
|
|
|
|
|
|
using Microsoft.Extensions.Options;
|
|
|
|
|
|
using Microsoft.Win32;
|
|
|
|
|
|
using SSPCTester.Devices.Config;
|
|
|
|
|
|
using SSPCTester.Devices.Interfaces;
|
2026-07-08 00:25:45 +08:00
|
|
|
|
using SSPCTester.Logic.Storage;
|
2026-07-01 20:04:27 +08:00
|
|
|
|
using SSPCTester.UI.Services;
|
|
|
|
|
|
|
|
|
|
|
|
namespace SSPCTester.UI.ViewModels;
|
|
|
|
|
|
|
|
|
|
|
|
public partial class SettingsViewModel : ObservableObject
|
|
|
|
|
|
{
|
|
|
|
|
|
private readonly UiLogSink _log;
|
|
|
|
|
|
private readonly ISspc _sspc;
|
|
|
|
|
|
private readonly IPowerSupply _power;
|
|
|
|
|
|
private readonly ILoad _load;
|
|
|
|
|
|
private readonly IDaq _daq;
|
|
|
|
|
|
private readonly ISafetyDio _safetyDio;
|
|
|
|
|
|
private readonly DeviceOptions _runtimeDevices;
|
|
|
|
|
|
private readonly StorageOptions _runtimeStorage;
|
|
|
|
|
|
private readonly ReportOptions _runtimeReport;
|
|
|
|
|
|
private readonly UserSettingsStore _settingsStore;
|
2026-07-08 00:25:45 +08:00
|
|
|
|
private readonly ProjectStore _projectStore;
|
2026-07-01 20:04:27 +08:00
|
|
|
|
private Action? _closeAction;
|
2026-07-08 00:25:45 +08:00
|
|
|
|
private Func<bool>? _canRefreshProjects;
|
|
|
|
|
|
private Func<string, Task>? _projectRefreshAction;
|
2026-07-01 20:04:27 +08:00
|
|
|
|
|
|
|
|
|
|
public SettingsViewModel(
|
|
|
|
|
|
IOptions<DeviceOptions> devOpts,
|
|
|
|
|
|
IOptions<StorageOptions> storeOpts,
|
|
|
|
|
|
IOptions<ReportOptions> reportOpts,
|
|
|
|
|
|
ISspc sspc, IPowerSupply power, ILoad load, IDaq daq, ISafetyDio safetyDio, UiLogSink log,
|
2026-07-08 00:25:45 +08:00
|
|
|
|
UserSettingsStore settingsStore,
|
|
|
|
|
|
ProjectStore projectStore)
|
2026-07-01 20:04:27 +08:00
|
|
|
|
{
|
|
|
|
|
|
_runtimeDevices = devOpts.Value;
|
|
|
|
|
|
_runtimeStorage = storeOpts.Value;
|
|
|
|
|
|
_runtimeReport = reportOpts.Value;
|
|
|
|
|
|
_sspc = sspc;
|
|
|
|
|
|
_power = power;
|
|
|
|
|
|
_load = load;
|
|
|
|
|
|
_daq = daq;
|
|
|
|
|
|
_safetyDio = safetyDio;
|
|
|
|
|
|
_log = log;
|
|
|
|
|
|
_settingsStore = settingsStore;
|
2026-07-08 00:25:45 +08:00
|
|
|
|
_projectStore = projectStore;
|
2026-07-01 20:04:27 +08:00
|
|
|
|
|
|
|
|
|
|
Devices = CloneDeviceOptions(_runtimeDevices);
|
|
|
|
|
|
Storage = CloneStorageOptions(_runtimeStorage);
|
|
|
|
|
|
Report = CloneReportOptions(_runtimeReport);
|
|
|
|
|
|
Devices.Daq.Channels = DaqChannelOptionsNormalizer.Normalize(Devices.Daq.Channels);
|
|
|
|
|
|
|
|
|
|
|
|
LoadDaqChannelEditors();
|
|
|
|
|
|
RefreshPorts();
|
|
|
|
|
|
_sspc.ConnectionChanged += (_, _) => OnPropertyChanged(nameof(SspcStatus));
|
|
|
|
|
|
_power.ConnectionChanged += (_, _) => OnPropertyChanged(nameof(PowerStatus));
|
|
|
|
|
|
_load.ConnectionChanged += (_, _) => OnPropertyChanged(nameof(LoadStatus));
|
|
|
|
|
|
_daq.ConnectionChanged += (_, _) => OnPropertyChanged(nameof(DaqStatus));
|
|
|
|
|
|
_safetyDio.ConnectionChanged += (_, _) => OnPropertyChanged(nameof(Pci2312Status));
|
|
|
|
|
|
_ = RefreshDaqDevices();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public DeviceOptions Devices { get; }
|
|
|
|
|
|
public StorageOptions Storage { get; }
|
|
|
|
|
|
public ReportOptions Report { get; }
|
|
|
|
|
|
public ObservableCollection<string> PortNames { get; } = new();
|
|
|
|
|
|
public ObservableCollection<DaqDeviceDescriptor> DaqDevices { get; } = new();
|
|
|
|
|
|
public ObservableCollection<DaqChannelEditor> DaqChannelEditors { get; } = new();
|
|
|
|
|
|
public IReadOnlyList<int> DaqChannelCounts { get; } = new[] { 1, 2, 4, 8 };
|
|
|
|
|
|
public IReadOnlyList<int> Pci2312Channels { get; } = Enumerable.Range(0, 16).ToArray();
|
|
|
|
|
|
public IReadOnlyList<string> DaqInputRanges { get; } = new[] { "PlusMinus5V", "PlusMinus1V" };
|
|
|
|
|
|
public IReadOnlyList<int> DaqClockDividerOptions { get; } = new[] { 1, 2, 5, 10, 20, 50, 100, 1_000, 10_000, 100_000 };
|
|
|
|
|
|
public IReadOnlyList<DaqRoleOption> DaqRoleOptions { get; } = new[]
|
|
|
|
|
|
{
|
|
|
|
|
|
new DaqRoleOption(nameof(DaqChannelRole.Unconfigured), "未配置"),
|
|
|
|
|
|
new DaqRoleOption(nameof(DaqChannelRole.OutputVoltage), "输出电压"),
|
|
|
|
|
|
new DaqRoleOption(nameof(DaqChannelRole.OutputCurrent), "输出电流"),
|
|
|
|
|
|
new DaqRoleOption(nameof(DaqChannelRole.InputVoltage), "输入电压"),
|
|
|
|
|
|
new DaqRoleOption(nameof(DaqChannelRole.InputCurrent), "输入电流"),
|
|
|
|
|
|
new DaqRoleOption(nameof(DaqChannelRole.Auxiliary), "辅助")
|
|
|
|
|
|
};
|
|
|
|
|
|
public IReadOnlyList<LoadQueryMode> LoadQueryModes { get; } = Enum.GetValues<LoadQueryMode>();
|
|
|
|
|
|
public IReadOnlyList<string> ReportFormatOptions { get; } = new[] { "Pdf", "Word", "Both" };
|
|
|
|
|
|
public IReadOnlyList<TerminatorOption> TerminatorOptions { get; } = new[]
|
|
|
|
|
|
{
|
|
|
|
|
|
new TerminatorOption("LF (\\n)", "\n"),
|
|
|
|
|
|
new TerminatorOption("CRLF (\\r\\n)", "\r\n"),
|
|
|
|
|
|
new TerminatorOption("CR (\\r)", "\r")
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
public int SelectedDaqLogicalId
|
|
|
|
|
|
{
|
|
|
|
|
|
get => Devices.Daq.LogicalDeviceId;
|
|
|
|
|
|
set
|
|
|
|
|
|
{
|
|
|
|
|
|
if (Devices.Daq.LogicalDeviceId == value) return;
|
|
|
|
|
|
Devices.Daq.LogicalDeviceId = value;
|
|
|
|
|
|
OnPropertyChanged();
|
|
|
|
|
|
NotifyDraftChanged();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public string SspcStatus => _sspc.IsConnected ? "已连接" : "未连接";
|
|
|
|
|
|
public string PowerStatus => _power.IsConnected ? "已连接" : "未连接";
|
|
|
|
|
|
public string LoadStatus => _load.IsConnected ? "已连接" : "未连接";
|
|
|
|
|
|
public string DaqStatus => _daq.IsConnected ? "已连接" : "未连接";
|
|
|
|
|
|
public string Pci2312Status => _safetyDio.IsConnected ? "已连接" : "未连接";
|
|
|
|
|
|
public double DaqActualSampleRateHz => Devices.Daq.ActualSampleRateHz;
|
|
|
|
|
|
public bool HasUnsavedChanges => !SettingsFingerprint(Devices, Storage, Report)
|
|
|
|
|
|
.Equals(SettingsFingerprint(_runtimeDevices, _runtimeStorage, _runtimeReport), StringComparison.Ordinal);
|
|
|
|
|
|
|
|
|
|
|
|
public string DaqValidationMessage
|
|
|
|
|
|
{
|
|
|
|
|
|
get
|
|
|
|
|
|
{
|
|
|
|
|
|
ApplyDaqChannelEditorsToDraft();
|
|
|
|
|
|
if (Devices.Daq.ChannelCount is not (1 or 2 or 4 or 8))
|
|
|
|
|
|
return "启用通道数只能为 1、2、4 或 8。";
|
|
|
|
|
|
if (Devices.Daq.ClockDivider < 1)
|
|
|
|
|
|
return "PCIe8586 时钟分频必须大于 0。";
|
|
|
|
|
|
if (Devices.Daq.PreTriggerMs <= 0 || Devices.Daq.PostTriggerMs <= 0)
|
|
|
|
|
|
return "前置和后置采集窗口必须大于 0。";
|
|
|
|
|
|
|
|
|
|
|
|
var enabled = Devices.Daq.Channels.Where(x => x.PhysicalChannel < Devices.Daq.ChannelCount).ToArray();
|
|
|
|
|
|
if (enabled.Length == 0)
|
|
|
|
|
|
return "没有启用的采集通道。";
|
|
|
|
|
|
var outputVoltage = enabled.Where(x =>
|
|
|
|
|
|
string.Equals(x.Role, nameof(DaqChannelRole.OutputVoltage), StringComparison.OrdinalIgnoreCase)).ToArray();
|
|
|
|
|
|
if (outputVoltage.Length != 1)
|
|
|
|
|
|
return $"曲线测试需要且只能配置一个输出电压角色,当前为 {outputVoltage.Length} 个。";
|
|
|
|
|
|
var voltageChannel = outputVoltage[0];
|
|
|
|
|
|
if (!voltageChannel.IsCalibrated)
|
|
|
|
|
|
return $"CH{voltageChannel.PhysicalChannel} 输出电压尚未勾选已标定。";
|
|
|
|
|
|
if (!double.IsFinite(voltageChannel.Scale) || voltageChannel.Scale == 0)
|
|
|
|
|
|
return $"CH{voltageChannel.PhysicalChannel} Scale 无效。";
|
|
|
|
|
|
if (!double.IsFinite(voltageChannel.Offset))
|
|
|
|
|
|
return $"CH{voltageChannel.PhysicalChannel} Offset 无效。";
|
|
|
|
|
|
return HasUnsavedChanges ? "采集配置有效。未保存,测试仍会使用上一次保存的配置。" : "采集配置有效。";
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void SetCloseAction(Action closeAction) => _closeAction = closeAction;
|
|
|
|
|
|
|
2026-07-08 00:25:45 +08:00
|
|
|
|
public void SetProjectRefreshAction(Func<string, Task> refreshAction, Func<bool>? canRefresh = null)
|
|
|
|
|
|
{
|
|
|
|
|
|
_projectRefreshAction = refreshAction;
|
|
|
|
|
|
_canRefreshProjects = canRefresh;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-01 20:04:27 +08:00
|
|
|
|
public void NotifyDraftChanged()
|
|
|
|
|
|
{
|
|
|
|
|
|
ApplyDaqChannelEditorsToDraft();
|
|
|
|
|
|
RefreshDaqEditorEnabledStates();
|
|
|
|
|
|
OnPropertyChanged(nameof(DaqActualSampleRateHz));
|
|
|
|
|
|
OnPropertyChanged(nameof(DaqValidationMessage));
|
|
|
|
|
|
OnPropertyChanged(nameof(HasUnsavedChanges));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[RelayCommand]
|
|
|
|
|
|
private void Close() => _closeAction?.Invoke();
|
|
|
|
|
|
|
|
|
|
|
|
[RelayCommand]
|
2026-07-08 00:25:45 +08:00
|
|
|
|
private async Task SaveSettings() =>
|
|
|
|
|
|
await SaveSettingsCore(logSaved: true);
|
|
|
|
|
|
|
|
|
|
|
|
private async Task SaveSettingsCore(bool logSaved)
|
2026-07-01 20:04:27 +08:00
|
|
|
|
{
|
|
|
|
|
|
ApplyDaqChannelEditorsToDraft();
|
|
|
|
|
|
Devices.Daq.Channels = DaqChannelOptionsNormalizer.Normalize(Devices.Daq.Channels);
|
|
|
|
|
|
ApplyToRuntime(Devices, _runtimeDevices);
|
|
|
|
|
|
ApplyToRuntime(Storage, _runtimeStorage);
|
|
|
|
|
|
ApplyToRuntime(Report, _runtimeReport);
|
2026-07-08 00:25:45 +08:00
|
|
|
|
ApplyProjectsRootToStore();
|
|
|
|
|
|
await _settingsStore.SaveAsync();
|
2026-07-01 20:04:27 +08:00
|
|
|
|
OnPropertyChanged(nameof(HasUnsavedChanges));
|
|
|
|
|
|
OnPropertyChanged(nameof(DaqValidationMessage));
|
2026-07-08 00:25:45 +08:00
|
|
|
|
if (logSaved)
|
|
|
|
|
|
_log.Add(UiLogLevel.Success, "设置已保存。重新连接请单独点击“重新连接”。");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void ApplyProjectsRootToStore()
|
|
|
|
|
|
{
|
|
|
|
|
|
_projectStore.ProjectsRoot = _runtimeStorage.ProjectsRoot;
|
|
|
|
|
|
if (!string.IsNullOrWhiteSpace(_projectStore.ProjectsRoot))
|
|
|
|
|
|
Directory.CreateDirectory(_projectStore.ProjectsRoot);
|
2026-07-01 20:04:27 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[RelayCommand]
|
|
|
|
|
|
private void RefreshPorts()
|
|
|
|
|
|
{
|
|
|
|
|
|
string? selected = Devices.Sspc.PortName;
|
|
|
|
|
|
PortNames.Clear();
|
|
|
|
|
|
foreach (string port in SerialPort.GetPortNames().OrderBy(x => x))
|
|
|
|
|
|
PortNames.Add(port);
|
|
|
|
|
|
if (!string.IsNullOrWhiteSpace(selected) && !PortNames.Contains(selected))
|
|
|
|
|
|
PortNames.Add(selected);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[RelayCommand]
|
|
|
|
|
|
private async Task ReconnectAll()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!EnsureSavedBeforeDeviceAction()) return;
|
|
|
|
|
|
await Reconnect("SSPC", _sspc).ConfigureAwait(false);
|
|
|
|
|
|
await Reconnect("电源", _power).ConfigureAwait(false);
|
|
|
|
|
|
await Reconnect("负载", _load).ConfigureAwait(false);
|
|
|
|
|
|
await TestDaqSavedConfig().ConfigureAwait(false);
|
|
|
|
|
|
await Reconnect("PCI2312", _safetyDio).ConfigureAwait(false);
|
|
|
|
|
|
RaiseStatuses();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[RelayCommand]
|
|
|
|
|
|
private async Task TestSspc()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!EnsureSavedBeforeDeviceAction()) return;
|
|
|
|
|
|
await Reconnect("SSPC", _sspc).ConfigureAwait(false);
|
|
|
|
|
|
OnPropertyChanged(nameof(SspcStatus));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[RelayCommand]
|
|
|
|
|
|
private async Task TestPower()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!EnsureSavedBeforeDeviceAction()) return;
|
|
|
|
|
|
await Reconnect("电源", _power).ConfigureAwait(false);
|
|
|
|
|
|
if (_power.IsConnected)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var id = await _power.QueryIdentityAsync().ConfigureAwait(false);
|
|
|
|
|
|
_log.Add(UiLogLevel.Success, $"UDP5080 设备标识:{id}");
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
_log.Add(UiLogLevel.Warning, $"UDP5080 已连接,但读取设备标识失败:{ex.Message}");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
OnPropertyChanged(nameof(PowerStatus));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[RelayCommand]
|
|
|
|
|
|
private async Task TestLoad()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!EnsureSavedBeforeDeviceAction()) return;
|
|
|
|
|
|
await Reconnect("负载", _load).ConfigureAwait(false);
|
|
|
|
|
|
if (_load.IsConnected)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var id = await _load.QueryIdentityAsync().ConfigureAwait(false);
|
|
|
|
|
|
var reading = await _load.ReadPowerAsync().ConfigureAwait(false);
|
|
|
|
|
|
_log.Add(UiLogLevel.Success, $"IT8702P 设备标识:{id},V={reading.Volts:F3}V I={reading.Amps:F3}A P={reading.Watts:F3}W");
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
_log.Add(UiLogLevel.Warning, $"IT8702P 已连接,但读取标识或测量值失败:{ex.Message}");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
OnPropertyChanged(nameof(LoadStatus));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[RelayCommand]
|
|
|
|
|
|
private async Task TestDaq()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!EnsureSavedBeforeDeviceAction()) return;
|
|
|
|
|
|
await TestDaqSavedConfig().ConfigureAwait(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[RelayCommand]
|
|
|
|
|
|
private async Task TestPci2312()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!EnsureSavedBeforeDeviceAction()) return;
|
|
|
|
|
|
await Reconnect("PCI2312", _safetyDio).ConfigureAwait(false);
|
|
|
|
|
|
if (_safetyDio.IsConnected)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
bool input = await _safetyDio.ReadInputAsync(_runtimeDevices.Pci2312.EmergencyInputChannel).ConfigureAwait(false);
|
|
|
|
|
|
_log.Add(UiLogLevel.Success, $"PCI2312 连接成功,DI{_runtimeDevices.Pci2312.EmergencyInputChannel}={(input ? 1 : 0)}");
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
_log.Add(UiLogLevel.Warning, $"PCI2312 已连接,但读取输入失败:{ex.Message}");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
OnPropertyChanged(nameof(Pci2312Status));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[RelayCommand]
|
|
|
|
|
|
private async Task RefreshDaqDevices()
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var devices = await _daq.EnumerateDevicesAsync().ConfigureAwait(false);
|
|
|
|
|
|
await RunOnUiAsync(() =>
|
|
|
|
|
|
{
|
|
|
|
|
|
DaqDevices.Clear();
|
|
|
|
|
|
foreach (var device in devices)
|
|
|
|
|
|
DaqDevices.Add(device);
|
|
|
|
|
|
|
|
|
|
|
|
if (devices.Count > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!devices.Any(x => x.LogicalId == SelectedDaqLogicalId))
|
|
|
|
|
|
SelectedDaqLogicalId = devices[0].LogicalId;
|
|
|
|
|
|
OnPropertyChanged(nameof(SelectedDaqLogicalId));
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
DaqDevices.Add(new DaqDeviceDescriptor(
|
|
|
|
|
|
SelectedDaqLogicalId, null, "当前配置的 PCIe8586", Devices.Daq.UseMock));
|
|
|
|
|
|
}
|
|
|
|
|
|
}).ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
|
|
_log.Add(devices.Count > 0 ? UiLogLevel.Success : UiLogLevel.Warning,
|
|
|
|
|
|
devices.Count > 0 ? $"发现 {devices.Count} 张 PCIe8586。" : "未发现 PCIe8586。");
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
await RunOnUiAsync(() =>
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!DaqDevices.Any(x => x.LogicalId == SelectedDaqLogicalId))
|
|
|
|
|
|
DaqDevices.Add(new DaqDeviceDescriptor(
|
|
|
|
|
|
SelectedDaqLogicalId, null, "当前配置的 PCIe8586", Devices.Daq.UseMock));
|
|
|
|
|
|
}).ConfigureAwait(false);
|
|
|
|
|
|
_log.Add(UiLogLevel.Error, $"枚举 PCIe8586 失败:{ex.Message}");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[RelayCommand]
|
|
|
|
|
|
private async Task DisconnectSspc()
|
|
|
|
|
|
{
|
|
|
|
|
|
await _sspc.DisconnectAsync().ConfigureAwait(false);
|
|
|
|
|
|
OnPropertyChanged(nameof(SspcStatus));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[RelayCommand]
|
|
|
|
|
|
private async Task DisconnectDaq()
|
|
|
|
|
|
{
|
|
|
|
|
|
await _daq.DisconnectAsync().ConfigureAwait(false);
|
|
|
|
|
|
OnPropertyChanged(nameof(DaqStatus));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[RelayCommand]
|
|
|
|
|
|
private async Task DisconnectPci2312()
|
|
|
|
|
|
{
|
|
|
|
|
|
await _safetyDio.DisconnectAsync().ConfigureAwait(false);
|
|
|
|
|
|
OnPropertyChanged(nameof(Pci2312Status));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[RelayCommand]
|
|
|
|
|
|
private void BrowseProjects()
|
|
|
|
|
|
{
|
|
|
|
|
|
string? selected = SelectFolder("选择项目数据目录", Storage.ProjectsRoot);
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(selected)) return;
|
|
|
|
|
|
Storage.ProjectsRoot = selected;
|
|
|
|
|
|
OnPropertyChanged(nameof(Storage));
|
|
|
|
|
|
NotifyDraftChanged();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[RelayCommand]
|
|
|
|
|
|
private void BrowseReports()
|
|
|
|
|
|
{
|
|
|
|
|
|
string? selected = SelectFolder("选择报告保存目录", Report.SavePath);
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(selected)) return;
|
|
|
|
|
|
Report.SavePath = selected;
|
|
|
|
|
|
OnPropertyChanged(nameof(Report));
|
|
|
|
|
|
NotifyDraftChanged();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[RelayCommand]
|
|
|
|
|
|
private void OpenProjectsDirectory() => OpenDirectory(Storage.ProjectsRoot);
|
|
|
|
|
|
|
2026-07-08 00:25:45 +08:00
|
|
|
|
[RelayCommand]
|
|
|
|
|
|
private async Task RefreshProjects()
|
|
|
|
|
|
{
|
|
|
|
|
|
NotifyDraftChanged();
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(Storage.ProjectsRoot))
|
|
|
|
|
|
{
|
|
|
|
|
|
_log.Add(UiLogLevel.Error, "项目数据路径为空,无法刷新工程。");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (_canRefreshProjects?.Invoke() == false)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
if (_projectRefreshAction == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
_log.Add(UiLogLevel.Warning, "刷新工程功能尚未初始化。");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
await SaveSettingsCore(logSaved: false);
|
|
|
|
|
|
await _projectRefreshAction(_projectStore.ProjectsRoot);
|
|
|
|
|
|
await RunOnUiAsync(() =>
|
|
|
|
|
|
{
|
|
|
|
|
|
OnPropertyChanged(nameof(HasUnsavedChanges));
|
|
|
|
|
|
OnPropertyChanged(nameof(DaqValidationMessage));
|
|
|
|
|
|
});
|
|
|
|
|
|
_log.Add(UiLogLevel.Success, "工程列表已刷新。");
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
_log.Add(UiLogLevel.Error, $"刷新工程失败:{ex.Message}");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-01 20:04:27 +08:00
|
|
|
|
[RelayCommand]
|
|
|
|
|
|
private void OpenReportsDirectory() => OpenDirectory(Report.SavePath);
|
|
|
|
|
|
|
|
|
|
|
|
private bool EnsureSavedBeforeDeviceAction()
|
|
|
|
|
|
{
|
|
|
|
|
|
NotifyDraftChanged();
|
|
|
|
|
|
if (!HasUnsavedChanges) return true;
|
|
|
|
|
|
_log.Add(UiLogLevel.Warning, "当前设置尚未保存。请先点击“保存设置”,再执行连接或测试。");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private async Task TestDaqSavedConfig()
|
|
|
|
|
|
{
|
|
|
|
|
|
try { await _daq.DisconnectAsync().ConfigureAwait(false); } catch { }
|
|
|
|
|
|
await RefreshDaqDevices().ConfigureAwait(false);
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
await _daq.ConnectAsync().ConfigureAwait(false);
|
|
|
|
|
|
var channels = Enumerable.Range(0, _runtimeDevices.Daq.ChannelCount).ToArray();
|
|
|
|
|
|
var readings = await _daq.ReadInstantAsync(channels).ConfigureAwait(false);
|
|
|
|
|
|
string preview = string.Join(", ", channels.Zip(readings, (ch, value) => $"CH{ch}={value:F4}"));
|
|
|
|
|
|
_log.Add(UiLogLevel.Success, $"PCIe8586 连接并采集成功:{preview}");
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
_log.Add(UiLogLevel.Error, $"PCIe8586 连接失败:{ex.Message}");
|
|
|
|
|
|
}
|
|
|
|
|
|
await RunOnUiAsync(() => OnPropertyChanged(nameof(DaqStatus))).ConfigureAwait(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static Task RunOnUiAsync(Action action)
|
|
|
|
|
|
{
|
|
|
|
|
|
var dispatcher = Application.Current?.Dispatcher;
|
|
|
|
|
|
if (dispatcher is null || dispatcher.CheckAccess())
|
|
|
|
|
|
{
|
|
|
|
|
|
action();
|
|
|
|
|
|
return Task.CompletedTask;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return dispatcher.InvokeAsync(action).Task;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private async Task Reconnect(string name, IDeviceConnection device)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
await device.DisconnectAsync().ConfigureAwait(false);
|
|
|
|
|
|
bool connected = await device.ConnectAsync().ConfigureAwait(false);
|
|
|
|
|
|
_log.Add(connected && device.IsConnected ? UiLogLevel.Success : UiLogLevel.Warning,
|
|
|
|
|
|
connected && device.IsConnected
|
|
|
|
|
|
? $"{name} 连接成功。"
|
|
|
|
|
|
: $"{name} 连接未成功,设备保持未连接。");
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
_log.Add(UiLogLevel.Error, $"{name} 连接失败:{ex.Message}");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void RaiseStatuses()
|
|
|
|
|
|
{
|
|
|
|
|
|
OnPropertyChanged(nameof(SspcStatus));
|
|
|
|
|
|
OnPropertyChanged(nameof(PowerStatus));
|
|
|
|
|
|
OnPropertyChanged(nameof(LoadStatus));
|
|
|
|
|
|
OnPropertyChanged(nameof(DaqStatus));
|
|
|
|
|
|
OnPropertyChanged(nameof(Pci2312Status));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void LoadDaqChannelEditors()
|
|
|
|
|
|
{
|
|
|
|
|
|
DaqChannelEditors.Clear();
|
|
|
|
|
|
foreach (var channel in Devices.Daq.Channels)
|
|
|
|
|
|
{
|
|
|
|
|
|
var editor = new DaqChannelEditor
|
|
|
|
|
|
{
|
|
|
|
|
|
PhysicalChannel = channel.PhysicalChannel,
|
|
|
|
|
|
Role = channel.Role,
|
|
|
|
|
|
Name = channel.Name,
|
|
|
|
|
|
Scale = channel.Scale,
|
|
|
|
|
|
Offset = channel.Offset,
|
|
|
|
|
|
IsCalibrated = channel.IsCalibrated,
|
|
|
|
|
|
IsEnabled = channel.PhysicalChannel < Devices.Daq.ChannelCount
|
|
|
|
|
|
};
|
|
|
|
|
|
editor.SetChangeHandler(OnDaqChannelEditorChanged);
|
|
|
|
|
|
DaqChannelEditors.Add(editor);
|
|
|
|
|
|
}
|
|
|
|
|
|
ApplyDaqChannelEditorsToDraft();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void OnDaqChannelEditorChanged()
|
|
|
|
|
|
{
|
|
|
|
|
|
ApplyDaqChannelEditorsToDraft();
|
|
|
|
|
|
OnPropertyChanged(nameof(DaqValidationMessage));
|
|
|
|
|
|
OnPropertyChanged(nameof(HasUnsavedChanges));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void RefreshDaqEditorEnabledStates()
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (var editor in DaqChannelEditors)
|
|
|
|
|
|
editor.IsEnabled = editor.PhysicalChannel < Devices.Daq.ChannelCount;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void ApplyDaqChannelEditorsToDraft()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (DaqChannelEditors.Count == 0) return;
|
|
|
|
|
|
Devices.Daq.Channels = DaqChannelOptionsNormalizer.Normalize(
|
|
|
|
|
|
DaqChannelEditors.Select(x => new DaqChannelOptions
|
|
|
|
|
|
{
|
|
|
|
|
|
PhysicalChannel = x.PhysicalChannel,
|
|
|
|
|
|
Role = x.Role,
|
|
|
|
|
|
Name = x.Name,
|
|
|
|
|
|
Scale = x.Scale,
|
|
|
|
|
|
Offset = x.Offset,
|
|
|
|
|
|
IsCalibrated = x.IsCalibrated
|
|
|
|
|
|
}));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static string? SelectFolder(string description, string currentPath)
|
|
|
|
|
|
{
|
|
|
|
|
|
var dialog = new OpenFolderDialog
|
|
|
|
|
|
{
|
|
|
|
|
|
Title = description,
|
|
|
|
|
|
InitialDirectory = Directory.Exists(currentPath) ? currentPath : null
|
|
|
|
|
|
};
|
|
|
|
|
|
return dialog.ShowDialog() == true ? dialog.FolderName : null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static void OpenDirectory(string path)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(path)) return;
|
|
|
|
|
|
Directory.CreateDirectory(path);
|
|
|
|
|
|
System.Diagnostics.Process.Start("explorer.exe", path);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static DeviceOptions CloneDeviceOptions(DeviceOptions source) => new()
|
|
|
|
|
|
{
|
|
|
|
|
|
MockMode = source.MockMode,
|
|
|
|
|
|
Sspc = CloneSspcOptions(source.Sspc),
|
|
|
|
|
|
PowerSupply = ClonePowerSupplyOptions(source.PowerSupply),
|
|
|
|
|
|
Load = CloneLoadOptions(source.Load),
|
|
|
|
|
|
Daq = CloneDaqOptions(source.Daq),
|
|
|
|
|
|
Pci2312 = ClonePci2312Options(source.Pci2312)
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
private static SspcOptions CloneSspcOptions(SspcOptions source) => new()
|
|
|
|
|
|
{
|
|
|
|
|
|
UseMock = source.UseMock,
|
|
|
|
|
|
AutoConnect = source.AutoConnect,
|
|
|
|
|
|
PortName = source.PortName,
|
|
|
|
|
|
BaudRate = source.BaudRate,
|
|
|
|
|
|
DataBits = source.DataBits,
|
|
|
|
|
|
Parity = source.Parity,
|
|
|
|
|
|
StopBits = source.StopBits,
|
|
|
|
|
|
ReadTimeoutMs = source.ReadTimeoutMs,
|
|
|
|
|
|
Retry = source.Retry,
|
|
|
|
|
|
DeviceProfilePath = source.DeviceProfilePath,
|
|
|
|
|
|
CloseAllRelaysOnDisconnect = source.CloseAllRelaysOnDisconnect,
|
|
|
|
|
|
LogRawFrames = source.LogRawFrames,
|
|
|
|
|
|
ModuleAddresses = source.ModuleAddresses.ToArray(),
|
|
|
|
|
|
ChannelsPerModule = source.ChannelsPerModule
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
private static PowerSupplyOptions ClonePowerSupplyOptions(PowerSupplyOptions source) => new()
|
|
|
|
|
|
{
|
|
|
|
|
|
UseMock = source.UseMock,
|
|
|
|
|
|
AutoConnect = source.AutoConnect,
|
|
|
|
|
|
PortName = source.PortName,
|
|
|
|
|
|
BaudRate = source.BaudRate,
|
|
|
|
|
|
Host = source.Host,
|
|
|
|
|
|
Port = source.Port,
|
|
|
|
|
|
TimeoutSeconds = source.TimeoutSeconds,
|
|
|
|
|
|
RequirePingBeforeConnect = source.RequirePingBeforeConnect,
|
|
|
|
|
|
Terminator = source.Terminator,
|
|
|
|
|
|
IdnCommand = source.IdnCommand,
|
|
|
|
|
|
VoltageCommand = source.VoltageCommand,
|
|
|
|
|
|
CurrentCommand = source.CurrentCommand
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
private static LoadOptions CloneLoadOptions(LoadOptions source) => new()
|
|
|
|
|
|
{
|
|
|
|
|
|
UseMock = source.UseMock,
|
|
|
|
|
|
AutoConnect = source.AutoConnect,
|
|
|
|
|
|
PortName = source.PortName,
|
|
|
|
|
|
BaudRate = source.BaudRate,
|
|
|
|
|
|
Host = source.Host,
|
|
|
|
|
|
Port = source.Port,
|
|
|
|
|
|
TimeoutSeconds = source.TimeoutSeconds,
|
|
|
|
|
|
Terminator = source.Terminator,
|
|
|
|
|
|
Channel = source.Channel,
|
|
|
|
|
|
UseFetch = source.UseFetch,
|
|
|
|
|
|
QueryMode = source.QueryMode
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
private static DaqOptions CloneDaqOptions(DaqOptions source) => new()
|
|
|
|
|
|
{
|
|
|
|
|
|
UseMock = source.UseMock,
|
|
|
|
|
|
AutoConnect = source.AutoConnect,
|
|
|
|
|
|
LogicalDeviceId = source.LogicalDeviceId,
|
|
|
|
|
|
ChannelCount = source.ChannelCount,
|
|
|
|
|
|
InputRange = source.InputRange,
|
|
|
|
|
|
ClockDivider = source.ClockDivider,
|
|
|
|
|
|
PreTriggerMs = source.PreTriggerMs,
|
|
|
|
|
|
PostTriggerMs = source.PostTriggerMs,
|
|
|
|
|
|
Channels = DaqChannelOptionsNormalizer.Normalize(source.Channels.Select(CloneDaqChannelOptions))
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
private static Pci2312Options ClonePci2312Options(Pci2312Options source) => new()
|
|
|
|
|
|
{
|
|
|
|
|
|
Enabled = source.Enabled,
|
|
|
|
|
|
UseMock = source.UseMock,
|
|
|
|
|
|
AutoConnect = source.AutoConnect,
|
|
|
|
|
|
DeviceId = source.DeviceId,
|
|
|
|
|
|
EmergencyInputChannel = source.EmergencyInputChannel,
|
|
|
|
|
|
EmergencyInputActiveHigh = source.EmergencyInputActiveHigh,
|
|
|
|
|
|
AlarmOutputChannel = source.AlarmOutputChannel,
|
|
|
|
|
|
AlarmOutputActiveHigh = source.AlarmOutputActiveHigh,
|
|
|
|
|
|
PollIntervalMs = source.PollIntervalMs
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
private static DaqChannelOptions CloneDaqChannelOptions(DaqChannelOptions source) => new()
|
|
|
|
|
|
{
|
|
|
|
|
|
PhysicalChannel = source.PhysicalChannel,
|
|
|
|
|
|
Role = source.Role,
|
|
|
|
|
|
Name = source.Name,
|
|
|
|
|
|
Scale = source.Scale,
|
|
|
|
|
|
Offset = source.Offset,
|
|
|
|
|
|
IsCalibrated = source.IsCalibrated
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
private static StorageOptions CloneStorageOptions(StorageOptions source) => new()
|
|
|
|
|
|
{
|
|
|
|
|
|
ProjectsRoot = source.ProjectsRoot
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
private static ReportOptions CloneReportOptions(ReportOptions source) => new()
|
|
|
|
|
|
{
|
|
|
|
|
|
SavePath = source.SavePath,
|
|
|
|
|
|
Format = source.Format
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
private static void ApplyToRuntime(DeviceOptions source, DeviceOptions target)
|
|
|
|
|
|
{
|
|
|
|
|
|
target.MockMode = source.MockMode;
|
|
|
|
|
|
ApplyToRuntime(source.Sspc, target.Sspc);
|
|
|
|
|
|
ApplyToRuntime(source.PowerSupply, target.PowerSupply);
|
|
|
|
|
|
ApplyToRuntime(source.Load, target.Load);
|
|
|
|
|
|
ApplyToRuntime(source.Daq, target.Daq);
|
|
|
|
|
|
ApplyToRuntime(source.Pci2312, target.Pci2312);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static void ApplyToRuntime(SspcOptions source, SspcOptions target)
|
|
|
|
|
|
{
|
|
|
|
|
|
target.UseMock = source.UseMock;
|
|
|
|
|
|
target.AutoConnect = source.AutoConnect;
|
|
|
|
|
|
target.PortName = source.PortName;
|
|
|
|
|
|
target.BaudRate = source.BaudRate;
|
|
|
|
|
|
target.DataBits = source.DataBits;
|
|
|
|
|
|
target.Parity = source.Parity;
|
|
|
|
|
|
target.StopBits = source.StopBits;
|
|
|
|
|
|
target.ReadTimeoutMs = source.ReadTimeoutMs;
|
|
|
|
|
|
target.Retry = source.Retry;
|
|
|
|
|
|
target.DeviceProfilePath = source.DeviceProfilePath;
|
|
|
|
|
|
target.CloseAllRelaysOnDisconnect = source.CloseAllRelaysOnDisconnect;
|
|
|
|
|
|
target.LogRawFrames = source.LogRawFrames;
|
|
|
|
|
|
target.ModuleAddresses = source.ModuleAddresses.ToArray();
|
|
|
|
|
|
target.ChannelsPerModule = source.ChannelsPerModule;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static void ApplyToRuntime(PowerSupplyOptions source, PowerSupplyOptions target)
|
|
|
|
|
|
{
|
|
|
|
|
|
target.UseMock = source.UseMock;
|
|
|
|
|
|
target.AutoConnect = source.AutoConnect;
|
|
|
|
|
|
target.PortName = source.PortName;
|
|
|
|
|
|
target.BaudRate = source.BaudRate;
|
|
|
|
|
|
target.Host = source.Host;
|
|
|
|
|
|
target.Port = source.Port;
|
|
|
|
|
|
target.TimeoutSeconds = source.TimeoutSeconds;
|
|
|
|
|
|
target.RequirePingBeforeConnect = source.RequirePingBeforeConnect;
|
|
|
|
|
|
target.Terminator = source.Terminator;
|
|
|
|
|
|
target.IdnCommand = source.IdnCommand;
|
|
|
|
|
|
target.VoltageCommand = source.VoltageCommand;
|
|
|
|
|
|
target.CurrentCommand = source.CurrentCommand;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static void ApplyToRuntime(LoadOptions source, LoadOptions target)
|
|
|
|
|
|
{
|
|
|
|
|
|
target.UseMock = source.UseMock;
|
|
|
|
|
|
target.AutoConnect = source.AutoConnect;
|
|
|
|
|
|
target.PortName = source.PortName;
|
|
|
|
|
|
target.BaudRate = source.BaudRate;
|
|
|
|
|
|
target.Host = source.Host;
|
|
|
|
|
|
target.Port = source.Port;
|
|
|
|
|
|
target.TimeoutSeconds = source.TimeoutSeconds;
|
|
|
|
|
|
target.Terminator = source.Terminator;
|
|
|
|
|
|
target.Channel = source.Channel;
|
|
|
|
|
|
target.UseFetch = source.UseFetch;
|
|
|
|
|
|
target.QueryMode = source.QueryMode;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static void ApplyToRuntime(DaqOptions source, DaqOptions target)
|
|
|
|
|
|
{
|
|
|
|
|
|
target.UseMock = source.UseMock;
|
|
|
|
|
|
target.AutoConnect = source.AutoConnect;
|
|
|
|
|
|
target.LogicalDeviceId = source.LogicalDeviceId;
|
|
|
|
|
|
target.ChannelCount = source.ChannelCount;
|
|
|
|
|
|
target.InputRange = source.InputRange;
|
|
|
|
|
|
target.ClockDivider = source.ClockDivider;
|
|
|
|
|
|
target.PreTriggerMs = source.PreTriggerMs;
|
|
|
|
|
|
target.PostTriggerMs = source.PostTriggerMs;
|
|
|
|
|
|
target.Channels = DaqChannelOptionsNormalizer.Normalize(source.Channels.Select(CloneDaqChannelOptions));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static void ApplyToRuntime(Pci2312Options source, Pci2312Options target)
|
|
|
|
|
|
{
|
|
|
|
|
|
target.Enabled = source.Enabled;
|
|
|
|
|
|
target.UseMock = source.UseMock;
|
|
|
|
|
|
target.AutoConnect = source.AutoConnect;
|
|
|
|
|
|
target.DeviceId = source.DeviceId;
|
|
|
|
|
|
target.EmergencyInputChannel = source.EmergencyInputChannel;
|
|
|
|
|
|
target.EmergencyInputActiveHigh = source.EmergencyInputActiveHigh;
|
|
|
|
|
|
target.AlarmOutputChannel = source.AlarmOutputChannel;
|
|
|
|
|
|
target.AlarmOutputActiveHigh = source.AlarmOutputActiveHigh;
|
|
|
|
|
|
target.PollIntervalMs = source.PollIntervalMs;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static void ApplyToRuntime(StorageOptions source, StorageOptions target)
|
|
|
|
|
|
{
|
|
|
|
|
|
target.ProjectsRoot = source.ProjectsRoot;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static void ApplyToRuntime(ReportOptions source, ReportOptions target)
|
|
|
|
|
|
{
|
|
|
|
|
|
target.SavePath = source.SavePath;
|
|
|
|
|
|
target.Format = source.Format;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static string SettingsFingerprint(DeviceOptions devices, StorageOptions storage, ReportOptions report)
|
|
|
|
|
|
{
|
|
|
|
|
|
var model = new
|
|
|
|
|
|
{
|
|
|
|
|
|
Devices = devices,
|
|
|
|
|
|
Storage = storage,
|
|
|
|
|
|
Report = report
|
|
|
|
|
|
};
|
|
|
|
|
|
return JsonSerializer.Serialize(model);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public sealed record TerminatorOption(string Name, string Value);
|
|
|
|
|
|
|
|
|
|
|
|
public sealed record DaqRoleOption(string Value, string Name);
|
|
|
|
|
|
|
|
|
|
|
|
public sealed partial class DaqChannelEditor : ObservableObject
|
|
|
|
|
|
{
|
|
|
|
|
|
private Action? _changed;
|
|
|
|
|
|
|
|
|
|
|
|
public int PhysicalChannel { get; init; }
|
|
|
|
|
|
public string ChannelName => $"CH{PhysicalChannel}";
|
|
|
|
|
|
|
|
|
|
|
|
[ObservableProperty] private string _role = nameof(DaqChannelRole.Unconfigured);
|
|
|
|
|
|
[ObservableProperty] private string _name = "";
|
|
|
|
|
|
[ObservableProperty] private double _scale = 1;
|
|
|
|
|
|
[ObservableProperty] private double _offset;
|
|
|
|
|
|
[ObservableProperty] private bool _isCalibrated;
|
|
|
|
|
|
[ObservableProperty] private bool _isEnabled;
|
|
|
|
|
|
|
|
|
|
|
|
public void SetChangeHandler(Action changed) => _changed = changed;
|
|
|
|
|
|
|
|
|
|
|
|
partial void OnRoleChanged(string value) => _changed?.Invoke();
|
|
|
|
|
|
|
|
|
|
|
|
partial void OnNameChanged(string value) => _changed?.Invoke();
|
|
|
|
|
|
|
|
|
|
|
|
partial void OnScaleChanged(double value) => _changed?.Invoke();
|
|
|
|
|
|
|
|
|
|
|
|
partial void OnOffsetChanged(double value) => _changed?.Invoke();
|
|
|
|
|
|
|
|
|
|
|
|
partial void OnIsCalibratedChanged(bool value) => _changed?.Invoke();
|
|
|
|
|
|
}
|