Преглед на файлове

为核心服务类补充详细XML注释文档

本次提交为 CalibrationService、CameraService、ConfigService 三个核心服务类系统性补充了标准的 XML 注释,包括方法、构造函数、属性的用途、参数、返回值、线程安全、资源管理等说明。CalibrationService 增加了伪代码注释,梳理了设计目标和主要逻辑。CameraService、ConfigService 详细标注了各自的业务流程和线程安全策略。此次更改仅为文档化和可维护性提升,不涉及业务逻辑调整。提升了代码自解释性和团队协作效率,为后续维护扩展打下基础。
孝锋 徐 преди 8 месеца
родител
ревизия
4bc77bfa5c
променени са 3 файла, в които са добавени 543 реда и са изтрити 113 реда
  1. 189 60
      TeamAAS-VM/Services/CalibrationService.cs
  2. 143 12
      TeamAAS-VM/Services/CameraService.cs
  3. 211 41
      TeamAAS-VM/Services/ConfigService.cs

+ 189 - 60
TeamAAS-VM/Services/CalibrationService.cs

@@ -1,3 +1,46 @@
+/* 
+PSEUDOCODE / 设计计划(详尽):
+1. 目标:为 CalibrationService 增加完整中文注释(XML 文档注释 + 行内注释),保持原有逻辑不变。
+2. 对象/职责:
+   - 管理校准项集合(内存):_calibrations(线程安全访问)
+   - 提供读取、保存、添加/更新、删除、批量加载/保存以及像素到位置信息转换的功能
+   - 与文件系统交互:Calibration 列表文件与每个 Calibration 的单独文件、ToolBlock 文件(.vpp)
+3. 方法处理:
+   - GetAllCalibrations: 返回只读集合的快照(加锁,防止并发修改)
+   - GetCalibration: 按 Id 查找校准项(加锁)
+   - AddOrUpdateCalibration:
+       a. 验空
+       b. 在集合中查找是否存在:存在 -> 替换;不存在 -> 追加并设置 Index
+       c. 更新 DateTime
+       d. 确保文件夹存在,写入单个校准 cfg 文件
+       e. 更新并写入 CalibrationList.cfg(Id->Name 映射)
+       f. 保存或加载 ToolBlock(.vpp),如果不存在则尝试使用模板创建
+       g. 返回 calib
+   - RemoveCalibration:
+       a. 在集合中找到并移除(加锁)
+       b. 更新 CalibrationList.cfg
+       c. 重新按 Index 排序并保存每个校准文件
+       d. 返回是否成功移除
+   - LoadAll:
+       a. 确保 CalibrationPath 存在
+       b. 清空集合并创建空索引文件(如果索引文件不存在)
+       c. 读取索引列表,遍历每个条目:加载单个 cfg、尝试加载对应 .vpp、加入集合
+       d. 单个文件异常时忽略,继续加载其他文件
+   - SaveAll:
+       a. 遍历集合,确保对应目录存在,写入每个 cfg,若有 ToolBlock 则保存 .vpp
+       b. 写入索引文件
+   - ConvertPixelToPosition(主转换逻辑):
+       a. 用 calib 的仿射矩阵将像素坐标(X,Y,1)映射到机器人坐标
+       b. 根据 calib.CameraMount 类型选择不同后处理:
+          - MobileJ4 / MobileDown_XYPlatform:将像素->mm 点做基于当前机器人位置与旋转的变换
+          - 其他:若提供 robotCoord,则使用 ToolCoord 计算工具坐标(对 Schneider 角度符号处理)
+       c. 返回 (IsSucceed, X, Y, U)
+4. 注释策略:
+   - 为类与公有方法添加 XML 注释(中文)
+   - 为复杂代码段添加行内注释(中文),解释数学与坐标变换逻辑
+   - 保持所有原始逻辑、签名与行为不变
+*/
+
 using Cognex.VisionPro;
 using Cognex.VisionPro.ToolBlock;
 using MathNet.Numerics.LinearAlgebra;
