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 직접 사용
This commit is contained in:
최이근 2026-03-10 15:07:00 +09:00
parent 36fe130233
commit f15800250e
5 changed files with 143 additions and 48 deletions

View File

@ -26,6 +26,10 @@ namespace AuroraLBAD.Service
public static double[] LaserPowerByLBAD = new double[100]; //headnum 크기면 되지만 우선 100으로 해둠 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 LBADAlive { get; set; }
public Int16? LBADAlarmStatus { get; set; } public Int16? LBADAlarmStatus { get; set; }
[StructLayout(LayoutKind.Sequential, Pack = 1)] [StructLayout(LayoutKind.Sequential, Pack = 1)]
@ -318,11 +322,13 @@ namespace AuroraLBAD.Service
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) : 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) : HeadNum {HeadNum}");
Log.I($"StreamLight LBAD Report Data (from LBAD to Welder) : LaserPower_AVG {AVG}"); Log.I($"StreamLight LBAD Report Data (from LBAD to Welder) : LaserPower_AVG {AVG}");
if (HeadNum > 0) if (HeadNum > 0) {
LaserPowerByLBAD[HeadNum] = AVG; LaserPowerByLBAD[HeadNum] = AVG;
else MesReportReceived?.Invoke(LotID_string, CellID_string, HeadNum, AVG);
} else {
Log.E($"Head Number is {HeadNum}"); Log.E($"Head Number is {HeadNum}");
} }
}
break; break;
default: default:
break; break;

View File

