셀 파이프라인 3단계 구조화 및 보고 데이터 소스 명확화

[CellInfoScheduler]                                                                                                                                                                                                                                                              - ReportTarget 제거 → Complete 슬롯 추가
  - Shift()가 Complete ← Prefetch1 ← Prefetch2 3단계 자동 전진                                                                                                                                                                                                                     - Prefetch1 volatile 처리로 EncoderMonitor 스레드 가시성 보장
  - HasReport에 IsCanExist·WeldComplete 조건 추가

  [SeqAutoRunCCWH52A]
  - ReportCellResult: LotId/CellId/JigNo/WeldingSpanTime을
    ActiveReportSlot에서 직접 읽도록 변경 (wd 경유 제거)
  - ReportCellResult: 레시피·레이저 파라미터를
    Prefetch2.InnerCellInfo에서 읽도록 변경
  - GetUploadWeldingList_SeqResult: 업로드 성공 후
    InnerCellInfo 및 Status 한 번에 갱신 (ReadyScannerList 중복 제거)
  - UpdateProductCount/UpdateProductInfoForView 메서드 인라인화
  - _TodayProduct/_TotalProduct 멤버변수 제거
  - CheckCurrentEncoderAngle → CheckInsideWeldPosition 리네임 및 정리
  - EncoderMonitor: JigNo > 0 방어 가드 추가
  - _RecipeNo를 InitSequenceVariable에서 한 번만 설정
This commit is contained in:
최이근 2026-03-10 00:40:19 +09:00
parent cb935e251e
commit 45c1de6cc8
4 changed files with 142 additions and 1391 deletions

View File

