Files
Sleep-Multi-functionality/Window5.xaml.cs
2026-05-07 16:51:45 +08:00

1688 lines
59 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
{
/// <summary>
/// 呼吸相关参数监控窗口Modbus通信+CO2数据记录
/// </summary>
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
/// <summary>
/// 初始化Modbus连接和定时器
/// </summary>
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
/// <summary>
/// 通用数字输入过滤(支持整数和小数)
/// </summary>
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
/// <summary>
/// 统一初始化所有定时器(避免重复配置)
/// </summary>
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)
{
try
{
bool[] result = _modbusMaster?.ReadCoils(0x01, 98, 1);
bool isTestRunning = result != null && result.Length > 0 && result[0];
TestStartButton.Dispatcher.Invoke(() =>
{
if (isTestRunning)
{
TestStartButton.Content = "测试启动成功";
TestStartButton.Foreground = Brushes.LightGreen;
}
else
{
TestStartButton.Content = "测试启动";
TestStartButton.Foreground = Brushes.White;
}
});
}
catch (Exception ex)
{
Console.WriteLine($"读取线圈或更新UI失败{ex.Message}");
}
}
private void OnResetTimerElapsed(object sender, ElapsedEventArgs e)
{
try
{
bool[] result = _modbusMaster?.ReadCoils(0x01, 3, 1);
bool isTestRunning = result != null && result.Length > 0 && result[0];
ResetBtn.Dispatcher.Invoke(() =>
{
if (isTestRunning)
{
ResetBtn.Content = "复位成功";
ResetBtn.Foreground = Brushes.LightGreen;
}
else
{
ResetBtn.Content = "复位";
ResetBtn.Foreground = Brushes.White;
}
});
}
catch (Exception ex)
{
Console.WriteLine($"读取线圈或更新UI失败{ex.Message}");
}
}
//int i = 0;
// 新增:记录当前状态(避免重复触发)
private string _currentBreathState = ""; // 可能的值:"吸气"、"呼气"、"异常"
private void OnbreathTimerElapsed(object sender, ElapsedEventArgs e)
{
try
{
// 读取线圈状态
bool[] inhaleResult = _modbusMaster?.ReadCoils(0x01, 51, 1);
bool[] exhaleResult = _modbusMaster?.ReadCoils(0x01, 55, 1);
bool isInhale = inhaleResult != null && inhaleResult.Length > 0 && inhaleResult[0];
bool isExhale = exhaleResult != null && exhaleResult.Length > 0 && exhaleResult[0];
// 确定新状态(先判断异常,再判断正常状态)
string newState;
if (isInhale && isExhale || !isInhale && !isExhale)
{
newState = "异常";
}
else if (isInhale)
{
newState = "吸气";
}
else // isExhale
{
newState = "呼气";
}
// 关键仅当新状态与当前状态不同时才更新UI避免重复触发
if (newState != _currentBreathState)
{
breathType.Dispatcher.Invoke(() =>
{
// 更新当前状态记录
_currentBreathState = newState;
if (newState == "异常")
{
breathType.Text = "待机";
// 停止所有动画,避免异常状态下动画混乱
StopAllAnimations();
breathType.Foreground = new SolidColorBrush(Colors.Orange);
breathType.FontSize = 20;
breathType.Opacity = 1.0;
}
else if (newState == "吸气")
{
breathType.Text = "吸气";
// 停止现有动画(避免与新动画冲突)
StopAllAnimations();
// 颜色动画
ColorAnimation colorAnimation = new ColorAnimation
{
From = Colors.Gray,
To = Colors.Green,
Duration = TimeSpan.FromSeconds(2.5)
};
SolidColorBrush colorBrush = new SolidColorBrush(Colors.Gray);
breathType.Foreground = colorBrush;
colorBrush.BeginAnimation(SolidColorBrush.ColorProperty, colorAnimation);
// 大小动画
DoubleAnimation sizeAnimation = new DoubleAnimation
{
From = 16,
To = 40,
Duration = TimeSpan.FromSeconds(2.5)
};
breathType.BeginAnimation(TextBlock.FontSizeProperty, sizeAnimation);
// 透明度动画
DoubleAnimation opacityAnimation = new DoubleAnimation
{
From = 0.5,
To = 1.0,
Duration = TimeSpan.FromSeconds(2.5)
};
breathType.BeginAnimation(TextBlock.OpacityProperty, opacityAnimation);
}
else // 呼气
{
breathType.Text = "呼气";
// 停止现有动画
StopAllAnimations();
// 颜色动画
ColorAnimation colorAnimation = new ColorAnimation
{
From = Colors.Green,
To = Colors.Red,
Duration = TimeSpan.FromSeconds(2.5)
};
SolidColorBrush colorBrush = new SolidColorBrush(Colors.Green);
breathType.Foreground = colorBrush;
colorBrush.BeginAnimation(SolidColorBrush.ColorProperty, colorAnimation);
// 大小动画
DoubleAnimation sizeAnimation = new DoubleAnimation
{
From = 40,
To = 16,
Duration = TimeSpan.FromSeconds(2.5)
};
breathType.BeginAnimation(TextBlock.FontSizeProperty, sizeAnimation);
// 透明度动画
DoubleAnimation opacityAnimation = new DoubleAnimation
{
From = 1.0,
To = 0.5,
Duration = TimeSpan.FromSeconds(2.5)
};
breathType.BeginAnimation(TextBlock.OpacityProperty, opacityAnimation);
}
});
}
}
catch (Exception ex)
{
Console.WriteLine($"读取线圈或更新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);
}
}
/// <summary>
/// 通用定时器创建方法(统一配置)
/// </summary>
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;
}
/// <summary>
/// 初始化数据库创建CO2记录表
/// </summary>
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}");
}
}
/// <summary>
/// 释放所有资源(避免内存泄漏)
/// </summary>
public void Dispose()
{
// 释放定时器(含数据记录定时器)
_outReadTimer?.Dispose();
_breathTimer?.Dispose();
_co2Timer?.Dispose();
_co2BeginEndTimer?.Dispose();
_co2AddTimer?.Dispose();
_beginRecordTimer?.Dispose();
// 释放Modbus连接
_tcpClient?.Close();
_tcpClient?.Dispose();
_modbusMaster = null;
}
/// <summary>
/// 窗口关闭时强制释放资源
/// </summary>
protected override void OnClosed(EventArgs e)
{
base.OnClosed(e);
Dispose();
}
#endregion
#region
/// <summary>
/// 鼻口参数读取(呼吸次数)
/// </summary>
private void OnOutTimerElapsed(object sender, ElapsedEventArgs e)
{
ReadAndUpdateSingleRegister(
_outRegisterAddress, false,
value => UpdateOutUI(value.ToString())
);
}
/// <summary>
/// 呼吸计时读取(分+秒)
/// </summary>
private void OnBreathTimerElapsed(object sender, ElapsedEventArgs e)
{
ReadAndUpdateDualRegisterUshort(
_breathMinuteAddress,
_breathSecondAddress,
(min, sec) => UpdateTimerUI(min.ToString(), sec.ToString())
);
}
/// <summary>
/// 呼路管压力+CO2浓度读取
/// </summary>
private void OnCo2TimerElapsed(object sender, ElapsedEventArgs e)
{
ReadAndUpdateDualRegister(
_breathOutAddress,
_co2Address,
(pressure, co2) => UpdateCo2TimerUI(pressure.ToString(), co2.ToString())
);
}
/// <summary>
/// 数据记录定时器定时保存CO2相关参数
/// </summary>
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}");
}
}
/// <summary>
/// CO2开始/结束浓度读取
/// </summary>
private void OnCO2BeginEndTimerElapsed(object sender, ElapsedEventArgs e)
{
ReadAndUpdateDualRegister(
_co2BeginAddress,
_co2EndAddress,
(begin, end) => UpdateCO2BeginEndTimerUI(begin.ToString(), end.ToString())
);
}
/// <summary>
/// CO2增加值读取
/// </summary>
private void OnCO2AddTimerElapsed(object sender, ElapsedEventArgs e)
{
ReadAndUpdateSingleRegister(
_co2AddAddress, true,
value => UpdateCO2AddTimerUI(value.ToString())
);
}
#endregion
#region Modbus通用操作
/// <summary>
/// 通用寄存器读取并更新UI支持16位整数和32位浮点数
/// </summary>
/// <param name="address">起始地址</param>
/// <param name="isFloat">是否为浮点型占用2个寄存器</param>
/// <param name="updateAction">更新UI的回调函数</param>
private void ReadAndUpdateSingleRegister(ushort address, bool isFloat, Action<object> 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]是D312data[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]是D312data[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;
}
}
/// <summary>
/// 读取两个寄存器并更新UI统一处理连接状态
/// </summary>
private void ReadAndUpdateDualRegister(ushort address1, ushort address2, Action<float, float> 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]是D312data[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]是D312data[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显示"读取失败"
}
}
/// <summary>
/// 读取两个寄存器并更新UI统一处理连接状态
/// </summary>
private void ReadAndUpdateDualRegisterUshort(ushort address1, ushort address2, Action<float, float> 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显示"读取失败"
}
}
/// <summary>
/// 检查Modbus连接状态统一判断逻辑
/// </summary>
private bool IsModbusConnected()
{
return _modbusMaster != null && _tcpClient?.Connected == true;
}
#endregion
#region UI更新线
/// <summary>
/// 更新呼吸次数UI
/// </summary>
private void UpdateOutUI(string value)
{
UpdateUiSafely(() =>
respiratoryCountTxt.Text = IsModbusConnected() ? value : "连接断开"
);
}
/// <summary>
/// 更新呼吸计时UI
/// </summary>
private void UpdateTimerUI(string minute, string second)
{
UpdateUiSafely(() =>
{
breathTimeBegin.Text = IsModbusConnected() ? minute : "连接断开";
breathTimeEnd.Text = IsModbusConnected() ? second : "连接断开";
});
}
/// <summary>
/// 更新CO2和呼路管压力UI
/// </summary>
private void UpdateCo2TimerUI(string pressure, string co2)
{
UpdateUiSafely(() =>
{
RespiratoryPressureTxt.Text = IsModbusConnected() ? pressure : "连接断开";
CO2PressureTxt.Text = IsModbusConnected() ? co2 : "连接断开";
});
}
/// <summary>
/// 更新CO2开始/结束浓度UI
/// </summary>
private void UpdateCO2BeginEndTimerUI(string begin, string end)
{
UpdateUiSafely(() =>
{
CO2BeginTb.Text = IsModbusConnected() ? begin : "连接断开";
CO2EndTb.Text = IsModbusConnected() ? end : "连接断开";
});
}
/// <summary>
/// 更新CO2增加值UI
/// </summary>
private void UpdateCO2AddTimerUI(string value)
{
UpdateUiSafely(() =>
CO2AddTxt.Text = IsModbusConnected() ? value : "连接断开"
);
}
/// <summary>
/// 更新流量压力UI按钮写入后刷新
/// </summary>
private void UpdateFlowPressureUI(string value, string value2)
{
UpdateUiSafely(() =>
{
OutTxt.Text = value;
InTxt.Text = value2;
});
}
/// <summary>
/// 更新潮气UI按钮写入后刷新
/// </summary>
private void UpdatePressureUI1(string value)
{
UpdateUiSafely(() => OutTxt.Text = value);
}
/// <summary>
/// 更新潮气UI按钮写入后刷新
/// </summary>
private void UpdatePressureUI2(string value)
{
UpdateUiSafely(() => InTxt.Text = value);
}
/// <summary>
/// 更新潮气UI按钮写入后刷新
/// </summary>
private void UpdatePressureUI3(string value)
{
UpdateUiSafely(() => moistureTxt.Text = value);
}
/// <summary>
/// 更新潮气UI按钮写入后刷新
/// </summary>
private void UpdatePressureUI4(string value)
{
UpdateUiSafely(() => respiratoryRateTxt.Text = value);
}
/// <summary>
/// 更新潮气UI按钮写入后刷新
/// </summary>
private void UpdatePressureUI5(string value)
{
UpdateUiSafely(() => frequencyTxt.Text = value);
}
/// <summary>
/// 线程安全的UI更新通用方法统一实现
/// </summary>
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
/// <summary>
/// 返回主窗口
/// </summary>
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();
}
/// <summary>
/// 打开报告窗口Button8
/// </summary>
private void Button_Click_8(object sender, RoutedEventArgs e)
{
new ReportWindow5().ShowDialog();
}
/// <summary>
/// 打开报告窗口Button9与Button8功能一致
/// </summary>
private void Button_Click_9(object sender, RoutedEventArgs e)
{
new ReportWindow6().ShowDialog();
}
/// <summary>
/// 复位操作Button10
/// </summary>
private async void Button_Click_10(object sender, RoutedEventArgs e)
{
if (!IsModbusConnected())
{
UpdateResetButtonStatus("连接断开", Brushes.Red);
ShowError("Modbus TCP 未连接");
return;
}
try
{
UpdateResetButtonStatus("正在复位...", 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("复位成功", Brushes.LightGreen);
}
catch (Exception ex)
{
Console.WriteLine($"复位异常: {ex.Message}");
UpdateResetButtonStatus("复位失败", Brushes.Red);
ShowError($"操作异常: {ex.Message}");
}
}
/// <summary>
/// 二氧化碳测试启动Button11
/// </summary>
private async void Button_Click_11(object sender, RoutedEventArgs e)
{
if (!IsModbusConnected())
{
UpdateButtonStatus("连接断开", Brushes.Red);
ShowError("Modbus TCP 未连接");
return;
}
try
{
UpdateButtonStatus("正在启动...", 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("测试启动成功", Brushes.LightGreen);
}
catch (Exception ex)
{
Console.WriteLine($"测试启动异常: {ex.Message}");
UpdateButtonStatus("测试启动失败", Brushes.Red);
ShowError($"操作异常: {ex.Message}");
}
}
/// <summary>
/// 测试停止Button12
/// </summary>
private async void Button_Click_12(object sender, RoutedEventArgs e)
{
await WriteCoilWithCheck(
coilAddress: _testStopAddress, // M8统一使用常量地址
value: true,
successMsg: null,
failMsg: "测试停止超时,状态异常",
logMsg: "测试停止"
);
TestStartButton.Content = "测试启动";
TestStartButton.Foreground = Brushes.White;
}
/// <summary>
/// 开始数据记录Button13
/// </summary>
private async void Button_Click_13(object sender, RoutedEventArgs e)
{
if (!IsModbusConnected())
{
ShowError("Modbus TCP 未连接");
return;
}
try
{
await Task.Delay(100);
fc.BtnClickFunctionForNew(Function.ButtonType., 63);
}
catch (Exception ex)
{
ShowError($"操作异常: {ex.Message}");
}
ShowWarning("已开始记录");
// 创建并启动数据记录定时器1秒间隔
_beginRecordTimer = CreateTimer(1000, OnBeginRecordTimerElapsed);
}
private async void Button_Click_14(object sender, RoutedEventArgs e)
{
if (!IsModbusConnected())
{
ShowError("Modbus TCP 未连接");
return;
}
try
{
await Task.Delay(100);
fc.BtnClickFunctionForNew(Function.ButtonType., 64);
}
catch (Exception ex)
{
ShowError($"操作异常: {ex.Message}");
}
// 步骤1停止定时器并释放资源
if (_beginRecordTimer != null)
{
MessageBox.Show("已停止记录", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
_beginRecordTimer.Stop();
_beginRecordTimer.Dispose();
_beginRecordTimer = null;
}
else
{
MessageBox.Show("未在记录状态,无需停止", "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
// 步骤2读取CO2表数据
List<CO2Record> co2Records = ReadCO2RecordsFromDatabase();
if (co2Records == null || !co2Records.Any())
{
MessageBox.Show("CO2表中无数据无法导出", "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
// 步骤3导出Excel
bool exportSuccess = ExportCO2RecordsToExcel(co2Records);
if (exportSuccess)
{
MessageBox.Show("数据已成功导出到Excel", "成功", MessageBoxButton.OK, MessageBoxImage.Information);
}
else
{
MessageBox.Show("Excel导出失败请检查文件是否被占用", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
/// <summary>
/// 从数据库CO2表读取数据不变
/// </summary>
private List<CO2Record> ReadCO2RecordsFromDatabase()
{
List<CO2Record> records = new List<CO2Record>();
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;
}
}
/// <summary>
/// 将CO2数据导出到ExcelWPF .NET 8 适配版)
/// </summary>
private bool ExportCO2RecordsToExcel(List<CO2Record> records)
{
try
{
// WPF 原生保存对话框(替代 WinForms
SaveFileDialog saveDialog = new SaveFileDialog
{
Filter = "Excel文件 (*.xlsx)|*.xlsx",
FileName = $"CO2记录_{DateTime.Now:yyyyMMddHHmmss}.xlsx",
Title = "保存CO2记录"
};
// 用户取消选择则返回
bool? result = saveDialog.ShowDialog(); if (!(result ?? false)) return false;
// EPPlus 许可设置(.NET 8 必须显式设置)
ExcelPackage.LicenseContext = LicenseContext.NonCommercial; // 非商业用途
// 创建并写入Excel
using (ExcelPackage package = new ExcelPackage(new FileInfo(saveDialog.FileName)))
{
ExcelWorksheet worksheet = package.Workbook.Worksheets.Add("CO2记录");
// 表头对应DataGrid列
worksheet.Cells[1, 1].Value = "二氧化碳浓度(%";
worksheet.Cells[1, 2].Value = "压力pa";
worksheet.Cells[1, 3].Value = "开始CO2浓度%";
worksheet.Cells[1, 4].Value = "终CO2浓度%";
worksheet.Cells[1, 5].Value = "CO2浓度相对增加%";
worksheet.Cells[1, 6].Value = "时间";
// 表头样式(加粗、居中)
using (var headerRange = worksheet.Cells[1, 1, 1, 6])
{
headerRange.Style.Font.Bold = true;
headerRange.Style.HorizontalAlignment = OfficeOpenXml.Style.ExcelHorizontalAlignment.Center;
}
// 填充数据从第2行开始
for (int i = 0; i < records.Count; i++)
{
int row = i + 2;
var record = records[i];
worksheet.Cells[row, 1].Value = record.Flow;
worksheet.Cells[row, 2].Value = record.Pressure;
worksheet.Cells[row, 3].Value = record.BeginCO2;
worksheet.Cells[row, 4].Value = record.EndCO2;
worksheet.Cells[row, 5].Value = record.CO2Added;
worksheet.Cells[row, 6].Value = record.RecordTime.ToString("yyyy-MM-dd HH:mm:ss");
}
// 自动调整列宽
worksheet.Cells.AutoFitColumns();
// 保存文件
package.Save();
}
return true;
}
catch (Exception ex)
{
MessageBox.Show($"导出失败:{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
return false;
}
}
/// <summary>
/// CO2记录实体类与数据库字段对应
/// </summary>
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
/// <summary>
/// 写入线圈并验证状态(通用方法)
/// </summary>
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(200);
// 等待并验证
await Task.Delay(500);
bool[] status = await _modbusMaster?.ReadCoilsAsync(0x01, coilAddress, 1);
if (status != null)
{
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}");
}
}
/// <summary>
/// 保存记录到数据库
/// </summary>
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状态更新
/// <summary>
/// 更新测试按钮状态UI
/// </summary>
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;
});
}
/// <summary>
/// 更新复位按钮状态UI
/// </summary>
private void UpdateResetButtonStatus(string text, Brush color)
{
UpdateUiSafely(() =>
{
ResetBtn.Content = text;
ResetBtn.Foreground = color;
});
}
/// <summary>
/// 日志路径
/// </summary>
private string logPath => System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "modbus_log.txt");
/// <summary>
/// 写入操作日志
/// </summary>
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}");
}
}
/// <summary>
/// 加载窗口背景
/// </summary>
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)
{
if (!IsModbusConnected())
{
ShowError("Modbus TCP 未连接");
return;
}
try
{
inputControl.Text = "操作中...";
Function ma = new Function(_modbusMaster);
ma.WriteToPLCForNew("", registerAddress, Function.DataType.);
// 等待并刷新
await Task.Delay(300);
//ReadAndUpdateSingleRegister(342, true, v => UpdatePressureUI1(v.ToString()));
//ReadAndUpdateSingleRegister(registerAddress, true, v => UpdatePressureUI1(v.ToString()));
//ReadAndUpdateSingleRegister(registerAddress, true, v => UpdatePressureUI1(v.ToString()));
//ReadAndUpdateSingleRegister(registerAddress, true, v => UpdatePressureUI1(v.ToString()));
//ReadAndUpdateSingleRegister(registerAddress, true, v => UpdatePressureUI1(v.ToString()));
}
catch (Exception ex)
{
ShowError($"操作失败: {ex.Message}");
}
finally
{
}
}
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();
}
}
/// <summary>
/// 开始持续加压循环
/// </summary>
private async Task StartPressurizeLoop()
{
// 检查Modbus连接
if (!IsModbusConnected())
{
UpdateAddButtonStatus("连接断开", Brushes.Red);
ShowError("Modbus TCP 未连接");
_isPressurizing = false; // 重置状态
return;
}
// 初始化取消令牌(用于安全停止循环)
_pressurizeCts = new CancellationTokenSource();
var token = _pressurizeCts.Token;
try
{
UpdateAddButtonStatus("正在加压...", Brushes.LightGreen);
// 异步循环:持续执行加压操作,直到状态为停止或被取消
while (_isPressurizing && !token.IsCancellationRequested)
{
await Task.Run(() =>
_modbusMaster.WriteSingleCoilAsync(0x01, 657, true)
);
await Task.Delay(200);
// 循环中再次检查连接(防止中途断开)
if (!IsModbusConnected())
{
throw new Exception("Modbus连接中途断开");
}
}
// 正常停止时更新状态
if (!token.IsCancellationRequested)
{
UpdateAddButtonStatus("加压", Brushes.White);
}
}
catch (Exception ex)
{
Console.WriteLine($"加压异常: {ex.Message}");
UpdateAddButtonStatus("加压失败", Brushes.Red);
ShowError($"操作异常: {ex.Message}");
_isPressurizing = false;
}
}
/// <summary>
/// 停止加压(复位状态和资源)
/// </summary>
private void StopPressurize()
{
// 取消循环令牌
if (_pressurizeCts != null && !_pressurizeCts.IsCancellationRequested)
{
_pressurizeCts.Cancel();
_pressurizeCts.Dispose(); // 释放资源
}
// 复位状态和显示
_isPressurizing = false;
UpdateAddButtonStatus("加压", Brushes.White);
try
{
if (IsModbusConnected())
{
_modbusMaster.WriteSingleCoilAsync(0x01, 657, false).Wait();
}
}
catch (Exception ex)
{
Console.WriteLine($"停止时复位线圈失败: {ex.Message}");
}
}
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
{
}
}
}
}