using MainShell.EventArgsFolder; using MainShell.Hardware; using MainShell.Manual.Model; using MainShell.Manual.ViewModel.State; using MainShell.Resources.CustomControl; using System; using System.Collections.Generic; using System.ComponentModel; using System.Threading.Tasks; namespace MainShell.Manual.ViewModel { public class LogisticsOperationViewModel : OperateViewModelBase { private readonly IDeviceIoMonitorService _ioMonitorService; private readonly Dictionary> _ioNameBindings = new Dictionary>(StringComparer.OrdinalIgnoreCase); private readonly Dictionary> _ioIdBindings = new Dictionary>(); private LogisticsOperationState _state; public new LogisticsOperationState State { get { return _state; } set { SetAndNotify(ref _state, value); } } public LogisticsOperationViewModel(IDeviceIoMonitorService ioMonitorService) { _ioMonitorService = ioMonitorService; State = BuildDefaultState(); State.PropertyChanged += OnStatePropertyChanged; InitializeIoBindings(); _ioMonitorService.IoChanged += OnIoSnapshotChanged; _ioMonitorService.Start(); ApplyEfemMode(State.IsManualWithoutEfem); RequestIoRefresh(); } private LogisticsOperationState BuildDefaultState() { var state = new LogisticsOperationState { SubstrateLoad = BuildSectionState("SUBSTRATE LOAD / 基板上料", "READY", "开始上料流程", "停止流程"), ChipLoad = BuildSectionState("CHIP LOAD / 芯片上料", "READY", "发送取料指令", "取消流程"), SubstrateOut = BuildSectionState("SUBSTRATE OUT / 基板下料", "IDLE", "开始下料出片", "停止流程"), ChipUnload = BuildSectionState("CHIP UNLOAD / 芯片下料", "AWAITING", "执行内部下料", "取消流程"), MagazineMap = MagazineMapViewModel.CreateDefault(25), FeedbackTitle = "FEEDBACK" }; state.ChipLoad.PhaseSteps.Add(new PhaseStepState { Title = "Phase 1: EFEM 取料", Status = "READY", ActionText = "发送取料指令", IsEnabled = true, ControlTag = "EFEM HANDOFF", RequiresEfemCommunication = true, TargetLayer = 1 }); state.ChipLoad.PhaseSteps.Add(new PhaseStepState { Title = "Phase 2: 设备内部吸取", Status = "内部手动", ActionText = "执行内部动作", IsEnabled = false, ControlTag = "INTERNAL", RequiresEfemCommunication = false }); state.ChipUnload.PhaseSteps.Add(new PhaseStepState { Title = "Phase 1: 内部移出至对接位", Status = "内部手动", ActionText = "执行内部下料", IsEnabled = true, ControlTag = "INTERNAL", RequiresEfemCommunication = false }); state.ChipUnload.PhaseSteps.Add(new PhaseStepState { Title = "Phase 2: EFEM 返料到料盒", Status = "AWAITING", ActionText = "发送取料指令", IsEnabled = false, ControlTag = "EFEM HANDOFF", RequiresEfemCommunication = true, TargetLayer = 1 }); return state; } private SectionState BuildSectionState(string title, string status, string primaryAction, string secondaryAction) { return new SectionState { Title = title, Status = status, PrimaryActionText = primaryAction, SecondaryActionText = secondaryAction, Indicators = { new IndicatorState { Name = "进片感应器触发", IsOn = false }, new IndicatorState { Name = "轨道电机运行中", IsOn = false }, new IndicatorState { Name = "平移机原点检测", IsOn = false }, new IndicatorState { Name = "真空吸附确认", IsOn = false } } }; } public async Task ScanAsync() { if (State.IsScanning) { return; } State.IsScanning = true; State.ScanProgress = 0; State.AddFeedback("自动扫码开始。", "INFO"); RequestIoRefresh(); for (var i = 1; i <= 100; i++) { State.ScanProgress = i; await Task.Delay(20); } State.IsScanning = false; State.AddFeedback("自动扫码完成,共扫描 25 个槽位。", "INFO"); RequestIoRefresh(); } private void OnStatePropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == nameof(LogisticsOperationState.IsManualWithoutEfem)) { ApplyEfemMode(State.IsManualWithoutEfem); } RequestIoRefresh(); } private void ApplyEfemMode(bool manualWithoutEfem) { if (State.ChipLoad.PhaseSteps.Count > 1) { State.ChipLoad.PhaseSteps[0].IsEnabled = !manualWithoutEfem; State.ChipLoad.PhaseSteps[1].IsEnabled = manualWithoutEfem; } if (State.ChipUnload.PhaseSteps.Count > 1) { State.ChipUnload.PhaseSteps[1].IsEnabled = !manualWithoutEfem; } RequestIoRefresh(); } public Task ChipLoadPhase1Async() { if (State.IsManualWithoutEfem) { State.AddFeedback("CHIP LOAD: 无EFEM调试模式,直接执行设备内部芯片上料流程。", "INFO"); return ChipLoadPhase2Async(); } var layer = State.ChipLoad.PhaseSteps[0].TargetLayer; State.AddFeedback(string.Format("CHIP LOAD Phase1: 发送EFEM取料指令,目标料盒层 L{0:D2}(预留通讯接口)。", layer), "INFO"); if (State.ChipLoad.PhaseSteps.Count > 1) { State.ChipLoad.PhaseSteps[1].IsEnabled = true; } RequestIoRefresh(); return Task.CompletedTask; } public Task ChipLoadPhase2Async() { State.AddFeedback("CHIP LOAD Phase2: 执行设备内部吸取动作。", "INFO"); RequestIoRefresh(); return Task.CompletedTask; } public Task ChipUnloadPhase1Async() { State.AddFeedback("CHIP UNLOAD Phase1: 内部移出至对接位。", "INFO"); RequestIoRefresh(); return Task.CompletedTask; } public Task ChipUnloadPhase2Async() { var layer = State.ChipUnload.PhaseSteps[1].TargetLayer; State.AddFeedback(string.Format("CHIP UNLOAD Phase2: 发送EFEM返料指令,目标料盒层 L{0:D2}(预留通讯接口)。", layer), "INFO"); RequestIoRefresh(); return Task.CompletedTask; } protected override void OnDeactivate() { _ioMonitorService.IoChanged -= OnIoSnapshotChanged; base.OnDeactivate(); } private void InitializeIoBindings() { BindIoName("LG.SubstrateLoad.SensorIn", value => SetIndicator(State.SubstrateLoad, 0, value)); BindIoName("LG.SubstrateLoad.TrackMotorRun", value => SetIndicator(State.SubstrateLoad, 1, value)); BindIoName("LG.SubstrateLoad.HomeChecked", value => SetIndicator(State.SubstrateLoad, 2, value)); BindIoName("LG.SubstrateLoad.VacuumOk", value => SetIndicator(State.SubstrateLoad, 3, value)); BindIoName("LG.ChipLoad.SensorIn", value => SetIndicator(State.ChipLoad, 0, value)); BindIoName("LG.ChipLoad.TrackMotorRun", value => SetIndicator(State.ChipLoad, 1, value)); BindIoName("LG.ChipLoad.HomeChecked", value => SetIndicator(State.ChipLoad, 2, value)); BindIoName("LG.ChipLoad.VacuumOk", value => SetIndicator(State.ChipLoad, 3, value)); BindIoName("LG.SubstrateOut.SensorIn", value => SetIndicator(State.SubstrateOut, 0, value)); BindIoName("LG.SubstrateOut.TrackMotorRun", value => SetIndicator(State.SubstrateOut, 1, value)); BindIoName("LG.SubstrateOut.HomeChecked", value => SetIndicator(State.SubstrateOut, 2, value)); BindIoName("LG.SubstrateOut.VacuumOk", value => SetIndicator(State.SubstrateOut, 3, value)); BindIoName("LG.ChipUnload.SensorIn", value => SetIndicator(State.ChipUnload, 0, value)); BindIoName("LG.ChipUnload.TrackMotorRun", value => SetIndicator(State.ChipUnload, 1, value)); BindIoName("LG.ChipUnload.HomeChecked", value => SetIndicator(State.ChipUnload, 2, value)); BindIoName("LG.ChipUnload.VacuumOk", value => SetIndicator(State.ChipUnload, 3, value)); BindIoId(1001, value => SetIndicator(State.SubstrateLoad, 0, value)); BindIoId(1002, value => SetIndicator(State.SubstrateLoad, 1, value)); BindIoId(1003, value => SetIndicator(State.SubstrateLoad, 2, value)); BindIoId(1004, value => SetIndicator(State.SubstrateLoad, 3, value)); } private void BindIoName(string name, Action setter) { _ioNameBindings[name] = setter; } private void BindIoId(int id, Action setter) { _ioIdBindings[id] = setter; } private void OnIoSnapshotChanged(object sender, DeviceIoChangedEventArgs e) { var changedPoints = e.ChangedPoints; for (var i = 0; i < changedPoints.Count; i++) { ApplyIoPoint(changedPoints[i]); } } private void ApplyIoPoint(DeviceIoPointState point) { if (point == null) { return; } Action setter; if (!string.IsNullOrWhiteSpace(point.Name) && _ioNameBindings.TryGetValue(point.Name, out setter)) { setter(point.Value); return; } if (_ioIdBindings.TryGetValue(point.Id, out setter)) { setter(point.Value); } } private void RequestIoRefresh() { _ioMonitorService.RequestRefresh(); } private static void SetIndicator(SectionState section, int index, bool isOn) { if (section == null || section.Indicators.Count <= index) { return; } section.Indicators[index].IsOn = isOn; } } }