| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278 |
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Threading.Tasks;
- using TeamAAS_VP.Models.Lights;
- namespace TeamAAS_VP.Core.Lights
- {
- /// <summary>
- /// 基于 KCS 协议的光源控制器实现。
- /// 该类通过注入的 <see cref="ICommunicationProtocol"/> 与物理设备通信,
- /// 并提供通道创建、连接/断开、初始化、全部开/关及通道亮度的内部读写实现。
- /// </summary>
- public class KCSLightController : LightControllerBase
- {
- // 通信协议抽象,用于发送/接收字节数据
- private readonly ICommunicationProtocol _protocol;
- // 使用 ASCII 编码将字符串转换为字节流
- private readonly Encoding _encoding;
- //光源配置
- private readonly LightControllerConfig _config;
- /// <summary>
- /// 创建一个新的 <see cref="KCSLightController"/> 实例。
- /// </summary>
- /// <param name="id">控制器的唯一标识符。</param>
- /// <param name="protocol">用于与设备通信的协议实现(必须已实现连接/发送/接收等)。</param>
- /// <param name="channelCount">该控制器管理的通道数量。</param>
- /// <param name="config">控制器的配置对象。</param>
- public KCSLightController(int id, ICommunicationProtocol protocol,int channelCount, LightControllerConfig config)
- : base(id, channelCount,config)
- {
- _protocol = protocol;
- _encoding = Encoding.ASCII;
- _config = config;
- }
- /// <summary>
- /// 为指定索引创建通道实例。
- /// </summary>
- /// <param name="index">通道索引(从 0 开始)。</param>
- /// <param name="channelName">通道名称。</param>
- /// <returns>返回对应的 <see cref="KCSLightChannel"/> 实例。</returns>
- protected override ILightChannel CreateChannel(int index,string channelName)
- {
- return new KCSLightChannel(index, this, channelName);
- }
- /// <summary>
- /// 异步建立与设备的连接并在成功后执行初始化。
- /// </summary>
- /// <returns>若连接成功返回 true,否则返回 false。</returns>
- public override async Task<bool> ConnectAsync()
- {
- var connected = await _protocol.ConnectAsync();
- if (connected)
- {
- IsConnected = true;
- await InitializeAsync();
- }
- 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");
- }
- commandBuilder.Append("C#");
- var response = await SendCommandAsync(commandBuilder.ToString());
- 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, 1500);
- 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)}#";
- if (_config.LightModel == LightModel.KCS_KDC3_24V300W_8T)
- {
- command = $"SR{(char)('A' + channelIndex)}#";
- }
- var response = await SendCommandAsync(command);
- // 响应示例(假设):?XDDD(总长度 5),亮度在索引 2 开始的 3 个字符
- if (response.Length == 5)
- {
- string valueStr = response.Substring(2, 3);
- if (int.TryParse(valueStr, out int brightness))
- {
- return brightness;
- }
- }
- else if (response.Length == 7)
- {
- string valueStr = response.Substring(3, 3);
- if (int.TryParse(valueStr, out int brightness))
- {
- 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);
- if (response=="!")
- {
- return true;
- }
- // 成功时返回通道字母(例如 'A'),去除空白后比较
- return response?.Trim() == ((char)('A' + channelIndex)).ToString();
- }
- /// <summary>
- /// 打开指定的多个通道并设置亮度,未指定通道保持当前状态不变。
- /// 命令格式:S{CH1:DDD}{动作}...C#
- /// </summary>
- /// <param name="channelBrightnessMap">通道索引与目标亮度的键值对,亮度为0时关闭通道。</param>
- /// <returns>若设备返回确认字符 '!' 则认为操作成功。</returns>
- public async Task<bool> TurnOnChannelsAsync(IDictionary<int, int> channelBrightnessMap)
- {
- StringBuilder commandBuilder = new StringBuilder();
- for (int i = 0; i < ChannelCount; i++)
- {
- var channel = (KCSLightChannel)ChannelsInternal[i];
- if (channelBrightnessMap.TryGetValue(i, out int brightness))
- {
- //// 指定通道:使用传入的亮度
- //brightness = Math.Max(0, Math.Min(255, brightness));
- //commandBuilder.Append(brightness.ToString("D3"));
- //// 亮度 > 0 表示打开,亮度 = 0 表示关闭
- //commandBuilder.Append(brightness > 0 ? "T" : "F");
- commandBuilder.Append($"S{(char)('A' + i)}0{brightness.ToString("D3")}#");
- }
- else
- {
- //// 未指定通道:保持当前亮度,无动作字符
- commandBuilder.Append($"S{(char)('A' + i)}0{brightness.ToString("D3")}#");
- }
-
- }
- var response = await SendCommandAsync(commandBuilder.ToString());
- return response?.Trim().ToUpper() == "ABCDEFGH";
- }
- /// <summary>
- /// 关闭指定的多个通道,未指定通道保持当前状态不变。
- /// 命令格式:S{CH1:DDD}{动作}...C#
- /// </summary>
- /// <param name="channelIndices">要关闭的通道索引集合。</param>
- /// <returns>若设备返回确认字符 '!' 则认为操作成功。</returns>
- public async Task<bool> TurnOffChannelsAsync(IEnumerable<int> channelIndices)
- {
- var offIndices = new HashSet<int>(channelIndices);
- StringBuilder commandBuilder = new StringBuilder("S");
- for (int i = 0; i < ChannelCount; i++)
- {
- var channel = (KCSLightChannel)ChannelsInternal[i];
- if (offIndices.Contains(i))
- {
- // 关闭:亮度 000 + 'F'
- commandBuilder.Append("000");
- commandBuilder.Append("F");
- }
- else
- {
- // 保持原状态:当前亮度,无动作字符
- commandBuilder.Append(channel.CurrentBrightness.ToString("D3"));
- }
- }
- commandBuilder.Append("C#");
- var response = await SendCommandAsync(commandBuilder.ToString());
- return response?.Trim() == "!";
- }
- }
- }
|