CellInfoScheduler 프로그램 최초 업로드

This commit is contained in:
최이근 2026-02-28 16:52:42 +09:00
parent 54b6b6ccdc
commit 614b5f1a5b
8 changed files with 486 additions and 212 deletions

View File

@ -0,0 +1,123 @@
using System;
using System.Collections.Generic;
using ILSOS_CCW_H52A.Sequences;
using ILSOS_CCW_H52A.Data;
namespace ILSOS_CCW_H52A.Data
{
// 물리 지그 번호 (0-based enum, 1-based 변환: (eJigNo)(jigNo - 1))
public enum eJigNo
{
Jig1, Jig2, Jig3, Jig4,
Jig5, Jig6, Jig7, Jig8,
Jig9, Jig10, Jig11, Jig12,
Jig13, Jig14, Jig15, Jig16,
}
/// <summary>
/// Dictionary&lt;eJigNo, JigInfo&gt; 상속으로 물리 지그 번호 직접 접근과
/// 파이프라인 단계 접근을 동시에 지원하는 셀 스케줄러.
///
/// - 물리 지그 접근: scheduler[eJigNo.Jig5]
/// - 파이프라인 접근: scheduler.Prefetch1, scheduler.Prefetch2
///
/// 3단계 파이프라인: Prefetch2 → Prefetch1 → ReportTarget
/// Prefetch2 : 셀 정보 수신 완료, 스캐너 리스트 업로드 전
/// Prefetch1 : 업로드 완료, 실행 중 — EncoderMonitor 감시 + 완료 체크 대상
/// ReportTarget: 완료, 보고 대기
///
/// Shift()는 Prefetch2→Prefetch1만 전진. GC 할당 없음.
/// Prefetch1 ← Prefetch2 ← this[nextJig]
/// </summary>
public class CellInfoScheduler : Dictionary<eJigNo, JigInfo>
{
public JigInfo Prefetch2 { get; private set; } // 전전 셀: 셀 정보 수신, 업로드 전
public JigInfo Prefetch1 { get; private set; } // 전 셀: 업로드 완료, 실행 중 / 완료 체크 대상
/// 현재 보고 대상 JigInfo (CheckScannerListComplete에서 지정, 보고 완료 후 null)
public JigInfo ReportTarget { get; set; }
// 재시작 보존용 독립 슬롯 — Reset()에서 건드리지 않음
private readonly JigInfo _pendingSlot = new JigInfo();
// 재시작 전 미완료 보고 슬롯 (유효 시 non-null)
public JigInfo PendingComplete => HasPendingComplete ? _pendingSlot : null;
// 재시작 보존 보고가 있는지
public bool HasPendingComplete
=> _pendingSlot.IsCanExist && !_pendingSlot.ReportComplete;
// 현재 활성 보고 슬롯 — 재시작 pending 우선
public JigInfo ActiveReportSlot
=> HasPendingComplete ? _pendingSlot : ReportTarget;
// 보고가 필요한 상태인지 (정상 + 재시작 모두 포함)
public bool HasReport
=> HasPendingComplete
|| (ReportTarget != null && !ReportTarget.ReportComplete);
public CellInfoScheduler()
{
foreach (eJigNo no in Enum.GetValues(typeof(eJigNo)))
Add(no, new JigInfo());
}
// 1-based jigNo (_WeldingJigNo 등) → 0-based eJigNo 변환 편의 인덱서
public JigInfo this[int jigNo] {
get => base[(eJigNo)(jigNo - 1)];
}
// Reset() 전에 호출 — 미완료 보고가 있으면 보류 슬롯(_pendingSlot)에 복사하여 보존.
public void SavePendingComplete()
{
if (ReportTarget != null && ReportTarget.IsCanExist
&& ReportTarget.WeldComplete && !ReportTarget.ReportComplete)
_pendingSlot.CopyFrom(ReportTarget);
}
// 보고 완료 처리 — ReportTarget 마킹 및 _pendingSlot 클리어.
// GetReportCellResult_SeqResult 성공 시 호출.
public void CompleteReport()
{
ReportTarget?.SetReportComplete(true);
ReportTarget = null;
_pendingSlot.Clear();
}
/// <summary>
/// Prefetch2→Prefetch1 한 칸 전진. GC 할당 없음.
/// 새 Prefetch2 슬롯은 Clear 후 반환 — 호출자가 셀 정보를 채워야 한다.
/// </summary>
/// <param name="nextJig">새 Prefetch2로 지정할 물리 지그 (eJigNo)</param>
public void Shift(eJigNo nextJig)
{
Prefetch1 = Prefetch2;
Prefetch2 = this[nextJig];
Prefetch2.Clear();
}
/// <summary>
/// 전체 초기화 (AutoRun 시작 시 호출).
/// SavePendingComplete() 호출 후 이 메서드를 호출할 것.
/// _pendingSlot은 재시작 보고 보존을 위해 건드리지 않음.
/// </summary>
public void Reset()
{
foreach (var info in Values)
info.Clear();
Prefetch2 = null;
Prefetch1 = null;
ReportTarget = null;
// _pendingSlot: SavePendingComplete()가 복사해 둔 데이터를 유지
}
/// <summary>
/// 스케줄러 자체는 Clear 할 수 없도록 함
/// </summary>
public new void Clear()
{
Reset();
}
}
}

View File

