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> _sharedCurveDataQueue = new System.Collections.Concurrent.ConcurrentQueue>(); // 页面实例 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.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.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); // 原DM1700,17001+1700=18701 ushort[] p2 = _modbusMaster?.ReadHoldingRegisters(0x01, 18705, 2); // 原DM1704,17001+1704=18705 ushort[] f1 = _modbusMaster?.ReadHoldingRegisters(0x01, 17045, 2); // 原DM44,17001+44=17045 ushort[] f2 = _modbusMaster?.ReadHoldingRegisters(0x01, 17085, 2); // 原DM84,17001+84=17085 // 设定次数(H64) ushort[] setCountArr = _modbusMaster?.ReadHoldingRegisters(0x01, 6734, 1); ushort[] actualCountArr = _modbusMaster?.ReadHoldingRegisters(0x01, 19000, 1); // 最大摩擦力1(DM1600):17001+1600=18601 ushort[] maxF1Arr = _modbusMaster?.ReadHoldingRegisters(0x01, 18601, 2); // 最大摩擦力2(DM1604):17001+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); } } } }