Преглед изворни кода

feat: 日志优化, 图像服务集成
1. 图像服务集成
2. 日志功能优化

kindring пре 1 недеља
родитељ
комит
e3b842d225
6 измењених фајлова са 784 додато и 7 уклоњено
  1. 63 0
      .gitattributes
  2. 81 0
      bird_tool/Config.cs
  3. 480 0
      bird_tool/ImageServer.cs
  4. 15 1
      bird_tool/bird_tool.Designer.cs
  5. 137 6
      bird_tool/bird_tool.cs
  6. 8 0
      bird_tool/bird_tool.csproj

+ 63 - 0
.gitattributes

@@ -0,0 +1,63 @@
+###############################################################################
+# Set default behavior to automatically normalize line endings.
+###############################################################################
+* text=auto
+
+###############################################################################
+# Set default behavior for command prompt diff.
+#
+# This is need for earlier builds of msysgit that does not have it on by
+# default for csharp files.
+# Note: This is only used by command line
+###############################################################################
+#*.cs     diff=csharp
+
+###############################################################################
+# Set the merge driver for project and solution files
+#
+# Merging from the command prompt will add diff markers to the files if there
+# are conflicts (Merging from VS is not affected by the settings below, in VS
+# the diff markers are never inserted). Diff markers may cause the following 
+# file extensions to fail to load in VS. An alternative would be to treat
+# these files as binary and thus will always conflict and require user
+# intervention with every merge. To do so, just uncomment the entries below
+###############################################################################
+#*.sln       merge=binary
+#*.csproj    merge=binary
+#*.vbproj    merge=binary
+#*.vcxproj   merge=binary
+#*.vcproj    merge=binary
+#*.dbproj    merge=binary
+#*.fsproj    merge=binary
+#*.lsproj    merge=binary
+#*.wixproj   merge=binary
+#*.modelproj merge=binary
+#*.sqlproj   merge=binary
+#*.wwaproj   merge=binary
+
+###############################################################################
+# behavior for image files
+#
+# image files are treated as binary by default.
+###############################################################################
+#*.jpg   binary
+#*.png   binary
+#*.gif   binary
+
+###############################################################################
+# diff behavior for common document formats
+# 
+# Convert binary document formats to text before diffing them. This feature
+# is only available from the command line. Turn it on by uncommenting the 
+# entries below.
+###############################################################################
+#*.doc   diff=astextplain
+#*.DOC   diff=astextplain
+#*.docx  diff=astextplain
+#*.DOCX  diff=astextplain
+#*.dot   diff=astextplain
+#*.DOT   diff=astextplain
+#*.pdf   diff=astextplain
+#*.PDF   diff=astextplain
+#*.rtf   diff=astextplain
+#*.RTF   diff=astextplain

+ 81 - 0
bird_tool/Config.cs

@@ -0,0 +1,81 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Newtonsoft.Json;
+using System.IO;
+
+namespace Bird_tool
+{
+    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 static class ConfigManager
+    {
+        private static readonly string ConfigPath =
+            Path.Combine(Environment.CurrentDirectory, "config.json");
+
+        public static AppConfig LoadConfig()
+        {
+            if (!File.Exists(ConfigPath))
+            {
+                var defaultConfig = new AppConfig();
+                SaveConfig(defaultConfig);
+                return defaultConfig;
+            }
+
+            //string json = File.ReadAllText(ConfigPath);
+            
+            try
+            {
+                using (var streamReader = new StreamReader(ConfigPath))
+                using (var jsonReader = new JsonTextReader(streamReader))
+                {
+                    var serializer = new JsonSerializer();
+                    return serializer.Deserialize<AppConfig>(jsonReader);
+                }
+            }
+            catch (JsonException ex)
+            {
+                Console.WriteLine($"配置文件加载失败: {ex.Message}");
+                // 处理损坏的配置文件
+                File.Delete(ConfigPath);
+                return new AppConfig();
+            }
+            catch (IOException ex)
+            {
+                Console.WriteLine($"文件访问错误: {ex.Message}");
+                return new AppConfig();
+            }
+        }
+
+        public static void SaveConfig(AppConfig config)
+        {
+            try
+            {
+                // 确保目录存在
+                Directory.CreateDirectory(Path.GetDirectoryName(ConfigPath));
+
+                // 使用临时文件写入,然后替换原文件,避免占用问题
+                string tempFile = Path.GetTempFileName();
+                string json = JsonConvert.SerializeObject(config, Formatting.Indented);
+                File.WriteAllText(tempFile, json);
+
+                // 替换原文件
+                File.Copy(tempFile, ConfigPath, true);
+                File.Delete(tempFile);
+            }
+            catch (Exception ex)
+            {
+                Console.WriteLine($"保存配置失败: {ex.Message}");
+            }
+        }
+    }
+}

