diff --git a/.vs/marking_gui.slnx/v18/.suo b/.vs/marking_gui.slnx/v18/.suo
index 67c01aa..fb1a699 100644
Binary files a/.vs/marking_gui.slnx/v18/.suo and b/.vs/marking_gui.slnx/v18/.suo differ
diff --git a/.vs/marking_gui.slnx/v18/DocumentLayout.backup.json b/.vs/marking_gui.slnx/v18/DocumentLayout.backup.json
index f151ae1..22dca5d 100644
--- a/.vs/marking_gui.slnx/v18/DocumentLayout.backup.json
+++ b/.vs/marking_gui.slnx/v18/DocumentLayout.backup.json
@@ -22,12 +22,16 @@
"DocumentGroups": [
{
"DockedWidth": 200,
- "SelectedChildIndex": 3,
+ "SelectedChildIndex": 4,
"Children": [
{
"$type": "Bookmark",
"Name": "ST:0:0:{aa2115a1-9712-457b-9047-dbb71ca2cdd2}"
},
+ {
+ "$type": "Bookmark",
+ "Name": "ST:0:0:{cce594b6-0c39-4442-ba28-10c64ac7e89f}"
+ },
{
"$type": "Document",
"DocumentIndex": 1,
@@ -37,8 +41,7 @@
"ToolTip": "C:\\Users\\COMPUTER1\\Desktop\\mobi\\marking_gui\\marking_gui\\QrVerificationWindow.xaml",
"RelativeToolTip": "marking_gui\\QrVerificationWindow.xaml",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.003549|",
- "WhenOpened": "2026-07-29T04:12:09.69Z",
- "EditorCaption": ""
+ "WhenOpened": "2026-07-29T04:12:09.69Z"
},
{
"$type": "Document",
@@ -49,8 +52,7 @@
"ToolTip": "C:\\Users\\COMPUTER1\\Desktop\\mobi\\marking_gui\\marking_gui\\SettingsWindow.xaml",
"RelativeToolTip": "marking_gui\\SettingsWindow.xaml",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.003549|",
- "WhenOpened": "2026-07-29T04:12:06.926Z",
- "EditorCaption": ""
+ "WhenOpened": "2026-07-29T04:12:06.926Z"
},
{
"$type": "Document",
diff --git a/.vs/marking_gui.slnx/v18/DocumentLayout.json b/.vs/marking_gui.slnx/v18/DocumentLayout.json
index f151ae1..22dca5d 100644
--- a/.vs/marking_gui.slnx/v18/DocumentLayout.json
+++ b/.vs/marking_gui.slnx/v18/DocumentLayout.json
@@ -22,12 +22,16 @@
"DocumentGroups": [
{
"DockedWidth": 200,
- "SelectedChildIndex": 3,
+ "SelectedChildIndex": 4,
"Children": [
{
"$type": "Bookmark",
"Name": "ST:0:0:{aa2115a1-9712-457b-9047-dbb71ca2cdd2}"
},
+ {
+ "$type": "Bookmark",
+ "Name": "ST:0:0:{cce594b6-0c39-4442-ba28-10c64ac7e89f}"
+ },
{
"$type": "Document",
"DocumentIndex": 1,
@@ -37,8 +41,7 @@
"ToolTip": "C:\\Users\\COMPUTER1\\Desktop\\mobi\\marking_gui\\marking_gui\\QrVerificationWindow.xaml",
"RelativeToolTip": "marking_gui\\QrVerificationWindow.xaml",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.003549|",
- "WhenOpened": "2026-07-29T04:12:09.69Z",
- "EditorCaption": ""
+ "WhenOpened": "2026-07-29T04:12:09.69Z"
},
{
"$type": "Document",
@@ -49,8 +52,7 @@
"ToolTip": "C:\\Users\\COMPUTER1\\Desktop\\mobi\\marking_gui\\marking_gui\\SettingsWindow.xaml",
"RelativeToolTip": "marking_gui\\SettingsWindow.xaml",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.003549|",
- "WhenOpened": "2026-07-29T04:12:06.926Z",
- "EditorCaption": ""
+ "WhenOpened": "2026-07-29T04:12:06.926Z"
},
{
"$type": "Document",
diff --git a/marking_gui/MainWindow.xaml b/marking_gui/MainWindow.xaml
index d4eb44c..a445187 100644
--- a/marking_gui/MainWindow.xaml
+++ b/marking_gui/MainWindow.xaml
@@ -449,7 +449,7 @@
-
+
diff --git a/marking_gui/MainWindow.xaml.cs b/marking_gui/MainWindow.xaml.cs
index 0d98f95..eb9bd08 100644
--- a/marking_gui/MainWindow.xaml.cs
+++ b/marking_gui/MainWindow.xaml.cs
@@ -355,7 +355,7 @@ namespace marking_gui
private void UpdateRangeGuides()
{
- txtVoltageRangeGuide.Text = string.Format("기준범위: {0:F1}V ~ {1:F1}V", _limits.MinVoltage, _limits.MaxVoltage);
+ txtVoltageRangeGuide.Text = string.Format("기준범위: {0:F2}V ~ {1:F2}V", _limits.MinVoltage, _limits.MaxVoltage);
txtCurrentRangeGuide.Text = string.Format("기준범위: {0:F2}mA ~ {1:F2}mA", _limits.MinCurrent, _limits.MaxCurrent);
txtResistanceRangeGuide.Text = string.Format("기준범위: {0:F3}kΩ ~ {1:F3}kΩ", _limits.MinResistance, _limits.MaxResistance);
}
@@ -1363,9 +1363,6 @@ namespace marking_gui
string lotNo = string.IsNullOrWhiteSpace(_loginInfo.LotNo) ? "00" : _loginInfo.LotNo.Trim();
string markedSerial = string.Format("{0}{1:D4}", lotNo, seq);
- string drawingNo = _markingConfig.DrawingNo;
- string hkmcNo = _markingConfig.HkmcNo;
- string qrString = string.Format("{0};{1};{2};{3};", serial, drawingNo, hkmcNo, markedSerial);
// 마킹 스캔 요청 데이터 빌드
var req = new MarkingRequest
@@ -1374,12 +1371,12 @@ namespace marking_gui
IcSerial = serial,
LotNo = lotNo,
ProductNo = _loginInfo.Model,
- DrawingNo = drawingNo,
- HkmcNo = hkmcNo,
+ DrawingNo = string.Empty,
+ HkmcNo = string.Empty,
EzdFileName = _currentEzdFile,
LineCode = _loginInfo.LineNo,
ShiftCode = "A", // 기본 1조
- QrString = qrString
+ QrString = string.Empty
};
MarkingResult markingRes;
@@ -1387,12 +1384,19 @@ namespace marking_gui
{
// 오프라인 모드 시뮬레이션
await System.Threading.Tasks.Task.Delay(500);
+ string offDrawingNo = _markingConfig.GetDrawingNoForEzd(_currentEzdFile);
+ string offHkmcNo = _markingConfig.GetHkmcNoForEzd(_currentEzdFile);
+ string offQr = string.Format("{0};{1};{2};{3};", serial, offDrawingNo, offHkmcNo, markedSerial);
+
markingRes = new MarkingResult
{
Success = true,
ErrorCode = 0,
ErrorMessage = "오프라인 모드 각인 완료 모사",
- MarkedAt = DateTime.Now
+ MarkedAt = DateTime.Now,
+ DrawingNo = offDrawingNo,
+ HkmcNo = offHkmcNo,
+ QrString = offQr
};
}
else
@@ -1403,13 +1407,17 @@ namespace marking_gui
// UI 스레드로 안전하게 컴백
await Dispatcher.InvokeAsync(async () =>
{
+ string ezdDrawingNo = markingRes.DrawingNo;
+ string ezdHkmcNo = markingRes.HkmcNo;
+ string ezdQrString = markingRes.QrString;
+
if (txtMarkedSerial != null)
{
txtMarkedSerial.Text = markedSerial;
}
if (txtMarkingQr != null)
{
- txtMarkingQr.Text = qrString;
+ txtMarkingQr.Text = string.IsNullOrWhiteSpace(ezdQrString) ? "---" : ezdQrString;
}
bool finalMarkingPass = markingRes.Success;
@@ -1418,10 +1426,10 @@ namespace marking_gui
{
if (txtMarkingStatus != null) txtMarkingStatus.Text = "마킹 완료 (QR 검증 대기)";
if (borderMarkingStatus != null) borderMarkingStatus.Background = new SolidColorBrush(Color.FromRgb(245, 158, 11)); // Amber
- LoggerService.Info($"[마킹 성공] S/N: {serial}, 각인번호: {markedSerial} -> QR 검증 팝업 오픈");
+ LoggerService.Info($"[마킹 성공] S/N: {serial}, 각인번호: {markedSerial}, 도면품번: {ezdDrawingNo}, HKMC품번: {ezdHkmcNo} -> QR 검증 팝업 오픈");
// DB 저장 전 레이저 각인 QR 코드 스캔 검증 팝업 오픈 (시리얼 스캐너 서비스 인계)
- var qrVerifyWin = new QrVerificationWindow(qrString, _barcodeScanner)
+ var qrVerifyWin = new QrVerificationWindow(ezdQrString, _barcodeScanner)
{
Owner = this
};
@@ -1439,8 +1447,8 @@ namespace marking_gui
if (borderMarkingStatus != null) borderMarkingStatus.Background = new SolidColorBrush(Color.FromRgb(16, 185, 129)); // Green
LoggerService.Info($"[QR 검증 성공] S/N: {serial}, 각인번호: {markedSerial} -> DB 저장 진행");
- // QR 검증 통과 시 최종 DB 저장 및 완료 처리
- await SaveAndFinalizeAsync(serial, finalVoltage, finalCurrent, finalResistance, true, "OK", markedSerial, bypassPrevSteps);
+ // QR 검증 통과 시 최종 DB 저장 및 완료 처리 (EZD 추출 실시간 품번 포함)
+ await SaveAndFinalizeAsync(serial, finalVoltage, finalCurrent, finalResistance, true, "OK", markedSerial, ezdDrawingNo, ezdHkmcNo, bypassPrevSteps);
}
else
{
@@ -1448,8 +1456,8 @@ namespace marking_gui
if (borderMarkingStatus != null) borderMarkingStatus.Background = new SolidColorBrush(Color.FromRgb(239, 68, 68)); // Red
LoggerService.Warn($"[QR 검증 실패] S/N: {serial}, 각인번호: {markedSerial} -> DB NG 저장 진행");
- // 마킹 성공 후 QR 검증 불일치/취소 시 NG로 DB 저장
- await SaveAndFinalizeAsync(serial, finalVoltage, finalCurrent, finalResistance, false, "NG", markedSerial, bypassPrevSteps);
+ // 마킹 성공 후 QR 검증 불일치/취소 시 NG로 DB 저장 (EZD 추출 실시간 품번 포함)
+ await SaveAndFinalizeAsync(serial, finalVoltage, finalCurrent, finalResistance, false, "NG", markedSerial, ezdDrawingNo, ezdHkmcNo, bypassPrevSteps);
}
}
else
@@ -1526,8 +1534,12 @@ namespace marking_gui
}
else
{
- // 불합격인 경우 즉시 DB 저장 및 복구
- await SaveAndFinalizeAsync(serial, finalVoltage, finalCurrent, finalResistance, isFinalPass, "NG", "", bypassPrevSteps);
+ // 불합격인 경우에도 Lot No 및 예정 각인번호/품번 정보를 포함하여 DB 저장 및 복구
+ string markedSerial = GetCurrentMarkedSerial();
+ string drawingNo = _markingConfig.GetDrawingNoForEzd(_currentEzdFile);
+ string hkmcNo = _markingConfig.GetHkmcNoForEzd(_currentEzdFile);
+
+ await SaveAndFinalizeAsync(serial, finalVoltage, finalCurrent, finalResistance, isFinalPass, "NG", markedSerial, drawingNo, hkmcNo, bypassPrevSteps: bypassPrevSteps);
}
}
}
@@ -1586,7 +1598,38 @@ namespace marking_gui
}
}
- private async Task SaveAndFinalizeAsync(string serial, double voltage, double current, double resistance, bool isFinalPass, string result, string markedSerial, bool bypassPrevSteps = false)
+ private string GetCurrentMarkedSerial()
+ {
+ int seq = 1;
+ if (txtNextSequence != null)
+ {
+ if (Dispatcher.CheckAccess())
+ {
+ string rawText = txtNextSequence.Text;
+ string digitsOnly = System.Text.RegularExpressions.Regex.Replace(rawText ?? "", @"[^\d]", "");
+ if (int.TryParse(digitsOnly, out int parsedSeq) && parsedSeq > 0)
+ {
+ seq = parsedSeq;
+ }
+ }
+ else
+ {
+ Dispatcher.Invoke(() =>
+ {
+ string rawText = txtNextSequence.Text;
+ string digitsOnly = System.Text.RegularExpressions.Regex.Replace(rawText ?? "", @"[^\d]", "");
+ if (int.TryParse(digitsOnly, out int parsedSeq) && parsedSeq > 0)
+ {
+ seq = parsedSeq;
+ }
+ });
+ }
+ }
+ string lotNo = string.IsNullOrWhiteSpace(_loginInfo.LotNo) ? "00" : _loginInfo.LotNo.Trim();
+ return string.Format("{0}{1:D4}", lotNo, seq);
+ }
+
+ private async Task SaveAndFinalizeAsync(string serial, double voltage, double current, double resistance, bool isFinalPass, string result, string markedSerial, string drawingNo = "", string hkmcNo = "", bool bypassPrevSteps = false)
{
// 종료 시간 기록 및 UI 갱신
_testEndTime = DateTime.Now;
@@ -1608,6 +1651,9 @@ namespace marking_gui
_currentProduct.PcbBarcode = serial;
}
+ string finalDrawingNo = string.IsNullOrWhiteSpace(drawingNo) ? _markingConfig.DrawingNo : drawingNo;
+ string finalHkmcNo = string.IsNullOrWhiteSpace(hkmcNo) ? _markingConfig.HkmcNo : hkmcNo;
+
try
{
// 데이터베이스 저장 (비동기 스레드 격리로 UI 프리징 방지)
@@ -1631,8 +1677,8 @@ namespace marking_gui
_testEndTime,
markedSerial,
pcbBarcodeToSave, // 강제검사 시 시리얼 번호 사용
- _markingConfig.DrawingNo,
- _markingConfig.HkmcNo);
+ finalDrawingNo,
+ finalHkmcNo);
});
if (dbResult)
@@ -1745,11 +1791,18 @@ namespace marking_gui
{
borderNormalStatus.Visibility = System.Windows.Visibility.Collapsed;
}
- }
- // 컨트롤 원상 복구 (재검사를 위해)
- txtSerial.IsEnabled = true;
- SetStartButtonReady();
+ _dbWasOffline = true;
+ SetStartButtonConnecting("⚠️ DB 연결 필요");
+ }
+ finally
+ {
+ txtSerial.IsEnabled = true;
+ if (!_dbWasOffline)
+ {
+ SetStartButtonReady();
+ }
+ }
}
// 화면 렌더링 완료 시 최초 로그인 팝업 구동
@@ -1835,6 +1888,11 @@ namespace marking_gui
{
// DB 재연결 감시 루프 중단
_dbWatcherCts?.Cancel();
+ _dbWatcherCts?.Dispose();
+
+ // 윈도우 타이머 정지 (WPF DispatcherTimer GC 메모리 누수 방지)
+ _clockTimer?.Stop();
+ _clockTimer = null;
// LMC 마킹 기계 무선/시리얼 연결 핸들 안전 해제 (재시작 시 LMC 장치 락 방지)
_markingService?.Shutdown();
@@ -1882,7 +1940,7 @@ namespace marking_gui
if (!_isInspectionRunning)
{
- Dispatcher.Invoke(() =>
+ await Dispatcher.InvokeAsync(() =>
{
SetStartButtonConnecting("⚠️ DB 연결 필요");
@@ -1906,7 +1964,7 @@ namespace marking_gui
if (!_isInspectionRunning)
{
- Dispatcher.Invoke(() =>
+ await Dispatcher.InvokeAsync(() =>
{
if (txtSystemStatus != null)
{
@@ -1921,11 +1979,6 @@ namespace marking_gui
await Dispatcher.InvokeAsync(() => RefreshNextSequence());
}
}
- else if (isNowConnected && !_dbWasOffline && !_isInspectionRunning)
- {
- // DB 연결 유지 상태에서도 DB 레코드 직접 변경/삭제 시 각인 순번 실시간 반영 (조용히 UI 갱신)
- await Dispatcher.InvokeAsync(() => RefreshNextSequenceSilently());
- }
}
catch (Exception ex)
{
@@ -2101,7 +2154,18 @@ namespace marking_gui
{
_markingConfig.SaveLastSelectedEzdFile(fullPath);
}
- LoggerService.Info($"EZD 도안 파일 선택 완료: {fileName} (절대경로: {fullPath})");
+
+ string drawingNo = string.Empty;
+ string hkmcNo = string.Empty;
+ if (_markingService is EzdMarkingService ezdService)
+ {
+ drawingNo = ezdService.GetTextFromEzd(_markingConfig.DrawingNoTag, fullPath);
+ hkmcNo = ezdService.GetTextFromEzd(_markingConfig.HkmcNoTag, fullPath);
+ }
+ if (string.IsNullOrWhiteSpace(drawingNo)) drawingNo = _markingConfig.GetDrawingNoForEzd(fullPath);
+ if (string.IsNullOrWhiteSpace(hkmcNo)) hkmcNo = _markingConfig.GetHkmcNoForEzd(fullPath);
+
+ LoggerService.Info($"EZD 도안 파일 선택 완료: {fileName} (절대경로: {fullPath}, 도면품번: {drawingNo}, HKMC품번: {hkmcNo})");
}
private void InitializeBarcodeScanner()
diff --git a/marking_gui/Manual/.obsidian/workspace.json b/marking_gui/Manual/.obsidian/workspace.json
index 3f3bab1..e323cef 100644
--- a/marking_gui/Manual/.obsidian/workspace.json
+++ b/marking_gui/Manual/.obsidian/workspace.json
@@ -41,7 +41,9 @@
"type": "file-explorer",
"state": {
"sortOrder": "alphabetical",
- "autoReveal": false
+ "autoReveal": false,
+ "showSearch": false,
+ "searchQuery": ""
},
"icon": "lucide-folder-closed",
"title": "파일 탐색기"
diff --git a/marking_gui/Manual/07_시험_프로세스.md b/marking_gui/Manual/07_시험_프로세스.md
index 335910b..fa876ef 100644
--- a/marking_gui/Manual/07_시험_프로세스.md
+++ b/marking_gui/Manual/07_시험_프로세스.md
@@ -67,8 +67,8 @@ graph TD
* 이 경우 레이저 마킹 장비 구동은 자동으로 제외(Skip)되며, `NG` 내역으로 DB에 기록됩니다.
2. **모든 항목이 합격인 경우 (OK)**:
* 모든 수치가 기준치 내에 들어오면 최종 합격(**`OK`**) 판정이 내려집니다.
- * 합격 판정과 동시에 레이저 마킹기에 각인 데이터가 전달되어 QR 코드 및 제품 일련번호 각인이 자동으로 실행됩니다.
- * 마킹 완료 후 QR 스캔 검증 팝업이 활성화되어 각인 상태를 교차 검증합니다.
+ * 합격 판정과 동시에 선택된 EZD 도안 파일(.ezd)에서 도면품번 및 HKMC품번을 실시간으로 추출하여 각인 및 QR 코드가 자동으로 조합되어 레이저 마킹이 실행됩니다.
+ * 마킹 완료 후 EZD 파싱 품번 기반의 QR 스캔 검증 팝업이 활성화되어 각인 상태를 교차 검증하고, 검증 완료 시 해당 품번 데이터로 DB 저장을 완료합니다.
---
@@ -76,12 +76,14 @@ graph TD
레이저 마킹이 완료되면 검증을 위해 **QR 검증 팝업 창**이 화면에 활성화되며 바코드 스캐너 또는 가상 키보드로 각인된 QR 코드를 읽어 들입니다.
-1. **QR 스캔 검증 성공 (즉시 자동 닫힘)**
+1. **QR 스캔 검증 성공 (즉시 자동 닫힘 및 DB OK 적재)**
- 스캔된 QR 코드가 예상 QR 코드와 정확히 일치하면 팝업 창이 지연 없이 **즉시 자동으로 닫히며** DB에 `OK` 상태로 최종 저장을 완료하고 대기 상태로 복구됩니다.
-2. **QR 스캔 검증 실패 (NG 판정 및 설명 표출)**
- - 스캔된 QR 코드가 불일치하거나 검증을 취소하는 경우, 작업자가 [확인] 버튼을 누르면 팝업 창이 닫힙니다.
- - 팝업이 닫힘과 동시에 메인 화면의 대형 판정 카드가 **`OK`**에서 **`NG`** (빨간색 테두리 및 배경)로 즉시 전환됩니다.
+2. **QR 스캔 검증 실패 (팝업 유지 및 연속 재스캔 지원)**
+ - 스캔된 QR 코드가 불일치(대조 실패)할 경우, 팝업 창이 **자동으로 닫히지 않고 열린 상태를 유지**하며 "QR 바코드 불일치" 경고 상태를 표출합니다.
+ - 팝업이 열려 있는 동안 바코드/QR 스캐너 또는 키보드 입력을 통해 올바른 QR 코드로 **연속 재스캔**할 수 있으며, 재스캔에서 대조 성공 시 자동으로 통과(닫힘) 및 DB `OK` 저장 처리됩니다.
+ - 대조 불일치 상태에서 작업자가 **[확인]** 또는 **[취소]** (ESC 키) 버튼을 누르면 팝업 창이 닫히며, 판정 결과 **`NG`**로 데이터베이스 저장을 완료합니다.
+ - 팝업이 닫힘과 동시에 메인 화면의 대형 판정 카드가 **`OK`**에서 **`NG`** (빨간색 테두리 및 배경)로 전환됩니다.
- 공정 진행 상태 및 시스템 상태 바에 `"마킹 완료 / 각인 QR 검증 NG"`, `"상태: 검사 불합격 (각인 QR 검증 실패)"`, `"각인 QR 검증 실패 (DB NG 저장 완료)"` 등 구체적인 원인 설명이 표시되며, DB에 최종 `NG` 결과로 기록됩니다.
diff --git a/marking_gui/Manual/Manual_Merged.md b/marking_gui/Manual/Manual_Merged.md
index b776e98..dbf36f9 100644
--- a/marking_gui/Manual/Manual_Merged.md
+++ b/marking_gui/Manual/Manual_Merged.md
@@ -658,7 +658,7 @@ Channel=1 ; 통신 채널 번호 (1 또는 2)
* **세부 정보 그리드**:
- **검사 시간 정보**: 전체 시퀀스 기동 시작 시간 및 종료 시간 표출
- **일련번호 정보**: `IC S/N`, **`Marking QR :`** (마킹 생성 QR 값), **`Scan QR :`** (스캐너 실측 QR 값), `각인 번호` 4개 항목을 수직 배열로 표출
- - **최종 판정결과**: 최종 **PASS/OK** (초록색) / **FAIL/NG** (빨간색) 대형 배지 표출 (성능 합격 후 마킹 QR 검증 실패 시에도 **NG**로 자동 반영됨)
+ - **최종 판정결과**: 최종 **`OK`** (초록색) / **`NG`** (빨간색) 대형 배지 표출 (성능 합격 후 마킹 QR 검증 실패 시에도 **`NG`**로 자동 반영됨)
- **시스템 상태**: 통신 및 디바이스 가동 상태 또는 에러/검증 실패 사유 메시지 배너 표출
---
@@ -840,8 +840,8 @@ graph TD
H --> I[12~14단계: 전압 / 저항 / 전류 실측]
I --> J[15단계: 테스트 주전원 안전 차단]
J --> K{최종 규격 비교 판정}
- K -- 1개 이상 이탈 FAIL --> L[대형 FAIL 표시 & 마킹 제외 & DB 불합격 적재]
- K -- 모두 통과 PASS --> M[대형 PASS 표시 & DB 합격 적재 & LMC1 레이저 QR 각인]
+ K -- 1개 이상 이탈 NG --> L[대형 NG 표시 & 마킹 제외 & DB NG 적재]
+ K -- 모두 통과 OK --> M[대형 OK 표시 & DB OK 적재 & LMC1 레이저 QR 각인]
L --> Z
M --> Z
```
@@ -880,13 +880,13 @@ graph TD
계측이 완료되면 측정된 전압(V), 저항(kΩ), 전류(mA) 값을 설정한 기준 규격값(SPEC)과 비교하여 최종 판정을 내립니다.
-1. **기준 규격을 벗어난 경우 (FAIL)**:
- * 전압, 전류, 저항 중 하나라도 기준을 벗어나면 최종 불합격(FAIL) 판정이 내려집니다.
- * 이 경우 레이저 마킹 장비 구동은 자동으로 제외(Skip)되며, 불합격 내역으로 DB에 기록됩니다.
-2. **모든 항목이 합격인 경우 (PASS)**:
- * 모든 수치가 기준치 내에 들어오면 최종 합격(PASS) 판정이 내려집니다.
- * 합격 판정과 동시에 레이저 마킹기에 각인 데이터가 전달되어 QR 코드 및 제품 일련번호 각인이 자동으로 실행됩니다.
- * 마킹 완료 후 QR 스캔 검증 팝업이 활성화되어 각인 상태를 교차 검증합니다.
+1. **기준 규격을 벗어난 경우 (NG)**:
+ * 전압, 전류, 저항 중 하나라도 기준을 벗어나면 최종 불합격(**`NG`**) 판정이 내려집니다.
+ * 이 경우 레이저 마킹 장비 구동은 자동으로 제외(Skip)되며, `NG` 내역으로 DB에 기록됩니다.
+2. **모든 항목이 합격인 경우 (OK)**:
+ * 모든 수치가 기준치 내에 들어오면 최종 합격(**`OK`**) 판정이 내려집니다.
+ * 합격 판정과 동시에 선택된 EZD 도안 파일(.ezd)에서 도면품번 및 HKMC품번을 실시간으로 추출하여 각인 및 QR 코드가 자동으로 조합되어 레이저 마킹이 실행됩니다.
+ * 마킹 완료 후 EZD 파싱 품번 기반의 QR 스캔 검증 팝업이 활성화되어 각인 상태를 교차 검증하고, 검증 완료 시 해당 품번 데이터로 DB 저장을 완료합니다.
---
@@ -894,12 +894,14 @@ graph TD
레이저 마킹이 완료되면 검증을 위해 **QR 검증 팝업 창**이 화면에 활성화되며 바코드 스캐너 또는 가상 키보드로 각인된 QR 코드를 읽어 들입니다.
-1. **QR 스캔 검증 성공 (즉시 자동 닫힘)**
+1. **QR 스캔 검증 성공 (즉시 자동 닫힘 및 DB OK 적재)**
- 스캔된 QR 코드가 예상 QR 코드와 정확히 일치하면 팝업 창이 지연 없이 **즉시 자동으로 닫히며** DB에 `OK` 상태로 최종 저장을 완료하고 대기 상태로 복구됩니다.
-2. **QR 스캔 검증 실패 (NG 판정 및 설명 표출)**
- - 스캔된 QR 코드가 불일치하거나 검증을 취소하는 경우, 작업자가 [확인] 버튼을 누르면 팝업 창이 닫힙니다.
- - 팝업이 닫힘과 동시에 메인 화면의 대형 판정 카드가 **`OK`**에서 **`NG`** (빨간색 테두리 및 배경)로 즉시 전환됩니다.
+2. **QR 스캔 검증 실패 (팝업 유지 및 연속 재스캔 지원)**
+ - 스캔된 QR 코드가 불일치(대조 실패)할 경우, 팝업 창이 **자동으로 닫히지 않고 열린 상태를 유지**하며 "QR 바코드 불일치" 경고 상태를 표출합니다.
+ - 팝업이 열려 있는 동안 바코드/QR 스캐너 또는 키보드 입력을 통해 올바른 QR 코드로 **연속 재스캔**할 수 있으며, 재스캔에서 대조 성공 시 자동으로 통과(닫힘) 및 DB `OK` 저장 처리됩니다.
+ - 대조 불일치 상태에서 작업자가 **[확인]** 또는 **[취소]** (ESC 키) 버튼을 누르면 팝업 창이 닫히며, 판정 결과 **`NG`**로 데이터베이스 저장을 완료합니다.
+ - 팝업이 닫힘과 동시에 메인 화면의 대형 판정 카드가 **`OK`**에서 **`NG`** (빨간색 테두리 및 배경)로 전환됩니다.
- 공정 진행 상태 및 시스템 상태 바에 `"마킹 완료 / 각인 QR 검증 NG"`, `"상태: 검사 불합격 (각인 QR 검증 실패)"`, `"각인 QR 검증 실패 (DB NG 저장 완료)"` 등 구체적인 원인 설명이 표시되며, DB에 최종 `NG` 결과로 기록됩니다.
diff --git a/marking_gui/Manual/Manual_for_pdf.html b/marking_gui/Manual/Manual_for_pdf.html
index e40fbdf..5a9e004 100644
--- a/marking_gui/Manual/Manual_for_pdf.html
+++ b/marking_gui/Manual/Manual_for_pdf.html
@@ -405,9 +405,9 @@ a:hover {
프로그램 실행 및 로그인
바탕화면의 Marking System (marking_gui) 프로그램을 실행한 뒤, 작업자 계정 정보를 입력하여 로그인합니다. (데이터베이스 점검 중인 경우 설정 파일을 통해 오프라인 모드로의 진입이 가능합니다.)
통신 상태 확인 및 채널 설정
-
우측 상단의 통신 설정 메뉴에서 센서가 올바른 포트와 채널에 연결되었는지 확인합니다.
+
화면 하단 바의 포트 설정 버튼을 클릭하여 센서가 올바른 포트와 채널에 연결되었는지 확인합니다.
검사 규격값 검증
-
우측 상단의 시험 설정 메뉴를 열고, 전압/전류/저항 검사 기준치와 각인 템플릿 도면 파일의 경로가 올바른지 점검합니다.
+
화면 하단 바의 파라미터 설정 버튼을 클릭하여 전압/전류/저항 검사 기준치와 각인 템플릿 도면 파일의 경로가 올바른지 점검합니다.
성능 검사 및 마킹 시작
안착 지그에 제품을 바르게 장착한 뒤 메인 화면 하단의 검사 시작 (START) 버튼을 눌러 계측 및 마킹을 수행합니다.
@@ -640,7 +640,7 @@ a:hover {
1) Setting.ini (주요 변경 가능 설정)
현장 네트워크 IP, 도안 파일 경로 및 검사 규격(임계치) 변경 시 수정하는 주요 설정 항목입니다.
[Settings]
-EzdFolderPath=c:\EzCad\EasyCfg ; EZD 도안 파일 기본 탐색/저장 폴더 경로
+EzdFolderPath=c:\EzCad ; EZD 도안 파일 기본 탐색/저장 폴더 경로
test=false ; 테스트 마킹 버튼 및 개발 모드 노출 여부 (true/false)
[Equipment]
@@ -738,9 +738,9 @@ Channel=1 ; 통신 채널 번호 (1 또는 2)
3.2.3 [카드 3] 측정 데이터 수치 카드 (우측 상단)
실제 계측기를 통해 수집된 3대 전기 파라미터 수치와 설정된 규격 범위를 표시하는 3열 구조의 카드입니다.
-* 측정 전압 (Voltage): DMM 계측 장비가 측정한 실측 전압 수치 (V 단위, 기본 기준범위: 0.0V ~ 0.5V)
-* 측정 전류 (Current): Power Supply 전원공급 장치가 측정한 소모 전류 수치 (mA 단위, 기본 기준범위: 8.00mA ~ 10.00mA)
-* 측정 저항 (Resistance): DMM 측정 장비가 측정한 전기적 저항 수치 (kΩ 단위, 기본 기준범위: 0.400kΩ ~ 0.800kΩ)
+* 측정 전압 (Voltage): DMM 계측 장비가 측정한 실측 전압 수치 (V 단위, 기본 기준범위: 1.00V ~ 6.00V)
+* 측정 전류 (Current): Power Supply 전원공급 장치가 측정한 소모 전류 수치 (mA 단위, 기본 기준범위: 5.00mA ~ 10.00mA)
+* 측정 저항 (Resistance): DMM 측정 장비가 측정한 전기적 저항 수치 (kΩ 단위, 기본 기준범위: 8.000kΩ ~ 13.000kΩ)
* 알림: 각 수치 카드 하단에는 설정 파일(Setting.ini)에서 지정한 임계 규격 범위가 표시되며, 실측값이 범위 내에 들어오면 카드 테두리가 초록색(PASS)으로, 범위를 벗어나면 빨간색(FAIL)으로 자동 전환됩니다.
3.2.4 [카드 4] 최종 판정 및 종합 상태 영역 (우측 하단)
@@ -749,7 +749,7 @@ Channel=1 ; 통신 채널 번호 (1 또는 2)
* 세부 정보 그리드:
- 검사 시간 정보: 전체 시퀀스 기동 시작 시간 및 종료 시간 표출
- 일련번호 정보: IC S/N, Marking QR : (마킹 생성 QR 값), Scan QR : (스캐너 실측 QR 값), 각인 번호 4개 항목을 수직 배열로 표출
- - 최종 판정결과: 최종 PASS/OK (초록색) / FAIL/NG (빨간색) 대형 배지 표출 (성능 합격 후 마킹 QR 검증 실패 시에도 NG로 자동 반영됨)
+ - 최종 판정결과: 최종 OK (초록색) / NG (빨간색) 대형 배지 표출 (성능 합격 후 마킹 QR 검증 실패 시에도 NG로 자동 반영됨)
- 시스템 상태: 통신 및 디바이스 가동 상태 또는 에러/검증 실패 사유 메시지 배너 표출
@@ -761,7 +761,7 @@ Channel=1 ; 통신 채널 번호 (1 또는 2)
포트 설정: 센서 통신 보드의 시리얼 COM 포트 및 채널 설정 창을 호출합니다.
강제 검사 (스킵): 이전 공정 이력이 없어도 검증을 건너뛰고 계측 및 마킹을 수행합니다.
테스트 마킹: 계측 과정 없이 레이저 각인 장비 동작을 테스트합니다.
-
참고: 강제 검사 (스킵) 및 테스트 마킹 버튼은 설정 파일(Setting.ini)에서 TestMode=true로 활성화된 경우에만 화면에 노출됩니다.
+
참고: 강제 검사 (스킵) 버튼은 설정 파일(Setting.ini)에서 test=true로 활성화된 경우에만 화면에 노출됩니다.
@@ -924,8 +924,8 @@ Channel=1 ; 통신 채널 번호 (1 또는 2)
H --> I[12~14단계: 전압 / 저항 / 전류 실측]
I --> J[15단계: 테스트 주전원 안전 차단]
J --> K{최종 규격 비교 판정}
- K -- 1개 이상 이탈 FAIL --> L[대형 FAIL 표시 & 마킹 제외 & DB 불합격 적재]
- K -- 모두 통과 PASS --> M[대형 PASS 표시 & DB 합격 적재 & LMC1 레이저 QR 각인]
+ K -- 1개 이상 이탈 NG --> L[대형 NG 표시 & 마킹 제외 & DB NG 적재]
+ K -- 모두 통과 OK --> M[대형 OK 표시 & DB OK 적재 & LMC1 레이저 QR 각인]
L --> Z
M --> Z
@@ -961,15 +961,15 @@ Channel=1 ; 통신 채널 번호 (1 또는 2)
7.3 최종 판정 및 마킹 실행 조건
계측이 완료되면 측정된 전압(V), 저항(kΩ), 전류(mA) 값을 설정한 기준 규격값(SPEC)과 비교하여 최종 판정을 내립니다.
-
기준 규격을 벗어난 경우 (FAIL):
-
전압, 전류, 저항 중 하나라도 기준을 벗어나면 최종 불합격(FAIL) 판정이 내려집니다.
-
이 경우 레이저 마킹 장비 구동은 자동으로 제외(Skip)되며, 불합격 내역으로 DB에 기록됩니다.
+
기준 규격을 벗어난 경우 (NG):
+
전압, 전류, 저항 중 하나라도 기준을 벗어나면 최종 불합격(NG) 판정이 내려집니다.
+
이 경우 레이저 마킹 장비 구동은 자동으로 제외(Skip)되며, NG 내역으로 DB에 기록됩니다.
-
모든 항목이 합격인 경우 (PASS):
-
모든 수치가 기준치 내에 들어오면 최종 합격(PASS) 판정이 내려집니다.
-
합격 판정과 동시에 레이저 마킹기에 각인 데이터가 전달되어 QR 코드 및 제품 일련번호 각인이 자동으로 실행됩니다.
-
마킹 완료 후 QR 스캔 검증 팝업이 활성화되어 각인 상태를 교차 검증합니다.
+
모든 항목이 합격인 경우 (OK):
+
모든 수치가 기준치 내에 들어오면 최종 합격(OK) 판정이 내려집니다.
+
합격 판정과 동시에 선택된 EZD 도안 파일(.ezd)에서 도면품번 및 HKMC품번을 실시간으로 추출하여 각인 및 QR 코드가 자동으로 조합되어 레이저 마킹이 실행됩니다.
+
마킹 완료 후 EZD 파싱 품번 기반의 QR 스캔 검증 팝업이 활성화되어 각인 상태를 교차 검증하고, 검증 완료 시 해당 품번 데이터로 DB 저장을 완료합니다.
@@ -977,15 +977,17 @@ Channel=1 ; 통신 채널 번호 (1 또는 2)
7.4 레이저 각인 QR 검증 팝업 및 결과 연동
레이저 마킹이 완료되면 검증을 위해 QR 검증 팝업 창이 화면에 활성화되며 바코드 스캐너 또는 가상 키보드로 각인된 QR 코드를 읽어 들입니다.
-
QR 스캔 검증 성공 (즉시 자동 닫힘)
+
QR 스캔 검증 성공 (즉시 자동 닫힘 및 DB OK 적재)
스캔된 QR 코드가 예상 QR 코드와 정확히 일치하면 팝업 창이 지연 없이 즉시 자동으로 닫히며 DB에 OK 상태로 최종 저장을 완료하고 대기 상태로 복구됩니다.
-
QR 스캔 검증 실패 (NG 판정 및 설명 표출)
+
QR 스캔 검증 실패 (팝업 유지 및 연속 재스캔 지원)
-
스캔된 QR 코드가 불일치하거나 검증을 취소하는 경우, 작업자가 [확인] 버튼을 누르면 팝업 창이 닫힙니다.
-
팝업이 닫힘과 동시에 메인 화면의 대형 판정 카드가 OK에서 NG (빨간색 테두리 및 배경)로 즉시 전환됩니다.
+
스캔된 QR 코드가 불일치(대조 실패)할 경우, 팝업 창이 자동으로 닫히지 않고 열린 상태를 유지하며 "QR 바코드 불일치" 경고 상태를 표출합니다.
+
팝업이 열려 있는 동안 바코드/QR 스캐너 또는 키보드 입력을 통해 올바른 QR 코드로 연속 재스캔할 수 있으며, 재스캔에서 대조 성공 시 자동으로 통과(닫힘) 및 DB OK 저장 처리됩니다.
+
대조 불일치 상태에서 작업자가 [확인] 또는 [취소] (ESC 키) 버튼을 누르면 팝업 창이 닫히며, 판정 결과 NG로 데이터베이스 저장을 완료합니다.
+
팝업이 닫힘과 동시에 메인 화면의 대형 판정 카드가 OK에서 NG (빨간색 테두리 및 배경)로 전환됩니다.
공정 진행 상태 및 시스템 상태 바에 "마킹 완료 / 각인 QR 검증 NG", "상태: 검사 불합격 (각인 QR 검증 실패)", "각인 QR 검증 실패 (DB NG 저장 완료)" 등 구체적인 원인 설명이 표시되며, DB에 최종 NG 결과로 기록됩니다.
@@ -996,7 +998,7 @@ Channel=1 ; 통신 채널 번호 (1 또는 2)
본 제어 프로그램은 설비 가동 중에 발생하는 계측기와의 통신 상태, 측정값 정보 및 각종 예외 오류 상황을 로그 파일로 기록합니다.
8.1.1 로그 파일 보관 방법
-
보관 폴더: 프로그램 실행 파일 경로의 하위 Logs 폴더에 저장됩니다.
+
보관 폴더: 프로그램 설치 경로(C:\marking\) 하위 logs/ 폴더에 저장됩니다. (예: C:\marking\logs\2026-08-05.log)
파일명 규칙: 날짜별로 파일이 자동으로 생성되어 보관됩니다.
로그 중요도 분류:
정보 (INFO): 정상적인 검사 프로세스 시작, 계측기 연결 성공, 레이저 인쇄 성공 및 데이터베이스 저장 완료 기록.
@@ -1070,7 +1072,7 @@ Channel=1 ; 통신 채널 번호 (1 또는 2)
현상: 성능 검사는 합격(PASS)했으나, 레이저 장비로의 데이터 전송 단계에서 경고 에러가 발생함.
원인 및 해결:
-
도면 파일 점검: 시험 설정 화면에서 세팅해 놓은 작업 도면 파일이 실제 해당 경로에 존재하는지 점검하십시오.
+
도면 파일 점검: 메인 화면의 파일 선택 버튼을 통해 선택한 작업 도면 파일(.ezd)이 실제 해당 경로에 존재하는지 점검하십시오.
마킹기 전원/연결 점검: 레이저 마킹기의 전원이 켜져 있는지 확인하고, 제어 PC와 마킹기 간의 연결 케이블 상태를 확인하십시오.
@@ -1092,8 +1094,8 @@ Channel=1 ; 통신 채널 번호 (1 또는 2)
현상: 로그인 화면에서 [로그인] 버튼 클릭 시 "DB 연결 상태 또는 ID/PW를 확인해 주십시오." 경고 팝업이 발생하며 로그인되지 않음.
원인 및 해결:
-
네트워크/DB 서버 점검: 중앙 MSSQL 서버와의 통신이 단선되었거나 서버 전원이 꺼진 경우 타임아웃(1초)에 의해 경고가 표출됩니다. Database.ini 파일의 IP 설정 및 데이터베이스 서버 연결 상태를 점검하십시오.
-
계정 정보 확인: 입력한 사용자 ID 및 비밀번호가 DB 계정 테이블 및 LoginOptions.json 계정 목록에 올바르게 등록되어 있는지 재확인하십시오.
+
네트워크/DB 서버 점검: 중앙 MSSQL 서버와의 통신이 단선되었거나 서버 전원이 꺼진 경우 타임아웃(3초)에 의해 경고가 표출됩니다. Database.ini 파일의 IP 설정 및 데이터베이스 서버 연결 상태를 점검하십시오.
+
계정 정보 확인: 입력한 사용자 ID 및 비밀번호가 MSSQL Users 테이블에 활성 상태(IsActive=1)로 등록되어 있는지 확인하십시오. (DB 접속 실패 시에도 LoginOptions.json 파일의 loginAccounts 배열에 등록된 계정으로 1차 인증을 진행할 수 있으므로, 필요시 해당 파일의 계정 설정도 함께 점검하십시오.)
diff --git a/marking_gui/Models/MarkingResult.cs b/marking_gui/Models/MarkingResult.cs
index 9734d8f..79f898c 100644
--- a/marking_gui/Models/MarkingResult.cs
+++ b/marking_gui/Models/MarkingResult.cs
@@ -24,12 +24,30 @@ namespace marking_gui.Models
///
public DateTime MarkedAt { get; set; }
+ ///
+ /// EZD 도안에서 파싱하여 사용된 도면품번
+ ///
+ public string DrawingNo { get; set; } = string.Empty;
+
+ ///
+ /// EZD 도안에서 파싱하여 사용된 HKMC품번
+ ///
+ public string HkmcNo { get; set; } = string.Empty;
+
+ ///
+ /// EZD 도안 품번 기반으로 최종 조합된 마킹/검증용 QR 코드 문자열
+ ///
+ public string QrString { get; set; } = string.Empty;
+
public MarkingResult()
{
Success = false;
ErrorCode = -1;
ErrorMessage = "Uninitialized";
MarkedAt = DateTime.MinValue;
+ DrawingNo = string.Empty;
+ HkmcNo = string.Empty;
+ QrString = string.Empty;
}
}
}
diff --git a/marking_gui/PortSettingsWindow.xaml b/marking_gui/PortSettingsWindow.xaml
index 82faaba..cac402c 100644
--- a/marking_gui/PortSettingsWindow.xaml
+++ b/marking_gui/PortSettingsWindow.xaml
@@ -105,7 +105,6 @@
-
diff --git a/marking_gui/PortSettingsWindow.xaml.cs b/marking_gui/PortSettingsWindow.xaml.cs
index a0cf7ff..230ee38 100644
--- a/marking_gui/PortSettingsWindow.xaml.cs
+++ b/marking_gui/PortSettingsWindow.xaml.cs
@@ -198,56 +198,6 @@ namespace marking_gui
}
}
- private void btnScanTest_Click(object sender, RoutedEventArgs e)
- {
- string scannerPort = cbScannerPort?.SelectedItem?.ToString() ?? "Auto";
- int scannerBaud = 115200;
- if (cbScannerBaudRate?.SelectedItem is ComboBoxItem selectedBaudItem && int.TryParse(selectedBaudItem.Content?.ToString(), out int parsedBaud))
- {
- scannerBaud = parsedBaud;
- }
-
- var tempSettings = new BarcodeScannerSettings
- {
- Enabled = true,
- PortName = scannerPort,
- BaudRate = scannerBaud
- };
-
- SerialBarcodeScanner tempScanner = null;
- try
- {
- try
- {
- tempScanner = new SerialBarcodeScanner(tempSettings);
- }
- catch { }
-
- string testSampleQr = "TEST_PORT_SCAN;DRAWING_01;HKMC_99;2607299999;";
- var qrTestWin = new QrVerificationWindow(testSampleQr, tempScanner)
- {
- Owner = this,
- Title = $"📷 [스캐너 포트 {scannerPort} / {scannerBaud} Baud] 연동 테스트"
- };
-
- bool? testResult = qrTestWin.ShowDialog();
-
- if (testResult == true)
- {
- MessageBox.Show($"스캐너 (포트: {scannerPort}) 통신 및 QR 대조 검증 성공!\n\n현재 선택한 포트로 스캐너가 정상 작동합니다.", "스캔 테스트 성공", MessageBoxButton.OK, MessageBoxImage.Information);
- }
- else
- {
- MessageBox.Show($"스캔 테스트가 중단되었거나 취소되었습니다.", "스캔 테스트 종료", MessageBoxButton.OK, MessageBoxImage.Information);
- }
- }
- finally
- {
- try { tempScanner?.Stop(); } catch { }
- try { tempScanner?.Dispose(); } catch { }
- }
- }
-
private void btnCancel_Click(object sender, RoutedEventArgs e)
{
DialogResult = false;
diff --git a/marking_gui/QrVerificationWindow.xaml.cs b/marking_gui/QrVerificationWindow.xaml.cs
index 90cf727..0fb22e3 100644
--- a/marking_gui/QrVerificationWindow.xaml.cs
+++ b/marking_gui/QrVerificationWindow.xaml.cs
@@ -206,6 +206,14 @@ namespace marking_gui
txtStatusMessage.Text = "QR 코드 검증 성공";
txtStatusMessage.Foreground = new SolidColorBrush(Color.FromRgb(22, 163, 74)); // Dark Green
}
+
+ // 합격 시 즉시 팝업 자동 닫기 (요청 사항: 합격은 자동 닫힘 유지)
+ if (!_isClosing)
+ {
+ _isClosing = true;
+ DialogResult = true;
+ Close();
+ }
}
else
{
@@ -238,14 +246,8 @@ namespace marking_gui
txtStatusMessage.Text = "QR 바코드 불일치";
txtStatusMessage.Foreground = new SolidColorBrush(Color.FromRgb(220, 38, 38)); // Red
}
- }
- // 합격이든 불합격이든 스캔 완료 즉시 팝업 닫기
- if (!_isClosing)
- {
- _isClosing = true;
- DialogResult = _isPassed;
- Close();
+ // 불합격(대조 실패) 시 팝업을 자동으로 닫지 않고 유지하여 계속 스캔 데이터 수신
}
}
diff --git a/marking_gui/Services/Board4251Service.cs b/marking_gui/Services/Board4251Service.cs
index 68196ba..ea0e97c 100644
--- a/marking_gui/Services/Board4251Service.cs
+++ b/marking_gui/Services/Board4251Service.cs
@@ -209,6 +209,11 @@ namespace marking_gui.Services
string currentData;
lock (_bufferLock)
{
+ // 수신 버퍼 8KB(8192자) 상한선 제한하여 메모리 누수 및 오버플로우 방지
+ if (_receiveBuffer.Length > 8192)
+ {
+ _receiveBuffer.Clear();
+ }
_receiveBuffer.Append(data);
currentData = _receiveBuffer.ToString();
}
@@ -235,6 +240,14 @@ namespace marking_gui.Services
public void Dispose()
{
+ try
+ {
+ if (_communication != null)
+ {
+ _communication.DataReceived -= OnDataReceived;
+ }
+ }
+ catch { }
Disconnect();
_reconnectTimer?.Dispose();
_lock.Dispose();
diff --git a/marking_gui/Services/EzdMarkingService.cs b/marking_gui/Services/EzdMarkingService.cs
index b2c7d6a..81cf7d5 100644
--- a/marking_gui/Services/EzdMarkingService.cs
+++ b/marking_gui/Services/EzdMarkingService.cs
@@ -1,6 +1,7 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
+using System.Threading.Tasks;
using marking_gui.Models;
namespace marking_gui.Services
@@ -38,15 +39,9 @@ namespace marking_gui.Services
[DllImport("MarkEzdWrapper.dll", EntryPoint = "ChangeTextByName", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
private static extern int ChangeTextByNameW(IntPtr hHandle, string strTextName, string strTextNew);
- [DllImport("MarkEzdWrapper.dll", EntryPoint = "ChangeTextByName", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
- private static extern int ChangeTextByNameA(IntPtr hHandle, string strTextName, string strTextNew);
-
[DllImport("MarkEzdWrapper.dll", EntryPoint = "GetTextByName", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
private static extern int GetTextByNameW(IntPtr hHandle, string strTextName, System.Text.StringBuilder strText);
- [DllImport("MarkEzdWrapper.dll", EntryPoint = "GetTextByName", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
- private static extern int GetTextByNameA(IntPtr hHandle, string strTextName, System.Text.StringBuilder strText);
-
[DllImport("MarkEzdWrapper.dll", EntryPoint = "Mark", CallingConvention = CallingConvention.Cdecl)]
private static extern int Mark(IntPtr hHandle);
@@ -61,6 +56,7 @@ namespace marking_gui.Services
private IntPtr _lastWindowHandle = IntPtr.Zero;
private string _lastConfigPath = string.Empty;
+ private string _lastLoadedEzdPath = string.Empty;
public bool Initialize(string configPath, IntPtr windowHandle)
{
@@ -68,92 +64,74 @@ namespace marking_gui.Services
{
try
{
- _lastConfigPath = configPath;
- if (windowHandle != IntPtr.Zero) _lastWindowHandle = windowHandle;
+ _lastConfigPath = configPath;
+ if (windowHandle != IntPtr.Zero) _lastWindowHandle = windowHandle;
- _lastInitErrorMessage = string.Empty;
- _config = new MarkingConfigService();
- _ezdFolderPath = _config.EzdFolderPath;
-
- if (string.IsNullOrEmpty(_ezdFolderPath))
- {
- _lastInitErrorMessage = "Setting.ini 내 EzdFolderPath 경로가 비어 있습니다.";
- _isInitialized = false;
- return false;
- }
+ _lastInitErrorMessage = string.Empty;
+ _config = new MarkingConfigService();
+ _ezdFolderPath = _config.EzdFolderPath;
- // EZCAD.CFG 파일 유무 검증 및 상위/하위 경로 자동 탐색
- string cfgFile = Path.Combine(_ezdFolderPath, "EZCAD.CFG");
- if (!File.Exists(cfgFile))
- {
- if (File.Exists(@"c:\EzCad\EZCAD.CFG"))
+ if (string.IsNullOrEmpty(_ezdFolderPath))
{
- _ezdFolderPath = @"c:\EzCad";
+ _lastInitErrorMessage = "Setting.ini 내 EzdFolderPath 경로가 비어 있습니다.";
+ _isInitialized = false;
+ return false;
}
- else if (File.Exists(@"c:\EzCad\EasyCfg\EZCAD.CFG"))
+
+ // EZCAD.CFG 파일 유무 검증 및 상위/하위 경로 자동 탐색
+ string cfgFile = Path.Combine(_ezdFolderPath, "EZCAD.CFG");
+ if (!File.Exists(cfgFile))
{
- _ezdFolderPath = @"c:\EzCad\EasyCfg";
+ if (File.Exists(@"c:\EzCad\EZCAD.CFG"))
+ {
+ _ezdFolderPath = @"c:\EzCad";
+ }
+ else if (File.Exists(@"c:\EzCad\EasyCfg\EZCAD.CFG"))
+ {
+ _ezdFolderPath = @"c:\EzCad\EasyCfg";
+ }
}
- }
-
- if (!Directory.Exists(_ezdFolderPath))
- {
- _lastInitErrorMessage = string.Format("EzCad 폴더가 존재하지 않습니다: {0} (Setting.ini 설정 확인 필요)", _ezdFolderPath);
- _isInitialized = false;
- return false;
- }
- // 기존 핸들이 열려 있다면 정리
- if (_markingHandle != IntPtr.Zero)
- {
- try { Close(_markingHandle); } catch { }
- _markingHandle = IntPtr.Zero;
- }
+ if (!Directory.Exists(_ezdFolderPath))
+ {
+ _lastInitErrorMessage = string.Format("마킹 라이브러리 폴더가 존재하지 않습니다: {0}", _ezdFolderPath);
+ _isInitialized = false;
+ return false;
+ }
- System.Diagnostics.Debug.WriteLine("[EzdMarking] 마킹 기계 LMC 초기화 시도. EzCadPath: " + _ezdFolderPath + ", WindowHandle: " + windowHandle);
-
- // 1. InitMarkEzd() 호출로 디바이스 전역 제어 핸들 획득
- _markingHandle = InitMarkEzd();
- System.Diagnostics.Debug.WriteLine(string.Format("[EzdMarking] InitMarkEzd() 완료. 생성된 핸들 포인터: 0x{0:X}", _markingHandle.ToInt64()));
+ if (_markingHandle == IntPtr.Zero)
+ {
+ _markingHandle = InitMarkEzd();
+ }
- if (_markingHandle == IntPtr.Zero)
- {
- _isInitialized = false;
- _lastInitErrorMessage = "InitMarkEzd() 실행 결과 생성된 핸들 포인터가 Zero(Null)입니다. 마킹 보드 및 드라이버 연결 상태를 점검하세요.";
- return false;
- }
+ if (_markingHandle == IntPtr.Zero)
+ {
+ _lastInitErrorMessage = "InitMarkEzd() 생성 실패 (IntPtr.Zero)";
+ _isInitialized = false;
+ return false;
+ }
- // 2. 획득된 핸들을 이용해 Init() 장치 매핑 수행
- int result = Init(_markingHandle, _ezdFolderPath, windowHandle);
-
- if (result == 0) // 0 이 성공 코드
- {
- _isInitialized = true;
- System.Diagnostics.Debug.WriteLine("[EzdMarking] 실서버 LMC 컨트롤 보드 장치 연결 및 초기화 성공.");
- }
- else
- {
- _isInitialized = false;
- _lastInitErrorMessage = string.Format("LMC 컨트롤 카드 통신 실패 (Init 에러코드: {0})", result);
- System.Diagnostics.Debug.WriteLine("[EzdMarking] LMC 장치 연결 실패. 에러 코드: " + result);
- try { Close(_markingHandle); } catch { }
- _markingHandle = IntPtr.Zero;
+ int result = Init(_markingHandle, _ezdFolderPath, windowHandle);
+ if (result == 0)
+ {
+ _isInitialized = true;
+ _lastLoadedEzdPath = string.Empty;
+ System.Diagnostics.Debug.WriteLine("[EzdMarking] 마킹기 연동 성공");
+ return true;
+ }
+ else
+ {
+ _lastInitErrorMessage = string.Format("Init() 실패 코드: {0} (경로: {1})", result, _ezdFolderPath);
+ try { Close(_markingHandle); } catch { }
+ _markingHandle = IntPtr.Zero;
+ _isInitialized = false;
+ return false;
+ }
}
-
- return _isInitialized;
- }
- catch (DllNotFoundException ex)
- {
- _isInitialized = false;
- _lastInitErrorMessage = "MarkEzdWrapper.dll 또는 하위 C++ DLL 로드 실패 (x86/32비트 아키텍처 타겟팅 컴파일 여부 확인 필요)";
- System.Diagnostics.Debug.WriteLine("[EzdMarking] MarkEzdWrapper.dll 또는 하위 DLL(Lmc1.dll 등)을 로드할 수 없습니다. 실행 폴더에 복사했는지 확인하세요. 상세: " + ex.Message);
- return false;
- }
catch (Exception ex)
{
+ _lastInitErrorMessage = "초기화 도중 예외: " + ex.Message;
_isInitialized = false;
- _lastInitErrorMessage = "마킹 컨트롤 보드 연동 예외 발생: " + ex.Message;
- System.Diagnostics.Debug.WriteLine("[EzdMarking] 초기화 예외 발생: " + ex.Message);
return false;
}
}
@@ -163,167 +141,177 @@ namespace marking_gui.Services
[System.Security.SecurityCritical]
public MarkingResult RequestMarking(MarkingRequest request)
{
- if (!_isInitialized || _markingHandle == IntPtr.Zero)
- {
- // 각인 요청 시 미초기화 상태이면 1회 자동 재시도(Retry) 수행
- System.Diagnostics.Debug.WriteLine("[EzdMarking] 디바이스 미초기화 상태 감지. 재연결 시도...");
- Initialize(_lastConfigPath, _lastWindowHandle);
- }
-
- if (!_isInitialized || _markingHandle == IntPtr.Zero)
- {
- return new MarkingResult
- {
- Success = false,
- ErrorCode = -10,
- ErrorMessage = "LMC 마킹 디바이스가 연결 및 초기화되지 않았습니다. 상세 원인: " + _lastInitErrorMessage,
- MarkedAt = DateTime.Now
- };
- }
-
- try
+ lock (_handleLock)
{
- // 1. 도안 파일 경로 확인
- string ezdFile = request.EzdFileName;
- string ezdPath = ezdFile;
-
- if (!File.Exists(ezdPath))
+ if (!_isInitialized || _markingHandle == IntPtr.Zero)
{
- ezdPath = Path.Combine(_ezdFolderPath, ezdFile);
+ System.Diagnostics.Debug.WriteLine("[EzdMarking] 디바이스 미초기화 상태 감지. 재연결 시도...");
+ Initialize(_lastConfigPath, _lastWindowHandle);
}
- if (!File.Exists(ezdPath))
+ if (!_isInitialized || _markingHandle == IntPtr.Zero)
{
- ezdPath = Path.Combine(_ezdFolderPath, "EasyCfg", ezdFile);
- }
-
- if (!File.Exists(ezdPath))
- {
- // 최종 확인 실패 시 에러
return new MarkingResult
{
Success = false,
- ErrorCode = -11,
- ErrorMessage = string.Format("도안 파일(.ezd)이 존재하지 않습니다: {0}", ezdFile),
+ ErrorCode = -10,
+ ErrorMessage = "LMC 마킹 디바이스가 연결 및 초기화되지 않았습니다. 상세 원인: " + _lastInitErrorMessage,
MarkedAt = DateTime.Now
};
}
- System.Diagnostics.Debug.WriteLine("[EzdMarking] 도안 파일 로드 시작: " + ezdPath);
- string absoluteEzdPath = Path.GetFullPath(ezdPath);
- int loadResult = LoadEzdFile(_markingHandle, absoluteEzdPath); // 획득한 핸들 전달
- if (loadResult != 0)
+ try
{
- return new MarkingResult
+ string ezdFile = request.EzdFileName;
+ string ezdPath = ezdFile;
+
+ if (!File.Exists(ezdPath)) ezdPath = Path.Combine(_ezdFolderPath, ezdFile);
+ if (!File.Exists(ezdPath)) ezdPath = Path.Combine(_ezdFolderPath, "EasyCfg", ezdFile);
+
+ if (!File.Exists(ezdPath))
{
- Success = false,
- ErrorCode = loadResult,
- ErrorMessage = string.Format("도안 파일(.ezd) 로드 실패. (경로: {0}, LoadEzdFile 에러코드: {1})", absoluteEzdPath, loadResult),
- MarkedAt = DateTime.Now
- };
- }
+ return new MarkingResult
+ {
+ Success = false,
+ ErrorCode = -11,
+ ErrorMessage = string.Format("도안 파일(.ezd)이 존재하지 않습니다: {0}", ezdFile),
+ MarkedAt = DateTime.Now
+ };
+ }
- // 1-1. 도안 파일(.ezd) 내부의 고정 텍스트 오브젝트(도면품번, HKMC 품번)를 직접 읽어오기
- string ezdDrawingNo = GetTextFromEzd(_config.DrawingNoTag);
- string ezdHkmcNo = GetTextFromEzd(_config.HkmcNoTag);
+ string absoluteEzdPath = Path.GetFullPath(ezdPath);
- if (!string.IsNullOrWhiteSpace(ezdDrawingNo))
- {
- request.DrawingNo = ezdDrawingNo;
- }
- if (!string.IsNullOrWhiteSpace(ezdHkmcNo))
- {
- request.HkmcNo = ezdHkmcNo;
- }
+ if (!string.Equals(_lastLoadedEzdPath, absoluteEzdPath, StringComparison.OrdinalIgnoreCase))
+ {
+ System.Diagnostics.Debug.WriteLine("[EzdMarking] 도안 파일 신규 로드: " + absoluteEzdPath);
+ int loadResult = LoadEzdFile(_markingHandle, absoluteEzdPath);
+ if (loadResult != 0)
+ {
+ _lastLoadedEzdPath = string.Empty;
+ return new MarkingResult
+ {
+ Success = false,
+ ErrorCode = loadResult,
+ ErrorMessage = string.Format("도안 파일(.ezd) 로드 실패. (경로: {0}, LoadEzdFile 에러코드: {1})", absoluteEzdPath, loadResult),
+ MarkedAt = DateTime.Now
+ };
+ }
+ _lastLoadedEzdPath = absoluteEzdPath;
+ }
- // EZD 고정값 기반으로 QR 코드 데이터 문자열 최종 재조합 ([IC S/N];[도면품번];[HKMC품번];[제품 S/N];)
- string icSerial = string.IsNullOrWhiteSpace(request.IcSerial) ? request.Serial : request.IcSerial;
- request.QrString = string.Format("{0};{1};{2};{3};", icSerial, request.DrawingNo, request.HkmcNo, request.Serial);
+ string ezdDrawingNo = GetTextFromEzd(_config.DrawingNoTag, absoluteEzdPath);
+ string ezdHkmcNo = GetTextFromEzd(_config.HkmcNoTag, absoluteEzdPath);
- // 2. 도안 내 지정된 5개 변수 태그만 치환 (SERIAL, YYMMDD, DATAMATRIX, PARTNO, HKMCPO)
- string seqOnly = (!string.IsNullOrEmpty(request.Serial) && request.Serial.Length >= 4)
- ? request.Serial.Substring(request.Serial.Length - 4)
- : request.Serial;
+ if (string.IsNullOrWhiteSpace(ezdDrawingNo) && _config != null)
+ ezdDrawingNo = _config.GetDrawingNoForEzd(absoluteEzdPath);
+ if (string.IsNullOrWhiteSpace(ezdHkmcNo) && _config != null)
+ ezdHkmcNo = _config.GetHkmcNoForEzd(absoluteEzdPath);
- ChangeTextInEzd(_config.SerialTag, seqOnly); // 1. SERIAL (4자리 순번)
- ChangeTextInEzd(_config.LotNoTag, request.LotNo); // 2. YYMMDD (LotNo)
- ChangeTextInEzd(_config.QrCodeTag, request.QrString); // 3. DATAMATRIX (QR코드)
- ChangeTextInEzd(_config.DrawingNoTag, request.DrawingNo); // 4. PARTNO (도면품번)
- ChangeTextInEzd(_config.HkmcNoTag, request.HkmcNo); // 5. HKMCPO (HKMC품번)
+ if (!string.IsNullOrWhiteSpace(ezdDrawingNo)) request.DrawingNo = ezdDrawingNo;
+ if (!string.IsNullOrWhiteSpace(ezdHkmcNo)) request.HkmcNo = ezdHkmcNo;
- System.Diagnostics.Debug.WriteLine(string.Format(
- "[EzdMarking] 도안 텍스트 치환 완료. Serial:{0}, LotNo:{1}, Product:{2}, Drawing:{3}, Hkmc:{4}, Qr:{5}",
- request.Serial, request.LotNo, request.ProductNo, request.DrawingNo, request.HkmcNo, request.QrString));
+ string icSerial = string.IsNullOrWhiteSpace(request.IcSerial) ? request.Serial : request.IcSerial;
+ request.QrString = string.Format("{0};{1};{2};{3};", icSerial, request.DrawingNo, request.HkmcNo, request.Serial);
- // 3. 레이저 마킹 시작 (획득한 핸들 전달)
- System.Diagnostics.Debug.WriteLine("[EzdMarking] 레이저 마킹 기계 동작 시작...");
- int markResult = Mark(_markingHandle);
+ string seqOnly = (!string.IsNullOrEmpty(request.Serial) && request.Serial.Length >= 4)
+ ? request.Serial.Substring(request.Serial.Length - 4)
+ : request.Serial;
- if (markResult == 0)
- {
- System.Diagnostics.Debug.WriteLine("[EzdMarking] 마킹 기계 동작 완료 (성공)");
- return new MarkingResult
+ ChangeTextInEzd(_config.SerialTag, seqOnly);
+ ChangeTextInEzd(_config.LotNoTag, request.LotNo);
+ ChangeTextInEzd(_config.QrCodeTag, request.QrString);
+ if (!string.IsNullOrWhiteSpace(request.DrawingNo)) ChangeTextInEzd(_config.DrawingNoTag, request.DrawingNo);
+ if (!string.IsNullOrWhiteSpace(request.HkmcNo)) ChangeTextInEzd(_config.HkmcNoTag, request.HkmcNo);
+
+ System.Diagnostics.Debug.WriteLine("[EzdMarking] 레이저 마킹 기계 동작 시작 (30초 타임아웃 제한)...");
+ IntPtr handleToMark = _markingHandle;
+ var markingTask = Task.Run(() => Mark(handleToMark));
+ var timeoutTask = Task.Delay(30000);
+
+ var completedTask = Task.WhenAny(markingTask, timeoutTask).Result;
+
+ if (completedTask == timeoutTask)
{
- Success = true,
- ErrorCode = 0,
- ErrorMessage = "마킹 완료",
- MarkedAt = DateTime.Now
- };
- }
- else
- {
- System.Diagnostics.Debug.WriteLine("[EzdMarking] 마킹 기계 동작 에러 발생: " + markResult);
- return new MarkingResult
+ _isInitialized = false;
+ _markingHandle = IntPtr.Zero;
+ _lastLoadedEzdPath = string.Empty;
+ return new MarkingResult { Success = false, ErrorCode = -98, ErrorMessage = "레이저 각인 하드웨어 응답 타임아웃 (30초)", MarkedAt = DateTime.Now };
+ }
+
+ int markResult = markingTask.Result;
+ if (markResult == 0)
{
- Success = false,
- ErrorCode = markResult,
- ErrorMessage = string.Format("레이저 각인 하드웨어 동작 에러 (에러코드: {0})", markResult),
- MarkedAt = DateTime.Now
- };
+ return new MarkingResult
+ {
+ Success = true,
+ ErrorCode = 0,
+ ErrorMessage = "마킹 완료",
+ MarkedAt = DateTime.Now,
+ DrawingNo = request.DrawingNo,
+ HkmcNo = request.HkmcNo,
+ QrString = request.QrString
+ };
+ }
+ else
+ {
+ _isInitialized = false;
+ _markingHandle = IntPtr.Zero;
+ _lastLoadedEzdPath = string.Empty;
+ return new MarkingResult { Success = false, ErrorCode = markResult, ErrorMessage = string.Format("레이저 각인 하드웨어 동작 에러 (에러코드: {0})", markResult), MarkedAt = DateTime.Now };
+ }
}
- }
- catch (Exception ex)
- {
- System.Diagnostics.Debug.WriteLine("[EzdMarking] RequestMarking 실행 중 예외 발생: " + ex.Message);
- return new MarkingResult
+ catch (Exception ex)
{
- Success = false,
- ErrorCode = -99,
- ErrorMessage = "마킹 처리 중 예외 발생: " + ex.Message,
- MarkedAt = DateTime.Now
- };
+ _isInitialized = false;
+ _markingHandle = IntPtr.Zero;
+ _lastLoadedEzdPath = string.Empty;
+ return new MarkingResult { Success = false, ErrorCode = -99, ErrorMessage = "마킹 처리 중 예외 발생: " + ex.Message, MarkedAt = DateTime.Now };
+ }
}
}
private bool _disposed = false;
- private string GetTextFromEzd(string tagName)
+ public string GetTextFromEzd(string tagName, string ezdFilePath = null)
{
- if (string.IsNullOrWhiteSpace(tagName) || _markingHandle == IntPtr.Zero)
- return string.Empty;
+ if (string.IsNullOrWhiteSpace(tagName)) return string.Empty;
- try
+ // 1단계: P/Invoke GetTextByNameW 호출 (DLL 지원 시 도안 텍스트 읽기)
+ if (_markingHandle != IntPtr.Zero)
{
- // 1차: Unicode (UTF-16) 시도
- var sbW = new System.Text.StringBuilder(512);
- int resW = GetTextByNameW(_markingHandle, tagName, sbW);
- if (resW == 0 && sbW.Length > 0)
+ try
{
- return sbW.ToString().Trim();
+ var sbW = new System.Text.StringBuilder(512);
+ int resW = GetTextByNameW(_markingHandle, tagName, sbW);
+ if (resW == 0 && sbW.Length > 0)
+ {
+ string val = sbW.ToString().Trim();
+ if (!string.IsNullOrWhiteSpace(val)) return val;
+ }
}
-
- // 2차: Ansi (CP949/EUC-KR) 시도 (C++ Wrapper가 ANSI인 경우 대비)
- var sbA = new System.Text.StringBuilder(512);
- int resA = GetTextByNameA(_markingHandle, tagName, sbA);
- if (resA == 0 && sbA.Length > 0)
+ catch (EntryPointNotFoundException)
+ {
+ System.Diagnostics.Debug.WriteLine($"[EzdMarking] GetTextByName({tagName}) DLL 내 엔트리포인트 없음.");
+ }
+ catch (Exception ex)
{
- return sbA.ToString().Trim();
+ System.Diagnostics.Debug.WriteLine($"[EzdMarking] GetTextByName({tagName}) 읽기 예외: {ex.Message}");
}
}
- catch (Exception ex)
+
+ // 2단계: Setting.ini / MarkingConfigService 사용자 명시 설정값 참조
+ if (_config != null)
{
- System.Diagnostics.Debug.WriteLine($"[EzdMarking] GetTextByName({tagName}) 읽기 예외: {ex.Message}");
+ if (tagName.Equals(_config.DrawingNoTag, StringComparison.OrdinalIgnoreCase))
+ {
+ return _config.GetDrawingNoForEzd(ezdFilePath);
+ }
+ if (tagName.Equals(_config.HkmcNoTag, StringComparison.OrdinalIgnoreCase))
+ {
+ return _config.GetHkmcNoForEzd(ezdFilePath);
+ }
}
+
return string.Empty;
}
@@ -332,11 +320,7 @@ namespace marking_gui.Services
if (string.IsNullOrWhiteSpace(tagName) || _markingHandle == IntPtr.Zero) return;
try
{
- int resW = ChangeTextByNameW(_markingHandle, tagName, textNew);
- if (resW != 0)
- {
- ChangeTextByNameA(_markingHandle, tagName, textNew);
- }
+ ChangeTextByNameW(_markingHandle, tagName, textNew);
}
catch
{
diff --git a/marking_gui/Services/MarkingConfigService.cs b/marking_gui/Services/MarkingConfigService.cs
index e6a55fd..b18fd45 100644
--- a/marking_gui/Services/MarkingConfigService.cs
+++ b/marking_gui/Services/MarkingConfigService.cs
@@ -122,6 +122,30 @@ namespace marking_gui.Services
public string DrawingNo => GetSetting("DrawingNo", string.Empty);
public string HkmcNo => GetSetting("HkmcNo", string.Empty);
+ public string GetDrawingNoForEzd(string ezdFileName)
+ {
+ if (!string.IsNullOrWhiteSpace(ezdFileName))
+ {
+ string fileNameOnly = System.IO.Path.GetFileName(ezdFileName);
+ string key = "DrawingNo_" + fileNameOnly;
+ string val = GetSetting(key, string.Empty);
+ if (!string.IsNullOrWhiteSpace(val)) return val;
+ }
+ return DrawingNo;
+ }
+
+ public string GetHkmcNoForEzd(string ezdFileName)
+ {
+ if (!string.IsNullOrWhiteSpace(ezdFileName))
+ {
+ string fileNameOnly = System.IO.Path.GetFileName(ezdFileName);
+ string key = "HkmcNo_" + fileNameOnly;
+ string val = GetSetting(key, string.Empty);
+ if (!string.IsNullOrWhiteSpace(val)) return val;
+ }
+ return HkmcNo;
+ }
+
public bool IsTestMode
{
get
diff --git a/marking_gui/Services/SerialBarcodeScanner.cs b/marking_gui/Services/SerialBarcodeScanner.cs
index 3c51632..cd356ab 100644
--- a/marking_gui/Services/SerialBarcodeScanner.cs
+++ b/marking_gui/Services/SerialBarcodeScanner.cs
@@ -67,12 +67,23 @@ namespace marking_gui.Services
private readonly BarcodeScannerSettings _settings;
private readonly HashSet _excludedPortNames;
private readonly List _sessions = new List();
+ private readonly object _sessionsLock = new object();
+ private Timer? _autoReconnectTimer;
private string? _confirmedPortName;
private bool _disposed;
public event EventHandler? ScanReceived;
- public bool IsRunning => _sessions.Count > 0;
+ public bool IsRunning
+ {
+ get
+ {
+ lock (_sessionsLock)
+ {
+ return _sessions.Any(s => s.Port.IsOpen);
+ }
+ }
+ }
public string? ConfirmedPortName => _confirmedPortName;
public SerialBarcodeScanner(BarcodeScannerSettings settings, IEnumerable? excludedPortNames = null)
@@ -87,6 +98,17 @@ namespace marking_gui.Services
Stop();
+ lock (_sessionsLock)
+ {
+ OpenCandidatePorts();
+ }
+
+ // 5초 주기로 닫힌 포트 자동 재연결 모니터링 타이머 가동
+ _autoReconnectTimer ??= new Timer(_ => AutoReconnectCallback(), null, 5000, 5000);
+ }
+
+ private void OpenCandidatePorts()
+ {
var candidatePorts = GetCandidatePortNames();
if (candidatePorts.Length == 0)
{
@@ -96,6 +118,9 @@ namespace marking_gui.Services
foreach (var portName in candidatePorts)
{
+ if (_sessions.Any(s => string.Equals(s.Port.PortName, portName, StringComparison.OrdinalIgnoreCase)))
+ continue;
+
try
{
var port = CreateSerialPort(portName);
@@ -117,19 +142,59 @@ namespace marking_gui.Services
}
}
- public void Stop()
+ private void AutoReconnectCallback()
{
- foreach (var session in _sessions)
+ if (_disposed || !_settings.Enabled) return;
+
+ lock (_sessionsLock)
{
try
{
- session.Port.DataReceived -= SerialPort_DataReceived;
- session.Dispose();
+ // 닫히거나 훼손된 포트 세션 정리
+ for (int i = _sessions.Count - 1; i >= 0; i--)
+ {
+ var session = _sessions[i];
+ if (!session.Port.IsOpen)
+ {
+ try
+ {
+ session.Port.DataReceived -= SerialPort_DataReceived;
+ session.Dispose();
+ }
+ catch { }
+ _sessions.RemoveAt(i);
+ }
+ }
+
+ // 닫힌 포트가 발견되었을 때만 후보 포트 재오픈 시도
+ OpenCandidatePorts();
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"[SerialBarcodeScanner] AutoReconnect 예외: {ex.Message}");
}
- catch { }
}
- _sessions.Clear();
- _confirmedPortName = null;
+ }
+
+ public void Stop()
+ {
+ _autoReconnectTimer?.Dispose();
+ _autoReconnectTimer = null;
+
+ lock (_sessionsLock)
+ {
+ foreach (var session in _sessions)
+ {
+ try
+ {
+ session.Port.DataReceived -= SerialPort_DataReceived;
+ session.Dispose();
+ }
+ catch { }
+ }
+ _sessions.Clear();
+ _confirmedPortName = null;
+ }
}
public void ConfirmPort(string portName)
@@ -141,19 +206,22 @@ namespace marking_gui.Services
_confirmedPortName = normalized;
- // 포트가 확정되면 다른 미사용 테스트 포트 세션 해제
- for (int i = _sessions.Count - 1; i >= 0; i--)
+ lock (_sessionsLock)
{
- var session = _sessions[i];
- if (!string.Equals(session.Port.PortName, normalized, StringComparison.OrdinalIgnoreCase))
+ // 포트가 확정되면 다른 미사용 테스트 포트 세션 해제
+ for (int i = _sessions.Count - 1; i >= 0; i--)
{
- try
+ var session = _sessions[i];
+ if (!string.Equals(session.Port.PortName, normalized, StringComparison.OrdinalIgnoreCase))
{
- session.Port.DataReceived -= SerialPort_DataReceived;
- session.Dispose();
+ try
+ {
+ session.Port.DataReceived -= SerialPort_DataReceived;
+ session.Dispose();
+ }
+ catch { }
+ _sessions.RemoveAt(i);
}
- catch { }
- _sessions.RemoveAt(i);
}
}
}
@@ -212,7 +280,11 @@ namespace marking_gui.Services
{
if (sender is not SerialPort port) return;
- var session = _sessions.FirstOrDefault(s => ReferenceEquals(s.Port, port));
+ PortSession? session = null;
+ lock (_sessionsLock)
+ {
+ session = _sessions.FirstOrDefault(s => ReferenceEquals(s.Port, port));
+ }
if (session == null) return;
try
@@ -222,6 +294,11 @@ namespace marking_gui.Services
lock (session.Buffer)
{
+ // 수신 버퍼 4KB(4096자) 상한선 제한하여 메모리 누수 방지
+ if (session.Buffer.Length > 4096)
+ {
+ session.Buffer.Clear();
+ }
session.Buffer.Append(text);
}
@@ -248,7 +325,14 @@ namespace marking_gui.Services
bool isConfirmed = string.Equals(_confirmedPortName, session.Port.PortName, StringComparison.OrdinalIgnoreCase);
- ScanReceived?.Invoke(this, new BarcodeScanReceivedEventArgs(session.Port.PortName, scanText, isConfirmed));
+ try
+ {
+ ScanReceived?.Invoke(this, new BarcodeScanReceivedEventArgs(session.Port.PortName, scanText, isConfirmed));
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"[ScanReceived Event Error] {ex.Message}");
+ }
}
public void Dispose()
diff --git a/marking_gui/Setting.ini b/marking_gui/Setting.ini
index 6e81465..5312958 100644
--- a/marking_gui/Setting.ini
+++ b/marking_gui/Setting.ini
@@ -9,10 +9,8 @@ EzdFolderPath=c:\EzCad
; 테스트 모드 여부 (true 시 하단 테스트 마킹, 강제 검사 버튼 노출 반대 false)
test=true
-; 도면품번 (EZD 도안의 도면품번 태그에 채워질 값 — DrawingNoTag에 매핑)
+; 도면품번 및 HKMC품번 지정 (사용자가 설정)
DrawingNo=APTSL04A
-
-; HKMC 품번 (EZD 도안의 HKMC 품번 태그에 채워질 값 — HkmcNoTag에 매핑)
HkmcNo=254H1-QI080
@@ -36,6 +34,7 @@ DrawingNoTag=PARTNO
HkmcNoTag=HKMCPO
+
; =================================================================
; 계측기 연동 설정 (SCPI / LAN)
; =================================================================
diff --git a/marking_gui/SettingsWindow.xaml.cs b/marking_gui/SettingsWindow.xaml.cs
index 208a28b..1c3ab66 100644
--- a/marking_gui/SettingsWindow.xaml.cs
+++ b/marking_gui/SettingsWindow.xaml.cs
@@ -17,8 +17,8 @@ namespace marking_gui
Limits = new MeasurementLimits(limits.MinVoltage, limits.MaxVoltage, limits.MinCurrent, limits.MaxCurrent, limits.MinResistance, limits.MaxResistance);
- txtMinVoltage.Text = Limits.MinVoltage.ToString("F1");
- txtMaxVoltage.Text = Limits.MaxVoltage.ToString("F1");
+ txtMinVoltage.Text = Limits.MinVoltage.ToString("F2");
+ txtMaxVoltage.Text = Limits.MaxVoltage.ToString("F2");
txtMinCurrent.Text = Limits.MinCurrent.ToString("F2");
txtMaxCurrent.Text = Limits.MaxCurrent.ToString("F2");
txtMinResistance.Text = Limits.MinResistance.ToString("F3");
diff --git a/test_bukuk.ezd b/test_bukuk.ezd
new file mode 100644
index 0000000..79258c9
Binary files /dev/null and b/test_bukuk.ezd differ