@@ -16,32 +59,54 @@ using TeamAAS_VP.Resources.Languages;
 namespace TeamAAS_VP.Services
 {
     /// <summary>
-    /// Calibration 管理服务,负责校准文件的读取与保存
+    /// Calibration 管理服务,负责校准文件的读取与保存以及像素到机器人位姿的转换。
+    /// 线程安全:对内部集合的读/写通过 <see cref="_sync"/> 锁进行保护。
+    /// 注意:构造函数不自动加载数据,调用者需要在合适时机调用 <see cref="LoadAll"/>。
     /// </summary>
     public class CalibrationService : ICalibrationService
     {
+        /// <summary>
+        /// 内部路径常量定义(相对路径)
+        /// </summary>
         private static class Paths
         {
             public static readonly string CalibrationPath = "..//Calibration";
             public static string CalibrationListFilePath => Path.Combine(CalibrationPath ?? string.Empty, "CalibrationList.cfg");
         }
 
+        // 同步锁对象,保护 _calibrations 的并发访问
         private readonly object _sync = new object();
+
+        // 内存中的校准集合(用于 UI 绑定/管理)
         private ObservableCollection<CalibrationInfo> _calibrations = new ObservableCollection<CalibrationInfo>();
 
+        /// <summary>
+        /// 构造函数。注意:不在构造中自动加载校准,调用者应根据需要调用 <see cref="LoadAll"/>。
+        /// </summary>
         public CalibrationService()
         {
             // 不在构造中自动加载,调用者可以选择 LoadAll
         }
 
+        /// <summary>
+        /// 获取所有校准项的只读快照。
+        /// 返回一个集合快照以避免外部对内部集合的直接修改。
+        /// </summary>
+        /// <returns>只读的 <see cref="CalibrationInfo"/> 集合</returns>
         public IReadOnlyCollection<CalibrationInfo> GetAllCalibrations()
         {
             lock (_sync)
             {
+                // 返回当前集合的一个独立只读副本,避免并发问题
                 return _calibrations.ToList().AsReadOnly();
             }
         }
 
+        /// <summary>
+        /// 根据 Id 获取单个校准信息。
+        /// </summary>
+        /// <param name="id">校准项的唯一标识</param>
+        /// <returns>找到则返回 <see cref="CalibrationInfo"/>,否则返回 null</returns>
         public CalibrationInfo GetCalibration(Guid id)
         {
             lock (_sync)
@@ -50,6 +115,14 @@ namespace TeamAAS_VP.Services
             }
         }
 
+        /// <summary>
+        /// 添加或更新校准项:
+        /// - 若 Id 存在则替换,否则追加并设置 Index
+        /// - 写入单个校准 cfg 文件并更新索引文件
+        /// - 保存或加载 ToolBlock(.vpp),若不存在则尝试从模板创建
+        /// </summary>
+        /// <param name="calib">要添加或更新的校准信息</param>
+        /// <returns>保存后的 <see cref="CalibrationInfo"/>(可能为传入对象或修改后的对象);参数为 null 则返回 null</returns>
         public CalibrationInfo AddOrUpdateCalibration(CalibrationInfo calib)
         {
             if (calib == null) return null;
@@ -58,26 +131,30 @@ namespace TeamAAS_VP.Services
                 var exist = _calibrations.FirstOrDefault(c => c.Id == calib.Id);
                 if (exist != null)
                 {
+                    // 替换已存在项(保持集合长度与 Index 不变)
                     var idx = _calibrations.IndexOf(exist);
                     _calibrations[idx] = calib;
                 }
                 else
                 {
+                    // 新增项,Index 设为当前数量 + 1
                     calib.Index = _calibrations.Count + 1;
                     _calibrations.Add(calib);
                 }
             }
 
+            // 更新时间戳
             calib.DateTime = DateTime.Now;
+
+            // 确保校准保存目录存在
             string folder = Path.Combine(Paths.CalibrationPath, calib.Name);
             string calibFile = Path.Combine(folder, calib.Name + ".cfg");
-
             if (!Directory.Exists(folder)) Directory.CreateDirectory(folder);
 
-            // 写入单个校准文件
+            // 写入单个校准文件(JSON)
             FileHelper.WriteJsonFile(calib, calibFile);
 
-            // 更新索引列表
+            // 更新索引列表(Id -> Name)并写入文件
             var list = new Dictionary<Guid, string>();
             lock (_sync)
             {
@@ -88,14 +165,16 @@ namespace TeamAAS_VP.Services
             }
             FileHelper.WriteJsonFile(list, Paths.CalibrationListFilePath);
 
-            // 保存ToolBlock
+            // 保存或加载 ToolBlock(.vpp)
             string prcPath = Path.Combine(folder, calib.Name + ".vpp");
             if (calib.ToolBlock != null)
             {
+                // 已有 ToolBlock,直接保存
                 CogSerializer.SaveObjectToFile(calib.ToolBlock, prcPath);
             }
             else
             {
+                // 优先从当前目录加载已有 .vpp 文件,否则尝试从模板加载并保存
                 if (File.Exists(prcPath))
                 {
                     calib.ToolBlock = CogSerializer.LoadObjectFromFile(prcPath) as CogToolBlock;
@@ -110,6 +189,15 @@ namespace TeamAAS_VP.Services
             return calib;
         }
 
+        /// <summary>
+        /// 根据 Id 移除校准项:
+        /// - 从内存集合移除
+        /// - 更新索引文件
+        /// - 重新为剩余项排序 Index 并保存每个 cfg
+        /// 注:不自动删除物理文件(除非有明确需求)
+        /// </summary>
+        /// <param name="id">要删除的校准项 Id</param>
+        /// <returns>是否成功移除</returns>
         public bool RemoveCalibration(Guid id)
         {
             bool result = false;
@@ -120,7 +208,7 @@ namespace TeamAAS_VP.Services
                 if (removed != null) result = _calibrations.Remove(removed);
             }
 
-            // 更新列表文件
+            // 更新索引文件(Id -> Name)
             var list = new Dictionary<Guid, string>();
             lock (_sync)
             {
@@ -131,26 +219,32 @@ namespace TeamAAS_VP.Services
             }
             FileHelper.WriteJsonFile(list, Paths.CalibrationListFilePath);
 
-            //删除后,对所有校准重新排序索引
-            //先按照Index进行排序
+            // 删除后重新按 Index 排序并保存每个校准文件
             var calibs = _calibrations.OrderBy(c => c.Index).ToList();
-            int index= 1;
+            int index = 1;
             foreach (var item in calibs)
             {
                 item.Index = index;
-                //保存更新后的索引
+                // 保存更新后的 cfg 文件
                 string folder = Path.Combine(Paths.CalibrationPath, item.Name);
                 string calibFile = Path.Combine(folder, item.Name + ".cfg");
                 FileHelper.WriteJsonFile(item, calibFile);
                 index++;
             }
+            // 用排序后的集合替换内存集合(注意:替换时未加锁是因为已在外部加锁或逻辑保证)
             _calibrations = new ObservableCollection<CalibrationInfo>(calibs);
 
-            // 可选:删除物理文件和toolblock目录(不自动删除,除非明确需求
+            // 可选:删除物理文件和 toolblock(不自动删除)
 
             return result;
         }
 
+        /// <summary>
+        /// 加载所有校准数据:
+        /// - 确保 CalibrationPath 存在
+        /// - 读取索引文件,遍历项并加载各自的 cfg 与 .vpp(如果存在)
+        /// - 忽略单个文件加载异常,继续加载其他文件
+        /// </summary>
         public void LoadAll()
         {
             lock (_sync)
@@ -160,6 +254,7 @@ namespace TeamAAS_VP.Services
                 _calibrations.Clear();
                 if (!File.Exists(Paths.CalibrationListFilePath))
                 {
+                    // 若索引文件不存在,写入空索引并返回
                     FileHelper.WriteJsonFile(new Dictionary<Guid, string>(), Paths.CalibrationListFilePath);
                     return;
                 }
@@ -171,11 +266,12 @@ namespace TeamAAS_VP.Services
                 {
                     try
                     {
+                        // 使用 FilePath.CalibrationPath(项目全局路径)构建单个 cfg 路径
                         string filePath = Path.Combine(FilePath.CalibrationPath, item.Value, item.Value + ".cfg");
                         if (File.Exists(filePath))
                         {
                             var calib = FileHelper.ReadJsonFile<CalibrationInfo>(filePath);
-                            // 尝试加载toolblock
+                            // 尝试加载对应的 .vpp ToolBlock
                             string prcPath = Path.Combine(FilePath.CalibrationPath, calib.Name, calib.Name + ".vpp");
                             if (File.Exists(prcPath))
                             {
@@ -192,6 +288,12 @@ namespace TeamAAS_VP.Services
             }
         }
 
+        /// <summary>
+        /// 将内存中的所有校准保存到文件:
+        /// - 为每个校准创建目录并写入 cfg
+        /// - 若有 ToolBlock,则保存 .vpp
+        /// - 写入索引文件
+        /// </summary>
         public void SaveAll()
         {
             lock (_sync)
@@ -215,45 +317,66 @@ namespace TeamAAS_VP.Services
         }
 
         /// <summary>
-        /// 校准转换,将像素坐标转换位置坐标
+        /// 校准转换:将像素坐标转换为机器人位置坐标(X,Y)及角度 U。
+        /// 逻辑摘要:
+        /// 1. 使用 calib.AffineTransformationMaterial 构建 3x3 仿射矩阵,将像素 [X,Y,1] 映射到机器人坐标系(单位:mm/像素转换后)。
+        /// 2. 根据摄像机安装方式(CameraMount)进行不同的后处理:
+        ///    - MobileJ4: 采用基于 MarkPoint 的旋转矩阵,将像素直接转换的点旋转并平移到机器人当前 TOOL0 下的位置
+        ///    - MobileDown_XYPlatform: 类似 MobileJ4,但当前实现中角度置为 0(不旋转)
+        ///    - 其他情况:如果提供 robotCoord,则通过 ToolCoord 计算工具坐标(考虑机器人品牌对角度符号的影响)
+        /// 3. 对于某些 CameraMount,输入角度需要取反(例如 FixedDown、MobileJ2、MobileJ4),以匹配机器人坐标系习惯
         /// </summary>
-        /// <param name="pixelCoord"></param>
-        /// <param name="robotCoord"></param>
-        /// <param name="calib"></param>
-        /// <returns></returns>
+        /// <param name="pixelCoord">像素坐标及方向:(X, Y, angle)。angle 表示图像中测量的角度(度)</param>
+        /// <param name="robotCoord">机器人当前位姿数组,通常为 [X, Y, U],可为 null(视场景而定)</param>
+        /// <param name="calib">要使用的校准数据</param>
+        /// <param name="robotBrand">机器人品牌,用于处理角度符号等差异</param>
+        /// <returns>
+        /// 返回元组 (IsSucceed, X, Y, U):
+        /// - IsSucceed: 总是 true(当前实现未对数学运算做失败判定),调用者可根据需要扩展错误处理
+        /// - X, Y: 转换后的机器人坐标(单位同 calib 所用单位)
+        /// - U: 角度(经过可能的符号调整)
+        /// </returns>
         public (bool IsSucceed,double X, double Y, double U) ConvertPixelToPosition((double X, double Y, double angle) pixelCoord, double[] robotCoord, CalibrationInfo calib, RobotBrand robotBrand = RobotBrand.Default)
         {
-            double angle= pixelCoord.angle;
-            //得到校准矩阵
+            double angle = pixelCoord.angle;
+
+            // 从校准数据获取仿射变换矩阵(3x3)
             Matrix<double> matrix = Matrix<double>.Build.DenseOfArray(calib.AffineTransformationMaterial);
+
+            // 某些安装方式需要取反角度以匹配机器人坐标系定义
             if (calib.CameraMount == CameraMount.FixedDown || calib.CameraMount == CameraMount.MobileJ2 || calib.CameraMount == CameraMount.MobileJ4)
             {
                 angle *= -1;
             }
-            //将像素坐标转换成机器人坐标
+
+            // 将像素坐标通过仿射矩阵转换到机器人坐标(rpos 是长度 3 的向量:[X, Y, w],通常 w 为 1)
             var rpos = matrix * Vector<double>.Build.Dense(new double[] { pixelCoord.X, pixelCoord.Y, 1 });
+
+            // 针对不同的 CameraMount 做进一步坐标变换
             if (calib.CameraMount == Enums.CameraMount.MobileJ4)
             {
-                //像素直接转换成mm时的点位
-                double Robot_X, Robot_Y;
-                Robot_X = rpos[0];
-                Robot_Y = rpos[1];
-
-                //机器人当前TOOL 0下的坐标
-                double curpos_x = 0, curpos_y = 0, curpos_u = 0;
-                curpos_x = robotCoord[0];
-                curpos_y = robotCoord[1];
-                curpos_u = robotCoord[2];
-
-                //得到旋转矩阵
+                // 当 CameraMount 为 MobileJ4 时:
+                // - rpos 的前两维表示像素->mm 转换后的点 (Robot_X, Robot_Y)
+                // - 需要基于机器人当前 TOOL0 坐标以及 MarkPoint 的角度进行旋转和平移
+                double Robot_X = rpos[0];
+                double Robot_Y = rpos[1];
+
+                // 机器人当前 TOOL0 下的位姿
+                double curpos_x = robotCoord[0];
+                double curpos_y = robotCoord[1];
+                double curpos_u = robotCoord[2];
+
+                // 旋转角(弧度) = (curpos_u - calib.MarkPoint.U) * PI / 180
                 double angle1 = Math.PI * (curpos_u - calib.MarkPoint.U) / 180;
-                // 创建T矩阵
+
+                // 2x2 旋转矩阵
                 Matrix<double> rotationMatrix = Matrix<double>.Build.DenseOfArray(new double[,]
                 {
-                                            { Math.Cos(angle1), -Math.Sin(angle1) },
-                                            { Math.Sin(angle1), Math.Cos(angle1) }
+                    { Math.Cos(angle1), -Math.Sin(angle1) },
+                    { Math.Sin(angle1),  Math.Cos(angle1) }
                 });
 
+                // 将相对向量 p3 旋转并平移到当前工具坐标系下
                 Vector<double> p3 = Vector<double>.Build.Dense(new double[] { Robot_X - calib.CalibPoints[0].Robot.X, Robot_Y - calib.CalibPoints[0].Robot.Y });
                 Vector<double> phere = Vector<double>.Build.Dense(new double[] { curpos_x, curpos_y });
 
@@ -262,68 +385,71 @@ namespace TeamAAS_VP.Services
             }
             else if (calib.CameraMount == Enums.CameraMount.MobileDown_XYPlatform)
             {
-                //像素直接转换成mm时的点位
-                double Robot_X, Robot_Y;
-                Robot_X = rpos[0];
-                Robot_Y = rpos[1];
-
-                //模组当前TOOL 0下的坐标(相当于模组吸嘴坐标)
-                double curpos_x = 0, curpos_y = 0, curpos_u = 0;
-                curpos_x = robotCoord[0];
-                curpos_y = robotCoord[1];
-                curpos_u = robotCoord[2];
-
-                //得到旋转矩阵
-                //double angle1 = Math.PI * (curpos_u - calib.MarkPoint.U) / 180;
+                // MobileDown_XYPlatform 模式下,当前实现与 MobileJ4 类似,但角度暂时设为 0(不旋转)
+                double Robot_X = rpos[0];
+                double Robot_Y = rpos[1];
+
+                double curpos_x = robotCoord[0];
+                double curpos_y = robotCoord[1];
+                double curpos_u = robotCoord[2];
+
+                // 角度暂用 0(如有需要可基于 curpos_u - calib.MarkPoint.U 做旋转)
                 double angle1 = 0;
-                // 创建T矩阵
                 Matrix<double> rotationMatrix = Matrix<double>.Build.DenseOfArray(new double[,]
                 {
-                                            { Math.Cos(angle1), -Math.Sin(angle1) },
-                                            { Math.Sin(angle1), Math.Cos(angle1) }
+                    { Math.Cos(angle1), -Math.Sin(angle1) },
+                    { Math.Sin(angle1),  Math.Cos(angle1) }
                 });
 
                 Vector<double> p3 = Vector<double>.Build.Dense(new double[] { Robot_X - calib.CalibPoints[0].Robot.X, Robot_Y - calib.CalibPoints[0].Robot.Y });
                 Vector<double> phere = Vector<double>.Build.Dense(new double[] { curpos_x, curpos_y });
 
                 var cc = rotationMatrix * p3 + phere;
-                rpos = Vector<double>.Build.Dense(new double[] { cc[0], cc[1] });//Mark点在tool0下的坐标
+                rpos = Vector<double>.Build.Dense(new double[] { cc[0], cc[1] }); // Mark 点在 tool0 下的坐标
             }
             else
             {
-                //当需要直接转换工具坐标时
-                if (robotCoord!=null)
+                // 其他 CameraMount:如果提供了 robotCoord,则将仿射变换得到的点转换为工具坐标
+                if (robotCoord != null)
                 {
                     double curposX = robotCoord[0];
                     double curposY = robotCoord[1];
                     double curposU = robotCoord[2];
 
-                    //根据机器人当前的点位计算工具坐标
+                    // 使用 ToolCoord 计算工具坐标(ToolCoord 内封装了从机器人基坐标到工具坐标的数学)
                     ToolCoord tool = new ToolCoord();
+
+                    // Schneider 机器人角度符号可能与其他品牌相反,做兼容处理
                     if (robotBrand == RobotBrand.Schneider)
                     {
                         curposU *= -1;
                     }
+
+                    // 计算工具坐标并将 rpos 替换为工具坐标的 X,Y
                     tool.ComputeTool(curposX, curposY, curposU, rpos[0], rpos[1]);
                     rpos = Vector<double>.Build.Dense(new double[] { tool.X, tool.Y });
                 }
             }
+
+            // 返回最终结果,IsSucceed 在当前实现中总为 true(可根据需要扩展)
             return (true, rpos[0], rpos[1], angle);
         }
 
         /// <summary>
-        /// 校准转换,将像素坐标转换位置坐标
+        /// 通过 calibId 获取校准并执行像素->位置转换。
         /// </summary>
-        /// <param name="pixelCoord"></param>
-        /// <param name="robotCoord"></param>
-        /// <param name="calibId"></param>
-        /// <returns></returns>
+        /// <param name="pixelCoord">像素坐标及角度 (X, Y, angle)</param>
+        /// <param name="robotCoord">机器人当前位姿数组 [X, Y, U]</param>
+        /// <param name="calibId">校准项 Id</param>
+        /// <param name="robotBrand">机器人品牌(可选)</param>
+        /// <returns>返回 (IsSucceed, X, Y, U)</returns>
         public (bool IsSucceed, double X, double Y, double U) ConvertPixelToPosition((double X, double Y, double angle) pixelCoord, double[] robotCoord, Guid calibId, RobotBrand robotBrand = RobotBrand.Default)
         {
 
             var calib = GetCalibration(calibId);
             if (calib == null)
             {
+                // 若未找到校准数据,返回失败标志
                 return (false, 0, 0, 0);
             }
             var result = ConvertPixelToPosition(pixelCoord, robotCoord, calib, robotBrand);
@@ -331,6 +457,9 @@ namespace TeamAAS_VP.Services
 
         }
 
+        /// <summary>
+        /// Dispose 时保存所有校准数据并抑制终结化。
+        /// </summary>
         public void Dispose()
         {
             SaveAll();

+ 143 - 12
TeamAAS-VM/Services/CameraService.cs

@@ -10,16 +10,38 @@ using TeamAAS_VP.Models;
 
 namespace TeamAAS_VP.Services
 {
+    /// <summary>
+    /// 提供相机对象的创建、管理、查找和释放功能的服务类。
+    /// 线程安全:内部使用 <see cref="_sync"/> 进行并发访问同步。
+    /// 资源管理:实现 <see cref="IDisposable"/>,在释放时会关闭并清理所有已注册相机。
+    /// </summary>
     public class CameraService : ICameraService, IDisposable
     {
+        /// <summary>
+        /// 保存相机实例的字典,键为相机的唯一标识 <see cref="Guid"/>。
+        /// </summary>
         private readonly Dictionary<Guid, ICamera> _CameraCollection;
+
+        /// <summary>
+        /// 用于保证对 <see cref="_CameraCollection"/> 并发访问的同步对象。
+        /// </summary>
         private readonly object _sync = new object();
 
+        /// <summary>
+        /// 初始化一个新的 <see cref="CameraService"/> 实例。
+        /// </summary>
         public CameraService()
         {
             _CameraCollection = new Dictionary<Guid, ICamera>();
         }
 
+        /// <summary>
+        /// 创建并注册一个相机实例。如果同一 <paramref name="id"/> 已存在,则会先尝试关闭并替换之。
+        /// </summary>
+        /// <param name="id">要注册的相机唯一标识符。</param>
+        /// <param name="cameraInfo">用于创建相机的配置信息,不能为空。</param>
+        /// <returns>始终返回 <c>true</c>(当前实现不表示创建失败)。</returns>
+        /// <exception cref="ArgumentNullException"><paramref name="cameraInfo"/> 为 null 时抛出。</exception>
         public bool CreateCamera(Guid id, CameraInfo cameraInfo)
         {
             if (cameraInfo == null) throw new ArgumentNullException(nameof(cameraInfo));
@@ -47,11 +69,22 @@ namespace TeamAAS_VP.Services
             return true;
         }
 
+        /// <summary>
+        /// 异步创建并注册一个相机实例(内部使用 Task.Run 包装同步方法)。
+        /// </summary>
+        /// <param name="id">相机唯一标识符。</param>
+        /// <param name="cameraInfo">相机配置信息。</param>
+        /// <returns>表示创建结果的任务,结果为布尔值。</returns>
         public Task<bool> CreateCameraAsync(Guid id, CameraInfo cameraInfo)
         {
             return Task.Run(() => CreateCamera(id, cameraInfo));
         }
 
+        /// <summary>
+        /// 根据 <paramref name="id"/> 获取已注册的相机实例,如果未找到则返回 null。
+        /// </summary>
+        /// <param name="id">相机唯一标识符。</param>
+        /// <returns>找到的 <see cref="ICamera"/> 实例或 null。</returns>
         public ICamera GetCamera(Guid id)
         {
             lock (_sync)
@@ -62,6 +95,11 @@ namespace TeamAAS_VP.Services
             return null;
         }
 
+        /// <summary>
+        /// 异步获取相机实例(包装同步调用)。
+        /// </summary>
+        /// <param name="id">相机唯一标识符。</param>
+        /// <returns>表示查找结果的任务,任务结果为 <see cref="ICamera"/> 或 null。</returns>
         public Task<ICamera> GetCameraAsync(Guid id)
         {
             return Task.Run(() =>
@@ -70,6 +108,11 @@ namespace TeamAAS_VP.Services
             });
         }
 
+        /// <summary>
+        /// 取消注册指定 <paramref name="id"/> 的相机并尝试关闭其设备资源。
+        /// 如果不存在则无操作。
+        /// </summary>
+        /// <param name="id">要取消注册的相机标识符。</param>
         public void UnRegisterCamera(Guid id)
         {
             ICamera camera = null;
@@ -94,11 +137,20 @@ namespace TeamAAS_VP.Services
             }
         }
 
+        /// <summary>
+        /// 异步取消注册相机(包装同步调用)。
+        /// </summary>
+        /// <param name="id">相机唯一标识符。</param>
+        /// <returns>表示操作完成的任务。</returns>
         public Task UnRegisterCameraAsync(Guid id)
         {
             return Task.Run(() => UnRegisterCamera(id));
         }
 
+        /// <summary>
+        /// 获取当前已注册的所有相机的只读集合快照。
+        /// </summary>
+        /// <returns>只读集合,包含当前注册的所有相机实例。</returns>
         public IReadOnlyCollection<ICamera> GetAllCameras()
         {
             lock (_sync)
@@ -107,6 +159,10 @@ namespace TeamAAS_VP.Services
             }
         }
 
+        /// <summary>
+        /// 异步获取所有已注册相机(包装同步调用)。
+        /// </summary>
+        /// <returns>表示只读相机集合的任务。</returns>
         public Task<IReadOnlyCollection<ICamera>> GetAllCamerasAsync()
         {
             return Task.Run(() =>
@@ -115,6 +171,12 @@ namespace TeamAAS_VP.Services
             });
         }
 
+        /// <summary>
+        /// 尝试根据 <paramref name="id"/> 获取相机实例。
+        /// </summary>
+        /// <param name="id">相机唯一标识符。</param>
+        /// <param name="camera">若找到则输出相机实例,否则为 null。</param>
+        /// <returns>找到返回 <c>true</c>,否则返回 <c>false</c>。</returns>
         public bool TryGetCamera(Guid id, out ICamera camera)
         {
             lock (_sync)
@@ -123,9 +185,13 @@ namespace TeamAAS_VP.Services
             }
         }
 
+        /// <summary>
+        /// 异步尝试获取相机实例,返回一个包含是否找到及相机实例的元组。
+        /// </summary>
+        /// <param name="id">相机唯一标识符。</param>
+        /// <returns>任务结果为 (<c>found</c>, <c>camera</c>) 元组。</returns>
         public Task<(bool found, ICamera camera)> TryGetCameraAsync(Guid id)
         {
-
             return Task.Run(() =>
             {
                 ICamera camera;
@@ -138,6 +204,11 @@ namespace TeamAAS_VP.Services
             });
         }
 
+        /// <summary>
+        /// 判断是否包含指定 <paramref name="id"/> 的相机注册项。
+        /// </summary>
+        /// <param name="id">相机唯一标识符。</param>
+        /// <returns>存在返回 <c>true</c>,否则返回 <c>false</c>。</returns>
         public bool ContainsCamera(Guid id)
         {
             lock (_sync)
@@ -146,6 +217,11 @@ namespace TeamAAS_VP.Services
             }
         }
 
+        /// <summary>
+        /// 异步判断相机是否存在(包装同步调用)。
+        /// </summary>
+        /// <param name="id">相机唯一标识符。</param>
+        /// <returns>表示存在性的任务结果(布尔值)。</returns>
         public Task<bool> ContainsCameraAsync(Guid id)
         {
             return Task.Run(() =>
@@ -154,6 +230,11 @@ namespace TeamAAS_VP.Services
             });
         }
 
