feat: M70509 기반 용접 전/후 Scout 추가

M70509(Scout Enable) 상시 ON 시, 용접 트리거(M70516) 감지 후 pre scout,
발진 완료 후 완료비트 송신 전 post scout 수행(각각 SeqScoutNormal 재사용,
0.1초 후 진행). M70509 OFF면 기존과 동일하게 용접만.

- eBitInput 0x09: PLC_RECIPE_CHANGE_REQ(미사용) → PLC_SCOUT_ENABLE 재정의
- 용접 완료비트(ACK off/BUSY off/COMPLETE on)를 WriteWeldComplete 노드로
  분리해 post scout 뒤에 송신
- 이미지 파일명 접미어: {기존}_auto_{recipeNo}_{pageNo}_{pre|post}.PNG

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
to3796zw 2026-06-22 16:04:12 +09:00
parent 15e5f5ba27
commit 4f03a0a7ce
4 changed files with 157 additions and 23 deletions

View File

@ -8,7 +8,7 @@ M70505,Heavy_ALStatus,Heavy_ALStatus
M70506,Door Interlok,Door Interlok
M70507,EMO Status,EMO Status
M70508,Page_CHGRequest,Page_CHGRequest
M70509,Recipe_CHGRequest,Recipe_CHGRequest
M70509,Scout Enable,Scout Enable(상시 ON 시 용접 전/후 Scout)
M70510,,
M70511,,
M70512,,

1 Device No Comment ENG Comment KOR
8 M70506 Door Interlok Door Interlok
9 M70507 EMO Status EMO Status
10 M70508 Page_CHGRequest Page_CHGRequest
11 M70509 Recipe_CHGRequest Scout Enable Recipe_CHGRequest Scout Enable(상시 ON 시 용접 전/후 Scout)
12 M70510
13 M70511
14 M70512

View File

@ -22,8 +22,9 @@ namespace ILSOS_PrismaticRD_PTOP.Device
PLC_EMO_STATUS = 0x07,
PLC_PAGE_CHANGE_REQ = 0x08,
// 260618: M70509 Recipe 변경 요청 (구 PLC_RMS_USED, RMS 미사용으로 폐기 후 재정의)
PLC_RECIPE_CHANGE_REQ = 0x09,
// 260622: M70509 = Scout Enable 플래그 (PLC가 상시 ON 유지 시 용접 전/후 Scout 수행)
// 구 PLC_RECIPE_CHANGE_REQ/RMS_USED 모두 미사용 → 재정의. Recipe 전환은 PAGE_CHANGE_REQ + RECIPE_NO 워드로 처리.
PLC_SCOUT_ENABLE = 0x09,
// 0x0A ~ 0x0E
// 260621: M70515 확정 = Weld_01 Ready (가변용접 시 PLC ON → Recipe/Page 확인 팝업 + Resume 트리거)
PLC_WELD_READY_REQ = 0x0F,

View File

