Parcourir la source

feat: excel加载功能开发
1. excel加载功能制作
2. 配置功能优化

kindring il y a 6 jours
Parent
commit
ca5b06e971

+ 39 - 5
bird_tool/Config.cs

@@ -8,23 +8,57 @@ using System.IO;
 
 namespace Bird_tool
 {
+    public class ExcelColumnConfig
+    {
+        // Excel中的原始列名
+        public string OriginalName { get; set; }
+
+        // 映射后的别名(程序中使用)
+        public string Alias { get; set; }
+
+        // 是否必需
+        public bool IsRequired { get; set; }
+
+        // 默认值(当列为空时使用)
+        public string DefaultValue { get; set; }
+    }
     public class AppConfig
     {
         public bool enable_local_server { get; set; } = true;
         public string api_address { get; set; } = "hofuniot.cn";
         public int web_port = 8080;
-        public string wifi_ssid { get; set; } = "hf_test";
-        public string wifi_passwd { get; set; } = "12345678";
+        public string wifi_ssid { get; set; } = "h_y_ap";
+        public string wifi_passwd { get; set; } = "51388513";
         public string pir { get; set; } = "90";
+        // 是否加载excel文件
+        public bool enable_excel_load { get; set; } = true;
+        
+        public string excel_path { get; set; } = "test.xlsx";
+        // excel 表格中主键的列名
+        public string excel_primary_key { get; set; } = "uuid";
+        // todo excel 中需要的列名 用于表示excel中列名对应的别名, 例如 key 对应authorKey. 用于在其它地方进行功能映射, 并且允许某些值为空, 配置默认值
+        public List<ExcelColumnConfig> excel_columns { get; set; } = new List<ExcelColumnConfig>
+        {
+            new ExcelColumnConfig { OriginalName = "uuid", Alias = "uuid", IsRequired = true },
+            new ExcelColumnConfig { OriginalName = "key", Alias = "authkey", IsRequired = true },
+            // 添加其他列配置示例
+            // new ExcelColumnConfig { OriginalName = "device_id", Alias = "deviceId", IsRequired = true, DefaultValue = "DEFAULT_001" }
+        };
     }
 
     public static class ConfigManager
     {
-        private static readonly string ConfigPath =
-            Path.Combine(Environment.CurrentDirectory, "config.json");
+        private static string ConfigName = "config.json";
+        private static string ConfigPath =
+            Path.Combine(Environment.CurrentDirectory, ConfigName);
 
-        public static AppConfig LoadConfig()
+        public static string getConfigPath()
+        {
+            return ConfigPath;
+        }
+        public static AppConfig LoadConfig(string ConfigName = "config.json")
         {
+            ConfigPath = Path.Combine(Environment.CurrentDirectory, ConfigName);
             if (!File.Exists(ConfigPath))
             {
                 var defaultConfig = new AppConfig();

+ 373 - 105
bird_tool/ExcelLoad.cs

@@ -6,100 +6,345 @@ using System.Linq;
 using OfficeOpenXml;
 using Newtonsoft.Json;
 
-namespace digital
+namespace Bird_tool
 {
+
     public class ExcelDataManager
     {
+        public delegate void LogHandler(string message, LogLevel level);
+        public delegate void LogShowHandler(string message);
+        public event LogHandler OnLog;
+        public event LogShowHandler OnLogShow;
+
+        static ExcelDataManager()
+        {
+            // 设置 EPPlus 许可证
+            ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
+        }
+
         private readonly string _excelPath;
         private readonly string _progressPath;
         private readonly List<Dictionary<string, string>> _rows = new List<Dictionary<string, string>>();
-        private readonly HashSet<string> _usedKeys = new HashSet<string>();
-        private int _currentIndex = 0;
+        private readonly Dictionary<string, UsageRecord> _usageRecords = new Dictionary<string, UsageRecord>(StringComparer.OrdinalIgnoreCase);
+        private int _lastUsedIndex = 0;
         private string[] _headers;
 
-        // 主键列名(用于进度跟踪)
-        private string _primaryKeyColumn = "账号";
+        private readonly AppConfig _config;
 
-        public ExcelDataManager(string excelPath, string progressPath = "progress.json", string primaryKeyColumn = "账号")
+        // 验证状态和错误信息
+        public bool IsValid { get; private set; }
+        public List<string> ValidationErrors { get; } = new List<string>();
+
+        // 主键列名
+        private string _primaryKeyColumn = "uuid";
+
+        public ExcelDataManager(AppConfig config, string progressPath = "progress.json")
         {
-            _excelPath = excelPath;
+            _config = config;
+            _excelPath = config.excel_path;
             _progressPath = progressPath;
-            _primaryKeyColumn = primaryKeyColumn;
-            LoadExcelData();
-            LoadProgress();
+            if (_config.enable_excel_load)
+            {
+                LoadExcelData();
+                ValidateExcelData();
+                if (IsValid)
+                {
+                    LoadProgress();
+                }
+                else
+                {
+                    LogValidationErrors();
+                }
+            }
         }
 
         private void LoadExcelData()
         {
-            FileInfo file = new FileInfo(_excelPath);
-            using (ExcelPackage package = new ExcelPackage(file))
+            if (!File.Exists(_excelPath))
+            {
+                ValidationErrors.Add($"Excel文件不存在: {_excelPath}");
+                return;
+            }
+            try
+            {
+                FileInfo file = new FileInfo(_excelPath);
+
+                using (ExcelPackage package = new ExcelPackage(file))
+                {
+                    if (package.Workbook.Worksheets.Count == 0)
+                    {
+                        ValidationErrors.Add("Excel文件中没有工作表");
+                        return;
+                    }
+                    ExcelWorksheet worksheet = package.Workbook.Worksheets[0];
+
+                    int rowCount = worksheet.Dimension?.Rows ?? 0;
+                    int colCount = worksheet.Dimension?.Columns ?? 0;
+
+                    if (rowCount < 2 || colCount == 0)
+                    {
+                        ValidationErrors.Add("Excel文件没有数据");
+                        return;
+                    }
+
+                    // 读取标题行
+                    _headers = new string[colCount];
+                    for (int col = 1; col <= colCount; col++)
+                    {
+                        _headers[col - 1] = worksheet.Cells[1, col].Text?.Trim() ?? $"Column{col}";
+                    }
+
+                    // 读取数据行
+                    for (int row = 2; row <= rowCount; row++)
+                    {
+                        var rowData = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
+                        for (int col = 1; col <= colCount; col++)
+                        {
+                            string header = _headers[col - 1];
+                            string cellValue = worksheet.Cells[row, col].Text?.Trim() ?? "";
+                            rowData[header] = cellValue;
+                        }
+
+                        // 只添加有主键的行
+                        if (rowData.ContainsKey(_primaryKeyColumn) &&
+                            !string.IsNullOrWhiteSpace(rowData[_primaryKeyColumn]))
+                        {
+                            _rows.Add(rowData);
+                        }
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                ValidationErrors.Add($"加载Excel数据失败: {ex.Message}");
+            }
+        }
+        private void ValidateExcelData()
+        {
+            ValidationErrors.Clear();
+
+            // 1. 检查主键配置是否存在
+            if (string.IsNullOrWhiteSpace(_config.excel_primary_key))
+            {
+                ValidationErrors.Add("配置错误: 未指定主键列名");
+            }
+
+            // 2. 检查列映射配置
+            if (_config.excel_columns == null || _config.excel_columns.Count == 0)
+            {
+                ValidationErrors.Add("配置错误: 未配置列映射");
+            }
+            else
+            {
+                // 检查主键列是否在映射中
+                bool primaryKeyFound = false;
+                foreach (var colConfig in _config.excel_columns)
+                {
+                    if (colConfig.Alias.Equals(_config.excel_primary_key, StringComparison.OrdinalIgnoreCase))
+                    {
+                        primaryKeyFound = true;
+                        break;
+                    }
+                }
+
+                if (!primaryKeyFound)
+                {
+                    ValidationErrors.Add($"配置错误: 主键列 '{_config.excel_primary_key}' 未在列映射中配置");
+                }
+            }
+
+            // 3. 检查Excel列是否存在
+            if (_rows.Count == 0)
             {
-                ExcelWorksheet worksheet = package.Workbook.Worksheets[0];
-                int rowCount = worksheet.Dimension.Rows;
-                int colCount = worksheet.Dimension.Columns;
+                ValidationErrors.Add("Excel中没有有效数据行");
+                IsValid = false;
+                return;
+            }
 
-                // 读取标题行
-                _headers = new string[colCount];
-                for (int col = 1; col <= colCount; col++)
+            // 检查必需的原始列是否存在
+            foreach (var colConfig in _config.excel_columns)
+            {
+                if (colConfig.IsRequired && !_headers.Contains(colConfig.OriginalName, StringComparer.OrdinalIgnoreCase))
                 {
-                    _headers[col - 1] = worksheet.Cells[1, col].Text.Trim();
+                    ValidationErrors.Add($"Excel中缺少必需的列: {colConfig.OriginalName}");
                 }
+            }
 
-                // 读取数据行
-                for (int row = 2; row <= rowCount; row++)
+            // 4. 检查主键列是否存在
+            bool primaryKeyColumnExists = false;
+            foreach (var header in _headers)
+            {
+                if (header.Equals(_config.excel_primary_key, StringComparison.OrdinalIgnoreCase))
                 {
-                    var rowData = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
-                    for (int col = 1; col <= colCount; col++)
+                    primaryKeyColumnExists = true;
+                    break;
+                }
+            }
+
+            if (!primaryKeyColumnExists)
+            {
+                ValidationErrors.Add($"Excel中缺少主键列: {_config.excel_primary_key}");
+            }
+
+            // 5. 检查主键值是否重复
+            var keyValues = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
+            for (int i = 0; i < _rows.Count; i++)
+            {
+                var row = _rows[i];
+                if (row.TryGetValue(_config.excel_primary_key, out string keyValue) && !string.IsNullOrWhiteSpace(keyValue))
+                {
+                    if (keyValues.Contains(keyValue))
+                    {
+                        ValidationErrors.Add($"主键值重复: {keyValue} (行 {i + 2})");
+                    }
+                    else
                     {
-                        string header = _headers[col - 1];
-                        string cellValue = worksheet.Cells[row, col].Text.Trim();
-                        rowData[header] = cellValue;
+                        keyValues.Add(keyValue);
                     }
+                }
+                else
+                {
+                    ValidationErrors.Add($"主键值为空 (行 {i + 2})");
+                }
+            }
+
+            // 6. 检查必需列的值
+            foreach (var colConfig in _config.excel_columns.Where(c => c.IsRequired))
+            {
+                for (int i = 0; i < _rows.Count; i++)
+                {
+                    var row = _rows[i];
+                    if (!row.TryGetValue(colConfig.OriginalName, out string value) || string.IsNullOrWhiteSpace(value))
+                    {
+                        ValidationErrors.Add($"必需列 '{colConfig.OriginalName}' 值为空 (行 {i + 2})");
+                    }
+                }
+            }
+
+            IsValid = ValidationErrors.Count == 0;
+        }
+
+        // 检查主键是否已使用
+        public bool IsKeyUsed(string keyValue)
+        {
+            return !string.IsNullOrWhiteSpace(keyValue) &&
+                   _usageRecords.ContainsKey(keyValue);
+        }
+
+        // 获取使用记录
+        public UsageRecord GetUsageRecord(string keyValue)
+        {
+            if (string.IsNullOrWhiteSpace(keyValue)) return null;
+            return _usageRecords.TryGetValue(keyValue, out var record) ? record : null;
+        }
 
-                    // 只添加有主键的行
-                    if (rowData.ContainsKey(_primaryKeyColumn))
+        public (List<string> VariableNames, List<string> VariableValues) GetVariableListFromRow(
+            Dictionary<string, string> row)
+        {
+            var variableNames = new List<string>();
+            var variableValues = new List<string>();
+
+            if (row == null || _config?.excel_columns == null)
+                return (variableNames, variableValues);
+
+            // 按照配置顺序处理列
+            foreach (var colConfig in _config.excel_columns)
+            {
+                // 使用别名作为变量名
+                string varName = colConfig.Alias;
+
+                // 尝试获取值
+                string value = null;
+                if (row.TryGetValue(colConfig.OriginalName, out string rawValue))
+                {
+                    value = rawValue;
+                }
+
+                // 处理空值和默认值
+                if (string.IsNullOrWhiteSpace(value))
+                {
+                    if (!string.IsNullOrWhiteSpace(colConfig.DefaultValue))
+                    {
+                        value = colConfig.DefaultValue;
+                    }
+                    else if (colConfig.IsRequired)
                     {
-                        _rows.Add(rowData);
+                        // 必需列但值为空,记录警告
+                        OnLog?.Invoke($"必需列 '{colConfig.OriginalName}' 值为空,使用空字符串", LogLevel.error);
+                        value = "";
                     }
                 }
+
+                // 添加到结果
+                variableNames.Add(varName);
+                variableValues.Add(value ?? "");
             }
+
+            return (variableNames, variableValues);
         }
 
+
         // 顺序获取下一行数据
-        public Dictionary<string, string> GetNextRow()
+        // 顺序获取下一行数据(从未使用的位置开始)
+        public Dictionary<string, string> GetNextRow(bool markAsUsed = true, string usedBy = null, string macAddress = null)
         {
-            while (_currentIndex < _rows.Count)
+            // 从未使用的位置开始查找
+            int startIndex = _lastUsedIndex;
+            int index = startIndex;
+            int checkedCount = 0;
+            if (!IsValid)
+            {
+                throw new InvalidOperationException("Excel数据未通过验证,无法获取数据");
+            }
+
+            while (checkedCount < _rows.Count)
             {
-                var row = _rows[_currentIndex++];
+                // 循环遍历所有行
+                if (index >= _rows.Count) index = 0;
+
+                var row = _rows[index];
                 string key = GetPrimaryKey(row);
 
-                if (!_usedKeys.Contains(key))
+                // 找到未使用的行
+                if (!IsKeyUsed(key))
                 {
-                    _usedKeys.Add(key);
+                    _lastUsedIndex = index + 1; // 更新最后使用位置
+
+                    if (markAsUsed)
+                    {
+                        MarkKeyAsUsed(key, usedBy, macAddress);
+                    }
                     return new Dictionary<string, string>(row);
                 }
+
+                // 移动到下一行
+                index++;
+                checkedCount++;
             }
+
+            // 所有行都已使用
             return null;
         }
 
         // 根据主键值获取整行数据
-        public Dictionary<string, string> GetRowByKey(string keyValue)
+        public Dictionary<string, string> GetRowByKey(string keyValue, bool markAsUsed = false, string usedBy = null, string macAddress = null)
         {
             if (string.IsNullOrWhiteSpace(keyValue)) return null;
 
             var row = _rows.FirstOrDefault(r =>
-                GetPrimaryKey(r).Equals(keyValue, StringComparison.OrdinalIgnoreCase) &&
-                !_usedKeys.Contains(GetPrimaryKey(r)));
+                GetPrimaryKey(r).Equals(keyValue, StringComparison.OrdinalIgnoreCase));
 
             if (row != null)
             {
-                _usedKeys.Add(GetPrimaryKey(row));
+                if (markAsUsed)
+                {
+                    MarkKeyAsUsed(keyValue, usedBy, macAddress);
+                }
                 return new Dictionary<string, string>(row);
             }
             return null;
         }
 
-        // 根据任意列查询获取整行数据
         public Dictionary<string, string> FindRowByColumnValue(string columnName, string columnValue)
         {
             if (string.IsNullOrWhiteSpace(columnName)) return null;
@@ -107,26 +352,50 @@ namespace digital
 
             var row = _rows.FirstOrDefault(r =>
                 r.ContainsKey(columnName) &&
-                r[columnName].Equals(columnValue, StringComparison.OrdinalIgnoreCase) &&
-                !_usedKeys.Contains(GetPrimaryKey(r)));
+                r[columnName].Equals(columnValue, StringComparison.OrdinalIgnoreCase));
 
             if (row != null)
             {
-                _usedKeys.Add(GetPrimaryKey(row));
                 return new Dictionary<string, string>(row);
             }
             return null;
         }
 
+        // 标记主键为已使用(带额外信息)
+        public void MarkKeyAsUsed(string keyValue, string usedBy = null, string macAddress = null)
+        {
+            if (!string.IsNullOrWhiteSpace(keyValue))
+            {
+                // 如果已经标记过,更新记录
+                if (_usageRecords.TryGetValue(keyValue, out var record))
+                {
+                    record.LastUsedTime = DateTime.Now;
+                    if (!string.IsNullOrWhiteSpace(usedBy)) record.UsedBy = usedBy;
+                    if (!string.IsNullOrWhiteSpace(macAddress)) record.MacAddress = macAddress;
+                }
+                else
+                {
+                    // 创建新记录
+                    _usageRecords[keyValue] = new UsageRecord
+                    {
+                        KeyValue = keyValue,
+                        FirstUsedTime = DateTime.Now,
+                        LastUsedTime = DateTime.Now,
+                        UsedBy = usedBy,
+                        MacAddress = macAddress
+                    };
+                }
+            }
+        }
+
         // 根据主键值获取指定列的值
-        public string GetColumnValue(string keyValue, string columnName)
+        public string GetColumnValue(string keyValue, string columnName, bool markAsUsed = false, string usedBy = null, string macAddress = null)
         {
-            var row = GetRowByKey(keyValue);
+            var row = GetRowByKey(keyValue, markAsUsed, usedBy, macAddress);
             return row != null && row.TryGetValue(columnName, out string value) ? value : null;
         }
 
-        // 根据任意列查询获取指定列的值
-        public string GetColumnValueByQuery(string queryColumn, string queryValue, string resultColumn)
+        public string GetColumnValueByQuery(string queryColumn, string queryValue, string resultColumn, bool markAsUsed = false)
         {
             var row = FindRowByColumnValue(queryColumn, queryValue);
             return row != null && row.TryGetValue(resultColumn, out string value) ? value : null;
@@ -138,16 +407,35 @@ namespace digital
             return row.TryGetValue(_primaryKeyColumn, out string value) ? value : "";
         }
 
+        // 根据主键获取变量列表
+        public (List<string> VariableNames, List<string> VariableValues) GetVariableListByKey(
+            string keyValue, bool markAsUsed = false, string usedBy = null, string macAddress = null)
+        {
+            var row = GetRowByKey(keyValue, markAsUsed, usedBy, macAddress);
+            return GetVariableListFromRow(row);
+        }
+
+        // 获取下一行的变量列表
+        public (List<string> VariableNames, List<string> VariableValues) GetNextVariableList(
+            bool markAsUsed = true, string usedBy = null, string macAddress = null)
+        {
+            var row = GetNextRow(markAsUsed, usedBy, macAddress);
+            return GetVariableListFromRow(row);
+        }
+
+
+
+
         // 保存进度
         public void SaveProgress()
         {
             var progress = new ProgressData
             {
-                CurrentIndex = _currentIndex,
-                UsedKeys = _usedKeys.ToList()
+                LastUsedIndex = _lastUsedIndex,
+                UsageRecords = _usageRecords.Values.ToList()
             };
 
-            File.WriteAllText(_progressPath, JsonConvert.SerializeObject(progress));
+            File.WriteAllText(_progressPath, JsonConvert.SerializeObject(progress, Formatting.Indented));
         }
 
         // 加载进度
@@ -160,11 +448,18 @@ namespace digital
                     var json = File.ReadAllText(_progressPath);
                     var progress = JsonConvert.DeserializeObject<ProgressData>(json);
 
-                    _currentIndex = progress.CurrentIndex;
-                    _usedKeys.UnionWith(progress.UsedKeys);
+                    _lastUsedIndex = progress.LastUsedIndex;
+
+                    // 加载使用记录
+                    foreach (var record in progress.UsageRecords)
+                    {
+                        _usageRecords[record.KeyValue] = record;
+                    }
                 }
-                catch
+                catch (Exception ex)
                 {
+                    Console.WriteLine($"加载进度失败: {ex.Message}");
+                    // 进度文件损坏时重置
                     ResetProgress();
                 }
             }
@@ -173,77 +468,50 @@ namespace digital
         // 重置进度
         public void ResetProgress()
         {
-            _currentIndex = 0;
-            _usedKeys.Clear();
+            _lastUsedIndex = 0;
+            _usageRecords.Clear();
             SaveProgress();
         }
 
-        // 获取所有列名
-        public IEnumerable<string> GetColumnNames()
-        {
-            return _headers;
-        }
-        // 查找所有匹配的行(不标记为已使用)
-        public List<Dictionary<string, string>> FindAllRowsByColumnValue(string columnName, string columnValue)
-        {
-            return _rows.Where(r =>
-                r.ContainsKey(columnName) &&
-                r[columnName].Equals(columnValue, StringComparison.OrdinalIgnoreCase))
-                .Select(r => new Dictionary<string, string>(r))
-                .ToList();
-        }
-
-        // 多条件查询
-        public Dictionary<string, string> FindRowByMultipleConditions(
-            params (string column, string value)[] conditions)
+        private void LogValidationErrors()
         {
-            foreach (var row in _rows)
+            foreach (var error in ValidationErrors)
             {
-                bool match = true;
-                string key = GetPrimaryKey(row);
-
-                if (_usedKeys.Contains(key)) continue;
-
-                foreach (var condition in conditions)
-                {
-                    if (!row.TryGetValue(condition.column, out string value) ||
-                        !value.Equals(condition.value, StringComparison.OrdinalIgnoreCase))
-                    {
-                        match = false;
-                        break;
-                    }
-                }
-
-                if (match)
-                {
-                    _usedKeys.Add(key);
-                    return new Dictionary<string, string>(row);
-                }
+                OnLog?.Invoke(error, LogLevel.error);
             }
-            return null;
+            OnLogShow?.Invoke("Excel数据验证失败,详情查看日志");
         }
 
-        // 获取当前进度状态
-        public (int total, int used, int remaining) GetProgressStatus()
+        // 
+
+        // 获取所有使用记录
+        public List<UsageRecord> GetAllUsageRecords()
         {
-            int total = _rows.Count;
-            int used = _usedKeys.Count;
-            return (total, used, total - used);
+            return _usageRecords.Values.ToList();
         }
 
-        // 标记主键为已使用
-        public void MarkKeyAsUsed(string keyValue)
+        // 使用记录类
+        public class UsageRecord
         {
-            if (!string.IsNullOrWhiteSpace(keyValue))
+            public string KeyValue { get; set; }
+            public DateTime FirstUsedTime { get; set; }
+            public DateTime LastUsedTime { get; set; }
+            public string UsedBy { get; set; }
+            public string MacAddress { get; set; }
+
+            // 格式化显示
+            public override string ToString()
             {
-                _usedKeys.Add(keyValue);
+                return $"{KeyValue}: 首次使用 {FirstUsedTime:yyyy-MM-dd HH:mm}, 最后使用 {LastUsedTime:yyyy-MM-dd HH:mm}, " +
+                       $"使用者: {UsedBy ?? "未知"}, MAC: {MacAddress ?? "未知"}";
             }
         }
 
+        // 进度数据类
         private class ProgressData
         {
-            public int CurrentIndex { get; set; }
-            public List<string> UsedKeys { get; set; }
+            public int LastUsedIndex { get; set; }
+            public List<UsageRecord> UsageRecords { get; set; }
         }
     }
 }

+ 1 - 1
bird_tool/ProgressPanel.cs

@@ -271,7 +271,7 @@ namespace Bird_tool
         private void StartButton_Click(object sender, EventArgs e)
         {
             OnStartRequested?.Invoke(this, EventArgs.Empty);
-            startTest();
+            
         }
         public void startTest()
         {

+ 2 - 2
bird_tool/Properties/Resources.Designer.cs

@@ -8,7 +8,7 @@
 // </auto-generated>
 //------------------------------------------------------------------------------
 
-namespace digital.Properties {
+namespace Bird_tool.Properties {
     using System;
     
     
@@ -39,7 +39,7 @@ namespace digital.Properties {
         internal static global::System.Resources.ResourceManager ResourceManager {
             get {
                 if (object.ReferenceEquals(resourceMan, null)) {
-                    global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("digital.Properties.Resources", typeof(Resources).Assembly);
+                    global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Bird_tool.Properties.Resources", typeof(Resources).Assembly);
                     resourceMan = temp;
                 }
                 return resourceMan;

+ 10 - 1
bird_tool/SerialManager.cs

@@ -8,6 +8,11 @@ namespace Bird_tool
 {
     class SerialManager
     {
+        public delegate void LogHandler(string message, LogLevel level);
+        public delegate void LogShowHandler(string message);
+        public event LogHandler OnLog;
+        public event LogShowHandler OnLogShow;
+
         public delegate void DisconnectHandler(bool isError);
         public delegate void ConnectHandler();
 
@@ -40,7 +45,11 @@ namespace Bird_tool
 
         public bool Connect(string portName, int baudRate = 115200)
         {
-            if (_uart.IsOpen) return true;
+            if (_uart.IsOpen)
+            {
+                OnLog?.Invoke("串口已经打开", LogLevel.debug);
+                return true;
+            }
 
             _uart.PortName = portName;
             _uart.BaudRate = baudRate;

+ 130 - 61
bird_tool/TestEngine.cs

@@ -166,9 +166,9 @@ namespace Bird_tool
         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> VariableNames { get; set; } = new List<string>();
         // 变量值列表
-        public List<string> VariableValues { get; } = new List<string>();
+        public List<string> VariableValues { get; set; } = new List<string>();
 
         public int Timeout { get; set; } = 3000;
         // 是否为隐私命令, 隐私命令不显示对应发送命令
@@ -309,6 +309,8 @@ namespace Bird_tool
 
         public delegate void LogHandler(string message, LogLevel level);
         public delegate void LogShowHandler(string message);
+        public event LogHandler OnLog;
+        public event LogShowHandler OnLogShow;
 
         public delegate void PromptHandler(string question, Action onYes, Action onNo, int waitTime = 60);
         public delegate bool StepValidator(string response, TestContext context);
@@ -321,8 +323,7 @@ namespace Bird_tool
 
 
 
-        public event LogHandler OnLog;
-        public event LogShowHandler OnLogShow;
+        
         public event PromptHandler OnPrompt;
         public event StepChangedHandler OnStepChanged;
         public event TestFailHandler OnFailed;
@@ -341,6 +342,7 @@ namespace Bird_tool
         private Task _queueProcessor;
 
         public bool isStart { get; private set; } = false;
+        private bool _isStopping = false; // 添加停止状态标志
         public bool isInit { get; private set; } = false;
         public bool enableMakeResult { get; private set; } = false;
 
@@ -400,13 +402,53 @@ namespace Bird_tool
             }
         }
 
+        private void cleanTest(bool testEnd = false)
+        {
+
+            // 清理资源
+            if (_serialManager != null)
+            {
+                _serialManager.Disconnect();
+                _serialManager.OnLineReceived -= HandleResponse;
+            }
+            OnLog?.Invoke($"_serialManager clean", LogLevel.debug);
+
+            // 重置上下文但保留配置
+            if (_context != null)
+            {
+                _context.IsRunning = false;
+                _context.CurrentStepIndex = -1;
+                _context.Report = new TestReport();
+            }
+
+            OnLog?.Invoke($"_cts?.Dispose(); ", LogLevel.debug);
+            // 重新创建资源
+            _cts?.Dispose();
+            _cts = new CancellationTokenSource();
+
+            if (testEnd && isStart)
+            {
+                OnTestEnd?.Invoke(false);
+            }
+            isStart = false;
+            _isStopping = false;
+            OnLog?.Invoke($"重新启动队列处理器", LogLevel.info);
+            // 重新启动队列处理器
+            StartQueueProcessor();
+        }
+
         public async Task StopTestAsync(bool waitForCompletion = false, bool testEnd = false)
         {
+            if (_isStopping) return;
+            _isStopping = true;
             try
             {
                 // 取消所有任务
                 _cts.Cancel();
                 // 清空任务队列
+                // 清空任务队列
+                while (_taskQueue.Reader.TryRead(out _)) { }
+
                 OnLog?.Invoke($"_cts.Cancel();", LogLevel.debug);
                 // 等待队列处理完成(如果需要)
                 if (waitForCompletion && _queueProcessor != null && !_queueProcessor.IsCompleted)
@@ -414,46 +456,30 @@ namespace Bird_tool
                     OnLog?.Invoke($"_queueProcessor", LogLevel.debug);
                     await _queueProcessor;
                 }
+                else if (_queueProcessor != null)
+                {
+                    // 添加超时等待
+                    var timeoutTask = Task.Delay(5000);
+                    var completedTask = await Task.WhenAny(_queueProcessor, timeoutTask);
+                    if (completedTask == timeoutTask)
+                    {
+                        OnLog?.Invoke("队列处理器停止超时,强制终止", LogLevel.error);
+                    }
+                }
                 OnLog?.Invoke($"_queueProcessor ok", LogLevel.debug);
+                cleanTest();
+
             }
             catch (Exception ex)
             {
-                OnLog?.Invoke($"停止测试时出错: {ex.Message}", LogLevel.error);
+                OnLog?.Invoke($"停止测试时出错: {ex.Message} {ex.StackTrace}", LogLevel.error);
             }
             finally
             {
-                // 清理资源
-                if (_serialManager != null)
-                {
-                    _serialManager.Disconnect();
-                    _serialManager.OnLineReceived -= HandleResponse;
-                }
-                OnLog?.Invoke($"_serialManager clean", LogLevel.debug);
-
-                // 重置上下文但保留配置
-                if (_context != null)
+                if (isStart)
                 {
-                    _context.IsRunning = false;
-                    _context.CurrentStepIndex = -1;
-                    _context.Report = new TestReport();
+                    cleanTest();
                 }
-
-                OnLog?.Invoke($"_cts?.Dispose(); ", LogLevel.debug);
-                // 重新创建资源
-                _cts?.Dispose();
-                _cts = new CancellationTokenSource();
-                
-                
-                
-                if (testEnd && isStart)
-                {
-                    OnTestEnd?.Invoke(false);
-                }
-                isStart = false;
-                OnLog?.Invoke($"资源重置", LogLevel.info);
-                // 重新启动队列处理器
-                StartQueueProcessor();
-
             }
         }
 
@@ -465,19 +491,22 @@ namespace Bird_tool
                 OnLogShow?.Invoke("串口打开失败");
                 return false;
             }
+
             _serialManager.OnLineReceived += HandleResponse;
 
             lock (_lock)
             {
-
-
                 ResetAllSteps();
                 if (steps == null || steps.Count == 0)
                 {
                     OnLog?.Invoke("步骤列表为空,无法启动测试", LogLevel.error);
                     return false;
                 }
-
+                if (!isInit || isStart || _isStopping)
+                {
+                    OnLog?.Invoke("测试尚未初始化或仍在停止中", LogLevel.error);
+                    return false;
+                }
                 EnqueueTask(async ct =>
                 {
                     if (isStart)
@@ -486,10 +515,12 @@ namespace Bird_tool
                         StopTestAsync(true, false);
                     }
                     
+
                     if (_context != null && _context.IsRunning)
                     {
                         await StopTestAsync(true, false);
                     }
+                    OnLog?.Invoke("开始测试 StartTestWithSteps", LogLevel.info);
                     // 初始化上下文
                     _context = new TestContext
                     {
@@ -517,7 +548,7 @@ namespace Bird_tool
                 step.JumpCount = 0;
             }
         }
-        public bool StartTest(bool mkResult = true)
+        public bool StartTest(bool mkResult = true, string csvPath = "TestReport/report.csv")
         {
             lock (_lock)
             {
@@ -526,6 +557,7 @@ namespace Bird_tool
                     return false;
                 }
                 enableMakeResult = mkResult;
+                _csvReportPath = csvPath;
                 return StartTestWithSteps(_step_map); ;
             }
         }
@@ -724,7 +756,7 @@ namespace Bird_tool
         // 字符串模板替换方法
         public string ReplaceTemplateVariables(string input, Dictionary<string, string> variables)
         {
-            if (string.IsNullOrWhiteSpace(input))
+            if (string.IsNullOrWhiteSpace(input) || string.IsNullOrEmpty(input))
                 return input;
 
             // 创建大小写不敏感的合并字典
@@ -803,7 +835,7 @@ namespace Bird_tool
             value = null;
             return false;
         }
-
+        // 实际执行对应步骤任务
         private async Task ExecuteCurrentStepAsync(CancellationToken ct)
         {
             if (_context == null || _context.CurrentStep == null)
@@ -812,9 +844,12 @@ namespace Bird_tool
                 return;
             }
             var step = _context.CurrentStep;
-            RecordStepResult(step, TestStatus.Running, "步骤开始执行");
+            if (step.JumpCount == 0 && step.RetryCount == 0)
+            {
+                RecordStepResult(step, TestStatus.Running, "步骤开始执行");
+                OnLog?.Invoke($"开始步骤: {step.Name}", LogLevel.info);
+            }
             // 记录步骤开始
-            OnLog?.Invoke($"开始步骤: {step.Name}", LogLevel.info);
             step.stepStatus = StepStatus.Running;
             OnStepChanged?.Invoke(step, _context, true);
             if (step.DelayBefore > 0)
@@ -865,18 +900,7 @@ namespace Bird_tool
                 // 新增模板变量替换
                 command = ReplaceTemplateVariables(command, _context.Variables);
 
-                // 发送命令
-                if (!string.IsNullOrEmpty(command))
-                {
-                    OnLog?.Invoke($"发送命令: {command.Trim()}", step.PrivateCammand? LogLevel.debug: 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;
 
@@ -889,17 +913,48 @@ namespace Bird_tool
                         string.IsNullOrEmpty(step.SuccessPattern) &&
                         string.IsNullOrEmpty(step.SuccessText))
                     {
+                        // 不用匹配回答则直接发送命令, 随后直接询问操作员
+                        if (!string.IsNullOrEmpty(command))
+                        {
+                            OnLog?.Invoke($"发送命令: {command.Trim()}", step.PrivateCammand ? LogLevel.debug : LogLevel.info);
+                            _serialManager.SendCommand(command);
+                        }
                         await PromptQuestionAsync(step, linkedCts.Token);
                     }
                     else
                     {
-                        OnLog?.Invoke($"等待响应(命令已发送)", LogLevel.debug);
-                        var response = await WaitForResponseAsync(step, linkedCts.Token);
+                        // 发送命令后等待响应
+                        Task<string> responseTask = null;
+                        if (step.DelayAfter > 0)
+                        {
+                            if (!string.IsNullOrEmpty(command))
+                            {
+                                OnLog?.Invoke($"发送命令: {command.Trim()}", step.PrivateCammand ? LogLevel.debug : LogLevel.info);
+                                _serialManager.SendCommand(command);
+                            }
+                            OnLog?.Invoke($"等待 {step.DelayBefore}ms (发送后延迟)", LogLevel.debug);
+                            await Task.Delay(step.DelayAfter);
+                            OnLog?.Invoke($"等待响应(命令已发送)", LogLevel.debug);
+                            responseTask = WaitForResponseAsync(step, linkedCts.Token);
+                        }
+                        else
+                        {
+                            OnLog?.Invoke($"创建接收处理任务", LogLevel.debug);
+                            responseTask = WaitForResponseAsync(step, linkedCts.Token);
+                            if (!string.IsNullOrEmpty(command))
+                            {
+                                OnLog?.Invoke($"发送命令: {command.Trim()}", step.PrivateCammand ? LogLevel.debug : LogLevel.info);
+                                _serialManager.SendCommand(command);
+                            }
+                        }
+
+                        var response = await responseTask;
                     }
+
                 }
                 catch (OperationCanceledException) when (timeoutCts.IsCancellationRequested)
                 {
-                    HandleStepFailure("操作超时", false);
+                    HandleStepFailure("等待任务执行超时", false);
                 }
             }
 
@@ -914,8 +969,14 @@ namespace Bird_tool
                     OnLog?.Invoke($"<忽略响应> 步骤已变更", LogLevel.debug);
                     return;
                 }
+                string successText = "";
+                if (!string.IsNullOrEmpty(step.SuccessText))
+                {
+                    // 尝试将变量替换
+                    successText = ReplaceTemplateVariables(step.SuccessText, _context.Variables);
+                }
                 if ((!string.IsNullOrEmpty(step.SuccessPattern) && Regex.IsMatch(data, step.SuccessPattern)) ||
-                (!string.IsNullOrEmpty(step.SuccessText) && data.Contains(step.SuccessText)))
+                (!string.IsNullOrEmpty(successText) && data.Contains(successText)))
                 {
                     if (!string.IsNullOrEmpty(step.ExtractPattern) && !ExtractVariables(data, step))
                     {
@@ -1060,12 +1121,14 @@ namespace Bird_tool
             HandleTestEnd(false);
             OnFailed?.Invoke(_context.CurrentStep, _context);
             StopTestAsync(false, true);
+            OnTestEnd?.Invoke(false);
         }
         private void HandleTestSuccess()
         {
             HandleTestEnd(true);
             OnSuccess?.Invoke(_context);
             StopTestAsync(false, true);
+            OnTestEnd?.Invoke(false);
         }
 
         // 替换现有的 PromptQuestion 方法
@@ -1110,7 +1173,6 @@ namespace Bird_tool
             _timeoutTimer = null;
             var step = _context.CurrentStep;
             step.stepStatus = StepStatus.Success;
-            RecordStepResult(step, TestStatus.Passed, message);
             // 捕获设备信息
             if (step.IsDeviceInfoItem)
             {
@@ -1502,12 +1564,19 @@ namespace Bird_tool
             });
         }
 
-
         // 新增:追加CSV记录
         private void AppendReportToCsv(TestReport report)
         {
             lock (_csvLock)
             {
+                string directoryPath = Path.GetDirectoryName(_csvReportPath);
+
+                // 如果文件夹不存在,则创建
+                if (!Directory.Exists(directoryPath))
+                {
+                    Directory.CreateDirectory(directoryPath);
+                }
+
                 bool fileExists = File.Exists(_csvReportPath);
 
                 using (var writer = new StreamWriter(_csvReportPath, true, Encoding.UTF8))

+ 106 - 0
bird_tool/TopWindow.cs

@@ -0,0 +1,106 @@
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+
+namespace Bird_tool
+{
+    public static class TopMostMessageBox
+    {
+        public static void Show(string message)
+        {
+            // 确保在UI线程执行
+            if (Application.OpenForms.Count > 0)
+            {
+                Form mainForm = Application.OpenForms[0];
+                if (mainForm.InvokeRequired)
+                {
+                    mainForm.Invoke(new Action(() => CreateMessageBox(message)));
+                }
+                else
+                {
+                    CreateMessageBox(message);
+                }
+            }
+            else
+            {
+                // 如果没有主窗体,直接创建
+                CreateMessageBox(message);
+            }
+        }
+
+        private static void CreateMessageBox(string message)
+        {
+            using (var form = new Form
+            {
+                Width = 350,
+                Height = 180,
+                FormBorderStyle = FormBorderStyle.FixedDialog,
+                MaximizeBox = false,
+                MinimizeBox = false,
+                StartPosition = FormStartPosition.CenterScreen,
+                Text = "系统提示",
+                TopMost = true,
+                BackColor = System.Drawing.Color.WhiteSmoke,
+                Padding = new Padding(10),
+                Font = new System.Drawing.Font("微软雅黑", 10)
+            })
+            {
+                // 消息标签 - 带自动换行
+                var label = new Label
+                {
+                    Text = message,
+                    Dock = DockStyle.Fill,
+                    TextAlign = System.Drawing.ContentAlignment.MiddleCenter,
+                    AutoSize = false,
+                    Padding = new Padding(0, 10, 0, 20)
+                };
+
+                // 确定按钮
+                var panel = new Panel
+                {
+                    Dock = DockStyle.Bottom,
+                    Height = 50,
+                    BackColor = Color.Transparent
+                };
+                // 确定按钮
+                var button = new Button
+                {
+                    Text = "确定",
+                    Size = new Size(80, 35),
+                    Location = new Point((panel.Width - 80) / 2, 5)
+                };
+                button.Click += (s, e) => form.Close();
+
+                
+                // 添加控件
+                panel.Controls.Add(button);
+
+                form.Controls.Add(label);
+                form.Controls.Add(panel);
+
+                // 为主窗体添加激活事件处理程序
+                foreach (Form mainForm in Application.OpenForms)
+                {
+                    if (mainForm != form)
+                    {
+                        // 安全添加事件处理
+                        mainForm.Activated += (s, e) =>
+                        {
+                            if (!form.IsDisposed)
+                            {
+                                form.TopMost = true;
+                            }
+                        };
+                    }
+                }
+
+                form.AcceptButton = button;
+                form.ShowDialog();
+            }
+        }
+    }
+}

+ 179 - 5
bird_tool/bird_tool.Designer.cs

@@ -1,5 +1,6 @@
 using System.Windows.Forms;
 using System.Drawing;
+using System;
 
 namespace Bird_tool
 {
@@ -26,6 +27,7 @@ namespace Bird_tool
         #region Windows Form Designer generated code
 
 
+
         // 显示日志窗口
         
         /// <summary>
@@ -120,7 +122,7 @@ namespace Bird_tool
             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);
+            this.el_btn_config.Click += new System.EventHandler(this.Evt_config_click);
 
             this.el_btn_sd_test.Anchor = System.Windows.Forms.AnchorStyles.None;
             this.el_btn_sd_test.BackColor = System.Drawing.SystemColors.Control;
@@ -181,10 +183,10 @@ namespace Bird_tool
                 TabIndex = 4
             };
             progressPanel.OnLogRequested += (s, ev) => ShowLogWindow();
-            progressPanel.OnStopRequested += (s, ev) => StopTest(true);
-            progressPanel.OnStartRequested += (s, ev) => start_query_test();
-            progressPanel.OnReturnRequested += (s, ev) => re_main();
-            progressPanel.StepList.OnStepButtonClick += setpStart;
+            progressPanel.OnStopRequested += (s, ev) => Evt_StopTest(true);
+            progressPanel.OnStartRequested += (s, ev) => Evt_start_query_test();
+            progressPanel.OnReturnRequested += (s, ev) => Evt_re_main();
+            progressPanel.StepList.OnStepButtonClick += Evt_setpStart;
 
 
 
@@ -320,5 +322,177 @@ namespace Bird_tool
         private System.Windows.Forms.ComboBox cmbIPAddresses;
 
         System.Windows.Forms.Button btn_connect_wifi;
+
+        private string ShowIdUsedOptions(string deviceId)
+        {
+            string result = ""; // 返回用户的选择
+            using (Form optionsForm = new Form())
+            {
+                optionsForm.Width = 350;
+                optionsForm.Height = 180;
+                optionsForm.FormBorderStyle = FormBorderStyle.FixedDialog;
+                optionsForm.MaximizeBox = false;
+                optionsForm.MinimizeBox = false;
+                optionsForm.StartPosition = FormStartPosition.CenterParent;
+                optionsForm.Text = "设备ID已使用";
+
+                Label lblMessage = new Label()
+                {
+                    Text = $"设备ID '{deviceId}' 已被使用,请选择操作:",
+                    Location = new Point(20, 20),
+                    AutoSize = true
+                };
+
+                Button btnReconfigure = new Button()
+                {
+                    Text = "重新配置",
+                    Location = new Point(10, 70),
+                    Size = new Size(100, 35),
+                    Tag = "reconfigure"
+                };
+
+                Button btnReenter = new Button()
+                {
+                    Text = "重新输入",
+                    Location = new Point(120, 70),
+                    Size = new Size(100, 35),
+                    Tag = "reenter"
+                };
+
+                Button btnCancel = new Button()
+                {
+                    Text = "取消",
+                    Location = new Point(230, 70),
+                    Size = new Size(100, 35),
+                    Tag = "cancel"
+                };
+
+                btnReconfigure.Click += (s, ev) =>
+                {
+                    result = "reconfigure";
+                    optionsForm.Close();
+                };
+
+                btnReenter.Click += (s, ev) =>
+                {
+                    result = "reenter";
+                    optionsForm.Close();
+                };
+
+                btnCancel.Click += (s, ev) =>
+                {
+                    result = "cancel";
+                    optionsForm.Close();
+                };
+
+                optionsForm.Controls.Add(lblMessage);
+                optionsForm.Controls.Add(btnReconfigure);
+                optionsForm.Controls.Add(btnReenter);
+                optionsForm.Controls.Add(btnCancel);
+
+                optionsForm.ShowDialog();
+            }
+
+            return result;
+        }
+    }
+
+    public partial class IdInputDialog : Form
+    {
+        public string EnteredId { get; private set; }
+        public static string tips { get; set; } = "请输入设备ID:";
+
+        public IdInputDialog(string tip_text = "请输入设备ID:")
+        {
+            tips = tip_text;
+            InitializeComponent();
+        }
+
+        private void btnOK_Click(object sender, System.EventArgs e)
+        {
+            EnteredId = txtId.Text;
+            DialogResult = DialogResult.OK;
+            Close();
+        }
+
+        private void btnCancel_Click(object sender, System.EventArgs e)
+        {
+            DialogResult = DialogResult.Cancel;
+            Close();
+        }
+
+        #region Windows Form Designer generated code
+        private TextBox txtId;
+        private Button btnOK;
+        private Button btnCancel;
+        private Label label1;
+
+        private void InitializeComponent()
+        {
+            this.txtId = new System.Windows.Forms.TextBox();
+            this.btnOK = new System.Windows.Forms.Button();
+            this.btnCancel = new System.Windows.Forms.Button();
+            this.label1 = new System.Windows.Forms.Label();
+            this.SuspendLayout();
+            // 
+            // txtId
+            // 
+            this.txtId.Font = new System.Drawing.Font("宋体", 12F);
+            this.txtId.Location = new System.Drawing.Point(20, 50);
+            this.txtId.Name = "txtId";
+            this.txtId.Size = new System.Drawing.Size(260, 26);
+            this.txtId.TabIndex = 0;
+            // 
+            // btnOK
+            // 
+            this.btnOK.DialogResult = System.Windows.Forms.DialogResult.OK;
+            this.btnOK.Location = new System.Drawing.Point(60, 100);
+            this.btnOK.Name = "btnOK";
+            this.btnOK.Size = new System.Drawing.Size(80, 30);
+            this.btnOK.TabIndex = 1;
+            this.btnOK.Text = "确定";
+            this.btnOK.UseVisualStyleBackColor = true;
+            this.btnOK.Click += new System.EventHandler(this.btnOK_Click);
+            // 
+            // btnCancel
+            // 
+            this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
+            this.btnCancel.Location = new System.Drawing.Point(160, 100);
+            this.btnCancel.Name = "btnCancel";
+            this.btnCancel.Size = new System.Drawing.Size(80, 30);
+            this.btnCancel.TabIndex = 2;
+            this.btnCancel.Text = "取消";
+            this.btnCancel.UseVisualStyleBackColor = true;
+            this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click);
+            // 
+            // label1
+            // 
+            this.label1.AutoSize = true;
+            this.label1.Font = new System.Drawing.Font("宋体", 12F);
+            this.label1.Location = new System.Drawing.Point(20, 20);
+            this.label1.Name = "label1";
+            this.label1.Size = new System.Drawing.Size(120, 16);
+            this.label1.TabIndex = 3;
+            this.label1.Text = tips;
+            // 
+            // IdInputDialog
+            // 
+            this.AcceptButton = this.btnOK;
+            this.CancelButton = this.btnCancel;
+            this.ClientSize = new System.Drawing.Size(300, 150);
+            this.Controls.Add(this.label1);
+            this.Controls.Add(this.btnCancel);
+            this.Controls.Add(this.btnOK);
+            this.Controls.Add(this.txtId);
+            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
+            this.MaximizeBox = false;
+            this.MinimizeBox = false;
+            this.Name = "IdInputDialog";
+            this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
+            this.Text = "设备ID输入";
+            this.ResumeLayout(false);
+            this.PerformLayout();
+        }
+        #endregion
     }
 }

+ 570 - 269
bird_tool/bird_tool.cs

@@ -58,10 +58,16 @@ namespace Bird_tool
         private static LogLevel show_log_level = LogLevel.info;
         private static StringBuilder logBuilder = new StringBuilder();
 
+        private static ExcelDataManager m_excel_manager;
+        private string m_config_uuid = "";
+        
+
         private bool result_success = false;
         private string result_msg = "";
         private TestMode m_test_mode = TestMode.none;
 
+        private string AppConfigPath = "";
+
         private bool SetWifi = false;
         private string wifi_ssid = "";
         private string wifi_password = "";
@@ -72,6 +78,9 @@ namespace Bird_tool
         private string excel_path = "test.xlsx";
         private string excel_primary_key = "uuid";
 
+        private string hw_test_csv_path = "TestReport/report.csv";
+        private string config_csv_path = "ConfigReport/report.csv";
+
 
         public bird_tool()
         {
@@ -117,7 +126,7 @@ namespace Bird_tool
 
         static public void Log_show(string message)
         {
-            MessageBox.Show(message);
+            TopMostMessageBox.Show(message);
         }
 
         private void ShowLogWindow()
@@ -287,7 +296,9 @@ namespace Bird_tool
 
         private void Tool_load(object sender, EventArgs e)
         {
+            
             load_app_config();
+            
             _uart_ui_change(false);
             SerialPort_Load();
             
@@ -309,20 +320,47 @@ namespace Bird_tool
             {
                 _uart_ui_change(false);
                 // 判断是否在测试
-                StopTest(isError);
+                if (isError)
+                {
+                    StopTest(false, "串口异常断开连接");
+                }
             };
+            _serialManager.OnLog += Log;
+            Log("初始化串口组件中...");
             _serialManager.OnConnect += () =>
             {
                 _uart_ui_change(true);
             };
+            
+            if (appConfig.enable_excel_load)
+            {
+                m_excel_manager = new ExcelDataManager(appConfig);
+                m_excel_manager.OnLog += Log;
+                m_excel_manager.OnLogShow += Log_show;
+                if (!m_excel_manager.IsValid)
+                {
+                    Log("excel 文件加载失败, 将禁用ecxel相关功能", LogLevel.error);
+                    Log_show("excel 文件加载失败, 将禁用ecxel相关功能");
+                }
+            }
         }
         private void load_app_config()
         {
+            Log($"加载配置文件 {ConfigManager.getConfigPath()}");
             appConfig = ConfigManager.LoadConfig();
             wifi_ssid = appConfig.wifi_ssid;
             wifi_password = appConfig.wifi_passwd;
             txt_ssid.Text = wifi_ssid;
             txt_password.Text = wifi_password;
+            if (appConfig.enable_excel_load)
+            {
+                Log($"启用excel文件,尝试加载excel文件中 {appConfig.excel_path}");
+            }
+            else
+            {
+                Log($"不使用excel加载, 仅使用基础检测功能");
+                Log($"如果需要使用excel配置功能, 请在配置文件{ConfigManager.getConfigPath()}中进行配置");
+            }
         }
 
         private void SerialPort_Load()
@@ -339,7 +377,6 @@ namespace Bird_tool
 
             // 初始加载串口列表
             RefreshPortList();
-            Log("inti ");
             _testExecutor = new TestExecutor(_serialManager);
 
             // 绑定事件处理
@@ -425,13 +462,10 @@ namespace Bird_tool
             }
             // 尝试连接串口
             Log($"连接串口{currentSelection}");
-
-
-
             if (_serialManager.IsOpen())
             {
                 // 调用停止函数
-                StopTest(true);
+                StopTest(true, "主动关闭串口");
                 _serialManager.Disconnect();
                 System.Threading.Thread.Sleep(100);
                 _uart_ui_change(false);
@@ -446,7 +480,7 @@ namespace Bird_tool
             Log("串口开启成功");
             // 设置窗口内容
             _uart_ui_change(true);
-
+            
 
         }
 
@@ -463,6 +497,23 @@ namespace Bird_tool
             checkWifi();
             hw_test();
         }