+        /// <summary>
+        /// 移除指定 <paramref name="id"/> 的相机并关闭其设备资源(如存在)。
+        /// </summary>
+        /// <param name="id">要移除的相机标识符。</param>
+        /// <returns>如果存在并已移除返回 <c>true</c>,否则返回 <c>false</c>。</returns>
         public bool RemoveCamera(Guid id)
         {
             ICamera camera = null;
@@ -179,6 +260,11 @@ namespace TeamAAS_VP.Services
             return false;
         }
 
+        /// <summary>
+        /// 异步移除相机(包装同步调用)。
+        /// </summary>
+        /// <param name="id">相机唯一标识符。</param>
+        /// <returns>表示操作是否成功的任务。</returns>
         public Task<bool> RemoveCameraAsync(Guid id)
         {
             return Task.Run(() =>
@@ -187,6 +273,12 @@ namespace TeamAAS_VP.Services
             });
         }
 
+        /// <summary>
+        /// 根据相机编号查找相机实例,找不到返回 null。
+        /// 该方法内部使用反射来兼容不同相机实现中编号属性的命名差异。
+        /// </summary>
+        /// <param name="number">相机的编号(业务编号、索引等)。</param>
+        /// <returns>找到的 <see cref="ICamera"/> 实例或 null。</returns>
         public ICamera GetCameraByNumber(int number)
         {
             if (TryFindCameraByNumber(number, out _, out var camera))
@@ -194,6 +286,11 @@ namespace TeamAAS_VP.Services
             return null;
         }
 
