Compare commits

...

8 Commits

Author SHA1 Message Date
d19560b504 CellTracker에서 이미 셀정보를 넣어준 케이스에서 정지하면..
대기 셀(prefetch2)를 기준으로 지그번호를 유추해서 재시작 시, 보존하도록 한다.
2026-03-10 18:38:19 +09:00
d31020df72 LbadAvg
- NaN = 미수신
- 0.0은 수신됐으나 측정값 0

Add WeldingLog
2026-03-10 16:37:19 +09:00
f15800250e feat: LBAD AVG 타이밍 보장을 위한 4단계 파이프라인 적용 LBAD MES Report가 용접 완료 후 ~100ms 뒤에 수신되어 기존 3단계(Prefetch2→Prefetch1→Complete) 보고 시점에 데이터가 없던 문제를 파이프라인 확장으로 해결. ReportSlot 단계 추가 — 지그 1회전(~320ms) 경과 후 보고하므로 LBAD 데이터 수신 보장. 비동기 대기 없이 메인 시퀀스 타이밍 유지.
변경 내용:
  - CellInfoScheduler: ReportSlot 추가, 4단계 Shift()                                                                                                                                                                                                                                pending 슬롯을 _pendingFromReportSlot / _pendingFromComplete로 분리                                                                                                                                                                                                              TrySetLbadAvg(cellId, avg) 추가 — Complete→ReportSlot 순 폴백 탐색                                                                                                                                                                                                             - JigInfo: LbadAvg 필드 추가 (Clear/CopyFrom 포함)                                                                                                                                                                                                                               - StreamLightLBAD: MesReportReceived 이벤트 추가, 수신 시 발화                                                                                                                                                                                                                   - ProcessManager: RegisterLbadStream() / OnLbadMesReportReceived() 추가                                                                                                                                                                                                            LBAD 콜백 처리를 시퀀스가 아닌 ProcessManager에서 담당                                                                                                                                                                                                                         - SeqAutoRunCCWH52A: RegisterLbadStream 호출,                                                                                                                                                                                                                                      ReportCellResult에서 report.LbadAvg 직접 사용
2026-03-10 15:07:00 +09:00
36fe130233 Can이 없어도 CellTracker에서 받아온 데이터는 계속 소비해줘야함
CellTracker는 CanExist의 유무와 상관없이 데이터를 줌
(앞단에서 찍은 바코드 정보에 따라 데이터를 전달해줌)
2026-03-10 12:21:50 +09:00
d581c24f6b 불필요 주석/region 삭제 2026-03-10 11:08:32 +09:00
62a1127107 version up
1.0.0.1
2026-03-10 10:16:27 +09:00
3edbc28d3c LBAD INPUT 데이터
문자열 처리 보완
2026-03-10 10:11:59 +09:00
45c1de6cc8 셀 파이프라인 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에서 한 번만 설정
2026-03-10 00:40:19 +09:00
9 changed files with 361 additions and 1473 deletions

View File

