using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using HME_MoistureLossMeter.Models; using HME_MoistureLossMeter.Services; using Serilog; using System; using System.Collections.ObjectModel; using System.Data; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Media.Media3D; namespace HME_MoistureLossMeter.ViewModels { public partial class TestViewModel : ViewModelBase { private readonly IPlcService _plcService; private readonly IMesService _mesService; private readonly DeviceConfiguration _deviceConfig; private CancellationTokenSource _cts; private bool _isDataLoopRunning; private float _totalWaterTemp; private float _totalChamberTemp; private float _totalTidalVolume; private float _totalFrequency; private float _totalAirVolume; private float _totalOutletFlow; private int _dataSampleCount; // 实时数据属性 [ObservableProperty] private float _waterTemp; [ObservableProperty] private float _chamberTemp; [ObservableProperty] private float _weight; [ObservableProperty] private float _airVolume; [ObservableProperty] private float _outletFlow; [ObservableProperty] private int _presetHour; [ObservableProperty] private int _presetMinute; [ObservableProperty] private int _displayHour; [ObservableProperty] private int _displayMinute; [ObservableProperty] private int _displaySecond; [ObservableProperty] private float _initialMass; [ObservableProperty] private float _finalMass; [ObservableProperty] private float _moistureLoss; [ObservableProperty] private string _batchNo = ""; [ObservableProperty] private int _operatorId; [ObservableProperty] private int _experimentId; [ObservableProperty] private float _tidalVolume; [ObservableProperty] private float _frequency; [ObservableProperty] private int _breathCount; [ObservableProperty] private float _dryAirFlow; [ObservableProperty] private bool _isTesting; [ObservableProperty] private bool _isHeating; [ObservableProperty] private bool _isInhale; [ObservableProperty] private bool _isExhale; [ObservableProperty] private bool _isRising; [ObservableProperty] private bool _isFalling; [ObservableProperty] private bool _isConnected; [ObservableProperty] private string _connectionStatus = "未连接"; [ObservableProperty] private DateTime _currentTime = DateTime.Now; public ObservableCollection TestRecords { get; } = new(); [RelayCommand] private async Task MoveUp() => await ExecuteCoilCommand(ModbusTcpPlcService.COIL_UP, "上升"); [RelayCommand] private async Task MoveDown() => await ExecuteCoilCommand(ModbusTcpPlcService.COIL_DOWN, "下降"); [RelayCommand] private async Task ManualInhale() => await ExecuteCoilCommand(ModbusTcpPlcService.COIL_MANUAL_INHALE, "手动吸气"); [RelayCommand] private async Task ManualExhale() => await ExecuteCoilCommand(ModbusTcpPlcService.COIL_MANUAL_EXHALE, "手动呼气"); // 辅助方法 private async Task ExecuteCoilCommand(ushort coilAddress, string commandName) { try { IsBusy = true; await _plcService.WriteCoilAsync(coilAddress, true); StatusMessage = $"{commandName}已执行"; await Task.Delay(200); await _plcService.WriteCoilAsync(coilAddress, false); } catch (Exception ex) { StatusMessage = $"{commandName}失败: {ex.Message}"; Log.Error(ex, "{Command}失败", commandName); MessageBox.Show($"{commandName}失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); } finally { IsBusy = false; } } public TestViewModel(IPlcService plcService, IMesService mesService, DeviceConfiguration deviceConfig) { _plcService = plcService; _mesService = mesService; _deviceConfig = deviceConfig; // 初始化定时更新 var timer = new System.Timers.Timer(1000); timer.Elapsed += (s, e) => CurrentTime = DateTime.Now; timer.Start(); } partial void OnIsTestingChanged(bool value) { if (value) { // 开始测试时重置数据 _dataSampleCount = 0; _totalWaterTemp = 0; _totalChamberTemp = 0; _totalTidalVolume = 0; _totalFrequency = 0; _totalAirVolume = 0; _totalOutletFlow = 0; } } [RelayCommand] private async Task Connect() { try { IsBusy = true; StatusMessage = "正在连接PLC..."; await _plcService.EnsureConnectedAsync(); IsConnected = true; ConnectionStatus = "已连接"; StatusMessage = "PLC连接成功"; Log.Information("PLC连接成功"); // 启动数据循环 StartDataLoop(); } catch (Exception ex) { IsConnected = false; ConnectionStatus = "连接失败"; StatusMessage = $"连接失败: {ex.Message}"; Log.Error(ex, "PLC连接失败"); MessageBox.Show($"PLC连接失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); } finally { IsBusy = false; } } [RelayCommand] private void Disconnect() { StopDataLoop(); IsConnected = false; ConnectionStatus = "已断开"; StatusMessage = "已断开PLC连接"; Log.Information("PLC连接已断开"); } private void StartDataLoop() { if (_isDataLoopRunning) return; _cts = new CancellationTokenSource(); _isDataLoopRunning = true; Task.Run(async () => { while (!_cts.Token.IsCancellationRequested && IsConnected) { try { await ReadAllData(); await Task.Delay(_deviceConfig.UpdateIntervalMs, _cts.Token); } catch (OperationCanceledException) { break; } catch (Exception ex) { Log.Error(ex, "读取数据异常"); StatusMessage = $"读取数据异常: {ex.Message}"; await Task.Delay(2000); } } }, _cts.Token); } private void StopDataLoop() { _isDataLoopRunning = false; _cts?.Cancel(); _cts?.Dispose(); _cts = null; } private async Task ReadAllData() { try { // 读取浮点数数据 WaterTemp = await _plcService.ReadFloatAsync(ModbusTcpPlcService.ADDR_WATER_TEMP); ChamberTemp = await _plcService.ReadFloatAsync(ModbusTcpPlcService.ADDR_CHAMBER_TEMP); Weight = await _plcService.ReadFloatAsync(ModbusTcpPlcService.ADDR_WEIGHT); AirVolume = await _plcService.ReadFloatAsync(ModbusTcpPlcService.ADDR_AIR_VOLUME); OutletFlow = await _plcService.ReadFloatAsync(ModbusTcpPlcService.ADDR_OUTLET_FLOW); TidalVolume = await _plcService.ReadFloatAsync(ModbusTcpPlcService.ADDR_TIDAL_VOLUME); Frequency = await _plcService.ReadFloatAsync(ModbusTcpPlcService.ADDR_FREQUENCY); DryAirFlow = await _plcService.ReadFloatAsync(ModbusTcpPlcService.ADDR_DRY_AIR_FLOW); InitialMass = await _plcService.ReadFloatAsync(ModbusTcpPlcService.ADDR_INITIAL_MASS); FinalMass = await _plcService.ReadFloatAsync(ModbusTcpPlcService.ADDR_FINAL_MASS); MoistureLoss = await _plcService.ReadFloatAsync(ModbusTcpPlcService.ADDR_MOISTURE_LOSS); // 读取整数数据 PresetHour = await _plcService.ReadInt32Async(ModbusTcpPlcService.ADDR_PRESET_HOUR); PresetMinute = await _plcService.ReadInt32Async(ModbusTcpPlcService.ADDR_PRESET_MINUTE); DisplayHour = await _plcService.ReadInt32Async(ModbusTcpPlcService.ADDR_DISPLAY_HOUR); DisplayMinute = await _plcService.ReadInt32Async(ModbusTcpPlcService.ADDR_DISPLAY_MINUTE); DisplaySecond = await _plcService.ReadInt32Async(ModbusTcpPlcService.ADDR_DISPLAY_SECOND); BreathCount = await _plcService.ReadInt32Async(ModbusTcpPlcService.ADDR_BREATH_COUNT); OperatorId = await _plcService.ReadInt32Async(ModbusTcpPlcService.ADDR_OPERATOR_ID); ExperimentId = await _plcService.ReadInt32Async(ModbusTcpPlcService.ADDR_EXPERIMENT_ID); // 读取字符串(批号) var batchRegs = await _plcService.ReadHoldingRegistersAsync(ModbusTcpPlcService.ADDR_BATCH_NO, 10); BatchNo = RegistersToString(batchRegs); // 读取线圈状态 IsTesting = await _plcService.ReadCoilAsync(ModbusTcpPlcService.COIL_TEST); IsHeating = await _plcService.ReadCoilAsync(ModbusTcpPlcService.COIL_HEAT); IsInhale = await _plcService.ReadCoilAsync(ModbusTcpPlcService.COIL_INHALE); IsExhale = await _plcService.ReadCoilAsync(ModbusTcpPlcService.COIL_EXHALE); IsRising = await _plcService.ReadCoilAsync(ModbusTcpPlcService.COIL_UP); IsFalling = await _plcService.ReadCoilAsync(ModbusTcpPlcService.COIL_DOWN); // 如果正在测试,收集数据用于平均值计算 if (IsTesting) { _dataSampleCount++; _totalWaterTemp += WaterTemp; _totalChamberTemp += ChamberTemp; _totalTidalVolume += TidalVolume; _totalFrequency += Frequency; _totalAirVolume += AirVolume; _totalOutletFlow += OutletFlow; // 发送实时数据到MES await SendRealtimeDataToMes(); } StatusMessage = "数据读取成功"; IsConnected = true; ConnectionStatus = "已连接"; } catch (Exception ex) { IsConnected = false; ConnectionStatus = "通信异常"; Log.Error(ex, "读取数据失败"); throw; } } private string RegistersToString(ushort[] registers) { var bytes = new byte[registers.Length * 2]; for (int i = 0; i < registers.Length; i++) { var regBytes = BitConverter.GetBytes(registers[i]); bytes[i * 2] = regBytes[0]; bytes[i * 2 + 1] = regBytes[1]; } return System.Text.Encoding.ASCII.GetString(bytes).TrimEnd('\0'); } private async Task SendRealtimeDataToMes() { try { var data = new RealtimeDataModel { DeviceId = _deviceConfig.DeviceId, Timestamp = DateTime.Now.ToString("o"), Realtime = new RealtimeData { WaterTemp = WaterTemp, ChamberTemp = ChamberTemp, Weight = Weight, AirVolume = AirVolume, OutletFlow = OutletFlow, TidalVolume = TidalVolume, Frequency = Frequency, BreathRate = BreathCount, DryAirFlow = DryAirFlow }, Settings = new SettingsData { PresetHour = PresetHour, PresetMinute = PresetMinute, PresetSecond = DisplaySecond, OperatorId = OperatorId, BatchNo = BatchNo, ExperimentId = ExperimentId }, Status = new StatusData { IsTesting = IsTesting, IsFault = false } }; await _mesService.SendRealtimeDataAsync(data); } catch (Exception ex) { Log.Error(ex, "发送实时数据到MES失败"); } } // 测试控制命令 [RelayCommand] private async Task StartTest() { try { IsBusy = true; await _plcService.WriteCoilAsync(ModbusTcpPlcService.COIL_TEST, true); StatusMessage = "测试已启动"; Log.Information("测试启动"); } catch (Exception ex) { Log.Error(ex, "启动测试失败"); MessageBox.Show($"启动测试失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); } finally { IsBusy = false; } } [RelayCommand] private async Task StopTest() { try { IsBusy = true; await _plcService.WriteCoilAsync(ModbusTcpPlcService.COIL_STOP, true); StatusMessage = "测试已停止"; Log.Information("测试停止"); } catch (Exception ex) { Log.Error(ex, "停止测试失败"); MessageBox.Show($"停止测试失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); } finally { IsBusy = false; } } [RelayCommand] private async Task Reset() { try { IsBusy = true; await _plcService.WriteCoilAsync(ModbusTcpPlcService.COIL_RESET, true); StatusMessage = "已复位"; Log.Information("设备复位"); } catch (Exception ex) { Log.Error(ex, "复位失败"); MessageBox.Show($"复位失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); } finally { IsBusy = false; } } [RelayCommand] private async Task Clear() { try { IsBusy = true; await _plcService.WriteCoilAsync(ModbusTcpPlcService.COIL_CLEAR, true); StatusMessage = "已清零"; Log.Information("数据清零"); } catch (Exception ex) { Log.Error(ex, "清零失败"); MessageBox.Show($"清零失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); } finally { IsBusy = false; } } [RelayCommand] private async Task ToggleHeat() { try { IsBusy = true; bool currentState = await _plcService.ReadCoilAsync(ModbusTcpPlcService.COIL_HEAT); await _plcService.WriteCoilAsync(ModbusTcpPlcService.COIL_HEAT, !currentState); StatusMessage = currentState ? "加热关闭" : "加热开启"; Log.Information("加热切换: {State}", !currentState); } catch (Exception ex) { Log.Error(ex, "切换加热失败"); MessageBox.Show($"切换加热失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); } finally { IsBusy = false; } } [RelayCommand] private async Task RecordP1() { try { IsBusy = true; await _plcService.WriteCoilAsync(ModbusTcpPlcService.COIL_P1_RECORD, true); StatusMessage = "P1记录已触发"; Log.Information("P1记录触发"); } catch (Exception ex) { Log.Error(ex, "P1记录失败"); MessageBox.Show($"P1记录失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); } finally { IsBusy = false; } } [RelayCommand] private async Task RecordP2() { try { IsBusy = true; await _plcService.WriteCoilAsync(ModbusTcpPlcService.COIL_P2_RECORD, true); StatusMessage = "P2记录已触发"; Log.Information("P2记录触发"); } catch (Exception ex) { Log.Error(ex, "P2记录失败"); MessageBox.Show($"P2记录失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); } finally { IsBusy = false; } } [RelayCommand] private async Task Print() { try { IsBusy = true; await _plcService.WriteCoilAsync(ModbusTcpPlcService.COIL_PRINT, true); StatusMessage = "打印已触发"; Log.Information("打印触发"); } catch (Exception ex) { Log.Error(ex, "打印失败"); MessageBox.Show($"打印失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); } finally { IsBusy = false; } } public void Dispose() { StopDataLoop(); _plcService?.Dispose(); } } }