+ 480 - 0
bird_tool/ImageServer.cs

@@ -0,0 +1,480 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.IO;
+using System.Net;
+using System.Threading;
+using Emgu.CV;
+using Emgu.CV.CvEnum;
+using Emgu.CV.Structure;
+using System.Drawing.Imaging;
+using System.Drawing;
+using HttpMultipartParser;
+using System.Net.NetworkInformation;
+using System.Net.Sockets;
+
+namespace Bird_tool
+{
+
+
+    class ImageSharpnessServer
+    {
+        public delegate void LogShow(string message);
+        public delegate void LogHandler(string message, LogLevel level);
+
+        public event LogHandler OnLog;
+        public event LogShow OnLogShow;
+        private readonly HttpListener _listener;
+        private readonly int _port;
+        private readonly string _ip;
+        private bool _isRunning;
+
+        public ImageSharpnessServer(string ip, int port = 8080)
+        {
+            _ip = ip;
+            _port = port;
+            
+            _listener = new HttpListener();
+            string urlPrefix = $"http://*:{_port}/";
+            OnLog?.Invoke($"Server started on {urlPrefix}", LogLevel.info);
+            _listener.Prefixes.Add(urlPrefix);
+        }
+
+        public bool Start()
+        {
+            try
+            {
+                _isRunning = true;
+                _listener.Start();
+                OnLog?.Invoke($"Server started on port {_port}", LogLevel.info);
+                ThreadPool.QueueUserWorkItem(Listen);
+                return true;
+            }
+            catch (HttpListenerException ex) when (ex.ErrorCode == 5) // 拒绝访问
+            {
+                if (_ip == "+" || _ip == "*")
+                {
+                    OnLog?.Invoke("需要注册URL保留,正在尝试...", LogLevel.info);
+                    try
+                    {
+                        RegisterUrlReservation();
+
+                        // 重试启动
+                        _listener.Start();
+                        OnLog?.Invoke($"服务器在端口 {_port} 启动成功", LogLevel.info);
+                        ThreadPool.QueueUserWorkItem(Listen);
+                        return true;
+                    }
+                    catch (Exception innerEx)
+                    {
+                        OnLog?.Invoke($"无法启动服务器: {innerEx.Message}", LogLevel.info);
+                        OnLogShow?.Invoke("无法启动服务器, 请尝试使用管理员模式启动");
+                        return false;
+                        throw;
+                    }
+                }
+                else
+                {
+                    OnLogShow?.Invoke("无法注册服务, 修改配置文件后再次启动");
+                    return false;
+                    throw;
+                }
+            }
+        }
+
+        public void Stop()
+        {
+            _isRunning = false;
+            _listener.Stop();
+        }
+
+        private void Listen(object state)
+        {
+            while (_isRunning)
+            {
+                try
+                {
+                    var context = _listener.GetContext();
+                    ThreadPool.QueueUserWorkItem(ProcessRequest, context);
+                }
+                catch (Exception ex)
+                {
+                    if (_isRunning) OnLog?.Invoke($"监听端口失败 Error: {ex.Message}", LogLevel.info);
+                }
+            }
+        }
+
+        private void RegisterUrlReservation()
+        {
+            try
+            {
+                string command = $"http add urlacl url=http://+:{_port}/ user=Everyone";
+
+                var process = new System.Diagnostics.Process
+                {
+                    StartInfo = new System.Diagnostics.ProcessStartInfo
+                    {
+                        FileName = "netsh",
+                        Arguments = command,
+                        Verb = "runas", // 以管理员身份运行
+                        UseShellExecute = true,
+                        WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden
+                    }
+                };
+
+                process.Start();
+                process.WaitForExit(5000); // 最多等待5秒
+
+                if (process.ExitCode == 0)
+                {
+                    OnLog?.Invoke("URL保留注册成功", LogLevel.info);
+                }
+                else
+                {
+                    throw new Exception($"netsh命令失败,退出代码: {process.ExitCode}");
+                }
+            }
+            catch (Exception ex)
+            {
+                throw new Exception($"注册URL保留失败: {ex.Message}");
+            }
+        }
+
+        // 获取本机所有IPv4地址
+        public List<string> GetLocalIPv4Addresses()
+        {
+            var ipAddresses = new List<string>();
+
+            try
+            {
+                // 获取所有网络接口
+                NetworkInterface[] interfaces = NetworkInterface.GetAllNetworkInterfaces();
+
+                foreach (NetworkInterface iface in interfaces)
+                {
+                    // 跳过非活动接口和回环接口
+                    if (iface.OperationalStatus != OperationalStatus.Up ||
+                        iface.NetworkInterfaceType == NetworkInterfaceType.Loopback)
+                        continue;
+
+                    // 获取IP属性
+                    IPInterfaceProperties properties = iface.GetIPProperties();
+
+                    // 获取IPv4地址
+                    foreach (UnicastIPAddressInformation ip in properties.UnicastAddresses)
+                    {
+                        if (ip.Address.AddressFamily == AddressFamily.InterNetwork)
+                        {
+                            ipAddresses.Add(ip.Address.ToString());
+                        }
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                OnLog?.Invoke($"获取IP地址失败: {ex.Message}", LogLevel.info);
+            }
+
+            return ipAddresses;
+        }
+
+        private void ProcessRequest(object state)
+        {
+            var context = (HttpListenerContext)state;
+            try
+            {
+                switch (context.Request.HttpMethod)
+                {
+                    case "POST":
+                        HandleImageUpload(context);
+                        break;
+                    case "GET":
+                        HandleGetRequest(context);
+                        break;
+                    default:
+                        SendResponse(context, 405, "Method Not Allowed");
+                        break;
+                }
+            }
+            catch (Exception ex)
+            {
+                OnLog?.Invoke($"Processing error: {ex}", LogLevel.info);
+                SendResponse(context, 500, $"Internal Server Error: {ex.Message}");
+            }
+        }
+
+        private void HandleImageUpload(HttpListenerContext context)
+        {
+            try
+            {
+                // 获取内容类型
+                string contentType = context.Request.ContentType;
+                if (string.IsNullOrEmpty(contentType) || !contentType.Contains("multipart/form-data"))
+                {
+                    SendResponse(context, 400, "Content-Type must be multipart/form-data");
+                    return;
+                }
+
+                // 从Content-Type中提取boundary
+                string boundary = GetBoundaryFromContentType(contentType);
+                if (string.IsNullOrEmpty(boundary))
+                {
+                    SendResponse(context, 400, "Invalid boundary in Content-Type");
+                    return;
+                }
+
+                // 读取整个请求体
+                byte[] requestBody;
+                using (var ms = new MemoryStream())
+                {
+                    context.Request.InputStream.CopyTo(ms);
+                    requestBody = ms.ToArray();
+                }
+
+                // 解析multipart数据
+                byte[] imageData = ExtractImageFromMultipart(requestBody, boundary);
+                if (imageData == null)
+                {
+                    SendResponse(context, 400, "Failed to extract image from multipart data");
+                    return;
+                }
+
+                // 验证图片数据
+                if (imageData.Length == 0)
+                {
+                    SendResponse(context, 400, "Empty image data");
+                    return;
+                }
+
+                Directory.CreateDirectory("upload");
+                // 保存原始数据用于调试
+                File.WriteAllBytes($"upload/upload_{DateTime.Now:yyyyMMddHHmmss}.jpg", imageData);
+
+                // 计算清晰度
+                var (score, isValid) = CalculateSharpness(imageData);
+                if (!isValid)
+                {
+                    SendResponse(context, 400, "Unsupported image format or corrupted data");
+                    return;
+                }
+                OnLog?.Invoke($"img score:{score}", LogLevel.info);
+                // 返回结果
+                string response = $"{{\"sharpness_score\": {score}}}";
+                SendResponse(context, 200, response, "application/json");
+            }
+            catch (Exception ex)
+            {
+                OnLog?.Invoke($"Upload processing error: {ex}", LogLevel.error);
+                SendResponse(context, 500, $"Internal server error: {ex.Message}");
+            }
+        }
+        private string GetBoundaryFromContentType(string contentType)
+        {
+            // 查找boundary参数
+            int boundaryIndex = contentType.IndexOf("boundary=", StringComparison.OrdinalIgnoreCase);
+            if (boundaryIndex == -1) return null;
+
+            // 提取boundary值
+            string boundaryValue = contentType.Substring(boundaryIndex + "boundary=".Length).Trim();
+
+            // 处理带引号的boundary
+            if (boundaryValue.StartsWith("\"") && boundaryValue.EndsWith("\""))
+            {
+                boundaryValue = boundaryValue.Substring(1, boundaryValue.Length - 2);
+            }
+
+            return boundaryValue;
+        }
+
+        private byte[] ExtractImageFromMultipart(byte[] requestBody, string boundary)
+        {
+            // 将boundary转换为字节数组
+            byte[] boundaryBytes = Encoding.ASCII.GetBytes($"--{boundary}");
+
+            // 查找第一个boundary的位置
+            int startIndex = FindSequence(requestBody, boundaryBytes, 0);
+            if (startIndex == -1) return null;
+            startIndex += boundaryBytes.Length;
+
+            // 查找第二个boundary(文件部分结束的位置)
+            int endIndex = FindSequence(requestBody, boundaryBytes, startIndex);
+            if (endIndex == -1) return null;
+
+            // 查找头部结束位置(两个CRLF)
+            byte[] headerEndPattern = new byte[] { 0x0D, 0x0A, 0x0D, 0x0A }; // \r\n\r\n
+            int headerEndIndex = FindSequence(requestBody, headerEndPattern, startIndex);
+            if (headerEndIndex == -1) return null;
+
+            headerEndIndex += headerEndPattern.Length;
+
+            // 提取图片数据(从headerEndIndex到endIndex,减去末尾的CRLF)
+            int dataLength = endIndex - headerEndIndex - 2; // 减去末尾的CRLF
+            if (dataLength <= 0) return null;
+
+            byte[] imageData = new byte[dataLength];
+            Buffer.BlockCopy(requestBody, headerEndIndex, imageData, 0, dataLength);
+
+            return imageData;
+        }
+
+        private int FindSequence(byte[] source, byte[] pattern, int startIndex)
+        {
+            for (int i = startIndex; i <= source.Length - pattern.Length; i++)
+            {
+                bool found = true;
+                for (int j = 0; j < pattern.Length; j++)
+                {
+                    if (source[i + j] != pattern[j])
+                    {
+                        found = false;
+                        break;
+                    }
+                }
+                if (found)
+                    return i;
+            }
+            return -1;
+        }
+        private (double score, bool isValid) CalculateSharpness(byte[] imageData)
+        {
+            try
+            {
+                // 使用 System.Drawing 二次验证
+                try
+                {
+                    using (var ms = new MemoryStream(imageData))
+                    using (var img = Image.FromStream(ms))
+                    {
+                        // 图片验证通过
+                    }
+                }
+                catch (Exception ex)
+                {
+                    OnLog?.Invoke($"System.Drawing validation failed: {ex}", LogLevel.info);
+                    return (-1, false);
+                }
+
+                // OpenCV 处理
+                using (var img = new Mat())
+                {
+                    CvInvoke.Imdecode(imageData, ImreadModes.Grayscale, img);
+
+                    if (img.IsEmpty)
+                    {
+                        OnLog?.Invoke("OpenCV failed to decode image", LogLevel.info);
+                        return (-1, false);
+                    }
+
+                    // 使用拉普拉斯算子计算清晰度
+                    using (var laplacian = new Mat())
+                    {
+                        CvInvoke.Laplacian(img, laplacian, DepthType.Cv64F);
+
+                        MCvScalar mean = new MCvScalar();
+                        MCvScalar stdDev = new MCvScalar();
+                        CvInvoke.MeanStdDev(laplacian, ref mean, ref stdDev);
+
+                        return (stdDev.V0 * stdDev.V0, true);
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                OnLog?.Invoke($"CV processing error: {ex}", LogLevel.error);
+                return (-1, false);
+            }
+        }
+
+        private bool IsValidImageData(byte[] data)
+        {
+            if (data.Length < 4) return false;
+
+            // JPEG
+            if (data[0] == 0xFF && data[1] == 0xD8 && data[2] == 0xFF)
+                return true;
+
+            // PNG
+            if (data[0] == 0x89 && data[1] == 0x50 && data[2] == 0x4E && data[3] == 0x47)
+                return true;
+
+            // BMP
+            if (data[0] == 0x42 && data[1] == 0x4D)
+                return true;
+
+            // WebP
+            if (data.Length > 12 &&
+                data[0] == 0x52 && data[1] == 0x49 && data[2] == 0x46 && data[3] == 0x46 &&
+                data[8] == 0x57 && data[9] == 0x45 && data[10] == 0x42 && data[11] == 0x50)
+                return true;
+
+            // TIFF (little endian)
+            if (data[0] == 0x49 && data[1] == 0x49 && data[2] == 0x2A && data[3] == 0x00)
+                return true;
+
+            // TIFF (big endian)
+            if (data[0] == 0x4D && data[1] == 0x4D && data[2] == 0x00 && data[3] == 0x2A)
+                return true;
+
+            return false;
+        }
+
+        private byte[] ConvertToJpeg(byte[] imageData)
+        {
+            try
+            {
+                using (var msIn = new MemoryStream(imageData))
+                using (var image = Image.FromStream(msIn))
+                using (var msOut = new MemoryStream())
+                {
+                    // 转换为 JPEG 格式,质量为 95%
+                    var encoderParams = new EncoderParameters(1);
+                    encoderParams.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 95L);
+
+                    var jpegEncoder = ImageCodecInfo.GetImageEncoders()
+                        .FirstOrDefault(e => e.FormatID == ImageFormat.Jpeg.Guid);
+
+                    image.Save(msOut, jpegEncoder, encoderParams);
+                    return msOut.ToArray();
+                }
+            }
+            catch
+            {
+                // 转换失败返回原始数据
+                return imageData;
+            }
+        }
+
+
+        private void HandleGetRequest(HttpListenerContext context)
+        {
+            string html = @"<!DOCTYPE html>
+<html>
+<head><title>Image Sharpness Test</title></head>
+<body>
+    <h1>Upload Image for Sharpness Evaluation</h1>
+    <form action='/' method='post' enctype='multipart/form-data'>
+        <input type='file' name='image' accept='image/*' required>
+        <button type='submit'>Evaluate</button>
+    </form>
+</body>
+</html>";
+
+            SendResponse(context, 200, html, "text/html");
+        }
+
+
+        private void SendResponse(HttpListenerContext context, int statusCode, string message, string contentType = "text/plain")
+        {
+            byte[] buffer = Encoding.UTF8.GetBytes(message);
+
+            context.Response.StatusCode = statusCode;
+            context.Response.ContentType = contentType;
+            context.Response.ContentLength64 = buffer.Length;
+
+            context.Response.OutputStream.Write(buffer, 0, buffer.Length);
+            context.Response.Close();
+        }
+    }
+}

+ 15 - 1
bird_tool/bird_tool.Designer.cs

@@ -44,6 +44,7 @@ namespace Bird_tool
             this.tableLayoutPanel.SuspendLayout();
             this.SuspendLayout();
             this.topPanel = new System.Windows.Forms.Panel();
+            this.cmbIPAddresses = new System.Windows.Forms.ComboBox();
             // 
             // el_serialList
             // 
@@ -172,8 +173,9 @@ namespace Bird_tool
             this.panelWifi.Controls.Add(this.lbl_password);
             this.panelWifi.Controls.Add(this.txt_password);
             this.panelWifi.Controls.Add(this.btn_connect_wifi);
+            this.panelWifi.Controls.Add(this.cmbIPAddresses);
             this.panelWifi.Location = new System.Drawing.Point(360, 10); // 移到串口控件右侧
-            this.panelWifi.Size = new System.Drawing.Size(550, 40); // 单行高度
+            this.panelWifi.Size = new System.Drawing.Size(650, 40); // 单行高度
             this.panelWifi.TabIndex = 3;
             
             // lbl_ssid
@@ -221,6 +223,14 @@ namespace Bird_tool
             this.btn_connect_wifi.UseVisualStyleBackColor = false;
             this.btn_connect_wifi.Click += new System.EventHandler(this.Evt_set_wifi);
 
+            
+            this.cmbIPAddresses.BackColor = System.Drawing.SystemColors.Control;
+            this.cmbIPAddresses.DropDownStyle = ComboBoxStyle.DropDownList;
+            this.cmbIPAddresses.Font = new System.Drawing.Font("宋体", 9F);
+            this.cmbIPAddresses.Location = new System.Drawing.Point(470, 9);
+            this.cmbIPAddresses.Size = new System.Drawing.Size(120, 30);
+            this.cmbIPAddresses.TabIndex = 5;
+
             // 调整串口控件位置
             this.el_serialList.Location = new System.Drawing.Point(13, 15);
             this.el_btn_refresh.Location = new System.Drawing.Point(197, 14);
@@ -281,6 +291,10 @@ namespace Bird_tool
         private System.Windows.Forms.TextBox txt_ssid;
         private System.Windows.Forms.Label lbl_password;
         private System.Windows.Forms.TextBox txt_password;
+
+        // ip地址面板
+        private System.Windows.Forms.ComboBox cmbIPAddresses;
+
         System.Windows.Forms.Button btn_connect_wifi;
     }
 }

+ 137 - 6
bird_tool/bird_tool.cs

@@ -44,11 +44,13 @@ namespace Bird_tool
     {
         private SerialManager _serialManager = new SerialManager();//定义串口
         private TestExecutor _testExecutor;
+        private ImageSharpnessServer _imageSharpnessServer;
 
         private BindingList<string> _portList = new BindingList<string>();
         private string _lastSelectedPort = ""; // 保存上次选择的串口
 
         // 是否加载了配置
+        private AppConfig appConfig = ConfigManager.LoadConfig();
         private bool flag_load_config = false;
         // 是否选择串口
         private bool flag_selected_serial = false;
@@ -61,6 +63,7 @@ namespace Bird_tool
         private string wifi_password = "";
         private string wifi_act = "hfyswj100";
         private string sd_act = "hfyswj188";
+        private string debug_passwd = "hfyswj100";
 
 
         public bird_tool()
@@ -69,7 +72,10 @@ namespace Bird_tool
             //serialTimer.Interval = 1000;
             //serialTimer.Enabled = true;
         }
-
+        public void change_log_level(LogLevel logLevel)
+        {
+            show_log_level = logLevel;
+        }
         static public void Log(string message, LogLevel level = LogLevel.info)
         {
             string logStr = "";
@@ -134,12 +140,23 @@ namespace Bird_tool
                 Height = 40
             };
 
+            Button cleanButton = new Button
+            {
+                Text = "清除日志",
+                Size = new Size(100, 30),
+                Location = new Point(10, 5)
+            };
+            cleanButton.Click += (s, e) =>
+            {
+                logBuilder.Clear();
+                logBox.Clear();
+            };
             // 复制按钮
             Button copyButton = new Button
             {
                 Text = "复制日志",
                 Size = new Size(100, 30),
-                Location = new Point(10, 5)
+                Location = new Point(120, 5)
             };
             copyButton.Click += (s, e) =>
             {
@@ -152,7 +169,7 @@ namespace Bird_tool
             {
                 Text = "保存日志",
                 Size = new Size(100, 30),
-                Location = new Point(120, 5)
+                Location = new Point(230, 5)
             };
             saveButton.Click += (s, e) =>
             {
@@ -169,18 +186,86 @@ namespace Bird_tool
                 }
             };
 
+            Button changeLogLevelButton = new Button
+            {
+                Text = show_log_level == LogLevel.debug ? "关闭调试" : "启用调试",
+                Size = new Size(100, 30),
+                Location = new Point(340, 5)
+            };
+
             // 关闭按钮
             Button closeButton = new Button
             {
                 Text = "关闭",
                 Size = new Size(100, 30),
-                Location = new Point(230, 5)
+                Location = new Point(450, 5)
             };
             closeButton.Click += (s, e) => logForm.Close();
+            // 日志级别切换事件
+            changeLogLevelButton.Click += (s, e) =>
+            {
+                bool enableDebug = show_log_level == LogLevel.debug;
+
+                if (enableDebug)
+                {
+                    // 关闭调试模式
+                    show_log_level = LogLevel.info;
+                    changeLogLevelButton.Text = "启用调试";
+                    logBox.Text = logBuilder.ToString(); // 刷新日志显示
+                }
+                else
+                {
+                    // 创建密码输入对话框
+                    using (Form pwdForm = new Form())
+                    {
+                        pwdForm.Text = "密码验证";
+                        pwdForm.Size = new Size(300, 150);
+                        pwdForm.FormBorderStyle = FormBorderStyle.FixedDialog;
+                        pwdForm.StartPosition = FormStartPosition.CenterParent;
+                        pwdForm.MaximizeBox = false;
+                        pwdForm.MinimizeBox = false;
+
+                        Label lbl = new Label { Text = "请输入调试密码:", AutoSize = true, Location = new Point(20, 20) };
+                        TextBox txtPwd = new TextBox
+                        {
+                            Location = new Point(20, 50),
+                            Size = new Size(240, 20),
+                            PasswordChar = '*'
+                        };
+
+                        Button btnOk = new Button
+                        {
+                            Text = "确定",
+                            DialogResult = DialogResult.OK,
+                            Location = new Point(80, 80)
+                        };
+
+                        pwdForm.Controls.AddRange(new Control[] { lbl, txtPwd, btnOk });
+                        pwdForm.AcceptButton = btnOk;
+
+                        if (pwdForm.ShowDialog(logForm) == DialogResult.OK)
+                        {
+                            if (txtPwd.Text == debug_passwd) // 验证密码
+                            {
+                                show_log_level = LogLevel.debug;
+                                changeLogLevelButton.Text = "关闭调试";
+                                logBox.Text = logBuilder.ToString(); // 刷新日志显示
+                            }
+                            else
+                            {
+                                MessageBox.Show("密码错误,调试模式未开启", "验证失败",
+                                    MessageBoxButtons.OK, MessageBoxIcon.Warning);
+                            }
+                        }
+                    }
+                }
+            };
 
             // 组装按钮面板
+            buttonPanel.Controls.Add(cleanButton);
             buttonPanel.Controls.Add(copyButton);
             buttonPanel.Controls.Add(saveButton);
+            buttonPanel.Controls.Add(changeLogLevelButton);
             buttonPanel.Controls.Add(closeButton);
 
             // 组装日志窗体
@@ -192,10 +277,33 @@ namespace Bird_tool
 
         private void Tool_load(object sender, EventArgs e)
         {
+            load_app_config();
             _uart_ui_change(false);
             SerialPort_Load();
+            
+            _imageSharpnessServer = new ImageSharpnessServer("*", appConfig.web_port);
+            _ip_ui_change();
+            if (appConfig.enable_local_server)
+            {
+                if (_imageSharpnessServer.Start())
+                {
+                    Log_show($"网络服务已经启动, localhost:{appConfig.web_port}");
+                }
+                else
+                {
+                    Log_show($"网络服务启动失败, 请用管理员模式启动或者更换端口");
+                }
+            }
             //_serialManager.OnLineReceived += _handle_received_line;
         }
+        private void load_app_config()
+        {
+            appConfig = ConfigManager.LoadConfig();
+            wifi_ssid = appConfig.wifi_ssid;
+            wifi_password = appConfig.wifi_passwd;
+            txt_ssid.Text = wifi_ssid;
+            txt_password.Text = wifi_password;
+        }
 
         private void SerialPort_Load()
         {
@@ -356,6 +464,10 @@ namespace Bird_tool
             wifi_ssid = ssid;
             wifi_password = password;
             SetWifi = true;
+            appConfig.wifi_ssid = wifi_ssid;
+            appConfig.wifi_passwd = wifi_password;
+
+            ConfigManager.SaveConfig(appConfig);
         }
 
         private void _SetTestButtonsEnabled(bool enabled)
@@ -427,7 +539,25 @@ namespace Bird_tool
             _SetTestButtonsEnabled(uart_flag);
         }
 