@ -26,6 +26,10 @@ namespace AuroraLBAD.Service
public static double[] LaserPowerByLBAD = new double[100]; //headnum 크기면 되지만 우선 100으로 해둠
/// LBAD MES Report 수신 시 발화 — (lotId, cellId, headNum, avg)
/// 시퀀스에서 구독하여 Complete 슬롯의 LbadAvg를 채우는 데 사용
public event Action<string, string, int, double> MesReportReceived;
public Int16 LBADAlive { get; set; }
public Int16? LBADAlarmStatus { get; set; }
[StructLayout(LayoutKind.Sequential, Pack = 1)]
@ -276,25 +280,6 @@ namespace AuroraLBAD.Service
return E1.OK;
}
public static unsafe stLBADReportData BytesToLBADReportDataStruct(byte[] bytes)
{
stLBADReportData data = new stLBADReportData();
for (int i=0; i<40; i++)
{
data.LotID[i] = bytes[i];
}
for (int i=0; i<40; i++)
{
data.CellID[i] = bytes[i + 40];
}
data.HeadNum = BitConverter.ToInt16(bytes, 80);
data.AVG = BitConverter.ToDouble(bytes, 82);
return data;
}
public static byte[] StructToBytes<T>(T structure) where T : struct
{
int size = Marshal.SizeOf(typeof(T));
@ -326,37 +311,22 @@ namespace AuroraLBAD.Service
Log.I($"StreamLight LBAD Alarm Status : {LBADAlarmStatus}");
break;
case MESSAGE_LBAD_MES_REPORT:
Log.I($"Received Data : {data}");
//byte[] data_byte = BitConverter.GetBytes(data);
Log.I($"Received Data");
if (data is byte[] bytes && bytes.Length > 0)
{
if (data is byte[] bytes && bytes.Length > 0) {
Log.I($"Received Data Size : {bytes.Length}");
var replyParsed_LBAD = BytesToLBADReportDataStruct(bytes);
unsafe
{
char[] LotID = new char[40];
char[] CellID = new char[40];
for (int i=0; i<40; i++)
{
LotID[i] = (char)bytes[i];
}
for (int i=40; i<80; i++)
{
CellID[i-40] = (char)bytes[i];
}
string LotID_string = new string(LotID);
string CellID_string = new string(CellID);
Log.I($"StreamLight LBAD Report Data : LotID({LotID_string}) / CellID({CellID_string})");
Log.I($"StreamLight LBAD Report Data : HeadNum {replyParsed_LBAD.HeadNum}");
Log.I($"StreamLight LBAD Report Data : LaserPower_AVG {replyParsed_LBAD.AVG}");
if (replyParsed_LBAD.HeadNum > 0)
LaserPowerByLBAD[replyParsed_LBAD.HeadNum] = replyParsed_LBAD.AVG;
else
{
Log.E($"Head Number is {replyParsed_LBAD.HeadNum}");
}
string LotID_string = Encoding.ASCII.GetString(bytes, 0, 40).TrimEnd('\0');
string CellID_string = Encoding.ASCII.GetString(bytes, 40, 40).TrimEnd('\0');
short HeadNum = BitConverter.ToInt16(bytes, 80);
double AVG = BitConverter.ToDouble(bytes, 82);
Log.I($"StreamLight LBAD Report Data (from LBAD to Welder) : LotID {LotID_string}, CellID {CellID_string}");
Log.I($"StreamLight LBAD Report Data (from LBAD to Welder) : HeadNum {HeadNum}");
Log.I($"StreamLight LBAD Report Data (from LBAD to Welder) : LaserPower_AVG {AVG}");
if (HeadNum > 0) {
LaserPowerByLBAD[HeadNum] = AVG;
MesReportReceived?.Invoke(LotID_string, CellID_string, HeadNum, AVG);
} else {
Log.E($"Head Number is {HeadNum}");
}
}
break;

View File

@ -21,15 +21,17 @@ namespace ILSOS_CCW_H52A.Data
/// 파이프라인 단계 접근을 동시에 지원하는 셀 스케줄러.
///
/// - 물리 지그 접근: scheduler[eJigNo.Jig5]
/// - 파이프라인 접근: scheduler.Prefetch1, scheduler.Prefetch2
/// - 파이프라인 접근: scheduler.Prefetch2, scheduler.Prefetch1, scheduler.Complete, scheduler.ReportSlot
///
/// 3단계 파이프라인: Prefetch2 → Prefetch1 → ReportTarget
/// 4단계 파이프라인: Prefetch2 → Prefetch1 → Complete → ReportSlot
/// Prefetch2 : 셀 정보 수신 완료, 스캐너 리스트 업로드 전
/// Prefetch1 : 업로드 완료, 실행 중 — EncoderMonitor 감시 + 완료 체크 대상
/// ReportTarget: 완료, 보고 대기
/// Complete : 용접 완료, LBAD MES Report 수신 대기 (~100ms)
/// MesReportReceived 콜백이 CellId 매칭 후 LbadAvg를 채움
/// ReportSlot : 지그 1회전(~320ms) 경과 → LBAD 데이터 보장, CellTracker 보고 대상
///
/// Shift()는 Prefetch2→Prefetch1만 전진. GC 할당 없음.
/// Prefetch1 ← Prefetch2 ← this[nextJig]
/// Shift()가 4단계 전체를 전진. GC 할당 없음.
/// ReportSlot ← Complete ← Prefetch1 ← Prefetch2 ← this[nextJig]
/// </summary>
public class CellInfoScheduler : Dictionary<eJigNo, JigInfo>
{
@ -44,31 +46,57 @@ namespace ILSOS_CCW_H52A.Data
private set => _prefetch1 = value;
}
/// 현재 보고 대상 JigInfo (CheckScannerListComplete에서 지정, 보고 완료 후 null)
public JigInfo ReportTarget { get; set; }
public JigInfo Complete { get; private set; } // 용접 완료, LBAD 수신 대기
public JigInfo ReportSlot { get; private set; } // LBAD 보장, CellTracker 보고 대상
// 재시작 보존용 독립 슬롯 — Reset()에서 건드리지 않음
// 재시작 시, List를 최초 리스트 2번을 생성하는 로직 때문에.. 필요함
private readonly JigInfo _pendingSlot = new JigInfo(); // 용접 직후 (용접 완, 보고 미완)
private readonly JigInfo _pendingPrefetch1 = new JigInfo(); // 정지 직전 Prefetch1 (업로드 완료, 용접 미완)
private readonly JigInfo _pendingPrefetch2 = new JigInfo(); // 정지 직전 Prefetch2 (업로드 완료, 용접 미완)
// ── 재시작 보존용 독립 슬롯 — Reset()에서 건드리지 않음 ──────────────────────
private readonly JigInfo _pendingPrefetch1 = new JigInfo(); // 정지 직전 Prefetch1 (업로드 완료, 용접 미완)
private readonly JigInfo _pendingPrefetch2 = new JigInfo(); // 정지 직전 Prefetch2 (업로드 완료, 용접 미완)
// 재시작 전 미완료 보고 슬롯 (유효 시 non-null)
public JigInfo PendingComplete => HasPendingComplete ? _pendingSlot : null;
// 정지 직전 ReportSlot: LBAD 수신 완료 상태로 보고만 남은 셀
private readonly JigInfo _pendingFromReportSlot = new JigInfo();
// 정지 직전 Complete: 용접은 완료됐으나 ReportSlot 진입 전 정지 (LBAD 수신 여부 불확실)
private readonly JigInfo _pendingFromComplete = new JigInfo();
// 정지 직전 큐 잔류: Dequeue 전 큐에만 있던 셀 (파이프라인 미진입)
private readonly JigInfo _pendingFromQueue = new JigInfo();
// 재시작 보존 보고가 있는지
public bool HasPendingComplete => _pendingSlot.IsCanExist && !_pendingSlot.ReportComplete;
// ── 재시작 미완료 보고 접근 ──────────────────────────────────────────────────
// 우선순위: _pendingFromReportSlot > _pendingFromComplete
public JigInfo PendingComplete {
get {
if (_pendingFromReportSlot.IsCanExist && !_pendingFromReportSlot.ReportComplete)
return _pendingFromReportSlot;
if (_pendingFromComplete.IsCanExist && !_pendingFromComplete.ReportComplete)
return _pendingFromComplete;
return null;
}
}
// 현재 활성 보고 슬롯 — 재시작 pending 우선
public JigInfo ActiveReportSlot => HasPendingComplete ? _pendingSlot : ReportTarget;
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(_pendingPrefetch2.CellId)
|| !string.IsNullOrEmpty(_pendingFromQueue.CellId);
// 보고가 필요한 상태인지 (정상 + 재시작 모두 포함) >> 용접 완, 보고 미완
public bool HasReport => HasPendingComplete || (ReportTarget != null && !ReportTarget.ReportComplete);
// ReportSlot은 Shift()마다 갱신되므로 IsCanExist·WeldComplete 조건을 함께 확인한다.
public bool HasReport => HasPendingComplete ||
(ReportSlot != null && ReportSlot.IsCanExist && ReportSlot.WeldComplete && !ReportSlot.ReportComplete);
public CellInfoScheduler()
{
@ -76,7 +104,7 @@ namespace ILSOS_CCW_H52A.Data
Add(no, new JigInfo());
}
// 1-based jigNo (_WeldingJigNo 등) → 0-based eJigNo 변환 의의 인덱서
// 1-based jigNo (_WeldingJigNo 등) → 0-based eJigNo 변환 인덱서
public JigInfo this[int jigNo] {
get => base[(eJigNo)(jigNo - 1)];
}
@ -84,15 +112,25 @@ namespace ILSOS_CCW_H52A.Data
// Reset() 전에 호출 — 미완료 보고 및 미용접 슬롯을 보존.
public void SavePendingComplete()
{
// 용접 완료됐으나 보고 전 정지된 경우 (_pendingSlot)
if (ReportTarget != null
&& ReportTarget.IsCanExist
&& ReportTarget.WeldComplete
&& !ReportTarget.ReportComplete)
_pendingSlot.CopyFrom(ReportTarget);
// 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)
@ -114,6 +152,7 @@ namespace ILSOS_CCW_H52A.Data
/// 재시작 시 해당 JigNo의 보존된 셀 정보 복원 시도.
/// 성공 시 해당 슬롯을 즉시 클리어하여 한 번만 사용되도록 보장.
/// canExist=false 지그가 들어오면 슬롯을 소비(폐기)하여 이후 오염 방지.
/// 우선순위: _pendingPrefetch1 → _pendingPrefetch2 → _pendingFromQueue
/// </summary>
/// <returns>복원 성공 시 true (ReceivedCellInfoQueue TryDequeue 불필요)</returns>
public bool TryRestoreCell(int jigNo, out string lotId, out string cellId)
@ -130,54 +169,104 @@ namespace ILSOS_CCW_H52A.Data
_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;
}
// 보고 완료 처리 — ReportTarget 마킹 및 _pendingSlot 클리어.
// GetReportCellResult_SeqResult 성공 시 호출.
public void CompleteReport()
/// <summary>
/// 정지 시점에 큐에 잔류한 셀을 JigNo와 함께 보존.
/// Prefetch2.JigNo 기준으로 다음 지그 번호를 계산하여 호출자가 전달.
/// DrainCellInfoQueue() 호출 전에 먼저 호출해야 한다.
/// </summary>
public void SaveQueueEntry(string lotId, string cellId, int jigNo)
{
_log.I($"Report >> CellId({ReportTarget.CellId}), JigNo({ReportTarget.JigNo})");
ReportTarget?.SetReportComplete(true);
ReportTarget = null;
_pendingSlot.Clear();
_pendingFromQueue.Clear();
_pendingFromQueue.LotId = lotId;
_pendingFromQueue.CellId = cellId;
_pendingFromQueue.JigNo = jigNo;
// IsCanExist는 재시작 후 ReadyScannerList에서 PLC 실측값으로 채워지므로 여기서 설정하지 않음
}
/// <summary>
/// Prefetch2→Prefetch1 한 칸 전진. GC 할당 없음.
/// 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>
/// <param name="nextJig">새 Prefetch2로 지정할 물리 지그 (eJigNo)</param>
public void Shift(eJigNo nextJig)
{
Prefetch1 = Prefetch2;
Prefetch2 = base[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)
{
Prefetch1 = Prefetch2;
Prefetch2 = this[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() 호출 후 이 메서드를 호출할 것.
/// _pendingSlot / _pendingPrefetch1 / _pendingPrefetch2 는 재시작 복원을 위해 건드리지 않음.
/// pending 슬롯들은 재시작 복원을 위해 건드리지 않음.
/// </summary>
public void Reset()
{
foreach (var info in Values)
info.Clear();
Prefetch2 = null;
Prefetch1 = null;
ReportTarget = null;
// _pendingSlot, _pendingPrefetch1/2: SavePendingComplete()가 복사해 둔 데이터를 유지
Prefetch2 = null;
Prefetch1 = null;
Complete = null;
ReportSlot = null;
// _pendingFromReportSlot, _pendingFromComplete, _pendingPrefetch1/2:
// SavePendingComplete()가 복사해 둔 데이터를 유지
}
/// <summary>

View File

@ -27,6 +27,9 @@ namespace ILSOS_CCW_H52A.Data
public string LotId;
public string CellId;
// LBAD MES Report 수신 값 — Complete 단계에서 콜백으로 채워짐, ReportSlot 단계에서 보고에 사용
public double LbadAvg;
// --- 플래그 ---
public bool CheckSendCellinfo;
public bool ReportComplete;
@ -60,6 +63,7 @@ namespace ILSOS_CCW_H52A.Data
WeldingSpanTime = src.WeldingSpanTime;
LotId = src.LotId;
CellId = src.CellId;
LbadAvg = src.LbadAvg;
CheckSendCellinfo = src.CheckSendCellinfo;
ReportComplete = src.ReportComplete;
UpdateCellInfo(src.InnerCellInfo);
@ -85,6 +89,7 @@ namespace ILSOS_CCW_H52A.Data
LotId = string.Empty;
CellId = string.Empty;
LbadAvg = double.NaN; // NaN = 미수신, 0.0은 수신됐으나 측정값 0
CheckSendCellinfo = false;
ReportComplete = false;

View File

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

View File

@ -49,5 +49,5 @@ using System.Windows;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyVersion("1.0.0.1")]
[assembly: AssemblyFileVersion("1.0.0.1")]

View File

@ -70,12 +70,43 @@ namespace ILSOS_CCW_H52A.Sequences.ProcessObjects
cellScheduler = new CellInfoScheduler();
}
/// <summary>
/// LBAD MES Report 수신 콜백을 등록.
/// InitDev에서 StreamLightLBAD 생성 후 호출.
/// </summary>
public void RegisterLbadStream(AuroraLBAD.Service.StreamLightLBAD lbad)
{
lbad.MesReportReceived += OnLbadMesReportReceived;
}
private void OnLbadMesReportReceived(string lotId, string cellId, int headNum, double avg)
{
if (cellScheduler.TrySetLbadAvg(cellId, avg))
_log.I($"[LBAD] AVG 저장 >> CellId({cellId}) HeadNum({headNum}) AVG({avg:F3})");
else
_log.W($"[LBAD] 매칭 슬롯 없음 >> CellId({cellId}) HeadNum({headNum}) AVG({avg:F3})");
}
private void OnCellReceived(string lotId, string cellId)
{
lotId = lotId.Replace("\0", "").Trim();
cellId = cellId.Replace("\0", "").Trim();
ReceivedCellInfoQueue.Enqueue((lotId, cellId));
_log.I($"Cell Tracker CallBack Received >> [LotId({lotId})] [CellId({cellId})]");
_log.I($"Cell Tracker CallBack Received >> [LotId({lotId})] [CellId({cellId})] [QueueCount({ReceivedCellInfoQueue.Count})]");
}
/// <summary>
/// 재시작 시 큐 잔류 데이터 전량 폐기.
/// CellTracker는 투입 기준으로 선읽음하므로, 정지 시점에 큐에 남은 데이터는
/// 이미 처리됐거나 이전 회차의 셀 정보일 수 있어 모두 버린다.
/// _pendingPrefetch1/2 는 이미 TryDequeue 이후 저장된 별도 슬롯이므로 영향 없음.
/// </summary>
public void DrainCellInfoQueue()
{
int count = 0;
while (ReceivedCellInfoQueue.TryDequeue(out _)) count++;
if (count > 0)
_log.W($"[QueueDrain] 재시작 시 잔류 데이터 {count}건 폐기");
}
public IEnumerable<bool> CheckRotaryState()

View File

@ -21,10 +21,12 @@ using ILSOS_CCW_H52A.RMS;
using ILSOS_CCW_H52A.Sequences.ProcessObjects;
using Microsoft.Win32;
using OpenCvSharp;
using OpenCvSharp.Aruco;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reactive.Subjects;
using System.Reflection;
using System.Threading;
using System.Windows;
@ -107,6 +109,7 @@ namespace ILSOS_CCW_H52A.Sequences
private SystemParameter _SystemParameter;
private ScannerCanvasUserCtrl _ScannerCanvasUserCtrl; //스캐너 동작관련 객체 선언
private int _RecipeNo;
private int _ModelNo;
private IRecipeManager _RecipeManager;
@ -117,9 +120,6 @@ namespace ILSOS_CCW_H52A.Sequences
private SeqAutoRunCCWH52A_InitParam _InitParam;
private uint _TodayProduct;
private uint _TotalProduct;
private double _LaserPowerByLBAD;
private bool _ModulationMode;
@ -141,7 +141,6 @@ namespace ILSOS_CCW_H52A.Sequences
private double _WeldingStartAngle;
private bool isTheFirstListExecute;
private int _RecipeNo;
private bool UsePlcShotThemAll { get; set; } = false;
private bool UsePlcPositionCompensation { get; set; } = false;
@ -283,8 +282,6 @@ namespace ILSOS_CCW_H52A.Sequences
private bool InitSequenceVariable(SequenceContext context)
{
_AutoRunStartTime = DateTime.Now;
_TodayProduct = _SystemParameter.TodayProductCount;
_TotalProduct = _SystemParameter.TotalProductCount;
var ActivateRecipeName = _RecipeManager.ActivatedRecipe();
Log.I($"Current Activate Recipe, ID : {ActivateRecipeName.Id}, Name : {ActivateRecipeName.Name}");
@ -294,8 +291,9 @@ namespace ILSOS_CCW_H52A.Sequences
_WeldingScannerListNo = eListNo.LIST1;
_RecipeNo = ActivateRecipeName.Id;
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;
return true;
@ -310,6 +308,7 @@ namespace ILSOS_CCW_H52A.Sequences
_StreamLightLBAD = new StreamLightLBAD();
_StreamLightLBAD._Client = context.GetStreamLightClient();
_StreamLightLBAD._Sub = context.GetStreamLightSub();
ProcessManager.Instance.RegisterLbadStream(_StreamLightLBAD);
}
_CellTracker = context.GetCellTracker();
_PLC = context.Devices[DeviceIds.PLCDevice] as PLCControllerCCWH52A;
@ -445,13 +444,25 @@ namespace ILSOS_CCW_H52A.Sequences
// JigInfo 풀 스케줄러 초기화 (전전/전/대기/완료 셀 파이프라인)
// ── 재시작 전 미완료 보고 보존 ───────────────────────────────────────────────
// ReportTarget이 있는 상태로 정지가 들어오면 Reset() 후 슬롯이 Clear()되어 보고 불가.
// Complete가 있는 상태로 정지가 들어오면 Reset() 후 슬롯이 Clear()되어 보고 불가.
// SavePendingComplete()로 _pendingSlot에 복사한 뒤 Reset()을 호출한다.
_CellScheduler.SavePendingComplete();
// 큐 잔류 셀 보존 — DrainCellInfoQueue 전에 선행 (Prefetch2 다음 지그 기준)
// Prefetch2.JigNo가 확정값이므로 다음 지그 = (JigNo % 16) + 1 로 신뢰성 있게 계산
if (_CellScheduler.Prefetch2 != null && _CellScheduler.Prefetch2.JigNo > 0) {
int nextJigNo = (_CellScheduler.Prefetch2.JigNo % ProcessManager.RotaryJigCount) + 1;
if (ProcessManager.Instance.ReceivedCellInfoQueue.TryDequeue(out var qCell)) {
_CellScheduler.SaveQueueEntry(qCell.LotId, qCell.CellId, nextJigNo);
Log.I($"[QueueSave] Jig{nextJigNo} 큐 잔류 셀 보존 >> CellId({qCell.CellId})");
}
}
if (_CellScheduler.HasPendingPrefetch)
Log.I("[PendingRestore] 미용접 셀 보존됨 >> 재시작 후 JigNo 매칭 시 복원 예정");
_CellScheduler.Reset();
ProcessManager.Instance.DrainCellInfoQueue();
// 정지 전 미저장 APD 잔존 데이터 클리어 — 재시작 시 중복 누적 방지
Status.GetProcessingWeldingData().APD.Clear();
@ -529,9 +540,6 @@ namespace ILSOS_CCW_H52A.Sequences
//return SEQ_CHECK_LOAD_COMPLETE;
}
#region TODO: [장비구현필요]
// TODO: [장비구현필요] 공정 Node 구현
// 지그 상태 확인
private SequenceActionResult CheckLoadComplete(SequenceContext context, string id, SequenceActionResult param)
{
@ -563,7 +571,7 @@ namespace ILSOS_CCW_H52A.Sequences
Log.I($"[Warmup] ScannerList {_WeldingCount}: pipeline not ready, skip complete check");
return SequenceAction.Ok();
}
if (!CheckCurrentEncoderAngle(jigComplete.StartAngle)) {
if (!CheckInsideWeldPosition(jigComplete.StartAngle)) {
// IsCanExist=false(공 지그)는 리스트 실행은 하지만 용접이 없으므로
// 엔코더 범위를 벗어나도 에러가 아님
if (jigComplete.IsCanExist) {
@ -583,9 +591,7 @@ namespace ILSOS_CCW_H52A.Sequences
TimeSpan weldInterval = jigComplete.WeldEndTime - _prevWeldEndTime;
_prevWeldEndTime = jigComplete.WeldEndTime;
// 보고 대상 슬롯 지정 (스케줄러가 관리)
_CellScheduler.ReportTarget = jigComplete;
// 보고 대상 지정 불필요 — Shift()가 Prefetch1 → Complete로 자동 전진
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"),
@ -597,8 +603,36 @@ namespace ILSOS_CCW_H52A.Sequences
_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();
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();
}
@ -633,25 +667,37 @@ namespace ILSOS_CCW_H52A.Sequences
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;
_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 + 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
if (currentAngle < startAngle)
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;
}
@ -717,38 +763,47 @@ namespace ILSOS_CCW_H52A.Sequences
#warning OC2 ACW 기준 >> Cell Tracker는 어느 시점에 데이터는 주는지 모르겠지만, '전전전 셀' 쯤에서 이미 건네받음! 다른 프로젝트에서 확인 필요함
// 복원 우선: 정지 전 업로드됐으나 로터리 감속·정지로 용접 미완된 셀 정보 복원.
// canExist=false 지그도 TryRestoreCell을 호출하여 슬롯 소비(폐기) — 이후 오염 방지.
(string LotId, string CellId) recvInfo = (string.Empty, string.Empty);
if (_CellScheduler.TryRestoreCell(_WeldingJigNo, out var rLot, out var rCell)) {
if (canExist) {
_CellScheduler.Prefetch2.LotId = rLot;
_CellScheduler.Prefetch2.CellId = rCell;
Status.GetProcessingWeldingData().WorkLotId = rLot;
Status.GetProcessingWeldingData().WorkCellId = rCell;
recvInfo.LotId = rLot;
recvInfo.CellId = rCell;
Log.I($"[PendingRestore] Jig{_WeldingJigNo} 셀 복원 >> CellId({rCell})");
}
else {
Log.W($"[PendingRestore] Jig{_WeldingJigNo} canExist=false, 복원 데이터 폐기 >> CellId({rCell})");
}
}
else if (canExist && ProcessManager.Instance.ReceivedCellInfoQueue.TryDequeue(out var cell)) {
_CellScheduler.Prefetch2.LotId = cell.LotId;
_CellScheduler.Prefetch2.CellId = cell.CellId;
Status.GetProcessingWeldingData().WorkLotId = cell.LotId;
Status.GetProcessingWeldingData().WorkCellId = cell.CellId;
else if (ProcessManager.Instance.ReceivedCellInfoQueue.TryDequeue(out var cell)) {
if (canExist) {
recvInfo.LotId = cell.LotId;
recvInfo.CellId = cell.CellId;
} else {
// 앞공정 NG 셀: CellTracker는 투입 시 이미 스캔했으므로 큐에서 소비 후 폐기.
// 소비하지 않으면 다음 정상 셀이 이 NG 데이터를 가져가 정합성 붕괴.
Log.W($"[NG Skip] Jig{_WeldingJigNo} canExist=false, 큐 데이터 폐기 >> CellId({cell.CellId})");
}
} else {
if (canExist)
Log.E($"[QueueEmpty] Jig{_WeldingJigNo} canExist=true 이지만 큐가 비어있음");
_CellTracker.getDummyData(out string lotId, out string cellId);
_CellScheduler.Prefetch2.LotId = lotId;
_CellScheduler.Prefetch2.CellId = cellId;
Status.GetProcessingWeldingData().WorkLotId = lotId;
Status.GetProcessingWeldingData().WorkCellId = cellId;
recvInfo.LotId = lotId;
recvInfo.CellId = 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)
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})]" +
$"[StartAngle(Y)({_CellScheduler.Prefetch2.StartAngle})]");
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})]" +
$"[StartAngle(Y)({_CellScheduler.Prefetch1.StartAngle})]");
@ -764,9 +819,6 @@ namespace ILSOS_CCW_H52A.Sequences
}
#endregion
_RecipeNo = _RecipeManager.ActivatedRecipe().Id;
SetWeldingData();
return SEQ_SET_PARAMS_UPLOAD_WELDING_LIST_SEQ;
}
else {
@ -786,20 +838,6 @@ namespace ILSOS_CCW_H52A.Sequences
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)
{
@ -833,28 +871,7 @@ namespace ILSOS_CCW_H52A.Sequences
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)
//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;
else
return false;
@ -884,14 +901,22 @@ 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;
ScannerParam laserParam = null;
laserParam = GetSelectedDrawObjectData(_SelectedDrawObjectList[0]);
_CellScheduler.Prefetch2.LotId = LotId;
_CellScheduler.Prefetch2.CellId = CellId;
_CellScheduler.Prefetch2.InnerCellInfo.RecipeNo = _RecipeNo;
_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");
return SequenceAction.Ok();
}
@ -948,7 +973,6 @@ namespace ILSOS_CCW_H52A.Sequences
private SequenceActionResult EncoderMonitor(SequenceContext context, string id, SequenceActionResult param)
{
double send_time_offset = 0.1;
string LotID = "", CellID = "";
while (!context.Stopped) {
double encoderX, currentAngle;
@ -968,15 +992,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})");
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);
executeTarget.CheckSendCellinfo = _StreamLightLBAD.SendCellInfo(executeTarget.LotId, executeTarget.CellId, _RecipeNo, _ModelNo, executeTarget.JigNo);
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
Log.I($"[StreamLight]SendCellInfo LotID [{LotID}], CellID [{CellID}], JigNo [{executeTarget.JigNo}]");
Log.I($"[StreamLight]SendCellInfo LotID [{executeTarget.LotId}], CellID [{executeTarget.CellId}], JigNo [{executeTarget.JigNo}]");
}
}
}
@ -993,15 +1015,19 @@ namespace ILSOS_CCW_H52A.Sequences
}
}
// List의 실행의 토글을 확인함
// List의 시작/종료 로그 — MOTF에서 LIST1 -> LIST2 전환 시 종료+시작 각각 기록
_Scanner.GetListPointer(out eListNo curList, out uint pos);
var isBusy = _Scanner.GetBusyStatus();
if (!isBusy && (curList == eListNo.LIST1 || curList == eListNo.LIST2))
curList = eListNo.NONE;
eListNo effectiveList = (!isBusy && (curList == eListNo.LIST1 || curList == eListNo.LIST2))
? eListNo.NONE : curList;
var IsToggleListState = ChangeChecker.IsChangedInt("ListState", (int)curList);
if (IsToggleListState)
Log.I($"Fork Laser On({LaserIO}) Encoder X : ({encoderX}), Encoder Y : ({currentAngle}), PLC Rotary : ({CurrentPosPLC}), List {executeTarget?.ListNo}, JigNo : ({executeTarget?.JigNo})");
if (ChangeChecker.IsChangedInt("ListState", (int)effectiveList, out int prevListRaw)) {
var prevList = (eListNo)prevListRaw;
if (prevList != eListNo.NONE)
Log.I($"[ListEnd] List {prevList} 종료 >> LaserOn({LaserIO}) EncoderY({currentAngle:F3}) JigNo({executeTarget?.JigNo})");
if (effectiveList != eListNo.NONE)
Log.I($"[ListStart] List {effectiveList} 시작 >> LaserOn({LaserIO}) EncoderY({currentAngle:F3}) JigNo({executeTarget?.JigNo})");
}
for (int i = 0; i < _RotaryJigCount; i++) {
if (_PLC.ReadBit((int)eBitInput.PLC_USE_WELD_JIG_1 + i).data == false) {
@ -1015,46 +1041,6 @@ namespace ILSOS_CCW_H52A.Sequences
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)
{
Status.SetWorkingPosition(eWorkingPosition.REPORTING_RESULT);
@ -1062,7 +1048,16 @@ namespace ILSOS_CCW_H52A.Sequences
string strDate = DateTime.Now.Date.ToString("yyyyMMdd");
string strTime = DateTime.Now.ToString("HHmmss");
// 셀 식별 정보 + LBAD AVG는 스케줄러 슬롯에서 직접 읽음 (ReportSlot 또는 pendingSlot)
// ReportSlot 단계에서는 지그 1회전(~320ms) 경과 → LBAD MES Report(~100ms) 보장 수신
var report = _CellScheduler.ActiveReportSlot;
bool lbadReceived = !double.IsNaN(report.LbadAvg);
string lbadAvg = lbadReceived ? report.LbadAvg.ToString("F3") : "0";
if (!lbadReceived)
Log.W($"[LBAD] ReportSlot LbadAvg 미수신 >> CellId({report.CellId})");
// 레시피·장비 계측값은 Status에서 읽음 (TodayProduct/TotalProduct 포함)
var wd = Status.GetProcessingWeldingData();
string reportDataLocalPath = _CellTracker._CellTrackerConfig.reportDataLocalPath;
string equipName = _CellTracker._CellTrackerConfig.EquipmentName;
@ -1085,23 +1080,23 @@ namespace ILSOS_CCW_H52A.Sequences
equipName + "DATA007={10}," +
equipName + "DATA008={11}," +
equipName + "DATA009={12}",
strDate, strTime, wd.LotId, wd.CellId,
wd.RecipeNo, wd.LaserPowerCore, wd.MarkSpeed, wd.FocalLength,
wd.WeldingJigNo, wd.WeldingSpanTimeMilliseconds, wd.LaserPowerByLBAD,
strDate, strTime, report.LotId, report.CellId,
report.InnerCellInfo.RecipeNo, report.InnerCellInfo.LaserPowerCore, report.InnerCellInfo.MarkSpeed, report.InnerCellInfo.FocalLength,
report.JigNo, report.WeldingSpanTime.TotalMilliseconds.ToString(), lbadAvg,
wd.TodayProduct, wd.TotalProduct);
_CellTracker.SendEcsData(wd.CellId, strPath, strData + "");
Log.I($"Report to CellTracker >> [LotID({wd.LotId.Replace("\0", "").Trim()})] [CellID({wd.CellId.Replace("\0", "").Trim()})]");
_CellTracker.SendEcsData(report.CellId, strPath, strData + "");
Log.I($"Report to CellTracker >> [LotID({report.LotId})] [CellID({report.CellId})] [LbadAvg({lbadAvg})]");
// 로컬 CSV 저장
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}",
strDate, strTime, wd.LotId, wd.CellId,
wd.RecipeNo, wd.LaserPowerCore, wd.MarkSpeed, wd.FocalLength,
wd.WeldingJigNo, wd.WeldingSpanTimeMilliseconds, wd.LaserPowerByLBAD,
strDate, strTime, report.LotId, report.CellId,
report.InnerCellInfo.RecipeNo, report.InnerCellInfo.LaserPowerCore, report.InnerCellInfo.MarkSpeed, report.InnerCellInfo.FocalLength,
report.JigNo, report.WeldingSpanTime.TotalMilliseconds.ToString(), lbadAvg,
wd.TodayProduct, wd.TotalProduct);
WeldingCommon.Utils.ExcelManagementUnit.AppendStringInCSV(strPath, strItem, strCsvData);
// 보고 완료: ReportTarget.ReportComplete=true 설정 + pendingSlot 클리어
// 보고 완료: Complete.ReportComplete=true 설정 + pendingSlot 클리어
_CellScheduler.CompleteReport();
return SequenceAction.Ok();
@ -1146,12 +1141,11 @@ namespace ILSOS_CCW_H52A.Sequences
return SEQ_CHECK_LOAD_COMPLETE;
}
}
// END ALL
#endregion
private string CheckCanWelded(SequenceContext context, string id, ICollection<string> branches, SequenceActionResult param)
{
// ── 재시작 전 미완료 보고 우선 처리 ─────────────────────────────────────────
// SavePendingComplete()로 보존된 슬롯이 있으면 정상 파이프라인(ReportTarget)보다 우선.
// SavePendingComplete()로 보존된 슬롯이 있으면 정상 파이프라인(Complete)보다 우선.
if (_CellScheduler.HasPendingComplete) {
var p = _CellScheduler.PendingComplete;
Log.I($"[PendingReport] 재시작 전 미완료 보고: LotId={p.LotId}, CellId={p.CellId}, JigNo={p.JigNo}");
@ -1160,8 +1154,8 @@ namespace ILSOS_CCW_H52A.Sequences
// ─────────────────────────────────────────────────────────────────────────────
// 정상 보고 경로:
// - ReportTarget이 설정돼 있고 아직 보고 완료되지 않은 경우
// - 첫번째, 두번째 반복은 SetReportTarget이 호출되지 않으므로 자연스럽게 통과
// - Complete(Shift로 자동 승격)에 용접 완료된 셀이 있고 아직 보고 미완인 경우
// - 예열 구간(Complete=null 또는 WeldComplete=false)은 자연스럽게 통과
if (_CellScheduler.HasReport)
return SEQ_REPORT_CELL_RESULT;

File diff suppressed because it is too large Load Diff

View File

@ -57,5 +57,23 @@ namespace WeldingCommon.Utils
mappingInt[Resource] = value;
return true;
}
public static bool IsChangedInt(string Resource, int value, out int prevalue, int defaultVal = 0)
{
if (mappingInt.ContainsKey(Resource) == false) {
mappingInt[Resource] = defaultVal;
prevalue = mappingInt[Resource];
return false;
}
if (mappingInt[Resource] == value) {
prevalue = value;
return false;
}
prevalue = mappingInt[Resource];
mappingInt[Resource] = value;
return true;
}
}
}