445 lines
16 KiB
C#
445 lines
16 KiB
C#
using Modbus.Device;
|
||
using Sunny.UI;
|
||
using Sunny.UI.Win32;
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.ComponentModel;
|
||
using System.Data;
|
||
using System.Drawing;
|
||
using System.Linq;
|
||
using System.Net.Sockets;
|
||
using System.Text;
|
||
using System.Threading.Tasks;
|
||
using System.Windows.Forms;
|
||
using 薄膜气体透过率测试仪.Data;
|
||
|
||
namespace 薄膜气体透过率测试仪
|
||
{
|
||
public partial class CoefficientForm : UIForm
|
||
{
|
||
#region 私有字段
|
||
|
||
private TcpClient _tcpClient => ModbusResourceManager.Instance.TcpClient;
|
||
private IModbusMaster _modbusMaster => ModbusResourceManager.Instance.ModbusMaster;
|
||
private System.Windows.Forms.Timer _readTimer;
|
||
|
||
#endregion
|
||
|
||
private TestScreen _testScreen;
|
||
Function ma;
|
||
DataChange c = new DataChange();
|
||
|
||
|
||
public CoefficientForm()
|
||
{
|
||
InitializeComponent();
|
||
InitTimer();
|
||
}
|
||
|
||
private System.Windows.Forms.Timer InitTimer()
|
||
{
|
||
var timer = new System.Windows.Forms.Timer()
|
||
{
|
||
Interval = 1000
|
||
};
|
||
timer.Tick += async (s, e) =>
|
||
{
|
||
if (_modbusMaster != null)
|
||
{
|
||
try
|
||
{
|
||
await ReadAddr262DataAsync();
|
||
}
|
||
catch { }
|
||
}
|
||
};
|
||
//timer.Start();
|
||
return timer;
|
||
}
|
||
|
||
|
||
private async System.Threading.Tasks.Task ReadAddr262DataAsync()
|
||
{
|
||
try
|
||
{
|
||
// 添加空值检查
|
||
if (_modbusMaster == null || _tcpClient == null || !_tcpClient.Connected)
|
||
{
|
||
return;
|
||
}
|
||
|
||
// 创建任务列表
|
||
var tasks = new List<Task>
|
||
{
|
||
ReadAndUpdateFloatAsync(1218, 2, uiLabel6,"F2",""),
|
||
ReadLeakTestParametersAsync()
|
||
|
||
//ReadStatusAsync()
|
||
};
|
||
|
||
await Task.WhenAll(tasks);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
ShowError($"读取数据失败:{ex.Message}");
|
||
}
|
||
}
|
||
|
||
private async Task ReadAndUpdateFloatAsync(int address, int length, Label control, string format, string unit)
|
||
{
|
||
// 空值校验:避免传入空控件导致后续Invoke报错
|
||
if (control == null)
|
||
{
|
||
System.Diagnostics.Debug.WriteLine($"读取地址{address}失败:目标Label控件为空");
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
ushort[] registers = await Task.Run(async () =>
|
||
{
|
||
// 双重空值校验:避免_modbusMaster为空导致调用异常
|
||
if (_modbusMaster == null) return null;
|
||
return await _modbusMaster.ReadHoldingRegistersAsync(1, (ushort)address, (ushort)length);
|
||
});
|
||
|
||
// ===================== 数据有效性校验 & 解析 =====================
|
||
// 场景1:读取到2个及以上寄存器 → 解析为浮点型数据(占2个16位寄存器)
|
||
if (registers != null && registers.Length >= 2)
|
||
{
|
||
// c.UshortToFloat:自定义工具方法,将2个16位无符号寄存器转换为32位浮点型
|
||
// 参数顺序:registers[1](高位寄存器)、registers[0](低位寄存器)→ 需匹配设备数据存储格式
|
||
float value = c.UshortToFloat(registers[1], registers[0]);
|
||
|
||
// ===================== WinForm UI控件更新 =====================
|
||
// Control.Invoke:判断是否跨线程,若跨线程则切换到UI线程更新控件
|
||
// WinForm中UI控件只能由创建它的主线程修改,否则会抛出跨线程异常
|
||
if (control.InvokeRequired)
|
||
{
|
||
control.Invoke(new Action(() =>
|
||
{
|
||
control.Text = $"{value.ToString(format)}{unit}";
|
||
}));
|
||
}
|
||
else
|
||
{
|
||
// 若当前已是UI线程,直接更新
|
||
control.Text = $"{value.ToString(format)}{unit}";
|
||
}
|
||
}
|
||
// 场景2:读取到1个寄存器 → 解析为整型数据(占1个16位寄存器)
|
||
else if (registers != null && registers.Length >= 1)
|
||
{
|
||
// 直接取第一个寄存器的值作为整型数据
|
||
int value = registers[0];
|
||
|
||
// 同样通过Control.Invoke保证UI线程安全更新
|
||
if (control.InvokeRequired)
|
||
{
|
||
control.Invoke(new Action(() =>
|
||
{
|
||
control.Text = $"{value.ToString(format)}{unit}";
|
||
}));
|
||
}
|
||
else
|
||
{
|
||
control.Text = $"{value.ToString(format)}{unit}";
|
||
}
|
||
}
|
||
// 场景3:registers为null或长度为0 → 无有效数据,不执行更新(静默处理)
|
||
else if (registers == null || registers.Length == 0)
|
||
{
|
||
System.Diagnostics.Debug.WriteLine($"读取地址{address}失败:未获取到有效寄存器数据");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
|
||
//System.Diagnostics.Debug.WriteLine($"读取地址{address}失败:{ex.Message}\n异常堆栈:{ex.StackTrace}");
|
||
ShowError($"读取地址{address}失败:{ex.Message}\n异常堆栈:{ex.StackTrace}");
|
||
}
|
||
}
|
||
|
||
|
||
|
||
private void ShowError(string msg) => MessageBox.Show(msg, "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||
|
||
private class RegisterConfig
|
||
{
|
||
public ushort Address { get; set; } // Modbus寄存器地址(ushort类型匹配方法参数)
|
||
public UITextBox TextBox { get; set; } // 适配SunnyUI的UITextBox控件
|
||
public string Format { get; set; } // 数值格式化字符串
|
||
public bool IsInteger { get; set; } // 是否为整数类型(新增)
|
||
}
|
||
|
||
private async Task ReadLeakTestParametersAsync()
|
||
{
|
||
// 前置校验:连接状态检查
|
||
if (_tcpClient == null || !_tcpClient.Connected || _modbusMaster == null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
// 配置所有寄存器映射关系(统一管理,便于维护)
|
||
var registerConfigs = new List<RegisterConfig>
|
||
{
|
||
CreateRegisterConfig(1018, uiTextBox1, "F3",false), // 高压
|
||
CreateRegisterConfig(1118, uiTextBox3, "F3",false), // 低压
|
||
CreateRegisterConfig(1222, uiTextBox2, "F3",false), // 温度
|
||
CreateRegisterConfig(234, uiTextBox7, "F4",false), // 压降值
|
||
CreateRegisterConfig(230, uiTextBox12, "0",true), // 1#平稳系数
|
||
CreateRegisterConfig(242, uiTextBox9, "F2",false), // CM3 GTR系数
|
||
CreateRegisterConfig(246, uiTextBox10, "F2",false), // CM3 GT系数
|
||
CreateRegisterConfig(252, uiTextBox11, "F2",false), // mol GTR系数
|
||
CreateRegisterConfig(256, uiTextBox5, "F2",false) // mol GT系数
|
||
|
||
};
|
||
|
||
// 批量处理所有寄存器读取和UI更新
|
||
foreach (var config in registerConfigs)
|
||
{
|
||
await ReadRegisterAndUpdateUIAsync(config);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
this.Invoke(new Action(() =>
|
||
{
|
||
UIMessageBox.ShowError($"读取系数失败:{ex.Message}");
|
||
}));
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 辅助方法:创建寄存器配置对象(适配C# 7.3语法)
|
||
/// </summary>
|
||
/// <param name="address">寄存器地址</param>
|
||
/// <param name="textBox">绑定的SunnyUI文本框</param>
|
||
/// <param name="format">数值格式化字符串</param>
|
||
private RegisterConfig CreateRegisterConfig(int address, UITextBox textBox, string format, bool isInteger)
|
||
{
|
||
var config = new RegisterConfig();
|
||
config.Address = (ushort)address; // 显式转换为ushort,解决类型不匹配
|
||
config.TextBox = textBox;
|
||
config.Format = format;
|
||
config.IsInteger = isInteger; // 赋值整数类型标识
|
||
return config;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 通用方法:读取寄存器并更新SunnyUI文本框(核心逻辑复用)
|
||
/// </summary>
|
||
/// <param name="config">寄存器配置</param>
|
||
private async Task ReadRegisterAndUpdateUIAsync(RegisterConfig config)
|
||
{
|
||
// 异步读取Modbus寄存器(避免阻塞UI线程)
|
||
ushort[] registers = await Task.Run(() =>
|
||
_modbusMaster?.ReadHoldingRegisters(1, config.Address, 2));
|
||
|
||
// 数据有效性校验 + 区分整数/浮点处理
|
||
if (registers != null && registers.Length >= 2)
|
||
{
|
||
this.Invoke(new Action(() =>
|
||
{
|
||
if (config.IsInteger)
|
||
{
|
||
// 整数处理:合并两个ushort为32位整数(根据Modbus字节序调整)
|
||
// 注意:字节序需匹配设备端(此处为registers[1]高位 + registers[0]低位)
|
||
int intValue = (registers[1] << 16) | registers[0];
|
||
config.TextBox.Text = intValue.ToString(config.Format);
|
||
}
|
||
else
|
||
{
|
||
// 浮点处理:保持原有逻辑
|
||
float floatValue = c.UshortToFloat(registers[1], registers[0]);
|
||
config.TextBox.Text = floatValue.ToString(config.Format);
|
||
}
|
||
}));
|
||
}
|
||
}
|
||
|
||
private void ShowErrorMsg(string msg)
|
||
{
|
||
// 判断当前线程是否是UI线程,避免跨线程访问控件异常
|
||
if (this.InvokeRequired)
|
||
{
|
||
// 跨线程时通过Invoke切换到UI线程执行
|
||
this.Invoke(new Action<string>(ShowErrorMsg), msg);
|
||
return;
|
||
}
|
||
|
||
// UI线程直接显示错误提示框
|
||
MessageBox.Show(msg, "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
//返回
|
||
private void uiButton1_Click(object sender, EventArgs e)
|
||
{
|
||
SwitchWindow(ref _testScreen, () => new TestScreen());
|
||
}
|
||
|
||
private void CoefficientForm_Load(object sender, EventArgs e)
|
||
{
|
||
string plcIp = "192.168.1.10";
|
||
bool initSuccess = Data.ModbusResourceManager.Instance.Init(plcIp, 502);
|
||
if (!initSuccess)
|
||
{
|
||
MessageBox.Show("连接Modbus服务器失败!", "错误");
|
||
this.Close();
|
||
return;
|
||
}
|
||
|
||
// 检查连接状态
|
||
if (_tcpClient == null || !_tcpClient.Connected)
|
||
{
|
||
MessageBox.Show("Modbus连接异常!", "错误");
|
||
this.Close();
|
||
return;
|
||
}
|
||
ma = new Function(_modbusMaster);
|
||
|
||
_readTimer = InitTimer();
|
||
// 等_modbusMaster初始化完成后,手动启动
|
||
if (_modbusMaster != null)
|
||
{
|
||
_readTimer.Start();
|
||
}
|
||
}
|
||
|
||
private void SwitchWindow<T>(ref T windowInstance, Func<T> createFunc) where T : UIForm
|
||
{
|
||
//1.停止当前窗口的定时器(不释放资源)
|
||
_readTimer?.Stop();
|
||
|
||
// 2. 检查资源是否可用(添加重连机制)
|
||
if (_tcpClient == null || !_tcpClient.Connected || _modbusMaster == null)
|
||
{
|
||
//尝试重新连接
|
||
bool reconnectSuccess = TryReconnect();
|
||
if (!reconnectSuccess)
|
||
{
|
||
MessageBox.Show("TCP连接已断开,请重新连接!", "提示");
|
||
return;
|
||
}
|
||
}
|
||
|
||
// 3. 复用窗口实例:不存在则创建,存在则激活
|
||
if (windowInstance == null)
|
||
{
|
||
windowInstance = createFunc();
|
||
// 添加窗口关闭事件处理
|
||
windowInstance.Closed += (s, args) =>
|
||
{
|
||
// 窗口关闭时重新启动定时器并显示当前窗口
|
||
//_readtimer?.Start();
|
||
//this.Show();
|
||
this.Activate();
|
||
};
|
||
}
|
||
else
|
||
{
|
||
// 激活已存在的窗口(前置显示)
|
||
windowInstance.Activate();
|
||
return;
|
||
}
|
||
|
||
// 4. 切换窗口:隐藏当前窗口,显示目标窗口(非模态)
|
||
this.Hide();
|
||
windowInstance.Show(); // 使用 Show() 而不是 ShowDialog()
|
||
}
|
||
|
||
private bool TryReconnect()
|
||
{
|
||
try
|
||
{
|
||
string plcIp = "192.168.1.10";
|
||
bool initSuccess = Data.ModbusResourceManager.Instance.Init(plcIp, 502);
|
||
if (initSuccess)
|
||
{
|
||
ma = new Function(_modbusMaster);
|
||
return true;
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
ShowError($"重新连接失败:{ex.Message}");
|
||
}
|
||
return false;
|
||
}
|
||
|
||
|
||
private void uiTextBox1_Click(object sender, EventArgs e)
|
||
{
|
||
ma?.WriteToPLCForNew(uiTextBox1.Text.Trim(), 1018, Function.DataType.浮点型, 3);//高压
|
||
System.Threading.Tasks.Task.Delay(50);
|
||
}
|
||
|
||
private void uiTextBox3_Click(object sender, EventArgs e)
|
||
{
|
||
ma?.WriteToPLCForNew(uiTextBox3.Text.Trim(), 1118, Function.DataType.浮点型, 3);//低压
|
||
System.Threading.Tasks.Task.Delay(50);
|
||
}
|
||
|
||
private void uiTextBox2_Click(object sender, EventArgs e)
|
||
{
|
||
ma?.WriteToPLCForNew(uiTextBox2.Text.Trim(), 1222, Function.DataType.浮点型, 3);//温度
|
||
System.Threading.Tasks.Task.Delay(50);
|
||
}
|
||
|
||
private void uiTextBox7_Click(object sender, EventArgs e)
|
||
{
|
||
ma?.WriteToPLCForNew(uiTextBox7.Text.Trim(), 234, Function.DataType.浮点型, 4);//自身压降值
|
||
System.Threading.Tasks.Task.Delay(50);
|
||
}
|
||
|
||
private void uiTextBox12_Click(object sender, EventArgs e)
|
||
{
|
||
ma?.WriteToPLCForNew(uiTextBox12.Text.Trim(), 230, Function.DataType.整形);//1#平稳系数
|
||
System.Threading.Tasks.Task.Delay(50);
|
||
}
|
||
|
||
private void uiTextBox9_Click(object sender, EventArgs e)
|
||
{
|
||
ma?.WriteToPLCForNew(uiTextBox9.Text.Trim(), 242, Function.DataType.浮点型);//CM3 GTR系数
|
||
System.Threading.Tasks.Task.Delay(50);
|
||
}
|
||
|
||
private void uiTextBox10_Click(object sender, EventArgs e)
|
||
{
|
||
ma?.WriteToPLCForNew(uiTextBox10.Text.Trim(), 246, Function.DataType.浮点型);//CM3 GT系数
|
||
System.Threading.Tasks.Task.Delay(50);
|
||
}
|
||
|
||
private void uiTextBox11_Click(object sender, EventArgs e)
|
||
{
|
||
ma?.WriteToPLCForNew(uiTextBox11.Text.Trim(), 252, Function.DataType.浮点型);//mol GTR系数
|
||
System.Threading.Tasks.Task.Delay(50);
|
||
}
|
||
|
||
private void uiTextBox5_Click(object sender, EventArgs e)
|
||
{
|
||
ma?.WriteToPLCForNew(uiTextBox5.Text.Trim(), 256, Function.DataType.浮点型);//mol GT系数
|
||
System.Threading.Tasks.Task.Delay(50);
|
||
}
|
||
|
||
private void CoefficientForm_FormClosing(object sender, FormClosingEventArgs e)
|
||
{
|
||
_readTimer?.Stop();
|
||
_readTimer?.Dispose();
|
||
|
||
// 仅用户主动关闭时退出应用
|
||
if (e.CloseReason == CloseReason.UserClosing)
|
||
{
|
||
ModbusResourceManager.Instance?.Dispose();
|
||
Application.Exit();
|
||
}
|
||
}
|
||
}
|
||
}
|