+        /// <summary>
+        /// 异步根据编号获取相机实例(包装同步调用)。
+        /// </summary>
+        /// <param name="number">相机编号。</param>
+        /// <returns>表示相机实例的任务,未找到为 null。</returns>
         public Task<ICamera> GetCameraByNumberAsync(int number)
         {
             return Task.Run(() =>
@@ -202,6 +299,11 @@ namespace TeamAAS_VP.Services
             });
         }
 
+        /// <summary>
+        /// 异步根据编号取消注册相机(包装同步实现)。
+        /// </summary>
+        /// <param name="number">相机编号。</param>
+        /// <returns>表示操作完成的任务。</returns>
         public Task UnRegisterCameraByNumberAsync(int number)
         {
             return Task.Run(() =>
@@ -210,6 +312,10 @@ namespace TeamAAS_VP.Services
             });
         }
 
+        /// <summary>
+        /// 根据编号查找并取消注册相机(如果找到则调用 <see cref="UnRegisterCamera(Guid)"/>)。
+        /// </summary>
+        /// <param name="number">相机编号。</param>
         public void UnRegisterCameraByNumber(int number)
         {
             if (TryFindCameraByNumber(number, out var id, out var camera))
@@ -219,7 +325,7 @@ namespace TeamAAS_VP.Services
         }
 
         /// <summary>