@ -76,6 +76,23 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences
const string SEQ_WRITE_COMPLETION_PLC_BIT = "[SeqAutoRunPTOP] Write Completion PLC Bit";
const string SEQ_COMPLETE = "[SeqAutoRunPTOP] SEQ_COMPLETE"; // 완료
#region 260622: M70509(Scout Enable) 기반 용접 전/후 Scout
// 용접 전: M70516(Weld Trigger) 감지 후 M70509 ON이면 scout(pre) → 0.1s → 용접
const string SEQ_PRE_SCOUT_CHECK = "[SeqAutoRunPTOP] Pre-Weld Scout Check"; // 분기: M70509 ON/OFF
const string SEQ_PRE_SCOUT_SET_PARAMS = "[SeqAutoRunPTOP] Pre-Weld Scout SetParams";
const string SEQ_PRE_SCOUT_RUN = "[SeqAutoRunPTOP] Pre-Weld Scout Run"; // SeqScoutNormal 서브시퀀스
const string SEQ_PRE_SCOUT_DONE = "[SeqAutoRunPTOP] Pre-Weld Scout Done"; // 0.1s 후 진행
// 용접 후: 발진 완료 후 완료비트 송신 전 M70509 ON이면 scout(post) → 0.1s → 완료비트
const string SEQ_POST_SCOUT_CHECK = "[SeqAutoRunPTOP] Post-Weld Scout Check";
const string SEQ_POST_SCOUT_SET_PARAMS = "[SeqAutoRunPTOP] Post-Weld Scout SetParams";
const string SEQ_POST_SCOUT_RUN = "[SeqAutoRunPTOP] Post-Weld Scout Run";
const string SEQ_POST_SCOUT_DONE = "[SeqAutoRunPTOP] Post-Weld Scout Done"; // 0.1s 후 진행
// 용접 완료 비트 송신(post-scout 분기 뒤로 분리): TriggerACK off / BUSY off / COMPLETE on
const string SEQ_WRITE_WELD_COMPLETE = "[SeqAutoRunPTOP] Write Weld Complete";
private const int SCOUT_PROCEED_DELAY_MS = 100; // scout 완료 후 진행 지연(0.1초)
#endregion
public enum ePrismaticDOAddress
{
CoreLaser_Control = 9,
@ -140,8 +157,23 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences
// 260513 plc
sb.AddAction(SEQ_CHECK_WELDING_START_READ_BIT, WeldingStartReadBit);
// 260622: 용접 전 Scout (M70509 ON 시)
sb.AddDecision(SEQ_PRE_SCOUT_CHECK, PreScoutCheck);
sb.AddAction(SEQ_PRE_SCOUT_SET_PARAMS, PreScoutSetParams);
sb.AddSubsequence(SEQ_PRE_SCOUT_RUN, SequenceManager.GetSeqBuilder_SeqScoutNormal());
sb.AddAction(SEQ_PRE_SCOUT_DONE, PreScoutDone);
sb.AddAction(SEQ_WAIT_WELDING_START, WaitWeldingStart);
sb.AddAction(SEQ_EXECUTE_WELDING, ExecuteWelding);
// 260622: 용접 후 Scout (M70509 ON 시) → 완료비트 송신
sb.AddDecision(SEQ_POST_SCOUT_CHECK, PostScoutCheck);
sb.AddAction(SEQ_POST_SCOUT_SET_PARAMS, PostScoutSetParams);
sb.AddSubsequence(SEQ_POST_SCOUT_RUN, SequenceManager.GetSeqBuilder_SeqScoutNormal());
sb.AddAction(SEQ_POST_SCOUT_DONE, PostScoutDone);
sb.AddAction(SEQ_WRITE_WELD_COMPLETE, WriteWeldComplete);
sb.AddDecision(SEQ_CHECK_CYCLE_STOP, CheckCycleStop); // 체크페이지
sb.AddAction(SEQ_SAVE_RESULT_DATA, SaveResultData); // 해당 공정의 정보를 기록 함.
@ -161,9 +193,22 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences
> sb[SEQ_DO_ID_UPLOAD_WELDING_LIST_SEQ] // 2 리스트 업로드 (서브시퀀스) - TeachingRun 노드 참고
> sb[SEQ_GET_RESULT_UPLOAD_WELDING_LIST_SEQ] // 3 업로드 결과 확인 - TeachingRun 노드 참고
#endregion
> sb[SEQ_CHECK_WELDING_START_READ_BIT] // while read bit
> sb[SEQ_CHECK_WELDING_START_READ_BIT] // while read bit (M70516 대기)
> sb[SEQ_PRE_SCOUT_CHECK] // 260622: M70509 ON → pre scout
> sb[SEQ_PRE_SCOUT_SET_PARAMS]
> sb[SEQ_PRE_SCOUT_RUN]
> sb[SEQ_PRE_SCOUT_DONE] // 0.1s 후 용접
> sb[SEQ_WAIT_WELDING_START] // 4 업로드 완료 PLC 완료 신호 및 BUSY 신호로 변경
> sb[SEQ_EXECUTE_WELDING] // 5 Scanner 실행 (실제 용접)
> sb[SEQ_EXECUTE_WELDING] // 5 Scanner 실행 (실제 용접, 완료비트는 보류)
> sb[SEQ_POST_SCOUT_CHECK] // 260622: M70509 ON → post scout
> sb[SEQ_POST_SCOUT_SET_PARAMS]
> sb[SEQ_POST_SCOUT_RUN]
> sb[SEQ_POST_SCOUT_DONE] // 0.1s 후 완료비트
> sb[SEQ_WRITE_WELD_COMPLETE] // 용접 완료 비트 송신 → 다음 시퀀스
> sb[SEQ_CHECK_CYCLE_STOP]
> sb[SEQ_SAVE_RESULT_DATA]
@ -171,6 +216,10 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences
> sb[SEQ_COMPLETE] // 다시 enter로 올리기
> sb.Final);
// 260622: Scout 스킵 분기 (M70509 OFF → 기존과 동일하게 용접만)
sb.Flows(sb[SEQ_PRE_SCOUT_CHECK] > sb[SEQ_WAIT_WELDING_START]);
sb.Flows(sb[SEQ_POST_SCOUT_CHECK] > sb[SEQ_WRITE_WELD_COMPLETE]);
sb.Flows(sb[SEQ_CHECK_CYCLE_STOP] > sb[SEQ_CHECK_PAGENO]); // 루프백: 매 사이클 Page Change bit(0x08) 대기부터 재확인
sb.Flows(sb[SEQ_CHECK_INIT_CONDITION] > sb[SEQ_SET_PARAMS_UPLOAD_WELDING_LIST_SEQ]); // 260621: Resume 시 핸드셰이크 스킵
sb.Flows(sb[SEQ_CHECK_INIT_CONDITION] > sb[SEQ_COMPLETE] > sb.Final);
@ -787,21 +836,10 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences
Log.D(string.Format($"[ExecuteWelding] Change SelectCorrectionTable Time : {_WeldingSpanTime.TotalMilliseconds}"));
Thread.Sleep(500);
// 양극(P) 미사용 → 모든 페이지 음극 완료 비트로 응답
_PLC.WriteBit((int)eBitOutput.PC_WELD_TRIGGER_ACK, false);
Log.I($"[ExecuteWelding] 용접완료 송신 - READY_ACK=false (PageNo:{plcPageNo})");
Thread.Sleep(50);
_PLC.WriteBit((int)eBitOutput.PC_WELD_BUSY, false);
Log.I($"[ExecuteWelding] 용접완료 송신 - BUSY=false (PageNo:{plcPageNo})");
Thread.Sleep(50);
_PLC.WriteBit((int)eBitOutput.PC_WELD_COMPLETE, true);
Log.I($"[ExecuteWelding] 용접완료 송신 - COMPLETE=true (PageNo:{plcPageNo})");
Thread.Sleep(5);
Status.SetWorkingPosition(eWorkingPosition.UNLOADING_CELL);
#endregion
Log.I($"[ExecuteWelding] 완료 → CheckCycleStop 이동");
// 260622: 완료비트(TriggerACK off/BUSY off/COMPLETE on) 송신은 post-scout(M70509) 분기 뒤로 분리 → WriteWeldComplete 노드
Log.I($"[ExecuteWelding] 발진 완료 (완료비트 송신 보류) → PostScoutCheck 이동");
return SequenceAction.Ok();
}
@ -822,6 +860,91 @@ namespace ILSOS_PrismaticRD_PTOP.Sequences
return SEQ_CHECK_PAGENO;
}
#region 260622: M70509(Scout Enable) 기반 용접 전/후 Scout
// 이미지 파일명 접미어: auto_{recipeNo}_{pageNo}_{pre|post} → 최종 {기존}_auto_{recipeNo}_{pageNo}_{phase}.PNG
private string ScoutSaveTag(string phase) => $"auto_{plcRecipeNo}_{plcPageNo}_{phase}";
private SeqScoutNormal_InitParam BuildScoutParams(string saveTag)
{
return new SeqScoutNormal_InitParam()
{
IsRealTimeScout = false,
ScoutSeqParams = new ScoutSeqParams(VisionRecipeManager.GetFovInfos()[0], eDisplayType.Machine_B),
PageIndex = _CurrentPageNo, // 현재 처리 중인 페이지(0-base)
SaveTag = saveTag,
};
}
// ── 용접 전 Scout (M70516 감지 후) ──────────────────────────
private string PreScoutCheck(SequenceContext context, string id, ICollection<string> branches, SequenceActionResult param)
{
bool scout = _PLC.ReadBit(eBitInput.PLC_SCOUT_ENABLE).data; // M70509
Log.I($"[PreScoutCheck] M70509(Scout Enable) = {scout}");
// M70509 ON → pre scout / OFF → 기존과 동일하게 바로 용접
return scout ? SEQ_PRE_SCOUT_SET_PARAMS : SEQ_WAIT_WELDING_START;
}
private SequenceActionResult PreScoutSetParams(SequenceContext context, string id, SequenceActionResult param)
{
Log.I("[PreScout] Scout 파라미터 구성(pre)");
return SequenceAction.Ok(BuildScoutParams(ScoutSaveTag("pre")));
}
private SequenceActionResult PreScoutDone(SequenceContext context, string id, SequenceActionResult param)
{
var result = param.Param as SeqScoutNormal_OutParam;
if (result == null || result.Success == false)
{
Log.E("[PreScout] Scout 실패 → 시퀀스 중단");
return null;
}
Thread.Sleep(SCOUT_PROCEED_DELAY_MS); // 0.1초 후 용접 진행
Log.I($"[PreScout] 완료 → {SCOUT_PROCEED_DELAY_MS}ms 후 WaitWeldingStart 이동");
return SequenceAction.Ok();
}
// ── 용접 후 Scout (완료비트 송신 전) ────────────────────────
private string PostScoutCheck(SequenceContext context, string id, ICollection<string> branches, SequenceActionResult param)
{
bool scout = _PLC.ReadBit(eBitInput.PLC_SCOUT_ENABLE).data; // M70509
Log.I($"[PostScoutCheck] M70509(Scout Enable) = {scout}");
// M70509 ON → post scout / OFF → 바로 완료비트 송신
return scout ? SEQ_POST_SCOUT_SET_PARAMS : SEQ_WRITE_WELD_COMPLETE;
}
private SequenceActionResult PostScoutSetParams(SequenceContext context, string id, SequenceActionResult param)
{
Log.I("[PostScout] Scout 파라미터 구성(post)");
return SequenceAction.Ok(BuildScoutParams(ScoutSaveTag("post")));
}
private SequenceActionResult PostScoutDone(SequenceContext context, string id, SequenceActionResult param)
{
var result = param.Param as SeqScoutNormal_OutParam;
if (result == null || result.Success == false)
{
Log.E("[PostScout] Scout 실패 → 시퀀스 중단");
return null;
}
Thread.Sleep(SCOUT_PROCEED_DELAY_MS); // 0.1초 후 완료비트 송신
Log.I($"[PostScout] 완료 → {SCOUT_PROCEED_DELAY_MS}ms 후 WriteWeldComplete 이동");
return SequenceAction.Ok();
}
// 용접 완료 비트 송신(post-scout 분기 뒤로 분리). 음극 비트로 응답(양극 P 미사용).
private SequenceActionResult WriteWeldComplete(SequenceContext context, string id, SequenceActionResult param)
{
_PLC.WriteBit((int)eBitOutput.PC_WELD_TRIGGER_ACK, false);
Log.I($"[WriteWeldComplete] 용접완료 송신 - READY_ACK=false (PageNo:{plcPageNo})");
Thread.Sleep(50);
_PLC.WriteBit((int)eBitOutput.PC_WELD_BUSY, false);
Log.I($"[WriteWeldComplete] 용접완료 송신 - BUSY=false (PageNo:{plcPageNo})");
Thread.Sleep(50);
_PLC.WriteBit((int)eBitOutput.PC_WELD_COMPLETE, true);
Log.I($"[WriteWeldComplete] 용접완료 송신 - COMPLETE=true (PageNo:{plcPageNo})");
Thread.Sleep(5);
Status.SetWorkingPosition(eWorkingPosition.UNLOADING_CELL);
Log.I($"[WriteWeldComplete] 완료 → CheckCycleStop 이동");
return SequenceAction.Ok();
}
#endregion
private SequenceActionResult SaveResultData(SequenceContext context, string id, SequenceActionResult param)
{
Log.I($"[SaveResultData] 용접 결과 데이터 저장 시작");

View File

@ -296,8 +296,14 @@ namespace WeldingCommon.Sequences
//4.스캐너 필드 이미지 파일 저장
// TODO: Path 설정하는 것 따로 만들어서 넣기. 여기저기 흩어져 있음...
// 디렉터리는 기존 그대로(날짜 폴더). SaveTag 가 있으면 기존 파일명 뒤에 '_접미어' 추가.
// 기존: {_ScoutNo+1}_{HHmmss}.PNG
// SaveTag 적용: {_ScoutNo+1}_{HHmmss}_{SaveTag}.PNG (예: 1_143052_auto_11_2_pre.PNG)
string dateDir = $"{DateTime.Now.Year}_{DateTime.Now.Month}_{DateTime.Now.Day}";
string baseName = $"{_ScoutNo + 1}_{DateTime.Now.ToString("HHmmss")}";
string fileName = string.IsNullOrEmpty(_InitParam.SaveTag) ? $"{baseName}.PNG" : $"{baseName}_{_InitParam.SaveTag}.PNG";
string fieldScanImageFile = Path.Combine(ConfigurationManager.AppSettings.Get("DATA_PATH"), "_vision", "FieldScanImage",
string.Format($"{DateTime.Now.Year}_{DateTime.Now.Month}_{DateTime.Now.Day}/{_ScoutNo + 1}_{DateTime.Now.ToString("HHmmss")}.PNG"));
dateDir, fileName);
_SystemParameter.ScannerFieldImageFile = fieldScanImageFile;
//5.압축하여 저장
@ -385,6 +391,10 @@ namespace WeldingCommon.Sequences
// 기존: SeqScoutNormal 이 MainPage.selectedIndexNo(GUI 전역 상태)에만 의존 → 페이지 이동/자동운전 시 stale.
// 변경: 호출부가 "현재 처리/표시 중인 페이지 번호"를 명시 주입.
public int PageIndex { get; set; } = 0;
// [추가 2026-06-22] 파일명 접미어. 디렉터리는 기존 날짜 폴더 그대로, 비면(기본) 기존 파일명 유지(하위호환).
// 적용 시: {기존파일명}_{SaveTag}.PNG (호출부에서 예: "auto_{recipeNo}_{pageNo}_pre" 전달)
public string SaveTag { get; set; } = "";
}
public class SeqScoutNormal_OutParam : OutParam