- 분석_bit15_Resume_핸드셰이크_260805.md - 작업지시서_PTOP_PLC핸드셰이크_260805.md - 분석_HeavyAlarm_비트_동작_260810.md - 작업일지 2026_08_06.md
214 lines
10 KiB
Markdown
214 lines
10 KiB
Markdown
# 작업지시서 — 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`
|