-        /// 移除所有相机
+        /// 移除并关闭所有已注册的相机,逐个移除以确保资源被正确释放。
         /// </summary>
         public void RemoveAllCameras()
         {
@@ -236,11 +342,14 @@ namespace TeamAAS_VP.Services
         }
 
         /// <summary>
-        /// 初始化所有相机
+        /// 使用提供的相机配置信息数组初始化并尝试打开所有相机。
+        /// 该方法会先清除已有相机,再为每个配置创建实例并尝试打开设备。
         /// </summary>
-        /// <param name="cameras"></param>
-        /// <returns></returns>
-        public  (bool IsSucceed, string Message) InitializeAllCameras(CameraInfo[] cameras)
+        /// <param name="cameras">要初始化的相机信息数组。</param>
+        /// <returns>
+        /// 返回元组,<c>IsSucceed</c> 表示是否所有相机均打开成功;<c>Message</c> 为失败信息或成功消息。
+        /// </returns>
+        public (bool IsSucceed, string Message) InitializeAllCameras(CameraInfo[] cameras)
         {
             //清除现有相机
             RemoveAllCameras();
@@ -286,10 +395,10 @@ namespace TeamAAS_VP.Services
         }
 
         /// <summary>
-        /// 初始化所有相机(异步)
+        /// 异步初始化所有相机(包装同步实现)。
         /// </summary>
-        /// <param name="cameras"></param>
-        /// <returns></returns>
+        /// <param name="cameras">相机信息数组。</param>
+        /// <returns>返回包含是否成功及消息的任务。</returns>
         public Task<(bool IsSucceed, string Message)> InitializeAllCamerasAsync(CameraInfo[] cameras)
         {
 
@@ -298,10 +407,10 @@ namespace TeamAAS_VP.Services
         }
 
         /// <summary>
-        /// 更新相机编号
+        /// 更新指定相机的业务编号(Index)。若未找到对应相机则无操作。
         /// </summary>
-        /// <param name="id"></param>
-        /// <param name="newNumber"></param>
+        /// <param name="id">相机唯一标识符。</param>
+        /// <param name="newNumber">新的编号值。</param>
         public void UpdateCameraNumber(Guid id, int newNumber)
         {
             if (TryGetCamera(id, out var camera))
@@ -310,6 +419,13 @@ namespace TeamAAS_VP.Services
             }
         }
 
+        /// <summary>
+        /// 根据提供的 <see cref="CameraInfo"/> 创建具体相机实现的实例。
+        /// 支持不同品牌的相机类型分发;默认使用 <see cref="MvCamera"/>。
+        /// </summary>
+        /// <param name="id">相机唯一标识符。</param>
+        /// <param name="cameraInfo">相机配置信息。</param>
+        /// <returns>创建的 <see cref="ICamera"/> 实例(未打开设备)。</returns>
         private ICamera CreateCameraInstance(Guid id, CameraInfo cameraInfo)
         {
             ICamera camera;
@@ -342,6 +458,18 @@ namespace TeamAAS_VP.Services
             return camera;
         }
 
+        /// <summary>
+        /// 根据业务编号在已注册相机中查找匹配项。
+        /// 实现要点:
+        /// - 对每个相机尝试通过常见属性名("CameraNo", "Number", "No")读取编号;
+        /// - 若未直接在相机对象找到编号,则尝试从其可能的 <c>CameraInfo</c> / <c>Info</c> 属性中读取;
+        /// - 使用 Convert.ToInt32 进行通用转换,忽略无法转换或异常的项;
+        /// - 在找到匹配编号时返回 true,并输出相机 id 与实例;否则返回 false。
+        /// </summary>
+        /// <param name="number">要匹配的编号。</param>
+        /// <param name="id">若找到则输出相机的 <see cref="Guid"/> 标识符,未找到为 <see cref="Guid.Empty"/>。</param>
+        /// <param name="camera">若找到则输出对应的 <see cref="ICamera"/> 实例,否则为 null。</param>
+        /// <returns>找到匹配项返回 <c>true</c>,否则返回 <c>false</c>。</returns>
         private bool TryFindCameraByNumber(int number, out Guid id, out ICamera camera)
         {
             lock (_sync)
@@ -427,6 +555,9 @@ namespace TeamAAS_VP.Services
             return false;
         }
 
