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

10 KiB
Raw Blame History

작업지시서 — 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 의 에러 코드를 아무도 검사하지 않는다 ★ 최우선

// 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

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 제거, 대기 + 토스트

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회 수집을 실제 일치 검증으로 + 조기 종료

// 같은 값 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 버튼이 대기 중에도 듣는지.

관련 문서