LGES-OC5-PtoP-Welder/docs/작업지시서_PTOP_PLC핸드셰이크_260805.md
NohDoyeon241104 a43a531e00 docs: 미커밋 분석·작업지시서·작업일지 반영 (260805~260810)
- 분석_bit15_Resume_핸드셰이크_260805.md
- 작업지시서_PTOP_PLC핸드셰이크_260805.md
- 분석_HeavyAlarm_비트_동작_260810.md
- 작업일지 2026_08_06.md
2026-08-11 09:46:02 +09:00

214 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 작업지시서 — PTOP AutoRun PLC 핸드셰이크 읽기 검증
- **작성일**: 2026-08-05
- **대상**: `Machines/ILSOS_PrismaticRD_PTOP/Sequences/SeqAutoRunPTOP.cs`
- **증상**: AutoRun 중 간헐적으로 **Recipe No / Page No 를 PLC에서 받고도 제대로 읽어오지 못함**
- **상태**: 아래 §3만 소스 반영. §4는 **미반영 — 작업 대기**
---
## 1. 배경
정지(STOP) 시 BIT는 초기화하는데 WORD(Page·Recipe No)는 초기화하지 않는다는 점 때문에
WORD 0 초기화를 추가하려 했으나, 분석 결과 **증상과 방향이 반대**임이 확인됐다.
| | 방향 | 증상과의 관계 |
|---|---|---|
| WORD 0 초기화 | PC → PLC (쓰기) | **무관** |
| 실제 증상 | PLC → PC (읽기) | 여기가 원인 |
정지 시 WORD를 지울 필요가 없는 이유(코드 근거):
- 재진입 시 `CheckRecipeNoComp` / `CheckPageNoComp` 가 어차피 워드를 **재에코**한다
(`:395`, `:429`, `:563`).
- PLC가 보는 핸드셰이크 게이트는 워드가 아니라 **`PC_PAGE_CHANGE_COMPLETE`(0x08) 비트**이고
(`PLCController.Address.cs:63`), 그 비트는 정지 시 이미 false로 내려간다 (`:1206`).
- bit15 Resume(Manual 수동 출사 후 이어하기)은 직전 Recipe/Page 에코가 **남아 있어야** 한다
(`UCMainAutoRunViewModel.cs:226`).
---
## 2. 확인된 사실 (코드 근거)
### 2-1. `ReadWord` 의 에러 코드를 아무도 검사하지 않는다 ★ 최우선
```csharp
// Device/PLC/PLCController.Logic.cs:16
public (int err, short data) ReadWord(eWordInput address) => base.ReadWord((ushort)address, ePLCDevice.D);
```
`SeqAutoRunPTOP.cs` 에서 `.err` 참조 **0건**. 전 호출부가 `.data` 만 꺼낸다.
| 위치 | 호출 |
|---|---|
| `:349` | `plcRecipeNo = _PLC.ReadWord(PLC_RECIPE_NO).data;` |
| `:376` | `plcRecipeNo = _PLC.ReadWord(PLC_RECIPE_NO).data;` (재폴링) |
| `:508` | `plcPageNoList.Add(_PLC.ReadWord(PLC_PAGE_NO).data);` |
| `:571` | `curPlcPageNo = _PLC.ReadWord(PLC_PAGE_NO).data;` (ACK 검증) |
| `:763` | `thetaRaw = _PLC.ReadWord(HEAD_THETA_POSITION).data;` |
> 프레임워크 자신은 검사한다 — `WeldingCommon/Devices/PLC/PLCController.Logic.cs:30`
> `if (readValue.err != E1.OK) { Log.E("RMS Parameter Read Fail"); return E2.CANCELLED; }`
> **같은 코드베이스에 확립된 패턴인데 이 시퀀스만 따르지 않는다.**
`[추론]` 읽기 실패 시 `data` 가 `default(short)` = 0 으로 오는 것으로 보이나 **미검증**
(Melsec 드라이버까지 확인 필요). 다만 아래 2-2·2-3은 0이 올 때 증상과 정확히 일치한다.
### 2-2. 레시피는 1회만 읽고, 실패값 0이 "전환 의사 없음"으로 흡수된다
```
:348 // Recipe No 1회 읽기 (Page Change bit와 함께 래치되어 안정적이라고 가정 → 노이즈 검증 생략)
:349 plcRecipeNo = _PLC.ReadWord(PLC_RECIPE_NO).data;
:353 if (plcRecipeNo == 0) → "레시피 전환 생략, 현 Recipe 유지"
:356 return SequenceAction.Ok(); ← 알람도 정지도 없음, 에코도 안 함
```
**주석의 "가정"이 깨지는 것이 지금 증상이다.** 통신 실패 0과 PLC가 준 0이 구분되지 않아
**이전 레시피 그대로 용접**하는 경로가 열려 있다 (`rules.md` #1 — 주석 맹신 금지 사례).
읽기 순서상 레시피가 **가장 먼저, 지연 없이** 읽힌다:
```
CheckPageNo:457 PLC_PAGE_CHANGE_REQ ON 감지 → 즉시 break
↓ 지연 없음
CheckRecipeNoComp:349 ReadWord(PLC_RECIPE_NO) ← 1회, 무검증 ★ 가장 취약
↓
CheckPageNoComp:508 ReadWord(PLC_PAGE_NO) ×3 (200ms 간격)
```
PLC가 REQ 비트를 먼저 올리고 워드를 몇 스캔 뒤에 쓴다면 **레시피만 stale/0** 을 읽는다.
페이지는 600ms를 버니 살아남는다 — "레시피 번호를 못 읽어온다"와 부합.
### 2-3. "3회 연속 검증" 이 실제로는 검증이 아니다
```
:492 Log.I("PLC 페이지 번호 읽기 시작 (3회 연속 검증)")
:508 3개를 plcPageNoList 에 수집
:551 Log.E($"List {[0]}/{[1]}/{[2]}") ← 로그로 찍기만 함
:532 범위 검사는 plcPageNo(= 마지막 값) 하나만
```
**세 값을 서로 비교하는 코드가 없다.** `2, 0, 3` 으로 읽혀도 마지막 `3` 이 범위 안이면 통과한다.
즉 반복만 있고 일치 확인이 없어 **반복의 효과를 얻지 못한 채 600ms만 소비**한다.
---
## 3. 이번에 반영한 것 (소스 수정 완료)
### 3-1. `OnSeqActionStop()` — 워드 0 초기화 제거, 정지 모드만 로그
`SeqAutoRunPTOP.cs` `#region 260805`
```csharp
if (!ResumeMode) Log.I("일반 정지 (ResumeMode=false) → 에코 워드 보존, 0 초기화 안 함 (…)");
else Log.I("Resume 정지 (ResumeMode=true, bit15 Manual 전환) → 에코 워드 반드시 보존 (…)");
```
- **PLC 동작 영향 0** — 워드를 쓰지 않는다. 260621 협의 동작 그대로.
- 로그 값은 캐시 필드(`plcPageNo`/`plcRecipeNo`)다. 정지(특히 `OnForceError` 경로)에서
PLC를 다시 읽지 않기 위함.
- 0 초기화가 필요하다고 판단되면 **PLC 담당 확인 후 `!ResumeMode` 쪽에만** `WriteWord` 를 넣는다.
### 3-2. `CheckPageNoComp` — `return null` 제거, 대기 + 토스트
```csharp
invalidPageWait++;
Log.E($"Invalid PageNo received: {plcPageNo} (활성 Recipe 페이지 수: {recipePageCount}) — 대기 {invalidPageWait}회차");
if (invalidPageWait == 1 || invalidPageWait % TOAST_EVERY == 0)
MessageUtil.ShowErrorToast($"PLC Page No 이상: {plcPageNo}\n모니터링 확인해주세요.");
plcPageNoList.Clear(); checknum = 0; Thread.Sleep(3000); continue;
```
- 종전 `return null` 은 **Aurora 시퀀스 중단**이었다
(`docs/Aurora에이전트레퍼런스모음/02_patterns/sequence-builder.md:36` — `null 중단`).
일시적 오독 한 번에 AutoRun이 죽고 재시작이 필요했다.
- 이제 **중단하지 않고 이 노드에서 대기·재수집**한다. 작업자가 토스트를 보고 모니터링에서
값을 확인·조치하면 다음 회차에 정상값을 읽어 **AutoRun 재시작 없이 이어간다.**
- `recipePageCount` 는 매 회차 활성 Recipe 의 `Model.Layers.Count()` 로 다시 구한다
(`:531`). **Recipe마다 페이지 수가 다르고**, 레시피 반영이 늦으면 이 값도 늦게 맞으므로
재시도가 그 지연을 흡수한다.
- 토스트는 첫 회차 + 이후 10회마다(약 30초 간격). 폭주 방지.
- 탈출 경로는 `while (!context.Stopped)` — **Stop 버튼 정상 동작.**
> ⚠ 상한이 없으므로 조건이 해소되지 않으면 **무기한 대기**한다. 의도된 동작이다
> (작업자 개입 전제). 상한이 필요해지면 `invalidPageWait` 로 쉽게 추가할 수 있다.
---
## 4. 작업 지시 (미반영 — 대기)
우선순위 순. **1번만 해도 원인 규명이 된다.**
### [P1] `ReadWord` err 체크 래퍼 도입
- `using Hermes;` 추가 (`E1.OK`). 같은 폴더의 `SeqStreamTest.cs` 등이 이미 사용.
- 헬퍼 예: `private (bool ok, short data) ReadWordChecked(eWordInput addr, string caller, int retry, int delayMs)`
- `err == E1.OK` 확인 → 실패 시 재시도 → 최종 실패면 `ok=false`
- **재시도 후 성공한 경우도 `Log.W`** 로 남길 것 — "간헐적 삐끗"이 로그에 잡히는 지점
- 적용: §2-1 표의 5개 호출부
- **원칙: 시퀀스 흐름은 바꾸지 않는다.** 현장 가동 중이므로 로그·토스트로만 구분한다.
로그로 갈려야 하는 4가지:
| 로그 | 의미 |
|---|---|
| `읽기 재시도 N회 후 성공` | 간헐적 통신 삐끗 ← **현 증상 후보** |
| `ReadWord 최종 실패` | 지속 통신 장애 |
| `PLC_RECIPE_NO: [0] ※읽기실패값` | 0이 통신 실패였음 |
| `PLC_RECIPE_NO: [0]` (경고 없음) | **PLC가 진짜 0을 준 것** |
### [P2] 레시피 다중 읽기 + 일치 검증
`CheckRecipeNoComp:349` 를 페이지와 대칭으로. §2-2의 settle race 흡수용.
**단, P1 로그로 원인을 확정한 뒤 판단할 것** — 불필요하면 사이클 타임만 늘어난다.
### [P3] 3회 수집을 실제 일치 검증으로 + 조기 종료
```csharp
// 같은 값 2회 연속 → 안정으로 보고 조기 종료 (정상 시 600ms → 400ms)
if (checknum >= 2 && plcPageNoList[checknum-1] == plcPageNoList[checknum-2]) break;
// 3회 다 읽었는데 흔들리면 → 마지막 값을 믿지 말고 재수집
if (checknum > 2 && plcPageNoList.Distinct().Count() > 1) { 재수집 }
```
⚠ 범위 검사가 `if (checknum > 2)` 블록 **안**에 있으므로(`:532`), 조기 종료를 넣으려면
그 검사를 블록 밖으로 빼는 구조 변경이 필요하다.
### [P4] `ReadBit` err 체크
`:457`, `:695`, `:902`, `:928`, `:1148`. 전부 대기 루프 안이라 실패해도 다음 폴링에서
재시도된다 — **위험도 낮음.** 여력 있을 때.
---
## 5. PLC 협의 / 미확인 사항
| 항목 | 내용 |
|---|---|
| REQ 비트 ↔ 워드 타이밍 | PLC가 `PAGE_CHANGE_REQ`(M70508)와 워드(D70501/D70502)를 **같은 스캔**에 쓰는가, 비트를 먼저 올리는가. 비트가 먼저면 P2 필수, 동시면 P1만으로 충분 |
| 읽기 실패 시 반환값 | `[추론]` `data`=0 으로 보이나 미검증. Melsec 드라이버 확인 필요 |
| 정지 시 에코 워드 0 초기화 | PLC가 PC측 에코 워드 0을 "무효"로 보는지 "0번"으로 보는지. **현재 미결 → 그래서 3-1에서 쓰지 않음** |
| 현장 로그 | `PLC_RECIPE_NO: [0]` / `Invalid PageNo received: 0` 이 남아 있는지. 있으면 §2 분석이 즉시 확정된다 |
---
## 6. 검증 방법
1. **빌드**
`msbuild Machines/ILSOS_PrismaticRD_PTOP/ILSOS_PrismaticRD_PTOP.csproj /p:Configuration=Debug /p:Platform=x64 /v:m /nologo`
2. **3-1 확인** — AutoRun 정지 후 로그에 `일반 정지 (ResumeMode=false)` 또는
`Resume 정지 (ResumeMode=true…)` 중 하나가 찍히는지. PLC 모니터에서 D70001/D70002가
**이전 값을 유지**하는지.
3. **3-2 확인** — 범위 밖 PageNo 유입 시 AutoRun이 **죽지 않고** 토스트가 뜨는지.
모니터링에서 값 조치 후 **재시작 없이 이어지는지.** Stop 버튼이 대기 중에도 듣는지.
---
## 관련 문서
- **bit15 Resume 정체 분석 + PLC 질문 목록**:
[분석_bit15_Resume_핸드셰이크_260805.md](./분석_bit15_Resume_핸드셰이크_260805.md)
- 하네스: [`harness-ptop.md`](./harness-ptop.md)
- 협업 규칙: [`../rules.md`](../rules.md) — 특히 #1(주석 맹신 금지), #2(수정 승인), #15·#16(추론 표기)
- Aurora 시퀀스 규칙: `docs/Aurora에이전트레퍼런스모음/02_patterns/sequence-builder.md`