feat: CheckPageNo 핸드셰이크 진단계측 (ISSUE-260826-01)

본접 레시피(23/24)에서 page 5 완료 후 page 6 으로 진행되지 않는 건의 원인 구간을
좁히기 위해 CheckPageNo 대기 중 PLC/PC 신호 전체 스냅샷을 기록한다.
2026-08-26 현장 재현으로 원인 확정 완료.

진단계측 (마커 [진단계측 2026-08-26], region 삭제로 원복):
- CheckPageNo 대기 진입 시 스냅샷 1회
- 1초 주기 변화 감지 (값이 바뀔 때만 기록 -> 정상 통과 구간은 1~2줄, 로그 폭주 없음)
- 기존 60초 TIMEOUT 로그에 누적 대기시간 + 스냅샷 추가
- REQ 수신 순간 확정값 (성공 케이스 기준선)
- 기록: PLC 입력 9종(REQ/PAGE/RECIPE/AUTO/AUTO_REQ/M70515/TRIG_REQ/CMP_ACK/D70510)
        + PC 출력 되읽기 7종. 읽기 실패는 E 로 표기해 "0 인지 못 읽은 건지" 구분
- REQ 폴링은 10ms 유지, 스냅샷만 1초 주기(MC 개별읽기 16회라 주기 단축 금지)

계측 결과 (상세 문서는 로컬 전용 — docs/ 는 .gitignore 추적 제외):
  docs/분석/2026/08/26/Page5_Page6_REQ_미발행_PLC문의_260826.md
- page5->6 진입 시 PC 출력이 가접/본접 비트 단위로 완전 동일한데 PLC 반응만 다름
  가접 21/22: 2.39s / 2.41s 후 REQ:1 -> page6 진행
  본접 23   : 76.6s 동안 REQ:0 / PAGE:5 / AUTO:1 무변화 -> 이동 스텝 미진입
- PC_WELD_COMPLETE 는 전환 조건이 아님이 증명됨(전 구간 0 인데 가접은 정상 진행)
- 에코 워드 0/0 에서도 REQ 발행됨 -> "PC 에코 == PLC 워드" 조건 아님 (260820 문항 c 종결)
- REQ 가 페이지 워드보다 약 200ms 먼저 올라옴(성공 케이스 포함) -> PLC 에 순서 교정 요청

PLC 제어팀 전달 완료, 회신 대기 중.
This commit is contained in:
노도연 2026-08-27 09:27:25 +09:00
parent 5799872867
commit 18ca9bc3f4

View File

