1340 lines
70 KiB
C#
1340 lines
70 KiB
C#
using Aurora;
|
|
using Aurora.Api;
|
|
using Aurora.Api.Manager;
|
|
using Aurora.Util.HislogPlus;
|
|
using Aurora.View;
|
|
using AuroraCellTracker;
|
|
using AuroraCellTracker.DaekhonCoporation;
|
|
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_CCW_H52A.Data;
|
|
using ILSOS_CCW_H52A.Device;
|
|
using ILSOS_CCW_H52A.Models;
|
|
using ILSOS_CCW_H52A.RMS;
|
|
using ILSOS_CCW_H52A.Sequences.ProcessObjects;
|
|
using Microsoft.Win32;
|
|
using OpenCvSharp;
|
|
using OpenCvSharp.Aruco;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Reactive.Subjects;
|
|
using System.Reflection;
|
|
using System.Threading;
|
|
using System.Windows;
|
|
using System.Windows.Media.Animation;
|
|
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_CCW_H52A.Device.IOController_CCW_H52A;
|
|
using static ILSOS_CCW_H52A.Device.PLCControllerCCWH52A;
|
|
using static ILSOS_CCW_H52A.Device.ScannerListCCWH52A;
|
|
|
|
namespace ILSOS_CCW_H52A.Sequences
|
|
{
|
|
class SeqAutoRunCCWH52A
|
|
{
|
|
static readonly Logger Log = new Logger("SeqAutoRunCCWH52A");
|
|
|
|
const string SEQ_ENTER = "[SeqAutoRunCCWH52A] SEQ_ENTER"; // inputParam 으로 받자.
|
|
const string SEQ_CHECK_INIT_CONDITION = "[SeqAutoRunCCWH52A] Check Init Condition"; // Check Init Condition
|
|
|
|
const string SEQ_FORK_ENCODER_MONITOR = "[SeqAutoRunCCWH52A] Fork Encoder Monitor";
|
|
const string SEQ_ENCODER_MONITOR = "[SeqAutoRunCCWH52A] Check Encoder Monitor";
|
|
const string SEQ_JOIN_ENCODER_MONITOR = "[SeqAutoRunCCWH52A] Join Encoder Monitor";
|
|
|
|
// TODO: [장비구현필요] 실제 공정 추가
|
|
const string SEQ_CHECK_LOAD_COMPLETE = "[SeqAutoRunCCWH52A] Check Load Complete";
|
|
|
|
public const string SEQ_CHECK_SCANNER_LIST_COMPLETE = "[SeqAutoRunCCWH52A] Check Scanner List Complete";
|
|
const string SEQ_READY_SCANNER_LIST = "[SeqAutoRunCCWH52A] Ready ScannerList";
|
|
|
|
//const string SEQ_SET_PARAMS_RECEIVE_CELL_INFO_SEQ = "[SeqAutoRunCCWH52A] Set Sequence Param (ReceiveCellInfo Seq)";
|
|
//const string SEQ_DO_ID_RECEIVE_CELL_INFO_SEQ = "[SeqAutoRunCCWH52A] Process (ReceiveCellInfo Seq)";
|
|
//const string SEQ_GET_PARAMS_RECEIVE_CELL_INFO_SEQ = "[SeqAutoRunCCWH52A] Get Sequence Result (ReceiveCellInfo Seq)";
|
|
|
|
const string SEQ_SET_PARAMS_UPLOAD_WELDING_LIST_SEQ = "[SeqAutoRunCCWH52A] Set Sequence Param (UploadWeldingList Seq)";
|
|
const string SEQ_DO_ID_UPLOAD_WELDING_LIST_SEQ = "[SeqAutoRunCCWH52A] Process (UploadWeldingList Seq)";
|
|
const string SEQ_GET_RESULT_UPLOAD_WELDING_LIST_SEQ = "[SeqAutoRunCCWH52A] Get Sequence Result (UploadWeldingList Seq)";
|
|
|
|
const string SEQ_EXECUTE_WELDING = "[SeqAutoRunCCWH52A] Execute Welding";
|
|
const string SEQ_REQUEST_UNLOAD_CELL = "[SeqAutoRunCCWH52A] Request Unload Cell";
|
|
//const string SEQ_WAIT_LBAD_JUDGE = "[SeqAutoRunCCWH52A] Wait LBAD Judge";
|
|
const string SEQ_REPORT_CELL_RESULT = "[SeqAutoRunCCWH52A] Report Cell Result";
|
|
|
|
//const string SEQ_SET_PARAMS_REPORT_CELL_RESULT_SEQ = "[SeqAutoRunCCWH52A] Set Sequence Param (ReportCellResult Seq)";
|
|
//const string SEQ_DO_ID_REPORT_CELL_RESULT_SEQ = "[SeqAutoRunCCWH52A] Process (ReportCellResult Seq)";
|
|
//const string SEQ_GET_RESULT_REPORT_CELL_RESULT_SEQ = "[SeqAutoRunCCWH52A] Get Sequence Result (ReportCellResult Seq)";
|
|
|
|
//const string SEQ_REPORT_CELLINFO_TO_PLC = "[SeqAutoRunCCWH52A] ReportCellInfoToPLC";
|
|
const string SEQ_CHECK_CYCLE_STOP = "[SeqAutoRunCCWH52A] Check Cycle Stop";
|
|
const string SEQ_CHECK_CAN_WELDED = "[SeqAutoRunCCWH52A] Check Can Welded";
|
|
|
|
const string SEQ_SAVE_RESULT_DATA = "[SeqAutoRunCCWH52A] Save Result Data";
|
|
|
|
const string SEQ_WRITE_COMPLETION_PLC_BIT = "[SeqAutoRunCCWH52A] Write Completion PLC Bit";
|
|
const string SEQ_INIT_CONDITION_ERROR = "[SeqAutoRunCCWH52A] Init Condition Error";
|
|
const string SEQ_COMPLETE = "[SeqAutoRunCCWH52A] SEQ_COMPLETE"; // 완료
|
|
|
|
static class ContextKey
|
|
{
|
|
public const string KEY_SEQUENCE_RESULT = "KEY_SEQUENCE_RESULT_SeqAutoRunCCWH52A";
|
|
public const string KEY_SEQUENCE_START_TIME = "KEY_SEQUENCE_START_TIME_SeqAutoRunCCWH52A";
|
|
public const string KEY_SEQUENCE_ERROR_CODE = "KEY_SEQUENCE_ERROR_CODE_SeqAutoRunCCWH52A";
|
|
}
|
|
|
|
const int PLC_TIMEOUT = 60; // sec, Timeout은 시퀀스 정지가 아니라 타임아웃 시간마다 로그만 남기도록 한다
|
|
|
|
private Scanner _Scanner;
|
|
private LaserDevice _Laser;
|
|
|
|
private PLCControllerCCWH52A _PLC;
|
|
private IODevice _IO;
|
|
private StreamLightLBAD _StreamLightLBAD;
|
|
private static CellTracker _CellTracker;
|
|
|
|
private SystemParameter _SystemParameter;
|
|
private ScannerCanvasUserCtrl _ScannerCanvasUserCtrl; //스캐너 동작관련 객체 선언
|
|
|
|
private int _RecipeNo;
|
|
private int _ModelNo;
|
|
|
|
private IRecipeManager _RecipeManager;
|
|
|
|
private eListNo _WeldingScannerListNo;
|
|
|
|
private List<IDrawObject> _SelectedDrawObjectList;
|
|
|
|
private SeqAutoRunCCWH52A_InitParam _InitParam;
|
|
|
|
private double _LaserPowerByLBAD;
|
|
|
|
private bool _ModulationMode;
|
|
|
|
private DateTime _AutoRunStartTime;
|
|
// 연속 지그 간 용접 간격 계산 전용 (보고 데이터 아님 — 인터벌 로그용)
|
|
private DateTime _prevWeldEndTime;
|
|
|
|
private int _EncoderOverflowCount = 0;
|
|
|
|
private double _RotaryEncoderOffset = 0;
|
|
private int _WeldingJigNo = -1;
|
|
private int _WeldingCount = 0;
|
|
private int _RotaryJigCount = 16;
|
|
|
|
// 전전 셀/전 셀/대기 셀/완료 셀 파이프라인 스케줄러 (16개 JigInfo 풀, GC-free)
|
|
private CellInfoScheduler _CellScheduler;
|
|
|
|
private double _WeldingStartAngle;
|
|
|
|
private bool isTheFirstListExecute;
|
|
|
|
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);
|
|
|
|
#warning 아래 서브시퀀스가 CellTracker에서 받은 데이터를 WeldingData에 넣기만함, ProcessManager에서 Cell정보를 직접 Callback으로 받도록 수정함
|
|
//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_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_EXECUTE_WELDING, ExecuteWelding);
|
|
sb.AddAction(SEQ_REQUEST_UNLOAD_CELL, RequestUnloadCell);
|
|
sb.AddDecision(SEQ_CHECK_CAN_WELDED, CheckCanWelded);
|
|
sb.AddAction(SEQ_REPORT_CELL_RESULT, ReportCellResult);
|
|
|
|
#warning ReportCellResult함수로 아래 시퀀스 대체
|
|
//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.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_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_REPORT_CELL_RESULT]
|
|
//> 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.Final);
|
|
|
|
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] > sb.Final);
|
|
|
|
#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("WeldingSeqAutoRunH52A.pdf");
|
|
|
|
return sb;
|
|
}
|
|
|
|
private SequenceActionResult Sequence_Enter(SequenceContext context, string id, SequenceActionResult param)
|
|
{
|
|
Log.I("H52A Welding Sequence Start");
|
|
Status.SetWorkingPosition(eWorkingPosition.CHECKING_CONDITION);
|
|
|
|
_InitParam = param.Param as SeqAutoRunCCWH52A_InitParam;
|
|
_RecipeManager = Application.Current.AuroraEnv().RecipeManager;
|
|
_SystemParameter = context.GetSetting<SystemParameter>(CommonSettingIds.SystemParameter);
|
|
|
|
InitDev(context);
|
|
if (!CheckDeviceReady(context)) {
|
|
//Check Device
|
|
Log.E("The devices are not active.");
|
|
return SequenceAction.ForceStopByAlarm(AlarmCode.ALARM_SEQUENCE_AutoRun_DeviceNotReady);
|
|
}
|
|
if (InitSequenceVariable(context) == false) {
|
|
return SequenceAction.ForceStopByAlarm(AlarmCode.ALARM_SEQUENCE_AutoRun_InitVariableError);
|
|
}
|
|
Thread.Sleep(1); // 부하 방지
|
|
return SequenceAction.Ok();
|
|
}
|
|
|
|
private bool InitSequenceVariable(SequenceContext context)
|
|
{
|
|
_AutoRunStartTime = DateTime.Now;
|
|
|
|
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;
|
|
|
|
_RecipeNo = ActivateRecipeName.Id;
|
|
var currentModelName = context.Recipes.ActivatedModelName();
|
|
var modelItem = context.Models.ModelItem(currentModelName, CommonModelIds.EquipmentSpecificModel) as EquipmentSpecificModel_CCW_H52A;
|
|
_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();
|
|
ProcessManager.Instance.RegisterLbadStream(_StreamLightLBAD);
|
|
}
|
|
_CellTracker = context.GetCellTracker();
|
|
_PLC = context.Devices[DeviceIds.PLCDevice] as PLCControllerCCWH52A;
|
|
_IO = context.GetIOUnit();
|
|
_CellScheduler = ProcessManager.Instance.cellScheduler;
|
|
|
|
}
|
|
|
|
private bool CheckDeviceReady(SequenceContext context)
|
|
{
|
|
List<string> deviceListToCheck = new List<string>() { 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)
|
|
{
|
|
int InitializeCode = InitializeDeviceStatus(context);
|
|
if (InitializeCode == (int)InitErrorList.SUCCESS)
|
|
return true;
|
|
else
|
|
context.PutValue(ContextKey.KEY_SEQUENCE_ERROR_CODE, InitializeCode);
|
|
return false;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
///// AutoRun Bit가 OFF인 경우
|
|
if (_PLC.ReadBit((int)eBitInput.PLC_AUTO_STATUS).data == false) {
|
|
// AutoRun Request 상태 체크
|
|
if (_PLC.ReadBit((int)eBitInput.PLC_AUTO_REQ).data == false) {
|
|
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];
|
|
|
|
// 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})"));
|
|
|
|
// JigInfo 풀 스케줄러 초기화 (전전/전/대기/완료 셀 파이프라인)
|
|
// ── 재시작 전 미완료 보고 보존 ───────────────────────────────────────────────
|
|
// Complete가 있는 상태로 정지가 들어오면 Reset() 후 슬롯이 Clear()되어 보고 불가.
|
|
// SavePendingComplete()로 _pendingSlot에 복사한 뒤 Reset()을 호출한다.
|
|
|
|
_CellScheduler.SavePendingComplete();
|
|
|
|
// 큐 잔류 셀 보존 — DrainCellInfoQueue 전에 선행 (Prefetch2 다음 지그 기준)
|
|
// Prefetch2.JigNo가 확정값이므로 다음 지그 = (JigNo % 16) + 1 로 신뢰성 있게 계산
|
|
if (_CellScheduler.Prefetch2 != null && _CellScheduler.Prefetch2.JigNo > 0) {
|
|
int nextJigNo = (_CellScheduler.Prefetch2.JigNo % ProcessManager.RotaryJigCount) + 1;
|
|
if (ProcessManager.Instance.ReceivedCellInfoQueue.TryDequeue(out var qCell)) {
|
|
_CellScheduler.SaveQueueEntry(qCell.LotId, qCell.CellId, nextJigNo);
|
|
Log.I($"[QueueSave] Jig{nextJigNo} 큐 잔류 셀 보존 >> CellId({qCell.CellId})");
|
|
}
|
|
}
|
|
|
|
if (_CellScheduler.HasPendingPrefetch)
|
|
Log.I("[PendingRestore] 미용접 셀 보존됨 >> 재시작 후 JigNo 매칭 시 복원 예정");
|
|
_CellScheduler.Reset();
|
|
ProcessManager.Instance.DrainCellInfoQueue();
|
|
|
|
// 정지 전 미저장 APD 잔존 데이터 클리어 — 재시작 시 중복 누적 방지
|
|
Status.GetProcessingWeldingData().APD.Clear();
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
isTheFirstListExecute = true; // 제일 첫 List는 ExecuteList 필요. 이후는 AutoChange
|
|
|
|
_PLC.WriteBit(eBitOutput.PC_AUTO_STATUS, true);
|
|
Log.I(string.Format($"[PLCInterface]SET PC_AUTO_STATUS True"));
|
|
#endregion
|
|
|
|
return (int)InitErrorList.SUCCESS;
|
|
}
|
|
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 현재 Rotary의 회전 각도에서, 현재 용접 카운트와 지그 번호를 계산. 추가로 Overflow 카운트도 초기화
|
|
/// </summary>
|
|
/// <param name="rotaryAngleOffset">Rotary 회전 Offset. 운전 시작 시 Rotary가 지그 1번 정중심에서부터 시작했을 경우가 0. 범위는 0이상</param>
|
|
/// <param name="rotaryJigCount">Rotary 내 지그 갯수임. 장비 및 T/T에 따라서, 360도 당 지그의 개수는 다를 수 있으므로.</param>
|
|
/// <param name="weldingCount">Rotary Welding Count. 30도 구간 하나가 지나갈 때마다 +1</param>
|
|
/// <param name="weldingJigNo">현재 '용접 중' 구간에 들어가 있는 지그 번호</param>
|
|
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<string> 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;
|
|
}
|
|
|
|
// 지그 상태 확인
|
|
private SequenceActionResult CheckLoadComplete(SequenceContext context, string id, SequenceActionResult param)
|
|
{
|
|
Status.SetWorkingPosition(eWorkingPosition.LOADING_CELL);
|
|
|
|
// MOTF WaitScannerLoadListEnable
|
|
// List가 실행 중이라면(ex : 엔코더 대기 상태 등), 반환값은 0, LoadList 할 수 없는 상태(List 실행 중이거나, 엔코더 대기 상태)
|
|
// LoadList가 0보다 크면 다음 시퀀스 진행해야 함
|
|
WaitContinue:
|
|
var ret = context.RetryUntilSuccess(() => _Scanner.LoadList(_WeldingScannerListNo) > 0, int.MaxValue, 10, true);
|
|
if (ret == E2.TIMEOUT) {
|
|
Log.E("TIMEOUT_Scanner_LoadList : 600시간 Run");
|
|
goto WaitContinue;
|
|
}
|
|
|
|
Log.I($"LoadList function success : List {_WeldingScannerListNo}");
|
|
Thread.Sleep(1); // 부하 방지
|
|
return SequenceAction.Ok();
|
|
}
|
|
|
|
private SequenceActionResult CheckScannerListComplete(SequenceContext context, string id, SequenceActionResult param)
|
|
{
|
|
// Prefetch1 = 2회 전 업로드된 지그 = 현재 물리적으로 레이저 통과 중 = 완료 체크 대상.
|
|
// Shift()는 이 메서드 이후 ReadyScannerList에서 실행되므로, 이 시점의 Prefetch1이
|
|
// 원본 코드의 InputScannerList[(listNo-1)]과 동일 대상이다.
|
|
// 예열 구간: iter 1~2(Pf1=null) → 통과. iter 3~: Pf1=Jig1 → 실제 처리.
|
|
var jigComplete = _CellScheduler.Prefetch1;
|
|
if (jigComplete == null) {
|
|
Log.I($"[Warmup] ScannerList {_WeldingCount}: pipeline not ready, skip complete check");
|
|
return SequenceAction.Ok();
|
|
}
|
|
if (!CheckInsideWeldPosition(jigComplete.StartAngle)) {
|
|
// IsCanExist=false(공 지그)는 리스트 실행은 하지만 용접이 없으므로
|
|
// 엔코더 범위를 벗어나도 에러가 아님
|
|
if (jigComplete.IsCanExist) {
|
|
Log.I("ERR_SCANNER_LIST_PROCESSING_TIMEOVER");
|
|
return SequenceAction.Error();
|
|
}
|
|
return SequenceAction.Ok();
|
|
} else {
|
|
if (jigComplete.IsCanExist) {
|
|
#region 용접 시간, 엔코더 측정/기록
|
|
double elaspedTime = _Scanner.GetTime();
|
|
|
|
if (jigComplete.WeldComplete) {
|
|
jigComplete.WeldEndTime = DateTime.Now;
|
|
jigComplete.WeldingSpanTime = TimeSpan.FromSeconds(elaspedTime);
|
|
TimeSpan weldInterval = jigComplete.WeldEndTime - _prevWeldEndTime;
|
|
_prevWeldEndTime = jigComplete.WeldEndTime;
|
|
|
|
// 보고 대상 지정 불필요 — Shift()가 Prefetch1 → Complete로 자동 전진
|
|
Log.I(string.Format("JigNo : {0}, Weld start : {1}, Weld End : {2}, WeldTime : {3}, interval : {4:F3}",
|
|
jigComplete.JigNo,
|
|
jigComplete.WeldStartTime.ToString("HH:mm:ss.fff"),
|
|
jigComplete.WeldEndTime.ToString("HH:mm:ss.fff"),
|
|
jigComplete.WeldingSpanTime.TotalMilliseconds,
|
|
weldInterval.TotalSeconds));
|
|
|
|
if (_PLC.ReadBit((int)eBitInput.PLC_USE_WELD_JIG_1 + jigComplete.JigNo - 1).data) {
|
|
_PLC.WriteBit((int)eBitOutput.PC_WELD_COMPLETE_1 + jigComplete.JigNo - 1, true);
|
|
Log.I(string.Format($"PC_WELD_COMPLETE_{jigComplete.JigNo} -> true)"));
|
|
}
|
|
|
|
if (_SystemParameter.LastResetTime < DateTime.Now.Date) {
|
|
_SystemParameter.LastResetTime = DateTime.Now.Date;
|
|
Log.I($"Today Count Reset. {_SystemParameter.TodayProductCount} -> 0");
|
|
_SystemParameter.TodayProductCount = 0;
|
|
}
|
|
|
|
if (_SystemParameter.TodayProductCount < uint.MaxValue) {
|
|
_SystemParameter.TodayProductCount++;
|
|
}
|
|
if (_SystemParameter.TotalProductCount < uint.MaxValue) {
|
|
_SystemParameter.TotalProductCount++;
|
|
}
|
|
|
|
Status.GetProcessingWeldingData().TotalProduct = _SystemParameter.TotalProductCount;
|
|
Status.GetProcessingWeldingData().TodayProduct = _SystemParameter.TodayProductCount;
|
|
Status.GetProcessingWeldingData().AutoRunStartTime = _AutoRunStartTime;
|
|
Status.GetProcessingWeldingData().RecipeNo = _RecipeNo;
|
|
Status.GetProcessingWeldingData().LaserPowerByLBAD = _LaserPowerByLBAD.ToString();
|
|
|
|
Status.GetProcessingWeldingData().LotId = jigComplete.LotId;
|
|
Status.GetProcessingWeldingData().CellId = jigComplete.CellId;
|
|
Status.GetProcessingWeldingData().WeldingStartTime = jigComplete.WeldStartTime;
|
|
Status.GetProcessingWeldingData().WeldingEndTime = jigComplete.WeldEndTime;
|
|
Status.GetProcessingWeldingData().WeldingSpanTimeMilliseconds = jigComplete.WeldingSpanTime.TotalMilliseconds.ToString();
|
|
Status.GetProcessingWeldingData().JigNo = jigComplete.JigNo.ToString();
|
|
SaveCellAPD();
|
|
}
|
|
|
|
UtilUnit.logUnit.SetLog(LogUnit.eLogType.PRODUCT, MethodBase.GetCurrentMethod().Name, "UPDATE_WELDING_TIME;" + jigComplete.WeldingSpanTime.TotalMilliseconds);
|
|
UtilUnit.logUnit.SetLog(LogUnit.eLogType.PRODUCT, MethodBase.GetCurrentMethod().Name, "UPDATE_PRODUCT_COUNT;" + jigComplete.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#" + jigComplete.JigNo.ToString();
|
|
string DataFilePath = ProjectUtil.GetDataLogSetting().GetFilePath_ScannerRecordLog(RecordFileName, true);
|
|
_Scanner.SaveRecord(DataFilePath);
|
|
}
|
|
#endregion
|
|
}
|
|
}
|
|
return SequenceAction.Ok();
|
|
}
|
|
|
|
/// <summary>
|
|
/// startAngle startAngle+45
|
|
/// │←── OK 구간 ────►│
|
|
/// ────┼─────────────────┼──────────────► 로터리 각도
|
|
/// 리스트 여기까지
|
|
/// 시작 와야 완료 체크 가능
|
|
/// 판정 로직:
|
|
/// currentAngle < startAngle → Too early (아직 리스트 실행 중, 완료 체크 너무 이름)
|
|
/// startAngle ≤ current < start+45 → OK (정상 완료 구간)
|
|
/// currentAngle ≥ startAngle + 45 → Too late (너무 늦음, 타임오버 에러)
|
|
/// </summary>
|
|
/// <param name="startAngle"> 현재 지그의 시작 각도 </param>
|
|
/// <returns> 지나갔으면 True </returns>
|
|
public bool CheckInsideWeldPosition(double startAngle)
|
|
{
|
|
double encoderX, currentAngle;
|
|
_Scanner.GetEncoder(out encoderX, out currentAngle, true);
|
|
|
|
// Encoder 반전되는 순간이며, 이 때만 인터락 무시
|
|
if (0 < startAngle && currentAngle < 0)
|
|
return false;
|
|
|
|
if (startAngle <= currentAngle && currentAngle < startAngle + 45) {
|
|
Log.I(string.Format("Current encoder check OK, startAngle : {0}, currentAngle : {1})", startAngle, currentAngle));
|
|
return true;
|
|
}
|
|
|
|
if (currentAngle < startAngle)
|
|
Log.W(string.Format("Current encoder check NG(Too early), startAngle : {0}, currentAngle : {1})", startAngle, currentAngle));
|
|
else
|
|
Log.W(string.Format("Current encoder check NG(Too late), startAngle : {0}, currentAngle : {1})", startAngle, currentAngle));
|
|
|
|
return false;
|
|
}
|
|
|
|
private string ReadyScannerList(SequenceContext context, string id, ICollection<string> 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);
|
|
|
|
// canExist 여부와 무관하게 매 지그마다 파이프라인 전진
|
|
// canExist=false 지그도 Shift()해야 Prefetch1 타이밍이 올바르게 유지됨.
|
|
_CellScheduler.Shift(_WeldingJigNo);
|
|
|
|
_CellScheduler.Prefetch2.JigNo = _WeldingJigNo;
|
|
_CellScheduler.Prefetch2.ListNo = _WeldingScannerListNo;
|
|
_CellScheduler.Prefetch2.WriteTime = DateTime.Now;
|
|
_CellScheduler.Prefetch2.StartAngle = _WeldingStartAngle;
|
|
_CellScheduler.Prefetch2.WeldingCount = _WeldingCount;
|
|
_CellScheduler.Prefetch2.IsCanExist = canExist;
|
|
|
|
#warning OC2 ACW 기준 >> Cell Tracker는 어느 시점에 데이터는 주는지 모르겠지만, '전전전 셀' 쯤에서 이미 건네받음! 다른 프로젝트에서 확인 필요함
|
|
// 복원 우선: 정지 전 업로드됐으나 로터리 감속·정지로 용접 미완된 셀 정보 복원.
|
|
// canExist=false 지그도 TryRestoreCell을 호출하여 슬롯 소비(폐기) — 이후 오염 방지.
|
|
(string LotId, string CellId) recvInfo = (string.Empty, string.Empty);
|
|
if (_CellScheduler.TryRestoreCell(_WeldingJigNo, out var rLot, out var rCell)) {
|
|
if (canExist) {
|
|
recvInfo.LotId = rLot;
|
|
recvInfo.CellId = rCell;
|
|
Log.I($"[PendingRestore] Jig{_WeldingJigNo} 셀 복원 >> CellId({rCell})");
|
|
} else {
|
|
Log.W($"[PendingRestore] Jig{_WeldingJigNo} canExist=false, 복원 데이터 폐기 >> CellId({rCell})");
|
|
}
|
|
} else if (ProcessManager.Instance.ReceivedCellInfoQueue.TryDequeue(out var cell)) {
|
|
if (canExist) {
|
|
recvInfo.LotId = cell.LotId;
|
|
recvInfo.CellId = cell.CellId;
|
|
} else {
|
|
// 앞공정 NG 셀: CellTracker는 투입 시 이미 스캔했으므로 큐에서 소비 후 폐기.
|
|
// 소비하지 않으면 다음 정상 셀이 이 NG 데이터를 가져가 정합성 붕괴.
|
|
Log.W($"[NG Skip] Jig{_WeldingJigNo} canExist=false, 큐 데이터 폐기 >> CellId({cell.CellId})");
|
|
}
|
|
} else {
|
|
if (canExist)
|
|
Log.E($"[QueueEmpty] Jig{_WeldingJigNo} canExist=true 이지만 큐가 비어있음");
|
|
|
|
_CellTracker.getDummyData(out string lotId, out string cellId);
|
|
recvInfo.LotId = lotId;
|
|
recvInfo.CellId = cellId;
|
|
}
|
|
|
|
_CellScheduler.Prefetch2.LotId = recvInfo.LotId;
|
|
_CellScheduler.Prefetch2.CellId = recvInfo.CellId;
|
|
Status.GetProcessingWeldingData().WorkLotId = recvInfo.LotId;
|
|
Status.GetProcessingWeldingData().WorkCellId = recvInfo.CellId;
|
|
Status.GetProcessingWeldingData().WorkJigNo = _WeldingJigNo.ToString();
|
|
|
|
if (_CellScheduler.Prefetch2 != null)
|
|
Log.I($"Prefetch2 >> CellId({_CellScheduler.Prefetch2.CellId}) [CanExist({_CellScheduler.Prefetch2.IsCanExist})] [JigNo({_CellScheduler.Prefetch2.JigNo})] " +
|
|
$"[ListNo({_CellScheduler.Prefetch2.ListNo})] [WeldingCount({_CellScheduler.Prefetch2.WeldingCount})]" +
|
|
$"[StartAngle(Y)({_CellScheduler.Prefetch2.StartAngle})]");
|
|
|
|
if (_CellScheduler.Prefetch1 != null)
|
|
Log.I($"Prefetch1 >> CellId({_CellScheduler.Prefetch1.CellId}) [CanExist({_CellScheduler.Prefetch1.IsCanExist})] [JigNo({_CellScheduler.Prefetch1.JigNo})] " +
|
|
$"[ListNo({_CellScheduler.Prefetch1.ListNo})] [WeldingCount({_CellScheduler.Prefetch1.WeldingCount})]" +
|
|
$"[StartAngle(Y)({_CellScheduler.Prefetch1.StartAngle})]");
|
|
|
|
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
|
|
|
|
return SEQ_SET_PARAMS_UPLOAD_WELDING_LIST_SEQ;
|
|
} else {
|
|
return SEQ_EXECUTE_WELDING;
|
|
}
|
|
#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;
|
|
}
|
|
|
|
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)
|
|
{
|
|
if (jigNo <= _RotaryJigCount)
|
|
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 = 0,
|
|
CellMeasureCenterOffsetY = 0,
|
|
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;
|
|
}
|
|
ScannerParam laserParam = null;
|
|
laserParam = GetSelectedDrawObjectData(_SelectedDrawObjectList[0]);
|
|
|
|
_CellScheduler.Prefetch2.InnerCellInfo.RecipeNo = _RecipeNo;
|
|
_CellScheduler.Prefetch2.InnerCellInfo.LaserPowerCore = laserParam.LaserPower1.ToString();
|
|
_CellScheduler.Prefetch2.InnerCellInfo.LaserPowerRing = laserParam.LaserPower2.ToString();
|
|
_CellScheduler.Prefetch2.InnerCellInfo.MarkSpeed = laserParam.MarkSpeed.ToString();
|
|
_CellScheduler.Prefetch2.InnerCellInfo.FocalLength = (_PLC.ReadDWord((int)eWordInput.PLC_WELDING_FOCAL_LENGTH).data / 1000f).ToString();
|
|
|
|
// 업로드 성공 확인 후 뷰/EDC용 Status 갱신 (ReadyScannerList에서 중복 갱신 제거)
|
|
Status.GetProcessingWeldingData().LaserPowerCore = laserParam.LaserPower1.ToString();
|
|
Status.GetProcessingWeldingData().LaserPowerRing = laserParam.LaserPower2.ToString();
|
|
Status.GetProcessingWeldingData().MarkSpeed = laserParam.MarkSpeed.ToString();
|
|
Status.GetProcessingWeldingData().FocalLength = _CellScheduler.Prefetch2.InnerCellInfo.FocalLength;
|
|
Log.FuncI("CellTracker Load");
|
|
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)
|
|
{
|
|
double send_time_offset = 0.1;
|
|
|
|
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;
|
|
|
|
// ── JigScheduler: 포크 내에서 참조 캡처 (Shift와의 경합 방지) ──────
|
|
// Prefetch1: 2회 전 업로드된 지그 = 현재 물리적으로 실행 중인 지그.
|
|
// 원본 코드의 InputScannerList[(listNo-1)]과 동일 대상.
|
|
// Shift()는 메인 시퀀스에서만 호출되므로 루프 시작 시 캡처해도 안전.
|
|
var executeTarget = _CellScheduler.Prefetch1;
|
|
|
|
if (WeldingInPosition) {
|
|
if (executeTarget != null && executeTarget.IsCanExist && executeTarget.JigNo > 0) {
|
|
Log.I($"Fork Before Min Rotate Encoder X : ({encoderX}), Encoder Y : ({currentAngle}), PLC Rotary : ({CurrentPosPLC}), List {executeTarget.ListNo}, JigNo : ({executeTarget.JigNo})");
|
|
if (!_SystemParameter.BypassModeLBAD) {
|
|
if (!executeTarget.CheckSendCellinfo) {
|
|
if (currentAngle > executeTarget.StartAngle) {
|
|
executeTarget.CheckSendCellinfo = _StreamLightLBAD.SendCellInfo(executeTarget.LotId, executeTarget.CellId, _RecipeNo, _ModelNo, executeTarget.JigNo);
|
|
|
|
if (!executeTarget.CheckSendCellinfo)
|
|
Log.I($"[LBAD Interface]SendCellInfo null, LotID [{executeTarget.LotId}], CellID [{executeTarget.CellId}], JigNo [{executeTarget.JigNo}]");
|
|
else
|
|
Log.I($"[StreamLight]SendCellInfo LotID [{executeTarget.LotId}], CellID [{executeTarget.CellId}], JigNo [{executeTarget.JigNo}]");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var LaserIO = _IO._iOUnit.ReadDiBit(Convert.ToInt32(eADIAddress.LaserOn));
|
|
var IsLaserOn = ChangeChecker.IsChangedBoolMonitoring("LaserOn", LaserIO, true);
|
|
if (IsLaserOn) {
|
|
if (executeTarget != null && executeTarget.IsCanExist && executeTarget.WeldStartTime == DateTime.MinValue) {
|
|
executeTarget.WeldStartTime = DateTime.Now;
|
|
executeTarget.WeldComplete = true;
|
|
Log.I($"Laser On, Welding Complete. JigNo : ({executeTarget.JigNo})");
|
|
}
|
|
}
|
|
|
|
// List의 시작/종료 로그 — MOTF에서 LIST1 -> LIST2 전환 시 종료+시작 각각 기록
|
|
_Scanner.GetListPointer(out eListNo curList, out uint pos);
|
|
var isBusy = _Scanner.GetBusyStatus();
|
|
eListNo effectiveList = (!isBusy && (curList == eListNo.LIST1 || curList == eListNo.LIST2))
|
|
? eListNo.NONE : curList;
|
|
|
|
if (ChangeChecker.IsChangedInt("ListState", (int)effectiveList, out int prevListRaw)) {
|
|
var prevList = (eListNo)prevListRaw;
|
|
if (prevList != eListNo.NONE)
|
|
Log.I($"[ListEnd] List {prevList} 종료 >> LaserOn({LaserIO}) EncoderY({currentAngle:F3}) JigNo({executeTarget?.JigNo})");
|
|
if (effectiveList != eListNo.NONE)
|
|
Log.I($"[ListStart] List {effectiveList} 시작 >> LaserOn({LaserIO}) EncoderY({currentAngle:F3}) JigNo({executeTarget?.JigNo})");
|
|
}
|
|
|
|
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 * 100_000f));
|
|
Thread.Sleep(1); // 부하 방지
|
|
}
|
|
return SequenceAction.Ok();
|
|
}
|
|
|
|
private SequenceActionResult ReportCellResult(SequenceContext context, string id, SequenceActionResult param)
|
|
{
|
|
Status.SetWorkingPosition(eWorkingPosition.REPORTING_RESULT);
|
|
|
|
string strDate = DateTime.Now.Date.ToString("yyyyMMdd");
|
|
string strTime = DateTime.Now.ToString("HHmmss");
|
|
|
|
// 셀 식별 정보 + LBAD AVG는 스케줄러 슬롯에서 직접 읽음 (ReportSlot 또는 pendingSlot)
|
|
// ReportSlot 단계에서는 지그 1회전(~320ms) 경과 → LBAD MES Report(~100ms) 보장 수신
|
|
var report = _CellScheduler.ActiveReportSlot;
|
|
bool lbadReceived = !double.IsNaN(report.LbadAvg);
|
|
string lbadAvg = lbadReceived ? report.LbadAvg.ToString("F3") : "0";
|
|
if (!lbadReceived)
|
|
Log.W($"[LBAD] ReportSlot LbadAvg 미수신 >> CellId({report.CellId})");
|
|
// 레시피·장비 계측값은 Status에서 읽음 (TodayProduct/TotalProduct 포함)
|
|
var wd = Status.GetProcessingWeldingData();
|
|
|
|
string reportDataLocalPath = _CellTracker._CellTrackerConfig.reportDataLocalPath;
|
|
string equipName = _CellTracker._CellTrackerConfig.EquipmentName;
|
|
|
|
string directory = string.Format(reportDataLocalPath + "{0}", strDate);
|
|
if (!Directory.Exists(directory))
|
|
Directory.CreateDirectory(directory);
|
|
|
|
string strPath = string.Format(reportDataLocalPath + "{0}\\{1}_{2}_{3}.csv",
|
|
strDate, strDate, "H52A_" + equipName, strTime.Substring(0, 2));
|
|
|
|
// ECS 전송용 strData 빌드
|
|
string strData = string.Format(
|
|
"Date={0},Time={1},LotID={2},CellID={3}," +
|
|
equipName + "DATA001={4}," +
|
|
equipName + "DATA002={5}," +
|
|
equipName + "DATA003={6}," +
|
|
equipName + "DATA004={7}," +
|
|
equipName + "DATA005={8}," +
|
|
equipName + "DATA006={9}," +
|
|
equipName + "DATA007={10}," +
|
|
equipName + "DATA008={11}," +
|
|
equipName + "DATA009={12}",
|
|
strDate, strTime, report.LotId, report.CellId,
|
|
report.InnerCellInfo.RecipeNo, report.InnerCellInfo.LaserPowerCore, report.InnerCellInfo.MarkSpeed, report.InnerCellInfo.FocalLength,
|
|
report.JigNo, report.WeldingSpanTime.TotalMilliseconds.ToString(), lbadAvg,
|
|
wd.TodayProduct, wd.TotalProduct);
|
|
_CellTracker.SendEcsData(report.CellId, strPath, strData + "");
|
|
|
|
Log.I($"Report to CellTracker >> [LotID({report.LotId})] [CellID({report.CellId})] [JigNo({report.JigNo})] [LbadAvg({lbadAvg})]");
|
|
Log.I($"Cell Data : {strData}");
|
|
|
|
// 로컬 CSV 저장
|
|
string strItem = "//Date, Time, LotID, CellID, RecipeNo, LaserPower, MarkSpeed, FocalLength, HeadNo, WeldingTime, LaserPowerByLBAD, TodayCount, TotalCount";
|
|
string strCsvData = string.Format("{0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}, {10}, {11}, {12}",
|
|
strDate, strTime, report.LotId, report.CellId,
|
|
report.InnerCellInfo.RecipeNo, report.InnerCellInfo.LaserPowerCore, report.InnerCellInfo.MarkSpeed, report.InnerCellInfo.FocalLength,
|
|
report.JigNo, report.WeldingSpanTime.TotalMilliseconds.ToString(), lbadAvg,
|
|
wd.TodayProduct, wd.TotalProduct);
|
|
WeldingCommon.Utils.ExcelManagementUnit.AppendStringInCSV(strPath, strItem, strCsvData);
|
|
|
|
// 보고 완료: Complete.ReportComplete=true 설정 + pendingSlot 클리어
|
|
_CellScheduler.CompleteReport();
|
|
|
|
return SequenceAction.Ok();
|
|
}
|
|
|
|
//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<string> 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;
|
|
}
|
|
}
|
|
|
|
private string CheckCanWelded(SequenceContext context, string id, ICollection<string> branches, SequenceActionResult param)
|
|
{
|
|
// ── 재시작 전 미완료 보고 우선 처리 ─────────────────────────────────────────
|
|
// SavePendingComplete()로 보존된 슬롯이 있으면 정상 파이프라인(Complete)보다 우선.
|
|
if (_CellScheduler.HasPendingComplete) {
|
|
var p = _CellScheduler.PendingComplete;
|
|
Log.I($"[PendingReport] 재시작 전 미완료 보고: LotId={p.LotId}, CellId={p.CellId}, JigNo={p.JigNo}");
|
|
return SEQ_REPORT_CELL_RESULT;
|
|
}
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
// 정상 보고 경로:
|
|
// - Complete(Shift로 자동 승격)에 용접 완료된 셀이 있고 아직 보고 미완인 경우
|
|
// - 예열 구간(Complete=null 또는 WeldComplete=false)은 자연스럽게 통과
|
|
if (_CellScheduler.HasReport)
|
|
return SEQ_REPORT_CELL_RESULT;
|
|
|
|
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, "SeqAutoRunCCWH52A");
|
|
|
|
context.TryGetValue(ContextKey.KEY_SEQUENCE_RESULT, out bool seqResult);
|
|
SeqAutoRunCCWH52A_OutParam result = new SeqAutoRunCCWH52A_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, "SeqAutoRunCCWH52A");
|
|
|
|
OnSeqActionStop();
|
|
|
|
Log.I("SequenceAction Ok : OnForceError");
|
|
}
|
|
|
|
private void OnStopped(SequenceContext context)
|
|
{
|
|
context.Settings.Save(CommonSettingIds.SystemParameter, _SystemParameter, "SeqAutoRunCCWH52A");
|
|
|
|
OnSeqActionStop();
|
|
|
|
Log.I("SequenceAction Ok : OnStopped");
|
|
}
|
|
|
|
private void OnSeqActionStop()
|
|
{
|
|
Status.EquipmentMode = eEquipmentMode.MANUAL;
|
|
|
|
foreach (var info in ProcessManager.Instance.ReceivedCellInfoQueue.Select((value, index) => (value, index))) {
|
|
Log.I($"[{info.index}]Queue >> CellId({info.value.CellId}) LotId({info.value.LotId})");
|
|
}
|
|
|
|
// 정지 시점 파이프라인 상태 로그 (재시작 후 복원 여부 사전 확인용)
|
|
if (_CellScheduler.Prefetch1 != null && !string.IsNullOrEmpty(_CellScheduler.Prefetch1.CellId))
|
|
Log.I($"Stoped Seq >> Prefetch1[Jig{_CellScheduler.Prefetch1.JigNo}] CellId={_CellScheduler.Prefetch1.CellId}, WeldComplete={_CellScheduler.Prefetch1.WeldComplete}");
|
|
if (_CellScheduler.Prefetch2 != null && !string.IsNullOrEmpty(_CellScheduler.Prefetch2.CellId))
|
|
Log.I($"Stoped Seq >> Prefetch2[Jig{_CellScheduler.Prefetch2.JigNo}] CellId={_CellScheduler.Prefetch2.CellId}, WeldComplete={_CellScheduler.Prefetch2.WeldComplete}");
|
|
|
|
//PLC All BIT OFF
|
|
for (int i = 0; i < _RotaryJigCount; i++)
|
|
_PLC.WriteBit(eBitOutput.PC_WELD_COMPLETE_1 + i, false);
|
|
_PLC.WriteBit(eBitOutput.PC_AUTO_STATUS, false);
|
|
Log.I("[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 SeqAutoRunCCWH52A_InitParam : InitParam
|
|
{
|
|
public ScoutSeqParams ScoutSeqParams { get; set; }
|
|
public bool UseStream { get; set; }
|
|
|
|
}
|
|
|
|
public class SeqAutoRunCCWH52A_OutParam : OutParam
|
|
{
|
|
|
|
}
|
|
} |