Files
New-version-HME-moisture-lo…/ViewModels/TestViewModel.cs

573 lines
19 KiB
C#
Raw Normal View History

2026-06-17 15:04:35 +08:00
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<HistoryRecordModel> 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();
}
}
}