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
{
///
/// 参数配置界面逻辑
///
public partial class ParameterConfigWindow : Window
{
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, "成功", 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);
///
/// 初始化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;
// 初始化定时器
//InitializeTimers();
// 初始化数据库
//InitializeDatabase();
ma = new Function(_modbusMaster);
}
catch (Exception ex)
{
ShowError($"Modbus初始化失败: {ex.Message}");
}
}
///
/// 初始化定时器
///
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;
}
///
/// 窗口加载完成后启动定时器
///
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 定时器读取逻辑(模拟下位机读取)
///
/// 流量参数定时器:读取下位机数据(模拟)
///
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
}
}
///
/// PID参数定时器:读取下位机数据(模拟)
///
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());
}
///
/// 系数参数定时器:读取下位机数据(模拟)
///
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更新逻辑(避免覆盖用户编辑)
///
/// 更新流量参数到TextBox(如果TextBox没有焦点则更新)
///
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");
}
///
/// 更新PID参数到TextBox
///
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");
}
///
/// 更新系数参数到TextBox
///
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: "校准指令已被设备接收并执行",
failMsg: "校准执行超时,状态异常",
logMsg: "校准指令执行成功");
}
// 吸流量校准按钮
private async void OnCalibrateInspiratoryClick(object sender, RoutedEventArgs e)
{
await WriteCoilWithCheck(
coilAddress: 104,
value: true,
successMsg: "校准指令已被设备接收并执行",
failMsg: "校准执行超时,状态异常",
logMsg: "校准指令执行成功");
}
///
/// 检查Modbus连接状态(统一判断逻辑)
///
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("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(100);
// 等待并验证
await Task.Delay(500);
bool[] status = await _modbusMaster?.ReadCoilsAsync(0x01, coilAddress, 1);
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}");
}
}
///
/// 日志路径
///
private string logPath => System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "modbus_log.txt");
///
/// 写入操作日志
///
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
}
}