Просмотр исходного кода

1、优化切换检测模式的逻辑
2、记录曲线的点击逻辑优化

LC 1 неделя назад
Родитель
Сommit
5e7d023681

+ 27 - 14
Core/TcpServerFrame.cs

@@ -106,10 +106,14 @@ namespace SWRIS.Core
             _connectedClients.Clear();
 
             // 关闭监听器
-            if (_listener != null && _listener.Connected)
+            if (_listener != null)
             {
-                _listener.Shutdown(SocketShutdown.Both);
-                _listener.Close();
+                try
+                {
+                    _listener.Close();
+                }
+                catch (SocketException) { }
+                catch (ObjectDisposedException) { }
                 _listener = null;
             }
         }
@@ -216,7 +220,7 @@ namespace SWRIS.Core
                     }
 
                     // 继续接收数据
-                    if (state.ClientSocket?.Connected == true)
+                    if (state.ClientSocket != null)
                     {
                         Array.Clear(state.Buffer, 0, state.Buffer.Length);
                         state.ClientSocket.BeginReceive(state.Buffer, 0, state.Buffer.Length,
@@ -856,7 +860,7 @@ namespace SWRIS.Core
         /// </summary>
         /// <param name="isOpen">是否开启</param>
         /// <param name="serialNo">设备序列号</param>
-        /// <param name="client"></param>
+        /// <param name="clientSocket"></param>
         /// <returns></returns>
         [Obfuscation(Feature = "virtualization", Exclude = false)]
         public bool SendTurnLiveStreamData(bool isOpen, string serialNo, Socket clientSocket)
@@ -906,8 +910,6 @@ namespace SWRIS.Core
                     ms.WriteByte(0x00); // 操作,0=获取判伤捻距系数,1=设置判伤捻距系数
                     WriteBytes(ms, 0); // 判伤捻距系数
                     byte[] buffer = CreateData(0x13, ms.ToArray(), serialNo);
-                    DebugMessageReceived?.Invoke(this,
-                        new DebugMessageReceivedEventArgs($"获取设备判伤捻距系数", ipAddress: ipAddress, serialNo: serialNo));
                     clientSocket.Send(buffer);
                     return true;
                 }
@@ -1201,10 +1203,13 @@ namespace SWRIS.Core
                 if (buffer[1] == 0x00)
                 {
                     byte[] response = new byte[] { 0xD0, 0x00 };
-                    clientSocket.Send(response); // 直接使用Socket发送
+                    clientSocket?.Send(response); // 直接使用Socket发送
                 }
             }