+        private void Evt_config_click(object sender, EventArgs e)
+        {
+            if (appConfig.enable_excel_load && !m_excel_manager.IsValid)
+            {
+                Log_show($"excel 文件异常, 请检查配置文件 {ConfigManager.getConfigPath()}");
+                return;
+            }
+            if (!_serialManager.IsOpen())
+            {
+                MessageBox.Show("请先打开串口");
+                _serialManager.Disconnect();
+                System.Threading.Thread.Sleep(100);
+                _uart_ui_change(false);
+                return;
+            }
+            config_test();
+        }
         private void Evt_sd_click(object sender, EventArgs e)
         {
             if (!_serialManager.IsOpen())
@@ -481,6 +532,27 @@ namespace Bird_tool
             checkWifi();
         }
 
+
+        private void Evt_re_main()
+        {
+            ui_show_start_panpel();
+            progressPanel.ResetPanel();
+        }
+
+        private void Evt_start_query_test()
+        {
+            StartTest(TestType.StartIndex);
+        }
+
+        private void Evt_setpStart(string key, TestType testType)
+        {
+            StartTest(testType, key);
+        }
+        private void Evt_StopTest(bool isQuery)
+        {
+            StopTest(isQuery, "手动停止测试");
+        }
+
         private void checkWifi ()
         {
             // 获取wifi值
@@ -508,7 +580,16 @@ namespace Bird_tool
         {
             el_btn_hw_test.Enabled = enabled;
             el_btn_sd_test.Enabled = enabled;
-            el_btn_config.Enabled = enabled;
+
+            if (appConfig.enable_excel_load)
+            {
+                el_btn_config.Enabled = enabled;
+            }
+            else
+            {
+                el_btn_config.Enabled = false;
+            }
+
 
             // 可选:添加视觉反馈
             if (enabled)
@@ -518,11 +599,13 @@ namespace Bird_tool
                 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;
-
+                if (appConfig.enable_excel_load)
+                {
+                    el_btn_config.BackColor = SystemColors.Control;
+                    el_btn_config.ForeColor = SystemColors.ControlText;
+                    el_btn_config.FlatStyle = FlatStyle.Standard;
+                    el_btn_config.Cursor = Cursors.Default;
+                }
                 el_btn_sd_test.BackColor = SystemColors.Control;  // 标准按钮背景色
                 el_btn_sd_test.ForeColor = SystemColors.ControlText;  // 标准文本色
                 el_btn_sd_test.FlatStyle = FlatStyle.Standard;  // 恢复原始样式
@@ -571,17 +654,29 @@ namespace Bird_tool
             this.Refresh();
         }
 
+        private void UpdateConnButtonText(bool uart_flag, string text)
+        {
+            if (el_btn_conn.InvokeRequired)
+            {
+                el_btn_conn.Invoke(new Action<bool, string>(UpdateConnButtonText), uart_flag, text);
+            }
+            else
+            {
+                el_btn_conn.Text = text;
+                _SetTestButtonsEnabled(uart_flag);
+            }
+        }
+
         private void _uart_ui_change(bool uart_flag)
         {
             if (uart_flag)
             {
-                el_btn_conn.Text = "断开连接";
+                UpdateConnButtonText(uart_flag, "断开连接");
             }
             else
             {
-                el_btn_conn.Text = "连接串口";
+                UpdateConnButtonText(uart_flag, "连接串口");
             }
-            _SetTestButtonsEnabled(uart_flag);
         }
 
         private void _ip_ui_change()
@@ -604,187 +699,6 @@ namespace Bird_tool
             }
         }
 
