Files
Sleep-Multi-functionality/Window7.xaml.cs
2026-05-08 19:10:44 +08:00

545 lines
22 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Modbus.Device;
using Modbus;
using OxyPlot.Series;
using OxyPlot;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using System.Timers;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Windows.Threading;
using OxyPlot.Axes;
using OxyPlot.Legends;
using ;
using static OfficeOpenXml.ExcelErrorValue;
namespace ShanghaiEnvironmentalTechnology
{
/// <summary>
/// 参数配置界面逻辑
/// </summary>
public partial class ParameterConfigWindow : Window
{
private readonly string _lang = ConfigurationManager.AppSettings["Language"] ?? "zh-CN";
Function ma;
DataChange c = new DataChange();
#region
// 流量参数9个
private class FlowParameters
{
public float ExhaustP { get; set; } // 排气P
public float FlowP { get; set; } // 流量P
public float InhaleP { get; set; } // 吸P
public float ExhaustI { get; set; } // 排气I
public float FlowI { get; set; } // 流量I
public float InhaleI { get; set; } // 吸I
public float ExhaustD { get; set; } // 排气D
public float FlowD { get; set; } // 流量D
public float InhaleD { get; set; } // 吸D
}
// PID参数4个
private class PIDParameters
{
public float ExpiratoryPID { get; set; } // 呼PID加系数
public float InspiratoryPID { get; set; } // 吸PID加系数
public float ExhaustFlowPID { get; set; } // 排气流PID加系数
public float ResistancePID { get; set; } // 气阻PID加系数
}
// 系数参数4个
private class CoefficientParameters
{
public float ExpiratoryFlowCoeff { get; set; } // 呼流量系数
public float InspiratoryFlowCoeff { get; set; } // 吸流量系数
public float NasalPressureCoeff { get; set; } // 鼻口压力系数
public float InterfacePressureCoeff { get; set; } // 连接口压力系数
}
#endregion
#region
// 定时器3个分别对应三类参数
private readonly System.Timers.Timer _flowTimer = new System.Timers.Timer(1000); // 流量参数定时器1秒更新一次
private readonly System.Timers.Timer _pidTimer = new System.Timers.Timer(1000); // PID参数定时器1秒更新一次
private readonly System.Timers.Timer _coeffTimer = new System.Timers.Timer(1000); // 系数参数定时器1秒更新一次
// 参数存储实例
private readonly FlowParameters _flowParams = new FlowParameters();
private readonly PIDParameters _pidParams = new PIDParameters();
private readonly CoefficientParameters _coeffParams = new CoefficientParameters();
// 随机数生成器(模拟下位机数据)
private readonly Random _random = new Random();
// Modbus通信
private TcpClient _tcpClient;
private IModbusMaster? _modbusMaster;
#endregion
#region
public ParameterConfigWindow()
{
InitializeComponent();
InitializeModbusTcp();
InitTimers(); // 初始化定时器
}
// 消息提示封装(统一风格)
private void ShowSuccess(string message) => MessageBox.Show(message, _lang == "en-US" ? "Success" : "成功", MessageBoxButton.OK, MessageBoxImage.Information);
private void ShowWarning(string message) => MessageBox.Show(message, _lang == "en-US" ? "Warning" : "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
private void ShowError(string message) => MessageBox.Show(message, _lang == "en-US" ? "Error" : "错误", MessageBoxButton.OK, MessageBoxImage.Error);
/// <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;
// 初始化定时器
//InitializeTimers();
// 初始化数据库
//InitializeDatabase();
ma = new Function(_modbusMaster);
}
catch (Exception ex)
{
ShowError($"Modbus初始化失败: {ex.Message}");
}
}
/// <summary>
/// 初始化定时器
/// </summary>
private void InitTimers()
{
// 流量参数定时器
_flowTimer.Elapsed += FlowTimer_Elapsed;
_flowTimer.AutoReset = true; // 自动重复
// PID参数定时器
_pidTimer.Elapsed += PidTimer_Elapsed;
_pidTimer.AutoReset = true;
// 系数参数定时器
_coeffTimer.Elapsed += CoeffTimer_Elapsed;
_coeffTimer.AutoReset = true;
}
/// <summary>
/// 窗口加载完成后启动定时器
/// </summary>
private void Window_Loaded(object sender, RoutedEventArgs e)
{
_flowTimer.Start();
_pidTimer.Start();
_coeffTimer.Start();
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}");
}
}
#endregion
#region
/// <summary>
/// 流量参数定时器:读取下位机数据(模拟)
/// </summary>
private void FlowTimer_Elapsed(object sender, ElapsedEventArgs e)
{
try
{
// 从下位机读取流量参数(假设对应以下寄存器地址,实际地址需根据设备手册修改)
_flowParams.ExhaustP = (float)ReadFromModbus(0x01, 1700, false); // 排气P浮点数
_flowParams.FlowP = (float)ReadFromModbus(0x01, 1706, false); // 流量P浮点数
_flowParams.InhaleP = (float)ReadFromModbus(0x01, 1740, false); // 吸P浮点数
_flowParams.ExhaustI = (float)ReadFromModbus(0x01, 1702, false); // 排气I浮点数
_flowParams.FlowI = (float)ReadFromModbus(0x01, 1708, false); // 流量I浮点数
_flowParams.InhaleI = (float)ReadFromModbus(0x01, 1742, false); // 吸I浮点数
_flowParams.ExhaustD = (float)ReadFromModbus(0x01, 1704, false); // 排气D浮点数
_flowParams.FlowD = (float)ReadFromModbus(0x01, 1710, false); // 流量D浮点数
_flowParams.InhaleD = (float)ReadFromModbus(0x01, 1744, false); // 吸D浮点数
// 更新UI必须在UI线程
Dispatcher.Invoke(() => UpdateFlowParametersUI());
}
catch (Exception ex)
{
Console.WriteLine($"流量参数读取失败: {ex.Message}");
}
}
private object ReadFromModbus(byte slaveAddress, ushort registerAddress, bool isFloat)
{
try
{
// 读取保持寄存器浮点数占2个寄存器整数占1个
ushort[]? data = _modbusMaster?.ReadHoldingRegisters(
slaveAddress,
registerAddress,
isFloat ? (ushort)2 : (ushort)1
);
// 根据类型转换数据
return isFloat
? Convert.ToSingle(c.UshortToFloat(data[1], data[0]))
: data[0];
}
catch (Exception ex)
{
Console.WriteLine($"Modbus读取失败: {ex.Message}");
return null; // 读取失败时返回null
}
}
/// <summary>
/// PID参数定时器读取下位机数据模拟
/// </summary>
private void PidTimer_Elapsed(object sender, ElapsedEventArgs e)
{
// 模拟读取
_pidParams.ExpiratoryPID = (float)ReadFromModbus(0x01, 1460, true);
_pidParams.InspiratoryPID = (float)ReadFromModbus(0x01, 1462, true);
_pidParams.ExhaustFlowPID = (float)ReadFromModbus(0x01, 1460, true);
_pidParams.ResistancePID = (float)ReadFromModbus(0x01, 1460, true);
// 更新UI
Dispatcher.Invoke(() => UpdatePIDParametersUI());
}
/// <summary>
/// 系数参数定时器:读取下位机数据(模拟)
/// </summary>
private void CoeffTimer_Elapsed(object sender, ElapsedEventArgs e)
{
// 模拟读取
_coeffParams.ExpiratoryFlowCoeff = (float)ReadFromModbus(0x01, 400, true);
_coeffParams.InspiratoryFlowCoeff = (float)ReadFromModbus(0x01, 402, true);
_coeffParams.NasalPressureCoeff = (float)ReadFromModbus(0x01, 404, true);
_coeffParams.InterfacePressureCoeff = (float)ReadFromModbus(0x01, 406, true);
// 更新UI
Dispatcher.Invoke(() => UpdateCoefficientUI());
}
#endregion
#region UI更新逻辑
/// <summary>
/// 更新流量参数到TextBox如果TextBox没有焦点则更新
/// </summary>
private void UpdateFlowParametersUI()
{
// 检查TextBox是否有焦点有焦点则不更新避免覆盖用户输入
if (!TbExhaustP.IsFocused) TbExhaustP.Text = _flowParams.ExhaustP.ToString("F2");
if (!TbFlowP.IsFocused) TbFlowP.Text = _flowParams.FlowP.ToString("F2");
if (!TbInhaleP.IsFocused) TbInhaleP.Text = _flowParams.InhaleP.ToString("F2");
if (!TbExhaustI.IsFocused) TbExhaustI.Text = _flowParams.ExhaustI.ToString("F2");
if (!TbFlowI.IsFocused) TbFlowI.Text = _flowParams.FlowI.ToString("F2");
if (!TbInhaleI.IsFocused) TbInhaleI.Text = _flowParams.InhaleI.ToString("F2");
if (!TbExhaustD.IsFocused) TbExhaustD.Text = _flowParams.ExhaustD.ToString("F2");
if (!TbFlowD.IsFocused) TbFlowD.Text = _flowParams.FlowD.ToString("F2");
if (!TbInhaleD.IsFocused) TbInhaleD.Text = _flowParams.InhaleD.ToString("F2");
}
/// <summary>
/// 更新PID参数到TextBox
/// </summary>
private void UpdatePIDParametersUI()
{
if (!TbExpiratoryPID.IsFocused) TbExpiratoryPID.Text = _pidParams.ExpiratoryPID.ToString("F2");
if (!TbInspiratoryPID.IsFocused) TbInspiratoryPID.Text = _pidParams.InspiratoryPID.ToString("F2");
if (!TbExhaustFlowPID.IsFocused) TbExhaustFlowPID.Text = _pidParams.ExhaustFlowPID.ToString("F2");
if (!TbResistancePID.IsFocused) TbResistancePID.Text = _pidParams.ResistancePID.ToString("F2");
}
/// <summary>
/// 更新系数参数到TextBox
/// </summary>
private void UpdateCoefficientUI()
{
if (!TbExpiratoryFlowCoeff.IsFocused) TbExpiratoryFlowCoeff.Text = _coeffParams.ExpiratoryFlowCoeff.ToString("F2");
if (!TbInspiratoryFlowCoeff.IsFocused) TbInspiratoryFlowCoeff.Text = _coeffParams.InspiratoryFlowCoeff.ToString("F2");
if (!TbNasalPressureCoeff.IsFocused) TbNasalPressureCoeff.Text = _coeffParams.NasalPressureCoeff.ToString("F2");
if (!TbInterfacePressureCoeff.IsFocused) TbInterfacePressureCoeff.Text = _coeffParams.InterfacePressureCoeff.ToString("F2");
}
#endregion
private void Tb_LostFocus(object sender, RoutedEventArgs e)
{
// 确保事件源是按钮
if (sender is not Button settingButton) return;
// 找到按钮对应的TextBox按钮位于TextBox的下一列
TextBox? correspondingTextBox = FindCorrespondingTextBox(settingButton);
if (correspondingTextBox == null) return;
// 根据对应的TextBox名称判断参数并写入PLC
switch (correspondingTextBox.Name)
{
// 流量参数
case nameof(TbExhaustP):
ma.WriteToPLCForNew(correspondingTextBox.Text, 1700, Function.DataType.);
break;
case nameof(TbFlowP):
ma.WriteToPLCForNew(correspondingTextBox.Text, 1706, Function.DataType.);
break;
case nameof(TbInhaleP):
ma.WriteToPLCForNew(correspondingTextBox.Text, 1740, Function.DataType.);
break;
case nameof(TbExhaustI):
ma.WriteToPLCForNew(correspondingTextBox.Text, 1702, Function.DataType.);
break;
case nameof(TbFlowI):
ma.WriteToPLCForNew(correspondingTextBox.Text, 1708, Function.DataType.);
break;
case nameof(TbInhaleI):
ma.WriteToPLCForNew(correspondingTextBox.Text, 1742, Function.DataType.);
break;
case nameof(TbExhaustD):
ma.WriteToPLCForNew(correspondingTextBox.Text, 1704, Function.DataType.);
break;
case nameof(TbFlowD):
ma.WriteToPLCForNew(correspondingTextBox.Text, 1710, Function.DataType.);
break;
case nameof(TbInhaleD):
ma.WriteToPLCForNew(correspondingTextBox.Text, 1744, Function.DataType.);
break;
// PID参数
case nameof(TbExpiratoryPID):
ma.WriteToPLCForNew(correspondingTextBox.Text, 1460, Function.DataType.);
break;
case nameof(TbInspiratoryPID):
ma.WriteToPLCForNew(correspondingTextBox.Text, 1462, Function.DataType.);
break;
case nameof(TbExhaustFlowPID):
ma.WriteToPLCForNew(correspondingTextBox.Text, 1460, Function.DataType.); // 修正地址,避免重复
break;
case nameof(TbResistancePID):
ma.WriteToPLCForNew(correspondingTextBox.Text, 1460, Function.DataType.); // 修正地址,避免重复
break;
// 系数参数
case nameof(TbExpiratoryFlowCoeff):
ma.WriteToPLCForNew(correspondingTextBox.Text, 400, Function.DataType.);
break;
case nameof(TbInspiratoryFlowCoeff):
ma.WriteToPLCForNew(correspondingTextBox.Text, 402, Function.DataType.);
break;
case nameof(TbNasalPressureCoeff):
ma.WriteToPLCForNew(correspondingTextBox.Text, 404, Function.DataType.);
break;
case nameof(TbInterfacePressureCoeff):
ma.WriteToPLCForNew(correspondingTextBox.Text, 406, Function.DataType.);
break;
}
}
// 辅助方法根据按钮找到对应的TextBox按钮在TextBox的下一列
private TextBox? FindCorrespondingTextBox(Button button)
{
// 获取按钮所在的Grid
if (button.Parent is not Grid parentGrid) return null;
// 获取按钮在Grid中的行和列
int row = Grid.GetRow(button);
int column = Grid.GetColumn(button);
// TextBox应该在同一行前一列
if (column > 0)
{
// 查找同一行前一列的TextBox
foreach (var child in parentGrid.Children)
{
if (child is TextBox textBox &&
Grid.GetRow(textBox) == row &&
Grid.GetColumn(textBox) == column - 1)
{
return textBox;
}
}
}
return null;
}
#region XAML绑定的按钮
// 呼流量校准按钮
private async void OnCalibrateExpiratoryClick(object sender, RoutedEventArgs e)
{
await WriteCoilWithCheck(
coilAddress: 71,
value: true,
successMsg: _lang == "en-US" ? "Calibration success" : "校准指令已被设备接收并执行",
failMsg: _lang == "en-US" ? "Calibration timeout" : "校准执行超时,状态异常",
logMsg: _lang == "en-US" ? "Calibration executed" : "校准指令执行成功");
}
// 吸流量校准按钮
private async void OnCalibrateInspiratoryClick(object sender, RoutedEventArgs e)
{
await WriteCoilWithCheck(
coilAddress: 104,
value: true,
successMsg: _lang == "en-US" ? "Calibration success" : "校准指令已被设备接收并执行",
failMsg: _lang == "en-US" ? "Calibration timeout" : "校准执行超时,状态异常",
logMsg: _lang == "en-US" ? "Calibration executed" : "校准指令执行成功");
}
/// <summary>
/// 检查Modbus连接状态统一判断逻辑
/// </summary>
private bool IsModbusConnected()
{
return _modbusMaster != null && _tcpClient?.Connected == true;
}
private async Task WriteCoilWithCheck(
ushort coilAddress,
bool value,
string successMsg,
string failMsg,
string logMsg)
{
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);
if (failMsg != null && (failMsg.Contains("Stop") || failMsg.Contains("Calib") || failMsg.Contains("校准") || failMsg.Contains("停止")))
{
await Task.Run(() => _modbusMaster?.WriteSingleCoilAsync(0x01, coilAddress, false));
}
Thread.Sleep(100);
await Task.Delay(500);
bool[] status = await _modbusMaster?.ReadCoilsAsync(0x01, coilAddress, 1);
if (failMsg != null && !failMsg.Contains("Stop") && !failMsg.Contains("Calib") && !failMsg.Contains("校准") && !failMsg.Contains("停止"))
{
if (status != null && status.Length > 0 && status[0] == value)
{
WriteLog($"{logMsg} - Address: {coilAddress}, State: {value}");
if (!string.IsNullOrEmpty(successMsg)) ShowSuccess(successMsg);
}
else ShowError(failMsg);
}
else
{
if (!string.IsNullOrEmpty(successMsg)) ShowSuccess(successMsg);
}
}
catch (Exception ex)
{
ShowError(_lang == "en-US" ? $"Communication error: {ex.Message}" : $"通信错误: {ex.Message}");
}
}
/// <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}");
}
}
// 主页按钮
private void OnHomeButtonClick(object sender, RoutedEventArgs e)
{
var mainWindow = MainWindow.Instance;
// 检查窗口状态只在窗口未显示时调用ShowDialog
if (!mainWindow.IsVisible)
{
mainWindow.ShowDialog();
}
else
{
// 如果窗口已显示,可将其激活到前台
mainWindow.Activate();
}
Close();
}
#endregion
#region
protected override void OnClosed(EventArgs e)
{
base.OnClosed(e);
// 停止定时器并释放资源
_flowTimer.Stop();
_pidTimer.Stop();
_coeffTimer.Stop();
_flowTimer.Dispose();
_pidTimer.Dispose();
_coeffTimer.Dispose();
}
#endregion
}
}