## 배경 (현장 사고 2026-09-02)
파워측정 / 파워캘 / 롱텀(1시간 연속출사) 작업 중 Door Open 상태에서 레이저가
출사되었다. 현장 요청: "메뉴얼에서도 Door open 상태에서 레이저 출사 안 되도록
Interlock 필요".
## 원인 (수정 전)
1) SeqComponentTeachingRun.CheckCondition_Interlock()
Door 차단 코드가 '#warning to KSJ > 지워도 무방해보임' 과 함께 전체 주석 처리
되어 있었다. 남은 검사는 HasAlarmLevelHigh() 뿐이라 Door Open 이어도 통과한다.
이 함수가 Manual 경로(Teaching Run / Laser Shot On / Scanner Calibration)의
유일한 출사 게이트였다.
2) UCScannerLaserControlViewModel.PerformCmdLaserShotOnPercent / ...OnWatt
IsUseLaserAutoOffChecked == false 분기(= 파워측정 직접 출사)는 시퀀스를 아예
타지 않고 _Scanner.LaserSignalOn() 을 직접 호출한다. 이 분기에는 Door / EMO /
Busy / LaserReady 어떤 인터락도 없었다. LaserSignalOn 은 자동 오프가 없어
Shot Off 를 누를 때까지 계속 출사되며, 이것이 롱텀 1시간 연속출사의 구현
경로다. 이번 사고의 실제 경로.
3) Door 신호 2계통이 모두 출사를 막지 않고 있었다
- PLC 비트: AlarmManager.IsPlcDoorOpen() 을 호출하는 살아있는 코드가 0곳
(SeqComponentTeachingRun 주석, UCMainAutoRunViewModel 블록 주석)
- IO DI 접점: IODevice.MonitorIOInterfaceMap 이 DoorLockOpen1~4 를 읽지만
타워램프 점등과 EVENT_DOOR_INTERLOCK 발행만 하고, 그 이벤트의 구독자가 없다
## 변경 (수정 후)
[WeldingCommon/Alarm/AlarmManager.cs]
- IsDoorOpen(caller) 신규: PLC 비트(PLC_DOOR_INTERLOCK)와 IO DI 접점
(DoorLockOpen1~4)을 OR 판정. 한쪽 배선/맵이 틀려도 다른 쪽이 잡도록 했다.
- IsIoDoorOpen(), GetDoorStateText() 신규.
- PAIXIO.ReadDiBit 은 비트 1개마다 DI 배열 전체를 읽으므로(PAIXIO.cs:305~307)
80ms TTL 캐시를 두어 100ms 감시 1틱당 실제 읽기 1회로 제한했다.
- IsPlcDoorOpen(): _Plc 지연 조회 보완. 기존에는 AlarmManager 생성 시점에 PLC
등록이 안 끝났으면 _Plc 가 null 로 남아 영구히 false 를 반환 → Door 인터락이
조용히 무력화될 수 있었다.
- PLC/IO 미연결 계통은 판정에서 제외(의도적 fail-open). Door 맵이 없는 타 설비가
이 변경으로 갑자기 출사 불가가 되는 것을 막기 위함. PTOP 는 둘 다 연결되어 있다.
[WeldingCommon/Views/Manual/Scanner/UCScannerLaserControlViewModel.cs]
- PerformCmdLaserShotOnPercent / ...OnWatt 진입부에 Door 게이트 추가
-> 시퀀스 분기와 직접 출사 분기를 모두 커버한다.
- OnMonitoring(100ms)에 MonitorDoorInterlock() 추가: 출사 중 Door 가 열리면
StopExecution + LaserSignalOff + SetStandby 로 강제 차단한다. 시작 게이트만으로는
롱텀 도중 문이 열리는 이번 사고 상황을 막을 수 없어 추가했다.
- VisibleChanged: 직접 출사 중에는 화면을 벗어나도 감시 타이머를 유지한다.
기존처럼 Stop 하면 롱텀 출사 중 Door 감시가 통째로 사라진다.
- 롱텀 추적용 10초 주기 하트비트 로그(Door 상태 + DA1/DA2 전압) 추가.
[ILSOS_PrismaticRD_PTOP/Sequences/Manual/SeqComponentTeachingRun.cs]
- CheckCondition_Interlock(): 주석 처리돼 있던 Door 차단을 IsDoorOpen() 으로 복구.
- DoComponentWelding(): ExecuteList 직전 최종 게이트 추가. 시작 게이트는 시퀀스
진입 시 1회만 돌아, 객체가 여러 개일 때 매 출사 직전 재확인이 필요하다.
## 영향 범위
- SeqAutoRunPTOP 은 자체 ExecuteWelding(SeqAutoRunPTOP.cs:1314)을 사용하며
SeqComponentTeachingRun 을 서브시퀀스로 타지 않는다 -> Auto 생산 동작 변경 없음.
- WeldingCommon 공용 파일을 수정했으나, Door 맵이 없는 설비는 fail-open 으로
기존 동작이 유지된다.
## 남은 사항 (별건)
- 본 변경은 SW 방어이지 안전 인터락이 아니다. Door 감시가 UI 스레드
DispatcherTimer 기반이라 UI 정지 시 함께 멈춘다. Class 4 레이저 기준 최종
차단은 PLC/하드와이어 안전회로로 별도 협의가 필요하다.
- SeqComponentTeachingRun 의 EMO 차단(IsPlcEmoAlarm)도 함께 주석 처리되어 있다.
이번 범위에서 제외했으며, PLC_EMO_STATUS 맵 확인 후 복구 여부를 결정한다.
- UCMainAutoRunViewModel.ApproveAutorunByUserOrAuto() 전체가 블록 주석
(399~431행)이다. Auto 시작 게이트 복구는 Vision Recipe 검사까지 함께 살아나므로
별건으로 검토한다.
840 lines
39 KiB
C#
840 lines
39 KiB
C#
using Aurora;
|
|
using Aurora.Api;
|
|
using Aurora.Api.Services;
|
|
using Aurora.View;
|
|
using AuroraIO;
|
|
using AuroraLaser;
|
|
using AuroraLaser.Ekspla;
|
|
using AuroraLaser.Trumpf;
|
|
using AuroraLaserScanner;
|
|
using AuroraLBAD;
|
|
using AuroraLBAD.Services;
|
|
using AuroraOCT;
|
|
using CoreUnit.Control.UserControl.Canvas;
|
|
using CoreUnit.Scanner.UserControl.ScannerCanvas;
|
|
using DB;
|
|
using GUI;
|
|
using Hermes;
|
|
using ILSOS_PrismaticRD_PTOP.Alarm;
|
|
using ILSOS_PrismaticRD_PTOP.Device;
|
|
using ILSOS_PrismaticRD_PTOP.Device.IO;
|
|
using ILSOS_PrismaticRD_PTOP.Settings;
|
|
using Matrox.MatroxImagingLibrary;
|
|
using OpenCvSharp.Dnn;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Configuration;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Runtime.Remoting.Contexts;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using VisionCommon;
|
|
using WeldingCommon;
|
|
using WeldingCommon.Alarm;
|
|
using WeldingCommon.Data;
|
|
using WeldingCommon.Sequences;
|
|
using WeldingCommon.Settings;
|
|
using WeldingCommon.Utils;
|
|
using WeldingCommon.Views;
|
|
using static AuroraIO.AJINEXTEK.AjinIO.IOModuleInfo;
|
|
using static AuroraIO.Vendor.PAIX.PAIXIO;
|
|
using static CoreUnit.Scanner.UserControl.ScannerCanvas.ScannerParam;
|
|
using static ILSOS_PrismaticRD_PTOP.Device.PLCControllerPTOP;
|
|
|
|
namespace ILSOS_PrismaticRD_PTOP.Sequences
|
|
{
|
|
public class SeqComponentTeachingRun
|
|
{
|
|
static readonly Logger Log = new Logger("SeqComponentTeachingRun");
|
|
|
|
const string SEQ_ENTER = "[SeqComponentTeachingRun] SEQ_ENTER"; // inputParam 으로 받자.
|
|
const string SEQ_CHECK_INIT_CONDITION = "[SeqComponentTeachingRun] Check Init Condition"; // Check Init Condition
|
|
|
|
const string SEQ_SET_PARAMS_UPLOAD_WELDING_LIST_SEQ = "[SeqComponentTeachingRun] Set Sequence Param (UploadWeldingList Seq)";
|
|
const string SEQ_DO_ID_UPLOAD_WELDING_LIST_SEQ = "[SeqComponentTeachingRun] Process (UploadWeldingList Seq)";
|
|
const string SEQ_GET_RESULT_UPLOAD_WELDING_LIST_SEQ = "[SeqComponentTeachingRun] Get Sequence Result (UploadWeldingList Seq)";
|
|
const string SEQ_SET_BLOW_ON = "[SeqComponentTeachingRun] Set Blow On";
|
|
const string SEQ_SET_BLOW_OFF = "[SeqComponentTeachingRun] Set Blow Off";
|
|
|
|
const string SEQ_SAVE_RESULT_DATA = "[SeqComponentTeachingRun] Save Result Data";
|
|
|
|
const string SEQ_COMPLETE = "[SeqComponentTeachingRun] SEQ_COMPLETE"; // 완료
|
|
|
|
const string SEQ_UPDATE_COMPONENT_WELDING_PARAMETERS = "[SeqComponentTeachingRun] Update Component Welding Parameters";
|
|
const string SEQ_START_WELDING = "[SeqComponentTeachingRun] Start All Welding";
|
|
const string SEQ_DO_COMPONENT_WELDING = "[SeqComponentTeachingRun] Do Component Welding";
|
|
const string SEQ_CHECK_END_LIST = "[SeqComponentTeachingRun] Check End List";
|
|
const string SEQ_END_WELDING = "[SeqComponentTeachingRun] End All Welding";
|
|
|
|
const int timeoutSeconds = 10;
|
|
static class ContextKey
|
|
{
|
|
public const string KEY_SEQUENCE_RESULT = "KEY_SEQUENCE_RESULT_SeqComponentTeachingRun";
|
|
public const string KEY_SEQUENCE_START_TIME = "KEY_SEQUENCE_START_TIME_SeqComponentTeachingRun";
|
|
}
|
|
|
|
private SeqTeachingRun_InitParam _InitParam;
|
|
|
|
private static Scanner _Scanner;
|
|
private static PAIX_PTOP _IO;
|
|
#warning to KSJ > _Laser 참조되는거 다 IOLaser로 변경해야함, 필요 없으면 지워도 무방
|
|
private Laser _Laser;
|
|
#warning to KSJ > _Laser 참조되는거 다 IOLaser로 변경해야함, 필요 없으면 지워도 무방
|
|
private ILaser _LaserDevice;
|
|
#warning to KSJ > _Laser 참조되는거 다 IOLaser로 변경해야함, 필요 없으면 지워도 무방
|
|
private LaserTruFiber _LaserTruFiber;
|
|
private LaserPowerCalParam _LaserPowerCalParam;
|
|
private PLCControllerPTOP _plc;
|
|
|
|
private SystemParameter _SystemParameter;
|
|
private ScannerCanvasUserCtrl _ScannerCanvasUserCtrl; //스캐너 동작관련 객체 선언
|
|
|
|
private static SequenceSetting _SequenceSetting;
|
|
|
|
private eListNo _WeldingScannerListNo;
|
|
|
|
|
|
private List<IDrawObject> _SelectedObjectList;
|
|
private Queue<List<IDrawObject>> _DivideObjectsQueue;
|
|
private List<IDrawObject> _TargetObjects;
|
|
private static DateTime _SequenceCompleteTime; // 시퀸스 시간
|
|
private static TimeSpan _MeasurementSpanTime; //측정 시간
|
|
private static TimeSpan _ScannerSaveLogTime;
|
|
private static DateTime _partialWeldingStartTime;
|
|
private static int _CountWeldingObject; // 트럼프 레시피에서 부분 용접 할 Ojbect의 개수
|
|
private static int _CurrentobjectIndex;
|
|
private static int _BeforeObjectIndex;
|
|
private static int _BeforeProgramNo; // 이전 트럼프 레이저 용접 번호
|
|
private static int _BeforeParameterNo; //
|
|
|
|
private static int _CenterBeamPower;
|
|
private static int _RingBeamPower;
|
|
private static bool _LaserPowerControlByRatio = false;
|
|
private static int _CenterRingRatio; // Center Ring의 파워 비율
|
|
private static double _LaserPowerRatio; // Laser에서 출력 파워 비율
|
|
public SequenceBuilder Builder()
|
|
{
|
|
var sb = new SequenceBuilder(1000, true);
|
|
////var sb = new SequenceBuilder(1000, false); // With log
|
|
|
|
sb.AddAction(SEQ_ENTER, Sequence_Enter);
|
|
sb.AddAction(SEQ_CHECK_INIT_CONDITION, CheckInitCondition); //분기
|
|
sb.AddAction(SEQ_SET_BLOW_ON, SetBlowOn);
|
|
sb.AddAction(SEQ_START_WELDING, StartWelding);
|
|
sb.AddDecision(SEQ_CHECK_END_LIST, CheckEndList);
|
|
sb.AddAction(SEQ_UPDATE_COMPONENT_WELDING_PARAMETERS, UpdateComponentWeldingParameters);
|
|
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);
|
|
sb.AddAction(SEQ_DO_COMPONENT_WELDING, DoComponentWelding);
|
|
sb.AddAction(SEQ_END_WELDING, EndWelding);
|
|
sb.AddAction(SEQ_SET_BLOW_OFF, SetBlowOff);
|
|
sb.AddAction(SEQ_SAVE_RESULT_DATA, SaveResultData); // 해당 공정의 정보를 기록 함.
|
|
sb.AddAction(SEQ_COMPLETE, Complete);
|
|
|
|
|
|
|
|
sb.Flows(sb.Init > sb[SEQ_ENTER] > sb[SEQ_CHECK_INIT_CONDITION] > sb[SEQ_SET_BLOW_ON]
|
|
> sb[SEQ_START_WELDING] > sb[SEQ_CHECK_END_LIST]);
|
|
|
|
sb.Flows(sb[SEQ_CHECK_END_LIST] > sb[SEQ_UPDATE_COMPONENT_WELDING_PARAMETERS]
|
|
> 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_DO_COMPONENT_WELDING] > sb[SEQ_CHECK_END_LIST]);
|
|
sb.Flows(sb[SEQ_CHECK_END_LIST] > sb[SEQ_END_WELDING] > sb[SEQ_SET_BLOW_OFF]
|
|
> sb[SEQ_SAVE_RESULT_DATA] > sb[SEQ_COMPLETE] > sb.Final);
|
|
|
|
sb.OnStopped(OnStopped);
|
|
sb.OnForceError(OnForceError);
|
|
|
|
|
|
//sb.Draw("SeqComponentTeachingRun.pdf");
|
|
|
|
return sb;
|
|
}
|
|
|
|
private SequenceActionResult Sequence_Enter(SequenceContext context, string id, SequenceActionResult param)
|
|
{
|
|
Log.I("SeqTeachingRun Start");
|
|
|
|
_InitParam = param.Param as SeqTeachingRun_InitParam;
|
|
_SystemParameter = context.GetSetting<SystemParameter>(CommonSettingIds.SystemParameter);
|
|
|
|
_Laser = context.GetLaser();
|
|
_Laser.GetLaserDevice(ref _LaserDevice);
|
|
//_LaserTruFiber = _LaserDevice as LaserTruFiber;
|
|
|
|
if (_plc == null)
|
|
_plc = context.Devices[DeviceIds.PLCDevice] as PLCControllerPTOP;
|
|
|
|
// Laser 시퀀스 Running. 안 넣어줘도 됨.
|
|
//_LaserTruFiber.SetIsSeqRunning(true);
|
|
|
|
_LaserPowerCalParam = context.GetSetting<LaserPowerCalParam>(CommonSettingIds.LaserPowerCalParam);//우선 AuroraRecipe로 상속받아서 함수 사용하도록 함
|
|
//laserPowerCalParam 초기화(CSV 파일 불러오기)
|
|
_LaserPowerCalParam.Initialize(_LaserPowerCalParam.PowerCalibrationCenterBeamCSVFile, _LaserPowerCalParam.PowerCalibrationRingBeamCSVFile);
|
|
|
|
_SequenceSetting = context.Settings[CommonSettingIds.Sequence] as SequenceSetting;
|
|
InitDev(context);
|
|
if (!CheckDeviceReady(context)) { //Check Device
|
|
AlarmManager.Alarm(AlarmCode.ALARM_SEQUENCE_TeachingRun_DeviceNotReady);
|
|
Log.E("The devices are not active.");
|
|
return null;
|
|
}
|
|
|
|
if (InitSequenceVariable(context) == false) {
|
|
AlarmManager.Alarm(AlarmCode.ALARM_SEQUENCE_TeachingRun_InitVariableError);
|
|
return null;
|
|
}
|
|
|
|
|
|
return SequenceAction.Ok();
|
|
}
|
|
private void InitDev(SequenceContext context)
|
|
{
|
|
//Device Init
|
|
_Scanner = context.GetLaserScanner();
|
|
_IO = context.Devices[CommonDeviceIds.IODevice] as PAIX_PTOP;
|
|
|
|
}
|
|
private bool CheckDeviceReady(SequenceContext context)
|
|
{
|
|
List<string> deviceListToCheck = new List<string>() { CommonDeviceIds.Scanner, CommonDeviceIds.IODevice };
|
|
|
|
bool isAllDeviceActivated = true;
|
|
foreach (string item in deviceListToCheck) {
|
|
if (!context.Devices[item].IsActivated) {
|
|
isAllDeviceActivated = isAllDeviceActivated && false;
|
|
}
|
|
}
|
|
|
|
return isAllDeviceActivated;
|
|
}
|
|
private bool InitSequenceVariable(SequenceContext context)
|
|
{
|
|
var mainPage = CommonHelper.GetMainPage();
|
|
_ScannerCanvasUserCtrl = mainPage.ScannerCanvasCtrlTeaching;
|
|
|
|
_WeldingScannerListNo = eListNo.LIST1;
|
|
|
|
_SelectedObjectList = FindObjects();
|
|
|
|
return true;
|
|
}
|
|
|
|
private List<IDrawObject> FindObjects()
|
|
{
|
|
if (_InitParam.IsUseExternalObjects) {
|
|
return _InitParam.ExternalObjects;
|
|
} else if (_InitParam.IsObjectSelectAll) {
|
|
return _ScannerCanvasUserCtrl.Model.ActiveLayer.Objects;
|
|
} else {
|
|
return _ScannerCanvasUserCtrl.Canvas.Model.SelectedObjects;
|
|
}
|
|
|
|
}
|
|
|
|
private SequenceActionResult CheckInitCondition(SequenceContext context, string id, SequenceActionResult param)
|
|
{
|
|
if (!CheckCondition()) {
|
|
AlarmManager.Alarm(AlarmCode.ALARM_SEQUENCE_TeachingRun_CheckInitCondition);
|
|
Log.E("Condition is not in state to start sequence.");
|
|
context.PutValue(ContextKey.KEY_SEQUENCE_RESULT, false);
|
|
return null;
|
|
}
|
|
|
|
_CurrentobjectIndex = 0;
|
|
_BeforeObjectIndex = 0;
|
|
_CountWeldingObject = 0;
|
|
_BeforeParameterNo = -1;
|
|
_BeforeProgramNo = -1;
|
|
_ScannerSaveLogTime = TimeSpan.Zero;
|
|
|
|
var startTime = DateTime.Now;
|
|
context.PutValue(ContextKey.KEY_SEQUENCE_START_TIME, startTime);
|
|
|
|
Log.D("ComponentTeachingRun Sequence CheckInitCondition");
|
|
return SequenceAction.Ok();
|
|
}
|
|
private bool CheckCondition()
|
|
{
|
|
if (CheckCondition_Interlock()) {
|
|
if (InitializeDeviceStatus()) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private bool CheckCondition_Interlock()
|
|
{
|
|
//// PLC, Data, IO 등 Interlock checking
|
|
bool boolian = AlarmManager.HasAlarmLevelHigh();
|
|
if (boolian) {
|
|
Log.E($"Clear Heavy Alarms For Welding!");
|
|
MessageUtil.ShowWarningToast(string.Format("Clear Heavy Alarms For Welding!"));
|
|
return false;
|
|
}
|
|
|
|
// [Door Interlock 2026-09-02] 주석 처리되어 있던 Door 차단을 복구 (ISSUE-260902-01)
|
|
// 기존: '#warning to KSJ > 지워도 무방해보임' 과 함께 전체 주석 → Manual 경로에서 Door Open 이어도 출사됨.
|
|
// 변경: PLC 비트 + IO DI 접점 OR 판정(AlarmManager.IsDoorOpen)으로 복구.
|
|
// IsPlcDoorOpen() 단독이 아니라 OR 을 쓰는 이유는 한쪽 배선/맵이 틀려도 다른 쪽이 잡도록 하기 위함.
|
|
boolian = AlarmManager.IsDoorOpen("SeqComponentTeachingRun");
|
|
if (boolian) {
|
|
Log.E($"[DoorInterlock] Door Open 상태 → 용접(출사) 차단. {AlarmManager.GetDoorStateText()}");
|
|
MessageUtil.ShowWarningToast(string.Format("Door Open 상태입니다. 문을 닫고 다시 시도하세요. (Laser Interlock)"));
|
|
return false;
|
|
}
|
|
Log.I($"[DoorInterlock] Door Close 확인 → 용접(출사) 허용. {AlarmManager.GetDoorStateText()}");
|
|
|
|
// TODO(ISSUE-260902-01): EMO 차단도 함께 주석 처리되어 있음. 이번 변경 범위(Door)에서 제외했으니
|
|
// PLC_EMO_STATUS 맵 확인 후 별도로 복구 여부 결정할 것.
|
|
//boolian = AlarmManager.IsPlcEmoAlarm();
|
|
//if (boolian) {
|
|
// Log.E($"PLC EMO Pushed!! Can't Weld Now.");
|
|
// MessageUtil.ShowWarningToast(string.Format("Clear PLC EMO Status!"));
|
|
// return false;
|
|
//}
|
|
return true;
|
|
}
|
|
|
|
private bool InitializeDeviceStatus()
|
|
{
|
|
//Vision Correction File 선택
|
|
_Scanner.SelectCorrectionTable(2);
|
|
return true;
|
|
}
|
|
|
|
private SequenceActionResult SetBlowOn(SequenceContext context, string id, SequenceActionResult param)
|
|
{
|
|
if (_InitParam.UseBlow == false) {
|
|
return SequenceAction.Ok();
|
|
}
|
|
|
|
int intervalTime = _InitParam.IntervalTimeBlowOn;
|
|
_IO._iOUnit.WriteDoBit(Convert.ToInt32(ePDOAddress.SeamWeldingN2BlowSol), true);
|
|
Thread.Sleep(intervalTime);
|
|
|
|
Log.D(string.Format($"[ComponentTeachingRun] Blow ON Complete"));
|
|
|
|
return SequenceAction.Ok();
|
|
}
|
|
|
|
private void SaveScannerRecordFileForamt()
|
|
{
|
|
if (_Scanner.Config.UseRecordSignal) {
|
|
DateTime startTime = DateTime.Now;
|
|
_Scanner.SaveStackRecord("Normal");
|
|
_ScannerSaveLogTime += DateTime.Now.Subtract(startTime);
|
|
}
|
|
}
|
|
|
|
private SequenceActionResult StartWelding(SequenceContext context, string id, SequenceActionResult param)
|
|
{
|
|
Log.D(string.Format($"[ComponentTeachingRun] Start Component Welding"));
|
|
_SequenceCompleteTime = DateTime.Now;
|
|
// Laser 판넬 On
|
|
//_IO.WriteDoBit(PAIX_PTOP.ePrismaticDOAddress.CCCAPLaserOnLampOn, true);
|
|
|
|
// Group: 펼치지 않고 단일 배치로 큐 삽입 → SeqUploadWeldingList에서 GroupRampingScannerParam 처리
|
|
// 비-Group: 기존처럼 재귀 펼침 후 ScannerParamID 동일한 것끼리 묶음
|
|
_DivideObjectsQueue = BuildDivideObjectsQueue(_SelectedObjectList);
|
|
Log.I($"Divided DrawObjects List Count[{_DivideObjectsQueue.Count}]");
|
|
|
|
return SequenceAction.Ok();
|
|
}
|
|
|
|
// 펼쳐진 객체(_FlattenRecipes)를 여러개의 스캔 파라미터에 따라 여러개로 나눈다
|
|
public static Queue<List<IDrawObject>> SplitBySerialize(List<IDrawObject> source)
|
|
{
|
|
var result = new Queue<List<IDrawObject>>();
|
|
if (source == null || source.Count == 0)
|
|
return result;
|
|
|
|
List<IDrawObject> current = null;
|
|
int? currentId = null;
|
|
|
|
foreach (var item in source) {
|
|
if (current == null || currentId.Value != item.ScannerParamID) {
|
|
current = new List<IDrawObject>();
|
|
result.Enqueue(current);
|
|
currentId = item.ScannerParamID;
|
|
}
|
|
|
|
current.Add(item);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 선택된 객체 목록을 용접 순서 큐로 변환한다.
|
|
/// - Group 객체 : 펼치지 않고 단일 배치 [group] 으로 큐에 삽입
|
|
/// → SeqUploadWeldingList 의 GroupRampingScannerParam 경로로 처리됨
|
|
/// - 비-Group 객체 : 재귀적으로 펼친 뒤 ScannerParamID 가 동일한 것끼리 묶어 삽입
|
|
/// → 기존 SplitBySerialize 동작과 동일
|
|
/// 처리 순서(원래 선택 순서)는 보장된다.
|
|
/// </summary>
|
|
private static Queue<List<IDrawObject>> BuildDivideObjectsQueue(List<IDrawObject> source)
|
|
{
|
|
var result = new Queue<List<IDrawObject>>();
|
|
if (source == null || source.Count == 0)
|
|
return result;
|
|
|
|
// 비-Group 객체를 모아뒀다가 한꺼번에 SplitBySerialize 하는 임시 버퍼
|
|
var flatBuffer = new List<IDrawObject>();
|
|
|
|
foreach (var obj in source) {
|
|
if (obj is Group) {
|
|
// 앞서 쌓인 비-Group 객체를 먼저 큐에 삽입 (순서 보장)
|
|
if (flatBuffer.Count > 0) {
|
|
foreach (var chunk in SplitBySerialize(flatBuffer))
|
|
result.Enqueue(chunk);
|
|
flatBuffer.Clear();
|
|
}
|
|
// Group 은 펼치지 않고 통째로 단일 배치
|
|
result.Enqueue(new List<IDrawObject> { obj });
|
|
} else {
|
|
// 비-Group 은 재귀 펼침 후 버퍼에 누적
|
|
FlattenIntoBuffer(obj, flatBuffer);
|
|
}
|
|
}
|
|
|
|
// 버퍼에 남은 비-Group 객체 처리
|
|
if (flatBuffer.Count > 0) {
|
|
foreach (var chunk in SplitBySerialize(flatBuffer))
|
|
result.Enqueue(chunk);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 비-Group 객체를 재귀적으로 펼쳐 buffer 에 추가한다 (UnpackRecipes 대체).
|
|
/// 최상위 Group 은 BuildDivideObjectsQueue 에서 처리하므로 여기서는 중첩 Group 만 해당.
|
|
/// </summary>
|
|
private static void FlattenIntoBuffer(IDrawObject obj, List<IDrawObject> buffer)
|
|
{
|
|
if (obj is Group group) {
|
|
foreach (var sub in group.SubObjects)
|
|
FlattenIntoBuffer(sub, buffer);
|
|
} else {
|
|
buffer.Add(obj);
|
|
}
|
|
}
|
|
|
|
private string CheckEndList(SequenceContext context, string id, ICollection<string> branches, SequenceActionResult param)
|
|
{
|
|
if (_DivideObjectsQueue.Count == 0)
|
|
return SEQ_END_WELDING;
|
|
|
|
// 파일 저장 변수 초기화
|
|
if (_Scanner.Config.UseRecordSignal) {
|
|
DateTime startTime = DateTime.Now;
|
|
_Scanner.InitializeRecord();
|
|
_ScannerSaveLogTime += DateTime.Now.Subtract(startTime);
|
|
}
|
|
|
|
return SEQ_UPDATE_COMPONENT_WELDING_PARAMETERS;
|
|
|
|
}
|
|
|
|
private SequenceActionResult UpdateComponentWeldingParameters(SequenceContext context, string id, SequenceActionResult param)
|
|
{
|
|
_partialWeldingStartTime = DateTime.Now;
|
|
var currentDivideObject = _DivideObjectsQueue.Peek();
|
|
int paramIndex = currentDivideObject[0].ScannerParamID - 1;
|
|
Log.I($"Remain Divided DrawObjects Count[{currentDivideObject.Count}] / ParameterNo[{paramIndex + 1}]");
|
|
|
|
int SlotNo = _plc.ReadWord(eWordInput.PLC_CURRENT_SLOT_NO).data;
|
|
if (SlotNo <= 0)
|
|
SlotNo = 1;
|
|
//// 결과 Data 를 업데이트 함.
|
|
var power = Status.GetProcessingWeldingData().UploadProcessDatas.Where(data => data.DataName == $"Power_{SlotNo}_{_SequenceSetting.LastPageNo}").FirstOrDefault();
|
|
power.Value = ScannerCanvasUserCtrl.ScannerParams[paramIndex].LaserPower1 + ScannerCanvasUserCtrl.ScannerParams[paramIndex].LaserPower2;
|
|
var velocity = Status.GetProcessingWeldingData().UploadProcessDatas.Where(data => data.DataName == $"Velocity_{SlotNo}_{_SequenceSetting.LastPageNo}").FirstOrDefault();
|
|
velocity.Value = ScannerCanvasUserCtrl.ScannerParams[paramIndex].MarkSpeed;
|
|
|
|
//// FDC 데이터 상신
|
|
//_plc.UploadReportDatas(Status.GetProcessingWeldingData().UploadProcessDatas);
|
|
|
|
_CenterBeamPower = (int)ScannerCanvasUserCtrl.ScannerParams[paramIndex].LaserPower1;
|
|
_RingBeamPower = (int)ScannerCanvasUserCtrl.ScannerParams[paramIndex].LaserPower2;
|
|
_CenterRingRatio = 0;
|
|
|
|
if (_CenterBeamPower == 0 && _RingBeamPower == 0)
|
|
_CenterRingRatio = 100;
|
|
else
|
|
_CenterRingRatio = (int)Math.Round((double)(100 * _CenterBeamPower) / (double)(_CenterBeamPower + _RingBeamPower), 0); // Discrete 한 값이라서 Ring은 오차 발생
|
|
// KSJ 레이저 IO 출사 테스트를 위한 주석
|
|
//if (_Laser.Config.TruFiberType == eTruFiberType.TruFiberP) {
|
|
// _CenterBeamPower = _LaserPowerCalParam.CalibrateWattToWatt(_CenterBeamPower, (uint)LaserPowerCalParam.beamMode.Center);
|
|
// _RingBeamPower = _LaserPowerCalParam.CalibrateWattToWatt(_RingBeamPower, (uint)LaserPowerCalParam.beamMode.Ring);
|
|
//}
|
|
// Ready 상태 변경 확인(Complete 상태에서 Laser Ready로 변환되기까지 시간이 걸림)
|
|
// 여기서는 왜 IW03과 Center를 비교하는 것인지.... 아직 값을 써주지 않았으므로 레이저 출사 가능 여부만 확인하면 되지 않을까.
|
|
var res = context.RetryUntilSuccess(_Laser.GetLaserReady, timeoutSeconds * 1000, 1);
|
|
|
|
if (res == E2.TIMEOUT) {
|
|
Log.E(string.Format($"[Page Change Req] Laser Program Ready Timeout When LaserReady"));
|
|
return null;
|
|
}
|
|
Log.D(string.Format($"[Page Change Req] Laser Program Ready OK"));
|
|
|
|
// Laser Program Parameters 설정(Laser Ready 상태는 용접 직전에 확인)
|
|
List<(int, int, bool)> datas = new List<(int, int, bool)>();
|
|
datas.Add((csWriteNodeNo.IW03, _CenterBeamPower, true));
|
|
datas.Add((csWriteNodeNo.IW04, _RingBeamPower, true));
|
|
datas.Add((csWriteNodeNo.IW05, _CenterRingRatio, true));
|
|
|
|
// 업데이트 될 경우, 프로그램 전환을 위한 프로그램 종료 시퀸스 진행됨.
|
|
// KSJ 레이저 IO 출사 테스트를 위한 주석
|
|
//if (_LaserTruFiber.UpdatesDatasIfChanged(datas)) {
|
|
|
|
// // 프로그램이 종료 될 때까지 대기.
|
|
// res = context.RetryUntilSuccess(_LaserTruFiber.GetProgramCompleted, timeoutSeconds * 1000, 10);
|
|
|
|
// if (res == E2.TIMEOUT) {
|
|
// Log.E(string.Format($"[Page Change Req] Laser Program Completed Timeout When Parameter Changes"));
|
|
// return null;
|
|
// }
|
|
// Log.D(string.Format($"[Page Change Req] Laser Program Completed OK When Data Updated"));
|
|
//}
|
|
|
|
return SequenceAction.Ok();
|
|
}
|
|
private SequenceActionResult SetUploadWeldingList_SeqParams(SequenceContext context, string id, SequenceActionResult param)
|
|
{
|
|
Status.SetWorkingPosition(eWorkingPosition.UPLOADING_WELD_LIST);
|
|
|
|
// 각도(D70510) 기반 Front/Down offset 선택 (기존 단일 CellCenterOffset 대체)
|
|
var thetaOffsetSetting = context.GetSetting<LaserInterlockSetting>(SettingIds.LaserInterlock);
|
|
var (angleOffX, angleOffY) = LaserAngleOffsetSelector.Select(_plc, _SystemParameter, thetaOffsetSetting, Log, "ComponentTeachingRun");
|
|
|
|
// TeachingRun에는 용접 시퀀스만 정의해서 Auto Run에서도 사용하므로 Offset 넣어줘야 함
|
|
SeqUploadWeldingList_InitParam seqParams = new SeqUploadWeldingList_InitParam() {
|
|
NeedSetLaserTiming = _InitParam.NeedSetLaserTiming,
|
|
IncludeListStartEnd = true,
|
|
CellMeasureCenterOffsetX = angleOffX + _InitParam.CellCenterOffsetX,
|
|
CellMeasureCenterOffsetY = angleOffY + _InitParam.CellCenterOffsetY,
|
|
SelectedDrawObjectList = _DivideObjectsQueue.Peek(),
|
|
WeldingScannerListNo = _WeldingScannerListNo,
|
|
};
|
|
|
|
var _PageNo = _SequenceSetting.LastPageNo;
|
|
|
|
Log.D(string.Format($"[ComponentTeachingRun] Set UploadWelindList Seq Param (page {_PageNo}, Cell {Status.GetProcessingWeldingData().CellId})"));
|
|
|
|
return SequenceAction.Ok(seqParams);
|
|
}
|
|
|
|
private SequenceActionResult GetUploadWeldingList_SeqResult(SequenceContext context, string id, SequenceActionResult param)
|
|
{
|
|
Log.D(string.Format($"[ComponentTeachingRun] Get UploadWelindList Seq Param"));
|
|
|
|
var resultOutParam = param.Param as SeqUploadWeldingList_OutParam;
|
|
if (resultOutParam.Success == false) {
|
|
Log.E(string.Format($"[ComponentTeachingRun] Failed UploadWeldingList Sequence"));
|
|
return null;
|
|
}
|
|
|
|
return SequenceAction.Ok();
|
|
}
|
|
|
|
|
|
private SequenceActionResult DoComponentWelding(SequenceContext context, string id, SequenceActionResult param)
|
|
{
|
|
_DivideObjectsQueue.Dequeue();
|
|
Log.I("UploadWeldingList Complete");
|
|
Log.D(string.Format($"[ComponentTeachingRun] Component Welding Start idx :({_BeforeObjectIndex} ~ {_BeforeObjectIndex + _CountWeldingObject - 1})"));
|
|
|
|
#region PSTART_DYN 사용시 참고
|
|
//// PSTART_DYN에서는 레이저가 활성화되었는지 확인하여야 함. 하지만, PSTART_STATICALLY에서는 굳이 확인할 필요 없음. (이전에 확인해줄 것이므로)
|
|
// var res = context.RetryUntilSuccess(_LaserTruFiber.GetProgramActive, timeoutSeconds * 1000, 1);
|
|
//if (res == E2.TIMEOUT)
|
|
//{
|
|
// Log.E(string.Format($"[ComponentTeachingRun] Laser Program Active Timeout"));
|
|
// return null;
|
|
//}
|
|
#endregion
|
|
|
|
Log.D(string.Format($"[ComponentTeachingRun] Check LaserReadyResponse"));
|
|
// 만일 프로그램이 정상적으로 종료 및 변경 원할 경우, 레이저 스레드에서 Ready 상태로 만들어주고 있을 것임.
|
|
// Ready 상태 변경 확인
|
|
var res = context.RetryUntilSuccess(LaserReadyResponse, timeoutSeconds * 1000, 10);
|
|
if (res == E2.TIMEOUT) {
|
|
Log.E(string.Format($"[ComponentTeachingRun] Laser Program Ready Timeout When LaserReadyResponse"));
|
|
return null;
|
|
}
|
|
Log.D(string.Format($"[ComponentTeachingRun] Laser Program Ready OK When ReadyResponse"));
|
|
|
|
Status.SetWorkingPosition(eWorkingPosition.EXECUTING_WELD);
|
|
|
|
Log.I($"Welding List Space : {_Scanner.GetListSpace()}");
|
|
|
|
// === Head Theta 인터락 (① 안전 게이트 + ② 레시피별 방향 차단) — Manual 경로 신규 추가 ===
|
|
var thetaSetting = context.GetSetting<LaserInterlockSetting>(SettingIds.LaserInterlock);
|
|
if (thetaSetting != null && thetaSetting.HeadThetaInterlockEnabled)
|
|
{
|
|
bool blocked = false;
|
|
if (_plc == null || !_plc.IsActivated)
|
|
{
|
|
Log.W("[DoComponentWelding] LaserInterlock - PLC 미연결/미활성 → 발진 차단(fail-closed)");
|
|
blocked = true;
|
|
}
|
|
else
|
|
{
|
|
short thetaRaw = _plc.ReadWord(eWordInput.HEAD_THETA_POSITION).data;
|
|
double scale = thetaSetting.HeadThetaPlcScale != 0 ? thetaSetting.HeadThetaPlcScale : 1; // 0 가드
|
|
double thetaDeg = thetaRaw / scale;
|
|
bool okFront = Math.Abs(thetaDeg - thetaSetting.HeadThetaFront) <= thetaSetting.HeadThetaTolerance;
|
|
bool okDown = Math.Abs(thetaDeg - thetaSetting.HeadThetaDown) <= thetaSetting.HeadThetaTolerance;
|
|
Log.I($"[DoComponentWelding] HeadTheta raw={thetaRaw}, deg={thetaDeg:F1} (Front={thetaSetting.HeadThetaFront}/Down={thetaSetting.HeadThetaDown}/±{thetaSetting.HeadThetaTolerance})");
|
|
// ① 안전 게이트: 안전 구역(Front/Down 부근) 아니면 차단
|
|
if (!okFront && !okDown)
|
|
{
|
|
Log.W($"[DoComponentWelding] ① HeadTheta 인터락 → Front/Down ±{thetaSetting.HeadThetaTolerance}° 벗어남(deg={thetaDeg:F1}) → 발진 차단");
|
|
blocked = true;
|
|
}
|
|
else
|
|
{
|
|
// ② 레시피별 방향 차단: 체크된 방향의 구역이면 차단
|
|
var rl = RecipeLaserInterlock.Load();
|
|
if ((rl.Front && okFront) || (rl.Down && okDown))
|
|
{
|
|
Log.W($"[DoComponentWelding] ② 레시피 차단 deg={thetaDeg:F1} Front?{rl.Front}/{okFront} Down?{rl.Down}/{okDown} → 발진 차단");
|
|
blocked = true;
|
|
}
|
|
}
|
|
}
|
|
if (blocked)
|
|
{
|
|
try { AlarmManager.Alarm(AlarmCode_PTOP.ALARM_LASER_HEAD_THETA_INTERLOCK); }
|
|
catch (Exception ex) { Log.E($"[DoComponentWelding] 알람 표시 실패: {ex.Message}"); }
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// [Door Interlock 2026-09-02] 출사 직전 최종 게이트 (ISSUE-260902-01)
|
|
// CheckInitCondition 의 시작 게이트는 시퀀스 진입 시 1회만 돈다.
|
|
// 객체가 여러 개면 이 구간이 반복되므로, 매 출사 직전마다 Door 를 다시 확인한다.
|
|
if (AlarmManager.IsDoorOpen("DoComponentWelding")) {
|
|
Log.E($"[DoorInterlock] 출사 직전 Door Open 감지 → ExecuteList 중단. {AlarmManager.GetDoorStateText()}");
|
|
MessageUtil.ShowWarningToast(string.Format("Door Open 상태입니다. 출사를 중단합니다. (Laser Interlock)"));
|
|
return null;
|
|
}
|
|
|
|
DateTime weldingTime = DateTime.Now;
|
|
Log.I($"[DoorInterlock] 출사 직전 Door Close 확인 → ExecuteList 실행 (ListNo:{_WeldingScannerListNo})");
|
|
_Scanner.ExecuteList(_WeldingScannerListNo);
|
|
_Scanner.WaitForListTaskCompleted(1000);//(_SystemParameter.WeldLinesTaskTimeOut);
|
|
_MeasurementSpanTime = DateTime.Now.Subtract(weldingTime);
|
|
Log.D(string.Format($"[ComponentTeachingRun] Component Welding Complete Time : {_MeasurementSpanTime.TotalMilliseconds}"));
|
|
|
|
#region PSTART_DYN 사용시 참고
|
|
//// 굳이 확인할 필요가 없을 듯 함.PSTAT_STATICALLY 활용시.
|
|
//res = context.RetryUntilSuccess(_LaserTruFiber.GetProgramCompleted, timeoutSeconds * 1000, 1);
|
|
|
|
//if (res == E2.TIMEOUT)
|
|
//{
|
|
// Log.E(string.Format($"[ComponentTeachingRun] Laser Program Complete Timeout"));
|
|
// return null;
|
|
//}
|
|
#endregion
|
|
|
|
Log.D(string.Format($"[ComponentTeachingRun] Laser Program Complete OK"));
|
|
|
|
|
|
if (_Scanner.Config.UseRecordSignal) {
|
|
DateTime startTime = DateTime.Now;
|
|
_Scanner.StackRecord();
|
|
_ScannerSaveLogTime += DateTime.Now.Subtract(startTime);
|
|
}
|
|
|
|
// excel 파일 작성
|
|
SaveScannerRecordFileForamt();
|
|
Log.D(string.Format($"[ComponentTeachingRun] SaveScannerRecordFileForamt Time : {_ScannerSaveLogTime.TotalMilliseconds}"));
|
|
|
|
var PartialTactTime = DateTime.Now.Subtract(_partialWeldingStartTime);
|
|
Log.D(string.Format($"[TactTime] Complete Partial Component Weld : {PartialTactTime.TotalMilliseconds}"));
|
|
|
|
_ScannerCanvasUserCtrl.TimeLabelUpdate(_MeasurementSpanTime.TotalMilliseconds, PartialTactTime.TotalMilliseconds);
|
|
|
|
//// 용접 끝난 후, 초기화 시킴
|
|
//var UploadDatas = Status.GetProcessingWeldingData().UploadProcessDatas;
|
|
//foreach (var data in UploadDatas)
|
|
// data.Clear();
|
|
//_plc.UploadReportDatas(UploadDatas);
|
|
|
|
return SequenceAction.Ok();
|
|
}
|
|
private bool LaserReadyResponse()
|
|
{
|
|
//if (ApplicationExt.IsSimulation)
|
|
return true;
|
|
|
|
//// Ready 상태를 확인하기 위해서는 프로그램 파라메터가 최신 상태인지, Ready 상태인지 둘 다 확인해야 한다.
|
|
//return Convert.ToInt32(_LaserTruFiber.GetWriteInputWord(csWriteNodeNo.IW03)) == _CenterBeamPower
|
|
// && Convert.ToInt32(_LaserTruFiber.GetWriteInputWord(csWriteNodeNo.IW04)) == _RingBeamPower
|
|
// && Convert.ToInt32(_LaserTruFiber.GetWriteInputWord(csWriteNodeNo.IW05)) == _CenterRingRatio;
|
|
//&& _LaserTruFiber.GetProgramNo() == _BeforeProgramNo
|
|
//&& _LaserTruFiber.GetLaserReady();
|
|
}
|
|
|
|
private SequenceActionResult EndWelding(SequenceContext context, string id, SequenceActionResult param)
|
|
{
|
|
_MeasurementSpanTime = DateTime.Now.Subtract(_SequenceCompleteTime);
|
|
Log.D(string.Format($"[TactTime] Complete All Component Weld : {_MeasurementSpanTime.TotalMilliseconds}"));
|
|
|
|
|
|
//Anlog 출력 0으로 변경, efunctiontype을 command로 변경해주면 list가 아니라 직접 변경됨
|
|
_Scanner.WriteDA((uint)eAnalogPortOut.DA1, 0, eFunctionType.COMMAND);
|
|
_Scanner.WriteDA((uint)eAnalogPortOut.DA2, 0, eFunctionType.COMMAND);
|
|
_Scanner.JumpAbs(0, 0, eFunctionType.COMMAND);
|
|
|
|
// Laser 판넬 Off
|
|
//_IO.WriteDoBit(PAIX_PTOP.ePrismaticDOAddress.CCCAPLaserOnLampOn, false);
|
|
|
|
var mesurementTime = DateTime.Now;
|
|
|
|
//용접 완료 후 CorrectionFile Vision으로 원복
|
|
_Scanner.SelectCorrectionTable(1);
|
|
|
|
_MeasurementSpanTime = DateTime.Now.Subtract(mesurementTime);
|
|
Log.D(string.Format($"[ComponentTeachingRun] Change SelectCorrectionTable Time : {_MeasurementSpanTime.TotalMilliseconds}"));
|
|
|
|
return SequenceAction.Ok();
|
|
}
|
|
|
|
private SequenceActionResult SetBlowOff(SequenceContext context, string id, SequenceActionResult param)
|
|
{
|
|
if (_InitParam.UseBlow == false) {
|
|
return SequenceAction.Ok();
|
|
}
|
|
|
|
int intervalTime = _InitParam.IntervalTimeBlowOff;
|
|
Thread.Sleep(intervalTime);
|
|
|
|
_IO._iOUnit.WriteDoBit(Convert.ToInt32(ePDOAddress.SeamWeldingN2BlowSol), false);
|
|
|
|
Log.D(string.Format($"[ComponentTeachingRun] Blow OFF Complete"));
|
|
|
|
return SequenceAction.Ok();
|
|
}
|
|
|
|
private SequenceActionResult SaveResultData(SequenceContext context, string id, SequenceActionResult param)
|
|
{
|
|
context.PutValue(ContextKey.KEY_SEQUENCE_RESULT, true);
|
|
return SequenceAction.Ok();
|
|
}
|
|
|
|
private SequenceActionResult Complete(SequenceContext context, string id, SequenceActionResult param)
|
|
{
|
|
context.TryGetValue(ContextKey.KEY_SEQUENCE_RESULT, out bool seqResult);
|
|
SeqTeachingRun_OutParam result = new SeqTeachingRun_OutParam() { Success = seqResult };
|
|
|
|
if (seqResult == false) {
|
|
AlarmManager.Alarm(AlarmCode.ALARM_SEQUENCE_TeachingRun_ResultFailed);
|
|
}
|
|
|
|
Log.D("ComponentTeachingRun Sequence Complete");
|
|
return SequenceAction.Ok(result);
|
|
}
|
|
|
|
|
|
|
|
private void OnForceError(SequenceContext context, string arg2)
|
|
{
|
|
// Scanner Log 저장
|
|
SaveScannerRecordFileForamt();
|
|
|
|
//List 실행 종료
|
|
_Scanner.StopExecution();
|
|
|
|
//Anlog 출력 0으로 변경, efunctiontype을 command로 변경해주면 list가 아니라 직접 변경됨
|
|
_Scanner.WriteDA((uint)eAnalogPortOut.DA1, 0, eFunctionType.COMMAND);
|
|
_Scanner.WriteDA((uint)eAnalogPortOut.DA2, 0, eFunctionType.COMMAND);
|
|
|
|
// TruFiber레이저 Ready로 변경
|
|
ForceLaserReady();
|
|
|
|
//Scanner 원점 복귀
|
|
// 복귀 시 Scanner Busy 상태일 수 있으므로 진행중인 작업이 끝날 때까지 대기
|
|
Thread.Sleep(10);
|
|
_Scanner.JumpAbs(0, 0, eFunctionType.COMMAND);
|
|
Thread.Sleep(10);
|
|
|
|
Log.I("SequenceAction Ok : OnForceError");
|
|
}
|
|
|
|
private void OnStopped(SequenceContext context)
|
|
{
|
|
// Scanner Log 저장
|
|
SaveScannerRecordFileForamt();
|
|
|
|
//List 실행 종료
|
|
_Scanner.StopExecution();
|
|
|
|
//Anlog 출력 0으로 변경, efunctiontype을 command로 변경해주면 list가 아니라 직접 변경됨
|
|
_Scanner.WriteDA((uint)eAnalogPortOut.DA1, 0, eFunctionType.COMMAND);
|
|
_Scanner.WriteDA((uint)eAnalogPortOut.DA2, 0, eFunctionType.COMMAND);
|
|
|
|
// TruFiber레이저 Ready로 변경
|
|
ForceLaserReady();
|
|
|
|
//Scanner 원점 복귀
|
|
// 복귀 시 Scanner Busy 상태일 수 있으므로 진행중인 작업이 끝날 때까지 대기
|
|
Thread.Sleep(10);
|
|
_Scanner.JumpAbs(0, 0, eFunctionType.COMMAND);
|
|
Thread.Sleep(10);
|
|
|
|
// Laser 판넬 Off
|
|
//_IO.WriteDoBit(PAIX_PTOP.ePrismaticDOAddress.CCCAPLaserOnLampOn, false);
|
|
|
|
Log.I("SequenceAction Ok : OnStopped");
|
|
}
|
|
|
|
|
|
|
|
private void ForceLaserReady()
|
|
{
|
|
// 트럼프 레이저에서 PStartDyn, Prog_Stop을 감시하여 강제로 Ready 상태로 만드는 함수
|
|
if (_Laser.IsSimul) {
|
|
return;
|
|
}
|
|
|
|
//// PSTART_STATICAL 에서는 필요 없어보임.
|
|
//_LaserTruFiber.SetProgEnd();
|
|
|
|
// Laser 판넬 Off
|
|
//_IO.WriteDoBit(PAIX_PTOP.ePrismaticDOAddress.CCCAPLaserOnLampOn, false);
|
|
|
|
Log.I("SequenceAction Ok : Set Laser Ready");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// TODO: UseStream 으로 TeachingRun 을 할 필요가 있을까?? 확인 필요
|
|
public class SeqComponentTeachingRun_InitParam : InitParam
|
|
{
|
|
public bool IsObjectSelectAll { get; set; } // All == true, Selected Object == false
|
|
|
|
public bool UseStream { get; set; }
|
|
|
|
public bool UseBlow { get; set; }
|
|
public int IntervalTimeBlowOn { get; set; }
|
|
public int IntervalTimeBlowOff { get; set; }
|
|
|
|
public bool NeedSetLaserTiming { get; set; } // For SubSequence, SeqUploadWeldingList
|
|
|
|
}
|
|
|
|
public class SeqComponentTeachingRun_OutParam : OutParam
|
|
{
|
|
|
|
}
|
|
} |