@ -21,15 +21,15 @@ namespace ILSOS_CCW_H52A.Data
/// 파이프라인 단계 접근을 동시에 지원하는 셀 스케줄러. /// 파이프라인 단계 접근을 동시에 지원하는 셀 스케줄러.
/// ///
/// - 물리 지그 접근: scheduler[eJigNo.Jig5] /// - 물리 지그 접근: scheduler[eJigNo.Jig5]
/// - 파이프라인 접근: scheduler.Prefetch1, scheduler.Prefetch2 /// - 파이프라인 접근: scheduler.Prefetch2, scheduler.Prefetch1, scheduler.Complete
/// ///
/// 3단계 파이프라인: Prefetch2 → Prefetch1 → ReportTarget /// 3단계 파이프라인: Prefetch2 → Prefetch1 → Complete
/// Prefetch2 : 셀 정보 수신 완료, 스캐너 리스트 업로드 전 /// Prefetch2 : 셀 정보 수신 완료, 스캐너 리스트 업로드 전
/// Prefetch1 : 업로드 완료, 실행 중 — EncoderMonitor 감시 + 완료 체크 대상 /// Prefetch1 : 업로드 완료, 실행 중 — EncoderMonitor 감시 + 완료 체크 대상
/// ReportTarget: 완료, 보고 대기 /// Complete : 용접 완료, 보고 대기
/// ///
/// Shift()는 Prefetch2→Prefetch1만 전진. GC 할당 없음. /// Shift()가 3단계 전체를 전진. GC 할당 없음.
/// Prefetch1 ← Prefetch2 ← this[nextJig] /// Complete ← Prefetch1 ← Prefetch2 ← this[nextJig]
/// </summary> /// </summary>
public class CellInfoScheduler : Dictionary<eJigNo, JigInfo> public class CellInfoScheduler : Dictionary<eJigNo, JigInfo>
{ {
@ -44,8 +44,7 @@ namespace ILSOS_CCW_H52A.Data
private set => _prefetch1 = value; private set => _prefetch1 = value;
} }
/// 현재 보고 대상 JigInfo (CheckScannerListComplete에서 지정, 보고 완료 후 null) public JigInfo Complete { get; private set; } // 전 셀: 용접 완료, 보고 대기
public JigInfo ReportTarget { get; set; }
// 재시작 보존용 독립 슬롯 — Reset()에서 건드리지 않음 // 재시작 보존용 독립 슬롯 — Reset()에서 건드리지 않음
// 재시작 시, List를 최초 리스트 2번을 생성하는 로직 때문에.. 필요함 // 재시작 시, List를 최초 리스트 2번을 생성하는 로직 때문에.. 필요함
@ -60,7 +59,7 @@ namespace ILSOS_CCW_H52A.Data
public bool HasPendingComplete => _pendingSlot.IsCanExist && !_pendingSlot.ReportComplete; public bool HasPendingComplete => _pendingSlot.IsCanExist && !_pendingSlot.ReportComplete;
// 현재 활성 보고 슬롯 — 재시작 pending 우선 // 현재 활성 보고 슬롯 — 재시작 pending 우선
public JigInfo ActiveReportSlot => HasPendingComplete ? _pendingSlot : ReportTarget; public JigInfo ActiveReportSlot => HasPendingComplete ? _pendingSlot : Complete;
// 정지 전 미용접 슬롯이 보존돼 있는지 (재시작 후 복원 가능 여부) // 정지 전 미용접 슬롯이 보존돼 있는지 (재시작 후 복원 가능 여부)
public bool HasPendingPrefetch => public bool HasPendingPrefetch =>
@ -68,7 +67,9 @@ namespace ILSOS_CCW_H52A.Data
|| !string.IsNullOrEmpty(_pendingPrefetch2.CellId); || !string.IsNullOrEmpty(_pendingPrefetch2.CellId);
// 보고가 필요한 상태인지 (정상 + 재시작 모두 포함) >> 용접 완, 보고 미완 // 보고가 필요한 상태인지 (정상 + 재시작 모두 포함) >> 용접 완, 보고 미완
public bool HasReport => HasPendingComplete || (ReportTarget != null && !ReportTarget.ReportComplete); // Complete는 Shift()마다 갱신되므로 IsCanExist·WeldComplete 조건을 함께 확인한다.
public bool HasReport => HasPendingComplete ||
(Complete != null && Complete.IsCanExist && Complete.WeldComplete && !Complete.ReportComplete);
public CellInfoScheduler() public CellInfoScheduler()
{ {
@ -85,11 +86,11 @@ namespace ILSOS_CCW_H52A.Data
public void SavePendingComplete() public void SavePendingComplete()
{ {
// 용접 완료됐으나 보고 전 정지된 경우 (_pendingSlot) // 용접 완료됐으나 보고 전 정지된 경우 (_pendingSlot)
if (ReportTarget != null if (Complete != null
&& ReportTarget.IsCanExist && Complete.IsCanExist
&& ReportTarget.WeldComplete && Complete.WeldComplete
&& !ReportTarget.ReportComplete) && !Complete.ReportComplete)
_pendingSlot.CopyFrom(ReportTarget); _pendingSlot.CopyFrom(Complete);
// 업로드 완료됐으나 로터리 정지로 용접 자체가 안 된 경우 (_pendingPrefetch1/2) // 업로드 완료됐으나 로터리 정지로 용접 자체가 안 된 경우 (_pendingPrefetch1/2)
// (연속셀이 들어왔을때, 중간에 멈춘 경우) // (연속셀이 들어왔을때, 중간에 멈춘 경우)
@ -135,31 +136,33 @@ namespace ILSOS_CCW_H52A.Data
return false; return false;
} }
// 보고 완료 처리 — ReportTarget 마킹 및 _pendingSlot 클리어. // 보고 완료 처리 — Complete 마킹 및 _pendingSlot 클리어.
// GetReportCellResult_SeqResult 성공 시 호출. // GetReportCellResult_SeqResult 성공 시 호출.
public void CompleteReport() public void CompleteReport()
{ {
_log.I($"Report >> CellId({ReportTarget.CellId}), JigNo({ReportTarget.JigNo})"); var reported = ActiveReportSlot;
ReportTarget?.SetReportComplete(true); _log.I($"Report >> CellId({reported?.CellId}), JigNo({reported?.JigNo})");
ReportTarget = null; Complete?.SetReportComplete(true); // 정상 보고 마킹 (pending 경로면 no-op)
_pendingSlot.Clear(); _pendingSlot.Clear(); // 재시작 보존 슬롯 항상 클리어
} }
/// <summary> /// <summary>
/// Prefetch2→Prefetch1 한 칸 전진. GC 할당 없음. /// 3단계 파이프라인 전진. GC 할당 없음.
/// Complete ← Prefetch1 ← Prefetch2 ← this[nextJig] (Clear)
/// 새 Prefetch2 슬롯은 Clear 후 반환 — 호출자가 셀 정보를 채워야 한다. /// 새 Prefetch2 슬롯은 Clear 후 반환 — 호출자가 셀 정보를 채워야 한다.
/// </summary> /// </summary>
/// <param name="nextJig">새 Prefetch2로 지정할 물리 지그 (eJigNo)</param>
public void Shift(eJigNo nextJig) public void Shift(eJigNo nextJig)
{ {
Prefetch1 = Prefetch2; Complete = Prefetch1; // Prefetch1 → Complete
Prefetch1 = Prefetch2; // Prefetch2 → Prefetch1 (volatile write: release fence)
Prefetch2 = base[nextJig]; Prefetch2 = base[nextJig];
Prefetch2.Clear(); Prefetch2.Clear();
} }
// AutoRun에서 사용되는 자료형으로 추가 (편의 인덱서 사용) // AutoRun에서 사용되는 자료형으로 추가 (편의 인덱서 사용)
public void Shift(int nextJig) public void Shift(int nextJig)
{ {
Prefetch1 = Prefetch2; Complete = Prefetch1; // Prefetch1 → Complete
Prefetch1 = Prefetch2; // Prefetch2 → Prefetch1 (volatile write: release fence)
Prefetch2 = this[nextJig]; Prefetch2 = this[nextJig];
Prefetch2.Clear(); Prefetch2.Clear();
} }
@ -174,9 +177,9 @@ namespace ILSOS_CCW_H52A.Data
foreach (var info in Values) foreach (var info in Values)
info.Clear(); info.Clear();
Prefetch2 = null; Prefetch2 = null;
Prefetch1 = null; Prefetch1 = null;
ReportTarget = null; Complete = null;
// _pendingSlot, _pendingPrefetch1/2: SavePendingComplete()가 복사해 둔 데이터를 유지 // _pendingSlot, _pendingPrefetch1/2: SavePendingComplete()가 복사해 둔 데이터를 유지
} }

View File

