1518 lines
59 KiB
C#
1518 lines
59 KiB
C#
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)
|
||
{
|
||
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);
|
||
}
|
||
}
|
||
|
||
|
||
/// <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]是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;
|
||
}
|
||
}
|
||
|
||
/// <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]是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显示"读取失败"
|
||
}
|
||
}
|
||
|
||
/// <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更新(统一线程安全处理)
|
||
|
||
|
||
|
||
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" : "连接断开"));
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
|
||
/// <summary>
|
||
/// 更新呼吸计时UI
|
||
/// </summary>
|
||
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" : "连接断开");
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新CO2和呼路管压力UI
|
||
/// </summary>
|
||
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" : "连接断开");
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新CO2开始/结束浓度UI
|
||
/// </summary>
|
||
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" : "连接断开");
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新CO2增加值UI
|
||
/// </summary>
|
||
private void UpdateCO2AddTimerUI(string value)
|
||
{
|
||
string currentLanguage = ConfigurationManager.AppSettings["Language"] ?? "zh-CN";
|
||
UpdateUiSafely(() =>
|
||
CO2AddTxt.Text = IsModbusConnected() ? value : (currentLanguage == "en-US" ? "Disconnected" : "连接断开")
|
||
);
|
||
}
|
||
|
||
|
||
/// <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)
|
||
{
|
||
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}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 二氧化碳测试启动(Button11)
|
||
/// </summary>
|
||
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}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 测试停止(Button12)
|
||
/// </summary>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 开始数据记录(Button13)
|
||
/// </summary>
|
||
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);
|
||
}
|
||
|
||
|
||
/// <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数据导出到Excel(WPF .NET 8 适配版)
|
||
/// </summary>
|
||
private bool ExportCO2RecordsToExcel(List<CO2Record> 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;
|
||
}
|
||
}
|
||
|
||
/// <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)
|
||
{
|
||
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}");
|
||
}
|
||
}
|
||
|
||
/// <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)
|
||
{
|
||
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();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 开始持续加压循环
|
||
/// </summary>
|
||
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;
|
||
}
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 停止加压(复位状态和资源)
|
||
/// </summary>
|
||
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
|
||
{
|
||
}
|
||
}
|
||
}
|
||
} |