# ILSOS_PrismaticRD_PTOP Recipe 동적 활성화 분석 **분석 일시:** 2026-06-16 13:59:37 **대상:** SeqAutoRunPTOP.cs + Aurora RecipeManager 메커니즘 **목표:** PLC에서 RecipeNum + PageNum을 받아 동적으로 Recipe를 변경하고 공정별 페이지 선택하는 설계 --- ## 1. 현재 상황 분석 ### 1.1 현재 구조 ``` SeqAutoRunPTOP.cs (Line 215-216): ├─ activatedRecipeId = Application.Current.AuroraEnv().RecipeManager.ActivatedRecipe().Id ├─ recipeName = RecipeHelper.SearchRecipeItem(activatedRecipeId) └─ 활성화된 Recipe 1개에 대해서만 작동 └─ PLC에서 PageNum(1,2)만 받음 → 해당 Recipe의 페이지 선택 ``` ### 1.2 현재 제약사항 - **단일 Recipe 지원**: 미리 활성화된 Recipe 1개만 사용 - **동적 변경 불가**: Recipe 활성화 코드 없음 (UI에서만 가능) - **공정 분리 불가**: 3개 공정(CC, CAP, U-Frame)을 구분할 수 없음 --- ## 2. Aurora RecipeManager 메커니즘 분석 ### 2.1 Recipe 활성화 방법 **발견 위치:** `RMSHandlerForScanner.cs` Line 173 ```csharp // Recipe 활성화 핵심 메서드 _aurora.RecipeManager.ActivateRecipe(recipe.Id, "RMS Request"); ``` **메서드 시그니처:** ```csharp void ActivateRecipe(int recipeId, string reason) ``` **매개변수:** - `recipeId` (int): Recipe의 고유 ID - `reason` (string): 활성화 사유 (로그/감사용) - 예: "RMS Request", "Manual Change", "Auto Selection" ### 2.2 Recipe 정보 조회 **Recipe ID로 조회:** ```csharp // RMSHandlerForScanner.cs Line 125 var recipe = _recipeEntry.Recipes().FirstOrDefault(x => x.Id == recipeId); ``` **Recipe 이름으로 조회:** ```csharp // RMSHandlerForScanner.cs Line 132 var recipeInfo = RecipeHelper.getRecipeInfo(recipe.Name); ``` ### 2.3 Recipe 로드/저장 **Recipe 로드 (활성화 후):** ```csharp // RMSHandlerForScanner.cs Line 136 RecipeHelper.ReloadRecipe(recipe.Name); ``` **Recipe 저장:** ```csharp // RMSHandlerForScanner.cs Line 146 RecipeHelper.SaveRecipe(recipe.Name); ``` ### 2.4 이벤트 트리거 **Recipe 변경 이벤트:** ```csharp // EquipDefCommon.cs Line 145 public const string EVENT_CHANGE_RECIPE_INFO = "EventChangeRecipeInfo"; // RMSHandlerForScanner.cs Line 56-59 (구독 예시) aurora.EventObservable.AsObservable(CommonEventIds.EVENT_CHANGE_RECIPE_INFO) .ObserveOnDispatcher() .Select(param => (RecipeInfo)param.Param) .Subscribe(OnUpdateCurrentActivatedRecipe); ``` **활성화 완료 흐름:** ``` RecipeManager.ActivateRecipe(id, reason) ↓ (EVENT_CHANGE_RECIPE_INFO 발행) ↓ RMSHandlerForScanner.OnUpdateCurrentActivatedRecipe(RecipeInfo info) ├─ RecipeHelper.ReloadRecipe(info.Name) └─ UploadActivatedRecipeParameter() ``` --- ## 3. 변경 요구사항 분석 ### 3.1 요구사항 | # | 요구사항 | 현재 상태 | 변경 필요 | |---|---------|---------|----------| | 1 | 3개 공정 지원 (CC, CAP, U-Frame) | 단일 Recipe만 지원 | ✓ 다중 Recipe 매핑 | | 2 | 각 공정별 Recipe 활성화 | UI에서만 가능 | ✓ PLC 신호로 동적 활성화 | | 3 | RecipeNum + PageNum 수신 | PageNum만 수신 | ✓ RecipeNum WORD 추가 | | 4 | RecipeNum → RecipeId 매핑 | 없음 | ✓ 설정 테이블 추가 | | 5 | UI에서 공정별 RecipeNum 관리 | 없음 | ✓ 설정 UI 추가 (후순위) | ### 3.2 예상 동작 **변경 전:** ``` PLC → ReadWord(PLC_PAGE_NO) → PageNum(1,2) ↓ SeqAutoRunPTOP → 활성된 Recipe의 Page 선택 ↓ _SelectedObjectList 구성 ``` **변경 후:** ``` PLC → ReadWord(PLC_RECIPE_NO) + ReadWord(PLC_PAGE_NO) ↓ SeqAutoRunPTOP → RecipeNum으로 Recipe 동적 활성화 ↓ RecipeManager.ActivateRecipe(mappedRecipeId, "PLC Request") ↓ EVENT_CHANGE_RECIPE_INFO 발생 ↓ 해당 Recipe 로드 + 설정 적용 ↓ Page 선택 → _SelectedObjectList 구성 ``` --- ## 4. 구현 계획 ### Phase 1: 설정 테이블 정의 **위치:** SeqAutoRunPTOP.cs 또는 별도 설정 클래스 ```csharp // 공정별 RecipeNum ↔ RecipeId 매핑 public class ProcessRecipeMapping { public enum eProcessType { CC = 1, // 공정1 CAP = 2, // 공정2 UFrame = 3 // 공정3 } // 매핑 테이블 (사용자 설정 후 저장) private Dictionary _recipeIdMap = new Dictionary() { { eProcessType.CC, 101 }, // CC 공정 → Recipe ID 101 { eProcessType.CAP, 102 }, // CAP 공정 → Recipe ID 102 { eProcessType.UFrame, 103 } // U-Frame 공정 → Recipe ID 103 }; public int GetRecipeId(eProcessType process) => _recipeIdMap[process]; public int GetRecipeId(int recipeNum) => _recipeIdMap[(eProcessType)recipeNum]; } ``` ### Phase 2: PLC 신호 정의 **SeqAutoRunPTOP.cs에 추가:** ```csharp // PLC에서 수신할 WORD 정의 private int _plcRecipeNum = 0; // PLC_RECIPE_NO (Word) private int _plcPageNum = 0; // PLC_PAGE_NO (Word) // CheckPageNo 시퀀스 수정 위치 // Line 354: plcPageNoList.Add(_PLC.ReadWord(eWordInput.PLC_PAGE_NO).data); // // 변경 후: // ┌─────────────────────────────────────────┐ // │ Step 1: RecipeNum 읽기 │ // │ _plcRecipeNum = _PLC.ReadWord(...) │ // │ │ // │ Step 2: RecipeId 매핑 │ // │ int recipeId = mappingTable.GetRecipeId(_plcRecipeNum) │ // │ │ // │ Step 3: Recipe 활성화 │ // │ _AuroraEnv.RecipeManager.ActivateRecipe │ // │ (recipeId, "PLC Request") │ // │ │ // │ Step 4: Recipe 로드 완료 대기 │ // │ (이벤트 핸들러에서 처리) │ // │ │ // │ Step 5: PageNum 읽기 │ // │ _plcPageNum = _PLC.ReadWord(...) │ // └─────────────────────────────────────────┘ ``` ### Phase 3: 코드 구조 변경 **SeqAutoRunPTOP.CheckPageNo() 수정 전략:** ```csharp // 현재 (Line 282-333): private SequenceActionResult CheckPageNo(SequenceContext context, string id, SequenceActionResult param) { // PLC_PAGE_CHANGE_REQ 대기 → PageNum 읽기 → 완료 } // 변경 후: private SequenceActionResult CheckPageNo(SequenceContext context, string id, SequenceActionResult param) { // Step 1: PLC_PAGE_CHANGE_REQ 대기 (기존 로직) // Step 2: RecipeNum 읽기 추가 // Step 3: Recipe 활성화 (동기/비동기 결정) // Step 4: Recipe 로드 완료 확인 // Step 5: PageNum 읽기 (기존 로직) // Step 6: 완료 } ``` **Recipe 활성화 이벤트 핸들러:** ```csharp // SeqAutoRunPTOP 클래스 내부 private bool _recipeChangeCompleted = false; // Aurora 이벤트 구독 (Sequence_Enter에서) _AuroraEnv.EventObservable .AsObservable(CommonEventIds.EVENT_CHANGE_RECIPE_INFO) .ObserveOnDispatcher() .Subscribe(msg => { Log.I("[CheckPageNo] Recipe Change Event 감지"); _recipeChangeCompleted = true; }); // CheckPageNo에서 완료 대기 private SequenceActionResult WaitForRecipeChange(SequenceContext context, int timeoutSec = 5) { _recipeChangeCompleted = false; var startTime = DateTime.Now; while (!context.Stopped && !_recipeChangeCompleted) { if (DateTime.Now.Subtract(startTime).TotalSeconds > timeoutSec) { Log.E("[CheckPageNo] Recipe Change Timeout"); return null; } Thread.Sleep(10); } Log.I("[CheckPageNo] Recipe Change 완료"); return SequenceAction.Ok(); } ``` ### Phase 4: RecipeHelper를 통한 로드 확인 ```csharp // RMSHandlerForScanner.cs의 OnUpdateCurrentActivatedRecipe 패턴 활용 private void OnRecipeActivated(RecipeInfo info) { Log.I($"[SeqAutoRunPTOP] Recipe 활성화됨: {info.Name}"); // Recipe 파라미터 로드 RecipeHelper.ReloadRecipe(info.Name); // 필요시 로드 검증 var loadedRecipe = RecipeHelper.getRecipeInfo(info.Name); if (loadedRecipe == null) { Log.E("[SeqAutoRunPTOP] Recipe 로드 실패"); AlarmManager.SetAlarm(AlarmCode.RECIPE_LOAD_FAILED); return; } Log.I("[SeqAutoRunPTOP] Recipe 로드 완료"); _recipeChangeCompleted = true; } ``` --- ## 5. 구현 순서 (권장) ### Step 1: 설정 클래스 생성 - `ProcessRecipeMapping` 클래스 작성 - RecipeNum ↔ RecipeId 매핑 설정 ### Step 2: SeqAutoRunPTOP.cs 수정 1. 이벤트 핸들러 추가 (Recipe 활성화 완료 감지) 2. CheckPageNo에 RecipeNum 읽기 로직 추가 3. RecipeManager.ActivateRecipe() 호출 4. Recipe 로드 완료 대기 ### Step 3: PLC 통신 검증 - ReadWord(RecipeNum) 정상 작동 확인 - Recipe 활성화 타이밍 검증 - PageNum 읽기가 Recipe 로드 후 실행되는지 확인 ### Step 4: 에러 처리 추가 - Recipe ID 미존재 시 처리 - Recipe 로드 타임아웃 처리 - Fallback 로직 (기본 Recipe 활용) ### Step 5: UI 설정 추가 (후순위) - 공정별 RecipeNum 설정 화면 - 매핑 테이블 UI 편집 --- ## 6. 예상 코드 위치 | 파일 | 라인 | 수정 내용 | |------|------|---------| | SeqAutoRunPTOP.cs | 50-100 | 클래스 멤버 추가 (매핑, 이벤트) | | SeqAutoRunPTOP.cs | 202-240 | Sequence_Enter에서 이벤트 구독 | | SeqAutoRunPTOP.cs | 282-333 | CheckPageNo에 RecipeNum 읽기 추가 | | 별도 파일 | - | ProcessRecipeMapping 클래스 | --- ## 7. 핵심 검증 포인트 1. **RecipeManager.ActivateRecipe() 호출 후 Event 발생 타이밍** - 동기인가 비동기인가? - 즉시 발생하는가 아니면 지연되는가? 2. **RecipeHelper.ReloadRecipe()의 필요성** - EVENT_CHANGE_RECIPE_INFO에서 자동으로 로드되는가? - 수동으로 ReloadRecipe를 호출해야 하는가? 3. **_SelectedObjectList 업데이트 시점** - Recipe 활성화 후 새 Recipe의 Canvas가 로드되는가? - 별도로 처리해야 하는가? 4. **타이밍 문제** - RecipeNum 읽기 → 활성화 → 로드 완료 → PageNum 읽기 - 각 단계의 예상 소요시간? --- ## 8. 참고 코드 ### RecipeManager.ActivateRecipe 사용 사례 **파일:** `RMSHandlerForScanner.cs` Line 173 ```csharp var preActivatedRecipe = _aurora.RecipeManager.ActivatedRecipe(); _aurora.RecipeManager.ActivateRecipe(recipe.Id, "RMS Request"); MessageUtil.ShowToast($"RMS Change Recipe ... ( {preActivatedRecipe.Id} -> {recipe.Id} )", Log); ``` ### EVENT_CHANGE_RECIPE_INFO 구독 사례 **파일:** `RMSHandlerForScanner.cs` Line 56-66 ```csharp aurora.EventObservable.AsObservable(CommonEventIds.EVENT_CHANGE_RECIPE_INFO) .ObserveOnDispatcher() .Select(param => (RecipeInfo)param.Param) .Subscribe(OnUpdateCurrentActivatedRecipe); private void OnUpdateCurrentActivatedRecipe(RecipeInfo info) { RecipeHelper.ReloadRecipe(info.Name); UploadActivatedRecipeParameter(); } ``` --- ## 9. 다음 단계 1. **코드 분석 후 결정 필요:** - RecipeManager.ActivateRecipe()의 정확한 동작 방식 (동기/비동기) - EVENT_CHANGE_RECIPE_INFO 발생 타이밍 - RecipeHelper.ReloadRecipe()의 필요성 2. **설계 결정:** - RecipeNum을 WORD로 받을 것인가? (또는 별도 IO?) - 매핑 테이블을 하드코딩할 것인가? (또는 설정 파일?) - Fallback 시나리오 정의 3. **구현 시작:** - 위 확인 후 Phase 1부터 순차 진행 --- **작성자:** Claude Code Analysis **검토 필요:** 실제 Aurora RecipeManager 메서드 시그니처 확인 필요