alielie
2026-06-15 f08871fe7d148d667f2f11f80f64de71a0d9504c
优化标准老化界面
12个文件已修改
10个文件已添加
2 文件已重命名
1653 ■■■■■ 已修改文件
HMI.Util/Alarm/AlarmUtil.cs 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/BurnInUI.xaml.cs 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/DataTable.xaml 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/DataTable.xaml.cs 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/ManualDebug/ManualCell.xaml 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/ManualDebug/ManualCell.xaml.cs 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/ManualDebug/ManualDebug.xaml 212 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/ManualDebug/ManualDebug.xaml.cs 691 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/ManualDebug/ManualDebugStyle.xaml 130 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/ManualDebug/ManualDebugWindow.xaml 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/ManualDebug/ManualDebugWindow.xaml.cs 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/ManualDebug/StatusBtn.xaml 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/ManualDebug/StatusBtn.xaml.cs 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/ManualDebug/StatusCell.xaml 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/ManualDebug/StatusCell.xaml.cs 71 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/Plugin.BurnIn.csproj 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/Scanner.xaml.cs 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/SingleCell.xaml.cs 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.BurnIn/TestResultView.xaml.cs 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/DataTable.xaml.cs 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TsExecution/SingleControl.xaml 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TsExecution/SingleControl.xaml.cs 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SimpleUtil/HMI.Simple.Util/DataSender.cs 103 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SimpleUtil/HMI.Simple.Util/HMI.Simple.Util.csproj 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.Util/Alarm/AlarmUtil.cs
@@ -20,7 +20,7 @@
        public event EventHandler<LoadMsg> LoadMessageAppend;
        private static ILog log = LogManager.GetLogger("AlarmHelper");
        internal static ILog log = LogManager.GetLogger("AlarmHelper");
        internal AlarmHelper()
        {
@@ -353,10 +353,12 @@
        public static void Init()
        {
            AlarmHelper.log.Info("AlarmUtil initing...");
            Instance = new AlarmHelper();
            Instance.LoadMessageAppend += Instance_LoadMessageAppend; ;
            Instance.InitInstance();
            Instance.OnAlarm += Instance_OnAlarm;
            AlarmHelper.log.Info("AlarmUtil inited finished.");
        }
        private static void Instance_LoadMessageAppend(object sender, LoadMsg e)
Plugin.BurnIn/BurnInUI.xaml.cs
@@ -128,12 +128,6 @@
                }
                if (EventCase == 8)
                {
                    IsRunning = false;
                    return;
                }
                if (EventCase != 10014) return;
                if (MethodCode == 0)
@@ -155,6 +149,10 @@
                {
                    /// 更新生产状态
                    produceStatus = strData;
                    if (string.Equals(strData, "stop", StringComparison.OrdinalIgnoreCase))
                    {
                        IsRunning = false;
                    }
                    return;
                }
            }
@@ -391,7 +389,7 @@
                log.Info("总停止按钮按下");
                testResultView.StopTest();
                /// 已经加了WaitForExit,这里可以改为false
                IsRunning = false;
                //IsRunning = false;
            }
            catch (Exception ex)
            {
Plugin.BurnIn/DataTable.xaml
@@ -16,6 +16,7 @@
             BorderBrush="Black"
             BorderThickness="1"
             x:Name="control"
             PreviewMouseRightButtonDown="control_PreviewMouseRightButtonDown"
             Loaded="UserControl_Loaded"
             d:DesignHeight="450" d:DesignWidth="800">
    <UserControl.Resources>
Plugin.BurnIn/DataTable.xaml.cs
@@ -1,4 +1,5 @@
using HMI.Base;
using HMI.Core.Manager;
using HMI.Core.Setting;
using HMI.Core.Util;
using HMI.LogUtil;
@@ -584,12 +585,12 @@
                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]
                    value = values[2],
                    unit = values[3],
                    limitUp = values[4],
                    limitDown = values[5],
                    result = values[6],
                    updateTime = values[7]
                });
            });
            //session.Send("1");
@@ -599,17 +600,10 @@
        {
            infoTable.UpdateNumber(infoTable.okCount + ok, infoTable.ngCount + ng);
            if (ParamSetting.ProbeByModel)
            {
            }
            else
            {
                IniFileHelper.WriteIniData($"Oven_{index}", "Pass", infoTable.okCount.ToString(),
            IniFileHelper.WriteIniData($"Oven_{index}", "Pass", infoTable.okCount.ToString(),
                   this.CountIniPath);
                IniFileHelper.WriteIniData($"Oven_{index}", "Fail", infoTable.ngCount.ToString(),
                    this.CountIniPath);
            }
            IniFileHelper.WriteIniData($"Oven_{index}", "Fail", infoTable.ngCount.ToString(),
                this.CountIniPath);
        }
        /// <summary>
@@ -883,5 +877,52 @@
            string key = $"StationHMI.UUTHMI[{this.index}].Abort";
            parent.executionWrapper?.SetStationGlobalValue(key, 0, true);
        }
        private void MenuItem_Click(object sender, RoutedEventArgs e)
        {
            string manualDebugSeq = ParameterManager.GetCommonParameterByName("ManualDebugSeqPath")?.Value;
            if (string.IsNullOrEmpty(manualDebugSeq))
            {
                MsgBoxHelper2.Error("请先设置手动调试序列路径[ManualDebugSeqPath]!");
                return;
            }
            string modelPath = parent.testModelFile;
            var win = new ManualDebugWindow(manualDebugSeq, modelPath, true, cells, mainGrid.Columns)
            {
                CellCount = cells.Length,
                Title = $"{this.title}手动调试",
                Owner = Window.GetWindow(this),
                OvenIndex = this.index,
                OvenName = this.title,
                CellName = this.title,
                CellIndex = -1
            };
            win.ShowDialog();
        }
        private void ContextMenu_Closed(object sender, RoutedEventArgs e)
        {
            this.ContextMenu = null;
        }
        private void control_PreviewMouseRightButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            if (!parent.inManualDebugMode)
            {
                return;
            }
            ContextMenu contextMenu = new ContextMenu();
            contextMenu.Closed += ContextMenu_Closed;
            var item = new MenuItem
            {
                IsCheckable = false,
                Name = "manualDebugItem",
                Header = this.lang("手动调试"),
            };
            item.Click += MenuItem_Click;
            contextMenu.Items.Add(item);
            this.ContextMenu = contextMenu;
        }
    }
}
Plugin.BurnIn/ManualDebug/ManualCell.xaml
New file
@@ -0,0 +1,33 @@
<UserControl x:Class="Plugin.BurnIn.ManualCell"
             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:pu="https://opensource.panuon.com/wpf-ui"
             mc:Ignorable="d" Padding="5" MaxHeight="80"
             d:DesignHeight="450" d:DesignWidth="800">
    <UserControl.Style>
        <Style TargetType="UserControl">
            <Style.Triggers>
                <DataTrigger Binding="{Binding Selected}" Value="True">
                    <Setter Property="Background" Value="{DynamicResource SuccessColor}"/>
                    <Setter Property="Foreground" Value="#FFF"/>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </UserControl.Style>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <TextBlock FontSize="20" FontWeight="Bold" Text="{Binding Title}"/>
        <pu:Switch Grid.Row="1" FontSize="18" BoxHeight="20" Cursor="Hand"
        Content="关"
        CheckedBackground="Green"
        Background="Red"
        CheckedContent="开"
        IsChecked="{Binding Selected,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
    </Grid>
