SlaveTestForm.cs 47 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123
  1. using System;
  2. using System.Windows.Forms;
  3. using System.Drawing;
  4. using System.Diagnostics;
  5. using System.Threading;
  6. using System.Threading.Tasks;
  7. using CCDCount.DLL.CanBus;
  8. using CanTest;
  9. namespace CanOpenSlaveTest
  10. {
  11. /// <summary>
  12. /// CANopen从站设备测试界面
  13. /// </summary>
  14. public class SlaveTestForm : Form
  15. {
  16. private CanOpenSlaveDevice m_slaveDevice;
  17. // UI控件
  18. private GroupBox grpDeviceInfo;
  19. private Label lblNodeId;
  20. private Label lblState;
  21. private Label lblStatus;
  22. private GroupBox grpControl;
  23. private Button btnStart;
  24. private Button btnStop;
  25. private GroupBox grpPdo1Send;
  26. private Label lblPdo1Data0;
  27. private Label lblPdo1Data1;
  28. private Label lblPdo1Data2;
  29. private Label lblPdo1Data3;
  30. private Label lblPdo1Data4;
  31. private Label lblPdo1Data5;
  32. private Label lblPdo1Data6;
  33. private Label lblPdo1Data7;
  34. private TextBox txtPdo1Data0;
  35. private TextBox txtPdo1Data1;
  36. private TextBox txtPdo1Data2;
  37. private TextBox txtPdo1Data3;
  38. private TextBox txtPdo1Data4;
  39. private TextBox txtPdo1Data5;
  40. private TextBox txtPdo1Data6;
  41. private TextBox txtPdo1Data7;
  42. private Button btnSendTpdo1;
  43. private Button btnRuleSend;
  44. private Button btnContinuousSend;
  45. // 连续发送相关控件
  46. private Label lblPeriodTime;
  47. private TextBox txtPeriodTime;
  48. private Label lblPeriodUnit;
  49. // 连续发送运行时长相关控件
  50. private Label lblContinuousDuration;
  51. private TextBox txtContinuousDuration;
  52. private Label lblContinuousDurationUnit;
  53. // 规则发送运行时长相关控件
  54. private Label lblRuleDuration;
  55. private TextBox txtRuleDuration;
  56. private Label lblRuleDurationUnit;
  57. private TextBox txtLog;
  58. private System.ComponentModel.IContainer components;
  59. private System.Windows.Forms.Timer m_updateTimer;
  60. // 连续发送状态
  61. private bool m_isContinuousSending = false;
  62. private CancellationTokenSource m_continuousSendCts;
  63. private long m_continuousSendCounter = 0;
  64. // 连续发送运行时长相关
  65. private DateTime m_continuousSendStartTime;
  66. private int m_continuousSendDurationMs = 0;
  67. // 规则发送状态
  68. private bool m_isRuleSending = false;
  69. private long m_ruleSendCounter = 0;
  70. // 规则发送运行时长相关
  71. private DateTime m_ruleSendStartTime;
  72. private int m_ruleSendDurationMs = 0;
  73. private CancellationTokenSource m_ruleSendCts;
  74. public SlaveTestForm()
  75. {
  76. InitializeComponent();
  77. // 在构造函数中创建从站设备(避免设计器错误)
  78. if (!DesignMode)
  79. {
  80. m_slaveDevice = new CanOpenSlaveDevice(nodeId: 1);
  81. // 注册事件
  82. m_slaveDevice.OnRpdoReceived += SlaveDevice_OnRpdoReceived;
  83. m_slaveDevice.OnNmtStateChanged += SlaveDevice_OnNmtStateChanged;
  84. m_slaveDevice.OnSdoReadRequest += SlaveDevice_OnSdoReadRequest;
  85. m_slaveDevice.OnSdoWriteRequest += SlaveDevice_OnSdoWriteRequest;
  86. // 初始化日志
  87. AppendLog("CANopen从站测试程序已启动");
  88. AppendLog("节点ID: " + m_slaveDevice.NodeId.ToString());
  89. AppendLog("心跳周期: 1000ms (固定值)");
  90. AppendLog("点击'启动从站'按钮开始测试\n");
  91. }
  92. }
  93. private void InitializeComponent()
  94. {
  95. this.components = new System.ComponentModel.Container();
  96. this.grpDeviceInfo = new System.Windows.Forms.GroupBox();
  97. this.lblNodeId = new System.Windows.Forms.Label();
  98. this.lblState = new System.Windows.Forms.Label();
  99. this.lblStatus = new System.Windows.Forms.Label();
  100. this.grpControl = new System.Windows.Forms.GroupBox();
  101. this.btnStart = new System.Windows.Forms.Button();
  102. this.btnStop = new System.Windows.Forms.Button();
  103. this.grpPdo1Send = new System.Windows.Forms.GroupBox();
  104. this.lblPdo1Data0 = new System.Windows.Forms.Label();
  105. this.lblPdo1Data1 = new System.Windows.Forms.Label();
  106. this.lblPdo1Data2 = new System.Windows.Forms.Label();
  107. this.lblPdo1Data3 = new System.Windows.Forms.Label();
  108. this.lblPdo1Data4 = new System.Windows.Forms.Label();
  109. this.lblPdo1Data5 = new System.Windows.Forms.Label();
  110. this.lblPdo1Data6 = new System.Windows.Forms.Label();
  111. this.lblPdo1Data7 = new System.Windows.Forms.Label();
  112. this.txtPdo1Data0 = new System.Windows.Forms.TextBox();
  113. this.txtPdo1Data1 = new System.Windows.Forms.TextBox();
  114. this.txtPdo1Data2 = new System.Windows.Forms.TextBox();
  115. this.txtPdo1Data3 = new System.Windows.Forms.TextBox();
  116. this.txtPdo1Data4 = new System.Windows.Forms.TextBox();
  117. this.txtPdo1Data5 = new System.Windows.Forms.TextBox();
  118. this.txtPdo1Data6 = new System.Windows.Forms.TextBox();
  119. this.txtPdo1Data7 = new System.Windows.Forms.TextBox();
  120. this.btnSendTpdo1 = new System.Windows.Forms.Button();
  121. this.btnRuleSend = new System.Windows.Forms.Button();
  122. this.btnContinuousSend = new System.Windows.Forms.Button();
  123. this.lblPeriodTime = new System.Windows.Forms.Label();
  124. this.txtPeriodTime = new System.Windows.Forms.TextBox();
  125. this.lblPeriodUnit = new System.Windows.Forms.Label();
  126. // 添加连续发送时长相关控件
  127. this.lblContinuousDuration = new System.Windows.Forms.Label();
  128. this.txtContinuousDuration = new System.Windows.Forms.TextBox();
  129. this.lblContinuousDurationUnit = new System.Windows.Forms.Label();
  130. // 添加规则发送时长相关控件
  131. this.lblRuleDuration = new System.Windows.Forms.Label();
  132. this.txtRuleDuration = new System.Windows.Forms.TextBox();
  133. this.lblRuleDurationUnit = new System.Windows.Forms.Label();
  134. this.txtLog = new System.Windows.Forms.TextBox();
  135. this.m_updateTimer = new System.Windows.Forms.Timer(this.components);
  136. this.grpDeviceInfo.SuspendLayout();
  137. this.grpControl.SuspendLayout();
  138. this.grpPdo1Send.SuspendLayout();
  139. this.SuspendLayout();
  140. //
  141. // grpDeviceInfo
  142. //
  143. this.grpDeviceInfo.Controls.Add(this.lblNodeId);
  144. this.grpDeviceInfo.Controls.Add(this.lblState);
  145. this.grpDeviceInfo.Controls.Add(this.lblStatus);
  146. this.grpDeviceInfo.Location = new System.Drawing.Point(10, 10);
  147. this.grpDeviceInfo.Name = "grpDeviceInfo";
  148. this.grpDeviceInfo.Size = new System.Drawing.Size(380, 100);
  149. this.grpDeviceInfo.TabIndex = 0;
  150. this.grpDeviceInfo.TabStop = false;
  151. this.grpDeviceInfo.Text = "设备信息";
  152. //
  153. // lblNodeId
  154. //
  155. this.lblNodeId.AutoSize = true;
  156. this.lblNodeId.Location = new System.Drawing.Point(10, 25);
  157. this.lblNodeId.Name = "lblNodeId";
  158. this.lblNodeId.Size = new System.Drawing.Size(77, 15);
  159. this.lblNodeId.TabIndex = 0;
  160. this.lblNodeId.Text = "节点ID: -";
  161. //
  162. // lblState
  163. //
  164. this.lblState.AutoSize = true;
  165. this.lblState.Location = new System.Drawing.Point(10, 50);
  166. this.lblState.Name = "lblState";
  167. this.lblState.Size = new System.Drawing.Size(61, 15);
  168. this.lblState.TabIndex = 1;
  169. this.lblState.Text = "状态: -";
  170. //
  171. // lblStatus
  172. //
  173. this.lblStatus.AutoSize = true;
  174. this.lblStatus.Location = new System.Drawing.Point(10, 75);
  175. this.lblStatus.Name = "lblStatus";
  176. this.lblStatus.Size = new System.Drawing.Size(128, 15);
  177. this.lblStatus.TabIndex = 2;
  178. this.lblStatus.Text = "运行状态: 未启动";
  179. //
  180. // grpControl
  181. //
  182. this.grpControl.Controls.Add(this.btnStart);
  183. this.grpControl.Controls.Add(this.btnStop);
  184. this.grpControl.Location = new System.Drawing.Point(400, 10);
  185. this.grpControl.Name = "grpControl";
  186. this.grpControl.Size = new System.Drawing.Size(380, 100);
  187. this.grpControl.TabIndex = 1;
  188. this.grpControl.TabStop = false;
  189. this.grpControl.Text = "控制";
  190. //
  191. // btnStart
  192. //
  193. this.btnStart.Location = new System.Drawing.Point(10, 25);
  194. this.btnStart.Name = "btnStart";
  195. this.btnStart.Size = new System.Drawing.Size(100, 30);
  196. this.btnStart.TabIndex = 0;
  197. this.btnStart.Text = "启动从站";
  198. this.btnStart.Click += new System.EventHandler(this.BtnStart_Click);
  199. //
  200. // btnStop
  201. //
  202. this.btnStop.Enabled = false;
  203. this.btnStop.Location = new System.Drawing.Point(120, 25);
  204. this.btnStop.Name = "btnStop";
  205. this.btnStop.Size = new System.Drawing.Size(100, 30);
  206. this.btnStop.TabIndex = 1;
  207. this.btnStop.Text = "停止从站";
  208. this.btnStop.Click += new System.EventHandler(this.BtnStop_Click);
  209. //
  210. // grpPdo1Send
  211. //
  212. this.grpPdo1Send.Controls.Add(this.lblPdo1Data0);
  213. this.grpPdo1Send.Controls.Add(this.lblPdo1Data1);
  214. this.grpPdo1Send.Controls.Add(this.lblPdo1Data2);
  215. this.grpPdo1Send.Controls.Add(this.lblPdo1Data3);
  216. this.grpPdo1Send.Controls.Add(this.lblPdo1Data4);
  217. this.grpPdo1Send.Controls.Add(this.lblPdo1Data5);
  218. this.grpPdo1Send.Controls.Add(this.lblPdo1Data6);
  219. this.grpPdo1Send.Controls.Add(this.lblPdo1Data7);
  220. this.grpPdo1Send.Controls.Add(this.txtPdo1Data0);
  221. this.grpPdo1Send.Controls.Add(this.txtPdo1Data1);
  222. this.grpPdo1Send.Controls.Add(this.txtPdo1Data2);
  223. this.grpPdo1Send.Controls.Add(this.txtPdo1Data3);
  224. this.grpPdo1Send.Controls.Add(this.txtPdo1Data4);
  225. this.grpPdo1Send.Controls.Add(this.txtPdo1Data5);
  226. this.grpPdo1Send.Controls.Add(this.txtPdo1Data6);
  227. this.grpPdo1Send.Controls.Add(this.txtPdo1Data7);
  228. this.grpPdo1Send.Controls.Add(this.btnSendTpdo1);
  229. this.grpPdo1Send.Controls.Add(this.btnRuleSend);
  230. this.grpPdo1Send.Controls.Add(this.btnContinuousSend);
  231. this.grpPdo1Send.Controls.Add(this.lblPeriodTime);
  232. this.grpPdo1Send.Controls.Add(this.txtPeriodTime);
  233. this.grpPdo1Send.Controls.Add(this.lblPeriodUnit);
  234. // 添加连续发送时长相关控件
  235. this.grpPdo1Send.Controls.Add(this.lblContinuousDuration);
  236. this.grpPdo1Send.Controls.Add(this.txtContinuousDuration);
  237. this.grpPdo1Send.Controls.Add(this.lblContinuousDurationUnit);
  238. // 添加规则发送时长相关控件到界面
  239. this.grpPdo1Send.Controls.Add(this.lblRuleDuration);
  240. this.grpPdo1Send.Controls.Add(this.txtRuleDuration);
  241. this.grpPdo1Send.Controls.Add(this.lblRuleDurationUnit);
  242. this.grpPdo1Send.Location = new System.Drawing.Point(10, 120);
  243. this.grpPdo1Send.Name = "grpPdo1Send";
  244. this.grpPdo1Send.Size = new System.Drawing.Size(770, 190); // 增加高度以容纳新控件
  245. this.grpPdo1Send.TabIndex = 2;
  246. this.grpPdo1Send.TabStop = false;
  247. this.grpPdo1Send.Text = "TPDO1发送配置";
  248. //
  249. // lblPdo1Data0
  250. //
  251. this.lblPdo1Data0.AutoSize = true;
  252. this.lblPdo1Data0.Location = new System.Drawing.Point(10, 25);
  253. this.lblPdo1Data0.Name = "lblPdo1Data0";
  254. this.lblPdo1Data0.Size = new System.Drawing.Size(47, 15);
  255. this.lblPdo1Data0.TabIndex = 0;
  256. this.lblPdo1Data0.Text = "Byte0:";
  257. //
  258. // lblPdo1Data1
  259. //
  260. this.lblPdo1Data1.AutoSize = true;
  261. this.lblPdo1Data1.Location = new System.Drawing.Point(90, 25);
  262. this.lblPdo1Data1.Name = "lblPdo1Data1";
  263. this.lblPdo1Data1.Size = new System.Drawing.Size(47, 15);
  264. this.lblPdo1Data1.TabIndex = 1;
  265. this.lblPdo1Data1.Text = "Byte1:";
  266. //
  267. // lblPdo1Data2
  268. //
  269. this.lblPdo1Data2.AutoSize = true;
  270. this.lblPdo1Data2.Location = new System.Drawing.Point(170, 25);
  271. this.lblPdo1Data2.Name = "lblPdo1Data2";
  272. this.lblPdo1Data2.Size = new System.Drawing.Size(47, 15);
  273. this.lblPdo1Data2.TabIndex = 2;
  274. this.lblPdo1Data2.Text = "Byte2:";
  275. //
  276. // lblPdo1Data3
  277. //
  278. this.lblPdo1Data3.AutoSize = true;
  279. this.lblPdo1Data3.Location = new System.Drawing.Point(250, 25);
  280. this.lblPdo1Data3.Name = "lblPdo1Data3";
  281. this.lblPdo1Data3.Size = new System.Drawing.Size(47, 15);
  282. this.lblPdo1Data3.TabIndex = 3;
  283. this.lblPdo1Data3.Text = "Byte3:";
  284. //
  285. // lblPdo1Data4
  286. //
  287. this.lblPdo1Data4.AutoSize = true;
  288. this.lblPdo1Data4.Location = new System.Drawing.Point(330, 25);
  289. this.lblPdo1Data4.Name = "lblPdo1Data4";
  290. this.lblPdo1Data4.Size = new System.Drawing.Size(47, 15);
  291. this.lblPdo1Data4.TabIndex = 4;
  292. this.lblPdo1Data4.Text = "Byte4:";
  293. //
  294. // lblPdo1Data5
  295. //
  296. this.lblPdo1Data5.AutoSize = true;
  297. this.lblPdo1Data5.Location = new System.Drawing.Point(410, 25);
  298. this.lblPdo1Data5.Name = "lblPdo1Data5";
  299. this.lblPdo1Data5.Size = new System.Drawing.Size(47, 15);
  300. this.lblPdo1Data5.TabIndex = 5;
  301. this.lblPdo1Data5.Text = "Byte5:";
  302. //
  303. // lblPdo1Data6
  304. //
  305. this.lblPdo1Data6.AutoSize = true;
  306. this.lblPdo1Data6.Location = new System.Drawing.Point(490, 25);
  307. this.lblPdo1Data6.Name = "lblPdo1Data6";
  308. this.lblPdo1Data6.Size = new System.Drawing.Size(47, 15);
  309. this.lblPdo1Data6.TabIndex = 6;
  310. this.lblPdo1Data6.Text = "Byte6:";
  311. //
  312. // lblPdo1Data7
  313. //
  314. this.lblPdo1Data7.AutoSize = true;
  315. this.lblPdo1Data7.Location = new System.Drawing.Point(570, 25);
  316. this.lblPdo1Data7.Name = "lblPdo1Data7";
  317. this.lblPdo1Data7.Size = new System.Drawing.Size(47, 15);
  318. this.lblPdo1Data7.TabIndex = 7;
  319. this.lblPdo1Data7.Text = "Byte7:";
  320. //
  321. // txtPdo1Data0
  322. //
  323. this.txtPdo1Data0.Location = new System.Drawing.Point(10, 45);
  324. this.txtPdo1Data0.MaxLength = 3;
  325. this.txtPdo1Data0.Name = "txtPdo1Data0";
  326. this.txtPdo1Data0.Size = new System.Drawing.Size(60, 25);
  327. this.txtPdo1Data0.TabIndex = 8;
  328. this.txtPdo1Data0.Text = "0";
  329. this.txtPdo1Data0.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
  330. //
  331. // txtPdo1Data1
  332. //
  333. this.txtPdo1Data1.Location = new System.Drawing.Point(90, 45);
  334. this.txtPdo1Data1.MaxLength = 3;
  335. this.txtPdo1Data1.Name = "txtPdo1Data1";
  336. this.txtPdo1Data1.Size = new System.Drawing.Size(60, 25);
  337. this.txtPdo1Data1.TabIndex = 9;
  338. this.txtPdo1Data1.Text = "255";
  339. this.txtPdo1Data1.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
  340. //
  341. // txtPdo1Data2
  342. //
  343. this.txtPdo1Data2.Location = new System.Drawing.Point(170, 45);
  344. this.txtPdo1Data2.MaxLength = 3;
  345. this.txtPdo1Data2.Name = "txtPdo1Data2";
  346. this.txtPdo1Data2.Size = new System.Drawing.Size(60, 25);
  347. this.txtPdo1Data2.TabIndex = 10;
  348. this.txtPdo1Data2.Text = "255";
  349. this.txtPdo1Data2.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
  350. //
  351. // txtPdo1Data3
  352. //
  353. this.txtPdo1Data3.Location = new System.Drawing.Point(250, 45);
  354. this.txtPdo1Data3.MaxLength = 3;
  355. this.txtPdo1Data3.Name = "txtPdo1Data3";
  356. this.txtPdo1Data3.Size = new System.Drawing.Size(60, 25);
  357. this.txtPdo1Data3.TabIndex = 11;
  358. this.txtPdo1Data3.Text = "255";
  359. this.txtPdo1Data3.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
  360. //
  361. // txtPdo1Data4
  362. //
  363. this.txtPdo1Data4.Location = new System.Drawing.Point(330, 45);
  364. this.txtPdo1Data4.MaxLength = 3;
  365. this.txtPdo1Data4.Name = "txtPdo1Data4";
  366. this.txtPdo1Data4.Size = new System.Drawing.Size(60, 25);
  367. this.txtPdo1Data4.TabIndex = 12;
  368. this.txtPdo1Data4.Text = "255";
  369. this.txtPdo1Data4.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
  370. //
  371. // txtPdo1Data5
  372. //
  373. this.txtPdo1Data5.Location = new System.Drawing.Point(410, 45);
  374. this.txtPdo1Data5.MaxLength = 3;
  375. this.txtPdo1Data5.Name = "txtPdo1Data5";
  376. this.txtPdo1Data5.Size = new System.Drawing.Size(60, 25);
  377. this.txtPdo1Data5.TabIndex = 13;
  378. this.txtPdo1Data5.Text = "255";
  379. this.txtPdo1Data5.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
  380. //
  381. // txtPdo1Data6
  382. //
  383. this.txtPdo1Data6.Location = new System.Drawing.Point(490, 45);
  384. this.txtPdo1Data6.MaxLength = 3;
  385. this.txtPdo1Data6.Name = "txtPdo1Data6";
  386. this.txtPdo1Data6.Size = new System.Drawing.Size(60, 25);
  387. this.txtPdo1Data6.TabIndex = 14;
  388. this.txtPdo1Data6.Text = "255";
  389. this.txtPdo1Data6.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
  390. //
  391. // txtPdo1Data7
  392. //
  393. this.txtPdo1Data7.Location = new System.Drawing.Point(570, 45);
  394. this.txtPdo1Data7.MaxLength = 3;
  395. this.txtPdo1Data7.Name = "txtPdo1Data7";
  396. this.txtPdo1Data7.Size = new System.Drawing.Size(60, 25);
  397. this.txtPdo1Data7.TabIndex = 15;
  398. this.txtPdo1Data7.Text = "255";
  399. this.txtPdo1Data7.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
  400. //
  401. // btnSendTpdo1
  402. //
  403. this.btnSendTpdo1.Enabled = false;
  404. this.btnSendTpdo1.Location = new System.Drawing.Point(10, 95);
  405. this.btnSendTpdo1.Name = "btnSendTpdo1";
  406. this.btnSendTpdo1.Size = new System.Drawing.Size(100, 25);
  407. this.btnSendTpdo1.TabIndex = 16;
  408. this.btnSendTpdo1.Text = "立即发送";
  409. this.btnSendTpdo1.Click += new System.EventHandler(this.BtnSendTpdo1_Click);
  410. //
  411. // btnRuleSend
  412. //
  413. this.btnRuleSend.Enabled = false;
  414. this.btnRuleSend.Location = new System.Drawing.Point(120, 95);
  415. this.btnRuleSend.Name = "btnRuleSend";
  416. this.btnRuleSend.Size = new System.Drawing.Size(100, 25);
  417. this.btnRuleSend.TabIndex = 17;
  418. this.btnRuleSend.Text = "规则发送";
  419. this.btnRuleSend.Click += new System.EventHandler(this.BtnRuleSend_Click);
  420. //
  421. // btnContinuousSend
  422. //
  423. this.btnContinuousSend.Enabled = false;
  424. this.btnContinuousSend.Location = new System.Drawing.Point(230, 95);
  425. this.btnContinuousSend.Name = "btnContinuousSend";
  426. this.btnContinuousSend.Size = new System.Drawing.Size(100, 25);
  427. this.btnContinuousSend.TabIndex = 18;
  428. this.btnContinuousSend.Text = "连续发送";
  429. this.btnContinuousSend.Click += new System.EventHandler(this.BtnContinuousSend_Click);
  430. //
  431. // lblPeriodTime
  432. //
  433. this.lblPeriodTime.AutoSize = true;
  434. this.lblPeriodTime.Location = new System.Drawing.Point(350, 98);
  435. this.lblPeriodTime.Name = "lblPeriodTime";
  436. this.lblPeriodTime.Size = new System.Drawing.Size(77, 15);
  437. this.lblPeriodTime.TabIndex = 19;
  438. this.lblPeriodTime.Text = "周期时间:";
  439. //
  440. // txtPeriodTime
  441. //
  442. this.txtPeriodTime.Location = new System.Drawing.Point(430, 95);
  443. this.txtPeriodTime.Name = "txtPeriodTime";
  444. this.txtPeriodTime.Size = new System.Drawing.Size(80, 25);
  445. this.txtPeriodTime.TabIndex = 20;
  446. this.txtPeriodTime.Text = "1000";
  447. this.txtPeriodTime.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
  448. //
  449. // lblPeriodUnit
  450. //
  451. this.lblPeriodUnit.AutoSize = true;
  452. this.lblPeriodUnit.Location = new System.Drawing.Point(520, 98);
  453. this.lblPeriodUnit.Name = "lblPeriodUnit";
  454. this.lblPeriodUnit.Size = new System.Drawing.Size(23, 15);
  455. this.lblPeriodUnit.TabIndex = 21;
  456. this.lblPeriodUnit.Text = "us";
  457. //
  458. // lblContinuousDuration
  459. //
  460. this.lblContinuousDuration.AutoSize = true;
  461. this.lblContinuousDuration.Location = new System.Drawing.Point(10, 128);
  462. this.lblContinuousDuration.Name = "lblContinuousDuration";
  463. this.lblContinuousDuration.Size = new System.Drawing.Size(77, 15);
  464. this.lblContinuousDuration.TabIndex = 22;
  465. this.lblContinuousDuration.Text = "持续时间:";
  466. //
  467. // txtContinuousDuration
  468. //
  469. this.txtContinuousDuration.Location = new System.Drawing.Point(90, 125);
  470. this.txtContinuousDuration.Name = "txtContinuousDuration";
  471. this.txtContinuousDuration.Size = new System.Drawing.Size(80, 25);
  472. this.txtContinuousDuration.TabIndex = 23;
  473. this.txtContinuousDuration.Text = "0";
  474. this.txtContinuousDuration.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
  475. //
  476. // lblContinuousDurationUnit
  477. //
  478. this.lblContinuousDurationUnit.AutoSize = true;
  479. this.lblContinuousDurationUnit.Location = new System.Drawing.Point(180, 128);
  480. this.lblContinuousDurationUnit.Name = "lblContinuousDurationUnit";
  481. this.lblContinuousDurationUnit.Size = new System.Drawing.Size(62, 15);
  482. this.lblContinuousDurationUnit.TabIndex = 24;
  483. this.lblContinuousDurationUnit.Text = "秒(0=无限制)";
  484. //
  485. // lblRuleDuration
  486. //
  487. this.lblRuleDuration.AutoSize = true;
  488. this.lblRuleDuration.Location = new System.Drawing.Point(350, 128);
  489. this.lblRuleDuration.Name = "lblRuleDuration";
  490. this.lblRuleDuration.Size = new System.Drawing.Size(77, 15);
  491. this.lblRuleDuration.TabIndex = 25;
  492. this.lblRuleDuration.Text = "规则时长:";
  493. //
  494. // txtRuleDuration
  495. //
  496. this.txtRuleDuration.Location = new System.Drawing.Point(430, 125);
  497. this.txtRuleDuration.Name = "txtRuleDuration";
  498. this.txtRuleDuration.Size = new System.Drawing.Size(80, 25);
  499. this.txtRuleDuration.TabIndex = 26;
  500. this.txtRuleDuration.Text = "0";
  501. this.txtRuleDuration.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
  502. //
  503. // lblRuleDurationUnit
  504. //
  505. this.lblRuleDurationUnit.AutoSize = true;
  506. this.lblRuleDurationUnit.Location = new System.Drawing.Point(520, 128);
  507. this.lblRuleDurationUnit.Name = "lblRuleDurationUnit";
  508. this.lblRuleDurationUnit.Size = new System.Drawing.Size(62, 15);
  509. this.lblRuleDurationUnit.TabIndex = 27;
  510. this.lblRuleDurationUnit.Text = "秒(0=无限制)";
  511. //
  512. // txtLog
  513. //
  514. this.txtLog.BackColor = System.Drawing.Color.White;
  515. this.txtLog.Font = new System.Drawing.Font("微软雅黑", 9F);
  516. this.txtLog.ForeColor = System.Drawing.Color.Black;
  517. this.txtLog.Location = new System.Drawing.Point(10, 320); // 调整位置
  518. this.txtLog.Multiline = true;
  519. this.txtLog.Name = "txtLog";
  520. this.txtLog.ReadOnly = true;
  521. this.txtLog.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
  522. this.txtLog.Size = new System.Drawing.Size(770, 210); // 调整尺寸
  523. this.txtLog.TabIndex = 3;
  524. //
  525. // m_updateTimer
  526. //
  527. this.m_updateTimer.Enabled = true;
  528. this.m_updateTimer.Interval = 500;
  529. //
  530. // SlaveTestForm
  531. //
  532. this.ClientSize = new System.Drawing.Size(782, 543);
  533. this.Controls.Add(this.grpDeviceInfo);
  534. this.Controls.Add(this.grpControl);
  535. this.Controls.Add(this.grpPdo1Send);
  536. this.Controls.Add(this.txtLog);
  537. this.Name = "SlaveTestForm";
  538. this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
  539. this.Text = "CANopen从站设备测试";
  540. this.grpDeviceInfo.ResumeLayout(false);
  541. this.grpDeviceInfo.PerformLayout();
  542. this.grpControl.ResumeLayout(false);
  543. this.grpPdo1Send.ResumeLayout(false);
  544. this.grpPdo1Send.PerformLayout();
  545. this.ResumeLayout(false);
  546. this.PerformLayout();
  547. }
  548. private void BtnStart_Click(object sender, EventArgs e)
  549. {
  550. try
  551. {
  552. AppendLog("正在启动从站...");
  553. if (m_slaveDevice.Start(CanBaudRate.BaudRate_1M))
  554. {
  555. AppendLog("✓ 从站启动成功");
  556. btnStart.Enabled = false;
  557. btnStop.Enabled = true;
  558. btnSendTpdo1.Enabled = true;
  559. btnRuleSend.Enabled = true;
  560. btnContinuousSend.Enabled = true;
  561. UpdateDeviceInfo();
  562. }
  563. else
  564. {
  565. AppendLog("✗ 从站启动失败");
  566. MessageBox.Show("启动失败!请检查CAN设备连接。", "错误",
  567. MessageBoxButtons.OK, MessageBoxIcon.Error);
  568. }
  569. }
  570. catch (Exception ex)
  571. {
  572. AppendLog("✗ 启动错误: " + ex.Message);
  573. MessageBox.Show(ex.Message, "错误",
  574. MessageBoxButtons.OK, MessageBoxIcon.Error);
  575. }
  576. }
  577. private void BtnStop_Click(object sender, EventArgs e)
  578. {
  579. try
  580. {
  581. // 如果正在连续发送,先停止
  582. if (m_isContinuousSending)
  583. {
  584. StopContinuousSend();
  585. }
  586. // 如果正在规则发送,先停止
  587. if (m_isRuleSending)
  588. {
  589. StopRuleSend();
  590. }
  591. AppendLog("正在停止从站...");
  592. m_slaveDevice.Stop();
  593. AppendLog("✓ 从站已停止");
  594. btnStart.Enabled = true;
  595. btnStop.Enabled = false;
  596. btnSendTpdo1.Enabled = false;
  597. btnRuleSend.Enabled = false;
  598. btnContinuousSend.Enabled = false;
  599. btnContinuousSend.Text = "连续发送";
  600. btnRuleSend.Text = "规则发送";
  601. UpdateDeviceInfo();
  602. }
  603. catch (Exception ex)
  604. {
  605. AppendLog("✗ 停止错误: " + ex.Message);
  606. }
  607. }
  608. private void BtnSendTpdo1_Click(object sender, EventArgs e)
  609. {
  610. try
  611. {
  612. // 从界面读取PDO1数据
  613. byte[] pdoData = new byte[8];
  614. pdoData[0] = ParseByteValue(txtPdo1Data0.Text, 0);
  615. pdoData[1] = ParseByteValue(txtPdo1Data1.Text, 1);
  616. pdoData[2] = ParseByteValue(txtPdo1Data2.Text, 2);
  617. pdoData[3] = ParseByteValue(txtPdo1Data3.Text, 3);
  618. pdoData[4] = ParseByteValue(txtPdo1Data4.Text, 4);
  619. pdoData[5] = ParseByteValue(txtPdo1Data5.Text, 5);
  620. pdoData[6] = ParseByteValue(txtPdo1Data6.Text, 6);
  621. pdoData[7] = ParseByteValue(txtPdo1Data7.Text, 7);
  622. // 构建数据字符串用于日志
  623. string dataStr = BitConverter.ToString(pdoData).Replace("-", " ");
  624. AppendLog("→ 发送TPDO1: [" + dataStr + "]");
  625. // TODO: 这里需要修改CanOpenSlaveDevice以支持自定义数据发送
  626. // 暂时使用默认发送方法
  627. m_slaveDevice.SendTpdo(1, pdoData);
  628. }
  629. catch (Exception ex)
  630. {
  631. AppendLog("✗ 发送TPDO1失败: " + ex.Message);
  632. }
  633. }
  634. private void BtnContinuousSend_Click(object sender, EventArgs e)
  635. {
  636. if (m_isContinuousSending)
  637. {
  638. // 停止连续发送
  639. StopContinuousSend();
  640. }
  641. else
  642. {
  643. // 启动连续发送
  644. StartContinuousSend();
  645. }
  646. }
  647. /// <summary>
  648. /// 规则发送按钮点击事件
  649. /// </summary>
  650. private void BtnRuleSend_Click(object sender, EventArgs e)
  651. {
  652. if (m_isRuleSending)
  653. {
  654. // 停止规则发送
  655. StopRuleSend();
  656. }
  657. else
  658. {
  659. // 启动规则发送
  660. StartRuleSend();
  661. }
  662. }
  663. /// <summary>
  664. /// 启动连续发送
  665. /// </summary>
  666. private void StartContinuousSend()
  667. {
  668. try
  669. {
  670. // 解析周期时间(微秒)
  671. if (!double.TryParse(txtPeriodTime.Text.Trim(), out double periodUs) || periodUs <= 0)
  672. {
  673. AppendLog("✗ 周期时间无效,请输入大于0的数值(单位:微秒)");
  674. return;
  675. }
  676. // 解析运行时长(秒转毫秒),如果输入为空或无效,默认为0(无限制)
  677. int durationSeconds = 0;
  678. if (int.TryParse(txtContinuousDuration.Text.Trim(), out int parsedSeconds))
  679. {
  680. durationSeconds = Math.Max(0, parsedSeconds); // 确保非负数
  681. }
  682. m_continuousSendDurationMs = durationSeconds * 1000; // 转换为毫秒
  683. // 重置计数器
  684. m_continuousSendCounter = 0;
  685. m_continuousSendStartTime = DateTime.Now;
  686. // 创建取消令牌
  687. m_continuousSendCts = new CancellationTokenSource();
  688. m_isContinuousSending = true;
  689. // 更新按钮文本
  690. btnContinuousSend.Text = "停止发送";
  691. string durationText = durationSeconds > 0 ? durationSeconds.ToString() + "秒" : "无限制";
  692. AppendLog($"▶ 开始连续发送 - 周期: {periodUs:F0}us, 时长: {durationText}");
  693. // 启动异步发送任务
  694. Task.Run(() => ContinuousSendLoop(periodUs, m_continuousSendCts.Token));
  695. // 如果设置了运行时长,则启动定时器来检查时间
  696. if (m_continuousSendDurationMs > 0)
  697. {
  698. Task.Delay(m_continuousSendDurationMs, m_continuousSendCts.Token)
  699. .ContinueWith(t =>
  700. {
  701. if (!t.IsCanceled)
  702. {
  703. // 时间到了,自动停止连续发送
  704. if (this.InvokeRequired)
  705. {
  706. this.Invoke(new Action(StopContinuousSend));
  707. }
  708. else
  709. {
  710. StopContinuousSend();
  711. }
  712. }
  713. });
  714. }
  715. }
  716. catch (Exception ex)
  717. {
  718. AppendLog("✗ 启动连续发送失败: " + ex.Message);
  719. m_isContinuousSending = false;
  720. btnContinuousSend.Text = "连续发送";
  721. }
  722. }
  723. /// <summary>
  724. /// 停止连续发送
  725. /// </summary>
  726. private void StopContinuousSend()
  727. {
  728. try
  729. {
  730. m_isContinuousSending = false;
  731. m_continuousSendCts?.Cancel();
  732. // 等待任务结束
  733. Thread.Sleep(100);
  734. btnContinuousSend.Text = "连续发送";
  735. AppendLog("⏹ 连续发送已停止 - 共发送 " + m_continuousSendCounter.ToString() + " 次");
  736. }
  737. catch (Exception ex)
  738. {
  739. AppendLog("✗ 停止连续发送失败: " + ex.Message);
  740. }
  741. }
  742. /// <summary>
  743. /// 连续发送循环(高精度)
  744. /// </summary>
  745. private async Task ContinuousSendLoop(double periodUs, CancellationToken cancellationToken)
  746. {
  747. Stopwatch stopwatch = new Stopwatch();
  748. long sendCount = 0;
  749. // 计算每次循环的目标时间间隔(纳秒)
  750. long periodNs = (long)(periodUs * 1000.0);
  751. AppendLog("[调试] 目标周期: " + periodUs.ToString("F0") + "us = " + periodNs.ToString() + "ns");
  752. while (!cancellationToken.IsCancellationRequested)
  753. {
  754. // 检查是否达到运行时长限制
  755. if (m_continuousSendDurationMs > 0)
  756. {
  757. double elapsedMs = (DateTime.Now - m_continuousSendStartTime).TotalMilliseconds;
  758. if (elapsedMs >= m_continuousSendDurationMs)
  759. {
  760. StopContinuousSend();
  761. return;
  762. }
  763. }
  764. try
  765. {
  766. // 记录开始时间
  767. stopwatch.Restart();
  768. // 构建PDO数据 - 第一个字节自增
  769. byte[] pdoData = new byte[8];
  770. pdoData[0] = (byte)(m_continuousSendCounter % 256); // 第一个字节自增
  771. pdoData[1] = ParseByteValue(txtPdo1Data1.Text, 1);
  772. pdoData[2] = ParseByteValue(txtPdo1Data2.Text, 2);
  773. pdoData[3] = ParseByteValue(txtPdo1Data3.Text, 3);
  774. pdoData[4] = ParseByteValue(txtPdo1Data4.Text, 4);
  775. pdoData[5] = ParseByteValue(txtPdo1Data5.Text, 5);
  776. pdoData[6] = ParseByteValue(txtPdo1Data6.Text, 6);
  777. pdoData[7] = ParseByteValue(txtPdo1Data7.Text, 7);
  778. // 发送PDO(注意:这里需要修改CanOpenSlaveDevice支持自定义数据)
  779. // 暂时使用默认方法,实际应该传入pdoData
  780. m_slaveDevice.SendTpdo(1, pdoData);
  781. sendCount++;
  782. m_continuousSendCounter++; // 计数器自增
  783. // 每100次输出一次日志,避免日志过多
  784. if (sendCount % 100 == 0)
  785. {
  786. AppendLog("→ 连续发送 #" + sendCount.ToString() + ", Byte0=" + m_continuousSendCounter.ToString());
  787. }
  788. // 计算已经消耗的时间
  789. long elapsedNs = stopwatch.ElapsedTicks * 1000000000L / Stopwatch.Frequency;
  790. long remainingNs = periodNs - elapsedNs;
  791. // 如果还有剩余时间,进行精确延迟
  792. if (remainingNs > 0)
  793. {
  794. // 对于较大的延迟,使用Task.Delay
  795. if (remainingNs > 100000) // 大于100微秒
  796. {
  797. int delayMs = (int)(remainingNs / 1000000);
  798. await Task.Delay(delayMs, cancellationToken).ConfigureAwait(false);
  799. }
  800. else
  801. {
  802. // 对于较小的延迟,使用SpinWait
  803. SpinWait.SpinUntil(() => cancellationToken.IsCancellationRequested,
  804. TimeSpan.FromTicks(remainingNs * Stopwatch.Frequency / 1000000000L));
  805. }
  806. }
  807. }
  808. catch (OperationCanceledException)
  809. {
  810. // 正常取消
  811. break;
  812. }
  813. catch (Exception ex)
  814. {
  815. AppendLog("✗ 连续发送错误: " + ex.Message);
  816. break;
  817. }
  818. }
  819. }
  820. private byte ParseByteValue(string text, int index)
  821. {
  822. if (string.IsNullOrWhiteSpace(text))
  823. return 0;
  824. if (byte.TryParse(text.Trim(), out byte value))
  825. return value;
  826. AppendLog("⚠ Byte" + index.ToString() + " 值无效: '" + text + "',使用默认值0");
  827. return 0;
  828. }
  829. /// <summary>
  830. /// 根据规则发送TPDO1(第一字节自增)
  831. /// </summary>
  832. private void SendRuleBasedTpdo1()
  833. {
  834. // 检查是否达到运行时长限制
  835. if (m_ruleSendDurationMs > 0)
  836. {
  837. double elapsedMs = (DateTime.Now - m_ruleSendStartTime).TotalMilliseconds;
  838. if (elapsedMs >= m_ruleSendDurationMs)
  839. {
  840. StopRuleSend();
  841. return;
  842. }
  843. }
  844. try
  845. {
  846. // 构建PDO数据 - 第一个字节自增
  847. byte[] pdoData = new byte[8];
  848. pdoData[0] = (byte)(m_ruleSendCounter % 256); // 第一个字节自增
  849. pdoData[1] = ParseByteValue(txtPdo1Data1.Text, 1);
  850. pdoData[2] = ParseByteValue(txtPdo1Data2.Text, 2);
  851. pdoData[3] = ParseByteValue(txtPdo1Data3.Text, 3);
  852. pdoData[4] = ParseByteValue(txtPdo1Data4.Text, 4);
  853. pdoData[5] = ParseByteValue(txtPdo1Data5.Text, 5);
  854. pdoData[6] = ParseByteValue(txtPdo1Data6.Text, 6);
  855. pdoData[7] = ParseByteValue(txtPdo1Data7.Text, 7);
  856. // 发送PDO
  857. m_slaveDevice.SendTpdo(1, pdoData);
  858. // 计数器自增
  859. m_ruleSendCounter++;
  860. // 输出日志
  861. string dataStr = BitConverter.ToString(pdoData).Replace("-", " ");
  862. AppendLog("→ 规则发送 #" + m_ruleSendCounter.ToString() + ", Byte0=" + ((m_ruleSendCounter - 1) % 256).ToString() + ", Data: [" + dataStr + "]");
  863. }
  864. catch (Exception ex)
  865. {
  866. AppendLog("✗ 规则发送失败: " + ex.Message);
  867. }
  868. }
  869. private void SlaveDevice_OnRpdoReceived(byte nodeId, byte pdoNumber, byte[] data)
  870. {
  871. if (this.InvokeRequired)
  872. {
  873. this.Invoke(new Action(() =>
  874. SlaveDevice_OnRpdoReceived(nodeId, pdoNumber, data)));
  875. return;
  876. }
  877. string dataStr = BitConverter.ToString(data).Replace("-", " ");
  878. AppendLog("← 收到RPDO" + pdoNumber.ToString() + " [节点" + nodeId.ToString() + "]: " + dataStr);
  879. // 如果启用了规则发送,则发送TPDO1
  880. if (m_isRuleSending)
  881. {
  882. SendRuleBasedTpdo1();
  883. }
  884. }
  885. private void SlaveDevice_OnNmtStateChanged(NmtState oldState, NmtState newState)
  886. {
  887. if (this.InvokeRequired)
  888. {
  889. this.Invoke(new Action(() =>
  890. SlaveDevice_OnNmtStateChanged(oldState, newState)));
  891. return;
  892. }
  893. AppendLog("⟳ NMT状态改变: " + oldState.ToString() + " → " + newState.ToString());
  894. UpdateDeviceInfo();
  895. }
  896. private void SlaveDevice_OnSdoReadRequest(byte nodeId, ushort index, byte subIndex)
  897. {
  898. if (this.InvokeRequired)
  899. {
  900. this.Invoke(new Action(() =>
  901. SlaveDevice_OnSdoReadRequest(nodeId, index, subIndex)));
  902. return;
  903. }
  904. AppendLog("? SDO读取请求: 索引0x" + index.ToString("X4") + ", 子索引" + subIndex.ToString());
  905. }
  906. private void SlaveDevice_OnSdoWriteRequest(byte nodeId, ushort index, byte subIndex, uint value)
  907. {
  908. if (this.InvokeRequired)
  909. {
  910. this.Invoke(new Action(() =>
  911. SlaveDevice_OnSdoWriteRequest(nodeId, index, subIndex, value)));
  912. return;
  913. }
  914. AppendLog("↓ SDO写入请求: 索引0x" + index.ToString("X4") + ", 子索引" + subIndex.ToString() + ", 值0x" + value.ToString("X8"));
  915. }
  916. private void UpdateTimer_Tick(object sender, EventArgs e)
  917. {
  918. UpdateDeviceInfo();
  919. }
  920. private void UpdateDeviceInfo()
  921. {
  922. if (this.InvokeRequired)
  923. {
  924. this.Invoke(new Action(UpdateDeviceInfo));
  925. return;
  926. }
  927. lblNodeId.Text = "节点ID: " + m_slaveDevice.NodeId.ToString();
  928. lblState.Text = "状态: " + m_slaveDevice.CurrentState.ToString();
  929. lblStatus.Text = "运行状态: " + (m_slaveDevice.IsRunning ? "运行中" : "已停止");
  930. }
  931. private void AppendLog(string message)
  932. {
  933. if (this.InvokeRequired)
  934. {
  935. this.Invoke(new Action<string>(AppendLog), message);
  936. return;
  937. }
  938. string timestamp = DateTime.Now.ToString("HH:mm:ss.fff");
  939. // 使用字符串拼接而非插值,确保兼容性
  940. txtLog.AppendText("[" + timestamp + "] " + message + "\r\n");
  941. // 自动滚动到底部
  942. txtLog.SelectionStart = txtLog.Text.Length;
  943. txtLog.ScrollToCaret();
  944. }
  945. protected override void OnFormClosing(FormClosingEventArgs e)
  946. {
  947. // 如果正在连续发送,先停止
  948. if (m_isContinuousSending)
  949. {
  950. StopContinuousSend();
  951. }
  952. // 如果正在规则发送,先停止
  953. if (m_isRuleSending)
  954. {
  955. StopRuleSend();
  956. }
  957. if (m_slaveDevice != null)
  958. {
  959. m_slaveDevice.Stop();
  960. m_slaveDevice.Dispose();
  961. }
  962. m_continuousSendCts?.Dispose();
  963. m_updateTimer?.Stop();
  964. m_updateTimer?.Dispose();
  965. base.OnFormClosing(e);
  966. }
  967. /// <summary>
  968. /// 启动规则发送
  969. /// </summary>
  970. private void StartRuleSend()
  971. {
  972. try
  973. {
  974. // 解析运行时长(秒转毫秒),如果输入为空或无效,默认为0(无限制)
  975. int durationSeconds = 0;
  976. if (int.TryParse(txtRuleDuration.Text.Trim(), out int parsedSeconds))
  977. {
  978. durationSeconds = Math.Max(0, parsedSeconds); // 确保非负数
  979. }
  980. m_ruleSendDurationMs = durationSeconds * 1000; // 转换为毫秒
  981. // 重置计数器
  982. m_ruleSendCounter = 0;
  983. m_ruleSendStartTime = DateTime.Now;
  984. m_isRuleSending = true;
  985. m_ruleSendCts = new CancellationTokenSource();
  986. // 更新按钮文本
  987. btnRuleSend.Text = "停止规则发送";
  988. string durationText = durationSeconds > 0 ? durationSeconds.ToString() + "秒" : "无限制";
  989. AppendLog("▶ 开始规则发送 - 时长: " + durationText);
  990. // 如果设置了运行时长,则启动定时器来检查时间
  991. if (m_ruleSendDurationMs > 0)
  992. {
  993. Task.Delay(m_ruleSendDurationMs, m_ruleSendCts.Token)
  994. .ContinueWith(t =>
  995. {
  996. if (!t.IsCanceled)
  997. {
  998. // 时间到了,自动停止规则发送
  999. if (this.InvokeRequired)
  1000. {
  1001. this.Invoke(new Action(StopRuleSend));
  1002. }
  1003. else
  1004. {
  1005. StopRuleSend();
  1006. }
  1007. }
  1008. });
  1009. }
  1010. }
  1011. catch (Exception ex)
  1012. {
  1013. AppendLog("✗ 启动规则发送失败: " + ex.Message);
  1014. m_isRuleSending = false;
  1015. btnRuleSend.Text = "规则发送";
  1016. }
  1017. }
  1018. /// <summary>
  1019. /// 停止规则发送
  1020. /// </summary>
  1021. private void StopRuleSend()
  1022. {
  1023. try
  1024. {
  1025. m_isRuleSending = false;
  1026. m_ruleSendCts?.Cancel();
  1027. btnRuleSend.Text = "规则发送";
  1028. AppendLog("⏹ 规则发送已停止 - 共发送 " + m_ruleSendCounter.ToString() + " 次");
  1029. }
  1030. catch (Exception ex)
  1031. {
  1032. AppendLog("✗ 停止规则发送失败: " + ex.Message);
  1033. }
  1034. }
  1035. }
  1036. }