diff --git a/Machines/ILSOS_PrismaticRD_PTOP/Sequences/SeqAutoRunPTOP.cs b/Machines/ILSOS_PrismaticRD_PTOP/Sequences/SeqAutoRunPTOP.cs index 9b2b0d4..c11a130 100644 --- a/Machines/ILSOS_PrismaticRD_PTOP/Sequences/SeqAutoRunPTOP.cs +++ b/Machines/ILSOS_PrismaticRD_PTOP/Sequences/SeqAutoRunPTOP.cs @@ -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을 제거) /*