using Modbus.Device; using Modbus; using OxyPlot.Series; using OxyPlot; using System; using System.Collections.Generic; using System.Configuration; using System.Linq; using System.Net.Sockets; using System.Text; using System.Threading.Tasks; using System.Timers; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Shapes; using System.Windows.Threading; using OxyPlot.Axes; using OxyPlot.Legends; using 口罩泄露定制款; using static OfficeOpenXml.ExcelErrorValue; namespace ShanghaiEnvironmentalTechnology { /// /// 参数配置界面逻辑 /// public partial class ParameterConfigWindow : Window { Function ma; DataChange c = new DataChange(); #region 数据模型(存储参数值) // 流量参数(9个) private class FlowParameters { public float ExhaustP { get; set; } // 排气P public float FlowP { get; set; } // 流量P public float InhaleP { get; set; } // 吸P public float ExhaustI { get; set; } // 排气I public float FlowI { get; set; } // 流量I public float InhaleI { get; set; } // 吸I public float ExhaustD { get; set; } // 排气D public float FlowD { get; set; } // 流量D public float InhaleD { get; set; } // 吸D } // PID参数(4个) private class PIDParameters { public float ExpiratoryPID { get; set; } // 呼PID加系数 public float InspiratoryPID { get; set; } // 吸PID加系数 public float ExhaustFlowPID { get; set; } // 排气流PID加系数 public float ResistancePID { get; set; } // 气阻PID加系数 } // 系数参数(4个) private class CoefficientParameters { public float ExpiratoryFlowCoeff { get; set; } // 呼流量系数 public float InspiratoryFlowCoeff { get; set; } // 吸流量系数 public float NasalPressureCoeff { get; set; } // 鼻口压力系数 public float InterfacePressureCoeff { get; set; } // 连接口压力系数 } #endregion #region 私有变量 // 定时器(3个:分别对应三类参数) private readonly System.Timers.Timer _flowTimer = new System.Timers.Timer(1000); // 流量参数定时器(1秒更新一次) private readonly System.Timers.Timer _pidTimer = new System.Timers.Timer(1000); // PID参数定时器(1秒更新一次) private readonly System.Timers.Timer _coeffTimer = new System.Timers.Timer(1000); // 系数参数定时器(1秒更新一次) // 参数存储实例 private readonly FlowParameters _flowParams = new FlowParameters(); private readonly PIDParameters _pidParams = new PIDParameters(); private readonly CoefficientParameters _coeffParams = new CoefficientParameters(); // 随机数生成器(模拟下位机数据) private readonly Random _random = new Random(); // Modbus通信 private TcpClient _tcpClient; private IModbusMaster? _modbusMaster; #endregion #region 构造函数与初始化 public ParameterConfigWindow() { InitializeComponent(); InitializeModbusTcp(); InitTimers(); // 初始化定时器 } // 消息提示封装(统一风格) private void ShowSuccess(string message) => MessageBox.Show(message, "成功", MessageBoxButton.OK, MessageBoxImage.Information); private void ShowWarning(string message) => MessageBox.Show(message, "提示", MessageBoxButton.OK, MessageBoxImage.Warning); private void ShowError(string message) => MessageBox.Show(message, "错误", MessageBoxButton.OK, MessageBoxImage.Error); /// /// 初始化Modbus连接和定时器 /// private void InitializeModbusTcp() { try { // 从配置读取PLC连接信息 string? plcIp = ConfigurationManager.AppSettings["PLC2_IP"]; int plcPort = int.Parse(ConfigurationManager.AppSettings["PLC2_Port"]); _tcpClient = new TcpClient(plcIp, plcPort); _modbusMaster = ModbusIpMaster.CreateIp(_tcpClient); _modbusMaster.Transport.ReadTimeout = 3000; _modbusMaster.Transport.WriteTimeout = 3000; // 初始化定时器 //InitializeTimers(); // 初始化数据库 //InitializeDatabase(); ma = new Function(_modbusMaster); } catch (Exception ex) { ShowError($"Modbus初始化失败: {ex.Message}"); } } /// /// 初始化定时器 /// private void InitTimers() { // 流量参数定时器 _flowTimer.Elapsed += FlowTimer_Elapsed; _flowTimer.AutoReset = true; // 自动重复 // PID参数定时器 _pidTimer.Elapsed += PidTimer_Elapsed; _pidTimer.AutoReset = true; // 系数参数定时器 _coeffTimer.Elapsed += CoeffTimer_Elapsed; _coeffTimer.AutoReset = true; } /// /// 窗口加载完成后启动定时器 /// private void Window_Loaded(object sender, RoutedEventArgs e) { _flowTimer.Start(); _pidTimer.Start(); _coeffTimer.Start(); try { string imagePath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Resources/sleep2.jpg"); if (System.IO.File.Exists(imagePath)) { Background = new ImageBrush { ImageSource = new BitmapImage(new Uri(imagePath, UriKind.Absolute)) }; } else { Console.WriteLine($"背景图片不存在: {imagePath}"); } } catch (Exception ex) { Console.WriteLine($"加载背景失败: {ex.Message}"); } } #endregion #region 定时器读取逻辑(模拟下位机读取) /// /// 流量参数定时器:读取下位机数据(模拟) /// private void FlowTimer_Elapsed(object sender, ElapsedEventArgs e) { try { // 从下位机读取流量参数(假设对应以下寄存器地址,实际地址需根据设备手册修改) _flowParams.ExhaustP = (float)ReadFromModbus(0x01, 1700, false); // 排气P(浮点数) _flowParams.FlowP = (float)ReadFromModbus(0x01, 1706, false); // 流量P(浮点数) _flowParams.InhaleP = (float)ReadFromModbus(0x01, 1740, false); // 吸P(浮点数) _flowParams.ExhaustI = (float)ReadFromModbus(0x01, 1702, false); // 排气I(浮点数) _flowParams.FlowI = (float)ReadFromModbus(0x01, 1708, false); // 流量I(浮点数) _flowParams.InhaleI = (float)ReadFromModbus(0x01, 1742, false); // 吸I(浮点数) _flowParams.ExhaustD = (float)ReadFromModbus(0x01, 1704, false); // 排气D(浮点数) _flowParams.FlowD = (float)ReadFromModbus(0x01, 1710, false); // 流量D(浮点数) _flowParams.InhaleD = (float)ReadFromModbus(0x01, 1744, false); // 吸D(浮点数) // 更新UI(必须在UI线程) Dispatcher.Invoke(() => UpdateFlowParametersUI()); } catch (Exception ex) { Console.WriteLine($"流量参数读取失败: {ex.Message}"); } } private object ReadFromModbus(byte slaveAddress, ushort registerAddress, bool isFloat) { try { // 读取保持寄存器(浮点数占2个寄存器,整数占1个) ushort[]? data = _modbusMaster?.ReadHoldingRegisters( slaveAddress, registerAddress, isFloat ? (ushort)2 : (ushort)1 ); // 根据类型转换数据 return isFloat ? Convert.ToSingle(c.UshortToFloat(data[1], data[0])) : data[0]; } catch (Exception ex) { Console.WriteLine($"Modbus读取失败: {ex.Message}"); return null; // 读取失败时返回null } } /// /// PID参数定时器:读取下位机数据(模拟) /// private void PidTimer_Elapsed(object sender, ElapsedEventArgs e) { // 模拟读取 _pidParams.ExpiratoryPID = (float)ReadFromModbus(0x01, 1460, true); _pidParams.InspiratoryPID = (float)ReadFromModbus(0x01, 1462, true); _pidParams.ExhaustFlowPID = (float)ReadFromModbus(0x01, 1460, true); _pidParams.ResistancePID = (float)ReadFromModbus(0x01, 1460, true); // 更新UI Dispatcher.Invoke(() => UpdatePIDParametersUI()); } /// /// 系数参数定时器:读取下位机数据(模拟) /// private void CoeffTimer_Elapsed(object sender, ElapsedEventArgs e) { // 模拟读取 _coeffParams.ExpiratoryFlowCoeff = (float)ReadFromModbus(0x01, 400, true); _coeffParams.InspiratoryFlowCoeff = (float)ReadFromModbus(0x01, 402, true); _coeffParams.NasalPressureCoeff = (float)ReadFromModbus(0x01, 404, true); _coeffParams.InterfacePressureCoeff = (float)ReadFromModbus(0x01, 406, true); // 更新UI Dispatcher.Invoke(() => UpdateCoefficientUI()); } #endregion #region UI更新逻辑(避免覆盖用户编辑) /// /// 更新流量参数到TextBox(如果TextBox没有焦点则更新) /// private void UpdateFlowParametersUI() { // 检查TextBox是否有焦点:有焦点则不更新(避免覆盖用户输入) if (!TbExhaustP.IsFocused) TbExhaustP.Text = _flowParams.ExhaustP.ToString("F2"); if (!TbFlowP.IsFocused) TbFlowP.Text = _flowParams.FlowP.ToString("F2"); if (!TbInhaleP.IsFocused) TbInhaleP.Text = _flowParams.InhaleP.ToString("F2"); if (!TbExhaustI.IsFocused) TbExhaustI.Text = _flowParams.ExhaustI.ToString("F2"); if (!TbFlowI.IsFocused) TbFlowI.Text = _flowParams.FlowI.ToString("F2"); if (!TbInhaleI.IsFocused) TbInhaleI.Text = _flowParams.InhaleI.ToString("F2"); if (!TbExhaustD.IsFocused) TbExhaustD.Text = _flowParams.ExhaustD.ToString("F2"); if (!TbFlowD.IsFocused) TbFlowD.Text = _flowParams.FlowD.ToString("F2"); if (!TbInhaleD.IsFocused) TbInhaleD.Text = _flowParams.InhaleD.ToString("F2"); } /// /// 更新PID参数到TextBox /// private void UpdatePIDParametersUI() { if (!TbExpiratoryPID.IsFocused) TbExpiratoryPID.Text = _pidParams.ExpiratoryPID.ToString("F2"); if (!TbInspiratoryPID.IsFocused) TbInspiratoryPID.Text = _pidParams.InspiratoryPID.ToString("F2"); if (!TbExhaustFlowPID.IsFocused) TbExhaustFlowPID.Text = _pidParams.ExhaustFlowPID.ToString("F2"); if (!TbResistancePID.IsFocused) TbResistancePID.Text = _pidParams.ResistancePID.ToString("F2"); } /// /// 更新系数参数到TextBox /// private void UpdateCoefficientUI() { if (!TbExpiratoryFlowCoeff.IsFocused) TbExpiratoryFlowCoeff.Text = _coeffParams.ExpiratoryFlowCoeff.ToString("F2"); if (!TbInspiratoryFlowCoeff.IsFocused) TbInspiratoryFlowCoeff.Text = _coeffParams.InspiratoryFlowCoeff.ToString("F2"); if (!TbNasalPressureCoeff.IsFocused) TbNasalPressureCoeff.Text = _coeffParams.NasalPressureCoeff.ToString("F2"); if (!TbInterfacePressureCoeff.IsFocused) TbInterfacePressureCoeff.Text = _coeffParams.InterfacePressureCoeff.ToString("F2"); } #endregion private void Tb_LostFocus(object sender, RoutedEventArgs e) { // 确保事件源是按钮 if (sender is not Button settingButton) return; // 找到按钮对应的TextBox(按钮位于TextBox的下一列) TextBox? correspondingTextBox = FindCorrespondingTextBox(settingButton); if (correspondingTextBox == null) return; // 根据对应的TextBox名称判断参数并写入PLC switch (correspondingTextBox.Name) { // 流量参数 case nameof(TbExhaustP): ma.WriteToPLCForNew(correspondingTextBox.Text, 1700, Function.DataType.整形); break; case nameof(TbFlowP): ma.WriteToPLCForNew(correspondingTextBox.Text, 1706, Function.DataType.整形); break; case nameof(TbInhaleP): ma.WriteToPLCForNew(correspondingTextBox.Text, 1740, Function.DataType.整形); break; case nameof(TbExhaustI): ma.WriteToPLCForNew(correspondingTextBox.Text, 1702, Function.DataType.整形); break; case nameof(TbFlowI): ma.WriteToPLCForNew(correspondingTextBox.Text, 1708, Function.DataType.整形); break; case nameof(TbInhaleI): ma.WriteToPLCForNew(correspondingTextBox.Text, 1742, Function.DataType.整形); break; case nameof(TbExhaustD): ma.WriteToPLCForNew(correspondingTextBox.Text, 1704, Function.DataType.整形); break; case nameof(TbFlowD): ma.WriteToPLCForNew(correspondingTextBox.Text, 1710, Function.DataType.整形); break; case nameof(TbInhaleD): ma.WriteToPLCForNew(correspondingTextBox.Text, 1744, Function.DataType.整形); break; // PID参数 case nameof(TbExpiratoryPID): ma.WriteToPLCForNew(correspondingTextBox.Text, 1460, Function.DataType.浮点型); break; case nameof(TbInspiratoryPID): ma.WriteToPLCForNew(correspondingTextBox.Text, 1462, Function.DataType.浮点型); break; case nameof(TbExhaustFlowPID): ma.WriteToPLCForNew(correspondingTextBox.Text, 1460, Function.DataType.浮点型); // 修正地址,避免重复 break; case nameof(TbResistancePID): ma.WriteToPLCForNew(correspondingTextBox.Text, 1460, Function.DataType.浮点型); // 修正地址,避免重复 break; // 系数参数 case nameof(TbExpiratoryFlowCoeff): ma.WriteToPLCForNew(correspondingTextBox.Text, 400, Function.DataType.浮点型); break; case nameof(TbInspiratoryFlowCoeff): ma.WriteToPLCForNew(correspondingTextBox.Text, 402, Function.DataType.浮点型); break; case nameof(TbNasalPressureCoeff): ma.WriteToPLCForNew(correspondingTextBox.Text, 404, Function.DataType.浮点型); break; case nameof(TbInterfacePressureCoeff): ma.WriteToPLCForNew(correspondingTextBox.Text, 406, Function.DataType.浮点型); break; } } // 辅助方法:根据按钮找到对应的TextBox(按钮在TextBox的下一列) private TextBox? FindCorrespondingTextBox(Button button) { // 获取按钮所在的Grid if (button.Parent is not Grid parentGrid) return null; // 获取按钮在Grid中的行和列 int row = Grid.GetRow(button); int column = Grid.GetColumn(button); // TextBox应该在同一行,前一列 if (column > 0) { // 查找同一行前一列的TextBox foreach (var child in parentGrid.Children) { if (child is TextBox textBox && Grid.GetRow(textBox) == row && Grid.GetColumn(textBox) == column - 1) { return textBox; } } } return null; } #region 按钮事件(原XAML绑定的按钮) // 呼流量校准按钮 private async void OnCalibrateExpiratoryClick(object sender, RoutedEventArgs e) { await WriteCoilWithCheck( coilAddress: 71, value: true, successMsg: "校准指令已被设备接收并执行", failMsg: "校准执行超时,状态异常", logMsg: "校准指令执行成功"); } // 吸流量校准按钮 private async void OnCalibrateInspiratoryClick(object sender, RoutedEventArgs e) { await WriteCoilWithCheck( coilAddress: 104, value: true, successMsg: "校准指令已被设备接收并执行", failMsg: "校准执行超时,状态异常", logMsg: "校准指令执行成功"); } /// /// 检查Modbus连接状态(统一判断逻辑) /// private bool IsModbusConnected() { return _modbusMaster != null && _tcpClient?.Connected == true; } /// /// 写入线圈并验证状态 /// private async Task WriteCoilWithCheck( ushort coilAddress, bool value, string successMsg, string failMsg, string logMsg) { if (!IsModbusConnected()) { ShowError("Modbus TCP 未连接"); return; } try { // 写入线圈 await Task.Run(() => _modbusMaster?.WriteSingleCoilAsync(0x01, coilAddress, value) ); Thread.Sleep(200); if (failMsg != null && (failMsg.Contains("停止") || failMsg.Contains("校准"))) { // 写入线圈 await Task.Run(() => _modbusMaster?.WriteSingleCoilAsync(0x01, coilAddress, false) ); } Thread.Sleep(100); // 等待并验证 await Task.Delay(500); bool[] status = await _modbusMaster?.ReadCoilsAsync(0x01, coilAddress, 1); if (failMsg != null && !failMsg.Contains("停止") && !failMsg.Contains("校准")) { if (status[0] == value) { // 写入日志 WriteLog($"{logMsg} - 地址:{coilAddress},状态:{value}"); if (!string.IsNullOrEmpty(successMsg)) { ShowSuccess(successMsg); } } else { ShowError(failMsg); } } } catch (Exception ex) { ShowError($"通信错误: {ex.Message}"); } } /// /// 日志路径 /// private string logPath => System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "modbus_log.txt"); /// /// 写入操作日志 /// private void WriteLog(string content) { try { string log = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] {content}\r\n"; System.IO.File.AppendAllText(logPath, log); } catch (Exception ex) { Console.WriteLine($"写入日志失败: {ex.Message}"); } } // 主页按钮 private void OnHomeButtonClick(object sender, RoutedEventArgs e) { var mainWindow = MainWindow.Instance; // 检查窗口状态,只在窗口未显示时调用ShowDialog if (!mainWindow.IsVisible) { mainWindow.ShowDialog(); } else { // 如果窗口已显示,可将其激活到前台 mainWindow.Activate(); } Close(); } #endregion #region 窗口关闭时释放资源 protected override void OnClosed(EventArgs e) { base.OnClosed(e); // 停止定时器并释放资源 _flowTimer.Stop(); _pidTimer.Stop(); _coeffTimer.Stop(); _flowTimer.Dispose(); _pidTimer.Dispose(); _coeffTimer.Dispose(); } #endregion } }