using System; using System.Collections.ObjectModel; using System.Linq; using System.Threading; using System.Threading.Tasks; using Prism.Commands; using Prism.Mvvm; using TeamAAS.Feeder.Enums; using TeamAAS.Feeder.Interfaces; using TeamAAS.Feeder.Models; namespace TeamAAS.Feeder.UI { /// /// 通用(Team 品牌)供料器调试页的视图模型:从宿主 SettingViewModel 下沉而来(Phase 2 迁移)。 /// 绑定一个 设备实例 + 其 配置,直接调用设备 API。 /// IFeederManager 经 TeamAAS.Global.ServiceRegistry 拉取(避免 Feeder 库反向依赖 TeamAAS.Core)。 /// 方向/振动集/序列集/料斗IO/输入触发/系统参数/背光 全部在此,与硬件库调试页范式一致。 /// public class FeederDebugViewModel : BindableBase { private const string LogSource = "Feeder"; private IFeeder _feeder; private FeederInfo _info; private IFeederManager _manager; private CancellationTokenSource _sequenceCts; public FeederDebugViewModel() { FeederDirectionCommand = new DelegateCommand(async p => await ExecuteDirectionAsync(Convert.ToInt32(p))); StopFeederActionCommand = new DelegateCommand(async () => await StopFeederAsync()); ToggleBacklightCommand = new DelegateCommand(async () => await ToggleBacklightAsync()); ExecuteActionCommand = new DelegateCommand(async () => await ExecuteCurrentActionAsync()); DownloadParamCommand = new DelegateCommand(async () => await DownloadFeederParamsAsync()); AddSequenceCommand = new DelegateCommand(AddSequence); DeleteSequenceCommand = new DelegateCommand(DeleteSequence); RenameSequenceCommand = new DelegateCommand(RenameSequence); AddSequenceStepCommand = new DelegateCommand(AddSequenceStep); MoveUpStepCommand = new DelegateCommand(MoveUpStep); MoveDownStepCommand = new DelegateCommand(MoveDownStep); DeleteStepCommand = new DelegateCommand(DeleteStep); ExecuteSequenceCommand = new DelegateCommand(async () => await ExecuteSequenceAsync()); StopSequenceCommand = new DelegateCommand(StopSequence); ReadHopperParamCommand = new DelegateCommand(async () => await ReadHopperParamAsync()); WriteHopperParamCommand = new DelegateCommand(async () => await WriteHopperParamAsync()); RunHopperOutputCommand = new DelegateCommand(async () => await RunHopperOutputAsync()); StopHopperOutputCommand = new DelegateCommand(async () => await StopHopperOutputAsync()); ReadInputTriggerCommand = new DelegateCommand(async () => await ReadInputTriggerAsync()); WriteInputTriggerCommand = new DelegateCommand(async () => await WriteInputTriggerAsync()); ReadSystemParamCommand = new DelegateCommand(async () => await ReadSystemParamAsync()); WriteSystemParamCommand = new DelegateCommand(async () => await WriteSystemParamAsync()); } /// 绑定供料器设备(页面装载/选中时由 IFeederDebugPage.BindFeeder 调用)。 public void Bind(IFeeder feeder) { _feeder = feeder; try { _manager = TeamAAS.Global.ServiceRegistry.Default.GetService(); } catch { _manager = null; } _info = null; if (_feeder != null && _manager != null) { try { _info = _manager.GetAll().FirstOrDefault(f => f.Id == _feeder.Id); } catch { _info = null; } } SelectedSequence = _info?.Sequences?.FirstOrDefault(); StatusText = _feeder == null ? "未连接" : (_feeder.IsConnected ? $"{_feeder.Name} 已就绪" : "未连接"); RaisePropertyChanged(nameof(Info)); RaisePropertyChanged(nameof(CurrentAction)); RaisePropertyChanged(nameof(CurrentSequences)); RaisePropertyChanged(nameof(HasSelectedStep)); } /// 解绑(切换供料器/页面卸载)。 public void Unbind() { try { _sequenceCts?.Cancel(); } catch { } _feeder = null; _info = null; } private string _statusText = "未连接"; public string StatusText { get { return _statusText; } set { SetProperty(ref _statusText, value); } } /// 当前绑定供料器的配置模型(方向/振动集/序列/背光状态等的数据源);未绑定时为 null。 public FeederInfo Info { get { return _info; } } private bool EnsureConnected() { if (_feeder == null || !_feeder.IsConnected) { TeamAAS.DialogHelper.Warning("请先连接供料器"); return false; } return true; } private void SaveSilent() { if (_info == null || _manager == null) return; try { _manager.SaveConfig(); } catch { } } #region 振动集 / 方向 public int[] VibrationSetList { get; } = Enumerable.Range(1, 11).ToArray(); public string[] ActionLetterList { get; } = { "← A 左", "↙ B 左下", "↖ C 左上", "↓ D 下", "↑ E 上", "→ F 右", "↘ G 右下", "↗ H 右上", "⤨ I 散开", "⇔ J 水平聚集", "⇕ K 垂直聚集" }; private int _selectedVibrationSetIndex; public int SelectedVibrationSetIndex { get { return _selectedVibrationSetIndex; } set { if (SetProperty(ref _selectedVibrationSetIndex, value)) RaisePropertyChanged(nameof(CurrentAction)); } } private int _selectedActionIndex; public int SelectedActionIndex { get { return _selectedActionIndex; } set { if (SetProperty(ref _selectedActionIndex, value)) RaisePropertyChanged(nameof(CurrentAction)); } } private int _currentDuration = 500; public int CurrentDuration { get { return _currentDuration; } set { SetProperty(ref _currentDuration, value); } } public SingleActionParam CurrentAction { get { if (_info == null || _info.ActionGroups == null) return null; int idx = SelectedVibrationSetIndex < 0 ? 0 : SelectedVibrationSetIndex > 10 ? 10 : SelectedVibrationSetIndex; return _info.ActionGroups[idx]; } } private bool _isSyncChanging; public bool IsSyncChanging { get { return _isSyncChanging; } set { SetProperty(ref _isSyncChanging, value); } } private int _globalAmplitude = 50; public int GlobalAmplitude { get { return _globalAmplitude; } set { if (SetProperty(ref _globalAmplitude, value) && IsSyncChanging && CurrentAction != null) { CurrentAction.Motor1.Amplitude = (ushort)value; CurrentAction.Motor2.Amplitude = (ushort)value; CurrentAction.Motor3.Amplitude = (ushort)value; CurrentAction.Motor4.Amplitude = (ushort)value; } } } private int _globalFrequency = 100; public int GlobalFrequency { get { return _globalFrequency; } set { if (SetProperty(ref _globalFrequency, value) && IsSyncChanging && CurrentAction != null) { CurrentAction.Motor1.Frequency = (ushort)value; CurrentAction.Motor2.Frequency = (ushort)value; CurrentAction.Motor3.Frequency = (ushort)value; CurrentAction.Motor4.Frequency = (ushort)value; } } } public DelegateCommand FeederDirectionCommand { get; } public DelegateCommand StopFeederActionCommand { get; } public DelegateCommand ToggleBacklightCommand { get; } public DelegateCommand ExecuteActionCommand { get; } public DelegateCommand DownloadParamCommand { get; } private async Task ExecuteDirectionAsync(int dir) { if (!EnsureConnected()) return; try { var action = (FeederAction)dir; StatusText = $"执行方向: {action}"; await _feeder.RunDirectionAsync(action, CurrentDuration); TeamAAS.AppLogger.Info($"供料器方向执行: {action} 持续 {CurrentDuration}ms", LogSource); StatusText = $"方向 {action} 执行完成"; } catch (Exception ex) { StatusText = "执行失败"; TeamAAS.DialogHelper.ShowError("方向执行失败", ex); } } private async Task StopFeederAsync() { if (_feeder == null) return; try { await _feeder.StopAsync(); StatusText = "停止振动"; } catch (Exception ex) { TeamAAS.DialogHelper.ShowError("停止失败", ex); } } private async Task ToggleBacklightAsync() { if (!EnsureConnected()) return; try { bool on = await _feeder.QueryBacklightAsync(); if (on) { await _feeder.CloseBacklightAsync(); if (_info != null) _info.IsBacklightOn = false; StatusText = "背光关"; } else { await _feeder.OpenBacklightAsync(); if (_info != null) _info.IsBacklightOn = true; StatusText = "背光开"; } RaisePropertyChanged(nameof(Info)); } catch (Exception ex) { TeamAAS.DialogHelper.ShowError("背光控制失败", ex); } } private async Task ExecuteCurrentActionAsync() { if (!EnsureConnected()) return; try { await _feeder.SetActionParamAsync(SelectedVibrationSetIndex, CurrentAction); await _feeder.RunDirectionAsync((FeederAction)SelectedActionIndex, CurrentDuration); StatusText = $"执行振动集 {SelectedVibrationSetIndex + 1} ({ActionLetterList[SelectedActionIndex]})"; } catch (Exception ex) { StatusText = "执行失败"; TeamAAS.DialogHelper.ShowError("执行动作失败", ex); } } private async Task DownloadFeederParamsAsync() { if (!EnsureConnected() || _info == null) return; try { StatusText = "下载参数中..."; await _feeder.DownloadAllParamsAsync(_info); TeamAAS.AppLogger.Info($"供料器下载振动集参数: {_info.FeederName}", LogSource); await _feeder.SaveToDeviceAsync(); StatusText = "参数已下载并保存到设备"; TeamAAS.DialogHelper.Success("参数已下载到设备"); } catch (Exception ex) { StatusText = "下载失败"; TeamAAS.DialogHelper.ShowError("下载参数失败", ex); } } #endregion #region 序列集(送料流程) public ObservableCollection CurrentSequences => _info?.Sequences; private SequenceSet _selectedSequence; public SequenceSet SelectedSequence { get { return _selectedSequence; } set { if (SetProperty(ref _selectedSequence, value)) RaisePropertyChanged(nameof(CurrentSequenceSteps)); } } public ObservableCollection CurrentSequenceSteps => _selectedSequence?.Steps; private SequenceStep _selectedStep; public SequenceStep SelectedStep { get { return _selectedStep; } set { if (SetProperty(ref _selectedStep, value)) RaisePropertyChanged(nameof(HasSelectedStep)); } } public bool HasSelectedStep => _selectedStep != null; private string _sequenceStatusText = ""; public string SequenceStatusText { get { return _sequenceStatusText; } set { SetProperty(ref _sequenceStatusText, value); } } public DelegateCommand AddSequenceCommand { get; } public DelegateCommand DeleteSequenceCommand { get; } public DelegateCommand RenameSequenceCommand { get; } public DelegateCommand AddSequenceStepCommand { get; } public DelegateCommand MoveUpStepCommand { get; } public DelegateCommand MoveDownStepCommand { get; } public DelegateCommand DeleteStepCommand { get; } public DelegateCommand ExecuteSequenceCommand { get; } public DelegateCommand StopSequenceCommand { get; } private void AddSequence() { if (_info == null) { TeamAAS.DialogHelper.Warning("请先选择供料器"); return; } string name = TeamAAS.DialogHelper.Input("流程名称", "新建流程", "新流程"); if (string.IsNullOrWhiteSpace(name)) return; if (_info.Sequences.Any(s => string.Equals(s.Name, name, StringComparison.OrdinalIgnoreCase))) { TeamAAS.DialogHelper.Warning($"流程 \"{name}\" 已存在,请换一个名称"); return; } var seq = new SequenceSet { Name = name }; _info.Sequences.Add(seq); SelectedSequence = seq; RenumberSteps(); SaveSilent(); } private void DeleteSequence() { if (SelectedSequence == null) return; if (!TeamAAS.DialogHelper.ConfirmYesNo($"确认删除流程 \"{SelectedSequence.Name}\" 吗?", "确认")) return; var list = CurrentSequences; int idx = list.IndexOf(SelectedSequence); list.Remove(SelectedSequence); SelectedSequence = list.Count > 0 ? list[Math.Min(idx, list.Count - 1)] : null; RaisePropertyChanged(nameof(CurrentSequenceSteps)); RenumberSteps(); SaveSilent(); } private void RenameSequence() { if (SelectedSequence == null || _info == null) return; string name = TeamAAS.DialogHelper.Input("流程名称", "重命名", SelectedSequence.Name); if (string.IsNullOrWhiteSpace(name)) return; if (!string.Equals(name, SelectedSequence.Name, StringComparison.OrdinalIgnoreCase) && _info.Sequences.Any(s => string.Equals(s.Name, name, StringComparison.OrdinalIgnoreCase))) { TeamAAS.DialogHelper.Warning($"流程 \"{name}\" 已存在,请换一个名称"); return; } SelectedSequence.Name = name; SaveSilent(); } private void AddSequenceStep(string type) { if (SelectedSequence == null) { TeamAAS.DialogHelper.Warning("请先选择或创建一个流程"); return; } var step = new SequenceStep { StepType = type == "Delay" ? SequenceStepType.Delay : SequenceStepType.Direction, TimeMs = type == "Delay" ? 300 : 500, Direction = FeederAction.Right, Enabled = true, }; SelectedSequence.Steps.Add(step); RenumberSteps(); SaveSilent(); } private void MoveUpStep() { if (SelectedStep == null || CurrentSequenceSteps == null) return; int idx = CurrentSequenceSteps.IndexOf(SelectedStep); if (idx <= 0) return; CurrentSequenceSteps.Move(idx, idx - 1); SelectedStep = CurrentSequenceSteps[idx - 1]; RenumberSteps(); SaveSilent(); } private void MoveDownStep() { if (SelectedStep == null || CurrentSequenceSteps == null) return; int idx = CurrentSequenceSteps.IndexOf(SelectedStep); if (idx < 0 || idx >= CurrentSequenceSteps.Count - 1) return; CurrentSequenceSteps.Move(idx, idx + 1); SelectedStep = CurrentSequenceSteps[idx + 1]; RenumberSteps(); SaveSilent(); } private void DeleteStep() { if (SelectedStep == null || CurrentSequenceSteps == null) return; CurrentSequenceSteps.Remove(SelectedStep); SelectedStep = null; RenumberSteps(); SaveSilent(); } private void RenumberSteps() { if (CurrentSequenceSteps == null) return; for (int i = 0; i < CurrentSequenceSteps.Count; i++) CurrentSequenceSteps[i].Index = i + 1; } private async Task ExecuteSequenceAsync() { if (SelectedSequence == null) { TeamAAS.DialogHelper.Warning("请先选择流程"); return; } if (!EnsureConnected()) return; _sequenceCts = new CancellationTokenSource(); var token = _sequenceCts.Token; try { int stepIdx = 0; foreach (var step in SelectedSequence.Steps) { stepIdx++; if (!step.Enabled) continue; if (token.IsCancellationRequested) break; if (step.StepType == SequenceStepType.Delay) { SequenceStatusText = $"等待 {step.TimeMs}ms... ({stepIdx}/{SelectedSequence.Steps.Count})"; await Task.Delay(step.TimeMs, token); } else { SequenceStatusText = $"{step.Direction} 振动 {step.TimeMs}ms ({stepIdx}/{SelectedSequence.Steps.Count})"; await _feeder.RunDirectionAsync(step.Direction, step.TimeMs > 0 ? step.TimeMs : (int?)null); } } SequenceStatusText = token.IsCancellationRequested ? "流程已停止" : "流程执行完成"; } catch (TaskCanceledException) { SequenceStatusText = "流程已停止"; } catch (Exception ex) { SequenceStatusText = "流程执行异常"; TeamAAS.DialogHelper.ShowError("流程执行异常", ex); } } private void StopSequence() { try { _sequenceCts?.Cancel(); } catch { } try { var t = _feeder?.StopAsync(); } catch { } } #endregion #region 料斗输入输出 public int[] HopperGroupList { get; } = Enumerable.Range(0, 26).ToArray(); private int _selectedHopperGroup; public int SelectedHopperGroup { get { return _selectedHopperGroup; } set { SetProperty(ref _selectedHopperGroup, value); } } private HopperOutputParam _hopperParam = new HopperOutputParam(); public HopperOutputParam HopperParam { get { return _hopperParam; } set { SetProperty(ref _hopperParam, value); } } public DelegateCommand ReadHopperParamCommand { get; } public DelegateCommand WriteHopperParamCommand { get; } public DelegateCommand RunHopperOutputCommand { get; } public DelegateCommand StopHopperOutputCommand { get; } private async Task ReadHopperParamAsync() { if (!EnsureConnected()) return; try { StatusText = $"读取料斗输出参数(组 {SelectedHopperGroup})..."; HopperParam = await _feeder.GetHopperParamAsync(SelectedHopperGroup); StatusText = "料斗输出参数已读取"; } catch (Exception ex) { StatusText = "读取料斗参数失败"; TeamAAS.AppLogger.Error("读取料斗输出参数失败", ex, LogSource); TeamAAS.DialogHelper.ShowError("读取料斗参数失败", ex); } } private async Task WriteHopperParamAsync() { if (!EnsureConnected()) return; try { StatusText = $"下载料斗输出参数(组 {SelectedHopperGroup})..."; await _feeder.SetHopperParamAsync(SelectedHopperGroup, HopperParam); StatusText = "料斗输出参数已下载"; TeamAAS.DialogHelper.Success("料斗输出参数已下载到设备"); } catch (Exception ex) { StatusText = "下载料斗参数失败"; TeamAAS.AppLogger.Error("下载料斗输出参数失败", ex, LogSource); TeamAAS.DialogHelper.ShowError("下载料斗参数失败", ex); } } private async Task RunHopperOutputAsync() { if (!EnsureConnected()) return; try { int id = SelectedHopperGroup + 1; StatusText = $"执行料斗动作 {id}..."; await _feeder.RunHopperOutputAsync(id, HopperParam.HopperDuration > 0 ? HopperParam.HopperDuration : (int?)null); StatusText = $"料斗动作 {id} 执行完成"; } catch (Exception ex) { StatusText = "料斗动作执行失败"; TeamAAS.AppLogger.Error("执行料斗动作失败", ex, LogSource); TeamAAS.DialogHelper.ShowError("料斗动作执行失败", ex); } } private async Task StopHopperOutputAsync() { if (_feeder == null) return; try { await _feeder.StopHopperOutputAsync(); StatusText = "料斗振动已停止"; } catch (Exception ex) { TeamAAS.AppLogger.Error("停止料斗振动失败", ex, LogSource); TeamAAS.DialogHelper.ShowError("停止料斗振动失败", ex); } } #endregion #region 输入触发序列ID private int _inputTrigger1; public int InputTrigger1 { get { return _inputTrigger1; } set { SetProperty(ref _inputTrigger1, value); } } private int _inputTrigger2; public int InputTrigger2 { get { return _inputTrigger2; } set { SetProperty(ref _inputTrigger2, value); } } public DelegateCommand ReadInputTriggerCommand { get; } public DelegateCommand WriteInputTriggerCommand { get; } private async Task ReadInputTriggerAsync() { if (!EnsureConnected()) return; try { InputTrigger1 = await _feeder.GetInputTriggerAsync(0); InputTrigger2 = await _feeder.GetInputTriggerAsync(1); StatusText = $"输入触发已读取: 输入1={InputTrigger1} 输入2={InputTrigger2}"; } catch (Exception ex) { StatusText = "读取输入触发失败"; TeamAAS.AppLogger.Error("读取输入触发序列ID失败", ex, LogSource); TeamAAS.DialogHelper.ShowError("读取输入触发失败", ex); } } private async Task WriteInputTriggerAsync() { if (!EnsureConnected()) return; try { await _feeder.SetInputTriggerAsync(0, InputTrigger1); await _feeder.SetInputTriggerAsync(1, InputTrigger2); StatusText = "输入触发已写入"; TeamAAS.DialogHelper.Success("输入触发参数已写入设备"); } catch (Exception ex) { StatusText = "写入输入触发失败"; TeamAAS.AppLogger.Error("写入输入触发序列ID失败", ex, LogSource); TeamAAS.DialogHelper.ShowError("写入输入触发失败", ex); } } #endregion #region 系统参数(超时/背光) private FeederSystemParam _systemParam = new FeederSystemParam(); public FeederSystemParam SystemParam { get { return _systemParam; } set { SetProperty(ref _systemParam, value); } } public DelegateCommand ReadSystemParamCommand { get; } public DelegateCommand WriteSystemParamCommand { get; } private async Task ReadSystemParamAsync() { if (!EnsureConnected()) return; try { StatusText = "读取系统参数..."; SystemParam = await _feeder.GetSystemParamAsync(); StatusText = "系统参数已读取"; } catch (Exception ex) { StatusText = "读取系统参数失败"; TeamAAS.AppLogger.Error("读取供料器系统参数失败", ex, LogSource); TeamAAS.DialogHelper.ShowError("读取系统参数失败", ex); } } private async Task WriteSystemParamAsync() { if (!EnsureConnected()) return; try { StatusText = "写入系统参数..."; await _feeder.SetSystemParamAsync(SystemParam); StatusText = "系统参数已写入并保存"; TeamAAS.DialogHelper.Success("系统参数已写入设备"); } catch (Exception ex) { StatusText = "写入系统参数失败"; TeamAAS.AppLogger.Error("写入供料器系统参数失败", ex, LogSource); TeamAAS.DialogHelper.ShowError("写入系统参数失败", ex); } } #endregion } }