-        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_show_sd(bool isShow)
         {
@@ -810,28 +724,15 @@ namespace Bird_tool
             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()
         {
-            Log_show("无法创建测试表");
-            StopTest(false);
+            StopTest(false, "配置模式");
+            m_test_mode = TestMode.config;
+            ui_show_test_panpel();
+            progressPanel.ResetPanel();
+            progressPanel.Message = "点击开始测试按钮进行设备配置";
             return;
         }
 
@@ -843,7 +744,7 @@ namespace Bird_tool
             if (!_testExecutor.InitTest(steps))
             {
                 Log_show("无法创建测试表");
-                StopTest(false);
+                StopTest(false, "无法创建测试表");
                 return;
             }
             m_test_mode = TestMode.hw_test;
@@ -871,28 +772,92 @@ namespace Bird_tool
             if (!_testExecutor.InitTest(steps))
             {
                 Log_show("无法创建测试表");
-                StopTest(false);
+                StopTest(false, "无法创建测试表");
                 return;
             }
-            m_test_mode = TestMode.sd_test;
-            ui_show_test_panpel();
-            progressPanel.ResetPanel();
+            m_test_mode = TestMode.sd_test;
+            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}] 格式化sd卡");
+        }
+        private string ask_query_id()
+        {
+            string deviceId = "";
+            while (true) // 使用循环代替递归
+            {
+                if (!appConfig.enable_excel_load || m_excel_manager == null || !m_excel_manager.IsValid)
+                {
+                    Log_show("excel组件加载异常, 请检查excel配置文件");
+                    return "";
+                }
+
+                using (var idDialog = new IdInputDialog("请输入uuid"))
+                {
+                    if (idDialog.ShowDialog() == DialogResult.OK)
+                    {
+                        deviceId = idDialog.EnteredId;
+                        var row = m_excel_manager.GetRowByKey(deviceId);
+                        if (row == null)
+                        {
+                            StopTest(false, "uuid异常, 请重新输入或者检查excel文件");
+                            return "";
+                        }
+
+                        if (m_excel_manager.IsKeyUsed(deviceId))
+                        {
+                            // 显示选项并获取用户选择
+                            var result = ShowIdUsedOptions(deviceId);
+                            if (result == "reenter")
+                            {
+                                continue; // 继续循环让用户重新输入
+                            }
+                            else if (result == "reconfigure")
+                            {
+                                return deviceId; // 返回ID进行重新配置
+                            }
+                            else if (result == "cancel")
+                            {
+                                StopTest(false, "未输入设备uuid");
+                                return ""; // 返回ID进行重新配置
+                            }
+                        }
+                        else
+                        {
+                            return deviceId; // ID可用,返回
+                        }
+                    }
+                    else
+                    {
+                        StopTest(false, "未输入设备uuid");
+                        return "";
+                    }
+                }
+            }
+        }
 
-            var groups = _testExecutor.GetGroupInfos();
-            foreach (var group in groups)
+        private async void StartTest(TestType testType, string groupId = null)
+        {
+            // 
+            if (m_test_mode == TestMode.config)
             {
-                if (group.ShowStep)
+                if (!initConfigTest())
                 {
-                    progressPanel.AddTestStep(group.RowKey, group.GroupName);
+                    Log("涂鸦uuid输入错误!!", LogLevel.error);
+                    //Log_show("无法初始化涂鸦相关命令");
+                    return;
                 }
             }
-            progressPanel.Message = "点击开始测试按钮进行测试";
-
-            Log($"[{DateTime.Now}] 格式化sd卡");
-        }
-
-        private void StartTest(TestType testType, string groupId = null)
-        {
             if (!_testExecutor.isInit)
             {
                 Log_show("测试引擎未初始化!");
@@ -908,21 +873,31 @@ namespace Bird_tool
                     progressPanel.AddTestStep(group.RowKey, group.GroupName);
                 }
             }
-            progressPanel.StartTimer();
+            
             // 初始化测试状态
             progressPanel.ProgressValue = 0;
             progressPanel.CurrentTest = "初始化测试环境";
             progressPanel.Message = "准备开始硬件测试...";
+            await Task.Delay(500);
+            progressPanel.StartTimer();
             progressPanel.startTest();
+
             bool start_flag = false;
             bool makeResult = false;
+            string csv_path = "";
             if (m_test_mode == TestMode.hw_test)
             {
                 makeResult = true;
+                csv_path = hw_test_csv_path;
+            }
+            if (m_test_mode == TestMode.config)
+            {
+                makeResult = true;
+                csv_path = config_csv_path;
             }
             if (string.IsNullOrEmpty(groupId))
             {
-                start_flag = _testExecutor.StartTest(makeResult);
+                start_flag = _testExecutor.StartTest(makeResult, csv_path);
             }
             else
             {
@@ -936,19 +911,26 @@ namespace Bird_tool
                 }
                 else
                 {
-                    start_flag = _testExecutor.StartTest(makeResult);
+                    StopTest(false, "无法找到对应的分组列表, 请重试");
                 }
             }
 
             if (!start_flag)
             {
                 Log("启动测试失败, 等待重新执行中", LogLevel.error);
-                StopTest(false);
+                StopTest(false, "无法启动测试");
             }
             
         }
-        private void StopTest(bool isQuery = true)
+        private void StopTest(bool isQuery = true, string message = "停止异常中止")
         {
+           
+            if(!_testExecutor.isStart)
+            {
+                Log($"停止测试:{message} 但是测试未开始");
+                return;
+            }
+            Log($"开始执行停止测试, 原因:{message}");
             // 停止测试
             if (isQuery && _testExecutor.isStart)
             {
@@ -961,21 +943,19 @@ namespace Bird_tool
                     progressPanel.ShowTestResult(false, "测试已取消", LoadCancelledImage());
 
                     // 添加日志
-                    logBuilder.AppendLine($"[{DateTime.Now}] 测试已取消");
+                    Log($"[{DateTime.Now}] 测试已取消");
                     _testExecutor.StopTestAsync();
                     progressPanel.Message = "用户手动停止测试";
                     Log($"[{DateTime.Now}] 测试中止");
                 }
-               
             }
             else
             {
                 progressPanel.ShowTestResult(false, "测试已取消", LoadCancelledImage());
 
                 // 添加日志
-                logBuilder.AppendLine($"[{DateTime.Now}] 测试已取消");
-                _testExecutor.StopTestAsync();
-                progressPanel.Message = "停止异常中止";
+                //_testExecutor.StopTestAsync();
+                progressPanel.Message = message;
                 Log($"[{DateTime.Now}] 测试中止");
             }
             
