SlaveTestForm.cs 47 KB

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