diff --git a/头罩视野slove/头罩视野/Services/GetArea.cs b/头罩视野slove/头罩视野/Services/GetArea.cs index f814fe8..edef976 100644 --- a/头罩视野slove/头罩视野/Services/GetArea.cs +++ b/头罩视野slove/头罩视野/Services/GetArea.cs @@ -40,6 +40,10 @@ namespace 头罩视野.Services // 只处理亮灯的情况 if (lightData[i] == 1) { + if (lightPositions.Count < lightData.Count()) + { + return 0; + } var (m, n) = lightPositions[i]; // 关键:只取下爪灯条(n == 1),因为只有它才对应下方的垂直视野 @@ -63,7 +67,7 @@ namespace 头罩视野.Services //视野保存率 public static double CalcVisionRate(double binocularRate) { - + // 1. 总视野保存率 double ratioTotal = binocularRate / _standardTotalArea; double gammaTotal = GetVisionGamma(ratioTotal); @@ -114,8 +118,14 @@ namespace 头罩视野.Services List brightPoints = new List(); for (int i = 0; i < totalLights; i++) { + if (totalLights > lightData.Count()) + { + + } if (lightData[i] == 1) { + + var (m, n) = lightPositions[i]; System.Drawing.Point p = GetLightPoint(m, n); brightPoints.Add(p); @@ -127,7 +137,7 @@ namespace 头罩视野.Services // 返回面积 return area; - } + } /// 生成设备全部243盏灯的(m,n)位置 上爪1条、下爪1条、左右共用1条,各81灯 public static System.Drawing.Point GetLightPoint(int m, int n) { @@ -162,45 +172,45 @@ namespace 头罩视野.Services // 最小二乘法拟合椭圆(核心算法)cx:椭圆中心点的 X 坐标 cy:椭圆中心点的 Y 坐标 a:椭圆的长半轴长度(较大的那个半径)b:椭圆的短半轴长度(较小的那个半径) public static (double cx, double cy, double a, double b, double area) FitEllipse(List points) - { - int n = points.Count; + { + int n = points.Count; if (n < 5) //throw new Exception("至少需要5个点来拟合椭圆"); - return new (0,0,0,0,0) ; + return new(0, 0, 0, 0, 0); // 这里是正确写法 var M = MathNetMatrix.Build.Dense(n, 5); var Y = MathNetVector.Build.Dense(n, i => -1.0); for (int i = 0; i < n; i++) - { - double x = points[i].X; - double y = points[i].Y; - M[i, 0] = x * x; - M[i, 1] = x * y; - M[i, 2] = y * y; - M[i, 3] = x; - M[i, 4] = y; - } + { + double x = points[i].X; + double y = points[i].Y; + M[i, 0] = x * x; + M[i, 1] = x * y; + M[i, 2] = y * y; + M[i, 3] = x; + M[i, 4] = y; + } - // 求解 - Vector sol = M.QR().Solve(Y); - double A = sol[0], B = sol[1], C = sol[2], D = sol[3], E = sol[4], F = 1; + // 求解 + Vector sol = M.QR().Solve(Y); + double A = sol[0], B = sol[1], C = sol[2], D = sol[3], E = sol[4], F = 1; - // 椭圆中心 - double cx = (2 * C * D - B * E) / (B * B - 4 * A * C); - double cy = (2 * A * E - B * D) / (B * B - 4 * A * C); + // 椭圆中心 + double cx = (2 * C * D - B * E) / (B * B - 4 * A * C); + double cy = (2 * A * E - B * D) / (B * B - 4 * A * C); - // 半轴 - double term1 = 2 * (A * E * E + C * D * D - B * D * E + (B * B - 4 * A * C) * F); - double term2 = (A + C) + Math.Sqrt((A - C) * (A - C) + B * B); - double term3 = (A + C) - Math.Sqrt((A - C) * (A - C) + B * B); + // 半轴 + double term1 = 2 * (A * E * E + C * D * D - B * D * E + (B * B - 4 * A * C) * F); + double term2 = (A + C) + Math.Sqrt((A - C) * (A - C) + B * B); + double term3 = (A + C) - Math.Sqrt((A - C) * (A - C) + B * B); - double a = Math.Sqrt(Math.Abs(term1 / ((B * B - 4 * A * C) * term3))); - double b = Math.Sqrt(Math.Abs(term1 / ((B * B - 4 * A * C) * term2))); + double a = Math.Sqrt(Math.Abs(term1 / ((B * B - 4 * A * C) * term3))); + double b = Math.Sqrt(Math.Abs(term1 / ((B * B - 4 * A * C) * term2))); - if (a < b) (a, b) = (b, a); - double area = Math.PI * a * b; + if (a < b) (a, b) = (b, a); + double area = Math.PI * a * b; return (cx, cy, a, b, area); } diff --git a/头罩视野slove/头罩视野/Views/PageTest.xaml.cs b/头罩视野slove/头罩视野/Views/PageTest.xaml.cs index 09f8b45..a1395c4 100644 --- a/头罩视野slove/头罩视野/Views/PageTest.xaml.cs +++ b/头罩视野slove/头罩视野/Views/PageTest.xaml.cs @@ -42,7 +42,6 @@ namespace 头罩视野.Views private double currentAngle; #endregion - private double _leftTotalArea = 0; // 左目总视野面积 private double _rightTotalArea = 0; // 右目总视野面积 private double _binocularTotalArea = 0; // 双目总视野面积 @@ -69,13 +68,18 @@ namespace 头罩视野.Views MessageBox.Show("未连接"); return; } - } // 硬件固定参数(提前定义好,不要改) private const int LightsPerStrip = 81; // 单条灯条81个灯 private const int HalfLights = (LightsPerStrip - 1) / 2; // 40,给左右灯条用 + double stepAngle; + + bool isLeftOnly = false; + bool isRightOnly = false; + bool isBinocular = false; + protected readonly object _lock = new object(); private List<(int m, int n)> _lightPositions; private void InitLightPositions() { @@ -213,30 +217,11 @@ namespace 头罩视野.Views } //测试btn 测试按钮:读取数据,存入共享列表 private async void Button_Click_Test(object sender, RoutedEventArgs e) - { - ma.BtnClickFunction(Function.ButtonType.复归型, 100); ButtonTest.Content = "测试中...."; testTimer.Start(); - - - double stepAngle; - double.TryParse(fbspeed.Text.Trim(), out double val); - - // 范围判断:必须在 0 ~ 180 之间 - if (val <= 0 || val > 180) - { - stepAngle = 10.0; // 超出范围用默认值 - } - else - { - stepAngle = val; // 正常就用输入值 - } - // 1. 读取输入框 - // 分辨角度 例:10 - bool isLeftOnly = btnLeft.Content.ToString() == "左眼开" && btnRight.Content.ToString() == "右眼关"; bool isRightOnly = btnRight.Content.ToString() == "右眼开" && btnLeft.Content.ToString() == "左眼关"; bool isBinocular = btnLeft.Content.ToString() == "左眼开" && btnRight.Content.ToString() == "右眼开"; @@ -254,9 +239,133 @@ namespace 头罩视野.Views _binocularTotalArea = 0; } + } + //页面渲染值 + + public void UpdateVisionResults(double BotViAn) + { + zmsyarea.Text = _leftTotalArea.ToString("0.00"); // 左目 + ymsyarea.Text = _rightTotalArea.ToString("0.00"); // 右目 + smsyarea.Text = _binocularTotalArea.ToString("0.00"); // 双目 + + // 3. 计算空白区视野面积(双目时才有效) + if (double.TryParse(smsyarea.Text, out double binocularTotalArea)) + { + double blankArea = GetArea.GetBlankViewArea(binocularTotalArea); + kbsyarea.Text = blankArea.ToString("0.00"); // 空白视野面积 + } + //// 4. 计算下方视野面积 + int botViAnInt = (int)Math.Round(BotViAn); + xfsyarea.Text = botViAnInt.ToString("0.0"); // 下方视野 + + // 5. 计算视野保存率(双目) + if (double.TryParse(smsyarea.Text, out double totalAreaForRate)) + { + double binocularRate = GetArea.CalcVisionRate(totalAreaForRate); + sybhl.Text = binocularRate.ToString("0.00"); // 视野保存率 + } + } + + //读取灯泡的数据 + + private async Task ReadLightBarData() + { + // 无连接直接返回 + if (_modbusMaster == null || !ModbusHelper.TcpClient.Connected) + return; + + try + { + DataList.Clear(); + // 读取灯条寄存器(地址350,长度15) + ushort[] registers = await _modbusMaster.ReadHoldingRegistersAsync(1, 350, 15); + + // 每个 ushort 是16位,低位在前 + foreach (ushort reg in registers) + { + for (int bit = 0; bit < 16; bit++) + { + // 取第bit位的值:0或1 + int lightBit = (reg & (1 << bit)) != 0 ? 1 : 0; + lock (_lock) + { + DataList.Add(lightBit); + } + + + } + } + // 调试:打印转换后的数据长度 + System.Diagnostics.Debug.WriteLine($"灯条二进制数据总长度:{DataList.Count}"); + } + catch (Exception ex) + { + Console.WriteLine($"灯条数据读取失败:{ex.Message}"); + //DataList.Clear(); // 出错时清空数据 + } + } + private async void Timer_Tick(object sender, EventArgs e) + + { + //System.Diagnostics.Debug.WriteLine("定时器触发了!" + DateTime.Now); + // 调用你原来的读取方 + //await ReadAddr262DataAsync(); + await calCurrentangle(); + // 组装当前数据 + var data = new TestDataStore.TestRecord + { + Date = DateTime.Now.ToString("yyyy-MM-dd"), + Time = DateTime.Now.ToString("HH:mm:ss"), + LeftEyeArea = double.TryParse(zmsyarea.Text, out var l) ? l : 0, + RightEyeArea = double.TryParse(ymsyarea.Text, out var r) ? r : 0, + BinocularArea = double.TryParse(smsyarea.Text, out var b) ? b : 0, + LowerVision = double.TryParse(xfsyarea.Text, out var lv) ? lv : 0, + VisionRetentionRate = double.TryParse(sybhl.Text, out var vr) ? vr : 0 + }; + + + // ==================== 去重判断 ==================== + if (_lastRecord != null && + data.LeftEyeArea == _lastRecord.LeftEyeArea && + data.RightEyeArea == _lastRecord.RightEyeArea && + data.BinocularArea == _lastRecord.BinocularArea && + data.LowerVision == _lastRecord.LowerVision && + data.VisionRetentionRate == _lastRecord.VisionRetentionRate) + { + return; // 一样就不添加 + } + //原来存的数据清空 切换页面会清空 + //TestDataStore.Records.Clear(); + // 不一样 → 插入表格 + TestDataStore.AddNewRecord(data); + _lastRecord = data; + + + } + + + + private async Task calCurrentangle() + { + + double.TryParse(fbspeed.Text.Trim(), out double val); + + //// 范围判断:必须在 0 ~ 180 之间 + //if (val <= 0 || val > 180) + //{ + // stepAngle = 10.0; // 超出范围用默认值 + //} + //else + //{ + // stepAngle = val; // 正常就用输入值 + //} + // 1. 读取输入框 + // 分辨角度 例:10 + + // 2. 从0转到180,每 stepAngle 执行一次 - for (double current = stepAngle; current <= 180; current += stepAngle) + for (double current = stepAngle; current < 180; current += stepAngle) { await ReadLightBarData(); @@ -264,6 +373,10 @@ namespace 头罩视野.Views // 读完再转数组 int[] lightData = DataList.Cast().ToArray(); + if (lightData.Length == 0) + { + return; + } //开始计算视野面积 double singleArea = GetArea.CalculateEllipseArea(lightData, _lightPositions); @@ -298,103 +411,6 @@ namespace 头罩视野.Views // minBottomViewAngle = bottomViewAngle; // } } - - } - //页面渲染值 - - public void UpdateVisionResults(double BotViAn) - { - zmsyarea.Text = _leftTotalArea.ToString("0.00"); // 左目 - ymsyarea.Text = _rightTotalArea.ToString("0.00"); // 右目 - smsyarea.Text = _binocularTotalArea.ToString("0.00"); // 双目 - - // 3. 计算空白区视野面积(双目时才有效) - if (double.TryParse(smsyarea.Text, out double binocularTotalArea)) - { - double blankArea = GetArea.GetBlankViewArea(binocularTotalArea); - kbsyarea.Text = blankArea.ToString("0.00"); // 空白视野面积 - } - - //// 4. 计算下方视野面积 - int botViAnInt = (int)Math.Round(BotViAn); - xfsyarea.Text = botViAnInt.ToString("0.0"); // 下方视野 - - // 5. 计算视野保存率(双目) - if (double.TryParse(smsyarea.Text, out double totalAreaForRate)) - { - double binocularRate = GetArea.CalcVisionRate(totalAreaForRate); - sybhl.Text = binocularRate.ToString("0.00"); // 视野保存率 - } - } - - //读取灯泡的数据 - - private async Task ReadLightBarData() - { - // 无连接直接返回 - if (_modbusMaster == null || !ModbusHelper.TcpClient.Connected) - return; - - try - { - DataList.Clear(); - // 读取灯条寄存器(地址350,长度15) - ushort[] registers = await _modbusMaster.ReadHoldingRegistersAsync(1, 350, 15); - - // 每个 ushort 是16位,低位在前 - foreach (ushort reg in registers) - { - for (int bit = 0; bit < 16; bit++) - { - // 取第bit位的值:0或1 - int lightBit = (reg & (1 << bit)) != 0 ? 1 : 0; - DataList.Add(lightBit); - } - } - - // 调试:打印转换后的数据长度 - System.Diagnostics.Debug.WriteLine($"灯条二进制数据总长度:{DataList.Count}"); - } - catch (Exception ex) - { - Console.WriteLine($"灯条数据读取失败:{ex.Message}"); - //DataList.Clear(); // 出错时清空数据 - } - } - private async void Timer_Tick(object sender, EventArgs e) - - { - //System.Diagnostics.Debug.WriteLine("定时器触发了!" + DateTime.Now); - // 调用你原来的读取方 - //await ReadAddr262DataAsync(); - - // 组装当前数据 - var data = new TestDataStore.TestRecord - { - Date = DateTime.Now.ToString("yyyy-MM-dd"), - Time = DateTime.Now.ToString("HH:mm:ss"), - LeftEyeArea = double.TryParse(zmsyarea.Text, out var l) ? l : 0, - RightEyeArea = double.TryParse(ymsyarea.Text, out var r) ? r : 0, - BinocularArea = double.TryParse(smsyarea.Text, out var b) ? b : 0, - LowerVision = double.TryParse(xfsyarea.Text, out var lv) ? lv : 0, - VisionRetentionRate = double.TryParse(sybhl.Text, out var vr) ? vr : 0 - }; - - // ==================== 去重判断 ==================== - if (_lastRecord != null && - data.LeftEyeArea == _lastRecord.LeftEyeArea && - data.RightEyeArea == _lastRecord.RightEyeArea && - data.BinocularArea == _lastRecord.BinocularArea && - data.LowerVision == _lastRecord.LowerVision && - data.VisionRetentionRate == _lastRecord.VisionRetentionRate) - { - return; // 一样就不添加 - } - //原来存的数据清空 切换页面会清空 - //TestDataStore.Records.Clear(); - // 不一样 → 插入表格 - TestDataStore.AddNewRecord(data); - _lastRecord = data; } //打印 @@ -408,7 +424,7 @@ namespace 头罩视野.Views //试样测试 - private void TbTest_Checked(object sender, RoutedEventArgs e) + private async void TbTest_Checked(object sender, RoutedEventArgs e) { // 选中 → 试样测试 @@ -417,16 +433,16 @@ namespace 头罩视野.Views tbTest.Background = System.Windows.Media.Brushes.LightSkyBlue; System.Diagnostics.Debug.WriteLine($"1232312312"); - _modbusMaster.WriteSingleCoilAsync(1, 41, true); + await _modbusMaster.WriteSingleCoilAsync(1, 41, true); } - private void TbTest_Unchecked(object sender, RoutedEventArgs e) + private async void TbTest_Unchecked(object sender, RoutedEventArgs e) { // 取消 → 空白测试 tbTest.Content = "试样测试"; tbTest.Background = System.Windows.Media.Brushes.LightGray; - _modbusMaster.WriteSingleCoilAsync(1, 41, false); + await _modbusMaster.WriteSingleCoilAsync(1, 41, false); } private void Button_Click_home(object sender, RoutedEventArgs e) { @@ -470,7 +486,7 @@ namespace 头罩视野.Views //ReadAndUpdateFloatAsync(210 ,2, ymsyarea, "F2", " "), ReadAndUpdateFloatRangeAsync(200, 12, "F2", "°"), //ReadAndUpdateFloatAsync(424 ,2, kbsyarea, "F2", "cm²"), - ReadAndUpdateFloatAsync(310, 2, zdangle, "F2", "°/S"), + ReadAndUpdateFloatAsync(310, 2, zdangle, "F2", ""), //ReadAndUpdateFloatAsync(430 ,2, sybhl, "F2", " "), //前1从站地址,后1是长度 @@ -624,9 +640,6 @@ namespace 头罩视野.Views } - - - } }