@@ -984,7 +964,6 @@ namespace Bird_tool
         private void TestFailedhandle(TestStepConfig step, TestContext context)
         {
             string str = $"------ 测试失败-------";
-            _testExecutor.StopTestAsync();
             result_success = false;
             result_msg = str;
             Log(str);
@@ -994,7 +973,24 @@ namespace Bird_tool
         private void TestSuccesshandle(TestContext context)
         {
             string str = $"------ 测试完成, 请更换设备-------";
-            _testExecutor.StopTestAsync();
+            if (m_test_mode == TestMode.config)
+            {
+                // 获取之前测试使用的uuid
+                if (!string.IsNullOrEmpty(m_config_uuid))
+                {
+                    string mac = context.Variables.TryGetValue("mac", out mac) ? mac : null;
+                    string uuid = context.Variables.TryGetValue("uuid", out uuid) ? uuid : null;
+                    m_excel_manager.MarkKeyAsUsed(m_config_uuid, "测试工具", mac);
+                    m_config_uuid = "";
+                    m_excel_manager.SaveProgress();
+                }
+                else
+                {
+                    Log($"测试完成, 但是未获取到正确的uuid. 联系管理员", LogLevel.error);
+                }
+
+            }
+            //_testExecutor.StopTestAsync();
             result_success = true;
             result_msg = str;
             Log(str);
@@ -1014,9 +1010,6 @@ namespace Bird_tool
 
             return new List<TestStepConfig>
             {
-
-
-
                 // 按钮长按功能测试
                 ///**
                 new TestStepConfig
@@ -1024,7 +1017,7 @@ namespace Bird_tool
                     GroupId = "btn_long",
                     GroupName = "按钮测试",
                     Name = "长按测试1",
-                    Tips = "请长按电源按钮 6秒后再次长按 第一次",
+                    Tips = "请长按电源按钮5秒后松开, 没成功在按一次按钮 等6秒后再次长按",
                     Command = "\r\n",
                     SuccessPattern = "sys off",
                     Timeout = 20000,
@@ -1035,7 +1028,7 @@ namespace Bird_tool
                 {
                     GroupId = "btn_long",
                     Name = "开机测试1",
-                    Tips = "点击电源按钮 2秒后再次点击 第一次",
+                    Tips = "灯灭后按下电源按钮后松开 2秒后再次按下后松开",
                     Command = "\r\n",
                     SuccessPattern = "OS start",
                     Timeout = 20000,
@@ -1126,24 +1119,12 @@ namespace Bird_tool
                     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 = "请将手移动至红外传感器上 第次",
+                    Name = "红外触发1",
+                    Tips = "请将手移动至红外传感器上 第一次",
                     Command = "\r\n",
                     SuccessPattern = "cmd:17",
                     Timeout = 30000,
@@ -1272,8 +1253,9 @@ namespace Bird_tool
                     DelayBefore = 1000,
                     MaxJump = 5,
                 },
-                
 
+                // 设备基础参数配置
+                
                 // 音频测试
                 ///**
                 new TestStepConfig
@@ -1361,8 +1343,6 @@ namespace Bird_tool
                     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,
@@ -1386,8 +1366,6 @@ namespace Bird_tool
                     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,
@@ -1487,6 +1465,147 @@ namespace Bird_tool
                 },
             };
         }
