From 92a602072be119f6bf0d7b4e3dfb0f4e1527432b Mon Sep 17 00:00:00 2001 From: seuuung0 Date: Thu, 13 Aug 2026 14:41:55 +0900 Subject: [PATCH] =?UTF-8?q?260813=20=EC=95=84=EB=AA=A8=EC=84=BC=EC=8A=A4?= =?UTF-8?q?=20=EB=82=A9=ED=92=88(PCB=20=EB=B0=94=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=97=86=EC=9D=B4=20=EA=B2=80=EC=82=AC=20=EA=B0=80=EB=8A=A5)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DB/dbo.EOL_Schema.sql | 9 +- leak_test_project/App.xaml.cs | 5 +- leak_test_project/Models/ProjectModels.cs | 2 +- .../PublishProfiles/FolderProfile.pubxml.user | 2 +- leak_test_project/Services/BarcodeSettings.cs | 168 ++++++++++++++++++ leak_test_project/Services/Board4251.cs | 23 +-- .../Services/EolDatabaseService.cs | 2 +- .../Services/TestProcessService.cs | 74 +++++--- leak_test_project/Setting.ini | 7 + leak_test_project/ViewModels/HomeViewModel.cs | 8 +- leak_test_project/Views/ParametersWindow.xaml | 6 + .../Views/ParametersWindow.xaml.cs | 15 +- leak_test_project/leak_test_project.csproj | 3 + ob/01_System_Architecture.md | 1 + ob/02_DB_Design_ERD.md | 2 +- ob/05_Implementation_History.md | 36 ++++ ob/db_login_integration.md | 4 +- 17 files changed, 313 insertions(+), 54 deletions(-) create mode 100644 leak_test_project/Services/BarcodeSettings.cs create mode 100644 leak_test_project/Setting.ini 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드라이브 배포 자동화**: