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 }