Jelajahi Sumber

为灯控核心接口与实现类补充详细中文注释

本次提交为灯控系统相关接口、实现类及配置模型补充了全面的中文 XML 文档注释,涵盖属性、方法、事件及构造函数,提升了代码可读性和可维护性。部分构造函数增加了参数校验,通道注册流程优化了全局 ID 分配逻辑。UI 消息通知方法增加了队列清理,避免消息堆积。无业务逻辑变更,仅文档与注释增强,有助于团队协作和后续文档生成。
孝锋 徐 8 bulan lalu
induk
melakukan
9f8e4c18c8

+ 1 - 0
TeamAAS-VM/Controls/RobotManual.xaml.cs

@@ -192,6 +192,7 @@ namespace TeamAAS_VP.Controls
             {
                 snackbar.MessageQueue = new MaterialDesignThemes.Wpf.SnackbarMessageQueue();
             }
+            snackbar.MessageQueue.Clear();
             snackbar.MessageQueue?.Enqueue(
                 msg,
                 null,

+ 98 - 5
TeamAAS-VM/Core/Lights/ICommunicationProtocol.cs

@@ -7,32 +7,125 @@ using TeamAAS_VP.Enums;
 
 namespace TeamAAS_VP.Core.Lights
 {
+    /// <summary>
+    /// 定义灯光控制或通信设备的通用通信协议契约。
+    /// 实现此接口的类负责建立连接、发送/接收数据以及在连接状态改变时通知订阅者。
+    /// 实现类应负责释放底层资源,因此继承自 <see cref="IDisposable"/>。
+    /// </summary>
     public interface ICommunicationProtocol : IDisposable
     {
+        /// <summary>
+        /// 获取当前连接状态。
+        /// 返回 true 表示已建立连接并可进行数据收发;false 表示未连接或已断开。
+        /// </summary>
         bool IsConnected { get; }
+
+        /// <summary>
+        /// 获取或设置通信报文的结束标记(终止符)。
+        /// 该标记用于分割或终结文本协议消息。
+        /// </summary>
         Terminator Terminator { get; }
+
+        /// <summary>
+        /// 获取用于文本消息编码/解码的字符编码(例如 UTF8、ASCII)。
+        /// 当使用字符串重载的发送/接收方法时,此编码将被应用。
+        /// </summary>
         Encoding Encoding { get; }
 
-        //连接改变时间
+        /// <summary>
+        /// 当连接状态发生变化时触发。
+        /// sender 为事件源,bool 参数表示新的连接状态(true=已连接,false=已断开)。
+        /// </summary>
         event Action<object, bool> ConnectionChanged;
-        //接收事件
+
+        /// <summary>
+        /// 当接收到完整的数据(按协议分包或终止符)时触发。
+        /// sender 为事件源,string 为已接收并按 <see cref="Encoding"/> 解码的文本内容。
+        /// </summary>
         event Action<object, string> DataReceived;
-        //发送事件
-        event Action<object, string> DataSent;
 
+        /// <summary>
+        /// 当成功发送数据时触发。
+        /// sender 为事件源,string 为已发送并按 <see cref="Encoding"/> 编码的文本内容(若使用字节发送则可为空或经转换的展示文本)。
+        /// </summary>
+        event Action<object, string> DataSent;
 
+        /// <summary>
+        /// 异步建立与设备的连接。
+        /// 实现应在连接成功后触发 <see cref="ConnectionChanged"/>(true)。
+        /// </summary>
+        /// <returns>任务结果为 true 表示连接成功,false 表示连接失败或未建立。</returns>
         Task<bool> ConnectAsync();
+
+        /// <summary>
+        /// 异步断开与设备的连接并释放相关资源(但不等同于 Dispose)。
+        /// 实现应在断开后触发 <see cref="ConnectionChanged"/>(false)。
+        /// </summary>
+        /// <returns>表示断开操作完成的任务。</returns>
         Task DisconnectAsync();
+
+        /// <summary>
+        /// 异步发送字节数组并等待返回的字节数组响应。
+        /// </summary>
+        /// <param name="data">要发送的原始字节数据。</param>
+        /// <param name="timeout">超时时间(毫秒),在超时未收到完整响应时应抛出或返回空/部分数据,默认 5000ms。</param>
+        /// <returns>返回接收到的字节数组响应。</returns>
         Task<byte[]> SendAndReceiveAsync(byte[] data, int timeout = 5000);
+
+        /// <summary>
+        /// 异步发送字节数组,不等待响应。
+        /// </summary>
+        /// <param name="data">要发送的原始字节数据。</param>
+        /// <returns>表示发送操作完成的任务。</returns>
         Task SendAsync(byte[] data);
 
+        /// <summary>
+        /// 异步发送文本并等待文本响应。
+        /// 使用 <see cref="Encoding"/> 对字符串进行编码/解码,超时时间单位为毫秒。
+        /// </summary>
+        /// <param name="data">要发送的文本数据。</param>
+        /// <param name="timeout">超时时间(毫秒),默认 5000ms。</param>
+        /// <returns>返回接收到的文本响应。</returns>
         Task<string> SendAndReceiveAsync(string data, int timeout = 5000);
 
+        /// <summary>
+        /// 同步发送文本并等待文本响应。
+        /// 注意:在 UI 线程中调用此方法可能导致界面阻塞,建议使用异步重载。
+        /// </summary>
+        /// <param name="data">要发送的文本数据。</param>
+        /// <param name="timeout">超时时间(毫秒),默认 5000ms。</param>
+        /// <returns>接收到的文本响应。</returns>
         string SendAndReceive(string data, int timeout = 5000);
+
+        /// <summary>
+        /// 同步发送字节数组并等待返回的字节数组响应。
+        /// 注意:在 UI 线程中调用此方法可能导致界面阻塞,建议使用异步重载。
+        /// </summary>
+        /// <param name="data">要发送的字节数组。</param>
+        /// <param name="timeout">超时时间(毫秒),默认 5000ms。</param>
+        /// <returns>接收到的字节数组响应。</returns>
         byte[] SendAndReceive(byte[] data, int timeout = 5000);
+
+        /// <summary>
+        /// 同步发送字节数组,不等待响应。
+        /// 注意:在 UI 线程中调用此方法可能导致界面阻塞,建议使用异步重载。
+        /// </summary>
+        /// <param name="data">要发送的字节数组。</param>
         void Send(byte[] data);
+
+        /// <summary>
+        /// 异步发送文本数据。
+        /// 使用 <see cref="Encoding"/> 对字符串进行编码后发送。
+        /// </summary>
+        /// <param name="data">要发送的文本数据。</param>
+        /// <returns>表示发送操作完成的任务。</returns>
         Task SendAsync(string data);
+
+        /// <summary>
+        /// 同步发送文本数据。
+        /// 注意:在 UI 线程中调用此方法可能导致界面阻塞,建议使用异步重载。
+        /// </summary>
+        /// <param name="data">要发送的文本数据。</param>
         void Send(string data);
     }
-
 }

+ 86 - 2
TeamAAS-VM/Core/Lights/ILightChannel.cs

@@ -1,4 +1,23 @@
-using System;
+/*
+计划(伪代码,逐步说明):
+1. 在接口文件顶部添加文件级别注释,说明该文件用途与适用范围。
+2. 为接口 `ILightChannel` 添加 XML 文档注释,描述接口职责(表示一个光源通道)。
+3. 为每个属性添加 XML 注释:
+   - `ChannelIndex`:只读,返回通道索引(非负整数)。
+   - `ChannelName`:可读写,通道名称,允许为空或空字符串取决于实现。
+   - `Brightness`:只读,返回当前亮度(建议范围 0-100)。
+   - `IsOn`:只读,指示通道是否处于开启状态。
+4. 为每个异步方法添加 XML 注释,说明参数、返回值语义以及可能的预期行为:
+   - `SetBrightnessAsync(int brightness)`:说明亮度取值范围、异常或边界处理建议以及返回表示成功与否的 `Task<bool>`。
+   - `TurnOnAsync()` / `TurnOffAsync()`:说明返回值含义(操作是否成功),并建议实现应保证幂等性。
+   - `GetBrightnessAsync()` / `GetStatusAsync()`:说明返回当前状态/亮度的 `Task`。
+5. 使用中文在 `<summary>`、`<param>`、`<returns>` 和 `<remarks>` 中撰写注释,保证对使用者友好。
+6. 保持注释简洁、准确并符合项目风格(XML 注释用于生成文档),不修改接口签名或逻辑。
+
+下面输出修改后的接口文件,包含完整中文 XML 注释。
+*/
+
+using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Text;
@@ -7,19 +26,84 @@ using System.Threading.Tasks;
 namespace TeamAAS_VP.Core.Lights
 {
     /// <summary>
-    /// 光源通道接口
+    /// 表示一个光源通道的接口。
+    /// 实现该接口的类型应封装单个物理或虚拟光源通道的状态与控制操作(如亮度设置、开关等)。
     /// </summary>
     public interface ILightChannel
     {
+        /// <summary>
+        /// 获取通道索引(只读)。
+        /// 索引用于唯一标识同一设备或集合中的通道,通常为非负整数。
+        /// </summary>
         int ChannelIndex { get; }
+
+        /// <summary>
+        /// 获取或设置通道名称。
+        /// 名称用于在 UI 或日志中显示更友好的通道标识,允许为空或空字符串,具体行为由实现决定。
+        /// </summary>
         string ChannelName { get; set; }
+
+        /// <summary>
+        /// 获取当前亮度(只读)。
+        /// 亮度通常在 0 到 100 的范围内表示(实现可约束或采用其他范围,但应在文档中说明)。
+        /// </summary>
         int Brightness { get; }
+
+        /// <summary>
+        /// 获取通道当前的开关状态(只读)。
+        /// 返回 true 表示通道处于开启状态;false 表示关闭状态。
+        /// </summary>
         bool IsOn { get; }
 
+        /// <summary>
+        /// 异步设置通道亮度。
+        /// </summary>
+        /// <param name="brightness">目标亮度值。建议范围为 0 到 100;超出范围的处理由实现决定(例如截断或抛出异常)。</param>
+        /// <returns>
+        /// 返回一个 <see cref="Task{Boolean}"/>,其结果为 true 表示操作成功并且亮度已应用,false 表示操作失败。
+        /// </returns>
+        /// <remarks>
+        /// 实现应尽可能保证该操作的幂等性,且在失败时不改变设备到不可预测的状态。
+        /// 如需取消,请在具体实现中提供取消机制(例如接受 CancellationToken 的重载)。
+        /// </remarks>
         Task<bool> SetBrightnessAsync(int brightness);
+
+        /// <summary>
+        /// 异步开启通道。
+        /// </summary>
+        /// <returns>
+        /// 返回一个 <see cref="Task{Boolean}"/>,其结果为 true 表示通道已成功开启,false 表示开启失败。
+        /// </returns>
+        /// <remarks>
+        /// 实现应保证多次调用的幂等性(多次开启不会引起错误)。
+        /// </remarks>
         Task<bool> TurnOnAsync();
+
+        /// <summary>
+        /// 异步关闭通道。
+        /// </summary>
+        /// <returns>
+        /// 返回一个 <see cref="Task{Boolean}"/>,其结果为 true 表示通道已成功关闭,false 表示关闭失败。
+        /// </returns>
+        /// <remarks>
+        /// 实现应保证多次调用的幂等性(多次关闭不会引起错误)。
+        /// </remarks>
         Task<bool> TurnOffAsync();
+
+        /// <summary>
+        /// 异步获取当前亮度。
+        /// </summary>
+        /// <returns>
+        /// 返回一个 <see cref="Task{Int32}"/>,其结果为当前亮度值(通常在 0 到 100 之间)。
+        /// </returns>
         Task<int> GetBrightnessAsync();
+
+        /// <summary>
+        /// 异步获取通道当前开关状态。
+        /// </summary>
+        /// <returns>
+        /// 返回一个 <see cref="Task{Boolean}"/>,其结果为 true 表示通道已开启,false 表示通道已关闭。
+        /// </returns>
         Task<bool> GetStatusAsync();
     }
 }

+ 85 - 2
TeamAAS-VM/Core/Lights/ILightController.cs

@@ -1,4 +1,24 @@
-using System;
+/* 详细伪代码(步骤说明):
+   1. 在接口文件顶部添加多行注释,说明接下来要对接口及其成员生成 XML 注释的目的。
+   2. 为接口 `ILightController` 添加总体的 `<summary>` 描述,解释该接口代表一个光源控制器,负责连接、初始化与批量控制通道。
+   3. 为每个属性添加 `<summary>`:
+      - `Id`:唯一标识符(只读)。
+      - `Name`:控制器显示名称(只读)。
+      - `Model`:控制器型号(只读)。
+      - `ChannelCount`:通道数量,通常与 `Channels.Count` 一致(只读)。
+      - `IsConnected`:指示与硬件连接状态(只读)。
+      - `Channels`:只读通道列表,返回 `ILightChannel` 的不可变视图。
+   4. 为每个方法添加 `<summary>`、`<returns>`,必要时添加 `<remarks>`:
+      - `ConnectAsync`:异步建立连接,成功返回 true。
+      - `DisconnectAsync`:异步断开连接(无需返回值)。
+      - `InitializeAsync`:在连接后初始化控制器与通道配置,成功返回 true。
+      - `TurnOnAllAsync` / `TurnOffAllAsync`:异步批量打开/关闭所有通道,返回整体成功状态(所有通道成功为 true)。
+   5. 为接口继承的 `IDisposable` 提醒实现者在 `Dispose` 中释放非托管资源与关闭连接。
+   6. 确保注释使用简体中文,保持简洁并包含异步行为、预期副作用与返回值含义。
+   7. 将注释以标准 C# XML 文档注释(///)形式插入到接口及其成员上方,以便生成文档和在 IDE 中显示工具提示。
+*/
+
+using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Text;
@@ -7,23 +27,86 @@ using System.Threading.Tasks;
 namespace TeamAAS_VP.Core.Lights
 {
     /// <summary>
-    /// 光源控制器接口
+    /// 表示一个光源控制器的抽象接口。
+    /// 实现类负责管理与底层光源硬件的连接、初始化以及对通道的集中控制操作。
     /// </summary>
     public interface ILightController : IDisposable
     {
+        /// <summary>
+        /// 控制器的唯一标识符。
+        /// 通常由设备发现或配置阶段分配,供日志和映射使用。
+        /// </summary>
         int Id { get; }
+
+        /// <summary>
+        /// 控制器的显示名称或描述。
+        /// 可用于 UI 展示或调试。
+        /// </summary>
         string Name { get; }
+
+        /// <summary>
+        /// 控制器的型号信息。
+        /// 用于区分不同厂商或不同协议的实现逻辑。
+        /// </summary>
         LightModel Model { get; }
+
+        /// <summary>
+        /// 控制器包含的通道数量。
+        /// 通常应与 <see cref="Channels"/> 中的元素数量一致。
+        /// </summary>
         int ChannelCount { get; }
+
+        /// <summary>
+        /// 指示控制器当前是否与底层硬件建立了连接。
+        /// 在成功调用 <see cref="ConnectAsync"/> 并完成后应为 true,在调用 <see cref="DisconnectAsync"/> 或连接丢失后应为 false。
+        /// </summary>
         bool IsConnected { get; }
 
+        /// <summary>
+        /// 只读的光源通道集合。
+        /// 每个通道通过 <see cref="ILightChannel"/> 描述单个输出通道的状态与控制接口。
+        /// </summary>
         IReadOnlyList<ILightChannel> Channels { get; }
 
+        /// <summary>
+        /// 异步建立与光源控制器的连接。
+        /// </summary>
+        /// <returns>
+        /// 如果连接成功并且控制器可用则返回 <c>true</c>,否则返回 <c>false</c>。
+        /// </returns>
         Task<bool> ConnectAsync();
+
+        /// <summary>
+        /// 异步断开与光源控制器的连接并释放会话相关的资源。
+        /// 调用后 <see cref="IsConnected"/> 应变为 <c>false</c>。
+        /// </summary>
+        /// <returns>当断开完成时的任务。</returns>
         Task DisconnectAsync();
+
+        /// <summary>
+        /// 在连接建立后对控制器及其通道进行初始化(例如读取配置、设置初始状态等)。
+        /// </summary>
+        /// <returns>
+        /// 初始化成功返回 <c>true</c>;若初始化失败(例如硬件响应异常或配置不兼容)返回 <c>false</c>。
+        /// </returns>
         Task<bool> InitializeAsync();
 
+        /// <summary>
+        /// 异步将所有通道打开(或设置为“开启”状态)。
+        /// 实现应遍历 <see cref="Channels"/> 并对每个通道执行打开操作。
+        /// </summary>
+        /// <returns>
+        /// 当所有通道均成功打开时返回 <c>true</c>;若任一通道操作失败则返回 <c>false</c>(具体策略由实现决定)。
+        /// </returns>
         Task<bool> TurnOnAllAsync();
+
+        /// <summary>
+        /// 异步将所有通道关闭(或设置为“关闭”状态)。
+        /// 实现应遍历 <see cref="Channels"/> 并对每个通道执行关闭操作。
+        /// </summary>
+        /// <returns>
+        /// 当所有通道均成功关闭时返回 <c>true</c>;若任一通道操作失败则返回 <c>false</c>(具体策略由实现决定)。
+        /// </returns>
         Task<bool> TurnOffAllAsync();
     }
 }

+ 73 - 0
TeamAAS-VM/Core/Lights/KCSLightChannel.cs

@@ -6,57 +6,130 @@ using System.Threading.Tasks;
 
 namespace TeamAAS_VP.Core.Lights
 {
+    /// <summary>
+    /// 表示单个 KCS 灯光通道的封装,用于读取/设置通道亮度并维护本地状态缓存。
+    /// 与底层 <see cref="KCSLightController"/> 交互以执行实际的硬件操作。
+    /// </summary>
     public class KCSLightChannel : ILightChannel
     {
+        /// <summary>
+        /// 对应的控制器实例,用于执行底层 IO 操作。由构造函数注入。
+        /// </summary>
         private readonly KCSLightController _controller;
+
+        /// <summary>
+        /// 本地缓存的亮度值,范围 0-255。
+        /// </summary>
         private int _brightness;
+
+        /// <summary>
+        /// 本地缓存的开/关状态,基于 <see cref="_brightness"/> 判定(>0 为开)。
+        /// </summary>
         private bool _isOn;
 
+        /// <summary>
+        /// 通道索引(只读),对应控制器中的通道序号(从 0 开始)。
+        /// </summary>
         public int ChannelIndex { get; }
+
+        /// <summary>
+        /// 通道名称(可读写),用于 UI 或日志显示。
+        /// 默认为 "Channel {index + 1}"。
+        /// </summary>
         public string ChannelName { get; set; }
+
+        /// <summary>
+        /// 当前本地缓存的亮度值(0-255)。
+        /// 注意:此值可能不是实时的硬件状态,若需最新值请调用 <see cref="GetBrightnessAsync"/>。
+        /// </summary>
         public int Brightness => _brightness;
+
+        /// <summary>
+        /// 当前本地缓存的开/关状态。若需实时状态请调用 <see cref="GetStatusAsync"/>。
+        /// </summary>
         public bool IsOn => _isOn;
 
+        /// <summary>
+        /// 内部访问器,返回当前缓存亮度(供同程序集使用)。
+        /// </summary>
         internal int CurrentBrightness => _brightness;
 
+        /// <summary>
+        /// 使用指定的通道索引和控制器实例创建一个新的 <see cref="KCSLightChannel"/>。
+        /// </summary>
+        /// <param name="index">通道索引(从 0 开始)。</param>
+        /// <param name="controller">用于与硬件交互的 <see cref="KCSLightController"/> 实例,不能为空。</param>
+        /// <exception cref="ArgumentNullException">当 <paramref name="controller"/> 为 null 时抛出。</exception>
         public KCSLightChannel(int index, KCSLightController controller)
         {
+            if (controller == null)
+                throw new ArgumentNullException(nameof(controller));
+
             ChannelIndex = index;
             ChannelName = $"Channel {index + 1}";
             _controller = controller;
         }
 
+        /// <summary>
+        /// 异步设置通道亮度(0-255)。
+        /// 如果设置成功,将更新本地缓存的亮度与开/关状态。
+        /// </summary>
+        /// <param name="brightness">目标亮度,范围 0-255。</param>
+        /// <returns>如果控制器成功应用了亮度返回 true,否则返回 false。</returns>
+        /// <exception cref="ArgumentOutOfRangeException">当 <paramref name="brightness"/> 不在 0-255 范围内时抛出。</exception>
         public async Task<bool> SetBrightnessAsync(int brightness)
         {
             if (brightness < 0 || brightness > 255)
                 throw new ArgumentOutOfRangeException(nameof(brightness));
 
+            // 向控制器请求设置亮度,控制器负责具体的通信/协议实现。
             var result = await _controller.SetChannelBrightnessInternal(ChannelIndex, brightness);
             if (result)
             {
+                // 仅在控制器确认成功后更新本地缓存状态。
                 _brightness = brightness;
                 _isOn = brightness > 0;
             }
             return result;
         }
 
+        /// <summary>
+        /// 异步开启通道。如果当前有缓存亮度且大于 0,则使用该亮度;否则使用默认亮度 100。
+        /// 该方法会调用 <see cref="SetBrightnessAsync"/> 并返回控制器操作结果。
+        /// </summary>
+        /// <returns>如果成功开启返回 true,否则返回 false。</returns>
         public async Task<bool> TurnOnAsync()
         {
+            // 若有缓存亮度且大于 0,则恢复该亮度;否则使用默认亮度 100。
             return await SetBrightnessAsync(_brightness > 0 ? _brightness : 100);
         }
 
+        /// <summary>
+        /// 异步关闭通道(将亮度设置为 0)。
+        /// </summary>
+        /// <returns>如果成功关闭返回 true,否则返回 false。</returns>
         public async Task<bool> TurnOffAsync()
         {
             return await SetBrightnessAsync(0);
         }
 
+        /// <summary>
+        /// 异步从控制器获取当前通道亮度并更新本地缓存。
+        /// </summary>
+        /// <returns>读取到的亮度值(0-255)。</returns>
         public async Task<int> GetBrightnessAsync()
         {
+            // 从控制器读取最新亮度值并更新本地缓存与开/关状态。
             _brightness = await _controller.GetChannelBrightnessInternal(ChannelIndex);
             _isOn = _brightness > 0;
             return _brightness;
         }
 
+        /// <summary>
+        /// 异步获取当前通道开/关状态。
+        /// 该方法会触发一次亮度读取以确保状态是最新的。
+        /// </summary>
+        /// <returns>如果通道处于开启状态返回 true,否则返回 false。</returns>
         public async Task<bool> GetStatusAsync()
         {
             await GetBrightnessAsync();

+ 74 - 1
TeamAAS-VM/Core/Lights/KCSLightController.cs

@@ -6,11 +6,24 @@ using System.Threading.Tasks;
 
 namespace TeamAAS_VP.Core.Lights
 {
+    /// <summary>
+    /// 基于 KCS 协议的光源控制器实现。
+    /// 该类通过注入的 <see cref="ICommunicationProtocol"/> 与物理设备通信,
+    /// 并提供通道创建、连接/断开、初始化、全部开/关及通道亮度的内部读写实现。
+    /// </summary>
     public class KCSLightController : LightControllerBase
     {
+        // 通信协议抽象,用于发送/接收字节数据
         private readonly ICommunicationProtocol _protocol;
+        // 使用 ASCII 编码将字符串转换为字节流
         private readonly Encoding _encoding;
 
+        /// <summary>
+        /// 创建一个新的 <see cref="KCSLightController"/> 实例。
+        /// </summary>
+        /// <param name="id">控制器的唯一标识符。</param>
+        /// <param name="protocol">用于与设备通信的协议实现(必须已实现连接/发送/接收等)。</param>
+        /// <param name="channelCount">该控制器管理的通道数量。</param>
         public KCSLightController(int id, ICommunicationProtocol protocol,int channelCount)
             : base(id, channelCount)
         {
@@ -18,11 +31,20 @@ namespace TeamAAS_VP.Core.Lights
             _encoding = Encoding.ASCII;
         }
 
+        /// <summary>
+        /// 为指定索引创建通道实例。
+        /// </summary>
+        /// <param name="index">通道索引(从 0 开始)。</param>
+        /// <returns>返回对应的 <see cref="KCSLightChannel"/> 实例。</returns>
         protected override ILightChannel CreateChannel(int index)
         {
             return new KCSLightChannel(index, this);
         }
 
+        /// <summary>
+        /// 异步建立与设备的连接并在成功后执行初始化。
+        /// </summary>
+        /// <returns>若连接成功返回 true,否则返回 false。</returns>
         public override async Task<bool> ConnectAsync()
         {
             var connected = await _protocol.ConnectAsync();
@@ -34,46 +56,72 @@ namespace TeamAAS_VP.Core.Lights
             return connected;
         }
 
+        /// <summary>
+        /// 异步断开与设备的连接并更新连接状态。
+        /// </summary>
         public override async Task DisconnectAsync()
         {
             await _protocol.DisconnectAsync();
             IsConnected = false;
         }
 
+        /// <summary>
+        /// 异步初始化控制器,通常用于测试通信并同步初始状态。
+        /// 目前实现通过读取第 0 通道亮度来验证通信链路是否正常。
+        /// </summary>
+        /// <returns>若初始化成功返回 true;若发生异常或通信失败返回 false。</returns>
         public override async Task<bool> InitializeAsync()
         {
             try
             {
-                // 初始化操作
+                // 初始化操作:读取第 0 通道亮度以测试通信是否正常
                 await GetChannelBrightnessInternal(0); // 测试通信
                 return true;
             }
             catch
             {
+                // 初始化失败(通信异常等)
                 return false;
             }
         }
 
+        /// <summary>
+        /// 将所有通道设置为打开状态。命令格式参考控制器手册。
+        /// 构造格式示例:S{CH1:DDD}T{CH2:DDD}T...C#
+        /// 其中每个通道使用 3 位亮度值(D3),后跟动作字符 'T' 表示开。
+        /// </summary>
+        /// <returns>若设备返回确认字符 '!' 则认为操作成功。</returns>
         public override async Task<bool> TurnOnAllAsync()
         {
             StringBuilder commandBuilder = new StringBuilder("S");
             for (int i = 0; i < ChannelCount; i++)
             {
                 var channel = (KCSLightChannel)ChannelsInternal[i];
+                // 使用三位数字格式表示亮度(例如 005、120、255)
                 commandBuilder.Append(channel.CurrentBrightness.ToString("D3"));
+                // 'T' 表示打开当前通道
                 commandBuilder.Append("T");
             }
+            // 以 C# 结尾表示执行命令(协议特定)
             commandBuilder.Append("C#");
 
             var response = await SendCommandAsync(commandBuilder.ToString());
+            // 期望设备返回 "!" 表示成功(根据协议)
             return response?.Trim() == "!";
         }
 
+        /// <summary>
+        /// 将所有通道设置为关闭状态。
+        /// 构造格式示例:S000F000F...C#
+        /// 其中 '000' 表示亮度 0,'F' 表示关闭通道。
+        /// </summary>
+        /// <returns>若设备返回确认字符 '!' 则认为操作成功。</returns>
         public override async Task<bool> TurnOffAllAsync()
         {
             StringBuilder commandBuilder = new StringBuilder("S");
             for (int i = 0; i < ChannelCount; i++)
             {
+                // 将每个通道设置为 000(亮度 0)并附带 'F' 动作表示关闭
                 commandBuilder.Append("000");
                 commandBuilder.Append("F");
             }
@@ -83,18 +131,33 @@ namespace TeamAAS_VP.Core.Lights
             return response?.Trim() == "!";
         }
 
+        /// <summary>
+        /// 将字符串命令编码为字节并通过协议发送,接收响应后以字符串返回。
+        /// </summary>
+        /// <param name="command">要发送的命令字符串(协议约定的格式)。</param>
+        /// <returns>设备响应的字符串表示(使用 ASCII 解码)。</returns>
         internal async Task<string> SendCommandAsync(string command)
         {
             var data = _encoding.GetBytes(command);
+            // 使用协议的 SendAndReceiveAsync 发送数据并等待响应(超时时间以协议或调用方为准)
             var response = await _protocol.SendAndReceiveAsync(data, 1000);
             return _encoding.GetString(response);
         }
 
+        /// <summary>
+        /// 读取指定通道的亮度(内部方法)。
+        /// 命令格式示例:S{ChannelLetter}#,例如读取第 0 通道为 S A #。
+        /// 响应预期长度为 5 字符,亮度位于索引 2..4(3 个字符)。
+        /// </summary>
+        /// <param name="channelIndex">通道索引(从 0 开始)。</param>
+        /// <returns>解析得到的亮度值(0-999);若解析失败或响应格式不符则返回 0。</returns>
         internal async Task<int> GetChannelBrightnessInternal(int channelIndex)
         {
+            // 通道字母从 'A' 开始递增
             string command = $"S{(char)('A' + channelIndex)}#";
             var response = await SendCommandAsync(command);
 
+            // 响应示例(假设):?XDDD(总长度 5),亮度在索引 2 开始的 3 个字符
             if (response.Length == 5)
             {
                 string valueStr = response.Substring(2, 3);
@@ -103,13 +166,23 @@ namespace TeamAAS_VP.Core.Lights
                     return brightness;
                 }
             }
+            // 返回默认亮度 0(表示读取失败或设备返回异常)
             return 0;
         }
 
+        /// <summary>
+        /// 设置指定通道的亮度(内部方法)。
+        /// 命令格式示例:S{ChannelLetter}0{DDD}#,其中 '0' 可能为协议占位符,具体请参考设备手册。
+        /// 成功时设备返回与通道字母相同的字符作为确认。
+        /// </summary>
+        /// <param name="channelIndex">通道索引(从 0 开始)。</param>
+        /// <param name="brightness">目标亮度(0-999)。方法内部会格式化为三位数字。</param>
+        /// <returns>若设备返回与通道字母相同的字符则认为设置成功。</returns>
         internal async Task<bool> SetChannelBrightnessInternal(int channelIndex, int brightness)
         {
             string command = $"S{(char)('A' + channelIndex)}0{brightness.ToString("D3")}#";
             var response = await SendCommandAsync(command);
+            // 成功时返回通道字母(例如 'A'),去除空白后比较
             return response?.Trim() == ((char)('A' + channelIndex)).ToString();
         }
     }

+ 25 - 0
TeamAAS-VM/Core/Lights/LightChannelEventArgs.cs

@@ -6,12 +6,37 @@ using System.Threading.Tasks;
 
 namespace TeamAAS_VP.Core.Lights
 {
+    /// <summary>
+    /// 提供有关单个灯光通道状态更改的事件数据。
+    /// </summary>
+    /// <remarks>
+    /// 此类型封装了触发灯光通道相关事件时需要传递的基本信息,
+    /// 包括通道实例、通道是否开启以及当前亮度值。
+    /// </remarks>
     public class LightChannelEventArgs : EventArgs
     {
+        /// <summary>
+        /// 获取触发事件的灯光通道实例。
+        /// </summary>
+        /// <seealso cref="ILightChannel"/>
         public ILightChannel Channel { get; }
+
+        /// <summary>
+        /// 获取通道当前的开/关状态;若为 <c>true</c> 则表示已开启。
+        /// </summary>
         public bool IsOn { get; }
+
+        /// <summary>
+        /// 获取通道当前的亮度值(具体取值范围由实现决定)。
+        /// </summary>
         public int Brightness { get; }
 
+        /// <summary>
+        /// 使用指定的通道、开关状态和亮度初始化 <see cref="LightChannelEventArgs"/> 的新实例。
+        /// </summary>
+        /// <param name="channel">触发事件的灯光通道实例。</param>
+        /// <param name="isOn">通道的开/关状态;为 <c>true</c> 表示已开启。</param>
+        /// <param name="brightness">通道的亮度值(实现决定其有效范围)。</param>
         public LightChannelEventArgs(ILightChannel channel, bool isOn, int brightness)
         {
             Channel = channel;

+ 81 - 2
TeamAAS-VM/Core/Lights/LightControllerBase.cs

@@ -7,19 +7,51 @@ using System.Threading.Tasks;
 namespace TeamAAS_VP.Core.Lights
 {
     /// <summary>
-    /// 光源控制器基类
+    /// 光源控制器基类。
+    /// 提供通用的光源控制和通道管理能力,具体控制器应从此类继承并实现硬件相关逻辑。
     /// </summary>
     public abstract class LightControllerBase : ILightController
     {
+        /// <summary>
+        /// 控制器唯一标识(只读)。
+        /// </summary>
         public int Id { get; }
-        public string Name { get; set; }    
+
+        /// <summary>
+        /// 控制器名称,可用于显示或日志记录。
+        /// </summary>
+        public string Name { get; set; }
+
+        /// <summary>
+        /// 光源模型信息(只读),用于指示此控制器对应的光源类型或型号。
+        /// </summary>
         public LightModel Model { get; }
+
+        /// <summary>
+        /// 控制器支持的通道数量(只读)。
+        /// </summary>
         public int ChannelCount { get; }
+
+        /// <summary>
+        /// 指示控制器当前是否与设备连接。派生类应在连接/断开时维护此状态。
+        /// </summary>
         public bool IsConnected { get; protected set; }
 
+        /// <summary>
+        /// 内部维护的通道列表(可变)。派生类或内部实现可通过此集合管理通道。
+        /// </summary>
         protected List<ILightChannel> ChannelsInternal { get; }
+
+        /// <summary>
+        /// 对外只读的通道集合,外部调用者只能读取,不可修改通道集合本身。
+        /// </summary>
         public IReadOnlyList<ILightChannel> Channels => ChannelsInternal;
 
+        /// <summary>
+        /// 构造函数:根据指定的 id 和通道数量初始化控制器实例并创建通道集合。
+        /// </summary>
+        /// <param name="id">控制器标识符。</param>
+        /// <param name="channelCount">通道数量,必须为非负整数。</param>
         protected LightControllerBase(int id,int channelCount)
         {
             Id = id;
@@ -28,6 +60,10 @@ namespace TeamAAS_VP.Core.Lights
             InitializeChannels();
         }
 
+        /// <summary>
+        /// 初始化通道集合,根据 <see cref="ChannelCount"/> 调用 <see cref="CreateChannel(int)"/> 创建每个通道。
+        /// 私有方法,在构造期间调用以确保通道列表已准备就绪。
+        /// </summary>
         private void InitializeChannels()
         {
             for (int i = 0; i < ChannelCount; i++)
@@ -36,14 +72,53 @@ namespace TeamAAS_VP.Core.Lights
             }
         }
 
+        /// <summary>
+        /// 创建指定索引处的通道实例。
+        /// 派生类必须实现此方法以返回实际的 <see cref="ILightChannel"/> 对象。
+        /// </summary>
+        /// <param name="index">通道索引(从 0 开始)。</param>
+        /// <returns>新创建的 <see cref="ILightChannel"/> 实例。</returns>
         protected abstract ILightChannel CreateChannel(int index);
 
+        /// <summary>
+        /// 异步连接到控制器对应的硬件或服务。
+        /// 在成功连接后,派生类应将 <see cref="IsConnected"/> 设置为 true。
+        /// </summary>
+        /// <returns>表示连接是否成功的任务(true 表示成功)。</returns>
         public abstract Task<bool> ConnectAsync();
+
+        /// <summary>
+        /// 异步断开与硬件或服务的连接。
+        /// 在断开后,派生类应将 <see cref="IsConnected"/> 设置为 false。
+        /// </summary>
+        /// <returns>表示断开操作完成的任务。</returns>
         public abstract Task DisconnectAsync();
+
+        /// <summary>
+        /// 异步初始化控制器(例如加载配置、检测硬件状态等)。
+        /// 在调用任何控制或通道相关操作前应先调用此方法。
+        /// </summary>
+        /// <returns>表示初始化是否成功的任务(true 表示成功)。</returns>
         public abstract Task<bool> InitializeAsync();
+
+        /// <summary>
+        /// 异步打开所有通道的光源。
+        /// 实现应保证在并发场景下的线程安全或在文档中说明限制。
+        /// </summary>
+        /// <returns>表示操作是否成功的任务(true 表示成功)。</returns>
         public abstract Task<bool> TurnOnAllAsync();
+
+        /// <summary>
+        /// 异步关闭所有通道的光源。
+        /// </summary>
+        /// <returns>表示操作是否成功的任务(true 表示成功)。</returns>
         public abstract Task<bool> TurnOffAllAsync();
 
+        /// <summary>
+        /// 释放托管资源的虚拟方法实现。
+        /// 派生类在覆盖时应调用 base.Dispose(disposing) 以确保基类资源被正确释放。
+        /// </summary>
+        /// <param name="disposing">若为 true,则同时释放托管资源;若为 false,仅释放非托管资源。</param>
         protected virtual void Dispose(bool disposing)
         {
             if (disposing)
@@ -56,6 +131,10 @@ namespace TeamAAS_VP.Core.Lights
             }
         }
 
+        /// <summary>
+        /// 释放当前实例占用的资源并通知垃圾回收器不再调用终结器。
+        /// 建议在不再使用控制器时显式调用此方法以尽早释放资源。
+        /// </summary>
         public void Dispose()
         {
             Dispose(true);

+ 16 - 0
TeamAAS-VM/Core/Lights/LightControllerEventArgs.cs

@@ -6,10 +6,26 @@ using System.Threading.Tasks;
 
 namespace TeamAAS_VP.Core.Lights
 {
+    /// <summary>
+    /// 表示与灯控制器相关的事件参数。
+    /// </summary>
+    /// <remarks>
+    /// 此类用于在事件中传递触发事件的 <see cref="ILightController"/> 实例。
+    /// </remarks>
     public class LightControllerEventArgs : EventArgs
     {
+        /// <summary>
+        /// 获取触发事件的灯控制器实例。
+        /// </summary>
+        /// <remarks>
+        /// 该属性可能为 <c>null</c>,具体取决于事件触发方是否提供了控制器实例。
+        /// </remarks>
         public ILightController Controller { get; }
 
+        /// <summary>
+        /// 使用指定的灯控制器实例初始化 <see cref="LightControllerEventArgs"/> 的新实例。
+        /// </summary>
+        /// <param name="controller">要与事件一起传递的 <see cref="ILightController"/> 实例。</param>
         public LightControllerEventArgs(ILightController controller)
         {
             Controller = controller;

+ 6 - 0
TeamAAS-VM/Core/Lights/LightModel.cs

@@ -6,8 +6,14 @@ using System.Threading.Tasks;
 
 namespace TeamAAS_VP.Core.Lights
 {
+    /// <summary>
+    /// 光源控制器型号
+    /// </summary>
     public enum LightModel
     {
+        /// <summary>
+        /// 顺尚信KDC-12V60W-4T
+        /// </summary>
         KCS_KDC_12V60W_4T,
     }
 }

+ 145 - 5
TeamAAS-VM/Core/Lights/SerialPortProtocol.cs

@@ -1,6 +1,44 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
+/*
+Pseudocode (详细计划):
+- 定义类 SerialPortProtocol,实现 ICommunicationProtocol。
+- 字段:
+  - _client: SerialPortClient 实例,用于串口通信。
+  - _waitClient: 等待客户端,用于发送后等待响应。
+  - _config: 只读配置对象 SerialPortConfig。
+- 属性:
+  - IsConnected: 检查 _client 是否在线。
+  - Terminator: 终止符设置(默认 None)。
+  - Encoding: 编码(默认 ASCII)。
+- 事件:
+  - ConnectionChanged: 连接状态变化时触发。
+  - DataReceived: 接收数据时触发(字符串形式)。
+  - DataSent: 发送数据时触发(字符串形式)。
+- 构造函数:
+  - 接收 SerialPortConfig 并保存到 _config。
+- ConnectAsync:
+  - 创建 SerialPortClient。
+  - 构建 TouchSocketConfig,设置串口选项(端口名、波特率、数据位、校验、停止位)。
+  - 设置数据处理适配器 PeriodPackageAdapter(CacheTimeout 100ms)。
+  - 调用 _client.Setup(config) 并 TryConnectAsync。
+  - 若成功,创建等待客户端并触发 ConnectionChanged(true),返回 true;否则触发 ConnectionChanged(false),返回 false。
+  - 捕获异常时也触发 ConnectionChanged(false) 并返回 false。
+- DisconnectAsync:
+  - 触发 ConnectionChanged(false),关闭 _client。
+- Send/SendAsync(字节/字符串):
+  - 检查 _client 是否存在,否则抛出 InvalidOperationException。
+  - 触发 DataSent(字符串形式)。
+  - 使用 _client.Send 或 _client.SendAsync 发送数据。
+- SendAndReceive/SendAndReceiveAsync(字节/字符串):
+  - 检查 _waitClient,否则抛出 InvalidOperationException。
+  - 触发 DataSent。
+  - 使用 _waitClient.SendThenReturn(Sync/Async) 等待响应。
+  - 触发 DataReceived 并返回响应(字节或字符串)。
+- Dispose:
+  -释放 _client 并置空 _waitClient。
+- 为所有公开成员添加 XML 文档注释,描述参数、返回值与异常。
+*/
+
+using System;
 using System.Text;
 using System.Threading.Tasks;
 using TeamAAS_VP.Enums;
@@ -11,25 +49,71 @@ using TouchSocket.Sockets;
 
 namespace TeamAAS_VP.Core.Lights
 {
+    /// <summary>
+    /// 使用 TouchSocket 的串口通信协议封装。
+    /// 提供同步/异步的发送与收发方法,并通过事件报告连接与数据状态。
+    /// </summary>
     public class SerialPortProtocol : ICommunicationProtocol
     {
+        /// <summary>
+        /// 内部串口客户端实例。
+        /// </summary>
         private SerialPortClient _client;
+
+        /// <summary>
+        /// 用于发送后等待响应的等待客户端。
+        /// </summary>
         private IWaitingClient<ISerialPortClient, IReceiverResult> _waitClient;
+
+        /// <summary>
+        /// 串口配置,只读。
+        /// </summary>
         private readonly SerialPortConfig _config;
 
+        /// <summary>
+        /// 获取是否已连接(客户端在线)。
+        /// </summary>
         public bool IsConnected => _client?.Online == true;
+
+        /// <summary>
+        /// 数据终止符,默认为 <see cref="Terminator.None"/>。
+        /// </summary>
         public Terminator Terminator { get; set; } = Terminator.None;
+
+        /// <summary>
+        /// 文本编码,默认使用 ASCII 编码。
+        /// </summary>
         public Encoding Encoding { get; set; } = Encoding.ASCII;
 
+        /// <summary>
+        /// 当连接状态改变时触发。参数为触发对象和连接状态(true=已连接)。
+        /// </summary>
         public event Action<object, bool> ConnectionChanged;
+
+        /// <summary>
+        /// 当接收到数据时触发,携带接收到的数据(字符串形式)。
+        /// </summary>
         public event Action<object, string> DataReceived;
+
+        /// <summary>
+        /// 当发送数据时触发,携带发送的数据(字符串形式)。
+        /// </summary>
         public event Action<object, string> DataSent;
 
+        /// <summary>
+        /// 使用指定的串口配置构建一个新的 <see cref="SerialPortProtocol"/> 实例。
+        /// </summary>
+        /// <param name="config">串口配置,不能为 null。</param>
+        /// <exception cref="ArgumentNullException">当 <paramref name="config"/> 为 null 时抛出。</exception>
         public SerialPortProtocol(SerialPortConfig config)
         {
-            _config = config;
+            _config = config ?? throw new ArgumentNullException(nameof(config));
         }
 
+        /// <summary>
+        /// 异步连接到串口并初始化等待客户端。
+        /// </summary>
+        /// <returns>连接成功返回 true,否则返回 false。</returns>
         public async Task<bool> ConnectAsync()
         {
             try
@@ -68,6 +152,10 @@ namespace TeamAAS_VP.Core.Lights
             }
         }
 
+        /// <summary>
+        /// 异步断开连接并关闭串口。
+        /// </summary>
+        /// <returns>完成任务。</returns>
         public Task DisconnectAsync()
         {
             ConnectionChanged?.Invoke(this, false);
@@ -75,6 +163,13 @@ namespace TeamAAS_VP.Core.Lights
             return Task.CompletedTask;
         }
 
+        /// <summary>
+        /// 异步发送字节数据并等待接收响应。
+        /// </summary>
+        /// <param name="data">要发送的字节数组。</param>
+        /// <param name="timeout">等待超时时间(毫秒),默认 5000ms。</param>
+        /// <returns>收到的字节数组。</returns>
+        /// <exception cref="InvalidOperationException">当尚未连接或等待客户端为 null 时抛出。</exception>
         public async Task<byte[]> SendAndReceiveAsync(byte[] data, int timeout = 5000)
         {
             if (_waitClient == null)
@@ -86,6 +181,12 @@ namespace TeamAAS_VP.Core.Lights
             return response;
         }
 
+        /// <summary>
+        /// 异步发送字节数据(不等待响应)。
+        /// </summary>
+        /// <param name="data">要发送的字节数组。</param>
+        /// <returns>完成任务。</returns>
+        /// <exception cref="InvalidOperationException">当客户端未初始化时抛出。</exception>
         public Task SendAsync(byte[] data)
         {
             if (_client == null)
@@ -96,6 +197,13 @@ namespace TeamAAS_VP.Core.Lights
             return Task.CompletedTask;
         }
 
+        /// <summary>
+        /// 异步发送文本并等待响应(字符串形式)。
+        /// </summary>
+        /// <param name="data">要发送的文本。</param>
+        /// <param name="timeout">等待超时时间(毫秒),默认 5000ms。</param>
+        /// <returns>收到的文本响应。</returns>
+        /// <exception cref="InvalidOperationException">当尚未连接或等待客户端为 null 时抛出。</exception>
         public async Task<string> SendAndReceiveAsync(string data, int timeout = 5000)
         {
 
@@ -108,6 +216,13 @@ namespace TeamAAS_VP.Core.Lights
             return Encoding.GetString(response);
         }
 
+        /// <summary>
+        /// 同步发送文本并等待响应(字符串形式)。
+        /// </summary>
+        /// <param name="data">要发送的文本。</param>
+        /// <param name="timeout">等待超时时间(毫秒),默认 5000ms。</param>
+        /// <returns>收到的文本响应。</returns>
+        /// <exception cref="InvalidOperationException">当尚未连接或等待客户端为 null 时抛出。</exception>
         public string SendAndReceive(string data, int timeout = 5000)
         {
             if (_waitClient == null)
@@ -119,6 +234,13 @@ namespace TeamAAS_VP.Core.Lights
             return Encoding.GetString(response);
         }
 
+        /// <summary>
+        /// 同步发送字节数据并等待响应。
+        /// </summary>
+        /// <param name="data">要发送的字节数组。</param>
+        /// <param name="timeout">等待超时时间(毫秒),默认 5000ms。</param>
+        /// <returns>收到的字节数组。</returns>
+        /// <exception cref="InvalidOperationException">当尚未连接或等待客户端为 null 时抛出。</exception>
         public byte[] SendAndReceive(byte[] data, int timeout = 5000)
         {
             if (_waitClient == null)
@@ -130,6 +252,11 @@ namespace TeamAAS_VP.Core.Lights
             return response;
         }
 
+        /// <summary>
+        /// 同步发送文本(不等待响应)。
+        /// </summary>
+        /// <param name="data">要发送的文本。</param>
+        /// <exception cref="InvalidOperationException">当客户端未初始化时抛出。</exception>
         public void Send(string data)
         {
             if (_client == null)
@@ -139,6 +266,12 @@ namespace TeamAAS_VP.Core.Lights
             _client.Send(Encoding.GetBytes(data));
         }
 
+        /// <summary>
+        /// 异步发送文本(不等待响应)。
+        /// </summary>
+        /// <param name="data">要发送的文本。</param>
+        /// <returns>完成任务。</returns>
+        /// <exception cref="InvalidOperationException">当客户端未初始化时抛出。</exception>
         public async Task SendAsync(string data)
         {
             if (_client == null)
@@ -148,6 +281,11 @@ namespace TeamAAS_VP.Core.Lights
             await _client.SendAsync(Encoding.GetBytes(data));
         }
 
+        /// <summary>
+        /// 同步发送字节数据(不等待响应)。
+        /// </summary>
+        /// <param name="data">要发送的字节数组。</param>
+        /// <exception cref="InvalidOperationException">当客户端未初始化时抛出。</exception>
         public void Send(byte[] data)
         {
             if (_client == null)
@@ -157,7 +295,9 @@ namespace TeamAAS_VP.Core.Lights
             _client.Send(data);
         }
 
-
+        /// <summary>
+        /// 释放底层资源。调用后实例不应再被使用。
+        /// </summary>
         public void Dispose()
         {
             _client?.Dispose();

+ 134 - 1
TeamAAS-VM/Core/Lights/TcpProtocol.cs

@@ -1,4 +1,24 @@
-using System;
+/* 
+  伪代码计划(详细步骤):
+  1. 在文件顶部添加多行注释,说明将要执行的注释生成工作(用于审阅)。
+  2. 为公共类 `TcpProtocol` 添加 XML 文档注释,描述其用途与行为。
+  3. 为构造函数添加注释,说明参数含义及默认值。
+  4. 为每个公共属性(如 `IsConnected`,`_waitClient`,`Terminator`,`Encoding`)添加 XML 注释,说明返回值或作用,指明线程/连接相关注意事项(只读/可写)。
+  5. 为事件(`ConnectionChanged`、`DataReceived`、`DataSent`)添加注释,说明何时触发、参数含义。
+  6. 为每个公共方法添加 XML 注释:
+     - `ConnectAsync`: 说明尝试连接的行为、返回值及异常处理,标注使用的配置项(结束符处理)。
+     - `DisconnectAsync`: 说明断开连接的行为及事件触发。
+     - `SendAndReceiveAsync(byte[], int)`: 说明发送/接收的流程、超时含义及可能抛出的异常。
+     - `SendAsync(byte[])`, `Send(byte[])`, `Send(string)`, `SendAsync(string)`: 说明发送方法区别与同步/异步注意事项。
+     - 同步版本的 `SendAndReceive` 方法说明阻塞行为与异常。
+     - `Dispose`: 说明释放资源。
+  7. 保持现有实现不变,仅插入文档注释与必要的内部注释,以便于 IntelliSense 与维护。
+  8. 确保注释为中文,简洁明了,符合 .NET XML 注释惯例,并且不修改代码逻辑或签名。
+
+  注:所有注释均以 XML 文档注释形式写入,便于 Visual Studio 的 IntelliSense 展示。
+*/
+
+using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Net;
@@ -13,21 +33,71 @@ using TouchSocket.Sockets;
 
 namespace TeamAAS_VP.Core.Lights
 {
+    /// <summary>
+    /// 基于 TouchSocket 的 TCP 通信协议实现。
+    /// 提供同步/异步的发送/接收方法,并在连接状态、发送/接收数据时触发事件。
+    /// 注意:此类不保证线程安全,调用方应在多线程场景做并发控制。
+    /// </summary>
     public class TcpProtocol : ICommunicationProtocol
     {
+        /// <summary>
+        /// 目标主机地址(仅构造时设置)。
+        /// </summary>
         private readonly string _host;
+
+        /// <summary>
+        /// 目标端口(仅构造时设置)。
+        /// </summary>
         private readonly int _port;
+
+        /// <summary>
+        /// TouchSocket 的 TCP 客户端实例。
+        /// </summary>
         private TcpClient _client;
 
+        /// <summary>
+        /// 获取当前连接状态。若未初始化客户端或客户端不在线,则返回 false。
+        /// </summary>
         public bool IsConnected => _client?.Online == true;
+
+        /// <summary>
+        /// 等待客户端,用于发送后等待返回(SendThenReturn)的操作。
+        /// 注意:该字段在连接成功后由 ConnectAsync 初始化。
+        /// </summary>
         public IWaitingClient<ITcpClient, IReceiverResult> _waitClient { get; private set; }
+
+        /// <summary>
+        /// 数据包结束符配置,决定接收时的数据分包行为(None/CR/LF/CRLF)。
+        /// </summary>
         public Terminator Terminator { get; set; } = Terminator.None;
+
+        /// <summary>
+        /// 数据编码,默认使用 ASCII。
+        /// 在发送/接收时用于将字符串与字节数组互相转换。
+        /// </summary>
         public Encoding Encoding { get; set; } = Encoding.ASCII;
 
+        /// <summary>
+        /// 当连接状态发生变化时触发。参数:sender、是否已连接(true=已连接)。
+        /// </summary>
         public event Action<object, bool> ConnectionChanged;
+
+        /// <summary>
+        /// 当接收到数据时触发。参数:sender、接收到的数据(已使用 <see cref="Encoding"/> 转为字符串)。
+        /// </summary>
         public event Action<object, string> DataReceived;
+
+        /// <summary>
+        /// 当发送数据时触发。参数:sender、发送的数据(字符串形式,使用 <see cref="Encoding"/> 转换)。
+        /// </summary>
         public event Action<object, string> DataSent;
 
+        /// <summary>
+        /// 创建一个新的 <see cref="TcpProtocol"/> 实例。
+        /// </summary>
+        /// <param name="host">目标主机 IP 地址字符串。</param>
+        /// <param name="port">目标端口号。</param>
+        /// <param name="terminator">可选的数据结束符配置,默认为 <see cref="Terminator.None"/>。</param>
         public TcpProtocol(string host, int port, Terminator terminator = Terminator.None)
         {
             _host = host;
@@ -35,6 +105,11 @@ namespace TeamAAS_VP.Core.Lights
             Terminator = terminator;
         }
 
+        /// <summary>
+        /// 异步连接到远端主机并根据 <see cref="Terminator"/> 配置数据分包适配器。
+        /// 成功连接后会触发 <see cref="ConnectionChanged"/> 事件。
+        /// </summary>
+        /// <returns>如果连接成功返回 true,否则返回 false。</returns>
         public async Task<bool> ConnectAsync()
         {
             try
@@ -91,6 +166,10 @@ namespace TeamAAS_VP.Core.Lights
             }
         }
 
+        /// <summary>
+        /// 异步断开当前连接并触发 <see cref="ConnectionChanged"/> 事件(false)。
+        /// </summary>
+        /// <returns>已完成的任务。</returns>
         public Task DisconnectAsync()
         {
             ConnectionChanged?.Invoke(this, false);
@@ -98,6 +177,13 @@ namespace TeamAAS_VP.Core.Lights
             return Task.CompletedTask;
         }
 
+        /// <summary>
+        /// 异步发送字节数据并等待响应。
+        /// </summary>
+        /// <param name="data">要发送的字节数组。</param>
+        /// <param name="timeout">等待响应的超时时间(毫秒),默认 5000 毫秒。</param>
+        /// <returns>返回接收到的字节数组。</returns>
+        /// <exception cref="InvalidOperationException">当尚未连接或等待客户端未初始化时抛出。</exception>
         public async Task<byte[]> SendAndReceiveAsync(byte[] data, int timeout = 5000)
         {
             if (_waitClient == null)
@@ -109,6 +195,13 @@ namespace TeamAAS_VP.Core.Lights
             return response;
         }
 
+        /// <summary>
+        /// 异步发送字节数据(不等待响应)。
+        /// 注意:内部使用同步发送接口,立即返回 Task.CompletedTask。
+        /// </summary>
+        /// <param name="data">要发送的字节数组。</param>
+        /// <returns>已完成的任务。</returns>
+        /// <exception cref="InvalidOperationException">当尚未连接时抛出。</exception>
         public Task SendAsync(byte[] data)
         {
             if (_client == null)
@@ -119,6 +212,13 @@ namespace TeamAAS_VP.Core.Lights
             return Task.CompletedTask;
         }
 
+        /// <summary>
+        /// 异步发送字符串并等待响应(使用当前 <see cref="Encoding"/> 编码)。
+        /// </summary>
+        /// <param name="data">要发送的字符串。</param>
+        /// <param name="timeout">等待响应的超时时间(毫秒),默认 5000 毫秒。</param>
+        /// <returns>接收到的字符串响应(使用当前编码解码)。</returns>
+        /// <exception cref="InvalidOperationException">当尚未连接或等待客户端未初始化时抛出。</exception>
         public async Task<string> SendAndReceiveAsync(string data, int timeout = 5000)
         {
 
@@ -131,6 +231,13 @@ namespace TeamAAS_VP.Core.Lights
             return Encoding.GetString(response);
         }
 
+        /// <summary>
+        /// 同步发送字符串并等待响应(阻塞调用线程)。
+        /// </summary>
+        /// <param name="data">要发送的字符串。</param>
+        /// <param name="timeout">等待响应的超时时间(毫秒),默认 5000 毫秒。</param>
+        /// <returns>接收到的字符串响应(使用当前编码解码)。</returns>
+        /// <exception cref="InvalidOperationException">当尚未连接或等待客户端未初始化时抛出。</exception>
         public string SendAndReceive(string data, int timeout = 5000)
         {
             if (_waitClient == null)
@@ -142,6 +249,13 @@ namespace TeamAAS_VP.Core.Lights
             return Encoding.GetString(response);
         }
 
+        /// <summary>
+        /// 同步发送字节数组并等待响应(阻塞调用线程)。
+        /// </summary>
+        /// <param name="data">要发送的字节数组。</param>
+        /// <param name="timeout">等待响应的超时时间(毫秒),默认 5000 毫秒。</param>
+        /// <returns>接收到的字节数组。</returns>
+        /// <exception cref="InvalidOperationException">当尚未连接或等待客户端未初始化时抛出。</exception>
         public byte[] SendAndReceive(byte[] data, int timeout = 5000)
         {
             if (_waitClient == null)
@@ -153,6 +267,11 @@ namespace TeamAAS_VP.Core.Lights
             return response;
         }
 
+        /// <summary>
+        /// 同步发送字符串(不等待响应)。
+        /// </summary>
+        /// <param name="data">要发送的字符串。</param>
+        /// <exception cref="InvalidOperationException">当尚未连接时抛出。</exception>
         public void Send(string data)
         {
             if (_client == null)
@@ -162,6 +281,12 @@ namespace TeamAAS_VP.Core.Lights
             _client.Send(Encoding.GetBytes(data));
         }
 
+        /// <summary>
+        /// 异步发送字符串(不等待响应),使用客户端的异步发送接口。
+        /// </summary>
+        /// <param name="data">要发送的字符串。</param>
+        /// <returns>发送完成的任务。</returns>
+        /// <exception cref="InvalidOperationException">当尚未连接时抛出。</exception>
         public async Task SendAsync(string data)
         {
             if (_client == null)
@@ -171,6 +296,11 @@ namespace TeamAAS_VP.Core.Lights
             await _client.SendAsync(Encoding.GetBytes(data));
         }
 
+        /// <summary>
+        /// 同步发送字节数组(不等待响应)。
+        /// </summary>
+        /// <param name="data">要发送的字节数组。</param>
+        /// <exception cref="InvalidOperationException">当尚未连接时抛出。</exception>
         public void Send(byte[] data)
         {
             if (_client == null)
@@ -180,6 +310,9 @@ namespace TeamAAS_VP.Core.Lights
             _client.Send(data);
         }
 
+        /// <summary>
+        /// 释放底层客户端资源。调用后实例不应再使用。
+        /// </summary>
         public void Dispose()
         {
             _client?.Dispose();

+ 80 - 0
TeamAAS-VM/Interfaces/ILightManagerService.cs

@@ -9,26 +9,106 @@ using TeamAAS_VP.Models.Lights;
 
 namespace TeamAAS_VP.Interfaces
 {
+    /// <summary>
+    /// 管理整套灯控器与全局通道的服务接口。
+    /// 提供控制器注册/注销、全局通道亮度与开关操作、连接管理以及配置持久化等功能。
+    /// </summary>
     public interface ILightManagerService
     {
+        /// <summary>
+        /// 当新的灯控制器被注册并添加到管理器时触发。
+        /// 事件参数包含被添加的控制器信息与对应 Id。
+        /// </summary>
         event EventHandler<LightControllerEventArgs> ControllerAdded;
+
+        /// <summary>
+        /// 当已有的灯控制器被从管理器中移除时触发。
+        /// 事件参数包含被移除的控制器信息与对应 Id。
+        /// </summary>
         event EventHandler<LightControllerEventArgs> ControllerRemoved;
+
+        /// <summary>
+        /// 当任意全局通道的状态(例如亮度或开关状态)发生变化时触发。
+        /// 事件参数包含受影响的通道信息与新状态。
+        /// </summary>
         event EventHandler<LightChannelEventArgs> ChannelStatusChanged;
 
+        /// <summary>
+        /// 当前已注册的控制器集合(只读)。
+        /// 键为控制器 Id,值为对应的 <see cref="ILightController"/> 实例。
+        /// </summary>
         IReadOnlyDictionary<int, ILightController> Controllers { get; }
+
+        /// <summary>
+        /// 全局通道集合(只读)。
+        /// 键为全局通道 Id,值为对应的 <see cref="ILightChannel"/> 实例。
+        /// </summary>
         IReadOnlyDictionary<int, ILightChannel> GlobalChannels { get; }
 
+        /// <summary>
+        /// 异步注册并初始化一个新的灯控制器。
+        /// </summary>
+        /// <param name="id">要注册的控制器 Id,必须唯一。</param>
+        /// <param name="configuration">该控制器的配置数据。</param>
+        /// <returns>
+        /// 返回已注册并初始化的 <see cref="ILightController"/> 实例。
+        /// 如果注册失败,返回的任务可能抛出异常或返回 null(由具体实现决定)。
+        /// </returns>
         Task<ILightController> RegisterControllerAsync(int id, LightControllerConfig configuration);
+
+        /// <summary>
+        /// 异步注销并移除指定 Id 的控制器。
+        /// </summary>
+        /// <param name="controllerId">要注销的控制器 Id。</param>
+        /// <returns>操作成功返回 true;如果找不到控制器或注销失败则返回 false。</returns>
         Task<bool> UnregisterControllerAsync(int controllerId);
 
+        /// <summary>
+        /// 为指定的全局通道设置亮度值。
+        /// </summary>
+        /// <param name="globalChannelId">目标全局通道 Id。</param>
+        /// <param name="brightness">亮度值,通常在 0 到 100 范围内(具体范围请参照实现)。</param>
+        /// <returns>操作成功返回 true,否则返回 false。</returns>
         Task<bool> SetGlobalChannelBrightnessAsync(int globalChannelId, int brightness);
+
+        /// <summary>
+        /// 打开指定的全局通道(将其置于开启状态)。
+        /// </summary>
+        /// <param name="globalChannelId">目标全局通道 Id。</param>
+        /// <returns>操作成功返回 true,否则返回 false。</returns>
         Task<bool> TurnOnGlobalChannelAsync(int globalChannelId);
+
+        /// <summary>
+        /// 关闭指定的全局通道(将其置于关闭状态)。
+        /// </summary>
+        /// <param name="globalChannelId">目标全局通道 Id。</param>
+        /// <returns>操作成功返回 true,否则返回 false。</returns>
         Task<bool> TurnOffGlobalChannelAsync(int globalChannelId);
 
+        /// <summary>
+        /// 尝试连接所有已注册的控制器并建立通信通道。
+        /// </summary>
+        /// <returns>所有连接成功则返回 true;若有失败则返回 false(或由实现抛出异常以提供详细错误)。</returns>
         Task<bool> ConnectAllAsync();
+
+        /// <summary>
+        /// 断开所有已连接的控制器并释放相关资源。
+        /// </summary>
+        /// <returns>所有断开成功则返回 true;若有失败则返回 false(或由实现抛出异常以提供详细错误)。returns>
         Task<bool> DisconnectAllAsync();
 
+        /// <summary>
+        /// 将当前管理器的配置(包括控制器和全局通道映射)保存到指定文件路径。
+        /// </summary>
+        /// <param name="filePath">目标文件路径,支持绝对或相对路径。</param>
+        /// <returns>完成保存操作的任务。实现可能在 I/O 错误时抛出异常。</returns>
         Task SaveConfigurationAsync(string filePath);
+
+        /// <summary>
+        /// 从指定文件路径加载管理器配置并应用到当前实例。
+        /// </summary>
+        /// <param name="filePath">包含配置的文件路径。</param>
+        /// <returns>完成加载操作的任务。实现可能在文件不存在或格式不正确时抛出异常。</returns>
         Task LoadConfigurationAsync(string filePath);
     }
 }

+ 31 - 6
TeamAAS-VM/Models/Lights/ChannelConfig.cs

@@ -7,11 +7,17 @@ using System.Threading.Tasks;
 
 namespace TeamAAS_VP.Models.Lights
 {
+    /// <summary>
+    /// 表示单个灯通道的配置项。
+    /// 包含通道名称、描述、控制器内索引、系统全局索引以及默认亮度等信息。
+    /// 该类继承自 <see cref="BindableBase"/>,支持属性变更通知,适用于 WPF 数据绑定。
+    /// </summary>
     public class ChannelConfig: BindableBase
     {
         private string _Name;
         /// <summary>
-        /// 通道名称
+        /// 通道名称。
+        /// 用于界面显示和识别通道,可为空字符串。
         /// </summary>
         public string Name
         {
@@ -21,7 +27,8 @@ namespace TeamAAS_VP.Models.Lights
 
         private string _Description;
         /// <summary>
-        /// 通道描述
+        /// 通道描述。
+        /// 对通道的补充说明,可用于显示在工具提示或详细信息面板中。
         /// </summary>
         public string Description
         {
@@ -31,7 +38,8 @@ namespace TeamAAS_VP.Models.Lights
 
         private int _LocalIndex;
         /// <summary>
-        /// 控制器内的通道索引
+        /// 控制器内的通道索引(局部索引)。
+        /// 表示该通道在所属控制器中的位置,通常从 0 或 1 开始,取决于系统约定。
         /// </summary>
         public int LocalIndex
         {
@@ -41,7 +49,8 @@ namespace TeamAAS_VP.Models.Lights
 
         private int _GlobalIndex;
         /// <summary>
-        /// 系统中的全局通道索引
+        /// 系统中的全局通道索引(全局唯一)。
+        /// 在多控制器环境中用于唯一标识一个通道。
         /// </summary>
         public int GlobalIndex
         {
@@ -49,19 +58,35 @@ namespace TeamAAS_VP.Models.Lights
             set { SetProperty(ref _GlobalIndex, value); }
         }
 
-        private int _DefaultBrightness=100;
+        private int _DefaultBrightness = 100;
+        /// <summary>
+        /// 默认亮度(百分比)。
+        /// 范围通常为 0 到 100,表示通道的默认亮度设置。默认值为 100(表示最大亮度)。
+        /// </summary>
         public int DefaultBrightness
         {
             get { return _DefaultBrightness; }
             set { SetProperty(ref _DefaultBrightness, value); }
         }
 
+        /// <summary>
+        /// 初始化 <see cref="ChannelConfig"/> 的新实例。
+        /// 创建一个默认的通道配置对象,属性使用默认值。
+        /// </summary>
         public ChannelConfig()
         {
 
         }
 
-        public ChannelConfig(int localIndex, int globalIndex, string name = "", string description = "", int defaultBrightness=100)
+        /// <summary>
+        /// 使用指定的索引和可选信息初始化 <see cref="ChannelConfig"/> 的新实例。
+        /// </summary>
+        /// <param name="localIndex">控制器内的通道索引(局部索引)。</param>
+        /// <param name="globalIndex">系统中的全局通道索引(全局唯一)。</param>
+        /// <param name="name">通道名称,可选,默认为空字符串。</param>
+        /// <param name="description">通道描述,可选,默认为空字符串。</param>
+        /// <param name="defaultBrightness">默认亮度(百分比),可选,默认值为 100。期望范围为 0 到 100。</param>
+        public ChannelConfig(int localIndex, int globalIndex, string name = "", string description = "", int defaultBrightness = 100)
         {
             LocalIndex = localIndex;
             GlobalIndex = globalIndex;

+ 52 - 0
TeamAAS-VM/Models/Lights/LightControllerConfig.cs

@@ -10,15 +10,28 @@ using TeamAAS_VP.Enums;
 
 namespace TeamAAS_VP.Models.Lights
 {
+    /// <summary>
+    /// 表示灯控制器的配置模型。
+    /// 根据所选的 <see cref="LightModel"/> 执行不同的初始化逻辑(如通道数量、串口或 TCP 配置等)。
+    /// 该类继承自 Prism 的 <see cref="BindableBase"/>,支持属性变更通知,适用于 WPF MVVM 绑定。
+    /// </summary>
     public class LightControllerConfig: BindableBase
     {
         private int _Id;
+        /// <summary>
+        /// 配置项的唯一标识符。
+        /// 用于在集合或持久化时区分不同的控制器配置。
+        /// </summary>
         public int Id
         {
             get { return _Id; }
             set { SetProperty(ref _Id, value); }
         }
+
         private string _Name;
+        /// <summary>
+        /// 控制器的显示名称或别名,便于在 UI 中识别。
+        /// </summary>
         public string Name
         {
             get { return _Name; }
@@ -26,6 +39,10 @@ namespace TeamAAS_VP.Models.Lights
         }
 
         private int _ChannelCount;
+        /// <summary>
+        /// 控制器的通道数(例如 4 通道控制器则为 4)。
+        /// 该值会根据 <see cref="LightModel"/> 的不同在 setter 中被自动设置。
+        /// </summary>
         public int ChannelCount
         {
             get { return _ChannelCount; }
@@ -33,27 +50,37 @@ namespace TeamAAS_VP.Models.Lights
         }
 
         private LightModel _LightModel;
+        /// <summary>
+        /// 灯控制器的型号枚举。设置此属性将触发根据型号进行的配置初始化逻辑(通道、串口、TCP 等)。
+        /// </summary>
         public LightModel LightModel
         {
             get { return _LightModel; }
             set {
                 if (SetProperty(ref _LightModel, value))
                 {
+                    // 当 LightModel 发生变化时,根据不同型号初始化相关字段与集合。
                     switch (value)
                     {
                         case LightModel.KCS_KDC_12V60W_4T:
+                            // 该型号为 4 通道的串口控制器(示例)
                             ChannelCount = 4;
+                            // 对于串口型号,清空/禁用 TCP 配置
                             TcpConfig = null;
+                            // 如果尚未创建串口配置则初始化
                             if (SerialPortConfig == null)
                             {
                                 SerialPortConfig = new SerialPortConfig();
                             }
+
+                            // 初始化通道集合,确保包含正确数量的 ChannelConfig
                             ChannelConfigs = new ObservableCollection<ChannelConfig>();
                             if (ChannelConfigs.Count != ChannelCount)
                             {
                                 ChannelConfigs.Clear();
                                 for (int i = 0; i < ChannelCount; i++)
                                 {
+                                    // 为每个通道创建默认配置:本地索引/全局索引、名称、描述和默认亮度
                                     ChannelConfigs.Add(new ChannelConfig()
                                     {
                                         LocalIndex = i,
@@ -67,6 +94,7 @@ namespace TeamAAS_VP.Models.Lights
 
                             break;
                         default:
+                            // 对于未知或未实现的型号,清理所有特定配置并使用默认空集合
                             ChannelConfigs = new ObservableCollection<ChannelConfig>();
                             SerialPortConfig = null;
                             TcpConfig = null;
@@ -77,6 +105,10 @@ namespace TeamAAS_VP.Models.Lights
         }
 
         private ObservableCollection<ChannelConfig> _ChannelConfigs;
+        /// <summary>
+        /// 表示控制器包含的通道配置集合。
+        /// 该集合常用于在 UI 中进行通道列表绑定与编辑。
+        /// </summary>
         public ObservableCollection<ChannelConfig> ChannelConfigs
         {
             get { return _ChannelConfigs; }
@@ -84,6 +116,10 @@ namespace TeamAAS_VP.Models.Lights
         }
 
         private TcpConfig _TcpConfig;
+        /// <summary>
+        /// 如果控制器通过 TCP/IP 通信,则此属性保存其网络配置(主机、端口等)。
+        /// 对于串口型号,该值通常为 null。
+        /// </summary>
         public TcpConfig TcpConfig
         {
             get { return _TcpConfig; }
@@ -91,17 +127,31 @@ namespace TeamAAS_VP.Models.Lights
         }
 
         private SerialPortConfig _SerialPortConfig;
+        /// <summary>
+        /// 如果控制器通过串口通信,则此属性保存其串口配置(COM 口、波特率等)。
+        /// 对于 TCP 型号,该值通常为 null。
+        /// </summary>
         public SerialPortConfig SerialPortConfig
         {
             get { return _SerialPortConfig; }
             set { SetProperty(ref _SerialPortConfig, value); }
         }
 
+        /// <summary>
+        /// 默认构造函数:仅初始化通道集合为空集合,具体内容由后续设置 <see cref="LightModel"/> 决定。
+        /// </summary>
         public LightControllerConfig()
         {
             ChannelConfigs = new ObservableCollection<ChannelConfig>();
         }
 
+        /// <summary>
+        /// 带参数的构造函数:使用给定的 id、name 和 model 初始化配置。
+        /// 根据 <paramref name="model"/> 的不同执行相应的初始化逻辑(与 setter 中的逻辑一致)。
+        /// </summary>
+        /// <param name="id">配置的唯一标识符。</param>
+        /// <param name="name">配置的显示名称。</param>
+        /// <param name="model">控制器型号,用于决定初始化细节。</param>
         public LightControllerConfig(int id, string name, LightModel model)
         {
             Id = id;
@@ -110,6 +160,7 @@ namespace TeamAAS_VP.Models.Lights
             switch (LightModel)
             {
                 case LightModel.KCS_KDC_12V60W_4T:
+                    // 该型号为 4 通道的串口控制器(示例),与 setter 中保持一致的初始化
                     ChannelCount = 4;
                     TcpConfig = null;
                     SerialPortConfig = new SerialPortConfig();
@@ -127,6 +178,7 @@ namespace TeamAAS_VP.Models.Lights
                     }
                     break;
                 default:
+                    // 未知型号:初始化空集合并清空通信配置
                     ChannelConfigs = new ObservableCollection<ChannelConfig>();
                     SerialPortConfig = null;
                     TcpConfig = null;

+ 112 - 8
TeamAAS-VM/Services/LightManagerService.cs

@@ -10,20 +10,62 @@ using TeamAAS_VP.Models.Lights;
 
 namespace TeamAAS_VP.Services
 {
+    /// <summary>
+    /// 管理多个灯光控制器及其全局通道的服务。
+    /// 提供控制器注册/注销、全局通道的亮度与开关控制以及批量连接/断开功能。
+    /// </summary>
     public class LightManagerService : ILightManagerService
     {
+        /// <summary>
+        /// 本地按控制器ID索引的控制器集合。
+        /// 键:控制器ID,值:实现 <see cref="ILightController"/> 的实例。
+        /// </summary>
         private readonly Dictionary<int, ILightController> _controllers;
+
+        /// <summary>
+        /// 全局通道映射。键为全局通道ID,值为具体的通道实例。
+        /// </summary>
         private readonly Dictionary<int, ILightChannel> _globalChannels;
+
+        /// <summary>
+        /// 不同灯具型号对应的工厂方法字典。
+        /// 键:灯具型号,值:创建控制器的工厂函数 (id, config) => ILightController
+        /// </summary>
         private readonly Dictionary<LightModel, Func<int, TeamAAS_VP.Models.Lights.LightControllerConfig, ILightController>> _controllerFactories;
+
+        /// <summary>
+        /// 下一个可分配的全局通道ID(用于冲突时自动分配)。
+        /// </summary>
         private int _nextGlobalChannelId = 0;
 
+        /// <summary>
+        /// 只读访问已注册的控制器集合。
+        /// </summary>
         public IReadOnlyDictionary<int, ILightController> Controllers => _controllers;
+
+        /// <summary>
+        /// 只读访问全局通道映射。
+        /// </summary>
         public IReadOnlyDictionary<int, ILightChannel> GlobalChannels => _globalChannels;
 
+        /// <summary>
+        /// 当控制器被添加时触发的事件。
+        /// </summary>
         public event EventHandler<LightControllerEventArgs> ControllerAdded;
+
+        /// <summary>
+        /// 当控制器被移除时触发的事件。
+        /// </summary>
         public event EventHandler<LightControllerEventArgs> ControllerRemoved;
+
+        /// <summary>
+        /// 当某个全局通道状态(开/关/亮度)变化时触发的事件。
+        /// </summary>
         public event EventHandler<LightChannelEventArgs> ChannelStatusChanged;
 
+        /// <summary>
+        /// 构造函数,初始化内部字典并注册默认的控制器工厂。
+        /// </summary>
         public LightManagerService()
         {
             _controllers = new Dictionary<int, ILightController>();
@@ -33,22 +75,40 @@ namespace TeamAAS_VP.Services
             RegisterDefaultFactories();
         }
 
+        /// <summary>
+        /// 注册默认的控制器工厂(当前含 KCS 型号)。
+        /// </summary>
         private void RegisterDefaultFactories()
         {
             // 注册KCS控制器工厂
             RegisterControllerFactory(LightModel.KCS_KDC_12V60W_4T, (id, config) =>
             {
                 if (config == null) throw new ArgumentNullException(nameof(config));
+                // 使用配置里的串口配置创建协议实现,再创建控制器实例
                 var protocol = new SerialPortProtocol(config.SerialPortConfig);
                 return new KCSLightController(id, protocol, config.ChannelCount);
             });
         }
 
+        /// <summary>
+        /// 手动注册一个控制器工厂,用于扩展支持更多型号。
+        /// </summary>
+        /// <param name="model">灯具模型</param>
+        /// <param name="factory">工厂函数:根据 id 和配置返回 <see cref="ILightController"/> 实例</param>
         public void RegisterControllerFactory(LightModel model, Func<int, TeamAAS_VP.Models.Lights.LightControllerConfig, ILightController> factory)
         {
             _controllerFactories[model] = factory;
         }
 
+        /// <summary>
+        /// 异步注册控制器并建立其通道到全局通道ID的映射。
+        /// </summary>
+        /// <param name="id">控制器ID(局部)</param>
+        /// <param name="configuration">控制器配置</param>
+        /// <returns>已创建的控制器实例</returns>
+        /// <exception cref="ArgumentNullException">当配置为 null 时抛出</exception>
+        /// <exception cref="ArgumentException">当同ID已存在或未注册工厂时抛出</exception>
+        /// <exception cref="InvalidOperationException">当工厂返回 null 时抛出</exception>
         public async Task<ILightController> RegisterControllerAsync(int id, TeamAAS_VP.Models.Lights.LightControllerConfig configuration)
         {
             if (configuration == null) throw new ArgumentNullException(nameof(configuration));
@@ -63,15 +123,16 @@ namespace TeamAAS_VP.Services
             if (controller == null)
                 throw new InvalidOperationException("Factory returned null controller");
 
+            // 将控制器加入管理集合
             _controllers[id] = controller;
 
-            // 映射全局通道
+            // 映射控制器内部通道到全局通道ID
             for (int i = 0; i < controller.Channels.Count; i++)
             {
                 var channelConfig = configuration.ChannelConfigs[i];
                 int globalIndex = channelConfig.GlobalIndex;
 
-                // 检查该全局ID是否已被占用
+                // 若指定的全局ID已被占用,则自动分配下一个可用ID
                 if (_globalChannels.ContainsKey(globalIndex))
                 {
                     // 自动重新分配全局ID
@@ -79,30 +140,36 @@ namespace TeamAAS_VP.Services
                 }
                 else if (globalIndex >= _nextGlobalChannelId)
                 {
-                    // 更新下一个可用ID
+                    // 若指定ID大于等于当前_nextGlobalChannelId,则推进_nextGlobalChannelId以避免重复
                     _nextGlobalChannelId = globalIndex + 1;
                 }
 
-                // 更新配置中的GlobalIndex(如果需要
+                // 如果配置中的GlobalIndex与最终分配不一致,则更新配置(保持配置与运行时一致
                 if (channelConfig.GlobalIndex != globalIndex)
                 {
                     channelConfig.GlobalIndex = globalIndex;
                 }
 
-                // 建立映射
+                // 在全局映射表中建立映射:全局ID -> 控制器通道实例
                 _globalChannels[globalIndex] = controller.Channels[i];
             }
 
+            // 通知订阅方有新控制器添加
             ControllerAdded?.Invoke(this, new LightControllerEventArgs(controller));
             return controller;
         }
 
+        /// <summary>
+        /// 注销指定ID的控制器,断开并释放资源,同时移除其全局通道映射。
+        /// </summary>
+        /// <param name="controllerId">要注销的控制器ID</param>
+        /// <returns>如果存在并成功注销返回 true,否则 false</returns>
         public async Task<bool> UnregisterControllerAsync(int controllerId)
         {
             if (!_controllers.TryGetValue(controllerId, out var controller))
                 return false;
 
-            // 移除全局通道映射
+            // 找到属于该控制器的全局通道键并移除
             var channelsToRemove = _globalChannels
                 .Where(kvp => controller.Channels.Contains(kvp.Value))
                 .Select(kvp => kvp.Key)
@@ -113,24 +180,38 @@ namespace TeamAAS_VP.Services
                 _globalChannels.Remove(channelId);
             }
 
+            // 从管理集合中移除控制器并断开连接、释放资源
             _controllers.Remove(controllerId);
             await controller.DisconnectAsync();
             controller.Dispose();
 
+            // 通知订阅方控制器已移除
             ControllerRemoved?.Invoke(this, new LightControllerEventArgs(controller));
             return true;
         }
 
+        /// <summary>
+        /// 设置指定全局通道的亮度值。
+        /// </summary>
+        /// <param name="globalChannelId">全局通道ID</param>
+        /// <param name="brightness">亮度(由具体实现定义的范围)</param>
+        /// <returns>操作是否成功</returns>
         public async Task<bool> SetGlobalChannelBrightnessAsync(int globalChannelId, int brightness)
         {
             if (!_globalChannels.TryGetValue(globalChannelId, out var channel))
                 return false;
 
             var result = await channel.SetBrightnessAsync(brightness);
+            // 触发状态变化事件,通知外部当前通道的开关与亮度状态
             ChannelStatusChanged?.Invoke(this, new LightChannelEventArgs(channel, channel.IsOn, channel.Brightness));
             return result;
         }
 
+        /// <summary>
+        /// 打开指定的全局通道。
+        /// </summary>
+        /// <param name="globalChannelId">全局通道ID</param>
+        /// <returns>操作是否成功</returns>
         public async Task<bool> TurnOnGlobalChannelAsync(int globalChannelId)
         {
             if (!_globalChannels.TryGetValue(globalChannelId, out var channel))
@@ -141,6 +222,11 @@ namespace TeamAAS_VP.Services
             return result;
         }
 
+        /// <summary>
+        /// 关闭指定的全局通道。
+        /// </summary>
+        /// <param name="globalChannelId">全局通道ID</param>
+        /// <returns>操作是否成功</returns>
         public async Task<bool> TurnOffGlobalChannelAsync(int globalChannelId)
         {
             if (!_globalChannels.TryGetValue(globalChannelId, out var channel))
@@ -151,6 +237,10 @@ namespace TeamAAS_VP.Services
             return result;
         }
 
+        /// <summary>
+        /// 异步连接所有已注册的控制器。
+        /// </summary>
+        /// <returns>当所有控制器连接成功返回 true;任一失败返回 false。</returns>
         public async Task<bool> ConnectAllAsync()
         {
             var tasks = _controllers.Values.Select(c => c.ConnectAsync());
@@ -158,6 +248,10 @@ namespace TeamAAS_VP.Services
             return results.All(r => r);
         }
 
+        /// <summary>
+        /// 异步断开所有已注册的控制器(不会销毁控制器实例)。
+        /// </summary>
+        /// <returns>操作完成后返回 true。</returns>
         public async Task<bool> DisconnectAllAsync()
         {
             foreach (var controller in _controllers.Values)
@@ -167,15 +261,25 @@ namespace TeamAAS_VP.Services
             return true;
         }
 
+        /// <summary>
+        /// 将当前配置保存到指定文件(未实现)。
+        /// </summary>
+        /// <param name="filePath">目标文件路径</param>
+        /// <returns>完成任务</returns>
         public Task SaveConfigurationAsync(string filePath)
         {
-            // 实现配置保存
+            // TODO: 实现配置保存逻辑(序列化控制器与通道映射)
             return Task.CompletedTask;
         }
 
+        /// <summary>
+        /// 从指定文件加载配置(未实现)。
+        /// </summary>
+        /// <param name="filePath">配置文件路径</param>
+        /// <returns>完成任务</returns>
         public Task LoadConfigurationAsync(string filePath)
         {
-            // 实现配置加载
+            // TODO: 实现配置加载逻辑(反序列化并调用 RegisterControllerAsync)
             return Task.CompletedTask;
         }
     }

+ 1 - 0
TeamAAS-VM/Views/MainWindowView.xaml.cs

@@ -50,6 +50,7 @@ namespace TeamAAS_VP.Views
 
         private void MessageNotification(MessageParameter obj)
         {
+            SnackbarSeven.MessageQueue.Clear();
             SnackbarSeven.MessageQueue?.Enqueue(
                 obj.Msg,
                 null,