ViewAViewModel.cs 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601
  1. using CommunityToolkit.Mvvm.ComponentModel;
  2. using CommunityToolkit.Mvvm.Input;
  3. using Serilog;
  4. using YZWater.Core.Models;
  5. using YZWater.Core.Services;
  6. namespace YZWater.Core.ViewModels;
  7. /// <summary>
  8. /// 主工艺视图 ViewModel(接入真实 PLC 数据)
  9. /// </summary>
  10. public partial class ViewAViewModel : ObservableObject, IDisposable
  11. {
  12. private readonly LanguageService _lang = LanguageService.Instance;
  13. private readonly SynchronizationContext? _uiContext = SynchronizationContext.Current;
  14. private DateTime _startTime = DateTime.Now;
  15. private System.Timers.Timer? _clockTimer;
  16. private bool _disposed;
  17. // ═══════════════════════════════════════════════
  18. // 水箱液位
  19. // ═══════════════════════════════════════════════
  20. [ObservableProperty] private float _tank1Level;
  21. [ObservableProperty] private float _tank2Level;
  22. [ObservableProperty] private float _tank3Level;
  23. [ObservableProperty] private float _tank4Level;
  24. // ═══════════════════════════════════════════════
  25. // 流量
  26. // ═══════════════════════════════════════════════
  27. [ObservableProperty] private float _inflowRate;
  28. [ObservableProperty] private float _outflowRate;
  29. [ObservableProperty] private float _flowDelta;
  30. // ═══════════════════════════════════════════════
  31. // 泵状态
  32. // ═══════════════════════════════════════════════
  33. [ObservableProperty] private bool _pump1Running;
  34. [ObservableProperty] private bool _pump2Running;
  35. [ObservableProperty] private bool _pump3Running;
  36. [ObservableProperty] private bool _pump4Running;
  37. [ObservableProperty] private bool _pump5Running;
  38. // 泵状态颜色
  39. [ObservableProperty] private string _pump1StatusColor = "#4B5563";
  40. [ObservableProperty] private string _pump2StatusColor = "#4B5563";
  41. [ObservableProperty] private string _pump3StatusColor = "#4B5563";
  42. [ObservableProperty] private string _pump4StatusColor = "#4B5563";
  43. [ObservableProperty] private string _pump5StatusColor = "#4B5563";
  44. // 泵状态文本
  45. [ObservableProperty] private string _pump1Status = "STOPPED";
  46. [ObservableProperty] private string _pump2Status = "STOPPED";
  47. [ObservableProperty] private string _pump3Status = "STOPPED";
  48. [ObservableProperty] private string _pump4Status = "STOPPED";
  49. [ObservableProperty] private string _pump5Status = "STOPPED";
  50. // ═══════════════════════════════════════════════
  51. // 风扇状态
  52. // ═══════════════════════════════════════════════
  53. [ObservableProperty] private bool _fan1Running;
  54. [ObservableProperty] private bool _fan2Running;
  55. [ObservableProperty] private string _fan1StatusColor = "#4B5563";
  56. [ObservableProperty] private string _fan2StatusColor = "#4B5563";
  57. [ObservableProperty] private string _fan1Status = "STOPPED";
  58. [ObservableProperty] private string _fan2Status = "STOPPED";
  59. // ═══════════════════════════════════════════════
  60. // 阀门状态
  61. // ═══════════════════════════════════════════════
  62. [ObservableProperty] private ValveStatus _valve1Status = ValveStatus.Middle;
  63. [ObservableProperty] private ValveStatus _valve2Status = ValveStatus.Middle;
  64. [ObservableProperty] private ValveStatus _valve3Status = ValveStatus.Middle;
  65. [ObservableProperty] private ValveStatus _valve4Status = ValveStatus.Middle;
  66. [ObservableProperty] private string _valve1StatusColor = "#4B5563";
  67. [ObservableProperty] private string _valve2StatusColor = "#4B5563";
  68. [ObservableProperty] private string _valve3StatusColor = "#4B5563";
  69. [ObservableProperty] private string _valve4StatusColor = "#4B5563";
  70. // 模式
  71. [ObservableProperty] private ValveStatus _modeStatus = ValveStatus.Open;
  72. // 流动状态
  73. [ObservableProperty] private bool _isInflowRunning = true;
  74. [ObservableProperty] private bool _isOutflowRunning = true;
  75. // ═══════════════════════════════════════════════
  76. // 统计
  77. // ═══════════════════════════════════════════════
  78. [ObservableProperty] private int _runningDeviceCount;
  79. [ObservableProperty] private string _runningTime = "0d 0h 0m";
  80. [ObservableProperty] private double _efficiency = 90.0;
  81. // ═══════════════════════════════════════════════
  82. // 报警
  83. // ═══════════════════════════════════════════════
  84. [ObservableProperty] private bool _hasAlarm;
  85. [ObservableProperty] private string _alarmMessage = string.Empty;
  86. [ObservableProperty] private int _alarmCount;
  87. // ═══════════════════════════════════════════════
  88. // 连接状态
  89. // ═══════════════════════════════════════════════
  90. [ObservableProperty] private bool _isPlcConnected;
  91. // ═══════════════════════════════════════════════
  92. // 时间
  93. // ═══════════════════════════════════════════════
  94. [ObservableProperty] private DateTime _currentTime = DateTime.Now;
  95. // ═══════════════════════════════════════════════
  96. // 翻译文字
  97. // ═══════════════════════════════════════════════
  98. [ObservableProperty] private string _titleText;
  99. [ObservableProperty] private string _subtitleText;
  100. [ObservableProperty] private string _plcStatusText;
  101. [ObservableProperty] private string _connectText;
  102. [ObservableProperty] private string _disconnectText;
  103. [ObservableProperty] private string _refreshText;
  104. [ObservableProperty] private string _tank1Text;
  105. [ObservableProperty] private string _tank2Text;
  106. [ObservableProperty] private string _tank3Text;
  107. [ObservableProperty] private string _tank4Text;
  108. [ObservableProperty] private string _pump1Text;
  109. [ObservableProperty] private string _pump2Text;
  110. [ObservableProperty] private string _pump3Text;
  111. [ObservableProperty] private string _pump4Text;
  112. [ObservableProperty] private string _pump5Text;
  113. [ObservableProperty] private string _fan1Text;
  114. [ObservableProperty] private string _fan2Text;
  115. [ObservableProperty] private string _valve1Text;
  116. [ObservableProperty] private string _valve2Text;
  117. [ObservableProperty] private string _valve3Text;
  118. [ObservableProperty] private string _valve4Text;
  119. [ObservableProperty] private string _modeText;
  120. [ObservableProperty] private string _realtimeText;
  121. [ObservableProperty] private string _inflowText;
  122. [ObservableProperty] private string _outflowText;
  123. [ObservableProperty] private string _deltaText;
  124. [ObservableProperty] private string _statisticsText;
  125. [ObservableProperty] private string _runtimeText;
  126. [ObservableProperty] private string _devicesText;
  127. [ObservableProperty] private string _alarmText;
  128. [ObservableProperty] private string _efficiencyText;
  129. [ObservableProperty] private string _quickOpsText;
  130. [ObservableProperty] private string _startAllText;
  131. [ObservableProperty] private string _stopAllText;
  132. [ObservableProperty] private string _ackAlarmText;
  133. [ObservableProperty] private string _exportText;
  134. [ObservableProperty] private string _flowMetersText;
  135. [ObservableProperty] private string _deviceStatusText;
  136. [ObservableProperty] private string _processFlowText;
  137. [ObservableProperty] private string _pump1TitleText;
  138. [ObservableProperty] private string _pump2TitleText;
  139. [ObservableProperty] private string _refluxPumpText;
  140. [ObservableProperty] private string _fan1TitleText;
  141. [ObservableProperty] private string _fan2TitleText;
  142. [ObservableProperty]
  143. [NotifyPropertyChangedFor(nameof(Is2DView))]
  144. private bool _is3DView = true;
  145. public bool Is2DView => !Is3DView;
  146. public ViewAViewModel()
  147. {
  148. UpdateTexts();
  149. _lang.LanguageChanged += OnLanguageChanged;
  150. // 订阅 PLC 轮询数据
  151. PlcPollingService.Instance.DataUpdated += OnDataUpdated;
  152. PlcPollingService.Instance.ConnectionStateChanged += OnConnectionStateChanged;
  153. // 订阅报警
  154. AlarmService.Instance.AlarmStateChanged += OnAlarmStateChanged;
  155. // 时钟更新
  156. StartClockTimer();
  157. }
  158. private void OnLanguageChanged() => RunOnUi(UpdateTexts);
  159. private void RunOnUi(Action action)
  160. {
  161. if (_disposed) return;
  162. if (_uiContext != null && SynchronizationContext.Current != _uiContext)
  163. _uiContext.Post(_ =>
  164. {
  165. if (!_disposed) action();
  166. }, null);
  167. else
  168. action();
  169. }
  170. private void UpdateTexts()
  171. {
  172. TitleText = _lang.Get("AppName");
  173. SubtitleText = _lang.Get("ProcessFlow");
  174. PlcStatusText = _lang.Get("PlcConnected");
  175. ConnectText = _lang.Get("Connect");
  176. DisconnectText = _lang.Get("Disconnect");
  177. RefreshText = _lang.Get("Refresh");
  178. Tank1Text = _lang.Get("TankInlet");
  179. Tank2Text = _lang.Get("TankBio");
  180. Tank3Text = _lang.Get("TankSediment");
  181. Tank4Text = _lang.Get("TankOutlet");
  182. Pump1Text = _lang.Get("Pump1");
  183. Pump2Text = _lang.Get("Pump2");
  184. Pump3Text = _lang.Get("Pump3");
  185. Pump4Text = _lang.Get("Pump4");
  186. Pump5Text = _lang.Get("Pump5");
  187. Fan1Text = _lang.Get("Fan1");
  188. Fan2Text = _lang.Get("Fan2");
  189. Valve1Text = _lang.Get("ValveInlet");
  190. Valve2Text = _lang.Get("ValveAeration");
  191. Valve3Text = _lang.Get("ValveReflux");
  192. Valve4Text = _lang.Get("ValveOutlet");
  193. ModeText = _lang.Get("Mode");
  194. RealtimeText = _lang.Get("Realtime");
  195. InflowText = _lang.Get("Inflow");
  196. OutflowText = _lang.Get("Outflow");
  197. DeltaText = _lang.Get("Delta");
  198. StatisticsText = _lang.Get("Statistics");
  199. RuntimeText = _lang.Get("Runtime");
  200. DevicesText = _lang.Get("Devices");
  201. AlarmText = _lang.Get("Alarm");
  202. EfficiencyText = _lang.Get("Efficiency");
  203. QuickOpsText = _lang.Get("QuickOps");
  204. StartAllText = _lang.Get("StartAll");
  205. StopAllText = _lang.Get("StopAll");
  206. AckAlarmText = _lang.Get("AckAlarm");
  207. ExportText = _lang.Get("Export");
  208. FlowMetersText = _lang.Get("FlowMeters");
  209. DeviceStatusText = _lang.Get("DeviceStatus");
  210. ProcessFlowText = _lang.Get("ProcessFlow");
  211. Pump1TitleText = _lang.Get("Pump1Title");
  212. Pump2TitleText = _lang.Get("Pump2Title");
  213. RefluxPumpText = _lang.Get("RefluxPump");
  214. Fan1TitleText = _lang.Get("Fan1Title");
  215. Fan2TitleText = _lang.Get("Fan2Title");
  216. }
  217. private void StartClockTimer()
  218. {
  219. _clockTimer = new System.Timers.Timer(1000);
  220. _clockTimer.Elapsed += (s, e) =>
  221. {
  222. RunOnUi(() =>
  223. {
  224. CurrentTime = DateTime.Now;
  225. var elapsed = DateTime.Now - _startTime;
  226. RunningTime = $"{elapsed.Days}d {elapsed.Hours}h {elapsed.Minutes}m";
  227. });
  228. };
  229. _clockTimer.Start();
  230. }
  231. /// <summary>
  232. /// PLC 数据更新回调
  233. /// </summary>
  234. private void OnDataUpdated(PlcDataModel data)
  235. {
  236. var snapshot = CreateSnapshot(data);
  237. RunOnUi(() => ApplyData(snapshot));
  238. }
  239. /// <summary>
  240. /// 仅在值变化时更新属性(使用 Object.Equals 比较,适用于所有类型)
  241. /// </summary>
  242. private bool UpdateIfChanged<T>(ref T field, T newValue, [System.Runtime.CompilerServices.CallerMemberName] string? propertyName = null)
  243. {
  244. if (!Equals(field, newValue))
  245. {
  246. field = newValue;
  247. OnPropertyChanged(propertyName);
  248. return true;
  249. }
  250. return false;
  251. }
  252. private void ApplyData(PlcDataModel data)
  253. {
  254. // 更新水箱液位(仅在值变化时通知)
  255. UpdateIfChanged(ref _tank1Level, data.Tank1Level);
  256. UpdateIfChanged(ref _tank2Level, data.Tank2Level);
  257. UpdateIfChanged(ref _tank3Level, data.Tank3Level);
  258. UpdateIfChanged(ref _tank4Level, data.Tank4Level);
  259. // 更新流量
  260. UpdateIfChanged(ref _inflowRate, data.InflowRate);
  261. UpdateIfChanged(ref _outflowRate, data.OutflowRate);
  262. UpdateIfChanged(ref _flowDelta, data.FlowDelta);
  263. // 更新泵状态
  264. UpdateIfChanged(ref _pump1Running, data.Pump1Running);
  265. UpdateIfChanged(ref _pump2Running, data.Pump2Running);
  266. UpdateIfChanged(ref _pump3Running, data.Pump3Running);
  267. UpdateIfChanged(ref _pump4Running, data.Pump4Running);
  268. UpdateIfChanged(ref _pump5Running, data.Pump5Running);
  269. // 更新风扇状态
  270. UpdateIfChanged(ref _fan1Running, data.Fan1Running);
  271. UpdateIfChanged(ref _fan2Running, data.Fan2Running);
  272. // 更新阀门状态
  273. var v1 = data.Valve1Position > 50 ? ValveStatus.Open :
  274. data.Valve1Position < 50 ? ValveStatus.Closed : ValveStatus.Middle;
  275. var v2 = data.Valve2Position > 50 ? ValveStatus.Open :
  276. data.Valve2Position < 50 ? ValveStatus.Closed : ValveStatus.Middle;
  277. var v3 = data.Valve3Position > 50 ? ValveStatus.Open :
  278. data.Valve3Position < 50 ? ValveStatus.Closed : ValveStatus.Middle;
  279. var v4 = data.Valve4Position > 50 ? ValveStatus.Open :
  280. data.Valve4Position < 50 ? ValveStatus.Closed : ValveStatus.Middle;
  281. UpdateIfChanged(ref _valve1Status, v1);
  282. UpdateIfChanged(ref _valve2Status, v2);
  283. UpdateIfChanged(ref _valve3Status, v3);
  284. UpdateIfChanged(ref _valve4Status, v4);
  285. // 更新模式
  286. var mode = data.IsAutoMode ? ValveStatus.Open : ValveStatus.Closed;
  287. UpdateIfChanged(ref _modeStatus, mode);
  288. // 更新流动状态
  289. UpdateIfChanged(ref _isInflowRunning, data.InflowRate > 0);
  290. UpdateIfChanged(ref _isOutflowRunning, data.OutflowRate > 0);
  291. // 更新设备状态
  292. UpdateDeviceStatuses(data);
  293. // 更新统计
  294. var deviceCount = data.RunningPumpCount + data.RunningFanCount;
  295. UpdateIfChanged(ref _runningDeviceCount, deviceCount);
  296. var efficiency = data.InflowRate > 0
  297. ? Math.Clamp(data.OutflowRate / data.InflowRate * 100, 0, 100)
  298. : 0;
  299. UpdateIfChanged(ref _efficiency, (float)efficiency);
  300. // 检查报警
  301. CheckAlarms(data);
  302. }
  303. /// <summary>
  304. /// 连接状态变更
  305. /// </summary>
  306. private void OnConnectionStateChanged(bool connected)
  307. {
  308. RunOnUi(() => IsPlcConnected = connected);
  309. }
  310. /// <summary>
  311. /// 报警状态变更
  312. /// </summary>
  313. private void OnAlarmStateChanged()
  314. {
  315. RunOnUi(() =>
  316. {
  317. HasAlarm = AlarmService.Instance.HasActiveAlarm;
  318. AlarmMessage = AlarmService.Instance.ActiveAlarmMessage;
  319. AlarmCount = AlarmService.Instance.ActiveAlarmCount;
  320. });
  321. }
  322. private static PlcDataModel CreateSnapshot(PlcDataModel data) => new()
  323. {
  324. Tank1Level = data.Tank1Level,
  325. Tank2Level = data.Tank2Level,
  326. Tank3Level = data.Tank3Level,
  327. Tank4Level = data.Tank4Level,
  328. InflowRate = data.InflowRate,
  329. OutflowRate = data.OutflowRate,
  330. FlowDelta = data.FlowDelta,
  331. Pump1Running = data.Pump1Running,
  332. Pump2Running = data.Pump2Running,
  333. Pump3Running = data.Pump3Running,
  334. Pump4Running = data.Pump4Running,
  335. Pump5Running = data.Pump5Running,
  336. Pump1Fault = data.Pump1Fault,
  337. Pump2Fault = data.Pump2Fault,
  338. Pump3Fault = data.Pump3Fault,
  339. Pump4Fault = data.Pump4Fault,
  340. Pump5Fault = data.Pump5Fault,
  341. Pump1Freq = data.Pump1Freq,
  342. Pump2Freq = data.Pump2Freq,
  343. Pump3Freq = data.Pump3Freq,
  344. Pump4Freq = data.Pump4Freq,
  345. Pump5Freq = data.Pump5Freq,
  346. Fan1Running = data.Fan1Running,
  347. Fan2Running = data.Fan2Running,
  348. Fan1Fault = data.Fan1Fault,
  349. Fan2Fault = data.Fan2Fault,
  350. Valve1Position = data.Valve1Position,
  351. Valve2Position = data.Valve2Position,
  352. Valve3Position = data.Valve3Position,
  353. Valve4Position = data.Valve4Position,
  354. IsAutoMode = data.IsAutoMode,
  355. LastReadTime = data.LastReadTime,
  356. IsDataValid = data.IsDataValid,
  357. IsPlcConnected = data.IsPlcConnected
  358. };
  359. /// <summary>
  360. /// 更新设备状态
  361. /// </summary>
  362. private void UpdateDeviceStatuses(PlcDataModel data)
  363. {
  364. // 仅在状态变化时更新(避免不必要的字符串分配和通知)
  365. UpdateIfChanged(ref _pump1StatusColor, data.Pump1Running ? "#00897B" : (data.Pump1Fault ? "#EF4444" : "#4B5563"));
  366. UpdateIfChanged(ref _pump2StatusColor, data.Pump2Running ? "#00897B" : (data.Pump2Fault ? "#EF4444" : "#4B5563"));
  367. UpdateIfChanged(ref _pump3StatusColor, data.Pump3Running ? "#00897B" : (data.Pump3Fault ? "#EF4444" : "#4B5563"));
  368. UpdateIfChanged(ref _pump4StatusColor, data.Pump4Running ? "#00897B" : (data.Pump4Fault ? "#EF4444" : "#4B5563"));
  369. UpdateIfChanged(ref _pump5StatusColor, data.Pump5Running ? "#00897B" : (data.Pump5Fault ? "#EF4444" : "#4B5563"));
  370. UpdateIfChanged(ref _fan1StatusColor, data.Fan1Running ? "#00897B" : (data.Fan1Fault ? "#EF4444" : "#4B5563"));
  371. UpdateIfChanged(ref _fan2StatusColor, data.Fan2Running ? "#00897B" : (data.Fan2Fault ? "#EF4444" : "#4B5563"));
  372. UpdateIfChanged(ref _valve1StatusColor, data.Valve1Position > 50 ? "#00897B" :
  373. data.Valve1Position < 50 ? "#F59E0B" : "#4B5563");
  374. UpdateIfChanged(ref _valve2StatusColor, data.Valve2Position > 50 ? "#00897B" :
  375. data.Valve2Position < 50 ? "#F59E0B" : "#4B5563");
  376. UpdateIfChanged(ref _valve3StatusColor, data.Valve3Position > 50 ? "#00897B" :
  377. data.Valve3Position < 50 ? "#F59E0B" : "#4B5563");
  378. UpdateIfChanged(ref _valve4StatusColor, data.Valve4Position > 50 ? "#00897B" :
  379. data.Valve4Position < 50 ? "#F59E0B" : "#4B5563");
  380. UpdateIfChanged(ref _pump1Status, data.Pump1Running ? $"RUNNING {data.Pump1Freq:F0}Hz" : (data.Pump1Fault ? "FAULT" : "STOPPED"));
  381. UpdateIfChanged(ref _pump2Status, data.Pump2Running ? $"RUNNING {data.Pump2Freq:F0}Hz" : (data.Pump2Fault ? "FAULT" : "STOPPED"));
  382. UpdateIfChanged(ref _pump3Status, data.Pump3Running ? $"RUNNING {data.Pump3Freq:F0}Hz" : (data.Pump3Fault ? "FAULT" : "STOPPED"));
  383. UpdateIfChanged(ref _pump4Status, data.Pump4Running ? $"RUNNING {data.Pump4Freq:F0}Hz" : (data.Pump4Fault ? "FAULT" : "STOPPED"));
  384. UpdateIfChanged(ref _pump5Status, data.Pump5Running ? $"RUNNING {data.Pump5Freq:F0}Hz" : (data.Pump5Fault ? "FAULT" : "STOPPED"));
  385. UpdateIfChanged(ref _fan1Status, data.Fan1Running ? "RUNNING" : (data.Fan1Fault ? "FAULT" : "STOPPED"));
  386. UpdateIfChanged(ref _fan2Status, data.Fan2Running ? "RUNNING" : (data.Fan2Fault ? "FAULT" : "STOPPED"));
  387. }
  388. /// <summary>
  389. /// 检查报警(从 PlcPollingService 数据)
  390. /// </summary>
  391. private void CheckAlarms(PlcDataModel data)
  392. {
  393. var config = ConfigService.GetConfig();
  394. if (config == null) return;
  395. HasAlarm = false;
  396. AlarmMessage = string.Empty;
  397. AlarmCount = 0;
  398. if (data.Tank1Level > config.LevelHighAlarm || data.Tank1Level < config.LevelLowAlarm)
  399. {
  400. HasAlarm = true;
  401. AlarmCount++;
  402. }
  403. if (data.Tank2Level > config.LevelHighAlarm || data.Tank2Level < config.LevelLowAlarm)
  404. {
  405. HasAlarm = true;
  406. AlarmCount++;
  407. }
  408. if (data.InflowRate > config.FlowHighAlarm)
  409. {
  410. HasAlarm = true;
  411. AlarmCount++;
  412. }
  413. if (data.HasAnyFault)
  414. {
  415. HasAlarm = true;
  416. AlarmCount++;
  417. }
  418. RunningDeviceCount = data.RunningPumpCount + data.RunningFanCount;
  419. }
  420. // ═══════════════════════════════════════════════
  421. // PLC 控制命令
  422. // ═══════════════════════════════════════════════
  423. [RelayCommand]
  424. private async Task ConnectPlcAsync() => IsPlcConnected = await PlcService.ConnectAsync();
  425. [RelayCommand]
  426. private void DisconnectPlc() { PlcService.Disconnect(); IsPlcConnected = false; }
  427. [RelayCommand]
  428. private async Task RefreshDataAsync() => await PlcPollingService.Instance.RefreshNowAsync();
  429. [RelayCommand]
  430. private void Set2DView() => Is3DView = false;
  431. [RelayCommand]
  432. private void Set3DView() => Is3DView = true;
  433. /// <summary>
  434. /// 控制泵启停
  435. /// </summary>
  436. [RelayCommand]
  437. private async Task TogglePumpAsync(string pumpId)
  438. {
  439. if (!PlcService.IsConnected) { Log.Warning("PLC 未连接"); return; }
  440. var index = pumpId switch { "P001" => 0, "P002" => 1, "P003" => 2, "P004" => 3, "P005" => 4, _ => -1 };
  441. if (index < 0) return;
  442. var address = PlcConfig.GetPumpRunAddress(index);
  443. var currentState = PlcPollingService.Instance.Data.GetPumpRunning(index);
  444. await PlcService.WriteBoolAsync(address, !currentState);
  445. }
  446. /// <summary>
  447. /// 控制风机启停
  448. /// </summary>
  449. [RelayCommand]
  450. private async Task ToggleFanAsync(string fanId)
  451. {
  452. if (!PlcService.IsConnected) { Log.Warning("PLC 未连接"); return; }
  453. var address = fanId == "F001" ? PlcConfig.Fan1Start : PlcConfig.Fan2Start;
  454. var currentState = fanId == "F001" ? PlcPollingService.Instance.Data.Fan1Running : PlcPollingService.Instance.Data.Fan2Running;
  455. await PlcService.WriteBoolAsync(address, !currentState);
  456. }
  457. /// <summary>
  458. /// 控制阀门
  459. /// </summary>
  460. [RelayCommand]
  461. private async Task ToggleValveAsync(string valveId)
  462. {
  463. if (!PlcService.IsConnected) { Log.Warning("PLC 未连接"); return; }
  464. var index = valveId switch { "V001" => 0, "V002" => 1, "V003" => 2, "V004" => 3, _ => -1 };
  465. if (index < 0) return;
  466. var address = PlcConfig.GetValveControlAddress(index);
  467. await PlcService.WriteBoolAsync(address, true);
  468. await Task.Delay(500);
  469. await PlcService.WriteBoolAsync(address, false);
  470. }
  471. /// <summary>
  472. /// 切换模式
  473. /// </summary>
  474. [RelayCommand]
  475. private async Task ToggleModeAsync()
  476. {
  477. if (!PlcService.IsConnected) return;
  478. await PlcService.WriteBoolAsync(PlcConfig.SystemMode, ModeStatus != ValveStatus.Open);
  479. }
  480. /// <summary>
  481. /// 全部启动
  482. /// </summary>
  483. [RelayCommand]
  484. private async Task StartAllAsync()
  485. {
  486. if (!PlcService.IsConnected) return;
  487. for (int i = 0; i < 5; i++)
  488. await PlcService.WriteBoolAsync(PlcConfig.GetPumpRunAddress(i), true);
  489. for (int i = 0; i < 2; i++)
  490. await PlcService.WriteBoolAsync(i == 0 ? PlcConfig.Fan1Start : PlcConfig.Fan2Start, true);
  491. }
  492. /// <summary>
  493. /// 全部停止
  494. /// </summary>
  495. [RelayCommand]
  496. private async Task StopAllAsync()
  497. {
  498. if (!PlcService.IsConnected) return;
  499. for (int i = 0; i < 5; i++)
  500. await PlcService.WriteBoolAsync(PlcConfig.GetPumpRunAddress(i), false);
  501. for (int i = 0; i < 2; i++)
  502. await PlcService.WriteBoolAsync(i == 0 ? PlcConfig.Fan1Start : PlcConfig.Fan2Start, false);
  503. }
  504. /// <summary>
  505. /// 确认报警
  506. /// </summary>
  507. [RelayCommand]
  508. private async Task AckAlarmAsync()
  509. {
  510. await AlarmService.Instance.AcknowledgeAllAsync();
  511. }
  512. public void Dispose()
  513. {
  514. if (_disposed) return;
  515. _disposed = true;
  516. _clockTimer?.Stop();
  517. _clockTimer?.Dispose();
  518. _clockTimer = null;
  519. _lang.LanguageChanged -= OnLanguageChanged;
  520. PlcPollingService.Instance.DataUpdated -= OnDataUpdated;
  521. PlcPollingService.Instance.ConnectionStateChanged -= OnConnectionStateChanged;
  522. AlarmService.Instance.AlarmStateChanged -= OnAlarmStateChanged;
  523. }
  524. }
  525. /// <summary>
  526. /// 阀门状态枚举
  527. /// </summary>
  528. public enum ValveStatus { Closed, Middle, Open }