wangchong пре 3 месеци
комит
e24ab3a418
100 измењених фајлова са 10411 додато и 0 уклоњено
  1. 125 0
      Config/Config.json
  2. 25 0
      Controllers/RecordController.cs
  3. 59 0
      Controls/DamageRangeControl.xaml
  4. 78 0
      Controls/DamageRangeControl.xaml.cs
  5. 111 0
      Controls/DamageRecordControl.xaml
  6. 49 0
      Controls/DamageRecordControl.xaml.cs
  7. 38 0
      Controls/NotificationBar.xaml
  8. 47 0
      Controls/NotificationBar.xaml.cs
  9. 14 0
      Controls/RealTimeLineChart.xaml
  10. 343 0
      Controls/RealTimeLineChart.xaml.cs
  11. 14 0
      Controls/RecordLineChart.xaml
  12. 236 0
      Controls/RecordLineChart.xaml.cs
  13. 102 0
      Controls/RopeEquipmentControl.xaml
  14. 212 0
      Controls/RopeEquipmentControl.xaml.cs
  15. 20 0
      Converters/BoolToVisibilityConverter.cs
  16. 19 0
      Converters/BooleanInverterConverter.cs
  17. 22 0
      Converters/CollectionCountToVisibilityConverter.cs
  18. 93 0
      Converters/ColorToHoverBackgroundConverter.cs
  19. 36 0
      Converters/ConnectivityFaultToColorConverter.cs
  20. 25 0
      Converters/ConnectivityToVisibilityConverter.cs
  21. 46 0
      Converters/DamageLevelToColorConverter.cs
  22. 21 0
      Converters/DamageLevelToTextConverter.cs
  23. 26 0
      Converters/DirectionStateToBooleanConverter.cs
  24. 39 0
      Converters/EncoderDirectionToBooleanConverter.cs
  25. 54 0
      Converters/LimitStateToColorConverter.cs
  26. 38 0
      Converters/MenuNameToColorConverter.cs
  27. 27 0
      Converters/MenuNameToPositionConverter.cs
  28. 33 0
      Converters/ModuleTypeToVisibilityConverter.cs
  29. 23 0
      Converters/NullToBooleanConverter.cs
  30. 20 0
      Converters/NullToVisibilityConverter.cs
  31. 65 0
      Converters/PercentageConverter.cs
  32. 39 0
      Converters/ResetTypeToBooleanConverter.cs
  33. 63 0
      Converters/RiskLevelToColorConverter.cs
  34. 27 0
      Converters/RiskLevelToTextConverter.cs
  35. 25 0
      Converters/StringEmptyToVisibilityConverter.cs
  36. 35 0
      Converters/StringToColorConverter.cs
  37. 24 0
      Converters/StringToImageSourceConverter.cs
  38. 273 0
      Core/CalibrationCore.cs
  39. 166 0
      Core/ConfigHelper.cs
  40. 1714 0
      Core/TcpServerFrame.cs
  41. 94 0
      Dtos/DamageDto.cs
  42. 28 0
      Dtos/GetRecordsInputDto.cs
  43. 36 0
      Dtos/KeyAndValueDto.cs
  44. 310 0
      Dtos/RecordDto.cs
  45. 14 0
      Dtos/RecordIdAnDataFilePathDto.cs
  46. 17 0
      Dtos/SimpleRecordDto.cs
  47. 18 0
      Enums/AlarmSourceType.cs
  48. 14 0
      Enums/AlarmType.cs
  49. 44 0
      Enums/DamageLevel.cs
  50. 26 0
      Enums/DataFormat.cs
  51. 23 0
      Enums/DirectionState.cs
  52. 19 0
      Enums/EncoderDirection.cs
  53. 37 0
      Enums/ExecutionState.cs
  54. 14 0
      Enums/FaultState.cs
  55. 15 0
      Enums/FaultType.cs
  56. 28 0
      Enums/LayType.cs
  57. 9 0
      Enums/LimitState.cs
  58. 24 0
      Enums/ModuleType.cs
  59. 27 0
      Enums/ResetType.cs
  60. 94 0
      Enums/RiskLevel.cs
  61. 19 0
      Enums/RopeCoreType.cs
  62. 60 0
      Enums/RunningStatus.cs
  63. 18 0
      Enums/WireMaterialType.cs
  64. 14 0
      Enums/WireSurfaceType.cs
  65. 16 0
      Events/AlarmDataReceivedEventArgs.cs
  66. 21 0
      Events/ClientConnectedEventArgs.cs
  67. 16 0
      Events/ClientDisconnectedEventArgs.cs
  68. 16 0
      Events/ClockResultReceivedEventArgs.cs
  69. 19 0
      Events/DebugMessageReceivedEventArgs.cs
  70. 16 0
      Events/DetectionDataReceivedEventArgs.cs
  71. 17 0
      Events/DetectionRawDataReceivedEventArgs.cs
  72. 17 0
      Events/DetectionRawDataResultReceivedEventArgs.cs
  73. 16 0
      Events/DetectionStatusResultReceivedEventArgs.cs
  74. 17 0
      Events/EncoderDirectionResultReceivedEventArgs.cs
  75. 16 0
      Events/FaultDataReceivedEventArgs.cs
  76. 14 0
      Events/HeartbeatReceviedEventArgs.cs
  77. 16 0
      Events/LiveStreamReceivedEventArgs.cs
  78. 16 0
      Events/RealTimeDataReceivedEventArgs.cs
  79. 17 0
      Events/ResetSystemDataReceivedEventArgs.cs
  80. 21 0
      Events/SetAbsolutePositionDataReceivedEventArgs.cs
  81. 16 0
      Events/UpgradedResultReceivedEventArgs.cs
  82. 591 0
      Extensions/ByteTransform.cs
  83. 81 0
      Extensions/DatFileHandler.cs
  84. 623 0
      Extensions/FileHelper.cs
  85. 154 0
      Extensions/FirewallHelper.cs
  86. 91 0
      Extensions/JsonCryptoHelper.cs
  87. 60 0
      Extensions/LogHelper.cs
  88. 309 0
      Extensions/ReportPdfExporter.cs
  89. 171 0
      Extensions/SimpleLogger.cs
  90. 199 0
      Extensions/SingleInstanceManager.cs
  91. 195 0
      Extensions/SoftAuth.cs
  92. 1628 0
      Extensions/SoftBasic.cs
  93. 255 0
      Extensions/Tools.cs
  94. 284 0
      Extensions/TypeExtension.cs
  95. 55 0
      Extensions/WinHelper.cs
  96. BIN
      Fonts/Alibaba-PuHuiTi-Bold.ttf
  97. BIN
      Fonts/Alibaba-PuHuiTi-Heavy.ttf
  98. BIN
      Fonts/Alibaba-PuHuiTi-Light.ttf
  99. BIN
      Fonts/Alibaba-PuHuiTi-Medium.ttf
  100. BIN
      Fonts/Alibaba-PuHuiTi-Regular.ttf

+ 125 - 0
Config/Config.json

@@ -0,0 +1,125 @@
+{
+  "AppName": "在线式钢丝绳电磁检测系统",
+  "Version": "250902",
+  "Copyright": "河南恒达机电设备有限公司",
+  "IpAddress": "192.168.1.233",
+  "Port": 9005,
+  "DataSaveDays": 120,
+  "CleanScheduledTime": "02:00:00",
+  "DamageNearPointCount": 20,
+  "ShowDebugMessage": true,
+  "SoundRiskLevel": 1,
+  "SpeedVariationThreshold": 0.25,
+  "DamageExtent": 0.5,
+  "AlarmValidCount": 0,
+  "Crane": "炼钢一跨1#",
+  "Calibration": {
+    "ModuleType": "S1200",
+    "Limits": [
+      {
+        "Name": "上预限",
+        "Address": "I8.0",
+        "Position": 0.0,
+        "IsEnable": true,
+        "RelatedRopes": [ 1, 2 ]
+      }
+    ],
+    "Tcp": {
+      "IpAddress": "192.168.1.127",
+      "Port": 102,
+      "Station": 1
+    },
+    "Serial": {
+      "PortName": "COM1",
+      "BaudRate": 9600,
+      "DataBits": 8,
+      "Parity": "None",
+      "StopBits": 1,
+      "Station": 1
+    }
+  },
+  "Equipments": [
+    {
+      "RopeNumber": 2,
+      "RopeName": "起升钢丝绳3#",
+      "IpAddress": "192.168.1.127",
+      "SerialNo": "2509002",
+      "Supplier": "宁夏恒力",
+      "RopeCoreType": 1,
+      "WireMaterialType": 1,
+      "WireSurfaceType": 1,
+      "LiftHightRatio": 2,
+      "LayType": 1,
+      "DisableAlarm": false,
+      "InUseSensor": [ 1, 2, 3, 4 ],
+      "Parameter": {
+        "MainBoardSoftwareVersion": "2.07.01 1.1.0",
+        "SensorCount": 4,
+        "SamplingStep": 1.1275,
+        "FrequencyDivisionFactor": 1,
+        "TimeDomainFrequency": 1000,
+        "DamageThreshold": 0,
+        "ScrapUpperLimit": 10,
+        "FrontMagnetLength": 200,
+        "BackMagnetLength": 200,
+        "ValueCoefficient": 1.0,
+        "EffectiveStrokeLength": 2.0,
+        "ZeroPositionCorrectionDuration": 10,
+        "ZeroPositionCorrectionOffset": 50,
+        "WarningValue": 3.0,
+        "AlarmValue": 6.0,
+        "SoundLightAlarmAutoResetMode": -1,
+        "WireRopeType": 0,
+        "WireRopeCount": 1,
+        "WireRopeLength": 32.0,
+        "WireRopeDiameter": 20.0,
+        "WireRopeStrandCount": 6,
+        "WireRopeStrandWireCount": 19,
+        "EncoderDirection": 0,
+        "SystemTime": null,
+        "TwistFactor": 0.0
+      }
+    },
+    {
+      "RopeNumber": 1,
+      "RopeName": "起升钢丝绳1#",
+      "IpAddress": "192.168.1.241",
+      "SerialNo": "2509001",
+      "Supplier": "宁夏恒力",
+      "RopeCoreType": 1,
+      "WireMaterialType": 1,
+      "WireSurfaceType": 1,
+      "LiftHightRatio": 2,
+      "LayType": 1,
+      "DisableAlarm": false,
+      "InUseSensor": [ 1, 2, 3 ],
+      "Parameter": {
+        "MainBoardSoftwareVersion": "2.07.01 1.1.0",
+        "SensorCount": 4,
+        "SamplingStep": 1.1275,
+        "FrequencyDivisionFactor": 1,
+        "TimeDomainFrequency": 1000,
+        "DamageThreshold": 0,
+        "ScrapUpperLimit": 10,
+        "FrontMagnetLength": 200,
+        "BackMagnetLength": 200,
+        "ValueCoefficient": 1.0,
+        "EffectiveStrokeLength": 2.0,
+        "ZeroPositionCorrectionDuration": 10,
+        "ZeroPositionCorrectionOffset": 50,
+        "WarningValue": 3.0,
+        "AlarmValue": 6.0,
+        "SoundLightAlarmAutoResetMode": -1,
+        "WireRopeType": 0,
+        "WireRopeCount": 1,
+        "WireRopeLength": 24,
+        "WireRopeDiameter": 20.0,
+        "WireRopeStrandCount": 6,
+        "WireRopeStrandWireCount": 19,
+        "EncoderDirection": 0,
+        "SystemTime": null,
+        "TwistFactor": 0.0
+      }
+    }
+  ]
+}

+ 25 - 0
Controllers/RecordController.cs

@@ -0,0 +1,25 @@
+using System.Web.Http;
+
+namespace SWRIS.Controllers
+{
+    [RoutePrefix("api/record")]
+    public class RecordController : ApiController
+    {
+        [HttpGet]
+        [Route("getAll")]
+        public IHttpActionResult GetAll()
+        {
+            return Json(new
+            {
+                Id = 1,
+                Name = "Name"
+            });
+        }
+        [HttpGet]
+        [Route("get")]
+        public IHttpActionResult Get(int id)
+        {
+            return null;
+        }
+    }
+}

+ 59 - 0
Controls/DamageRangeControl.xaml

@@ -0,0 +1,59 @@
+<UserControl x:Class="SWRIS.Controls.DamageRangeControl"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+             xmlns:local="clr-namespace:SWRIS.Controls"
+             mc:Ignorable="d" 
+             d:DesignHeight="50" d:DesignWidth="1100">
+    <Grid>
+        <Grid.RowDefinitions>
+            <RowDefinition Height="*"/>
+            <RowDefinition Height="15"/>
+        </Grid.RowDefinitions>
+        <Border CornerRadius="5" Grid.Row="0">
+            <Border.Background>
+                <LinearGradientBrush StartPoint="0,0.5" EndPoint="1,0.5">
+                    <GradientStop Color="#3300FF78" Offset="0"/>
+                    <GradientStop Color="#33FF9600" Offset="0.33"/>
+                    <GradientStop Color="#33FF0000" Offset="0.66"/>
+                    <GradientStop Color="#33FF008A" Offset="1"/>
+                </LinearGradientBrush>
+            </Border.Background>
+            <UniformGrid Columns="6">
+                <TextBlock FontWeight="Regular" VerticalAlignment="Center" HorizontalAlignment="Center">
+                    <Run Text="轻微"/>
+                    <Run Text="{Binding MildCount, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
+                </TextBlock>
+                <TextBlock FontWeight="Regular" VerticalAlignment="Center" HorizontalAlignment="Center">
+                    <Run Text="轻度"/>
+                    <Run Text="{Binding LightCount, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
+                </TextBlock>
+                <TextBlock FontWeight="Regular" VerticalAlignment="Center" HorizontalAlignment="Center">
+                    <Run Text="中度"/>
+                    <Run Text="{Binding ModerateCount, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
+                </TextBlock>
+                <TextBlock FontWeight="Regular" VerticalAlignment="Center" HorizontalAlignment="Center">
+                    <Run Text="较重"/>
+                    <Run Text="{Binding SevereCount, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
+                </TextBlock>
+                <TextBlock FontWeight="Regular" VerticalAlignment="Center" HorizontalAlignment="Center">
+                    <Run Text="严重"/>
+                    <Run Text="{Binding CriticalCount, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
+                </TextBlock>
+                <TextBlock FontWeight="Regular" VerticalAlignment="Center" HorizontalAlignment="Center">
+                    <Run Text="超限"/>
+                    <Run Text="{Binding ExtremeCount, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
+                </TextBlock>
+            </UniformGrid>
+        </Border>
+        <UniformGrid Columns="6" Margin="0,5" Grid.Row="1">
+            <Rectangle Height="5" Fill="#00FF78" RadiusX="2.5" RadiusY="2.5" Margin="0,0,3,0"/>
+            <Rectangle Height="5" Fill="#FFF000" RadiusX="2.5" RadiusY="2.5" Margin="3,0,3,0"/>
+            <Rectangle Height="5" Fill="#FF9400" RadiusX="2.5" RadiusY="2.5" Margin="3,0,3,0"/>
+            <Rectangle Height="5" Fill="#FF6C00" RadiusX="2.5" RadiusY="2.5" Margin="3,0,3,0"/>
+            <Rectangle Height="5" Fill="#FF0000" RadiusX="2.5" RadiusY="2.5" Margin="3,0,3,0"/>
+            <Rectangle Height="5" Fill="#FF008A" RadiusX="2.5" RadiusY="2.5" Margin="3,0,0,0"/>
+        </UniformGrid>
+    </Grid>
+</UserControl>

+ 78 - 0
Controls/DamageRangeControl.xaml.cs

@@ -0,0 +1,78 @@
+using System.Windows;
+using System.Windows.Controls;
+
+namespace SWRIS.Controls
+{
+    /// <summary>
+    /// DamageRangeControl.xaml 的交互逻辑
+    /// </summary>
+    public partial class DamageRangeControl : UserControl
+    {
+        public DamageRangeControl()
+        {
+            InitializeComponent();
+        }
+        #region 依赖属性
+        public static readonly DependencyProperty MildCountProperty =
+           DependencyProperty.Register("MildCount", typeof(string), typeof(DamageRangeControl), new PropertyMetadata("0"));
+
+        public static readonly DependencyProperty LightCountProperty =
+            DependencyProperty.Register("LightCount", typeof(string), typeof(DamageRangeControl), new PropertyMetadata("0"));
+
+        public static readonly DependencyProperty ModerateCountProperty =
+            DependencyProperty.Register("ModerateCount", typeof(string), typeof(DamageRangeControl), new PropertyMetadata("0"));
+
+        public static readonly DependencyProperty SevereCountProperty =
+            DependencyProperty.Register("SevereCount", typeof(string), typeof(DamageRangeControl), new PropertyMetadata("0"));
+
+        public static readonly DependencyProperty CriticalCountProperty =
+            DependencyProperty.Register("CriticalCount", typeof(string), typeof(DamageRangeControl), new PropertyMetadata("0"));
+
+        public static readonly DependencyProperty ExtremeCountProperty =
+            DependencyProperty.Register("ExtremeCount", typeof(string), typeof(DamageRangeControl), new PropertyMetadata("0"));
+
+
+        #endregion
+
+        #region 属性包装器
+
+        public string MildCount
+        {
+            get { return (string)GetValue(MildCountProperty); }
+            set { SetValue(MildCountProperty, value); }
+        }
+
+        public string LightCount
+        {
+            get { return (string)GetValue(LightCountProperty); }
+            set { SetValue(LightCountProperty, value); }
+        }
+
+        public string ModerateCount
+        {
+            get { return (string)GetValue(ModerateCountProperty); }
+            set { SetValue(ModerateCountProperty, value); }
+        }
+
+        public string SevereCount
+        {
+            get { return (string)GetValue(SevereCountProperty); }
+            set { SetValue(SevereCountProperty, value); }
+        }
+
+        public string CriticalCount
+        {
+            get { return (string)GetValue(CriticalCountProperty); }
+            set { SetValue(CriticalCountProperty, value); }
+        }
+
+        public string ExtremeCount
+        {
+            get { return (string)GetValue(ExtremeCountProperty); }
+            set { SetValue(ExtremeCountProperty, value); }
+        }
+
+        #endregion
+    }
+}
+

+ 111 - 0
Controls/DamageRecordControl.xaml

@@ -0,0 +1,111 @@
+<UserControl x:Class="SWRIS.Controls.DamageRecordControl"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+             xmlns:local="clr-namespace:SWRIS.Controls" xmlns:cvt="clr-namespace:SWRIS.Converters"
+             mc:Ignorable="d" 
+             d:DesignHeight="450" d:DesignWidth="800">
+    <UserControl.Resources>
+        <cvt:RiskLevelToColorConverter x:Key="RiskLevelToColorConverter"/>
+        <cvt:RiskLevelToTextConverter x:Key="RiskLevelToTextConverter"/>
+        <Storyboard x:Key="NewItemGlowAnimation" RepeatBehavior="3x">
+            <DoubleAnimation
+                Storyboard.TargetProperty="Effect.Opacity"
+                From="0" To="0.7" Duration="0:0:0.20"
+                AutoReverse="True"/>
+        </Storyboard>
+    </UserControl.Resources>
+    <Border CornerRadius="10" Background="#1e1d43">
+        <StackPanel Orientation="Vertical">
+            <Border CornerRadius="10">
+                <Border.Background>
+                    <LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">
+                        <GradientStop Color="#1f1e44" Offset="0"/>
+                        <GradientStop Color="#36366d" Offset="1"/>
+                    </LinearGradientBrush>
+                </Border.Background>
+                <TextBlock Text="{Binding Title, RelativeSource={RelativeSource AncestorType=UserControl}}" 
+                          HorizontalAlignment="Center" FontSize="22" FontFamily="{StaticResource PuHuiTiBold}" FontWeight="Bold" Margin="14" />
+            </Border>
+            <ScrollViewer MaxHeight="842" VerticalScrollBarVisibility="Hidden" Margin="25,8">
+                <StackPanel Orientation="Vertical">
+                    <ItemsControl ItemsSource="{Binding Records, RelativeSource={RelativeSource AncestorType=UserControl}}">
+                        <ItemsControl.ItemTemplate>
+                            <DataTemplate>
+                                <Border Background="{Binding RiskLevel, Converter={StaticResource RiskLevelToColorConverter}, ConverterParameter=0.15}" 
+                                    CornerRadius="10" Height="110" Width="295" Margin="0,7">
+                                    <Border.Effect>
+                                        <DropShadowEffect x:Name="GlowEffect" 
+                                                          ShadowDepth="0"
+                                                          BlurRadius="1"
+                                                          Color="#00ffff"
+                                                          Opacity="0"/>
+                                    </Border.Effect>
+                                    <!-- 新项发光触发器 -->
+                                    <Border.Style>
+                                        <Style TargetType="Border">
+                                            <Style.Triggers>
+                                                <DataTrigger Binding="{Binding IsNew}" Value="True">
+                                                    <DataTrigger.EnterActions>
+                                                        <BeginStoryboard Storyboard="{StaticResource NewItemGlowAnimation}"/>
+                                                    </DataTrigger.EnterActions>
+                                                </DataTrigger>
+                                            </Style.Triggers>
+                                        </Style>
+                                    </Border.Style>
+
+                                    <!-- 鼠标悬停触发器 -->
+                                    <Border.Triggers>
+                                        <EventTrigger RoutedEvent="MouseEnter">
+                                            <BeginStoryboard>
+                                                <Storyboard>
+                                                    <DoubleAnimation
+                                                        Storyboard.TargetProperty="Effect.Opacity"
+                                                        From="0" To="0.7" Duration="0:0:0.20"
+                                                        AutoReverse="True" RepeatBehavior="1x"/>
+                                                </Storyboard>
+                                            </BeginStoryboard>
+                                        </EventTrigger>
+                                        <EventTrigger RoutedEvent="MouseLeave">
+                                            <BeginStoryboard>
+                                                <Storyboard>
+                                                    <DoubleAnimation
+                                                        Storyboard.TargetProperty="Effect.Opacity"
+                                                        To="0" Duration="0:0:0.1"/>
+                                                </Storyboard>
+                                            </BeginStoryboard>
+                                        </EventTrigger>
+                                    </Border.Triggers>
+                                    <StackPanel Orientation="Horizontal">
+                                        <!-- 风险等级指示器 -->
+                                        <Border CornerRadius="10" Width="35" Height="110"
+                                            Background="{Binding RiskLevel, Converter={StaticResource RiskLevelToColorConverter}, ConverterParameter=0.8}">
+                                            <StackPanel Orientation="Vertical" VerticalAlignment="Center" HorizontalAlignment="Center">
+                                                <TextBlock FontSize="18" FontWeight="Bold" FontFamily="{StaticResource PuHuiTiBold}"
+                                                       Text="{Binding RiskLevel, Converter={StaticResource RiskLevelToTextConverter}, ConverterParameter=0}"/>
+                                                <TextBlock FontSize="18" FontWeight="Bold" FontFamily="{StaticResource PuHuiTiBold}"
+                                                       Text="{Binding RiskLevel, Converter={StaticResource RiskLevelToTextConverter}, ConverterParameter=1}"/>
+                                            </StackPanel>
+                                        </Border>
+                                        <!-- 内容区域 -->
+                                        <StackPanel Orientation="Vertical" VerticalAlignment="Center" Margin="18,0" Tag="{Binding Id}"
+                                                    MouseLeftButtonDown="Record_MouseDown" Cursor="Hand">
+                                            <TextBlock Text="{Binding RopeName}" FontSize="18" FontWeight="Bold" FontFamily="{StaticResource PuHuiTiBold}"
+                                                       Margin="0,5,0,8"
+                                                   Foreground="{Binding RiskLevel, Converter={StaticResource RiskLevelToColorConverter}}"/>
+                                            <TextBlock Text="{Binding Time, StringFormat='yyyy/MM/dd HH:mm:ss'}" FontSize="18" 
+                                                   Foreground="{Binding RiskLevel, Converter={StaticResource RiskLevelToColorConverter}}"/>
+                                            <TextBlock Text="{Binding Description}" FontSize="18" Margin="0,0,0,3"
+                                                   Foreground="{Binding RiskLevel, Converter={StaticResource RiskLevelToColorConverter}}"/>
+                                        </StackPanel>
+                                    </StackPanel>
+                                </Border>
+                            </DataTemplate>
+                        </ItemsControl.ItemTemplate>
+                    </ItemsControl>
+                </StackPanel>
+            </ScrollViewer>
+        </StackPanel>
+    </Border>
+</UserControl>

+ 49 - 0
Controls/DamageRecordControl.xaml.cs

@@ -0,0 +1,49 @@
+using System;
+using System.Collections;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+
+namespace SWRIS.Controls
+{
+    /// <summary>
+    /// RecordControl.xaml 的交互逻辑
+    /// </summary>
+    public partial class DamageRecordControl : UserControl
+    {
+        public DamageRecordControl()
+        {
+            InitializeComponent();
+        }
+        public static readonly DependencyProperty RecordsProperty =
+        DependencyProperty.Register("Records", typeof(IEnumerable), typeof(DamageRecordControl),
+            new PropertyMetadata(null));
+
+        public static readonly DependencyProperty TitleProperty =
+            DependencyProperty.Register("Title", typeof(string), typeof(DamageRecordControl),
+                new PropertyMetadata("检测信息"));
+
+        public IEnumerable Records
+        {
+            get => (IEnumerable)GetValue(RecordsProperty);
+            set => SetValue(RecordsProperty, value);
+        }
+
+        public string Title
+        {
+            get => (string)GetValue(TitleProperty);
+            set => SetValue(TitleProperty, value);
+        }
+
+        private void Record_MouseDown(object sender, MouseButtonEventArgs e)
+        {
+            if (e.ClickCount == 2)
+            {
+                if (sender is StackPanel panel && int.TryParse(panel.Tag?.ToString(), out int recordId))
+                {
+                    (Application.Current.MainWindow as MainWindow).NavigatePage(new Uri($"Pages/DamagesPage.xaml?id=" + recordId, uriKind: UriKind.Relative));
+                }
+            }
+        }
+    }
+}

+ 38 - 0
Controls/NotificationBar.xaml

@@ -0,0 +1,38 @@
+<UserControl x:Class="SWRIS.Controls.NotificationBar"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+             xmlns:local="clr-namespace:SWRIS.Controls" 
+             xmlns:cvt="clr-namespace:SWRIS.Converters"
+             mc:Ignorable="d" x:Name="Root"
+             d:DesignHeight="50" d:DesignWidth="800">
+    <UserControl.Resources>
+        <cvt:CollectionCountToVisibilityConverter x:Key="CollectionCountToVisibilityConverter"/>
+    </UserControl.Resources>
+    <Grid>
+        <Border Background="#1AFF0000"
+            CornerRadius="{Binding ElementName=Root,Path=CornerRadius}" 
+            Padding="{Binding ElementName=Root,Path=TextPadding}"
+            Visibility="{Binding Messages.Count, Converter={StaticResource CollectionCountToVisibilityConverter}}">
+            <ItemsControl ItemsSource="{Binding Messages}">
+                <ItemsControl.ItemsPanel>
+                    <ItemsPanelTemplate>
+                        <StackPanel Orientation="Horizontal" HorizontalAlignment="Left"/>
+                    </ItemsPanelTemplate>
+                </ItemsControl.ItemsPanel>
+                <ItemsControl.ItemTemplate>
+                    <DataTemplate>
+                        <StackPanel Orientation="Horizontal" Margin="5 0">
+                            <Grid Width="22" Height="22" Margin="0 0 8 0">
+                                <Ellipse Fill="#4DFF2626" Width="22" Height="22"/>
+                                <Ellipse Fill="#FF2626" Width="10" Height="10" HorizontalAlignment="Center" VerticalAlignment="Center"/>
+                            </Grid>
+                            <TextBlock Text="{Binding}" Foreground="#FF0000" VerticalAlignment="Center"/>
+                        </StackPanel>
+                    </DataTemplate>
+                </ItemsControl.ItemTemplate>
+            </ItemsControl>
+        </Border>
+    </Grid>
+</UserControl>

+ 47 - 0
Controls/NotificationBar.xaml.cs

@@ -0,0 +1,47 @@
+using System.Collections;
+using System.Windows;
+using System.Windows.Controls;
+
+namespace SWRIS.Controls
+{
+    /// <summary>
+    /// NotificationBar.xaml 的交互逻辑
+    /// </summary>
+    public partial class NotificationBar : UserControl
+    {
+        public NotificationBar()
+        {
+            InitializeComponent();
+        }
+
+        public static readonly DependencyProperty MessagesProperty =
+           DependencyProperty.Register("Messages", typeof(IEnumerable), typeof(NotificationBar),
+               new PropertyMetadata(null));
+
+        public IEnumerable Messages
+        {
+            get { return (IEnumerable)GetValue(MessagesProperty); }
+            set { SetValue(MessagesProperty, value); }
+        }
+
+        public static readonly DependencyProperty CornerRadiusProperty =
+         DependencyProperty.Register("CornerRadius", typeof(CornerRadius), typeof(NotificationBar),
+             new PropertyMetadata(new CornerRadius(25, 25, 25, 25)));
+
+        public CornerRadius CornerRadius
+        {
+            get { return (CornerRadius)GetValue(CornerRadiusProperty); }
+            set { SetValue(CornerRadiusProperty, value); }
+        }
+
+        public static readonly DependencyProperty TextPaddingProperty =
+        DependencyProperty.Register("TextPadding", typeof(Thickness), typeof(NotificationBar),
+            new PropertyMetadata(new Thickness(20, 10, 20, 10)));
+
+        public Thickness TextPadding
+        {
+            get { return (Thickness)GetValue(TextPaddingProperty); }
+            set { SetValue(TextPaddingProperty, value); }
+        }
+    }
+}

+ 14 - 0
Controls/RealTimeLineChart.xaml

@@ -0,0 +1,14 @@
+<UserControl x:Class="SWRIS.Controls.RealTimeLineChart"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+             xmlns:ScottPlot="clr-namespace:ScottPlot.WPF;assembly=ScottPlot.WPF"
+             mc:Ignorable="d"
+             d:DesignHeight="450" d:DesignWidth="800">
+
+    <Grid>
+        <!-- 替换原来的 LiveCharts 图表为 ScottPlot -->
+        <ScottPlot:WpfPlot x:Name="WpfPlot" Margin="5"/>
+    </Grid>
+</UserControl>

+ 343 - 0
Controls/RealTimeLineChart.xaml.cs

@@ -0,0 +1,343 @@
+using Microsoft.Win32;
+using ScottPlot;
+using ScottPlot.Plottables;
+using ScottPlot.WPF;
+using SWRIS.Core;
+using SWRIS.Models;
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Threading;
+namespace SWRIS.Controls
+{
+    /// <summary>
+    /// 动态折线图用户控件(基于 ScottPlot.WPF 的高性能版本)
+    /// </summary>
+    public partial class RealTimeLineChart : UserControl
+    {
+        #region 依赖属性定义
+
+        public static readonly DependencyProperty SensorCountProperty =
+            DependencyProperty.Register(
+                nameof(SensorCount),
+                typeof(int),
+                typeof(RealTimeLineChart),
+                new PropertyMetadata(4, OnSensorCountChanged));
+
+        public static readonly DependencyProperty RopeLengthProperty =
+            DependencyProperty.Register(
+                nameof(RopeLength),
+                typeof(double),
+                typeof(RealTimeLineChart),
+                new PropertyMetadata(100.0, OnAxisXChanged));
+
+        public static readonly DependencyProperty SamplingStepProperty =
+          DependencyProperty.Register(
+              nameof(SamplingStep),
+              typeof(double),
+              typeof(RealTimeLineChart),
+              new PropertyMetadata(0.1275));
+
+        #endregion
+
+        #region CLR 包装属性
+
+        public int SensorCount
+        {
+            get => (int)GetValue(SensorCountProperty);
+            set => SetValue(SensorCountProperty, value);
+        }
+
+        public double RopeLength
+        {
+            get => (double)GetValue(RopeLengthProperty);
+            set => SetValue(RopeLengthProperty, value);
+        }
+
+        public double SamplingStep
+        {
+            get => (double)GetValue(SamplingStepProperty);
+            set => SetValue(SamplingStepProperty, value);
+        }
+
+        #endregion
+
+        public double? _lastPosition = null;
+        private ConcurrentQueue<ushort> _sensorDataQueues;
+        private const double MINDAMAGE = 40; // 最小损伤值
+        private const double MAXDAMAGE = 120; // 最大损伤值
+        private int _displayPoints = 1000000; // 显示点数
+        private DispatcherTimer UpdatePlotTimer;
+        private DataLogger _dataLogger;
+
+        public RealTimeLineChart()
+        {
+            InitializeComponent();
+            InitializePlot();
+        }
+
+        private void OnLoaded(object sender, RoutedEventArgs e)
+        {
+            InitializePlot();
+        }
+
+        #region 初始化方法
+        private void InitializePlot()
+        {
+            WpfPlot.Menu?.Clear();
+
+            WpfPlot.Menu.Add("保存图片", pt =>
+            {
+                OpenSaveImageDialog(pt);
+            });
+            WpfPlot.Menu.Add("清除曲线", pt =>
+            {
+                ClearPoints();
+            });
+            WpfPlot.Menu.Add("重置视图", pt =>
+            {
+                SetLimits();
+            });
+            var plot = WpfPlot.Plot;
+            plot.Clear();
+            plot.DataBackground.Color = Colors.Transparent;
+            plot.FigureBackground.Color = Colors.Transparent;
+            plot.Grid.MajorLineColor = new Color(59, 59, 123).WithOpacity(0.6);
+            plot.Grid.XAxis.TickLabelStyle.ForeColor = new Color(151, 166, 212);
+            plot.Grid.YAxis.TickLabelStyle.ForeColor = new Color(151, 166, 212);
+            plot.Grid.XAxis.FrameLineStyle.Color = new Color(53, 53, 112);
+            plot.Grid.XAxis.FrameLineStyle.Width = 2;
+            plot.Grid.YAxis.FrameLineStyle.Color = new Color(53, 53, 112);
+            plot.Grid.YAxis.FrameLineStyle.Width = 2;
+            plot.Grid.XAxis.MajorTickStyle.Color = new Color(53, 53, 112);
+            plot.Grid.XAxis.MajorTickStyle.Width = 1;
+            plot.Grid.YAxis.MajorTickStyle.Color = new Color(53, 53, 112);
+            plot.Grid.YAxis.MajorTickStyle.Width = 1;
+            plot.Grid.YAxis.MinorTickStyle.Color = new Color(53, 53, 112);
+            plot.Grid.XAxis.MinorTickStyle.Color = new Color(53, 53, 112);
+            plot.Axes.Hairline(false);
+
+            _sensorDataQueues = new ConcurrentQueue<ushort>();
+
+            _dataLogger = plot.Add.DataLogger();
+            _dataLogger.LineWidth = 2;
+            _dataLogger.Color = new Color("#f47c7c");
+            _dataLogger.ManageAxisLimits = false;
+
+            WpfPlot.Plot.Axes.SetLimits(0, RopeLength * 1.2, MINDAMAGE, MAXDAMAGE);
+            WpfPlot.Refresh();
+
+            UpdatePlotTimer?.Stop();
+            UpdatePlotTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(200) };
+            UpdatePlotTimer.Tick += (s, d) =>
+            {
+                if (_dataLogger.HasNewData)
+                {
+                    SetLimits();
+                }
+            };
+            UpdatePlotTimer.Start();
+        }
+        #endregion
+
+        #region 数据操作方法
+        private void SetLimits()
+        {
+            if (_lastPosition == null || _lastPosition.Value < RopeLength)
+            {
+                WpfPlot.Plot.Axes.SetLimits(0, RopeLength * 1.2, MINDAMAGE, MAXDAMAGE);
+            }
+            else
+            {
+                WpfPlot.Plot.Axes.SetLimits(0, _lastPosition.Value + (RopeLength * 0.12), MINDAMAGE, MAXDAMAGE);
+            }
+            WpfPlot.Refresh();
+        }
+        public void ClearPoints()
+        {
+            lock (_sensorDataQueues)
+            {
+                while (_sensorDataQueues.TryDequeue(out _)) ;
+            }
+            _dataLogger.Clear();
+            WpfPlot.Refresh();
+        }
+        public void AddDataPoints(Coordinates[] chartData)
+        {
+            _dataLogger.Add(chartData);
+            _lastPosition = chartData.LastOrDefault().X;
+            WpfPlot.Refresh();
+        }
+        public Coordinates[] GetChartSource()
+        {
+            return _dataLogger.Data.Coordinates.ToArray();
+        }
+        public void AddDataPoints((double Position, LiveStreamDataModel ListStream) dataPoints, int[] inUseSensors)
+        {
+            if (_lastPosition == null)
+            {
+                _lastPosition = dataPoints.Position;
+                return;
+            }
+            if (dataPoints.Position < _lastPosition)
+            {
+                lock (_sensorDataQueues)
+                {
+                    while (_sensorDataQueues.TryDequeue(out _)) ;
+                    _lastPosition = dataPoints.Position;
+                }
+                _dataLogger.Data.Clear();
+            }
+            else
+            {
+                for (int i = 0; i < dataPoints.ListStream.SampleCount; i++)
+                {
+                    ushort totalValue = 0;
+                    ushort totalCount = 0;
+                    ushort[] values = dataPoints.ListStream.Data[i];
+                    foreach (var j in inUseSensors)
+                    {
+                        totalCount++;
+                        totalValue += values[j - 1];
+                    }
+                    _sensorDataQueues.Enqueue((ushort)(totalValue / totalCount));
+                }
+            }
+            if (dataPoints.Position > _lastPosition)
+            {
+                UpdateLoop(_lastPosition.Value, dataPoints.Position);
+                _lastPosition = dataPoints.Position;
+            }
+        }
+        public void OpenSaveImageDialog(Plot plot)
+        {
+            SaveFileDialog saveFileDialog = new SaveFileDialog
+            {
+                Filter = "PNG Files (*.png)|*.png|JPEG Files (*.jpg, *.jpeg)|*.jpg;*.jpeg|BMP Files (*.bmp)|*.bmp|WebP Files (*.webp)|*.webp|SVG Files (*.svg)|*.svg|All files (*.*)|*.*"
+            };
+            bool? flag = saveFileDialog.ShowDialog();
+            if (flag.HasValue && flag == true && !string.IsNullOrEmpty(saveFileDialog.FileName))
+            {
+                ImageFormat format;
+                try
+                {
+                    format = ImageFormats.FromFilename(saveFileDialog.FileName);
+                }
+                catch (ArgumentException)
+                {
+                    MessageBox.Show("Unsupported image file format", "ERROR", MessageBoxButton.OK, MessageBoxImage.Hand);
+                    return;
+                }
+
+                try
+                {
+                    PixelSize size = plot.RenderManager.LastRender.FigureRect.Size;
+                    plot.Save(saveFileDialog.FileName, (int)size.Width, (int)size.Height, format);
+                }
+                catch (Exception)
+                {
+                    MessageBox.Show("Image save failed", "ERROR", MessageBoxButton.OK, MessageBoxImage.Hand);
+                }
+            }
+        }
+        private void UpdateLoop(double lastPosition, double position)
+        {
+            try
+            {
+                // 在后台线程准备数据
+                List<(double x, ushort y)> dataToAdd = new List<(double x, ushort y)>();
+
+                var step = (position - lastPosition) / Math.Max(1, _sensorDataQueues.Count);
+                double currentX = lastPosition;
+
+                while (_sensorDataQueues.TryDequeue(out ushort data))
+                {
+                    currentX += step;
+                    // 确保 x 值严格递增
+                    if (dataToAdd.Count > 0 && currentX <= dataToAdd[dataToAdd.Count - 1].x)
+                    {
+                        currentX = dataToAdd[dataToAdd.Count - 1].x + Math.Abs(step) * 0.001;
+                    }
+                    dataToAdd.Add((currentX, data));
+                }
+
+                // 在UI线程批量添加
+                Dispatcher.Invoke(() =>
+                {
+                    lock (_dataLogger)
+                    {
+                        // 获取最后一个点的 x 值
+                        double lastX = _dataLogger.Data.Coordinates.Count > 0
+                            ? _dataLogger.Data.Coordinates[_dataLogger.Data.Coordinates.Count - 1].X
+                            : double.MinValue;
+
+                        foreach (var point in dataToAdd)
+                        {
+                            double safeX = point.x;
+                            if (safeX <= lastX)
+                            {
+                                safeX = lastX + Math.Abs(position - lastPosition) * 0.0001;
+                            }
+                            _dataLogger.Add(safeX, point.y);
+                            lastX = safeX;
+                        }
+                    }
+
+                    // 清理操作
+                    if (_dataLogger.Data.Coordinates.Count > _displayPoints)
+                    {
+                        int removeCount = (int)(_displayPoints * 0.1d);
+                        removeCount = Math.Min(removeCount, _dataLogger.Data.Coordinates.Count - 1);
+                        if (removeCount > 0)
+                        {
+                            _dataLogger.Data.Coordinates.RemoveRange(0, removeCount);
+                        }
+                    }
+                });
+            }
+            catch (Exception ex)
+            {
+                LogHelper.Error($"绘制曲线时发生错误:{ex.Message}", ex);
+            }
+        }
+
+        #endregion
+
+        #region 静态回调
+
+        private static void OnSensorCountChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+        {
+            if (d is RealTimeLineChart control)
+                control.OnSensorCountChanged((int)e.OldValue, (int)e.NewValue);
+        }
+        private static void OnAxisXChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+        {
+            if (d is RealTimeLineChart control)
+                control.UpdateAxisX();
+        }
+
+        #endregion
+        #region 坐标轴更新逻辑
+        private void UpdateAxisX()
+        {
+            if (WpfPlot?.Plot == null) return;
+
+            WpfPlot.Plot.Axes.SetLimitsX(0, RopeLength * 1.2);
+            WpfPlot.Refresh();
+        }
+        #endregion
+        #region 传感器数量变化处理
+
+        private void OnSensorCountChanged(int oldCount, int newCount)
+        {
+            Dispatcher.Invoke(() =>
+            {
+                InitializePlot();
+            });
+        }
+        #endregion
+    }
+}

+ 14 - 0
Controls/RecordLineChart.xaml

@@ -0,0 +1,14 @@
+<UserControl x:Class="SWRIS.Controls.RecordLineChart"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+             xmlns:ScottPlot="clr-namespace:ScottPlot.WPF;assembly=ScottPlot.WPF"
+             mc:Ignorable="d"
+             d:DesignHeight="450" d:DesignWidth="800">
+
+    <Grid>
+        <!-- 替换原来的 LiveCharts 图表为 ScottPlot -->
+        <ScottPlot:WpfPlot x:Name="WpfPlot" Margin="5" />
+    </Grid>
+</UserControl>

+ 236 - 0
Controls/RecordLineChart.xaml.cs

@@ -0,0 +1,236 @@
+using Microsoft.Win32;
+using ScottPlot;
+using ScottPlot.Plottables;
+using ScottPlot.WPF;
+using SWRIS.Dtos;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows;
+using System.Windows.Controls;
+namespace SWRIS.Controls
+{
+    /// <summary>
+    /// 动态折线图用户控件(基于 ScottPlot.WPF 的高性能版本)
+    /// </summary>
+    public partial class RecordLineChart : UserControl
+    {
+        private readonly Color[] _colors = new Color[] {
+            new Color("#70a1d7"), new Color("#a1de93"),
+            new Color("#f7f48b"), new Color("#f47c7c"),
+            new Color("#c264fe"), new Color("#00fff5"),
+            new Color("#f8b195"), new Color("#7a08fa") };
+
+        private const double MAXDAMAGE = 128; // 最大损伤值
+        private SignalXY[] _sensorSignals;
+        private SignalXY _mergeSignal;
+        private Scatter[] _makerScatters;
+        private AxisLimits _axisLimit;
+        public RecordLineChart()
+        {
+            InitializeComponent();
+            InitializePlot();
+        }
+        private void InitializePlot()
+        {
+            WpfPlot.Menu?.Clear();
+
+            WpfPlot.Menu.Add("保存图片", pt =>
+            {
+                OpenSaveImageDialog(pt);
+            });
+            WpfPlot.Menu.Add("重置视图", pt =>
+            {
+                WpfPlot.Plot.Axes.SetLimits(_axisLimit);
+                WpfPlot.Refresh();
+            });
+            var plot = WpfPlot.Plot;
+            plot.Clear();
+            plot.DataBackground.Color = Colors.Transparent;
+            plot.FigureBackground.Color = Colors.Transparent;
+            plot.Grid.MajorLineColor = new Color(59, 59, 123).WithOpacity(0.6);
+            plot.Grid.XAxis.TickLabelStyle.ForeColor = new Color(151, 166, 212);
+            plot.Grid.YAxis.TickLabelStyle.ForeColor = new Color(151, 166, 212);
+            plot.Grid.XAxis.FrameLineStyle.Color = new Color(53, 53, 112);
+            plot.Grid.XAxis.FrameLineStyle.Width = 3;
+            plot.Grid.YAxis.FrameLineStyle.Color = new Color(53, 53, 112);
+            plot.Grid.YAxis.FrameLineStyle.Width = 3;
+            plot.Grid.XAxis.MajorTickStyle.Color = new Color(53, 53, 112);
+            plot.Grid.XAxis.MajorTickStyle.Width = 1;
+            plot.Grid.YAxis.MajorTickStyle.Color = new Color(53, 53, 112);
+            plot.Grid.YAxis.MajorTickStyle.Width = 1;
+            plot.Grid.YAxis.MinorTickStyle.Color = new Color(53, 53, 112);
+            plot.Grid.XAxis.MinorTickStyle.Color = new Color(53, 53, 112);
+            plot.Axes.Hairline(false);
+        }
+
+        public void AddDataPoints((double[] Positions, ushort[,] Damages) dataPoints, List<DamageDto> alertDamages)
+        {
+            if (dataPoints.Damages == null || dataPoints.Positions == null)
+            {
+                return;
+            }
+            int sensorCount = dataPoints.Damages.GetLength(0);
+            int sampleCount = dataPoints.Damages.GetLength(1);
+            double startPoint = dataPoints.Positions.First();
+            double endPoint = dataPoints.Positions.Last();
+
+            _sensorSignals = new SignalXY[sensorCount];
+            double[] mergeData = new double[sampleCount];
+
+            // 先计算所有传感器的合并数据
+            for (int j = 0; j < sampleCount; j++)
+            {
+                double sum = 0;
+                for (int i = 0; i < sensorCount; i++)
+                {
+                    sum += dataPoints.Damages[i, j];
+                }
+                mergeData[j] = sum / sensorCount;  // 计算平均值
+            }
+
+            AddMaker(dataPoints.Positions, mergeData, alertDamages);
+
+            // 然后处理每个传感器的数据
+            for (int i = 0; i < sensorCount; i++)
+            {
+                ushort[] sensorData = new ushort[sampleCount];
+                for (int j = 0; j < sampleCount; j++)
+                {
+                    sensorData[j] = dataPoints.Damages[i, j];
+                }
+                var signalPlot = WpfPlot.Plot.Add.SignalXY(dataPoints.Positions, sensorData);
+                signalPlot.LineWidth = 1;
+                signalPlot.Color = _colors[i];
+                signalPlot.IsVisible = false;
+                _sensorSignals[i] = signalPlot;
+            }
+
+            _mergeSignal = WpfPlot.Plot.Add.SignalXY(dataPoints.Positions, mergeData);
+            _mergeSignal.LineWidth = 2;
+            _mergeSignal.Color = new Color("#9664F3");
+
+            //差值
+            double offset = (endPoint - startPoint) * 0.1;
+            _axisLimit = new AxisLimits(startPoint - offset, endPoint + offset, 0, MAXDAMAGE);
+            WpfPlot.Plot.Axes.SetLimits(_axisLimit);
+            WpfPlot.Refresh();
+        }
+
+        public void SignalPlotVisible(int id, bool isVisible)
+        {
+            if (id > _sensorSignals.Length) return;
+
+            if (id == 0)
+            {
+                _mergeSignal.IsVisible = isVisible;
+                foreach (var maker in _makerScatters)
+                {
+                    maker.IsVisible = isVisible;
+                }
+            }
+            else
+            {
+                _sensorSignals[id - 1].IsVisible = isVisible;
+            }
+            WpfPlot.Refresh();
+        }
+        private void AddMaker(double[] dataPoints, double[] mergeData, List<DamageDto> alertDamages)
+        {
+            int markerCount = alertDamages.Count;
+            _makerScatters = new Scatter[markerCount];
+            for (int i = 0; i < markerCount; i++)
+            {
+                int index = FindNearestIndex(dataPoints, alertDamages[i].DamagePoint);
+                if (index < 0 || index >= dataPoints.Length)
+                    continue;
+                var marker = WpfPlot.Plot.Add.Scatter(dataPoints[index], mergeData[index]);
+                marker.LineWidth = 0;  // 不显示连线
+                marker.MarkerSize = 10;
+                marker.MarkerShape = MarkerShape.OpenCircle;
+                marker.MarkerLineWidth = 2;
+                switch (alertDamages[i].DamageLevel)
+                {
+                    case Enums.DamageLevel.Mild:
+                        marker.Color = Color.FromHex("#00FF78");
+                        break;
+                    case Enums.DamageLevel.Light:
+                        marker.Color = Color.FromHex("#FFF000");
+                        break;
+                    case Enums.DamageLevel.Moderate:
+                        marker.Color = Color.FromHex("#FF9400");
+                        break;
+                    case Enums.DamageLevel.Severe:
+                        marker.Color = Color.FromHex("#FF6C00");
+                        break;
+                    case Enums.DamageLevel.Critical:
+                        marker.Color = Color.FromHex("#FF0000");
+                        break;
+                    case Enums.DamageLevel.ExceededLimit:
+                        marker.Color = Color.FromHex("#FF008A");
+                        break;
+                }
+                _makerScatters[i] = marker;
+            }
+        }
+        private int FindNearestIndex(double[] positions, double target)
+        {
+            if (positions == null || positions.Length == 0)
+                return -1;
+
+            int index = Array.BinarySearch(positions, target);
+
+            // 如果找到精确匹配
+            if (index >= 0)
+                return index;
+
+            // 如果没找到,BinarySearch 返回下一个更大元素的补码
+            int largerIndex = ~index;
+
+            // 处理边界情况
+            if (largerIndex == 0)
+                return 0;
+
+            if (largerIndex == positions.Length)
+                return positions.Length - 1;
+
+            // 比较左右两个元素哪个更接近
+            double diffLeft = Math.Abs(positions[largerIndex - 1] - target);
+            double diffRight = Math.Abs(positions[largerIndex] - target);
+
+            return diffLeft < diffRight ? largerIndex - 1 : largerIndex;
+        }
+        public void OpenSaveImageDialog(Plot plot)
+        {
+            SaveFileDialog saveFileDialog = new SaveFileDialog
+            {
+                Filter = "PNG Files (*.png)|*.png|JPEG Files (*.jpg, *.jpeg)|*.jpg;*.jpeg|BMP Files (*.bmp)|*.bmp|WebP Files (*.webp)|*.webp|SVG Files (*.svg)|*.svg|All files (*.*)|*.*"
+            };
+            bool? flag = saveFileDialog.ShowDialog();
+            if (flag.HasValue && flag == true && !string.IsNullOrEmpty(saveFileDialog.FileName))
+            {
+                ImageFormat format;
+                try
+                {
+                    format = ImageFormats.FromFilename(saveFileDialog.FileName);
+                }
+                catch (ArgumentException)
+                {
+                    MessageBox.Show("Unsupported image file format", "ERROR", MessageBoxButton.OK, MessageBoxImage.Hand);
+                    return;
+                }
+
+                try
+                {
+                    PixelSize size = plot.RenderManager.LastRender.FigureRect.Size;
+                    plot.Save(saveFileDialog.FileName, (int)size.Width, (int)size.Height, format);
+                }
+                catch (Exception)
+                {
+                    MessageBox.Show("Image save failed", "ERROR", MessageBoxButton.OK, MessageBoxImage.Hand);
+                }
+            }
+        }
+        
+    }
+}

+ 102 - 0
Controls/RopeEquipmentControl.xaml

@@ -0,0 +1,102 @@
+<UserControl x:Class="SWRIS.Controls.RopeEquipmentControl"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+             xmlns:local="clr-namespace:SWRIS.Controls" 
+             xmlns:cvt="clr-namespace:SWRIS.Converters"
+             mc:Ignorable="d"  x:Name="Root"
+             d:DesignHeight="900" d:DesignWidth="300">
+    <UserControl.Resources>
+        <Storyboard x:Key="RopeAnimation" RepeatBehavior="Forever">
+            <RectAnimation Storyboard.TargetName="RopeRectangle" 
+                           Storyboard.TargetProperty="Viewport"
+                           Duration="0:0:0.5"/>
+        </Storyboard>
+        <cvt:LimitStateToColorConverter x:Key="LimitStateToColorConverter"/>
+        <cvt:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
+    </UserControl.Resources>
+    <Grid Margin="0,0,30,0">
+        <Grid.ColumnDefinitions>
+            <ColumnDefinition Width="*"/>
+            <ColumnDefinition Width="*"/>
+        </Grid.ColumnDefinitions>
+        <StackPanel Grid.Column="0" Orientation="Vertical" Margin="{Binding PanelMargin,ElementName=Root}">
+            <ItemsControl ItemsSource="{Binding Limits}" HorizontalAlignment="Right" Margin="-10,-10,0,10">
+                <ItemsControl.ItemsPanel>
+                    <ItemsPanelTemplate>
+                        <StackPanel Orientation="Horizontal"/>
+                    </ItemsPanelTemplate>
+                </ItemsControl.ItemsPanel>
+                <ItemsControl.ItemTemplate>
+                    <DataTemplate>
+                        <Border Visibility="{Binding IsEnable,Converter={StaticResource BoolToVisibilityConverter}}"
+                                Background="{Binding State,Converter={StaticResource LimitStateToColorConverter},ConverterParameter=0.20}" 
+                                MinWidth="80" Height="24" CornerRadius="11" Margin="5,0,0,0">
+                            <StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center">
+                                <Ellipse Height="10" Width="10" Margin="0,0,5,0" Fill="{Binding State,Converter={StaticResource LimitStateToColorConverter}}"/>
+                                <TextBlock Text="{Binding Name}" Foreground="{Binding State,Converter={StaticResource LimitStateToColorConverter}}"
+                                           FontSize="14" FontWeight="Regular" VerticalAlignment="Center"/>
+                            </StackPanel>
+                        </Border>
+                    </DataTemplate>
+                </ItemsControl.ItemTemplate>
+            </ItemsControl>
+            <TextBlock Text="检测位置" Foreground="#7886B2" FontSize="{Binding PanelNameFontSize,ElementName=Root}" 
+                       FontWeight="Regular" HorizontalAlignment="Right" Visibility="Visible"/>
+            <TextBlock FontSize="{Binding PanelValueFontSize,ElementName=Root}" FontWeight="Bold" FontFamily="{StaticResource PuHuiTiBold}"
+                       Margin="-30,-3,0,0" HorizontalAlignment="Right" Visibility="Visible">
+                <Run Text="{Binding AbsolutePosition,StringFormat=#0.00}"/>
+                <Run Text="m"/>
+            </TextBlock>
+            <TextBlock Text="检测速度" Foreground="#7886B2" FontSize="{Binding PanelNameFontSize,ElementName=Root}" 
+                       FontWeight="Regular" HorizontalAlignment="Right"/>
+            <TextBlock FontSize="{Binding PanelValueFontSize,ElementName=Root}" FontWeight="Bold" FontFamily="{StaticResource PuHuiTiBold}"
+                       Margin="-30,-3,0,0" HorizontalAlignment="Right">
+                <Run Text="{Binding Speed,StringFormat=#0.00}"/>
+                <Run Text="m/min"/>
+                <TextBlock.Style>
+                    <Style TargetType="TextBlock">
+                        <Style.Triggers>
+                            <DataTrigger Binding="{Binding IsSpeedStable}" Value="False">
+                                <Setter Property="Foreground" >
+                                    <Setter.Value>
+                                        <LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">
+                                            <GradientStop Color="#FFFFFF" Offset="0.45"/>
+                                            <GradientStop Color="#FF6C00" Offset="1"/>
+                                        </LinearGradientBrush>
+                                    </Setter.Value>
+                                </Setter>
+                            </DataTrigger>
+                        </Style.Triggers>
+                    </Style>
+                </TextBlock.Style>
+            </TextBlock>
+        </StackPanel>
+        <Canvas Grid.Column="1">
+            <Rectangle Width="40" Height="{Binding RopeHeight,ElementName=Root}" Canvas.Left="30">
+                <Rectangle.Fill>
+                    <ImageBrush x:Name="RopeRectangle" ImageSource="{Binding RopeImageSource,ElementName=Root}" TileMode="FlipX" Viewport="0,0,40,34" 
+                         ViewportUnits="Absolute"/>
+                </Rectangle.Fill>
+            </Rectangle>
+            <Canvas Canvas.Top="{Binding EquipmentTopPosition,ElementName=Root}">
+                <Image Width="90" Canvas.Left="5">
+                    <Image.Style>
+                        <Style TargetType="Image">
+                            <Style.Triggers>
+                                <DataTrigger Binding="{Binding IsAlerting}" Value="False">
+                                    <Setter Property="Source" Value="/Resources/blue_bg.png"/>
+                                </DataTrigger>
+                                <DataTrigger Binding="{Binding IsAlerting}" Value="True">
+                                    <Setter Property="Source" Value="/Resources/red_bg.png"/>
+                                </DataTrigger>
+                            </Style.Triggers>
+                        </Style>
+                    </Image.Style>
+                </Image>
+                <Image Source="{Binding EquipmentImageSource,ElementName=Root}" Width="100" Canvas.Top="70"/>
+            </Canvas>
+        </Canvas>
+    </Grid>
+</UserControl>

+ 212 - 0
Controls/RopeEquipmentControl.xaml.cs

@@ -0,0 +1,212 @@
+using SWRIS.Models;
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media;
+using System.Windows.Media.Animation;
+
+namespace SWRIS.Controls
+{
+    /// <summary>
+    /// RopeControl.xaml 的交互逻辑
+    /// </summary>
+    public partial class RopeEquipmentControl : UserControl
+    {
+        private readonly Storyboard _animationStoryboard;
+        public RopeEquipmentControl()
+        {
+            InitializeComponent();
+
+            // 初始化动画
+            _animationStoryboard = (Storyboard)Resources["RopeAnimation"];
+
+            // 设置初始动画值
+            UpdateAnimationValues();
+        }
+        #region 依赖属性
+
+        // 绳索高度
+        public static readonly DependencyProperty RopeHeightProperty =
+            DependencyProperty.Register("RopeHeight", typeof(double), typeof(RopeEquipmentControl),
+                new PropertyMetadata(900.0));
+
+        // 绳索图片
+        public static readonly DependencyProperty RopeImageSourceProperty =
+            DependencyProperty.Register("RopeImageSource", typeof(ImageSource), typeof(RopeEquipmentControl));
+
+        // 设备图片
+        public static readonly DependencyProperty EquipmentImageSourceProperty =
+            DependencyProperty.Register("EquipmentImageSource", typeof(ImageSource), typeof(RopeEquipmentControl));
+
+        // 是否运行动画
+        public static readonly DependencyProperty IsAnimationRunningProperty =
+            DependencyProperty.Register("IsAnimationRunning", typeof(bool), typeof(RopeEquipmentControl),
+                new PropertyMetadata(false, OnIsAnimationRunningChanged));
+
+        // 动画方向(正向/反向)
+        public static readonly DependencyProperty IsAnimationReversedProperty =
+            DependencyProperty.Register("IsAnimationReversed", typeof(bool), typeof(RopeEquipmentControl),
+                new PropertyMetadata(false, OnAnimationDirectionChanged));
+
+        // 动画速度(秒)
+        public static readonly DependencyProperty AnimationDurationProperty =
+            DependencyProperty.Register("AnimationDuration", typeof(double), typeof(RopeEquipmentControl),
+                new PropertyMetadata(0.5, OnAnimationDurationChanged));
+
+        // 数据面板位置
+        public static readonly DependencyProperty PanelMarginProperty =
+            DependencyProperty.Register("PanelMargin", typeof(Thickness), typeof(RopeEquipmentControl));
+
+        // 数据面板文字大小
+        public static readonly DependencyProperty PanelNameFontSizeProperty =
+            DependencyProperty.Register("PanelNameFontSize", typeof(double), typeof(RopeEquipmentControl),
+                new PropertyMetadata(28d));
+
+        // 数据面板文字大小
+        public static readonly DependencyProperty PanelValueFontSizeProperty =
+            DependencyProperty.Register("PanelValueFontSize", typeof(double), typeof(RopeEquipmentControl),
+                new PropertyMetadata(28d));
+
+        // 设备图片位置
+        public static readonly DependencyProperty EquipmentTopPositionProperty =
+            DependencyProperty.Register("EquipmentTopPosition", typeof(double), typeof(RopeEquipmentControl));
+
+
+
+        #endregion
+        #region 属性包装器
+
+        public double RopeHeight
+        {
+            get => (double)GetValue(RopeHeightProperty);
+            set => SetValue(RopeHeightProperty, value);
+        }
+
+        public ImageSource RopeImageSource
+        {
+            get => (ImageSource)GetValue(RopeImageSourceProperty);
+            set => SetValue(RopeImageSourceProperty, value);
+        }
+
+        public ImageSource EquipmentImageSource
+        {
+            get => (ImageSource)GetValue(EquipmentImageSourceProperty);
+            set => SetValue(EquipmentImageSourceProperty, value);
+        }
+        public bool IsAnimationRunning
+        {
+            get => (bool)GetValue(IsAnimationRunningProperty);
+            set => SetValue(IsAnimationRunningProperty, value);
+        }
+
+        public bool IsAnimationReversed
+        {
+            get => (bool)GetValue(IsAnimationReversedProperty);
+            set => SetValue(IsAnimationReversedProperty, value);
+        }
+
+        public double AnimationDuration
+        {
+            get => (double)GetValue(AnimationDurationProperty);
+            set => SetValue(AnimationDurationProperty, value);
+        }
+        public Thickness PanelMargin
+        {
+            get => (Thickness)GetValue(PanelMarginProperty);
+            set => SetValue(PanelMarginProperty, value);
+        }
+        public double PanelNameFontSize
+        {
+            get => (double)GetValue(PanelNameFontSizeProperty);
+            set => SetValue(PanelNameFontSizeProperty, value);
+        }
+        public double PanelValueFontSize
+        {
+            get => (double)GetValue(PanelValueFontSizeProperty);
+            set => SetValue(PanelValueFontSizeProperty, value);
+        }
+        public double EquipmentTopPosition
+        {
+            get => (double)GetValue(EquipmentTopPositionProperty);
+            set => SetValue(EquipmentTopPositionProperty, value);
+        }
+        #endregion
+
+        #region 动画控制
+
+        private static void OnIsAnimationRunningChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+        {
+            var control = (RopeEquipmentControl)d;
+            control.UpdateAnimationState();
+        }
+
+        private static void OnAnimationDirectionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+        {
+            var control = (RopeEquipmentControl)d;
+            control.UpdateAnimationValues();
+        }
+
+        private static void OnAnimationDurationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+        {
+            var control = (RopeEquipmentControl)d;
+            control.UpdateAnimationValues();
+        }
+
+        private void UpdateAnimationValues()
+        {
+            var rectAnimation = (RectAnimation)_animationStoryboard.Children[0];
+            rectAnimation.Duration = TimeSpan.FromSeconds(AnimationDuration);
+
+            if (IsAnimationReversed)
+            {
+                rectAnimation.From = new Rect(0, -34, 40, 34);
+                rectAnimation.To = new Rect(0, 0, 40, 34);
+            }
+            else
+            {
+                rectAnimation.From = new Rect(0, 0, 40, 34);
+                rectAnimation.To = new Rect(0, -34, 40, 34);
+            }
+
+            if (IsAnimationRunning)
+            {
+                _animationStoryboard.Begin(this, true);
+            }
+        }
+
+        private void UpdateAnimationState()
+        {
+            if (IsAnimationRunning)
+            {
+                _animationStoryboard.Begin(this, true);
+            }
+            else
+            {
+                _animationStoryboard.Stop(this);
+            }
+        }
+
+        #endregion
+        #region 公共方法
+
+        public void StartAnimation(bool reversed = false)
+        {
+            IsAnimationReversed = reversed;
+            IsAnimationRunning = true;
+        }
+
+        public void StopAnimation()
+        {
+            IsAnimationRunning = false;
+        }
+
+        public void ReverseAnimation()
+        {
+            IsAnimationReversed = !IsAnimationReversed;
+        }
+
+        #endregion
+    }
+}

+ 20 - 0
Converters/BoolToVisibilityConverter.cs

@@ -0,0 +1,20 @@
+using System;
+using System.Globalization;
+using System.Windows;
+using System.Windows.Data;
+
+namespace SWRIS.Converters
+{
+    public class BoolToVisibilityConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            return value != null && value is bool isEnable && isEnable ? Visibility.Visible : Visibility.Collapsed;
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 19 - 0
Converters/BooleanInverterConverter.cs

@@ -0,0 +1,19 @@
+using System;
+using System.Globalization;
+using System.Windows.Data;
+
+namespace SWRIS.Converters
+{
+    public class BooleanInverterConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            return value is bool b ? !b : value;
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            return value is bool b ? !b : value;
+        }
+    }
+}

+ 22 - 0
Converters/CollectionCountToVisibilityConverter.cs

@@ -0,0 +1,22 @@
+using System;
+using System.Globalization;
+using System.Windows;
+using System.Windows.Data;
+
+namespace SWRIS.Converters
+{
+    public class CollectionCountToVisibilityConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            if (value is int count && count > 0)
+                return Visibility.Visible;
+            return Visibility.Hidden;
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 93 - 0
Converters/ColorToHoverBackgroundConverter.cs

@@ -0,0 +1,93 @@
+using System;
+using System.Globalization;
+using System.Windows;
+using System.Windows.Data;
+using System.Windows.Media;
+
+namespace SWRIS.Converters
+{
+    public class ColorStringToHoverBackgroundConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            if (value is string colorString && !string.IsNullOrEmpty(colorString))
+            {
+                try
+                {
+                    // 移除#号并确保6位十六进制
+                    string hex = colorString.TrimStart('#');
+                    if (hex.Length == 6)
+                    {
+                        return $"#1A{hex}"; // 添加透明度
+                    }
+                }
+                catch
+                {
+                    return DependencyProperty.UnsetValue;
+                }
+            }
+            return DependencyProperty.UnsetValue;
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+
+    public class ColorStringToHoverBorderBrushConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            if (value is string colorString && !string.IsNullOrEmpty(colorString))
+            {
+                try
+                {
+                    string hex = colorString.TrimStart('#');
+                    if (hex.Length == 6)
+                    {
+                        return $"#4D{hex}"; // 添加透明度
+                    }
+                }
+                catch
+                {
+                    return DependencyProperty.UnsetValue;
+                }
+            }
+            return DependencyProperty.UnsetValue;
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+
+    public class ColorStringToCheckedBackgroundConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            if (value is string colorString && !string.IsNullOrEmpty(colorString))
+            {
+                try
+                {
+                    string hex = colorString.TrimStart('#');
+                    if (hex.Length == 6)
+                    {
+                        return $"#4C{hex}"; // 添加透明度
+                    }
+                }
+                catch
+                {
+                    return DependencyProperty.UnsetValue;
+                }
+            }
+            return DependencyProperty.UnsetValue;
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 36 - 0
Converters/ConnectivityFaultToColorConverter.cs

@@ -0,0 +1,36 @@
+using SWRIS.Models;
+using System;
+using System.Collections.ObjectModel;
+using System.Globalization;
+using System.Linq;
+using System.Windows.Data;
+using System.Windows.Media;
+
+namespace SWRIS.Converters
+{
+    public class ConnectivityFaultToColorConverter : IMultiValueConverter
+    {
+        private static readonly SolidColorBrush OnlineBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#00FF78"));
+        private static readonly SolidColorBrush OfflineBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#7886B2"));
+        private static readonly SolidColorBrush FaultBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#FF0000"));
+ 
+        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
+        {
+            if (values.Length < 2 || !(values[0] is bool isConnected) || !(values[1] is ObservableCollection<FaultDataModel> faults))
+                return OfflineBrush;
+
+            if (!isConnected)
+                return OfflineBrush;
+
+            if (faults != null && faults.Any(f => f != null)) // 检查非空故障
+                return FaultBrush;
+
+            return OnlineBrush;
+        }
+
+        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 25 - 0
Converters/ConnectivityToVisibilityConverter.cs

@@ -0,0 +1,25 @@
+using System;
+using System.Globalization;
+using System.Net.Sockets;
+using System.Windows;
+using System.Windows.Data;
+
+namespace SWRIS.Converters
+{
+    public class ConnectivityToVisibilityConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            if (value == null || !(value is bool isConnected))
+            {
+                return Visibility.Hidden;
+            }
+            return isConnected ? Visibility.Visible : Visibility.Hidden;
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 46 - 0
Converters/DamageLevelToColorConverter.cs

@@ -0,0 +1,46 @@
+using SWRIS.Enums;
+using System;
+using System.Globalization;
+using System.Windows.Data;
+using System.Windows.Media;
+
+namespace SWRIS.Converters
+{
+    public class DamageLevelToColorConverter : IValueConverter
+    {
+        private static readonly SolidColorBrush MildBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#00FF78"));
+        private static readonly SolidColorBrush LightBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#FFF000"));
+        private static readonly SolidColorBrush ModerateBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#FF9400"));
+        private static readonly SolidColorBrush SevereBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#FF6C00"));
+        private static readonly SolidColorBrush CriticalBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#FF0000"));
+        private static readonly SolidColorBrush ExtremeBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#FF008A"));
+
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            if (value is DamageLevel level)
+            {
+                switch (level)
+                {
+                    case DamageLevel.Mild:
+                        return MildBrush;
+                    case DamageLevel.Light:
+                        return LightBrush;
+                    case DamageLevel.Moderate:
+                        return ModerateBrush;
+                    case DamageLevel.Severe:
+                        return SevereBrush;
+                    case DamageLevel.Critical:
+                        return CriticalBrush;
+                    case DamageLevel.ExceededLimit:
+                        return ExtremeBrush;
+                }
+            }
+            return Brushes.Transparent;
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 21 - 0
Converters/DamageLevelToTextConverter.cs

@@ -0,0 +1,21 @@
+using SWRIS.Enums;
+using SWRIS.Extensions;
+using System;
+using System.Globalization;
+using System.Windows.Data;
+
+namespace SWRIS.Converters
+{
+    public class DamageLevelToTextConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            return ((DamageLevel)value).GetDescription();
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 26 - 0
Converters/DirectionStateToBooleanConverter.cs

@@ -0,0 +1,26 @@
+using SWRIS.Enums;
+using System;
+using System.Globalization;
+using System.Windows.Data;
+
+namespace SWRIS.Converters
+{
+    public class DirectionStateToBooleanConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            if (value == null) { return false; }
+
+            if (value is DirectionState valueDirection)
+            {
+                return valueDirection != DirectionState.Stoped;
+            }
+            return false;
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 39 - 0
Converters/EncoderDirectionToBooleanConverter.cs

@@ -0,0 +1,39 @@
+using SWRIS.Enums;
+using System;
+using System.Globalization;
+using System.Windows.Data;
+
+namespace SWRIS.Converters
+{
+    public class EncoderDirectionToBooleanConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            if (value == null || parameter == null) { return false; }
+
+            if (value is EncoderDirection valueDirection && Enum.TryParse(parameter.ToString(), true, out EncoderDirection parameterDirection))
+            {
+                return valueDirection == parameterDirection;
+            }
+            return false;
+        }
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            // 空值检查:若输入或参数为 null,不更新源数据
+            if (value == null || parameter == null)
+                return EncoderDirection.Forward;
+
+            // 仅当 value 为 true 时尝试转换
+            if (value is bool boolValue && boolValue)
+            {
+                // 将参数解析为 EncoderDirection 枚举
+                if (Enum.TryParse(parameter.ToString(), true, out EncoderDirection parameterDirection))
+                {
+                    return parameterDirection; // 返回解析结果
+                }
+            }
+            // 默认情况:不更新源数据
+            return EncoderDirection.Forward;
+        }
+    }
+}

+ 54 - 0
Converters/LimitStateToColorConverter.cs

@@ -0,0 +1,54 @@
+using SWRIS.Enums;
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO.Ports;
+using System.Windows.Data;
+using System.Windows.Media;
+
+namespace SWRIS.Converters
+{
+    public class LimitStateToColorConverter : IValueConverter
+    {
+        private static readonly Dictionary<string, SolidColorBrush> _brushCache = new Dictionary<string, SolidColorBrush>();
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            if (value is LimitState limit)
+            {
+                double opacity = parameter != null ? System.Convert.ToDouble(parameter) : 1;
+                string cacheKey = $"{limit}_{opacity}";
+                // 如果缓存中存在,直接返回
+                if (_brushCache.TryGetValue(cacheKey, out var cachedBrush))
+                {
+                    return cachedBrush;
+                }
+                SolidColorBrush colorBrush;
+                switch (limit)
+                {
+                    case LimitState.Offline:
+                        colorBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#7886B2"));
+                        break;
+                    case LimitState.NotInLimit:
+                        colorBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#00FF78"));
+                        break;
+                    case LimitState.AtLimit:
+                        colorBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#FF0000"));
+                        break;
+                    default:
+                        colorBrush = Brushes.Transparent;
+                        break;
+                }
+                colorBrush.Opacity = opacity;
+                colorBrush.Freeze();
+                _brushCache[cacheKey] = colorBrush;
+                return colorBrush;
+            }
+            return Brushes.Transparent;
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            return value is bool b ? !b : value;
+        }
+    }
+}

+ 38 - 0
Converters/MenuNameToColorConverter.cs

@@ -0,0 +1,38 @@
+using System;
+using System.Globalization;
+using System.Windows.Data;
+using System.Windows.Media;
+
+namespace SWRIS.Converters
+{
+    public class MenuNameToColorConverter : IValueConverter
+    {
+        private static readonly SolidColorBrush activeBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#FFFFFF"));
+        private static readonly SolidColorBrush unActiveBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#AEBAE8"));
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            var menuName = (string)value;
+            switch (menuName)
+            { 
+                case "Home":
+                    if (parameter != null && parameter.ToString() == "Home")
+                    {
+                        return activeBrush;
+                    }
+                    return unActiveBrush;
+                case "Record":
+                    if (parameter != null && parameter.ToString() == "Record")
+                    {
+                        return activeBrush;
+                    }
+                    return unActiveBrush;
+            }
+            return 0;
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 27 - 0
Converters/MenuNameToPositionConverter.cs

@@ -0,0 +1,27 @@
+using System;
+using System.Globalization;
+using System.Windows.Data;
+
+namespace SWRIS.Converters
+{
+    public class MenuNameToPositionConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            var menuName = (string)value;
+            switch (menuName)
+            {
+                case "Home":
+                    return 0;
+                case "Record":
+                    return 157;
+            }
+            return 0;
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 33 - 0
Converters/ModuleTypeToVisibilityConverter.cs

@@ -0,0 +1,33 @@
+using SWRIS.Enums;
+using System;
+using System.Globalization;
+using System.Windows;
+using System.Windows.Data;
+
+namespace SWRIS.Converters
+{
+    public class ModuleTypeToVisibilityConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            if (value != null && (ModuleType)value is ModuleType moduleType)
+            {
+                parameter = parameter ?? "Tcp";
+                if (moduleType == ModuleType.ModbusRtu)
+                {
+                    return parameter.ToString() == "Serial" ? Visibility.Visible : Visibility.Collapsed;
+                }
+                else
+                {
+                    return parameter.ToString() == "Tcp" ? Visibility.Visible : Visibility.Collapsed;
+                }
+            }
+            return Visibility.Collapsed;
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 23 - 0
Converters/NullToBooleanConverter.cs

@@ -0,0 +1,23 @@
+using System;
+using System.Globalization;
+using System.Windows.Data;
+
+namespace SWRIS.Converters
+{
+    public class NullToBooleanConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            if (value == null || value.ToString() == string.Empty)
+            {
+                return true;
+            }
+            return false;
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            return value is bool b ? !b : value;
+        }
+    }
+}

+ 20 - 0
Converters/NullToVisibilityConverter.cs

@@ -0,0 +1,20 @@
+using System;
+using System.Globalization;
+using System.Windows;
+using System.Windows.Data;
+
+namespace SWRIS.Converters
+{
+    public class NullToVisibilityConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            return value != null ? Visibility.Visible : Visibility.Collapsed;
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 65 - 0
Converters/PercentageConverter.cs

@@ -0,0 +1,65 @@
+using System;
+using System.Globalization;
+using System.Windows.Data;
+
+namespace SWRIS.Converters
+{
+    public class PercentageConverter : IValueConverter
+    {
+        /// <summary>
+        /// 将值转换为百分比显示(例如:0.85 -> "85%")
+        /// </summary>
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            if (value == null) return "0";
+            try
+            {
+                // 支持多种数值类型
+                double numericValue = System.Convert.ToDouble(value);
+                // 检查是否有参数指定小数位数
+                int decimalPlaces = 0;
+                if (parameter != null && int.TryParse(parameter.ToString(), out int places))
+                {
+                    decimalPlaces = places;
+                }
+                // 转换为百分比(乘以100)
+                double percentage = numericValue * 100;
+                // 格式化输出
+                string format = $"F{decimalPlaces}";
+                return percentage.ToString(format);
+            }
+            catch
+            {
+                return "0";
+            }
+        }
+
+        /// <summary>
+        /// 将百分比字符串转换回数值(例如:"85%" -> 0.85)
+        /// </summary>
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            if (value == null) return 0.0;
+
+            string stringValue = value.ToString();
+
+            try
+            {
+                // 移除百分比符号和空格
+                stringValue = stringValue.Replace("%", "").Trim();
+
+                if (double.TryParse(stringValue, NumberStyles.Any, culture, out double result))
+                {
+                    // 转换回小数(除以100)
+                    return result / 100.0;
+                }
+
+                return 0.0;
+            }
+            catch
+            {
+                return 0.0;
+            }
+        }
+    }
+}

+ 39 - 0
Converters/ResetTypeToBooleanConverter.cs

@@ -0,0 +1,39 @@
+using System;
+using System.Globalization;
+using System.Windows.Data;
+
+namespace SWRIS.Converters
+{
+    public class ResetTypeToBooleanConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            if (value == null || parameter == null) { return false; }
+
+            if (value is short valueResetType && short.TryParse(parameter.ToString(), out short parameterResetType))
+            {
+                if (parameterResetType == 1)
+                {
+                    return valueResetType >= parameterResetType;
+                }
+                return valueResetType == parameterResetType;
+            }
+            return false;
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            if (value == null || parameter == null)
+                return 0;
+
+            if (value is bool boolValue && boolValue)
+            {
+                if (short.TryParse(parameter.ToString(), out short parameterResetType))
+                {
+                    return parameterResetType;
+                }
+            }
+            return 0;
+        }
+    }
+}

+ 63 - 0
Converters/RiskLevelToColorConverter.cs

@@ -0,0 +1,63 @@
+using SWRIS.Enums;
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Windows.Data;
+using System.Windows.Media;
+
+namespace SWRIS.Converters
+{
+    public class RiskLevelToColorConverter : IValueConverter
+    {
+        private static readonly Dictionary<string, SolidColorBrush> _brushCache = new Dictionary<string, SolidColorBrush>();
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            if (value is RiskLevel risk)
+            {
+                double opacity = parameter != null ? System.Convert.ToDouble(parameter) : 1;
+                string cacheKey = $"{risk}_{opacity}";
+                // 如果缓存中存在,直接返回
+                if (_brushCache.TryGetValue(cacheKey, out var cachedBrush))
+                {
+                    return cachedBrush;
+                }
+                SolidColorBrush colorBrush;
+                switch (risk)
+                {
+                    case RiskLevel.Normal:
+                    case RiskLevel.Mild:
+                        colorBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#00FF78"));
+                        break;
+                    case RiskLevel.Light:
+                        colorBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#FFF000"));
+                        break;
+                    case RiskLevel.Moderate:
+                        colorBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#FF9400"));
+                        break;
+                    case RiskLevel.Severe:
+                        colorBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#FF6C00"));
+                        break;
+                    case RiskLevel.Critical:
+                        colorBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#FF0000"));
+                        break;
+                    case RiskLevel.ExceededLimit:
+                        colorBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#FF008A"));
+                        break;
+                    default:
+                        colorBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#00FF78"));
+                        break;
+                }
+                colorBrush.Opacity = opacity;
+                colorBrush.Freeze();
+                _brushCache[cacheKey] = colorBrush;
+                return colorBrush;
+            }
+            return Brushes.Transparent;
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 27 - 0
Converters/RiskLevelToTextConverter.cs

@@ -0,0 +1,27 @@
+using SWRIS.Enums;
+using SWRIS.Extensions;
+using System;
+using System.Globalization;
+using System.Windows.Data;
+
+namespace SWRIS.Converters
+{
+    public class RiskLevelToTextConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            var riskLevel = ((RiskLevel)value).GetDescription();
+            var texts = riskLevel.ToCharArray();
+            if (parameter != null && int.TryParse(parameter.ToString(), out int index) && index >= 0 && index < texts.Length)
+            {
+                return texts[index];
+            }
+            return riskLevel;
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 25 - 0
Converters/StringEmptyToVisibilityConverter.cs

@@ -0,0 +1,25 @@
+using SWRIS.Extensions;
+using System;
+using System.Globalization;
+using System.Windows;
+using System.Windows.Data;
+
+namespace SWRIS.Converters
+{
+    public class StringEmptyToVisibilityConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            if (value == null || !(value is string message))
+            {
+                return Visibility.Collapsed;
+            }
+            return message.IsNullOrEmpty() ? Visibility.Collapsed : Visibility.Visible;
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 35 - 0
Converters/StringToColorConverter.cs

@@ -0,0 +1,35 @@
+using System;
+using System.Globalization;
+using System.Windows.Data;
+using System.Windows.Media;
+
+namespace SWRIS.Converters
+{
+    public class StringToColorConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            if (value is string colorString && !string.IsNullOrEmpty(colorString))
+            {
+                try
+                {
+                    return (Color)ColorConverter.ConvertFromString(colorString);
+                }
+                catch
+                {
+                    return Colors.Black; // 默认颜色
+                }
+            }
+            return Colors.Black;
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            if (value is Color color)
+            {
+                return $"#{color.R:X2}{color.G:X2}{color.B:X2}";
+            }
+            return "#000000";
+        }
+    }
+}

+ 24 - 0
Converters/StringToImageSourceConverter.cs

@@ -0,0 +1,24 @@
+using System;
+using System.Globalization;
+using System.Windows.Data;
+using System.Windows.Media.Imaging;
+
+namespace SWRIS.Converters
+{
+    public class StringToImageSourceConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            if (value is string path)
+            {
+                return new BitmapImage(new Uri($"pack://application:,,,/{path}"));
+            }
+            return null;
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 273 - 0
Core/CalibrationCore.cs

@@ -0,0 +1,273 @@
+using HslCommunication;
+using HslCommunication.Core;
+using HslCommunication.ModBus;
+using HslCommunication.Profinet.Siemens;
+using SWRIS.Enums;
+using SWRIS.Models;
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace SWRIS.Core
+{
+    public class CalibrationCore : IDisposable
+    {
+        private IReadWriteNet moduleDevice;
+        private readonly CalibrationSettings config;
+        private readonly ModuleStateModel moduleState;
+        private readonly CancellationTokenSource ctsToken = new CancellationTokenSource();
+        public bool IsConnected { get; set; }
+        public event EventHandler<LimitData> LimitStateChanged;
+        public event EventHandler<bool> ConnectionStateChanged;
+        public CalibrationCore(CalibrationSettings config)
+        {
+            this.config = config;
+            moduleState = new ModuleStateModel(false);
+            InitializeModule();
+            ConnectServer();
+            ThreadPool.QueueUserWorkItem(o => ReadLimitData(ctsToken.Token));
+        }
+        private void InitializeModule()
+        {
+            // 根据模块类型创建相应的设备实例
+            switch (config.ModuleType)
+            {
+                case ModuleType.S1200:
+                    moduleDevice = new SiemensS7Net(SiemensPLCS.S1200)
+                    {
+                        IpAddress = config.Tcp.IpAddress,
+                        Port = config.Tcp.Port,
+                        ConnectTimeOut = 3000,
+                        ReceiveTimeOut = 2000
+                    };
+                    break;
+
+                case ModuleType.S1500:
+                    moduleDevice = new SiemensS7Net(SiemensPLCS.S1500)
+                    {
+                        IpAddress = config.Tcp.IpAddress,
+                        Port = config.Tcp.Port,
+                        ConnectTimeOut = 3000,
+                        ReceiveTimeOut = 2000
+                    };
+                    break;
+
+                case ModuleType.S300:
+                    moduleDevice = new SiemensS7Net(SiemensPLCS.S300)
+                    {
+                        IpAddress = config.Tcp.IpAddress,
+                        Port = config.Tcp.Port,
+                        ConnectTimeOut = 3000,
+                        ReceiveTimeOut = 2000
+                    };
+                    break;
+
+                case ModuleType.S400:
+                    moduleDevice = new SiemensS7Net(SiemensPLCS.S400)
+                    {
+                        IpAddress = config.Tcp.IpAddress,
+                        Port = config.Tcp.Port,
+                        ConnectTimeOut = 3000,
+                        ReceiveTimeOut = 2000
+                    };
+                    break;
+
+                case ModuleType.S200:
+                    moduleDevice = new SiemensS7Net(SiemensPLCS.S200)
+                    {
+                        IpAddress = config.Tcp.IpAddress,
+                        Port = config.Tcp.Port,
+                        ConnectTimeOut = 3000,
+                        ReceiveTimeOut = 2000
+                    };
+                    break;
+
+                case ModuleType.S200Smart:
+                    moduleDevice = new SiemensS7Net(SiemensPLCS.S200Smart)
+                    {
+                        IpAddress = config.Tcp.IpAddress,
+                        Port = config.Tcp.Port,
+                        ConnectTimeOut = 3000,
+                        ReceiveTimeOut = 2000
+                    };
+                    break;
+
+                case ModuleType.ModbusTcp:
+                    moduleDevice = new ModbusTcpNet(config.Tcp.IpAddress, config.Tcp.Port, config.Tcp.Station)
+                    {
+                        ConnectTimeOut = 3000,
+                        ReceiveTimeOut = 2000
+                    };
+                    break;
+
+                case ModuleType.ModbusRtu:
+                    moduleDevice = new ModbusRtu(config.Serial.Station);
+                    (moduleDevice as ModbusRtu).SerialPortInni(config.Serial.PortName,
+                                                               config.Serial.BaudRate,
+                                                               config.Serial.DataBits,
+                                                               config.Serial.StopBits,
+                                                               config.Serial.Parity);
+                    break;
+
+                default:
+                    throw new NotSupportedException($"不支持的模块类型: {config.ModuleType}");
+            }
+        }
+        private async void ReadLimitData(CancellationToken token)
+        {
+            OperateResult<bool> readLimitResult;
+            while (!token.IsCancellationRequested)
+            {
+                try
+                {
+                    if (IsConnected)
+                    {
+                        foreach (var limit in config.Limits)
+                        {
+                            if (!limit.IsEnable)
+                                continue;
+                            readLimitResult = await moduleDevice.ReadBoolAsync(limit.Address);
+                            if (StateMonitor(readLimitResult))
+                            {
+                                var limitState = (limit.IsReverse ? !readLimitResult.Content : readLimitResult.Content) ? LimitState.AtLimit : LimitState.NotInLimit;
+                                if (limit.State != limitState)
+                                {
+                                    limit.State = limitState;
+                                    LimitStateChanged?.Invoke(this, limit);
+                                }
+                            }
+                            await Task.Delay(50);
+                        }
+                    }
+                }
+                catch (Exception ex)
+                {
+                    LogHelper.Error("读取限位信号时发生错误:" + ex.Message, ex);
+                }
+                finally
+                {
+                    Thread.Sleep(200);
+                }
+            }
+        }
+        private bool StateMonitor(OperateResult<bool> readLimitResult)
+        {
+            if (readLimitResult.IsSuccess)
+            {
+                moduleState.Depth = moduleState.Depth < 0 ? 1 : moduleState.Depth > 20 ? 20 : moduleState.Depth + 1;
+                if (!moduleState.IsOnline && moduleState.Depth >= 3)
+                {
+                    ChangeModuleState(true);
+                }
+                return true;
+            }
+            else
+            {
+                moduleState.Depth = moduleState.Depth > 0 ? -1 : moduleState.Depth < -20 ? -20 : moduleState.Depth - 1;
+                if (moduleState.IsOnline && moduleState.Depth <= -3)
+                {
+                    ChangeModuleState(false);
+                }
+                return false;
+            }
+        }
+        private void ChangeModuleState(bool isOnline)
+        {
+            moduleState.IsOnline = isOnline;
+            moduleState.Depth = 0;
+            ConnectionStateChanged?.Invoke(this, isOnline);
+        }
+        private bool ConnectDevice()
+        {
+            try
+            {
+                if (moduleDevice is SiemensS7Net siemensDevice)
+                {
+                    return siemensDevice.ConnectServer().IsSuccess;
+                }
+                else if (moduleDevice is ModbusTcpNet modbusTcpDevice)
+                {
+                    return modbusTcpDevice.ConnectServer().IsSuccess;
+                }
+                else if (moduleDevice is ModbusRtu modbusRtuDevice)
+                {
+                    return modbusRtuDevice.Open().IsSuccess;
+                }
+                return false;
+            }
+            catch (Exception ex)
+            {
+                LogHelper.Error($"设备连接异常: {ex.Message}", ex);
+                return false;
+            }
+        }
+        private void CloseDeviceConnection()
+        {
+            try
+            {
+                if (moduleDevice is SiemensS7Net siemensDevice)
+                {
+                    siemensDevice.ConnectClose();
+                }
+                else if (moduleDevice is ModbusTcpNet modbusTcpDevice)
+                {
+                    modbusTcpDevice.ConnectClose();
+                }
+                else if (moduleDevice is ModbusRtu modbusRtuDevice)
+                {
+                    modbusRtuDevice.Close();
+                }
+                if (moduleDevice is IDisposable disposable)
+                {
+                    disposable.Dispose();
+                }
+            }
+            catch (Exception ex)
+            {
+                LogHelper.Error($"关闭设备连接异常: {ex.Message}", ex);
+            }
+            finally
+            {
+                moduleDevice = null;
+                IsConnected = false;
+            }
+        }
+
+        public void ConnectServer()
+        {
+            ThreadPool.QueueUserWorkItem(action =>
+            {
+                while (!ctsToken.IsCancellationRequested)
+                {
+                    if (!IsConnected)
+                    {
+                        try
+                        {
+                            if (ConnectDevice())
+                            {
+                                IsConnected = true;
+                                LogHelper.Info($"连接成功: {config.ModuleType} - {config.Tcp.IpAddress}:{config.Tcp.Port}");
+                            }
+                        }
+                        catch (Exception ex)
+                        {
+                            LogHelper.Error($"连接异常: {ex.Message}", ex);
+                        }
+                    }
+                    Thread.Sleep(5000);
+                }
+            });
+        }
+
+        public void CloseServer()
+        {
+            CloseDeviceConnection();
+        }
+        public void Dispose()
+        {
+            ctsToken?.Cancel();
+            ctsToken?.Dispose();
+            CloseDeviceConnection();
+        }
+    }
+}

+ 166 - 0
Core/ConfigHelper.cs

@@ -0,0 +1,166 @@
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using SWRIS.Models;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading;
+
+namespace SWRIS.Core
+{
+    public class ConfigHelper
+    {
+        public static readonly string AesKey = "mhSnwPLLL5T6ITmLJQogp3et5U2gYw5wiLtdZaZajRU="; // 32字节的Base64字符串
+        public static readonly string AesIV = "Z/stNJl+kA7G02oBlpeERA==";  // 16字节的Base64字符串
+        private static readonly string filePath = AppDomain.CurrentDomain.BaseDirectory + "Config/config.json";
+        private static readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();
+        private const int LockTimeout = 5000; // 5秒超时
+
+        public static ConfigModel Read()
+        {
+            try
+            {
+                if (!_lock.TryEnterReadLock(LockTimeout))
+                {
+                    LogHelper.Error("获取读取锁超时!");
+                    return null;
+                }
+
+                try
+                {
+                    if (File.Exists(filePath))
+                    {
+                        using (StreamReader r = new StreamReader(filePath))
+                        {
+                            var json = r.ReadToEnd();
+                            var config = JsonConvert.DeserializeObject<ConfigModel>(json);
+                            if (config.Calibration.Tcp == null)
+                            {
+                                config.Calibration.Tcp = new TcpSettings();
+                            }
+                            if (config.Calibration.Serial == null)
+                            {
+                                config.Calibration.Serial = new SerialSettings();
+                            }
+                            if (config.Calibration.Limits == null)
+                            {
+                                config.Calibration.Limits = new List<LimitData> { new LimitData(), new LimitData() };
+                            }
+                            else
+                            {
+                                // 补充缺少的元素
+                                while (config.Calibration.Limits.Count < 2)
+                                {
+                                    config.Calibration.Limits.Add(new LimitData());
+                                }
+                            }
+                            return config;
+                        }
+                    }
+                    return null;
+                }
+                finally
+                {
+                    _lock.ExitReadLock();
+                }
+            }
+            catch (Exception ex)
+            {
+                LogHelper.Error("读取配置文件失败!", ex);
+                return null;
+            }
+        }
+
+        public static bool Set(ConfigModel config)
+        {
+            try
+            {
+                if (!_lock.TryEnterWriteLock(LockTimeout))
+                {
+                    LogHelper.Error("获取写入锁超时!");
+                    return false;
+                }
+
+                try
+                {
+                    string jsonData = JsonConvert.SerializeObject(config, Formatting.Indented);
+                    File.WriteAllText(filePath, jsonData);
+                    return true;
+                }
+                finally
+                {
+                    _lock.ExitWriteLock();
+                }
+            }
+            catch (Exception ex)
+            {
+                LogHelper.Error("保存配置文件失败!", ex);
+                return false;
+            }
+        }
+
+        public static bool SetEquipment(EquipmentModel equipmentData)
+        {
+            // 参数检查
+            if (equipmentData == null || equipmentData.Parameter == null || string.IsNullOrEmpty(equipmentData.IpAddress))
+            {
+                return false;
+            }
+
+            try
+            {
+                // 获取写入锁
+                if (!_lock.TryEnterWriteLock(LockTimeout))
+                {
+                    LogHelper.Error("获取写入锁超时!");
+                    return false;
+                }
+
+                try
+                {
+                    // 检查文件是否存在
+                    if (!File.Exists(filePath))
+                    {
+                        return false;
+                    }
+
+                    // 读取JSON文件
+                    JObject jsonObject;
+                    using (StreamReader reader = File.OpenText(filePath))
+                    using (JsonTextReader jsonTextReader = new JsonTextReader(reader))
+                    {
+                        jsonObject = (JObject)JToken.ReadFrom(jsonTextReader);
+                    }
+
+                    // 获取设备数组
+                    if (!(jsonObject["Equipments"] is JArray equipments)) return false;
+
+                    // 查找要修改的设备
+                    var targetEquipment = equipments.FirstOrDefault(c =>
+                        c["IpAddress"] != null &&
+                        c["IpAddress"].ToString() == equipmentData.IpAddress);
+
+                    if (targetEquipment == null) return false;
+
+                    // 关键修改点:先移除旧设备,再添加新设备
+                    equipments.Remove(targetEquipment);
+                    equipments.Add(JObject.FromObject(equipmentData));
+
+                    string output = JsonConvert.SerializeObject(jsonObject, Formatting.Indented);
+                    File.WriteAllText(filePath, output);
+                    return true;
+                }
+                finally
+                {
+                    _lock.ExitWriteLock();
+                }
+            }
+            catch (Exception ex)
+            {
+                LogHelper.Error("保存配置文件失败!", ex);
+                return false;
+            }
+        }
+    }
+}

+ 1714 - 0
Core/TcpServerFrame.cs

@@ -0,0 +1,1714 @@
+using ScottPlot.Colormaps;
+using SWRIS.Enums;
+using SWRIS.Events;
+using SWRIS.Extensions;
+using SWRIS.Models;
+using SWRIS.Models.Data;
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+using System.Security.Cryptography;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace SWRIS.Core
+{
+    public class TcpServerFrame : IDisposable
+    {
+        // 定义事件
+        public event EventHandler<ClientConnectedEventArgs> ClientConnected;
+        public event EventHandler<ClientDisconnectedEventArgs> ClientDisconnected;
+        public event EventHandler<LiveStreamReceivedEventArgs> LiveStreamReceived;
+        public event EventHandler<AlarmDataReceivedEventArgs> AlarmDataReceived;
+        public event EventHandler<FaultDataReceivedEventArgs> FaultDataReceived;
+        public event EventHandler<DetectionDataReceivedEventArgs> DetectionDataReceived;
+        public event EventHandler<RealTimeDataReceivedEventArgs> RealTimeDataReceived;
+        public event EventHandler<DetectionRawDataReceivedEventArgs> DetectionRawDataReceived;
+        public event EventHandler<ResetSystemDataReceivedEventArgs> ResetSystemDataReceived;
+        public event EventHandler<SetAbsolutePositionDataReceivedEventArgs> SetAbsolutePositionDataReceived;
+        public event EventHandler<DetectionRawDataResultReceivedEventArgs> DetectionRawDataResultReceived;
+        public event EventHandler<DetectionStatusResultReceivedEventArgs> DetectionStatusResultReceived;
+        public event EventHandler<UpgradedResultReceivedEventArgs> UpgradedRequestResultReceived;
+        public event EventHandler<ClockResultReceivedEventArgs> ClockResultReceived;
+        public event EventHandler<EncoderDirectionResultReceivedEventArgs> EncoderDirectionResultReceived;
+        public event EventHandler<HeartbeatReceviedEventArgs> HeartbeatReceived;
+        public event EventHandler<DebugMessageReceivedEventArgs> DebugMessageReceived;
+
+        private Socket _listener;
+        private bool _isRunning;
+        private readonly string _ipAddress;
+        private readonly int _port;
+        private readonly ByteTransform byteTransform;
+        private const string PASSWORD = "WNDTM4";
+        private static ConcurrentDictionary<string, ClientState> _connectedClients;
+        public TcpServerFrame(string ipAddress, int port)
+        {
+            _ipAddress = ipAddress;
+            _port = port;
+            byteTransform = new ByteTransform(DataFormat.DCBA);
+            _connectedClients = new ConcurrentDictionary<string, ClientState>();
+        }
+        public void Start()
+        {
+            _isRunning = true;
+            _listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+            try
+            {
+                // 使用指定的IP地址和端口
+                IPAddress ipAddress = IPAddress.Parse(_ipAddress);
+                _listener.Bind(new IPEndPoint(ipAddress, _port));
+
+                _listener.Listen(10);
+
+                // 启动心跳检测线程
+                StartHeartbeatCheck();
+
+                // 开始接受客户端连接(异步)
+                _listener.BeginAccept(OnClientConnected, null);
+
+                DebugMessageReceived?.Invoke(this, new DebugMessageReceivedEventArgs($"服务已在 {_ipAddress}:{_port} 上启动"));
+            }
+            catch (SocketException ex)
+            {
+                LogHelper.Error($"启动服务时发生错误: {ex.Message}");
+                if (ex.SocketErrorCode == SocketError.AddressAlreadyInUse)
+                {
+                    LogHelper.Error("端口已被占用,请更换端口或关闭占用该端口的程序", ex);
+                }
+                else if (ex.SocketErrorCode == SocketError.AddressNotAvailable)
+                {
+                    LogHelper.Error($"IP地址 {_ipAddress} 不可用,请检查网络配置", ex);
+                }
+            }
+            catch (Exception ex)
+            {
+                LogHelper.Error($"TCP服务启动时发生未知错误: {ex.Message}", ex);
+            }
+        }
+
+        public void Stop()
+        {
+            _isRunning = false;
+            _listener.Shutdown(SocketShutdown.Both);
+            _listener?.Close();
+        }
+
+        private void OnClientConnected(IAsyncResult ar)
+        {
+            if (!_isRunning)
+                return;
+            try
+            {
+                Socket clientSocket = _listener.EndAccept(ar);
+                string ipAddress = (clientSocket.RemoteEndPoint as IPEndPoint)?.Address?.ToString();
+                var equipmentData = App.Config.Equipments.FirstOrDefault(c => c.IpAddress == ipAddress);
+                if (equipmentData != null)
+                {
+                    ClientState client = new ClientState(clientSocket, ipAddress, equipmentData.SerialNo);
+                    // 添加到连接列表
+                    _connectedClients.TryAdd(client.IpAddress, client);
+                    // 开始异步接收数据
+                    clientSocket.BeginReceive(client.Buffer, 0, client.Buffer.Length, SocketFlags.None, OnDataReceived, client);
+                    // 触发客户端连接事件
+                    ClientConnected?.Invoke(this, new ClientConnectedEventArgs(clientSocket, ipAddress));
+                }
+                // 继续接受新连接
+                _listener.BeginAccept(OnClientConnected, null);
+            }
+            catch (Exception ex)
+            {
+                Console.WriteLine($"Error accepting client: {ex.Message}");
+            }
+        }
+        private void StartHeartbeatCheck()
+        {
+            ThreadPool.QueueUserWorkItem(act =>
+            {
+                while (_isRunning)
+                {
+                    Thread.Sleep(TimeSpan.FromSeconds(5)); // 每5秒检测一次
+
+                    foreach (var client in _connectedClients.ToArray())
+                    {
+                        if ((DateTime.UtcNow - client.Value.LastActiveTime).TotalSeconds > 15)
+                        {
+                            SafeCloseClient(client.Value);
+                            DebugMessageReceived?.Invoke(this,
+                                new DebugMessageReceivedEventArgs($"心跳超时,强制断开", ipAddress: client.Value.IpAddress));
+                        }
+                    }
+                }
+            });
+        }
+
+        private async void OnDataReceived(IAsyncResult ar)
+        {
+            var state = (ClientState)ar.AsyncState;
+            if (state == null || state.ClientSocket == null || !state.ClientSocket.Connected)
+            {
+                return;
+            }
+
+            try
+            {
+                int bytesRead = state.ClientSocket.EndReceive(ar);
+                if (bytesRead > 0)
+                {
+                    // 将接收到的数据添加到缓冲区
+                    state.ReceiveBuffer.Write(state.Buffer, 0, bytesRead);
+
+                    // 处理缓冲区中的所有数据包
+                    while (state.ReceiveBuffer.Length > 0)
+                    {
+                        byte packetType = state.ReceiveBuffer.PeekByte();
+
+                        if (packetType == 0xC0) // 心跳包
+                        {
+                            ProcessHeartbeatPacket(state);
+                        }
+                        else if (packetType == 0x30) // 数据包
+                        {
+                            bool processed = await ProcessDataPacket(state);
+                            if (!processed) break; // 数据包不完整,等待更多数据
+                        }
+                        else
+                        {
+                            // 未知包类型,跳过1字节继续检查
+                            DebugMessageReceived?.Invoke(this,
+                                new DebugMessageReceivedEventArgs($"未知包类型: 0x{packetType:X2}", state.IpAddress));
+                            state.ReceiveBuffer.Remove(1);
+                        }
+                    }
+
+                    // 继续接收数据
+                    if (state.ClientSocket?.Connected == true)
+                    {
+                        Array.Clear(state.Buffer, 0, state.Buffer.Length);
+                        state.ClientSocket.BeginReceive(state.Buffer, 0, state.Buffer.Length,
+                            SocketFlags.None, OnDataReceived, state);
+                    }
+                }
+                else
+                {
+                    // 客户端断开连接
+                    DebugMessageReceived?.Invoke(this,
+                        new DebugMessageReceivedEventArgs((bytesRead == 0 ? "设备主动断开连接" : "连接异常或错误") + $"({bytesRead})", state.IpAddress));
+                    SafeCloseClient(state);
+                }
+            }
+            catch (SocketException sex)
+            {
+                HandleSocketException(sex, state);
+            }
+            catch (Exception ex)
+            {
+                var message = $"接收数据时错误: {ex.Message}";
+                LogHelper.Error(message, ex);
+                SafeCloseClient(state);
+            }
+        }
+        private void HandleSocketException(SocketException sex, ClientState client)
+        {
+            string message;
+
+            switch (sex.SocketErrorCode)
+            {
+                case SocketError.ConnectionReset:
+                    message = "设备强制断开连接 (Connection Reset)";
+                    break;
+
+                case SocketError.OperationAborted:
+                    message = "操作被中止";
+                    break;
+
+                case SocketError.ConnectionAborted:
+                    message = "连接被中止";
+                    break;
+
+                case SocketError.TimedOut:
+                    message = "连接超时";
+                    break;
+
+                case SocketError.Shutdown:
+                    message = "连接已关闭";
+                    break;
+
+                case SocketError.NetworkReset:
+                    message = "网络连接被重置";
+                    break;
+
+                case SocketError.HostUnreachable:
+                    message = "主机不可达";
+                    break;
+
+                default:
+                    message = $"Socket错误: {sex.SocketErrorCode} - {sex.Message}";
+                    break;
+            }
+
+            DebugMessageReceived?.Invoke(this,
+                new DebugMessageReceivedEventArgs(message, client.IpAddress));
+            LogHelper.Error(message, sex);
+            SafeCloseClient(client);
+        }
+        private void ProcessHeartbeatPacket(ClientState client)
+        {
+            // 心跳包固定长度为2字节: 0xC0 + 0x00
+            if (client.ReceiveBuffer.Length < 2)
+                return; // 等待更多数据
+
+            byte[] heartBeatData = new byte[2];
+            client.ReceiveBuffer.Read(heartBeatData, 0, 2);
+
+            client.LastActiveTime = DateTime.UtcNow;
+            RespondHeartbeat(heartBeatData, client.IpAddress, client.ClientSocket);
+
+            HeartbeatReceived?.Invoke(this, new HeartbeatReceviedEventArgs(client.IpAddress));
+
+        }
+
+        private async Task<bool> ProcessDataPacket(ClientState client)
+        {
+            // 检查是否有足够的数据读取长度信息
+            if (client.ReceiveBuffer.Length < 5) // 至少需要包类型 + 最小长度信息
+                return false;
+
+            // 窥探前10个字节来解析长度
+            byte[] peekData = new byte[Math.Min(client.ReceiveBuffer.Length, 10)];
+            int peeked = client.ReceiveBuffer.Peek(peekData, 0, peekData.Length);
+
+            // 解析剩余长度
+            (int byteLength, int remainingLength) = GetRemainingLength(peekData);
+            if (byteLength == 0 || remainingLength == 0)
+            {
+                // 长度解析失败,可能是数据损坏,跳过1字节
+                client.ReceiveBuffer.Remove(1);
+                return true;
+            }
+
+            // 计算完整包长度
+            int totalPacketLength = 1 + byteLength + remainingLength;
+
+            // 检查是否收到完整包
+            if (client.ReceiveBuffer.Length < totalPacketLength)
+                return false;
+
+            // 读取完整数据包
+            byte[] packetData = new byte[totalPacketLength];
+            client.ReceiveBuffer.Read(packetData, 0, totalPacketLength);
+
+            // 提取事件类型和有效载荷
+            int payloadStart = 1 + byteLength;
+            if (payloadStart >= packetData.Length)
+            {
+                DebugMessageReceived?.Invoke(this,
+                    new DebugMessageReceivedEventArgs($"数据异常", client.IpAddress));
+                return true;
+            }
+
+            byte eventType = packetData[payloadStart];
+            byte[] payload = new byte[remainingLength];
+            Buffer.BlockCopy(packetData, payloadStart, payload, 0, remainingLength);
+
+            // 处理数据包
+            await ProcessEventByType(eventType, payload, client);
+
+            return true;
+        }
+        /// <summary>
+        /// 根据事件类型处理数据
+        /// </summary>
+        /// <param name="eventType"></param>
+        /// <param name="payload"></param>
+        /// <param name="client"></param>
+        /// <returns></returns>
+        private async Task ProcessEventByType(byte eventType, byte[] payload, ClientState client)
+        {
+            switch (eventType)
+            {
+                case 0x01:
+                    await ProcessSyncParameters(payload, client);
+                    break;
+                case 0x06:
+                    await ProcessDetectionStatus(payload, client);
+                    break;
+                case 0x07:
+                    await ProcessFaultData(payload, client);
+                    break;
+                case 0x08:
+                    await ProcessAlarmData(payload, client);
+                    break;
+                case 0x09:
+                    await ProcessDetectionData(payload, client);
+                    break;
+                case 0x10:
+                    await ProcessClockData(payload, client);
+                    break;
+                case 0x11:
+                    await ProcessUpgradedData(payload, client);
+                    break;
+                case 0x12:
+                    await ProcessEncoderDirectionData(payload, client);
+                    break;
+                case 0x13:
+                    await ProcessTwistFactorData(payload, client);
+                    break;
+                case 0x0A:
+                    await ProcessOpenOrClosedRealtimeData(payload, client);
+                    break;
+                case 0x0B:
+                    await ProcessRealTimeData(payload, client);
+                    break;
+                case 0x0D:
+                    await ProcessLiveStreamData(payload, client);
+                    break;
+                case 0x0C:
+                    await ProcessTurnLiveStreamData(payload, client);
+                    break;
+                case 0x0E:
+                    await ProcessDetectionRawResultData(payload, client);
+                    break;
+                case 0x0F:
+                    await ProcessDetectionRawData(payload, client);
+                    break;
+                case 0x14:
+                    await ProcessSystemResetData(payload, client);
+                    break;
+                case 0x15:
+                    await ProcessSetAbsolutePositionData(payload, client);
+                    break;
+                case 0x16:
+                    await ProcessSerialNoData(payload, client);
+                    break;
+            }
+        }
+        /// <summary>
+        /// 设置实时位置
+        /// </summary>
+        /// <param name="buffer"></param>
+        /// <param name="client"></param>
+        /// <returns></returns>
+        private async Task ProcessSetAbsolutePositionData(byte[] buffer, ClientState client)
+        {
+            var resetSystemData = ReceiveSetAbsolutePositionData(buffer);
+            if (resetSystemData != null)
+            {
+                // 设置当前实时位置应答
+                SetAbsolutePositionDataReceived?.Invoke(this,
+                    new SetAbsolutePositionDataReceivedEventArgs(client.IpAddress, resetSystemData));
+            }
+            await Task.CompletedTask;
+        }
+        /// <summary>
+        /// 主板复位
+        /// </summary>
+        /// <param name="buffer"></param>
+        /// <param name="client"></param>
+        /// <returns></returns>
+        private async Task ProcessSystemResetData(byte[] buffer, ClientState client)
+        {
+            var resetSystemData = ReceiveResetSystemData(buffer);
+            if (resetSystemData != null)
+            {
+                // 主板复位结果
+                ResetSystemDataReceived?.Invoke(this,
+                    new ResetSystemDataReceivedEventArgs(client.IpAddress, resetSystemData));
+            }
+            await Task.CompletedTask;
+        }
+
+        private async Task ProcessDetectionRawData(byte[] buffer, ClientState client)
+        {
+            await Task.Run(() =>
+            {
+                var detectionRawData = ReceiveDetectionRawData(buffer);
+                if (detectionRawData != null)
+                {
+                    // 处理检测原始数据
+                    DetectionRawDataReceived?.Invoke(this,
+                        new DetectionRawDataReceivedEventArgs(client.IpAddress, detectionRawData));
+                }
+            });
+            await Task.CompletedTask;
+        }
+        private async Task ProcessDetectionRawResultData(byte[] buffer, ClientState client)
+        {
+            await Task.Run(() =>
+            {
+                var detectionRawResultData = ReceiveDetectionRawResultData(buffer);
+                if (detectionRawResultData != null)
+                {
+                    DetectionRawDataResultReceived?.Invoke(this,
+                        new DetectionRawDataResultReceivedEventArgs(client.IpAddress, detectionRawResultData));
+                }
+            });
+            await Task.CompletedTask;
+        }
+        private async Task ProcessTurnLiveStreamData(byte[] buffer, ClientState client)
+        {
+            var liveStreamStatus = ReceiveTurnLiveStreamData(buffer);
+            if (liveStreamStatus > 0)
+            {
+                // 开启/关闭结果,0=成功,>0=失败-原因码
+                LogHelper.Error($"开启/关闭实时流数据失败,原因码:{liveStreamStatus}");
+            }
+            await Task.CompletedTask;
+        }
+        private async Task ProcessLiveStreamData(byte[] buffer, ClientState client)
+        {
+            await Task.Run(() =>
+            {
+                var liveStreamData = ReceiveLiveStreamDataModel(buffer);
+                if (liveStreamData != null && liveStreamData.Data.Any())
+                {
+                    Console.WriteLine($"接收实时流数据: {liveStreamData.ToString()}");
+                    // 触发事件
+                    LiveStreamReceived?.Invoke(this,
+                        new LiveStreamReceivedEventArgs(client.IpAddress, liveStreamData));
+                }
+            });
+            await Task.CompletedTask;
+        }
+
+        private async Task ProcessRealTimeData(byte[] buffer, ClientState client)
+        {
+            await Task.Run(() =>
+            {
+                var realTimeData = ReceiveRealTimeData(buffer);
+                if (realTimeData != null)
+                {
+                    // 触发实时数据接收事件
+                    RealTimeDataReceived?.Invoke(this,
+                        new RealTimeDataReceivedEventArgs(client.IpAddress, realTimeData));
+                }
+            });
+            await Task.CompletedTask;
+        }
+        private async Task ProcessOpenOrClosedRealtimeData(byte[] buffer, ClientState client)
+        {
+            var openOrClosed = ReceiveOpenOrClosedRealtimeData(buffer);
+            if (openOrClosed > 0)
+            {
+                LogHelper.Error("开启或关闭实时数据失败,原因码:" + openOrClosed);
+            }
+            await Task.CompletedTask;
+        }
+        private async Task ProcessTwistFactorData(byte[] buffer, ClientState client)
+        {
+            var twistFactorData = ReceiveTwistFactorData(buffer);
+            if (twistFactorData != null)
+            {
+                var equipmentData = App.Config.Equipments.FirstOrDefault(c => c.IpAddress == client.IpAddress);
+                if (equipmentData != null && equipmentData.Parameter != null)
+                {
+                    // 处理判伤捻距系数数据
+                    equipmentData.Parameter.TwistFactor = twistFactorData.TwistFactor;
+                }
+            }
+            await Task.CompletedTask;
+        }
+        private async Task ProcessEncoderDirectionData(byte[] buffer, ClientState client)
+        {
+            var encoderDirection = ReceiveEncoderDirectionData(buffer);
+            if (encoderDirection != null)
+            {
+                var equipmentData = App.Config.Equipments.FirstOrDefault(c => c.IpAddress == client.IpAddress);
+                if (equipmentData != null && equipmentData.Parameter != null)
+                {
+                    // 处理编码器方向数据
+                    equipmentData.Parameter.EncoderDirection = encoderDirection.Direction;
+                    // 编码器方向结果返回事件
+                    EncoderDirectionResultReceived?.Invoke(this,
+                        new EncoderDirectionResultReceivedEventArgs(client.IpAddress, encoderDirection.Direction));
+                }
+            }
+            await Task.CompletedTask;
+        }
+        private async Task ProcessClockData(byte[] buffer, ClientState client)
+        {
+            var clockData = ReceiveClockData(buffer);
+            if (clockData != null)
+            {
+                ClockResultReceived?.Invoke(this,
+                    new ClockResultReceivedEventArgs(client.IpAddress, clockData.Time));
+            }
+            await Task.CompletedTask;
+        }
+        private async Task ProcessDetectionData(byte[] buffer, ClientState client)
+        {
+            var detectionData = ReceiveDetectionData(buffer);
+            if (detectionData != null)
+            {
+                // 回复检测结果确认
+                if (SendDetectionConfirm(detectionData.StartTime, detectionData.EndTime, client.SerialNo, client.ClientSocket))
+                {
+                    if (detectionData.DamageCount <= 0)
+                    {
+                        DebugMessageReceived?.Invoke(this, new DebugMessageReceivedEventArgs($"检测结果未发现损伤,本次结果不记录",
+                            serialNo: client.SerialNo));
+                    }
+                    else
+                    {
+                        // 发送获取原始数据请求
+                        SendGetDetectionRawData(client.SerialNo, client.ClientSocket);
+                        // 处理检测数据
+                        DetectionDataReceived?.Invoke(this,
+                            new DetectionDataReceivedEventArgs(client.IpAddress, detectionData));
+                    }
+                }
+            }
+            await Task.CompletedTask;
+        }
+        private async Task ProcessFaultData(byte[] buffer, ClientState client)
+        {
+            var faultData = ReceiveFaultData(buffer);
+            if (faultData != null)
+            {
+                // 处理故障数据
+                FaultDataReceived?.Invoke(this, new FaultDataReceivedEventArgs(client.IpAddress, faultData));
+            }
+            await Task.CompletedTask;
+        }
+        private async Task ProcessAlarmData(byte[] buffer, ClientState client)
+        {
+            var alarmData = ReceiveAlarmData(buffer);
+            if (alarmData != null)
+            {
+                //处理报警数据
+                AlarmDataReceived?.Invoke(this, new AlarmDataReceivedEventArgs(client.IpAddress, alarmData));
+            }
+            await Task.CompletedTask;
+
+        }
+        private async Task ProcessSyncParameters(byte[] data, ClientState client)
+        {
+            var (serialNo, parametersData) = ReceiveSyncParameters(data);
+            if (parametersData != null)
+            {
+                var equipmentData = App.Config.Equipments.FirstOrDefault(c => c.IpAddress == client.IpAddress);
+                if (equipmentData != null)
+                {
+                    equipmentData.SerialNo = serialNo;
+                    equipmentData.Parameter = parametersData;
+
+                    await Task.Run(() =>
+                    {
+                        lock (App.Config.Equipments)
+                        {
+                            ConfigHelper.SetEquipment(equipmentData);
+                        }
+                    });
+                }
+            }
+        }
+        private async Task ProcessDetectionStatus(byte[] buffer, ClientState client)
+        {
+            byte code = ReceiveStartOrStopDetectionResultData(buffer);
+            if (code == 0)
+            {
+                // 启动/停止检测结果,0=成功,>0=失败-原因码
+                DetectionStatusResultReceived?.Invoke(this,
+                    new DetectionStatusResultReceivedEventArgs(client.IpAddress, code));
+            }
+            else
+            {
+                LogHelper.Error($"启动/停止检测结果失败,原因码:{code}");
+            }
+            await Task.CompletedTask;
+        }
+        public async Task ProcessUpgradedData(byte[] buffer, ClientState client)
+        {
+            byte code = ReceiveUpgradedResultData(buffer);
+
+            // 程序更新请求,0=成功,>0=拒绝-原因码
+            UpgradedRequestResultReceived?.Invoke(this,
+                new UpgradedResultReceivedEventArgs(client.IpAddress, code));
+
+            await Task.CompletedTask;
+        }
+        public byte ReceiveTurnLiveStreamData(byte[] buffer)
+        {
+            (string serialNo, byte[] bytes) = GetSerialNoAndData(buffer);
+            return bytes[0];
+        }
+        public byte ReceiveOpenOrClosedRealtimeData(byte[] buffer)
+        {
+            (string serialNo, byte[] bytes) = GetSerialNoAndData(buffer);
+            return bytes[0]; // 开启/关闭结果,0=成功,>0=失败-原因码
+        }
+        public LiveStreamDataModel ReceiveLiveStreamDataModel(byte[] buffer)
+        {
+            (string serialNo, byte[] bytes) = GetSerialNoAndData(buffer);
+            var liveStreamData = new LiveStreamDataModel()
+            {
+                SensorCount = bytes[0],
+                SampleCount = byteTransform.TransUInt16(bytes, 1)
+            };
+
+            var data = bytes.Skip(3).ToArray(); // 跳过前3个字节
+            liveStreamData.Data = ParseSensorData(data, liveStreamData.SensorCount, liveStreamData.SampleCount);
+            return liveStreamData;
+        }
+
+        public TwistFactorDataModel ReceiveTwistFactorData(byte[] buffer)
+        {
+            (string serialNo, byte[] bytes) = GetSerialNoAndData(buffer);
+            var twistFactorData = new TwistFactorDataModel
+            {
+                Type = bytes[0],
+                TwistFactor = byteTransform.TransSingle(bytes, 1) // 判伤捻距系数
+            };
+            return twistFactorData;
+        }
+        /// <summary>
+        /// 发送获取编码器方向请求
+        /// </summary>
+        /// <param name="serialNo"></param>
+        /// <param name="client"></param>
+        /// <returns></returns>
+        public bool SendGetEncoderDirectionData(string serialNo, Socket clientSocket)
+        {
+            try
+            {
+                if (clientSocket == null || !clientSocket.Connected)
+                    return false;
+
+                using (var ms = new MemoryStream())
+                {
+                    ms.WriteByte(0x00); // 操作,0 = 获取编码器方向,1 = 设置编码器方向
+                    ms.WriteByte(0x01); // 编码器方向,1=正向,2=反向
+                    byte[] buffer = CreateData(0x12, ms.ToArray(), serialNo);
+                    DebugMessageReceived?.Invoke(this, new DebugMessageReceivedEventArgs($"获取设备编码器方向", serialNo: serialNo));
+                    clientSocket.Send(buffer);
+                    return true;
+                }
+            }
+            catch (Exception ex)
+            {
+                LogHelper.Error("发送获取编码器方向数据命令时错误", ex);
+                return false;
+            }
+        }
+        /// <summary>
+        /// 发送设置编码器方向数据命令
+        /// </summary>
+        /// <param name="direction"></param>
+        /// <param name="serialNo"></param>
+        /// <param name="stream"></param>
+        /// <returns></returns>
+        public bool SendSetEncoderDirectionData(EncoderDirection direction, string serialNo, Socket clientSocket)
+        {
+            try
+            {
+                if (clientSocket == null || !clientSocket.Connected)
+                    return false;
+
+                using (var ms = new MemoryStream())
+                {
+                    ms.WriteByte(0x01); // 操作,0 = 获取编码器方向,1 = 设置编码器方向
+                    ms.WriteByte((byte)direction); // 编码器方向,1=正向,2=反向
+                    byte[] buffer = CreateData(0x12, ms.ToArray(), serialNo);
+                    DebugMessageReceived?.Invoke(this, new DebugMessageReceivedEventArgs($"设置设备编码器方向", serialNo: serialNo));
+                    clientSocket.Send(buffer);
+                    return true;
+                }
+            }
+            catch (Exception ex)
+            {
+                LogHelper.Error("发送设置编码器方向数据命令时错误", ex);
+                return false;
+            }
+        }
+        /// <summary>
+        /// 发送检测结果原始数据请求
+        /// </summary>
+        /// <param name="serialNo"></param>
+        /// <param name="clientSocket"></param>
+        /// <returns></returns>
+        private bool SendGetDetectionRawData(string serialNo, Socket clientSocket)
+        {
+            try
+            {
+                if (clientSocket == null || !clientSocket.Connected)
+                    return false;
+
+                using (var ms = new MemoryStream())
+                {
+                    ms.WriteByte(0); // 数据类型,0=正常,1=报警,2=故障
+                    WriteBytes(ms, (short)2048); // 单包数据大小,单位:Byte
+                    ms.WriteByte(0x00); // 传输速度控制,单位:KB/s(B/ms),为0则不控速
+                    byte[] buffer = CreateData(0x0E, ms.ToArray(), serialNo);
+                    DebugMessageReceived?.Invoke(this, new DebugMessageReceivedEventArgs($"获取检测结果原始数据", serialNo: serialNo));
+                    clientSocket.Send(buffer);
+                    return true;
+                }
+            }
+            catch (Exception ex)
+            {
+                LogHelper.Error("发送获取编码器方向数据命令时错误", ex);
+                return false;
+            }
+        }
+        /// <summary>
+        /// 接收获取原始数据结果
+        /// </summary>
+        /// <param name="buffer"></param>
+        /// <returns></returns>
+        public DetectionRawResultDataModel ReceiveDetectionRawResultData(byte[] buffer)
+        {
+            (string serialNo, byte[] bytes) = GetSerialNoAndData(buffer);
+            var detectionRawResultData = new DetectionRawResultDataModel
+            {
+                Code = bytes[0],
+                Timestamp = byteTransform.TransInt32(bytes, 1),
+                SamplingStep = byteTransform.TransSingle(bytes, 5),
+                StartAbsolutePosition = byteTransform.TransSingle(bytes, 9),
+                EndAbsolutePosition = byteTransform.TransSingle(bytes, 13),
+                SensorCount = bytes[17]
+            };
+            return detectionRawResultData;
+        }
+        /// <summary>
+        /// 获取原始数据包
+        /// </summary>
+        /// <param name="buffer"></param>
+        /// <returns></returns>
+        public DetectionRawDataModel ReceiveDetectionRawData(byte[] buffer)
+        {
+            (string serialNo, byte[] bytes) = GetSerialNoAndData(buffer);
+            var detectionRawData = new DetectionRawDataModel
+            {
+                PacketNumber = byteTransform.TransUInt32(bytes, 0),
+                TotalPackets = byteTransform.TransUInt32(bytes, 4),
+                DataLength = byteTransform.TransUInt16(bytes, 8),
+                Data = bytes.Skip(10).ToArray() // 跳过前10个字节
+            };
+
+            return detectionRawData;
+        }
+        /// <summary>
+        /// 发送开启实时流数据命令
+        /// </summary>
+        /// <param name="isOpen">是否开启</param>
+        /// <param name="serialNo">设备序列号</param>
+        /// <param name="client"></param>
+        /// <returns></returns>
+        public bool SendTurnLiveStreamData(bool isOpen, string serialNo, Socket clientSocket)
+        {
+            try
+            {
+                if (clientSocket == null || !clientSocket.Connected)
+                    return false;
+
+                using (var ms = new MemoryStream())
+                {
+                    ms.WriteByte((byte)(isOpen ? 1 : 0)); // 0=关闭,1=开启
+                    byte[] buffer = CreateData(0x0C, ms.ToArray(), serialNo);
+                    DebugMessageReceived?.Invoke(this, new DebugMessageReceivedEventArgs($"{(isOpen ? "开启" : "关闭")}设备实时流", serialNo: serialNo));
+                    clientSocket.Send(buffer);
+                    return true;
+                }
+            }
+            catch (Exception ex)
+            {
+                LogHelper.Error("发送开启实时流数据命令时错误", ex);
+                return false;
+            }
+        }
+        /// <summary>
+        /// 发送获取判伤捻距系数数据命令
+        /// </summary>
+        /// <param name="serialNo"></param>
+        /// <param name="client"></param>
+        /// <returns></returns>
+        public bool SendGetTwistFactorData(string serialNo, Socket clientSocket)
+        {
+            try
+            {
+                if (clientSocket == null || !clientSocket.Connected)
+                    return false;
+
+                using (var ms = new MemoryStream())
+                {
+                    ms.WriteByte(0x00); // 操作,0=获取判伤捻距系数,1=设置判伤捻距系数
+                    WriteBytes(ms, 0); // 判伤捻距系数
+                    byte[] buffer = CreateData(0x13, ms.ToArray(), serialNo);
+                    DebugMessageReceived?.Invoke(this,
+                        new DebugMessageReceivedEventArgs($"获取设备判伤捻距系数", serialNo: serialNo));
+                    clientSocket.Send(buffer);
+                    return true;
+                }
+            }
+            catch (Exception ex)
+            {
+                LogHelper.Error("发送获取判伤捻距系数数据命令时错误", ex);
+                return false;
+            }
+        }
+        /// <summary>
+        /// 设置判伤捻距系数
+        /// </summary>
+        /// <param name="twistFactor"></param>
+        /// <param name="serialNo"></param>
+        /// <param name="stream"></param>
+        /// <returns></returns>
+        public bool SendSetTwistFactorData(float twistFactor, string serialNo, Socket clientSocket)
+        {
+            try
+            {
+                if (clientSocket == null || !clientSocket.Connected)
+                    return false;
+
+                using (var ms = new MemoryStream())
+                {
+                    ms.WriteByte(0x01); // 操作,0=获取判伤捻距系数,1=设置判伤捻距系数
+                    WriteBytes(ms, twistFactor); // 判伤捻距系数
+                    byte[] buffer = CreateData(0x13, ms.ToArray(), serialNo);
+                    DebugMessageReceived?.Invoke(this, new DebugMessageReceivedEventArgs($"设置设备判伤捻距系数", serialNo: serialNo));
+                    clientSocket.Send(buffer);
+                    return true;
+                }
+            }
+            catch (Exception ex)
+            {
+                LogHelper.Error("发送设置判伤捻距系数数据命令时错误", ex);
+                return false;
+            }
+        }
+
+        /// <summary>
+        /// 返回编码器方向
+        /// </summary>
+        /// <param name="buffer"></param>
+        /// <returns></returns>
+        public EncoderDirectionDataModel ReceiveEncoderDirectionData(byte[] buffer)
+        {
+            (string serialNo, byte[] bytes) = GetSerialNoAndData(buffer);
+            var encoderDirection = new EncoderDirectionDataModel
+            {
+                Code = bytes[0],
+                Type = bytes[1],
+                Direction = (EncoderDirection)bytes[2] // 编码器方向,1=正向,2=反向
+            };
+            return encoderDirection;
+        }
+        /// <summary>
+        /// 接收返回系统时钟数据
+        /// </summary>
+        /// <param name="buffer"></param>
+        /// <returns></returns>
+        public ClockDataModel ReceiveClockData(byte[] buffer)
+        {
+            (string serialNo, byte[] bytes) = GetSerialNoAndData(buffer);
+            ClockDataModel clockData = new ClockDataModel
+            {
+                Code = bytes[0],
+                Type = bytes[1],
+                Time = byteTransform.TransInt32(bytes, 2)
+            };
+            return clockData;
+        }
+
+        /// <summary>
+        /// 主板复位应答
+        /// </summary>
+        /// <param name="buffer"></param>
+        /// <returns></returns>
+        public ResetSystemDataModel ReceiveResetSystemData(byte[] buffer)
+        {
+            (string serialNo, byte[] bytes) = GetSerialNoAndData(buffer);
+            var resetSystemData = new ResetSystemDataModel
+            {
+                Code = bytes[0]
+            };
+            return resetSystemData;
+        }
+
+        /// <summary>
+        /// 设置当前实时位置应答
+        /// </summary>
+        /// <param name="buffer"></param>
+        /// <returns></returns>
+        public SetAbsolutePositionDataModel ReceiveSetAbsolutePositionData(byte[] buffer)
+        {
+            (string serialNo, byte[] bytes) = GetSerialNoAndData(buffer);
+            var setAbsolutePositionData = new SetAbsolutePositionDataModel
+            {
+                Code = bytes[0]
+            };
+            return setAbsolutePositionData;
+        }
+        /// <summary>
+        /// 发送获取时钟数据命令
+        /// </summary>
+        /// <param name="serialNo"></param>
+        /// <param name="stream"></param>
+        /// <returns></returns>
+        public bool SendGetClockData(string serialNo, Socket clientSocket)
+        {
+            try
+            {
+                if (clientSocket == null || !clientSocket.Connected)
+                    return false;
+
+                using (var ms = new MemoryStream())
+                {
+                    ms.WriteByte(0x00); // 操作,0=获取时钟,1=设置时钟
+                    WriteBytes(ms, 0);// 设置时间戳
+                    byte[] buffer = CreateData(0x10, ms.ToArray(), serialNo);
+                    DebugMessageReceived?.Invoke(this, new DebugMessageReceivedEventArgs($"获取设备时钟", serialNo: serialNo));
+                    clientSocket.Send(buffer);
+                    return true;
+                }
+            }
+            catch (Exception ex)
+            {
+                LogHelper.Error("发送获取时钟数据命令时错误", ex);
+                return false;
+            }
+        }
+        /// <summary>
+        /// 发送设置时钟数据命令
+        /// </summary>
+        /// <param name="timestamp">时间戳</param>
+        /// <param name="serialNo">序列号</param>
+        /// <param name="stream">网络数据流</param>
+        /// <returns></returns>
+        public bool SendSetClockData(int timestamp, string serialNo, Socket clientSocket)
+        {
+            try
+            {
+                if (clientSocket == null || !clientSocket.Connected)
+                    return false;
+
+                using (var ms = new MemoryStream())
+                {
+                    ms.WriteByte(0x01); // 操作,0=获取时钟,1=设置时钟
+                    WriteBytes(ms, timestamp);// 设置时间戳
+                    byte[] buffer = CreateData(0x10, ms.ToArray(), serialNo);
+                    DebugMessageReceived?.Invoke(this, new DebugMessageReceivedEventArgs($"设置设备时钟", serialNo: serialNo));
+                    clientSocket.Send(buffer);
+                    return true;
+                }
+            }
+            catch (Exception ex)
+            {
+                LogHelper.Error("发送设置时钟数据命令时错误", ex);
+                return false;
+            }
+        }
+        /// <summary>
+        /// 接收实时数据
+        /// </summary>
+        /// <param name="buffer"></param>
+        /// <returns></returns>
+        public RealTimeDataModel ReceiveRealTimeData(byte[] buffer)
+        {
+            (string serialNo, byte[] bytes) = GetSerialNoAndData(buffer);
+            RealTimeDataModel realTimeData = new RealTimeDataModel
+            {
+                Status = (RunningStatus)bytes[0],
+                Speed = byteTransform.TransSingle(bytes, 1),
+                AbsolutePosition = byteTransform.TransSingle(bytes, 5),
+                Position = byteTransform.TransSingle(bytes, 9)
+            };
+            return realTimeData;
+        }
+        /// <summary>
+        /// 接收检测结果
+        /// </summary>
+        /// <param name="buffer"></param>
+        /// <returns></returns>
+        public DetectionDataModel ReceiveDetectionData(byte[] buffer)
+        {
+            var verifyData = GetVerifyDataAndCheckCaption(buffer);
+            if (!verifyData.IsValid)
+            {
+                LogHelper.Error("接收检测结果时验证码校验失败");
+                return null;
+            }
+            var data = verifyData.Data;
+            DetectionDataModel detectionData = new DetectionDataModel
+            {
+                StartTime = byteTransform.TransInt32(data, 0),
+                EndTime = byteTransform.TransInt32(data, 4),
+                StartPoint = byteTransform.TransSingle(data, 8),
+                EndPoint = byteTransform.TransSingle(data, 12),
+                DetectionLength = byteTransform.TransSingle(data, 16),
+                DetectedSpeed = byteTransform.TransSingle(data, 20),
+                DamageCount = byteTransform.TransInt16(data, 24),
+                Damages = new List<DamageModel>()
+            };
+            for (int i = 0; i < detectionData.DamageCount; i++)
+            {
+                int offset = 26 + i * 10; // 每个损伤数据占8字节
+                if (offset + 10 > data.Length) break; // 防止越界
+                var damageData = new DamageModel
+                {
+                    RopeNumber = data[offset],
+                    DamagePoint = byteTransform.TransSingle(data, offset + 1),
+                    DamageValue = byteTransform.TransSingle(data, offset + 5),
+                    DamageLevel = (DamageLevel)data[offset + 9]
+                };
+                detectionData.Damages.Add(damageData);
+            }
+            return detectionData;
+        }
+        /// <summary>
+        /// 接收损伤信息
+        /// </summary>
+        /// <param name="buffer"></param>
+        /// <returns></returns>
+        public AlarmDataModel ReceiveAlarmData(byte[] buffer)
+        {
+            var verifyData = GetVerifyDataAndCheckCaption(buffer);
+            if (!verifyData.IsValid)
+            {
+                LogHelper.Error("接收同步参数时验证码校验失败");
+                return null;
+            }
+            var data = verifyData.Data;
+            AlarmDataModel alarmData = new AlarmDataModel
+            {
+                RopeNumber = data[1],
+                DamagePosition = byteTransform.TransSingle(data, 2),
+                DamageValue = byteTransform.TransSingle(data, 6),
+                DamageLevel = (DamageLevel)data[10]
+            };
+            return alarmData;
+        }
+        /// <summary>
+        /// 接收故障信息
+        /// </summary>
+        /// <param name="buffer"></param>
+        /// <returns></returns>
+        public FaultDataModel ReceiveFaultData(byte[] buffer)
+        {
+            var verifyData = GetVerifyDataAndCheckCaption(buffer);
+            if (!verifyData.IsValid)
+            {
+                LogHelper.Error("接收设备故障时验证码校验失败");
+                return null;
+            }
+            FaultDataModel faultData = new FaultDataModel();
+            var data = verifyData.Data;
+            faultData.FaultType = (FaultType)data[0];
+
+            return faultData;
+        }
+        public byte ReceiveStartOrStopDetectionResultData(byte[] buffer)
+        {
+            (string serialNo, byte[] bytes) = GetSerialNoAndData(buffer);
+            return bytes[0];
+        }
+        public byte ReceiveUpgradedResultData(byte[] buffer)
+        {
+            (string serialNo, byte[] bytes) = GetSerialNoAndData(buffer);
+            return bytes[0];
+        }
+        public byte ReceiveChangeSerialNoResultData(byte[] buffer)
+        {
+            (string serialNo, byte[] bytes) = GetSerialNoAndData(buffer);
+            return bytes[0];
+        }
+        /// <summary>
+        /// 回复心跳
+        /// </summary>
+        /// <returns></returns>
+        private void RespondHeartbeat(byte[] buffer, string ipAddress, Socket clientSocket)
+        {
+            if (buffer[1] == 0x00)
+            {
+                byte[] response = new byte[] { 0xD0, 0x00 };
+                clientSocket.Send(response); // 直接使用Socket发送
+            }
+        }
+        private async Task ProcessSerialNoData(byte[] buffer, ClientState client)
+        {
+            byte code = ReceiveChangeSerialNoResultData(buffer);
+            if (code == 0)
+            {
+                DebugMessageReceived?.Invoke(this, new DebugMessageReceivedEventArgs($"修改设备序列号成功", serialNo: client.SerialNo));
+            }
+            else
+            {
+                LogHelper.Error($"修改设备序列号结果失败,原因码:{code}");
+            }
+            await Task.CompletedTask;
+        }
+        /// <summary>
+        /// 接收同步参数
+        /// </summary>
+        /// <param name="buffer"></param>
+        /// <returns></returns>
+        public (string SerialNo, ParameterModel Parameter) ReceiveSyncParameters(byte[] buffer)
+        {
+            ParameterModel parameterData = new ParameterModel();
+            var verifyData = GetVerifyDataAndCheckCaption(buffer);
+            if (!verifyData.IsValid)
+            {
+                LogHelper.Error("接收同步参数时验证码校验失败");
+                return (null, null);
+            }
+            byte[] data = verifyData.Data;
+            int index = 0;
+
+            #region 主板软件版本号
+            short versionLength = data[index]; index += 1;
+            if (data.Length - index < versionLength)
+                return (null, null);
+
+            parameterData.MainBoardSoftwareVersion = Encoding.ASCII.GetString(data.Skip(index).Take(versionLength).ToArray());
+            index += versionLength;
+            #endregion
+
+            #region 基础参数
+            parameterData.SensorCount = byteTransform.TransInt16(data, index); index += 2;
+            parameterData.SamplingStep = byteTransform.TransSingle(data, index); index += 4;
+            parameterData.FrequencyDivisionFactor = byteTransform.TransInt16(data, index); index += 2;
+            parameterData.TimeDomainFrequency = byteTransform.TransInt16(data, index); index += 2;
+            #endregion
+
+            #region 判伤参数
+            parameterData.DamageThreshold = byteTransform.TransInt16(data, index); index += 2;
+            parameterData.ScrapUpperLimit = byteTransform.TransInt16(data, index); index += 2;
+            parameterData.FrontMagnetLength = byteTransform.TransInt16(data, index); index += 2;
+            parameterData.BackMagnetLength = byteTransform.TransInt16(data, index); index += 2;
+            parameterData.ValueCoefficient = byteTransform.TransSingle(data, index); index += 4;
+            #endregion
+
+            #region 零点位参数
+            parameterData.EffectiveStrokeLength = byteTransform.TransSingle(data, index); index += 4;
+            parameterData.ZeroPositionCorrectionDuration = byteTransform.TransInt16(data, index); index += 2;
+            parameterData.ZeroPositionCorrectionOffset = byteTransform.TransInt16(data, index); index += 2;
+            #endregion
+
+            #region 报警参数
+            parameterData.WarningValue = byteTransform.TransSingle(data, index); index += 4;
+            parameterData.AlarmValue = byteTransform.TransSingle(data, index); index += 4;
+            parameterData.SoundLightAlarmAutoResetMode = byteTransform.TransInt16(data, index); index += 2;
+            #endregion
+
+            #region 钢丝绳信息
+            parameterData.WireRopeType = byteTransform.TransInt16(data, index); index += 2;
+            parameterData.WireRopeCount = byteTransform.TransInt16(data, index); index += 2;
+            parameterData.WireRopeLength = byteTransform.TransSingle(data, index); index += 4;
+            parameterData.WireRopeDiameter = byteTransform.TransSingle(data, index); index += 4;
+            parameterData.WireRopeStrandCount = byteTransform.TransInt16(data, index); index += 2;
+            parameterData.WireRopeStrandWireCount = byteTransform.TransInt16(data, index); index += 2;
+            #endregion
+
+            return (verifyData.SerialNo, parameterData);
+        }
+        /// <summary>
+        /// 发送同步参数
+        /// </summary>
+        /// <param name="parameterData"></param>
+        /// <returns></returns>
+        public bool SendSyncParameters(ParameterModel parameters, string serialNo, Socket clientSocket)
+        {
+            try
+            {
+                if (clientSocket == null || !clientSocket.Connected)
+                    return false;
+
+                using (var ms = new MemoryStream())
+                {
+                    // Basic parameters
+                    WriteBytes(ms, parameters.SensorCount);
+                    WriteBytes(ms, parameters.SamplingStep);
+                    WriteBytes(ms, parameters.FrequencyDivisionFactor);
+                    WriteBytes(ms, parameters.TimeDomainFrequency);
+
+                    // Damage judgment parameters
+                    WriteBytes(ms, parameters.DamageThreshold);
+                    WriteBytes(ms, parameters.ScrapUpperLimit);
+                    WriteBytes(ms, parameters.FrontMagnetLength);
+                    WriteBytes(ms, parameters.BackMagnetLength);
+                    WriteBytes(ms, parameters.ValueCoefficient);
+
+                    // Zero position parameters
+                    WriteBytes(ms, parameters.EffectiveStrokeLength);
+                    WriteBytes(ms, parameters.ZeroPositionCorrectionDuration);
+                    WriteBytes(ms, parameters.ZeroPositionCorrectionOffset);
+
+                    // Alarm parameters
+                    WriteBytes(ms, parameters.WarningValue);
+                    WriteBytes(ms, parameters.AlarmValue);
+                    WriteBytes(ms, (ushort)parameters.SoundLightAlarmAutoResetMode);
+
+                    // Wire rope info
+                    WriteBytes(ms, parameters.WireRopeType);
+                    WriteBytes(ms, parameters.WireRopeCount);
+                    WriteBytes(ms, parameters.WireRopeLength);
+                    WriteBytes(ms, parameters.WireRopeDiameter);
+                    WriteBytes(ms, parameters.WireRopeStrandCount);
+                    WriteBytes(ms, parameters.WireRopeStrandWireCount);
+
+                    var buffer = CreateData(0x02, ms.ToArray(), serialNo);
+                    DebugMessageReceived?.Invoke(this, new DebugMessageReceivedEventArgs($"同步设备参数", serialNo: serialNo));
+                    clientSocket.Send(buffer);
+                    return true;
+                }
+            }
+            catch (Exception ex)
+            {
+                LogHelper.Error("发送同步参数时错误", ex);
+                return false;
+            }
+        }
+        /// <summary>
+        /// 发送开始或停止检测命令
+        /// </summary>
+        /// <param name="isStart">是否开始</param>
+        /// <param name="serialNo">设备序列号</param>
+        /// <param name="clientSocket">数据流</param>
+        public bool SendStartOrStopDetection(bool isStart, string serialNo, Socket clientSocket)
+        {
+            try
+            {
+                if (clientSocket == null || !clientSocket.Connected)
+                    return false;
+
+                using (var ms = new MemoryStream())
+                {
+                    ms.WriteByte((byte)(isStart ? 1 : 0)); // 0=停止检测,1=开始检测
+                    byte[] buffer = CreateData(0x06, ms.ToArray(), serialNo);
+                    DebugMessageReceived?.Invoke(this, new DebugMessageReceivedEventArgs($"{(isStart ? "开始" : "停止")}设备检测", serialNo: serialNo));
+                    clientSocket.Send(buffer);
+                    return true;
+                }
+            }
+            catch (Exception ex)
+            {
+                LogHelper.Error("发送开始或停止检测命令时错误", ex);
+                return false;
+            }
+        }
+        /// <summary>
+        /// 发送修改设备序列号请求
+        /// </summary>
+        /// <param name="serialNo">设备序列号</param>
+        /// <param name="clientSocket">数据流</param>
+        /// <returns></returns>
+        public bool SendSerialNoRequest(string serialNo, Socket clientSocket)
+        {
+            try
+            {
+                if (clientSocket == null || !clientSocket.Connected || serialNo.IsNullOrEmpty())
+                    return false;
+
+                using (var ms = new MemoryStream())
+                {
+                    byte[] serialNoBytes = Encoding.UTF8.GetBytes(serialNo);
+                    ms.WriteByte((byte)serialNoBytes.Length); // 序列号长度
+                    ms.Write(serialNoBytes, 0, serialNoBytes.Length); // 序列号
+                    byte[] buffer = CreateData(0x16, ms.ToArray(), serialNo);
+                    DebugMessageReceived?.Invoke(this, new DebugMessageReceivedEventArgs($"请求修改设备序列号", serialNo: null));
+                    clientSocket.Send(buffer);
+                    return true;
+                }
+            }
+            catch (Exception ex)
+            {
+                LogHelper.Error("发送设备序列号请求命令时错误", ex);
+                return false;
+
+            }
+        }
+        /// <summary>
+        /// 发送程序更新请求
+        /// </summary>
+        /// <param name="serialNo">设备序列号</param>
+        /// <param name="clientSocket">数据流</param>
+        /// <returns></returns>
+        public bool SendUpgradeRequest(string serialNo, Socket clientSocket)
+        {
+            try
+            {
+                if (clientSocket == null || !clientSocket.Connected)
+                    return false;
+
+                using (var ms = new MemoryStream())
+                {
+                    ms.WriteByte(1);
+                    byte[] buffer = CreateData(0x11, ms.ToArray(), serialNo);
+                    DebugMessageReceived?.Invoke(this, new DebugMessageReceivedEventArgs($"发起程序更新请求", serialNo: serialNo));
+                    clientSocket.Send(buffer);
+                    return true;
+                }
+            }
+            catch (Exception ex)
+            {
+                LogHelper.Error("发送程序更新请求命令时错误", ex);
+                return false;
+            }
+        }
+        /// <summary>
+        /// 发送开始或停止实时数据
+        /// </summary>
+        /// <param name="isOpen">是否开启</param>
+        /// <param name="serialNo">序列号</param>
+        /// <param name="clientSocket"></param>
+        public void SendOpenOrClosedRealtimeData(bool isOpen, string serialNo, Socket clientSocket)
+        {
+            try
+            {
+                if (clientSocket == null || !clientSocket.Connected)
+                    return;
+
+                using (var ms = new MemoryStream())
+                {
+                    ms.WriteByte((byte)(isOpen ? 1 : 0)); // 0=关闭,1=开启
+                    byte[] buffer = CreateData(0x06, ms.ToArray(), serialNo);
+                    DebugMessageReceived?.Invoke(this, new DebugMessageReceivedEventArgs($"{(isOpen ? "开启" : "停止")}设备实时数据", serialNo: serialNo));
+                    clientSocket.Send(buffer);
+                }
+            }
+            catch (Exception ex)
+            {
+                LogHelper.Error("发送开始或停止实时数据命令时错误", ex);
+            }
+        }
+        private void SafeCloseClient(ClientState client)
+        {
+            try
+            {
+                ClientDisconnected?.Invoke(this, new ClientDisconnectedEventArgs(client.ClientSocket, client.IpAddress));
+                _connectedClients.TryRemove(client.IpAddress, out _);
+                if (client.ClientSocket?.Connected == true)
+                {
+                    client.ClientSocket?.Shutdown(SocketShutdown.Both);
+                    client.ClientSocket?.Close();
+                    client.ClientSocket?.Dispose();
+                    client.ClientSocket = null;
+                }
+            }
+            catch (Exception ex)
+            {
+                Console.WriteLine($"关闭Socket时错误: {ex.Message}");
+            }
+        }
+        private void WriteBytes(MemoryStream ms, dynamic value)
+        {
+            var bytes = byteTransform.TransByte(value);
+            ms.Write(bytes, 0, bytes.Length);
+        }
+        /// <summary>
+        /// 发送检测结果确认
+        /// </summary>
+        /// <param name="parameterData"></param>
+        /// <returns></returns>
+        public bool SendDetectionConfirm(int startTime, int endTime, string serialNo, Socket clientSocket)
+        {
+            try
+            {
+                if (clientSocket == null || !clientSocket.Connected)
+                    return false;
+                using (var ms = new MemoryStream())
+                {
+                    WriteBytes(ms, startTime);
+                    WriteBytes(ms, endTime);
+
+                    byte[] buffer = CreateData(0x09, ms.ToArray(), serialNo);
+                    DebugMessageReceived?.Invoke(this, new DebugMessageReceivedEventArgs($"确认设备检测结果", serialNo: serialNo));
+                    clientSocket.Send(buffer);
+                    return true;
+                }
+            }
+            catch (Exception ex)
+            {
+                LogHelper.Error("发送检测结果确认时错误", ex);
+                return false;
+            }
+        }
+        /// <summary>
+        /// 发送主板复位请求
+        /// </summary>
+        /// <param name="serialNo"></param>
+        /// <param name="clientSocket"></param>
+        /// <returns></returns>
+        public bool SendResetSystemData(string serialNo, Socket clientSocket)
+        {
+
+            try
+            {
+                if (clientSocket == null || !clientSocket.Connected)
+                    return false;
+                using (var ms = new MemoryStream())
+                {
+                    byte[] buffer = CreateData(0x14, ms.ToArray(), serialNo);
+                    DebugMessageReceived?.Invoke(this, new DebugMessageReceivedEventArgs($"发送主板复位请求", serialNo: serialNo));
+                    clientSocket.Send(buffer);
+                    return true;
+                }
+            }
+            catch (Exception ex)
+            {
+                LogHelper.Error("发送主板复位请求时错误", ex);
+                return false;
+            }
+        }
+        /// <summary>
+        /// 发送设置当前实时位置
+        /// </summary>
+        /// <returns></returns>
+        public bool SendRealTimeAbsolutePositionData(float absolutePosition, string serialNo, Socket clientSocket)
+        {
+            try
+            {
+                if (clientSocket == null || !clientSocket.Connected)
+                    return false;
+                using (var ms = new MemoryStream())
+                {
+                    ms.WriteByte(1);// 1=设置当前实时位置
+                    WriteBytes(ms, absolutePosition);
+                    byte[] buffer = CreateData(0x15, ms.ToArray(), serialNo);
+                    DebugMessageReceived?.Invoke(this, new DebugMessageReceivedEventArgs($"发送设置当前实时位置请求", serialNo: serialNo));
+                    clientSocket.Send(buffer);
+                    return true;
+                }
+            }
+            catch (Exception ex)
+            {
+                LogHelper.Error("发送设置当前实时位置请求时错误", ex);
+                return false;
+            }
+        }
+        /// <summary>
+        /// 获取数据并验证验证码
+        /// </summary>
+        /// <param name="buffer">数据</param>
+        /// <returns></returns>
+        private VerifyDataModel GetVerifyDataAndCheckCaption(byte[] buffer)
+        {
+            VerifyDataModel verifyData = new VerifyDataModel() { Password = PASSWORD };
+            // 使用Span直接操作内存
+            Span<byte> span = buffer.AsSpan();
+
+            // 移除事件类型
+            span = span.Slice(1);
+
+            // 读取序列号长度
+            if (span.IsEmpty) return verifyData;
+            int serialNoLength = span[0];
+
+            // 读取序列号
+            if (span.Length - 1 < serialNoLength) return verifyData;
+            verifyData.SerialNo = SoftBasic.GetAsciiStringRender(span.Slice(1, serialNoLength).ToArray());
+            span = span.Slice(1 + serialNoLength);
+
+            // 读取随机码
+            if (span.Length < 4) return verifyData;
+            verifyData.RandomCode = byteTransform.TransUInt32(span.ToArray(), 0);
+            span = span.Slice(4);
+
+            // 读取验证码长度
+            if (span.IsEmpty) return verifyData;
+            int captchaLength = span[0];
+
+            // 验证验证码
+            if (span.Length - 1 < captchaLength) return verifyData;
+            verifyData.Captcha = SoftBasic.AsciiBytesToBytes(span.Slice(1, captchaLength).ToArray());
+
+            // 截取剩余数据
+            verifyData.Data = span.Slice(captchaLength + 1).ToArray();
+
+            // 校验验证码
+            verifyData.IsValid = VerifyCaptcha(verifyData);
+
+            return verifyData;
+        }
+        /// <summary>
+        /// 验证码校验
+        /// </summary>
+        /// <returns></returns>
+        private static bool VerifyCaptcha(VerifyDataModel verifyData)
+        {
+            byte[] computedHash;
+            using (var md5 = MD5.Create())
+            {
+                computedHash = md5.ComputeHash(Encoding.UTF8.GetBytes(verifyData.SerialNo + verifyData.Password + verifyData.RandomCode));
+            }
+            var buffer = verifyData.Captcha;
+            for (int i = 0; i < 16; i++)
+            {
+                if (buffer[i] != computedHash[i])
+                    return false;
+            }
+
+            return true;
+        }
+
+        ///<summary>
+        ///创建数据验证码
+        ///</summary>
+        /// <returns></returns>
+        protected byte[] CreateData(byte eventType, byte[] data, string serialNo)
+        {
+            using (var md5 = MD5.Create())
+            {
+                using (var ms = new MemoryStream())
+                {
+                    // 1. 预先计算所有字段的长度
+                    uint randomCode = (uint)DateTimeOffset.UtcNow.ToUnixTimeSeconds();
+                    byte[] randomCodeBytes = byteTransform.TransByte(randomCode);
+                    byte[] hash = md5.ComputeHash(Encoding.ASCII.GetBytes(serialNo + PASSWORD + randomCode));
+                    byte[] captcha = SoftBasic.BytesToAsciiBytes(hash);
+
+                    // 2. 正确计算剩余长度(关键修正点)
+                    // 剩余长度 = 事件类型(1) + 序列号长度(1) + 序列号数据 + 随机码 + 验证码长度(1) + 验证码 + 用户数据
+                    short remainingLength = (short)(
+                        1 +                        // eventType
+                        randomCodeBytes.Length +   // 随机码(固定4字节)
+                        1 + captcha.Length +       // 验证码(长度字节 + 数据)
+                        data.Length                // 用户数据
+                    );
+
+                    // 3. 写入头部(0x30 + 剩余长度)
+                    ms.WriteByte(0x30);
+                    if (remainingLength <= 127)
+                    {
+                        ms.WriteByte((byte)remainingLength); // 单字节表示
+                    }
+                    else
+                    {
+                        // 假设byteTransform.TransByte将short转为2字节大端序
+                        byte[] remainingLengthBytes = byteTransform.TransByte(remainingLength);
+                        ms.Write(remainingLengthBytes, 0, remainingLengthBytes.Length);
+                    }
+
+                    // 4. 写入各字段(保持原逻辑)
+                    ms.WriteByte(eventType);
+                    ms.Write(randomCodeBytes, 0, randomCodeBytes.Length);
+                    ms.WriteByte((byte)captcha.Length);
+                    ms.Write(captcha, 0, captcha.Length);
+                    ms.Write(data, 0, data.Length);
+
+                    return ms.ToArray();
+                }
+            }
+        }
+
+        public ushort[][] ParseSensorData(byte[] data, int sensorCount, int sampleCount)
+        {
+            // 检查数据长度是否匹配
+            int expectedLength = sensorCount * sampleCount;
+            if (data.Length != expectedLength)
+            {
+                LogHelper.Error($"数据长度不匹配。预期 {expectedLength} 字节,实际 {data.Length} 字节。");
+                return null;
+            }
+
+            // 初始化结果列表,每个传感器的数据存储在一个单独的 ushort[]
+            ushort[][] sensorDataList = new ushort[sampleCount][];
+            for (int i = 0; i < sampleCount; i++)
+            {
+                sensorDataList[i] = new ushort[sensorCount];
+            }
+
+            // 解析数据
+            for (int sampleIndex = 0; sampleIndex < sampleCount; sampleIndex++)
+            {
+                for (int sensorIndex = 0; sensorIndex < sensorCount; sensorIndex++)
+                {
+                    // 计算数据在字节数组中的位置
+                    int dataIndex = (sampleIndex * sensorCount) + sensorIndex;
+                    // 将 byte 转换为 ushort(直接赋值,因为 byte 可以隐式转换为 ushort)
+                    sensorDataList[sampleIndex][sensorIndex] = data[dataIndex];
+                }
+            }
+            return sensorDataList;
+        }
+
+        /// <summary>
+        /// 获取剩余长度
+        /// </summary>
+        /// <param name="bytes"></param>
+        /// <param name="startIndex"></param>
+        /// <returns></returns>
+        public static (int BytesConsumed, int Length) GetRemainingLength(byte[] bytes)
+        {
+            if (bytes == null || bytes.Length < 2) return (0, 0);
+
+            int index = 1; // 跳过包类型字节
+            int multiplier = 1;
+            int value = 0;
+            byte encodedByte;
+            int bytesConsumed = 0;
+
+            do
+            {
+                if (index >= bytes.Length) return (0, 0);
+
+                encodedByte = bytes[index];
+                value += (encodedByte & 0x7F) * multiplier;
+                multiplier *= 128;
+                index++;
+                bytesConsumed++;
+
+                if (multiplier > 128 * 128 * 128) // 防止溢出
+                    return (0, 0);
+
+            } while ((encodedByte & 0x80) != 0 && bytesConsumed < 4); // 最多4字节长度
+
+            return (bytesConsumed, value);
+        }
+
+        private static (string SerialNo, byte[] Bytes) GetSerialNoAndData(byte[] buffer)
+        {
+            // 使用Span直接操作内存
+            Span<byte> span = buffer.AsSpan();
+
+            // 移除事件类型
+            span = span.Slice(1);
+
+            // 读取序列号长度
+            if (span.IsEmpty) return (null, null);
+            int serialNoLength = span[0];
+
+            // 读取序列号
+            if (span.Length - 1 < serialNoLength) return (null, null);
+
+            var serialNo = SoftBasic.GetAsciiStringRender(span.Slice(1, serialNoLength).ToArray());
+            buffer = span.Slice(1 + serialNoLength).ToArray();
+
+            return (serialNo, buffer);
+        }
+
+        public void Dispose()
+        {
+            try
+            {
+                _isRunning = false;
+                Thread.Sleep(100);
+                if (_listener != null)
+                {
+                    if (_listener.Connected)
+                    {
+                        _listener.Shutdown(SocketShutdown.Both);
+                    }
+                    _listener.Close();
+                    _listener.Dispose();
+                }
+            }
+            catch (SocketException ex) when (ex.SocketErrorCode == SocketError.NotConnected)
+            {
+                _listener?.Dispose();
+            }
+            catch (Exception ex)
+            {
+                LogHelper.Error($"停止服务时发生异常: {ex.Message}", ex);
+            }
+            finally
+            {
+                _listener = null;
+            }
+        }
+    }
+}

+ 94 - 0
Dtos/DamageDto.cs

@@ -0,0 +1,94 @@
+using SWRIS.Enums;
+using System.ComponentModel;
+
+namespace SWRIS.Dtos
+{
+    public class DamageDto : INotifyPropertyChanged
+    {
+        private int? _id;
+        private int? _recordId;
+        private float _damagePoint;
+        private float _damageValue;
+        private DamageLevel _damageLevel;
+
+        public int? Id
+        {
+            get => _id;
+            set
+            {
+                if (_id != value)
+                {
+                    _id = value;
+                    OnPropertyChanged(nameof(Id));
+                }
+            }
+        }
+        /// <summary>
+        /// 记录Id
+        /// </summary>
+        public int? RecordId
+        {
+            get => _recordId;
+            set
+            {
+                if (_recordId != value)
+                {
+                    _recordId = value;
+                    OnPropertyChanged(nameof(RecordId));
+                }
+            }
+        }
+        /// <summary>
+        /// 位置 m
+        /// </summary>
+        public float DamagePoint
+        {
+            get => _damagePoint;
+            set
+            {
+                if (_damagePoint != value)
+                {
+                    _damagePoint = value;
+                    OnPropertyChanged(nameof(DamagePoint));
+                }
+            }
+        }
+        /// <summary>
+        /// 损伤值
+        /// </summary>
+        public float DamageValue
+        {
+            get => _damageValue;
+            set
+            {
+                if (_damageValue != value)
+                {
+                    _damageValue = value;
+                    OnPropertyChanged(nameof(DamageValue));
+                }
+            }
+        }
+        /// <summary>
+        /// 损伤程度
+        /// </summary>
+        public DamageLevel DamageLevel
+        {
+            get => _damageLevel;
+            set
+            {
+                if (_damageLevel != value)
+                {
+                    _damageLevel = value;
+                    OnPropertyChanged(nameof(DamageLevel));
+                }
+            }
+        }
+
+
+        public event PropertyChangedEventHandler PropertyChanged;
+        protected internal virtual void OnPropertyChanged(string propertyName)
+        {
+            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+        }
+    }
+}

+ 28 - 0
Dtos/GetRecordsInputDto.cs

@@ -0,0 +1,28 @@
+using SWRIS.Enums;
+using System;
+using System.ComponentModel;
+
+namespace SWRIS.Dtos
+{
+    public class GetRecordsInputDto : INotifyPropertyChanged
+    {
+        private int? ropeNumber;
+        private DateTime startTime = DateTime.Today.AddDays(-1);
+        private DateTime endTime = DateTime.Today.AddDays(1).AddSeconds(-1);
+        private int? limit;
+        private int? offset;
+        private int? riskLevel;
+        public int? RopeNumber { get => ropeNumber; set { ropeNumber = value; OnPropertyChanged("RopeNumber"); } }
+        public int? Limit { get => limit; set { limit = value; OnPropertyChanged("Limit"); } }
+        public int? Offset { get => offset; set { offset = value; OnPropertyChanged("Offset"); } }
+        public DateTime StartTime { get => startTime; set { startTime = value; OnPropertyChanged("StartTime"); } }
+        public DateTime EndTime { get => endTime; set { endTime = value; OnPropertyChanged("EndTime"); } }
+        public int? RiskLevel { get => riskLevel; set { riskLevel = value; OnPropertyChanged("RiskLevel"); } }
+
+        public event PropertyChangedEventHandler PropertyChanged;
+        protected internal virtual void OnPropertyChanged(string propertyName)
+        {
+            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+        }
+    }
+}

+ 36 - 0
Dtos/KeyAndValueDto.cs

@@ -0,0 +1,36 @@
+using System.ComponentModel;
+
+namespace SWRIS.Dtos
+{
+    public class KeyAndValueDto : INotifyPropertyChanged
+    {
+        private int? key;
+        private string value;
+        public int? Key
+        {
+            get { return key; }
+            set { key = value; OnPropertyChanged("Key"); }
+        }
+        public string Value
+        {
+            get { return this.value; }
+            set { this.value = value; OnPropertyChanged("Value"); }
+        }
+
+        public event PropertyChangedEventHandler PropertyChanged;
+
+        protected internal virtual void OnPropertyChanged(string propertyName)
+        {
+            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+        }
+    }
+    public class KeyAndValueWithChecked : KeyAndValueDto
+    {
+        private bool isChecked;
+ 
+        public bool IsChecked {
+            get { return isChecked; }
+            set { isChecked = value; OnPropertyChanged("IsChecked"); }
+        }       
+    }
+}

+ 310 - 0
Dtos/RecordDto.cs

@@ -0,0 +1,310 @@
+using SWRIS.Enums;
+using System;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+
+namespace SWRIS.Dtos
+{
+    public class RecordDto : INotifyPropertyChanged
+    {
+        private int? id;
+        private int ropeNumber;
+        private string ropeName;
+        private DateTime startTime;
+        private DateTime endTime;
+        private double startPoint;
+        private double endPoint;
+        private double detectionLength;
+        private double detectedSpeed;
+        private short damageCount;
+        private string dataFilePath;
+        private int sensorCount;
+        private double samplingStep;
+        private RiskLevel riskLevel = RiskLevel.Normal;
+        private int exportCount;
+        private string inUseSensors;
+        private bool isSelected = false;
+        private ObservableCollection<DamageDto> damages = new ObservableCollection<DamageDto>();
+
+
+        public int? Id
+        {
+            get => id;
+            set
+            {
+                if (id != value)
+                {
+                    id = value;
+                    OnPropertyChanged(nameof(Id));
+                }
+            }
+        }
+
+        /// <summary>
+        /// 钢丝绳名称
+        /// </summary>
+        public int RopeNumber
+        {
+            get => ropeNumber;
+            set
+            {
+                if (ropeNumber != value)
+                {
+                    ropeNumber = value;
+                    OnPropertyChanged(nameof(RopeNumber));
+                }
+            }
+        }
+        public string RopeName
+        {
+            get => ropeName;
+            set
+            {
+                if (ropeName != value)
+                {
+                    ropeName = value;
+                    OnPropertyChanged(nameof(RopeName));
+                }
+            }
+        }
+
+        /// <summary>
+        /// 开始时间 时间戳
+        /// </summary>
+        public DateTime StartTime
+        {
+            get => startTime;
+            set
+            {
+                if (startTime != value)
+                {
+                    startTime = value;
+                    OnPropertyChanged(nameof(StartTime));
+                }
+            }
+        }
+
+        /// <summary>
+        /// 结束时间 时间戳
+        /// </summary>
+        public DateTime EndTime
+        {
+            get => endTime;
+            set
+            {
+                if (endTime != value)
+                {
+                    endTime = value;
+                    OnPropertyChanged(nameof(EndTime));
+                }
+            }
+        }
+
+        /// <summary>
+        /// 开始位置 m
+        /// </summary>
+        public double StartPoint
+        {
+            get => startPoint;
+            set
+            {
+                if (startPoint != value)
+                {
+                    startPoint = value;
+                    OnPropertyChanged(nameof(StartPoint));
+                }
+            }
+        }
+
+        /// <summary>
+        /// 结束位置 m
+        /// </summary>
+        public double EndPoint
+        {
+            get => endPoint;
+            set
+            {
+                if (endPoint != value)
+                {
+                    endPoint = value;
+                    OnPropertyChanged(nameof(EndPoint));
+                }
+            }
+        }
+
+        /// <summary>
+        /// 检测长度 m
+        /// </summary>
+        public double DetectionLength
+        {
+            get => detectionLength;
+            set
+            {
+                if (detectionLength != value)
+                {
+                    detectionLength = value;
+                    OnPropertyChanged(nameof(DetectionLength));
+                }
+            }
+        }
+
+        /// <summary>
+        /// 检测速度 m/s
+        /// </summary>
+        public double DetectedSpeed
+        {
+            get => detectedSpeed;
+            set
+            {
+                if (detectedSpeed != value)
+                {
+                    detectedSpeed = value;
+                    OnPropertyChanged(nameof(DetectedSpeed));
+                }
+            }
+        }
+
+        /// <summary>
+        /// 损伤数量
+        /// </summary>
+        public short DamageCount
+        {
+            get => damageCount;
+            set
+            {
+                if (damageCount != value)
+                {
+                    damageCount = value;
+                    OnPropertyChanged(nameof(DamageCount));
+                }
+            }
+        }
+
+        /// <summary>
+        /// 数据文件路径
+        /// </summary>
+        public string DataFilePath
+        {
+            get => dataFilePath;
+            set
+            {
+                if (dataFilePath != value)
+                {
+                    dataFilePath = value;
+                    OnPropertyChanged(nameof(DataFilePath));
+                }
+            }
+        }
+
+        /// <summary>
+        /// 传感器数量
+        /// </summary>
+        public int SensorCount
+        {
+            get => sensorCount;
+            set
+            {
+                if (sensorCount != value)
+                {
+                    sensorCount = value;
+                    OnPropertyChanged(nameof(SensorCount));
+                }
+            }
+        }
+
+        /// <summary>
+        /// 采样步长 mm/点
+        /// </summary>
+        public double SamplingStep
+        {
+            get => samplingStep;
+            set
+            {
+                if (samplingStep != value)
+                {
+                    samplingStep = value;
+                    OnPropertyChanged(nameof(SamplingStep));
+                }
+            }
+        }
+
+        /// <summary>
+        /// 钢丝绳状态
+        /// </summary>
+        public RiskLevel RiskLevel
+        {
+            get => riskLevel;
+            set
+            {
+                if (riskLevel != value)
+                {
+                    riskLevel = value;
+                    OnPropertyChanged(nameof(RiskLevel));
+                }
+            }
+        }
+        /// <summary>
+        /// 导出次数
+        /// </summary>
+        public int ExportCount
+        {
+            get => exportCount;
+            set
+            {
+                if (exportCount != value)
+                {
+                    exportCount = value;
+                    OnPropertyChanged(nameof(ExportCount));
+                }
+            }
+        }
+        /// <summary>
+        /// 在用传感器 1,2,3,4
+        /// </summary>
+        public string InUseSensors
+        {
+            get => inUseSensors;
+            set
+            {
+                if (inUseSensors != value)
+                {
+                    inUseSensors = value;
+                    OnPropertyChanged(nameof(InUseSensors));
+                }
+            }
+        }
+        /// <summary>
+        /// 是否选中
+        /// </summary>
+        public bool IsSelected
+        {
+            get => isSelected;
+            set
+            {
+                if (isSelected != value)
+                {
+                    isSelected = value;
+                    OnPropertyChanged(nameof(IsSelected));
+                }
+            }
+        }
+        public ObservableCollection<DamageDto> Damages
+        {
+            get => damages;
+            set
+            {
+                if (damages != value)
+                {
+                    damages = value;
+                    OnPropertyChanged(nameof(Damages));
+                }
+            }
+        }
+
+        public event PropertyChangedEventHandler PropertyChanged;
+        protected internal virtual void OnPropertyChanged(string propertyName)
+        {
+            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+        }
+    }
+}

+ 14 - 0
Dtos/RecordIdAnDataFilePathDto.cs

@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SWRIS.Dtos
+{
+    public class RecordIdAnDataFilePathDto
+    {
+        public int RecordId { get; set; }
+        public string DataFilePath { get; set; }
+    }
+}

+ 17 - 0
Dtos/SimpleRecordDto.cs

@@ -0,0 +1,17 @@
+using SWRIS.Enums;
+using System;
+
+namespace SWRIS.Dtos
+{
+    public class SimpleRecordDto
+    {
+        public int Id { get; set; }
+        public string RopeName { get; set; }
+        public DateTime Time { get; set; }
+        public RiskLevel RiskLevel { get; set; }
+        public string Description { get; set; }
+        public double DetectionLength { get; set; }
+        public bool IsNew { get; set; }
+
+    }
+}

+ 18 - 0
Enums/AlarmSourceType.cs

@@ -0,0 +1,18 @@
+using System.ComponentModel;
+
+namespace SWRIS.Enums
+{
+    public enum AlarmSourceType
+    {
+        /// <summary>
+        /// 实时报警
+        /// </summary>
+        [Description("实时报警")]
+        RealTime = 1,
+        /// <summary>
+        /// 检测结果
+        /// </summary>
+        [Description("检测结果")]
+        Detection = 2
+    }
+}

+ 14 - 0
Enums/AlarmType.cs

@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SWRIS.Enums
+{
+    public enum AlarmType
+    {
+        LF = 1,
+        LMA = 2,
+    }
+}

+ 44 - 0
Enums/DamageLevel.cs

@@ -0,0 +1,44 @@
+using System.ComponentModel;
+
+namespace SWRIS.Enums
+{
+    public enum DamageLevel
+    {
+        /// <summary>
+        /// 0 - 轻微
+        /// </summary>
+        [Description("轻微")]
+        Mild = 0,
+
+        /// <summary>
+        /// 1 - 轻度
+        /// </summary>
+        [Description("轻度")]
+        Light = 1,
+
+        /// <summary>
+        /// 2 - 中度
+        /// </summary>
+        [Description("中度")]
+        Moderate = 2,
+
+        /// <summary>
+        /// 3 - 较重
+        /// </summary>
+        [Description("较重")]
+        Severe = 3,
+
+        /// <summary>
+        /// 4 - 严重
+        /// </summary>
+        [Description("严重")]
+        Critical = 4,
+
+        /// <summary>
+        /// 5 - 超限
+        /// </summary>
+        [Description("超限")]
+        ExceededLimit = 5
+
+    }
+}

+ 26 - 0
Enums/DataFormat.cs

@@ -0,0 +1,26 @@
+namespace  SWRIS.Enums
+{
+	/// <summary>
+	/// 应用于多字节数据的解析或是生成格式<br />
+	/// Parsing or generating format for multibyte data
+	/// </summary>
+	public enum DataFormat
+	{
+		/// <summary>
+		/// 按照顺序排序
+		/// </summary>
+		ABCD = 0,
+		/// <summary>
+		/// 按照单字反转
+		/// </summary>
+		BADC = 1,
+		/// <summary>
+		/// 按照双字反转
+		/// </summary>
+		CDAB = 2,
+		/// <summary>
+		/// 按照倒序排序
+		/// </summary>
+		DCBA = 3,
+	}
+}

+ 23 - 0
Enums/DirectionState.cs

@@ -0,0 +1,23 @@
+using System.ComponentModel;
+
+namespace SWRIS.Enums
+{
+    public enum DirectionState
+    {
+        /// <summary>
+        /// 停止
+        /// </summary>
+        [Description("停止")]
+        Stoped = 0,
+        /// <summary>
+        /// 正向
+        /// </summary>
+        [Description("正向")]
+        Forward = 1,
+        /// <summary>
+        /// 反向
+        /// </summary>
+        [Description("反向")]
+        Reverse = 2
+    }
+}

+ 19 - 0
Enums/EncoderDirection.cs

@@ -0,0 +1,19 @@
+using System.ComponentModel;
+
+namespace SWRIS.Enums
+{
+    public enum EncoderDirection
+    {
+        /// <summary>
+        /// 正向
+        /// </summary>
+        [Description("正向")]
+        Forward = 1,
+        /// <summary>
+        /// 反向
+        /// </summary>
+        [Description("反向")]
+        Reverse = 2
+
+    }
+}

+ 37 - 0
Enums/ExecutionState.cs

@@ -0,0 +1,37 @@
+using System;
+
+namespace SWRIS.Enums
+{
+    [Flags]
+    public enum ExecutionState : uint
+    {
+        /// <summary>
+        /// Forces the system to be in the working state by resetting the system idle timer.
+        /// </summary>
+        SystemRequired = 0x01,
+
+        /// <summary>
+        /// Forces the display to be on by resetting the display idle timer.
+        /// </summary>
+        DisplayRequired = 0x02,
+
+        /// <summary>
+        /// This value is not supported. If <see cref="UserPresent"/> is combined with other esFlags values, the call will fail and none of the specified states will be set.
+        /// </summary>
+        [Obsolete("This value is not supported.")]
+        UserPresent = 0x04,
+
+        /// <summary>
+        /// Enables away mode. This value must be specified with <see cref="Continuous"/>.
+        /// <para />
+        /// Away mode should be used only by media-recording and media-distribution applications that must perform critical background processing on desktop computers while the computer appears to be sleeping.
+        /// </summary>
+        AwaymodeRequired = 0x40,
+
+        /// <summary>
+        /// Informs the system that the state being set should remain in effect until the next call that uses <see cref="Continuous"/> and one of the other state flags is cleared.
+        /// </summary>
+        Continuous = 0x80000000,
+    }
+
+}

+ 14 - 0
Enums/FaultState.cs

@@ -0,0 +1,14 @@
+namespace SWRIS.Enums
+{
+    public enum FaultState
+    {
+        /// <summary>
+        /// 正常
+        /// </summary>
+        Normal = 0,
+        /// <summary>
+        /// 异常
+        /// </summary>
+        Abnormal = 1
+    }
+}

+ 15 - 0
Enums/FaultType.cs

@@ -0,0 +1,15 @@
+namespace SWRIS.Enums
+{
+    public enum FaultType
+    {
+        /// <summary>
+        /// 传感器故障
+        /// </summary>
+        Sensor = 1,
+        /// <summary>
+        /// 编码器故障
+        /// </summary>
+        Encoder = 2,
+
+    }
+}

+ 28 - 0
Enums/LayType.cs

@@ -0,0 +1,28 @@
+using System.ComponentModel;
+
+namespace SWRIS.Enums
+{
+    public enum LayType
+    {
+        /// <summary>
+        /// 右交互捻
+        /// </summary>
+        [Description("右交互捻")]
+        ZS = 1,
+        /// <summary>
+        /// 左交互捻
+        /// </summary>
+        [Description("左交互捻")]
+        SZ = 2,
+        /// <summary>
+        /// 右同向捻
+        /// </summary>
+        [Description("右同向捻")]
+        ZZ = 3,
+        /// <summary>
+        /// 左同向捻
+        /// </summary>
+        [Description("左同向捻")]
+        SS = 4,
+    }
+}

+ 9 - 0
Enums/LimitState.cs

@@ -0,0 +1,9 @@
+namespace SWRIS.Enums
+{
+    public enum LimitState
+    {
+        Offline = -1,
+        NotInLimit = 0,
+        AtLimit = 1
+    }
+}

+ 24 - 0
Enums/ModuleType.cs

@@ -0,0 +1,24 @@
+using System.ComponentModel;
+
+namespace SWRIS.Enums
+{
+    public enum ModuleType
+    {
+        [Description("ModbusRtu")]
+        ModbusRtu = 1,
+        [Description("ModbusTcp")]
+        ModbusTcp = 2,
+        [Description("西门子S200")]
+        S200 = 3,
+        [Description("西门子S200Smart")]
+        S200Smart = 4,
+        [Description("西门子S300")]
+        S300 = 5,
+        [Description("西门子S400")]
+        S400 = 6,
+        [Description("西门子S1200")]
+        S1200 = 7,
+        [Description("西门子S1500")]
+        S1500 = 8,
+    }
+}

+ 27 - 0
Enums/ResetType.cs

@@ -0,0 +1,27 @@
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+
+namespace SWRIS.Enums
+{
+    public enum ResetType
+    {
+        /// <summary>
+        /// 不重置
+        /// </summary>
+        [Description("不重置")]
+        NoReset = 0,
+
+        /// <summary>
+        /// 延时重置
+        /// </summary>
+        [Description("延时重置")]
+        [Range(1, short.MaxValue)]
+        DelayedReset = 1,
+
+        /// <summary>
+        /// 新测试重置
+        /// </summary>
+        [Description("新测试重置")]
+        NewTestReset = -1
+    }
+}

+ 94 - 0
Enums/RiskLevel.cs

@@ -0,0 +1,94 @@
+using SWRIS.Models;
+using System;
+using System.ComponentModel;
+using System.Linq;
+
+namespace SWRIS.Enums
+{
+    public enum RiskLevel
+    {
+        /// <summary>
+        /// 0 - 正常
+        /// </summary>
+        [Description("正常")]
+        Normal = -1,
+
+        /// <summary>
+        /// 0 - 轻微
+        /// </summary>
+        [Description("轻微")]
+        Mild = 0,
+
+        /// <summary>
+        /// 1 - 轻度
+        /// </summary>
+        [Description("轻度")]
+        Light = 1,
+
+        /// <summary>
+        /// 2 - 中度
+        /// </summary>
+        [Description("中度")]
+        Moderate = 2,
+
+        /// <summary>
+        /// 3 - 较重
+        /// </summary>
+        [Description("较重")]
+        Severe = 3,
+
+        /// <summary>
+        /// 4 - 严重
+        /// </summary>
+        [Description("严重")]
+        Critical = 4,
+
+        /// <summary>
+        /// 5 - 超限
+        /// </summary>
+        [Description("超限")]
+        ExceededLimit = 5
+
+    }
+
+    public static class RiskLevelEvaluator
+    {
+        public static RiskLevel Evaluate(double maxDamageValue, double avgDamageValue)
+        {
+            if (maxDamageValue <= 0 && avgDamageValue <= 0)
+            {
+                return RiskLevel.Normal;
+            }
+            // Check from highest to lowest severity
+            foreach (RiskLevel level in Enum.GetValues(typeof(RiskLevel)).Cast<RiskLevel>().OrderByDescending(x => x))
+            {
+                var attribute = GetRiskLevelConfig(level);
+
+                // First check max damage range
+                bool isMaxRangeMatch = maxDamageValue > attribute.MaxDamageRange.Min && maxDamageValue <= attribute.MaxDamageRange.Max;
+                if (isMaxRangeMatch)
+                {
+                    return level;
+                }
+
+                // If max range doesn't match, then check avg damage range
+                bool isAvgRangeMatch = avgDamageValue > attribute.AvgDamageRange.Min && avgDamageValue <= attribute.AvgDamageRange.Max;
+                if (isAvgRangeMatch)
+                {
+                    return level;
+                }
+            }
+            return RiskLevel.Normal;
+        }
+        public static RiskLevelConfig GetRiskLevelConfig(this RiskLevel level)
+        {
+            return App.Config.RiskLevelSettings.Levels
+                  .FirstOrDefault(x => x.Level == level) ?? new RiskLevelConfig
+                  {
+                      Level = RiskLevel.Normal,
+                      MaxDamageRange = new Range { Min = 0, Max = 0 },
+                      AvgDamageRange = new Range { Min = 0, Max = 0 },
+                  };
+        }
+    }
+}

+ 19 - 0
Enums/RopeCoreType.cs

@@ -0,0 +1,19 @@
+using System.ComponentModel;
+
+namespace SWRIS.Enums
+{
+    public enum RopeCoreType
+    {
+        /// <summary>
+        /// 钢芯
+        /// </summary>
+        [Description("钢芯")]
+        IWR = 1,
+        /// <summary>
+        /// 纤维芯
+        /// </summary>
+        [Description("纤维芯")]
+        FC = 2,
+
+    }
+}

+ 60 - 0
Enums/RunningStatus.cs

@@ -0,0 +1,60 @@
+using System;
+using System.ComponentModel;
+
+namespace SWRIS.Enums
+{
+    /// <summary>
+    /// 设备运行状态(支持位运算组合状态)
+    /// </summary>
+    [Flags]
+    public enum RunningStatus
+    {
+        /// <summary>
+        /// 0 - 停止检测
+        /// </summary>
+        [Description("停止检测")]
+        Stopped = 0,
+
+        /// <summary>
+        /// 1 - 检测中且正常
+        /// </summary>
+        [Description("检测中")]
+        RunningNormal = 1,
+
+        /// <summary>
+        /// 2 - 传感器故障
+        /// </summary>
+        [Description("传感器故障")]
+        SensorFault = 2,
+
+        /// <summary>
+        /// 4 - 预警(黄色预警)
+        /// </summary>
+        [Description("黄色预警")]
+        YellowAlert = 4,
+
+        /// <summary>
+        /// 8 - 报警(红色报警)
+        /// </summary>
+        [Description("红色报警")]
+        RedAlert = 8,
+
+        /// <summary>
+        /// 16 - 预停止
+        /// </summary>
+        [Description("预停止")]
+        PreStop = 16,
+
+        /// <summary>
+        /// 32 - 预运行
+        /// </summary>
+        [Description("预运行")]
+        PreRunning = 32,
+
+        /// <summary>
+        /// 64 - 预标定
+        /// </summary>
+        [Description("预标定")]
+        PreCalibration = 64
+    }
+}

+ 18 - 0
Enums/WireMaterialType.cs

@@ -0,0 +1,18 @@
+using System.ComponentModel;
+
+namespace SWRIS.Enums
+{
+    public enum WireMaterialType
+    {
+        /// <summary>
+        /// 碳钢
+        /// </summary>
+        [Description("碳钢")]
+        CarbonSteel = 1,
+        /// <summary>
+        /// 不锈钢
+        /// </summary>
+        [Description("不锈钢")]
+        StainlessSteel = 2
+    }
+}

+ 14 - 0
Enums/WireSurfaceType.cs

@@ -0,0 +1,14 @@
+using System.ComponentModel;
+
+namespace SWRIS.Enums
+{
+    public enum WireSurfaceType
+    {
+        [Description("光面")]
+        NAT = 1,
+        [Description("A级镀锌")]
+        ZAA = 2,
+        [Description("B级镀锌")]
+        ABB = 3,
+    }
+}

+ 16 - 0
Events/AlarmDataReceivedEventArgs.cs

@@ -0,0 +1,16 @@
+using SWRIS.Models;
+using System;
+
+namespace SWRIS.Events
+{
+    public class AlarmDataReceivedEventArgs : EventArgs
+    {
+        public string IpAddress { get; }
+        public AlarmDataModel AlarmData { get; }
+        public AlarmDataReceivedEventArgs(string ipAddress, AlarmDataModel alarmData)
+        {
+            IpAddress = ipAddress;
+            AlarmData = alarmData;
+        }
+    }
+}

+ 21 - 0
Events/ClientConnectedEventArgs.cs

@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Sockets;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SWRIS.Events
+{
+    public class ClientConnectedEventArgs : EventArgs
+    {
+        public Socket ClientSocket { get; }
+        public string IpAddress { get; }
+
+        public ClientConnectedEventArgs(Socket clientSocket, string ipAddress)
+        {
+            ClientSocket = clientSocket;
+            IpAddress = ipAddress;
+        }
+    }
+}

+ 16 - 0
Events/ClientDisconnectedEventArgs.cs

@@ -0,0 +1,16 @@
+using System;
+using System.Net.Sockets;
+
+namespace SWRIS.Events
+{
+    public class ClientDisconnectedEventArgs : EventArgs
+    {
+        public Socket ClientSocket { get; }
+        public string IpAddress { get; }
+        public ClientDisconnectedEventArgs(Socket clientSocket, string ipAddress)
+        {
+            ClientSocket = clientSocket;
+            IpAddress = ipAddress;
+        }
+    }
+}

+ 16 - 0
Events/ClockResultReceivedEventArgs.cs

@@ -0,0 +1,16 @@
+using System;
+
+namespace SWRIS.Events
+{
+    public class ClockResultReceivedEventArgs : EventArgs
+    {
+        public string IpAddress { get; }
+        public int Timestamp { get; set; }
+
+        public ClockResultReceivedEventArgs(string ipAddress, int timestamp)
+        {
+            IpAddress = ipAddress;
+            Timestamp = timestamp;
+        }
+    }
+}

+ 19 - 0
Events/DebugMessageReceivedEventArgs.cs

@@ -0,0 +1,19 @@
+using System;
+
+namespace SWRIS.Events
+{
+    public class DebugMessageReceivedEventArgs : EventArgs
+    {
+        public string Message { get; }
+        public string IpAddress { get; }
+        public string SerialNo { get; }
+
+        public DebugMessageReceivedEventArgs(string message, string ipAddress = null, string serialNo = null)
+        {
+            Message = message;
+            IpAddress = ipAddress;
+            SerialNo = serialNo;
+        }
+
+    }
+}

+ 16 - 0
Events/DetectionDataReceivedEventArgs.cs

@@ -0,0 +1,16 @@
+using SWRIS.Models;
+using System;
+
+namespace SWRIS.Events
+{
+    public class DetectionDataReceivedEventArgs : EventArgs
+    {
+        public string IpAddress { get; }
+        public DetectionDataModel DetectionData { get; }
+        public DetectionDataReceivedEventArgs(string ipAddress, DetectionDataModel detectionData)
+        {
+            IpAddress = ipAddress;
+            DetectionData = detectionData;
+        }
+    }
+}

+ 17 - 0
Events/DetectionRawDataReceivedEventArgs.cs

@@ -0,0 +1,17 @@
+using SWRIS.Models;
+using System;
+
+namespace SWRIS.Events
+{
+    public class ResetSystemDataReceivedEventArgs : EventArgs
+    {
+        public string IpAddress { get; }
+        public ResetSystemDataModel ResetSystemData { get; set; }
+
+        public ResetSystemDataReceivedEventArgs(string ipAddress, ResetSystemDataModel resetSystemData)
+        {
+            IpAddress = ipAddress;
+            ResetSystemData = resetSystemData;
+        }
+    }
+}

+ 17 - 0
Events/DetectionRawDataResultReceivedEventArgs.cs

@@ -0,0 +1,17 @@
+using SWRIS.Models;
+using System;
+
+namespace SWRIS.Events
+{
+    public class DetectionRawDataResultReceivedEventArgs : EventArgs
+    {
+        public string IpAddress { get; }
+        public DetectionRawResultDataModel DetectionRawResultData { get; set; }
+
+        public DetectionRawDataResultReceivedEventArgs(string ipAddress, DetectionRawResultDataModel detectionRawResultData)
+        {
+            IpAddress = ipAddress;
+            DetectionRawResultData = detectionRawResultData;
+        }
+    }
+}

+ 16 - 0
Events/DetectionStatusResultReceivedEventArgs.cs

@@ -0,0 +1,16 @@
+using System;
+
+namespace SWRIS.Events
+{
+    public class DetectionStatusResultReceivedEventArgs : EventArgs
+    {
+        public string IpAddress { get; }
+        public byte Code { get; set; }
+
+        public DetectionStatusResultReceivedEventArgs(string ipAddress, byte code)
+        {
+            IpAddress = ipAddress;
+            Code = code;
+        }
+    }
+}

+ 17 - 0
Events/EncoderDirectionResultReceivedEventArgs.cs

@@ -0,0 +1,17 @@
+using SWRIS.Enums;
+using System;
+
+namespace SWRIS.Events
+{
+    public class EncoderDirectionResultReceivedEventArgs : EventArgs
+    {
+        public string IpAddress { get; }
+        public EncoderDirection Direction { get; set; }
+
+        public EncoderDirectionResultReceivedEventArgs(string ipAddress, EncoderDirection direction)
+        {
+            IpAddress = ipAddress;
+            Direction = direction;
+        }
+    }
+}

+ 16 - 0
Events/FaultDataReceivedEventArgs.cs

@@ -0,0 +1,16 @@
+using SWRIS.Models;
+using System;
+
+namespace SWRIS.Events
+{
+    public class FaultDataReceivedEventArgs : EventArgs
+    {
+        public string IpAddress { get; }
+        public FaultDataModel FaultData { get; }
+        public FaultDataReceivedEventArgs(string ipAddress, FaultDataModel faultData)
+        {
+            IpAddress = ipAddress;
+            FaultData = faultData;
+        }
+    }
+}

+ 14 - 0
Events/HeartbeatReceviedEventArgs.cs

@@ -0,0 +1,14 @@
+using System;
+
+namespace SWRIS.Events
+{
+    public class HeartbeatReceviedEventArgs : EventArgs
+    {
+        public string IpAddress { get; }
+
+        public HeartbeatReceviedEventArgs(string ipAddress)
+        {
+            IpAddress = ipAddress;
+        }
+    }
+}

+ 16 - 0
Events/LiveStreamReceivedEventArgs.cs

@@ -0,0 +1,16 @@
+using SWRIS.Models;
+using System;
+
+namespace SWRIS.Events
+{
+    public class LiveStreamReceivedEventArgs : EventArgs
+    {
+        public string IpAddress { get; }
+        public LiveStreamDataModel LiveStream { get; }
+        public LiveStreamReceivedEventArgs(string ipAddress, LiveStreamDataModel liveStream)
+        {
+            IpAddress = ipAddress;
+            LiveStream = liveStream;
+        }
+    }
+}

+ 16 - 0
Events/RealTimeDataReceivedEventArgs.cs

@@ -0,0 +1,16 @@
+using SWRIS.Models;
+using System;
+
+namespace SWRIS.Events
+{
+    public class RealTimeDataReceivedEventArgs : EventArgs
+    {
+        public string IpAddress { get; }
+        public RealTimeDataModel RealTimeData { get; }
+        public RealTimeDataReceivedEventArgs(string ipAddress, RealTimeDataModel realTimeData)
+        {
+            IpAddress = ipAddress;
+            RealTimeData = realTimeData;
+        }
+    }
+}

+ 17 - 0
Events/ResetSystemDataReceivedEventArgs.cs

@@ -0,0 +1,17 @@
+using SWRIS.Models;
+using System;
+
+namespace SWRIS.Events
+{
+    public class DetectionRawDataReceivedEventArgs : EventArgs
+    {
+        public string IpAddress { get; }
+        public DetectionRawDataModel DetectionRawData { get; set; }
+
+        public DetectionRawDataReceivedEventArgs(string ipAddress, DetectionRawDataModel detectionRawData)
+        {
+            IpAddress = ipAddress;
+            DetectionRawData = detectionRawData;
+        }
+    }
+}

+ 21 - 0
Events/SetAbsolutePositionDataReceivedEventArgs.cs

@@ -0,0 +1,21 @@
+using SWRIS.Models;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SWRIS.Events
+{
+    public class SetAbsolutePositionDataReceivedEventArgs : EventArgs
+    {
+        public string IpAddress { get; }
+        public SetAbsolutePositionDataModel SetAbsolutePositionData { get; set; }
+
+        public SetAbsolutePositionDataReceivedEventArgs(string ipAddress, SetAbsolutePositionDataModel  setAbsolutePosition)
+        {
+            IpAddress = ipAddress;
+            SetAbsolutePositionData = setAbsolutePosition;
+        }
+    }
+}

+ 16 - 0
Events/UpgradedResultReceivedEventArgs.cs

@@ -0,0 +1,16 @@
+using System;
+
+namespace SWRIS.Events
+{
+    public class UpgradedResultReceivedEventArgs : EventArgs
+    {
+        public string IpAddress { get; }
+        public byte Code { get; set; }
+
+        public UpgradedResultReceivedEventArgs(string ipAddress, byte code)
+        {
+            IpAddress = ipAddress;
+            Code = code;
+        }
+    }
+}

+ 591 - 0
Extensions/ByteTransform.cs

@@ -0,0 +1,591 @@
+using SWRIS.Enums;
+using System;
+using System.Text;
+
+namespace SWRIS.Extensions
+{
+    public class ByteTransform
+    {
+        #region Constructor
+
+        /// <summary>
+        /// 实例化一个默认的对象<br />
+        /// Instantiate a default object
+        /// </summary>
+        public ByteTransform()
+        {
+            DataFormat = DataFormat.ABCD;
+        }
+
+        /// <summary>
+        /// 使用指定的数据解析来实例化对象<br />
+        /// Instantiate the object using the specified data parsing
+        /// </summary>
+        /// <param name="dataFormat">数据规则</param>
+        public ByteTransform(DataFormat dataFormat)
+        {
+            this.DataFormat = dataFormat;
+        }
+
+        #endregion
+
+        #region Get Value From Bytes
+
+        /// <inheritdoc cref="IByteTransform.TransBool(byte[], int)"/>
+        public virtual bool TransBool(byte[] buffer, int index) => GetBoolByIndex(buffer, index);
+
+        /// <inheritdoc cref="IByteTransform.TransBool(byte[], int, int)"/>
+        public virtual bool[] TransBool(byte[] buffer, int index, int length)
+        {
+            bool[] result = new bool[length];
+            for (int i = 0; i < length; i++)
+            {
+                result[i] = GetBoolByIndex(buffer, i + index);
+            }
+            return result;
+        }
+        /// <summary>
+        /// 获取Byte数组的第 boolIndex 偏移的bool值,这个偏移值可以为 10,就是第 1 个字节的 第3位 <br />
+        /// Get the bool value of the boolIndex offset of the Byte array. The offset value can be 10, which is the third bit of the first byte
+        /// </summary>
+        /// <param name="bytes">字节数组信息</param>
+        /// <param name="boolIndex">指定字节的位偏移</param>
+        /// <returns>bool值</returns>
+        public static bool GetBoolByIndex(byte[] bytes, int boolIndex)
+        {
+            return SoftBasic.BoolOnByteIndex(bytes[boolIndex / 8], boolIndex % 8);
+        }
+        /// <inheritdoc cref="IByteTransform.TransByte(byte[], int)"/>
+        public virtual byte TransByte(byte[] buffer, int index) => buffer[index];
+
+        /// <inheritdoc cref="IByteTransform.TransByte(byte[], int, int)"/>
+        public virtual byte[] TransByte(byte[] buffer, int index, int length)
+        {
+            byte[] tmp = new byte[length];
+            Array.Copy(buffer, index, tmp, 0, length);
+            return tmp;
+        }
+
+        /// <inheritdoc cref="IByteTransform.TransInt16(byte[], int)"/>
+        public virtual short TransInt16(byte[] buffer, int index) => BitConverter.ToInt16(ByteTransDataFormat2(buffer, index), 0);
+
+        /// <inheritdoc cref="IByteTransform.TransInt16(byte[], int, int)"/>
+        public virtual short[] TransInt16(byte[] buffer, int index, int length)
+        {
+            short[] tmp = new short[length];
+            for (int i = 0; i < length; i++)
+            {
+                tmp[i] = TransInt16(buffer, index + 2 * i);
+            }
+            return tmp;
+        }
+
+        /// <inheritdoc cref="IByteTransform.TransInt16(byte[], int, int, int)"/>
+        public short[,] TransInt16(byte[] buffer, int index, int row, int col)
+        {
+            return CreateTwoArrayFromOneArray(TransInt16(buffer, index, row * col), row, col);
+        }
+
+        /// <inheritdoc cref="IByteTransform.TransUInt16(byte[], int)"/>
+        public virtual ushort TransUInt16(byte[] buffer, int index) => BitConverter.ToUInt16(ByteTransDataFormat2(buffer, index), 0);
+
+        /// <inheritdoc cref="IByteTransform.TransUInt16(byte[], int, int)"/>
+        public virtual ushort[] TransUInt16(byte[] buffer, int index, int length)
+        {
+            ushort[] tmp = new ushort[length];
+            for (int i = 0; i < length; i++)
+            {
+                tmp[i] = TransUInt16(buffer, index + 2 * i);
+            }
+            return tmp;
+        }
+
+        /// <inheritdoc cref="IByteTransform.TransUInt16(byte[], int, int, int)"/>
+        public ushort[,] TransUInt16(byte[] buffer, int index, int row, int col)
+        {
+            return CreateTwoArrayFromOneArray(TransUInt16(buffer, index, row * col), row, col);
+        }
+
+        /// <inheritdoc cref="IByteTransform.TransInt32(byte[], int)"/>
+        public virtual int TransInt32(byte[] buffer, int index) => BitConverter.ToInt32(ByteTransDataFormat4(buffer, index), 0);
+
+        /// <inheritdoc cref="IByteTransform.TransInt32(byte[], int, int)"/>
+        public virtual int[] TransInt32(byte[] buffer, int index, int length)
+        {
+            int[] tmp = new int[length];
+            for (int i = 0; i < length; i++)
+            {
+                tmp[i] = TransInt32(buffer, index + 4 * i);
+            }
+            return tmp;
+        }
+
+        /// <inheritdoc cref="IByteTransform.TransInt32(byte[], int, int, int)"/>
+        public int[,] TransInt32(byte[] buffer, int index, int row, int col)
+        {
+            return CreateTwoArrayFromOneArray(TransInt32(buffer, index, row * col), row, col);
+        }
+
+        /// <inheritdoc cref="IByteTransform.TransUInt32(byte[], int)"/>
+        public virtual uint TransUInt32(byte[] buffer, int index) => BitConverter.ToUInt32(ByteTransDataFormat4(buffer, index), 0);
+
+        /// <inheritdoc cref="IByteTransform.TransUInt32(byte[], int, int)"/>
+        public virtual uint[] TransUInt32(byte[] buffer, int index, int length)
+        {
+            uint[] tmp = new uint[length];
+            for (int i = 0; i < length; i++)
+            {
+                tmp[i] = TransUInt32(buffer, index + 4 * i);
+            }
+            return tmp;
+        }
+
+        /// <inheritdoc cref="IByteTransform.TransUInt32(byte[], int, int, int)"/>
+        public uint[,] TransUInt32(byte[] buffer, int index, int row, int col)
+        {
+            return CreateTwoArrayFromOneArray(TransUInt32(buffer, index, row * col), row, col);
+        }
+
+
+        /// <inheritdoc cref="IByteTransform.TransInt64(byte[], int)"/>
+        public virtual long TransInt64(byte[] buffer, int index) => BitConverter.ToInt64(ByteTransDataFormat8(buffer, index), 0);
+
+        /// <inheritdoc cref="IByteTransform.TransInt64(byte[], int, int)"/>
+        public virtual long[] TransInt64(byte[] buffer, int index, int length)
+        {
+            long[] tmp = new long[length];
+            for (int i = 0; i < length; i++)
+            {
+                tmp[i] = TransInt64(buffer, index + 8 * i);
+            }
+            return tmp;
+        }
+
+        /// <inheritdoc cref="IByteTransform.TransInt64(byte[], int, int, int)"/>
+        public long[,] TransInt64(byte[] buffer, int index, int row, int col)
+        {
+            return CreateTwoArrayFromOneArray(TransInt64(buffer, index, row * col), row, col);
+        }
+
+        /// <inheritdoc cref="IByteTransform.TransUInt64(byte[], int)"/>
+        public virtual ulong TransUInt64(byte[] buffer, int index) => BitConverter.ToUInt64(ByteTransDataFormat8(buffer, index), 0);
+
+        /// <inheritdoc cref="IByteTransform.TransUInt64(byte[], int, int)"/>
+        public virtual ulong[] TransUInt64(byte[] buffer, int index, int length)
+        {
+            ulong[] tmp = new ulong[length];
+            for (int i = 0; i < length; i++)
+            {
+                tmp[i] = TransUInt64(buffer, index + 8 * i);
+            }
+            return tmp;
+        }
+
+        /// <inheritdoc cref="IByteTransform.TransUInt64(byte[], int, int, int)"/>
+        public ulong[,] TransUInt64(byte[] buffer, int index, int row, int col)
+        {
+            return CreateTwoArrayFromOneArray(TransUInt64(buffer, index, row * col), row, col);
+        }
+
+        /// <inheritdoc cref="IByteTransform.TransSingle(byte[], int)"/>
+        public virtual float TransSingle(byte[] buffer, int index) => BitConverter.ToSingle(ByteTransDataFormat4(buffer, index), 0);
+
+        /// <inheritdoc cref="IByteTransform.TransSingle(byte[], int, int)"/>
+        public virtual float[] TransSingle(byte[] buffer, int index, int length)
+        {
+            float[] tmp = new float[length];
+            for (int i = 0; i < length; i++)
+            {
+                tmp[i] = TransSingle(buffer, index + 4 * i);
+            }
+            return tmp;
+        }
+
+        /// <inheritdoc cref="IByteTransform.TransSingle(byte[], int, int, int)"/>
+        public float[,] TransSingle(byte[] buffer, int index, int row, int col)
+        {
+            return CreateTwoArrayFromOneArray(TransSingle(buffer, index, row * col), row, col);
+        }
+
+        /// <inheritdoc cref="IByteTransform.TransDouble(byte[], int)"/>
+        public virtual double TransDouble(byte[] buffer, int index) => BitConverter.ToDouble(ByteTransDataFormat8(buffer, index), 0);
+
+        /// <inheritdoc cref="IByteTransform.TransDouble(byte[], int, int)"/>
+        public virtual double[] TransDouble(byte[] buffer, int index, int length)
+        {
+            double[] tmp = new double[length];
+            for (int i = 0; i < length; i++)
+            {
+                tmp[i] = TransDouble(buffer, index + 8 * i);
+            }
+            return tmp;
+        }
+
+        /// <inheritdoc cref="IByteTransform.TransDouble(byte[], int, int, int)"/>
+        public double[,] TransDouble(byte[] buffer, int index, int row, int col)
+        {
+            return CreateTwoArrayFromOneArray(TransDouble(buffer, index, row * col), row, col);
+        }
+
+        /// <inheritdoc cref="IByteTransform.TransString(byte[], int, int, Encoding)"/>
+        public virtual string TransString(byte[] buffer, int index, int length, Encoding encoding)
+        {
+            byte[] tmp = TransByte(buffer, index, length);
+            if (IsStringReverseByteWord)
+                return encoding.GetString(SoftBasic.BytesReverseByWord(tmp));
+            else
+                return encoding.GetString(tmp);
+        }
+
+        /// <summary>
+        /// 将一个一维数组中的所有数据按照行列信息拷贝到二维数组里,返回当前的二维数组
+        /// </summary>
+        /// <typeparam name="T">数组的类型对象</typeparam>
+        /// <param name="array">一维数组信息</param>
+        /// <param name="row">行</param>
+        /// <param name="col">列</param>
+        public static T[,] CreateTwoArrayFromOneArray<T>(T[] array, int row, int col)
+        {
+            T[,] twoArray = new T[row, col];
+            int count = 0;
+            for (int i = 0; i < row; i++)
+            {
+                for (int j = 0; j < col; j++)
+                {
+                    twoArray[i, j] = array[count];
+                    count++;
+                }
+            }
+            return twoArray;
+        }
+        /// <inheritdoc cref="IByteTransform.TransString(byte[], Encoding)"/>
+        public virtual string TransString(byte[] buffer, Encoding encoding) => encoding.GetString(buffer);
+
+        #endregion
+
+        #region Get Bytes From Value
+
+        /// <inheritdoc cref="IByteTransform.TransByte(bool)"/>
+        public virtual byte[] TransByte(bool value) => TransByte(new bool[] { value });
+
+        /// <inheritdoc cref="IByteTransform.TransByte(bool[])"/>
+        public virtual byte[] TransByte(bool[] values) => values == null ? null : SoftBasic.BoolArrayToByte(values);
+
+        /// <inheritdoc cref="IByteTransform.TransByte(byte)"/>
+        public virtual byte[] TransByte(byte value) => new byte[] { value };
+
+        /// <inheritdoc cref="IByteTransform.TransByte(short)"/>
+        public virtual byte[] TransByte(short value) => TransByte(new short[] { value });
+
+        /// <inheritdoc cref="IByteTransform.TransByte(short[])"/>
+        public virtual byte[] TransByte(short[] values)
+        {
+            if (values == null) return null;
+            byte[] buffer = new byte[values.Length * 2];
+            for (int i = 0; i < values.Length; i++)
+            {
+                ByteTransDataFormat2(BitConverter.GetBytes(values[i])).CopyTo(buffer, 2 * i);
+            }
+            return buffer;
+        }
+
+        /// <inheritdoc cref="IByteTransform.TransByte(ushort)"/>
+        public virtual byte[] TransByte(ushort value) => TransByte(new ushort[] { value });
+
+        /// <inheritdoc cref="IByteTransform.TransByte(ushort[])"/>
+        public virtual byte[] TransByte(ushort[] values)
+        {
+            if (values == null) return null;
+
+            byte[] buffer = new byte[values.Length * 2];
+            for (int i = 0; i < values.Length; i++)
+            {
+                ByteTransDataFormat2(BitConverter.GetBytes(values[i])).CopyTo(buffer, 2 * i);
+            }
+
+            return buffer;
+        }
+
+        /// <inheritdoc cref="IByteTransform.TransByte(int)"/>
+        public virtual byte[] TransByte(int value) => TransByte(new int[] { value });
+
+        /// <inheritdoc cref="IByteTransform.TransByte(int[])"/>
+        public virtual byte[] TransByte(int[] values)
+        {
+            if (values == null) return null;
+
+            byte[] buffer = new byte[values.Length * 4];
+            for (int i = 0; i < values.Length; i++)
+            {
+                ByteTransDataFormat4(BitConverter.GetBytes(values[i])).CopyTo(buffer, 4 * i);
+            }
+
+            return buffer;
+        }
+
+        /// <inheritdoc cref="IByteTransform.TransByte(uint)"/>
+        public virtual byte[] TransByte(uint value) => TransByte(new uint[] { value });
+
+        /// <inheritdoc cref="IByteTransform.TransByte(uint[])"/>
+        public virtual byte[] TransByte(uint[] values)
+        {
+            if (values == null) return null;
+
+            byte[] buffer = new byte[values.Length * 4];
+            for (int i = 0; i < values.Length; i++)
+            {
+                ByteTransDataFormat4(BitConverter.GetBytes(values[i])).CopyTo(buffer, 4 * i);
+            }
+
+            return buffer;
+        }
+
+        /// <inheritdoc cref="IByteTransform.TransByte(long)"/>
+        public virtual byte[] TransByte(long value) => TransByte(new long[] { value });
+
+        /// <inheritdoc cref="IByteTransform.TransByte(long[])"/>
+        public virtual byte[] TransByte(long[] values)
+        {
+            if (values == null) return null;
+
+            byte[] buffer = new byte[values.Length * 8];
+            for (int i = 0; i < values.Length; i++)
+            {
+                ByteTransDataFormat8(BitConverter.GetBytes(values[i])).CopyTo(buffer, 8 * i);
+            }
+
+            return buffer;
+        }
+
+        /// <inheritdoc cref="IByteTransform.TransByte(ulong)"/>
+        public virtual byte[] TransByte(ulong value) => TransByte(new ulong[] { value });
+
+        /// <inheritdoc cref="IByteTransform.TransByte(ulong[])"/>
+        public virtual byte[] TransByte(ulong[] values)
+        {
+            if (values == null) return null;
+
+            byte[] buffer = new byte[values.Length * 8];
+            for (int i = 0; i < values.Length; i++)
+            {
+                ByteTransDataFormat8(BitConverter.GetBytes(values[i])).CopyTo(buffer, 8 * i);
+            }
+
+            return buffer;
+        }
+
+        /// <inheritdoc cref="IByteTransform.TransByte(float)"/>
+        public virtual byte[] TransByte(float value) => TransByte(new float[] { value });
+
+        /// <inheritdoc cref="IByteTransform.TransByte(float[])"/>
+        public virtual byte[] TransByte(float[] values)
+        {
+            if (values == null) return null;
+
+            byte[] buffer = new byte[values.Length * 4];
+            for (int i = 0; i < values.Length; i++)
+            {
+                ByteTransDataFormat4(BitConverter.GetBytes(values[i])).CopyTo(buffer, 4 * i);
+            }
+
+            return buffer;
+        }
+
+        /// <inheritdoc cref="IByteTransform.TransByte(double)"/>
+        public virtual byte[] TransByte(double value) => TransByte(new double[] { value });
+
+        /// <inheritdoc cref="IByteTransform.TransByte(double[])"/>
+        public virtual byte[] TransByte(double[] values)
+        {
+            if (values == null) return null;
+
+            byte[] buffer = new byte[values.Length * 8];
+            for (int i = 0; i < values.Length; i++)
+            {
+                ByteTransDataFormat8(BitConverter.GetBytes(values[i])).CopyTo(buffer, 8 * i);
+            }
+
+            return buffer;
+        }
+
+        /// <inheritdoc cref="IByteTransform.TransByte(string, Encoding)"/>
+        public virtual byte[] TransByte(string value, Encoding encoding)
+        {
+            if (value == null) return null;
+            byte[] buffer = encoding.GetBytes(value);
+            return IsStringReverseByteWord ? SoftBasic.BytesReverseByWord(buffer) : buffer;
+        }
+
+        /// <inheritdoc cref="IByteTransform.TransByte(string, int, Encoding)"/>
+        public virtual byte[] TransByte(string value, int length, Encoding encoding)
+        {
+            if (value == null) return null;
+            byte[] buffer = encoding.GetBytes(value);
+            return IsStringReverseByteWord ? SoftBasic.ArrayExpandToLength(SoftBasic.BytesReverseByWord(buffer), length) : SoftBasic.ArrayExpandToLength(buffer, length);
+        }
+
+        #endregion
+
+        #region DataFormat Support
+
+        /// <summary>
+        /// 反转两个字节的数据信息
+        /// </summary>
+        /// <param name="value">原始字节数据</param>
+        /// <param name="index">起始的索引</param>
+        /// <returns>反转后的字节</returns>
+        protected virtual byte[] ByteTransDataFormat2(byte[] value, int index = 0)
+        {
+            byte[] buffer = new byte[2];
+            switch (DataFormat)
+            {
+                case DataFormat.ABCD:
+                case DataFormat.CDAB:
+                    {
+                        buffer[0] = value[index + 1];
+                        buffer[1] = value[index + 0];
+                        break;
+                    }
+                case DataFormat.BADC:
+                case DataFormat.DCBA:
+                    {
+                        buffer[0] = value[index + 0];
+                        buffer[1] = value[index + 1];
+                        break;
+                    }
+            }
+            return buffer;
+        }
+
+        /// <summary>
+        /// 反转多字节的数据信息
+        /// </summary>
+        /// <param name="value">数据字节</param>
+        /// <param name="index">起始索引,默认值为0</param>
+        /// <returns>实际字节信息</returns>
+        protected virtual byte[] ByteTransDataFormat4(byte[] value, int index = 0)
+        {
+            byte[] buffer = new byte[4];
+            switch (DataFormat)
+            {
+                case DataFormat.ABCD:
+                    {
+                        buffer[0] = value[index + 3];
+                        buffer[1] = value[index + 2];
+                        buffer[2] = value[index + 1];
+                        buffer[3] = value[index + 0];
+                        break;
+                    }
+                case DataFormat.BADC:
+                    {
+                        buffer[0] = value[index + 2];
+                        buffer[1] = value[index + 3];
+                        buffer[2] = value[index + 0];
+                        buffer[3] = value[index + 1];
+                        break;
+                    }
+
+                case DataFormat.CDAB:
+                    {
+                        buffer[0] = value[index + 1];
+                        buffer[1] = value[index + 0];
+                        buffer[2] = value[index + 3];
+                        buffer[3] = value[index + 2];
+                        break;
+                    }
+                case DataFormat.DCBA:
+                    {
+                        buffer[0] = value[index + 0];
+                        buffer[1] = value[index + 1];
+                        buffer[2] = value[index + 2];
+                        buffer[3] = value[index + 3];
+                        break;
+                    }
+            }
+            return buffer;
+        }
+
+
+        /// <summary>
+        /// 反转多字节的数据信息
+        /// </summary>
+        /// <param name="value">数据字节</param>
+        /// <param name="index">起始索引,默认值为0</param>
+        /// <returns>实际字节信息</returns>
+        protected virtual byte[] ByteTransDataFormat8(byte[] value, int index = 0)
+        {
+            byte[] buffer = new byte[8];
+            switch (DataFormat)
+            {
+                case DataFormat.ABCD:
+                    {
+                        buffer[0] = value[index + 7];
+                        buffer[1] = value[index + 6];
+                        buffer[2] = value[index + 5];
+                        buffer[3] = value[index + 4];
+                        buffer[4] = value[index + 3];
+                        buffer[5] = value[index + 2];
+                        buffer[6] = value[index + 1];
+                        buffer[7] = value[index + 0];
+                        break;
+                    }
+                case DataFormat.BADC:
+                    {
+                        buffer[0] = value[index + 6];
+                        buffer[1] = value[index + 7];
+                        buffer[2] = value[index + 4];
+                        buffer[3] = value[index + 5];
+                        buffer[4] = value[index + 2];
+                        buffer[5] = value[index + 3];
+                        buffer[6] = value[index + 0];
+                        buffer[7] = value[index + 1];
+                        break;
+                    }
+
+                case DataFormat.CDAB:
+                    {
+                        buffer[0] = value[index + 1];
+                        buffer[1] = value[index + 0];
+                        buffer[2] = value[index + 3];
+                        buffer[3] = value[index + 2];
+                        buffer[4] = value[index + 5];
+                        buffer[5] = value[index + 4];
+                        buffer[6] = value[index + 7];
+                        buffer[7] = value[index + 6];
+                        break;
+                    }
+                case DataFormat.DCBA:
+                    {
+                        buffer[0] = value[index + 0];
+                        buffer[1] = value[index + 1];
+                        buffer[2] = value[index + 2];
+                        buffer[3] = value[index + 3];
+                        buffer[4] = value[index + 4];
+                        buffer[5] = value[index + 5];
+                        buffer[6] = value[index + 6];
+                        buffer[7] = value[index + 7];
+                        break;
+                    }
+            }
+            return buffer;
+        }
+        #endregion
+
+        #region Public Properties
+
+        /// <inheritdoc cref="IByteTransform.DataFormat"/>
+        public DataFormat DataFormat { get; set; }
+
+        /// <inheritdoc cref="IByteTransform.IsStringReverseByteWord"/>
+        public bool IsStringReverseByteWord { get; set; }
+
+        #endregion
+
+        #region Object Override
+
+        /// <inheritdoc/>
+        public override string ToString() => $"RegularByteTransform[{DataFormat}]";
+
+        #endregion
+    }
+}

+ 81 - 0
Extensions/DatFileHandler.cs

@@ -0,0 +1,81 @@
+using SWRIS.Core;
+using System;
+using System.IO;
+using System.Text.Json;
+
+namespace SWRIS.Extensions
+{
+    public class DatFileHandler
+    {
+        public static bool CreateDatFile(string filePath, MemoryStream memoryData)
+        {
+            try
+            {
+                byte[] bytes = memoryData.ToArray();
+                JsonCryptoHelper.EncryptToFile(bytes, filePath, ConfigHelper.AesKey, ConfigHelper.AesIV);
+                return true;
+            }
+            catch (Exception ex)
+            {
+                LogHelper.Error($"创建JSON文件失败: {ex.Message}", ex);
+                return false;
+            }
+        }
+        public static (string absolutePath, string relativePath) GetDatFilePath(string serialNo)
+        {
+            return GetDateFilePath(AppDomain.CurrentDomain.BaseDirectory, serialNo);
+        }
+        public static byte[] ReadDatFile(string filePath)
+        {
+            filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, filePath);
+            byte[] bytes = null;
+            try
+            {
+                if (!File.Exists(filePath))
+                {
+                    return bytes;
+                }
+                bytes = JsonCryptoHelper.DecryptFromFile<byte[]>(filePath, ConfigHelper.AesKey, ConfigHelper.AesIV);
+            }
+            catch (JsonException jsonEx)
+            {
+                LogHelper.Error($"JSON反序列化失败: {jsonEx.Message}", jsonEx);
+            }
+            catch (Exception ex)
+            {
+                LogHelper.Error($"读取文件失败: {ex.Message}", ex);
+            }
+            return bytes;
+        }
+        public static bool DeleteDatFile(string filePath)
+        {
+            try
+            {
+                if (!File.Exists(filePath))
+                {
+                    return true;
+                }
+                File.Delete(filePath);
+            }
+            catch (Exception ex)
+            {
+                LogHelper.Error($"删除文件失败: {ex.Message}", ex);
+                return false;
+            }
+            return true;
+        }
+        private static (string absolutePath, string relativePath) GetDateFilePath(string baseDirectory, string serialNo)
+        {
+            string relativeFolder = Path.Combine("Data", DateTime.Now.ToString("yyyy-MM-dd"));
+            string absoluteFolder = Path.Combine(baseDirectory, relativeFolder);
+            if (!Directory.Exists(absoluteFolder))
+            {
+                Directory.CreateDirectory(absoluteFolder);
+            }
+            string relativePath = Path.Combine(relativeFolder, $"{serialNo}-{DateTime.Now:yyyy-MM-dd HH-mm-ss}.dat");// 相对路径
+            string absolutePath = Path.Combine(baseDirectory, relativePath);// 绝对路径
+
+            return (absolutePath, relativePath);
+        }
+    }
+}

+ 623 - 0
Extensions/FileHelper.cs

@@ -0,0 +1,623 @@
+using SWRIS.Extensions;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading;
+
+namespace SWRIS.Core
+{
+    public class FileHelper
+    {
+        public static string FileToString(string filePath)
+        {
+            return FileToString(filePath, Encoding.UTF8);
+        }
+
+        public static string FileToString(string filePath, Encoding encoding)
+        {
+            string result;
+            if (!File.Exists(filePath))
+            {
+                result = string.Empty;
+            }
+            else
+            {
+                try
+                {
+                    using (StreamReader streamReader = new StreamReader(filePath, encoding))
+                    {
+                        result = streamReader.ReadToEnd();
+                    }
+                }
+                catch (Exception ex)
+                {
+                    throw ex;
+                }
+            }
+            return result;
+        }
+
+        public static FileStream FileToStream(string path)
+        {
+            ArgumentNotNullOrEmpty(path, "path");
+            return new FileStream(path, FileMode.Open, FileAccess.Read);
+        }
+
+        public static byte[] FileToBytes(string filePath, int len = 0, int start = 0)
+        {
+            byte[] result;
+            using (FileStream fileStream = FileToStream(filePath))
+            {
+                int num = (fileStream.Length > 2147483647L) ? int.MaxValue : ((int)fileStream.Length);
+                int num2 = (len == 0) ? num : len;
+                num2 = ((num2 >= num) ? num : num2);
+                byte[] array = new byte[num2];
+                fileStream.Read(array, start, num2);
+                result = array;
+            }
+            return result;
+        }
+
+        public static void CreateDirectory(string directoryPath)
+        {
+            if (!string.IsNullOrEmpty(directoryPath))
+            {
+                if (!Directory.Exists(directoryPath))
+                {
+                    ThreadPool.QueueUserWorkItem(delegate (object obj)
+                    {
+                        try
+                        {
+                            Directory.CreateDirectory(directoryPath);
+                        }
+                        catch (Exception ex)
+                        {
+                            LogHelper.Error(string.Format("文件夹{0}创建失败", directoryPath), ex);
+                        }
+                    });
+                    
+                }
+            }
+        }
+
+        public static string CreateFileDirectory(string filePath)
+        {
+            string filePath2 = GetFilePath(filePath, '\\');
+            CreateDirectory(filePath2);
+            return filePath2;
+        }
+
+        public static void CreateFile(string filePath)
+        {
+            if (!string.IsNullOrEmpty(filePath))
+            {
+                try
+                {
+                    if (!File.Exists(filePath))
+                    {
+                        string filePath2 = GetFilePath(filePath, '\\');
+                        CreateDirectory(filePath2);
+                        lock (_sync)
+                        {
+                            using (new FileStream(filePath, FileMode.OpenOrCreate))
+                            {
+                            }
+                        }
+                    }
+                }
+                catch (Exception innerException)
+                {
+                    throw new Exception("创建文件异常", innerException);
+                }
+            }
+        }
+
+        public static string CreateFile(string filePath, byte[] buffer)
+        {
+            if (!string.IsNullOrEmpty(filePath))
+            {
+                filePath = GetFileSavaPath(filePath);
+                try
+                {
+                    FileInfo fileInfo = new FileInfo(filePath);
+                    using (FileStream fileStream = fileInfo.Create())
+                    {
+                        fileStream.Write(buffer, 0, buffer.Length);
+                    }
+                    return filePath;
+                }
+                catch
+                {
+
+                }
+            }
+            return null;
+
+        }
+
+        public static void CreateFile(string filePath, string text)
+        {
+            CreateFile(filePath, text, Encoding.GetEncoding("utf-8"));
+        }
+
+        public static void CreateFile(string filePath, string text, Encoding encoding)
+        {
+            if (filePath != null && !(filePath == string.Empty))
+            {
+                string filePath2 = GetFilePath(filePath, '\\');
+                if (!Directory.Exists(filePath2))
+                {
+                    Directory.CreateDirectory(filePath2);
+                }
+                FileInfo fileInfo = new FileInfo(filePath);
+                using (FileStream fileStream = fileInfo.Create())
+                {
+                    using (StreamWriter streamWriter = new StreamWriter(fileStream, encoding))
+                    {
+                        streamWriter.Write(text);
+                        streamWriter.Flush();
+                    }
+                }
+            }
+        }
+
+        public static void Open(params string[] path)
+        {
+            if (!path.IsInvalid<string>())
+            {
+                string text = FileHelper.ConnectPath(path);
+                try
+                {
+                    Process.Start("explorer.exe", text);
+                }
+                catch (Exception innerException)
+                {
+                    throw new Exception("打开文件出现异常:" + text, innerException);
+                }
+            }
+        }
+
+        public static void DeleteFile(string filePath)
+        {
+            if (!filePath.IsInvalid())
+            {
+                try
+                {
+                    if (File.Exists(filePath))
+                    {
+                        File.Delete(filePath);
+                    }
+
+                }
+                catch (Exception innerException)
+                {
+                    throw new Exception("删除文件异常:" + filePath, innerException);
+                }
+            }
+        }
+
+        public static string GetFileName(string source, char separate = '\\')
+        {
+            string result;
+            if (source.IsInvalid())
+            {
+                result = string.Empty;
+            }
+            else
+            {
+                source = source.TrimEnd(new char[]
+                {
+                    separate
+                });
+                int num = source.LastIndexOf(separate);
+                if (num > 0)
+                {
+                    result = source.Substring(num + 1, source.Length - num - 1);
+                }
+                else
+                {
+                    result = source;
+                }
+            }
+            return result;
+        }
+
+        public static string GetFilePath(string source, char separate = '\\')
+        {
+            string result;
+            if (source.IsInvalid())
+            {
+                result = string.Empty;
+            }
+            else
+            {
+                source = source.TrimEnd(new char[]
+                {
+                    separate
+                });
+                int num = source.LastIndexOf(separate);
+                if (num > 0)
+                {
+                    result = source.Substring(0, num + 1);
+                }
+                else
+                {
+                    result = source;
+                }
+            }
+            return result;
+        }
+
+        public static string ConnectPath(char separate, params string[] path)
+        {
+            string result;
+            if (path.IsInvalid<string>())
+            {
+                result = string.Empty;
+            }
+            else if (path.Length == 2)
+            {
+                result = string.Format("{0}{1}{2}", path[0].TrimEnd(new char[]
+                {
+                    separate
+                }), separate, path[1].TrimStart(new char[]
+                {
+                    separate
+                }));
+            }
+            else if (path.Length == 1)
+            {
+                result = path[0];
+            }
+            else
+            {
+                StringBuilder stringBuilder = new StringBuilder(32);
+                foreach (string text in path)
+                {
+                    stringBuilder.Append(text.TrimEnd(new char[]
+                    {
+                        separate
+                    }).TrimStart(new char[]
+                    {
+                        separate
+                    })).Append(separate);
+                }
+                result = stringBuilder.ToString().TrimEnd(new char[]
+                {
+                    separate
+                });
+            }
+            return result;
+        }
+
+        public static string ConnectPath(params string[] path)
+        {
+            return ConnectPath('\\', path);
+        }
+
+        public static string ConvertLinuxPath(string linuxpath, char validChar = '_')
+        {
+            int num = linuxpath.IndexOfAny(InvalidPathChars);
+            string result;
+            if (num >= 0)
+            {
+                result = ConvertLinuxPath(linuxpath.Replace(linuxpath[num], validChar), '_');
+            }
+            else
+            {
+                result = linuxpath.Replace('/', '\\');
+            }
+            return result;
+        }
+
+        public static string ConnectLinuxPath(string path1, string path2)
+        {
+            return ConnectPath('/', new string[]
+            {
+                path1,
+                path2
+            });
+        }
+
+        public static string GetLinuxFileName(string source)
+        {
+            return GetFileName(source, '/');
+        }
+
+        public static string GetLinuxFilePath(string source)
+        {
+            return GetFilePath(source, '/');
+        }
+
+        public static string GetExtension(string filePath)
+        {
+            ArgumentNotNullOrEmpty(filePath, "filePath");
+            string result = string.Empty;
+            try
+            {
+                result = Path.GetExtension(filePath).Replace(".", string.Empty);
+            }
+            catch (Exception)
+            {
+            }
+            return result;
+        }
+
+        public static string TryGetExtension(string filename, char splitChar)
+        {
+            ArgumentNotNullOrEmpty(filename, "filePath");
+            string[] source = filename.Split(new char[]
+            {
+                splitChar
+            });
+            string result = string.Empty;
+            if (source.Any())
+            {
+                result = source.Last();
+            }
+            return result;
+        }
+
+        public static string GetPhysicalPath(string relativePath)
+        {
+            string result;
+            if (string.IsNullOrEmpty(relativePath))
+            {
+                result = string.Empty;
+            }
+            else
+            {
+                relativePath = relativePath.Replace("/", "\\").Replace("~", string.Empty).Replace("~/", string.Empty);
+                relativePath = (relativePath.StartsWith("\\") ? relativePath.Substring(1, relativePath.Length - 1) : relativePath);
+                string applicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
+                string text = Path.Combine(applicationBase, relativePath);
+                result = text;
+            }
+            return result;
+        }
+
+        public static string GetFileSize(string filePath)
+        {
+            ArgumentNotNullOrEmpty(filePath, "filePath");
+            string result;
+            if (!File.Exists(filePath))
+            {
+                result = string.Empty;
+            }
+            else
+            {
+                FileInfo fileInfo = new FileInfo(filePath);
+                result = GetFileSize(fileInfo.Length, "F2");
+            }
+            return result;
+        }
+
+        public static long GetFileLength(string filepath)
+        {
+            long result = 0L;
+            using (FileStream fileStream = new FileStream(filepath, FileMode.Open, FileAccess.Read))
+            {
+                result = fileStream.Length;
+            }
+            return result;
+        }
+
+        public static string GetFileSize(long len, string format = "F2")
+        {
+            string result;
+            if (len <= 0L)
+            {
+                result = "0 KB";
+            }
+            else
+            {
+                string str = " B";
+                double num = len;
+                double num2 = 1024.0;
+                if (len >= num2)
+                {
+                    num = len / num2;
+                    str = " KB";
+                }
+                if (num > num2)
+                {
+                    num /= num2;
+                    str = " MB";
+                }
+                if (num > num2)
+                {
+                    num /= num2;
+                    str = " GB";
+                }
+                if (num - Math.Truncate(num) == 0.0)
+                {
+                    result = num.ToString("F2") + str;
+                }
+                else
+                {
+                    result = num.ToString("F2") + str;
+                }
+            }
+            return result;
+        }
+
+        public static string GetFileSize(int len)
+        {
+            return GetFileSize(len, "F2");
+        }
+
+        public static IList<FileInfo> GetFiles(string folder, string extension)
+        {
+            return GetFiles(folder, new string[]
+            {
+                extension
+            });
+        }
+
+
+        public static string GetFileSavaPath(string fileName)
+        {
+            string directory = Path.GetDirectoryName(fileName);   //文件所在路径
+            string filename = Path.GetFileNameWithoutExtension(fileName);  //文件名
+            string extension = Path.GetExtension(fileName);    //文件后缀 带点(.)
+            int counter = 1;
+            string newFilename = string.Format("{0}{1}", filename, extension); //文件名+后缀
+            string newFullPath = Path.Combine(directory, newFilename);
+            while (File.Exists(newFullPath)) //如果文件存在,如果存在count+1,继续循环
+            {
+                newFilename = string.Format("{0}({1}){2}", filename, counter, extension); //文件名+(count)+后缀
+                newFullPath = Path.Combine(directory, newFilename);  //保存路径
+                counter++; //count+1
+            }
+            return newFullPath;
+        }
+
+
+        public static IList<FileInfo> GetFiles(string folder, string[] extensions)
+        {
+            string[] files = Directory.GetFiles(folder, "*.*", SearchOption.AllDirectories);
+            return (from file in files
+                    let extension = Path.GetExtension(file)
+                    where extension != null && extensions.Contains(extension.ToLower())
+                    select new FileInfo(file)).ToList();
+        }
+
+        public static bool IsValid(string file)
+        {
+            bool result;
+            if (file.IsInvalid())
+            {
+                result = false;
+            }
+            else if (!File.Exists(file))
+            {
+                result = false;
+            }
+            else
+            {
+                FileInfo fileInfo = new FileInfo(file);
+                result = (fileInfo.Length > 0L);
+            }
+            return result;
+        }
+
+        public static bool IsValidDictory(string path)
+        {
+            return !path.IsInvalid() && Directory.Exists(path);
+        }
+
+        public static byte[] ReadFileHead(string filePath, int index = 4)
+        {
+            byte[] result;
+            using (FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
+            {
+                byte[] array = new byte[index];
+                fileStream.Read(array, 0, index);
+                result = array;
+            }
+            return result;
+        }
+
+        public static string FilterInvalidFileName(string oriName)
+        {
+            return _InvalidChars.Aggregate(oriName, (string current, char invalidChar) => current.Replace(invalidChar.ToString(), string.Empty));
+        }
+
+        public static bool IsFileInUsing(string path)
+        {
+            bool result = true;
+            try
+            {
+                using (new FileStream(path, FileMode.Open))
+                {
+                    result = false;
+                }
+            }
+            catch
+            {
+            }
+            return result;
+        }
+
+        public static bool InputPathIsValid(string path)
+        {
+            return IsPositiveNumber(path) && IsHasUninvalidPathChars(path);
+        }
+        private static bool IsPositiveNumber(string path)
+        {
+            return Regex.IsMatch(path, "^[a-zA-Z]:\\\\[^\\/\\:\\*\\?\\\"\\<\\>\\|\\,]+$", RegexOptions.IgnoreCase);
+        }
+
+        private static bool IsHasUninvalidPathChars(string path)
+        {
+            return !Path.GetInvalidPathChars().Any(new Func<char, bool>(path.Contains<char>));
+        }
+
+        private static readonly object _sync = new object();
+
+        private static readonly char[] InvalidPathChars = new char[]
+        {
+            '"',
+            '<',
+            '>',
+            '|',
+            '\0',
+            '\u0001',
+            '\u0002',
+            '\u0003',
+            '\u0004',
+            '\u0005',
+            '\u0006',
+            '\a',
+            '\b',
+            '\t',
+            '\n',
+            '\v',
+            '\f',
+            '\r',
+            '\u000e',
+            '\u000f',
+            '\u0010',
+            '\u0011',
+            '\u0012',
+            '\u0013',
+            '\u0014',
+            '\u0015',
+            '\u0016',
+            '\u0017',
+            '\u0018',
+            '\u0019',
+            '\u001a',
+            '\u001b',
+            '\u001c',
+            '\u001d',
+            '\u001e',
+            '\u001f',
+            '*',
+            '?',
+            ':'
+        };
+        public static void ArgumentNotNull(object value, string argumentName)
+        {
+            if (value == null)
+            {
+                throw new ArgumentNullException(argumentName, $"参数{argumentName}不能为NULL");
+            }
+        }
+
+        // Token: 0x0600027D RID: 637 RVA: 0x0000BDCC File Offset: 0x00009FCC
+        public static void ArgumentNotNullOrEmpty(string value, string argumentName)
+        {
+            if (value.IsNullOrEmptyOrWhiteSpace())
+            {
+                throw new ArgumentNullException(argumentName, $"参数{argumentName}不能为NULL或者为空");
+            }
+        }
+        private static readonly char[] _InvalidChars = Path.GetInvalidFileNameChars();
+      
+    }
+}

+ 154 - 0
Extensions/FirewallHelper.cs

@@ -0,0 +1,154 @@
+using NetFwTypeLib;
+using SWRIS.Core;
+using System;
+
+namespace SWRIS.Extensions
+{
+    public class FirewallHelper
+    {
+
+        /// <summary>
+        /// 为WindowsDefender防火墙添加一条通信端口出站规则
+        /// </summary>
+        /// <param name="type">规则类型</param>
+        /// <param name="ruleName">规则名称</param>
+        /// <param name="appPath">应用程序完整路径</param>
+        /// <param name="localAddresses">本地地址</param>
+        /// <param name="localPorts">本地端口</param>
+        /// <param name="remoteAddresses">远端地址</param>
+        /// <param name="remotePorts">远端端口</param>
+        public static bool CreateOutRule(NET_FW_IP_PROTOCOL_ type, string ruleName, string appPath, string localAddresses = null, string localPorts = null, string remoteAddresses = null, string remotePorts = null)
+        {
+            //创建防火墙策略类的实例
+            INetFwPolicy2 policy2 = (INetFwPolicy2)Activator.CreateInstance(Type.GetTypeFromProgID("HNetCfg.FwPolicy2"));
+            //检查是否有同名规则
+            foreach (INetFwRule item in policy2.Rules)
+            {
+                if (item.Name == ruleName)
+                {
+                    return true;
+                }
+            }
+            //创建防火墙规则类的实例: 有关该接口的详细介绍:https://docs.microsoft.com/zh-cn/windows/win32/api/netfw/nn-netfw-inetfwrule
+            INetFwRule rule = (INetFwRule)Activator.CreateInstance(Type.GetTypeFromProgID("HNetCfg.FwRule"));
+            //为规则添加名称
+            rule.Name = ruleName;
+            //为规则添加描述
+            rule.Description = "允许程序访问指定端口";
+            //选择入站规则还是出站规则,IN为入站,OUT为出站
+            rule.Direction = NET_FW_RULE_DIRECTION_.NET_FW_RULE_DIR_OUT;
+            //为规则添加协议类型
+            rule.Protocol = (int)type;
+            //为规则添加应用程序(注意这里是应用程序的绝对路径名)
+            rule.ApplicationName = appPath;
+            //为规则添加本地IP地址    
+            if (!string.IsNullOrEmpty(localAddresses))
+            {
+                rule.LocalAddresses = localAddresses;
+            }
+
+            //为规则添加本地端口
+            if (!string.IsNullOrEmpty(localPorts))
+            {
+                //需要移除空白字符(不能包含空白字符,下同)
+                rule.LocalPorts = localPorts.Replace(" ", "");// "1-29999, 30003-33332, 33334-55554, 55556-60004, 60008-65535";
+            }
+            //为规则添加远程IP地址
+            if (!string.IsNullOrEmpty(remoteAddresses))
+            {
+                rule.RemoteAddresses = remoteAddresses;
+            }
+            //为规则添加远程端口
+            if (!string.IsNullOrEmpty(remotePorts))
+            {
+                rule.RemotePorts = remotePorts.Replace(" ", "");
+            }
+            //设置规则是阻止还是允许(ALLOW=允许,BLOCK=阻止)
+            rule.Action = NET_FW_ACTION_.NET_FW_ACTION_ALLOW;
+            //分组 名
+            rule.Grouping = "SCWS";
+            rule.InterfaceTypes = "All";
+            //是否启用规则
+            rule.Enabled = true;
+            try
+            {
+                //添加规则到防火墙策略
+                policy2.Rules.Add(rule);
+            }
+            catch (Exception ex)
+            {
+                string error = $"防火墙添加规则出错:{ruleName} {ex.Message}";
+                LogHelper.Error(error, ex);
+                return false;
+            }
+            return true;
+        }
+        /// <summary>
+        /// 为WindowsDefender防火墙添加一条U3D通信TCP端口出站规则
+        /// </summary>
+        /// <param name="appPath">应用程序完整路径</param>
+        /// <param name="localAddresses">本地地址</param>
+        /// <param name="localPorts">本地端口</param>
+        /// <param name="remoteAddresses">远端地址</param>
+        /// <param name="remotePorts">远端端口</param>
+        public static bool CreateTCPOutRule(string appPath, string localAddresses = null, string localPorts = null, string remoteAddresses = null, string remotePorts = null)
+        {
+            try
+            {
+                string ruleName = $"{System.IO.Path.GetFileNameWithoutExtension(appPath)}TCP";
+                CreateOutRule(NET_FW_IP_PROTOCOL_.NET_FW_IP_PROTOCOL_TCP, ruleName, appPath, localAddresses, localPorts, remoteAddresses, remotePorts);
+            }
+            catch (Exception e)
+            {
+                LogHelper.Error(e.Message, e);
+                return false;
+            }
+            return true;
+        }
+        /// <summary>
+        /// 为WindowsDefender防火墙添加一条通信UDP端口出站规则
+        /// </summary>
+        /// <param name="appPath">应用程序完整路径</param>
+        /// <param name="localAddresses">本地地址</param>
+        /// <param name="localPorts">本地端口</param>
+        /// <param name="remoteAddresses">远端地址</param>
+        /// <param name="remotePorts">远端端口</param>
+        public static bool CreateUDPOutRule(string appPath, string localAddresses = null, string localPorts = null, string remoteAddresses = null, string remotePorts = null)
+        {
+            try
+            {
+                string ruleName = $"{System.IO.Path.GetFileNameWithoutExtension(appPath)}UDP";
+                CreateOutRule(NET_FW_IP_PROTOCOL_.NET_FW_IP_PROTOCOL_UDP, ruleName, appPath, localAddresses, localPorts, remoteAddresses, remotePorts);
+
+            }
+            catch (Exception e)
+            {
+                LogHelper.Error(e.Message, e);
+                return false;
+            }
+            return true;
+        }
+        /// <summary>
+        /// 删除WindowsDefender防火墙规则
+        /// <summary>
+        /// <param name="appPath">应用程序完整路径</param>
+        public static bool DeleteRule(string appPath)
+        {
+            //创建防火墙策略类的实例
+            INetFwPolicy2 policy2 = (INetFwPolicy2)Activator.CreateInstance(Type.GetTypeFromProgID("HNetCfg.FwPolicy2"));
+            string ruleName = System.IO.Path.GetFileNameWithoutExtension(appPath);
+            try
+            {
+                //根据规则名称移除规则
+                policy2.Rules.Remove(ruleName);
+            }
+            catch (Exception e)
+            {
+                string error = $"防火墙删除规则出错:{ruleName} {e.Message}";
+                LogHelper.Error(error, e);
+                return false;
+            }
+            return true;
+        }
+    }
+}

+ 91 - 0
Extensions/JsonCryptoHelper.cs

@@ -0,0 +1,91 @@
+using Newtonsoft.Json;
+using System;
+using System.IO;
+using System.Security.Cryptography;
+
+namespace SWRIS.Extensions
+{
+    public static class JsonCryptoHelper
+    {
+        public static void EncryptToFile<T>(T data, string filePath, string key, string iv)
+        {
+            // 序列化为 JSON 字符串
+            string json = JsonConvert.SerializeObject(data, Formatting.None);
+
+            // 加密 JSON 字符串
+            byte[] encrypted = EncryptStringToBytes(json, key, iv);
+
+            // 写入文件
+            File.WriteAllBytes(filePath, encrypted);
+        }
+
+        public static T DecryptFromFile<T>(string filePath, string key, string iv)
+        {
+            // 读取加密文件
+            byte[] encrypted = File.ReadAllBytes(filePath);
+
+            // 解密字节数组
+            string json = DecryptStringFromBytes(encrypted, key, iv);
+
+            // 反序列化为对象
+            return JsonConvert.DeserializeObject<T>(json);
+        }
+
+        private static byte[] EncryptStringToBytes(string plainText, string key, string iv)
+        {
+            byte[] keyBytes = Convert.FromBase64String(key);
+            byte[] ivBytes = Convert.FromBase64String(iv);
+            byte[] encrypted;
+
+            using (Aes aes = Aes.Create())
+            {
+                aes.Key = keyBytes;
+                aes.IV = ivBytes;
+
+                ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
+
+                using (MemoryStream ms = new MemoryStream())
+                {
+                    using (CryptoStream cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
+                    {
+                        using (StreamWriter sw = new StreamWriter(cs))
+                        {
+                            sw.Write(plainText);
+                        }
+                        encrypted = ms.ToArray();
+                    }
+                }
+            }
+
+            return encrypted;
+        }
+
+        private static string DecryptStringFromBytes(byte[] cipherText, string key, string iv)
+        {
+            byte[] keyBytes = Convert.FromBase64String(key);
+            byte[] ivBytes = Convert.FromBase64String(iv);
+            string plaintext;
+
+            using (Aes aes = Aes.Create())
+            {
+                aes.Key = keyBytes;
+                aes.IV = ivBytes;
+
+                ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV);
+
+                using (MemoryStream ms = new MemoryStream(cipherText))
+                {
+                    using (CryptoStream cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
+                    {
+                        using (StreamReader sr = new StreamReader(cs))
+                        {
+                            plaintext = sr.ReadToEnd();
+                        }
+                    }
+                }
+            }
+
+            return plaintext;
+        }
+    }
+}

+ 60 - 0
Extensions/LogHelper.cs

@@ -0,0 +1,60 @@
+using SWRIS.Extensions;
+using System;
+using System.ComponentModel;
+
+namespace SWRIS.Core
+{
+    public static class LogHelper
+    {
+        static LogHelper()
+        {
+            _logger.LogPath = FileHelper.GetPhysicalPath("Log");
+        }
+
+        public static string LogPath
+        {
+            set => _logger.LogPath = value;
+        }
+
+        public static void Info(string message, Exception exception = null)
+        {
+            _logger.Log(message, LogLevel.Info.GetDescription(), exception);
+        }
+
+        public static void Debug(string message, Exception exception = null)
+        {
+            _logger.Log(message, LogLevel.Debug.GetDescription(), exception);
+        }
+
+        public static void Warn(string message, Exception exception = null)
+        {
+            _logger.Log(message, LogLevel.Warn.GetDescription(), exception);
+        }
+
+        public static void Error(string message, Exception exception = null)
+        {
+            _logger.Log(message, LogLevel.Error.GetDescription(), exception);
+        }
+
+        public static void Fatal(string message, Exception exception = null)
+        {
+            _logger.Log(message, LogLevel.Fatal.GetDescription(), exception);
+        }
+
+        private static readonly SimpleLogger _logger = new SimpleLogger();
+
+        private enum LogLevel
+        {
+            [Description("信息")]
+            Info,
+            [Description("调试")]
+            Debug,
+            [Description("警告")]
+            Warn,
+            [Description("错误")]
+            Error,
+            [Description("严重错误")]
+            Fatal
+        }
+    }
+}

+ 309 - 0
Extensions/ReportPdfExporter.cs

@@ -0,0 +1,309 @@
+using QuestPDF.Fluent;
+using QuestPDF.Helpers;
+using QuestPDF.Infrastructure;
+using SWRIS.Core;
+using SWRIS.Dtos;
+using SWRIS.Models;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace SWRIS.Extensions
+{
+    public class ReportPdfExporter
+    {
+        private readonly string[] ChineseNumbers = { "零", "一", "二", "三", "四", "五", "六", "七", "八", "九", "十" };
+        private readonly string _crane;
+        private readonly RecordDto _record;
+        private readonly EquipmentModel _equipment;
+        private readonly int _damageNearPointCount = 20;
+        public ReportPdfExporter(string crane, RecordDto record, EquipmentModel equipment)
+        {
+            _crane = crane;
+            _record = record;
+            _equipment = equipment;
+            _damageNearPointCount = App.Config.DamageNearPointCount;
+        }
+        public bool SavePdf(string filePath)
+        {
+            if (_record == null || _equipment == null)
+            {
+                return false;
+            }
+            Document.Create(document =>
+            {
+                var damageList = _record.Damages.OrderByDescending(c => c.DamageValue).Take(5).OrderBy(c => c.DamagePoint).ToList();
+                List<(double? position, double? damage, byte[] image)> images = GenerateImage(_record.DataFilePath,
+                                                                                            _record.SensorCount,
+                                                                                            _record.StartPoint,
+                                                                                            _record.EndPoint,
+                                                                                            damageList: damageList);
+                document.Page(page =>
+                {
+                    page.Size(PageSizes.A4);
+                    page.MarginTop(2, Unit.Centimetre);
+                    page.MarginBottom(1, Unit.Centimetre);
+                    page.MarginLeft(1, Unit.Centimetre);
+                    page.MarginRight(1, Unit.Centimetre);
+                    page.Header().AlignCenter().AlignTop().Text("钢丝绳无损检测报告").FontSize(20).Bold();
+                    page.Content()
+                        .AlignCenter()
+                        .AlignTop()
+                        .PaddingTop(20)
+                        .Table(table =>
+                        {
+                            table.ColumnsDefinition(columns =>
+                            {
+                                columns.ConstantColumn(22);
+                                columns.RelativeColumn();
+                                columns.RelativeColumn();
+                                columns.RelativeColumn();
+                                columns.RelativeColumn();
+                                columns.RelativeColumn();
+                                columns.RelativeColumn();
+                            });
+                            table.Cell().Row(1).Column(1).ColumnSpan(4).PaddingBottom(5).Text($"所在起重机:{_crane}").AlignLeft();
+                            table.Cell().Row(1).Column(5).ColumnSpan(3).PaddingBottom(5).Text($"检测时间:{_record.StartTime:yyyy-MM-dd HH:mm:ss}")
+                                 .AlignRight();
+
+                            table.Cell().Row(2).Column(1).RowSpan(7).Border(1).AlignMiddle().Text("基本信息").Bold().AlignCenter();
+
+                            table.Cell().Row(2).Column(2).Element(CellNameStyle).Text("被检测钢丝绳");
+                            table.Cell().Row(2).Column(3).ColumnSpan(2).Element(CellValueStyle).Text(_equipment.RopeName);
+                            table.Cell().Row(2).Column(5).Element(CellNameStyle).Text("钢丝绳制造商");
+                            table.Cell().Row(2).Column(6).ColumnSpan(2).Element(CellValueStyle).Text(_equipment.Supplier);
+
+                            table.Cell().Row(3).Column(2).Element(CellNameStyle).Text("钢丝绳长");
+                            table.Cell().Row(3).Column(3).ColumnSpan(2).Element(CellValueStyle).Text(_equipment.Parameter.WireRopeLength.ToString() + "m");
+                            table.Cell().Row(3).Column(5).Element(CellNameStyle).Text("检测长度");
+                            table.Cell().Row(3).Column(6).ColumnSpan(2).Element(CellValueStyle).Text(_record.DetectionLength.ToString("F3") + "m");
+
+                            table.Cell().Row(4).Column(2).Element(CellNameStyle).Text("开始位置");
+                            table.Cell().Row(4).Column(3).ColumnSpan(2).Element(CellValueStyle).Text(_record.StartPoint.ToString("F3") + "m");
+                            table.Cell().Row(4).Column(5).Element(CellNameStyle).Text("结束位置");
+                            table.Cell().Row(4).Column(6).ColumnSpan(2).Element(CellValueStyle).Text(_record.EndPoint.ToString("F3") + "m");
+
+                            table.Cell().Row(5).Column(2).Element(CellNameStyle).Text("钢丝绳规格");
+                            table.Cell().Row(5).Column(3).ColumnSpan(2).Element(CellValueStyle).Text(
+                                 $"{_equipment.Parameter.WireRopeDiameter} {_equipment.WireSurfaceType} " +
+                                 $"{_equipment.Parameter.WireRopeStrandCount}*" +
+                                 $"{_equipment.Parameter.WireRopeStrandWireCount}S+{_equipment.RopeCoreType} " +
+                                 $"{_equipment.LayType}");
+                            table.Cell().Row(5).Column(5).Element(CellNameStyle).Text("钢丝材质");
+                            table.Cell().Row(5).Column(6).ColumnSpan(2).Element(CellValueStyle).Text(_equipment.WireMaterialType.GetDescription());
+
+                            table.Cell().Row(6).Column(2).Element(CellNameStyle).Text("钢丝绳公称直径");
+                            table.Cell().Row(6).Column(3).ColumnSpan(2).Element(CellValueStyle).Text(_equipment.Parameter.WireRopeDiameter + "mm");
+                            table.Cell().Row(6).Column(5).Element(CellNameStyle).Text("绳芯材质");
+                            table.Cell().Row(6).Column(6).ColumnSpan(2).Element(CellValueStyle).Text(_equipment.RopeCoreType.GetDescription());
+
+                            table.Cell().Row(7).Column(2).Element(CellNameStyle).Text("捻制方法");
+                            table.Cell().Row(7).Column(3).ColumnSpan(2).Element(CellValueStyle).Text(_equipment.LayType.GetDescription());
+                            table.Cell().Row(7).Column(5).Element(CellNameStyle).Text("钢丝绳表面状态");
+                            table.Cell().Row(7).Column(6).ColumnSpan(2).Element(CellValueStyle).Text(_equipment.WireSurfaceType.GetDescription());
+
+                            table.Cell().Row(8).Column(2).Element(CellNameStyle).Text("依据标准");
+                            table.Cell().Row(8).Column(3).ColumnSpan(5).Element(CellValueStyle).AlignLeft().Text(
+                                "GB/T 21837-2023 《铁磁性钢丝绳电磁检测方法》\r\n" +
+                                "GB/T 26832-2011 《无损检测仪器 钢丝绳电磁检测仪技术条件》\r\n" +
+                                "GB/T 5972-2023  《起重机 钢丝绳 保养、维护、检验和报废》");
+
+                            table.Cell().Row(9).Column(1).RowSpan(5).Border(1).AlignMiddle().Text("检测结果").Bold().AlignCenter();
+
+                            table.Cell().Row(9).RowSpan(2).Column(2).Element(CellNameStyle).Text("最大损伤\r\n程度");
+                            table.Cell().Row(9).Column(3).ColumnSpan(5).Element(CellNameStyle).Text("五处重要损伤");
+
+                            table.Cell().Row(10).Column(3).Element(CellValueStyle).Text("第一处");
+                            table.Cell().Row(10).Column(4).Element(CellValueStyle).Text("第二处");
+                            table.Cell().Row(10).Column(5).Element(CellValueStyle).Text("第三处");
+                            table.Cell().Row(10).Column(6).Element(CellValueStyle).Text("第四处");
+                            table.Cell().Row(10).Column(7).Element(CellValueStyle).Text("第五处");
+
+                            table.Cell().Row(11).Column(2).Element(CellNameStyle).Text("损伤位置");
+                            table.Cell().Row(12).Column(2).Element(CellNameStyle).Text("损伤当量");
+                            table.Cell().Row(13).Column(2).Element(CellNameStyle).Text("损伤级别");
+
+                            for (int i = 0; i < 5; i++)
+                            {
+                                string positionValue = i < damageList.Count ? damageList[i].DamagePoint.ToString("F3") + "m" : "-";
+                                string damageValue = i < damageList.Count ? damageList[i].DamageValue.ToString("F3") + "%" : "-";
+                                string damageLevel = i < damageList.Count ? damageList[i].DamageLevel.GetDescription() : "-";
+                                table.Cell().Row(11).Column((uint)(3 + i)).Element(CellValueStyle).Text(positionValue);
+                                table.Cell().Row(12).Column((uint)(3 + i)).Element(CellValueStyle).Text(damageValue);
+                                table.Cell().Row(13).Column((uint)(3 + i)).Element(CellValueStyle).Text(damageLevel);
+                            }
+                        });
+                });
+                document.Page(page =>
+                {
+                    page.Size(PageSizes.A4);
+                    page.MarginTop(2, Unit.Centimetre);
+                    page.MarginBottom(2, Unit.Centimetre);
+                    page.MarginLeft(1, Unit.Centimetre);
+                    page.MarginRight(1, Unit.Centimetre);
+                    page.Content().Column(column =>
+                    {
+                        for (int i = 0; i < images.Count; i++)
+                        {
+                            if (i == 0)
+                            {
+                                column.Item().PaddingLeft(20).Text($"整绳检测波形图").FontSize(12).Bold();
+                                column.Item().Image(images[i].image);
+                                column.Item().Height(35);
+                            }
+                            else
+                            {
+                                column.Item().PaddingLeft(20).Text($"第{ChineseNumbers[i]}处重要损伤").FontSize(12).Bold();
+                                column.Item().Image(images[i].image);
+                                column.Item().AlignCenter().Text($"损伤位置:{images[i].position?.ToString("F3")}m  " +
+                                    $"损伤量值:{images[i].damage?.ToString("F3")}%").FontSize(10).FontColor(Color.FromHex("#7a08fa"));
+                                column.Item().Height(34);
+                            }
+                        }
+                    });
+                });
+            }).GeneratePdf(filePath);
+            return true;
+        }
+        protected List<(double? position, double? damage, byte[] image)> GenerateImage(string dataFilePath,
+                                                                                            int sensorCount,
+                                                                                            double startPoint,
+                                                                                            double endPoint,
+                                                                                            List<DamageDto> damageList)
+        {
+            List<(double? position, double? damage, byte[] image)> imageDamages = new List<(double? position, double? damage, byte[] image)>();
+            if (dataFilePath.IsNullOrEmpty())
+            {
+                return null;
+            }
+            var damageData = DatFileHandler.ReadDatFile(dataFilePath);
+            if (damageData.Length > 0)
+            {
+                (double[] positions, ushort[] damages) = ParseSensorData(damageData, sensorCount, startPoint, endPoint);
+                if (positions != null && damages != null)
+                {
+
+                    ScottPlot.Plot plot = new ScottPlot.Plot();
+                    plot.Add.Scatter(positions, damages, new ScottPlot.Color("#70a1d7"));
+                    plot.Axes.SetLimits(startPoint * 0.95, endPoint * 1.05, 40, 80);
+                    imageDamages.Add((null, null, plot.GetImageBytes(900, 300)));
+
+                    foreach (var damage in damageList)
+                    {
+                        int damageIndex = FindClosestIndexBinarySearch(positions, damage.DamagePoint);
+                        if (damageIndex >= 0)
+                        {
+                            // 计算左右各10个点的索引范围
+                            int startIndex = Math.Max(0, damageIndex - _damageNearPointCount);
+                            int endIndex = Math.Min(positions.Length - 1, damageIndex + _damageNearPointCount);
+
+                            // 设置X轴范围为左右各10个点
+                            double start = positions[startIndex];
+                            double end = positions[endIndex];
+                            plot.Axes.SetLimitsX(start, end);
+                        }
+                        else
+                        {
+                            // 如果找不到精确匹配,使用近似值
+                            double start = damage.DamagePoint * 0.995;
+                            double end = damage.DamagePoint * 1.005;
+                            plot.Axes.SetLimitsX(start, end);
+                        }
+
+                        imageDamages.Add((damage.DamagePoint, damage.DamageValue, plot.GetImageBytes(900, 300)));
+                    }
+                }
+
+            }
+            return imageDamages;
+
+        }
+        public (double[] Positions, ushort[] Damages) ParseSensorData(byte[] data,
+                                                                             int sensorCount,
+                                                                             double startPosition,
+                                                                             double endPosition)
+        {
+            // 检查数据长度是否能被传感器数量整除
+            if (data.Length % sensorCount != 0)
+            {
+                LogHelper.Error($"数据长度不匹配。数据长度 {data.Length} 字节不能被传感器数量 {sensorCount} 整除。");
+                return (null, null);
+            }
+
+            // 计算样本数量
+            int sampleCount = data.Length / sensorCount;
+            // 计算采样步长
+            double samplingStep = (endPosition - startPosition) / (sampleCount - 1);
+
+            // 初始化结果数组
+            double[] positions = new double[sampleCount];
+            ushort[] damages = new ushort[sampleCount];
+
+            // 解析数据并计算平均值
+            for (int sampleIndex = 0; sampleIndex < sampleCount; sampleIndex++)
+            {
+                positions[sampleIndex] = startPosition;
+                // 计算当前样本的所有传感器Damage总和
+                int sum = 0;
+                for (int sensorIndex = 0; sensorIndex < sensorCount; sensorIndex++)
+                {
+                    // 计算数据在字节数组中的位置
+                    int dataIndex = (sampleIndex * sensorCount) + sensorIndex;
+                    sum += data[dataIndex];
+                }
+                // 计算平均值
+                damages[sampleIndex] = (ushort)(sum / sensorCount);
+                startPosition += samplingStep;
+            }
+            return (positions, damages);
+        }
+        /// <summary>
+        /// 使用二分查找在有序数组中查找最接近目标值的索引
+        /// </summary>
+        /// <returns></returns>
+        private int FindClosestIndexBinarySearch(double[] array, double targetValue)
+        {
+            if (array == null || array.Length == 0)
+                return -1;
+
+            int left = 0;
+            int right = array.Length - 1;
+
+            while (left <= right)
+            {
+                int mid = left + (right - left) / 2;
+
+                if (array[mid] == targetValue)
+                    return mid;
+                else if (array[mid] < targetValue)
+                    left = mid + 1;
+                else
+                    right = mid - 1;
+            }
+
+            // 此时left和right已经交叉,需要比较最接近的两个值
+            if (right < 0) return 0;
+            if (left >= array.Length) return array.Length - 1;
+
+            return Math.Abs(array[left] - targetValue) < Math.Abs(array[right] - targetValue) ? left : right;
+        }
+        static IContainer CellNameStyle(IContainer container)
+        {
+            return container
+                .DefaultTextStyle(x => x.FontColor(Colors.Black).Bold())
+                .Border(1)
+                .Padding(10)
+                .AlignCenter()
+                .AlignMiddle();
+        }
+        static IContainer CellValueStyle(IContainer container)
+        {
+            return container
+                .DefaultTextStyle(x => x.FontColor(Colors.Black).Medium())
+                .Border(1)
+                .Padding(10)
+                .AlignCenter()
+                .AlignMiddle();
+        }
+    }
+}

+ 171 - 0
Extensions/SimpleLogger.cs

@@ -0,0 +1,171 @@
+using SWRIS.Core;
+using System;
+using System.Globalization;
+using System.IO;
+using System.Text;
+using System.Threading;
+
+namespace SWRIS.Extensions
+{
+    public class SimpleLogger
+    {
+        private string GetFilePath()
+        {
+            if (DateTime.Now.Date > LastDate.Date)
+            {
+                FileIndex = 1;
+            }
+            LogPath = (LogPath.EndsWith("\\") ? LogPath.Substring(0, LogPath.Length - 1) : LogPath);
+            string text = string.Format("{0}\\{1}\\{2}{3}.log", new object[]
+            {
+                LogPath,
+                DateTime.Now.ToString("yyyy-MM-dd"),
+                FileName,
+                FileIndex
+            });
+            if (!File.Exists(text))
+            {
+                FileHelper.CreateFile(text, string.Empty);
+            }
+            string result;
+            if (GetFileSize(text) > MaxSize)
+            {
+                FileIndex++;
+                result = GetFilePath();
+            }
+            else
+            {
+                LastDate = DateTime.Now;
+                result = text;
+            }
+            return result;
+        }
+
+        private long GetFileSize(string filePath)
+        {
+            return new FileInfo(filePath).Length / 1024L;
+        }
+
+        private void Write(string text)
+        {
+            if (IsAsyn)
+            {
+                AsynWriteLog(text);
+            }
+            else
+            {
+                WriteLog(text);
+            }
+        }
+
+        private void WriteLog(string text)
+        {
+            try
+            {
+                string filePath = GetFilePath();
+                using (StreamWriter streamWriter = new StreamWriter(filePath, true))
+                {
+                    streamWriter.WriteLine(text);
+                    streamWriter.Flush();
+                }
+            }
+            catch
+            {
+            }
+        }
+
+        private void AsynWriteLog(string value)
+        {
+            ThreadPool.QueueUserWorkItem(delegate (object obj)
+            {
+                try
+                {
+                    WriteLog(value);
+                }
+                catch (Exception ex)
+                {
+                    LogHelper.Error(ex.Message, ex);
+                }
+            });
+        }
+        private string LogFormat(string content, string logLevel, Exception exception)
+        {
+            StringBuilder stringBuilder = new StringBuilder();
+            stringBuilder.Append(Environment.NewLine);
+            stringBuilder.AppendLine("//==========================================================================================================");
+            stringBuilder.AppendFormat("系统时间:{0}", DateTime.Now.ToString(CultureInfo.InvariantCulture));
+            stringBuilder.Append("  ");
+            stringBuilder.AppendFormat("当前线程ID:{0}", Thread.CurrentThread.ManagedThreadId);
+            stringBuilder.Append(Environment.NewLine);
+            stringBuilder.AppendFormat("日志等级:{0}", logLevel);
+            stringBuilder.Append(Environment.NewLine);
+            stringBuilder.AppendFormat("日志内容:{0}", content);
+            if (exception != null)
+            {
+                stringBuilder.Append(Environment.NewLine);
+                stringBuilder.AppendLine("异常信息:");
+                stringBuilder.Append(exception.ToLogString());
+            }
+            return stringBuilder.ToString();
+        }
+
+        [Obsolete("记录消息,无任何格式处理,记录系统日志请勿使用该功能")]
+        public void LogMessage(string content)
+        {
+            try
+            {
+                string filePath = GetFilePath();
+                using (StreamWriter streamWriter = new StreamWriter(filePath, true))
+                {
+                    streamWriter.WriteLine(content);
+                    streamWriter.Flush();
+                }
+            }
+            catch
+            {
+            }
+        }
+        [Obsolete("删除传入日期前一天的日志信息")]
+        public void DeleteLog(DateTime dateTime)
+        {
+            try
+            {
+                LogPath = (LogPath.EndsWith("\\") ? LogPath.Substring(0, LogPath.Length - 1) : LogPath);
+                string text = string.Format("{0}\\{1}", new object[]
+                {
+                LogPath,
+                dateTime.AddDays(-1).ToString("yyyy-MM-dd")
+
+                });
+                if (Directory.Exists(text))
+                {
+                    Directory.Delete(text, true);
+                }
+
+            }
+            catch
+            {
+            }
+        }
+        public void Log(string content, string level, Exception ex)
+        {
+#if Debug
+            Console.WriteLine(content);
+#endif
+            string text = LogFormat(content, level, ex);
+            Write(text);
+        }
+
+        public int MaxSize = 2048;
+
+        public string LogPath = FileHelper.GetPhysicalPath("Log");
+
+        public string FileName = "Log";
+
+        public bool IsAsyn = true;
+
+        private int FileIndex = 1;
+
+        private DateTime LastDate = DateTime.Now;
+    }
+}

+ 199 - 0
Extensions/SingleInstanceManager.cs

@@ -0,0 +1,199 @@
+using System;
+using System.Diagnostics;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Security.Permissions;
+using System.Threading;
+using System.Windows;
+
+namespace SWRIS.Extensions
+{
+    /// <summary>
+    /// 单实例应用程序管理器
+    /// </summary>
+    public class SingleInstanceManager
+    {
+        private readonly string _mutexName;
+        private readonly string _applicationName;
+        private Mutex _mutex;
+
+        [DllImport("user32.dll")]
+        private static extern bool SetForegroundWindow(IntPtr hWnd);
+
+        [DllImport("user32.dll")]
+        private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
+
+        [DllImport("user32.dll")]
+        private static extern bool IsIconic(IntPtr hWnd);
+
+        [DllImport("user32.dll")]
+        private static extern bool IsWindowVisible(IntPtr hWnd);
+
+        // Window show commands
+        private const int SW_HIDE = 0;
+        private const int SW_SHOWNORMAL = 1;
+        private const int SW_SHOWMINIMIZED = 2;
+        private const int SW_SHOWMAXIMIZED = 3;
+        private const int SW_SHOWNOACTIVATE = 4;
+        private const int SW_RESTORE = 9;
+
+        /// <summary>
+        /// 当需要激活已有实例时触发
+        /// </summary>
+        public event Action<Process> InstanceActivated;
+
+        /// <summary>
+        /// 初始化单实例管理器
+        /// </summary>
+        /// <param name="applicationName">应用程序唯一名称</param>
+        public SingleInstanceManager(string applicationName)
+        {
+            _applicationName = applicationName ?? throw new ArgumentNullException(nameof(applicationName));
+            _mutexName = $@"Global\{applicationName}";
+        }
+
+        /// <summary>
+        /// 检查是否已是运行实例
+        /// </summary>
+        /// <returns>true表示已有实例运行,false表示这是第一个实例</returns>
+        public bool IsAlreadyRunning()
+        {
+            try
+            {
+                _mutex = new Mutex(true, _mutexName, out bool createdNew);
+                return !createdNew;
+            }
+            catch (Exception ex)
+            {
+                // 记录日志或处理异常
+                Debug.WriteLine($"Mutex创建失败: {ex.Message}");
+                return false;
+            }
+        }
+
+        /// <summary>
+        /// 切换到已有运行实例
+        /// </summary>
+        /// <returns>true表示成功切换到已有实例,false表示切换失败</returns>
+        public bool SwitchToExistingInstance()
+        {
+            try
+            {
+                Process currentProcess = Process.GetCurrentProcess();
+                Process existingProcess = FindExistingProcess(currentProcess);
+
+                if (existingProcess != null && existingProcess.MainWindowHandle != IntPtr.Zero)
+                {
+                    ActivateProcessWindow(existingProcess);
+                    InstanceActivated?.Invoke(existingProcess);
+                    return true;
+                }
+
+                return false;
+            }
+            catch (Exception ex)
+            {
+                Debug.WriteLine($"切换到已有实例失败: {ex.Message}");
+                return false;
+            }
+        }
+        /// <summary>
+        /// 切换到已有运行实例
+        /// </summary>
+        /// <returns>true表示成功切换到已有实例,false表示切换失败</returns>
+        public bool SwitchToExistingInstance(string processName)
+        {
+            try
+            {
+                Process existingProcess = Process.GetProcessesByName(processName).FirstOrDefault();
+                if (existingProcess != null && existingProcess.MainWindowHandle != IntPtr.Zero)
+                {
+                    ActivateProcessWindow(existingProcess);
+                    InstanceActivated?.Invoke(existingProcess);
+                    return true;
+                }
+
+                return false;
+            }
+            catch (Exception ex)
+            {
+                Debug.WriteLine($"切换到已有实例失败: {ex.Message}");
+                return false;
+            }
+        }
+        /// <summary>
+        /// 查找已有的应用程序进程
+        /// </summary>
+        private Process FindExistingProcess(Process currentProcess)
+        {
+            Process[] processes = Process.GetProcessesByName(currentProcess.ProcessName);
+
+            foreach (Process process in processes)
+            {
+                if (process.Id != currentProcess.Id &&
+                    !process.HasExited &&
+                    process.MainWindowHandle != IntPtr.Zero)
+                {
+                    return process;
+                }
+            }
+
+            return null;
+        }
+
+        /// <summary>
+        /// 激活进程窗口
+        /// </summary>
+        private void ActivateProcessWindow(Process process)
+        {
+            IntPtr mainWindowHandle = process.MainWindowHandle;
+
+            if (IsIconic(mainWindowHandle))
+            {
+                ShowWindow(mainWindowHandle, SW_RESTORE);
+            }
+
+            SetForegroundWindow(mainWindowHandle);
+
+            // 如果窗口不可见,尝试显示它
+            if (!IsWindowVisible(mainWindowHandle))
+            {
+                ShowWindow(mainWindowHandle, SW_SHOWNORMAL);
+            }
+        }
+
+        /// <summary>
+        /// 激活当前应用程序的主窗口
+        /// </summary>
+        public void ActivateCurrentApplication()
+        {
+            Application.Current.Dispatcher.Invoke(() =>
+            {
+                Window mainWindow = Application.Current.MainWindow;
+
+                if (mainWindow != null)
+                {
+                    if (mainWindow.WindowState == WindowState.Minimized)
+                    {
+                        mainWindow.WindowState = WindowState.Normal;
+                    }
+
+                    mainWindow.Show();
+                    mainWindow.Activate();
+                    mainWindow.Topmost = true;
+                    mainWindow.Topmost = false;
+                    mainWindow.Focus();
+                }
+            });
+        }
+ 
+        /// <summary>
+        /// 释放资源
+        /// </summary>
+        public void Dispose()
+        {
+            _mutex?.Dispose();
+            _mutex = null;
+        }
+    }
+}

+ 195 - 0
Extensions/SoftAuth.cs

@@ -0,0 +1,195 @@
+using Microsoft.Win32;
+using System;
+using System.IO;
+using System.Management;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace SWRIS.Core
+{
+    /// <summary>
+    /// 软件授权类
+    /// </summary>
+    public class SoftAuth
+    {
+        /// <summary>
+        /// 获取本计算机唯一的机器码(长度取前12个字符)
+        /// </summary>
+        /// <returns>字符串形式的机器码</returns>
+        public static string GetMachineCode()
+        {
+            string unique = "";
+            //bios名称
+            unique += HWID.BIOS + "|";
+            //cpu信息
+            unique += HWID.CPU + "|";
+            //主板信息
+            unique += HWID.BaseBoard + "|";
+
+            //获取SMBIOS的Id
+            ManagementClass cimobject3 = new ManagementClass("Win32_ComputerSystemProduct");
+            ManagementObjectCollection moc3 = cimobject3.GetInstances();
+            foreach (ManagementObject mo in moc3)
+            {
+                unique += (string)mo.Properties["UUID"].Value;
+                break;
+            }
+            SHA1CryptoServiceProvider SHA1 = new SHA1CryptoServiceProvider();
+            var md5 = ByteToHexString(SHA1.ComputeHash(Encoding.Unicode.GetBytes(unique)), (char)0, 0);
+            return md5.Substring(0, 12);
+        }
+        /// <summary>
+        /// 获取本机激活码
+        /// </summary>
+        /// <returns></returns>
+        public static string GetFinalCode()
+        {
+            return MD5Encrypt(GetMachineCode()).Substring(0, 16);
+        }
+        /// <summary>
+        /// 判断是否已激活
+        /// </summary>
+        /// <returns></returns>
+        public static bool IsAuthorizeSuccess()
+        {
+            if (GetFinalCode() == ReadRegistry())
+            {
+                return true;
+            }
+            else
+            {
+                return false;
+            }
+        }
+        /// <summary>
+        /// 验证激活码是否正确
+        /// </summary>
+        /// <param name="finalCode">用户输入的注册码</param>
+        /// <returns></returns>
+        public static bool CheckFinalCode(string finalCode)
+        {
+            if (GetFinalCode() == finalCode.Trim().ToUpper())
+            {
+                return true;
+            }
+            else
+            {
+                return false;
+            }
+        }
+
+        /// <summary>
+        /// 字节数据转化成16进制表示的字符串
+        /// </summary>
+        /// <param name="InBytes">字节数组</param>
+        /// <param name="segment">分割符</param>
+        /// <param name="newLineCount">每隔指定数量的时候进行换行</param>
+        /// <returns>返回的字符串</returns>
+        /// <example>
+        /// </example>
+        private static string ByteToHexString(byte[] InBytes, char segment, int newLineCount)
+        {
+            if (InBytes == null) return string.Empty;
+            StringBuilder sb = new StringBuilder();
+            long tick = 0;
+            foreach (byte InByte in InBytes)
+            {
+                if (segment == 0) sb.Append(string.Format("{0:X2}", InByte));
+                else sb.Append(string.Format("{0:X2}{1}", InByte, segment));
+
+                tick++;
+                if (newLineCount > 0 && tick >= newLineCount)
+                {
+                    sb.Append(Environment.NewLine);
+                    tick = 0;
+                }
+            }
+
+            if (segment != 0 && sb.Length > 1 && sb[sb.Length - 1] == segment)
+            {
+                sb.Remove(sb.Length - 1, 1);
+            }
+            return sb.ToString();
+        }
+        /// <summary>
+        /// 加密数据,采用DES对称加密的方式
+        /// </summary>
+        /// <param name="pToEncrypt">待加密的数据</param>
+        /// <param name="password">密钥,长度为8,英文或数字</param>
+        /// <returns>加密后的数据</returns>
+        public static string MD5Encrypt(string pToEncrypt, string password = "!234Qwer")
+        {
+            DESCryptoServiceProvider des = new DESCryptoServiceProvider();
+            byte[] inputByteArray = Encoding.Default.GetBytes(pToEncrypt);
+            des.Key = Encoding.ASCII.GetBytes(password);
+            des.IV = Encoding.ASCII.GetBytes(password);
+            MemoryStream ms = new MemoryStream();
+            CryptoStream cs = new CryptoStream(ms, des.CreateEncryptor(), CryptoStreamMode.Write);
+            cs.Write(inputByteArray, 0, inputByteArray.Length);
+            cs.FlushFinalBlock();
+            StringBuilder ret = new StringBuilder();
+            foreach (byte b in ms.ToArray())
+            {
+                ret.AppendFormat("{0:X2}", b);
+            }
+            ret.ToString();
+            return ret.ToString();
+        }
+        /// <summary>
+        /// 激活码写入注册表
+        /// </summary>
+        /// <param name="code"></param>
+        /// <returns></returns>
+        public static bool WriteRegistry(string code)
+        {
+            try
+            {
+                RegistryKey regkeySetKey = Registry.CurrentUser.CreateSubKey(@"SOFTWARE\HenDai\SoftAuthorize\Common", true);
+                regkeySetKey.SetValue("Code", code);
+                regkeySetKey.SetValue("Time", DateTime.Now.ToString());
+                return true;
+            }
+            catch { return false; }
+        }
+        /// <summary>
+        /// 读取注册表内激活码
+        /// </summary>
+        /// <returns></returns>
+        public static string ReadRegistry()
+        {
+            try
+            {
+                RegistryKey regkey = Registry.CurrentUser.OpenSubKey(@"SOFTWARE\HenDai\SoftAuthorize\Common");
+                return regkey?.GetValue("Code", string.Empty).ToString();
+            }
+            catch
+            {
+                return string.Empty;
+            }
+        }
+        private class HWID
+        {
+            public static string BIOS { get { return GetWMIIdent("Win32_BIOS", "Manufacturer", "SerialNumber", "SMBIOSBIOSVersion"); } }
+            public static string CPU { get { return GetWMIIdent("Win32_Processor", "ProcessorId"); } }
+            public static string HDD { get { return GetWMIIdent("Win32_DiskDrive", "Model"); } }
+            public static string BaseBoard { get { return GetWMIIdent("Win32_BaseBoard", "SerialNumber"); } }
+        }
+        private static string GetWMIIdent(string Class, string Property)
+        {
+            var ident = "";
+            var objCol = new ManagementClass(Class).GetInstances();
+            foreach (var obj in objCol)
+            {
+                if ((ident = obj.GetPropertyValue(Property) as string) != "")
+                    break;
+            }
+            return ident;
+        }
+        private static string GetWMIIdent(string Class, params string[] Propertys)
+        {
+            var ident = "";
+            Array.ForEach(Propertys, prop => ident += GetWMIIdent(Class, prop) + " ");
+            return ident;
+        }
+    }
+}

+ 1628 - 0
Extensions/SoftBasic.cs

@@ -0,0 +1,1628 @@
+using Newtonsoft.Json.Linq;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Runtime.Serialization.Formatters.Binary;
+using System.Security.Cryptography;
+using System.Text;
+using System.Text.RegularExpressions;
+
+namespace SWRIS.Extensions
+{
+    /// <summary>
+    /// 一个软件基础类,提供常用的一些静态方法,比如字符串转换,字节转换的方法<br />
+    /// A software-based class that provides some common static methods,Such as string conversion, byte conversion method
+    /// </summary>
+    public class SoftBasic
+    {
+        #region MD5 Calculate
+
+        /// <summary>
+        /// 获取文件的md5码<br />
+        /// Get the MD5 code of the file
+        /// </summary>
+        /// <param name="filePath">文件的路径,既可以是完整的路径,也可以是相对的路径 -> The path to the file</param>
+        /// <returns>Md5字符串</returns>
+        /// <example>
+        /// 下面举例实现获取一个文件的md5码
+        /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\BasicFramework\SoftBasicExample.cs" region="CalculateFileMD5Example" title="CalculateFileMD5示例" />
+        /// </example>
+        public static string CalculateFileMD5(string filePath)
+        {
+            string str_md5 = string.Empty;
+            using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
+            {
+                str_md5 = CalculateStreamMD5(fs);
+            }
+            return str_md5;
+        }
+
+        /// <summary>
+        /// 获取数据流的md5码<br />
+        /// Get the MD5 code for the data stream
+        /// </summary>
+        /// <param name="stream">数据流,可以是内存流,也可以是文件流</param>
+        /// <returns>Md5字符串</returns>
+        /// <example>
+        /// 下面举例实现获取一个流的md5码
+        /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\BasicFramework\SoftBasicExample.cs" region="CalculateStreamMD5Example1" title="CalculateStreamMD5示例" />
+        /// </example>
+        public static string CalculateStreamMD5(Stream stream)
+        {
+            byte[] bytes_md5 = null;
+            using (MD5 md5 = new MD5CryptoServiceProvider())
+            {
+                bytes_md5 = md5.ComputeHash(stream);
+            }
+            return BitConverter.ToString(bytes_md5).Replace("-", "");
+        }
+
+        /// <summary>
+        /// 获取文本字符串信息的Md5码,编码为UTF8<br />
+        /// Get the Md5 code of the text string information, using the utf-8 encoding
+        /// </summary>
+        /// <param name="data">文本数据信息</param>
+        /// <returns>Md5字符串</returns>
+        public static string CalculateStreamMD5(string data) => CalculateStreamMD5(data, Encoding.UTF8);
+
+        /// <summary>
+        /// 获取文本字符串信息的Md5码,使用指定的编码<br />
+        /// Get the Md5 code of the text string information, using the specified encoding
+        /// </summary>
+        /// <param name="data">文本数据信息</param>
+        /// <param name="encode">编码信息</param>
+        /// <returns>Md5字符串</returns>
+        public static string CalculateStreamMD5(string data, Encoding encode)
+        {
+            string str_md5 = string.Empty;
+            using (MD5 md5 = new MD5CryptoServiceProvider())
+            {
+                byte[] bytes_md5 = md5.ComputeHash(encode.GetBytes(data));
+                str_md5 = BitConverter.ToString(bytes_md5).Replace("-", "");
+            }
+            return str_md5;
+        }
+#if NET35 || NET451
+		/// <summary>
+		/// 获取内存图片的md5码<br />
+		/// Get the MD5 code of the memory picture
+		/// </summary>
+		/// <param name="bitmap">内存图片</param>
+		/// <returns>Md5字符串</returns>
+		/// <example>
+		/// 下面举例实现获取一个图像的md5码
+		/// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\BasicFramework\SoftBasicExample.cs" region="CalculateStreamMD5Example2" title="CalculateStreamMD5示例" />
+		/// </example>
+		public static string CalculateStreamMD5( Bitmap bitmap )
+		{
+			MemoryStream ms = new MemoryStream( );
+			bitmap.Save( ms, bitmap.RawFormat );
+			byte[] bytes_md5 = null;
+			using (MD5 md5 = new MD5CryptoServiceProvider( ))
+				bytes_md5 = md5.ComputeHash( ms );
+			ms.Dispose( );
+			return ByteToHexString( bytes_md5 );
+		}
+#endif
+        #endregion
+
+        #region DataSize Format
+
+        /// <summary>
+        /// 从一个字节大小返回带单位的描述,主要是用于显示操作<br />
+        /// Returns a description with units from a byte size, mainly for display operations
+        /// </summary>
+        /// <param name="size">实际的大小值</param>
+        /// <returns>最终的字符串值</returns>
+        /// <example>
+        /// 比如说我们获取了文件的长度,这个长度可以来自于本地,也可以来自于数据库查询
+        /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\BasicFramework\SoftBasicExample.cs" region="GetSizeDescriptionExample" title="GetSizeDescription示例" />
+        /// </example>
+        public static string GetSizeDescription(long size)
+        {
+            if (size < 1000)
+            {
+                return size + " B";
+            }
+            else if (size < 1000 * 1000)
+            {
+                float data = (float)size / 1024;
+                return data.ToString("F2") + " Kb";
+            }
+            else if (size < 1000 * 1000 * 1000)
+            {
+                float data = (float)size / 1024 / 1024;
+                return data.ToString("F2") + " Mb";
+            }
+            else
+            {
+                float data = (float)size / 1024 / 1024 / 1024;
+                return data.ToString("F2") + " Gb";
+            }
+        }
+
+        #endregion
+
+
+        #region Array Format
+
+        /// <summary>
+        /// 将数组格式化为显示的字符串的信息,支持所有的类型对象<br />
+        /// Formats the array into the displayed string information, supporting all types of objects
+        /// </summary>
+        /// <typeparam name="T">数组的类型</typeparam>
+        /// <param name="array">数组信息</param>
+        /// <returns>最终显示的信息</returns>
+        public static string ArrayFormat<T>(T[] array) => ArrayFormat(array, string.Empty);
+
+        /// <summary>
+        /// 将数组格式化为显示的字符串的信息,支持所有的类型对象<br />
+        /// Formats the array into the displayed string information, supporting all types of objects
+        /// </summary>
+        /// <typeparam name="T">数组的类型</typeparam>
+        /// <param name="array">数组信息</param>
+        /// <param name="format">格式化的信息</param>
+        /// <returns>最终显示的信息</returns>
+        public static string ArrayFormat<T>(T[] array, string format)
+        {
+            if (array == null) return "NULL";
+            StringBuilder sb = new StringBuilder("[");
+            for (int i = 0; i < array.Length; i++)
+            {
+                sb.Append(string.IsNullOrEmpty(format) ? array[i].ToString() : string.Format(format, array[i]));
+                if (i != array.Length - 1) sb.Append(",");
+            }
+            sb.Append("]");
+            return sb.ToString();
+        }
+
+        /// <summary>
+        /// 将数组格式化为显示的字符串的信息,支持所有的类型对象<br />
+        /// Formats the array into the displayed string information, supporting all types of objects
+        /// </summary>
+        /// <typeparam name="T">数组的类型</typeparam>
+        /// <param name="array">数组信息</param>
+        /// <returns>最终显示的信息</returns>
+        public static string ArrayFormat<T>(T array) => ArrayFormat(array, string.Empty);
+
+        /// <summary>
+        /// 将数组格式化为显示的字符串的信息,支持所有的类型对象<br />
+        /// Formats the array into the displayed string information, supporting all types of objects
+        /// </summary>
+        /// <typeparam name="T">数组的类型</typeparam>
+        /// <param name="array">数组信息</param>
+        /// <param name="format">格式化的信息</param>
+        /// <returns>最终显示的信息</returns>
+        public static string ArrayFormat<T>(T array, string format)
+        {
+            StringBuilder sb = new StringBuilder("[");
+            if (array is Array array1)
+            {
+                foreach (var item in array1)
+                {
+                    sb.Append(string.IsNullOrEmpty(format) ? item.ToString() : string.Format(format, item));
+                    sb.Append(",");
+                }
+                if (array1.Length > 0 && sb[sb.Length - 1] == ',') sb.Remove(sb.Length - 1, 1);
+            }
+            else
+            {
+                sb.Append(string.IsNullOrEmpty(format) ? array.ToString() : string.Format(format, array));
+            }
+            sb.Append("]");
+            return sb.ToString();
+        }
+
+        #endregion
+
+        #region Array Expand
+
+        /// <summary>
+        /// 一个通用的数组新增个数方法,会自动判断越界情况,越界的情况下,会自动的截断或是填充<br />
+        /// A common array of new methods, will automatically determine the cross-border situation, in the case of cross-border, will be automatically truncated or filled
+        /// </summary>
+        /// <typeparam name="T">数据类型</typeparam>
+        /// <param name="array">原数据</param>
+        /// <param name="data">等待新增的数据</param>
+        /// <param name="max">原数据的最大值</param>
+        /// <example>
+        /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\BasicFramework\SoftBasicExample.cs" region="AddArrayDataExample" title="AddArrayData示例" />
+        /// </example>
+        public static void AddArrayData<T>(ref T[] array, T[] data, int max)
+        {
+            if (data == null) return;           // 数据为空
+            if (data.Length == 0) return;       // 数据长度为空
+
+            if (array.Length == max)
+            {
+                Array.Copy(array, data.Length, array, 0, array.Length - data.Length);
+                Array.Copy(data, 0, array, array.Length - data.Length, data.Length);
+            }
+            else
+            {
+                if ((array.Length + data.Length) > max)
+                {
+                    T[] tmp = new T[max];
+                    for (int i = 0; i < (max - data.Length); i++)
+                    {
+                        tmp[i] = array[i + (array.Length - max + data.Length)];
+                    }
+                    for (int i = 0; i < data.Length; i++)
+                    {
+                        tmp[tmp.Length - data.Length + i] = data[i];
+                    }
+                    // 更新数据
+                    array = tmp;
+                }
+                else
+                {
+                    T[] tmp = new T[array.Length + data.Length];
+                    for (int i = 0; i < array.Length; i++)
+                    {
+                        tmp[i] = array[i];
+                    }
+                    for (int i = 0; i < data.Length; i++)
+                    {
+                        tmp[tmp.Length - data.Length + i] = data[i];
+                    }
+
+                    array = tmp;
+                }
+            }
+        }
+
+        /// <summary>
+        /// 将一个数组进行扩充到指定长度,或是缩短到指定长度<br />
+        /// Extend an array to a specified length, or shorten to a specified length or fill
+        /// </summary>
+        /// <typeparam name="T">数组的类型</typeparam>
+        /// <param name="data">原先数据的数据</param>
+        /// <param name="length">新数组的长度</param>
+        /// <returns>新数组长度信息</returns>
+        /// <example>
+        /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\BasicFramework\SoftBasicExample.cs" region="ArrayExpandToLengthExample" title="ArrayExpandToLength示例" />
+        /// </example>
+        public static T[] ArrayExpandToLength<T>(T[] data, int length)
+        {
+            if (data == null) return new T[length];
+
+            if (data.Length == length) return data;
+
+            T[] buffer = new T[length];
+
+            Array.Copy(data, buffer, Math.Min(data.Length, buffer.Length));
+
+            return buffer;
+        }
+
+
+        /// <summary>
+        /// 将一个数组进行扩充到偶数长度<br />
+        /// Extend an array to even lengths
+        /// </summary>
+        /// <typeparam name="T">数组的类型</typeparam>
+        /// <param name="data">原先数据的数据</param>
+        /// <returns>新数组长度信息</returns>
+        /// <example>
+        /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\BasicFramework\SoftBasicExample.cs" region="ArrayExpandToLengthEvenExample" title="ArrayExpandToLengthEven示例" />
+        /// </example>
+        public static T[] ArrayExpandToLengthEven<T>(T[] data)
+        {
+            if (data == null) return new T[0];
+
+            if (data.Length % 2 == 1)
+                return ArrayExpandToLength(data, data.Length + 1);
+            else
+                return data;
+        }
+
+        /// <summary>
+        /// 将指定的数据按照指定长度进行分割,例如int[10],指定长度4,就分割成int[4],int[4],int[2],然后拼接list<br />
+        /// Divide the specified data according to the specified length, such as int [10], and specify the length of 4 to divide into int [4], int [4], int [2], and then concatenate the list
+        /// </summary>
+        /// <typeparam name="T">数组的类型</typeparam>
+        /// <param name="array">等待分割的数组</param>
+        /// <param name="length">指定的长度信息</param>
+        /// <returns>分割后结果内容</returns>
+        /// <example>
+        /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\BasicFramework\SoftBasicExample.cs" region="ArraySplitByLengthExample" title="ArraySplitByLength示例" />
+        /// </example>
+        public static List<T[]> ArraySplitByLength<T>(T[] array, int length)
+        {
+            if (array == null) return new List<T[]>();
+
+            List<T[]> result = new List<T[]>();
+            int index = 0;
+            while (index < array.Length)
+            {
+                if (index + length < array.Length)
+                {
+                    T[] tmp = new T[length];
+                    Array.Copy(array, index, tmp, 0, length);
+                    index += length;
+                    result.Add(tmp);
+                }
+                else
+                {
+                    T[] tmp = new T[array.Length - index];
+                    Array.Copy(array, index, tmp, 0, tmp.Length);
+                    index += length;
+                    result.Add(tmp);
+                }
+            }
+            return result;
+        }
+
+        /// <summary>
+        /// 将整数进行有效的拆分成数组,指定每个元素的最大值<br />
+        /// Effectively split integers into arrays, specifying the maximum value for each element
+        /// </summary>
+        /// <param name="integer">整数信息</param>
+        /// <param name="everyLength">单个的数组长度</param>
+        /// <returns>拆分后的数组长度</returns>
+        /// <example>
+        /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\BasicFramework\SoftBasicExample.cs" region="SplitIntegerToArrayExample" title="SplitIntegerToArray示例" />
+        /// </example>
+        public static int[] SplitIntegerToArray(int integer, int everyLength)
+        {
+            int[] result = new int[(integer / everyLength) + ((integer % everyLength) == 0 ? 0 : 1)];
+            for (int i = 0; i < result.Length; i++)
+            {
+                if (i == result.Length - 1)
+                {
+                    result[i] = (integer % everyLength) == 0 ? everyLength : (integer % everyLength);
+                }
+                else
+                {
+                    result[i] = everyLength;
+                }
+            }
+            return result;
+        }
+
+        #endregion
+
+        #region Byte Array compare
+
+        /// <summary>
+        /// 判断两个字节的指定部分是否相同<br />
+        /// Determines whether the specified portion of a two-byte is the same
+        /// </summary>
+        /// <param name="b1">第一个字节</param>
+        /// <param name="start1">第一个字节的起始位置</param>
+        /// <param name="b2">第二个字节</param>
+        /// <param name="start2">第二个字节的起始位置</param>
+        /// <param name="length">校验的长度</param>
+        /// <returns>返回是否相等</returns>
+        /// <exception cref="IndexOutOfRangeException"></exception>
+        /// <example>
+        /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\BasicFramework\SoftBasicExample.cs" region="IsTwoBytesEquelExample1" title="IsTwoBytesEquel示例" />
+        /// </example>
+        public static bool IsTwoBytesEquel(byte[] b1, int start1, byte[] b2, int start2, int length)
+        {
+            if (b1 == null || b2 == null) return false;
+            for (int i = 0; i < length; i++)
+            {
+                if (b1[i + start1] != b2[i + start2])
+                {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        /// <summary>
+        /// 判断两个字节的指定部分是否相同<br />
+        /// Determines whether the specified portion of a two-byte is the same
+        /// </summary>
+        /// <param name="b1">第一个字节</param>
+        /// <param name="b2">第二个字节</param>
+        /// <returns>返回是否相等</returns>
+        /// <example>
+        /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\BasicFramework\SoftBasicExample.cs" region="IsTwoBytesEquelExample2" title="IsTwoBytesEquel示例" />
+        /// </example>
+        public static bool IsTwoBytesEquel(byte[] b1, byte[] b2)
+        {
+            if (b1 == null || b2 == null) return false;
+            if (b1.Length != b2.Length) return false;
+            return IsTwoBytesEquel(b1, 0, b2, 0, b1.Length);
+        }
+
+        /// <summary>
+        /// 判断两个数据的令牌是否相等<br />
+        /// Determines whether the tokens of two data are equal
+        /// </summary>
+        /// <param name="head">字节数据</param>
+        /// <param name="token">GUID数据</param>
+        /// <returns>返回是否相等</returns>
+        /// <example>
+        /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\BasicFramework\SoftBasicExample.cs" region="IsTwoTokenEquelExample" title="IsByteTokenEquel示例" />
+        /// </example>
+        public static bool IsByteTokenEquel(byte[] head, Guid token) => IsTwoBytesEquel(head, 12, token.ToByteArray(), 0, 16);
+
+        /// <summary>
+        /// 判断两个数据的令牌是否相等<br />
+        /// Determines whether the tokens of two data are equal
+        /// </summary>
+        /// <param name="token1">第一个令牌</param>
+        /// <param name="token2">第二个令牌</param>
+        /// <returns>返回是否相等</returns>
+        public static bool IsTwoTokenEquel(Guid token1, Guid token2) => IsTwoBytesEquel(token1.ToByteArray(), 0, token2.ToByteArray(), 0, 16);
+
+        #endregion
+
+        #region Enum About
+
+        /// <summary>
+        /// 获取一个枚举类型的所有枚举值,可直接应用于组合框数据<br />
+        /// Gets all the enumeration values of an enumeration type that can be applied directly to the combo box data
+        /// </summary>
+        /// <typeparam name="TEnum">枚举的类型值</typeparam>
+        /// <returns>枚举值数组</returns>
+        /// <example>
+        /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\BasicFramework\SoftBasicExample.cs" region="GetEnumValuesExample" title="GetEnumValues示例" />
+        /// </example>
+        public static TEnum[] GetEnumValues<TEnum>() where TEnum : struct => (TEnum[])Enum.GetValues(typeof(TEnum));
+
+        /// <summary>
+        /// 从字符串的枚举值数据转换成真实的枚举值数据<br />
+        /// Convert enumeration value data from strings to real enumeration value data
+        /// </summary>
+        /// <typeparam name="TEnum">枚举的类型值</typeparam>
+        /// <param name="value">枚举的字符串的数据值</param>
+        /// <returns>真实的枚举值</returns>
+        /// <example>
+        /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\BasicFramework\SoftBasicExample.cs" region="GetEnumFromStringExample" title="GetEnumFromString示例" />
+        /// </example>
+        public static TEnum GetEnumFromString<TEnum>(string value) where TEnum : struct => (TEnum)Enum.Parse(typeof(TEnum), value);
+
+        #endregion
+
+        #region JSON XML Data Get
+
+        /// <summary>
+        /// 一个泛型方法,提供json对象的数据读取<br />
+        /// A generic method that provides data read for a JSON object
+        /// </summary>
+        /// <typeparam name="T">读取的泛型</typeparam>
+        /// <param name="json">json对象</param>
+        /// <param name="name">值名称</param>
+        /// <param name="defaultValue">默认值</param>
+        /// <returns>值对象</returns>
+        /// <example>
+        /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\BasicFramework\SoftBasicExample.cs" region="GetValueFromJsonObjectExample" title="GetValueFromJsonObject示例" />
+        /// </example>
+        public static T GetValueFromJsonObject<T>(JObject json, string name, T defaultValue)
+        {
+            if (json.Property(name) != null)
+            {
+                return json.Property(name).Value.Value<T>();
+            }
+            else
+            {
+                return defaultValue;
+            }
+        }
+
+        /// <summary>
+        /// 一个泛型方法,提供json对象的数据写入<br />
+        /// A generic method that provides data writing to a JSON object
+        /// </summary>
+        /// <typeparam name="T">写入的泛型</typeparam>
+        /// <param name="json">json对象</param>
+        /// <param name="property">值名称</param>
+        /// <param name="value">值数据</param>
+        /// <example>
+        /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\BasicFramework\SoftBasicExample.cs" region="JsonSetValueExample" title="JsonSetValue示例" />
+        /// </example>
+        public static void JsonSetValue<T>(JObject json, string property, T value)
+        {
+            if (json.Property(property) != null)
+            {
+                json.Property(property).Value = new JValue(value);
+            }
+            else
+            {
+                json.Add(property, new JValue(value));
+            }
+        }
+
+        #endregion
+
+
+        /// <summary>
+        /// 获取一个异常的完整错误信息<br />
+        /// Gets the complete error message for an exception
+        /// </summary>
+        /// <param name="ex">异常对象</param>
+        /// <returns>完整的字符串数据</returns>
+        /// <remarks>获取异常的完整信息</remarks>
+        /// <exception cref="NullReferenceException">ex不能为空</exception>
+        /// <example>
+        /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\BasicFramework\SoftBasicExample.cs" region="GetExceptionMessageExample1" title="GetExceptionMessage示例" />
+        /// </example>
+        public static string GetExceptionMessage(Exception ex)
+        {
+            return "错误信息:" + ex.Message + Environment.NewLine +
+                "错误堆栈:" + ex.StackTrace + Environment.NewLine +
+                "错误方法:" + ex.TargetSite;
+        }
+
+        /// <summary>
+        /// 获取一个异常的完整错误信息,和额外的字符串描述信息<br />
+        /// Gets the complete error message for an exception, and additional string description information
+        /// </summary>
+        /// <param name="extraMsg">额外的信息</param>
+        /// <param name="ex">异常对象</param>
+        /// <returns>完整的字符串数据</returns>
+        /// <exception cref="NullReferenceException"></exception>
+        /// <example>
+        /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\BasicFramework\SoftBasicExample.cs" region="GetExceptionMessageExample2" title="GetExceptionMessage示例" />
+        /// </example>
+        public static string GetExceptionMessage(string extraMsg, Exception ex)
+        {
+            if (string.IsNullOrEmpty(extraMsg))
+                return GetExceptionMessage(ex);
+            else
+                return extraMsg + Environment.NewLine + GetExceptionMessage(ex);
+        }
+
+
+
+        #region Hex string and Byte[] transform
+
+        /// <summary>
+        /// 字节数据转化成16进制表示的字符串<br />
+        /// Byte data into a string of 16 binary representations
+        /// </summary>
+        /// <param name="InBytes">字节数组</param>
+        /// <returns>返回的字符串</returns>
+        /// <example>
+        /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\BasicFramework\SoftBasicExample.cs" region="ByteToHexStringExample1" title="ByteToHexString示例" />
+        /// </example>
+        public static string ByteToHexString(byte[] InBytes) => ByteToHexString(InBytes, (char)0);
+
+        /// <summary>
+        /// 字节数据转化成16进制表示的字符串<br />
+        /// Byte data into a string of 16 binary representations
+        /// </summary>
+        /// <param name="InBytes">字节数组</param>
+        /// <param name="segment">分割符</param>
+        /// <returns>返回的字符串</returns>
+        /// <example>
+        /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\BasicFramework\SoftBasicExample.cs" region="ByteToHexStringExample2" title="ByteToHexString示例" />
+        /// </example>
+        public static string ByteToHexString(byte[] InBytes, char segment) => ByteToHexString(InBytes, segment, 0);
+
+        /// <summary>
+        /// 字节数据转化成16进制表示的字符串<br />
+        /// Byte data into a string of 16 binary representations
+        /// </summary>
+        /// <param name="InBytes">字节数组</param>
+        /// <param name="segment">分割符,如果设置为0,则没有分隔符信息</param>
+        /// <param name="newLineCount">每隔指定数量的时候进行换行,如果小于等于0,则不进行分行显示</param>
+        /// <param name="format">格式信息,默认为{0:X2}</param>
+        /// <returns>返回的字符串</returns>
+        /// <example>
+        /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\BasicFramework\SoftBasicExample.cs" region="ByteToHexStringExample2" title="ByteToHexString示例" />
+        /// </example>
+        public static string ByteToHexString(byte[] InBytes, char segment, int newLineCount, string format = "{0:X2}")
+        {
+            if (InBytes == null) return string.Empty;
+            StringBuilder sb = new StringBuilder();
+            long tick = 0;
+            foreach (byte InByte in InBytes)
+            {
+                if (segment == 0) sb.Append(string.Format(format, InByte));
+                else sb.Append(string.Format(format + "{1}", InByte, segment));
+
+                tick++;
+                if (newLineCount > 0 && tick >= newLineCount)
+                {
+                    sb.Append(Environment.NewLine);
+                    tick = 0;
+                }
+            }
+
+            if (segment != 0 && sb.Length > 1 && sb[sb.Length - 1] == segment)
+            {
+                sb.Remove(sb.Length - 1, 1);
+            }
+            return sb.ToString();
+        }
+
+        /// <summary>
+        /// 字符串数据转化成16进制表示的字符串<br />
+        /// String data into a string of 16 binary representations
+        /// </summary>
+        /// <param name="InString">输入的字符串数据</param>
+        /// <returns>返回的字符串</returns>
+        /// <exception cref="NullReferenceException"></exception>
+        public static string ByteToHexString(string InString) => ByteToHexString(Encoding.Unicode.GetBytes(InString));
+
+        private static int GetHexCharIndex(char ch)
+        {
+            switch (ch)
+            {
+                case '0': return 0;
+                case '1': return 1;
+                case '2': return 2;
+                case '3': return 3;
+                case '4': return 4;
+                case '5': return 5;
+                case '6': return 6;
+                case '7': return 7;
+                case '8': return 8;
+                case '9': return 9;
+                case 'A':
+                case 'a': return 10;
+                case 'B':
+                case 'b': return 11;
+                case 'C':
+                case 'c': return 12;
+                case 'D':
+                case 'd': return 13;
+                case 'E':
+                case 'e': return 14;
+                case 'F':
+                case 'f': return 15;
+                default: return -1;
+            }
+        }
+
+        /// <summary>
+        /// 将16进制的字符串转化成Byte数据,将检测每2个字符转化,也就是说,中间可以是任意字符<br />
+        /// Converts a 16-character string into byte data, which will detect every 2 characters converted, that is, the middle can be any character
+        /// </summary>
+        /// <param name="hex">十六进制的字符串,中间可以是任意的分隔符</param>
+        /// <returns>转换后的字节数组</returns>
+        /// <remarks>参数举例:AA 01 34 A8</remarks>
+        /// <example>
+        /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\BasicFramework\SoftBasicExample.cs" region="HexStringToBytesExample" title="HexStringToBytes示例" />
+        /// </example>
+        public static byte[] HexStringToBytes(string hex)
+        {
+            MemoryStream ms = new MemoryStream();
+
+            for (int i = 0; i < hex.Length; i++)
+            {
+                if ((i + 1) < hex.Length)
+                {
+                    if (GetHexCharIndex(hex[i]) >= 0 && GetHexCharIndex(hex[i + 1]) >= 0)
+                    {
+                        // 这是一个合格的字节数据
+                        ms.WriteByte((byte)(GetHexCharIndex(hex[i]) * 16 + GetHexCharIndex(hex[i + 1])));
+                        i++;
+                    }
+                }
+            }
+
+            byte[] result = ms.ToArray();
+            ms.Dispose();
+            return result;
+        }
+
+        #endregion
+
+        #region Byte Reverse By Word
+
+        /// <summary>
+        /// 将byte数组按照双字节进行反转,如果为单数的情况,则自动补齐<br />
+        /// Reverses the byte array by double byte, or if the singular is the case, automatically
+        /// </summary>
+        /// <remarks>
+        /// 例如传入的字节数据是 01 02 03 04, 那么反转后就是 02 01 04 03
+        /// </remarks>
+        /// <param name="inBytes">输入的字节信息</param>
+        /// <returns>反转后的数据</returns>
+        /// <example>
+        /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\BasicFramework\SoftBasicExample.cs" region="BytesReverseByWord" title="BytesReverseByWord示例" />
+        /// </example>
+        public static byte[] BytesReverseByWord(byte[] inBytes)
+        {
+            if (inBytes == null) return null;
+            if (inBytes.Length == 0) return new byte[0];
+
+            byte[] buffer = ArrayExpandToLengthEven(CopyArray(inBytes));
+
+            for (int i = 0; i < buffer.Length / 2; i++)
+            {
+                byte tmp = buffer[i * 2 + 0];
+                buffer[i * 2 + 0] = buffer[i * 2 + 1];
+                buffer[i * 2 + 1] = tmp;
+            }
+
+            return buffer;
+        }
+        /// <summary>
+        /// 拷贝当前的实例数组,是基于引用层的浅拷贝,如果类型为值类型,那就是深度拷贝,如果类型为引用类型,就是浅拷贝
+        /// </summary>
+        /// <typeparam name="T">类型对象</typeparam>
+        /// <param name="value">数组对象</param>
+        /// <returns>拷贝的结果内容</returns>
+        public static T[] CopyArray<T>(T[] value)
+        {
+            if (value == null) return null;
+            T[] buffer = new T[value.Length];
+            Array.Copy(value, buffer, value.Length);
+            return buffer;
+        }
+        #endregion
+
+        #region Byte[] and AsciiByte[] transform
+
+        /// <summary>
+        /// 将字节数组显示为ASCII格式的字符串,当遇到0x20以下及0x7E以上的不可见字符时,使用十六进制的数据显示<br />
+        /// Display the byte array as a string in ASCII format, when encountering invisible characters below 0x20 and above 0x7E, use hexadecimal data to display<br />
+        /// </summary>
+        /// <param name="content">字节数组信息</param>
+        /// <returns>ASCII格式的字符串信息</returns>
+        public static string GetAsciiStringRender(byte[] content)
+        {
+            if (content == null) return string.Empty;
+            StringBuilder sb = new StringBuilder();
+            for (int i = 0; i < content.Length; i++)
+            {
+                if (content[i] < 0x20 || content[i] > 0x7E)
+                {
+                    sb.Append($"\\{content[i]:X2}");
+                }
+                else
+                {
+                    sb.Append((char)content[i]);
+                }
+            }
+            return sb.ToString();
+        }
+
+        /// <summary>
+        /// 从显示的ASCII格式的字符串,转为原始字节数组,如果遇到 \00 这种表示原始字节的内容,则直接进行转换操作,遇到 \r 直接转换 0x0D, \n 直接转换 0x0A<br />
+        /// Convert from the displayed string in ASCII format to the original byte array. If you encounter \00, which represents the original byte content,
+        /// the conversion operation is performed directly. When encountering \r, it is directly converted to 0x0D, and \n is directly converted to 0x0A.
+        /// </summary>
+        /// <param name="render">等待转换的字符串</param>
+        /// <returns>原始字节数组</returns>
+        public static byte[] GetFromAsciiStringRender(string render)
+        {
+            if (string.IsNullOrEmpty(render)) return new byte[0];
+            MatchEvaluator matchEvaluator = new MatchEvaluator(m => string.Format("{0}", (char)Convert.ToByte(m.Value.Substring(1), 16)));
+            return Encoding.ASCII.GetBytes(Regex.Replace(render.Replace("\\r", "\r").Replace("\\n", "\n"), @"\\[0-9A-Fa-f]{2}", matchEvaluator));
+        }
+
+        /// <summary>
+        /// 将原始的byte数组转换成ascii格式的byte数组<br />
+        /// Converts the original byte array to an ASCII-formatted byte array
+        /// </summary>
+        /// <param name="inBytes">等待转换的byte数组</param>
+        /// <returns>转换后的数组</returns>
+        public static byte[] BytesToAsciiBytes(byte[] inBytes) => Encoding.ASCII.GetBytes(ByteToHexString(inBytes));
+
+        /// <summary>
+        /// 将ascii格式的byte数组转换成原始的byte数组<br />
+        /// Converts an ASCII-formatted byte array to the original byte array
+        /// </summary>
+        /// <param name="inBytes">等待转换的byte数组</param>
+        /// <returns>转换后的数组</returns>
+        public static byte[] AsciiBytesToBytes(byte[] inBytes) => HexStringToBytes(Encoding.ASCII.GetString(inBytes));
+
+        /// <summary>
+        /// 从字节构建一个ASCII格式的数据内容<br />
+        /// Build an ASCII-formatted data content from bytes
+        /// </summary>
+        /// <param name="value">数据</param>
+        /// <returns>ASCII格式的字节数组</returns>
+        public static byte[] BuildAsciiBytesFrom(byte value) => Encoding.ASCII.GetBytes(value.ToString("X2"));
+
+        /// <summary>
+        /// 从short构建一个ASCII格式的数据内容<br />
+        /// Constructing an ASCII-formatted data content from a short
+        /// </summary>
+        /// <param name="value">数据</param>
+        /// <returns>ASCII格式的字节数组</returns>
+        public static byte[] BuildAsciiBytesFrom(short value) => Encoding.ASCII.GetBytes(value.ToString("X4"));
+
+        /// <summary>
+        /// 从ushort构建一个ASCII格式的数据内容<br />
+        /// Constructing an ASCII-formatted data content from ushort
+        /// </summary>
+        /// <param name="value">数据</param>
+        /// <returns>ASCII格式的字节数组</returns>
+        public static byte[] BuildAsciiBytesFrom(ushort value) => Encoding.ASCII.GetBytes(value.ToString("X4"));
+
+        /// <summary>
+        /// 从uint构建一个ASCII格式的数据内容<br />
+        /// Constructing an ASCII-formatted data content from uint
+        /// </summary>
+        /// <param name="value">数据</param>
+        /// <returns>ASCII格式的字节数组</returns>
+        public static byte[] BuildAsciiBytesFrom(uint value) => Encoding.ASCII.GetBytes(value.ToString("X8"));
+
+        /// <summary>
+        /// 从字节数组构建一个ASCII格式的数据内容<br />
+        /// Byte array to construct an ASCII format data content
+        /// </summary>
+        /// <param name="value">字节信息</param>
+        /// <returns>ASCII格式的地址</returns>
+        public static byte[] BuildAsciiBytesFrom(byte[] value)
+        {
+            byte[] buffer = new byte[value.Length * 2];
+            for (int i = 0; i < value.Length; i++)
+            {
+                SoftBasic.BuildAsciiBytesFrom(value[i]).CopyTo(buffer, 2 * i);
+            }
+            return buffer;
+        }
+
+        #endregion
+
+        #region Bool and Byte transform
+
+        private static byte GetDataByBitIndex(int offset)
+        {
+            switch (offset)
+            {
+                case 0: return 0x01;
+                case 1: return 0x02;
+                case 2: return 0x04;
+                case 3: return 0x08;
+                case 4: return 0x10;
+                case 5: return 0x20;
+                case 6: return 0x40;
+                case 7: return 0x80;
+                default: return 0;
+            }
+        }
+
+        /// <summary>
+        /// 获取byte数据类型的第offset位,是否为True<br />
+        /// Gets the index bit of the byte data type, whether it is True
+        /// </summary>
+        /// <param name="value">byte数值</param>
+        /// <param name="offset">索引位置</param>
+        /// <returns>结果</returns>
+        /// <example>
+        /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\BasicFramework\SoftBasicExample.cs" region="BoolOnByteIndex" title="BoolOnByteIndex示例" />
+        /// </example>
+        public static bool BoolOnByteIndex(byte value, int offset)
+        {
+            byte temp = GetDataByBitIndex(offset);
+            return (value & temp) == temp;
+        }
+
+        /// <summary>
+        /// 设置取byte数据类型的第offset位,是否为True<br />
+        /// Set the offset bit of the byte data type, whether it is True
+        /// </summary>
+        /// <param name="byt">byte数值</param>
+        /// <param name="offset">索引位置</param>
+        /// <param name="value">写入的结果值</param>
+        /// <returns>结果</returns>
+        /// <example>
+        /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\BasicFramework\SoftBasicExample.cs" region="SetBoolOnByteIndex" title="SetBoolOnByteIndex示例" />
+        /// </example>
+        public static byte SetBoolOnByteIndex(byte byt, int offset, bool value)
+        {
+            byte temp = GetDataByBitIndex(offset);
+            if (value) return (byte)(byt | temp);
+            return (byte)(byt & (~temp));
+        }
+
+        /// <summary>
+        /// 将bool数组转换到byte数组<br />
+        /// Converting a bool array to a byte array
+        /// </summary>
+        /// <param name="array">bool数组</param>
+        /// <returns>转换后的字节数组</returns>
+        /// <example>
+        /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\BasicFramework\SoftBasicExample.cs" region="BoolArrayToByte" title="BoolArrayToByte示例" />
+        /// </example>
+        public static byte[] BoolArrayToByte(bool[] array)
+        {
+            if (array == null) return null;
+
+            int length = array.Length % 8 == 0 ? array.Length / 8 : array.Length / 8 + 1;
+            byte[] buffer = new byte[length];
+
+            for (int i = 0; i < array.Length; i++)
+            {
+                if (array[i]) buffer[i / 8] += GetDataByBitIndex(i % 8);
+            }
+
+            return buffer;
+        }
+
+        /// <summary>
+        /// 将bool数组转换为字符串进行显示,true被转为1,false转换为0<br />
+        /// Convert the bool array to a string for display, true is converted to 1, false is converted to 0
+        /// </summary>
+        /// <param name="array">bool数组</param>
+        /// <returns>转换后的字符串</returns>
+        public static string BoolArrayToString(bool[] array)
+        {
+            if (array == null) return string.Empty;
+            StringBuilder sb = new StringBuilder();
+            for (int i = 0; i < array.Length; i++)
+            {
+                sb.Append(array[i] ? "1" : "0");
+            }
+            return sb.ToString();
+        }
+
+        /// <summary>
+        /// 从Byte数组中提取位数组,length代表位数,例如数组 03 A1 长度10转为 [1100 0000 10]<br />
+        /// Extracts a bit array from a byte array, length represents the number of digits
+        /// </summary>
+        /// <param name="inBytes">原先的字节数组</param>
+        /// <param name="length">想要转换的长度,如果超出自动会缩小到数组最大长度</param>
+        /// <returns>转换后的bool数组</returns>
+        /// <example>
+        /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\BasicFramework\SoftBasicExample.cs" region="ByteToBoolArray" title="ByteToBoolArray示例" />
+        /// </example> 
+        public static bool[] ByteToBoolArray(byte[] inBytes, int length)
+        {
+            if (inBytes == null) return null;
+
+            if (length > inBytes.Length * 8) length = inBytes.Length * 8;
+            bool[] buffer = new bool[length];
+
+            for (int i = 0; i < length; i++)
+            {
+                buffer[i] = BoolOnByteIndex(inBytes[i / 8], i % 8);
+            }
+
+            return buffer;
+        }
+
+        /// <summary>
+        /// 从Byte数组中提取位数组,length代表位数,例如数组 01 00 长度10转为 [true,false]<br />
+        /// </summary>
+        /// <param name="inBytes">原先的字节数组</param>
+        /// <param name="length">想要转换的长度,如果超出自动会缩小到数组最大长度</param>
+        /// <param name="trueValue">当为true时对应的byte值</param>
+        /// <returns>转换后bool数组</returns>
+        public static bool[] ByteToBoolArray(byte[] inBytes, int length, byte trueValue)
+        {
+            if (inBytes == null) return null;
+
+            if (length > inBytes.Length) length = inBytes.Length;
+            bool[] buffer = new bool[length];
+
+            for (int i = 0; i < length; i++)
+            {
+                buffer[i] = inBytes[i] == trueValue;
+            }
+
+            return buffer;
+        }
+
+        /// <summary>
+        /// 从Byte数组中提取所有的位数组<br />
+        /// Extracts a bit array from a byte array, length represents the number of digits
+        /// </summary>
+        /// <param name="InBytes">原先的字节数组</param>
+        /// <returns>转换后的bool数组</returns>
+        /// <example>
+        /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\BasicFramework\SoftBasicExample.cs" region="ByteToBoolArray" title="ByteToBoolArray示例" />
+        /// </example> 
+        public static bool[] ByteToBoolArray(byte[] InBytes) => InBytes == null ? null : ByteToBoolArray(InBytes, InBytes.Length * 8);
+
+        #endregion
+
+        #region Array Clip
+
+        /// <summary>
+        /// 将一个数组的前后移除指定位数,返回新的一个数组<br/>
+        /// Removes a array before and after the specified number of bits, returning a new array
+        /// </summary>
+        /// <param name="value">数组</param>
+        /// <param name="leftLength">前面的位数</param>
+        /// <param name="rightLength">后面的位数</param>
+        /// <returns>新的数组</returns>
+        /// <exception cref="RankException"></exception>
+        /// <example>
+        /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\BasicFramework\SoftBasicExample.cs" region="ArrayRemoveDouble" title="ArrayRemoveDouble示例" />
+        /// </example> 
+        public static T[] ArrayRemoveDouble<T>(T[] value, int leftLength, int rightLength)
+        {
+            if (value == null) return null;
+            if (value.Length <= (leftLength + rightLength)) return new T[0];
+
+            T[] buffer = new T[value.Length - leftLength - rightLength];
+            Array.Copy(value, leftLength, buffer, 0, buffer.Length);
+
+            return buffer;
+        }
+
+        /// <summary>
+        /// 将一个数组的前面指定位数移除,返回新的一个数组<br />
+        /// Removes the preceding specified number of bits in a array, returning a new array
+        /// </summary>
+        /// <param name="value">数组</param>
+        /// <param name="length">等待移除的长度</param>
+        /// <returns>新的数组</returns>
+        /// <exception cref="RankException"></exception>
+        /// <example>
+        /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\BasicFramework\SoftBasicExample.cs" region="ArrayRemoveBegin" title="ArrayRemoveBegin示例" />
+        /// </example> 
+        public static T[] ArrayRemoveBegin<T>(T[] value, int length) => ArrayRemoveDouble(value, length, 0);
+
+        /// <summary>
+        /// 将一个数组的后面指定位数移除,返回新的一个数组<br />
+        /// Removes the specified number of digits after a array, returning a new array
+        /// </summary>
+        /// <param name="value">数组</param>
+        /// <param name="length">等待移除的长度</param>
+        /// <returns>新的数组</returns>
+        /// <exception cref="RankException"></exception>
+        /// <example>
+        /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\BasicFramework\SoftBasicExample.cs" region="ArrayRemoveLast" title="ArrayRemoveLast示例" />
+        /// </example> 
+        public static T[] ArrayRemoveLast<T>(T[] value, int length) => ArrayRemoveDouble(value, 0, length);
+
+        /// <summary>
+        /// 获取到数组里面的中间指定长度的数组<br />
+        /// Get an array of the specified length in the array
+        /// </summary>
+        /// <param name="value">数组</param>
+        /// <param name="index">起始索引</param>
+        /// <param name="length">数据的长度</param>
+        /// <returns>新的数组值</returns>
+        /// <exception cref="IndexOutOfRangeException"></exception>
+        /// <example>
+        /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\BasicFramework\SoftBasicExample.cs" region="ArraySelectMiddle" title="ArraySelectMiddle示例" />
+        /// </example> 
+        public static T[] ArraySelectMiddle<T>(T[] value, int index, int length)
+        {
+            if (value == null) return null;
+            if (length == 0) return new T[0];
+            T[] buffer = new T[Math.Min(value.Length, length)];
+            Array.Copy(value, index, buffer, 0, buffer.Length);
+            return buffer;
+        }
+
+        /// <summary>
+        /// 选择一个数组的前面的几个数据信息<br />
+        /// Select the begin few items of data information of a array
+        /// </summary>
+        /// <param name="value">数组</param>
+        /// <param name="length">数据的长度</param>
+        /// <returns>新的数组</returns>
+        /// <example>
+        /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\BasicFramework\SoftBasicExample.cs" region="ArraySelectBegin" title="ArraySelectBegin示例" />
+        /// </example> 
+        public static T[] ArraySelectBegin<T>(T[] value, int length)
+        {
+            if (length == 0) return new T[0];
+            T[] buffer = new T[Math.Min(value.Length, length)];
+            if (buffer.Length > 0) Array.Copy(value, 0, buffer, 0, buffer.Length);
+            return buffer;
+        }
+
+        /// <summary>
+        /// 选择一个数组的后面的几个数据信息<br />
+        /// Select the last few items of data information of a array
+        /// </summary>
+        /// <param name="value">数组</param>
+        /// <param name="length">数据的长度</param>
+        /// <returns>新的数组信息</returns>
+        /// <example>
+        /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\BasicFramework\SoftBasicExample.cs" region="ArraySelectLast" title="ArraySelectLast示例" />
+        /// </example> 
+        public static T[] ArraySelectLast<T>(T[] value, int length)
+        {
+            if (length == 0) return new T[0];
+            T[] buffer = new T[Math.Min(value.Length, length)];
+            Array.Copy(value, value.Length - length, buffer, 0, buffer.Length);
+            return buffer;
+        }
+
+        #endregion
+
+        #region Byte[] Splice
+
+        /// <summary>
+        /// 拼接任意个泛型数组为一个总的泛型数组对象,采用深度拷贝实现。<br />
+        /// Splicing any number of generic arrays into a total generic array object is implemented using deep copy.
+        /// </summary>
+        /// <typeparam name="T">数组的类型信息</typeparam>
+        /// <param name="arrays">任意个长度的数组</param>
+        /// <returns>拼接之后的最终的结果对象</returns>
+        /// <example>
+        /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\BasicFramework\SoftBasicExample.cs" region="SpliceByteArray" title="SpliceByteArray示例" />
+        /// </example> 
+        public static T[] SpliceArray<T>(params T[][] arrays)
+        {
+            int count = 0;
+            for (int i = 0; i < arrays.Length; i++)
+            {
+                if (arrays[i]?.Length > 0)
+                {
+                    count += arrays[i].Length;
+                }
+            }
+            int index = 0;
+            T[] buffer = new T[count];
+            for (int i = 0; i < arrays.Length; i++)
+            {
+                if (arrays[i]?.Length > 0)
+                {
+                    arrays[i].CopyTo(buffer, index);
+                    index += arrays[i].Length;
+                }
+            }
+            return buffer;
+        }
+
+        #endregion
+
+        #region String Array Splice
+
+        /// <summary>
+        /// 将一个<see cref="string"/>的数组和多个<see cref="string"/> 类型的对象整合成一个数组<br />
+        /// Combine an array of <see cref = "string" /> and multiple objects of type <see cref = "string" /> into an array
+        /// </summary>
+        /// <param name="first">第一个数组对象</param>
+        /// <param name="array">字符串数组信息</param>
+        /// <returns>总的数组对象</returns>
+        public static string[] SpliceStringArray(string first, string[] array)
+        {
+            List<string> list = new List<string>();
+            list.Add(first);
+            list.AddRange(array);
+            return list.ToArray();
+        }
+
+        /// <summary>
+        /// 将两个<see cref="string"/>的数组和多个<see cref="string"/> 类型的对象整合成一个数组<br />
+        /// Combine two arrays of <see cref="string" /> and multiple objects of type <see cref="string"/> into one array
+        /// </summary>
+        /// <param name="first">第一个数据对象</param>
+        /// <param name="second">第二个数据对象</param>
+        /// <param name="array">字符串数组信息</param>
+        /// <returns>总的数组对象</returns>
+        public static string[] SpliceStringArray(string first, string second, string[] array)
+        {
+            List<string> list = new List<string>();
+            list.Add(first);
+            list.Add(second);
+            list.AddRange(array);
+            return list.ToArray();
+        }
+
+        /// <summary>
+        /// 将两个<see cref="string"/>的数组和多个<see cref="string"/> 类型的对象整合成一个数组<br />
+        /// Combine two arrays of <see cref="string" /> and multiple objects of type <see cref="string"/> into one array
+        /// </summary>
+        /// <param name="first">第一个数据对象</param>
+        /// <param name="second">第二个数据对象</param>
+        /// <param name="third">第三个数据对象</param>
+        /// <param name="array">字符串数组信息</param>
+        /// <returns>总的数组对象</returns>
+        public static string[] SpliceStringArray(string first, string second, string third, string[] array)
+        {
+            List<string> list = new List<string>();
+            list.Add(first);
+            list.Add(second);
+            list.Add(third);
+            list.AddRange(array);
+            return list.ToArray();
+        }
+
+        #endregion
+
+        #region Url Encode Decode
+
+        // 以下代码来自于微软自身的开源库
+
+        private static int HexToInt(char h)
+        {
+            return (h >= '0' && h <= '9') ? h - '0' :
+            (h >= 'a' && h <= 'f') ? h - 'a' + 10 :
+            (h >= 'A' && h <= 'F') ? h - 'A' + 10 :
+            -1;
+        }
+
+        private static string ValidateString(string input, bool skipUtf16Validation)
+        {
+            if (skipUtf16Validation || String.IsNullOrEmpty(input))
+            {
+                return input;
+            }
+
+            // locate the first surrogate character
+            int idxOfFirstSurrogate = -1;
+            for (int i = 0; i < input.Length; i++)
+            {
+                if (Char.IsSurrogate(input[i]))
+                {
+                    idxOfFirstSurrogate = i;
+                    break;
+                }
+            }
+
+            // fast case: no surrogates = return input string
+            if (idxOfFirstSurrogate < 0)
+            {
+                return input;
+            }
+
+            // slow case: surrogates exist, so we need to validate them
+            char[] chars = input.ToCharArray();
+            for (int i = idxOfFirstSurrogate; i < chars.Length; i++)
+            {
+                char thisChar = chars[i];
+
+                // If this character is a low surrogate, then it was not preceded by
+                // a high surrogate, so we'll replace it.
+                if (Char.IsLowSurrogate(thisChar))
+                {
+                    chars[i] = '\uFFFD';
+                    continue;
+                }
+
+                if (Char.IsHighSurrogate(thisChar))
+                {
+                    // If this character is a high surrogate and it is followed by a
+                    // low surrogate, allow both to remain.
+                    if (i + 1 < chars.Length && Char.IsLowSurrogate(chars[i + 1]))
+                    {
+                        i++; // skip the low surrogate also
+                        continue;
+                    }
+
+                    // If this character is a high surrogate and it is not followed
+                    // by a low surrogate, replace it.
+                    chars[i] = '\uFFFD';
+                    continue;
+                }
+
+                // Otherwise, this is a non-surrogate character and just move to the
+                // next character.
+            }
+            return new String(chars);
+        }
+
+        private static bool ValidateUrlEncodingParameters(byte[] bytes, int offset, int count)
+        {
+            if (bytes == null && count == 0)
+                return false;
+            if (bytes == null)
+            {
+                throw new ArgumentNullException("bytes");
+            }
+            if (offset < 0 || offset > bytes.Length)
+            {
+                throw new ArgumentOutOfRangeException("offset");
+            }
+            if (count < 0 || offset + count > bytes.Length)
+            {
+                throw new ArgumentOutOfRangeException("count");
+            }
+
+            return true;
+        }
+
+        private static bool IsUrlSafeChar(char ch)
+        {
+            if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9'))
+                return true;
+
+            switch (ch)
+            {
+                case '-':
+                case '_':
+                case '.':
+                case '!':
+                case '*':
+                case '(':
+                case ')':
+                    return true;
+            }
+
+            return false;
+        }
+
+        private static string UrlEncodeSpaces(string str)
+        {
+            if (str != null && str.IndexOf(' ') >= 0)
+                str = str.Replace(" ", "%20");
+            return str;
+        }
+
+        private static char IntToHex(int n)
+        {
+            Debug.Assert(n < 0x10);
+
+            if (n <= 9)
+                return (char)(n + (int)'0');
+            else
+                return (char)(n - 10 + (int)'A');
+        }
+
+        private static byte[] UrlEncodeToBytes(byte[] bytes)
+        {
+            int offset = 0;
+            int count = bytes.Length;
+            if (!ValidateUrlEncodingParameters(bytes, offset, count))
+            {
+                return null;
+            }
+
+            int cSpaces = 0;
+            int cUnsafe = 0;
+
+            // count them first
+            for (int i = 0; i < count; i++)
+            {
+                char ch = (char)bytes[offset + i];
+
+                if (ch == ' ')
+                    cSpaces++;
+                else if (!IsUrlSafeChar(ch))
+                    cUnsafe++;
+            }
+
+            // nothing to expand?
+            if (cSpaces == 0 && cUnsafe == 0)
+            {
+                // DevDiv 912606: respect "offset" and "count"
+                if (0 == offset && bytes.Length == count)
+                {
+                    return bytes;
+                }
+                else
+                {
+                    var subarray = new byte[count];
+                    Buffer.BlockCopy(bytes, offset, subarray, 0, count);
+                    return subarray;
+                }
+            }
+
+            // expand not 'safe' characters into %XX, spaces to +s
+            byte[] expandedBytes = new byte[count + cUnsafe * 2];
+            int pos = 0;
+
+            for (int i = 0; i < count; i++)
+            {
+                byte b = bytes[offset + i];
+                char ch = (char)b;
+
+                if (IsUrlSafeChar(ch))
+                {
+                    expandedBytes[pos++] = b;
+                }
+                else if (ch == ' ')
+                {
+                    expandedBytes[pos++] = (byte)'+';
+                }
+                else
+                {
+                    expandedBytes[pos++] = (byte)'%';
+                    expandedBytes[pos++] = (byte)IntToHex((b >> 4) & 0xf);
+                    expandedBytes[pos++] = (byte)IntToHex(b & 0x0f);
+                }
+            }
+
+            return expandedBytes;
+        }
+
+        private static byte[] UrlEncode(byte[] bytes, bool alwaysCreateNewReturnValue)
+        {
+            byte[] encoded = UrlEncodeToBytes(bytes);
+
+            return (alwaysCreateNewReturnValue && (encoded != null) && (encoded == bytes))
+                ? (byte[])encoded.Clone()
+                : encoded;
+        }
+
+        /// <summary>
+        /// 将字符串编码为URL可以识别的字符串,中文会被编码为%开头的数据,例如 中文 转义为 %2F%E4%B8%AD%E6%96%87 <br />
+        /// Encoding a string as a URL-recognizable string Chinese encoded as data that begins with %, such as 中文 escaped as %2F%E4%B8%AD%E6%96%87
+        /// </summary>
+        /// <param name="str">等待转换的字符串数据</param>
+        /// <param name="e">编码信息,一般为 UTF8 </param>
+        /// <returns>编码之后的结果</returns>
+        public static string UrlEncode(string str, Encoding e)
+        {
+            if (str == null)
+                return null;
+            byte[] bytes = e.GetBytes(str);
+
+            return Encoding.ASCII.GetString(UrlEncode(bytes, true));
+        }
+
+        /// <summary>
+        /// 将url的编码解码为真实的字符串,例如 %2F%E4%B8%AD%E6%96%87 解码为 中文<br />
+        /// Decode the encoding of url as a real string, for example %2F%E4%B8%AD%E6%96%87 to 中文
+        /// </summary>
+        /// <param name="value">等待转换的值</param>
+        /// <param name="encoding">编码信息,一般为 UTF8</param>
+        /// <returns>解码之后的结果</returns>
+        public static string UrlDecode(string value, Encoding encoding)
+        {
+            int count = value.Length;
+            UrlDecoder helper = new UrlDecoder(count, encoding);
+
+            // go through the string's chars collapsing %XX and %uXXXX and
+            // appending each char as char, with exception of %XX constructs
+            // that are appended as bytes
+
+            for (int pos = 0; pos < count; pos++)
+            {
+                char ch = value[pos];
+
+                if (ch == '+')
+                {
+                    ch = ' ';
+                }
+                else if (ch == '%' && pos < count - 2)
+                {
+                    if (value[pos + 1] == 'u' && pos < count - 5)
+                    {
+                        int h1 = HexToInt(value[pos + 2]);
+                        int h2 = HexToInt(value[pos + 3]);
+                        int h3 = HexToInt(value[pos + 4]);
+                        int h4 = HexToInt(value[pos + 5]);
+
+                        if (h1 >= 0 && h2 >= 0 && h3 >= 0 && h4 >= 0)
+                        {   // valid 4 hex chars
+                            ch = (char)((h1 << 12) | (h2 << 8) | (h3 << 4) | h4);
+                            pos += 5;
+
+                            // only add as char
+                            helper.AddChar(ch);
+                            continue;
+                        }
+                    }
+                    else
+                    {
+                        int h1 = HexToInt(value[pos + 1]);
+                        int h2 = HexToInt(value[pos + 2]);
+
+                        if (h1 >= 0 && h2 >= 0)
+                        {     // valid 2 hex chars
+                            byte b = (byte)((h1 << 4) | h2);
+                            pos += 2;
+
+                            // don't add as char
+                            helper.AddByte(b);
+                            continue;
+                        }
+                    }
+                }
+
+                if ((ch & 0xFF80) == 0)
+                    helper.AddByte((byte)ch); // 7 bit have to go as bytes because of Unicode
+                else
+                    helper.AddChar(ch);
+            }
+            return ValidateString(helper.GetString(), true);
+        }
+
+        #endregion
+
+        #region Deep Clone
+
+        /// <summary>
+        /// 使用序列化反序列化深度克隆一个对象,该对象需要支持序列化特性<br />
+        /// Cloning an object with serialization deserialization depth that requires support for serialization attributes
+        /// </summary>
+        /// <param name="oringinal">源对象,支持序列化</param>
+        /// <returns>新的一个实例化的对象</returns>
+        /// <exception cref="NullReferenceException"></exception>
+        /// <exception cref="NonSerializedAttribute"></exception>
+        /// <remarks>
+        /// <note type="warning">
+        /// <paramref name="oringinal"/> 参数必须实现序列化的特性
+        /// </note>
+        /// </remarks>
+        /// <example>
+        /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\BasicFramework\SoftBasicExample.cs" region="DeepClone" title="DeepClone示例" />
+        /// </example>
+        public static object DeepClone(object oringinal)
+        {
+            using (System.IO.MemoryStream stream = new System.IO.MemoryStream())
+            {
+                BinaryFormatter formatter = new BinaryFormatter()
+                {
+                    Context = new System.Runtime.Serialization.StreamingContext(System.Runtime.Serialization.StreamingContextStates.Clone)
+                };
+                formatter.Serialize(stream, oringinal);
+                stream.Position = 0;
+                return formatter.Deserialize(stream);
+            }
+        }
+
+        #endregion
+
+        #region Unique String Get
+
+        /// <summary>
+        /// 获取一串唯一的随机字符串,长度为20,由Guid码和4位数的随机数组成,保证字符串的唯一性<br />
+        /// Gets a string of unique random strings with a length of 20, consisting of a GUID code and a 4-digit random number to guarantee the uniqueness of the string
+        /// </summary>
+        /// <returns>随机字符串数据</returns>
+        /// <example>
+        /// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\BasicFramework\SoftBasicExample.cs" region="GetUniqueStringByGuidAndRandom" title="GetUniqueStringByGuidAndRandom示例" />
+        /// </example>
+        public static string GetUniqueStringByGuidAndRandom()
+        {
+            return Guid.NewGuid().ToString("N") + new Random().Next(1000, 10000);
+        }
+
+        #endregion
+
+    }
+
+    class UrlDecoder
+    {
+        private int _bufferSize;
+
+        // Accumulate characters in a special array
+        private int _numChars;
+        private char[] _charBuffer;
+
+        // Accumulate bytes for decoding into characters in a special array
+        private int _numBytes;
+        private byte[] _byteBuffer;
+
+        // Encoding to convert chars to bytes
+        private Encoding _encoding;
+
+        private void FlushBytes()
+        {
+            if (_numBytes > 0)
+            {
+                _numChars += _encoding.GetChars(_byteBuffer, 0, _numBytes, _charBuffer, _numChars);
+                _numBytes = 0;
+            }
+        }
+
+        internal UrlDecoder(int bufferSize, Encoding encoding)
+        {
+            _bufferSize = bufferSize;
+            _encoding = encoding;
+
+            _charBuffer = new char[bufferSize];
+            // byte buffer created on demand
+        }
+
+        internal void AddChar(char ch)
+        {
+            if (_numBytes > 0)
+                FlushBytes();
+
+            _charBuffer[_numChars++] = ch;
+        }
+
+        internal void AddByte(byte b)
+        {
+            // if there are no pending bytes treat 7 bit bytes as characters
+            // this optimization is temp disable as it doesn't work for some encodings
+            /*
+							if (_numBytes == 0 && ((b & 0x80) == 0)) {
+								AddChar((char)b);
+							}
+							else
+			*/
+            {
+                if (_byteBuffer == null)
+                    _byteBuffer = new byte[_bufferSize];
+
+                _byteBuffer[_numBytes++] = b;
+            }
+        }
+
+        internal string GetString()
+        {
+            if (_numBytes > 0)
+                FlushBytes();
+
+            if (_numChars > 0)
+                return new string(_charBuffer, 0, _numChars);
+            else
+                return string.Empty;
+        }
+    }
+}

+ 255 - 0
Extensions/Tools.cs

@@ -0,0 +1,255 @@
+using IWshRuntimeLibrary;
+using SWRIS.Core;
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Net;
+using System.Net.NetworkInformation;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace SWRIS.Extensions
+{
+    public static class Tools
+    {
+        #region 获取 本周、本月、本季度、本年 的开始时间或结束时间
+        /// <summary>
+        /// 获取结束时间
+        /// </summary>
+        /// <param name="timeType">Week、Month、Season、Year</param>
+        /// <param name="time"></param>
+        /// <returns></returns>
+        public static DateTime GetTimeStartByType(string timeType, DateTime time)
+        {
+            switch (timeType)
+            {
+                case "Day":
+                    return time.Date;
+                case "Week":
+                    DayOfWeek firstDay = DayOfWeek.Monday; // 周一为第一天
+                    return time.AddDays(-((7 + (time.DayOfWeek - firstDay)) % 7));
+                case "Month":
+                    return time.AddDays(-time.Day + 1);
+                case "Season":
+                    var time1 = time.AddMonths(0 - ((time.Month - 1) % 3));
+                    return time1.AddDays(-time1.Day + 1);
+                case "Year":
+                    return time.AddDays(-time.DayOfYear + 1);
+                default:
+                    return time;
+            }
+        }
+
+        /// <summary>
+        /// 获取结束时间
+        /// </summary>
+        /// <param name="timeType">Week、Month、Season、Year</param>
+        /// <param name="time"></param>
+        /// <returns></returns>
+        public static DateTime GetTimeEndByType(string timeType, DateTime time)
+        {
+            switch (timeType)
+            {
+                case "Day":
+                    return time.Date.AddDays(1).AddSeconds(-1);
+                case "Week":
+                    DayOfWeek firstDay = DayOfWeek.Monday; // 周一为第一天
+                    return time.AddDays(-((7 + (time.DayOfWeek - firstDay)) % 7) + 7).AddSeconds(-1);
+                case "Month":
+                    return time.AddMonths(1).AddDays(-time.AddMonths(1).Day + 1).AddSeconds(-1);
+                case "Season":
+                    var time1 = time.AddMonths((3 - ((time.Month - 1) % 3) - 1));
+                    return time1.AddMonths(1).AddDays(-time1.AddMonths(1).Day + 1).AddSeconds(-1);
+                case "Year":
+                    var time2 = time.AddYears(1);
+                    return time2.AddDays(-time2.DayOfYear).AddSeconds(-1);
+                default:
+                    return time;
+            }
+        }
+
+        #endregion
+
+        public static string GetWeek(int week)
+        {
+            switch (week)
+            {
+                case 1:
+                    return "星期一";
+                case 2:
+                    return "星期二";
+                case 3:
+                    return "星期三";
+                case 4:
+                    return "星期四";
+                case 5:
+                    return "星期五";
+                case 6:
+                    return "星期六";
+                case 7:
+                    return "星期日";
+                default:
+                    return "星期日";
+            }
+        }
+
+        public static string GetIpAddress()
+        {
+            string ip = string.Empty;
+            foreach (IPAddress _IPAddress in Dns.GetHostEntry(Dns.GetHostName()).AddressList)
+            {
+                if (_IPAddress.AddressFamily.ToString() == "InterNetwork")
+                {
+                    if ("127.0.0.1" == _IPAddress.ToString())
+                    {
+                        continue;
+                    }
+                    else
+                    {
+                        ip = _IPAddress.ToString();
+                        break;
+                    }
+                }
+            }
+            return ip;
+        }
+        /// <summary>
+        /// 检查指定的IP地址是否存在于本机的网络接口中
+        /// </summary>
+        /// <param name="targetIp"></param>
+        /// <returns></returns>
+        public static bool CheckIpAddressExists(string targetIp)
+        {
+            // 获取所有网络接口
+            NetworkInterface[] interfaces = NetworkInterface.GetAllNetworkInterfaces();
+
+            foreach (NetworkInterface ni in interfaces)
+            {
+                // 只获取运行中的接口
+                if (ni.OperationalStatus == OperationalStatus.Up)
+                {
+                    IPInterfaceProperties ipProperties = ni.GetIPProperties();
+
+                    // 获取该接口的所有IP地址
+                    foreach (UnicastIPAddressInformation ip in ipProperties.UnicastAddresses)
+                    {
+                        if (ip.Address.ToString() == targetIp)
+                        {
+                            return true;
+                        }
+                    }
+                }
+            }
+            return false;
+        }
+        /// <summary>
+        /// 检查指定端口是否被占用
+        /// </summary>
+        /// <param name="port"></param>
+        /// <returns></returns>
+        public static bool CheckPortInUse(int port)
+        {
+            IPGlobalProperties ipProperties = IPGlobalProperties.GetIPGlobalProperties();
+            IPEndPoint[] endPoints = ipProperties.GetActiveTcpListeners();
+
+            foreach (IPEndPoint endPoint in endPoints)
+            {
+                if (endPoint.Port == port)
+                {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        public static byte[] GetMD5Bytes(string input)
+        {
+            using (MD5 md5 = MD5.Create())
+            {
+                byte[] inputBytes = Encoding.UTF8.GetBytes(input);
+                byte[] hashBytes = md5.ComputeHash(inputBytes);
+                return hashBytes;
+            }
+        }
+
+        /// <summary>
+        /// DateTime 转换为时间戳
+        /// </summary>
+        public static int DateTimeToTimestamp(this DateTime time)
+        {
+            DateTimeOffset dto = new DateTimeOffset(time);
+            return (int)(dto.ToUnixTimeSeconds());
+        }
+        /// <summary>
+        /// 时间戳转换为DateTime
+        /// </summary>
+        public static DateTime TimestampToDateTime(this int timestamp)
+        {
+            DateTimeOffset dto = DateTimeOffset.FromUnixTimeSeconds(timestamp);
+            return dto.LocalDateTime;
+        }
+        /// <summary>
+        /// 创建桌面会计快捷方式,要求管理员权限运行
+        /// </summary>
+        /// <param name="applicationPath"></param>
+        /// <param name="shortcutName"></param>
+        /// <returns></returns>
+        public static bool CreateShortcut(string applicationPath, string shortcutName)
+        {
+            string desktopPath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
+            string shortcutPath = Path.Combine(desktopPath, shortcutName + ".lnk");
+
+            // 检查快捷方式是否已存在
+            if (System.IO.File.Exists(shortcutPath))
+            {
+                return false; // 已存在,不创建
+            }
+
+            try
+            {
+                WshShell shell = new WshShell();
+                IWshShortcut shortcut = (IWshShortcut)shell.CreateShortcut(shortcutPath);
+
+                shortcut.TargetPath = applicationPath;
+                shortcut.WorkingDirectory = Path.GetDirectoryName(applicationPath);
+                shortcut.Description = "需要管理员权限运行";
+                shortcut.IconLocation = applicationPath + ",0";
+
+                // 保存快捷方式
+                shortcut.Save();
+
+                // 修改快捷方式以要求管理员权限
+                using (var fs = new FileStream(shortcutPath, FileMode.Open, FileAccess.ReadWrite))
+                {
+                    fs.Seek(21, SeekOrigin.Begin);
+                    fs.WriteByte(0x22); // 设置标志位以管理员身份运行
+                }
+                return true; // 创建成功
+            }
+            catch
+            {
+                // 如果创建过程中出错,尝试删除可能已部分创建的快捷方式
+                if (System.IO.File.Exists(shortcutPath))
+                {
+                    try { System.IO.File.Delete(shortcutPath); } catch { }
+                }
+                throw;
+            }
+        }
+        public static void OpenUrl(string url)
+        {
+            try
+            {
+                Process.Start(new ProcessStartInfo
+                {
+                    FileName = url,
+                    UseShellExecute = true // 使用系统默认浏览器
+                });
+            }
+            catch (Exception ex)
+            {
+                LogHelper.Error($"打开网页失败:{ex.Message}", ex);
+            }
+        }
+    }
+}

+ 284 - 0
Extensions/TypeExtension.cs

@@ -0,0 +1,284 @@
+using SWRIS.Dtos;
+using System;
+using System.Collections;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Windows;
+using System.Windows.Media.Imaging;
+
+namespace SWRIS.Extensions
+{
+    public static class TypeExtension
+    {
+        public static string ToLogString(this Exception ex)
+        {
+            string result;
+            if (ex == null)
+            {
+                result = string.Empty;
+            }
+            else
+            {
+                StringBuilder stringBuilder = new StringBuilder();
+                stringBuilder.Append("  >>异常消息(Message):").AppendLine(ex.Message);
+                stringBuilder.Append("  >>异常来源(Source):").AppendLine(ex.Source);
+                stringBuilder.Append("  >>异常类型(ExceptionType):").AppendLine(ex.GetType().ToString());
+                stringBuilder.Append("  >>原生异常类型(BaseExceptionType):").AppendLine(ex.GetBaseException().GetType().ToString());
+                stringBuilder.Append("  >>出错的方法签名(TargetSite):").Append(ex.TargetSite);
+                if (ex.Data.Count > 0)
+                {
+                    stringBuilder.Append(Environment.NewLine);
+                    stringBuilder.Append("  >>自定义数据(Data):");
+                    StringBuilder stringBuilder2 = new StringBuilder();
+                    foreach (object obj in ex.Data)
+                    {
+                        DictionaryEntry dictionaryEntry = (DictionaryEntry)obj;
+                        stringBuilder2.Append(string.Concat(new object[]
+                        {
+                            "Key:",
+                            dictionaryEntry.Key,
+                            ",Value:",
+                            dictionaryEntry.Value,
+                            "; "
+                        }));
+                    }
+                    stringBuilder.Append(stringBuilder2);
+                }
+                if (!string.IsNullOrEmpty(ex.StackTrace))
+                {
+                    stringBuilder.Append(Environment.NewLine);
+                    stringBuilder.Append("  >>堆栈信息(StackTrace):");
+                    stringBuilder.Append(Environment.NewLine);
+                    stringBuilder.Append(ex.StackTrace);
+                }
+                if (ex.InnerException != null)
+                {
+                    stringBuilder.Append(Environment.NewLine);
+                    stringBuilder.Append(">>========================== 内部异常(InnerException)==========================");
+                    stringBuilder.Append(Environment.NewLine);
+                    stringBuilder.Append(ex.InnerException.ToLogString());
+                }
+                result = stringBuilder.ToString();
+            }
+            return result;
+        }
+        public static bool IsInvalid<T>(this IEnumerable<T> source)
+        {
+            return !source.IsValid<T>();
+        }
+        public static bool IsValid<T>(this IEnumerable<T> source)
+        {
+            return source != null && source.Any<T>();
+        }
+        public static bool IsNullOrEmpty(this string value)
+        {
+            return string.IsNullOrEmpty(value);
+        }
+
+        public static bool IsNullOrEmptyOrWhiteSpace(this string value)
+        {
+            bool flag = value.IsNullOrEmpty();
+            if (!flag)
+            {
+                flag = value.Trim().IsNullOrEmpty();
+            }
+            return flag;
+        }
+        public static byte[] TranslateToBytes(this string hexString)
+        {
+            try
+            {
+                if (!Regex.IsMatch(hexString, @"^[0-9A-Fa-f]+H?$", RegexOptions.IgnoreCase))
+                    throw new ArgumentException("无效的十六进制格式");
+
+                hexString = hexString.TrimEnd('H', 'h');
+                if (hexString.Length != 4)
+                    throw new ArgumentException("字符串长度不正确");
+
+                return (GetBigEndianBytes(Convert.ToUInt16(hexString, 16), 2));
+            }
+            catch (Exception ex)
+            {
+                Console.WriteLine($"识别码或序列号转化字节数组失败: {ex.Message}");
+                return null;
+            }
+        }
+        public static byte[] GetBigEndianBytes(uint value, int byteCount)
+        {
+            byte[] bytes = new byte[byteCount];
+            for (int i = 0; i < byteCount; i++)
+            {
+                bytes[i] = (byte)(value >> (8 * (byteCount - 1 - i)));
+            }
+            return bytes;
+        }
+        /// <summary>
+        /// 计算校验和(所有字节相加之和,取8位,溢出不计)
+        /// </summary>
+        public static byte CalculateChecksum(this byte[] data)
+        {
+            byte checksum = 0;
+            foreach (byte b in data)
+            {
+                unchecked { checksum += b; }
+            }
+            return checksum;
+        }
+        /// <summary>
+        /// 计算校验和(所有字节相加之和,取8位,溢出不计)
+        /// </summary>
+        public static byte CalculateChecksum(this IEnumerable<byte> data)
+        {
+            byte checksum = 0;
+            foreach (byte b in data)
+            {
+                unchecked { checksum += b; }
+            }
+            return checksum;
+        }
+
+        public static void AddRange<T>(this ICollection<T> @this, IEnumerable<T> values)
+        {
+            foreach (T item in values)
+            {
+                @this.Add(item);
+            }
+        }
+        public static BitmapImage ConvertBitmapToBitmapImage(this Bitmap bitmap)
+        {
+            MemoryStream stream = new MemoryStream();
+            bitmap.Save(stream, ImageFormat.Bmp);
+            BitmapImage image = new BitmapImage();
+            image.BeginInit();
+            image.StreamSource = stream;
+            image.EndInit();
+            return image;
+        }
+        public static bool? ShowDialog(this Window window, bool isMaskVisible)
+        {
+            bool? result = null;
+            if (window != null)
+            {
+                if (isMaskVisible)
+                {
+                    MainWindow mainWindow = Application.Current.MainWindow as MainWindow;
+                    mainWindow.IsMaskVisible = true;
+                    result = window.ShowDialog();
+                    mainWindow.IsMaskVisible = false;
+                }
+                else
+                {
+                    result = window.ShowDialog();
+                }
+            }
+            return result;
+        }
+        public static string GetDescription(this Enum @this)
+        {
+            return _CacheDescriptions.GetOrAdd(@this, delegate (Enum key)
+            {
+                Type type = key.GetType();
+                FieldInfo field = type.GetField(key.ToString());
+                return (field == null) ? key.GetDescriptions(",") : GetDescription(field);
+            });
+        }
+        public static string GetDescriptions(this Enum @this, string separator = ",")
+        {
+            string[] array = @this.ToString().Split(new char[]
+            {
+                ','
+            });
+            string[] array2 = new string[array.Length];
+            Type type = @this.GetType();
+            for (int i = 0; i < array.Length; i++)
+            {
+                FieldInfo field = type.GetField(array[i].Trim());
+                if (!(field == null))
+                {
+                    array2[i] = GetDescription(field);
+                }
+            }
+            return string.Join(separator, array2);
+        }
+        public static string GetDefaultValue(this Enum @this)
+        {
+            return _CacheDefaultValues.GetOrAdd(@this, delegate (Enum key)
+            {
+                Type type = key.GetType();
+                FieldInfo field = type.GetField(key.ToString());
+                return (field == null) ? key.GetDefaultValues(",") : GetDefaultValue(field);
+            });
+        }
+
+        public static string GetDefaultValues(this Enum @this, string separator = ",")
+        {
+            string[] array = @this.ToString().Split(new char[]
+            {
+                ','
+            });
+            string[] array2 = new string[array.Length];
+            Type type = @this.GetType();
+            for (int i = 0; i < array.Length; i++)
+            {
+                FieldInfo field = type.GetField(array[i].Trim());
+                if (!(field == null))
+                {
+                    array2[i] = GetDefaultValue(field);
+                }
+            }
+            return string.Join(separator, array2);
+        }
+        public static ObservableCollection<T> ConvertToObservableCollection<T>(this IEnumerable<T> from)
+        {
+            ObservableCollection<T> to = new ObservableCollection<T>();
+            if (from != null)
+            {
+                foreach (var f in from)
+                {
+                    to.Add(f);
+                }
+            }
+            return to;
+        }
+        public static List<KeyAndValueDto> ToKeyAndDescriptionList(this Type type)
+        {
+            if (type.IsEnum)
+            {
+                List<KeyAndValueDto> list = new List<KeyAndValueDto>();
+                Array _enumValues = Enum.GetValues(type);
+                foreach (Enum value in _enumValues)
+                {
+                    list.Add(new KeyAndValueDto()
+                    {
+                        Key = Convert.ToInt32(value),
+                        Value = GetDescription(value)
+                    });
+                }
+                return list;
+            }
+            return null;
+        }
+        private static string GetDefaultValue(FieldInfo field)
+        {
+            Attribute customAttribute = Attribute.GetCustomAttribute(field, typeof(DefaultValueAttribute), false);
+            return (customAttribute == null) ? string.Empty : ((DefaultValueAttribute)customAttribute).Value.ToString();
+        }
+        private static string GetDescription(FieldInfo field)
+        {
+            Attribute customAttribute = Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute), false);
+            return (customAttribute == null) ? string.Empty : ((DescriptionAttribute)customAttribute).Description;
+        }
+        private static readonly ConcurrentDictionary<Enum, string> _CacheDescriptions = new ConcurrentDictionary<Enum, string>();
+        private static readonly ConcurrentDictionary<Enum, string> _CacheDefaultValues = new ConcurrentDictionary<Enum, string>();
+    }
+}
+

+ 55 - 0
Extensions/WinHelper.cs

@@ -0,0 +1,55 @@
+using SWRIS.Enums;
+using System.Runtime.InteropServices;
+
+namespace SWRIS.Extensions
+{
+    /// <summary>
+    /// 包含控制屏幕关闭以及系统休眠相关的方法。
+    /// </summary>
+    public static class SystemSleep
+    {
+        /// <summary>
+        /// Enables an application to inform the system that it is in use, thereby preventing the system from entering sleep or turning off the display while the application is running.
+        /// </summary>
+        [DllImport("kernel32")]
+        private static extern ExecutionState SetThreadExecutionState(ExecutionState esFlags);
+
+        /// <summary>
+        /// 设置此线程此时开始一直将处于运行状态,此时计算机不应该进入睡眠状态。
+        /// 此线程退出后,设置将失效。
+        /// 如果需要恢复,请调用 <see cref="RestoreForCurrentThread"/> 方法。
+        /// </summary>
+        /// <param name="keepDisplayOn">
+        /// 表示是否应该同时保持屏幕不关闭。
+        /// 对于游戏、视频和演示相关的任务需要保持屏幕不关闭;而对于后台服务、下载和监控等任务则不需要。
+        /// </param>
+        public static void PreventForCurrentThread(bool keepDisplayOn = true)
+        {
+            SetThreadExecutionState(keepDisplayOn
+                ? ExecutionState.Continuous | ExecutionState.SystemRequired | ExecutionState.DisplayRequired
+                : ExecutionState.Continuous | ExecutionState.SystemRequired);
+        }
+
+        /// <summary>
+        /// 恢复此线程的运行状态,操作系统现在可以正常进入睡眠状态和关闭屏幕。
+        /// </summary>
+        public static void RestoreForCurrentThread()
+        {
+            SetThreadExecutionState(ExecutionState.Continuous);
+        }
+
+        /// <summary>
+        /// 重置系统睡眠或者关闭屏幕的计时器,这样系统睡眠或者屏幕能够继续持续工作设定的超时时间。
+        /// </summary>
+        /// <param name="keepDisplayOn">
+        /// 表示是否应该同时保持屏幕不关闭。
+        /// 对于游戏、视频和演示相关的任务需要保持屏幕不关闭;而对于后台服务、下载和监控等任务则不需要。
+        /// </param>
+        public static void ResetIdle(bool keepDisplayOn = true)
+        {
+            SetThreadExecutionState(keepDisplayOn
+                ? ExecutionState.SystemRequired | ExecutionState.DisplayRequired
+                : ExecutionState.SystemRequired);
+        }
+    }
+}

BIN
Fonts/Alibaba-PuHuiTi-Bold.ttf


BIN
Fonts/Alibaba-PuHuiTi-Heavy.ttf


BIN
Fonts/Alibaba-PuHuiTi-Light.ttf


BIN
Fonts/Alibaba-PuHuiTi-Medium.ttf


BIN
Fonts/Alibaba-PuHuiTi-Regular.ttf


Неке датотеке нису приказане због велике количине промена