</UserControl>
Plugin.BurnIn/ManualDebug/ManualCell.xaml.cs
New file
@@ -0,0 +1,47 @@
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace Plugin.BurnIn
{
    /// <summary>
    /// ManualCell.xaml 的交互逻辑
    /// </summary>
    [ObservableObject]
    public partial class ManualCell : UserControl
    {
        [ObservableProperty]
        private string title;
        [ObservableProperty]
        private bool selected;
        [ObservableProperty]
        private int index;
        public event EventHandler SelectedChanged;
        public ManualCell()
        {
            this.DataContext = this;
            InitializeComponent();
        }
        partial void OnSelectedChanged(bool value)
        {
            SelectedChanged?.Invoke(this, new EventArgs());
        }
    }
}
Plugin.BurnIn/ManualDebug/ManualDebug.xaml
New file
@@ -0,0 +1,212 @@
<UserControl x:Class="Plugin.BurnIn.ManualDebug"
        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"
        xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI"
        mc:Ignorable="d"
        x:Name="self"
        MinHeight="400" MinWidth="500">
    <UserControl.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="/Util.ManualDebug;component/ManualDebugStyle.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </UserControl.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Grid Margin="10">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
            <Grid x:Name="CellSelectBorder">
                <Grid.RowDefinitions>
                    <RowDefinition/>
                    <RowDefinition/>
                </Grid.RowDefinitions>
                <Border Background="{StaticResource TsPanelBrush}" Margin="0,0,10,5"
BorderBrush="{StaticResource BorderBrush}"
BorderThickness="1" CornerRadius="4" Padding="15">
                    <StackPanel>
                        <TextBlock Text="穴位选择" FontSize="14" FontWeight="SemiBold"
           Foreground="{StaticResource PrimaryBrush}" Margin="0,0,0,15"/>
                        <UniformGrid IsEnabled="False" x:Name="CellGrid" dp:UniformGridHelper.InnerBorder="1"
 dp:UniformGridHelper.InnerBorderBrush="{StaticResource BorderBrush}">
                        </UniformGrid>
                    </StackPanel>
                </Border>
                <Border Grid.Row="1" Background="{StaticResource TsPanelBrush}" Margin="0,5,10,0"
BorderBrush="{StaticResource BorderBrush}"
BorderThickness="1" CornerRadius="4" Padding="15">
                    <StackPanel>
                        <TextBlock Text="穴位测试结果" FontSize="14" FontWeight="SemiBold"
           Foreground="{StaticResource PrimaryBrush}" Margin="0,0,0,15"/>
                        <UniformGrid IsEnabled="False" x:Name="CellResultGrid" dp:UniformGridHelper.InnerBorder="1"
 dp:UniformGridHelper.InnerBorderBrush="{StaticResource BorderBrush}">
                        </UniformGrid>
                    </StackPanel>
                </Border>
            </Grid>
            <Grid Grid.Column="1">
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition Height="5"/>
                    <RowDefinition Height="*"/>
                    <RowDefinition Height="5"/>
                    <RowDefinition Height="*"/>
                </Grid.RowDefinitions>
                <WindowsFormsHost x:Name="host" Visibility="Collapsed"></WindowsFormsHost>
                <!-- 按钮和状态区域 -->
                <Border Grid.Row="0" Background="{StaticResource TsPanelBrush}"
                BorderBrush="{StaticResource BorderBrush}"
                BorderThickness="1" CornerRadius="4" Padding="15">
                    <StackPanel>
                        <TextBlock Text="手动调试控制" FontSize="14" FontWeight="SemiBold"
                           Foreground="{StaticResource PrimaryBrush}" Margin="0,0,0,15"/>
                        <WrapPanel x:Name="btnPanel">
                        </WrapPanel>
                    </StackPanel>
                </Border>
                <!-- 分隔条1 -->
                <GridSplitter Grid.Row="1" Height="5" HorizontalAlignment="Stretch"
                      Background="Transparent" Cursor="SizeNS"/>
                <!-- TX/RX 数据区域 -->
                <Border Grid.Row="2" Background="{StaticResource TsPanelBrush}"
                BorderBrush="{StaticResource BorderBrush}"
                BorderThickness="1" CornerRadius="4" Padding="10">
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="*"/>
                        </Grid.RowDefinitions>
                        <StackPanel Orientation="Horizontal" Grid.Row="0" Margin="0,0,0,10">
                            <TextBlock Text="测试数据" FontSize="14" FontWeight="SemiBold"
                               Foreground="{StaticResource PrimaryBrush}" VerticalAlignment="Center"/>
                            <Button Content="清空" Style="{StaticResource ActionButtonStyle}"
                            Margin="15,0,0,0" Click="OnClearClick"/>
                        </StackPanel>
                        <DataGrid x:Name="DataGridComm" Grid.Row="1"
                          ItemsSource="{Binding TestData,ElementName=self}">
                            <DataGrid.Columns>
                                <DataGridTextColumn Header="测试项" Binding="{Binding Name}" Width="120" IsReadOnly="True"/>
                                <DataGridTextColumn Header="收" Binding="{Binding Receive}" Width="*" IsReadOnly="True"/>
                                <DataGridTextColumn Header="发" Binding="{Binding Send}" Width="*" IsReadOnly="True"/>
                                <DataGridTextColumn Header="单位" Binding="{Binding Unit}" Width="60" IsReadOnly="True"/>
                                <DataGridTextColumn Header="上限" Binding="{Binding UpperLimit}" Width="80" IsReadOnly="True"/>
                                <DataGridTextColumn Header="下限" Binding="{Binding LowerLimit}" Width="80" IsReadOnly="True"/>
                                <DataGridTextColumn Header="结果" Binding="{Binding Result}" Width="70" IsReadOnly="True"/>
                                <DataGridTextColumn Header="时间" Binding="{Binding Time}" Width="90" IsReadOnly="True"/>
                            </DataGrid.Columns>
                            <DataGrid.RowStyle>
                                <Style TargetType="DataGridRow">
                                    <Style.Triggers>
                                        <DataTrigger Value="True">
                                            <DataTrigger.Binding>
                                                <MultiBinding Converter="{x:Static local:ResultFailedConverter.Instance}">
                                                    <Binding Path="Result"/>
                                                </MultiBinding>
                                            </DataTrigger.Binding>
                                            <Setter Property="Background" Value="#FFE0E0"/>
                                            <Setter Property="Foreground" Value="#C53030"/>
                                        </DataTrigger>
                                    </Style.Triggers>
                                </Style>
                            </DataGrid.RowStyle>
                        </DataGrid>
                    </Grid>
                </Border>
                <!-- 分隔条2 -->
                <GridSplitter Grid.Row="3" Height="5" HorizontalAlignment="Stretch"
                      Background="Transparent" Cursor="SizeNS"/>
                <!-- 日志区域 -->
                <Border Grid.Row="4" Background="{StaticResource TsPanelBrush}"
                BorderBrush="{StaticResource BorderBrush}"
                BorderThickness="1" CornerRadius="4" Padding="10">
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="*"/>
                        </Grid.RowDefinitions>
                        <StackPanel Orientation="Horizontal" Grid.Row="0" Margin="0,0,0,8">
                            <TextBlock Text="日志" FontSize="14" FontWeight="SemiBold"
                               Foreground="{StaticResource PrimaryBrush}" VerticalAlignment="Center"/>
                            <Button Content="清空日志" Style="{StaticResource ActionButtonStyle}"
                            Margin="15,0,0,0" Click="OnClearLogClick"/>
                        </StackPanel>
                        <RichTextBox x:Name="LogRichTextBox" Grid.Row="1"
                             IsReadOnly="True"
                             VerticalScrollBarVisibility="Auto"
                             FontFamily="Consolas"
                             FontSize="11"
                             BorderThickness="1"
                             BorderBrush="Black"
                             Padding="4"/>
                    </Grid>
                </Border>
            </Grid>
            <Border Background="#AAFFFFFF" Grid.ColumnSpan="2" x:Name="mask">
                <TextBlock Text="初始化中..."
                       FontSize="16" FontWeight="Bold" Foreground="{StaticResource PrimaryBrush}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
            </Border>
        </Grid>
        <StatusBar FontSize="14" Grid.Row="1">
            <StatusBar.ItemsPanel>
                <ItemsPanelTemplate>
                    <DockPanel LastChildFill="False"/>
                </ItemsPanelTemplate>
            </StatusBar.ItemsPanel>
            <StatusBarItem DockPanel.Dock="Left">
                <StackPanel Orientation="Horizontal" dp:StackPanelHelper.ChildSpace="8">
                    <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=self}" Value="ERROR">
                                            <Setter Property="Fill" Value="Red"/>
                                        </DataTrigger>
                                        <DataTrigger Binding="{Binding TcpServerStatus,ElementName=self}" Value="RUNNING">
                                            <Setter Property="Fill" Value="Green"/>
                                        </DataTrigger>
                                    </Style.Triggers>
                                </Style>
                            </Ellipse.Style>
                        </Ellipse>
                        <TextBlock Text="{Binding TcpServerInfo,ElementName=self}"/>
                        <Button Padding="0" Margin="0" BorderThickness="0" Command="{Binding StartTcpServerCommand,ElementName=self}">🔄</Button>
                    </StackPanel>
                </StackPanel>
            </StatusBarItem>
        </StatusBar>
    </Grid>
