using Microsoft.Win32; using Modbus.Device; using Modbus; using OfficeOpenXml; using System; using System.Configuration; using System.Data.SQLite; using System.IO; using System.Net.Sockets; using System.Runtime.Intrinsics; using System.Threading.Tasks; using System.Timers; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Media.Imaging; using 口罩泄露定制款; namespace ShanghaiEnvironmentalTechnology { /// /// 呼吸相关参数监控窗口(Modbus通信+CO2数据记录) /// public partial class Window5 : Window, IDisposable { DataChange c = new DataChange(); #region 寄存器/线圈地址定义(按功能分组,标注物理意义) // 鼻口相关 private readonly ushort _outRegisterAddress = 0x0038; // 呼吸次数(D56) private readonly ushort _breathMinuteAddress = 0x09CA; // 呼吸计时分 private readonly ushort _breathSecondAddress = 0x09C6; // 呼吸计时秒 // 呼路管与CO2相关 private readonly ushort _breathOutAddress = 3030; // 呼路管压力 private readonly ushort _co2Address = 3006; // CO2浓度 private readonly ushort _co2BeginAddress = 0x0190; // CO2开始浓度 private readonly ushort _co2EndAddress = 0x019A; // CO2结束浓度 private readonly ushort _co2AddAddress = 0x0198; // CO2浓度增加值 // 控制线圈(M代码) private readonly ushort _testStartAddress = 97; // 二氧化碳测试启动(M97) private readonly ushort _resetAddress = 0x0003; // 复位(M2) private readonly ushort _testStopAddress = 0x0008; // 测试停止(M8) Function fc; #endregion #region 私有字段 // Modbus通信 private TcpClient _tcpClient; private IModbusMaster _modbusMaster; // 定时器(按功能分组) private System.Timers.Timer _outReadTimer; // 鼻口参数读取 private System.Timers.Timer _breathTimer; // 呼吸计时读取 private System.Timers.Timer _co2Timer; // 呼路管+CO2浓度读取 private System.Timers.Timer _co2BeginEndTimer; // CO2开始/结束浓度读取 private System.Timers.Timer _co2AddTimer; // CO2增加值读取 private System.Timers.Timer _beginRecordTimer; // 数据记录定时器 private System.Timers.Timer startTimer; // 启动状态实时定时器 private System.Timers.Timer resetTimer; // 启动状态实时定时器 private System.Timers.Timer breathTimer; #endregion #region 私有字段(新增) // 编辑状态标志位(控制是否更新TextBox) private bool _isEditingOut; // 呼吸比-呼气 private bool _isEditingIn; // 呼吸比-吸气 private bool _isEditingMoisture; // 潮气量 private bool _isEditingRespRate; // 呼吸频率 private bool _isEditingFrequency; // 频率系数 // 定时器(1个定时器管理4个参数的实时读取) private System.Timers.Timer _paramReadTimer; #endregion public Window5() { InitializeComponent(); InitializeModbusTcp(); Loaded += Window_Loaded; // 延迟加载背景,避免初始化阻塞 } #region 初始化与资源释放(强化资源管理) /// /// 初始化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; _paramReadTimer = CreateTimer(1000, OnParamTimerElapsed); // 初始化定时器 InitializeTimers(); fc = new Function(_modbusMaster); // 初始化数据库 InitializeDatabase(); } catch (Exception ex) { ShowError($"Modbus初始化失败: {ex.Message}"); } } private void OnParamTimerElapsed(object sender, ElapsedEventArgs e) { // 1. 读取呼吸比(呼气) if (!_isEditingOut) { ReadAndUpdateSingleRegister(342, true, v1 => { UpdatePressureUI1(v1.ToString()); }); } // 2. 读取呼吸比(吸气) if (!_isEditingIn) { ReadAndUpdateSingleRegister( 340, isFloat: true, value => UpdatePressureUI2(value.ToString()) ); } // 3. 读取潮气量 if (!_isEditingMoisture) { ReadAndUpdateSingleRegister( 300, isFloat: true, value => UpdatePressureUI3(value.ToString()) ); } // 4. 读取呼吸频率 if (!_isEditingRespRate) { ReadAndUpdateSingleRegister( 210, isFloat: true, value => UpdatePressureUI4(value.ToString()) ); } // 5. 读取频率系数 if (!_isEditingFrequency) { ReadAndUpdateSingleRegister( 368, isFloat: true, value => UpdatePressureUI5(value.ToString()) ); } } #region 输入验证(仅允许数字输入) /// /// 通用数字输入过滤(支持整数和小数) /// private void NumberPreviewTextInput(object sender, TextCompositionEventArgs e) { var textBox = sender as TextBox; // 允许数字和一个小数点(如“123”“123.45”) var isNumber = System.Text.RegularExpressions.Regex.IsMatch(e.Text, @"^[0-9]*(?:\.[0-9]*)?$"); // 禁止重复输入小数点 if (e.Text == "." && textBox?.Text.Contains(".") == true) { isNumber = false; } e.Handled = !isNumber; } #endregion /// /// 统一初始化所有定时器(避免重复配置) /// private void InitializeTimers() { _outReadTimer = CreateTimer(1000, OnOutTimerElapsed); _breathTimer = CreateTimer(1000, OnBreathTimerElapsed); _co2Timer = CreateTimer(500, OnCo2TimerElapsed); _co2BeginEndTimer = CreateTimer(1000, OnCO2BeginEndTimerElapsed); _co2AddTimer = CreateTimer(1000, OnCO2AddTimerElapsed); startTimer = CreateTimer(1000, OnStartTimerElapsed); resetTimer = CreateTimer(1000, OnResetTimerElapsed); breathTimer = CreateTimer(1000, OnbreathTimerElapsed); } private void OnStartTimerElapsed(object sender, ElapsedEventArgs e) { string _lang = ConfigurationManager.AppSettings["Language"] ?? "zh-CN"; try { bool[] result = _modbusMaster?.ReadCoils(0x01, 98, 1); bool isRunning = result != null && result.Length > 0 && result[0]; TestStartButton.Dispatcher.Invoke(() => { if (isRunning) { btn1.IsEnabled = false; btn2.IsEnabled = false; btn3.IsEnabled = false; btn4.IsEnabled = false; btn5.IsEnabled = false; TestStartButton.Content = _lang == "en-US" ? "Test Started" : "测试启动成功"; TestStartButton.Foreground = Brushes.LightGreen; } else { btn1.IsEnabled = true; btn2.IsEnabled = true; btn3.IsEnabled = true; btn4.IsEnabled = true; btn5.IsEnabled = true; TestStartButton.Content = _lang == "en-US" ? "Start Test" : "测试启动"; TestStartButton.Foreground = Brushes.White; } }); } catch (Exception ex) { Console.WriteLine(_lang == "en-US" ? $"Failed to read coil: {ex.Message}" : $"读取线圈或更新UI失败:{ex.Message}"); } } private void OnResetTimerElapsed(object sender, ElapsedEventArgs e) { string _lang = ConfigurationManager.AppSettings["Language"] ?? "zh-CN"; try { bool[] result = _modbusMaster?.ReadCoils(0x01, 3, 1); bool isRunning = result != null && result.Length > 0 && result[0]; ResetBtn.Dispatcher.Invoke(() => { if (isRunning) { ResetBtn.Content = _lang == "en-US" ? "Reset Success" : "复位成功"; ResetBtn.Foreground = Brushes.LightGreen; } else { ResetBtn.Content = _lang == "en-US" ? "Reset" : "复位"; ResetBtn.Foreground = Brushes.White; } }); } catch (Exception ex) { Console.WriteLine(_lang == "en-US" ? $"Failed to read coil: {ex.Message}" : $"读取线圈或更新UI失败:{ex.Message}"); } } //int i = 0; // 新增:记录当前状态(避免重复触发) private string _currentBreathState = ""; // 可能的值:"吸气"、"呼气"、"异常" private void OnbreathTimerElapsed(object sender, ElapsedEventArgs e) { string _lang = ConfigurationManager.AppSettings["Language"] ?? "zh-CN"; try { bool[] inhale = _modbusMaster?.ReadCoils(0x01, 51, 1); bool[] exhale = _modbusMaster?.ReadCoils(0x01, 55, 1); bool isIn = inhale != null && inhale.Length > 0 && inhale[0]; bool isEx = exhale != null && exhale.Length > 0 && exhale[0]; string newState; if ((isIn && isEx) || (!isIn && !isEx)) newState = _lang == "en-US" ? "Error" : "异常"; else if (isIn) newState = _lang == "en-US" ? "Inhale" : "吸气"; else newState = _lang == "en-US" ? "Exhale" : "呼气"; if (newState != _currentBreathState) { breathType.Dispatcher.Invoke(() => { _currentBreathState = newState; if (newState == (_lang == "en-US" ? "Error" : "异常")) { breathType.Text = _lang == "en-US" ? "Standby" : "待机"; StopAllAnimations(); breathType.Foreground = Brushes.Orange; breathType.FontSize = 20; breathType.Opacity = 1.0; } else if (newState == (_lang == "en-US" ? "Inhale" : "吸气")) { breathType.Text = _lang == "en-US" ? "Inhale" : "吸气"; StopAllAnimations(); var colorAnim = new ColorAnimation(Colors.Gray, Colors.Green, TimeSpan.FromSeconds(2.5)); var brush = new SolidColorBrush(Colors.Gray); breathType.Foreground = brush; brush.BeginAnimation(SolidColorBrush.ColorProperty, colorAnim); var sizeAnim = new DoubleAnimation(16, 40, TimeSpan.FromSeconds(2.5)); var opacAnim = new DoubleAnimation(0.5, 1.0, TimeSpan.FromSeconds(2.5)); breathType.BeginAnimation(TextBlock.FontSizeProperty, sizeAnim); breathType.BeginAnimation(TextBlock.OpacityProperty, opacAnim); } else { breathType.Text = _lang == "en-US" ? "Exhale" : "呼气"; StopAllAnimations(); var colorAnim = new ColorAnimation(Colors.Green, Colors.Red, TimeSpan.FromSeconds(2.5)); var brush = new SolidColorBrush(Colors.Green); breathType.Foreground = brush; brush.BeginAnimation(SolidColorBrush.ColorProperty, colorAnim); var sizeAnim = new DoubleAnimation(40, 16, TimeSpan.FromSeconds(2.5)); var opacAnim = new DoubleAnimation(1.0, 0.5, TimeSpan.FromSeconds(2.5)); breathType.BeginAnimation(TextBlock.FontSizeProperty, sizeAnim); breathType.BeginAnimation(TextBlock.OpacityProperty, opacAnim); } }); } } catch (Exception ex) { Console.WriteLine(_lang == "en-US" ? $"Failed to read coil: {ex.Message}" : $"读取线圈或更新UI失败:{ex.Message}"); } } // 新增:停止所有动画的辅助方法 private void StopAllAnimations() { // 停止动画(null表示移除动画) breathType.BeginAnimation(TextBlock.FontSizeProperty, null); breathType.BeginAnimation(TextBlock.OpacityProperty, null); if (breathType.Foreground is SolidColorBrush brush) { //brush.BeginAnimation(SolidColorBrush.ColorProperty, null); } } /// /// 通用定时器创建方法(统一配置) /// private System.Timers.Timer CreateTimer(int intervalMs, ElapsedEventHandler elapsedAction) { var timer = new System.Timers.Timer(intervalMs) { AutoReset = true, Enabled = true }; timer.Elapsed += elapsedAction; return timer; } /// /// 初始化数据库(创建CO2记录表) /// private void InitializeDatabase() { try { using (var connection = new SQLiteConnection(CSConstant.DbConnectionString)) { connection.Open(); string createTableSql = @" CREATE TABLE IF NOT EXISTS CO2 ( Id INTEGER PRIMARY KEY AUTOINCREMENT, Flow REAL NOT NULL, Pressure REAL NOT NULL, BeginCO2 REAL NOT NULL, EndCO2 REAL NOT NULL, CO2Added REAL NOT NULL, RecordTime DATETIME NOT NULL );"; using (var command = new SQLiteCommand(createTableSql, connection)) { command.ExecuteNonQuery(); } } } catch (Exception ex) { Console.WriteLine($"数据库初始化失败: {ex.Message}"); ShowError($"数据库错误: {ex.Message}"); } } /// /// 释放所有资源(避免内存泄漏) /// public void Dispose() { // 释放定时器(含数据记录定时器) _outReadTimer?.Dispose(); _breathTimer?.Dispose(); _co2Timer?.Dispose(); _co2BeginEndTimer?.Dispose(); _co2AddTimer?.Dispose(); _beginRecordTimer?.Dispose(); // 释放Modbus连接 _tcpClient?.Close(); _tcpClient?.Dispose(); _modbusMaster = null; } /// /// 窗口关闭时强制释放资源 /// protected override void OnClosed(EventArgs e) { base.OnClosed(e); Dispose(); } #endregion #region 定时器读取逻辑(优化异常处理) /// /// 鼻口参数读取(呼吸次数) /// private void OnOutTimerElapsed(object sender, ElapsedEventArgs e) { ReadAndUpdateSingleRegister( _outRegisterAddress, false, value => UpdateOutUI(value.ToString()) ); } /// /// 呼吸计时读取(分+秒) /// private void OnBreathTimerElapsed(object sender, ElapsedEventArgs e) { ReadAndUpdateDualRegisterUshort( _breathMinuteAddress, _breathSecondAddress, (min, sec) => UpdateTimerUI(min.ToString(), sec.ToString()) ); } /// /// 呼路管压力+CO2浓度读取 /// private void OnCo2TimerElapsed(object sender, ElapsedEventArgs e) { ReadAndUpdateDualRegister( _breathOutAddress, _co2Address, (pressure, co2) => UpdateCo2TimerUI(pressure.ToString(), co2.ToString()) ); } /// /// 数据记录定时器(定时保存CO2相关参数) /// private void OnBeginRecordTimerElapsed(object sender, ElapsedEventArgs e) { if (!IsModbusConnected()) return; try { float flowData = ReadAndUpdateSingleRegisterWithNoUI(_co2Address, true); float pressureData = ReadAndUpdateSingleRegisterWithNoUI(_breathOutAddress, true); float beginCo2Data = ReadAndUpdateSingleRegisterWithNoUI(_co2BeginAddress, true); float endCo2Data = ReadAndUpdateSingleRegisterWithNoUI(_co2EndAddress, true); float addCo2Data = ReadAndUpdateSingleRegisterWithNoUI(_co2AddAddress, true); // 保存到数据库 SaveRecordToDatabase( flowData, pressureData, beginCo2Data, endCo2Data, addCo2Data ); } catch (Exception ex) { Console.WriteLine($"数据记录失败: {ex.Message}"); } } /// /// CO2开始/结束浓度读取 /// private void OnCO2BeginEndTimerElapsed(object sender, ElapsedEventArgs e) { ReadAndUpdateDualRegister( _co2BeginAddress, _co2EndAddress, (begin, end) => UpdateCO2BeginEndTimerUI(begin.ToString(), end.ToString()) ); } /// /// CO2增加值读取 /// private void OnCO2AddTimerElapsed(object sender, ElapsedEventArgs e) { ReadAndUpdateSingleRegister( _co2AddAddress, true, value => UpdateCO2AddTimerUI(value.ToString()) ); } #endregion #region Modbus通用操作(提取重复逻辑) /// /// 通用寄存器读取并更新UI,支持16位整数和32位浮点数 /// /// 起始地址 /// 是否为浮点型(占用2个寄存器) /// 更新UI的回调函数 private void ReadAndUpdateSingleRegister(ushort address, bool isFloat, Action updateAction) { if (!IsModbusConnected()) { updateAction?.Invoke(isFloat ? (object)float.NaN : (ushort)0); return; } try { // 根据数据类型确定读取的寄存器数量 int registerCount = isFloat ? 2 : 1; ushort[] data = _modbusMaster?.ReadHoldingRegisters(0x01, address, (ushort)registerCount); if (isFloat) { // 浮点型转换(2个16位寄存器组合为32位浮点数) if (data.Length >= 2) { // 2. 解析寄存器值(data[0]是D312,data[1]是D313) ushort a = data[0]; // 高位寄存器值 ushort b = data[1]; // 低位寄存器值 float floatValue = c.UshortToFloat(b, a); floatValue = (float)Math.Round(floatValue, 2); updateAction?.Invoke(floatValue); } else { updateAction?.Invoke(float.NaN); } } else { // 16位整数直接返回 updateAction?.Invoke(data.Length > 0 ? data[0] : (ushort)0); } } catch (Exception ex) { Console.WriteLine($"读取寄存器[{address:X4}]失败: {ex.Message}"); // 异常时根据类型返回对应的值 updateAction?.Invoke(isFloat ? (object)float.NaN : (ushort)0); } } private float ReadAndUpdateSingleRegisterWithNoUI(ushort address, bool isFloat) { if (!IsModbusConnected()) { return 0; } try { // 根据数据类型确定读取的寄存器数量 int registerCount = isFloat ? 2 : 1; ushort[] data = _modbusMaster?.ReadHoldingRegisters(0x01, address, (ushort)registerCount); if (isFloat) { // 浮点型转换(2个16位寄存器组合为32位浮点数) if (data.Length >= 2) { // 2. 解析寄存器值(data[0]是D312,data[1]是D313) ushort a = data[0]; // 高位寄存器值 ushort b = data[1]; // 低位寄存器值 float floatValue = c.UshortToFloat(b, a); floatValue = (float)Math.Round(floatValue, 2); return floatValue; } else { return 0; } } else { return 0; } } catch (Exception ex) { Console.WriteLine($"读取寄存器[{address:X4}]失败: {ex.Message}"); return 0; } } /// /// 读取两个寄存器并更新UI(统一处理连接状态) /// private void ReadAndUpdateDualRegister(ushort address1, ushort address2, Action updateAction) { if (!IsModbusConnected()) { updateAction?.Invoke(0, 0); // 触发UI显示"连接断开" return; } try { ushort[] data1 = _modbusMaster?.ReadHoldingRegisters(0x01, address1, 2); ushort[] data2 = _modbusMaster?.ReadHoldingRegisters(0x01, address2, 2); // 2. 解析寄存器值(data[0]是D312,data[1]是D313) ushort a = data1[0]; // 高位寄存器值 ushort b = data1[1]; // 低位寄存器值 float floatValue = c.UshortToFloat(b, a); floatValue = (float)Math.Round(floatValue, 2); // 2. 解析寄存器值(data[0]是D312,data[1]是D313) ushort a1 = data2[0]; // 高位寄存器值 ushort b1 = data2[1]; // 低位寄存器值 float floatValue2 = c.UshortToFloat(b1, a1); floatValue2 = (float)Math.Round(floatValue2, 2); updateAction?.Invoke(floatValue, floatValue2); } catch (Exception ex) { Console.WriteLine($"读取寄存器[{address1:X4},{address2:X4}]失败: {ex.Message}"); updateAction?.Invoke(0, 0); // 触发UI显示"读取失败" } } /// /// 读取两个寄存器并更新UI(统一处理连接状态) /// private void ReadAndUpdateDualRegisterUshort(ushort address1, ushort address2, Action updateAction) { if (!IsModbusConnected()) { updateAction?.Invoke(0, 0); // 触发UI显示"连接断开" return; } try { ushort[] data1 = _modbusMaster?.ReadHoldingRegisters(0x01, address1, 2); ushort[] data2 = _modbusMaster?.ReadHoldingRegisters(0x01, address2, 2); ushort floatValue = data1[0]; ushort floatValue2 = data2[0]; updateAction?.Invoke(floatValue, floatValue2); } catch (Exception ex) { Console.WriteLine($"读取寄存器[{address1:X4},{address2:X4}]失败: {ex.Message}"); updateAction?.Invoke(0, 0); // 触发UI显示"读取失败" } } /// /// 检查Modbus连接状态(统一判断逻辑) /// private bool IsModbusConnected() { return _modbusMaster != null && _tcpClient?.Connected == true; } #endregion #region UI更新(统一线程安全处理) private void UpdateConnectionTextUI(System.Windows.Controls.TextBox textBox, string value) { // 获取当前语言(默认中文) string currentLanguage = ConfigurationManager.AppSettings["Language"] ?? "zh-CN"; // 多语言提示信息 string disconnectMsg = currentLanguage == "en-US" ? "Connection disconnected" : "连接断开"; // 安全更新UI UpdateUiSafely(() => textBox.Text = IsModbusConnected() ? value : disconnectMsg ); } private void UpdateConnectionTextUI(System.Windows.Controls.TextBlock textBox, string value) { // 获取当前语言(默认中文) string currentLanguage = ConfigurationManager.AppSettings["Language"] ?? "zh-CN"; // 多语言提示信息 string disconnectMsg = currentLanguage == "en-US" ? "Connection disconnected" : "连接断开"; // 安全更新UI UpdateUiSafely(() => textBox.Text = IsModbusConnected() ? value : disconnectMsg ); } private void UpdateOutUI(string val) { string currentLanguage = ConfigurationManager.AppSettings["Language"] ?? "zh-CN"; UpdateUiSafely(() => respiratoryCountTxt.Text = IsModbusConnected() ? val : (currentLanguage == "en-US" ? "Disconnected" : "连接断开")); } /// /// 更新呼吸计时UI /// private void UpdateTimerUI(string minute, string second) { string currentLanguage = ConfigurationManager.AppSettings["Language"] ?? "zh-CN"; UpdateUiSafely(() => { breathTimeBegin.Text = IsModbusConnected() ? minute : (currentLanguage == "en-US" ? "Disconnected" : "连接断开"); breathTimeEnd.Text = IsModbusConnected() ? second : (currentLanguage == "en-US" ? "Disconnected" : "连接断开"); }); } /// /// 更新CO2和呼路管压力UI /// private void UpdateCo2TimerUI(string pressure, string co2) { string currentLanguage = ConfigurationManager.AppSettings["Language"] ?? "zh-CN"; UpdateUiSafely(() => { RespiratoryPressureTxt.Text = IsModbusConnected() ? pressure : (currentLanguage == "en-US" ? "Disconnected" : "连接断开"); CO2PressureTxt.Text = IsModbusConnected() ? co2 : (currentLanguage == "en-US" ? "Disconnected" : "连接断开"); }); } /// /// 更新CO2开始/结束浓度UI /// private void UpdateCO2BeginEndTimerUI(string begin, string end) { string currentLanguage = ConfigurationManager.AppSettings["Language"] ?? "zh-CN"; UpdateUiSafely(() => { CO2BeginTb.Text = IsModbusConnected() ? begin : (currentLanguage == "en-US" ? "Disconnected" : "连接断开"); CO2EndTb.Text = IsModbusConnected() ? end : (currentLanguage == "en-US" ? "Disconnected" : "连接断开"); }); } /// /// 更新CO2增加值UI /// private void UpdateCO2AddTimerUI(string value) { string currentLanguage = ConfigurationManager.AppSettings["Language"] ?? "zh-CN"; UpdateUiSafely(() => CO2AddTxt.Text = IsModbusConnected() ? value : (currentLanguage == "en-US" ? "Disconnected" : "连接断开") ); } /// /// 更新潮气UI(按钮写入后刷新) /// private void UpdatePressureUI1(string value) { UpdateUiSafely(() => OutTxt.Text = value); } /// /// 更新潮气UI(按钮写入后刷新) /// private void UpdatePressureUI2(string value) { UpdateUiSafely(() => InTxt.Text = value); } /// /// 更新潮气UI(按钮写入后刷新) /// private void UpdatePressureUI3(string value) { UpdateUiSafely(() => moistureTxt.Text = value); } /// /// 更新潮气UI(按钮写入后刷新) /// private void UpdatePressureUI4(string value) { UpdateUiSafely(() => respiratoryRateTxt.Text = value); } /// /// 更新潮气UI(按钮写入后刷新) /// private void UpdatePressureUI5(string value) { UpdateUiSafely(() => frequencyTxt.Text = value); } /// /// 线程安全的UI更新通用方法(统一实现) /// private void UpdateUiSafely(Action action) { if (action == null) return; if (Dispatcher.HasShutdownStarted) { return; } try { if (!Dispatcher.CheckAccess()) { Dispatcher.Invoke(action, TimeSpan.FromSeconds(2)); } else { action.Invoke(); } } catch (TaskCanceledException) { } catch (Exception ex) { Console.WriteLine($"UI更新失败:{ex.Message}"); } } #endregion #region /// /// 返回主窗口 /// private void Button_Click_4(object sender, RoutedEventArgs e) { var mainWindow = MainWindow.Instance; // 检查窗口状态,只在窗口未显示时调用ShowDialog if (!mainWindow.IsVisible) { mainWindow.ShowDialog(); } else { // 如果窗口已显示,可将其激活到前台 mainWindow.Activate(); } Close(); } // 未实现的按钮事件(保留原空实现) private void Button_Click_5(object sender, RoutedEventArgs e) { } private void Button_Click_6(object sender, RoutedEventArgs e) { new LineChartDemo.MainWindow().ShowDialog(); Close(); } private void Button_Click_7(object sender, RoutedEventArgs e) { new Window6().ShowDialog(); Close(); } /// /// 打开报告窗口(Button8) /// private void Button_Click_8(object sender, RoutedEventArgs e) { new ReportWindow5().ShowDialog(); } /// /// 打开报告窗口(Button9,与Button8功能一致) /// private void Button_Click_9(object sender, RoutedEventArgs e) { new ReportWindow6().ShowDialog(); } /// /// 复位操作(Button10) /// private async void Button_Click_10(object sender, RoutedEventArgs e) { string _lang = ConfigurationManager.AppSettings["Language"] ?? "zh-CN"; if (!IsModbusConnected()) { UpdateResetButtonStatus(_lang == "en-US" ? "Disconnected" : "连接断开", Brushes.Red); ShowError(_lang == "en-US" ? "Modbus TCP not connected" : "Modbus TCP 未连接"); return; } try { UpdateResetButtonStatus(_lang == "en-US" ? "Resetting..." : "正在复位...", Brushes.LightGreen); await Task.Run(() => _modbusMaster.WriteSingleCoil(0x01, _resetAddress, true)); fc.BtnClickFunctionForNew(Function.ButtonType.复位型, _resetAddress); await Task.Delay(100); await Task.Run(() => _modbusMaster.WriteSingleCoil(0x01, _resetAddress, false)); fc.BtnClickFunctionForNew(Function.ButtonType.复位型, _resetAddress); UpdateResetButtonStatus(_lang == "en-US" ? "Reset Success" : "复位成功", Brushes.LightGreen); } catch (Exception ex) { Console.WriteLine(_lang == "en-US" ? $"Reset error: {ex.Message}" : $"复位异常: {ex.Message}"); UpdateResetButtonStatus(_lang == "en-US" ? "Reset Failed" : "复位失败", Brushes.Red); ShowError(_lang == "en-US" ? $"Operation error: {ex.Message}" : $"操作异常: {ex.Message}"); } } /// /// 二氧化碳测试启动(Button11) /// private async void Button_Click_11(object sender, RoutedEventArgs e) { string _lang = ConfigurationManager.AppSettings["Language"] ?? "zh-CN"; if (!IsModbusConnected()) { UpdateButtonStatus(_lang == "en-US" ? "Disconnected" : "连接断开", Brushes.Red); ShowError(_lang == "en-US" ? "Modbus TCP not connected" : "Modbus TCP 未连接"); return; } try { UpdateButtonStatus(_lang == "en-US" ? "Starting..." : "正在启动...", Brushes.LightGreen); // 写入启动线圈 await Task.Run(() => _modbusMaster.WriteSingleCoilAsync(0x01, _testStartAddress, true) ); // 等待并验证状态 await Task.Delay(200); // 写入启动线圈 await Task.Run(() => _modbusMaster.WriteSingleCoilAsync(0x01, _testStartAddress, false) ); // 等待并验证状态 await Task.Delay(200); UpdateButtonStatus(_lang == "en-US" ? "Test Start Success" : "测试启动成功", Brushes.LightGreen); } catch (Exception ex) { Console.WriteLine($"测试启动异常: {ex.Message}"); UpdateButtonStatus(_lang == "en-US" ? "Test Start Failed" : "测试启动失败", Brushes.Red); ShowError($"操作异常: {ex.Message}"); } } /// /// 测试停止(Button12) /// private async void Button_Click_12(object sender, RoutedEventArgs e) { string _lang = ConfigurationManager.AppSettings["Language"] ?? "zh-CN"; await WriteCoilWithCheck(_testStopAddress, true, null, _lang == "en-US" ? "Test stop timeout" : "测试停止超时,状态异常", _lang == "en-US" ? "Test Stop" : "测试停止"); TestStartButton.Content = _lang == "en-US" ? "Start Test" : "测试启动"; TestStartButton.Foreground = Brushes.White; } /// /// 开始数据记录(Button13) /// private async void Button_Click_13(object sender, RoutedEventArgs e) { string _lang = ConfigurationManager.AppSettings["Language"] ?? "zh-CN"; if (!IsModbusConnected()) { ShowError(_lang == "en-US" ? "Modbus TCP not connected" : "Modbus TCP 未连接"); return; } try { await Task.Delay(100); fc.BtnClickFunctionForNew(Function.ButtonType.切换型, 63); } catch (Exception ex) { ShowError(_lang == "en-US" ? $"Operation error: {ex.Message}" : $"操作异常: {ex.Message}"); } ShowWarning(_lang == "en-US" ? "Recording started" : "已开始记录"); _beginRecordTimer = CreateTimer(1000, OnBeginRecordTimerElapsed); } private async void Button_Click_14(object sender, RoutedEventArgs e) { string _lang = ConfigurationManager.AppSettings["Language"] ?? "zh-CN"; if (!IsModbusConnected()) { ShowError(_lang == "en-US" ? "Modbus TCP not connected" : "Modbus TCP 未连接"); return; } try { await Task.Delay(100); fc.BtnClickFunctionForNew(Function.ButtonType.切换型, 64); } catch (Exception ex) { ShowError(_lang == "en-US" ? $"Operation error: {ex.Message}" : $"操作异常: {ex.Message}"); } if (_beginRecordTimer != null) { MessageBox.Show(_lang == "en-US" ? "Recording stopped" : "已停止记录", _lang == "en-US" ? "Info" : "提示", MessageBoxButton.OK, MessageBoxImage.Information); _beginRecordTimer.Stop(); _beginRecordTimer.Dispose(); _beginRecordTimer = null; } else { MessageBox.Show(_lang == "en-US" ? "Not recording" : "未在记录状态,无需停止", _lang == "en-US" ? "Warning" : "提示", MessageBoxButton.OK, MessageBoxImage.Warning); return; } var records = ReadCO2RecordsFromDatabase(); if (records == null || !records.Any()) { MessageBox.Show(_lang == "en-US" ? "No CO2 data to export" : "CO2表中无数据,无法导出", _lang == "en-US" ? "Warning" : "提示", MessageBoxButton.OK, MessageBoxImage.Warning); return; } bool ok = ExportCO2RecordsToExcel(records); if (ok) MessageBox.Show(_lang == "en-US" ? "Export to Excel success" : "数据已成功导出到Excel", _lang == "en-US" ? "Success" : "成功", MessageBoxButton.OK, MessageBoxImage.Information); else MessageBox.Show(_lang == "en-US" ? "Export failed" : "Excel导出失败,请检查文件是否被占用", _lang == "en-US" ? "Error" : "错误", MessageBoxButton.OK, MessageBoxImage.Error); } /// /// 从数据库CO2表读取数据(不变) /// private List ReadCO2RecordsFromDatabase() { List records = new List(); try { using (SQLiteConnection conn = new SQLiteConnection(CSConstant.DbConnectionString)) { conn.Open(); // 查询CO2表所有记录(按时间排序) string query = "SELECT Flow, Pressure, BeginCO2, EndCO2, CO2Added, RecordTime FROM CO2 ORDER BY RecordTime desc limit 1 "; using (SQLiteCommand cmd = new SQLiteCommand(query, conn)) { using (SQLiteDataReader reader = cmd.ExecuteReader()) { while (reader.Read()) { records.Add(new CO2Record { Flow = reader.GetDouble(0), // 二氧化碳浓度(%) Pressure = reader.GetDouble(1), // 压力(pa) BeginCO2 = reader.GetDouble(2), // 开始CO2浓度(%) EndCO2 = reader.GetDouble(3), // 终CO2浓度(%) CO2Added = reader.GetDouble(4), // CO2浓度相对增加(%) RecordTime = reader.GetDateTime(5) // 时间 }); } } } } return records; } catch (Exception ex) { MessageBox.Show($"读取CO2表失败:{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); return null; } } /// /// 将CO2数据导出到Excel(WPF .NET 8 适配版) /// private bool ExportCO2RecordsToExcel(List records) { string _lang = ConfigurationManager.AppSettings["Language"] ?? "zh-CN"; try { SaveFileDialog dlg = new SaveFileDialog { Filter = _lang == "en-US" ? "Excel File (*.xlsx)|*.xlsx" : "Excel文件 (*.xlsx)|*.xlsx", FileName = $"CO2_Record_{DateTime.Now:yyyyMMddHHmmss}.xlsx", Title = _lang == "en-US" ? "Save CO2 Record" : "保存CO2记录" }; if (dlg.ShowDialog() != true) return false; ExcelPackage.LicenseContext = LicenseContext.NonCommercial; using (var pkg = new ExcelPackage(new FileInfo(dlg.FileName))) { var ws = pkg.Workbook.Worksheets.Add(_lang == "en-US" ? "CO2 Records" : "CO2记录"); ws.Cells[1, 1].Value = _lang == "en-US" ? "CO2(%)" : "二氧化碳浓度(%)"; ws.Cells[1, 2].Value = _lang == "en-US" ? "Pressure(Pa)" : "压力(pa)"; ws.Cells[1, 3].Value = _lang == "en-US" ? "Begin CO2(%)" : "开始CO2浓度(%)"; ws.Cells[1, 4].Value = _lang == "en-US" ? "End CO2(%)" : "终CO2浓度(%)"; ws.Cells[1, 5].Value = _lang == "en-US" ? "CO2 Added(%)" : "CO2浓度相对增加(%)"; ws.Cells[1, 6].Value = _lang == "en-US" ? "Time" : "时间"; var head = ws.Cells[1, 1, 1, 6]; head.Style.Font.Bold = true; head.Style.HorizontalAlignment = OfficeOpenXml.Style.ExcelHorizontalAlignment.Center; for (int i = 0; i < records.Count; i++) { var row = i + 2; var r = records[i]; ws.Cells[row, 1].Value = r.Flow; ws.Cells[row, 2].Value = r.Pressure; ws.Cells[row, 3].Value = r.BeginCO2; ws.Cells[row, 4].Value = r.EndCO2; ws.Cells[row, 5].Value = r.CO2Added; ws.Cells[row, 6].Value = r.RecordTime.ToString("yyyy-MM-dd HH:mm:ss"); } ws.Cells.AutoFitColumns(); pkg.Save(); } return true; } catch (Exception ex) { MessageBox.Show(_lang == "en-US" ? $"Export failed: {ex.Message}" : $"导出失败:{ex.Message}", _lang == "en-US" ? "Error" : "错误", MessageBoxButton.OK, MessageBoxImage.Error); return false; } } /// /// CO2记录实体类(与数据库字段对应) /// public class CO2Record { public double Flow { get; set; } // 二氧化碳浓度(%) public double Pressure { get; set; } // 压力(pa) public double BeginCO2 { get; set; } // 开始CO2浓度(%) public double EndCO2 { get; set; } // 终CO2浓度(%) public double CO2Added { get; set; } // CO2浓度相对增加(%) public DateTime RecordTime { get; set; } // 时间 } private void Button_Click_15(object sender, RoutedEventArgs e) { } #endregion #region 通用操作方法(提取重复逻辑) /// /// 写入线圈并验证状态(通用方法) /// private async Task WriteCoilWithCheck(ushort coilAddress, bool value, string successMsg, string failMsg, string logMsg) { string _lang = ConfigurationManager.AppSettings["Language"] ?? "zh-CN"; if (!IsModbusConnected()) { ShowError(_lang == "en-US" ? "Modbus TCP not connected" : "Modbus TCP 未连接"); return; } try { await Task.Run(() => _modbusMaster?.WriteSingleCoilAsync(0x01, coilAddress, value)); Thread.Sleep(200); bool needReset = false; if (failMsg != null) { bool stop = failMsg.Contains("停止") || failMsg.Contains("Stop") || failMsg.Contains("stop"); bool calib = failMsg.Contains("校准") || failMsg.Contains("Calib") || failMsg.Contains("calib"); needReset = stop || calib; } if (needReset) { await Task.Run(() => _modbusMaster?.WriteSingleCoilAsync(0x01, coilAddress, false)); } Thread.Sleep(100); await Task.Delay(500); bool[] status = await _modbusMaster?.ReadCoilsAsync(0x01, coilAddress, 1); bool noReset = false; if (failMsg != null) { bool stop = failMsg.Contains("停止") || failMsg.Contains("Stop") || failMsg.Contains("stop"); bool calib = failMsg.Contains("校准") || failMsg.Contains("Calib") || failMsg.Contains("calib"); noReset = !stop && !calib; } if (noReset && status != null && status.Length > 0 && status[0] == value) { WriteLog($"{logMsg} - Address: {coilAddress}, State: {value}"); if (!string.IsNullOrEmpty(successMsg)) ShowSuccess(successMsg); } else if (noReset) ShowError(failMsg); } catch (Exception ex) { ShowError(_lang == "en-US" ? $"Communication error: {ex.Message}" : $"通信错误: {ex.Message}"); } } /// /// 保存记录到数据库 /// private void SaveRecordToDatabase(double flow, double pressure, double beginCo2, double endCo2, double co2Added) { try { flow = Math.Round(flow, 2); pressure = Math.Round(pressure, 2); beginCo2 = Math.Round(beginCo2, 2); endCo2 = Math.Round(endCo2, 2); co2Added = Math.Round(co2Added, 2); using (var conn = new SQLiteConnection(CSConstant.DbConnectionString)) { conn.Open(); string insertSql = @" INSERT INTO CO2(Flow, Pressure, BeginCO2, EndCO2, CO2Added, RecordTime) VALUES (@Flow, @Pressure, @BeginCO2, @EndCO2, @CO2Added, @RecordTime);"; using (var cmd = new SQLiteCommand(insertSql, conn)) { cmd.Parameters.AddWithValue("@Flow", flow); cmd.Parameters.AddWithValue("@Pressure", pressure); cmd.Parameters.AddWithValue("@BeginCO2", beginCo2); cmd.Parameters.AddWithValue("@EndCO2", endCo2); cmd.Parameters.AddWithValue("@CO2Added", co2Added); cmd.Parameters.AddWithValue("@RecordTime", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); cmd.ExecuteNonQuery(); } } } catch (Exception ex) { Console.WriteLine($"保存数据库失败: {ex.Message}"); ShowWarning($"数据保存失败: {ex.Message}"); } } #endregion #region 辅助方法(UI状态更新、日志、消息提示) /// /// 更新测试按钮状态UI /// private void UpdateButtonStatus(string text, Brush color) { UpdateUiSafely(() => { TestStartButton.Content = text; TestStartButton.Foreground = color; }); } private void UpdateAddButtonStatus(string text, Brush color) { UpdateUiSafely(() => { addPaBtn.Content = text; addPaBtn.Foreground = color; }); } /// /// 更新复位按钮状态UI /// private void UpdateResetButtonStatus(string text, Brush color) { UpdateUiSafely(() => { ResetBtn.Content = text; ResetBtn.Foreground = color; }); } /// /// 日志路径 /// 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 Window_Loaded(object sender, RoutedEventArgs e) { 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}"); } } bool _isWriting; // 消息提示封装(统一风格) 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); #endregion private async Task WriteRegisterWithValidation(TextBox inputControl, ushort registerAddress, float minValue, float maxValue) { string _lang = ConfigurationManager.AppSettings["Language"] ?? "zh-CN"; if (!IsModbusConnected()) { ShowError(_lang == "en-US" ? "Modbus TCP not connected" : "Modbus TCP 未连接"); return; } try { inputControl.Text = _lang == "en-US" ? "Processing..." : "操作中..."; Function ma = new Function(_modbusMaster); ma.WriteToPLCForNew("", registerAddress, Function.DataType.浮点型); await Task.Delay(300); } catch (Exception ex) { ShowError(_lang == "en-US" ? $"Operation failed: {ex.Message}" : $"操作失败: {ex.Message}"); } } private bool _isPressurizing = false; private CancellationTokenSource _pressurizeCts; private async void Button_Click_16(object sender, RoutedEventArgs e) { _isPressurizing = !_isPressurizing; if (_isPressurizing) { // 开始持续加压 await StartPressurizeLoop(); } else { // 停止加压(取消循环并复位状态) StopPressurize(); } } /// /// 开始持续加压循环 /// private async Task StartPressurizeLoop() { string _lang = ConfigurationManager.AppSettings["Language"] ?? "zh-CN"; if (!IsModbusConnected()) { UpdateAddButtonStatus(_lang == "en-US" ? "Disconnected" : "连接断开", Brushes.Red); ShowError(_lang == "en-US" ? "Modbus TCP not connected" : "Modbus TCP 未连接"); _isPressurizing = false; return; } _pressurizeCts = new CancellationTokenSource(); var token = _pressurizeCts.Token; try { UpdateAddButtonStatus(_lang == "en-US" ? "Pressurizing..." : "正在加压...", Brushes.LightGreen); while (_isPressurizing && !token.IsCancellationRequested) { await Task.Run(() => _modbusMaster.WriteSingleCoilAsync(0x01, 657, true)); await Task.Delay(200); if (!IsModbusConnected()) throw new Exception(_lang == "en-US" ? "Modbus disconnected" : "Modbus连接中途断开"); } if (!token.IsCancellationRequested) UpdateAddButtonStatus(_lang == "en-US" ? "Pressurize" : "加压", Brushes.White); } catch (Exception ex) { Console.WriteLine(_lang == "en-US" ? $"Pressurize error: {ex.Message}" : $"加压异常: {ex.Message}"); UpdateAddButtonStatus(_lang == "en-US" ? "Press Failed" : "加压失败", Brushes.Red); ShowError(_lang == "en-US" ? $"Operation error: {ex.Message}" : $"操作异常: {ex.Message}"); _isPressurizing = false; } } /// /// 停止加压(复位状态和资源) /// private void StopPressurize() { string _lang = ConfigurationManager.AppSettings["Language"] ?? "zh-CN"; if (_pressurizeCts != null && !_pressurizeCts.IsCancellationRequested) { _pressurizeCts.Cancel(); _pressurizeCts.Dispose(); } _isPressurizing = false; UpdateAddButtonStatus(_lang == "en-US" ? "Pressurize" : "加压", Brushes.White); try { if (IsModbusConnected()) _modbusMaster.WriteSingleCoilAsync(0x01, 657, false).Wait(); } catch { } } private async void Button_Click_17(object sender, RoutedEventArgs e) { try { // 验证并写入呼吸比-呼气值 await WriteRegisterWithValidation( inputControl: OutTxt, registerAddress: 342, // 呼吸比-呼气寄存器地址 minValue: 1, // 呼吸比最小1 maxValue: 10000 // 呼吸比最大20 ); } finally { } } private async void Button_Click_21(object sender, RoutedEventArgs e) { try { // 验证并写入频率系数值 await WriteRegisterWithValidation( inputControl: frequencyTxt, registerAddress: 368, // 频率系数寄存器地址(根据实际地址修改) minValue: 0.1f, // 频率系数最小0.1 maxValue: 10f // 频率系数最大10 ); } finally { } } private async void Button_Click_20(object sender, RoutedEventArgs e) { try { // 验证并写入呼吸频率值 await WriteRegisterWithValidation( inputControl: respiratoryRateTxt, registerAddress: 210, // 呼吸频率寄存器地址(根据实际地址修改) minValue: 1, // 呼吸频率最小5 maxValue: 10000 // 呼吸频率最大60 ); } finally { } } private async void Button_Click_19(object sender, RoutedEventArgs e) { try { // 验证并写入潮气量值 await WriteRegisterWithValidation( inputControl: moistureTxt, registerAddress: 300, minValue: 0.1f, // 潮气量最小0.1 maxValue: 10000f // 潮气量最大20 ); } finally { } } private async void Button_Click_18(object sender, RoutedEventArgs e) { try { // 验证并写入呼吸比-吸气值 await WriteRegisterWithValidation( inputControl: InTxt, registerAddress: 340, minValue: 1, // 呼吸比最小1 maxValue: 10000 // 呼吸比最大500 ); } finally { } } } }