LGES-OC5-PtoP-Welder/docs/분석/2026/06/16/135937_recipe_activation_analysis.md
NohDoyeon241104 6c435ec04a revert: docs/분석 다시 git 추적 (커밋 포함 유지)
사유: 분석 문서가 다른 PC에서 풀 받을 때 사라지면 안 되어 git에 포함 유지.
- .gitignore의 /docs/분석/ 제외 규칙 제거
- docs/분석 전체 재추적(기존 22 + 금일 신규 3 = 25개)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 14:48:19 +09:00

12 KiB

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

// Recipe 활성화 핵심 메서드
_aurora.RecipeManager.ActivateRecipe(recipe.Id, "RMS Request");

메서드 시그니처:

void ActivateRecipe(int recipeId, string reason)

매개변수:

  • recipeId (int): Recipe의 고유 ID
  • reason (string): 활성화 사유 (로그/감사용) - 예: "RMS Request", "Manual Change", "Auto Selection"

2.2 Recipe 정보 조회

Recipe ID로 조회:

// RMSHandlerForScanner.cs Line 125
var recipe = _recipeEntry.Recipes().FirstOrDefault(x => x.Id == recipeId);

Recipe 이름으로 조회:

// RMSHandlerForScanner.cs Line 132
var recipeInfo = RecipeHelper.getRecipeInfo(recipe.Name);

2.3 Recipe 로드/저장

Recipe 로드 (활성화 후):

// RMSHandlerForScanner.cs Line 136
RecipeHelper.ReloadRecipe(recipe.Name);

Recipe 저장:

// RMSHandlerForScanner.cs Line 146
RecipeHelper.SaveRecipe(recipe.Name);

2.4 이벤트 트리거

Recipe 변경 이벤트:

// 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 또는 별도 설정 클래스

// 공정별 RecipeNum ↔ RecipeId 매핑
public class ProcessRecipeMapping
{
    public enum eProcessType
    {
        CC = 1,        // 공정1
        CAP = 2,       // 공정2
        UFrame = 3     // 공정3
    }
    
    // 매핑 테이블 (사용자 설정 후 저장)
    private Dictionary<eProcessType, int> _recipeIdMap = new Dictionary<eProcessType, int>()
    {
        { 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에 추가:

// 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() 수정 전략:

// 현재 (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 활성화 이벤트 핸들러:

// 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를 통한 로드 확인

// 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

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

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 메서드 시그니처 확인 필요