KCSLightController.cs 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6. namespace TeamAAS_VP.Core.Lights
  7. {
  8. /// <summary>
  9. /// 基于 KCS 协议的光源控制器实现。
  10. /// 该类通过注入的 <see cref="ICommunicationProtocol"/> 与物理设备通信,
  11. /// 并提供通道创建、连接/断开、初始化、全部开/关及通道亮度的内部读写实现。
  12. /// </summary>
  13. public class KCSLightController : LightControllerBase
  14. {
  15. // 通信协议抽象,用于发送/接收字节数据
  16. private readonly ICommunicationProtocol _protocol;
  17. // 使用 ASCII 编码将字符串转换为字节流
  18. private readonly Encoding _encoding;
  19. /// <summary>
  20. /// 创建一个新的 <see cref="KCSLightController"/> 实例。
  21. /// </summary>
  22. /// <param name="id">控制器的唯一标识符。</param>
  23. /// <param name="protocol">用于与设备通信的协议实现(必须已实现连接/发送/接收等)。</param>
  24. /// <param name="channelCount">该控制器管理的通道数量。</param>
  25. public KCSLightController(int id, ICommunicationProtocol protocol,int channelCount)
  26. : base(id, channelCount)
  27. {
  28. _protocol = protocol;
  29. _encoding = Encoding.ASCII;
  30. }
  31. /// <summary>
  32. /// 为指定索引创建通道实例。
  33. /// </summary>
  34. /// <param name="index">通道索引(从 0 开始)。</param>
  35. /// <returns>返回对应的 <see cref="KCSLightChannel"/> 实例。</returns>
  36. protected override ILightChannel CreateChannel(int index)
  37. {
  38. return new KCSLightChannel(index, this);
  39. }
  40. /// <summary>
  41. /// 异步建立与设备的连接并在成功后执行初始化。
  42. /// </summary>
  43. /// <returns>若连接成功返回 true,否则返回 false。</returns>
  44. public override async Task<bool> ConnectAsync()
  45. {
  46. var connected = await _protocol.ConnectAsync();
  47. if (connected)
  48. {
  49. IsConnected = true;
  50. await InitializeAsync();
  51. }
  52. return connected;
  53. }
  54. /// <summary>
  55. /// 异步断开与设备的连接并更新连接状态。
  56. /// </summary>
  57. public override async Task DisconnectAsync()
  58. {
  59. await _protocol.DisconnectAsync();
  60. IsConnected = false;
  61. }
  62. /// <summary>
  63. /// 异步初始化控制器,通常用于测试通信并同步初始状态。
  64. /// 目前实现通过读取第 0 通道亮度来验证通信链路是否正常。
  65. /// </summary>
  66. /// <returns>若初始化成功返回 true;若发生异常或通信失败返回 false。</returns>
  67. public override async Task<bool> InitializeAsync()
  68. {
  69. try
  70. {
  71. // 初始化操作:读取第 0 通道亮度以测试通信是否正常
  72. await GetChannelBrightnessInternal(0); // 测试通信
  73. return true;
  74. }
  75. catch
  76. {
  77. // 初始化失败(通信异常等)
  78. return false;
  79. }
  80. }
  81. /// <summary>
  82. /// 将所有通道设置为打开状态。命令格式参考控制器手册。
  83. /// 构造格式示例:S{CH1:DDD}T{CH2:DDD}T...C#
  84. /// 其中每个通道使用 3 位亮度值(D3),后跟动作字符 'T' 表示开。
  85. /// </summary>
  86. /// <returns>若设备返回确认字符 '!' 则认为操作成功。</returns>
  87. public override async Task<bool> TurnOnAllAsync()
  88. {
  89. StringBuilder commandBuilder = new StringBuilder("S");
  90. for (int i = 0; i < ChannelCount; i++)
  91. {
  92. var channel = (KCSLightChannel)ChannelsInternal[i];
  93. // 使用三位数字格式表示亮度(例如 005、120、255)
  94. commandBuilder.Append(channel.CurrentBrightness.ToString("D3"));
  95. // 'T' 表示打开当前通道
  96. commandBuilder.Append("T");
  97. }
  98. // 以 C# 结尾表示执行命令(协议特定)
  99. commandBuilder.Append("C#");
  100. var response = await SendCommandAsync(commandBuilder.ToString());
  101. // 期望设备返回 "!" 表示成功(根据协议)
  102. return response?.Trim() == "!";
  103. }
  104. /// <summary>
  105. /// 将所有通道设置为关闭状态。
  106. /// 构造格式示例:S000F000F...C#
  107. /// 其中 '000' 表示亮度 0,'F' 表示关闭通道。
  108. /// </summary>
  109. /// <returns>若设备返回确认字符 '!' 则认为操作成功。</returns>
  110. public override async Task<bool> TurnOffAllAsync()
  111. {
  112. StringBuilder commandBuilder = new StringBuilder("S");
  113. for (int i = 0; i < ChannelCount; i++)
  114. {
  115. // 将每个通道设置为 000(亮度 0)并附带 'F' 动作表示关闭
  116. commandBuilder.Append("000");
  117. commandBuilder.Append("F");
  118. }
  119. commandBuilder.Append("C#");
  120. var response = await SendCommandAsync(commandBuilder.ToString());
  121. return response?.Trim() == "!";
  122. }
  123. /// <summary>
  124. /// 将字符串命令编码为字节并通过协议发送,接收响应后以字符串返回。
  125. /// </summary>
  126. /// <param name="command">要发送的命令字符串(协议约定的格式)。</param>
  127. /// <returns>设备响应的字符串表示(使用 ASCII 解码)。</returns>
  128. internal async Task<string> SendCommandAsync(string command)
  129. {
  130. var data = _encoding.GetBytes(command);
  131. // 使用协议的 SendAndReceiveAsync 发送数据并等待响应(超时时间以协议或调用方为准)
  132. var response = await _protocol.SendAndReceiveAsync(data, 1000);
  133. return _encoding.GetString(response);
  134. }
  135. /// <summary>
  136. /// 读取指定通道的亮度(内部方法)。
  137. /// 命令格式示例:S{ChannelLetter}#,例如读取第 0 通道为 S A #。
  138. /// 响应预期长度为 5 字符,亮度位于索引 2..4(3 个字符)。
  139. /// </summary>
  140. /// <param name="channelIndex">通道索引(从 0 开始)。</param>
  141. /// <returns>解析得到的亮度值(0-999);若解析失败或响应格式不符则返回 0。</returns>
  142. internal async Task<int> GetChannelBrightnessInternal(int channelIndex)
  143. {
  144. // 通道字母从 'A' 开始递增
  145. string command = $"S{(char)('A' + channelIndex)}#";
  146. var response = await SendCommandAsync(command);
  147. // 响应示例(假设):?XDDD(总长度 5),亮度在索引 2 开始的 3 个字符
  148. if (response.Length == 5)
  149. {
  150. string valueStr = response.Substring(2, 3);
  151. if (int.TryParse(valueStr, out int brightness))
  152. {
  153. return brightness;
  154. }
  155. }
  156. // 返回默认亮度 0(表示读取失败或设备返回异常)
  157. return 0;
  158. }
  159. /// <summary>
  160. /// 设置指定通道的亮度(内部方法)。
  161. /// 命令格式示例:S{ChannelLetter}0{DDD}#,其中 '0' 可能为协议占位符,具体请参考设备手册。
  162. /// 成功时设备返回与通道字母相同的字符作为确认。
  163. /// </summary>
  164. /// <param name="channelIndex">通道索引(从 0 开始)。</param>
  165. /// <param name="brightness">目标亮度(0-999)。方法内部会格式化为三位数字。</param>
  166. /// <returns>若设备返回与通道字母相同的字符则认为设置成功。</returns>
  167. internal async Task<bool> SetChannelBrightnessInternal(int channelIndex, int brightness)
  168. {
  169. string command = $"S{(char)('A' + channelIndex)}0{brightness.ToString("D3")}#";
  170. var response = await SendCommandAsync(command);
  171. // 成功时返回通道字母(例如 'A'),去除空白后比较
  172. return response?.Trim() == ((char)('A' + channelIndex)).ToString();
  173. }
  174. }
  175. }