using Aurora; using Aurora.Api; using Aurora.Api.Manager; using Aurora.View; using AuroraLaserScanner; using CoreUnit.Scanner.UserControl.ScannerCanvas; using ILSOS_PrismaticRD_PTOP.Device; using ILSOS_PrismaticRD_PTOP.Recipes; using ILSOS_PrismaticRD_PTOP.Sequences; using System; using System.Collections.Generic; using System.Linq; using System.Reactive.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Threading; using VisionCommon; using WeldingCommon; using WeldingCommon.Alarm; using WeldingCommon.Data; using WeldingCommon.Devices; using WeldingCommon.Popup; using WeldingCommon.Sequences; using WeldingCommon.Sequences.Main; using WeldingCommon.Settings; using WeldingCommon.Utils; using WeldingCommon.Views; using static ILSOS_PrismaticRD_PTOP.Device.PLCControllerPTOP; using static WeldingCommon.Sequences.Main.SeqExecutePlcBitInterface_InitParam; using static WeldingCommon.Views.UCCommonAutoRunViewModel; using ILSOS_PrismaticRD_PTOP.Sequences; namespace ILSOS_PrismaticRD_PTOP.Views { public class UCMainAutoRunViewModel : UCCommonAutoRunViewModel { private readonly Logger Log = new Logger("UCMainAutoRunViewModel"); public ICommand CMD_VisibleChanged { get; } public ICommand CMD_RunSequenceAutoRun { get; } public ICommand CMD_StopSequenceAutoRun { get; } public ICommand CMD_Reset { get; } // Totoal Product, Today Product public ICommand CMD_AutoRunResume { get; } // 260621: bit15 모드전환 후 Auto 이어하기(Resume) private readonly IRecipeManager _RecipeManager; private readonly ISettingEntry _SettingEntry; private readonly IAlarmService _AlarmService; private SystemParameter _SystemParameter; private readonly Scanner _Scanner; private readonly PLCControllerPTOP _Plc; private ISequence _SeqAutoRun; // TestRun으로 우선 확인 후 private ISequence _SeqPlcInterface; private ISequence _SeqQTest; // 260605 현장 SeqAutoRunPTOP private ISequence _SeqAutoRunPTOP; public bool IsSequenceRunning_AutoRun { get => _IsSequenceRunning_AutoRun; set => SetProperty(ref _IsSequenceRunning_AutoRun, value); } private bool _IsSequenceRunning_AutoRun; public bool IsAlarm { get => _IsAlarm; set => SetProperty(ref _IsAlarm, value); } private bool _IsAlarm; private DateTime _NowDateTime; public DateTime NowDateTime { get => _NowDateTime; set => SetProperty(ref _NowDateTime, value); } public int CurrentPageNum { get => _CurrentPageNum; set => SetProperty(ref _CurrentPageNum, value); } private int _CurrentPageNum; // 260621: bit15(MODECHANGE) 팝업 — 상승엣지 래치 + 재진입 가드 private bool _prevBit15 = false; private bool _isPopupOpen = false; // 260621: bit15 살아있을 때만 [AutoRun Resume] 버튼 표시 public bool IsModeChangeActive { get => _IsModeChangeActive; set => SetProperty(ref _IsModeChangeActive, value); } private bool _IsModeChangeActive; public WeldingData CurrentWeldingData { get => Status.GetProcessingWeldingData(); } private readonly DispatcherTimer _MonitorTimer; public UCMainAutoRunViewModel(IAuroraEnvironment aurora, string sequenceId) : base(aurora, sequenceId) { _RecipeManager = _Aurora.RecipeManager; _SettingEntry = _Aurora.SettingEntry; _AlarmService = _Aurora.AlarmService; _SystemParameter = _Aurora.SettingEntry[CommonSettingIds.SystemParameter] as SystemParameter; _Scanner = _Aurora.DeviceManager[CommonDeviceIds.Scanner] as Scanner; _Plc = _Aurora.DeviceManager[CommonDeviceIds.PLCDevice] as PLCControllerPTOP; // MainViewModel에서 RunStateChanged를 설정해준다면, ManualView 상태에서도 해당 이벤트가 호출될지.. 호출될 듯 하긴 함. _SeqAutoRun = _Aurora.SequenceEntry[SequenceIds.SeqAutoRun]; _SeqAutoRunPTOP = _Aurora.SequenceEntry[SequenceIds.SeqAutoRunPTOP]; // 260605 // Autorun과 Status 변경(Manual, Auto 변경) 시 사용하는 시퀸스ID가 다르도록 설정. 같을 경우, 문제 발생. _SeqPlcInterface = _Aurora.SequenceEntry[SequenceIds.SeqExecutePlcBitInterfaceForStatusChange]; // Q Test _SeqQTest = _Aurora.SequenceEntry[SequenceIds.SeqEventQTest]; InitVariable(); CMD_VisibleChanged = new DelegateCommand(VisibleChanged); CMD_RunSequenceAutoRun = new DelegateCommand(OnAutoRunStartRequest); // 260511 이곳 시퀸스 CMD_StopSequenceAutoRun = new DelegateCommand(OnStopSequenceAutoRun); CMD_Reset = new DelegateCommand(PerformCmdReset); CMD_AutoRunResume = new DelegateCommand(OnAutoRunResume); _MonitorTimer = new DispatcherTimer(); _MonitorTimer.Tick += OnMonitoring; _MonitorTimer.Interval = TimeSpan.FromMilliseconds(100); //이벤트로 AUTORUN 스타트 요청에 대한 메소드 실행 오면 그때 동작함. 시퀸스 돔 _Aurora.EventObservable.AsObservable(CommonEventIds.EVENT_AUTO_RUN_START_REQUEST) .ObserveOnDispatcher() .Subscribe(x => OnAutoRunStartRequest()); // Stop 요청 시 정지 로직 실행 _Aurora.EventObservable.AsObservable(CommonEventIds.EVENT_AUTO_RUN_STOP_REQUEST) .ObserveOnDispatcher() .Subscribe(x => OnStopSequenceAutoRun()); _Aurora.EventObservable.AsObservable(CommonEventIds.EVENT_EQUIPMENT_STATUS_CHANGE) .Subscribe(x => IsSequenceRunning_AutoRun = Status.EquipmentStatus == eEqpStatus.Auto); Observable.Merge( _Aurora.EventObservable.AsObservable(AuroraSysEvent.SYS_ALARM_SET_ALARM), _Aurora.EventObservable.AsObservable(AuroraSysEvent.SYS_ALARM_RESET_ALARM), _Aurora.EventObservable.AsObservable(AuroraSysEvent.SYS_ALARM_RESET_ALL_ALARM)) .Subscribe(x => IsAlarm = AlarmManager.HasAlarmLevelHigh()); _Aurora.EventObservable.AsObservable(CommonEventIds.EVENT_CHANGE_RECIPE_INFO) .ObserveOnDispatcher() .Select(param => (RecipeInfo)param.Param) .Subscribe(OnUpdateCurrentActivatedRecipe); _Aurora.EventObservable.AsObservable(CommonEventIds.EVENT_AUTO_RUN_UPDATE_PAGENUM) .ObserveOnDispatcher() .Subscribe(x => OnAutoRunUpdatePageNum(x)); Status.SetWorkingPosition(eWorkingPosition.NONE); } private void InitVariable() { NowDateTime = DateTime.Now; if (NowDateTime.Day != _SystemParameter.LastProductTime.Day) InitializeTodayCount(); CurrentWeldingData.TodayProduct = _SystemParameter.TodayProductCount; CurrentWeldingData.TotalProduct = _SystemParameter.TotalProductCount; CurrentWeldingData.AutoRunStartTime = default(DateTime); CurrentWeldingData.WeldingEndTime = _SystemParameter.LastProductTime; CurrentWeldingData.WeldingSpanTimeMilliseconds = "0"; CurrentWeldingData.LotId = "Default_Lot_Id"; CurrentWeldingData.CellId = "Default_Cell_Id"; } private void OnUpdateCurrentActivatedRecipe(RecipeInfo info) { // Activate Layer 불러오기 RecipeHelper.OnActiveReloaded(); // Page UI 업데이트 RecipeHelper.UpdateAutorunRecipe(); } private void OnAutoRunUpdatePageNum(EventMessage x) { if (x.Param is int pageIdx) { CurrentPageNum = pageIdx + 1; } } private void VisibleChanged(object obj) { bool visible = (bool)obj; if (visible) { _MonitorTimer.Start(); } else { _MonitorTimer.Stop(); } } private void OnMonitoring(object sender, EventArgs e) { // 켜져있는 상태에서는 24시 넘어갈 때만 초기화 if (NowDateTime.Day != DateTime.Now.Day) InitializeTodayCount(); NowDateTime = DateTime.Now; // 260621: PLC bit15(MODECHANGE) 상승엣지 → Recipe/Page 확인 팝업 → Auto→Manual 잠시 전환 if (_Plc == null || _Plc.IsSimul) return; // 음극/양극 어느 쪽이 줘도 트리거 (AutoRun이 두 극을 함께 쓰는 구조와 동일) bool bit15 = _Plc.ReadBit(eBitInput.PLC_MODE_CHANGE).data || _Plc.ReadBit(eBitInput.PLC_MODE_CHANGE_P).data; IsModeChangeActive = bit15; // bit15 살아있을 때만 [AutoRun Resume] 버튼 표시 if (bit15 && !_prevBit15 && !_isPopupOpen) { short r = _Plc.ReadWord(eWordInput.PLC_RECIPE_NO).data; short p = _Plc.ReadWord(eWordInput.PLC_PAGE_NO).data; ShowRecipePageConfirmAndSwitchManual(r, p); } _prevBit15 = bit15; } // 260621: bit15 모드전환 — 정지(상태보존) → 안내 팝업 → Auto→Manual (작업자 가변 용접 수동 진행) private void ShowRecipePageConfirmAndSwitchManual(short recipeNo, short pageNo) { _isPopupOpen = true; try { // 정지 시 에코 워드(PC_RECIPE_NO/PC_PAGE_NO)·활성 레시피 보존 (OnSeqActionStop이 워드 0 안 씀) SeqAutoRunPTOP.ResumeMode = true; if (_SeqAutoRunPTOP.RunState == SequenceRunState.Running) _SeqAutoRunPTOP.Stop(); // 정지 후 안내 팝업 (OK-only 모달, [확인]까지 블로킹). FindStrToken 우회 위해 MessageDialog 직접 호출. string msg = $"Recipe No : {recipeNo}\nPage No : {pageNo}\n\n용접 위치 확인해주세요."; Aurora.Api.Control.MessageDialog.Show(Application.Current.MainWindow, "용접 위치 확인", msg); // [확인] 후 Auto → Manual (레시피는 이미 활성 → 재활성/롤백 안 함). 작업자가 가변 용접 직접 진행. Status.SetStatus(eEqpStatus.Manual); _Plc.WriteBit(eBitOutput.PC_AUTO_STATUS, false); _Plc.WriteBit(eBitOutput.PC_AUTO_STATUS_P, false); Log.I($"[Bit15] ModeChange → 정지(보존)·Manual 전환 (Recipe:{recipeNo}, Page:{pageNo})"); } finally { _isPopupOpen = false; } } // 260621: [AutoRun Resume] — PC→PLC bit15 ON + Auto 이어하기 (ResumeMode 유지 → 핸드셰이크 스킵) private void OnAutoRunResume(object obj = null) { if (_Plc == null) return; if (!SeqAutoRunPTOP.ResumeMode) { MessageUtil.ShowWarningToast("Resume 상태가 아닙니다."); return; } // PC→PLC bit15(MODECHANGE) ON — resume run 신호 (한 사이클 완료 후 시퀀스가 OFF/해제) _Plc.WriteBit(eBitOutput.PC_MODE_CHANGE, true); _Plc.WriteBit(eBitOutput.PC_MODE_CHANGE_P, true); // Auto 복귀 (ResumeMode=true → CheckInitCondition에서 핸드셰이크 스킵하고 업로드→용접) _Plc.WriteBit(eBitOutput.PC_AUTO_STATUS, true); _Plc.WriteBit(eBitOutput.PC_AUTO_STATUS_P, true); Status.GetProcessingWeldingData().AutoRunStartTime = DateTime.Now; RunSequenceAutoRun(); Log.I("[Bit15] AutoRun Resume → PC_MODE_CHANGE ON + Auto 이어하기(핸드셰이크 스킵)"); } // PLC 비트에 의한 AutoRun 실행의 경우, PLC 인터페이스 이후 AutoRun 필요 public override async void OnAutoRunStartRequest(object obj = null) { if (!ApproveAutorunByUserOrAuto()) { return; } SeqAutoRunPTOP.ResumeMode = false; // 일반 시작은 항상 핸드셰이크 포함 (resume 아님) base.OnAutoRunStartRequest(); #region 팝업 띄우는 창과 함께 Bit false bool IsStart = await TimerMessageBox.ShowAsync(Application.Current.MainWindow, "운전 시작", "자동 운전을 시작하시겠습니까?", true, 5000); if (!IsStart) { _Plc.WriteBit(eBitOutput.PC_AUTO_STATUS, false); _Plc.WriteBit(eBitOutput.PC_AUTO_STATUS_P, false); //양극 return; } #endregion _Plc.WriteBit(eBitOutput.PC_AUTO_STATUS, true); // 음극 _Plc.WriteBit(eBitOutput.PC_AUTO_STATUS_P, true); //양극 // PLC에선 Auto Req 받으면 바로 살려주도록 Status.GetProcessingWeldingData().AutoRunStartTime = DateTime.Now; RunSequenceAutoRun(); } private void RunSequenceAutoRun() { // 자동으로 Activated Recipe로 변경 OnUpdateCurrentActivatedRecipe(null); //var param = new SeqAutoRunPrismatic_InitParam() //{ // VisionRecipeNo = 0 //}; var param = new SeqTeachingRun_InitParam() { IsObjectSelectAll = true, }; Status.SetStatus(eEqpStatus.Auto); //_SeqAutoRun.Start(param, OnCompleteAutoRun); // 260605 _SeqAutoRunPTOP.Start(param, OnCompleteAutoRunPTOP); } private void OnCompleteAutoRun(SequenceActionResult res) { if (res != null) { if (res.Param is SeqAutoRunPrismatic_OutParam result && result.Success == true) { MessageUtil.ShowSuccessToast(string.Format("Success SeqAutoRunPrismatic")); return; } } MessageUtil.ShowWarningToast(string.Format("Fail SeqAutoRunPrismatic")); } #region 260605 private void OnCompleteAutoRunPTOP(SequenceActionResult res) { if (res != null) { // OutParam 타입을 SeqAutoRunPTOP_OutParam으로 수정 if (res.Param is SeqAutoRunPTOP.SeqAutoRunPTOP_OutParam result && result.Success == true) { MessageUtil.ShowSuccessToast(string.Format("Success SeqAutoRunPTOP")); return; } } MessageUtil.ShowWarningToast(string.Format("Fail SeqAutoRunPTOP")); } #endregion private bool ApproveAutorunByUserOrAuto() { // [테스트용] 알람/인터락 체크 일시 주석 처리 /* bool boolian = AlarmManager.HasAlarmLevelHigh(); if (boolian) { Log.E($"Clear Heavy Alarms For Welding!"); MessageUtil.ShowWarningToast(string.Format("Clear Heavy Alarms For Welding!")); return false; } boolian = AlarmManager.IsPlcDoorOpen(); if (boolian) { Log.E($"PLC Door Opened!! Can't Weld Now."); MessageUtil.ShowWarningToast(string.Format("Close Door ! Clear PLC DoorInterlock!")); return false; } boolian = AlarmManager.IsPlcEmoAlarm(); if (boolian) { Log.E($"PLC EMO Pushed!! Can't Weld Now."); MessageUtil.ShowWarningToast(string.Format("Clear PLC EMO Status!")); return false; } if (VisionRecipeManager.GetFovInfos().Count == 0) { MessageUtil.ShowWarningToast(string.Format("Please check the Vision Recipe of the currently active Recipe.")); return false; } */ if (_SeqAutoRunPTOP.RunState == SequenceRunState.Running) { MessageUtil.ShowWarningToast(string.Format("이미 시퀀스가 Start 상태입니다.", "SeqAutoRunPTOP"), Log); return false; } return true; } public override void OnAutoRunStopRequest() { base.OnAutoRunStopRequest(); //// Prewelding은 자동 운전 정지 와도 무시 //StopSequenceAutoRun(); } private void OnStopSequenceAutoRun(object obj = null) { //if (_SeqAutoRun.RunState == SequenceRunState.Running) // _SeqAutoRun.Stop(); if (_SeqAutoRunPTOP.RunState == SequenceRunState.Running) _SeqAutoRunPTOP.Stop(); Status.SetStatus(eEqpStatus.Manual); _Plc.WriteBit(eBitOutput.PC_AUTO_STATUS, false); _Plc.WriteBit(eBitOutput.PC_AUTO_STATUS_P, false); //Recipe를 기존에 선택된 Recipe로 Rollback 해 줌 RecipeHelper.RollbackRecipe(); //AutoRun 정지 시 CorrectionFile을 Vision으로 변경 _Scanner.SelectCorrectionTable(1); Log.I("Stop AutoRun Requested and Cleaned up."); } private void PerformCmdReset(object commandParameter) { // SystemParameter를 저장하기 전에는 반드시 업데이트를 해주어야 한다. _SystemParameter = _Aurora.SettingEntry[CommonSettingIds.SystemParameter] as SystemParameter; switch ((string)commandParameter) { case "TotalProduct": { CurrentWeldingData.TotalProduct = 0; _SystemParameter.TotalProductCount = CurrentWeldingData.TotalProduct; Status.GetProcessingWeldingData().TotalProduct = 0; _SettingEntry.Save(CommonSettingIds.SystemParameter, _SystemParameter, "UCMainAutoRunViewModel"); } break; case "TodayProduct": { CurrentWeldingData.TodayProduct = 0; _SystemParameter.TodayProductCount = CurrentWeldingData.TodayProduct; Status.GetProcessingWeldingData().TodayProduct = 0; _SettingEntry.Save(CommonSettingIds.SystemParameter, _SystemParameter, "UCMainAutoRunViewModel"); } break; default: return; } } private void InitializeTodayCount() { // SystemParameter를 저장하기 전에는 반드시 업데이트를 해주어야 한다. // SystemParameter에 저장된 값 불러오기 _SystemParameter = _Aurora.SettingEntry[CommonSettingIds.SystemParameter] as SystemParameter; CurrentWeldingData.TodayProduct = 0; _SystemParameter.TodayProductCount = CurrentWeldingData.TodayProduct; _SettingEntry.Save(CommonSettingIds.SystemParameter, _SystemParameter, "UCMainAutoRunViewModel"); } } enum InputType { keyboardInput, ClickInput } }