using Microsoft.Win32; using Modbus.Device; using Sunny.UI; using System; using System.Collections.Generic; using System.IO; using System.Text; using System.Timers; 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.Navigation; using System.Windows.Shapes; using 头罩视野.Services; using 头罩视野.Services.Data; using static 头罩视野.TestDataStore; namespace 头罩视野.Views { /// /// RecordDate.xaml 的交互逻辑 /// /// public partial class RecordDate : Page { private IModbusMaster _modbusMaster => ModbusResourceManager.Instance.ModbusMaster; private System.Timers.Timer? _plcReadTimer; // 表跟数据存储列表 public List LeftEyeDataList = new List(); public List RightEyeDataList = new List(); public List CalLeftData = new List(); public List CalRightData = new List(); // 配置:和你PLC地址完全对应 左目 private const int LeftEyeStartAddress = 1362; // D1362 private const int ChannelCount = 72; // 72个通道 private const int RegistersPerChannel = 1; // 每个通道2个寄存器(Float) //右目 private const int RightEyeStartAddress = 1218; // D1218 //// 长按清除用 private bool _isClearPressed = false; private Thread _clearThread; public RecordDate() { InitializeComponent(); DynamicHeader(); // 2. 调用(名字和上面的变量一致) // 2. 启动定时器,定时读取数据(每100ms读一次) StartPlcReadTimer(1000); //// 判断连接 if (!ModbusHelper.IsConnected) { MessageBox.Show("未连接"); return; } } //动态生成表头 void DynamicHeader() { // 2. 循环生成 72 个 ch 列 for (int i = 1; i <= ChannelCount; i++) { dataGrid1.Columns.Add(new DataGridTextColumn { Header = $"ch.{i}", Binding = new System.Windows.Data.Binding($"Ch{i}") }); dataGrid2.Columns.Add(new DataGridTextColumn { Header = $"ch.{i}", Binding = new System.Windows.Data.Binding($"Ch{i}") }); } } public void StopPlcTimer() { if (_plcReadTimer != null) { _plcReadTimer.Stop(); _plcReadTimer.Dispose(); _plcReadTimer = null; } } //定时读取 PLC 数据 public void StartPlcReadTimer(int intervalMs) { // 防止重复创建 if (_plcReadTimer != null && _plcReadTimer.Enabled) return; _plcReadTimer = new System.Timers.Timer(intervalMs); _plcReadTimer.Elapsed += ReadPlcData; _plcReadTimer.Start(); } private void ReadPlcData(object? sender, ElapsedEventArgs e) { // 左通道 ReadPlcDataGeneric( slaveAddress: 1, startAddress: LeftEyeStartAddress, count: 72, dataList: LeftEyeDataList, dataGrid: dataGrid1); //// 右通道 //ReadPlcDataGeneric( // slaveAddress: 1, // startAddress: RightEyeStartAddress, // count: (ushort)(ChannelCount * RegistersPerChannel), // dataList: RightEyeDataList, // dataGrid: dataGrid2); } /// /// 读取PLC HoldingRegisters 并更新到列表和DataGrid /// 设备站号(一般是1) /// 起始地址 /// 寄存器总数 /// 数据缓存列表 /// 要更新的DataGrid控件 /// 通用PLC读取方法(左右通道通用) /// private void ReadPlcDataGeneric( byte slaveAddress, ushort startAddress, ushort count, List dataList, DataGrid dataGrid) { if (_modbusMaster == null || !ModbusHelper.TcpClient.Connected) return; try { // 读取寄存器 ushort[] registers = _modbusMaster.ReadHoldingRegisters( slaveAddress: slaveAddress, startAddress: startAddress, numberOfPoints: (ushort)count); uint[] data32 = ConvertRegistersToUInt32(registers, true); // 先把变量存为局部变量,解决闭包问题 var regCopy = data32; var listCopy = dataList; var gridCopy = dataGrid; // 交给UI线程更新数据和表格 Dispatcher.Invoke(() => { AddPlcDataRow(regCopy, listCopy, gridCopy); }); System.Diagnostics.Debug.WriteLine("读取寄存器数据" ); } catch (Exception ex) { Console.WriteLine($"PLC读取失败: {ex.Message}"); } } private uint[] ConvertRegistersToUInt32(ushort[] registers, bool isLittleEndian) { if (registers == null || registers.Length % 2 != 0) throw new ArgumentException("寄存器数量必须是偶数"); uint[] result = new uint[registers.Length / 2]; for (int i = 0; i < result.Length; i++) { ushort low = registers[i * 2]; ushort high = registers[i * 2 + 1]; if (isLittleEndian) result[i] = (uint)(low | (high << 16)); else result[i] = (uint)(high | (low << 16)); } return result; } /// /// 把PLC数据添加到动态表格 private int _rowIndex = 1; private void AddPlcDataRow(uint[] registers, List dataList, DataGrid dg) { // 1. 先清空临时列表,不影响历史数据 //dataList.Clear(); // 2. 构建一次采集的单行数据(包含所有通道) dynamic newRow = new System.Dynamic.ExpandoObject(); var dict = (IDictionary)newRow; // 固定列:编号、时间、日期(每次采集的这一行) dict["Id"] = _rowIndex++; dict["Time"] = DateTime.Now.ToString("HH:mm:ss"); dict["Date"] = DateTime.Now.ToString("yyyy-MM-dd"); // 关键:循环给所有通道赋值,一行里包含所有通道值 for (int i = 0; i < registers.Length; i++) { // 字段名和你界面列绑定的名称必须完全一致,比如 ch.1 / Ch1 dict[$"Ch{i + 1}"] = registers[i]; //dict[$"ch.{i + 1}"] = registers[i]; } // 3. 把这一行加到历史列表,实现“读多行” dataList.Add(newRow); // 4. 强制刷新DataGrid,让界面显示多行 dg.Dispatcher.Invoke(() => { dg.ItemsSource = null; dg.ItemsSource = dataList; dg.Items.Refresh(); // 强制刷新视图,避免不渲染 }); } //面积的计算方法 public void getAllData(List leftEyeDataList, List RightEyeDataList, int perAngle) { // 1. 先去除异常值,生成新列表(不修改原列表) var filteredLeft = GetArea.RemoveOutliers(leftEyeDataList); var filteredRight = GetArea.RemoveOutliers(RightEyeDataList); //左目视野面积 GlobalData.LeftEyeArea = GetArea.CalculateEyeArea(filteredLeft); //右目视野面积 GlobalData.RightEyeArea = GetArea.CalculateEyeArea(filteredRight); //双目视野面积 GlobalData.BinocularArea = GetArea.CalcBinocularArea(filteredLeft, filteredRight); //// 总视野面积 GlobalData.TotalEyeArea = GlobalData.LeftEyeArea + GlobalData.RightEyeArea - GlobalData.BinocularArea; //// 空白视野面积 GlobalData.BlankArea = GetArea.StandardTotal - GlobalData.TotalEyeArea; //视野保存率 // 左眼平均值数组 double[] leftAvg = GetArea.GetEyeAvgArray(filteredLeft); // 右眼平均值数组 double[] rightAvg = GetArea.GetEyeAvgArray(RightEyeDataList); double leftLowerAngle = GetArea.CalcLowerAngle(leftAvg, perAngle); double rightLowerAngle = GetArea.CalcLowerAngle(rightAvg, perAngle); //下方视野 GlobalData.LowerVision = Math.Min(leftLowerAngle, rightLowerAngle); //视野保存率 GlobalData.VisionRetentionRate = GetArea.CalcVisionRate(GlobalData.LeftEyeArea, GlobalData.RightEyeArea); //打印数值显示在系统上面 System.Diagnostics.Debug.WriteLine("左目视野面积" + GlobalData.LeftEyeArea); System.Diagnostics.Debug.WriteLine("右目视野面积" + GlobalData.RightEyeArea); System.Diagnostics.Debug.WriteLine("双目视野面积" + GlobalData.BinocularArea); System.Diagnostics.Debug.WriteLine("总视野面积" + GlobalData.TotalEyeArea); System.Diagnostics.Debug.WriteLine("下方视野" + GlobalData.LowerVision); System.Diagnostics.Debug.WriteLine("视野保存率" + GlobalData.VisionRetentionRate); } //#endregion // 保存左眼 private void btnSaveLeft_Click(object sender, RoutedEventArgs e) { //SaveToCsv(LeftEyeDataList, $"左眼数据_{DateTime.Now:yyyyMMddHHmmss}.csv"); ModbusHelper.SaveToCsv(LeftEyeDataList, $"左眼数据_{DateTime.Now:yyyyMMddHHmmss}.csv"); } // 保存右眼 private void btnSaveRight_Click(object sender, RoutedEventArgs e) { ModbusHelper.SaveToCsv(RightEyeDataList,$"右眼数据_{DateTime.Now:yyyyMMddHHmmss}.csv"); } //清除 private void btnClear_MouseDown(object sender, MouseButtonEventArgs e) { //_isClearPressed = true; //_clearThread = new Thread(() => //{ // Thread.Sleep(500); // 长按1秒触发 // if (_isClearPressed) // { // Application.Current.Dispatcher.Invoke(() => ClearAllData()); // } //}); //_clearThread.Start(); _plcReadTimer?.Stop(); } // 清除所有数据 private void ClearAllData() { LeftEyeDataList.Clear(); dataGrid1.Items.Clear(); RightEyeDataList.Clear(); dataGrid2.Items.Clear(); MessageBox.Show("数据已清除"); } private void btnClear_MouseUp(object sender, MouseButtonEventArgs e) { _isClearPressed = false; _clearThread?.Join(100); // 等待线程结束最多100毫秒,然后强制结束 } private void Page_Unloaded(object sender, RoutedEventArgs e) { _plcReadTimer?.Stop(); _plcReadTimer?.Dispose(); //_modbusMaster?.Dispose(); //ModbusHelper.TcpClient?.Close(); } private void Page_Loaded(object sender, RoutedEventArgs e) { } private void GoHome(object s, RoutedEventArgs e) => NavigationService.Content = null; private void GoTest(object s, RoutedEventArgs e) => NavigationService.Content = new Views.PageTest(); private void GoRecord(object s, RoutedEventArgs e) => NavigationService.Content = new Views.RecordDate(); private void GoView(object s, RoutedEventArgs e) => NavigationService.Content = new Views.RecordPage(); //NavigationService.Navigate(new Views.RecordDate()); 页面相互跳转 } }