to3796zw 25edd79325 feat: Page Change bit로 Recipe+Page 전환 통합
PLC가 Page Change bit(0x08) 하나만 트리거로 보내면 Recipe No와
Page No를 함께 읽어 Recipe를 먼저 전환한 뒤 Page를 전환하도록 변경.
Recipe Change bit(0x09)는 트리거로 미사용(enum 정의는 보존).

- CheckRecipeNo Decision 제거, 흐름 재배선(CheckPageNo→Recipe→Page)
- Recipe No 1회 읽기, 0/음수/미등록/동일이면 전환 생략(정지 안 함)
- 실제 전환 시 ActivateRecipe 후 Dispatcher.Invoke(OnActiveReloaded)로
  Model.Layers 동기 갱신 + 페이지 검증/레이어 선택을 UI 스레드 직렬화
  (ActivateRecipe-Layers 비동기 race 차단)
- PC_RECIPE_CHANGE_COMPLETE 미사용 처리, PC_RECIPE_NO 에코는 유지

VS Rebuild(Debug|x64) EXITCODE=0 확인.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 00:04:35 +09:00

1138 lines
52 KiB
C#

using Aurora;
using Aurora.Api;
using Aurora.Api.Manager;
using Aurora.View;
using AuroraIO;
using AuroraLaserScanner;
using AuroraPLC;
using CoreUnit.Control.UserControl.Canvas;
using CoreUnit.Scanner.UserControl.ScannerCanvas;
using ILSOS_PrismaticRD_PTOP.Device;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using VisionCommon;
using WeldingCommon;
using WeldingCommon.Devices;
using WeldingCommon.Devices.Laser;
using WeldingCommon.Sequences;
using WeldingCommon.Utils;
using WeldingCommon.Alarm;
using ILSOS_PrismaticRD_PTOP.Alarm;
using ILSOS_PrismaticRD_PTOP.Settings;
using static AuroraIO.AJINEXTEK.AjinIO.IOModuleInfo;
using static ILSOS_PrismaticRD_PTOP.Device.PLCControllerPTOP;
using static WeldingCommon.Devices.PLCController;
using Application = System.Windows.Application;
using SystemParameter = WeldingCommon.Settings.SystemParameter;
namespace ILSOS_PrismaticRD_PTOP.Sequences
{
/*
*
*
[Sequence_Enter] 시퀀스 진입 - 레시피 초기화 및 장비 연결
[CheckInitCondition] 조건 확인 완료 → CheckPageNo 이동
[CheckPageNo] PLC_PAGE_CHANGE_REQ 신호 대기 중...
[CheckPageNo] PLC_PAGE_CHANGE_REQ ON 감지 → CheckPageNoComp 이동
[CheckPageNoComp] 페이지 확정 - PC_PAGE_NO: [1] 에코 완료
[SetUploadWeldingList] Group Count: [2], Total Line Count: [34]
[WeldingStartReadBit] PLC_WELD_READY_REQ ON 감지 → WaitWeldingStart 이동
[WaitWeldingStart] READY_ACK/BUSY ON, COMPLETE OFF 완료
[ExecuteWelding] Laser Gate ON (Core+Ring)
[ExecuteWelding] Gate ON Delay 15ms 완료 → ExecuteList 시작
[ExecuteWelding] WaitForListTaskCompleted 완료
[ExecuteWelding] Laser Gate OFF (Core+Ring)
[ExecuteWelding] 용접 완료 - TactTime: [285ms]
[CheckCycleStop] 사이클 계속 진행 → CheckPageNo 이동
*
*
*/
class SeqAutoRunPTOP
{
static readonly Logger Log = new Logger("SeqAutoRunPTOP");
static readonly IAuroraEnvironment aurora; // 완료
const string SEQ_ENTER = "[SeqAutoRunPTOP] SEQ_ENTER"; // inputParam 으로 받자.
const string SEQ_CHECK_INIT_CONDITION = "[SeqAutoRunPTOP] Check Init Condition"; // Check Init Condition
const string SEQ_CHECK_RECIPENO_COMPLETE = "[SeqAutoRunPTOP] CHECK RecipeNo Complete"; // 260618 Recipe Change
const string SEQ_CHECK_PAGENO = "[SeqAutoRunPTOP] CHECK PageNo";
const string SEQ_CHECK_PAGENO_COMPLETE = "[SeqAutoRunPTOP] CHECK PageNo Complete"; // 260513
const string SEQ_CHECK_WELDING_START_READ_BIT = "[SeqAutoRunPTOP] Check Welding Start ReadBit"; // 26 05 13
const string SEQ_WAIT_WELDING_START = "[SeqAutoRunPTOP] Wait Welding Start";
#region 티칭런에 있던 주요 노드 복사
const string SEQ_SET_PARAMS_UPLOAD_WELDING_LIST_SEQ = "[SeqAutoRunPTOP] Set Sequence Param (UploadWeldingList Seq)";
const string SEQ_DO_ID_UPLOAD_WELDING_LIST_SEQ = "[SeqAutoRunPTOP] Process (UploadWeldingList Seq)";
const string SEQ_GET_RESULT_UPLOAD_WELDING_LIST_SEQ = "[SeqAutoRunPTOP] Get Sequence Result (UploadWeldingList Seq)";
#endregion
const string SEQ_EXECUTE_WELDING = "[SeqAutoRunPTOP] Execute Welding";
const string SEQ_CHECK_CYCLE_STOP = "[SeqAutoRunPTOP] Check Cycle Stop";
const string SEQ_SAVE_RESULT_DATA = "[SeqAutoRunPTOP] Save Result Data";
const string SEQ_WRITE_COMPLETION_PLC_BIT = "[SeqAutoRunPTOP] Write Completion PLC Bit";
const string SEQ_COMPLETE = "[SeqAutoRunPTOP] SEQ_COMPLETE"; // 완료
public enum ePrismaticDOAddress
{
CoreLaser_Control = 9,
CoreLaser_ErrorClearing,
RingLaser_Control = 13,
RingLaser_ErrorClearing,
EX_Red_Control = 17,
}
static class ContextKey
{
public const string KEY_SEQUENCE_RESULT = "KEY_SEQUENCE_RESULT_SeqAutoRunPTOP";
public const string KEY_SEQUENCE_START_TIME = "KEY_SEQUENCE_START_TIME_SeqAutoRunPTOP";
}
public IAuroraEnvironment AuroraEnv { get; set; }
// 장비 3개만 initDev
private Scanner _Scanner;
private PLCControllerPTOP _PLC;
//private PLCController _PLC;
private IODevice _IO;
private SystemParameter _SystemParameter;
private IRecipeManager _RecipeManager;
private ScannerCanvasUserCtrl _ScannerCanvasUserCtrl; //스캐너 동작관련 객체 선언
private SeqTeachingRun_InitParam _InitParam; // Enter에 기본으로 적용할 Param
//
const int PLC_TIMEOUT = 60; // sec, Timeout은 시퀀스 정지가 아니라 타임아웃 시간마다 로그만 남기도록 한다
private eListNo _WeldingScannerListNo;
private List<IDrawObject> _SelectedObjectList;
private static TimeSpan _WeldingSpanTime; //TeacingRun 시퀀스에서 용접 소요시간
private int _CurrentPageNo;
private short plcPageNo = 0;// [0] 페이지 1 =음극 A [1]페이지 2= 양극 P(C)
private List<short> plcPageNoList = new List<short>();
private short plcRecipeNo = 0; // 260618 PLC Recipe No (11/21/22/31/32)
int laserEmissionGateDelayMs = 15; // 260518
public SequenceBuilder Builder()
{
var sb = new SequenceBuilder(100000, true);
sb.AddAction(SEQ_ENTER, Sequence_Enter);
sb.AddDecision(SEQ_CHECK_INIT_CONDITION, CheckInitCondition); //분기
sb.AddAction(SEQ_CHECK_PAGENO, CheckPageNo); // 단일 트리거: PLC_PAGE_CHANGE_REQ(0x08) 대기
sb.AddAction(SEQ_CHECK_RECIPENO_COMPLETE, CheckRecipeNoComp); // Recipe 먼저 확정/전환 (Page보다 상위)
sb.AddAction(SEQ_CHECK_PAGENO_COMPLETE, CheckPageNoComp); // 그다음 Page 확정
#region
sb.AddAction(SEQ_SET_PARAMS_UPLOAD_WELDING_LIST_SEQ, SetUploadWeldingList_SeqParams);
sb.AddSubsequence(SEQ_DO_ID_UPLOAD_WELDING_LIST_SEQ, CommonSequenceManager.GetSeqBuilder_SeqUploadWeldingList());
sb.AddAction(SEQ_GET_RESULT_UPLOAD_WELDING_LIST_SEQ, GetUploadWeldingList_SeqResult);
#endregion
// 260513 plc
sb.AddAction(SEQ_CHECK_WELDING_START_READ_BIT, WeldingStartReadBit);
sb.AddAction(SEQ_WAIT_WELDING_START, WaitWeldingStart);
sb.AddAction(SEQ_EXECUTE_WELDING, ExecuteWelding);
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_COMPLETE, Complete); // Enter로 올린다.
sb.Flows(sb.Init
> sb[SEQ_ENTER]
> sb[SEQ_CHECK_INIT_CONDITION]
> sb[SEQ_CHECK_PAGENO] // 단일 트리거 PLC_PAGE_CHANGE_REQ(0x08) ON 대기
> sb[SEQ_CHECK_RECIPENO_COMPLETE] // Recipe 먼저 확정/전환
> sb[SEQ_CHECK_PAGENO_COMPLETE] // 그다음 Page 확정
#region TeachingRun 시퀸스의 중간 용접리스트 생성 구간
> sb[SEQ_SET_PARAMS_UPLOAD_WELDING_LIST_SEQ] // 1 파라미터 세팅 - TeachingRun 노드 참고
> sb[SEQ_DO_ID_UPLOAD_WELDING_LIST_SEQ] // 2 리스트 업로드 (서브시퀀스) - TeachingRun 노드 참고
> sb[SEQ_GET_RESULT_UPLOAD_WELDING_LIST_SEQ] // 3 업로드 결과 확인 - TeachingRun 노드 참고
#endregion
> sb[SEQ_CHECK_WELDING_START_READ_BIT] // while read bit
> sb[SEQ_WAIT_WELDING_START] // 4 업로드 완료 PLC 완료 신호 및 BUSY 신호로 변경
> sb[SEQ_EXECUTE_WELDING] // 5 Scanner 실행 (실제 용접)
> sb[SEQ_CHECK_CYCLE_STOP]
> sb[SEQ_SAVE_RESULT_DATA]
> sb[SEQ_WRITE_COMPLETION_PLC_BIT] // PLC 비트 완료 신호 보내기
> sb[SEQ_COMPLETE] // 다시 enter로 올리기
> sb.Final);
sb.Flows(sb[SEQ_CHECK_CYCLE_STOP] > sb[SEQ_CHECK_PAGENO]); // 루프백: 매 사이클 Page Change bit(0x08) 대기부터 재확인
sb.Flows(sb[SEQ_CHECK_INIT_CONDITION] > sb[SEQ_COMPLETE] > sb.Final);
sb.OnStopped(OnStopped);
sb.OnForceError(OnForceError);
sb.Draw("SeqAutoRunPTOP.pdf");
return sb;
}
private bool InitSequenceVariable(SequenceContext context)
{
Log.I($"[InitSequenceVariable] 노드 Start");
var mainPage = CommonHelper.GetMainPage();
_ScannerCanvasUserCtrl = mainPage.ScannerCanvasCtrlTeaching;
_WeldingScannerListNo = eListNo.LIST1;
if (_InitParam.IsObjectSelectAll)
{
//_SelectedObjectList = _ScannerCanvasUserCtrl.Model.GetLayer(_CurrentPageNo).Objects;
_SelectedObjectList = _ScannerCanvasUserCtrl.Model.ActiveLayer.Objects;
}
else
{
_SelectedObjectList = _ScannerCanvasUserCtrl.Canvas.Model.SelectedObjects;
}
//Status.GetProcessingWeldingData().WeldingScannerListNo = _WeldingScannerListNo;
//Status.GetProcessingWeldingData().SelectedDrawObjectList = _SelectedObjectList;
Log.I($"[InitSequenceVariable] 노드 End");
return true;
}
public SeqAutoRunPTOP()
{
_SystemParameter = Application.Current.AuroraEnv().SettingEntry[CommonSettingIds.SystemParameter] as SystemParameter;
}
private SequenceActionResult Sequence_Enter(SequenceContext context, string id, SequenceActionResult param)
{
Log.I($"[Sequence_Enter] 시퀀스 진입 - 레시피 초기화 및 장비 연결");
// 디버그 끝내고 삭제
MessageUtil.ShowToast("Sequence_Enter");
//Thread.Sleep(1000);
// 260618 RecipeManager 미리 확보(Recipe Change 노드 CheckRecipeNoComp에서 사용)
_RecipeManager = Application.Current.AuroraEnv().RecipeManager;
// 활성화된레시피ID
var activatedRecipeId = _RecipeManager.ActivatedRecipe().Id;
var recipeName = RecipeHelper.SearchRecipeItem(activatedRecipeId);
_InitParam = param.Param as SeqTeachingRun_InitParam; // 260511 - param NULL
Status.GetProcessingWeldingData().AutoRunStartTime = DateTime.Now;
Status.GetProcessingWeldingData().Clear();
Log.I($"[Sequence_Enter] 레시피 [{activatedRecipeId}], {recipeName.ToString()} 활성화, 장비 초기화 완료");
//레시피의 현재 변수 false인지 여부 확인할 것 .
#region [TEST_BYPASS] Alarm Check - Init Variable
/*
if (InitSequenceVariable(context) == false)
{
AlarmManager.Alarm(AlarmCode.ALARM_SEQUENCE_TeachingRun_InitVariableError);
return null;
}
*/
InitSequenceVariable(context);
#endregion
// 장비 체크
InitDev(context);
_PLC.WriteBit(eBitOutput.PC_AUTO_STATUS, true); // 음극
_PLC.WriteBit(eBitOutput.PC_AUTO_STATUS_P, true); //양극
#region [TEST_BYPASS] Alarm Check - DeFvice Ready
/*
if (!CheckDeviceReady(context))
{ //Check Device
AlarmManager.Alarm(AlarmCode.ALARM_SEQUENCE_AutoRun_DeviceNotReady);
Log.E("The devices are not active.");
return null;
}
*/
#endregion
Log.I($"[Sequence_Enter] 장비 초기화 완료");
Thread.Sleep(5); // 추후 제거
Log.I($"[Sequence_Enter] 완료 → CheckInitCondition 이동");
return SequenceAction.Ok();
}
private string CheckInitCondition(SequenceContext context, string id, ICollection<string> branches, SequenceActionResult param)
{
Log.I($"[CheckInitCondition] 초기 조건 확인 중");
Status.SetWorkingPosition(eWorkingPosition.CHECKING_CONDITION);
//if (!CheckCondition(context))
//{
// AlarmManager.Alarm(AlarmCode.ALARM_SEQUENCE_AutoRun_CheckInitCondition);
// Log.E("Condition is not in state to start sequence.");
// context.PutValue(ContextKey.KEY_SEQUENCE_RESULT, false);
// return SEQ_COMPLETE;
//}
Thread.Sleep(5);
Log.I($"[CheckInitCondition] 조건 확인 완료 → CheckPageNo 이동");
return SEQ_CHECK_PAGENO;
}
private int _CenterBeamPower;
private int _RingBeamPower;
private int _CenterRingRatio;
// CheckRecipeNoComp: 단일 트리거(PAGE_CHANGE_REQ 0x08) 통합 — Page보다 먼저 Recipe를 확정/전환.
// PLC_RECIPE_NO 1회 읽기 → 0/미등록/현재활성과 동일이면 전환·워드 재기록 생략(시퀀스 정지 없음).
// 실제로 다를 때만 ActivateRecipe + UI 스레드 동기 reload(Model.Layers 갱신 보장) + PC_RECIPE_NO 에코.
private SequenceActionResult CheckRecipeNoComp(SequenceContext context, string id, SequenceActionResult param)
{
// Recipe No 1회 읽기 (Page Change bit와 함께 래치되어 안정적이라고 가정 → 노이즈 검증 생략)
plcRecipeNo = _PLC.ReadWord(eWordInput.PLC_RECIPE_NO).data;
Log.I($"[CheckRecipeNoComp] PLC_RECIPE_NO: [{plcRecipeNo}]");
// 0이면 레시피 전환 의사 없음 → 현 Recipe 유지하고 Page로
if (plcRecipeNo == 0)
{
Log.I("[CheckRecipeNoComp] PLC_RECIPE_NO=0 → 레시피 전환 생략, 현 Recipe 유지 → CheckPageNoComp 이동");
return SequenceAction.Ok();
}
// 등록 레시피 조회(.Id 1:1, 하드코딩 금지). 미등록이면 전환 생략(정지 안 함)
var recipe = _RecipeManager.Recipes().FirstOrDefault(x => x.Id == plcRecipeNo);
if (recipe == null)
{
Log.W($"[CheckRecipeNoComp] {plcRecipeNo}번 레시피 미등록 → 전환 생략, 현 Recipe 유지");
return SequenceAction.Ok();
}
// 이미 활성 레시피면 재활성화·워드 재기록 생략 (매 페이지마다 PC_RECIPE_NO 워드 변경 금지)
var preActivated = _RecipeManager.ActivatedRecipe();
if (preActivated != null && preActivated.Id == recipe.Id)
{
Log.I($"[CheckRecipeNoComp] 이미 활성 레시피({recipe.Id}) → 재활성화 생략 → CheckPageNoComp 이동");
return SequenceAction.Ok();
}
// ── 실제 전환 ──
_RecipeManager.ActivateRecipe(recipe.Id, "PLC Recipe Change");
Log.I($"[CheckRecipeNoComp] ActivateRecipe ({(preActivated == null ? -1 : preActivated.Id)} -> {recipe.Id})");
// ActivatedRecipe().Id 반영 대기 (비동기 race 주의)
var actStart = DateTime.Now;
while (!context.Stopped)
{
var cur = _RecipeManager.ActivatedRecipe();
if (cur != null && cur.Id == recipe.Id) break;
if (DateTime.Now.Subtract(actStart).TotalSeconds > PLC_TIMEOUT)
{
Log.W("[CheckRecipeNoComp] ActivateRecipe 반영 대기 TIMEOUT...");
actStart = DateTime.Now;
}
Thread.Sleep(10);
}
if (context.Stopped) return null;
// ★ Model.Layers를 새 레시피로 동기 갱신 (UI 스레드).
// ActivateRecipe의 이벤트 체인(ObserveOnDispatcher 2단)은 비동기라 Page 검증 전 Layers 갱신을 보장 못 함.
// RecipeHelper.OnActiveReloaded()가 ReloadRecipe를 동기 수행 → 반환 시점에 Layers가 새 레시피로 보장됨.
Application.Current.Dispatcher.Invoke(() => RecipeHelper.OnActiveReloaded());
Log.I("[CheckRecipeNoComp] Model.Layers 동기 reload 완료 (UI thread)");
// 에코 (실제 전환 시에만). PC_RECIPE_CHANGE_COMPLETE는 미사용(PLC가 감시 안 함).
_PLC.WriteWord(eWordOutput.PC_RECIPE_NO, plcRecipeNo);
Log.I($"---> [CheckRecipeNoComp] Recipe 전환 완료 - Set PC_RECIPE_NO: [{plcRecipeNo}] → CheckPageNoComp 이동");
return SequenceAction.Ok();
}
// 페이지 체크 - 페이지만 감시 AutoRunStatus는 실제 상위에서 동작 체크하고 시퀸스 동작한다.
private SequenceActionResult CheckPageNo(SequenceContext context, string id, SequenceActionResult param)
{
Log.I($"[CheckPageNo] PLC_PAGE_CHANGE_REQ 신호 대기 중...");
Status.SetWorkingPosition(eWorkingPosition.LOADING_CELL);
// 디버그 끝내고 삭제
//MessageUtil.ShowToast("CheckPageNo -> Wait...");
//Thread.Sleep(1000);
// 1. PLC_PAGE_CHANGE_REQ (bit 8) ON 대기
var interfaceStartTime = DateTime.Now;
while (!context.Stopped)
{
//plc
if (_PLC == null)
{
Log.E("[CheckPageNo] _PLC instance is null! Handshake cannot proceed.");
return null;
}
if (_PLC.ReadBit(eBitInput.PLC_PAGE_CHANGE_REQ).data)
{
Log.I("---> [CheckPageNo] Check OK -> PLC_PAGE_CHANGE_REQ : ON");
// 임시 미리 한 번 더 읽기 260515
//plcPageNo = _PLC.ReadWord(eWordInput.PLC_PAGE_NO).data;
//Log.I($"---> [CheckPageNo] Check OK -> PLC_PAGE_CHANGE_REQ : {plcPageNo}");
// 디버그 끝내고 삭제
//MessageUtil.ShowToast("PLC_PAGE_CHANGE_REQ : ON");
//Thread.Sleep(1000);
break;
}
else
{
//Log.I("[CheckPageNo] Detected PLC_PAGE_CHANGE_REQ: f");
}
if (DateTime.Now.Subtract(interfaceStartTime).TotalSeconds > PLC_TIMEOUT)
{
Log.W("[CheckPageNo] Waiting for PLC_PAGE_CHANGE_REQ -> TIMEOUT...");
interfaceStartTime = DateTime.Now;
}
Thread.Sleep(10);
}
if (context.Stopped) return null;
Log.I($"[CheckPageNo] PLC_PAGE_CHANGE_REQ ON 감지 → CheckPageNoComp 이동");
return SequenceAction.Ok();
}
private SequenceActionResult CheckPageNoComp(SequenceContext context, string id, SequenceActionResult param)
{
Log.I($"[CheckPageNoComp] PLC 페이지 번호 읽기 시작 (3회 연속 검증)");
Status.SetWorkingPosition(eWorkingPosition.LOADING_CELL);
int checknum = 0;
plcPageNoList = new List<short>();
while (!context.Stopped)
{
//Thread.Sleep(1000);
// 2. PLC 데이터 읽기 및 인덱스 변환 (1,2 -> 0,1)
//plcPageNo = _PLC.ReadWord(eWordInput.PLC_PAGE_NO).data;
plcPageNoList.Add(_PLC.ReadWord(eWordInput.PLC_PAGE_NO).data);
plcPageNo = plcPageNoList[checknum];
checknum++;
_CurrentPageNo = plcPageNo - 1; // 1-based to 0-based // page index
// 디버그 끝내고 삭제
//MessageUtil.ShowToast($"ReadWord -> {plcPageNo}");
Thread.Sleep(200);
string cellId = _PLC.CellId();
string lotId = _PLC.LotId();
Status.GetProcessingWeldingData().CellId = cellId;
Status.GetProcessingWeldingData().LotId = lotId;
Log.I($"[CheckPageNoComp]페이지 확인 [{checknum}/3] - PLC_Page: [{plcPageNo}], PC_Index: [{_CurrentPageNo}], CellId: [{cellId}], LotId: [{lotId}]");
if (checknum > 2)
{
// 260618: 상한 하드코딩(>2) 제거 → 활성 Recipe의 페이지 수(레이어 수)로 가변 검증 (Recipe별 2~5페이지)
// B-1: Model.Layers 접근을 UI 스레드로 직렬화 (레시피 reload와의 race 방지)
int recipePageCount = Application.Current.Dispatcher.Invoke(() => _ScannerCanvasUserCtrl.Model.Layers.Count());
if (plcPageNo > recipePageCount || plcPageNo < 1)
{
Log.E($"[CheckPageNoComp] Invalid PageNo received: {plcPageNo} (활성 Recipe 페이지 수: {recipePageCount})");
// 디버그 끝내고 삭제
//MessageUtil.ShowErrorToast($"ReadWord -> {plcPageNo}");
MessageUtil.ShowErrorToast($"ReadWord -> {plcPageNo}");
Thread.Sleep(3000);
// 나중에 알람 처리해.
// 에러 발생 시 시퀀스 중단 또는 기본값 처리 (현장 상황에 따라 선택)
//AlarmManager.Alarm(AlarmCode.ALARM_SEQUENCE_TeachingRun_InitVariableError);
return null;
}
else
{
//for (int i = 0; i > plcPageNoList.Count; i++)
//{
Log.E($"[CheckPageNoComp] List {plcPageNoList[0]}/{plcPageNoList[1]}/{plcPageNoList[2]}");
//}
break;
}
}
}
Thread.Sleep(5);
// 3. 에코 및 완료 비트(Ack) ON
_PLC.WriteWord(eWordOutput.PC_PAGE_NO, plcPageNo);
Log.I($"---> [CheckPageNoComp] Set PC_PAGE_NO: {plcPageNo}");
//Thread.Sleep(1000);
//MessageUtil.ShowErrorToast($"WriteWord -> {plcPageNo}");
//Thread.Sleep(1000);
_PLC.WriteBit(eBitOutput.PC_PAGE_CHANGE_COMPLETE, false);
Log.I("[CheckPageNoComp] Set PC_PAGE_CHANGE_COMPLETE false");
//Thread.Sleep(5);
// UI 페이지 업데이트 알림
//if (Application.Current != null && Application.Current.AuroraEnv() != null)
{
Application.Current.AuroraEnv().EventPublisher.Publish(new EventMessage("", CommonEventIds.EVENT_AUTO_RUN_UPDATE_PAGENUM, (int)_CurrentPageNo));
}
Thread.Sleep(5); // 추후 제거
Log.I($"[CheckPageNoComp] 페이지 확정 - PC_PAGE_NO: [{plcPageNo}] 에코 완료 → WeldingList 업로드 이동");
return SequenceAction.Ok();
}
private bool LaserReadyResponse()
{
Log.I($"[LaserReadyResponse] 노드 Start");
//if (ApplicationExt.IsSimulation)
Log.I($"[LaserReadyResponse] 노드 End");
return true;
}
private SequenceActionResult SetUploadWeldingList_SeqParams(SequenceContext context, string id, SequenceActionResult param)
{
Log.I($"[SetUploadWeldingList_SeqParams] PageNo [{_CurrentPageNo}] 용접 리스트 레이어 선택 시작");
Status.SetWorkingPosition(eWorkingPosition.UPLOADING_WELD_LIST);
#region 페이지 번호에 따라 용접 리스트 레이어 선택 됨. 아래 데이터가 정확한지는 확인 필요
// 페이지 번호에 따라 용접 리스트(레이어) 선택
int totalLineCount = 0;
// B-1: Model.Layers 접근(레이어 선택)을 UI 스레드로 직렬화 (레시피 reload와의 race 방지)
Application.Current.Dispatcher.Invoke(() =>
{
int layerCount = _ScannerCanvasUserCtrl.Model.Layers.Count();
if (layerCount > _CurrentPageNo && _CurrentPageNo >= 0)
{
var layer = _ScannerCanvasUserCtrl.Model.Layers[_CurrentPageNo];
_SelectedObjectList = layer.Objects;
Log.I($"[SetUploadWeldingList] Selected PageNo [{_CurrentPageNo}] → Layer Name: [{layer.No}], Object Count: [{_SelectedObjectList.Count()}]");
}
else
{
Log.E($"[SetUploadWeldingList] Invalid PageNo [{_CurrentPageNo}] (Total Layers: {layerCount}) → Fallback to Active Layer");
_SelectedObjectList = _ScannerCanvasUserCtrl.Model.ActiveLayer.Objects;
Log.W($"[SetUploadWeldingList] Fallback Layer Name: [{_ScannerCanvasUserCtrl.Model.ActiveLayer.No}], Object Count: [{_SelectedObjectList.Count()}]");
}
});
if (_SelectedObjectList == null || _SelectedObjectList.Count() == 0)
{
Log.E("[SetUploadWeldingList] No welding objects found! Sequence will fail.");
return null;
}
// 공통 로그 - if/else 밖으로 이동
var groups = _SelectedObjectList.OfType<Group>().ToList();
totalLineCount = groups.Sum(g => g.SubObjects.OfType<Line>().Count());
Log.I($"[SetUploadWeldingList] Group Count: [{groups.Count}], Total Line Count: [{totalLineCount}]");
for (int i = 0; i < groups.Count; i++)
{
int lineCount = groups[i].SubObjects.OfType<Line>().Count();
Log.I($"[SetUploadWeldingList] Group[{i}] Line Count: [{lineCount}]");
}
#endregion
// TeachingRun 에서는 CellMeasureCenterOffset 사용하지 않음. 0 으로 넘기자
SeqUploadWeldingList_InitParam seqParams = new SeqUploadWeldingList_InitParam()
{
NeedSetLaserTiming = _InitParam.NeedSetLaserTiming,
IncludeListStartEnd = true,
CellMeasureCenterOffsetX = 0.0,
CellMeasureCenterOffsetY = 0.0,
SelectedDrawObjectList = _SelectedObjectList, // 이게 중요 포인트
WeldingScannerListNo = _WeldingScannerListNo,
};
//Thread.Sleep(1000); // 추후 제거
Log.I($"[SetUploadWeldingList_SeqParams] 용접 파라미터 세팅 완료 → UploadWeldingList 서브시퀀스 실행");
return SequenceAction.Ok(seqParams);
}
private SequenceActionResult GetUploadWeldingList_SeqResult(SequenceContext context, string id, SequenceActionResult param)
{
Log.I($"[GetUploadWeldingList_SeqResult] 용접 리스트 업로드 결과 확인 중");
Status.SetWorkingPosition(eWorkingPosition.UPLOADING_WELD_LIST);
var resultOutParam = param.Param as SeqUploadWeldingList_OutParam;
if (resultOutParam.Success == false)
{
//Thread.Sleep(1000); // 추후 제거
Log.E($"[GetUploadWeldingList_SeqResult] 업로드 실패 → 시퀀스 중단");
return null;
}
//Thread.Sleep(1000); // 추후 제거
Log.I($"[GetUploadWeldingList_SeqResult] 업로드 성공 → WeldingStartReadBit 이동");
return SequenceAction.Ok();
}
// 260513 추가노드
private SequenceActionResult WeldingStartReadBit(SequenceContext context, string id, SequenceActionResult param)
{
Log.I($"---> [WeldingStartReadBit] PLC_WELD_READY_REQ 신호 대기 중... ");
// 1. PLC 용접 요청 대기
var interfaceStartTime = DateTime.Now;
while (!context.Stopped)
{
if (_PLC == null)
{
Log.E("[WeldingStartReadBit] _PLC instance is null!");
return null;
}
if (_PLC.ReadBit(eBitInput.PLC_WELD_READY_REQ).data)
{
Log.I($"[WeldingStartReadBit] Check OK -> PLC_WELD_READY_REQ : ON");
// 디버그 끝내고 삭제
MessageUtil.ShowToast("PLC_WELD_READY_REQ : ON");
//Thread.Sleep(1000);
break;
}
if (DateTime.Now.Subtract(interfaceStartTime).TotalSeconds > PLC_TIMEOUT)
{
Log.W($"[WeldingStartReadBit] PLC_WELD_READY_REQ -> TIMEOUT...");
interfaceStartTime = DateTime.Now;
}
Thread.Sleep(10);
}
Log.I($"---> [WeldingStartReadBit] PLC_WELD_READY_REQ ON 감지 → WaitWeldingStart 이동");
return SequenceAction.Ok();
}
private SequenceActionResult WaitWeldingStart(SequenceContext context, string id, SequenceActionResult param)
{
Log.I($"[---> WaitWeldingStart] 용접 준비 신호 송신 - PageNo: [{plcPageNo}] CurrentPage: {_CurrentPageNo + 1})");
Status.SetWorkingPosition(eWorkingPosition.WAITING_WELD_START);
if (plcPageNo == 1)
{
// 2. 준비 완료(Ack) 및 가동 중(Busy) 비트 ON
_PLC.WriteBit((int)eBitOutput.PC_WELD_READY_ACK, true);
_PLC.WriteBit((int)eBitOutput.PC_WELD_BUSY, true);
Thread.Sleep(200);
_PLC.WriteBit((int)eBitOutput.PC_WELD_COMPLETE, false);
Thread.Sleep(5);
Log.I($"[WaitWeldingStart] WriteBit Anode Welding Ready ACK TRUE");
Log.I($"[WaitWeldingStart] WriteBit Anode Welding Busy TRUE");
Log.I($"[WaitWeldingStart] WriteBit Anode Welding Comp False");
//Thread.Sleep(10);
}
else if (plcPageNo == 2)
{
// 2. 준비 완료(Ack) 및 가동 중(Busy) 비트 ON
_PLC.WriteBit((int)eBitOutput.PC_WELD_P_READY_REQ, true);
_PLC.WriteBit((int)eBitOutput.PC_WELD_P_BUSY, true);
Thread.Sleep(200);
_PLC.WriteBit((int)eBitOutput.PC_WELD_P_COMP_ACK, false);
Thread.Sleep(5);
Log.I($"[WaitWeldingStart] WriteBit Cathode Welding Ready REQ TRUE");
Log.I($"[WaitWeldingStart] WriteBit Cathode Welding Busy TRUE");
Log.I($"[WaitWeldingStart] WriteBit Cathode Welding Comp False");
//Thread.Sleep(10);
}
else // PAGE NUM = 0
{
Log.I($"[WaitWeldingStart] Page num 1,2 etc..");
}
// 디버그 끝내고 삭제
//MessageUtil.ShowToast("WaitWeldingStart -> WriteBit True");
//Thread.Sleep(3000);
Log.I($"---> [WaitWeldingStart] READY_ACK/BUSY ON, COMPLETE OFF 완료 → ExecuteWelding 이동");
return SequenceAction.Ok();
}
// 이곳에서 SeqTeachingRun.cs 를 태운다. (레시피의 WeldingRun 호출 구간)
private SequenceActionResult ExecuteWelding(SequenceContext context, string id, SequenceActionResult param)
{
Log.I($"[ExecuteWelding] 용접 실행 시작 - PageNo: [{plcPageNo}], ListNo: [{_WeldingScannerListNo}]"); // ← 여기
#region 코드 확인 필요
Status.SetWorkingPosition(eWorkingPosition.EXECUTING_WELD); // 현재 시퀸스 위치
// === Head Theta 인터락: 수평/수직(±tol)에서만 발진 허용 (체크박스 사용 시) ===
var thetaSetting = Application.Current.AuroraEnv().SettingEntry[SettingIds.LaserInterlock] as LaserInterlockSetting;
if (thetaSetting != null && thetaSetting.HeadThetaInterlockEnabled)
{
bool blocked = false;
if (_PLC == null || !_PLC.IsActivated)
{
Log.W("[ExecuteWelding] HeadTheta 인터락 - PLC 미연결/미활성 → 발진 차단");
blocked = true; // fail-closed
}
else
{
short thetaRaw = _PLC.ReadWord(eWordInput.HEAD_THETA_POSITION).data;
double scale = thetaSetting.HeadThetaPlcScale != 0 ? thetaSetting.HeadThetaPlcScale : 1; // 0 가드
double thetaDeg = thetaRaw / scale;
bool okH = Math.Abs(thetaDeg - thetaSetting.HeadThetaHorizontal) <= thetaSetting.HeadThetaTolerance;
bool okV = Math.Abs(thetaDeg - thetaSetting.HeadThetaVertical) <= thetaSetting.HeadThetaTolerance;
Log.I($"[ExecuteWelding] HeadTheta raw={thetaRaw}, deg={thetaDeg:F1} (H={thetaSetting.HeadThetaHorizontal}/V={thetaSetting.HeadThetaVertical}/±{thetaSetting.HeadThetaTolerance})");
if (!okH && !okV)
{
Log.W($"[ExecuteWelding] HeadTheta 인터락 → 수평/수직 ±{thetaSetting.HeadThetaTolerance}° 벗어남(deg={thetaDeg:F1}) → 발진 차단");
blocked = true;
}
}
if (blocked)
{
try { AlarmManager.Alarm(AlarmCode_PTOP.ALARM_LASER_HEAD_THETA_INTERLOCK); }
catch (Exception ex) { Log.E($"[ExecuteWelding] HeadTheta 알람 표시 실패(테이블 미등록?): {ex.Message}"); }
return null; // 발진 차단 + 시퀀스 중단
}
}
_IO._iOUnit.WriteDoBit(Convert.ToInt32(ePrismaticDOAddress.CoreLaser_Control), true);
_IO._iOUnit.WriteDoBit(Convert.ToInt32(ePrismaticDOAddress.RingLaser_Control), true);
Log.I($"[ExecuteWelding] Laser Gate ON (Core+Ring)");
Thread.Sleep(laserEmissionGateDelayMs); // 260518 매뉴얼 기준 ≥10ms, 여유분 포함 =15ms 적용함.
Log.I($"[ExecuteWelding] Gate ON Delay {laserEmissionGateDelayMs}ms 완료 → ExecuteList 시작");
// ── SeqTeachingRun SubSequence 실행 ──────────────────────────────
DateTime weldingTime = DateTime.Now;
Status.GetProcessingWeldingData().WeldingStartTime = weldingTime;
_Scanner.ExecuteList(_WeldingScannerListNo); // 레이저 출사 막을 땐 주석
Log.I($"[ExecuteWelding] ExecuteList 시작 - ListNo: {_WeldingScannerListNo}");
_Scanner.WaitForListTaskCompleted(1000);//(_SystemParameter.WeldLinesTaskTimeOut);
Log.I($"[ExecuteWelding] WaitForListTaskCompleted 완료");
#region Gate OFF 위치 변경 - 용접 완료 즉시 OFF (TruPulse nano 매뉴얼 기준 ≥10ms)
// ↓ 이 두 줄 삭제
_IO._iOUnit.WriteDoBit(Convert.ToInt32(ePrismaticDOAddress.CoreLaser_Control), false);
_IO._iOUnit.WriteDoBit(Convert.ToInt32(ePrismaticDOAddress.RingLaser_Control), false);
Log.I($"[ExecuteWelding] Laser Gate OFF (Core+Ring)");
#endregion
_WeldingSpanTime = DateTime.Now.Subtract(weldingTime);
Status.GetProcessingWeldingData().WeldingEndTime = DateTime.Now;
if (_CurrentPageNo == 1)
{
Log.D(string.Format($"[ExecuteWelding] -->(Wedling TackTime) {_CurrentPageNo} Welding Complete Time : {_WeldingSpanTime.TotalMilliseconds}"));
}
else
{
Log.D(string.Format($"[ExecuteWelding] -->(Wedling TackTime) {_CurrentPageNo} Welding Complete Time : {_WeldingSpanTime.TotalMilliseconds}"));
}
double dExecuteListTime = _Scanner.GetTime();
//Log.I(string.Format($"[ExecuteWelding] ExecuteList Time : {dExecuteListTime * 1000} msec"));
// File Format Record Test
weldingTime = DateTime.Now;
_WeldingSpanTime = DateTime.Now.Subtract(weldingTime);
Log.D(string.Format($"[ExecuteWelding] SaveScannerRecordFileForamt Time : {_WeldingSpanTime.TotalMilliseconds}"));
weldingTime = DateTime.Now;
_Scanner.JumpAbs(0, 0, eFunctionType.COMMAND);
//용접 완료 후 CorrectionFile Vision으로 원복
_Scanner.SelectCorrectionTable(1);
_WeldingSpanTime = DateTime.Now.Subtract(weldingTime);
Log.D($"[ExecuteWelding] JumpAbs + SelectCorrectionTable Time: {_WeldingSpanTime.TotalMilliseconds}ms");
Log.D(string.Format($"[ExecuteWelding] Change SelectCorrectionTable Time : {_WeldingSpanTime.TotalMilliseconds}"));
if (plcPageNo == 1)
{
// 용접끝
_PLC.WriteBit((int)eBitOutput.PC_WELD_READY_ACK, false);
Log.I("[PLCInterface] PageNo[1] READY_ACK false");
Thread.Sleep(5);
// 용접중 신호로 변경
_PLC.WriteBit((int)eBitOutput.PC_WELD_BUSY, false);
Log.I("[PLCInterface] PageNo[1] BUSY false");
Thread.Sleep(5);
_PLC.WriteBit((int)eBitOutput.PC_WELD_COMPLETE, true);
Log.I("[PLCInterface] PageNo[1] COMPLETE true");
Thread.Sleep(5);
}
else if (plcPageNo == 2)
{
// 용접끝
_PLC.WriteBit((int)eBitOutput.PC_WELD_P_READY_REQ, false);
Log.I("[PLCInterface] PageNo[2] READY_REQ false");
Thread.Sleep(5);
// 용접중 신호로 변경
_PLC.WriteBit((int)eBitOutput.PC_WELD_P_BUSY, false);
Log.I("[PLCInterface] PageNo[2] BUSY false");
Thread.Sleep(5);
_PLC.WriteBit((int)eBitOutput.PC_WELD_P_COMP_ACK, true);
Log.I("[PLCInterface] PageNo[2] COMP_ACK true");
Thread.Sleep(5);
}
else
{
}
Status.SetWorkingPosition(eWorkingPosition.UNLOADING_CELL);
#endregion
Log.I($"[ExecuteWelding] 완료 → CheckCycleStop 이동");
return SequenceAction.Ok();
}
private string CheckCycleStop(SequenceContext context, string id, ICollection<string> branches, SequenceActionResult param)
{
Log.I($"[CheckCycleStop] 사이클 중지 여부 확인 중");
Status.SetWorkingPosition(eWorkingPosition.CHECKING_CYCLE);
Log.I($"[CheckCycleStop] 사이클 계속 진행 → CheckPageNo 이동");
return SEQ_CHECK_PAGENO;
}
private SequenceActionResult SaveResultData(SequenceContext context, string id, SequenceActionResult param)
{
Log.I($"[SaveResultData] 용접 결과 데이터 저장 시작");
Status.SetWorkingPosition(eWorkingPosition.REPORTING_RESULT);
// 스캐너의 결과를 저장하는 동작 추가
SaveScannerRecordFileForamt();
// 결과 Data 를 업데이트 함.
context.PutValue(ContextKey.KEY_SEQUENCE_RESULT, true);
Status.GetProcessingWeldingData().IsVisionNG = false;
Log.I("[SaveResultData] Welding Process Result: SUCCESS");
// 결과 데이터 저장하는 시퀸스
//Thread.Sleep(1000); // 추후 제거
Log.I($"[SaveResultData] 결과 저장 완료 → WriteCompletionPLCBit 이동");
return SequenceAction.Ok();
}
// 실제 동작 _Scanner에서
private void SaveScannerRecordFileForamt()
{
Log.I($"[SaveScannerRecordFileForamt] 노드 Start");
// ── 용접 완료 후 파형 데이터 저장 ────────────────────────────
_Scanner.SaveRecord("AutoRunTDC");
Log.I("[SaveResultData] SaveRecord Complete");
_Scanner.JumpAbs(0, 0, eFunctionType.COMMAND);
_Scanner.SelectCorrectionTable(1);
Log.D("[SaveResultData] JumpAbs + SelectCorrectionTable Complete");
//Thread.Sleep(1000); // 추후 제거
Log.I($"[SaveScannerRecordFileForamt] 노드 End");
}
// 이곳에서 bit 초기화 진행함. 260512
private SequenceActionResult WriteCompletionPLCBit(SequenceContext context, string id, SequenceActionResult param)
{
Log.I($"[WriteCompletionPLCBit] PLC 완료 비트 초기화 시작");
Status.SetWorkingPosition(eWorkingPosition.REPORTING_RESULT);
// 완료 비트 write 하는 Node 추가 필요. format 유지, ReceiveCellInfo 없음
if (RestoreDeviceStatus())
{
// 초기화 완료
Log.D("AutoRun Sequence WriteCompletionPLCBit");
}
//Thread.Sleep(1000); // 추후 제거
Log.I($"[WriteCompletionPLCBit] PLC 비트 초기화 완료 → Complete 이동");
return SequenceAction.Ok();
}
private SequenceActionResult Complete(SequenceContext context, string id, SequenceActionResult param)
{
Log.I($"[Complete] 1사이클 완료 처리]");
context.TryGetValue(ContextKey.KEY_SEQUENCE_RESULT, out bool seqResult);
SeqAutoRunPTOP_OutParam result = new SeqAutoRunPTOP_OutParam() { Success = seqResult };
if (seqResult)
{
uint prevTotal = _SystemParameter.TotalProductCount;
uint prevToday = _SystemParameter.TodayProductCount;
_SystemParameter.TotalProductCount++;
_SystemParameter.TodayProductCount++;
_SystemParameter.LastProductTime = DateTime.Now;
//Application.Current.AuroraEnv().SettingEntry.Save(CommonSettingIds.SystemParameter, _SystemParameter);
Status.GetProcessingWeldingData().TotalProduct = _SystemParameter.TotalProductCount;
Status.GetProcessingWeldingData().TodayProduct = _SystemParameter.TodayProductCount;
Log.I($"[Complete] Product Count Updated - Total: {prevTotal} -> {_SystemParameter.TotalProductCount}, Today: {prevToday} -> {_SystemParameter.TodayProductCount}");
}
else
{
Status.GetProcessingWeldingData().IsVisionNG = true;
Log.W("[Complete] Sequence result was FAIL. Product count not incremented.");
}
Status.SetWorkingPosition(eWorkingPosition.DONE);
//Thread.Sleep(1000); // 추후 제거
Log.I($"[Complete] 카운트 업데이트 완료 → 다음 사이클 진입");
return SequenceAction.Ok(result);
}
private bool RestoreDeviceStatus()
{
Log.I($"[RestoreDeviceStatus] 노드 Start");
////PLC All BIT OFF
_PLC.WriteBit((int)eCommonBitOutput.PC_PAGE_CHANGE_COMPLETE, false); // 페이지 false
_PLC.WriteBit((int)eCommonBitOutput.PC_WELD_READY_ACK, false); // 용접 준비 false
_PLC.WriteBit((int)eCommonBitOutput.PC_WELD_BUSY, false); // 동작 중 false
_PLC.WriteBit((int)eCommonBitOutput.PC_WELD_COMPLETE, false); // 용접 완료 false
Log.I(string.Format("[PLCInterface]PC Anod Bit Off"));
_PLC.WriteBit((int)eCommonBitOutput.PC_WELD_P_READY_REQ, false); // 용접 준비 false
_PLC.WriteBit((int)eCommonBitOutput.PC_WELD_P_BUSY, false); // 동작 중 false
_PLC.WriteBit((int)eCommonBitOutput.PC_WELD_P_COMP_ACK, false); // 용접 완료 false
Log.I(string.Format("[PLCInterface]PC Cathode Bit Off"));
//Thread.Sleep(1000); // 추후 제거
Log.I($"[RestoreDeviceStatus] 노드 End");
return true;
}
// 장비 3개 가져오기 완료
private void InitDev(SequenceContext context)
{
Log.I($"[InitDev] 노드 Start");
_Scanner = context.GetLaserScanner();
_PLC = context.GetPLC() as PLCControllerPTOP;
_IO = context.GetIOUnit();
Log.I($"[InitDev] 노드 End");
}
private bool CheckDeviceReady(SequenceContext context)
{
Log.I($"[CheckDeviceReady] 노드 Start");
List<string> deviceListToCheck = new List<string>() { CommonDeviceIds.Scanner, CommonDeviceIds.Laser,
CommonDeviceIds.PLCDevice, CommonDeviceIds.IODevice };
bool isAllDeviceActivated = true;
foreach (string item in deviceListToCheck)
{
if (!context.Devices[item].IsActivated)
{
isAllDeviceActivated = isAllDeviceActivated && false;
}
}
Log.I($"[CheckDeviceReady] 노드 End");
return isAllDeviceActivated;
}
private bool CheckCondition(SequenceContext context)
{
Log.I($"[CheckCondition] 노드 Start");
// 체크 신호 plc
if (CheckCondition_Interlock())
{
if (InitializeDeviceStatus(context))
{
Log.I($"[CheckCondition] 노드 End");
return true;
}
}
Log.I($"[CheckCondition] 노드 End");
return false;
}
private bool CheckCondition_Interlock()
{
Log.I($"[CheckCondition_Interlock] 노드 Start");
// PLC, Data, IO 등 Interlock checking
Log.I($"[CheckCondition_Interlock] 노드 End");
return true;
}
private bool InitializeDeviceStatus(SequenceContext context)
{
Log.I($"[InitializeDeviceStatus] 노드 Start");
// PLC Interface AutoStatus Start
InitAutoRunBit();
// Laser Initialize
//_Laser.PilotLaserOff();
//_Laser.LaserOn();
//Thread.Sleep(100);
//if (!_Laser.GetLaserReady())
//{
// Log.E("ERROR Initialize Laser");
// return false;
//}
// Scanner Initialize
// Scanner 없음.
// OCT Initialize
// TODO: EnterSequenceModeOCT 와 함께 Initialize 하는 것을 재구현 필요함. 초반에 한번만 check 하고 돌아 갈수 있도록
// PLC Initialize
#region [TEST_BYPASS] Alarm Check - PLC_AUTO_STATUS
/*
if (_PLC.ReadBit((int)eBitInput.PLC_AUTO_STATUS).data == false)
{
Log.E(string.Format("ERROR Initialize PLC, Bit Off PLC_AUTO_STATUS"));
return false;
}
*/
#endregion
_IO._iOUnit.WriteDoBit(Convert.ToInt32(eADOAddress.PCLBADReadyStatus), true);
Log.I(string.Format($"[IOInterface]SET PCLBADReadyStatus true"));
Log.I($"[InitializeDeviceStatus] 노드 End");
return true;
}
private void InitAutoRunBit()
{
Log.I($"[InitAutoRunBit] 노드 Start");
Log.I(string.Format($"[PLCInterface] Start AutoRun PLC Bit Initialize"));
_PLC.WriteBit((int)eBitOutput.PC_WELD_READY_ACK, false);
Log.I(string.Format($"[PLCInterface]SET (-)PC_WELD_READY_ACK False"));
_PLC.WriteBit((int)eBitOutput.PC_WELD_BUSY, false);
Log.I(string.Format($"[PLCInterface]SET (-)PC_WELD_BUSY False"));
_PLC.WriteBit((int)eBitOutput.PC_WELD_COMPLETE, false);
Log.I(string.Format($"[PLCInterface]SET (-)PC_WELD_COMPLETE False"));
_PLC.WriteBit((int)eBitOutput.PC_AUTO_STATUS, true);
Log.I(string.Format($"[PLCInterface]SET (-)PC_AUTO_STATUS True"));
Log.I(string.Format($"[PLCInterface] Start AutoRun PLC Bit Initialize"));
_PLC.WriteBit((int)eBitOutput.PC_WELD_P_READY_REQ, false);
Log.I(string.Format($"[PLCInterface]SET (+)PC_WELD_READY_ACK False"));
_PLC.WriteBit((int)eBitOutput.PC_WELD_P_BUSY, false);
Log.I(string.Format($"[PLCInterface]SET (+)PC_WELD_BUSY False"));
_PLC.WriteBit((int)eBitOutput.PC_WELD_P_COMP_ACK, false);
Log.I(string.Format($"[PLCInterface]SET (+)PC_WELD_COMPLETE False"));
_PLC.WriteBit((int)eBitOutput.PC_AUTO_STATUS_P, true);
Log.I(string.Format($"[PLCInterface]SET (+)PC_AUTO_STATUS True"));
Log.I($"[InitAutoRunBit] 노드 End");
}
private void OnForceError(SequenceContext arg1, string arg2)
{
Log.I($"[OnForceError] 노드 Start");
OnSeqActionStop();
Log.I("SequenceAction Ok : OnForceError");
Log.I($"[OnForceError] 노드 End");
}
private void OnStopped(SequenceContext obj)
{
Log.I($"[OnStopped] 노드 Start");
OnSeqActionStop();
Log.I("SequenceAction Ok : OnStopped");
Log.I($"[OnStopped] 노드 End");
}
private void OnSeqActionStop()
{
Log.I($"[OnSeqActionStop] 시퀀스 강제 중지 - PLC/IO/Scanner 초기화 시작");
// PLC All BIT OFF
_PLC.WriteBit((int)eBitOutput.PC_AUTO_STATUS, false);
_PLC.WriteBit((int)eBitOutput.PC_WELD_READY_ACK, false);
_PLC.WriteBit((int)eBitOutput.PC_WELD_BUSY, false);
_PLC.WriteBit((int)eBitOutput.PC_WELD_COMPLETE, false);
Log.I("[OnSeqActionStop] PLC ANODE(-) Output All false");
_PLC.WriteBit((int)eBitOutput.PC_AUTO_STATUS_P, false);
_PLC.WriteBit((int)eBitOutput.PC_WELD_P_READY_REQ, false);
_PLC.WriteBit((int)eBitOutput.PC_WELD_P_COMP_ACK, false);
_PLC.WriteBit((int)eBitOutput.PC_WELD_P_BUSY, false);
Log.I("[OnSeqActionStop] PLC Cathod(+) Output All false");
_PLC.WriteBit((int)eBitOutput.PC_PAGE_CHANGE_COMPLETE, false);
Log.I("[OnSeqActionStop] PLC IO Output PAGECHANGE Init");
// 260512 워드
_PLC.WriteDWord((int)eWordOutput.PC_ERROR_CODE, 0);
_PLC.WriteDWord((int)eWordOutput.PC_PAGE_NO, 0);
_PLC.WriteDWord((int)eWordOutput.PC_RECIPE_NO, 0); // 260618 레시피 No init
Log.I("[OnSeqActionStop] PLC IO Output Word Init");
Log.I("[PLCInterface] PC All Bit Off (Auto, ReadyAck, Busy, Complete)");
// LBAD IO All BIT OFF
try
{
_IO._iOUnit.WriteDoBit(Convert.ToInt32(ePrismaticDOAddress.CoreLaser_Control), false);
_IO._iOUnit.WriteDoBit(Convert.ToInt32(ePrismaticDOAddress.RingLaser_Control), false);
//_IO._iOUnit.WriteDoBit(Convert.ToInt32(eADOAddress.PCLBADWeldingTrigger), false);
//_IO._iOUnit.WriteDoBit(Convert.ToInt32(eADOAddress.PCLBADReadyStatus), false);
Log.I("[IOInterface] PC All Bit Off (Laser Control, LBAD Trigger, LBAD Ready)");
}
catch (Exception ex)
{
Log.E($"[IOInterface] Failed to reset IO Bits: {ex.Message}");
}
// List 실행 종료 및 원점 복귀
_Scanner.StopExecution();
_Scanner.JumpAbs(0, 0, eFunctionType.COMMAND);
Log.I("[Scanner] StopExecution and JumpAbs(0,0)");
// 용접 완료 후 CorrectionFile Vision으로 원복
_Scanner.SelectCorrectionTable(1);
Log.I($"[OnSeqActionStop] 모든 장비 초기화 완료");
}
public class SeqAutoRunPTOP_OutParam : OutParam
{
}
}
}