ScpiDevice.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  1. using System;
  2. using System.IO.Ports;
  3. using System.Net.Sockets;
  4. using System.Text;
  5. using System.Threading;
  6. namespace APS7100TestTool.Services
  7. {
  8. /// <summary>
  9. /// 连接类型枚举
  10. /// </summary>
  11. public enum ConnectionType
  12. {
  13. None, // 未连接
  14. SerialPort, // 串口
  15. Ethernet // 网口
  16. }
  17. /// <summary>
  18. /// SCPI 设备通讯类,支持串口和网口连接
  19. /// </summary>
  20. public class ScpiDevice : IDisposable
  21. {
  22. private SerialPort? _serialPort;
  23. private TcpClient? _tcpClient;
  24. private NetworkStream? _networkStream;
  25. private ConnectionType _connectionType;
  26. private readonly object _lockObj = new object();
  27. private volatile bool _isDisposed = false;
  28. private volatile bool _isDisconnecting = false;
  29. public bool IsConnected
  30. {
  31. get
  32. {
  33. // 不使用锁,避免阻塞 UI 线程(这些属性读取是线程安全的)
  34. if (_isDisposed || _isDisconnecting) return false;
  35. return _connectionType switch
  36. {
  37. ConnectionType.SerialPort => _serialPort?.IsOpen ?? false,
  38. ConnectionType.Ethernet => _tcpClient?.Connected ?? false,
  39. _ => false
  40. };
  41. }
  42. }
  43. public ConnectionType CurrentConnectionType => _connectionType;
  44. /// <summary>
  45. /// 通过串口连接设备
  46. /// </summary>
  47. public bool ConnectSerial(string portName, int baudRate = 9600, int dataBits = 8,
  48. Parity parity = Parity.None, StopBits stopBits = StopBits.One)
  49. {
  50. try
  51. {
  52. lock (_lockObj)
  53. {
  54. Disconnect();
  55. _serialPort = new SerialPort
  56. {
  57. PortName = portName,
  58. BaudRate = baudRate,
  59. DataBits = dataBits,
  60. Parity = parity,
  61. StopBits = stopBits,
  62. Encoding = Encoding.ASCII,
  63. ReadTimeout = 1000, // 减少超时以更快检测断连
  64. WriteTimeout = 1000,
  65. NewLine = "\n"
  66. };
  67. _serialPort.Open();
  68. Thread.Sleep(100); // 等待端口稳定
  69. // 清空缓冲区
  70. _serialPort.DiscardInBuffer();
  71. _serialPort.DiscardOutBuffer();
  72. _connectionType = ConnectionType.SerialPort;
  73. return true;
  74. }
  75. }
  76. catch (Exception ex)
  77. {
  78. throw new Exception($"串口连接失败: {ex.Message}", ex);
  79. }
  80. }
  81. /// <summary>
  82. /// 连接超时时间(毫秒)
  83. /// </summary>
  84. public int ConnectionTimeout { get; set; } = 5000;
  85. /// <summary>
  86. /// 通过网口连接设备
  87. /// </summary>
  88. public bool ConnectEthernet(string ipAddress, int port = 2268)
  89. {
  90. TcpClient? tempClient = null;
  91. try
  92. {
  93. lock (_lockObj)
  94. {
  95. // 先彻底断开并释放旧连接
  96. ForceDisconnect();
  97. // 等待端口完全释放
  98. Thread.Sleep(100);
  99. tempClient = new TcpClient();
  100. // 设置 LingerState 以立即释放端口
  101. tempClient.LingerState = new System.Net.Sockets.LingerOption(true, 0);
  102. tempClient.NoDelay = true;
  103. // 使用带超时的连接
  104. var connectTask = tempClient.ConnectAsync(ipAddress, port);
  105. if (!connectTask.Wait(ConnectionTimeout))
  106. {
  107. // 超时 - 强制关闭
  108. try { tempClient.Close(); } catch { }
  109. try { tempClient.Dispose(); } catch { }
  110. tempClient = null;
  111. throw new TimeoutException($"连接超时({ConnectionTimeout/1000}秒),请检查设备IP地址和端口是否正确");
  112. }
  113. // 检查连接是否真的成功
  114. if (!tempClient.Connected)
  115. {
  116. try { tempClient.Close(); } catch { }
  117. try { tempClient.Dispose(); } catch { }
  118. tempClient = null;
  119. throw new Exception("连接失败,设备未响应");
  120. }
  121. _tcpClient = tempClient;
  122. tempClient = null; // 转移所有权,防止 finally 中释放
  123. _networkStream = _tcpClient.GetStream();
  124. _networkStream.ReadTimeout = 1000;
  125. _networkStream.WriteTimeout = 1000;
  126. Thread.Sleep(100); // 等待连接稳定
  127. _connectionType = ConnectionType.Ethernet;
  128. return true;
  129. }
  130. }
  131. catch (TimeoutException)
  132. {
  133. throw;
  134. }
  135. catch (AggregateException ae)
  136. {
  137. var innerEx = ae.InnerException ?? ae;
  138. throw new Exception($"网口连接失败: {innerEx.Message}", innerEx);
  139. }
  140. catch (Exception ex)
  141. {
  142. throw new Exception($"网口连接失败: {ex.Message}", ex);
  143. }
  144. finally
  145. {
  146. // 确保临时客户端被释放(如果没有成功转移所有权)
  147. if (tempClient != null)
  148. {
  149. try { tempClient.Close(); } catch { }
  150. try { tempClient.Dispose(); } catch { }
  151. }
  152. }
  153. }
  154. /// <summary>
  155. /// 强制断开连接并释放所有资源
  156. /// </summary>
  157. private void ForceDisconnect()
  158. {
  159. _isDisconnecting = true;
  160. try
  161. {
  162. // 关闭网络流
  163. if (_networkStream != null)
  164. {
  165. try { _networkStream.Close(); } catch { }
  166. try { _networkStream.Dispose(); } catch { }
  167. _networkStream = null;
  168. }
  169. // 强制关闭 TCP 连接
  170. if (_tcpClient != null)
  171. {
  172. try
  173. {
  174. // 设置 LingerState 为立即关闭
  175. _tcpClient.LingerState = new System.Net.Sockets.LingerOption(true, 0);
  176. }
  177. catch { }
  178. try { _tcpClient.Close(); } catch { }
  179. try { _tcpClient.Dispose(); } catch { }
  180. _tcpClient = null;
  181. }
  182. // 关闭串口
  183. if (_serialPort != null)
  184. {
  185. try
  186. {
  187. if (_serialPort.IsOpen)
  188. {
  189. _serialPort.DiscardInBuffer();
  190. _serialPort.DiscardOutBuffer();
  191. _serialPort.Close();
  192. }
  193. }
  194. catch { }
  195. try { _serialPort.Dispose(); } catch { }
  196. _serialPort = null;
  197. }
  198. _connectionType = ConnectionType.None;
  199. }
  200. finally
  201. {
  202. _isDisconnecting = false;
  203. }
  204. }
  205. /// <summary>
  206. /// 连接设备(兼容旧方法)
  207. /// </summary>
  208. [Obsolete("请使用 ConnectSerial 或 ConnectEthernet")]
  209. public bool Connect(string portName, int baudRate = 9600, int dataBits = 8,
  210. Parity parity = Parity.None, StopBits stopBits = StopBits.One)
  211. {
  212. return ConnectSerial(portName, baudRate, dataBits, parity, stopBits);
  213. }
  214. /// <summary>
  215. /// 断开连接
  216. /// </summary>
  217. public void Disconnect()
  218. {
  219. lock (_lockObj)
  220. {
  221. ForceDisconnect();
  222. }
  223. }
  224. /// <summary>
  225. /// 启用命令调试日志
  226. /// </summary>
  227. public static bool EnableCommandLog { get; set; } = false;
  228. /// <summary>
  229. /// 命令日志事件
  230. /// </summary>
  231. public static event Action<string>? OnCommandLog;
  232. /// <summary>
  233. /// 发送 SCPI 命令(无返回)
  234. /// </summary>
  235. public void SendCommand(string command)
  236. {
  237. lock (_lockObj)
  238. {
  239. CheckConnectionState();
  240. try
  241. {
  242. if (EnableCommandLog)
  243. {
  244. OnCommandLog?.Invoke($"[SCPI TX] {command}");
  245. }
  246. if (_connectionType == ConnectionType.SerialPort)
  247. {
  248. if (_serialPort == null || !_serialPort.IsOpen)
  249. throw new InvalidOperationException("串口未打开");
  250. _serialPort.WriteLine(command);
  251. }
  252. else // Ethernet
  253. {
  254. if (_networkStream == null || !_networkStream.CanWrite)
  255. throw new InvalidOperationException("网络流不可写");
  256. byte[] data = Encoding.ASCII.GetBytes(command + "\n");
  257. _networkStream.Write(data, 0, data.Length);
  258. }
  259. Thread.Sleep(20); // 给设备处理时间(减少延迟)
  260. }
  261. catch (ObjectDisposedException)
  262. {
  263. throw new InvalidOperationException("设备连接已关闭");
  264. }
  265. catch (System.IO.IOException ex)
  266. {
  267. throw new InvalidOperationException($"通信错误: {ex.Message}", ex);
  268. }
  269. }
  270. }
  271. /// <summary>
  272. /// 发送 SCPI 查询命令并获取返回值
  273. /// </summary>
  274. public string Query(string command)
  275. {
  276. lock (_lockObj)
  277. {
  278. CheckConnectionState();
  279. string response;
  280. try
  281. {
  282. if (EnableCommandLog)
  283. {
  284. OnCommandLog?.Invoke($"[SCPI TX] {command}");
  285. }
  286. if (_connectionType == ConnectionType.SerialPort)
  287. {
  288. if (_serialPort == null || !_serialPort.IsOpen)
  289. throw new InvalidOperationException("串口未打开");
  290. // 串口方式
  291. _serialPort.DiscardInBuffer();
  292. _serialPort.WriteLine(command);
  293. Thread.Sleep(30); // 减少延迟以提高响应速度
  294. response = _serialPort.ReadLine().Trim();
  295. }
  296. else // Ethernet
  297. {
  298. if (_networkStream == null || !_networkStream.CanWrite)
  299. throw new InvalidOperationException("网络流不可用");
  300. // 网口方式
  301. byte[] sendData = Encoding.ASCII.GetBytes(command + "\n");
  302. _networkStream.Write(sendData, 0, sendData.Length);
  303. Thread.Sleep(30); // 减少延迟以提高响应速度
  304. byte[] buffer = new byte[4096];
  305. int bytesRead = _networkStream.Read(buffer, 0, buffer.Length);
  306. response = Encoding.ASCII.GetString(buffer, 0, bytesRead).Trim();
  307. }
  308. }
  309. catch (ObjectDisposedException)
  310. {
  311. throw new InvalidOperationException("设备连接已关闭");
  312. }
  313. catch (System.IO.IOException ex)
  314. {
  315. throw new InvalidOperationException($"通信错误: {ex.Message}", ex);
  316. }
  317. catch (TimeoutException ex)
  318. {
  319. throw new InvalidOperationException($"通信超时: {ex.Message}", ex);
  320. }
  321. return response;
  322. }
  323. }
  324. /// <summary>
  325. /// 检查连接状态(必须在锁内调用)
  326. /// </summary>
  327. private void CheckConnectionState()
  328. {
  329. if (_isDisposed)
  330. throw new ObjectDisposedException(nameof(ScpiDevice));
  331. if (_isDisconnecting)
  332. throw new InvalidOperationException("设备正在断开连接");
  333. bool connected = _connectionType switch
  334. {
  335. ConnectionType.SerialPort => _serialPort?.IsOpen ?? false,
  336. ConnectionType.Ethernet => _tcpClient?.Connected ?? false,
  337. _ => false
  338. };
  339. if (!connected)
  340. throw new InvalidOperationException("设备未连接");
  341. }
  342. /// <summary>
  343. /// 获取可用的串口列表
  344. /// </summary>
  345. public static string[] GetAvailablePorts()
  346. {
  347. return SerialPort.GetPortNames();
  348. }
  349. public void Dispose()
  350. {
  351. if (!_isDisposed)
  352. {
  353. Disconnect();
  354. _serialPort?.Dispose();
  355. _isDisposed = true;
  356. }
  357. }
  358. }
  359. }