SerialPortProtocol.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. /*
  2. Pseudocode (详细计划):
  3. - 定义类 SerialPortProtocol,实现 ICommunicationProtocol。
  4. - 字段:
  5. - _client: SerialPortClient 实例,用于串口通信。
  6. - _waitClient: 等待客户端,用于发送后等待响应。
  7. - _config: 只读配置对象 SerialPortConfig。
  8. - 属性:
  9. - IsConnected: 检查 _client 是否在线。
  10. - Terminator: 终止符设置(默认 None)。
  11. - Encoding: 编码(默认 ASCII)。
  12. - 事件:
  13. - ConnectionChanged: 连接状态变化时触发。
  14. - DataReceived: 接收数据时触发(字符串形式)。
  15. - DataSent: 发送数据时触发(字符串形式)。
  16. - 构造函数:
  17. - 接收 SerialPortConfig 并保存到 _config。
  18. - ConnectAsync:
  19. - 创建 SerialPortClient。
  20. - 构建 TouchSocketConfig,设置串口选项(端口名、波特率、数据位、校验、停止位)。
  21. - 设置数据处理适配器 PeriodPackageAdapter(CacheTimeout 100ms)。
  22. - 调用 _client.Setup(config) 并 TryConnectAsync。
  23. - 若成功,创建等待客户端并触发 ConnectionChanged(true),返回 true;否则触发 ConnectionChanged(false),返回 false。
  24. - 捕获异常时也触发 ConnectionChanged(false) 并返回 false。
  25. - DisconnectAsync:
  26. - 触发 ConnectionChanged(false),关闭 _client。
  27. - Send/SendAsync(字节/字符串):
  28. - 检查 _client 是否存在,否则抛出 InvalidOperationException。
  29. - 触发 DataSent(字符串形式)。
  30. - 使用 _client.Send 或 _client.SendAsync 发送数据。
  31. - SendAndReceive/SendAndReceiveAsync(字节/字符串):
  32. - 检查 _waitClient,否则抛出 InvalidOperationException。
  33. - 触发 DataSent。
  34. - 使用 _waitClient.SendThenReturn(Sync/Async) 等待响应。
  35. - 触发 DataReceived 并返回响应(字节或字符串)。
  36. - Dispose:
  37. -释放 _client 并置空 _waitClient。
  38. - 为所有公开成员添加 XML 文档注释,描述参数、返回值与异常。
  39. */
  40. using System;
  41. using System.Text;
  42. using System.Threading.Tasks;
  43. using TeamAAS_VP.Enums;
  44. using TeamAAS_VP.Models;
  45. using TouchSocket.Core;
  46. using TouchSocket.SerialPorts;
  47. using TouchSocket.Sockets;
  48. namespace TeamAAS_VP.Core.Lights
  49. {
  50. /// <summary>
  51. /// 使用 TouchSocket 的串口通信协议封装。
  52. /// 提供同步/异步的发送与收发方法,并通过事件报告连接与数据状态。
  53. /// </summary>
  54. public class SerialPortProtocol : ICommunicationProtocol
  55. {
  56. /// <summary>
  57. /// 内部串口客户端实例。
  58. /// </summary>
  59. private SerialPortClient _client;
  60. /// <summary>
  61. /// 用于发送后等待响应的等待客户端。
  62. /// </summary>
  63. private IWaitingClient<ISerialPortClient, IReceiverResult> _waitClient;
  64. /// <summary>
  65. /// 串口配置,只读。
  66. /// </summary>
  67. private readonly SerialPortConfig _config;
  68. /// <summary>
  69. /// 获取是否已连接(客户端在线)。
  70. /// </summary>
  71. public bool IsConnected => _client?.Online == true;
  72. /// <summary>
  73. /// 数据终止符,默认为 <see cref="Terminator.None"/>。
  74. /// </summary>
  75. public Terminator Terminator { get; set; } = Terminator.None;
  76. /// <summary>
  77. /// 文本编码,默认使用 ASCII 编码。
  78. /// </summary>
  79. public Encoding Encoding { get; set; } = Encoding.ASCII;
  80. /// <summary>
  81. /// 当连接状态改变时触发。参数为触发对象和连接状态(true=已连接)。
  82. /// </summary>
  83. public event Action<object, bool> ConnectionChanged;
  84. /// <summary>
  85. /// 当接收到数据时触发,携带接收到的数据(字符串形式)。
  86. /// </summary>
  87. public event Action<object, string> DataReceived;
  88. /// <summary>
  89. /// 当发送数据时触发,携带发送的数据(字符串形式)。
  90. /// </summary>
  91. public event Action<object, string> DataSent;
  92. /// <summary>
  93. /// 使用指定的串口配置构建一个新的 <see cref="SerialPortProtocol"/> 实例。
  94. /// </summary>
  95. /// <param name="config">串口配置,不能为 null。</param>
  96. /// <exception cref="ArgumentNullException">当 <paramref name="config"/> 为 null 时抛出。</exception>
  97. public SerialPortProtocol(SerialPortConfig config)
  98. {
  99. _config = config ?? throw new ArgumentNullException(nameof(config));
  100. }
  101. /// <summary>
  102. /// 异步连接到串口并初始化等待客户端。
  103. /// </summary>
  104. /// <returns>连接成功返回 true,否则返回 false。</returns>
  105. public async Task<bool> ConnectAsync()
  106. {
  107. try
  108. {
  109. _client = new SerialPortClient();
  110. var config = new TouchSocketConfig()
  111. .SetSerialPortOption(new SerialPortOption()
  112. {
  113. PortName = _config.PortName,
  114. BaudRate = _config.BaudRate,
  115. DataBits = _config.DataBits,
  116. Parity = _config.Parity,
  117. StopBits = _config.StopBits
  118. })
  119. .SetSerialDataHandlingAdapter(() => new PeriodPackageAdapter()
  120. {
  121. CacheTimeout = TimeSpan.FromMilliseconds(100)
  122. });
  123. _client.Setup(config);
  124. var result = await _client.TryConnectAsync();
  125. if (result.IsSuccess)
  126. {
  127. _waitClient = _client.CreateWaitingClient(new WaitingOptions());
  128. ConnectionChanged?.Invoke(this, true);
  129. return true;
  130. }
  131. ConnectionChanged?.Invoke(this, false);
  132. return false;
  133. }
  134. catch
  135. {
  136. ConnectionChanged?.Invoke(this, false);
  137. return false;
  138. }
  139. }
  140. /// <summary>
  141. /// 异步断开连接并关闭串口。
  142. /// </summary>
  143. /// <returns>完成任务。</returns>
  144. public Task DisconnectAsync()
  145. {
  146. ConnectionChanged?.Invoke(this, false);
  147. _client?.Close();
  148. return Task.CompletedTask;
  149. }
  150. /// <summary>
  151. /// 异步发送字节数据并等待接收响应。
  152. /// </summary>
  153. /// <param name="data">要发送的字节数组。</param>
  154. /// <param name="timeout">等待超时时间(毫秒),默认 5000ms。</param>
  155. /// <returns>收到的字节数组。</returns>
  156. /// <exception cref="InvalidOperationException">当尚未连接或等待客户端为 null 时抛出。</exception>
  157. public async Task<byte[]> SendAndReceiveAsync(byte[] data, int timeout = 5000)
  158. {
  159. if (_waitClient == null)
  160. throw new InvalidOperationException("Not connected");
  161. DataSent?.Invoke(this, Encoding.GetString(data));
  162. var response = await _waitClient.SendThenReturnAsync(data, timeout);
  163. DataReceived?.Invoke(this, Encoding.GetString(response));
  164. return response;
  165. }
  166. /// <summary>
  167. /// 异步发送字节数据(不等待响应)。
  168. /// </summary>
  169. /// <param name="data">要发送的字节数组。</param>
  170. /// <returns>完成任务。</returns>
  171. /// <exception cref="InvalidOperationException">当客户端未初始化时抛出。</exception>
  172. public Task SendAsync(byte[] data)
  173. {
  174. if (_client == null)
  175. throw new InvalidOperationException("Not connected");
  176. DataSent?.Invoke(this, Encoding.GetString(data));
  177. _client.Send(data);
  178. return Task.CompletedTask;
  179. }
  180. /// <summary>
  181. /// 异步发送文本并等待响应(字符串形式)。
  182. /// </summary>
  183. /// <param name="data">要发送的文本。</param>
  184. /// <param name="timeout">等待超时时间(毫秒),默认 5000ms。</param>
  185. /// <returns>收到的文本响应。</returns>
  186. /// <exception cref="InvalidOperationException">当尚未连接或等待客户端为 null 时抛出。</exception>
  187. public async Task<string> SendAndReceiveAsync(string data, int timeout = 5000)
  188. {
  189. if (_waitClient == null)
  190. throw new InvalidOperationException("Not connected");
  191. DataSent?.Invoke(this, data);
  192. var response = await SendAndReceiveAsync(Encoding.GetBytes(data), timeout);
  193. DataReceived?.Invoke(this, Encoding.GetString(response));
  194. return Encoding.GetString(response);
  195. }
  196. /// <summary>
  197. /// 同步发送文本并等待响应(字符串形式)。
  198. /// </summary>
  199. /// <param name="data">要发送的文本。</param>
  200. /// <param name="timeout">等待超时时间(毫秒),默认 5000ms。</param>
  201. /// <returns>收到的文本响应。</returns>
  202. /// <exception cref="InvalidOperationException">当尚未连接或等待客户端为 null 时抛出。</exception>
  203. public string SendAndReceive(string data, int timeout = 5000)
  204. {
  205. if (_waitClient == null)
  206. throw new InvalidOperationException("Not connected");
  207. DataSent?.Invoke(this, data);
  208. var response = _waitClient.SendThenReturn(Encoding.GetBytes(data), timeout);
  209. DataReceived?.Invoke(this, Encoding.GetString(response));
  210. return Encoding.GetString(response);
  211. }
  212. /// <summary>
  213. /// 同步发送字节数据并等待响应。
  214. /// </summary>
  215. /// <param name="data">要发送的字节数组。</param>
  216. /// <param name="timeout">等待超时时间(毫秒),默认 5000ms。</param>
  217. /// <returns>收到的字节数组。</returns>
  218. /// <exception cref="InvalidOperationException">当尚未连接或等待客户端为 null 时抛出。</exception>
  219. public byte[] SendAndReceive(byte[] data, int timeout = 5000)
  220. {
  221. if (_waitClient == null)
  222. throw new InvalidOperationException("Not connected");
  223. DataSent?.Invoke(this, Encoding.GetString(data));
  224. var response = _waitClient.SendThenReturn(data, timeout);
  225. DataReceived?.Invoke(this, Encoding.GetString(response));
  226. return response;
  227. }
  228. /// <summary>
  229. /// 同步发送文本(不等待响应)。
  230. /// </summary>
  231. /// <param name="data">要发送的文本。</param>
  232. /// <exception cref="InvalidOperationException">当客户端未初始化时抛出。</exception>
  233. public void Send(string data)
  234. {
  235. if (_client == null)
  236. throw new InvalidOperationException("Not connected");
  237. DataSent?.Invoke(this, data);
  238. _client.Send(Encoding.GetBytes(data));
  239. }
  240. /// <summary>
  241. /// 异步发送文本(不等待响应)。
  242. /// </summary>
  243. /// <param name="data">要发送的文本。</param>
  244. /// <returns>完成任务。</returns>
  245. /// <exception cref="InvalidOperationException">当客户端未初始化时抛出。</exception>
  246. public async Task SendAsync(string data)
  247. {
  248. if (_client == null)
  249. throw new InvalidOperationException("Not connected");
  250. DataSent?.Invoke(this, data);
  251. await _client.SendAsync(Encoding.GetBytes(data));
  252. }
  253. /// <summary>
  254. /// 同步发送字节数据(不等待响应)。
  255. /// </summary>
  256. /// <param name="data">要发送的字节数组。</param>
  257. /// <exception cref="InvalidOperationException">当客户端未初始化时抛出。</exception>
  258. public void Send(byte[] data)
  259. {
  260. if (_client == null)
  261. throw new InvalidOperationException("Not connected");
  262. DataSent?.Invoke(this, Encoding.GetString(data));
  263. _client.Send(data);
  264. }
  265. /// <summary>
  266. /// 释放底层资源。调用后实例不应再被使用。
  267. /// </summary>
  268. public void Dispose()
  269. {
  270. _client?.Dispose();
  271. _waitClient = null;
  272. }
  273. }
  274. }