MotionDebugView.xaml.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655
  1. using System;
  2. using System.Globalization;
  3. using System.Windows;
  4. using System.Windows.Controls;
  5. using System.Windows.Controls.Primitives;
  6. using System.Windows.Input;
  7. using System.Windows.Media;
  8. using TeamAAS.Global.Devices;
  9. using TeamAAS.Motion.Models;
  10. using TeamAAS.Motion.Motions;
  11. namespace TeamAAS.Motion
  12. {
  13. /// <summary>
  14. /// 运动卡通用调试页:轴选择 + 使能/回零/点动/定位/急停 + 本地/EtherCAT IO。
  15. /// </summary>
  16. public partial class MotionDebugView : UserControl
  17. {
  18. private IMotionCard _motion;
  19. private IMotionSafetyHost _safety;
  20. private IMotionIoHost _io;
  21. private IAxis _selectedAxis;
  22. private bool _jogActive;
  23. private bool _updatingIo;
  24. private int _ioLayoutKey = int.MinValue;
  25. private readonly System.Windows.Threading.DispatcherTimer _refreshTimer;
  26. public MotionDebugView()
  27. {
  28. InitializeComponent();
  29. _refreshTimer = new System.Windows.Threading.DispatcherTimer
  30. {
  31. Interval = TimeSpan.FromMilliseconds(200)
  32. };
  33. _refreshTimer.Tick += (_, __) => RefreshStatus();
  34. Unloaded += (_, __) =>
  35. {
  36. _refreshTimer.Stop();
  37. UnhookSafety();
  38. };
  39. }
  40. public void Bind(object motion)
  41. {
  42. UnhookSafety();
  43. _motion = motion as IMotionCard;
  44. _safety = _motion as IMotionSafetyHost;
  45. _io = _motion as IMotionIoHost;
  46. _selectedAxis = null;
  47. _jogActive = false;
  48. _ioLayoutKey = int.MinValue;
  49. CmbAxis.ItemsSource = null;
  50. if (_motion == null)
  51. {
  52. OpsRoot.IsEnabled = false;
  53. IoRoot.IsEnabled = false;
  54. _refreshTimer.Stop();
  55. TxtStatus.Text = "---";
  56. TxtMessage.Text = "未绑定运动卡";
  57. ClearAxisMetrics();
  58. SetConnDot(false);
  59. ApplySafetyBanner();
  60. RebuildIoPanels();
  61. return;
  62. }
  63. if (_safety != null)
  64. _safety.SafetyChanged += OnSafetyChanged;
  65. var axes = _motion.GetAxes();
  66. CmbAxis.ItemsSource = axes;
  67. if (axes != null && axes.Count > 0)
  68. CmbAxis.SelectedIndex = 0;
  69. OpsRoot.IsEnabled = _motion.IsConnected;
  70. IoRoot.IsEnabled = _motion.IsConnected;
  71. _refreshTimer.Start();
  72. RefreshStatus();
  73. }
  74. private void UnhookSafety()
  75. {
  76. if (_safety != null)
  77. _safety.SafetyChanged -= OnSafetyChanged;
  78. _safety = null;
  79. }
  80. private void OnSafetyChanged(object sender, EventArgs e)
  81. {
  82. Dispatcher.BeginInvoke(new Action(RefreshStatus));
  83. }
  84. private IAxis CurrentAxis
  85. {
  86. get
  87. {
  88. if (_selectedAxis != null) return _selectedAxis;
  89. return CmbAxis.SelectedItem as IAxis;
  90. }
  91. }
  92. private void CmbAxis_SelectionChanged(object sender, SelectionChangedEventArgs e)
  93. {
  94. _selectedAxis = CmbAxis.SelectedItem as IAxis;
  95. RefreshAxisInfo();
  96. }
  97. private double ParseDouble(TextBox box, double fallback)
  98. {
  99. return double.TryParse(box.Text, NumberStyles.Any, CultureInfo.InvariantCulture, out var v)
  100. ? v
  101. : fallback;
  102. }
  103. private void Msg(string message) => TxtMessage.Text = message;
  104. private bool TryGetReadyAxis(out IAxis axis, string action)
  105. {
  106. axis = CurrentAxis;
  107. if (axis == null)
  108. {
  109. Msg("未选择轴");
  110. return false;
  111. }
  112. if (_motion == null || !_motion.IsConnected)
  113. {
  114. Msg("运动卡未连接");
  115. return false;
  116. }
  117. if (!axis.IsEnabled)
  118. {
  119. Msg($"轴未使能,无法{action}");
  120. return false;
  121. }
  122. if (_safety != null && !_safety.AllowMotion(out var reason))
  123. {
  124. Msg(reason);
  125. return false;
  126. }
  127. return true;
  128. }
  129. private void RefreshStatus()
  130. {
  131. if (_motion == null)
  132. {
  133. TxtStatus.Text = "---";
  134. SetConnDot(false);
  135. ClearAxisMetrics();
  136. return;
  137. }
  138. try
  139. {
  140. var connected = _motion.IsConnected;
  141. TxtStatus.Text = connected ? "已连接" : "未连接";
  142. SetConnDot(connected);
  143. OpsRoot.IsEnabled = connected;
  144. IoRoot.IsEnabled = connected && _io != null;
  145. RefreshAxisInfo();
  146. RebuildIoPanels();
  147. RefreshIoBits();
  148. }
  149. catch { }
  150. }
  151. private void RefreshAxisInfo()
  152. {
  153. var axis = CurrentAxis;
  154. if (axis == null)
  155. {
  156. ClearAxisMetrics();
  157. return;
  158. }
  159. TxtAxisTitle.Text = $"{axis.Name} · 轴 {axis.AxisNo}";
  160. TxtPosActual.Text = axis.ActualPosition.ToString("F3");
  161. TxtPosCommand.Text = axis.CommandPosition.ToString("F3");
  162. TxtVelocity.Text = axis.ActualVelocity.ToString("F3");
  163. ApplyBanner(axis);
  164. ApplySafetyBanner();
  165. SetTile(TileEnable, TxtEnable, axis.IsEnabled, "已使能", "未使能", "#2E7D32", "#C62828");
  166. SetTile(TileMoving, TxtMoving, axis.IsMoving, "运动中", "静止", "#EF6C00", "#757575");
  167. SetTile(TileHomed, TxtHomed, axis.IsHomed, "已回零", "未回零", "#1565C0", "#757575");
  168. var enabled = axis.IsEnabled;
  169. bool estop = _safety != null && _safety.IsEstopActive;
  170. bool stop = _safety != null && _safety.IsStopActive;
  171. BtnEnable.IsEnabled = !enabled && !estop;
  172. BtnDisable.IsEnabled = enabled;
  173. BtnStop.IsEnabled = axis.IsMoving;
  174. MotionOps.IsEnabled = enabled && !estop && !stop;
  175. if (estop)
  176. {
  177. TxtNeedEnable.Text = "急停有效:禁止使能与运动";
  178. TxtNeedEnable.Visibility = Visibility.Visible;
  179. }
  180. else if (stop)
  181. {
  182. TxtNeedEnable.Text = "停止信号有效:禁止运动";
  183. TxtNeedEnable.Visibility = Visibility.Visible;
  184. }
  185. else
  186. {
  187. TxtNeedEnable.Text = "当前未使能,请先点击「使能」后再点动或定位";
  188. TxtNeedEnable.Visibility = enabled ? Visibility.Collapsed : Visibility.Visible;
  189. }
  190. }
  191. private void ApplySafetyBanner()
  192. {
  193. if (_safety == null || _motion == null || !_motion.IsConnected)
  194. {
  195. BannerSafety.Visibility = Visibility.Collapsed;
  196. return;
  197. }
  198. if (_safety.IsEstopActive)
  199. {
  200. BannerSafety.Visibility = Visibility.Visible;
  201. BannerSafety.Background = BrushOf("#B71C1C");
  202. TxtSafetyHeadline.Text = "急停有效";
  203. TxtSafetyHint.Text = "所有轴已下使能。手动调试与流程自动使能/运动均无法成功,请先复位急停按钮。";
  204. return;
  205. }
  206. if (_safety.IsStopActive)
  207. {
  208. BannerSafety.Visibility = Visibility.Visible;
  209. BannerSafety.Background = BrushOf("#E65100");
  210. TxtSafetyHeadline.Text = "停止信号有效";
  211. TxtSafetyHint.Text = "所有轴禁止运动。手动调试与流程自动定位/点动/回零均无法成功,请先松开停止按钮。";
  212. return;
  213. }
  214. BannerSafety.Visibility = Visibility.Collapsed;
  215. }
  216. private void ApplyBanner(IAxis axis)
  217. {
  218. string headline;
  219. string hint;
  220. string color;
  221. if (_motion == null || !_motion.IsConnected)
  222. {
  223. headline = "未连接";
  224. hint = "请先在上方连接控制卡";
  225. color = "#616161";
  226. }
  227. else if (_safety != null && _safety.IsEstopActive)
  228. {
  229. headline = "急停有效";
  230. hint = "禁止使能与运动,请先复位急停";
  231. color = "#B71C1C";
  232. }
  233. else if (_safety != null && _safety.IsStopActive)
  234. {
  235. headline = "停止信号有效";
  236. hint = "禁止运动,请先松开停止按钮";
  237. color = "#E65100";
  238. }
  239. else if (axis.State == AxisState.Error)
  240. {
  241. headline = "轴故障";
  242. hint = "请检查报警后重新使能";
  243. color = "#B71C1C";
  244. }
  245. else if (!axis.IsEnabled || axis.State == AxisState.Off)
  246. {
  247. headline = "伺服未使能";
  248. hint = "当前无法运动,请先点击右侧「使能」";
  249. color = "#C62828";
  250. }
  251. else if (axis.State == AxisState.Homing)
  252. {
  253. headline = "正在回零";
  254. hint = "回零过程中请勿点动或定位";
  255. color = "#1565C0";
  256. }
  257. else if (axis.State == AxisState.ContinuousMotion || (axis.IsMoving && _jogActive))
  258. {
  259. headline = "点动中";
  260. hint = "松开按钮或点击「停止」即可停下";
  261. color = "#EF6C00";
  262. }
  263. else if (axis.State == AxisState.DiscreteMotion || axis.IsMoving)
  264. {
  265. headline = "定位运动中";
  266. hint = "可点击「停止」或「急停」中断";
  267. color = "#EF6C00";
  268. }
  269. else
  270. {
  271. headline = axis.IsHomed ? "已使能 · 静止" : "已使能 · 未回零";
  272. hint = axis.IsHomed ? "可以点动或定位" : "建议先回零,再做定位";
  273. color = "#2E7D32";
  274. }
  275. TxtStateHeadline.Text = headline;
  276. TxtStateHint.Text = hint;
  277. BannerState.Background = BrushOf(color);
  278. }
  279. private void ClearAxisMetrics()
  280. {
  281. TxtAxisTitle.Text = "---";
  282. TxtPosActual.Text = "---";
  283. TxtPosCommand.Text = "---";
  284. TxtVelocity.Text = "---";
  285. TxtStateHeadline.Text = _motion == null ? "未绑定运动卡" : (_motion.IsConnected ? "未选择轴" : "未连接");
  286. TxtStateHint.Text = "请选择轴并连接控制卡";
  287. BannerState.Background = BrushOf("#616161");
  288. SetTile(TileEnable, TxtEnable, false, "已使能", "未使能", "#2E7D32", "#757575");
  289. SetTile(TileMoving, TxtMoving, false, "运动中", "静止", "#EF6C00", "#757575");
  290. SetTile(TileHomed, TxtHomed, false, "已回零", "未回零", "#1565C0", "#757575");
  291. BtnEnable.IsEnabled = false;
  292. BtnDisable.IsEnabled = false;
  293. BtnStop.IsEnabled = false;
  294. MotionOps.IsEnabled = false;
  295. TxtNeedEnable.Visibility = Visibility.Collapsed;
  296. ApplySafetyBanner();
  297. }
  298. private static void SetTile(Border tile, TextBlock label, bool on, string onText, string offText, string onColor, string offColor)
  299. {
  300. label.Text = on ? onText : offText;
  301. tile.Background = BrushOf(on ? onColor : offColor);
  302. }
  303. private static SolidColorBrush BrushOf(string hex)
  304. => new SolidColorBrush((Color)ColorConverter.ConvertFromString(hex));
  305. private void SetConnDot(bool connected)
  306. {
  307. DotConn.Fill = BrushOf(connected ? "#43A047" : "#9E9E9E");
  308. }
  309. private void BtnEnable_Click(object sender, RoutedEventArgs e)
  310. {
  311. var axis = CurrentAxis;
  312. if (axis == null) { Msg("未选择轴"); return; }
  313. if (_safety != null && !_safety.AllowEnable(out var reason))
  314. {
  315. Msg(reason);
  316. return;
  317. }
  318. try
  319. {
  320. axis.Enable();
  321. Msg(axis.IsEnabled ? "已使能" : "使能失败");
  322. RefreshAxisInfo();
  323. }
  324. catch (Exception ex) { Msg("使能失败: " + ex.Message); }
  325. }
  326. private void BtnDisable_Click(object sender, RoutedEventArgs e)
  327. {
  328. var axis = CurrentAxis;
  329. if (axis == null) { Msg("未选择轴"); return; }
  330. try
  331. {
  332. StopJogIfActive();
  333. axis.Disable();
  334. Msg("已下使能");
  335. RefreshAxisInfo();
  336. }
  337. catch (Exception ex) { Msg("下使能失败: " + ex.Message); }
  338. }
  339. private void BtnHome_Click(object sender, RoutedEventArgs e)
  340. {
  341. if (!TryGetReadyAxis(out var axis, "回零")) return;
  342. try
  343. {
  344. StopJogIfActive();
  345. axis.Home();
  346. Msg("回零完成");
  347. RefreshAxisInfo();
  348. }
  349. catch (Exception ex) { Msg("回零失败: " + ex.Message); }
  350. }
  351. private void BtnStop_Click(object sender, RoutedEventArgs e)
  352. {
  353. try
  354. {
  355. StopJogIfActive();
  356. CurrentAxis?.Stop();
  357. Msg("已停止");
  358. RefreshAxisInfo();
  359. }
  360. catch (Exception ex) { Msg("停止失败: " + ex.Message); }
  361. }
  362. private void BtnEStop_Click(object sender, RoutedEventArgs e)
  363. {
  364. try
  365. {
  366. _jogActive = false;
  367. _motion?.EmergencyStop();
  368. Msg("急停已触发");
  369. RefreshAxisInfo();
  370. }
  371. catch (Exception ex) { Msg("急停失败: " + ex.Message); }
  372. }
  373. private void BtnJog_Down(object sender, MouseButtonEventArgs e)
  374. {
  375. if (!TryGetReadyAxis(out var axis, "点动")) return;
  376. var btn = sender as UIElement;
  377. var tag = (sender as Button)?.Tag as string;
  378. double vel = ParseDouble(TxtJogVel, 50);
  379. if (tag == "-") vel = -vel;
  380. try
  381. {
  382. axis.Jog(vel);
  383. _jogActive = true;
  384. btn?.CaptureMouse();
  385. Msg($"点动 {tag} 速度 {vel:F1}");
  386. }
  387. catch (Exception ex) { Msg("点动失败: " + ex.Message); }
  388. }
  389. private void BtnJog_Up(object sender, MouseButtonEventArgs e)
  390. {
  391. var btn = sender as UIElement;
  392. if (btn != null && btn.IsMouseCaptured)
  393. btn.ReleaseMouseCapture();
  394. else
  395. StopJogIfActive();
  396. }
  397. private void BtnJog_LostCapture(object sender, MouseEventArgs e)
  398. {
  399. StopJogIfActive();
  400. }
  401. private void StopJogIfActive()
  402. {
  403. if (!_jogActive) return;
  404. _jogActive = false;
  405. try
  406. {
  407. CurrentAxis?.Stop();
  408. Msg("点动停止");
  409. RefreshAxisInfo();
  410. }
  411. catch (Exception ex) { Msg("停止失败: " + ex.Message); }
  412. }
  413. private void BtnMoveAbs_Click(object sender, RoutedEventArgs e)
  414. {
  415. if (!TryGetReadyAxis(out var axis, "定位")) return;
  416. try
  417. {
  418. var target = ParseDouble(TxtTargetPos, 0);
  419. axis.Velocity = ParseDouble(TxtMoveVel, 100);
  420. axis.MoveTo(target);
  421. Msg($"绝对定位 → {target:F3}");
  422. }
  423. catch (Exception ex) { Msg("绝对定位失败: " + ex.Message); }
  424. }
  425. private void BtnMoveRel_Click(object sender, RoutedEventArgs e)
  426. {
  427. if (!TryGetReadyAxis(out var axis, "定位")) return;
  428. try
  429. {
  430. var dist = ParseDouble(TxtRelativeDist, 10);
  431. axis.Velocity = ParseDouble(TxtMoveVel, 100);
  432. axis.MoveBy(dist);
  433. Msg($"相对定位 {dist:F3}");
  434. }
  435. catch (Exception ex) { Msg("相对定位失败: " + ex.Message); }
  436. }
  437. private void BtnSetZero_Click(object sender, RoutedEventArgs e)
  438. {
  439. var axis = CurrentAxis;
  440. if (axis == null) { Msg("未选择轴"); return; }
  441. try
  442. {
  443. axis.SetPosition(0);
  444. Msg("当前位置已清零");
  445. RefreshAxisInfo();
  446. }
  447. catch (Exception ex) { Msg("清零失败: " + ex.Message); }
  448. }
  449. private MotionDeviceConfig CurrentIoConfig
  450. {
  451. get
  452. {
  453. if (_motion is SimulatedMotionCard sim) return sim.DeviceConfig;
  454. if (_motion is InovanceMotionCard imc) return imc.DeviceConfig;
  455. return null;
  456. }
  457. }
  458. private void RebuildIoPanels()
  459. {
  460. int diL = _io == null ? 0 : _io.GetDiCount(MotionIoBank.Local);
  461. int doL = _io == null ? 0 : _io.GetDoCount(MotionIoBank.Local);
  462. int diE = _io == null ? 0 : _io.GetDiCount(MotionIoBank.EtherCat);
  463. int doE = _io == null ? 0 : _io.GetDoCount(MotionIoBank.EtherCat);
  464. int key = diL | (doL << 8) | (diE << 16) | (doE << 24);
  465. if (_io != null && _io.CanSimulateDi) key ^= 0x40000000;
  466. if (key == _ioLayoutKey) return;
  467. _ioLayoutKey = key;
  468. FillIoPanel(PanelLocalDi, MotionIoBank.Local, false, diL);
  469. FillIoPanel(PanelLocalDo, MotionIoBank.Local, true, doL);
  470. FillIoPanel(PanelEcatDi, MotionIoBank.EtherCat, false, diE);
  471. FillIoPanel(PanelEcatDo, MotionIoBank.EtherCat, true, doE);
  472. }
  473. private void FillIoPanel(WrapPanel panel, MotionIoBank bank, bool isDo, int count)
  474. {
  475. panel.Children.Clear();
  476. if (count <= 0)
  477. {
  478. panel.Children.Add(new TextBlock
  479. {
  480. Text = "无",
  481. FontSize = 12,
  482. Foreground = BrushOf("#9E9E9E"),
  483. Margin = new Thickness(0, 4, 0, 0)
  484. });
  485. return;
  486. }
  487. string prefix = isDo ? "DO" : "DI";
  488. bool canWrite = isDo || (_io != null && _io.CanSimulateDi);
  489. for (int i = 0; i < count; i++)
  490. {
  491. var btn = new ToggleButton
  492. {
  493. Content = prefix + i,
  494. Width = 52,
  495. Height = 34,
  496. Margin = new Thickness(0, 0, 6, 6),
  497. FontSize = 11,
  498. Tag = new IoBitTag { Bank = bank, IsOutput = isDo, Index = i, CanWrite = canWrite },
  499. Cursor = canWrite ? Cursors.Hand : Cursors.Arrow
  500. };
  501. btn.Style = (Style)FindResource("IoBitButton");
  502. btn.Click += IoBit_Click;
  503. panel.Children.Add(btn);
  504. }
  505. }
  506. private void RefreshIoBits()
  507. {
  508. if (_io == null) return;
  509. _updatingIo = true;
  510. try
  511. {
  512. RefreshIoPanel(PanelLocalDi, MotionIoBank.Local, false);
  513. RefreshIoPanel(PanelLocalDo, MotionIoBank.Local, true);
  514. RefreshIoPanel(PanelEcatDi, MotionIoBank.EtherCat, false);
  515. RefreshIoPanel(PanelEcatDo, MotionIoBank.EtherCat, true);
  516. }
  517. finally { _updatingIo = false; }
  518. }
  519. private void RefreshIoPanel(WrapPanel panel, MotionIoBank bank, bool isDo)
  520. {
  521. foreach (var child in panel.Children)
  522. {
  523. var btn = child as ToggleButton;
  524. if (btn == null) continue;
  525. var tag = btn.Tag as IoBitTag;
  526. if (tag == null) continue;
  527. bool on = isDo ? _io.ReadDO(bank, tag.Index) : _io.ReadDI(bank, tag.Index);
  528. btn.IsChecked = on;
  529. string role = SafetyRole(bank, tag.Index, isDo);
  530. if (role == "estop")
  531. btn.Background = BrushOf(on ? "#B71C1C" : "#7F1D1D");
  532. else if (role == "stop")
  533. btn.Background = BrushOf(on ? "#E65100" : "#BF360C");
  534. else
  535. btn.Background = BrushOf(on ? "#2E7D32" : "#757575");
  536. btn.Foreground = Brushes.White;
  537. btn.ToolTip = role == "estop" ? "急停 DI" : role == "stop" ? "停止 DI" : (isDo ? "输出" : "输入");
  538. }
  539. }
  540. private string SafetyRole(MotionIoBank bank, int index, bool isDo)
  541. {
  542. if (isDo) return null;
  543. var cfg = CurrentIoConfig;
  544. if (cfg == null) return null;
  545. if (cfg.EstopDiIndex == index && cfg.EstopDiBank == bank) return "estop";
  546. if (cfg.StopDiIndex == index && cfg.StopDiBank == bank) return "stop";
  547. return null;
  548. }
  549. private void IoBit_Click(object sender, RoutedEventArgs e)
  550. {
  551. if (_updatingIo) return;
  552. var btn = sender as ToggleButton;
  553. var tag = btn == null ? null : btn.Tag as IoBitTag;
  554. if (tag == null || _io == null || _motion == null || !_motion.IsConnected)
  555. return;
  556. bool on = btn.IsChecked == true;
  557. bool ok;
  558. if (tag.IsOutput)
  559. {
  560. ok = _io.WriteDO(tag.Bank, tag.Index, on);
  561. Msg(ok ? $"{BankName(tag.Bank)} DO{tag.Index} = {(on ? 1 : 0)}" : "写 DO 失败");
  562. }
  563. else if (tag.CanWrite)
  564. {
  565. ok = _io.WriteDI(tag.Bank, tag.Index, on);
  566. Msg(ok ? $"{BankName(tag.Bank)} DI{tag.Index} = {(on ? 1 : 0)}" : "写 DI 失败");
  567. }
  568. else
  569. {
  570. RefreshIoBits();
  571. return;
  572. }
  573. if (!ok) RefreshIoBits();
  574. }
  575. private static string BankName(MotionIoBank bank)
  576. => bank == MotionIoBank.EtherCat ? "EtherCAT" : "本地";
  577. private sealed class IoBitTag
  578. {
  579. public MotionIoBank Bank;
  580. public bool IsOutput;
  581. public int Index;
  582. public bool CanWrite;
  583. }
  584. }
  585. /// <summary>运动卡通用调试页工厂。</summary>
  586. public class MotionDebugPageFactory : TeamAAS.Global.Devices.IDeviceDebugPageFactory
  587. {
  588. public string DeviceCategory => "运动卡";
  589. public object CreatePage(object device)
  590. {
  591. var view = new MotionDebugView();
  592. view.Bind(device);
  593. return view;
  594. }
  595. }
  596. }