using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using Serilog; using YZWater.Core.Models; using YZWater.Core.Services; namespace YZWater.Core.ViewModels; /// /// 主工艺视图 ViewModel(接入真实 PLC 数据) /// public partial class ViewAViewModel : ObservableObject, IDisposable { private readonly LanguageService _lang = LanguageService.Instance; private readonly SynchronizationContext? _uiContext = SynchronizationContext.Current; private DateTime _startTime = DateTime.Now; private System.Timers.Timer? _clockTimer; private bool _disposed; // ═══════════════════════════════════════════════ // 水箱液位 // ═══════════════════════════════════════════════ [ObservableProperty] private float _tank1Level; [ObservableProperty] private float _tank2Level; [ObservableProperty] private float _tank3Level; [ObservableProperty] private float _tank4Level; // ═══════════════════════════════════════════════ // 流量 // ═══════════════════════════════════════════════ [ObservableProperty] private float _inflowRate; [ObservableProperty] private float _outflowRate; [ObservableProperty] private float _flowDelta; // ═══════════════════════════════════════════════ // 泵状态 // ═══════════════════════════════════════════════ [ObservableProperty] private bool _pump1Running; [ObservableProperty] private bool _pump2Running; [ObservableProperty] private bool _pump3Running; [ObservableProperty] private bool _pump4Running; [ObservableProperty] private bool _pump5Running; // 泵状态颜色 [ObservableProperty] private string _pump1StatusColor = "#4B5563"; [ObservableProperty] private string _pump2StatusColor = "#4B5563"; [ObservableProperty] private string _pump3StatusColor = "#4B5563"; [ObservableProperty] private string _pump4StatusColor = "#4B5563"; [ObservableProperty] private string _pump5StatusColor = "#4B5563"; // 泵状态文本 [ObservableProperty] private string _pump1Status = "STOPPED"; [ObservableProperty] private string _pump2Status = "STOPPED"; [ObservableProperty] private string _pump3Status = "STOPPED"; [ObservableProperty] private string _pump4Status = "STOPPED"; [ObservableProperty] private string _pump5Status = "STOPPED"; // ═══════════════════════════════════════════════ // 风扇状态 // ═══════════════════════════════════════════════ [ObservableProperty] private bool _fan1Running; [ObservableProperty] private bool _fan2Running; [ObservableProperty] private string _fan1StatusColor = "#4B5563"; [ObservableProperty] private string _fan2StatusColor = "#4B5563"; [ObservableProperty] private string _fan1Status = "STOPPED"; [ObservableProperty] private string _fan2Status = "STOPPED"; // ═══════════════════════════════════════════════ // 阀门状态 // ═══════════════════════════════════════════════ [ObservableProperty] private ValveStatus _valve1Status = ValveStatus.Middle; [ObservableProperty] private ValveStatus _valve2Status = ValveStatus.Middle; [ObservableProperty] private ValveStatus _valve3Status = ValveStatus.Middle; [ObservableProperty] private ValveStatus _valve4Status = ValveStatus.Middle; [ObservableProperty] private string _valve1StatusColor = "#4B5563"; [ObservableProperty] private string _valve2StatusColor = "#4B5563"; [ObservableProperty] private string _valve3StatusColor = "#4B5563"; [ObservableProperty] private string _valve4StatusColor = "#4B5563"; // 模式 [ObservableProperty] private ValveStatus _modeStatus = ValveStatus.Open; // 流动状态 [ObservableProperty] private bool _isInflowRunning = true; [ObservableProperty] private bool _isOutflowRunning = true; // ═══════════════════════════════════════════════ // 统计 // ═══════════════════════════════════════════════ [ObservableProperty] private int _runningDeviceCount; [ObservableProperty] private string _runningTime = "0d 0h 0m"; [ObservableProperty] private double _efficiency = 90.0; // ═══════════════════════════════════════════════ // 报警 // ═══════════════════════════════════════════════ [ObservableProperty] private bool _hasAlarm; [ObservableProperty] private string _alarmMessage = string.Empty; [ObservableProperty] private int _alarmCount; // ═══════════════════════════════════════════════ // 连接状态 // ═══════════════════════════════════════════════ [ObservableProperty] private bool _isPlcConnected; // ═══════════════════════════════════════════════ // 时间 // ═══════════════════════════════════════════════ [ObservableProperty] private DateTime _currentTime = DateTime.Now; // ═══════════════════════════════════════════════ // 翻译文字 // ═══════════════════════════════════════════════ [ObservableProperty] private string _titleText; [ObservableProperty] private string _subtitleText; [ObservableProperty] private string _plcStatusText; [ObservableProperty] private string _connectText; [ObservableProperty] private string _disconnectText; [ObservableProperty] private string _refreshText; [ObservableProperty] private string _tank1Text; [ObservableProperty] private string _tank2Text; [ObservableProperty] private string _tank3Text; [ObservableProperty] private string _tank4Text; [ObservableProperty] private string _pump1Text; [ObservableProperty] private string _pump2Text; [ObservableProperty] private string _pump3Text; [ObservableProperty] private string _pump4Text; [ObservableProperty] private string _pump5Text; [ObservableProperty] private string _fan1Text; [ObservableProperty] private string _fan2Text; [ObservableProperty] private string _valve1Text; [ObservableProperty] private string _valve2Text; [ObservableProperty] private string _valve3Text; [ObservableProperty] private string _valve4Text; [ObservableProperty] private string _modeText; [ObservableProperty] private string _realtimeText; [ObservableProperty] private string _inflowText; [ObservableProperty] private string _outflowText; [ObservableProperty] private string _deltaText; [ObservableProperty] private string _statisticsText; [ObservableProperty] private string _runtimeText; [ObservableProperty] private string _devicesText; [ObservableProperty] private string _alarmText; [ObservableProperty] private string _efficiencyText; [ObservableProperty] private string _quickOpsText; [ObservableProperty] private string _startAllText; [ObservableProperty] private string _stopAllText; [ObservableProperty] private string _ackAlarmText; [ObservableProperty] private string _exportText; [ObservableProperty] private string _flowMetersText; [ObservableProperty] private string _deviceStatusText; [ObservableProperty] private string _processFlowText; [ObservableProperty] private string _pump1TitleText; [ObservableProperty] private string _pump2TitleText; [ObservableProperty] private string _refluxPumpText; [ObservableProperty] private string _fan1TitleText; [ObservableProperty] private string _fan2TitleText; [ObservableProperty] [NotifyPropertyChangedFor(nameof(Is2DView))] private bool _is3DView = true; public bool Is2DView => !Is3DView; public ViewAViewModel() { UpdateTexts(); _lang.LanguageChanged += OnLanguageChanged; // 订阅 PLC 轮询数据 PlcPollingService.Instance.DataUpdated += OnDataUpdated; PlcPollingService.Instance.ConnectionStateChanged += OnConnectionStateChanged; // 订阅报警 AlarmService.Instance.AlarmStateChanged += OnAlarmStateChanged; // 时钟更新 StartClockTimer(); } private void OnLanguageChanged() => RunOnUi(UpdateTexts); private void RunOnUi(Action action) { if (_disposed) return; if (_uiContext != null && SynchronizationContext.Current != _uiContext) _uiContext.Post(_ => { if (!_disposed) action(); }, null); else action(); } private void UpdateTexts() { TitleText = _lang.Get("AppName"); SubtitleText = _lang.Get("ProcessFlow"); PlcStatusText = _lang.Get("PlcConnected"); ConnectText = _lang.Get("Connect"); DisconnectText = _lang.Get("Disconnect"); RefreshText = _lang.Get("Refresh"); Tank1Text = _lang.Get("TankInlet"); Tank2Text = _lang.Get("TankBio"); Tank3Text = _lang.Get("TankSediment"); Tank4Text = _lang.Get("TankOutlet"); Pump1Text = _lang.Get("Pump1"); Pump2Text = _lang.Get("Pump2"); Pump3Text = _lang.Get("Pump3"); Pump4Text = _lang.Get("Pump4"); Pump5Text = _lang.Get("Pump5"); Fan1Text = _lang.Get("Fan1"); Fan2Text = _lang.Get("Fan2"); Valve1Text = _lang.Get("ValveInlet"); Valve2Text = _lang.Get("ValveAeration"); Valve3Text = _lang.Get("ValveReflux"); Valve4Text = _lang.Get("ValveOutlet"); ModeText = _lang.Get("Mode"); RealtimeText = _lang.Get("Realtime"); InflowText = _lang.Get("Inflow"); OutflowText = _lang.Get("Outflow"); DeltaText = _lang.Get("Delta"); StatisticsText = _lang.Get("Statistics"); RuntimeText = _lang.Get("Runtime"); DevicesText = _lang.Get("Devices"); AlarmText = _lang.Get("Alarm"); EfficiencyText = _lang.Get("Efficiency"); QuickOpsText = _lang.Get("QuickOps"); StartAllText = _lang.Get("StartAll"); StopAllText = _lang.Get("StopAll"); AckAlarmText = _lang.Get("AckAlarm"); ExportText = _lang.Get("Export"); FlowMetersText = _lang.Get("FlowMeters"); DeviceStatusText = _lang.Get("DeviceStatus"); ProcessFlowText = _lang.Get("ProcessFlow"); Pump1TitleText = _lang.Get("Pump1Title"); Pump2TitleText = _lang.Get("Pump2Title"); RefluxPumpText = _lang.Get("RefluxPump"); Fan1TitleText = _lang.Get("Fan1Title"); Fan2TitleText = _lang.Get("Fan2Title"); } private void StartClockTimer() { _clockTimer = new System.Timers.Timer(1000); _clockTimer.Elapsed += (s, e) => { RunOnUi(() => { CurrentTime = DateTime.Now; var elapsed = DateTime.Now - _startTime; RunningTime = $"{elapsed.Days}d {elapsed.Hours}h {elapsed.Minutes}m"; }); }; _clockTimer.Start(); } /// /// PLC 数据更新回调 /// private void OnDataUpdated(PlcDataModel data) { var snapshot = CreateSnapshot(data); RunOnUi(() => ApplyData(snapshot)); } /// /// 仅在值变化时更新属性(使用 Object.Equals 比较,适用于所有类型) /// private bool UpdateIfChanged(ref T field, T newValue, [System.Runtime.CompilerServices.CallerMemberName] string? propertyName = null) { if (!Equals(field, newValue)) { field = newValue; OnPropertyChanged(propertyName); return true; } return false; } private void ApplyData(PlcDataModel data) { // 更新水箱液位(仅在值变化时通知) UpdateIfChanged(ref _tank1Level, data.Tank1Level); UpdateIfChanged(ref _tank2Level, data.Tank2Level); UpdateIfChanged(ref _tank3Level, data.Tank3Level); UpdateIfChanged(ref _tank4Level, data.Tank4Level); // 更新流量 UpdateIfChanged(ref _inflowRate, data.InflowRate); UpdateIfChanged(ref _outflowRate, data.OutflowRate); UpdateIfChanged(ref _flowDelta, data.FlowDelta); // 更新泵状态 UpdateIfChanged(ref _pump1Running, data.Pump1Running); UpdateIfChanged(ref _pump2Running, data.Pump2Running); UpdateIfChanged(ref _pump3Running, data.Pump3Running); UpdateIfChanged(ref _pump4Running, data.Pump4Running); UpdateIfChanged(ref _pump5Running, data.Pump5Running); // 更新风扇状态 UpdateIfChanged(ref _fan1Running, data.Fan1Running); UpdateIfChanged(ref _fan2Running, data.Fan2Running); // 更新阀门状态 var v1 = data.Valve1Position > 50 ? ValveStatus.Open : data.Valve1Position < 50 ? ValveStatus.Closed : ValveStatus.Middle; var v2 = data.Valve2Position > 50 ? ValveStatus.Open : data.Valve2Position < 50 ? ValveStatus.Closed : ValveStatus.Middle; var v3 = data.Valve3Position > 50 ? ValveStatus.Open : data.Valve3Position < 50 ? ValveStatus.Closed : ValveStatus.Middle; var v4 = data.Valve4Position > 50 ? ValveStatus.Open : data.Valve4Position < 50 ? ValveStatus.Closed : ValveStatus.Middle; UpdateIfChanged(ref _valve1Status, v1); UpdateIfChanged(ref _valve2Status, v2); UpdateIfChanged(ref _valve3Status, v3); UpdateIfChanged(ref _valve4Status, v4); // 更新模式 var mode = data.IsAutoMode ? ValveStatus.Open : ValveStatus.Closed; UpdateIfChanged(ref _modeStatus, mode); // 更新流动状态 UpdateIfChanged(ref _isInflowRunning, data.InflowRate > 0); UpdateIfChanged(ref _isOutflowRunning, data.OutflowRate > 0); // 更新设备状态 UpdateDeviceStatuses(data); // 更新统计 var deviceCount = data.RunningPumpCount + data.RunningFanCount; UpdateIfChanged(ref _runningDeviceCount, deviceCount); var efficiency = data.InflowRate > 0 ? Math.Clamp(data.OutflowRate / data.InflowRate * 100, 0, 100) : 0; UpdateIfChanged(ref _efficiency, (float)efficiency); // 检查报警 CheckAlarms(data); } /// /// 连接状态变更 /// private void OnConnectionStateChanged(bool connected) { RunOnUi(() => IsPlcConnected = connected); } /// /// 报警状态变更 /// private void OnAlarmStateChanged() { RunOnUi(() => { HasAlarm = AlarmService.Instance.HasActiveAlarm; AlarmMessage = AlarmService.Instance.ActiveAlarmMessage; AlarmCount = AlarmService.Instance.ActiveAlarmCount; }); } private static PlcDataModel CreateSnapshot(PlcDataModel data) => new() { Tank1Level = data.Tank1Level, Tank2Level = data.Tank2Level, Tank3Level = data.Tank3Level, Tank4Level = data.Tank4Level, InflowRate = data.InflowRate, OutflowRate = data.OutflowRate, FlowDelta = data.FlowDelta, Pump1Running = data.Pump1Running, Pump2Running = data.Pump2Running, Pump3Running = data.Pump3Running, Pump4Running = data.Pump4Running, Pump5Running = data.Pump5Running, Pump1Fault = data.Pump1Fault, Pump2Fault = data.Pump2Fault, Pump3Fault = data.Pump3Fault, Pump4Fault = data.Pump4Fault, Pump5Fault = data.Pump5Fault, Pump1Freq = data.Pump1Freq, Pump2Freq = data.Pump2Freq, Pump3Freq = data.Pump3Freq, Pump4Freq = data.Pump4Freq, Pump5Freq = data.Pump5Freq, Fan1Running = data.Fan1Running, Fan2Running = data.Fan2Running, Fan1Fault = data.Fan1Fault, Fan2Fault = data.Fan2Fault, Valve1Position = data.Valve1Position, Valve2Position = data.Valve2Position, Valve3Position = data.Valve3Position, Valve4Position = data.Valve4Position, IsAutoMode = data.IsAutoMode, LastReadTime = data.LastReadTime, IsDataValid = data.IsDataValid, IsPlcConnected = data.IsPlcConnected }; /// /// 更新设备状态 /// private void UpdateDeviceStatuses(PlcDataModel data) { // 仅在状态变化时更新(避免不必要的字符串分配和通知) UpdateIfChanged(ref _pump1StatusColor, data.Pump1Running ? "#00897B" : (data.Pump1Fault ? "#EF4444" : "#4B5563")); UpdateIfChanged(ref _pump2StatusColor, data.Pump2Running ? "#00897B" : (data.Pump2Fault ? "#EF4444" : "#4B5563")); UpdateIfChanged(ref _pump3StatusColor, data.Pump3Running ? "#00897B" : (data.Pump3Fault ? "#EF4444" : "#4B5563")); UpdateIfChanged(ref _pump4StatusColor, data.Pump4Running ? "#00897B" : (data.Pump4Fault ? "#EF4444" : "#4B5563")); UpdateIfChanged(ref _pump5StatusColor, data.Pump5Running ? "#00897B" : (data.Pump5Fault ? "#EF4444" : "#4B5563")); UpdateIfChanged(ref _fan1StatusColor, data.Fan1Running ? "#00897B" : (data.Fan1Fault ? "#EF4444" : "#4B5563")); UpdateIfChanged(ref _fan2StatusColor, data.Fan2Running ? "#00897B" : (data.Fan2Fault ? "#EF4444" : "#4B5563")); UpdateIfChanged(ref _valve1StatusColor, data.Valve1Position > 50 ? "#00897B" : data.Valve1Position < 50 ? "#F59E0B" : "#4B5563"); UpdateIfChanged(ref _valve2StatusColor, data.Valve2Position > 50 ? "#00897B" : data.Valve2Position < 50 ? "#F59E0B" : "#4B5563"); UpdateIfChanged(ref _valve3StatusColor, data.Valve3Position > 50 ? "#00897B" : data.Valve3Position < 50 ? "#F59E0B" : "#4B5563"); UpdateIfChanged(ref _valve4StatusColor, data.Valve4Position > 50 ? "#00897B" : data.Valve4Position < 50 ? "#F59E0B" : "#4B5563"); UpdateIfChanged(ref _pump1Status, data.Pump1Running ? $"RUNNING {data.Pump1Freq:F0}Hz" : (data.Pump1Fault ? "FAULT" : "STOPPED")); UpdateIfChanged(ref _pump2Status, data.Pump2Running ? $"RUNNING {data.Pump2Freq:F0}Hz" : (data.Pump2Fault ? "FAULT" : "STOPPED")); UpdateIfChanged(ref _pump3Status, data.Pump3Running ? $"RUNNING {data.Pump3Freq:F0}Hz" : (data.Pump3Fault ? "FAULT" : "STOPPED")); UpdateIfChanged(ref _pump4Status, data.Pump4Running ? $"RUNNING {data.Pump4Freq:F0}Hz" : (data.Pump4Fault ? "FAULT" : "STOPPED")); UpdateIfChanged(ref _pump5Status, data.Pump5Running ? $"RUNNING {data.Pump5Freq:F0}Hz" : (data.Pump5Fault ? "FAULT" : "STOPPED")); UpdateIfChanged(ref _fan1Status, data.Fan1Running ? "RUNNING" : (data.Fan1Fault ? "FAULT" : "STOPPED")); UpdateIfChanged(ref _fan2Status, data.Fan2Running ? "RUNNING" : (data.Fan2Fault ? "FAULT" : "STOPPED")); } /// /// 检查报警(从 PlcPollingService 数据) /// private void CheckAlarms(PlcDataModel data) { var config = ConfigService.GetConfig(); if (config == null) return; HasAlarm = false; AlarmMessage = string.Empty; AlarmCount = 0; if (data.Tank1Level > config.LevelHighAlarm || data.Tank1Level < config.LevelLowAlarm) { HasAlarm = true; AlarmCount++; } if (data.Tank2Level > config.LevelHighAlarm || data.Tank2Level < config.LevelLowAlarm) { HasAlarm = true; AlarmCount++; } if (data.InflowRate > config.FlowHighAlarm) { HasAlarm = true; AlarmCount++; } if (data.HasAnyFault) { HasAlarm = true; AlarmCount++; } RunningDeviceCount = data.RunningPumpCount + data.RunningFanCount; } // ═══════════════════════════════════════════════ // PLC 控制命令 // ═══════════════════════════════════════════════ [RelayCommand] private async Task ConnectPlcAsync() => IsPlcConnected = await PlcService.ConnectAsync(); [RelayCommand] private void DisconnectPlc() { PlcService.Disconnect(); IsPlcConnected = false; } [RelayCommand] private async Task RefreshDataAsync() => await PlcPollingService.Instance.RefreshNowAsync(); [RelayCommand] private void Set2DView() => Is3DView = false; [RelayCommand] private void Set3DView() => Is3DView = true; /// /// 控制泵启停 /// [RelayCommand] private async Task TogglePumpAsync(string pumpId) { if (!PlcService.IsConnected) { Log.Warning("PLC 未连接"); return; } var index = pumpId switch { "P001" => 0, "P002" => 1, "P003" => 2, "P004" => 3, "P005" => 4, _ => -1 }; if (index < 0) return; var address = PlcConfig.GetPumpRunAddress(index); var currentState = PlcPollingService.Instance.Data.GetPumpRunning(index); await PlcService.WriteBoolAsync(address, !currentState); } /// /// 控制风机启停 /// [RelayCommand] private async Task ToggleFanAsync(string fanId) { if (!PlcService.IsConnected) { Log.Warning("PLC 未连接"); return; } var address = fanId == "F001" ? PlcConfig.Fan1Start : PlcConfig.Fan2Start; var currentState = fanId == "F001" ? PlcPollingService.Instance.Data.Fan1Running : PlcPollingService.Instance.Data.Fan2Running; await PlcService.WriteBoolAsync(address, !currentState); } /// /// 控制阀门 /// [RelayCommand] private async Task ToggleValveAsync(string valveId) { if (!PlcService.IsConnected) { Log.Warning("PLC 未连接"); return; } var index = valveId switch { "V001" => 0, "V002" => 1, "V003" => 2, "V004" => 3, _ => -1 }; if (index < 0) return; var address = PlcConfig.GetValveControlAddress(index); await PlcService.WriteBoolAsync(address, true); await Task.Delay(500); await PlcService.WriteBoolAsync(address, false); } /// /// 切换模式 /// [RelayCommand] private async Task ToggleModeAsync() { if (!PlcService.IsConnected) return; await PlcService.WriteBoolAsync(PlcConfig.SystemMode, ModeStatus != ValveStatus.Open); } /// /// 全部启动 /// [RelayCommand] private async Task StartAllAsync() { if (!PlcService.IsConnected) return; for (int i = 0; i < 5; i++) await PlcService.WriteBoolAsync(PlcConfig.GetPumpRunAddress(i), true); for (int i = 0; i < 2; i++) await PlcService.WriteBoolAsync(i == 0 ? PlcConfig.Fan1Start : PlcConfig.Fan2Start, true); } /// /// 全部停止 /// [RelayCommand] private async Task StopAllAsync() { if (!PlcService.IsConnected) return; for (int i = 0; i < 5; i++) await PlcService.WriteBoolAsync(PlcConfig.GetPumpRunAddress(i), false); for (int i = 0; i < 2; i++) await PlcService.WriteBoolAsync(i == 0 ? PlcConfig.Fan1Start : PlcConfig.Fan2Start, false); } /// /// 确认报警 /// [RelayCommand] private async Task AckAlarmAsync() { await AlarmService.Instance.AcknowledgeAllAsync(); } public void Dispose() { if (_disposed) return; _disposed = true; _clockTimer?.Stop(); _clockTimer?.Dispose(); _clockTimer = null; _lang.LanguageChanged -= OnLanguageChanged; PlcPollingService.Instance.DataUpdated -= OnDataUpdated; PlcPollingService.Instance.ConnectionStateChanged -= OnConnectionStateChanged; AlarmService.Instance.AlarmStateChanged -= OnAlarmStateChanged; } } /// /// 阀门状态枚举 /// public enum ValveStatus { Closed, Middle, Open }