@ -111,7 +111,6 @@
<Compile Include="Sequences\ProcessObjects\ProcessManager.cs" /> <Compile Include="Sequences\ProcessObjects\ProcessManager.cs" />
<Compile Include="Sequences\ProcessObjects\ReceivedCellInfo.cs" /> <Compile Include="Sequences\ProcessObjects\ReceivedCellInfo.cs" />
<Compile Include="Sequences\SeqAutoRunCCWH52A.cs" /> <Compile Include="Sequences\SeqAutoRunCCWH52A.cs" />
<Compile Include="Sequences\SeqAutoRunRMOTF.cs" />
<Compile Include="Sequences\SequenceContextEx.cs" /> <Compile Include="Sequences\SequenceContextEx.cs" />
<Compile Include="Views\Main\UCMainAutoRun.xaml.cs"> <Compile Include="Views\Main\UCMainAutoRun.xaml.cs">
<DependentUpon>UCMainAutoRun.xaml</DependentUpon> <DependentUpon>UCMainAutoRun.xaml</DependentUpon>

View File

@ -21,10 +21,12 @@ using ILSOS_CCW_H52A.RMS;
using ILSOS_CCW_H52A.Sequences.ProcessObjects; using ILSOS_CCW_H52A.Sequences.ProcessObjects;
using Microsoft.Win32; using Microsoft.Win32;
using OpenCvSharp; using OpenCvSharp;
using OpenCvSharp.Aruco;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reactive.Subjects;
using System.Reflection; using System.Reflection;
using System.Threading; using System.Threading;
using System.Windows; using System.Windows;
@ -107,6 +109,7 @@ namespace ILSOS_CCW_H52A.Sequences
private SystemParameter _SystemParameter; private SystemParameter _SystemParameter;
private ScannerCanvasUserCtrl _ScannerCanvasUserCtrl; //스캐너 동작관련 객체 선언 private ScannerCanvasUserCtrl _ScannerCanvasUserCtrl; //스캐너 동작관련 객체 선언
private int _RecipeNo;
private int _ModelNo; private int _ModelNo;
private IRecipeManager _RecipeManager; private IRecipeManager _RecipeManager;
@ -117,9 +120,6 @@ namespace ILSOS_CCW_H52A.Sequences
private SeqAutoRunCCWH52A_InitParam _InitParam; private SeqAutoRunCCWH52A_InitParam _InitParam;
private uint _TodayProduct;
private uint _TotalProduct;
private double _LaserPowerByLBAD; private double _LaserPowerByLBAD;
private bool _ModulationMode; private bool _ModulationMode;
@ -141,7 +141,6 @@ namespace ILSOS_CCW_H52A.Sequences
private double _WeldingStartAngle; private double _WeldingStartAngle;
private bool isTheFirstListExecute; private bool isTheFirstListExecute;
private int _RecipeNo;
private bool UsePlcShotThemAll { get; set; } = false; private bool UsePlcShotThemAll { get; set; } = false;
private bool UsePlcPositionCompensation { get; set; } = false; private bool UsePlcPositionCompensation { get; set; } = false;
@ -283,8 +282,6 @@ namespace ILSOS_CCW_H52A.Sequences
private bool InitSequenceVariable(SequenceContext context) private bool InitSequenceVariable(SequenceContext context)
{ {
_AutoRunStartTime = DateTime.Now; _AutoRunStartTime = DateTime.Now;
_TodayProduct = _SystemParameter.TodayProductCount;
_TotalProduct = _SystemParameter.TotalProductCount;
var ActivateRecipeName = _RecipeManager.ActivatedRecipe(); var ActivateRecipeName = _RecipeManager.ActivatedRecipe();
Log.I($"Current Activate Recipe, ID : {ActivateRecipeName.Id}, Name : {ActivateRecipeName.Name}"); Log.I($"Current Activate Recipe, ID : {ActivateRecipeName.Id}, Name : {ActivateRecipeName.Name}");
@ -294,8 +291,9 @@ namespace ILSOS_CCW_H52A.Sequences
_WeldingScannerListNo = eListNo.LIST1; _WeldingScannerListNo = eListNo.LIST1;
_RecipeNo = ActivateRecipeName.Id;
var currentModelName = context.Recipes.ActivatedModelName(); var currentModelName = context.Recipes.ActivatedModelName();
var modelItem = context.GetModel<EquipmentSpecificModel_CCW_H52A>(currentModelName, CommonModelIds.EquipmentSpecificModel); var modelItem = context.Models.ModelItem(currentModelName, CommonModelIds.EquipmentSpecificModel) as EquipmentSpecificModel_CCW_H52A;
_ModelNo = modelItem.Model_No; _ModelNo = modelItem.Model_No;
return true; return true;
@ -445,7 +443,7 @@ namespace ILSOS_CCW_H52A.Sequences
// JigInfo 풀 스케줄러 초기화 (전전/전/대기/완료 셀 파이프라인) // JigInfo 풀 스케줄러 초기화 (전전/전/대기/완료 셀 파이프라인)
// ── 재시작 전 미완료 보고 보존 ─────────────────────────────────────────────── // ── 재시작 전 미완료 보고 보존 ───────────────────────────────────────────────
// ReportTarget이 있는 상태로 정지가 들어오면 Reset() 후 슬롯이 Clear()되어 보고 불가. // Complete가 있는 상태로 정지가 들어오면 Reset() 후 슬롯이 Clear()되어 보고 불가.
// SavePendingComplete()로 _pendingSlot에 복사한 뒤 Reset()을 호출한다. // SavePendingComplete()로 _pendingSlot에 복사한 뒤 Reset()을 호출한다.
_CellScheduler.SavePendingComplete(); _CellScheduler.SavePendingComplete();
@ -563,7 +561,7 @@ namespace ILSOS_CCW_H52A.Sequences
Log.I($"[Warmup] ScannerList {_WeldingCount}: pipeline not ready, skip complete check"); Log.I($"[Warmup] ScannerList {_WeldingCount}: pipeline not ready, skip complete check");
return SequenceAction.Ok(); return SequenceAction.Ok();
} }
if (!CheckCurrentEncoderAngle(jigComplete.StartAngle)) { if (!CheckInsideWeldPosition(jigComplete.StartAngle)) {
// IsCanExist=false(공 지그)는 리스트 실행은 하지만 용접이 없으므로 // IsCanExist=false(공 지그)는 리스트 실행은 하지만 용접이 없으므로
// 엔코더 범위를 벗어나도 에러가 아님 // 엔코더 범위를 벗어나도 에러가 아님
if (jigComplete.IsCanExist) { if (jigComplete.IsCanExist) {
@ -583,9 +581,7 @@ namespace ILSOS_CCW_H52A.Sequences
TimeSpan weldInterval = jigComplete.WeldEndTime - _prevWeldEndTime; TimeSpan weldInterval = jigComplete.WeldEndTime - _prevWeldEndTime;
_prevWeldEndTime = jigComplete.WeldEndTime; _prevWeldEndTime = jigComplete.WeldEndTime;
// 보고 대상 슬롯 지정 (스케줄러가 관리) // 보고 대상 지정 불필요 — Shift()가 Prefetch1 → Complete로 자동 전진
_CellScheduler.ReportTarget = jigComplete;
Log.I(string.Format("JigNo : {0}, Weld start : {1}, Weld End : {2}, WeldTime : {3}, interval : {4:F3}", Log.I(string.Format("JigNo : {0}, Weld start : {1}, Weld End : {2}, WeldTime : {3}, interval : {4:F3}",
jigComplete.JigNo, jigComplete.JigNo,
jigComplete.WeldStartTime.ToString("HH:mm:ss.fff"), jigComplete.WeldStartTime.ToString("HH:mm:ss.fff"),
@ -597,8 +593,36 @@ namespace ILSOS_CCW_H52A.Sequences
_PLC.WriteBit((int)eBitOutput.PC_WELD_COMPLETE_1 + jigComplete.JigNo - 1, true); _PLC.WriteBit((int)eBitOutput.PC_WELD_COMPLETE_1 + jigComplete.JigNo - 1, true);
Log.I(string.Format($"PC_WELD_COMPLETE_{jigComplete.JigNo} -> true)")); Log.I(string.Format($"PC_WELD_COMPLETE_{jigComplete.JigNo} -> true)"));
} }
UpdateProductCount(context);
UpdateProductInfoForView(); if (_SystemParameter.LastResetTime < DateTime.Now.Date) {
_SystemParameter.LastResetTime = DateTime.Now.Date;
Log.I($"Today Count Reset. {_SystemParameter.TodayProductCount} -> 0");
_SystemParameter.TodayProductCount = 0;
}
if (_SystemParameter.TodayProductCount < uint.MaxValue) {
_SystemParameter.TodayProductCount++;
}
if (_SystemParameter.TotalProductCount < uint.MaxValue) {
_SystemParameter.TotalProductCount++;
}
Status.GetProcessingWeldingData().TotalProduct = _SystemParameter.TotalProductCount;
Status.GetProcessingWeldingData().TodayProduct = _SystemParameter.TodayProductCount;
Status.GetProcessingWeldingData().AutoRunStartTime = _AutoRunStartTime;
Status.GetProcessingWeldingData().RecipeNo = _RecipeNo;
Status.GetProcessingWeldingData().LaserPowerByLBAD = _LaserPowerByLBAD.ToString();
// 보고 데이터는 스케줄러의 ActiveReportSlot(Complete 또는 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();
}
SaveCellAPD(); SaveCellAPD();
} }
@ -633,25 +657,37 @@ namespace ILSOS_CCW_H52A.Sequences
return SequenceAction.Ok(); return SequenceAction.Ok();
} }
public bool CheckCurrentEncoderAngle(double startAngle) /// <summary>
/// startAngle startAngle+45
/// │←── OK 구간 ────►│
/// ────┼─────────────────┼──────────────► 로터리 각도
/// 리스트 여기까지
/// 시작 와야 완료 체크 가능
/// 판정 로직:
/// currentAngle < startAngle → Too early (아직 리스트 실행 중, 완료 체크 너무 이름)
/// startAngle ≤ current < start+45 → OK (정상 완료 구간)
/// currentAngle ≥ startAngle + 45 → Too late (너무 늦음, 타임오버 에러)
/// </summary>
/// <param name="startAngle"> 현재 지그의 시작 각도 </param>
/// <returns> 지나갔으면 True </returns>
public bool CheckInsideWeldPosition(double startAngle)
{ {
double encoderX, currentAngle; double encoderX, currentAngle;
_Scanner.GetEncoder(out encoderX, out currentAngle, true); _Scanner.GetEncoder(out encoderX, out currentAngle, true);
if (0 < startAngle && currentAngle < 0) { // Encoder 반전되는 순간이며, 이 때만 인터락 무시
// Encoder 반전되는 순간이며, 이 때만 인터락 무시 if (0 < startAngle && currentAngle < 0)
return false;
if (startAngle <= currentAngle && currentAngle < startAngle + 45) {
Log.I(string.Format("Current encoder check OK, startAngle : {0}, currentAngle : {1})", startAngle, currentAngle));
return true;
} }
else if (startAngle <= currentAngle) {
// 다음 리스트 구간이 끝나기 전까지는 완료가 되어야 한다 if (currentAngle < startAngle)
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 early), startAngle : {0}, currentAngle : {1})", startAngle, currentAngle)); Log.W(string.Format("Current encoder check NG(Too early), startAngle : {0}, currentAngle : {1})", startAngle, currentAngle));
else
Log.W(string.Format("Current encoder check NG(Too late), startAngle : {0}, currentAngle : {1})", startAngle, currentAngle));
return false; return false;
} }
@ -717,12 +753,11 @@ namespace ILSOS_CCW_H52A.Sequences
#warning OC2 ACW 기준 >> Cell Tracker는 어느 시점에 데이터는 주는지 모르겠지만, '전전전 셀' 쯤에서 이미 건네받음! 다른 프로젝트에서 확인 필요함 #warning OC2 ACW 기준 >> Cell Tracker는 어느 시점에 데이터는 주는지 모르겠지만, '전전전 셀' 쯤에서 이미 건네받음! 다른 프로젝트에서 확인 필요함
// 복원 우선: 정지 전 업로드됐으나 로터리 감속·정지로 용접 미완된 셀 정보 복원. // 복원 우선: 정지 전 업로드됐으나 로터리 감속·정지로 용접 미완된 셀 정보 복원.
// canExist=false 지그도 TryRestoreCell을 호출하여 슬롯 소비(폐기) — 이후 오염 방지. // canExist=false 지그도 TryRestoreCell을 호출하여 슬롯 소비(폐기) — 이후 오염 방지.
(string LotId, string CellId) recvInfo = (string.Empty, string.Empty);
if (_CellScheduler.TryRestoreCell(_WeldingJigNo, out var rLot, out var rCell)) { if (_CellScheduler.TryRestoreCell(_WeldingJigNo, out var rLot, out var rCell)) {
if (canExist) { if (canExist) {
_CellScheduler.Prefetch2.LotId = rLot; recvInfo.LotId = rLot;
_CellScheduler.Prefetch2.CellId = rCell; recvInfo.CellId = rCell;
Status.GetProcessingWeldingData().WorkLotId = rLot;
Status.GetProcessingWeldingData().WorkCellId = rCell;
Log.I($"[PendingRestore] Jig{_WeldingJigNo} 셀 복원 >> CellId({rCell})"); Log.I($"[PendingRestore] Jig{_WeldingJigNo} 셀 복원 >> CellId({rCell})");
} }
else { else {
@ -730,25 +765,26 @@ namespace ILSOS_CCW_H52A.Sequences
} }
} }
else if (canExist && ProcessManager.Instance.ReceivedCellInfoQueue.TryDequeue(out var cell)) { else if (canExist && ProcessManager.Instance.ReceivedCellInfoQueue.TryDequeue(out var cell)) {
_CellScheduler.Prefetch2.LotId = cell.LotId; recvInfo.LotId = cell.LotId;
_CellScheduler.Prefetch2.CellId = cell.CellId; recvInfo.CellId = cell.CellId;
Status.GetProcessingWeldingData().WorkLotId = cell.LotId;
Status.GetProcessingWeldingData().WorkCellId = cell.CellId;
} else { } else {
_CellTracker.getDummyData(out string lotId, out string cellId); _CellTracker.getDummyData(out string lotId, out string cellId);
_CellScheduler.Prefetch2.LotId = lotId; recvInfo.LotId = lotId;
_CellScheduler.Prefetch2.CellId = cellId; recvInfo.CellId = cellId;
Status.GetProcessingWeldingData().WorkLotId = lotId;
Status.GetProcessingWeldingData().WorkCellId = cellId;
} }
_CellScheduler.Prefetch2.LotId = recvInfo.LotId;
_CellScheduler.Prefetch2.CellId = recvInfo.CellId;
Status.GetProcessingWeldingData().WorkLotId = recvInfo.LotId;
Status.GetProcessingWeldingData().WorkCellId = recvInfo.CellId;
if (_CellScheduler.Prefetch2 != null) if (_CellScheduler.Prefetch2 != null)
Log.I($"상태 시프트(Rotate) >> 용접 전전셀({_CellScheduler.Prefetch2.CellId}) [CanExist({_CellScheduler.Prefetch2.IsCanExist})] [JigNo({_CellScheduler.Prefetch2.JigNo})] " + Log.I($"Prefetch2 >> CellId({_CellScheduler.Prefetch2.CellId}) [CanExist({_CellScheduler.Prefetch2.IsCanExist})] [JigNo({_CellScheduler.Prefetch2.JigNo})] " +
$"[ListNo({_CellScheduler.Prefetch2.ListNo})] [WeldingCount({_CellScheduler.Prefetch2.WeldingCount})]" + $"[ListNo({_CellScheduler.Prefetch2.ListNo})] [WeldingCount({_CellScheduler.Prefetch2.WeldingCount})]" +
$"[StartAngle(Y)({_CellScheduler.Prefetch2.StartAngle})]"); $"[StartAngle(Y)({_CellScheduler.Prefetch2.StartAngle})]");
if(_CellScheduler.Prefetch1 != null) if(_CellScheduler.Prefetch1 != null)
Log.I($"이번 용접대상 >> 용접 전셀({_CellScheduler.Prefetch1.CellId}) [CanExist({_CellScheduler.Prefetch1.IsCanExist})] [JigNo({_CellScheduler.Prefetch1.JigNo})] " + Log.I($"Prefetch1 >> CellId({_CellScheduler.Prefetch1.CellId}) [CanExist({_CellScheduler.Prefetch1.IsCanExist})] [JigNo({_CellScheduler.Prefetch1.JigNo})] " +
$"[ListNo({_CellScheduler.Prefetch1.ListNo})] [WeldingCount({_CellScheduler.Prefetch1.WeldingCount})]" + $"[ListNo({_CellScheduler.Prefetch1.ListNo})] [WeldingCount({_CellScheduler.Prefetch1.WeldingCount})]" +
$"[StartAngle(Y)({_CellScheduler.Prefetch1.StartAngle})]"); $"[StartAngle(Y)({_CellScheduler.Prefetch1.StartAngle})]");
@ -764,9 +800,6 @@ namespace ILSOS_CCW_H52A.Sequences
} }
#endregion #endregion
_RecipeNo = _RecipeManager.ActivatedRecipe().Id;
SetWeldingData();
return SEQ_SET_PARAMS_UPLOAD_WELDING_LIST_SEQ; return SEQ_SET_PARAMS_UPLOAD_WELDING_LIST_SEQ;
} }
else { else {
@ -786,20 +819,6 @@ namespace ILSOS_CCW_H52A.Sequences
return null; return null;
} }
private void SetWeldingData()
{
ScannerParam laserParam = null;
laserParam = GetSelectedDrawObjectData(_SelectedDrawObjectList[0]);
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();
}
}
public void WaitForEncoderNextRotarySection(int weldingCount, int rotaryJigCount, double rotaryAngleOffset, double encoderResetOffset, out double sectionStartAngle) public void WaitForEncoderNextRotarySection(int weldingCount, int rotaryJigCount, double rotaryAngleOffset, double encoderResetOffset, out double sectionStartAngle)
{ {
@ -833,28 +852,7 @@ namespace ILSOS_CCW_H52A.Sequences
public bool GetRotaryCanExist(int jigNo) public bool GetRotaryCanExist(int jigNo)
{ {
#region 지그 16개 요청기록 로그 남기기, 빠져나간 캔에 대한 Complete Bit 초기화
//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, ";
// 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)"));
// }
// }
//}
//UtilUnit.logUnit.SetLog(LogUnit.eLogType.DEBUG, MethodBase.GetCurrentMethod().Name, string.Format("PLC -> PC 용접 Cmd : ({0})", result));
#endregion
if (jigNo <= _RotaryJigCount) if (jigNo <= _RotaryJigCount)
//return m_rscPlc.GetDIStatus(string.Format("PLC_USE_WELD_JIG_{0}", jigNo));
return _PLC.ReadBit((int)eBitInput.PLC_USE_WELD_JIG_1 + (jigNo - 1)).data; return _PLC.ReadBit((int)eBitInput.PLC_USE_WELD_JIG_1 + (jigNo - 1)).data;
else else
return false; return false;
@ -884,14 +882,22 @@ namespace ILSOS_CCW_H52A.Sequences
return SequenceAction.ForceStopByAlarm(AlarmCode.ALARM_SEQUENCE_UploadWeldingList_ResultFailed); return SequenceAction.ForceStopByAlarm(AlarmCode.ALARM_SEQUENCE_UploadWeldingList_ResultFailed);
//return null; //return null;
} }
// 입력한 Scanner List를 완료대기열에 추가 ScannerParam laserParam = null;
// Won : GetProcessingWeldingData().WorkLotId 는 CellTrakcer로 부터 받은 상태의 LotId laserParam = GetSelectedDrawObjectData(_SelectedDrawObjectList[0]);
// GetProcessingWeldingData().LotId 는 AutorunView에 표기할 용접이 완료 된 LotID
string LotId = Status.GetProcessingWeldingData().WorkLotId; // CellTracker로 부터 WorkLotID, WorkCellID 받는 구문 추가 후 해당 데이터로 입력 필요
string CellId = Status.GetProcessingWeldingData().WorkCellId;
_CellScheduler.Prefetch2.LotId = LotId; _CellScheduler.Prefetch2.InnerCellInfo.RecipeNo = _RecipeNo;
_CellScheduler.Prefetch2.CellId = CellId; _CellScheduler.Prefetch2.InnerCellInfo.LaserPowerCore = laserParam.LaserPower1.ToString();
_CellScheduler.Prefetch2.InnerCellInfo.LaserPowerRing = laserParam.LaserPower2.ToString();
_CellScheduler.Prefetch2.InnerCellInfo.MarkSpeed = laserParam.MarkSpeed.ToString();
_CellScheduler.Prefetch2.InnerCellInfo.FocalLength = (_PLC.ReadDWord((int)eWordInput.PLC_WELDING_FOCAL_LENGTH).data / 1000f).ToString();
_CellScheduler.Prefetch2.InnerCellInfo.WeldingJigNo = _WeldingJigNo.ToString();
// 업로드 성공 확인 후 뷰/EDC용 Status 갱신 (ReadyScannerList에서 중복 갱신 제거)
Status.GetProcessingWeldingData().LaserPowerCore = laserParam.LaserPower1.ToString();
Status.GetProcessingWeldingData().LaserPowerRing = laserParam.LaserPower2.ToString();
Status.GetProcessingWeldingData().MarkSpeed = laserParam.MarkSpeed.ToString();
Status.GetProcessingWeldingData().FocalLength = _CellScheduler.Prefetch2.InnerCellInfo.FocalLength;
Status.GetProcessingWeldingData().WeldingJigNo = _WeldingJigNo.ToString();
Log.FuncI("CellTracker Load"); Log.FuncI("CellTracker Load");
return SequenceAction.Ok(); return SequenceAction.Ok();
} }
@ -948,7 +954,6 @@ namespace ILSOS_CCW_H52A.Sequences
private SequenceActionResult EncoderMonitor(SequenceContext context, string id, SequenceActionResult param) private SequenceActionResult EncoderMonitor(SequenceContext context, string id, SequenceActionResult param)
{ {
double send_time_offset = 0.1; double send_time_offset = 0.1;
string LotID = "", CellID = "";
while (!context.Stopped) { while (!context.Stopped) {
double encoderX, currentAngle; double encoderX, currentAngle;
@ -968,15 +973,13 @@ namespace ILSOS_CCW_H52A.Sequences
Log.I($"Fork Before Min Rotate Encoder X : ({encoderX}), Encoder Y : ({currentAngle}), PLC Rotary : ({CurrentPosPLC}), List {executeTarget.ListNo}, JigNo : ({executeTarget.JigNo})"); 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 (!_SystemParameter.BypassModeLBAD) {
if (!executeTarget.CheckSendCellinfo) { if (!executeTarget.CheckSendCellinfo) {
LotID = executeTarget.LotId;
CellID = executeTarget.CellId;
if (currentAngle > executeTarget.StartAngle) { if (currentAngle > executeTarget.StartAngle) {
executeTarget.CheckSendCellinfo = _StreamLightLBAD.SendCellInfo(LotID, CellID, _RecipeNo, _ModelNo, executeTarget.JigNo); executeTarget.CheckSendCellinfo = _StreamLightLBAD.SendCellInfo(executeTarget.LotId, executeTarget.CellId, _RecipeNo, _ModelNo, executeTarget.JigNo);
if (!executeTarget.CheckSendCellinfo) if (!executeTarget.CheckSendCellinfo)
Log.I($"[LBAD Interface]SendCellInfo null, LotID [{LotID}], CellID [{CellID}], JigNo [{executeTarget.JigNo}]"); Log.I($"[LBAD Interface]SendCellInfo null, LotID [{executeTarget.LotId}], CellID [{executeTarget.CellId}], JigNo [{executeTarget.JigNo}]");
else else
Log.I($"[StreamLight]SendCellInfo LotID [{LotID}], CellID [{CellID}], JigNo [{executeTarget.JigNo}]"); Log.I($"[StreamLight]SendCellInfo LotID [{executeTarget.LotId}], CellID [{executeTarget.CellId}], JigNo [{executeTarget.JigNo}]");
} }
} }
} }
@ -1015,46 +1018,6 @@ namespace ILSOS_CCW_H52A.Sequences
return SequenceAction.Ok(); return SequenceAction.Ok();
} }
private void UpdateProductCount(SequenceContext context)
{
if (_SystemParameter.LastResetTime < DateTime.Now.Date) {
_SystemParameter.LastResetTime = DateTime.Now.Date;
Log.I($"Today Count Reset. {_SystemParameter.TodayProductCount} -> 0");
_SystemParameter.TodayProductCount = 0;
}
_TodayProduct = _SystemParameter.TodayProductCount;
_TotalProduct = _SystemParameter.TotalProductCount;
if (_TodayProduct < uint.MaxValue) {
_TodayProduct++;
}
if (_TotalProduct < uint.MaxValue) {
_TotalProduct++;
}
_SystemParameter.TodayProductCount = _TodayProduct;
_SystemParameter.TotalProductCount = _TotalProduct;
}
private void UpdateProductInfoForView()
{
Status.GetProcessingWeldingData().TotalProduct = _TotalProduct;
Status.GetProcessingWeldingData().TodayProduct = _TodayProduct;
Status.GetProcessingWeldingData().AutoRunStartTime = _AutoRunStartTime;
Status.GetProcessingWeldingData().RecipeNo = _RecipeNo;
Status.GetProcessingWeldingData().LaserPowerByLBAD = _LaserPowerByLBAD.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 ReportCellResult(SequenceContext context, string id, SequenceActionResult param) private SequenceActionResult ReportCellResult(SequenceContext context, string id, SequenceActionResult param)
{ {
Status.SetWorkingPosition(eWorkingPosition.REPORTING_RESULT); Status.SetWorkingPosition(eWorkingPosition.REPORTING_RESULT);
@ -1062,7 +1025,11 @@ namespace ILSOS_CCW_H52A.Sequences
string strDate = DateTime.Now.Date.ToString("yyyyMMdd"); string strDate = DateTime.Now.Date.ToString("yyyyMMdd");
string strTime = DateTime.Now.ToString("HHmmss"); string strTime = DateTime.Now.ToString("HHmmss");
// 셀 식별 정보는 스케줄러 슬롯에서 직접 읽음 (Complete 또는 pendingSlot)
var report = _CellScheduler.ActiveReportSlot;
// 레시피·장비 계측값은 Status에서 읽음 (TodayProduct/TotalProduct 포함)
var wd = Status.GetProcessingWeldingData(); var wd = Status.GetProcessingWeldingData();
string reportDataLocalPath = _CellTracker._CellTrackerConfig.reportDataLocalPath; string reportDataLocalPath = _CellTracker._CellTrackerConfig.reportDataLocalPath;
string equipName = _CellTracker._CellTrackerConfig.EquipmentName; string equipName = _CellTracker._CellTrackerConfig.EquipmentName;
@ -1085,23 +1052,23 @@ namespace ILSOS_CCW_H52A.Sequences
equipName + "DATA007={10}," + equipName + "DATA007={10}," +
equipName + "DATA008={11}," + equipName + "DATA008={11}," +
equipName + "DATA009={12}", equipName + "DATA009={12}",
strDate, strTime, wd.LotId, wd.CellId, strDate, strTime, report.LotId, report.CellId,
wd.RecipeNo, wd.LaserPowerCore, wd.MarkSpeed, wd.FocalLength, report.InnerCellInfo.RecipeNo, report.InnerCellInfo.LaserPowerCore, report.InnerCellInfo.MarkSpeed, report.InnerCellInfo.FocalLength,
wd.WeldingJigNo, wd.WeldingSpanTimeMilliseconds, wd.LaserPowerByLBAD, report.JigNo, report.WeldingSpanTime.TotalMilliseconds.ToString(), wd.LaserPowerByLBAD,
wd.TodayProduct, wd.TotalProduct); wd.TodayProduct, wd.TotalProduct);
_CellTracker.SendEcsData(wd.CellId, strPath, strData + ""); _CellTracker.SendEcsData(report.CellId, strPath, strData + "");
Log.I($"Report to CellTracker >> [LotID({wd.LotId.Replace("\0", "").Trim()})] [CellID({wd.CellId.Replace("\0", "").Trim()})]"); Log.I($"Report to CellTracker >> [LotID({report.LotId})] [CellID({report.CellId})]");
// 로컬 CSV 저장 // 로컬 CSV 저장
string strItem = "//Date, Time, LotID, CellID, RecipeNo, LaserPower, MarkSpeed, FocalLength, HeadNo, WeldingTime, LaserPowerByLBAD, TodayCount, TotalCount"; string strItem = "//Date, Time, LotID, CellID, RecipeNo, LaserPower, MarkSpeed, FocalLength, HeadNo, WeldingTime, LaserPowerByLBAD, TodayCount, TotalCount";
string strCsvData = string.Format("{0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}, {10}, {11}, {12}", string strCsvData = string.Format("{0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}, {10}, {11}, {12}",
strDate, strTime, wd.LotId, wd.CellId, strDate, strTime, report.LotId, report.CellId,
wd.RecipeNo, wd.LaserPowerCore, wd.MarkSpeed, wd.FocalLength, report.InnerCellInfo.RecipeNo, report.InnerCellInfo.LaserPowerCore, report.InnerCellInfo.MarkSpeed, report.InnerCellInfo.FocalLength,
wd.WeldingJigNo, wd.WeldingSpanTimeMilliseconds, wd.LaserPowerByLBAD, report.JigNo, report.WeldingSpanTime.TotalMilliseconds.ToString(), wd.LaserPowerByLBAD,
wd.TodayProduct, wd.TotalProduct); wd.TodayProduct, wd.TotalProduct);
WeldingCommon.Utils.ExcelManagementUnit.AppendStringInCSV(strPath, strItem, strCsvData); WeldingCommon.Utils.ExcelManagementUnit.AppendStringInCSV(strPath, strItem, strCsvData);
// 보고 완료: ReportTarget.ReportComplete=true 설정 + pendingSlot 클리어 // 보고 완료: Complete.ReportComplete=true 설정 + pendingSlot 클리어
_CellScheduler.CompleteReport(); _CellScheduler.CompleteReport();
return SequenceAction.Ok(); return SequenceAction.Ok();
@ -1151,7 +1118,7 @@ namespace ILSOS_CCW_H52A.Sequences
private string CheckCanWelded(SequenceContext context, string id, ICollection<string> branches, SequenceActionResult param) private string CheckCanWelded(SequenceContext context, string id, ICollection<string> branches, SequenceActionResult param)
{ {
// ── 재시작 전 미완료 보고 우선 처리 ───────────────────────────────────────── // ── 재시작 전 미완료 보고 우선 처리 ─────────────────────────────────────────
// SavePendingComplete()로 보존된 슬롯이 있으면 정상 파이프라인(ReportTarget)보다 우선. // SavePendingComplete()로 보존된 슬롯이 있으면 정상 파이프라인(Complete)보다 우선.
if (_CellScheduler.HasPendingComplete) { if (_CellScheduler.HasPendingComplete) {
var p = _CellScheduler.PendingComplete; var p = _CellScheduler.PendingComplete;
Log.I($"[PendingReport] 재시작 전 미완료 보고: LotId={p.LotId}, CellId={p.CellId}, JigNo={p.JigNo}"); Log.I($"[PendingReport] 재시작 전 미완료 보고: LotId={p.LotId}, CellId={p.CellId}, JigNo={p.JigNo}");
@ -1160,8 +1127,8 @@ namespace ILSOS_CCW_H52A.Sequences
// ───────────────────────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────────────────────
// 정상 보고 경로: // 정상 보고 경로:
// - ReportTarget이 설정돼 있고 아직 보고 완료되지 않은 경우 // - Complete(Shift로 자동 승격)에 용접 완료된 셀이 있고 아직 보고 미완인 경우
// - 첫번째, 두번째 반복은 SetReportTarget이 호출되지 않으므로 자연스럽게 통과 // - 예열 구간(Complete=null 또는 WeldComplete=false)은 자연스럽게 통과
if (_CellScheduler.HasReport) if (_CellScheduler.HasReport)
return SEQ_REPORT_CELL_RESULT; return SEQ_REPORT_CELL_RESULT;

File diff suppressed because it is too large Load Diff