diff --git a/docs/PLC_RecipeChange_구현_수정가이드.md b/docs/PLC_RecipeChange_구현_수정가이드.md deleted file mode 100644 index 131d8fa..0000000 --- a/docs/PLC_RecipeChange_구현_수정가이드.md +++ /dev/null @@ -1,226 +0,0 @@ -# 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`