diff --git a/Doc/ILSOSConfig/PLC_DeviceMap_Bit_Input_M_Device.csv b/Doc/ILSOSConfig/PLC_DeviceMap_Bit_Input_M_Device.csv index fffea2c..77e43dd 100644 --- a/Doc/ILSOSConfig/PLC_DeviceMap_Bit_Input_M_Device.csv +++ b/Doc/ILSOSConfig/PLC_DeviceMap_Bit_Input_M_Device.csv @@ -8,8 +8,8 @@ M70505,Heavy_ALStatus,Heavy_ALStatus M70506,Door Interlok,Door Interlok M70507,EMO Status,EMO Status M70508,Page_CHGRequest,Page_CHGRequest -M70509,Recipe_CHGRequest,Recipe_CHGRequest -M70510,, +M70509,PreWeld Scout Request,용접 전 Scout Request +M70510,PostWeld Scout Request,용접 후 Scout Request M70511,, M70512,, M70513,, diff --git a/Doc/ILSOSConfig/PLC_DeviceMap_Bit_Output_M_Device.csv b/Doc/ILSOSConfig/PLC_DeviceMap_Bit_Output_M_Device.csv index 70a573b..1b82d39 100644 --- a/Doc/ILSOSConfig/PLC_DeviceMap_Bit_Output_M_Device.csv +++ b/Doc/ILSOSConfig/PLC_DeviceMap_Bit_Output_M_Device.csv @@ -8,8 +8,8 @@ M70005,Heavy_ALStatus,Heavy_ALStatus M70006,, M70007,, M70008,Page_CHGComplete,Page_CHGComplete -M70009,Recipe_CHGComplete,Recipe_CHGComplete -M70010,, +M70009,PreWeld Scout Complete,용접 전 Scout Complete +M70010,PostWeld Scout Complete,용접 후 Scout Complete M70011,, M70012,, M70013,, diff --git a/Machines/ILSOS_PrismaticRD_PTOP/Device/PLC/PLCController.Address.cs b/Machines/ILSOS_PrismaticRD_PTOP/Device/PLC/PLCController.Address.cs index 55958fe..ede58f2 100644 --- a/Machines/ILSOS_PrismaticRD_PTOP/Device/PLC/PLCController.Address.cs +++ b/Machines/ILSOS_PrismaticRD_PTOP/Device/PLC/PLCController.Address.cs @@ -22,9 +22,12 @@ namespace ILSOS_PrismaticRD_PTOP.Device PLC_EMO_STATUS = 0x07, PLC_PAGE_CHANGE_REQ = 0x08, - // 260618: M70509 Recipe 변경 요청 (구 PLC_RMS_USED, RMS 미사용으로 폐기 후 재정의) - PLC_RECIPE_CHANGE_REQ = 0x09, - // 0x0A ~ 0x0E + // 260622: M70509 = 용접 전 Scout Request (구 PLC_RECIPE_CHANGE_REQ/RMS_USED, 모두 미사용 → 재정의) + // Recipe 전환은 PAGE_CHANGE_REQ 트리거 + PLC_RECIPE_NO 워드로 처리하므로 이 bit는 Scout가 사용. + PLC_PRE_WELD_SCOUT_REQ = 0x09, + // 260622: M70510 = 용접 후 Scout Request (신규) + PLC_POST_WELD_SCOUT_REQ = 0x0A, + // 0x0B ~ 0x0E // 260621: M70515 확정 = Weld_01 Ready (가변용접 시 PLC ON → Recipe/Page 확인 팝업 + Resume 트리거) PLC_WELD_READY_REQ = 0x0F, PLC_WELD_TRIGGER_REQ = 0x10, // M70516 (구 Ready) @@ -60,9 +63,11 @@ namespace ILSOS_PrismaticRD_PTOP.Device PC_DOOR_INTERLOCK = 0x06, PC_EMO_STATUS = 0x07, PC_PAGE_CHANGE_COMPLETE = 0x08, - // 260618: M70009 Recipe 변경 완료 응답 (구 PC_EMPTY01, 미사용 → 재정의) - PC_RECIPE_CHANGE_COMPLETE = 0x09, - // 0x0A ~ 0x0E + // 260622: M70009 = 용접 전 Scout Complete (구 PC_RECIPE_CHANGE_COMPLETE, PLC 미감시 → 재정의) + PC_PRE_WELD_SCOUT_COMPLETE = 0x09, + // 260622: M70010 = 용접 후 Scout Complete (신규) + PC_POST_WELD_SCOUT_COMPLETE = 0x0A, + // 0x0B ~ 0x0E // 260621: M70015 확정 = Weld_01 ReadyACK (Resume ACK/run 신호) PC_WELD_READY_ACK = 0x0F, PC_WELD_TRIGGER_ACK = 0x10, // M70016 (구 ReadyACK) diff --git a/Machines/ILSOS_PrismaticRD_PTOP/Sequences/SeqAutoRunPTOP.cs b/Machines/ILSOS_PrismaticRD_PTOP/Sequences/SeqAutoRunPTOP.cs index eabd7b6..ef4fd9c 100644 --- a/Machines/ILSOS_PrismaticRD_PTOP/Sequences/SeqAutoRunPTOP.cs +++ b/Machines/ILSOS_PrismaticRD_PTOP/Sequences/SeqAutoRunPTOP.cs @@ -76,6 +76,24 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences const string SEQ_WRITE_COMPLETION_PLC_BIT = "[SeqAutoRunPTOP] Write Completion PLC Bit"; const string SEQ_COMPLETE = "[SeqAutoRunPTOP] SEQ_COMPLETE"; // 완료 + #region 260622: 용접 전/후 Scout 핸드셰이크 노드 + // 용접 전 Scout: 페이지 확정 후 ~ 용접 전. PLC_PRE_WELD_SCOUT_REQ(M70509) 대기 → Scout(이미지 저장) + // → PC_PRE_WELD_SCOUT_COMPLETE(M70009) ON + const string SEQ_PRE_SCOUT_CHECK = "[SeqAutoRunPTOP] Pre-Weld Scout Check"; // 분기: Req 대기 / 스킵 + const string SEQ_PRE_SCOUT_SET_PARAMS = "[SeqAutoRunPTOP] Pre-Weld Scout SetParams"; + const string SEQ_PRE_SCOUT_RUN = "[SeqAutoRunPTOP] Pre-Weld Scout Run"; // SeqScoutNormal 서브시퀀스 + const string SEQ_PRE_SCOUT_DONE = "[SeqAutoRunPTOP] Pre-Weld Scout Done"; + // 용접 후 Scout: ExecuteWelding(완료) 후. PLC_POST_WELD_SCOUT_REQ(M70510) 대기 → Scout + // → PC_POST_WELD_SCOUT_COMPLETE(M70010) ON + const string SEQ_POST_SCOUT_CHECK = "[SeqAutoRunPTOP] Post-Weld Scout Check"; + const string SEQ_POST_SCOUT_SET_PARAMS = "[SeqAutoRunPTOP] Post-Weld Scout SetParams"; + const string SEQ_POST_SCOUT_RUN = "[SeqAutoRunPTOP] Post-Weld Scout Run"; + const string SEQ_POST_SCOUT_DONE = "[SeqAutoRunPTOP] Post-Weld Scout Done"; + + // Scout 라이브 활성 플래그. PLC 미준비 시 false로 두면 Scout 노드는 즉시 통과(기존 사이클 영향 없음). + private const bool SCOUT_ENABLED = true; + #endregion + public enum ePrismaticDOAddress { CoreLaser_Control = 9, @@ -138,11 +156,24 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences sb.AddAction(SEQ_GET_RESULT_UPLOAD_WELDING_LIST_SEQ, GetUploadWeldingList_SeqResult); #endregion - // 260513 plc + // 260622: 용접 전 Scout 핸드셰이크 (페이지 확정 후 ~ 용접 전) + sb.AddDecision(SEQ_PRE_SCOUT_CHECK, PreScoutCheck); + sb.AddAction(SEQ_PRE_SCOUT_SET_PARAMS, PreScoutSetParams); + sb.AddSubsequence(SEQ_PRE_SCOUT_RUN, SequenceManager.GetSeqBuilder_SeqScoutNormal()); + sb.AddAction(SEQ_PRE_SCOUT_DONE, PreScoutDone); + + // 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); // 체크페이지 + + // 260622: 용접 후 Scout 핸드셰이크 (ExecuteWelding 완료 후) + sb.AddDecision(SEQ_POST_SCOUT_CHECK, PostScoutCheck); + sb.AddAction(SEQ_POST_SCOUT_SET_PARAMS, PostScoutSetParams); + sb.AddSubsequence(SEQ_POST_SCOUT_RUN, SequenceManager.GetSeqBuilder_SeqScoutNormal()); + sb.AddAction(SEQ_POST_SCOUT_DONE, PostScoutDone); + + 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 @@ -156,21 +187,35 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences > sb[SEQ_CHECK_RECIPENO_COMPLETE] // Recipe 먼저 확정/전환 > sb[SEQ_CHECK_PAGENO_COMPLETE] // 그다음 Page 확정 - #region TeachingRun 시퀸스의 중간 용접리스트 생성 구간 + > sb[SEQ_PRE_SCOUT_CHECK] // 260622: 용접 전 Scout Req 대기(분기) + > sb[SEQ_PRE_SCOUT_SET_PARAMS] // Scout 파라미터(PreWeld) + > sb[SEQ_PRE_SCOUT_RUN] // SeqScoutNormal (캡처+저장) + > sb[SEQ_PRE_SCOUT_DONE] // Complete bit(M70009) ON + > 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_CHECK_WELDING_START_READ_BIT] // while read bit > sb[SEQ_WAIT_WELDING_START] // 4 업로드 완료 PLC 완료 신호 및 BUSY 신호로 변경 > sb[SEQ_EXECUTE_WELDING] // 5 Scanner 실행 (실제 용접) + + > sb[SEQ_POST_SCOUT_CHECK] // 260622: 용접 후 Scout Req 대기(분기) + > sb[SEQ_POST_SCOUT_SET_PARAMS] // Scout 파라미터(PostWeld) + > sb[SEQ_POST_SCOUT_RUN] // SeqScoutNormal (캡처+저장) + > sb[SEQ_POST_SCOUT_DONE] // Complete bit(M70010) ON + > sb[SEQ_CHECK_CYCLE_STOP] > sb[SEQ_SAVE_RESULT_DATA] - > sb[SEQ_WRITE_COMPLETION_PLC_BIT] // PLC 비트 완료 신호 보내기 - > sb[SEQ_COMPLETE] // 다시 enter로 올리기 + > sb[SEQ_WRITE_COMPLETION_PLC_BIT] // PLC 비트 완료 신호 보내기 + > sb[SEQ_COMPLETE] // 다시 enter로 올리기 > sb.Final); + // 260622: Scout 스킵 분기 (SCOUT_ENABLED=false 또는 Req 미수신 시 우회) + sb.Flows(sb[SEQ_PRE_SCOUT_CHECK] > sb[SEQ_SET_PARAMS_UPLOAD_WELDING_LIST_SEQ]); + sb.Flows(sb[SEQ_POST_SCOUT_CHECK] > sb[SEQ_CHECK_CYCLE_STOP]); + sb.Flows(sb[SEQ_CHECK_CYCLE_STOP] > sb[SEQ_CHECK_PAGENO]); // 루프백: 매 사이클 Page Change bit(0x08) 대기부터 재확인 sb.Flows(sb[SEQ_CHECK_INIT_CONDITION] > sb[SEQ_SET_PARAMS_UPLOAD_WELDING_LIST_SEQ]); // 260621: Resume 시 핸드셰이크 스킵 sb.Flows(sb[SEQ_CHECK_INIT_CONDITION] > sb[SEQ_COMPLETE] > sb.Final); @@ -822,6 +867,118 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences return SEQ_CHECK_PAGENO; } + #region 260622: 용접 전/후 Scout 핸드셰이크 + // ── 재사용 헬퍼 ───────────────────────────────────────────── + // Scout Request 비트(PLC→PC) 대기. SCOUT_ENABLED=false면 즉시 false 반환(스킵). + // 반환: true=Req 수신(Scout 진행) / false=스킵(또는 정지) + private bool WaitScoutRequestBit(SequenceContext context, eBitInput reqBit, string phase) + { + if (!SCOUT_ENABLED) + { + Log.I($"[{phase}Scout] SCOUT_ENABLED=false → Scout 스킵"); + return false; + } + + Log.I($"[{phase}Scout] {reqBit} 대기 중..."); + var t0 = DateTime.Now; + while (!context.Stopped) + { + if (_PLC == null) + { + Log.E($"[{phase}Scout] _PLC instance is null! Scout 스킵"); + return false; + } + if (_PLC.ReadBit(reqBit).data) + { + Log.I($"---> [{phase}Scout] {reqBit} ON 감지"); + return true; + } + if (DateTime.Now.Subtract(t0).TotalSeconds > PLC_TIMEOUT) + { + Log.W($"[{phase}Scout] {reqBit} 대기 TIMEOUT..."); + t0 = DateTime.Now; + } + Thread.Sleep(10); + } + return false; // 정지 + } + + // 이미지 파일명 접미어: auto_{recipeNo}_{pageNo}_{pre|post} + // 최종 파일명 = {기존파일명}_auto_{recipeNo}_{pageNo}_{phase}.PNG (디렉터리는 기존 날짜 폴더 유지) + // recipeNo=PLC Recipe No(CheckRecipeNoComp에서 확정), pageNo=PLC Page No(1-base, CheckPageNoComp에서 확정) + private string ScoutSaveTag(string phase) => $"auto_{plcRecipeNo}_{plcPageNo}_{phase}"; + + // SeqScoutNormal 서브시퀀스 파라미터 구성. saveTag로 저장 파일명 접미어 분기(용접 전/후). + private SeqScoutNormal_InitParam BuildScoutParams(string saveTag) + { + return new SeqScoutNormal_InitParam() + { + IsRealTimeScout = false, + ScoutSeqParams = new ScoutSeqParams(VisionRecipeManager.GetFovInfos()[0], eDisplayType.Machine_B), + PageIndex = _CurrentPageNo, // 현재 처리 중인 페이지(0-base) + SaveTag = saveTag, + }; + } + + // Scout Complete 비트(PC→PLC) 기록 + private void WriteScoutCompleteBit(eBitOutput completeBit, bool on, string phase) + { + _PLC.WriteBit((int)completeBit, on); + Log.I($"[{phase}Scout] {completeBit} = {on}"); + } + + // ── 용접 전 Scout ─────────────────────────────────────────── + private string PreScoutCheck(SequenceContext context, string id, ICollection branches, SequenceActionResult param) + { + bool need = WaitScoutRequestBit(context, eBitInput.PLC_PRE_WELD_SCOUT_REQ, "Pre"); + if (context.Stopped) return null; + // Req 수신 → Scout 진행 / 아니면 업로드 단계로 스킵 + return need ? SEQ_PRE_SCOUT_SET_PARAMS : SEQ_SET_PARAMS_UPLOAD_WELDING_LIST_SEQ; + } + private SequenceActionResult PreScoutSetParams(SequenceContext context, string id, SequenceActionResult param) + { + Log.I("[PreScout] Scout 파라미터 구성(pre)"); + return SequenceAction.Ok(BuildScoutParams(ScoutSaveTag("pre"))); + } + private SequenceActionResult PreScoutDone(SequenceContext context, string id, SequenceActionResult param) + { + var result = param.Param as SeqScoutNormal_OutParam; + if (result == null || result.Success == false) + { + Log.E("[PreScout] Scout 실패 → 시퀀스 중단"); + return null; + } + WriteScoutCompleteBit(eBitOutput.PC_PRE_WELD_SCOUT_COMPLETE, true, "Pre"); + Log.I("[PreScout] 완료 → UploadWeldingList 이동"); + return SequenceAction.Ok(); + } + + // ── 용접 후 Scout ─────────────────────────────────────────── + private string PostScoutCheck(SequenceContext context, string id, ICollection branches, SequenceActionResult param) + { + bool need = WaitScoutRequestBit(context, eBitInput.PLC_POST_WELD_SCOUT_REQ, "Post"); + if (context.Stopped) return null; + return need ? SEQ_POST_SCOUT_SET_PARAMS : SEQ_CHECK_CYCLE_STOP; + } + private SequenceActionResult PostScoutSetParams(SequenceContext context, string id, SequenceActionResult param) + { + Log.I("[PostScout] Scout 파라미터 구성(post)"); + return SequenceAction.Ok(BuildScoutParams(ScoutSaveTag("post"))); + } + private SequenceActionResult PostScoutDone(SequenceContext context, string id, SequenceActionResult param) + { + var result = param.Param as SeqScoutNormal_OutParam; + if (result == null || result.Success == false) + { + Log.E("[PostScout] Scout 실패 → 시퀀스 중단"); + return null; + } + WriteScoutCompleteBit(eBitOutput.PC_POST_WELD_SCOUT_COMPLETE, true, "Post"); + Log.I("[PostScout] 완료 → CheckCycleStop 이동"); + return SequenceAction.Ok(); + } + #endregion + private SequenceActionResult SaveResultData(SequenceContext context, string id, SequenceActionResult param) { Log.I($"[SaveResultData] 용접 결과 데이터 저장 시작"); @@ -916,9 +1073,13 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences _PLC.WriteBit((int)eCommonBitOutput.PC_WELD_TRIGGER_ACK, false); // 용접 준비 false _PLC.WriteBit((int)eCommonBitOutput.PC_WELD_BUSY, false); // 동작 중 false - _PLC.WriteBit((int)eCommonBitOutput.PC_WELD_COMPLETE, false); // 용접 완료 false + _PLC.WriteBit((int)eCommonBitOutput.PC_WELD_COMPLETE, false); // 용접 완료 false Log.I(string.Format("[PLCInterface]PC Anod Bit Off")); + // 260622: Scout Complete 비트(M70009/M70010) 사이클 종료 시 리셋 + _PLC.WriteBit((int)eBitOutput.PC_PRE_WELD_SCOUT_COMPLETE, false); + _PLC.WriteBit((int)eBitOutput.PC_POST_WELD_SCOUT_COMPLETE, false); + // 양극(P) 미사용 — 양극 용접 비트 리셋 제거 //Thread.Sleep(1000); // 추후 제거 @@ -1065,6 +1226,10 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences _PLC.WriteBit((int)eBitOutput.PC_PAGE_CHANGE_COMPLETE, false); Log.I("[OnSeqActionStop] PLC IO Output PAGECHANGE Init"); + // 260622: Scout Complete 비트(M70009/M70010) 정지 시 리셋 + _PLC.WriteBit((int)eBitOutput.PC_PRE_WELD_SCOUT_COMPLETE, false); + _PLC.WriteBit((int)eBitOutput.PC_POST_WELD_SCOUT_COMPLETE, false); + // 260512 워드 // 260621: 에코 워드(PC_PAGE_NO/PC_RECIPE_NO)는 정지 시 0으로 지우지 않음 — PLC가 정합 확인에 쓰는 // 마지막 핸드셰이크 값을 보존(다음 사이클에 어차피 재에코). 기존 0 초기화 2줄 제거. diff --git a/WeldingComponents/WeldingCommon/Sequences/Main/SeqScoutNormal.cs b/WeldingComponents/WeldingCommon/Sequences/Main/SeqScoutNormal.cs index e3208f5..897d589 100644 --- a/WeldingComponents/WeldingCommon/Sequences/Main/SeqScoutNormal.cs +++ b/WeldingComponents/WeldingCommon/Sequences/Main/SeqScoutNormal.cs @@ -296,8 +296,15 @@ namespace WeldingCommon.Sequences //4.스캐너 필드 이미지 파일 저장 // TODO: Path 설정하는 것 따로 만들어서 넣기. 여기저기 흩어져 있음... + // 디렉터리는 기존 그대로(날짜 폴더). SaveTag 가 있으면 기존 파일명 뒤에 '_접미어'로 붙임. + // 기존: {_ScoutNo+1}_{HHmmss}.PNG + // SaveTag 적용: {_ScoutNo+1}_{HHmmss}_{SaveTag}.PNG (예: 1_143052_auto_11_2_pre.PNG) + string dateDir = $"{DateTime.Now.Year}_{DateTime.Now.Month}_{DateTime.Now.Day}"; + string saveTag = _InitParam.SaveTag; + string baseName = $"{_ScoutNo + 1}_{DateTime.Now.ToString("HHmmss")}"; + string fileName = string.IsNullOrEmpty(saveTag) ? $"{baseName}.PNG" : $"{baseName}_{saveTag}.PNG"; string fieldScanImageFile = Path.Combine(ConfigurationManager.AppSettings.Get("DATA_PATH"), "_vision", "FieldScanImage", - string.Format($"{DateTime.Now.Year}_{DateTime.Now.Month}_{DateTime.Now.Day}/{_ScoutNo + 1}_{DateTime.Now.ToString("HHmmss")}.PNG")); + dateDir, fileName); _SystemParameter.ScannerFieldImageFile = fieldScanImageFile; //5.압축하여 저장 @@ -385,6 +392,11 @@ namespace WeldingCommon.Sequences // 기존: SeqScoutNormal 이 MainPage.selectedIndexNo(GUI 전역 상태)에만 의존 → 페이지 이동/자동운전 시 stale. // 변경: 호출부가 "현재 처리/표시 중인 페이지 번호"를 명시 주입. public int PageIndex { get; set; } = 0; + + // [추가 2026-06-22] 파일명 접미어. 호출부가 같은 SeqScoutNormal을 단계별로 호출하며 파일명을 분기하기 위함. + // 디렉터리는 기존 날짜 폴더 그대로. 비어있으면(기본) 기존 파일명 그대로 유지(하위호환). + // 적용 시 파일명: {기존파일명}_{SaveTag}.PNG (호출부에서 예: "auto_{recipeNo}_{pageNo}_pre" 전달) + public string SaveTag { get; set; } = ""; } public class SeqScoutNormal_OutParam : OutParam