-            finally { }
+            // 设备刚断开时回复心跳会抛 SocketException(对端重置),
+            // 就地吞掉即可,后续接收回调会走正常断开流程
+            catch (SocketException) { }
+            catch (ObjectDisposedException) { }
         }
         private async Task ProcessSerialNoData(byte[] buffer, ClientState client)
         {
@@ -1468,16 +1473,24 @@ namespace SWRIS.Core
         }
         private void SafeCloseClient(ClientState client)
         {
+            if (client == null)
+                return;
             try
             {
                 ClientDisconnected?.Invoke(this, new ClientDisconnectedEventArgs(client.ClientSocket, client.IpAddress));
                 _connectedClients.TryRemove(client.IpAddress, out _);
-                if (client.ClientSocket?.Connected == true)
+                var socket = client.ClientSocket;
+                client.ClientSocket = null;
+                if (socket != null)
                 {
-                    client.ClientSocket?.Shutdown(SocketShutdown.Both);
-                    client.ClientSocket?.Close();
-                    client.ClientSocket?.Dispose();
-                    client.ClientSocket = null;
+                    try
+                    {
+                        socket.Shutdown(SocketShutdown.Both);
+                    }
+                    catch (SocketException) { }
+                    catch (ObjectDisposedException) { }
+                    socket.Close();
+                    socket.Dispose();
                 }
             }
             catch (Exception ex)

+ 3 - 2
Extensions/Tools.cs

@@ -183,9 +183,10 @@ namespace SWRIS.Extensions
                 var startInfo = new ProcessStartInfo
                 {
                     FileName = "netstat",
-                    Arguments = $"-ano | findstr :{port}",
+                    Arguments = "-ano",
                     UseShellExecute = false,
                     RedirectStandardOutput = true,
+                    RedirectStandardError = true,
                     CreateNoWindow = true
                 };
 
@@ -195,7 +196,7 @@ namespace SWRIS.Extensions
 
                     foreach (string line in output.Split('\n'))
                     {
-                        if (line.Contains($"TCP") && line.Contains($":{port}") && line.Contains("LISTENING"))
+                        if (line.Contains("TCP") && line.Contains($":{port}") && line.Contains("LISTENING"))
                         {
                             var parts = line.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
                             if (parts.Length >= 5 && int.TryParse(parts[4], out int pid))

+ 83 - 17
MainWindow.xaml.cs

@@ -19,6 +19,7 @@ using System.Threading.Tasks;
 using System.Windows;
 using System.Windows.Controls;
 using System.Windows.Markup;
+using System.Windows.Threading;
 using Tools = SWRIS.Extensions.Tools;
 namespace SWRIS
 {
@@ -29,7 +30,10 @@ namespace SWRIS
     {
         // 屏幕比例阈值
         private const double ASPECT_RATIO_THRESHOLD = 1.5; // 大于此值视为宽屏(16:9),小于此值视为方屏(4:3)
+        // 预状态(PreStop/PreRunning/PreCalibration)等待设备确认的超时阈值
+        private static readonly long PreStatusTimeoutTicks = TimeSpan.FromSeconds(10).Ticks;
         private readonly SpeechSynthesizer speech;
+        private readonly DispatcherTimer _preStatusTimer;
         public MainViewModel MainView { get; set; }
 
         private readonly IRecordRepository _recordRepository;
@@ -57,6 +61,7 @@ namespace SWRIS
             App.TcpServer.DetectionRawDataReceived += TcpServer_DetectionRawDataReceived;
             App.TcpServer.DetectionRawDataResultReceived += TcpServer_DetectionRawDataResultReceived;
             App.TcpServer.DetectionStatusResultReceived += TcpServer_DetectionStatusResultReceived;
+            App.TcpServer.SetAbsolutePositionDataReceived += TcpServer_SetAbsolutePositionDataReceived;
             App.TcpServer.DebugMessageReceived += TcpServer_DebugMessageReceived;
             App.TcpServer.HeartbeatReceived += TcpServer_HeartbeatReceived;
             App.TcpServer.UpgradedRequestResultReceived += TcpServer_UpgradedRequestResultReceived;
@@ -65,9 +70,32 @@ namespace SWRIS
 
             App.TcpServer.Start();
 
+            // 周期性检查预状态(PreStop/PreRunning/PreCalibration)是否超时未获设备确认,超时则回退
+            _preStatusTimer = new DispatcherTimer
+            {
+                Interval = TimeSpan.FromSeconds(2)
+            };
+            _preStatusTimer.Tick += PreStatusTimer_Tick;
+            _preStatusTimer.Start();
+
             DataContext = MainView;
         }
 
+        /// <summary>
+        /// 预状态超时检查:设备长时间未确认命令(检测状态结果),清除预状态位,回退为设备实际状态
+        /// </summary>
+        private void PreStatusTimer_Tick(object sender, EventArgs e)
+        {
+            foreach (var equipmentData in App.DataCenter.Equipments)
+            {
+                if (equipmentData.PreStatusTimestamp != 0 &&
+                    DateTime.UtcNow.Ticks - equipmentData.PreStatusTimestamp > PreStatusTimeoutTicks)
+                {
+                    equipmentData.ClearAllPreStatus();
+                }
+            }
+        }
+
         private async void TcpServer_LiveStreamReceived(object sender, LiveStreamReceivedEventArgs e)
         {
             var equipmentData = App.DataCenter.Equipments.FirstOrDefault(c => c.IpAddress == e.IpAddress);
@@ -155,8 +183,8 @@ namespace SWRIS
                         if (limitData.State == LimitState.AtLimit)
                         {
                             limit.State = LimitState.AtLimit;
-                            // 当前运行状态添加预标定状态
-                            equipment.RunningStatus |= RunningStatus.PreCalibration;
+                            // 当前运行状态添加预标定状态并记录挂起时间,超时未确认时由定时器回退
+                            equipment.MarkPreStatus(RunningStatus.PreCalibration);
                             if (App.TcpServer.SendRealTimeAbsolutePositionData(limit.SwitchIndex, (float)limitData.Position, equipment.SerialNo, equipment.ClientSocket))
                             {
                                 DebugMessageShow($"{limitData.Name}标定成功", equipment.IpAddress, equipment.SerialNo);
@@ -198,7 +226,7 @@ namespace SWRIS
 
         }
         /// <summary>
-        /// 接收检测状态开关结果
+        /// 接收检测状态开关结果(0x06 应答):确认 PreStop/PreRunning 预状态
         /// </summary>
         private void TcpServer_DetectionStatusResultReceived(object sender, DetectionStatusResultReceivedEventArgs e)
         {
@@ -207,23 +235,35 @@ namespace SWRIS
             {
                 if (equipmentData.RunningStatus.HasFlag(RunningStatus.PreStop))
                 {
-                    equipmentData.RunningStatus = RunningStatus.Stopped;
-                    Application.Current.Dispatcher.Invoke(() =>
-                    {
-                        NoticeBox.Show("自动检测停止", "通知", MessageBoxIcon.Warning, true, 3000);
-                    });
+                    // 按位合并,保留报警/故障等其他状态位
+                    equipmentData.ConfirmPreStatus(RunningStatus.PreStop, RunningStatus.Stopped);
+                    DebugMessageShow("自动检测停止", e.IpAddress, equipmentData.SerialNo);
                 }
                 else if (equipmentData.RunningStatus.HasFlag(RunningStatus.PreRunning))
                 {
-                    equipmentData.RunningStatus = RunningStatus.SpatialNormal;
-                    Application.Current.Dispatcher.Invoke(() =>
-                    {
-                        NoticeBox.Show("自动检测恢复", "通知", MessageBoxIcon.Success, true, 3000);
-                    });
+                    // 用启动命令时记录的请求模式恢复检测形式(时域/空域),避免使用切换前的旧模式位
+                    var mode = equipmentData.PreStatusRunningMode == RunningStatus.TemporalNormal
+                        ? RunningStatus.TemporalNormal : RunningStatus.SpatialNormal;
+                    var modeName = mode == RunningStatus.TemporalNormal ? "时域检测" : "空域检测";
+                    equipmentData.ConfirmPreStatus(RunningStatus.PreRunning, mode);
+                    DebugMessageShow($"自动检测开始,检测形式:{modeName}", e.IpAddress, equipmentData.SerialNo);
                 }
-                else if (equipmentData.RunningStatus.HasFlag(RunningStatus.PreCalibration))
+            }
+        }
+        /// <summary>
+        /// 接收设置实时位置(标定)应答(0x15):确认 PreCalibration 预状态
+        /// </summary>
+        private void TcpServer_SetAbsolutePositionDataReceived(object sender, SetAbsolutePositionDataReceivedEventArgs e)
+        {
+            var equipmentData = App.DataCenter.Equipments.FirstOrDefault(c => c.IpAddress == e.IpAddress);
+            if (equipmentData != null && e.SetAbsolutePositionData != null)
+            {
+                // 标定命令已获得设备应答(无论成败均视为确认),清除预标定位;
+                // 若还有其他挂起的预状态位则保留其超时时间戳
+                equipmentData.RunningStatus &= ~RunningStatus.PreCalibration;
+                if ((equipmentData.RunningStatus & (RunningStatus.PreStop | RunningStatus.PreRunning | RunningStatus.PreCalibration)) == 0)
                 {
-                    equipmentData.RunningStatus = RunningStatus.SpatialNormal;
+                    equipmentData.PreStatusTimestamp = 0;
                 }
             }
         }
@@ -246,9 +286,33 @@ namespace SWRIS
             {
                 equipmentData.Speed = Math.Round(e.RealTimeData.Speed * 60, 2);
                 equipmentData.Position = e.RealTimeData.Position;
-                equipmentData.RunningStatus = e.RealTimeData.Status;
+                var preStatusMask = RunningStatus.PreStop | RunningStatus.PreRunning | RunningStatus.PreCalibration;
+                // 设备实时状态已带预状态位 => 设备已确认命令并进入预状态,清除本地挂起时间戳,预状态以设备状态为准
+                if ((e.RealTimeData.Status & preStatusMask) != 0)
+                {
+                    equipmentData.PreStatusTimestamp = 0;
+                    equipmentData.RunningStatus = e.RealTimeData.Status;
+                }
+                else if (equipmentData.PreStatusTimestamp != 0)
+                {
+                    // 存在待设备确认的预状态且设备尚未确认:保留本地挂起的预状态位,等待确认或超时回退
+                    var pendingFlags = equipmentData.RunningStatus & preStatusMask;
+                    equipmentData.RunningStatus = e.RealTimeData.Status | pendingFlags;
+                }
+                else
+                {
+                    // 无挂起预状态:0x0B 只更新设备实际状态位
+                    equipmentData.RunningStatus = e.RealTimeData.Status;
+                }
                 equipmentData.AbsolutePosition = e.RealTimeData.AbsolutePosition;
-                equipmentData.LiftHeight = (equipmentData.RopeLength - equipmentData.AbsolutePosition) / equipmentData.LiftHightRatio;
+                if (equipmentData.RunningStatus == RunningStatus.SpatialNormal)
+                {
+                    equipmentData.LiftHeight = (equipmentData.RopeLength - equipmentData.AbsolutePosition) / equipmentData.LiftHightRatio;
+                }
+                else if (equipmentData.LiftHeight != 0)
+                {
+                    equipmentData.LiftHeight = 0;
+                }
                 equipmentData.Direction = equipmentData.Speed > 0 ? DirectionState.Forward :
                     (equipmentData.Speed == 0f ? DirectionState.Stoped : DirectionState.Reverse);
                 equipmentData.AddSpeedData(equipmentData.Speed);
@@ -295,6 +359,8 @@ namespace SWRIS
                 equipmentData.AbsolutePosition = 0;
                 equipmentData.Position = 0;
                 equipmentData.Direction = DirectionState.Stoped;
+                // 断线时清除挂起的预状态位,避免重连后残留影响状态判断
+                equipmentData.ClearAllPreStatus();
             }
         }
         /// <summary>

+ 57 - 0
Models/EquipmentDataModel.cs

@@ -138,6 +138,63 @@ namespace SWRIS.Models
             get => runningStatus;
             set { runningStatus = value; OnPropertyChanged(nameof(RunningStatus)); }
         }
+
+        /// <summary>
+        /// 预状态挂起时间戳(UtcNow.Ticks),0 表示无挂起的预状态
+        /// </summary>
+        public long PreStatusTimestamp { get; set; }
+
+        /// <summary>
+        /// 预运行请求的检测模式(TemporalNormal=时域,SpatialNormal=空域),用于 0x06 应答确认时恢复正确模式
+        /// </summary>
+        public RunningStatus PreStatusRunningMode { get; set; }
+
+        /// <summary>
+        /// 标记预状态位并记录挂起时间戳(本地先行、等待设备确认)
+        /// </summary>
+        public void MarkPreStatus(RunningStatus flag)
+        {
+            RunningStatus |= flag;
+            PreStatusTimestamp = DateTime.UtcNow.Ticks;
+        }
+
+        /// <summary>
+        /// 标记预运行状态并记录本次请求的检测模式(时域/空域),清除旧模式位避免切换时残留
+        /// </summary>
+        public void MarkPreRunning(RunningStatus mode)
+        {
+            RunningStatus = (RunningStatus & ~(RunningStatus.SpatialNormal | RunningStatus.TemporalNormal)) | mode | RunningStatus.PreRunning;
+            PreStatusRunningMode = mode;
+            PreStatusTimestamp = DateTime.UtcNow.Ticks;
+        }
+
+        /// <summary>
+        /// 设备已确认命令:按位清除对应预状态位并合并实际状态,保留报警/故障等其他状态位
+        /// </summary>
+        public void ConfirmPreStatus(RunningStatus preFlag, RunningStatus actualStatus)
+        {
+            RunningStatus = (RunningStatus & ~preFlag) | actualStatus;
+            if (preFlag == RunningStatus.PreRunning)
+            {
+                PreStatusRunningMode = 0;
+            }
+            // 仅当没有其他挂起的预状态位时才清零挂起时间戳,避免影响其他预状态的超时判断
+            if ((RunningStatus & (RunningStatus.PreStop | RunningStatus.PreRunning | RunningStatus.PreCalibration)) == 0)
+            {
+                PreStatusTimestamp = 0;
+            }
+        }
+
+        /// <summary>
+        /// 清除所有挂起的预状态位(用于断线、超时等场景)
+        /// </summary>
+        public void ClearAllPreStatus()
+        {
+            RunningStatus &= ~(RunningStatus.PreStop | RunningStatus.PreRunning | RunningStatus.PreCalibration);
+            PreStatusRunningMode = 0;
+            PreStatusTimestamp = 0;
+        }
+
         /// <summary>
         /// 运行方向
         /// </summary>

+ 19 - 1
Models/ViewModel/DamagesViewModel.cs

@@ -9,6 +9,7 @@ namespace SWRIS.Models.ViewModel
         private RecordDto _record;
         private ObservableCollection<SensorModel> _sensors;
         private bool _isSummaryActive = true;
+        private bool _isSensorSyncing;
         public RecordDto Record
         {
             get => _record;
@@ -43,18 +44,35 @@ namespace SWRIS.Models.ViewModel
                     _isSummaryActive = value;
                     OnPropertyChanged(nameof(IsSummaryActive));
 
-                    // 如果合值被选中,取消所有传感器的选中状态
                     if (_isSummaryActive)
                     {
+                        // 合值被选中:取消所有传感器的选中状态
                         foreach (var sensor in Sensors)
                         {
                             sensor.IsActive = false;
                         }
                     }
+                    else if (!_isSensorSyncing)
+                    {
+                        // 合值被取消(用户主动操作):自动选中所有传感器
+                        foreach (var sensor in Sensors)
+                        {
+                            sensor.IsActive = true;
+                        }
+                    }
                 }
             }
         }
 
+        /// <summary>
+        /// 标记当前是否由"选中传感器"联动取消合值,避免联动时反向自动选中所有传感器
+        /// </summary>
+        public bool IsSensorSyncing
+        {
+            get => _isSensorSyncing;
+            set => _isSensorSyncing = value;
+        }
+
         public event PropertyChangedEventHandler PropertyChanged;
         protected internal virtual void OnPropertyChanged(string propertyName)
         {

+ 11 - 0
Pages/DamagesPage.xaml.cs

@@ -5,6 +5,7 @@ using SWRIS.Extensions;
 using SWRIS.Models;
 using SWRIS.Models.ViewModel;
 using SWRIS.Repository;
+using System;
 using System.Collections.ObjectModel;
 using System.Linq;
 using System.Windows;
@@ -60,8 +61,11 @@ namespace SWRIS.Pages
                             {
                                 if (s is SensorModel sensor1 && sensor1 != null && sensor1.IsActive)
                                 {
+                                    // 标记由传感器联动取消合值,避免触发"取消合值自动选中所有传感器"
+                                    DamagesView.IsSensorSyncing = true;
                                     // 如果有传感器被选中,取消合值的选中状态
                                     DamagesView.IsSummaryActive = false;
+                                    DamagesView.IsSensorSyncing = false;
                                 }
                             }
                         };
@@ -100,12 +104,19 @@ namespace SWRIS.Pages
         }
         public (double[] Positions, ushort[,] Damages) ParseSensorData(byte[] data, int sensorCount, double startPosition, double endPosition)
         {
+            // 起止位置差值的绝对值过小时,采样步长趋近于0,位置轴退化,直接返回
+            if (Math.Abs(endPosition - startPosition) < 0.001)
+            {
+                LogHelper.Error($"起止位置差值过小({Math.Abs(endPosition - startPosition)}),跳过数据解析。");
+                return (null, null);
+            }
             // 检查数据长度是否能被传感器数量整除
             if (data.Length % sensorCount != 0)
             {
                 LogHelper.Error($"数据长度不匹配。数据长度 {data.Length} 字节不能被传感器数量 {sensorCount} 整除。");
                 return (null, null);
             }
+
             // 计算样本数量
             int sampleCount = data.Length / sensorCount;
             // 计算采样步长

+ 1 - 1
Pages/ParameterDialog.xaml

@@ -145,7 +145,7 @@
                             </StackPanel>
                             <StackPanel Orientation="Horizontal" Margin="0,7"  HorizontalAlignment="Right">
                                 <TextBlock Text="股丝" VerticalAlignment="Center"/>
-                                <TextBox Margin="10,0,8,0" Width="63" Text="{Binding Parameter.WireRopeStrandCount}"/>
+                                <TextBox Margin="15,0,8,0" Width="63" Text="{Binding Parameter.WireRopeStrandCount}"/>
                                 <TextBlock Text="*" VerticalAlignment="Center"/>
                                 <TextBox Margin="7,0,15,0" Width="64" Text="{Binding Parameter.WireRopeStrandWireCount}"/>
                             </StackPanel>

+ 7 - 4
Pages/ParameterDialog.xaml.cs

@@ -22,6 +22,7 @@ namespace SWRIS.Pages
     public partial class ParameterDialog : WindowX
     {
         private string _serialNo;
+        private string _ipAddress;
         private Socket _clientSocket;
         private byte _runningMode = 0;
         private EquipmentSettingViewModel Equipment { get; set; }
@@ -30,6 +31,7 @@ namespace SWRIS.Pages
         {
             InitializeComponent();
             _serialNo = equipment.SerialNo;
+            _ipAddress = equipment.IpAddress;
             _clientSocket = clientSocket;
             _runningMode = GetRunningStatusByte(runningStatus);
             Equipment = equipment.ToEquipmentSettingViewModel(runningStatus);
@@ -99,17 +101,18 @@ namespace SWRIS.Pages
             byte runningMode;
             if (runningStatus.HasFlag(RunningStatus.TemporalNormal))
             {
-                equipmentData.RunningStatus |= RunningStatus.PreRunning;
+                // 记录本次请求的检测模式,0x06 应答确认时据此恢复正确模式
+                equipmentData.MarkPreRunning(RunningStatus.TemporalNormal);
                 runningMode = (byte)RunningStatus.TemporalNormal;
             }
             else if (runningStatus.HasFlag(RunningStatus.SpatialNormal))
             {
-                equipmentData.RunningStatus |= RunningStatus.PreRunning;
+                equipmentData.MarkPreRunning(RunningStatus.SpatialNormal);
                 runningMode = (byte)RunningStatus.SpatialNormal;
             }
             else
             {
-                equipmentData.RunningStatus |= RunningStatus.PreStop;
+                equipmentData.MarkPreStatus(RunningStatus.PreStop);
                 runningMode = (byte)RunningStatus.Stopped;
             }
 
@@ -206,7 +209,7 @@ namespace SWRIS.Pages
                     if (_runningMode != newMode)
                     {
                         _runningMode = newMode;
-                        var equipmentData = App.DataCenter.Equipments.FirstOrDefault(c => c.SerialNo == _serialNo);
+                        var equipmentData = App.DataCenter.Equipments.FirstOrDefault(c => c.SerialNo == _serialNo || c.IpAddress == _ipAddress);
                         if (equipmentData != null)
                         {
                             StartOrStopDetection(equipmentData, runningStatus);

+ 10 - 1
README.md

@@ -1,6 +1,15 @@
 # 在线钢丝绳检测系统升级记录
+## 版本 1.2.0 - 2026-8-26
 
-## 版本 1.2.1 - 2026-8-17
+### 新增功能
+- **优化实时曲线**
+  - 修复从其它页面切换至实时页面时,补绘实时曲线时重叠的问题。
+- **增加实时数值显示**
+  - 在设备底部调试信息的右侧增加传感器实时数据显示,50毫秒刷新一次,从接收数据中取值。
+- **修改检测模式**
+  - 去掉原来的停止检测按钮,优化点击事件,避免重复发送指令。
+  - 时域检测时起升高度保持为0m
+## 版本 1.2.0 - 2026-8-17
 
 ### 新增功能
 - **修改参数修改逻辑**