</UserControl>
Plugin.BurnIn/ManualDebug/ManualDebug.xaml.cs
New file
@@ -0,0 +1,691 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using HMI.Core.Util;
using HMI.UI.DpUtil;
using NationalInstruments.TestStand.Interop.API;
using Plugin.BurnIn.Model;
using Plugin.TsSupport;
using SuperSocket.SocketBase.Config;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Media;
namespace Plugin.BurnIn
{
    public class BtnConfig
    {
        public string Title { get; set; }
        public string Name { get; set; }
    }
    // 结果失败判断转换器(支持不区分大小写)
    public class ResultFailedConverter : IMultiValueConverter
    {
        public static readonly ResultFailedConverter Instance = new ResultFailedConverter();
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            if (values.Length > 0 && values[0] is string)
            {
                string result = (string)values[0];
                string upper = result.ToUpperInvariant();
                return upper == "NG" || upper == "FAIL";
            }
            return false;
        }
        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
    // 日志级别枚举
    public enum LogLevel
    {
        Debug = 0,
        Info = 1,
        Warn = 2,
        Error = 3
    }
    [ObservableObject]
    public partial class ManualDebug : UserControl
    {
        public static readonly DependencyProperty OvenNameProperty = DependencyProperty.Register(
                            "OvenName",
                            typeof(string),
                            typeof(ManualDebug),
                            new PropertyMetadata(""));
        public static readonly DependencyProperty CellNameProperty = DependencyProperty.Register(
                            "CellName",
                            typeof(string),
                            typeof(ManualDebug),
                            new PropertyMetadata(""));
        public static readonly DependencyProperty OvenIndexProperty = DependencyProperty.Register(
                            "OvenIndex",
                            typeof(int),
                            typeof(ManualDebug),
                            new PropertyMetadata(0));
        public static readonly DependencyProperty CellIndexProperty = DependencyProperty.Register(
                            "CellIndex",
                            typeof(int),
                            typeof(ManualDebug),
                            new PropertyMetadata(-1));
        public static readonly DependencyProperty CellCountProperty = DependencyProperty.Register(
                            "CellCount",
                            typeof(int),
                            typeof(ManualDebug),
                            new PropertyMetadata(0));
        public string OvenName
        {
            get { return (string)GetValue(OvenNameProperty); }
            set
            {
                SetValue(OvenNameProperty, value);
            }
        }
        public string CellName
        {
            get { return (string)GetValue(CellNameProperty); }
            set { SetValue(CellNameProperty, value); }
        }
        public int OvenIndex
        {
            get { return (int)GetValue(OvenIndexProperty); }
            set { SetValue(OvenIndexProperty, value); }
        }
        public int CellIndex
        {
            get { return (int)GetValue(CellIndexProperty); }
            set { SetValue(CellIndexProperty, value); }
        }
        public int CellCount
        {
            get { return (int)GetValue(CellCountProperty); }
            set { SetValue(CellCountProperty, value); }
        }
        public ObservableCollection<TestDataItem> TestData { get; private set; } = new ObservableCollection<TestDataItem>();
        private Dictionary<string, StatusBtn> btnCache = new Dictionary<string, StatusBtn>();
        // 日志级别颜色
        private readonly SolidColorBrush _debugBrush;
        private readonly SolidColorBrush _infoBrush;
        private readonly SolidColorBrush _warnBrush;
        private readonly SolidColorBrush _errorBrush;
        private ITestExecutionWrapper executionWrapper;
        private TcpServer tcpServer;
        public string seqPath { get; set; }
        public string modelPath { get; set; } = "C:\\Program Files\\National Instruments\\TestStand 2020\\Components\\Models\\TestStandModels\\ParallelModel.seq";
        [ObservableProperty]
        private string tcpServerStatus;
        [ObservableProperty]
        private string tcpServerInfo;
        public ManualDebug()
        {
            _debugBrush = new SolidColorBrush(Color.FromRgb(0x71, 0x80, 0x8A)); // 灰色
            _infoBrush = new SolidColorBrush(Colors.Black);
            _warnBrush = new SolidColorBrush(Color.FromRgb(0xD6, 0x9E, 0x2E));  // 橙黄色
            _errorBrush = new SolidColorBrush(Color.FromRgb(0xC5, 0x30, 0x30));   // 红色
            this.Loaded += ManualDebug_Loaded;
            InitializeComponent();
            RefreshButtons(null);
        }
        public void HideCells(bool flag)
        {
            CellSelectBorder.Visibility = flag ? Visibility.Collapsed : Visibility.Visible;
        }
        private List<ManualCell> Cells = new List<ManualCell>();
        private List<StatusCell> StatusCells = new List<StatusCell>();
        public void RefreshGrid(SingleCell[] cells)
        {
            if (cells != null)
            {
                int i = 0;
                foreach (var cell in cells)
                {
                    var mCell = new ManualCell
                    {
                        Title = cell.title,
                        Index = cell.index
                    };
                    mCell.SelectedChanged += MCell_SelectedChanged;
                    Cells.Add(mCell);
                    CellGrid.Children.Add(mCell);
                    var sCell = new StatusCell
                    {
                        Title = cell.title,
                        Index = cell.index
                    };
                    StatusCells.Add(sCell);
                    CellResultGrid.Children.Add(sCell);
                    BindingOperations.SetBinding(sCell, StatusCell.StatusProperty, new Binding(nameof(ManualCell.Selected))
                    {
                        Source = mCell,
                        Mode = BindingMode.OneWay,
                        //UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
                        Converter = CellStatusConverter.Instance
                    });
                }
                UniformGridHelper.RefreshGridInnerBorder(CellGrid);
                UniformGridHelper.RefreshGridInnerBorder(CellResultGrid);
            }
        }
        private void MCell_SelectedChanged(object sender, EventArgs e)
        {
            object[] arr = Cells.Select(c => (object)c.Selected).ToArray();
            SetArray($"Running.CellIndex", PropertyValueTypes.PropValType_Boolean, arr);
        }
        private void ManualDebug_Loaded(object sender, RoutedEventArgs e)
        {
            StartTcpServer();
            Task.Run(() =>
            {
                System.Threading.Thread.Sleep(500);
                Dispatcher.BeginInvoke(() =>
                {
                    InitWrapper();
                    StartSeq();
                    mask.Visibility = Visibility.Collapsed;
                });
            });
        }
        /// <summary>
        /// 启动Tcp服务器
        /// </summary>
        [RelayCommand]
        private void StartTcpServer()
        {
            var serverConfig = new ServerConfig()
            {
                MaxRequestLength = 1024 * 1024,
                Port = 7777
            };
            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;
            });
        }
        private void TcpServer_NewRequestReceived(TcpSession session, SuperSocket.SocketBase.Protocol.StringRequestInfo requestInfo)
        {
            string msg = requestInfo.Body;
            string[] datas_10015 = msg.Split(';');
            //int cellIndex = int.Parse(datas_10015[1]);
            string strs = datas_10015[2];
            string[] arr = strs.Split(',');
            Dispatcher.Invoke(() =>
            {
                AddTestData(arr[0], arr[1], arr[2], arr[3], arr[4], arr[5], arr[6], arr[7]);
            });
        }
        public void RefreshButtons(List<BtnConfig> settings)
        {
            if (settings == null) return;
            for (int i = 0; i < settings.Count; i++)
            {
                var setting = settings[i];
                var margin = new Thickness(0, 0, 20, 0);
                if (i == settings.Count - 1)
                {
                    margin = new Thickness(0);
                }
                var btn = new StatusBtn
                {
                    Title = setting.Title,
                    Tag = setting,
                    Margin = margin
                };
                btn.BtnClick += Btn_BtnClick;
                btnCache[setting.Name] = btn;
                btnPanel.Children.Add(btn);
            }
        }
        public void StartSeq()
        {
            try
            {
                executionWrapper?.StartSequence(seqPath, modelPath);
            }
            catch (Exception ex)
            {
                AppendLog(LogLevel.Error, $"启动测试序列失败 {ex.Message} {ex.StackTrace}");
            }
        }
        private void InitWrapper()
        {
            try
            {
                var wrapper = new TeststandWrapper();
                var AxApplicationMgr = wrapper.AxApplicationMgr;
                ((System.ComponentModel.ISupportInitialize)(AxApplicationMgr)).BeginInit();
                host.Child = AxApplicationMgr;
                ((System.ComponentModel.ISupportInitialize)(AxApplicationMgr)).EndInit();
                wrapper.Init();
                executionWrapper = wrapper;
                wrapper.UIMessageEvent += Wrapper_UIMessageEvent;
            }
            catch (Exception ex)
            {
                AppendLog(LogLevel.Error, $"初始化Teststand {ex.Message} {ex.StackTrace}");
            }
        }
        private void SetArray(string name, PropertyValueTypes type, object[] arr)
        {
            try
            {
                var context = ((TeststandWrapper)executionWrapper).context;
                context.FileGlobals.DeleteSubProperty(name, 2);
                context.FileGlobals.NewSubProperty(name, type, true, null, 1);
                context.FileGlobals.GetPropertyObject(name, 0).SetNumElements(arr.Length);
                for (int i = 0; i < arr.Length; i++)
                {
                    if (type == PropertyValueTypes.PropValType_Boolean)
                    {
                        context.FileGlobals.SetValBoolean($"{name}[{i}]", 0, arr[i] == null ? false : (bool)arr[i]);
                    }
                    else
                    {
                        context.FileGlobals.SetValString($"{name}[{i}]", 0, arr[i]?.ToString());
                    }
                    // (type == PropertyValueTypes.PropValType_String)
                }
            }
            catch (Exception ex)
            {
                AppendLog(LogLevel.Error, $"写数组 {name} 错误: {ex.Message} {ex.StackTrace}");
            }
        }
        private void Wrapper_UIMessageEvent(object sender, UIMessageEvent e)
        {
            try
            {
                if (e.EventCode == 10000)
                {
                    executionWrapper?.SetContext(e.Data);
                    string key = $"Running.OvenName";
                    executionWrapper?.SetFileGlobalValue(key, 1, OvenName);
                    key = $"Running.OvenIndex";
                    executionWrapper?.SetFileGlobalValue(key, 1, OvenIndex);
                    //key = $"Running.CellIndex";
                    //executionWrapper?.SetFileGlobalValue(key, 1, CellIndex);
                    if (CellIndex >= 0)
                    {
                        key = $"Running.CellIndex";
                        object[] arr = new object[CellCount];
                        arr[CellIndex] = true;
                        SetArray(key, PropertyValueTypes.PropValType_Boolean, arr);
                        key = $"Running.CellName";
                        string[] names = new string[CellCount];
                        names[CellIndex] = CellName;
                        SetArray(key, PropertyValueTypes.PropValType_String, names);
                    }
                    else
                    {
                        string[] names = Cells.Select(c => c.Title).ToArray();
                        SetArray($"Running.CellName", PropertyValueTypes.PropValType_String, names);
                    }
                    Dispatcher.Invoke(() =>
                    {
                        CellGrid.IsEnabled = true;
                        AppendLog(LogLevel.Debug, "Context初始化完成");
                    });
                    return;
                }
                if (e.EventCode == 10001)
                {
                    string[] arr = e.StrData.Split(',');
                    Dispatcher.BeginInvoke(() =>
                    {
                        AddTestData(arr[0], arr[1], arr[2], arr[3], arr[4], arr[5], arr[6], arr[7]);
                    });
                    return;
                }
                if (e.EventCode == 10002)
                {
                    string[] arr = e.StrData.Split(',');
                    LogLevel level = (LogLevel)int.Parse(arr[0]);
                    Dispatcher.BeginInvoke(() =>
                    {
                        AppendLog(level, arr[1]);
                    });
                    return;
                }
                if (e.EventCode == 10003)
                {
                    string[] arr = e.StrData.Split(',');
                    DebugStatus status = (DebugStatus)int.Parse(arr[1]);
                    Dispatcher.BeginInvoke(() =>
                    {
                        SetStatus(arr[0], status);
                    });
                    return;
                }
                if (e.EventCode == 10004)
                {
                    int index = Convert.ToInt32(e.NumberData);
                    //CellStatus status = (CellStatus)Enum.Parse(typeof(CellStatus), e.StrData);
                    CellStatus status = (CellStatus)int.Parse(e.StrData);
                    Dispatcher.BeginInvoke(() =>
                    {
                        if (index < StatusCells.Count)
                        {
                            StatusCells[index].SetCurrentValue(StatusCell.StatusProperty, status);
                        }
                    });
                    return;
                }
            }
            catch (Exception ex)
            {
                AppendLog(LogLevel.Error, $"处理UI信息失败 {ex.Message} {ex.StackTrace}");
            }
        }
        private void Btn_BtnClick(object sender, RoutedEventArgs e)
        {
            var btn = (StatusBtn)sender;
            BtnConfig config = (BtnConfig)btn.Tag;
            StartSingleTest(config.Name);
        }
        private void StartSingleTest(string name)
        {
            try
            {
                string key = $"Running.ActionName";
                executionWrapper?.SetFileGlobalValue(key, 1, name);
            }
            catch (Exception ex)
            {
                AppendLog(LogLevel.Error, $"启动功能失败 {ex.Message} {ex.StackTrace}");
            }
        }
        // 设置状态显示
        private void SetStatus(string labelName, DebugStatus status)
        {
            if (btnCache.ContainsKey(labelName))
            {
                var btn = btnCache[labelName];
                btn.Status = status;
            }
        }
        // 重置状态显示
        private void ResetStatus(string labelName)
        {
            SetStatus(labelName, DebugStatus.Idle);
        }
        // 设置测试中状态并禁用按钮
        private void SetTestingStatus(string labelName, Button button)
        {
            SetStatus(labelName, DebugStatus.Testing);
            button.IsEnabled = false;
        }
        private void OnClearClick(object sender, RoutedEventArgs e)
        {
            TestData.Clear();
        }
        private void OnClearLogClick(object sender, RoutedEventArgs e)
        {
            LogRichTextBox.Document.Blocks.Clear();
        }
        private void AddTestData(string name, string send, string receive, string unit, string upper, string lower, string result, string time)
        {
            TestData.Add(new TestDataItem
            {
                Name = name,
                Send = send,
                Receive = receive,
                Unit = unit,
                UpperLimit = upper,
                LowerLimit = lower,
                Result = result,
                Time = time
            });
            if (DataGridComm.Items.Count > 0)
            {
                DataGridComm.ScrollIntoView(DataGridComm.Items[DataGridComm.Items.Count - 1]);
            }
        }
        // 获取日志级别对应的颜色
        private Brush GetLogLevelBrush(LogLevel level)
        {
            switch (level)
            {
                case LogLevel.Debug:
                    return _debugBrush;
                case LogLevel.Info:
                    return _infoBrush;
                case LogLevel.Warn:
                    return _warnBrush;
                case LogLevel.Error:
                    return _errorBrush;
                default:
                    return _infoBrush;
            }
        }
        // 添加带级别的日志
        private void AppendLog(LogLevel level, string message)
        {
            Dispatcher.BeginInvoke(() =>
            {
                string timestamp = DateTime.Now.ToString("HH:mm:ss.fff");
                string levelStr = level.ToString().ToUpper();
                Paragraph? paragraph = LogRichTextBox.Document.Blocks.LastBlock as Paragraph;
                if (paragraph == null)
                {
                    paragraph = new Paragraph();
                    LogRichTextBox.Document.Blocks.Add(paragraph);
                }
                // 时间戳(灰色)
                Run timestampRun = new Run("[" + timestamp + "] ") { Foreground = _debugBrush };
                paragraph.Inlines.Add(timestampRun);
                // 级别标签
                Brush levelBrush = GetLogLevelBrush(level);
                Run levelRun = new Run("[" + levelStr + "] ") { Foreground = levelBrush, FontWeight = FontWeights.SemiBold };
                paragraph.Inlines.Add(levelRun);
                // 消息内容
                Run messageRun = new Run(message + "\n") { Foreground = levelBrush };
                paragraph.Inlines.Add(messageRun);
                LogRichTextBox.ScrollToEnd();
            });
        }
        // 兼容旧调用方式,默认 Info 级别
        private void AppendLog(string message)
        {
            AppendLog(LogLevel.Info, message);
        }
        public void Stop()
        {
            try
            {
                executionWrapper?.SetFileGlobalValue("Running.StopTest", 1, true);
                executionWrapper?.WaitForExit();
                tcpServer?.Stop();
            }
            catch (Exception ex)
            {
            }
        }
    }
    public class TestDataItem : INotifyPropertyChanged
    {
        private string _name = "";
        private string _receive = "";
        private string _send = "";
        private string _unit = "";
        private string _upperLimit = "";
        private string _lowerLimit = "";
        private string _result = "";
        private string _time = "";
        public string Name
        {
            get { return _name; }
            set { _name = value; OnPropertyChanged("Name"); }
        }
        public string Receive
        {
            get { return _receive; }
            set { _receive = value; OnPropertyChanged("Receive"); }
        }
        public string Send
        {
            get { return _send; }
            set { _send = value; OnPropertyChanged("Send"); }
        }
        public string Unit
        {
            get { return _unit; }
            set { _unit = value; OnPropertyChanged("Unit"); }
        }
        public string UpperLimit
        {
            get { return _upperLimit; }
            set { _upperLimit = value; OnPropertyChanged("UpperLimit"); }
        }
        public string LowerLimit
        {
            get { return _lowerLimit; }
            set { _lowerLimit = value; OnPropertyChanged("LowerLimit"); }
        }
        public string Result
        {
            get { return _result; }
            set { _result = value; OnPropertyChanged("Result"); }
        }
        public string Time
        {
            get { return _time; }
            set { _time = value; OnPropertyChanged("Time"); }
        }
        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}
