using Modbus.Device; using OxyPlot; using OxyPlot.Series; using OxyPlot.Wpf; using PdfSharpCore.Drawing; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Configuration; using System.IO; using System.Linq; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading.Tasks; 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 软胶囊弹性硬度测试仪; using 软胶囊弹性硬度测试仪.Data; namespace EmptyLoadTest { /// /// Window1.xaml 的交互逻辑 /// public partial class EmergencyStopWindow : Window { private ObservableCollection _reportData; private DispatcherTimer _dataInsertTimer; private int _dataIndex = 1; #region 私有字段 private TcpClient _tcpClient => ModbusResourceManager.Instance.TcpClient; private IModbusMaster _modbusMaster => ModbusResourceManager.Instance.ModbusMaster; private readonly DispatcherTimer _readTimer; private DispatcherTimer _dataTimer; #endregion 软胶囊弹性硬度测试仪.Function ma; DataChange c = new DataChange(); private PlotModel _plotModel; private LineSeries _temperatureSeries; public EmergencyStopWindow() { InitializeComponent(); InitializeData(); InitializeUI(); InitializeModbusTcp(); _readTimer = InitDispatcherTimer(); _dataTimer = InitDispatcherTimer2(); } private void InitializeUI() { // 初始化图表 _plotModel = new PlotModel { Title = "扭矩-转速曲线", Subtitle = "医用刨削器特征" }; _plotModel.Axes.Add(new OxyPlot.Axes.LinearAxis { Position = OxyPlot.Axes.AxisPosition.Bottom, Title = "扭矩(N.cm)", Minimum = 0 }); _plotModel.Axes.Add(new OxyPlot.Axes.LinearAxis { Position = OxyPlot.Axes.AxisPosition.Left, Title = "转速(rad/min)" }); _temperatureSeries = new LineSeries { Title = "转速变化", Color = OxyColors.Blue, MarkerType = MarkerType.Circle, MarkerSize = 3 }; _plotModel.Series.Add(_temperatureSeries); plotView.Model = _plotModel; } private static EmergencyStopWindow _instance; public static EmergencyStopWindow Instance { get { if (_instance == null || !_instance.IsLoaded) { _instance = new EmergencyStopWindow(); } return _instance; } } private void InitializeModbusTcp() { try { //string plcIp = "192.168.1.10"; string plcIp = "127.0.0.1"; bool initSuccess = 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); } catch (Exception ex) { ShowError($"Modbus初始化失败: {ex.Message}"); } } private void ShowError(string msg) => MessageBox.Show(msg, "错误", MessageBoxButton.OK, MessageBoxImage.Error); private DispatcherTimer InitDispatcherTimer() { var timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(100) }; timer.Tick += async (s, e) => { if (_modbusMaster != null) { try { await ReadAddr262DataAsync(); } catch { } } }; return timer; } private DispatcherTimer InitDispatcherTimer2() { var timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(50) }; timer.Tick += DataTimer_Tick; return timer; } private async System.Threading.Tasks.Task ReadAddr262DataAsync() { try { // 创建任务列表 var tasks = new List { ReadAndUpdateFloatAsync(1306, 1, actualSpeed, "F2", " "), ReadAndUpdateFloatAsync(1304, 2, force, "F2", " "), ReadAndUpdateFloatAsync(322, 2, settingforce, "F3", " "), ReadAndUpdateFloatAsync(300, 2, speed, "F3", " "), ReadAndUpdateFloatAsync(430, 2, maxforce, "F2", " "), ReadAndUpdateFloatAsync(4324, 1, reachforce, "F0", " "), ReadAndUpdateFloatAsync(4322, 1, maxforce2, "F0", " "), ReadStatusAsync(), ReadresetAndStopStatusAsync() }; await Task.WhenAll(tasks); } catch (Exception ex) { ShowError($"读取数据失败:{ex.Message}"); } } private void updateReport() { // 2. 读取配置文件(App.config) Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None); config.AppSettings.Settings["txtSetTorque"].Value = settingforce.Text; config.AppSettings.Settings["txtTestTime"].Value = (DateTime.Now - starttime).TotalSeconds.ToString("F1"); config.AppSettings.Settings["txtSpeedAtSetTorque"].Value = maxforce2.Text; config.AppSettings.Settings["txtSpeedAtMaxTorque"].Value = maxforce2.Text; // 4. 保存配置更改(关键:不调用Save则修改不生效) config.Save(ConfigurationSaveMode.Modified); // 刷新配置管理器,让后续读取能获取最新值 ConfigurationManager.RefreshSection("appSettings"); } // 添加这些变量 private bool _testStarted = false; private bool _testCompleted = false; private async Task ReadStatusAsync() { try { bool[] registers = await Task.Run(async () => await _modbusMaster?.ReadCoilsAsync(1, 321, 1) ); if (registers != null && registers.Length >= 1) { bool value = registers[0]; Dispatcher.Invoke(() => { if (value) { statusB.Background = Brushes.Green; status.Text = "运行中"; _dataInsertTimer?.Start(); _dataTimer?.Start(); updateReport(); // 测试已开始 if (!_testStarted) { _testStarted = true; } } else { statusB.Background = Brushes.Blue; status.Text = "空闲"; // 只有当测试确实开始过且尚未完成时才导出 if (_testStarted && !_testCompleted) { _testCompleted = true; // 检查是否有测试数据 if (_reportData != null && _reportData.Count > 0) { // 延时一小段时间确保数据已保存 Task.Delay(500).ContinueWith(_ => { Dispatcher.Invoke(async () => { await AutoExportCSV(); }); }); } } _dataInsertTimer?.Stop(); _dataTimer?.Stop(); } }); } } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"读取地址{200}失败:{ex.Message}"); } } private void InitializeData() { _reportData = new ObservableCollection(); _dataInsertTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(20) // 每秒插入一次数据 }; _dataInsertTimer.Tick += InsertData_Tick; } // 添加线程锁,防止并发问题 private readonly object _dataLock = new object(); private int _lastForceValueIndex = 0; private float _lastForceValue = 0f; private const float FORCE_CHANGE_THRESHOLD = 0.1f; // 力变化阈值,防止重复记录 private void InsertData_Tick(object sender, EventArgs e) { try { // 解析当前力值 if (!float.TryParse(force.Text, out float currentForceValue)) { currentForceValue = 0; } //ushort[] registers = _modbusMaster?.ReadHoldingRegisters(1, 1304, 2); //if (registers != null && registers.Length >= 2) //{ // currentForceValue = c.UshortToFloat(registers[1], registers[0]); //} if (!float.TryParse(actualSpeed.Text, out float currentSpeedValue)) { currentSpeedValue = 0; } // 简化条件:去掉阈值限制或降低第一次记录的阈值 if (_reportData.Count == 0 || currentForceValue >= 0.2f) { // 使用锁确保线程安全 lock (_dataLock) { // 更新最后记录的值 _lastForceValue = currentForceValue; _lastForceValueIndex = _dataIndex; // 创建报表项 var reportItem = new ReportItem { Index = _dataIndex, Time = DateTime.Now, Torque = currentForceValue, Speed = (int)currentSpeedValue, }; // 限制最大数据条数,避免内存过大 if (_reportData.Count > 1000) { _reportData.RemoveAt(0); } // 添加到报表数据 _reportData.Add(reportItem); // 递增索引 _dataIndex++; } } } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"插入数据失败:{ex.Message}"); } } public ObservableCollection GetReportData() { return _reportData; } // 清空报表数据 public void ClearReportData() { _reportData?.Clear(); _dataIndex = 1; } private async Task ReadresetAndStopStatusAsync() { try { bool[] registers = await Task.Run(async () => await _modbusMaster?.ReadCoilsAsync(1, 91, 2) ); if (registers != null && registers.Length >= 1) { bool value = registers[0]; Dispatcher.Invoke(() => { if (value) { fastleftB.Background = Brushes.Red; fastleft.Text = "复位中"; } else if (registers[1]) { fastleftB.Background = Brushes.Green; fastleft.Text = "复位完成"; shacheB.Visibility = Visibility.Hidden; } }); } bool[] registers2 = await Task.Run(async () => await _modbusMaster?.ReadCoilsAsync(1, 310, 1) ); if (registers2 != null && registers2.Length >= 1) { bool value2 = registers2[0]; Dispatcher.Invoke(() => { if (value2) { limit.Visibility = Visibility.Visible; } else { limit.Visibility = Visibility.Hidden; } }); } bool[] registers3 = await Task.Run(async () => await _modbusMaster?.ReadCoilsAsync(1, 1300, 1) ); if (registers3 != null && registers3.Length >= 1) { bool value3 = registers3[0]; Dispatcher.Invoke(() => { if (value3) { stopB.Background = Brushes.Red; stop.Text = "急停未复位"; faststop.Visibility = Visibility.Visible; } else { stopB.Background = Brushes.Green; stop.Text = "急停已复位,系统可重新运行"; faststop.Visibility = Visibility.Hidden; } }); } } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"读取地址{200}失败:{ex.Message}"); } } private async Task ReadAndUpdateFloatAsync(int address, int length, System.Windows.Controls.TextBox control, string format, string unit) { try { ushort[] registers = await Task.Run(async () => await _modbusMaster?.ReadHoldingRegistersAsync(1, (ushort)address, (ushort)length) ); if (registers != null && registers.Length >= 2) { float value = c.UshortToFloat(registers[1], registers[0]); Dispatcher.Invoke(() => control.Text = value.ToString(format) + unit); } else if (registers != null && registers.Length >= 1) { int value = registers[0]; Dispatcher.Invoke(() => control.Text = value.ToString(format) + unit); } } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"读取地址{address}失败:{ex.Message}"); } } private async Task ReadAndUpdateFloatAsync(int address, int length, System.Windows.Controls.TextBlock control, string format, string unit) { try { ushort[] registers = await Task.Run(async () => await _modbusMaster?.ReadHoldingRegistersAsync(1, (ushort)address, (ushort)length) ); if (registers != null && registers.Length >= 2) { float value = c.UshortToFloat(registers[1], registers[0]); Dispatcher.Invoke(() => control.Text = value.ToString(format) + unit); } else if (registers != null && registers.Length >= 1) { int value = registers[0]; Dispatcher.Invoke(() => control.Text = value.ToString(format) + unit); } } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"读取地址{address}失败:{ex.Message}"); } } private void settingforce_GotFocus(object sender, RoutedEventArgs e) { ma.WriteToPLCForNew(settingforce.Text.Trim(), 322, Function.DataType.浮点型); System.Threading.Tasks.Task.Delay(50); t0.Focus(); } private void speed_GotFocus(object sender, RoutedEventArgs e) { ma.WriteToPLCForNew(speed.Text.Trim(), 300, Function.DataType.浮点型); System.Threading.Tasks.Task.Delay(50); t0.Focus(); } private void Button_Click(object sender, RoutedEventArgs e) { //ma.BtnClickFunctionForNew(Function.ButtonType.置位型, 300); } private void Button_Click_1(object sender, RoutedEventArgs e) { //ma.BtnClickFunctionForNew(Function.ButtonType.置位型, 301); } private void Button_Click_2(object sender, RoutedEventArgs e) { ma.BtnClickFunctionForNew(Function.ButtonType.置位型, 320); starttime = DateTime.Now; // 重置测试状态标志 _testStarted = true; _testCompleted = false; _reportData?.Clear(); // 清空之前的测试数据 _dataIndex = 1; } DateTime starttime; private readonly Queue _forceBuffer = new Queue(); private readonly Queue _speedBuffer = new Queue(); private const int BUFFER_SIZE = 5; // 滑动窗口大小 private void DataTimer_Tick(object sender, EventArgs e) { float forceV = float.Parse(force.Text); float actualSpeedV = float.Parse(actualSpeed.Text); float settingforceV = float.Parse(settingforce.Text); _temperatureSeries.Points.Add(new OxyPlot.DataPoint(forceV, actualSpeedV)); _plotModel.InvalidatePlot(true); //CurveWindow.Instance.UpdateData(forceV, actualSpeedV); if (forceV + 1 >= settingforceV) { shacheB.Visibility = Visibility.Visible; shacheB.Background = Brushes.Red; shache.Text = "请松踏板!!"; } } private void Button_Click_3(object sender, RoutedEventArgs e) { ma.BtnClickFunctionForNew(Function.ButtonType.复归型, 90); //shacheB.Visibility = Visibility.Hidden; } private void Button_Click_4(object sender, RoutedEventArgs e) { var mainWindow = ExperimentReportWindow.Instance; // 检查窗口状态,只在窗口未显示时调用ShowDialog if (!mainWindow.IsVisible) { mainWindow.Show(); } else { // 如果窗口已显示,可将其激活到前台 mainWindow.Activate(); } Close(); } private void Button_Click_5(object sender, RoutedEventArgs e) { var mainWindow = ProcessReportWindow.Instance; // 检查窗口状态,只在窗口未显示时调用ShowDialog if (!mainWindow.IsVisible) { mainWindow.Show(); } else { // 如果窗口已显示,可将其激活到前台 mainWindow.Activate(); } Close(); } private void Window_Loaded(object sender, RoutedEventArgs e) { _readTimer?.Start(); } private void 上升_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) { _modbusMaster?.WriteSingleCoil(1, 300, true); ((Button)sender).CaptureMouse(); } private void 上升_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e) { _modbusMaster?.WriteSingleCoil(1, 300, false); ((Button)sender).ReleaseMouseCapture(); } private void 上升_LostMouseCapture(object sender, MouseEventArgs e) { _modbusMaster?.WriteSingleCoil(1, 300, false); } // 鼠标按下时触发 private void 下降_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) { // 鼠标按下时置位 _modbusMaster?.WriteSingleCoil(1, 301, true); // 捕获鼠标,确保即使拖动出按钮范围也能收到抬起事件 ((Button)sender).CaptureMouse(); } private void 下降_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e) { // 鼠标抬起时复位 _modbusMaster?.WriteSingleCoil(1, 301, false); // 释放鼠标捕获 ((Button)sender).ReleaseMouseCapture(); } private void 下降_LostMouseCapture(object sender, MouseEventArgs e) { // 如果意外失去鼠标捕获,也复位 _modbusMaster?.WriteSingleCoil(1, 301, false); } private void Button_Click_6(object sender, RoutedEventArgs e) { var mainWindow = MainWindow.Instance; if (!mainWindow.IsVisible) { mainWindow.Show(); } else { mainWindow.Activate(); } Close(); } private void Button_Click_7(object sender, RoutedEventArgs e) { _temperatureSeries?.Points.Clear(); // 如果使用了滑动窗口缓冲区,也需要清除 if (_forceBuffer != null) _forceBuffer.Clear(); if (_speedBuffer != null) _speedBuffer.Clear(); _dataIndex = 0; // 刷新图表 _plotModel?.InvalidatePlot(true); } private void Button_Click_8(object sender, RoutedEventArgs e) { SaveChartAsImage(); } private void SaveChartAsImage() { try { int imageWidth = 1000; // 设置宽度 int imageHeight = 700; // 设置高度 if (plotView == null || _temperatureSeries.Points.Count == 0) { MessageBox.Show("没有数据可保存", "提示", MessageBoxButton.OK, MessageBoxImage.Information); return; } var saveFileDialog = new Microsoft.Win32.SaveFileDialog { Filter = "PNG图片 (*.png)|*.png", FileName = $"曲线图_{DateTime.Now:yyyyMMdd_HHmmss}.png", DefaultExt = ".png" }; if (saveFileDialog.ShowDialog() == true) { // 方法1:直接截图plotView控件 RenderTargetBitmap renderBitmap = new RenderTargetBitmap( imageWidth, imageHeight, 96d, 96d, PixelFormats.Pbgra32); renderBitmap.Render(plotView); // 保存为PNG using (var fileStream = new FileStream(saveFileDialog.FileName, FileMode.Create)) { PngBitmapEncoder encoder = new PngBitmapEncoder(); encoder.Frames.Add(BitmapFrame.Create(renderBitmap)); encoder.Save(fileStream); } MessageBox.Show($"图片已保存到:{saveFileDialog.FileName}", "保存成功", MessageBoxButton.OK, MessageBoxImage.Information); } } catch (Exception ex) { MessageBox.Show($"保存失败:{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); } } private async Task AutoExportCSV() { try { // 再次检查是否有测试数据 if (_reportData == null || _reportData.Count == 0) { return; } string fileName = $"过程报表_{DateTime.Now:yyyyMMdd_HHmmss}.csv"; string folderPath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Reports"); Directory.CreateDirectory(folderPath); string filePath = System.IO.Path.Combine(folderPath, fileName); await Task.Run(() => { using (var writer = new StreamWriter(filePath, false, System.Text.Encoding.UTF8)) { writer.WriteLine("序号,时间,实时扭矩(N.cm),实时转速(Rad/Min)"); foreach (var item in _reportData) { writer.WriteLine($"{item.Index},{item.Time:HH:mm:ss},{item.Torque:F1},{item.Speed}"); } } }); Dispatcher.Invoke(() => { MessageBox.Show($"测试完成,报表已自动导出到:{filePath}", "导出成功", MessageBoxButton.OK, MessageBoxImage.Information); }); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"自动导出报表失败:{ex.Message}"); } } } }