281 lines
13 KiB
C#
281 lines
13 KiB
C#
using Aurora.Api;
|
|
using Aurora.Util.HislogPlus;
|
|
using ILSOS_CCW_H52A.Data;
|
|
using ILSOS_CCW_H52A.Sequences;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
|
|
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<eJigNo, JigInfo> 상속으로 물리 지그 번호 직접 접근과
|
|
/// 파이프라인 단계 접근을 동시에 지원하는 셀 스케줄러.
|
|
///
|
|
/// - 물리 지그 접근: scheduler[eJigNo.Jig5]
|
|
/// - 파이프라인 접근: scheduler.Prefetch2, scheduler.Prefetch1, scheduler.Complete, scheduler.ReportSlot
|
|
///
|
|
/// 4단계 파이프라인: Prefetch2 → Prefetch1 → Complete → ReportSlot
|
|
/// Prefetch2 : 셀 정보 수신 완료, 스캐너 리스트 업로드 전
|
|
/// Prefetch1 : 업로드 완료, 실행 중 — EncoderMonitor 감시 + 완료 체크 대상
|
|
/// Complete : 용접 완료, LBAD MES Report 수신 대기 (~100ms)
|
|
/// MesReportReceived 콜백이 CellId 매칭 후 LbadAvg를 채움
|
|
/// ReportSlot : 지그 1회전(~320ms) 경과 → LBAD 데이터 보장, CellTracker 보고 대상
|
|
///
|
|
/// Shift()가 4단계 전체를 전진. GC 할당 없음.
|
|
/// ReportSlot ← Complete ← Prefetch1 ← Prefetch2 ← this[nextJig]
|
|
/// </summary>
|
|
public class CellInfoScheduler : Dictionary<eJigNo, JigInfo>
|
|
{
|
|
private readonly Logger _log = new Logger("CellInfoScheduler");
|
|
public JigInfo Prefetch2 { get; private set; } // 전전 셀: 셀 정보 수신, 업로드 전
|
|
|
|
// volatile: Shift()에서 Prefetch1 = Prefetch2 대입이 release fence 역할.
|
|
// EncoderMonitor 스레드가 Prefetch1을 읽을 때 acquire fence로 JigNo 등 모든 필드 가시성 보장.
|
|
private volatile JigInfo _prefetch1;
|
|
public JigInfo Prefetch1 {
|
|
get => _prefetch1;
|
|
private set => _prefetch1 = value;
|
|
}
|
|
|
|
public JigInfo Complete { get; private set; } // 용접 완료, LBAD 수신 대기
|
|
public JigInfo ReportSlot { get; private set; } // LBAD 보장, CellTracker 보고 대상
|
|
|
|
// ── 재시작 보존용 독립 슬롯 — Reset()에서 건드리지 않음 ──────────────────────
|
|
private readonly JigInfo _pendingPrefetch1 = new JigInfo(); // 정지 직전 Prefetch1 (업로드 완료, 용접 미완)
|
|
private readonly JigInfo _pendingPrefetch2 = new JigInfo(); // 정지 직전 Prefetch2 (업로드 완료, 용접 미완)
|
|
|
|
// 정지 직전 ReportSlot: LBAD 수신 완료 상태로 보고만 남은 셀
|
|
private readonly JigInfo _pendingFromReportSlot = new JigInfo();
|
|
// 정지 직전 Complete: 용접은 완료됐으나 ReportSlot 진입 전 정지 (LBAD 수신 여부 불확실)
|
|
private readonly JigInfo _pendingFromComplete = new JigInfo();
|
|
// 정지 직전 큐 잔류: Dequeue 전 큐에만 있던 셀 (파이프라인 미진입)
|
|
private readonly JigInfo _pendingFromQueue = new JigInfo();
|
|
|
|
// ── 재시작 미완료 보고 접근 ──────────────────────────────────────────────────
|
|
// 우선순위: _pendingFromReportSlot > _pendingFromComplete
|
|
public JigInfo PendingComplete {
|
|
get {
|
|
if (_pendingFromReportSlot.IsCanExist && !_pendingFromReportSlot.ReportComplete)
|
|
return _pendingFromReportSlot;
|
|
if (_pendingFromComplete.IsCanExist && !_pendingFromComplete.ReportComplete)
|
|
return _pendingFromComplete;
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public bool HasPendingComplete =>
|
|
(_pendingFromReportSlot.IsCanExist && !_pendingFromReportSlot.ReportComplete)
|
|
|| (_pendingFromComplete.IsCanExist && !_pendingFromComplete.ReportComplete);
|
|
|
|
// 현재 활성 보고 슬롯 — 재시작 pending 우선, 그 다음 ReportSlot
|
|
public JigInfo ActiveReportSlot {
|
|
get {
|
|
if (_pendingFromReportSlot.IsCanExist && !_pendingFromReportSlot.ReportComplete)
|
|
return _pendingFromReportSlot;
|
|
if (_pendingFromComplete.IsCanExist && !_pendingFromComplete.ReportComplete)
|
|
return _pendingFromComplete;
|
|
return ReportSlot;
|
|
}
|
|
}
|
|
|
|
// 정지 전 미용접 슬롯이 보존돼 있는지 (재시작 후 복원 가능 여부)
|
|
public bool HasPendingPrefetch =>
|
|
!string.IsNullOrEmpty(_pendingPrefetch1.CellId)
|
|
|| !string.IsNullOrEmpty(_pendingPrefetch2.CellId)
|
|
|| !string.IsNullOrEmpty(_pendingFromQueue.CellId);
|
|
|
|
// 보고가 필요한 상태인지 (정상 + 재시작 모두 포함) >> 용접 완, 보고 미완
|
|
// ReportSlot은 Shift()마다 갱신되므로 IsCanExist·WeldComplete 조건을 함께 확인한다.
|
|
public bool HasReport => HasPendingComplete ||
|
|
(ReportSlot != null && ReportSlot.IsCanExist && ReportSlot.WeldComplete && !ReportSlot.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() 전에 호출 — 미완료 보고 및 미용접 슬롯을 보존.
|
|
public void SavePendingComplete()
|
|
{
|
|
// ReportSlot: LBAD 수신 완료 상태, 보고만 남은 셀
|
|
if (ReportSlot != null
|
|
&& ReportSlot.IsCanExist
|
|
&& ReportSlot.WeldComplete
|
|
&& !ReportSlot.ReportComplete)
|
|
_pendingFromReportSlot.CopyFrom(ReportSlot);
|
|
else
|
|
_pendingFromReportSlot.Clear();
|
|
|
|
// Complete: 용접 완료, ReportSlot 진입 전 정지 (LBAD 도착 여부 불확실)
|
|
if (Complete != null
|
|
&& Complete.IsCanExist
|
|
&& Complete.WeldComplete
|
|
&& !Complete.ReportComplete)
|
|
_pendingFromComplete.CopyFrom(Complete);
|
|
else
|
|
_pendingFromComplete.Clear();
|
|
|
|
// 업로드 완료됐으나 로터리 정지로 용접 자체가 안 된 경우 (_pendingPrefetch1/2)
|
|
if (Prefetch1 != null
|
|
&& Prefetch1.IsCanExist
|
|
&& !string.IsNullOrEmpty(Prefetch1.CellId)
|
|
&& !Prefetch1.WeldComplete)
|
|
_pendingPrefetch1.CopyFrom(Prefetch1);
|
|
else
|
|
_pendingPrefetch1.Clear();
|
|
|
|
if (Prefetch2 != null
|
|
&& Prefetch2.IsCanExist
|
|
&& !string.IsNullOrEmpty(Prefetch2.CellId)
|
|
&& !Prefetch2.WeldComplete)
|
|
_pendingPrefetch2.CopyFrom(Prefetch2);
|
|
else
|
|
_pendingPrefetch2.Clear();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 재시작 시 해당 JigNo의 보존된 셀 정보 복원 시도.
|
|
/// 성공 시 해당 슬롯을 즉시 클리어하여 한 번만 사용되도록 보장.
|
|
/// canExist=false 지그가 들어오면 슬롯을 소비(폐기)하여 이후 오염 방지.
|
|
/// 우선순위: _pendingPrefetch1 → _pendingPrefetch2 → _pendingFromQueue
|
|
/// </summary>
|
|
/// <returns>복원 성공 시 true (ReceivedCellInfoQueue TryDequeue 불필요)</returns>
|
|
public bool TryRestoreCell(int jigNo, out string lotId, out string cellId)
|
|
{
|
|
if (_pendingPrefetch1.JigNo == jigNo && !string.IsNullOrEmpty(_pendingPrefetch1.CellId)) {
|
|
lotId = _pendingPrefetch1.LotId;
|
|
cellId = _pendingPrefetch1.CellId;
|
|
_pendingPrefetch1.Clear();
|
|
return true;
|
|
}
|
|
if (_pendingPrefetch2.JigNo == jigNo && !string.IsNullOrEmpty(_pendingPrefetch2.CellId)) {
|
|
lotId = _pendingPrefetch2.LotId;
|
|
cellId = _pendingPrefetch2.CellId;
|
|
_pendingPrefetch2.Clear();
|
|
return true;
|
|
}
|
|
if (_pendingFromQueue.JigNo == jigNo && !string.IsNullOrEmpty(_pendingFromQueue.CellId)) {
|
|
lotId = _pendingFromQueue.LotId;
|
|
cellId = _pendingFromQueue.CellId;
|
|
_pendingFromQueue.Clear();
|
|
return true;
|
|
}
|
|
lotId = null;
|
|
cellId = null;
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 정지 시점에 큐에 잔류한 셀을 JigNo와 함께 보존.
|
|
/// Prefetch2.JigNo 기준으로 다음 지그 번호를 계산하여 호출자가 전달.
|
|
/// DrainCellInfoQueue() 호출 전에 먼저 호출해야 한다.
|
|
/// </summary>
|
|
public void SaveQueueEntry(string lotId, string cellId, int jigNo)
|
|
{
|
|
_pendingFromQueue.Clear();
|
|
_pendingFromQueue.LotId = lotId;
|
|
_pendingFromQueue.CellId = cellId;
|
|
_pendingFromQueue.JigNo = jigNo;
|
|
// IsCanExist는 재시작 후 ReadyScannerList에서 PLC 실측값으로 채워지므로 여기서 설정하지 않음
|
|
}
|
|
|
|
/// <summary>
|
|
/// LBAD MES Report 수신 시 CellId로 파이프라인 슬롯을 찾아 LbadAvg를 저장.
|
|
/// 정상 경로: Complete 단계에서 수신 (~100ms 후).
|
|
/// 폴백: Shift 타이밍이 빠른 경우 ReportSlot에도 확인.
|
|
/// </summary>
|
|
public bool TrySetLbadAvg(string cellId, double avg)
|
|
{
|
|
if (Complete != null && Complete.CellId == cellId) {
|
|
Complete.LbadAvg = avg;
|
|
return true;
|
|
}
|
|
if (ReportSlot != null && ReportSlot.CellId == cellId) {
|
|
ReportSlot.LbadAvg = avg;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// 보고 완료 처리.
|
|
// 정상 경로: ReportSlot.ReportComplete = true
|
|
// 재시작 경로: _pendingFromReportSlot 또는 _pendingFromComplete 클리어
|
|
public void CompleteReport()
|
|
{
|
|
var reported = ActiveReportSlot;
|
|
_log.I($"Report >> CellId({reported?.CellId}), JigNo({reported?.JigNo})");
|
|
|
|
if (_pendingFromReportSlot.IsCanExist && !_pendingFromReportSlot.ReportComplete)
|
|
_pendingFromReportSlot.Clear();
|
|
else if (_pendingFromComplete.IsCanExist && !_pendingFromComplete.ReportComplete)
|
|
_pendingFromComplete.Clear();
|
|
else
|
|
ReportSlot?.SetReportComplete(true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 4단계 파이프라인 전진. GC 할당 없음.
|
|
/// ReportSlot ← Complete ← Prefetch1 ← Prefetch2 ← this[nextJig] (Clear)
|
|
/// 새 Prefetch2 슬롯은 Clear 후 반환 — 호출자가 셀 정보를 채워야 한다.
|
|
/// </summary>
|
|
public void Shift(eJigNo nextJig)
|
|
{
|
|
ReportSlot = Complete; // Complete → ReportSlot
|
|
Complete = Prefetch1; // Prefetch1 → Complete
|
|
Prefetch1 = Prefetch2; // Prefetch2 → Prefetch1 (volatile write: release fence)
|
|
Prefetch2 = base[nextJig];
|
|
Prefetch2.Clear();
|
|
}
|
|
// AutoRun에서 사용되는 자료형으로 추가 (편의 인덱서 사용)
|
|
public void Shift(int nextJig)
|
|
{
|
|
ReportSlot = Complete; // Complete → ReportSlot
|
|
Complete = Prefetch1; // Prefetch1 → Complete
|
|
Prefetch1 = Prefetch2; // Prefetch2 → Prefetch1 (volatile write: release fence)
|
|
Prefetch2 = this[nextJig];
|
|
Prefetch2.Clear();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 전체 초기화 (AutoRun 시작 시 호출).
|
|
/// SavePendingComplete() 호출 후 이 메서드를 호출할 것.
|
|
/// pending 슬롯들은 재시작 복원을 위해 건드리지 않음.
|
|
/// </summary>
|
|
public void Reset()
|
|
{
|
|
foreach (var info in Values)
|
|
info.Clear();
|
|
|
|
Prefetch2 = null;
|
|
Prefetch1 = null;
|
|
Complete = null;
|
|
ReportSlot = null;
|
|
// _pendingFromReportSlot, _pendingFromComplete, _pendingPrefetch1/2:
|
|
// SavePendingComplete()가 복사해 둔 데이터를 유지
|
|
}
|
|
|
|
/// <summary>
|
|
/// 스케줄러 자체는 Clear 할 수 없도록 함
|
|
/// </summary>
|
|
public new void Clear()
|
|
{
|
|
Reset();
|
|
}
|
|
}
|
|
}
|