using Aurora; using Aurora.Api; using Aurora.Api.Manager; using Aurora.View; using AuroraCellTracker; using AuroraIO; using AuroraLaser; using AuroraLaserScanner; using AuroraLBAD.Service; using CoreUnit.Common; using CoreUnit.Control.UserControl.Canvas; using CoreUnit.Scanner.UserControl.ScannerCanvas; using DB; using Hermes; using ILSOS_ACW_H52A.Models; using ILSOS_ACW_H52A.RMS; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Threading; using System.Windows; using WeldingCommon; using WeldingCommon.Alarm; using WeldingCommon.Devices; using WeldingCommon.Recipes; using WeldingCommon.Sequences; using WeldingCommon.Settings; using WeldingCommon.Utils; using static AuroraLBAD.Service.StreamLightLBAD; using static ILSOS_ACW_H52A.Device.IOController_ACW_H52A; using static ILSOS_ACW_H52A.Device.PLCControllerACWH52A; using static ILSOS_ACW_H52A.Device.ScannerListACWH52A; namespace ILSOS_ACW_H52A.Sequences { class SeqAutoRunACWH52A { static readonly Logger Log = new Logger("SeqAutoRunACWH52A"); const string SEQ_ENTER = "[SeqAutoRunACWH52A] SEQ_ENTER"; // inputParam 으로 받자. const string SEQ_CHECK_INIT_CONDITION = "[SeqAutoRunACWH52A] Check Init Condition"; // Check Init Condition const string SEQ_FORK_ENCODER_MONITOR = "[SeqAutoRunACWH52A] Fork Encoder Monitor"; const string SEQ_ENCODER_MONITOR = "[SeqAutoRunACWH52A] Check Encoder Monitor"; const string SEQ_JOIN_ENCODER_MONITOR = "[SeqAutoRunACWH52A] Join Encoder Monitor"; // TODO: [장비구현필요] 실제 공정 추가 const string SEQ_CHECK_LOAD_COMPLETE = "[SeqAutoRunACWH52A] Check Load Complete"; public const string SEQ_CHECK_SCANNER_LIST_COMPLETE = "[SeqAutoRunACWH52A] Check Scanner List Complete"; const string SEQ_READY_SCANNER_LIST = "[SeqAutoRunACWH52A] Ready ScannerList"; const string SEQ_SET_PARAMS_UPLOAD_WELDING_LIST_SEQ = "[SeqAutoRunACWH52A] Set Sequence Param (UploadWeldingList Seq)"; const string SEQ_DO_ID_UPLOAD_WELDING_LIST_SEQ = "[SeqAutoRunACWH52A] Process (UploadWeldingList Seq)"; const string SEQ_GET_RESULT_UPLOAD_WELDING_LIST_SEQ = "[SeqAutoRunACWH52A] Get Sequence Result (UploadWeldingList Seq)"; const string SEQ_SET_PARAMS_RECEIVE_CELL_INFO_SEQ = "[SeqAutoRunACWH52A] Set Sequence Param (ReceiveCellInfo Seq)"; const string SEQ_DO_ID_RECEIVE_CELL_INFO_SEQ = "[SeqAutoRunACWH52A] Process (ReceiveCellInfo Seq)"; const string SEQ_GET_PARAMS_RECEIVE_CELL_INFO_SEQ = "[SeqAutoRunACWH52A] Get Sequence Result (ReceiveCellInfo Seq)"; const string SEQ_EXECUTE_WELDING = "[SeqAutoRunACWH52A] Execute Welding"; const string SEQ_REQUEST_UNLOAD_CELL = "[SeqAutoRunACWH52A] Request Unload Cell"; //const string SEQ_WAIT_LBAD_JUDGE = "[SeqAutoRunACWH52A] Wait LBAD Judge"; const string SEQ_SET_PARAMS_REPORT_CELL_RESULT_SEQ = "[SeqAutoRunACWH52A] Set Sequence Param (ReportCellResult Seq)"; const string SEQ_DO_ID_REPORT_CELL_RESULT_SEQ = "[SeqAutoRunACWH52A] Process (ReportCellResult Seq)"; const string SEQ_GET_RESULT_REPORT_CELL_RESULT_SEQ = "[SeqAutoRunACWH52A] Get Sequence Result (ReportCellResult Seq)"; //const string SEQ_REPORT_CELLINFO_TO_PLC = "[SeqAutoRunACWH52A] ReportCellInfoToPLC"; const string SEQ_CHECK_CYCLE_STOP = "[SeqAutoRunACWH52A] Check Cycle Stop"; const string SEQ_CHECK_CAN_WELDED = "[SeqAutoRunACWH52A] Check Can Welded"; const string SEQ_SAVE_RESULT_DATA = "[SeqAutoRunACWH52A] Save Result Data"; const string SEQ_WRITE_COMPLETION_PLC_BIT = "[SeqAutoRunACWH52A] Write Completion PLC Bit"; const string SEQ_INIT_CONDITION_ERROR = "[SeqAutoRunACWH52A] Init Condition Error"; const string SEQ_COMPLETE = "[SeqAutoRunACWH52A] SEQ_COMPLETE"; // 완료 static class ContextKey { public const string KEY_SEQUENCE_RESULT = "KEY_SEQUENCE_RESULT_SeqAutoRunACWH52A"; public const string KEY_SEQUENCE_START_TIME = "KEY_SEQUENCE_START_TIME_SeqAutoRunACWH52A"; public const string KEY_SEQUENCE_ERROR_CODE = "KEY_SEQUENCE_ERROR_CODE_SeqAutoRunACWH52A"; } const int PLC_TIMEOUT = 60; // sec, Timeout은 시퀀스 정지가 아니라 타임아웃 시간마다 로그만 남기도록 한다 private Scanner _Scanner; private LaserDevice _Laser; private PLCController _PLC; private IODevice _IO; private StreamLightLBAD _StreamLightLBAD; private static CellTracker _CellTracker; private SystemParameter _SystemParameter; private ScannerCanvasUserCtrl _ScannerCanvasUserCtrl; //스캐너 동작관련 객체 선언 private string _LotId = "N/A"; private string _CellId = "N/A"; private int _ModelNo; private IRecipeManager _RecipeManager; private eListNo _WeldingScannerListNo; private List _SelectedDrawObjectList; private SeqAutoRunACWH52A_InitParam _InitParam; private uint _TodayProduct; private uint _TotalProduct; private double _LaserPowerByLBAD; private int _CompletedJigNo; private static bool isWeled; private bool _ModulationMode; private DateTime _AutoRunStartTime; private DateTime _WeldingStartTime; private TimeSpan _WeldingSpanTime; private DateTime _WeldEndTime; private TimeSpan _InterfaceSpanTime; private DateTime _InterfaceStartTime; private double _CellMeasureCenterOffsetX; private double _CellMeasureCenterOffsetY; // MOTF private TimeSpan _WeldInterval; private int _LastScannerParamNo = -1; //마지막 작업한 스캔 변수값들이 현재 작업하려는 스캔 변수값과 같은지 판단함(같으면 설정작업을 skip함) private int _EncoderOverflowCount = 0; private double _RotaryEncoderOffset = 0; private int _WeldingJigNo = -1; private int _WeldingCount = 0; private int _RotaryJigCount = 16; private double _WeldingStartAngle; private bool isTheFirstListWrite; private bool isTheSecondListWrite; private bool isTheFirstListExecute; private int _RecipeNo; private bool UsePlcShotThemAll { get; set; } = false; private bool UsePlcPositionCompensation { get; set; } = false; private string RecipeFileName { get; set; } = ""; private enum InitErrorList { SUCCESS = 0, LBAD_CLIENT_ACTIVATE_FAILED, LBAD_SUB_ACTIVATE_FAILED, LASER_INIT_ERROR, PLC_AUTORUN_BIT_OFF, MOTF_OFF, PLC_ALARM, } public SequenceBuilder Builder() { var sb = new SequenceBuilder(1000, true); sb.AddAction(SEQ_ENTER, Sequence_Enter); sb.AddDecision(SEQ_CHECK_INIT_CONDITION, CheckInitCondition); //분기 sb.AddFork(SEQ_FORK_ENCODER_MONITOR); sb.AddAction(SEQ_ENCODER_MONITOR, EncoderMonitor); sb.AddJoin(SEQ_JOIN_ENCODER_MONITOR); sb.AddAction(SEQ_CHECK_LOAD_COMPLETE, CheckLoadComplete); sb.AddAction(SEQ_CHECK_SCANNER_LIST_COMPLETE, CheckScannerListComplete); sb.AddDecision(SEQ_READY_SCANNER_LIST, ReadyScannerList); sb.AddAction(SEQ_SET_PARAMS_UPLOAD_WELDING_LIST_SEQ, SetUploadWeldingList_SeqParams); sb.AddSubsequence(SEQ_DO_ID_UPLOAD_WELDING_LIST_SEQ, SequenceManager.GetSeqBuilder_SeqUploadWeldingList()); sb.AddAction(SEQ_GET_RESULT_UPLOAD_WELDING_LIST_SEQ, GetUploadWeldingList_SeqResult); sb.AddAction(SEQ_SET_PARAMS_RECEIVE_CELL_INFO_SEQ, SetReceiveCellInfo_SeqParams); // Cell 정보를 받아와 입력한다. sb.AddSubsequence(SEQ_DO_ID_RECEIVE_CELL_INFO_SEQ, SequenceManager.GetSeqBuilder_SeqReceiveCellInfo()); // cell 정보를 받아오는 Sub Seq 실행 sb.AddAction(SEQ_GET_PARAMS_RECEIVE_CELL_INFO_SEQ, GetReceiveCellInfo_SeqResult); // Cell 정보를 가져온다 sb.AddAction(SEQ_EXECUTE_WELDING, ExecuteWelding); sb.AddAction(SEQ_REQUEST_UNLOAD_CELL, RequestUnloadCell); sb.AddAction(SEQ_SET_PARAMS_REPORT_CELL_RESULT_SEQ, SetReportCellResult_SeqParams); sb.AddSubsequence(SEQ_DO_ID_REPORT_CELL_RESULT_SEQ, SequenceManager.GetSeqBuilder_SeqReportCellResult()); sb.AddAction(SEQ_GET_RESULT_REPORT_CELL_RESULT_SEQ, GetReportCellResult_SeqResult); sb.AddDecision(SEQ_CHECK_CYCLE_STOP, CheckCycleStop); sb.AddDecision(SEQ_CHECK_CAN_WELDED, CheckCanWelded); sb.AddAction(SEQ_SAVE_RESULT_DATA, SaveResultData); // 해당 공정의 정보를 기록 함. sb.AddAction(SEQ_WRITE_COMPLETION_PLC_BIT, WriteCompletionPLCBit); // 정상적으로 종료 되면, complete bit write sb.AddAction(SEQ_INIT_CONDITION_ERROR, InitConditionError); sb.AddAction(SEQ_COMPLETE, Complete); sb.Flows(sb.Init > sb[SEQ_ENTER] > sb[SEQ_CHECK_INIT_CONDITION] > sb[SEQ_FORK_ENCODER_MONITOR] > sb[SEQ_CHECK_LOAD_COMPLETE] > sb[SEQ_SET_PARAMS_RECEIVE_CELL_INFO_SEQ] > sb[SEQ_DO_ID_RECEIVE_CELL_INFO_SEQ] > sb[SEQ_GET_PARAMS_RECEIVE_CELL_INFO_SEQ] > sb[SEQ_CHECK_SCANNER_LIST_COMPLETE] > sb[SEQ_READY_SCANNER_LIST] > sb[SEQ_SET_PARAMS_UPLOAD_WELDING_LIST_SEQ] > sb[SEQ_DO_ID_UPLOAD_WELDING_LIST_SEQ] > sb[SEQ_GET_RESULT_UPLOAD_WELDING_LIST_SEQ] > sb[SEQ_EXECUTE_WELDING] > sb[SEQ_REQUEST_UNLOAD_CELL] > sb[SEQ_CHECK_CAN_WELDED] > sb[SEQ_SET_PARAMS_REPORT_CELL_RESULT_SEQ] > sb[SEQ_DO_ID_REPORT_CELL_RESULT_SEQ] > sb[SEQ_GET_RESULT_REPORT_CELL_RESULT_SEQ] > sb[SEQ_SAVE_RESULT_DATA] > sb[SEQ_CHECK_CYCLE_STOP] > sb[SEQ_WRITE_COMPLETION_PLC_BIT] > sb[SEQ_JOIN_ENCODER_MONITOR] > sb[SEQ_COMPLETE]); sb.Flows(sb[SEQ_READY_SCANNER_LIST] > sb[SEQ_EXECUTE_WELDING]); sb.Flows(sb[SEQ_CHECK_CAN_WELDED] > sb[SEQ_CHECK_CYCLE_STOP]); // 용접을 하지 않은 Cell은 상위보고 하지 않는다. sb.Flows(sb[SEQ_CHECK_CYCLE_STOP] > sb[SEQ_CHECK_LOAD_COMPLETE]); sb.Flows(sb[SEQ_CHECK_INIT_CONDITION] > sb[SEQ_INIT_CONDITION_ERROR] > sb.Final); sb.Flows(sb[SEQ_FORK_ENCODER_MONITOR] > sb[SEQ_ENCODER_MONITOR] > sb[SEQ_JOIN_ENCODER_MONITOR] > sb[SEQ_COMPLETE]); #region //sb.Flows(sb[SEQ_FORK_ENCODER_MONITOR] > sb[SEQ_ENCODER_MONITOR]); ////sb.Flows(sb[SEQ_FORK_ENCODER_MONITOR] > sb[SEQ_CHECK_LOAD_COMPLETE]); //sb.Flows(sb[SEQ_JOIN_ENCODER_MONITOR] > sb[SEQ_COMPLETE]); /// //// 1. 시퀀스 시작 ~ 포크 //sb.Flows(sb.Init > sb[SEQ_ENTER] > sb[SEQ_CHECK_INIT_CONDITION] > sb[SEQ_FORK_ENCODER_MONITOR]); //// 2. Fork 이후 메인 루트 //sb.Flows(sb[SEQ_FORK_ENCODER_MONITOR] > sb[SEQ_CHECK_LOAD_COMPLETE]); //sb.Flows(sb[SEQ_CHECK_LOAD_COMPLETE] > sb[SEQ_CHECK_SCANNER_LIST_COMPLETE]); //sb.Flows(sb[SEQ_CHECK_SCANNER_LIST_COMPLETE] > sb[SEQ_READY_SCANNER_LIST]); //sb.Flows(sb[SEQ_READY_SCANNER_LIST] > sb[SEQ_SET_PARAMS_UPLOAD_WELDING_LIST_SEQ]); //sb.Flows(sb[SEQ_SET_PARAMS_UPLOAD_WELDING_LIST_SEQ] > sb[SEQ_DO_ID_UPLOAD_WELDING_LIST_SEQ]); //sb.Flows(sb[SEQ_DO_ID_UPLOAD_WELDING_LIST_SEQ] > sb[SEQ_GET_RESULT_UPLOAD_WELDING_LIST_SEQ]); //sb.Flows(sb[SEQ_GET_RESULT_UPLOAD_WELDING_LIST_SEQ] > sb[SEQ_EXECUTE_WELDING]); //sb.Flows(sb[SEQ_EXECUTE_WELDING] > sb[SEQ_REQUEST_UNLOAD_CELL]); //sb.Flows(sb[SEQ_REQUEST_UNLOAD_CELL] > sb[SEQ_CHECK_CYCLE_STOP]); //sb.Flows(sb[SEQ_CHECK_CYCLE_STOP] > sb[SEQ_SAVE_RESULT_DATA]); //sb.Flows(sb[SEQ_SAVE_RESULT_DATA] > sb[SEQ_WRITE_COMPLETION_PLC_BIT]); //sb.Flows(sb[SEQ_WRITE_COMPLETION_PLC_BIT] > sb[SEQ_JOIN_ENCODER_MONITOR]); //// 3. Fork 이후 병렬 브랜치 //sb.Flows(sb[SEQ_FORK_ENCODER_MONITOR] > sb[SEQ_ENCODER_MONITOR]); //sb.Flows(sb[SEQ_ENCODER_MONITOR] > sb[SEQ_JOIN_ENCODER_MONITOR]); //// 4. Join 이후 Complete //sb.Flows(sb[SEQ_JOIN_ENCODER_MONITOR] > sb[SEQ_COMPLETE]); //// 5. 에러 분기/종료 //sb.Flows(sb[SEQ_CHECK_INIT_CONDITION] > sb[SEQ_INIT_CONDITION_ERROR] > sb.Final); //// 6. 루프, 분기(선택적) //sb.Flows(sb[SEQ_READY_SCANNER_LIST] > sb[SEQ_EXECUTE_WELDING]); //sb.Flows(sb[SEQ_CHECK_CYCLE_STOP] > sb[SEQ_CHECK_LOAD_COMPLETE]); #endregion sb.OnStopped(OnStopped); sb.OnForceError(OnForceError); sb.Draw("SeqAutoRunACWH52A.pdf"); return sb; } private SequenceActionResult Sequence_Enter(SequenceContext context, string id, SequenceActionResult param) { Log.I("SeqAutoRunACWH52A Start"); Status.SetWorkingPosition(eWorkingPosition.CHECKING_CONDITION); _InitParam = param.Param as SeqAutoRunACWH52A_InitParam; _RecipeManager = Application.Current.AuroraEnv().RecipeManager; _SystemParameter = context.GetSetting(CommonSettingIds.SystemParameter); InitDev(context); if (!CheckDeviceReady(context)) { //Check Device //AlarmManager.Alarm(AlarmCode.ALARM_SEQUENCE_AutoRun_DeviceNotReady); Log.E("The devices are not active."); return SequenceAction.ForceStopByAlarm(AlarmCode.ALARM_SEQUENCE_AutoRun_DeviceNotReady); //return null; } if (InitSequenceVariable(context) == false) { //AlarmManager.Alarm(AlarmCode.ALARM_SEQUENCE_AutoRun_InitVariableError); return SequenceAction.ForceStopByAlarm(AlarmCode.ALARM_SEQUENCE_AutoRun_InitVariableError); //return null; } Thread.Sleep(1); // 부하 방지 return SequenceAction.Ok(); } private bool InitSequenceVariable(SequenceContext context) { _AutoRunStartTime = DateTime.Now; _TodayProduct = _SystemParameter.TodayProductCount; _TotalProduct = _SystemParameter.TotalProductCount; var ActivateRecipeName = _RecipeManager.ActivatedRecipe(); Log.I($"Current Activate Recipe, ID : {ActivateRecipeName.Id}, Name : {ActivateRecipeName.Name}"); var mainPage = WeldingRecipeManager.GetWeldingPage(); _ScannerCanvasUserCtrl = mainPage.ScannerCanvasCtrlAutoPage; _WeldingScannerListNo = eListNo.LIST1; _CellMeasureCenterOffsetX = 0.0; _CellMeasureCenterOffsetY = 0.0; var currentModelName = context.Recipes.ActivatedModelName(); var modelItem = context.GetModel(currentModelName, CommonModelIds.EquipmentSpecificModel); _ModelNo = modelItem.Model_No; return true; } private void InitDev(SequenceContext context) { //Device Init _Scanner = context.GetLaserScanner(); _Laser = context.GetLaser(); if (_InitParam.UseStream) { _StreamLightLBAD = new StreamLightLBAD(); _StreamLightLBAD._Client = context.GetStreamLightClient(); _StreamLightLBAD._Sub = context.GetStreamLightSub(); } _CellTracker = context.GetCellTracker(); _PLC = context.GetPLC(); _IO = context.GetIOUnit(); } private bool CheckDeviceReady(SequenceContext context) { List deviceListToCheck = new List() { CommonDeviceIds.Scanner, CommonDeviceIds.Laser, CommonDeviceIds.PLCDevice, CommonDeviceIds.IODevice }; if (_InitParam.UseStream) { deviceListToCheck.Add(CommonServiceIds.StreamLightLBADClient); deviceListToCheck.Add(CommonServiceIds.StreamLightLBADSub); } bool isAllDeviceActivated = true; foreach (string item in deviceListToCheck) { if (!context.Devices[item].IsActivated) { isAllDeviceActivated = isAllDeviceActivated && false; } } return isAllDeviceActivated; } private bool CheckCondition(SequenceContext context) { if (CheckCondition_Interlock()) { int InitializeCode = InitializeDeviceStatus(context); if (InitializeCode == (int)InitErrorList.SUCCESS) return true; else context.PutValue(ContextKey.KEY_SEQUENCE_ERROR_CODE, InitializeCode); } return false; } private bool CheckCondition_Interlock() { // PLC, Data, IO 등 Interlock checking // Config checking //if (_Scanner.Config.UseMarkingOnTheFly == false) //{ // Log.E("ERROR Config Setting : Scanner Marking on the fly (OFF)"); // return false; //} return true; } private int InitializeDeviceStatus(SequenceContext context) { // PLC Interface AutoStatus Start // Won : PC의 AutoStatus 가 True 되는 순간 Rotary가 돌기 시작한다. // Rotary 돌기 시작 한 후 Encoder 값을 읽으면 안 되므로 정지상태에서 읽은 후 AutoStatus true 하도록 // InitAutoRunBit 시점 변경 //InitAutoRunBit(); // LBAD Initialize // LBAD 는 server 이기 때문에, 만약 끊어졌으면 다시 접속이 필요함 if (_SystemParameter.BypassModeLBAD == false) { if (!_StreamLightLBAD._Client.IsActivated) { int ret = _StreamLightLBAD._Client.Activate(); Thread.Sleep(500); if (ret != E1.OK) { Log.E($"StreamLightClient activate failed"); return (int)InitErrorList.LBAD_CLIENT_ACTIVATE_FAILED; } } if (_StreamLightLBAD._Sub.IsActivated == false) { int ret = _StreamLightLBAD._Sub.Activate(); Thread.Sleep(500); if (ret != E1.OK) { Log.E($"StreamLightSub activate failed"); return (int)InitErrorList.LBAD_SUB_ACTIVATE_FAILED; } } _StreamLightLBAD.Initialize(); if (_StreamLightLBAD.SendReadyStatus(eRequestData.Ready_On) == false) { Log.I(string.Format($"[LBAD Interface]CheckReadyStatus null")); //return false; } } // Laser Initialize _Laser.PilotLaserOff(); _Laser.LaserOn(); Thread.Sleep(500); if (!_Laser.GetLaserReady()) { Log.E("ERROR Initialize Laser"); return (int)InitErrorList.LASER_INIT_ERROR; } // Scanner Initialize if (_Scanner.GetModulationMode()) { _ModulationMode = true; } else { _ModulationMode = false; } //// PLC Initialize AutoRun Bit 최소화 작업 진행 if (_PLC.ReadBit((int)eBitInput.PLC_AUTO_STATUS).data == false) // AutoRun Bit가 OFF인 경우 { if (_PLC.ReadBit((int)eBitInput.PLC_AUTO_REQ).data == false) // AutoRun Request 상태 체크 { Log.E(string.Format("ERROR Initialize PLC, PLC Alarm ON")); // AutoRun Request와 AutoRun Bit 모두 OFF인 경우, PLC 측에서 Alarm 발생함. return (int)InitErrorList.PLC_ALARM; } else { Log.E(string.Format("ERROR Initialize PLC, Bit Off PLC_AUTO_STATUS")); // AutoRun Request가 ON인데 AutoRun Bit 모두 OFF인 경우, PLC에서 AutoRun Bit를 켜지 않음. return (int)InitErrorList.PLC_AUTORUN_BIT_OFF; } } // Marking On The Flying Off if (_Scanner.Config.UseMarkingOnTheFly == false) { Log.E("ERROR Config Setting : Scanner Marking on the fly (OFF)"); return (int)InitErrorList.MOTF_OFF; } // MOTF Set Scanner List _Scanner.SelectCorrectionTable(2); Log.I(string.Format("Normal, Select Laser Correction File")); // Aurora 내부에서 찾아야할 듯 함 //UsePlcShotThemAll = Dao.SystemRecipe.PlcShotThemAll; //UsePlcPositionCompensation = Dao.SystemRecipe.PlcPositionCompensationUse; string[] strArr = Dao.SystemRecipe.WorkRecipeFile.Split('\\'); RecipeFileName = strArr[strArr.Length - 1]; _LastScannerParamNo = -1; // 트럼프 TruDisk를 쓰기 위해서는 SetFirstPulseKiller를 사용해야 함. // Rotary는 시작 트리거 미사용 _Scanner.SetControlMode(0); UtilUnit.logUnit.SetLog(LogUnit.eLogType.DEBUG, MethodBase.GetCurrentMethod().Name, "[AutoRun] 외부 시작 트리거 DISABLE"); #region MOTF Absolute Encoder 기준으로 초기화 // Set Rotate Center는 Ctrl명령, 처음 한번만 실행 _Scanner.SetRotateCenter(_Scanner.Config.MarkingOnTheFlyRotateCenterX, _Scanner.Config.MarkingOnTheFlyRotateCenterY); _Scanner.InitFly2D(); // X, Y 엔코더 리셋 _EncoderOverflowCount = 0; // 현재 정지상태의 Rotary 엔코더 값을 읽어와서 저장 _RotaryEncoderOffset = GetRotaryCurrentPosition(); // m_abwRotaryEncoderOffset의 범위는 0 이상 360 미만 InitRotaryOnTheFlyWeldingCount(_RotaryEncoderOffset, _RotaryJigCount, out _WeldingCount, out _WeldingJigNo); Log.I(string.Format($"ScannerUnit.InitRotaryOnTheFlyWeldingCount : m_abwRotaryEncoderOffset({_RotaryEncoderOffset}), weldingCount({_WeldingCount}), weldingJigNo({_WeldingJigNo}), rotaryJigCount({_RotaryJigCount})")); // 용접 완료 대기 스캐너 List 리셋 ResetInputScannerList(); WaitingScannerList.Reset(); CompleteScannerList.Reset(); isTheFirstListWrite = false; // 작업 시작 시 처음 2개의 List는 입력하고 시작한다 isTheSecondListWrite = false; isTheFirstListExecute = true; // 제일 첫 List는 ExecuteList 필요. 이후는 AutoChange InitAutoRunBit(); // GetRotaryCurrentPosition() 호출 이후 AutoStatus True 변경 #endregion return (int)InitErrorList.SUCCESS; } private void InitAutoRunBit() { _PLC.WriteBit((int)eBitOutput.PC_AUTO_STATUS, true); Log.I(string.Format($"[PLCInterface]SET PC_AUTO_STATUS True")); } private double GetRotaryCurrentPosition() { try { //double finalValue = Convert.ToDouble(_plc.GetLongAIValue("PLC_CURRENT_POS_1") / 100000f); double finalValue = Convert.ToDouble(_PLC.ReadDWord((int)eWordInput.PLC_CURRENT_POS_1).data / 100000f); finalValue += _Scanner.Config.RotaryEncoderOffset; if (360.0 <= finalValue) { while (360.0 <= finalValue) { finalValue -= 360.0; } } else if (finalValue < 0) { while (finalValue < 0) { finalValue += 360.0; } } return finalValue; } catch { return -9999.9999; } } /// /// 현재 Rotary의 회전 각도에서, 현재 용접 카운트와 지그 번호를 계산. 추가로 Overflow 카운트도 초기화 /// /// Rotary 회전 Offset. 운전 시작 시 Rotary가 지그 1번 정중심에서부터 시작했을 경우가 0. 범위는 0이상 /// Rotary 내 지그 갯수임. 장비 및 T/T에 따라서, 360도 당 지그의 개수는 다를 수 있으므로. /// Rotary Welding Count. 30도 구간 하나가 지나갈 때마다 +1 /// 현재 '용접 중' 구간에 들어가 있는 지그 번호 public void InitRotaryOnTheFlyWeldingCount(double rotaryAngleOffset, int rotaryJigCount, out int weldingCount, out int weldingJigNo) { // 현재 로터리 엔코더 값을 기준으로 현재 카운트, 지그 번호를 확인 double anglePerOneJig = 360 / (double)rotaryJigCount; //double로 바꿔서 계산해야 22가 아닌 22.5로 anglePerOneJig가 계산됨 25.08.02 weldingCount = (int)((rotaryAngleOffset + anglePerOneJig / 2) / anglePerOneJig); // _WeldingCount = 0, 1, 2, ... weldingJigNo = (int)(weldingCount % rotaryJigCount) + 1; // _WeldingJigNo = 1, 2, ... , 12 Log.I($"Angle Per One Jig : {anglePerOneJig}"); } private string CheckInitCondition(SequenceContext context, string id, ICollection branches, SequenceActionResult param) { 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_INIT_CONDITION_ERROR; } Status.EquipmentMode = eEquipmentMode.AUTO; var startTime = DateTime.Now; context.PutValue(ContextKey.KEY_SEQUENCE_START_TIME, startTime); Thread.Sleep(1); // 부하 방지 Log.D("ReceiveCellInfo Sequence CheckInitCondition"); return SEQ_FORK_ENCODER_MONITOR; //return SEQ_CHECK_LOAD_COMPLETE; } #region TODO: [장비구현필요] // TODO: [장비구현필요] 공정 Node 구현 // 지그 상태 확인 private SequenceActionResult CheckLoadComplete(SequenceContext context, string id, SequenceActionResult param) { Status.SetWorkingPosition(eWorkingPosition.LOADING_CELL); // MOTF WaitScannerLoadListEnable // List가 실행 중이라면(ex : 엔코더 대기 상태 등), 반환값은 0, LoadList 할 수 없는 상태(List 실행 중이거나, 엔코더 대기 상태) // LoadList가 0보다 크면 다음 시퀀스 진행해야 함 _InterfaceStartTime = DateTime.Now; while (!context.Stopped) { if (_Scanner.LoadList(_WeldingScannerListNo) > 0) { // List가 실행 상태가 아니라면(ex : 시작을 안 했거나, 완료되었으면) 반환값은 1 또는 2(리스트 번호) Log.I(string.Format($"LoadList function success : List {_WeldingScannerListNo}")); break; } _InterfaceSpanTime = DateTime.Now.Subtract(_InterfaceStartTime); if (_InterfaceSpanTime.TotalMilliseconds > (PLC_TIMEOUT * 1000)) { Log.E("TIMEOUT_Scanner_LoadList"); _InterfaceStartTime = DateTime.Now; } Thread.Sleep(10); } Thread.Sleep(1); // 부하 방지 return SequenceAction.Ok(); } private SequenceActionResult CheckScannerListComplete(SequenceContext context, string id, SequenceActionResult param) { if (isTheFirstListWrite) // 작업 시작 시, 첫번째 리스트는 바로 쓰고, 바로 시작한다(ExecuteList). 다만 List 명령에 의해 Encoder가 들어올 때까지 대기. { isTheFirstListWrite = false; Log.I("Write the 1st scanner list"); } else { if (isTheSecondListWrite) // 두번째 리스트는 바로 쓰지만, 첫번째 리스트 실행이 완료되면 자동으로 이어서 실행된다(AutoChange) { isTheSecondListWrite = false; Log.I("Write the 2nd scanner list"); } else { isWeled = false; // 첫번째나 두번째가 아니라면, 실제 리스트 실행이 끝난 것이기 때문에 완료 처리가 필요 CompleteScannerList = InputScannerList[(int)_WeldingScannerListNo - 1]; if (!CheckCurrentEncoderAngle(CompleteScannerList.StartAngle)) { // 에러 상황(현재 Rotary Encoder 값이 정상적인 스캐너 리스트 각도 범위를 벗어남) Log.I("ERR_SCANNER_LIST_PROCESSING_TIMEOVER"); return SequenceAction.Error(); // WON : 2칸 전에 용접을 진행하지 않아 현재 Angle이 CompleteScannerList.StartAngle + 45 범위를 벗어난 경우라면 에러 상황이 아니므로 에러로 처리 할 필요가 없다 // 위에 CheckCurrentEncoderAngle if문 조건 대신 아래 내용으로 테스트 해본 뒤에 적용 필요 //if (!CheckCurrentEncoderAngle(CompleteScannerList.StartAngle)) { // // 에러 상황(현재 Rotary Encoder 값이 정상적인 스캐너 리스트 각도 범위를 벗어남) // if (CompleteScannerList.CanExist) { // Log.I("ERR_SCANNER_LIST_PROCESSING_TIMEOVER"); // return SequenceAction.Error(); // } // return SequenceAction.Ok(); } else { if (CompleteScannerList.CanExist) { isWeled = true; #region 용접 시간, 엔코더 측정/기록 //_WeldInterval = DateTime.Now - _WeldEndTime; //_WeldEndTime = DateTime.Now; //double elapsedTime = _Scanner.GetTime(); //_WeldingSpanTime = TimeSpan.FromSeconds(elapsedTime); // Encoder 대기 넘어간 후의, 실제 스캐너 리스트 실행 시간 //_WeldingStartTime = _WeldEndTime.Subtract(_WeldingSpanTime); double elaspedTime = _Scanner.GetTime(); if (CompleteScannerList.WeldComplete) { _WeldInterval = DateTime.Now - _WeldEndTime; _WeldEndTime = DateTime.Now; _WeldingStartTime = CompleteScannerList.WeldStartTime; _WeldingSpanTime = TimeSpan.FromSeconds(elaspedTime); _LotId = CompleteScannerList.LotId; _CellId = CompleteScannerList.CellId; Log.I(string.Format("JigNo : {0}, Weld start : {1}, Weld End : {2}, WeldTime : {3}, interval : {4:F3}", CompleteScannerList.JigNo, _WeldingStartTime.ToString("HH:mm:ss.fff"), _WeldEndTime.ToString("HH:mm:ss.fff"), _WeldingSpanTime.TotalMilliseconds, _WeldInterval.TotalSeconds)); if (_PLC.ReadBit((int)eBitInput.PLC_USE_WELD_JIG_1 + CompleteScannerList.JigNo - 1).data) { _PLC.WriteBit((int)eBitOutput.PC_WELD_COMPLETE_1 + CompleteScannerList.JigNo - 1, true); Log.I(string.Format($"PC_WELD_COMPLETE_{CompleteScannerList.JigNo} -> true)")); } UpdateProductCount(context); UpdateProductInfoForView(); SaveCellAPD(); } //if (_SystemParameter.BypassModeLBAD == false) { // if (_StreamLightLBAD.SendWeldingStop() == false) { // Log.I(string.Format($"[LBAD Interface]SendWeldingStop null")); // //return null; // } //} UtilUnit.logUnit.SetLog(LogUnit.eLogType.PRODUCT, MethodBase.GetCurrentMethod().Name, "UPDATE_WELDING_TIME;" + _WeldingSpanTime.TotalMilliseconds); UtilUnit.logUnit.SetLog(LogUnit.eLogType.PRODUCT, MethodBase.GetCurrentMethod().Name, "UPDATE_PRODUCT_COUNT;" + _WeldEndTime.ToString("yyyy-MM-dd HH:mm:ss")); double encoder0_0, encoder1_0, encoder0_1, encoder1_1; _Scanner.ReadEncoder(out encoder0_0, out encoder1_0, out encoder0_1, out encoder1_1, true); double startEncoderCount = encoder0_0; double endEncoderCount = encoder0_1; double angularVelocity = (endEncoderCount - startEncoderCount) / elaspedTime; // 단위는 [degree/sec] Log.I(string.Format("Encoder start : {0:F4}, end : {1:F4}, vel : {2:F2}", startEncoderCount, endEncoderCount, angularVelocity)); #endregion #region 스캐너 리스트 Record 파일 기록 if (_Scanner.Config.UseRecordSignal) { String RecordFileName = _RecipeManager.ActivatedRecipe().Name + "_Jig#" + CompleteScannerList.JigNo.ToString(); string DataFilePath = ProjectUtil.GetDataLogSetting().GetFilePath_ScannerRecordLog(RecordFileName, true); _Scanner.SaveRecord(DataFilePath); } #endregion } } } } return SequenceAction.Ok(); } public bool CheckCurrentEncoderAngle(double startAngle) { double encoderX, currentAngle; _Scanner.GetEncoder(out encoderX, out currentAngle, true); if (0 < startAngle && currentAngle < 0) { // Encoder 반전되는 순간이며, 이 때만 인터락 무시 } else if (startAngle <= currentAngle) { if (currentAngle < startAngle + 45)// 다음 리스트 구간이 끝나기 전까지는 완료가 되어야 한다 { Log.I(string.Format("Current encoder check OK, startAngle : {0}, currentAngle : {1})", startAngle, currentAngle)); return true; } else Log.I(string.Format("Current encoder check NG(Too late), startAngle : {0}, currentAngle : {1})", startAngle, currentAngle)); } else Log.I(string.Format("Current encoder check NG(Too early), startAngle : {0}, currentAngle : {1})", startAngle, currentAngle)); return false; } private string ReadyScannerList(SequenceContext context, string id, ICollection branches, SequenceActionResult param) { Status.SetWorkingPosition(eWorkingPosition.UPLOADING_WELD_LIST); //_SelectedDrawObjectList = _ScannerCanvasUserCtrl.Model.Layers[0].Objects; #region List 입력 시점의 엔코더 카운트 로그 기록 double encoderX, encoderY; _Scanner.GetEncoder(out encoderX, out encoderY, true); UtilUnit.logUnit.SetLog(LogUnit.eLogType.SYSTEM, MethodBase.GetCurrentMethod().Name, string.Format("Encoder X, Y, Offset, Y-X+Offset : {0}, {1}, {2}, {3}", encoderX.ToString("F5"), encoderY.ToString("F5"), _RotaryEncoderOffset.ToString("F5"), (encoderY - encoderX + _RotaryEncoderOffset).ToString("F5"))); #endregion // 스캐너 위치 초기화 _Scanner.Fly_Return(_Scanner.Config.MarkingOntheFlyParkingPositionX, _Scanner.Config.MarkingOntheFlyParkingPositionY); #region List 입력(엔코더 조건 만족 대기) _WeldingCount++; double compensateEncoderReset = 0.015; // 엔코더 리셋 보상 각도 설정(70도/sec 기준 약 0.015도) WaitForEncoderNextRotarySection(_WeldingCount, _RotaryJigCount, _RotaryEncoderOffset, compensateEncoderReset, out _WeldingStartAngle); Log.I(string.Format("ScannerUnit.WaitForEncoderNextRotarySection : m_weldingCount({0}),m_abwRotaryEncoderOffset({1:F3}), compensateEncoderReset({2:F3}), overflowCount({3}), weldingStartAngle({4:F3})", _WeldingCount, _RotaryEncoderOffset, compensateEncoderReset, _EncoderOverflowCount, _WeldingStartAngle)); #endregion // 3개임으로 3으로 작성 #region List 입력(용접 좌표 입력) if (_WeldingJigNo == _RotaryJigCount) _WeldingJigNo = 1; else _WeldingJigNo++; bool canExist; // 지그 용접 수행 여부 확인 if (UsePlcShotThemAll) { canExist = true; Log.I("Settings - PLC : Shot Them All Mode"); } else canExist = GetRotaryCanExist(_WeldingJigNo); Log.I("Can Exist : " + canExist.ToString() + ", JigNo " + _WeldingJigNo + " ,CountNo " + _WeldingCount + ", StartAngle(Y) : " + _WeldingStartAngle); // 입력한 Scanner List를 완료대기열에 추가 //SetInputScannerList(_WeldingScannerListNo, DateTime.Now, _WeldingCount, _WeldingStartAngle, _WeldingJigNo, _RotaryJigCount, canExist); //if (canExist) // Can이 존재할 경우만 리스트 안에 내용이 들어가며, 그렇지 않은 경우는 빈 리스트가 된다 //{ // #region 용접할 Objects 선택 // if (UsePlcPositionCompensation && _WeldingJigNo - 1 < _ScannerCanvasUserCtrl.Model.Layers.Length) { // _SelectedDrawObjectList = _ScannerCanvasUserCtrl.Model.Layers[_WeldingJigNo - 1].Objects; // Log.I(string.Format("Page 불러오기 : [{0}])", _WeldingJigNo - 1)); // } else { // _SelectedDrawObjectList = _ScannerCanvasUserCtrl.Model.Layers[0].Objects; // Log.I(string.Format("Page 불러오기 : [0])")); // } // #endregion // // 여기서 어디로 가는지 설정 필요함 // return SEQ_SET_PARAMS_UPLOAD_WELDING_LIST_SEQ; //} //// 기존 소스는 UploadWeldingList 건너 뛰고 SEQ_EXECUTE_WELDING 으로 바로 감 //// SEQ_WAIT_WELDING_START 의 기능이 어떻게 되는 지 확인 후 //// 건너뛰어도 되면 SEQ_EXECUTE_WELDING 으로 변경 해도 됨 //return SEQ_WAIT_WELDING_START; if (canExist) { #region 용접할 Objects 선택 if (_WeldingJigNo - 1 < _ScannerCanvasUserCtrl.Model.Layers.Length) { _SelectedDrawObjectList = _ScannerCanvasUserCtrl.Model.Layers[_WeldingJigNo - 1].Objects; Log.I(string.Format("Page 불러오기 : [{0}])", _WeldingJigNo - 1)); } else { _SelectedDrawObjectList = _ScannerCanvasUserCtrl.Model.Layers[0].Objects; Log.I(string.Format("Page 불러오기 : [0])")); } #endregion // 입력한 Scanner List를 완료대기열에 추가 // Won : GetProcessingWeldingData().WorkLotId 는 CellTrakcer로 부터 받은 상태의 LotId // GetProcessingWeldingData().LotId 는 AutorunView에 표기할 용접이 완료 된 LotID string LotId = Status.GetProcessingWeldingData().WorkLotId; // CellTracker로 부터 WorkLotID, WorkCellID 받는 구문 추가 후 해당 데이터로 입력 필요 string CellId = Status.GetProcessingWeldingData().WorkCellId; SetInputScannerList(_WeldingScannerListNo, DateTime.Now, _WeldingCount, _WeldingStartAngle, _WeldingJigNo, _RotaryJigCount, canExist, LotId, CellId); _RecipeNo = _RecipeManager.ActivatedRecipe().Id; SetWeldingData(); } else { return SEQ_EXECUTE_WELDING; } return SEQ_SET_PARAMS_UPLOAD_WELDING_LIST_SEQ; #endregion } private ScannerParam GetSelectedDrawObjectData(IDrawObject DrawObj) { if (DrawObj is Line lineItem) return WeldingRecipeManager.GetScannerParam(lineItem.ScannerParamNo); else if (DrawObj is Arc arcItem) return WeldingRecipeManager.GetScannerParam(arcItem.ScannerParamNo); else if (DrawObj is Group groupItem) return WeldingRecipeManager.GetScannerParam(groupItem.ScannerParamNo); return null; } private void SetWeldingData() { ScannerParam laserParam = null; laserParam = GetSelectedDrawObjectData(_SelectedDrawObjectList[0]); if (laserParam != null) { // EDC 보내는 용도 Status.GetProcessingWeldingData().LaserPowerCore = laserParam.LaserPower1.ToString(); Status.GetProcessingWeldingData().MarkSpeed = laserParam.MarkSpeed.ToString(); Status.GetProcessingWeldingData().FocalLength = (_PLC.ReadDWord((int)eWordInput.PLC_WELDING_FOCAL_LENGTH).data / 1000f).ToString(); Status.GetProcessingWeldingData().WeldingJigNo = _WeldingJigNo.ToString(); } } public void WaitForEncoderNextRotarySection(int weldingCount, int rotaryJigCount, double rotaryAngleOffset, double encoderResetOffset, out double sectionStartAngle) { // 한 바퀴당 지그 숫자에 따른 변경 double encoderAllowedMinDegree = Int32.MinValue / _Scanner.Config.MarkingOnTheFlyEncoderResolutionRotate * 360.0; double encoderAllowedMaxDegree = Int32.MaxValue / _Scanner.Config.MarkingOnTheFlyEncoderResolutionRotate * 360.0; double anglePerOneJig = (double)(360 / (double)rotaryJigCount); // 아래는 지그 당 각도가 30도라는 가정하의 수식임. // 1.1. 시작 각도 = (현재 지그 번호 * 30도) -15도 - 초기화 엔코더 값 - 엔코더 리셋 보상값 // rotaryAngleOffset = x가 0도 이상 15도 미만일 때, weldingCount = 1부터 시작, sectionStartAngle = 15 - x에서 30씩 증가 // rotaryAngleOffset = x가 15도 이상 45도 미만일 때, weldingCount = 2부터 시작, sectionStartAngle = 30 - x에서 30씩 증가 // rotaryAngleOffset = x가 45도 이상 75도 미만일 때, weldingCount = 3부터 시작, sectionStartAngle = 30 - x에서 30씩 증가 // rotaryAngleOffset = x가 315도 이상 345도 미만일 때, weldingCount = 12부터 시작, sectionStartAngle = 30 - x에서 30씩 증가 // rotaryAngleOffset = x가 345도 이상 360도 미만일 때, weldingCount = 13부터 시작, sectionStartAngle = 30 - x에서 30씩 증가 sectionStartAngle = weldingCount * anglePerOneJig - (anglePerOneJig / 2) - rotaryAngleOffset - encoderResetOffset - _EncoderOverflowCount * (encoderAllowedMaxDegree - encoderAllowedMinDegree); // 1.2. 계산된 시작각도가 이미 지나버렸으면 알람 발생 필요 // 1.3. Overflow가 나는 숫자라면, 보상할 것. if (sectionStartAngle > encoderAllowedMaxDegree) { sectionStartAngle -= encoderAllowedMaxDegree - encoderAllowedMinDegree; _EncoderOverflowCount++; } double sectionEndAngle = (encoderAllowedMaxDegree < sectionStartAngle + anglePerOneJig) ? encoderAllowedMaxDegree : sectionStartAngle + anglePerOneJig; _Scanner.WaitForEncoderRotateRange(encoderAllowedMinDegree, encoderAllowedMaxDegree, sectionStartAngle, sectionEndAngle); } public bool GetRotaryCanExist(int jigNo) { #region 지그 16개 요청기록 로그 남기기, 빠져나간 캔에 대한 Complete Bit 초기화 string result = ""; for (int i = 1; i <= _RotaryJigCount; i++) { if (_PLC.ReadBit((int)eBitInput.PLC_USE_WELD_JIG_1 + i).data) result += "1, "; else { result += "0, "; string outId = string.Format("PC_WELD_COMPLETE_{0}", i); if (_PLC.ReadBit((int)eBitInput.PLC_USE_WELD_JIG_1 + i).data) { _PLC.WriteBit((int)eBitOutput.PC_WELD_COMPLETE_1 + i, false); Log.I(string.Format($"PC_WELD_COMPLETE_{1 + i} -> FALSE)")); } } } //UtilUnit.logUnit.SetLog(LogUnit.eLogType.DEBUG, MethodBase.GetCurrentMethod().Name, string.Format("PLC -> PC 용접 Cmd : ({0})", result)); #endregion if (jigNo <= _RotaryJigCount) //return m_rscPlc.GetDIStatus(string.Format("PLC_USE_WELD_JIG_{0}", jigNo)); return _PLC.ReadBit((int)eBitInput.PLC_USE_WELD_JIG_1 + (jigNo - 1)).data; else return false; } private SequenceActionResult SetUploadWeldingList_SeqParams(SequenceContext context, string id, SequenceActionResult param) { //현재 시퀀스에서 LoadList를 해주기 때문에 Sub 시퀀스에 IncludeListStartEnd를 false로 넘겨줘야 함 SeqUploadWeldingList_InitParam seqParams = new SeqUploadWeldingList_InitParam() { NeedSetLaserTiming = _ModulationMode, IncludeListStartEnd = false, CellMeasureCenterOffsetX = _CellMeasureCenterOffsetX, CellMeasureCenterOffsetY = _CellMeasureCenterOffsetY, SelectedDrawObjectList = _SelectedDrawObjectList, WeldingScannerListNo = _WeldingScannerListNo, Rotation_Degree = 11.25, }; return SequenceAction.Ok(seqParams); } private SequenceActionResult GetUploadWeldingList_SeqResult(SequenceContext context, string id, SequenceActionResult param) { var resultOutParam = param.Param as SeqUploadWeldingList_OutParam; if (resultOutParam.Success == false) { //AlarmManager.Alarm(AlarmCode.ALARM_SEQUENCE_UploadWeldingList_ResultFailed); return SequenceAction.ForceStopByAlarm(AlarmCode.ALARM_SEQUENCE_UploadWeldingList_ResultFailed); //return null; } return SequenceAction.Ok(); } private SequenceActionResult SetReceiveCellInfo_SeqParams(SequenceContext context, string id, SequenceActionResult param) { SeqReceiveCellInfo_InitParam seqParams = new SeqReceiveCellInfo_InitParam() { }; return SequenceAction.Ok(seqParams); } private SequenceActionResult GetReceiveCellInfo_SeqResult(SequenceContext context, string id, SequenceActionResult param) { var resultOutParam = param.Param as SeqReceiveCellInfo_OutParam; if (resultOutParam.Success == false) { return null; } return SequenceAction.Ok(); } private SequenceActionResult ExecuteWelding(SequenceContext context, string id, SequenceActionResult param) { Status.SetWorkingPosition(eWorkingPosition.EXECUTING_WELD); // MOTF Welding // List 닫기 _Scanner.SetEndList(); #region List 실행(처음엔 ExecuteList, 다음부터 AutoChangeList, 이후 대상 리스트 교체 (1 <-> 2)) if (isTheFirstListExecute) { isTheFirstListExecute = false; _Scanner.ExecuteList(_WeldingScannerListNo); Log.I(string.Format($"START Welding : ExecuteList_{_WeldingScannerListNo}")); } else { _Scanner.AutoChange(); Log.I(string.Format($"Next upload list : uploadList_{_WeldingScannerListNo}")); } _WeldingScannerListNo = (_WeldingScannerListNo != eListNo.LIST1) ? eListNo.LIST1 : eListNo.LIST2; #endregion return SequenceAction.Ok(); } private SequenceActionResult RequestUnloadCell(SequenceContext context, string id, SequenceActionResult param) { Status.SetWorkingPosition(eWorkingPosition.UNLOADING_CELL); return SequenceAction.Ok(); } private SequenceActionResult EncoderMonitor(SequenceContext context, string id, SequenceActionResult param) { int EncoderLogCount = 0; double send_time_offset = 0.1; string LotID = "", CellID = ""; while (!context.Stopped) { double encoderX, currentAngle; _Scanner.GetEncoder(out encoderX, out currentAngle, true); double CurrentPosPLC = GetRotaryCurrentPosition(); bool WeldingInPosition = encoderX > _Scanner.Config.MarkingRangeMinRotate + 11.25 - send_time_offset - 0.1 && encoderX < _Scanner.Config.MarkingRangeMinRotate + 11.25 - send_time_offset + 0.1; if (WeldingInPosition) { if (InputScannerList[(int)_WeldingScannerListNo - 1].CanExist) { Log.I($"Fork Before Min Rotate Encoder X : ({encoderX}), Encoder Y : ({currentAngle}), PLC Rotary : ({CurrentPosPLC}), List {_WeldingScannerListNo}, JigNo : ({InputScannerList[(int)_WeldingScannerListNo - 1].JigNo})"); if (!_SystemParameter.BypassModeLBAD) { if (InputScannerList[(int)_WeldingScannerListNo - 1].CheckSendCellinfo == false) { LotID = InputScannerList[(int)_WeldingScannerListNo - 1].LotId; CellID = InputScannerList[(int)_WeldingScannerListNo - 1].CellId; if (currentAngle > InputScannerList[(int)_WeldingScannerListNo - 1].StartAngle) { InputScannerList[(int)_WeldingScannerListNo - 1].CheckSendCellinfo = _StreamLightLBAD.SendCellInfo(LotID, CellID, _RecipeNo, _ModelNo, InputScannerList[(int)_WeldingScannerListNo - 1].JigNo); if (InputScannerList[(int)_WeldingScannerListNo - 1].CheckSendCellinfo == false) Log.I($"[LBAD Interface]SendCellInfo null, LotID [{LotID}], CellID [{CellID}], JigNo [{InputScannerList[(int)_WeldingScannerListNo - 1].JigNo}]"); else Log.I($"[StreamLight]SendCellInfo LotID [{LotID}], CellID [{CellID}], JigNo [{InputScannerList[(int)_WeldingScannerListNo - 1].JigNo}]"); } } } } } if (_IO._iOUnit.ReadDiBit(Convert.ToInt32(eADIAddress.LaserOn))) { if (InputScannerList[(int)_WeldingScannerListNo - 1].WeldStartTime == DateTime.MinValue && InputScannerList[(int)_WeldingScannerListNo - 1].CanExist) { InputScannerList[(int)_WeldingScannerListNo - 1].WeldStartTime = DateTime.Now; InputScannerList[(int)_WeldingScannerListNo - 1].WeldComplete = true; EncoderLogCount = 0; Log.I($"Laser On, Welding Complete. JigNo : ({InputScannerList[(int)_WeldingScannerListNo - 1].JigNo})"); } if (EncoderLogCount == 0) { Log.I($"Fork Laser On Encoder X : ({encoderX}), Encoder Y : ({currentAngle}), PLC Rotary : ({CurrentPosPLC}), List {_WeldingScannerListNo}, JigNo : ({InputScannerList[(int)_WeldingScannerListNo - 1].JigNo})"); } EncoderLogCount = (EncoderLogCount + 1) % 7; } for (int i = 0; i < _RotaryJigCount; i++) { if (_PLC.ReadBit((int)eBitInput.PLC_USE_WELD_JIG_1 + i).data == false) { _PLC.WriteBit((int)eBitOutput.PC_WELD_COMPLETE_1 + i, false); } } _PLC.WriteDWord((int)eWordOutput.PC_CURRENT_POS, (int)((currentAngle + _RotaryEncoderOffset) % 360 * 100000f)); Thread.Sleep(1); // 부하 방지 } return SequenceAction.Ok(); } private void UpdateProductCount(SequenceContext context) { if (_SystemParameter.LastResetTime < DateTime.Now.Date) { _SystemParameter.LastResetTime = DateTime.Now.Date; Log.I($"Today Count Reset. {_SystemParameter.TodayProductCount} -> 0"); _SystemParameter.TodayProductCount = 0; } _TodayProduct = _SystemParameter.TodayProductCount; _TotalProduct = _SystemParameter.TotalProductCount; if (_TodayProduct < uint.MaxValue) { _TodayProduct++; } if (_TotalProduct < uint.MaxValue) { _TotalProduct++; } _SystemParameter.TodayProductCount = _TodayProduct; _SystemParameter.TotalProductCount = _TotalProduct; } private void UpdateProductInfoForView() { Status.GetProcessingWeldingData().TotalProduct = _TotalProduct; Status.GetProcessingWeldingData().TodayProduct = _TodayProduct; Status.GetProcessingWeldingData().AutoRunStartTime = _AutoRunStartTime; Status.GetProcessingWeldingData().WeldingStartTime = _WeldingStartTime; Status.GetProcessingWeldingData().WeldingEndTime = _WeldEndTime; Status.GetProcessingWeldingData().WeldingSpanTimeMilliseconds = _WeldingSpanTime.TotalMilliseconds.ToString(); // Status.GetProcessingWeldingData().LotId 는 Autorun View에 표기할 용접이 완료 된 LotId 이므로 WorkLotID 와의 구분 확인 Status.GetProcessingWeldingData().LotId = _LotId; Status.GetProcessingWeldingData().CellId = _CellId; Status.GetProcessingWeldingData().RecipeNo = _RecipeNo; Status.GetProcessingWeldingData().LaserPowerByLBAD = _LaserPowerByLBAD.ToString(); Status.GetProcessingWeldingData().WeldingJigNo = _CompletedJigNo.ToString(); } private SequenceActionResult SetReportCellResult_SeqParams(SequenceContext context, string id, SequenceActionResult param) { Status.SetWorkingPosition(eWorkingPosition.REPORTING_RESULT); SeqReportCellResult_InitParam seqParams = new SeqReportCellResult_InitParam() { EDC_Interface = new EDC(), }; return SequenceAction.Ok(seqParams); } private SequenceActionResult GetReportCellResult_SeqResult(SequenceContext context, string id, SequenceActionResult param) { var resultOutParam = param.Param as SeqReportCellResult_OutParam; if (resultOutParam.Success == false) { return null; } return SequenceAction.Ok(); } private static string CheckCycleStop(SequenceContext context, string id, ICollection branches, SequenceActionResult param) { Status.SetWorkingPosition(eWorkingPosition.CHECKING_CYCLE); // TODO: 확인 필요, CybleStop 에 대한 내용, cycle stop 여부 가져오기 //bCycleStop = GetCycleStopRequest(); // TODO: 한번만 돌도록 설정함, PLC 로 받으면 좋음. //Jin : cycleStop 명령을 용접 프로그램에서 할 경우가 현재는 없음.. // CheckCycleStop 노드와 Complete 노드 사이 다른 노드가 필요하다면 해당 노드를 CheckCycleStop 노드 위로 올리자 bool cycleStop = false; if (cycleStop) { return SEQ_SAVE_RESULT_DATA; } else { return SEQ_CHECK_LOAD_COMPLETE; } } // END ALL #endregion private static string CheckCanWelded(SequenceContext context, string id, ICollection branches, SequenceActionResult param) { if (isWeled) return SEQ_SET_PARAMS_REPORT_CELL_RESULT_SEQ; else return SEQ_CHECK_CYCLE_STOP; } private void SaveCellAPD() { if (Status.GetProcessingWeldingData().APD.Count > 0) { string directoryPath = "D:\\ILSOSConfig\\APD_History"; string fileName = DateTime.Now.ToString("HH") + "_CellAPD"; string fullPath = Path.Combine(directoryPath, Status.GetProcessingWeldingData().APD.First().Date.ToString("yyyy-MM-dd"), fileName); Status.GetProcessingWeldingData().APD.CreateFiles(fullPath); } } private SequenceActionResult SaveResultData(SequenceContext context, string id, SequenceActionResult param) { context.PutValue(ContextKey.KEY_SEQUENCE_RESULT, true); return SequenceAction.Ok(); } private SequenceActionResult WriteCompletionPLCBit(SequenceContext context, string id, SequenceActionResult param) { // 완료 비트 write 하는 Node 추가 필요. format 유지, ReceiveCellInfo 없음 Log.D("AutoRun Sequence WriteCompletionPLCBit"); return SequenceAction.Ok(); } private SequenceActionResult InitConditionError(SequenceContext context, string id, SequenceActionResult param) { context.TryGetValue(ContextKey.KEY_SEQUENCE_ERROR_CODE, out int InitializeCode); switch (InitializeCode) { case (int)InitErrorList.PLC_AUTORUN_BIT_OFF: Log.D("AutoRun Sequence InitConditionError : PLC_AUTORUN_OFF"); return SequenceAction.ForceStopByAlarm(AlarmCode.ALARM_SEQUENCE_AutoRun_CheckInitCondition_PLC_AUTORUN_OFF); case (int)InitErrorList.LBAD_SUB_ACTIVATE_FAILED: Log.D("AutoRun Sequence InitConditionError : LBAD_SUB_ERR"); return SequenceAction.ForceStopByAlarm(AlarmCode.ALARM_SEQUENCE_AutoRun_CheckInitCondition_LBAD_SUB_ERR); case (int)InitErrorList.LBAD_CLIENT_ACTIVATE_FAILED: Log.D("AutoRun Sequence InitConditionError : LBAD_CLIENT_ERR"); return SequenceAction.ForceStopByAlarm(AlarmCode.ALARM_SEQUENCE_AutoRun_CheckInitCondition_LBAD_CLIENT_ERR); case (int)InitErrorList.LASER_INIT_ERROR: Log.D("AutoRun Sequence InitConditionError : LASER_ERR"); return SequenceAction.ForceStopByAlarm(AlarmCode.ALARM_SEQUENCE_AutoRun_CheckInitCondition_LASER_ERR); case (int)InitErrorList.MOTF_OFF: Log.D("AutoRun Sequence InitConditionError : MOTF_OFF"); return SequenceAction.ForceStopByAlarm(AlarmCode.ALARM_SEQUENCE_AutoRun_CheckInitCondition_MOTF_OFF); case (int)InitErrorList.PLC_ALARM: Log.D("AutoRun Sequence InitConditionError : PLC_ALARM"); return null; default: Log.D("AutoRun Sequence InitConditionError"); return SequenceAction.ForceStopByAlarm(AlarmCode.ALARM_SEQUENCE_AutoRun_CheckInitCondition); } } private SequenceActionResult Complete(SequenceContext context, string id, SequenceActionResult param) { RestoreDeviceStatus(); context.Settings.Save(CommonSettingIds.SystemParameter, _SystemParameter, "SeqAutoRunACWH52A"); context.TryGetValue(ContextKey.KEY_SEQUENCE_RESULT, out bool seqResult); SeqAutoRunACWH52A_OutParam result = new SeqAutoRunACWH52A_OutParam() { Success = seqResult }; if (seqResult == false) { //AlarmManager.Alarm(AlarmCode.ALARM_SEQUENCE_AutoRun_ResultFailed); } Log.D("AutoRun Sequence Complete"); Status.SetWorkingPosition(eWorkingPosition.DONE); return SequenceAction.Ok(result); } private bool RestoreDeviceStatus() { //PLC All BIT OFF for (int i = 0; i < _RotaryJigCount; i++) { _PLC.WriteBit((int)eBitOutput.PC_WELD_COMPLETE_1 + i, false); } _PLC.WriteBit((int)eBitOutput.PC_AUTO_STATUS, false); //_PLC.WriteBit((int)eBitOutput.PC_RECIPE_PARAM_UPLOAD_COMPLETE, false); Log.I(string.Format("[PLCInterface]PC All Bit Off")); //LBAD Ready Off //_LBAD.SetReadybyAuto(false); if (_SystemParameter.BypassModeLBAD == false) { if (_StreamLightLBAD.SendReadyStatus(eRequestData.Ready_Off) == false) { Log.I(string.Format($"[LBAD Interface]CheckReadyStatus null")); } } //Laser Off _Laser.LaserOff(); //List 실행 종료 _Scanner.StopExecution(); //Anlog 출력 0으로 변경, efunctiontype을 command로 변경해주면 list가 아니라 직접 변경됨 _Scanner.WriteDA((uint)eAnalogPortOut.DA1, 0, eFunctionType.COMMAND); _Scanner.WriteDA((uint)eAnalogPortOut.DA2, 0, eFunctionType.COMMAND); //Scanner 원점 복귀 _Scanner.JumpAbs(0, 0, eFunctionType.COMMAND); //Vision Correctiong 파일 변경 _Scanner.SelectCorrectionTable(1); //Recipe를 기존에 Manual에서 작업하던 화면으로 rollback WeldingRecipeManager.RollbackRecipe(); return true; } private void OnForceError(SequenceContext context, string arg2) { context.Settings.Save(CommonSettingIds.SystemParameter, _SystemParameter, "SeqAutoRunACWH52A"); OnSeqActionStop(); Log.I("SequenceAction Ok : OnForceError"); } private void OnStopped(SequenceContext context) { context.Settings.Save(CommonSettingIds.SystemParameter, _SystemParameter, "SeqAutoRunACWH52A"); OnSeqActionStop(); Log.I("SequenceAction Ok : OnStopped"); } private void OnSeqActionStop() { Status.EquipmentMode = eEquipmentMode.MANUAL; //PLC All BIT OFF for (int i = 0; i < _RotaryJigCount; i++) { _PLC.WriteBit((int)eBitOutput.PC_WELD_COMPLETE_1 + i, false); } _PLC.WriteBit((int)eBitOutput.PC_AUTO_STATUS, false); //_PLC.WriteBit((int)eBitOutput.PC_RECIPE_PARAM_UPLOAD_COMPLETE, false); Log.I(string.Format("[PLCInterface]PC All Bit Off")); //LBAD Ready Off //_LBAD.SetReadybyAuto(false); if (_SystemParameter.BypassModeLBAD == false) { if (_StreamLightLBAD.SendReadyStatus(eRequestData.Ready_Off) == false) { Log.I(string.Format($"[LBAD Interface]CheckReadyStatus null")); } } //Laser Off _Laser.LaserOff(); //List 실행 종료 _Scanner.StopExecution(); //Anlog 출력 0으로 변경, efunctiontype을 command로 변경해주면 list가 아니라 직접 변경됨 _Scanner.WriteDA((uint)eAnalogPortOut.DA1, 0, eFunctionType.COMMAND); _Scanner.WriteDA((uint)eAnalogPortOut.DA2, 0, eFunctionType.COMMAND); //Scanner 원점 복귀 _Scanner.JumpAbs(0, 0, eFunctionType.COMMAND); //Vision Correctiong 파일 변경 _Scanner.SelectCorrectionTable(1); //Recipe를 기존에 Manual에서 작업하던 화면으로 rollback WeldingRecipeManager.RollbackRecipe(); } } // For CSV 저장용 public class CenterPositionData : NotifyPropertyChangedBase { private double _CenterX; public double CenterX { get => _CenterX; set => SetProperty(ref _CenterX, value); } private double _CenterY; public double CenterY { get => _CenterY; set => SetProperty(ref _CenterY, value); } } public class SeqAutoRunACWH52A_InitParam : InitParam { public ScoutSeqParams ScoutSeqParams { get; set; } public bool UseStream { get; set; } } public class SeqAutoRunACWH52A_OutParam : OutParam { } }