SlaveTestForm.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391
  1. using System;
  2. using System.Windows.Forms;
  3. using System.Drawing;
  4. using CCDCount.DLL.CanBus;
  5. namespace CanOpenSlaveTest
  6. {
  7. /// <summary>
  8. /// CANopen从站设备测试界面
  9. /// </summary>
  10. public class SlaveTestForm : Form
  11. {
  12. private CanOpenSlaveDevice m_slaveDevice;
  13. // UI控件
  14. private GroupBox grpDeviceInfo;
  15. private Label lblNodeId;
  16. private Label lblState;
  17. private Label lblStatus;
  18. private GroupBox grpControl;
  19. private Button btnStart;
  20. private Button btnStop;
  21. private Button btnSendTpdo1;
  22. private Button btnSendTpdo2;
  23. private Button btnSendAllTpdos;
  24. private GroupBox grpHeartbeat;
  25. private NumericUpDown nudHeartbeatTime;
  26. private Button btnConfigureHeartbeat;
  27. private TextBox txtLog;
  28. private System.Windows.Forms.Timer m_updateTimer;
  29. public SlaveTestForm()
  30. {
  31. InitializeComponent();
  32. // 在构造函数中创建从站设备(避免设计器错误)
  33. if (!DesignMode)
  34. {
  35. m_slaveDevice = new CanOpenSlaveDevice(nodeId: 1);
  36. // 注册事件
  37. m_slaveDevice.OnRpdoReceived += SlaveDevice_OnRpdoReceived;
  38. m_slaveDevice.OnNmtStateChanged += SlaveDevice_OnNmtStateChanged;
  39. m_slaveDevice.OnSdoReadRequest += SlaveDevice_OnSdoReadRequest;
  40. m_slaveDevice.OnSdoWriteRequest += SlaveDevice_OnSdoWriteRequest;
  41. // 初始化日志
  42. AppendLog("CANopen从站测试程序已启动");
  43. AppendLog("节点ID: " + m_slaveDevice.NodeId.ToString());
  44. AppendLog("点击'启动从站'按钮开始测试\n");
  45. }
  46. }
  47. private void InitializeComponent()
  48. {
  49. this.Text = "CANopen从站设备测试";
  50. this.Size = new Size(800, 600);
  51. this.StartPosition = FormStartPosition.CenterScreen;
  52. // 设备信息组
  53. grpDeviceInfo = new GroupBox();
  54. grpDeviceInfo.Text = "设备信息";
  55. grpDeviceInfo.Location = new Point(10, 10);
  56. grpDeviceInfo.Size = new Size(380, 100);
  57. lblNodeId = new Label();
  58. lblNodeId.Text = "节点ID: -";
  59. lblNodeId.Location = new Point(10, 25);
  60. lblNodeId.AutoSize = true;
  61. lblState = new Label();
  62. lblState.Text = "状态: -";
  63. lblState.Location = new Point(10, 50);
  64. lblState.AutoSize = true;
  65. lblStatus = new Label();
  66. lblStatus.Text = "运行状态: 未启动";
  67. lblStatus.Location = new Point(10, 75);
  68. lblStatus.AutoSize = true;
  69. grpDeviceInfo.Controls.AddRange(new Control[] { lblNodeId, lblState, lblStatus });
  70. // 控制组
  71. grpControl = new GroupBox();
  72. grpControl.Text = "控制";
  73. grpControl.Location = new Point(400, 10);
  74. grpControl.Size = new Size(380, 100);
  75. btnStart = new Button();
  76. btnStart.Text = "启动从站";
  77. btnStart.Location = new Point(10, 25);
  78. btnStart.Size = new Size(100, 30);
  79. btnStart.Click += BtnStart_Click;
  80. btnStop = new Button();
  81. btnStop.Text = "停止从站";
  82. btnStop.Location = new Point(120, 25);
  83. btnStop.Size = new Size(100, 30);
  84. btnStop.Click += BtnStop_Click;
  85. btnStop.Enabled = false;
  86. btnSendTpdo1 = new Button();
  87. btnSendTpdo1.Text = "发送TPDO1";
  88. btnSendTpdo1.Location = new Point(10, 60);
  89. btnSendTpdo1.Size = new Size(100, 30);
  90. btnSendTpdo1.Click += BtnSendTpdo1_Click;
  91. btnSendTpdo1.Enabled = false;
  92. btnSendTpdo2 = new Button();
  93. btnSendTpdo2.Text = "发送TPDO2";
  94. btnSendTpdo2.Location = new Point(120, 60);
  95. btnSendTpdo2.Size = new Size(100, 30);
  96. btnSendTpdo2.Click += BtnSendTpdo2_Click;
  97. btnSendTpdo2.Enabled = false;
  98. btnSendAllTpdos = new Button();
  99. btnSendAllTpdos.Text = "发送所有TPDO";
  100. btnSendAllTpdos.Location = new Point(230, 60);
  101. btnSendAllTpdos.Size = new Size(120, 30);
  102. btnSendAllTpdos.Click += BtnSendAllTpdos_Click;
  103. btnSendAllTpdos.Enabled = false;
  104. grpControl.Controls.AddRange(new Control[] {
  105. btnStart, btnStop, btnSendTpdo1, btnSendTpdo2, btnSendAllTpdos
  106. });
  107. // 心跳配置组
  108. grpHeartbeat = new GroupBox();
  109. grpHeartbeat.Text = "心跳配置";
  110. grpHeartbeat.Location = new Point(10, 120);
  111. grpHeartbeat.Size = new Size(770, 60);
  112. Label lblHeartbeat = new Label();
  113. lblHeartbeat.Text = "心跳时间(ms):";
  114. lblHeartbeat.Location = new Point(10, 25);
  115. lblHeartbeat.AutoSize = true;
  116. nudHeartbeatTime = new NumericUpDown();
  117. nudHeartbeatTime.Location = new Point(110, 22);
  118. nudHeartbeatTime.Size = new Size(100, 25);
  119. nudHeartbeatTime.Minimum = 0;
  120. nudHeartbeatTime.Maximum = 10000;
  121. nudHeartbeatTime.Value = 100;
  122. btnConfigureHeartbeat = new Button();
  123. btnConfigureHeartbeat.Text = "配置心跳";
  124. btnConfigureHeartbeat.Location = new Point(220, 20);
  125. btnConfigureHeartbeat.Size = new Size(100, 30);
  126. btnConfigureHeartbeat.Click += BtnConfigureHeartbeat_Click;
  127. btnConfigureHeartbeat.Enabled = false;
  128. grpHeartbeat.Controls.AddRange(new Control[] { lblHeartbeat, nudHeartbeatTime, btnConfigureHeartbeat });
  129. // 日志显示
  130. txtLog = new TextBox();
  131. txtLog.Multiline = true;
  132. txtLog.ScrollBars = ScrollBars.Vertical;
  133. txtLog.ReadOnly = true;
  134. txtLog.Location = new Point(10, 190);
  135. txtLog.Size = new Size(770, 360);
  136. // 使用支持中文的字体(微软雅黑或宋体)
  137. txtLog.Font = new Font("Microsoft YaHei", 9);
  138. txtLog.ForeColor = Color.Black;
  139. txtLog.BackColor = Color.White;
  140. // 添加控件到窗体
  141. this.Controls.AddRange(new Control[] {
  142. grpDeviceInfo, grpControl, grpHeartbeat, txtLog
  143. });
  144. // 更新定时器
  145. m_updateTimer = new System.Windows.Forms.Timer();
  146. m_updateTimer.Interval = 500;
  147. m_updateTimer.Tick += UpdateTimer_Tick;
  148. m_updateTimer.Start();
  149. }
  150. private void BtnStart_Click(object sender, EventArgs e)
  151. {
  152. try
  153. {
  154. AppendLog("正在启动从站...");
  155. if (m_slaveDevice.Start(CanBaudRate.BaudRate_1M))
  156. {
  157. AppendLog("✓ 从站启动成功");
  158. btnStart.Enabled = false;
  159. btnStop.Enabled = true;
  160. btnSendTpdo1.Enabled = true;
  161. btnSendTpdo2.Enabled = true;
  162. btnSendAllTpdos.Enabled = true;
  163. btnConfigureHeartbeat.Enabled = true;
  164. UpdateDeviceInfo();
  165. }
  166. else
  167. {
  168. AppendLog("✗ 从站启动失败");
  169. MessageBox.Show("启动失败!请检查CAN设备连接。", "错误",
  170. MessageBoxButtons.OK, MessageBoxIcon.Error);
  171. }
  172. }
  173. catch (Exception ex)
  174. {
  175. AppendLog($"✗ 启动错误: {ex.Message}");
  176. MessageBox.Show(ex.Message, "错误",
  177. MessageBoxButtons.OK, MessageBoxIcon.Error);
  178. }
  179. }
  180. private void BtnStop_Click(object sender, EventArgs e)
  181. {
  182. try
  183. {
  184. AppendLog("正在停止从站...");
  185. m_slaveDevice.Stop();
  186. AppendLog("✓ 从站已停止");
  187. btnStart.Enabled = true;
  188. btnStop.Enabled = false;
  189. btnSendTpdo1.Enabled = false;
  190. btnSendTpdo2.Enabled = false;
  191. btnSendAllTpdos.Enabled = false;
  192. btnConfigureHeartbeat.Enabled = false;
  193. UpdateDeviceInfo();
  194. }
  195. catch (Exception ex)
  196. {
  197. AppendLog($"✗ 停止错误: {ex.Message}");
  198. }
  199. }
  200. private void BtnSendTpdo1_Click(object sender, EventArgs e)
  201. {
  202. try
  203. {
  204. m_slaveDevice.SendTpdo(1);
  205. AppendLog("→ 已发送TPDO1");
  206. }
  207. catch (Exception ex)
  208. {
  209. AppendLog($"✗ 发送TPDO1失败: {ex.Message}");
  210. }
  211. }
  212. private void BtnSendTpdo2_Click(object sender, EventArgs e)
  213. {
  214. try
  215. {
  216. m_slaveDevice.SendTpdo(2);
  217. AppendLog("→ 已发送TPDO2");
  218. }
  219. catch (Exception ex)
  220. {
  221. AppendLog($"✗ 发送TPDO2失败: {ex.Message}");
  222. }
  223. }
  224. private void BtnSendAllTpdos_Click(object sender, EventArgs e)
  225. {
  226. try
  227. {
  228. m_slaveDevice.SendAllTpdos();
  229. AppendLog("→ 已发送所有TPDO");
  230. }
  231. catch (Exception ex)
  232. {
  233. AppendLog($"✗ 发送TPDO失败: {ex.Message}");
  234. }
  235. }
  236. private void BtnConfigureHeartbeat_Click(object sender, EventArgs e)
  237. {
  238. try
  239. {
  240. ushort heartbeatTime = (ushort)nudHeartbeatTime.Value;
  241. m_slaveDevice.ConfigureHeartbeat(heartbeatTime);
  242. AppendLog($"✓ 心跳已配置: {heartbeatTime}ms");
  243. }
  244. catch (Exception ex)
  245. {
  246. AppendLog($"✗ 配置心跳失败: {ex.Message}");
  247. }
  248. }
  249. private void SlaveDevice_OnRpdoReceived(byte nodeId, byte pdoNumber, byte[] data)
  250. {
  251. if (this.InvokeRequired)
  252. {
  253. this.Invoke(new Action(() =>
  254. SlaveDevice_OnRpdoReceived(nodeId, pdoNumber, data)));
  255. return;
  256. }
  257. string dataStr = BitConverter.ToString(data).Replace("-", " ");
  258. AppendLog($"← 收到RPDO{pdoNumber} [节点{nodeId}]: {dataStr}");
  259. }
  260. private void SlaveDevice_OnNmtStateChanged(NmtState oldState, NmtState newState)
  261. {
  262. if (this.InvokeRequired)
  263. {
  264. this.Invoke(new Action(() =>
  265. SlaveDevice_OnNmtStateChanged(oldState, newState)));
  266. return;
  267. }
  268. AppendLog($"⟳ NMT状态改变: {oldState} → {newState}");
  269. UpdateDeviceInfo();
  270. }
  271. private void SlaveDevice_OnSdoReadRequest(byte nodeId, ushort index, byte subIndex)
  272. {
  273. if (this.InvokeRequired)
  274. {
  275. this.Invoke(new Action(() =>
  276. SlaveDevice_OnSdoReadRequest(nodeId, index, subIndex)));
  277. return;
  278. }
  279. AppendLog($"? SDO读取请求: 索引0x{index:X4}, 子索引{subIndex}");
  280. }
  281. private void SlaveDevice_OnSdoWriteRequest(byte nodeId, ushort index, byte subIndex, uint value)
  282. {
  283. if (this.InvokeRequired)
  284. {
  285. this.Invoke(new Action(() =>
  286. SlaveDevice_OnSdoWriteRequest(nodeId, index, subIndex, value)));
  287. return;
  288. }
  289. AppendLog($"↓ SDO写入请求: 索引0x{index:X4}, 子索引{subIndex}, 值0x{value:X8}");
  290. }
  291. private void UpdateTimer_Tick(object sender, EventArgs e)
  292. {
  293. UpdateDeviceInfo();
  294. }
  295. private void UpdateDeviceInfo()
  296. {
  297. if (this.InvokeRequired)
  298. {
  299. this.Invoke(new Action(UpdateDeviceInfo));
  300. return;
  301. }
  302. lblNodeId.Text = $"节点ID: {m_slaveDevice.NodeId}";
  303. lblState.Text = $"状态: {m_slaveDevice.CurrentState}";
  304. lblStatus.Text = $"运行状态: {(m_slaveDevice.IsRunning ? "运行中" : "已停止")}";
  305. }
  306. private void AppendLog(string message)
  307. {
  308. if (this.InvokeRequired)
  309. {
  310. this.Invoke(new Action<string>(AppendLog), message);
  311. return;
  312. }
  313. string timestamp = DateTime.Now.ToString("HH:mm:ss.fff");
  314. // 使用字符串拼接而非插值,确保兼容性
  315. txtLog.AppendText("[" + timestamp + "] " + message + "\r\n");
  316. // 自动滚动到底部
  317. txtLog.SelectionStart = txtLog.Text.Length;
  318. txtLog.ScrollToCaret();
  319. }
  320. protected override void OnFormClosing(FormClosingEventArgs e)
  321. {
  322. if (m_slaveDevice != null)
  323. {
  324. m_slaveDevice.Stop();
  325. m_slaveDevice.Dispose();
  326. }
  327. m_updateTimer?.Stop();
  328. m_updateTimer?.Dispose();
  329. base.OnFormClosing(e);
  330. }
  331. }
  332. }