浏览代码

feat: 硬件测试

kindring 2 周之前
父节点
当前提交
5a3051c387
共有 8 个文件被更改,包括 3566 次插入10 次删除
  1. 3 1
      .gitignore
  2. 455 0
      bird_tool/ProgressPanel.cs
  3. 183 0
      bird_tool/SerialManager.cs
  4. 1055 0
      bird_tool/TestEngine.cs
  5. 344 0
      bird_tool/TestStepList.cs
  6. 190 5
      bird_tool/bird_tool.Designer.cs
  7. 1328 4
      bird_tool/bird_tool.cs
  8. 8 0
      bird_tool/bird_tool.csproj

+ 3 - 1
.gitignore

@@ -1 +1,3 @@
-.vs/
+.vs/
+bird_tool/bin/*
+bird_tool/obj/*

+ 455 - 0
bird_tool/ProgressPanel.cs

@@ -0,0 +1,455 @@
+using System;
+using System.Drawing;
+using System.Windows.Forms;
+
+namespace Bird_tool
+{
+    public partial class ProgressPanel : UserControl
+    {
+        // 添加计时器和开始时间字段
+        private System.Windows.Forms.Timer _timer;
+        private DateTime _startTime;
+        private Label _totalTimeLabel; // 总耗时标签
+
+
+
+        public ProgressPanel()
+        {
+            InitializeTimer();
+            SetupUI();
+        }
+
+        private void InitializeTimer()
+        {
+            _timer = new System.Windows.Forms.Timer();
+            _timer.Interval = 1000; // 每秒更新一次
+            _timer.Tick += Timer_Tick;
+        }
+
+        private void SafeInvoke(Action action)
+        {
+            if (this.InvokeRequired)
+            {
+                this.Invoke(action);
+            }
+            else
+            {
+                action();
+            }
+        }
+
+        // 步骤项数据结构
+        public class TestStep
+        {
+            public string Name { get; set; }
+            public StepStatus Status { get; set; }
+            public string Details { get; set; }
+            public DateTime? StartTime { get; set; }
+            public DateTime? EndTime { get; set; }
+            public TimeSpan? Duration => EndTime.HasValue && StartTime.HasValue
+                ? EndTime - StartTime
+                : null;
+        }
+
+        
+
+        private void Timer_Tick(object sender, EventArgs e)
+        {
+            if (_startTime != DateTime.MinValue)
+            {
+                TimeSpan elapsed = DateTime.Now - _startTime;
+                _totalTimeLabel.Text = $"总耗时: {elapsed:hh\\:mm\\:ss}";
+            }
+        }
+
+        private void SetupUI()
+        {
+            // 主布局
+            this.BackColor = SystemColors.Window;
+            this.Dock = DockStyle.Fill;
+            this.Padding = new Padding(20);
+
+            // 主表格布局
+            TableLayoutPanel mainLayout = new TableLayoutPanel
+            {
+                Dock = DockStyle.Fill,
+                ColumnCount = 2, // 改为两列
+                RowCount = 6,
+                ColumnStyles =
+                {
+                    new ColumnStyle(SizeType.Percent, 30F), // 左侧列 (步骤列表)
+                    new ColumnStyle(SizeType.Percent, 70F)  // 右侧列 (信息区域)
+                },
+                RowStyles =
+                {
+                    new RowStyle(SizeType.AutoSize),
+                    new RowStyle(SizeType.AutoSize),
+                    new RowStyle(SizeType.AutoSize),
+                    new RowStyle(SizeType.AutoSize),
+                    new RowStyle(SizeType.Percent, 100F), // 主内容区域
+                    new RowStyle(SizeType.AutoSize)
+                }
+            };
+
+            // 1. 进度条
+            ProgressBar = new ProgressBar
+            {
+                Dock = DockStyle.Fill,
+                Height = 30,
+                Style = ProgressBarStyle.Continuous,
+                Margin = new Padding(0, 0, 0, 15)
+            };
+
+            // 2. 当前测试项标签
+            CurrentTestLabel = new Label
+            {
+                Dock = DockStyle.Fill,
+                Text = "当前测试项:初始化中...",
+                Font = new Font("Microsoft YaHei UI", 20F, FontStyle.Bold),
+                TextAlign = ContentAlignment.MiddleLeft,
+                Margin = new Padding(0, 0, 0, 20),
+                AutoSize = true
+            };
+            // 测试步骤标签
+            StepLabel = new Label
+            {
+                Dock = DockStyle.Fill,
+                Text = "状态: 准备开始",
+                Font = new Font("Microsoft YaHei UI", 9F, FontStyle.Regular),
+                TextAlign = ContentAlignment.MiddleLeft,
+                ForeColor = Color.DarkGray,
+                Margin = new Padding(0, 0, 0, 5),
+                AutoSize = true
+            };
+            // 4. 新增:总耗时标签
+            _totalTimeLabel = new Label
+            {
+                Dock = DockStyle.Fill,
+                Text = "总耗时: 00:00:00",
+                Font = new Font("Microsoft YaHei UI", 9F, FontStyle.Regular),
+                TextAlign = ContentAlignment.MiddleRight, // 靠右显示
+                ForeColor = Color.DarkBlue,
+                Margin = new Padding(0, 0, 0, 10),
+                AutoSize = true
+            };
+
+            // 创建测试步骤列表
+            StepList = new TestStepList();
+
+            // 中央提示区域
+            Panel centerPanel = new Panel
+            {
+                Dock = DockStyle.Fill,
+                BackColor = Color.Transparent
+            };
+
+            // 提示文字(居中)
+            MessageLabel = new Label
+            {
+                Text = "测试进行中,请稍候...",
+                Font = new Font("Microsoft YaHei UI", 24F, FontStyle.Regular),
+                TextAlign = ContentAlignment.MiddleCenter,
+                Dock = DockStyle.Fill,
+                AutoSize = false
+            };
+
+            // 图片显示区域
+            ImageBox = new PictureBox
+            {
+                SizeMode = PictureBoxSizeMode.Zoom,
+                Visible = false,
+                Dock = DockStyle.Top,
+                Height = 150
+            };
+
+            // 组装中央面板
+            centerPanel.Controls.Add(MessageLabel);
+            centerPanel.Controls.Add(ImageBox);
+
+            // 4. 日志按钮区域
+            // 5. 按钮区域
+            FlowLayoutPanel buttonPanel = new FlowLayoutPanel
+            {
+                Dock = DockStyle.Fill,
+                AutoSize = true,
+                FlowDirection = FlowDirection.RightToLeft,
+                Padding = new Padding(5),
+                Margin = new Padding(0, 20, 0, 0)
+            };
+
+            LogButton = new Button
+            {
+                Text = "显示日志",
+                Size = new Size(100, 50),
+                Font = new Font("Microsoft YaHei UI", 14F),
+                BackColor = Color.LightGray,
+                FlatStyle = FlatStyle.Flat,
+                Margin = new Padding(5)
+            };
+            LogButton.Click += LogButton_Click;
+
+            // 停止按钮
+            StopButton = new Button
+            {
+                Text = "停止测试",
+                Size = new Size(100, 50),
+                Font = new Font("Microsoft YaHei UI", 14F),
+                BackColor = Color.LightCoral,
+                FlatStyle = FlatStyle.Flat,
+                Margin = new Padding(5),
+                Tag = "stop" // 用于标识按钮状态
+            };
+            StopButton.Click += StopButton_Click;
+
+            StartButton = new Button
+            { 
+                Text = "开始测试",
+                Size = new Size(100, 50),
+                Font = new Font("Microsoft YaHei UI", 14F),
+                BackColor = Color.Green,
+                FlatStyle = FlatStyle.Flat,
+                Margin = new Padding(5),
+                Visible = false
+            };
+            StartButton.Click += StartButton_Click;
+
+            // 组装面板
+            buttonPanel.Controls.Add(LogButton);
+            buttonPanel.Controls.Add(StartButton);
+            buttonPanel.Controls.Add(StopButton);
+
+            mainLayout.Controls.Add(ProgressBar, 0, 0);
+            mainLayout.SetColumnSpan(ProgressBar, 2); // 跨两列
+
+            mainLayout.Controls.Add(CurrentTestLabel, 0, 1);
+            mainLayout.SetColumnSpan(CurrentTestLabel, 2); // 跨两列
+
+            mainLayout.Controls.Add(StepLabel, 0, 2);
+            mainLayout.SetColumnSpan(StepLabel, 2); // 跨两列
+
+            // 添加总耗时标签(跨两列)
+            mainLayout.Controls.Add(_totalTimeLabel, 0, 3);
+            mainLayout.SetColumnSpan(_totalTimeLabel, 2);
+
+            mainLayout.Controls.Add(StepList, 0, 4); // 新增步骤列表
+            mainLayout.Controls.Add(centerPanel, 1, 4);
+
+            mainLayout.Controls.Add(buttonPanel, 0, 5);
+            mainLayout.SetColumnSpan(buttonPanel, 2); // 跨两列
+
+            this.Controls.Add(mainLayout);
+        }
+
+        // 公共属性
+        public ProgressBar ProgressBar { get; private set; }
+        public Label CurrentTestLabel { get; private set; }
+        public Label StepLabel { get; private set; }
+        public TestStepList StepList { get; private set; }
+        public Label MessageLabel { get; private set; }
+        public PictureBox ImageBox { get; private set; }
+        public Button LogButton { get; private set; }
+        public Button StopButton { get; private set; }
+        public Button StartButton { get; private set; }
+        private void LogButton_Click(object sender, EventArgs e)
+        {
+            OnLogRequested?.Invoke(this, EventArgs.Empty);
+        }
+
+        private void StopButton_Click(object sender, EventArgs e)
+        {
+            if (StopButton.Tag.ToString() == "stop")
+            {
+                // 停止测试
+                OnStopRequested?.Invoke(this, EventArgs.Empty);
+            }
+            else
+            {
+                // 返回主界面
+                OnReturnRequested?.Invoke(this, EventArgs.Empty);
+            }
+        }
+        private void StartButton_Click(object sender, EventArgs e)
+        {
+            OnStartRequested?.Invoke(this, EventArgs.Empty);
+            startTest();
+        }
+        public void startTest()
+        {
+            // 隐藏开始测试按钮
+            StartButton.Visible = false;
+
+            // 重置停止按钮
+            StopButton.Text = "停止测试";
+            StopButton.BackColor = Color.LightCoral;
+            StopButton.Tag = "stop";
+            StartTimer();
+            StepList.SetTestRunning(true);
+
+        }
+
+        // 启动计时器
+        public void StartTimer()
+        {
+            _startTime = DateTime.Now;
+            _timer.Start();
+        }
+
+        // 停止计时器
+        public void StopTimer()
+        {
+            _timer.Stop();
+            // 更新最后一次时间
+            if (_startTime != DateTime.MinValue)
+            {
+                TimeSpan elapsed = DateTime.Now - _startTime;
+                _totalTimeLabel.Text = $"总耗时: {elapsed:hh\\:mm\\:ss}";
+            }
+        }
+
+        
+
+        // 进度管理
+        public int ProgressValue
+        {
+            get => ProgressBar.Value;
+            set => ProgressBar.Value = Math.Max(0, Math.Min(100, value));
+        }
+
+        // 当前测试项
+        public string CurrentTest
+        {
+            get => CurrentTestLabel.Text.Replace("当前测试项:", "");
+            set => CurrentTestLabel.Text = $"当前测试项:{value}";
+        }
+
+
+        public void SetTestStatus(string statusText, Color color)
+        {
+            StepLabel.Text = statusText;
+            StepLabel.ForeColor = color;
+        }
+
+        // 提示消息
+        public string Message
+        {
+            get => MessageLabel.Text;
+            set => MessageLabel.Text = value;
+        }
+
+        // 提示信息
+        public string StateText
+        {
+            get => StepLabel.Text;
+            set => StepLabel.Text = value;
+        }
+
+        // 设置图片
+        public void SetImage(Image image)
+        {
+            if (image != null)
+            {
+                ImageBox.Image = image;
+                ImageBox.Visible = true;
+            }
+            else
+            {
+                ImageBox.Visible = false;
+            }
+        }
+
+        // 添加步骤的公共方法
+        public void AddTestStep(string key, string name, string details = "")
+        {
+            StepList.AddStep(key, name);
+        }
+
+
+
+        // 更新步骤状态
+        public void UpdateTestStepStatus(string key, string name, StepStatus status, string details = "")
+        {
+            StepList.UpdateStepStatus(key, name, status);
+        }
+
+
+
+        public void ShowTestResult(bool isSuccess, string message, Image resultImage = null)
+        {
+            SafeInvoke(() =>
+            {
+                try
+                {
+                    // 停止计时器
+                    StopTimer();
+                    StepList.SetTestRunning(false);
+                    // 更新消息
+                    MessageLabel.Text = message;
+                    MessageLabel.ForeColor = isSuccess ? Color.Green : Color.Red;
+
+                    // 设置结果图片
+                    SetImage(resultImage);
+
+                    // 更新停止按钮为返回按钮
+                    StopButton.Text = "返回主页";
+                    StopButton.BackColor = isSuccess ? Color.LightGreen : Color.LightPink;
+                    StopButton.Tag = "return";
+
+                    // 显示开始测试按钮
+                    StartButton.Visible = true;
+
+                    // 隐藏进度条
+                    ProgressBar.Visible = false;
+
+                    // 更新状态标签
+                    StepLabel.Text = isSuccess ? "状态: 测试成功" : "状态: 测试失败";
+                    StepLabel.ForeColor = isSuccess ? Color.Green : Color.Red;
+                }
+                catch (Exception ex)
+                {
+                    // 可以记录日志或显示错误
+                    System.Diagnostics.Debug.WriteLine($"显示测试结果时出错: {ex.Message}");
+                }
+            });
+        }
+
+
+        // 重置测试面板
+        public void ResetPanel()
+        {
+            // 重置计时器
+            _timer.Stop();
+            _totalTimeLabel.Text = "总耗时: 00:00:00";
+            _startTime = DateTime.MinValue;
+
+            ProgressBar.Visible = true;
+            ProgressBar.Value = 0;
+            CurrentTest = "准备开始";
+            Message = "测试即将开始...";
+            MessageLabel.ForeColor = SystemColors.ControlText;
+            StepLabel.Text = "状态: 准备开始";
+            StepLabel.ForeColor = Color.DarkGray;
+            SetImage(null);
+
+            // 重置停止按钮
+            //StopButton.Text = "停止测试";
+            //StopButton.BackColor = Color.LightCoral;
+            //StopButton.Tag = "stop";
+
+            StopButton.Text = "返回主页";
+            StopButton.BackColor = Color.LightGreen;
+            StopButton.Tag = "return";
+
+            StartButton.Visible = true;
+
+            StepList.Reset();
+        }
+
+        
+
+        // 日志请求事件
+        public event EventHandler OnLogRequested;
+        public event EventHandler OnStopRequested;
+        public event EventHandler OnReturnRequested;
+        public event EventHandler OnStartRequested;
+    }
+}

+ 183 - 0
bird_tool/SerialManager.cs

@@ -0,0 +1,183 @@
+using System;
+using System.IO.Ports; // 添加必要的命名空间
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Windows.Forms; // 用于BeginInvoke调用
+
+namespace Bird_tool
+{
+    class SerialManager
+    {
+        private SerialPort _uart = new SerialPort();
+        private int MAX_BUFFER_SIZE = 4096; // 最大缓冲区大小
+
+        // 定义行数据接收事件
+        public event Action<string> OnLineReceived;
+
+        public string _tmp_text = ""; // 暂存串口返回的数据
+
+        public Regex reg_R = new Regex(@"\r");      // 匹配/r回车符
+        public Regex reg_N = new Regex(@"\n");      // 匹配/n换行符
+        public Regex reg_RN = new Regex(@"\r\n");   // 匹配标准换行符
+
+        public void Config(int dataBits = 8, StopBits stopBits = StopBits.One, Parity parity = Parity.None)
+        {
+            _uart.DataBits = dataBits;   // 数据位
+            _uart.StopBits = stopBits;   // 停止位
+            _uart.Parity = parity;      // 校验位
+        }
+
+        public void SetMaxBuffer(int buffer_size)
+        {
+            MAX_BUFFER_SIZE = buffer_size;
+        }
+
+        public bool Connect(string portName, int baudRate = 115200)
+        {
+            if (_uart.IsOpen) return false;
+
+            _uart.PortName = portName;
+            _uart.BaudRate = baudRate;
+            _uart.Encoding = Encoding.ASCII;
+            _uart.DataReceived += CommDataReceived;
+
+            try
+            {
+                _uart.Open();
+                System.Threading.Thread.Sleep(150);
+                return true;
+            }
+            catch (Exception ex)
+            {
+                ErrorHandle("串口打开失败", ex);
+                return false;
+            }
+        }
+
+        public void Disconnect()
+        {
+            if (_uart.IsOpen)
+            {
+                _uart.DataReceived -= CommDataReceived;
+                _uart.Close();
+            }
+            _tmp_text = ""; // 清空缓冲区
+        }
+
+        public bool IsOpen ()
+        {
+            return _uart.IsOpen;
+        }
+
+        private void CommDataReceived(object sender, SerialDataReceivedEventArgs e)
+        {
+            try
+            {
+                int bytesToRead = _uart.BytesToRead;
+                byte[] buffer = new byte[bytesToRead];
+                _uart.Read(buffer, 0, bytesToRead);
+                string receivedText = Encoding.ASCII.GetString(buffer);
+
+                // 使用委托更新UI线程
+                if (System.Windows.Forms.Application.OpenForms.Count > 0)
+                {
+                    var mainForm = System.Windows.Forms.Application.OpenForms[0];
+                    mainForm.BeginInvoke((MethodInvoker)delegate {
+                        ProcessReceivedData(receivedText);
+                    });
+                }
+                else
+                {
+                    // 非UI环境直接处理
+                    ProcessReceivedData(receivedText);
+                }
+            }
+            catch (Exception ex)
+            {
+                ErrorHandle("串口接收异常", ex);
+            }
+        }
+
+        private void ProcessReceivedData(string newData)
+        {
+            _tmp_text += newData;
+
+            // 缓冲区溢出保护
+            if (_tmp_text.Length > MAX_BUFFER_SIZE)
+            {
+                ParseText(_tmp_text.Substring(0, MAX_BUFFER_SIZE));
+                _tmp_text = _tmp_text.Substring(MAX_BUFFER_SIZE);
+            }
+            
+            // 处理所有可能的换行符组合
+            while (true)
+            {
+                int rnIndex = _tmp_text.IndexOf("\r\n");
+                int rIndex = _tmp_text.IndexOf('\r');
+                int nIndex = _tmp_text.IndexOf('\n');
+
+                int splitIndex = -1;
+                int splitLength = 0;
+
+                if (rnIndex >= 0)
+                {
+                    splitIndex = rnIndex;
+                    splitLength = 2;
+                }
+                else if (rIndex >= 0 && (rIndex < nIndex || nIndex < 0))
+                {
+                    splitIndex = rIndex;
+                    splitLength = 1;
+                }
+                else if (nIndex >= 0)
+                {
+                    splitIndex = nIndex;
+                    splitLength = 1;
+                }
+
+                if (splitIndex < 0) break;
+
+                string line = _tmp_text.Substring(0, splitIndex);
+                _tmp_text = _tmp_text.Substring(splitIndex + splitLength);
+                
+                if (!string.IsNullOrWhiteSpace(line))
+                {
+                    ParseText(line);
+                }
+            }
+        }
+
+        int i = 0;
+        private void ParseText(string line)
+        {
+            // 调用外部注册的事件处理器
+            OnLineReceived?.Invoke(line);
+        }
+
+        public void SendCommand(string command)
+        {
+            if (_uart.IsOpen)
+            {
+                try
+                {
+                    _uart.WriteLine(command);
+                }
+                catch (Exception ex)
+                {
+                    ErrorHandle("发送命令失败", ex);
+                }
+            }
+        }
+
+        private void ErrorHandle(string tips, Exception ex)
+        {
+            // 确保日志系统存在或替换为您的实际实现
+            bird_tool.Log_show($"{tips} {ex.Message}");
+            bird_tool.Log($"{tips} {ex.Message}");
+
+            Console.WriteLine($"{tips}: {ex.Message}");
+        }
+
+        
+    }
+}

+ 1055 - 0
bird_tool/TestEngine.cs

@@ -0,0 +1,1055 @@
+
+
+// TestEngine.cs
+using System;
+using System.Collections.Generic;
+using System.IO.Ports;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Bird_tool
+{
+    public delegate bool StepValidator(string response, TestContext context);
+
+
+
+    //public class TestResult
+    //{
+    //    public bool IsRunning { get; set; } = true;
+    //    public int TotalSteps => Steps.Count;
+    //    public int CompletedSteps { get; set; }
+    //    public string CurrentStepName { get; set; }
+    //    public string CurrentStepDetail { get; set; }
+    //    public int CurrentStepProgress { get; set; }
+    //    public int OverallProgress => CompletedSteps * 100 / TotalSteps;
+
+    //    public List<TestStepResult> StepResults { get; } = new List<TestStepResult>();
+    //    public List<TestStepConfig> Steps { get; set; }
+
+    //    public void StartStep(TestStepConfig step)
+    //    {
+    //        CurrentStepName = step.Name;
+    //        CurrentStepDetail = "开始执行...";
+    //        CurrentStepProgress = 0;
+
+    //        StepResults.Add(new TestStepResult
+    //        {
+    //            Name = step.Name,
+    //            Status = TestStatus.Running,
+    //            StartTime = DateTime.Now
+    //        });
+    //    }
+
+    //    public void CompleteStep(TestStatus status, string details = "")
+    //    {
+    //        if (StepResults.Count == 0) return;
+
+    //        var currentResult = StepResults.Last();
+    //        currentResult.Status = status;
+    //        currentResult.EndTime = DateTime.Now;
+    //        currentResult.Details = details;
+
+    //        if (status == TestStatus.Passed || status == TestStatus.Skipped)
+    //        {
+    //            CompletedSteps++;
+    //        }
+    //    }
+
+    //    public void FailCurrentStep(string reason)
+    //    {
+    //        CompleteStep(TestStatus.Failed, reason);
+    //        IsRunning = false;
+    //    }
+
+    //    public void MarkAsCompleted()
+    //    {
+    //        CompleteStep(TestStatus.Passed, "所有步骤完成");
+    //        IsRunning = false;
+    //    }
+
+    //    public void SkipRemaining()
+    //    {
+    //        foreach (var step in Steps.Skip(StepResults.Count))
+    //        {
+    //            StepResults.Add(new TestStepResult
+    //            {
+    //                Name = step.Name,
+    //                Status = TestStatus.Skipped,
+    //                StartTime = DateTime.Now,
+    //                EndTime = DateTime.Now
+    //            });
+    //        }
+    //        IsRunning = false;
+    //    }
+    //}
+
+    public class TestStepResult
+    {
+        public string Name { get; set; }
+        public TestStatus Status { get; set; } = TestStatus.NotRun;
+        public string Details { get; set; } = "";
+        public DateTime StartTime { get; set; }
+        public DateTime EndTime { get; set; }
+        public TimeSpan Duration => EndTime - StartTime;
+        public int RetryCount { get; set; }
+        public string ErrorMessage { get; set; } = "";
+    }
+
+    public enum TestStatus
+    {
+        NotRun,
+        Running,
+        Passed,
+        Failed,
+        Timeout,
+        Skipped
+    }
+
+    // 步骤运行状态
+    public enum StepStatus
+    {
+        NotRun,
+        Running,
+        Waiting,
+        Failed,
+        Success
+    }
+    public enum ActionMode
+    {
+        SendCmd,
+        SetVal,
+        WaitTime,
+    }
+    public class TestGroup
+    {
+        // 测试组, 用于多组测试项合并测试
+        public string GroupId { get; set; }
+        // 测试组 名称 
+        public string GroupName { get; set; }
+        // 测试组 列名
+        public string RowKey { get; set; }
+        // 是否显示
+        public bool ShowStep { get; set; } = true;
+
+        public static List<TestStepConfig> GetStepsById(List<TestStepConfig> steps, string groupId)
+        {
+            return steps.Where(step => step.EffectiveGroupId == groupId).ToList();
+        }
+
+        // 获取分组信息列表
+        // 获取分组信息列表
+        public static List<TestGroup> GetGroupInfos(List<TestStepConfig> steps)
+        {
+            return steps
+                .GroupBy(step => step.EffectiveGroupId)
+                .Select(g => new TestGroup
+                {
+                    GroupId = g.Key,
+                    GroupName = g.Last().GroupName ?? g.Last().Name, // 默认使用分组ID作为名称
+                    RowKey = g.First().RowKey ?? g.Key, // 默认使用分组ID作为列键
+                    ShowStep = g.First().ShowStep
+                })
+                .ToList();
+        }
+
+
+
+    }
+
+
+    public class TestStepConfig
+    {
+        // 新增有效分组ID属性
+        public string EffectiveGroupId =>
+        string.IsNullOrEmpty(GroupId) ? Key : GroupId;
+        // 测试组id, 用于多个测试项合并成一个测试项
+        public string GroupId { get; set; }
+        // 测试组 名称 
+        public string GroupName { get; set; }
+        // 测试组 列名
+        public string RowKey { get; set; }
+        // 基础配置
+        public string Name { get; set; }
+        public string Details { get; set; }
+        // 界面中间显示的引导操作, 或者提示性文字
+        public string Tips { get; set; }
+        // 任务失败后的提示性文字, 用于提示操作员什么地方异常
+        public string FailTips { get; set; }
+        // 动作形式
+        public ActionMode Action { get; set; } = ActionMode.SendCmd;
+        public string Command { get; set; }
+        public Func<TestContext, string> CommandGenerator { get; set; }
+
+        public string SuccessPattern { get; set; }
+        public string SuccessText { get; set; }
+        public string FailurePattern { get; set; }
+        public string FailureText { get; set; }
+        public string ExtractPattern { get; set; }
+        public List<string> VariableNames { get; } = new List<string>();
+        // 变量值列表
+        public List<string> VariableValues { get; } = new List<string>();
+
+        public int Timeout { get; set; } = 3000;
+        // 是否为隐私命令, 隐私命令不显示对应发送命令
+        public bool PrivateCammand { get; set; } = false;
+
+        // 失败后重试
+        public int MaxRetries { get; set; } = 3;
+        public int RetryCount { get; set; } = 0;
+
+        // 是否创建对应的提示列表
+        public bool ShowStep { get; set; } = true;
+
+        // 跳转任务失败次数只有在包含 RetryFromKey 时生效
+        public string Key { get; set; }
+        // 失败后要跳转到的条目
+        public string RetryFromKey { get; set; }
+        // 失败后是否重试当前步骤
+        public bool StepRetry { get; set; } = true;
+        public int MaxJump { get; set; } = 3;
+        public int JumpCount { get; set; } = 0;
+
+        // 失败后是否继续测试
+        public bool FailContinue { get; set; } = false;
+
+        public string Description { get; set; }
+
+        public int DelayBefore { get; set; } = 0;
+
+        public int DelayAfter { get; set; } = 0;
+
+        public int DelayAfterPrompt { get; set; } = 0;
+
+
+        // 用户交互
+        public bool RequiresUserPrompt { get; set; }
+        // 用户确认
+        public string PromptQuestion { get; set; }
+
+
+        public StepStatus stepStatus { get; set; } = StepStatus.NotRun;
+
+        // result data
+
+
+        // 自定义逻辑
+        public StepValidator Validator { get; set; } // 自定义验证函数
+        public Action<TestContext> OnSuccess { get; set; }
+        public Action<TestContext> OnFailure { get; set; }
+
+        // 自动生成描述(如果未设置)
+        public string GetDescription()
+        {
+            if (!string.IsNullOrEmpty(Description))
+                return Description;
+
+            var sb = new StringBuilder();
+
+            if (!string.IsNullOrEmpty(Command))
+            {
+                sb.AppendLine($"执行命令: {Command}");
+            }
+
+            if (!string.IsNullOrEmpty(SuccessPattern))
+            {
+                sb.AppendLine($"成功匹配: {SuccessPattern}");
+            }
+            if (!string.IsNullOrEmpty(SuccessText))
+            {
+                sb.AppendLine($"成功匹配: {SuccessText}");
+            }
+
+            if (!string.IsNullOrEmpty(FailurePattern))
+            {
+                sb.AppendLine($"失败匹配: {FailurePattern}");
+            }
+
+            if (!string.IsNullOrEmpty(FailureText))
+            {
+                sb.AppendLine($"失败匹配: {FailureText}");
+            }
+
+            if (RequiresUserPrompt)
+            {
+                sb.AppendLine($"需要用户确认: {PromptQuestion}");
+            }
+
+            if (Validator != null)
+            {
+                sb.AppendLine("包含自定义验证逻辑");
+            }
+
+            return sb.ToString();
+        }
+
+    }
+
+    public class TestContext
+    {
+        public Dictionary<string, string> Variables { get; } = new Dictionary<string, string>();
+        public int CurrentStepIndex { get; set; }
+        // 重试次数
+        public int RetryCount { get; set; }
+        // 当前测试项的状态
+        public TestStepConfig CurrentStep => Steps?[CurrentStepIndex];
+        public List<TestStepConfig> Steps { get; set; }
+        //public TestResult Result { get; set; }
+        public bool IsRunning { get; set; }
+    }
+
+    class TestExecutor
+    {
+        public delegate void LogHandler(string message, LogLevel level);
+
+        public delegate void PromptHandler(string question, Action onYes, Action onNo, int waitTime = 60);
+        public delegate bool StepValidator(string response, TestContext context);
+        public delegate void StepChangedHandler(TestStepConfig step, TestContext context, bool isStarting);
+        public delegate void TestFailHandler(TestStepConfig step, TestContext context);
+        public delegate void TestSuccessHandler(TestContext context);
+
+
+        public event LogHandler OnLog;
+        public event PromptHandler OnPrompt;
+        public event StepChangedHandler OnStepChanged;
+        public event TestFailHandler OnFailed;
+        public event TestSuccessHandler OnSuccess;
+
+        private readonly SerialManager _serialManager;
+        private TestContext _context;
+        private System.Threading.Timer _timeoutTimer;
+        private readonly object _lock = new object();
+        // 步骤表
+        private List<TestStepConfig> _step_map;
+
+        public bool isStart { get; private set; } = false;
+        public bool isInit { get; private set; } = false;
+
+        public TestExecutor(SerialManager serialManager)
+        {
+            bird_tool.Log("TestExecutor ");
+            _serialManager = serialManager;
+            _serialManager.OnLineReceived += HandleResponse;
+        }
+
+        // 新增方法:将重试操作加入队列异步执行
+        private void QueueRetryOperation(Action operation)
+        {
+            OnLog?.Invoke($"排队重试操作: {operation.Method.Name}", LogLevel.debug);
+            Task.Run(() =>
+            {
+                OnLog?.Invoke($"开始执行排队操作: {operation.Method.Name}", LogLevel.debug);
+                // 添加短暂延迟避免立即重试
+                Thread.Sleep(100);
+                
+                lock (_lock)
+                {
+                    // 检查测试是否仍在运行
+                    if (_context != null && _context.IsRunning)
+                    {
+                        operation();
+                    }
+                }
+            });
+        }
+
+        
+        public void StopTest()
+        {
+            lock (_lock)
+            {
+                
+                if (isStart)
+                {
+                    isStart = false;
+                    // 确保释放计时器资源
+                    _timeoutTimer?.Dispose();
+                    _timeoutTimer = null;
+
+                    _context.IsRunning = false;
+                    _context = null;
+                }
+                
+            }
+        }
+        private bool StartTestWithSteps(List<TestStepConfig> steps)
+        {
+            lock (_lock)
+            {
+                if (steps == null || steps.Count == 0)
+                {
+                    OnLog?.Invoke("步骤列表为空,无法启动测试", LogLevel.error);
+                    return false;
+                }
+                foreach (var step in steps)
+                {
+                    step.JumpCount = 0;
+                    step.stepStatus = StepStatus.NotRun;
+                    step.RetryCount = 0;
+                }
+                // 初始化上下文
+                _context = new TestContext
+                {
+                    Steps = steps, // 使用传入的步骤列表
+                    CurrentStepIndex = -1
+                };
+
+                isStart = true;
+
+                MoveToNextStep();
+                return true;
+            }
+        }
+
+        public bool StartTest()
+        {
+            lock (_lock)
+            {
+                if (!isInit)
+                {
+                    return false;
+                }
+                StartTestWithSteps(_step_map);
+                return true;
+            }
+        }
+        public bool StartTestFromGroup(string groupId)
+        {
+            lock (_lock)
+            {
+                if (!isInit)
+                {
+                    return false;
+                }
+
+                // 获取分组的所有步骤
+                int firstIndex = _step_map.FindIndex(step => step.EffectiveGroupId == groupId);
+                if (firstIndex == -1)
+                {
+                    OnLog?.Invoke($"无法找到分组 {groupId} 的起始步骤", LogLevel.error);
+                    return false;
+                }
+                // todo 从第一个id出现的位置提取分组后面的所有内容
+                var stepsFromGroup = _step_map.Skip(firstIndex).ToList();
+                foreach (var step in stepsFromGroup)
+                {
+                    step.JumpCount = 0;
+                    step.stepStatus = StepStatus.NotRun;
+                    step.RetryCount = 0;
+                }
+                return StartTestWithSteps(stepsFromGroup);
+            }
+        }
+        public bool StartTestGroup(string groupId)
+        {
+            lock (_lock)
+            {
+                if (!isInit)
+                {
+                    return false;
+                }
+
+                // 直接从 _step_map 中提取分组步骤
+                var groupSteps = TestGroup.GetStepsById(_step_map, groupId);
+                if (groupSteps.Count == 0)
+                {
+                    OnLog?.Invoke($"未找到分组ID为 {groupId} 的步骤", LogLevel.error);
+                    return false;
+                }
+                OnLog?.Invoke($"找到分组ID为 {groupId} 的步骤", LogLevel.info);
+                
+
+                // 使用提取的分组步骤启动测试
+                return StartTestWithSteps(groupSteps);
+            }
+        }
+
+
+        public bool InitTest(List<TestStepConfig> steps)
+        {
+            if (steps.Count < 1)
+            {
+                return false;
+            }
+            lock (_lock)
+            {
+
+                _step_map = InitSteps(steps);
+
+                isStart = false;
+                isInit = true;
+                return true;
+            }
+        }
+        public List<TestStepConfig> InitSteps(List<TestStepConfig> steps)
+        {
+            var keySet = new HashSet<string>();
+            int counter = 1;
+
+            // 创建分组字典,用于存储分组信息
+            var groupInfoDict = new Dictionary<string, (string GroupName, string RowKey)>();
+
+            foreach (var step in steps)
+            {
+                // 确保每个步骤都有唯一的Key
+                if (string.IsNullOrEmpty(step.Key))
+                {
+                    step.Key = $"step_{counter++}";
+                }
+                else if (keySet.Contains(step.Key))
+                {
+                    int suffix = 2;
+                    string newKey;
+                    do
+                    {
+                        newKey = $"{step.Key}_{suffix++}";
+                    } while (keySet.Contains(newKey));
+
+                    step.Key = newKey;
+                    OnLog?.Invoke($"警告: 步骤Key '{step.Key}' 已存在,已重命名为 '{newKey}'", LogLevel.error);
+                }
+                keySet.Add(step.Key);
+
+                // 处理分组信息
+                if (string.IsNullOrEmpty(step.GroupId))
+                {
+                    // 如果没有分组ID,使用Key作为默认分组ID
+                    step.GroupId = step.Key;
+                    step.GroupName = step.Name;
+                    step.RowKey = step.GroupId;
+                }
+            }
+
+            // 第二遍遍历:按分组处理
+            var groupedSteps = steps
+                .GroupBy(s => s.GroupId)
+                .ToList();
+
+            foreach (var group in groupedSteps)
+            {
+                var groupId = group.Key;
+                var groupSteps = group.ToList();
+
+                // 确定分组名:
+                // 1. 优先使用组内已设置的GroupName
+                // 2. 如果都没有设置,使用最后一个步骤的Name
+                string groupName = groupSteps
+                    .Where(s => !string.IsNullOrEmpty(s.GroupName))
+                    .Select(s => s.GroupName)
+                    .FirstOrDefault();
+
+                if (string.IsNullOrEmpty(groupName))
+                {
+                    groupName = groupSteps.Last().Name;
+                }
+
+                // 确定行键:
+                // 1. 优先使用组内已设置的RowKey
+                // 2. 如果都没有设置,使用分组ID
+                string rowKey = groupSteps
+                    .Where(s => !string.IsNullOrEmpty(s.RowKey))
+                    .Select(s => s.RowKey)
+                    .FirstOrDefault();
+
+                if (string.IsNullOrEmpty(rowKey))
+                {
+                    rowKey = groupId;
+                }
+
+
+                // 应用分组信息到所有步骤
+                foreach (var step in groupSteps)
+                {
+                    step.GroupName = groupName;
+                    step.RowKey = rowKey;
+                }
+            }
+
+            return steps;
+        }
+
+
+        // 获取所有分组ID
+        public List<string> GetAllGroupIds()
+        {
+            lock (_lock)
+            {
+                return _step_map?
+                    .Select(step => step.EffectiveGroupId)
+                    .Distinct()
+                    .ToList() ?? new List<string>();
+            }
+        }
+
+        // 获取分组信息列表
+        public List<TestGroup> GetGroupInfos()
+        {
+            lock (_lock)
+            {
+                return _step_map?
+                    .GroupBy(step => step.EffectiveGroupId)
+                    .Select(g => new TestGroup
+                    {
+                        GroupId = g.Key,
+                        GroupName = !string.IsNullOrWhiteSpace(g.First().GroupName)
+                            ? g.First().GroupName
+                            : g.First().Name,
+                        RowKey = !string.IsNullOrWhiteSpace(g.First().RowKey)
+                                ? g.First().RowKey
+                                : g.Key // 默认使用分组ID作为列键
+                    })
+                    .ToList() ?? new List<TestGroup>();
+            }
+        }
+
+        // 从特定方法开始执行
+
+
+        // 新增字符串模板替换方法
+        public string ReplaceTemplateVariables(string input, Dictionary<string, string> variables)
+        {
+            return Regex.Replace(input, @"\{\{(\w+)\}\}", match =>
+            {
+                var varName = match.Groups[1].Value;
+                return variables.TryGetValue(varName, out var value) ? value : $"{{{{UNDEFINED:{varName}}}}}";
+            });
+        }
+
+        private async void ExecuteCurrentStep()
+        {
+            var step = _context.CurrentStep;
+
+            // 记录步骤开始
+            OnLog?.Invoke($"开始步骤: {step.Name}", LogLevel.info);
+            step.stepStatus = StepStatus.Running;
+            OnStepChanged?.Invoke(step, _context, true);
+            if (step.DelayBefore > 0)
+            {
+                OnLog?.Invoke($"等待 {step.DelayBefore}ms (执行前延迟)", LogLevel.debug);
+                await Task.Delay(step.DelayBefore);
+            }
+
+            if (step.Action == ActionMode.SetVal)
+            {
+                // 从VaruableValues 将内容设置至 VaruableNames 中
+                if (step.VariableValues.Count == step.VariableNames.Count)
+                {
+                    for (int i = 0; i < step.VariableValues.Count; i++)
+                    {
+                        var varName = step.VariableNames[i];
+                        string tmp_str = $"设置变量: {varName} = {step.VariableValues[i]}";
+                        _context.Variables[varName] = step.VariableValues[i];
+                        if (step.PrivateCammand)
+                        {
+                            OnLog?.Invoke("设置变量", LogLevel.debug);
+                        }
+                        else
+                        {
+                            OnLog?.Invoke(tmp_str, LogLevel.info);
+                        }
+                    }
+                    HandleSetpSuccess("变量设置完成");
+                }
+                else
+                {
+                    HandleStepFailure("配置文件异常, 变量长度异常");
+                }
+            }
+            else if (step.Action == ActionMode.SendCmd)
+            {
+                // 重置超时计时器
+                _timeoutTimer?.Dispose();
+                _timeoutTimer = null;
+
+                var command = step.Command;
+                // 支持动态命令生成
+                if (step.CommandGenerator != null)
+                {
+                    command = step.CommandGenerator(_context);
+                }
+
+                // 新增模板变量替换
+                command = ReplaceTemplateVariables(command, _context.Variables);
+
+                // 发送命令
+                if (!string.IsNullOrEmpty(command))
+                {
+                    if (!step.PrivateCammand)
+                    {
+                        OnLog?.Invoke($"发送命令: {command.Trim()}", LogLevel.info);
+                    }
+                    _serialManager.SendCommand(command);
+                }
+
+                if (step.DelayAfter > 0)
+                {
+                    OnLog?.Invoke($"等待 {step.DelayBefore}ms (发送后延迟)", LogLevel.debug);
+                    await Task.Delay(step.DelayAfter);
+                }
+                // 让当前任务的状态转变未Waiting, 用于在发送消息在进行匹配
+                step.stepStatus = StepStatus.Waiting;
+
+                OnLog?.Invoke($"配置定时器 {step.Timeout}", LogLevel.debug);
+                _context.IsRunning = true;
+                _timeoutTimer?.Dispose();
+                _timeoutTimer = null;
+                _timeoutTimer = new System.Threading.Timer(
+                    _ => HandleTimeout(),
+                    null,
+                    step.Timeout,
+                    Timeout.Infinite
+                );
+
+                // 如果没有SuccessPattern 与 SuccessText则直接进行询问
+                if (step.RequiresUserPrompt && string.IsNullOrEmpty(step.SuccessPattern) && string.IsNullOrEmpty(step.SuccessText))
+                {
+                    PromptQuestion(step);
+                }
+            }
+
+        }
+
+        public void HandleResponse(string data)
+        {
+
+            // 减少锁的持有时间
+            TestContext localContext = null;
+            TestStepConfig step = null;
+            OnLog?.Invoke($"收到响应 {data} ", LogLevel.debug);
+
+            lock (_lock)
+            {
+                if (_context == null || !_context.IsRunning) return;
+                localContext = _context;
+                step = _context.CurrentStep;
+            }
+
+            // 判断当前任务是否已经开始
+            if (step.stepStatus != StepStatus.Waiting)
+            {
+                return;
+            }
+
+            // 检查成功模式
+            if (
+                (!string.IsNullOrEmpty(step.SuccessPattern) && Regex.IsMatch(data, step.SuccessPattern))
+                ||
+                (!string.IsNullOrEmpty(step.SuccessText) && data.Contains(step.SuccessText))
+                )
+            {
+
+                if (!string.IsNullOrEmpty(step.ExtractPattern) && !ExtractVariables(data, step))
+                {
+                    if (!ExtractVariables(data, step))
+                    {
+                        OnLog?.Invoke($"匹配成功, 但是无法提取变量 {data} ", LogLevel.info);
+                        return;
+                    }
+                }
+
+                // 自定义验证逻辑
+                bool shouldRetry = false;
+                if (step.Validator != null)
+                {
+                    shouldRetry = step.Validator(data, _context);
+                }
+
+                if (shouldRetry)
+                {
+                    OnLog?.Invoke("验证失败,需要重试", LogLevel.info);
+                    HandleStepFailure("自定义验证失败", false);
+                }
+                else
+                {
+                    // 判断是否需要进行提问. 如果需要提问则先进行提问
+                    if (step.RequiresUserPrompt)
+                    {
+                        PromptQuestion(step);
+                    }
+                    else
+                    {
+                        HandleSetpSuccess("匹配到成功关键词");
+                    }
+                }
+
+                return;
+            }
+
+            // 检查失败模式
+            if ((!string.IsNullOrEmpty(step.FailurePattern) && Regex.IsMatch(data, step.FailurePattern))
+                ||
+                (!string.IsNullOrEmpty(step.FailureText) && data.Contains(step.FailureText))
+                )
+            {
+                HandleStepFailure("匹配到失败关键词", false);
+                return;
+            }
+            
+
+        }
+
+        private bool ExtractVariables(string data, TestStepConfig step)
+        {
+            if (string.IsNullOrEmpty(step.ExtractPattern)) return false;
+            bool isMatch = false;
+            int matchTotal = 0;
+            var match = Regex.Match(data, step.ExtractPattern);
+            if (match.Success)
+            {
+                for (int i = 1; i < match.Groups.Count; i++)
+                {
+                    var varName = step.VariableNames.Count >= i ?
+                        step.VariableNames[i - 1] : $"var{i - 1}";
+                    string msg = $"提取变量: {varName} = {match.Groups[i].Value}";
+                    _context.Variables[varName] = match.Groups[i].Value;
+                    matchTotal++;
+                    isMatch = true;
+                    OnLog?.Invoke(msg, LogLevel.info);
+                }
+            }
+            // 记录缺失变量
+            if (step.VariableNames.Count > match.Groups.Count - 1)
+            {
+                OnLog?.Invoke($"警告: 提取变量不足(需要{step.VariableNames.Count} 实际{match.Groups.Count - 1})",
+                              LogLevel.error);
+            }
+            return isMatch;
+
+        }
+
+        private void HandleTestFail()
+        {
+            OnFailed?.Invoke(_context.CurrentStep, _context);
+            StopTest();
+        }
+        private void HandleTestSuccess()
+        {
+            OnSuccess?.Invoke(_context);
+            StopTest();
+        }
+
+        private async void PromptQuestion(TestStepConfig step)
+        {
+            OnLog?.Invoke($"等待用户确认: {step.PromptQuestion}", LogLevel.info);
+            // 停止计时器, 防止用户在确认阶段导致失败
+            _timeoutTimer?.Dispose();
+            _timeoutTimer = null;
+
+            if (step.DelayBefore > 0)
+            {
+                OnLog?.Invoke($"等待 {step.DelayBefore}ms (等待确认前延迟)", LogLevel.debug);
+                await Task.Delay(step.DelayBefore);
+            }
+
+            OnPrompt?.Invoke(
+                ReplaceTemplateVariables(step.PromptQuestion, _context.Variables),
+                () => HandleSetpSuccess("用户确认成功"),
+                () => HandleStepFailure("用户确认失败", true)
+            );
+        }
+
+
+        private void HandleSetpSuccess(string message)
+        {
+            _timeoutTimer?.Dispose();
+            _timeoutTimer = null;
+            var step = _context.CurrentStep;
+            step.stepStatus = StepStatus.Success;
+            OnStepChanged?.Invoke(step, _context, false);
+            OnLog?.Invoke($"✅ 步骤完成: {step.Name} ({message})", LogLevel.info);
+            MoveToNextStep();
+        }
+
+        private void HandleStepFailure(string message, bool isQuestion = false)
+        {
+            _timeoutTimer?.Dispose();
+            _timeoutTimer = null;
+            if (_context == null)
+            {
+                OnLog?.Invoke($"测试异常 {message}", LogLevel.error);
+                HandleTestFail();
+                return;
+            }
+            var step = _context.CurrentStep;
+            step.stepStatus = StepStatus.Failed;
+            step.RetryCount++;
+            string failStr = $"【{step.Name}】失败 {message} (重试 {step.RetryCount}/{step.MaxRetries})";
+            
+            OnLog?.Invoke(failStr, LogLevel.info);
+            
+            LogLevel level = LogLevel.info;
+            // 判断是跳转到特定任务继续执行还是走失败的逻辑
+            bool isJump = false;
+            int jumpIdx = -1;
+            bool allowRetry = false;
+            // 判断当前步骤是否含有跳转执行的机会
+            if (!string.IsNullOrEmpty(step.RetryFromKey))
+            {
+                isJump = true;
+                if (step.JumpCount < step.MaxJump)
+                {
+                    jumpIdx = FindKeyByIndex(step.RetryFromKey);
+                    if (jumpIdx < 0)
+                    {
+                        failStr = $"【{step.Name}】 无法找到要跳转的任务【{step.RetryFromKey}】, 请检查配置";
+                        level = LogLevel.error;
+                        OnLog?.Invoke(failStr, level);
+                    }
+                }
+                else
+                {
+                    // 需要跳转, 但是已经没有机会了
+                    failStr = $"【{step.Name}】任务多次跳转执行失败";
+                    level = LogLevel.error;
+                }
+            }
+
+
+            // 判断是否需要执行当前的步骤
+            if (step.StepRetry)
+            {
+                // 判断是否已经超出次数
+                if (step.RetryCount >= step.MaxRetries)
+                {
+                    failStr = $"【{step.Name}】 {message} (超过最大重试次数)";
+                    level = LogLevel.info;
+                    if (isJump && jumpIdx >= 0)
+                    {
+                        OnLog?.Invoke(
+                            $"【{step.Name}】 子步骤失败: {step.Name} 将跳转至【{step.RetryFromKey}】重新执行",
+                            LogLevel.info
+                            );
+                    }
+                }
+                else
+                {
+                    allowRetry = true;
+                }
+            }
+
+
+            if (allowRetry)
+            {
+                // 重新执行任务
+                QueueRetryOperation(() => ExecuteCurrentStep());
+            }
+            else if (isJump && jumpIdx >= 0)
+            {
+                // 跳转至特定任务重新执行
+                QueueRetryOperation(() => JumpToStep(jumpIdx));
+            }
+            else
+            {
+                _context.IsRunning = false;
+               OnLog?.Invoke(failStr, level);
+                // 自定义失败处理
+                step.OnFailure?.Invoke(_context);
+                OnStepChanged?.Invoke(step, _context, false);
+                // 测试失败, 判断是直接失败还是忽略此项
+                if (step.FailContinue)
+                {
+                    QueueRetryOperation(MoveToNextStep);
+                }
+                else
+                {
+                    HandleTestFail();
+                }
+            }
+        }
+
+        private void MoveToNextStep()
+        {
+            // 判断是否需要等待 DelayAfter
+
+            _context.CurrentStepIndex++;
+
+            if (_context.CurrentStepIndex >= _context.Steps.Count)
+            {
+                // 测试完成
+                _context.IsRunning = false;
+                OnLog?.Invoke("所有测试步骤完成", LogLevel.info);
+                HandleTestSuccess();
+                return;
+            }
+            if (_context.CurrentStepIndex < _context.Steps.Count)
+            {
+                OnStepChanged?.Invoke(_context.CurrentStep, _context, true);
+                OnLog?.Invoke($"========== 进入步骤: {_context.CurrentStep.Name} ==========", LogLevel.info);
+            }
+            _context.RetryCount = 0;
+
+            // 执行下一步
+            ExecuteCurrentStep();
+        }
+        private int FindKeyByIndex(string key)
+        {
+            for (int i = 0; i < _context.Steps.Count; i++)
+            {
+                var step = _context.Steps[i];
+                if (step.Key == key)
+                {
+                    return i;
+                }
+            }
+            return -1;
+        }
+        private void JumpToStep(int newIndex)
+        {
+            if (newIndex < 0 || newIndex >= _context.Steps.Count)
+            {
+                OnLog?.Invoke($"无效的步骤索引: {newIndex}", LogLevel.error);
+                return;
+            }
+            // 跳转计数器增加
+            _context.CurrentStep.JumpCount++;
+            OnLog?.Invoke($"跳转到步骤: {_context.Steps[newIndex].Name}", LogLevel.debug);
+            // 当前步骤的失败保留, 前面任务的失败进行移除
+            int oldIdx = _context.CurrentStepIndex;
+            if (oldIdx >= 0)
+            {
+                if (oldIdx < newIndex)
+                {
+                    // 跳转到后方, 一般不会出现
+                    for (int i = oldIdx; i <= newIndex; i++)
+                    {
+                        var step = _context.Steps[i];
+                        step.RetryCount = 0;
+                    }
+
+                }
+                else if (newIndex < oldIdx)
+                {
+                    // 跳转到前方执行
+                    for (int i = newIndex; i < oldIdx; i++)
+                    {
+                        var step = _context.Steps[i];
+                        step.RetryCount = 0;
+                    }
+                }
+            }
+
+            _context.CurrentStepIndex = newIndex;
+            ExecuteCurrentStep();
+        }
+
+        private void HandleTimeout()
+        {
+            // 使用 try-catch 避免锁嵌套问题
+            bool shouldHandle = false;
+            lock (_lock)
+            {
+                shouldHandle = (_context != null && _context.IsRunning);
+            }
+
+            if (shouldHandle)
+            {
+                OnLog?.Invoke("操作超时", LogLevel.info);
+                HandleStepFailure("操作超时", false);
+            }
+        }
+
+
+    }
+}

+ 344 - 0
bird_tool/TestStepList.cs

@@ -0,0 +1,344 @@
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Windows.Forms;
+
+namespace Bird_tool
+{
+    public class TestStepList : UserControl
+    {
+        // 添加按钮事件委托
+        public delegate void StepButtonClickHandler(string key, TestType testType);
+        public event StepButtonClickHandler OnStepButtonClick;
+
+        // 步骤项数据结构
+        public class TestStep
+        {
+            public string Key { get; set; }
+            public string Name { get; set; }
+            public StepStatus Status { get; set; }
+            public DateTime? StartTime { get; set; }
+            public DateTime? EndTime { get; set; }
+            public Button BtnStartFrom { get; set; }
+            public Button BtnTestSingle { get; set; }
+        }
+
+
+        // 控件
+        private FlowLayoutPanel listContainer;
+        private List<TestStep> steps = new List<TestStep>();
+        private Dictionary<string, Panel> stepControls = new Dictionary<string, Panel>();
+        private bool isTestRunning = false; // 测试运行状态标志
+
+        // 状态颜色
+        private static readonly Color PendingColor = Color.LightGray;
+        private static readonly Color RunningColor = Color.DodgerBlue;
+        private static readonly Color SuccessColor = Color.LimeGreen;
+        private static readonly Color WaitingColor = Color.Gold;
+        private static readonly Color FailedColor = Color.OrangeRed;
+
+        // 图标
+        private static readonly Image PendingIcon = CreateStatusIcon(PendingColor);
+        private static readonly Image RunningIcon = CreateStatusIcon(RunningColor);
+        private static readonly Image SuccessIcon = CreateStatusIcon(SuccessColor);
+        private static readonly Image WaitingIcon = CreateStatusIcon(WaitingColor);
+        private static readonly Image FailedIcon = CreateStatusIcon(FailedColor);
+
+
+        public TestStepList()
+        {
+            SetupUI();
+        }
+
+        private void SetupUI()
+        {
+            this.Dock = DockStyle.Fill;
+            this.Padding = new Padding(5);
+            this.BackColor = Color.White;
+            this.BorderStyle = BorderStyle.FixedSingle;
+
+            // 主容器 - 垂直滚动列表
+            listContainer = new FlowLayoutPanel
+            {
+                Dock = DockStyle.Fill,
+                FlowDirection = FlowDirection.TopDown,
+                WrapContents = false,
+                AutoScroll = true,
+                BackColor = Color.White,
+                AutoScrollMargin = new Size(0, 5)
+            };
+
+            this.Controls.Add(listContainer);
+        }
+
+        // 添加测试步骤
+        public void AddStep(string key, string name)
+        {
+            var step = new TestStep
+            {
+                Key = key,
+                Name = name,
+                Status = StepStatus.NotRun
+            };
+
+            steps.Add(step);
+            AddStepToUI(step);
+        }
+
+        // 更新步骤状态
+        public void UpdateStepStatus(string key, string name, StepStatus status)
+        {
+            var step = steps.Find(s => s.Key == key);
+            if (step != null)
+            {
+                step.Status = status;
+
+                if (status == StepStatus.Running && !step.StartTime.HasValue)
+                {
+                    step.StartTime = DateTime.Now;
+                }
+                else if ((status == StepStatus.Success || status == StepStatus.Failed ||
+                         status == StepStatus.Waiting) && !step.EndTime.HasValue)
+                {
+                    step.EndTime = DateTime.Now;
+                }
+
+                UpdateStepUI(step);
+            }
+        }
+
+        // 设置测试运行状态
+        public void SetTestRunning(bool running)
+        {
+            if (this.InvokeRequired)
+            {
+                this.Invoke(new Action(() => SetTestRunning(running)));
+                return;
+            }
+
+            isTestRunning = running;
+            UpdateButtonsVisibility();
+        }
+
+        // 更新按钮可见性
+        private void UpdateButtonsVisibility()
+        {
+            foreach (var step in steps)
+            {
+                if (step.BtnStartFrom != null)
+                    step.BtnStartFrom.Visible = !isTestRunning;
+
+                if (step.BtnTestSingle != null)
+                    step.BtnTestSingle.Visible = !isTestRunning;
+            }
+        }
+
+
+        // 添加步骤到UI (简化布局)
+        private void AddStepToUI(TestStep step)
+        {
+            if (this.InvokeRequired)
+            {
+                this.Invoke(new Action(() => AddStepToUI(step)));
+                return;
+            }
+
+            // 主容器面板
+            var stepPanel = new Panel
+            {
+                Width = this.ClientSize.Width - 35,
+                Height = 70, // 增加高度以容纳按钮
+                Margin = new Padding(0, 0, 0, 5),
+                BackColor = Color.White,
+                BorderStyle = BorderStyle.FixedSingle,
+                Tag = step
+            };
+            // 添加"从此开始"按钮
+            var btnStartFrom = new Button
+            {
+                Text = "从此开始",
+                Tag = step.Key, // 存储步骤Key
+                Size = new Size(80, 25),
+                Location = new Point(40, 35),
+                FlatStyle = FlatStyle.Flat,
+                BackColor = Color.LightSkyBlue,
+                ForeColor = Color.White
+            };
+            btnStartFrom.Click += (s, e) =>
+            {
+                OnStepButtonClick?.Invoke(btnStartFrom.Tag.ToString(), TestType.StartFrom);
+            };
+            step.BtnStartFrom = btnStartFrom; // 保存按钮引用
+
+            // 添加"单项测试"按钮
+            var btnTestSingle = new Button
+            {
+                Text = "单项测试",
+                Tag = step.Key, // 存储步骤Key
+                Size = new Size(80, 25),
+                Location = new Point(130, 35),
+                FlatStyle = FlatStyle.Flat,
+                BackColor = Color.LightSeaGreen,
+                ForeColor = Color.White
+            };
+            btnTestSingle.Click += (s, e) =>
+            {
+                OnStepButtonClick?.Invoke(btnTestSingle.Tag.ToString(), TestType.TestSingle);
+            };
+            step.BtnTestSingle = btnTestSingle; // 保存按钮引用
+
+            // 初始可见性设置
+            btnStartFrom.Visible = !isTestRunning;
+            btnTestSingle.Visible = !isTestRunning;
+
+            // 状态图标
+            var iconBox = new PictureBox
+            {
+                Image = GetStatusIcon(step.Status),
+                SizeMode = PictureBoxSizeMode.CenterImage,
+                Size = new Size(30, 30),
+                Location = new Point(5, 2),
+                BackColor = Color.Transparent
+            };
+
+            // 步骤名称标签 - 占据剩余空间
+            var nameLabel = new Label
+            {
+                Text = step.Name,
+                Font = new Font("Microsoft YaHei UI", 10, FontStyle.Regular),
+                AutoSize = false,
+                Location = new Point(40, 0),
+                Size = new Size(stepPanel.Width - 45, 35),
+                TextAlign = ContentAlignment.MiddleLeft,
+                AutoEllipsis = true,
+                Padding = new Padding(5, 0, 5, 0)
+            };
+
+            // 悬停效果
+            stepPanel.MouseEnter += (s, e) =>
+            {
+                stepPanel.BackColor = Color.FromArgb(245, 245, 245);
+            };
+
+            stepPanel.MouseLeave += (s, e) =>
+            {
+                stepPanel.BackColor = Color.White;
+            };
+
+            // 添加控件
+            stepPanel.Controls.Add(iconBox);
+            stepPanel.Controls.Add(nameLabel);
+            stepPanel.Controls.Add(btnStartFrom);
+            stepPanel.Controls.Add(btnTestSingle);
+
+            listContainer.Controls.Add(stepPanel);
+            stepControls[step.Key] = stepPanel;
+        }
+
+        // 更新步骤UI
+        private void UpdateStepUI(TestStep step)
+        {
+            if (this.InvokeRequired)
+            {
+                this.Invoke(new Action(() => UpdateStepUI(step)));
+                return;
+            }
+
+            if (stepControls.TryGetValue(step.Key, out var stepPanel))
+            {
+                // 更新图标
+                var iconBox = stepPanel.Controls[0] as PictureBox;
+                if (iconBox != null)
+                    iconBox.Image = GetStatusIcon(step.Status);
+
+                // 更新名称标签
+                var nameLabel = stepPanel.Controls[1] as Label;
+                if (nameLabel != null)
+                {
+                    nameLabel.Text = step.Name;
+                }
+            }
+        }
+
+        // 获取状态图标
+        private Image GetStatusIcon(StepStatus status)
+        {
+            switch (status)
+            {
+                case StepStatus.NotRun: return PendingIcon;
+                case StepStatus.Waiting: return WaitingIcon;
+                case StepStatus.Running: return RunningIcon;
+                case StepStatus.Success: return SuccessIcon;
+                case StepStatus.Failed: return FailedIcon;
+                default: return PendingIcon;
+            }
+        }
+
+        // 创建状态图标
+        private static Image CreateStatusIcon(Color color)
+        {
+            var size = 20;
+            var bmp = new Bitmap(30, 30); // 固定尺寸30x30
+
+            using (var g = Graphics.FromImage(bmp))
+            {
+                g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
+                g.Clear(Color.Transparent);
+
+                // 绘制状态圆形
+                using (var brush = new SolidBrush(color))
+                {
+                    g.FillEllipse(brush, 5, 5, size, size);
+                }
+
+                // 添加白色边框
+                using (var pen = new Pen(Color.White, 1))
+                {
+                    g.DrawEllipse(pen, 5, 5, size, size);
+                }
+
+                // 添加状态符号
+                if (color == SuccessColor)
+                {
+                    using (var pen = new Pen(Color.White, 2))
+                    {
+                        g.DrawLine(pen, 8, 15, 13, 20);
+                        g.DrawLine(pen, 13, 20, 22, 10);
+                    }
+                }
+                else if (color == FailedColor)
+                {
+                    using (var pen = new Pen(Color.White, 2))
+                    {
+                        g.DrawLine(pen, 8, 8, 22, 22);
+                        g.DrawLine(pen, 22, 8, 8, 22);
+                    }
+                }
+                else if (color == WaitingColor)
+                {
+                    using (var pen = new Pen(Color.White, 2))
+                    {
+                        g.DrawLine(pen, 15, 8, 15, 18);
+                        g.FillEllipse(Brushes.White, 14, 20, 2, 2);
+                    }
+                }
+            }
+
+            return bmp;
+        }
+
+        // 重置所有步骤
+        public void Reset()
+        {
+            if (this.InvokeRequired)
+            {
+                this.Invoke(new Action(Reset));
+                return;
+            }
+
+            steps.Clear();
+            stepControls.Clear();
+            listContainer.Controls.Clear();
+        }
+    }
+}

+ 190 - 5
bird_tool/bird_tool.Designer.cs

@@ -1,4 +1,5 @@
 using System.Windows.Forms;
+using System.Drawing;
 
 namespace Bird_tool
 {
@@ -24,6 +25,9 @@ namespace Bird_tool
 
         #region Windows Form Designer generated code
 
+
+        // 显示日志窗口
+        
         /// <summary>
         /// Required method for Designer support - do not modify
         /// the contents of this method with the code editor.
@@ -34,7 +38,12 @@ namespace Bird_tool
             this.el_serialList = new System.Windows.Forms.ComboBox();
             this.el_btn_refresh = new System.Windows.Forms.Button();
             this.el_btn_conn = new System.Windows.Forms.Button();
+            this.el_btn_hw_test = new System.Windows.Forms.Button();
+            this.el_btn_config = new System.Windows.Forms.Button();
+            this.tableLayoutPanel = new System.Windows.Forms.TableLayoutPanel();
+            this.tableLayoutPanel.SuspendLayout();
             this.SuspendLayout();
+            this.topPanel = new System.Windows.Forms.Panel();
             // 
             // el_serialList
             // 
@@ -74,28 +83,204 @@ namespace Bird_tool
             this.el_btn_conn.Text = "连接串口";
             this.el_btn_conn.UseVisualStyleBackColor = false;
             this.el_btn_conn.Click += new System.EventHandler(this.Evt_conn_click);
+            // 
+            // el_btn_hw_test
+            // 
+            this.el_btn_hw_test.Anchor = System.Windows.Forms.AnchorStyles.None;
+            this.el_btn_hw_test.BackColor = System.Drawing.SystemColors.Control;
+            this.el_btn_hw_test.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Stretch;
+            this.el_btn_hw_test.Cursor = System.Windows.Forms.Cursors.No;
+            this.el_btn_hw_test.Enabled = false;
+            this.el_btn_hw_test.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
+            this.el_btn_hw_test.Font = new System.Drawing.Font("宋体", 24F);
+            this.el_btn_hw_test.ForeColor = System.Drawing.SystemColors.GrayText;
+            this.el_btn_hw_test.Location = new System.Drawing.Point(105, 208);
+            this.el_btn_hw_test.Margin = new System.Windows.Forms.Padding(2);
+            this.el_btn_hw_test.Name = "el_btn_hw_test";
+            this.el_btn_hw_test.Size = new System.Drawing.Size(270, 140);
+            this.el_btn_hw_test.TabIndex = 1;
+            this.el_btn_hw_test.Text = "硬件测试";
+            this.el_btn_hw_test.UseVisualStyleBackColor = false;
+            this.el_btn_hw_test.Click += new System.EventHandler(this.Evt_hw_click);
+            // 
+            // el_btn_config
+            // 
+            this.el_btn_config.Anchor = System.Windows.Forms.AnchorStyles.None;
+            this.el_btn_config.BackColor = System.Drawing.SystemColors.Control;
+            this.el_btn_config.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Stretch;
+            this.el_btn_config.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
+            this.el_btn_config.Font = new System.Drawing.Font("宋体", 24F);
+            this.el_btn_config.ForeColor = System.Drawing.SystemColors.ButtonShadow;
+            this.el_btn_config.Location = new System.Drawing.Point(586, 208);
+            this.el_btn_config.Margin = new System.Windows.Forms.Padding(2);
+            this.el_btn_config.Name = "el_btn_config";
+            this.el_btn_config.Size = new System.Drawing.Size(270, 140);
+            this.el_btn_config.TabIndex = 1;
+            this.el_btn_config.Text = "出货配置";
+            this.el_btn_config.UseVisualStyleBackColor = false;
+            this.el_btn_config.Click += new System.EventHandler(this.Evt_conn_click);
+            // 
+            // tableLayoutPanel
+            // 
+            this.tableLayoutPanel.ColumnCount = 3;
+            this.tableLayoutPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F));
+            this.tableLayoutPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
+            this.tableLayoutPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F));
+            this.tableLayoutPanel.Controls.Add(this.el_btn_hw_test, 0, 1);
+            this.tableLayoutPanel.Controls.Add(this.el_btn_config, 2, 1);
+            this.tableLayoutPanel.Dock = System.Windows.Forms.DockStyle.Fill;
+            this.tableLayoutPanel.Location = new System.Drawing.Point(0, 0);
+            this.tableLayoutPanel.Name = "tableLayoutPanel";
+            this.tableLayoutPanel.RowCount = 3;
+            this.tableLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F));
+            this.tableLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle());
+            this.tableLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F));
+            this.tableLayoutPanel.Size = new System.Drawing.Size(962, 557);
+            this.tableLayoutPanel.TabIndex = 2;
+            //Console.WriteLine("TableLayoutPanel visible: " + tableLayoutPanel.Visible);
+
+            
+            // 添加新的WiFi输入控件
+            this.lbl_ssid = new System.Windows.Forms.Label();
+            this.txt_ssid = new System.Windows.Forms.TextBox();
+            this.lbl_password = new System.Windows.Forms.Label();
+            this.txt_password = new System.Windows.Forms.TextBox();
+            this.panelWifi = new System.Windows.Forms.Panel();
+            this.btn_connect_wifi = new System.Windows.Forms.Button();
+
+
+
+            progressPanel = new ProgressPanel
+            {
+                Dock = DockStyle.Fill,
+                Visible = false,
+                TabIndex = 4
+            };
+            progressPanel.OnLogRequested += (s, ev) => ShowLogWindow();
+            progressPanel.OnStopRequested += (s, ev) => StopTest();
+            progressPanel.OnStartRequested += (s, ev) => start_query_test();
+            progressPanel.OnReturnRequested += (s, ev) => re_main();
+            progressPanel.StepList.OnStepButtonClick += setpStart;
+
+
+
+            // 添加WiFi配置面板
+            // panelWifi (单行布局)
+            this.panelWifi.BackColor = System.Drawing.SystemColors.ControlLight;
+            this.panelWifi.Controls.Add(this.lbl_ssid);
+            this.panelWifi.Controls.Add(this.txt_ssid);
+            this.panelWifi.Controls.Add(this.lbl_password);
+            this.panelWifi.Controls.Add(this.txt_password);
+            this.panelWifi.Controls.Add(this.btn_connect_wifi);
+            this.panelWifi.Location = new System.Drawing.Point(360, 10); // 移到串口控件右侧
+            this.panelWifi.Size = new System.Drawing.Size(550, 40); // 单行高度
+            this.panelWifi.TabIndex = 3;
+            
+            // lbl_ssid
+            this.lbl_ssid.AutoSize = true;
+            this.lbl_ssid.Font = new System.Drawing.Font("宋体", 9F);
+            this.lbl_ssid.Location = new System.Drawing.Point(5, 12);
+            this.lbl_ssid.Name = "lbl_ssid";
+            this.lbl_ssid.Size = new System.Drawing.Size(60, 12);
+            this.lbl_ssid.TabIndex = 0;
+            this.lbl_ssid.Text = "WiFi名称:";
+            
+            // txt_ssid
+            this.txt_ssid.Font = new System.Drawing.Font("宋体", 9F);
+            this.txt_ssid.Location = new System.Drawing.Point(65, 9);
+            this.txt_ssid.Name = "txt_ssid";
+            this.txt_ssid.Size = new System.Drawing.Size(120, 21);
+            this.txt_ssid.TabIndex = 1;
+            this.txt_ssid.Text = "hf_test";
+            
+            // lbl_password
+            this.lbl_password.AutoSize = true;
+            this.lbl_password.Font = new System.Drawing.Font("宋体", 9F);
+            this.lbl_password.Location = new System.Drawing.Point(190, 12);
+            this.lbl_password.Name = "lbl_password";
+            this.lbl_password.Size = new System.Drawing.Size(60, 12);
+            this.lbl_password.TabIndex = 2;
+            this.lbl_password.Text = "WiFi密码:";
+            
+            // txt_password
+            this.txt_password.Font = new System.Drawing.Font("宋体", 9F);
+            this.txt_password.Location = new System.Drawing.Point(250, 9);
+            this.txt_password.Name = "txt_password";
+            this.txt_password.Size = new System.Drawing.Size(120, 21);
+            this.txt_password.TabIndex = 3;
+            this.txt_password.Text = "12345678";
+            
+            // btn_connect_wifi
+            this.btn_connect_wifi.BackColor = System.Drawing.SystemColors.Control;
+            this.btn_connect_wifi.Font = new System.Drawing.Font("宋体", 9F);
+            this.btn_connect_wifi.Location = new System.Drawing.Point(380, 8);
+            this.btn_connect_wifi.Name = "btn_connect_wifi";
+            this.btn_connect_wifi.Size = new System.Drawing.Size(80, 24);
+            this.btn_connect_wifi.TabIndex = 4;
+            this.btn_connect_wifi.Text = "设置wifi";
+            this.btn_connect_wifi.UseVisualStyleBackColor = false;
+            this.btn_connect_wifi.Click += new System.EventHandler(this.Evt_set_wifi);
+
+            // 调整串口控件位置
+            this.el_serialList.Location = new System.Drawing.Point(13, 15);
+            this.el_btn_refresh.Location = new System.Drawing.Point(197, 14);
+            this.el_btn_conn.Location = new System.Drawing.Point(231, 14);
+
+            this.topPanel.Controls.Add(this.el_serialList);
+            this.topPanel.Controls.Add(this.el_btn_refresh);
+            this.topPanel.Controls.Add(this.el_btn_conn);
+            this.topPanel.Controls.Add(this.panelWifi);
+            this.topPanel.Dock = System.Windows.Forms.DockStyle.Top;
+            this.topPanel.Location = new System.Drawing.Point(0, 0);
+            this.topPanel.Name = "topPanel";
+            this.topPanel.Size = new System.Drawing.Size(962, 60); // 减小高度
+            this.topPanel.TabIndex = 4;
+
+            this.tableLayoutPanel.Dock = System.Windows.Forms.DockStyle.Fill;
+            this.tableLayoutPanel.Location = new System.Drawing.Point(0, 60); // 从顶部面板下方开始
+            this.tableLayoutPanel.Size = new System.Drawing.Size(962, 497); // 高度增加
+
             // 
             // bird_tool
             // 
             this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
             this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
             this.ClientSize = new System.Drawing.Size(962, 557);
-            this.Controls.Add(this.el_serialList);
-            this.Controls.Add(this.el_btn_refresh);
-            this.Controls.Add(this.el_btn_conn);
+            this.Controls.Add(this.tableLayoutPanel);
+            this.Controls.Add(progressPanel);
+            this.Controls.Add(this.topPanel); // 添加顶部面板
             this.KeyPreview = true;
             this.Name = "bird_tool";
             this.Text = "观鸟器测试工具";
             this.Load += new System.EventHandler(this.Tool_load);
+            this.tableLayoutPanel.ResumeLayout(false);
             this.ResumeLayout(false);
 
         }
 
         #endregion
-        
-        private System.Windows.Forms.Timer serialTimer;
+
+        //private System.Windows.Forms.Timer _timeoutTimer;
+        private System.Windows.Forms.Panel topPanel;
         private System.Windows.Forms.ComboBox el_serialList;
         private System.Windows.Forms.Button el_btn_refresh;
         private System.Windows.Forms.Button el_btn_conn;
+
+        // 居中组件
+        private System.Windows.Forms.TableLayoutPanel tableLayoutPanel;
+        // 选择项目按钮
+        private System.Windows.Forms.Button el_btn_hw_test;
+        private System.Windows.Forms.Button el_btn_config;
+        
+        // 测试进度面板
+        private ProgressPanel progressPanel;
+
+        // wifi信息面板
+        private System.Windows.Forms.Panel panelWifi;
+        private System.Windows.Forms.Label lbl_ssid;
+        private System.Windows.Forms.TextBox txt_ssid;
+        private System.Windows.Forms.Label lbl_password;
+        private System.Windows.Forms.TextBox txt_password;
+        System.Windows.Forms.Button btn_connect_wifi;
     }
 }

+ 1328 - 4
bird_tool/bird_tool.cs

@@ -19,9 +19,31 @@ using System.Text.RegularExpressions;
 
 namespace Bird_tool
 {
+    public enum LogLevel
+    {
+        debug,
+        info,
+        error
+    };
+
+    public enum TestMode
+    {
+        none,
+        hw_test,
+        config
+    }
+
+    public enum TestType
+    {
+        StartIndex,
+        StartFrom,
+        TestSingle
+    }
+
     public partial class bird_tool : Form
     {
-        private SerialPort _uart = new SerialPort();//定义串口
+        private SerialManager _serialManager = new SerialManager();//定义串口
+        private TestExecutor _testExecutor;
 
         private BindingList<string> _portList = new BindingList<string>();
         private string _lastSelectedPort = ""; // 保存上次选择的串口
@@ -30,6 +52,15 @@ namespace Bird_tool
         private bool flag_load_config = false;
         // 是否选择串口
         private bool flag_selected_serial = false;
+        private static LogLevel show_log_level = LogLevel.info;
+        private static StringBuilder logBuilder = new StringBuilder();
+
+
+        private bool SetWifi = false;
+        private string wifi_ssid = "";
+        private string wifi_password = "";
+        private string wifi_act = "hfyswj100";
+        private string sd_act = "hfyswj188";
 
 
         public bird_tool()
@@ -39,15 +70,131 @@ namespace Bird_tool
             //serialTimer.Enabled = true;
         }
 
-        private void Log(string message)
+        static public void Log(string message, LogLevel level = LogLevel.info)
         {
-            System.Diagnostics.Debug.WriteLine(message);
+            string logStr = "";
+            if (level == LogLevel.debug)
+            {
+                logStr = $"[Debug] {message}";
+
+            }
+            else if (level == LogLevel.info)
+            {
+                logStr = $"[Info] {message}";
+            }
+            else if (level == LogLevel.error)
+            {
+                logStr = $"[Error] {message}";
+            }
+            else
+            {
+                logStr = $"[Unknow] {message}";
+            }
+            // 调试控制台, 全部输出
+            System.Diagnostics.Debug.WriteLine(logStr);
+            // 将内容追加至本地log文件中
+            if (level < show_log_level)
+            {
+                return;
+            }
+            // 值得保存的调试控制台
+            Console.WriteLine(logStr);
+            logBuilder.AppendLine(logStr);
+        }
+
+        static public void Log_show(string message)
+        {
+            MessageBox.Show(message);
+        }
+
+        private void ShowLogWindow()
+        {
+            Form logForm = new Form
+            {
+                Text = "测试日志",
+                Size = new Size(700, 500),
+                StartPosition = FormStartPosition.CenterParent
+            };
+
+            // 日志文本框
+            TextBox logBox = new TextBox
+            {
+                Multiline = true,
+                Dock = DockStyle.Fill,
+                ScrollBars = ScrollBars.Both,
+                ReadOnly = true,
+                Font = new Font("Consolas", 10),
+                Text = logBuilder.ToString()
+            };
+
+            // 底部按钮面板
+            Panel buttonPanel = new Panel
+            {
+                Dock = DockStyle.Bottom,
+                Height = 40
+            };
+
+            // 复制按钮
+            Button copyButton = new Button
+            {
+                Text = "复制日志",
+                Size = new Size(100, 30),
+                Location = new Point(10, 5)
+            };
+            copyButton.Click += (s, e) =>
+            {
+                Clipboard.SetText(logBuilder.ToString());
+                MessageBox.Show("日志已复制到剪贴板");
+            };
+
+            // 保存按钮
+            Button saveButton = new Button
+            {
+                Text = "保存日志",
+                Size = new Size(100, 30),
+                Location = new Point(120, 5)
+            };
+            saveButton.Click += (s, e) =>
+            {
+                SaveFileDialog saveDialog = new SaveFileDialog
+                {
+                    Filter = "文本文件|*.txt",
+                    FileName = $"test_log_{DateTime.Now:yyyyMMdd_HHmmss}.txt"
+                };
+
+                if (saveDialog.ShowDialog() == DialogResult.OK)
+                {
+                    File.WriteAllText(saveDialog.FileName, logBuilder.ToString());
+                    MessageBox.Show("日志已保存");
+                }
+            };
+
+            // 关闭按钮
+            Button closeButton = new Button
+            {
+                Text = "关闭",
+                Size = new Size(100, 30),
+                Location = new Point(230, 5)
+            };
+            closeButton.Click += (s, e) => logForm.Close();
+
+            // 组装按钮面板
+            buttonPanel.Controls.Add(copyButton);
+            buttonPanel.Controls.Add(saveButton);
+            buttonPanel.Controls.Add(closeButton);
+
+            // 组装日志窗体
+            logForm.Controls.Add(logBox);
+            logForm.Controls.Add(buttonPanel);
+
+            logForm.ShowDialog();
         }
 
-        
         private void Tool_load(object sender, EventArgs e)
         {
+            _uart_ui_change(false);
             SerialPort_Load();
+            //_serialManager.OnLineReceived += _handle_received_line;
         }
 
         private void SerialPort_Load()
@@ -64,6 +211,18 @@ namespace Bird_tool
 
             // 初始加载串口列表
             RefreshPortList();
+            Log("inti ");
+            _testExecutor = new TestExecutor(_serialManager);
+
+            // 绑定事件处理
+            _testExecutor.OnLog += Log;
+
+            _testExecutor.OnPrompt += (question, onYes, onNo, waitTime) =>
+                this.Invoke((Action)(() => PromptUser(question, onYes, onNo, waitTime)));
+
+            _testExecutor.OnFailed += TestFailedhandle;
+            _testExecutor.OnSuccess += TestSuccesshandle;
+            _testExecutor.OnStepChanged += HandleStepChanged;
         }
 
         // 刷新串口列表并保持选择
@@ -136,6 +295,1171 @@ namespace Bird_tool
             }
             // 尝试连接串口
             Log($"连接串口{currentSelection}");
+
+
+
+            if (_serialManager.IsOpen())
+            {
+                // 调用停止函数
+                StopTest();
+                _serialManager.Disconnect();
+                System.Threading.Thread.Sleep(100);
+                _uart_ui_change(false);
+                return;
+            }
+            if (!_serialManager.Connect(currentSelection))
+            {
+                Log("串口开启失败");
+                _uart_ui_change(false);
+                return;
+            }
+            Log("串口开启成功");
+            // 设置窗口内容
+            _uart_ui_change(true);
+
+
+        }
+
+        private void Evt_hw_click(object sender, EventArgs e)
+        {
+            if (!_serialManager.IsOpen())
+            {
+                MessageBox.Show("请先打开串口");
+                _serialManager.Disconnect();
+                System.Threading.Thread.Sleep(100);
+                _uart_ui_change(false);
+                return;
+            }
+            checkWifi();
+            hw_test();
+        }
+
+        private void Evt_set_wifi(object sender, EventArgs e)
+        {
+            checkWifi();
         }
+
+        private void checkWifi ()
+        {
+            // 获取wifi值
+            string ssid = txt_ssid.Text;
+            string password = txt_password.Text;
+            Log($"ssid:{ssid} passwd:{password}");
+            // string.IsNullOrEmpty()
+            if (string.IsNullOrEmpty(ssid) || string.IsNullOrEmpty(password))
+            {
+                Log_show("请输入wifi信息");
+                SetWifi = false;
+                return;
+            }
+            Log_show($"将使用{ssid} 密码:{password}让设备连接WiFi");
+            wifi_ssid = ssid;
+            wifi_password = password;
+            SetWifi = true;
+        }
+
+        private void _SetTestButtonsEnabled(bool enabled)
+        {
+            el_btn_hw_test.Enabled = enabled;
+            el_btn_config.Enabled = enabled;
+
+            // 可选:添加视觉反馈
+            if (enabled)
+            {
+                el_btn_hw_test.BackColor = SystemColors.Control;  // 标准按钮背景色
+                el_btn_hw_test.ForeColor = SystemColors.ControlText;  // 标准文本色
+                el_btn_hw_test.FlatStyle = FlatStyle.Standard;  // 恢复原始样式
+                el_btn_hw_test.Cursor = Cursors.Default;
+
+                el_btn_config.BackColor = SystemColors.Control;
+                el_btn_config.ForeColor = SystemColors.ControlText;
+                el_btn_config.FlatStyle = FlatStyle.Standard;
+                el_btn_config.Cursor = Cursors.Default;
+
+            }
+            else
+            {
+                // 禁用状态:更直观的视觉反馈
+                el_btn_hw_test.BackColor = SystemColors.Control;  // 保持背景色
+                el_btn_hw_test.ForeColor = SystemColors.GrayText;  // 标准禁用文本色
+                el_btn_hw_test.FlatStyle = FlatStyle.Flat;  // 扁平化样式增强禁用感
+                el_btn_hw_test.Cursor = Cursors.No;
+
+                el_btn_config.BackColor = SystemColors.Control;
+                el_btn_config.ForeColor = SystemColors.GrayText;
+                el_btn_config.FlatStyle = FlatStyle.Flat;
+                el_btn_config.Cursor = Cursors.No;
+            }
+        }
+
+        private void AdjustTopPanelHeight(bool showFull)
+        {
+            if (showFull)
+            {
+                // 恢复完整高度
+                topPanel.Height = 60;
+                tableLayoutPanel.Location = new System.Drawing.Point(0, 60);
+                tableLayoutPanel.Size = new System.Drawing.Size(962, 497);
+            }
+            else
+            {
+                // 缩小顶部面板
+                topPanel.Height = 30;
+                tableLayoutPanel.Location = new System.Drawing.Point(0, 30);
+                tableLayoutPanel.Size = new System.Drawing.Size(962, 527);
+            }
+
+            // 重新布局确保控件正确显示
+            topPanel.PerformLayout();
+            this.Refresh();
+        }
+
+        private void _uart_ui_change(bool uart_flag)
+        {
+            if (uart_flag)
+            {
+                el_btn_conn.Text = "断开连接";
+            }
+            else
+            {
+                el_btn_conn.Text = "连接串口";
+            }
+            _SetTestButtonsEnabled(uart_flag);
+        }
+
+
+
+        private void PromptUser(string question, Action onYes, Action onNo, int waitTime = -1)
+        {
+            this.Invoke((Action)(() =>
+            {
+                // 创建自定义提示窗口
+                var prompt = new Form
+                {
+                    Width = 450,
+                    Height = 250,
+                    FormBorderStyle = FormBorderStyle.FixedDialog,
+                    Text = "操作确认",
+                    StartPosition = FormStartPosition.CenterParent,
+                    Font = new Font("Microsoft YaHei UI", 10),
+                    Icon = this.Icon,
+                    MinimizeBox = false,
+                    MaximizeBox = false
+                };
+
+                // 主面板 - 使用表格布局实现更好的响应式设计
+                var mainPanel = new TableLayoutPanel
+                {
+                    Dock = DockStyle.Fill,
+                    ColumnCount = 1,
+                    RowCount = 3,
+                    Padding = new Padding(15),
+                    RowStyles = {
+                new RowStyle(SizeType.Percent, 70F),
+                new RowStyle(SizeType.AutoSize),
+                new RowStyle(SizeType.AutoSize)
+            }
+                };
+
+                // 问题标签
+                var label = new Label
+                {
+                    Text = question,
+                    Dock = DockStyle.Fill,
+                    TextAlign = ContentAlignment.MiddleCenter,
+                    Font = new Font("Microsoft YaHei UI", 12, FontStyle.Regular),
+                    AutoSize = false
+                };
+
+                // 倒计时标签(初始隐藏)
+                var countdownLabel = new Label
+                {
+                    Text = "",
+                    Dock = DockStyle.Top,
+                    TextAlign = ContentAlignment.MiddleCenter,
+                    Font = new Font("Microsoft YaHei UI", 9, FontStyle.Italic),
+                    ForeColor = Color.Gray,
+                    Height = 30,
+                    Visible = false
+                };
+
+                // 按钮面板
+                var buttonPanel = new FlowLayoutPanel
+                {
+                    Dock = DockStyle.Fill,
+                    FlowDirection = FlowDirection.RightToLeft,
+                    Padding = new Padding(0, 10, 0, 0),
+                    AutoSize = true
+                };
+
+                // 否按钮
+                var noButton = new Button
+                {
+                    Text = "否",
+                    DialogResult = DialogResult.No,
+                    Size = new Size(100, 35),
+                    Font = new Font("Microsoft YaHei UI", 9.5F),
+                    BackColor = Color.White,
+                    FlatStyle = FlatStyle.Flat,
+                    Cursor = Cursors.Hand
+                };
+
+                // 是按钮
+                var yesButton = new Button
+                {
+                    Text = "是",
+                    DialogResult = DialogResult.Yes,
+                    Size = new Size(100, 35),
+                    Font = new Font("Microsoft YaHei UI", 9.5F, FontStyle.Bold),
+                    BackColor = Color.FromArgb(220, 235, 252),
+                    FlatStyle = FlatStyle.Flat,
+                    Cursor = Cursors.Hand
+                };
+
+                // 计时器(用于倒计时)
+                System.Windows.Forms.Timer timeoutTimer = null;
+                int remainingTime = waitTime;
+
+                if (waitTime > 0)
+                {
+                    // 显示倒计时标签
+                    countdownLabel.Visible = true;
+                    countdownLabel.Text = $"操作将在 {remainingTime} 秒后自动取消";
+
+                    // 设置按钮初始文本(包含倒计时)
+                    noButton.Text = $"否 ({remainingTime})";
+
+                    // 创建并启动计时器
+                    timeoutTimer = new System.Windows.Forms.Timer { Interval = 1000 };
+                    timeoutTimer.Tick += (s, e) => {
+                        remainingTime--;
+
+                        // 更新倒计时显示
+                        countdownLabel.Text = $"操作将在 {remainingTime} 秒后自动取消";
+                        noButton.Text = $"否 ({remainingTime})";
+
+                        // 倒计时结束
+                        if (remainingTime <= 0)
+                        {
+                            timeoutTimer.Stop();
+                            prompt.Close();
+                            onNo?.Invoke();
+                        }
+                    };
+                    timeoutTimer.Start();
+
+                    // 记录日志
+                    Log($"配置询问倒计时 {waitTime} 秒,超时则自动选择'否'");
+                }
+
+                // 是按钮点击事件
+                yesButton.Click += (sender, e) => {
+                    timeoutTimer?.Stop();
+                    prompt.Close();
+                    onYes?.Invoke();
+                };
+
+                // 否按钮点击事件
+                noButton.Click += (sender, e) => {
+                    timeoutTimer?.Stop();
+                    prompt.Close();
+                    onNo?.Invoke();
+                };
+
+                // 处理窗体关闭事件
+                prompt.FormClosed += (sender, e) => {
+                    timeoutTimer?.Stop();
+                    // 如果窗体关闭时倒计时仍在运行,执行No操作
+                    if (timeoutTimer != null && timeoutTimer.Enabled)
+                    {
+                        onNo?.Invoke();
+                    }
+                };
+
+                // 添加键盘快捷键
+                prompt.KeyPreview = true;
+                prompt.KeyDown += (sender, e) => {
+                    if (e.KeyCode == Keys.Enter)
+                    {
+                        yesButton.PerformClick();
+                        e.Handled = true;
+                    }
+                    else if (e.KeyCode == Keys.Escape)
+                    {
+                        noButton.PerformClick();
+                        e.Handled = true;
+                    }
+                };
+
+                // 组装按钮面板
+                buttonPanel.Controls.Add(noButton);
+                buttonPanel.Controls.Add(yesButton);
+
+                // 添加间距
+                buttonPanel.Controls.Add(new Panel { Width = 15 });
+
+                // 组装主面板
+                mainPanel.Controls.Add(label, 0, 0);
+                mainPanel.Controls.Add(countdownLabel, 0, 1);
+                mainPanel.Controls.Add(buttonPanel, 0, 2);
+
+                prompt.Controls.Add(mainPanel);
+
+                // 显示窗口
+                prompt.ShowDialog(this);
+            }));
+        }
+
+        // ui控件操作
+        private void ui_hide_start_panpel()
+        {
+            tableLayoutPanel.Visible = false;
+            panelWifi.Visible = false;
+        }
+        private void ui_show_start_panpel()
+        {
+            tableLayoutPanel.Visible = true;
+            panelWifi.Visible = true;
+            progressPanel.Visible = false;
+        }
+
+
+        private void ui_show_test_panpel()
+        {
+            ui_hide_start_panpel();
+            // 显示进度面板
+            progressPanel.Visible = true;
+            progressPanel.BringToFront();
+        }
+
+        private void re_main()
+        {
+            ui_show_start_panpel();
+            progressPanel.ResetPanel();
+        }
+
+        private void start_query_test()
+        {
+            Log($"start_query_test");
+            // 判断当前的id是
+            StartTest(TestType.StartIndex);
+        }
+
+        private void setpStart(string key, TestType testType)
+        {
+            StartTest(testType, key);
+        }
+
+        private void config_test()
+        {
+
+        }
+
+        // device mac
+        private void hw_test()
+        {
+            
+            var steps = CreateTestSteps();
+            if (!_testExecutor.InitTest(steps))
+            {
+                Log_show("无法创建测试表");
+                StopTest();
+                return;
+            }
+            ui_show_test_panpel();
+            progressPanel.ResetPanel();
+
+            var groups = _testExecutor.GetGroupInfos();
+            foreach (var group in groups)
+            {
+                if (group.ShowStep)
+                {
+                    progressPanel.AddTestStep(group.RowKey, group.GroupName);
+                }
+            }
+            progressPanel.Message = "点击开始测试按钮进行测试";
+            
+
+            Log($"[{DateTime.Now}] 开始硬件测试");
+        }
+
+        private void StartTest(TestType testType, string groupId = null)
+        {
+            if (!_testExecutor.isInit)
+            {
+                Log_show("测试引擎未初始化!");
+                return;
+            }
+            Log($"testType{testType} by {groupId}");
+            progressPanel.StartTimer();
+            // 初始化测试状态
+            progressPanel.ProgressValue = 0;
+            progressPanel.CurrentTest = "初始化测试环境";
+            progressPanel.Message = "准备开始硬件测试...";
+            progressPanel.startTest();
+            if (string.IsNullOrEmpty(groupId))
+            {
+                _testExecutor.StartTest();
+            }
+            else
+            {
+                if (testType == TestType.StartFrom)
+                {
+                    _testExecutor.StartTestFromGroup(groupId);
+                }
+                else if (testType == TestType.TestSingle)
+                {
+                    _testExecutor.StartTestGroup(groupId);
+                }
+                else
+                {
+                    _testExecutor.StartTest();
+                }
+            }
+            
+        }
+        private void StopTest()
+        {
+            // 停止测试
+            if (_testExecutor.isStart)
+            {
+                if (MessageBox.Show("确定要停止当前测试吗?", "确认停止",
+                MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
+                {
+                    // 设置停止标志
+
+                    // 更新UI
+                    progressPanel.ShowTestResult(false, "测试已取消", LoadCancelledImage());
+
+                    // 添加日志
+                    logBuilder.AppendLine($"[{DateTime.Now}] 测试已取消");
+                    _testExecutor.StopTest();
+                    progressPanel.Message = "用户手动停止测试";
+                    Log($"[{DateTime.Now}] 测试中止");
+                }
+               
+            }
+            else
+            {
+                Log($"[{DateTime.Now}] 测试中止");
+            }
+            
+        }
+
+        private void TestFailedhandle(TestStepConfig step, TestContext context)
+        {
+            string str = $"------ 测试失败-------";
+            _testExecutor.StopTest();
+            progressPanel.ShowTestResult(false, str);
+            Log(str);
+            Log($"------测试结束-------");
+        }
+
+        private void TestSuccesshandle(TestContext context)
+        {
+            string str = $"------ 测试完成, 请更换设备-------";
+            progressPanel.ShowTestResult(true, str);
+            Log(str);
+            Log($"------测试通过-------");
+            Log("------测试成功-------");
+
+        }
+
+
+        private List<TestStepConfig> CreateTestSteps()
+        {
+
+            return new List<TestStepConfig>
+            {
+                
+
+                
+                // 按钮长按功能测试
+                new TestStepConfig
+                {
+                    GroupId = "btn_long",
+                    GroupName = "按钮测试",
+                    Name = "长按测试1",
+                    Tips = "请长按电源按钮 5秒后再次长按 第一次",
+                    Command = "\r\n",
+                    SuccessPattern = "sys off",
+                    Timeout = 20000,
+                    DelayBefore = 200,
+                    MaxRetries = 3,
+                },
+                new TestStepConfig
+                {
+                    GroupId = "btn_long",
+                    Name = "开机测试1",
+                    Tips = "点击电源按钮 2秒后再次点击 第一次",
+                    Command = "\r\n",
+                    SuccessPattern = "OS start",
+                    Timeout = 20000,
+                    DelayBefore = 1000,
+                    MaxRetries = 3,
+                },
+
+                // 按钮双击功能测试
+                new TestStepConfig
+                {
+                    GroupId = "btn_long",
+                    Name = "双击测试1",
+                    Tips = "请双击电源按钮 第一次",
+                    Command = "\r\n",
+                    SuccessPattern = "_msg_dbclick_callback",
+                    Timeout = 20000,
+                    DelayBefore = 500,
+                    MaxRetries = 3,
+                },
+                new TestStepConfig
+                {
+                    GroupId = "btn_long",
+                    Name = "双击测试2",
+                    Tips = "请再次双击电源按钮 第二次",
+                    Command = "\r\n",
+                    SuccessPattern = "_msg_dbclick_callback",
+                    Timeout = 20000,
+                    DelayBefore = 1000,
+                    MaxRetries = 3,
+                },
+                new TestStepConfig
+                {
+                    GroupId = "btn_long",
+                    Name = "双击测试3",
+                    Tips = "请再次双击电源按钮 第三次",
+                    Command = "\r\n",
+                    SuccessPattern = "_msg_dbclick_callback",
+                    Timeout = 20000,
+                    DelayBefore = 1000,
+                    MaxRetries = 3,
+                },
+
+                // 红外传感器测试
+                new TestStepConfig
+                {
+                    GroupId = "pir_test",
+                    Name = "配置设备状态",
+                    Tips = "等待任务自动执行",
+                    Command = "AT+RBOT=0\r\n",
+                    SuccessPattern = "OS start",
+                    Timeout = 3000,
+                    DelayBefore = 1000,
+                    MaxRetries = 5,
+                },
+                new TestStepConfig
+                {
+                    GroupId = "pir_test",
+                    Name = "调试模式",
+                    Tips = "等待任务执行",
+                    Command = "AT+OSLP=0,1\r\n",
+                    SuccessPattern = "Wakeup, unsleep:0 debug:1",
+                    Timeout = 3000,
+                    DelayBefore = 1000,
+                    MaxRetries = 5,
+                },
+
+                new TestStepConfig
+                {
+                    GroupId = "pir_test",
+                    Name = "红外触发1",
+                    Tips = "请将手移动至红外传感器上 第一次",
+                    Command = "\r\n",
+                    SuccessPattern = "SOC: <=OK cmd:17",
+                    Timeout = 30000,
+                    DelayAfter = 100,
+                    MaxRetries = 3,
+                },
+                new TestStepConfig
+                {
+                    GroupId = "pir_test",
+                    Name = "等待红外录像完成 1",
+                    Tips = "等待红外任务结束 第一次",
+                    Command = "\r\n",
+                    SuccessPattern = "CAM: Close",
+                    Timeout = 30000,
+                    DelayAfter = 10,
+                    MaxRetries = 3,
+                },
+
+                new TestStepConfig
+                {
+                    GroupId = "pir_test",
+                    GroupName = "红外触发测试",
+                    Name = "红外触发2",
+                    Tips = "请将手移动至红外传感器上 第二次",
+                    Command = "\r\n",
+                    SuccessPattern = "cmd:17",
+                    Timeout = 30000,
+                    MaxRetries = 3,
+                },
+                new TestStepConfig
+                {
+                    GroupId = "btn_reset",
+                    GroupName = "重置按钮测试",
+                    Name = "重置按钮1",
+                    Tips = "请按下重置按钮 第一次",
+                    Command = "\r\n",
+                    SuccessPattern = "OS start",
+                    Timeout = 30000,
+                    MaxRetries = 3,
+                },
+            
+                // 重置按钮测试
+                new TestStepConfig
+                {
+                    GroupId = "btn_reset",
+                    Name = "重置按钮2",
+                    Tips = "请按下重置按钮 第二次",
+                    Command = "\r\n",
+                    SuccessPattern = "OS start",
+                    Timeout = 30000,
+                    MaxRetries = 3,
+                },
+                new TestStepConfig
+                {
+                    GroupId = "btn_reset",
+                    Name = "重置按钮3",
+                    Tips = "请按下重置按钮 第三次",
+                    Command = "\r\n",
+                    SuccessPattern = "OS start",
+                    Timeout = 30000,
+                    MaxRetries = 3,
+                },
+                new TestStepConfig
+                {
+                    GroupId = "btn_reset",
+                    Name = "重置按钮4",
+                    Tips = "请按下重置按钮 第四次",
+                    Command = "\r\n",
+                    SuccessPattern = "OS start",
+                    Timeout = 30000,
+                    MaxRetries = 3,
+                },
+                new TestStepConfig
+                {
+                    GroupId = "btn_reset",
+                    Name = "重置按钮5",
+                    Tips = "请按下重置按钮 第五次",
+                    Command = "\r\n",
+                    SuccessPattern = "OS start",
+                    Timeout = 30000,
+                    MaxRetries = 3,
+                },
+
+                // 其它命令性质测试
+                new TestStepConfig
+                {
+                    Name = "测试模式",
+                    Command = "AT+OSLP=1,0\r\n",
+                    Tips = "启用测试模式中",
+                    SuccessPattern = "Wakeup, unsleep:1 debug:0",
+                    FailurePattern = "ERROR|FAIL",
+                    Timeout = 4000,
+                    DelayBefore = 300,
+                },
+                new TestStepConfig
+                {
+                    Name = "获取MAC地址",
+                    Tips = "提取设备MAC地址",
+                    Command = "AT+MAC?\r\n",
+                    SuccessPattern = "MAC:",
+                    FailurePattern = "ERROR|FAIL",
+                    ExtractPattern = @"MAC:([0-9A-Fa-f]{2}(?::[0-9A-Fa-f]{2}){5})",
+                    VariableNames = { "mac" },
+                    PrivateCammand = true,
+                    Timeout = 3000,
+                    DelayBefore = 300,
+                    Validator = (response, ctx) =>
+                    {
+                        // 示例:检查MAC地址格式
+                        var mac = ctx.Variables["mac"];
+                        Log($"设备mac地址{mac}", LogLevel.info);
+                        return !IsValidMac(mac);
+                    }
+                },
+                // 版本号检查
+                new TestStepConfig
+                {
+                    Name = "获取版本信息",
+                    Tips = "设备版本检查地址",
+                    FailTips = "设备固件异常, 版本号:{{hwVersion}}{{hwTime}}",
+                    Command = "AT+SEND=1, AT+HWCUST?\r\n",
+                    SuccessPattern = @"\+HWCUST:",
+                    FailurePattern = "ERROR|FAIL",
+                    ExtractPattern = @"\+HWCUST:\s*\d+,\s*""([^""]+)"",\s*""([^""]+)""",
+                    VariableNames = { "hwVersion" , "hwTime"},
+                    Timeout = 6000,
+                    DelayBefore = 300,
+                    MaxRetries = 10,
+                    Validator = (response, ctx) =>
+                    {
+                        // 示例:检查MAC地址格式
+                        var hwVersion = ctx.Variables["hwVersion"];
+                        var hwTime = ctx.Variables["hwTime"];
+                        Log($"设备版本号 ${hwVersion}", LogLevel.info);
+                        Log($"固件时间 ${hwTime}", LogLevel.info);
+                        // 判断版本号是否为
+                        return false;
+                    }
+                },
+                // sd卡测试
+                new TestStepConfig
+                {
+                    GroupId = "sdLoad",
+                    Key = "sdLoad",
+                    Name = "加载SD卡",
+                    Command = "AT+SEND=1, AT+SDHCI=1\r\n",
+                    Tips = "请确保SD卡插入",
+                    SuccessPattern = "<=OK cmd:20",
+                    FailurePattern = "ERROR|FAIL",
+                    Timeout = 4000,
+                    DelayBefore = 300,
+                    FailContinue = true,
+                },
+                new TestStepConfig
+                {
+                    GroupId = "sdLoad",
+                    GroupName = "SD卡检查",
+                    Name = "检查是否加载成功",
+                    Command = "AT+SEND=1,AT+CAMPARA?\r\n",
+                    Tips = "检查sd卡加载状态中",
+                    SuccessPattern = "/mnt/sdcard",
+                    RetryFromKey = "sdLoad",
+                    Timeout = 3000,
+                    DelayBefore = 1000,
+                    MaxJump = 5,
+                },
+
+                new TestStepConfig
+                {
+                    GroupId = "Audio_test",
+                    Name = "配置视频格式",
+                    Tips = "配置视频格式...",
+                    Command = "AT+SEND=1, AT+CAMPARA=5\\,\"h264\"\r\n",
+                    SuccessPattern = "<=OK cmd:20",
+                    Timeout = 6000,
+                    DelayBefore = 1000,
+                    MaxRetries = 2,
+                },
+                new TestStepConfig
+                {
+                    GroupId = "Audio_test",
+                    Key = "audioTest",
+                    Name = "配置音频格式",
+                    Tips = "音频格式配置",
+                    Command = "AT+SEND=1, AT+CAMPARA=20\\,3\r\n",
+                    SuccessPattern = "<=OK cmd:20",
+                    Timeout = 6000,
+                    DelayBefore = 1000,
+                    MaxRetries = 2,
+                },
+                new TestStepConfig
+                {
+                    GroupId = "Audio_test",
+
+                    Name = "录制视频",
+                    Command = "AT+SEND=1,AT+CAMRV=0\\\\,\"test\"\r\n",
+                    Tips = "视频录制中 请对着麦克风持续讲话",
+                    FailTips = "无法录制视频, 检查设备镜头",
+                    SuccessPattern = @"\+CAMRV: OK",
+                    FailurePattern = "ERROR|FAIL",
+                    ExtractPattern = $@"""(.*?.pcma)""",
+                    VariableNames = { "audioFile" },
+                    Timeout = 20000,
+                    DelayBefore = 1000,
+                    OnSuccess = ctx =>
+                    {
+                        Log($"视频文件{ctx.Variables["audioFile"]}");
+                        // 保存文件路径到上下文
+                    }
+                },
+                new TestStepConfig
+                {
+                    GroupId = "Audio_test",
+                    GroupName = "音频测试",
+                    Name = "播放音频",
+                    Command = "AT+SEND=1, AT+HWAUDIO=5\\,\"{{audioFile}}\"\\,800\r\n",
+                    //CommandGenerator = ctx =>
+                    //    $"AT+SEND=1, AT+HWAUDIO=5\\,\"{ctx.Variables["audioFile"]}\"\\,800\r\n",
+                    RequiresUserPrompt = true,
+                    PromptQuestion = "是否听到音频?",
+                    Timeout = 10000,
+                    DelayBefore = 500,
+                    MaxRetries = 2,
+                    RetryFromKey = "audioTest",
+                    OnFailure = ctx =>
+                    {
+                        // 失败时重新录制
+                    }
+                },
+                
+                // 补光灯测试
+                new TestStepConfig
+                {
+                    GroupId = "Led_test",
+                    Name = "打开LED",
+                    Command = "AT+SEND=1,AT+FACTORY=4\\,\"echo 1 > /sys/class/gpio/gpio107/value\"\r\n",
+                    Tips = "补光灯测试中",
+                    SuccessPattern = @"\+FACTORY: 4",
+                    RequiresUserPrompt = true,
+                    PromptQuestion = "LED补光灯是否亮起?",
+                    Timeout = 5000,
+                    DelayBefore = 200,
+                    MaxRetries = 3,
+                },
+                new TestStepConfig
+                {
+                    GroupId = "Led_test",
+                    GroupName = "补光灯测试",
+                    Name = "关闭LED补光灯",
+                    Tips = "补光灯测试中",
+                    Command = "AT+SEND=1,AT+FACTORY=4\\,\"echo 0 > /sys/class/gpio/gpio107/value\"\r\n",
+                    SuccessText = "+FACTORY: 4",
+                    RequiresUserPrompt = true,
+                    PromptQuestion = "LED补光灯是否熄灭?",
+                    Timeout = 5000,
+                    DelayBefore = 200,
+                    MaxRetries = 3,
+                },
+                new TestStepConfig
+                {
+                    GroupId = "red_test",
+                    GroupName = "红外灯测试",
+                    Name = "打开红外灯",
+                    Command = "AT+SEND=1,AT+FACTORY=4\\,\"echo 1 > /sys/class/gpio/gpio108/value\"\r\n",
+                    SuccessText = "+FACTORY: 4",
+                    RequiresUserPrompt = true,
+                    PromptQuestion = "红外补光灯是否亮起?",
+                    Timeout = 5000,
+                    DelayBefore = 200,
+                    MaxRetries = 3,
+                },
+                new TestStepConfig
+                {
+                    GroupId = "red_test",
+                    Name = "关闭红外灯",
+                    Command = "AT+SEND=1,AT+FACTORY=4\\,\"echo 0 > /sys/class/gpio/gpio108/value\"\r\n",
+                    SuccessText = "+FACTORY: 4",
+                    RequiresUserPrompt = true,
+                    PromptQuestion = "红外补光灯是否熄灭?",
+                    Timeout = 5000,
+                    DelayBefore = 200,
+                    MaxRetries = 3,
+                },
+                // 设置基础信息
+                new TestStepConfig
+                {
+                    GroupId = "wifi_test",
+                    ShowStep = false,
+                    Name = "配置sd卡激活码",
+                    Tips = "自动配置测试信息",
+                    Action = ActionMode.SetVal,
+                    VariableNames = { "sdCode" },
+                    VariableValues = { sd_act },
+                    PrivateCammand = true,
+                },
+                // wifi测试
+                new TestStepConfig
+                {
+                    GroupId = "wifi_test",
+                    GroupName = "WiFi测试",
+                    ShowStep = false,
+                    Name = "配置WiFi",
+                    Tips = "自动配置wifi信息",
+                    Action = ActionMode.SetVal,
+                    VariableNames = { "ssid", "password" },
+                    VariableValues = { wifi_ssid, wifi_password },
+                },
+                new TestStepConfig
+                {
+                    GroupId = "wifi_test",
+                    ShowStep = false,
+                    Name = "配置设备激活码",
+                    Tips = "自动配置wifi信息",
+                    Action = ActionMode.SetVal,
+                    VariableNames = { "activeCode" },
+                    VariableValues = { wifi_act },
+                },
+                new TestStepConfig
+                {
+                    GroupId = "wifi_test",
+                    Name = "WiFi测试",
+                    Tips = "正在连接WiFi中",
+                    Command = "AT+WIFI=\"{{ssid}}\",\"{{password}}\",\"CN\",\"{{activeCode}}\"\r\n",
+                    SuccessText = "WiFi_STAT: Connected",
+                    Timeout = 10000,
+                    DelayBefore = 500,
+                    MaxRetries = 5,
+                },
+                // 上传测试
+
+
+
+            };
+        }
+        
+        private bool IsValidMac(string mac)
+        {
+            // 实现MAC地址验证逻辑
+            return !string.IsNullOrEmpty(mac) && mac.Length == 17;
+        }
+
+        
+        private void HandleStepChanged(TestStepConfig step, TestContext context, bool isStarting)
+        {
+            this.Invoke((Action)(() =>
+            {
+                progressPanel.StateText = GetProgressState(step);
+                if (step.ShowStep)
+                {
+                    Log($"step.ShowStep {step.Name}");
+                    progressPanel.UpdateTestStepStatus(step.GroupId, step.Name, step.stepStatus);
+                }
+                if (isStarting)
+                {
+                    // 步骤开始提示
+                    ShowStepStart(step, context);
+                }
+                else
+                {
+                    // 步骤结束提示
+                    ShowStepCompletion(step, context);
+                }
+            }));
+        }
+
+        private void ShowStepStart(TestStepConfig step, TestContext context)
+        {
+            // 更新UI显示当前步骤
+            //el_currentStepLabel.Text = $"{context.CurrentStepIndex + 1}/{context.Steps.Count} {step.Name}";
+            //el_stepDetailTextBox.Text = "正在执行...";
+
+            // 显示步骤提示
+            var tooltip = new ToolTip();
+            tooltip.ToolTipTitle = "步骤开始";
+            //tooltip.SetToolTip(el_currentStepLabel, step.Name);
+
+            // 在日志中显示
+            Log($"\n===== 开始步骤: {step.Name} =====");
+            Log($"描述: {GetStepDescription(step)}");
+            progressPanel.ProgressValue = 0;
+            
+            if (step.JumpCount > 0)
+            {
+                progressPanel.CurrentTest = $"{step.Name} ({step.JumpCount}/{step.MaxJump})";
+            }
+            else if (step.RetryCount > 0)
+            {
+                progressPanel.CurrentTest = $"{step.Name} ({step.RetryCount}/{step.MaxRetries})";
+            }
+            else
+            {
+                progressPanel.CurrentTest = $"{step.Name}";
+            }
+
+            if (string.IsNullOrEmpty(step.Tips))
+            {
+                progressPanel.Message = GetStepDescription(step);
+            }
+            else
+            {
+                progressPanel.Message = step.Tips;
+            }
+        }
+
+        private void ShowStepCompletion(TestStepConfig step, TestContext context)
+        {
+            string statusMessage = "➖ 步骤结束";
+
+            if (step.JumpCount > 0)
+            {
+                progressPanel.CurrentTest = $"{step.Name} ({step.JumpCount}/{step.MaxJump}) {statusMessage}";
+            }
+            else if (step.RetryCount > 0)
+            {
+                progressPanel.CurrentTest = $"{step.Name} ({step.RetryCount}/{step.MaxRetries}) {statusMessage}";
+            }
+            else
+            {
+                progressPanel.CurrentTest = $"{step.Name} {statusMessage}";
+            }
+            if (!string.IsNullOrEmpty(step.Tips))
+            {
+                progressPanel.Message = GetStepDescription(step);
+            }
+            else
+            {
+                progressPanel.Message = step.Tips;
+            }
+
+
+        }
+    
+        private string GetProgressState(TestStepConfig step)
+        {
+            string str = "";
+            switch (step.stepStatus)
+            {
+                
+                case StepStatus.Failed:
+                    str = "执行失败";
+                    break;
+                case StepStatus.Running:
+                    str = "等待执行";
+                    break;
+                case StepStatus.Waiting:
+                    str = "等待结果";
+                    break;
+                case StepStatus.Success:
+                    str = "执行完成";
+                    break;
+                case StepStatus.NotRun:
+                default:
+                    str = "待执行";
+                    break;
+            }
+            return str;
+
+        }
+        private string GetStepDescription(TestStepConfig step)
+        {
+            var sb = new StringBuilder();
+            if (string.IsNullOrEmpty(step.Tips))
+            {
+                return step.Tips;
+            }
+            if (!string.IsNullOrEmpty(step.Command))
+            {
+                if (!step.PrivateCammand)
+                {
+                    sb.AppendLine($"命令: {Truncate(step.Command, 50)}");
+                }
+            }
+
+            if (!string.IsNullOrEmpty(step.SuccessPattern))
+            {
+                sb.AppendLine($"期待匹配: {step.SuccessPattern}");
+            }
+
+            if (!string.IsNullOrEmpty(step.FailurePattern))
+            {
+                sb.AppendLine($"失败匹配: {step.FailurePattern}");
+            }
+
+            if (step.RequiresUserPrompt)
+                {
+                sb.AppendLine($"用户确认: {step.PromptQuestion}");
+            }
+
+            return sb.ToString();
+        }
+
+        private string Truncate(string value, int maxLength)
+        {
+            return value.Length <= maxLength ? value : value.Substring(0, maxLength) + "...";
+        }
+
+
+        // 加载成功图片
+        private Image LoadSuccessImage()
+        {
+            try
+            {
+                // 从资源加载
+                // return Properties.Resources.SuccessIcon;
+
+                // 动态生成成功图标
+                return CreateStatusImage(true);
+            }
+            catch
+            {
+                return null;
+            }
+        }
+
+        // 加载失败图片
+        private Image LoadFailureImage()
+        {
+            try
+            {
+                // 从资源加载
+                // return Properties.Resources.FailureIcon;
+
+                // 动态生成失败图标
+                return CreateStatusImage(false);
+            }
+            catch
+            {
+                return null;
+            }
+        }
+
+        // 加载取消图片
+        private Image LoadCancelledImage()
+        {
+            try
+            {
+                // 从资源加载
+                // return Properties.Resources.CancelledIcon;
+
+                // 动态生成取消图标
+                Bitmap bmp = new Bitmap(100, 100);
+                using (Graphics g = Graphics.FromImage(bmp))
+                {
+                    g.Clear(Color.White);
+                    g.DrawEllipse(new Pen(Color.Orange, 3), 10, 10, 80, 80);
+
+                    // 感叹号
+                    g.FillRectangle(Brushes.Orange, 45, 20, 10, 40);
+                    g.FillRectangle(Brushes.Orange, 45, 70, 10, 10);
+                }
+                return bmp;
+            }
+            catch
+            {
+                return null;
+            }
+        }
+
+        // 创建状态图标
+        private Image CreateStatusImage(bool isSuccess)
+        {
+            Bitmap bmp = new Bitmap(100, 100);
+            using (Graphics g = Graphics.FromImage(bmp))
+            {
+                g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
+                g.Clear(Color.White);
+
+                if (isSuccess)
+                {
+                    // 绿色对勾
+                    using (Pen pen = new Pen(Color.Green, 4))
+                    {
+                        g.DrawEllipse(pen, 10, 10, 80, 80);
+                        g.DrawLine(pen, 30, 50, 45, 70);
+                        g.DrawLine(pen, 45, 70, 75, 30);
+                    }
+                }
+                else
+                {
+                    // 红色叉号
+                    using (Pen pen = new Pen(Color.Red, 4))
+                    {
+                        g.DrawEllipse(pen, 10, 10, 80, 80);
+                        g.DrawLine(pen, 30, 30, 70, 70);
+                        g.DrawLine(pen, 70, 30, 30, 70);
+                    }
+                }
+            }
+            return bmp;
+        }
+
+
+
+
     }
+
 }
+
+    
+

+ 8 - 0
bird_tool/bird_tool.csproj

@@ -44,11 +44,19 @@
       <DependentUpon>bird_tool.cs</DependentUpon>
     </Compile>
     <Compile Include="Program.cs" />
+    <Compile Include="ProgressPanel.cs">
+      <SubType>UserControl</SubType>
+    </Compile>
     <Compile Include="Properties\Resources.Designer.cs">
       <AutoGen>True</AutoGen>
       <DesignTime>True</DesignTime>
       <DependentUpon>Resources.resx</DependentUpon>
     </Compile>
+    <Compile Include="SerialManager.cs" />
+    <Compile Include="TestEngine.cs" />
+    <Compile Include="TestStepList.cs">
+      <SubType>UserControl</SubType>
+    </Compile>
   </ItemGroup>
   <ItemGroup>
     <Reference Include="Microsoft.CSharp" />