289 lines
14 KiB
Markdown
289 lines
14 KiB
Markdown
# 작업지시서 — AutoRun 중 비활성 레시피 저장 차단 (PLC AutoStatus 게이트)
|
||
|
||
- 작성일: 2026-08-03
|
||
- 대상 브랜치: `feature/ubisam-pptx-integration` (현장 반영 시 작업 PC 브랜치 기준)
|
||
- 작업 분류: 방어 로직 추가 (레시피 파일 오염 차단) — 기능 변경 없음
|
||
- ⚠️ 본 문서는 **수정안(검토용)** 입니다. **아직 소스는 변경하지 않았습니다.** 작업 PC에서 본 지시서대로 적용하세요.
|
||
- 근거 분석: [레시피 저장 교차 오염 — 활성 레시피 ↔ 선택 레시피 상태 불일치 원인 분석](../분석/2026/08/03/레시피저장_활성vs선택_교차오염_원인분석.md)
|
||
|
||
---
|
||
|
||
## 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 차단 코드가 있습니다.**
|
||
|
||
```csharp
|
||
// UCScannerObjectTeachingViewModel.cs:189-192 (현재 코드)
|
||
if (!IsManualStatus) {
|
||
MessageUtil.ShowErrorToast("Auto 상태에서 레시피 변경 불가", Log);
|
||
return false;
|
||
}
|
||
```
|
||
|
||
그런데 `IsManualStatus`의 출처는 **PC측 장비 상태**입니다.
|
||
|
||
```csharp
|
||
// :77
|
||
IsManualStatus = Status.EquipmentStatus == eEqpStatus.Manual;
|
||
```
|
||
|
||
**bit15 Resume 흐름이 이 게이트를 정확히 무력화합니다.**
|
||
|
||
```csharp
|
||
// 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` 수정안
|
||
|
||
```csharp
|
||
// 파일 상단 using 에 추가 (E1.OK 사용 목적)
|
||
using Hermes;
|
||
```
|
||
|
||
```csharp
|
||
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`
|
||
|
||
```csharp
|
||
var activeRecipe = _RecipeManager.ActivatedRecipe();
|
||
RecipeHelper.SaveRecipe(activeRecipe.Name); // ← 사용자가 저장을 누른 적이 없는데 파일이 바뀜
|
||
```
|
||
|
||
AutoRun이 `ActivateRecipe(A)` 하는 순간(`SeqAutoRunPTOP.cs:403`), **작업자가 B를 티칭 중이던 캔버스가 A 파일에 기록**됩니다.
|
||
로그도 남지 않아 조용히 오염됩니다.
|
||
|
||
**권장 보완 (별건 작업으로 분리 가능, 3~5줄):**
|
||
|
||
```csharp
|
||
// 캔버스에 로드된 레시피와 활성 레시피가 같을 때만 저장
|
||
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`
|
||
|
||
```csharp
|
||
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.cs` 3-2 적용 (`using Hermes;` 포함)
|
||
- [ ] 빌드 확인
|
||
- [ ] T1·T3·T4 현장 검증 (T4 로그 `[SaveGate] 차단` 확인)
|
||
- [ ] (권장, 별건) 5-3 `OnSaved` 저장 대상 → `recipeName` 통일
|
||
- [ ] (권장, 별건) 5-1 활성화 자동저장 캔버스 일치 가드
|
||
- [ ] SW Version 시트 반영 · 버전업
|