PTOP 변경 과정에서 Recipe 탭 진입 시 IO Device DI 읽기 실패로 시스템 크래시 발생. 근본 원인: - DI 24가 PTOP 하드웨어에 미정의됨 - UCScannerObjectTeachingControlBoxViewModel에서 DI 9, 24를 함께 읽으려고 시도 - 범위 초과 오류(ERROR CODE:3102) 발생 변경사항: - DI 24 읽기 제거 (Scanner 레거시 코드, PTOP에 불필요) - DI 9 읽기만 수행하도록 변경 - try-catch 예외 처리 추가로 안정성 개선 - 오류 발생 시 에러 로깅 Fixes: Recipe 탭 시스템 셧다운 또는 더 간단하게: fix: Recipe 탭 DI 읽기 오류 해결 DI 24가 PTOP에 미정의되어 OnMonitoring에서 범위 초과 에러 발생. DI 9만 읽도록 수정하고 try-catch 예외 처리 추가.
12 KiB
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의 고유 IDreason(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 수정
- 이벤트 핸들러 추가 (Recipe 활성화 완료 감지)
- CheckPageNo에 RecipeNum 읽기 로직 추가
- RecipeManager.ActivateRecipe() 호출
- 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. 핵심 검증 포인트
-
RecipeManager.ActivateRecipe() 호출 후 Event 발생 타이밍
- 동기인가 비동기인가?
- 즉시 발생하는가 아니면 지연되는가?
-
RecipeHelper.ReloadRecipe()의 필요성
- EVENT_CHANGE_RECIPE_INFO에서 자동으로 로드되는가?
- 수동으로 ReloadRecipe를 호출해야 하는가?
-
_SelectedObjectList 업데이트 시점
- Recipe 활성화 후 새 Recipe의 Canvas가 로드되는가?
- 별도로 처리해야 하는가?
-
타이밍 문제
- 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. 다음 단계
-
코드 분석 후 결정 필요:
- RecipeManager.ActivateRecipe()의 정확한 동작 방식 (동기/비동기)
- EVENT_CHANGE_RECIPE_INFO 발생 타이밍
- RecipeHelper.ReloadRecipe()의 필요성
-
설계 결정:
- RecipeNum을 WORD로 받을 것인가? (또는 별도 IO?)
- 매핑 테이블을 하드코딩할 것인가? (또는 설정 파일?)
- Fallback 시나리오 정의
-
구현 시작:
- 위 확인 후 Phase 1부터 순차 진행
작성자: Claude Code Analysis
검토 필요: 실제 Aurora RecipeManager 메서드 시그니처 확인 필요