using Aurora; using Aurora.Api; using Aurora.Api.Devices; using Aurora.Api.Manager; using Aurora.View; using AuroraCellTracker; using AuroraIO; using AuroraLaser; using AuroraLaser.Trumpf; using AuroraLaserScanner; using AuroraLBAD; using AuroraLight; using AuroraOCT; using AuroraPLC; using CoreUnit.Control.UserControl.Canvas; using CoreUnit.Scanner.UserControl.ScannerCanvas; using DB; using DB.Recipe; using Hermes; using Hermes.Functional.Option; using ILSOS_PrismaticScanner.Device; using ILSOS_PrismaticScanner.Models; using Matrox.MatroxImagingLibrary; using MilCommon; using MilUI; using OpenCvSharp; using OpenCvSharp.Dnn; using OpenCvSharp.WpfExtensions; using System; using System.CodeDom.Compiler; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Configuration; using System.Data; using System.Diagnostics; using System.Drawing; using System.Globalization; using System.IO; using System.IO.Pipes; using System.Linq; using System.Runtime.InteropServices.ComTypes; using System.Runtime.Remoting.Contexts; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Forms.VisualStyles; using System.Windows.Media.Imaging; using VisionCommon; using WeldingCommon; using WeldingCommon.Alarm; using WeldingCommon.Data; using WeldingCommon.Devices; using WeldingCommon.Devices.ACS; using WeldingCommon.Models; using WeldingCommon.Sequences; using WeldingCommon.Sequences.Main; using WeldingCommon.Settings; using WeldingCommon.Utils; using WeldingCommon.Views; using WeldingCommon.Vision.WeldingObjectGenerator; using static Aurora.Api.SequenceBuilder; using static AuroraIO.AJINEXTEK.AjinIO.IOModuleInfo; using static AuroraIO.Vendor.PAIX.PAIXIO; using static ILSOS_PrismaticScanner.Device.PLCControllerPrismaticScanner; using static WeldingCommon.Devices.PLCController; using static WeldingCommon.Sequences.Main.SeqExecutePlcBitInterface_InitParam; namespace ILSOS_PrismaticScanner.Sequences { /// /// ACS 모션 + 갈바노 스캐너 MOTF 자동운전 시퀀스 /// 기존 SeqAutoRunPrismaticScanner 기반으로 작성 /// - 비전 보상 / 이미지 저장 제거 (MOTF 구조상 불필요) /// - InitFly2D(엔코더 리셋) 노드 추가 /// - Object 리스트를 List1 / List2 로 분리하여 각각 MOTF 용접 수행 /// 작성일: 26.04.09 NDY /// class SeqAutoRunPrismaticScannerACS { static readonly Logger Log = new Logger("SeqAutoRunPrismaticScannerACS"); #region 시퀀스 노드 이름 // 기존 SeqAutoRunPrismaticScanner 노드명과 동일하게 유지 const string SEQ_ENTER = "SEQ_ENTER"; const string SEQ_CHECK_INIT_CONDITION = "Check Init Condition"; const string SEQ_SET_PARAMS_PLC_PAGE_CHANGE_OR_WELD_REQ_INTERFACE_SEQ = "Set Sequence Param (ExecutePlcPageChangeOrWeldRequestInterface Seq)"; const string SEQ_WAIT_CHANGE_PAGE_OR_WELD_REQUEST = "Process (Wait Page Change Request Or Weld Request)"; const string SEQ_GET_PARAMS_PLC_PAGE_CHANGE_OR_WELD_REQ_INTERFACE_SEQ = "Get Sequence Result (ExecutePlcPageChangeOrWeldRequestInterface Seq)"; const string SEQ_CHECK_WELDDING_RETRY = "Check Welding Retry"; const string SEQ_GENERATE_WELDING_OBJECT = "Generate Welding Object"; const string SEQ_SET_PARAMS_COMPONENT_TEACHINGRUN_SEQ = "Set Params Component Teachingrun Seq"; const string SEQ_DO_ID_COMPONENT_TEACHINGRUN_SEQ = "Process (Component Teachingrun Seq)"; const string SEQ_GET_RESULT_COMPONENT_TEACHINGRUN_SEQ = "Get Result Component Teachingrun Seq"; const string SEQ_GET_PARAMS_PLC_WELD_COMPLETE_INTERFACE_SEQ = "Get Sequence Result (PLC Weld Complete Interface Seq)"; const string SEQ_DO_PLC_WELD_COMPLETE_INTERFACE_SEQ = "Process (PLC Weld Complete Interface Seq)"; const string SEQ_SET_PARAMS_PLC_WELD_COMPLETE_INTERFACE_SEQ = "Set Sequence Param (PLC Weld Complete Interface Seq)"; const string SEQ_SAVE_RESULT_DATA = "Save Result Data"; const string SEQ_COMPLETE = "SEQ_COMPLETE"; #region 26.04.09 NDY - MOTF 추가 노드 // 기존 노드에 없던 ACS MOTF 전용 노드 // 노드명 뒤에 "ACS" 또는 "1"/"2" 붙여서 기존과 구분 // [MOTF Step 1] Linear 원점에서 엔코더 리셋 const string SEQ_RESET_ENCODER_ACS = "Reset Encoder ACS (InitFly2D)"; // [MOTF Step 3] 1번 Object 리스트 오프셋 이동 const string SEQ_MOVE_OBJECT_LIST1_ACS = "Move Object List1 ACS (Scanner Center Offset)"; // [MOTF Step 5] 2번 Object 리스트 세팅 및 용접 const string SEQ_SET_PARAMS_COMPONENT_TEACHINGRUN_SEQ2 = "Set Params Component Teachingrun Seq2"; const string SEQ_DO_ID_COMPONENT_TEACHINGRUN_SEQ2 = "Process (Component Teachingrun Seq2)"; const string SEQ_GET_RESULT_COMPONENT_TEACHINGRUN_SEQ2 = "Get Result Component Teachingrun Seq2"; // [MOTF Step 6] 2번 Object 리스트 오프셋 이동 const string SEQ_MOVE_OBJECT_LIST2_ACS = "Move Object List2 ACS (Scanner Center Offset)"; #endregion #region 26.04.10 NDY - 시뮬레이션 우회 전용 노드 // PLC 서브시퀀스를 개발 환경에서 스킵하기 위한 노드 // 실제 장비에서는 이 노드를 타지 않음 const string SEQ_SKIP_PLC_WELD_COMPLETE_ACS = "Skip PLC WeldComplete ACS (Simulation)"; #endregion #endregion static class ContextKey { // 기존과 동일한 패턴, 클래스명만 ACS로 변경 public const string KEY_SEQUENCE_RESULT = "KEY_SEQUENCE_RESULT_SeqAutoRunPrismaticScannerACS"; public const string KEY_SEQUENCE_START_TIME = "KEY_SEQUENCE_START_TIME_SeqAutoRunPrismaticScannerACS"; } #region 변수 정의 #region 26.04.10 NDY - 시뮬레이션 1사이클 테스트용 // true: 1사이클 후 루프 중단, false: 기존처럼 계속 루프 private bool _IsOneCycleTest = true; #endregion // 기존 SeqAutoRunPrismaticScanner 변수 그대로 유지 // 디바이스 정의 private Scanner _Scanner; private Laser _Laser; private PLCControllerPrismaticScanner _PLC; private IODevice _IO; private LightDevice _Light; // 비전 정의 // MOTF에서는 비전 보상을 사용하지 않으나, 향후 확장 가능성을 위해 선언 유지 private CameraAgentBase _CameraAgent; private ICameraDisplay _CameraDisplay; private PatternAgentBase _PatternAgent; private List _Patterns; private PatternInfo _PatternInfo; private PatternResult _PatternResult; private FieldScanParams _FieldScanParams; private object _GrabImage; private MIL_ID _GrabImageMilId; private int _RetryCount = 0; private double _OffsetXfromModelFinder = 0; private double _OffsetYfromModelFinder = 0; // 시스템 및 용접 파라미터 정의 private SystemParameter _SystemParameter; private static SequenceSetting _SequenceSetting; private ScannerCanvasUserCtrl _ScannerCanvasUserCtrl; private List _SelectedDrawObjectList; private static List _UnpackDrawObject; // InitParam 타입만 ACS용으로 변경, 나머지 구조 동일 private SeqAutoRunPrismaticScannerACS_InitParam _InitParam; // 생산 데이터 정의 private uint _TodayProduct; private uint _TotalProduct; private DateTime _AutoRunStartTime; private DateTime _WeldStartTime; private DateTime _WeldEndTime; private TimeSpan _WeldSpanTime; private TimeSpan _InterfaceSpanTime; private DateTime _InterfaceStartTime; private string _LotId = "N/A"; private string _CellId = "N/A"; const int timeoutSeconds = 10; private bool _FirstUpdateStep = true; private bool _IsWeldingRetry = false; private int _CurrentWeldingStep = 0; private int _TotalWeldingStep = 0; private int _PreviousTotalWeldingStep = 0; private int _PageNo = 1; private MIL_ID _ResultImageMilId; private bool _SuccessVisionResult = false; const int PLC_TIMEOUT = 60; public string EVENT_TRIGGER_SAVE_FILE = "EventTriggerSaveFile"; // 공정 시간 측정 private static DateTime _WeldReqTime; private static DateTime _WeldComTime; private List lightValues = new List { 255, 255, 255, 255 }; private (double X, double Y) TargetObjectCenterPosition = (X: 0.0, Y: 0.0); #region 26.04.09 NDY - ACS MOTF 추가 변수 // ACS 모션 컨트롤러 참조 // WeldingCommon/Devices/ACS/ScannerACS.cs 에 등록된 장치 // Stage X 축(AxisX2), Y1/Y2/Z 축 제어 및 엔코더 값 읽기에 사용 private ScannerACS _ScannerACS; // MOTF용 Object 리스트 분리 // 이미지 기준: // List1 - Page1 끝점 ~ Page2 시작점 (RM 용접, Virtual Line 포함) // List2 - Page3 끝점 ~ Page4 시작점 (LM 용접, Virtual Line 포함) // 기존 _SelectedDrawObjectList 명명 패턴 유지, 숫자로 구분 private List _SelectedDrawObjectList1; private List _SelectedDrawObjectList2; // 각 Object 리스트별 스캐너 오프셋 // 스캐너 중심(50, 0) ~ Linear 원점(0, 0) 간 거리만큼 Object 좌표를 이동 // → WaitForEncoderX가 RM(25,0) / LM(75,0) 시작점에서 정확히 트리거되도록 보장 // TODO: 이정인 선임 ACS 매핑 파일 수령 후 실제 수치로 확정 필요 private double _ObjectList1OffsetX = 0; private double _ObjectList1OffsetY = 0; private double _ObjectList2OffsetX = 0; private double _ObjectList2OffsetY = 0; #endregion #endregion public SequenceBuilder Builder() { var sb = new SequenceBuilder(1000, true); // 기존 노드 등록 (SeqAutoRunPrismaticScanner와 동일 구조) sb.AddAction(SEQ_ENTER, Sequence_Enter); sb.AddDecision(SEQ_CHECK_INIT_CONDITION, CheckInitCondition); #region 26.04.10 NDY - PLC WeldRequest : Action → Decision 변경 // 기존: sb.AddAction(SEQ_SET_PARAMS_PLC_PAGE_CHANGE_OR_WELD_REQ_INTERFACE_SEQ, ...) // 변경 이유: 시뮬레이션 환경에서 서브시퀀스(SEQ_WAIT_CHANGE_PAGE_OR_WELD_REQUEST)를 // 타지 않도록 Decision 분기 처리가 필요 // 실제 장비: SEQ_WAIT_CHANGE_PAGE_OR_WELD_REQUEST → SEQ_GET_PARAMS → SEQ_CHECK_WELDDING_RETRY // 시뮬레이션: 서브시퀀스 스킵 → 바로 SEQ_CHECK_WELDDING_RETRY sb.AddDecision(SEQ_SET_PARAMS_PLC_PAGE_CHANGE_OR_WELD_REQ_INTERFACE_SEQ, SetPlcPageChangeOrWeldRequest_SeqParams); #endregion sb.AddSubsequence(SEQ_WAIT_CHANGE_PAGE_OR_WELD_REQUEST, SequenceManager.GetSeqBuilder_SeqWaitWeldOrChangePage()); sb.AddAction(SEQ_GET_PARAMS_PLC_PAGE_CHANGE_OR_WELD_REQ_INTERFACE_SEQ, GetPlcPageChangeOrWeldRequest_SeqParams); sb.AddDecision(SEQ_CHECK_WELDDING_RETRY, CheckWeldingRetry); sb.AddAction(SEQ_GENERATE_WELDING_OBJECT, GenerateWeldingObject); // List1 용접 (RM) sb.AddAction(SEQ_SET_PARAMS_COMPONENT_TEACHINGRUN_SEQ, SetComponentTeachingrun_SeqParams); sb.AddSubsequence(SEQ_DO_ID_COMPONENT_TEACHINGRUN_SEQ, SequenceManager.GetSeqBuilder_SeqComponentTeachingRun()); sb.AddAction(SEQ_GET_RESULT_COMPONENT_TEACHINGRUN_SEQ, GetComponentTeachingrun_SeqResult); // List2 용접 (LM) sb.AddAction(SEQ_SET_PARAMS_COMPONENT_TEACHINGRUN_SEQ2, SetComponentTeachingrun_SeqParams2); sb.AddSubsequence(SEQ_DO_ID_COMPONENT_TEACHINGRUN_SEQ2, SequenceManager.GetSeqBuilder_SeqComponentTeachingRun()); sb.AddAction(SEQ_GET_RESULT_COMPONENT_TEACHINGRUN_SEQ2, GetComponentTeachingrun_SeqResult2); #region 26.04.10 NDY - PLC WeldComplete : Action → Decision 변경 // 기존: sb.AddAction(SEQ_SET_PARAMS_PLC_WELD_COMPLETE_INTERFACE_SEQ, ...) // 변경 이유: 시뮬레이션 환경에서 서브시퀀스(SEQ_DO_PLC_WELD_COMPLETE_INTERFACE_SEQ)를 // 타지 않도록 Decision 분기 처리가 필요 // 실제 장비: SEQ_DO_PLC_WELD_COMPLETE → SEQ_GET_PARAMS → SEQ_SAVE_RESULT_DATA // 시뮬레이션: SEQ_SKIP_PLC_WELD_COMPLETE_ACS → SEQ_SAVE_RESULT_DATA sb.AddDecision(SEQ_SET_PARAMS_PLC_WELD_COMPLETE_INTERFACE_SEQ, SetPlcWeldCompleteInterface_SeqParams); sb.AddAction(SEQ_SKIP_PLC_WELD_COMPLETE_ACS, SkipPlcWeldComplete); #endregion sb.AddSubsequence(SEQ_DO_PLC_WELD_COMPLETE_INTERFACE_SEQ, SequenceManager.GetSeqBuilder_SeqExecutePlcBitInterface()); sb.AddAction(SEQ_GET_PARAMS_PLC_WELD_COMPLETE_INTERFACE_SEQ, GetPlcWeldCompleteInterface_SeqParams); sb.AddAction(SEQ_SAVE_RESULT_DATA, SaveResultData); sb.AddAction(SEQ_COMPLETE, Complete); #region 26.04.09 NDY - MOTF 추가 노드 등록 sb.AddAction(SEQ_RESET_ENCODER_ACS, ResetEncoder); sb.AddAction(SEQ_MOVE_OBJECT_LIST1_ACS, MoveObjectList1); sb.AddAction(SEQ_MOVE_OBJECT_LIST2_ACS, MoveObjectList2); #endregion // ── 메인 흐름 ────────────────────────────────────────────── sb.Flows(sb.Init > sb[SEQ_ENTER] > sb[SEQ_CHECK_INIT_CONDITION] > sb[SEQ_RESET_ENCODER_ACS] > sb[SEQ_SET_PARAMS_PLC_PAGE_CHANGE_OR_WELD_REQ_INTERFACE_SEQ]); #region 26.04.10 NDY - PLC WeldRequest 분기 흐름 // 실제 장비: 서브시퀀스 진입 sb.Flows(sb[SEQ_SET_PARAMS_PLC_PAGE_CHANGE_OR_WELD_REQ_INTERFACE_SEQ] > sb[SEQ_WAIT_CHANGE_PAGE_OR_WELD_REQUEST] > sb[SEQ_GET_PARAMS_PLC_PAGE_CHANGE_OR_WELD_REQ_INTERFACE_SEQ] > sb[SEQ_CHECK_WELDDING_RETRY]); // 시뮬레이션: 서브시퀀스 스킵 → 바로 CheckWeldingRetry sb.Flows(sb[SEQ_SET_PARAMS_PLC_PAGE_CHANGE_OR_WELD_REQ_INTERFACE_SEQ] > sb[SEQ_CHECK_WELDDING_RETRY]); #endregion // CheckWeldingRetry 이후 공통 흐름 sb.Flows(sb[SEQ_CHECK_WELDDING_RETRY] > sb[SEQ_GENERATE_WELDING_OBJECT] > sb[SEQ_MOVE_OBJECT_LIST1_ACS] > sb[SEQ_SET_PARAMS_COMPONENT_TEACHINGRUN_SEQ] > sb[SEQ_DO_ID_COMPONENT_TEACHINGRUN_SEQ] > sb[SEQ_GET_RESULT_COMPONENT_TEACHINGRUN_SEQ] > sb[SEQ_MOVE_OBJECT_LIST2_ACS] > sb[SEQ_SET_PARAMS_COMPONENT_TEACHINGRUN_SEQ2] > sb[SEQ_DO_ID_COMPONENT_TEACHINGRUN_SEQ2] > sb[SEQ_GET_RESULT_COMPONENT_TEACHINGRUN_SEQ2] > sb[SEQ_SET_PARAMS_PLC_WELD_COMPLETE_INTERFACE_SEQ]); // 재시도: Object 생성 스킵하고 List1 이동으로 sb.Flows(sb[SEQ_CHECK_WELDDING_RETRY] > sb[SEQ_MOVE_OBJECT_LIST1_ACS]); #region 26.04.10 NDY - PLC WeldComplete 분기 흐름 // 실제 장비: 서브시퀀스 진입 → 결과 수신 → SaveResultData sb.Flows(sb[SEQ_SET_PARAMS_PLC_WELD_COMPLETE_INTERFACE_SEQ] > sb[SEQ_DO_PLC_WELD_COMPLETE_INTERFACE_SEQ] > sb[SEQ_GET_PARAMS_PLC_WELD_COMPLETE_INTERFACE_SEQ] > sb[SEQ_SAVE_RESULT_DATA]); // 시뮬레이션: 서브시퀀스 스킵 → SkipNode → SaveResultData sb.Flows(sb[SEQ_SET_PARAMS_PLC_WELD_COMPLETE_INTERFACE_SEQ] > sb[SEQ_SKIP_PLC_WELD_COMPLETE_ACS] > sb[SEQ_SAVE_RESULT_DATA]); #endregion // 결과 저장 후 PLC 요청 대기로 루프 sb.Flows(sb[SEQ_SAVE_RESULT_DATA] > sb[SEQ_SET_PARAMS_PLC_PAGE_CHANGE_OR_WELD_REQ_INTERFACE_SEQ]); // CheckInitCondition 실패 시 종료 sb.Flows(sb[SEQ_CHECK_INIT_CONDITION] > sb[SEQ_COMPLETE] > sb.Final); // 26.04.10 1사이클만 체크하고 빠져나오는 시퀸스로 테스트용 추가 Builder()에 추가 -- sb.Flows(sb[SEQ_SET_PARAMS_PLC_PAGE_CHANGE_OR_WELD_REQ_INTERFACE_SEQ] > sb[SEQ_COMPLETE]); sb.OnForceError(OnForceError); sb.OnStopped(OnStopped); //sb.Draw("SeqAutoRunPrismaticScannerACS.pdf"); return sb; } private string CheckWeldingRetry(SequenceContext context, string id, ICollection branches, SequenceActionResult param) { // 기존 SeqAutoRunPrismaticScanner와 동일 로직 if (_IsWeldingRetry) return SEQ_MOVE_OBJECT_LIST1_ACS; else return SEQ_GENERATE_WELDING_OBJECT; } #region 시퀀스 함수 #region 1. 디바이스 및 변수 상태 확인 private SequenceActionResult Sequence_Enter(SequenceContext context, string id, SequenceActionResult param) { Log.I("SeqAutoRunPrismaticScannerACS Start"); _InitParam = param.Param as SeqAutoRunPrismaticScannerACS_InitParam; _SystemParameter = context.GetSetting(CommonSettingIds.SystemParameter); _SequenceSetting = context.GetSetting(CommonSettingIds.Sequence); InitDev(context); if (!CheckDeviceReady(context)) { AlarmManager.Alarm(AlarmCode.ALARM_SEQUENCE_AutoRun_DeviceNotReady); Log.E("The devices are not active."); return null; } if (InitSequenceVariable(context) == false) { AlarmManager.Alarm(AlarmCode.ALARM_SEQUENCE_AutoRun_InitVariableError); return null; } _PageNo = _SequenceSetting.LastPageNo; Log.I($"Previous Page No is {_PageNo}"); return SequenceAction.Ok(); } private string CheckInitCondition(SequenceContext context, string id, ICollection branches, SequenceActionResult param) { Status.SetWorkingPosition(eWorkingPosition.CHECKING_CONDITION); if (!CheckCondition(context)) { AlarmManager.Alarm(AlarmCode.ALARM_SEQUENCE_AutoRun_CheckInitCondition); Log.E("Condition is not in state to start sequence."); context.PutValue(ContextKey.KEY_SEQUENCE_RESULT, false); return SEQ_COMPLETE; } var startTime = DateTime.Now; context.PutValue(ContextKey.KEY_SEQUENCE_START_TIME, startTime); #region 26.04.10 NDY - 시뮬레이션 환경 우회 if (ApplicationExt.IsSimulation) { Log.I("Simulation Mode - Skip Laser/PLC CheckInitCondition"); return SEQ_RESET_ENCODER_ACS; } #endregion // 레이저 레디 확인 (기존과 동일) if (!_Laser.GetLaserReady()) { _Laser.LaserOn(); Log.I("Start Laser ON in CheckInitCondition"); } var res = context.RetryUntilSuccess(_Laser.GetLaserReady, timeoutSeconds * 1000, 10); if (res == E2.TIMEOUT) { Log.E("Laser Ready Fail When Autorun"); return null; } InitializePlcBits(); _PLC.WriteBit(eBitOutput.PC_AUTO_STATUS, true); _PLC.WriteWord(eWordOutput.PC_PAGE_NO, (short)_PageNo); Log.I("Complete CheckInitCondition."); return SEQ_RESET_ENCODER_ACS; } #endregion #region 26.04.09 NDY - MOTF Step 1 : 엔코더 리셋 /// /// [MOTF Step 1] /// 자동운전 시작 시 Linear 원점 위치에서 InitFly2D() 호출 /// → 스캐너 내부 X 엔코더 값을 0으로 초기화 /// → 이후 SeqUploadWeldingList 내 WaitForEncoderX가 /// RM(25,0) / LM(75,0) 시작점에서 정확히 트리거되도록 보장 /// private SequenceActionResult ResetEncoder(SequenceContext context, string id, SequenceActionResult param) { Log.I("MOTF Step1: InitFly2D - Encoder Reset at Linear Origin"); _Scanner.InitFly2D(); return SequenceAction.Ok(); } #endregion #region 2. PLC 페이지 변경 / 용접 요청 대기 #region 26.04.10 NDY - Action → Decision 변경 (PLC WeldRequest) /// /// 기존 Action에서 Decision으로 변경 /// - 시뮬레이션: SEQ_CHECK_WELDDING_RETRY 로 직접 분기 (서브시퀀스 완전 스킵) /// - 실제 장비: SEQ_WAIT_CHANGE_PAGE_OR_WELD_REQUEST 서브시퀀스로 진입 /// private string SetPlcPageChangeOrWeldRequest_SeqParams(SequenceContext context, string id, ICollection branches, SequenceActionResult param) { if (ApplicationExt.IsSimulation) { #region 26.04.10 NDY - 1사이클 테스트 if (_IsOneCycleTest && !_FirstUpdateStep) { // 첫 사이클 이후 루프 진입 시 종료 Log.I("Simulation Mode - 1 Cycle Test Complete, Stop Loop"); return SEQ_COMPLETE; } #endregion Log.I("Simulation Mode - Skip PLC WeldRequest, proceed to CheckWeldingRetry"); _PageNo = 1; _FirstUpdateStep = false; _TotalWeldingStep = 1; return SEQ_CHECK_WELDDING_RETRY; } // 기존 SeqAutoRunPrismaticScanner와 동일 Log.I(string.Format("PLC Interface(PageChange, WeldReq) Start; (REQ,ACK); ({0},{1});({2},{3})", (int)eBitInput.PLC_PAGE_CHANGE_REQ, (int)eBitOutput.PC_PAGE_CHANGE_COMPLETE, (int)eBitInput.PLC_WELD_READY_REQ, (int)eBitOutput.PC_WELD_READY_ACK)); Status.SetWorkingPosition(eWorkingPosition.LOADING_CELL); return SEQ_WAIT_CHANGE_PAGE_OR_WELD_REQUEST; } #endregion private SequenceActionResult GetPlcPageChangeOrWeldRequest_SeqParams(SequenceContext context, string id, SequenceActionResult param) { #region 26.04.10 NDY - 시뮬레이션 환경 우회 if (ApplicationExt.IsSimulation) { Log.I("Simulation Mode - Skip PLC WeldRequest Result"); return SequenceAction.Ok(); } #endregion // 기존 SeqAutoRunPrismaticScanner와 동일 var resultOutParam = param.Param as SeqWaitWeldOrChangePage_OutParam; _WeldReqTime = resultOutParam._WeldReqTime; if (resultOutParam.Success == false) { Log.E("Failed Page Change & Weld Request Interface"); return null; } _PreviousTotalWeldingStep = _TotalWeldingStep; _PageNo = resultOutParam.PLCPageNo; _TotalWeldingStep = Convert.ToInt16(_PLC.ReadWord(eWordInput.PLC_STEP_NO).data); _SequenceSetting.LastPageNo = _PageNo; context.Settings.Save(CommonSettingIds.Sequence, _SequenceSetting, "SeqAutoRunPrismaticScannerACS"); Status.GetProcessingWeldingData().IsVisionNG = false; _PLC.WriteBit(eBitOutput.PC_WELD_NG_JUDGE, false); var UploadDatas = Status.GetProcessingWeldingData().UploadProcessDatas; foreach (var data in UploadDatas) data.Clear(); _PLC.UploadReportDatas(UploadDatas); Log.I(string.Format("Read Words; Page No: {0}; Step No: {1}", _PageNo, _TotalWeldingStep)); if (_PageNo > _ScannerCanvasUserCtrl.Model.Layers.Count() && _PageNo > 0) { Log.E(string.Format("Page No isn't proper. ;Page No : {0}; Layers Count: {1}", _PageNo, _ScannerCanvasUserCtrl.Model.Layers.Count())); return null; } Status.GetProcessingWeldingData().CellId = _PLC.CellId(); Status.GetProcessingWeldingData().LotId = _PLC.LotId(); if (_FirstUpdateStep) { _FirstUpdateStep = false; return SequenceAction.Ok(); } if (_PreviousTotalWeldingStep != _TotalWeldingStep) { Log.E(string.Format("SeqCheckWeldingComplete Error;Changed Step No; Prev: {0}; Current: {1}", _PreviousTotalWeldingStep, _TotalWeldingStep)); return null; } return SequenceAction.Ok(); } #endregion #region 3. 용접 Object 생성 및 List 분리 /// /// [MOTF Step 2] /// 레시피 레이어에서 Object를 꺼내 List1 / List2 로 분리 /// - _SelectedDrawObjectList : 기존과 동일하게 전체 Object 보관 /// - _SelectedDrawObjectList1 : RM 용접용 (Page1 끝~Page2 시작, Virtual Line 포함) /// - _SelectedDrawObjectList2 : LM 용접용 (Page3 끝~Page4 시작, Virtual Line 포함) /// TODO: 실제 분리 기준(ScannerParamID 또는 레이어 구성)은 이정인 선임 확인 후 확정 /// private SequenceActionResult GenerateWeldingObject(SequenceContext context, string id, SequenceActionResult param) { // 기존 _SelectedDrawObjectList 유지 (전체 Object) _SelectedDrawObjectList = _ScannerCanvasUserCtrl.Model.Layers[_PageNo - 1].Objects; // TODO: 실제 분리 기준 확인 후 구현 // 현재는 절반으로 단순 분리 (임시) int half = _SelectedDrawObjectList.Count / 2; _SelectedDrawObjectList1 = _SelectedDrawObjectList.Take(half).ToList(); _SelectedDrawObjectList2 = _SelectedDrawObjectList.Skip(half).ToList(); Log.I($"GenerateWeldingObject: Total({_SelectedDrawObjectList.Count}), List1({_SelectedDrawObjectList1.Count}), List2({_SelectedDrawObjectList2.Count})"); return SequenceAction.Ok(); } #endregion #region 26.04.09 NDY - MOTF Step 3 : List1 오프셋 이동 /// /// [MOTF Step 3] /// 1번 Object 리스트를 스캐너 중심으로부터 원점위치까지 떨어진 양만큼 오프셋 이동 /// - 스캐너 중심 = (50, 0), Linear 원점 = (0, 0) → 오프셋 X = -50mm /// - 이 오프셋은 SeqUploadWeldingList 내 CellMeasureCenterOffset으로 전달됨 /// - WaitForEncoderX가 RM 용접 시작점(25, 0)에서 트리거되도록 준비 /// TODO: ScannerConfig 또는 SystemParameter에서 스캐너 중심 좌표 가져오도록 수정 /// private SequenceActionResult MoveObjectList1(SequenceContext context, string id, SequenceActionResult param) { _ObjectList1OffsetX = -_Scanner.Config.MarkingOntheFlyOffsetX; _ObjectList1OffsetY = -_Scanner.Config.MarkingOntheFlyOffsetY; Log.I($"MOTF Step3: MoveObjectList1 Offset({_ObjectList1OffsetX}, {_ObjectList1OffsetY})"); return SequenceAction.Ok(); } #endregion #region 4. List1 용접 (RM) private SequenceActionResult SetComponentTeachingrun_SeqParams(SequenceContext context, string id, SequenceActionResult param) { // 기존 SeqAutoRunPrismaticScanner와 동일 구조 _PLC.WriteBit(eBitOutput.PC_WELD_BUSY, true); Log.I("Weld Busy On ! Set ComponentTeachingrun (List1) Start."); _WeldStartTime = DateTime.Now; Status.GetProcessingWeldingData().WeldingStartTime = _WeldStartTime; var currentModelInfo = EquipmentSpecificModelUtil.GetCurrentRecipeEquipmentSpecificModel(); if (currentModelInfo.equipmentSpecificModel == null) { MessageUtil.ShowWarningToast(string.Format("선택된 model 이 없습니다.", "Laser On")); return null; } var intervalTimeBlowOn = 0; var intervalTimeBlowOff = 0; if (currentModelInfo.equipmentSpecificModel.UseBlow) { intervalTimeBlowOn = currentModelInfo.equipmentSpecificModel.IntervalTimeBlowOn; intervalTimeBlowOff = currentModelInfo.equipmentSpecificModel.IntervalTimeBlowOff; } SeqTeachingRun_InitParam seqParam = new SeqTeachingRun_InitParam() { IsUseExternalObjects = true, ExternalObjects = _SelectedDrawObjectList1, // List1 사용 UseStream = currentModelInfo.equipmentSpecificModel.UseStream, UseBlow = currentModelInfo.equipmentSpecificModel.UseBlow, IntervalTimeBlowOn = intervalTimeBlowOn, IntervalTimeBlowOff = intervalTimeBlowOff, NeedSetLaserTiming = true, // MOTF 오프셋 전달 (기존 CellCenterOffset 파라미터 재활용) CellCenterOffsetX = _ObjectList1OffsetX, CellCenterOffsetY = _ObjectList1OffsetY, }; Log.I("Set ComponentTeachingrun (List1) Complete!"); return SequenceAction.Ok(seqParam); } private SequenceActionResult GetComponentTeachingrun_SeqResult(SequenceContext context, string id, SequenceActionResult param) { var resultOutParam = param.Param as SeqTeachingRun_OutParam; if (resultOutParam.Success == false) { Log.E("ComponentTeachingrun (List1) Failed."); return null; } Log.I("ComponentTeachingrun (List1) Complete!"); return SequenceAction.Ok(); } #endregion #region 26.04.09 NDY - MOTF Step 6 : List2 오프셋 이동 /// /// [MOTF Step 6] /// 2번 Object 리스트를 스캐너 중심으로부터 원점위치까지 떨어진 양만큼 오프셋 이동 /// - LM 용접 시작점(75, 0)에서 WaitForEncoderX가 트리거되도록 준비 /// TODO: List2 기준 오프셋 수치 확인 후 수정 /// private SequenceActionResult MoveObjectList2(SequenceContext context, string id, SequenceActionResult param) { _ObjectList2OffsetX = -_Scanner.Config.MarkingOntheFlyOffsetX; _ObjectList2OffsetY = -_Scanner.Config.MarkingOntheFlyOffsetY; Log.I($"MOTF Step6: MoveObjectList2 Offset({_ObjectList2OffsetX}, {_ObjectList2OffsetY})"); return SequenceAction.Ok(); } #endregion #region 26.04.09 NDY - List2 용접 (LM) private SequenceActionResult SetComponentTeachingrun_SeqParams2(SequenceContext context, string id, SequenceActionResult param) { // List1과 동일 구조, ExternalObjects와 Offset만 List2 기준으로 변경 var currentModelInfo = EquipmentSpecificModelUtil.GetCurrentRecipeEquipmentSpecificModel(); if (currentModelInfo.equipmentSpecificModel == null) { MessageUtil.ShowWarningToast(string.Format("선택된 model 이 없습니다.", "Laser On")); return null; } var intervalTimeBlowOn = 0; var intervalTimeBlowOff = 0; if (currentModelInfo.equipmentSpecificModel.UseBlow) { intervalTimeBlowOn = currentModelInfo.equipmentSpecificModel.IntervalTimeBlowOn; intervalTimeBlowOff = currentModelInfo.equipmentSpecificModel.IntervalTimeBlowOff; } SeqTeachingRun_InitParam seqParam = new SeqTeachingRun_InitParam() { IsUseExternalObjects = true, ExternalObjects = _SelectedDrawObjectList2, // List2 사용 UseStream = currentModelInfo.equipmentSpecificModel.UseStream, UseBlow = currentModelInfo.equipmentSpecificModel.UseBlow, IntervalTimeBlowOn = intervalTimeBlowOn, IntervalTimeBlowOff = intervalTimeBlowOff, NeedSetLaserTiming = true, CellCenterOffsetX = _ObjectList2OffsetX, CellCenterOffsetY = _ObjectList2OffsetY, }; Log.I("Set ComponentTeachingrun (List2) Complete!"); return SequenceAction.Ok(seqParam); } private SequenceActionResult GetComponentTeachingrun_SeqResult2(SequenceContext context, string id, SequenceActionResult param) { var resultOutParam = param.Param as SeqTeachingRun_OutParam; if (resultOutParam.Success == false) { Log.E("ComponentTeachingrun (List2) Failed."); return null; } // List2 용접까지 완료되면 WELD_BUSY OFF _PLC.WriteBit(eBitOutput.PC_WELD_BUSY, false); Log.I("Weld Busy OFF!"); _WeldEndTime = DateTime.Now; _WeldSpanTime = _WeldEndTime.Subtract(_WeldStartTime); Log.I($"[TactTime] Page({_PageNo}) Total Welding Time : {_WeldSpanTime.TotalMilliseconds}msec"); return SequenceAction.Ok(); } #endregion #region 5. 용접 완료 PLC 인터페이스 #region 26.04.10 NDY - Action → Decision 변경 (PLC WeldComplete) /// /// 기존 Action에서 Decision으로 변경 /// - 시뮬레이션: SEQ_SKIP_PLC_WELD_COMPLETE_ACS 로 분기 (서브시퀀스 완전 스킵) /// - 실제 장비: SEQ_DO_PLC_WELD_COMPLETE_INTERFACE_SEQ 서브시퀀스로 진입 /// private string SetPlcWeldCompleteInterface_SeqParams(SequenceContext context, string id, ICollection branches, SequenceActionResult param) { if (ApplicationExt.IsSimulation) { Log.I("Simulation Mode - Skip PLC WeldComplete Interface"); return SEQ_SKIP_PLC_WELD_COMPLETE_ACS; } // 기존 SeqAutoRunPrismaticScanner와 동일 Log.I("SeqCheckWeldingComplete Set Parameters"); return SEQ_DO_PLC_WELD_COMPLETE_INTERFACE_SEQ; } /// /// 시뮬레이션 환경에서 PLC WeldComplete 서브시퀀스 스킵 시 호출 /// 실제 장비에서는 이 노드를 타지 않음 /// private SequenceActionResult SkipPlcWeldComplete(SequenceContext context, string id, SequenceActionResult param) { Log.I("Simulation Mode - PLC WeldComplete Skipped, Update ProductCount"); _CurrentWeldingStep = 0; _IsWeldingRetry = false; Status.SetWorkingPosition(eWorkingPosition.UNLOADING_CELL); UpdateProductCount(context); UpdateProductInfoForView(); return SequenceAction.Ok(); } #endregion private SequenceActionResult GetPlcWeldCompleteInterface_SeqParams(SequenceContext context, string id, SequenceActionResult param) { #region 26.04.10 NDY - 시뮬레이션 환경 우회 if (ApplicationExt.IsSimulation) { Log.I("Simulation Mode - Skip PLC WeldComplete Result"); return SequenceAction.Ok(); } #endregion // 기존 SeqAutoRunPrismaticScanner와 동일 var resultOutParam = param.Param as SeqExecutePlcBitInterface_OutParam; if (resultOutParam.Success == false) { Log.E("SeqCheckWeldingComplete Error;Success Failed."); return null; } Log.I("SeqCheckWeldingComplete Success;Welding Complete!"); _WeldComTime = DateTime.Now; Log.I($"***** 공정 시간 WeldPage No : {_PageNo} WeldRequest ~ WeldComplete : {_WeldComTime.Subtract(_WeldReqTime).TotalMilliseconds}ms"); _CurrentWeldingStep++; if (_CurrentWeldingStep >= _TotalWeldingStep) { Log.I(string.Format("Need to Update the Cell Info;{0}/{1}", _CurrentWeldingStep, _TotalWeldingStep)); _IsWeldingRetry = false; _CurrentWeldingStep = 0; Status.SetWorkingPosition(eWorkingPosition.UNLOADING_CELL); UpdateProductCount(context); UpdateProductInfoForView(); } else { Log.I(string.Format("Update Welding Step;{0}/{1}", _CurrentWeldingStep, _TotalWeldingStep)); _IsWeldingRetry = true; } return SequenceAction.Ok(); } #endregion #region 6. 결과 저장 및 종료 private SequenceActionResult SaveResultData(SequenceContext context, string id, SequenceActionResult param) { Status.SetWorkingPosition(eWorkingPosition.REPORTING_RESULT); context.PutValue(ContextKey.KEY_SEQUENCE_RESULT, true); return SequenceAction.Ok(); } private SequenceActionResult Complete(SequenceContext context, string id, SequenceActionResult param) { RestoreDeviceStatus(); context.TryGetValue(ContextKey.KEY_SEQUENCE_RESULT, out bool seqResult); SeqAutoRunPrismaticScannerACS_OutParam result = new SeqAutoRunPrismaticScannerACS_OutParam() { Success = seqResult }; if (seqResult == false) AlarmManager.Alarm(AlarmCode.ALARM_SEQUENCE_AutoRun_ResultFailed); Status.SetWorkingPosition(eWorkingPosition.DONE); Log.I($"SeqAutoRunPrismaticScannerACS Complete. Result: {seqResult}"); // ← 추가 확인용 return SequenceAction.Ok(result); } #endregion #endregion #region 구동 함수 private bool InitSequenceVariable(SequenceContext context) { var mainPage = CommonHelper.GetMainPage(); _ScannerCanvasUserCtrl = mainPage.ScannerCanvasCtrlTeaching; // 기존과 동일 _PageNo = 1; _CurrentWeldingStep = 0; _PreviousTotalWeldingStep = 0; _TotalWeldingStep = 0; _OffsetXfromModelFinder = 0; _OffsetYfromModelFinder = 0; _IsWeldingRetry = false; _FirstUpdateStep = true; _RetryCount = 0; #region 26.04.09 NDY - ACS 추가 변수 초기화 _SelectedDrawObjectList1 = new List(); _SelectedDrawObjectList2 = new List(); _ObjectList1OffsetX = 0; _ObjectList1OffsetY = 0; _ObjectList2OffsetX = 0; _ObjectList2OffsetY = 0; #endregion return true; } private void InitDev(SequenceContext context) { // 기존과 동일 _Scanner = context.GetLaserScanner(); _Laser = context.GetLaser(); _PLC = context.GetPLC() as PLCControllerPrismaticScanner; _Light = context.GetLight(); #region 26.04.09 NDY - ACS 장치 초기화 // ScannerACS: WeldingCommon/Devices/ACS/ScannerACS.cs 에 등록된 장치 // Stage X(AxisX2), Y1/Y2/Z 축 제어 및 엔코더 읽기 담당 try { _ScannerACS = context.Devices[CommonDeviceIds.ACS] as ScannerACS; } catch (Exception ex) { Log.E($"ScannerACS Device Init Failed: {ex.Message}"); } #endregion } private bool CheckDeviceReady(SequenceContext context) { #region 26.04.10 NDY - 시뮬레이션 환경 우회 // 시뮬레이션 환경에서는 장치 활성화 체크 스킵 if (ApplicationExt.IsSimulation) { Log.I("Simulation Mode - Skip Device Ready Check"); return true; } #endregion Log.I("CheckDeviceReady Start"); // 기존과 동일 List deviceListToCheck = new List() { CommonDeviceIds.Scanner, CommonDeviceIds.Laser, CommonDeviceIds.PLCDevice }; bool isAllDeviceActivated = true; foreach (string item in deviceListToCheck) { bool isActivated = context.Devices[item].IsActivated; Log.I($"Device: {item}, IsActivated: {isActivated}"); if (!isActivated) isAllDeviceActivated = false; } #region 26.04.09 NDY - ScannerACS 장치 활성화 확인 if (_ScannerACS == null || !_ScannerACS.IsActivated) { Log.E("ScannerACS is not activated."); isAllDeviceActivated = false; } #endregion Log.I($"CheckDeviceReady Result: {isAllDeviceActivated}"); return isAllDeviceActivated; } private bool CheckCondition(SequenceContext context) { if (CheckCondition_Interlock()) { if (InitializeDeviceStatus(context)) { return true; } } return false; } private bool CheckCondition_Interlock() { // TODO: 필요한 인터락 조건 추가 return true; } private bool InitializeDeviceStatus(SequenceContext context) { return true; } private void InitializePlcBits() { // 기존 SeqAutoRunPrismaticScanner와 동일 _PLC.WriteBit(eBitOutput.PC_WELD_READY_ACK, false); _PLC.WriteBit(eBitOutput.PC_WELD_BUSY, false); _PLC.WriteBit(eBitOutput.PC_WELD_COMPLETE, false); _PLC.WriteBit(eBitOutput.PC_PAGE_CHANGE_COMPLETE, false); _PLC.WriteBit(eBitOutput.PC_WELD_NG_JUDGE, false); var UploadDatas = Status.GetProcessingWeldingData().UploadProcessDatas; foreach (var data in UploadDatas) data.Clear(); _PLC.UploadReportDatas(UploadDatas); } private bool RestoreDeviceStatus() { #region 26.04.10 NDY - 시뮬레이션 환경 우회 if (ApplicationExt.IsSimulation) { Log.I("Simulation Mode - Skip RestoreDeviceStatus"); Status.SetStatus(eEqpStatus.Manual); return true; } #endregion if (_Laser.GetLaserReady()) _Laser.LaserOff(); Status.SetStatus(eEqpStatus.Manual); _PLC.WriteBit(eBitOutput.PC_AUTO_STATUS, false); InitializePlcBits(); return true; } private void UpdateProductCount(SequenceContext context) { // 기존 SeqAutoRunPrismaticScanner와 동일 _SystemParameter = context.GetSetting(CommonSettingIds.SystemParameter); _TodayProduct = _SystemParameter.TodayProductCount; _TotalProduct = _SystemParameter.TotalProductCount; if (_TodayProduct < uint.MaxValue) _TodayProduct++; if (_TotalProduct < uint.MaxValue) _TotalProduct++; _SystemParameter.TodayProductCount = _TodayProduct; _SystemParameter.TotalProductCount = _TotalProduct; _WeldEndTime = DateTime.Now; _SystemParameter.LastProductTime = _WeldEndTime; context.Settings.Save(CommonSettingIds.SystemParameter, _SystemParameter, "SeqAutoRunPrismaticScannerACS"); } private void UpdateProductInfoForView() { // 기존 SeqAutoRunPrismaticScanner와 동일 Status.GetProcessingWeldingData().TotalProduct = _TotalProduct; Status.GetProcessingWeldingData().TodayProduct = _TodayProduct; Status.GetProcessingWeldingData().WeldingStartTime = _WeldStartTime; Status.GetProcessingWeldingData().WeldingEndTime = _WeldEndTime; Status.GetProcessingWeldingData().WeldingSpanTimeMilliseconds = _WeldSpanTime.TotalMilliseconds.ToString(); Status.GetProcessingWeldingData().LotId = _LotId; Status.GetProcessingWeldingData().CellId = _CellId; } private static void UnpackRecipes(List recipes) { // 기존 SeqAutoRunPrismaticScanner와 동일 if (recipes == null) return; for (int i = 0; i < recipes.Count; i++) { if (recipes[i] is CoreUnit.Control.UserControl.Canvas.Group) { CoreUnit.Control.UserControl.Canvas.Group subGroup = recipes[i] as CoreUnit.Control.UserControl.Canvas.Group; List subrecipes = subGroup.SubObjects as List; UnpackRecipes(subrecipes); } else { _UnpackDrawObject.Add(recipes[i]); } } } private void OnForceError(SequenceContext arg1, string arg2) { OnSeqActionStop(); Log.E("SeqAutoRunPrismaticScannerACS OnForceError"); } private void OnStopped(SequenceContext obj) { OnSeqActionStop(); Log.E("SeqAutoRunPrismaticScannerACS OnStopped"); } private void OnSeqActionStop() { #region 26.04.10 NDY - 시뮬레이션 환경 우회 if (ApplicationExt.IsSimulation) { Log.I("Simulation Mode - Skip OnSeqActionStop"); Status.SetStatus(eEqpStatus.Manual); return; } #endregion // 기존 SeqAutoRunPrismaticScanner와 동일 if (_Laser.GetLaserReady()) _Laser.LaserOff(); Status.SetStatus(eEqpStatus.Manual); _PLC.WriteBit(eBitOutput.PC_AUTO_STATUS, false); InitializePlcBits(); } #endregion } public class SeqAutoRunPrismaticScannerACS_InitParam : InitParam { // 기존 SeqAutoRunPrismaticScanner_InitParam 패턴 유지 // 추후 MOTF 관련 파라미터 추가 가능 } public class SeqAutoRunPrismaticScannerACS_OutParam : OutParam { } }