Plugin.BurnIn/ManualDebug/ManualDebugStyle.xaml
New file
@@ -0,0 +1,130 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <!-- 颜色定义 -->
    <SolidColorBrush x:Key="TsBgBrush" Color="#F0F2F5"/>
    <SolidColorBrush x:Key="TsPanelBrush" Color="#FFFFFF"/>
    <SolidColorBrush x:Key="BorderBrush" Color="#A0AEC0"/>
    <SolidColorBrush x:Key="PrimaryBrush" Color="#2E5C8A"/>
    <!-- 状态颜色画刷 -->
    <SolidColorBrush x:Key="StatusOKBrush" Color="#2D8659"/>
    <SolidColorBrush x:Key="StatusNGBrush" Color="#C53030"/>
    <SolidColorBrush x:Key="StatusTestingBrush" Color="#3182CE"/>
    <SolidColorBrush x:Key="StatusDefaultBrush" Color="#E0E0E0"/>
    <SolidColorBrush x:Key="BorderDarkBrush" Color="#718096"/>
    <!-- ActionButtonStyle 按钮样式 -->
    <Style x:Key="ActionButtonStyle" TargetType="Button">
        <Setter Property="Height" Value="22"/>
        <Setter Property="Padding" Value="8,0"/>
        <Setter Property="Background" Value="White"/>
        <Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
        <Setter Property="BorderThickness" Value="1"/>
        <Setter Property="FontSize" Value="11"/>
        <Setter Property="Cursor" Value="Hand"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="Button">
                    <Border Background="{TemplateBinding Background}"
                            BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}"
                            CornerRadius="2"
                            Padding="{TemplateBinding Padding}">
                        <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
                    </Border>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsMouseOver" Value="True">
                            <Setter Property="Background" Value="#F0F0F0"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    <!-- ManualDebug 窗口专用按钮样式 -->
    <Style x:Key="DebugButtonStyle" TargetType="Button">
        <Setter Property="Width" Value="100"/>
        <Setter Property="Height" Value="32"/>
        <Setter Property="Background" Value="White"/>
        <Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
        <Setter Property="BorderThickness" Value="1"/>
        <Setter Property="FontSize" Value="11"/>
        <Setter Property="Cursor" Value="Hand"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="Button">
                    <Border Background="{TemplateBinding Background}"
                            BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}"
                            CornerRadius="4"
                            Padding="{TemplateBinding Padding}">
                        <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
                    </Border>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsMouseOver" Value="True">
                            <Setter Property="Background" Value="#E8E8E8"/>
                        </Trigger>
                        <Trigger Property="IsPressed" Value="True">
                            <Setter Property="Background" Value="#D0D0D0"/>
                        </Trigger>
                        <Trigger Property="IsEnabled" Value="False">
                            <Setter Property="Background" Value="LightGray"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    <!-- DataGrid样式 -->
    <Style TargetType="DataGrid">
        <Setter Property="AutoGenerateColumns" Value="False"/>
        <Setter Property="CanUserAddRows" Value="False"/>
        <Setter Property="CanUserDeleteRows" Value="False"/>
        <Setter Property="HeadersVisibility" Value="Column"/>
        <Setter Property="GridLinesVisibility" Value="All"/>
        <Setter Property="HorizontalGridLinesBrush" Value="{StaticResource BorderBrush}"/>
        <Setter Property="VerticalGridLinesBrush" Value="{StaticResource BorderBrush}"/>
        <Setter Property="BorderBrush" Value="{StaticResource BorderDarkBrush}"/>
        <Setter Property="BorderThickness" Value="1"/>
        <Setter Property="Background" Value="White"/>
        <Setter Property="RowBackground" Value="White"/>
        <Setter Property="AlternatingRowBackground" Value="#F5F5F5"/>
        <Setter Property="FontSize" Value="11"/>
    </Style>
    <Style TargetType="DataGridColumnHeader">
        <Setter Property="Background" Value="#F5F5F5"/>
        <Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
        <Setter Property="BorderThickness" Value="0,0,1,1"/>
        <Setter Property="Padding" Value="4"/>
        <Setter Property="Height" Value="26"/>
        <Setter Property="FontWeight" Value="Normal"/>
    </Style>
    <Style TargetType="DataGridCell">
        <Setter Property="BorderBrush" Value="{StaticResource BorderBrush}"/>
        <Setter Property="BorderThickness" Value="0"/>
        <Setter Property="Padding" Value="2,4"/>
        <Style.Triggers>
            <Trigger Property="IsSelected" Value="True">
                <Setter Property="Background" Value="#BBDEFB"/>
                <Setter Property="Foreground" Value="Black"/>
            </Trigger>
        </Style.Triggers>
    </Style>
    <Style TargetType="DataGridRow">
        <Style.Triggers>
            <Trigger Property="IsSelected" Value="True">
                <Setter Property="Background" Value="#BBDEFB"/>
            </Trigger>
            <Trigger Property="IsMouseOver" Value="True">
                <Setter Property="Background" Value="#E3F2FD"/>
            </Trigger>
        </Style.Triggers>
    </Style>
