feat: bit15 보강·양극 미사용 정리·로그 개편

- D70002(PC_RECIPE_NO): 동일 레시피여도 매 핸드셰이크 에코 유지
- 정지 시 에코워드(Page/Recipe No) 0 초기화 제거(보존)
- bit15 Manual 전환 시 레시피 활성화+로드, bit15 ON 중 AUTO_REQ 자동시작 무시
- [AutoRun Resume] PC bit15(0x0F) 1초 펄스 OFF
- 양극(P) 미사용: PC_AUTO_STATUS_P/양극 용접 핸드셰이크 제거, 용접 응답 음극 통일
- 공유 eCommon READY(0x0F) 제거(PTOP enum은 유지)
- SeqAutoRunPTOP 로그 설명형 개편(노드 Start/End 제거)
- 빌드 0오류. Recipe/Page 수정분은 현장 미적용(추후 검증 예정)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
to3796zw 2026-06-21 20:15:43 +09:00
parent d131ad9176
commit e61e99f9ab
4 changed files with 149 additions and 143 deletions

View File

@ -184,7 +184,7 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences
private bool InitSequenceVariable(SequenceContext context) private bool InitSequenceVariable(SequenceContext context)
{ {
Log.I($"[InitSequenceVariable] 노드 Start"); Log.I($"[InitSequenceVariable] 캔버스 핸들 확보 + 용접대상 오브젝트 선택 시작");
var mainPage = CommonHelper.GetMainPage(); var mainPage = CommonHelper.GetMainPage();
_ScannerCanvasUserCtrl = mainPage.ScannerCanvasCtrlTeaching; _ScannerCanvasUserCtrl = mainPage.ScannerCanvasCtrlTeaching;
@ -204,7 +204,7 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences
//Status.GetProcessingWeldingData().WeldingScannerListNo = _WeldingScannerListNo; //Status.GetProcessingWeldingData().WeldingScannerListNo = _WeldingScannerListNo;
//Status.GetProcessingWeldingData().SelectedDrawObjectList = _SelectedObjectList; //Status.GetProcessingWeldingData().SelectedDrawObjectList = _SelectedObjectList;
Log.I($"[InitSequenceVariable] 노드 End"); Log.I($"[InitSequenceVariable] 변수 초기화 완료 (선택 오브젝트 {_SelectedObjectList.Count()}개)");
return true; return true;
} }
@ -250,8 +250,7 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences
// 장비 체크 // 장비 체크
InitDev(context); InitDev(context);
_PLC.WriteBit(eBitOutput.PC_AUTO_STATUS, true); // 음극 _PLC.WriteBit(eBitOutput.PC_AUTO_STATUS, true); // 음극 (양극 P 미사용)
_PLC.WriteBit(eBitOutput.PC_AUTO_STATUS_P, true); //양극
#region [TEST_BYPASS] Alarm Check - DeFvice Ready #region [TEST_BYPASS] Alarm Check - DeFvice Ready
/* /*
if (!CheckDeviceReady(context)) if (!CheckDeviceReady(context))
@ -321,11 +320,13 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences
return SequenceAction.Ok(); return SequenceAction.Ok();
} }
// 이미 활성 레시피면 재활성화·워드 재기록 생략 (매 페이지마다 PC_RECIPE_NO 워드 변경 금지) // 이미 활성 레시피면 재활성화·Model reload는 생략하되, PC_RECIPE_NO 에코는 반드시 유지.
// (정지 시 OnSeqActionStop이 D70002=0으로 지우므로, 동일 레시피 재진입에서 에코를 건너뛰면 PLC가 0/이전값을 봄)
var preActivated = _RecipeManager.ActivatedRecipe(); var preActivated = _RecipeManager.ActivatedRecipe();
if (preActivated != null && preActivated.Id == recipe.Id) if (preActivated != null && preActivated.Id == recipe.Id)
{ {
Log.I($"[CheckRecipeNoComp] 이미 활성 레시피({recipe.Id}) → 재활성화 생략 → CheckPageNoComp 이동"); _PLC.WriteWord(eWordOutput.PC_RECIPE_NO, plcRecipeNo);
Log.I($"[CheckRecipeNoComp] 이미 활성 레시피({recipe.Id}) → 재활성화 생략, PC_RECIPE_NO 에코({plcRecipeNo}) 유지 → CheckPageNoComp 이동");
return SequenceAction.Ok(); return SequenceAction.Ok();
} }
@ -515,9 +516,8 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences
} }
private bool LaserReadyResponse() private bool LaserReadyResponse()
{ {
Log.I($"[LaserReadyResponse] 노드 Start"); Log.I($"[LaserReadyResponse] 레이저 Ready 응답 (현재 no-op)");
//if (ApplicationExt.IsSimulation) //if (ApplicationExt.IsSimulation)
Log.I($"[LaserReadyResponse] 노드 End");
return true; return true;
} }
private SequenceActionResult SetUploadWeldingList_SeqParams(SequenceContext context, string id, SequenceActionResult param) private SequenceActionResult SetUploadWeldingList_SeqParams(SequenceContext context, string id, SequenceActionResult param)
@ -648,38 +648,13 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences
Log.I($"[---> WaitWeldingStart] 용접 준비 신호 송신 - PageNo: [{plcPageNo}] CurrentPage: {_CurrentPageNo + 1})"); Log.I($"[---> WaitWeldingStart] 용접 준비 신호 송신 - PageNo: [{plcPageNo}] CurrentPage: {_CurrentPageNo + 1})");
Status.SetWorkingPosition(eWorkingPosition.WAITING_WELD_START); Status.SetWorkingPosition(eWorkingPosition.WAITING_WELD_START);
if (plcPageNo == 1) // 양극(P) 미사용 → 모든 페이지 음극 용접 비트로 응답 (PLC 트리거가 음극 PLC_WELD_TRIGGER_REQ라 정합)
{ _PLC.WriteBit((int)eBitOutput.PC_WELD_TRIGGER_ACK, true);
// 2. 준비 완료(Ack) 및 가동 중(Busy) 비트 ON _PLC.WriteBit((int)eBitOutput.PC_WELD_BUSY, true);
_PLC.WriteBit((int)eBitOutput.PC_WELD_TRIGGER_ACK, true); Thread.Sleep(200);
_PLC.WriteBit((int)eBitOutput.PC_WELD_BUSY, true); _PLC.WriteBit((int)eBitOutput.PC_WELD_COMPLETE, false);
Thread.Sleep(200); Thread.Sleep(5);
_PLC.WriteBit((int)eBitOutput.PC_WELD_COMPLETE, false); Log.I($"[WaitWeldingStart] Welding READY_ACK=ON / BUSY=ON / COMPLETE=OFF 송신 (PageNo:{plcPageNo})");
Thread.Sleep(5);
Log.I($"[WaitWeldingStart] WriteBit Anode Welding Ready ACK TRUE");
Log.I($"[WaitWeldingStart] WriteBit Anode Welding Busy TRUE");
Log.I($"[WaitWeldingStart] WriteBit Anode Welding Comp False");
//Thread.Sleep(10);
}
else if (plcPageNo == 2)
{
// 2. 준비 완료(Ack) 및 가동 중(Busy) 비트 ON
_PLC.WriteBit((int)eBitOutput.PC_WELD_P_READY_REQ, true);
_PLC.WriteBit((int)eBitOutput.PC_WELD_P_BUSY, true);
Thread.Sleep(200);
_PLC.WriteBit((int)eBitOutput.PC_WELD_P_COMP_ACK, false);
Thread.Sleep(5);
Log.I($"[WaitWeldingStart] WriteBit Cathode Welding Ready REQ TRUE");
Log.I($"[WaitWeldingStart] WriteBit Cathode Welding Busy TRUE");
Log.I($"[WaitWeldingStart] WriteBit Cathode Welding Comp False");
//Thread.Sleep(10);
}
else // PAGE NUM = 0
{
Log.I($"[WaitWeldingStart] Page num 1,2 etc..");
}
// 디버그 끝내고 삭제 // 디버그 끝내고 삭제
//MessageUtil.ShowToast("WaitWeldingStart -> WriteBit True"); //MessageUtil.ShowToast("WaitWeldingStart -> WriteBit True");
@ -789,38 +764,16 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences
Log.D(string.Format($"[ExecuteWelding] Change SelectCorrectionTable Time : {_WeldingSpanTime.TotalMilliseconds}")); Log.D(string.Format($"[ExecuteWelding] Change SelectCorrectionTable Time : {_WeldingSpanTime.TotalMilliseconds}"));
if (plcPageNo == 1) // 양극(P) 미사용 → 모든 페이지 음극 완료 비트로 응답
{ _PLC.WriteBit((int)eBitOutput.PC_WELD_TRIGGER_ACK, false);
// 용접끝 Log.I($"[ExecuteWelding] 용접완료 송신 - READY_ACK=false (PageNo:{plcPageNo})");
_PLC.WriteBit((int)eBitOutput.PC_WELD_TRIGGER_ACK, false); Thread.Sleep(5);
Log.I("[PLCInterface] PageNo[1] READY_ACK false"); _PLC.WriteBit((int)eBitOutput.PC_WELD_BUSY, false);
Thread.Sleep(5); Log.I($"[ExecuteWelding] 용접완료 송신 - BUSY=false (PageNo:{plcPageNo})");
// 용접중 신호로 변경 Thread.Sleep(5);
_PLC.WriteBit((int)eBitOutput.PC_WELD_BUSY, false); _PLC.WriteBit((int)eBitOutput.PC_WELD_COMPLETE, true);
Log.I("[PLCInterface] PageNo[1] BUSY false"); Log.I($"[ExecuteWelding] 용접완료 송신 - COMPLETE=true (PageNo:{plcPageNo})");
Thread.Sleep(5); Thread.Sleep(5);
_PLC.WriteBit((int)eBitOutput.PC_WELD_COMPLETE, true);
Log.I("[PLCInterface] PageNo[1] COMPLETE true");
Thread.Sleep(5);
}
else if (plcPageNo == 2)
{
// 용접끝
_PLC.WriteBit((int)eBitOutput.PC_WELD_P_READY_REQ, false);
Log.I("[PLCInterface] PageNo[2] READY_REQ false");
Thread.Sleep(5);
// 용접중 신호로 변경
_PLC.WriteBit((int)eBitOutput.PC_WELD_P_BUSY, false);
Log.I("[PLCInterface] PageNo[2] BUSY false");
Thread.Sleep(5);
_PLC.WriteBit((int)eBitOutput.PC_WELD_P_COMP_ACK, true);
Log.I("[PLCInterface] PageNo[2] COMP_ACK true");
Thread.Sleep(5);
}
else
{
}
Status.SetWorkingPosition(eWorkingPosition.UNLOADING_CELL); Status.SetWorkingPosition(eWorkingPosition.UNLOADING_CELL);
#endregion #endregion
@ -867,17 +820,17 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences
// 실제 동작 _Scanner에서 // 실제 동작 _Scanner에서
private void SaveScannerRecordFileForamt() private void SaveScannerRecordFileForamt()
{ {
Log.I($"[SaveScannerRecordFileForamt] 노드 Start"); Log.I($"[SaveScannerRecordFileForamt] 스캐너 파형 기록 저장 시작 (SaveRecord)");
// ── 용접 완료 후 파형 데이터 저장 ──────────────────────────── // ── 용접 완료 후 파형 데이터 저장 ────────────────────────────
_Scanner.SaveRecord("AutoRunTDC"); _Scanner.SaveRecord("AutoRunTDC");
Log.I("[SaveResultData] SaveRecord Complete"); Log.I("[SaveScannerRecordFileForamt] SaveRecord 완료");
_Scanner.JumpAbs(0, 0, eFunctionType.COMMAND); _Scanner.JumpAbs(0, 0, eFunctionType.COMMAND);
_Scanner.SelectCorrectionTable(1); _Scanner.SelectCorrectionTable(1);
Log.D("[SaveResultData] JumpAbs + SelectCorrectionTable Complete"); Log.D("[SaveScannerRecordFileForamt] JumpAbs(0,0) + CorrectionTable(1) 복귀 완료");
//Thread.Sleep(1000); // 추후 제거 //Thread.Sleep(1000); // 추후 제거
Log.I($"[SaveScannerRecordFileForamt] 노드 End"); Log.I($"[SaveScannerRecordFileForamt] 스캐너 파형 기록 저장 완료");
} }
// 이곳에서 bit 초기화 진행함. 260512 // 이곳에서 bit 초기화 진행함. 260512
private SequenceActionResult WriteCompletionPLCBit(SequenceContext context, string id, SequenceActionResult param) private SequenceActionResult WriteCompletionPLCBit(SequenceContext context, string id, SequenceActionResult param)
@ -934,7 +887,7 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences
private bool RestoreDeviceStatus() private bool RestoreDeviceStatus()
{ {
Log.I($"[RestoreDeviceStatus] 노드 Start"); Log.I($"[RestoreDeviceStatus] PLC 출력 비트 리셋 시작 (Page/Weld 음극)");
////PLC All BIT OFF ////PLC All BIT OFF
_PLC.WriteBit((int)eCommonBitOutput.PC_PAGE_CHANGE_COMPLETE, false); // 페이지 false _PLC.WriteBit((int)eCommonBitOutput.PC_PAGE_CHANGE_COMPLETE, false); // 페이지 false
@ -943,29 +896,26 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences
_PLC.WriteBit((int)eCommonBitOutput.PC_WELD_COMPLETE, false); // 용접 완료 false _PLC.WriteBit((int)eCommonBitOutput.PC_WELD_COMPLETE, false); // 용접 완료 false
Log.I(string.Format("[PLCInterface]PC Anod Bit Off")); Log.I(string.Format("[PLCInterface]PC Anod Bit Off"));
_PLC.WriteBit((int)eCommonBitOutput.PC_WELD_P_READY_REQ, false); // 용접 준비 false // 양극(P) 미사용 — 양극 용접 비트 리셋 제거
_PLC.WriteBit((int)eCommonBitOutput.PC_WELD_P_BUSY, false); // 동작 중 false
_PLC.WriteBit((int)eCommonBitOutput.PC_WELD_P_COMP_ACK, false); // 용접 완료 false
Log.I(string.Format("[PLCInterface]PC Cathode Bit Off"));
//Thread.Sleep(1000); // 추후 제거 //Thread.Sleep(1000); // 추후 제거
Log.I($"[RestoreDeviceStatus] 노드 End"); Log.I($"[RestoreDeviceStatus] PLC 출력 비트 리셋 완료");
return true; return true;
} }
// 장비 3개 가져오기 완료 // 장비 3개 가져오기 완료
private void InitDev(SequenceContext context) private void InitDev(SequenceContext context)
{ {
Log.I($"[InitDev] 노드 Start"); Log.I($"[InitDev] 디바이스 핸들 확보 시작 (Scanner/PLC/IO)");
_Scanner = context.GetLaserScanner(); _Scanner = context.GetLaserScanner();
_PLC = context.GetPLC() as PLCControllerPTOP; _PLC = context.GetPLC() as PLCControllerPTOP;
_IO = context.GetIOUnit(); _IO = context.GetIOUnit();
Log.I($"[InitDev] 노드 End"); Log.I($"[InitDev] 디바이스 핸들 확보 완료");
} }
private bool CheckDeviceReady(SequenceContext context) private bool CheckDeviceReady(SequenceContext context)
{ {
Log.I($"[CheckDeviceReady] 노드 Start"); Log.I($"[CheckDeviceReady] 디바이스 활성 상태 확인 시작 (Scanner/Laser/PLC/IO)");
List<string> deviceListToCheck = new List<string>() { CommonDeviceIds.Scanner, CommonDeviceIds.Laser, List<string> deviceListToCheck = new List<string>() { CommonDeviceIds.Scanner, CommonDeviceIds.Laser,
CommonDeviceIds.PLCDevice, CommonDeviceIds.IODevice }; CommonDeviceIds.PLCDevice, CommonDeviceIds.IODevice };
bool isAllDeviceActivated = true; bool isAllDeviceActivated = true;
@ -978,38 +928,37 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences
} }
Log.I($"[CheckDeviceReady] 노드 End"); Log.I($"[CheckDeviceReady] 디바이스 활성 상태 확인 완료 (AllActive={isAllDeviceActivated})");
return isAllDeviceActivated; return isAllDeviceActivated;
} }
private bool CheckCondition(SequenceContext context) private bool CheckCondition(SequenceContext context)
{ {
Log.I($"[CheckCondition] 노드 Start"); Log.I($"[CheckCondition] 시작 조건 확인 (인터락 + 디바이스 초기상태)");
// 체크 신호 plc // 체크 신호 plc
if (CheckCondition_Interlock()) if (CheckCondition_Interlock())
{ {
if (InitializeDeviceStatus(context)) if (InitializeDeviceStatus(context))
{ {
Log.I($"[CheckCondition] 노드 End"); Log.I($"[CheckCondition] 조건 충족 → 시작 가능");
return true; return true;
} }
} }
Log.I($"[CheckCondition] 노드 End"); Log.I($"[CheckCondition] 조건 불충족 → 시작 불가");
return false; return false;
} }
private bool CheckCondition_Interlock() private bool CheckCondition_Interlock()
{ {
Log.I($"[CheckCondition_Interlock] 노드 Start");
// PLC, Data, IO 등 Interlock checking // PLC, Data, IO 등 Interlock checking
Log.I($"[CheckCondition_Interlock] 노드 End"); Log.I($"[CheckCondition_Interlock] 인터락 확인 (현재 항상 통과)");
return true; return true;
} }
private bool InitializeDeviceStatus(SequenceContext context) private bool InitializeDeviceStatus(SequenceContext context)
{ {
Log.I($"[InitializeDeviceStatus] 노드 Start"); Log.I($"[InitializeDeviceStatus] 디바이스 초기상태 설정 시작 (AutoRun 비트 + LBAD Ready)");
// PLC Interface AutoStatus Start // PLC Interface AutoStatus Start
InitAutoRunBit(); InitAutoRunBit();
@ -1043,15 +992,14 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences
_IO._iOUnit.WriteDoBit(Convert.ToInt32(eADOAddress.PCLBADReadyStatus), true); _IO._iOUnit.WriteDoBit(Convert.ToInt32(eADOAddress.PCLBADReadyStatus), true);
Log.I(string.Format($"[IOInterface]SET PCLBADReadyStatus true")); Log.I(string.Format($"[IOInterface]SET PCLBADReadyStatus true"));
Log.I($"[InitializeDeviceStatus] 노드 End"); Log.I($"[InitializeDeviceStatus] 디바이스 초기상태 설정 완료");
return true; return true;
} }
private void InitAutoRunBit() private void InitAutoRunBit()
{ {
Log.I($"[InitAutoRunBit] 노드 Start"); Log.I($"[InitAutoRunBit] AutoRun 시작 PLC 비트 초기화 시작 (음극)");
Log.I(string.Format($"[PLCInterface] Start AutoRun PLC Bit Initialize"));
_PLC.WriteBit((int)eBitOutput.PC_WELD_TRIGGER_ACK, false); _PLC.WriteBit((int)eBitOutput.PC_WELD_TRIGGER_ACK, false);
Log.I(string.Format($"[PLCInterface]SET (-)PC_WELD_TRIGGER_ACK False")); Log.I(string.Format($"[PLCInterface]SET (-)PC_WELD_TRIGGER_ACK False"));
_PLC.WriteBit((int)eBitOutput.PC_WELD_BUSY, false); _PLC.WriteBit((int)eBitOutput.PC_WELD_BUSY, false);
@ -1061,35 +1009,24 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences
_PLC.WriteBit((int)eBitOutput.PC_AUTO_STATUS, true); _PLC.WriteBit((int)eBitOutput.PC_AUTO_STATUS, true);
Log.I(string.Format($"[PLCInterface]SET (-)PC_AUTO_STATUS True")); Log.I(string.Format($"[PLCInterface]SET (-)PC_AUTO_STATUS True"));
Log.I(string.Format($"[PLCInterface] Start AutoRun PLC Bit Initialize")); // 양극(P) 미사용 — 양극 초기화 비트 제거
_PLC.WriteBit((int)eBitOutput.PC_WELD_P_READY_REQ, false); Log.I($"[InitAutoRunBit] AutoRun 시작 PLC 비트 초기화 완료");
Log.I(string.Format($"[PLCInterface]SET (+)PC_WELD_TRIGGER_ACK False"));
_PLC.WriteBit((int)eBitOutput.PC_WELD_P_BUSY, false);
Log.I(string.Format($"[PLCInterface]SET (+)PC_WELD_BUSY False"));
_PLC.WriteBit((int)eBitOutput.PC_WELD_P_COMP_ACK, false);
Log.I(string.Format($"[PLCInterface]SET (+)PC_WELD_COMPLETE False"));
_PLC.WriteBit((int)eBitOutput.PC_AUTO_STATUS_P, true);
Log.I(string.Format($"[PLCInterface]SET (+)PC_AUTO_STATUS True"));
Log.I($"[InitAutoRunBit] 노드 End");
} }
private void OnForceError(SequenceContext arg1, string arg2) private void OnForceError(SequenceContext arg1, string arg2)
{ {
Log.I($"[OnForceError] 노드 Start"); Log.I($"[OnForceError] ForceError 발생 → 장비 정지 처리 시작");
OnSeqActionStop(); OnSeqActionStop();
Log.I("SequenceAction Ok : OnForceError"); Log.I($"[OnForceError] ForceError 정지 처리 완료");
Log.I($"[OnForceError] 노드 End");
} }
private void OnStopped(SequenceContext obj) private void OnStopped(SequenceContext obj)
{ {
Log.I($"[OnStopped] 노드 Start"); Log.I($"[OnStopped] 시퀀스 Stop 수신 → 장비 정지 처리 시작");
OnSeqActionStop(); OnSeqActionStop();
Log.I("SequenceAction Ok : OnStopped"); Log.I($"[OnStopped] Stop 정지 처리 완료");
Log.I($"[OnStopped] 노드 End");
} }
private void OnSeqActionStop() private void OnSeqActionStop()
@ -1100,27 +1037,15 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences
_PLC.WriteBit((int)eBitOutput.PC_WELD_TRIGGER_ACK, false); _PLC.WriteBit((int)eBitOutput.PC_WELD_TRIGGER_ACK, false);
_PLC.WriteBit((int)eBitOutput.PC_WELD_BUSY, false); _PLC.WriteBit((int)eBitOutput.PC_WELD_BUSY, false);
_PLC.WriteBit((int)eBitOutput.PC_WELD_COMPLETE, false); _PLC.WriteBit((int)eBitOutput.PC_WELD_COMPLETE, false);
Log.I("[OnSeqActionStop] PLC ANODE(-) Output All false"); Log.I("[OnSeqActionStop] PLC 음극 Output All false (양극 P 미사용 — 양극 비트 제거)");
_PLC.WriteBit((int)eBitOutput.PC_AUTO_STATUS_P, false);
_PLC.WriteBit((int)eBitOutput.PC_WELD_P_READY_REQ, false);
_PLC.WriteBit((int)eBitOutput.PC_WELD_P_COMP_ACK, false);
_PLC.WriteBit((int)eBitOutput.PC_WELD_P_BUSY, false);
Log.I("[OnSeqActionStop] PLC Cathod(+) Output All false");
_PLC.WriteBit((int)eBitOutput.PC_PAGE_CHANGE_COMPLETE, false); _PLC.WriteBit((int)eBitOutput.PC_PAGE_CHANGE_COMPLETE, false);
Log.I("[OnSeqActionStop] PLC IO Output PAGECHANGE Init"); Log.I("[OnSeqActionStop] PLC IO Output PAGECHANGE Init");
// 260512 워드 // 260512 워드
_PLC.WriteDWord((int)eWordOutput.PC_ERROR_CODE, 0); _PLC.WriteDWord((int)eWordOutput.PC_ERROR_CODE, 0);
// 260621: Resume 정지면 에코 워드(Page/Recipe No) 보존 — PLC가 정합 확인에 쓰므로 0으로 안 지움 // 260621: 에코 워드(PC_PAGE_NO/PC_RECIPE_NO)는 정지 시 0으로 지우지 않음 — PLC가 정합 확인에 쓰는
if (!ResumeMode) // 마지막 핸드셰이크 값을 보존(다음 사이클에 어차피 재에코). 기존 0 초기화 2줄 제거.
{
_PLC.WriteDWord((int)eWordOutput.PC_PAGE_NO, 0);
_PLC.WriteDWord((int)eWordOutput.PC_RECIPE_NO, 0); // 260618 레시피 No init
}
Log.I("[OnSeqActionStop] PLC IO Output Word Init"); Log.I("[OnSeqActionStop] PLC IO Output Word Init");

View File

@ -233,10 +233,24 @@ namespace ILSOS_PrismaticRD_PTOP.Views
string msg = $"Recipe No : {recipeNo}\nPage No : {pageNo}\n\n용접 위치 확인해주세요."; string msg = $"Recipe No : {recipeNo}\nPage No : {pageNo}\n\n용접 위치 확인해주세요.";
Aurora.Api.Control.MessageDialog.Show(Application.Current.MainWindow, "용접 위치 확인", msg); Aurora.Api.Control.MessageDialog.Show(Application.Current.MainWindow, "용접 위치 확인", msg);
// [확인] 후 Auto → Manual (레시피는 이미 활성 → 재활성/롤백 안 함). 작업자가 가변 용접 직접 진행. // [확인] 후 Auto → Manual. 작업자가 가변 용접 직접 진행.
Status.SetStatus(eEqpStatus.Manual); Status.SetStatus(eEqpStatus.Manual);
_Plc.WriteBit(eBitOutput.PC_AUTO_STATUS, false); _Plc.WriteBit(eBitOutput.PC_AUTO_STATUS, false);
_Plc.WriteBit(eBitOutput.PC_AUTO_STATUS_P, false);
// PLC 지정 레시피를 활성화 + 티칭/캔버스 로드 → Manual에서 해당 Recipe/Page로 이동·수동용접 가능.
// (미등록/0이면 현재 활성 레시피로 폴백. OnMonitoring=UI 스레드라 OnActiveReloaded 직접 호출 OK)
var recipe = _RecipeManager.Recipes().FirstOrDefault(x => x.Id == recipeNo)
?? _RecipeManager.ActivatedRecipe();
if (recipe != null)
{
_RecipeManager.ActivateRecipe(recipe.Id, "Bit15 Manual Weld");
RecipeHelper.OnActiveReloaded();
Log.I($"[Bit15] Recipe 활성화+로드 (Id:{recipe.Id}) → Manual 이동 가능");
}
else
{
Log.W($"[Bit15] 활성화할 Recipe 없음 (PLC RecipeNo:{recipeNo}, 활성 레시피도 null)");
}
Log.I($"[Bit15] ModeChange → 정지(보존)·Manual 전환 (Recipe:{recipeNo}, Page:{pageNo})"); Log.I($"[Bit15] ModeChange → 정지(보존)·Manual 전환 (Recipe:{recipeNo}, Page:{pageNo})");
} }
@ -252,11 +266,19 @@ namespace ILSOS_PrismaticRD_PTOP.Views
MessageUtil.ShowWarningToast("Resume 상태가 아닙니다."); MessageUtil.ShowWarningToast("Resume 상태가 아닙니다.");
return; return;
} }
// PC→PLC bit15(Weld_01 ReadyACK) ON — resume run 신호 (한 사이클 완료 후 시퀀스가 OFF/해제) // PC→PLC bit15(Weld_01 ReadyACK) ON — resume run 신호. 1초 펄스 후 자동 OFF(아래 타이머).
_Plc.WriteBit(eBitOutput.PC_WELD_READY_ACK, true); _Plc.WriteBit(eBitOutput.PC_WELD_READY_ACK, true);
// 1초 후 PC bit15 OFF (one-shot DispatcherTimer)
var bit15OffTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };
bit15OffTimer.Tick += (s, ev) =>
{
bit15OffTimer.Stop();
_Plc.WriteBit(eBitOutput.PC_WELD_READY_ACK, false);
Log.I("[Bit15] PC_WELD_READY_ACK OFF (1초 펄스)");
};
bit15OffTimer.Start();
// Auto 복귀 (ResumeMode=true → CheckInitCondition에서 핸드셰이크 스킵하고 업로드→용접) // Auto 복귀 (ResumeMode=true → CheckInitCondition에서 핸드셰이크 스킵하고 업로드→용접)
_Plc.WriteBit(eBitOutput.PC_AUTO_STATUS, true); _Plc.WriteBit(eBitOutput.PC_AUTO_STATUS, true);
_Plc.WriteBit(eBitOutput.PC_AUTO_STATUS_P, true);
Status.GetProcessingWeldingData().AutoRunStartTime = DateTime.Now; Status.GetProcessingWeldingData().AutoRunStartTime = DateTime.Now;
RunSequenceAutoRun(); RunSequenceAutoRun();
Log.I("[Bit15] AutoRun Resume → PC_WELD_READY_ACK ON + Auto 이어하기(핸드셰이크 스킵)"); Log.I("[Bit15] AutoRun Resume → PC_WELD_READY_ACK ON + Auto 이어하기(핸드셰이크 스킵)");
@ -265,6 +287,15 @@ namespace ILSOS_PrismaticRD_PTOP.Views
// PLC 비트에 의한 AutoRun 실행의 경우, PLC 인터페이스 이후 AutoRun 필요 // PLC 비트에 의한 AutoRun 실행의 경우, PLC 인터페이스 이후 AutoRun 필요
public override async void OnAutoRunStartRequest(object obj = null) public override async void OnAutoRunStartRequest(object obj = null)
{ {
// PLC bit15(MODECHANGE)가 ON인 동안에는 PLC_AUTO_REQ 자동시작 신호를 무시.
// (bit15 Manual 전환 중 AUTO_REQ rising edge가 AutoRun을 띄워 Manual을 덮어쓰는 현상 차단.
// 이어하기는 전용 [AutoRun Resume] 버튼(OnAutoRunResume) 경로라 이 가드와 무관.)
if (_Plc != null && !_Plc.IsSimul && _Plc.ReadBit(eBitInput.PLC_WELD_READY_REQ).data)
{
Log.I("[Bit15] bit15 ON 중 → AUTO_REQ 자동시작 무시 (Manual 유지)");
return;
}
if (!ApproveAutorunByUserOrAuto()) { return; } if (!ApproveAutorunByUserOrAuto()) { return; }
SeqAutoRunPTOP.ResumeMode = false; // 일반 시작은 항상 핸드셰이크 포함 (resume 아님) SeqAutoRunPTOP.ResumeMode = false; // 일반 시작은 항상 핸드셰이크 포함 (resume 아님)
@ -275,13 +306,11 @@ namespace ILSOS_PrismaticRD_PTOP.Views
if (!IsStart) if (!IsStart)
{ {
_Plc.WriteBit(eBitOutput.PC_AUTO_STATUS, false); _Plc.WriteBit(eBitOutput.PC_AUTO_STATUS, false);
_Plc.WriteBit(eBitOutput.PC_AUTO_STATUS_P, false); //양극
return; return;
} }
#endregion #endregion
_Plc.WriteBit(eBitOutput.PC_AUTO_STATUS, true); // 음극 _Plc.WriteBit(eBitOutput.PC_AUTO_STATUS, true); // 음극 (양극 P 미사용)
_Plc.WriteBit(eBitOutput.PC_AUTO_STATUS_P, true); //양극
// PLC에선 Auto Req 받으면 바로 살려주도록 // PLC에선 Auto Req 받으면 바로 살려주도록
@ -408,7 +437,6 @@ namespace ILSOS_PrismaticRD_PTOP.Views
Status.SetStatus(eEqpStatus.Manual); Status.SetStatus(eEqpStatus.Manual);
_Plc.WriteBit(eBitOutput.PC_AUTO_STATUS, false); _Plc.WriteBit(eBitOutput.PC_AUTO_STATUS, false);
_Plc.WriteBit(eBitOutput.PC_AUTO_STATUS_P, false);
//Recipe를 기존에 선택된 Recipe로 Rollback 해 줌 //Recipe를 기존에 선택된 Recipe로 Rollback 해 줌
RecipeHelper.RollbackRecipe(); RecipeHelper.RollbackRecipe();

View File

@ -230,8 +230,7 @@ namespace WeldingCommon.Devices
PLC_PAGE_CHANGE_REQ = 0x08, PLC_PAGE_CHANGE_REQ = 0x08,
PLC_RMS_USED = 0x09, PLC_RMS_USED = 0x09,
// 0x0A ~ 0x0E // 0x0A ~ 0x0F
PLC_WELD_READY_REQ = 0x0F, // 260621: Weld_01 Ready (PTOP 동기화)
PLC_WELD_TRIGGER_REQ = 0x10, PLC_WELD_TRIGGER_REQ = 0x10,
PLC_WELD_WAIT = 0x11, PLC_WELD_WAIT = 0x11,
PLC_WELD_COMPLETE_ACK = 0x12, PLC_WELD_COMPLETE_ACK = 0x12,
@ -267,8 +266,7 @@ namespace WeldingCommon.Devices
PC_EMO_STATUS = 0x07, PC_EMO_STATUS = 0x07,
PC_PAGE_CHANGE_COMPLETE = 0x08, PC_PAGE_CHANGE_COMPLETE = 0x08,
PC_RECIPE_CHANGE_COMPLETE = 0x09, // 260618: PTOP enum과 동기화 (RestoreDeviceStatus가 common enum 사용) PC_RECIPE_CHANGE_COMPLETE = 0x09, // 260618: PTOP enum과 동기화 (RestoreDeviceStatus가 common enum 사용)
// 0x0A ~ 0x0E // 0x0A ~ 0x0F
PC_WELD_READY_ACK = 0x0F, // 260621: Weld_01 ReadyACK (PTOP 동기화)
PC_WELD_TRIGGER_ACK = 0x10, PC_WELD_TRIGGER_ACK = 0x10,
PC_WELD_BUSY = 0x11, PC_WELD_BUSY = 0x11,
PC_WELD_COMPLETE = 0x12, PC_WELD_COMPLETE = 0x12,

View File

@ -59,3 +59,58 @@
- 21~24 페이지 수 미확정(코드는 `Layers.Count()` 가변 대응). - 21~24 페이지 수 미확정(코드는 `Layers.Count()` 가변 대응).
- 알람 타이밍: `device.Activate()`가 연결완료까지 동기인지/개시만인지 디바이스별 확인. - 알람 타이밍: `device.Activate()`가 연결완료까지 동기인지/개시만인지 디바이스별 확인.
- 제품 1개 = Recipe×Page 사이클 예상 시나리오: `docs/분석/2026/06/21/121750_제품1개_recipe_page_사이클_예상시나리오.md`. - 제품 1개 = Recipe×Page 사이클 예상 시나리오: `docs/분석/2026/06/21/121750_제품1개_recipe_page_사이클_예상시나리오.md`.
## 8. 현장 피드백 반영 SW 수정 (5건, 빌드 0오류·미커밋)
> 모두 MSBuild Debug|x64 EXITCODE=0 확인. 현장 런타임 검증 대기.
1. **D70002(PC_RECIPE_NO) 에코 누락 수정** — `SeqAutoRunPTOP.cs` `CheckRecipeNoComp`
- 증상: Page CHG Request 수신·레시피는 그대로인데 `PC_RECIPE_NO`(D70002)가 안 바뀜.
- 원인: "이미 활성 레시피" 분기가 에코 없이 조기 return → 정지 시 0으로 지워진 값이 그대로 남음(PageNo는 매번 에코라 비대칭).
- 조치: 동일 레시피여도 `WriteWord(PC_RECIPE_NO, plcRecipeNo)` 에코 유지(재활성/reload는 계속 생략).
2. **bit15 Manual 전환 시 레시피 활성화+로드 추가** — `UCMainAutoRunViewModel.cs` `ShowRecipePageConfirmAndSwitchManual`
- 증상: bit15 팝업 후 Manual로 가도 레시피 비활성화되어 해당 Recipe/Page로 이동·수동용접 불가.
- 원인: "레시피 이미 활성" 가정으로 ActivateRecipe/OnActiveReloaded 생략 → 티칭/캔버스(SelectedRecipe)가 PLC 레시피로 로드 안 됨.
- 조치: `ActivateRecipe(recipe.Id)` + `RecipeHelper.OnActiveReloaded()` 추가(미등록/0이면 현재 활성 레시피 폴백).
3. **bit15 ON 중 AUTO_REQ 자동시작 무시** — `UCMainAutoRunViewModel.cs` `OnAutoRunStartRequest`
- 증상: bit15 살아있을 때 Recipe/Page 팝업과 "자동운전 시작?" 팝업이 동시에 뜨고, Manual이 아닌 Auto로 복귀.
- 원인: AutoRun 팝업은 `PLC_AUTO_REQ`(0x02) rising edge로 트리거(bit15와 별개·무가드), TimerMessageBox default=true(5초 자동확인)가 Manual을 덮어씀.
- 조치: `OnAutoRunStartRequest` 진입부에 `PLC_WELD_READY_REQ`(0x0F=bit15) ON이면 즉시 return. (Resume 버튼 경로는 별개라 무관)
4. **정지 시 에코 워드 0 초기화 제거** — `SeqAutoRunPTOP.cs` `OnSeqActionStop`
- `if(!ResumeMode){ PC_PAGE_NO=0; PC_RECIPE_NO=0; }` 블록 제거 → 모든 정지에서 D70001·D70002 보존(다음 사이클 재에코). `PC_ERROR_CODE=0`은 유지.
5. **[AutoRun Resume] PC bit15 1초 펄스 OFF** — `UCMainAutoRunViewModel.cs` `OnAutoRunResume`
- `PC_WELD_READY_ACK`(PC쪽 bit15) ON 직후 one-shot DispatcherTimer로 1초 뒤 자동 false.
- 참고: 시퀀스 `CheckCycleStop`의 동일 OFF는 이제 중복(무해) — 정리는 별도 검토.
### 검증 메모 (bit15 흐름)
- bit15 감지는 `OnMonitoring`이 **실 PLC(비-Simul)** 에서만 동작(Simul 가드) → 벤치 검증 불가, 실 PLC 필요.
- 코드는 입력 **0x0F(`PLC_WELD_READY_REQ`)** 를 봄 → PLC의 "15번 bit" 실주소가 0x0F(M70515)인지 디바이스맵 대조 필요(안 먹히면 1순위 원인).
- 확인 로그: `[Bit15] ModeChange…` / `[Bit15] Recipe 활성화+로드…` / `[Bit15] bit15 ON 중 → AUTO_REQ 자동시작 무시` / `[Bit15] PC_WELD_READY_ACK OFF (1초 펄스)`.
## 9. 양극(P) 미사용 정리 + eCommon READY 제거 + 로그 개편 (빌드 0오류·미커밋)
### 9-1. 공유 enum eCommon READY(0x0F) 제거 — `WeldingCommon/Devices/PLC/PLCController.Address.cs`
- `eCommonBitInput.PLC_WELD_READY_REQ` / `eCommonBitOutput.PC_WELD_READY_ACK` (0x0F) **제거**(`// 0x0A~0x0F` 예약범위로). 공유 enum의 READY는 미사용 항목(실제 bit15는 PTOP enum 0x0F 사용)이라 제거 안전. `PC_WELD_TRIGGER_ACK`(0x10)는 유지.
- **PTOP enum의 READY(0x0F)는 유지** → bit15/Resume 코드 영향 없음.
### 9-2. 양극(P) PC→PLC 출력 전체 제거 (현장: 현재 장비 P 미사용)
- 증상: `Out of Range: 0x0065 (WriteBit)` — 0x65=`PC_AUTO_STATUS_P`(양극 Auto, M70601)가 출력맵에 없는데 코드가 씀.
- **PC_AUTO_STATUS_P(0x65) 쓰기 8곳 제거** — `SeqAutoRunPTOP`(Sequence_Enter/InitAutoRunBit/OnSeqActionStop) + `UCMainAutoRunViewModel`(bit15핸들러/OnAutoRunResume/OnAutoRunStartRequest×2/OnStopSequenceAutoRun).
- **양극 용접 핸드셰이크(PC_WELD_P_* 0x74~76) 제거** — `WaitWeldingStart`·`ExecuteWelding`의 `plcPageNo==2` 양극 분기 삭제 → **모든 페이지 음극 비트(0x10~12)로 통일**(용접 트리거가 음극 `PLC_WELD_TRIGGER_REQ`라 정합). `RestoreDeviceStatus`·`InitAutoRunBit` 양극 리셋도 제거.
- 검증: 두 파일에 `PC_AUTO_STATUS_P`/`PC_WELD_P_` 0건.
- ⚠️ PLC 확인: 이제 page2 용접 완료도 음극 COMPLETE(0x12)로 송신 → PLC가 전 페이지를 음극 완료로 받는지 정합 확인 필요. (보조 `SeqAutoRunPrismatic`의 양극 비트는 미변경.)
### 9-3. SeqAutoRunPTOP 로그 개편 (로그만 수정, 로직 무변경)
- 제네릭 `[X] 노드 Start/End` 전부 제거 → 각 노드 동작을 설명하는 로그로 교체(InitSequenceVariable/LaserReadyResponse/SaveScannerRecordFileForamt/RestoreDeviceStatus/InitDev/CheckDeviceReady/CheckCondition/CheckCondition_Interlock/InitializeDeviceStatus/InitAutoRunBit/OnForceError/OnStopped). 잘못된 프리픽스(`SaveScannerRecordFileForamt` 내 `[SaveResultData]`) 정정.
- 핵심 동작 노드는 기존 촘촘한 로그 유지 → 로그만으로 시퀀스 어느 구간인지 추적 가능.
## 10. 현장 진행 상황 / 이어서 할 일
- **확인 완료(현장)**: 앞서 수정분으로 해당 구간 **정상 통과** 확인, **Auto↔Manual 모드 전환** 동작 확인.
- **보류**: Recipe No / Page No 관련 **이번 세션 수정 코드는 아직 현장 미적용** → 추후 현장에 적용 후 검증 예정.
- **이어서 할 일(다음)**: 수정 코드(D70002 에코, 정지 시 에코워드 보존, 양극 제거/음극 통일, bit15 흐름, 로그 개편) 현장 반영 → Recipe/Page 핸드셰이크 + 용접 완료신호(음극) PLC 정합 재확인부터 이어감.
- 상태: 이번 세션 변경 4개 파일 + 작업일지 **미커밋**.