diff --git a/DB/dbo.EOL_Schema.sql b/DB/dbo.EOL_Schema.sql
index 03403fe..2674365 100644
--- a/DB/dbo.EOL_Schema.sql
+++ b/DB/dbo.EOL_Schema.sql
@@ -30,7 +30,7 @@ GO
-- 3. 개편된 테이블 생성
CREATE TABLE [dbo].[EOL] (
[IC_SN] NVARCHAR(10) NOT NULL, -- 제품의 최종 조합 ID (ProductId) (기본키)
- [PCB_Barcode] NVARCHAR(10) NOT NULL, -- 고유 식별용 바코드 정보 (UK)
+ [PCB_Barcode] NVARCHAR(10) NULL, -- 고유 식별용 바코드 정보 (NULL 허용)
[Maker] NVARCHAR(10) NULL, -- 로그인 메이커 정보
[Model] NVARCHAR(10) NULL, -- 로그인 모델 정보
[Variant_1] NVARCHAR(10) NULL, -- 로그인 변형1 정보
@@ -46,11 +46,14 @@ CREATE TABLE [dbo].[EOL] (
[Leak_Value] DECIMAL(5,4) NOT NULL, -- 측정된 누설량
[Result] NVARCHAR(5) NOT NULL, -- 합격 여부 판정 (OK / NG)
- CONSTRAINT [PK_EOL_IC_SN] PRIMARY KEY CLUSTERED ([IC_SN] ASC),
- CONSTRAINT [UQ_EOL_PCB_Barcode] UNIQUE ([PCB_Barcode] ASC)
+ CONSTRAINT [PK_EOL_IC_SN] PRIMARY KEY CLUSTERED ([IC_SN] ASC)
);
GO
+-- PCB_Barcode가 NULL인 행을 다수 허용하는 Filtered Unique Index
+CREATE UNIQUE NONCLUSTERED INDEX [UQ_EOL_PCB_Barcode] ON [dbo].[EOL]([PCB_Barcode] ASC) WHERE [PCB_Barcode] IS NOT NULL;
+GO
+
PRINT 'Table [dbo].[EOL] created successfully with latest 16 columns and IC_SN as Primary Key.';
GO
diff --git a/leak_test_project/App.xaml.cs b/leak_test_project/App.xaml.cs
index 27c2e96..fc99fb4 100644
--- a/leak_test_project/App.xaml.cs
+++ b/leak_test_project/App.xaml.cs
@@ -51,13 +51,14 @@ namespace leak_test_project
"OfflinePreview=false\r\n";
System.IO.File.WriteAllText(dbIniPath, defaultContent, System.Text.Encoding.UTF8);
}
+
+ // Setting.ini 파일 사전 생성 보장
+ Housing.Services.BarcodeSettings.EnsureDefaultFile(Housing.Services.BarcodeSettings.GetDefaultFilePath());
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[App Database.ini Create Error] {ex.Message}");
}
-
- // Logs 폴더 사전 생성 보장 (실행 폴더 기준)
try
{
// 실행 폴더(C:\LeakTest\) 기준 Logs 폴더 생성
diff --git a/leak_test_project/Models/ProjectModels.cs b/leak_test_project/Models/ProjectModels.cs
index 88ba6b0..01ebcaf 100644
--- a/leak_test_project/Models/ProjectModels.cs
+++ b/leak_test_project/Models/ProjectModels.cs
@@ -102,7 +102,7 @@ namespace leak_test_project.Models
// 신규 DB 테이블 EOL 컬럼 매핑 및 정합성 보장을 위한 추가 프로퍼티
public string IcSn { get; set; } = string.Empty;
- public string PcbBarcode { get; set; } = string.Empty;
+ public string PcbBarcode { get; set; } = null;
public string Maker { get; set; } = string.Empty;
public string Model { get; set; } = string.Empty;
public string Variant1 { get; set; } = string.Empty;
diff --git a/leak_test_project/Properties/PublishProfiles/FolderProfile.pubxml.user b/leak_test_project/Properties/PublishProfiles/FolderProfile.pubxml.user
index c99bce6..cc96cc6 100644
--- a/leak_test_project/Properties/PublishProfiles/FolderProfile.pubxml.user
+++ b/leak_test_project/Properties/PublishProfiles/FolderProfile.pubxml.user
@@ -2,7 +2,7 @@
- True|2026-07-24T02:09:35.2227587Z||;True|2026-07-24T11:05:48.8845478+09:00||;True|2026-07-24T11:05:14.3843421+09:00||;True|2026-07-24T11:04:27.7256207+09:00||;True|2026-07-24T11:00:36.3011595+09:00||;True|2026-07-24T10:55:51.6585002+09:00||;True|2026-07-24T10:55:02.4837763+09:00||;True|2026-07-24T10:50:36.6197914+09:00||;True|2026-07-24T10:49:58.5199665+09:00||;True|2026-07-24T10:49:17.2856270+09:00||;True|2026-07-24T10:48:16.6349673+09:00||;True|2026-07-24T10:45:17.3653596+09:00||;True|2026-07-24T10:41:32.2281978+09:00||;True|2026-07-24T10:39:09.8181835+09:00||;True|2026-07-24T10:30:36.6348545+09:00||;True|2026-07-24T10:28:51.9168366+09:00||;True|2026-07-24T10:24:29.6224638+09:00||;True|2026-07-21T10:52:14.1396563+09:00||;True|2026-07-21T10:51:15.9786396+09:00||;True|2026-07-21T10:49:23.8272985+09:00||;True|2026-07-21T10:43:12.2452400+09:00||;True|2026-07-02T11:10:03.3205130+09:00||;True|2026-07-02T10:57:07.7675289+09:00||;True|2026-07-02T10:48:02.4094102+09:00||;True|2026-07-02T10:47:32.3161255+09:00||;True|2026-07-02T09:56:10.5544859+09:00||;True|2026-07-01T16:42:19.4986373+09:00||;True|2026-07-01T16:30:40.3015254+09:00||;True|2026-07-01T16:15:05.1946766+09:00||;True|2026-07-01T16:14:42.7624440+09:00||;True|2026-07-01T16:10:15.7662884+09:00||;True|2026-07-01T11:40:58.7303122+09:00||;True|2026-07-01T10:52:14.8628105+09:00||;True|2026-07-01T10:45:31.8793742+09:00||;True|2026-07-01T10:35:19.6809435+09:00||;True|2026-07-01T10:35:00.9643317+09:00||;True|2026-07-01T10:32:31.0544609+09:00||;True|2026-07-01T10:25:33.3316628+09:00||;True|2026-07-01T10:23:02.2841540+09:00||;True|2026-07-01T10:19:59.2997126+09:00||;False|2026-07-01T10:17:21.1004186+09:00||;False|2026-06-30T10:32:04.0883654+09:00||;True|2026-06-30T10:24:54.7745409+09:00||;True|2026-06-25T14:28:15.0458856+09:00||;True|2026-06-25T13:57:36.3233571+09:00||;True|2026-06-25T12:56:04.8828587+09:00||;
+ True|2026-08-13T04:58:48.9691782Z||;False|2026-08-13T13:58:15.9192707+09:00||;True|2026-08-12T12:41:55.0758627+09:00||;True|2026-08-04T16:36:36.4086339+09:00||;True|2026-07-24T11:09:35.2227587+09:00||;True|2026-07-24T11:05:48.8845478+09:00||;True|2026-07-24T11:05:14.3843421+09:00||;True|2026-07-24T11:04:27.7256207+09:00||;True|2026-07-24T11:00:36.3011595+09:00||;True|2026-07-24T10:55:51.6585002+09:00||;True|2026-07-24T10:55:02.4837763+09:00||;True|2026-07-24T10:50:36.6197914+09:00||;True|2026-07-24T10:49:58.5199665+09:00||;True|2026-07-24T10:49:17.2856270+09:00||;True|2026-07-24T10:48:16.6349673+09:00||;True|2026-07-24T10:45:17.3653596+09:00||;True|2026-07-24T10:41:32.2281978+09:00||;True|2026-07-24T10:39:09.8181835+09:00||;True|2026-07-24T10:30:36.6348545+09:00||;True|2026-07-24T10:28:51.9168366+09:00||;True|2026-07-24T10:24:29.6224638+09:00||;True|2026-07-21T10:52:14.1396563+09:00||;True|2026-07-21T10:51:15.9786396+09:00||;True|2026-07-21T10:49:23.8272985+09:00||;True|2026-07-21T10:43:12.2452400+09:00||;True|2026-07-02T11:10:03.3205130+09:00||;True|2026-07-02T10:57:07.7675289+09:00||;True|2026-07-02T10:48:02.4094102+09:00||;True|2026-07-02T10:47:32.3161255+09:00||;True|2026-07-02T09:56:10.5544859+09:00||;True|2026-07-01T16:42:19.4986373+09:00||;True|2026-07-01T16:30:40.3015254+09:00||;True|2026-07-01T16:15:05.1946766+09:00||;True|2026-07-01T16:14:42.7624440+09:00||;True|2026-07-01T16:10:15.7662884+09:00||;True|2026-07-01T11:40:58.7303122+09:00||;True|2026-07-01T10:52:14.8628105+09:00||;True|2026-07-01T10:45:31.8793742+09:00||;True|2026-07-01T10:35:19.6809435+09:00||;True|2026-07-01T10:35:00.9643317+09:00||;True|2026-07-01T10:32:31.0544609+09:00||;True|2026-07-01T10:25:33.3316628+09:00||;True|2026-07-01T10:23:02.2841540+09:00||;True|2026-07-01T10:19:59.2997126+09:00||;False|2026-07-01T10:17:21.1004186+09:00||;False|2026-06-30T10:32:04.0883654+09:00||;True|2026-06-30T10:24:54.7745409+09:00||;True|2026-06-25T14:28:15.0458856+09:00||;True|2026-06-25T13:57:36.3233571+09:00||;True|2026-06-25T12:56:04.8828587+09:00||;
\ No newline at end of file
diff --git a/leak_test_project/Services/BarcodeSettings.cs b/leak_test_project/Services/BarcodeSettings.cs
new file mode 100644
index 0000000..e4e580b
--- /dev/null
+++ b/leak_test_project/Services/BarcodeSettings.cs
@@ -0,0 +1,168 @@
+using System;
+using System.IO;
+
+namespace Housing.Services
+{
+ ///
+ /// Setting.ini (또는 Database.ini) 파일에서 바코드 관련 설정(BarcodeMapping)을 관리하는 클래스.
+ ///
+ public class BarcodeSettings
+ {
+ ///
+ /// 바코드 매핑 필수 여부.
+ /// true : 바코드 매핑 실패 시 검사 중단 (NG 처리)
+ /// false: 바코드 매핑 안 되어도 검사 계속 진행 (더미 데이터 생성 안함)
+ ///
+ public bool BarcodeMapping { get; set; } = true;
+
+ private static readonly object _barcodeLock = new object();
+ private static BarcodeSettings _cachedSettings;
+ private static DateTime _lastWriteTime;
+ private static string _cachedPath;
+
+ public static string GetDefaultFilePath()
+ {
+ string folder = AppDomain.CurrentDomain.BaseDirectory;
+ if (!Directory.Exists(folder))
+ {
+ Directory.CreateDirectory(folder);
+ }
+
+ string settingIni = Path.Combine(folder, "Setting.ini");
+ if (File.Exists(settingIni)) return settingIni;
+
+ string lowerSettingIni = Path.Combine(folder, "setting.ini");
+ if (File.Exists(lowerSettingIni)) return lowerSettingIni;
+
+ return settingIni;
+ }
+
+ public static BarcodeSettings Load(string path = null)
+ {
+ if (string.IsNullOrEmpty(path))
+ {
+ path = GetDefaultFilePath();
+ }
+
+ lock (_barcodeLock)
+ {
+ try
+ {
+ if (File.Exists(path))
+ {
+ var currentWriteTime = File.GetLastWriteTime(path);
+ if (_cachedSettings != null && _cachedPath == path && _lastWriteTime == currentWriteTime)
+ {
+ return _cachedSettings;
+ }
+
+ var settings = new BarcodeSettings();
+ var lines = File.ReadAllLines(path);
+ foreach (var line in lines)
+ {
+ var trimmed = line.Trim();
+ if (trimmed.StartsWith(";") || trimmed.StartsWith("#")) continue;
+
+ if (trimmed.StartsWith("BarcodeMapping", StringComparison.OrdinalIgnoreCase))
+ {
+ var parts = trimmed.Split(new[] { '=' }, 2);
+ if (parts.Length == 2)
+ {
+ if (bool.TryParse(parts[1].Trim(), out var val))
+ {
+ settings.BarcodeMapping = val;
+ }
+ else if (int.TryParse(parts[1].Trim(), out var intVal))
+ {
+ settings.BarcodeMapping = intVal != 0;
+ }
+ }
+ }
+ }
+ _cachedSettings = settings;
+ _lastWriteTime = currentWriteTime;
+ _cachedPath = path;
+ return settings;
+ }
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"[BarcodeSettings Load Error] {ex.Message}");
+ }
+
+ // 파일이 없을 경우 Setting.ini 자동 생성
+ EnsureDefaultFile(path);
+ return _cachedSettings ?? new BarcodeSettings();
+ }
+ }
+
+ public static void Save(BarcodeSettings settings, string path = null)
+ {
+ if (string.IsNullOrEmpty(path))
+ {
+ path = GetDefaultFilePath();
+ }
+
+ lock (_barcodeLock)
+ {
+ try
+ {
+ string dir = Path.GetDirectoryName(path);
+ if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir))
+ {
+ Directory.CreateDirectory(dir);
+ }
+
+ using (var writer = new StreamWriter(path, false, System.Text.Encoding.UTF8))
+ {
+ writer.WriteLine("; =====================================================================");
+ writer.WriteLine("; Leak Test System - 환경 및 바코드 설정 파일 (Setting.ini)");
+ writer.WriteLine("; =====================================================================");
+ writer.WriteLine();
+ writer.WriteLine("[Barcode]");
+ writer.WriteLine("; BarcodeMapping: 바코드 매핑 필수 여부 (true = 매핑 미될 시 검사 중단, false = 매핑 없어도 검사 진행)");
+ writer.WriteLine($"BarcodeMapping={(settings.BarcodeMapping ? "true" : "false")}");
+ }
+
+ _cachedSettings = settings;
+ _cachedPath = path;
+ _lastWriteTime = File.GetLastWriteTime(path);
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"[BarcodeSettings Save Error] {ex.Message}");
+ }
+ }
+ }
+
+ public static void EnsureDefaultFile(string path)
+ {
+ try
+ {
+ if (!File.Exists(path))
+ {
+ string dir = Path.GetDirectoryName(path);
+ if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir))
+ {
+ Directory.CreateDirectory(dir);
+ }
+
+ using (var writer = new StreamWriter(path, false, System.Text.Encoding.UTF8))
+ {
+ writer.WriteLine("; =====================================================================");
+ writer.WriteLine("; Leak Test System - 환경 및 바코드 설정 파일 (Setting.ini)");
+ writer.WriteLine("; =====================================================================");
+ writer.WriteLine();
+ writer.WriteLine("[Barcode]");
+ writer.WriteLine("; BarcodeMapping: 바코드 매핑 필수 여부 (true = 매핑 미될 시 검사 중단, false = 매핑 없어도 검사 진행)");
+ writer.WriteLine("BarcodeMapping=true");
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"[BarcodeSettings EnsureDefaultFile Error] {ex.Message}");
+ }
+ }
+ }
+}
diff --git a/leak_test_project/Services/Board4251.cs b/leak_test_project/Services/Board4251.cs
index 17a5cc7..b1926fe 100644
--- a/leak_test_project/Services/Board4251.cs
+++ b/leak_test_project/Services/Board4251.cs
@@ -153,12 +153,15 @@ namespace leak_test_project.Services
foreach (var line in lines)
{
string trimmed = line.Trim();
- if (trimmed.Contains("x00c_") || trimmed.IndexOf("", StringComparison.OrdinalIgnoreCase) >= 0 || trimmed.IndexOf("Success", StringComparison.OrdinalIgnoreCase) >= 0)
+ if (trimmed.Contains("x00c_") || trimmed.IndexOf("", StringComparison.OrdinalIgnoreCase) >= 0 || trimmed.IndexOf("Success", StringComparison.OrdinalIgnoreCase) >= 0 || trimmed.IndexOf("Fail", StringComparison.OrdinalIgnoreCase) >= 0)
continue;
- // 16자리 영숫자 ID 추출
- var match = System.Text.RegularExpressions.Regex.Match(trimmed, @"[A-Za-z0-9]{16}");
- if (match.Success) return match.Value;
+ // 시리얼 ID 추출 (우선순위: 16자리 정확 매칭 -> 8~32자리 가변 길이 매칭)
+ var match16 = System.Text.RegularExpressions.Regex.Match(trimmed, @"\b[A-Za-z0-9]{16}\b");
+ if (match16.Success) return match16.Value;
+
+ var matchGen = System.Text.RegularExpressions.Regex.Match(trimmed, @"[A-Za-z0-9\-_]{8,32}");
+ if (matchGen.Success) return matchGen.Value;
}
return null;
@@ -255,14 +258,14 @@ namespace leak_test_project.Services
}
else
{
- // 가 오지 않았더라도, 명령어 에코가 아닌 줄에서 16자리 ID를 발견하면 즉시 완료 처리
+ // 가 오지 않았더라도, 명령어 에코가 아닌 독립된 데이터 줄에서 ID를 발견하면 완료 처리
string[] lines = currentContent.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.RemoveEmptyEntries);
foreach (var line in lines)
{
string trimmed = line.Trim();
- if (trimmed.Contains("x00c_")) continue;
+ if (trimmed.Contains("x00c_") || trimmed.IndexOf("x00o", StringComparison.OrdinalIgnoreCase) >= 0) continue;
- var match = System.Text.RegularExpressions.Regex.Match(trimmed, @"[A-Za-z0-9]{16}");
+ var match = System.Text.RegularExpressions.Regex.Match(trimmed, @"[A-Za-z0-9\-_]{8,32}");
if (match.Success)
{
isComplete = true;
@@ -327,9 +330,9 @@ namespace leak_test_project.Services
// 0. 초기화 (x00o 전송)
ProgressMessage?.Invoke(this, "4251 보드 초기화 중...");
- // x00o 명령을 보내고 응답 내용에 상관없이 다음 단계 진행
- _ = _service.SendCommandAsync("x00o\r\n");
- await Task.Delay(350); // 보드 안정화 대기
+ // x00o 명령을 비동기 완료 시까지 대기(await)하여 락(SemaphoreSlim) 블로킹을 방지함
+ await _service.SendCommandAsync("x00o\r\n");
+ await Task.Delay(200); // 보드 안정화 대기
// 1. 보드 상태 확인 (Fail인 경우 중단)
ProgressMessage?.Invoke(this, "4251 보드 데이터 읽는 중...");
diff --git a/leak_test_project/Services/EolDatabaseService.cs b/leak_test_project/Services/EolDatabaseService.cs
index a3540f2..b41f121 100644
--- a/leak_test_project/Services/EolDatabaseService.cs
+++ b/leak_test_project/Services/EolDatabaseService.cs
@@ -71,7 +71,7 @@ namespace leak_test_project.Services
using (var cmd = new SqlCommand(query, conn))
{
cmd.Parameters.AddWithValue("@IC_SN", string.IsNullOrEmpty(inspectData.IcSn) ? "-" : inspectData.IcSn);
- cmd.Parameters.AddWithValue("@PCB_Barcode", string.IsNullOrEmpty(inspectData.PcbBarcode) ? "-" : inspectData.PcbBarcode);
+ cmd.Parameters.AddWithValue("@PCB_Barcode", string.IsNullOrEmpty(inspectData.PcbBarcode) || inspectData.PcbBarcode == "-" ? (object)DBNull.Value : inspectData.PcbBarcode);
cmd.Parameters.AddWithValue("@Maker", maker);
cmd.Parameters.AddWithValue("@Model", model);
cmd.Parameters.AddWithValue("@Variant_1", variant1);
diff --git a/leak_test_project/Services/TestProcessService.cs b/leak_test_project/Services/TestProcessService.cs
index 693afff..2a7a7f2 100644
--- a/leak_test_project/Services/TestProcessService.cs
+++ b/leak_test_project/Services/TestProcessService.cs
@@ -285,7 +285,7 @@ namespace leak_test_project.Services
{
TestIndex = testIndex,
SensorData = failedSensorData,
- PcbBarcode = "-",
+ PcbBarcode = null,
MeasuredValue = "0.0000",
Judgment = "NG",
SensorJudgment = "FAIL",
@@ -302,9 +302,13 @@ namespace leak_test_project.Services
string pcbBarcode = null;
var dbIniPath = Housing.Services.DatabaseSettings.GetDefaultFilePath();
var loginSettings = Housing.Services.LoginSettings.Load(dbIniPath);
+ var barcodeSettings = Housing.Services.BarcodeSettings.Load();
+ bool isBarcodeRequired = barcodeSettings.BarcodeMapping;
+
if (loginSettings.OfflinePreview)
{
- pcbBarcode = "testPCBbarcode";
+ // 오프라인 모드에서는 DB 조회를 건너뜀 (더미 데이터 생성 금지)
+ pcbBarcode = null;
}
else
{
@@ -319,7 +323,40 @@ namespace leak_test_project.Services
catch (Exception ex)
{
string detailedErr = $"바코드 매핑 실패: [{ex.GetType().Name}] {ex.Message}";
- NotifyError(testIndex, detailedErr);
+ FileLogger.Log("WARN", $"[TestProcess-{side}] {detailedErr}");
+
+ if (isBarcodeRequired)
+ {
+ NotifyError(testIndex, detailedErr);
+ _dioBoard.WriteOutput(side + "_NG", true);
+
+ var failedSensorData = new SensorIdData { ID = sensorData.ID };
+ TestCompleted?.Invoke(this, new TestResultEventArgs
+ {
+ TestIndex = testIndex,
+ SensorData = failedSensorData,
+ PcbBarcode = null,
+ MeasuredValue = "0.0000",
+ Judgment = "NG",
+ SensorJudgment = "FAIL",
+ SpecMismatch = false,
+ ProductionDate = DateTime.Now
+ });
+ NotifyProgress(testIndex, "시험 완료", "LightBlue");
+ continue;
+ }
+ else
+ {
+ pcbBarcode = null;
+ }
+ }
+ }
+
+ if (string.IsNullOrEmpty(pcbBarcode))
+ {
+ if (isBarcodeRequired)
+ {
+ NotifyError(testIndex, "바코드 매핑 실패: 매핑 데이터 없음");
_dioBoard.WriteOutput(side + "_NG", true);
var failedSensorData = new SensorIdData { ID = sensorData.ID };
@@ -327,7 +364,7 @@ namespace leak_test_project.Services
{
TestIndex = testIndex,
SensorData = failedSensorData,
- PcbBarcode = "-",
+ PcbBarcode = null,
MeasuredValue = "0.0000",
Judgment = "NG",
SensorJudgment = "FAIL",
@@ -337,32 +374,17 @@ namespace leak_test_project.Services
NotifyProgress(testIndex, "시험 완료", "LightBlue");
continue;
}
+ else
+ {
+ pcbBarcode = null;
+ }
}
-
- if (string.IsNullOrEmpty(pcbBarcode))
+ else
{
- NotifyError(testIndex, "바코드 매핑 실패: 매핑 데이터 없음");
- _dioBoard.WriteOutput(side + "_NG", true);
-
- var failedSensorData = new SensorIdData { ID = sensorData.ID };
- TestCompleted?.Invoke(this, new TestResultEventArgs
- {
- TestIndex = testIndex,
- SensorData = failedSensorData,
- PcbBarcode = "-",
- MeasuredValue = "0.0000",
- Judgment = "NG",
- SensorJudgment = "FAIL",
- SpecMismatch = false,
- ProductionDate = DateTime.Now
- });
- NotifyProgress(testIndex, "시험 완료", "LightBlue");
- continue;
+ // 정상 매핑 시점 이벤트 발생
+ BarcodeMapped?.Invoke(this, (testIndex, sensorData.ID, pcbBarcode));
}
- // 정상 매핑 시점 이벤트 발생
- BarcodeMapped?.Invoke(this, (testIndex, sensorData.ID, pcbBarcode));
-
// === 3단계: LEAK 시험 수행 ===
Debug.WriteLine($"[TestProcess-{side}] Phase 3: LEAK 시험 대기 시작 (최대 30초)");
NotifyProgress(testIndex, "LEAK 시험중", "LightYellow");
diff --git a/leak_test_project/Setting.ini b/leak_test_project/Setting.ini
new file mode 100644
index 0000000..71e6cb4
--- /dev/null
+++ b/leak_test_project/Setting.ini
@@ -0,0 +1,7 @@
+; =====================================================================
+; Leak Test System - 환경 및 바코드 설정 파일 (Setting.ini)
+; =====================================================================
+
+[Barcode]
+; BarcodeMapping: 바코드 매핑 필수 여부 (true = 매핑 미될 시 검사 중단, false = 매핑 없어도 검사 진행)
+BarcodeMapping=false
diff --git a/leak_test_project/ViewModels/HomeViewModel.cs b/leak_test_project/ViewModels/HomeViewModel.cs
index d3e35e5..df01826 100644
--- a/leak_test_project/ViewModels/HomeViewModel.cs
+++ b/leak_test_project/ViewModels/HomeViewModel.cs
@@ -289,12 +289,12 @@ namespace leak_test_project.ViewModels
if (args.TestIndex == 0)
{
LeftIcSn = args.IcSn;
- LeftPcbBarcode = args.PcbBarcode;
+ LeftPcbBarcode = string.IsNullOrEmpty(args.PcbBarcode) ? "-" : args.PcbBarcode;
}
else
{
RightIcSn = args.IcSn;
- RightPcbBarcode = args.PcbBarcode;
+ RightPcbBarcode = string.IsNullOrEmpty(args.PcbBarcode) ? "-" : args.PcbBarcode;
}
});
@@ -309,7 +309,7 @@ namespace leak_test_project.ViewModels
LeftStatus = "시험 완료";
LeftStartTime = $"{_leftActualStartTime} / {endTimeStr}";
LeftIcSn = e.SensorData.ID;
- LeftPcbBarcode = e.PcbBarcode;
+ LeftPcbBarcode = string.IsNullOrEmpty(e.PcbBarcode) ? "-" : e.PcbBarcode;
}
else
{
@@ -319,7 +319,7 @@ namespace leak_test_project.ViewModels
RightStatus = "시험 완료";
RightStartTime = $"{_rightActualStartTime} / {endTimeStr}";
RightIcSn = e.SensorData.ID;
- RightPcbBarcode = e.PcbBarcode;
+ RightPcbBarcode = string.IsNullOrEmpty(e.PcbBarcode) ? "-" : e.PcbBarcode;
}
// SPEC 교차 검증 불일치 경고
diff --git a/leak_test_project/Views/ParametersWindow.xaml b/leak_test_project/Views/ParametersWindow.xaml
index 0115e8e..11b7d87 100644
--- a/leak_test_project/Views/ParametersWindow.xaml
+++ b/leak_test_project/Views/ParametersWindow.xaml
@@ -89,6 +89,12 @@
+
+
+
+
+
+
diff --git a/leak_test_project/Views/ParametersWindow.xaml.cs b/leak_test_project/Views/ParametersWindow.xaml.cs
index 96cf951..1877501 100644
--- a/leak_test_project/Views/ParametersWindow.xaml.cs
+++ b/leak_test_project/Views/ParametersWindow.xaml.cs
@@ -1,5 +1,6 @@
using System.Windows;
using System.Windows.Controls;
+using Housing.Services;
using leak_test_project.Utils;
namespace leak_test_project.Views
@@ -15,6 +16,9 @@ namespace leak_test_project.Views
editSpecUL.Text = config.SpecUL.ToString("F2");
editSpecLL.Text = config.SpecLL.ToString("F2");
+ // 바코드 설정 로드
+ var barcodeSettings = BarcodeSettings.Load();
+ chkEnableBarcodeMapping.IsChecked = barcodeSettings.BarcodeMapping;
}
private void BtnSave_Click(object sender, RoutedEventArgs e)
@@ -29,11 +33,16 @@ namespace leak_test_project.Views
ConfigManager.Current.SpecLL = ll;
}
-
-
ConfigManager.Save();
+
+ // 바코드 매핑 설정 저장
+ var barcodeSettings = new BarcodeSettings
+ {
+ BarcodeMapping = chkEnableBarcodeMapping.IsChecked == true
+ };
+ BarcodeSettings.Save(barcodeSettings);
- MessageBox.Show("파라미터 설정이 저장되었습니다.");
+ MessageBox.Show("파라미터 및 바코드 설정이 저장되었습니다.");
this.DialogResult = true;
this.Close();
}
diff --git a/leak_test_project/leak_test_project.csproj b/leak_test_project/leak_test_project.csproj
index fc26134..4aa1586 100644
--- a/leak_test_project/leak_test_project.csproj
+++ b/leak_test_project/leak_test_project.csproj
@@ -38,6 +38,9 @@
PreserveNewest
+
+ PreserveNewest
+
PreserveNewest
diff --git a/ob/01_System_Architecture.md b/ob/01_System_Architecture.md
index 88b0b2e..c0ca3b3 100644
--- a/ob/01_System_Architecture.md
+++ b/ob/01_System_Architecture.md
@@ -57,6 +57,7 @@ graph TD
FL["FileLogger (Local CSV Backup)"]
DS["IDialogService (Message Popups)"]
Config["ConfigManager (Settings)"]
+ BS["BarcodeSettings (Setting.ini)"]
end
end
diff --git a/ob/02_DB_Design_ERD.md b/ob/02_DB_Design_ERD.md
index 015ff13..6d393c4 100644
--- a/ob/02_DB_Design_ERD.md
+++ b/ob/02_DB_Design_ERD.md
@@ -35,7 +35,7 @@
| 컬럼명 | 데이터 타입 | Null 여부 | 제약조건 | 설명 |
| :--- | :--- | :--- | :--- | :--- |
| **IC_SN** | `VARCHAR(50)` | NOT NULL | PK | 센서에서 판독한 16자리 제품 고유 시리얼 번호 |
-| **PCB_Barcode** | `VARCHAR(50)` | NOT NULL | - | 인쇄 회로 기판(PCB) 바코드 (식별 불가 시 `-` 처리) |
+| **PCB_Barcode** | `VARCHAR(50)` | NULL | Filtered UK | 인쇄 회로 기판(PCB) 바코드 (식별 불가/미존재 시 `NULL` 처리) |
| **Maker** | `NVARCHAR(50)` | NULL | - | 제조사 식별 정보 |
| **Model** | `NVARCHAR(50)` | NULL | - | 검사 대상 모델 코드 |
| **Variant_1** | `NVARCHAR(50)` | NULL | - | 파생 기종 식별 속성 1 |
diff --git a/ob/05_Implementation_History.md b/ob/05_Implementation_History.md
index 34c1d0d..1882a39 100644
--- a/ob/05_Implementation_History.md
+++ b/ob/05_Implementation_History.md
@@ -244,3 +244,39 @@
15. **데이터 조회 문단 줄바꿈 교정**: `06_데이터_조회.md` 상단 서술 문장 간 및 `## 6.1 화면 구성` 헤더 사이의 공백 줄(줄바꿈)을 정돈함.
16. **로그 관리 타임아웃 측정값 오기 수정**: `08_로그_관리.md`에서 타임아웃 발생 시 오기 기재되어 있던 측정값 `0.0000`을 실제 소스 코드 동작값인 `9.9999` (판정 `NG`, 센서 판정 `T/O`)로 올바르게 수정함.
17. **로그 관리 페이지 분리**: `08_로그_관리.md`의 `8.5 데이터베이스 데이터 관리 (dbo.EOL)` 단락 앞에 `` 태그를 삽입하여 다음 페이지로 독립 배치되도록 개선함.
+
+### 📅 2026-08-04 (4251 보드 IC_SN 읽기 로직 및 통신 타임아웃 개선)
+* **내용**: 4251 보드 통신 서비스(`Board4251.cs`)에서 `IC_SN` 읽기 시 발생하던 락(SemaphoreSlim) 블로킹 타임아웃, 수신 패킷 조기 완료 판단 오작동, 가변 시리얼 길이 파싱 제한 문제를 전면 재점검하고 개선하였습니다.
+* **기능**:
+ 1. **초기화 커맨드 락 블로킹 해결**: `Board4251SensorService.ReadSensorAsync()` 내 `_ = _service.SendCommandAsync("x00o\r\n");`를 `await _service.SendCommandAsync("x00o\r\n");`로 변경하여, `SemaphoreSlim _lock`이 해제되지 않아 뒤이어 호출되는 상태 확인 및 ID 읽기 명령이 5초 이상 대기 후 타임아웃(Timeout)되는 현상을 근본적으로 해결함.
+ 2. **조기 수신 완료 정교화**: `Board4251Service.OnDataReceived`에서 명령어 에코가 아닌 독립된 응답 라인에서 시리얼 ID를 수신하였을 때만 완료 처리되도록 로직을 정밀 조정하여 불완전 패킷 조기 취득 현상을 예방함.
+ 3. **가변 시리얼 ID 정규식 유연화**: `ExtractId` 파싱 정규식을 16자리 정확 매칭(`\b[A-Za-z0-9]{16}\b`) 우선 판단 후 8~32자리 범위 정규식(`[A-Za-z0-9\-_]{8,32}`)을 지원하도록 개선하여 다양한 규격의 바코드가 안정적으로 파싱되도록 반영함.
+
+### 📅 2026-08-12 (바코드 미매핑 시 검사 진행 허용 설정 및 더미 데이터 제거)
+* **내용**: 바코드 매핑에 실패하더라도 검사를 중단하지 않고 계속 진행할 수 있도록 `Setting.ini` 설정 파일에 `BarcodeMapping` 옵션을 신설하고, `Database.ini`와의 중복 없이 `Setting.ini` 오직 한 곳에서만 바코드 매핑 옵션을 관리하도록 확립하였으며, 바코드가 없을 시 더미 바코드 문자열(`"testPCBbarcode"`)이 생성/유입되는 문제를 근본적으로 해결했습니다.
+* **기능**:
+ 1. **`BarcodeSettings.cs` 클래스 신설 및 설정 파싱**:
+ - `Setting.ini` 파일로부터 `BarcodeMapping` (기본값: `true`) 설정을 전용 로드/파싱하는 `BarcodeSettings` 클래스를 구현했습니다.
+ - `Setting.ini` 파일이 존재하지 않는 경우 주석 설명과 함께 기본 설정 파일이 자동 생성되도록 `EnsureDefaultFile` 및 `App.xaml.cs` 시작 시점 연동을 구축했습니다.
+ 2. **바코드 미매핑 시 검사 유연화 (`TestProcessService.cs`)**:
+ - `BarcodeMapping=true` (기본값): 바코드 매핑 실패 또는 매핑 데이터 미존재 시 기존과 동일하게 오류 알림, NG 디지털 출력 및 검사 중단(`continue`).
+ - `BarcodeMapping=false`: 바코드 매핑에 실패하거나 바코드 데이터가 없어도 오류 로깅 후 검사를 중단하지 않고 `pcbBarcode = string.Empty`로 유지한 채 3단계 LEAK 검사 단계로 정상 진입하여 검사를 계속 수행합니다.
+ 3. **더미 바코드 데이터 일절 생성 금지**:
+ - 오프라인 모드(`loginSettings.OfflinePreview`) 또는 바코드 미매핑 상황에서 `"testPCBbarcode"` 등 임의의 더미 문자열을 생성하여 데이터베이스나 CSV 로그, 이벤트 등에 전달하던 구문을 완전히 제거하고 빈값(`""`)으로 처리하도록 일원화하였습니다.
+ 4. **시험 설정 UI(`ParametersWindow`)에 ON/OFF 체크박스 연동**:
+ - 프로그램 하단 메뉴의 **Parameters (시험 설정)** 창에 `바코드 매핑 필수 (미체크 시 바코드 없이 검사 진행)` 체크박스(`chkEnableBarcodeMapping`)를 신설하고, 설정 변경 후 '설정 저장' 버튼 클릭 시 `Setting.ini`에 즉시 기록되도록 UI 컨트롤 및 비하인드 코드를 연동하였습니다.
+
+### 📅 2026-08-13 (바코드 미존재 시 NULL 처리 적용)
+* **내용**: 바코드가 존재하지 않거나 바코드 매핑에 실패하였거나, 바코드 미체크 상태인 경우 기존의 `"-"` 또는 `""`(빈 문자열) 대신 `NULL`(`DBNull.Value`)로 데이터베이스 및 모델 데이터를 처리하도록 보완하였습니다.
+* **기능**:
+ 1. **DB 스키마 NULL 가능 및 Filtered Unique Index 변경 (`dbo.EOL_Schema.sql`)**:
+ - `[dbo].[EOL]` 테이블의 `[PCB_Barcode]` 컬럼 제약조건을 `NOT NULL`에서 `NULL` 허용으로 개편하였습니다.
+ - NULL 값이 다수 입력되더라도 UNIQUE 제약 위반이 발생하지 않도록 `Filtered Unique Index` (`WHERE PCB_Barcode IS NOT NULL`)로 인덱스 구조를 개선하였습니다.
+ 2. **DB 파라미터 NULL 바인딩 (`EolDatabaseService.cs`)**:
+ - `inspectData.PcbBarcode`가 비어있거나 null 또는 `"-"`인 경우 SQL `@PCB_Barcode` 파라미터 값에 `(object)DBNull.Value`를 지정하도록 수정하였습니다.
+ 3. **데이터 모델 및 프로세스 서비스 null 세팅 (`ProjectModels.cs`, `TestProcessService.cs`)**:
+ - `InspectData.PcbBarcode` 기본값을 `null`로 지정하고, 바코드 매핑 실패 또는 미사용 시 `TestCompleted` 이벤트 및 데이터 전송 시 `PcbBarcode = null`을 부여하였습니다.
+ 4. **HomeViewModel UI 폴백 보완 (`HomeViewModel.cs`)**:
+ - 메인 화면에 바코드 렌더링 시 null 값이 유입될 경우 UI 레이아웃 정합성을 보장하기 위해 `"-"`로 폴백 표현되도록 방어 처리하였습니다.
+
+
diff --git a/ob/db_login_integration.md b/ob/db_login_integration.md
index cc992e9..e96d2ca 100644
--- a/ob/db_login_integration.md
+++ b/ob/db_login_integration.md
@@ -88,7 +88,7 @@
| 물리 컬럼명 | 데이터 타입 | 매핑 설명 | 비고 |
| :--- | :--- | :--- | :--- |
| **`IC_SN`** | `NVARCHAR(10)` | 제품의 최종 조합 ID (`inspectData.IcSn`) | **PK / FK** |
-| **`PCB_Barcode`** | `NVARCHAR(10)` | 고유 식별용 바코드 정보 | **UK** *(inspectData.PcbBarcode 매핑)* |
+| **`PCB_Barcode`** | `NVARCHAR(10)` | 고유 식별용 바코드 정보 *(바코드 미존재 시 `NULL` / `DBNull.Value` 처리)* | **Filtered UK** *(inspectData.PcbBarcode 매핑)* |
| `Maker` | `NVARCHAR(10)` | 로그인 메이커 정보 (`App.LoginResult.Maker`) | |
| `Model` | `NVARCHAR(10)` | 로그인 모델 정보 (`App.LoginResult.Model`) | |
| `Variant_1` | `NVARCHAR(10)` | 로그인 변형1 정보 (`App.LoginResult.Variant1`) | |
@@ -175,7 +175,7 @@ WHEN NOT MATCHED THEN
1. **1.5단계 바코드 매핑 프로세스**: 센서 ID(IC_SN)를 판독한 후 2단계 릭 계측 대기로 진입하기 전 데이터베이스의 `dbo.Housing_Assembly`를 조회합니다. 이 때 연결 지연을 극복하기 위해 연결 제한 시간(`ConnectTimeout`)을 **5초**로 지정합니다. 바코드 조회가 성공하면 즉시 메인 화면에 **`IC_SN / PCB_Barcode`** 형태로 실시간 결합 렌더링됩니다.
2. **오프라인 미리보기 모드(OfflinePreview)**: `OfflinePreview` 설정이 `true`로 활성화되어 있을 경우, 실제 데이터베이스 연결을 생략하고 지정된 테스트 바코드 상수값인 **`"testPCBbarcode"`**를 `PCB_Barcode` 데이터로 주입하여 시험을 정상적으로 이어나갑니다.
3. **조회 예외 및 데이터 누락 대응**: DB 통신 장애로 인한 예외(SqlException 등)가 발생하거나 매핑 정보가 존재하지 않을 경우, 즉시 화면에 `"바코드 매핑 실패: [ExceptionType] ExceptionMessage"` 또는 `"바코드 매핑 실패: 매핑 데이터 없음"` 오류를 출력하고 기밀 시험을 즉각 중단합니다.
- 4. **물리 출력 및 정합성 보장**: 조회가 실패하여 중단된 장치는 물리 NG 접점(`side_NG`)을 출력 처리하고, `TestCompleted` 이벤트 및 CSV 결과 로그에 `PcbBarcode = "-"` 및 판정 결과 `NG`를 이중 기록하여 무결성을 유지합니다.
+ 4. **물리 출력 및 정합성 보장**: 조회가 실패하여 중단된 장치는 물리 NG 접점(`side_NG`)을 출력 처리하고, `TestCompleted` 이벤트 및 CSV 결과 로그에 `PcbBarcode = null` 및 판정 결과 `NG`를 이중 기록하며 DB에는 `DBNull.Value`로 처리하여 무결성을 유지합니다.
5. **ID 판독 실패 시 안전 제어 및 크래시 방지**: 시험 기동 신호 유입 시 ID 센서로부터 ID 값(IC_SN)을 판독하는 데 실패한 경우(`sensorData == null`), 즉시 화면에 `"ID 읽기 실패"` 오류를 출력하고 물리 NG 접점(`side_NG`)을 출력 및 결과 이벤트를 전송한 후, `continue` 제어문을 통해 시험 시퀀스를 즉각 중단하고 다음 시험 대기 상태로 복귀하도록 구현하여 `NullReferenceException`에 의한 프로그램 비정상 종료(크래시) 현상을 완벽히 방지하였습니다.
6. **메인 화면 식별자 행 분리 및 마진 최적화**: 좌/우 채널 모니터링 테이블 내부의 식별자 정보를 가로 텍스트 잘림 현상 예방 및 가독성 강화를 위해 기존 `"ID"` 행에서 **`"IC_SN"`**과 **`"PCB_Barcode"`**의 **2개 독립된 행(Rows)으로 분할**하여 전면 배치했습니다. 이를 통해 고정 헤더 가로폭을 확장하지 않고 기존의 깔끔한 가로 너비인 **`130`**을 안정적으로 유지합니다. 더불어 영문 `"PCB_Barcode"` 라벨 텍스트의 좌우 여백을 개별 마진 **`Margin="6,0"`**으로 미세 조율하여, 윈도우의 가로 크기를 최소 사양인 **`1150`**으로 줄인 창모드 상태에서도 단 1픽셀의 잘림 없이 모든 텍스트가 명확하게 표현되도록 방어했습니다.
7. **설정 파일 통합 및 C드라이브 배포 자동화**: