1280 lines
46 KiB
C#
1280 lines
46 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.Threading;
|
||
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.Imaging;
|
||
using 口罩泄露定制款;
|
||
using static ShanghaiEnvironmentalTechnology.Window5;
|
||
|
||
namespace ShanghaiEnvironmentalTechnology
|
||
{
|
||
/// <summary>
|
||
/// 防窒息压力测试窗口(Modbus通信+压力数据处理)
|
||
/// </summary>
|
||
public partial class Window3 : Window, IDisposable
|
||
{
|
||
DataChange c = new DataChange();
|
||
|
||
#region 寄存器/线圈地址定义(按功能分组)
|
||
// 压力设置相关
|
||
private readonly ushort _pressureSettingRegisterAddress = 350; // 开阀压差判别(D350)
|
||
private readonly ushort _pressureCloseSettingRegisterAddress = 352; // 关阀压差判别(D352)
|
||
private readonly ushort _openRegisterAddress = 202; // 开阀压力记录地址
|
||
private readonly ushort _closeRegisterAddress = 206; // 关阀压力记录地址
|
||
private readonly ushort _pressureRegisterAddress = 1372; // 实时压力读取地址
|
||
|
||
// 测试控制线圈
|
||
private readonly ushort _testStartAddress = 0x0014; // 防窒息压力测试启动(M20)
|
||
private readonly ushort _testStopAddress = 0x001E; // 防窒息压力测试停止(M30)
|
||
private readonly ushort _calibrationCoilAddress = 0x0048; // 校准线圈(M72)
|
||
#endregion
|
||
|
||
#region 私有字段
|
||
private TcpClient _tcpClient;
|
||
private IModbusMaster _modbusMaster;
|
||
private System.Timers.Timer _pressureReadTimer; // 实时压力读取定时器
|
||
private System.Timers.Timer _settingReadTimer; // 开阀压力设置定时器
|
||
private System.Timers.Timer _settingReadTimer2; // 关阀压力设置定时器
|
||
|
||
private System.Timers.Timer startTimer; // 启动状态实时定时器
|
||
|
||
|
||
Function fc;
|
||
#endregion
|
||
|
||
public Window3()
|
||
{
|
||
InitializeComponent();
|
||
InitializeModbusTcp();
|
||
Loaded += Window_Loaded;
|
||
}
|
||
|
||
#region 初始化与资源释放
|
||
/// <summary>
|
||
/// 初始化Modbus连接和定时器
|
||
/// </summary>
|
||
private void InitializeModbusTcp()
|
||
{
|
||
try
|
||
{
|
||
string plcIp = ConfigurationManager.AppSettings["PLC1_IP"];
|
||
int plcPort = int.Parse(ConfigurationManager.AppSettings["PLC1_Port"]);
|
||
|
||
_tcpClient = new TcpClient(plcIp, plcPort);
|
||
_modbusMaster = ModbusIpMaster.CreateIp(_tcpClient);
|
||
_modbusMaster.Transport.ReadTimeout = 3000;
|
||
_modbusMaster.Transport.WriteTimeout = 3000;
|
||
|
||
// 初始化定时器
|
||
_pressureReadTimer = CreateTimer(1000, OnPressureTimerElapsed);
|
||
_settingReadTimer = CreateTimer(1000, OnSettingReadTimerTimerElapsed);
|
||
_settingReadTimer2 = CreateTimer(1000, OnSettingReadTimerTimerElapsed2);
|
||
startTimer = CreateTimer(1000, OnStartTimerElapsed);
|
||
|
||
|
||
fc = new Function(_modbusMaster);
|
||
// 初始化数据库
|
||
InitializeDatabase();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
ShowError($"Modbus初始化失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <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>
|
||
/// 实时流量读取定时器(修正跨线程访问UI问题)
|
||
/// </summary>
|
||
private void OnStartTimerElapsed(object sender, ElapsedEventArgs e)
|
||
{
|
||
try
|
||
{
|
||
bool[] result = _modbusMaster?.ReadCoils(0x01, 21, 1);
|
||
bool isTestRunning = result != null && result.Length > 0 && result[0];
|
||
|
||
|
||
string testStartButtonText = "";
|
||
string ButtonStatus = "";
|
||
string currentLanguage = ConfigurationManager.AppSettings["Language"] ?? "zh-CN";
|
||
if (currentLanguage == "en-US")
|
||
{
|
||
testStartButtonText = "Test Start Success";
|
||
ButtonStatus = "Test initiation";
|
||
}
|
||
else if (currentLanguage == "zh-CN")
|
||
{
|
||
testStartButtonText = "测试启动成功";
|
||
ButtonStatus = "测试启动";
|
||
}
|
||
|
||
|
||
TestStartButton.Dispatcher.Invoke(() =>
|
||
{
|
||
if (isTestRunning)
|
||
{
|
||
TestStartButton.Content = testStartButtonText;
|
||
TestStartButton.Foreground = Brushes.LightGreen;
|
||
}
|
||
else
|
||
{
|
||
TestStartButton.Content = ButtonStatus;
|
||
TestStartButton.Foreground = Brushes.White;
|
||
}
|
||
});
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"读取线圈或更新UI失败:{ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 释放资源(定时器、TCP连接)
|
||
/// </summary>
|
||
public void Dispose()
|
||
{
|
||
_pressureReadTimer?.Dispose();
|
||
_settingReadTimer?.Dispose();
|
||
_settingReadTimer2?.Dispose();
|
||
_tcpClient?.Close();
|
||
_tcpClient?.Dispose();
|
||
_modbusMaster = null;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 窗口关闭时释放资源
|
||
/// </summary>
|
||
protected override void OnClosed(EventArgs e)
|
||
{
|
||
base.OnClosed(e);
|
||
Dispose();
|
||
}
|
||
#endregion
|
||
|
||
#region 定时器读取逻辑
|
||
/// <summary>
|
||
/// 实时压力读取定时器
|
||
/// </summary>
|
||
private void OnPressureTimerElapsed(object sender, ElapsedEventArgs e)
|
||
{
|
||
ReadAndUpdateRegister(
|
||
_pressureRegisterAddress,
|
||
true,
|
||
value => UpdateRealTimerPressureUI(value.ToString())
|
||
);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 开阀压力设置定时器(带编辑锁判断)
|
||
/// </summary>
|
||
private void OnSettingReadTimerTimerElapsed(object sender, ElapsedEventArgs e)
|
||
{
|
||
ReadAndUpdateRegister(
|
||
_pressureSettingRegisterAddress, false,
|
||
value => UpdateSettingUI(value.ToString())
|
||
);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 关阀压力设置定时器(带编辑锁判断)
|
||
/// </summary>
|
||
private void OnSettingReadTimerTimerElapsed2(object sender, ElapsedEventArgs e)
|
||
{
|
||
ReadAndUpdateRegister(
|
||
_pressureCloseSettingRegisterAddress, false,
|
||
value => UpdateSettingUI2(value.ToString())
|
||
);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 通用寄存器读取并更新UI,支持16位整数和32位浮点数
|
||
/// </summary>
|
||
private void ReadAndUpdateRegister(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)
|
||
{
|
||
if (data.Length >= 2)
|
||
{
|
||
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
|
||
{
|
||
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);
|
||
}
|
||
}
|
||
#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
|
||
);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新实时压力UI
|
||
/// </summary>
|
||
private void UpdateRealTimerPressureUI(string value)
|
||
{
|
||
UpdateUiSafely(() =>
|
||
UpdateConnectionTextUI(TimerPressTxt, value)
|
||
);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新开阀压力设置UI
|
||
/// </summary>
|
||
private void UpdateSettingUI(string value)
|
||
{
|
||
UpdateUiSafely(() =>
|
||
UpdateConnectionTextUI(SettingPaTextBox, value)
|
||
);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新关阀压力设置UI
|
||
/// </summary>
|
||
private void UpdateSettingUI2(string value)
|
||
{
|
||
UpdateUiSafely(() =>
|
||
UpdateConnectionTextUI(clostTxt, value)
|
||
);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新开阀压力设置UI(旧方法兼容)
|
||
/// </summary>
|
||
private void UpdatePressureUI(string value)
|
||
{
|
||
UpdateUiSafely(() => SettingPaTextBox.Text = value);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新关阀压力设置UI(旧方法兼容)
|
||
/// </summary>
|
||
private void UpdateClosePressureUI(string value)
|
||
{
|
||
UpdateUiSafely(() => clostTxt.Text = value);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新测试按钮状态UI
|
||
/// </summary>
|
||
private void UpdateButtonStatus(string text, Brush color)
|
||
{
|
||
UpdateUiSafely(() =>
|
||
{
|
||
TestStartButton.Content = text;
|
||
TestStartButton.Foreground = color;
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 线程安全的UI更新通用方法
|
||
/// </summary>
|
||
private void UpdateUiSafely(Action action)
|
||
{
|
||
if (action == null) return;
|
||
|
||
if (this.Dispatcher.HasShutdownStarted || this.Dispatcher.HasShutdownFinished)
|
||
return;
|
||
|
||
if (!Dispatcher.CheckAccess())
|
||
{
|
||
try
|
||
{
|
||
Dispatcher.BeginInvoke(action);
|
||
}
|
||
catch (TaskCanceledException)
|
||
{
|
||
|
||
}
|
||
}
|
||
else
|
||
{
|
||
action.Invoke();
|
||
}
|
||
}
|
||
#endregion
|
||
|
||
#region 按钮事件(功能操作)
|
||
/// <summary>
|
||
/// 开阀压差判别设置(SettingPaTextBox)
|
||
/// </summary>
|
||
private async void Button_Click(object sender, RoutedEventArgs e)
|
||
{
|
||
|
||
// 获取当前语言(默认中文)
|
||
string currentLanguage = ConfigurationManager.AppSettings["Language"] ?? "zh-CN";
|
||
|
||
// 多语言提示信息
|
||
string disconnectMsg = currentLanguage == "en-US" ? "Set valve opening pressure differential discrimination:" : "设置开阀压差判别:";
|
||
|
||
|
||
|
||
|
||
await WriteRegisterWithValidation(
|
||
inputControl: SettingPaTextBox,
|
||
registerAddress: _pressureSettingRegisterAddress,
|
||
minValue: 0,
|
||
maxValue: 10000,
|
||
successMsg: value => $"{disconnectMsg}: {value} Pa",
|
||
refreshAction: () => RefreshCurrentPressure()
|
||
);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 关阀压差判别设置(clostTxt)
|
||
/// </summary>
|
||
private async void Button_Click_1(object sender, RoutedEventArgs e)
|
||
{
|
||
// 获取当前语言(默认中文)
|
||
string currentLanguage = ConfigurationManager.AppSettings["Language"] ?? "zh-CN";
|
||
|
||
// 多语言提示信息
|
||
string disconnectMsg = currentLanguage == "en-US" ? "Set valve closing pressure differential discrimination:" : "设置关阀压差判别:";
|
||
|
||
|
||
|
||
|
||
await WriteRegisterWithValidation(
|
||
inputControl: clostTxt,
|
||
registerAddress: _pressureCloseSettingRegisterAddress,
|
||
minValue: 0,
|
||
maxValue: 10000,
|
||
successMsg: value => $"{disconnectMsg}: {value} Pa",
|
||
refreshAction: () => RefreshClosePressure()
|
||
);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 读取开阀/关阀压力记录并保存
|
||
/// </summary>
|
||
private async void Button_Click_2(object sender, RoutedEventArgs e)
|
||
{
|
||
await ReadRegisterAndSave(
|
||
address: _openRegisterAddress,
|
||
address2: _closeRegisterAddress,
|
||
control: recordOpenTxt
|
||
);
|
||
}
|
||
|
||
private async void Button_Click_4(object sender, RoutedEventArgs e)
|
||
{
|
||
string currentLanguage = ConfigurationManager.AppSettings["Language"] ?? "zh-CN";
|
||
|
||
string success = currentLanguage == "en-US"
|
||
? "Anti-suffocation pressure calibration received"
|
||
: "防窒息压力校准指令已被设备接收并执行";
|
||
|
||
string fail = currentLanguage == "en-US"
|
||
? "Calibration timeout, abnormal status"
|
||
: "校准执行超时,状态异常";
|
||
|
||
string log = currentLanguage == "en-US"
|
||
? "Anti-suffocation pressure calibration success"
|
||
: "防窒息压力校准指令执行成功";
|
||
|
||
await WriteCoilWithCheck(
|
||
coilAddress: _calibrationCoilAddress,
|
||
value: true,
|
||
successMsg: success,
|
||
failMsg: fail,
|
||
logMsg: log
|
||
);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 返回主窗口
|
||
/// </summary>
|
||
private void Button_Click_5(object sender, RoutedEventArgs e)
|
||
{
|
||
var mainWindow = MainWindow.Instance;
|
||
|
||
// 检查窗口状态,只在窗口未显示时调用ShowDialog
|
||
if (!mainWindow.IsVisible)
|
||
{
|
||
mainWindow.ShowDialog();
|
||
}
|
||
else
|
||
{
|
||
// 如果窗口已显示,可将其激活到前台
|
||
mainWindow.Activate();
|
||
}
|
||
|
||
Close();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 打开报告窗口
|
||
/// </summary>
|
||
private void Button_Click_6(object sender, RoutedEventArgs e)
|
||
{
|
||
new ReportWindow3().Show();
|
||
}
|
||
|
||
private async void Button_Click_7(object sender, RoutedEventArgs e)
|
||
{
|
||
// 获取当前语言(和你原有逻辑保持一致)
|
||
string currentLanguage = ConfigurationManager.AppSettings["Language"] ?? "zh-CN";
|
||
|
||
if (!IsModbusConnected())
|
||
{
|
||
UpdateButtonStatus(currentLanguage == "en-US" ? "Disconnected" : "连接断开", Brushes.Red);
|
||
ShowError(currentLanguage == "en-US" ? "Modbus TCP not connected" : "Modbus TCP 未连接");
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
UpdateButtonStatus(currentLanguage == "en-US" ? "Starting..." : "正在启动...", Brushes.LightGreen);
|
||
|
||
// 写入启动线圈(脉冲式触发)
|
||
await Task.Run(() => _modbusMaster.WriteSingleCoilAsync(0x01, _testStartAddress, true));
|
||
await Task.Delay(100);
|
||
await Task.Run(() => _modbusMaster.WriteSingleCoilAsync(0x01, _testStartAddress, false));
|
||
|
||
UpdateButtonStatus(currentLanguage == "en-US" ? "Test started successfully" : "测试启动成功", Brushes.LightGreen);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
UpdateButtonStatus(currentLanguage == "en-US" ? "Test start failed" : "测试启动失败", Brushes.Red);
|
||
ShowError(currentLanguage == "en-US" ? $"Operation error: {ex.Message}" : $"操作异常: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 防窒息压力测试停止(M30线圈)
|
||
/// </summary>
|
||
private async void Button_Click_8(object sender, RoutedEventArgs e)
|
||
{
|
||
string currentLanguage = ConfigurationManager.AppSettings["Language"] ?? "zh-CN";
|
||
|
||
string failMsg = currentLanguage == "en-US"
|
||
? "Test stop timeout, abnormal status"
|
||
: "防窒息压力测试停止超时,状态异常";
|
||
|
||
string logMsg = currentLanguage == "en-US"
|
||
? "Anti-suffocation test stopped"
|
||
: "防窒息压力测试停止";
|
||
|
||
await WriteCoilWithCheck(
|
||
coilAddress: _testStopAddress,
|
||
value: true,
|
||
successMsg: null,
|
||
failMsg: failMsg,
|
||
logMsg: logMsg
|
||
);
|
||
|
||
TestStartButton.Content = currentLanguage == "en-US" ? "Test initiation" : "测试启动";
|
||
TestStartButton.Foreground = Brushes.White;
|
||
}
|
||
#endregion
|
||
|
||
#region 通用操作方法(提取重复逻辑)
|
||
/// <summary>
|
||
/// 带输入验证的寄存器写入(带成功消息)
|
||
/// </summary>
|
||
private async Task WriteRegisterWithValidation(
|
||
TextBox inputControl,
|
||
ushort registerAddress,
|
||
int minValue,
|
||
int maxValue,
|
||
Func<int, string> successMsg,
|
||
Action refreshAction)
|
||
{
|
||
string currentLanguage = ConfigurationManager.AppSettings["Language"] ?? "zh-CN";
|
||
string noConn = currentLanguage == "en-US" ? "Modbus TCP not connected" : "Modbus TCP 未连接";
|
||
string invalid = currentLanguage == "en-US"
|
||
? $"Please enter integer between {minValue}~{maxValue}"
|
||
: $"请输入{minValue}~{maxValue}的整数";
|
||
string fail = currentLanguage == "en-US" ? "Operation failed" : "操作失败";
|
||
|
||
if (!IsModbusConnected())
|
||
{
|
||
ShowError(noConn);
|
||
return;
|
||
}
|
||
|
||
if (!int.TryParse(inputControl.Text.Trim(), out int value) || value < minValue || value > maxValue)
|
||
{
|
||
ShowWarning(invalid);
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
string operating = currentLanguage == "en-US" ? "Operating..." : "操作中...";
|
||
inputControl.Text = operating;
|
||
|
||
await Task.Run(() =>
|
||
_modbusMaster.WriteSingleRegister(0x01, registerAddress, (ushort)value)
|
||
);
|
||
|
||
await Task.Delay(300);
|
||
refreshAction?.Invoke();
|
||
ShowSuccess(successMsg(value));
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
ShowError($"{fail}: {ex.Message}");
|
||
}
|
||
finally
|
||
{
|
||
inputControl.Text = value.ToString();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 带输入验证的寄存器写入(开阀压力专用)
|
||
/// </summary>
|
||
private async Task WriteRegisterWithValidation(
|
||
TextBox inputControl,
|
||
ushort registerAddress,
|
||
float minValue,
|
||
float maxValue)
|
||
{
|
||
string currentLanguage = ConfigurationManager.AppSettings["Language"] ?? "zh-CN";
|
||
string noConn = currentLanguage == "en-US" ? "Modbus TCP not connected" : "Modbus TCP 未连接";
|
||
string invalid = currentLanguage == "en-US"
|
||
? $"Please enter number between {minValue}~{maxValue}"
|
||
: $"请输入{minValue}~{maxValue}的数字";
|
||
string fail = currentLanguage == "en-US" ? "Operation failed" : "操作失败";
|
||
string operating = currentLanguage == "en-US" ? "Operating..." : "操作中...";
|
||
|
||
if (!IsModbusConnected())
|
||
{
|
||
ShowError(noConn);
|
||
return;
|
||
}
|
||
|
||
if (!float.TryParse(inputControl.Text.Trim(), out float value) || value < minValue || value > maxValue)
|
||
{
|
||
ShowWarning(invalid);
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
inputControl.Text = operating;
|
||
Function ma = new Function(_modbusMaster);
|
||
ma.WriteToPLCForNew(value.ToString(), registerAddress, Function.DataType.整形);
|
||
|
||
await Task.Delay(300);
|
||
ReadAndUpdateRegister(registerAddress, true, v => UpdatePressureUI(v.ToString()));
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
ShowError($"{fail}: {ex.Message}");
|
||
}
|
||
finally
|
||
{
|
||
inputControl.Text = value.ToString();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 带输入验证的寄存器写入(关阀压力专用)
|
||
/// </summary>
|
||
private async Task WriteRegisterWithValidation2(
|
||
TextBox inputControl,
|
||
ushort registerAddress,
|
||
float minValue,
|
||
float maxValue)
|
||
{
|
||
string currentLanguage = ConfigurationManager.AppSettings["Language"] ?? "zh-CN";
|
||
string noConn = currentLanguage == "en-US" ? "Modbus TCP not connected" : "Modbus TCP 未连接";
|
||
string invalid = currentLanguage == "en-US"
|
||
? $"Please enter number between {minValue}~{maxValue}"
|
||
: $"请输入{minValue}~{maxValue}的数字";
|
||
string fail = currentLanguage == "en-US" ? "Operation failed" : "操作失败";
|
||
string operating = currentLanguage == "en-US" ? "Operating..." : "操作中...";
|
||
|
||
if (!IsModbusConnected())
|
||
{
|
||
ShowError(noConn);
|
||
return;
|
||
}
|
||
|
||
if (!float.TryParse(inputControl.Text.Trim(), out float value) || value < minValue || value > maxValue)
|
||
{
|
||
ShowWarning(invalid);
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
inputControl.Text = operating;
|
||
Function ma = new Function(_modbusMaster);
|
||
ma.WriteToPLCForNew(value.ToString(), registerAddress, Function.DataType.整形);
|
||
|
||
await Task.Delay(300);
|
||
ReadAndUpdateRegister(registerAddress, true, v => UpdateClosePressureUI(v.ToString()));
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
ShowError($"{fail}: {ex.Message}");
|
||
}
|
||
finally
|
||
{
|
||
inputControl.Text = value.ToString();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 写入线圈并验证状态
|
||
/// </summary>
|
||
private async Task WriteCoilWithCheck(
|
||
ushort coilAddress,
|
||
bool value,
|
||
string successMsg,
|
||
string failMsg,
|
||
string logMsg)
|
||
{
|
||
string currentLanguage = ConfigurationManager.AppSettings["Language"] ?? "zh-CN";
|
||
string commError = currentLanguage == "en-US" ? "Communication error" : "通信错误";
|
||
|
||
if (!IsModbusConnected())
|
||
{
|
||
InitializeModbusTcp();
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
await Task.Run(() =>
|
||
_modbusMaster.WriteSingleCoilAsync(0x01, coilAddress, value)
|
||
);
|
||
Thread.Sleep(200);
|
||
|
||
if (failMsg != null)
|
||
{
|
||
bool isStop = failMsg.Contains("停止") || failMsg.Equals("Stop", StringComparison.OrdinalIgnoreCase);
|
||
bool isCalib = failMsg.Contains("校准") || failMsg.Equals("Calibrate", StringComparison.OrdinalIgnoreCase);
|
||
|
||
if (isStop || isCalib)
|
||
{
|
||
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)
|
||
{
|
||
bool isStop = failMsg.Contains("停止") || failMsg.Equals("Stop", StringComparison.OrdinalIgnoreCase);
|
||
bool isCalib = failMsg.Contains("校准") || failMsg.Equals("Calibrate", StringComparison.OrdinalIgnoreCase);
|
||
|
||
if (!isStop && !isCalib)
|
||
{
|
||
if (status[0] == value)
|
||
{
|
||
WriteLog($"{logMsg} - Address:{coilAddress},Status:{value}");
|
||
if (!string.IsNullOrEmpty(successMsg)) ShowSuccess(successMsg);
|
||
}
|
||
else
|
||
{
|
||
ShowError(failMsg);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
ShowError($"{commError}: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
private async void CaptureModeComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||
{
|
||
if (sender == null) return;
|
||
var comboBox = sender as ComboBox;
|
||
if (comboBox == null) return;
|
||
var selectedItem = comboBox.SelectedItem as ComboBoxItem;
|
||
if (selectedItem == null) return;
|
||
var content = selectedItem.Content?.ToString();
|
||
if (string.IsNullOrEmpty(content)) return;
|
||
|
||
// 获取当前语言
|
||
string currentLanguage = ConfigurationManager.AppSettings["Language"] ?? "zh-CN";
|
||
string stopMsg = currentLanguage == "en-US" ? "Stop" : "停止";
|
||
|
||
// 双语匹配:中文 + 英文都能识别
|
||
switch (content)
|
||
{
|
||
case "开阀捕捉":
|
||
case "Open the valve to capture":
|
||
await WriteCoilWithCheck(0, true, null, stopMsg, "开关阀选择");
|
||
break;
|
||
|
||
case "关阀捕捉":
|
||
case "Closing the valve to capture":
|
||
await WriteCoilWithCheck(1, true, null, stopMsg, "开关阀选择");
|
||
break;
|
||
default:
|
||
return;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 刷新开阀压力设置
|
||
/// </summary>
|
||
private void RefreshCurrentPressure()
|
||
{
|
||
ReadAndUpdateRegister(_pressureSettingRegisterAddress, true, v => UpdatePressureUI(v.ToString()));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 刷新关阀压力设置
|
||
/// </summary>
|
||
private void RefreshClosePressure()
|
||
{
|
||
ReadAndUpdateRegister(_pressureCloseSettingRegisterAddress, true, v => UpdateClosePressureUI(v.ToString()));
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 读取寄存器并保存到数据库
|
||
/// </summary>
|
||
private async Task ReadRegisterAndSave(ushort address, ushort address2, TextBlock control)
|
||
{
|
||
if (!IsModbusConnected())
|
||
{
|
||
control.Text = "连接断开";
|
||
ShowError("Modbus TCP 未连接");
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
control.Text = "读取中...";
|
||
recordCloseTxt.Text = "读取中...";
|
||
|
||
// 读取开阀压力
|
||
ushort[] data = await Task.Run(() =>
|
||
_modbusMaster?.ReadHoldingRegisters(0x01, address, 2)
|
||
);
|
||
ushort a = data[0];
|
||
ushort b = data[1];
|
||
float value = c.UshortToFloat(b, a);
|
||
value = (float)Math.Round(value, 2);
|
||
control.Text = value.ToString("0.00");
|
||
|
||
// 读取关阀压力
|
||
ushort[] data2 = await Task.Run(() =>
|
||
_modbusMaster?.ReadHoldingRegisters(0x01, address2, 2)
|
||
);
|
||
ushort a2 = data2[0];
|
||
ushort b2 = data2[1];
|
||
float value2 = c.UshortToFloat(b2, a2);
|
||
value2 = (float)Math.Round(value2, 2);
|
||
recordCloseTxt.Text = value2.ToString("0.00");
|
||
|
||
// 保存到数据库
|
||
SaveRecordToDatabase(value, value2);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
control.Text = "读取失败";
|
||
recordCloseTxt.Text = "读取失败";
|
||
ShowError($"读取失败: {ex.Message}");
|
||
}
|
||
}
|
||
#endregion
|
||
|
||
#region 数据库与日志操作
|
||
/// <summary>
|
||
/// 初始化数据库
|
||
/// </summary>
|
||
private void InitializeDatabase()
|
||
{
|
||
try
|
||
{
|
||
using (var conn = new SQLiteConnection(CSConstant.DbConnectionString))
|
||
{
|
||
conn.Open();
|
||
string createSql = @"
|
||
CREATE TABLE IF NOT EXISTS PreventSuffocation (
|
||
Id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
Flow REAL NOT NULL,
|
||
Pressure REAL NOT NULL,
|
||
RecordTime DATETIME NOT NULL
|
||
);";
|
||
using (var cmd = new SQLiteCommand(createSql, conn))
|
||
{
|
||
cmd.ExecuteNonQuery();
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"数据库初始化失败: {ex.Message}");
|
||
ShowError($"数据库错误: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 保存记录到数据库
|
||
/// </summary>
|
||
private void SaveRecordToDatabase(double flow, double pressure)
|
||
{
|
||
try
|
||
{
|
||
flow = Math.Round(flow, 2);
|
||
pressure = Math.Round(pressure, 2);
|
||
using (var conn = new SQLiteConnection(CSConstant.DbConnectionString))
|
||
{
|
||
conn.Open();
|
||
string insertSql = @"
|
||
INSERT INTO PreventSuffocation (Flow, Pressure, RecordTime)
|
||
VALUES (@Flow, @Pressure, @RecordTime);";
|
||
using (var cmd = new SQLiteCommand(insertSql, conn))
|
||
{
|
||
cmd.Parameters.AddWithValue("@Flow", flow);
|
||
cmd.Parameters.AddWithValue("@Pressure", pressure);
|
||
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}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 写入操作日志
|
||
/// </summary>
|
||
private void WriteLog(string content)
|
||
{
|
||
try
|
||
{
|
||
string log = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] {content}\r\n";
|
||
string logPath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "modbus_log.txt");
|
||
System.IO.File.AppendAllText(logPath, log);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"写入日志失败: {ex.Message}");
|
||
}
|
||
}
|
||
#endregion
|
||
|
||
#region 输入框焦点与编辑事件(核心修复部分)
|
||
|
||
|
||
/// <summary>
|
||
/// SettingPaTextBox输入验证(仅允许数字和小数点)
|
||
/// </summary>
|
||
private void SettingPaTextBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
|
||
{
|
||
var isNumber = System.Text.RegularExpressions.Regex.IsMatch(e.Text, @"^[0-9]*(?:\.[0-9]*)?$");
|
||
e.Handled = !isNumber;
|
||
}
|
||
|
||
/// <summary>
|
||
/// clostTxt输入验证(仅允许数字和小数点)
|
||
/// </summary>
|
||
private void ClostTxt_PreviewTextInput(object sender, TextCompositionEventArgs e)
|
||
{
|
||
var isNumber = System.Text.RegularExpressions.Regex.IsMatch(e.Text, @"^[0-9]*(?:\.[0-9]*)?$");
|
||
e.Handled = !isNumber;
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
#endregion
|
||
|
||
#region 辅助方法(连接检查、消息提示、背景加载)
|
||
/// <summary>
|
||
/// 检查Modbus连接状态
|
||
/// </summary>
|
||
private bool IsModbusConnected()
|
||
{
|
||
return _modbusMaster != null && _tcpClient?.Connected == true;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 加载窗口背景
|
||
/// </summary>
|
||
private void Window_Loaded(object sender, RoutedEventArgs e)
|
||
{
|
||
try
|
||
{
|
||
string imgPath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Resources/sleep2.jpg");
|
||
if (System.IO.File.Exists(imgPath))
|
||
{
|
||
Background = new ImageBrush
|
||
{
|
||
ImageSource = new BitmapImage(new Uri(imgPath, UriKind.Absolute))
|
||
};
|
||
}
|
||
else
|
||
{
|
||
Console.WriteLine($"背景图片不存在: {imgPath}");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"加载背景失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
// 消息提示封装
|
||
private void ShowSuccess(string msg) => MessageBox.Show(msg, "成功", MessageBoxButton.OK, MessageBoxImage.Information);
|
||
private void ShowWarning(string msg) => MessageBox.Show(msg, "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||
private void ShowError(string msg) => MessageBox.Show(msg, "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||
#endregion
|
||
|
||
private async void Button_Click_3(object sender, RoutedEventArgs e)
|
||
{
|
||
string currentLanguage = ConfigurationManager.AppSettings["Language"] ?? "zh-CN";
|
||
string noConn = currentLanguage == "en-US" ? "Modbus TCP not connected" : "Modbus TCP 未连接";
|
||
string invalidData = currentLanguage == "en-US" ? "Invalid flow or pressure value" : "流量或压力值格式不正确,请检查输入";
|
||
string noData = currentLanguage == "en-US" ? "No data to export" : "防室息阀压力表中无数据,无法导出";
|
||
string exportSuccess = currentLanguage == "en-US" ? "Export success" : "数据已成功导出到Excel";
|
||
string exportFail = currentLanguage == "en-US" ? "Export failed" : "Excel导出失败,请检查文件是否被占用";
|
||
|
||
if (!IsModbusConnected())
|
||
{
|
||
recordOpenTxt.Text = currentLanguage == "en-US" ? "Disconnected" : "连接断开";
|
||
ShowError(noConn);
|
||
return;
|
||
}
|
||
|
||
fc.BtnClickFunctionForNew(Function.ButtonType.切换型, 170);
|
||
fc.BtnClickFunctionForNew(Function.ButtonType.切换型, 170);
|
||
|
||
ReadAndUpdateRegister(_openRegisterAddress, true, value => UpdateFlowFlowUI(value.ToString()));
|
||
ReadAndUpdateRegister(_closeRegisterAddress, true, value => UpdateCloseFlowUI(value.ToString()));
|
||
|
||
if (float.TryParse(recordOpenTxt.Text, out float flowValue) && float.TryParse(recordCloseTxt.Text, out float pressureValue))
|
||
{
|
||
SaveRecordToDatabase(flowValue, pressureValue);
|
||
}
|
||
else
|
||
{
|
||
ShowError(invalidData);
|
||
return;
|
||
}
|
||
|
||
List<CO2Record> co2Records = ReadCO2RecordsFromDatabase();
|
||
if (co2Records == null || !co2Records.Any())
|
||
{
|
||
MessageBox.Show(noData, currentLanguage == "en-US" ? "Info" : "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||
return;
|
||
}
|
||
|
||
bool exportSuccessFlag = ExportCO2RecordsToExcel(co2Records);
|
||
if (exportSuccessFlag)
|
||
{
|
||
MessageBox.Show(exportSuccess, currentLanguage == "en-US" ? "Success" : "成功", MessageBoxButton.OK, MessageBoxImage.Information);
|
||
}
|
||
else
|
||
{
|
||
MessageBox.Show(exportFail, currentLanguage == "en-US" ? "Error" : "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||
}
|
||
}
|
||
|
||
|
||
private bool ExportCO2RecordsToExcel(List<CO2Record> records)
|
||
{
|
||
try
|
||
{
|
||
// WPF 原生保存对话框(替代 WinForms)
|
||
|
||
SaveFileDialog saveDialog = new SaveFileDialog
|
||
{
|
||
Filter = "Excel文件 (*.xlsx)|*.xlsx",
|
||
FileName = $"防室息阀压力记录_{DateTime.Now:yyyyMMddHHmmss}.xlsx",
|
||
Title = "保存防室息阀压力记录"
|
||
};
|
||
|
||
// 用户取消选择则返回
|
||
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("防室息阀压力记录");
|
||
|
||
// 表头(对应DataGrid列)
|
||
worksheet.Cells[1, 1].Value = "开阀压力pa";
|
||
worksheet.Cells[1, 2].Value = "关阀压力pa";
|
||
worksheet.Cells[1, 3].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.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>
|
||
///
|
||
/// </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, RecordTime FROM PreventSuffocation 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)
|
||
RecordTime = reader.GetDateTime(2) // 时间
|
||
});
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return records;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
MessageBox.Show($"读取防室息阀压力表失败:{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 更新实时流量UI
|
||
/// </summary>
|
||
private void UpdateFlowFlowUI(string value)
|
||
{
|
||
UpdateUiSafely(() =>
|
||
recordOpenTxt.Text = IsModbusConnected() ? value : "连接断开"
|
||
);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新实时流量UI
|
||
/// </summary>
|
||
private void UpdateCloseFlowUI(string value)
|
||
{
|
||
UpdateUiSafely(() =>
|
||
recordCloseTxt.Text = IsModbusConnected() ? value : "连接断开"
|
||
);
|
||
}
|
||
|
||
private void Button_Click_9(object sender, RoutedEventArgs e)
|
||
{
|
||
if (!IsModbusConnected())
|
||
{
|
||
recordOpenTxt.Text = "连接断开";
|
||
ShowError("Modbus TCP 未连接");
|
||
return;
|
||
}
|
||
|
||
|
||
fc.BtnClickFunctionForNew(Function.ButtonType.切换型, 11);
|
||
fc.BtnClickFunctionForNew(Function.ButtonType.切换型, 11);
|
||
|
||
ReadAndUpdateRegister(
|
||
_openRegisterAddress, true,
|
||
value => UpdateFlowFlowUI(value.ToString())
|
||
);
|
||
|
||
ReadAndUpdateRegister(
|
||
_closeRegisterAddress, true,
|
||
value => UpdateCloseFlowUI(value.ToString())
|
||
);
|
||
|
||
// 尝试将文本框内容转换为float
|
||
if (float.TryParse(recordOpenTxt.Text, out float flowValue) &&
|
||
float.TryParse(recordCloseTxt.Text, out float pressureValue))
|
||
{
|
||
SaveRecordToDatabase(flowValue, pressureValue);
|
||
}
|
||
else
|
||
{
|
||
// 转换失败,提示错误信息
|
||
ShowError("流量或压力值格式不正确,请检查输入");
|
||
}
|
||
// 步骤2:读取CO2表数据
|
||
List<CO2Record> co2Records = ReadCO2RecordsFromDatabase();
|
||
if (co2Records == null || !co2Records.Any())
|
||
{
|
||
MessageBox.Show("防室息阀压力表中无数据,无法导出", "提示", 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);
|
||
}
|
||
|
||
|
||
|
||
}
|
||
|
||
private async void Button_Click_10(object sender, RoutedEventArgs e)
|
||
{
|
||
try
|
||
{
|
||
await WriteRegisterWithValidation(
|
||
inputControl: SettingPaTextBox,
|
||
registerAddress: _pressureSettingRegisterAddress,
|
||
minValue: 0,
|
||
maxValue: 10000
|
||
);
|
||
_settingReadTimer.Start();
|
||
}
|
||
finally
|
||
{
|
||
|
||
}
|
||
}
|
||
|
||
private async void Button_Click_11(object sender, RoutedEventArgs e)
|
||
{
|
||
try
|
||
{
|
||
await WriteRegisterWithValidation2(
|
||
inputControl: clostTxt,
|
||
registerAddress: _pressureCloseSettingRegisterAddress,
|
||
minValue: 0,
|
||
maxValue: 10000
|
||
);
|
||
_settingReadTimer2.Start();
|
||
}
|
||
finally
|
||
{
|
||
|
||
}
|
||
}
|
||
}
|
||
}
|