Compare commits
8 Commits
cb935e251e
...
d19560b504
| Author | SHA1 | Date | |
|---|---|---|---|
| d19560b504 | |||
| d31020df72 | |||
| f15800250e | |||
| 36fe130233 | |||
| d581c24f6b | |||
| 62a1127107 | |||
| 3edbc28d3c | |||
| 45c1de6cc8 |
@ -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;
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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")]
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user