Files
Sleep-Multi-functionality/Window5.xaml.cs

1772 lines
62 KiB
C#
Raw Normal View History

2026-05-04 14:46:58 +08:00
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>
///// 呼吸比设置OutTxt和InTxt
///// </summary>
//private async void Button_Click(object sender, RoutedEventArgs e)
//{
// if (!IsModbusConnected())
// {
// ShowError("Modbus TCP 未连接");
// return;
// }
// // 输入验证
// if (!int.TryParse(OutTxt.Text.Trim(), out int outValue) ||
// !int.TryParse(InTxt.Text.Trim(), out int inValue))
// {
// ShowWarning("请输入有效的整数");
// return;
// }
// if (outValue < 0 || outValue > 10000 || inValue < 0 || inValue > 10000)
// {
// ShowWarning("值范围应为 0~10000");
// return;
// }
// try
// {
// // 显示操作中状态
// OutTxt.Text = "操作中...";
// InTxt.Text = "操作中...";
// // 写入两个非连续寄存器
// _modbusMaster.WriteSingleRegister(0x01, _outAddress, (ushort)outValue);
// _modbusMaster.WriteSingleRegister(0x01, _inAddress, (ushort)inValue);
// await Task.Delay(300); // 等待PLC处理
// RefreshFlowPressure(); // 刷新显示
// ShowSuccess($"设置呼吸比: {outValue}-{inValue}");
// }
// catch (Exception ex)
// {
// ShowError($"设置失败: {ex.Message}");
// }
// finally
// {
// OutTxt.Text = outValue.ToString();
// InTxt.Text = inValue.ToString();
// }
//}
///// <summary>
///// 潮气量设置moistureTxt
///// </summary>
//private async void Button_Click_1(object sender, RoutedEventArgs e)
//{
// await WriteSingleRegisterWithUI(
// inputControl: moistureTxt,
// registerAddress: _moistureTxtAddress,
// successMessage: value => $"设置潮气量: {value} L"
// );
//}
///// <summary>
///// 呼吸频率设置respiratoryRateTxt
///// </summary>
//private async void Button_Click_2(object sender, RoutedEventArgs e)
//{
// await WriteSingleRegisterWithUI(
// inputControl: respiratoryRateTxt,
// registerAddress: _respiratoryRateAddress,
// successMessage: value => $"设置呼吸频率: {value}"
// );
//}
///// <summary>
///// 频率系数设置frequencyTxt
///// </summary>
//private async void Button_Click_3(object sender, RoutedEventArgs e)
//{
// await WriteSingleRegisterWithUI(
// inputControl: frequencyTxt,
// registerAddress: _frequencyAddress,
// successMessage: value => $"设置频率系数: {value}"
// );
//}
/// <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
{
}
}
}
}