</ResourceDictionary>
Plugin.BurnIn/ManualDebug/ManualDebugWindow.xaml
File was renamed from Plugin.BurnIn/ManualDebugWindow.xaml
@@ -4,16 +4,18 @@
        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"
        xmlns:local="clr-namespace:Plugin.BurnIn"
        Style="{StaticResource ModalWindow}"
        Closing="Window_Closing"
        WindowState="Maximized"
        WindowStartupLocation="CenterScreen"
        pu:WindowXModalDialog.Buttons="{x:Null}"
        Height="1000" Width="900">
    <Grid x:Name="sb">
        <sb:ManualDebug OvenName="{Binding OvenName,Mode=OneWay}"
        <local:ManualDebug OvenName="{Binding OvenName,Mode=OneWay}"
                           CellCount="{Binding CellCount,Mode=OneWay}"
    CellName="{Binding CellName,Mode=OneWay}" 
    OvenIndex="{Binding OvenIndex,Mode=OneWay}" 
    CellIndex="{Binding CellIndex,Mode=OneWay}"/>
Plugin.BurnIn/ManualDebug/ManualDebugWindow.xaml.cs
File was renamed from Plugin.BurnIn/ManualDebugWindow.xaml.cs
@@ -3,7 +3,6 @@
using System.ComponentModel;
using System.IO;
using System.Windows;
using Util.SB;
namespace Plugin.BurnIn
{
@@ -15,15 +14,26 @@
        private ManualDebug debug;
        public ManualDebugWindow(string seqPath, string modelPath)
        public ManualDebugWindow(string seqPath, string modelPath, bool isMuilt) : this(seqPath, modelPath, isMuilt, null, 1)
        {
        }
        public ManualDebugWindow(string seqPath, string modelPath, bool isMuilt, SingleCell[] cells, int col)
        {
            DataContext = this;
            InitializeComponent();
            debug = (ManualDebug)sb.Children[0];
            ReadBtns();
            if (!isMuilt)
            {
                debug.HideCells(true);
            }
            //List<BtnConfig> settings = new List<BtnConfig> { new BtnConfig { Title = "上电激活", Name = "Active" } };
            debug.seqPath = seqPath;
            debug.modelPath = modelPath;
            debug.CellGrid.Columns = col;
            debug.CellResultGrid.Columns = col;
            debug.RefreshGrid(cells);
        }
        private string ovenName;
@@ -56,7 +66,7 @@
            }
        }
        private int cellIndex;
        private int cellIndex = -1;
        public int CellIndex
        {
            get => cellIndex;
@@ -66,6 +76,16 @@
            }
        }
        private int cellCount = 0;
        public int CellCount
        {
            get => cellCount;
            set
            {
                Set(ref cellCount, value);
            }
        }
        private void ReadBtns()
        {
            string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Settings.ini");
Plugin.BurnIn/ManualDebug/StatusBtn.xaml
New file
@@ -0,0 +1,75 @@
<UserControl x:Class="Plugin.BurnIn.StatusBtn"
             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"
             d:DesignHeight="450" d:DesignWidth="800">
    <UserControl.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="/Plugin.BurnIn;component/ManualDebug/ManualDebugStyle.xaml"/>
            </ResourceDictionary.MergedDictionaries>
            <Style x:Key="StatusTextStyle" TargetType="TextBlock">
                <Setter Property="HorizontalAlignment" Value="Center"/>
                <Setter Property="VerticalAlignment" Value="Center"/>
                <Setter Property="FontWeight" Value="SemiBold"/>
                <Setter Property="FontSize" Value="12"/>
                <Setter Property="Text" Value="-"/>
                <Style.Triggers>
                    <DataTrigger Binding="{Binding Status}" Value="{x:Static local:DebugStatus.NG}">
                        <Setter Property="Text" Value="NG"/>
                        <Setter Property="Foreground" Value="#FFF"/>
                    </DataTrigger>
                    <DataTrigger Binding="{Binding Status}" Value="{x:Static local:DebugStatus.OK}">
                        <Setter Property="Text" Value="OK"/>
                        <Setter Property="Foreground" Value="#FFF"/>
                    </DataTrigger>
                    <DataTrigger Binding="{Binding Status}" Value="{x:Static local:DebugStatus.Testing}">
                        <Setter Property="Text" Value="TESTING"/>
                        <Setter Property="Foreground" Value="#FFF"/>
                    </DataTrigger>
                </Style.Triggers>
            </Style>
            <!-- 状态边框样式 -->
            <Style x:Key="StatusBorderStyle" TargetType="Border">
                <Setter Property="Background" Value="{StaticResource StatusDefaultBrush}"/>
                <Setter Property="CornerRadius" Value="2"/>
                <Setter Property="HorizontalAlignment" Value="Center"/>
                <Setter Property="Width" Value="100"/>
                <Setter Property="Height" Value="24"/>
                <Setter Property="Margin" Value="0,5,0,0"/>
                <Style.Triggers>
                    <DataTrigger Binding="{Binding Status}" Value="{x:Static local:DebugStatus.NG}">
                        <Setter Property="Background" Value="{StaticResource StatusNGBrush}"/>
                    </DataTrigger>
                    <DataTrigger Binding="{Binding Status}" Value="{x:Static local:DebugStatus.OK}">
                        <Setter Property="Background" Value="{StaticResource StatusOKBrush}"/>
                    </DataTrigger>
                    <DataTrigger Binding="{Binding Status}" Value="{x:Static local:DebugStatus.Testing}">
                        <Setter Property="Background" Value="{StaticResource StatusTestingBrush}"/>
                    </DataTrigger>
                </Style.Triggers>
            </Style>
            <Style x:Key="btnStyle" TargetType="Button" BasedOn="{StaticResource DebugButtonStyle}">
                <Style.Triggers>
                    <DataTrigger Binding="{Binding Status}" Value="{x:Static local:DebugStatus.Testing}">
                        <Setter Property="IsEnabled" Value="False"/>
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </ResourceDictionary>
    </UserControl.Resources>
    <StackPanel>
        <Button x:Name="btn" Content="{Binding Title}" Style="{StaticResource btnStyle}" Click="Button_Click"/>
        <Border Style="{StaticResource StatusBorderStyle}">
            <TextBlock Style="{StaticResource StatusTextStyle}"/>
        </Border>
    </StackPanel>
</UserControl>
Plugin.BurnIn/ManualDebug/StatusBtn.xaml.cs
New file
@@ -0,0 +1,40 @@
using CommunityToolkit.Mvvm.ComponentModel;
using System.Windows;
using System.Windows.Controls;
namespace Plugin.BurnIn
{
    // 状态枚举
    public enum DebugStatus
    {
        Idle = 0,    // 空闲
        Testing = 1, // 测试中
        OK = 2,      // 通过
        NG = 3       // 失败
    }
    /// <summary>
    /// StatusBtn.xaml 的交互逻辑
    /// </summary>
    [ObservableObject]
    public partial class StatusBtn : UserControl
    {
        public event RoutedEventHandler BtnClick;
        public StatusBtn()
        {
            DataContext = this;
            InitializeComponent();
        }
        [ObservableProperty]
        private string title = string.Empty;
        [ObservableProperty]
        private DebugStatus status = DebugStatus.Idle;
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            BtnClick?.Invoke(this, e);
        }
    }
}
Plugin.BurnIn/ManualDebug/StatusCell.xaml
New file
@@ -0,0 +1,39 @@
<UserControl x:Class="Plugin.BurnIn.StatusCell"
             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:pu="https://opensource.panuon.com/wpf-ui"
             mc:Ignorable="d" Padding="5" MaxHeight="80"
             d:DesignHeight="450" d:DesignWidth="800">
    <UserControl.Style>
        <Style TargetType="UserControl">
            <Style.Triggers>
                <DataTrigger Binding="{Binding Status}" Value="{x:Static local:CellStatus.Closed}">
                    <Setter Property="Background" Value="Gray"/>
                </DataTrigger>
                <DataTrigger Binding="{Binding Status}" Value="{x:Static local:CellStatus.OK}">
                    <Setter Property="Background" Value="#2D8659"/>
                    <Setter Property="Foreground" Value="#FFF"/>
                </DataTrigger>
                <DataTrigger Binding="{Binding Status}" Value="{x:Static local:CellStatus.NG}">
                    <Setter Property="Background" Value="#C53030"/>
                    <Setter Property="Foreground" Value="#FFF"/>
                </DataTrigger>
                <DataTrigger Binding="{Binding Status}" Value="{x:Static local:CellStatus.Testing}">
                    <Setter Property="Background" Value="#3182CE"/>
                    <Setter Property="Foreground" Value="#FFF"/>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </UserControl.Style>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <TextBlock FontSize="20" FontWeight="Bold" Text="{Binding Title}"/>
        <TextBlock Grid.Row="1" FontSize="18" Text="{Binding Status}"/>
    </Grid>
