| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655 |
- using System;
- using System.Globalization;
- using System.Windows;
- using System.Windows.Controls;
- using System.Windows.Controls.Primitives;
- using System.Windows.Input;
- using System.Windows.Media;
- using TeamAAS.Global.Devices;
- using TeamAAS.Motion.Models;
- using TeamAAS.Motion.Motions;
- namespace TeamAAS.Motion
- {
- /// <summary>
- /// 运动卡通用调试页:轴选择 + 使能/回零/点动/定位/急停 + 本地/EtherCAT IO。
- /// </summary>
- public partial class MotionDebugView : UserControl
- {
- private IMotionCard _motion;
- private IMotionSafetyHost _safety;
- private IMotionIoHost _io;
- private IAxis _selectedAxis;
- private bool _jogActive;
- private bool _updatingIo;
- private int _ioLayoutKey = int.MinValue;
- private readonly System.Windows.Threading.DispatcherTimer _refreshTimer;
- public MotionDebugView()
- {
- InitializeComponent();
- _refreshTimer = new System.Windows.Threading.DispatcherTimer
- {
- Interval = TimeSpan.FromMilliseconds(200)
- };
- _refreshTimer.Tick += (_, __) => RefreshStatus();
- Unloaded += (_, __) =>
- {
- _refreshTimer.Stop();
- UnhookSafety();
- };
- }
- public void Bind(object motion)
- {
- UnhookSafety();
- _motion = motion as IMotionCard;
- _safety = _motion as IMotionSafetyHost;
- _io = _motion as IMotionIoHost;
- _selectedAxis = null;
- _jogActive = false;
- _ioLayoutKey = int.MinValue;
- CmbAxis.ItemsSource = null;
- if (_motion == null)
- {
- OpsRoot.IsEnabled = false;
- IoRoot.IsEnabled = false;
- _refreshTimer.Stop();
- TxtStatus.Text = "---";
- TxtMessage.Text = "未绑定运动卡";
- ClearAxisMetrics();
- SetConnDot(false);
- ApplySafetyBanner();
- RebuildIoPanels();
- return;
- }
- if (_safety != null)
- _safety.SafetyChanged += OnSafetyChanged;
- var axes = _motion.GetAxes();
- CmbAxis.ItemsSource = axes;
- if (axes != null && axes.Count > 0)
- CmbAxis.SelectedIndex = 0;
- OpsRoot.IsEnabled = _motion.IsConnected;
- IoRoot.IsEnabled = _motion.IsConnected;
- _refreshTimer.Start();
- RefreshStatus();
- }
- private void UnhookSafety()
- {
- if (_safety != null)
- _safety.SafetyChanged -= OnSafetyChanged;
- _safety = null;
- }
- private void OnSafetyChanged(object sender, EventArgs e)
- {
- Dispatcher.BeginInvoke(new Action(RefreshStatus));
- }
- private IAxis CurrentAxis
- {
- get
- {
- if (_selectedAxis != null) return _selectedAxis;
- return CmbAxis.SelectedItem as IAxis;
- }
- }
- private void CmbAxis_SelectionChanged(object sender, SelectionChangedEventArgs e)
- {
- _selectedAxis = CmbAxis.SelectedItem as IAxis;
- RefreshAxisInfo();
- }
- private double ParseDouble(TextBox box, double fallback)
- {
- return double.TryParse(box.Text, NumberStyles.Any, CultureInfo.InvariantCulture, out var v)
- ? v
- : fallback;
- }
- private void Msg(string message) => TxtMessage.Text = message;
- private bool TryGetReadyAxis(out IAxis axis, string action)
- {
- axis = CurrentAxis;
- if (axis == null)
- {
- Msg("未选择轴");
- return false;
- }
- if (_motion == null || !_motion.IsConnected)
- {
- Msg("运动卡未连接");
- return false;
- }
- if (!axis.IsEnabled)
- {
- Msg($"轴未使能,无法{action}");
- return false;
- }
- if (_safety != null && !_safety.AllowMotion(out var reason))
- {
- Msg(reason);
- return false;
- }
- return true;
- }
- private void RefreshStatus()
- {
- if (_motion == null)
- {
- TxtStatus.Text = "---";
- SetConnDot(false);
- ClearAxisMetrics();
- return;
- }
- try
- {
- var connected = _motion.IsConnected;
- TxtStatus.Text = connected ? "已连接" : "未连接";
- SetConnDot(connected);
- OpsRoot.IsEnabled = connected;
- IoRoot.IsEnabled = connected && _io != null;
- RefreshAxisInfo();
- RebuildIoPanels();
- RefreshIoBits();
- }
- catch { }
- }
- private void RefreshAxisInfo()
- {
- var axis = CurrentAxis;
- if (axis == null)
- {
- ClearAxisMetrics();
- return;
- }
- TxtAxisTitle.Text = $"{axis.Name} · 轴 {axis.AxisNo}";
- TxtPosActual.Text = axis.ActualPosition.ToString("F3");
- TxtPosCommand.Text = axis.CommandPosition.ToString("F3");
- TxtVelocity.Text = axis.ActualVelocity.ToString("F3");
- ApplyBanner(axis);
- ApplySafetyBanner();
- SetTile(TileEnable, TxtEnable, axis.IsEnabled, "已使能", "未使能", "#2E7D32", "#C62828");
- SetTile(TileMoving, TxtMoving, axis.IsMoving, "运动中", "静止", "#EF6C00", "#757575");
- SetTile(TileHomed, TxtHomed, axis.IsHomed, "已回零", "未回零", "#1565C0", "#757575");
- var enabled = axis.IsEnabled;
- bool estop = _safety != null && _safety.IsEstopActive;
- bool stop = _safety != null && _safety.IsStopActive;
- BtnEnable.IsEnabled = !enabled && !estop;
- BtnDisable.IsEnabled = enabled;
- BtnStop.IsEnabled = axis.IsMoving;
- MotionOps.IsEnabled = enabled && !estop && !stop;
- if (estop)
- {
- TxtNeedEnable.Text = "急停有效:禁止使能与运动";
- TxtNeedEnable.Visibility = Visibility.Visible;
- }
- else if (stop)
- {
- TxtNeedEnable.Text = "停止信号有效:禁止运动";
- TxtNeedEnable.Visibility = Visibility.Visible;
- }
- else
- {
- TxtNeedEnable.Text = "当前未使能,请先点击「使能」后再点动或定位";
- TxtNeedEnable.Visibility = enabled ? Visibility.Collapsed : Visibility.Visible;
- }
- }
- private void ApplySafetyBanner()
- {
- if (_safety == null || _motion == null || !_motion.IsConnected)
- {
- BannerSafety.Visibility = Visibility.Collapsed;
- return;
- }
- if (_safety.IsEstopActive)
- {
- BannerSafety.Visibility = Visibility.Visible;
- BannerSafety.Background = BrushOf("#B71C1C");
- TxtSafetyHeadline.Text = "急停有效";
- TxtSafetyHint.Text = "所有轴已下使能。手动调试与流程自动使能/运动均无法成功,请先复位急停按钮。";
- return;
- }
- if (_safety.IsStopActive)
- {
- BannerSafety.Visibility = Visibility.Visible;
- BannerSafety.Background = BrushOf("#E65100");
- TxtSafetyHeadline.Text = "停止信号有效";
- TxtSafetyHint.Text = "所有轴禁止运动。手动调试与流程自动定位/点动/回零均无法成功,请先松开停止按钮。";
- return;
- }
- BannerSafety.Visibility = Visibility.Collapsed;
- }
- private void ApplyBanner(IAxis axis)
- {
- string headline;
- string hint;
- string color;
- if (_motion == null || !_motion.IsConnected)
- {
- headline = "未连接";
- hint = "请先在上方连接控制卡";
- color = "#616161";
- }
- else if (_safety != null && _safety.IsEstopActive)
- {
- headline = "急停有效";
- hint = "禁止使能与运动,请先复位急停";
- color = "#B71C1C";
- }
- else if (_safety != null && _safety.IsStopActive)
- {
- headline = "停止信号有效";
- hint = "禁止运动,请先松开停止按钮";
- color = "#E65100";
- }
- else if (axis.State == AxisState.Error)
- {
- headline = "轴故障";
- hint = "请检查报警后重新使能";
- color = "#B71C1C";
- }
- else if (!axis.IsEnabled || axis.State == AxisState.Off)
- {
- headline = "伺服未使能";
- hint = "当前无法运动,请先点击右侧「使能」";
- color = "#C62828";
- }
- else if (axis.State == AxisState.Homing)
- {
- headline = "正在回零";
- hint = "回零过程中请勿点动或定位";
- color = "#1565C0";
- }
- else if (axis.State == AxisState.ContinuousMotion || (axis.IsMoving && _jogActive))
- {
- headline = "点动中";
- hint = "松开按钮或点击「停止」即可停下";
- color = "#EF6C00";
- }
- else if (axis.State == AxisState.DiscreteMotion || axis.IsMoving)
- {
- headline = "定位运动中";
- hint = "可点击「停止」或「急停」中断";
- color = "#EF6C00";
- }
- else
- {
- headline = axis.IsHomed ? "已使能 · 静止" : "已使能 · 未回零";
- hint = axis.IsHomed ? "可以点动或定位" : "建议先回零,再做定位";
- color = "#2E7D32";
- }
- TxtStateHeadline.Text = headline;
- TxtStateHint.Text = hint;
- BannerState.Background = BrushOf(color);
- }
- private void ClearAxisMetrics()
- {
- TxtAxisTitle.Text = "---";
- TxtPosActual.Text = "---";
- TxtPosCommand.Text = "---";
- TxtVelocity.Text = "---";
- TxtStateHeadline.Text = _motion == null ? "未绑定运动卡" : (_motion.IsConnected ? "未选择轴" : "未连接");
- TxtStateHint.Text = "请选择轴并连接控制卡";
- BannerState.Background = BrushOf("#616161");
- SetTile(TileEnable, TxtEnable, false, "已使能", "未使能", "#2E7D32", "#757575");
- SetTile(TileMoving, TxtMoving, false, "运动中", "静止", "#EF6C00", "#757575");
- SetTile(TileHomed, TxtHomed, false, "已回零", "未回零", "#1565C0", "#757575");
- BtnEnable.IsEnabled = false;
- BtnDisable.IsEnabled = false;
- BtnStop.IsEnabled = false;
- MotionOps.IsEnabled = false;
- TxtNeedEnable.Visibility = Visibility.Collapsed;
- ApplySafetyBanner();
- }
- private static void SetTile(Border tile, TextBlock label, bool on, string onText, string offText, string onColor, string offColor)
- {
- label.Text = on ? onText : offText;
- tile.Background = BrushOf(on ? onColor : offColor);
- }
- private static SolidColorBrush BrushOf(string hex)
- => new SolidColorBrush((Color)ColorConverter.ConvertFromString(hex));
- private void SetConnDot(bool connected)
- {
- DotConn.Fill = BrushOf(connected ? "#43A047" : "#9E9E9E");
- }
- private void BtnEnable_Click(object sender, RoutedEventArgs e)
- {
- var axis = CurrentAxis;
- if (axis == null) { Msg("未选择轴"); return; }
- if (_safety != null && !_safety.AllowEnable(out var reason))
- {
- Msg(reason);
- return;
- }
- try
- {
- axis.Enable();
- Msg(axis.IsEnabled ? "已使能" : "使能失败");
- RefreshAxisInfo();
- }
- catch (Exception ex) { Msg("使能失败: " + ex.Message); }
- }
- private void BtnDisable_Click(object sender, RoutedEventArgs e)
- {
- var axis = CurrentAxis;
- if (axis == null) { Msg("未选择轴"); return; }
- try
- {
- StopJogIfActive();
- axis.Disable();
- Msg("已下使能");
- RefreshAxisInfo();
- }
- catch (Exception ex) { Msg("下使能失败: " + ex.Message); }
- }
- private void BtnHome_Click(object sender, RoutedEventArgs e)
- {
- if (!TryGetReadyAxis(out var axis, "回零")) return;
- try
- {
- StopJogIfActive();
- axis.Home();
- Msg("回零完成");
- RefreshAxisInfo();
- }
- catch (Exception ex) { Msg("回零失败: " + ex.Message); }
- }
- private void BtnStop_Click(object sender, RoutedEventArgs e)
- {
- try
- {
- StopJogIfActive();
- CurrentAxis?.Stop();
- Msg("已停止");
- RefreshAxisInfo();
- }
- catch (Exception ex) { Msg("停止失败: " + ex.Message); }
- }
- private void BtnEStop_Click(object sender, RoutedEventArgs e)
- {
- try
- {
- _jogActive = false;
- _motion?.EmergencyStop();
- Msg("急停已触发");
- RefreshAxisInfo();
- }
- catch (Exception ex) { Msg("急停失败: " + ex.Message); }
- }
- private void BtnJog_Down(object sender, MouseButtonEventArgs e)
- {
- if (!TryGetReadyAxis(out var axis, "点动")) return;
- var btn = sender as UIElement;
- var tag = (sender as Button)?.Tag as string;
- double vel = ParseDouble(TxtJogVel, 50);
- if (tag == "-") vel = -vel;
- try
- {
- axis.Jog(vel);
- _jogActive = true;
- btn?.CaptureMouse();
- Msg($"点动 {tag} 速度 {vel:F1}");
- }
- catch (Exception ex) { Msg("点动失败: " + ex.Message); }
- }
- private void BtnJog_Up(object sender, MouseButtonEventArgs e)
- {
- var btn = sender as UIElement;
- if (btn != null && btn.IsMouseCaptured)
- btn.ReleaseMouseCapture();
- else
- StopJogIfActive();
- }
- private void BtnJog_LostCapture(object sender, MouseEventArgs e)
- {
- StopJogIfActive();
- }
- private void StopJogIfActive()
- {
- if (!_jogActive) return;
- _jogActive = false;
- try
- {
- CurrentAxis?.Stop();
- Msg("点动停止");
- RefreshAxisInfo();
- }
- catch (Exception ex) { Msg("停止失败: " + ex.Message); }
- }
- private void BtnMoveAbs_Click(object sender, RoutedEventArgs e)
- {
- if (!TryGetReadyAxis(out var axis, "定位")) return;
- try
- {
- var target = ParseDouble(TxtTargetPos, 0);
- axis.Velocity = ParseDouble(TxtMoveVel, 100);
- axis.MoveTo(target);
- Msg($"绝对定位 → {target:F3}");
- }
- catch (Exception ex) { Msg("绝对定位失败: " + ex.Message); }
- }
- private void BtnMoveRel_Click(object sender, RoutedEventArgs e)
- {
- if (!TryGetReadyAxis(out var axis, "定位")) return;
- try
- {
- var dist = ParseDouble(TxtRelativeDist, 10);
- axis.Velocity = ParseDouble(TxtMoveVel, 100);
- axis.MoveBy(dist);
- Msg($"相对定位 {dist:F3}");
- }
- catch (Exception ex) { Msg("相对定位失败: " + ex.Message); }
- }
- private void BtnSetZero_Click(object sender, RoutedEventArgs e)
- {
- var axis = CurrentAxis;
- if (axis == null) { Msg("未选择轴"); return; }
- try
- {
- axis.SetPosition(0);
- Msg("当前位置已清零");
- RefreshAxisInfo();
- }
- catch (Exception ex) { Msg("清零失败: " + ex.Message); }
- }
- private MotionDeviceConfig CurrentIoConfig
- {
- get
- {
- if (_motion is SimulatedMotionCard sim) return sim.DeviceConfig;
- if (_motion is InovanceMotionCard imc) return imc.DeviceConfig;
- return null;
- }
- }
- private void RebuildIoPanels()
- {
- int diL = _io == null ? 0 : _io.GetDiCount(MotionIoBank.Local);
- int doL = _io == null ? 0 : _io.GetDoCount(MotionIoBank.Local);
- int diE = _io == null ? 0 : _io.GetDiCount(MotionIoBank.EtherCat);
- int doE = _io == null ? 0 : _io.GetDoCount(MotionIoBank.EtherCat);
- int key = diL | (doL << 8) | (diE << 16) | (doE << 24);
- if (_io != null && _io.CanSimulateDi) key ^= 0x40000000;
- if (key == _ioLayoutKey) return;
- _ioLayoutKey = key;
- FillIoPanel(PanelLocalDi, MotionIoBank.Local, false, diL);
- FillIoPanel(PanelLocalDo, MotionIoBank.Local, true, doL);
- FillIoPanel(PanelEcatDi, MotionIoBank.EtherCat, false, diE);
- FillIoPanel(PanelEcatDo, MotionIoBank.EtherCat, true, doE);
- }
- private void FillIoPanel(WrapPanel panel, MotionIoBank bank, bool isDo, int count)
- {
- panel.Children.Clear();
- if (count <= 0)
- {
- panel.Children.Add(new TextBlock
- {
- Text = "无",
- FontSize = 12,
- Foreground = BrushOf("#9E9E9E"),
- Margin = new Thickness(0, 4, 0, 0)
- });
- return;
- }
- string prefix = isDo ? "DO" : "DI";
- bool canWrite = isDo || (_io != null && _io.CanSimulateDi);
- for (int i = 0; i < count; i++)
- {
- var btn = new ToggleButton
- {
- Content = prefix + i,
- Width = 52,
- Height = 34,
- Margin = new Thickness(0, 0, 6, 6),
- FontSize = 11,
- Tag = new IoBitTag { Bank = bank, IsOutput = isDo, Index = i, CanWrite = canWrite },
- Cursor = canWrite ? Cursors.Hand : Cursors.Arrow
- };
- btn.Style = (Style)FindResource("IoBitButton");
- btn.Click += IoBit_Click;
- panel.Children.Add(btn);
- }
- }
- private void RefreshIoBits()
- {
- if (_io == null) return;
- _updatingIo = true;
- try
- {
- RefreshIoPanel(PanelLocalDi, MotionIoBank.Local, false);
- RefreshIoPanel(PanelLocalDo, MotionIoBank.Local, true);
- RefreshIoPanel(PanelEcatDi, MotionIoBank.EtherCat, false);
- RefreshIoPanel(PanelEcatDo, MotionIoBank.EtherCat, true);
- }
- finally { _updatingIo = false; }
- }
- private void RefreshIoPanel(WrapPanel panel, MotionIoBank bank, bool isDo)
- {
- foreach (var child in panel.Children)
- {
- var btn = child as ToggleButton;
- if (btn == null) continue;
- var tag = btn.Tag as IoBitTag;
- if (tag == null) continue;
- bool on = isDo ? _io.ReadDO(bank, tag.Index) : _io.ReadDI(bank, tag.Index);
- btn.IsChecked = on;
- string role = SafetyRole(bank, tag.Index, isDo);
- if (role == "estop")
- btn.Background = BrushOf(on ? "#B71C1C" : "#7F1D1D");
- else if (role == "stop")
- btn.Background = BrushOf(on ? "#E65100" : "#BF360C");
- else
- btn.Background = BrushOf(on ? "#2E7D32" : "#757575");
- btn.Foreground = Brushes.White;
- btn.ToolTip = role == "estop" ? "急停 DI" : role == "stop" ? "停止 DI" : (isDo ? "输出" : "输入");
- }
- }
- private string SafetyRole(MotionIoBank bank, int index, bool isDo)
- {
- if (isDo) return null;
- var cfg = CurrentIoConfig;
- if (cfg == null) return null;
- if (cfg.EstopDiIndex == index && cfg.EstopDiBank == bank) return "estop";
- if (cfg.StopDiIndex == index && cfg.StopDiBank == bank) return "stop";
- return null;
- }
- private void IoBit_Click(object sender, RoutedEventArgs e)
- {
- if (_updatingIo) return;
- var btn = sender as ToggleButton;
- var tag = btn == null ? null : btn.Tag as IoBitTag;
- if (tag == null || _io == null || _motion == null || !_motion.IsConnected)
- return;
- bool on = btn.IsChecked == true;
- bool ok;
- if (tag.IsOutput)
- {
- ok = _io.WriteDO(tag.Bank, tag.Index, on);
- Msg(ok ? $"{BankName(tag.Bank)} DO{tag.Index} = {(on ? 1 : 0)}" : "写 DO 失败");
- }
- else if (tag.CanWrite)
- {
- ok = _io.WriteDI(tag.Bank, tag.Index, on);
- Msg(ok ? $"{BankName(tag.Bank)} DI{tag.Index} = {(on ? 1 : 0)}" : "写 DI 失败");
- }
- else
- {
- RefreshIoBits();
- return;
- }
- if (!ok) RefreshIoBits();
- }
- private static string BankName(MotionIoBank bank)
- => bank == MotionIoBank.EtherCat ? "EtherCAT" : "本地";
- private sealed class IoBitTag
- {
- public MotionIoBank Bank;
- public bool IsOutput;
- public int Index;
- public bool CanWrite;
- }
- }
- /// <summary>运动卡通用调试页工厂。</summary>
- public class MotionDebugPageFactory : TeamAAS.Global.Devices.IDeviceDebugPageFactory
- {
- public string DeviceCategory => "运动卡";
- public object CreatePage(object device)
- {
- var view = new MotionDebugView();
- view.Bind(device);
- return view;
- }
- }
- }
|