14 KiB
작업지시서 — AutoRun 중 비활성 레시피 저장 차단 (PLC AutoStatus 게이트)
- 작성일: 2026-08-03
- 대상 브랜치:
feature/ubisam-pptx-integration(현장 반영 시 작업 PC 브랜치 기준) - 작업 분류: 방어 로직 추가 (레시피 파일 오염 차단) — 기능 변경 없음
- ⚠️ 본 문서는 수정안(검토용) 입니다. 아직 소스는 변경하지 않았습니다. 작업 PC에서 본 지시서대로 적용하세요.
- 근거 분석: 레시피 저장 교차 오염 — 활성 레시피 ↔ 선택 레시피 상태 불일치 원인 분석
0. 결론 — 요청하신 방식, 구현 가능합니다
"AutoRunPTOP 시퀀스 동작 중일 때 해당 PLC의 AutoStatus bit 값을 확인해서, 활성화된 레시피 이외의 레시피는 저장이 안 되도록 막을 수 있는가?"
가능합니다. 신규 배선·신규 디바이스 참조 없이, 파일 1개·함수 1개(PreSaved)만 수정하면 됩니다.
| 필요한 것 | 현황 |
|---|---|
| 저장 직전 가로채는 훅 | 이미 있음 — IRecipeViewModel.PreSaved(id, recipeName), false 반환 시 Aurora가 저장 취소 |
| 저장 대상 레시피 이름 | 이미 인자로 들어옴 — PreSaved의 recipeName (Aurora.xml:4017-4023 계약) |
| 활성 레시피 | _RecipeManager.ActivatedRecipe() — VM이 이미 보유 |
| PLC AutoStatus bit | _PLC.ReadBit(PLCController.eCommonBitInput.PLC_AUTO_STATUS) = M70501 — VM이 이미 _PLC 필드 보유(UCScannerObjectTeachingViewModel.cs:34,50) |
즉 추가로 끌어와야 할 의존성이 하나도 없습니다.
1. 배경 — 왜 지금 게이트가 못 막는가
PreSaved에 이미 Auto 차단 코드가 있습니다.
// UCScannerObjectTeachingViewModel.cs:189-192 (현재 코드)
if (!IsManualStatus) {
MessageUtil.ShowErrorToast("Auto 상태에서 레시피 변경 불가", Log);
return false;
}
그런데 IsManualStatus의 출처는 PC측 장비 상태입니다.
// :77
IsManualStatus = Status.EquipmentStatus == eEqpStatus.Manual;
bit15 Resume 흐름이 이 게이트를 정확히 무력화합니다.
// UCMainAutoRunViewModel.cs:229-238
if (_SeqAutoRunPTOP.RunState == SequenceRunState.Running) _SeqAutoRunPTOP.Stop();
Aurora.Api.Control.MessageDialog.Show(...); // "용접 위치 확인해주세요."
Status.SetStatus(eEqpStatus.Manual); // ← IsManualStatus = true 가 됨
_Plc.WriteBit(eBitOutput.PC_AUTO_STATUS, false); // ← PC측만 Auto OFF
→ 작업자가 가변 용접을 하도록 일부러 Manual로 내려주는 흐름이므로 IsManualStatus == true,
따라서 기존 게이트는 통과되고 어떤 레시피든 저장 가능한 상태가 됩니다.
이때 라인(PLC)은 여전히 Auto 이고, 활성 레시피는 PLC가 지정한 것(ActivateRecipe, UCMainAutoRunViewModel.cs:246)입니다.
2026-08-01 현장 상황이 정확히 이 구간입니다. 활성=A(구 캘 레시피)인데 작업자는 B(신규 복사본)를 티칭·저장 → 캔버스/저장대상 불일치로 B 파일이 A 내용으로 덮어써짐.
핵심:
IsManualStatus(PC 상태)로는 "라인이 Auto 운전 중인가"를 알 수 없습니다. 그래서 PLC측 원본 신호(M70501)를 직접 보는 것이 맞습니다. 요청하신 방향이 정확합니다.
2. 판정 규칙
target = PreSaved 인자 recipeName (Aurora가 알려주는 실제 저장 대상)
active = _RecipeManager.ActivatedRecipe()?.Name
plcAuto = _PLC.ReadBit(eCommonBitInput.PLC_AUTO_STATUS).data // M70501
if (_PLC == null || _PLC.IsSimul) → 게이트 미적용 (통과) // 오프라인/시뮬 티칭 보호
if (PLC 읽기 err != E1.OK) → 게이트 미적용 + Log.W // fail-open (아래 3-3 참고)
if (!plcAuto) → 통과 // 라인 정지 상태 = 자유 티칭
if (active == null) → 통과 + Log.W
if (target != active) → ★ 차단 (Toast 안내)
else → 통과
진리표
| PLC M70501 | PC 상태 | 저장 대상 | 활성 레시피 | 결과 |
|---|---|---|---|---|
| OFF | Manual | B | A | 저장 O (라인 정지, 자유 티칭) |
| OFF | Manual | A | A | 저장 O |
| ON | Auto | 아무거나 | – | 저장 X (기존 IsManualStatus 게이트가 먼저 차단) |
| ON | Manual (bit15 Resume) | B | A | 저장 X ← 이번에 막는 케이스 |
| ON | Manual (bit15 Resume) | A | A | 저장 O (활성 레시피 보정은 허용) |
| (PLC null / Simul) | – | – | – | 저장 O (게이트 미적용) |
기존 IsManualStatus 게이트는 그대로 둡니다. 신규 게이트는 그 아래에 추가되는 2차 방어선입니다.
3. 수정 내용
3-1. 대상 파일 (1개)
WeldingComponents/WeldingCommon/Views/Recipes/UCScannerObjectTeachingViewModel.cs
3-2. PreSaved 수정안
// 파일 상단 using 에 추가 (E1.OK 사용 목적)
using Hermes;
public bool PreSaved(int id, string recipeName)
{
try {
if (ApplicationExt.PermissionLevel > (uint)PermissionLevel.LEVEL3) {
MessageUtil.ShowErrorToast("Engineer 권한으로 로그인 하시기 바랍니다.", Log);
return false;
}
if (!IsManualStatus) {
MessageUtil.ShowErrorToast("Auto 상태에서 레시피 변경 불가", Log);
return false;
}
// ── 260803 추가: PLC AutoStatus(M70501) ON 이면 활성 레시피만 저장 허용 ─────────────
// 배경: bit15 Resume 경로(UCMainAutoRunViewModel.cs:229-238)는 라인이 Auto인 채로
// PC만 Manual로 내린다. 이때 위의 IsManualStatus 게이트가 통과되므로,
// PLC측 원본 신호로 한 번 더 판정한다.
if (!IsActiveRecipeOnlySaveAllowed(recipeName)) {
return false;
}
return true;
} catch (Exception ex) {
Log.E($"PreSaved failed: {ex}");
}
return false;
}
/// <summary>
/// PLC가 Auto 운전 중(M70501 ON)이면 활성 레시피 외 저장을 거부한다.
/// PLC 미연결·시뮬·읽기 실패 시에는 게이트를 적용하지 않는다(fail-open, 티칭 작업 보호).
/// </summary>
private bool IsActiveRecipeOnlySaveAllowed(string recipeName)
{
if (_PLC == null || _PLC.IsSimul) {
return true;
}
var auto = _PLC.ReadBit(PLCController.eCommonBitInput.PLC_AUTO_STATUS); // M70501
if (auto.err != E1.OK) {
Log.W($"[SaveGate] PLC AutoStatus 읽기 실패(err:{auto.err}) → 게이트 미적용");
return true;
}
if (!auto.data) {
return true; // 라인 정지 → 자유 티칭
}
var active = _RecipeManager.ActivatedRecipe();
if (active == null) {
Log.W("[SaveGate] PLC Auto ON 이지만 활성 레시피 없음 → 게이트 미적용");
return true;
}
if (!string.Equals(recipeName, active.Name, StringComparison.Ordinal)) {
Log.W($"[SaveGate] 차단 — PLC Auto 운전 중 비활성 레시피 저장 시도 " +
$"(대상:[{recipeName}] / 활성:[{active.Name}] / 선택:[{RecipeHelper.SelectedRecipe.name}])");
MessageUtil.ShowErrorToast(
$"자동 운전 중에는 활성 레시피만 저장할 수 있습니다.\n" +
$"활성 : {active.Name}\n저장 시도 : {recipeName}", Log);
return false;
}
Log.I($"[SaveGate] 통과 — PLC Auto 운전 중 활성 레시피 저장 (대상:[{recipeName}])");
return true;
}
3-3. fail-open / fail-closed 선택 근거
본 게이트는 안전(safety) 인터락이 아니라 데이터 보호 게이트입니다.
PLC 읽기 실패 시 fail-closed로 잡으면 PLC 통신이 불안정할 때 티칭 저장이 전부 막혀 현장 작업이 멈춥니다.
따라서 위 안은 fail-open + 경고 로그로 잡았습니다.
반대 정책(읽기 실패 시 차단)을 원하시면 auto.err != E1.OK 분기를 return false 로 바꾸면 됩니다. 정책 결정 필요 — 4장 확인 사항 ①
4. 확인 필요 사항 (현장 확인 후 확정)
| # | 항목 | 왜 필요한가 | 확인 방법 |
|---|---|---|---|
| ① | PLC 읽기 실패 시 정책 (fail-open / fail-closed) | 3-3 참고 | 담당자 결정 |
| ② | bit15 Resume 구간에서 M70501이 ON을 유지하는가 | 이 게이트의 대전제. PLC가 이 구간에 Auto를 내려버리면 게이트가 발동하지 않음 | 현장에서 bit15 팝업 뜬 상태로 PLC M70501 모니터 확인 |
| ③ | ② 가 OFF로 확인될 경우 대체 신호 | 게이트 조건 교체 필요 | SeqAutoRunPTOP.ResumeMode == true 또는 PC_RECIPE_NO != 0 을 OR 조건으로 추가 (5-2 참고) |
| ④ | AutoRun 중 "활성 레시피 저장"을 허용할지 | 표의 5행. 허용이 기본안이나, 운전 중 활성 레시피 변경 자체를 막으려면 조건을 plcAuto → 전면 차단으로 단순화 |
담당자 결정 |
②가 확정되기 전에는 이 게이트가 현장에서 발동하지 않을 수 있습니다. 적용 후 반드시 6장 테스트로 발동 여부를 눈으로 확인하세요.
5. 본 게이트가 막지 못하는 경로 (중요)
PreSaved는 Aurora 저장 버튼 경로에만 걸립니다. 아래 두 경로는 RecipeHelper.SaveRecipe()를 직접 호출하므로 이 게이트를 타지 않습니다. 2026-08-01 오염의 3단계(자동 저장) 가 여기에 해당합니다.
5-1. 레시피 활성화 시 자동 저장 — UCScannerObjectTeachingViewModel.cs:155-156
var activeRecipe = _RecipeManager.ActivatedRecipe();
RecipeHelper.SaveRecipe(activeRecipe.Name); // ← 사용자가 저장을 누른 적이 없는데 파일이 바뀜
AutoRun이 ActivateRecipe(A) 하는 순간(SeqAutoRunPTOP.cs:403), 작업자가 B를 티칭 중이던 캔버스가 A 파일에 기록됩니다.
로그도 남지 않아 조용히 오염됩니다.
권장 보완 (별건 작업으로 분리 가능, 3~5줄):
// 캔버스에 로드된 레시피와 활성 레시피가 같을 때만 저장
var loaded = System.IO.Path.GetFileName(
System.IO.Path.GetDirectoryName(DB.Dao.SystemRecipe.WorkRecipeFile));
if (string.Equals(loaded, activeRecipe.Name, StringComparison.Ordinal)) {
RecipeHelper.SaveRecipe(activeRecipe.Name);
} else {
Log.W($"[SaveGate] 활성화 자동저장 생략 — 캔버스:[{loaded}] ≠ 활성:[{activeRecipe.Name}]");
}
5-2. 신규 레시피 생성 시 자동 저장 — UCScannerObjectTeachingViewModel.cs:111-115
includeModel:false라 도형은 안 쓰이지만 용접 파라미터는 그 순간 캔버스에 로드돼 있던 레시피의 것이 기록됩니다.
신규/복사 레시피가 엉뚱한 파라미터로 출발합니다. (분석문서 §5-1)
5-3. OnSaved가 인자를 버리는 문제 — :202-208
public void OnSaved(int id, string recipeName) // Aurora가 대상을 알려줌
...
RecipeHelper.SaveRecipe(RecipeHelper.SelectedRecipe.name); // ← 그런데 static 값으로 씀
PreSaved에서 recipeName으로 검증했는데 OnSaved에서 SelectedRecipe.name으로 쓰면 검증한 대상과 실제 쓰는 대상이 다를 수 있습니다.
본 게이트의 실효성을 위해 OnSaved의 저장 대상도 recipeName으로 통일할 것을 강력히 권장합니다 (해당 함수 내 RecipeHelper.SelectedRecipe.name → recipeName 치환, 1줄 + 이후 참조 정리).
정리: 본 작업(3장)만 적용하면 "작업자가 저장 버튼을 눌러 남의 레시피를 덮는" 경로는 차단됩니다. "시퀀스가 조용히 덮는" 경로(5-1)는 별도 작업이 필요합니다. 두 건은 원인이 같으므로 함께 적용하는 것을 권장합니다.
6. 테스트 시나리오
| # | 절차 | 기대 결과 |
|---|---|---|
| T1 | PLC 정지(M70501 OFF) + Manual → 임의 레시피 B 편집 후 저장 | 정상 저장 (기존과 동일, 회귀 없음) |
| T2 | Auto 운전(M70501 ON, PC도 Auto) → 저장 시도 | 기존 토스트 "Auto 상태에서 레시피 변경 불가" |
| T3 | Auto 운전 중 bit15 ON → 팝업 [확인] → Manual 전환 → 활성 레시피 A 저장 | 정상 저장 + 로그 [SaveGate] 통과 |
| T4 | T3 상태에서 콤보로 B 선택 → 편집 → 저장 | 차단 토스트 + 로그 [SaveGate] 차단 — ... 대상:[B] / 활성:[A] |
| T5 | PLC 케이블 분리(통신 실패) 상태에서 저장 | fail-open 정책이면 저장됨 + Log.W ... 읽기 실패 |
| T6 | 시뮬 모드에서 저장 | 게이트 미적용, 정상 저장 |
T4가 이번 작업의 핵심 검증 항목입니다. T4에서 차단이 안 뜨면 4장 ② (M70501 상태)를 먼저 확인하세요.
7. 영향도 / 롤백
| 항목 | 내용 |
|---|---|
| 수정 파일 | WeldingCommon/Views/Recipes/UCScannerObjectTeachingViewModel.cs 1개 |
| 신규 참조 | 없음 (_PLC, _RecipeManager 모두 기존 필드) |
| PLC 맵 변경 | 없음 — 기존 M70501(PLC_AUTO_STATUS) 읽기만 추가. PLC측 작업 불필요 |
| 시퀀스 영향 | 없음 (읽기 전용, 저장 경로에서만 동작) |
| 정상 운전 영향 | 없음 (M70501 OFF 시 기존 동작과 100% 동일) |
| 롤백 | IsActiveRecipeOnlySaveAllowed 호출 1줄 주석 처리 |
8. 작업 순서 체크리스트
- 4장 ①·②·④ 정책 확인 (특히 ② — bit15 구간 M70501 상태)
UCScannerObjectTeachingViewModel.cs3-2 적용 (using Hermes;포함)- 빌드 확인
- T1·T3·T4 현장 검증 (T4 로그
[SaveGate] 차단확인) - (권장, 별건) 5-3
OnSaved저장 대상 →recipeName통일 - (권장, 별건) 5-1 활성화 자동저장 캔버스 일치 가드
- SW Version 시트 반영 · 버전업