295 lines
11 KiB
C#
295 lines
11 KiB
C#
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<string, Action<bool>> _ioNameBindings = new Dictionary<string, Action<bool>>(StringComparer.OrdinalIgnoreCase);
|
||
private readonly Dictionary<int, Action<bool>> _ioIdBindings = new Dictionary<int, Action<bool>>();
|
||
|
||
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<bool> setter)
|
||
{
|
||
_ioNameBindings[name] = setter;
|
||
}
|
||
|
||
private void BindIoId(int id, Action<bool> 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<bool> 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;
|
||
}
|
||
}
|
||
}
|