@ -0,0 +1,150 @@
using AuroraLaserScanner;
using System;
using WeldingCommon.Data;
namespace ILSOS_CCW_H52A.Data
{
public class JigInfo
{
// --- 지그 식별 ---
public int JigNo; // 물리 지그 번호 (1-based, -1: 미설정)
// --- 스캐너 리스트 파라미터 (ReadyScannerList에서 채워짐) ---
public eListNo ListNo;
public DateTime WriteTime;
public double StartAngle;
public int WeldingCount;
// --- 캔 유무 / 용접 상태 ---
public bool IsCanExist;
public bool IsWelding;
public bool WeldComplete;
public DateTime WeldStartTime;
public DateTime WeldEndTime;
public TimeSpan WeldingSpanTime; // 스캐너 리스트 실행 시간 (GetTime() 결과)
// --- CellTracker 수신 정보 (GetReceiveCellInfo_SeqResult에서 채워짐) ---
public string LotId;
public string CellId;
// --- 플래그 ---
public bool CheckSendCellinfo;
public bool CheckSendPLCLaserPower;
public bool ReportComplete;
// --- 리포팅/뷰 용 상세 정보 ---
public WeldingData InnerCellInfo;
public JigInfo()
{
InnerCellInfo = new WeldingData();
Clear();
}
public void SetReportComplete(bool comp) => ReportComplete = comp;
/// <summary>
/// 다른 JigInfo의 모든 필드를 이 슬롯에 복사 (재시작 보존용 pendingSlot에 사용)
/// </summary>
public void CopyFrom(JigInfo src)
{
JigNo = src.JigNo;
ListNo = src.ListNo;
WriteTime = src.WriteTime;
StartAngle = src.StartAngle;
WeldingCount = src.WeldingCount;
IsCanExist = src.IsCanExist;
IsWelding = src.IsWelding;
WeldComplete = src.WeldComplete;
WeldStartTime = src.WeldStartTime;
WeldEndTime = src.WeldEndTime;
WeldingSpanTime = src.WeldingSpanTime;
LotId = src.LotId;
CellId = src.CellId;
CheckSendCellinfo = src.CheckSendCellinfo;
CheckSendPLCLaserPower = src.CheckSendPLCLaserPower;
ReportComplete = src.ReportComplete;
UpdateCellInfo(src.InnerCellInfo);
}
/// <summary>
/// 슬롯 전체 초기화 (Shift 시 새 Prefetch2 슬롯에 자동 호출됨)
/// </summary>
public void Clear()
{
JigNo = -1;
ListNo = 0;
WriteTime = DateTime.MinValue;
StartAngle = 0.0;
WeldingCount = -1;
IsCanExist = false;
IsWelding = false;
WeldComplete = false;
WeldStartTime = DateTime.MinValue;
WeldEndTime = DateTime.MinValue;
WeldingSpanTime = TimeSpan.Zero;
LotId = string.Empty;
CellId = string.Empty;
CheckSendCellinfo = false;
CheckSendPLCLaserPower = false;
ReportComplete = false;
InnerCellInfo.CellId = string.Empty;
InnerCellInfo.LotId = string.Empty;
InnerCellInfo.AutoRunStartTime = DateTime.MinValue;
InnerCellInfo.WeldingStartTime = DateTime.MinValue;
InnerCellInfo.WeldingEndTime = DateTime.MinValue;
InnerCellInfo.WeldingSpanTimeMilliseconds = string.Empty;
InnerCellInfo.TodayProduct = 0;
InnerCellInfo.TotalProduct = 0;
InnerCellInfo.RecipeNo = 0;
InnerCellInfo.WeldStartTime = string.Empty;
InnerCellInfo.WeldTime = string.Empty;
InnerCellInfo.WorkCellId = string.Empty;
InnerCellInfo.WorkLotId = string.Empty;
InnerCellInfo.LBAD_Judge = string.Empty;
InnerCellInfo.LDD_Judge = string.Empty;
InnerCellInfo.LaserPowerCore = string.Empty;
InnerCellInfo.LaserPowerRing = string.Empty;
InnerCellInfo.MarkSpeed = string.Empty;
InnerCellInfo.FocalLength = string.Empty;
InnerCellInfo.WeldingJigNo = string.Empty;
InnerCellInfo.LaserPowerByLBAD = string.Empty;
InnerCellInfo.CheckCanExist = false;
InnerCellInfo.CheckScannerListEnd = false;
}
/// <summary>
/// InnerCellInfo를 WeldingData 로부터 필드 단위로 복사 (GC 할당 없음)
/// </summary>
public void UpdateCellInfo(WeldingData data)
{
InnerCellInfo.CellId = data.CellId;
InnerCellInfo.LotId = data.LotId;
InnerCellInfo.AutoRunStartTime = data.AutoRunStartTime;
InnerCellInfo.WeldingStartTime = data.WeldingStartTime;
InnerCellInfo.WeldingEndTime = data.WeldingEndTime;
InnerCellInfo.WeldingSpanTimeMilliseconds = data.WeldingSpanTimeMilliseconds;
InnerCellInfo.TodayProduct = data.TodayProduct;
InnerCellInfo.TotalProduct = data.TotalProduct;
InnerCellInfo.RecipeNo = data.RecipeNo;
InnerCellInfo.WeldStartTime = data.WeldStartTime;
InnerCellInfo.WeldTime = data.WeldTime;
InnerCellInfo.WorkCellId = data.WorkCellId;
InnerCellInfo.WorkLotId = data.WorkLotId;
InnerCellInfo.LBAD_Judge = data.LBAD_Judge;
InnerCellInfo.LDD_Judge = data.LDD_Judge;
InnerCellInfo.LaserPowerCore = data.LaserPowerCore;
InnerCellInfo.LaserPowerRing = data.LaserPowerRing;
InnerCellInfo.MarkSpeed = data.MarkSpeed;
InnerCellInfo.FocalLength = data.FocalLength;
InnerCellInfo.WeldingJigNo = data.WeldingJigNo;
InnerCellInfo.LaserPowerByLBAD = data.LaserPowerByLBAD;
InnerCellInfo.CheckCanExist = data.CheckCanExist;
InnerCellInfo.CheckScannerListEnd = data.CheckScannerListEnd;
}
}
}

View File

