MotionDebugView.xaml.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. using System;
  2. using System.Globalization;
  3. using System.Windows;
  4. using System.Windows.Controls;
  5. using System.Windows.Input;
  6. using System.Windows.Media;
  7. using TeamAAS.Global.Devices;
  8. namespace TeamAAS.Motion
  9. {
  10. /// <summary>
  11. /// 运动卡通用调试页:轴选择 + 使能/回零/点动/定位/急停。
  12. /// 仿真卡与后续真实卡共用;绑定 <see cref="IMotionCard"/> 实例。
  13. /// </summary>
  14. public partial class MotionDebugView : UserControl
  15. {
  16. private IMotionCard _motion;
  17. private IAxis _selectedAxis;
  18. private bool _jogActive;
  19. private readonly System.Windows.Threading.DispatcherTimer _refreshTimer;
  20. public MotionDebugView()
  21. {
  22. InitializeComponent();
  23. _refreshTimer = new System.Windows.Threading.DispatcherTimer
  24. {
  25. Interval = TimeSpan.FromMilliseconds(200)
  26. };
  27. _refreshTimer.Tick += (_, __) => RefreshStatus();
  28. Unloaded += (_, __) => _refreshTimer.Stop();
  29. }
  30. public void Bind(object motion)
  31. {
  32. _motion = motion as IMotionCard;
  33. _selectedAxis = null;
  34. _jogActive = false;
  35. CmbAxis.ItemsSource = null;
  36. if (_motion == null)
  37. {
  38. OpsRoot.IsEnabled = false;
  39. _refreshTimer.Stop();
  40. TxtStatus.Text = "---";
  41. TxtMessage.Text = "未绑定运动卡";
  42. ClearAxisMetrics();
  43. SetConnDot(false);
  44. return;
  45. }
  46. var axes = _motion.GetAxes();
  47. CmbAxis.ItemsSource = axes;
  48. if (axes != null && axes.Count > 0)
  49. CmbAxis.SelectedIndex = 0;
  50. OpsRoot.IsEnabled = _motion.IsConnected;
  51. _refreshTimer.Start();
  52. RefreshStatus();
  53. }
  54. private IAxis CurrentAxis
  55. {
  56. get
  57. {
  58. if (_selectedAxis != null) return _selectedAxis;
  59. return CmbAxis.SelectedItem as IAxis;
  60. }
  61. }
  62. private void CmbAxis_SelectionChanged(object sender, SelectionChangedEventArgs e)
  63. {
  64. _selectedAxis = CmbAxis.SelectedItem as IAxis;
  65. RefreshAxisInfo();
  66. }
  67. private double ParseDouble(TextBox box, double fallback)
  68. {
  69. return double.TryParse(box.Text, NumberStyles.Any, CultureInfo.InvariantCulture, out var v)
  70. ? v
  71. : fallback;
  72. }
  73. private void Msg(string message) => TxtMessage.Text = message;
  74. private bool TryGetReadyAxis(out IAxis axis, string action)
  75. {
  76. axis = CurrentAxis;
  77. if (axis == null)
  78. {
  79. Msg("未选择轴");
  80. return false;
  81. }
  82. if (_motion == null || !_motion.IsConnected)
  83. {
  84. Msg("运动卡未连接");
  85. return false;
  86. }
  87. if (!axis.IsEnabled)
  88. {
  89. Msg($"轴未使能,无法{action}");
  90. return false;
  91. }
  92. return true;
  93. }
  94. private void RefreshStatus()
  95. {
  96. if (_motion == null)
  97. {
  98. TxtStatus.Text = "---";
  99. SetConnDot(false);
  100. ClearAxisMetrics();
  101. return;
  102. }
  103. try
  104. {
  105. var connected = _motion.IsConnected;
  106. TxtStatus.Text = connected ? "已连接" : "未连接";
  107. SetConnDot(connected);
  108. OpsRoot.IsEnabled = connected;
  109. RefreshAxisInfo();
  110. }
  111. catch { }
  112. }
  113. private void RefreshAxisInfo()
  114. {
  115. var axis = CurrentAxis;
  116. if (axis == null)
  117. {
  118. ClearAxisMetrics();
  119. return;
  120. }
  121. TxtAxisTitle.Text = $"{axis.Name} · 轴 {axis.AxisNo}";
  122. TxtPosActual.Text = axis.ActualPosition.ToString("F3");
  123. TxtPosCommand.Text = axis.CommandPosition.ToString("F3");
  124. TxtVelocity.Text = axis.ActualVelocity.ToString("F3");
  125. ApplyBanner(axis);
  126. SetTile(TileEnable, TxtEnable, axis.IsEnabled, "已使能", "未使能", "#2E7D32", "#C62828");
  127. SetTile(TileMoving, TxtMoving, axis.IsMoving, "运动中", "静止", "#EF6C00", "#757575");
  128. SetTile(TileHomed, TxtHomed, axis.IsHomed, "已回零", "未回零", "#1565C0", "#757575");
  129. var enabled = axis.IsEnabled;
  130. BtnEnable.IsEnabled = !enabled;
  131. BtnDisable.IsEnabled = enabled;
  132. BtnStop.IsEnabled = axis.IsMoving;
  133. MotionOps.IsEnabled = enabled;
  134. TxtNeedEnable.Visibility = enabled ? Visibility.Collapsed : Visibility.Visible;
  135. }
  136. private void ApplyBanner(IAxis axis)
  137. {
  138. string headline;
  139. string hint;
  140. string color;
  141. if (_motion == null || !_motion.IsConnected)
  142. {
  143. headline = "未连接";
  144. hint = "请先在上方连接控制卡";
  145. color = "#616161";
  146. }
  147. else if (axis.State == AxisState.Error)
  148. {
  149. headline = "轴故障";
  150. hint = "请检查报警后重新使能";
  151. color = "#B71C1C";
  152. }
  153. else if (!axis.IsEnabled || axis.State == AxisState.Off)
  154. {
  155. headline = "伺服未使能";
  156. hint = "当前无法运动,请先点击右侧「使能」";
  157. color = "#C62828";
  158. }
  159. else if (axis.State == AxisState.Homing)
  160. {
  161. headline = "正在回零";
  162. hint = "回零过程中请勿点动或定位";
  163. color = "#1565C0";
  164. }
  165. else if (axis.State == AxisState.ContinuousMotion || (axis.IsMoving && _jogActive))
  166. {
  167. headline = "点动中";
  168. hint = "松开按钮或点击「停止」即可停下";
  169. color = "#EF6C00";
  170. }
  171. else if (axis.State == AxisState.DiscreteMotion || axis.IsMoving)
  172. {
  173. headline = "定位运动中";
  174. hint = "可点击「停止」或「急停」中断";
  175. color = "#EF6C00";
  176. }
  177. else
  178. {
  179. headline = axis.IsHomed ? "已使能 · 静止" : "已使能 · 未回零";
  180. hint = axis.IsHomed ? "可以点动或定位" : "建议先回零,再做定位";
  181. color = "#2E7D32";
  182. }
  183. TxtStateHeadline.Text = headline;
  184. TxtStateHint.Text = hint;
  185. BannerState.Background = BrushOf(color);
  186. }
  187. private void ClearAxisMetrics()
  188. {
  189. TxtAxisTitle.Text = "---";
  190. TxtPosActual.Text = "---";
  191. TxtPosCommand.Text = "---";
  192. TxtVelocity.Text = "---";
  193. TxtStateHeadline.Text = _motion == null ? "未绑定运动卡" : (_motion.IsConnected ? "未选择轴" : "未连接");
  194. TxtStateHint.Text = "请选择轴并连接控制卡";
  195. BannerState.Background = BrushOf("#616161");
  196. SetTile(TileEnable, TxtEnable, false, "已使能", "未使能", "#2E7D32", "#757575");
  197. SetTile(TileMoving, TxtMoving, false, "运动中", "静止", "#EF6C00", "#757575");
  198. SetTile(TileHomed, TxtHomed, false, "已回零", "未回零", "#1565C0", "#757575");
  199. BtnEnable.IsEnabled = false;
  200. BtnDisable.IsEnabled = false;
  201. BtnStop.IsEnabled = false;
  202. MotionOps.IsEnabled = false;
  203. TxtNeedEnable.Visibility = Visibility.Collapsed;
  204. }
  205. private static void SetTile(Border tile, TextBlock label, bool on, string onText, string offText, string onColor, string offColor)
  206. {
  207. label.Text = on ? onText : offText;
  208. tile.Background = BrushOf(on ? onColor : offColor);
  209. }
  210. private static SolidColorBrush BrushOf(string hex)
  211. => new SolidColorBrush((Color)ColorConverter.ConvertFromString(hex));
  212. private void SetConnDot(bool connected)
  213. {
  214. DotConn.Fill = BrushOf(connected ? "#43A047" : "#9E9E9E");
  215. }
  216. private void BtnEnable_Click(object sender, RoutedEventArgs e)
  217. {
  218. var axis = CurrentAxis;
  219. if (axis == null) { Msg("未选择轴"); return; }
  220. try
  221. {
  222. axis.Enable();
  223. Msg("已使能");
  224. RefreshAxisInfo();
  225. }
  226. catch (Exception ex) { Msg("使能失败: " + ex.Message); }
  227. }
  228. private void BtnDisable_Click(object sender, RoutedEventArgs e)
  229. {
  230. var axis = CurrentAxis;
  231. if (axis == null) { Msg("未选择轴"); return; }
  232. try
  233. {
  234. StopJogIfActive();
  235. axis.Disable();
  236. Msg("已下使能");
  237. RefreshAxisInfo();
  238. }
  239. catch (Exception ex) { Msg("下使能失败: " + ex.Message); }
  240. }
  241. private void BtnHome_Click(object sender, RoutedEventArgs e)
  242. {
  243. if (!TryGetReadyAxis(out var axis, "回零")) return;
  244. try
  245. {
  246. StopJogIfActive();
  247. axis.Home();
  248. Msg("回零完成");
  249. RefreshAxisInfo();
  250. }
  251. catch (Exception ex) { Msg("回零失败: " + ex.Message); }
  252. }
  253. private void BtnStop_Click(object sender, RoutedEventArgs e)
  254. {
  255. try
  256. {
  257. StopJogIfActive();
  258. CurrentAxis?.Stop();
  259. Msg("已停止");
  260. RefreshAxisInfo();
  261. }
  262. catch (Exception ex) { Msg("停止失败: " + ex.Message); }
  263. }
  264. private void BtnEStop_Click(object sender, RoutedEventArgs e)
  265. {
  266. try
  267. {
  268. _jogActive = false;
  269. _motion?.EmergencyStop();
  270. Msg("急停已触发");
  271. RefreshAxisInfo();
  272. }
  273. catch (Exception ex) { Msg("急停失败: " + ex.Message); }
  274. }
  275. private void BtnJog_Down(object sender, MouseButtonEventArgs e)
  276. {
  277. if (!TryGetReadyAxis(out var axis, "点动")) return;
  278. var btn = sender as UIElement;
  279. var tag = (sender as Button)?.Tag as string;
  280. double vel = ParseDouble(TxtJogVel, 50);
  281. if (tag == "-") vel = -vel;
  282. try
  283. {
  284. axis.Jog(vel);
  285. _jogActive = true;
  286. btn?.CaptureMouse();
  287. Msg($"点动 {tag} 速度 {vel:F1}");
  288. }
  289. catch (Exception ex) { Msg("点动失败: " + ex.Message); }
  290. }
  291. private void BtnJog_Up(object sender, MouseButtonEventArgs e)
  292. {
  293. var btn = sender as UIElement;
  294. if (btn != null && btn.IsMouseCaptured)
  295. btn.ReleaseMouseCapture();
  296. else
  297. StopJogIfActive();
  298. }
  299. private void BtnJog_LostCapture(object sender, MouseEventArgs e)
  300. {
  301. StopJogIfActive();
  302. }
  303. private void StopJogIfActive()
  304. {
  305. if (!_jogActive) return;
  306. _jogActive = false;
  307. try
  308. {
  309. CurrentAxis?.Stop();
  310. Msg("点动停止");
  311. RefreshAxisInfo();
  312. }
  313. catch (Exception ex) { Msg("停止失败: " + ex.Message); }
  314. }
  315. private void BtnMoveAbs_Click(object sender, RoutedEventArgs e)
  316. {
  317. if (!TryGetReadyAxis(out var axis, "定位")) return;
  318. try
  319. {
  320. var target = ParseDouble(TxtTargetPos, 0);
  321. axis.Velocity = ParseDouble(TxtMoveVel, 100);
  322. axis.MoveTo(target);
  323. Msg($"绝对定位 → {target:F3}");
  324. }
  325. catch (Exception ex) { Msg("绝对定位失败: " + ex.Message); }
  326. }
  327. private void BtnMoveRel_Click(object sender, RoutedEventArgs e)
  328. {
  329. if (!TryGetReadyAxis(out var axis, "定位")) return;
  330. try
  331. {
  332. var dist = ParseDouble(TxtRelativeDist, 10);
  333. axis.Velocity = ParseDouble(TxtMoveVel, 100);
  334. axis.MoveBy(dist);
  335. Msg($"相对定位 {dist:F3}");
  336. }
  337. catch (Exception ex) { Msg("相对定位失败: " + ex.Message); }
  338. }
  339. private void BtnSetZero_Click(object sender, RoutedEventArgs e)
  340. {
  341. var axis = CurrentAxis;
  342. if (axis == null) { Msg("未选择轴"); return; }
  343. try
  344. {
  345. axis.SetPosition(0);
  346. Msg("当前位置已清零");
  347. RefreshAxisInfo();
  348. }
  349. catch (Exception ex) { Msg("清零失败: " + ex.Message); }
  350. }
  351. }
  352. /// <summary>运动卡通用调试页工厂。</summary>
  353. public class MotionDebugPageFactory : TeamAAS.Global.Devices.IDeviceDebugPageFactory
  354. {
  355. public string DeviceCategory => "运动卡";
  356. public object CreatePage(object device)
  357. {
  358. var view = new MotionDebugView();
  359. view.Bind(device);
  360. return view;
  361. }
  362. }
  363. }