</UserControl>
Plugin.BurnIn/ManualDebug/StatusCell.xaml.cs
New file
@@ -0,0 +1,71 @@
using CommunityToolkit.Mvvm.ComponentModel;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace Plugin.BurnIn
{
    public enum CellStatus
    {
        Testing = 0,
        OK = 1,
        NG = 2,
        Closed = 99,
        Open = 100,
        Waiting = 101
    }
    public class CellStatusConverter : IValueConverter
    {
        public static CellStatusConverter Instance = new CellStatusConverter();
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value is bool b)
            {
                return b ? CellStatus.Open : CellStatus.Closed;
            }
            return CellStatus.Closed;
        }
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return null;
        }
    }
    /// <summary>
    /// ManualCell.xaml 的交互逻辑
    /// </summary>
    [ObservableObject]
    public partial class StatusCell : UserControl
    {
        [ObservableProperty]
        private string title;
        public static readonly DependencyProperty StatusProperty = DependencyProperty.Register(
                            "Status",
                            typeof(CellStatus),
                            typeof(StatusCell),
                            new PropertyMetadata(CellStatus.Closed));
        public CellStatus Status
        {
            get => (CellStatus)GetValue(StatusProperty);
            set
            {
                SetValue(StatusProperty, value);
            }
        }
        [ObservableProperty]
        private int index;
        public StatusCell()
        {
            this.DataContext = this;
            InitializeComponent();
        }
    }
}
Plugin.BurnIn/Plugin.BurnIn.csproj
@@ -8,6 +8,9 @@
    <ImplicitUsings>enable</ImplicitUsings>
    <UseWPF>true</UseWPF>
    <Platforms>AnyCPU;x64</Platforms>
    <AssemblyVersion>1.0.0.2</AssemblyVersion>
    <FileVersion>1.0.0.2</FileVersion>
    <IncludeSymbols>False</IncludeSymbols>
  </PropertyGroup>
  <ItemGroup>