@ -1,7 +1,15 @@
using WeldingCommon;
using Aurora;
using Aurora.Api;
using ILSOS_CCW_H52A.Sequences.ProcessObjects;
using WeldingCommon;
namespace ILSOS_CCW_H52A
{
public static class AuroraEnvironmentExt
{
public static ProcessManager GetProcessManager(this IAuroraEnvironment _) => ProcessManager.Instance;
public static ProcessManager GetProcessManager(this SequenceContext _) => ProcessManager.Instance;
}
internal class EquipDef
{
}

View File

@ -76,6 +76,8 @@ namespace ILSOS_CCW_H52A
base.OnStartUp();
AuroraEnv.SequenceEntry[SequenceIds.AlarmMonitoring].Start();
AuroraEnv.GetProcessManager().Init(AuroraEnv);
}
// For Selection LaserDevice

View File

@ -92,6 +92,8 @@
<ItemGroup>
<Compile Include="Alarm\AlarmHelper.cs" />
<Compile Include="Alarm\AlarmManager.cs" />
<Compile Include="Datas\CellInfoScheduler.cs" />
<Compile Include="Datas\JigInfo.cs" />
<Compile Include="Device\IO\IOController.Address.cs" />
<Compile Include="Device\IO\IOController.Logic.cs" />
<Compile Include="Device\Laser\LaserCoherentArm_IO.cs" />

View File

