Files
huadongmocaceshiyi/MainWindow.xaml.cs
2026-03-11 19:11:16 +08:00

577 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 Sunny.UI;
using System;
using System.ComponentModel;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Net.Sockets;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Threading;
using ;
namespace PLCDataMonitor
{
public partial class MainWindow : Window, INotifyPropertyChanged
{
// 数据属性
private Brush _statusColor;
private string _connectionStatus;
string _plcIpAddress = string.Empty;
int _plcPort = 502;
private bool _isConnected;
private Thread _readThread;
private bool _keepReading;
private Function ma;
private DataChange c = new DataChange();
// 关键修改:创建一个共享的线程安全数据队列
private System.Collections.Concurrent.ConcurrentQueue<Tuple<DateTime, double, double>> _sharedCurveDataQueue = new System.Collections.Concurrent.ConcurrentQueue<Tuple<DateTime, double, double>>();
// 页面实例
private HomePage _homePage;
private ReportPage _reportPage;
private CurvePage _curvePage;
// 属性更改通知事件
public event PropertyChangedEventHandler PropertyChanged;
private TcpClient _tcpClient;
private IModbusMaster _modbusMaster;
// PLC 地址请根据实际PLC修改
private const int PauseControlCoil = 1000; // 写入 true=暂停false=继续(线圈地址)
private const int PauseStatusCoil = 1001; // 读取当前暂停状态(线圈地址)
// 记录暂停时间段(用于导出过滤)
private List<(DateTime start, DateTime end)> _pausePeriods = new List<(DateTime, DateTime)>();
private bool _lastPausedState = false; // 上一次读取的暂停状态
private DateTime _pauseStartTime; // 本次暂停开始时间
public List<(DateTime start, DateTime end)> PausePeriods => _pausePeriods;
// 添加清空方法
public void ClearPausePeriods()
{
_pausePeriods.Clear();
_lastPausedState = false;
_pauseStartTime = default;
}
// 连接状态属性
public Brush StatusColor
{
get { return _statusColor; }
set { _statusColor = value; OnPropertyChanged(nameof(StatusColor)); }
}
public string ConnectionStatus
{
get { return _connectionStatus; }
set { _connectionStatus = value; OnPropertyChanged(nameof(ConnectionStatus)); }
}
// 报表相关:记录是否已插入当前批次报表(避免重复插入)
public bool _hasInsertedReport = false;
public MainWindow()
{
InitializeComponent();
DataContext = this;
// 初始化状态
StatusColor = Brushes.Red;
ConnectionStatus = "未连接";
_homePage = new HomePage();
// 订阅暂停/继续事件
_homePage.PauseRequested += (s, e) => SendPauseCommand();
_homePage.ResumeRequested += (s, e) => SendResumeCommand();
// 关键修改:创建报表页时传入共享队列
_reportPage = new ReportPage();
_reportPage.CurveDataQueue = _sharedCurveDataQueue;
// 关键修改:创建曲线页时传入同一个共享队列
_curvePage = new CurvePage(_sharedCurveDataQueue);
// 默认显示数据监控页
MainContentFrame.Navigate(_homePage);
SetupLogging();
_reportPage.OnExportCompleted += () =>
{
_hasInsertedReport = false; // 重置插入标记
};
}
public void SetupLogging()
{
var logFilePath = "log.txt";
var fileListener = new TextWriterTraceListener(File.CreateText(logFilePath));
Trace.Listeners.Add(fileListener);
Trace.AutoFlush = true;
}
private void InitializeModbusTcp()
{
try
{
_tcpClient = new TcpClient();
_plcIpAddress = ConfigurationManager.AppSettings["PLC_IP"];
_plcPort = ConfigurationManager.AppSettings["PLC_PORT"].ToString().ToInt16();
var connectResult = _tcpClient.BeginConnect(_plcIpAddress, _plcPort, null, null);
var success = connectResult.AsyncWaitHandle.WaitOne(3000);
if (!success || !_tcpClient.Connected)
throw new Exception("连接PLC超时请检查IP和端口");
_modbusMaster = ModbusIpMaster.CreateIp(_tcpClient);
_modbusMaster.Transport.ReadTimeout = 1000;
_modbusMaster.Transport.WriteTimeout = 1000;
ma = new Function(_modbusMaster);
}
catch (Exception ex)
{
throw new Exception($"Modbus初始化失败{ex.Message}");
}
}
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private void BtnHome_Click(object sender, RoutedEventArgs e)
{
UpdateNavButtonStyle(BtnHome, BtnReport, BtnCurve);
MainContentFrame.Navigate(_homePage);
}
private void BtnReport_Click(object sender, RoutedEventArgs e)
{
UpdateNavButtonStyle(BtnReport, BtnHome, BtnCurve);
MainContentFrame.Navigate(_reportPage);
}
private void BtnCurve_Click(object sender, RoutedEventArgs e)
{
UpdateNavButtonStyle(BtnCurve, BtnHome, BtnReport);
MainContentFrame.Navigate(_curvePage);
}
private void UpdateNavButtonStyle(FrameworkElement activeBtn,
params FrameworkElement[] inactiveBtns)
{
if (activeBtn is Button btnActive)
{
btnActive.Background = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#3498DB"));
}
foreach (var btn in inactiveBtns)
{
if (btn is Button btnInactive)
{
btnInactive.Background = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#2C3E50"));
}
}
}
private void ConnectToPLC()
{
try
{
Trace.WriteLine("Initializing Modbus TCP connection...");
InitializeModbusTcp();
_isConnected = true;
StatusColor = Brushes.Green;
ConnectionStatus = "已连接";
Trace.WriteLine("Connection established successfully.");
StartReadingData();
}
catch (Exception ex)
{
Trace.WriteLine($"Connection failed: {ex.Message}");
Trace.WriteLine($"Stack Trace: {ex.StackTrace}");
MessageBox.Show($"连接失败:{ex.Message}", "错误",
MessageBoxButton.OK, MessageBoxImage.Error);
StatusColor = Brushes.Orange;
ConnectionStatus = "连接失败";
}
}
private void DisconnectFromPLC()
{
try
{
_keepReading = false;
// 使用超时等待线程结束
if (_readThread != null && _readThread.IsAlive)
{
if (!_readThread.Join(500))
{
}
}
_modbusMaster?.Dispose();
_modbusMaster = null;
if (_tcpClient != null)
{
_tcpClient.Close();
_tcpClient.Dispose();
_tcpClient = null;
}
_isConnected = false;
StatusColor = Brushes.Red;
ConnectionStatus = "已断开";
_homePage.UpdateData("0.0", "0.0", "0.0", "0.0", 0, 0, "0", "0", "0");
}
catch (Exception ex)
{
MessageBox.Show($"断开失败:{ex.Message}", "错误",
MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void StartReadingData()
{
_keepReading = true;
_readThread = new Thread(ReadDataLoop) { IsBackground = true };
_readThread.Start();
}
private void ReadDataLoop()
{
//int i = 0;
//while (i < 100)
//{
// // 添加随机测试数据
// Random random = new Random();
// // 生成随机摩擦力值(包含负值)
// double friction1 = random.Next(-200, 200);
// double friction2 = random.Next(-200, 200);
// // 创建数据点(时间戳相同,确保数据一致性)
// var dataPoint = new Tuple<DateTime, double, double>(
// DateTime.Now,
// friction1,
// friction2
// );
// // 关键修改:同时添加到两个地方
// // 1. 添加到共享曲线数据队列(曲线页面和报表页面共享)
// _sharedCurveDataQueue.Enqueue(dataPoint);
// // 2. 添加到报表数据
// _reportPage.AddReportRecord(new ReportData
// {
// D = random.Next(1, 100),
// Friction1 = friction1.ToString("F1"), // 使用相同的摩擦力值
// Friction2 = friction2.ToString("F1"), // 使用相同的摩擦力值
// Id = (i + 11).ToString(),
// Pressure1 = random.Next(1, 10).ToString(),
// Pressure2 = random.Next(2, 12).ToString(),
// t1 = random.Next(20, 40),
// t2 = random.Next(25, 45),
// Time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") // 使用相同的时间格式
// });
// // 3. 通知曲线页面更新(这个调用只是触发重绘,数据已经在队列中)
// _curvePage.AddFrictionData(friction1, friction2);
// // 为了让曲线更平滑,可以少量数据生成
// Thread.Sleep(100); // 每100ms一个数据点
// i++;
//}
while (_keepReading)
{
try
{
if (_isConnected && _tcpClient != null && _tcpClient.Connected)
{
// 读取所有数据(包含新增的次数、最大摩擦力)
var data = ReadModbusData();
// 读取暂停状态
bool isPaused = false;
try
{
bool[] pauseStatus = _modbusMaster?.ReadCoils(0x01, (ushort)PauseStatusCoil, 1);
isPaused = pauseStatus != null && pauseStatus.Length > 0 && pauseStatus[0];
}
catch
{
// 读取失败时默认 false
}
// --- 记录暂停时间段 ---
if (isPaused != _lastPausedState)
{
if (isPaused) // 进入暂停
{
_pauseStartTime = DateTime.Now;
}
else // 退出暂停
{
if (_pauseStartTime != default)
{
_pausePeriods.Add((_pauseStartTime, DateTime.Now));
}
}
_lastPausedState = isPaused;
}
// --- 结束记录暂停时间段 ---
var pressure1 = data.pressure1;
var pressure2 = data.pressure2;
var friction1 = data.friction1;
var friction2 = data.friction2;
var maxFriction1 = data.maxFriction1;
var maxFriction2 = data.maxFriction2;
// 更新UI数据监控页+曲线页)
// 使用 BeginInvoke 避免阻塞后台读取线程,减少 UI 卡死风险
Dispatcher.BeginInvoke(new Action(() =>
{
// 更新数据监控页数值
_homePage.UpdateData(
pressure1.ToString("F1"),
pressure2.ToString("F1"),
friction1.ToString("F1"),
friction2.ToString("F1"),
data.setCount,
data.actualCount,
data.t1.ToString("F1"),
data.t2.ToString("F1"),
data.D.ToString("F1")
);
// 关键修改:确保曲线数据正确记录
if (data.setCount > 0 && data.actualCount > 0 &&
data.setCount > data.actualCount) // 点击了开始,且实际次数小于设定次数,才更新曲线
{
// 创建数据点
var dataPoint = new Tuple<DateTime, double, double>(
DateTime.Now,
friction1,
friction2
);
// 添加到共享队列
_sharedCurveDataQueue.Enqueue(dataPoint);
// 通知曲线页面更新
_curvePage.AddFrictionData(friction1, friction2);
// 关键修改:只添加一次,不需要重复添加到报表页面
// 报表页面的CurveDataQueue和_sharedCurveDataQueue是同一个对象
}
if (data.setCount > 0 && data.actualCount > 0 &&
data.setCount == data.actualCount && !_hasInsertedReport) // 一次测试执行完成,保存报表
{
// 创建报表记录(时间+压力+最大摩擦力)
var reportRecord = new ReportData(
DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), // 当前时间
pressure1.ToString("F1"), // 工位1压力
pressure2.ToString("F1"), // 工位2压力
maxFriction1.ToString("F1"), // 工位1最大摩擦力
maxFriction2.ToString("F1"),
data.t1,
data.t2,
data.D
);
// 插入报表
_reportPage.AddReportRecord(reportRecord);
// 标记为已插入(避免同一批次重复插入)
_hasInsertedReport = true;
}
if (data.actualCount < data.setCount)
{
_hasInsertedReport = false;
}
}), System.Windows.Threading.DispatcherPriority.Background);
}
}
catch (Exception ex)
{
// 非阻塞地报告错误到 UI
Dispatcher.BeginInvoke(new Action(() =>
{
StatusColor = Brushes.Orange;
ConnectionStatus = $"读取错误:{ex.Message}";
}), System.Windows.Threading.DispatcherPriority.Background);
}
Thread.Sleep(200); // 100ms刷新一次
}
}
private (float pressure1, float pressure2, float friction1, float friction2,
int setCount, int actualCount, float maxFriction1, float maxFriction2, float t1, float t2, float D, bool status) ReadModbusData()
{
try
{
// 修正地址按手册公式计算后的Modbus地址
ushort[] p1 = _modbusMaster?.ReadHoldingRegisters(0x01, 18701, 2); // 原DM170017001+1700=18701
ushort[] p2 = _modbusMaster?.ReadHoldingRegisters(0x01, 18705, 2); // 原DM170417001+1704=18705
ushort[] f1 = _modbusMaster?.ReadHoldingRegisters(0x01, 17045, 2); // 原DM4417001+44=17045
ushort[] f2 = _modbusMaster?.ReadHoldingRegisters(0x01, 17085, 2); // 原DM8417001+84=17085
// 设定次数H64
ushort[] setCountArr = _modbusMaster?.ReadHoldingRegisters(0x01, 6734, 1);
ushort[] actualCountArr = _modbusMaster?.ReadHoldingRegisters(0x01, 19000, 1);
// 最大摩擦力1DM160017001+1600=18601
ushort[] maxF1Arr = _modbusMaster?.ReadHoldingRegisters(0x01, 18601, 2);
// 最大摩擦力2DM160417001+1604=18605
ushort[] maxF2Arr = _modbusMaster?.ReadHoldingRegisters(0x01, 18605, 2);
// 温度和其他数据
ushort[] p11 = _modbusMaster?.ReadHoldingRegisters(0x01, 18201, 2);
ushort[] p12 = _modbusMaster?.ReadHoldingRegisters(0x01, 18205, 2);
ushort[] p13 = _modbusMaster?.ReadHoldingRegisters(0x01, 19101, 2);
bool[] p14 = _modbusMaster?.ReadCoils(0x01, 7100, 100);
if (p14[0])
{
}
// 转换数据压力、实时摩擦力float类型
float pressure1 = c.UshortToFloat(p1[0], p1[1]);
float pressure2 = c.UshortToFloat(p2[0], p2[1]);
float friction1 = c.UshortToFloat(f1[0], f1[1]);
float friction2 = c.UshortToFloat(f2[0], f2[1]);
float pressure11 = c.UshortToFloat(p11[0], p11[1]);
float pressure12 = c.UshortToFloat(p12[0], p12[1]);
float pressure13 = c.UshortToFloat(p13[0], p13[1]);
// 转换数据设定次数、实际次数int类型
int setCount = setCountArr[0]; // 16位寄存器直接转int
int actualCount = actualCountArr[0];
// 转换数据最大摩擦力float类型
float maxFriction1 = c.UshortToFloat(maxF1Arr[0], maxF1Arr[1]);
float maxFriction2 = c.UshortToFloat(maxF2Arr[0], maxF2Arr[1]);
bool stauts = p14[0];
// 保留4位小数返回
return (
(float)Math.Round(pressure1, 4),
(float)Math.Round(pressure2, 4),
(float)Math.Round(friction1, 4),
(float)Math.Round(friction2, 4),
setCount,
actualCount,
maxFriction1,
maxFriction2,
pressure11,
pressure12,
pressure13,
stauts
);
}
catch (Exception ex)
{
throw new Exception($"Modbus读取错误{ex.Message}");
}
}
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
DisconnectFromPLC();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
if (!_isConnected)
{
ConnectToPLC();
}
else
{
DisconnectFromPLC();
}
}
private void ConnectButton_Click(object sender, RoutedEventArgs e)
{
if (!_isConnected)
{
ConnectToPLC();
}
else
{
DisconnectFromPLC();
}
}
private void SendPauseCommand()
{
try
{
if (_isConnected && _modbusMaster != null)
{
_modbusMaster.WriteSingleCoil(0x01, (ushort)PauseControlCoil, true);
}
}
catch (Exception ex)
{
MessageBox.Show($"发送暂停命令失败:{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void SendResumeCommand()
{
try
{
if (_isConnected && _modbusMaster != null)
{
_modbusMaster.WriteSingleCoil(0x01, (ushort)PauseControlCoil, false);
}
}
catch (Exception ex)
{
MessageBox.Show($"发送继续命令失败:{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
}
}