@@ -30,7 +33,6 @@
    <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>
@@ -62,6 +64,12 @@
  </ItemGroup>
  <ItemGroup>
    <Compile Update="ManualDebug\StatusCell.xaml.cs">
      <SubType>Code</SubType>
    </Compile>
    <Compile Update="ManualDebug\StatusBtn.xaml.cs">
      <SubType>Code</SubType>
    </Compile>
    <Compile Update="TestResultView.xaml.cs">
      <SubType>Code</SubType>
    </Compile>
Plugin.BurnIn/Scanner.xaml.cs
@@ -390,7 +390,7 @@
            try
            {
                targetCellInfo = null;
                serialPort.Dispose();
                serialPort?.Dispose();
                serialPort = null;
            }
            catch (Exception ex)
Plugin.BurnIn/SingleCell.xaml.cs
@@ -521,16 +521,21 @@
            //waterChart?.AddData("水箱温度", temp);
            //waterChart?.AddData("回水温度", backTemp);
            //waterChart?.AddData("流量", pressure);
            var time = DateTime.Now;
            charts[0].AddData("水箱温度", time, temp);
            charts[0].AddData("回水温度", time, backTemp);
            charts[0].AddData("流量", time, pressure);
        }
        public void AddElectricData(double data)
        {
            charts[1].AddData("电流", DateTime.Now, data);
            //electricChart.AddData("", data);
        }
        public void AddVData(double data)
        {
            //vChart.AddData("", data);
            charts[2].AddData("电压", DateTime.Now, data);
        }
        public void ClearChart()
@@ -1059,15 +1064,16 @@
                return;
            }
            string modelPath = table.parent.testModelFile;
            int dutNo = table.index * table.cellCount + this.index;
            var win = new ManualDebugWindow(manualDebugSeq, modelPath)
            var win = new ManualDebugWindow(manualDebugSeq, modelPath, false)
            {
                Title = $"{this.title}手动调试",
                Owner = Window.GetWindow(this),
                OvenIndex = this.table.index,
                OvenName = this.table.title,
                CellCount = this.table.cells.Length,
                CellIndex = dutNo,
                CellIndex = this.index,
                CellName = this.title
            };
            win.ShowDialog();