@ -3,12 +3,14 @@ using Aurora.Api;
using Aurora.Util.HislogPlus;
using AuroraLaserScanner;
using AuroraPLC;
using ILSOS_CCW_H52A.Data;
using ILSOS_CCW_H52A.Device;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
@ -29,12 +31,15 @@ namespace ILSOS_CCW_H52A.Sequences.ProcessObjects
return _Instance;
}
}
private bool usedAnalyzerLog = false;
private readonly Logger _log = new Logger("ProcessManager");
private IAuroraEnvironment _env;
private PLCControllerCCWH52A _plc;
private ScannerCCWH52A _scanner;
private bool usedAnalyzerLog = false;
private readonly Logger _log = new Logger("ProcessManager");
public CellInfoScheduler cellScheduler { get; set; }
public const double compensateEncoderReset = 0.015; // 엔코더 리셋 보상 각도 설정(70도/sec 기준 약 0.015도)
public const int RotaryJigCount = 16;
@ -48,6 +53,7 @@ namespace ILSOS_CCW_H52A.Sequences.ProcessObjects
_env = env;
_plc = _env.DeviceManager[DeviceIds.PLCDevice] as PLCControllerCCWH52A;
_scanner = _env.DeviceManager[DeviceIds.Scanner] as ScannerCCWH52A;
cellScheduler = new CellInfoScheduler();
}
public IEnumerable<bool> CheckRotaryState()
{

View File

@ -14,9 +14,11 @@ 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 System;
using System.Collections.Generic;
using System.IO;
@ -52,10 +54,6 @@ namespace ILSOS_CCW_H52A.Sequences
// TODO: [장비구현필요] 실제 공정 추가
const string SEQ_CHECK_LOAD_COMPLETE = "[SeqAutoRunCCWH52A] Check Load Complete";
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)";
public const string SEQ_CHECK_SCANNER_LIST_COMPLETE = "[SeqAutoRunCCWH52A] Check Scanner List Complete";
const string SEQ_READY_SCANNER_LIST = "[SeqAutoRunCCWH52A] Ready ScannerList";
@ -63,6 +61,10 @@ namespace ILSOS_CCW_H52A.Sequences
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";
@ -101,8 +103,6 @@ namespace ILSOS_CCW_H52A.Sequences
private SystemParameter _SystemParameter;
private ScannerCanvasUserCtrl _ScannerCanvasUserCtrl; //스캐너 동작관련 객체 선언
private string _LotId = "N/A";
private string _CellId = "N/A";
private int _ModelNo;
private IRecipeManager _RecipeManager;
@ -117,25 +117,13 @@ namespace ILSOS_CCW_H52A.Sequences
private uint _TotalProduct;
private double _LaserPowerByLBAD;
private int _CompletedJigNo;
private static bool isWeled;
private bool _ModulationMode;
private DateTime _AutoRunStartTime;
private DateTime _WeldingStartTime;
private TimeSpan _WeldingSpanTime;
private DateTime _WeldEndTime;
private TimeSpan _InterfaceSpanTime;
private DateTime _InterfaceStartTime;
// 연속 지그 간 용접 간격 계산 전용 (보고 데이터 아님 — 인터벌 로그용)
private DateTime _prevWeldEndTime;
private double _CellMeasureCenterOffsetX;
private double _CellMeasureCenterOffsetY;
// MOTF
private TimeSpan _WeldInterval;
private int _LastScannerParamNo = -1; //마지막 작업한 스캔 변수값들이 현재 작업하려는 스캔 변수값과 같은지 판단함(같으면 설정작업을 skip함)
private int _EncoderOverflowCount = 0;
private double _RotaryEncoderOffset = 0;
@ -143,10 +131,11 @@ namespace ILSOS_CCW_H52A.Sequences
private int _WeldingCount = 0;
private int _RotaryJigCount = 16;
// 전전 셀/전 셀/대기 셀/완료 셀 파이프라인 스케줄러 (16개 JigInfo 풀, GC-free)
private CellInfoScheduler _CellScheduler;
private double _WeldingStartAngle;
private bool isTheFirstListWrite;
private bool isTheSecondListWrite;
private bool isTheFirstListExecute;
private int _RecipeNo;
@ -176,13 +165,13 @@ namespace ILSOS_CCW_H52A.Sequences
sb.AddAction(SEQ_CHECK_LOAD_COMPLETE, CheckLoadComplete);
sb.AddAction(SEQ_CHECK_SCANNER_LIST_COMPLETE, CheckScannerListComplete);
sb.AddDecision(SEQ_READY_SCANNER_LIST, ReadyScannerList);
sb.AddAction(SEQ_SET_PARAMS_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_CHECK_SCANNER_LIST_COMPLETE, CheckScannerListComplete);
sb.AddDecision(SEQ_READY_SCANNER_LIST, ReadyScannerList);
sb.AddAction(SEQ_SET_PARAMS_UPLOAD_WELDING_LIST_SEQ, SetUploadWeldingList_SeqParams);
sb.AddSubsequence(SEQ_DO_ID_UPLOAD_WELDING_LIST_SEQ, SequenceManager.GetSeqBuilder_SeqUploadWeldingList());
sb.AddAction(SEQ_GET_RESULT_UPLOAD_WELDING_LIST_SEQ, GetUploadWeldingList_SeqResult);
@ -203,8 +192,8 @@ namespace ILSOS_CCW_H52A.Sequences
sb.AddAction(SEQ_COMPLETE, Complete);
sb.Flows(sb.Init > sb[SEQ_ENTER] > sb[SEQ_CHECK_INIT_CONDITION] > sb[SEQ_FORK_ENCODER_MONITOR] > sb[SEQ_CHECK_LOAD_COMPLETE]
> sb[SEQ_SET_PARAMS_RECEIVE_CELL_INFO_SEQ] > sb[SEQ_DO_ID_RECEIVE_CELL_INFO_SEQ] > sb[SEQ_GET_PARAMS_RECEIVE_CELL_INFO_SEQ]
> sb[SEQ_CHECK_SCANNER_LIST_COMPLETE] > sb[SEQ_READY_SCANNER_LIST]
> sb[SEQ_SET_PARAMS_RECEIVE_CELL_INFO_SEQ] > sb[SEQ_DO_ID_RECEIVE_CELL_INFO_SEQ] > sb[SEQ_GET_PARAMS_RECEIVE_CELL_INFO_SEQ]
> sb[SEQ_SET_PARAMS_UPLOAD_WELDING_LIST_SEQ] > sb[SEQ_DO_ID_UPLOAD_WELDING_LIST_SEQ] > sb[SEQ_GET_RESULT_UPLOAD_WELDING_LIST_SEQ]
> sb[SEQ_EXECUTE_WELDING] > sb[SEQ_REQUEST_UNLOAD_CELL] > sb[SEQ_CHECK_CAN_WELDED]
> sb[SEQ_SET_PARAMS_REPORT_CELL_RESULT_SEQ] > sb[SEQ_DO_ID_REPORT_CELL_RESULT_SEQ] > sb[SEQ_GET_RESULT_REPORT_CELL_RESULT_SEQ]
@ -299,9 +288,6 @@ namespace ILSOS_CCW_H52A.Sequences
_WeldingScannerListNo = eListNo.LIST1;
_CellMeasureCenterOffsetX = 0.0;
_CellMeasureCenterOffsetY = 0.0;
var currentModelName = context.Recipes.ActivatedModelName();
var modelItem = context.GetModel<EquipmentSpecificModel_CCW_H52A>(currentModelName, CommonModelIds.EquipmentSpecificModel);
_ModelNo = modelItem.Model_No;
@ -322,6 +308,8 @@ namespace ILSOS_CCW_H52A.Sequences
_CellTracker = context.GetCellTracker();
_PLC = context.Devices[DeviceIds.PLCDevice] as PLCControllerCCWH52A;
_IO = context.GetIOUnit();
_CellScheduler = ProcessManager.Instance.cellScheduler;
}
private bool CheckDeviceReady(SequenceContext context)
@ -403,15 +391,13 @@ namespace ILSOS_CCW_H52A.Sequences
_ModulationMode = false;
}
//// PLC Initialize AutoRun Bit 최소화 작업 진행
if (_PLC.ReadBit((int)eBitInput.PLC_AUTO_STATUS).data == false) // AutoRun Bit가 OFF인 경우
{
if (_PLC.ReadBit((int)eBitInput.PLC_AUTO_REQ).data == false) // AutoRun Request 상태 체크
{
///// 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 {
} 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;
}
@ -433,10 +419,6 @@ namespace ILSOS_CCW_H52A.Sequences
string[] strArr = Dao.SystemRecipe.WorkRecipeFile.Split('\\');
RecipeFileName = strArr[strArr.Length - 1];
_LastScannerParamNo = -1;
// 트럼프 TruDisk를 쓰기 위해서는 SetFirstPulseKiller를 사용해야 함.
// Rotary는 시작 트리거 미사용
_Scanner.SetControlMode(0);
UtilUnit.logUnit.SetLog(LogUnit.eLogType.DEBUG, MethodBase.GetCurrentMethod().Name, "[AutoRun] 외부 시작 트리거 DISABLE");
@ -455,13 +437,15 @@ namespace ILSOS_CCW_H52A.Sequences
Log.I(string.Format($"ScannerUnit.InitRotaryOnTheFlyWeldingCount : m_abwRotaryEncoderOffset({_RotaryEncoderOffset}), weldingCount({_WeldingCount}), weldingJigNo({_WeldingJigNo}), rotaryJigCount({_RotaryJigCount})"));
// 용접 완료 대기 스캐너 List 리셋
ResetInputScannerList();
WaitingScannerList.Reset();
CompleteScannerList.Reset();
// JigInfo 풀 스케줄러 초기화 (전전/전/대기/완료 셀 파이프라인)
// ── 재시작 전 미완료 보고 보존 ───────────────────────────────────────────────
// ReportTarget이 있는 상태로 정지가 들어오면 Reset() 후 슬롯이 Clear()되어 보고 불가.
// SavePendingComplete()로 _pendingSlot에 복사한 뒤 Reset()을 호출한다.
_CellScheduler.SavePendingComplete();
_CellScheduler.Reset();
// ─────────────────────────────────────────────────────────────────────────────
isTheFirstListWrite = true; // 작업 시작 시 처음 2개의 List는 입력하고 시작한다
isTheSecondListWrite = true;
isTheFirstListExecute = true; // 제일 첫 List는 ExecuteList 필요. 이후는 AutoChange
_PLC.WriteBit(eBitOutput.PC_AUTO_STATUS, true);
@ -559,100 +543,80 @@ namespace ILSOS_CCW_H52A.Sequences
private SequenceActionResult CheckScannerListComplete(SequenceContext context, string id, SequenceActionResult param)
{
if (isTheFirstListWrite) // 작업 시작 시, 첫번째 리스트는 바로 쓰고, 바로 시작한다(ExecuteList). 다만 List 명령에 의해 Encoder가 들어올 때까지 대기.
{
isTheFirstListWrite = false;
Log.I("Write the 1st scanner list");
// 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 (!CheckCurrentEncoderAngle(jigComplete.StartAngle)) {
// IsCanExist=false(공 지그)는 리스트 실행은 하지만 용접이 없으므로
// 엔코더 범위를 벗어나도 에러가 아님
if (jigComplete.IsCanExist) {
Log.I("ERR_SCANNER_LIST_PROCESSING_TIMEOVER");
return SequenceAction.Error();
}
return SequenceAction.Ok();
}
else {
if (isTheSecondListWrite) // 두번째 리스트는 바로 쓰지만, 첫번째 리스트 실행이 완료되면 자동으로 이어서 실행된다(AutoChange)
{
isTheSecondListWrite = false;
Log.I("Write the 2nd scanner list");
if (jigComplete.IsCanExist) {
#region 용접 시간, 엔코더 측정/기록
double elaspedTime = _Scanner.GetTime();
}
else {
isWeled = false;
// 첫번째나 두번째가 아니라면, 실제 리스트 실행이 끝난 것이기 때문에 완료 처리가 필요
CompleteScannerList = InputScannerList[(int)_WeldingScannerListNo - 1];
if (jigComplete.WeldComplete) {
jigComplete.WeldEndTime = DateTime.Now;
jigComplete.WeldingSpanTime = TimeSpan.FromSeconds(elaspedTime);
TimeSpan weldInterval = jigComplete.WeldEndTime - _prevWeldEndTime;
_prevWeldEndTime = jigComplete.WeldEndTime;
if (!CheckCurrentEncoderAngle(CompleteScannerList.StartAngle)) {
// 에러 상황(현재 Rotary Encoder 값이 정상적인 스캐너 리스트 각도 범위를 벗어남)
Log.I("ERR_SCANNER_LIST_PROCESSING_TIMEOVER");
// 보고 대상 슬롯 지정 (스케줄러가 관리)
_CellScheduler.ReportTarget = jigComplete;
return SequenceAction.Error();
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));
// WON : 2칸 전에 용접을 진행하지 않아 현재 Angle이 CompleteScannerList.StartAngle + 45 범위를 벗어난 경우라면 에러 상황이 아니므로 에러로 처리 할 필요가 없다
// 위에 CheckCurrentEncoderAngle if문 조건 대신 아래 내용으로 테스트 해본 뒤에 적용 필요
//if (!CheckCurrentEncoderAngle(CompleteScannerList.StartAngle)) {
// // 에러 상황(현재 Rotary Encoder 값이 정상적인 스캐너 리스트 각도 범위를 벗어남)
// if (CompleteScannerList.CanExist) {
// Log.I("ERR_SCANNER_LIST_PROCESSING_TIMEOVER");
// return SequenceAction.Error();
// }
// return SequenceAction.Ok();
}
else {
if (CompleteScannerList.CanExist) {
isWeled = true;
#region 용접 시간, 엔코더 측정/기록
//_WeldInterval = DateTime.Now - _WeldEndTime;
//_WeldEndTime = DateTime.Now;
//double elapsedTime = _Scanner.GetTime();
//_WeldingSpanTime = TimeSpan.FromSeconds(elapsedTime); // Encoder 대기 넘어간 후의, 실제 스캐너 리스트 실행 시간
//_WeldingStartTime = _WeldEndTime.Subtract(_WeldingSpanTime);
double elaspedTime = _Scanner.GetTime();
if (CompleteScannerList.WeldComplete) {
_WeldInterval = DateTime.Now - _WeldEndTime;
_WeldEndTime = DateTime.Now;
_WeldingStartTime = CompleteScannerList.WeldStartTime;
_WeldingSpanTime = TimeSpan.FromSeconds(elaspedTime);
_LotId = CompleteScannerList.LotId;
_CellId = CompleteScannerList.CellId;
Log.I(string.Format("JigNo : {0}, Weld start : {1}, Weld End : {2}, WeldTime : {3}, interval : {4:F3}",
CompleteScannerList.JigNo, _WeldingStartTime.ToString("HH:mm:ss.fff"), _WeldEndTime.ToString("HH:mm:ss.fff"), _WeldingSpanTime.TotalMilliseconds, _WeldInterval.TotalSeconds));
if (_PLC.ReadBit((int)eBitInput.PLC_USE_WELD_JIG_1 + CompleteScannerList.JigNo - 1).data) {
_PLC.WriteBit((int)eBitOutput.PC_WELD_COMPLETE_1 + CompleteScannerList.JigNo - 1, true);
Log.I(string.Format($"PC_WELD_COMPLETE_{CompleteScannerList.JigNo} -> true)"));
}
UpdateProductCount(context);
UpdateProductInfoForView();
SaveCellAPD();
}
//if (_SystemParameter.BypassModeLBAD == false) {
// if (_StreamLightLBAD.SendWeldingStop() == false) {
// Log.I(string.Format($"[LBAD Interface]SendWeldingStop null"));
// //return null;
// }
//}
UtilUnit.logUnit.SetLog(LogUnit.eLogType.PRODUCT, MethodBase.GetCurrentMethod().Name, "UPDATE_WELDING_TIME;" + _WeldingSpanTime.TotalMilliseconds);
UtilUnit.logUnit.SetLog(LogUnit.eLogType.PRODUCT, MethodBase.GetCurrentMethod().Name, "UPDATE_PRODUCT_COUNT;" + _WeldEndTime.ToString("yyyy-MM-dd HH:mm:ss"));
double encoder0_0, encoder1_0, encoder0_1, encoder1_1;
_Scanner.ReadEncoder(out encoder0_0, out encoder1_0, out encoder0_1, out encoder1_1, true);
double startEncoderCount = encoder0_0;
double endEncoderCount = encoder0_1;
double angularVelocity = (endEncoderCount - startEncoderCount) / elaspedTime; // 단위는 [degree/sec]
Log.I(string.Format("Encoder start : {0:F4}, end : {1:F4}, vel : {2:F2}", startEncoderCount, endEncoderCount, angularVelocity));
#endregion
#region 스캐너 리스트 Record 파일 기록
if (_Scanner.Config.UseRecordSignal) {
String RecordFileName = _RecipeManager.ActivatedRecipe().Name + "_Jig#" + CompleteScannerList.JigNo.ToString();
string DataFilePath = ProjectUtil.GetDataLogSetting().GetFilePath_ScannerRecordLog(RecordFileName, true);
_Scanner.SaveRecord(DataFilePath);
}
#endregion
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)"));
}
UpdateProductCount(context);
UpdateProductInfoForView();
SaveCellAPD();
}
//if (_SystemParameter.BypassModeLBAD == false) {
// if (_StreamLightLBAD.SendWeldingStop() == false) {
// Log.I(string.Format($"[LBAD Interface]SendWeldingStop null"));
// //return null;
// }
//}
UtilUnit.logUnit.SetLog(LogUnit.eLogType.PRODUCT, MethodBase.GetCurrentMethod().Name, "UPDATE_WELDING_TIME;" + 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();
@ -668,14 +632,15 @@ namespace ILSOS_CCW_H52A.Sequences
// Encoder 반전되는 순간이며, 이 때만 인터락 무시
}
else if (startAngle <= currentAngle) {
if (currentAngle < startAngle + 45)// 다음 리스트 구간이 끝나기 전까지는 완료가 되어야 한다
{
// 다음 리스트 구간이 끝나기 전까지는 완료가 되어야 한다
if (currentAngle < startAngle + 45) {
Log.I(string.Format("Current encoder check OK, startAngle : {0}, currentAngle : {1})", startAngle, currentAngle));
return true;
}
else Log.W(string.Format("Current encoder check NG(Too late), startAngle : {0}, currentAngle : {1})", startAngle, currentAngle));
} else
Log.W(string.Format("Current encoder check NG(Too late), startAngle : {0}, currentAngle : {1})", startAngle, currentAngle));
}
else 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 early), startAngle : {0}, currentAngle : {1})", startAngle, currentAngle));
return false;
}
@ -727,6 +692,17 @@ namespace ILSOS_CCW_H52A.Sequences
Log.I("Can Exist : " + canExist.ToString() + ", JigNo " + _WeldingJigNo + " ,CountNo " + _WeldingCount + ", StartAngle(Y) : " + _WeldingStartAngle);
// canExist 여부와 무관하게 매 지그마다 파이프라인 전진
// canExist=false 지그도 Shift()해야 Prefetch1 타이밍이 올바르게 유지됨.
_CellScheduler.Shift((eJigNo)(_WeldingJigNo - 1));
_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;
if (canExist) {
#region 용접할 Objects 선택
if (_WeldingJigNo - 1 < _ScannerCanvasUserCtrl.Model.Layers.Length) {
@ -738,16 +714,11 @@ namespace ILSOS_CCW_H52A.Sequences
Log.I(string.Format("Page 불러오기 : [0])"));
}
#endregion
// 입력한 Scanner List를 완료대기열에 추가
// Won : GetProcessingWeldingData().WorkLotId 는 CellTrakcer로 부터 받은 상태의 LotId
// GetProcessingWeldingData().LotId 는 AutorunView에 표기할 용접이 완료 된 LotID
string LotId = Status.GetProcessingWeldingData().WorkLotId; // CellTracker로 부터 WorkLotID, WorkCellID 받는 구문 추가 후 해당 데이터로 입력 필요
string CellId = Status.GetProcessingWeldingData().WorkCellId;
SetInputScannerList(_WeldingScannerListNo, DateTime.Now, _WeldingCount, _WeldingStartAngle, _WeldingJigNo, _RotaryJigCount, canExist, LotId, CellId);
_RecipeNo = _RecipeManager.ActivatedRecipe().Id;
SetWeldingData();
return SEQ_SET_PARAMS_UPLOAD_WELDING_LIST_SEQ;
return SEQ_SET_PARAMS_RECEIVE_CELL_INFO_SEQ;
}
else {
return SEQ_EXECUTE_WELDING;
@ -814,22 +785,22 @@ namespace ILSOS_CCW_H52A.Sequences
public bool GetRotaryCanExist(int jigNo)
{
#region 지그 16개 요청기록 로그 남기기, 빠져나간 캔에 대한 Complete Bit 초기화
string result = "";
//string result = "";
for (int i = 1; i <= _RotaryJigCount; i++) {
if (_PLC.ReadBit((int)eBitInput.PLC_USE_WELD_JIG_1 + i).data)
result += "1, ";
else {
result += "0, ";
//for (int i = 1; i <= _RotaryJigCount; i++) {
// if (_PLC.ReadBit((int)eBitInput.PLC_USE_WELD_JIG_1 + i).data)
// result += "1, ";
// else {
// result += "0, ";
string outId = string.Format("PC_WELD_COMPLETE_{0}", i);
// string outId = string.Format("PC_WELD_COMPLETE_{0}", i);
if (_PLC.ReadBit((int)eBitInput.PLC_USE_WELD_JIG_1 + i).data) {
_PLC.WriteBit((int)eBitOutput.PC_WELD_COMPLETE_1 + i, false);
Log.I(string.Format($"PC_WELD_COMPLETE_{1 + i} -> FALSE)"));
}
}
}
// if (_PLC.ReadBit((int)eBitInput.PLC_USE_WELD_JIG_1 + i).data) {
// _PLC.WriteBit((int)eBitOutput.PC_WELD_COMPLETE_1 + i, false);
// Log.I(string.Format($"PC_WELD_COMPLETE_{1 + i} -> FALSE)"));
// }
// }
//}
//UtilUnit.logUnit.SetLog(LogUnit.eLogType.DEBUG, MethodBase.GetCurrentMethod().Name, string.Format("PLC -> PC 용접 Cmd : ({0})", result));
#endregion
@ -846,8 +817,8 @@ namespace ILSOS_CCW_H52A.Sequences
SeqUploadWeldingList_InitParam seqParams = new SeqUploadWeldingList_InitParam() {
NeedSetLaserTiming = _ModulationMode,
IncludeListStartEnd = false,
CellMeasureCenterOffsetX = _CellMeasureCenterOffsetX,
CellMeasureCenterOffsetY = _CellMeasureCenterOffsetY,
CellMeasureCenterOffsetX = 0,
CellMeasureCenterOffsetY = 0,
SelectedDrawObjectList = _SelectedDrawObjectList,
WeldingScannerListNo = _WeldingScannerListNo,
Rotation_Degree = 11.25,
@ -864,7 +835,15 @@ namespace ILSOS_CCW_H52A.Sequences
return SequenceAction.ForceStopByAlarm(AlarmCode.ALARM_SEQUENCE_UploadWeldingList_ResultFailed);
//return null;
}
// 입력한 Scanner List를 완료대기열에 추가
// Won : GetProcessingWeldingData().WorkLotId 는 CellTrakcer로 부터 받은 상태의 LotId
// GetProcessingWeldingData().LotId 는 AutorunView에 표기할 용접이 완료 된 LotID
string LotId = Status.GetProcessingWeldingData().WorkLotId; // CellTracker로 부터 WorkLotID, WorkCellID 받는 구문 추가 후 해당 데이터로 입력 필요
string CellId = Status.GetProcessingWeldingData().WorkCellId;
_CellScheduler.Prefetch2.LotId = LotId;
_CellScheduler.Prefetch2.CellId = CellId;
Log.FuncI("CellTracker Load");
return SequenceAction.Ok();
}
@ -919,7 +898,6 @@ namespace ILSOS_CCW_H52A.Sequences
}
private SequenceActionResult EncoderMonitor(SequenceContext context, string id, SequenceActionResult param)
{
int EncoderLogCount = 0;
double send_time_offset = 0.1;
string LotID = "", CellID = "";
@ -929,40 +907,46 @@ namespace ILSOS_CCW_H52A.Sequences
double CurrentPosPLC = GetRotaryCurrentPosition();
bool WeldingInPosition = encoderX > _Scanner.Config.MarkingRangeMinRotate + 11.25 - send_time_offset - 0.1 && encoderX < _Scanner.Config.MarkingRangeMinRotate + 11.25 - send_time_offset + 0.1;
if (WeldingInPosition) {
if (InputScannerList[(int)_WeldingScannerListNo - 1].CanExist) {
Log.I($"Fork Before Min Rotate Encoder X : ({encoderX}), Encoder Y : ({currentAngle}), PLC Rotary : ({CurrentPosPLC}), List {_WeldingScannerListNo}, JigNo : ({InputScannerList[(int)_WeldingScannerListNo - 1].JigNo})");
if (!_SystemParameter.BypassModeLBAD) {
if (InputScannerList[(int)_WeldingScannerListNo - 1].CheckSendCellinfo == false) {
LotID = InputScannerList[(int)_WeldingScannerListNo - 1].LotId;
CellID = InputScannerList[(int)_WeldingScannerListNo - 1].CellId;
if (currentAngle > InputScannerList[(int)_WeldingScannerListNo - 1].StartAngle) {
InputScannerList[(int)_WeldingScannerListNo - 1].CheckSendCellinfo = _StreamLightLBAD.SendCellInfo(LotID, CellID, _RecipeNo, _ModelNo, InputScannerList[(int)_WeldingScannerListNo - 1].JigNo);
// ── JigScheduler: 포크 내에서 참조 캡처 (Shift와의 경합 방지) ──────
// Prefetch1: 2회 전 업로드된 지그 = 현재 물리적으로 실행 중인 지그.
// 원본 코드의 InputScannerList[(listNo-1)]과 동일 대상.
// Shift()는 메인 시퀀스에서만 호출되므로 루프 시작 시 캡처해도 안전.
var executeTarget = _CellScheduler.Prefetch1;
if (InputScannerList[(int)_WeldingScannerListNo - 1].CheckSendCellinfo == false)
Log.I($"[LBAD Interface]SendCellInfo null, LotID [{LotID}], CellID [{CellID}], JigNo [{InputScannerList[(int)_WeldingScannerListNo - 1].JigNo}]");
if (WeldingInPosition) {
if (executeTarget != null && executeTarget.IsCanExist) {
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) {
LotID = executeTarget.LotId;
CellID = executeTarget.CellId;
if (currentAngle > executeTarget.StartAngle) {
executeTarget.CheckSendCellinfo = _StreamLightLBAD.SendCellInfo(LotID, CellID, _RecipeNo, _ModelNo, executeTarget.JigNo);
if (!executeTarget.CheckSendCellinfo)
Log.I($"[LBAD Interface]SendCellInfo null, LotID [{LotID}], CellID [{CellID}], JigNo [{executeTarget.JigNo}]");
else
Log.I($"[StreamLight]SendCellInfo LotID [{LotID}], CellID [{CellID}], JigNo [{InputScannerList[(int)_WeldingScannerListNo - 1].JigNo}]");
Log.I($"[StreamLight]SendCellInfo LotID [{LotID}], CellID [{CellID}], JigNo [{executeTarget.JigNo}]");
}
}
}
}
}
if (_IO._iOUnit.ReadDiBit(Convert.ToInt32(eADIAddress.LaserOn))) {
if (InputScannerList[(int)_WeldingScannerListNo - 1].WeldStartTime == DateTime.MinValue && InputScannerList[(int)_WeldingScannerListNo - 1].CanExist) {
InputScannerList[(int)_WeldingScannerListNo - 1].WeldStartTime = DateTime.Now;
InputScannerList[(int)_WeldingScannerListNo - 1].WeldComplete = true;
EncoderLogCount = 0;
Log.I($"Laser On, Welding Complete. JigNo : ({InputScannerList[(int)_WeldingScannerListNo - 1].JigNo})");
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})");
}
if (EncoderLogCount == 0) {
Log.I($"Fork Laser On Encoder X : ({encoderX}), Encoder Y : ({currentAngle}), PLC Rotary : ({CurrentPosPLC}), List {_WeldingScannerListNo}, JigNo : ({InputScannerList[(int)_WeldingScannerListNo - 1].JigNo})");
}
EncoderLogCount = (EncoderLogCount + 1) % 7;
}
var IsLaserToggle = ChangeChecker.IsChangedBool("LaserToggle", LaserIO);
if (IsLaserToggle)
Log.I($"Fork Laser On({IsLaserToggle}) Encoder X : ({encoderX}), Encoder Y : ({currentAngle}), PLC Rotary : ({CurrentPosPLC}), List {executeTarget?.ListNo}, 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);
@ -999,22 +983,20 @@ namespace ILSOS_CCW_H52A.Sequences
{
Status.GetProcessingWeldingData().TotalProduct = _TotalProduct;
Status.GetProcessingWeldingData().TodayProduct = _TodayProduct;
Status.GetProcessingWeldingData().AutoRunStartTime = _AutoRunStartTime;
Status.GetProcessingWeldingData().WeldingStartTime = _WeldingStartTime;
Status.GetProcessingWeldingData().WeldingEndTime = _WeldEndTime;
Status.GetProcessingWeldingData().WeldingSpanTimeMilliseconds = _WeldingSpanTime.TotalMilliseconds.ToString();
// Status.GetProcessingWeldingData().LotId 는 Autorun View에 표기할 용접이 완료 된 LotId 이므로 WorkLotID 와의 구분 확인
Status.GetProcessingWeldingData().LotId = _LotId;
Status.GetProcessingWeldingData().CellId = _CellId;
Status.GetProcessingWeldingData().RecipeNo = _RecipeNo;
Status.GetProcessingWeldingData().LaserPowerByLBAD = _LaserPowerByLBAD.ToString();
Status.GetProcessingWeldingData().WeldingJigNo = _CompletedJigNo.ToString();
// 보고 데이터는 스케줄러의 ActiveReportSlot(ReportTarget 또는 PendingComplete)에서 읽음
var report = _CellScheduler.ActiveReportSlot;
if (report != null) {
Status.GetProcessingWeldingData().LotId = report.LotId;
Status.GetProcessingWeldingData().CellId = report.CellId;
Status.GetProcessingWeldingData().WeldingStartTime = report.WeldStartTime;
Status.GetProcessingWeldingData().WeldingEndTime = report.WeldEndTime;
Status.GetProcessingWeldingData().WeldingSpanTimeMilliseconds = report.WeldingSpanTime.TotalMilliseconds.ToString();
Status.GetProcessingWeldingData().WeldingJigNo = report.JigNo.ToString();
}
}
private SequenceActionResult SetReportCellResult_SeqParams(SequenceContext context, string id, SequenceActionResult param)
{
@ -1034,9 +1016,8 @@ namespace ILSOS_CCW_H52A.Sequences
return null;
}
// 중복 보고 방지 플래그 세팅
CompleteScannerList.SetReportComplete(true);
isWeled = false;
// 보고 완료: ReportTarget.ReportComplete=true 설정 + pendingSlot 클리어
_CellScheduler.CompleteReport();
return SequenceAction.Ok();
}
@ -1063,18 +1044,19 @@ namespace ILSOS_CCW_H52A.Sequences
#endregion
private string CheckCanWelded(SequenceContext context, string id, ICollection<string> branches, SequenceActionResult param)
{
// 중복 보고 방지:
// - CanExist=false (skip 대상) 는 보고하지 않음(미사용)
// - WeldComplete=false (레이저 ON 감지/완료 처리 전) 는 보고하지 않음(미사용)
// - ReportComplete=true (이미 보고 완료) 는 다시 보고하지 않음
// .시퀀스가 너무 빨리 내려가서 보고하는 케이스로 예상
// ── 재시작 전 미완료 보고 우선 처리 ─────────────────────────────────────────
// SavePendingComplete()로 보존된 슬롯이 있으면 정상 파이프라인(ReportTarget)보다 우선.
if (_CellScheduler.HasPendingComplete) {
var p = _CellScheduler.PendingComplete;
Log.I($"[PendingReport] 재시작 전 미완료 보고: LotId={p.LotId}, CellId={p.CellId}, JigNo={p.JigNo}");
return SEQ_SET_PARAMS_REPORT_CELL_RESULT_SEQ;
}
// ─────────────────────────────────────────────────────────────────────────────
// 보고 누락:
// - 첫번째, 두번째는 UploadWeldingList를 해야하기 때문에 일단 넘긴다.
if (isWeled
//CompleteScannerList.CanExist &&
//CompleteScannerList.WeldComplete &&
&& !CompleteScannerList.ReportComplete)
// 정상 보고 경로:
// - ReportTarget이 설정돼 있고 아직 보고 완료되지 않은 경우
// - 첫번째, 두번째 반복은 SetReportTarget이 호출되지 않으므로 자연스럽게 통과
if (_CellScheduler.HasReport)
return SEQ_SET_PARAMS_REPORT_CELL_RESULT_SEQ;
return SEQ_CHECK_CYCLE_STOP;

View File

@ -760,6 +760,7 @@ namespace ILSOS_CCW_H52A.Sequences
if (laserParam != null) {
// EDC 보내는 용도
Status.GetProcessingWeldingData().LaserPowerCore = laserParam.LaserPower1.ToString();
Status.GetProcessingWeldingData().LaserPowerRing = laserParam.LaserPower2.ToString();
Status.GetProcessingWeldingData().MarkSpeed = laserParam.MarkSpeed.ToString();
Status.GetProcessingWeldingData().FocalLength = (_PLC.ReadDWord((int)eWordInput.PLC_WELDING_FOCAL_LENGTH).data / 1000f).ToString();
Status.GetProcessingWeldingData().WeldingJigNo = _WeldingJigNo.ToString();