using Aurora; using Aurora.Api; using Aurora.Api.Manager; using Aurora.View; using AuroraIO; using AuroraLaserScanner; using AuroraPLC; using CoreUnit.Control.UserControl.Canvas; using CoreUnit.Scanner.UserControl.ScannerCanvas; using ILSOS_PrismaticRD_PTOP.Device; using System; using System.Collections.Generic; using System.Linq; using System.Threading; using VisionCommon; using WeldingCommon; using WeldingCommon.Devices; using WeldingCommon.Devices.Laser; using WeldingCommon.Sequences; using WeldingCommon.Utils; using WeldingCommon.Alarm; using ILSOS_PrismaticRD_PTOP.Alarm; using ILSOS_PrismaticRD_PTOP.Settings; using static AuroraIO.AJINEXTEK.AjinIO.IOModuleInfo; using static ILSOS_PrismaticRD_PTOP.Device.PLCControllerPTOP; using static WeldingCommon.Devices.PLCController; using Application = System.Windows.Application; using SystemParameter = WeldingCommon.Settings.SystemParameter; namespace ILSOS_PrismaticRD_PTOP.Sequences { /* * * [Sequence_Enter] 시퀀스 진입 - 레시피 초기화 및 장비 연결 [CheckInitCondition] 조건 확인 완료 → CheckPageNo 이동 [CheckPageNo] PLC_PAGE_CHANGE_REQ 신호 대기 중... [CheckPageNo] PLC_PAGE_CHANGE_REQ ON 감지 → CheckPageNoComp 이동 [CheckPageNoComp] 페이지 확정 - PC_PAGE_NO: [1] 에코 완료 [SetUploadWeldingList] Group Count: [2], Total Line Count: [34] [WeldingStartReadBit] PLC_WELD_READY_REQ ON 감지 → WaitWeldingStart 이동 [WaitWeldingStart] READY_ACK/BUSY ON, COMPLETE OFF 완료 [ExecuteWelding] Laser Gate ON (Core+Ring) [ExecuteWelding] Gate ON Delay 15ms 완료 → ExecuteList 시작 [ExecuteWelding] WaitForListTaskCompleted 완료 [ExecuteWelding] Laser Gate OFF (Core+Ring) [ExecuteWelding] 용접 완료 - TactTime: [285ms] [CheckCycleStop] 사이클 계속 진행 → CheckPageNo 이동 * * */ class SeqAutoRunPTOP { static readonly Logger Log = new Logger("SeqAutoRunPTOP"); static readonly IAuroraEnvironment aurora; // 완료 const string SEQ_ENTER = "[SeqAutoRunPTOP] SEQ_ENTER"; // inputParam 으로 받자. const string SEQ_CHECK_INIT_CONDITION = "[SeqAutoRunPTOP] Check Init Condition"; // Check Init Condition const string SEQ_CHECK_RECIPENO_COMPLETE = "[SeqAutoRunPTOP] CHECK RecipeNo Complete"; // 260618 Recipe Change const string SEQ_CHECK_PAGENO = "[SeqAutoRunPTOP] CHECK PageNo"; const string SEQ_CHECK_PAGENO_COMPLETE = "[SeqAutoRunPTOP] CHECK PageNo Complete"; // 260513 const string SEQ_CHECK_WELDING_START_READ_BIT = "[SeqAutoRunPTOP] Check Welding Start ReadBit"; // 26 05 13 const string SEQ_WAIT_WELDING_START = "[SeqAutoRunPTOP] Wait Welding Start"; #region 티칭런에 있던 주요 노드 복사 const string SEQ_SET_PARAMS_UPLOAD_WELDING_LIST_SEQ = "[SeqAutoRunPTOP] Set Sequence Param (UploadWeldingList Seq)"; const string SEQ_DO_ID_UPLOAD_WELDING_LIST_SEQ = "[SeqAutoRunPTOP] Process (UploadWeldingList Seq)"; const string SEQ_GET_RESULT_UPLOAD_WELDING_LIST_SEQ = "[SeqAutoRunPTOP] Get Sequence Result (UploadWeldingList Seq)"; #endregion const string SEQ_EXECUTE_WELDING = "[SeqAutoRunPTOP] Execute Welding"; const string SEQ_CHECK_CYCLE_STOP = "[SeqAutoRunPTOP] Check Cycle Stop"; const string SEQ_SAVE_RESULT_DATA = "[SeqAutoRunPTOP] Save Result Data"; const string SEQ_WRITE_COMPLETION_PLC_BIT = "[SeqAutoRunPTOP] Write Completion PLC Bit"; const string SEQ_COMPLETE = "[SeqAutoRunPTOP] SEQ_COMPLETE"; // 완료 public enum ePrismaticDOAddress { CoreLaser_Control = 9, CoreLaser_ErrorClearing, RingLaser_Control = 13, RingLaser_ErrorClearing, EX_Red_Control = 17, } static class ContextKey { public const string KEY_SEQUENCE_RESULT = "KEY_SEQUENCE_RESULT_SeqAutoRunPTOP"; public const string KEY_SEQUENCE_START_TIME = "KEY_SEQUENCE_START_TIME_SeqAutoRunPTOP"; } public IAuroraEnvironment AuroraEnv { get; set; } // 장비 3개만 initDev private Scanner _Scanner; private PLCControllerPTOP _PLC; //private PLCController _PLC; private IODevice _IO; private SystemParameter _SystemParameter; private IRecipeManager _RecipeManager; private ScannerCanvasUserCtrl _ScannerCanvasUserCtrl; //스캐너 동작관련 객체 선언 private SeqTeachingRun_InitParam _InitParam; // Enter에 기본으로 적용할 Param // const int PLC_TIMEOUT = 60; // sec, Timeout은 시퀀스 정지가 아니라 타임아웃 시간마다 로그만 남기도록 한다 private eListNo _WeldingScannerListNo; private List _SelectedObjectList; private static TimeSpan _WeldingSpanTime; //TeacingRun 시퀀스에서 용접 소요시간 private int _CurrentPageNo; private short plcPageNo = 0;// [0] 페이지 1 =음극 A [1]페이지 2= 양극 P(C) private List plcPageNoList = new List(); private short plcRecipeNo = 0; // 260618 PLC Recipe No (11/21/22/31/32) int laserEmissionGateDelayMs = 15; // 260518 public SequenceBuilder Builder() { var sb = new SequenceBuilder(100000, true); sb.AddAction(SEQ_ENTER, Sequence_Enter); sb.AddDecision(SEQ_CHECK_INIT_CONDITION, CheckInitCondition); //분기 sb.AddAction(SEQ_CHECK_PAGENO, CheckPageNo); // 단일 트리거: PLC_PAGE_CHANGE_REQ(0x08) 대기 sb.AddAction(SEQ_CHECK_RECIPENO_COMPLETE, CheckRecipeNoComp); // Recipe 먼저 확정/전환 (Page보다 상위) sb.AddAction(SEQ_CHECK_PAGENO_COMPLETE, CheckPageNoComp); // 그다음 Page 확정 #region sb.AddAction(SEQ_SET_PARAMS_UPLOAD_WELDING_LIST_SEQ, SetUploadWeldingList_SeqParams); sb.AddSubsequence(SEQ_DO_ID_UPLOAD_WELDING_LIST_SEQ, CommonSequenceManager.GetSeqBuilder_SeqUploadWeldingList()); sb.AddAction(SEQ_GET_RESULT_UPLOAD_WELDING_LIST_SEQ, GetUploadWeldingList_SeqResult); #endregion // 260513 plc sb.AddAction(SEQ_CHECK_WELDING_START_READ_BIT, WeldingStartReadBit); sb.AddAction(SEQ_WAIT_WELDING_START, WaitWeldingStart); sb.AddAction(SEQ_EXECUTE_WELDING, ExecuteWelding); sb.AddDecision(SEQ_CHECK_CYCLE_STOP, CheckCycleStop); // 체크페이지 sb.AddAction(SEQ_SAVE_RESULT_DATA, SaveResultData); // 해당 공정의 정보를 기록 함. sb.AddAction(SEQ_WRITE_COMPLETION_PLC_BIT, WriteCompletionPLCBit); // 정상적으로 종료 되면, complete bit write sb.AddAction(SEQ_COMPLETE, Complete); // Enter로 올린다. sb.Flows(sb.Init > sb[SEQ_ENTER] > sb[SEQ_CHECK_INIT_CONDITION] > sb[SEQ_CHECK_PAGENO] // 단일 트리거 PLC_PAGE_CHANGE_REQ(0x08) ON 대기 > sb[SEQ_CHECK_RECIPENO_COMPLETE] // Recipe 먼저 확정/전환 > sb[SEQ_CHECK_PAGENO_COMPLETE] // 그다음 Page 확정 #region TeachingRun 시퀸스의 중간 용접리스트 생성 구간 > sb[SEQ_SET_PARAMS_UPLOAD_WELDING_LIST_SEQ] // 1 파라미터 세팅 - TeachingRun 노드 참고 > sb[SEQ_DO_ID_UPLOAD_WELDING_LIST_SEQ] // 2 리스트 업로드 (서브시퀀스) - TeachingRun 노드 참고 > sb[SEQ_GET_RESULT_UPLOAD_WELDING_LIST_SEQ] // 3 업로드 결과 확인 - TeachingRun 노드 참고 #endregion > sb[SEQ_CHECK_WELDING_START_READ_BIT] // while read bit > sb[SEQ_WAIT_WELDING_START] // 4 업로드 완료 PLC 완료 신호 및 BUSY 신호로 변경 > sb[SEQ_EXECUTE_WELDING] // 5 Scanner 실행 (실제 용접) > sb[SEQ_CHECK_CYCLE_STOP] > sb[SEQ_SAVE_RESULT_DATA] > sb[SEQ_WRITE_COMPLETION_PLC_BIT] // PLC 비트 완료 신호 보내기 > sb[SEQ_COMPLETE] // 다시 enter로 올리기 > sb.Final); sb.Flows(sb[SEQ_CHECK_CYCLE_STOP] > sb[SEQ_CHECK_PAGENO]); // 루프백: 매 사이클 Page Change bit(0x08) 대기부터 재확인 sb.Flows(sb[SEQ_CHECK_INIT_CONDITION] > sb[SEQ_COMPLETE] > sb.Final); sb.OnStopped(OnStopped); sb.OnForceError(OnForceError); sb.Draw("SeqAutoRunPTOP.pdf"); return sb; } private bool InitSequenceVariable(SequenceContext context) { Log.I($"[InitSequenceVariable] 노드 Start"); var mainPage = CommonHelper.GetMainPage(); _ScannerCanvasUserCtrl = mainPage.ScannerCanvasCtrlTeaching; _WeldingScannerListNo = eListNo.LIST1; if (_InitParam.IsObjectSelectAll) { //_SelectedObjectList = _ScannerCanvasUserCtrl.Model.GetLayer(_CurrentPageNo).Objects; _SelectedObjectList = _ScannerCanvasUserCtrl.Model.ActiveLayer.Objects; } else { _SelectedObjectList = _ScannerCanvasUserCtrl.Canvas.Model.SelectedObjects; } //Status.GetProcessingWeldingData().WeldingScannerListNo = _WeldingScannerListNo; //Status.GetProcessingWeldingData().SelectedDrawObjectList = _SelectedObjectList; Log.I($"[InitSequenceVariable] 노드 End"); return true; } public SeqAutoRunPTOP() { _SystemParameter = Application.Current.AuroraEnv().SettingEntry[CommonSettingIds.SystemParameter] as SystemParameter; } private SequenceActionResult Sequence_Enter(SequenceContext context, string id, SequenceActionResult param) { Log.I($"[Sequence_Enter] 시퀀스 진입 - 레시피 초기화 및 장비 연결"); // 디버그 끝내고 삭제 MessageUtil.ShowToast("Sequence_Enter"); //Thread.Sleep(1000); // 260618 RecipeManager 미리 확보(Recipe Change 노드 CheckRecipeNoComp에서 사용) _RecipeManager = Application.Current.AuroraEnv().RecipeManager; // 활성화된레시피ID var activatedRecipeId = _RecipeManager.ActivatedRecipe().Id; var recipeName = RecipeHelper.SearchRecipeItem(activatedRecipeId); _InitParam = param.Param as SeqTeachingRun_InitParam; // 260511 - param NULL Status.GetProcessingWeldingData().AutoRunStartTime = DateTime.Now; Status.GetProcessingWeldingData().Clear(); Log.I($"[Sequence_Enter] 레시피 [{activatedRecipeId}], {recipeName.ToString()} 활성화, 장비 초기화 완료"); //레시피의 현재 변수 false인지 여부 확인할 것 . #region [TEST_BYPASS] Alarm Check - Init Variable /* if (InitSequenceVariable(context) == false) { AlarmManager.Alarm(AlarmCode.ALARM_SEQUENCE_TeachingRun_InitVariableError); return null; } */ InitSequenceVariable(context); #endregion // 장비 체크 InitDev(context); _PLC.WriteBit(eBitOutput.PC_AUTO_STATUS, true); // 음극 _PLC.WriteBit(eBitOutput.PC_AUTO_STATUS_P, true); //양극 #region [TEST_BYPASS] Alarm Check - DeFvice Ready /* if (!CheckDeviceReady(context)) { //Check Device AlarmManager.Alarm(AlarmCode.ALARM_SEQUENCE_AutoRun_DeviceNotReady); Log.E("The devices are not active."); return null; } */ #endregion Log.I($"[Sequence_Enter] 장비 초기화 완료"); Thread.Sleep(5); // 추후 제거 Log.I($"[Sequence_Enter] 완료 → CheckInitCondition 이동"); return SequenceAction.Ok(); } private string CheckInitCondition(SequenceContext context, string id, ICollection branches, SequenceActionResult param) { Log.I($"[CheckInitCondition] 초기 조건 확인 중"); Status.SetWorkingPosition(eWorkingPosition.CHECKING_CONDITION); //if (!CheckCondition(context)) //{ // AlarmManager.Alarm(AlarmCode.ALARM_SEQUENCE_AutoRun_CheckInitCondition); // Log.E("Condition is not in state to start sequence."); // context.PutValue(ContextKey.KEY_SEQUENCE_RESULT, false); // return SEQ_COMPLETE; //} Thread.Sleep(5); Log.I($"[CheckInitCondition] 조건 확인 완료 → CheckPageNo 이동"); return SEQ_CHECK_PAGENO; } private int _CenterBeamPower; private int _RingBeamPower; private int _CenterRingRatio; // CheckRecipeNoComp: 단일 트리거(PAGE_CHANGE_REQ 0x08) 통합 — Page보다 먼저 Recipe를 확정/전환. // PLC_RECIPE_NO 1회 읽기 → 0/미등록/현재활성과 동일이면 전환·워드 재기록 생략(시퀀스 정지 없음). // 실제로 다를 때만 ActivateRecipe + UI 스레드 동기 reload(Model.Layers 갱신 보장) + PC_RECIPE_NO 에코. private SequenceActionResult CheckRecipeNoComp(SequenceContext context, string id, SequenceActionResult param) { // Recipe No 1회 읽기 (Page Change bit와 함께 래치되어 안정적이라고 가정 → 노이즈 검증 생략) plcRecipeNo = _PLC.ReadWord(eWordInput.PLC_RECIPE_NO).data; Log.I($"[CheckRecipeNoComp] PLC_RECIPE_NO: [{plcRecipeNo}]"); // 0이면 레시피 전환 의사 없음 → 현 Recipe 유지하고 Page로 if (plcRecipeNo == 0) { Log.I("[CheckRecipeNoComp] PLC_RECIPE_NO=0 → 레시피 전환 생략, 현 Recipe 유지 → CheckPageNoComp 이동"); return SequenceAction.Ok(); } // 등록 레시피 조회(.Id 1:1, 하드코딩 금지). 미등록이면 전환 생략(정지 안 함) var recipe = _RecipeManager.Recipes().FirstOrDefault(x => x.Id == plcRecipeNo); if (recipe == null) { Log.W($"[CheckRecipeNoComp] {plcRecipeNo}번 레시피 미등록 → 전환 생략, 현 Recipe 유지"); return SequenceAction.Ok(); } // 이미 활성 레시피면 재활성화·워드 재기록 생략 (매 페이지마다 PC_RECIPE_NO 워드 변경 금지) var preActivated = _RecipeManager.ActivatedRecipe(); if (preActivated != null && preActivated.Id == recipe.Id) { Log.I($"[CheckRecipeNoComp] 이미 활성 레시피({recipe.Id}) → 재활성화 생략 → CheckPageNoComp 이동"); return SequenceAction.Ok(); } // ── 실제 전환 ── _RecipeManager.ActivateRecipe(recipe.Id, "PLC Recipe Change"); Log.I($"[CheckRecipeNoComp] ActivateRecipe ({(preActivated == null ? -1 : preActivated.Id)} -> {recipe.Id})"); // ActivatedRecipe().Id 반영 대기 (비동기 race 주의) var actStart = DateTime.Now; while (!context.Stopped) { var cur = _RecipeManager.ActivatedRecipe(); if (cur != null && cur.Id == recipe.Id) break; if (DateTime.Now.Subtract(actStart).TotalSeconds > PLC_TIMEOUT) { Log.W("[CheckRecipeNoComp] ActivateRecipe 반영 대기 TIMEOUT..."); actStart = DateTime.Now; } Thread.Sleep(10); } if (context.Stopped) return null; // ★ Model.Layers를 새 레시피로 동기 갱신 (UI 스레드). // ActivateRecipe의 이벤트 체인(ObserveOnDispatcher 2단)은 비동기라 Page 검증 전 Layers 갱신을 보장 못 함. // RecipeHelper.OnActiveReloaded()가 ReloadRecipe를 동기 수행 → 반환 시점에 Layers가 새 레시피로 보장됨. Application.Current.Dispatcher.Invoke(() => RecipeHelper.OnActiveReloaded()); Log.I("[CheckRecipeNoComp] Model.Layers 동기 reload 완료 (UI thread)"); // 에코 (실제 전환 시에만). PC_RECIPE_CHANGE_COMPLETE는 미사용(PLC가 감시 안 함). _PLC.WriteWord(eWordOutput.PC_RECIPE_NO, plcRecipeNo); Log.I($"---> [CheckRecipeNoComp] Recipe 전환 완료 - Set PC_RECIPE_NO: [{plcRecipeNo}] → CheckPageNoComp 이동"); return SequenceAction.Ok(); } // 페이지 체크 - 페이지만 감시 AutoRunStatus는 실제 상위에서 동작 체크하고 시퀸스 동작한다. private SequenceActionResult CheckPageNo(SequenceContext context, string id, SequenceActionResult param) { Log.I($"[CheckPageNo] PLC_PAGE_CHANGE_REQ 신호 대기 중..."); Status.SetWorkingPosition(eWorkingPosition.LOADING_CELL); // 디버그 끝내고 삭제 //MessageUtil.ShowToast("CheckPageNo -> Wait..."); //Thread.Sleep(1000); // 1. PLC_PAGE_CHANGE_REQ (bit 8) ON 대기 var interfaceStartTime = DateTime.Now; while (!context.Stopped) { //plc if (_PLC == null) { Log.E("[CheckPageNo] _PLC instance is null! Handshake cannot proceed."); return null; } if (_PLC.ReadBit(eBitInput.PLC_PAGE_CHANGE_REQ).data) { Log.I("---> [CheckPageNo] Check OK -> PLC_PAGE_CHANGE_REQ : ON"); // 임시 미리 한 번 더 읽기 260515 //plcPageNo = _PLC.ReadWord(eWordInput.PLC_PAGE_NO).data; //Log.I($"---> [CheckPageNo] Check OK -> PLC_PAGE_CHANGE_REQ : {plcPageNo}"); // 디버그 끝내고 삭제 //MessageUtil.ShowToast("PLC_PAGE_CHANGE_REQ : ON"); //Thread.Sleep(1000); break; } else { //Log.I("[CheckPageNo] Detected PLC_PAGE_CHANGE_REQ: f"); } if (DateTime.Now.Subtract(interfaceStartTime).TotalSeconds > PLC_TIMEOUT) { Log.W("[CheckPageNo] Waiting for PLC_PAGE_CHANGE_REQ -> TIMEOUT..."); interfaceStartTime = DateTime.Now; } Thread.Sleep(10); } if (context.Stopped) return null; Log.I($"[CheckPageNo] PLC_PAGE_CHANGE_REQ ON 감지 → CheckPageNoComp 이동"); return SequenceAction.Ok(); } private SequenceActionResult CheckPageNoComp(SequenceContext context, string id, SequenceActionResult param) { Log.I($"[CheckPageNoComp] PLC 페이지 번호 읽기 시작 (3회 연속 검증)"); Status.SetWorkingPosition(eWorkingPosition.LOADING_CELL); int checknum = 0; plcPageNoList = new List(); while (!context.Stopped) { //Thread.Sleep(1000); // 2. PLC 데이터 읽기 및 인덱스 변환 (1,2 -> 0,1) //plcPageNo = _PLC.ReadWord(eWordInput.PLC_PAGE_NO).data; plcPageNoList.Add(_PLC.ReadWord(eWordInput.PLC_PAGE_NO).data); plcPageNo = plcPageNoList[checknum]; checknum++; _CurrentPageNo = plcPageNo - 1; // 1-based to 0-based // page index // 디버그 끝내고 삭제 //MessageUtil.ShowToast($"ReadWord -> {plcPageNo}"); Thread.Sleep(200); string cellId = _PLC.CellId(); string lotId = _PLC.LotId(); Status.GetProcessingWeldingData().CellId = cellId; Status.GetProcessingWeldingData().LotId = lotId; Log.I($"[CheckPageNoComp]페이지 확인 [{checknum}/3] - PLC_Page: [{plcPageNo}], PC_Index: [{_CurrentPageNo}], CellId: [{cellId}], LotId: [{lotId}]"); if (checknum > 2) { // 260618: 상한 하드코딩(>2) 제거 → 활성 Recipe의 페이지 수(레이어 수)로 가변 검증 (Recipe별 2~5페이지) // B-1: Model.Layers 접근을 UI 스레드로 직렬화 (레시피 reload와의 race 방지) int recipePageCount = Application.Current.Dispatcher.Invoke(() => _ScannerCanvasUserCtrl.Model.Layers.Count()); if (plcPageNo > recipePageCount || plcPageNo < 1) { Log.E($"[CheckPageNoComp] Invalid PageNo received: {plcPageNo} (활성 Recipe 페이지 수: {recipePageCount})"); // 디버그 끝내고 삭제 //MessageUtil.ShowErrorToast($"ReadWord -> {plcPageNo}"); MessageUtil.ShowErrorToast($"ReadWord -> {plcPageNo}"); Thread.Sleep(3000); // 나중에 알람 처리해. // 에러 발생 시 시퀀스 중단 또는 기본값 처리 (현장 상황에 따라 선택) //AlarmManager.Alarm(AlarmCode.ALARM_SEQUENCE_TeachingRun_InitVariableError); return null; } else { //for (int i = 0; i > plcPageNoList.Count; i++) //{ Log.E($"[CheckPageNoComp] List {plcPageNoList[0]}/{plcPageNoList[1]}/{plcPageNoList[2]}"); //} break; } } } Thread.Sleep(5); // 3. 에코 및 완료 비트(Ack) ON _PLC.WriteWord(eWordOutput.PC_PAGE_NO, plcPageNo); Log.I($"---> [CheckPageNoComp] Set PC_PAGE_NO: {plcPageNo}"); //Thread.Sleep(1000); //MessageUtil.ShowErrorToast($"WriteWord -> {plcPageNo}"); //Thread.Sleep(1000); _PLC.WriteBit(eBitOutput.PC_PAGE_CHANGE_COMPLETE, false); Log.I("[CheckPageNoComp] Set PC_PAGE_CHANGE_COMPLETE false"); //Thread.Sleep(5); // UI 페이지 업데이트 알림 //if (Application.Current != null && Application.Current.AuroraEnv() != null) { Application.Current.AuroraEnv().EventPublisher.Publish(new EventMessage("", CommonEventIds.EVENT_AUTO_RUN_UPDATE_PAGENUM, (int)_CurrentPageNo)); } Thread.Sleep(5); // 추후 제거 Log.I($"[CheckPageNoComp] 페이지 확정 - PC_PAGE_NO: [{plcPageNo}] 에코 완료 → WeldingList 업로드 이동"); return SequenceAction.Ok(); } private bool LaserReadyResponse() { Log.I($"[LaserReadyResponse] 노드 Start"); //if (ApplicationExt.IsSimulation) Log.I($"[LaserReadyResponse] 노드 End"); return true; } private SequenceActionResult SetUploadWeldingList_SeqParams(SequenceContext context, string id, SequenceActionResult param) { Log.I($"[SetUploadWeldingList_SeqParams] PageNo [{_CurrentPageNo}] 용접 리스트 레이어 선택 시작"); Status.SetWorkingPosition(eWorkingPosition.UPLOADING_WELD_LIST); #region 페이지 번호에 따라 용접 리스트 레이어 선택 됨. 아래 데이터가 정확한지는 확인 필요 // 페이지 번호에 따라 용접 리스트(레이어) 선택 int totalLineCount = 0; // B-1: Model.Layers 접근(레이어 선택)을 UI 스레드로 직렬화 (레시피 reload와의 race 방지) Application.Current.Dispatcher.Invoke(() => { int layerCount = _ScannerCanvasUserCtrl.Model.Layers.Count(); if (layerCount > _CurrentPageNo && _CurrentPageNo >= 0) { var layer = _ScannerCanvasUserCtrl.Model.Layers[_CurrentPageNo]; _SelectedObjectList = layer.Objects; Log.I($"[SetUploadWeldingList] Selected PageNo [{_CurrentPageNo}] → Layer Name: [{layer.No}], Object Count: [{_SelectedObjectList.Count()}]"); } else { Log.E($"[SetUploadWeldingList] Invalid PageNo [{_CurrentPageNo}] (Total Layers: {layerCount}) → Fallback to Active Layer"); _SelectedObjectList = _ScannerCanvasUserCtrl.Model.ActiveLayer.Objects; Log.W($"[SetUploadWeldingList] Fallback Layer Name: [{_ScannerCanvasUserCtrl.Model.ActiveLayer.No}], Object Count: [{_SelectedObjectList.Count()}]"); } }); if (_SelectedObjectList == null || _SelectedObjectList.Count() == 0) { Log.E("[SetUploadWeldingList] No welding objects found! Sequence will fail."); return null; } // 공통 로그 - if/else 밖으로 이동 var groups = _SelectedObjectList.OfType().ToList(); totalLineCount = groups.Sum(g => g.SubObjects.OfType().Count()); Log.I($"[SetUploadWeldingList] Group Count: [{groups.Count}], Total Line Count: [{totalLineCount}]"); for (int i = 0; i < groups.Count; i++) { int lineCount = groups[i].SubObjects.OfType().Count(); Log.I($"[SetUploadWeldingList] Group[{i}] Line Count: [{lineCount}]"); } #endregion // TeachingRun 에서는 CellMeasureCenterOffset 사용하지 않음. 0 으로 넘기자 SeqUploadWeldingList_InitParam seqParams = new SeqUploadWeldingList_InitParam() { NeedSetLaserTiming = _InitParam.NeedSetLaserTiming, IncludeListStartEnd = true, CellMeasureCenterOffsetX = 0.0, CellMeasureCenterOffsetY = 0.0, SelectedDrawObjectList = _SelectedObjectList, // 이게 중요 포인트 WeldingScannerListNo = _WeldingScannerListNo, }; //Thread.Sleep(1000); // 추후 제거 Log.I($"[SetUploadWeldingList_SeqParams] 용접 파라미터 세팅 완료 → UploadWeldingList 서브시퀀스 실행"); return SequenceAction.Ok(seqParams); } private SequenceActionResult GetUploadWeldingList_SeqResult(SequenceContext context, string id, SequenceActionResult param) { Log.I($"[GetUploadWeldingList_SeqResult] 용접 리스트 업로드 결과 확인 중"); Status.SetWorkingPosition(eWorkingPosition.UPLOADING_WELD_LIST); var resultOutParam = param.Param as SeqUploadWeldingList_OutParam; if (resultOutParam.Success == false) { //Thread.Sleep(1000); // 추후 제거 Log.E($"[GetUploadWeldingList_SeqResult] 업로드 실패 → 시퀀스 중단"); return null; } //Thread.Sleep(1000); // 추후 제거 Log.I($"[GetUploadWeldingList_SeqResult] 업로드 성공 → WeldingStartReadBit 이동"); return SequenceAction.Ok(); } // 260513 추가노드 private SequenceActionResult WeldingStartReadBit(SequenceContext context, string id, SequenceActionResult param) { Log.I($"---> [WeldingStartReadBit] PLC_WELD_READY_REQ 신호 대기 중... "); // 1. PLC 용접 요청 대기 var interfaceStartTime = DateTime.Now; while (!context.Stopped) { if (_PLC == null) { Log.E("[WeldingStartReadBit] _PLC instance is null!"); return null; } if (_PLC.ReadBit(eBitInput.PLC_WELD_READY_REQ).data) { Log.I($"[WeldingStartReadBit] Check OK -> PLC_WELD_READY_REQ : ON"); // 디버그 끝내고 삭제 MessageUtil.ShowToast("PLC_WELD_READY_REQ : ON"); //Thread.Sleep(1000); break; } if (DateTime.Now.Subtract(interfaceStartTime).TotalSeconds > PLC_TIMEOUT) { Log.W($"[WeldingStartReadBit] PLC_WELD_READY_REQ -> TIMEOUT..."); interfaceStartTime = DateTime.Now; } Thread.Sleep(10); } Log.I($"---> [WeldingStartReadBit] PLC_WELD_READY_REQ ON 감지 → WaitWeldingStart 이동"); return SequenceAction.Ok(); } private SequenceActionResult WaitWeldingStart(SequenceContext context, string id, SequenceActionResult param) { Log.I($"[---> WaitWeldingStart] 용접 준비 신호 송신 - PageNo: [{plcPageNo}] CurrentPage: {_CurrentPageNo + 1})"); Status.SetWorkingPosition(eWorkingPosition.WAITING_WELD_START); if (plcPageNo == 1) { // 2. 준비 완료(Ack) 및 가동 중(Busy) 비트 ON _PLC.WriteBit((int)eBitOutput.PC_WELD_READY_ACK, true); _PLC.WriteBit((int)eBitOutput.PC_WELD_BUSY, true); Thread.Sleep(200); _PLC.WriteBit((int)eBitOutput.PC_WELD_COMPLETE, false); Thread.Sleep(5); Log.I($"[WaitWeldingStart] WriteBit Anode Welding Ready ACK TRUE"); Log.I($"[WaitWeldingStart] WriteBit Anode Welding Busy TRUE"); Log.I($"[WaitWeldingStart] WriteBit Anode Welding Comp False"); //Thread.Sleep(10); } else if (plcPageNo == 2) { // 2. 준비 완료(Ack) 및 가동 중(Busy) 비트 ON _PLC.WriteBit((int)eBitOutput.PC_WELD_P_READY_REQ, true); _PLC.WriteBit((int)eBitOutput.PC_WELD_P_BUSY, true); Thread.Sleep(200); _PLC.WriteBit((int)eBitOutput.PC_WELD_P_COMP_ACK, false); Thread.Sleep(5); Log.I($"[WaitWeldingStart] WriteBit Cathode Welding Ready REQ TRUE"); Log.I($"[WaitWeldingStart] WriteBit Cathode Welding Busy TRUE"); Log.I($"[WaitWeldingStart] WriteBit Cathode Welding Comp False"); //Thread.Sleep(10); } else // PAGE NUM = 0 { Log.I($"[WaitWeldingStart] Page num 1,2 etc.."); } // 디버그 끝내고 삭제 //MessageUtil.ShowToast("WaitWeldingStart -> WriteBit True"); //Thread.Sleep(3000); Log.I($"---> [WaitWeldingStart] READY_ACK/BUSY ON, COMPLETE OFF 완료 → ExecuteWelding 이동"); return SequenceAction.Ok(); } // 이곳에서 SeqTeachingRun.cs 를 태운다. (레시피의 WeldingRun 호출 구간) private SequenceActionResult ExecuteWelding(SequenceContext context, string id, SequenceActionResult param) { Log.I($"[ExecuteWelding] 용접 실행 시작 - PageNo: [{plcPageNo}], ListNo: [{_WeldingScannerListNo}]"); // ← 여기 #region 코드 확인 필요 Status.SetWorkingPosition(eWorkingPosition.EXECUTING_WELD); // 현재 시퀸스 위치 // === Head Theta 인터락: 수평/수직(±tol)에서만 발진 허용 (체크박스 사용 시) === var thetaSetting = Application.Current.AuroraEnv().SettingEntry[SettingIds.LaserInterlock] as LaserInterlockSetting; if (thetaSetting != null && thetaSetting.HeadThetaInterlockEnabled) { bool blocked = false; if (_PLC == null || !_PLC.IsActivated) { Log.W("[ExecuteWelding] HeadTheta 인터락 - PLC 미연결/미활성 → 발진 차단"); blocked = true; // fail-closed } else { short thetaRaw = _PLC.ReadWord(eWordInput.HEAD_THETA_POSITION).data; double scale = thetaSetting.HeadThetaPlcScale != 0 ? thetaSetting.HeadThetaPlcScale : 1; // 0 가드 double thetaDeg = thetaRaw / scale; bool okH = Math.Abs(thetaDeg - thetaSetting.HeadThetaHorizontal) <= thetaSetting.HeadThetaTolerance; bool okV = Math.Abs(thetaDeg - thetaSetting.HeadThetaVertical) <= thetaSetting.HeadThetaTolerance; Log.I($"[ExecuteWelding] HeadTheta raw={thetaRaw}, deg={thetaDeg:F1} (H={thetaSetting.HeadThetaHorizontal}/V={thetaSetting.HeadThetaVertical}/±{thetaSetting.HeadThetaTolerance})"); if (!okH && !okV) { Log.W($"[ExecuteWelding] HeadTheta 인터락 → 수평/수직 ±{thetaSetting.HeadThetaTolerance}° 벗어남(deg={thetaDeg:F1}) → 발진 차단"); blocked = true; } } if (blocked) { try { AlarmManager.Alarm(AlarmCode_PTOP.ALARM_LASER_HEAD_THETA_INTERLOCK); } catch (Exception ex) { Log.E($"[ExecuteWelding] HeadTheta 알람 표시 실패(테이블 미등록?): {ex.Message}"); } return null; // 발진 차단 + 시퀀스 중단 } } _IO._iOUnit.WriteDoBit(Convert.ToInt32(ePrismaticDOAddress.CoreLaser_Control), true); _IO._iOUnit.WriteDoBit(Convert.ToInt32(ePrismaticDOAddress.RingLaser_Control), true); Log.I($"[ExecuteWelding] Laser Gate ON (Core+Ring)"); Thread.Sleep(laserEmissionGateDelayMs); // 260518 매뉴얼 기준 ≥10ms, 여유분 포함 =15ms 적용함. Log.I($"[ExecuteWelding] Gate ON Delay {laserEmissionGateDelayMs}ms 완료 → ExecuteList 시작"); // ── SeqTeachingRun SubSequence 실행 ────────────────────────────── DateTime weldingTime = DateTime.Now; Status.GetProcessingWeldingData().WeldingStartTime = weldingTime; _Scanner.ExecuteList(_WeldingScannerListNo); // 레이저 출사 막을 땐 주석 Log.I($"[ExecuteWelding] ExecuteList 시작 - ListNo: {_WeldingScannerListNo}"); _Scanner.WaitForListTaskCompleted(1000);//(_SystemParameter.WeldLinesTaskTimeOut); Log.I($"[ExecuteWelding] WaitForListTaskCompleted 완료"); #region Gate OFF 위치 변경 - 용접 완료 즉시 OFF (TruPulse nano 매뉴얼 기준 ≥10ms) // ↓ 이 두 줄 삭제 _IO._iOUnit.WriteDoBit(Convert.ToInt32(ePrismaticDOAddress.CoreLaser_Control), false); _IO._iOUnit.WriteDoBit(Convert.ToInt32(ePrismaticDOAddress.RingLaser_Control), false); Log.I($"[ExecuteWelding] Laser Gate OFF (Core+Ring)"); #endregion _WeldingSpanTime = DateTime.Now.Subtract(weldingTime); Status.GetProcessingWeldingData().WeldingEndTime = DateTime.Now; if (_CurrentPageNo == 1) { Log.D(string.Format($"[ExecuteWelding] -->(Wedling TackTime) {_CurrentPageNo} Welding Complete Time : {_WeldingSpanTime.TotalMilliseconds}")); } else { Log.D(string.Format($"[ExecuteWelding] -->(Wedling TackTime) {_CurrentPageNo} Welding Complete Time : {_WeldingSpanTime.TotalMilliseconds}")); } double dExecuteListTime = _Scanner.GetTime(); //Log.I(string.Format($"[ExecuteWelding] ExecuteList Time : {dExecuteListTime * 1000} msec")); // File Format Record Test weldingTime = DateTime.Now; _WeldingSpanTime = DateTime.Now.Subtract(weldingTime); Log.D(string.Format($"[ExecuteWelding] SaveScannerRecordFileForamt Time : {_WeldingSpanTime.TotalMilliseconds}")); weldingTime = DateTime.Now; _Scanner.JumpAbs(0, 0, eFunctionType.COMMAND); //용접 완료 후 CorrectionFile Vision으로 원복 _Scanner.SelectCorrectionTable(1); _WeldingSpanTime = DateTime.Now.Subtract(weldingTime); Log.D($"[ExecuteWelding] JumpAbs + SelectCorrectionTable Time: {_WeldingSpanTime.TotalMilliseconds}ms"); Log.D(string.Format($"[ExecuteWelding] Change SelectCorrectionTable Time : {_WeldingSpanTime.TotalMilliseconds}")); if (plcPageNo == 1) { // 용접끝 _PLC.WriteBit((int)eBitOutput.PC_WELD_READY_ACK, false); Log.I("[PLCInterface] PageNo[1] READY_ACK false"); Thread.Sleep(5); // 용접중 신호로 변경 _PLC.WriteBit((int)eBitOutput.PC_WELD_BUSY, false); Log.I("[PLCInterface] PageNo[1] BUSY false"); Thread.Sleep(5); _PLC.WriteBit((int)eBitOutput.PC_WELD_COMPLETE, true); Log.I("[PLCInterface] PageNo[1] COMPLETE true"); Thread.Sleep(5); } else if (plcPageNo == 2) { // 용접끝 _PLC.WriteBit((int)eBitOutput.PC_WELD_P_READY_REQ, false); Log.I("[PLCInterface] PageNo[2] READY_REQ false"); Thread.Sleep(5); // 용접중 신호로 변경 _PLC.WriteBit((int)eBitOutput.PC_WELD_P_BUSY, false); Log.I("[PLCInterface] PageNo[2] BUSY false"); Thread.Sleep(5); _PLC.WriteBit((int)eBitOutput.PC_WELD_P_COMP_ACK, true); Log.I("[PLCInterface] PageNo[2] COMP_ACK true"); Thread.Sleep(5); } else { } Status.SetWorkingPosition(eWorkingPosition.UNLOADING_CELL); #endregion Log.I($"[ExecuteWelding] 완료 → CheckCycleStop 이동"); return SequenceAction.Ok(); } private string CheckCycleStop(SequenceContext context, string id, ICollection branches, SequenceActionResult param) { Log.I($"[CheckCycleStop] 사이클 중지 여부 확인 중"); Status.SetWorkingPosition(eWorkingPosition.CHECKING_CYCLE); Log.I($"[CheckCycleStop] 사이클 계속 진행 → CheckPageNo 이동"); return SEQ_CHECK_PAGENO; } private SequenceActionResult SaveResultData(SequenceContext context, string id, SequenceActionResult param) { Log.I($"[SaveResultData] 용접 결과 데이터 저장 시작"); Status.SetWorkingPosition(eWorkingPosition.REPORTING_RESULT); // 스캐너의 결과를 저장하는 동작 추가 SaveScannerRecordFileForamt(); // 결과 Data 를 업데이트 함. context.PutValue(ContextKey.KEY_SEQUENCE_RESULT, true); Status.GetProcessingWeldingData().IsVisionNG = false; Log.I("[SaveResultData] Welding Process Result: SUCCESS"); // 결과 데이터 저장하는 시퀸스 //Thread.Sleep(1000); // 추후 제거 Log.I($"[SaveResultData] 결과 저장 완료 → WriteCompletionPLCBit 이동"); return SequenceAction.Ok(); } // 실제 동작 _Scanner에서 private void SaveScannerRecordFileForamt() { Log.I($"[SaveScannerRecordFileForamt] 노드 Start"); // ── 용접 완료 후 파형 데이터 저장 ──────────────────────────── _Scanner.SaveRecord("AutoRunTDC"); Log.I("[SaveResultData] SaveRecord Complete"); _Scanner.JumpAbs(0, 0, eFunctionType.COMMAND); _Scanner.SelectCorrectionTable(1); Log.D("[SaveResultData] JumpAbs + SelectCorrectionTable Complete"); //Thread.Sleep(1000); // 추후 제거 Log.I($"[SaveScannerRecordFileForamt] 노드 End"); } // 이곳에서 bit 초기화 진행함. 260512 private SequenceActionResult WriteCompletionPLCBit(SequenceContext context, string id, SequenceActionResult param) { Log.I($"[WriteCompletionPLCBit] PLC 완료 비트 초기화 시작"); Status.SetWorkingPosition(eWorkingPosition.REPORTING_RESULT); // 완료 비트 write 하는 Node 추가 필요. format 유지, ReceiveCellInfo 없음 if (RestoreDeviceStatus()) { // 초기화 완료 Log.D("AutoRun Sequence WriteCompletionPLCBit"); } //Thread.Sleep(1000); // 추후 제거 Log.I($"[WriteCompletionPLCBit] PLC 비트 초기화 완료 → Complete 이동"); return SequenceAction.Ok(); } private SequenceActionResult Complete(SequenceContext context, string id, SequenceActionResult param) { Log.I($"[Complete] 1사이클 완료 처리]"); context.TryGetValue(ContextKey.KEY_SEQUENCE_RESULT, out bool seqResult); SeqAutoRunPTOP_OutParam result = new SeqAutoRunPTOP_OutParam() { Success = seqResult }; if (seqResult) { uint prevTotal = _SystemParameter.TotalProductCount; uint prevToday = _SystemParameter.TodayProductCount; _SystemParameter.TotalProductCount++; _SystemParameter.TodayProductCount++; _SystemParameter.LastProductTime = DateTime.Now; //Application.Current.AuroraEnv().SettingEntry.Save(CommonSettingIds.SystemParameter, _SystemParameter); Status.GetProcessingWeldingData().TotalProduct = _SystemParameter.TotalProductCount; Status.GetProcessingWeldingData().TodayProduct = _SystemParameter.TodayProductCount; Log.I($"[Complete] Product Count Updated - Total: {prevTotal} -> {_SystemParameter.TotalProductCount}, Today: {prevToday} -> {_SystemParameter.TodayProductCount}"); } else { Status.GetProcessingWeldingData().IsVisionNG = true; Log.W("[Complete] Sequence result was FAIL. Product count not incremented."); } Status.SetWorkingPosition(eWorkingPosition.DONE); //Thread.Sleep(1000); // 추후 제거 Log.I($"[Complete] 카운트 업데이트 완료 → 다음 사이클 진입"); return SequenceAction.Ok(result); } private bool RestoreDeviceStatus() { Log.I($"[RestoreDeviceStatus] 노드 Start"); ////PLC All BIT OFF _PLC.WriteBit((int)eCommonBitOutput.PC_PAGE_CHANGE_COMPLETE, false); // 페이지 false _PLC.WriteBit((int)eCommonBitOutput.PC_WELD_READY_ACK, false); // 용접 준비 false _PLC.WriteBit((int)eCommonBitOutput.PC_WELD_BUSY, false); // 동작 중 false _PLC.WriteBit((int)eCommonBitOutput.PC_WELD_COMPLETE, false); // 용접 완료 false Log.I(string.Format("[PLCInterface]PC Anod Bit Off")); _PLC.WriteBit((int)eCommonBitOutput.PC_WELD_P_READY_REQ, false); // 용접 준비 false _PLC.WriteBit((int)eCommonBitOutput.PC_WELD_P_BUSY, false); // 동작 중 false _PLC.WriteBit((int)eCommonBitOutput.PC_WELD_P_COMP_ACK, false); // 용접 완료 false Log.I(string.Format("[PLCInterface]PC Cathode Bit Off")); //Thread.Sleep(1000); // 추후 제거 Log.I($"[RestoreDeviceStatus] 노드 End"); return true; } // 장비 3개 가져오기 완료 private void InitDev(SequenceContext context) { Log.I($"[InitDev] 노드 Start"); _Scanner = context.GetLaserScanner(); _PLC = context.GetPLC() as PLCControllerPTOP; _IO = context.GetIOUnit(); Log.I($"[InitDev] 노드 End"); } private bool CheckDeviceReady(SequenceContext context) { Log.I($"[CheckDeviceReady] 노드 Start"); List deviceListToCheck = new List() { CommonDeviceIds.Scanner, CommonDeviceIds.Laser, CommonDeviceIds.PLCDevice, CommonDeviceIds.IODevice }; bool isAllDeviceActivated = true; foreach (string item in deviceListToCheck) { if (!context.Devices[item].IsActivated) { isAllDeviceActivated = isAllDeviceActivated && false; } } Log.I($"[CheckDeviceReady] 노드 End"); return isAllDeviceActivated; } private bool CheckCondition(SequenceContext context) { Log.I($"[CheckCondition] 노드 Start"); // 체크 신호 plc if (CheckCondition_Interlock()) { if (InitializeDeviceStatus(context)) { Log.I($"[CheckCondition] 노드 End"); return true; } } Log.I($"[CheckCondition] 노드 End"); return false; } private bool CheckCondition_Interlock() { Log.I($"[CheckCondition_Interlock] 노드 Start"); // PLC, Data, IO 등 Interlock checking Log.I($"[CheckCondition_Interlock] 노드 End"); return true; } private bool InitializeDeviceStatus(SequenceContext context) { Log.I($"[InitializeDeviceStatus] 노드 Start"); // PLC Interface AutoStatus Start InitAutoRunBit(); // Laser Initialize //_Laser.PilotLaserOff(); //_Laser.LaserOn(); //Thread.Sleep(100); //if (!_Laser.GetLaserReady()) //{ // Log.E("ERROR Initialize Laser"); // return false; //} // Scanner Initialize // Scanner 없음. // OCT Initialize // TODO: EnterSequenceModeOCT 와 함께 Initialize 하는 것을 재구현 필요함. 초반에 한번만 check 하고 돌아 갈수 있도록 // PLC Initialize #region [TEST_BYPASS] Alarm Check - PLC_AUTO_STATUS /* if (_PLC.ReadBit((int)eBitInput.PLC_AUTO_STATUS).data == false) { Log.E(string.Format("ERROR Initialize PLC, Bit Off PLC_AUTO_STATUS")); return false; } */ #endregion _IO._iOUnit.WriteDoBit(Convert.ToInt32(eADOAddress.PCLBADReadyStatus), true); Log.I(string.Format($"[IOInterface]SET PCLBADReadyStatus true")); Log.I($"[InitializeDeviceStatus] 노드 End"); return true; } private void InitAutoRunBit() { Log.I($"[InitAutoRunBit] 노드 Start"); Log.I(string.Format($"[PLCInterface] Start AutoRun PLC Bit Initialize")); _PLC.WriteBit((int)eBitOutput.PC_WELD_READY_ACK, false); Log.I(string.Format($"[PLCInterface]SET (-)PC_WELD_READY_ACK False")); _PLC.WriteBit((int)eBitOutput.PC_WELD_BUSY, false); Log.I(string.Format($"[PLCInterface]SET (-)PC_WELD_BUSY False")); _PLC.WriteBit((int)eBitOutput.PC_WELD_COMPLETE, false); Log.I(string.Format($"[PLCInterface]SET (-)PC_WELD_COMPLETE False")); _PLC.WriteBit((int)eBitOutput.PC_AUTO_STATUS, true); Log.I(string.Format($"[PLCInterface]SET (-)PC_AUTO_STATUS True")); Log.I(string.Format($"[PLCInterface] Start AutoRun PLC Bit Initialize")); _PLC.WriteBit((int)eBitOutput.PC_WELD_P_READY_REQ, false); Log.I(string.Format($"[PLCInterface]SET (+)PC_WELD_READY_ACK False")); _PLC.WriteBit((int)eBitOutput.PC_WELD_P_BUSY, false); Log.I(string.Format($"[PLCInterface]SET (+)PC_WELD_BUSY False")); _PLC.WriteBit((int)eBitOutput.PC_WELD_P_COMP_ACK, false); Log.I(string.Format($"[PLCInterface]SET (+)PC_WELD_COMPLETE False")); _PLC.WriteBit((int)eBitOutput.PC_AUTO_STATUS_P, true); Log.I(string.Format($"[PLCInterface]SET (+)PC_AUTO_STATUS True")); Log.I($"[InitAutoRunBit] 노드 End"); } private void OnForceError(SequenceContext arg1, string arg2) { Log.I($"[OnForceError] 노드 Start"); OnSeqActionStop(); Log.I("SequenceAction Ok : OnForceError"); Log.I($"[OnForceError] 노드 End"); } private void OnStopped(SequenceContext obj) { Log.I($"[OnStopped] 노드 Start"); OnSeqActionStop(); Log.I("SequenceAction Ok : OnStopped"); Log.I($"[OnStopped] 노드 End"); } private void OnSeqActionStop() { Log.I($"[OnSeqActionStop] 시퀀스 강제 중지 - PLC/IO/Scanner 초기화 시작"); // PLC All BIT OFF _PLC.WriteBit((int)eBitOutput.PC_AUTO_STATUS, false); _PLC.WriteBit((int)eBitOutput.PC_WELD_READY_ACK, false); _PLC.WriteBit((int)eBitOutput.PC_WELD_BUSY, false); _PLC.WriteBit((int)eBitOutput.PC_WELD_COMPLETE, false); Log.I("[OnSeqActionStop] PLC ANODE(-) Output All false"); _PLC.WriteBit((int)eBitOutput.PC_AUTO_STATUS_P, false); _PLC.WriteBit((int)eBitOutput.PC_WELD_P_READY_REQ, false); _PLC.WriteBit((int)eBitOutput.PC_WELD_P_COMP_ACK, false); _PLC.WriteBit((int)eBitOutput.PC_WELD_P_BUSY, false); Log.I("[OnSeqActionStop] PLC Cathod(+) Output All false"); _PLC.WriteBit((int)eBitOutput.PC_PAGE_CHANGE_COMPLETE, false); Log.I("[OnSeqActionStop] PLC IO Output PAGECHANGE Init"); // 260512 워드 _PLC.WriteDWord((int)eWordOutput.PC_ERROR_CODE, 0); _PLC.WriteDWord((int)eWordOutput.PC_PAGE_NO, 0); _PLC.WriteDWord((int)eWordOutput.PC_RECIPE_NO, 0); // 260618 레시피 No init Log.I("[OnSeqActionStop] PLC IO Output Word Init"); Log.I("[PLCInterface] PC All Bit Off (Auto, ReadyAck, Busy, Complete)"); // LBAD IO All BIT OFF try { _IO._iOUnit.WriteDoBit(Convert.ToInt32(ePrismaticDOAddress.CoreLaser_Control), false); _IO._iOUnit.WriteDoBit(Convert.ToInt32(ePrismaticDOAddress.RingLaser_Control), false); //_IO._iOUnit.WriteDoBit(Convert.ToInt32(eADOAddress.PCLBADWeldingTrigger), false); //_IO._iOUnit.WriteDoBit(Convert.ToInt32(eADOAddress.PCLBADReadyStatus), false); Log.I("[IOInterface] PC All Bit Off (Laser Control, LBAD Trigger, LBAD Ready)"); } catch (Exception ex) { Log.E($"[IOInterface] Failed to reset IO Bits: {ex.Message}"); } // List 실행 종료 및 원점 복귀 _Scanner.StopExecution(); _Scanner.JumpAbs(0, 0, eFunctionType.COMMAND); Log.I("[Scanner] StopExecution and JumpAbs(0,0)"); // 용접 완료 후 CorrectionFile Vision으로 원복 _Scanner.SelectCorrectionTable(1); Log.I($"[OnSeqActionStop] 모든 장비 초기화 완료"); } public class SeqAutoRunPTOP_OutParam : OutParam { } } }