From 42c5565249cabb45a8d7be959b64d1429f556cb2 Mon Sep 17 00:00:00 2001 From: NohDoyeon241104 Date: Thu, 18 Jun 2026 15:31:43 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20PLC=20Recipe=20Change=20=ED=95=B8?= =?UTF-8?q?=EB=93=9C=EC=85=B0=EC=9D=B4=ED=81=AC=20=EA=B5=AC=ED=98=84=20(Se?= =?UTF-8?q?qAutoRunPTOP)=20+=20=EC=A3=BC=EC=86=8C=20=EC=A0=95=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 근거: 가이드 + Excel 260618(빨간셀 M70509/M70009/D70502/D70002). Page Change 미러링. - 주소 enum: PLC_RECIPE_CHANGE_REQ(0x09)/PC_RECIPE_CHANGE_COMPLETE(0x09)/ PLC_RECIPE_NO(idx2)/PC_RECIPE_NO(idx2). 공용 eCommonBitOutput 동기화. 충돌: SeqStatusMonitor RMS_USED 읽기 주석, SeqAutoRunPrismatic 참조 이름만 교체. - 시퀀스: CheckRecipeNo(논블로킹 Decision) + CheckRecipeNoComp(.Id 1:1 조회/활성화/ 반영폴링/에코·완료비트), Builder 흐름·엣지·루프백, 리셋 비트·워드, _RecipeManager Enter 확보. - Page 검증 상한 하드코딩 제거 → 활성 Recipe Layers.Count() 가변. - (별도) D:\ILSOSConfig\...\External\PLC CSV 디바이스맵 4종 라벨 정합(.bak260618 백업). 빌드는 VS Rebuild로 검증 필요(레거시 포함). STEP5 UI/PLC 사양 최종정합은 후속. Co-Authored-By: Claude Opus 4.8 --- .../Device/PLC/PLCController.Address.cs | 12 +- .../Sequences/Monitoring/SeqStatusMonitor.cs | 4 +- .../Sequences/SeqAutoRunPTOP.cs | 138 ++++++++++- .../Sequences/SeqAutoRunPrismatic.cs | 2 +- .../Devices/PLC/PLCController.Address.cs | 3 +- docs/PLC_RecipeChange_구현_수정가이드.md | 226 ++++++++++++++++++ docs/작업일지/2026_06_18.md | 28 +++ 7 files changed, 394 insertions(+), 19 deletions(-) create mode 100644 docs/PLC_RecipeChange_구현_수정가이드.md diff --git a/Machines/ILSOS_PrismaticRD_PTOP/Device/PLC/PLCController.Address.cs b/Machines/ILSOS_PrismaticRD_PTOP/Device/PLC/PLCController.Address.cs index a7f5272..274bc26 100644 --- a/Machines/ILSOS_PrismaticRD_PTOP/Device/PLC/PLCController.Address.cs +++ b/Machines/ILSOS_PrismaticRD_PTOP/Device/PLC/PLCController.Address.cs @@ -22,7 +22,8 @@ namespace ILSOS_PrismaticRD_PTOP.Device PLC_EMO_STATUS = 0x07, PLC_PAGE_CHANGE_REQ = 0x08, - PLC_RMS_USED = 0x09, + // 260618: M70509 Recipe 변경 요청 (구 PLC_RMS_USED, RMS 미사용으로 폐기 후 재정의) + PLC_RECIPE_CHANGE_REQ = 0x09, // 0x0A ~ 0x0F PLC_WELD_READY_REQ = 0x10, PLC_WELD_WAIT = 0x11, @@ -57,8 +58,9 @@ namespace ILSOS_PrismaticRD_PTOP.Device PC_DOOR_INTERLOCK = 0x06, PC_EMO_STATUS = 0x07, PC_PAGE_CHANGE_COMPLETE = 0x08, - PC_EMPTY01 = 0x09, - // 0x09 ~ 0x0F + // 260618: M70009 Recipe 변경 완료 응답 (구 PC_EMPTY01, 미사용 → 재정의) + PC_RECIPE_CHANGE_COMPLETE = 0x09, + // 0x0A ~ 0x0F PC_WELD_READY_ACK = 0x10, PC_WELD_BUSY = 0x11, PC_WELD_COMPLETE = 0x12, @@ -81,7 +83,7 @@ namespace ILSOS_PrismaticRD_PTOP.Device { PLC_ERROR_CODE = 0X00, PLC_PAGE_NO, - PLC_STEP_NO, + PLC_RECIPE_NO, // 260618: D70502 Recipe No (구 PLC_STEP_NO, index 2 유지) PLC_CURRENT_SLOT_NO, // EL / CLEAN 사용 EMPTY_04, EMPTY_05, @@ -149,7 +151,7 @@ namespace ILSOS_PrismaticRD_PTOP.Device { PC_ERROR_CODE = 0X00, PC_PAGE_NO, - EMPTY_02, + PC_RECIPE_NO, // 260618: D70002 Recipe No 에코 (구 EMPTY_02, index 2 유지) EMPTY_03, EMPTY_04, EMPTY_05, diff --git a/Machines/ILSOS_PrismaticRD_PTOP/Sequences/Monitoring/SeqStatusMonitor.cs b/Machines/ILSOS_PrismaticRD_PTOP/Sequences/Monitoring/SeqStatusMonitor.cs index e944515..f9d5603 100644 --- a/Machines/ILSOS_PrismaticRD_PTOP/Sequences/Monitoring/SeqStatusMonitor.cs +++ b/Machines/ILSOS_PrismaticRD_PTOP/Sequences/Monitoring/SeqStatusMonitor.cs @@ -103,7 +103,9 @@ namespace WeldingCommon.Sequences private static SequenceActionResult UpdateIO(SequenceContext context, string id, SequenceActionResult param) { - Status.RMSUse = _plc.ReadBit(PLCControllerPTOP.eBitInput.PLC_RMS_USED).data; + // 260618: M70509(구 PLC_RMS_USED)를 Recipe 변경 요청(PLC_RECIPE_CHANGE_REQ)으로 재정의. + // RMS 핸들러는 비활성(EquipmentSetup) 상태이고 Status.RMSUse 소비처가 없어 읽기 제거. + //Status.RMSUse = _plc.ReadBit(PLCControllerPTOP.eBitInput.PLC_RMS_USED).data; Thread.Sleep(NODE_SLEEP); return SequenceAction.Ok(); } diff --git a/Machines/ILSOS_PrismaticRD_PTOP/Sequences/SeqAutoRunPTOP.cs b/Machines/ILSOS_PrismaticRD_PTOP/Sequences/SeqAutoRunPTOP.cs index 7c73dff..5f91676 100644 --- a/Machines/ILSOS_PrismaticRD_PTOP/Sequences/SeqAutoRunPTOP.cs +++ b/Machines/ILSOS_PrismaticRD_PTOP/Sequences/SeqAutoRunPTOP.cs @@ -55,6 +55,8 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences 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 = "[SeqAutoRunPTOP] CHECK RecipeNo"; // 260618 Recipe Change + 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 @@ -107,10 +109,12 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences private static TimeSpan _WeldingSpanTime; //TeacingRun 시퀀스에서 용접 소요시간 private int _CurrentPageNo; - private short plcPageNo = 0;// [0] 페이지 1 =음극 A [1]페이지 2= 양극 P(C) + 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() @@ -119,6 +123,8 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences sb.AddAction(SEQ_ENTER, Sequence_Enter); sb.AddDecision(SEQ_CHECK_INIT_CONDITION, CheckInitCondition); //분기 + sb.AddDecision(SEQ_CHECK_RECIPENO, CheckRecipeNo); // 260618 Recipe(상위) — 논블로킹 peek 분기 + sb.AddAction(SEQ_CHECK_RECIPENO_COMPLETE, CheckRecipeNoComp); // 260618 Recipe 확정/활성화 sb.AddAction(SEQ_CHECK_PAGENO, CheckPageNo); sb.AddAction(SEQ_CHECK_PAGENO_COMPLETE, CheckPageNoComp); // 260513 ㅍㅔㅇㅣㅈㅣ ㅅㅓㄹㅈㅓㅇ @@ -144,6 +150,8 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences sb.Flows(sb.Init > sb[SEQ_ENTER] > sb[SEQ_CHECK_INIT_CONDITION] + > sb[SEQ_CHECK_RECIPENO] // 260618 Recipe 먼저(상위) + > sb[SEQ_CHECK_RECIPENO_COMPLETE] // REQ ON 경로 > sb[SEQ_CHECK_PAGENO] > sb[SEQ_CHECK_PAGENO_COMPLETE] @@ -162,7 +170,8 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences > sb[SEQ_COMPLETE] // 다시 enter로 올리기 > sb.Final); - sb.Flows(sb[SEQ_CHECK_CYCLE_STOP] > sb[SEQ_CHECK_PAGENO]); + sb.Flows(sb[SEQ_CHECK_CYCLE_STOP] > sb[SEQ_CHECK_RECIPENO]); // 260618 루프백: 매 사이클 Recipe부터 재확인 + sb.Flows(sb[SEQ_CHECK_RECIPENO] > sb[SEQ_CHECK_PAGENO]); // 260618 REQ OFF 경로(논블로킹): Recipe 유지하고 Page로 sb.Flows(sb[SEQ_CHECK_INIT_CONDITION] > sb[SEQ_COMPLETE] > sb.Final); sb.OnStopped(OnStopped); sb.OnForceError(OnForceError); @@ -211,8 +220,11 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences MessageUtil.ShowToast("Sequence_Enter"); //Thread.Sleep(1000); + // 260618 RecipeManager 미리 확보(Recipe Change 노드 CheckRecipeNoComp에서 사용) + _RecipeManager = Application.Current.AuroraEnv().RecipeManager; + // 활성화된레시피ID - var activatedRecipeId = Application.Current.AuroraEnv().RecipeManager.ActivatedRecipe().Id; + var activatedRecipeId = _RecipeManager.ActivatedRecipe().Id; var recipeName = RecipeHelper.SearchRecipeItem(activatedRecipeId); _InitParam = param.Param as SeqTeachingRun_InitParam; // 260511 - param NULL @@ -271,14 +283,113 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences // return SEQ_COMPLETE; //} Thread.Sleep(5); - Log.I($"[CheckInitCondition] 조건 확인 완료 → CheckPageNo 이동"); - return SEQ_CHECK_PAGENO; + Log.I($"[CheckInitCondition] 조건 확인 완료 → CheckRecipeNo 이동"); + return SEQ_CHECK_RECIPENO; } private int _CenterBeamPower; private int _RingBeamPower; private int _CenterRingRatio; - // 페이지 체크 - 페이지만 감시 AutoRunStatus는 실제 상위에서 동작 체크하고 시퀸스 동작한다. + // ── 260618 Recipe Change 핸드셰이크 (Page의 상위 개념. 기존 CheckPageNo/CheckPageNoComp 미러) ── + // CheckRecipeNo: 논블로킹 peek(결정 2). REQ OFF면 현 Recipe 유지하고 즉시 Page로, ON이면 Comp로. + private string CheckRecipeNo(SequenceContext context, string id, ICollection branches, SequenceActionResult param) + { + if (_PLC == null) + { + Log.E("[CheckRecipeNo] _PLC instance is null! Recipe 건너뛰고 CheckPageNo 이동"); + return SEQ_CHECK_PAGENO; + } + + // Page와 달리 ON까지 무한대기하지 않는다(논블로킹 1회 확인) + bool req = _PLC.ReadBit(eBitInput.PLC_RECIPE_CHANGE_REQ).data; + if (!req) + { + Log.I("[CheckRecipeNo] PLC_RECIPE_CHANGE_REQ OFF → 현 Recipe 유지, CheckPageNo 이동"); + return SEQ_CHECK_PAGENO; + } + + Log.I("---> [CheckRecipeNo] PLC_RECIPE_CHANGE_REQ ON 감지 → CheckRecipeNoComp 이동"); + return SEQ_CHECK_RECIPENO_COMPLETE; + } + + // CheckRecipeNoComp: Recipe No 읽기(3회 검증) → .Id 직접 1:1 조회 → 활성화(반영 폴링) → 에코/완료비트(결정 3 미러형) + private SequenceActionResult CheckRecipeNoComp(SequenceContext context, string id, SequenceActionResult param) + { + Log.I("[CheckRecipeNoComp] PLC Recipe 번호 읽기 시작 (3회 연속 검증)"); + + int checknum = 0; + var recipeNoList = new List(); + + while (!context.Stopped) + { + recipeNoList.Add(_PLC.ReadWord(eWordInput.PLC_RECIPE_NO).data); + plcRecipeNo = recipeNoList[checknum]; + checknum++; + Thread.Sleep(200); + + Log.I($"[CheckRecipeNoComp] Recipe 확인 [{checknum}/3] - PLC_Recipe: [{plcRecipeNo}]"); + + if (checknum > 2) + { + Log.I($"[CheckRecipeNoComp] List {recipeNoList[0]}/{recipeNoList[1]}/{recipeNoList[2]}"); + break; + } + } + if (context.Stopped) return null; + + // 레시피 조회 (결정 4 — .Id 직접 1:1, 유효값은 등록 레시피로 검증 / 하드코딩 금지). 선례: RMSHandlerForPTOP + // _RecipeManager는 Enter 노드에서 미리 확보됨. + var recipe = _RecipeManager.Recipes().FirstOrDefault(x => x.Id == plcRecipeNo); + if (recipe == null) + { + Log.E($"[CheckRecipeNoComp] {plcRecipeNo}번 레시피 없음"); + MessageUtil.ShowErrorToast($"PLC Recipe No [{plcRecipeNo}] 에 해당하는 레시피가 없습니다."); + Thread.Sleep(3000); + return null; + } + + // 활성화 + 반영 확인 (비동기 race 주의: ActivatedRecipe().Id 일치까지 폴링) + var preActivated = _RecipeManager.ActivatedRecipe(); + if (preActivated == null || preActivated.Id != recipe.Id) + { + _RecipeManager.ActivateRecipe(recipe.Id, "PLC Recipe Change"); + Log.I($"[CheckRecipeNoComp] ActivateRecipe ({(preActivated == null ? -1 : preActivated.Id)} -> {recipe.Id})"); + + 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; + } + else + { + Log.I($"[CheckRecipeNoComp] 이미 활성 레시피({recipe.Id}) → 재활성화 생략"); + } + + Thread.Sleep(5); + + // 에코 + 완료 비트 (결정 3 — 기존 Page 미러형: COMPLETE 비트만 처리, REQ-OFF 대기 없음) + _PLC.WriteWord(eWordOutput.PC_RECIPE_NO, plcRecipeNo); + Log.I($"---> [CheckRecipeNoComp] Set PC_RECIPE_NO: {plcRecipeNo}"); + + _PLC.WriteBit(eBitOutput.PC_RECIPE_CHANGE_COMPLETE, false); + Log.I("[CheckRecipeNoComp] Set PC_RECIPE_CHANGE_COMPLETE false"); + + Thread.Sleep(5); + Log.I($"[CheckRecipeNoComp] Recipe 확정 - PC_RECIPE_NO: [{plcRecipeNo}] 에코 완료 → CheckPageNo 이동"); + return SequenceAction.Ok(); + } + + // 페이지 체크 - 페이지만 감시 AutoRunStatus는 실제 상위에서 동작 체크하고 시퀸스 동작한다. private SequenceActionResult CheckPageNo(SequenceContext context, string id, SequenceActionResult param) { Log.I($"[CheckPageNo] PLC_PAGE_CHANGE_REQ 신호 대기 중..."); @@ -372,9 +483,11 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences if (checknum > 2) { - if (plcPageNo > 2 || plcPageNo < 1) + // 260618: 상한 하드코딩(>2) 제거 → 활성 Recipe의 페이지 수(레이어 수)로 가변 검증 (Recipe별 2~5페이지) + int recipePageCount = _ScannerCanvasUserCtrl.Model.Layers.Count(); + if (plcPageNo > recipePageCount || plcPageNo < 1) { - Log.E($"[CheckPageNoComp] Invalid PageNo received: {plcPageNo}"); + Log.E($"[CheckPageNoComp] Invalid PageNo received: {plcPageNo} (활성 Recipe 페이지 수: {recipePageCount})"); // 디버그 끝내고 삭제 //MessageUtil.ShowErrorToast($"ReadWord -> {plcPageNo}"); @@ -711,8 +824,8 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences Log.I($"[CheckCycleStop] 사이클 중지 여부 확인 중"); Status.SetWorkingPosition(eWorkingPosition.CHECKING_CYCLE); - Log.I($"[CheckCycleStop] 사이클 계속 진행 → CheckPageNo 이동"); - return SEQ_CHECK_PAGENO; + Log.I($"[CheckCycleStop] 사이클 계속 진행 → CheckRecipeNo 이동"); + return SEQ_CHECK_RECIPENO; } private SequenceActionResult SaveResultData(SequenceContext context, string id, SequenceActionResult param) @@ -805,7 +918,8 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences { Log.I($"[RestoreDeviceStatus] 노드 Start"); ////PLC All BIT OFF - _PLC.WriteBit((int)eCommonBitOutput.PC_PAGE_CHANGE_COMPLETE, false); // 페이지 false + _PLC.WriteBit((int)eCommonBitOutput.PC_PAGE_CHANGE_COMPLETE, false); // 페이지 false + _PLC.WriteBit((int)eCommonBitOutput.PC_RECIPE_CHANGE_COMPLETE, false); // 260618 레시피 false _PLC.WriteBit((int)eCommonBitOutput.PC_WELD_READY_ACK, false); // 용접 준비 false _PLC.WriteBit((int)eCommonBitOutput.PC_WELD_BUSY, false); // 동작 중 false @@ -980,11 +1094,13 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences Log.I("[OnSeqActionStop] PLC Cathod(+) Output All false"); _PLC.WriteBit((int)eBitOutput.PC_PAGE_CHANGE_COMPLETE, false); + _PLC.WriteBit((int)eBitOutput.PC_RECIPE_CHANGE_COMPLETE, false); // 260618 레시피 완료비트 init 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"); diff --git a/Machines/ILSOS_PrismaticRD_PTOP/Sequences/SeqAutoRunPrismatic.cs b/Machines/ILSOS_PrismaticRD_PTOP/Sequences/SeqAutoRunPrismatic.cs index 26d91e1..ce8d584 100644 --- a/Machines/ILSOS_PrismaticRD_PTOP/Sequences/SeqAutoRunPrismatic.cs +++ b/Machines/ILSOS_PrismaticRD_PTOP/Sequences/SeqAutoRunPrismatic.cs @@ -419,7 +419,7 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences _PreviousTotalWeldingStep = _TotalWeldingStep; _PageNo = resultOutParam.PLCPageNo; // Total은 항상 같으므로 상관 없을 듯. - _TotalWeldingStep = Convert.ToInt16(_PLC.ReadWord(eWordInput.PLC_STEP_NO).data); + _TotalWeldingStep = Convert.ToInt16(_PLC.ReadWord(eWordInput.PLC_RECIPE_NO).data); // 260618: index 2 enum rename(PLC_STEP_NO→PLC_RECIPE_NO), 레거시 참조 이름만 교체(로직 불변) _SequenceSetting.LastPageNo = _PageNo; context.Settings.Save(CommonSettingIds.Sequence, _SequenceSetting, "SeqAutoRunPrismatic_New"); diff --git a/WeldingComponents/WeldingCommon/Devices/PLC/PLCController.Address.cs b/WeldingComponents/WeldingCommon/Devices/PLC/PLCController.Address.cs index 3350c71..90fac33 100644 --- a/WeldingComponents/WeldingCommon/Devices/PLC/PLCController.Address.cs +++ b/WeldingComponents/WeldingCommon/Devices/PLC/PLCController.Address.cs @@ -265,7 +265,8 @@ namespace WeldingCommon.Devices PC_DOOR_INTERLOCK = 0x06, PC_EMO_STATUS = 0x07, PC_PAGE_CHANGE_COMPLETE = 0x08, - // 0x09 ~ 0x0F + PC_RECIPE_CHANGE_COMPLETE = 0x09, // 260618: PTOP enum과 동기화 (RestoreDeviceStatus가 common enum 사용) + // 0x0A ~ 0x0F PC_WELD_READY_ACK = 0x10, PC_WELD_BUSY = 0x11, PC_WELD_COMPLETE = 0x12, diff --git a/docs/PLC_RecipeChange_구현_수정가이드.md b/docs/PLC_RecipeChange_구현_수정가이드.md new file mode 100644 index 0000000..131d8fa --- /dev/null +++ b/docs/PLC_RecipeChange_구현_수정가이드.md @@ -0,0 +1,226 @@ +# PLC Recipe Change 인터페이스 구현 — 수정 가이드 + +> 작성일: 2026-06-18 +> 대상 시퀀스: **ILSOS_PrismaticRD_PTOP / SeqAutoRunPTOP** (현재 활성 AutoRun) +> 근거 문서: `Doc/유니셀 PtoP 단동기 Laser Interface_260618.xlsx` (시트 `M70K`, `D70K`, `Recipe Number`) +> 이 문서는 **다른 agent가 읽고 직접 구현**하기 위한 구현 명세서다. 분석은 끝났고, 아래 절차대로 코드를 추가하면 된다. +> 기존 **Page Change** 핸드셰이크를 그대로 본떠서 **Recipe Change** 핸드셰이크를 추가하는 것이 목표다. + +--- + +## 1. 목표 + +PLC가 **Recipe 변경을 요청**하면, PC(Laser SW)가 해당 Recipe를 활성화하고 완료를 응답하는 **비트/워드 핸드셰이크**를 추가한다. +구조는 기존 Page Change와 동일하며, **Recipe가 상위 개념, Page가 하위 개념**이다 (Recipe 1개 = Page 여러 개). + +핵심 원칙: +- 신규 코드는 **기존 Page Change 노드(`CheckPageNo` / `CheckPageNoComp`)를 미러링**한다. +- **런타임 실행** 시퀀스는 **`SeqAutoRunPTOP`** 하나뿐이다 (시작 근거: `UCMainAutoRunViewModel.cs:243`, 등록 `EquipmentSetup.cs:214`). +- ⚠️ 단, `SeqAutoRunPrismatic`는 **빌드 대상이다**: `EquipmentSetup.cs:202`에서 `.Builder()`가 호출되고 `:207`에서 `SequenceIds.SeqAutoRun`으로 **등록**되어 있다(다만 `.Start()`는 호출 안 됨 → 런타임 미실행). `SeqAutoRunComponentWeld`도 `:203`에서 빌드된다. 즉 **"미사용=미실행"이지 "미컴파일"이 아니다.** enum rename(충돌 ②) 시 이들이 깨질 수 있으니 STEP 1·3절을 반드시 확인. + +--- + +## 2. 인터페이스 사양 (Excel 260618 기준) + +> **출처/변경 표기**: 엑셀에는 변경이력 시트·셀 코멘트가 없고, 이번 개정의 신규 주소는 **빨간 글씨(font FFFF0000)** 로만 표시돼 있다. 워크북 전체 빨간 셀은 **5개**: `Recipe_CHGRequest`(M70509), `Recipe_CHGComplete`(M70009), `PLC_RecipeNo`(D70502), `PC_Recipe No`(D70002) — 본 문서가 다루는 4개 — 과 **별개 1개 `Head_Theta_Position`(D70510, Recipe 무관, 본 문서 범위 외)**. 즉 아래 표의 "신규" 항목은 모두 엑셀 빨간 글씨 근거. + +### 2-1. 비트 핸드셰이크 — Page 대비 **오프셋 +1** + +| 신호 | 방향 | M-Device | enum / 오프셋 | 비고 | +|---|---|---|---|---| +| `Page_CHGRequest` (기존) | PLC→PC (Input) | M70508 | `eBitInput.PLC_PAGE_CHANGE_REQ = 0x08` | 참고용 | +| **`Recipe_CHGRequest` (신규)** | **PLC→PC (Input)** | **M70509** | **0x09** | ⚠️ **충돌**: 현재 `PLC_RMS_USED = 0x09` | +| `Page_CHGComplete` (기존) | PC→PLC (Output) | M70008 | `eBitOutput.PC_PAGE_CHANGE_COMPLETE = 0x08` | 참고용 | +| **`Recipe_CHGComplete` (신규)** | **PC→PLC (Output)** | **M70009** | **0x09** | 현재 `PC_EMPTY01 = 0x09` (미사용, 안전) | + +### 2-2. 워드 — Page 대비 **인덱스 +1** + +| 신호 | 방향 | D-Device | enum index | 비고 | +|---|---|---|---|---| +| `PLC_PageNo` (기존) | PLC→PC (Input) | D70501 | `eWordInput.PLC_PAGE_NO = 1` | 참고용 | +| **`PLC_RecipeNo` (신규)** | **PLC→PC (Input)** | **D70502** | **index 2** | ⚠️ **충돌**: 현재 `PLC_STEP_NO`(index 2) | +| `PC_PageNo` (기존) | PC→PLC (Output) | D70001 | `eWordOutput.PC_PAGE_NO = 1` | 참고용 | +| **`PC_RecipeNo` (신규)** | **PC→PLC (Output)** | **D70002** | **index 2** | 현재 `EMPTY_02`(index 2) (미사용, 안전) | + +> 주소 베이스: `eBitInput`=M70500, `eBitOutput`=M70000, `eWordInput`=D70500, `eWordOutput`=D70000. +> (⚠️ 베이스 절대주소 자체는 enum 코드가 아니라 외부 device-map/mmap 설정에서 온다 → 본 표의 오프셋 산술은 Excel·디바이스맵 기준이며 코드만으로는 베이스 검증 불가.) +> 정의 위치: `Machines/ILSOS_PrismaticRD_PTOP/Device/PLC/PLCController.Address.cs` +> (eBitInput L13–47, eBitOutput L49–78, eWordInput L80–146, eWordOutput L148–214) + +### 2-3. Recipe Number ↔ Page 매핑 (`Recipe Number` 시트) + +| Recipe No | Page 수 | 용접부 | +|---|---|---| +| 11 | 1, 2 | 양극 탭 비드 용접 / 음극 탭 비드 용접 | +| 21 | 1 ~ 5 | 전면 용접 | +| 22 | 1 ~ 5 | 후면 용접 | +| 31 | 1, 2 | 상면 용접 | +| 32 | 1, 2 | 하면 용접 | + +> **중요**: Recipe No는 `11/21/22/31/32` 로 **불연속**이며, Recipe마다 **Page 수가 2~5개로 다르다**(파일 갱신으로 21/22가 4→5페이지). 특정 상한을 절대 하드코딩하지 말 것. +> 따라서 기존 `CheckPageNoComp`의 하드코딩 검증 `plcPageNo > 2 || plcPageNo < 1`(`SeqAutoRunPTOP.cs:370`)은 **Recipe별 Page 수에 따라 가변**으로 바꿔야 한다. (STEP 6 참고) + +--- + +## 3. ⚠️ 주소 충돌 — 구현 전 반드시 확인할 것 + +### 충돌 ① `PLC_RMS_USED = 0x09` (eBitInput) — **현재 사용 중** +- 사용처: `Sequences/Monitoring/SeqStatusMonitor.cs:106` + ```csharp + Status.RMSUse = _plc.ReadBit(PLCControllerPTOP.eBitInput.PLC_RMS_USED).data; + ``` +- Excel 260618은 M70509(=0x09)를 `Recipe_CHGRequest`로 재정의함. +- **즉 동일 비트(0x09)가 RMS_USED 상태와 Recipe 변경 요청으로 충돌한다.** +- **결정 필요(PLC 담당자 확인)**: RMS_USED 비트를 폐기/이설하고 0x09를 Recipe_CHGRequest로 넘길 것인지. → 4절 "미해결 질문" 참조. + +### 충돌 ② `PLC_STEP_NO` (eWordInput index 2) — **레거시에서 컴파일됨** +- 사용처: `Sequences/SeqAutoRunPrismatic.cs:414` +- 활성 `SeqAutoRunPTOP`는 PLC_STEP_NO를 쓰지 않으나, `SeqAutoRunPrismatic`는 **등록·빌드 대상**(`EquipmentSetup.cs:202,207`)이다. +- ⚠️ **빌드 영향**: `PLC_STEP_NO` → `PLC_RECIPE_NO`로 **이름만 바꾸면 `:414`가 컴파일 에러**가 난다. +- **결정 필요(택1)**: + - (a) `:414`도 같이 정리(레거시 수정 허용 시) — 6절 "레거시 미수정" 원칙과 충돌하므로 사용자 승인 필요, **또는** + - (b) `PLC_STEP_NO`(index 2)는 **그대로 두고**, Recipe용으로 **별도 신규 멤버를 추가**(단, eWordInput은 암묵 순번이라 index 2를 두 이름이 가질 수 없음 → 명시값 부여 `PLC_RECIPE_NO = 2`로 두되 `PLC_STEP_NO`도 `= 2`로 명시해 공존시키거나, PLC_STEP_NO를 의미상 PLC_RECIPE_NO로 **rename + :414 동시 수정**). + → 결국 index 2 한 칸을 누가 쓰느냐의 문제이므로 **(a) rename + :414 동시 수정**이 가장 깔끔. STEP 1에서 확정. + +### 안전 (그대로 rename 가능) +- `PC_EMPTY01 = 0x09` (eBitOutput): 참조 없음 → `PC_RECIPE_CHANGE_COMPLETE`로 교체 안전. +- `EMPTY_02` (eWordOutput index 2): 참조 없음 → `PC_RECIPE_NO`로 교체 안전. + +### ⚠️ enum 이중 정의 (Recipe 완료 비트 구현에 직접 영향) +- `PC_PAGE_CHANGE_COMPLETE`는 **두 곳에 0x08로 중복 정의**되어 있고 코드가 혼용한다: + - PTOP: `Device/PLC/PLCController.Address.cs` `eBitOutput.PC_PAGE_CHANGE_COMPLETE = 0x08` + - 공용: `WeldingComponents/WeldingCommon/Devices/PLC/PLCController.Address.cs` `eCommonBitOutput.PC_PAGE_CHANGE_COMPLETE = 0x08` +- 사용처가 갈린다: `RestoreDeviceStatus`(`SeqAutoRunPTOP.cs:803`)는 **`eCommonBitOutput`**, `OnSeqActionStop`(`:977`)은 **`eBitOutput`** 사용. +- **즉 신규 `PC_RECIPE_CHANGE_COMPLETE`를 PTOP `eBitOutput`에만 추가하면 `RestoreDeviceStatus`(common 경로)에서 못 쓴다.** → STEP 1에서 **양쪽 enum 동기화** 또는 캐스팅 통일 필요(STEP 4 참조). + +--- + +## 4. 확정된 설계 결정 (2026-06-18, 사용자 협의) + +| # | 결정 사항 | 채택안 | 비고 | +|---|---|---|---| +| 1 | **시퀀스 흐름/배치** | **매 사이클 Page 앞에서 확인** | `CheckInitCondition → CheckRecipeNo → CheckRecipeNoComp → CheckPageNo …`. 루프백(CheckCycleStop)은 **CheckRecipeNo로** 회귀. | +| 2 | **CheckRecipeNo 동작** | **논블로킹 peek** | Page와 달리 REQ ON까지 무한대기 ❌. REQ OFF면 현 Recipe 유지하고 즉시 Page로 통과. | +| 3 | **핸드셰이크 형태** | **기존 Page 미러형(B)** | `CheckPageNoComp` 방식 그대로. COMPLETE 비트만 처리, REQ-OFF 대기 없음(일관성 우선). | +| 4 | **RecipeNo→레시피 매핑** | **`.Id` 직접 1:1** | `Recipes().FirstOrDefault(x => x.Id == plcRecipeNo)` → 없으면 알람 → `ActivateRecipe(recipe.Id, "PLC Recipe Change")`. 선례: `RMSHandlerForPTOP.cs:125-173`. | +| 5 | **비트 0x09 충돌(①)** | **RMS_USED 폐기 → Recipe 전환** | `PLC_RMS_USED`→`PLC_RECIPE_CHANGE_REQ`. `SeqStatusMonitor.cs:106` RMSUse 읽기 제거/주석. RMS 핸들러 이미 비활성(`EquipmentSetup.cs:92-94`)이라 기능 영향 없음. | +| 6 | **Page 수 가변(2~5)** | **런타임 동적 산출** | 상한 하드코딩 금지. `Models.Layers.Count()` 또는 활성 Recipe 메타 기준(STEP 6). | +| 7 | **워드 idx 2 충돌(②)** | **rename + 레거시 :414 동시 수정** | `PLC_STEP_NO`→`PLC_RECIPE_NO` rename, `SeqAutoRunPrismatic.cs:414`의 참조도 **이름만** 교체(로직 불변). 사용자 승인 완료. | + +### 사양 전제 (PLC 담당자와 정합성 확인 권장, 코드 결정 아님) +- PLC가 보내는 RecipeNo가 실제 등록 레시피의 `.Id`(`11/21/22/31/32`)와 일치한다는 전제. 불일치 시 결정 4를 매핑 테이블로 보강. + +> ⚠️ 참고 — **기존 활성 Page Change 핸드셰이크는 미완결**이다. `CheckPageNoComp`(`SeqAutoRunPTOP.cs:408`)는 `PC_PAGE_CHANGE_COMPLETE`를 **false로만** 쓴다. 결정 3(미러형)에 따라 Recipe도 이 방식을 **그대로** 본뜬다(향후 Page 핸드셰이크가 보정되면 Recipe도 동일하게 따라간다). + +--- + +## 5. 수정 절차 + +### STEP 1 — 주소 enum 추가 +파일: `Machines/ILSOS_PrismaticRD_PTOP/Device/PLC/PLCController.Address.cs` + +- `eBitInput`: `PLC_RMS_USED = 0x09` → **`PLC_RECIPE_CHANGE_REQ = 0x09`** 로 rename (결정 5). 동시에 `SeqStatusMonitor.cs:106`의 `Status.RMSUse = _plc.ReadBit(...PLC_RMS_USED)` 줄을 **제거 또는 주석** 처리(`Status.RMSUse`는 write-only·소비처 0건이라 안전). + - ⚠️ 공용 `WeldingCommon/Devices/PLC/PLCController.Address.cs`의 `eCommonBitInput.PLC_RMS_USED = 0x09`는 **별도 enum이며 참조 0건** → 빌드 영향 없음. 이름 일관성을 원하면 동반 rename(선택). +- `eBitOutput`: `PC_EMPTY01 = 0x09` → `PC_RECIPE_CHANGE_COMPLETE = 0x09` 로 교체. +- `eWordInput`: index 2(충돌 ②, 결정 7) `PLC_STEP_NO` → `PLC_RECIPE_NO` rename + **`SeqAutoRunPrismatic.cs:414`의 참조도 이름만 교체**(로직 불변, 승인 완료). +- `eWordOutput`: `EMPTY_02` → `PC_RECIPE_NO` 로 교체. +- **공용 enum 동기화(필수)**: 위 PTOP enum과 동일하게 `WeldingCommon/Devices/PLC/PLCController.Address.cs`의 `eCommonBitOutput` 0x09 자리에도 `PC_RECIPE_CHANGE_COMPLETE = 0x09`를 추가한다. (STEP 4의 `RestoreDeviceStatus`가 `eCommonBitOutput`을 쓰기 때문.) — §3 "enum 이중 정의" 참조. + +> enum은 **암묵적 순번**(eWordInput/eWordOutput)이라 멤버 **이름만** 바꾸면 인덱스는 유지된다. 새 값을 끼워 넣어 뒤 순번이 밀리지 않도록 **이름 교체** 방식으로 처리할 것. +> ⚠️ index 2는 한 칸뿐이라 `PLC_STEP_NO`와 `PLC_RECIPE_NO`가 공존할 수 없다 → rename 단일화가 원칙(충돌 ② 참조). + +### STEP 2 — Recipe Change 시퀀스 노드 추가 +파일: `Machines/ILSOS_PrismaticRD_PTOP/Sequences/SeqAutoRunPTOP.cs` + +기존 `CheckPageNo`(L277) / `CheckPageNoComp`(L331)를 본떠 **`CheckRecipeNo` / `CheckRecipeNoComp`** 추가. (결정 1~5 반영) + +- **`CheckRecipeNo` (논블로킹 peek — 결정 2)**: `_PLC.ReadBit(eBitInput.PLC_RECIPE_CHANGE_REQ)`를 **1회 확인**. + - REQ **OFF** → Recipe 변경 없음. **즉시 `CheckPageNo`로 통과**(현 Recipe 유지). ⚠️ `CheckPageNo`처럼 ON까지 무한대기하지 **말 것.** + - REQ **ON** → `CheckRecipeNoComp`로 진행. + - **분기는 Decision 노드로 구현**(결정 2 확정) — STEP 3 참고. +- **`CheckRecipeNoComp`**: + 1. `_PLC.ReadWord(eWordInput.PLC_RECIPE_NO)` 읽기 (필요 시 `CheckPageNoComp`의 3회 적재 패턴 재사용). + 2. **레시피 조회/매핑 (결정 4 — `.Id` 직접 1:1)**: + ```csharp + var recipe = AuroraEnv.RecipeManager /* 또는 _recipeEntry */ + .Recipes().FirstOrDefault(x => x.Id == plcRecipeNo); + if (recipe == null) { /* "{plcRecipeNo}번 레시피 없음" 알람 */ return null; } + ``` + - 선례 그대로: `RMSHandlerForPTOP.cs:125-130`(조회/없음 알람), `:173`(활성화). + 3. **Recipe 활성화**: `RecipeManager.ActivateRecipe(recipe.Id, "PLC Recipe Change")` + - 현재값 조회: `RecipeManager.ActivatedRecipe().Id`. + - ⚠️ **반영 확인 필수(비동기 race 주의)**: `ActivateRecipe`는 `EVENT_CHANGE_RECIPE_INFO` 이벤트 체인으로 반영되며, RMS 선례(`RMSHandlerForPTOP.cs:134,145`)는 `Dispatcher.Invoke`로 UI스레드에서 Reload/Save한다. 본 시퀀스는 **워커 스레드**이므로, 활성화 직후 `ActivatedRecipe().Id == recipe.Id` 가 될 때까지 **폴링/대기**한 뒤 다음(PageNo)으로 진행할 것. 누락 시 직후 Page 검증이 **이전 레시피 기준**으로 돌 위험. + 4. `_PLC.WriteWord(eWordOutput.PC_RECIPE_NO, plcRecipeNo)` 에코. + 5. **핸드셰이크 응답 (결정 3 — 기존 Page 미러형)**: `CheckPageNoComp`(`:401-409`)의 COMPLETE 비트 처리 방식을 **그대로** 본떠 `PC_RECIPE_CHANGE_COMPLETE` 처리. **REQ-OFF 대기 단계는 두지 않는다**(현 Page와 동일). + 6. (선택) UI 알림: `EVENT_AUTO_RUN_UPDATE_PAGENUM` 패턴 본떠 Recipe 표시 이벤트 publish (STEP 5). + +### STEP 3 — 상태머신에 노드 연결 +같은 파일 `Builder()` (L114–171). 현재 흐름(L142–163): +``` +ENTER → CheckInitCondition → CheckPageNo → CheckPageNoComp → (UploadWeldingList) → ... → CheckCycleStop ─(loop)→ CheckPageNo +``` +Recipe는 Page의 상위이므로 **`CheckPageNo` 앞에** 삽입 (결정 1): +``` +... CheckInitCondition → CheckRecipeNo → CheckRecipeNoComp → CheckPageNo → CheckPageNoComp → ... +``` +- `sb.AddAction(SEQ_CHECK_RECIPENO, CheckRecipeNo);` +- `sb.AddAction(SEQ_CHECK_RECIPENO_COMPLETE, CheckRecipeNoComp);` +- 본 흐름(`Builder()` L142-161)에 두 노드를 `CheckInitCondition`과 `CheckPageNo` 사이에 끼움. +- **분기(결정 2 논블로킹)**: `CheckRecipeNo`를 **Decision 노드(`sb.AddDecision`)**로 만들어 `REQ ON → SEQ_CHECK_RECIPENO_COMPLETE`, `REQ OFF → SEQ_CHECK_PAGENO` 문자열을 반환 분기(Decision은 다음 노드 id 문자열을 반환 — `CheckInitCondition` `:259,269,273` 패턴). + - ⚠️ **엣지 선언 필수**: Decision의 반환 타깃이 유효하려면 두 분기 엣지가 그래프에 모두 존재해야 한다. 메인 Flows에 ON 경로(`CheckRecipeNo > CheckRecipeNoComp > CheckPageNo`)를 넣고, OFF 경로는 **별도로** `sb.Flows(sb[SEQ_CHECK_RECIPENO] > sb[SEQ_CHECK_PAGENO]);` 를 추가 선언할 것(선례: `CheckCycleStop`의 대체 분기 `:163`, `CheckInitCondition`의 `:164`). +- **루프백 변경**: 현재 `sb.Flows(sb[SEQ_CHECK_CYCLE_STOP] > sb[SEQ_CHECK_PAGENO]);`(L163)을 **`> sb[SEQ_CHECK_RECIPENO]`** 로 바꿔, 다음 사이클마다 Recipe부터 재확인. + +### STEP 4 — 리셋/정지 시 비트 초기화 +같은 파일: +- `RestoreDeviceStatus()` (L799~803): 이 메서드는 **`eCommonBitOutput`** 을 쓴다(`:803`). 따라서 `eCommonBitOutput.PC_RECIPE_CHANGE_COMPLETE` false 를 추가 — **STEP 1에서 공용 enum에 멤버를 추가했어야 컴파일된다.** (안 했으면 여기서 빌드 에러) +- `OnSeqActionStop` 계열(L977 비트, L981-982 워드, PC_PAGE 초기화 지점): 이쪽은 **`eBitOutput`/`eWordOutput`(PTOP)** 사용. `PC_RECIPE_CHANGE_COMPLETE` false, `PC_RECIPE_NO` 0 추가. +- ⚠️ 두 지점이 서로 다른 enum(common vs ptop)을 쓰므로, **사용하는 enum을 지점별로 맞출 것**(혼동 시 컴파일 에러). + +### STEP 5 — (선택) UI 표시 +Recipe 번호를 UI에 노출하려면 `Views/Main/UCMainAutoRunViewModel.cs`의 `CurrentPageNum`(L71-72) 패턴을 본떠 `CurrentRecipeNo` 속성 + 이벤트 구독 추가. + +### STEP 6 — Page 검증 Recipe-aware 화 +`CheckPageNoComp`의 페이지 범위 검증 `plcPageNo > 2 || plcPageNo < 1`(`SeqAutoRunPTOP.cs:370`)을 **활성 Recipe의 Page 수**(질문 5)에 따른 상한으로 교체. +- ⚠️ 이 검증은 **3회 폴링 게이트 `if (checknum > 2)`(`:368`) 안**에 있다. 상한만 바꾸고 `checknum > 2` 누적 폴링 구조(`plcPageNoList` 3회 적재)는 그대로 유지할 것. +- Page 수 출처 후보: 활성 Recipe 메타, 또는 `Models.Layers.Count()`(레이어 수). Recipe 21/22는 5페이지, 11/31/32는 2페이지(2-3절 표) — **상한 하드코딩 금지, 런타임 동적 산출.** + +--- + +## 6. 변경 대상 파일 요약 + +| 파일 | 변경 | +|---|---| +| `ILSOS_PrismaticRD_PTOP/Device/PLC/PLCController.Address.cs` | PTOP enum 4종에 Recipe 신호 추가/교체 (STEP 1) | +| `WeldingCommon/Devices/PLC/PLCController.Address.cs` | `eCommonBitOutput`에 `PC_RECIPE_CHANGE_COMPLETE=0x09` 동기화 (STEP 1·4) | +| `Sequences/SeqAutoRunPTOP.cs` | 노드 2개 추가, Builder 흐름, 리셋, Page 검증 (STEP 2·3·4·6) | +| `Views/Main/UCMainAutoRunViewModel.cs` | (선택) Recipe 표시 (STEP 5) | +| `Sequences/Monitoring/SeqStatusMonitor.cs` | 충돌 ① 처리 시 RMS_USED 참조 정리 | +| `Sequences/SeqAutoRunPrismatic.cs` | 충돌 ②(결정 7): `:414` PLC_STEP_NO → PLC_RECIPE_NO 이름만 교체 (로직 불변, 승인 완료) | + +> **런타임 미실행이지만 빌드 대상**인 레거시(`SeqAutoRunPrismatic.cs`, `SeqAutoRunComponentWeld.cs`)는 enum rename 영향이 미친다 — 컴파일 통과를 위해 필요한 최소 수정만 하되, 로직 변경은 하지 말 것. +> **완전 미참여 파일**(`SeqWaitWeldOrChangePage.cs`, `SeqTestRun.cs`)은 참고만 하고 수정하지 않는다. + +--- + +## 7. 검증 체크리스트 + +- [ ] STEP 1 enum 인덱스가 밀리지 않았는가 (이름 교체 방식 확인). +- [ ] 충돌 ①(RMS_USED) / 충돌 ②(STEP_NO) 처리 방침이 확정·반영되었는가. +- [ ] `recipeNo` 유효값 `{11,21,22,31,32}` 외 입력 시 알람/중단 처리. +- [ ] `ActivateRecipe` 호출 후 실제 레시피가 바뀌고 `ActivatedRecipe().Id`가 일치하는가. +- [ ] 핸드셰이크: REQ ON → (work) → COMPLETE 응답 → REQ OFF → COMPLETE 해제 순서가 PLC 사양과 맞는가. +- [ ] 정지/리셋 시 `PC_RECIPE_CHANGE_COMPLETE`, `PC_RECIPE_NO` 초기화. +- [ ] Recipe별 Page 수 가변 검증(STEP 6)이 11/21/22/31/32 모두에서 동작. +- [ ] 솔루션 빌드 통과(레거시 포함). 빌드 실패를 경고로 넘기지 말 것. + +--- + +## 8. 핵심 참고 코드 위치 (active path) + +- 상태머신: `SeqAutoRunPTOP.Builder()` — `SeqAutoRunPTOP.cs:114-171` (흐름 L142-163) +- Page REQ 대기: `CheckPageNo` — `SeqAutoRunPTOP.cs:277-328` +- Page 확정/에코: `CheckPageNoComp` — `SeqAutoRunPTOP.cs:331-422` +- 리셋: `RestoreDeviceStatus` — `SeqAutoRunPTOP.cs:799+` / `OnSeqActionStop` 인근 L976-981 +- 주소맵: `Device/PLC/PLCController.Address.cs` +- Recipe 활성화 API: `Recipes/RMSHandlerForPTOP.cs:173` (`RecipeManager.ActivateRecipe`), 조회 `:125-130` +- 레거시 완전형 핸드셰이크(참고): `Sequences/SeqWaitWeldOrChangePage.cs:251-318` diff --git a/docs/작업일지/2026_06_18.md b/docs/작업일지/2026_06_18.md index 8319a08..e91e5a2 100644 --- a/docs/작업일지/2026_06_18.md +++ b/docs/작업일지/2026_06_18.md @@ -129,3 +129,31 @@ - DA1/DA2 Voltage UniformGrid에 `Visibility="{Binding IsNoLaser, Converter={StaticResource convTrueVisible}}"` 추가. - Time이 Use Spot Time 체크 시 표시되는 것과 동일하게, **No Laser 체크(전압만) 시에만 DA1/DA2 Voltage 표시**, Laser Fire(해제) 시 숨김. + +## 14:33 + +### IOInterfaceView DI 경로 PTOP 전환 분석 (소스 무수정) + +- Monitoring IOInterfaceView의 DI 표시가 Scanner DI를 보던 원인 분석. `IODevice.MappingDIO`가 `IO_Board.json`의 `IOInterfaceMapDI`(절대경로)를 그대로 읽음(path-agnostic) → 소스 수정 불필요. +- PTOP 전환 = `D:\ILSOSConfig\ILSOS_PrismaticRD_PTOP\_config_device\IO_Board.json`의 DI/DO 경로를 PTOP로 + DI/DO.csv 존재 + DataPath 확인. (Device 설정에서 파일 위치만 지정하면 됨) +- 분석일지: `docs/분석/2026/06/18/143345_io_interface_di_path_ptop_analysis.md` + +## 15:30 + +### PLC Recipe Change 인터페이스 구현 (SeqAutoRunPTOP) + 주소/디바이스맵 정합 + +- 근거: `docs/PLC_RecipeChange_구현_수정가이드.md` + Excel `유니셀 PtoP 단동기 Laser Interface_260618.xlsx`(빨간셀 M70509/M70009/D70502/D70002). 기존 Page Change 핸드셰이크를 미러링해 Recipe Change(상위 개념) 추가. +- **STEP1 주소 enum** (`Device/PLC/PLCController.Address.cs`): + - eBitInput `PLC_RMS_USED`→`PLC_RECIPE_CHANGE_REQ`(0x09), eBitOutput `PC_EMPTY01`→`PC_RECIPE_CHANGE_COMPLETE`(0x09) + - eWordInput `PLC_STEP_NO`→`PLC_RECIPE_NO`(idx2), eWordOutput `EMPTY_02`→`PC_RECIPE_NO`(idx2) + - 공용 `eCommonBitOutput`에 `PC_RECIPE_CHANGE_COMPLETE=0x09` 동기화(RestoreDeviceStatus가 common enum 사용) + - 충돌: `SeqStatusMonitor`의 RMS_USED 읽기 주석(소비처 0), `SeqAutoRunPrismatic`의 PLC_STEP_NO 참조 이름만 교체(로직 불변) +- **STEP2~4,6 시퀀스** (`Sequences/SeqAutoRunPTOP.cs`): + - `CheckRecipeNo`(Decision, 논블로킹 peek: REQ OFF면 Page로, ON이면 Comp로) + `CheckRecipeNoComp`(3회 검증→`.Id` 1:1 조회→활성화+반영 폴링→에코/완료비트, Page 미러형) 추가 + - Builder: `CheckInitCondition → CheckRecipeNo → CheckRecipeNoComp → CheckPageNo`, REQ-OFF 엣지 추가, 루프백(CheckCycleStop)을 RecipeNo로 회귀 + - 리셋: `RestoreDeviceStatus`/`OnSeqActionStop`에 Recipe 비트·워드 초기화 + - `_RecipeManager`는 **Enter 노드에서 미리 확보**해 사용 + - STEP6: 페이지 상한 하드코딩(`>2`) 제거 → `_ScannerCanvasUserCtrl.Model.Layers.Count()` 가변 검증(Recipe별 2~5페이지) +- **CSV 디바이스맵 4종**(`D:\ILSOSConfig\…PTOP\External\PLC`): M70509/M70009/D70502/D70002 라벨을 enum과 정합(`.bak260618` 백업). enum 값/인덱스 ↔ CSV 주소 ↔ Excel 빨간셀 전부 1:1 일치 확인. +- STEP5(UI Recipe 표시)는 선택이라 생략. 핸드셰이크는 기존 Page 미러형(COMPLETE 비트만) — PLC 사양 최종 정합/base 절대주소는 PLC 담당자 확인 권장. +- ⚠️ 빌드는 VS Rebuild로 검증 필요(레거시 포함). 작업 중 Excel 워킹트리본(57249)을 커밋본(46119)으로 되돌린 이슈 있었음(도연님 확인 → 문제없음).