From 771a0e7b6b1276087515bbe94de28358cb92963b Mon Sep 17 00:00:00 2001
From: alielie <alielie@git.com>
Date: 星期四, 04 六月 2026 10:32:27 +0800
Subject: [PATCH] 优化老化;

---
 Plugin.BurnIn/Util/DpUtil.cs                     |  220 +
 Plugin.DataBoard/Plugin.DataBoard.csproj         |   64 
 Plugin.DataBoard/packages.config                 |   22 
 WPFProject/BuildCmd.bat                          |   30 
 Plugin.BurnIn/Util/FontSizeConverter.cs          |   32 
 Plugin.TestResult/DataTable.xaml                 |    1 
 Plugin.QualityReport/AssemblyInfo.cs             |   13 
 Plugin.TsSupport/TsExecutionWrapper.cs           |   26 
 HMI.Core/Util/MSMQHelper.cs                      |    4 
 Plugin.BurnIn/ChartWindow.xaml.cs                |   31 
 Plugin.TestResult/TestResultView.xaml.cs         |  143 
 Plugin.TsExecution/Plugin.TsExecution.csproj     |    8 
 Plugin.BurnIn/Resource/ok2.png                   |    0 
 Plugin.BurnIn/Resource/ng2.png                   |    0 
 Plugin.TestResult/ViewControl.xaml.cs            |  127 
 WPFProject/App.config                            |    3 
 Plugin.TsExecution/TsExecution.xaml.cs           |  574 +
 Plugin.QualityReport/QualityReport.xaml          |  249 +
 Plugin.BurnIn/UI/LiveChart.xaml                  |   29 
 Plugin.BurnIn/Resource/checkIn.png               |    0 
 Plugin.BurnIn/Resource/waiting2.png              |    0 
 HMI.Core/Util/Sqlite3Helper.cs                   |    2 
 HMI.UI/DpUtil/UniformGridHelper.cs               |   18 
 Plugin.BurnIn/Util/SNValidator.cs                |   86 
 HMI.Core/Setting/ParamSetting.cs                 |   51 
 Plugin.BurnIn/InfoWindow.xaml                    |  117 
 Plugin.BurnIn/Model/ScannerConfig.cs             |   34 
 Plugin.TsExecution/ScreenTouch.xaml.cs           |   21 
 Plugin.BurnIn/Util/PNUtil.cs                     |  106 
 Plugin.BurnIn/BurnInUI.xaml                      |  159 
 Plugin.DataBoard/DataBoard.xaml.cs               |  468 ++
 S7Driver/PLCS7Driver.cs                          |  166 
 Plugin.BurnIn/TestResultView.xaml                |   21 
 S7Driver/Properties/AssemblyInfo.cs              |    4 
 Plugin.BurnIn/InfoTable.xaml.cs                  |  203 +
 Plugin.TsSupport/ITestExecutionWrapper.cs        |    3 
 SimpleUtil/HMI.TapApi/Properties/AssemblyInfo.cs |   33 
 Plugin.TsExecution/SingleControl.xaml            |    3 
 WPFProject/WPFProject.sln                        |   99 
 HMI.Core/Entity/SortParam.cs                     |   18 
 Plugin.BurnIn/Scanner.xaml                       |   78 
 SimpleUtil/HMI.Simple.Util/AlarmUtil.cs          |   27 
 HMI.Util/ExcelHelper.cs                          |   30 
 Plugin.BurnIn/Util/CommonCodeParser.cs           |   28 
 Plugin.TestResult/Properties/AssemblyInfo.cs     |    4 
 StationGlobalsReader/MainWindow.xaml             |   29 
 HMI.Util/Alarm/AlarmUtil.cs                      |   34 
 Plugin.BurnIn/BurnInUI.xaml.cs                   |  494 ++
 Plugin.DataBoard/app.config                      |   11 
 Plugin.QualityReport/Libs/Static/重复性模板.xls       |    0 
 Plugin.TsExecution/Properties/AssemblyInfo.cs    |   14 
 Plugin.BurnIn/Model/CellInfo.cs                  |   17 
 Plugin.BurnIn/Model/BaseModel.cs                 |   21 
 Plugin.BurnIn/Model/TcpServer.cs                 |   59 
 Plugin.BurnIn/AssemblyInfo.cs                    |   12 
 Plugin.TestResult/SingleCell.xaml.cs             |   40 
 Plugin.TestResult/DataTable.xaml.cs              |   86 
 WPFProject/ResourceListener.cs                   |   14 
 Plugin.BurnIn/SingleCell.xaml                    |  146 
 Plugin.BurnIn/ChartWindow.xaml                   |   23 
 Plugin.BurnIn/CoolerStatus.xaml.cs               |   93 
 Plugin.BurnIn/SingleCell.xaml.cs                 | 1174 ++++++
 HMI.ParameterManage/HMI.ParameterManage.csproj   |    4 
 Plugin.BurnIn/Plugin.BurnIn.csproj               |   70 
 Plugin.BurnIn/InfoWindow.xaml.cs                 |  176 
 Plugin.BurnIn/UI/LiveChart.xaml.cs               |  516 ++
 Plugin.BurnIn/DataTable.xaml.cs                  |  887 ++++
 Plugin.BurnIn/Util/ScannerParser.cs              |   30 
 Plugin.QualityReport/Libs/Static/MSA模板.xlsx      |    0 
 StationGlobalsReader/StationGlobalsReader.csproj |   28 
 Plugin.BurnIn/Model/Info.cs                      |   69 
 Plugin.BurnIn/ScannerSetting.xaml.cs             |  147 
 Plugin.BurnIn/ManualDebugWindow.xaml             |   21 
 Plugin.TsSupport/Plugin.TsSupport.csproj         |    6 
 SimpleUtil/HMI.TapApi/ProductUtil.cs             |  164 
 Plugin.BurnIn/Model/DataTableModel.cs            |    7 
 HMI.ParameterManage/Model/XmlModel.cs            |   16 
 Plugin.DataBoard/DataBoard.xaml                  |   49 
 Plugin.TestResult/Scanner.xaml.cs                |   57 
 Plugin.BurnIn/ScannerSetting.xaml                |   66 
 Plugin.TsExecution/ScreenTouch.xaml              |    4 
 StationGlobalsReader/AssemblyInfo.cs             |   10 
 Plugin.BurnIn/DataTable.xaml                     |  164 
 Plugin.BurnIn/Constant/CommonConstant.cs         |   70 
 Plugin.BurnIn/ManualDebugWindow.xaml.cs          |   93 
 Plugin.BurnIn/TestResultView.xaml.cs             |  683 +++
 Plugin.BurnIn/Resource/checkInFailed2.png        |    0 
 Plugin.BurnIn/CoolerStatus.xaml                  |   25 
 Plugin.QualityReport/Plugin.QualityReport.csproj |   46 
 Plugin.BurnIn/Util/ModelCodeParser.cs            |   97 
 Plugin.BurnIn/Resource/barcode.png               |    0 
 Plugin.BurnIn/Resource/ready.png                 |    0 
 HMI.ParameterManage/SettingModal.xaml.cs         |   10 
 HMI.Core/HMI.Core.csproj                         |    1 
 Plugin.BurnIn/Util/JKCodeParser.cs               |   30 
 Plugin.BurnIn/Resource/testing2.png              |    0 
 WPFProject/WPFProject.csproj                     |   29 
 StationGlobalsReader/App.xaml.cs                 |   13 
 Plugin.BurnIn/Util/RunTimeVariablePool.cs        |   11 
 Plugin.BurnIn/Cooler.xaml.cs                     |  206 +
 Plugin.TsExecution/SingleControl.xaml.cs         |  136 
 StationGlobalsReader/MainWindow.xaml.cs          |   50 
 Plugin.BurnIn/Scanner.xaml.cs                    |  465 ++
 Plugin.TsSupport/TeststandWrapper.cs             |   16 
 SimpleUtil/HMI.Simple.Util/MSMQHelper.cs         |   21 
 StationGlobalsReader/App.xaml                    |    9 
 SimpleUtil/HMI.TapApi/HMI.TapApi.csproj          |   74 
 Plugin.BurnIn/Util/ICodeParser.cs                |   10 
 Plugin.TsSupport/TapWrapper.cs                   |  238 +
 HMI.Util/HMI.Util.csproj                         |    4 
 Plugin.BurnIn/Resource/abort.png                 |    0 
 /dev/null                                        |    1 
 Plugin.BurnIn/Model/BaseConfig.cs                |  198 +
 Plugin.BurnIn/InfoTable.xaml                     |   92 
 Plugin.DataBoard/SimpleData.cs                   |    7 
 Plugin.QualityReport/Libs/Static/CMK模板.xls       |    0 
 Plugin.BurnIn/Cooler.xaml                        |   77 
 Plugin.BurnIn/Resource/alert.png                 |    0 
 Plugin.QualityReport/QualityReport.xaml.cs       |  705 +++
 119 files changed, 10,909 insertions(+), 621 deletions(-)

diff --git a/HMI.Core/Entity/SortParam.cs b/HMI.Core/Entity/SortParam.cs
new file mode 100644
index 0000000..ddba1dc
--- /dev/null
+++ b/HMI.Core/Entity/SortParam.cs
@@ -0,0 +1,18 @@
+锘縰sing System.ComponentModel;
+
+namespace HMI.Core.Entity
+{
+    public class SortParam
+    {
+        public string field { get; set; }
+        public ListSortDirection direction { get; set; }
+        public string sort { get; set; }
+    }
+
+    public class PageParam
+    {
+        public int limit { get; set; }
+        public int offset { get; set; }
+
+    }
+}
diff --git a/HMI.Core/HMI.Core.csproj b/HMI.Core/HMI.Core.csproj
index 3a0a3b2..8fc307a 100644
--- a/HMI.Core/HMI.Core.csproj
+++ b/HMI.Core/HMI.Core.csproj
@@ -118,6 +118,7 @@
     <Compile Include="Entity\PermissionData.cs" />
     <Compile Include="Entity\ProductData.cs" />
     <Compile Include="Entity\SelectBase.cs" />
+    <Compile Include="Entity\SortParam.cs" />
     <Compile Include="Entity\TestItemData.cs" />
     <Compile Include="Entity\UserInfo.cs" />
     <Compile Include="Event\TabChange.cs" />
diff --git a/HMI.Core/Setting/ParamSetting.cs b/HMI.Core/Setting/ParamSetting.cs
index e01cc33..f14a912 100644
--- a/HMI.Core/Setting/ParamSetting.cs
+++ b/HMI.Core/Setting/ParamSetting.cs
@@ -1,17 +1,38 @@
 锘縰sing HMI.Core.Manager;
 using HMI.Core.Util;
+using System.Collections;
+using System.Configuration;
 using System.IO;
+using System.Runtime.CompilerServices;
 
 namespace HMI.Core.Setting
 {
     public class ParamSetting
     {
+        private static Hashtable customConfig = (Hashtable)ConfigurationManager.GetSection("TestResultConfig");
+
+        private static string GetValue([CallerMemberName] string propertyName = null)
+        {
+            return (customConfig != null && customConfig.ContainsKey(propertyName)) ? customConfig[propertyName].ToString() : null;
+        }
+
         /// <summary>
         /// MesAddress
         /// </summary>
         public static string MesAddress { get; private set; } = "127.0.0.1:8080";
+        /// <summary>
+        /// 鑰佸寲瀵煎嚭琛岃浆鍒�
+        /// </summary>
         public static bool ReverseCSV { get; private set; } = false;
+
+        /// <summary>
+        /// 瀹㈡埛缂栫爜
+        /// </summary>
         public static string CustomerCode { get; private set; }
+
+        /// <summary>
+        /// 鐢熶骇鏁版嵁DB瀛樻斁鐩綍
+        /// </summary>
 
         public static string DataLogFolder { get; private set; } = null;
 
@@ -28,15 +49,32 @@
         public static bool ProbeByModel { get; private set; } = false;
         public static int ReverseCSVSkipRow { get; private set; } = 3;
 
+        /// <summary>
+        /// 鎿嶄綔鏄惁闇�瑕佸瘑鐮�
+        /// </summary>
+
         public static bool IsOptRequirePassword { get; private set; } = false;
-        
+
         /// <summary>
         /// 鍚敤鎵爜鎹㈠瀷
         /// </summary>
         public static bool EnableAutoModel { get; private set; } = false;
+
+        public const string ShareConfigKey = "ShareConfig";
         public static string ShareConfig { get; private set; } = null;
         public static bool ShowBurnInAbort { get; private set; } = true;
         public static string SocketNames { get; private set; } = string.Empty;
+        public static string StationName { get; private set; } = string.Empty;
+
+
+        public static string EngineType
+        {
+            get
+            {
+                var data = GetValue();
+                return data == null ? "TestStand" : data.ToString();
+            }
+        }
 
         static ParamSetting()
         {
@@ -154,7 +192,7 @@
 
             try
             {
-                ShareConfig = ParameterManager.GetCommonParameterByName<string>("ShareConfig", null);
+                ShareConfig = ParameterManager.GetCommonParameterByName<string>(ShareConfigKey, null);
             }
             catch
             {
@@ -178,6 +216,15 @@
             {
 
             }
+
+            try
+            {
+                StationName = ParameterManager.GetCommonParameterByName<string>("StationName", string.Empty);
+            }
+            catch
+            {
+
+            }
         }
     }
 }
diff --git a/HMI.Core/Util/MSMQHelper.cs b/HMI.Core/Util/MSMQHelper.cs
index 41dcf48..efa87d5 100644
--- a/HMI.Core/Util/MSMQHelper.cs
+++ b/HMI.Core/Util/MSMQHelper.cs
@@ -13,7 +13,7 @@
     /// <summary>
     /// 娑堟伅闃熷垪鎿嶄綔绫�
     /// </summary>
-    public class MSMQHelper
+    public class MSMQHelperCCC
     {
         /// <summary>
         /// 娑堟伅闃熷垪鍚嶇О
@@ -34,7 +34,7 @@
         /// 鏋勯�犲嚱鏁�
         /// </summary>
         /// <param name="Name">娑堟伅闃熷垪鍚嶇О</param>
-        public MSMQHelper(string Name)
+        public MSMQHelperCCC(string Name)
         {
             try
             {
diff --git a/HMI.Core/Util/Sqlite3Helper.cs b/HMI.Core/Util/Sqlite3Helper.cs
index 57fe091..e271025 100644
--- a/HMI.Core/Util/Sqlite3Helper.cs
+++ b/HMI.Core/Util/Sqlite3Helper.cs
@@ -11,6 +11,8 @@
 
         private string ConnectString = string.Empty;
 
+        public const int MAX_ATTACHED = 30;
+
         private SQLiteConnection conn = null;
 
         public Sqlite3Helper(string DBFile)
diff --git a/HMI.ParameterManage/HMI.ParameterManage.csproj b/HMI.ParameterManage/HMI.ParameterManage.csproj
index c5043b6..dc481a8 100644
--- a/HMI.ParameterManage/HMI.ParameterManage.csproj
+++ b/HMI.ParameterManage/HMI.ParameterManage.csproj
@@ -172,6 +172,10 @@
       <Project>{afea100a-9223-448d-ab10-3015d93dd98c}</Project>
       <Name>HMI.Util</Name>
     </ProjectReference>
+    <ProjectReference Include="..\SimpleUtil\HMI.LogUtil\HMI.LogUtil.csproj">
+      <Project>{6d0aab08-0f3e-48ce-b755-9d29bc8f1225}</Project>
+      <Name>HMI.LogUtil</Name>
+    </ProjectReference>
   </ItemGroup>
   <ItemGroup>
     <Folder Include="Entity\" />
diff --git a/HMI.ParameterManage/Model/XmlModel.cs b/HMI.ParameterManage/Model/XmlModel.cs
index 92210e2..c04e4ad 100644
--- a/HMI.ParameterManage/Model/XmlModel.cs
+++ b/HMI.ParameterManage/Model/XmlModel.cs
@@ -1,10 +1,12 @@
 锘縰sing HMI.Core.Entity;
 using HMI.Core.Manager;
 using HMI.Core.Util;
+using HMI.LogUtil;
 using System.Collections.Generic;
 using System.Collections.ObjectModel;
 using System.IO;
 using System.Linq;
+using System.Text.RegularExpressions;
 
 namespace HMI.ParameterManage.Model
 {
@@ -71,8 +73,16 @@
 
         public bool ValidateParameterName(object value, out string msg)
         {
+            string pattern = @"^[\p{L}_][\p{L}\p{N}_]*$";
             msg = null;
             string name = value.ToString();
+
+            if (!Regex.IsMatch(name, pattern))
+            {
+                msg = "鍙傛暟鍚嶄笉鑳戒互鏁板瓧寮�澶�";
+                return false;
+            }
+
             if (Products == null) return true;
             var temp = Parameters.FirstOrDefault(p => p.Name == name);
             if (temp != null && temp != SelectedParameter)
@@ -125,6 +135,7 @@
             else
             {
                 InsertParameter(data);
+                LogHelper.Info("HMI", $"[{data.ProductName}] Add New Parameter {data.Name}. Value: {data.Value}", out _);
             }
         }
 
@@ -215,8 +226,13 @@
         public void UpdateParameter(int index, ParameterData data)
         {
             var temp = XmlHelper.DeepClone(Parameters);
+            var old = temp[index];
             temp[index] = data;
             SaveParameter(temp);
+            if (old.Value != data.Value)
+            {
+                LogHelper.Info("HMI", $"Update Parameter {data.Name}. Value: {old.Value} => {data.Value}", out _);
+            }
         }
 
         /// <summary>
diff --git a/HMI.ParameterManage/SettingModal.xaml.cs b/HMI.ParameterManage/SettingModal.xaml.cs
index 22569d6..c485bf6 100644
--- a/HMI.ParameterManage/SettingModal.xaml.cs
+++ b/HMI.ParameterManage/SettingModal.xaml.cs
@@ -11,7 +11,9 @@
 using System.Collections.ObjectModel;
 using System.ComponentModel;
 using System.Linq;
+using System.Text.RegularExpressions;
 using System.Windows.Controls;
+using System.Windows.Interop;
 
 namespace HMI.ParameterManage
 {
@@ -116,6 +118,14 @@
         public bool ValidateName(object value, out string err)
         {
             err = null;
+
+            string pattern = @"^[\p{L}_][\p{L}\p{N}_]*$";
+            if (!Regex.IsMatch(value.ToString(), pattern))
+            {
+                err = "鍙傛暟鍚嶄笉鑳戒互鏁板瓧寮�澶�";
+                return false;
+            }
+
             var data = Datasource?.FirstOrDefault(e => e.Name == value.ToString());
             if (data != null && data != Data)
             {
diff --git a/HMI.UI/DpUtil/UniformGridHelper.cs b/HMI.UI/DpUtil/UniformGridHelper.cs
index 6f98327..1b12a2b 100644
--- a/HMI.UI/DpUtil/UniformGridHelper.cs
+++ b/HMI.UI/DpUtil/UniformGridHelper.cs
@@ -108,6 +108,24 @@
                     control.BorderThickness = bt;
                 }
 
+                if (child is Border border)
+                {
+
+                    int col = i % columnCount;
+                    int row = i / columnCount;
+
+                    double left = (col == 0 && ignoreLeft) ? 0 : width;
+                    double top = (row == 0 && ignoreTop) ? 0 : width;
+
+                    double right = ignoreRight ? 0 : ((col == columnCount - 1) ? width : 0);
+                    double bottom = ignoreBottom ? 0 : ((row == rowCount - 1) ? width : 0);
+
+                    var bt = new Thickness(left, top, right, bottom);
+
+                    border.BorderBrush = brush;
+                    border.BorderThickness = bt;
+                }
+
             }
         }
 
diff --git a/HMI.Util/Alarm/AlarmUtil.cs b/HMI.Util/Alarm/AlarmUtil.cs
index 1044e1e..ea9988f 100644
--- a/HMI.Util/Alarm/AlarmUtil.cs
+++ b/HMI.Util/Alarm/AlarmUtil.cs
@@ -2,6 +2,7 @@
 using HMI.Core.Entity;
 using HMI.Core.Setting;
 using HMI.Core.Util;
+using HMI.Simple.Util;
 using log4net;
 using S7Driver;
 using System;
@@ -192,7 +193,7 @@
 
         private void RefershAlarm()
         {
-            alarmRefershToMain.Send("AlarmRefersh", string.Empty);
+            alarmRefershToMain.Send("AlarmRefersh", string.Empty, string.Empty, 0);
             List<AlarmText> alarms = new List<AlarmText>();
 
             foreach (AlarmText alarm in currentPLCAlarmCache)
@@ -223,8 +224,8 @@
             try
             {
                 mqTimer.Enabled = false;
-                alarmQueue.Receive(100, out string MessageID, out string MsgStr);
-                if (MessageID != string.Empty)
+                alarmQueue.Receive(100, out string MessageID, out MQAlarm alarm);
+                if (MessageID != string.Empty && alarm != null)
                 {
                     switch (MessageID)
                     {
@@ -251,28 +252,19 @@
                             //}
                             break;
                         default:
-                            string[] arr = MsgStr.Split(',');
-                            string source = "PC";
-                            string info = string.Empty;
-                            if (arr.Length > 1)
-                            {
-                                source = arr[0];
-                                info = arr[1];
-                            }
-                            else
-                            {
-                                info = MsgStr;
-                            }
+                            string source = string.IsNullOrEmpty(alarm.Source) ? "PC" : alarm.Source;
+                            string info = alarm.Content;
+
                             AlarmText newAlarm = new AlarmText
                             {
                                 Source = source,
                                 VirtualId = $"{source}:{info}",
                                 AlarmInfo = info,
-                                Level = 3,
+                                Level = alarm.Level,
                                 Time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")
                             };
                             if (currentMQAlarmCache
-                                .Find(alarm => alarm.Source == newAlarm.Source && alarm.AlarmInfo == newAlarm.AlarmInfo) == null)
+                                .Find(a => a.Source == newAlarm.Source && a.AlarmInfo == newAlarm.AlarmInfo) == null)
                             {
                                 currentMQAlarmCache.Add(newAlarm);
                                 RefershAlarm();
@@ -297,7 +289,7 @@
             BlockReadCache.Clear();
             try
             {
-                BlockSettings = XmlHelper.DeserializeFromXml<ObservableCollection<AlarmSetting>>(blockFilePath);
+                BlockSettings = HMI.Core.Util.XmlHelper.DeserializeFromXml<ObservableCollection<AlarmSetting>>(blockFilePath);
             }
             catch (Exception ex)
             {
@@ -321,7 +313,7 @@
         {
             try
             {
-                MarkSettings = XmlHelper.DeserializeFromXml<ObservableCollection<AlarmSetting>>(markFilePath);
+                MarkSettings = HMI.Core.Util.XmlHelper.DeserializeFromXml<ObservableCollection<AlarmSetting>>(markFilePath);
             }
             catch (Exception ex)
             {
@@ -338,13 +330,13 @@
 
         public void UpdateBlockSettings(IEnumerable<AlarmSetting> settings)
         {
-            XmlHelper.SerializeToXml(blockFilePath, settings);
+            HMI.Core.Util.XmlHelper.SerializeToXml(blockFilePath, settings);
             RefreshBlockSettings();
         }
 
         public void UpdateMarkSettings(IEnumerable<AlarmSetting> settings)
         {
-            XmlHelper.SerializeToXml(markFilePath, settings);
+            HMI.Core.Util.XmlHelper.SerializeToXml(markFilePath, settings);
             RefreshMarkSettings();
         }
     }
diff --git a/HMI.Util/ExcelHelper.cs b/HMI.Util/ExcelHelper.cs
index 0708a5c..801b657 100644
--- a/HMI.Util/ExcelHelper.cs
+++ b/HMI.Util/ExcelHelper.cs
@@ -104,6 +104,36 @@
             }
         }
 
+        public static void ExportExcel(string targetPath, List<string[]> rows)
+        {
+            IWorkbook workbook = null;
+            if (targetPath.EndsWith(".xlsx"))
+            {
+                workbook = new XSSFWorkbook();
+            }
+            else if (targetPath.EndsWith(".xls"))
+            {
+                workbook = new HSSFWorkbook();
+            }
+            workbook.CreateSheet("sheet1");////鏂板缓涓�涓伐浣滆〃
+            ISheet SheetOne = workbook.GetSheet("sheet1");
+
+
+            for (int i = 0; i < rows.Count; i++)
+            {
+                IRow rowTemp = SheetOne.CreateRow(i);
+                for (int j = 0; j < rows[i].Length; j++)
+                {
+                    rowTemp.CreateCell(j).SetCellValue(rows[i][j]);
+                }
+            }
+
+            using (FileStream fs = new FileStream(targetPath, FileMode.OpenOrCreate))
+            {
+                workbook.Write(fs);
+            }
+        }
+
 
         public static List<T> ReadFromExcel<T>(string sourcePath)
         {
diff --git a/HMI.Util/HMI.Util.csproj b/HMI.Util/HMI.Util.csproj
index fc1f1c8..1404835 100644
--- a/HMI.Util/HMI.Util.csproj
+++ b/HMI.Util/HMI.Util.csproj
@@ -99,6 +99,10 @@
       <Project>{628ef12b-9ed2-4b20-950f-dc3ecc3fd15f}</Project>
       <Name>S7Driver</Name>
     </ProjectReference>
+    <ProjectReference Include="..\SimpleUtil\HMI.Simple.Util\HMI.Simple.Util.csproj">
+      <Project>{CF5BEBE9-2803-49C3-B773-73D082E0984A}</Project>
+      <Name>HMI.Simple.Util</Name>
+    </ProjectReference>
   </ItemGroup>
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
 </Project>
\ No newline at end of file
diff --git a/Plugin.BurnIn/AssemblyInfo.cs b/Plugin.BurnIn/AssemblyInfo.cs
new file mode 100644
index 0000000..ba840b1
--- /dev/null
+++ b/Plugin.BurnIn/AssemblyInfo.cs
@@ -0,0 +1,12 @@
+using HMI.Core.Annotation;
+using System.Windows;
+
+[assembly:ThemeInfo(
+    ResourceDictionaryLocation.None,            //where theme specific resource dictionaries are located
+                                                //(used if a resource is not found in the page,
+                                                // or application resource dictionaries)
+    ResourceDictionaryLocation.SourceAssembly   //where the generic resource dictionary is located
+                                                //(used if a resource is not found in the page,
+                                                // app, or any theme specific resource dictionaries)
+)]
+[assembly: PluginClassName("Plugin.BurnIn.BurnInUI")]
diff --git a/Plugin.BurnIn/BurnInUI.xaml b/Plugin.BurnIn/BurnInUI.xaml
new file mode 100644
index 0000000..040a05e
--- /dev/null
+++ b/Plugin.BurnIn/BurnInUI.xaml
@@ -0,0 +1,159 @@
+锘�<hb:PluginMaskControl x:Class="Plugin.BurnIn.BurnInUI"
+                      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+                      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+                      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+                      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+                      xmlns:hb="clr-namespace:HMI.Base;assembly=HMI.Base"
+                      xmlns:std="clr-namespace:HMI.StdControl;assembly=HMI.StdControl"
+                      xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI"
+                      xmlns:local="clr-namespace:Plugin.BurnIn"
+                      xmlns:pu="https://opensource.panuon.com/wpf-ui"
+                      mc:Ignorable="d" 
+                      Style="{StaticResource pluginMaskControl}"
+             Loaded="UserControl_Loaded"
+             d:DesignHeight="450" d:DesignWidth="800">
+    <UserControl.Resources>
+        <local:FontSizeConverter x:Key="FontSizeConverter"/>
+        <Style TargetType="GroupBox">
+            <Setter Property="FontSize" Value="{Binding ActualWidth,RelativeSource={RelativeSource AncestorType=UserControl}, 
+Converter={StaticResource FontSizeConverter}, ConverterParameter='20,1920'}" />
+        </Style>
+
+        <Style TargetType="Button">
+            <Setter Property="Margin" Value="0,10"/>
+            <Setter Property="Padding" Value="5"/>
+        </Style>
+
+        <Style TargetType="Label">
+            <Setter Property="BorderThickness" Value="1"/>
+            <Setter Property="BorderBrush" Value="LightGray"/>
+            <Setter Property="Margin" Value="0,10"/>
+            <Setter Property="Padding" Value="5"/>
+        </Style>
+    </UserControl.Resources>
+    <Grid>
+        <Grid.RowDefinitions>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition/>
+        </Grid.RowDefinitions>
+        <!--<Grid.ColumnDefinitions>
+            <ColumnDefinition Width="7.5*"/>
+            <ColumnDefinition Width="*"/>
+        </Grid.ColumnDefinitions>-->
+        <local:TestResultView x:Name="testResultView" Grid.Row="1" Grid.Column="0" Loaded="testResultView_Loaded" />
+        <DockPanel Margin="10,0" Grid.Row="0" Grid.Column="1" LastChildFill="False" Background="#FFF" VerticalAlignment="Center">
+            <pu:FormGroup DockPanel.Dock="Right">
+                <StackPanel Orientation="Horizontal" VerticalAlignment="Center" dp:StackPanelHelper.ChildSpace="20">
+                    <Button x:Name="btnStart" Click="Start_Click"
+                       Width="100" FontSize="14" Padding="0" Height="30" Content="{DynamicResource App.Start}">
+                        <Button.Style>
+                            <Style TargetType="Button" BasedOn="{StaticResource SuccessButton}">
+                                <Style.Triggers>
+                                    <DataTrigger Binding="{Binding EnableManualDebug}" Value="True">
+                                        <Setter Property="IsEnabled" Value="False"/>
+                                    </DataTrigger>
+                                    <DataTrigger Binding="{Binding IsRunning}" Value="True">
+                                        <Setter Property="IsEnabled" Value="False"/>
+                                    </DataTrigger>
+                                </Style.Triggers>
+                            </Style>
+                        </Button.Style>
+                    </Button>
+
+                    <Button x:Name="btnStop" Click="Stop_Click" Style="{StaticResource DangerButton}"
+                            Width="100" FontSize="14" Padding="0" Height="30"
+                    Content="{DynamicResource App.Stop}"/>
+
+                    <Button x:Name="btnClear" Click="BtnClear_Click" Style="{StaticResource PrimaryButton}"
+                            Width="100" FontSize="14" Padding="0" Height="30"
+                    Content="{DynamicResource Ts.BtnClearNum}"/>
+
+                    <Button x:Name="scannerSetting" Click="ScannerSetting_Click" Style="{StaticResource PrimaryButton}"
+                            Width="100" FontSize="14" Padding="0" Height="30" Content="{DynamicResource App.ScannerSetting}" Cursor="Hand"/>
+
+                    <!--<Button x:Name="btnScan1" Click="Scan_Click_1" Style="{StaticResource PrimaryButton}"
+                    Content="{DynamicResource Tr.Zone1Scan}"/>
+
+            <Button x:Name="btnScan2" Click="Scan_Click_2" Style="{StaticResource PrimaryButton}"
+                    Content="{DynamicResource Tr.Zone2Scan}"/>-->
+                </StackPanel>
+            </pu:FormGroup>
+            <pu:FormGroup Header="{DynamicResource Tr.ProductInfo}" FontSize="16" Visibility="Collapsed">
+                <ComboBox x:Name="modelSelect" Style="{StaticResource FormComboxStyle}" Width="200"
+                          Height="28"
+                          pu:ComboBoxHelper.ClearButtonVisibility="Collapsed"
+                          SelectionChanged="modelSelect_SelectionChanged"/>
+            </pu:FormGroup>
+
+            <pu:FormGroup Header="{DynamicResource Tr.TestProgram}" FontSize="16">
+                <TextBox IsReadOnly="True" x:Name="tbFileName" Style="{StaticResource EditorTextBoxStyle}" Width="200"
+Height="28" Text="{Binding FileName}"/>
+            </pu:FormGroup>
+
+            <pu:FormGroup>
+                <std:StdEnableMes DockPanel.Dock="Left" FontSize="20" 
+                                  x:Name="stdEnableMes"
+                                  ValueChanged="StdEnableMes_ValueChanged"/>
+            </pu:FormGroup>
+
+            <pu:FormGroup x:Name="FgEnableManualDebug" Visibility="Collapsed">
+                <CheckBox FontSize="20"
+                          IsChecked="{Binding EnableManualDebug,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" 
+          VerticalContentAlignment="Center"
+          Content="鎵嬪姩璋冭瘯" />
+            </pu:FormGroup>
+
+
+            <!--<pu:FormGroup Header="{DynamicResource Tr.Status}" FontSize="16">
+                <StackPanel Orientation="Horizontal" dp:StackPanelHelper.ChildSpace="10">
+                    <Label HorizontalContentAlignment="Center" VerticalAlignment="Center">
+                        <Label.Style>
+                            <Style TargetType="Label">
+                                <Setter Property="HorizontalContentAlignment"  Value="Center"/>
+                                <Setter Property="HorizontalAlignment"  Value="Stretch"/>
+                                <Setter Property="Margin" Value="0,10"/>
+                                <Style.Triggers>
+                                    <DataTrigger Binding="{Binding mesStatus}" Value="true">
+                                        <Setter Property="Background" Value="Green"/>
+                                        <Setter Property="Content" Value="{DynamicResource Tr.MES_OPEN}"/>
+                                        <Setter Property="Foreground" Value="White"/>
+                                    </DataTrigger>
+                                    <DataTrigger Binding="{Binding mesStatus}" Value="false">
+                                        <Setter Property="Background" Value="Red"/>
+                                        <Setter Property="Content" Value="{DynamicResource Tr.MES_CLOSE}"/>
+                                        <Setter Property="Foreground" Value="White"/>
+                                    </DataTrigger>
+                                </Style.Triggers>
+                            </Style>
+                        </Label.Style>
+                    </Label>
+                    <Label>
+                        <Label.Style>
+                            <Style TargetType="Label">
+                                <Setter Property="HorizontalContentAlignment"  Value="Center"/>
+                                <Setter Property="HorizontalAlignment"  Value="Stretch"/>
+                                <Setter Property="VerticalAlignment"  Value="Center"/>
+                                <Setter Property="Content" Value="{Binding produceStatus}"/>
+                                <Setter Property="Margin" Value="0,10"/>
+                                <Style.Triggers>
+                                    <DataTrigger Binding="{Binding produceStatus}" Value="INIT">
+                                        <Setter Property="Background" Value="Gray"/>
+                                        <Setter Property="Foreground" Value="White"/>
+                                    </DataTrigger>
+                                    <DataTrigger Binding="{Binding produceStatus}" Value="STOP">
+                                        <Setter Property="Background" Value="Red"/>
+                                        <Setter Property="Foreground" Value="White"/>
+                                    </DataTrigger>
+                                    <DataTrigger Binding="{Binding produceStatus}" Value="RUNNING">
+                                        <Setter Property="Background" Value="Green"/>
+                                        <Setter Property="Foreground" Value="White"/>
+                                    </DataTrigger>
+                                </Style.Triggers>
+                            </Style>
+                        </Label.Style>
+                    </Label>
+                </StackPanel>
+            </pu:FormGroup>-->
+        </DockPanel>
+    </Grid>
+</hb:PluginMaskControl>
diff --git a/Plugin.BurnIn/BurnInUI.xaml.cs b/Plugin.BurnIn/BurnInUI.xaml.cs
new file mode 100644
index 0000000..7c372f1
--- /dev/null
+++ b/Plugin.BurnIn/BurnInUI.xaml.cs
@@ -0,0 +1,494 @@
+锘縰sing HMI.Base;
+using HMI.Core.Api;
+using HMI.Core.Entity;
+using HMI.Core.Manager;
+using HMI.Core.Setting;
+using HMI.Core.Util;
+using HMI.LogUtil;
+using HMI.StdControl;
+using HMI.UI.Util;
+using log4net;
+using NationalInstruments.TestStand.Interop.API;
+using PLCHelper;
+using Plugin.BurnIn.Util;
+using Plugin.TsSupport;
+using System.ComponentModel;
+using System.IO;
+using System.Windows;
+using System.Windows.Controls;
+
+namespace Plugin.BurnIn
+{
+    /// <summary>
+    /// ViewControl.xaml 鐨勪氦浜掗�昏緫
+    /// </summary>
+    public partial class BurnInUI : PluginMaskControl, IAlarmHandler
+    {
+
+        private System.Timers.Timer timer;
+
+        public static ILog Logger { get; private set; }
+
+
+        private bool _mesStatus;
+
+        public bool mesStatus
+        {
+            get => _mesStatus;
+            set => Set(ref _mesStatus, value);
+        }
+
+        private string _produceStatus = "INIT";
+
+        public string produceStatus
+        {
+            get => _produceStatus;
+            set => Set(ref _produceStatus, value);
+        }
+
+        private string _fileName;
+
+        public string FileName
+        {
+            get => _fileName;
+            set => Set(ref _fileName, value);
+        }
+
+        private bool enableManualDebug;
+        public bool EnableManualDebug
+        {
+            get => enableManualDebug;
+            set
+            {
+                if (IsRunning)
+                {
+                    MsgBoxHelper2.Warn("璇峰厛缁撴潫鑷姩娴嬭瘯");
+                    return;
+                }
+                testResultView.inManualDebugMode = value;
+                Set(ref enableManualDebug, value);
+            }
+        }
+
+        public override void SetLogger(ILog logger)
+        {
+            base.SetLogger(logger);
+            Logger = logger;
+        }
+
+        private bool isRunning = false;
+        public bool IsRunning
+        {
+            get => isRunning;
+            set
+            {
+                Set(ref isRunning, value);
+                testResultView.IsRunning = value;
+                this.modelSelect.IsEnabled = !value;
+                this.btnStop.IsEnabled = value;
+                stdEnableMes.CbEnabled = !value;
+            }
+        }
+
+        private bool AutoExecuteSeq = true;
+
+        #region TS鐩稿叧
+
+        /// <summary>
+        /// 娴嬭瘯搴忓垪鎿嶄綔鍙ユ焺
+        /// </summary>
+        private SequenceContext SequenceContext = null;
+        #endregion
+
+        public BurnInUI()
+        {
+            S7Context.Init(SystemSetting.Instance.PlcIp, SystemSetting.Instance.PlcPort);
+            DataContext = this;
+            timer = new System.Timers.Timer();
+            timer.Interval = 1000;
+            timer.Elapsed += Timer_Elapsed;
+            InitializeComponent();
+
+
+            //testResultView.testApp.ExitApplication += new EventHandler(this.axApplicationMgr_ExitApplication);
+            //testResultView.testApp.UIMessageEvent += new _ApplicationMgrEvents_UIMessageEventEventHandler(this.axApplicationMgr_UIMessageEvent);
+            //testResultView.testApp.ExecutionClosed += new _ApplicationMgrEvents_ExecutionClosedEventHandler(this.axApplicationMgr_ExecutionClosed);
+        }
+
+        private void HandleUIMessage(int EventCase, int MethodCode, string strData, object xData)
+        {
+            try
+            {
+                try
+                {
+                    log.Debug($"EventCase: {EventCase}, MethodCode: {MethodCode}, StrData: {strData}");
+                }
+                catch
+                {
+
+                }
+
+                if (EventCase == 8)
+                {
+                    IsRunning = false;
+                    return;
+                }
+
+                if (EventCase != 10014) return;
+
+                if (MethodCode == 0)
+                {
+                    /// 璁剧疆鍙ユ焺
+                    this.SequenceContext = (SequenceContext)xData;
+                    return;
+                }
+
+
+                if (MethodCode == 8)
+                {
+                    /// 鏇存柊MES鐘舵��
+                    mesStatus = strData == "1";
+                    return;
+                }
+
+                if (MethodCode == 9)
+                {
+                    /// 鏇存柊鐢熶骇鐘舵��
+                    produceStatus = strData;
+                    return;
+                }
+            }
+            catch
+            { }
+        }
+
+        private void ExecutionWrapper_UIMessageEvent(object sender, UIMessageEvent e)
+        {
+            HandleUIMessage(e.EventCode, Convert.ToInt16(e.NumberData), e.StrData, e.Data);
+        }
+
+        private void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
+        {
+            testResultView.RefreshCooler();
+        }
+
+        public TestResultView View
+        {
+            get => testResultView;
+        }
+        public static string SannerIniFile { get; private set; }
+
+        public override void InitFinish()
+        {
+            base.InitFinish();
+            string path = Path.Combine(BaseFolder, "Count.ini");
+            testResultView.SetCountIniPath(path);
+            testResultView.InitMainGrid();
+
+            path = Path.Combine(BaseFolder, "Scanner.ini");
+            if (!File.Exists(path))
+            {
+                File.Create(path).Close();
+            }
+            SannerIniFile = path;
+            LoadProduct();
+            var api = S7Context.Get(RunTimeVariablePool.DEFAULT_PLC_NAME);
+            log.Debug($"PLC {api.IpAddress}:{api.Port}");
+            try
+            {
+                var value = ParameterManager.GetCommonParameterByName("AutoExecuteSeq");
+                AutoExecuteSeq = bool.Parse(value.Value);
+            }
+            catch
+            {
+                AutoExecuteSeq = true;
+            }
+
+            try
+            {
+                var value = ParameterManager.GetCommonParameterByName("BurnInMainSeq");
+                testResultView.testSeqFile = value.Value;
+                FileName = Path.GetFileName(value.Value);
+            }
+            catch
+            {
+                testResultView.testSeqFile = string.Empty;
+                FileName = string.Empty;
+            }
+            string type = Path.GetExtension(FileName);
+            bool isTap = string.Equals(".tapplan", type, StringComparison.OrdinalIgnoreCase);
+
+            testResultView.InitTestStandEngine(isTap);
+            testResultView.executionWrapper.UIMessageEvent += ExecutionWrapper_UIMessageEvent;
+
+            testResultView.SetOvenName();
+
+            RefreshUIConfig();
+        }
+
+        /// <summary>
+        /// 鍒锋柊UI閰嶇疆
+        /// </summary>
+        private void RefreshUIConfig()
+        {
+            try
+            {
+                /// 杩愯涓氨涓嶇
+                if (IsRunning)
+                {
+                    return;
+                }
+                bool enableManualDebug = ParameterManager.GetCommonParameterByName("EnableManualDebug", false);
+                if (enableManualDebug)
+                {
+                    FgEnableManualDebug.Visibility = Visibility.Visible;
+                }
+                else
+                {
+                    FgEnableManualDebug.Visibility = Visibility.Collapsed;
+                    EnableManualDebug = false;
+                }
+            }
+            catch (Exception ex)
+            {
+                log.Error("RefreshUIConfig Error.", ex);
+            }
+        }
+
+        private bool isLoaded;
+        private void UserControl_Loaded(object sender, RoutedEventArgs e)
+        {
+            AlarmShowHelper.AddHandler(this);
+            if (AutoExecuteSeq)
+            {
+                ShowMask(() =>
+                {
+                    Dispatcher.BeginInvoke(new Action(() =>
+                    {
+                        Start_Click(null, null);
+                    }), System.Windows.Threading.DispatcherPriority.Background);
+                }, (ex) =>
+                {
+                    ToastHelper.Error("Loaded Failed.");
+                    log.Error("Loaded Failed.", ex);
+                }, "璇风◢鍚�", LangUtil.Get("Ts.SequenceIsStarting"));
+            }
+            else
+            {
+                IsRunning = false;
+            }
+            Loaded -= UserControl_Loaded;
+        }
+
+        public void LoadProduct()
+        {
+            List<ProductData> products = ProductManager.GetProducts<List<ProductData>>()
+                ?? new List<ProductData>();
+            this.modelSelect.Items.Clear();
+            List<string> productSource = new List<string>();
+            foreach (var prd in products)
+            {
+                this.modelSelect.Items.Add(prd.Name);
+                productSource.Add(prd.Name);
+            }
+            testResultView.SetProductSource(productSource);
+            testResultView.SetCurrentProduct(null);
+
+            //string model = testResultView.executionWrapper.GetStationGlobalValue<string>("ProductModel", 1);
+            ////testResultView.engine.Globals.GetValString("ProductModel", 1);
+            //if (!string.IsNullOrEmpty(model))
+            //{
+            //    this.modelSelect.SelectedItem = model;
+            //    testResultView.SetCurrentProduct(model);
+            //}
+            //else if (products.Count > 0)
+            //{
+            //    this.modelSelect.SelectedIndex = 0;
+            //    testResultView.SetCurrentProduct(products[0].Name);
+            //}
+        }
+
+        private void modelSelect_SelectionChanged(object sender, SelectionChangedEventArgs e)
+        {
+            try
+            {
+                string currentModel = this.modelSelect.SelectedItem.ToString();
+                List<ParameterData> parameters = ProductManager.GetParametersByProductName(currentModel);
+
+                testResultView.executionWrapper.SetStationGlobalValue<string>("ProductModel", 0, currentModel);
+                testResultView.executionWrapper.CommitToDisk(false);
+
+                //testResultView.engine.Globals.SetValString("ProductModel", 0, currentModel);
+                //testResultView.engine.CommitGlobalsToDisk(promptOnSaveConflicts: false);
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show("鍒囨崲浜у搧鍨嬪彿澶辫触!" + ex.Message + ex.StackTrace);
+            }
+        }
+
+        private void Start_Click(object sender, RoutedEventArgs e)
+        {
+            try
+            {
+                if (string.IsNullOrEmpty(testResultView.testSeqFile))
+                {
+                    MsgBoxHelper2.Warn("璇峰厛璁剧疆BurnInMainSeq灞炴��!");
+                    IsRunning = false;
+                    return;
+                }
+                LogHelper.Info("UI", "鎬诲惎鍔ㄦ寜閽寜涓�", out _);
+                log.Info("鎬诲惎鍔ㄦ寜閽寜涓�");
+                ShowMask(() =>
+                {
+                    testResultView.StartTestSequence();
+                    Dispatcher.Invoke(() =>
+                    {
+                        IsRunning = true;
+                    });
+                }, (ex) =>
+                {
+                    log.Error("StartTestSequence Error.", ex);
+                    Dispatcher.Invoke(() =>
+                    {
+                        MsgBoxHelper.Error(Window.GetWindow(this), $"{this.lang("Ts.StartTestSequenceFailed")}: {ex.Message}");
+                    });
+                }, null, "璇风◢鍚�...");
+            }
+            catch (Exception ex)
+            {
+                log.Error("Start_Click Failed.", ex);
+                MsgBoxHelper.Error(Window.GetWindow(this),
+                    $"{this.lang("Ts.StartTestSequenceFailed")}: {ex.Message}");
+            }
+        }
+
+        private void Stop_Click(object sender, RoutedEventArgs e)
+        {
+            try
+            {
+                bool breakStop = false;
+                foreach (var oven in TestResultView.tables)
+                {
+                    if (oven.IsTesting)
+                    {
+                        breakStop = true;
+                        break;
+                    }
+                }
+                if (breakStop)
+                {
+                    MsgBoxHelper2.Warn(this.lang("Tr.OvenIsTesting"));
+                    return;
+                }
+
+                if (MsgBoxHelper2.WarnChoose(this.lang("Tr.MakeSureStopAllTest")) != MessageBoxResult.OK)
+                {
+                    return;
+                }
+
+                LogHelper.Info("UI", "鎬诲仠姝㈡寜閽寜涓�", out _);
+                log.Info("鎬诲仠姝㈡寜閽寜涓�");
+                testResultView.StopTest();
+                /// 宸茬粡鍔犱簡WaitForExit,杩欓噷鍙互鏀逛负false
+                IsRunning = false;
+            }
+            catch (Exception ex)
+            {
+                log.Error("Stop_Click Failed.", ex);
+                MsgBoxHelper.Error(Window.GetWindow(this),
+                    $"{this.lang("Ts.StopTestSequenceFailed")}: {ex.Message}");
+            }
+        }
+
+        private void testResultView_Loaded(object sender, RoutedEventArgs e)
+        {
+        }
+
+        /// <summary>
+        /// 璁℃暟娓呴浂鎸夐挳浜嬩欢
+        /// </summary>
+        /// <param name="sender"></param>
+        /// <param name="e"></param>
+        private void BtnClear_Click(object sender, RoutedEventArgs e)
+        {
+            testResultView.ClearNumber();
+        }
+
+        private void ScannerSetting_Click(object sender, RoutedEventArgs e)
+        {
+            ScannerSetting setting = new ScannerSetting()
+            {
+                Owner = Window.GetWindow(this)
+            };
+            setting.Open(false, "Config");
+        }
+
+        private void StdEnableMes_ValueChanged(object sender, StdUIEventArg e)
+        {
+            testResultView.executionWrapper.SetStationGlobalValue<bool>($"SystemSetting.{e.StringValue}", 1, e.BoolValue);
+            //testResultView.SequenceContext?.StationGlobals.SetValBoolean($"SystemSetting.{e.StringValue}", 1, e.BoolValue);
+        }
+
+        public void HandleAlarm(AlarmText alarm)
+        {
+            if (alarm.Level >= 4 && TestResultView.tables != null)
+            {
+                log.Info($"4绾ф姤璀﹁Е鍙�: [{alarm.AlarmInfo}],灏嗗仠姝㈡墍鏈夌倝浣撴祴璇�");
+                foreach (var oven in TestResultView.tables)
+                {
+                    testResultView.SetStartFlag(oven.index, false);
+                }
+                return;
+            }
+
+            DataTable targetOven = null;
+            foreach (var oven in TestResultView.tables)
+            {
+                if (alarm.AlarmInfo?.StartsWith(oven.title) == true)
+                {
+                    targetOven = oven;
+                    break;
+                }
+            }
+            if (targetOven == null)
+            {
+                return;
+            }
+            if (alarm.Level >= 3)
+            {
+                /// 3绾т互涓婄殑鎶ヨ浼氬仠姝㈡祴璇�
+                log.Info($"3绾ф姤璀﹁Е鍙�: [{alarm.AlarmInfo}],灏嗗仠姝㈣�佸寲鐐塠{targetOven.title}]娴嬭瘯");
+                testResultView.SetStartFlag(targetOven.index, false);
+            }
+        }
+
+        public override void Active()
+        {
+            base.Active();
+            LoadProduct();
+            RefreshUIConfig();
+        }
+
+        public override void OnClose(object sender, CancelEventArgs e)
+        {
+            if (IsRunning)
+            {
+                MsgBoxHelper2.Warn("璇峰厛鍋滄鑰佸寲娴嬭瘯锛�");
+                e.Cancel = true;
+            }
+        }
+
+        public override void DoClose(object sender, EventArgs e)
+        {
+            try
+            {
+                timer?.Stop();
+                testResultView.ReleaseResource();
+            }
+            catch
+            {
+            }
+        }
+    }
+}
diff --git a/Plugin.BurnIn/ChartWindow.xaml b/Plugin.BurnIn/ChartWindow.xaml
new file mode 100644
index 0000000..3e8c283
--- /dev/null
+++ b/Plugin.BurnIn/ChartWindow.xaml
@@ -0,0 +1,23 @@
+锘�<pu:WindowX x:Class="Plugin.BurnIn.ChartWindow"
+        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+        xmlns:local="clr-namespace:Plugin.BurnIn"
+        mc:Ignorable="d"
+        Style="{StaticResource ModalWindow}"
+        
+ xmlns:hb="clr-namespace:HMI.Base;assembly=HMI.Base"
+ xmlns:uic="clr-namespace:HMI.UI.Converter;assembly=HMI.UI"
+ xmlns:pw="https://opensource.panuon.com/wpf"
+ xmlns:pu="https://opensource.panuon.com/wpf-ui"
+ xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI"
+ xmlns:cc="clr-namespace:HMI.Core;assembly=HMI.Core"
+            InteropOwnersMask="False"
+        pu:WindowXModalDialog.Buttons="{x:Null}"
+        Unloaded="Window_Unloaded"
+        Title="{Binding title}" Height="450" Width="800">
+    <UniformGrid x:Name="mainGrid" Columns="1">
+
+    </UniformGrid>
+</pu:WindowX>
diff --git a/Plugin.BurnIn/ChartWindow.xaml.cs b/Plugin.BurnIn/ChartWindow.xaml.cs
new file mode 100644
index 0000000..ebab99b
--- /dev/null
+++ b/Plugin.BurnIn/ChartWindow.xaml.cs
@@ -0,0 +1,31 @@
+锘縰sing HMI.Core.Util;
+using Panuon.WPF.UI;
+using System.Windows;
+
+namespace Plugin.BurnIn
+{
+    /// <summary>
+    /// ChartWindow.xaml 鐨勪氦浜掗�昏緫
+    /// </summary>
+    public partial class ChartWindow : WindowX
+    {
+        private string _title;
+        public string title
+        {
+            get => _title;
+            set => Set(ref _title, value);
+        }
+
+        public ChartWindow(string title)
+        {
+            this.title = $"{title}{this.lang("Tr.DetailChart")}";
+            this.DataContext = this;
+            InitializeComponent();
+        }
+
+        private void Window_Unloaded(object sender, RoutedEventArgs e)
+        {
+            this.mainGrid.Children.Clear();
+        }
+    }
+}
diff --git a/Plugin.BurnIn/Constant/CommonConstant.cs b/Plugin.BurnIn/Constant/CommonConstant.cs
new file mode 100644
index 0000000..56b844b
--- /dev/null
+++ b/Plugin.BurnIn/Constant/CommonConstant.cs
@@ -0,0 +1,70 @@
+锘縰sing System.Drawing;
+using System.Globalization;
+using System.Windows.Data;
+
+namespace Plugin.BurnIn.Constant
+{
+    public class CommonStatus
+    {
+        public static readonly string OK = "ok";
+        public static readonly string NG = "ng";
+        public static readonly string WAITING = "waiting";
+        public static readonly string CHECKINFAILED = "checkInFailed";
+        public static readonly string CHECK_IN = "checkIn";
+        public static readonly string TESTING = "testing";
+        public static readonly string ALERT = "alert";
+        public static readonly string READY = "ready";
+
+        private static string[] cache = new string[] { "ok", "ng", "waiting", "checkInFailed", "checkIn", "testing", "alert", "ready" };
+
+        public static readonly Dictionary<string, string> STATUS_MAP = new Dictionary<string, string>() {
+            { "ok","姝e父"},
+            { "ng","寮傚父"},
+            { "waiting","绛夊緟涓�"},
+            { "checkInFailed","CheckIn澶辫触"},
+            { "checkIn","CheckIn"},
+            { "testing","娴嬭瘯涓�"},
+            { "alert","鎶ヨ"},
+            { "ready","鍑嗗灏辩华"},
+            { "abort","鎵嬪姩鍋滄"},
+        };
+
+        public static readonly Dictionary<string, SolidBrush> COLOR_MAP = new Dictionary<string, SolidBrush>() {
+            { "ok",new SolidBrush(Color.FromArgb(0,0,0,0))},
+            { "ng",new SolidBrush(Color.FromArgb(0,0,0,0))},
+            { "waiting",new SolidBrush(Color.FromArgb(0,0,0,0))},
+            { "testing",new SolidBrush(Color.FromArgb(0,0,0,0))},
+            { "checkInFailed",new SolidBrush(Color.FromArgb(0,0,0,0))},
+            { "checkIn",new SolidBrush(Color.FromArgb(0,0,0,0))},
+            { "abort",new SolidBrush(Color.FromArgb(0,0,0,0))},
+        };
+
+        public static string GetRandomStatus()
+        {
+            return cache[new Random().Next(cache.Length)];
+        }
+
+        public static string GetStatusDescription(string status)
+        {
+            if (STATUS_MAP.ContainsKey(status)) return STATUS_MAP[status];
+            return "";
+        }
+    }
+
+    public class StatusColorConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            if (value is string status)
+            {
+
+            }
+            return null;
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}
diff --git a/Plugin.BurnIn/Cooler.xaml b/Plugin.BurnIn/Cooler.xaml
new file mode 100644
index 0000000..1f75d21
--- /dev/null
+++ b/Plugin.BurnIn/Cooler.xaml
@@ -0,0 +1,77 @@
+锘�<UserControl x:Class="Plugin.BurnIn.Cooler"
+             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:Plugin.BurnIn"
+             mc:Ignorable="d" 
+        xmlns:pu="https://opensource.panuon.com/wpf-ui"
+             d:DesignHeight="450" d:DesignWidth="800">
+    <GroupBox Header="{Binding Title}">
+        <Grid>
+            <Grid.RowDefinitions>
+                <RowDefinition />
+                <RowDefinition Height="Auto"/>
+            </Grid.RowDefinitions>
+            <Label x:Name="label">
+                <Label.Style>
+                    <Style TargetType="Label">
+                        <Setter Property="HorizontalContentAlignment" Value="Center"/>
+                        <Setter Property="VerticalContentAlignment" Value="Center"/>
+                        <Style.Triggers>
+                            <DataTrigger Binding="{Binding Status}" Value="0">
+                                <Setter Property="Background" Value="Gray"/>
+                                <Setter Property="Content" Value="鍋滄"/>
+                            </DataTrigger>
+                            <DataTrigger Binding="{Binding Status}" Value="1">
+                                <Setter Property="Content" Value="杩愭按涓�"/>
+                            </DataTrigger>
+                            <DataTrigger Binding="{Binding Status}" Value="2">
+                                <Setter Property="Background" Value="Gray"/>
+                                <Setter Property="Content" Value="{Binding LabelContent}"/>
+                            </DataTrigger>
+                            <DataTrigger Binding="{Binding Status}" Value="3">
+                                <Setter Property="Background" Value="Gray"/>
+                                <Setter Property="Content" Value="娴嬪帇涓�"/>
+                            </DataTrigger>
+                        </Style.Triggers>
+                    </Style>
+                </Label.Style>
+            </Label>
+            <StackPanel Grid.Row="1">
+                <!--<Button Click="Button_Click" Style="{StaticResource PrimaryButton}" Tag="1" IsEnabled="False" Content="杩愭按"/>-->
+                <Button Click="Start_Click" Tag="2" x:Name="startBtn" Content="鍚规按">
+                    <Button.Style>
+                        <Style TargetType="Button" BasedOn="{StaticResource PrimaryButton}">
+                            <Style.Triggers>
+                                <DataTrigger Binding="{Binding Status}" Value="2">
+                                    <Setter Property="IsEnabled" Value="False"/>
+                                </DataTrigger>
+                            </Style.Triggers>
+                        </Style>
+                    </Button.Style>
+                </Button>
+                <!--<Button Click="Button_Click" Style="{StaticResource PrimaryButton}" Tag="3" IsEnabled="False" Content="娴嬪帇"/>-->
+                <Button Click="Stop_Click" Style="{StaticResource DangerButton}" Tag="0" Content="鍋滄" Cursor="Hand"/>
+                <!--<Button.Style>
+                    <Style TargetType="Button" BasedOn="{StaticResource PrimaryButton}">
+                        <Setter Property="Content" Value="鎵嬪姩鍚规按"/>
+                        <Setter Property="IsEnabled" Value="False"/>
+                        <Style.Triggers>
+                            <DataTrigger Binding="{Binding Status}" Value="0">
+                                <Setter Property="Content" Value="鎵嬪姩鍚规按"/>
+                                <Setter Property="IsEnabled" Value="True"/>
+                                <Setter Property="Background" Value="{DynamicResource SuccessColor}"/>
+                            </DataTrigger>
+                            <DataTrigger Binding="{Binding Status}" Value="2">
+                                <Setter Property="Content" Value="鍋滄鍚规按"/>
+                                <Setter Property="IsEnabled" Value="True"/>
+                                <Setter Property="Background" Value="{DynamicResource DangerColor}"/>
+                            </DataTrigger>
+                        </Style.Triggers>
+                    </Style>
+                </Button.Style>-->
+            </StackPanel>
+        </Grid>
+    </GroupBox>
+</UserControl>
diff --git a/Plugin.BurnIn/Cooler.xaml.cs b/Plugin.BurnIn/Cooler.xaml.cs
new file mode 100644
index 0000000..35ca5a8
--- /dev/null
+++ b/Plugin.BurnIn/Cooler.xaml.cs
@@ -0,0 +1,206 @@
+锘縰sing HMI.Core.Manager;
+using HMI.UI.Util;
+using PLCHelper;
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+using System.Timers;
+using System.Windows;
+using System.Windows.Controls;
+
+namespace Plugin.BurnIn
+{
+    /// <summary>
+    /// Cooler.xaml 鐨勪氦浜掗�昏緫
+    /// </summary>
+    public partial class Cooler : UserControl, INotifyPropertyChanged
+    {
+        #region ...
+        public virtual event PropertyChangedEventHandler PropertyChanged;
+
+        protected virtual void Set([CallerMemberName] string name = null)
+        {
+
+            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
+        }
+
+        protected void Set<T>(ref T old, T @new, [CallerMemberName] string propertyName = null)
+        {
+            old = @new;
+            if (propertyName != null)
+            {
+                Set(propertyName);
+            }
+        }
+        #endregion
+
+        public int index { get; private set; }
+
+        public string Title { get; set; }
+
+        private System.Timers.Timer timer;
+
+        public Cooler(int index)
+        {
+            DataContext = this;
+            this.index = index;
+            this.Title = $"绗瑊index + 1}璺按鍐锋満";
+            InitializeComponent();
+        }
+
+        private ushort status;
+        public ushort Status
+        {
+            get => status;
+            set
+            {
+                Set(ref status, value);
+            }
+        }
+
+        private int totalTime;
+
+        private string labelContent = "鍚规按涓�";
+        public string LabelContent
+        {
+            get => labelContent;
+            set
+            {
+                Set(ref labelContent, value);
+            }
+        }
+
+        private void InitTimer(int time)
+        {
+            if (time <= 0)
+            {
+                time = 240;
+            }
+            totalTime = time;
+            LabelContent = $"鍚规按涓璡r\n鍓﹞totalTime}s...";
+            timer = new System.Timers.Timer();
+            timer.Interval = 1000;
+            timer.Elapsed += Timer_Elapsed;
+            timer.Enabled = true;
+        }
+
+        private void Timer_Elapsed(object sender, ElapsedEventArgs e)
+        {
+            try
+            {
+                timer.AutoReset = false;
+                if (totalTime > 0)
+                {
+                    totalTime -= 1;
+                    LabelContent = $"鍚规按涓璡r\n鍓﹞totalTime}s...";
+                }
+                else
+                {
+                    if (S7Context.WriteData("", $"Cooler{index + 1}_Status", 0, out string errMsg))
+                    {
+                        Status = 0;
+                        Dispatcher.Invoke(() =>
+                        {
+                            startBtn.IsEnabled = true;
+                        });
+                    }
+                    else
+                    {
+                        Dispatcher.Invoke(() =>
+                        {
+                            ToastHelper.Error(errMsg);
+                        });
+                    }
+                    Release();
+                }
+            }
+            catch (Exception ex)
+            {
+            }
+            finally
+            {
+                if (timer?.Enabled == true) timer.AutoReset = true;
+            }
+        }
+
+        private void Start_Click(object sender, RoutedEventArgs e)
+        {
+            if (timer != null) return;
+            var btn = sender as Button;
+
+            int totalTime = 240;
+
+            try
+            {
+                var p = ParameterManager.GetCommonParameterByName("鍚规按鏃堕棿")?.Value;
+                if (!string.IsNullOrEmpty(p)) totalTime = int.Parse(p);
+            }
+            catch (Exception ex)
+            {
+
+            }
+
+            if (MsgBoxHelper2.WarnChoose($"绗� [{index + 1}] 璺按鍐锋満纭畾瑕亄btn.Content}鍚�?") == MessageBoxResult.OK)
+            {
+                try
+                {
+                    if (S7Context.WriteData("", $"Cooler{index + 1}_Status", 2, out string errMsg))
+                    {
+                        Status = 2;
+                        InitTimer(totalTime);
+                        btn.IsEnabled = false;
+                    }
+                    else
+                    {
+                        ToastHelper.Error(errMsg);
+                    }
+                }
+                catch (Exception ex)
+                {
+                    ToastHelper.Error(ex.Message);
+                }
+            }
+        }
+
+        private void Stop_Click(object sender, RoutedEventArgs e)
+        {
+            var btn = sender as Button;
+
+            if (MsgBoxHelper2.WarnChoose($"绗� [{index + 1}] 璺按鍐锋満纭畾瑕亄btn.Content}鍚�?") == MessageBoxResult.OK)
+            {
+                try
+                {
+                    if (timer != null)
+                    {
+                        timer.Enabled = false;
+                        timer.Dispose();
+                        timer = null;
+                    }
+
+                    if (S7Context.WriteData("", $"Cooler{index + 1}_Status", 0, out string errMsg))
+                    {
+                        Status = 0;
+                        startBtn.IsEnabled = true;
+                    }
+                    else
+                    {
+                        ToastHelper.Error(errMsg);
+                    }
+                }
+                catch (Exception ex)
+                {
+                    ToastHelper.Error(ex.Message);
+                }
+            }
+        }
+
+        public void Release()
+        {
+            if (timer != null)
+            {
+                timer.Enabled = false;
+                timer.Dispose();
+                timer = null;
+            }
+        }
+    }
+}
diff --git a/Plugin.BurnIn/CoolerStatus.xaml b/Plugin.BurnIn/CoolerStatus.xaml
new file mode 100644
index 0000000..34b2311
--- /dev/null
+++ b/Plugin.BurnIn/CoolerStatus.xaml
@@ -0,0 +1,25 @@
+锘�<pu:WindowX x:Class="Plugin.BurnIn.CoolerStatus"
+        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+        xmlns:local="clr-namespace:Plugin.BurnIn"
+        WindowStartupLocation="CenterScreen"
+        FontSize="20"
+        mc:Ignorable="d"
+        Style="{StaticResource ModalWindow}"
+        xmlns:hb="clr-namespace:HMI.Base;assembly=HMI.Base"
+        xmlns:uic="clr-namespace:HMI.UI.Converter;assembly=HMI.UI"
+        xmlns:pw="https://opensource.panuon.com/wpf"
+        xmlns:pu="https://opensource.panuon.com/wpf-ui"
+        xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI"
+        xmlns:cc="clr-namespace:HMI.Core;assembly=HMI.Core"
+        InteropOwnersMask="False"
+        pu:WindowXModalDialog.Buttons="{x:Null}"
+        Closed="WindowX_Closed"
+        Loaded="WindowX_Loaded" 
+        Width="600" Height="400">
+    <Window.Resources>
+    </Window.Resources>
+    <Grid Margin="10" x:Name="mainGrid"></Grid>
+</pu:WindowX>
diff --git a/Plugin.BurnIn/CoolerStatus.xaml.cs b/Plugin.BurnIn/CoolerStatus.xaml.cs
new file mode 100644
index 0000000..d446edf
--- /dev/null
+++ b/Plugin.BurnIn/CoolerStatus.xaml.cs
@@ -0,0 +1,93 @@
+锘縰sing Panuon.WPF.UI;
+using PLCHelper;
+using System.Timers;
+using System.Windows;
+using System.Windows.Controls;
+
+namespace Plugin.BurnIn
+{
+    /// <summary>
+    /// CoolerStatus.xaml 鐨勪氦浜掗�昏緫
+    /// </summary>
+    public partial class CoolerStatus : WindowX
+    {
+        private int ovenIndex;
+        private int columns;
+        private System.Timers.Timer timer;
+        private List<Cooler> coolers = new List<Cooler>();
+
+        public CoolerStatus(int ovenIndex, int columns, string title)
+        {
+            DataContext = this;
+            this.ovenIndex = ovenIndex;
+            this.columns = columns;
+            Title = title;
+            timer = new System.Timers.Timer();
+            timer.Interval = 1000;
+            timer.Elapsed += Timer_Elapsed;
+            InitializeComponent();
+            RefreshUI();
+            RefreshStatus();
+        }
+
+        private void Timer_Elapsed(object sender, ElapsedEventArgs e)
+        {
+            RefreshStatus();
+        }
+
+        private void RefreshStatus()
+        {
+            foreach (var cooler in coolers)
+            {
+                try
+                {
+                    if (S7Context.GetData("", $"Cooler{cooler.index + 1}_Status", out object val))
+                    {
+                        cooler.Dispatcher.Invoke(() =>
+                        {
+                            cooler.Status = (ushort)val;
+                        });
+                    }
+                }
+                catch (Exception ex)
+                {
+
+                }
+            }
+        }
+
+        private void RefreshUI()
+        {
+            mainGrid.Children.Clear();
+            mainGrid.ColumnDefinitions.Clear();
+
+            for (int i = 0; i < this.columns; i++)
+            {
+                ColumnDefinition newCol = new ColumnDefinition();
+                newCol.Width = new GridLength(1, GridUnitType.Star);
+                mainGrid.ColumnDefinitions.Add(newCol);
+                var cooler = new Cooler(ovenIndex * columns + i);
+                Grid.SetColumn(cooler, i);
+                mainGrid.Children.Add(cooler);
+                coolers.Add(cooler);
+            }
+        }
+
+        private void WindowX_Closed(object sender, EventArgs e)
+        {
+            timer.Enabled = false;
+            timer.Dispose();
+
+            foreach (var cooler in coolers)
+            {
+                cooler.Release();
+            }
+        }
+
+        private void WindowX_Loaded(object sender, RoutedEventArgs e)
+        {
+            timer.Enabled = true;
+            this.Loaded -= WindowX_Loaded;
+        }
+    }
+}
diff --git a/Plugin.BurnIn/DataTable.xaml b/Plugin.BurnIn/DataTable.xaml
new file mode 100644
index 0000000..4d57492
--- /dev/null
+++ b/Plugin.BurnIn/DataTable.xaml
@@ -0,0 +1,164 @@
+锘�<UserControl x:Class="Plugin.BurnIn.DataTable"
+             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:Plugin.BurnIn"
+            xmlns:hb="clr-namespace:HMI.Base;assembly=HMI.Base"
+xmlns:uic="clr-namespace:HMI.UI.Converter;assembly=HMI.UI"
+xmlns:pw="https://opensource.panuon.com/wpf"
+xmlns:pu="https://opensource.panuon.com/wpf-ui"
+xmlns:ui="clr-namespace:HMI.UI;assembly=HMI.UI"
+xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI"
+xmlns:dc="clr-namespace:HMI.UI.Converter;assembly=HMI.UI"
+             mc:Ignorable="d"
+             Background="#FFF"
+             BorderBrush="Black"
+             BorderThickness="1"
+             x:Name="control"
+             Loaded="UserControl_Loaded"
+             d:DesignHeight="450" d:DesignWidth="800">
+    <UserControl.Resources>
+        <local:FontSizeConverter x:Key="FontSizeConverter"/>
+        <dc:ReverseBoolConverter x:Key="reverseConverter"/>
+    </UserControl.Resources>
+    <Grid x:Name="grid">
+        <Grid.RowDefinitions>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition Height="8.5*"/>
+            <RowDefinition Height="1.5*"/>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition Height="Auto"/>
+        </Grid.RowDefinitions>
+        <Grid x:Name="infoGrid" Grid.Row="0" Background="#a1eafb">
+            <Grid.RowDefinitions>
+                <RowDefinition/>
+                <RowDefinition/>
+            </Grid.RowDefinitions>
+
+            <Grid.ColumnDefinitions>
+                <ColumnDefinition Width="Auto"/>
+                <ColumnDefinition/>
+            </Grid.ColumnDefinitions>
+
+            <TextBlock FontSize="{Binding ActualWidth,RelativeSource={RelativeSource AncestorType=UserControl}, 
+Converter={StaticResource FontSizeConverter}, ConverterParameter='20,940'}"
+                   FontWeight="Bold"
+                   Margin="0"
+                   HorizontalAlignment="Center"
+                   VerticalAlignment="Center" Text="{Binding title}"/>
+
+            <ComboBox Grid.Row="1" FontSize="{Binding ActualWidth,RelativeSource={RelativeSource AncestorType=UserControl}, 
+Converter={StaticResource FontSizeConverter}, ConverterParameter='15,940'}"
+                      Style="{StaticResource FormComboxStyle}"
+                      IsEnabled="{Binding IsTesting,Converter={StaticResource reverseConverter}}"
+                      pu:ComboBoxHelper.ClearButtonVisibility="Collapsed"
+                      BorderBrush="Black"
+                      Height="26"
+                      BorderThickness="0,1,0,0"
+                      pu:ComboBoxHelper.Watermark="{DynamicResource Tr.ProductInfo}"
+                      Width="{Binding ActualWidth,RelativeSource={RelativeSource AncestorType=UserControl}, 
+Converter={StaticResource FontSizeConverter}, ConverterParameter='150,940'}"
+                      SelectedValuePath="Content"
+                      x:Name="CbProduct" SelectionChanged="CbProduct_SelectionChanged"/>
+
+            <local:InfoTable x:Name="infoTable" Grid.Row="0" Grid.Column="1"
+                             Grid.RowSpan="2"
+               FontSize="{Binding ActualWidth,RelativeSource={RelativeSource AncestorType=UserControl}, 
+            Converter={StaticResource FontSizeConverter}, ConverterParameter='12,940'}"></local:InfoTable>
+        </Grid>
+
+        <UniformGrid x:Name="mainGrid" Grid.Row="1" dp:UniformGridHelper.InnerBorder="1"
+                     dp:UniformGridHelper.InnerBorderIgnore="Left,Right,Bottom">
+        </UniformGrid>
+
+        <!--<local:Chart Grid.Row="2" x:Name="chart" BorderThickness="0,1,0,1" BorderBrush="Black" 
+                      MouseDoubleClick="chart_MouseDoubleClick"/>-->
+
+        <StatusBar FontSize="14" Grid.Row="3">
+            <StatusBar.ItemsPanel>
+                <ItemsPanelTemplate>
+                    <DockPanel/>
+                </ItemsPanelTemplate>
+            </StatusBar.ItemsPanel>
+            <StatusBarItem DockPanel.Dock="Left">
+                <Button Style="{StaticResource LinkButton}" Click="Button_Click_1" Content="{DynamicResource App.ScannerSetting}"/>
+            </StatusBarItem>
+
+            <StatusBarItem DockPanel.Dock="Right">
+                <StackPanel Orientation="Horizontal" dp:StackPanelHelper.ChildSpace="8">
+                    <!--<Button Style="{StaticResource DeleteLink}" Click="Button_Click_2" Content="鍋滄娴嬭瘯"/>-->
+
+                    <StackPanel Orientation="Horizontal">
+                        <Ellipse Width="10" Height="10">
+                            <Ellipse.Style>
+                                <Style TargetType="Ellipse">
+                                    <Setter Property="Fill" Value="LightGray"/>
+                                    <Style.Triggers>
+                                        <DataTrigger Binding="{Binding TcpServerStatus,ElementName=control}" Value="ERROR">
+                                            <Setter Property="Fill" Value="Red"/>
+                                        </DataTrigger>
+                                        <DataTrigger Binding="{Binding TcpServerStatus,ElementName=control}" Value="RUNNING">
+                                            <Setter Property="Fill" Value="Green"/>
+                                        </DataTrigger>
+                                    </Style.Triggers>
+                                </Style>
+                            </Ellipse.Style>
+                        </Ellipse>
+                        <TextBlock Text="{Binding TcpServerInfo}"/>
+                        <Button Padding="0" Margin="0" BorderThickness="0" Click="Button_Click">馃攧</Button>
+                    </StackPanel>
+                </StackPanel>
+            </StatusBarItem>
+
+            <StatusBarItem DockPanel.Dock="Right">
+                <Button Click="Manual_Cooler_Opt" x:Name="coolerSetting">
+                    <Button.Style>
+                        <Style TargetType="Button" BasedOn="{StaticResource LinkButton}">
+                            <Setter Property="Content" Value="{DynamicResource Tr.CoolerSetting}"/>
+                            <Setter Property="Foreground" Value="{DynamicResource PrimaryColor}"/>
+                        </Style>
+                    </Button.Style>
+                </Button>
+            </StatusBarItem>
+
+            <StatusBarItem >
+                <TextBox IsReadOnly="True" FontSize="16" Background="Transparent" BorderThickness="0" Text="{Binding statusText}"/>
+            </StatusBarItem>
+        </StatusBar>
+
+        <UniformGrid Grid.Row="4" Rows="1">
+            <!--<UniformGrid.ColumnDefinitions>
+                <ColumnDefinition/>
+                <ColumnDefinition/>
+                <ColumnDefinition Width="Auto"/>
+                <ColumnDefinition/>
+            </UniformGrid.ColumnDefinitions>-->
+            <Button Click="ScanWindow_Click" Style="{StaticResource PrimaryButton}"
+                    IsEnabled="{Binding IsTesting,Converter={StaticResource reverseBool}}"
+        Content="{DynamicResource Tr.Scan}"/>
+            <Button Grid.Column="1" Click="ClearAllSnr_Click" Style="{StaticResource DangerButton}"
+                    IsEnabled="{Binding IsTesting,Converter={StaticResource reverseBool}}"
+Content="{DynamicResource Tr.ClearAllSNR}"/>
+            <!--<Button Grid.Column="2" Click="SetStationGlobal_Click" Style="{StaticResource SuccessButton}"
+        Content="{DynamicResource App.Start}"/> Background="#1296db"-->
+
+            <Button Grid.Column="2" Click="Abort_Click" x:Name="abortBtn"
+        Style="{DynamicResource DangerButton}" Background="#1296db" Content="Abort"/>
+
+            <Button Grid.Column="3" Click="StartSingleClick">
+                <Button.Style>
+                    <Style TargetType="Button" BasedOn="{StaticResource SuccessButton}">
+                        <Setter Property="Content" Value="{DynamicResource App.Start}"/>
+                        <Style.Triggers>
+                            <DataTrigger Binding="{Binding IsTesting}" Value="True">
+                                <Setter Property="Background" Value="{DynamicResource DangerVerBar}"/>
+                                <Setter Property="Content" Value="鍋滄娴嬭瘯"/>
+                            </DataTrigger>
+                        </Style.Triggers>
+                    </Style>
+                </Button.Style>
+            </Button>
+        </UniformGrid>
+    </Grid>
+</UserControl>
\ No newline at end of file
diff --git a/Plugin.BurnIn/DataTable.xaml.cs b/Plugin.BurnIn/DataTable.xaml.cs
new file mode 100644
index 0000000..f9ad8ac
--- /dev/null
+++ b/Plugin.BurnIn/DataTable.xaml.cs
@@ -0,0 +1,887 @@
+锘縰sing HMI.Base;
+using HMI.Core.Setting;
+using HMI.Core.Util;
+using HMI.LogUtil;
+using HMI.UI.Util;
+using HMI.Util;
+using Plugin.BurnIn.Model;
+using Plugin.BurnIn.Util;
+using Plugin.TsSupport;
+using SuperSocket.SocketBase.Config;
+using SuperSocket.SocketBase.Protocol;
+using System.ComponentModel;
+using System.IO;
+using System.Runtime.CompilerServices;
+using System.Timers;
+using System.Windows;
+using System.Windows.Controls;
+
+namespace Plugin.BurnIn
+{
+    /// <summary>
+    /// DataTable.xaml 鐨勪氦浜掗�昏緫
+    /// </summary>
+    public partial class DataTable : UserControl, INotifyPropertyChanged
+    {
+        #region ...
+        public event PropertyChangedEventHandler PropertyChanged;
+
+        public virtual void RaisePropertyChanged(string name)
+        {
+            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
+        }
+
+        public void RaiseAndSetIfchanged<T>(ref T old, T @new, [CallerMemberName] string propertyName = null)
+        {
+            old = @new;
+            if (propertyName != null) RaisePropertyChanged(propertyName);
+        }
+        #endregion
+
+        private string _title;
+
+        public string title
+        {
+            get { return _title; }
+            set => RaiseAndSetIfchanged(ref _title, value);
+        }
+
+        private string _statusText;
+
+        public string statusText
+        {
+            get { return _statusText; }
+            set => RaiseAndSetIfchanged(ref _statusText, value);
+        }
+
+        private bool _isTesting;
+
+        public bool IsTesting
+        {
+            get { return _isTesting; }
+            set => RaiseAndSetIfchanged(ref _isTesting, value);
+        }
+
+        private ushort coolerStatus;
+        public ushort CoolerStatus
+        {
+            get => coolerStatus;
+            set
+            {
+                RaiseAndSetIfchanged(ref coolerStatus, value);
+            }
+        }
+
+        private string tcpServerStatus;
+
+        public string TcpServerStatus
+        {
+            get { return tcpServerStatus; }
+            set { RaiseAndSetIfchanged(ref tcpServerStatus, value); }
+        }
+
+        private string tcpServerInfo = LangUtil.Get("App.NotStarted");
+
+        public string TcpServerInfo
+        {
+            get { return tcpServerInfo; }
+            set { RaiseAndSetIfchanged(ref tcpServerInfo, value); }
+        }
+        public int index { get; private set; }
+        public readonly int cellCount;
+        private readonly string chartTitle;
+        public readonly TestResultView parent;
+
+        private BaseConfig config;
+        private LiveChart chart;
+        private LiveChart multiChart;
+        public SingleCell[] cells { get; private set; }
+
+        private TcpServer tcpServer;
+        private string CountIniPath;
+
+        private DateTime? testStartTime;
+
+        private System.Timers.Timer testTimeTimer;
+
+        // 骞冲潎娓╁害
+        public List<double> tempratures = new List<double>();
+        public Dictionary<string, List<double>> detailTempratures
+            = new Dictionary<string, List<double>>();
+
+        public DataTable(TestResultView parent, int index, string title,
+            string countIniPath,
+            BaseConfig config)
+        {
+            this.CountIniPath = countIniPath;
+            this.config = config;
+            this.index = index;
+            this.parent = parent;
+            this.title = title;
+            this.cellCount = config.PositionCount;
+            this.cells = new SingleCell[cellCount];
+            this.chartTitle = this.lang("Tr.AvgTemp");
+            DataContext = this;
+            InitializeComponent();
+            mainGrid.Columns = config.PositionColumn;
+            this.chart = new LiveChart("骞冲潎娓╁害,骞冲潎娓╁害(鈩�)", false, true);
+            this.multiChart = new LiveChart(",璇︾粏娓╁害(鈩�)", true)
+            {
+                BorderThickness = new Thickness(0)
+            };
+            this.grid.Children.Add(this.chart);
+            Grid.SetRow(this.chart, 2);
+            this.chart.MouseDoubleClick += chart_MouseDoubleClick;
+
+            for (int i = 0; i < this.cellCount; i++)
+            {
+                var cellRow = (i / config.PositionColumn) + 1;
+                var cellCol = (i % config.PositionColumn) + 1;
+                var cellTitle = $"{title}-{cellRow}-{cellCol}";
+
+                var cell = new SingleCell(this, cellTitle, i, config.CellListMaxSize, countIniPath);
+                mainGrid.Children.Add(cell);
+                cells[i] = cell;
+            }
+
+            ScannerParser.Init(config.CodeParserType);
+
+            ReadCount();
+            testTimeTimer = new System.Timers.Timer();
+            testTimeTimer.Interval = 10000;
+            testTimeTimer.AutoReset = true;
+            testTimeTimer.Elapsed += TestTimeTimer_Elapsed;
+            if (!ParamSetting.ShowCoolerSetting) coolerSetting.Visibility = Visibility.Collapsed;
+            if (!ParamSetting.ShowBurnInAbort) abortBtn.Visibility = Visibility.Collapsed;
+        }
+
+        private void TestTimeTimer_Elapsed(object sender, ElapsedEventArgs e)
+        {
+            if (testStartTime == null) return;
+            var now = DateTime.Now;
+            Dispatcher.BeginInvoke(new Action(() =>
+            {
+                //SetConsumeTime((now - testStartTime).Value.TotalSeconds);
+            }));
+        }
+
+        public int GetSize()
+        {
+            return this.cellCount;
+        }
+
+        public SingleCell GetSingleCell(int index)
+        {
+            return cells[index];
+        }
+
+        public void SetStatusText(string statusText)
+        {
+            this.statusText = statusText;
+        }
+
+        public void SetTemperature(double t)
+        {
+            infoTable.temperature = t;
+            tempratures.Add(t);
+            chart.AddDynamicData("骞冲潎娓╁害", "骞冲潎娓╁害(鈩�)", DateTime.Now, t);
+        }
+
+        public void SetTemperature(string name, double t)
+        {
+            //List<double> list;
+            //if (detailTempratures.ContainsKey(name))
+            //{
+            //    list = detailTempratures[name];
+            //}
+            //else
+            //{
+            //    list = new List<double>();
+            //    detailTempratures.Add(name, list);
+            //}
+            //list.Add(t);
+
+            multiChart.AddDynamicData(name, "璇︾粏娓╁害(鈩�)", DateTime.Now, t);
+            //multiChart.AddData(name, t);
+        }
+
+        public void SetStatus(string status)
+        {
+            BurnInUI.Logger.Debug($"In Status: {status}");
+            Dispatcher.BeginInvoke(new Action(() =>
+            {
+                var lastStatus = infoTable.status;
+                if (status == "preCheck")
+                {
+                    BurnInUI.Logger.Debug($"In preCheck");
+                    IsTesting = true;
+                    return;
+                }
+                if (status == "stop")
+                {
+                    IsTesting = false;
+                    return;
+                }
+                if (status == "abort")
+                {
+                    IsTesting = false;
+                }
+                infoTable.status = status;
+
+                //if (lastStatus != "testing" && status == "testing")
+                //{
+                //    testStartTime = DateTime.Now;
+                //    SetStartTime(testStartTime?.ToString("yyyy-MM-dd HH:mm:ss"));
+                //    testTimeTimer.Start();
+                //}
+                //if (lastStatus == "testing" && status != "testing")
+                //{
+                //    testTimeTimer.Stop();
+                //    var now = DateTime.Now;
+                //    infoTable.UpdateConsumeTime((now - testStartTime).Value.TotalSeconds, null);
+                //    SetEndTime(now.ToString("yyyy-MM-dd HH:mm:ss"));
+                //    testStartTime = null;
+                //}
+            }));
+        }
+
+        public void SetOkCount(int count)
+        {
+            infoTable.okCount = count;
+        }
+
+        public void SetNgCount(int count)
+        {
+            infoTable.ngCount = count;
+        }
+
+        public void SetStartTime(string startTime)
+        {
+            infoTable.startTimeStr = startTime;
+        }
+
+        public void SetEndTime(string endTime)
+        {
+            infoTable.endTimeStr = endTime;
+        }
+
+        public void UpdateConsumeTime(double consumeTime, double? leftTime)
+        {
+            infoTable.UpdateConsumeTime(consumeTime, leftTime);
+        }
+
+        private ChartWindow window;
+
+        private void chart_MouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
+        {
+            /// 鎵撳紑澶氫釜娓╁害鏇茬嚎
+            if (window == null)
+            {
+                window = new ChartWindow(title);
+                window.Owner = Window.GetWindow(this);
+                window.Closed += Window_Closed;
+                window.mainGrid.Children.Add(multiChart);
+                window.Show();
+            }
+            else
+            {
+                window.Activate();
+            }
+        }
+
+        private Scanner scanner;
+
+        private void Window_Closed(object sender, EventArgs e)
+        {
+            window = null;
+        }
+
+        private void SetStationGlobal_Click(object sender, RoutedEventArgs e)
+        {
+
+        }
+
+        private void StartSingleClick(object sender, RoutedEventArgs e)
+        {
+            if (IsTesting)
+            {
+                if (MsgBoxHelper2.WarnChoose(this.lang("Tr.MakeSureStopOvenTest", title)) == MessageBoxResult.OK)
+                {
+                    LogHelper.Info("UI", $"鑰佸寲鐐塠{title}]鍋滄鎸夐挳鎸変笅", out _);
+                    BurnInUI.Logger.Info($"鑰佸寲鐐塠{title}]鍋滄鎸夐挳鎸変笅");
+                    parent.SetStartFlag(this.index, false);
+                }
+            }
+            else
+            {
+                /// 妫�鏌ユ槸鍚﹀叧闂�
+                //if (!GetDoorClose(parent.executionWrapper))
+                //{
+                //    /// 娌″叧闂ㄤ笉鍏佽鍚姩
+                //    MsgBoxHelper2.Warn("璇峰厛鍏抽棴鑸遍棬锛�");
+                //    return;
+                //}
+
+                /// 妫�鏌ユ槸鍚︽湁鏉$爜
+                bool flag = false;
+                foreach (var cell in cells)
+                {
+                    if (!string.IsNullOrEmpty(cell.sn))
+                    {
+                        flag = true;
+                        break;
+                    }
+                }
+                if (flag)
+                {
+                    LogHelper.Info("UI", $"鑰佸寲鐐塠{title}]鍚姩鎸夐挳鎸変笅", out _);
+                    BurnInUI.Logger.Info($"鑰佸寲鐐塠{title}]鍚姩鎸夐挳鎸変笅");
+                    ClearChartData();
+                    parent.SetStartFlag(this.index, true);
+                }
+                else
+                {
+                    MsgBoxHelper2.Warn("璇峰厛瀹屾垚鎵爜锛�");
+                }
+            }
+        }
+
+        private void ScanWindow_Click(object sender, RoutedEventArgs e)
+        {
+            if (!parent.IsRunning)
+            {
+                MsgBoxHelper.WarnMessage(this, this.lang("Tr.StartSeqFirst"), null, MessageBoxButton.OK);
+                return;
+            }
+
+            if (scanner != null)
+            {
+                scanner.ShowDialog();
+            }
+            else
+            {
+                int rows = config.CellCount / config.PositionColumn + 1;
+                scanner = new Scanner(this.index, this.lang("Tr.ScanTitle", title), CbProduct.SelectedItem?.ToString(), mainGrid.Columns, rows)
+                {
+                    Owner = Window.GetWindow(this),
+                    Tag = title
+                };
+                scanner.FinishBarcode += Scanner_FinishBarcode;
+                scanner.ComplateClick += Scanner_ComplateClick; ;
+                scanner.Closed += Scanner_Closed;
+                scanner.ShowDialog();
+            }
+        }
+
+        private void Scanner_ComplateClick(object sender, RoutedEventArgs e)
+        {
+            string paramName = $"StationHMI.UUTHMI[{index}].Scanner";
+
+            parent.executionWrapper.SetStationGlobalValue<bool>(paramName, 1, true);
+            //parent.engine.Globals.SetValBoolean(paramName, 1, true);
+        }
+
+        private void Scanner_Closed(object sender, EventArgs e)
+        {
+            scanner = null;
+        }
+
+        public bool GetDoorClose(ITestExecutionWrapper wrapper)
+        {
+            return wrapper?.GetStationGlobalValue<bool>($"StationHMI.UUTHMI[{this.index}].DoorClose", 0) == true;
+        }
+
+        private void Scanner_FinishBarcode(object sender, BarcodeEventArg e)
+        {
+            if (parent.executionWrapper == null) return;
+            //if (parent.SequenceContext == null) return;
+
+            int rows = (config.CellCount / config.PositionColumn) + 1;
+            CellInfo cellInfo = ScannerParser.GetCellInfoByCode(CbProduct.SelectedItem?.ToString(), mainGrid.Columns, rows, e.positionCode);
+            string positionCode = e.positionCode, snrCode = e.snrCode;
+
+            //if (cellInfo.OvenName != this.title)
+            //{
+            //    MsgBoxHelper2.Warn($"璇峰垏鎹㈣嚦 [{cellInfo.OvenName}] 鍖烘壂鐮侊紝褰撳墠涓� [{this.title}] 鍖猴紒");
+            //    return;
+            //}
+
+
+            var targetTable = TestResultView.nameTables[cellInfo.OvenName];
+
+            //灏嗚鍒扮殑鏉$爜鍐欏叆鍒癝N涓�
+            string LookupStr = $"StationHMI.UUTHMI[{targetTable.index}].SN[{cellInfo.Index}]";
+            cellInfo.LookUpStr = LookupStr;
+
+            BurnInUI.Logger.Info($"PositionCode: [{e.positionCode}], SNR: [{e.snrCode}], LookUpStr: [{LookupStr}]");
+            BurnInUI.Logger.Info($"OvenName: [{cellInfo.OvenName}], CellIndex: [{cellInfo.Index}]");
+            var cell = targetTable.GetSingleCell(cellInfo.Index);
+
+            if (targetTable.GetDoorClose(parent.executionWrapper))
+            {
+                /// 鍏抽棬浜嗗氨涓嶅厑璁告竻鏉$爜
+                MsgBoxHelper2.Warn($"{targetTable.title}鐐夌敓浜т腑锛屼笉鍏佽鎿嶄綔鏉$爜");
+                //MsgBoxHelper2.Warn(this.lang("Tr.CantClearSNR"));
+                return;
+            }
+
+            if (snrCode == null)
+            {
+                var oldCellInfo = e.lastInfo;
+                var oldTable = TestResultView.nameTables[oldCellInfo.OvenName];
+                var oldCell = oldTable.GetSingleCell(oldCellInfo.Index);
+                if (oldCell.status != "hadCode")
+                {
+                    throw new Exception(this.lang("Tr.PositionCantClearSNR", oldCellInfo.OriginCode));
+                }
+                // 绉婚櫎鏉$爜
+                parent.executionWrapper.SetStationGlobalValue<string>(oldCellInfo.LookUpStr, 1, "");
+                //parent.executionWrapper.CommitToDisk(false);
+
+                //parent.engine.Globals.SetValString(oldCellInfo.LookUpStr, 1, "");
+                //parent.engine.CommitGlobalsToDisk(promptOnSaveConflicts: false);
+                oldCell.SetStatus("waiting");
+                oldCell.ClearSN();
+                return;
+            }
+
+            /// 娓呮帀涓婁釜鏉$爜鐨勮褰�
+            var lastSn = cell.sn;
+            if (!string.IsNullOrEmpty(lastSn))
+            {
+                Scanner.RemoveSNR(lastSn);
+            }
+
+            // 鍐欏叆鏉$爜
+            parent.executionWrapper.SetStationGlobalValue<string>(LookupStr, 1, snrCode);
+            //parent.executionWrapper.CommitToDisk(false);
+
+            cell.SetStatus("hadCode");
+            cell.sn = snrCode;
+            Scanner.InsertNewCode(snrCode, cellInfo);
+            cell.LightGreen(true, out string msg);            
+        }
+
+
+        /// <summary>
+        /// 娓呯┖涓や釜鍥捐〃鏁版嵁
+        /// </summary>
+        public void ClearChartData()
+        {
+            chart.Clear();
+            multiChart.Clear();
+
+            tempratures.Clear();
+            detailTempratures.Clear();
+        }
+
+        /// <summary>
+        /// 鍚姩Tcp鏈嶅姟鍣�
+        /// </summary>
+        private void StartTcpServer()
+        {
+            var serverConfig = new ServerConfig()
+            {
+                MaxRequestLength = 1024 * 1024,
+                Port = config.PortStart + index
+            };
+            try
+            {
+                if (tcpServer == null)
+                {
+                    tcpServer = new TcpServer();
+                    tcpServer.NewRequestReceived += TcpServer_NewRequestReceived;
+                    if (!tcpServer.Setup(serverConfig))
+                    {
+                        ChangeTcpServerStatus("ERROR", this.lang("Tr.TcpServer.SettingFailed"));
+                        return;
+                    }
+                    if (!tcpServer.Start())
+                    {
+                        ChangeTcpServerStatus("ERROR", this.lang("Tr.TcpServer.StartFailed"));
+                        return;
+                    }
+                    ChangeTcpServerStatus("RUNNING", this.lang("Tr.TcpServer.Running"));
+                }
+                else if (tcpServer.State != SuperSocket.SocketBase.ServerState.Running)
+                {
+                    if (!tcpServer.Setup(serverConfig))
+                    {
+                        ChangeTcpServerStatus("ERROR", this.lang("Tr.TcpServer.SettingFailed"));
+                        return;
+                    }
+                    if (!tcpServer.Start())
+                    {
+                        ChangeTcpServerStatus("ERROR", this.lang("Tr.TcpServer.StartFailed"));
+                        return;
+                    }
+                    ChangeTcpServerStatus("RUNNING", this.lang("Tr.TcpServer.Running"));
+                }
+            }
+            catch (Exception ex)
+            {
+                ChangeTcpServerStatus("ERROR", $"{ex.Message}");
+                tcpServer?.Stop();
+                tcpServer = null;
+            }
+        }
+
+        /// <summary>
+        /// 鏇存柊Tcp鏈嶅姟鍣ㄧ姸鎬�
+        /// </summary>
+        /// <param name="status"></param>
+        /// <param name="info"></param>
+        private void ChangeTcpServerStatus(string status, string info)
+        {
+            Dispatcher.Invoke(() =>
+            {
+                TcpServerStatus = status;
+                TcpServerInfo = info;
+            });
+        }
+
+        /// <summary>
+        /// 鍋滄Tcp鏈嶅姟鍣�
+        /// </summary>
+        private void StopTcpServer()
+        {
+            try
+            {
+                tcpServer?.Stop();
+                tcpServer = null;
+            }
+            catch (Exception ex)
+            {
+
+            }
+        }
+
+        /// <summary>
+        /// 閲婃斁璧勬簮
+        /// </summary>
+        public void RelaseResource()
+        {
+            testTimeTimer?.Stop();
+            StopTcpServer();
+        }
+
+        /// <summary>
+        /// Tcp娑堟伅澶勭悊
+        /// </summary>
+        /// <param name="session"></param>
+        /// <param name="requestInfo"></param>
+        private void TcpServer_NewRequestReceived(TcpSession session, StringRequestInfo requestInfo)
+        {
+            string msg = requestInfo.Body;
+            BurnInUI.Logger.Debug($"TCP Data From {session.RemoteEndPoint}: {msg}.");
+            string[] datas_10015 = msg.Split(';');
+            int cellIndex = int.Parse(datas_10015[1]);
+
+            string strs = datas_10015[2];
+            string[] values = strs.Split(',');
+            Dispatcher.Invoke(() =>
+            {
+                GetSingleCell(cellIndex).SetInfos(new Info
+                {
+                    step = values[0],
+                    value = values[1],
+                    limitUp = values[2],
+                    limitDown = values[3],
+                    unit = values[4],
+                    result = values[5],
+                    updateTime = values[6]
+                });
+            });
+            //session.Send("1");
+        }
+
+        public void RefreshCount(int ok, int ng)
+        {
+            infoTable.UpdateNumber(infoTable.okCount + ok, infoTable.ngCount + ng);
+
+            if (ParamSetting.ProbeByModel)
+            {
+
+            }
+            else
+            {
+                IniFileHelper.WriteIniData($"Oven_{index}", "Pass", infoTable.okCount.ToString(),
+                   this.CountIniPath);
+                IniFileHelper.WriteIniData($"Oven_{index}", "Fail", infoTable.ngCount.ToString(),
+                    this.CountIniPath);
+            }
+        }
+
+        /// <summary>
+        /// 璇诲彇淇濆瓨鐨勬暟鎹�
+        /// </summary>
+        public void ReadCount()
+        {
+            try
+            {
+                int ok = Convert.ToInt32(IniFileHelper.ReadIniData($"Oven_{index}", "Pass", "", CountIniPath));
+                int ng = Convert.ToInt32(IniFileHelper.ReadIniData($"Oven_{index}", "Fail", "", CountIniPath));
+                infoTable.UpdateNumber(ok, ng);
+            }
+            catch (Exception ex)
+            {
+                //do nothing
+            }
+        }
+
+        /// <summary>
+        /// 璁℃暟娓呴浂
+        /// </summary>
+        public void ClearNumber()
+        {
+            infoTable.UpdateNumber(0, 0);
+            IniFileHelper.WriteIniData($"Oven_{index}", "Pass", infoTable.okCount.ToString(),
+                this.CountIniPath);
+            IniFileHelper.WriteIniData($"Oven_{index}", "Fail", infoTable.ngCount.ToString(),
+                this.CountIniPath);
+        }
+
+        private void UserControl_Loaded(object sender, RoutedEventArgs e)
+        {
+            StartTcpServer();
+            Loaded -= UserControl_Loaded;
+        }
+
+        private void Button_Click(object sender, RoutedEventArgs e)
+        {
+            StartTcpServer();
+        }
+
+        public void SaveTempFile(string filePath)
+        {
+            try
+            {
+                FileInfo fi = new FileInfo(filePath);
+                if (!fi.Directory.Exists)
+                {
+                    fi.Directory.Create();  //鏂囦欢涓嶅瓨鍦紝鍒涘缓鏂囦欢
+                }
+
+                using (FileStream fs = new FileStream(filePath, FileMode.Create, FileAccess.Write))
+                using (StreamWriter sw = new StreamWriter(fs, System.Text.Encoding.UTF8))
+                {
+                    string title = string.Join(",", detailTempratures.Keys) + "," + this.lang("Tr.AvgTemp");
+                    sw.WriteLine(title);
+
+                    for (int i = 0; i < tempratures.Count; i++)
+                    {
+                        string line = "";
+                        foreach (string key in detailTempratures.Keys)
+                        {
+                            if (detailTempratures[key] == null || detailTempratures[key].Count <= i)
+                            {
+                                line += ",";
+                            }
+                            else
+                            {
+                                line += $"{detailTempratures[key][i]},";
+                            }
+                        }
+                        line += tempratures[i];
+                        sw.WriteLine(line);
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                Console.WriteLine(ex.ToString());
+            }
+        }
+
+        public void SetProductSource(List<string> productSource)
+        {
+            CbProduct.ItemsSource = productSource;
+        }
+
+        public void SetCurrentProduct(string currentProduct)
+        {
+            CbProduct.SelectedItem = currentProduct;
+        }
+
+        private void CbProduct_SelectionChanged(object sender, SelectionChangedEventArgs e)
+        {
+            if (e.AddedItems.Count > 0)
+            {
+                var str = e.AddedItems[0];
+                parent.SetOvenProduct(index, str.ToString());
+            }
+            else
+            {
+                parent.SetOvenProduct(index, "");
+            }
+
+            foreach (var cell in cells)
+            {
+                cell.RefreshProbe();
+                cell.RefreshCharts();
+            }
+        }
+
+        public void ClearCellData()
+        {
+            foreach (var cell in cells)
+            {
+                cell.ClearChart();
+            }
+        }
+
+        //public void ClearElectricData()
+        //{
+        //    foreach (var cell in cells)
+        //    {
+        //        cell.ClearElectricData();
+        //    }
+        //}
+
+        //public void ClearVData()
+        //{
+        //    foreach (var cell in cells)
+        //    {
+        //        cell.ClearVData();
+        //    }
+        //}
+
+        //public void ClearWaterData()
+        //{
+        //    foreach (var cell in cells)
+        //    {
+        //        cell.ClearWaterData();
+        //    }
+        //}
+
+        private void Button_Click_1(object sender, RoutedEventArgs e)
+        {
+            ScannerSetting setting = new ScannerSetting()
+            {
+                Owner = Window.GetWindow(this)
+            };
+            setting.Open(true, this.title);
+        }
+
+        private void Manual_Cooler_Opt(object sender, RoutedEventArgs e)
+        {
+            //var btn = (Button)sender;
+            //var target = CoolerStatus == 2 ? 0 : 2;
+            //if (MsgBoxHelper2.WarnChoose($"纭畾瑕佽繘琛寋btn.Content}鍚楋紵") == MessageBoxResult.OK)
+            //{
+            //    if (S7Context.WriteData("1", $"Oven{index + 1}_CoolerStatus", (ushort)target, out string msg))
+            //    {
+            //        CoolerStatus = (ushort)target;
+            //        ToastHelper.Success("Success");
+            //    }
+            //    else
+            //    {
+            //        ToastHelper.Error($"{msg}");
+            //    }
+            //}
+            if (parent.IsRunning)
+            {
+                MsgBoxHelper2.Warn(this.lang("Tr.CantOptCooler"));
+                return;
+            }
+            var columns = mainGrid.Columns;
+            var window = new CoolerStatus(this.index, columns, title + this.lang("Tr.CoolerSetting"))
+            {
+
+                Owner = Window.GetWindow(this)
+            };
+            window.ShowDialog();
+        }
+
+        private void ClearAllSnr_Click(object sender, RoutedEventArgs e)
+        {
+            if (GetDoorClose(parent.executionWrapper))
+            {
+                /// 鍏抽棬浜嗗氨涓嶅厑璁告竻鏉$爜
+                MsgBoxHelper2.Warn(this.lang("Tr.CantClearSNR"));
+                return;
+            }
+
+            if (MsgBoxHelper.WarnMessage(Window.GetWindow(this), LangUtil.Get("Tr.MakeSureClearAllSNR")) == MessageBoxResult.OK)
+            {
+                try
+                {
+                    ClearAllSN();
+                    ToastHelper.Success(LangUtil.Get("App.DeleteSuccess"));
+                }
+                catch (Exception ex)
+                {
+                    BurnInUI.Logger.Error("ClearAllSnr_Click Error.", ex);
+                    MsgBoxHelper.Error(Window.GetWindow(this), ex.Message);
+                }
+            }
+        }
+
+        public void ClearAllSN()
+        {
+            for (int i = 0; i < this.cellCount; i++)
+            {
+                ClearCellSN(i);
+            }
+        }
+
+        public void TryClearCellSN(int cellIndex)
+        {
+            if (GetDoorClose(parent.executionWrapper))
+            {
+                MsgBoxHelper.Error(Window.GetWindow(this), this.lang("Tr.CantClearSNR"));
+                return;
+            }
+            ClearCellSN(cellIndex);
+        }
+
+        private void ClearCellSN(int index)
+        {
+            var cell = GetSingleCell(index);
+            string LookupStr = $"StationHMI.UUTHMI[{this.index}].SN[{index}]";
+            var lastSN = cell.sn;
+            cell.SetStatus("waiting");
+
+            parent.executionWrapper.SetStationGlobalValue<string>(LookupStr, 1, "");
+            //parent.executionWrapper.CommitToDisk(false);
+
+            //parent.engine.Globals.SetValString(LookupStr, 1, "");
+            //parent.engine.CommitGlobalsToDisk(promptOnSaveConflicts: false);
+            if (!string.IsNullOrEmpty(lastSN)) Scanner.RemoveSNR(lastSN);
+            cell.ClearSN();
+        }
+
+        private void Abort_Click(object sender, RoutedEventArgs e)
+        {
+            if (!parent.IsRunning)
+            {
+                MsgBoxHelper.WarnMessage(this, this.lang("Tr.StartSeqFirst"), null, MessageBoxButton.OK);
+                return;
+            }
+
+            //string key = $"StationState.Start[{this.index}]";
+            //if (parent.executionWrapper?.GetStationGlobalValue<bool>(key, 0) == true)
+            //{
+            //    MsgBoxHelper2.Warn("娴嬭瘯鍚姩涓紝涓嶅厑璁窤bort");
+            //    return;
+            //}
+            var win = new RequirePasswordByUser("admin")
+            {
+                Owner = Window.GetWindow(this)
+            };
+            if (win.ShowDialog() != true)
+            {
+                return;
+            }
+
+            if (MsgBoxHelper2.WarnChoose("璇风‘璁よ�佸寲浠撳湪瀹夊叏娓╁害锛屾按鍐锋満鍚规按瀹屾垚锛屽崟鍑籓K鎸夐挳浠撲綅鍥炲埌鍒濆鍖栫姸鎬�") != MessageBoxResult.OK)
+            {
+                return;
+            }
+            LogHelper.Info("UI", $"{title} Abort鎸夐挳鎸変笅", out _);
+            BurnInUI.Logger.Info($"{title} Abort鎸夐挳鎸変笅");
+            string key = $"StationHMI.UUTHMI[{this.index}].Abort";
+            parent.executionWrapper?.SetStationGlobalValue(key, 0, true);
+        }
+    }
+}
diff --git a/Plugin.BurnIn/InfoTable.xaml b/Plugin.BurnIn/InfoTable.xaml
new file mode 100644
index 0000000..8d6ae8a
--- /dev/null
+++ b/Plugin.BurnIn/InfoTable.xaml
@@ -0,0 +1,92 @@
+锘�<UserControl x:Class="Plugin.BurnIn.InfoTable"
+             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:Plugin.BurnIn"
+             xmlns:hb="clr-namespace:HMI.Base;assembly=HMI.Base"
+xmlns:uic="clr-namespace:HMI.UI.Converter;assembly=HMI.UI"
+xmlns:pw="https://opensource.panuon.com/wpf"
+xmlns:pu="https://opensource.panuon.com/wpf-ui"
+xmlns:ui="clr-namespace:HMI.UI;assembly=HMI.UI"
+xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI"
+             mc:Ignorable="d"
+             d:DesignHeight="450" d:DesignWidth="800">
+    <UserControl.Resources>
+        <Style TargetType="Label">
+            <Setter Property="VerticalContentAlignment" Value="Center"/>
+            <Setter Property="FontWeight" Value="Bold"/>
+        </Style>
+        <Style TargetType="TextBox">
+            <Setter Property="VerticalContentAlignment" Value="Center"/>
+            <Setter Property="FontWeight" Value="Bold"/>
+            <Setter Property="IsReadOnly" Value="True"/>
+            <Setter Property="BorderThickness" Value="0"/>
+        </Style>
+    </UserControl.Resources>
+    <Grid x:Name="infoGrid" Grid.Row="0" dp:GridHelper.InnerBorder="1" 
+          dp:GridHelper.InnerBorderIgnore="Top,Right,Bottom">
+        <Grid.RowDefinitions>
+            <RowDefinition/>
+            <RowDefinition/>
+        </Grid.RowDefinitions>
+        <Grid.ColumnDefinitions>
+            <ColumnDefinition Width="Auto"/>
+            <ColumnDefinition Width="1.5*"/>
+            <ColumnDefinition Width="Auto"/>
+            <ColumnDefinition Width="1.5*"/>
+            <ColumnDefinition Width="Auto"/>
+            <ColumnDefinition Width="2*"/>
+        </Grid.ColumnDefinitions>
+        <Label Grid.Row="0" Grid.Column="0" Content="{DynamicResource Tr.Temperature}"/>
+        <Label Grid.Row="0" Grid.Column="1" Background="{DynamicResource CellValue}" Content="{Binding temperatureStr}"/>
+        <Label Grid.Row="0" Grid.Column="2" Content="{DynamicResource Tr.Status}"/>
+        <Label Grid.Row="0" Grid.Column="3" Content="{Binding statusStr}">
+            <Label.Style>
+                <Style TargetType="Label">
+                    <Setter Property="FontWeight" Value="Bold"/>
+                    <Setter Property="VerticalContentAlignment" Value="Center"/>
+                    <Style.Triggers>
+                        <DataTrigger Binding="{Binding status}" Value="ok">
+                            <Setter Property="Background" Value="#FF0F8853"/>
+                        </DataTrigger>
+                        <DataTrigger Binding="{Binding status}" Value="ng">
+                            <Setter Property="Background" Value="#fd6b6d"/>
+                        </DataTrigger>
+                        <DataTrigger Binding="{Binding status}" Value="checkInFailed">
+                            <Setter Property="Background" Value="#ffac00"/>
+                        </DataTrigger>
+                        <DataTrigger Binding="{Binding status}" Value="checkIn">
+                            <Setter Property="Background" Value="#FF00E2FF"/>
+                        </DataTrigger>
+                        <DataTrigger Binding="{Binding status}" Value="testing">
+                            <Setter Property="Background" Value="#1296db"/>
+                        </DataTrigger>
+                        <DataTrigger Binding="{Binding status}" Value="waiting">
+                            <Setter Property="Background" Value="#A5A5A5"/>
+                        </DataTrigger>
+                        <DataTrigger Binding="{Binding status}" Value="alert">
+                            <Setter Property="Background" Value="#e04733"/>
+                        </DataTrigger>
+                        <DataTrigger Binding="{Binding status}" Value="ready">
+                            <Setter Property="Background" Value="#FF08FF00"/>
+                        </DataTrigger>
+                        <DataTrigger Binding="{Binding status}" Value="abort">
+                            <Setter Property="Background" Value="Black"/>
+                            <Setter Property="Foreground" Value="White"/>
+                        </DataTrigger>
+                    </Style.Triggers>
+                </Style>
+            </Label.Style>
+        </Label>
+        <Label Grid.Row="0" Grid.Column="4" Content="{DynamicResource Tr.Count}"/>
+        <Label Grid.Row="0" Grid.Column="5" Background="{DynamicResource CellValue}" Content="{Binding countStr}"/>
+
+        <Label Grid.Row="1" Grid.Column="0" Content="{DynamicResource Tr.StartTime}"/>
+        <Label Grid.Row="1" Grid.Column="1" Background="{DynamicResource CellValue}" Content="{Binding startTimeStr}"/>
+        <Label Grid.Row="1" Grid.Column="2" Content="{DynamicResource Tr.ElapsedTime}"/>
+        <Label Grid.Row="1" Grid.Column="3" Background="{DynamicResource CellValue}" Content="{Binding consumeTimeStr}"/>
+        <Label Grid.Row="1" Grid.Column="4" Content="{DynamicResource Tr.EstimatedEndTime}"/>
+        <Label Grid.Row="1" Grid.Column="5" Background="{DynamicResource CellValue}" Content="{Binding endTimeStr}"/>
+    </Grid>
+</UserControl>
diff --git a/Plugin.BurnIn/InfoTable.xaml.cs b/Plugin.BurnIn/InfoTable.xaml.cs
new file mode 100644
index 0000000..83f3472
--- /dev/null
+++ b/Plugin.BurnIn/InfoTable.xaml.cs
@@ -0,0 +1,203 @@
+锘縰sing Plugin.BurnIn.Constant;
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+using System.Windows.Controls;
+
+namespace Plugin.BurnIn
+{
+    /// <summary>
+    /// InfoTable.xaml 鐨勪氦浜掗�昏緫
+    /// </summary>
+    public partial class InfoTable : UserControl, INotifyPropertyChanged
+    {
+
+        #region ...
+        public event PropertyChangedEventHandler PropertyChanged;
+
+        public virtual void RaisePropertyChanged(string name)
+        {
+            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
+        }
+
+        public void RaiseAndSetIfchanged<T>(ref T old, T @new, [CallerMemberName] string propertyName = null)
+        {
+            old = @new;
+            if (propertyName != null) RaisePropertyChanged(propertyName);
+        }
+        #endregion
+
+        private double _temperature;
+        public double temperature
+        {
+            get => _temperature;
+            set
+            {
+                RaiseAndSetIfchanged(ref _temperature, value);
+                RaisePropertyChanged("temperatureStr");
+            }
+        }
+
+        public string temperatureStr
+        {
+            get => $"{_temperature.ToString("f2")}鈩�";
+        }
+
+        private string _status;
+        public string status
+        {
+            get => _status;
+            set
+            {
+                RaiseAndSetIfchanged(ref _status, value);
+                RaisePropertyChanged("statusStr");
+            }
+        }
+
+        public string statusStr
+        {
+            get => status != null && CommonStatus.STATUS_MAP.ContainsKey(status) ? CommonStatus.STATUS_MAP[status] : "";
+        }
+
+        private int _okCount;
+        public int okCount
+        {
+            get => _okCount;
+            set
+            {
+                RaiseAndSetIfchanged(ref _okCount, value);
+            }
+        }
+
+        private int _ngCount;
+
+        public int ngCount
+        {
+            get => _ngCount;
+            set
+            {
+                RaiseAndSetIfchanged(ref _ngCount, value);
+            }
+        }
+
+        private int _allCount;
+
+        public int allCount
+        {
+            get => _allCount;
+            set
+            {
+                RaiseAndSetIfchanged(ref _allCount, value);
+            }
+        }
+
+        public string countStr
+        {
+            get => $"OK:{okCount},NG:{ngCount},ALL:{allCount}";
+        }
+
+        private string _startTimeStr;
+        public string startTimeStr
+        {
+            get => _startTimeStr;
+            set
+            {
+                RaiseAndSetIfchanged(ref _startTimeStr, value);
+            }
+        }
+
+        private double _consumeTime;
+        public double consumeTime
+        {
+            get => _consumeTime;
+            set
+            {
+                RaiseAndSetIfchanged(ref _consumeTime, value);
+            }
+        }
+
+        private double? _leftTime;
+        public double? leftTime
+        {
+            get => _leftTime;
+            set
+            {
+                RaiseAndSetIfchanged(ref _leftTime, value);
+            }
+        }
+
+        public string consumeTimeStr
+        {
+            get
+            {
+                var str1 = ConvertDurtion(consumeTime);
+                if (leftTime != null)
+                {
+                    str1 += $"/{ConvertDurtion(leftTime.Value)}";
+                }
+                return str1;
+            }
+        }
+
+        private string ConvertDurtion(double seconds)
+        {
+            if (seconds <= 0)
+            {
+                return "0s";
+            }
+            if (seconds <= 60)
+            {
+                return $"{seconds.ToString("f2")}s";
+            }
+            else if (seconds <= 3600)
+            {
+                var min = Math.Floor(seconds / 60);
+                seconds = seconds % 60;
+                if (seconds > 0)
+                {
+                    return $"{min}m{seconds.ToString("f2")}s";
+                }
+                return $"{min}m";
+            }
+            else
+            {
+                var hour = Math.Floor(seconds / 3600);
+                var min = Math.Floor((seconds % 3600) / 60);
+                seconds = seconds % 60;
+                if (seconds > 0)
+                {
+                    return $"{hour}h{min}m{seconds.ToString("f2")}s";
+                }
+                return $"{hour}h{min}m";
+            }
+        }
+
+        private string _endTimeStr;
+
+        public string endTimeStr
+        {
+            get => _endTimeStr;
+            set => RaiseAndSetIfchanged(ref _endTimeStr, value);
+        }
+
+        public void UpdateNumber(int ok, int ng)
+        {
+            this._okCount = ok;
+            this._ngCount = ng;
+            this._allCount = this._okCount + this._ngCount;
+            RaisePropertyChanged("countStr");
+        }
+
+        public void UpdateConsumeTime(double consumeTime, double? leftTime)
+        {
+            this.consumeTime = consumeTime;
+            this.leftTime = leftTime;
+            RaisePropertyChanged("consumeTimeStr");
+        }
+
+        public InfoTable()
+        {
+            this.DataContext = this;
+            InitializeComponent();
+        }
+    }
+}
diff --git a/Plugin.BurnIn/InfoWindow.xaml b/Plugin.BurnIn/InfoWindow.xaml
new file mode 100644
index 0000000..949678a
--- /dev/null
+++ b/Plugin.BurnIn/InfoWindow.xaml
@@ -0,0 +1,117 @@
+锘�<pu:WindowX x:Class="Plugin.BurnIn.InfoWindow"
+        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+        xmlns:local="clr-namespace:Plugin.BurnIn"
+        mc:Ignorable="d"
+        Style="{StaticResource ModalWindow}"
+        xmlns:hb="clr-namespace:HMI.Base;assembly=HMI.Base"
+        xmlns:uic="clr-namespace:HMI.UI.Converter;assembly=HMI.UI"
+        xmlns:pw="https://opensource.panuon.com/wpf"
+        xmlns:pu="https://opensource.panuon.com/wpf-ui"
+        xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI"
+        xmlns:cc="clr-namespace:HMI.Core;assembly=HMI.Core"
+        InteropOwnersMask="False"
+        Topmost="False"
+        Loaded="Window_Loaded"
+        SizeToContent="Height"
+        Unloaded="Window_Unloaded"
+        SizeChanged="Window_SizeChanged"
+        pu:WindowXModalDialog.Buttons="{x:Null}"
+        Title="{Binding title}" Width="800" Height="825">
+    <Grid x:Name="mainGrid">
+        <Grid.RowDefinitions>
+            <RowDefinition Height="500"/>
+            <RowDefinition Height="*"/>
+            <RowDefinition Height="*"/>
+            <RowDefinition Height="*"/>
+            <RowDefinition Height="25"/>
+        </Grid.RowDefinitions>
+        <DataGrid ItemsSource="{Binding dataSource}" AutoGenerateColumns="False"
+              CanUserAddRows="False"
+              CanUserDeleteRows="False"
+              IsReadOnly="True"
+              ColumnWidth="*"
+              VirtualizingPanel.IsContainerVirtualizable="True"
+              VirtualizingPanel.IsVirtualizing="True"
+              VirtualizingStackPanel.VirtualizationMode="Recycling"
+              FontSize="16">
+            <DataGrid.Resources>
+                <Style TargetType="ScrollBar" BasedOn="{StaticResource ScrollStyle_Mobile}"/>
+            </DataGrid.Resources>
+            <DataGrid.Columns>
+                <!--<DataGridTemplateColumn Header="#" CanUserResize="False" Width="Auto">
+                    <DataGridTemplateColumn.CellTemplate>
+                        <DataTemplate>
+                            <TextBlock Text="{Binding Converter={StaticResource rowNumber},
+RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type DataGridRow},AncestorLevel=1}}"/>
+                        </DataTemplate>
+                    </DataGridTemplateColumn.CellTemplate>
+                </DataGridTemplateColumn>-->
+                <DataGridTextColumn Binding="{Binding step}">
+                    <DataGridTextColumn.Header>
+                        <TextBlock Text="{DynamicResource Test.Name}"/>
+                    </DataGridTextColumn.Header>
+                </DataGridTextColumn>
+                <DataGridTextColumn Binding="{Binding value}">
+                    <DataGridTextColumn.Header>
+                        <TextBlock Text="{DynamicResource Test.Value}"/>
+                    </DataGridTextColumn.Header>
+                </DataGridTextColumn>
+                <DataGridTextColumn Binding="{Binding limitUp}">
+                    <DataGridTextColumn.Header>
+                        <TextBlock Text="{DynamicResource Test.LimitUp}"/>
+                    </DataGridTextColumn.Header>
+                </DataGridTextColumn>
+                <DataGridTextColumn Binding="{Binding limitDown}">
+                    <DataGridTextColumn.Header>
+                        <TextBlock Text="{DynamicResource Test.LimitDown}"/>
+                    </DataGridTextColumn.Header>
+                </DataGridTextColumn>
+                <DataGridTextColumn Binding="{Binding unit}">
+                    <DataGridTextColumn.Header>
+                        <TextBlock Text="{DynamicResource Test.Unit}"/>
+                    </DataGridTextColumn.Header>
+                </DataGridTextColumn>
+                <DataGridTextColumn Binding="{Binding result}">
+                    <DataGridTextColumn.Header>
+                        <TextBlock Text="{DynamicResource Test.Result}"/>
+                    </DataGridTextColumn.Header>
+                </DataGridTextColumn>
+                <DataGridTextColumn Binding="{Binding updateTime}">
+                    <DataGridTextColumn.Header>
+                        <TextBlock Text="{DynamicResource Test.TestTime}"/>
+                    </DataGridTextColumn.Header>
+                </DataGridTextColumn>
+            </DataGrid.Columns>
+        </DataGrid>
+
+        <Grid Background="#f1eded" Grid.Row="4">
+            <DockPanel>
+                <StackPanel DockPanel.Dock="Right" Orientation="Horizontal" Margin="0,0,30,0">
+                    <Label Padding="0" FontSize="12" 
+VerticalAlignment="Center"
+VerticalContentAlignment="Center" Content="{DynamicResource Ts.ProbeCount}"/>
+                    <Label Padding="0" FontSize="12"  Margin="0,0,15,0"
+VerticalAlignment="Center"
+VerticalContentAlignment="Center" Content="{Binding ProbeCount}"/>
+
+
+                    <Label x:Name="alarmLabel" Padding="0" FontSize="12" 
+VerticalAlignment="Center"
+VerticalContentAlignment="Center" Content="{DynamicResource Ts.ProbeAlarmCount}"/>
+                    <Label x:Name="alarmValue" Padding="0" FontSize="12" 
+VerticalAlignment="Center"
+VerticalContentAlignment="Center" Content="{Binding ProbeWarn}"/>
+
+                    <Button Style="{StaticResource LinkButton}" Margin="10,0,0,0" Click="Button_Click" >娓呯┖</Button>
+                </StackPanel>
+
+                <Label x:Name="probeCountAlarm" Visibility="Collapsed" DockPanel.Dock="Left" Padding="30,0" FontSize="12" Foreground="Red"
+VerticalAlignment="Center"
+VerticalContentAlignment="Center" Content="{DynamicResource Ts.ProbeAlarm}"/>
+            </DockPanel>
+        </Grid>
+    </Grid>
+</pu:WindowX>
diff --git a/Plugin.BurnIn/InfoWindow.xaml.cs b/Plugin.BurnIn/InfoWindow.xaml.cs
new file mode 100644
index 0000000..afc7eb1
--- /dev/null
+++ b/Plugin.BurnIn/InfoWindow.xaml.cs
@@ -0,0 +1,176 @@
+锘縰sing HMI.Core.Util;
+using HMI.UI.Util;
+using Panuon.WPF.UI;
+using Plugin.BurnIn.Model;
+using System.Collections.ObjectModel;
+using System.Windows;
+using System.Windows.Controls;
+
+namespace Plugin.BurnIn
+{
+    /// <summary>
+    /// InfoWindow.xaml 鐨勪氦浜掗�昏緫
+    /// </summary>
+    public partial class InfoWindow : WindowX
+    {
+
+        private string _title;
+        public string title
+        {
+            get => _title;
+            set => Set(ref _title, value);
+        }
+
+        private ObservableCollection<Info> _dataSource;
+
+        public ObservableCollection<Info> dataSource
+        {
+            get => _dataSource == null ? new ObservableCollection<Info>() : _dataSource;
+            set => Set(ref _dataSource, value);
+        }
+        private LiveChart waterChart;
+        private LiveChart electricChart;
+        private LiveChart vChart;
+
+        //public InfoWindow(string title, ObservableCollection<Info> infos, Chart waterChart, Chart electricChart, Chart vChart, int probeCount, int probeWarn)
+        //{
+        //    this.title = title;
+        //    this.dataSource = infos;
+        //    DataContext = this;
+        //    Topmost = false;
+        //    this.ProbeWarn = probeWarn;
+        //    InitializeComponent();
+
+
+        //    UpdateProbeCount(probeCount,probeWarn);
+
+        //    if (waterChart != null)
+        //    {
+        //        this.waterChart = waterChart;
+        //        mainGrid.Children.Add(waterChart);
+        //        Grid.SetRow(waterChart, 1);
+        //    }
+
+        //    this.electricChart = electricChart;
+        //    mainGrid.Children.Add(electricChart);
+        //    Grid.SetRow(electricChart, 2);
+
+
+        //    this.vChart = vChart;
+        //    mainGrid.Children.Add(vChart);
+        //    Grid.SetRow(vChart, 3);
+        //}
+
+        public InfoWindow(string title, ObservableCollection<Info> infos, LiveChart[] charts, int probeCount, int probeWarn)
+        {
+            this.title = title;
+            this.dataSource = infos;
+            DataContext = this;
+            Topmost = false;
+            this.ProbeWarn = probeWarn;
+            InitializeComponent();
+
+
+            UpdateProbeCount(probeCount, probeWarn);
+            int row = 1;
+            foreach (var chart in charts)
+            {
+                if (chart == null) return;
+                mainGrid.Children.Add(chart);
+                Grid.SetRow(chart, row++);
+            }
+
+            //if (waterChart != null)
+            //{
+            //    this.waterChart = waterChart;
+            //    mainGrid.Children.Add(waterChart);
+            //    Grid.SetRow(waterChart, 1);
+            //}
+
+            //this.electricChart = electricChart;
+            //mainGrid.Children.Add(electricChart);
+            //Grid.SetRow(electricChart, 2);
+
+            //this.vChart = vChart;
+            //mainGrid.Children.Add(vChart);
+            //Grid.SetRow(vChart, 3);
+        }
+
+        public event EventHandler ClearProbeCountEvent;
+
+        private int probeCount;
+        public int ProbeCount
+        {
+            get => probeCount;
+            set
+            {
+                Set(ref probeCount, value);
+            }
+        }
+
+        public int ProbeWarn
+        {
+            get;
+            set;
+        }
+
+        public void UpdateProbeCount(int count, int warn)
+        {
+            Dispatcher.Invoke(() =>
+            {
+                ProbeCount = count;
+                ProbeWarn = warn;
+
+                if (ProbeWarn < 0)
+                {
+                    alarmLabel.Visibility = Visibility.Collapsed;
+                    alarmValue.Visibility = Visibility.Collapsed;
+                }
+                else
+                {
+                    probeCountAlarm.Visibility = (ProbeCount >= ProbeWarn) ? Visibility.Visible : Visibility.Collapsed;
+                }
+            });
+        }
+
+        public void UpdateSource(ObservableCollection<Info> infos)
+        {
+            this.dataSource = infos;
+        }
+
+        private void Window_Loaded(object sender, RoutedEventArgs e)
+        {
+            DataContext = this;
+        }
+
+        private void Window_Unloaded(object sender, RoutedEventArgs e)
+        {
+            DataContext = null;
+        }
+
+        protected override void OnClosed(EventArgs e)
+        {
+            waterChart = null;
+            electricChart = null;
+            vChart = null;
+            mainGrid.Children.Clear();
+            base.OnClosed(e);
+        }
+
+        private void Window_SizeChanged(object sender, SizeChangedEventArgs e)
+        {
+            //if (electricChart != null)
+            //{
+            //    electricChart.Height = this.ActualHeight * 0.3;
+            //}
+        }
+
+        private void Button_Click(object sender, RoutedEventArgs e)
+        {
+            if (MsgBoxHelper.WarnMessage(this, this.lang("Ts.MakeSureToClearProbeCount")) == MessageBoxResult.OK)
+            {
+                ClearProbeCountEvent?.Invoke(this, null);
+            }
+        }
+    }
+}
diff --git a/Plugin.BurnIn/ManualDebugWindow.xaml b/Plugin.BurnIn/ManualDebugWindow.xaml
new file mode 100644
index 0000000..ba5a019
--- /dev/null
+++ b/Plugin.BurnIn/ManualDebugWindow.xaml
@@ -0,0 +1,21 @@
+锘�<pu:WindowX x:Class="Plugin.BurnIn.ManualDebugWindow"
+        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+        mc:Ignorable="d"
+        xmlns:sb="clr-namespace:Util.SB;assembly=Util.ManualDebug"
+        Title="ManualDebugWindow" 
+        xmlns:pu="https://opensource.panuon.com/wpf-ui"
+        Style="{StaticResource ModalWindow}"
+        Closing="Window_Closing"
+        WindowStartupLocation="CenterScreen"
+        pu:WindowXModalDialog.Buttons="{x:Null}"
+        Height="1000" Width="900">
+    <Grid x:Name="sb">
+        <sb:ManualDebug OvenName="{Binding OvenName,Mode=OneWay}" 
+    CellName="{Binding CellName,Mode=OneWay}" 
+    OvenIndex="{Binding OvenIndex,Mode=OneWay}" 
+    CellIndex="{Binding CellIndex,Mode=OneWay}"/>
+    </Grid>
+</pu:WindowX>
diff --git a/Plugin.BurnIn/ManualDebugWindow.xaml.cs b/Plugin.BurnIn/ManualDebugWindow.xaml.cs
new file mode 100644
index 0000000..3118483
--- /dev/null
+++ b/Plugin.BurnIn/ManualDebugWindow.xaml.cs
@@ -0,0 +1,93 @@
+锘縰sing HMI.Util;
+using Panuon.WPF.UI;
+using System.ComponentModel;
+using System.IO;
+using System.Windows;
+using Util.SB;
+
+namespace Plugin.BurnIn
+{
+    /// <summary>
+    /// ManualDebugWindow.xaml 鐨勪氦浜掗�昏緫
+    /// </summary>
+    public partial class ManualDebugWindow : WindowX
+    {
+
+        private ManualDebug debug;
+
+        public ManualDebugWindow(string seqPath, string modelPath)
+        {
+            DataContext = this;
+            InitializeComponent();
+            debug = (ManualDebug)sb.Children[0];
+            ReadBtns();
+            //List<BtnConfig> settings = new List<BtnConfig> { new BtnConfig { Title = "涓婄數婵�娲�", Name = "Active" } };
+            debug.seqPath = seqPath;
+            debug.modelPath = modelPath;
+        }
+
+        private string ovenName;
+        public string OvenName
+        {
+            get => ovenName;
+            set
+            {
+                Set(ref ovenName, value);
+            }
+        }
+
+        private string cellName;
+        public string CellName
+        {
+            get => cellName;
+            set
+            {
+                Set(ref cellName, value);
+            }
+        }
+
+        private int ovenIndex;
+        public int OvenIndex
+        {
+            get => ovenIndex;
+            set
+            {
+                Set(ref ovenIndex, value);
+            }
+        }
+
+        private int cellIndex;
+        public int CellIndex
+        {
+            get => cellIndex;
+            set
+            {
+                Set(ref cellIndex, value);
+            }
+        }
+
+        private void ReadBtns()
+        {
+            string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Settings.ini");
+            var map = IniFileHelper.GetKeyValuesBySection(path, "Btns");
+            List<BtnConfig> settings = new List<BtnConfig>();
+            foreach (var kv in map)
+            {
+                settings.Add(new BtnConfig { Title = kv.Key, Name = kv.Value });
+            }
+            debug.RefreshButtons(settings);
+        }
+
+        private void Window_Closing(object sender, CancelEventArgs e)
+        {
+            if (MessageBox.Show("纭畾瑕佸叧闂悧锛�", "鎻愮ず", MessageBoxButton.OKCancel) == MessageBoxResult.OK)
+            {
+                debug.Stop();
+            }
+            else
+            {
+                e.Cancel = true;
+            }
+        }
+    }
+}
diff --git a/Plugin.BurnIn/Model/BaseConfig.cs b/Plugin.BurnIn/Model/BaseConfig.cs
new file mode 100644
index 0000000..631a81c
--- /dev/null
+++ b/Plugin.BurnIn/Model/BaseConfig.cs
@@ -0,0 +1,198 @@
+锘縰sing HMI.Core.Entity;
+using HMI.Core.Manager;
+using System;
+using System.Collections;
+using System.Configuration;
+using System.Runtime.CompilerServices;
+
+namespace Plugin.BurnIn.Model
+{
+    public class BaseConfig
+    {
+        private static Hashtable customConfig = (Hashtable)ConfigurationManager.GetSection("TestResultConfig");
+
+        private string GetValue([CallerMemberName] string propertyName = null)
+        {
+            return (customConfig != null && customConfig.ContainsKey(propertyName)) ? customConfig[propertyName].ToString() : null;
+        }
+
+        /// <summary>
+        /// 棣栭〉甯冨眬琛屾暟
+        /// </summary>
+        private int row = 1;
+        public int Row
+        {
+            get
+            {
+                var data = GetValue();
+                return data == null ? row : int.Parse(data);
+            }
+            set
+            {
+                customConfig[nameof(Row)] = value;
+            }
+        }
+
+        /// <summary>
+        /// 棣栭〉甯冨眬鍒楁暟
+        /// </summary>
+        private int column = 2;
+        public int Column
+        {
+            get
+            {
+                var data = GetValue();
+                return data == null ? column : int.Parse(data);
+            }
+            set
+            {
+                customConfig[nameof(Column)] = value;
+            }
+        }
+
+        /// <summary>
+        /// 鐐夋暟 -1 灏辨槸row*column
+        /// </summary>
+        private int cellCount = -1;
+        public int CellCount
+        {
+            get
+            {
+                var data = GetValue();
+                return data == null ? cellCount : int.Parse(data);
+            }
+            set
+            {
+                customConfig[nameof(CellCount)] = value;
+            }
+        }
+
+        /// <summary>
+        /// 鐐逛綅鍒楁暟
+        /// </summary>
+        private int positionColumn = 6;
+        public int PositionColumn
+        {
+            get
+            {
+                var data = GetValue();
+                return data == null ? positionColumn : int.Parse(data);
+            }
+            set
+            {
+                customConfig[nameof(PositionColumn)] = value;
+            }
+        }
+
+        /// <summary>
+        /// 姣忎釜鐐夊瓙鐨勭偣浣嶆暟
+        /// </summary>
+        private int positionCount = 54;
+        public int PositionCount
+        {
+            get
+            {
+                var data = GetValue();
+                return data == null ? positionCount : int.Parse(data);
+            }
+            set
+            {
+                customConfig[nameof(PositionCount)] = value;
+            }
+        }
+
+        /// <summary>
+        /// 鏇茬嚎鏈�澶氫繚瀛樺灏戞暟鎹�
+        /// </summary>
+        private int maxSize = 10000;
+        public int MaxSize
+        {
+            get
+            {
+                var data = GetValue();
+                return data == null ? maxSize : int.Parse(data);
+            }
+            set
+            {
+                customConfig[nameof(MaxSize)] = value;
+            }
+        }
+
+        /// <summary>
+        /// 鍥捐〃鍒锋柊棰戠巼 ms
+        /// </summary>
+        private int refreshInterval = 500;
+        public int RefreshInterval
+        {
+            get
+            {
+                var data = GetValue();
+                return data == null ? refreshInterval : int.Parse(data);
+            }
+            set
+            {
+                customConfig[nameof(RefreshInterval)] = value;
+            }
+        }
+
+        /// <summary>
+        /// 鐐夊瓙鍚嶇О
+        /// </summary>
+        private string nameStr = "A,B,C,D,E,F,G";
+        public string NameStr
+        {
+            get
+            {
+                var data = GetValue();
+                return data == null ? nameStr : data;
+            }
+            set
+            {
+                customConfig[nameof(NameStr)] = value;
+            }
+        }
+
+        /// <summary>
+        /// 绌翠綅Step鍒楄〃鏈�澶у閲�
+        /// </summary>
+        private int cellListMaxSize = 10000;
+        public int CellListMaxSize
+        {
+            get
+            {
+                var data = GetValue();
+                return data == null ? cellListMaxSize : int.Parse(data);
+            }
+            set
+            {
+                customConfig[nameof(CellListMaxSize)] = value;
+            }
+        }
+
+        private int portStart = 6660;
+        public int PortStart
+        {
+            get
+            {
+                var data = GetValue();
+                return data == null ? portStart : int.Parse(data);
+            }
+            set
+            {
+                customConfig[nameof(PortStart)] = value;
+            }
+        }
+
+        private string codeParserType;
+        public string CodeParserType
+        {
+            get
+            {
+                var data = GetValue();
+                return data == null ? codeParserType : data.ToString();
+            }
+        }
+
+        public bool testing = false;
+    }
+}
diff --git a/Plugin.BurnIn/Model/BaseModel.cs b/Plugin.BurnIn/Model/BaseModel.cs
new file mode 100644
index 0000000..e7cba9c
--- /dev/null
+++ b/Plugin.BurnIn/Model/BaseModel.cs
@@ -0,0 +1,21 @@
+锘縰sing System.ComponentModel;
+using System.Runtime.CompilerServices;
+
+namespace Plugin.BurnIn.Model
+{
+    public class BaseModel : INotifyPropertyChanged
+    {
+        public event PropertyChangedEventHandler PropertyChanged;
+
+        public virtual void RaisePropertyChanged(string name)
+        {
+            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
+        }
+
+        public void RaiseAndSetIfchanged<T>(ref T old, T @new, [CallerMemberName] string propertyName = null)
+        {
+            old = @new;
+            if (propertyName != null) RaisePropertyChanged(propertyName);
+        }
+    }
+}
diff --git a/Plugin.BurnIn/Model/CellInfo.cs b/Plugin.BurnIn/Model/CellInfo.cs
new file mode 100644
index 0000000..53b502d
--- /dev/null
+++ b/Plugin.BurnIn/Model/CellInfo.cs
@@ -0,0 +1,17 @@
+锘縩amespace Plugin.BurnIn.Model
+{
+    public class CellInfo
+    {
+        public string OriginCode { get; set; }
+        public string MachineName { get; set; }
+        /// <summary>
+        /// 鐐夊悕
+        /// </summary>
+        public string OvenName { get; set; }
+        /// <summary>
+        /// 绌翠綅涓嬫爣
+        /// </summary>
+        public int Index { get; set; }
+        public string LookUpStr { get; set; }
+    }
+}
diff --git a/Plugin.BurnIn/Model/DataTableModel.cs b/Plugin.BurnIn/Model/DataTableModel.cs
new file mode 100644
index 0000000..caf55ff
--- /dev/null
+++ b/Plugin.BurnIn/Model/DataTableModel.cs
@@ -0,0 +1,7 @@
+锘縩amespace Plugin.BurnIn.Model
+{
+    public class DataTableModel : BaseModel
+    {
+
+    }
+}
diff --git a/Plugin.BurnIn/Model/Info.cs b/Plugin.BurnIn/Model/Info.cs
new file mode 100644
index 0000000..f4259ec
--- /dev/null
+++ b/Plugin.BurnIn/Model/Info.cs
@@ -0,0 +1,69 @@
+锘縩amespace Plugin.BurnIn.Model
+{
+    public class Info : BaseModel
+    {
+        private string _step;
+
+        public string step
+        {
+            get => _step;
+
+            set => RaiseAndSetIfchanged(ref _step, value);
+        }
+
+        private string _value;
+
+        public string value
+        {
+            get => _value;
+
+            set => RaiseAndSetIfchanged(ref _value, value);
+        }
+
+        private string _unit;
+
+        public string unit
+        {
+            get => _unit;
+
+            set => RaiseAndSetIfchanged(ref _unit, value);
+        }
+
+        private string _result;
+
+        public string result
+        {
+            get => _result;
+
+            set => RaiseAndSetIfchanged(ref _result, value);
+        }
+
+        private string _limitDown;
+
+        public string limitDown
+        {
+            get => _limitDown;
+
+            set => RaiseAndSetIfchanged(ref _limitDown, value);
+        }
+
+        private string _limitUp;
+
+        public string limitUp
+        {
+            get => _limitUp;
+
+            set => RaiseAndSetIfchanged(ref _limitUp, value);
+        }
+
+        private string _updateTime;
+
+        public string updateTime
+        {
+            get => _updateTime;
+
+            set => RaiseAndSetIfchanged(ref _updateTime, value);
+        }
+
+    }
+}
diff --git a/Plugin.BurnIn/Model/ScannerConfig.cs b/Plugin.BurnIn/Model/ScannerConfig.cs
new file mode 100644
index 0000000..986e041
--- /dev/null
+++ b/Plugin.BurnIn/Model/ScannerConfig.cs
@@ -0,0 +1,34 @@
+锘縰sing HMI.Util;
+using System;
+using System.IO.Ports;
+
+namespace Plugin.BurnIn.Model
+{
+    public class ScannerConfig
+    {
+        public string PortName { get; set; }
+
+        public int BaudRate { get; set; }
+        public Parity Parity { get; set; } = Parity.None;
+        public int DataBits { get; set; }
+        public StopBits StopBits { get; set; } = StopBits.One;
+        public Handshake Handshake { get; set; } = Handshake.None;
+        public bool DtrEnable { get; set; } = true;
+        public bool RtsEnable { get; set; } = true;
+
+
+        public static ScannerConfig ReadFromIni(string iniFile, string section)
+        {
+            var res = new ScannerConfig();
+            res.PortName = IniFileHelper.ReadIniData(section, "PortName", "", iniFile);
+            res.BaudRate = int.Parse(IniFileHelper.ReadIniData(section, "BaudRate", "0", iniFile));
+            res.Parity = (Parity)Enum.Parse(typeof(Parity), IniFileHelper.ReadIniData(section, "Parity", "None", iniFile));
+            res.DataBits = int.Parse(IniFileHelper.ReadIniData(section, "DataBits", "0", iniFile));
+            res.StopBits = (StopBits)Enum.Parse(typeof(StopBits), IniFileHelper.ReadIniData(section, "StopBits", "One", iniFile));
+            res.Handshake = (Handshake)Enum.Parse(typeof(Handshake), IniFileHelper.ReadIniData(section, "Handshake", "None", iniFile));
+            res.DtrEnable = bool.Parse(IniFileHelper.ReadIniData(section, "DtrEnable", "true", iniFile));
+            res.RtsEnable = bool.Parse(IniFileHelper.ReadIniData(section, "RtsEnable", "true", iniFile));
+            return res;
+        }
+    }
+}
diff --git a/Plugin.BurnIn/Model/TcpServer.cs b/Plugin.BurnIn/Model/TcpServer.cs
new file mode 100644
index 0000000..0c11084
--- /dev/null
+++ b/Plugin.BurnIn/Model/TcpServer.cs
@@ -0,0 +1,59 @@
+锘縰sing SuperSocket.SocketBase;
+using SuperSocket.SocketBase.Protocol;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Plugin.BurnIn.Model
+{
+    public class DTReceiveFilter : ReceiveFilterBase<StringRequestInfo>
+    {
+        public override StringRequestInfo Filter(byte[] readBuffer, int offset, int length, bool toBeCopied, out int rest)
+        {
+            rest = 0;
+            int i = offset;
+            List<byte> bytes = new List<byte>();
+            while (i < readBuffer.Length)
+            {
+                byte b = readBuffer[i++];
+                if (b == '\n')
+                {
+                    break;
+                }
+                bytes.Add(b);
+            }
+            byte[] arr = bytes.ToArray();
+            return new StringRequestInfo(DateTime.Now.ToString("yyyyMMddHHmmssfff"), Encoding.UTF8.GetString(arr), null);//sb.ToString()
+        }
+    }
+
+    public class StringParser : IRequestInfoParser<StringRequestInfo>
+    {
+
+        public StringRequestInfo ParseRequestInfo(string source)
+        {
+            return new StringRequestInfo(DateTime.Now.ToString("yyyyMMddHHmmssfff"), source, null);
+        }
+    }
+
+    //public class TcpServer : AppServer<TcpSession>
+    //{
+    //    public TcpServer() : base(new DefaultReceiveFilterFactory<DTReceiveFilter, StringRequestInfo>())
+    //    {
+
+    //    }
+    //}
+
+    public class TcpServer : AppServer<TcpSession>
+    {
+        public TcpServer() : base(new TerminatorReceiveFilterFactory("\n", Encoding.UTF8, new StringParser()))
+        {
+
+        }
+    }
+
+    public class TcpSession : AppSession<TcpSession, StringRequestInfo>
+    {
+
+    }
+}
diff --git a/Plugin.BurnIn/Plugin.BurnIn.csproj b/Plugin.BurnIn/Plugin.BurnIn.csproj
new file mode 100644
index 0000000..3be23dc
--- /dev/null
+++ b/Plugin.BurnIn/Plugin.BurnIn.csproj
@@ -0,0 +1,70 @@
+锘�<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Library</OutputType>
+    <TargetFramework>net472</TargetFramework>
+    <Nullable>enable</Nullable>
+    <LangVersion>10.0</LangVersion>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <UseWPF>true</UseWPF>
+    <Platforms>AnyCPU;x64</Platforms>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
+    <PackageReference Include="LiveChartsCore" Version="2.0.0-rc5.4" />
+    <PackageReference Include="LiveChartsCore.SkiaSharpView" Version="2.0.0-rc5.4" />
+    <PackageReference Include="LiveChartsCore.SkiaSharpView.WPF" Version="2.0.0-rc5.4" />
+    <PackageReference Include="log4net" Version="3.0.4" />
+    <PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.135" />
+    <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
+    <PackageReference Include="Panuon.WPF.UI" Version="1.3.0.2" />
+    <PackageReference Include="SuperSocket" Version="1.6.6.1" />
+    <PackageReference Include="SuperSocket.Engine" Version="1.6.6.1" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\HMI.Base\HMI.Base.csproj" />
+    <ProjectReference Include="..\HMI.Core\HMI.Core.csproj" />
+    <ProjectReference Include="..\HMI.StdControl\HMI.StdControl.csproj" />
+    <ProjectReference Include="..\PLCHelper\PLCHelper.csproj" />
+    <ProjectReference Include="..\Plugin.TsSupport\Plugin.TsSupport.csproj" />
+    <ProjectReference Include="..\SimpleUtil\HMI.LogUtil\HMI.LogUtil.csproj" />
+    <ProjectReference Include="..\Util.ManualDebug\Util.ManualDebug.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Reference Include="HslCommunication">
+      <HintPath>..\Libs\HslCommunication.dll</HintPath>
+    </Reference>
+    <Reference Include="NationalInstruments.TestStand.Interop.API">
+      <HintPath>..\Libs\NationalInstruments.TestStand.Interop.API.dll</HintPath>
+    </Reference>
+    <Reference Include="NationalInstruments.TestStand.Interop.UI.AxControls">
+      <HintPath>..\Libs\NationalInstruments.TestStand.Interop.UI.AxControls.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Net.Http" />
+    <Reference Include="System.Windows.Forms" />
+    <Reference Include="WindowsFormsIntegration" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Resource Include="Resource\abort.png" />
+    <Resource Include="Resource\alert.png" />
+    <Resource Include="Resource\barcode.png" />
+    <Resource Include="Resource\checkIn.png" />
+    <Resource Include="Resource\checkInFailed2.png" />
+    <Resource Include="Resource\ng2.png" />
+    <Resource Include="Resource\ok2.png" />
+    <Resource Include="Resource\ready.png" />
+    <Resource Include="Resource\testing2.png" />
+    <Resource Include="Resource\waiting2.png" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Compile Update="TestResultView.xaml.cs">
+      <SubType>Code</SubType>
+    </Compile>
+  </ItemGroup>
+
+</Project>
diff --git a/Plugin.BurnIn/Resource/abort.png b/Plugin.BurnIn/Resource/abort.png
new file mode 100644
index 0000000..1b6b094
--- /dev/null
+++ b/Plugin.BurnIn/Resource/abort.png
Binary files differ
diff --git a/Plugin.BurnIn/Resource/alert.png b/Plugin.BurnIn/Resource/alert.png
new file mode 100644
index 0000000..6a9e531
--- /dev/null
+++ b/Plugin.BurnIn/Resource/alert.png
Binary files differ
diff --git a/Plugin.BurnIn/Resource/barcode.png b/Plugin.BurnIn/Resource/barcode.png
new file mode 100644
index 0000000..b68a9d6
--- /dev/null
+++ b/Plugin.BurnIn/Resource/barcode.png
Binary files differ
diff --git a/Plugin.BurnIn/Resource/checkIn.png b/Plugin.BurnIn/Resource/checkIn.png
new file mode 100644
index 0000000..eb4f984
--- /dev/null
+++ b/Plugin.BurnIn/Resource/checkIn.png
Binary files differ
diff --git a/Plugin.BurnIn/Resource/checkInFailed2.png b/Plugin.BurnIn/Resource/checkInFailed2.png
new file mode 100644
index 0000000..b4e6cfd
--- /dev/null
+++ b/Plugin.BurnIn/Resource/checkInFailed2.png
Binary files differ
diff --git a/Plugin.BurnIn/Resource/ng2.png b/Plugin.BurnIn/Resource/ng2.png
new file mode 100644
index 0000000..b1da88a
--- /dev/null
+++ b/Plugin.BurnIn/Resource/ng2.png
Binary files differ
diff --git a/Plugin.BurnIn/Resource/ok2.png b/Plugin.BurnIn/Resource/ok2.png
new file mode 100644
index 0000000..637da7a
--- /dev/null
+++ b/Plugin.BurnIn/Resource/ok2.png
Binary files differ
diff --git a/Plugin.BurnIn/Resource/ready.png b/Plugin.BurnIn/Resource/ready.png
new file mode 100644
index 0000000..df8dfd3
--- /dev/null
+++ b/Plugin.BurnIn/Resource/ready.png
Binary files differ
diff --git a/Plugin.BurnIn/Resource/testing2.png b/Plugin.BurnIn/Resource/testing2.png
new file mode 100644
index 0000000..531bff9
--- /dev/null
+++ b/Plugin.BurnIn/Resource/testing2.png
Binary files differ
diff --git a/Plugin.BurnIn/Resource/waiting2.png b/Plugin.BurnIn/Resource/waiting2.png
new file mode 100644
index 0000000..2fd9c21
--- /dev/null
+++ b/Plugin.BurnIn/Resource/waiting2.png
Binary files differ
diff --git a/Plugin.BurnIn/Scanner.xaml b/Plugin.BurnIn/Scanner.xaml
new file mode 100644
index 0000000..315cca4
--- /dev/null
+++ b/Plugin.BurnIn/Scanner.xaml
@@ -0,0 +1,78 @@
+锘�<pu:WindowX x:Class="Plugin.BurnIn.Scanner"
+        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+        xmlns:local="clr-namespace:Plugin.BurnIn"
+        WindowStartupLocation="CenterScreen"
+        Closing="Window_Closing"
+        FontSize="20"
+        mc:Ignorable="d"
+        Style="{StaticResource ModalWindow}"
+        xmlns:hb="clr-namespace:HMI.Base;assembly=HMI.Base"
+        xmlns:uic="clr-namespace:HMI.UI.Converter;assembly=HMI.UI"
+        xmlns:pw="https://opensource.panuon.com/wpf"
+        xmlns:pu="https://opensource.panuon.com/wpf-ui"
+        xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI"
+        xmlns:cc="clr-namespace:HMI.Core;assembly=HMI.Core"
+        InteropOwnersMask="False"
+        SizeToContent="Height"
+        pu:WindowXModalDialog.Buttons="{x:Null}"
+        Title="{Binding title}" Width="600">
+    <Window.Resources>
+        <local:FontSizeConverter x:Key="FontSizeConverter"/>
+        <Style TargetType="Label">
+            <Setter Property="HorizontalAlignment" Value="Right"/>
+            <Setter Property="FontSize" Value="{Binding ActualWidth,RelativeSource={RelativeSource AncestorType=Window}, 
+Converter={StaticResource FontSizeConverter}, ConverterParameter='80,1920'}" />
+        </Style>
+        <Style TargetType="TextBox">
+            <Setter Property="FontSize" Value="{Binding ActualWidth,RelativeSource={RelativeSource AncestorType=Window}, 
+Converter={StaticResource FontSizeConverter}, ConverterParameter='100,1920'}" />
+        </Style>
+        <Style TargetType="Button" BasedOn="{StaticResource PrimaryButton}">
+            <Setter Property="FontSize" Value="{Binding ActualWidth,RelativeSource={RelativeSource AncestorType=Window}, 
+Converter={StaticResource FontSizeConverter}, ConverterParameter='20,1920'}" />
+        </Style>
+    </Window.Resources>
+
+    <Grid Margin="10">
+        <Grid.RowDefinitions>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition Height="Auto"/>
+        </Grid.RowDefinitions>
+
+        <Grid.ColumnDefinitions>
+            <ColumnDefinition/>
+            <ColumnDefinition/>
+        </Grid.ColumnDefinitions>
+
+        <!--<Label Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" HorizontalAlignment="Stretch" Padding="0"
+               Content="{DynamicResource Tr.PleaseScanSnr}"
+               Margin="0,0,0,20"
+               />-->
+        <GroupBox Grid.Row="1" Header="{DynamicResource Tr.PleaseScanSnr}" Padding="0,0,0,10" Grid.ColumnSpan="2" >
+            <StackPanel>
+                <pu:FormGroup 
+         Header="{DynamicResource Tr.PositionSnr}"
+         dp:FormGroupHelper.IsRequired="True"
+         Style="{StaticResource InputFormGroupStyle}">
+                    <TextBox Style="{StaticResource EditorTextBoxStyle}" x:Name="positionText" TextWrapping="NoWrap" KeyDown="TextBox_KeyDown"/>
+                </pu:FormGroup>
+
+                <pu:FormGroup 
+         Header="{DynamicResource Tr.ProductSnr}"
+         dp:FormGroupHelper.IsRequired="True" Style="{StaticResource InputFormGroupStyle}">
+                    <TextBox Style="{StaticResource EditorTextBoxStyle}" x:Name="snrText" TextWrapping="NoWrap" KeyDown="TextBox_KeyDown"/>
+                </pu:FormGroup>
+            </StackPanel>
+
+        </GroupBox>
+
+        <!--<Button Grid.Row="2" Click="Con_Click" Style="{StaticResource PrimaryButton}"
+        Content="{DynamicResource Tr.ContinueScan}"/>-->
+        <Button Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" Click="Ok_Click" Style="{StaticResource SuccessButton}"
+        Content="{DynamicResource Tr.FinishScan}"/>
+    </Grid>
+</pu:WindowX>
diff --git a/Plugin.BurnIn/Scanner.xaml.cs b/Plugin.BurnIn/Scanner.xaml.cs
new file mode 100644
index 0000000..ae98c61
--- /dev/null
+++ b/Plugin.BurnIn/Scanner.xaml.cs
@@ -0,0 +1,465 @@
+锘縰sing HMI.Core.Manager;
+using HMI.Core.Setting;
+using HMI.Core.Util;
+using HMI.UI.Util;
+using HMI.Util;
+using Panuon.WPF.UI;
+using Plugin.BurnIn.Model;
+using Plugin.BurnIn.Util;
+using System.ComponentModel;
+using System.IO.Ports;
+using System.Windows;
+using System.Windows.Input;
+
+namespace Plugin.BurnIn
+{
+    public class BarcodeEventArg
+    {
+        public string name { get; private set; }
+        public string positionCode { get; private set; }
+        public string snrCode { get; private set; }
+        public CellInfo lastInfo { get; private set; }
+
+        public BarcodeEventArg(string name, string positionCode, string snrCode, CellInfo lastInfo)
+        {
+            this.name = name;
+            this.positionCode = positionCode;
+            this.snrCode = snrCode;
+            this.lastInfo = lastInfo;
+        }
+    }
+
+    /// <summary>
+    /// Scanner.xaml 鐨勪氦浜掗�昏緫
+    /// </summary>
+    public partial class Scanner : WindowX
+    {
+        private string _title;
+
+        public string title
+        {
+            get { return _title; }
+            set => Set(ref _title, value);
+        }
+
+        public event EventHandler<BarcodeEventArg> FinishBarcode;
+        public event EventHandler<RoutedEventArgs> ComplateClick;
+
+        private static Dictionary<string, CellInfo> snrCache = new Dictionary<string, CellInfo>();
+        SerialPort serialPort;
+
+        private string model;
+        private int columns;
+        private int rows;
+        private int ovenIndex;
+        private CellInfo targetCellInfo;
+        public Scanner(int index, string title, string model, int columns, int rows)
+        {
+            this.ovenIndex = index;
+            this.title = title;
+            this.model = model;
+            this.columns = columns;
+            this.rows = rows;
+            this.DataContext = this;
+            InitializeComponent();
+            Loaded += Scanner_Loaded;
+        }
+
+        private void Scanner_Loaded(object sender, RoutedEventArgs e)
+        {
+            try
+            {
+                InitScanner();
+                Loaded -= Scanner_Loaded;
+            }
+            catch (Exception ex)
+            {
+                BurnInUI.Logger.Error("Open Scanner Window Error.", ex);
+                MsgBoxHelper.Error(this, $"{ex.Message}");
+            }
+        }
+
+        /// <summary>
+        /// 璁剧疆鎵爜鏋�
+        /// </summary>
+        private void InitScanner()
+        {
+            string comStr = "";
+            if (serialPort != null) return;
+
+            string section = this.Tag.ToString();
+            ScannerConfig common = ScannerConfig.ReadFromIni(BurnInUI.SannerIniFile, "Config");
+
+            string portName = IniFileHelper.ReadIniData(section, "PortName", "", BurnInUI.SannerIniFile);
+            if (string.IsNullOrEmpty(portName))
+            {
+                if (section.StartsWith("A"))
+                {
+                    portName = "COM1";
+                }
+                else
+                {
+                    portName = "COM2";
+                }
+            }
+            common.PortName = portName;
+
+
+            // 閰嶇疆涓插彛
+            serialPort = new SerialPort
+            {
+                PortName = common.PortName,
+                BaudRate = common.BaudRate,
+                Parity = Parity.None,
+                DataBits = common.DataBits,
+                StopBits = common.StopBits,
+                Handshake = Handshake.None,
+                DtrEnable = common.DtrEnable,
+                RtsEnable = common.RtsEnable
+                //ReadTimeout = 500,
+                //WriteTimeout = 500,
+                //ReciveBufferSize=4096
+            };
+
+            try
+            {
+                // 鎵撳紑涓插彛
+                serialPort.Open();
+                // 娉ㄥ唽鎺ユ敹鏁版嵁浜嬩欢
+                serialPort.DataReceived += SerialPort_DataReceived;
+            }
+            catch (Exception ex)
+            {
+                BurnInUI.Logger.Error("Init Scan Gun Failed.", ex);
+                MsgBoxHelper.Error(this, $"{this.lang("Tr.InitGunFailed")}: {ex.StackTrace}");
+            }
+        }
+
+        private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
+        {
+            try
+            {
+                SerialPort sp = (SerialPort)sender;
+                string barcode = ReadDataFrom(sp).Trim();
+
+                //string barcode = 
+                Dispatcher.Invoke(() =>
+                {
+                    if (snrText.IsFocused)
+                    {
+                        snrText.Text = barcode;
+                        Con_Click(null, null);
+                    }
+                    else
+                    {
+                        try
+                        {
+                            var targetCellInfo = ScannerParser.GetCellInfoByCode(this.model, this.columns, this.rows, barcode);
+                            if (!TestResultView.nameTables.ContainsKey(targetCellInfo.OvenName))
+                            {
+                                MsgBoxHelper2.Error($"鑰佸寲鐐� [{targetCellInfo.OvenName}] 涓嶅瓨鍦紝璇锋鏌ラ厤缃�");
+                                return;
+                            }
+                            var sourceTable = TestResultView.tables[this.ovenIndex];
+                            if (sourceTable.title != targetCellInfo.OvenName)
+                            {
+                                MsgBoxHelper2.Error($"璇峰垏鎹㈣嚦 [{targetCellInfo.OvenName}] 鍖烘壂鐮侊紝褰撳墠涓� [{sourceTable.title}] 鍖猴紒");
+                                return;
+                            }
+
+                            var oven = TestResultView.nameTables[targetCellInfo.OvenName];
+                            if (oven.cells.Length < targetCellInfo.Index + 1)
+                            {
+                                MsgBoxHelper2.Error($"绌翠綅鍙� [{barcode}] 瓒呭嚭绱㈠紩 {targetCellInfo.Index}");
+                                return;
+                            }
+
+                            this.targetCellInfo = targetCellInfo;
+                            positionText.Text = barcode;
+                            //positionText.Focus();
+                            //snrText.Text = "";
+                            snrText.Focus();
+                        }
+                        catch (Exception ex)
+                        {
+                            BurnInUI.Logger.Error("Parse Position Code Error.", ex);
+                            MsgBoxHelper2.Error($"绌翠綅鍙� [{barcode}] 瑙f瀽澶辫触: {ex.Message}");
+                        }
+                    }
+                });
+            }
+            catch (Exception ex)
+            {
+                BurnInUI.Logger.Error("Get Scan Gun Data Failed.", ex);
+                MessageBox.Show($"{this.lang("Tr.GetGunData")}: {ex.StackTrace}");
+            }
+        }
+
+        public static void InsertNewCode(string snr, CellInfo info)
+        {
+            snrCache[snr] = info;
+        }
+
+        public static void RemoveSNR(string snr)
+        {
+            snrCache.Remove(snr);
+        }
+
+        private void SendCode(string tag, string position, string snr)
+        {
+            if (snrCache.ContainsKey(snr))
+            {
+                string oldPosition = snrCache[snr].OriginCode;
+                if (MsgBoxHelper2.WarnChoose($"鏉$爜[{snr}]宸叉斁缃簬[{oldPosition}]涓紝鏄惁娓呴櫎锛�") == MessageBoxResult.OK)
+                {
+                    try
+                    {
+                        FinishBarcode.Invoke(this, new BarcodeEventArg(tag, position, null, snrCache[snr]));
+                        snrCache.Remove(snr);
+                        FinishBarcode.Invoke(this, new BarcodeEventArg(tag, position, snr, null));
+                    }
+                    catch (Exception ex)
+                    {
+                        ToastHelper.Error(ex.Message);
+                    }
+                }
+            }
+            else
+            {
+                FinishBarcode.Invoke(this, new BarcodeEventArg(tag, position, snr, null));
+            }
+        }
+
+        private void Con_Click(object sender, RoutedEventArgs e)
+        {
+            Dispatcher.Invoke(() =>
+            {
+                string position = positionText.Text, snr = snrText.Text;
+                try
+                {
+                    var EnableMes = ParameterManager.GetCommonParameterByName<bool>("EnableMes", false);
+                    if (ParamSetting.CustomerCode == "LIXUN" && EnableMes)
+                    {
+                        var table = TestResultView.nameTables[targetCellInfo.OvenName];
+                        // TestResultView.tables[this.ovenIndex]
+                        string currentPn = table.CbProduct.Text;
+
+                        /// 妫�鏌ユ牸寮�
+                        if (!SNValidator.ValidateSN(currentPn, snr, out string msg))
+                        {
+                            table.GetSingleCell(targetCellInfo.Index).LightRed(true, out _);
+                            MsgBoxHelper2.Error($"妫�鏌ヤ骇鍝佹潯鐮佸紓甯革細{msg}");
+                            snrText.Text = string.Empty;
+                            snrText.Focus();
+                            return;
+                        }
+
+                        /// 鑾峰彇绔欏悕
+                        string stationName = ProductManager.GetParameterByProductAndName(currentPn, "MES绔欎綅")?.Value;
+                        if (string.IsNullOrEmpty(stationName))
+                        {
+                            table.GetSingleCell(targetCellInfo.Index).LightRed(true, out _);
+                            MsgBoxHelper2.Error($"妫�鏌ヤ骇鍝佹潯鐮佸紓甯革紝褰撳墠浜у搧[{currentPn}]鏈缃弬鏁癕ES绔欎綅");
+                            snrText.Text = string.Empty;
+                            snrText.Focus();
+                            return;
+                        }
+
+                        /// 妫�鏌N
+                        if (PNUtil.GetPN(ParamSetting.MesAddress, stationName, snr, out string pn, out msg))
+                        {
+
+                            if (pn != currentPn)
+                            {
+                                table.GetSingleCell(targetCellInfo.Index).LightRed(true, out _);
+                                MsgBoxHelper2.Error($"褰撳墠娴嬭瘯鏂欏彿涓� [{currentPn}]锛屾壂鐮佷骇鍝佹枡鍙蜂负 [{pn}] 涓嶅厑璁告祴璇�");
+                                snrText.Text = string.Empty;
+                                snrText.Focus();
+                                return;
+                            }
+                        }
+                        else
+                        {
+                            table.GetSingleCell(targetCellInfo.Index).LightRed(true, out _);
+                            MsgBoxHelper2.Error($"妫�鏌ヤ骇鍝佹潯鐮佸紓甯革細{msg}");
+                            snrText.Text = string.Empty;
+                            snrText.Focus();
+                            return;
+                        }
+
+                        /// 妫�鏌ュ伐搴�
+                        if (PNUtil.CheckIn(BurnInUI.Logger, ParamSetting.MesAddress, stationName, snr, out bool needCheckIn, out msg))
+                        {
+                            if (needCheckIn != true)
+                            {
+                                table.GetSingleCell(targetCellInfo.Index).LightRed(true, out _);
+                                MsgBoxHelper2.Error($"鏉$爜[{snr}]宸ュ簭涓嶅湪鏈珯锛歿msg}");
+                                snrText.Text = string.Empty;
+                                snrText.Focus();
+                                return;
+                            }
+                        }
+                        else
+                        {
+                            table.GetSingleCell(targetCellInfo.Index).LightRed(true, out _);
+                            MsgBoxHelper2.Error($"鏉$爜[{snr}]CheckIn澶辫触锛歿msg}");
+                            snrText.Text = string.Empty;
+                            snrText.Focus();
+                            return;
+                        }
+                    }
+                    //var _ = ScannerParser.GetCellInfoByCode(this.model, this.columns, this.rows, snr);
+                    //ToastHelper.Warning(this, "浜у搧鐮佹牸寮忓紓甯�");
+                    //snrText.Text = string.Empty;
+                    //snrText.Focus();
+                    //return;
+                }
+                catch (Exception ex)
+                {
+                    BurnInUI.Logger.Error("Scanner Check Error.", ex);
+                    MsgBoxHelper2.Error($"鎵爜妫�鏌ュ紓甯革細{ex.Message}");
+                    return;
+                }
+
+                if (string.IsNullOrEmpty(position) || string.IsNullOrEmpty(snr))
+                {
+                    ToastHelper.Warning(this, this.lang("Tr.PleaseInputSnrs"));
+                    return;
+                }
+                try
+                {
+                    SendCode(Tag.ToString(), position, snr);
+                    positionText.Text = "";
+                    snrText.Text = "";
+                    positionText.Focus();
+                }
+                catch (Exception ex)
+                {
+                    BurnInUI.Logger.Error("Con_Click Failed.", ex);
+                    ToastHelper.Warning(this, ex.Message);
+                }
+            });
+        }
+
+        /// <summary>
+        /// 鍘熸潵鐨勫畬鎴愭壂鎻忛�昏緫
+        /// </summary>
+        /// <param name="sender"></param>
+        /// <param name="e"></param>
+        //private void Ok_Click(object sender, RoutedEventArgs e)
+        //{
+        //    string position = positionText.Text, snr = snrText.Text;
+        //    if (string.IsNullOrEmpty(position) || string.IsNullOrEmpty(snr))
+        //    {
+        //        ToastHelper.Warning(this, this.lang("Tr.PleaseInputSnrs"));
+        //        return;
+        //    }
+        //    try
+        //    {
+        //        SendCode(Tag.ToString(), position, snr);
+        //        this.Close();
+        //    }
+        //    catch (Exception ex)
+        //    {
+        //        ViewControl.Logger.Error("Con_Click Failed.", ex);
+        //        ToastHelper.Warning(this, ex.Message);
+        //    }
+        //}
+
+        private void Ok_Click(object sender, RoutedEventArgs e)
+        {
+            ComplateClick?.Invoke(this, e);
+            this.Close();
+        }
+
+        private string ReadDataFrom(SerialPort sp)
+        {
+            string barcode = "";
+            while (sp.BytesToRead > 0)
+            {
+                byte[] buffer = new byte[sp.BytesToRead];
+                sp.Read(buffer, 0, buffer.Length);
+                barcode += System.Text.Encoding.ASCII.GetString(buffer);
+                System.Threading.Thread.Sleep(50);
+            }
+            return barcode;
+        }
+
+        private void Window_Closing(object sender, CancelEventArgs e)
+        {
+            try
+            {
+                targetCellInfo = null;
+                serialPort.Dispose();
+                serialPort = null;
+            }
+            catch (Exception ex)
+            {
+            }
+        }
+
+        private void TextBox_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
+        {
+            if (e.Key == Key.Enter)
+            {
+                //string barcode = 
+                Dispatcher.Invoke(() =>
+                {
+                    if (snrText.IsFocused)
+                    {
+                        Con_Click(null, null);
+                    }
+                    else
+                    {
+                        //if (ParamSetting.CustomerCode != "LIXUN")
+                        //{
+                        //    snrText.Focus();
+                        //    return;
+                        //}
+                        string barcode = positionText.Text;
+                        try
+                        {
+                            targetCellInfo = ScannerParser.GetCellInfoByCode(this.model, this.columns, this.rows, barcode);
+                            if (!TestResultView.nameTables.ContainsKey(targetCellInfo.OvenName))
+                            {
+                                MsgBoxHelper2.Error($"鑰佸寲鐐� [{targetCellInfo.OvenName}] 涓嶅瓨鍦紝璇锋鏌ラ厤缃�");
+                                positionText.Text = string.Empty;
+                                positionText.Focus();
+                                targetCellInfo = null;
+                                return;
+                            }
+
+                            var sourceTable = TestResultView.tables[this.ovenIndex];
+                            if (sourceTable.title != targetCellInfo.OvenName)
+                            {
+                                MsgBoxHelper2.Error($"璇峰垏鎹㈣嚦 [{targetCellInfo.OvenName}] 鍖烘壂鐮侊紝褰撳墠涓� [{sourceTable.title}] 鍖猴紒");
+                                positionText.Text = string.Empty;
+                                positionText.Focus();
+                                targetCellInfo = null;
+                                return;
+                            }
+
+                            var oven = TestResultView.nameTables[targetCellInfo.OvenName];
+                            if (oven.cells.Length < targetCellInfo.Index + 1)
+                            {
+                                MsgBoxHelper2.Error($"绌翠綅鍙� [{barcode}] 瓒呭嚭绱㈠紩 {targetCellInfo.Index}");
+                                positionText.Text = string.Empty;
+                                positionText.Focus();
+                                targetCellInfo = null;
+                                return;
+                            }
+                            snrText.Focus();
+                        }
+                        catch (Exception ex)
+                        {
+                            positionText.Text = string.Empty;
+                            positionText.Focus();
+                            BurnInUI.Logger.Error("Parse Position Code Error.", ex);
+                            MsgBoxHelper2.Error($"绌翠綅鍙� [{barcode}] 瑙f瀽澶辫触: {ex.Message}");
+                        }
+                    }
+                });
+            }
+        }
+    }
+}
diff --git a/Plugin.BurnIn/ScannerSetting.xaml b/Plugin.BurnIn/ScannerSetting.xaml
new file mode 100644
index 0000000..063b303
--- /dev/null
+++ b/Plugin.BurnIn/ScannerSetting.xaml
@@ -0,0 +1,66 @@
+锘�<pu:WindowX x:Class="Plugin.BurnIn.ScannerSetting"
+        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+        xmlns:local="clr-namespace:Plugin.BurnIn"
+        mc:Ignorable="d"
+        Style="{StaticResource ModalWindow}"
+        xmlns:hb="clr-namespace:HMI.Base;assembly=HMI.Base"
+        xmlns:uic="clr-namespace:HMI.UI.Converter;assembly=HMI.UI"
+        xmlns:pw="https://opensource.panuon.com/wpf"
+        xmlns:pu="https://opensource.panuon.com/wpf-ui"
+        xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI"
+        xmlns:cc="clr-namespace:HMI.Core;assembly=HMI.Core"
+        xmlns:sys="clr-namespace:System;assembly=mscorlib"
+        xmlns:ports="clr-namespace:System.IO.Ports;assembly=System"
+        SizeToContent="Height"
+        WindowStartupLocation="CenterOwner"
+        Title="{Binding title}" Width="600">
+    <Window.Resources>
+        <ObjectDataProvider x:Key="stopBitsItems" 
+                  MethodName="GetValues" 
+                  ObjectType="{x:Type sys:Enum}">
+            <ObjectDataProvider.MethodParameters>
+                <x:Type TypeName="ports:StopBits"/>
+            </ObjectDataProvider.MethodParameters>
+        </ObjectDataProvider>
+    </Window.Resources>
+
+
+    <Grid Margin="10">
+        <StackPanel x:Name="formPanel">
+            <pu:FormGroup x:Name="FgPortName" Style="{StaticResource InputFormGroupStyle}" Header="{DynamicResource Tr.PortName}"
+              GroupName="MyFormGroup">
+                <ComboBox x:Name="CbPortName" Style="{StaticResource FormComboxStyle}" />
+            </pu:FormGroup>
+
+            <pu:FormGroup Style="{StaticResource InputFormGroupStyle}" Header="{DynamicResource Tr.BaudRate}"
+                          dp:FormGroupHelper.IsRequired="True"
+                          GroupName="MyFormGroup">
+                <pu:NumberInput x:Name="NiBaudRate" IsEnabled="{Binding SbIsEnabled}" Style="{StaticResource FormNumberInput}" />
+            </pu:FormGroup>
+
+            <pu:FormGroup Style="{StaticResource InputFormGroupStyle}" Header="{DynamicResource Tr.DataBits}"
+            dp:FormGroupHelper.IsRequired="True" GroupName="MyFormGroup">
+                <pu:NumberInput x:Name="NiDataBits" IsEnabled="{Binding SbIsEnabled}" Style="{StaticResource FormNumberInput}" />
+            </pu:FormGroup>
+
+            <pu:FormGroup Style="{StaticResource InputFormGroupStyle}" Header="{DynamicResource Tr.StopBits}" 
+                          dp:FormGroupHelper.IsRequired="True" GroupName="MyFormGroup">
+                <ComboBox x:Name="CbStopBits" IsEnabled="{Binding SbIsEnabled}" Style="{StaticResource FormComboxStyle}"
+                          ItemsSource="{Binding Source={StaticResource stopBitsItems}}"
+          SelectedValuePath="Content">
+                </ComboBox>
+            </pu:FormGroup>
+
+            <pu:FormGroup Style="{StaticResource InputFormGroupStyle}" Header="{DynamicResource Tr.DtrEnable}" GroupName="MyFormGroup">
+                <CheckBox x:Name="CxDtrEnable" IsEnabled="{Binding SbIsEnabled}"/>
+            </pu:FormGroup>
+
+            <pu:FormGroup Style="{StaticResource InputFormGroupStyle}" Header="{DynamicResource Tr.RtsEnable}"  GroupName="MyFormGroup">
+                <CheckBox x:Name="CxRtsEnable" IsEnabled="{Binding SbIsEnabled}"/>
+            </pu:FormGroup>
+        </StackPanel>
+    </Grid>
+</pu:WindowX>
diff --git a/Plugin.BurnIn/ScannerSetting.xaml.cs b/Plugin.BurnIn/ScannerSetting.xaml.cs
new file mode 100644
index 0000000..e2bc377
--- /dev/null
+++ b/Plugin.BurnIn/ScannerSetting.xaml.cs
@@ -0,0 +1,147 @@
+锘縰sing HMI.Core.Util;
+using HMI.UI.DpUtil;
+using HMI.UI.Util;
+using HMI.Util;
+using Panuon.WPF.UI;
+using Plugin.BurnIn.Model;
+using System.ComponentModel;
+using System.IO.Ports;
+
+namespace Plugin.BurnIn
+{
+    /// <summary>
+    /// ScannerSetting.xaml 鐨勪氦浜掗�昏緫
+    /// </summary>
+    public partial class ScannerSetting : WindowX
+    {
+        public ScannerSetting()
+        {
+            DataContext = this;
+            InitializeComponent();
+        }
+
+        public ScannerConfig Data { get; set; }
+
+        private bool _isProcessing;
+
+        protected override void OnClosing(CancelEventArgs e)
+        {
+            //Press Continue Button
+            if (DialogResult == true)
+            {
+                if (_isProcessing)
+                {
+                    e.Cancel = true;
+                    return;
+                }
+                var okButton = WindowXModalDialog.GetOKButton(this);
+                try
+                {
+                    _isProcessing = true;
+                    ButtonHelper.SetIsPending(okButton, true);
+                    if (!ValidateForm())
+                    {
+                        e.Cancel = true;
+                        return;
+                    }
+
+                    SaveOrUpdateData();
+                }
+                catch (Exception ex)
+                {
+                    ToastHelper.Error(this, $"Error锛亄ex.Message}");
+                    e.Cancel = true;
+                    return;
+                }
+                finally
+                {
+                    _isProcessing = false;
+                    ButtonHelper.SetIsPending(okButton, false);
+                }
+            }
+            base.OnClosing(e);
+        }
+
+
+        private bool ValidateForm()
+        {
+            bool flag = FormGroupHelper.ValidateForm(formPanel);
+            if (flag)
+            {
+                Data.PortName = CbPortName.SelectedItem?.ToString();
+                Data.BaudRate = (int)(NiBaudRate.Value??0);
+                Data.DataBits = (int)(NiDataBits.Value??0);
+                Data.StopBits = (StopBits)CbStopBits.SelectedItem;
+                Data.DtrEnable = CxDtrEnable.IsChecked ?? false;
+                Data.RtsEnable = CxRtsEnable.IsChecked ?? false;
+            }
+            return flag;
+        }
+
+        private bool _isEnabled;
+        public bool SbIsEnabled {
+            get => _isEnabled;
+            set {
+                Set(ref _isEnabled, value);
+            }
+        }
+
+        private string section;
+        public bool? Open(bool showPortName, string section)
+        {
+            string[] ports = SerialPort.GetPortNames();
+            CbPortName.ItemsSource = ports;
+
+            this.section = section;
+            this.Title = section == "Config" ? this.lang("Tr.CommonSetting") : (section + this.lang("Tr.ScannerSetting"));
+            if (showPortName)
+            {
+                FgPortName.Visibility = System.Windows.Visibility.Visible;
+                FormGroupHelper.SetIsRequired(FgPortName, true);
+                SbIsEnabled = false;
+            }
+            else
+            {
+                FgPortName.Visibility = System.Windows.Visibility.Collapsed;
+                FormGroupHelper.SetIsRequired(FgPortName, false);
+                SbIsEnabled = true;
+            }
+
+            var data = ScannerConfig.ReadFromIni(BurnInUI.SannerIniFile, "Config");
+            string portName = IniFileHelper.ReadIniData(section, "PortName", "", BurnInUI.SannerIniFile);
+            if (string.IsNullOrEmpty(portName)) {
+                if (section.StartsWith("A"))
+                {
+                    portName = "COM1";
+                }
+                else {
+                    portName = "COM2";
+                }
+            }
+            data.PortName = portName;
+
+            Data = data;
+            CbPortName.SelectedItem = Data.PortName;
+            NiBaudRate.Value = Data.BaudRate;
+            NiDataBits.Value = Data.DataBits;
+            CbStopBits.SelectedItem = Data.StopBits;
+            CxDtrEnable.IsChecked = Data.DtrEnable;
+            CxRtsEnable.IsChecked = Data.RtsEnable;
+            return this.ShowDialog();
+        }
+
+        private void SaveOrUpdateData()
+        {
+            string iniFile = BurnInUI.SannerIniFile;
+            IniFileHelper.WriteIniData(section, "PortName", Data.PortName, iniFile);
+            if (section == "Config")
+            {
+                IniFileHelper.WriteIniData(section, "BaudRate", Data.BaudRate.ToString(), iniFile);
+                IniFileHelper.WriteIniData(section, "DataBits", Data.DataBits.ToString(), iniFile);
+                IniFileHelper.WriteIniData(section, "StopBits", Data.StopBits.ToString(), iniFile);
+                IniFileHelper.WriteIniData(section, "DtrEnable", Data.DtrEnable.ToString(), iniFile);
+                IniFileHelper.WriteIniData(section, "RtsEnable", Data.RtsEnable.ToString(), iniFile);
+            }
+        }
+    }
+}
diff --git a/Plugin.BurnIn/SingleCell.xaml b/Plugin.BurnIn/SingleCell.xaml
new file mode 100644
index 0000000..5f5236c
--- /dev/null
+++ b/Plugin.BurnIn/SingleCell.xaml
@@ -0,0 +1,146 @@
+锘�<UserControl x:Class="Plugin.BurnIn.SingleCell"
+             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:Plugin.BurnIn"
+             mc:Ignorable="d" 
+             xmlns:pw="https://opensource.panuon.com/wpf"
+xmlns:pu="https://opensource.panuon.com/wpf-ui"
+xmlns:ui="clr-namespace:HMI.UI;assembly=HMI.UI"
+             x:Name="uc"
+             PreviewMouseRightButtonDown="OnPreviewMouseRightButtonDown"
+             MouseDoubleClick="UserControl_MouseDoubleClick"
+             d:DesignHeight="450" d:DesignWidth="800">
+    <UserControl.Resources>
+        <local:FontSizeConverter x:Key="FontSizeConverter"/>
+    </UserControl.Resources>
+
+    <UserControl.Style>
+        <Style TargetType="UserControl">
+            <Style.Triggers>
+                <DataTrigger Binding="{Binding status}" Value="ok">
+                    <Setter Property="Background" Value="#FF0F8853"/>
+                </DataTrigger>
+                <DataTrigger Binding="{Binding status}" Value="ng">
+                    <Setter Property="Background" Value="#fd6b6d"/>
+                </DataTrigger>
+                <DataTrigger Binding="{Binding status}" Value="checkInFailed">
+                    <Setter Property="Background" Value="#ffac00"/>
+                </DataTrigger>
+                <DataTrigger Binding="{Binding status}" Value="checkIn">
+                    <Setter Property="Background" Value="#FF00E2FF"/>
+                </DataTrigger>
+                <DataTrigger Binding="{Binding status}" Value="testing">
+                    <Setter Property="Background" Value="#1296db"/>
+                </DataTrigger>
+                <DataTrigger Binding="{Binding status}" Value="waiting">
+                    <Setter Property="Background" Value="#A5A5A5"/>
+                </DataTrigger>
+                <DataTrigger Binding="{Binding status}" Value="alert">
+                    <Setter Property="Background" Value="#e04733"/>
+                </DataTrigger>
+                <DataTrigger Binding="{Binding status}" Value="ready">
+                    <Setter Property="Background" Value="#FF08FF00"/>
+                </DataTrigger>
+                <DataTrigger Binding="{Binding status}" Value="hadCode">
+                    <Setter Property="Background" Value="#FFF"/>
+                </DataTrigger>
+                <DataTrigger Binding="{Binding status}" Value="abort">
+                    <Setter Property="Background" Value="Black"/>
+                </DataTrigger>
+            </Style.Triggers>
+        </Style>
+    </UserControl.Style>
+    <Grid>
+        <Grid.RowDefinitions>
+            <RowDefinition />
+            <RowDefinition Height="14"/>
+            <RowDefinition Height="14"/>
+        </Grid.RowDefinitions>
+        <Grid.ColumnDefinitions>
+            <ColumnDefinition/>
+            <ColumnDefinition Width="Auto"/>
+        </Grid.ColumnDefinitions>
+        <Label Grid.Row="0" Grid.Column="0" HorizontalContentAlignment="Left" VerticalContentAlignment="Top" Padding="0" Margin="5,5,0,0" 
+      FontSize="{Binding ActualWidth,RelativeSource={RelativeSource AncestorType=UserControl}, 
+   Converter={StaticResource FontSizeConverter}, ConverterParameter='8,103'}" 
+      FontWeight="Bold"
+Background="Transparent" BorderThickness="0" Content="{Binding title}">
+            <Label.Style>
+                <Style TargetType="Label">
+                    <Style.Triggers>
+                        <DataTrigger Binding="{Binding status}" Value="abort">
+                            <Setter Property="Foreground" Value="#FFF"/>
+                        </DataTrigger>
+                    </Style.Triggers>
+                </Style>
+            </Label.Style>
+        </Label>
+
+        <Image Grid.Row="0" Grid.Column="1" Width="{Binding ActualWidth,RelativeSource={RelativeSource AncestorType=UserControl}, 
+   Converter={StaticResource FontSizeConverter}, ConverterParameter='10,20'}" HorizontalAlignment="Right"
+               Margin="0,5,5,0">
+            <Image.Style>
+                <Style TargetType="Image">
+                    <Setter Property="HorizontalAlignment" Value="Center"/>
+                    <Setter Property="VerticalAlignment" Value="Top"/>
+                    <Style.Triggers>
+                        <DataTrigger Binding="{Binding status}" Value="ok">
+                            <Setter Property="Source" Value="Resource/ok2.png"/>
+                        </DataTrigger>
+                        <DataTrigger Binding="{Binding status}" Value="ng">
+                            <Setter Property="Source" Value="Resource/ng2.png"/>
+                        </DataTrigger>
+                        <DataTrigger Binding="{Binding status}" Value="checkInFailed">
+                            <Setter Property="Source" Value="Resource/checkInFailed2.png"/>
+                        </DataTrigger>
+                        <DataTrigger Binding="{Binding status}" Value="checkIn">
+                            <Setter Property="Source" Value="Resource/checkIn.png"/>
+                        </DataTrigger>
+                        <DataTrigger Binding="{Binding status}" Value="testing">
+                            <Setter Property="Source" Value="Resource/testing2.png"/>
+                        </DataTrigger>
+                        <DataTrigger Binding="{Binding status}" Value="waiting">
+                            <Setter Property="Source" Value="Resource/waiting2.png"/>
+                        </DataTrigger>
+                        <DataTrigger Binding="{Binding status}" Value="alert">
+                            <Setter Property="Source" Value="Resource/alert.png"/>
+                        </DataTrigger>
+                        <DataTrigger Binding="{Binding status}" Value="ready">
+                            <Setter Property="Source" Value="Resource/ready.png"/>
+                        </DataTrigger>
+                        <DataTrigger Binding="{Binding status}" Value="hadCode">
+                            <Setter Property="Source" Value="Resource/barcode.png"/>
+                        </DataTrigger>
+                        <DataTrigger Binding="{Binding status}" Value="abort">
+                            <Setter Property="Source" Value="Resource/abort.png"/>
+                        </DataTrigger>
+                    </Style.Triggers>
+                </Style>
+            </Image.Style>
+        </Image>
+
+        <ui:ScrollLabel Grid.Row="1" Grid.Column="0" FontSize="10" Grid.ColumnSpan="2" Margin="3,0,0,0" Padding="0" Value="{Binding sn}"/>
+
+        <StackPanel Orientation="Horizontal" Background="LightGray" Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" VerticalAlignment="Top" HorizontalAlignment="Right">
+            <TextBlock Foreground="Red" Text="{DynamicResource Ts.ProbeAlarm}" FontSize="10">
+                <TextBlock.Style>
+                    <Style TargetType="TextBlock">
+                        <Setter Property="Visibility" Value="Collapsed"/>
+                        <Style.Triggers>
+                            <DataTrigger Binding="{Binding probeAlarm}" Value="True">
+                                <Setter Property="Visibility" Value="Visible"/>
+                            </DataTrigger>
+                        </Style.Triggers>
+                    </Style>
+                </TextBlock.Style>
+            </TextBlock>
+            <TextBlock Margin="0" FontSize="10">
+        <Run Text="{Binding ProbeCount}"/>
+        <Run Text="/"/>
+        <Run Text="{Binding ProbeWarn}"/>
+            </TextBlock>
+        </StackPanel>
+    </Grid>
+</UserControl>
diff --git a/Plugin.BurnIn/SingleCell.xaml.cs b/Plugin.BurnIn/SingleCell.xaml.cs
new file mode 100644
index 0000000..8fafd4f
--- /dev/null
+++ b/Plugin.BurnIn/SingleCell.xaml.cs
@@ -0,0 +1,1174 @@
+锘縰sing HMI.Core.Entity;
+using HMI.Core.Manager;
+using HMI.Core.Setting;
+using HMI.Core.Util;
+using HMI.UI.Util;
+using HMI.Util;
+using PLCHelper;
+using Plugin.BurnIn.Constant;
+using Plugin.BurnIn.Model;
+using Plugin.BurnIn.Util;
+using System.Collections.Concurrent;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.IO;
+using System.Runtime.CompilerServices;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+
+namespace Plugin.BurnIn
+{
+    /// <summary>
+    /// SingleCell.xaml 鐨勪氦浜掗�昏緫
+    /// </summary>
+    public partial class SingleCell : UserControl, INotifyPropertyChanged
+    {
+
+        #region ...
+        public event PropertyChangedEventHandler PropertyChanged;
+
+        public virtual void RaisePropertyChanged(string name)
+        {
+            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
+        }
+
+        public void RaiseAndSetIfchanged<T>(ref T old, T @new, [CallerMemberName] string propertyName = null)
+        {
+            old = @new;
+            if (propertyName != null) RaisePropertyChanged(propertyName);
+        }
+        #endregion
+
+        private static readonly HashSet<string> IgnoreTestItem = new HashSet<string> {
+            "浜у搧鏉$爜","checkin","棰勬鐢垫祦","precheckresult","checkout",
+        };
+
+        private InfoWindow window;
+        private int maxSize;
+
+        public DataTable table { get; private set; }
+
+        public SingleCell(DataTable table, string title, int index, int maxSize, string iniFile)
+        {
+            this.table = table;
+            this.title = title;
+            this.index = index;
+            this.maxSize = maxSize;
+            this.countIniPath = iniFile;
+            DataContext = this;
+            InitializeComponent();
+            RefreshProbe();
+        }
+
+        public void RefreshProbe()
+        {
+            try
+            {
+                string key = string.Empty;
+                ParameterData value;
+                if (ParamSetting.ProbeByModel)
+                {
+                    string model = table.CbProduct?.SelectedItem?.ToString();
+                    key = $"{title}_{model}";
+                    value = ProductManager.GetParameterByProductAndName(model, "ProbeWarn");
+                    if (value == null)
+                    {
+                        value = ParameterManager.GetCommonParameterByName("ProbeWarn");
+                    }
+                    ProbeWarn = value != null ? int.Parse(value.Value) : 1000;
+                }
+                else
+                {
+                    key = $"{title}";
+                    value = ParameterManager.GetCommonParameterByName("ProbeWarn");
+                    ProbeWarn = value != null ? int.Parse(value.Value) : 1000;
+                }
+                string temp = IniFileHelper.ReadIniData(key, "ProbeCount", "0", countIniPath);
+                int count = Convert.ToInt32(temp);
+                ProbeCount = count;
+            }
+            catch
+            {
+                ProbeWarn = 1000;
+            }
+            if (window != null) window.UpdateProbeCount(ProbeCount, ProbeWarn);
+        }
+
+        public void RefreshCharts()
+        {
+            charts = new LiveChart[3];
+            try
+            {
+                ParameterData value;
+                string model = table.CbProduct?.SelectedItem?.ToString();
+
+                for (int i = 0; i < charts.Length; i++)
+                {
+                    value = ProductManager.GetParameterByProductAndName(model, $"ChartConfig_{i}");
+                    if (value == null)
+                    {
+                        value = ParameterManager.GetCommonParameterByName($"ChartConfig_{i}");
+                    }
+                    var config = value != null ? value.Value : null;
+
+                    if (string.IsNullOrEmpty(config))
+                    {
+                        charts[i] = null;
+                    }
+                    else
+                    {
+                        charts[i] = new LiveChart(config) //new string[] { "娓╁害(鈩�)", "娴侀噺(L/m)" }
+                        {
+                            MinHeight = 120
+                        };
+                    }
+                }
+            }
+            catch
+            {
+                charts = new LiveChart[0];
+            }
+        }
+
+        private string _sn;
+        public string sn
+        {
+            get { return _sn; }
+            set
+            {
+                RaiseAndSetIfchanged(ref _sn, value);
+                RaisePropertyChanged("sn");
+            }
+        }
+
+        private int _index;
+
+        public int index
+        {
+            get { return _index; }
+            set
+            {
+                RaiseAndSetIfchanged(ref _index, value);
+                RaisePropertyChanged("title");
+            }
+        }
+
+        private bool cellIsDisabled;
+
+        public bool CellIsDisabled
+        {
+            get { return cellIsDisabled; }
+            set
+            {
+                RaiseAndSetIfchanged(ref cellIsDisabled, value);
+            }
+        }
+
+        private string _title;
+        public string title
+        {
+            get => _title;
+            set
+            {
+                RaiseAndSetIfchanged(ref _title, value);
+            }
+        }
+
+        private int _probeWarn;
+        public int ProbeWarn
+        {
+            get => _probeWarn;
+            set
+            {
+                RaiseAndSetIfchanged(ref _probeWarn, value);
+            }
+        }
+
+        private string _status = CommonStatus.WAITING;
+
+        public string tooltipContent
+        {
+            get
+            {
+                string dataStr = "";
+                if (infos != null && infos.Count > 0)
+                {
+                    foreach (var info in infos)
+                    {
+                        dataStr += $"\r\nStep:{info.step},Result:{info.result}";
+                    }
+                }
+                return $"{title}\r\n鐘舵��:{CommonStatus.GetStatusDescription(status)}{dataStr}";
+            }
+        }
+
+        public string status
+        {
+            get { return _status; }
+            set => RaiseAndSetIfchanged(ref _status, value);
+        }
+
+        private ConcurrentQueue<Info> queue = new ConcurrentQueue<Info>();
+
+        private List<Info> allInfos = new List<Info>();
+
+        private ObservableCollection<Info> _infos = new ObservableCollection<Info>();
+
+        public ObservableCollection<Info> infos
+        {
+            get => _infos;
+            set
+            {
+                RaiseAndSetIfchanged(ref _infos, value);
+                RaisePropertyChanged("tooltipContent");
+            }
+        }
+
+        private string countIniPath;
+        /// <summary>
+        /// 鎺㈤拡瀵垮懡
+        /// </summary>
+        private int probeCount;
+        public int ProbeCount
+        {
+            get => probeCount;
+            set
+            {
+                RaiseAndSetIfchanged(ref probeCount, value);
+                probeAlarm = value >= ProbeWarn;
+            }
+        }
+
+        private bool _probeAlarm;
+        public bool probeAlarm
+        {
+            get => _probeAlarm;
+            set
+            {
+                RaiseAndSetIfchanged(ref _probeAlarm, value);
+            }
+        }
+
+        private void UpdateProbeCount(int count)
+        {
+            ProbeCount = count;
+            string key = $"{title}";
+            if (ParamSetting.ProbeByModel)
+            {
+                string model = table?.CbProduct?.SelectedItem?.ToString();
+                key = $"{title}_{model}";
+            }
+            IniFileHelper.WriteIniData(key, "ProbeCount", ProbeCount.ToString(), countIniPath);
+            if (window != null) window.UpdateProbeCount(ProbeCount, ProbeWarn);
+        }
+
+        private void UserControl_MouseDoubleClick(object sender, MouseButtonEventArgs e)
+        {
+            if (window == null)
+            {
+                window = new InfoWindow(title, infos, charts, ProbeCount, ProbeWarn);
+                window.Closed += Window_Closed;
+                window.ClearProbeCountEvent += Window_ClearProbeCountEvent;
+                window.Owner = Window.GetWindow(this);
+                window.Show();
+            }
+            else
+            {
+                window.Activate();
+            }
+        }
+
+        private void Window_ClearProbeCountEvent(object sender, EventArgs e)
+        {
+            UpdateProbeCount(0);
+        }
+
+        private void Window_Closed(object sender, EventArgs e)
+        {
+            this.window = null;
+        }
+
+        private string visualTestId;
+        private Sqlite3Helper helper;
+
+        private void InitCurrentTestHelper()
+        {
+            if (helper == null)
+            {
+                var now = DateTime.Now;
+                string dateStr = now.ToString("yyyyMMdd");
+                visualTestId = $"{title}_{now.ToString("yyyyMMddHHmmssfff")}";
+                helper = CheckSqlHelper(dateStr);
+            }
+        }
+
+        public void SetStatus(string status)
+        {
+            Dispatcher.Invoke(() =>
+            {
+                var oldStatus = this.status;
+                this.status = status;
+                if (oldStatus != "testing" && status == "testing")
+                {
+                    UpdateProbeCount(ProbeCount + 1);
+                    InitCurrentTestHelper();
+                    InsertARecord();
+                }
+
+                if (oldStatus != "ok" && status == "ok")
+                {
+                    SaveInfoToDB();
+                }
+
+                if (oldStatus != "ng" && status == "ng")
+                {
+                    SaveInfoToDB();
+                }
+            });
+        }
+
+        private void InsertARecord()
+        {
+            try
+            {
+                string model = table?.CbProduct?.SelectedItem?.ToString();
+                var timestr = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
+                var sql = $"INSERT INTO testResult (sn,model,station,position,result,time,resultId) VALUES ('{sn}','{model}','','{title}','{this.status}','{timestr}','{visualTestId}');";
+                helper.Execute(sql);
+            }
+            catch (Exception ex)
+            {
+                BurnInUI.Logger.Error($"Cell[{title}] InsertARecord Error.", ex);
+            }
+        }
+
+        public void WriteCSV(Info info)
+        {
+            //try
+            //{
+            //    if (ParamSetting.CustomerCode != "LIXUN")
+            //    {
+            //        return;
+            //    }
+            //    string fileName = this.sn?.Replace("/", "#")?.Replace("\\", "#") + ".csv";
+            //    string dir = Path.Combine(ParamSetting.DataLogFolder, "csv", this.title);
+            //    string title = $"{this.lang("Test.Name")},{this.lang("Test.Value")},{this.lang("Test.LimitDown")},{this.lang("Test.LimitUp")},{this.lang("Test.Unit")},{this.lang("Test.Result")},{this.lang("Test.TestTime")}";
+            //    string line = $"{info.step},{info.value},{info.limitDown},{info.limitUp},{info.unit},{info.result},{info.updateTime}";
+
+            //    CSVPrinter.Print(dir, fileName, title, line);
+            //    // ReverseCSV(Path.Combine(dir, fileName));
+            //}
+            //catch (Exception ex)
+            //{
+            //}
+        }
+
+        public void SendToChart(Info info)
+        {
+            try
+            {
+                foreach (var chart in charts)
+                {
+                    if (chart?.CanHandle(info.step) == true)
+                    {
+                        chart.AddData(info.step, DateTime.Parse(info.updateTime), double.Parse(info.value));
+                    }
+                }
+                //if (electricChart.CanHandle(info.step))
+                //{
+                //    electricChart.AddData(info.step, DateTime.Parse(info.updateTime), double.Parse(info.value));
+                //}
+
+                //if (vChart.CanHandle(info.step))
+                //{
+                //    vChart.AddData(info.step, DateTime.Parse(info.updateTime), double.Parse(info.value));
+                //}
+
+                //if (waterChart?.CanHandle(info.step) == true)
+                //{
+                //    waterChart?.AddData(info.step, DateTime.Parse(info.updateTime), double.Parse(info.value));
+                //}
+            }
+            catch (Exception ex)
+            {
+            }
+        }
+
+        public void SetInfos(Info info)
+        {
+            string sql = string.Empty;
+            try
+            {
+                allInfos.Add(info);
+                SendToChart(info);
+                WriteCSV(info);
+
+                /// 鐩墠鍏堜笉闄愬埗鏁伴噺锛屽悗缁鏋滄暟鎹繃澶氬鑷存�ц兘闂鍐嶈�冭檻闄愬埗鏁伴噺
+                //if (queue.Count >= maxSize)
+                //{
+                //    queue.TryDequeue(out var first);
+                //    queue.Enqueue(info);
+                //    this.infos = new ObservableCollection<Info>(queue);
+                //    if (window != null) window.UpdateSource(this.infos);
+                //}
+                //else
+                //{
+                //    queue.Enqueue(info);
+                //    if (window != null)
+                //    {
+                //        window.Dispatcher.Invoke(() =>
+                //        {
+                //            this.infos.Add(info);
+                //        });
+                //    }
+                //    else
+                //    {
+                //        Dispatcher.Invoke(() =>
+                //        {
+                //            this.infos.Add(info);
+                //        });
+                //    }
+                //}
+
+                if (window != null)
+                {
+                    window.Dispatcher.Invoke(() =>
+                    {
+                        this.infos.Add(info);
+                    });
+                }
+                else
+                {
+                    Dispatcher.Invoke(() =>
+                    {
+                        this.infos.Add(info);
+                    });
+                }
+
+                Dispatcher.Invoke(() =>
+                {
+                    string dateStr = string.Empty;
+                    try
+                    {
+                        dateStr = DateTime.Parse(info.updateTime).ToString("yyyy-MM-dd HH:mm:ss");
+                    }
+                    catch
+                    {
+                        dateStr = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
+                    }
+
+                    try
+                    {
+                        InitCurrentTestHelper();
+                        string model = table?.CbProduct?.SelectedItem?.ToString();
+                        sql = $@"INSERT INTO serialData (sn,model,station,position,step,limitDown,limitUp,unit,value,status,finalStatus,time,resultId) 
+VALUES ('{sn}','{model}','','{this.title}','{info.step}','{info.limitDown}','{info.limitUp}','{info.unit}','{info.value}','{info.result}',
+NULL,'{dateStr}','{visualTestId}');";
+                        helper.Execute(sql);
+                    }
+                    catch (Exception ex)
+                    {
+                        BurnInUI.Logger.Error($"Save DB Test Data Error. {sql}", ex);
+                    }
+                });
+            }
+            catch (Exception ex)
+            {
+                BurnInUI.Logger.Error($"Add Test Data Error. {sql}", ex);
+            }
+        }
+
+        public void ClearInfo()
+        {
+            allInfos.Clear();
+            queue = new ConcurrentQueue<Info>();
+            var list = new ObservableCollection<Info>(queue);
+            this.infos = list;
+            if (window != null) window.UpdateSource(this.infos);
+        }
+
+        //private Chart waterChart = null;
+        //private Chart electricChart = new Chart(LangUtil.Get("Tr.CurrentChart"), false, 20)
+        //{
+        //    Height = 120
+        //};
+
+        //private Chart vChart = new Chart(LangUtil.Get("Tr.VoltChart"), false, 20)
+        //{
+        //    Height = 120
+        //};
+
+        private LiveChart[] charts = new LiveChart[0];
+
+        //private LiveChart waterChart = null;
+        //private LiveChart electricChart = new LiveChart("瀛愭澘鐢垫祦,鐢垫祦(A);姣嶆澘鐢垫祦,鐢垫祦(A)") //new string[] { "瀛愭澘鐢垫祦", "妯℃澘鐢垫祦" }
+        //{
+        //    MinHeight = 120
+        //};
+
+        //private LiveChart vChart = new LiveChart("瀛愭澘鐢靛帇,鐢靛帇(V);姣嶆澘鐢靛帇,鐢靛帇(V)") //new string[] { "瀛愭澘鐢靛帇", "妯℃澘鐢靛帇" }
+        //{
+        //    MinHeight = 120
+        //};
+
+        public void AddWaterData(double temp, double backTemp, double pressure)
+        {
+            //waterChart?.AddData(0, "姘寸娓╁害", temp);
+            //waterChart?.AddData(0, "鍥炴按娓╁害", backTemp);
+            //waterChart?.AddData(1, "娴侀噺", pressure);
+
+            //waterChart?.AddData("姘寸娓╁害", temp);
+            //waterChart?.AddData("鍥炴按娓╁害", backTemp);
+            //waterChart?.AddData("娴侀噺", pressure);
+        }
+
+        public void AddElectricData(double data)
+        {
+            //electricChart.AddData("", data);
+        }
+
+        public void AddVData(double data)
+        {
+            //vChart.AddData("", data);
+        }
+
+        public void ClearChart()
+        {
+            foreach (var chart in charts)
+            {
+                chart?.Clear();
+            }
+        }
+
+        //public void ClearWaterData()
+        //{
+        //    waterChart?.Clear();
+        //}
+
+        //public void ClearElectricData()
+        //{
+        //    electricChart.Clear();
+        //}
+
+        //public void ClearVData()
+        //{
+        //    vChart.Clear();
+        //}
+
+
+        public void SaveElectricDataToCSV(string filePath)
+        {
+            try
+            {
+                List<double> datas = charts[1]?.GetSingleData("");
+
+                FileInfo fi = new FileInfo(filePath);
+                if (!fi.Directory.Exists)
+                {
+                    fi.Directory.Create();  //鏂囦欢涓嶅瓨鍦紝鍒涘缓鏂囦欢
+                }
+
+                using (FileStream fs = new FileStream(filePath, FileMode.Create, FileAccess.Write))
+                using (StreamWriter sw = new StreamWriter(fs, System.Text.Encoding.UTF8))
+                {
+                    string title = this.lang("Tr.CurrentChart");
+                    sw.WriteLine(title);
+                    if (datas != null)
+                    {
+                        for (int i = 0; i < datas.Count; i++)
+                        {
+                            sw.WriteLine(datas[i].ToString());
+                        }
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                Console.WriteLine(ex.ToString());
+            }
+        }
+
+        public void SaveVDataToCSV(string filePath)
+        {
+            try
+            {
+                List<double> datas = charts[2]?.GetSingleData("");
+
+                FileInfo fi = new FileInfo(filePath);
+                if (!fi.Directory.Exists)
+                {
+                    fi.Directory.Create();  //鏂囦欢涓嶅瓨鍦紝鍒涘缓鏂囦欢
+                }
+
+                using (FileStream fs = new FileStream(filePath, FileMode.Create, FileAccess.Write))
+                using (StreamWriter sw = new StreamWriter(fs, System.Text.Encoding.UTF8))
+                {
+                    string title = this.lang("Tr.VoltChart");
+                    sw.WriteLine(title);
+                    if (datas != null)
+                    {
+                        for (int i = 0; i < datas.Count; i++)
+                        {
+                            sw.WriteLine(datas[i].ToString());
+                        }
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                Console.WriteLine(ex.ToString());
+            }
+        }
+
+        public void NormalCSV(string filePath)
+        {
+            try
+            {
+                FileInfo fi = new FileInfo(filePath);
+                if (!fi.Directory.Exists)
+                {
+                    fi.Directory.Create();  //鏂囦欢涓嶅瓨鍦紝鍒涘缓鏂囦欢
+                }
+
+                using (FileStream fs = new FileStream(filePath, FileMode.Create, FileAccess.Write))
+                using (StreamWriter sw = new StreamWriter(fs, System.Text.Encoding.UTF8))
+                {
+                    string title = $"{this.lang("Test.Name")},{this.lang("Test.Value")},{this.lang("Test.LimitDown")},{this.lang("Test.LimitUp")},{this.lang("Test.Unit")},{this.lang("Test.Result")},{this.lang("Test.TestTime")}";
+                    sw.WriteLine(title);
+                    foreach (var info in allInfos)
+                    {
+                        string row = $"{info.step},{info.value},{info.limitDown},{info.limitUp},{info.unit},{info.result},{info.updateTime}";
+                        sw.WriteLine(row);
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                BurnInUI.Logger.Error($"Cell[{title}] SaveInfoToCSV Error.", ex);
+            }
+        }
+
+        public void ReverseCSV_Bak(string filePath)
+        {
+            try
+            {
+                if (allInfos == null || allInfos.Count < 4) return;
+
+                Dictionary<string, int> columnMap = new Dictionary<string, int>();
+                int colPoint = 0;
+                List<string> titleRow = new List<string>();
+                List<string[]> datas = new List<string[]>();
+
+                void AddTitle(string title)
+                {
+                    if (titleRow.IndexOf(title) < 0)
+                    {
+                        titleRow.Add(title);
+                    }
+                }
+
+                // 鍏堣缃〃澶�
+                for (int row = 0; row < allInfos.Count; row++)
+                {
+                    var info = allInfos[row];
+                    if (IgnoreTestItem.Contains(info.step.ToLower()))
+                    {
+                        continue;
+                    }
+
+                    string title = $"{info.step}{(string.IsNullOrEmpty(info.unit) ? "" : $"({info.unit})")}";
+                    AddTitle("娴嬭瘯鏃堕棿");
+                    AddTitle(title);
+                    if (!string.IsNullOrEmpty(info.limitDown))
+                    {
+                        string key2 = $"{info.step}涓嬮檺";
+                        AddTitle(key2);
+                    }
+
+                    if (!string.IsNullOrEmpty(info.limitUp))
+                    {
+                        string key2 = $"{info.step}涓婇檺";
+                        AddTitle(key2);
+                    }
+
+                    if (!string.IsNullOrEmpty(info.limitUp))
+                    {
+                        string key2 = $"{info.step}娴嬭瘯缁撴灉";
+                        AddTitle(key2);
+                    }
+
+                    //if (!string.IsNullOrEmpty(info.updateTime))
+                    //{
+                    //    string key2 = $"{info.step}娴嬭瘯鏃堕棿";
+                    //    AddTitle(key2);
+                    //}
+                }
+
+                int rowPoint = -1;
+                for (int row = 0; row < allInfos.Count; row++)
+                {
+                    var info = allInfos[row];
+                    if (IgnoreTestItem.Contains(info.step.ToLower()))
+                    {
+                        continue;
+                    }
+
+                    string title = $"{info.step}{(string.IsNullOrEmpty(info.unit) ? "" : $"({info.unit})")}";
+                    int col = titleRow.IndexOf(title);
+                    if (col == 1)
+                    {
+                        rowPoint++;
+                    }
+                    if (datas.Count <= rowPoint)
+                    {
+                        datas.Add(new string[titleRow.Count]);
+                    }
+                    var arr = datas[rowPoint];
+                    arr[col] = info.value;
+
+
+                    if (!string.IsNullOrEmpty(info.limitDown))
+                    {
+                        string key2 = $"{info.step}涓嬮檺";
+                        int col2 = titleRow.IndexOf(key2);
+                        arr[col2] = info.limitDown;
+                    }
+
+                    if (!string.IsNullOrEmpty(info.limitUp))
+                    {
+                        string key2 = $"{info.step}涓婇檺";
+                        int col2 = titleRow.IndexOf(key2);
+                        arr[col2] = info.limitUp;
+                    }
+
+                    if (!string.IsNullOrEmpty(info.limitUp))
+                    {
+                        string key2 = $"{info.step}娴嬭瘯缁撴灉";
+                        int col2 = titleRow.IndexOf(key2);
+                        arr[col2] = info.result;
+                    }
+
+                    if (!string.IsNullOrEmpty(info.updateTime))
+                    {
+                        string key2 = "娴嬭瘯鏃堕棿";// $"{info.step}娴嬭瘯鏃堕棿";
+                        int col2 = titleRow.IndexOf(key2);
+                        arr[col2] = info.updateTime;
+                    }
+                }
+
+
+                FileInfo fi = new FileInfo(filePath);
+                if (!fi.Directory.Exists)
+                {
+                    fi.Directory.Create();  //鏂囦欢涓嶅瓨鍦紝鍒涘缓鏂囦欢
+                }
+
+                using (FileStream fs = new FileStream(filePath, FileMode.Create, FileAccess.Write))
+                using (StreamWriter sw = new StreamWriter(fs, System.Text.Encoding.UTF8))
+                {
+                    string title = string.Join(",", titleRow);
+                    sw.WriteLine(title);
+                    foreach (var info in datas)
+                    {
+                        string row = string.Join(",", info);
+                        sw.WriteLine(row);
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                BurnInUI.Logger.Error($"Cell[{title}] SaveInfoToCSV Error.", ex);
+            }
+        }
+
+        public void ReverseCSV(string filePath)
+        {
+            try
+            {
+                if (allInfos == null || allInfos.Count < 4) return;
+
+                Dictionary<string, int> columnMap = new Dictionary<string, int>();
+                int colPoint = 0;
+                List<string> titleRow = new List<string>();
+                List<string[]> datas = new List<string[]>();
+                List<string> timeRow = new List<string>();
+
+                void AddTitle(string title)
+                {
+                    if (titleRow.IndexOf(title) < 0)
+                    {
+                        titleRow.Add(title);
+                    }
+                }
+
+                // 鍏堣缃〃澶�
+                for (int i = 0; i < allInfos.Count; i++)
+                {
+                    var info = allInfos[i];
+                    if (IgnoreTestItem.Contains(info.step.ToLower()))
+                    {
+                        continue;
+                    }
+                    string testTime = info.updateTime;
+                    int rowIndex = timeRow.IndexOf(testTime);
+                    if (rowIndex < 0)
+                    {
+                        timeRow.Add(testTime);
+                    }
+
+                    string title = $"{info.step}{(string.IsNullOrEmpty(info.unit) ? "" : $"({info.unit})")}";
+                    AddTitle("娴嬭瘯鏃堕棿");
+                    AddTitle(title);
+                    if (!string.IsNullOrEmpty(info.limitDown))
+                    {
+                        string key2 = $"{info.step}涓嬮檺";
+                        AddTitle(key2);
+                    }
+
+                    if (!string.IsNullOrEmpty(info.limitUp))
+                    {
+                        string key2 = $"{info.step}涓婇檺";
+                        AddTitle(key2);
+                    }
+
+                    if (!string.IsNullOrEmpty(info.limitUp))
+                    {
+                        string key2 = $"{info.step}娴嬭瘯缁撴灉";
+                        AddTitle(key2);
+                    }
+
+                    //if (!string.IsNullOrEmpty(info.updateTime))
+                    //{
+                    //    string key2 = $"{info.step}娴嬭瘯鏃堕棿";
+                    //    AddTitle(key2);
+                    //}
+                }
+
+                for (int i = 0; i < allInfos.Count; i++)
+                {
+                    var info = allInfos[i];
+                    if (IgnoreTestItem.Contains(info.step.ToLower()))
+                    {
+                        continue;
+                    }
+                    string testTime = info.updateTime;
+                    int row = timeRow.IndexOf(testTime);
+                    if (row < 0)
+                    {
+                        continue;
+                    }
+
+
+
+                    string title = $"{info.step}{(string.IsNullOrEmpty(info.unit) ? "" : $"({info.unit})")}";
+                    int col = titleRow.IndexOf(title);
+
+                    if (datas.Count <= row)
+                    {
+                        datas.Add(new string[titleRow.Count]);
+                    }
+                    var arr = datas[row];
+                    arr[col] = info.value;
+
+
+                    if (!string.IsNullOrEmpty(info.limitDown))
+                    {
+                        string key2 = $"{info.step}涓嬮檺";
+                        int col2 = titleRow.IndexOf(key2);
+                        arr[col2] = info.limitDown;
+                    }
+
+                    if (!string.IsNullOrEmpty(info.limitUp))
+                    {
+                        string key2 = $"{info.step}涓婇檺";
+                        int col2 = titleRow.IndexOf(key2);
+                        arr[col2] = info.limitUp;
+                    }
+
+                    if (!string.IsNullOrEmpty(info.limitUp))
+                    {
+                        string key2 = $"{info.step}娴嬭瘯缁撴灉";
+                        int col2 = titleRow.IndexOf(key2);
+                        arr[col2] = info.result;
+                    }
+
+                    if (!string.IsNullOrEmpty(info.updateTime))
+                    {
+                        string key2 = "娴嬭瘯鏃堕棿";// $"{info.step}娴嬭瘯鏃堕棿";
+                        int col2 = titleRow.IndexOf(key2);
+                        arr[col2] = info.updateTime;
+                    }
+                }
+
+
+                FileInfo fi = new FileInfo(filePath);
+                if (!fi.Directory.Exists)
+                {
+                    fi.Directory.Create();  //鏂囦欢涓嶅瓨鍦紝鍒涘缓鏂囦欢
+                }
+
+                using (FileStream fs = new FileStream(filePath, FileMode.Create, FileAccess.Write))
+                using (StreamWriter sw = new StreamWriter(fs, System.Text.Encoding.UTF8))
+                {
+                    string title = string.Join(",", titleRow);
+                    sw.WriteLine(title);
+                    foreach (var info in datas)
+                    {
+                        string row = string.Join(",", info);
+                        sw.WriteLine(row);
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                BurnInUI.Logger.Error($"Cell[{title}] SaveInfoToCSV Error.", ex);
+            }
+        }
+
+        /// <summary>
+        /// 淇濆瓨娴嬭瘯鏁版嵁鍒癈SV
+        /// </summary>
+        /// <param name="filePath"></param>
+        public void SaveInfoToCSV(string filePath)
+        {
+            if (ParamSetting.ReverseCSV)
+            {
+                ReverseCSV(filePath);
+            }
+            else
+            {
+                NormalCSV(filePath);
+            }
+        }
+
+        private Sqlite3Helper CheckSqlHelper(string dateStr = null)
+        {
+            dateStr = string.IsNullOrEmpty(dateStr) ? DateTime.Now.ToString("yyyyMMdd") : dateStr;
+            string dbPath = Path.Combine(ParamSetting.DataLogFolder, $"Data_{dateStr}.db");
+            if (!File.Exists(dbPath))
+            {
+                Sqlite3Helper.CreateDatabase(dbPath);
+            }
+            var sqliteHelper = new Sqlite3Helper(dbPath);
+            sqliteHelper.Execute(@"CREATE TABLE IF NOT EXISTS serialData (
+                    id INTEGER PRIMARY KEY AUTOINCREMENT,
+                    sn TEXT NOT NULL,
+                    model TEXT,
+                    station TEXT NOT NULL,
+                    step TEXT NOT NULL,
+                    position TEXT,
+                    limitDown TEXT,
+                    limitUp TEXT,
+                    unit TEXT,
+                    offset TEXT,
+                    value TEXT,
+                    status TEXT,
+                    finalStatus TEXT,
+                    consumeTime TEXT, 
+                    totalConsumeTime TEXT, 
+                    resultId TEXT,
+                    time DATETIME NOT NULL);");
+            sqliteHelper.Execute(@"CREATE TABLE IF NOT EXISTS testResult (
+                    id INTEGER PRIMARY KEY AUTOINCREMENT,
+                    sn TEXT NOT NULL,
+                    model TEXT,
+                    station TEXT NOT NULL,
+                    position TEXT,
+                    status TEXT,
+                    result TEXT NOT NULL,
+                    resultId TEXT,
+                    time DATETIME NOT NULL);");
+            if (!sqliteHelper.HasData("SELECT 1 FROM pragma_table_info('serialData') WHERE name = 'resultId';"))
+            {
+                sqliteHelper.Execute("ALTER TABLE serialData ADD resultId TEXT;");
+            }
+            if (!sqliteHelper.HasData("SELECT 1 FROM pragma_table_info('testResult') WHERE name = 'resultId';"))
+            {
+                sqliteHelper.Execute("ALTER TABLE testResult ADD resultId TEXT;");
+            }
+            return sqliteHelper;
+        }
+
+        /// <summary>
+        /// 淇濆瓨娴嬭瘯鏁版嵁鍒癝qlite
+        /// </summary>
+        /// <param name="filePath"></param>
+        public void SaveInfoToDB()
+        {
+            Dispatcher.Invoke(() =>
+            {
+                try
+                {
+                    string model = table?.CbProduct?.SelectedItem?.ToString();
+                    var timestr = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
+                    //var sql = $"INSERT INTO testResult (sn,model,station,position,result,time,resultId) VALUES ('{sn}','{model}','','{title}','{this.status}','{timestr}','{visualTestId}');";
+                    var sql = $"UPDATE testResult SET time = '{timestr}', result = '{this.status}' WHERE resultId = '{visualTestId}'";
+                    helper.Execute(sql);
+                    helper.Dispose();
+                    helper = null;
+                    //                using (var helper = CheckSqlHelper())
+                    //                {
+                    //                    List<string> sqls = new List<string>();
+                    //                    var timestr = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
+                    //                    foreach (var info in allInfos)
+                    //                    {
+                    //                        string sql = $@"INSERT INTO serialData (sn,station,step,limitDown,limitUp,unit,value,status,finalStatus,time) 
+                    //VALUES ('{sn}','','{info.step}','{info.limitDown}','{info.limitUp}','{info.unit}','{info.value}','{info.result}','{this.status}','{timestr}');";
+                    //                        sqls.Add(sql);
+                    //                    }
+                    //                    sqls.Add($"INSERT INTO testResult (sn,station,position,result,time) VALUES ('{sn}','','{title}','{this.status}','{timestr}');");
+                    //                    helper.ExecuteBatchSql(sqls);
+                    //                }
+                }
+                catch (Exception ex)
+                {
+                    BurnInUI.Logger.Error($"Cell[{title}] SaveToDB Error.", ex);
+                }
+            });
+        }
+
+        private void MenuItem_Click(object sender, RoutedEventArgs e)
+        {
+            var item = sender as MenuItem;
+            switch (item?.Name)
+            {
+                case "clearSNRItem":
+                    table.TryClearCellSN(this.index);
+                    break;
+                case "exportCSVItem":
+                    TryExportCSV();
+                    break;
+                case "manualDebugItem":
+                    OpenManualDebugWindow();
+                    break;
+                default:
+                    break;
+            }
+        }
+
+        /// <summary>
+        /// 鎵撳紑鎵嬪姩璋冭瘯绐楀彛
+        /// </summary>
+        private void OpenManualDebugWindow()
+        {
+            string manualDebugSeq = ParameterManager.GetCommonParameterByName("ManualDebugSeqPath")?.Value;
+            if (string.IsNullOrEmpty(manualDebugSeq))
+            {
+                MsgBoxHelper2.Error("璇峰厛璁剧疆鎵嬪姩璋冭瘯搴忓垪璺緞[ManualDebugSeqPath]!");
+                return;
+            }
+            string modelPath = table.parent.testModelFile;
+            int dutNo = table.index * table.cellCount + this.index;
+            var win = new ManualDebugWindow(manualDebugSeq, modelPath)
+            {
+                Title = $"{this.title}鎵嬪姩璋冭瘯",
+                Owner = Window.GetWindow(this),
+                OvenIndex = this.table.index,
+                OvenName = this.table.title,
+
+                CellIndex = dutNo,
+                CellName = this.title
+            };
+            win.ShowDialog();
+        }
+
+        /// <summary>
+        /// 灏濊瘯瀵煎嚭CSV
+        /// </summary>
+        private void TryExportCSV()
+        {
+            System.Windows.Forms.SaveFileDialog sfd = new System.Windows.Forms.SaveFileDialog
+            {
+                Filter = "CSV (*.csv)|*.csv"
+            };
+            if (sfd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
+            {
+                this.NormalCSV(sfd.FileName);
+            }
+        }
+
+        public void ClearSN()
+        {
+            sn = string.Empty;
+            LightGreen(false, out _);
+            LightRed(false, out _);
+        }
+
+        public bool LightGreen(bool on, out string msg)
+        {
+            string key = $"Oven{table.index + 1}_Cell{index + 1}_Light_OK";
+            bool res = S7Context.WriteData(RunTimeVariablePool.DEFAULT_PLC_NAME, key, on, out msg);
+            if (res)
+            {
+                BurnInUI.Logger.Debug($"Set Light [{key}] {on}.");
+            }
+            else
+            {
+                BurnInUI.Logger.Debug($"Open Light [{key}] {on} Error.{msg}");
+            }
+            return res;
+        }
+
+        public bool LightRed(bool on, out string msg)
+        {
+            return S7Context.WriteData(RunTimeVariablePool.DEFAULT_PLC_NAME,
+                $"Oven{table.index + 1}_Cell{index + 1}_Light_NG", on, out msg);
+        }
+
+        private void OnPreviewMouseRightButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
+        {
+
+            ContextMenu contextMenu = new ContextMenu();
+            contextMenu.Closed += ContextMenu_Closed;
+
+            //<  />
+            //<
+            //< MenuItem IsCheckable = "False" x: Name = "manualDebugItem" Click = "MenuItem_Click" Header = "鎵嬪姩璋冭瘯" >
+            //    < MenuItem.Style >
+            //        < Style TargetType = "MenuItem" BasedOn = "{StaticResource {x:Static pu:StyleKeys.MenuItemStyle}}" >
+
+            //        </ Style >
+            //    </ MenuItem.Style >
+            //</ MenuItem >
+
+            var item = new MenuItem
+            {
+                IsCheckable = false,
+                IsEnabled = !table.IsTesting,
+                Name = "clearSNRItem",
+                Header = this.lang("Tr.ClearSNR"),
+            };
+            item.Click += MenuItem_Click;
+            contextMenu.Items.Add(item);
+
+            item = new MenuItem
+            {
+                IsCheckable = false,
+                Name = "exportCSVItem",
+                Header = this.lang("App.ExportCSV"),
+            };
+            item.Click += MenuItem_Click;
+            contextMenu.Items.Add(item);
+
+            if (table.parent.inManualDebugMode)
+            {
+                item = new MenuItem
+                {
+                    IsCheckable = false,
+                    Name = "manualDebugItem",
+                    Header = this.lang("鎵嬪姩璋冭瘯"),
+                };
+                item.Click += MenuItem_Click;
+                contextMenu.Items.Add(item);
+            }
+
+            this.ContextMenu = contextMenu;
+        }
+
+        private void ContextMenu_Closed(object sender, RoutedEventArgs e)
+        {
+            this.ContextMenu = null;
+        }
+    }
+}
diff --git a/Plugin.BurnIn/TestResultView.xaml b/Plugin.BurnIn/TestResultView.xaml
new file mode 100644
index 0000000..1c28689
--- /dev/null
+++ b/Plugin.BurnIn/TestResultView.xaml
@@ -0,0 +1,21 @@
+锘�<UserControl x:Class="Plugin.BurnIn.TestResultView"
+             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:Plugin.BurnIn"
+xmlns:hb="clr-namespace:HMI.Base;assembly=HMI.Base"
+xmlns:uic="clr-namespace:HMI.UI.Converter;assembly=HMI.UI"
+xmlns:pw="https://opensource.panuon.com/wpf"
+xmlns:pu="https://opensource.panuon.com/wpf-ui"
+xmlns:ui="clr-namespace:HMI.UI;assembly=HMI.UI"
+xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI"
+             mc:Ignorable="d" 
+             d:DesignHeight="450" d:DesignWidth="800">
+    <Grid x:Name="panel">
+        <WindowsFormsHost x:Name="host" Visibility="Collapsed"></WindowsFormsHost>
+        <Grid x:Name="mainGrid" dp:GridHelper.Space="6">
+
+        </Grid>
+    </Grid>
+</UserControl>
diff --git a/Plugin.BurnIn/TestResultView.xaml.cs b/Plugin.BurnIn/TestResultView.xaml.cs
new file mode 100644
index 0000000..f58724d
--- /dev/null
+++ b/Plugin.BurnIn/TestResultView.xaml.cs
@@ -0,0 +1,683 @@
+锘縰sing HMI.Core.Entity;
+using HMI.Core.Manager;
+using HMI.Core.Util;
+using HMI.UI.Util;
+using NationalInstruments.TestStand.Interop.UI.Ax;
+using Plugin.BurnIn.Model;
+using Plugin.TsSupport;
+using System.Windows;
+using System.Windows.Controls;
+
+namespace Plugin.BurnIn
+{
+    /// <summary>
+    /// TestResultView.xaml 鐨勪氦浜掗�昏緫
+    /// </summary>
+    public partial class TestResultView : UserControl
+    {
+        private int rowCount;
+        private int colCount;
+        private int cellCount;
+
+        public static DataTable[] tables { get; private set; }
+        public static Dictionary<string, DataTable> nameTables { get; private set; } = new Dictionary<string, DataTable>();
+        private readonly BaseConfig config;
+
+
+
+        public string testModelFile;
+        public string testSeqFile;
+        public bool inManualDebugMode = false;
+
+        #region TS鐩稿叧
+        //public AxApplicationMgr testApp;
+        ///// <summary>
+        ///// 娴嬭瘯搴忓垪
+        ///// </summary>
+        //private SequenceFile testSeq = null;
+        //public Engine engine;
+        //private Execution execution = null;
+        ///// <summary>
+        ///// 娴嬭瘯搴忓垪鎿嶄綔鍙ユ焺
+        ///// </summary>
+        //public SequenceContext SequenceContext { get; private set; } = null;
+        ///// <summary>
+        ///// 娴嬭瘯妯″瀷搴忓垪
+        ///// </summary>
+        //private SequenceFile modeSeq;
+
+        public ITestExecutionWrapper executionWrapper;
+        #endregion
+
+        public TestResultView() : this(new BaseConfig { testing = false })
+        {
+
+        }
+
+        public bool IsRunning { get; set; } = false;
+
+        public void RefreshCooler()
+        {
+
+        }
+
+        private void RefreshNumberConfig(BaseConfig config)
+        {
+            ParameterData value = null;
+            try
+            {
+                value = ParameterManager.GetCommonParameterByName("BurnInOvenCount");
+                string[] datas = value.Value.Trim().Split(',');
+                config.Row = int.Parse(datas[0]);
+                config.Column = int.Parse(datas[1]);
+            }
+            catch (Exception ex)
+            {
+
+            }
+
+            try
+            {
+                value = ParameterManager.GetCommonParameterByName("BurnInOvenName");
+                config.NameStr = value.Value;
+            }
+            catch (Exception ex)
+            {
+
+            }
+
+            try
+            {
+                value = ParameterManager.GetCommonParameterByName("BurnInOvenPort");
+                config.PortStart = int.Parse(value.Value);
+            }
+            catch (Exception ex)
+            {
+
+            }
+
+            try
+            {
+                value = ParameterManager.GetCommonParameterByName("BurnInCellCount");
+                string[] datas = value.Value.Trim().Split(',');
+                config.PositionColumn = int.Parse(datas[0]);
+                config.PositionCount = int.Parse(datas[1]);
+            }
+            catch (Exception ex)
+            {
+
+            }
+        }
+
+        public TestResultView(BaseConfig config)
+        {
+            try
+            {
+                var value = ParameterManager.GetCommonParameterByName("TsModelPath");
+                testModelFile = value?.Value ?? @"C:\Users\Public\Documents\National Instruments\TestStand 2020 (64-bit)\Components\Models\ParallelModel.seq";
+            }
+            catch
+            {
+                //@"C:\Program Files\National Instruments\TestStand 2020\Components\Models\TestStandModels\ParallelModel.seq"
+                testModelFile = @"C:\Users\Public\Documents\National Instruments\TestStand 2020 (64-bit)\Components\Models\ParallelModel.seq";
+            }
+
+            RefreshNumberConfig(config);
+            this.rowCount = config.Row;
+            this.colCount = config.Column;
+            this.config = config;
+            if (config.CellCount < 0)
+            {
+                this.cellCount = rowCount * colCount;
+            }
+            else
+            {
+                this.cellCount = config.CellCount;
+            }
+            tables = new DataTable[this.cellCount];
+
+
+            InitializeComponent();
+
+            
+        }
+
+        private void ExecutionWrapper_UIMessageEvent(object sender, UIMessageEvent e)
+        {
+            try
+            {
+                HandleUIMessage(e.EventCode, Convert.ToInt16(e.NumberData), e.StrData, e.Data);
+            }
+            catch (Exception ex)
+            {
+
+            }
+        }
+
+        private void Refresh()
+        {
+        }
+
+        /// <summary>
+        /// 鎺㈤拡璁℃暟閰嶇疆璺緞
+        /// </summary>
+        private string CountIniPath;
+        public void SetCountIniPath(string countIniPath)
+        {
+            CountIniPath = countIniPath;
+        }
+
+        public void InitMainGrid()
+        {
+            nameTables = new Dictionary<string, DataTable>();
+            for (int r = 0; r < rowCount; r++)
+            {
+                RowDefinition row = new RowDefinition();
+                mainGrid.RowDefinitions.Add(row);
+            }
+
+            for (int c = 0; c < colCount; c++)
+            {
+                ColumnDefinition col = new ColumnDefinition();
+                mainGrid.ColumnDefinitions.Add(col);
+            }
+            string[] names = config.NameStr.Split(',');
+            for (int n = 0; n < cellCount; n++)
+            {
+                string name = n < names.Length ? names[n] : "";
+                DataTable child = new DataTable(this, n, name, CountIniPath, config);
+                mainGrid.Children.Add(child);
+                tables[n] = child;
+                nameTables[name] = child;
+                int row = n / colCount;
+                int col = n % colCount;
+                Grid.SetRow(child, row);
+                Grid.SetColumn(child, col);
+            }
+            if (config.testing) Refresh();
+        }
+
+        public DataTable GetDataTable(int index)
+        {
+            if (index >= 0 && index < tables.Length) return tables[index];
+            return null;
+        }
+
+        /// <summary>
+        /// 鍚姩娴嬭瘯搴忓垪
+        /// </summary>
+        public void InitTestStandEngine(bool isTap)
+        {
+            try
+            {
+                if (isTap)
+                {
+                    executionWrapper = new TapWrapper();
+                }
+                else
+                {
+                    executionWrapper = new TsExecutionWrapper();
+                }
+                executionWrapper.UIMessageEvent += ExecutionWrapper_UIMessageEvent;
+
+                if (executionWrapper is TsExecutionWrapper tsWrapper)
+                {
+                    ((System.ComponentModel.ISupportInitialize)(tsWrapper.AxApplicationMgr)).BeginInit();
+                    host.Child = tsWrapper.AxApplicationMgr;
+                    ((System.ComponentModel.ISupportInitialize)(tsWrapper.AxApplicationMgr)).EndInit();
+                }
+                //this.engine = this.testApp.GetEngine();
+                //this.engine.PersistBreakpoints = false;
+                //this.testApp.IsEditor = true;
+                //this.testApp.LoginOnStart = false;
+                //this.testApp.Start();
+                //this.modeSeq = this.engine.GetSequenceFileEx(testModelFile);
+                executionWrapper.Init();
+            }
+            catch (Exception ex)
+            {
+                BurnInUI.Logger.Error("InitTestStandEngine Failed.", ex);
+                throw new Exception($"{this.lang("Ts.InitTestStandEngineFail")}: {ex.Message}");
+            }
+        }
+
+        public void SetOvenName()
+        {
+            foreach (var oven in tables)
+            {
+                executionWrapper?.SetStationGlobalValue<string>($"StationHMI.UUTHMI[{oven.index}].Name", 1, oven.Name);
+            }
+            executionWrapper?.CommitToDisk(false);
+        }
+
+        public void StartTestSequence()
+        {
+            try
+            {
+                if (string.IsNullOrEmpty(testSeqFile)) return;
+                executionWrapper?.StartSequence(this.testSeqFile,
+                    @"C:\Program Files\National Instruments\TestStand 2020\Components\Models\TestStandModels\ParallelModel.seq");
+
+                executionWrapper.SetStationGlobalValue<bool>("StationHMI.AllStop", 0, false);
+            }
+            catch (Exception ex)
+            {
+                BurnInUI.Logger.Error("StartTestSequence Failed.", ex);
+                throw new Exception($"{this.lang("Ts.StartTestSequenceFailed")}: {ex.Message}");
+            }
+        }
+
+        public void StopTest()
+        {
+            try
+            {
+                executionWrapper?.SetStationGlobalValue<bool>("StationState.StopTest", 0, true, true);
+
+                try
+                {
+                    executionWrapper?.WaitForExit();
+                    executionWrapper?.ReleaseSeqFile();
+                }
+                catch (Exception ex)
+                {
+                    BurnInUI.Logger.Error("Wait For Exit error.", ex);
+                    //if (ex is NullReferenceException ne)
+                    //{
+
+                    //}
+                    //else
+                    //{
+                    //    throw ex;
+                    //}
+                }
+            }
+            catch (Exception ex)
+            {
+                BurnInUI.Logger.Error("StopTest Failed.", ex);
+                throw new Exception($"{this.lang("Ts.StopTestSequenceFailed")}: {ex.Message}");
+            }
+        }
+
+        [Obsolete]
+        private void axApplicationMgr_ExitApplication(object sender, EventArgs e)
+        {
+            //Environment.ExitCode = testApp.ExitCode;
+            //TSHelper.DoSynchronousGCForCOMObjectDestruction();
+        }
+
+        [Obsolete]
+        private void axApplicationMgr_ExecutionClosed(object sender, _ApplicationMgrEvents_ExecutionClosedEvent e)
+        {
+            try
+            {
+                //this.ReleaseSequenceFile();
+            }
+            catch (Exception ex)
+            {
+                MsgBoxHelper.Error(Window.GetWindow(this),
+                    $"{this.lang("Ts.ReleaseSequenceFileFail")}: {ex.Message}");
+            }
+        }
+
+
+        private void HandleUIMessage(int EventCase, int MethodCode, string strData, object xData)
+        {
+            try
+            {
+                if (EventCase != 10014) return;
+
+                if (MethodCode == 0)
+                {
+                    /// 璁剧疆鍙ユ焺
+                    executionWrapper.SetContext(xData);
+                    return;
+                }
+
+                if (MethodCode == 1)
+                {
+                    /// 鏇存柊鐐夊瓙鐘舵��
+                    string[] datas_10014 = strData.Split(';');
+                    int ovenIndex = int.Parse(datas_10014[0]);
+
+                    string status = datas_10014[1];
+                    tables[ovenIndex].SetStatus(status);
+                    return;
+                }
+                if (MethodCode == 2)
+                {
+                    /// 鏇存柊鐐夊瓙鏁伴噺淇℃伅
+                    string[] datas_10014 = strData.Split(';');
+                    int ovenIndex = int.Parse(datas_10014[0]);
+
+                    string okCount = datas_10014[1];
+                    string ngCount = datas_10014[2];
+                    tables[ovenIndex].SetOkCount(int.Parse(okCount));
+                    tables[ovenIndex].SetNgCount(int.Parse(ngCount));
+                    return;
+                }
+                if (MethodCode == 3)
+                {
+                    /// 鏇存柊鐐夊瓙鏃堕棿淇℃伅
+                    string[] datas_10014 = strData.Split(';');
+                    int ovenIndex = int.Parse(datas_10014[0]);
+                    string startTime = datas_10014[1];
+                    string consumeTime = datas_10014[2];
+                    string planEndTime = datas_10014[3];
+
+                    tables[ovenIndex].SetStartTime(startTime);
+                    tables[ovenIndex].SetEndTime(planEndTime);
+                    double? leftTime = null;
+                    /// 涓轰簡鍏煎鏃х増鏈紝濡傛灉鏈夊墿浣欐椂闂村氨浼狅紝娌℃湁灏变紶null
+                    if (datas_10014.Length >= 5)
+                    {
+                        leftTime = double.Parse(datas_10014[4]);
+                    }
+                    tables[ovenIndex].UpdateConsumeTime(double.Parse(consumeTime), leftTime);
+                    return;
+                }
+                if (MethodCode == 4)
+                {
+                    /// 鏇存柊鑰佸寲鐐夊簳閮ㄧ姸鎬佹爮
+                    string[] datas_10014 = strData.Split(';');
+                    int ovenIndex = int.Parse(datas_10014[0]);
+
+                    string statusBar = datas_10014[1];
+                    tables[ovenIndex].SetStatusText(statusBar);
+                    return;
+                }
+                if (MethodCode == 5)
+                {
+                    /// 閲囬泦鐐夋俯搴�
+                    string[] datas_10016 = strData.Split(';');
+                    int ovenIndex = int.Parse(datas_10016[0]);
+
+                    string[] names = datas_10016[1].Split(',');
+                    string[] values = datas_10016[2].Split(',');
+
+                    for (int x = 0; x < values.Length; x++)
+                    {
+                        if (x == 0) tables[ovenIndex].SetTemperature(double.Parse(values[x]));
+                        else tables[ovenIndex].SetTemperature(names[x - 1], double.Parse(values[x]));
+                    }
+                    return;
+                }
+                if (MethodCode == 6)
+                {
+                    Dispatcher.Invoke(() =>
+                    {/// 鏇存柊绌翠綅鐘舵��
+                        string[] datas_10015 = strData.Split(';');
+                        int ovenIndex = int.Parse(datas_10015[0]);
+                        int cellIndex = int.Parse(datas_10015[1]);
+                        string status_10015 = datas_10015[2];
+                        tables[ovenIndex].GetSingleCell(cellIndex).SetStatus(status_10015);
+
+                        if (status_10015 == "OK" || status_10015 == "ok")
+                        {
+                            tables[ovenIndex].RefreshCount(1, 0);
+                        }
+                        else if (status_10015 == "ng" || status_10015 == "NG")
+                        {
+                            tables[ovenIndex].RefreshCount(0, 1);
+                        }
+                    });
+                    return;
+                }
+                if (MethodCode == 7)
+                {
+                    /// 鏇存柊绌翠綅鍙傛暟
+                    string[] datas_10015 = strData.Split(';');
+                    int ovenIndex = int.Parse(datas_10015[0]);
+                    int cellIndex = int.Parse(datas_10015[1]);
+
+                    string strs = datas_10015[2];
+                    string[] values = strs.Split(',');
+
+                    //string step = datas_10015[2];
+                    //string result = datas_10015[3];
+                    tables[ovenIndex].GetSingleCell(cellIndex).SetInfos(new Info
+                    {
+                        step = values[0],
+                        value = values[1],
+                        limitUp = values[2],
+                        limitDown = values[3],
+                        unit = values[4],
+                        result = values[5],
+                        updateTime = values[6]
+                    });
+                    return;
+                }
+                if (MethodCode == 10)
+                {
+                    /// 娓呯┖鐐夊瓙鐨勬俯搴﹀浘琛�
+                    int ovenIndex = int.Parse(strData);
+                    tables[ovenIndex].ClearChartData();
+                    return;
+                }
+                if (MethodCode == 11)
+                {
+                    /// 娓呴櫎绌翠綅鍙傛暟
+                    string[] datas_10015 = strData.Split(';');
+                    int ovenIndex = int.Parse(datas_10015[0]);
+                    int cellIndex = int.Parse(datas_10015[1]);
+                    tables[ovenIndex].GetSingleCell(cellIndex).ClearInfo();
+                    //GC.Collect();
+                    return;
+                }
+                if (MethodCode == 12)
+                {
+                    /// 淇濆瓨鐐変綋娓╁害鏂囦欢
+                    string[] datas = strData.Split(';');
+                    int ovenIndex = int.Parse(datas[0]);
+                    string folder = datas[1];
+                    tables[ovenIndex].SaveTempFile(folder);
+                    return;
+                }
+                if (MethodCode == 13)
+                {
+                    /// 娣诲姞鐢垫祦鏁版嵁
+                    string[] datas = strData.Split(';');
+                    int ovenIndex = int.Parse(datas[0]);
+                    int cellIndex = int.Parse(datas[1]);
+                    double eData = double.Parse(datas[2]);
+                    tables[ovenIndex].GetSingleCell(cellIndex).AddElectricData(eData);
+                    return;
+                }
+
+                if (MethodCode == 14)
+                {
+                    /// 娓呯┖鐢垫祦鍜岀數鍘嬫暟鎹�
+                    int ovenIndex = int.Parse(strData);
+                    tables[ovenIndex].ClearCellData();
+                    //tables[ovenIndex].ClearElectricData();
+                    //tables[ovenIndex].ClearVData();
+                    //tables[ovenIndex].ClearWaterData();
+                    return;
+                }
+
+                if (MethodCode == 15)
+                {
+                    /// 淇濆瓨鐢垫祦鏁版嵁
+                    string[] datas = strData.Split(';');
+                    int ovenIndex = int.Parse(datas[0]);
+                    int cellIndex = int.Parse(datas[1]);
+                    string filePath = datas[2];
+                    tables[ovenIndex].GetSingleCell(cellIndex).SaveElectricDataToCSV(filePath);
+                    return;
+                }
+
+                if (MethodCode == 16)
+                {
+                    /// 娣诲姞鐢靛帇鍥捐〃鏁版嵁
+                    string[] datas = strData.Split(';');
+                    int ovenIndex = int.Parse(datas[0]);
+                    int cellIndex = int.Parse(datas[1]);
+                    double eData = double.Parse(datas[2]);
+                    tables[ovenIndex].GetSingleCell(cellIndex).AddVData(eData);
+                    return;
+                }
+
+                if (MethodCode == 17)
+                {
+                    /// 淇濆瓨鐢靛帇鏁版嵁
+                    string[] datas = strData.Split(';');
+                    int ovenIndex = int.Parse(datas[0]);
+                    int cellIndex = int.Parse(datas[1]);
+                    string filePath = datas[2];
+                    tables[ovenIndex].GetSingleCell(cellIndex).SaveVDataToCSV(filePath);
+                    return;
+                }
+
+                if (MethodCode == 18)
+                {
+                    /// 淇濆瓨绌翠綅娴嬭瘯鏁版嵁鍒癈SV
+                    string[] datas = strData.Split(';');
+                    int ovenIndex = int.Parse(datas[0]);
+                    int cellIndex = int.Parse(datas[1]);
+                    string filePath = datas[2];
+                    tables[ovenIndex].GetSingleCell(cellIndex).SaveInfoToCSV(filePath);
+                    return;
+                }
+                if (MethodCode == 180)
+                {
+                    /// 淇濆瓨绌翠綅娴嬭瘯鏁版嵁鍒癈SV
+                    string[] datas = strData.Split(';');
+                    int ovenIndex = int.Parse(datas[0]);
+                    int cellIndex = int.Parse(datas[1]);
+                    string filePath = datas[2];
+                    tables[ovenIndex].GetSingleCell(cellIndex).NormalCSV(filePath);
+                    return;
+                }
+
+                if (MethodCode == 19)
+                {
+                    /// 淇濆瓨绌翠綅娴嬭瘯鏁版嵁鍒癝qlite
+                    string[] datas = strData.Split(';');
+                    int ovenIndex = int.Parse(datas[0]);
+                    int cellIndex = int.Parse(datas[1]);
+                    tables[ovenIndex].GetSingleCell(cellIndex).SaveInfoToDB();
+                    return;
+                }
+
+                if (MethodCode == 20)
+                {
+                    /// 娓呯┖鏉$爜
+                    int ovenIndex = int.Parse(strData);
+                    tables[ovenIndex].ClearAllSN();
+                    return;
+                }
+
+                if (MethodCode == 21)
+                {
+                    /// 娣诲姞姘存祦鍥捐〃鏁版嵁
+                    string[] datas = strData.Split(';');
+                    int ovenIndex = int.Parse(datas[0]);
+                    int cellIndex = int.Parse(datas[1]);
+                    double waterTemp = double.Parse(datas[2]);
+                    double backWaterTemp = double.Parse(datas[3]);
+                    double waterPressure = double.Parse(datas[4]);
+                    tables[ovenIndex].GetSingleCell(cellIndex).AddWaterData(waterTemp, backWaterTemp, waterPressure);
+                    return;
+                }
+
+                if (MethodCode == 99)
+                {
+                    string[] datas = strData.Split(';');
+                    int index = int.Parse(datas[0]);
+                    if (MsgBoxHelper2.WarnChoose(datas[1], "鎻愮ず") == MessageBoxResult.OK)
+                    {
+                        executionWrapper.SetStationGlobalValue<int>($"StationState.Pipe[{index}]", 1, 1);
+                        //SequenceContext.StationGlobals.SetValNumber($"StationState.Pipe[{index}]", 1, 1);
+                    }
+                    else
+                    {
+                        executionWrapper.SetStationGlobalValue<int>($"StationState.Pipe[{index}]", 1, 0);
+                        //SequenceContext.StationGlobals.SetValNumber($"StationState.Pipe[{index}]", 1, 0);
+                    }
+                    return;
+                }
+            }
+            catch (Exception ex)
+            {
+                BurnInUI.Logger.Warn("TestStand Event Error.", ex);
+            }
+        }
+
+        private void axApplicationMgr_UIMessageEvent(object sender, _ApplicationMgrEvents_UIMessageEventEvent e)
+        {
+            int EventCase = Convert.ToInt32(e.uiMsg.Event);
+            int MethodCode = Convert.ToInt16(e.uiMsg.NumericData);
+            string strData = e.uiMsg.StringData;
+            HandleUIMessage(EventCase, MethodCode, strData, e.uiMsg.ActiveXData);
+        }
+
+        public void SetStartFlag(int index, bool data)
+        {
+            string name = tables[index].title;
+            string key = string.Empty;
+            if (data)
+            {
+                BurnInUI.Logger?.Info($"鍚姩鑰佸寲鐐塠{name}]");
+                key = $"StationHMI.UUTHMI[{index}].Start";
+            }
+            else
+            {
+                BurnInUI.Logger?.Info($"鍋滄鑰佸寲鐐塠{name}]");
+                key = $"StationHMI.UUTHMI[{index}].Stop";
+            }
+            executionWrapper?.SetStationGlobalValue<bool>(key, 1, true);
+        }
+
+        public void ReleaseResource()
+        {
+            executionWrapper.UIMessageEvent -= ExecutionWrapper_UIMessageEvent;
+
+            foreach (var table in tables)
+            {
+                table.RelaseResource();
+            }
+        }
+
+        /// <summary>
+        /// 璁℃暟娓呴浂
+        /// </summary>
+        public void ClearNumber()
+        {
+            foreach (var table in tables)
+            {
+                table.ClearNumber();
+            }
+        }
+
+        public void SetProductSource(List<string> productSource)
+        {
+            foreach (var table in tables)
+            {
+                table.SetProductSource(productSource);
+            }
+        }
+
+        public void SetCurrentProduct(string currentProduct)
+        {
+            for (int i = 0; i < tables.Length; i++)
+            {
+                var table = tables[i];
+                string LookupStr = $"StationHMI.UUTHMI[{i}].Model";
+
+                string tableModel = executionWrapper?.GetStationGlobalValue<string>(LookupStr, 1);
+                //engine?.Globals?.GetValString(LookupStr, 1);
+                table.SetCurrentProduct(tableModel);
+            }
+        }
+
+        public void SetOvenProduct(int index, string product)
+        {
+            string LookupStr = $"StationHMI.UUTHMI[{index}].Model";
+            executionWrapper?.SetStationGlobalValue<string>(LookupStr, 1, product);
+            executionWrapper?.CommitToDisk(false);
+        }
+
+        public void RefreshProducts()
+        {
+        }
+    }
+}
diff --git a/Plugin.BurnIn/UI/LiveChart.xaml b/Plugin.BurnIn/UI/LiveChart.xaml
new file mode 100644
index 0000000..72d5fce
--- /dev/null
+++ b/Plugin.BurnIn/UI/LiveChart.xaml
@@ -0,0 +1,29 @@
+锘�<hb:NotifyControl x:Class="Plugin.BurnIn.LiveChart"
+             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:Plugin.BurnIn"
+             mc:Ignorable="d" 
+             
+             xmlns:hb="clr-namespace:HMI.Base;assembly=HMI.Base"
+             xmlns:lvc="clr-namespace:LiveChartsCore.SkiaSharpView.WPF;assembly=LiveChartsCore.SkiaSharpView.WPF"
+             
+             Loaded="UserControl_Loaded"
+             Unloaded="UserControl_Unloaded"
+             SizeChanged="UserControl_SizeChanged"
+             d:DesignHeight="450" d:DesignWidth="800">
+    <Grid>
+        <lvc:CartesianChart Series="{Binding Series}" BorderThickness="1" BorderBrush="Black"
+                            Padding="0"
+                            Margin="2"
+                            SyncContext="{Binding Sync}"
+                            XAxes="{Binding XAxes}"
+                            YAxes="{Binding YAxes}"
+                            TooltipTextPaint="{Binding TooltipTextPaint}"
+                            TooltipTextSize="{Binding TooltipTextSize}"
+                            DrawMarginFrame="{Binding DrawMarginFrame}"
+                            x:Name="chart">
+        </lvc:CartesianChart>
+    </Grid>
+</hb:NotifyControl>
diff --git a/Plugin.BurnIn/UI/LiveChart.xaml.cs b/Plugin.BurnIn/UI/LiveChart.xaml.cs
new file mode 100644
index 0000000..6e5ad76
--- /dev/null
+++ b/Plugin.BurnIn/UI/LiveChart.xaml.cs
@@ -0,0 +1,516 @@
+锘縰sing HMI.Base;
+using LiveChartsCore;
+using LiveChartsCore.Defaults;
+using LiveChartsCore.Drawing;
+using LiveChartsCore.Drawing.Layouts;
+using LiveChartsCore.Kernel.Sketches;
+using LiveChartsCore.SkiaSharpView;
+using LiveChartsCore.SkiaSharpView.Drawing;
+using LiveChartsCore.SkiaSharpView.Drawing.Geometries;
+using LiveChartsCore.SkiaSharpView.Drawing.Layouts;
+using LiveChartsCore.SkiaSharpView.Painting;
+using LiveChartsCore.SkiaSharpView.SKCharts;
+using SkiaSharp;
+using System.Collections.ObjectModel;
+
+namespace Plugin.BurnIn
+{
+    public class SeriesConfig
+    {
+        public static readonly float FontSize = 12;
+        /// <summary>
+        /// 鏁版嵁鍚�
+        /// </summary>
+        public string Name { get; set; }
+        public string YAxesName { get; set; }
+        public int YAxesIndex { get; set; }
+    }
+
+    public class CustomLegend : SKDefaultLegend
+    {
+
+        protected override Layout<SkiaSharpDrawingContext> GetLayout(LiveChartsCore.Chart chart)
+        {
+            var stackLayout = new StackLayout
+            {
+                Orientation = ContainerOrientation.Horizontal,
+                Padding = new Padding(0, 10, 0, 0),
+                HorizontalAlignment = Align.Start,
+                VerticalAlignment = Align.Middle,
+            };
+
+            foreach (var series in chart.Series.Where(x => x.IsVisibleAtLegend))
+            {
+                stackLayout.Children.Add(new LegendItem(series));
+            }
+            return stackLayout;
+        }
+    }
+
+    public class LegendItem : StackLayout
+    {
+        public LegendItem(ISeries series)
+        {
+            Orientation = ContainerOrientation.Horizontal;
+            Padding = new Padding(0);
+            VerticalAlignment = Align.Start;
+            HorizontalAlignment = Align.Middle;
+            Opacity = series.IsVisible ? 1 : 0.5f;
+
+            var miniature = (IDrawnElement<SkiaSharpDrawingContext>)series.GetMiniatureGeometry(null);
+            Children = new List<IDrawnElement<SkiaSharpDrawingContext>> {
+                miniature,
+            new LabelGeometry
+            {
+                Text = series.Name,
+                TextSize = SeriesConfig.FontSize,
+                Paint = new SolidColorPaint(SKColors.Black){
+                    SKTypeface = SKFontManager.Default.MatchCharacter('姹�')
+                },
+                Padding = new Padding(5,0,10,0),
+                VerticalAlign = Align.Middle,
+                HorizontalAlign = Align.Start
+            }};
+        }
+    }
+
+    public partial class LiveChart : NotifyControl
+    {
+        private static SKColor[] colors = new SKColor[] {
+            //new SKColor(229, 84, 62),
+            //new SKColor(72, 143, 239),
+            //new SKColor(147, 196, 83),
+            new SKColor(255, 0, 0),
+            new SKColor(0, 0, 255),
+            new SKColor(0, 255, 0),
+            new SKColor(76, 184, 210),
+            new SKColor(246, 198, 50),
+        };
+
+        private DateTime startTime;
+        private string configText;
+        private bool showLegend;
+        private bool enableResize;
+
+        private SolidColorPaint tooltipTextPaint = new SolidColorPaint(SKColors.Black)
+        {
+            SKTypeface = SKFontManager.Default.MatchCharacter('姹�')
+        };
+        public SolidColorPaint TooltipTextPaint
+        {
+            get => tooltipTextPaint;
+            set
+            {
+                Set(ref tooltipTextPaint, value);
+            }
+        }
+
+        private double tooltipTextSize = SeriesConfig.FontSize;
+        public double TooltipTextSize
+        {
+            get => tooltipTextSize;
+            set
+            {
+                Set(ref tooltipTextSize, value);
+            }
+        }
+
+        private Dictionary<string, SeriesConfig> configCache = new Dictionary<string, SeriesConfig>();
+
+        private Dictionary<string, ISeries> seriesCache = new Dictionary<string, ISeries>();
+        private Dictionary<string, List<DateTimePoint>> valueCache = new Dictionary<string, List<DateTimePoint>>();
+
+        private Dictionary<string, double> minValueCache = new Dictionary<string, double>();
+        private Dictionary<string, double> maxValueCache = new Dictionary<string, double>();
+
+        private ObservableCollection<ISeries> series = new ObservableCollection<ISeries>();
+        public ObservableCollection<ISeries> Series
+        {
+            get => series;
+            set
+            {
+
+                Set(ref series, value);
+            }
+        }
+
+        public ObservableCollection<ICartesianAxis> YAxes { get; set; } = new ObservableCollection<ICartesianAxis>();
+
+        private Axis[] xAxes;
+        public Axis[] XAxes
+        {
+            get => xAxes; set
+            {
+                Set(ref xAxes, value);
+            }
+        }
+
+        public DrawMarginFrame DrawMarginFrame => new DrawMarginFrame()
+        {
+            Fill = new SolidColorPaint(new SKColor(223, 223, 223)),
+            Stroke = new SolidColorPaint(new SKColor(180, 180, 180), 1)
+        };
+
+        public object Sync { get; } = new object();
+
+        private int CreateYAxis(string name)
+        {
+            for (int i = 0; i < YAxes.Count; i++)
+            {
+                if (YAxes[i].Name == name)
+                {
+                    return i;
+                }
+            }
+            if (YAxes.Count == 0)
+            {
+                YAxes.Add(new Axis
+                {
+                    Name = name,
+                    MinStep = 1,
+                    TextSize = SeriesConfig.FontSize,
+                    NameTextSize = SeriesConfig.FontSize,
+                    NamePaint = new SolidColorPaint(new SKColor(0, 0, 0))
+                    {
+                        SKTypeface = SKFontManager.Default.MatchCharacter('姹�')
+                    },
+                    Padding = new Padding(0, 0, 5, 0),
+                    LabelsPaint = new SolidColorPaint(new SKColor(0, 0, 0)),
+                    SeparatorsPaint = new SolidColorPaint
+                    {
+                        Color = SKColors.Black.WithAlpha(30),
+                        StrokeThickness = 0.5f
+                    },
+                    TicksPaint = new SolidColorPaint
+                    {
+                        Color = SKColor.Empty,
+                        StrokeThickness = 0
+                    },
+                });
+            }
+            else
+            {
+                YAxes.Add(new Axis
+                {
+                    Name = name,
+                    Position = LiveChartsCore.Measure.AxisPosition.End,
+                    MinStep = 1,
+                    TextSize = SeriesConfig.FontSize,
+                    NameTextSize = SeriesConfig.FontSize,
+                    NamePaint = new SolidColorPaint(new SKColor(0, 0, 0))
+                    {
+                        SKTypeface = SKFontManager.Default.MatchCharacter('姹�')
+                    },
+                    Padding = new Padding(5, 0, 0, 0),
+                    LabelsPaint = new SolidColorPaint(new SKColor(0, 0, 0)),
+                    TicksPaint = new SolidColorPaint
+                    {
+                        Color = SKColor.Empty,
+                        StrokeThickness = 0
+                    },
+                    ShowSeparatorLines = false,
+                });
+            }
+            return YAxes.Count - 1;
+        }
+
+        public void RefreshConfig(string configStr)
+        {
+            configText = configStr;
+            configCache.Clear();
+            seriesCache.Clear();
+            valueCache.Clear();
+            minValueCache.Clear();
+            maxValueCache.Clear();
+            Series.Clear();
+            YAxes.Clear();
+            startTime = DateTime.MinValue;
+            try
+            {
+                string[] arr = configStr.Split(';');
+                for (int i = 0; i < arr.Length; i++)
+                {
+                    var config = arr[i];
+                    string[] datas = config.Split(',');
+                    var sConfig = new SeriesConfig();
+                    if (datas.Length >= 2)
+                    {
+                        sConfig.YAxesName = datas[1];
+                    }
+                    else
+                    {
+                        sConfig.YAxesName = datas[0];
+                    }
+
+                    sConfig.Name = datas[0];
+                    sConfig.YAxesIndex = CreateYAxis(sConfig.YAxesName);
+                    if (!string.IsNullOrEmpty(sConfig.Name))
+                    {
+                        configCache[sConfig.Name] = sConfig;
+                        var values = new List<DateTimePoint>();
+                        valueCache[sConfig.Name] = values;
+
+                        var ls = new LineSeries<DateTimePoint>
+                        {
+                            Name = sConfig.Name,
+                            Values = values,
+                            ScalesYAt = sConfig.YAxesIndex,
+                            Fill = null,
+                            GeometryFill = null,
+                            GeometryStroke = null,
+                            LineSmoothness = 0,
+                            Stroke = new SolidColorPaint(GetColor(i)) { StrokeThickness = 0.8f },
+                        };
+                        seriesCache[sConfig.Name] = ls;
+                        Series.Add(ls);
+                    }
+                }
+                if (showLegend)
+                {
+                    chart.LegendPosition = LiveChartsCore.Measure.LegendPosition.Top;
+                    chart.Legend = new CustomLegend();
+                }
+                chart.TooltipTextPaint = new SolidColorPaint(SKColors.Black)
+                {
+                    SKTypeface = SKFontManager.Default.MatchCharacter('姹�')
+                };
+            }
+            catch (Exception ex)
+            {
+
+            }
+        }
+
+        public LiveChart(string configStr, bool showLegend = true, bool enableResize = false)
+        {
+            DataContext = this;
+            this.showLegend = showLegend;
+            this.enableResize = enableResize;
+
+            var xAxis = new DateTimeAxis(TimeSpan.FromSeconds(10), Formatter)
+            {
+                Padding = new Padding(0),
+                TextSize = SeriesConfig.FontSize,
+                NameTextSize = SeriesConfig.FontSize,
+                CustomSeparators = GetSeparators(),
+                AnimationsSpeed = TimeSpan.FromMilliseconds(0),
+                SeparatorsPaint = new SolidColorPaint(SKColors.Black.WithAlpha(30)),
+            };
+
+            XAxes = new Axis[] { xAxis };
+            InitializeComponent();
+            RefreshConfig(configStr);
+        }
+
+        private double[] GetSeparators()
+        {
+            if (valueCache.Count <= 0)
+            {
+                return new double[] { DateTime.Now.Ticks };
+            }
+            if (valueCache.Values.First().Count <= 5)
+            {
+                //return new double[] { now.Ticks };
+                return valueCache.Values.First().Select(e => (double)e.DateTime.Ticks).ToArray();
+            }
+            var now = valueCache.Values.First().Last().DateTime;
+
+            var step = (now - startTime).TotalSeconds / 6;
+
+            return
+            new double[] {
+                now.AddSeconds(-5*step).Ticks,
+                now.AddSeconds(-4*step).Ticks,
+                now.AddSeconds(-3*step).Ticks,
+                now.AddSeconds(-2*step).Ticks,
+                now.AddSeconds(-step).Ticks,
+            };
+        }
+
+        private static string Formatter(DateTime date)
+        {
+            //var secsAgo = (DateTime.Now - date).TotalSeconds;
+            //return secsAgo < 1
+            //    ? "now"
+            //    : $"{secsAgo:N0}s ago";
+            return date.ToString("HH:mm:ss");
+        }
+
+        private void RefreshChart()
+        {
+            XAxes[0].CustomSeparators = GetSeparators();
+        }
+
+        private double GetMaxByName(string name)
+        {
+            return maxValueCache.ContainsKey(name) ? maxValueCache[name] : double.MinValue;
+        }
+
+        private double GetMinByName(string name)
+        {
+            return minValueCache.ContainsKey(name) ? minValueCache[name] : double.MaxValue;
+        }
+
+        //public void AddData(string name, double value)
+        //{
+        //    var now = DateTime.Now;
+        //    if (valueCache.ContainsKey(name))
+        //    {
+        //        var config = configCache[name];
+        //        valueCache[name].Add(new DateTimePoint(now, value));
+
+
+        //        minValueCache[config.YAxesName] = Math.Min(GetMinByName(config.YAxesName), value);
+        //        maxValueCache[config.YAxesName] = Math.Max(GetMaxByName(config.YAxesName), value);
+
+        //        YAxes[config.YAxesIndex].MinLimit = minValueCache[config.YAxesName] - 1;
+        //        YAxes[config.YAxesIndex].MaxLimit = maxValueCache[config.YAxesName] + 1;
+
+        //        RefreshChart();
+        //    }
+        //    if (startTime == DateTime.MinValue)
+        //    {
+        //        startTime = now;
+        //    }
+        //}
+
+        private SKColor GetColor(int index)
+        {
+            if (index >= colors.Length)
+            {
+                return colors[index % colors.Length];
+            }
+            return colors[index];
+        }
+
+        private void AddNewSeries(string name, string YName)
+        {
+            var config = new SeriesConfig
+            {
+                Name = name,
+                YAxesName = YName,
+                YAxesIndex = CreateYAxis(YName)
+            };
+            configCache[name] = config;
+
+            var values = new List<DateTimePoint>();
+            valueCache[name] = values;
+
+            var ls = new LineSeries<DateTimePoint>
+            {
+                Name = name,
+                Values = values,
+                ScalesYAt = config.YAxesIndex,
+                Fill = null,
+                GeometryFill = null,
+                GeometryStroke = null,
+                LineSmoothness = 0,
+                Stroke = new SolidColorPaint(GetColor(Series.Count)) { StrokeThickness = 0.8f },
+            };
+            seriesCache[Name] = ls;
+            Series.Add(ls);
+        }
+
+        public void AddDynamicData(string name, string YName, DateTime now, double value)
+        {
+            if (!valueCache.ContainsKey(name))
+            {
+                AddNewSeries(name, YName);
+            }
+
+            var config = configCache[name];
+            valueCache[name].Add(new DateTimePoint(now, value));
+
+
+            minValueCache[config.YAxesName] = Math.Min(GetMinByName(config.YAxesName), value);
+            maxValueCache[config.YAxesName] = Math.Max(GetMaxByName(config.YAxesName), value);
+
+            YAxes[config.YAxesIndex].MinLimit = minValueCache[config.YAxesName] - 1;
+            YAxes[config.YAxesIndex].MaxLimit = maxValueCache[config.YAxesName] + 1;
+
+            RefreshChart();
+
+
+            if (startTime == DateTime.MinValue)
+            {
+                startTime = now;
+            }
+        }
+
+        public void AddData(string name, DateTime now, double value)
+        {
+            if (valueCache.ContainsKey(name))
+            {
+                var config = configCache[name];
+                valueCache[name].Add(new DateTimePoint(now, value));
+
+
+                minValueCache[config.YAxesName] = Math.Min(GetMinByName(config.YAxesName), value);
+                maxValueCache[config.YAxesName] = Math.Max(GetMaxByName(config.YAxesName), value);
+
+                YAxes[config.YAxesIndex].MinLimit = minValueCache[config.YAxesName] - 1;
+                YAxes[config.YAxesIndex].MaxLimit = maxValueCache[config.YAxesName] + 1;
+
+                RefreshChart();
+            }
+            if (startTime == DateTime.MinValue)
+            {
+                startTime = now;
+            }
+        }
+
+        public void Clear()
+        {
+            RefreshConfig(configText);
+        }
+
+        public bool CanHandle(string name)
+        {
+            return configCache.ContainsKey(name);
+        }
+
+        public List<double> GetSingleData(string name)
+        {
+            if (valueCache.ContainsKey(name))
+            {
+                return valueCache[name].Select(d => d.Value.Value).ToList();
+            }
+            return null;
+        }
+
+        private void UserControl_Loaded(object sender, System.Windows.RoutedEventArgs e)
+        {
+            //refreshTimer.Start();
+            //timer?.Start();
+        }
+
+        private void UserControl_Unloaded(object sender, System.Windows.RoutedEventArgs e)
+        {
+            //timer?.Stop();
+        }
+
+        private void UserControl_SizeChanged(object sender, System.Windows.SizeChangedEventArgs e)
+        {
+            if (enableResize)
+            {
+                var size = FontSizeConverter.CountFontSize(12, ActualHeight, 92);
+                foreach (var y in YAxes)
+                {
+                    y.TextSize = size;
+                    y.NameTextSize = size;
+                }
+
+                foreach (var x in XAxes)
+                {
+                    x.TextSize = size;
+                    x.NameTextSize = size;
+                }
+                TooltipTextSize = size;
+                //TooltipTextPaint = new SolidColorPaint(SKColors.Black)
+                //{
+                //    SKTypeface = SKFontManager.Default.MatchCharacter('姹�')
+                //};
+            }
+        }
+    }
+}
diff --git a/Plugin.BurnIn/Util/CommonCodeParser.cs b/Plugin.BurnIn/Util/CommonCodeParser.cs
new file mode 100644
index 0000000..7acea73
--- /dev/null
+++ b/Plugin.BurnIn/Util/CommonCodeParser.cs
@@ -0,0 +1,28 @@
+锘縰sing Plugin.BurnIn.Model;
+
+namespace Plugin.BurnIn.Util
+{
+    public class CommonCodeParser : ICodeParser
+    {
+        public CellInfo GetCellInfoByCode(string code)
+        {
+            string[] arr = code.Split('-');
+            if (arr == null || arr.Length != 3)
+            {
+                throw new Exception("鎵爜鏍煎紡寮傚父");
+            }
+            return new CellInfo()
+            {
+                OriginCode = code,
+                MachineName = arr[0],
+                OvenName = arr[1],
+                Index = int.Parse(arr[2]) - 1,
+            };
+        }
+
+        public CellInfo GetCellInfoByCode(string model, int columns, int rows, string code)
+        {
+            return GetCellInfoByCode(code);
+        }
+    }
+}
diff --git a/Plugin.BurnIn/Util/DpUtil.cs b/Plugin.BurnIn/Util/DpUtil.cs
new file mode 100644
index 0000000..c1bfd5a
--- /dev/null
+++ b/Plugin.BurnIn/Util/DpUtil.cs
@@ -0,0 +1,220 @@
+锘縰sing System.Windows;
+using System.Windows.Controls;
+using System.Windows.Controls.Primitives;
+using System.Windows.Media;
+
+namespace Plugin.BurnIn
+{
+    public class GridProperties
+    {
+        public static bool GetShowBorder(DependencyObject obj)
+        {
+            return (bool)obj.GetValue(ShowBorderProperty);
+        }
+        public static void SetShowBorder(DependencyObject obj, bool value)
+        {
+            obj.SetValue(ShowBorderProperty, value);
+        }
+        public static readonly DependencyProperty ShowBorderProperty =
+            DependencyProperty.RegisterAttached("ShowBorder", typeof(bool), typeof(Grid), new PropertyMetadata(false, OnShowBorderChanged));
+
+        public static int GetBorderSpace(DependencyObject obj)
+        {
+            return (int)obj.GetValue(BorderSpaceProperty);
+        }
+        public static void SetBorderSpace(DependencyObject obj, int value)
+        {
+            obj.SetValue(BorderSpaceProperty, value);
+        }
+        public static readonly DependencyProperty BorderSpaceProperty =
+            DependencyProperty.RegisterAttached("BorderSpace", typeof(int), typeof(Grid), new PropertyMetadata(0, OnShowBorderChanged));
+
+
+
+        private static void OnShowBorderChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+        {
+            if (d is Grid grid)
+            {
+                if (e.Property == ShowBorderProperty)
+                {
+                    grid.Loaded += GridInnerBorderLoaded;
+                    return;
+                }
+                else if (e.Property == BorderSpaceProperty)
+                {
+                    grid.Loaded += GridSpaceOnLoaded;
+                    return;
+                }
+            }
+            else if (d is UniformGrid uniformGrid)
+            {
+                uniformGrid.Loaded += UniformGridOnLoad;
+                return;
+            }
+        }
+
+        private static void GridInnerBorderLoaded(object sender, RoutedEventArgs e)
+        {
+            if (!(sender is Grid grid)) return;
+            var rowCount = grid.RowDefinitions.Count;
+            var columnCount = grid.ColumnDefinitions.Count;
+            int totalCount = rowCount * columnCount;
+
+            int width = 1;
+
+            string baseData = "None";
+            HashSet<string> data = new HashSet<string>(baseData.Split(','));
+            bool ignoreTop = data.Contains("All") || data.Contains("Top"),
+                ignoreBottom = data.Contains("All") || data.Contains("Bottom"),
+                ignoreLeft = data.Contains("All") || data.Contains("Left"),
+                ignoreRight = data.Contains("All") || data.Contains("Right");
+
+            foreach (var child in grid.Children)
+            {
+                int row = Grid.GetRow((UIElement)child);
+                int col = Grid.GetColumn((UIElement)child);
+
+                var bt = new Thickness(col == 0 ? (ignoreLeft ? 0 : width) : 0,
+                    row == 0 ? (ignoreTop ? 0 : width) : 0,
+                    (col == columnCount - 1 && ignoreRight) ? 0 : width,
+                    width);
+
+                if (child is UserControl control)
+                {
+                    control.BorderBrush = Brushes.Black;
+                    control.BorderThickness = bt;
+                }
+
+                if (child is Label label)
+                {
+                    label.BorderBrush = Brushes.Black;
+                    label.BorderThickness = bt;
+                }
+
+                if (child is TextBox box)
+                {
+                    box.BorderBrush = Brushes.Black;
+                    box.BorderThickness = bt;
+                }
+            }
+
+            /// 寮冪敤
+            //for (int i = 0; i < totalCount; i++)
+            //{
+            //    int row = i / columnCount;
+            //    int col = i % columnCount;
+
+            //    if (i > grid.Children.Count - 1) {
+            //        continue;
+            //    }
+            //    var cell = grid.Children[i];
+            //    var bt = new Thickness(col == 0 ? (ignoreLeft ? 0 : width) : 0,
+            //        row == 0 ? (ignoreTop ? 0 : width) : 0,
+            //        (col == columnCount - 1 && ignoreRight) ? 0 : width,
+            //        width);
+
+            //    if (cell is UserControl child)
+            //    {
+            //        child.BorderBrush = Brushes.Black;
+            //        child.BorderThickness = bt;
+            //    }
+
+            //    if (cell is Label label)
+            //    {
+            //        label.BorderBrush = Brushes.Black;
+            //        label.BorderThickness = bt;
+            //    }
+
+            //    if (cell is TextBox box)
+            //    {
+            //        box.BorderBrush = Brushes.Black;
+            //        box.BorderThickness = bt;
+            //    }
+            //}
+            grid.Loaded -= GridInnerBorderLoaded;
+        }
+
+        private static void GridSpaceOnLoaded(object sender, RoutedEventArgs e)
+        {
+            if (!(sender is Grid grid)) return;
+            int space = GetBorderSpace(grid);
+            if (space <= 0)
+            {
+                return;
+            }
+
+            var rowCount = grid.RowDefinitions.Count;
+            var columnCount = grid.ColumnDefinitions.Count;
+            int totalCount = rowCount * columnCount;
+
+            for (int i = 0; i < totalCount; i++)
+            {
+                int row = i / columnCount;
+                int col = i % columnCount;
+
+                if (i > grid.Children.Count - 1)
+                {
+                    continue;
+                }
+                var cell = grid.Children[i];
+
+                var bt = new Thickness(col == 0 ? space : 0, row == 0 ? space : 0, space, space);
+
+                if (cell is UserControl child)
+                {
+                    child.Margin = bt;
+                }
+            }
+            grid.Loaded -= GridSpaceOnLoaded;
+        }
+
+        private static void UniformGridOnLoad(object sender, RoutedEventArgs e)
+        {
+            if (!(sender is UniformGrid grid)) return;
+            //var rowCount = grid.Rows;
+            int columnCount = grid.Columns;
+            int totalCount = grid.Children.Count;
+            int rowCount = totalCount / columnCount;
+            int width = 1;
+
+            for (int i = 0; i < grid.Children.Count; i++)
+            {
+                int row = i / columnCount;
+                int col = i % columnCount;
+                var child = grid.Children[i];
+
+                string baseData = "None";
+                HashSet<string> data = new HashSet<string>(baseData.Split(','));
+                bool ignoreTop = data.Contains("All") || data.Contains("Top"),
+                    ignoreBottom = data.Contains("All") || data.Contains("Bottom"),
+                    ignoreLeft = data.Contains("All") || data.Contains("Left"),
+                    ignoreRight = data.Contains("All") || data.Contains("Right");
+
+                var bt = new Thickness(col == 0 ? (ignoreLeft ? 0 : width) : 0,
+                    row == 0 ? (ignoreTop ? 0 : width) : 0,
+                    (col == columnCount - 1 && ignoreRight) ? 0 : width,
+                    (row == rowCount && ignoreBottom) ? 0 : width);
+
+                if (child is UserControl control)
+                {
+                    control.BorderBrush = Brushes.Black;
+                    control.BorderThickness = bt;
+                }
+
+                if (child is Label label)
+                {
+                    label.BorderBrush = Brushes.Black;
+                    label.BorderThickness = bt;
+                }
+
+                if (child is TextBox box)
+                {
+                    box.BorderBrush = Brushes.Black;
+                    box.BorderThickness = bt;
+                }
+            }
+            grid.Loaded -= UniformGridOnLoad;
+        }
+    }
+
+}
diff --git a/Plugin.BurnIn/Util/FontSizeConverter.cs b/Plugin.BurnIn/Util/FontSizeConverter.cs
new file mode 100644
index 0000000..9e04df9
--- /dev/null
+++ b/Plugin.BurnIn/Util/FontSizeConverter.cs
@@ -0,0 +1,32 @@
+锘縰sing System.Globalization;
+using System.Windows.Data;
+
+namespace Plugin.BurnIn
+{
+    public class FontSizeConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            if (parameter == null) return 15;
+
+            string[] ps = parameter.ToString().Split(',');
+            int size = int.Parse(ps[0]);
+            int baseSize = int.Parse(ps[1]);
+            if (value is double width)
+            {
+                return CountFontSize(size, width, baseSize);
+            }
+            return 15;
+        }
+
+        public static double CountFontSize(double size, double limit, double baseSize)
+        {
+            return size * limit / baseSize;
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}
diff --git a/Plugin.BurnIn/Util/ICodeParser.cs b/Plugin.BurnIn/Util/ICodeParser.cs
new file mode 100644
index 0000000..ef4865d
--- /dev/null
+++ b/Plugin.BurnIn/Util/ICodeParser.cs
@@ -0,0 +1,10 @@
+锘縰sing Plugin.BurnIn.Model;
+
+namespace Plugin.BurnIn.Util
+{
+    public interface ICodeParser
+    {
+        CellInfo GetCellInfoByCode(string code);
+        CellInfo GetCellInfoByCode(string model,int columns,int rows, string code);
+    }
+}
diff --git a/Plugin.BurnIn/Util/JKCodeParser.cs b/Plugin.BurnIn/Util/JKCodeParser.cs
new file mode 100644
index 0000000..1176191
--- /dev/null
+++ b/Plugin.BurnIn/Util/JKCodeParser.cs
@@ -0,0 +1,30 @@
+锘縰sing Plugin.BurnIn.Model;
+
+namespace Plugin.BurnIn.Util
+{
+    public class JKCodeParser : ICodeParser
+    {
+        public CellInfo GetCellInfoByCode(string code)
+        {
+            string[] arr = code.Split('-');
+            if (arr == null || arr.Length != 3)
+            {
+                throw new Exception("鎵爜鏍煎紡寮傚父");
+            }
+            int row = int.Parse(arr[1]);
+            int index = int.Parse(arr[2]);
+
+            return new CellInfo()
+            {
+                OriginCode = code,
+                OvenName = arr[0],
+                Index = (row - 1) * 4 + (index - 1),
+            };
+        }
+
+        public CellInfo GetCellInfoByCode(string model, int columns, int rows, string code)
+        {
+            return GetCellInfoByCode(code);
+        }
+    }
+}
diff --git a/Plugin.BurnIn/Util/ModelCodeParser.cs b/Plugin.BurnIn/Util/ModelCodeParser.cs
new file mode 100644
index 0000000..da04201
--- /dev/null
+++ b/Plugin.BurnIn/Util/ModelCodeParser.cs
@@ -0,0 +1,97 @@
+锘縰sing HMI.Core.Entity;
+using HMI.Core.Manager;
+using Plugin.BurnIn.Model;
+
+namespace Plugin.BurnIn.Util
+{
+    public class ModelCodeParser : ICodeParser
+    {
+        public CellInfo GetCellInfoByCode(string code)
+        {
+            string[] arr = code.Split('-');
+            if (arr == null || arr.Length != 3)
+            {
+                throw new Exception("鎵爜鏍煎紡寮傚父");
+            }
+            return new CellInfo()
+            {
+                OriginCode = code,
+                MachineName = arr[0],
+                OvenName = arr[1],
+                Index = int.Parse(arr[2]) - 1,
+            };
+        }
+
+        public CellInfo GetCellInfoByCode(string model, int columns, int rows, string code)
+        {
+            var list = ProductManager.GetParametersByProductName(model);
+            ParameterData config = null;
+            if (list != null && list.Count > 0)
+            {
+                config = list.Where(e => e.Name == "PositionCodeConfig").FirstOrDefault();
+            }
+            if (string.IsNullOrEmpty(config?.Value))
+            {
+                // 浜у搧娌℃湁灏辨寜榛樿鐨勫幓閰�
+                config = ParameterManager.GetCommonParameterByName("PositionCodeConfig");
+                if (string.IsNullOrEmpty(config?.Value))
+                {
+                    throw new Exception($"褰撳墠鍨嬪彿[{model}]鏈厤缃┐浣嶈鍒�");
+                }
+            }
+            string value = config.Value;
+            string[] arr = value?.Split(';');
+            try
+            {
+                if (arr.Length == 2)
+                {
+                    string[] v1s = arr[0].Split(',');
+                    string[] v2s = arr[1].Split(',');
+
+                    return new CellInfo
+                    {
+                        OriginCode = code,
+                        OvenName = code.Substring(int.Parse(v1s[0]), int.Parse(v1s[1])),
+                        Index = int.Parse(code.Substring(int.Parse(v2s[0]), int.Parse(v2s[1]))) - 1
+                    };
+                }
+                else if (arr.Length == 3)
+                {
+                    string[] v1s = arr[0].Split(',');
+                    string[] v2s = arr[1].Split(',');
+                    string[] v3s = arr[2].Split(',');
+
+                    int row = int.Parse(code.Substring(int.Parse(v2s[0]), int.Parse(v2s[1])));
+                    int index = int.Parse(code.Substring(int.Parse(v3s[0]), int.Parse(v3s[1])));
+
+                    return new CellInfo()
+                    {
+                        OriginCode = code,
+                        OvenName = code.Substring(int.Parse(v1s[0]), int.Parse(v1s[1])),
+                        Index = (row - 1) * columns + (index - 1),
+                    };
+                }
+                else if (arr.Length >= 4)
+                {
+                    string[] v1s = arr[0].Split(',');
+                    string[] v2s = arr[1].Split(',');
+                    string[] v3s = arr[2].Split(',');
+                    columns = int.Parse(arr[3]);
+                    int row = int.Parse(code.Substring(int.Parse(v2s[0]), int.Parse(v2s[1])));
+                    int index = int.Parse(code.Substring(int.Parse(v3s[0]), int.Parse(v3s[1])));
+                    return new CellInfo()
+                    {
+                        OriginCode = code,
+                        OvenName = code.Substring(int.Parse(v1s[0]), int.Parse(v1s[1])),
+                        Index = (row - 1) * columns + (index - 1),
+                    };
+                }
+                throw new Exception($"褰撳墠鍨嬪彿[{model}]绌翠綅瑙勫垯閰嶇疆寮傚父");
+            }
+            catch (Exception ex)
+            {
+                throw new Exception($"褰撳墠鍨嬪彿[{model}]瑙f瀽寮傚父:{ex.Message}", ex);
+            }
+        }
+    }
+}
diff --git a/Plugin.BurnIn/Util/PNUtil.cs b/Plugin.BurnIn/Util/PNUtil.cs
new file mode 100644
index 0000000..955f732
--- /dev/null
+++ b/Plugin.BurnIn/Util/PNUtil.cs
@@ -0,0 +1,106 @@
+锘縰sing HMI.Core.Util;
+using log4net;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using System.Net.Http;
+using System.Text;
+
+namespace Plugin.BurnIn.Util
+{
+    public class HttpResult
+    {
+        public int code { get; set; }
+        public string msg { get; set; }
+        public string ipn { get; set; }
+    }
+
+    public class PNUtil
+    {
+        public static bool GetPN(string mesAddress, string stationName, string sn, out string pn, out string msg)
+        {
+            pn = null;
+            msg = string.Empty;
+            try
+            {
+                //OP170Burn-In鑰佸寲
+                string json = $"{{\"csn\":\"{sn}\",\"station_name\":\"{stationName}\"}}";
+                HttpClient val = new HttpClient();
+                BurnInUI.Logger.Debug($"Get PN Send: [{json}]");
+                try
+                {
+                    StringContent val2 = new StringContent(json, Encoding.UTF8, "application/json");
+                    HttpResponseMessage result = val.PostAsync($"http://{mesAddress}/mes-module-system-2.1.1/lixun/api/getPn", (HttpContent)(object)val2).Result;
+                    result.EnsureSuccessStatusCode();
+                    string resJson = result.Content.ReadAsStringAsync().Result;
+                    HttpResult httpResult = JsonUtil.Deserialize<HttpResult>(resJson);
+                    BurnInUI.Logger.Debug($"Get PN Receive: [{resJson}]");
+                    if (httpResult.code == 200)
+                    {
+                        pn = httpResult.ipn;
+                        return true;
+                    }
+                    msg = httpResult.msg;
+                    return false;
+                }
+                finally
+                {
+                    ((IDisposable)val)?.Dispose();
+                }
+            }
+            catch (Exception ex)
+            {
+                BurnInUI.Logger.Debug($"Get PN Error.", ex);
+                msg = $"璇锋眰PN寮傚父锛亄ex.Message}";
+                return false;
+            }
+        }
+
+
+        public static bool CheckIn(ILog log, string mesAddress, string stationName, string sn, out bool needCheckIn, out string msg)
+        {
+            JObject data = new JObject();
+            data.Add("cmd", "QUERY_RECORD");
+            data.Add("p", "UOP");
+
+            data.Add("sn", sn);
+            data.Add("station_name", stationName);
+            data.Add("op", "A0000001");
+
+            needCheckIn = false;
+            msg = string.Empty;
+            string sendData = string.Empty;
+            string resData = null;
+            try
+            {
+                sendData = data.ToString(Formatting.None);
+                using (HttpClient val = new HttpClient())
+                {
+                    StringContent val2 = new StringContent(sendData, Encoding.UTF8, "application/json");
+                    HttpResponseMessage result = val.PostAsync("http://" + mesAddress + "/mes-module-system-2.1.1/lixun/api/checkIn", (HttpContent)(object)val2).Result;
+                    result.EnsureSuccessStatusCode();
+                    resData = result.Content.ReadAsStringAsync().Result;
+                    JObject httpResult = JObject.Parse(resData);
+
+                    log.Info("CheckIn Send: " + httpResult.Value<string>("sendStr"));
+                    log.Info("CheckIn Receive: " + httpResult.Value<string>("receiveStr"));
+                    log.Info("CheckIn Get: " + resData);
+
+                    msg = httpResult.Value<string>("msg");
+                    if (httpResult.Value<int>("code") == 200)
+                    {
+                        needCheckIn = httpResult.Value<bool>("needCheckIn");
+                        return true;
+                    }
+                    log.Error("CheckIn Error: " + msg);
+                    return false;
+                }
+            }
+            catch (Exception ex)
+            {
+                msg = ex.Message + " in " + ex.StackTrace + ".SendStr: " + sendData + ",ResultStr: " + resData;
+                log.Error("CheckIn Unknow Error: " + msg);
+                return false;
+            }
+        }
+    }
+}
diff --git a/Plugin.BurnIn/Util/RunTimeVariablePool.cs b/Plugin.BurnIn/Util/RunTimeVariablePool.cs
new file mode 100644
index 0000000..1ea1933
--- /dev/null
+++ b/Plugin.BurnIn/Util/RunTimeVariablePool.cs
@@ -0,0 +1,11 @@
+锘縰sing System.Collections.Generic;
+
+namespace Plugin.BurnIn.Util
+{
+    public class RunTimeVariablePool
+    {
+        public static readonly string DEFAULT_PLC_NAME = "BurnInControler";
+
+        public static readonly Dictionary<string, string> CurrentOvenModel = new Dictionary<string, string>();
+    }
+}
diff --git a/Plugin.BurnIn/Util/SNValidator.cs b/Plugin.BurnIn/Util/SNValidator.cs
new file mode 100644
index 0000000..b51934f
--- /dev/null
+++ b/Plugin.BurnIn/Util/SNValidator.cs
@@ -0,0 +1,86 @@
+锘縰sing HMI.Core.Manager;
+
+namespace Plugin.BurnIn.Util
+{
+    public class SNValidator
+    {
+        public static bool ValidateSN(string model, string sn, out string msg)
+        {
+            msg = string.Empty;
+            string validate = string.Empty;
+            try
+            {
+                validate = ProductManager.GetParameterByProductAndName(model, "SNRValidate")?.Value;
+                if (string.IsNullOrEmpty(validate))
+                {
+                    msg = $"褰撳墠鍨嬪彿[{model}]鏈厤缃甋NRValidate鍙傛暟锛屾棤娉曟牎楠孲N鍚堟硶鎬�";
+                    return true;
+                }
+                string[] arr = validate.Split(';');
+                if (arr.Length == 1)
+                {
+                    if (!sn.Contains(arr[0]))
+                    {
+                        msg = $"鏉$爜涓嶅寘鍚浐瀹氱爜[{arr[0]}]";
+                        return false;
+                    }
+                    return true;
+                }
+
+                if (arr.Length == 2)
+                {
+                    int length = int.Parse(arr[1]);
+                    if (!sn.Contains(arr[0]))
+                    {
+                        msg = $"鏉$爜涓嶅寘鍚浐瀹氱爜[{arr[0]}]";
+                        return false;
+                    }
+                    if (sn.Length != length)
+                    {
+                        msg = $"鏉$爜闀垮害搴斾负[{length}]";
+                        return false;
+                    }
+                    return true;
+                }
+
+                if (arr.Length >= 3)
+                {
+                    int length = int.Parse(arr[1]);
+                    int startIndex = int.Parse(arr[2]);
+
+                    if (sn.Length != length)
+                    {
+                        msg = $"鏉$爜闀垮害搴斾负[{length}]";
+                        return false;
+                    }
+
+                    if (startIndex >= sn.Length)
+                    {
+                        msg = $"鏉$爜闀胯灏忎簬璧峰涓嬫爣[{validate}]";
+                        return false;
+                    }
+
+                    if (arr[0].Length > sn.Length)
+                    {
+                        msg = $"鏉$爜闀胯灏忎簬鍥哄畾鐮侀暱搴{validate}]";
+                        return false;
+                    }
+
+                    if (sn.Substring(startIndex, arr[0].Length) != arr[0])
+                    {
+                        msg = $"鍥哄畾鐮佹鏌ュけ璐ワ細 [{validate}]";
+                        return false;
+                    }
+                    return true;
+                }
+                msg = $"褰撳墠鍨嬪彿[{model}]閰嶇疆鍙傛暟[{validate}]瑙f瀽澶辫触";
+                return false;
+            }
+            catch (Exception ex)
+            {
+                msg = $"褰撳墠鍨嬪彿[{model}]閰嶇疆鍙傛暟[{validate}]瑙f瀽澶辫触";
+                return false;
+            }
+        }
+    }
+}
diff --git a/Plugin.BurnIn/Util/ScannerParser.cs b/Plugin.BurnIn/Util/ScannerParser.cs
new file mode 100644
index 0000000..3043ee5
--- /dev/null
+++ b/Plugin.BurnIn/Util/ScannerParser.cs
@@ -0,0 +1,30 @@
+锘縰sing Plugin.BurnIn.Model;
+
+namespace Plugin.BurnIn.Util
+{
+    public class ScannerParser
+    {
+        public static ICodeParser parser;
+
+        public static void Init(string code)
+        {
+            if (code == "JK01")
+            {
+                parser = new JKCodeParser();
+            }
+            else if (code == "Model")
+            {
+                parser = new ModelCodeParser();
+            }
+            else
+            {
+                parser = new CommonCodeParser();
+            }
+        }
+
+        public static CellInfo GetCellInfoByCode(string model, int columns, int rows, string code)
+        {
+            return parser?.GetCellInfoByCode(model, columns, rows, code);
+        }
+    }
+}
diff --git a/Plugin.DataBoard/DataBoard.xaml b/Plugin.DataBoard/DataBoard.xaml
index c512b0f..3dcec31 100644
--- a/Plugin.DataBoard/DataBoard.xaml
+++ b/Plugin.DataBoard/DataBoard.xaml
@@ -11,6 +11,7 @@
          xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI"
                   xmlns:wv2="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf"
              mc:Ignorable="d" 
+             xmlns:lvc="clr-namespace:LiveChartsCore.SkiaSharpView.WPF;assembly=LiveChartsCore.SkiaSharpView.WPF"
                   Loaded="PluginControl_Loaded"
              d:DesignHeight="450" d:DesignWidth="800">
     <UserControl.Resources>
@@ -130,10 +131,54 @@
         Content="{DynamicResource bd.btn}"/>
             </Grid>
 
-            <wv2:WebView2 Grid.Row="3" Grid.ColumnSpan="4" Name = "webview2"
+            <lvc:CartesianChart Grid.Row="3" Background="#FFF" BorderThickness="1" BorderBrush="{DynamicResource CommonBoarder}"
+                    Title="{Binding Chart0Title}"
+                    TooltipTextPaint="{Binding TooltipTextPaint}"
+                    Padding="0"
+                    Margin="2"
+                    Series="{Binding Chart0_series}"
+                    XAxes="{Binding Chart0_xAxes}"
+                    YAxes="{Binding Chart0_yAxes}"
+                    x:Name="chart0">
+            </lvc:CartesianChart>
+
+            <lvc:PieChart Grid.Row="3" Grid.Column="1" Background="#FFF" BorderThickness="1" BorderBrush="{DynamicResource CommonBoarder}"
+        Title="{Binding Chart1Title}"
+        TooltipTextPaint="{Binding TooltipTextPaint}"
+        Padding="0"
+        Margin="2"
+        Series="{Binding Chart1_series}"
+        LegendPosition="Top"
+        x:Name="chart1">
+            </lvc:PieChart>
+
+            <lvc:CartesianChart Grid.Row="3" Grid.Column="2" Background="#FFF" BorderThickness="1" BorderBrush="{DynamicResource CommonBoarder}"
+        Title="{Binding Chart2Title}"
+        TooltipTextPaint="{Binding TooltipTextPaint}"
+        Padding="0"
+        Margin="2"
+        Series="{Binding Chart2_series}"
+        XAxes="{Binding Chart2_xAxes}"
+        YAxes="{Binding Chart2_yAxes}"
+        LegendPosition="Top"
+        x:Name="chart2">
+            </lvc:CartesianChart>
+
+            <lvc:CartesianChart Grid.Row="3" Grid.Column="3" Background="#FFF" BorderThickness="1" BorderBrush="{DynamicResource CommonBoarder}"
+Title="{Binding Chart3Title}"
+TooltipTextPaint="{Binding TooltipTextPaint}"
+Padding="0"
+Margin="2"
+Series="{Binding Chart3_series}"
+XAxes="{Binding Chart3_xAxes}"
+YAxes="{Binding Chart3_yAxes}"
+x:Name="chart3">
+            </lvc:CartesianChart>
+
+            <!--<wv2:WebView2 Grid.Row="3" Grid.ColumnSpan="4" Name="webview2"
                       SizeChanged="webview2_SizeChanged"
                       NavigationCompleted="Webview2_NavigationCompleted"
-                      Loaded="webview2_Loaded"/>
+                      Loaded="webview2_Loaded"/>-->
         </Grid>
     </Border>
 </hb:PluginControl>
diff --git a/Plugin.DataBoard/DataBoard.xaml.cs b/Plugin.DataBoard/DataBoard.xaml.cs
index ad70928..538e8c7 100644
--- a/Plugin.DataBoard/DataBoard.xaml.cs
+++ b/Plugin.DataBoard/DataBoard.xaml.cs
@@ -5,12 +5,23 @@
 using HMI.Core.Util;
 using HMI.UI.Util;
 using HMI.Util;
+using LiveChartsCore;
+using LiveChartsCore.Drawing;
+using LiveChartsCore.Drawing.Layouts;
+using LiveChartsCore.Kernel.Sketches;
+using LiveChartsCore.SkiaSharpView;
+using LiveChartsCore.SkiaSharpView.Drawing;
+using LiveChartsCore.SkiaSharpView.Drawing.Geometries;
+using LiveChartsCore.SkiaSharpView.Drawing.Layouts;
+using LiveChartsCore.SkiaSharpView.Painting;
+using LiveChartsCore.SkiaSharpView.SKCharts;
+using LiveChartsCore.SkiaSharpView.VisualElements;
 using Microsoft.Web.WebView2.Core;
+using SkiaSharp;
 using System;
 using System.Collections.Generic;
 using System.Collections.ObjectModel;
 using System.Data;
-using System.Drawing;
 using System.IO;
 using System.Linq;
 using System.Threading.Tasks;
@@ -19,6 +30,54 @@
 
 namespace Plugin.DataBoard
 {
+    public class CustomLegend : SKDefaultLegend
+    {
+
+        protected override Layout<SkiaSharpDrawingContext> GetLayout(LiveChartsCore.Chart chart)
+        {
+            var stackLayout = new StackLayout
+            {
+                Orientation = ContainerOrientation.Horizontal,
+                Padding = new Padding(0, 10, 0, 0),
+                HorizontalAlignment = Align.Start,
+                VerticalAlignment = Align.Middle,
+            };
+
+            foreach (var series in chart.Series.Where(x => x.IsVisibleAtLegend))
+            {
+                stackLayout.Children.Add(new LegendItem(series));
+            }
+            return stackLayout;
+        }
+    }
+
+    public class LegendItem : StackLayout
+    {
+        public LegendItem(ISeries series)
+        {
+            Orientation = ContainerOrientation.Horizontal;
+            Padding = new Padding(0);
+            VerticalAlignment = Align.Start;
+            HorizontalAlignment = Align.Middle;
+            Opacity = series.IsVisible ? 1 : 0.5f;
+
+            var miniature = (IDrawnElement<SkiaSharpDrawingContext>)series.GetMiniatureGeometry(null);
+            Children = new List<IDrawnElement<SkiaSharpDrawingContext>> {
+                miniature,
+            new LabelGeometry
+            {
+                Text = series.Name,
+                TextSize = 12,
+                Paint = new SolidColorPaint(SKColors.Black){
+                    SKTypeface = SKFontManager.Default.MatchCharacter('姹�')
+                },
+                Padding = new Padding(5,0,10,0),
+                VerticalAlign = Align.Middle,
+                HorizontalAlign = Align.Start
+            }};
+        }
+    }
+
     /// <summary>
     /// UserControl1.xaml 鐨勪氦浜掗�昏緫
     /// </summary>
@@ -28,8 +87,308 @@
         {
             DataContext = this;
             InitializeComponent();
-            webview2.DefaultBackgroundColor = Color.Transparent;//Color.FromArgb(243, 249, 255);
+            //webview2.DefaultBackgroundColor = Color.Transparent;//Color.FromArgb(243, 249, 255);
         }
+
+        private SolidColorPaint tooltipTextPaint = new SolidColorPaint(SKColors.Black)
+        {
+            SKTypeface = SKFontManager.Default.MatchCharacter('姹�')
+        };
+        public SolidColorPaint TooltipTextPaint
+        {
+            get => tooltipTextPaint;
+            set
+            {
+                Set(ref tooltipTextPaint, value);
+            }
+        }
+        #region chart0
+
+        public LabelVisual Chart0Title { get; set; } = new LabelVisual
+       {
+           Text = "鎶曞叆浜у嚭缁熻",
+           TextSize = 20,
+           Paint = new SolidColorPaint(new SKColor(0, 0, 0))
+           {
+               SKTypeface = SKFontManager.Default.MatchCharacter('姹�')
+           },
+           Padding = new LiveChartsCore.Drawing.Padding(15)
+       };
+
+        private ISeries[] chart0_series = new ISeries[] {
+            new ColumnSeries<int> { 
+                Fill= new SolidColorPaint(new SKColor(34, 68, 139)),
+                DataLabelsPaint  = new SolidColorPaint(new SKColor(0, 0, 0)),
+                DataLabelsPosition = LiveChartsCore.Measure.DataLabelsPosition.Top,
+                Values = new int[]{ 0,0} }
+        };
+
+        public ISeries[] Chart0_series
+        {
+            get => chart0_series;
+            set
+            {
+                Set(ref chart0_series, value);
+            }
+        }
+
+        private ObservableCollection<Axis> chart0_yAxes = new ObservableCollection<Axis> {
+            new Axis
+            {
+                MinStep = 1,
+                MinLimit = 0,
+                LabelsPaint = new SolidColorPaint(new SKColor(0, 0, 0)),
+                SeparatorsPaint = new SolidColorPaint
+                {
+                    Color = SKColors.Black.WithAlpha(30),
+                    StrokeThickness = 0.5f
+                },
+                TicksPaint = new SolidColorPaint
+                {
+                    Color = SKColor.Empty,
+                    StrokeThickness = 0
+                },
+            }
+        };
+
+        public ObservableCollection<Axis> Chart0_yAxes
+        {
+            get => chart0_yAxes;
+            set
+            {
+                Set(ref chart0_yAxes, value);
+            }
+        }
+
+        private Axis[] chart0_xAxes = new Axis[] {
+            new Axis
+        {
+            Labels = new string[]{"鎶曞叆", "浜у嚭" },
+            LabelsPaint=new SolidColorPaint(new SKColor(0, 0, 0))
+            {
+                SKTypeface = SKFontManager.Default.MatchCharacter('姹�')
+            },
+            LabelsRotation = 0,
+            SeparatorsAtCenter = false,
+            TicksPaint = new SolidColorPaint(new SKColor(35, 35, 35)),
+            TicksAtCenter = true,
+            ForceStepToMin = true,
+            MinStep = 1
+        }
+        };
+        public Axis[] Chart0_xAxes
+        {
+            get => chart0_xAxes;
+            set
+            {
+                Set(ref chart0_xAxes, value);
+            }
+        }
+        #endregion
+
+        #region chart1
+
+        public LabelVisual Chart1Title { get; set; } = new LabelVisual
+        {
+            Text = "浜у搧鐘舵�佺粺璁�",
+            TextSize = 20,
+            Paint = new SolidColorPaint(new SKColor(0, 0, 0))
+            {
+                SKTypeface = SKFontManager.Default.MatchCharacter('姹�')
+            },
+            Padding = new LiveChartsCore.Drawing.Padding(15)
+        };
+
+        public ISeries[] Chart1_series { get; set; } = new PieSeries<int>[]
+        {
+            new PieSeries<int> {
+                Name="Passed",
+                Values = new int[]{ 0},
+            DataLabelsPosition = LiveChartsCore.Measure.PolarLabelsPosition.Outer,
+             DataLabelsSize = 15,
+             DataLabelsPaint = new SolidColorPaint(new SKColor(30, 30, 30)){
+                 SKTypeface = SKFontManager.Default.MatchCharacter('姹�')
+             },
+             Fill = new SolidColorPaint(new SKColor(0, 128, 0)),
+             DataLabelsFormatter =
+                point =>point.StackedValue.Total>0?
+                    $"Passed\r\n璁℃暟:{point.Coordinate.PrimaryValue}\r\n鍗犳瘮:{point.StackedValue.Share:P2}":"",
+             ToolTipLabelFormatter = point => $"{point.StackedValue.Share:P2}"
+            },
+            new PieSeries<int> { Values = new int[]{ 0} ,
+                Name="Failed",
+            DataLabelsPosition = LiveChartsCore.Measure.PolarLabelsPosition.Outer,
+             DataLabelsSize = 15,
+             DataLabelsPaint = new SolidColorPaint(new SKColor(30, 30, 30)){
+                 SKTypeface = SKFontManager.Default.MatchCharacter('姹�')
+             },
+             Fill = new SolidColorPaint(new SKColor(255, 0, 0)),
+             DataLabelsFormatter =
+                point =>point.StackedValue.Total>0?
+                    $"Failed\r\n璁℃暟:{point.Coordinate.PrimaryValue}\r\n鍗犳瘮:{point.StackedValue.Share:P2}":"",
+             ToolTipLabelFormatter = point => $"{point.StackedValue.Share:P2}"},
+        };
+
+        #endregion
+
+        #region chart2
+
+        public LabelVisual Chart2Title { get; set; } = new LabelVisual
+        {
+            Text = "鑹巼缁熻",
+            TextSize = 20,
+            Paint = new SolidColorPaint(new SKColor(0, 0, 0))
+            {
+                SKTypeface = SKFontManager.Default.MatchCharacter('姹�')
+            },
+            Padding = new LiveChartsCore.Drawing.Padding(15)
+        };
+
+        public ISeries[] Chart2_series { get; set; } = new ISeries[]
+        {
+            new LineSeries<double> {
+                Name="鐩撮�氱巼",
+                Values = new double[]{ 0},
+                DataLabelsPosition = LiveChartsCore.Measure.DataLabelsPosition.Top,
+                DataLabelsFormatter =
+                    point => $"鐩撮�氱巼:{point.Coordinate.PrimaryValue:f2}%",
+                DataLabelsSize = 15,
+                DataLabelsPaint = new SolidColorPaint(new SKColor(30, 30, 30)){
+                    SKTypeface = SKFontManager.Default.MatchCharacter('姹�')
+                },
+                Stroke = new SolidColorPaint(new SKColor(255,0,0)){
+                    StrokeThickness = 2
+                },
+                Fill = null,
+                GeometryStroke = new SolidColorPaint(new SKColor(255,0,0)){
+                    StrokeThickness = 2
+                },
+                GeometrySize=5
+            },
+            new LineSeries<double> {
+                Name="鑹巼",
+                Values = new double[]{ 0},
+            DataLabelsPosition = LiveChartsCore.Measure.DataLabelsPosition.Bottom,
+            DataLabelsFormatter =
+                point => $"鑹巼:{point.Coordinate.PrimaryValue:f2}%",
+             DataLabelsSize = 10,
+             DataLabelsPaint = new SolidColorPaint(new SKColor(30, 30, 30)){
+                 SKTypeface = SKFontManager.Default.MatchCharacter('姹�')
+             },
+                Stroke = new SolidColorPaint(new SKColor(34, 68, 139)){
+                    StrokeThickness = 2
+                },
+                GeometryStroke = new SolidColorPaint(new SKColor(34, 68, 139)){
+                    StrokeThickness = 2
+                },
+             Fill = null,
+            },
+        };
+
+        private ObservableCollection<Axis> chart2_yAxes = new ObservableCollection<Axis>  {
+            new Axis
+            {
+                LabelsPaint = new SolidColorPaint(new SKColor(0, 0, 0)),
+                SeparatorsPaint = new SolidColorPaint
+                {
+                    Color = SKColors.Black.WithAlpha(30),
+                    StrokeThickness = 0.5f
+                },
+                TicksPaint = new SolidColorPaint
+                {
+                    Color = SKColor.Empty,
+                    StrokeThickness = 0
+                },
+                MinLimit = 0
+            }
+        };
+        public ObservableCollection<Axis> Chart2_yAxes
+        {
+            get => chart2_yAxes;
+            set
+            {
+                Set(ref chart2_yAxes, value);
+            }
+        }
+
+        private ObservableCollection<ICartesianAxis> chart2_xAxes = new ObservableCollection<ICartesianAxis> {
+            new Axis
+            {
+            Labels = new string[]{"" },
+            }
+        };
+
+        public ObservableCollection<ICartesianAxis> Chart2_xAxes {
+            get => chart2_xAxes;
+            set {
+                Set(ref chart2_xAxes, value);
+            }
+        }
+
+        #endregion
+
+        #region chart3
+
+        public LabelVisual Chart3Title { get; set; } = new LabelVisual
+        {
+            Text = "TOP10涓嶈壇缁熻",
+            TextSize = 20,
+            Paint = new SolidColorPaint(new SKColor(0, 0, 0))
+            {
+                SKTypeface = SKFontManager.Default.MatchCharacter('姹�')
+            },
+            Padding = new LiveChartsCore.Drawing.Padding(15)
+        };
+
+        public ISeries[] Chart3_series { get; set; } = new ISeries[]
+        {
+            new RowSeries<SimpleData> {
+                Values = new SimpleData[]{},
+                DataLabelsPosition = LiveChartsCore.Measure.DataLabelsPosition.Left,
+                DataLabelsFormatter =
+                    point => $"{point.Model.Name}",
+
+                DataLabelsSize = 15,
+                DataLabelsPadding = new Padding(30,0,0,0),
+                DataLabelsTranslate = new LvcPoint(1, 0),
+                // DataLabelsTranslate = new LvcPoint(-1, 0),
+                DataLabelsPaint = new SolidColorPaint(new SKColor(255,0,0)){
+                    SKTypeface = SKFontManager.Default.MatchCharacter('姹�')
+                },
+                YToolTipLabelFormatter = point => $"{point.Model.Name} {point.Model.Value}"
+            },
+        };
+
+        public ObservableCollection<Axis> chart3_yAxes = new ObservableCollection<Axis>  {
+            new Axis
+            {
+                IsVisible = false
+            }
+        };
+        public ObservableCollection<Axis> Chart3_yAxes
+        {
+            get => chart3_yAxes;
+            set
+            {
+                Set(ref chart3_yAxes, value);
+            }
+        }
+
+        private ObservableCollection<ICartesianAxis> chart3_xAxes = new ObservableCollection<ICartesianAxis> {
+            new Axis { SeparatorsPaint = new SolidColorPaint(new SKColor(220, 220, 220)) }
+        };
+
+        public ObservableCollection<ICartesianAxis> Chart3_xAxes
+        {
+            get => chart3_xAxes;
+            set
+            {
+                Set(ref chart3_xAxes, value);
+            }
+        }
+
+        #endregion
+
 
         private ObservableCollection<OptionData> _options = new ObservableCollection<OptionData> {
             new OptionData { Name = LangUtil.Get("bd.today"), Value = "today" },
@@ -49,6 +408,7 @@
         private string DataFolder;
         public override void InitFinish()
         {
+            chart2.Legend = new CustomLegend();
             iniFile = Path.Combine(BaseFolder, "Config.ini");
             if (!File.Exists(iniFile))
             {
@@ -60,7 +420,10 @@
 
         private void PluginControl_Loaded(object sender, RoutedEventArgs e)
         {
-
+            cb0.SelectedIndex = 0;
+            cb1.SelectedIndex = 0;
+            cb2.SelectedIndex = 0;
+            cb3.SelectedIndex = 0;
         }
 
         private void LoadProduct()
@@ -223,7 +586,7 @@
                 segment = $"AND model = '{model}'";
             }
 
-            string sql = $@"SELECT * FROM testResult WHERE time BETWEEN '{start:yyyy-MM-dd HH:mm:ss}' AND '{end:yyyy-MM-dd HH:mm:ss}' {segment};";
+            string sql = $@"SELECT * FROM testResult WHERE time BETWEEN '{start:yyyy-MM-dd HH:mm:ss}' AND '{end:yyyy-MM-dd HH:mm:ss}' {segment} Order BY id DESC;";
 
             DataTable dt = new DataTable();
             foreach (string file in files)
@@ -240,22 +603,23 @@
         private void webview2_Loaded(object sender, RoutedEventArgs e)
         {
             string path = Path.Combine(PathHelper.AppFolder, "Libs", "Static", "test.html");
-            webview2.Source = new Uri(path);
+            //webview2.EnsureCoreWebView2Async();
+            // webview2.Source = new Uri(path);           
         }
 
         private void Webview2_NavigationCompleted(object sender, CoreWebView2NavigationCompletedEventArgs e)
         {
-            cb0.SelectedIndex = 0;
-            cb1.SelectedIndex = 0;
-            cb2.SelectedIndex = 0;
-            cb3.SelectedIndex = 0;
+            //cb0.SelectedIndex = 0;
+            //cb1.SelectedIndex = 0;
+            //cb2.SelectedIndex = 0;
+            //cb3.SelectedIndex = 0;
         }
 
         private void webview2_SizeChanged(object sender, SizeChangedEventArgs e)
         {
-            var width = this.webview2.ActualWidth - 100;
-            var height = this.webview2.ActualHeight;
-            webview2.CoreWebView2?.ExecuteScriptAsync($"resize({width},{height})");
+            //var width = this.webview2.ActualWidth - 100;
+            //var height = this.webview2.ActualHeight;
+            //webview2.CoreWebView2?.ExecuteScriptAsync($"resize({width},{height})");
         }
 
         //private void RefreshChart0(DateTime start, DateTime end)
@@ -305,7 +669,7 @@
                 {
                     Dispatcher.BeginInvoke(new Action(() =>
                     {
-                        webview2.CoreWebView2?.ExecuteScriptAsync($"loading0('鍔犺浇涓紝璇风◢鍚�...')");
+                        // webview2.CoreWebView2?.ExecuteScriptAsync($"loading0('鍔犺浇涓紝璇风◢鍚�...')");
                     }));
                     List<SerialData> datas = SelectResultDataByTime(model, start, end);
                     datas.OrderByDescending(e => e.time);
@@ -321,7 +685,8 @@
                     var fields = $"['{this.lang("bd.input")}','{this.lang("bd.output")}']";
                     Dispatcher.BeginInvoke(new Action(() =>
                     {
-                        webview2.CoreWebView2?.ExecuteScriptAsync($"drawChart0({fields}, [{all},{output}])");
+                        Chart0_series[0].Values = new int[] { all, output };
+                        //webview2.CoreWebView2?.ExecuteScriptAsync($"drawChart0({fields}, [{all},{output}])");
                     }));
                 }
                 catch (Exception ex)
@@ -343,12 +708,12 @@
                 {
                     Dispatcher.BeginInvoke(new Action(() =>
                     {
-                        webview2.CoreWebView2?.ExecuteScriptAsync($"loading1('鍔犺浇涓紝璇风◢鍚�...')");
+                        //webview2.CoreWebView2?.ExecuteScriptAsync($"loading1('鍔犺浇涓紝璇风◢鍚�...')");
                     }));
                     List<SerialData> datas = SelectResultDataByTime(model, start, end);
 
                     datas = datas.OrderByDescending(e => e.time).ToList();
-                    Dictionary<string, int?> pieData = new Dictionary<string, int?> {
+                    Dictionary<string, int> pieData = new Dictionary<string, int> {
                 { "Passed",0},
                 { "Failed",0},
                 //{ "Action",0},
@@ -380,7 +745,9 @@
                     res += "]";
                     Dispatcher.BeginInvoke(new Action(() =>
                     {
-                        webview2.CoreWebView2?.ExecuteScriptAsync($"drawChart1({res})");
+                        Chart1_series[0].Values = new int[] { pieData["Passed"] };
+                        Chart1_series[1].Values = new int[] { pieData["Failed"] };
+                        // webview2.CoreWebView2?.ExecuteScriptAsync($"drawChart1({res})");
                     }));
                 }
                 catch (Exception ex)
@@ -468,7 +835,7 @@
                 {
                     Dispatcher.BeginInvoke(new Action(() =>
                     {
-                        webview2.CoreWebView2?.ExecuteScriptAsync($"loading2('鍔犺浇涓紝璇风◢鍚�...')");
+                       // webview2.CoreWebView2?.ExecuteScriptAsync($"loading2('鍔犺浇涓紝璇风◢鍚�...')");
                     }));
                     List<SerialData> datas = SelectResultDataByTime(model, start, end);
 
@@ -513,20 +880,46 @@
                         });
                     }
 
-                    string fields = "[", vals0 = "[", vals1 = "[";
-                    foreach (var key in res.Keys)
-                    {
-                        fields += $"'{key}',";
-                        vals0 += $"{res[key].onePassRate},";
-                        vals1 += $"{res[key].passRate},";
-                    }
-                    fields += "]";
-                    vals0 += "]";
-                    vals1 += "]";
+                    //string fields = "[", vals0 = "[", vals1 = "[";
+                    //foreach (var key in res.Keys)
+                    //{
+                    //    fields += $"'{key}',";
+                    //    vals0 += $"{res[key].onePassRate},";
+                    //    vals1 += $"{res[key].passRate},";
+                    //}
+                    //fields += "]";
+                    //vals0 += "]";
+                    //vals1 += "]";
                     var names = $"['{this.lang("bd.onePassRate")}','{this.lang("bd.passRate")}']";
                     Dispatcher.BeginInvoke(new Action(() =>
                     {
-                        webview2.CoreWebView2?.ExecuteScriptAsync($"drawChart2({fields},{names},{vals0},{vals1})");
+                        if (res.Count == 0)
+                        {
+                            res[DateTime.Now.ToString("yyyy-MM-dd")] = new PassRateData
+                            {
+                                onePassRate = 0,
+                                passRate = 0
+                            };
+                        }
+
+                        string[] keys = new string[res.Count];
+                        double[] val0 = new double[res.Count];
+                        double[] val1 = new double[res.Count];
+                        int i = 0;
+                        foreach (var key in res.Keys)
+                        {
+                            keys[i] = key;
+                            val0[i] = res[key].onePassRate;
+                            val1[i] = res[key].passRate;
+                            i++;
+                        }
+                        Chart2_xAxes[0] = new Axis
+                        {
+                            Labels = keys,
+                        };
+                        Chart2_series[0].Values = val0;
+                        Chart2_series[1].Values = val1;
+                        //webview2.CoreWebView2?.ExecuteScriptAsync($"drawChart2({fields},{names},{vals0},{vals1})");
                     }));
                 }
                 catch (Exception ex)
@@ -548,7 +941,7 @@
                 {
                     Dispatcher.BeginInvoke(new Action(() =>
                     {
-                        webview2.CoreWebView2?.ExecuteScriptAsync($"loading3('鍔犺浇涓紝璇风◢鍚�...')");
+                        //webview2.CoreWebView2?.ExecuteScriptAsync($"loading3('鍔犺浇涓紝璇风◢鍚�...')");
                     }));
                     var st = DateTime.Now;
                     List<SerialData> datas = SelectSerialDataByTime(model, start, end);
@@ -585,15 +978,16 @@
                     var s3 = DateTime.Now;
                     log.Info($"RefreshChart3 Build {(s3 - s2).TotalMilliseconds}");
 
-                    string names = string.Empty, vals = string.Empty;
-                    foreach (var data in datasource)
-                    {
-                        names += $"'{data.Name}',";
-                        vals += $"{data.Value},";
-                    }
+                    //string names = string.Empty, vals = string.Empty;
+                    //foreach (var data in datasource)
+                    //{
+                    //    names += $"'{data.Name}',";
+                    //    vals += $"{data.Value},";
+                    //}
                     Dispatcher.BeginInvoke(new Action(() =>
                     {
-                        webview2.CoreWebView2?.ExecuteScriptAsync($"drawChart3([{names}],[{vals}])");
+                        Chart3_series[0].Values = datasource.OrderBy(x => x.Value).ToList(); ;
+                        //webview2.CoreWebView2?.ExecuteScriptAsync($"drawChart3([{names}],[{vals}])");
                     }));
                 }
                 catch (Exception ex)
diff --git a/Plugin.DataBoard/Plugin.DataBoard.csproj b/Plugin.DataBoard/Plugin.DataBoard.csproj
index 37ffdf2..b931527 100644
--- a/Plugin.DataBoard/Plugin.DataBoard.csproj
+++ b/Plugin.DataBoard/Plugin.DataBoard.csproj
@@ -52,8 +52,23 @@
     <ErrorReport>prompt</ErrorReport>
   </PropertyGroup>
   <ItemGroup>
+    <Reference Include="HarfBuzzSharp, Version=1.0.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL">
+      <HintPath>..\WPFProject\packages\HarfBuzzSharp.7.3.0.3\lib\net462\HarfBuzzSharp.dll</HintPath>
+    </Reference>
+    <Reference Include="LiveChartsCore, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc92a3e13d40c573, processorArchitecture=MSIL">
+      <HintPath>..\WPFProject\packages\LiveChartsCore.2.0.0-rc5.4\lib\net462\LiveChartsCore.dll</HintPath>
+    </Reference>
+    <Reference Include="LiveChartsCore.SkiaSharpView, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc92a3e13d40c573, processorArchitecture=MSIL">
+      <HintPath>..\WPFProject\packages\LiveChartsCore.SkiaSharpView.2.0.0-rc5.4\lib\net462\LiveChartsCore.SkiaSharpView.dll</HintPath>
+    </Reference>
+    <Reference Include="LiveChartsCore.SkiaSharpView.WPF, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc92a3e13d40c573, processorArchitecture=MSIL">
+      <HintPath>..\WPFProject\packages\LiveChartsCore.SkiaSharpView.WPF.2.0.0-rc5.4\lib\net462\LiveChartsCore.SkiaSharpView.WPF.dll</HintPath>
+    </Reference>
     <Reference Include="log4net, Version=3.0.4.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a, processorArchitecture=MSIL">
       <HintPath>..\WPFProject\packages\log4net.3.0.4\lib\net462\log4net.dll</HintPath>
+    </Reference>
+    <Reference Include="Microsoft.Bcl.AsyncInterfaces, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\WPFProject\packages\Microsoft.Bcl.AsyncInterfaces.8.0.0\lib\net462\Microsoft.Bcl.AsyncInterfaces.dll</HintPath>
     </Reference>
     <Reference Include="Microsoft.Web.WebView2.Core, Version=1.0.3240.44, Culture=neutral, PublicKeyToken=2a8ab48044d2601e, processorArchitecture=MSIL">
       <HintPath>..\WPFProject\packages\Microsoft.Web.WebView2.1.0.3240.44\lib\net462\Microsoft.Web.WebView2.Core.dll</HintPath>
@@ -70,10 +85,50 @@
     <Reference Include="Panuon.WPF.UI, Version=1.3.0.2, Culture=neutral, processorArchitecture=MSIL">
       <HintPath>..\WPFProject\packages\Panuon.WPF.UI.1.3.0.2\lib\net472\Panuon.WPF.UI.dll</HintPath>
     </Reference>
+    <Reference Include="SkiaSharp, Version=2.88.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL">
+      <HintPath>..\WPFProject\packages\SkiaSharp.2.88.9\lib\net462\SkiaSharp.dll</HintPath>
+    </Reference>
+    <Reference Include="SkiaSharp.HarfBuzz, Version=2.88.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL">
+      <HintPath>..\WPFProject\packages\SkiaSharp.HarfBuzz.2.88.9\lib\net462\SkiaSharp.HarfBuzz.dll</HintPath>
+    </Reference>
+    <Reference Include="SkiaSharp.Views.Desktop.Common, Version=2.88.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL">
+      <HintPath>..\WPFProject\packages\SkiaSharp.Views.Desktop.Common.2.88.9\lib\net462\SkiaSharp.Views.Desktop.Common.dll</HintPath>
+    </Reference>
+    <Reference Include="SkiaSharp.Views.WPF, Version=2.88.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL">
+      <HintPath>..\WPFProject\packages\SkiaSharp.Views.WPF.2.88.9\lib\net462\SkiaSharp.Views.WPF.dll</HintPath>
+    </Reference>
     <Reference Include="System" />
+    <Reference Include="System.Buffers, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\WPFProject\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll</HintPath>
+    </Reference>
     <Reference Include="System.Configuration" />
     <Reference Include="System.Data" />
     <Reference Include="System.Drawing" />
+    <Reference Include="System.Drawing.Common, Version=4.0.0.2, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\WPFProject\packages\System.Drawing.Common.4.7.3\lib\net461\System.Drawing.Common.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Memory, Version=4.0.1.2, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\WPFProject\packages\System.Memory.4.5.5\lib\net461\System.Memory.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Numerics" />
+    <Reference Include="System.Numerics.Vectors, Version=4.1.4.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+      <HintPath>..\WPFProject\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Runtime.CompilerServices.Unsafe, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+      <HintPath>..\WPFProject\packages\System.Runtime.CompilerServices.Unsafe.6.0.0\lib\net461\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Text.Encodings.Web, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\WPFProject\packages\System.Text.Encodings.Web.8.0.0\lib\net462\System.Text.Encodings.Web.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Text.Json, Version=8.0.0.5, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\WPFProject\packages\System.Text.Json.8.0.5\lib\net462\System.Text.Json.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Threading.Tasks.Extensions, Version=4.2.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\WPFProject\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll</HintPath>
+    </Reference>
+    <Reference Include="System.ValueTuple, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\WPFProject\packages\System.ValueTuple.4.5.0\lib\net47\System.ValueTuple.dll</HintPath>
+    </Reference>
     <Reference Include="System.Web" />
     <Reference Include="System.Xml" />
     <Reference Include="Microsoft.CSharp" />
@@ -120,6 +175,7 @@
       <Generator>ResXFileCodeGenerator</Generator>
       <LastGenOutput>Resources.Designer.cs</LastGenOutput>
     </EmbeddedResource>
+    <None Include="app.config" />
     <None Include="packages.config" />
     <None Include="Properties\Settings.settings">
       <Generator>SettingsSingleFileGenerator</Generator>
@@ -162,5 +218,13 @@
       <ErrorText>杩欏彴璁$畻鏈轰笂缂哄皯姝ら」鐩紩鐢ㄧ殑 NuGet 绋嬪簭鍖呫�備娇鐢ㄢ�淣uGet 绋嬪簭鍖呰繕鍘熲�濆彲涓嬭浇杩欎簺绋嬪簭鍖呫�傛湁鍏虫洿澶氫俊鎭紝璇峰弬瑙� http://go.microsoft.com/fwlink/?LinkID=322105銆傜己灏戠殑鏂囦欢鏄� {0}銆�</ErrorText>
     </PropertyGroup>
     <Error Condition="!Exists('..\WPFProject\packages\Microsoft.Web.WebView2.1.0.3240.44\build\Microsoft.Web.WebView2.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\WPFProject\packages\Microsoft.Web.WebView2.1.0.3240.44\build\Microsoft.Web.WebView2.targets'))" />
+    <Error Condition="!Exists('..\WPFProject\packages\HarfBuzzSharp.NativeAssets.macOS.7.3.0.3\build\net462\HarfBuzzSharp.NativeAssets.macOS.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\WPFProject\packages\HarfBuzzSharp.NativeAssets.macOS.7.3.0.3\build\net462\HarfBuzzSharp.NativeAssets.macOS.targets'))" />
+    <Error Condition="!Exists('..\WPFProject\packages\HarfBuzzSharp.NativeAssets.Win32.7.3.0.3\build\net462\HarfBuzzSharp.NativeAssets.Win32.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\WPFProject\packages\HarfBuzzSharp.NativeAssets.Win32.7.3.0.3\build\net462\HarfBuzzSharp.NativeAssets.Win32.targets'))" />
+    <Error Condition="!Exists('..\WPFProject\packages\SkiaSharp.NativeAssets.macOS.2.88.9\build\net462\SkiaSharp.NativeAssets.macOS.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\WPFProject\packages\SkiaSharp.NativeAssets.macOS.2.88.9\build\net462\SkiaSharp.NativeAssets.macOS.targets'))" />
+    <Error Condition="!Exists('..\WPFProject\packages\SkiaSharp.NativeAssets.Win32.2.88.9\build\net462\SkiaSharp.NativeAssets.Win32.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\WPFProject\packages\SkiaSharp.NativeAssets.Win32.2.88.9\build\net462\SkiaSharp.NativeAssets.Win32.targets'))" />
   </Target>
+  <Import Project="..\WPFProject\packages\HarfBuzzSharp.NativeAssets.macOS.7.3.0.3\build\net462\HarfBuzzSharp.NativeAssets.macOS.targets" Condition="Exists('..\WPFProject\packages\HarfBuzzSharp.NativeAssets.macOS.7.3.0.3\build\net462\HarfBuzzSharp.NativeAssets.macOS.targets')" />
+  <Import Project="..\WPFProject\packages\HarfBuzzSharp.NativeAssets.Win32.7.3.0.3\build\net462\HarfBuzzSharp.NativeAssets.Win32.targets" Condition="Exists('..\WPFProject\packages\HarfBuzzSharp.NativeAssets.Win32.7.3.0.3\build\net462\HarfBuzzSharp.NativeAssets.Win32.targets')" />
+  <Import Project="..\WPFProject\packages\SkiaSharp.NativeAssets.macOS.2.88.9\build\net462\SkiaSharp.NativeAssets.macOS.targets" Condition="Exists('..\WPFProject\packages\SkiaSharp.NativeAssets.macOS.2.88.9\build\net462\SkiaSharp.NativeAssets.macOS.targets')" />
+  <Import Project="..\WPFProject\packages\SkiaSharp.NativeAssets.Win32.2.88.9\build\net462\SkiaSharp.NativeAssets.Win32.targets" Condition="Exists('..\WPFProject\packages\SkiaSharp.NativeAssets.Win32.2.88.9\build\net462\SkiaSharp.NativeAssets.Win32.targets')" />
 </Project>
\ No newline at end of file
diff --git a/Plugin.DataBoard/SimpleData.cs b/Plugin.DataBoard/SimpleData.cs
index 0f33917..c51e555 100644
--- a/Plugin.DataBoard/SimpleData.cs
+++ b/Plugin.DataBoard/SimpleData.cs
@@ -1,8 +1,9 @@
-锘縩amespace Plugin.DataBoard
+锘縰sing LiveChartsCore.Defaults;
+
+namespace Plugin.DataBoard
 {
-    public class SimpleData
+    public class SimpleData: ObservableValue
     {
         public string Name { get; set; }
-        public int Value { get; set; }
     }
 }
diff --git a/Plugin.DataBoard/app.config b/Plugin.DataBoard/app.config
new file mode 100644
index 0000000..1696df6
--- /dev/null
+++ b/Plugin.DataBoard/app.config
@@ -0,0 +1,11 @@
+锘�<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+  <runtime>
+    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
+      <dependentAssembly>
+        <assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
+      </dependentAssembly>
+    </assemblyBinding>
+  </runtime>
+</configuration>
\ No newline at end of file
diff --git a/Plugin.DataBoard/packages.config b/Plugin.DataBoard/packages.config
index f28691b..13092b2 100644
--- a/Plugin.DataBoard/packages.config
+++ b/Plugin.DataBoard/packages.config
@@ -1,7 +1,29 @@
 锘�<?xml version="1.0" encoding="utf-8"?>
 <packages>
+  <package id="HarfBuzzSharp" version="7.3.0.3" targetFramework="net472" />
+  <package id="HarfBuzzSharp.NativeAssets.macOS" version="7.3.0.3" targetFramework="net472" />
+  <package id="HarfBuzzSharp.NativeAssets.Win32" version="7.3.0.3" targetFramework="net472" />
+  <package id="LiveChartsCore" version="2.0.0-rc5.4" targetFramework="net472" />
+  <package id="LiveChartsCore.SkiaSharpView" version="2.0.0-rc5.4" targetFramework="net472" />
+  <package id="LiveChartsCore.SkiaSharpView.WPF" version="2.0.0-rc5.4" targetFramework="net472" />
   <package id="log4net" version="3.0.4" targetFramework="net472" />
+  <package id="Microsoft.Bcl.AsyncInterfaces" version="8.0.0" targetFramework="net472" />
   <package id="Microsoft.Web.WebView2" version="1.0.3240.44" targetFramework="net472" />
   <package id="Panuon.WPF" version="1.1.3" targetFramework="net472" />
   <package id="Panuon.WPF.UI" version="1.3.0.2" targetFramework="net472" />
+  <package id="SkiaSharp" version="2.88.9" targetFramework="net472" />
+  <package id="SkiaSharp.HarfBuzz" version="2.88.9" targetFramework="net472" />
+  <package id="SkiaSharp.NativeAssets.macOS" version="2.88.9" targetFramework="net472" />
+  <package id="SkiaSharp.NativeAssets.Win32" version="2.88.9" targetFramework="net472" />
+  <package id="SkiaSharp.Views.Desktop.Common" version="2.88.9" targetFramework="net472" />
+  <package id="SkiaSharp.Views.WPF" version="2.88.9" targetFramework="net472" />
+  <package id="System.Buffers" version="4.5.1" targetFramework="net472" />
+  <package id="System.Drawing.Common" version="4.7.3" targetFramework="net472" />
+  <package id="System.Memory" version="4.5.5" targetFramework="net472" />
+  <package id="System.Numerics.Vectors" version="4.5.0" targetFramework="net472" />
+  <package id="System.Runtime.CompilerServices.Unsafe" version="6.0.0" targetFramework="net472" />
+  <package id="System.Text.Encodings.Web" version="8.0.0" targetFramework="net472" />
+  <package id="System.Text.Json" version="8.0.5" targetFramework="net472" />
+  <package id="System.Threading.Tasks.Extensions" version="4.5.4" targetFramework="net472" />
+  <package id="System.ValueTuple" version="4.5.0" targetFramework="net472" />
 </packages>
\ No newline at end of file
diff --git a/Plugin.QualityReport/AssemblyInfo.cs b/Plugin.QualityReport/AssemblyInfo.cs
new file mode 100644
index 0000000..f2c9b42
--- /dev/null
+++ b/Plugin.QualityReport/AssemblyInfo.cs
@@ -0,0 +1,13 @@
+using HMI.Core.Annotation;
+using System.Windows;
+
+[assembly:ThemeInfo(
+    ResourceDictionaryLocation.None,            //where theme specific resource dictionaries are located
+                                                //(used if a resource is not found in the page,
+                                                // or application resource dictionaries)
+    ResourceDictionaryLocation.SourceAssembly   //where the generic resource dictionary is located
+                                                //(used if a resource is not found in the page,
+                                                // app, or any theme specific resource dictionaries)
+)]
+
+[assembly: PluginClassName("Plugin.QualityReport.QualityReport")]
\ No newline at end of file
diff --git "a/Plugin.QualityReport/Libs/Static/CMK\346\250\241\346\235\277.xls" "b/Plugin.QualityReport/Libs/Static/CMK\346\250\241\346\235\277.xls"
new file mode 100644
index 0000000..a6e8cf8
--- /dev/null
+++ "b/Plugin.QualityReport/Libs/Static/CMK\346\250\241\346\235\277.xls"
Binary files differ
diff --git "a/Plugin.QualityReport/Libs/Static/MSA\346\250\241\346\235\277.xlsx" "b/Plugin.QualityReport/Libs/Static/MSA\346\250\241\346\235\277.xlsx"
new file mode 100644
index 0000000..c8fab10
--- /dev/null
+++ "b/Plugin.QualityReport/Libs/Static/MSA\346\250\241\346\235\277.xlsx"
Binary files differ
diff --git "a/Plugin.QualityReport/Libs/Static/\351\207\215\345\244\215\346\200\247\346\250\241\346\235\277.xls" "b/Plugin.QualityReport/Libs/Static/\351\207\215\345\244\215\346\200\247\346\250\241\346\235\277.xls"
new file mode 100644
index 0000000..6d78229
--- /dev/null
+++ "b/Plugin.QualityReport/Libs/Static/\351\207\215\345\244\215\346\200\247\346\250\241\346\235\277.xls"
Binary files differ
diff --git a/Plugin.QualityReport/Plugin.QualityReport.csproj b/Plugin.QualityReport/Plugin.QualityReport.csproj
new file mode 100644
index 0000000..55e04c1
--- /dev/null
+++ b/Plugin.QualityReport/Plugin.QualityReport.csproj
@@ -0,0 +1,46 @@
+锘�<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Library</OutputType>
+    <TargetFramework>net472</TargetFramework>
+    <Nullable>enable</Nullable>
+    <LangVersion>10.0</LangVersion>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <UseWPF>true</UseWPF>
+    <Platforms>AnyCPU;x64</Platforms>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
+    <PackageReference Include="log4net" Version="3.0.4" />
+    <PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.135" />
+    <PackageReference Include="NPOI" Version="2.4.1" />
+    <PackageReference Include="Panuon.WPF" Version="1.1.3" />
+    <PackageReference Include="Panuon.WPF.UI" Version="1.3.0.2" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\HMI.Base\HMI.Base.csproj" />
+    <ProjectReference Include="..\HMI.Core\HMI.Core.csproj" />
+    <ProjectReference Include="..\HMI.UI\HMI.UI.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Reference Include="NPOI">
+      <HintPath>..\HMI.ParameterManage\bin\x64\Debug\NPOI.dll</HintPath>
+    </Reference>
+  </ItemGroup>
+
+  <ItemGroup>
+    <None Update="Libs\Static\CMK妯℃澘.xls">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </None>
+    <None Update="Libs\Static\MSA妯℃澘.xlsx">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </None>
+    <None Update="Libs\Static\閲嶅鎬фā鏉�.xls">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </None>
+  </ItemGroup>
+
+</Project>
diff --git a/Plugin.QualityReport/QualityReport.xaml b/Plugin.QualityReport/QualityReport.xaml
new file mode 100644
index 0000000..484ff2a
--- /dev/null
+++ b/Plugin.QualityReport/QualityReport.xaml
@@ -0,0 +1,249 @@
+锘�<hb:SimpleMaskControl x:Class="Plugin.QualityReport.QualityReport"
+             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:Plugin.QualityReport"
+             mc:Ignorable="d" 
+             xmlns:hb="clr-namespace:HMI.Base;assembly=HMI.Base"
+             xmlns:pu="https://opensource.panuon.com/wpf-ui"
+             xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI"
+                      x:Name="control"
+             d:DesignHeight="450" d:DesignWidth="800">
+    <UserControl.Resources>
+
+       
+
+        <Style x:Key="ReportDataGridRow" TargetType="DataGridRow">
+            <Setter Property="Background" Value="White"/>
+            <Style.Triggers>
+                <Trigger Property="AlternationIndex" Value="1">
+                    <Setter Property="Background" Value="#eeeeee"/>
+                </Trigger>
+                <Trigger Property="IsMouseOver" Value="True">
+                    <Setter Property="Background" Value="{DynamicResource SecondPrimaryColor}"/>
+                    <Setter Property="Foreground" Value="{DynamicResource SecondPrimaryColorContract}"/>
+                </Trigger>
+            </Style.Triggers>
+        </Style>
+
+        <Style x:Key="ReportDataGrid" TargetType="DataGrid">
+            <Setter Property="FontSize" Value="16"/>
+            <Setter Property="AlternationCount" Value="2"/>
+            <Setter Property="AutoGenerateColumns" Value="False"/>
+            <Setter Property="RowHeight" Value="30"/>
+            <Setter Property="IsReadOnly" Value="True"/>
+            <Setter Property="RowStyle" Value="{StaticResource ReportDataGridRow}"/>
+        </Style>
+
+        <Style TargetType="ListBox" x:Key="ListBoxItemStyle1">
+            <Setter Property="Background" Value="Transparent"/>
+            <Setter Property="Padding" Value="-1"/>
+            <Setter Property="ItemContainerStyle">
+                <Setter.Value>
+                    <Style TargetType="ListBoxItem">
+                        <Setter Property="Margin" Value="0"/>
+                        <Setter Property="Padding" Value="0"/>
+                        <Setter Property="Foreground" Value="Black"/>
+                        <Setter Property="Template">
+                            <Setter.Value>
+                                <ControlTemplate TargetType="ListBoxItem">
+                                    <Border x:Name="border" Padding="10,5" Cursor="Hand" Margin="5,2" Height="30"
+                                                BorderThickness="1" BorderBrush="{DynamicResource CommonBoarder}">
+                                        <DockPanel VerticalAlignment="Center">
+                                            <Button Margin="10,0,0,0" DockPanel.Dock="Right" 
+                                                    Command="{Binding RemoveSNCommand,RelativeSource={RelativeSource AncestorType=local:QualityReport}}" 
+                                                    CommandParameter="{Binding }"
+                                                    Tag="{Binding }">X</Button>
+                                            <ContentPresenter x:Name="cp" Content="{TemplateBinding Content}" 
+ContentStringFormat="{TemplateBinding ContentStringFormat}" 
+ContentTemplate="{TemplateBinding ContentTemplate}" />
+                                        </DockPanel>
+                                    </Border>
+
+                                    <ControlTemplate.Triggers>
+                                        <Trigger Property="IsMouseOver" Value="True">
+                                            <Setter TargetName="border" Property="Background" Value="#7093d3" />
+                                            <Setter Property="Foreground" Value="{StaticResource SecondPrimaryColorContract}" />
+                                        </Trigger>
+                                    </ControlTemplate.Triggers>
+                                </ControlTemplate>
+                            </Setter.Value>
+                        </Setter>
+                    </Style>
+                </Setter.Value>
+            </Setter>
+            <Setter Property="ItemsPanel">
+                <Setter.Value>
+                    <ItemsPanelTemplate>
+                        <VirtualizingStackPanel IsItemsHost="True" Orientation="Horizontal"/>
+                    </ItemsPanelTemplate>
+                </Setter.Value>
+            </Setter>
+        </Style>
+    </UserControl.Resources>
+
+    <Border Style="{StaticResource CardBoarder}" Padding="0">
+        <TabControl Style="{StaticResource HeaderTabControl}" >
+            <TabItem Header="CMK">
+                <Grid dp:GridHelper.Space="10">
+                    <Grid.RowDefinitions>
+                        <RowDefinition Height="Auto"/>
+                        <RowDefinition Height="Auto"/>
+                        <RowDefinition/>
+                    </Grid.RowDefinitions>
+                    <StackPanel Orientation="Horizontal" VerticalAlignment="Center" dp:StackPanelHelper.ChildSpace="10">
+                        <pu:FormGroup Header="鍨嬪彿" Style="{StaticResource InputFormGroupStyle}">
+                            <ComboBox MinWidth="180" ItemsSource="{Binding Models}" Style="{StaticResource FormComboxStyle}" SelectedValue="{Binding Model}"
+SelectedValuePath="Name" DisplayMemberPath="Name" />
+                        </pu:FormGroup>
+
+                        <pu:FormGroup Header="鏃堕棿" Style="{StaticResource InputFormGroupStyle}">
+                            <StackPanel Orientation="Horizontal">
+                                <pu:DateTimePicker Style="{StaticResource TimePicker}" SelectedDateTime="{Binding StartTimeCMK}"></pu:DateTimePicker>
+                                <TextBlock VerticalAlignment="Center" Text="   鑷�   "/>
+                                <pu:DateTimePicker Style="{StaticResource TimePicker}" SelectedDateTime="{Binding EndTimeCMK}"></pu:DateTimePicker>
+                            </StackPanel>
+                        </pu:FormGroup>
+
+                        <pu:FormGroup Header="娴嬭瘯椤�" Style="{StaticResource InputFormGroupStyle}">
+                            <pu:SearchBox Text="{Binding TestItemCMK,Mode=TwoWay}" Tag="Step" SearchTextChanged="SearchTextChanged" MinWidth="180" />
+                        </pu:FormGroup>
+
+                        <Button Style="{StaticResource QueryBtn}" Command="{Binding QueryCMKCommand}"/>
+                        <Button Style="{StaticResource ExportBtn}" Command="{Binding ExportCMKCommand}"/>
+                    </StackPanel>
+                    <DataGrid Grid.Row="2" Style="{StaticResource ReportDataGrid}"
+                              ItemsSource="{Binding DatasourceCMK}">
+                        <DataGrid.Columns>
+                            <DataGridTextColumn Width="*" Header="鏁版嵁1" Binding="{Binding d1}"/>
+                            <DataGridTextColumn Width="*" Header="鏁版嵁2" Binding="{Binding d2}"/>
+                            <DataGridTextColumn Width="*" Header="鏁版嵁3" Binding="{Binding d3}"/>
+                            <DataGridTextColumn Width="*" Header="鏁版嵁4" Binding="{Binding d4}"/>
+                            <DataGridTextColumn Width="*" Header="鏁版嵁5" Binding="{Binding d5}"/>
+                        </DataGrid.Columns>
+                    </DataGrid>
+                </Grid>
+            </TabItem>
+            <TabItem Header="MSA">
+                <Grid dp:GridHelper.Space="10">
+                    <Grid.RowDefinitions>
+                        <RowDefinition Height="Auto"/>
+                        <RowDefinition Height="Auto"/>
+                        <RowDefinition/>
+                    </Grid.RowDefinitions>
+                    <StackPanel Orientation="Horizontal" VerticalAlignment="Center" dp:StackPanelHelper.ChildSpace="10">
+                        <pu:FormGroup Header="鍨嬪彿" Style="{StaticResource InputFormGroupStyle}">
+                            <ComboBox MinWidth="180" ItemsSource="{Binding Models}" Style="{StaticResource FormComboxStyle}" SelectedValue="{Binding Model}"
+              SelectedValuePath="Name" DisplayMemberPath="Name" />
+                        </pu:FormGroup>
+
+                        <pu:FormGroup Header="SN" Style="{StaticResource InputFormGroupStyle}">
+                            <DockPanel>
+                                <Button DockPanel.Dock="Right"
+                                        Command="{Binding AddSnrCommand}"
+                                        Style="{StaticResource PrimaryButton}" 
+                                        FontFamily="{StaticResource iconfont}"
+                                        Content="&#xe61a;"/>
+                                <pu:SearchBox Text="{Binding Snr,Mode=TwoWay}" Tag="SN" SearchTextChanged="SearchTextChanged" MinWidth="180" />
+                            </DockPanel>
+                        </pu:FormGroup>
+
+                        <pu:FormGroup Header="鏃堕棿" Style="{StaticResource InputFormGroupStyle}">
+                            <StackPanel Orientation="Horizontal">
+                                <pu:DateTimePicker Style="{StaticResource TimePicker}" SelectedDateTime="{Binding StartTimeCMK}"></pu:DateTimePicker>
+                                <TextBlock VerticalAlignment="Center" Text="   鑷�   "/>
+                                <pu:DateTimePicker Style="{StaticResource TimePicker}" SelectedDateTime="{Binding EndTimeCMK}"></pu:DateTimePicker>
+                            </StackPanel>
+                        </pu:FormGroup>
+
+                        <Button Style="{StaticResource QueryBtn}" Command="{Binding QueryMSACommand}"/>
+                        <Button Style="{StaticResource ExportBtn}" Command="{Binding ExportMSACommand}"/>
+                    </StackPanel>
+                    <pu:FormGroup Grid.Row="1" Header="宸查�塖N" Style="{StaticResource InputFormGroupStyle}">
+                        <ListBox Style="{StaticResource ListBoxItemStyle1}" ItemsSource="{Binding SelectedSnr}" Height="40">
+                        </ListBox>
+                    </pu:FormGroup>
+
+                    <DataGrid Grid.Row="2" Style="{StaticResource ReportDataGrid}"
+          ItemsSource="{Binding DatasourceMSA}">
+                        <DataGrid.Columns>
+                            <DataGridTextColumn Width="*" Header="鏉$爜1" Binding="{Binding d1}"/>
+                            <DataGridTextColumn Width="*" Header="鏉$爜2" Binding="{Binding d2}"/>
+                            <DataGridTextColumn Width="*" Header="鏉$爜3" Binding="{Binding d3}"/>
+                            <DataGridTextColumn Width="*" Header="鏉$爜4" Binding="{Binding d4}"/>
+                            <DataGridTextColumn Width="*" Header="鏉$爜5" Binding="{Binding d5}"/>
+                            <DataGridTextColumn Width="*" Header="鏉$爜6" Binding="{Binding d5}"/>
+                            <DataGridTextColumn Width="*" Header="鏉$爜7" Binding="{Binding d5}"/>
+                            <DataGridTextColumn Width="*" Header="鏉$爜8" Binding="{Binding d5}"/>
+                            <DataGridTextColumn Width="*" Header="鏉$爜9" Binding="{Binding d5}"/>
+                        </DataGrid.Columns>
+                    </DataGrid>
+                    <!--<Grid Grid.Row="2" x:Name="msaGrid" dp:GridHelper.InnerBorder="1" dp:GridHelper.InnerBorderBrush="Black">
+                        
+                    </Grid>-->
+                </Grid>
+            </TabItem>
+            <TabItem Header="閲嶅鎬�">
+                <Grid dp:GridHelper.Space="10">
+                    <Grid.RowDefinitions>
+                        <RowDefinition Height="Auto"/>
+                        <RowDefinition Height="Auto"/>
+                        <RowDefinition/>
+                    </Grid.RowDefinitions>
+                    <StackPanel Orientation="Horizontal" VerticalAlignment="Center" dp:StackPanelHelper.ChildSpace="10">
+                        <pu:FormGroup Header="鍨嬪彿" Style="{StaticResource InputFormGroupStyle}">
+                            <ComboBox MinWidth="180" ItemsSource="{Binding Models}" Style="{StaticResource FormComboxStyle}" SelectedValue="{Binding Model}"
+    SelectedValuePath="Name" DisplayMemberPath="Name" />
+                        </pu:FormGroup>
+
+                        <pu:FormGroup Header="SN" Style="{StaticResource InputFormGroupStyle}">
+                            <DockPanel>
+                                <Button DockPanel.Dock="Right"
+                              Command="{Binding AddSnr2RepeatCommand}"
+                              Style="{StaticResource PrimaryButton}" 
+                              FontFamily="{StaticResource iconfont}"
+                              Content="&#xe61a;"/>
+                                <pu:SearchBox Text="{Binding Snr,Mode=TwoWay}" Tag="SN" SearchTextChanged="SearchTextChanged" MinWidth="180" />
+                            </DockPanel>
+                        </pu:FormGroup>
+
+                        <pu:FormGroup Header="娴嬭瘯椤�" Style="{StaticResource InputFormGroupStyle}">
+                            <pu:SearchBox Text="{Binding TestItemCMK,Mode=TwoWay}" Tag="Step" SearchTextChanged="SearchTextChanged" MinWidth="180" />
+                        </pu:FormGroup>
+
+                        <pu:FormGroup Header="鏃堕棿" Style="{StaticResource InputFormGroupStyle}">
+                            <StackPanel Orientation="Horizontal">
+                                <pu:DateTimePicker Style="{StaticResource TimePicker}" SelectedDateTime="{Binding StartTimeCMK}"></pu:DateTimePicker>
+                                <TextBlock VerticalAlignment="Center" Text="   鑷�   "/>
+                                <pu:DateTimePicker Style="{StaticResource TimePicker}" SelectedDateTime="{Binding EndTimeCMK}"></pu:DateTimePicker>
+                            </StackPanel>
+                        </pu:FormGroup>
+
+                        <Button Style="{StaticResource QueryBtn}" Command="{Binding QueryRepeatCommand}"/>
+                        <Button Style="{StaticResource ExportBtn}" Command="{Binding ExportRepeatCommand}"/>
+                    </StackPanel>
+                    <pu:FormGroup Grid.Row="1" Header="宸查�塖N" Style="{StaticResource InputFormGroupStyle}">
+                        <ListBox Style="{StaticResource ListBoxItemStyle1}" ItemsSource="{Binding SelectedSnrRepeat}" Height="40">
+                        </ListBox>
+                    </pu:FormGroup>
+
+                    <DataGrid Grid.Row="2" Style="{StaticResource ReportDataGrid}" ItemsSource="{Binding DatasourceRepeat}">
+                        <DataGrid.Columns>
+                            <DataGridTextColumn Width="*" Header="鏉$爜1" Binding="{Binding d1}"/>
+                            <DataGridTextColumn Width="*" Header="鏉$爜2" Binding="{Binding d2}"/>
+                            <DataGridTextColumn Width="*" Header="鏉$爜3" Binding="{Binding d3}"/>
+                            <DataGridTextColumn Width="*" Header="鏉$爜4" Binding="{Binding d4}"/>
+                            <DataGridTextColumn Width="*" Header="鏉$爜5" Binding="{Binding d5}"/>
+                            <DataGridTextColumn Width="*" Header="鏉$爜6" Binding="{Binding d5}"/>
+                            <DataGridTextColumn Width="*" Header="鏉$爜7" Binding="{Binding d5}"/>
+                            <DataGridTextColumn Width="*" Header="鏉$爜8" Binding="{Binding d5}"/>
+                            <DataGridTextColumn Width="*" Header="鏉$爜9" Binding="{Binding d5}"/>
+                        </DataGrid.Columns>
+                    </DataGrid>
+                </Grid>
+
+            </TabItem>
+        </TabControl>
+    </Border>
+</hb:SimpleMaskControl>
diff --git a/Plugin.QualityReport/QualityReport.xaml.cs b/Plugin.QualityReport/QualityReport.xaml.cs
new file mode 100644
index 0000000..538968c
--- /dev/null
+++ b/Plugin.QualityReport/QualityReport.xaml.cs
@@ -0,0 +1,705 @@
+锘縰sing CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using HMI.Base;
+using HMI.Core.Entity;
+using HMI.Core.Manager;
+using HMI.Core.Setting;
+using HMI.Core.Util;
+using HMI.UI.Util;
+using Microsoft.Win32;
+using NPOI.HSSF.UserModel;
+using NPOI.SS.UserModel;
+using NPOI.Util;
+using NPOI.XSSF.UserModel;
+using Panuon.WPF.UI;
+using System.Collections.ObjectModel;
+using System.Data;
+using System.IO;
+using System.Windows;
+
+namespace Plugin.QualityReport
+{
+    public class CMKData
+    {
+        public object[] values = new object[5];
+        public object d1 { get => values[0]; set => values[0] = value; }
+        public object d2 { get => values[1]; set => values[1] = value; }
+        public object d3 { get => values[2]; set => values[2] = value; }
+        public object d4 { get => values[3]; set => values[3] = value; }
+        public object d5 { get => values[4]; set => values[4] = value; }
+    }
+
+    public class MSAData
+    {
+        public object[] values = new object[9];
+        public object d1 { get => values[0]; set => values[0] = value; }
+        public object d2 { get => values[1]; set => values[1] = value; }
+        public object d3 { get => values[2]; set => values[2] = value; }
+        public object d4 { get => values[3]; set => values[3] = value; }
+        public object d5 { get => values[4]; set => values[4] = value; }
+        public object d6 { get => values[5]; set => values[5] = value; }
+        public object d7 { get => values[6]; set => values[6] = value; }
+        public object d8 { get => values[7]; set => values[7] = value; }
+        public object d9 { get => values[8]; set => values[8] = value; }
+    }
+
+    public class RepeatData
+    {
+        public string Title { get; set; }
+
+        public object[] values = new object[10];
+        public object d1 { get => values[0]; set => values[0] = value; }
+        public object d2 { get => values[1]; set => values[1] = value; }
+        public object d3 { get => values[2]; set => values[2] = value; }
+        public object d4 { get => values[3]; set => values[3] = value; }
+        public object d5 { get => values[4]; set => values[4] = value; }
+        public object d6 { get => values[5]; set => values[5] = value; }
+        public object d7 { get => values[6]; set => values[6] = value; }
+        public object d8 { get => values[7]; set => values[7] = value; }
+        public object d9 { get => values[8]; set => values[8] = value; }
+        public object d10 { get => values[9]; set => values[9] = value; }
+    }
+
+    /// <summary>
+    /// QualityReport.xaml 鐨勪氦浜掗�昏緫
+    /// </summary>
+    [ObservableObject]
+    public partial class QualityReport : SimpleMaskControl
+    {
+        [ObservableProperty]
+        private string model;
+
+        [ObservableProperty]
+        private List<ProductData> models;
+
+        [ObservableProperty]
+        private DateTime startTimeCMK;
+
+        [ObservableProperty]
+        private DateTime endTimeCMK;
+
+        [ObservableProperty]
+        private string testItemCMK = "TestItem";
+
+        [ObservableProperty]
+        private List<CMKData> datasourceCMK = null;
+
+        [ObservableProperty]
+        private List<MSAData> datasourceMSA = null;
+
+        [ObservableProperty]
+        private List<RepeatData> datasourceRepeat = null;
+
+        [ObservableProperty]
+        private string snr;
+
+        [ObservableProperty]
+        private ObservableCollection<string> selectedSnr = new ObservableCollection<string> { };
+
+        [ObservableProperty]
+        private ObservableCollection<string> selectedSnrRepeat = new ObservableCollection<string> { };
+
+        public QualityReport()
+        {
+            DataContext = this;
+            InitializeComponent();
+            StartTimeCMK = DateTime.Today;
+            EndTimeCMK = DateTime.Today.AddDays(1);
+        }
+
+        public override void Active()
+        {
+            base.Active();
+            Models = ProductManager.GetProducts<List<ProductData>>()
+                ?? new List<ProductData>();
+        }
+
+        private string[] GetFiles(DateTime? startTime, DateTime? endTime)
+        {
+            string dir = ParamSetting.DataLogFolder;
+            string startName = $"Data_{startTime?.ToString("yyyyMMdd")}";
+            string endName = $"Data_{endTime?.ToString("yyyyMMdd")}";
+            var oldFiles = Directory.GetFiles(dir, "Data_*.db");
+            oldFiles = oldFiles.Where(file =>
+            {
+                var fileName = Path.GetFileNameWithoutExtension(file);
+                bool flag = true;
+                flag &= (string.Compare(fileName, startName) == 1 || fileName == startName);
+                flag &= (string.Compare(endName, fileName) == 1 || fileName == endName);
+                return flag;
+            }).ToArray();
+            return oldFiles;
+        }
+
+        public DataTable SelectByFiles(string[] files, Func<string, string, string> ConcatAction, SortParam sorter, PageParam page)
+        {
+            if (files == null || files.Length <= 0)
+            {
+                return new DataTable();
+            }
+            if (files.Length < Sqlite3Helper.MAX_ATTACHED)
+            {
+                using (var helper = new Sqlite3Helper(files[0]))
+                {
+                    string[] sqls = new string[files.Length];
+                    for (int i = 0; i < files.Length; i++)
+                    {
+                        var attach = $"ATTACH DATABASE '{files[i]}' AS 'db{i}'";
+                        helper.Execute(attach);
+                        sqls[i] = ConcatAction(files[i], $"db{i}");
+                    }
+                    string sql = string.Join(" UNION ALL ", sqls);
+
+                    sql = $"SELECT * FROM ({sql}) as _temp";
+                    if (sorter != null)
+                    {
+                        sql = $"{sql} ORDER BY {sorter.field} {sorter.sort}";
+                    }
+                    if (page != null)
+                    {
+                        sql = $"{sql} LIMIT {page.limit} OFFSET {page.offset}";
+                    }
+                    return helper.GetDataTable(sql);
+                }
+            }
+            else
+            {
+                DataTable dt = new DataTable();
+                Sqlite3Helper helper = null;
+                string[] sqls = new string[Sqlite3Helper.MAX_ATTACHED];
+
+                var fff = () =>
+                {
+                    if (helper != null)
+                    {
+                        string sql = string.Join(" UNION ALL ", sqls.Where(s => s != null));
+
+                        sql = $"SELECT * FROM ({sql}) as _temp";
+
+
+                        dt.Merge(helper.GetDataTable(sql));
+                        sqls = new string[Sqlite3Helper.MAX_ATTACHED];
+                        helper.Dispose();
+                        helper = null;
+                    }
+                };
+
+                try
+                {
+                    for (int i = 0; i < files.Length; i++)
+                    {
+                        if (i % Sqlite3Helper.MAX_ATTACHED == 0)
+                        {
+                            fff();
+                            helper = new Sqlite3Helper(files[i]);
+                        }
+                        var attach = $"ATTACH DATABASE '{files[i]}' AS 'db{i}'";
+                        helper.Execute(attach);
+                        sqls[i % Sqlite3Helper.MAX_ATTACHED] = ConcatAction(files[i], $"db{i}");
+                    }
+                    fff();
+                    if (dt.Rows.Count > 0)
+                    {
+                        if (sorter != null)
+                        {
+                            dt.DefaultView.Sort = $"{sorter.field} {sorter.sort}";
+                        }
+                        if (page != null)
+                        {
+                            var rows = dt.Rows.Cast<DataRow>();
+                            var curRows = rows.Skip(page.offset).Take(page.limit).ToArray();
+                            var sb = dt.Clone();
+                            if (dt.Rows.Count > page.offset)
+                            {
+                                if (dt.Rows.Count < page.limit)
+                                {
+                                    sb = dt;
+                                }
+                                else
+                                {
+                                    for (int i = page.offset; i < page.offset + page.limit; i++)
+                                    {
+                                        sb.ImportRow(dt.Rows[i]);
+                                    }
+                                }
+                            }
+                            dt = sb;
+                            // dt = new DataTable(curRows);
+                        }
+                    }
+                }
+                finally
+                {
+                    helper?.Dispose();
+                }
+                return dt;
+            }
+        }
+
+
+        [RelayCommand]
+        public void QueryCMK()
+        {
+            try
+            {
+                if (string.IsNullOrEmpty(Model) || string.IsNullOrEmpty(TestItemCMK) || StartTimeCMK == null || EndTimeCMK == null)
+                {
+                    ToastHelper.Error("璇疯緭鍏ュ瀷鍙枫�佹祴璇曢」銆佸紑濮嬫椂闂村拰缁撴潫鏃堕棿锛�");
+                    return;
+                }
+                string[] files = GetFiles(StartTimeCMK, EndTimeCMK);
+
+
+                DataTable dt = SelectByFiles(files, (file, db) =>
+                {
+                    return $"SELECT value,time FROM {db}.serialData WHERE model='{Model}' AND step='{TestItemCMK}' AND time>='{StartTimeCMK:yyyy-MM-dd HH:mm:ss}' AND time<'{EndTimeCMK:yyyy-MM-dd HH:mm:ss}'";
+                }, new SortParam { field = "time", sort = "DESC" }, new PageParam { limit = 50, offset = 0 });
+                List<CMKData> list = new List<CMKData>();
+                object[] values = null;
+                for (int i = 0; i < dt.Rows.Count; i++)
+                {
+                    if (i % 5 == 0)
+                    {
+                        values = new object[5];
+                        list.Add(new CMKData { values = values });
+                    }
+                    values[i % 5] = dt.Rows[i]["value"];
+                }
+                DatasourceCMK = list;
+            }
+            catch (Exception ex)
+            {
+
+            }
+        }
+
+        [RelayCommand]
+        public void ExportCMK()
+        {
+            try
+            {
+                if (string.IsNullOrEmpty(Model) || string.IsNullOrEmpty(TestItemCMK) || StartTimeCMK == null || EndTimeCMK == null)
+                {
+                    ToastHelper.Error("璇疯緭鍏ュ瀷鍙枫�佹祴璇曢」銆佸紑濮嬫椂闂村拰缁撴潫鏃堕棿锛�");
+                    return;
+                }
+                string[] files = GetFiles(StartTimeCMK, EndTimeCMK);
+
+
+                DataTable dt = SelectByFiles(files, (file, db) =>
+                {
+                    return $"SELECT value,time FROM {db}.serialData WHERE model='{Model}' AND step='{TestItemCMK}' AND time>='{StartTimeCMK:yyyy-MM-dd HH:mm:ss}' AND time<'{EndTimeCMK:yyyy-MM-dd HH:mm:ss}'";
+                }, new SortParam { field = "time", sort = "DESC" }, new PageParam { limit = 50, offset = 0 });
+                List<CMKData> list = new List<CMKData>();
+                object[] values = null;
+                for (int i = 0; i < dt.Rows.Count; i++)
+                {
+                    if (i % 5 == 0)
+                    {
+                        values = new object[5];
+                        list.Add(new CMKData { values = values });
+                    }
+                    values[i % 5] = dt.Rows[i]["value"];
+                }
+
+                using (FileStream fs = File.OpenRead(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Libs", "Static", "CMK妯℃澘.xls")))
+                {
+                    IWorkbook workbook = new HSSFWorkbook(fs);
+                    ISheet sheet = workbook.GetSheet("CMK");
+                    for (int i = 0; i < list.Count; i++)
+                    {
+                        int num = i + 9;
+                        if (num > 24)
+                        {
+                            break;
+                        }
+                        IRow row = sheet.GetRow(num);
+                        CMKData data = list[i];
+                        for (int j = 0; j < 5; j++)
+                        {
+                            if (data.values[j] != null)
+                            {
+                                row.GetCell(j).SetCellValue(double.Parse(data.values[j].ToString()));
+                            }
+                        }
+                    }
+                    sheet.ForceFormulaRecalculation = true;//鎵ц鍏紡
+
+                    //鏂规硶浜�
+                    //workbook.GetCreationHelper().CreateFormulaEvaluator().EvaluateAll();
+
+                    SaveFileDialog sfd = new SaveFileDialog
+                    {
+                        Filter = "Excel鏂囦欢 (*.xls)|*.xls",
+                    };
+                    if (sfd.ShowDialog() == true)
+                    {
+                        using (FileStream sfs = new FileStream(sfd.FileName, FileMode.OpenOrCreate))
+                        {
+                            workbook.Write(sfs);
+                        }
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+
+            }
+        }
+
+        [RelayCommand]
+        private void AddSnr()
+        {
+            if (SelectedSnr.Count >= 9)
+            {
+                ToastHelper.Warning("鏈�澶氶�夊彇9涓骇鍝�");
+                return;
+            }
+
+            if (SelectedSnr.Contains(Snr))
+            {
+                ToastHelper.Warning($"鏉$爜锛歔{Snr}] 宸茶閫変腑");
+                return;
+            }
+            SelectedSnr.Add(Snr);
+        }
+
+        [RelayCommand]
+        private void AddSnr2Repeat()
+        {
+            if (SelectedSnrRepeat.Count >= 10)
+            {
+                ToastHelper.Warning("鏈�澶氶�夊彇10涓骇鍝�");
+                return;
+            }
+
+            if (SelectedSnrRepeat.Contains(Snr))
+            {
+                ToastHelper.Warning($"鏉$爜锛歔{Snr}] 宸茶閫変腑");
+                return;
+            }
+            SelectedSnrRepeat.Add(Snr);
+        }
+
+        [RelayCommand]
+        public void RemoveSN(object obj)
+        {
+            SelectedSnr.Remove(obj.ToString());
+        }
+
+        [RelayCommand]
+        public void QueryMSA()
+        {
+            try
+            {
+                if (string.IsNullOrEmpty(Model) || SelectedSnr.Count <= 0 || StartTimeCMK == null || EndTimeCMK == null)
+                {
+                    ToastHelper.Error("璇疯緭鍏ュ瀷鍙枫�侀�変腑鐨勬潯鐮併�佸紑濮嬫椂闂村拰缁撴潫鏃堕棿锛�");
+                    return;
+                }
+                string[] files = GetFiles(StartTimeCMK, EndTimeCMK);
+                Dictionary<string, List<int>> map = new Dictionary<string, List<int>>();
+                foreach (var sn in SelectedSnr)
+                {
+                    List<int> datas = new List<int>();
+                    DataTable dt = SelectByFiles(files, (file, db) =>
+                {
+                    return $"SELECT result,time FROM {db}.testResult WHERE model='{Model}' AND sn='{sn}' AND time>='{StartTimeCMK:yyyy-MM-dd HH:mm:ss}' AND time<'{EndTimeCMK:yyyy-MM-dd HH:mm:ss}'";
+                }, new SortParam { field = "time", sort = "DESC" }, new PageParam { limit = 10, offset = 0 });
+
+                    datas = dt.Rows.Cast<DataRow>().Select(r =>
+                    string.Equals(r["result"].ToString(), "passed", StringComparison.OrdinalIgnoreCase)
+                    || string.Equals(r["result"].ToString(), "ok", StringComparison.OrdinalIgnoreCase) ? 1 : 0)
+                        .ToList();
+                    map[sn] = datas;
+                }
+                List<MSAData> temp = new List<MSAData>() { new MSAData() };
+
+                int i = 0;
+                foreach (var key in map.Keys)
+                {
+                    temp[0].values[i] = key;
+                    var vals = map[key];
+                    for (int j = 0; j < vals.Count; j++)
+                    {
+                        if (temp.Count < j + 2)
+                        {
+                            temp.Add(new MSAData());
+                        }
+                        temp[j + 1].values[i] = vals[j];
+                    }
+                    i++;
+                }
+                DatasourceMSA = temp;
+
+                //RefreshMSAGrid(map);
+            }
+            catch (Exception ex)
+            {
+
+            }
+        }
+
+
+        [RelayCommand]
+        public void ExportMSA()
+        {
+            try
+            {
+                if (string.IsNullOrEmpty(Model) || SelectedSnr.Count <= 0 || StartTimeCMK == null || EndTimeCMK == null)
+                {
+                    ToastHelper.Error("璇疯緭鍏ュ瀷鍙枫�侀�変腑鐨勬潯鐮併�佸紑濮嬫椂闂村拰缁撴潫鏃堕棿锛�");
+                    return;
+                }
+
+                string[] files = GetFiles(StartTimeCMK, EndTimeCMK);
+                Dictionary<string, List<int>> map = new Dictionary<string, List<int>>();
+                foreach (var sn in SelectedSnr)
+                {
+                    List<int> datas = new List<int>();
+                    DataTable dt = SelectByFiles(files, (file, db) =>
+                    {
+                        return $"SELECT result,time FROM {db}.testResult WHERE model='{Model}' AND sn='{sn}' AND time>='{StartTimeCMK:yyyy-MM-dd HH:mm:ss}' AND time<'{EndTimeCMK:yyyy-MM-dd HH:mm:ss}'";
+                    }, new SortParam { field = "time", sort = "DESC" }, new PageParam { limit = 10, offset = 0 });
+
+                    datas = dt.Rows.Cast<DataRow>().Select(r =>
+                    string.Equals(r["result"].ToString(), "passed", StringComparison.OrdinalIgnoreCase)
+                    || string.Equals(r["result"].ToString(), "ok", StringComparison.OrdinalIgnoreCase) ? 1 : 0)
+                        .ToList();
+                    map[sn] = datas;
+                }
+                using (FileStream fs = File.OpenRead(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Libs", "Static", "MSA妯℃澘.xlsx")))
+                {
+                    IWorkbook workbook = new XSSFWorkbook(fs);
+                    ISheet sheet = workbook.GetSheet("鏁版嵁杈撳叆");
+
+                    int column = 2;
+                    foreach (string snr in map.Keys)
+                    {
+                        if (column > 10)
+                        {
+                            break;
+                        }
+                        var datas = map[snr];
+                        sheet.GetRow(9).GetCell(column).SetCellValue(snr);
+
+                        for (int i = 0; i < datas.Count; i++)
+                        {
+                            if (i > 49) break;
+                            sheet.GetRow(i + 11).GetCell(column).SetCellValue(datas[i]);
+                        }
+                        column++;
+                    }
+                    sheet.ForceFormulaRecalculation = true;//鎵ц鍏紡
+
+                    //鏂规硶浜�
+                    //workbook.GetCreationHelper().CreateFormulaEvaluator().EvaluateAll();
+
+                    SaveFileDialog sfd = new SaveFileDialog
+                    {
+                        Filter = "Excel鏂囦欢 (*.xlsx)|*.xlsx",
+                    };
+                    if (sfd.ShowDialog() == true)
+                    {
+                        using (FileStream sfs = new FileStream(sfd.FileName, FileMode.OpenOrCreate))
+                        {
+                            workbook.Write(sfs);
+                        }
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+
+            }
+        }
+
+        [RelayCommand]
+        public void QueryRepeat()
+        {
+            try
+            {
+                if (string.IsNullOrEmpty(Model) || SelectedSnrRepeat.Count <= 0 || string.IsNullOrEmpty(TestItemCMK) || StartTimeCMK == null || EndTimeCMK == null)
+                {
+                    ToastHelper.Error("璇疯緭鍏ュ瀷鍙枫�侀�変腑鐨勬潯鐮併�佹祴璇曢」銆佸紑濮嬫椂闂村拰缁撴潫鏃堕棿锛�");
+                    return;
+                }
+
+                string[] files = GetFiles(StartTimeCMK, EndTimeCMK);
+                Dictionary<string, List<double>> map = new Dictionary<string, List<double>>();
+                foreach (var sn in SelectedSnrRepeat)
+                {
+                    DataTable dt = SelectByFiles(files, (file, db) =>
+                    {
+                        return $"SELECT value,time FROM {db}.serialData WHERE model='{Model}' And step='{TestItemCMK}' AND sn='{sn}' AND time>='{StartTimeCMK:yyyy-MM-dd HH:mm:ss}' AND time<'{EndTimeCMK:yyyy-MM-dd HH:mm:ss}'";
+                    }, new SortParam { field = "time", sort = "DESC" }, new PageParam { limit = 9, offset = 0 });
+
+                    var datas = dt.Rows.Cast<DataRow>().Select(r => Math.Round(double.Parse(r["value"].ToString()), 3, MidpointRounding.AwayFromZero)).ToList();
+                    map[sn] = datas;
+                }
+                List<RepeatData> temp = new List<RepeatData>() { new RepeatData { Title = "鏉$爜" } };
+
+                int i = 0;
+                foreach (var key in map.Keys)
+                {
+                    temp[0].values[i] = key;
+                    var vals = map[key];
+                    for (int j = 0; j < vals.Count; j++)
+                    {
+                        if (temp.Count < j + 2)
+                        {
+                            temp.Add(new RepeatData());
+                        }
+                        temp[j + 1].values[i] = vals[j];
+                    }
+                    i++;
+                }
+                DatasourceRepeat = temp;
+            }
+            catch (Exception ex)
+            {
+                log.Error("閲嶅鎬ф煡璇㈠け璐�", ex);
+                MsgBoxHelper2.Error($"閲嶅鎬ф煡璇㈠け璐�: {ex.Message}");
+            }
+        }
+
+
+        [RelayCommand]
+        public void ExportRepeat()
+        {
+            try
+            {
+                if (string.IsNullOrEmpty(Model) || SelectedSnrRepeat.Count <= 0 || string.IsNullOrEmpty(TestItemCMK) || StartTimeCMK == null || EndTimeCMK == null)
+                {
+                    ToastHelper.Error("璇疯緭鍏ュ瀷鍙枫�侀�変腑鐨勬潯鐮併�佹祴璇曢」銆佸紑濮嬫椂闂村拰缁撴潫鏃堕棿锛�");
+                    return;
+                }
+                string[] files = GetFiles(StartTimeCMK, EndTimeCMK);
+                Dictionary<string, List<double>> map = new Dictionary<string, List<double>>();
+                foreach (var sn in SelectedSnrRepeat)
+                {
+                    DataTable dt = SelectByFiles(files, (file, db) =>
+                    {
+                        return $"SELECT value,time FROM {db}.serialData WHERE model='{Model}' And step='{TestItemCMK}' AND sn='{sn}' AND time>='{StartTimeCMK:yyyy-MM-dd HH:mm:ss}' AND time<'{EndTimeCMK:yyyy-MM-dd HH:mm:ss}'";
+                    }, new SortParam { field = "time", sort = "DESC" }, new PageParam { limit = 9, offset = 0 });
+
+                    var datas = dt.Rows.Cast<DataRow>().Select(r => Math.Round(double.Parse(r["value"].ToString()), 3, MidpointRounding.AwayFromZero)).ToList();
+                    map[sn] = datas;
+                }
+                List<RepeatData> temp = new List<RepeatData>() { new RepeatData() };
+
+                int i = 0;
+                foreach (var key in map.Keys)
+                {
+                    temp[0].values[i] = key;
+                    var vals = map[key];
+                    for (int j = 0; j < vals.Count; j++)
+                    {
+                        if (temp.Count < j + 2)
+                        {
+                            temp.Add(new RepeatData());
+                        }
+                        temp[j + 1].values[i] = vals[j];
+                    }
+                    i++;
+                }
+
+                using (FileStream fs = File.OpenRead(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Libs", "Static", "閲嶅鎬фā鏉�.xls")))
+                {
+                    IWorkbook workbook = new HSSFWorkbook(fs);
+                    ISheet sheet = workbook.GetSheet("sheet1");
+
+                    sheet.GetRow(10).GetCell(2).SetCellValue(TestItemCMK);
+
+                    int column = 2;
+                    foreach (string snr in map.Keys)
+                    {
+                        if (column > 11)
+                        {
+                            break;
+                        }
+                        var datas = map[snr];
+                        sheet.GetRow(11).GetCell(column).SetCellValue(snr);
+
+                        for (int j = 0; j < datas.Count; j++)
+                        {
+                            if (j > 8) break;
+                            int row = j + 13;
+                            if (j > 2 && j <= 5)
+                            {
+                                row = j + 15;
+                            }
+                            else if (j > 5)
+                            {
+                                row = j + 17;
+                            }
+                            sheet.GetRow(row).GetCell(column).SetCellValue(datas[j]);
+                        }
+                        column++;
+                    }
+                    sheet.ForceFormulaRecalculation = true;//鎵ц鍏紡
+
+                    //鏂规硶浜�
+                    //workbook.GetCreationHelper().CreateFormulaEvaluator().EvaluateAll();
+
+                    SaveFileDialog sfd = new SaveFileDialog
+                    {
+                        Filter = "Excel鏂囦欢 (*.xls)|*.xls",
+                    };
+                    if (sfd.ShowDialog() == true)
+                    {
+                        using (FileStream sfs = new FileStream(sfd.FileName, FileMode.OpenOrCreate))
+                        {
+                            workbook.Write(sfs);
+                            ToastHelper.Success("瀵煎嚭鎴愬姛");
+                        }
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                log.Error("閲嶅鎬у鍑哄け璐�", ex);
+                MsgBoxHelper2.Error($"瀵煎嚭澶辫触: {ex.Message}");
+            }
+        }
+
+        private void SearchTextChanged(object sender, Panuon.WPF.SearchTextChangedRoutedEventArgs e)
+        {
+            SearchBox box = (SearchBox)sender;
+            try
+            {
+                string[] files = GetFiles(StartTimeCMK, EndTimeCMK).OrderBy(s => s).Take(7).ToArray();
+
+                if (box.Tag.ToString() == "Step")
+                {
+                    DataTable dt = SelectByFiles(files, (file, db) =>
+                    {
+                        return $"SELECT DISTINCT step FROM {db}.serialData WHERE model='{Model}' And step LIKE '%{TestItemCMK}%'";
+                    }, null, null);
+
+                    var datas = dt.Rows.Cast<DataRow>().Select(r => r["step"].ToString()).Distinct().ToList();
+                    box.ItemsSource = datas;
+                }
+                else
+                {
+                    DataTable dt = SelectByFiles(files, (file, db) =>
+                    {
+                        return $"SELECT DISTINCT sn FROM {db}.testResult WHERE model='{Model}' And sn LIKE '%{Snr}%'";
+                    }, null, null);
+
+                    var datas = dt.Rows.Cast<DataRow>().Select(r => r["sn"].ToString()).Distinct().ToList();
+                    box.ItemsSource = datas;
+                }
+            }
+            catch
+            {
+                box.ItemsSource = new string[] { };
+            }
+        }
+
+        private void Button_Click(object sender, RoutedEventArgs e)
+        {
+
+        }
+    }
+}
diff --git a/Plugin.TestResult/DataTable.xaml b/Plugin.TestResult/DataTable.xaml
index 12a6b61..13e0adf 100644
--- a/Plugin.TestResult/DataTable.xaml
+++ b/Plugin.TestResult/DataTable.xaml
@@ -135,6 +135,7 @@
                 <ColumnDefinition/>
             </UniformGrid.ColumnDefinitions>-->
             <Button Click="ScanWindow_Click" Style="{StaticResource PrimaryButton}"
+                    IsEnabled="{Binding IsTesting,Converter={StaticResource reverseBool}}"
         Content="{DynamicResource Tr.Scan}"/>
             <Button Grid.Column="1" Click="ClearAllSnr_Click" Style="{StaticResource DangerButton}"
                     IsEnabled="{Binding IsTesting,Converter={StaticResource reverseBool}}"
diff --git a/Plugin.TestResult/DataTable.xaml.cs b/Plugin.TestResult/DataTable.xaml.cs
index 4be9f27..29d402c 100644
--- a/Plugin.TestResult/DataTable.xaml.cs
+++ b/Plugin.TestResult/DataTable.xaml.cs
@@ -319,7 +319,7 @@
             else
             {
                 /// 妫�鏌ユ槸鍚﹀叧闂�
-                if (parent.executionWrapper?.GetStationGlobalValue<bool>($"StationState.DoorClose[{this.index}]", 0) != true)
+                if (!GetDoorClose(parent.executionWrapper))
                 {
                     /// 娌″叧闂ㄤ笉鍏佽鍚姩
                     MsgBoxHelper2.Warn("璇峰厛鍏抽棴鑸遍棬锛�");
@@ -390,6 +390,11 @@
             scanner = null;
         }
 
+        public bool GetDoorClose(ITestExecutionWrapper wrapper)
+        {
+            return wrapper?.GetStationGlobalValue<bool>($"StationState.DoorClose[{this.index}]", 0) == true;
+        }
+
         private void Scanner_FinishBarcode(object sender, BarcodeEventArg e)
         {
             if (parent.executionWrapper == null) return;
@@ -399,19 +404,37 @@
             CellInfo cellInfo = ScannerParser.GetCellInfoByCode(CbProduct.SelectedItem?.ToString(), mainGrid.Columns, rows, e.positionCode);
             string positionCode = e.positionCode, snrCode = e.snrCode;
 
-            //灏嗚鍒扮殑鏉$爜鍐欏叆鍒癝N涓�
-            int dutNo = this.index * this.cellCount + cellInfo.Index;
+            //if (cellInfo.OvenName != this.title)
+            //{
+            //    MsgBoxHelper2.Warn($"璇峰垏鎹㈣嚦 [{cellInfo.OvenName}] 鍖烘壂鐮侊紝褰撳墠涓� [{this.title}] 鍖猴紒");
+            //    return;
+            //}
 
+
+            var targetTable = TestResultView.nameTables[cellInfo.OvenName];
+
+            //灏嗚鍒扮殑鏉$爜鍐欏叆鍒癝N涓�
+            int dutNo = targetTable.index * targetTable.cellCount + cellInfo.Index;
             string LookupStr = $"StationState.SN[{dutNo}]";
             cellInfo.LookUpStr = LookupStr;
 
-            ViewControl.Logger.Debug($"PositionCode: [{e.positionCode}], SNR: [{e.snrCode}], LookUpStr: [{LookupStr}]");
-            ViewControl.Logger.Debug($"OvenName: [{cellInfo.OvenName}], CellIndex: [{cellInfo.Index}]");
-            var cell = TestResultView.nameTables[cellInfo.OvenName].GetSingleCell(cellInfo.Index);
+            ViewControl.Logger.Info($"PositionCode: [{e.positionCode}], SNR: [{e.snrCode}], LookUpStr: [{LookupStr}]");
+            ViewControl.Logger.Info($"OvenName: [{cellInfo.OvenName}], CellIndex: [{cellInfo.Index}]");
+            var cell = targetTable.GetSingleCell(cellInfo.Index);
+
+            if (targetTable.GetDoorClose(parent.executionWrapper))
+            {
+                /// 鍏抽棬浜嗗氨涓嶅厑璁告竻鏉$爜
+                MsgBoxHelper2.Warn($"{targetTable.title}鐐夌敓浜т腑锛屼笉鍏佽鎿嶄綔鏉$爜");
+                //MsgBoxHelper2.Warn(this.lang("Tr.CantClearSNR"));
+                return;
+            }
+
             if (snrCode == null)
             {
                 var oldCellInfo = e.lastInfo;
-                var oldCell = TestResultView.nameTables[oldCellInfo.OvenName].GetSingleCell(oldCellInfo.Index);
+                var oldTable = TestResultView.nameTables[oldCellInfo.OvenName];
+                var oldCell = oldTable.GetSingleCell(oldCellInfo.Index);
                 if (oldCell.status != "hadCode")
                 {
                     throw new Exception(this.lang("Tr.PositionCantClearSNR", oldCellInfo.OriginCode));
@@ -441,18 +464,10 @@
             //parent.engine.Globals.SetValString(LookupStr, 1, snrCode);
             //parent.engine.CommitGlobalsToDisk(promptOnSaveConflicts: false);
 
-            TestResultView.nameTables[cellInfo.OvenName].GetSingleCell(cellInfo.Index).SetStatus("hadCode");
-            TestResultView.nameTables[cellInfo.OvenName].GetSingleCell(cellInfo.Index).sn = snrCode;
+            cell.SetStatus("hadCode");
+            cell.sn = snrCode;
             Scanner.InsertNewCode(snrCode, cellInfo);
-            var light = $"Oven{this.index + 1}_Cell{cell.index + 1}_Light_OK";
-            if (cell.LightGreen(true, out string msg))
-            {
-                ViewControl.Logger.Debug($"Open Light [{light}] ON.");
-            }
-            else
-            {
-                ViewControl.Logger.Debug($"Open Light [{light}] Error.{msg}");
-            }
+            cell.LightGreen(true, out string msg);            
         }
 
 
@@ -572,16 +587,18 @@
 
             string strs = datas_10015[2];
             string[] values = strs.Split(',');
-
-            GetSingleCell(cellIndex).SetInfos(new Info
+            Dispatcher.Invoke(() =>
             {
-                step = values[0],
-                value = values[1],
-                limitUp = values[2],
-                limitDown = values[3],
-                unit = values[4],
-                result = values[5],
-                updateTime = values[6]
+                GetSingleCell(cellIndex).SetInfos(new Info
+                {
+                    step = values[0],
+                    value = values[1],
+                    limitUp = values[2],
+                    limitDown = values[3],
+                    unit = values[4],
+                    result = values[5],
+                    updateTime = values[6]
+                });
             });
             //session.Send("1");
         }
@@ -786,7 +803,7 @@
 
         private void ClearAllSnr_Click(object sender, RoutedEventArgs e)
         {
-            if (parent.executionWrapper?.GetStationGlobalValue<bool>($"StationState.DoorClose[{this.index}]", 0) == true)
+            if (GetDoorClose(parent.executionWrapper))
             {
                 /// 鍏抽棬浜嗗氨涓嶅厑璁告竻鏉$爜
                 MsgBoxHelper2.Warn(this.lang("Tr.CantClearSNR"));
@@ -818,7 +835,7 @@
 
         public void TryClearCellSN(int cellIndex)
         {
-            if (parent.executionWrapper?.GetStationGlobalValue<bool>($"StationState.DoorClose[{this.index}]", 0) == true)
+            if (GetDoorClose(parent.executionWrapper))
             {
                 MsgBoxHelper.Error(Window.GetWindow(this), this.lang("Tr.CantClearSNR"));
                 return;
@@ -851,10 +868,7 @@
                 return;
             }
 
-
-
-
-            string key = $"StationState.Start[{this.index}]";
+            //string key = $"StationState.Start[{this.index}]";
             //if (parent.executionWrapper?.GetStationGlobalValue<bool>(key, 0) == true)
             //{
             //    MsgBoxHelper2.Warn("娴嬭瘯鍚姩涓紝涓嶅厑璁窤bort");
@@ -873,9 +887,9 @@
             {
                 return;
             }
-            LogHelper.Info("UI", "Abort鎸夐挳鎸変笅", out _);
-            ViewControl.Logger.Info("Abort鎸夐挳鎸変笅");
-            key = $"StationState.Abort[{this.index}]";
+            LogHelper.Info("UI", $"{title} Abort鎸夐挳鎸変笅", out _);
+            ViewControl.Logger.Info($"{title} Abort鎸夐挳鎸変笅");
+            string key = $"StationState.Abort[{this.index}]";
             parent.executionWrapper?.SetStationGlobalValue(key, 0, true);
         }
     }
diff --git a/Plugin.TestResult/Properties/AssemblyInfo.cs b/Plugin.TestResult/Properties/AssemblyInfo.cs
index eebb8a0..dbda48a 100644
--- a/Plugin.TestResult/Properties/AssemblyInfo.cs
+++ b/Plugin.TestResult/Properties/AssemblyInfo.cs
@@ -50,6 +50,6 @@
 //鍙互鎸囧畾鎵�鏈夎繖浜涘�硷紝涔熷彲浠ヤ娇鐢ㄢ�滅敓鎴愬彿鈥濆拰鈥滀慨璁㈠彿鈥濈殑榛樿鍊�
 //閫氳繃浣跨敤 "*"锛屽涓嬫墍绀�:
 // [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("1.0.0.0")]
-[assembly: AssemblyFileVersion("1.0.0.0")]
+[assembly: AssemblyVersion("1.0.1.0")]
+[assembly: AssemblyFileVersion("1.0.1.0")]
 [assembly: PluginClassName("Plugin.TestResult_WPF.ViewControl")]
diff --git a/Plugin.TestResult/Scanner.xaml.cs b/Plugin.TestResult/Scanner.xaml.cs
index 48bf1c5..aeba0b8 100644
--- a/Plugin.TestResult/Scanner.xaml.cs
+++ b/Plugin.TestResult/Scanner.xaml.cs
@@ -158,12 +158,27 @@
                     {
                         try
                         {
-                            this.targetCellInfo = ScannerParser.GetCellInfoByCode(this.model, this.columns, this.rows, barcode);
+                            var targetCellInfo = ScannerParser.GetCellInfoByCode(this.model, this.columns, this.rows, barcode);
                             if (!TestResultView.nameTables.ContainsKey(targetCellInfo.OvenName))
                             {
                                 MsgBoxHelper2.Error($"鑰佸寲鐐� [{targetCellInfo.OvenName}] 涓嶅瓨鍦紝璇锋鏌ラ厤缃�");
                                 return;
                             }
+                            var sourceTable = TestResultView.tables[this.ovenIndex];
+                            if (sourceTable.title != targetCellInfo.OvenName)
+                            {
+                                MsgBoxHelper2.Error($"璇峰垏鎹㈣嚦 [{targetCellInfo.OvenName}] 鍖烘壂鐮侊紝褰撳墠涓� [{sourceTable.title}] 鍖猴紒");
+                                return;
+                            }
+
+                            var oven = TestResultView.nameTables[targetCellInfo.OvenName];
+                            if (oven.cells.Length < targetCellInfo.Index + 1)
+                            {
+                                MsgBoxHelper2.Error($"绌翠綅鍙� [{barcode}] 瓒呭嚭绱㈠紩 {targetCellInfo.Index}");
+                                return;
+                            }
+
+                            this.targetCellInfo = targetCellInfo;
                             positionText.Text = barcode;
                             //positionText.Focus();
                             //snrText.Text = "";
@@ -229,13 +244,14 @@
                     var EnableMes = ParameterManager.GetCommonParameterByName<bool>("EnableMes", false);
                     if (ParamSetting.CustomerCode == "LIXUN" && EnableMes)
                     {
-                        var table = TestResultView.tables[this.ovenIndex];
+                        var table = TestResultView.nameTables[targetCellInfo.OvenName];
+                        // TestResultView.tables[this.ovenIndex]
                         string currentPn = table.CbProduct.Text;
 
                         /// 妫�鏌ユ牸寮�
                         if (!SNValidator.ValidateSN(currentPn, snr, out string msg))
                         {
-                            TestResultView.tables[this.ovenIndex].GetSingleCell(targetCellInfo.Index).LightRed(true, out _);
+                            table.GetSingleCell(targetCellInfo.Index).LightRed(true, out _);
                             MsgBoxHelper2.Error($"妫�鏌ヤ骇鍝佹潯鐮佸紓甯革細{msg}");
                             snrText.Text = string.Empty;
                             snrText.Focus();
@@ -246,7 +262,7 @@
                         string stationName = ProductManager.GetParameterByProductAndName(currentPn, "MES绔欎綅")?.Value;
                         if (string.IsNullOrEmpty(stationName))
                         {
-                            TestResultView.tables[this.ovenIndex].GetSingleCell(targetCellInfo.Index).LightRed(true, out _);
+                            table.GetSingleCell(targetCellInfo.Index).LightRed(true, out _);
                             MsgBoxHelper2.Error($"妫�鏌ヤ骇鍝佹潯鐮佸紓甯革紝褰撳墠浜у搧[{currentPn}]鏈缃弬鏁癕ES绔欎綅");
                             snrText.Text = string.Empty;
                             snrText.Focus();
@@ -256,10 +272,10 @@
                         /// 妫�鏌N
                         if (PNUtil.GetPN(ParamSetting.MesAddress, stationName, snr, out string pn, out msg))
                         {
-                            
+
                             if (pn != currentPn)
                             {
-                                TestResultView.tables[this.ovenIndex].GetSingleCell(targetCellInfo.Index).LightRed(true, out _);
+                                table.GetSingleCell(targetCellInfo.Index).LightRed(true, out _);
                                 MsgBoxHelper2.Error($"褰撳墠娴嬭瘯鏂欏彿涓� [{currentPn}]锛屾壂鐮佷骇鍝佹枡鍙蜂负 [{pn}] 涓嶅厑璁告祴璇�");
                                 snrText.Text = string.Empty;
                                 snrText.Focus();
@@ -268,7 +284,7 @@
                         }
                         else
                         {
-                            TestResultView.tables[this.ovenIndex].GetSingleCell(targetCellInfo.Index).LightRed(true, out _);
+                            table.GetSingleCell(targetCellInfo.Index).LightRed(true, out _);
                             MsgBoxHelper2.Error($"妫�鏌ヤ骇鍝佹潯鐮佸紓甯革細{msg}");
                             snrText.Text = string.Empty;
                             snrText.Focus();
@@ -280,7 +296,7 @@
                         {
                             if (needCheckIn != true)
                             {
-                                TestResultView.tables[this.ovenIndex].GetSingleCell(targetCellInfo.Index).LightRed(true, out _);
+                                table.GetSingleCell(targetCellInfo.Index).LightRed(true, out _);
                                 MsgBoxHelper2.Error($"鏉$爜[{snr}]宸ュ簭涓嶅湪鏈珯锛歿msg}");
                                 snrText.Text = string.Empty;
                                 snrText.Focus();
@@ -289,7 +305,7 @@
                         }
                         else
                         {
-                            TestResultView.tables[this.ovenIndex].GetSingleCell(targetCellInfo.Index).LightRed(true, out _);
+                            table.GetSingleCell(targetCellInfo.Index).LightRed(true, out _);
                             MsgBoxHelper2.Error($"鏉$爜[{snr}]CheckIn澶辫触锛歿msg}");
                             snrText.Text = string.Empty;
                             snrText.Focus();
@@ -399,11 +415,11 @@
                     }
                     else
                     {
-                        if (ParamSetting.CustomerCode != "LIXUN")
-                        {
-                            snrText.Focus();
-                            return;
-                        }
+                        //if (ParamSetting.CustomerCode != "LIXUN")
+                        //{
+                        //    snrText.Focus();
+                        //    return;
+                        //}
                         string barcode = positionText.Text;
                         try
                         {
@@ -416,10 +432,21 @@
                                 targetCellInfo = null;
                                 return;
                             }
+
+                            var sourceTable = TestResultView.tables[this.ovenIndex];
+                            if (sourceTable.title != targetCellInfo.OvenName)
+                            {
+                                MsgBoxHelper2.Error($"璇峰垏鎹㈣嚦 [{targetCellInfo.OvenName}] 鍖烘壂鐮侊紝褰撳墠涓� [{sourceTable.title}] 鍖猴紒");
+                                positionText.Text = string.Empty;
+                                positionText.Focus();
+                                targetCellInfo = null;
+                                return;
+                            }
+
                             var oven = TestResultView.nameTables[targetCellInfo.OvenName];
                             if (oven.cells.Length < targetCellInfo.Index + 1)
                             {
-                                MsgBoxHelper2.Error($"绌翠綅涓嬫爣 [{targetCellInfo.Index}] 瓒呭嚭绱㈠紩");
+                                MsgBoxHelper2.Error($"绌翠綅鍙� [{barcode}] 瓒呭嚭绱㈠紩 {targetCellInfo.Index}");
                                 positionText.Text = string.Empty;
                                 positionText.Focus();
                                 targetCellInfo = null;
diff --git a/Plugin.TestResult/SingleCell.xaml.cs b/Plugin.TestResult/SingleCell.xaml.cs
index b9a6ab0..0e7a8b9 100644
--- a/Plugin.TestResult/SingleCell.xaml.cs
+++ b/Plugin.TestResult/SingleCell.xaml.cs
@@ -297,6 +297,17 @@
         private string visualTestId;
         private Sqlite3Helper helper;
 
+        private void InitCurrentTestHelper()
+        {
+            if (helper == null)
+            {
+                var now = DateTime.Now;
+                string dateStr = now.ToString("yyyyMMdd");
+                visualTestId = $"{title}_{now.ToString("yyyyMMddHHmmssfff")}";
+                helper = CheckSqlHelper(dateStr);
+            }
+        }
+
         public void SetStatus(string status)
         {
             Dispatcher.Invoke(() =>
@@ -306,11 +317,8 @@
                 if (oldStatus != "testing" && status == "testing")
                 {
                     UpdateProbeCount(ProbeCount + 1);
-                    var now = DateTime.Now;
-                    string dateStr = now.ToString("yyyyMMdd");
-                    visualTestId = $"{title}_{now.ToString("yyyyMMddHHmmssfff")}";
-                    helper = CheckSqlHelper(dateStr);
-                    InsertARecourd();
+                    InitCurrentTestHelper();
+                    InsertARecord();
                 }
 
                 if (oldStatus != "ok" && status == "ok")
@@ -325,7 +333,7 @@
             });
         }
 
-        private void InsertARecourd()
+        private void InsertARecord()
         {
             try
             {
@@ -336,7 +344,7 @@
             }
             catch (Exception ex)
             {
-                ViewControl.Logger.Error($"Cell[{title}] SaveToDB Error.", ex);
+                ViewControl.Logger.Error($"Cell[{title}] InsertARecord Error.", ex);
             }
         }
 
@@ -457,6 +465,7 @@
 
                     try
                     {
+                        InitCurrentTestHelper();
                         string model = table?.CbProduct?.SelectedItem?.ToString();
                         sql = $@"INSERT INTO serialData (sn,model,station,position,step,limitDown,limitUp,unit,value,status,finalStatus,time,resultId) 
 VALUES ('{sn}','{model}','','{this.title}','{info.step}','{info.limitDown}','{info.limitUp}','{info.unit}','{info.value}','{info.result}',
@@ -992,7 +1001,7 @@
         /// <param name="filePath"></param>
         public void SaveInfoToDB()
         {
-            Dispatcher.BeginInvoke(new Action(() =>
+            Dispatcher.Invoke(() =>
             {
                 try
                 {
@@ -1021,7 +1030,7 @@
                 {
                     ViewControl.Logger.Error($"Cell[{title}] SaveToDB Error.", ex);
                 }
-            }));
+            });
         }
 
         private void MenuItem_Click(object sender, RoutedEventArgs e)
@@ -1093,8 +1102,17 @@
 
         public bool LightGreen(bool on, out string msg)
         {
-            return S7Context.WriteData(RunTimeVariablePool.DEFAULT_PLC_NAME,
-                $"Oven{table.index + 1}_Cell{index + 1}_Light_OK", on, out msg);
+            string key = $"Oven{table.index + 1}_Cell{index + 1}_Light_OK";
+            bool res = S7Context.WriteData(RunTimeVariablePool.DEFAULT_PLC_NAME, key, on, out msg);
+            if (res)
+            {
+                ViewControl.Logger.Debug($"Set Light [{key}] {on}.");
+            }
+            else
+            {
+                ViewControl.Logger.Debug($"Open Light [{key}] {on} Error.{msg}");
+            }
+            return res;
         }
 
         public bool LightRed(bool on, out string msg)
diff --git a/Plugin.TestResult/TestResultView.xaml.cs b/Plugin.TestResult/TestResultView.xaml.cs
index 9182ea0..0d461da 100644
--- a/Plugin.TestResult/TestResultView.xaml.cs
+++ b/Plugin.TestResult/TestResultView.xaml.cs
@@ -2,13 +2,11 @@
 using HMI.Core.Manager;
 using HMI.Core.Util;
 using HMI.UI.Util;
-using NationalInstruments.TestStand.Interop.API;
 using NationalInstruments.TestStand.Interop.UI.Ax;
 using Plugin.TestResult_WPF.Model;
 using Plugin.TsSupport;
 using System;
 using System.Collections.Generic;
-using System.Runtime.InteropServices;
 using System.Windows;
 using System.Windows.Controls;
 
@@ -143,26 +141,19 @@
 
             InitializeComponent();
 
-            executionWrapper = new TsExecutionWrapper(); //new TsExecutionWrapper();
-            if (executionWrapper is TsExecutionWrapper tsWrapper)
+            
+        }
+
+        private void ExecutionWrapper_UIMessageEvent(object sender, UIMessageEvent e)
+        {
+            try
             {
-                tsWrapper.AxApplicationMgr.ExitApplication += this.axApplicationMgr_ExitApplication;
-                tsWrapper.AxApplicationMgr.UIMessageEvent += this.axApplicationMgr_UIMessageEvent;
-                tsWrapper.AxApplicationMgr.ExecutionClosed += this.axApplicationMgr_ExecutionClosed;
-                ((System.ComponentModel.ISupportInitialize)(tsWrapper.AxApplicationMgr)).BeginInit();//myClient涓虹涓夋柟鎺т欢鍚�
-                host.Child = tsWrapper.AxApplicationMgr;
-                ((System.ComponentModel.ISupportInitialize)(tsWrapper.AxApplicationMgr)).EndInit();
+                HandleUIMessage(e.EventCode, Convert.ToInt16(e.NumberData), e.StrData, e.Data);
             }
+            catch (Exception ex)
+            {
 
-
-
-            //testApp = TestAppManager.GetAxApplicationMgr();
-            //testApp.ExitApplication += this.axApplicationMgr_ExitApplication;
-            //testApp.UIMessageEvent += this.axApplicationMgr_UIMessageEvent;
-            //testApp.ExecutionClosed += this.axApplicationMgr_ExecutionClosed;
-            //((System.ComponentModel.ISupportInitialize)(this.testApp)).BeginInit();//myClient涓虹涓夋柟鎺т欢鍚�
-            //host.Child = testApp;
-            //((System.ComponentModel.ISupportInitialize)(this.testApp)).EndInit();
+            }
         }
 
         private void Refresh()
@@ -217,10 +208,26 @@
         /// <summary>
         /// 鍚姩娴嬭瘯搴忓垪
         /// </summary>
-        public void InitTestStandEngine()
+        public void InitTestStandEngine(bool isTap)
         {
             try
             {
+                if (isTap)
+                {
+                    executionWrapper = new TapWrapper();
+                }
+                else
+                {
+                    executionWrapper = new TsExecutionWrapper();
+                }
+                executionWrapper.UIMessageEvent += ExecutionWrapper_UIMessageEvent;
+
+                if (executionWrapper is TsExecutionWrapper tsWrapper)
+                {
+                    ((System.ComponentModel.ISupportInitialize)(tsWrapper.AxApplicationMgr)).BeginInit();
+                    host.Child = tsWrapper.AxApplicationMgr;
+                    ((System.ComponentModel.ISupportInitialize)(tsWrapper.AxApplicationMgr)).EndInit();
+                }
                 //this.engine = this.testApp.GetEngine();
                 //this.engine.PersistBreakpoints = false;
                 //this.testApp.IsEditor = true;
@@ -258,7 +265,7 @@
             try
             {
                 executionWrapper?.SetStationGlobalValue<bool>("StationState.StopTest", 0, true, true);
-                
+
                 try
                 {
                     executionWrapper?.WaitForExit();
@@ -284,12 +291,14 @@
             }
         }
 
+        [Obsolete]
         private void axApplicationMgr_ExitApplication(object sender, EventArgs e)
         {
             //Environment.ExitCode = testApp.ExitCode;
             //TSHelper.DoSynchronousGCForCOMObjectDestruction();
         }
 
+        [Obsolete]
         private void axApplicationMgr_ExecutionClosed(object sender, _ApplicationMgrEvents_ExecutionClosedEvent e)
         {
             try
@@ -303,31 +312,23 @@
             }
         }
 
-        private void axApplicationMgr_UIMessageEvent(object sender, _ApplicationMgrEvents_UIMessageEventEvent e)
+
+        private void HandleUIMessage(int EventCase, int MethodCode, string strData, object xData)
         {
             try
             {
-                int EventCase = Convert.ToInt32(e.uiMsg.Event);
-                int MethodCode = Convert.ToInt16(e.uiMsg.NumericData);
-                string strData;
-
                 if (EventCase != 10014) return;
 
                 if (MethodCode == 0)
                 {
                     /// 璁剧疆鍙ユ焺
-                    //this.SequenceContext = (SequenceContext)e.uiMsg.ActiveXData;
-                    if (executionWrapper is TsExecutionWrapper tsWrapper)
-                    {
-                        tsWrapper.context = (SequenceContext)e.uiMsg.ActiveXData;
-                    }
+                    executionWrapper.SetContext(xData);
                     return;
                 }
 
                 if (MethodCode == 1)
                 {
                     /// 鏇存柊鐐夊瓙鐘舵��
-                    strData = e.uiMsg.StringData;
                     string[] datas_10014 = strData.Split(';');
                     int ovenIndex = int.Parse(datas_10014[0]);
 
@@ -338,7 +339,6 @@
                 if (MethodCode == 2)
                 {
                     /// 鏇存柊鐐夊瓙鏁伴噺淇℃伅
-                    strData = e.uiMsg.StringData;
                     string[] datas_10014 = strData.Split(';');
                     int ovenIndex = int.Parse(datas_10014[0]);
 
@@ -351,7 +351,6 @@
                 if (MethodCode == 3)
                 {
                     /// 鏇存柊鐐夊瓙鏃堕棿淇℃伅
-                    strData = e.uiMsg.StringData;
                     string[] datas_10014 = strData.Split(';');
                     int ovenIndex = int.Parse(datas_10014[0]);
                     string startTime = datas_10014[1];
@@ -372,7 +371,6 @@
                 if (MethodCode == 4)
                 {
                     /// 鏇存柊鑰佸寲鐐夊簳閮ㄧ姸鎬佹爮
-                    strData = e.uiMsg.StringData;
                     string[] datas_10014 = strData.Split(';');
                     int ovenIndex = int.Parse(datas_10014[0]);
 
@@ -383,7 +381,6 @@
                 if (MethodCode == 5)
                 {
                     /// 閲囬泦鐐夋俯搴�
-                    strData = e.uiMsg.StringData;
                     string[] datas_10016 = strData.Split(';');
                     int ovenIndex = int.Parse(datas_10016[0]);
 
@@ -399,29 +396,28 @@
                 }
                 if (MethodCode == 6)
                 {
-                    /// 鏇存柊绌翠綅鐘舵��
-                    strData = e.uiMsg.StringData;
-                    string[] datas_10015 = strData.Split(';');
-                    int ovenIndex = int.Parse(datas_10015[0]);
-                    int cellIndex = int.Parse(datas_10015[1]);
-                    string status_10015 = datas_10015[2];
-                    tables[ovenIndex].GetSingleCell(cellIndex).SetStatus(status_10015);
+                    Dispatcher.Invoke(() =>
+                    {/// 鏇存柊绌翠綅鐘舵��
+                        string[] datas_10015 = strData.Split(';');
+                        int ovenIndex = int.Parse(datas_10015[0]);
+                        int cellIndex = int.Parse(datas_10015[1]);
+                        string status_10015 = datas_10015[2];
+                        tables[ovenIndex].GetSingleCell(cellIndex).SetStatus(status_10015);
 
-                    if (status_10015 == "OK" || status_10015 == "ok")
-                    {
-                        tables[ovenIndex].RefreshCount(1, 0);
-                    }
-                    else if (status_10015 == "ng" || status_10015 == "NG")
-                    {
-                        tables[ovenIndex].RefreshCount(0, 1);
-                    }
-
+                        if (status_10015 == "OK" || status_10015 == "ok")
+                        {
+                            tables[ovenIndex].RefreshCount(1, 0);
+                        }
+                        else if (status_10015 == "ng" || status_10015 == "NG")
+                        {
+                            tables[ovenIndex].RefreshCount(0, 1);
+                        }
+                    });
                     return;
                 }
                 if (MethodCode == 7)
                 {
                     /// 鏇存柊绌翠綅鍙傛暟
-                    strData = e.uiMsg.StringData;
                     string[] datas_10015 = strData.Split(';');
                     int ovenIndex = int.Parse(datas_10015[0]);
                     int cellIndex = int.Parse(datas_10015[1]);
@@ -446,7 +442,6 @@
                 if (MethodCode == 10)
                 {
                     /// 娓呯┖鐐夊瓙鐨勬俯搴﹀浘琛�
-                    strData = e.uiMsg.StringData;
                     int ovenIndex = int.Parse(strData);
                     tables[ovenIndex].ClearChartData();
                     return;
@@ -454,7 +449,6 @@
                 if (MethodCode == 11)
                 {
                     /// 娓呴櫎绌翠綅鍙傛暟
-                    strData = e.uiMsg.StringData;
                     string[] datas_10015 = strData.Split(';');
                     int ovenIndex = int.Parse(datas_10015[0]);
                     int cellIndex = int.Parse(datas_10015[1]);
@@ -465,7 +459,6 @@
                 if (MethodCode == 12)
                 {
                     /// 淇濆瓨鐐変綋娓╁害鏂囦欢
-                    strData = e.uiMsg.StringData;
                     string[] datas = strData.Split(';');
                     int ovenIndex = int.Parse(datas[0]);
                     string folder = datas[1];
@@ -475,7 +468,6 @@
                 if (MethodCode == 13)
                 {
                     /// 娣诲姞鐢垫祦鏁版嵁
-                    strData = e.uiMsg.StringData;
                     string[] datas = strData.Split(';');
                     int ovenIndex = int.Parse(datas[0]);
                     int cellIndex = int.Parse(datas[1]);
@@ -487,7 +479,6 @@
                 if (MethodCode == 14)
                 {
                     /// 娓呯┖鐢垫祦鍜岀數鍘嬫暟鎹�
-                    strData = e.uiMsg.StringData;
                     int ovenIndex = int.Parse(strData);
                     tables[ovenIndex].ClearCellData();
                     //tables[ovenIndex].ClearElectricData();
@@ -499,7 +490,6 @@
                 if (MethodCode == 15)
                 {
                     /// 淇濆瓨鐢垫祦鏁版嵁
-                    strData = e.uiMsg.StringData;
                     string[] datas = strData.Split(';');
                     int ovenIndex = int.Parse(datas[0]);
                     int cellIndex = int.Parse(datas[1]);
@@ -511,7 +501,6 @@
                 if (MethodCode == 16)
                 {
                     /// 娣诲姞鐢靛帇鍥捐〃鏁版嵁
-                    strData = e.uiMsg.StringData;
                     string[] datas = strData.Split(';');
                     int ovenIndex = int.Parse(datas[0]);
                     int cellIndex = int.Parse(datas[1]);
@@ -523,7 +512,6 @@
                 if (MethodCode == 17)
                 {
                     /// 淇濆瓨鐢靛帇鏁版嵁
-                    strData = e.uiMsg.StringData;
                     string[] datas = strData.Split(';');
                     int ovenIndex = int.Parse(datas[0]);
                     int cellIndex = int.Parse(datas[1]);
@@ -535,7 +523,6 @@
                 if (MethodCode == 18)
                 {
                     /// 淇濆瓨绌翠綅娴嬭瘯鏁版嵁鍒癈SV
-                    strData = e.uiMsg.StringData;
                     string[] datas = strData.Split(';');
                     int ovenIndex = int.Parse(datas[0]);
                     int cellIndex = int.Parse(datas[1]);
@@ -546,7 +533,6 @@
                 if (MethodCode == 180)
                 {
                     /// 淇濆瓨绌翠綅娴嬭瘯鏁版嵁鍒癈SV
-                    strData = e.uiMsg.StringData;
                     string[] datas = strData.Split(';');
                     int ovenIndex = int.Parse(datas[0]);
                     int cellIndex = int.Parse(datas[1]);
@@ -558,7 +544,6 @@
                 if (MethodCode == 19)
                 {
                     /// 淇濆瓨绌翠綅娴嬭瘯鏁版嵁鍒癝qlite
-                    strData = e.uiMsg.StringData;
                     string[] datas = strData.Split(';');
                     int ovenIndex = int.Parse(datas[0]);
                     int cellIndex = int.Parse(datas[1]);
@@ -569,7 +554,6 @@
                 if (MethodCode == 20)
                 {
                     /// 娓呯┖鏉$爜
-                    strData = e.uiMsg.StringData;
                     int ovenIndex = int.Parse(strData);
                     tables[ovenIndex].ClearAllSN();
                     return;
@@ -578,7 +562,6 @@
                 if (MethodCode == 21)
                 {
                     /// 娣诲姞姘存祦鍥捐〃鏁版嵁
-                    strData = e.uiMsg.StringData;
                     string[] datas = strData.Split(';');
                     int ovenIndex = int.Parse(datas[0]);
                     int cellIndex = int.Parse(datas[1]);
@@ -591,7 +574,6 @@
 
                 if (MethodCode == 99)
                 {
-                    strData = e.uiMsg.StringData;
                     string[] datas = strData.Split(';');
                     int index = int.Parse(datas[0]);
                     if (MsgBoxHelper2.WarnChoose(datas[1], "鎻愮ず") == MessageBoxResult.OK)
@@ -613,6 +595,14 @@
             }
         }
 
+        private void axApplicationMgr_UIMessageEvent(object sender, _ApplicationMgrEvents_UIMessageEventEvent e)
+        {
+            int EventCase = Convert.ToInt32(e.uiMsg.Event);
+            int MethodCode = Convert.ToInt16(e.uiMsg.NumericData);
+            string strData = e.uiMsg.StringData;
+            HandleUIMessage(EventCase, MethodCode, strData, e.uiMsg.ActiveXData);
+        }
+
         public void SetStartFlag(int index, bool data)
         {
             string name = tables[index].title;
@@ -620,28 +610,11 @@
             string key = $"StationState.Start[{index}]";
 
             executionWrapper?.SetStationGlobalValue<bool>(key, 1, data);
-
-            if (data == false)
-            {
-                key = $"StationState.Scanner[{index}]";
-                executionWrapper?.SetStationGlobalValue<bool>(key, 1, false);
-            }
-        }
-
-        public bool? GetStartFlag(int index)
-        {
-            string key = $"StationState.Start[{index}]";
-            return executionWrapper.GetStationGlobalValue<bool>(key, 0);
         }
 
         public void ReleaseResource()
         {
-            if (executionWrapper is TsExecutionWrapper tsWrapper)
-            {
-                tsWrapper.AxApplicationMgr.ExitApplication -= this.axApplicationMgr_ExitApplication;
-                tsWrapper.AxApplicationMgr.UIMessageEvent -= this.axApplicationMgr_UIMessageEvent;
-                tsWrapper.AxApplicationMgr.ExecutionClosed -= this.axApplicationMgr_ExecutionClosed;
-            }
+            executionWrapper.UIMessageEvent -= ExecutionWrapper_UIMessageEvent;
 
             foreach (var table in tables)
             {
diff --git a/Plugin.TestResult/ViewControl.xaml.cs b/Plugin.TestResult/ViewControl.xaml.cs
index d7e2f96..ebff038 100644
--- a/Plugin.TestResult/ViewControl.xaml.cs
+++ b/Plugin.TestResult/ViewControl.xaml.cs
@@ -112,17 +112,62 @@
             timer.Elapsed += Timer_Elapsed;
             InitializeComponent();
 
-            if (testResultView.executionWrapper is TsExecutionWrapper tsWrapper)
-            {
-                tsWrapper.AxApplicationMgr.ExitApplication += new EventHandler(this.axApplicationMgr_ExitApplication);
-                tsWrapper.AxApplicationMgr.UIMessageEvent += new _ApplicationMgrEvents_UIMessageEventEventHandler(this.axApplicationMgr_UIMessageEvent);
-                tsWrapper.AxApplicationMgr.ExecutionClosed += new _ApplicationMgrEvents_ExecutionClosedEventHandler(this.axApplicationMgr_ExecutionClosed);
-            }
-
 
             //testResultView.testApp.ExitApplication += new EventHandler(this.axApplicationMgr_ExitApplication);
             //testResultView.testApp.UIMessageEvent += new _ApplicationMgrEvents_UIMessageEventEventHandler(this.axApplicationMgr_UIMessageEvent);
             //testResultView.testApp.ExecutionClosed += new _ApplicationMgrEvents_ExecutionClosedEventHandler(this.axApplicationMgr_ExecutionClosed);
+        }
+
+        private void HandleUIMessage(int EventCase, int MethodCode, string strData, object xData)
+        {
+            try
+            {
+                try
+                {
+                    log.Debug($"EventCase: {EventCase}, MethodCode: {MethodCode}, StrData: {strData}");
+                }
+                catch
+                {
+
+                }
+
+                if (EventCase == 8)
+                {
+                    IsRunning = false;
+                    return;
+                }
+
+                if (EventCase != 10014) return;
+
+                if (MethodCode == 0)
+                {
+                    /// 璁剧疆鍙ユ焺
+                    this.SequenceContext = (SequenceContext)xData;
+                    return;
+                }
+
+
+                if (MethodCode == 8)
+                {
+                    /// 鏇存柊MES鐘舵��
+                    mesStatus = strData == "1";
+                    return;
+                }
+
+                if (MethodCode == 9)
+                {
+                    /// 鏇存柊鐢熶骇鐘舵��
+                    produceStatus = strData;
+                    return;
+                }
+            }
+            catch
+            { }
+        }
+
+        private void ExecutionWrapper_UIMessageEvent(object sender, UIMessageEvent e)
+        {
+            HandleUIMessage(e.EventCode, Convert.ToInt16(e.NumberData), e.StrData, e.Data);
         }
 
         private void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
@@ -142,7 +187,6 @@
             string path = Path.Combine(BaseFolder, "Count.ini");
             testResultView.SetCountIniPath(path);
             testResultView.InitMainGrid();
-            testResultView.InitTestStandEngine();
 
             path = Path.Combine(BaseFolder, "Scanner.ini");
             if (!File.Exists(path))
@@ -174,6 +218,12 @@
                 testResultView.testSeqFile = string.Empty;
                 FileName = string.Empty;
             }
+            string type = Path.GetExtension(FileName);
+            bool isTap = string.Equals(".tapplan", type, StringComparison.OrdinalIgnoreCase);
+
+            testResultView.InitTestStandEngine(isTap);
+            testResultView.executionWrapper.UIMessageEvent += ExecutionWrapper_UIMessageEvent;
+
             RefreshUIConfig();
         }
 
@@ -204,67 +254,6 @@
             {
                 log.Error("RefreshUIConfig Error.", ex);
             }
-        }
-
-        private void axApplicationMgr_ExitApplication(object sender, EventArgs e)
-        {
-        }
-
-        private void axApplicationMgr_ExecutionClosed(object sender, _ApplicationMgrEvents_ExecutionClosedEvent e)
-        {
-        }
-
-        private void axApplicationMgr_UIMessageEvent(object sender, _ApplicationMgrEvents_UIMessageEventEvent e)
-        {
-            try
-            {
-                int EventCase = Convert.ToInt32(e.uiMsg.Event);
-                int MethodCode = Convert.ToInt16(e.uiMsg.NumericData);
-                string strData;
-
-                try
-                {
-                    log.Debug($"EventCase: {EventCase}, MethodCode: {MethodCode}, StrData: {e.uiMsg.StringData}");
-                }
-                catch
-                {
-
-                }
-
-                if (EventCase == 8)
-                {
-                    IsRunning = false;
-                    return;
-                }
-
-                if (EventCase != 10014) return;
-
-                if (MethodCode == 0)
-                {
-                    /// 璁剧疆鍙ユ焺
-                    this.SequenceContext = (SequenceContext)e.uiMsg.ActiveXData;
-                    return;
-                }
-
-
-                if (MethodCode == 8)
-                {
-                    /// 鏇存柊MES鐘舵��
-                    strData = e.uiMsg.StringData;
-                    mesStatus = strData == "1";
-                    return;
-                }
-
-                if (MethodCode == 9)
-                {
-                    /// 鏇存柊鐢熶骇鐘舵��
-                    strData = e.uiMsg.StringData;
-                    produceStatus = strData;
-                    return;
-                }
-            }
-            catch
-            { }
         }
 
         private bool isLoaded;
diff --git a/Plugin.TsExecution/Plugin.TsExecution.csproj b/Plugin.TsExecution/Plugin.TsExecution.csproj
index ef4aad0..e63a2e4 100644
--- a/Plugin.TsExecution/Plugin.TsExecution.csproj
+++ b/Plugin.TsExecution/Plugin.TsExecution.csproj
@@ -88,6 +88,10 @@
       <SpecificVersion>False</SpecificVersion>
       <HintPath>..\Libs\NationalInstruments.TestStand.Interop.UI.AxControls.dll</HintPath>
     </Reference>
+    <Reference Include="NPOI, Version=2.4.1.0, Culture=neutral, PublicKeyToken=0df73ec7942b34e1, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>..\HMI.ParameterManage\bin\x64\Debug\NPOI.dll</HintPath>
+    </Reference>
     <Reference Include="OpenTK, Version=3.3.1.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4, processorArchitecture=MSIL">
       <HintPath>..\WPFProject\packages\OpenTK.3.3.1\lib\net20\OpenTK.dll</HintPath>
     </Reference>
@@ -264,6 +268,10 @@
       <Project>{f8b8b6b6-821b-4ff6-b556-9cab840ea57a}</Project>
       <Name>HMI.MQUtil</Name>
     </ProjectReference>
+    <ProjectReference Include="..\SimpleUtil\HMI.Simple.Util\HMI.Simple.Util.csproj">
+      <Project>{CF5BEBE9-2803-49C3-B773-73D082E0984A}</Project>
+      <Name>HMI.Simple.Util</Name>
+    </ProjectReference>
   </ItemGroup>
   <ItemGroup>
     <WCFMetadata Include="Connected Services\" />
diff --git a/Plugin.TsExecution/Properties/AssemblyInfo.cs b/Plugin.TsExecution/Properties/AssemblyInfo.cs
index 8c79373..87940bc 100644
--- a/Plugin.TsExecution/Properties/AssemblyInfo.cs
+++ b/Plugin.TsExecution/Properties/AssemblyInfo.cs
@@ -32,13 +32,13 @@
 //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
 
 
-[assembly:ThemeInfo(
+[assembly: ThemeInfo(
     ResourceDictionaryLocation.None, //涓婚鐗瑰畾璧勬簮璇嶅吀鎵�澶勪綅缃�
-                             //(鏈湪椤甸潰涓壘鍒拌祫婧愭椂浣跨敤锛�
-                             //鎴栧簲鐢ㄧ▼搴忚祫婧愬瓧鍏镐腑鎵惧埌鏃朵娇鐢�)
+                                     //(鏈湪椤甸潰涓壘鍒拌祫婧愭椂浣跨敤锛�
+                                     //鎴栧簲鐢ㄧ▼搴忚祫婧愬瓧鍏镐腑鎵惧埌鏃朵娇鐢�)
     ResourceDictionaryLocation.SourceAssembly //甯歌璧勬簮璇嶅吀鎵�澶勪綅缃�
-                                      //(鏈湪椤甸潰涓壘鍒拌祫婧愭椂浣跨敤锛�
-                                      //銆佸簲鐢ㄧ▼搴忔垨浠讳綍涓婚涓撶敤璧勬簮瀛楀吀涓壘鍒版椂浣跨敤)
+                                              //(鏈湪椤甸潰涓壘鍒拌祫婧愭椂浣跨敤锛�
+                                              //銆佸簲鐢ㄧ▼搴忔垨浠讳綍涓婚涓撶敤璧勬簮瀛楀吀涓壘鍒版椂浣跨敤)
 )]
 
 
@@ -49,6 +49,6 @@
 //      鐢熸垚鍙�
 //      淇鍙�
 //
-[assembly: AssemblyVersion("1.0.0.3")]
-[assembly: AssemblyFileVersion("1.0.0.3")]
+[assembly: AssemblyVersion("1.0.0.7")]
+[assembly: AssemblyFileVersion("1.0.0.7")]
 [assembly: PluginClassName("Plugin.TsExecution.TsExecution")]
diff --git a/Plugin.TsExecution/ScreenTouch.xaml b/Plugin.TsExecution/ScreenTouch.xaml
index f5ee1f2..fb7d5fb 100644
--- a/Plugin.TsExecution/ScreenTouch.xaml
+++ b/Plugin.TsExecution/ScreenTouch.xaml
@@ -8,7 +8,7 @@
              xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI" 
              xmlns:hb="clr-namespace:HMI.Base;assembly=HMI.Base" 
              d:DesignHeight="450" d:DesignWidth="800">
-    <UniformGrid x:Name="uniform" dp:UniformGridHelper.InnerBorder="0.3" Background="LightGray"
-                 dp:UniformGridHelper.InnerBorderBrush="Transparent">
+    <UniformGrid x:Name="uniform" dp:UniformGridHelper.InnerBorder="0.5"
+                 dp:UniformGridHelper.InnerBorderBrush="#37375f">
     </UniformGrid>
 </hb:NotifyControl>
diff --git a/Plugin.TsExecution/ScreenTouch.xaml.cs b/Plugin.TsExecution/ScreenTouch.xaml.cs
index 431f740..4cf1f19 100644
--- a/Plugin.TsExecution/ScreenTouch.xaml.cs
+++ b/Plugin.TsExecution/ScreenTouch.xaml.cs
@@ -1,4 +1,5 @@
 锘縰sing HMI.Base;
+using HMI.UI.DpUtil;
 using System;
 using System.Collections.ObjectModel;
 using System.Globalization;
@@ -108,6 +109,7 @@
                 children[i] = border;
                 uniform.Children.Add(border);
             }
+            UniformGridHelper.RefreshGridInnerBorder(uniform);
         }
 
         public void Refresh(int rows, int columns)
@@ -140,6 +142,25 @@
             Values = temp;
         }
 
+        public void DrawNewPoints(int[][] points)
+        {
+            var temp = Values == null ?
+                new ObservableCollection<bool>(new bool[rows * columns]) :
+                new ObservableCollection<bool>(Values);
+            for (int i = 0; i < points.Length; i++)
+            {
+                int[] point = points[i];
+                if (point == null)
+                {
+                    continue;
+                }
+                int index = point[1] * columns + point[0];
+                //children[point[0]][point[1]].Background = Brushes.Green;
+                temp[index] = true;
+            }
+            Values = temp;
+        }
+
         private void PaintPoints()
         {
             for (int i = 0; i < Values.Count; i++)
diff --git a/Plugin.TsExecution/SingleControl.xaml b/Plugin.TsExecution/SingleControl.xaml
index 8a31373..16720c0 100644
--- a/Plugin.TsExecution/SingleControl.xaml
+++ b/Plugin.TsExecution/SingleControl.xaml
@@ -125,8 +125,7 @@
                 </Label>
 
                 <Label Content="{Binding Status}" VerticalAlignment="Center" VerticalContentAlignment="Center"
-                       Padding="0"
-                       Margin="0"
+                       Padding="0" Margin="0"
                        HorizontalContentAlignment="Center"
                        Foreground="#FFF"
                        FontWeight="Bold"
diff --git a/Plugin.TsExecution/SingleControl.xaml.cs b/Plugin.TsExecution/SingleControl.xaml.cs
index 3e3c21a..373fcd4 100644
--- a/Plugin.TsExecution/SingleControl.xaml.cs
+++ b/Plugin.TsExecution/SingleControl.xaml.cs
@@ -1,14 +1,17 @@
 锘縰sing HMI.Core.Entity;
 using HMI.Core.Manager;
+using HMI.Core.Setting;
 using HMI.Core.Util;
 using HMI.Util;
 using NationalInstruments.TestStand.Interop.API;
+using NPOI.HSSF.UserModel;
+using NPOI.SS.UserModel;
 using Plugin.TsExecution.Entity;
 using System;
+using System.Collections.Generic;
 using System.Collections.ObjectModel;
 using System.ComponentModel;
 using System.IO;
-using System.Linq;
 using System.Runtime.CompilerServices;
 using System.Timers;
 using System.Windows;
@@ -243,7 +246,7 @@
                 Interval = 100
             };
             refreshTimer.Elapsed += RefreshTimer_Elapsed;
-            ReadProbeCount();
+            RefreshProbeCount(null);
             ReadCounter();
             InitializeComponent();
             try
@@ -397,37 +400,6 @@
             this.WriteCounter();
         }
 
-        /// <summary>
-        /// 浠巌ni璇诲彇鎺㈤拡浣跨敤娆℃暟
-        /// </summary>
-        private void ReadProbeCount()
-        {
-            try
-            {
-                var num = IniFileHelper.ReadIniData("Socket" + this.index.ToString(), "ProbeCount", "0", this.CountIniPath);
-                this.ProbeCount = int.Parse(num);
-            }
-            catch (Exception ex)
-            {
-                this.ProbeCount = 0;
-            }
-        }
-
-        /// <summary>
-        /// 鏇存柊鎺㈤拡浣跨敤娆℃暟
-        /// </summary>
-        /// <param name="count"></param>
-        private void UpdateProbeCount(int count)
-        {
-            /// 鍐欏埌閰嶇疆鏂囦欢
-            IniFileHelper.WriteIniData("Socket" + this.index.ToString(), "ProbeCount", count.ToString(), this.CountIniPath);
-            ProbeCount = count;
-
-            //if (ProbeWarn <= 0) probeCountAlarm.Visibility = Visibility.Collapsed;
-            //else if (count >= ProbeWarn) probeCountAlarm.Visibility = Visibility.Visible;
-            //else probeCountAlarm.Visibility = Visibility.Collapsed;
-        }
-
         private void WriteCounter()
         {
             IniFileHelper.WriteIniData("Socket" + this.index.ToString(), "Pass",
@@ -456,9 +428,48 @@
             this.UpdateTestCounter(null);
         }
 
+        public void WriteToExcel(string path)
+        {
+            Dispatcher.Invoke(() =>
+            {
+                try
+                {
+                    string suffix = Path.GetExtension(path);
+                    if (!string.Equals(".xlsx", suffix, StringComparison.OrdinalIgnoreCase) &&
+                    !string.Equals(".xls", suffix, StringComparison.OrdinalIgnoreCase))
+                    {
+                        path = path.Replace(suffix, ".xls");
+                    }
+
+                    FileInfo fi = new FileInfo(path);
+                    if (!fi.Directory.Exists)
+                    {
+                        fi.Directory.Create();  //鏂囦欢涓嶅瓨鍦紝鍒涘缓鏂囦欢
+                    }
+                    List<string[]> rows = new List<string[]>();
+                    rows.Add(new string[] { "娴嬭瘯鏃堕棿", "娴嬭瘯浜�", "娴嬭瘯璁惧", "鏉″舰鐮�", "娴嬭瘯缁撴灉" });
+                    rows.Add(new string[] { DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), UserInfo.User.Name, ParamSetting.StationName, Barcode, Status });
+                    rows.Add(new string[] { "椤圭洰鍚嶇О", "娴嬭瘯鍙傛暟", "娴嬭瘯鍊�", "涓婇檺", "涓嬮檺", "鍗曚綅", "缁撴灉" });
+                    if (Datasource != null && Datasource.Count > 0)
+                    {
+                        foreach (var data in Datasource)
+                        {
+                            rows.Add(new string[] { data.Name, data.Step, data.Value, data.LimitUp, data.LimitDown, data.Unit, data.Result });
+                        }
+                    }
+                    ExcelHelper.ExportExcel(path, rows);
+                }
+                catch (Exception ex)
+                {
+                    TsExecution.logger.Error("Write CSV File Failed.", ex);
+                }
+            });
+        }
+
+
         public void WriteToCSV(string path)
         {
-            Dispatcher.BeginInvoke(new Action(() =>
+            Dispatcher.Invoke(() =>
             {
                 try
                 {
@@ -488,7 +499,7 @@
                 {
                     TsExecution.logger.Error("Write CSV File Failed.", ex);
                 }
-            }));
+            });
         }
 
         public void ShowPausePanel()
@@ -534,17 +545,7 @@
 
         private void SendSnr2Ts()
         {
-            //try
-            //{
-            //    string snr = TbManualSnr.Text;
-            //    SequenceFileContext?.StationGlobals.SetValString($"StationState.SN[{index}]", 1, snr);
-            //    scannerPanel.Visibility = Visibility.Collapsed;
-            //    TbManualSnr.Text = string.Empty;
-            //}
-            //catch (Exception ex)
-            //{
-            //    ToastHelper.Error(ex.Message);
-            //}
+           
         }
 
         private void pausePanel_Loaded(object sender, RoutedEventArgs e)
@@ -605,16 +606,59 @@
             screens[index].DrawPoint(x, y);
         }
 
+        /// <summary>
+        /// 缁樺浘涔嬪墠娓呯┖宸叉湁鐨�
+        /// </summary>
+        /// <param name="index"></param>
+        /// <param name="points"></param>
         public void DrawScreenPoints(int index, int[][] points)
         {
             screens[index].DrawPoints(points);
         }
 
+        /// <summary>
+        /// 缁樺浘涔嬪墠涓嶆竻绌哄凡鏈夌殑
+        /// </summary>
+        /// <param name="index"></param>
+        /// <param name="points"></param>
+        public void DrawNewScreenPoints(int index, int[][] points)
+        {
+            screens[index].DrawNewPoints(points);
+        }
+
         public void ClearScreenPoint(int index)
         {
             screens[index].Clear();
         }
 
+        public void RefreshProbeCount(ProductData product)
+        {
+            try
+            {
+                var num = IniFileHelper.ReadIniData($"Socket{this.index.ToString()}", "ProbeCount", "0", this.CountIniPath);
+                this.ProbeCount = int.Parse(num);
+            }
+            catch (Exception ex)
+            {
+                this.ProbeCount = 0;
+            }
+        }
+
+        /// <summary>
+        /// 鏇存柊鎺㈤拡浣跨敤娆℃暟
+        /// </summary>
+        /// <param name="count"></param>
+        private void UpdateProbeCount(int count)
+        {
+            /// 鍐欏埌閰嶇疆鏂囦欢
+            IniFileHelper.WriteIniData("Socket" + this.index.ToString(), "ProbeCount", count.ToString(), this.CountIniPath);
+            ProbeCount = count;
+
+            //if (ProbeWarn <= 0) probeCountAlarm.Visibility = Visibility.Collapsed;
+            //else if (count >= ProbeWarn) probeCountAlarm.Visibility = Visibility.Visible;
+            //else probeCountAlarm.Visibility = Visibility.Collapsed;
+        }
+
         public void RefreshProduct(ProductData product)
         {
             if (product == null)
diff --git a/Plugin.TsExecution/TsExecution.xaml.cs b/Plugin.TsExecution/TsExecution.xaml.cs
index 4578fdb..9575fb8 100644
--- a/Plugin.TsExecution/TsExecution.xaml.cs
+++ b/Plugin.TsExecution/TsExecution.xaml.cs
@@ -6,11 +6,13 @@
 using HMI.Core.Setting;
 using HMI.Core.Util;
 using HMI.MQUtil;
+using HMI.Simple.Util;
 using HMI.UI.Util;
 using HMI.Util;
 using log4net;
 using NationalInstruments.TestStand.Interop.API;
-using NationalInstruments.TestStand.Interop.UI.Ax;
+using NPOI.HSSF.UserModel;
+using NPOI.SS.UserModel;
 using Plugin.TsSupport;
 using ScottPlot;
 using ScottPlot.Plottables;
@@ -20,8 +22,6 @@
 using System.ComponentModel;
 using System.IO;
 using System.Linq;
-using System.Runtime.InteropServices;
-using System.Timers;
 using System.Windows;
 using System.Windows.Controls;
 using System.Windows.Forms.DataVisualization.Charting;
@@ -37,24 +37,23 @@
         /// <summary>
         /// TS 寮曟搸
         /// </summary>
-        private Engine engine = null;
+        //private Engine engine = null;
+
+        ///// <summary>
+        ///// 娴嬭瘯搴忓垪
+        ///// </summary>
+        //private SequenceFile testSeq = null;
+
+        ///// <summary>
+        ///// 娴嬭瘯妯″瀷搴忓垪
+        ///// </summary>
+        //private SequenceFile modeSeq = null;
+
+        //private Execution execution = null;
 
         private string testSeqFile = string.Empty;
-        /// <summary>
-        /// 娴嬭瘯搴忓垪
-        /// </summary>
-        private SequenceFile testSeq = null;
 
-        /// <summary>
-        /// 娴嬭瘯妯″瀷搴忓垪
-        /// </summary>
-        private SequenceFile modeSeq = null;
-        public AxApplicationMgr AxApplicationMgr;
-        private Execution execution = null;
-
-        private SequenceContext sequenceContext = null;
-
-        private Chart chart;
+        private ITestExecutionWrapper testExecutionWrapper;
         #endregion
 
         public static ILog logger { get; private set; }
@@ -83,7 +82,6 @@
 
 
         private bool isLoadOK = false;
-        private Timer GCTimer;
 
         private int PassAmount = 0;
         private int FailAmount = 0;
@@ -109,6 +107,7 @@
                     foreach (SingleControl socket in tsSockets)
                     {
                         socket.RefreshProduct(value);
+                        // socket.RefreshProbeCount(value);
                     }
                 }
                 SetTestSeqFile(value);
@@ -182,9 +181,12 @@
             get => _enableCanLog;
             set
             {
-                if (WriteConfig("EnableCanLog", value))
+                if (RequirePassword() == true)
                 {
-                    Set(ref _enableCanLog, value);
+                    if (WriteConfig("EnableCanLog", value))
+                    {
+                        Set(ref _enableCanLog, value);
+                    }
                 }
             }
         }
@@ -227,10 +229,13 @@
             get => _enableSimpleMode;
             set
             {
-                if (WriteConfig("EnableSimpleMode", value))
+                if (RequirePassword() == true)
                 {
-                    Set(ref _enableSimpleMode, value);
-                    foreach (var cell in tsSockets) cell.SetSimpleUI(value);
+                    if (WriteConfig("EnableSimpleMode", value))
+                    {
+                        Set(ref _enableSimpleMode, value);
+                        foreach (var cell in tsSockets) cell.SetSimpleUI(value);
+                    }
                 }
             }
         }
@@ -241,10 +246,13 @@
             get => _ui_showInfoDock;
             set
             {
-                if (WriteConfig("UI_ShowInfoDock", value))
+                if (RequirePassword() == true)
                 {
-                    Set(ref _ui_showInfoDock, value);
-                    detailPanel.Visibility = value ? Visibility.Visible : Visibility.Collapsed;
+                    if (WriteConfig("UI_ShowInfoDock", value))
+                    {
+                        Set(ref _ui_showInfoDock, value);
+                        detailPanel.Visibility = value ? Visibility.Visible : Visibility.Collapsed;
+                    }
                 }
             }
         }
@@ -268,8 +276,8 @@
                 if (WriteIni("UI_CurrentWorkorder", value))
                 {
                     Set(ref currentWorkorder, value);
-                    engine?.Globals.SetValString("StationState.WorkOrder", 1, value);
-                    engine?.CommitGlobalsToDisk(promptOnSaveConflicts: false);
+                    testExecutionWrapper?.SetStationGlobalValue("StationState.WorkOrder", 1, value);
+                    testExecutionWrapper?.CommitToDisk(false);
                 }
             }
         }
@@ -280,9 +288,12 @@
             get => _DisableScanner;
             set
             {
-                if (WriteConfig("DisableScanner", value))
+                if (RequirePassword() == true)
                 {
-                    Set(ref _DisableScanner, value);
+                    if (WriteConfig("DisableScanner", value))
+                    {
+                        Set(ref _DisableScanner, value);
+                    }
                 }
             }
         }
@@ -297,13 +308,10 @@
         public TsExecution()
         {
             DataContext = this;
-            GCTimer = new Timer();
             InitializeComponent();
 
             InitChart();
             InitTsEngine();
-            //鍒濆鍖朤estStand寮曟搸
-            this.InitTestStandEngine();
         }
 
         public override void SetBaseFolder(string folder)
@@ -373,8 +381,9 @@
             {
                 UserInfo user = UserInfo.User;
                 CurrentUserCode = user?.Code;
-                engine?.Globals.SetValString("StationState.OPID", 1, CurrentUserCode);
-                engine?.CommitGlobalsToDisk(promptOnSaveConflicts: false);
+
+                testExecutionWrapper?.SetStationGlobalValue("StationState.OPID", 1, CurrentUserCode);
+                testExecutionWrapper?.CommitToDisk(false);
             }
             catch (Exception ex)
             {
@@ -443,7 +452,7 @@
             Products = ProductManager.GetProducts<ObservableCollection<ProductData>>();
             if (Products != null && Products.Count > 0)
             {
-                string lastModel = engine?.Globals.GetValString("StationState.ProductModel", 1);
+                string lastModel = testExecutionWrapper?.GetStationGlobalValue<string>("StationState.ProductModel", 1);
                 var tempProduct = Products[0];
                 if (!string.IsNullOrEmpty(lastModel))
                 {
@@ -487,25 +496,58 @@
         /// </summary>
         private void InitTsEngine()
         {
-            AxApplicationMgr = TestAppManager.GetAxApplicationMgr();
+            if (ParamSetting.EngineType == "Tap")
+            {
+                testExecutionWrapper = new TapWrapper();
+            }
+            else
+            {
+                testExecutionWrapper = new TeststandWrapper();
+            }
 
-
-            //AxApplicationMgr. += AxApplicationMgr_EndExecution;
-            AxApplicationMgr.EndExecution += AxApplicationMgr_EndExecution;
-
-            AxApplicationMgr.ExitApplication += this.AxApplicationMgr_ExitApplication;
-            AxApplicationMgr.UIMessageEvent += this.AxApplicationMgr_UIMessageEvent;
-            AxApplicationMgr.ExecutionClosed += this.AxApplicationMgr_ExecutionClosed;
-
-            ((System.ComponentModel.ISupportInitialize)(AxApplicationMgr)).BeginInit();
-            host.Child = AxApplicationMgr;
-            ((System.ComponentModel.ISupportInitialize)(AxApplicationMgr)).EndInit();
+            testExecutionWrapper.EndExecutionEvent += TestExecutionWrapper_EndExecutionEvent;
+            testExecutionWrapper.UIMessageEvent += TestExecutionWrapper_UIMessageEvent;
+            if (testExecutionWrapper is TeststandWrapper tsw)
+            {
+                var AxApplicationMgr = tsw.AxApplicationMgr;
+                ((System.ComponentModel.ISupportInitialize)(AxApplicationMgr)).BeginInit();
+                host.Child = AxApplicationMgr;
+                ((System.ComponentModel.ISupportInitialize)(AxApplicationMgr)).EndInit();
+            }
+            testExecutionWrapper.Init();
         }
 
-        private volatile bool canSwitchModel = false;
-        private volatile Action switchAction = null;
+        private void TestExecutionWrapper_UIMessageEvent(object sender, UIMessageEvent e)
+        {
+            try
+            {
+                int eventCode = Convert.ToInt32(e.EventCode);
+                int index = Convert.ToInt16(e.NumberData);//SocketIndex
+                string strData = e.StrData;
 
-        private void AxApplicationMgr_EndExecution(object sender, _ApplicationMgrEvents_EndExecutionEvent e)
+                try
+                {
+                    log.Debug($"Event: {eventCode}, NumericData: {index}, StringData: {strData}");
+                }
+                catch
+                {
+
+                }
+                if (eventCode == 10001)
+                {
+                    testExecutionWrapper?.SetContext((SequenceContext)e.Data);
+                    tsSockets[index].SequenceFileContext = (SequenceContext)e.Data;
+                    return;
+                }
+                HandleUIMessage(eventCode, index, strData);
+            }
+            catch (Exception ex)
+            {
+                log.Warn("Ts Event Error.", ex);
+            }
+        }
+
+        private void TestExecutionWrapper_EndExecutionEvent(object sender, ExecutionEndEvent e)
         {
             log.Info("AxApplicationMgr_EndExecution");
             if (ReleaseSequenceFile())
@@ -520,15 +562,8 @@
             }
         }
 
-        /// <summary>
-        /// 杩涚▼缁撴潫浜嬩欢
-        /// </summary>
-        /// <param name="sender"></param>
-        /// <param name="e"></param>
-        private void AxApplicationMgr_ExecutionClosed(object sender, _ApplicationMgrEvents_ExecutionClosedEvent e)
-        {
-            //ReleaseSequenceFile();
-        }
+        private volatile bool canSwitchModel = false;
+        private volatile Action switchAction = null;
 
         private void PluginControl_Loaded(object sender, RoutedEventArgs e)
         {
@@ -547,17 +582,6 @@
                 {
                     log.Error("TsExecution Loaded Failed.", ex);
                     MsgBoxHelper.Error(this, $"{LangUtil.Get("Ts.UserControlLoadFailed")}: {ex.Message}");
-                }
-
-                try
-                {
-                    if (Environment.Version.ToString() != this.AxApplicationMgr.GetEngine().DotNetCLRVersion)
-                        this.GCTimer.Enabled = true;
-                }
-                catch (Exception ex)
-                {
-                    log.Error("TsExecution GCTimer Start Failed", ex);
-                    MsgBoxHelper.Error(this, $"{LangUtil.Get("Ts.LoadFailed")}: {ex.Message}");
                 }
 
                 Dispatcher.Invoke(() =>
@@ -653,26 +677,6 @@
         }
 
         /// <summary>
-        /// 鍒濆鍖朤estStand寮曟搸
-        /// </summary>
-        private void InitTestStandEngine()
-        {
-            try
-            {
-                this.engine = this.AxApplicationMgr.GetEngine();
-                this.engine.PersistBreakpoints = false;
-                this.AxApplicationMgr.IsEditor = true;
-                this.AxApplicationMgr.LoginOnStart = false;
-                this.AxApplicationMgr.Start();
-                //this.modeSeq = this.engine.GetSequenceFileEx(@"C:\Program Files\National Instruments\TestStand 2020\Components\Models\TestStandModels\ParallelModel.seq");
-            }
-            catch (Exception ex)
-            {
-                throw new Exception(LangUtil.Get("Ts.InitTestStandEngineFail"), ex);
-            }
-        }
-
-        /// <summary>
         /// 璇诲彇Count閰嶇疆鏂囦欢鐨刾ass鍜宖ail鏁�
         /// </summary>
         /// <param name="pass">鍚堟牸鏁�</param>
@@ -695,7 +699,7 @@
         /// <param name="fail"></param>
         private void ReadConfig()
         {
-            ParameterData value;
+            HMI.Core.Entity.ParameterData value;
 
 
             bool flag = false;
@@ -955,7 +959,7 @@
                     foreach (var ts in tsSockets)
                     {
                         ts?.SequenceFileContext?.StationGlobals.SetValBoolean($"SystemSetting.{name}", 1, value);
-                        if (ts?.SequenceFileContext?.Sequence != null)
+                        if (name == "EnableDebugMode" && ts?.SequenceFileContext?.Sequence != null)
                         {
                             ts.SequenceFileContext.Sequence.FailureAction =
                                 value ? FailureActions.FailureAction_None : FailureActions.FailureAction_GotoCleanup;
@@ -1065,7 +1069,7 @@
             if (!isLoadOK) return;
             try
             {
-                engine?.Globals.SetValBoolean("StationState.StopTest", 1, false);
+                testExecutionWrapper?.SetStationGlobalValue("StationState.StopTest", 1, false);
             }
             catch (Exception ex)
             {
@@ -1085,9 +1089,10 @@
                         return;
                     }
                     this.testSeqFile = path;
-                    this.modeSeq = this.engine.GetSequenceFileEx(tsModelPath);
-                    this.testSeq = this.engine.GetSequenceFileEx(this.testSeqFile, 2, TypeConflictHandlerTypes.ConflictHandler_UseGlobalType);
-                    this.execution = this.engine.NewExecution(this.testSeq, "Test UUTs", this.modeSeq, false, 0, null, null, null);
+                    testExecutionWrapper?.StartSequence(this.testSeqFile, this.tsModelPath);
+                    //this.modeSeq = this.engine.GetSequenceFileEx(tsModelPath);
+                    //this.testSeq = this.engine.GetSequenceFileEx(this.testSeqFile, 2, TypeConflictHandlerTypes.ConflictHandler_UseGlobalType);
+                    //this.execution = this.engine.NewExecution(this.testSeq, "Test UUTs", this.modeSeq, false, 0, null, null, null);
                     IsTesting = true;
                 }
                 catch (Exception ex)
@@ -1104,11 +1109,12 @@
                 //MaskWindow mask = new MaskWindow();
                 try
                 {
-                    engine?.Globals.SetValString("StationState.ProductModel", 1, CurrentProductModel);
-                    engine?.CommitGlobalsToDisk(promptOnSaveConflicts: false);
-                    this.modeSeq = this.engine.GetSequenceFileEx(tsModelPath);
-                    this.testSeq = this.engine.GetSequenceFileEx(this.testSeqFile, 2, TypeConflictHandlerTypes.ConflictHandler_UseGlobalType);
-                    this.execution = this.engine.NewExecution(this.testSeq, "Test UUTs", this.modeSeq, false, 0, null, null, null);
+                    testExecutionWrapper?.SetStationGlobalValue("StationState.ProductModel", 1, CurrentProductModel);
+                    testExecutionWrapper?.CommitToDisk(false);
+                    testExecutionWrapper?.StartSequence(this.testSeqFile, this.tsModelPath);
+                    //this.modeSeq = this.engine.GetSequenceFileEx(tsModelPath);
+                    //this.testSeq = this.engine.GetSequenceFileEx(this.testSeqFile, 2, TypeConflictHandlerTypes.ConflictHandler_UseGlobalType);
+                    //this.execution = this.engine.NewExecution(this.testSeq, "Test UUTs", this.modeSeq, false, 0, null, null, null);
                     IsTesting = true;
                 }
                 catch (Exception ex)
@@ -1143,21 +1149,14 @@
         {
             try
             {
-                //if (this.tsSockets.Count > 0)
-                //{
-                //    for (int i = 0; i < tsSockets.Count; i++)
-                //    {
-                //        var context = this.tsSockets[i].SequenceFileContext;
-                //        context?.StationGlobals.SetValBoolean("StationState.StopTest", 1, true);
-                //    }
-                //}
-                engine.Globals.SetValBoolean("StationState.StopTest", 1, true);
-                if (this.execution == null) return true;
+                testExecutionWrapper.SetStationGlobalValue("StationState.StopTest", 1, true);
+
 
                 //bool res = this.execution.WaitForEndEx(-1, true, null, null);
-                bool res = this.execution?.WaitForEndEx(-1, true, Type.Missing, null) == true;
-                Marshal.ReleaseComObject(this.execution);
-                this.execution = null;
+                bool res = this.testExecutionWrapper.WaitForExit();
+                //if (this.execution != null)
+                //    Marshal.ReleaseComObject(this.execution);
+                //this.execution = null;
                 return res;
             }
             catch (Exception ex)
@@ -1217,22 +1216,229 @@
             //    }).Info(LangUtil.Get("Ts.SequenceIsStarting"));
         }
 
-        /// <summary>
-        /// 閫�鍑哄簲鐢�
-        /// </summary>
-        /// <param name="sender"></param>
-        /// <param name="e"></param>
-        private void AxApplicationMgr_ExitApplication(object sender, EventArgs e)
-        {
-            //Environment.ExitCode = this.AxApplicationMgr.ExitCode;
-            //this.Close();
-        }
-
         public void UpdatePositionStatus(int index, string status)
         {
             this.tsSockets[index].UpdateStatus(status);
+            OnStatusPublished(index, status);
             this.UpdateTestCounter();
         }
+
+        private void OnStatusPublished(int index, string status)
+        {
+            if (ParamSetting.CustomerCode != "SOUND")
+            {
+                return;
+            }
+            if (status == "Passed" || status == "Failed")
+            {
+                SaveSOUNDExcel(index, status);
+            }
+        }
+
+        private string ConvertStatus(string status)
+        {
+            if (string.Equals(status, "passed", StringComparison.OrdinalIgnoreCase))
+            {
+                return "Pass";
+            }
+            else
+            {
+                return "Fail";
+            }
+        }
+
+        #region 妗戝痉瀹氬埗
+        private ICell CreateCell(ISheet sheet, int r, int c, ICellStyle style)
+        {
+            var row = sheet.GetRow(r);
+            if (row == null)
+            {
+                row = sheet.CreateRow(r);
+            }
+            var cell = row.GetCell(c);
+            if (cell == null)
+            {
+                cell = row.CreateCell(c);
+            }
+            if (style != null)
+            {
+                cell.CellStyle = style;
+            }
+            return cell;
+        }
+
+        private string GetOutputFolder()
+        {
+            var value = ParameterManager.GetCommonParameterByName("SOUND_Output");
+            if (value != null)
+            {
+                return value.Value;
+            }
+            return ParamSetting.DataLogFolder;
+        }
+
+        private void SaveSOUNDExcel(int index, string status)
+        {
+            lock (this)
+            {
+                try
+                {
+                    string outputFolder = GetOutputFolder();
+
+                    var socket = this.tsSockets[index];
+                    DateTime testTime = DateTime.Now;
+                    string filePath = Path.Combine(outputFolder, $"{testTime.ToString("yy-MM-dd")}_{CurrentProductModel}.xls");
+                    if (!File.Exists(filePath))
+                    {
+                        Dictionary<string, int> columnMap = new Dictionary<string, int>();
+                        HSSFWorkbook workbook = new HSSFWorkbook();
+                        ISheet sheet = workbook.CreateSheet("Sheet1");
+
+                        ICellStyle border = workbook.CreateCellStyle();
+                        border.BorderBottom = NPOI.SS.UserModel.BorderStyle.Thin;
+                        border.BorderTop = NPOI.SS.UserModel.BorderStyle.Thin;
+                        border.BorderLeft = NPOI.SS.UserModel.BorderStyle.Thin;
+                        border.BorderRight = NPOI.SS.UserModel.BorderStyle.Thin;
+                        border.BottomBorderColor = NPOI.HSSF.Util.HSSFColor.Black.Index;
+                        border.TopBorderColor = NPOI.HSSF.Util.HSSFColor.Black.Index;
+                        border.LeftBorderColor = NPOI.HSSF.Util.HSSFColor.Black.Index;
+                        border.RightBorderColor = NPOI.HSSF.Util.HSSFColor.Black.Index;
+
+
+                        ICellStyle red = workbook.CreateCellStyle();
+                        red.CloneStyleFrom(border);
+                        red.FillForegroundColor = NPOI.HSSF.Util.HSSFColor.Red.Index;
+                        red.FillPattern = FillPattern.SolidForeground;
+
+                        ICellStyle green = workbook.CreateCellStyle();
+                        green.CloneStyleFrom(border);
+                        green.FillForegroundColor = NPOI.HSSF.Util.HSSFColor.BrightGreen.Index;
+                        green.FillPattern = FillPattern.SolidForeground;
+
+                        CreateCell(sheet, 1, 0, border);
+                        CreateCell(sheet, 1, 1, border);
+                        CreateCell(sheet, 1, 2, border);
+                        CreateCell(sheet, 1, 3, border);
+                        CreateCell(sheet, 1, 4, border);
+
+                        CreateCell(sheet, 2, 0, border);
+                        CreateCell(sheet, 2, 1, border);
+                        CreateCell(sheet, 2, 2, border);
+                        CreateCell(sheet, 2, 3, border);
+                        CreateCell(sheet, 2, 4, border).SetCellValue("涓婇檺");
+
+                        CreateCell(sheet, 3, 0, border).SetCellValue("娴嬭瘯鏃堕棿");
+                        CreateCell(sheet, 3, 1, border).SetCellValue("娴嬭瘯浜�");
+                        CreateCell(sheet, 3, 2, border).SetCellValue("娴嬭瘯璁惧");
+                        CreateCell(sheet, 3, 3, border).SetCellValue("鏉″舰鐮�");
+                        CreateCell(sheet, 3, 4, border).SetCellValue("涓嬮檺");
+
+                        int column = 5;
+                        int row = 4;
+
+                        CreateCell(sheet, row, 0, border).SetCellValue(testTime.ToString("*HH:mm:ss*"));
+                        CreateCell(sheet, row, 1, border).SetCellValue(UserInfo.User.Name);
+                        CreateCell(sheet, row, 2, border).SetCellValue(ParamSetting.StationName);
+                        CreateCell(sheet, row, 3, border).SetCellValue(socket.Barcode);
+                        CreateCell(sheet, row, 4, status == "Passed" ? green : red).SetCellValue(ConvertStatus(status));
+
+                        foreach (var data in socket.Datasource)
+                        {
+                            int col = column;
+                            if (columnMap.ContainsKey(data.Name))
+                            {
+                                col = columnMap[data.Name];
+                            }
+                            else
+                            {
+                                columnMap[data.Name] = column++;
+                            }
+
+                            CreateCell(sheet, 1, col, border).SetCellValue(data.Name);
+                            CreateCell(sheet, 2, col, border).SetCellValue(data.LimitUp);
+                            CreateCell(sheet, 3, col, border).SetCellValue(data.LimitDown);
+                            CreateCell(sheet, row, col, data.Result == "Failed" ? red : border).SetCellValue(data.Value);
+                        }
+                        using (FileStream fs = new FileStream(filePath, FileMode.OpenOrCreate))
+                        {
+                            workbook.Write(fs);
+                        }
+                    }
+                    else
+                    {
+                        using (FileStream fs = File.OpenRead(filePath))
+                        {
+                            IWorkbook workbook = new HSSFWorkbook(fs);
+                            ICellStyle border = workbook.CreateCellStyle();
+                            border.BorderBottom = NPOI.SS.UserModel.BorderStyle.Thin;
+                            border.BorderTop = NPOI.SS.UserModel.BorderStyle.Thin;
+                            border.BorderLeft = NPOI.SS.UserModel.BorderStyle.Thin;
+                            border.BorderRight = NPOI.SS.UserModel.BorderStyle.Thin;
+                            border.BottomBorderColor = NPOI.HSSF.Util.HSSFColor.Black.Index;
+                            border.TopBorderColor = NPOI.HSSF.Util.HSSFColor.Black.Index;
+                            border.LeftBorderColor = NPOI.HSSF.Util.HSSFColor.Black.Index;
+                            border.RightBorderColor = NPOI.HSSF.Util.HSSFColor.Black.Index;
+
+
+                            ICellStyle red = workbook.CreateCellStyle();
+                            red.CloneStyleFrom(border);
+                            red.FillForegroundColor = NPOI.HSSF.Util.HSSFColor.Red.Index;
+                            red.FillPattern = FillPattern.SolidForeground;
+
+                            ICellStyle green = workbook.CreateCellStyle();
+                            green.CloneStyleFrom(border);
+                            green.FillForegroundColor = NPOI.HSSF.Util.HSSFColor.BrightGreen.Index;
+                            green.FillPattern = FillPattern.SolidForeground;
+
+                            ISheet sheet = workbook.GetSheet("Sheet1");
+                            Dictionary<string, int> columnMap = new Dictionary<string, int>();
+                            var titleRow = sheet.GetRow(1);
+                            for (int i = 0; i < titleRow.LastCellNum; i++)
+                            {
+                                if (!string.IsNullOrEmpty(titleRow.GetCell(i).StringCellValue))
+                                {
+                                    columnMap[titleRow.GetCell(i).StringCellValue] = i;
+                                }
+                            }
+                            int row = sheet.LastRowNum + 1;
+                            CreateCell(sheet, row, 0, border).SetCellValue(testTime.ToString("*HH:mm:ss*"));
+                            CreateCell(sheet, row, 1, border).SetCellValue(UserInfo.User.Name);
+                            CreateCell(sheet, row, 2, border).SetCellValue(ParamSetting.StationName);
+                            CreateCell(sheet, row, 3, border).SetCellValue(socket.Barcode);
+                            CreateCell(sheet, row, 4, status == "Passed" ? green : red).SetCellValue(ConvertStatus(status));
+
+                            foreach (var data in socket.Datasource)
+                            {
+                                int col = titleRow.LastCellNum;
+                                if (columnMap.ContainsKey(data.Name))
+                                {
+                                    col = columnMap[data.Name];
+                                }
+                                else
+                                {
+                                    columnMap[data.Name] = col;
+                                }
+
+                                CreateCell(sheet, 1, col, border).SetCellValue(data.Name);
+                                CreateCell(sheet, 2, col, border).SetCellValue(data.LimitUp);
+                                CreateCell(sheet, 3, col, border).SetCellValue(data.LimitDown);
+                                CreateCell(sheet, row, col, data.Result == "Failed" ? red : border).SetCellValue(data.Value);
+                            }
+                            using (FileStream nfs = new FileStream(filePath, FileMode.OpenOrCreate))
+                            {
+                                workbook.Write(nfs);
+                            }
+                        }
+                    }
+
+                }
+                catch (Exception ex)
+                {
+                    log.Error("SOUND Export Excel Data Error.", ex);
+                }
+            }
+        }
+        #endregion
 
         public void UpdatePositionProcessValue(int index, int value)
         {
@@ -1296,9 +1502,11 @@
                 sqls.Add($@"INSERT INTO serialData (sn,model,station,step,position,limitDown,limitUp,unit,value,status,finalStatus,consumeTime,totalConsumeTime,time) VALUES
 ('{snr}','{model}','','{data.Name}','{index}','{data.LimitDown}','{data.LimitUp}','{data.Unit}','{data.Value}','{data.Result}','{socket.Status}','{data.ConsumeTime}','{socket.ElapseTime}','{time}')");
             }
-            sqls.Add($@"INSERT INTO testResult (sn,model,station,result,time,position,status) VALUES 
-('{snr}','{model}','','{socket.Status}','{time}','{index}','{status}')");
             sqliteHelper.ExecuteBatchSql(sqls);
+
+            var ssql = $@"INSERT INTO testResult (sn,model,station,result,time,position,status) VALUES 
+('{snr}','{model}','','{socket.Status}','{time}','{index}','{status}')";
+            sqliteHelper.ExecuteSql(ssql);
         }
 
         private void HandleUIMessage(int eventCode, int index, string strData)
@@ -1385,6 +1593,26 @@
                     this.tsSockets[index].SetTestProcess(ProcessVal);
                     break;
                 case 10031://瀵煎嚭CSV
+
+                    if (ParamSetting.CustomerCode == "SOUND")
+                    {
+                        string[] datas = strData.Split(';');
+                        this.tsSockets[index].WriteToExcel(datas[0]);
+                        if (datas.Length >= 2)
+                        {
+                            SavePositionDB(index, datas[1]);
+                        }
+                        else
+                        {
+                            Dispatcher.Invoke(() =>
+                            {
+                                var sss = this.tsSockets[index].Status;
+                                SavePositionDB(index, sss);
+                            });
+                        }
+                        return;
+                    }
+
                     if (string.IsNullOrEmpty(strData))
                     {
                         SaveTemplateCSV(index);
@@ -1400,8 +1628,11 @@
                         }
                         else
                         {
-                            var sss = this.tsSockets[index].Status;
-                            SavePositionDB(index, sss);
+                            Dispatcher.Invoke(() =>
+                            {
+                                var sss = this.tsSockets[index].Status;
+                                SavePositionDB(index, sss);
+                            });
                         }
                     }
                     catch (Exception ex)
@@ -1435,6 +1666,22 @@
                         points[i - 1] = new int[2] { int.Parse(strs[0]), int.Parse(strs[1]) };
                     }
                     this.tsSockets[index].DrawScreenPoints(int.Parse(arr_10035[0]), points);
+                    break;
+                case 100350:
+                    string[] arr_100350 = strData.Split(';');
+
+                    int[][] points_0 = new int[arr_100350.Length - 1][];
+
+                    for (int i = 1; i < arr_100350.Length; i++)
+                    {
+                        if (string.IsNullOrEmpty(arr_100350[i]))
+                        {
+                            continue;
+                        }
+                        string[] strs = arr_100350[i].Split(',');
+                        points_0[i - 1] = new int[2] { int.Parse(strs[0]), int.Parse(strs[1]) };
+                    }
+                    this.tsSockets[index].DrawNewScreenPoints(int.Parse(arr_100350[0]), points_0);
                     break;
                 case 10036:
                     this.tsSockets[index].ClearScreenPoint(int.Parse(strData));
@@ -1481,8 +1728,8 @@
                             {
                                 log?.Info("寮�濮嬭嚜鍔ㄦ崲鍨�");
                                 SelectedProduct = target;
-                                engine?.Globals.SetValString($"CacheSNR[{index}]", 1, snr);
-                                engine?.CommitGlobalsToDisk(false);
+                                testExecutionWrapper?.SetStationGlobalValue($"CacheSNR[{index}]", 1, snr);
+                                testExecutionWrapper?.CommitToDisk(false);
                                 StartClick();
                             }
                             catch (Exception ex)
@@ -1498,43 +1745,7 @@
         private void ShowAlarm(AlarmText alarm)
         {
             AlarmShowHelper.ShowAlarm(alarm);
-            mSMQHelper.Send("Alarm", $"ATE娴嬭瘯 {alarm.AlarmInfo}");
-        }
-
-        /// <summary>
-        /// TestStand鐢ㄦ埛浜や簰浜嬩欢
-        /// </summary>
-        /// <param name="sender"></param>
-        /// <param name="e"></param>
-        private void AxApplicationMgr_UIMessageEvent(object sender, _ApplicationMgrEvents_UIMessageEventEvent e)
-        {
-            try
-            {
-                int eventCode = Convert.ToInt32(e.uiMsg.Event);
-                int index = Convert.ToInt16(e.uiMsg.NumericData);//SocketIndex
-                string strData = e.uiMsg.StringData;
-
-                try
-                {
-                    log.Debug($"Event: {eventCode}, NumericData: {index}, StringData: {strData}");
-                }
-                catch
-                {
-
-                }
-                if (eventCode == 10001)
-                {
-                    sequenceContext = (SequenceContext)e.uiMsg.ActiveXData;
-
-                    tsSockets[index].SequenceFileContext = (SequenceContext)e.uiMsg.ActiveXData;
-                    return;
-                }
-                HandleUIMessage(eventCode, index, strData);
-            }
-            catch (Exception ex)
-            {
-                log.Warn("Ts Event Error.", ex);
-            }
+            mSMQHelper.Send("Alarm", "PC", $"ATE娴嬭瘯 {alarm.AlarmInfo}", 2);
         }
 
         private void UpdateTestCounter()
@@ -1557,7 +1768,7 @@
                 return;
             }
             string name = product.Name;
-            List<ParameterData> parameters = ProductManager.GetParametersByProductName(name);
+            List<HMI.Core.Entity.ParameterData> parameters = ProductManager.GetParametersByProductName(name);
             testSeqFile = parameters.FirstOrDefault(p => p.Name == "TestSequence")?.Value?.ToString();
             if (AutoExecuteSeq)
             {
@@ -1607,32 +1818,13 @@
             {
                 lock (this)
                 {
-                    if (this.testSeq == null)
-                    {
-                        return true;
-                    }
                     //execution.Abort();
                     int count = 10;
                     while (count > 0)
                     {
                         try
                         {
-                            if (this.testSeq == null)
-                                return true;
-
-                            if (this.engine?.ReleaseSequenceFileEx(this.testSeq, 4) == true)
-                            {
-                                Marshal.ReleaseComObject(this.testSeq);
-                                this.testSeq = null;
-                                // GC.Collect();
-                            }
-                            if (this.engine?.ReleaseSequenceFileEx(this.modeSeq, 4) == true)
-                            {
-                                Marshal.ReleaseComObject(this.modeSeq);
-                                this.modeSeq = null;
-                            }
-                            this.engine?.UnloadAllModules();
-                            return this.testSeq == null && this.modeSeq == null;
+                            return testExecutionWrapper.ReleaseSeqFile();
                         }
                         catch (Exception ex)
                         {
diff --git a/Plugin.TsSupport/ITestExecutionWrapper.cs b/Plugin.TsSupport/ITestExecutionWrapper.cs
index 2a80a38..ccd000b 100644
--- a/Plugin.TsSupport/ITestExecutionWrapper.cs
+++ b/Plugin.TsSupport/ITestExecutionWrapper.cs
@@ -1,5 +1,4 @@
-锘縰sing NationalInstruments.TestStand.Interop.API;
-using System;
+锘縰sing System;
 
 namespace Plugin.TsSupport
 {
diff --git a/Plugin.TsSupport/Plugin.TsSupport.csproj b/Plugin.TsSupport/Plugin.TsSupport.csproj
index ec9f306..4b296a0 100644
--- a/Plugin.TsSupport/Plugin.TsSupport.csproj
+++ b/Plugin.TsSupport/Plugin.TsSupport.csproj
@@ -72,6 +72,11 @@
       <EmbedInteropTypes>True</EmbedInteropTypes>
       <HintPath>..\Libs\NationalInstruments.TestStand.Interop.API.dll</HintPath>
     </Reference>
+    <Reference Include="NationalInstruments.TestStand.Interop.UI, Version=20.0.0.49158, Culture=neutral, PublicKeyToken=ad9244eb3b825cd8, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <EmbedInteropTypes>True</EmbedInteropTypes>
+      <HintPath>..\Libs\NationalInstruments.TestStand.Interop.UI.dll</HintPath>
+    </Reference>
     <Reference Include="NationalInstruments.TestStand.Interop.UI.AxControls, Version=20.0.0.49158, Culture=neutral, PublicKeyToken=ad9244eb3b825cd8, processorArchitecture=MSIL">
       <SpecificVersion>False</SpecificVersion>
       <HintPath>..\Libs\NationalInstruments.TestStand.Interop.UI.AxControls.dll</HintPath>
@@ -88,6 +93,7 @@
   </ItemGroup>
   <ItemGroup>
     <Compile Include="ITestExecutionWrapper.cs" />
+    <Compile Include="TapWrapper.cs" />
     <Compile Include="TestAppManager.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="TeststandWrapper.cs" />
diff --git a/Plugin.TsSupport/TapWrapper.cs b/Plugin.TsSupport/TapWrapper.cs
new file mode 100644
index 0000000..23ef29a
--- /dev/null
+++ b/Plugin.TsSupport/TapWrapper.cs
@@ -0,0 +1,238 @@
+锘縰sing System;
+//using AddInPlugin;
+//using AddInPlugin.Util;
+//using Microsoft.Win32;
+//using OpenTap;
+//using OpenTap.Addin;
+//using OpenTap.Addin.Util;
+//using System.ComponentModel;
+//using System.IO;
+
+namespace Plugin.TsSupport
+{
+
+    public class TapWrapper : ITestExecutionWrapper
+    {
+        public event EventHandler<UIMessageEvent> UIMessageEvent;
+        public event EventHandler<ExecutionEndEvent> EndExecutionEvent;
+
+        public void CommitToDisk(bool option)
+        {
+
+        }
+
+        public T GetFileGlobalValue<T>(string key, int option)
+        {
+            return default(T);
+        }
+
+        public T GetStationGlobalValue<T>(string key, int option)
+        {
+            return default(T);
+        }
+
+        public object Init()
+        {
+            return null;
+        }
+
+        public bool ReleaseSeqFile()
+        {
+            return true;
+        }
+
+        public void SetContext(object context)
+        {
+
+        }
+
+        public void SetFileGlobalValue<T>(string key, int option, T value)
+        {
+
+        }
+
+        public void SetStationGlobalValue<T>(string key, int option, T value, bool inContext = false)
+        {
+
+        }
+
+        public object StartSequence(string filePath, string modelPath)
+        {
+            return null;
+        }
+
+        public bool WaitForExit()
+        {
+            return true;
+        }
+    }
+
+    //public class Net47Getter : IRegGetter
+    //{
+    //    public string GetAppPath(string keyPath)
+    //    {
+    //        using (RegistryKey key = Registry.CurrentUser.OpenSubKey(keyPath))
+    //        {
+    //            if (key == null)
+    //            {
+    //                throw new Exception("娉ㄥ唽琛ㄤ笉瀛樺湪锛岃鍏堣繍琛屼竴娆$紪杈戝櫒");
+    //            }
+
+    //            string value = key.GetValue("Location")?.ToString();
+    //            if (string.IsNullOrEmpty(value))
+    //            {
+    //                throw new Exception("娉ㄥ唽琛ㄤ笉瀛樺湪锛岃鍏堣繍琛屼竴娆$紪杈戝櫒");
+    //            }
+    //            return value;
+    //        }
+    //    }
+    //}
+
+    //public class TapWrapper : ITestExecutionWrapper, IResultListener
+    //{
+    //    TapThread testPlanThread;
+    //    TestPlan Plan;
+    //    TestPlanRun PlanRun;
+
+    //    public event PropertyChangedEventHandler PropertyChanged;
+
+    //    public event EventHandler<UIMessageEvent> UIMessageEvent;
+    //    public event EventHandler<ExecutionEndEvent> EndExecutionEvent;
+
+    //    public string Name { get; set; }
+
+    //    public bool IsConnected => true;
+
+    //    public object Init()
+    //    {
+    //        RegUtil.Register(new Net47Getter());
+    //        StationGlobalsManager.Register(new DefaultStationGlobalsManager());
+    //        VariableResolverFactory.Register(new DefaultVariableResolverFactory());
+    //        ResultSettings.Current.Add(this);
+    //        return null;
+    //    }
+
+    //    private void PostUIMsgHandler_OnPostUIMsg(object sender, PostUIMsg e)
+    //    {
+    //        UIMessageEvent?.Invoke(this, new UIMessageEvent()
+    //        {
+    //            Origin = e,
+    //            EventCode = e.EventCode,
+    //            BoolData = e.BoolData,
+    //            NumberData = e.NumberData,
+    //            StrData = e.StringData
+    //        });
+    //    }
+
+    //    public object StartSequence(string filePath, string modelPath)
+    //    {
+    //        Plan = TestPlan.Load(filePath);
+    //        testPlanThread = TapThread.Start(() =>
+    //        {
+    //            var res = ResultSettings.Current;
+    //            Plan.Execute(ResultSettings.Current, null);
+    //        });
+    //        return Plan;
+    //    }
+
+
+    //    public T GetFileGlobalValue<T>(string key, int option)
+    //    {
+    //        return PlanRun.Resolve<T>(null, $"FileGlobals.{key}");
+    //    }
+
+    //    public T GetStationGlobalValue<T>(string key, int option)
+    //    {
+    //        if (PlanRun == null)
+    //        {
+    //            return StationGlobalsManager.GetValue<T>(key);
+    //        }
+    //        return PlanRun.Resolve<T>(null, $"StationGlobals.{key}");
+    //    }
+
+    //    public T GetValue<T>(string key, int option)
+    //    {
+    //        return default(T);
+    //    }
+
+    //    public void SetValue<T>(string key, T value, int option)
+    //    {
+
+    //    }
+
+    //    public void SetFileGlobalValue<T>(string key, int option, T value)
+    //    {
+    //        PlanRun.Update(null, $"FileGlobals.{key}", value);
+    //    }
+
+    //    public void SetStationGlobalValue<T>(string key, int option, T value, bool inContext = false)
+    //    {
+    //        if (PlanRun == null)
+    //        {
+    //            StationGlobalsManager.SetValue(key, value);
+    //        }
+    //        else
+    //        {
+    //            PlanRun.Update(null, $"StationGlobals.{key}", value);
+    //        }
+    //    }
+
+    //    public void CommitToDisk(bool option)
+    //    {
+    //        StationGlobalsManager.WriteToLocal(null);
+    //    }
+
+    //    public void OnTestPlanRunStart(TestPlanRun planRun)
+    //    {
+    //        PlanRun = planRun;
+    //    }
+
+    //    public void OnTestPlanRunCompleted(TestPlanRun planRun, Stream logStream)
+    //    {
+
+
+    //    }
+
+    //    public void OnTestStepRunStart(TestStepRun stepRun)
+    //    {
+
+    //    }
+
+    //    public void OnTestStepRunCompleted(TestStepRun stepRun)
+    //    {
+
+    //    }
+
+    //    public void OnResultPublished(Guid stepRunID, ResultTable result)
+    //    {
+
+    //    }
+
+    //    public void Open()
+    //    {
+    //        PostUIMsgHandler.OnPostUIMsg += PostUIMsgHandler_OnPostUIMsg;
+    //    }
+
+    //    public void Close()
+    //    {
+    //        PostUIMsgHandler.OnPostUIMsg -= PostUIMsgHandler_OnPostUIMsg;
+    //        EndExecutionEvent?.Invoke(this, new ExecutionEndEvent());
+    //        PlanRun = null;
+    //    }
+
+    //    public bool WaitForExit()
+    //    {
+    //        return true;
+    //    }
+
+    //    public bool ReleaseSeqFile()
+    //    {
+    //        return true;
+    //    }
+
+    //    public void SetContext(object context)
+    //    {
+
+    //    }
+    //}
+}
diff --git a/Plugin.TsSupport/TeststandWrapper.cs b/Plugin.TsSupport/TeststandWrapper.cs
index b3c7f07..ea15087 100644
--- a/Plugin.TsSupport/TeststandWrapper.cs
+++ b/Plugin.TsSupport/TeststandWrapper.cs
@@ -1,7 +1,7 @@
 锘縰sing NationalInstruments.TestStand.Interop.API;
 using NationalInstruments.TestStand.Interop.UI.Ax;
 using System;
-using System.Diagnostics;
+using System.Runtime.InteropServices;
 
 namespace Plugin.TsSupport
 {
@@ -213,11 +213,19 @@
 
         public bool ReleaseSeqFile()
         {
-            if (testSeq != null)
+            if (this.testSeq != null && this.engine?.ReleaseSequenceFileEx(this.testSeq, 4) == true)
             {
-                return engine?.ReleaseSequenceFileEx(testSeq, 4) == true;
+                Marshal.ReleaseComObject(this.testSeq);
+                this.testSeq = null;
             }
-            return true;
+
+            if (this.modeSeq != null && this.engine?.ReleaseSequenceFileEx(this.modeSeq, 4) == true)
+            {
+                Marshal.ReleaseComObject(this.modeSeq);
+                this.modeSeq = null;
+            }
+            this.engine?.UnloadAllModules();
+            return this.testSeq == null && this.modeSeq == null;
         }
 
         public void SetContext(object context)
diff --git a/Plugin.TsSupport/TsExecutionWrapper.cs b/Plugin.TsSupport/TsExecutionWrapper.cs
index 837bc75..1298dd5 100644
--- a/Plugin.TsSupport/TsExecutionWrapper.cs
+++ b/Plugin.TsSupport/TsExecutionWrapper.cs
@@ -40,23 +40,37 @@
         /// </summary>
         public AxApplicationMgr AxApplicationMgr { get; private set; }
 
-        public event _ApplicationMgrEvents_EndExecutionEventHandler OnEndExecution;
-
         public event EventHandler<UIMessageEvent> UIMessageEvent;
         public event EventHandler<ExecutionEndEvent> EndExecutionEvent;
 
         public TsExecutionWrapper()
         {
             AxApplicationMgr = TestAppManager.GetAxApplicationMgr();
-            //AxApplicationMgr.EndExecution += AxApplicationMgr_EndExecution;
+            AxApplicationMgr.EndExecution += AxApplicationMgr_EndExecution;
             //AxApplicationMgr.ExitApplication += new EventHandler(this.AxApplicationMgr_ExitApplication);
-            //AxApplicationMgr.UIMessageEvent +=
-            //    new _ApplicationMgrEvents_UIMessageEventEventHandler(this.AxApplicationMgr_UIMessageEvent);
+            AxApplicationMgr.UIMessageEvent += AxApplicationMgr_UIMessageEvent;
             //AxApplicationMgr.ExecutionClosed +=
             //    new _ApplicationMgrEvents_ExecutionClosedEventHandler(this.AxApplicationMgr_ExecutionClosed);
         }
 
         #endregion
+
+        private void AxApplicationMgr_EndExecution(object sender, _ApplicationMgrEvents_EndExecutionEvent e)
+        {
+            EndExecutionEvent?.Invoke(this, new ExecutionEndEvent() { Origin = e });
+        }
+
+        private void AxApplicationMgr_UIMessageEvent(object sender, _ApplicationMgrEvents_UIMessageEventEvent e)
+        {
+            UIMessageEvent?.Invoke(this, new UIMessageEvent()
+            {
+                Origin = e,
+                EventCode = Convert.ToInt32(e.uiMsg.Event),
+                NumberData = e.uiMsg.NumericData,
+                Data = e.uiMsg.ActiveXData,
+                StrData = e.uiMsg.StringData
+            });
+        }
 
         public object Init()
         {
@@ -208,7 +222,7 @@
 
         public void SetContext(object context)
         {
-
+            this.context = (SequenceContext)context;
         }
     }
 }
diff --git a/S7Driver/PLCS7Driver.cs b/S7Driver/PLCS7Driver.cs
index 6b666e3..aa3d531 100644
--- a/S7Driver/PLCS7Driver.cs
+++ b/S7Driver/PLCS7Driver.cs
@@ -61,60 +61,150 @@
             client?.Dispose();
         }
 
-        public bool Simens_S7_ReadBool(int DB, int StartAdd, int Pos)
+        public bool Simens_S7_ReadBool(int DB, int StartAdd, int Pos, out bool value, out string msg)
         {
-            var or = client.ReadBool($"DB{DB}.DBX{StartAdd}.{Pos}");
-            return or.Content;
+            value = false;
+            msg = null;
+            try
+            {
+                var or = client.ReadBool($"DB{DB}.DBX{StartAdd}.{Pos}");
+                if (!or.IsSuccess)
+                {
+                    msg = $"{or.ErrorCode} {or.Message}";
+                    return false;
+                }
+                value = or.Content;
+                return true;
+            }
+            catch (Exception ex)
+            {
+                msg = ex.Message;
+                return false;
+            }
         }
 
-        public void Simens_S7_WriteBool(int DB, int StartAdd, int Pos, bool Val)
+        public bool Simens_S7_WriteBool(int DB, int StartAdd, int Pos, bool Val, out string msg)
         {
-            var or = client.Write($"DB{DB}.DBX{StartAdd}.{Pos}", Val);
-            return;
+            msg = null;
+            try
+            {
+                var or = client.Write($"DB{DB}.DBX{StartAdd}.{Pos}", Val);
+                if (!or.IsSuccess)
+                {
+                    msg = $"{or.ErrorCode} {or.Message}";
+                    return false;
+                }
+                return true;
+            }
+            catch (Exception ex)
+            {
+                msg = ex.Message;
+                return false;
+            }
         }
 
-        public int Simens_S7_ReadInt(int DB, int StartAdd, int Length)
-        {
-            var or = client.ReadInt16($"DB{DB}.DBW{StartAdd}");
-            return or.Content;
-        }
-
-        public void Simens_S7_WriteInt(int DB, int StartAdd, short Val)
-        {
-            client.Write($"DB{DB}.DBW{StartAdd}", Val);
-        }
-
-        public float Simens_S7_ReadReal(int DB, int StartAdd, int Length)
-        {
-            var or = client.ReadFloat($"DB{DB}.DBW{StartAdd}");
-            return or.Content;
-        }
-
-        public void Simens_S7_WriteReal(int DB, int StartAdd, float Val)
-        {
-            client.Write($"DB{DB}.DBW{StartAdd}", Val);
-        }
-
-        public bool ReadShort(string address, out short value, out string errMsg)
+        public bool Simens_S7_ReadInt(int DB, int StartAdd, out int value, out string msg)
         {
             value = 0;
-            errMsg = null;
+            msg = null;
+            try
+            {
+                var or = client.ReadInt16($"DB{DB}.DBW{StartAdd}");
+                if (!or.IsSuccess)
+                {
+                    msg = $"{or.ErrorCode} {or.Message}";
+                    return false;
+                }
+                value = or.Content;
+                return true;
+            }
+            catch (Exception ex)
+            {
+                msg = ex.Message;
+                return false;
+            }
+        }
+
+        public bool Simens_S7_WriteInt(int DB, int StartAdd, short Val, out string msg)
+        {
+            msg = null;
+            try
+            {
+                var or = client.Write($"DB{DB}.DBW{StartAdd}", Val);
+                if (!or.IsSuccess)
+                {
+                    msg = $"{or.ErrorCode} {or.Message}";
+                    return false;
+                }
+                return true;
+            }
+            catch (Exception ex)
+            {
+                msg = ex.Message;
+                return false;
+            }
+        }
+
+        public bool Simens_S7_ReadReal(int DB, int StartAdd, out float value, out string msg)
+        {
+            value = 0;
+            msg = null;
+            try
+            {
+                var or = client.ReadFloat($"DB{DB}.DBW{StartAdd}");
+                if (!or.IsSuccess)
+                {
+                    msg = $"{or.ErrorCode} {or.Message}";
+                    return false;
+                }
+                value = or.Content;
+                return true;
+            }
+            catch (Exception ex)
+            {
+                msg = ex.Message;
+                return false;
+            }
+        }
+
+        public bool Simens_S7_WriteReal(int DB, int StartAdd, float Val, out string msg)
+        {
+            msg = null;
+            try
+            {
+                var or = client.Write($"DB{DB}.DBW{StartAdd}", Val);
+                if (!or.IsSuccess)
+                {
+                    msg = $"{or.ErrorCode} {or.Message}";
+                    return false;
+                }
+                return true;
+            }
+            catch (Exception ex)
+            {
+                msg = ex.Message;
+                return false;
+            }
+        }
+
+        public bool ReadShort(string address, out short value, out string msg)
+        {
+            value = 0;
+            msg = null;
             try
             {
                 var or = client.ReadInt16(address);
-                if (or.IsSuccess)
+                if (!or.IsSuccess)
                 {
-                    value = or.Content;
+                    msg = $"{or.ErrorCode} {or.Message}";
+                    return false;
                 }
-                else
-                {
-                    errMsg = or.Message;
-                }
-                return or.IsSuccess;
+                value = or.Content;
+                return true;
             }
             catch (Exception e)
             {
-                errMsg = e.Message;
+                msg = e.Message;
                 return false;
             }
         }
diff --git a/S7Driver/Properties/AssemblyInfo.cs b/S7Driver/Properties/AssemblyInfo.cs
index aa2b394..4959d2f 100644
--- a/S7Driver/Properties/AssemblyInfo.cs
+++ b/S7Driver/Properties/AssemblyInfo.cs
@@ -29,5 +29,5 @@
 //      鐢熸垚鍙�
 //      淇鍙�
 //
-[assembly: AssemblyVersion("1.0.0.4")]
-[assembly: AssemblyFileVersion("1.0.0.4")]
+[assembly: AssemblyVersion("1.0.0.5")]
+[assembly: AssemblyFileVersion("1.0.0.5")]
diff --git a/SimpleUtil/HMI.Simple.Util/AlarmUtil.cs b/SimpleUtil/HMI.Simple.Util/AlarmUtil.cs
index c6bcb1b..34c2ce9 100644
--- a/SimpleUtil/HMI.Simple.Util/AlarmUtil.cs
+++ b/SimpleUtil/HMI.Simple.Util/AlarmUtil.cs
@@ -4,19 +4,15 @@
 {
     public class AlarmUtil
     {
-        private static string ConcatAlarm(string source, string content)
-        {
-            return $"{source},{content}";
-        }
 
-        public static bool CreateAlarm(string source, string content, out string msg)
+        public static bool CreateAlarm(string source, string content, int level, out string msg)
         {
             msg = string.Empty;
             try
             {
                 using (var helper = new MSMQHelper("HMI_Alarm"))
                 {
-                    helper.Send("Alarm", ConcatAlarm(source, content));
+                    helper.Send("Alarm", source, content, level);
                     return true;
                 }
             }
@@ -34,16 +30,16 @@
         /// <param name="contents">鎶ヨ鍐呭锛屽悓涓�鏉ユ簮鐨勬姤璀﹀彲浠ュ鏉″悓鏃跺垱寤�</param>
         /// <param name="msg">鍒涘缓澶辫触鏃朵负寮傚父淇℃伅</param>
         /// <returns>鍒涘缓澶辫触鏃朵负寮傚父淇℃伅</returns>
-        public static bool CreateAlarm(string source, string[] contents, out string msg)
+        public static bool CreateAlarm(string source, string[] contents, int[] levels, out string msg)
         {
             msg = string.Empty;
             try
             {
                 using (var helper = new MSMQHelper("HMI_Alarm"))
                 {
-                    foreach (string content in contents)
+                    for (int i = 0; i < contents.Length; i++)
                     {
-                        helper.Send("Alarm", ConcatAlarm(source, content));
+                        helper.Send("Alarm", source, contents[i], levels[i]);
                     }
                     return true;
                 }
@@ -54,10 +50,13 @@
                 return false;
             }
         }
-
-        public static bool CreateAlarm(string content, out string msg)
-        {
-            return CreateAlarm("PC", content, out msg);
-        }
+        //private static string ConcatAlarm(string source, string content)
+        //{
+        //    return $"{source},{content}";
+        //}
+        //public static bool CreateAlarm(string content, int level, out string msg)
+        //{
+        //    return CreateAlarm("PC", content, level, out msg);
+        //}
     }
 }
diff --git a/SimpleUtil/HMI.Simple.Util/MSMQHelper.cs b/SimpleUtil/HMI.Simple.Util/MSMQHelper.cs
index 3232d7e..06d9f04 100644
--- a/SimpleUtil/HMI.Simple.Util/MSMQHelper.cs
+++ b/SimpleUtil/HMI.Simple.Util/MSMQHelper.cs
@@ -3,6 +3,15 @@
 
 namespace HMI.Simple.Util
 {
+
+    public class MQAlarm
+    {
+        public string Source { get; set; }
+        public string Content { get; set; }
+        public int Level { get; set; }
+    }
+
+
     public class MSMQHelper : IDisposable
     {
         /// <summary>
@@ -40,7 +49,7 @@
                     // 濡傛灉娑堟伅闃熷垪涓嶅瓨鍦紝鍒欏垱寤烘秷鎭鍨�
                     this.msgQueue = MessageQueue.Create(this.msmqPath);
                 }
-                this.msgQueue.Formatter = new XmlMessageFormatter(new Type[] { typeof(string) });
+                this.msgQueue.Formatter = new XmlMessageFormatter(new Type[] { typeof(MQAlarm) });
             }
             catch (Exception ex)
             {
@@ -53,11 +62,11 @@
         /// </summary>
         /// <param name="MessageID">娑堟伅ID</param>
         /// <param name="MsgString">瑕佸彂閫佺殑瀛楃涓�</param>
-        public void Send(string MessageID, string MsgString)
+        public void Send(string MessageID, string source, string MsgString, int level)
         {
             try
             {
-                this.msgQueue.Send(MsgString, MessageID);
+                this.msgQueue.Send(new MQAlarm { Source = source, Content = MsgString, Level = level }, MessageID);
             }
             catch (Exception ex)
             {
@@ -71,12 +80,12 @@
         /// <param name="TimeOut">瓒呮椂鏃堕棿(ms)</param>
         /// <param name="MessageID">娑堟伅ID</param>
         /// <param name="MsgString">娑堟伅鍐呭</param>
-        public void Receive(long TimeOut, out string MessageID, out string MsgString)
+        public void Receive(long TimeOut, out string MessageID, out MQAlarm alarm)
         {
             try
             {
                 MessageID = string.Empty;
-                MsgString = string.Empty;
+                alarm = null;
                 TimeSpan Time = new TimeSpan(TimeOut);
                 Message MsgReceived = null;
                 try
@@ -91,7 +100,7 @@
                 if (MsgReceived != null)
                 {
                     MessageID = MsgReceived.Label;
-                    MsgString = MsgReceived.Body.ToString();
+                    alarm = (MQAlarm)MsgReceived.Body;
                 }
             }
             catch (Exception ex)
diff --git a/SimpleUtil/HMI.TapApi/HMI.TapApi.csproj b/SimpleUtil/HMI.TapApi/HMI.TapApi.csproj
new file mode 100644
index 0000000..e2ab78f
--- /dev/null
+++ b/SimpleUtil/HMI.TapApi/HMI.TapApi.csproj
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{EEFDF6FE-41AD-4EB0-9CE6-87A07B88F9D7}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>HMI.TapApi</RootNamespace>
+    <AssemblyName>HMI.TapApi</AssemblyName>
+    <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <Deterministic>true</Deterministic>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="System" />
+    <Reference Include="System.Core" />
+    <Reference Include="System.Xml.Linq" />
+    <Reference Include="System.Data.DataSetExtensions" />
+    <Reference Include="Microsoft.CSharp" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Net.Http" />
+    <Reference Include="System.Xml" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="ProductUtil.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\..\..\..\Project\OpenTapEditor\OpenTap\BasicSteps\Tap.Plugins.BasicSteps.csproj">
+      <Project>{4BCFADC0-54F9-7032-B918-E4DD407CAA76}</Project>
+      <Name>Tap.Plugins.BasicSteps</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\..\..\..\Project\OpenTapEditor\OpenTap\Engine\Tap.Engine.csproj">
+      <Project>{efbd516d-3dc9-9d75-f082-48fbc76715f7}</Project>
+      <Name>Tap.Engine</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\..\..\..\Project\OpenTapEditor\Plugin\AddInPlugin\AddInPlugin.csproj">
+      <Project>{0d150ca1-d6ea-3362-7330-028939e66e1c}</Project>
+      <Name>AddInPlugin</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\..\HMI.Core\HMI.Core.csproj">
+      <Project>{3e3bf44a-0b69-45c5-9512-528cb48a7106}</Project>
+      <Name>HMI.Core</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\HMI.Simple.Util\HMI.Simple.Util.csproj">
+      <Project>{cf5bebe9-2803-49c3-b773-73d082e0984a}</Project>
+      <Name>HMI.Simple.Util</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\HMI.Util.Base\HMI.Util.Base.csproj">
+      <Project>{d59e25d1-9b27-46a1-8438-92661dd23fae}</Project>
+      <Name>HMI.Util.Base</Name>
+    </ProjectReference>
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+</Project>
\ No newline at end of file
diff --git a/SimpleUtil/HMI.TapApi/ProductUtil.cs b/SimpleUtil/HMI.TapApi/ProductUtil.cs
new file mode 100644
index 0000000..54a0877
--- /dev/null
+++ b/SimpleUtil/HMI.TapApi/ProductUtil.cs
@@ -0,0 +1,164 @@
+锘縰sing HMI.Simple.Util;
+using OpenTap;
+using OpenTap.Addin.Util;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+
+namespace HMI.TapApi
+{
+    public class ProductUtil
+    {
+        private static void WriteParameterData(object instance, ParameterData data)
+        {
+            TestPlanRun context = (TestPlanRun)instance;
+            string name = $"DUTSetting.{data.Name}";
+            if (data.IsArray)
+            {
+                Type type;
+                if (data.Type == "Bool")
+                {
+                    type = typeof(bool);
+                }
+                else if (data.Type == "Int")
+                {
+                    type = typeof(int);
+                }
+                else if (data.Type == "Double")
+                {
+                    type = typeof(double);
+                }
+                else
+                {
+                    type = typeof(string);
+                }
+                string[] arr = data.Value?.Split(',') ?? new string[0];
+                if (arr.Length > 0)
+                {
+                    //RuntimeVariable rv;
+                    //if (data.Type == "Int")
+                    //{
+                    //    int[] values = arr.Select(a => int.Parse(a)).ToArray();
+                    //    rv = new RuntimeVariable(context, name, values);
+                    //}
+                    //if (data.Type == "Double")
+                    //{
+                    //    double[] values = arr.Select(a => double.Parse(a)).ToArray();
+                    //    rv = new RuntimeVariable(context, name, values);
+                    //}
+                    //if (data.Type == "Bool")
+                    //{
+                    //    bool[] values = arr.Select(a => bool.Parse(a)).ToArray();
+                    //    rv = new RuntimeVariable(context, name, values);
+                    //}
+                    //else
+                    //{
+                    //    rv = new RuntimeVariable(context, name, arr);
+                    //}
+
+                    if (data.Type == "Int")
+                    {
+                        int[] values = arr.Select(a => int.Parse(a)).ToArray();
+                        context.Update(null, $"{ConstNames.FileGlobalsName}.{name}", values);
+                    }
+                    if (data.Type == "Double")
+                    {
+                        double[] values = arr.Select(a => double.Parse(a)).ToArray();
+                        context.Update(null, $"{ConstNames.FileGlobalsName}.{name}", values);
+                    }
+                    if (data.Type == "Bool")
+                    {
+                        bool[] values = arr.Select(a => bool.Parse(a)).ToArray();
+                        context.Update(null, $"{ConstNames.FileGlobalsName}.{name}", values);
+                    }
+                    else
+                    {
+                        context.Update(null, $"{ConstNames.FileGlobalsName}.{name}", arr);
+                    }
+                }
+            }
+            else
+            {
+                if (data.Type == "Bool")
+                {
+                    context.Update(null, $"{ConstNames.FileGlobalsName}.{name}", bool.Parse(data.Value));
+                }
+                else if (data.Type == "Int")
+                {
+                    context.Update(null, $"{ConstNames.FileGlobalsName}.{name}", int.Parse(data.Value));
+                }
+                else if (data.Type == "Double")
+                {
+                    context.Update(null, $"{ConstNames.FileGlobalsName}.{name}", double.Parse(data.Value));
+                }
+                else
+                {
+                    context.Update(null, $"{ConstNames.FileGlobalsName}.{name}", data.Value);
+                }
+            }
+        }
+
+        private static string GetDataFilePath()
+        {
+            string path = ParameterUtil.GetCommonParameterValue("ShareConfig", out _);
+            if (string.IsNullOrEmpty(path))
+            {
+                path = Path.Combine(HMI.Simple.Util.RegUtil.GetLocaltion(), "Config");
+            }
+            return path;
+        }
+
+        /// <summary>
+        /// 鏍规嵁浜у搧鍨嬪彿鍐欓厤缃�
+        /// </summary>
+        /// <param name="model">浜у搧鍨嬪彿</param>
+        /// <param name="instance">TestStand涓婁笅鏂�</param>
+        /// <param name="errMsg">寮傚父淇℃伅</param>
+        /// <returns>鏄惁鎴愬姛</returns>
+        public static bool WriteConfigToContextByModel(string model, object instance, out string errMsg)
+        {
+            errMsg = string.Empty;
+            try
+            {
+                string path = Path.Combine(GetDataFilePath(), "Product", "Parameter", $"Parameter_{model}.data");
+                return WriteConfigToContextByPath(path, instance, out errMsg);
+            }
+            catch (Exception ex)
+            {
+                errMsg = ex.Message;
+                return false;
+            }
+        }
+
+        /// <summary>
+        /// 鏍规嵁璺緞鍐欓厤缃�
+        /// </summary>
+        /// <param name="path">缁濆璺緞</param>
+        /// <param name="instance">TestStand涓婁笅鏂�</param>
+        /// <param name="errMsg">寮傚父淇℃伅</param>
+        /// <returns>鏄惁鎴愬姛</returns>
+        public static bool WriteConfigToContextByPath(string path, object instance, out string errMsg)
+        {
+            errMsg = string.Empty;
+            try
+            {
+                if (!File.Exists(path))
+                {
+                    throw new Exception($"鏈壘鍒拌閰嶇疆[{path}]锛岃妫�鏌�");
+                }
+                List<ParameterData> datas = XmlHelper.DeserializeFromXml<List<ParameterData>>(path);
+                foreach (var data in datas)
+                {
+                    WriteParameterData(instance, data);
+                }
+                return true;
+            }
+            catch (Exception ex)
+            {
+                errMsg = ex.Message;
+                return false;
+            }
+        }
+    }
+}
diff --git a/SimpleUtil/HMI.TapApi/Properties/AssemblyInfo.cs b/SimpleUtil/HMI.TapApi/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..5ae09f1
--- /dev/null
+++ b/SimpleUtil/HMI.TapApi/Properties/AssemblyInfo.cs
@@ -0,0 +1,33 @@
+锘縰sing System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// 鏈夊叧绋嬪簭闆嗙殑涓�鑸俊鎭敱浠ヤ笅
+// 鎺у埗銆傛洿鏀硅繖浜涚壒鎬у�煎彲淇敼
+// 涓庣▼搴忛泦鍏宠仈鐨勪俊鎭��
+[assembly: AssemblyTitle("HMI.TapApi")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("HMI.TapApi")]
+[assembly: AssemblyCopyright("Copyright 漏  2026")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// 灏� ComVisible 璁剧疆涓� false 浼氫娇姝ょ▼搴忛泦涓殑绫诲瀷
+//瀵� COM 缁勪欢涓嶅彲瑙併�傚鏋滈渶瑕佷粠 COM 璁块棶姝ょ▼搴忛泦涓殑绫诲瀷
+//璇峰皢姝ょ被鍨嬬殑 ComVisible 鐗规�ц缃负 true銆�
+[assembly: ComVisible(false)]
+
+// 濡傛灉姝ら」鐩悜 COM 鍏紑锛屽垯涓嬪垪 GUID 鐢ㄤ簬绫诲瀷搴撶殑 ID
+[assembly: Guid("eefdf6fe-41ad-4eb0-9ce6-87a07b88f9d7")]
+
+// 绋嬪簭闆嗙殑鐗堟湰淇℃伅鐢变笅鍒楀洓涓�肩粍鎴�: 
+//
+//      涓荤増鏈�
+//      娆$増鏈�
+//      鐢熸垚鍙�
+//      淇鍙�
+//
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/StationGlobalsReader/App.xaml b/StationGlobalsReader/App.xaml
new file mode 100644
index 0000000..84d066c
--- /dev/null
+++ b/StationGlobalsReader/App.xaml
@@ -0,0 +1,9 @@
+锘�<Application x:Class="StationGlobalsReader.App"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:local="clr-namespace:StationGlobalsReader"
+             StartupUri="MainWindow.xaml">
+    <Application.Resources>
+         
+    </Application.Resources>
+</Application>
diff --git a/StationGlobalsReader/App.xaml.cs b/StationGlobalsReader/App.xaml.cs
new file mode 100644
index 0000000..2fe9278
--- /dev/null
+++ b/StationGlobalsReader/App.xaml.cs
@@ -0,0 +1,13 @@
+锘縰sing System.Configuration;
+using System.Data;
+using System.Windows;
+
+namespace StationGlobalsReader;
+
+/// <summary>
+/// Interaction logic for App.xaml
+/// </summary>
+public partial class App : Application
+{
+}
+
diff --git a/StationGlobalsReader/AssemblyInfo.cs b/StationGlobalsReader/AssemblyInfo.cs
new file mode 100644
index 0000000..cc29e7f
--- /dev/null
+++ b/StationGlobalsReader/AssemblyInfo.cs
@@ -0,0 +1,10 @@
+using System.Windows;
+
+[assembly:ThemeInfo(
+    ResourceDictionaryLocation.None,            //where theme specific resource dictionaries are located
+                                                //(used if a resource is not found in the page,
+                                                // or application resource dictionaries)
+    ResourceDictionaryLocation.SourceAssembly   //where the generic resource dictionary is located
+                                                //(used if a resource is not found in the page,
+                                                // app, or any theme specific resource dictionaries)
+)]
diff --git a/StationGlobalsReader/MainWindow.xaml b/StationGlobalsReader/MainWindow.xaml
new file mode 100644
index 0000000..73c26bd
--- /dev/null
+++ b/StationGlobalsReader/MainWindow.xaml
@@ -0,0 +1,29 @@
+锘�<Window x:Class="StationGlobalsReader.MainWindow"
+        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+        xmlns:local="clr-namespace:StationGlobalsReader"
+        mc:Ignorable="d"
+        Title="MainWindow" Height="450" Width="800">
+    <Grid>
+        <Grid.RowDefinitions>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition/>
+        </Grid.RowDefinitions>
+        <Grid.ColumnDefinitions>
+            <ColumnDefinition Width="Auto"/>
+            <ColumnDefinition />
+        </Grid.ColumnDefinitions>
+        <WindowsFormsHost x:Name="host" Visibility="Collapsed"></WindowsFormsHost>
+        
+        <TextBlock Grid.Row="0" Grid.Column="0" Text="StationGlobals."/>
+        <TextBox Grid.Row="0" Grid.Column="1" Text="{Binding PropertyName}"/>
+
+        <TextBlock Grid.Row="1" Grid.Column="0" Text="鍊�"/>
+        <TextBox Grid.Row="1" Grid.Column="1" Text="{Binding Tagert}"/>
+
+        <Button Grid.Row="2" Grid.ColumnSpan="2" Click="Button_Click">璇诲彇</Button>
+    </Grid>
+</Window>
diff --git a/StationGlobalsReader/MainWindow.xaml.cs b/StationGlobalsReader/MainWindow.xaml.cs
new file mode 100644
index 0000000..d3fe27f
--- /dev/null
+++ b/StationGlobalsReader/MainWindow.xaml.cs
@@ -0,0 +1,50 @@
+锘縰sing CommunityToolkit.Mvvm.ComponentModel;
+using NationalInstruments.TestStand.Interop.API;
+using NationalInstruments.TestStand.Interop.UI.Ax;
+using System.Windows;
+using System.Windows.Forms.Integration;
+
+namespace StationGlobalsReader;
+
+/// <summary>
+/// Interaction logic for MainWindow.xaml
+/// </summary>
+[ObservableObject]
+public partial class MainWindow : Window
+{
+    public MainWindow()
+    {
+        axApplicationMgr = new AxApplicationMgr();
+        InitializeComponent();
+
+        this.engine = axApplicationMgr.GetEngine();
+        this.engine.PersistBreakpoints = false;
+        this.axApplicationMgr.IsEditor = true;
+        this.axApplicationMgr.LoginOnStart = false;
+        this.axApplicationMgr.Start();
+        ((System.ComponentModel.ISupportInitialize)(axApplicationMgr)).BeginInit();
+        host.Child = axApplicationMgr;
+        ((System.ComponentModel.ISupportInitialize)(axApplicationMgr)).EndInit();
+    }
+    private AxApplicationMgr axApplicationMgr;
+
+    private Engine engine = null;
+
+    [ObservableProperty]
+    private string propertyName;
+
+    [ObservableProperty]
+    private string target;
+
+    private void Button_Click(object sender, RoutedEventArgs e)
+    {
+        try
+        {
+            Target = engine.Globals.GetValString($"StationGlobals.{PropertyName}", 0);
+        }
+        catch (Exception ex)
+        {
+            MessageBox.Show(ex.Message);
+        }
+    }
+}
\ No newline at end of file
diff --git a/StationGlobalsReader/StationGlobalsReader.csproj b/StationGlobalsReader/StationGlobalsReader.csproj
new file mode 100644
index 0000000..516e0ac
--- /dev/null
+++ b/StationGlobalsReader/StationGlobalsReader.csproj
@@ -0,0 +1,28 @@
+锘�<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>WinExe</OutputType>
+    <TargetFramework>net472</TargetFramework>
+    <Nullable>enable</Nullable>
+    <LangVersion>10.0</LangVersion>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <UseWPF>true</UseWPF>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
+    <PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.135" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Reference Include="NationalInstruments.TestStand.Interop.API">
+      <HintPath>..\Libs\NationalInstruments.TestStand.Interop.API.dll</HintPath>
+    </Reference>
+    <Reference Include="NationalInstruments.TestStand.Interop.UI.AxControls">
+      <HintPath>..\Libs\NationalInstruments.TestStand.Interop.UI.AxControls.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Windows.Forms" />
+    <Reference Include="WindowsFormsIntegration" />
+  </ItemGroup>
+
+</Project>
diff --git a/WPFProject/App.config b/WPFProject/App.config
index fabccd6..0985874 100644
--- a/WPFProject/App.config
+++ b/WPFProject/App.config
@@ -3,7 +3,8 @@
 	<configSections>
 		<section name="TestResultConfig" type="System.Configuration.SingleTagSectionHandler" />
 	</configSections>
-	<TestResultConfig Row="2" Column="2" PortStart="6660" PositionColumn="4" PositionCount="20" NameStr="A,B,C,D,E,F,G" CodeParserType="Model" />
+	<TestResultConfig Row="2" Column="2" PortStart="6660" PositionColumn="4" PositionCount="20" NameStr="A,B,C,D,E,F,G" 
+					  CodeParserType="Model"/>
 	<startup>
 		<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
 	</startup>
diff --git a/WPFProject/BuildCmd.bat b/WPFProject/BuildCmd.bat
index cfde15f..2c58c66 100644
--- a/WPFProject/BuildCmd.bat
+++ b/WPFProject/BuildCmd.bat
@@ -1,15 +1,30 @@
 锘縄F "$(ConfigurationName)"=="Release" Confuser.CLI.exe $(TargeDir)..\ConfuserEx\$(TargetFileName).crproj
 
+
+mkdir "$(TargetDir)OpenTap"
+
+copy "$(TargeDir)HMI.Core.dll" "$(TargetDir)OpenTap"
+copy "$(TargeDir)HMI.Simple.Util.dll" "$(TargetDir)OpenTap"
+copy "$(TargeDir)HMI.Util.Base.dll" "$(TargetDir)OpenTap"
+
 move /y "$(TargeDir)HMI.Core.dll" $(TargeDir)Libs\HMI.Core.dll"
 move /y "$(TargeDir)HMI.Base.dll" $(TargeDir)Libs\HMI.Base.dll"
 move /y "$(TargeDir)HMI.UI.dll" $(TargeDir)Libs\HMI.UI.dll"
 move /y "$(TargeDir)HMI.Util.dll" $(TargeDir)Libs\HMI.Util.dll"
 move /y "$(TargeDir)ConnectionUtil.dll" $(TargeDir)Libs\ConnectionUtil.dll"
+move /y "$(TargeDir)HMI.StdControl.dll" $(TargeDir)Libs\HMI.StdControl.dll"
+move /y "$(TargeDir)S7Driver.dll" $(TargeDir)Libs\S7Driver.dll"
+move /y "$(TargeDir)HslCommunication.dll" $(TargeDir)Libs\HslCommunication.dll"
+move /y "$(TargeDir)PLCHelper.dll" $(TargeDir)Libs\PLCHelper.dll"
+move /y "$(TargeDir)ModBusTcpController.dll" $(TargeDir)Libs\ModBusTcpController.dll"
 
 move /y "$(TargeDir)HMI.LogUtil.dll" $(TargeDir)Api\HMI.LogUtil.dll"
 move /y "$(TargeDir)HMI.SerialData.Util.dll" $(TargeDir)Api\HMI.SerialData.Util.dll"
 move /y "$(TargeDir)HMI.Simple.Util.dll" $(TargeDir)Api\HMI.Simple.Util.dll"
 move /y "$(TargeDir)HMI.Util.Base.dll" $(TargeDir)Api\HMI.Util.Base.dll"
+move /y "$(TargeDir)CellStepSender.dll" $(TargeDir)Api\CellStepSender.dll"
+
+mkdir "$(TargetDir)Plugin"
 
 move /y "$(TargeDir)HMI.AlarmInfoManage.dll" $(TargeDir)Plugin\HMI.AlarmInfoManage.dll"
 move /y "$(TargeDir)HMI.DataViewer.dll" $(TargeDir)Plugin\HMI.DataViewer.dll"
@@ -30,6 +45,17 @@
 move /y "$(TargeDir)Plugin.MaterialOpt.dll" $(TargeDir)Plugin\Plugin.MaterialOpt.dll"
 move /y "$(TargeDir)Plugin.DODriverControl.dll" $(TargeDir)Plugin\Plugin.DODriverControl.dll"
 move /y "$(TargeDir)Plugin.ModelManage.dll" $(TargeDir)Plugin\Plugin.ModelManage.dll"
+move /y "$(TargeDir)Plugin.CurrentSensorControl.dll" $(TargeDir)Plugin\Plugin.CurrentSensorControl.dll"
+move /y "$(TargeDir)Plugin.TsSupport.dll" $(TargeDir)Plugin\Plugin.TsSupport.dll"
+move /y "$(TargeDir)Plugin.DataViewer.dll" $(TargeDir)Plugin\Plugin.DataViewer.dll"
+move /y "$(TargeDir)Util.ManualDebug.dll" $(TargeDir)Plugin\Util.ManualDebug.dll"
+move /y "$(TargeDir)Plugin.QualityReport.dll" $(TargeDir)Plugin\Plugin.QualityReport.dll"
+move /y "$(TargeDir)Plugin.BurnIn.dll" $(TargeDir)Plugin\Plugin.BurnIn.dll"
+
+move /y "$(TargeDir)AddInPlugin.dll" "$(TargetDir)OpenTap\AddInPlugin.dll"
+move /y "$(TargeDir)OpenTap.dll" "$(TargetDir)OpenTap\OpenTap.dll"
+move /y "$(TargeDir)OpenTap.Plugins.BasicSteps.dll" "$(TargetDir)OpenTap\OpenTap.Plugins.BasicSteps.dll"
+move /y "$(TargeDir)HMI.TapApi.dll" "$(TargetDir)OpenTap\HMI.TapApi.dll"
 
 setlocal enabledelayedexpansion
 
@@ -46,13 +72,13 @@
 )
 
 for /d %%D in ("%source_folder%\*") do (
-    if /I not "%%~nxD"=="Api" if /I not "%%~nxD"=="Config" if /I not "%%~nxD"=="Icon" if /I not "%%~nxD"=="Lang" if /I not "%%~nxD"=="Libs" if /I not "%%~nxD"=="Log" if /I not "%%~nxD"=="Plugin" if /I not "%%~nxD"=="TestPlatform.exe.WebView2" (
+    if /I not "%%~nxD"=="Api" if /I not "%%~nxD"=="Config" if /I not "%%~nxD"=="Icon" if /I not "%%~nxD"=="Lang" if /I not "%%~nxD"=="Libs" if /I not "%%~nxD"=="Log" if /I not "%%~nxD"=="Plugin" if /I not "%%~nxD"=="TestPlatform.exe.WebView2" if /I not "%%~nxD"=="OpenTap" (
         move /Y "%%D" "%destination_folder%"
     )
 )
 
 for %%F in ("%source_folder%\*") do (
-    if /I not "%%~nxF"=="$(TargetFileName)" if /I not "%%~nxF"=="$(TargetFileName).config" if /I not "%%~nxF"=="$(TargetName).pdb" if /I not "%%~nxF"=="clear.bat" if /I not "%%~nxF"=="瀹夎MSMQ.bat" (
+    if /I not "%%~nxF"=="$(TargetFileName)" if /I not "%%~nxF"=="$(TargetFileName).config" if /I not "%%~nxF"=="$(TargetName).pdb" if /I not "%%~nxF"=="clear.bat" if /I not "%%~nxF"=="瀹夎MSMQ.bat" if /I not "%%~nxF"=="Settings.ini" (
         move /Y "%%F" "%destination_folder%"
     )
 )
diff --git a/WPFProject/Plugin/Here put your plugins.txt b/WPFProject/Plugin/Here put your plugins.txt
deleted file mode 100644
index 5f28270..0000000
--- a/WPFProject/Plugin/Here put your plugins.txt
+++ /dev/null
@@ -1 +0,0 @@
-锘�
\ No newline at end of file
diff --git a/WPFProject/ResourceListener.cs b/WPFProject/ResourceListener.cs
index cbd8f05..3759620 100644
--- a/WPFProject/ResourceListener.cs
+++ b/WPFProject/ResourceListener.cs
@@ -1,10 +1,8 @@
-锘縰sing HMI.Simple.Util;
-using log4net;
+锘縰sing log4net;
 using System;
 using System.Diagnostics;
 using System.Management;
 using System.Timers;
-using System.Xml.Linq;
 
 namespace WPFProject
 {
@@ -97,11 +95,11 @@
                         totalCpuRate = totalCpuRate,
                         totalRemRate = usagePercent
                     });
-                    log.Info(info);
-                    //if (usagePercent > 80)
-                    //{
-                    //    AlarmUtil.CreateAlarm("App", "鍐呭瓨鍗犵敤杩囬珮锛岃秴80%", out _);
-                    //}
+                    if (usagePercent > 80)
+                    {
+                        log.Info(info);
+                        //AlarmUtil.CreateAlarm("App", "鍐呭瓨鍗犵敤杩囬珮锛岃秴80%", out _);
+                    }
                 }
             }
             catch (Exception ex)
diff --git a/WPFProject/WPFProject.csproj b/WPFProject/WPFProject.csproj
index 10f5341..4260917 100644
--- a/WPFProject/WPFProject.csproj
+++ b/WPFProject/WPFProject.csproj
@@ -326,9 +326,6 @@
     <Resource Include="Icon\t2.png" />
     <Resource Include="Icon\t3.png" />
     <Resource Include="Icon\t4.png" />
-    <Content Include="Plugin\Here put your plugins.txt">
-      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
-    </Content>
     <Resource Include="Resources\Background.png" />
     <Resource Include="Resources\JingCeLogo.png" />
     <EmbeddedResource Include="Controls\HolderForm.resx">
@@ -449,9 +446,17 @@
       <Project>{49db0c9b-b239-4608-a91e-f5ed43afaf96}</Project>
       <Name>ModBusTcpController</Name>
     </ProjectReference>
+    <ProjectReference Include="..\Plugin.BurnIn\Plugin.BurnIn.csproj">
+      <Project>{7964f444-d2c6-4c11-87f4-0df4faec10b6}</Project>
+      <Name>Plugin.BurnIn</Name>
+    </ProjectReference>
     <ProjectReference Include="..\Plugin.DataViewer\Plugin.DataViewer.csproj">
       <Project>{dbfe5040-963f-4bbf-93fb-26b3f18fda90}</Project>
       <Name>Plugin.DataViewer</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\Plugin.QualityReport\Plugin.QualityReport.csproj">
+      <Project>{0fd55872-b9f6-41ad-8d3f-6d7a7d3f1233}</Project>
+      <Name>Plugin.QualityReport</Name>
     </ProjectReference>
     <ProjectReference Include="..\Plugin.SeqEditor\Plugin.SeqEditor.csproj">
       <Project>{1260f995-1ae9-17b6-feb5-c430c1416bec}</Project>
@@ -564,6 +569,13 @@
   <PropertyGroup>
     <PostBuildEvent>IF "$(ConfigurationName)"=="Release" Confuser.CLI.exe $(TargeDir)..\ConfuserEx\$(TargetFileName).crproj
 
+
+mkdir "$(TargetDir)OpenTap"
+
+copy "$(TargeDir)HMI.Core.dll" "$(TargetDir)OpenTap"
+copy "$(TargeDir)HMI.Simple.Util.dll" "$(TargetDir)OpenTap"
+copy "$(TargeDir)HMI.Util.Base.dll" "$(TargetDir)OpenTap"
+
 move /y "$(TargeDir)HMI.Core.dll" $(TargeDir)Libs\HMI.Core.dll"
 move /y "$(TargeDir)HMI.Base.dll" $(TargeDir)Libs\HMI.Base.dll"
 move /y "$(TargeDir)HMI.UI.dll" $(TargeDir)Libs\HMI.UI.dll"
@@ -580,6 +592,8 @@
 move /y "$(TargeDir)HMI.Simple.Util.dll" $(TargeDir)Api\HMI.Simple.Util.dll"
 move /y "$(TargeDir)HMI.Util.Base.dll" $(TargeDir)Api\HMI.Util.Base.dll"
 move /y "$(TargeDir)CellStepSender.dll" $(TargeDir)Api\CellStepSender.dll"
+
+mkdir "$(TargetDir)Plugin"
 
 move /y "$(TargeDir)HMI.AlarmInfoManage.dll" $(TargeDir)Plugin\HMI.AlarmInfoManage.dll"
 move /y "$(TargeDir)HMI.DataViewer.dll" $(TargeDir)Plugin\HMI.DataViewer.dll"
@@ -604,6 +618,13 @@
 move /y "$(TargeDir)Plugin.TsSupport.dll" $(TargeDir)Plugin\Plugin.TsSupport.dll"
 move /y "$(TargeDir)Plugin.DataViewer.dll" $(TargeDir)Plugin\Plugin.DataViewer.dll"
 move /y "$(TargeDir)Util.ManualDebug.dll" $(TargeDir)Plugin\Util.ManualDebug.dll"
+move /y "$(TargeDir)Plugin.QualityReport.dll" $(TargeDir)Plugin\Plugin.QualityReport.dll"
+move /y "$(TargeDir)Plugin.BurnIn.dll" $(TargeDir)Plugin\Plugin.BurnIn.dll"
+
+move /y "$(TargeDir)AddInPlugin.dll" "$(TargetDir)OpenTap\AddInPlugin.dll"
+move /y "$(TargeDir)OpenTap.dll" "$(TargetDir)OpenTap\OpenTap.dll"
+move /y "$(TargeDir)OpenTap.Plugins.BasicSteps.dll" "$(TargetDir)OpenTap\OpenTap.Plugins.BasicSteps.dll"
+move /y "$(TargeDir)HMI.TapApi.dll" "$(TargetDir)OpenTap\HMI.TapApi.dll"
 
 setlocal enabledelayedexpansion
 
@@ -620,7 +641,7 @@
 )
 
 for /d %25%25D in ("%25source_folder%25\*") do (
-    if /I not "%25%25~nxD"=="Api" if /I not "%25%25~nxD"=="Config" if /I not "%25%25~nxD"=="Icon" if /I not "%25%25~nxD"=="Lang" if /I not "%25%25~nxD"=="Libs" if /I not "%25%25~nxD"=="Log" if /I not "%25%25~nxD"=="Plugin" if /I not "%25%25~nxD"=="TestPlatform.exe.WebView2" (
+    if /I not "%25%25~nxD"=="Api" if /I not "%25%25~nxD"=="Config" if /I not "%25%25~nxD"=="Icon" if /I not "%25%25~nxD"=="Lang" if /I not "%25%25~nxD"=="Libs" if /I not "%25%25~nxD"=="Log" if /I not "%25%25~nxD"=="Plugin" if /I not "%25%25~nxD"=="TestPlatform.exe.WebView2" if /I not "%25%25~nxD"=="OpenTap" (
         move /Y "%25%25D" "%25destination_folder%25"
     )
 )
diff --git a/WPFProject/WPFProject.sln b/WPFProject/WPFProject.sln
index 9b2595b..5546767 100644
--- a/WPFProject/WPFProject.sln
+++ b/WPFProject/WPFProject.sln
@@ -85,6 +85,20 @@
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Util.ManualDebug", "..\Util.ManualDebug\Util.ManualDebug.csproj", "{D0944E62-EE52-4ABE-832A-A09D98814DB1}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tap.Engine", "..\..\..\Project\OpenTapEditor\OpenTap\Engine\Tap.Engine.csproj", "{EFBD516D-3DC9-9D75-F082-48FBC76715F7}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AddInPlugin", "..\..\..\Project\OpenTapEditor\Plugin\AddInPlugin\AddInPlugin.csproj", "{0D150CA1-D6EA-3362-7330-028939E66E1C}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tap.Plugins.BasicSteps", "..\..\..\Project\OpenTapEditor\OpenTap\BasicSteps\Tap.Plugins.BasicSteps.csproj", "{4BCFADC0-54F9-7032-B918-E4DD407CAA76}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Plugin.QualityReport", "..\Plugin.QualityReport\Plugin.QualityReport.csproj", "{0FD55872-B9F6-41AD-8D3F-6D7A7D3F1233}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HMI.TapApi", "..\SimpleUtil\HMI.TapApi\HMI.TapApi.csproj", "{EEFDF6FE-41AD-4EB0-9CE6-87A07B88F9D7}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StationGlobalsReader", "..\StationGlobalsReader\StationGlobalsReader.csproj", "{87F7AD77-45E9-4185-A4F7-13041796E7B6}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Plugin.BurnIn", "..\Plugin.BurnIn\Plugin.BurnIn.csproj", "{7964F444-D2C6-4C11-87F4-0DF4FAEC10B6}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -575,6 +589,90 @@
 		{D0944E62-EE52-4ABE-832A-A09D98814DB1}.Release|x64.Build.0 = Release|Any CPU
 		{D0944E62-EE52-4ABE-832A-A09D98814DB1}.Release|x86.ActiveCfg = Release|x86
 		{D0944E62-EE52-4ABE-832A-A09D98814DB1}.Release|x86.Build.0 = Release|x86
+		{EFBD516D-3DC9-9D75-F082-48FBC76715F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{EFBD516D-3DC9-9D75-F082-48FBC76715F7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{EFBD516D-3DC9-9D75-F082-48FBC76715F7}.Debug|x64.ActiveCfg = Debug|x64
+		{EFBD516D-3DC9-9D75-F082-48FBC76715F7}.Debug|x64.Build.0 = Debug|x64
+		{EFBD516D-3DC9-9D75-F082-48FBC76715F7}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{EFBD516D-3DC9-9D75-F082-48FBC76715F7}.Debug|x86.Build.0 = Debug|Any CPU
+		{EFBD516D-3DC9-9D75-F082-48FBC76715F7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{EFBD516D-3DC9-9D75-F082-48FBC76715F7}.Release|Any CPU.Build.0 = Release|Any CPU
+		{EFBD516D-3DC9-9D75-F082-48FBC76715F7}.Release|x64.ActiveCfg = Release|x64
+		{EFBD516D-3DC9-9D75-F082-48FBC76715F7}.Release|x64.Build.0 = Release|x64
+		{EFBD516D-3DC9-9D75-F082-48FBC76715F7}.Release|x86.ActiveCfg = Release|Any CPU
+		{EFBD516D-3DC9-9D75-F082-48FBC76715F7}.Release|x86.Build.0 = Release|Any CPU
+		{0D150CA1-D6EA-3362-7330-028939E66E1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{0D150CA1-D6EA-3362-7330-028939E66E1C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{0D150CA1-D6EA-3362-7330-028939E66E1C}.Debug|x64.ActiveCfg = Debug|x64
+		{0D150CA1-D6EA-3362-7330-028939E66E1C}.Debug|x64.Build.0 = Debug|x64
+		{0D150CA1-D6EA-3362-7330-028939E66E1C}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{0D150CA1-D6EA-3362-7330-028939E66E1C}.Debug|x86.Build.0 = Debug|Any CPU
+		{0D150CA1-D6EA-3362-7330-028939E66E1C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{0D150CA1-D6EA-3362-7330-028939E66E1C}.Release|Any CPU.Build.0 = Release|Any CPU
+		{0D150CA1-D6EA-3362-7330-028939E66E1C}.Release|x64.ActiveCfg = Release|x64
+		{0D150CA1-D6EA-3362-7330-028939E66E1C}.Release|x64.Build.0 = Release|x64
+		{0D150CA1-D6EA-3362-7330-028939E66E1C}.Release|x86.ActiveCfg = Release|Any CPU
+		{0D150CA1-D6EA-3362-7330-028939E66E1C}.Release|x86.Build.0 = Release|Any CPU
+		{4BCFADC0-54F9-7032-B918-E4DD407CAA76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{4BCFADC0-54F9-7032-B918-E4DD407CAA76}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{4BCFADC0-54F9-7032-B918-E4DD407CAA76}.Debug|x64.ActiveCfg = Debug|x64
+		{4BCFADC0-54F9-7032-B918-E4DD407CAA76}.Debug|x64.Build.0 = Debug|x64
+		{4BCFADC0-54F9-7032-B918-E4DD407CAA76}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{4BCFADC0-54F9-7032-B918-E4DD407CAA76}.Debug|x86.Build.0 = Debug|Any CPU
+		{4BCFADC0-54F9-7032-B918-E4DD407CAA76}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{4BCFADC0-54F9-7032-B918-E4DD407CAA76}.Release|Any CPU.Build.0 = Release|Any CPU
+		{4BCFADC0-54F9-7032-B918-E4DD407CAA76}.Release|x64.ActiveCfg = Release|x64
+		{4BCFADC0-54F9-7032-B918-E4DD407CAA76}.Release|x64.Build.0 = Release|x64
+		{4BCFADC0-54F9-7032-B918-E4DD407CAA76}.Release|x86.ActiveCfg = Release|Any CPU
+		{4BCFADC0-54F9-7032-B918-E4DD407CAA76}.Release|x86.Build.0 = Release|Any CPU
+		{0FD55872-B9F6-41AD-8D3F-6D7A7D3F1233}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{0FD55872-B9F6-41AD-8D3F-6D7A7D3F1233}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{0FD55872-B9F6-41AD-8D3F-6D7A7D3F1233}.Debug|x64.ActiveCfg = Debug|x64
+		{0FD55872-B9F6-41AD-8D3F-6D7A7D3F1233}.Debug|x64.Build.0 = Debug|x64
+		{0FD55872-B9F6-41AD-8D3F-6D7A7D3F1233}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{0FD55872-B9F6-41AD-8D3F-6D7A7D3F1233}.Debug|x86.Build.0 = Debug|Any CPU
+		{0FD55872-B9F6-41AD-8D3F-6D7A7D3F1233}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{0FD55872-B9F6-41AD-8D3F-6D7A7D3F1233}.Release|Any CPU.Build.0 = Release|Any CPU
+		{0FD55872-B9F6-41AD-8D3F-6D7A7D3F1233}.Release|x64.ActiveCfg = Release|x64
+		{0FD55872-B9F6-41AD-8D3F-6D7A7D3F1233}.Release|x64.Build.0 = Release|x64
+		{0FD55872-B9F6-41AD-8D3F-6D7A7D3F1233}.Release|x86.ActiveCfg = Release|Any CPU
+		{0FD55872-B9F6-41AD-8D3F-6D7A7D3F1233}.Release|x86.Build.0 = Release|Any CPU
+		{EEFDF6FE-41AD-4EB0-9CE6-87A07B88F9D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{EEFDF6FE-41AD-4EB0-9CE6-87A07B88F9D7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{EEFDF6FE-41AD-4EB0-9CE6-87A07B88F9D7}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{EEFDF6FE-41AD-4EB0-9CE6-87A07B88F9D7}.Debug|x64.Build.0 = Debug|Any CPU
+		{EEFDF6FE-41AD-4EB0-9CE6-87A07B88F9D7}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{EEFDF6FE-41AD-4EB0-9CE6-87A07B88F9D7}.Debug|x86.Build.0 = Debug|Any CPU
+		{EEFDF6FE-41AD-4EB0-9CE6-87A07B88F9D7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{EEFDF6FE-41AD-4EB0-9CE6-87A07B88F9D7}.Release|Any CPU.Build.0 = Release|Any CPU
+		{EEFDF6FE-41AD-4EB0-9CE6-87A07B88F9D7}.Release|x64.ActiveCfg = Release|Any CPU
+		{EEFDF6FE-41AD-4EB0-9CE6-87A07B88F9D7}.Release|x64.Build.0 = Release|Any CPU
+		{EEFDF6FE-41AD-4EB0-9CE6-87A07B88F9D7}.Release|x86.ActiveCfg = Release|Any CPU
+		{EEFDF6FE-41AD-4EB0-9CE6-87A07B88F9D7}.Release|x86.Build.0 = Release|Any CPU
+		{87F7AD77-45E9-4185-A4F7-13041796E7B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{87F7AD77-45E9-4185-A4F7-13041796E7B6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{87F7AD77-45E9-4185-A4F7-13041796E7B6}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{87F7AD77-45E9-4185-A4F7-13041796E7B6}.Debug|x64.Build.0 = Debug|Any CPU
+		{87F7AD77-45E9-4185-A4F7-13041796E7B6}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{87F7AD77-45E9-4185-A4F7-13041796E7B6}.Debug|x86.Build.0 = Debug|Any CPU
+		{87F7AD77-45E9-4185-A4F7-13041796E7B6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{87F7AD77-45E9-4185-A4F7-13041796E7B6}.Release|Any CPU.Build.0 = Release|Any CPU
+		{87F7AD77-45E9-4185-A4F7-13041796E7B6}.Release|x64.ActiveCfg = Release|Any CPU
+		{87F7AD77-45E9-4185-A4F7-13041796E7B6}.Release|x64.Build.0 = Release|Any CPU
+		{87F7AD77-45E9-4185-A4F7-13041796E7B6}.Release|x86.ActiveCfg = Release|Any CPU
+		{87F7AD77-45E9-4185-A4F7-13041796E7B6}.Release|x86.Build.0 = Release|Any CPU
+		{7964F444-D2C6-4C11-87F4-0DF4FAEC10B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{7964F444-D2C6-4C11-87F4-0DF4FAEC10B6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{7964F444-D2C6-4C11-87F4-0DF4FAEC10B6}.Debug|x64.ActiveCfg = Debug|x64
+		{7964F444-D2C6-4C11-87F4-0DF4FAEC10B6}.Debug|x64.Build.0 = Debug|x64
+		{7964F444-D2C6-4C11-87F4-0DF4FAEC10B6}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{7964F444-D2C6-4C11-87F4-0DF4FAEC10B6}.Debug|x86.Build.0 = Debug|Any CPU
+		{7964F444-D2C6-4C11-87F4-0DF4FAEC10B6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{7964F444-D2C6-4C11-87F4-0DF4FAEC10B6}.Release|Any CPU.Build.0 = Release|Any CPU
+		{7964F444-D2C6-4C11-87F4-0DF4FAEC10B6}.Release|x64.ActiveCfg = Release|Any CPU
+		{7964F444-D2C6-4C11-87F4-0DF4FAEC10B6}.Release|x64.Build.0 = Release|Any CPU
+		{7964F444-D2C6-4C11-87F4-0DF4FAEC10B6}.Release|x86.ActiveCfg = Release|Any CPU
+		{7964F444-D2C6-4C11-87F4-0DF4FAEC10B6}.Release|x86.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -586,6 +684,7 @@
 		{D59E25D1-9B27-46A1-8438-92661DD23FAE} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
 		{F8B8B6B6-821B-4FF6-B556-9CAB840EA57A} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
 		{34FFBAFE-478F-4B6E-95E7-92A949011A9B} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
+		{EEFDF6FE-41AD-4EB0-9CE6-87A07B88F9D7} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {784CEA1D-A890-4625-B0C1-72294A6B4803}

--
Gitblit v1.9.1