TeamFeeder.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517
  1. using System;
  2. using System.Collections.Concurrent;
  3. using System.IO;
  4. using System.Net.Sockets;
  5. using System.Text;
  6. using System.Threading;
  7. using System.Threading.Tasks;
  8. using TeamAAS.Feeder.Attributes;
  9. using TeamAAS.Feeder.Interfaces;
  10. using TeamAAS.Feeder.Enums;
  11. using TeamAAS.Feeder.Models;
  12. namespace TeamAAS.Feeder.Devices
  13. {
  14. /// <summary>
  15. /// TEAM 品牌供料器 TCP 协议实现
  16. /// 协议格式:{命令}\r\n,ASCII 编码
  17. /// 用 .NET 内置 System.Net.Sockets,无第三方 TCP 依赖
  18. /// </summary>
  19. [Feeder("Team 供料器", FeederBrand.Team, Description = "内置 Team 品牌 TCP 协议实现")]
  20. public class TeamFeeder : IFeeder
  21. {
  22. private const int MillisecondsTimeout = 5000;
  23. private const int ReconnectIntervalMs = 3000;
  24. private const string Terminator = "\r\n";
  25. // 系统参数全局地址
  26. private const int AddrBackLightFlash = 33; // 背光闪烁时间
  27. private const int AddrLightBrightness = 34; // 光源亮度
  28. private const int AddrLightTimeout = 44; // 背光超时
  29. private const int AddrPlatShakeTimeout = 65; // 平台振动超时
  30. private const int AddrHopperShakeTimeout = 66;// 料斗振动超时
  31. private const int HopperParamStartAddress = 742; // 料斗输出参数起始地址(每组8个)
  32. private const int InputTriggerStartAddress = 39; // 输入触发序列ID起始地址
  33. private readonly object _ioLock = new object();
  34. private TcpClient _tcp;
  35. private NetworkStream _stream;
  36. private Thread _recvThread;
  37. private volatile bool _recvRunning;
  38. private readonly ConcurrentQueue<string> _rxQueue = new ConcurrentQueue<string>();
  39. private readonly StringBuilder _rxBuffer = new StringBuilder();
  40. private bool _isBacklightOn;
  41. private System.Threading.Timer _reconnectTimer;
  42. private volatile bool _manualDisconnect;
  43. public Guid Id { get; }
  44. public int FeederNo { get; }
  45. public string Name { get; }
  46. public FeederBrand Brand => FeederBrand.Team;
  47. public bool IsConnected => _tcp?.Connected ?? false;
  48. public bool IsBacklightOn => _isBacklightOn;
  49. public event Action<IFeeder, bool> ConnectionChanged;
  50. public event Action<IFeeder, string, bool> MessageExchanged;
  51. public TeamFeeder(Guid id, int feederNo, string name, string ip, int port)
  52. {
  53. Id = id;
  54. FeederNo = feederNo;
  55. Name = name;
  56. IP = ip;
  57. Port = port;
  58. }
  59. private string IP { get; }
  60. private int Port { get; }
  61. public async Task ConnectAsync()
  62. {
  63. _manualDisconnect = false;
  64. StopReconnectTimer();
  65. DisposeStream();
  66. _tcp = new TcpClient();
  67. await _tcp.ConnectAsync(IP, Port);
  68. _stream = _tcp.GetStream();
  69. _stream.ReadTimeout = MillisecondsTimeout;
  70. _stream.WriteTimeout = MillisecondsTimeout;
  71. lock (_ioLock)
  72. {
  73. while (_rxQueue.TryDequeue(out _)) { }
  74. _rxBuffer.Clear();
  75. }
  76. StartReceiveLoop();
  77. ConnectionChanged?.Invoke(this, true);
  78. }
  79. public async Task DisconnectAsync()
  80. {
  81. _manualDisconnect = true;
  82. StopReconnectTimer();
  83. _recvRunning = false;
  84. DisposeStream();
  85. _isBacklightOn = false;
  86. ConnectionChanged?.Invoke(this, false);
  87. await Task.CompletedTask;
  88. }
  89. public void Dispose()
  90. {
  91. _manualDisconnect = true;
  92. StopReconnectTimer();
  93. try { _recvRunning = false; DisposeStream(); } catch { }
  94. }
  95. private void DisposeStream()
  96. {
  97. try { _stream?.Close(); _stream?.Dispose(); } catch { }
  98. _stream = null;
  99. try { _tcp?.Close(); _tcp?.Dispose(); } catch { }
  100. _tcp = null;
  101. }
  102. private void StartReceiveLoop()
  103. {
  104. _recvRunning = true;
  105. _recvThread = new Thread(ReceiveLoop) { IsBackground = true, Name = $"FeederRecv-{Name}" };
  106. _recvThread.Start();
  107. }
  108. private void StartReconnectTimer()
  109. {
  110. if (_manualDisconnect) return;
  111. if (_reconnectTimer != null) return;
  112. _reconnectTimer = new System.Threading.Timer(ReconnectCallback, null,
  113. ReconnectIntervalMs, ReconnectIntervalMs);
  114. }
  115. private void StopReconnectTimer()
  116. {
  117. var t = _reconnectTimer;
  118. _reconnectTimer = null;
  119. try { t?.Dispose(); } catch { }
  120. }
  121. private void ReconnectCallback(object state)
  122. {
  123. if (_manualDisconnect) { StopReconnectTimer(); return; }
  124. if (IsConnected) { StopReconnectTimer(); return; }
  125. try
  126. {
  127. DisposeStream();
  128. var c = new TcpClient();
  129. c.Connect(IP, Port);
  130. _tcp = c;
  131. _stream = c.GetStream();
  132. _stream.ReadTimeout = MillisecondsTimeout;
  133. _stream.WriteTimeout = MillisecondsTimeout;
  134. lock (_ioLock)
  135. {
  136. while (_rxQueue.TryDequeue(out _)) { }
  137. _rxBuffer.Clear();
  138. }
  139. StartReceiveLoop();
  140. StopReconnectTimer();
  141. ConnectionChanged?.Invoke(this, true);
  142. }
  143. catch
  144. {
  145. try { DisposeStream(); } catch { }
  146. }
  147. }
  148. private void ReceiveLoop()
  149. {
  150. var buf = new byte[4096];
  151. try
  152. {
  153. while (_recvRunning && _stream != null && _tcp.Connected)
  154. {
  155. int n = _stream.Read(buf, 0, buf.Length);
  156. if (n <= 0) break;
  157. string chunk = Encoding.ASCII.GetString(buf, 0, n);
  158. lock (_ioLock)
  159. {
  160. _rxBuffer.Append(chunk);
  161. FlushMessages();
  162. }
  163. }
  164. }
  165. catch (IOException) { }
  166. catch (ObjectDisposedException) { }
  167. catch (Exception) { }
  168. finally
  169. {
  170. if (_recvRunning)
  171. {
  172. _recvRunning = false;
  173. ConnectionChanged?.Invoke(this, false);
  174. if (!_manualDisconnect)
  175. StartReconnectTimer();
  176. }
  177. }
  178. }
  179. private void FlushMessages()
  180. {
  181. string content = _rxBuffer.ToString();
  182. int idx;
  183. while ((idx = content.IndexOf(Terminator, StringComparison.Ordinal)) >= 0)
  184. {
  185. var msg = content.Substring(0, idx);
  186. content = content.Substring(idx + Terminator.Length);
  187. _rxQueue.Enqueue(msg);
  188. }
  189. _rxBuffer.Clear();
  190. _rxBuffer.Append(content);
  191. }
  192. #region 背光
  193. public async Task OpenBacklightAsync()
  194. {
  195. await SendAsync("{K1}");
  196. _isBacklightOn = true;
  197. }
  198. public async Task CloseBacklightAsync()
  199. {
  200. await SendAsync("{K0}");
  201. _isBacklightOn = false;
  202. }
  203. public async Task<bool> QueryBacklightAsync()
  204. {
  205. var resp = await SendAndRecvAsync("{K?}");
  206. return resp.Contains("1");
  207. }
  208. #endregion
  209. #region 方向执行
  210. public async Task<int> RunDirectionAsync(FeederAction action, int? durationMs = null)
  211. {
  212. char dirChar = (char)('A' + (int)action);
  213. string cmd = durationMs.HasValue
  214. ? $"{{C{dirChar}{durationMs.Value}}}"
  215. : $"{{C{dirChar}}}";
  216. var resp = await SendAndRecvAsync(cmd);
  217. var clean = resp.Trim()
  218. .Replace("{", "").Replace("}", "")
  219. .Replace("C" + dirChar, "");
  220. if (string.IsNullOrEmpty(clean)) return 0;
  221. int.TryParse(clean, out var ms);
  222. return ms;
  223. }
  224. public async Task StopAsync()
  225. {
  226. await SendAsync("{HC}");
  227. }
  228. #endregion
  229. #region 参数读写
  230. public async Task<SingleActionParam> GetActionParamAsync(int index)
  231. {
  232. if (index < 0 || index > 10) index = 0;
  233. char ch = (char)('A' + index);
  234. var resp = await SendAndRecvAsync($"{{LC{ch}}}");
  235. var body = resp.Trim();
  236. var start = body.IndexOf('(');
  237. var end = body.IndexOf(')');
  238. if (start < 0 || end < 0) throw new Exception($"Feeder LC{ch} 响应格式错误: {resp}");
  239. var inner = body.Substring(start + 1, end - start - 1);
  240. var parts = inner.Split(';');
  241. if (parts.Length < 17) throw new Exception($"Feeder LC{ch} 参数数量不足: {parts.Length}");
  242. var p = new SingleActionParam();
  243. int i = 0;
  244. p.Motor1 = ParseMotor(parts, ref i);
  245. p.Motor2 = ParseMotor(parts, ref i);
  246. p.Motor3 = ParseMotor(parts, ref i);
  247. p.Motor4 = ParseMotor(parts, ref i);
  248. p.DurationValue = int.Parse(parts[16]);
  249. return p;
  250. }
  251. public async Task SetActionParamAsync(int index, SingleActionParam param)
  252. {
  253. if (index < 0 || index > 10) index = 0;
  254. char ch = (char)('A' + index);
  255. var sb = new StringBuilder();
  256. sb.Append($"{param.Motor1.Amplitude};{param.Motor1.Frequency};{param.Motor1.Phase};{(int)param.Motor1.WaveShape};");
  257. sb.Append($"{param.Motor2.Amplitude};{param.Motor2.Frequency};{param.Motor2.Phase};{(int)param.Motor2.WaveShape};");
  258. sb.Append($"{param.Motor3.Amplitude};{param.Motor3.Frequency};{param.Motor3.Phase};{(int)param.Motor3.WaveShape};");
  259. sb.Append($"{param.Motor4.Amplitude};{param.Motor4.Frequency};{param.Motor4.Phase};{(int)param.Motor4.WaveShape};");
  260. sb.Append(param.DurationValue);
  261. await SendAndRecvAsync($"{{SC{ch}=({sb})}}");
  262. }
  263. public async Task DownloadAllParamsAsync(FeederInfo info)
  264. {
  265. if (info?.ActionGroups == null) return;
  266. for (int i = 0; i < info.ActionGroups.Count && i < 11; i++)
  267. {
  268. await SetActionParamAsync(i, info.ActionGroups[i]);
  269. }
  270. }
  271. public async Task SaveToDeviceAsync()
  272. {
  273. await SendAndRecvAsync("{DV}");
  274. }
  275. #endregion
  276. #region 系统参数(超时/背光)
  277. public async Task<FeederSystemParam> GetSystemParamAsync()
  278. {
  279. var p = new FeederSystemParam
  280. {
  281. LightTimeout = (ushort)await ReadParamQueryAsync(AddrLightTimeout),
  282. LightBrightness = (ushort)await ReadParamQueryAsync(AddrLightBrightness),
  283. LightFlashTime = (ushort)await ReadParamQueryAsync(AddrBackLightFlash),
  284. PlatShakeTimeout = (ushort)await ReadParamQueryAsync(AddrPlatShakeTimeout),
  285. HopperShakeTimeout = (ushort)await ReadParamQueryAsync(AddrHopperShakeTimeout),
  286. };
  287. return p;
  288. }
  289. public async Task SetSystemParamAsync(FeederSystemParam param)
  290. {
  291. if (param == null) return;
  292. await WriteParamAsync(AddrLightTimeout, param.LightTimeout);
  293. await WriteParamAsync(AddrLightBrightness, param.LightBrightness);
  294. await WriteParamAsync(AddrBackLightFlash, param.LightFlashTime);
  295. await WriteParamAsync(AddrPlatShakeTimeout, param.PlatShakeTimeout);
  296. await WriteParamAsync(AddrHopperShakeTimeout, param.HopperShakeTimeout);
  297. // 全局参数存至非易失内存
  298. await SendAndRecvAsync("{DG}");
  299. }
  300. #endregion
  301. #region 料斗输入输出
  302. public async Task<HopperOutputParam> GetHopperParamAsync(int group)
  303. {
  304. if (group < 0) group = 0;
  305. int start = HopperParamStartAddress + group * 8;
  306. var values = new int[8];
  307. for (int i = 0; i < 8; i++)
  308. values[i] = await ReadParamAsync(start + i);
  309. return new HopperOutputParam
  310. {
  311. HopperDigitalOutput1 = values[0] == 1,
  312. HopperAnalogOutput1 = values[1],
  313. HopperDigitalOutput2 = values[2] == 1,
  314. HopperAnalogOutput2 = values[3],
  315. HopperVibrationAmpl = values[4],
  316. HopperVibrationFreq = values[5],
  317. HopperVibrationWaveform = values[6],
  318. HopperDuration = values[7],
  319. };
  320. }
  321. public async Task SetHopperParamAsync(int group, HopperOutputParam param)
  322. {
  323. if (param == null) return;
  324. if (group < 0) group = 0;
  325. int start = HopperParamStartAddress + group * 8;
  326. await WriteParamAsync(start + 0, param.HopperDigitalOutput1 ? 1 : 0);
  327. await WriteParamAsync(start + 1, param.HopperAnalogOutput1);
  328. await WriteParamAsync(start + 2, param.HopperDigitalOutput2 ? 1 : 0);
  329. await WriteParamAsync(start + 3, param.HopperAnalogOutput2);
  330. await WriteParamAsync(start + 7, param.HopperDuration);
  331. // 振动集参数存至非易失内存
  332. await SendAndRecvAsync("{DV}");
  333. }
  334. public async Task<int> RunHopperOutputAsync(int id, int? timespan = null)
  335. {
  336. if (id < 1 || id > 26) id = 1;
  337. char ch = (char)('A' + (id - 1));
  338. string output = "B" + ch;
  339. string cmd = "{" + output + (timespan.HasValue ? timespan.Value.ToString() : "") + "}";
  340. var resp = await SendAndRecvAsync(cmd);
  341. var clean = resp.Trim().Replace("{", "").Replace("}", "").Replace(output, "");
  342. if (string.IsNullOrWhiteSpace(clean)) return 0;
  343. int.TryParse(clean, out var ms);
  344. return ms;
  345. }
  346. public async Task StopHopperOutputAsync()
  347. {
  348. await SendAsync("{HB}");
  349. }
  350. #endregion
  351. #region 输入触发
  352. public async Task<int> GetInputTriggerAsync(int index)
  353. {
  354. if (index < 0 || index > 2) index = 0;
  355. return await ReadParamAsync(InputTriggerStartAddress + index);
  356. }
  357. public async Task SetInputTriggerAsync(int index, int sequenceId)
  358. {
  359. if (index < 0 || index > 2) index = 0;
  360. await WriteParamAsync(InputTriggerStartAddress + index, sequenceId);
  361. }
  362. #endregion
  363. #region 通用参数读写(RP/WP)
  364. public async Task<int> ReadParamAsync(int id)
  365. {
  366. var resp = await SendAndRecvAsync($"{{RP{id}}}");
  367. return ParseParamValue(resp);
  368. }
  369. /// <summary>查询形式 {RPid?},用于全局系统参数。</summary>
  370. private async Task<int> ReadParamQueryAsync(int id)
  371. {
  372. var resp = await SendAndRecvAsync($"{{RP{id}?}}");
  373. return ParseParamValue(resp);
  374. }
  375. public async Task WriteParamAsync(int id, int value)
  376. {
  377. await SendAndRecvAsync($"{{WP{id}={value}}}");
  378. }
  379. /// <summary>从 {RPxx:value} 形式响应中解析出整数值。</summary>
  380. private static int ParseParamValue(string resp)
  381. {
  382. if (string.IsNullOrEmpty(resp)) return 0;
  383. var body = resp.Trim().Replace("{", "").Replace("}", "");
  384. var idx = body.IndexOf(':');
  385. var token = idx >= 0 ? body.Substring(idx + 1) : body;
  386. int.TryParse(token.Trim(), out var value);
  387. return value;
  388. }
  389. #endregion
  390. #region 底层通讯
  391. private void RaiseTx(string cmd)
  392. {
  393. try { MessageExchanged?.Invoke(this, cmd, true); } catch { }
  394. }
  395. private void RaiseRx(string resp)
  396. {
  397. try { MessageExchanged?.Invoke(this, resp, false); } catch { }
  398. }
  399. private Task SendAsync(string cmd)
  400. {
  401. if (_stream == null) throw new InvalidOperationException("Feeder 未连接");
  402. var data = Encoding.ASCII.GetBytes(cmd + Terminator);
  403. lock (_ioLock)
  404. {
  405. _stream.Write(data, 0, data.Length);
  406. }
  407. RaiseTx(cmd);
  408. return Task.CompletedTask;
  409. }
  410. private async Task<string> SendAndRecvAsync(string cmd)
  411. {
  412. if (_stream == null) throw new InvalidOperationException("Feeder 未连接");
  413. while (_rxQueue.TryDequeue(out _)) { }
  414. var data = Encoding.ASCII.GetBytes(cmd + Terminator);
  415. await _stream.WriteAsync(data, 0, data.Length);
  416. RaiseTx(cmd);
  417. var deadline = DateTime.UtcNow.AddMilliseconds(MillisecondsTimeout);
  418. while (DateTime.UtcNow < deadline)
  419. {
  420. if (_rxQueue.TryDequeue(out var resp))
  421. {
  422. RaiseRx(resp);
  423. return resp;
  424. }
  425. await Task.Delay(20);
  426. }
  427. throw new TimeoutException($"Feeder 命令超时: {cmd}");
  428. }
  429. private static VoiceMotorParam ParseMotor(string[] parts, ref int i)
  430. {
  431. return new VoiceMotorParam
  432. {
  433. Amplitude = ushort.Parse(parts[i++]),
  434. Frequency = ushort.Parse(parts[i++]),
  435. Phase = ushort.Parse(parts[i++]),
  436. WaveShape = (WaveShape)ushort.Parse(parts[i++]),
  437. };
  438. }
  439. #endregion
  440. }
  441. }