using System.Collections.ObjectModel; using System.IO; using System.Windows; using System.Windows.Threading; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using SSPCTester.Devices.Interfaces; using SSPCTester.Devices.Config; using Microsoft.Extensions.Options; using SSPCTester.Logic.Models; using SSPCTester.Logic.Storage; using SSPCTester.UI.Services; namespace SSPCTester.UI.ViewModels; /// 主窗口 VM:持有项目集合、当前页面、应用状态。 public partial class MainViewModel : ObservableObject { private readonly IServiceProvider _sp; private readonly ProjectStore _store; private readonly ILogger _logger; private readonly ISspc _sspc; private readonly IPowerSupply _power; private readonly ILoad _load; private readonly IDaq _daq; private readonly ISafetyDio _safetyDio; private readonly SafetyMonitorService _safetyMonitor; private readonly UiLogSink _logSink; private readonly DeviceOptions _deviceOptions; private readonly DispatcherTimer _deviceMonitorTimer; private readonly DispatcherTimer _safetyReadingsTimer; private bool _safetyReadingsBusy; private object? _viewBeforeSettings; public MainViewModel( IServiceProvider sp, ProjectStore store, ILogger logger, ISspc sspc, IPowerSupply power, ILoad load, IDaq daq, ISafetyDio safetyDio, SafetyMonitorService safetyMonitor, UiLogSink logSink, IOptions deviceOptions) { _sp = sp; _store = store; _logger = logger; _sspc = sspc; _power = power; _load = load; _daq = daq; _safetyDio = safetyDio; _safetyMonitor = safetyMonitor; _logSink = logSink; _deviceOptions = deviceOptions.Value; _sspc.ConnectionChanged += (_, _) => RefreshDeviceStatus(); _power.ConnectionChanged += (_, _) => RefreshDeviceStatus(); _load.ConnectionChanged += (_, _) => RefreshDeviceStatus(); _daq.ConnectionChanged += (_, _) => RefreshDeviceStatus(); _safetyDio.ConnectionChanged += (_, _) => RefreshDeviceStatus(); _safetyMonitor.StatusChanged += (_, _) => RefreshSafetyStatus(); _deviceMonitorTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) }; _deviceMonitorTimer.Tick += (_, _) => RefreshDeviceStatus(); _deviceMonitorTimer.Start(); _safetyReadingsTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) }; _safetyReadingsTimer.Tick += async (_, _) => await RefreshSafetyReadingsAsync().ConfigureAwait(true); AllProjects = new ObservableCollection(); TreeRoots = new ObservableCollection(); } /// 由 MainWindow.Loaded 调用,启动背景加载(避免在 DI 构造器中触发重入)。 public void Initialize() { if (_initialized) return; _initialized = true; _ = LoadProjectsAsync(); _ = ConnectConfiguredDevicesAsync(); _safetyMonitor.Start(); RefreshSafetyStatus(); } private bool _initialized; public ObservableCollection AllProjects { get; } public ObservableCollection TreeRoots { get; } [ObservableProperty] private AppState _state = AppState.Browsing; [ObservableProperty] private Project? _currentProject; [ObservableProperty] private object? _currentView; [ObservableProperty] private bool _devicesConnected; [ObservableProperty] private bool _devicePanelOpen; [ObservableProperty] private bool _safetyPanelOpen; [ObservableProperty] private bool _deviceOperationBusy; [ObservableProperty] private string _deviceSummary = "设备状态检查中"; [ObservableProperty] private string _sspcConnectionStatus = "检查中"; [ObservableProperty] private string _powerConnectionStatus = "检查中"; [ObservableProperty] private string _loadConnectionStatus = "检查中"; [ObservableProperty] private string _daqConnectionStatus = "检查中"; [ObservableProperty] private string _pci2312ConnectionStatus = "检查中"; [ObservableProperty] private string _safetyStatusText = "设备故障"; [ObservableProperty] private string _safetyDetailText = "设备安全监控未启动"; [ObservableProperty] private string _safetyPowerCurrentText = "--"; [ObservableProperty] private string _safetyLoadCurrentText = "--"; [ObservableProperty] private bool _alarmLampActive; [ObservableProperty] private string _testingMessage = ""; [ObservableProperty] private TestSessionViewModel? _currentSession; partial void OnStateChanged(AppState value) => RebuildTree(); partial void OnCurrentProjectChanged(Project? value) => RebuildTree(); partial void OnSafetyPanelOpenChanged(bool value) { if (value) { _safetyReadingsTimer.Start(); _ = RefreshSafetyReadingsAsync(); } else { _safetyReadingsTimer.Stop(); } } private async Task ConnectConfiguredDevicesAsync() { var jobs = new[] { ConnectOneAsync("SSPC", _sspc, _deviceOptions.Sspc.AutoConnect), ConnectOneAsync("电源", _power, _deviceOptions.PowerSupply.AutoConnect), ConnectOneAsync("负载", _load, _deviceOptions.Load.AutoConnect), ConnectOneAsync("PCIe8586", _daq, _deviceOptions.Daq.AutoConnect), ConnectOneAsync("PCI2312", _safetyDio, _deviceOptions.Pci2312.Enabled && _deviceOptions.Pci2312.AutoConnect) }; await Task.WhenAll(jobs); DevicesConnected = (!_deviceOptions.Sspc.AutoConnect || _sspc.IsConnected) && (!_deviceOptions.PowerSupply.AutoConnect || _power.IsConnected) && (!_deviceOptions.Load.AutoConnect || _load.IsConnected) && (!_deviceOptions.Daq.AutoConnect || _daq.IsConnected) && (!_deviceOptions.Pci2312.Enabled || !_deviceOptions.Pci2312.AutoConnect || _safetyDio.IsConnected); RefreshDeviceStatus(); } private async Task ConnectOneAsync(string name, IDeviceConnection device, bool autoConnect) { if (!autoConnect) return; try { bool connected = await device.ConnectAsync(); if (connected && device.IsConnected) { _logSink.Add(UiLogLevel.Success, $"{name} 已自动连接。"); } else { _logSink.Add(UiLogLevel.Warning, $"{name} 自动连接未成功,设备保持未连接。"); } } catch (Exception ex) { _logger.LogError(ex, "{Device} auto connect failed.", name); _logSink.Add(UiLogLevel.Error, $"{name} 自动连接失败:{ex.Message}"); } } private void RefreshDeviceStatus() { Application.Current?.Dispatcher.Invoke(() => { SspcConnectionStatus = DeviceStatusText(_sspc); PowerConnectionStatus = DeviceStatusText(_power); LoadConnectionStatus = DeviceStatusText(_load); DaqConnectionStatus = DeviceStatusText(_daq); Pci2312ConnectionStatus = DeviceStatusText(_safetyDio); DevicesConnected = (!_deviceOptions.Sspc.AutoConnect || _sspc.IsConnected) && (!_deviceOptions.PowerSupply.AutoConnect || _power.IsConnected) && (!_deviceOptions.Load.AutoConnect || _load.IsConnected) && (!_deviceOptions.Daq.AutoConnect || _daq.IsConnected) && (!_deviceOptions.Pci2312.Enabled || !_deviceOptions.Pci2312.AutoConnect || _safetyDio.IsConnected); int connected = new[] { _sspc.IsConnected, _power.IsConnected, _load.IsConnected, _daq.IsConnected, _safetyDio.IsConnected }.Count(x => x); DeviceSummary = connected == 5 ? "设备已连接" : $"设备 {connected}/5 已连接"; }); } private void RefreshSafetyStatus() { Application.Current?.Dispatcher.Invoke(() => { SafetyStatusText = _safetyMonitor.StatusText; SafetyDetailText = _safetyMonitor.DetailText; AlarmLampActive = _safetyMonitor.IsEmergencyActive || _safetyMonitor.IsAlarmOutputOn; }); } private async Task RefreshSafetyReadingsAsync() { if (_safetyReadingsBusy) return; _safetyReadingsBusy = true; try { SafetyPowerCurrentText = await ReadPowerCurrentTextAsync().ConfigureAwait(true); SafetyLoadCurrentText = await ReadLoadCurrentTextAsync().ConfigureAwait(true); } finally { _safetyReadingsBusy = false; } } private async Task ReadPowerCurrentTextAsync() { if (!_power.IsConnected) return "未连接"; try { var (_, amps) = await _power.ReadAsync().ConfigureAwait(true); return $"{amps:F3} A"; } catch (Exception ex) { _logger.LogDebug(ex, "Read UDP5080 current for safety panel failed."); return "读取失败"; } } private async Task ReadLoadCurrentTextAsync() { if (!_load.IsConnected) return "未连接"; try { var (_, amps) = await _load.ReadAsync().ConfigureAwait(true); return $"{amps:F3} A"; } catch (Exception ex) { _logger.LogDebug(ex, "Read IT8702P current for safety panel failed."); return "读取失败"; } } private static string DeviceStatusText(IDeviceConnection device) => device.IsConnected ? "已连接" : "未连接"; [RelayCommand] private void ToggleDevicePanel() { RefreshDeviceStatus(); DevicePanelOpen = !DevicePanelOpen; } [RelayCommand] private void ToggleSafetyPanel() { RefreshSafetyStatus(); SafetyPanelOpen = !SafetyPanelOpen; } [RelayCommand] private async Task ResetSafetyAlarm() { await _safetyMonitor.ResetAsync().ConfigureAwait(false); RefreshSafetyStatus(); } [RelayCommand] private async Task ConnectAllDevices() { if (DeviceOperationBusy) return; DeviceOperationBusy = true; try { await Task.WhenAll( ConnectOneAsync("SSPC", _sspc, true), ConnectOneAsync("电源", _power, true), ConnectOneAsync("负载", _load, true), ConnectOneAsync("PCIe8586", _daq, true), ConnectOneAsync("PCI2312", _safetyDio, _deviceOptions.Pci2312.Enabled)); } finally { DeviceOperationBusy = false; RefreshDeviceStatus(); } } [RelayCommand] private async Task DisconnectAllDevices() { if (DeviceOperationBusy) return; DeviceOperationBusy = true; try { await Task.WhenAll( SafeDisconnectAsync("SSPC", _sspc), SafeDisconnectAsync("电源", _power), SafeDisconnectAsync("负载", _load), SafeDisconnectAsync("PCIe8586", _daq), SafeDisconnectAsync("PCI2312", _safetyDio)); } finally { DeviceOperationBusy = false; RefreshDeviceStatus(); } } [RelayCommand] private void RefreshDevices() => RefreshDeviceStatus(); private async Task SafeDisconnectAsync(string name, IDeviceConnection device) { try { await device.DisconnectAsync(); _logSink.Add(UiLogLevel.Info, $"{name} 已断开。"); } catch (Exception ex) { _logger.LogWarning(ex, "{Device} disconnect failed.", name); _logSink.Add(UiLogLevel.Error, $"{name} 断开失败:{ex.Message}"); } } public async Task LoadProjectsAsync() { AllProjects.Clear(); var list = await _store.LoadAllAsync(); foreach (var p in list) AllProjects.Add(p); RebuildTree(); if (CurrentView == null) NavigateHome(); } public void RebuildTree() { TreeRoots.Clear(); var src = State == AppState.Testing && CurrentProject != null ? new[] { CurrentProject } : AllProjects.ToArray(); foreach (var p in src) { var node = new ProjectNode(p); node.Children.Add(new ProjectNode(p, NodeKind.Info)); node.Children.Add(new ProjectNode(p, NodeKind.Test)); node.Children.Add(new ProjectNode(p, NodeKind.Report)); TreeRoots.Add(node); } } [RelayCommand] private void NavigateHome() { CurrentView = _sp.GetRequiredService(); } [RelayCommand] private async Task NewProject() { var dlg = new Views.NewProjectDialog { Owner = Application.Current?.MainWindow }; if (dlg.ShowDialog() != true) return; var p = new Project { Name = dlg.ProjectName, Dut = new DutInfo { ProductName = dlg.ProductName, SerialNumber = dlg.SerialNumber, Model = dlg.Model, }, Site = new TestSiteInfo { Tester = dlg.Tester, TestDate = DateTime.Today, } }; await _store.SaveAsync(p); AllProjects.Insert(0, p); CurrentProject = p; OpenProjectInfo(p); } public void OpenProjectInfo(Project p) { CurrentProject = p; var vm = _sp.GetRequiredService(); vm.SetProject(p, StartTesting); CurrentView = vm; } public void OpenTestHost(Project p) { CurrentProject = p; var vm = _sp.GetRequiredService(); vm.SetProject(p); CurrentView = vm; } public void OpenReport(Project p) { CurrentProject = p; var vm = _sp.GetRequiredService(); vm.SetProject(p); CurrentView = vm; } [RelayCommand] private void OpenSettings() { if (CurrentView is SettingsViewModel) { CloseSettings(); return; } _viewBeforeSettings = CurrentView; var vm = _sp.GetRequiredService(); vm.SetCloseAction(CloseSettings); CurrentView = vm; DevicePanelOpen = false; } [RelayCommand] private void OpenSettingsFromDevicePanel() { DevicePanelOpen = false; OpenSettings(); } private void CloseSettings() { CurrentView = _viewBeforeSettings ?? _sp.GetRequiredService(); _viewBeforeSettings = null; } /// 从项目信息页点击"开始测试"调用。 public async void StartTesting(Project p) { CurrentProject = p; State = AppState.Testing; TestingMessage = $"测试进行中:{p.Name}"; var session = new TestSessionViewModel(_sp, p, _sspc, _power, _load, _daq, _logSink); CurrentSession = session; var host = _sp.GetRequiredService(); host.SetProject(p); host.AttachSession(session); CurrentView = host; try { await session.RunAllAsync(); _logSink.Add(UiLogLevel.Success, "全部测试完成。"); } catch (OperationCanceledException) { _logSink.Add(UiLogLevel.Warning, "测试已中止。"); } catch (Exception ex) { _logger.LogError(ex, "Test failed."); _logSink.Add(UiLogLevel.Error, "测试出错:" + ex.Message); } finally { await _store.SaveAsync(p); State = AppState.Browsing; CurrentSession = null; } } [RelayCommand] private void AbortTest() { CurrentSession?.Cancel(); } [RelayCommand] private void OpenProjectsFolder() { if (Directory.Exists(_store.ProjectsRoot)) System.Diagnostics.Process.Start("explorer.exe", _store.ProjectsRoot); } /// 由 View 双击树节点时调用。 public void HandleNodeActivated(ProjectNode? node) { if (node == null) return; switch (node.Kind) { case NodeKind.Root: case NodeKind.Info: OpenProjectInfo(node.Project); break; case NodeKind.Test: OpenTestHost(node.Project); break; case NodeKind.Report: OpenReport(node.Project); break; } } } /// 侧栏 TreeView 用的节点。 public sealed class ProjectNode { public Project Project { get; } public NodeKind Kind { get; } public string Title { get; } public ObservableCollection Children { get; } = new(); public ProjectNode(Project p, NodeKind kind = NodeKind.Root) { Project = p; Kind = kind; Title = kind switch { NodeKind.Root => p.Name, NodeKind.Info => "项目信息", NodeKind.Test => "测试", NodeKind.Report => "报告", _ => p.Name }; } } public enum NodeKind { Root, Info, Test, Report }