+        private void _ip_ui_change()
+        {
+            cmbIPAddresses.Items.Clear();
+            if (!appConfig.enable_local_server)
+            {
+                cmbIPAddresses.Items.Add($"{appConfig.api_address}");
+                return;
+            }
+            List<string> ipInfo = _imageSharpnessServer.GetLocalIPv4Addresses();
+            foreach (string ip in ipInfo)
+            {
+                cmbIPAddresses.Items.Add($"{ip}:{appConfig.web_port}");
+            }
 
+            if (cmbIPAddresses.Items.Count > 0)
+            {
+                cmbIPAddresses.SelectedIndex = 0;
+            }
+        }
 
         private void PromptUser(string question, Action onYes, Action onNo, int waitTime = -1)
         {
@@ -780,7 +910,7 @@ namespace Bird_tool
                     GroupId = "btn_long",
                     GroupName = "按钮测试",
                     Name = "长按测试1",
-                    Tips = "请长按电源按钮 5秒后再次长按 第一次",
+                    Tips = "请长按电源按钮 6秒后再次长按 第一次",
                     Command = "\r\n",
                     SuccessPattern = "sys off",
                     Timeout = 20000,
@@ -804,7 +934,7 @@ namespace Bird_tool
                 {
                     GroupId = "btn_long",
                     Name = "双击测试1",
-                    Tips = "请双击电源按钮 第一次",
+                    Tips = "听到开机声音后, 请双击电源按钮 第一次",
                     Command = "\r\n",
                     SuccessPattern = "_msg_dbclick_callback",
                     Timeout = 20000,
@@ -1191,6 +1321,7 @@ namespace Bird_tool
                     DelayBefore = 500,
                     MaxRetries = 5,
                 },
+                
                 // 上传测试
 
 

+ 8 - 0
bird_tool/bird_tool.csproj

@@ -43,6 +43,8 @@
     <Compile Include="bird_tool.Designer.cs">
       <DependentUpon>bird_tool.cs</DependentUpon>
     </Compile>
+    <Compile Include="Config.cs" />
+    <Compile Include="ImageServer.cs" />
     <Compile Include="Program.cs" />
     <Compile Include="ProgressPanel.cs">
       <SubType>UserControl</SubType>
@@ -90,6 +92,9 @@
     <PackageReference Include="Emgu.CV.runtime.windows">
       <Version>4.10.0.5680</Version>
     </PackageReference>
+    <PackageReference Include="HttpMultipartParser">
+      <Version>5.1.0</Version>
+    </PackageReference>
     <PackageReference Include="Microsoft.Bcl.AsyncInterfaces">
       <Version>9.0.0</Version>
     </PackageReference>
@@ -105,6 +110,9 @@
     <PackageReference Include="NAudio.WaveFormRenderer">
       <Version>2.0.0</Version>
     </PackageReference>
+    <PackageReference Include="Newtonsoft.Json">
+      <Version>13.0.3</Version>
+    </PackageReference>
     <PackageReference Include="OpenCV">
       <Version>2.4.11</Version>
     </PackageReference>