using Microsoft.Win32;
using Modbus;
using Modbus.Device;
using OfficeOpenXml;
using System;
using System.Configuration;
using System.Data.SQLite;
using System.IO;
using System.Net.Mail;
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 口罩泄露定制款;
using static OfficeOpenXml.ExcelErrorValue;
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(
3680,
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,
IsEnd BOOLEAN DEFAULT 0
);";
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