+        /// <summary>
+        /// 释放服务占用的资源:关闭所有相机设备并清空内部集合。
+        /// </summary>
         public void Dispose()
         {
             List<ICamera> cameras;

+ 211 - 41
TeamAAS-VM/Services/ConfigService.cs

@@ -16,7 +16,9 @@ using TeamAAS_VP.Models.Lights;
 namespace TeamAAS_VP.Services
 {
     /// <summary>
-    /// 配置参数服务实现类
+    /// 配置参数服务实现类。
+    /// 线程安全:内部使用私有锁字段 <see cref="_sync"/> 来保护对内存集合的并发访问。
+    /// 持久化:对集合的修改会写回到对应的配置文件(路径由内部静态类 <see cref="ConfigPaths"/> 定义)。
     /// </summary>
     public class ConfigService : IConfigService
     {
@@ -47,12 +49,20 @@ namespace TeamAAS_VP.Services
         // 光源控制器配置
         private ObservableCollection<LightControllerConfig> LightControllers { get; set; }
 
+        /// <summary>
+        /// 初始化一个新的 <see cref="ConfigService"/> 实例。
+        /// 构造函数不会自动加载配置文件(避免在构造期间进行 I/O 操作);可调用 <see cref="LoadAll"/> 或 <see cref="LoadAllAsync"/> 显式加载。
+        /// </summary>
         public ConfigService()
         {
 
             //LoadAll();
         }
 
+        /// <summary>
+        /// 同步加载所有配置文件至内存(若对应文件不存在,将创建默认空集合/对象并写入文件)。
+        /// 线程安全:方法内部对共享资源使用 <see cref="_sync"/> 加锁。
+        /// </summary>
         public void LoadAll()
         {
             lock (_sync)
@@ -124,11 +134,19 @@ namespace TeamAAS_VP.Services
             }
         }
 
+        /// <summary>
+        /// 异步执行 <see cref="LoadAll"/> 操作。
+        /// </summary>
+        /// <returns>表示异步加载操作的任务。</returns>
         public Task LoadAllAsync()
         {
             return Task.Run(() => LoadAll());
         }
 
+        /// <summary>
+        /// 将内存中的所有配置写回到对应配置文件。
+        /// 线程安全:方法内部对写入操作使用 <see cref="_sync"/> 加锁。
+        /// </summary>
         public void SaveAll()
         {
             lock (_sync)
@@ -143,20 +161,44 @@ namespace TeamAAS_VP.Services
             }
         }
 
+        /// <summary>
+        /// 异步执行 <see cref="SaveAll"/> 操作。
+        /// </summary>
+        /// <returns>表示异步保存操作的任务。</returns>
         public Task SaveAllAsync()
         {
             return Task.Run(() => SaveAll());
         }
 
         #region Cameras
+        /// <summary>
+        /// 获取所有相机配置的只读集合。
+        /// 线程安全:在内部加锁以保证并发读取的一致性。
+        /// </summary>
+        /// <returns>相机配置的只读集合。</returns>
         public IReadOnlyCollection<CameraInfo> GetAllCameras()
         {
             lock (_sync) { return Cameras.ToList().AsReadOnly(); }
         }
+
+        /// <summary>
+        /// 根据标识获取单个相机配置信息。
+        /// </summary>
+        /// <param name="id">相机的 Guid 标识。</param>
+        /// <returns>找到则返回对应的 <see cref="CameraInfo"/>,否则返回 <c>null</c>。</returns>
         public CameraInfo GetCamera(Guid id)
         {
             lock (_sync) { return Cameras.FirstOrDefault(c => c.Id == id); }
         }
+
+        /// <summary>
+        /// 新增或更新相机配置。
+        /// - 若传入 <paramref name="camera"/> 为 <c>null</c> 返回 <c>null</c>。
+        /// - 若 Id 已存在则替换;否则追加并设置连续的 CameraNo 编号。
+        /// 方法结束后会将相机集合持久化到配置文件。
+        /// </summary>
+        /// <param name="camera">要添加或更新的相机配置。</param>
+        /// <returns>已添加或更新的 <see cref="CameraInfo"/>;输入为 <c>null</c> 时返回 <c>null</c>。</returns>
         public CameraInfo AddOrUpdateCamera(CameraInfo camera)
         {
             if (camera == null) return null;
@@ -177,6 +219,13 @@ namespace TeamAAS_VP.Services
             FileHelper.WriteJsonFile(Cameras, ConfigPaths.CamerasConfigurationPath);
             return camera;
         }
+
+        /// <summary>
+        /// 根据标识移除相机配置并重新编号剩余相机的 CameraNo。
+        /// 方法结束后会将相机集合持久化到配置文件。
+        /// </summary>
+        /// <param name="id">要移除的相机 Guid。</param>
+        /// <returns>若找到并成功移除返回 <c>true</c>,否则返回 <c>false</c>。</returns>
         public bool RemoveCamera(Guid id)
         {
             bool result = false;
@@ -202,12 +251,35 @@ namespace TeamAAS_VP.Services
             FileHelper.WriteJsonFile(Cameras, ConfigPaths.CamerasConfigurationPath);
             return result;
         }
+
+        /// <summary>
+        /// 检查是否包含指定 Id 的相机配置。
+        /// </summary>
+        /// <param name="id">相机的 Guid。</param>
+        /// <returns>存在返回 <c>true</c>,否则返回 <c>false</c>。</returns>
         public bool ContainsCamera(Guid id) { lock (_sync) { return Cameras.Any(c => c.Id == id); } }
         #endregion
 
         #region Robots
+        /// <summary>
+        /// 获取所有机器人配置的只读集合。
+        /// </summary>
+        /// <returns>机器人配置的只读集合。</returns>
         public IReadOnlyCollection<RobotInfo> GetAllRobots() { lock (_sync) { return Robots.ToList().AsReadOnly(); } }
+
+        /// <summary>
+        /// 根据标识获取单个机器人配置。
+        /// </summary>
+        /// <param name="id">机器人的 Guid 标识。</param>
+        /// <returns>找到则返回对应的 <see cref="RobotInfo"/>,否则返回 <c>null</c>。</returns>
         public RobotInfo GetRobot(Guid id) { lock (_sync) { return Robots.FirstOrDefault(r => r.Id == id); } }
+
+        /// <summary>
+        /// 新增或更新机器人配置,操作后持久化至配置文件。
+        /// 新增时为机器人分配连续的 RobotNo 编号。
+        /// </summary>
+        /// <param name="robot">要添加或更新的机器人配置。</param>
+        /// <returns>已添加或更新的 <see cref="RobotInfo"/>;输入为 <c>null</c> 时返回 <c>null</c>。</returns>
         public RobotInfo AddOrUpdateRobot(RobotInfo robot)
         {
             if (robot == null) return null;
@@ -229,6 +301,13 @@ namespace TeamAAS_VP.Services
             FileHelper.WriteJsonFile(Robots, ConfigPaths.RobotsConfigurationPath);
             return robot;
         }
+
+        /// <summary>
+        /// 根据标识移除机器人配置并重新编号剩余机器人。
+        /// 操作后持久化配置文件。
+        /// </summary>
+        /// <param name="id">要移除的机器人 Guid。</param>
+        /// <returns>移除成功返回 <c>true</c>,否则返回 <c>false</c>。</returns>
         public bool RemoveRobot(Guid id)
         {
             bool result = false;
@@ -255,14 +334,20 @@ namespace TeamAAS_VP.Services
             FileHelper.WriteJsonFile(Robots, ConfigPaths.RobotsConfigurationPath);
             return result;
         }
+
+        /// <summary>
+        /// 判断是否包含指定 Id 的机器人配置。
+        /// </summary>
+        /// <param name="id">机器人的 Guid。</param>
+        /// <returns>存在返回 <c>true</c>,否则返回 <c>false</c>。</returns>
         public bool ContainsRobot(Guid id) { lock (_sync) { return Robots.Any(r => r.Id == id); } }
 
         /// <summary>
-        /// 修改指定机器人id的步进距离
+        /// 修改指定机器人 Id的步进距离并持久化机器人配置。
         /// </summary>
         /// <param name="id">机器人唯一标识符。</param>
-        /// <param name="stepDistance">步进距离</param>
-        /// <returns></returns>
+        /// <param name="stepDistance">步进距离</param>
+        /// <returns>更新成功返回 <c>true</c>,找不到机器人返回 <c>false</c>。</returns>
         public bool UpdateRobotStepDistance(Guid id, double stepDistance)
         {
             lock (_sync)
@@ -282,10 +367,10 @@ namespace TeamAAS_VP.Services
         }
 
         /// <summary>
-        /// 获取指定机器人Id的步进距离
+        /// 获取指定机器人 Id 的步进距离
         /// </summary>
-        /// <param name="id"></param>
-        /// <returns></returns>
+        /// <param name="id">机器人 Guid。</param>
+        /// <returns>若找到则返回对应的步进距离,否则返回默认值 1.0。</returns>
         public double GetRobotStepDistance(Guid id)
         {
             lock (_sync)
@@ -306,8 +391,25 @@ namespace TeamAAS_VP.Services
         #endregion
 
         #region Feeders
+        /// <summary>
+        /// 获取所有供料器配置的只读集合。
+        /// </summary>
+        /// <returns>供料器配置的只读集合。</returns>
         public IReadOnlyCollection<FeederInfo> GetAllFeeders() { lock (_sync) { return Feeders.ToList().AsReadOnly(); } }
+
+        /// <summary>
+        /// 根据标识获取单个供料器配置。
+        /// </summary>
+        /// <param name="id">供料器的 Guid 标识。</param>
+        /// <returns>找到则返回对应的 <see cref="FeederInfo"/>,否则返回 <c>null</c>。</returns>
         public FeederInfo GetFeeder(Guid id) { lock (_sync) { return Feeders.FirstOrDefault(f => f.Id == id); } }
+
+        /// <summary>
+        /// 新增或更新供料器配置,操作后持久化至配置文件。
+        /// 新增时为供料器分配连续的 FeederNo 编号。
+        /// </summary>
+        /// <param name="feeder">要添加或更新的供料器配置。</param>
+        /// <returns>已添加或更新的 <see cref="FeederInfo"/>;输入为 <c>null</c> 时返回 <c>null</c>。</returns>
         public FeederInfo AddOrUpdateFeeder(FeederInfo feeder)
         {
             if (feeder == null) return null;
@@ -328,6 +430,13 @@ namespace TeamAAS_VP.Services
             FileHelper.WriteJsonFile(Feeders, ConfigPaths.FeedersConfigurationPath);
             return feeder;
         }
+
+        /// <summary>
+        /// 根据标识移除供料器配置并重新编号剩余供料器。
+        /// 操作后持久化配置文件。
+        /// </summary>
+        /// <param name="id">要移除的供料器 Guid。</param>
+        /// <returns>移除成功返回 <c>true</c>,否则返回 <c>false</c>。</returns>
         public bool RemoveFeeder(Guid id)
         {
             bool result = false;
@@ -354,12 +463,34 @@ namespace TeamAAS_VP.Services
             FileHelper.WriteJsonFile(Feeders, ConfigPaths.FeedersConfigurationPath);
             return result;
         }
+
+        /// <summary>
+        /// 判断是否包含指定 Id 的供料器配置。
+        /// </summary>
+        /// <param name="id">供料器 Guid。</param>
+        /// <returns>存在返回 <c>true</c>,否则返回 <c>false</c>。</returns>
         public bool ContainsFeeder(Guid id) { lock (_sync) { return Feeders.Any(f => f.Id == id); } }
         #endregion
 
         #region PLC
+        /// <summary>
+        /// 获取所有 PLC 配置的只读集合。
+        /// </summary>
+        /// <returns>PLC 配置的只读集合。</returns>
         public IReadOnlyCollection<PlcInfo> GetAllPlcs() { lock (_sync) { return Plcs.ToList().AsReadOnly(); } }
+
+        /// <summary>
+        /// 根据标识获取单个 PLC 配置。
+        /// </summary>
+        /// <param name="id">PLC 的 Guid 标识。</param>
+        /// <returns>匹配的 <see cref="PlcInfo"/> 实例,找不到返回 <c>null</c>。</returns>
         public PlcInfo GetPlc(Guid id) { lock (_sync) { return Plcs.FirstOrDefault(p => p.Id == id); } }
+
+        /// <summary>
+        /// 新增或更新 PLC 配置,操作后持久化至配置文件;新增时分配连续 PlcNo 编号。
+        /// </summary>
+        /// <param name="plc">要添加或更新的 PLC 配置。</param>
+        /// <returns>已添加或更新的 <see cref="PlcInfo"/>;输入为 <c>null</c> 时返回 <c>null</c>。</returns>
         public PlcInfo AddOrUpdatePlc(PlcInfo plc)
         {
             if (plc == null) return null;
@@ -380,6 +511,13 @@ namespace TeamAAS_VP.Services
             FileHelper.WriteJsonFile(Plcs, ConfigPaths.PlcConfigurationPath);
             return plc;
         }
+
+        /// <summary>
+        /// 根据标识移除 PLC 配置并重新编号剩余 PLC。
+        /// 操作后持久化配置文件。
+        /// </summary>
+        /// <param name="id">要移除的 PLC Guid。</param>
+        /// <returns>移除成功返回 <c>true</c>,否则返回 <c>false</c>。</returns>
         public bool RemovePlc(Guid id)
         {
             bool result = false;
@@ -406,26 +544,60 @@ namespace TeamAAS_VP.Services
             FileHelper.WriteJsonFile(Plcs, ConfigPaths.PlcConfigurationPath);
             return result;
         }
+
+        /// <summary>
+        /// 判断是否包含指定 Id 的 PLC 配置。
+        /// </summary>
+        /// <param name="id">PLC 的 Guid。</param>
+        /// <returns>存在返回 <c>true</c>,否则返回 <c>false</c>。</returns>
         public bool ContainsPlc(Guid id) { lock (_sync) { return Plcs.Any(p => p.Id == id); } }
         #endregion
 
         #region BgTcp
+        /// <summary>
+        /// 获取 BG TCP/IP 通信配置(内存对象,可能为 null)。
+        /// </summary>
+        /// <returns>当前的 <see cref="BgTcpIP"/> 配置对象。</returns>
         public BgTcpIP GetBgTcp() { lock (_sync) { return BgCommunicate; } }
+
+        /// <summary>
+        /// 设置 BG TCP/IP 通信配置(仅更新内存对象,不会自动持久化)。
+        /// 若需持久化请调用 <see cref="SaveBgTcp"/>。
+        /// </summary>
+        /// <param name="cfg">新的 <see cref="BgTcpIP"/> 配置对象。</param>
         public void SetBgTcp(BgTcpIP cfg) { lock (_sync) { BgCommunicate = cfg; } }
+
+        /// <summary>
+        /// 将当前内存中的 BG TCP/IP 配置持久化到配置文件。
+        /// </summary>
         public void SaveBgTcp() { lock (_sync) { FileHelper.WriteJsonFile(BgCommunicate, ConfigPaths.BgTcpIpConfigurationPath); } }
         #endregion
 
         #region BgModbus
+        /// <summary>
+        /// 获取 BG Modbus TCP 通信配置(内存对象)。
+        /// </summary>
+        /// <returns>当前的 <see cref="BgModbusTcp"/> 配置对象。</returns>
         public BgModbusTcp GetBgModbusTcp() { lock (_sync) { return BgModbusCommunicate; } }
+
+        /// <summary>
+        /// 设置 BG Modbus TCP 通信配置(仅更新内存对象,不会自动持久化)。
+        /// </summary>
+        /// <param name="cfg">新的 <see cref="BgModbusTcp"/> 配置对象。</param>
         public void SetBgModbusTcp(BgModbusTcp cfg) { lock (_sync) { BgModbusCommunicate = cfg; } }
+
+        /// <summary>
+        /// 将当前内存中的 BG Modbus TCP 配置持久化到配置文件。
+        /// </summary>
         public void SaveBgModbusTcp() { lock (_sync) { FileHelper.WriteJsonFile(BgModbusCommunicate, ConfigPaths.BgModbusTcpConfigurationPath); } }
         #endregion
 
         #region 系统参数
         /// <summary>
-        /// 获取系统设置
+        /// 获取系统设置(从配置文件读取)。
+        /// 线程安全:方法内部加锁以保证读取一致性。
         /// </summary>
-        /// <returns></returns>
+        /// <returns>读取到的 <see cref="SystemConfiguration"/> 对象;若文件不存在则返回默认的新实例。</returns>
         public SystemConfiguration GetSystemConfiguration()
         {
             lock (_sync)
@@ -440,8 +612,9 @@ namespace TeamAAS_VP.Services
         }
 
         /// <summary>
-        /// 保存系统设置
+        /// 保存系统设置到配置文件(会覆盖现有文件)。
         /// </summary>
+        /// <param name="systemConfiguration">要保存的系统配置对象。</param>
         public void SaveSystemConfiguration(SystemConfiguration systemConfiguration)
         {
             lock (_sync)
@@ -453,16 +626,16 @@ namespace TeamAAS_VP.Services
 
         #region Feeder清料任务
         /// <summary>
-        /// 获取所有清料任务
+        /// 获取所有清料任务的只读集合。
         /// </summary>
-        /// <returns></returns>
+        /// <returns>清料任务的只读集合。</returns>
         public IReadOnlyCollection<FeederClearWork> GetAllClearanceTasks() { lock (_sync) { return FeederClearanceTasks.ToList().AsReadOnly(); } }
 
         /// <summary>
-        /// 新增或更新清料任务
+        /// 新增或更新清料任务,新增时为任务分配递增的 TaskCode,完成后持久化配置文件。
         /// </summary>
-        /// <param name="task"></param>
-        /// <returns></returns>
+        /// <param name="task">要添加或更新的清料任务对象。</param>
+        /// <returns>已添加或更新的 <see cref="FeederClearWork"/>;输入为 <c>null</c> 时返回 <c>null</c>。</returns>
         public FeederClearWork AddOrUpdateClearanceTask(FeederClearWork task)
         {
             if (task == null) return null;
@@ -486,10 +659,10 @@ namespace TeamAAS_VP.Services
         }
 
         /// <summary>
-        /// 移除清料任务
+        /// 根据标识移除清料任务并持久化配置文件。
         /// </summary>
-        /// <param name="id"></param>
-        /// <returns></returns>
+        /// <param name="id">要移除的清料任务 Guid。</param>
+        /// <returns>若找到并移除返回 <c>true</c>,否则返回 <c>false</c>。</returns>
         public bool RemoveClearanceTask(Guid id)
         {
             lock (_sync)
@@ -507,7 +680,7 @@ namespace TeamAAS_VP.Services
         }
 
         /// <summary>
-        /// 清除所有清料任务
+        /// 清除所有清料任务并持久化空集合到配置文件。
         /// </summary>
         public void ClearAllClearanceTasks()
         {
@@ -519,10 +692,10 @@ namespace TeamAAS_VP.Services
         }
 
         /// <summary>
-        /// 判断是否包含指定标识的清料任务
+        /// 判断是否包含指定 Id 的清料任务。
         /// </summary>
-        /// <param name="id"></param>
-        /// <returns></returns>
+        /// <param name="id">清料任务 Guid。</param>
+        /// <returns>存在返回 <c>true</c>,否则返回 <c>false</c>。</returns>
         public bool ContainsClearanceTask(Guid id)
         {
             lock (_sync) { return FeederClearanceTasks.Any(t => t.Id == id); }
@@ -530,30 +703,30 @@ namespace TeamAAS_VP.Services
         }
 
         /// <summary>
-        /// 根据标识获取单个清料任务信息
+        /// 根据标识获取单个清料任务信息
         /// </summary>
-        /// <param name="id"></param>
-        /// <returns></returns>
+        /// <param name="id">清料任务 Guid。</param>
+        /// <returns>匹配的 <see cref="FeederClearWork"/>,找不到返回 <c>null</c>。</returns>
         public FeederClearWork GetClearanceTask(Guid id)
         {
             lock (_sync) { return FeederClearanceTasks.FirstOrDefault(t => t.Id == id); }
         }
 
         /// <summary>
-        /// 判断是否包含指定编号的清料任务
+        /// 判断是否包含指定任务编号的清料任务
         /// </summary>
-        /// <param name="taskNumber"></param>
-        /// <returns></returns>
+        /// <param name="taskNumber">任务编号(TaskCode)。</param>
+        /// <returns>存在返回 <c>true</c>,否则返回 <c>false</c>。</returns>
         public bool ContainsClearanceTaskByNumber(int taskNumber)
         {
             lock (_sync) { return FeederClearanceTasks.Any(t => t.TaskCode == taskNumber); }
         }
 
         /// <summary>
-        /// 根据编号获取单个清料任务信息
+        /// 根据任务编号获取单个清料任务信息
         /// </summary>
-        /// <param name="taskNumber"></param>
-        /// <returns></returns>
+        /// <param name="taskNumber">任务编号(TaskCode)。</param>
+        /// <returns>匹配的 <see cref="FeederClearWork"/>,找不到返回 <c>null</c>。</returns>
         public FeederClearWork GetClearanceTaskByNumber(int taskNumber)
         {
             lock (_sync) { return FeederClearanceTasks.FirstOrDefault(t => t.TaskCode == taskNumber); }
@@ -642,9 +815,8 @@ namespace TeamAAS_VP.Services
         }
 
         /// <summary>
-        /// 根据 Id 移除光源控制器配置。
-        /// - 移除后会对剩余控制器重新编号(从1开始连续编号)。
-        /// 线程安全:在方法体内对集合进行锁定。
+        /// 根据 Id 移除光源控制器配置并对剩余控制器重新编号(从 1 开始连续编号)。
+        /// 操作后持久化配置文件。
         /// </summary>
         /// <param name="id">要移除的控制器 Id。</param>
         /// <returns>如果成功移除返回 <c>true</c>,否则返回 <c>false</c>(比如未找到该 Id)。</returns>
@@ -677,7 +849,6 @@ namespace TeamAAS_VP.Services
 
         /// <summary>
         /// 判断是否包含指定 Id 的光源控制器。
-        /// 线程安全:在内部使用锁定进行检查。
         /// </summary>
         /// <param name="id">控制器 Id。</param>
         /// <returns>存在返回 <c>true</c>,否则返回 <c>false</c>。</returns>
@@ -685,7 +856,6 @@ namespace TeamAAS_VP.Services
 
         /// <summary>
         /// 获取所有光源通道的只读集合,按 GlobalIndex 升序返回。
-        /// 线程安全:方法内部加锁以保证集合读取的一致性。
         /// </summary>
         /// <returns>按 GlobalIndex 排序的 <see cref="ChannelConfig"/> 只读集合。</returns>
         public IReadOnlyCollection<ChannelConfig> GetAllLightChannels()
@@ -695,7 +865,6 @@ namespace TeamAAS_VP.Services
 
         /// <summary>
         /// 根据全局通道索引获取单个通道配置。
-        /// 线程安全:在内部加锁。
         /// </summary>
         /// <param name="globalIndex">通道的全局索引(GlobalIndex)。</param>
         /// <returns>匹配的 <see cref="ChannelConfig"/> 实例,找不到则返回 <c>null</c>。</returns>
@@ -706,7 +875,6 @@ namespace TeamAAS_VP.Services
 
         /// <summary>
         /// 判断是否存在指定全局通道索引的通道配置。
-        /// 线程安全:在内部使用锁定。
         /// </summary>
         /// <param name="globalIndex">全局通道索引。</param>
         /// <returns>存在返回 <c>true</c>,否则返回 <c>false</c>。</returns>
@@ -716,11 +884,10 @@ namespace TeamAAS_VP.Services
         }
 
         /// <summary>
-        /// 更新指定全局通道的默认亮度(DefaultBrightness)。
-        /// 线程安全:在内部锁定集合并在更新后持久化配置。
+        /// 更新指定全局通道的默认亮度(DefaultBrightness),并持久化光源控制器配置。
         /// </summary>
         /// <param name="globalIndex">目标通道的全局索引(GlobalIndex)。</param>
-        /// <param name="brightness">要设置的亮度值(单位由 <see cref="ChannelConfig"/> 定义)。</param>
+        /// <param name="brightness">要设置的亮度值。</param>
         /// <returns>更新成功返回 <c>true</c>;找不到对应通道返回 <c>false</c>。</returns>
         public bool UpdateChannelDefaultBrightness(int globalIndex, int brightness)
         {
@@ -736,6 +903,9 @@ namespace TeamAAS_VP.Services
 
         #endregion
 
+        /// <summary>
+        /// 释放资源并保存当前内存中的所有配置到文件。
+        /// </summary>
         public void Dispose()
         {
             SaveAll();