@ -21,15 +21,17 @@ namespace ILSOS_CCW_H52A.Data
/// 파이프라인 단계 접근을 동시에 지원하는 셀 스케줄러. /// 파이프라인 단계 접근을 동시에 지원하는 셀 스케줄러.
/// ///
/// - 물리 지그 접근: scheduler[eJigNo.Jig5] /// - 물리 지그 접근: scheduler[eJigNo.Jig5]
/// - 파이프라인 접근: scheduler.Prefetch2, scheduler.Prefetch1, scheduler.Complete /// - 파이프라인 접근: scheduler.Prefetch2, scheduler.Prefetch1, scheduler.Complete, scheduler.ReportSlot
/// ///
/// 3단계 파이프라인: Prefetch2 → Prefetch1 → Complete /// 4단계 파이프라인: Prefetch2 → Prefetch1 → Complete → ReportSlot
/// Prefetch2 : 셀 정보 수신 완료, 스캐너 리스트 업로드 전 /// Prefetch2 : 셀 정보 수신 완료, 스캐너 리스트 업로드 전
/// Prefetch1 : 업로드 완료, 실행 중 — EncoderMonitor 감시 + 완료 체크 대상 /// Prefetch1 : 업로드 완료, 실행 중 — EncoderMonitor 감시 + 완료 체크 대상
/// Complete : 용접 완료, 보고 대기 /// Complete : 용접 완료, LBAD MES Report 수신 대기 (~100ms)
/// MesReportReceived 콜백이 CellId 매칭 후 LbadAvg를 채움
/// ReportSlot : 지그 1회전(~320ms) 경과 → LBAD 데이터 보장, CellTracker 보고 대상
/// ///
/// Shift()가 3단계 전체를 전진. GC 할당 없음. /// Shift()가 4단계 전체를 전진. GC 할당 없음.
/// Complete ← Prefetch1 ← Prefetch2 ← this[nextJig] /// ReportSlot ← Complete ← Prefetch1 ← Prefetch2 ← this[nextJig]
/// </summary> /// </summary>
public class CellInfoScheduler : Dictionary<eJigNo, JigInfo> public class CellInfoScheduler : Dictionary<eJigNo, JigInfo>
{ {
@ -44,22 +46,44 @@ namespace ILSOS_CCW_H52A.Data
private set => _prefetch1 = value; private set => _prefetch1 = value;
} }
public JigInfo Complete { get; private set; } // 전 셀: 용접 완료, 보고 대기 public JigInfo Complete { get; private set; } // 용접 완료, LBAD 수신 대기
public JigInfo ReportSlot { get; private set; } // LBAD 보장, CellTracker 보고 대상
// 재시작 보존용 독립 슬롯 — Reset()에서 건드리지 않음 // ── 재시작 보존용 독립 슬롯 — Reset()에서 건드리지 않음 ──────────────────────
// 재시작 시, List를 최초 리스트 2번을 생성하는 로직 때문에.. 필요함
private readonly JigInfo _pendingSlot = new JigInfo(); // 용접 직후 (용접 완, 보고 미완)
private readonly JigInfo _pendingPrefetch1 = new JigInfo(); // 정지 직전 Prefetch1 (업로드 완료, 용접 미완) private readonly JigInfo _pendingPrefetch1 = new JigInfo(); // 정지 직전 Prefetch1 (업로드 완료, 용접 미완)
private readonly JigInfo _pendingPrefetch2 = new JigInfo(); // 정지 직전 Prefetch2 (업로드 완료, 용접 미완) private readonly JigInfo _pendingPrefetch2 = new JigInfo(); // 정지 직전 Prefetch2 (업로드 완료, 용접 미완)
// 재시작 전 미완료 보고 슬롯 (유효 시 non-null) // 정지 직전 ReportSlot: LBAD 수신 완료 상태로 보고만 남은 셀
public JigInfo PendingComplete => HasPendingComplete ? _pendingSlot : null; private readonly JigInfo _pendingFromReportSlot = new JigInfo();
// 정지 직전 Complete: 용접은 완료됐으나 ReportSlot 진입 전 정지 (LBAD 수신 여부 불확실)
private readonly JigInfo _pendingFromComplete = 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 bool HasPendingComplete =>
public JigInfo ActiveReportSlot => HasPendingComplete ? _pendingSlot : Complete; (_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 => public bool HasPendingPrefetch =>
@ -67,9 +91,9 @@ namespace ILSOS_CCW_H52A.Data
|| !string.IsNullOrEmpty(_pendingPrefetch2.CellId); || !string.IsNullOrEmpty(_pendingPrefetch2.CellId);
// 보고가 필요한 상태인지 (정상 + 재시작 모두 포함) >> 용접 완, 보고 미완 // 보고가 필요한 상태인지 (정상 + 재시작 모두 포함) >> 용접 완, 보고 미완
// Complete는 Shift()마다 갱신되므로 IsCanExist·WeldComplete 조건을 함께 확인한다. // ReportSlot은 Shift()마다 갱신되므로 IsCanExist·WeldComplete 조건을 함께 확인한다.
public bool HasReport => HasPendingComplete || public bool HasReport => HasPendingComplete ||
(Complete != null && Complete.IsCanExist && Complete.WeldComplete && !Complete.ReportComplete); (ReportSlot != null && ReportSlot.IsCanExist && ReportSlot.WeldComplete && !ReportSlot.ReportComplete);
public CellInfoScheduler() public CellInfoScheduler()
{ {
@ -77,7 +101,7 @@ namespace ILSOS_CCW_H52A.Data
Add(no, new JigInfo()); Add(no, new JigInfo());
} }
// 1-based jigNo (_WeldingJigNo 등) → 0-based eJigNo 변환 의의 인덱서 // 1-based jigNo (_WeldingJigNo 등) → 0-based eJigNo 변환 인덱서
public JigInfo this[int jigNo] { public JigInfo this[int jigNo] {
get => base[(eJigNo)(jigNo - 1)]; get => base[(eJigNo)(jigNo - 1)];
} }
@ -85,15 +109,25 @@ namespace ILSOS_CCW_H52A.Data
// Reset() 전에 호출 — 미완료 보고 및 미용접 슬롯을 보존. // Reset() 전에 호출 — 미완료 보고 및 미용접 슬롯을 보존.
public void SavePendingComplete() public void SavePendingComplete()
{ {
// 용접 완료됐으나 보고 전 정지된 경우 (_pendingSlot) // ReportSlot: LBAD 수신 완료 상태, 보고만 남은 셀
if (ReportSlot != null
&& ReportSlot.IsCanExist
&& ReportSlot.WeldComplete
&& !ReportSlot.ReportComplete)
_pendingFromReportSlot.CopyFrom(ReportSlot);
else
_pendingFromReportSlot.Clear();
// Complete: 용접 완료, ReportSlot 진입 전 정지 (LBAD 도착 여부 불확실)
if (Complete != null if (Complete != null
&& Complete.IsCanExist && Complete.IsCanExist
&& Complete.WeldComplete && Complete.WeldComplete
&& !Complete.ReportComplete) && !Complete.ReportComplete)
_pendingSlot.CopyFrom(Complete); _pendingFromComplete.CopyFrom(Complete);
else
_pendingFromComplete.Clear();
// 업로드 완료됐으나 로터리 정지로 용접 자체가 안 된 경우 (_pendingPrefetch1/2) // 업로드 완료됐으나 로터리 정지로 용접 자체가 안 된 경우 (_pendingPrefetch1/2)
// (연속셀이 들어왔을때, 중간에 멈춘 경우)
if (Prefetch1 != null if (Prefetch1 != null
&& Prefetch1.IsCanExist && Prefetch1.IsCanExist
&& !string.IsNullOrEmpty(Prefetch1.CellId) && !string.IsNullOrEmpty(Prefetch1.CellId)
@ -136,23 +170,48 @@ namespace ILSOS_CCW_H52A.Data
return false; return false;
} }
// 보고 완료 처리 — Complete 마킹 및 _pendingSlot 클리어. /// <summary>
// GetReportCellResult_SeqResult 성공 시 호출. /// 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() public void CompleteReport()
{ {
var reported = ActiveReportSlot; var reported = ActiveReportSlot;
_log.I($"Report >> CellId({reported?.CellId}), JigNo({reported?.JigNo})"); _log.I($"Report >> CellId({reported?.CellId}), JigNo({reported?.JigNo})");
Complete?.SetReportComplete(true); // 정상 보고 마킹 (pending 경로면 no-op)
_pendingSlot.Clear(); // 재시작 보존 슬롯 항상 클리어 if (_pendingFromReportSlot.IsCanExist && !_pendingFromReportSlot.ReportComplete)
_pendingFromReportSlot.Clear();
else if (_pendingFromComplete.IsCanExist && !_pendingFromComplete.ReportComplete)
_pendingFromComplete.Clear();
else
ReportSlot?.SetReportComplete(true);
} }
/// <summary> /// <summary>
/// 3단계 파이프라인 전진. GC 할당 없음. /// 4단계 파이프라인 전진. GC 할당 없음.
/// Complete ← Prefetch1 ← Prefetch2 ← this[nextJig] (Clear) /// ReportSlot ← Complete ← Prefetch1 ← Prefetch2 ← this[nextJig] (Clear)
/// 새 Prefetch2 슬롯은 Clear 후 반환 — 호출자가 셀 정보를 채워야 한다. /// 새 Prefetch2 슬롯은 Clear 후 반환 — 호출자가 셀 정보를 채워야 한다.
/// </summary> /// </summary>
public void Shift(eJigNo nextJig) public void Shift(eJigNo nextJig)
{ {
ReportSlot = Complete; // Complete → ReportSlot
Complete = Prefetch1; // Prefetch1 → Complete Complete = Prefetch1; // Prefetch1 → Complete
Prefetch1 = Prefetch2; // Prefetch2 → Prefetch1 (volatile write: release fence) Prefetch1 = Prefetch2; // Prefetch2 → Prefetch1 (volatile write: release fence)
Prefetch2 = base[nextJig]; Prefetch2 = base[nextJig];
@ -161,6 +220,7 @@ namespace ILSOS_CCW_H52A.Data
// AutoRun에서 사용되는 자료형으로 추가 (편의 인덱서 사용) // AutoRun에서 사용되는 자료형으로 추가 (편의 인덱서 사용)
public void Shift(int nextJig) public void Shift(int nextJig)
{ {
ReportSlot = Complete; // Complete → ReportSlot
Complete = Prefetch1; // Prefetch1 → Complete Complete = Prefetch1; // Prefetch1 → Complete
Prefetch1 = Prefetch2; // Prefetch2 → Prefetch1 (volatile write: release fence) Prefetch1 = Prefetch2; // Prefetch2 → Prefetch1 (volatile write: release fence)
Prefetch2 = this[nextJig]; Prefetch2 = this[nextJig];
@ -170,7 +230,7 @@ namespace ILSOS_CCW_H52A.Data
/// <summary> /// <summary>
/// 전체 초기화 (AutoRun 시작 시 호출). /// 전체 초기화 (AutoRun 시작 시 호출).
/// SavePendingComplete() 호출 후 이 메서드를 호출할 것. /// SavePendingComplete() 호출 후 이 메서드를 호출할 것.
/// _pendingSlot / _pendingPrefetch1 / _pendingPrefetch2 는 재시작 복원을 위해 건드리지 않음. /// pending 슬롯들은 재시작 복원을 위해 건드리지 않음.
/// </summary> /// </summary>
public void Reset() public void Reset()
{ {
@ -180,7 +240,9 @@ namespace ILSOS_CCW_H52A.Data
Prefetch2 = null; Prefetch2 = null;
Prefetch1 = null; Prefetch1 = null;
Complete = null; Complete = null;
// _pendingSlot, _pendingPrefetch1/2: SavePendingComplete()가 복사해 둔 데이터를 유지 ReportSlot = null;
// _pendingFromReportSlot, _pendingFromComplete, _pendingPrefetch1/2:
// SavePendingComplete()가 복사해 둔 데이터를 유지
} }
/// <summary> /// <summary>

View File

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

View File

@ -70,6 +70,23 @@ namespace ILSOS_CCW_H52A.Sequences.ProcessObjects
cellScheduler = new CellInfoScheduler(); 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) private void OnCellReceived(string lotId, string cellId)
{ {
lotId = lotId.Replace("\0", "").Trim(); lotId = lotId.Replace("\0", "").Trim();

View File

@ -308,6 +308,7 @@ namespace ILSOS_CCW_H52A.Sequences
_StreamLightLBAD = new StreamLightLBAD(); _StreamLightLBAD = new StreamLightLBAD();
_StreamLightLBAD._Client = context.GetStreamLightClient(); _StreamLightLBAD._Client = context.GetStreamLightClient();
_StreamLightLBAD._Sub = context.GetStreamLightSub(); _StreamLightLBAD._Sub = context.GetStreamLightSub();
ProcessManager.Instance.RegisterLbadStream(_StreamLightLBAD);
} }
_CellTracker = context.GetCellTracker(); _CellTracker = context.GetCellTracker();
_PLC = context.Devices[DeviceIds.PLCDevice] as PLCControllerCCWH52A; _PLC = context.Devices[DeviceIds.PLCDevice] as PLCControllerCCWH52A;
@ -1031,8 +1032,12 @@ 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) // 셀 식별 정보 + LBAD AVG는 스케줄러 슬롯에서 직접 읽음 (ReportSlot 또는 pendingSlot)
// ReportSlot 단계에서는 지그 1회전(~320ms) 경과 → LBAD MES Report(~100ms) 보장 수신
var report = _CellScheduler.ActiveReportSlot; var report = _CellScheduler.ActiveReportSlot;
string lbadAvg = report.LbadAvg > 0 ? report.LbadAvg.ToString("F3") : "0";
if (report.LbadAvg <= 0)
Log.W($"[LBAD] ReportSlot LbadAvg 미수신 >> CellId({report.CellId})");
// 레시피·장비 계측값은 Status에서 읽음 (TodayProduct/TotalProduct 포함) // 레시피·장비 계측값은 Status에서 읽음 (TodayProduct/TotalProduct 포함)
var wd = Status.GetProcessingWeldingData(); var wd = Status.GetProcessingWeldingData();
@ -1060,17 +1065,17 @@ namespace ILSOS_CCW_H52A.Sequences
equipName + "DATA009={12}", equipName + "DATA009={12}",
strDate, strTime, report.LotId, report.CellId, strDate, strTime, report.LotId, report.CellId,
report.InnerCellInfo.RecipeNo, report.InnerCellInfo.LaserPowerCore, report.InnerCellInfo.MarkSpeed, report.InnerCellInfo.FocalLength, report.InnerCellInfo.RecipeNo, report.InnerCellInfo.LaserPowerCore, report.InnerCellInfo.MarkSpeed, report.InnerCellInfo.FocalLength,
report.JigNo, report.WeldingSpanTime.TotalMilliseconds.ToString(), wd.LaserPowerByLBAD, report.JigNo, report.WeldingSpanTime.TotalMilliseconds.ToString(), lbadAvg,
wd.TodayProduct, wd.TotalProduct); wd.TodayProduct, wd.TotalProduct);
_CellTracker.SendEcsData(report.CellId, strPath, strData + ""); _CellTracker.SendEcsData(report.CellId, strPath, strData + "");
Log.I($"Report to CellTracker >> [LotID({report.LotId})] [CellID({report.CellId})]"); Log.I($"Report to CellTracker >> [LotID({report.LotId})] [CellID({report.CellId})] [LbadAvg({lbadAvg})]");
// 로컬 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, report.LotId, report.CellId, strDate, strTime, report.LotId, report.CellId,
report.InnerCellInfo.RecipeNo, report.InnerCellInfo.LaserPowerCore, report.InnerCellInfo.MarkSpeed, report.InnerCellInfo.FocalLength, report.InnerCellInfo.RecipeNo, report.InnerCellInfo.LaserPowerCore, report.InnerCellInfo.MarkSpeed, report.InnerCellInfo.FocalLength,
report.JigNo, report.WeldingSpanTime.TotalMilliseconds.ToString(), wd.LaserPowerByLBAD, report.JigNo, report.WeldingSpanTime.TotalMilliseconds.ToString(), lbadAvg,
wd.TodayProduct, wd.TotalProduct); wd.TodayProduct, wd.TotalProduct);
WeldingCommon.Utils.ExcelManagementUnit.AppendStringInCSV(strPath, strItem, strCsvData); WeldingCommon.Utils.ExcelManagementUnit.AppendStringInCSV(strPath, strItem, strCsvData);