Plugin.BurnIn/TestResultView.xaml.cs
@@ -245,7 +245,7 @@
        {
            foreach (var oven in tables)
            {
                executionWrapper?.SetStationGlobalValue<string>($"StationHMI.UUTHMI[{oven.index}].Name", 1, oven.Name);
                executionWrapper?.SetStationGlobalValue<string>($"StationHMI.UUTHMI[{oven.index}].Name", 1, oven.title);
            }
            executionWrapper?.CommitToDisk(false);
        }
@@ -271,7 +271,7 @@
        {
            try
            {
                executionWrapper?.SetStationGlobalValue<bool>("StationState.StopTest", 0, true, true);
                executionWrapper?.SetStationGlobalValue<bool>("StationHMI.AllStop", 0, true, true);
                try
                {
@@ -453,6 +453,7 @@
                    tables[ovenIndex].ClearChartData();
                    return;
                }
                if (MethodCode == 11)
                {
                    /// 清除穴位参数
Plugin.TestResult/DataTable.xaml.cs
@@ -607,17 +607,18 @@
        {
            infoTable.UpdateNumber(infoTable.okCount + ok, infoTable.ngCount + ng);
            if (ParamSetting.ProbeByModel)
            {
            //if (ParamSetting.ProbeByModel)
            //{
            }
            else
            {
                IniFileHelper.WriteIniData($"Oven_{index}", "Pass", infoTable.okCount.ToString(),
                   this.CountIniPath);
                IniFileHelper.WriteIniData($"Oven_{index}", "Fail", infoTable.ngCount.ToString(),
                    this.CountIniPath);
            }
            //}
            //else
            //{
            //}
            IniFileHelper.WriteIniData($"Oven_{index}", "Pass", infoTable.okCount.ToString(),
                  this.CountIniPath);
            IniFileHelper.WriteIniData($"Oven_{index}", "Fail", infoTable.ngCount.ToString(),
                this.CountIniPath);
        }
        /// <summary>
Plugin.TsExecution/SingleControl.xaml
@@ -455,7 +455,13 @@
VerticalContentAlignment="Center" Content="{DynamicResource Ts.ProbeCount}"/>
                        <Label Padding="0" FontSize="12"  Margin="0,0,15,0"
VerticalAlignment="Center"
VerticalContentAlignment="Center" Content="{Binding ProbeCount}"/>
VerticalContentAlignment="Center">
                            <TextBlock>
                                <Run Text="{Binding ProbeCount}"/>
                                <Run Text="/"/>
                                <Run Text="{Binding ProbeWarn,Mode=OneWay}"/>
                            </TextBlock>
                        </Label>
                        <StackPanel x:Name="probeWarnPanel" Orientation="Horizontal" Visibility="Collapsed">
                            <Label x:Name="alarmLabel" Padding="0" FontSize="12" 
@@ -463,7 +469,7 @@
VerticalContentAlignment="Center" Content="{DynamicResource Ts.ProbeAlarmCount}"/>
                            <Label x:Name="alarmValue" Padding="0" FontSize="12" 
VerticalAlignment="Center"
VerticalContentAlignment="Center" Content="{Binding ProbeWarn}"/>
VerticalContentAlignment="Center" Content="{Binding ProbeWarn,Mode=OneWay}"/>
                        </StackPanel>
                        <Button FontSize="12" Style="{StaticResource LinkButton}" 
Plugin.TsExecution/SingleControl.xaml.cs
@@ -5,6 +5,7 @@
using HMI.Util;
using NationalInstruments.TestStand.Interop.API;
using NPOI.HSSF.UserModel;
using NPOI.SS.Formula.Functions;
using NPOI.SS.UserModel;
using Plugin.TsExecution.Entity;
using System;
@@ -180,6 +181,16 @@
            set
            {
                Set(ref _probeCount, value);
                try
                {
                    if (ProbeWarn <= 0) probeCountAlarm.Visibility = Visibility.Collapsed;
                    else if (value >= ProbeWarn) probeCountAlarm.Visibility = Visibility.Visible;
                    else probeCountAlarm.Visibility = Visibility.Collapsed;
                }
                catch
                {
                }
            }
        }
@@ -222,7 +233,15 @@
            }
        }
        public int ProbeWarn { get; private set; }
        private int _probeWarn;
        public int ProbeWarn
        {
            get => _probeWarn;
            private set
            {
                Set(ref _probeWarn, value);
            }
        }
        private bool isAutoMode;
        public bool IsAutoMode
@@ -246,9 +265,9 @@
                Interval = 100
            };
            refreshTimer.Elapsed += RefreshTimer_Elapsed;
            RefreshProbeCount(null);
            ReadCounter();
            InitializeComponent();
            RefreshProbeCount(null);
            try
            {
                var value = ParameterManager.GetCommonParameterByName("AutoTestMode");
@@ -653,10 +672,6 @@
            /// 写到配置文件
            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)
SimpleUtil/HMI.Simple.Util/DataSender.cs
New file
@@ -0,0 +1,103 @@
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
namespace HMI.Simple.Util
{
    public class DataSender
    {
        private string _ip;
        private int _port;
        private bool _isConnected = false; // 是否连接
        private Socket _clientSocket; // 套接字
        private Thread _receiveThread = null; // 接收线程
        private volatile bool needRecieve = true;
        public event EventHandler<string> ExecuteMessageChanged;
        public DataSender(string ip, int port)
        {
            this._ip = ip;
            this._port = port;
        }
        public void Connect()
        {
            try
            {
                _clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                IPAddress ipaddress = IPAddress.Parse(this._ip);
                IPEndPoint endpoint = new IPEndPoint(ipaddress, _port);
                _clientSocket.Connect(endpoint);
                _isConnected = true;
                _receiveThread = new Thread(Received);
                _receiveThread.IsBackground = true;
                _receiveThread.Start();
            }
            catch (Exception ex)
            {
                needRecieve = false;
                _receiveThread?.Abort();
                _receiveThread = null;
                _clientSocket.Close();
                _isConnected = false;
                throw ex;
            }
        }
        public void Disconnect()
        {
            needRecieve = false;
            _clientSocket.Close();
            _receiveThread?.Abort();
            _receiveThread = null;
            _isConnected = false;
        }
        private void Send(string data)
        {
            byte[] arrClientSendMsg = Encoding.UTF8.GetBytes(data);
            _clientSocket.Send(arrClientSendMsg);
        }
        private void Received()
        {
            //while (needRecieve)
            //{
            //    byte[] arrRecMsg = new byte[1024 * 1024];
            //    int length = _clientSocket.Receive(arrRecMsg);
            //    string strRecMsg = Encoding.UTF8.GetString(arrRecMsg, 0, length);
            //    ExecuteMessageChanged?.Invoke(this, strRecMsg);
            //    Thread.Sleep(100);
            //}
        }
        public bool SendStep(int ovenIndex, int cellIndex,
            string name, string send, string value, string unit, string upper, string lower, string result, string time,
            out string errorMsg)
        {
            errorMsg = null;
            try
            {
                if (!_isConnected)
                {
                    errorMsg = "Not Connected";
                    return false;
                }
                string msg = $"{ovenIndex};{cellIndex};{name},{send},{value},{unit},{upper},{lower},{result},{time}\n";
                Send(msg);
                return true;
            }
            catch (Exception ex)
            {
                errorMsg = ex.Message;
                return false;
            }
        }
    }
}
SimpleUtil/HMI.Simple.Util/HMI.Simple.Util.csproj
@@ -95,6 +95,7 @@
    <Compile Include="ProductUtil.cs" />
    <Compile Include="Properties\AssemblyInfo.cs" />
    <Compile Include="SqliteHelper.cs" />
    <Compile Include="DataSender.cs" />
    <Compile Include="XmlHelper.cs" />
  </ItemGroup>
  <ItemGroup>