SSPC-Tester/SSPCTester.UI/ViewModels/SettingsViewModel.Manual.cs
2026-07-08 00:25:45 +08:00

791 lines
30 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
using SSPCTester.Logic.Storage;
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;
private readonly ProjectStore _projectStore;
private Action? _closeAction;
private Func<bool>? _canRefreshProjects;
private Func<string, Task>? _projectRefreshAction;
public SettingsViewModel(
IOptions<DeviceOptions> devOpts,
IOptions<StorageOptions> storeOpts,
IOptions<ReportOptions> reportOpts,
ISspc sspc, IPowerSupply power, ILoad load, IDaq daq, ISafetyDio safetyDio, UiLogSink log,
UserSettingsStore settingsStore,
ProjectStore projectStore)
{
_runtimeDevices = devOpts.Value;
_runtimeStorage = storeOpts.Value;
_runtimeReport = reportOpts.Value;
_sspc = sspc;
_power = power;
_load = load;
_daq = daq;
_safetyDio = safetyDio;
_log = log;
_settingsStore = settingsStore;
_projectStore = projectStore;
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;
public void SetProjectRefreshAction(Func<string, Task> refreshAction, Func<bool>? canRefresh = null)
{
_projectRefreshAction = refreshAction;
_canRefreshProjects = canRefresh;
}
public void NotifyDraftChanged()
{
ApplyDaqChannelEditorsToDraft();
RefreshDaqEditorEnabledStates();
OnPropertyChanged(nameof(DaqActualSampleRateHz));
OnPropertyChanged(nameof(DaqValidationMessage));
OnPropertyChanged(nameof(HasUnsavedChanges));
}
[RelayCommand]
private void Close() => _closeAction?.Invoke();
[RelayCommand]
private async Task SaveSettings() =>
await SaveSettingsCore(logSaved: true);
private async Task SaveSettingsCore(bool logSaved)
{
ApplyDaqChannelEditorsToDraft();
Devices.Daq.Channels = DaqChannelOptionsNormalizer.Normalize(Devices.Daq.Channels);
ApplyToRuntime(Devices, _runtimeDevices);
ApplyToRuntime(Storage, _runtimeStorage);
ApplyToRuntime(Report, _runtimeReport);
ApplyProjectsRootToStore();
await _settingsStore.SaveAsync();
OnPropertyChanged(nameof(HasUnsavedChanges));
OnPropertyChanged(nameof(DaqValidationMessage));
if (logSaved)
_log.Add(UiLogLevel.Success, "设置已保存。重新连接请单独点击“重新连接”。");
}
private void ApplyProjectsRootToStore()
{
_projectStore.ProjectsRoot = _runtimeStorage.ProjectsRoot;
if (!string.IsNullOrWhiteSpace(_projectStore.ProjectsRoot))
Directory.CreateDirectory(_projectStore.ProjectsRoot);
}
[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);
[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}");
}
}
[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();
}