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)
{
Log.I($"[InitSequenceVariable] 노드 Start");
Log.I($"[InitSequenceVariable] 캔버스 핸들 확보 + 용접대상 오브젝트 선택 시작");
var mainPage = CommonHelper.GetMainPage();
_ScannerCanvasUserCtrl = mainPage.ScannerCanvasCtrlTeaching;
@ -204,7 +204,7 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences
//Status.GetProcessingWeldingData().WeldingScannerListNo = _WeldingScannerListNo;
//Status.GetProcessingWeldingData().SelectedDrawObjectList = _SelectedObjectList;
Log.I($"[InitSequenceVariable] 노드 End");
Log.I($"[InitSequenceVariable] 변수 초기화 완료 (선택 오브젝트 {_SelectedObjectList.Count()}개)");
return true;
}
@ -250,8 +250,7 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences
// 장비 체크
InitDev(context);
_PLC.WriteBit(eBitOutput.PC_AUTO_STATUS, true); // 음극
_PLC.WriteBit(eBitOutput.PC_AUTO_STATUS_P, true); //양극
_PLC.WriteBit(eBitOutput.PC_AUTO_STATUS, true); // 음극 (양극 P 미사용)
#region [TEST_BYPASS] Alarm Check - DeFvice Ready
/*
if (!CheckDeviceReady(context))
@ -321,11 +320,13 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences
return SequenceAction.Ok();
}
// 이미 활성 레시피면 재활성화·워드 재기록 생략 (매 페이지마다 PC_RECIPE_NO 워드 변경 금지)
// 이미 활성 레시피면 재활성화·Model reload는 생략하되, PC_RECIPE_NO 에코는 반드시 유지.
// (정지 시 OnSeqActionStop이 D70002=0으로 지우므로, 동일 레시피 재진입에서 에코를 건너뛰면 PLC가 0/이전값을 봄)
var preActivated = _RecipeManager.ActivatedRecipe();
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();
}
@ -515,9 +516,8 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences
}
private bool LaserReadyResponse()
{
Log.I($"[LaserReadyResponse] 노드 Start");
Log.I($"[LaserReadyResponse] 레이저 Ready 응답 (현재 no-op)");
//if (ApplicationExt.IsSimulation)
Log.I($"[LaserReadyResponse] 노드 End");
return true;
}
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})");
Status.SetWorkingPosition(eWorkingPosition.WAITING_WELD_START);
if (plcPageNo == 1)
{
// 2. 준비 완료(Ack) 및 가동 중(Busy) 비트 ON
_PLC.WriteBit((int)eBitOutput.PC_WELD_TRIGGER_ACK, true);
_PLC.WriteBit((int)eBitOutput.PC_WELD_BUSY, true);
Thread.Sleep(200);
_PLC.WriteBit((int)eBitOutput.PC_WELD_COMPLETE, false);
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..");
}
// 양극(P) 미사용 → 모든 페이지 음극 용접 비트로 응답 (PLC 트리거가 음극 PLC_WELD_TRIGGER_REQ라 정합)
_PLC.WriteBit((int)eBitOutput.PC_WELD_TRIGGER_ACK, true);
_PLC.WriteBit((int)eBitOutput.PC_WELD_BUSY, true);
Thread.Sleep(200);
_PLC.WriteBit((int)eBitOutput.PC_WELD_COMPLETE, false);
Thread.Sleep(5);
Log.I($"[WaitWeldingStart] Welding READY_ACK=ON / BUSY=ON / COMPLETE=OFF 송신 (PageNo:{plcPageNo})");
// 디버그 끝내고 삭제
//MessageUtil.ShowToast("WaitWeldingStart -> WriteBit True");
@ -789,38 +764,16 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences
Log.D(string.Format($"[ExecuteWelding] Change SelectCorrectionTable Time : {_WeldingSpanTime.TotalMilliseconds}"));
if (plcPageNo == 1)
{
// 용접끝
_PLC.WriteBit((int)eBitOutput.PC_WELD_TRIGGER_ACK, false);
Log.I("[PLCInterface] PageNo[1] READY_ACK false");
Thread.Sleep(5);
// 용접중 신호로 변경
_PLC.WriteBit((int)eBitOutput.PC_WELD_BUSY, false);
Log.I("[PLCInterface] PageNo[1] BUSY false");
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
{
}
// 양극(P) 미사용 → 모든 페이지 음극 완료 비트로 응답
_PLC.WriteBit((int)eBitOutput.PC_WELD_TRIGGER_ACK, false);
Log.I($"[ExecuteWelding] 용접완료 송신 - READY_ACK=false (PageNo:{plcPageNo})");
Thread.Sleep(5);
_PLC.WriteBit((int)eBitOutput.PC_WELD_BUSY, false);
Log.I($"[ExecuteWelding] 용접완료 송신 - BUSY=false (PageNo:{plcPageNo})");
Thread.Sleep(5);
_PLC.WriteBit((int)eBitOutput.PC_WELD_COMPLETE, true);
Log.I($"[ExecuteWelding] 용접완료 송신 - COMPLETE=true (PageNo:{plcPageNo})");
Thread.Sleep(5);
Status.SetWorkingPosition(eWorkingPosition.UNLOADING_CELL);
#endregion
@ -867,17 +820,17 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences
// 실제 동작 _Scanner에서
private void SaveScannerRecordFileForamt()
{
Log.I($"[SaveScannerRecordFileForamt] 노드 Start");
Log.I($"[SaveScannerRecordFileForamt] 스캐너 파형 기록 저장 시작 (SaveRecord)");
// ── 용접 완료 후 파형 데이터 저장 ────────────────────────────
_Scanner.SaveRecord("AutoRunTDC");
Log.I("[SaveResultData] SaveRecord Complete");
Log.I("[SaveScannerRecordFileForamt] SaveRecord 완료");
_Scanner.JumpAbs(0, 0, eFunctionType.COMMAND);
_Scanner.SelectCorrectionTable(1);
Log.D("[SaveResultData] JumpAbs + SelectCorrectionTable Complete");
Log.D("[SaveScannerRecordFileForamt] JumpAbs(0,0) + CorrectionTable(1) 복귀 완료");
//Thread.Sleep(1000); // 추후 제거
Log.I($"[SaveScannerRecordFileForamt] 노드 End");
//Thread.Sleep(1000); // 추후 제거
Log.I($"[SaveScannerRecordFileForamt] 스캐너 파형 기록 저장 완료");
}
// 이곳에서 bit 초기화 진행함. 260512
private SequenceActionResult WriteCompletionPLCBit(SequenceContext context, string id, SequenceActionResult param)
@ -934,7 +887,7 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences
private bool RestoreDeviceStatus()
{
Log.I($"[RestoreDeviceStatus] 노드 Start");
Log.I($"[RestoreDeviceStatus] PLC 출력 비트 리셋 시작 (Page/Weld 음극)");
////PLC All BIT OFF
_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
Log.I(string.Format("[PLCInterface]PC Anod Bit Off"));
_PLC.WriteBit((int)eCommonBitOutput.PC_WELD_P_READY_REQ, false); // 용접 준비 false
_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"));
// 양극(P) 미사용 — 양극 용접 비트 리셋 제거
//Thread.Sleep(1000); // 추후 제거
Log.I($"[RestoreDeviceStatus] 노드 End");
//Thread.Sleep(1000); // 추후 제거
Log.I($"[RestoreDeviceStatus] PLC 출력 비트 리셋 완료");
return true;
}
// 장비 3개 가져오기 완료
private void InitDev(SequenceContext context)
{
Log.I($"[InitDev] 노드 Start");
Log.I($"[InitDev] 디바이스 핸들 확보 시작 (Scanner/PLC/IO)");
_Scanner = context.GetLaserScanner();
_PLC = context.GetPLC() as PLCControllerPTOP;
_IO = context.GetIOUnit();
Log.I($"[InitDev] 노드 End");
Log.I($"[InitDev] 디바이스 핸들 확보 완료");
}
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,
CommonDeviceIds.PLCDevice, CommonDeviceIds.IODevice };
bool isAllDeviceActivated = true;
@ -978,38 +928,37 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences
}
Log.I($"[CheckDeviceReady] 노드 End");
Log.I($"[CheckDeviceReady] 디바이스 활성 상태 확인 완료 (AllActive={isAllDeviceActivated})");
return isAllDeviceActivated;
}
private bool CheckCondition(SequenceContext context)
{
Log.I($"[CheckCondition] 노드 Start");
// 체크 신호 plc
Log.I($"[CheckCondition] 시작 조건 확인 (인터락 + 디바이스 초기상태)");
// 체크 신호 plc
if (CheckCondition_Interlock())
{
if (InitializeDeviceStatus(context))
{
Log.I($"[CheckCondition] 노드 End");
Log.I($"[CheckCondition] 조건 충족 → 시작 가능");
return true;
}
}
Log.I($"[CheckCondition] 노드 End");
Log.I($"[CheckCondition] 조건 불충족 → 시작 불가");
return false;
}
private bool CheckCondition_Interlock()
{
Log.I($"[CheckCondition_Interlock] 노드 Start");
// PLC, Data, IO 등 Interlock checking
Log.I($"[CheckCondition_Interlock] 노드 End");
Log.I($"[CheckCondition_Interlock] 인터락 확인 (현재 항상 통과)");
return true;
}
private bool InitializeDeviceStatus(SequenceContext context)
{
Log.I($"[InitializeDeviceStatus] 노드 Start");
Log.I($"[InitializeDeviceStatus] 디바이스 초기상태 설정 시작 (AutoRun 비트 + LBAD Ready)");
// PLC Interface AutoStatus Start
InitAutoRunBit();
@ -1043,15 +992,14 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences
_IO._iOUnit.WriteDoBit(Convert.ToInt32(eADOAddress.PCLBADReadyStatus), true);
Log.I(string.Format($"[IOInterface]SET PCLBADReadyStatus true"));
Log.I($"[InitializeDeviceStatus] 노드 End");
Log.I($"[InitializeDeviceStatus] 디바이스 초기상태 설정 완료");
return true;
}
private void InitAutoRunBit()
{
Log.I($"[InitAutoRunBit] 노드 Start");
Log.I(string.Format($"[PLCInterface] Start AutoRun PLC Bit Initialize"));
Log.I($"[InitAutoRunBit] AutoRun 시작 PLC 비트 초기화 시작 (음극)");
_PLC.WriteBit((int)eBitOutput.PC_WELD_TRIGGER_ACK, false);
Log.I(string.Format($"[PLCInterface]SET (-)PC_WELD_TRIGGER_ACK 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);
Log.I(string.Format($"[PLCInterface]SET (-)PC_AUTO_STATUS True"));
Log.I(string.Format($"[PLCInterface] Start AutoRun PLC Bit Initialize"));
_PLC.WriteBit((int)eBitOutput.PC_WELD_P_READY_REQ, false);
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");
// 양극(P) 미사용 — 양극 초기화 비트 제거
Log.I($"[InitAutoRunBit] AutoRun 시작 PLC 비트 초기화 완료");
}
private void OnForceError(SequenceContext arg1, string arg2)
{
Log.I($"[OnForceError] 노드 Start");
Log.I($"[OnForceError] ForceError 발생 → 장비 정지 처리 시작");
OnSeqActionStop();
Log.I("SequenceAction Ok : OnForceError");
Log.I($"[OnForceError] 노드 End");
Log.I($"[OnForceError] ForceError 정지 처리 완료");
}
private void OnStopped(SequenceContext obj)
{
Log.I($"[OnStopped] 노드 Start");
Log.I($"[OnStopped] 시퀀스 Stop 수신 → 장비 정지 처리 시작");
OnSeqActionStop();
Log.I("SequenceAction Ok : OnStopped");
Log.I($"[OnStopped] 노드 End");
Log.I($"[OnStopped] Stop 정지 처리 완료");
}
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_BUSY, false);
_PLC.WriteBit((int)eBitOutput.PC_WELD_COMPLETE, false);
Log.I("[OnSeqActionStop] PLC ANODE(-) Output All false");
_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");
Log.I("[OnSeqActionStop] PLC 음극 Output All false (양극 P 미사용 — 양극 비트 제거)");
_PLC.WriteBit((int)eBitOutput.PC_PAGE_CHANGE_COMPLETE, false);
Log.I("[OnSeqActionStop] PLC IO Output PAGECHANGE Init");
// 260512 워드
_PLC.WriteDWord((int)eWordOutput.PC_ERROR_CODE, 0);
// 260621: Resume 정지면 에코 워드(Page/Recipe No) 보존 — PLC가 정합 확인에 쓰므로 0으로 안 지움
if (!ResumeMode)
{
_PLC.WriteDWord((int)eWordOutput.PC_PAGE_NO, 0);
_PLC.WriteDWord((int)eWordOutput.PC_RECIPE_NO, 0); // 260618 레시피 No init
}
// 260621: 에코 워드(PC_PAGE_NO/PC_RECIPE_NO)는 정지 시 0으로 지우지 않음 — PLC가 정합 확인에 쓰는
// 마지막 핸드셰이크 값을 보존(다음 사이클에 어차피 재에코). 기존 0 초기화 2줄 제거.
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용접 위치 확인해주세요.";
Aurora.Api.Control.MessageDialog.Show(Application.Current.MainWindow, "용접 위치 확인", msg);
// [확인] 후 Auto → Manual (레시피는 이미 활성 → 재활성/롤백 안 함). 작업자가 가변 용접 직접 진행.
// [확인] 후 Auto → Manual. 작업자가 가변 용접 직접 진행.
Status.SetStatus(eEqpStatus.Manual);
_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})");
}
@ -252,11 +266,19 @@ namespace ILSOS_PrismaticRD_PTOP.Views
MessageUtil.ShowWarningToast("Resume 상태가 아닙니다.");
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);
// 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에서 핸드셰이크 스킵하고 업로드→용접)
_Plc.WriteBit(eBitOutput.PC_AUTO_STATUS, true);
_Plc.WriteBit(eBitOutput.PC_AUTO_STATUS_P, true);
Status.GetProcessingWeldingData().AutoRunStartTime = DateTime.Now;
RunSequenceAutoRun();
Log.I("[Bit15] AutoRun Resume → PC_WELD_READY_ACK ON + Auto 이어하기(핸드셰이크 스킵)");
@ -265,6 +287,15 @@ namespace ILSOS_PrismaticRD_PTOP.Views
// PLC 비트에 의한 AutoRun 실행의 경우, PLC 인터페이스 이후 AutoRun 필요
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; }
SeqAutoRunPTOP.ResumeMode = false; // 일반 시작은 항상 핸드셰이크 포함 (resume 아님)
@ -275,15 +306,13 @@ namespace ILSOS_PrismaticRD_PTOP.Views
if (!IsStart)
{
_Plc.WriteBit(eBitOutput.PC_AUTO_STATUS, false);
_Plc.WriteBit(eBitOutput.PC_AUTO_STATUS_P, false); //양극
return;
}
#endregion
_Plc.WriteBit(eBitOutput.PC_AUTO_STATUS, true); // 음극
_Plc.WriteBit(eBitOutput.PC_AUTO_STATUS_P, true); //양극
_Plc.WriteBit(eBitOutput.PC_AUTO_STATUS, true); // 음극 (양극 P 미사용)
// PLC에선 Auto Req 받으면 바로 살려주도록
// PLC에선 Auto Req 받으면 바로 살려주도록
Status.GetProcessingWeldingData().AutoRunStartTime = DateTime.Now;
RunSequenceAutoRun();
@ -408,7 +437,6 @@ namespace ILSOS_PrismaticRD_PTOP.Views
Status.SetStatus(eEqpStatus.Manual);
_Plc.WriteBit(eBitOutput.PC_AUTO_STATUS, false);
_Plc.WriteBit(eBitOutput.PC_AUTO_STATUS_P, false);
//Recipe를 기존에 선택된 Recipe로 Rollback 해 줌
RecipeHelper.RollbackRecipe();

View File

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

View File

@ -59,3 +59,58 @@
- 21~24 페이지 수 미확정(코드는 `Layers.Count()` 가변 대응).
- 알람 타이밍: `device.Activate()`가 연결완료까지 동기인지/개시만인지 디바이스별 확인.
- 제품 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개 파일 + 작업일지 **미커밋**.