+
+        private bool initConfigTest()
+        {
+            string uuid = ask_query_id();
+            if (string.IsNullOrEmpty(uuid))
+            {
+                return false;
+            }
+            Log($"用户输入id: {uuid}");
+            var (names, values) = m_excel_manager.GetVariableListByKey(uuid);
+            m_config_uuid = uuid;
+            List<TestStepConfig> ret_steps = new List<TestStepConfig>();
+            // 构建配置文件
+            ret_steps.Add(new TestStepConfig
+            {
+                Name = "获取MAC地址",
+                Tips = "提取设备MAC地址",
+                IsDeviceInfoItem = true,
+                InfoDisplayName = "设备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);
+                }
+            });
+            ret_steps.Add(new TestStepConfig
+            {
+                GroupId = "tuya_config",
+                GroupName = "配置涂鸦sdk",
+                ShowStep = false,
+                IsDeviceInfoItem = true,
+                InfoDisplayName = "涂鸦uuid",
+                Name = "配置涂鸦id",
+                Tips = "配置涂鸦参数中",
+                Action = ActionMode.SetVal,
+                VariableNames = names,
+                VariableValues = values,
+            });
+            ret_steps.Add(new TestStepConfig
+            {
+                GroupId = "tuya_config",
+                Name = "涂鸦id配置",
+                Key = "config_id",
+                Tips = "配置涂鸦sdk账户中",
+                Command = "AT+SCFG={\"target\":\"tuya\"\\,\"data\":{\"pid\":\"9kifakenvwsj1a9v\"\\,\"uuid\":\"{{uuid}}\"\\,\"authkey\":\"{{authkey}}\"\\,\"state\":\"0\"\r\n}}",
+                SuccessPattern = "<=OK cmd:24",
+                Timeout = 6000,
+                DelayBefore = 1000,
+            });
+            ret_steps.Add(new TestStepConfig
+            {
+                GroupId = "tuya_config",
+                Name = "涂鸦基础配置",
+                Tips = "配置涂鸦sdk基础信息",
+                Command = "AT+SCFG={\"target\":\"tuya\"\\,\"data\":{\"storage\":\"/data/\"\\,\"media_path\":\"/tmp/media/\"\\,\"sd_path\":\"/tmp/sdcard/\"\\,\"upgrade_file\":\"/tmp/upgrade.file\"\\,\"app_version\":\"1.2.3\"}}\r\n",
+                SuccessPattern = "<=OK cmd:24",
+                Timeout = 6000,
+                DelayBefore = 1000,
+            });
+            // 查询配置
+            ret_steps.Add(new TestStepConfig
+            {
+                GroupId = "tuya_config",
+                Name = "涂鸦id查询",
+                Tips = "检查是否配置涂鸦uuid成功",
+                Command = "AT+SEND=1,AT+FACTORY=4\\,\"cat /data/tuya_device_cfg.json | grep uuid | awk -F[:\\,] '{for(i=1;i<=NF;i++){if($i~/uuid/){print $(i+1)}}}'\"\r\n",
+                SuccessText = "\"{{uuid}}",
+                RetryFromKey = "config_id",
+                Timeout = 6000,
+                DelayBefore = 1000,
+                MaxJump = 5,
+            });
+            ret_steps.Add(new TestStepConfig
+            {
+                GroupId = "tuya_config",
+                Name = "涂鸦id查询",
+                Tips = "检查是否配置涂鸦sdk成功",
+                Command = "AT+SEND=1,AT+FACTORY=4\\,\"cat /data/tuya_device_cfg.json | grep storage | awk -F[:\\,] '{for(i=1;i<=NF;i++){if($i~/storage/){print $(i+1)}}}'\"\r\n",
+                SuccessText = "\"/data",
+                RetryFromKey = "config_id",
+                Timeout = 6000,
+                DelayBefore = 1000,
+                MaxJump = 5,
+            });
+
+
+            // 配置梵悦测试信息
+            ret_steps.Add(new TestStepConfig
+            {
+                GroupId = "device_config",
+                GroupName = "设备参数配置",
+                Name = "配置视频水印",
+                Tips = "配置视频水印...",
+                Command = "AT+SEND=1, AT+CAMISP=6\\,0\\,0\r\n",
+                SuccessPattern = "<=OK cmd:20",
+                Timeout = 6000,
+                DelayBefore = 1000,
+                MaxRetries = 2,
+            });
+            ret_steps.Add(new TestStepConfig
+            {
+                GroupId = "device_config",
+                Name = "配置图片水印",
+                Tips = "配置图片水印...",
+                Command = "AT+SEND=1, AT+CAMISP=5\\,0\\,0\r\n",
+                SuccessPattern = "<=OK cmd:20",
+                Timeout = 6000,
+                DelayBefore = 1000,
+                MaxRetries = 2,
+            });
+
+
+            if (!_testExecutor.InitTest(ret_steps))
+            {
+                Log_show("无法创建测试表");
+                StopTest(false, "生成配置命令失败" );
+                return false;
+            }
+
+            var groups = _testExecutor.GetGroupInfos();
+            foreach (var group in groups)
+            {
+                if (group.ShowStep)
+                {
+                    progressPanel.AddTestStep(group.RowKey, group.GroupName);
+                }
+            }
+
+            return true;
+        }
+
         private bool IsValidMac(string mac)
         {
             // 实现MAC地址验证逻辑
@@ -1745,6 +1864,188 @@ namespace Bird_tool
 
 
 
+        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, 120F),
+                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", 20, 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);
+            }));
+        }
+
+
 
     }
 

+ 4 - 3
bird_tool/bird_tool.csproj

@@ -6,8 +6,8 @@
     <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
     <ProjectGuid>{D4446469-6858-416E-9A11-103FCA09A5DD}</ProjectGuid>
     <OutputType>Exe</OutputType>
-    <RootNamespace>digital</RootNamespace>
-    <AssemblyName>digital</AssemblyName>
+    <RootNamespace>Bird_tool</RootNamespace>
+    <AssemblyName>Bird_tool</AssemblyName>
     <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
     <FileAlignment>512</FileAlignment>
     <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
@@ -60,6 +60,7 @@
     <Compile Include="TestStepList.cs">
       <SubType>UserControl</SubType>
     </Compile>
+    <Compile Include="TopWindow.cs" />
   </ItemGroup>
   <ItemGroup>
     <Reference Include="Microsoft.CSharp" />
@@ -94,7 +95,7 @@
       <Version>4.10.0.5680</Version>
     </PackageReference>
     <PackageReference Include="EPPlus">
-      <Version>8.0.7</Version>
+      <Version>6.1.0</Version>
     </PackageReference>
     <PackageReference Include="HttpMultipartParser">
       <Version>5.1.0</Version>