# Recipe/Page 핸드셰이크 — "에코 보장" 방침 전환 분석 - 작성일: 2026-08-06 - 대상 브랜치: `feature/ubisam-pptx-integration` - 분류: **분석 전용 — 소스 미수정** - 발단: PLC측 시퀀스 확정 완료. **PLC는 Page/Recipe 넘버 일치만 보고 넘어간다**(2026-08-06 확인) - 관련: [AutoRun 재기동 워드 미반영 원인분석](../04/AutoRun재기동_Recipe_Page워드_미반영_원인분석.md) - **후속: [에코 수렴 루프 설계안](./RecipePage_에코수렴루프_설계.md)** — 2026-08-06 추가 확인 (**PLC는 일치를 상시 확인한다**. 30초는 확인 간격이 아니라 한 번의 확인 창이며 반복된다)으로 §5 조치표 일부가 정정됐습니다. **설계안을 우선 참조하세요.** --- ## 0. 결론 **PC측 책임이 "검증"에서 "에코 보장"으로 바뀝니다.** PLC가 넘버 일치를 판정하므로, PC가 자체 검증으로 **대기·중단하는 코드는 전부 교착 요인**입니다. PC가 멈추면 에코가 안 나가고, 에코가 안 나가면 PLC도 멈춰 **양쪽이 서로 기다립니다.** 현재 코드에는 **에코를 하지 않고 빠져나가거나 무한 대기하는 경로가 4건** 있습니다(§2). 전부 "PC가 신중하게 확인하려고" 넣은 것인데, 새 방침에서는 정확히 반대로 작동합니다. > **단, "잘 써주기"의 전제는 "제대로 읽기"입니다.** 틀린 값을 에코하면 PLC가 불일치로 안 넘어가 > 결국 멈춥니다. 따라서 [08/04 §4](../04/AutoRun재기동_Recipe_Page워드_미반영_원인분석.md)의 > **상승엣지 전환(조치 A)은 이 방침에서도 그대로 유효**하며, 오히려 전제 조건이 됩니다. → §4 --- ## 1. 확정된 프로토콜 (2026-08-06) | 주체 | 동작 | |---|---| | PLC | Recipe·Page 워드를 **동시에** 쓴다 → `M70508`(`PLC_PAGE_CHANGE_REQ`, bit 0x08) ON | | PLC | `PC_PAGE_NO`·`PC_RECIPE_NO`가 자기가 쓴 값과 **같아지면 다음 스텝으로 진행** | | PC | bit8 감지 → Recipe 읽기·활성화 → Page 읽기 → 에코 | **게이트는 워드다.** `PC_PAGE_CHANGE_COMPLETE`(M70008, `eBitOutput` 0x08)는 PLC가 보지 않는다. ### 코드 주석이 이와 반대로 적혀 있다 `SeqAutoRunPTOP.cs:1225` (`OnSeqActionStop`), 에코 워드 0 초기화를 **롤백한 근거**: ``` ② PLC가 보는 핸드셰이크 게이트는 워드가 아니라 PC_PAGE_CHANGE_COMPLETE 비트이고, 그 비트는 바로 위에서 이미 false로 내렸다 → 잔재 워드를 유효값으로 읽을 경로가 없다. ``` → **이 전제는 틀렸습니다.** 롤백 판단의 근거가 무효이므로 재검토 대상입니다. 다만 워드 0 초기화는 `SeqStatusMonitor.cs:104-109`가 Auto OFF 시 이미 수행하므로 **중복 구현 상태**입니다(`OnSeqActionStop`은 로그만, `:1231-1240`). ### 안전 측면은 PLC가 커버한다 (검증됨) 용접 실행은 `PLC_WELD_TRIGGER_REQ`(M70516) ON 대기가 **별도 게이트**입니다 (`SeqAutoRunPTOP.cs:196`, `:705`). PC가 페이지를 오독하면 → 에코 불일치 → PLC가 트리거를 주지 않음 → **용접이 나가지 않습니다.** 오독이 잘못된 용접으로 이어지는 경로는 없습니다. > 남는 비용은 **안전이 아니라 정지 시간**입니다. 그래서 이 문서의 초점은 "멈춤 제거"입니다. --- ## 2. ★ 핑퐁을 끊는 지점 전수 (에코 누락·무한 대기) ### ① `CheckRecipeNoComp` — 에코 없이 return 하는 경로 2건 > **정정(2026-08-06):** PLC는 일치를 **상시 확인**하므로 아래 "대기"는 영구 교착이 아닙니다. > PC가 맞추는 **즉시** 진행됩니다. → [수렴 루프 설계안 §0](./RecipePage_에코수렴루프_설계.md) | 위치 | 조건 | `PC_RECIPE_NO` | PLC 결과 | |---|---|---|---| | `:353-357` | `plcRecipeNo == 0` | **미기록** (직전 값 잔존) | 불일치 → 재시도 대기 (PC는 복구 시도 안 함) | | `:361-365` | 미등록 번호 (`Log.W` 한 줄) | **미기록** | 불일치 → 재시도 대기 (PC는 복구 시도 안 함) | | `:395-398` | 이미 활성 + 4회 폴링 동안 불변 | `preActivated.Id` | PLC 요구값과 다르면 대기 | | `:429` | 실제 전환 완료 | `plcRecipeNo` | 통과 | `ReadWord`는 `(int err, short data)`인데(`PLCController.Logic.cs:16`) `:349`가 `.data`만 씁니다. PLC 미연결·미활성이면 **에러 없이 `0`을 반환**하므로(`PLCBase.APIs.cs:105`, `:109`) 통신 이상이 자동으로 `:353` 경로에 빠집니다. 화면 표시 없음, 로그 1줄뿐. ### ② `CheckPageNoComp:534-556` — 범위 밖이면 3초 주기 무한 대기 260805에 `return null`(시퀀스 중단)에서 대기 루프로 바꾼 지점입니다. 중단은 아니지만 **에코를 하지 않은 채 머무릅니다.** PLC도 대기 중이므로 **완전 교착**입니다. 탈출구는 `while (!context.Stopped)`(`:501`) 하나 — 작업자가 정지를 눌러야 합니다. ### ③ `CheckRecipeNoComp:408-419` — ActivateRecipe 반영 대기가 사실상 무한 타임아웃 분기(`:413-417`)가 `Log.W` 후 **`actStart`를 리셋**합니다. 빠져나가지 않습니다. ### ④ `CheckPageNoComp:501` while 탈출 후 `Stopped` 가드 없음 형제 노드는 둘 다 있습니다(`CheckPageNo:483`, `CheckRecipeNoComp:420`). 정지로 루프를 빠져나와도 `:573`이 실행돼 **미검증 값을 에코**합니다. 워드가 게이트이므로 이건 로그 오염이 아니라 **정지한 PC가 PLC에 GO를 쏘는 것**입니다. --- ## 3. 방침에 비추어 과잉인 코드 | 위치 | 현재 | 새 방침에서의 평가 | |---|---|---| | `CheckPageNoComp:499-566` | Page 3회 수집 (`Sleep(200)` × 3) | **과잉.** 최소 600ms 지연만 발생. 값 검증은 PLC 몫 | | `:492`, `:527` 로그 | "3회 연속 검증" | **거짓 로그.** 3개 값을 비교하는 코드가 없다 (`:561`은 출력뿐, 판정은 마지막 값 `plcPageNo` 하나) | | `CheckRecipeNoComp:374-399` | 값 변화를 최대 4회(약 800ms) 기다림 | **과잉 + 유해.** 잘못된 트리거 시점의 우회책. [08/04 조치 B](../04/AutoRun재기동_Recipe_Page워드_미반영_원인분석.md)와 동일 판정 | | `CheckPageNoComp:581-584` | 에코 후 재읽기·비교 → ACK 비트 | **무효.** PLC가 M70008을 안 본다. 불일치여도 `Ok()` 반환(`:597`)이라 PC 흐름에도 영향 없음. **PC 로그용으로만 의미** | > `:374-399`의 `count++ < 3`은 실제로 **4회 순회**합니다(count 0·1·2·3). 주석("3번 체크하자")과 불일치. --- ## 4. "잘 써주기"의 전제 — 읽기 시점 새 방침은 PC측 검증을 덜어내지만, **읽는 값 자체가 틀리면 에코도 틀리고 결국 멈춥니다.** `CheckPageNo:457`은 `PLC_PAGE_CHANGE_REQ`를 **레벨(ON)로 판정**합니다. 상승엣지가 아닙니다. PLC 프로토콜이 `워드 write → REQ raise` 순서이므로([08/04 §4](../04/AutoRun재기동_Recipe_Page워드_미반영_원인분석.md)), **상승엣지 직후에 읽으면 워드는 항상 새 값**입니다. 반면 레벨 판정은 이미 ON인 잔재 신호에 붙어 **갱신 전 값**을 읽습니다. | 트리거 판정 | 워드 정합성 | 에코 결과 | |---|---|---| | 레벨 (현재) | 보장 없음 | 옛 값 에코 → PLC 불일치 → **멈춤** | | 상승엣지 | 보장됨 | 새 값 에코 → **핑퐁 성립** | → **엣지 전환은 검증 강화가 아니라 "정확히 1회 읽고 즉시 에코"를 가능하게 하는 선행 조치**입니다. 엣지로 바꾸면 §3의 3회 수집·800ms 재폴링을 **둘 다 안전하게 제거**할 수 있습니다. --- ## 5. 조치 후보 (미승인 — 승인 시 별도 작업지시서) > ⚠ **이 표는 [수렴 루프 설계안 §6](./RecipePage_에코수렴루프_설계.md)에서 갱신됐습니다.** > P2·P3 정정, P4·P5 제거, P1 우선순위 하향. **설계안 §6을 정본으로 봅니다.** 원칙: **PC는 자기가 읽은 값·실제 상태를 지체 없이 에코한다. 일치 판정은 PLC에 맡긴다.** | # | 대상 | 내용 | 근거 | |---|---|---|---| | **P1** | `CheckPageNo:457` | 레벨 → **상승엣지** 판정 | §4 (= 08/04 조치 A) | | **P2** | `CheckRecipeNoComp:353-365` | 에코 없이 return 하는 2경로에 **에코 추가**(현재 활성 레시피 번호) + 알람/토스트 | §2-① | | **P3** | `CheckPageNoComp:534-556` | 무한 대기 제거 → **읽은 값 그대로 에코**하고 진행. Layer 선택은 기존 폴백(`:626-628`) 사용 | §2-② | | P4 | `CheckPageNoComp:499-566` | 3회 수집 → **1회 읽기**로 축소 | §3 (P1 선행) | | P5 | `CheckRecipeNoComp:374-399` | 800ms 재폴링 **제거** | §3 (P1 선행) | | P6 | `:349`, `:510` | `err` 확인 — **중단이 아니라 재읽기** 목적. `err != OK`면 즉시 재시도 | §2-① | | P7 | `CheckPageNoComp:501` 직후 | `if (context.Stopped) return null;` 추가 | §2-④ | | P8 | `CheckRecipeNoComp:413-417` | 타임아웃 시 리셋 대신 **탈출 + 알람** | §2-③ | | P9 | `:492`, `:527` | 로그 문구 정정 ("3회 연속 검증" → 실제 동작) | §3 · `rules.md` #1 | | P10 | `:1219-1241` | 주석 ②의 잘못된 전제 정정 + `SeqStatusMonitor`와 중복 정리 | §1 | **P1~P3이 "멈춤 제거"의 직접 조치입니다.** P4·P5는 P1 적용 후에만 안전합니다. ### 주의 — `ResumeMode`(bit15) 예외 `OnSeqActionStop:1227`·`:1236-1240`, `SeqStatusMonitor:104`는 **M70515 ON 구간에서 에코 워드를 보존**합니다. 가접 Resume이 직전 Recipe/Page 에코에 의존하기 때문입니다. P2·P10 적용 시 **이 예외를 반드시 유지**해야 합니다. --- ## 6. PLC측 질문 정리 (08/04 ★ 항목 갱신) | # | 질문 | 상태 | |---|---|---| | ~~★3~~ | 워드를 쓴 뒤 REQ를 올리는가 | **해소(08/04)** — `write → raise` 확정 | | ~~★4~~ | `PC_PAGE_CHANGE_COMPLETE` 하강을 REQ 재발행 조건으로 쓰는가 | **해소(08/06)** — PLC는 넘버만 본다 | | ★1 | **Manual 전환 / PC 정지 시 M70508을 PLC가 OFF 시키는가** | **미해소** — P1(엣지 전환) 필요성의 근거 | | ★2 | M70515(Weld Ready REQ)를 PLC가 언제 OFF 시키는가 | 미해소 — [bit15 Resume 교착](../04/AutoRun재기동_Recipe_Page워드_미반영_원인분석.md) | | ★5 | `PC_WELD_READY_ACK`(M70015)를 레벨로 보는가 펄스로 보는가 | 미해소 | > ★1이 "OFF 시킨다"로 확인되면 P1 없이도 대부분 해소되지만, **엣지 판정이 더 강한 보장**이라 > 어느 쪽이든 P1을 권장합니다. --- ## 7. 부수 확인 사항 - `CheckPageNoComp:521-525` — CellId/LotId를 매 회차 읽어 `Status`에 즉시 대입. 읽기 실패 시 `string.Empty`가 그대로 들어갑니다(`PLCController.Address.cs:227`, `:239`). 빈 값 체크 없음. - `CheckRecipeNoComp:376`의 재폴링이 `plcRecipeNo` 필드를 덮어써, `ScoutSaveTag:886` 파일명에 0이 박힐 수 있습니다. (경미 — P5로 함께 해소) - 에코 워드 0 초기화 주체가 `SeqStatusMonitor:106-107` 한 곳입니다. `OnSeqActionStop`은 로그만 남깁니다(`:1231-1240`). 두 파일의 주석이 서로 다른 전제를 적고 있어 정리가 필요합니다.