@ -698,6 +698,22 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences
// 1. PLC_PAGE_CHANGE_REQ (bit 8) ON 대기
var interfaceStartTime = DateTime.Now;
#region [진단계측 2026-08-26] page5→6 REQ 미발행 추적 (원복: 이 region + 아래 2개 삽입부 + 헬퍼 2개 삭제)
// 배경: 2026-08-26 로그에서 레시피 23/24 의 page 5 Resume 후 PLC_PAGE_CHANGE_REQ 가 오지 않아 교착.
// 레시피 21/22 는 같은 5→6 전환이 2.4~3.1초 만에 성공한다(그 구간만 REQ 가 OFF 로 떨어진다).
// 기존 로그는 "TIMEOUT..." 한 줄뿐이라 대기 중 PLC 워드/비트가 어떻게 움직이는지 알 수 없었다.
// 특히 PLC_PAGE_NO 가 6 으로 올라갔는데 REQ 만 안 오는지, 5 에서 멈춘 건지 구분이 안 된다.
// · PAGE:6 + REQ:0 → PLC 가 페이지는 올렸으나 REQ 발행 조건 미충족
// · PAGE:5 + REQ:0 → PLC 스텝이 page 5 에서 아예 진행하지 않음
// 방식: REQ 폴링(10ms)은 그대로 두고, 전체 스냅샷은 1초 주기로만 뜬다(MC 개별읽기 부하 억제).
// 값이 바뀔 때만 남기므로 정상 통과(0~3초) 구간에서는 1~2줄이고 로그 폭주가 없다.
// 분석: docs/분석/2026/08/26/Page5_Page6_REQ_미발행_PLC문의_260826.md
var diagWaitStart = DateTime.Now;
var diagNextPoll = DateTime.Now;
string diagLastSnapshot = null;
Log.W($"[진단계측][CheckPageNo] 대기 진입 {BuildHandshakeSnapshot()}");
#endregion
while (!context.Stopped)
{
//plc
@ -727,18 +743,87 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences
if (DateTime.Now.Subtract(interfaceStartTime).TotalSeconds > PLC_TIMEOUT)
{
Log.W("[CheckPageNo] Waiting for PLC_PAGE_CHANGE_REQ -> TIMEOUT...");
// [진단계측 2026-08-26] 기존 한 줄에 스냅샷을 붙인다. 원복 시 뒤쪽 보간만 지우면 된다.
Log.W($"[CheckPageNo] Waiting for PLC_PAGE_CHANGE_REQ -> TIMEOUT... " +
$"(누적 {DateTime.Now.Subtract(diagWaitStart).TotalSeconds:F0}s) {BuildHandshakeSnapshot()}");
interfaceStartTime = DateTime.Now;
}
#region [진단계측 2026-08-26] 1초 주기 변화 감지 (원복: 이 region 삭제)
if (DateTime.Now >= diagNextPoll)
{
diagNextPoll = DateTime.Now.AddSeconds(1);
var snapshot = BuildHandshakeSnapshot();
if (snapshot != diagLastSnapshot)
{
Log.W($"[진단계측][CheckPageNo] 변화 " +
$"(대기 {DateTime.Now.Subtract(diagWaitStart).TotalSeconds:F1}s) {snapshot}");
diagLastSnapshot = snapshot;
}
}
#endregion
Thread.Sleep(10);
}
if (context.Stopped) return null;
// [진단계측 2026-08-26] REQ 수신 시점 확정값. 21/22(성공) 대비 23/24(실패) 비교의 기준선이 된다.
Log.W($"[진단계측][CheckPageNo] REQ 수신 " +
$"(대기 {DateTime.Now.Subtract(diagWaitStart).TotalSeconds:F2}s) {BuildHandshakeSnapshot()}");
Log.I($"[CheckPageNo] PLC_PAGE_CHANGE_REQ ON 감지 → CheckPageNoComp 이동");
return SequenceAction.Ok();
}
#region [진단계측 2026-08-26] 핸드셰이크 스냅샷 헬퍼 (원복: 이 region 통째로 삭제)
// PLC 입력 / PC 출력 되읽기를 한 줄로 만든다. 개별 MC 읽기 16회라 호출 주기는 1초 이상으로 유지할 것.
// 읽기 실패는 값 대신 E 로 표기해 "0 인지 못 읽은 건지"를 구분한다.
private string BuildHandshakeSnapshot()
{
if (_PLC == null) return "PLC=null";
try
{
var req = _PLC.ReadBit(eBitInput.PLC_PAGE_CHANGE_REQ);
var plcAuto = _PLC.ReadBit(eBitInput.PLC_AUTO_STATUS);
var plcAReq = _PLC.ReadBit(eBitInput.PLC_AUTO_REQ);
var bit15 = _PLC.ReadBit(eBitInput.PLC_WELD_READY_REQ);
var trigReq = _PLC.ReadBit(eBitInput.PLC_WELD_TRIGGER_REQ);
var cmpAck = _PLC.ReadBit(eBitInput.PLC_WELD_COMPLETE_ACK);
var plcPage = _PLC.ReadWord(eWordInput.PLC_PAGE_NO);
var plcRcp = _PLC.ReadWord(eWordInput.PLC_RECIPE_NO);
var theta = _PLC.ReadWord(eWordInput.HEAD_THETA_POSITION);
var pcAuto = _PLC.ReadOutBit(eBitOutput.PC_AUTO_STATUS);
var pcAck = _PLC.ReadOutBit(eBitOutput.PC_WELD_READY_ACK);
var pcPgCmp = _PLC.ReadOutBit(eBitOutput.PC_PAGE_CHANGE_COMPLETE);
var pcWldCmp = _PLC.ReadOutBit(eBitOutput.PC_WELD_COMPLETE);
var pcBusy = _PLC.ReadOutBit(eBitOutput.PC_WELD_BUSY);
var pcPage = _PLC.ReadOutWord(eWordOutput.PC_PAGE_NO);
var pcRcp = _PLC.ReadOutWord(eWordOutput.PC_RECIPE_NO);
return $"PLC[REQ:{DiagBit(req)} PAGE:{DiagWord(plcPage)} RCP:{DiagWord(plcRcp)} " +
$"AUTO:{DiagBit(plcAuto)} AUTO_REQ:{DiagBit(plcAReq)} bit15:{DiagBit(bit15)} " +
$"TRIG_REQ:{DiagBit(trigReq)} CMP_ACK:{DiagBit(cmpAck)} THETA:{DiagWord(theta)}] " +
$"PC[AUTO:{DiagBit(pcAuto)} READY_ACK:{DiagBit(pcAck)} PAGE_CMP:{DiagBit(pcPgCmp)} " +
$"WELD_CMP:{DiagBit(pcWldCmp)} BUSY:{DiagBit(pcBusy)} " +
$"PAGE:{DiagWord(pcPage)} RCP:{DiagWord(pcRcp)}] " +
$"ResumeMode:{(ResumeMode ? "1" : "0")}";
}
catch (Exception ex)
{
return $"스냅샷 실패({ex.GetType().Name}: {ex.Message})";
}
}
private static string DiagBit((int err, bool data) read)
=> read.err == Hermes.E1.OK ? (read.data ? "1" : "0") : "E";
private static string DiagWord((int err, short data) read)
=> read.err == Hermes.E1.OK ? read.data.ToString() : "E";
#endregion
#region 260806 구버전 보존 — CheckPageNoComp (원복: 아래 블록 주석을 풀고 뒤의 260806 신규 region을 제거)
/*