ManualTestForm.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539
  1. using System;
  2. using System.Drawing;
  3. using System.Threading.Tasks;
  4. using System.Windows.Forms;
  5. using APS7100TestTool.Controllers;
  6. using APS7100TestTool.Models;
  7. using APS7100TestTool.Libraries;
  8. namespace APS7100TestTool.Forms
  9. {
  10. public partial class ManualTestForm : Form
  11. {
  12. private IPowerSupplyController? _controller;
  13. private DeviceType _deviceType;
  14. private System.Windows.Forms.Timer _measureTimer;
  15. // 防止测量操作重叠
  16. private volatile bool _isMeasuring = false;
  17. // 回调:暂停/恢复 Modbus 轮询
  18. // 当发送本地模式命令时,需要同时暂停 Modbus 轮询,否则设备会立即切换回远程模式
  19. public Action? OnSuspendModbusPolling { get; set; }
  20. public Action? OnResumeModbusPolling { get; set; }
  21. public ManualTestForm(IPowerSupplyController? controller, DeviceType deviceType)
  22. {
  23. InitializeComponent();
  24. // 启用双缓冲减少闪烁
  25. this.DoubleBuffered = true;
  26. this.SetStyle(ControlStyles.OptimizedDoubleBuffer |
  27. ControlStyles.AllPaintingInWmPaint |
  28. ControlStyles.UserPaint, true);
  29. this.UpdateStyles();
  30. _controller = controller;
  31. _deviceType = deviceType;
  32. _measureTimer = new System.Windows.Forms.Timer();
  33. _measureTimer.Interval = 1000;
  34. _measureTimer.Tick += MeasureTimer_Tick;
  35. InitializeUI();
  36. }
  37. private void InitializeUI()
  38. {
  39. // 设置窗口标题
  40. string deviceName = _deviceType == DeviceType.APS7100 ? "APS-7100 (AC)" : "PSW-250 (DC)";
  41. this.Text = $"手动测试 - {deviceName}";
  42. // 根据设备类型调整UI
  43. UpdateDeviceTypeUI();
  44. // 初始化命令库
  45. InitializeCommandLibrary();
  46. // 更新连接状态
  47. UpdateConnectionState(_controller != null);
  48. if (_controller != null)
  49. {
  50. ReadCurrentSettings();
  51. // ⚠ 不自动启动测量定时器!
  52. // APS7100 收到任何 SCPI 命令后会自动进入远程模式
  53. // 用户需要手动点击"恢复测量"按钮来启动定时测量
  54. // _measureTimer.Start(); // 注释掉,不自动启动
  55. UpdateMeasureButtonState(false); // 初始状态:测量已暂停
  56. LogMessage("ℹ 测量已暂停。点击「恢复测量」按钮可开始自动测量。");
  57. if (_deviceType == DeviceType.APS7100)
  58. {
  59. LogMessage("⚠ 注意:启动测量后 APS7100 将自动切换到远程模式");
  60. }
  61. }
  62. else
  63. {
  64. UpdateMeasureButtonState(false);
  65. }
  66. }
  67. private void UpdateDeviceTypeUI()
  68. {
  69. bool isAC = (_deviceType == DeviceType.APS7100);
  70. // AC电源独有控件
  71. lblFrequency.Visible = isAC;
  72. numFrequency.Visible = isAC;
  73. btnSetFrequency.Visible = isAC;
  74. lblWaveform.Visible = isAC;
  75. cmbWaveform.Visible = isAC;
  76. lblVoltageRange.Visible = isAC;
  77. cmbVoltageRange.Visible = isAC;
  78. // AC电源独有的测量值
  79. lblFrequencyLabel.Visible = isAC;
  80. lblFrequencyValue.Visible = isAC;
  81. lblPFLabel.Visible = isAC;
  82. lblPFValue.Visible = isAC;
  83. // 调整电压/电流的范围
  84. if (isAC)
  85. {
  86. numVoltage.Maximum = 350;
  87. numVoltage.Value = 220;
  88. numCurrent.Maximum = 20;
  89. numCurrent.Value = 5;
  90. lblCurrent.Text = "电流限值(A):";
  91. }
  92. else
  93. {
  94. numVoltage.Maximum = 72;
  95. numVoltage.Value = 12;
  96. numCurrent.Maximum = 4.5m;
  97. numCurrent.Value = 1;
  98. lblCurrent.Text = "电流设定(A):";
  99. }
  100. }
  101. private void InitializeCommandLibrary()
  102. {
  103. cmbCommandCategory.Items.Clear();
  104. cmbCommandCategory.Items.Add("全部命令");
  105. var categories = _deviceType == DeviceType.APS7100
  106. ? ScpiCommandLibrary.GetCategories()
  107. : PSW250CommandLibrary.GetCategories();
  108. foreach (string category in categories)
  109. {
  110. cmbCommandCategory.Items.Add(category);
  111. }
  112. if (cmbCommandCategory.Items.Count > 0)
  113. cmbCommandCategory.SelectedIndex = 0;
  114. }
  115. private void UpdateConnectionState(bool connected)
  116. {
  117. grpControl.Enabled = connected;
  118. grpMeasure.Enabled = connected;
  119. grpScpi.Enabled = connected;
  120. if (!connected)
  121. {
  122. lblStatus.Text = "未连接";
  123. lblStatus.ForeColor = Color.Red;
  124. }
  125. else
  126. {
  127. lblStatus.Text = "已连接";
  128. lblStatus.ForeColor = Color.Green;
  129. }
  130. }
  131. /// <summary>
  132. /// 更新控制器(当主窗体连接/断开设备时调用)
  133. /// </summary>
  134. public void UpdateController(IPowerSupplyController? controller)
  135. {
  136. _controller = controller;
  137. UpdateConnectionState(_controller != null);
  138. if (_controller != null)
  139. {
  140. ReadCurrentSettings();
  141. // ⚠ 不自动启动测量定时器
  142. // _measureTimer.Start();
  143. UpdateMeasureButtonState(false);
  144. }
  145. else
  146. {
  147. _measureTimer.Stop();
  148. UpdateMeasureButtonState(false);
  149. }
  150. }
  151. private async void ReadCurrentSettings()
  152. {
  153. if (_controller == null) return;
  154. try
  155. {
  156. var controller = _controller;
  157. var deviceType = _deviceType;
  158. // 在后台线程执行读取操作,避免阻塞UI
  159. // 使用 try-catch 保护每个查询,避免不支持的命令导致整体失败
  160. var settings = await Task.Run(() =>
  161. {
  162. double voltage = 0, frequency = 0, current = 0;
  163. VoltageRange voltageRange = VoltageRange.Low;
  164. Waveform waveform = Waveform.Sine;
  165. bool outputOn = false;
  166. try { voltage = controller.GetVoltage(); } catch { }
  167. try { frequency = deviceType == DeviceType.APS7100 ? controller.GetFrequency() : 0; } catch { }
  168. try { current = controller.GetCurrent(); } catch { }
  169. try { voltageRange = deviceType == DeviceType.APS7100 ? controller.GetVoltageRange() : VoltageRange.Low; } catch { }
  170. try { waveform = deviceType == DeviceType.APS7100 ? controller.GetWaveform() : Waveform.Sine; } catch { }
  171. try { outputOn = controller.GetOutputState(); } catch { }
  172. return new
  173. {
  174. Voltage = voltage,
  175. Frequency = frequency,
  176. Current = current,
  177. VoltageRange = voltageRange,
  178. Waveform = waveform,
  179. OutputOn = outputOn
  180. };
  181. });
  182. // 在UI线程更新控件
  183. numVoltage.Value = (decimal)settings.Voltage;
  184. if (_deviceType == DeviceType.APS7100)
  185. {
  186. numFrequency.Value = (decimal)settings.Frequency;
  187. cmbVoltageRange.SelectedIndex = settings.VoltageRange == VoltageRange.Low ? 0 : 1;
  188. cmbWaveform.SelectedIndex = (int)settings.Waveform;
  189. }
  190. numCurrent.Value = (decimal)settings.Current;
  191. chkOutput.Checked = settings.OutputOn;
  192. }
  193. catch (Exception ex)
  194. {
  195. LogMessage($"读取设置失败: {ex.Message}");
  196. }
  197. }
  198. private async void MeasureTimer_Tick(object? sender, EventArgs e)
  199. {
  200. if (_controller == null || _isMeasuring) return;
  201. _isMeasuring = true;
  202. try
  203. {
  204. var controller = _controller;
  205. var deviceType = _deviceType;
  206. // 在后台线程执行测量操作,避免阻塞UI
  207. var measurements = await Task.Run(() =>
  208. {
  209. var result = new
  210. {
  211. Voltage = controller.MeasureVoltage(),
  212. Current = controller.MeasureCurrent(),
  213. Power = controller.MeasurePower(),
  214. Frequency = deviceType == DeviceType.APS7100 ? controller.MeasureFrequency() : 0,
  215. PowerFactor = deviceType == DeviceType.APS7100 ? controller.MeasurePowerFactor() : 0
  216. };
  217. return result;
  218. });
  219. // 在UI线程更新显示
  220. lblVoltageValue.Text = $"{measurements.Voltage:F2} V";
  221. lblCurrentValue.Text = $"{measurements.Current:F3} A";
  222. lblPowerValue.Text = $"{measurements.Power:F2} W";
  223. if (_deviceType == DeviceType.APS7100)
  224. {
  225. lblFrequencyValue.Text = $"{measurements.Frequency:F2} Hz";
  226. lblPFValue.Text = $"{measurements.PowerFactor:F3}";
  227. }
  228. }
  229. catch { }
  230. finally
  231. {
  232. _isMeasuring = false;
  233. }
  234. }
  235. #region Control Events
  236. private void btnSetVoltage_Click(object sender, EventArgs e)
  237. {
  238. if (_controller == null) return;
  239. try
  240. {
  241. _controller.SetVoltage((double)numVoltage.Value);
  242. LogMessage($"设置电压: {numVoltage.Value} V");
  243. }
  244. catch (Exception ex) { MessageBox.Show(ex.Message); }
  245. }
  246. private void btnSetFrequency_Click(object sender, EventArgs e)
  247. {
  248. if (_controller == null) return;
  249. try
  250. {
  251. _controller.SetFrequency((double)numFrequency.Value);
  252. LogMessage($"设置频率: {numFrequency.Value} Hz");
  253. }
  254. catch (Exception ex) { MessageBox.Show(ex.Message); }
  255. }
  256. private void btnSetCurrent_Click(object sender, EventArgs e)
  257. {
  258. if (_controller == null) return;
  259. try
  260. {
  261. _controller.SetCurrent((double)numCurrent.Value);
  262. LogMessage($"设置电流: {numCurrent.Value} A");
  263. }
  264. catch (Exception ex) { MessageBox.Show(ex.Message); }
  265. }
  266. private void cmbVoltageRange_SelectedIndexChanged(object sender, EventArgs e)
  267. {
  268. if (_controller == null || cmbVoltageRange.SelectedIndex < 0) return;
  269. try
  270. {
  271. var range = cmbVoltageRange.SelectedIndex == 0 ? VoltageRange.Low : VoltageRange.High;
  272. _controller.SetVoltageRange(range);
  273. LogMessage($"设置量程: {range}");
  274. }
  275. catch (Exception ex) { MessageBox.Show(ex.Message); }
  276. }
  277. private void cmbWaveform_SelectedIndexChanged(object sender, EventArgs e)
  278. {
  279. if (_controller == null || cmbWaveform.SelectedIndex < 0) return;
  280. try
  281. {
  282. var wave = (Waveform)cmbWaveform.SelectedIndex;
  283. _controller.SetWaveform(wave);
  284. LogMessage($"设置波形: {wave}");
  285. }
  286. catch (Exception ex) { MessageBox.Show(ex.Message); }
  287. }
  288. private void chkOutput_CheckedChanged(object sender, EventArgs e)
  289. {
  290. if (_controller == null) return;
  291. try
  292. {
  293. _controller.SetOutput(chkOutput.Checked);
  294. chkOutput.Text = chkOutput.Checked ? "输出 ON" : "输出 OFF";
  295. chkOutput.BackColor = chkOutput.Checked ? Color.LightGreen : SystemColors.Control;
  296. LogMessage($"输出: {chkOutput.Text}");
  297. }
  298. catch { }
  299. }
  300. private void btnReset_Click(object sender, EventArgs e)
  301. {
  302. if (_controller == null) return;
  303. if (MessageBox.Show("确定重置?", "确认", MessageBoxButtons.YesNo) == DialogResult.Yes)
  304. {
  305. try
  306. {
  307. _controller.Reset();
  308. LogMessage("设备已重置");
  309. ReadCurrentSettings();
  310. }
  311. catch (Exception ex) { MessageBox.Show(ex.Message); }
  312. }
  313. }
  314. #endregion
  315. #region SCPI Events
  316. private void cmbCommandCategory_SelectedIndexChanged(object sender, EventArgs e)
  317. {
  318. if (cmbCommandCategory.SelectedIndex < 0) return;
  319. lstCommands.Items.Clear();
  320. txtCommandDesc.Text = "";
  321. string? cat = cmbCommandCategory.SelectedItem?.ToString();
  322. if (cat == null) return;
  323. var cmds = cat == "全部命令"
  324. ? (_deviceType == DeviceType.APS7100 ? ScpiCommandLibrary.GetAllCommands() : PSW250CommandLibrary.GetAllCommands())
  325. : (_deviceType == DeviceType.APS7100 ? ScpiCommandLibrary.GetCommandsByCategory(cat) : PSW250CommandLibrary.GetCommandsByCategory(cat));
  326. foreach (var c in cmds) lstCommands.Items.Add(c);
  327. lstCommands.DisplayMember = "Command";
  328. }
  329. private void lstCommands_SelectedIndexChanged(object sender, EventArgs e)
  330. {
  331. if (lstCommands.SelectedItem is ScpiCommandInfo cmd)
  332. txtCommandDesc.Text = cmd.FullDescription;
  333. }
  334. private void btnUseCommand_Click(object sender, EventArgs e)
  335. {
  336. if (lstCommands.SelectedItem is ScpiCommandInfo cmd)
  337. txtScpiCommand.Text = cmd.Command;
  338. }
  339. private async void btnSendCommand_Click(object sender, EventArgs e)
  340. {
  341. if (_controller == null) return;
  342. string cmd = txtScpiCommand.Text.Trim();
  343. if (string.IsNullOrEmpty(cmd)) return;
  344. // 暂停定时器,避免与手动命令冲突
  345. _measureTimer.Stop();
  346. // 检测是否是"本地模式"命令
  347. // APS7100 收到任何 SCPI 命令后都会自动进入远程模式
  348. // 如果用户发送本地模式命令,就不应该恢复测量定时器
  349. bool isLocalModeCommand = IsLocalModeCommand(cmd);
  350. try
  351. {
  352. // 等待当前测量完成
  353. while (_isMeasuring)
  354. {
  355. await Task.Delay(50);
  356. }
  357. // 在后台线程执行命令,避免阻塞UI
  358. var result = await Task.Run(() =>
  359. {
  360. if (cmd.EndsWith("?"))
  361. {
  362. return new { IsQuery = true, Response = _controller.SendCustomQuery(cmd) };
  363. }
  364. else
  365. {
  366. _controller.SendCustomCommand(cmd);
  367. return new { IsQuery = false, Response = "" };
  368. }
  369. });
  370. if (result.IsQuery)
  371. {
  372. LogMessage($"Send: {cmd}, Resp: {result.Response}");
  373. }
  374. else
  375. {
  376. LogMessage($"Sent: {cmd}");
  377. }
  378. // 如果是本地模式命令,提示用户并保持定时器暂停
  379. if (isLocalModeCommand)
  380. {
  381. // 暂停 Modbus 轮询(这是关键!否则 Modbus 服务会继续发送命令)
  382. OnSuspendModbusPolling?.Invoke();
  383. LogMessage("⚠ 已发送本地模式命令");
  384. LogMessage(" ✓ 手动测试页测量已暂停");
  385. LogMessage(" ✓ Modbus 服务轮询已暂停");
  386. LogMessage(" 设备将保持本地模式,点击「恢复测量」可恢复");
  387. UpdateMeasureButtonState(false);
  388. }
  389. }
  390. catch (Exception ex)
  391. {
  392. LogMessage($"Error: {ex.Message}");
  393. }
  394. finally
  395. {
  396. // 只有非本地模式命令才恢复定时器
  397. if (_controller != null && !isLocalModeCommand)
  398. {
  399. _measureTimer.Start();
  400. }
  401. }
  402. }
  403. /// <summary>
  404. /// 检测是否是切换到本地模式的命令
  405. /// </summary>
  406. private bool IsLocalModeCommand(string cmd)
  407. {
  408. if (string.IsNullOrEmpty(cmd)) return false;
  409. string upperCmd = cmd.ToUpper().Trim();
  410. // 常见的本地模式命令
  411. return upperCmd.Contains("SYST:LOC") ||
  412. upperCmd.Contains("SYST:COMM:RLST") && upperCmd.Contains("LOCAL") ||
  413. upperCmd.Contains("RLSTATE") && upperCmd.Contains("LOCAL") ||
  414. upperCmd == "GTL"; // IEEE 488.2 Go To Local
  415. }
  416. /// <summary>
  417. /// 更新测量按钮状态
  418. /// </summary>
  419. private void UpdateMeasureButtonState(bool isMeasuring)
  420. {
  421. if (btnToggleMeasure != null)
  422. {
  423. btnToggleMeasure.Text = isMeasuring ? "暂停测量" : "恢复测量";
  424. btnToggleMeasure.BackColor = isMeasuring ? Color.LightCoral : Color.LightGreen;
  425. }
  426. }
  427. #endregion
  428. private void btnClearLog_Click(object sender, EventArgs e) => txtLog.Clear();
  429. private void btnToggleMeasure_Click(object sender, EventArgs e)
  430. {
  431. if (_measureTimer.Enabled)
  432. {
  433. // 暂停测量
  434. _measureTimer.Stop();
  435. OnSuspendModbusPolling?.Invoke();
  436. UpdateMeasureButtonState(false);
  437. LogMessage("✓ 测量已暂停");
  438. LogMessage(" ✓ Modbus 服务轮询已暂停");
  439. LogMessage(" 设备将保持本地模式");
  440. }
  441. else
  442. {
  443. // 恢复测量
  444. // ⚠ 恢复测量会使 APS7100 自动切换到远程模式
  445. if (_deviceType == DeviceType.APS7100)
  446. {
  447. LogMessage("⚠ 恢复测量后,APS7100 将自动切换到远程模式");
  448. }
  449. OnResumeModbusPolling?.Invoke();
  450. _measureTimer.Start();
  451. UpdateMeasureButtonState(true);
  452. LogMessage("✓ 测量已恢复");
  453. LogMessage(" ✓ Modbus 服务轮询已恢复");
  454. }
  455. }
  456. private void LogMessage(string msg)
  457. {
  458. if (this.InvokeRequired)
  459. {
  460. this.Invoke(new Action<string>(LogMessage), msg);
  461. return;
  462. }
  463. txtLog.AppendText($"[{DateTime.Now:HH:mm:ss}] {msg}\r\n");
  464. }
  465. protected override void OnFormClosing(FormClosingEventArgs e)
  466. {
  467. _measureTimer?.Stop();
  468. base.OnFormClosing(e);
  469. }
  470. }
  471. }